-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathencode.go
More file actions
229 lines (217 loc) · 7.31 KB
/
Copy pathencode.go
File metadata and controls
229 lines (217 loc) · 7.31 KB
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
package vibejson
import (
"sort"
"unicode/utf8"
"github.com/thesyncim/vibejson/document"
)
// MarshalJSON implements json.Marshaler.
func (v Value) MarshalJSON() ([]byte, error) {
return v.AppendJSON(nil), nil
}
// AppendJSON appends compact JSON for v to dst. Strings are decoded and
// re-encoded through appendJSONString, so non-canonical escapes in the source
// are normalized exactly as encoding/json would emit them; number spellings are
// preserved verbatim.
func (v Value) AppendJSON(dst []byte) []byte {
switch v.node.Kind() {
case document.Null:
return append(dst, "null"...)
case document.Bool:
if b, _ := v.node.Bool(); b {
return append(dst, "true"...)
}
return append(dst, "false"...)
case document.Number:
s, _ := v.node.NumberBytes()
return append(dst, s...)
case document.String:
return appendJSONNodeString(dst, v.node)
case document.Array:
dst = append(dst, '[')
iter, _ := v.node.ArrayIter()
for i := 0; ; i++ {
node, ok := iter.Next()
if !ok {
break
}
if i > 0 {
dst = append(dst, ',')
}
dst = v.with(node).AppendJSON(dst)
}
return append(dst, ']')
case document.Object:
dst = append(dst, '{')
iter, _ := v.node.ObjectIter()
for i := 0; ; i++ {
key, val, ok := iter.Next()
if !ok {
break
}
if i > 0 {
dst = append(dst, ',')
}
dst = appendJSONNodeString(dst, key)
dst = append(dst, ':')
dst = v.with(val).AppendJSON(dst)
}
return append(dst, '}')
default:
return append(dst, "null"...)
}
}
// appendJSONNodeString decodes a string node and re-encodes it, normalizing
// escape spelling the same way appendJSONString does for a Go string, but
// without allocating for the common unescaped case.
func appendJSONNodeString(dst []byte, node Node) []byte {
if b, ok := node.StringBytes(); ok {
return appendJSONStringBytes(dst, b)
}
decoded, _ := node.AppendText(nil)
return appendJSONStringBytes(dst, decoded)
}
// Indent validates src and returns a new owned formatted JSON buffer using
// prefix and indent. It neither modifies nor retains its inputs and is safe for
// concurrent calls. On error it returns nil and a [SyntaxError].
func Indent(src []byte, prefix, indent string) ([]byte, error) {
return AppendIndent(nil, src, prefix, indent)
}
// AppendIndent validates src and appends formatted JSON text using prefix and
// indent.
// Like json.Indent, string and number tokens are copied from src verbatim, so
// escape spelling and number literals are preserved exactly; only structural
// text is inserted. prefix and indent are copied verbatim and need not be JSON
// whitespace, so non-whitespace formatting can make the result invalid, just
// as with json.Indent. The returned slice is caller-owned and may reuse dst's
// capacity; no input is retained. The writable capacity of dst must not overlap
// src. On error it returns dst unchanged in length and a [SyntaxError], although
// unused capacity may contain partial output. Calls are safe concurrently when
// their sources remain immutable and their writable destination storage is
// independent.
func AppendIndent(dst, src []byte, prefix, indent string) ([]byte, error) {
return appendIndentBytes(dst, src, prefix, indent, DefaultMaxDepth)
}
// appendJSONStringBytes appends text as a quoted, canonically escaped JSON
// string. It is the shared core behind appendJSONString and the decoded-node
// path, so a Value re-encodes strings identically whether the caller holds a
// Go string or an already-decoded byte slice.
// Provenance: GO-STRING-001. Scalar escaping is conservatively treated as an
// adaptation of Go encoding/json appendString at commit
// d468ad3648be469ffc4090e4586c29709182d6b6; BSD-3-Clause, see LICENSE-GO.
// Byte-slice integration and SIMD prefix scanning are local changes.
func appendJSONStringBytes(dst, text []byte) []byte {
const hex = "0123456789abcdef"
dst = append(dst, '"')
start := 0
for i := 0; i < len(text); {
c := text[i]
if c >= utf8.RuneSelf {
r, size := utf8.DecodeRune(text[i:])
if size != 1 {
if r == '\u2028' || r == '\u2029' {
dst = append(dst, text[start:i]...)
dst = append(dst, '\\', 'u', '2', '0', '2', hex[r&0xf])
i += size
start = i
continue
}
i += size
continue
}
dst = append(dst, text[start:i]...)
dst = append(dst, '\\', 'u', 'f', 'f', 'f', 'd')
i++
start = i
continue
}
if c >= 0x20 && c != '"' && c != '\\' {
i++
continue
}
dst = append(dst, text[start:i]...)
switch c {
case '"', '\\':
dst = append(dst, '\\', c)
case '\b':
dst = append(dst, '\\', 'b')
case '\f':
dst = append(dst, '\\', 'f')
case '\n':
dst = append(dst, '\\', 'n')
case '\r':
dst = append(dst, '\\', 'r')
case '\t':
dst = append(dst, '\\', 't')
default:
dst = append(dst, '\\', 'u', '0', '0', hex[c>>4], hex[c&0xF])
}
i++
start = i
}
dst = append(dst, text[start:]...)
return append(dst, '"')
}
// Canonicalize validates src, sorts object members recursively, and returns a
// new owned compact JSON buffer. It is a deterministic vibejson form, not RFC
// 8785: decoded keys sort by UTF-8 byte order, duplicate keys are retained in
// their original relative order, arrays retain their order, string escapes are
// normalized like [Value.AppendJSON], and number spellings are preserved. It
// neither modifies nor retains src and is safe for concurrent calls. Invalid
// JSON returns a [SyntaxError]; any error returns a nil result.
func Canonicalize(src []byte) ([]byte, error) {
return AppendCanonicalize(nil, src)
}
// AppendCanonicalize validates src, sorts object members recursively, and
// appends the same form as Canonicalize to dst. The returned slice is
// caller-owned and may reuse dst's capacity; src is not retained. The writable
// capacity of dst must not overlap src. Validation completes before output
// begins, so an error returns dst without writing to it. Canonicalization builds
// temporary navigation storage and, for objects, member-order storage even when
// dst has sufficient capacity. Calls are safe concurrently when their sources
// remain immutable and their writable destination storage is independent.
func AppendCanonicalize(dst, src []byte) ([]byte, error) {
v, err := ParseOptions(src, Options{ZeroCopy: true})
if err != nil {
return dst, err
}
return appendCanonical(dst, v), nil
}
// appendCanonical appends compact JSON for v with every object's members sorted
// by decoded key. Arrays keep their order. Strings and numbers are normalized
// exactly as AppendJSON does, so the canonical form is stable regardless of the
// source's escape spelling or member order.
func appendCanonical(dst []byte, v Value) []byte {
switch v.node.Kind() {
case document.Array:
dst = append(dst, '[')
iter, _ := v.node.ArrayIter()
for i := 0; ; i++ {
node, ok := iter.Next()
if !ok {
break
}
if i > 0 {
dst = append(dst, ',')
}
dst = appendCanonical(dst, v.with(node))
}
return append(dst, ']')
case document.Object:
members, _ := v.Object()
sort.SliceStable(members, func(i, j int) bool {
return members[i].Key < members[j].Key
})
dst = append(dst, '{')
for i := range members {
if i > 0 {
dst = append(dst, ',')
}
dst = appendJSONString(dst, members[i].Key)
dst = append(dst, ':')
dst = appendCanonical(dst, members[i].Value)
}
return append(dst, '}')
default:
return v.AppendJSON(dst)
}
}