summaryrefslogtreecommitdiff
path: root/decode_scan_test.go
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@lukeshu.com>2023-01-29 20:59:37 -0700
committerLuke Shumaker <lukeshu@lukeshu.com>2023-01-30 22:56:40 -0700
commitb3f4186f2b8e992f56f898784b1cd28bfd7550ca (patch)
tree9c9f0086c386a63a12b622945248916d51c480a5 /decode_scan_test.go
parentbc1bacc410ddfa444c5bf0e56f33a7da440658ae (diff)
Invent "barriers" instead of nesting parsers
Diffstat (limited to 'decode_scan_test.go')
-rw-r--r--decode_scan_test.go263
1 files changed, 104 insertions, 159 deletions
diff --git a/decode_scan_test.go b/decode_scan_test.go
index 5bf5e2a..d0725e5 100644
--- a/decode_scan_test.go
+++ b/decode_scan_test.go
@@ -11,7 +11,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
"git.lukeshu.com/go/lowmemjson/internal"
)
@@ -23,8 +22,26 @@ type ReadRuneTypeResult struct {
e error
}
+const (
+ unreadRune = -1
+ pushReadBarrier = -2
+ popReadBarrier = -3
+ reset = -4
+)
+
func (r ReadRuneTypeResult) String() string {
- return fmt.Sprintf("{%q, %d, %#v, %v}", r.r, r.s, r.t, r.e)
+ switch r.s {
+ case unreadRune:
+ return fmt.Sprintf("{%q, unreadRune, %#v, %v}", r.r, r.t, r.e)
+ case pushReadBarrier:
+ return fmt.Sprintf("{%q, pushReadBarrier, %#v, %v}", r.r, r.t, r.e)
+ case popReadBarrier:
+ return fmt.Sprintf("{%q, popReadBarrier, %#v, %v}", r.r, r.t, r.e)
+ case reset:
+ return fmt.Sprintf("{%q, reset, %#v, %v}", r.r, r.t, r.e)
+ default:
+ return fmt.Sprintf("{%q, %d, %#v, %v}", r.r, r.s, r.t, r.e)
+ }
}
type runeTypeScannerTestcase struct {
@@ -33,31 +50,6 @@ type runeTypeScannerTestcase struct {
Exp []ReadRuneTypeResult
}
-func testRuneTypeScanner(t *testing.T, testcases map[string]runeTypeScannerTestcase, factory func(io.RuneScanner) runeTypeScanner) {
- for tcName, tc := range testcases {
- tc := tc
- t.Run(tcName, func(t *testing.T) {
- t.Parallel()
- reader := strings.NewReader(tc.Input)
- sc := factory(reader)
- var exp, act []string
- for _, iExp := range tc.Exp {
- var iAct ReadRuneTypeResult
- if iExp.s < 0 {
- iAct.s = iExp.s
- iAct.e = sc.UnreadRune()
- } else {
- iAct.r, iAct.s, iAct.t, iAct.e = sc.ReadRuneType()
- }
- exp = append(exp, iExp.String())
- act = append(act, iAct.String())
- }
- assert.Equal(t, exp, act)
- assert.Equal(t, tc.ExpRemainder, tc.Input[len(tc.Input)-reader.Len():])
- })
- }
-}
-
func TestRuneTypeScanner(t *testing.T) {
t.Parallel()
testcases := map[string]runeTypeScannerTestcase{
@@ -86,7 +78,7 @@ func TestRuneTypeScanner(t *testing.T) {
{'"', 1, internal.RuneTypeStringEnd, nil},
{':', 1, internal.RuneTypeObjectColon, nil},
{'1', 1, internal.RuneTypeNumberIntDig, nil},
- {0, -1, 0, nil},
+ {0, unreadRune, 0, nil},
{'1', 1, internal.RuneTypeNumberIntDig, nil},
{'2', 1, internal.RuneTypeNumberIntDig, nil},
{'.', 1, internal.RuneTypeNumberFracDot, nil},
@@ -104,8 +96,8 @@ func TestRuneTypeScanner(t *testing.T) {
{'"', 1, internal.RuneTypeStringEnd, nil},
{':', 1, internal.RuneTypeObjectColon, nil},
{'1', 1, internal.RuneTypeNumberIntDig, nil},
- {0, -1, 0, nil},
- {0, -1, 0, ErrInvalidUnreadRune},
+ {0, unreadRune, 0, nil},
+ {0, unreadRune, 0, ErrInvalidUnreadRune},
{'1', 1, internal.RuneTypeNumberIntDig, nil},
{'2', 1, internal.RuneTypeNumberIntDig, nil},
{'.', 1, internal.RuneTypeNumberFracDot, nil},
@@ -128,7 +120,7 @@ func TestRuneTypeScanner(t *testing.T) {
{'0', 1, internal.RuneTypeNumberFracDig, nil},
{'}', 1, internal.RuneTypeObjectEnd, nil},
{0, 0, internal.RuneTypeEOF, nil},
- {0, -1, 0, ErrInvalidUnreadRune},
+ {0, unreadRune, 0, ErrInvalidUnreadRune},
{0, 0, internal.RuneTypeEOF, nil},
{0, 0, internal.RuneTypeEOF, nil},
}},
@@ -174,150 +166,103 @@ func TestRuneTypeScanner(t *testing.T) {
{0, 0, internal.RuneTypeError, &DecodeSyntaxError{Offset: 0, Err: io.EOF}},
{0, 0, internal.RuneTypeError, &DecodeSyntaxError{Offset: 0, Err: io.EOF}},
}},
- }
- testRuneTypeScanner(t, testcases, func(reader io.RuneScanner) runeTypeScanner {
- return &runeTypeScannerImpl{
- inner: reader,
- }
- })
-}
-
-func TestElemRuneTypeScanner(t *testing.T) {
- t.Parallel()
- toplevelTestcases := map[string]runeTypeScannerTestcase{
- "basic": {`1`, ``, []ReadRuneTypeResult{
+ "basic2": {`1`, ``, []ReadRuneTypeResult{
{'1', 1, internal.RuneTypeNumberIntDig, nil},
{0, 0, internal.RuneTypeEOF, nil},
{0, 0, internal.RuneTypeEOF, nil},
{0, 0, internal.RuneTypeEOF, nil},
}},
- "syntax-error": {`[[0,]`, ``, []ReadRuneTypeResult{
- {'[', 1, internal.RuneTypeArrayBeg, nil},
- {'[', 1, internal.RuneTypeArrayBeg, nil},
- {'0', 1, internal.RuneTypeNumberIntZero, nil},
- {',', 1, internal.RuneTypeArrayComma, nil},
- {']', 1, internal.RuneTypeError, &DecodeSyntaxError{Offset: 5, Err: fmt.Errorf("invalid character %q looking for beginning of value", ']')}},
- {']', 1, internal.RuneTypeError, &DecodeSyntaxError{Offset: 5, Err: fmt.Errorf("invalid character %q looking for beginning of value", ']')}},
- {']', 1, internal.RuneTypeError, &DecodeSyntaxError{Offset: 5, Err: fmt.Errorf("invalid character %q looking for beginning of value", ']')}},
- }},
- "multi-value": {`1{}`, `{}`, []ReadRuneTypeResult{
+ "fragment": {`1,`, ``, []ReadRuneTypeResult{
{'1', 1, internal.RuneTypeNumberIntDig, nil},
- {0, 0, internal.RuneTypeEOF, nil},
- {0, 0, internal.RuneTypeEOF, nil},
- {0, 0, internal.RuneTypeEOF, nil},
+ {',', 1, internal.RuneTypeEOF, nil},
+ {',', 1, internal.RuneTypeEOF, nil},
+ {',', 1, internal.RuneTypeEOF, nil},
}},
- "fragment": {`1,`, `,`, []ReadRuneTypeResult{
+ "elem": {` { "foo" : 12.0 } `, ``, []ReadRuneTypeResult{
+ {'{', 1, internal.RuneTypeObjectBeg, nil},
+ {'"', 1, internal.RuneTypeStringBeg, nil},
+ {'f', 1, internal.RuneTypeStringChar, nil},
+ {'o', 1, internal.RuneTypeStringChar, nil},
+ {'o', 1, internal.RuneTypeStringChar, nil},
+ {'"', 1, internal.RuneTypeStringEnd, nil},
+ {':', 1, internal.RuneTypeObjectColon, nil},
+ {0, pushReadBarrier, 0, nil},
{'1', 1, internal.RuneTypeNumberIntDig, nil},
- {0, 0, internal.RuneTypeEOF, nil},
+ {'2', 1, internal.RuneTypeNumberIntDig, nil},
+ {'.', 1, internal.RuneTypeNumberFracDot, nil},
+ {'0', 1, internal.RuneTypeNumberFracDig, nil},
+ {'}', 1, internal.RuneTypeEOF, nil},
+ {'}', 1, internal.RuneTypeEOF, nil},
+ {0, popReadBarrier, 0, nil},
+ {'}', 1, internal.RuneTypeObjectEnd, nil},
{0, 0, internal.RuneTypeEOF, nil},
{0, 0, internal.RuneTypeEOF, nil},
}},
- "early-eof": {`{`, ``, []ReadRuneTypeResult{
- {'{', 1, internal.RuneTypeObjectBeg, nil},
- {0, 0, internal.RuneTypeError, &DecodeSyntaxError{Offset: 1, Err: io.ErrUnexpectedEOF}},
- {0, 0, internal.RuneTypeError, &DecodeSyntaxError{Offset: 1, Err: io.ErrUnexpectedEOF}},
- {0, 0, internal.RuneTypeError, &DecodeSyntaxError{Offset: 1, Err: io.ErrUnexpectedEOF}},
- }},
}
-
- childTestcases := make(map[string]runeTypeScannerTestcase, len(toplevelTestcases))
- for tcName, tc := range toplevelTestcases {
- tc.Input = `[` + tc.Input
- tc.Exp = append([]ReadRuneTypeResult(nil), tc.Exp...) // copy
- for i, res := range tc.Exp {
- if se, ok := res.e.(*DecodeSyntaxError); ok {
- seCopy := *se
- seCopy.Offset++
- tc.Exp[i].e = &seCopy
+ func() {
+ childTestcases := make(map[string]runeTypeScannerTestcase)
+ for tcName, tc := range testcases {
+ canChild := true
+ for _, res := range tc.Exp {
+ if res.s == pushReadBarrier {
+ canChild = false
+ break
+ }
}
- }
- childTestcases[tcName] = tc
- }
-
- t.Run("top-level", func(t *testing.T) {
- t.Parallel()
- testRuneTypeScanner(t, toplevelTestcases, func(reader io.RuneScanner) runeTypeScanner {
- return &elemRuneTypeScanner{
- inner: &runeTypeScannerImpl{
- inner: reader,
- },
+ if !canChild {
+ continue
}
- })
- })
- t.Run("child", func(t *testing.T) {
- t.Parallel()
- testRuneTypeScanner(t, childTestcases, func(reader io.RuneScanner) runeTypeScanner {
- inner := &runeTypeScannerImpl{
- inner: reader,
+ tc.Input = `[1,` + tc.Input
+ tc.Exp = append([]ReadRuneTypeResult{
+ {'[', 1, internal.RuneTypeArrayBeg, nil},
+ {'1', 1, internal.RuneTypeNumberIntDig, nil},
+ {',', 1, internal.RuneTypeArrayComma, nil},
+ {0, pushReadBarrier, 0, nil},
+ }, tc.Exp...)
+ for i := 2; i < len(tc.Exp); i++ {
+ if se, ok := tc.Exp[i].e.(*DecodeSyntaxError); ok {
+ seCopy := *se
+ seCopy.Offset += 3
+ tc.Exp[i].e = &seCopy
+ }
}
- var res ReadRuneTypeResult
- res.r, res.s, res.t, res.e = inner.ReadRuneType()
- require.Equal(t,
- ReadRuneTypeResult{'[', 1, internal.RuneTypeArrayBeg, nil}.String(),
- res.String())
-
- return &elemRuneTypeScanner{
- inner: inner,
+ childTestcases["child-"+tcName] = tc
+ }
+ for tcName, tc := range childTestcases {
+ testcases[tcName] = tc
+ }
+ }()
+ for tcName, tc := range testcases {
+ tc := tc
+ t.Run(tcName, func(t *testing.T) {
+ t.Parallel()
+ t.Logf("input=%q", tc.Input)
+ reader := strings.NewReader(tc.Input)
+ sc := &runeTypeScanner{inner: reader}
+ var exp, act []string
+ for _, iExp := range tc.Exp {
+ var iAct ReadRuneTypeResult
+ switch iExp.s {
+ case unreadRune:
+ iAct.s = iExp.s
+ iAct.e = sc.UnreadRune()
+ case pushReadBarrier:
+ sc.PushReadBarrier()
+ iAct.s = iExp.s
+ case popReadBarrier:
+ sc.PopReadBarrier()
+ iAct.s = iExp.s
+ case reset:
+ sc.Reset()
+ iAct.s = iExp.s
+ default:
+ iAct.r, iAct.s, iAct.t, iAct.e = sc.ReadRuneType()
+ }
+ exp = append(exp, iExp.String())
+ act = append(act, iAct.String())
}
+ assert.Equal(t, exp, act)
+ assert.Equal(t, tc.ExpRemainder, tc.Input[len(tc.Input)-reader.Len():])
})
- })
-}
-
-func TestElemRuneTypeScanner2(t *testing.T) {
- t.Parallel()
- parent := &runeTypeScannerImpl{
- inner: strings.NewReader(` { "foo" : 12.0 } `),
- }
- exp := []ReadRuneTypeResult{
- {'{', 1, internal.RuneTypeObjectBeg, nil},
- {'"', 1, internal.RuneTypeStringBeg, nil},
- {'f', 1, internal.RuneTypeStringChar, nil},
- {'o', 1, internal.RuneTypeStringChar, nil},
- {'o', 1, internal.RuneTypeStringChar, nil},
- {'"', 1, internal.RuneTypeStringEnd, nil},
- {':', 1, internal.RuneTypeObjectColon, nil},
- }
- expStr := make([]string, 0, len(exp))
- actStr := make([]string, 0, len(exp))
- for _, iExp := range exp {
- var iAct ReadRuneTypeResult
- iAct.r, iAct.s, iAct.t, iAct.e = parent.ReadRuneType()
- expStr = append(expStr, iExp.String())
- actStr = append(actStr, iAct.String())
- require.Equal(t, expStr, actStr)
- }
-
- child := &elemRuneTypeScanner{
- inner: parent,
- }
- exp = []ReadRuneTypeResult{
- {'1', 1, internal.RuneTypeNumberIntDig, nil},
- {'2', 1, internal.RuneTypeNumberIntDig, nil},
- {'.', 1, internal.RuneTypeNumberFracDot, nil},
- {'0', 1, internal.RuneTypeNumberFracDig, nil},
- {0, 0, internal.RuneTypeEOF, nil},
- {0, 0, internal.RuneTypeEOF, nil},
- }
- expStr, actStr = nil, nil
- for _, iExp := range exp {
- var iAct ReadRuneTypeResult
- iAct.r, iAct.s, iAct.t, iAct.e = child.ReadRuneType()
- expStr = append(expStr, iExp.String())
- actStr = append(actStr, iAct.String())
- require.Equal(t, expStr, actStr)
- }
-
- exp = []ReadRuneTypeResult{
- {'}', 1, internal.RuneTypeObjectEnd, nil},
- {0, 0, internal.RuneTypeEOF, nil},
- {0, 0, internal.RuneTypeEOF, nil},
- }
- expStr, actStr = nil, nil
- for _, iExp := range exp {
- var iAct ReadRuneTypeResult
- iAct.r, iAct.s, iAct.t, iAct.e = parent.ReadRuneType()
- expStr = append(expStr, iExp.String())
- actStr = append(actStr, iAct.String())
- require.Equal(t, expStr, actStr)
}
}