summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@lukeshu.com>2023-01-29 17:40:13 -0700
committerLuke Shumaker <lukeshu@lukeshu.com>2023-01-29 21:01:42 -0700
commitd1b5bc1f05624614f43ef85597f4aa9d7a166d23 (patch)
treec8bbf85b8aa320ea71de07411efa7ab28c42837e
parent0b57145421e7e4f165f64e73ee7c5d8102945569 (diff)
parse: Add an example of how the stack works for arrays, add tests
-rw-r--r--internal/parse.go38
-rw-r--r--internal/parse_test.go78
2 files changed, 108 insertions, 8 deletions
diff --git a/internal/parse.go b/internal/parse.go
index bb849e7..121857b 100644
--- a/internal/parse.go
+++ b/internal/parse.go
@@ -268,6 +268,8 @@ type Parser struct {
//
// Within each element type, the stack item is replaced, not pushed.
//
+ // (Keep each of these examples in-sync with parse_test.go.)
+ //
// For example, given the input string
//
// {"x":"y","a":"b"}
@@ -293,9 +295,34 @@ type Parser struct {
// o" {"x":"y","a":"b
// o {"x":"y","a":"b"
// {"x":"y","a":"b"}
+ //
+ // Or, given the input string
+ //
+ // ["x","y"]
+ //
+ // The stack would be
+ //
+ // stack processed
+ // ?
+ // [ [
+ // a" ["
+ // a" ["x
+ // a ["x"
+ // ] ["x",
+ // a" ["x","
+ // a" ["x","y
+ // a ["x","y"
+ // ["x","y"]
stack []RuneType
}
+func (par *Parser) init() {
+ if !par.initialized {
+ par.initialized = true
+ par.pushState(runeTypeAny)
+ }
+}
+
func (par *Parser) pushState(state RuneType) RuneType {
par.stack = append(par.stack, state)
return state
@@ -311,6 +338,7 @@ func (par *Parser) popState() {
}
func (par *Parser) stackString() string {
+ par.init()
var buf strings.Builder
for _, s := range par.stack {
buf.WriteString(s.String())
@@ -351,10 +379,7 @@ func (par *Parser) HandleEOF() (RuneType, error) {
if par.err != nil {
return RuneTypeError, par.err
}
- if !par.initialized {
- par.initialized = true
- par.pushState(runeTypeAny)
- }
+ par.init()
switch len(par.stack) {
case 0:
return RuneTypeEOF, nil
@@ -395,10 +420,7 @@ func (par *Parser) HandleRune(c rune) (RuneType, error) {
if par.err != nil {
return RuneTypeError, par.err
}
- if !par.initialized {
- par.initialized = true
- par.pushState(runeTypeAny)
- }
+ par.init()
if len(par.stack) == 0 {
switch c {
case 0x0020, 0x000A, 0x000D, 0x0009:
diff --git a/internal/parse_test.go b/internal/parse_test.go
new file mode 100644
index 0000000..91cd277
--- /dev/null
+++ b/internal/parse_test.go
@@ -0,0 +1,78 @@
+// Copyright (C) 2023 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package internal
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestParserHandleRune(t *testing.T) {
+ t.Parallel()
+ type testcase struct {
+ Input string
+ ExpStack []string
+ }
+ testcases := map[string]testcase{
+ // Keep these test-cases in-sync with the examples in parse.go.
+ "object": {
+ Input: `{"x":"y","a":"b"}`,
+ ExpStack: []string{
+ // st,// processed
+ `?`,
+ `{`, // {
+ `»"`, // {"
+ `»"`, // {"x
+ `»`, // {"x"
+ `o?`, // {"x":
+ `o"`, // {"x":"
+ `o"`, // {"x":"y
+ `o`, // {"x":"y"
+ `{`, // {"x":"y",
+ `»"`, // {"x":"y","
+ `»"`, // {"x":"y","a
+ `»`, // {"x":"y","a"
+ `o?`, // {"x":"y","a":
+ `o"`, // {"x":"y","a":"
+ `o"`, // {"x":"y","a":"b
+ `o`, // {"x":"y","a":"b"
+ ``, // {"x":"y","a":"b"}
+ },
+ },
+ "array": {
+ Input: `["x","y"]`,
+ ExpStack: []string{
+ // st,// processed
+ `?`,
+ `[`, // [
+ `a"`, // ["
+ `a"`, // ["x
+ `a`, // ["x"
+ `]`, // ["x",
+ `a"`, // ["x","
+ `a"`, // ["x","y
+ `a`, // ["x","y"
+ ``, // ["x","y"]
+ },
+ },
+ }
+ for tcName, tc := range testcases {
+ tc := tc
+ t.Run(tcName, func(t *testing.T) {
+ t.Parallel()
+ var par Parser
+ if !assert.Equal(t, len(tc.Input)+1, len(tc.ExpStack)) {
+ return
+ }
+ for i, r := range tc.Input {
+ assert.Equal(t, tc.ExpStack[i], par.stackString())
+ _, err := par.HandleRune(r)
+ assert.NoError(t, err)
+ assert.Equal(t, tc.ExpStack[i+1], par.stackString())
+ }
+ })
+ }
+}