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/read.go | 288 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 json/read.go (limited to 'json/read.go') 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 +} -- cgit v1.2.3