<- Back to shtanton's homepage
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharlie Stanton <charlie@shtanton.xyz>2023-04-25 14:20:20 +0100
committerCharlie Stanton <charlie@shtanton.xyz>2023-04-25 14:20:20 +0100
commit96a10b92057319b2c30782ea19f5427a333e6bc3 (patch)
treeb34fc1d164e7ed8331d31718ee0d0e2c8e2aee15
parent6a4e25b1c691846185e9dd698c1558981089738c (diff)
downloadstred-go-96a10b92057319b2c30782ea19f5427a333e6bc3.tar
Separates JSON parsing code into its own file
-rw-r--r--walk/read.go285
-rw-r--r--walk/walk.go278
2 files changed, 285 insertions, 278 deletions
diff --git a/walk/read.go b/walk/read.go
new file mode 100644
index 0000000..6f3acfe
--- /dev/null
+++ b/walk/read.go
@@ -0,0 +1,285 @@
+package walk
+
+import (
+ "bufio"
+ "math"
+ "strings"
+ "strconv"
+)
+
+type JSONInStructure int
+const (
+ JSONInRoot JSONInStructure = iota
+ JSONInMap
+ JSONInArray
+ JSONInValueEnd
+)
+
+type JSONIn struct {
+ path []Atom
+ reader *bufio.Reader
+ structure []JSONInStructure
+}
+
+func NewJSONIn(reader *bufio.Reader) JSONIn {
+ return JSONIn {
+ path: make([]Atom, 0, 256),
+ reader: reader,
+ structure: []JSONInStructure{JSONInRoot},
+ }
+}
+
+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 == '.'
+}
+
+func (in *JSONIn) popPath() {
+ if len(in.path) == 0 {
+ panic("Tried to pop from empty path")
+ }
+ finalAtom := in.path[len(in.path) - 1]
+ if finalAtom.Typ != AtomStringTerminal {
+ in.path = in.path[:len(in.path) - 1]
+ return
+ }
+ i := len(in.path) - 2
+ for {
+ if i < 0 {
+ panic("Missing string begin in path")
+ }
+ if in.path[i].Typ == AtomStringTerminal {
+ break
+ }
+ i--
+ }
+ in.path = in.path[:i]
+}
+
+func (in *JSONIn) nextNonWsRune() (rune, error) {
+ for {
+ r, _, err := in.reader.ReadRune()
+ if err != nil {
+ return 0, err
+ }
+ if !isWhitespace(r) {
+ return r, nil
+ }
+ }
+}
+
+func (in *JSONIn) requireString(criteria string) {
+ for _, r := range criteria {
+ in.require(r)
+ }
+}
+
+func (in *JSONIn) require(criterion rune) {
+ r, _, err := in.reader.ReadRune()
+ if err != nil {
+ panic("Error while reading required rune: " + err.Error())
+ }
+ if r != criterion {
+ panic("Required rune not read")
+ }
+}
+
+func (in *JSONIn) readString(out []Atom) []Atom {
+ // TODO: improve
+ out = append(out, NewAtomStringTerminal())
+ for {
+ r, _, err := in.reader.ReadRune()
+ if err != nil {
+ panic("Missing closing terminal in string input: " + err.Error())
+ }
+ if r == '"' {
+ break
+ }
+ if r == '\\' {
+ r, _, err = in.reader.ReadRune()
+ if err != nil {
+ panic("Missing rune after \\")
+ }
+ if len(out) == cap(out) {
+ newOut := make([]Atom, len(out), cap(out) * 2)
+ copy(newOut, out)
+ out = newOut
+ }
+ out = append(out, NewAtomStringRune(r))
+ continue
+ }
+ if len(out) == cap(out) {
+ newOut := make([]Atom, len(out), cap(out) * 2)
+ copy(newOut, out)
+ out = newOut
+ }
+ out = append(out, NewAtomStringRune(r))
+ }
+ out = append(out, NewAtomStringTerminal())
+ return out
+}
+
+func (in *JSONIn) Read() (WalkItem, error) {
+ restart:
+ // TODO: Escaping
+ // TODO: Don't allow trailing commas
+ // TODO: Proper float parsing with e and stuff
+ r, err := in.nextNonWsRune()
+ if err != nil {
+ return WalkItem {}, err
+ }
+ state := in.structure[len(in.structure) - 1]
+ switch state {
+ case JSONInMap:
+ in.popPath()
+ if r == '}' {
+ in.structure[len(in.structure) - 1] = JSONInValueEnd
+ return WalkItem {
+ Value: []Atom{NewAtomTerminal(MapEnd)},
+ Path: in.path,
+ }, nil
+ }
+ if r != '"' {
+ panic("Expected key, found something else")
+ }
+ in.path = in.readString(in.path)
+ r, err = in.nextNonWsRune()
+ if err != nil {
+ panic("Expected : got: " + err.Error())
+ }
+ if r != ':' {
+ panic("Expected : after key")
+ }
+ r, err = in.nextNonWsRune()
+ if err != nil {
+ panic("Missing map value after key: " + err.Error())
+ }
+ case JSONInArray:
+ if r == ']' {
+ in.structure[len(in.structure) - 1] = JSONInValueEnd
+ in.popPath()
+ return WalkItem {
+ Value: []Atom{NewAtomTerminal(ArrayEnd)},
+ Path: in.path,
+ }, nil
+ }
+ prevIndex := in.path[len(in.path) - 1]
+ if prevIndex.Typ == AtomNull {
+ prevIndex.Typ = AtomNumber
+ prevIndex.data = math.Float64bits(0)
+ } else if prevIndex.Typ == AtomNumber {
+ prevIndex.data = math.Float64bits(math.Float64frombits(prevIndex.data) + 1)
+ } else {
+ panic("Invalid index in array input")
+ }
+ in.path[len(in.path) - 1] = prevIndex
+ case JSONInRoot:
+ case JSONInValueEnd:
+ in.structure = in.structure[:len(in.structure) - 1]
+ underState := in.structure[len(in.structure) - 1]
+ if underState == JSONInRoot {
+ panic("More input after root JSON object ends")
+ } else if underState == JSONInMap && r == '}' {
+ in.structure[len(in.structure) - 1] = JSONInValueEnd
+ in.popPath()
+ return WalkItem {
+ Value: []Atom{NewAtomTerminal(MapEnd)},
+ Path: in.path,
+ }, nil
+ } else if underState == JSONInArray && r == ']' {
+ in.structure[len(in.structure) - 1] = JSONInValueEnd
+ in.popPath()
+ return WalkItem {
+ Value: []Atom{NewAtomTerminal(ArrayEnd)},
+ Path: in.path,
+ }, nil
+ }
+ if r != ',' {
+ panic("Expected , after JSON value, found: \"" + string(r) + "\"")
+ }
+ goto restart
+ default:
+ panic("Invalid JSONIn state")
+ }
+ switch r {
+ case 'n':
+ in.requireString("ull")
+ in.structure = append(in.structure, JSONInValueEnd)
+ return WalkItem {
+ Value: []Atom{NewAtomNull()},
+ Path: in.path,
+ }, nil
+ case 'f':
+ in.requireString("alse")
+ in.structure = append(in.structure, JSONInValueEnd)
+ return WalkItem {
+ Value: []Atom{NewAtomBool(false)},
+ Path: in.path,
+ }, nil
+ case 't':
+ in.requireString("rue")
+ in.structure = append(in.structure, JSONInValueEnd)
+ return WalkItem {
+ Value: []Atom{NewAtomBool(true)},
+ Path: in.path,
+ }, nil
+ case '"':
+ value := make([]Atom, 0, 64)
+ value = in.readString(value)
+ in.structure = append(in.structure, JSONInValueEnd)
+ return WalkItem {
+ Value: value,
+ Path: in.path,
+ }, nil
+ case '{':
+ in.structure = append(in.structure, JSONInMap)
+ in.path = append(in.path, NewAtomNull())
+ return WalkItem {
+ Value: []Atom{NewAtomTerminal(MapBegin)},
+ Path: in.path[:len(in.path) - 1],
+ }, nil
+ case '[':
+ in.structure = append(in.structure, JSONInArray)
+ in.path = append(in.path, NewAtomNull())
+ return WalkItem {
+ Value: []Atom{NewAtomTerminal(ArrayBegin)},
+ Path: in.path[:len(in.path) - 1],
+ }, nil
+ }
+ if isNumberRune(r) {
+ var builder strings.Builder
+ builder.WriteRune(r)
+ for {
+ r, _, err = in.reader.ReadRune()
+ if err != nil || !isNumberRune(r) {
+ break
+ }
+ builder.WriteRune(r)
+ }
+ in.reader.UnreadRune()
+ number, parseError := strconv.ParseFloat(builder.String(), 64)
+ if parseError != nil {
+ panic("Invalid number")
+ }
+ in.structure = append(in.structure, JSONInValueEnd)
+ return WalkItem {
+ Value: []Atom{NewAtomNumber(number)},
+ Path: in.path,
+ }, nil
+ }
+ panic("Invalid JSON value")
+}
+
+func (in *JSONIn) AssertDone() {
+ if len(in.structure) != 2 || in.structure[0] != JSONInRoot || in.structure[1] != JSONInValueEnd {
+ panic("Input ended on incomplete JSON root")
+ }
+}
diff --git a/walk/walk.go b/walk/walk.go
index 20eac38..6e86877 100644
--- a/walk/walk.go
+++ b/walk/walk.go
@@ -6,7 +6,6 @@ import (
"math"
"unicode/utf8"
"bufio"
- "strconv"
)
// int or string
@@ -50,283 +49,6 @@ type WalkItem struct {
Path []Atom
}
-type JSONInStructure int
-const (
- JSONInRoot JSONInStructure = iota
- JSONInMap
- JSONInArray
- JSONInValueEnd
-)
-
-type JSONIn struct {
- path []Atom
- reader *bufio.Reader
- structure []JSONInStructure
-}
-
-func NewJSONIn(reader *bufio.Reader) JSONIn {
- return JSONIn {
- path: make([]Atom, 0, 256),
- reader: reader,
- structure: []JSONInStructure{JSONInRoot},
- }
-}
-
-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 == '.'
-}
-
-func (in *JSONIn) popPath() {
- if len(in.path) == 0 {
- panic("Tried to pop from empty path")
- }
- finalAtom := in.path[len(in.path) - 1]
- if finalAtom.Typ != AtomStringTerminal {
- in.path = in.path[:len(in.path) - 1]
- return
- }
- i := len(in.path) - 2
- for {
- if i < 0 {
- panic("Missing string begin in path")
- }
- if in.path[i].Typ == AtomStringTerminal {
- break
- }
- i--
- }
- in.path = in.path[:i]
-}
-
-func (in *JSONIn) nextNonWsRune() (rune, error) {
- for {
- r, _, err := in.reader.ReadRune()
- if err != nil {
- return 0, err
- }
- if !isWhitespace(r) {
- return r, nil
- }
- }
-}
-
-func (in *JSONIn) requireString(criteria string) {
- for _, r := range criteria {
- in.require(r)
- }
-}
-
-func (in *JSONIn) require(criterion rune) {
- r, _, err := in.reader.ReadRune()
- if err != nil {
- panic("Error while reading required rune: " + err.Error())
- }
- if r != criterion {
- panic("Required rune not read")
- }
-}
-
-func (in *JSONIn) readString(out []Atom) []Atom {
- // TODO: improve
- out = append(out, NewAtomStringTerminal())
- for {
- r, _, err := in.reader.ReadRune()
- if err != nil {
- panic("Missing closing terminal in string input: " + err.Error())
- }
- if r == '"' {
- break
- }
- if r == '\\' {
- r, _, err = in.reader.ReadRune()
- if err != nil {
- panic("Missing rune after \\")
- }
- if len(out) == cap(out) {
- newOut := make([]Atom, len(out), cap(out) * 2)
- copy(newOut, out)
- out = newOut
- }
- out = append(out, NewAtomStringRune(r))
- continue
- }
- if len(out) == cap(out) {
- newOut := make([]Atom, len(out), cap(out) * 2)
- copy(newOut, out)
- out = newOut
- }
- out = append(out, NewAtomStringRune(r))
- }
- out = append(out, NewAtomStringTerminal())
- return out
-}
-
-func (in *JSONIn) Read() (WalkItem, error) {
- restart:
- // TODO: Escaping
- // TODO: Don't allow trailing commas
- // TODO: Proper float parsing with e and stuff
- r, err := in.nextNonWsRune()
- if err != nil {
- return WalkItem {}, err
- }
- state := in.structure[len(in.structure) - 1]
- switch state {
- case JSONInMap:
- in.popPath()
- if r == '}' {
- in.structure[len(in.structure) - 1] = JSONInValueEnd
- return WalkItem {
- Value: []Atom{NewAtomTerminal(MapEnd)},
- Path: in.path,
- }, nil
- }
- if r != '"' {
- panic("Expected key, found something else")
- }
- in.path = in.readString(in.path)
- r, err = in.nextNonWsRune()
- if err != nil {
- panic("Expected : got: " + err.Error())
- }
- if r != ':' {
- panic("Expected : after key")
- }
- r, err = in.nextNonWsRune()
- if err != nil {
- panic("Missing map value after key: " + err.Error())
- }
- case JSONInArray:
- if r == ']' {
- in.structure[len(in.structure) - 1] = JSONInValueEnd
- in.popPath()
- return WalkItem {
- Value: []Atom{NewAtomTerminal(ArrayEnd)},
- Path: in.path,
- }, nil
- }
- prevIndex := in.path[len(in.path) - 1]
- if prevIndex.Typ == AtomNull {
- prevIndex.Typ = AtomNumber
- prevIndex.data = math.Float64bits(0)
- } else if prevIndex.Typ == AtomNumber {
- prevIndex.data = math.Float64bits(math.Float64frombits(prevIndex.data) + 1)
- } else {
- panic("Invalid index in array input")
- }
- in.path[len(in.path) - 1] = prevIndex
- case JSONInRoot:
- case JSONInValueEnd:
- in.structure = in.structure[:len(in.structure) - 1]
- underState := in.structure[len(in.structure) - 1]
- if underState == JSONInRoot {
- panic("More input after root JSON object ends")
- } else if underState == JSONInMap && r == '}' {
- in.structure[len(in.structure) - 1] = JSONInValueEnd
- in.popPath()
- return WalkItem {
- Value: []Atom{NewAtomTerminal(MapEnd)},
- Path: in.path,
- }, nil
- } else if underState == JSONInArray && r == ']' {
- in.structure[len(in.structure) - 1] = JSONInValueEnd
- in.popPath()
- return WalkItem {
- Value: []Atom{NewAtomTerminal(ArrayEnd)},
- Path: in.path,
- }, nil
- }
- if r != ',' {
- panic("Expected , after JSON value, found: \"" + string(r) + "\"")
- }
- goto restart
- default:
- panic("Invalid JSONIn state")
- }
- switch r {
- case 'n':
- in.requireString("ull")
- in.structure = append(in.structure, JSONInValueEnd)
- return WalkItem {
- Value: []Atom{NewAtomNull()},
- Path: in.path,
- }, nil
- case 'f':
- in.requireString("alse")
- in.structure = append(in.structure, JSONInValueEnd)
- return WalkItem {
- Value: []Atom{NewAtomBool(false)},
- Path: in.path,
- }, nil
- case 't':
- in.requireString("rue")
- in.structure = append(in.structure, JSONInValueEnd)
- return WalkItem {
- Value: []Atom{NewAtomBool(true)},
- Path: in.path,
- }, nil
- case '"':
- value := make([]Atom, 0, 64)
- value = in.readString(value)
- in.structure = append(in.structure, JSONInValueEnd)
- return WalkItem {
- Value: value,
- Path: in.path,
- }, nil
- case '{':
- in.structure = append(in.structure, JSONInMap)
- in.path = append(in.path, NewAtomNull())
- return WalkItem {
- Value: []Atom{NewAtomTerminal(MapBegin)},
- Path: in.path[:len(in.path) - 1],
- }, nil
- case '[':
- in.structure = append(in.structure, JSONInArray)
- in.path = append(in.path, NewAtomNull())
- return WalkItem {
- Value: []Atom{NewAtomTerminal(ArrayBegin)},
- Path: in.path[:len(in.path) - 1],
- }, nil
- }
- if isNumberRune(r) {
- var builder strings.Builder
- builder.WriteRune(r)
- for {
- r, _, err = in.reader.ReadRune()
- if err != nil || !isNumberRune(r) {
- break
- }
- builder.WriteRune(r)
- }
- in.reader.UnreadRune()
- number, parseError := strconv.ParseFloat(builder.String(), 64)
- if parseError != nil {
- panic("Invalid number")
- }
- in.structure = append(in.structure, JSONInValueEnd)
- return WalkItem {
- Value: []Atom{NewAtomNumber(number)},
- Path: in.path,
- }, nil
- }
- panic("Invalid JSON value")
-}
-
-func (in *JSONIn) AssertDone() {
- if len(in.structure) != 2 || in.structure[0] != JSONInRoot || in.structure[1] != JSONInValueEnd {
- panic("Input ended on incomplete JSON root")
- }
-}
-
type JSONOutStructure int
const (
JSONOutRoot JSONOutStructure = iota