summaryrefslogtreecommitdiff
path: root/reencode_compactwsifunder.go
blob: 2349104b949f3e2195817b927d8338e674c91895 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// Copyright (C) 2022-2023  Luke Shumaker <lukeshu@lukeshu.com>
//
// SPDX-License-Identifier: GPL-2.0-or-later

package lowmemjson

import (
	"bytes"

	"git.lukeshu.com/go/lowmemjson/internal/jsonparse"
)

type reEncodeCompactWSIfUnder struct {
	out reEncoderModule

	// CompactWSIfUnder runs uses reEncodeCompactWScauses for
	// individual elements if doing so would cause that element to
	// be under this number of bytes.
	//
	// This has O(2^min(CompactWSIfUnder, depth)) time overhead,
	// so set with caution.
	CompactWSIfUnder int

	// state
	compactor        reEncodeWrite
	compacted        bytes.Buffer
	full             []handleRuneCall
	endWhenStackSize int
}

var _ reEncoderModule = (*reEncodeCompactWSIfUnder)(nil)

type handleRuneCall struct {
	c         rune
	t         jsonparse.RuneType
	escape    BackslashEscapeMode
	stackSize int
}

func (enc *reEncodeCompactWSIfUnder) reset() {
	enc.compactor = reEncodeWrite{}
	enc.compacted.Reset()
	enc.full = enc.full[:0]
	enc.endWhenStackSize = 0
}

func (enc *reEncodeCompactWSIfUnder) PopWriteBarrier() {
	enc.out.PopWriteBarrier()
}

func (enc *reEncodeCompactWSIfUnder) HandleRune(c rune, t jsonparse.RuneType, escape BackslashEscapeMode, stackSize int) error {
	if enc.compactor.out == nil { // not speculating
		switch t {
		case jsonparse.RuneTypeObjectBeg, jsonparse.RuneTypeArrayBeg: // start speculating
			enc.endWhenStackSize = stackSize - 1
			enc.compactor = reEncodeWrite{
				out: &enc.compacted,
			}
			enc.full = append(enc.full, handleRuneCall{
				c:         c,
				t:         t,
				escape:    escape,
				stackSize: stackSize,
			})
			return enc.compactor.HandleRune(c, t, escape, stackSize)
		default:
			return enc.out.HandleRune(c, t, escape, stackSize)
		}
	} else { // speculating
		enc.full = append(enc.full, handleRuneCall{
			c:         c,
			t:         t,
			escape:    escape,
			stackSize: stackSize,
		})
		if t != jsonparse.RuneTypeSpace {
			if err := enc.compactor.HandleRune(c, t, escape, stackSize); err != nil {
				return err
			}
		}
		switch {
		case enc.compacted.Len() >= enc.CompactWSIfUnder: // stop speculating; use indent
			buf := append([]handleRuneCall(nil), enc.full...)
			enc.reset()
			if err := enc.out.HandleRune(buf[0].c, buf[0].t, buf[0].escape, buf[0].stackSize); err != nil {
				return err
			}
			for _, tuple := range buf[1:] {
				if err := enc.HandleRune(tuple.c, tuple.t, tuple.escape, tuple.stackSize); err != nil {
					return err
				}
			}
		case stackSize == enc.endWhenStackSize: // stop speculating; use compact
			for _, tuple := range enc.full {
				if tuple.t == jsonparse.RuneTypeSpace {
					continue
				}
				if err := enc.out.HandleRune(tuple.c, tuple.t, tuple.escape, tuple.stackSize); err != nil {
					return err
				}
			}
			enc.reset()
		}
		return nil
	}
}