From 8cf10efe3b5a1bcc70bc6e5590ee63fd5eb00c5b Mon Sep 17 00:00:00 2001 From: Charlie Stanton Date: Wed, 19 Jul 2023 11:57:59 +0100 Subject: Huge refactor to a more value based system, doing away with terminals. Also introduces unit testing --- json/write.go | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 json/write.go (limited to 'json/write.go') 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() +} -- cgit v1.2.3