summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@lukeshu.com>2022-08-13 22:05:20 -0600
committerLuke Shumaker <lukeshu@datawire.io>2022-08-14 20:31:20 -0600
commit2ba5d96ccba101e6ccbf32b08e2fd18d4b8d7787 (patch)
tree1bbd66325cf9b433fbea08a0ee83a79927aaca35
parent4584172cdf3071cad76a67952f188863e6d4ed91 (diff)
parse_scan: Add reader abstractions on top of Parser
-rw-r--r--borrowed_decode_test.go6
-rw-r--r--borrowed_misc.go14
-rw-r--r--parse.go59
-rw-r--r--parse_scan.go241
-rw-r--r--parse_scan_test.go269
-rw-r--r--reencode.go2
6 files changed, 573 insertions, 18 deletions
diff --git a/borrowed_decode_test.go b/borrowed_decode_test.go
index a1fd695..ac8594b 100644
--- a/borrowed_decode_test.go
+++ b/borrowed_decode_test.go
@@ -453,8 +453,8 @@ var unmarshalTests = []unmarshalTest{
{in: `{"X": "foo", "Y"}`, err: &SyntaxError{"invalid character '}' after object key", 17}},
{in: `[1, 2, 3+]`, err: &SyntaxError{"invalid character '+' after array element", 9}},
{in: `{"X":12x}`, err: &SyntaxError{"invalid character 'x' after object key:value pair", 8}, useNumber: true},
- {in: `[2, 3`, err: &SyntaxError{msg: "unexpected end of JSON input", Offset: 5}},
- {in: `{"F3": -}`, ptr: new(V), out: V{F3: Number("-")}, err: &SyntaxError{msg: "invalid character '}' in numeric literal", Offset: 9}},
+ {in: `[2, 3`, err: &SyntaxError{Err: "unexpected end of JSON input", Offset: 5}}, // MODIFIED
+ {in: `{"F3": -}`, ptr: new(V), out: V{F3: Number("-")}, err: &SyntaxError{Err: "invalid character '}' in numeric literal", Offset: 9}}, // MODIFIED
// raw value errors
{in: "\x01 42", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}},
@@ -955,7 +955,7 @@ var unmarshalTests = []unmarshalTest{
in: `invalid`,
ptr: new(Number),
err: &SyntaxError{
- msg: "invalid character 'i' looking for beginning of value",
+ Err: "invalid character 'i' looking for beginning of value", // MODIFIED
Offset: 1,
},
},
diff --git a/borrowed_misc.go b/borrowed_misc.go
index 5c6bbb6..343c924 100644
--- a/borrowed_misc.go
+++ b/borrowed_misc.go
@@ -5,23 +5,9 @@
package lowmemjson
import (
- "fmt"
"reflect"
)
-// A SyntaxError is a description of a JSON syntax error.
-//
-// from scanner.go
-type SyntaxError struct {
- msg string // description of error
- Offset int64 // error occurred after reading Offset bytes
-}
-
-func (e *SyntaxError) Error() string {
- return fmt.Sprintf("JSON syntax error at input byte %v: %v",
- e.Offset, e.msg)
-}
-
// from encode.go
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
diff --git a/parse.go b/parse.go
index 58deb0b..9982944 100644
--- a/parse.go
+++ b/parse.go
@@ -66,6 +66,65 @@ const (
RuneTypeEOF
)
+func (t RuneType) GoString() string {
+ str, ok := map[RuneType]string{
+ RuneTypeError: "RuneTypeError",
+
+ RuneTypeSpace: "RuneTypeSpace",
+
+ RuneTypeObjectBeg: "RuneTypeObjectBeg",
+ RuneTypeObjectColon: "RuneTypeObjectColon",
+ RuneTypeObjectComma: "RuneTypeObjectComma",
+ RuneTypeObjectEnd: "RuneTypeObjectEnd",
+
+ RuneTypeArrayBeg: "RuneTypeArrayBeg",
+ RuneTypeArrayComma: "RuneTypeArrayComma",
+ RuneTypeArrayEnd: "RuneTypeArrayEnd",
+
+ RuneTypeStringBeg: "RuneTypeStringBeg",
+ RuneTypeStringChar: "RuneTypeStringChar",
+ RuneTypeStringEsc: "RuneTypeStringEsc",
+ RuneTypeStringEsc1: "RuneTypeStringEsc1",
+ RuneTypeStringEscU: "RuneTypeStringEscU",
+ RuneTypeStringEscUA: "RuneTypeStringEscUA",
+ RuneTypeStringEscUB: "RuneTypeStringEscUB",
+ RuneTypeStringEscUC: "RuneTypeStringEscUC",
+ RuneTypeStringEscUD: "RuneTypeStringEscUD",
+ RuneTypeStringEnd: "RuneTypeStringEnd",
+
+ RuneTypeNumberIntNeg: "RuneTypeNumberIntNeg",
+ RuneTypeNumberIntZero: "RuneTypeNumberIntZero",
+ RuneTypeNumberIntDig: "RuneTypeNumberIntDig",
+ RuneTypeNumberFracDot: "RuneTypeNumberFracDot",
+ RuneTypeNumberFracDig: "RuneTypeNumberFracDig",
+ RuneTypeNumberExpE: "RuneTypeNumberExpE",
+ RuneTypeNumberExpSign: "RuneTypeNumberExpSign",
+ RuneTypeNumberExpDig: "RuneTypeNumberExpDig",
+
+ RuneTypeTrueT: "RuneTypeTrueT",
+ RuneTypeTrueR: "RuneTypeTrueR",
+ RuneTypeTrueU: "RuneTypeTrueU",
+ RuneTypeTrueE: "RuneTypeTrueE",
+
+ RuneTypeFalseF: "RuneTypeFalseF",
+ RuneTypeFalseA: "RuneTypeFalseA",
+ RuneTypeFalseL: "RuneTypeFalseL",
+ RuneTypeFalseS: "RuneTypeFalseS",
+ RuneTypeFalseE: "RuneTypeFalseE",
+
+ RuneTypeNullN: "RuneTypeNullN",
+ RuneTypeNullU: "RuneTypeNullU",
+ RuneTypeNullL1: "RuneTypeNullL1",
+ RuneTypeNullL2: "RuneTypeNullL2",
+
+ RuneTypeEOF: "RuneTypeEOF",
+ }[t]
+ if ok {
+ return str
+ }
+ return fmt.Sprintf("RuneType(%d)", t)
+}
+
func (t RuneType) String() string {
str, ok := map[RuneType]string{
RuneTypeError: "x",
diff --git a/parse_scan.go b/parse_scan.go
new file mode 100644
index 0000000..e75f1c5
--- /dev/null
+++ b/parse_scan.go
@@ -0,0 +1,241 @@
+// Copyright (C) 2022 Luke Shumaker <lukeshu@lukeshu.com>
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package lowmemjson
+
+import (
+ "errors"
+ "fmt"
+ "io"
+)
+
+type ReadError struct {
+ Err error
+ Offset int64
+}
+
+func (e *ReadError) Error() string {
+ return fmt.Sprintf("json: I/O error at input byte %v: %v", e.Offset, e.Err)
+}
+func (e *ReadError) Unwrap() error { return e.Err }
+
+type SyntaxError struct {
+ Err string
+ Offset int64
+}
+
+func (e *SyntaxError) Error() string {
+ return fmt.Sprintf("json: syntax error at input byte %v: %v", e.Offset, e.Err)
+}
+
+type runeTypeScanner interface {
+ // The returned error is a *ReadError, a *SyntaxError, or nil.
+ // An EOF condition is represented either as
+ //
+ // (char, size, RuneTypeEOF, nil)
+ //
+ // or
+ //
+ // (char, size, RuneTypeError, &SyntaxError{Offset: offset: Err: io.ErrUnexepctedEOF})
+ ReadRuneType() (rune, int, RuneType, error)
+ // The returned error is a *ReadError, a *SyntaxError, io.EOF, or nil.
+ ReadRune() (rune, int, error)
+ UnreadRune() error
+ Reset()
+ InputOffset() int64
+}
+
+// runeTypeScannerImpl /////////////////////////////////////////////////////////////////////////////
+
+type runeTypeScannerImpl struct {
+ inner io.RuneReader
+
+ parser Parser
+ offset int64
+
+ repeat bool
+ stuck bool
+ rRune rune
+ rSize int
+ rType RuneType
+ rErr error
+}
+
+var _ runeTypeScanner = (*runeTypeScannerImpl)(nil)
+
+func (sc *runeTypeScannerImpl) Reset() {
+ sc.parser.Reset()
+ sc.stuck = false
+ sc.repeat = false
+}
+
+func (sc *runeTypeScannerImpl) ReadRuneType() (rune, int, RuneType, error) {
+ switch {
+ case sc.stuck:
+ // do nothing
+ case sc.repeat:
+ if _, ok := sc.inner.(io.RuneScanner); ok {
+ sc.inner.ReadRune()
+ }
+ default:
+ var err error
+ sc.rRune, sc.rSize, err = sc.inner.ReadRune()
+ sc.offset += int64(sc.rSize)
+ switch err {
+ case nil:
+ sc.rType, err = sc.parser.HandleRune(sc.rRune)
+ if err != nil {
+ sc.rErr = &SyntaxError{
+ Offset: sc.offset,
+ Err: err.Error(),
+ }
+ } else {
+ sc.rErr = nil
+ }
+ case io.EOF:
+ sc.rType, err = sc.parser.HandleEOF()
+ if err != nil {
+ sc.rErr = &SyntaxError{
+ Offset: sc.offset,
+ Err: err.Error(),
+ }
+ } else {
+ sc.rErr = nil
+ }
+ default:
+ sc.rType = 0
+ sc.rErr = &ReadError{
+ Offset: sc.offset,
+ Err: err,
+ }
+ }
+ }
+ sc.repeat = false
+ sc.stuck = sc.rType == RuneTypeEOF || sc.rType == RuneTypeError
+ return sc.rRune, sc.rSize, sc.rType, sc.rErr
+}
+
+func (sc *runeTypeScannerImpl) ReadRune() (rune, int, error) {
+ r, s, t, e := sc.ReadRuneType()
+ switch t {
+ case RuneTypeEOF:
+ return 0, 0, io.EOF
+ case RuneTypeError:
+ return 0, 0, e
+ default:
+ return r, s, nil
+ }
+}
+
+var ErrInvalidUnreadRune = errors.New("lowmemjson: invalid use of UnreadRune")
+
+// UnreadRune undoes a call to .ReadRune() or .ReadRuneType(). If the
+// last call to .ReadRune() or .ReadRuneType() has already been
+// unread, or if that call returned an error or RuneTypeEOF, then
+// ErrInvalidRune is returned. Otherwise, nil is returned.
+func (sc *runeTypeScannerImpl) UnreadRune() error {
+ if sc.stuck || sc.repeat {
+ return ErrInvalidUnreadRune
+ }
+ sc.repeat = true
+ if rs, ok := sc.inner.(io.RuneScanner); ok {
+ _ = rs.UnreadRune()
+ }
+ return nil
+}
+
+func (sc *runeTypeScannerImpl) InputOffset() int64 {
+ ret := sc.offset
+ if sc.repeat {
+ ret -= int64(sc.rSize)
+ }
+ return ret
+}
+
+// noWSRuneTypeScanner /////////////////////////////////////////////////////////////////////////////
+
+type noWSRuneTypeScanner struct {
+ inner runeTypeScanner
+}
+
+var _ runeTypeScanner = (*noWSRuneTypeScanner)(nil)
+
+func (sc *noWSRuneTypeScanner) ReadRuneType() (rune, int, RuneType, error) {
+again:
+ r, s, t, e := sc.inner.ReadRuneType()
+ if t == RuneTypeSpace {
+ goto again
+ }
+ return r, s, t, e
+}
+
+func (sc *noWSRuneTypeScanner) ReadRune() (rune, int, error) {
+ r, s, t, e := sc.ReadRuneType()
+ switch t {
+ case RuneTypeEOF:
+ return 0, 0, io.EOF
+ case RuneTypeError:
+ return 0, 0, e
+ default:
+ return r, s, nil
+ }
+}
+
+func (sc *noWSRuneTypeScanner) UnreadRune() error { return sc.inner.UnreadRune() }
+func (sc *noWSRuneTypeScanner) Reset() { sc.inner.Reset() }
+func (sc *noWSRuneTypeScanner) InputOffset() int64 { return sc.inner.InputOffset() }
+
+// elemRuneTypeScanner /////////////////////////////////////////////////////////////////////////////
+
+type elemRuneTypeScanner struct {
+ inner runeTypeScanner
+
+ parser Parser
+ repeat bool
+ rType RuneType
+}
+
+var _ runeTypeScanner = (*elemRuneTypeScanner)(nil)
+
+func (sc *elemRuneTypeScanner) ReadRuneType() (rune, int, RuneType, error) {
+ r, s, t, e := sc.inner.ReadRuneType()
+
+ // Check if we need to insert a premature EOF
+ if t != RuneTypeError && t != RuneTypeEOF {
+ if sc.repeat {
+ sc.repeat = false
+ } else {
+ sc.rType, _ = sc.parser.HandleRune(r)
+ }
+ if sc.rType == RuneTypeEOF {
+ _ = sc.inner.UnreadRune()
+ }
+ t = sc.rType
+ }
+ if t == RuneTypeEOF {
+ return 0, 0, RuneTypeEOF, nil
+ }
+
+ return r, s, t, e
+}
+
+func (sc *elemRuneTypeScanner) ReadRune() (rune, int, error) {
+ r, s, t, e := sc.ReadRuneType()
+ switch t {
+ case RuneTypeEOF:
+ return 0, 0, io.EOF
+ case RuneTypeError:
+ return 0, 0, e
+ default:
+ return r, s, nil
+ }
+}
+
+func (sc *elemRuneTypeScanner) UnreadRune() error {
+ sc.repeat = true
+ return sc.inner.UnreadRune()
+}
+
+func (sc *elemRuneTypeScanner) InputOffset() int64 { return sc.inner.InputOffset() }
+func (sc *elemRuneTypeScanner) Reset() {}
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)
+ }
+}
diff --git a/reencode.go b/reencode.go
index c4c31a2..856de99 100644
--- a/reencode.go
+++ b/reencode.go
@@ -82,7 +82,7 @@ func (enc *ReEncoder) Close() error {
if enc.bufLen > 0 {
return &SyntaxError{
Offset: enc.inputPos,
- msg: fmt.Sprintf("%v: unflushed unicode garbage: %q", io.ErrUnexpectedEOF, enc.buf[:enc.bufLen]),
+ Err: fmt.Sprintf("%v: unflushed unicode garbage: %q", io.ErrUnexpectedEOF, enc.buf[:enc.bufLen]),
}
}
if _, err := enc.par.HandleEOF(); err != nil {