Skip to content

Commit 494976d

Browse files
authored
feat(p/json): remove unnecessary code and optimize (#2939)
# Description Optimized the JSON package and simplified JSON node creation using the builder pattern. - in `buffer.gno` and `escape.gno` files are modified the use of map for lookup tables to use slice array instead. - refactor the `Unquote` function in `escape.gno` file - modified the existing functions that parsed numbers to use `strconv` package, and deleted related files and functions - especially, the `eisel_lemire` and `ryu` packages were deleted since they were files that had been added to handle `ParseUint` and `ParseFloat` in `strconv` package. ## JSON Generate Example **Plain JSON** ```go node := Builder(). WithString("name", "Alice"). WithNumber("age", 30). WithBool("is_student", false). Node() value, err := Marshal(node) if err != nil { t.Errorf("unexpected error: %s", err) } Output: {"name":"Alice","age":30,"is_student":false} ``` **Nested Structure** ```go node := Builder(). WriteString("name", "Alice"). WriteObject("address", func(b *NodeBuilder) { b.WriteString("city", "New York"). WriteNumber("zipcode", 10001) }). Node() // ... Output: {"name":"Alice","address":{"city":"New York","zipcode":10001}} ``` ## Benchmark Result for Unquote **Before** ```plain BenchmarkUnquote-8 12433488 98.06 ns/op 144 B/op 2 allocs/op BenchmarkUnquoteWorstCase-8 24727736 50.46 ns/op 48 B/op 1 allocs/op BenchmarkUnquoteBestCase-8 22542354 52.69 ns/op 48 B/op 1 allocs/op BenchmarkUnquoteEmptyString-8 394868628 3.067 ns/op 0 B/op 0 allocs/op ``` **After** ```plain BenchmarkUnquote-8 12464704 96.61 ns/op 144 B/op 2 allocs/op BenchmarkUnquoteWorstCase-8 25084070 48.02 ns/op 48 B/op 1 allocs/op BenchmarkUnquoteBestCase-8 23383227 52.66 ns/op 48 B/op 1 allocs/op BenchmarkUnquoteEmptyString-8 400496838 2.968 ns/op 0 B/op 0 allocs/op ```
1 parent 850182c commit 494976d

23 files changed

+383
-2561
lines changed

examples/gno.land/p/demo/json/buffer.gno

+21-44
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package json
33
import (
44
"errors"
55
"io"
6-
"strings"
76

87
"gno.land/p/demo/ufmt"
98
)
@@ -112,28 +111,6 @@ func (b *buffer) skip(bs byte) error {
112111
return io.EOF
113112
}
114113

115-
// skipAny moves the index until it encounters one of the given set of bytes.
116-
func (b *buffer) skipAny(endTokens map[byte]bool) error {
117-
for b.index < b.length {
118-
if _, exists := endTokens[b.data[b.index]]; exists {
119-
return nil
120-
}
121-
122-
b.index++
123-
}
124-
125-
// build error message
126-
var tokens []string
127-
for token := range endTokens {
128-
tokens = append(tokens, string(token))
129-
}
130-
131-
return ufmt.Errorf(
132-
"EOF reached before encountering one of the expected tokens: %s",
133-
strings.Join(tokens, ", "),
134-
)
135-
}
136-
137114
// skipAndReturnIndex moves the buffer index forward by one and returns the new index.
138115
func (b *buffer) skipAndReturnIndex() (int, error) {
139116
err := b.step()
@@ -165,7 +142,7 @@ func (b *buffer) skipUntil(endTokens map[byte]bool) (int, error) {
165142

166143
// significantTokens is a map where the keys are the significant characters in a JSON path.
167144
// The values in the map are all true, which allows us to use the map as a set for quick lookups.
168-
var significantTokens = map[byte]bool{
145+
var significantTokens = [256]bool{
169146
dot: true, // access properties of an object
170147
dollarSign: true, // root object
171148
atSign: true, // current object
@@ -174,7 +151,7 @@ var significantTokens = map[byte]bool{
174151
}
175152

176153
// filterTokens stores the filter expression tokens.
177-
var filterTokens = map[byte]bool{
154+
var filterTokens = [256]bool{
178155
aesterisk: true, // wildcard
179156
andSign: true,
180157
orSign: true,
@@ -186,7 +163,7 @@ func (b *buffer) skipToNextSignificantToken() {
186163
for b.index < b.length {
187164
current := b.data[b.index]
188165

189-
if _, ok := significantTokens[current]; ok {
166+
if significantTokens[current] {
190167
break
191168
}
192169

@@ -205,7 +182,7 @@ func (b *buffer) backslash() bool {
205182

206183
count := 0
207184
for i := b.index - 1; ; i-- {
208-
if i >= b.length || b.data[i] != backSlash {
185+
if b.data[i] != backSlash {
209186
break
210187
}
211188

@@ -220,7 +197,7 @@ func (b *buffer) backslash() bool {
220197
}
221198

222199
// numIndex holds a map of valid numeric characters
223-
var numIndex = map[byte]bool{
200+
var numIndex = [256]bool{
224201
'0': true,
225202
'1': true,
226203
'2': true,
@@ -255,11 +232,11 @@ func (b *buffer) pathToken() error {
255232
}
256233

257234
if err := b.skip(c); err != nil {
258-
return errors.New("unmatched quote in path")
235+
return errUnmatchedQuotePath
259236
}
260237

261238
if b.index >= b.length {
262-
return errors.New("unmatched quote in path")
239+
return errUnmatchedQuotePath
263240
}
264241

265242
case c == bracketOpen || c == parenOpen:
@@ -269,7 +246,7 @@ func (b *buffer) pathToken() error {
269246
case c == bracketClose || c == parenClose:
270247
inToken = true
271248
if len(stack) == 0 || (c == bracketClose && stack[len(stack)-1] != bracketOpen) || (c == parenClose && stack[len(stack)-1] != parenOpen) {
272-
return errors.New("mismatched bracket or parenthesis")
249+
return errUnmatchedParenthesis
273250
}
274251

275252
stack = stack[:len(stack)-1]
@@ -284,7 +261,7 @@ func (b *buffer) pathToken() error {
284261
inToken = true
285262
inNumber = true
286263
} else if !inToken {
287-
return errors.New("unexpected operator at start of token")
264+
return errInvalidToken
288265
}
289266

290267
default:
@@ -300,7 +277,7 @@ func (b *buffer) pathToken() error {
300277

301278
end:
302279
if len(stack) != 0 {
303-
return errors.New("unclosed bracket or parenthesis at end of path")
280+
return errUnmatchedParenthesis
304281
}
305282

306283
if first == b.index {
@@ -315,15 +292,15 @@ end:
315292
}
316293

317294
func pathStateContainsValidPathToken(c byte) bool {
318-
if _, ok := significantTokens[c]; ok {
295+
if significantTokens[c] {
319296
return true
320297
}
321298

322-
if _, ok := filterTokens[c]; ok {
299+
if filterTokens[c] {
323300
return true
324301
}
325302

326-
if _, ok := numIndex[c]; ok {
303+
if numIndex[c] {
327304
return true
328305
}
329306

@@ -342,7 +319,7 @@ func (b *buffer) numeric(token bool) error {
342319
for ; b.index < b.length; b.index++ {
343320
b.class = b.getClasses(doubleQuote)
344321
if b.class == __ {
345-
return errors.New("invalid token found while parsing path")
322+
return errInvalidToken
346323
}
347324

348325
b.state = StateTransitionTable[b.last][b.class]
@@ -351,7 +328,7 @@ func (b *buffer) numeric(token bool) error {
351328
break
352329
}
353330

354-
return errors.New("invalid token found while parsing path")
331+
return errInvalidToken
355332
}
356333

357334
if b.state < __ {
@@ -366,7 +343,7 @@ func (b *buffer) numeric(token bool) error {
366343
}
367344

368345
if b.last != ZE && b.last != IN && b.last != FR && b.last != E3 {
369-
return errors.New("invalid token found while parsing path")
346+
return errInvalidToken
370347
}
371348

372349
return nil
@@ -407,12 +384,12 @@ func (b *buffer) string(search byte, token bool) error {
407384
b.class = b.getClasses(search)
408385

409386
if b.class == __ {
410-
return errors.New("invalid token found while parsing path")
387+
return errInvalidToken
411388
}
412389

413390
b.state = StateTransitionTable[b.last][b.class]
414391
if b.state == __ {
415-
return errors.New("invalid token found while parsing path")
392+
return errInvalidToken
416393
}
417394

418395
if b.state < __ {
@@ -431,11 +408,11 @@ func (b *buffer) word(bs []byte) error {
431408
max := len(bs)
432409
index := 0
433410

434-
for ; b.index < b.length; b.index++ {
411+
for ; b.index < b.length && index < max; b.index++ {
435412
c = b.data[b.index]
436413

437414
if c != bs[index] {
438-
return errors.New("invalid token found while parsing path")
415+
return errInvalidToken
439416
}
440417

441418
index++
@@ -445,7 +422,7 @@ func (b *buffer) word(bs []byte) error {
445422
}
446423

447424
if index != max {
448-
return errors.New("invalid token found while parsing path")
425+
return errInvalidToken
449426
}
450427

451428
return nil

examples/gno.land/p/demo/json/buffer_test.gno

+3-32
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package json
22

3-
import "testing"
3+
import (
4+
"testing"
5+
)
46

57
func TestBufferCurrent(t *testing.T) {
68
tests := []struct {
@@ -242,37 +244,6 @@ func TestBufferSkip(t *testing.T) {
242244
}
243245
}
244246

245-
func TestBufferSkipAny(t *testing.T) {
246-
tests := []struct {
247-
name string
248-
buffer *buffer
249-
s map[byte]bool
250-
wantErr bool
251-
}{
252-
{
253-
name: "Skip any valid byte",
254-
buffer: &buffer{data: []byte("test"), length: 4, index: 0},
255-
s: map[byte]bool{'e': true, 'o': true},
256-
wantErr: false,
257-
},
258-
{
259-
name: "Skip any to EOF",
260-
buffer: &buffer{data: []byte("test"), length: 4, index: 0},
261-
s: map[byte]bool{'x': true, 'y': true},
262-
wantErr: true,
263-
},
264-
}
265-
266-
for _, tt := range tests {
267-
t.Run(tt.name, func(t *testing.T) {
268-
err := tt.buffer.skipAny(tt.s)
269-
if (err != nil) != tt.wantErr {
270-
t.Errorf("buffer.skipAny() error = %v, wantErr %v", err, tt.wantErr)
271-
}
272-
})
273-
}
274-
}
275-
276247
func TestSkipToNextSignificantToken(t *testing.T) {
277248
tests := []struct {
278249
name string
+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package json
2+
3+
type NodeBuilder struct {
4+
node *Node
5+
}
6+
7+
func Builder() *NodeBuilder {
8+
return &NodeBuilder{node: ObjectNode("", nil)}
9+
}
10+
11+
func (b *NodeBuilder) WriteString(key, value string) *NodeBuilder {
12+
b.node.AppendObject(key, StringNode("", value))
13+
return b
14+
}
15+
16+
func (b *NodeBuilder) WriteNumber(key string, value float64) *NodeBuilder {
17+
b.node.AppendObject(key, NumberNode("", value))
18+
return b
19+
}
20+
21+
func (b *NodeBuilder) WriteBool(key string, value bool) *NodeBuilder {
22+
b.node.AppendObject(key, BoolNode("", value))
23+
return b
24+
}
25+
26+
func (b *NodeBuilder) WriteNull(key string) *NodeBuilder {
27+
b.node.AppendObject(key, NullNode(""))
28+
return b
29+
}
30+
31+
func (b *NodeBuilder) WriteObject(key string, fn func(*NodeBuilder)) *NodeBuilder {
32+
nestedBuilder := &NodeBuilder{node: ObjectNode("", nil)}
33+
fn(nestedBuilder)
34+
b.node.AppendObject(key, nestedBuilder.node)
35+
return b
36+
}
37+
38+
func (b *NodeBuilder) WriteArray(key string, fn func(*ArrayBuilder)) *NodeBuilder {
39+
arrayBuilder := &ArrayBuilder{nodes: []*Node{}}
40+
fn(arrayBuilder)
41+
b.node.AppendObject(key, ArrayNode("", arrayBuilder.nodes))
42+
return b
43+
}
44+
45+
func (b *NodeBuilder) Node() *Node {
46+
return b.node
47+
}
48+
49+
type ArrayBuilder struct {
50+
nodes []*Node
51+
}
52+
53+
func (ab *ArrayBuilder) WriteString(value string) *ArrayBuilder {
54+
ab.nodes = append(ab.nodes, StringNode("", value))
55+
return ab
56+
}
57+
58+
func (ab *ArrayBuilder) WriteNumber(value float64) *ArrayBuilder {
59+
ab.nodes = append(ab.nodes, NumberNode("", value))
60+
return ab
61+
}
62+
63+
func (ab *ArrayBuilder) WriteInt(value int) *ArrayBuilder {
64+
return ab.WriteNumber(float64(value))
65+
}
66+
67+
func (ab *ArrayBuilder) WriteBool(value bool) *ArrayBuilder {
68+
ab.nodes = append(ab.nodes, BoolNode("", value))
69+
return ab
70+
}
71+
72+
func (ab *ArrayBuilder) WriteNull() *ArrayBuilder {
73+
ab.nodes = append(ab.nodes, NullNode(""))
74+
return ab
75+
}
76+
77+
func (ab *ArrayBuilder) WriteObject(fn func(*NodeBuilder)) *ArrayBuilder {
78+
nestedBuilder := &NodeBuilder{node: ObjectNode("", nil)}
79+
fn(nestedBuilder)
80+
ab.nodes = append(ab.nodes, nestedBuilder.node)
81+
return ab
82+
}
83+
84+
func (ab *ArrayBuilder) WriteArray(fn func(*ArrayBuilder)) *ArrayBuilder {
85+
nestedArrayBuilder := &ArrayBuilder{nodes: []*Node{}}
86+
fn(nestedArrayBuilder)
87+
ab.nodes = append(ab.nodes, ArrayNode("", nestedArrayBuilder.nodes))
88+
return ab
89+
}

0 commit comments

Comments
 (0)