summaryrefslogtreecommitdiff
path: root/parse_scan_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'parse_scan_test.go')
-rw-r--r--parse_scan_test.go269
1 files changed, 269 insertions, 0 deletions
diff --git a/parse_scan_test.go b/parse_scan_test.go
new file mode 100644
index 0000000..5ad454f
--- /dev/null
+++ b/parse_scan_test.go
@@ -0,0 +1,269 @@
+// Copyright (C) 2022 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package lowmemjson
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+type ReadRuneTypeResult struct {
+ r rune
+ s int
+ t RuneType
+ e error
+}
+
+func (r ReadRuneTypeResult) String() string {
+ return fmt.Sprintf("{%q, %d, %#v, %v}", r.r, r.s, r.t, r.e)
+}
+
+func TestRuneTypeScanner(t *testing.T) {
+ type testcase struct {
+ Input string
+ Exp []ReadRuneTypeResult
+ }
+ testcases := map[string]testcase{
+ "basic": {`{"foo": 12.0}`, []ReadRuneTypeResult{
+ {'{', 1, RuneTypeObjectBeg, nil},
+ {'"', 1, RuneTypeStringBeg, nil},
+ {'f', 1, RuneTypeStringChar, nil},
+ {'o', 1, RuneTypeStringChar, nil},
+ {'o', 1, RuneTypeStringChar, nil},
+ {'"', 1, RuneTypeStringEnd, nil},
+ {':', 1, RuneTypeObjectColon, nil},
+ {' ', 1, RuneTypeSpace, nil},
+ {'1', 1, RuneTypeNumberIntDig, nil},
+ {'2', 1, RuneTypeNumberIntDig, nil},
+ {'.', 1, RuneTypeNumberFracDot, nil},
+ {'0', 1, RuneTypeNumberFracDig, nil},
+ {'}', 1, RuneTypeObjectEnd, nil},
+ {0, 0, RuneTypeEOF, nil},
+ {0, 0, RuneTypeEOF, nil},
+ }},
+ "unread": {`{"foo": 12.0}`, []ReadRuneTypeResult{
+ {'{', 1, RuneTypeObjectBeg, nil},
+ {'"', 1, RuneTypeStringBeg, nil},
+ {'f', 1, RuneTypeStringChar, nil},
+ {'o', 1, RuneTypeStringChar, nil},
+ {'o', 1, RuneTypeStringChar, nil},
+ {'"', 1, RuneTypeStringEnd, nil},
+ {':', 1, RuneTypeObjectColon, nil},
+ {' ', 1, RuneTypeSpace, nil},
+ {'1', 1, RuneTypeNumberIntDig, nil},
+ {0, -1, 0, nil},
+ {'1', 1, RuneTypeNumberIntDig, nil},
+ {'2', 1, RuneTypeNumberIntDig, nil},
+ {'.', 1, RuneTypeNumberFracDot, nil},
+ {'0', 1, RuneTypeNumberFracDig, nil},
+ {'}', 1, RuneTypeObjectEnd, nil},
+ {0, 0, RuneTypeEOF, nil},
+ {0, 0, RuneTypeEOF, nil},
+ }},
+ "unread2": {`{"foo": 12.0}`, []ReadRuneTypeResult{
+ {'{', 1, RuneTypeObjectBeg, nil},
+ {'"', 1, RuneTypeStringBeg, nil},
+ {'f', 1, RuneTypeStringChar, nil},
+ {'o', 1, RuneTypeStringChar, nil},
+ {'o', 1, RuneTypeStringChar, nil},
+ {'"', 1, RuneTypeStringEnd, nil},
+ {':', 1, RuneTypeObjectColon, nil},
+ {' ', 1, RuneTypeSpace, nil},
+ {'1', 1, RuneTypeNumberIntDig, nil},
+ {0, -1, 0, nil},
+ {0, -1, 0, ErrInvalidUnreadRune},
+ {'1', 1, RuneTypeNumberIntDig, nil},
+ {'2', 1, RuneTypeNumberIntDig, nil},
+ {'.', 1, RuneTypeNumberFracDot, nil},
+ {'0', 1, RuneTypeNumberFracDig, nil},
+ {'}', 1, RuneTypeObjectEnd, nil},
+ {0, 0, RuneTypeEOF, nil},
+ {0, 0, RuneTypeEOF, nil},
+ }},
+ "unread-eof": {`{"foo": 12.0}`, []ReadRuneTypeResult{
+ {'{', 1, RuneTypeObjectBeg, nil},
+ {'"', 1, RuneTypeStringBeg, nil},
+ {'f', 1, RuneTypeStringChar, nil},
+ {'o', 1, RuneTypeStringChar, nil},
+ {'o', 1, RuneTypeStringChar, nil},
+ {'"', 1, RuneTypeStringEnd, nil},
+ {':', 1, RuneTypeObjectColon, nil},
+ {' ', 1, RuneTypeSpace, nil},
+ {'1', 1, RuneTypeNumberIntDig, nil},
+ {'2', 1, RuneTypeNumberIntDig, nil},
+ {'.', 1, RuneTypeNumberFracDot, nil},
+ {'0', 1, RuneTypeNumberFracDig, nil},
+ {'}', 1, RuneTypeObjectEnd, nil},
+ {0, 0, RuneTypeEOF, nil},
+ {0, -1, 0, ErrInvalidUnreadRune},
+ {0, 0, RuneTypeEOF, nil},
+ {0, 0, RuneTypeEOF, nil},
+ }},
+ }
+ for tcName, tc := range testcases {
+ t.Run(tcName, func(t *testing.T) {
+ sc := &runeTypeScannerImpl{
+ inner: strings.NewReader(tc.Input),
+ }
+ 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)
+ })
+ }
+}
+
+func TestNoWSRuneTypeScanner(t *testing.T) {
+ type testcase struct {
+ Input string
+ Exp []ReadRuneTypeResult
+ }
+ testcases := map[string]testcase{
+ "basic": {`{"foo": 12.0}`, []ReadRuneTypeResult{
+ {'{', 1, RuneTypeObjectBeg, nil},
+ {'"', 1, RuneTypeStringBeg, nil},
+ {'f', 1, RuneTypeStringChar, nil},
+ {'o', 1, RuneTypeStringChar, nil},
+ {'o', 1, RuneTypeStringChar, nil},
+ {'"', 1, RuneTypeStringEnd, nil},
+ {':', 1, RuneTypeObjectColon, nil},
+ {'1', 1, RuneTypeNumberIntDig, nil},
+ {'2', 1, RuneTypeNumberIntDig, nil},
+ {'.', 1, RuneTypeNumberFracDot, nil},
+ {'0', 1, RuneTypeNumberFracDig, nil},
+ {'}', 1, RuneTypeObjectEnd, nil},
+ {0, 0, RuneTypeEOF, nil},
+ {0, 0, RuneTypeEOF, nil},
+ }},
+ "unread": {`{"foo": 12.0}`, []ReadRuneTypeResult{
+ {'{', 1, RuneTypeObjectBeg, nil},
+ {'"', 1, RuneTypeStringBeg, nil},
+ {'f', 1, RuneTypeStringChar, nil},
+ {'o', 1, RuneTypeStringChar, nil},
+ {'o', 1, RuneTypeStringChar, nil},
+ {'"', 1, RuneTypeStringEnd, nil},
+ {':', 1, RuneTypeObjectColon, nil},
+ {'1', 1, RuneTypeNumberIntDig, nil},
+ {0, -1, 0, nil},
+ {'1', 1, RuneTypeNumberIntDig, nil},
+ {'2', 1, RuneTypeNumberIntDig, nil},
+ {'.', 1, RuneTypeNumberFracDot, nil},
+ {'0', 1, RuneTypeNumberFracDig, nil},
+ {'}', 1, RuneTypeObjectEnd, nil},
+ {0, 0, RuneTypeEOF, nil},
+ {0, 0, RuneTypeEOF, nil},
+ }},
+ "tail": {`{"foo": 12.0} `, []ReadRuneTypeResult{
+ {'{', 1, RuneTypeObjectBeg, nil},
+ {'"', 1, RuneTypeStringBeg, nil},
+ {'f', 1, RuneTypeStringChar, nil},
+ {'o', 1, RuneTypeStringChar, nil},
+ {'o', 1, RuneTypeStringChar, nil},
+ {'"', 1, RuneTypeStringEnd, nil},
+ {':', 1, RuneTypeObjectColon, nil},
+ {'1', 1, RuneTypeNumberIntDig, nil},
+ {'2', 1, RuneTypeNumberIntDig, nil},
+ {'.', 1, RuneTypeNumberFracDot, nil},
+ {'0', 1, RuneTypeNumberFracDig, nil},
+ {'}', 1, RuneTypeObjectEnd, nil},
+ {0, 0, RuneTypeEOF, nil},
+ {0, 0, RuneTypeEOF, nil},
+ }},
+ }
+ for tcName, tc := range testcases {
+ t.Run(tcName, func(t *testing.T) {
+ sc := &noWSRuneTypeScanner{
+ inner: &runeTypeScannerImpl{
+ inner: strings.NewReader(tc.Input),
+ },
+ }
+ 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)
+ })
+ }
+}
+
+func TestElemRuneTypeScanner(t *testing.T) {
+ parent := &noWSRuneTypeScanner{
+ inner: &runeTypeScannerImpl{
+ inner: strings.NewReader(` { "foo" : 12.0 } `),
+ },
+ }
+ exp := []ReadRuneTypeResult{
+ {'{', 1, RuneTypeObjectBeg, nil},
+ {'"', 1, RuneTypeStringBeg, nil},
+ {'f', 1, RuneTypeStringChar, nil},
+ {'o', 1, RuneTypeStringChar, nil},
+ {'o', 1, RuneTypeStringChar, nil},
+ {'"', 1, RuneTypeStringEnd, nil},
+ {':', 1, RuneTypeObjectColon, nil},
+ }
+ var expStr, actStr []string
+ 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, RuneTypeNumberIntDig, nil},
+ {'2', 1, RuneTypeNumberIntDig, nil},
+ {'.', 1, RuneTypeNumberFracDot, nil},
+ {'0', 1, RuneTypeNumberFracDig, nil},
+ {0, 0, RuneTypeEOF, nil},
+ {0, 0, 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, RuneTypeObjectEnd, nil},
+ {0, 0, RuneTypeEOF, nil},
+ {0, 0, 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)
+ }
+}