diff options
Diffstat (limited to 'json')
-rw-r--r-- | json/read.go | 288 | ||||
-rw-r--r-- | json/write.go | 202 |
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() +} |