From 2ba5d96ccba101e6ccbf32b08e2fd18d4b8d7787 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Sat, 13 Aug 2022 22:05:20 -0600 Subject: parse_scan: Add reader abstractions on top of Parser --- parse_scan_test.go | 269 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 parse_scan_test.go (limited to 'parse_scan_test.go') 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 +// +// 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) + } +} -- cgit v1.2.3-54-g00ecf