<- Back to shtanton's homepage
aboutsummaryrefslogtreecommitdiff
path: root/json
diff options
context:
space:
mode:
authorCharlie Stanton <charlie@shtanton.xyz>2023-07-19 11:57:59 +0100
committerCharlie Stanton <charlie@shtanton.xyz>2023-07-19 11:57:59 +0100
commit8cf10efe3b5a1bcc70bc6e5590ee63fd5eb00c5b (patch)
tree7a16883c17c2bdcc49b2f9d4f333dfc76c66248f /json
parent3c34366bdd5d817a184d6b1c901d03a16b6faa4b (diff)
downloadstred-go-8cf10efe3b5a1bcc70bc6e5590ee63fd5eb00c5b.tar
Huge refactor to a more value based system, doing away with terminals. Also introduces unit testing
Diffstat (limited to 'json')
-rw-r--r--json/read.go288
-rw-r--r--json/write.go202
2 files changed, 490 insertions, 0 deletions
diff --git a/json/read.go b/json/read.go
new file mode 100644
index 0000000..6a68467
--- /dev/null
+++ b/json/read.go
@@ -0,0 +1,288 @@
+package json
+
+import (
+ "bufio"
+ "main/walk"
+ "strings"
+ "strconv"
+ "errors"
+)
+
+func isWhitespace(r rune) bool {
+ for _, ws := range " \t\r\n" {
+ if r == ws {
+ return true
+ }
+ }
+ return false
+}
+
+func isNumberRune(r rune) bool {
+ return '0' <= r && r <= '9' || r == '.'
+}
+
+type JSONReaderStructure int
+const (
+ JSONReaderStructureArray JSONReaderStructure = iota
+ JSONReaderStructureMap
+)
+
+type JSONReaderState int
+const (
+ JSONReaderStateValue JSONReaderState = iota
+ JSONReaderStateValueEnd
+)
+
+func NewJSONReader(reader *bufio.Reader) *JSONReader {
+ return &JSONReader {
+ path: nil,
+ structure: nil,
+ state: JSONReaderStateValue,
+ reader: reader,
+ }
+}
+
+type JSONReader struct {
+ path []walk.Value
+ structure []JSONReaderStructure
+ state JSONReaderState
+ reader *bufio.Reader
+}
+
+func (reader *JSONReader) Read() (walk.WalkItem, error) {
+ switch reader.state {
+ case JSONReaderStateValue:
+ if len(reader.structure) == 0 {
+ path := reader.clonePath()
+ value, err := reader.readValue()
+ if err != nil {
+ panic("Missing JSON input")
+ }
+ return walk.WalkItem {
+ Path: path,
+ Value: []walk.Value{value},
+ }, nil
+ }
+ switch reader.structure[len(reader.structure) - 1] {
+ case JSONReaderStructureArray:
+ r, err := reader.nextNonWsRune()
+ if err != nil {
+ panic("Missing rest of array")
+ }
+ if r == ']' {
+ reader.structure = reader.structure[:len(reader.structure) - 1]
+ reader.path = reader.path[:len(reader.path) - 1]
+ reader.state = JSONReaderStateValueEnd
+ return reader.Read()
+ }
+ reader.reader.UnreadRune()
+ prevIndex := reader.path[len(reader.path) - 1].(walk.NumberScalar)
+ reader.path[len(reader.path) - 1] = prevIndex + 1
+ path := reader.clonePath()
+ value, err := reader.readValue()
+ if err != nil {
+ panic("Missing value in array")
+ }
+ return walk.WalkItem {
+ Path: path,
+ Value: []walk.Value{value},
+ }, nil
+ case JSONReaderStructureMap:
+ r, err := reader.nextNonWsRune()
+ if err != nil {
+ panic("Reached EOF inside JSON map")
+ }
+ if r == '}' {
+ reader.structure = reader.structure[:len(reader.structure) - 1]
+ reader.path = reader.path[:len(reader.path) - 1]
+ reader.state = JSONReaderStateValueEnd
+ return reader.Read()
+ }
+ if r != '"' {
+ panic("Expected key in map, found something else")
+ }
+ key := reader.readString()
+ reader.path[len(reader.path) - 1] = walk.StringStructure(key)
+ r, err = reader.nextNonWsRune()
+ if err != nil {
+ panic("Reached EOF after map key")
+ }
+ if r != ':' {
+ panic("Expected : after map key, found something else")
+ }
+ path := reader.clonePath()
+ value, err := reader.readValue()
+ if err != nil {
+ panic("Missing value in map")
+ }
+ return walk.WalkItem {
+ Path: path,
+ Value: []walk.Value{value},
+ }, nil
+ default:
+ panic("Invalid JSONReaderStructure")
+ }
+ case JSONReaderStateValueEnd:
+ if len(reader.structure) == 0 {
+ _, err := reader.nextNonWsRune()
+ if err == nil {
+ panic("input continues after JSON value")
+ }
+ return walk.WalkItem{}, errors.New("eof")
+ }
+ switch reader.structure[len(reader.structure) - 1] {
+ case JSONReaderStructureArray:
+ r, err := reader.nextNonWsRune()
+ if err != nil {
+ panic("Missing end of array")
+ }
+ if r == ']' {
+ reader.path = reader.path[:len(reader.path) - 1]
+ reader.structure = reader.structure[:len(reader.structure) - 1]
+ reader.state = JSONReaderStateValueEnd
+ return reader.Read()
+ }
+ if r != ',' {
+ panic("Missing , after array value")
+ }
+ reader.state = JSONReaderStateValue
+ return reader.Read()
+ case JSONReaderStructureMap:
+ r, err := reader.nextNonWsRune()
+ if err != nil {
+ panic("Missing end of map")
+ }
+ if r == '}' {
+ reader.path = reader.path[:len(reader.path) - 1]
+ reader.structure = reader.structure[:len(reader.structure) - 1]
+ reader.state = JSONReaderStateValueEnd
+ return reader.Read()
+ }
+ if r != ',' {
+ panic("Missing , after map value")
+ }
+ reader.state = JSONReaderStateValue
+ return reader.Read()
+ default:
+ panic("Invalid JSONReaderStructure")
+ }
+ default:
+ panic("Invalid JSONReaderState")
+ }
+}
+
+func (reader *JSONReader) readValue() (walk.Value, error) {
+ r, err := reader.nextNonWsRune()
+ if err != nil {
+ panic("Missing value in JSON")
+ }
+ switch r {
+ case 'n':
+ reader.requireString("ull")
+ reader.state = JSONReaderStateValueEnd
+ return walk.NullScalar{}, nil
+ case 'f':
+ reader.requireString("alse")
+ reader.state = JSONReaderStateValueEnd
+ return walk.BoolScalar(false), nil
+ case 't':
+ reader.requireString("rue")
+ reader.state = JSONReaderStateValueEnd
+ return walk.BoolScalar(true), nil
+ case '"':
+ v := reader.readString()
+ reader.state = JSONReaderStateValueEnd
+ return walk.StringStructure(v), nil
+ case '{':
+ reader.state = JSONReaderStateValue
+ reader.structure = append(reader.structure, JSONReaderStructureMap)
+ reader.path = append(reader.path, walk.StringStructure(""))
+ return walk.MapStructure(make(map[string]walk.Value)), nil
+ case '[':
+ reader.state = JSONReaderStateValue
+ reader.structure = append(reader.structure, JSONReaderStructureArray)
+ reader.path = append(reader.path, walk.NumberScalar(-1))
+ return walk.ArrayStructure{}, nil
+ }
+ if isNumberRune(r) {
+ var builder strings.Builder
+ builder.WriteRune(r)
+ for {
+ r, _, err = reader.reader.ReadRune()
+ if err != nil {
+ break
+ }
+ if !isNumberRune(r) {
+ reader.reader.UnreadRune()
+ break
+ }
+ builder.WriteRune(r)
+ }
+ number, parseError := strconv.ParseFloat(builder.String(), 64)
+ if parseError != nil {
+ panic("Invalid number")
+ }
+ reader.state = JSONReaderStateValueEnd
+ return walk.NumberScalar(number), nil
+ }
+ panic("Invalid JSON value starting with: " + string(r))
+}
+
+func (reader *JSONReader) readString() string {
+ var builder strings.Builder
+ for {
+ r, _, err := reader.reader.ReadRune()
+ if err != nil {
+ panic("Missing rest of string")
+ }
+ if r == '"' {
+ break
+ }
+ if r == '\\' {
+ r, _, err = reader.reader.ReadRune()
+ if err != nil {
+ panic("Missing rune after \\")
+ }
+ builder.WriteRune(r)
+ continue
+ }
+ builder.WriteRune(r)
+ }
+ return builder.String()
+}
+
+func (reader *JSONReader) nextNonWsRune() (rune, error) {
+ for {
+ r, _, err := reader.reader.ReadRune()
+ if err != nil {
+ return 0, err
+ }
+ if !isWhitespace(r) {
+ return r, nil
+ }
+ }
+}
+
+func (reader *JSONReader) requireString(criteria string) {
+ for _, r := range criteria {
+ reader.require(r)
+ }
+}
+
+func (reader *JSONReader) require(criterion rune) {
+ r, _, err := reader.reader.ReadRune()
+ if err != nil {
+ panic("Error while reading required rune: " + err.Error())
+ }
+ if r != criterion {
+ panic("Required rune not read")
+ }
+}
+
+func (reader *JSONReader) clonePath() []walk.Value {
+ return append([]walk.Value{}, reader.path...)
+}
+
+func (reader *JSONReader) AssertDone() {
+ // TODO
+}
diff --git a/json/write.go b/json/write.go
new file mode 100644
index 0000000..d024a56
--- /dev/null
+++ b/json/write.go
@@ -0,0 +1,202 @@
+package json
+
+import (
+ "bufio"
+ "fmt"
+ "main/walk"
+)
+
+func isNumber(value walk.Value) bool {
+ _, isFloat := value.(walk.NumberScalar)
+ return isFloat
+}
+
+func isString(value walk.Value) bool {
+ _, isString := value.(walk.StringStructure)
+ return isString
+}
+
+func segmentEqual(left walk.Value, right walk.Value) bool {
+ switch left := left.(type) {
+ case walk.NumberScalar:
+ _, isNumber := right.(walk.NumberScalar)
+ return isNumber
+ case walk.StringStructure:
+ right, isString := right.(walk.StringStructure)
+ return isString && left == right
+ default:
+ panic("Invalid path segment type")
+ }
+}
+
+type JSONWriterState int
+const (
+ JSONWriterStateBeforeValue JSONWriterState = iota
+ JSONWriterStateAfterValue JSONWriterState = iota
+ JSONWriterStateInArray
+ JSONWriterStateInMap
+)
+
+func NewJSONWriter(writer *bufio.Writer) *JSONWriter {
+ return &JSONWriter {
+ path: nil,
+ writer: writer,
+ state: JSONWriterStateBeforeValue,
+ }
+}
+
+type JSONWriter struct {
+ path []walk.Value
+ 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
+ }
+ }
+ return nil
+}
+
+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 {
+ diversionPoint := len(writer.path)
+ 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")
+ }
+
+ beforeValue: {
+ switch value := value.(type) {
+ case walk.NullScalar:
+ writer.writer.WriteString("null")
+ writer.state = JSONWriterStateAfterValue
+ return nil
+ case walk.BoolScalar:
+ if value {
+ writer.writer.WriteString("true")
+ } else {
+ writer.writer.WriteString("false")
+ }
+ writer.state = JSONWriterStateAfterValue
+ return nil
+ case walk.NumberScalar:
+ writer.writer.WriteString(fmt.Sprintf("%v", value))
+ writer.state = JSONWriterStateAfterValue
+ return nil
+ case walk.StringStructure:
+ writer.writer.WriteString(fmt.Sprintf("%q", value))
+ writer.state = JSONWriterStateAfterValue
+ return nil
+ case walk.ArrayStructure:
+ // TODO: write the contents of the structures
+ writer.writer.WriteString("[\n")
+ writer.state = JSONWriterStateInArray
+ return nil
+ case walk.MapStructure:
+ writer.writer.WriteString("{\n")
+ writer.state = JSONWriterStateInMap
+ return nil
+ default:
+ panic("Invalid value type")
+ }
+ }
+
+ 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")
+ 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")
+ writer.path = writer.path[:len(writer.path) - 1]
+ goto inMap
+ } else {
+ writer.writer.WriteString("\n")
+ writer.indent(len(writer.path) - 1)
+ writer.writer.WriteString("}")
+ writer.path = writer.path[:len(writer.path) - 1]
+ goto afterValue
+ }
+ default:
+ panic("Invalid path 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}")
+ 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]")
+ goto afterValue
+ }
+ }
+}
+
+func (writer *JSONWriter) AssertDone() {
+ for i := len(writer.path) - 1; i >= 0; i -= 1 {
+ switch writer.path[i].(type) {
+ case walk.NumberScalar:
+ writer.writer.WriteString("\n")
+ writer.indent(i)
+ writer.writer.WriteString("]")
+ case walk.StringStructure:
+ writer.writer.WriteString("\n")
+ writer.indent(i)
+ writer.writer.WriteString("}")
+ default:
+ panic("Invalid path segment type")
+ }
+ }
+ writer.writer.Flush()
+}