<- Back to shtanton's homepage
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharlie Stanton <charlie@shtanton.xyz>2023-07-21 13:00:57 +0100
committerCharlie Stanton <charlie@shtanton.xyz>2023-07-21 13:00:57 +0100
commitc52794f9d420e319900f27e9f16c8444e8842e92 (patch)
treed2dedfba9879d4d412f298fcf258c37dcbf9233c
parentac153f2b90b966baaf132a487514ae2194a64dd5 (diff)
downloadstred-go-c52794f9d420e319900f27e9f16c8444e8842e92.tar
Fixes JSONWriter to work with implicit data structures
-rw-r--r--json/write.go184
-rw-r--r--json/write_test.go183
-rw-r--r--subex/parse.go1
3 files changed, 322 insertions, 46 deletions
diff --git a/json/write.go b/json/write.go
index d024a56..9e349be 100644
--- a/json/write.go
+++ b/json/write.go
@@ -69,25 +69,40 @@ func (writer *JSONWriter) indent(level int) {
}
func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error {
- diversionPoint := len(writer.path)
+ diversionPoint := 0
for diversionPoint < len(writer.path) && diversionPoint < len(targetPath) && segmentEqual(writer.path[diversionPoint], targetPath[diversionPoint]) {
diversionPoint += 1
}
-
+
switch writer.state {
case JSONWriterStateBeforeValue:
goto beforeValue
case JSONWriterStateAfterValue:
goto afterValue
- case JSONWriterStateInMap:
- goto inMap
case JSONWriterStateInArray:
goto inArray
- default:
- panic("Invalid JSONWriterState")
+ case JSONWriterStateInMap:
+ goto inMap
}
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 walk.NumberScalar:
+ writer.writer.WriteString("[\n")
+ goto inArray
+ case walk.StringStructure:
+ writer.writer.WriteString("{\n")
+ goto inMap
+ default:
+ panic("Invalid path segment")
+ }
+ }
+
switch value := value.(type) {
case walk.NullScalar:
writer.writer.WriteString("null")
@@ -124,66 +139,143 @@ func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error
}
afterValue: {
- if len(writer.path) == 0 {
- writer.writer.WriteRune('\n')
- goto beforeValue
- }
- switch writer.path[len(writer.path) - 1].(type) {
- case walk.NumberScalar:
- // TODO: second part of this condition might be redundant
- if len(writer.path) - 1 <= diversionPoint && len(targetPath) >= len(writer.path) && isNumber(targetPath[len(writer.path) - 1]) {
- writer.writer.WriteString(",\n")
+ if diversionPoint < len(writer.path) {
+ if diversionPoint == len(writer.path) - 1 && diversionPoint < len(targetPath) {
+ segment := writer.path[diversionPoint]
+ switch segment.(type) {
+ case walk.NumberScalar:
+ _, isNumber := targetPath[diversionPoint].(walk.NumberScalar)
+ if isNumber {
+ writer.writer.WriteString(",\n")
+ writer.path = writer.path[:diversionPoint]
+ goto inArray
+ }
+ case walk.StringStructure:
+ _, isString := targetPath[diversionPoint].(walk.StringStructure)
+ if isString {
+ writer.writer.WriteString(",\n")
+ writer.path = writer.path[:diversionPoint]
+ goto inMap
+ }
+ default:
+ panic("Invalid segment type")
+ }
+ }
+
+ writer.writer.WriteString("\n")
+ switch writer.path[len(writer.path) - 1].(type) {
+ case walk.NumberScalar:
writer.path = writer.path[:len(writer.path) - 1]
goto inArray
- } else {
- writer.writer.WriteString("\n")
- writer.indent(len(writer.path) - 1)
- writer.writer.WriteString("]")
- writer.path = writer.path[:len(writer.path) - 1]
- goto afterValue
- }
- case walk.StringStructure:
- if len(writer.path) -1 <= diversionPoint && len(targetPath) >= len(writer.path) && isString(targetPath[len(writer.path) - 1]) {
- writer.writer.WriteString(",\n")
+ case walk.StringStructure:
writer.path = writer.path[:len(writer.path) - 1]
goto inMap
- } else {
+ default:
+ panic("Invalid segment type")
+ }
+ }
+
+ // TODO: handle len(writer.path) == 0
+ if diversionPoint < len(targetPath) {
+ if len(writer.path) == 0 {
writer.writer.WriteString("\n")
- writer.indent(len(writer.path) - 1)
- writer.writer.WriteString("}")
- writer.path = writer.path[:len(writer.path) - 1]
- goto afterValue
+ goto beforeValue
+ }
+ segment := writer.path[diversionPoint - 1]
+ writer.writer.WriteString(",")
+ diversionPoint--
+ writer.path = writer.path[:diversionPoint]
+ switch segment.(type) {
+ case walk.NumberScalar:
+ goto inArray
+ case walk.StringStructure:
+ goto inMap
+ default:
+ panic("Invalid segment type")
}
+ }
+
+ 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 walk.NumberScalar:
+ goto inArray
+ case walk.StringStructure:
+ goto inMap
default:
- panic("Invalid path segment type")
+ panic("Invalid segment type")
}
}
- inMap: {
- if len(writer.path) <= diversionPoint && len(targetPath) > len(writer.path) && isString(targetPath[len(writer.path)]) {
- writer.indent(len(writer.path) + 1)
- writer.writer.WriteString(fmt.Sprintf("%q: ", targetPath[len(writer.path)].(walk.StringStructure)))
- writer.path = append(writer.path, targetPath[len(writer.path)].(walk.StringStructure))
- goto beforeValue
- } else {
- writer.writer.WriteString("\n}")
+ 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 walk.NumberScalar:
+ writer.path = append(writer.path, s)
+ diversionPoint++
+ writer.indent(len(writer.path))
+ goto beforeValue
+ case walk.StringStructure:
+ writer.indent(len(writer.path))
+ writer.writer.WriteString("]")
+ goto afterValue
+ default:
+ panic("Invalid segment type")
+ }
+ }
+
+ writer.indent(len(writer.path))
+ writer.writer.WriteString("]")
+ goto afterValue
}
- inArray: {
- if len(writer.path) <= diversionPoint && len(targetPath) > len(writer.path) && isNumber(targetPath[len(writer.path)]) {
- writer.indent(len(writer.path) + 1)
- writer.path = append(writer.path, walk.NumberScalar(0))
- goto beforeValue
- } else {
- writer.writer.WriteString("\n]")
+ 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 walk.NumberScalar:
+ writer.indent(len(writer.path))
+ writer.writer.WriteString("}")
+ goto afterValue
+ case walk.StringStructure:
+ writer.path = append(writer.path, s)
+ diversionPoint++
+ writer.indent(len(writer.path))
+ writer.writer.WriteString(fmt.Sprintf("%q: ", s))
+ goto beforeValue
+ }
+ }
+
+ writer.indent(len(writer.path))
+ writer.writer.WriteString("}")
+ goto afterValue
}
}
func (writer *JSONWriter) AssertDone() {
+ switch writer.state {
+ case JSONWriterStateInArray:
+ writer.writer.WriteString("]")
+ case JSONWriterStateInMap:
+ writer.writer.WriteString("}")
+ }
for i := len(writer.path) - 1; i >= 0; i -= 1 {
switch writer.path[i].(type) {
case walk.NumberScalar:
diff --git a/json/write_test.go b/json/write_test.go
new file mode 100644
index 0000000..60ad609
--- /dev/null
+++ b/json/write_test.go
@@ -0,0 +1,183 @@
+package json
+
+import (
+ "bufio"
+ "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()
+ }
+
+ 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,
+ },
+ },
+ },
+ )
+}
+
+func TestExplicitMap(t *testing.T) {
+ tester(t).write(
+ make(walk.MapStructure),
+ ).write(
+ walk.NullScalar{},
+ "test",
+ ).expect(
+ map[string]interface{}{
+ "test": nil,
+ },
+ )
+}
+
+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",
+ },
+ },
+ },
+ )
+}
+
+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,
+ },
+ },
+ )
+}
+
+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{}{},
+ },
+ )
+}
diff --git a/subex/parse.go b/subex/parse.go
index 7d0e586..6c19df4 100644
--- a/subex/parse.go
+++ b/subex/parse.go
@@ -339,6 +339,7 @@ func parseSubex(l RuneReader, minPower int, runic bool) SubexAST {
if runic {
lhs = SubexASTCopyRune {r}
} else {
+ // TODO: Allow whitespace outside of runic sections
panic("Tried to match rune outside of string")
}
}