<- Back to shtanton's homepage
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharlie Stanton <charlie@shtanton.xyz>2024-03-25 14:35:09 +0000
committerCharlie Stanton <charlie@shtanton.xyz>2024-03-25 14:35:09 +0000
commitc6c1c67402f89c94fee040c2c740d96e61cab669 (patch)
treed829c7990dd327b543fe943dc5b01752fa5ccdea
parent81dcb87b2158f625ca10a20df5a93a42bbcaf26b (diff)
downloadstred-go-c6c1c67402f89c94fee040c2c740d96e61cab669.tar
Finish implementing new JSON writer
-rw-r--r--json/write.go467
-rw-r--r--json/write_test.go141
2 files changed, 355 insertions, 253 deletions
diff --git a/json/write.go b/json/write.go
index 97b3f4e..c2a220e 100644
--- a/json/write.go
+++ b/json/write.go
@@ -4,32 +4,8 @@ import (
"bufio"
"fmt"
"main/walk"
- // "text/scanner"
)
-func isInt(segment walk.PathSegment) bool {
- _, isInt := segment.(int)
- return isInt
-}
-
-func isString(segment walk.PathSegment) bool {
- _, isString := segment.(string)
- return isString
-}
-
-func segmentEqual(left walk.PathSegment, right walk.PathSegment) bool {
- switch left := left.(type) {
- case int:
- _, isInt := right.(int)
- return isInt
- case string:
- right, isString := right.(string)
- return isString && left == right
- default:
- panic("Invalid path segment type")
- }
-}
-
type JSONWriterState int
const (
JSONWriterStateBeforeValue JSONWriterState = iota
@@ -232,275 +208,260 @@ func (writer *JSONWriter) navigateTo(keepLen int, path []walk.PathSegment, state
}
}
-func (writer *JSONWriter) Write(value walk.Value) error {
- return nil
-}
+func (writer *JSONWriter) inMapAt(keepLen int, path []walk.PathSegment) bool {
+ if keepLen < len(path) {
+ return false
+ }
-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")
+ if keepLen == len(writer.path) {
+ return writer.state == JSONWriterStateInMap
}
+
+ _, isString := writer.path[keepLen].(string)
+ return isString
}
-func (writer *JSONWriter) indent(level int) {
- for i := 0; i < level; i += 1 {
- writer.writer.WriteRune('\t')
+func (writer *JSONWriter) inArrayAt(keepLen int, path []walk.PathSegment) bool {
+ if keepLen < len(path) {
+ return false
+ }
+
+ if keepLen == len(writer.path) {
+ return writer.state == JSONWriterStateInArray
}
+
+ _, isInt := writer.path[keepLen].(int)
+ return isInt
}
-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
+func countPrefix(current []walk.PathSegment, target []walk.PathSegment) int {
+ for i, c := range current {
+ if i >= len(target) || c != target[i] {
+ return i
+ }
}
+ return len(current)
+}
- switch writer.state {
- case JSONWriterStateBeforeValue:
- goto beforeValue
- case JSONWriterStateAfterValue:
- goto afterValue
- case JSONWriterStateInArray:
- goto inArray
- case JSONWriterStateInMap:
- goto inMap
+/*
+{
+ "a": {
+ "b": {
+ "c": null
+ },
+ "d": [],
+ "e": {},
+ "f": {"g": 5}
}
+}
- beforeValue: {
- if diversionPoint < len(writer.path) {
- panic("Writing a value before doing a necessary leave")
- }
- if diversionPoint < len(targetPath) {
- segment := targetPath[diversionPoint]
- switch segment.(type) {
- case int:
- writer.writer.WriteString("[\n")
- goto inArray
- case string:
- writer.writer.WriteString("{\n")
- goto inMap
- default:
- panic("Invalid path segment")
- }
- }
+write(true, true, [], {"a": ...})
+ write(true, true, ["a"], {"b": ..., "d": [], "e": {}, "f": ...})
+ write(true, false, ["a", "b"], {"c": null})
+ write(true, false, ["a", "b", "c"], null)
+ navigateTo(["a", "b", "c"], BeforeValue)
+ > null
+ > }
+ > , "d":
+ write(false, false, ["a", "d"], [])
+ > []
+ > , "e":
+ write(false, false, ["a", "e"], {})
+ > {}
+ > , "f":
+ write(false, true, ["a", "f"], {"g": 5})
+ > {"g":
+ write(false, true, ["a", "f", "g"], 5)
+ > 5
+ path = ["a", "f", "g"]
+ state = AfterValue
+*/
- switch value := value.(type) {
- case walk.NullValue:
- writer.writer.WriteString("null")
+/*
+{
+ "a": {}
+}
+
+write(true, true, [], {"a": {}})
+ write(true, true, ["a"], {})
+ if not in map at ["a"]
+ navigateTo(["a"], InMap)
+*/
+
+/*
+{
+ "a": {},
+ "b": null,
+ "c": {}
+}
+
+write(true, true, [], {...})
+ write(true, false, ["a"], {})
+ if in map at ["a"]
+ navigateTo(["a"], AfterValue)
+ else
+ navigateTo(["a"], BeforeValue)
+ > {}
+> , "b":
+ write(false, false, ["b"], null)
+ > null
+> , "c":
+ write(false, true, ["c"], {})
+ > {
+ path = ["c"]
+ state = InMap
+*/
+
+func (writer *JSONWriter) write(first bool, last bool, path []walk.PathSegment, value walk.Value) {
+ switch value := value.(type) {
+ case walk.NullValue:
+ if first {
+ keepLen := countPrefix(writer.path, path)
+ writer.navigateTo(keepLen, path[keepLen:], JSONWriterStateBeforeValue)
+ }
+ writer.writer.WriteString("null")
+ if last {
+ writer.path = path
writer.state = JSONWriterStateAfterValue
- return nil
- case walk.BoolValue:
- if value {
- writer.writer.WriteString("true")
- } else {
- writer.writer.WriteString("false")
- }
+ }
+ case walk.BoolValue:
+ if first {
+ keepLen := countPrefix(writer.path, path)
+ writer.navigateTo(keepLen, path[keepLen:], JSONWriterStateBeforeValue)
+ }
+ if value {
+ writer.writer.WriteString("true")
+ } else {
+ writer.writer.WriteString("false")
+ }
+ if last {
+ writer.path = path
writer.state = JSONWriterStateAfterValue
- return nil
- case walk.NumberValue:
- writer.writer.WriteString(fmt.Sprintf("%v", value))
+ }
+ case walk.NumberValue:
+ if first {
+ keepLen := countPrefix(writer.path, path)
+ writer.navigateTo(keepLen, path[keepLen:], JSONWriterStateBeforeValue)
+ }
+ fmt.Fprintf(writer.writer, "%v", value)
+ if last {
+ writer.path = path
writer.state = JSONWriterStateAfterValue
- return nil
- case walk.StringValue:
- writer.writer.WriteString(fmt.Sprintf("%q", value))
+ }
+ case walk.StringValue:
+ if first {
+ keepLen := countPrefix(writer.path, path)
+ writer.navigateTo(keepLen, path[keepLen:], JSONWriterStateBeforeValue)
+ }
+ fmt.Fprintf(writer.writer, "%q", value)
+ if last {
+ writer.path = path
writer.state = JSONWriterStateAfterValue
- return nil
- case walk.ArrayValue:
- // TODO: write the contents of the structures
- writer.writer.WriteString("[\n")
- writer.state = JSONWriterStateInArray
- return nil
- case walk.MapValue:
- writer.writer.WriteString("{\n")
- writer.state = JSONWriterStateInMap
- return nil
- default:
- panic("Invalid value type")
}
- }
-
- afterValue: {
- if diversionPoint < len(writer.path) {
- if diversionPoint == len(writer.path) - 1 && diversionPoint < len(targetPath) {
- segment := writer.path[diversionPoint]
- switch segment.(type) {
- case int:
- _, isNumber := targetPath[diversionPoint].(walk.NumberValue)
- if isNumber {
- writer.writer.WriteString(",\n")
- writer.path = writer.path[:diversionPoint]
- goto inArray
+ case walk.ArrayValue:
+ if len(value) == 0 {
+ if first {
+ if last {
+ keepLen := countPrefix(writer.path, path)
+ if !writer.inArrayAt(keepLen, path[keepLen:]) {
+ writer.navigateTo(keepLen, path[keepLen:], JSONWriterStateInArray)
}
- case string:
- _, isString := targetPath[diversionPoint].(walk.StringValue)
- if isString {
- writer.writer.WriteString(",\n")
- writer.path = writer.path[:diversionPoint]
- goto inMap
+ } else {
+ keepLen := countPrefix(writer.path, path)
+ if writer.inArrayAt(keepLen, path[keepLen:]) {
+ writer.navigateTo(keepLen, path[keepLen:], JSONWriterStateAfterValue)
+ } else {
+ writer.navigateTo(keepLen, path[keepLen:], JSONWriterStateBeforeValue)
+ writer.writer.WriteString("[]")
}
- default:
- panic("Invalid segment type")
+ }
+ } else {
+ if last {
+ writer.writer.WriteRune('[')
+ writer.path = path
+ writer.state = JSONWriterStateInArray
+ } else {
+ writer.writer.WriteString("[]")
}
}
-
- writer.writer.WriteString("\n")
- switch writer.path[len(writer.path) - 1].(type) {
- case int:
- writer.path = writer.path[:len(writer.path) - 1]
- goto inArray
- case string:
- writer.path = writer.path[:len(writer.path) - 1]
- goto inMap
- default:
- panic("Invalid segment type")
+ } else {
+ if !first {
+ writer.writer.WriteRune('[')
}
- }
-
- // TODO: handle len(writer.path) == 0
- if diversionPoint < len(targetPath) {
- if len(writer.path) == 0 {
- writer.writer.WriteString("\n")
- goto beforeValue
+ for i, el := range value {
+ if i != 0 {
+ writer.writer.WriteRune(',')
+ }
+ writer.write(first && i == 0, last && i == len(value) - 1, append(path, el.Index), el.Value)
}
- segment := writer.path[diversionPoint - 1]
- writer.writer.WriteString(",")
- diversionPoint--
- writer.path = writer.path[:diversionPoint]
- switch segment.(type) {
- case int:
- goto inArray
- case string:
- goto inMap
- default:
- panic("Invalid segment type")
+ if !last {
+ writer.writer.WriteRune(']')
}
}
-
- if len(writer.path) == 0 {
- writer.writer.WriteString("\n")
- goto beforeValue
- }
- segment := writer.path[diversionPoint - 1]
- writer.writer.WriteString(",\n")
- diversionPoint--
- writer.path = writer.path[:diversionPoint]
- switch segment.(type) {
- case int:
- goto inArray
- case string:
- goto inMap
- default:
- panic("Invalid segment type")
- }
- }
-
- inArray: {
- if diversionPoint < len(writer.path) {
- writer.indent(len(writer.path))
- writer.writer.WriteString("]")
- goto afterValue
- }
-
- if diversionPoint < len(targetPath) {
- switch s := targetPath[diversionPoint].(type) {
- case int:
- writer.path = append(writer.path, s)
- diversionPoint++
- writer.indent(len(writer.path))
- goto beforeValue
- case string:
- writer.indent(len(writer.path))
- writer.writer.WriteString("]")
- goto afterValue
- default:
- panic("Invalid segment type")
+ case walk.MapValue:
+ if len(value) == 0 {
+ if first {
+ if last {
+ keepLen := countPrefix(writer.path, path)
+ if !writer.inMapAt(keepLen, path[keepLen:]) {
+ writer.navigateTo(keepLen, path[keepLen:], JSONWriterStateInMap)
+ }
+ } else {
+ keepLen := countPrefix(writer.path, path)
+ if writer.inMapAt(keepLen, path[keepLen:]) {
+ writer.navigateTo(keepLen, path[keepLen:], JSONWriterStateAfterValue)
+ } else {
+ writer.navigateTo(keepLen, path[keepLen:], JSONWriterStateBeforeValue)
+ writer.writer.WriteString("{}")
+ }
+ }
+ } else {
+ if last {
+ writer.writer.WriteRune('{')
+ writer.path = path
+ writer.state = JSONWriterStateInMap
+ } else {
+ writer.writer.WriteString("{}")
+ }
}
- }
-
- writer.indent(len(writer.path))
- writer.writer.WriteString("]")
- goto afterValue
- }
-
- inMap: {
- if diversionPoint < len(writer.path) {
- writer.indent(len(writer.path))
- writer.writer.WriteString("}")
- goto afterValue
- }
-
- if diversionPoint < len(targetPath) {
- switch s := targetPath[diversionPoint].(type) {
- case int:
- writer.indent(len(writer.path))
- writer.writer.WriteString("}")
- goto afterValue
- case string:
- writer.path = append(writer.path, s)
- diversionPoint++
- writer.indent(len(writer.path))
- writer.writer.WriteString(fmt.Sprintf("%q: ", s))
- goto beforeValue
+ } else {
+ if !first {
+ writer.writer.WriteRune('{')
+ }
+ for i, el := range value {
+ if i != 0 {
+ writer.writer.WriteRune(',')
+ }
+ if i != 0 || !first {
+ fmt.Fprintf(writer.writer, "%q:", el.Key)
+ }
+ writer.write(first && i == 0, last && i == len(value) - 1, append(path, el.Key), el.Value)
+ }
+ if !last {
+ writer.writer.WriteRune('}')
}
}
-
- writer.indent(len(writer.path))
- writer.writer.WriteString("}")
- goto afterValue
}
}
+func (writer *JSONWriter) Write(value walk.Value) error {
+ writer.write(true, true, nil, value)
+ return nil
+}
+
func (writer *JSONWriter) AssertDone() {
switch writer.state {
case JSONWriterStateInArray:
- writer.writer.WriteString("]")
+ writer.writer.WriteRune(']')
case JSONWriterStateInMap:
- writer.writer.WriteString("}")
+ writer.writer.WriteRune('}')
}
for i := len(writer.path) - 1; i >= 0; i -= 1 {
switch writer.path[i].(type) {
case int:
- writer.writer.WriteString("\n")
- writer.indent(i)
- writer.writer.WriteString("]")
+ writer.writer.WriteRune(']')
case string:
- writer.writer.WriteString("\n")
- writer.indent(i)
- writer.writer.WriteString("}")
+ writer.writer.WriteRune('}')
default:
panic("Invalid path segment type")
}
diff --git a/json/write_test.go b/json/write_test.go
index 508ccfa..05b228e 100644
--- a/json/write_test.go
+++ b/json/write_test.go
@@ -103,3 +103,144 @@ func TestNavigateTo(t *testing.T) {
}
}
}
+
+func TestWrite(t *testing.T) {
+ type testCase struct {
+ values []walk.Value
+ expected string
+ }
+
+ tests := []testCase {
+ {
+ values: []walk.Value {
+ walk.MapValue {{
+ Key: "a",
+ Value: walk.MapValue {{
+ Key: "b",
+ Value: walk.StringValue("c"),
+ }},
+ }},
+ },
+ expected: `{"a":{"b":"c"}}`,
+ },
+ {
+ values: []walk.Value {
+ walk.MapValue {{
+ Key: "a",
+ Value: walk.MapValue {{
+ Key: "b",
+ Value: walk.StringValue("c"),
+ }},
+ }},
+ walk.MapValue {{
+ Key: "a",
+ Value: walk.MapValue {{
+ Key: "d",
+ Value: walk.NullValue{},
+ }},
+ }},
+ walk.MapValue {{
+ Key: "a",
+ Value: walk.MapValue{},
+ }},
+ walk.MapValue {{
+ Key: "a",
+ Value: walk.MapValue {{
+ Key: "e",
+ Value: walk.NumberValue(-4.3),
+ }},
+ }},
+ },
+ expected: `{"a":{"b":"c","d":null,"e":-4.3}}`,
+ },
+ {
+ values: []walk.Value {
+ walk.MapValue {{
+ Key: "a",
+ Value: walk.MapValue{{
+ Key: "aa",
+ Value: walk.StringValue("aav"),
+ }},
+ }},
+ walk.MapValue {{
+ Key: "a",
+ Value: walk.MapValue{},
+ }, {
+ Key: "b",
+ Value: walk.MapValue {{
+ Key: "bb",
+ Value: walk.StringValue("bbv"),
+ }},
+ }, {
+ Key: "c",
+ Value: walk.MapValue{},
+ }},
+ walk.MapValue {{
+ Key: "c",
+ Value: walk.MapValue {{
+ Key: "cc",
+ Value: walk.StringValue("ccv"),
+ }},
+ }},
+ },
+ expected: `{"a":{"aa":"aav"},"b":{"bb":"bbv"},"c":{"cc":"ccv"}}`,
+ },
+ {
+ values: []walk.Value {
+ walk.ArrayValue {{
+ Index: 0,
+ Value: walk.ArrayValue {{
+ Index: 5,
+ Value: walk.NumberValue(100),
+ }},
+ }},
+ walk.ArrayValue {{
+ Index: 0,
+ Value: walk.ArrayValue{},
+ }, {
+ Index: 0,
+ Value: walk.ArrayValue{},
+ }},
+ walk.ArrayValue {{
+ Index: 0,
+ Value: walk.NullValue{},
+ }, {
+ Index: 0,
+ Value: walk.ArrayValue{},
+ }},
+ walk.ArrayValue {{
+ Index: 0,
+ Value: walk.ArrayValue{},
+ }},
+ walk.ArrayValue {{
+ Index: 0,
+ Value: walk.ArrayValue {{
+ Index: 200,
+ Value: walk.NumberValue(200),
+ }},
+ }},
+ },
+ expected: `[[100],[],null,[200]]`,
+ },
+ }
+
+ for i, test := range tests {
+ var writer strings.Builder
+ jsonWriter := &JSONWriter {
+ path: nil,
+ writer: bufio.NewWriter(&writer),
+ state: JSONWriterStateBeforeValue,
+ }
+
+ for _, value := range test.values {
+ jsonWriter.Write(value)
+ }
+ jsonWriter.AssertDone()
+
+ jsonWriter.writer.Flush()
+ res := writer.String()
+ if res != test.expected {
+ t.Errorf(`Test %d: Expected '%s' found '%s'`, i, test.expected, res)
+ }
+ }
+}