From c6c1c67402f89c94fee040c2c740d96e61cab669 Mon Sep 17 00:00:00 2001 From: Charlie Stanton Date: Mon, 25 Mar 2024 14:35:09 +0000 Subject: Finish implementing new JSON writer --- json/write.go | 467 ++++++++++++++++++++++++----------------------------- json/write_test.go | 141 ++++++++++++++++ 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) + } + } +} -- cgit v1.2.3