<- Back to shtanton's homepage
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharlie Stanton <charlie@shtanton.xyz>2024-03-24 19:18:58 +0000
committerCharlie Stanton <charlie@shtanton.xyz>2024-03-24 19:18:58 +0000
commit81dcb87b2158f625ca10a20df5a93a42bbcaf26b (patch)
tree24d80db1428e5a61d2d96d9c929eaa82b1e40b92
parent0e4179fabbd0a66826f1375dae86ca7f681fb29d (diff)
downloadstred-go-81dcb87b2158f625ca10a20df5a93a42bbcaf26b.tar
Implements helper function navigateTo in json/write.go
-rw-r--r--json/read.go4
-rw-r--r--json/write.go303
-rw-r--r--json/write_test.go254
-rw-r--r--walk/walk.go2
-rw-r--r--walk/walk_test.go6
5 files changed, 353 insertions, 216 deletions
diff --git a/json/read.go b/json/read.go
index 8ed7e96..f3a0a65 100644
--- a/json/read.go
+++ b/json/read.go
@@ -42,10 +42,8 @@ func NewJSONReader(reader *bufio.Reader) *JSONReader {
}
}
-type PathSegment interface {}
-
type JSONReader struct {
- path []PathSegment
+ path []walk.PathSegment
structure []JSONReaderStructure
state JSONReaderState
reader *bufio.Reader
diff --git a/json/write.go b/json/write.go
index 9e349be..97b3f4e 100644
--- a/json/write.go
+++ b/json/write.go
@@ -4,25 +4,26 @@ import (
"bufio"
"fmt"
"main/walk"
+ // "text/scanner"
)
-func isNumber(value walk.Value) bool {
- _, isFloat := value.(walk.NumberScalar)
- return isFloat
+func isInt(segment walk.PathSegment) bool {
+ _, isInt := segment.(int)
+ return isInt
}
-func isString(value walk.Value) bool {
- _, isString := value.(walk.StringStructure)
+func isString(segment walk.PathSegment) bool {
+ _, isString := segment.(string)
return isString
}
-func segmentEqual(left walk.Value, right walk.Value) bool {
+func segmentEqual(left walk.PathSegment, right walk.PathSegment) bool {
switch left := left.(type) {
- case walk.NumberScalar:
- _, isNumber := right.(walk.NumberScalar)
- return isNumber
- case walk.StringStructure:
- right, isString := right.(walk.StringStructure)
+ case int:
+ _, isInt := right.(int)
+ return isInt
+ case string:
+ right, isString := right.(string)
return isString && left == right
default:
panic("Invalid path segment type")
@@ -32,7 +33,7 @@ func segmentEqual(left walk.Value, right walk.Value) bool {
type JSONWriterState int
const (
JSONWriterStateBeforeValue JSONWriterState = iota
- JSONWriterStateAfterValue JSONWriterState = iota
+ JSONWriterStateAfterValue
JSONWriterStateInArray
JSONWriterStateInMap
)
@@ -46,29 +47,243 @@ func NewJSONWriter(writer *bufio.Writer) *JSONWriter {
}
type JSONWriter struct {
- path []walk.Value
+ path []walk.PathSegment
writer *bufio.Writer
state JSONWriterState
}
-func (writer *JSONWriter) Write(item walk.WalkItem) error {
- path := item.Path
- for _, value := range item.Value {
- err := writer.write(path, value)
- if err != nil {
- return err
+func (writer *JSONWriter) navigateTo(keepLen int, path []walk.PathSegment, state JSONWriterState) {
+ for {
+ if keepLen > len(writer.path) {
+ panic("keepLen > len(writer.path)")
+ } else if len(writer.path) == keepLen {
+ if len(path) == 0 {
+ switch writer.state {
+ case JSONWriterStateBeforeValue:
+ switch state {
+ case JSONWriterStateBeforeValue:
+ return
+ case JSONWriterStateAfterValue:
+ panic("Cannot go from BeforeValue to AfterValue in navigateTo")
+ case JSONWriterStateInArray:
+ writer.writer.WriteRune('[')
+ writer.state = JSONWriterStateInArray
+ case JSONWriterStateInMap:
+ writer.writer.WriteRune('{')
+ writer.state = JSONWriterStateInMap
+ }
+ case JSONWriterStateAfterValue:
+ if state == JSONWriterStateAfterValue {
+ return
+ } else {
+ if keepLen == 0 {
+ writer.writer.WriteRune('\n')
+ writer.state = JSONWriterStateBeforeValue
+ } else {
+ writer.writer.WriteRune(',')
+ path = writer.path[len(writer.path) - 1:]
+ writer.path = writer.path[:len(writer.path) - 1]
+ keepLen -= 1
+ switch path[0].(type) {
+ case string:
+ writer.state = JSONWriterStateInMap
+ case int:
+ writer.state = JSONWriterStateInArray
+ }
+ }
+ }
+ case JSONWriterStateInArray:
+ if state == JSONWriterStateInArray {
+ return
+ } else {
+ writer.writer.WriteRune(']')
+ writer.state = JSONWriterStateAfterValue
+ }
+ case JSONWriterStateInMap:
+ if state == JSONWriterStateInMap {
+ return
+ } else {
+ writer.writer.WriteRune('}')
+ writer.state = JSONWriterStateAfterValue
+ }
+ }
+ } else {
+ // len(path) > 0
+ switch writer.state {
+ case JSONWriterStateBeforeValue:
+ switch path[0].(type) {
+ case string:
+ writer.writer.WriteRune('{')
+ writer.state = JSONWriterStateInMap
+ case int:
+ writer.writer.WriteRune('[')
+ writer.state = JSONWriterStateInArray
+ }
+ case JSONWriterStateAfterValue:
+ if keepLen == 0 {
+ writer.writer.WriteRune('\n')
+ writer.state = JSONWriterStateBeforeValue
+ } else {
+ writer.writer.WriteRune(',')
+ path = append(writer.path[len(writer.path) - 1:], path...)
+ writer.path = writer.path[:len(writer.path) - 1]
+ keepLen -= 1
+ switch path[0].(type) {
+ case string:
+ writer.state = JSONWriterStateInMap
+ case int:
+ writer.state = JSONWriterStateInArray
+ }
+ }
+ case JSONWriterStateInArray:
+ switch path[0].(type) {
+ case string:
+ writer.writer.WriteRune(']')
+ writer.state = JSONWriterStateAfterValue
+ case int:
+ writer.path = append(writer.path, path[0])
+ path = path[1:]
+ keepLen += 1
+ writer.state = JSONWriterStateBeforeValue
+ }
+ case JSONWriterStateInMap:
+ switch p := path[0].(type) {
+ case string:
+ fmt.Fprintf(writer.writer, "%q:", p)
+ writer.path = append(writer.path, p)
+ path = path[1:]
+ keepLen += 1
+ writer.state = JSONWriterStateBeforeValue
+ case int:
+ writer.writer.WriteRune('}')
+ writer.state = JSONWriterStateAfterValue
+ }
+ }
+ }
+ } else {
+ switch writer.state {
+ case JSONWriterStateBeforeValue:
+ panic("Cannot close structures from BeforeValue in navigateTo")
+ case JSONWriterStateAfterValue:
+ if len(writer.path) == keepLen + 1 {
+ if len(path) == 0 {
+ switch writer.path[len(writer.path) - 1].(type) {
+ case string:
+ if state == JSONWriterStateInMap {
+ writer.writer.WriteRune(',')
+ writer.path = writer.path[:len(writer.path) - 1]
+ writer.state = JSONWriterStateInMap
+ } else {
+ writer.writer.WriteRune('}')
+ writer.path = writer.path[:len(writer.path) - 1]
+ }
+ case int:
+ if state == JSONWriterStateInArray {
+ writer.writer.WriteRune(',')
+ writer.path = writer.path[:len(writer.path) - 1]
+ writer.state = JSONWriterStateInArray
+ } else {
+ writer.writer.WriteRune(']')
+ writer.path = writer.path[:len(writer.path) - 1]
+ }
+ }
+ } else {
+ switch writer.path[len(writer.path) - 1].(type) {
+ case string:
+ switch path[0].(type) {
+ case string:
+ writer.writer.WriteRune(',')
+ writer.path = writer.path[:len(writer.path) - 1]
+ writer.state = JSONWriterStateInMap
+ case int:
+ writer.writer.WriteRune('}')
+ writer.path = writer.path[:len(writer.path) - 1]
+ }
+ case int:
+ switch path[0].(type) {
+ case string:
+ writer.writer.WriteRune(']')
+ writer.path = writer.path[:len(writer.path) - 1]
+ case int:
+ writer.writer.WriteRune(',')
+ writer.path = writer.path[:len(writer.path) - 1]
+ writer.state = JSONWriterStateInArray
+ }
+ }
+ }
+ } else {
+ switch writer.path[len(writer.path) - 1].(type) {
+ case string:
+ writer.writer.WriteRune('}')
+ writer.path = writer.path[:len(writer.path) - 1]
+ case int:
+ writer.writer.WriteRune(']')
+ writer.path = writer.path[:len(writer.path) - 1]
+ }
+ }
+ case JSONWriterStateInArray:
+ writer.writer.WriteRune(']')
+ writer.state = JSONWriterStateAfterValue
+ case JSONWriterStateInMap:
+ writer.writer.WriteRune('}')
+ writer.state = JSONWriterStateAfterValue
+ }
}
}
+}
+
+func (writer *JSONWriter) Write(value walk.Value) error {
return nil
}
+func (writer *JSONWriter) pathWrite(value walk.Value, path []walk.PathSegment) error {
+ switch value := value.(type) {
+ case walk.NullValue:
+ return writer.write(path, value)
+ case walk.BoolValue:
+ return writer.write(path, value)
+ case walk.NumberValue:
+ return writer.write(path, value)
+ case walk.StringValue:
+ return writer.write(path, value)
+ case walk.ArrayValue:
+ if len(value) == 0 {
+ return writer.write(path, value)
+ } else {
+ for _, element := range value {
+ err := writer.pathWrite(element.Value, append(path, element.Index))
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+ case walk.MapValue:
+ if len(value) == 0 {
+ return writer.write(path, value)
+ } else {
+ for _, element := range value {
+ err := writer.pathWrite(element.Value, append(path, element.Key))
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+ case walk.RuneValue:
+ panic("Cannot write rune value")
+ default:
+ panic("Unrecognised value type")
+ }
+}
+
func (writer *JSONWriter) indent(level int) {
for i := 0; i < level; i += 1 {
writer.writer.WriteRune('\t')
}
}
-func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error {
+func (writer *JSONWriter) write(targetPath []walk.PathSegment, value walk.Value) error {
diversionPoint := 0
for diversionPoint < len(writer.path) && diversionPoint < len(targetPath) && segmentEqual(writer.path[diversionPoint], targetPath[diversionPoint]) {
diversionPoint += 1
@@ -92,10 +307,10 @@ func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error
if diversionPoint < len(targetPath) {
segment := targetPath[diversionPoint]
switch segment.(type) {
- case walk.NumberScalar:
+ case int:
writer.writer.WriteString("[\n")
goto inArray
- case walk.StringStructure:
+ case string:
writer.writer.WriteString("{\n")
goto inMap
default:
@@ -104,11 +319,11 @@ func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error
}
switch value := value.(type) {
- case walk.NullScalar:
+ case walk.NullValue:
writer.writer.WriteString("null")
writer.state = JSONWriterStateAfterValue
return nil
- case walk.BoolScalar:
+ case walk.BoolValue:
if value {
writer.writer.WriteString("true")
} else {
@@ -116,20 +331,20 @@ func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error
}
writer.state = JSONWriterStateAfterValue
return nil
- case walk.NumberScalar:
+ case walk.NumberValue:
writer.writer.WriteString(fmt.Sprintf("%v", value))
writer.state = JSONWriterStateAfterValue
return nil
- case walk.StringStructure:
+ case walk.StringValue:
writer.writer.WriteString(fmt.Sprintf("%q", value))
writer.state = JSONWriterStateAfterValue
return nil
- case walk.ArrayStructure:
+ case walk.ArrayValue:
// TODO: write the contents of the structures
writer.writer.WriteString("[\n")
writer.state = JSONWriterStateInArray
return nil
- case walk.MapStructure:
+ case walk.MapValue:
writer.writer.WriteString("{\n")
writer.state = JSONWriterStateInMap
return nil
@@ -143,15 +358,15 @@ func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error
if diversionPoint == len(writer.path) - 1 && diversionPoint < len(targetPath) {
segment := writer.path[diversionPoint]
switch segment.(type) {
- case walk.NumberScalar:
- _, isNumber := targetPath[diversionPoint].(walk.NumberScalar)
+ case int:
+ _, isNumber := targetPath[diversionPoint].(walk.NumberValue)
if isNumber {
writer.writer.WriteString(",\n")
writer.path = writer.path[:diversionPoint]
goto inArray
}
- case walk.StringStructure:
- _, isString := targetPath[diversionPoint].(walk.StringStructure)
+ case string:
+ _, isString := targetPath[diversionPoint].(walk.StringValue)
if isString {
writer.writer.WriteString(",\n")
writer.path = writer.path[:diversionPoint]
@@ -164,10 +379,10 @@ func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error
writer.writer.WriteString("\n")
switch writer.path[len(writer.path) - 1].(type) {
- case walk.NumberScalar:
+ case int:
writer.path = writer.path[:len(writer.path) - 1]
goto inArray
- case walk.StringStructure:
+ case string:
writer.path = writer.path[:len(writer.path) - 1]
goto inMap
default:
@@ -186,9 +401,9 @@ func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error
diversionPoint--
writer.path = writer.path[:diversionPoint]
switch segment.(type) {
- case walk.NumberScalar:
+ case int:
goto inArray
- case walk.StringStructure:
+ case string:
goto inMap
default:
panic("Invalid segment type")
@@ -204,9 +419,9 @@ func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error
diversionPoint--
writer.path = writer.path[:diversionPoint]
switch segment.(type) {
- case walk.NumberScalar:
+ case int:
goto inArray
- case walk.StringStructure:
+ case string:
goto inMap
default:
panic("Invalid segment type")
@@ -222,12 +437,12 @@ func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error
if diversionPoint < len(targetPath) {
switch s := targetPath[diversionPoint].(type) {
- case walk.NumberScalar:
+ case int:
writer.path = append(writer.path, s)
diversionPoint++
writer.indent(len(writer.path))
goto beforeValue
- case walk.StringStructure:
+ case string:
writer.indent(len(writer.path))
writer.writer.WriteString("]")
goto afterValue
@@ -250,11 +465,11 @@ func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error
if diversionPoint < len(targetPath) {
switch s := targetPath[diversionPoint].(type) {
- case walk.NumberScalar:
+ case int:
writer.indent(len(writer.path))
writer.writer.WriteString("}")
goto afterValue
- case walk.StringStructure:
+ case string:
writer.path = append(writer.path, s)
diversionPoint++
writer.indent(len(writer.path))
@@ -278,11 +493,11 @@ func (writer *JSONWriter) AssertDone() {
}
for i := len(writer.path) - 1; i >= 0; i -= 1 {
switch writer.path[i].(type) {
- case walk.NumberScalar:
+ case int:
writer.writer.WriteString("\n")
writer.indent(i)
writer.writer.WriteString("]")
- case walk.StringStructure:
+ case string:
writer.writer.WriteString("\n")
writer.indent(i)
writer.writer.WriteString("}")
diff --git a/json/write_test.go b/json/write_test.go
index 60ad609..508ccfa 100644
--- a/json/write_test.go
+++ b/json/write_test.go
@@ -5,179 +5,101 @@ import (
"main/walk"
"strings"
"testing"
- "encoding/json"
)
-type writeTester struct {
- writer *JSONWriter
- output *strings.Builder
- t *testing.T
-}
-
-func (t writeTester) write(value walk.Value, path ...interface{}) writeTester {
- var pathValues []walk.Value
- for _, segment := range path {
- switch s := segment.(type) {
- case int:
- pathValues = append(pathValues, walk.NumberScalar(s))
- case string:
- pathValues = append(pathValues, walk.StringStructure(s))
- default:
- panic("Invalid path segment type")
- }
- }
-
- t.writer.Write(walk.WalkItem {
- Value: []walk.Value{value},
- Path: pathValues,
- })
-
- return t
-}
-
-func (t writeTester) expect(expected interface{}) writeTester {
- t.writer.AssertDone()
- output := t.output.String()
- var actual interface{}
- err := json.Unmarshal([]byte(output), &actual)
- if err != nil {
- t.t.Log("Produced invalid JSON:")
- t.t.Log(output)
- t.t.FailNow()
- }
-
- expectedBytes, err1 := json.Marshal(expected)
- actualBytes, err2 := json.Marshal(actual)
-
- if err1 != nil || err2 != nil {
- panic("Error marshalling")
- }
-
- expectedString := string(expectedBytes)
- actualString := string(actualBytes)
-
- if expectedString != actualString {
- t.t.Log("Expected:")
- t.t.Log(expectedString)
- t.t.Log("Found:")
- t.t.Log(actualString)
- t.t.FailNow()
+func TestNavigateTo(t *testing.T) {
+ type testCase struct {
+ fromPath []walk.PathSegment
+ fromState JSONWriterState
+ toKeep int
+ toPath []walk.PathSegment
+ toState JSONWriterState
+ expected string
}
- return t
-}
-
-func tester(t *testing.T) writeTester {
- var output strings.Builder
- return writeTester {
- writer: NewJSONWriter(bufio.NewWriter(&output)),
- output: &output,
- t: t,
- }
-}
-
-func TestImplicitStructures(t *testing.T) {
- tester(t).write(
- walk.NullScalar{},
- 0, "test", 0,
- ).expect(
- []interface{}{
- map[string]interface{}{
- "test": []interface{}{
- nil,
- },
- },
+ tests := []testCase {
+ {
+ fromPath: []walk.PathSegment{},
+ fromState: JSONWriterStateBeforeValue,
+ toKeep: 0,
+ toPath: []walk.PathSegment{"a", "b", "c"},
+ toState: JSONWriterStateBeforeValue,
+ expected: `{"a":{"b":{"c":`,
},
- )
-}
-
-func TestExplicitMap(t *testing.T) {
- tester(t).write(
- make(walk.MapStructure),
- ).write(
- walk.NullScalar{},
- "test",
- ).expect(
- map[string]interface{}{
- "test": nil,
+ {
+ fromPath: []walk.PathSegment{},
+ fromState: JSONWriterStateBeforeValue,
+ toKeep: 0,
+ toPath: []walk.PathSegment{0, "a", "a", 0, 1},
+ toState: JSONWriterStateInMap,
+ expected: `[{"a":{"a":[[{`,
},
- )
-}
-
-func TestExplicitNested(t *testing.T) {
- tester(t).write(
- make(walk.MapStructure),
- ).write(
- make(walk.MapStructure),
- "first",
- ).write(
- make(walk.MapStructure),
- "first", "second",
- ).write(
- walk.StringStructure("test"),
- "first", "second", "third",
- ).expect(
- map[string]interface{}{
- "first": map[string]interface{}{
- "second": map[string]interface{}{
- "third": "test",
- },
- },
+ {
+ fromPath: []walk.PathSegment{},
+ fromState: JSONWriterStateInMap,
+ toKeep: 0,
+ toPath: []walk.PathSegment{},
+ toState: JSONWriterStateInArray,
+ expected: "}\n[",
},
- )
-}
-
-func TestArrayOfMaps(t *testing.T) {
- tester(t).write(
- walk.ArrayStructure{},
- ).write(
- make(walk.MapStructure),
- 0,
- ).write(
- walk.NumberScalar(0),
- 0, "number",
- ).write(
- make(walk.MapStructure),
- 1,
- ).write(
- walk.NumberScalar(1),
- 1, "nested", "number",
- ).write(
- make(walk.MapStructure),
- 2,
- ).write(
- walk.NumberScalar(2),
- 2, "number",
- ).expect(
- []interface{}{
- map[string]interface{}{
- "number": 0,
- },
- map[string]interface{}{
- "nested": map[string]interface{}{
- "number": 1,
- },
- },
- map[string]interface{}{
- "number": 2,
- },
+ {
+ fromPath: []walk.PathSegment{0, 0},
+ fromState: JSONWriterStateBeforeValue,
+ toKeep: 2,
+ toPath: []walk.PathSegment{"a"},
+ toState: JSONWriterStateInArray,
+ expected: `{"a":[`,
},
- )
-}
-
-func TestStructures1(t *testing.T) {
- tester(t).write(
- make(walk.MapStructure),
- ).write(
- make(walk.MapStructure),
- "map",
- ).write(
- walk.ArrayStructure{},
- "array",
- ).expect(
- map[string]interface{}{
- "map": map[string]interface{}{},
- "array": []interface{}{},
+ {
+ fromPath: []walk.PathSegment{"a", "b"},
+ fromState: JSONWriterStateAfterValue,
+ toKeep: 1,
+ toPath: []walk.PathSegment{"c"},
+ toState: JSONWriterStateBeforeValue,
+ expected: `,"c":`,
},
- )
+ {
+ fromPath: []walk.PathSegment{0, "a"},
+ fromState: JSONWriterStateInArray,
+ toKeep: 0,
+ toPath: []walk.PathSegment{"b", 1},
+ toState: JSONWriterStateInMap,
+ expected: `]}]` + "\n" + `{"b":[{`,
+ },
+ {
+ fromPath: []walk.PathSegment{"a", "b", "c", "d", "e"},
+ fromState: JSONWriterStateAfterValue,
+ toKeep: 2,
+ toPath: []walk.PathSegment{"f", "g", "h"},
+ toState: JSONWriterStateBeforeValue,
+ expected: `}},"f":{"g":{"h":`,
+ },
+ {
+ fromPath: []walk.PathSegment{"a", 0, "b"},
+ fromState: JSONWriterStateAfterValue,
+ toKeep: 2,
+ toPath: []walk.PathSegment{0},
+ toState: JSONWriterStateBeforeValue,
+ expected: `},[`,
+ },
+ }
+
+ for i, test := range tests {
+ var writer strings.Builder
+ jsonWriter := &JSONWriter {
+ path: test.fromPath,
+ writer: bufio.NewWriter(&writer),
+ state: test.fromState,
+ }
+ jsonWriter.navigateTo(
+ test.toKeep,
+ test.toPath,
+ test.toState,
+ )
+ jsonWriter.writer.Flush()
+ res := writer.String()
+ if res != test.expected {
+ t.Errorf(`Test %d: Expected '%s' found '%s'`, i, test.expected, res)
+ }
+ }
}
diff --git a/walk/walk.go b/walk/walk.go
index 289d9ee..fc9e9de 100644
--- a/walk/walk.go
+++ b/walk/walk.go
@@ -5,6 +5,8 @@ import (
"strings"
)
+type PathSegment interface {}
+
type Value interface {
value()
Debug() string
diff --git a/walk/walk_test.go b/walk/walk_test.go
index c05da02..759c501 100644
--- a/walk/walk_test.go
+++ b/walk/walk_test.go
@@ -7,9 +7,9 @@ import (
func TestValueIter(t *testing.T) {
values := ValueList{
- NumberScalar(1),
- NumberScalar(2),
- NumberScalar(3),
+ NumberValue(1),
+ NumberValue(2),
+ NumberValue(3),
}
valuesCopy := ValueList{}