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() }