<- Back to shtanton's homepage
aboutsummaryrefslogtreecommitdiff
path: root/walk/walk.go
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 /walk/walk.go
parent6a4e25b1c691846185e9dd698c1558981089738c (diff)
downloadstred-go-96a10b92057319b2c30782ea19f5427a333e6bc3.tar
Separates JSON parsing code into its own file
Diffstat (limited to 'walk/walk.go')
-rw-r--r--walk/walk.go278
1 files changed, 0 insertions, 278 deletions
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