<- Back to shtanton's homepage
aboutsummaryrefslogtreecommitdiff
path: root/json/read.go
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/read.go
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/read.go')
-rw-r--r--json/read.go288
1 files changed, 288 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
+}