<- Back to shtanton's homepage
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharlie Stanton <charlie@shtanton.xyz>2023-05-12 11:37:44 +0100
committerCharlie Stanton <charlie@shtanton.xyz>2023-05-12 11:37:44 +0100
commit551613765c9e60e2221ac920d2756b949e68f373 (patch)
treeac579a9e0d6c015edca694880f259c8dac4d7a04
parente98ebbad387def55d8347adb5bf45034d542cce0 (diff)
downloadstred-go-551613765c9e60e2221ac920d2756b949e68f373.tar
Move reading and writing of tokens into a separate package to prepare for other input and output formats
-rw-r--r--json_tokens/read.go490
-rw-r--r--json_tokens/write.go151
-rw-r--r--main/command.go9
-rw-r--r--main/main.go18
-rw-r--r--walk/atom.go12
-rw-r--r--walk/read.go494
-rw-r--r--walk/walk.go135
7 files changed, 676 insertions, 633 deletions
diff --git a/json_tokens/read.go b/json_tokens/read.go
new file mode 100644
index 0000000..95bbb9d
--- /dev/null
+++ b/json_tokens/read.go
@@ -0,0 +1,490 @@
+package json_tokens
+
+import (
+ "main/walk"
+ "bufio"
+ "strings"
+ "strconv"
+ "fmt"
+)
+
+type ReadAction int
+const (
+ ActionReadValue ReadAction = iota
+ ActionAppendPath
+ ActionPopPath
+ ActionIncrementPath
+ ActionAppendPathNull
+)
+
+type JSONInStructure int
+const (
+ JSONInMap JSONInStructure = iota
+ JSONInArray
+)
+
+type JSONInState int
+const (
+ JSONInValueEnd JSONInState = iota
+ JSONInValue
+ JSONInValueStart
+ JSONInString
+ JSONInKey
+)
+
+type JSONIn struct {
+ path []walk.Atom
+ reader *bufio.Reader
+ structure []JSONInStructure
+ state JSONInState
+ readBuffer []walk.Atom
+ readIndex int
+ readBufferCapacity int
+ actionBuffer []ReadAction
+ actionIndex int
+}
+
+func NewJSONIn(reader *bufio.Reader) *JSONIn {
+ return &JSONIn {
+ path: make([]walk.Atom, 0, 256),
+ reader: reader,
+ structure: []JSONInStructure{},
+ state: JSONInValueStart,
+ readBuffer: make([]walk.Atom, 0, 256),
+ readIndex: 0,
+ readBufferCapacity: 256,
+ actionBuffer: make([]ReadAction, 0, 256),
+ actionIndex: 0,
+ }
+}
+
+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 != walk.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 == walk.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")
+ }
+}
+
+// Returns the first full value of a list of atoms and also a boolean to indicate if there isn't a value at the beginning
+func firstValue(atoms []walk.Atom) ([]walk.Atom, bool) {
+ if len(atoms) == 0 {
+ return nil, true
+ }
+ if atoms[0].Typ != walk.AtomStringTerminal {
+ return atoms[0:1], false
+ }
+ i := 1
+ for {
+ if i == len(atoms) {
+ return nil, true
+ }
+ if atoms[i].Typ == walk.AtomStringTerminal {
+ return atoms[0:i+1], false
+ }
+ i++
+ }
+}
+
+func (in *JSONIn) readValue() []walk.Atom {
+ try:
+ value, incomplete := firstValue(in.readBuffer[in.readIndex:])
+ if incomplete {
+ if in.readIndex == 0 {
+ newReadBuffer := make([]walk.Atom, len(in.readBuffer), in.readBufferCapacity * 2)
+ in.readBufferCapacity *= 2
+ copy(newReadBuffer, in.readBuffer)
+ in.readBuffer = newReadBuffer
+ in.fillReadBuffer()
+ goto try
+ }
+ copy(in.readBuffer, in.readBuffer[in.readIndex:])
+ in.readBuffer = in.readBuffer[:len(in.readBuffer) - in.readIndex]
+ in.readIndex = 0
+ copy(in.actionBuffer, in.actionBuffer[in.actionIndex:])
+ in.actionBuffer = in.actionBuffer[:len(in.actionBuffer) - in.actionIndex]
+ in.actionIndex = 0
+ in.fillReadBuffer()
+ goto try
+ }
+ in.readIndex += len(value)
+ return value
+}
+
+func (in *JSONIn) Read() (walk.WalkItem, error) {
+ for {
+ if in.actionIndex == len(in.actionBuffer) {
+ in.actionIndex = 0
+ in.readIndex = 0
+ in.actionBuffer = in.actionBuffer[:0]
+ in.readBuffer = in.readBuffer[:0]
+ err := in.fillReadBuffer()
+ if len(in.actionBuffer) == 0 {
+ return walk.WalkItem{}, err
+ }
+ }
+ action := in.actionBuffer[in.actionIndex]
+ in.actionIndex++
+ switch action {
+ case ActionReadValue:
+ value := in.readValue()
+ return walk.WalkItem {
+ Value: value,
+ Path: in.path,
+ }, nil
+ case ActionAppendPath:
+ value := in.readValue()
+ in.path = append(in.path, value...)
+ case ActionAppendPathNull:
+ in.path = append(in.path, walk.NewAtomNull())
+ case ActionPopPath:
+ in.popPath()
+ case ActionIncrementPath:
+ prevIndex := in.path[len(in.path) - 1]
+ if prevIndex.Typ == walk.AtomNull {
+ prevIndex = walk.NewAtomNumber(0)
+ } else if prevIndex.Typ == walk.AtomNumber {
+ prevIndex = walk.NewAtomNumber(prevIndex.Number() + 1)
+ } else {
+ panic("Invalid index in array input. Type: " + fmt.Sprintf("%v", prevIndex.Typ))
+ }
+ in.path[len(in.path) - 1] = prevIndex
+ default:
+ panic("Invalid ReadAction")
+ }
+ }
+}
+
+func (in *JSONIn) AssertDone() {
+ if len(in.structure) != 0 || in.state != JSONInValueEnd || in.readIndex < len(in.readBuffer) {
+ panic("Input ended on incomplete JSON root")
+ }
+}
+
+func (in *JSONIn) pushReadBuffer(atom walk.Atom) bool {
+ in.readBuffer = append(in.readBuffer, atom)
+ return len(in.readBuffer) == in.readBufferCapacity
+}
+
+func (in *JSONIn) pushActionBuffer(action ReadAction) {
+ in.actionBuffer = append(in.actionBuffer, action)
+}
+
+// Appends to the readBuffer until it has reached capacity
+// Also appends to the actionBuffer as needed
+func (in *JSONIn) fillReadBuffer() error {
+ switch in.state {
+ case JSONInValueStart:
+ goto valueStart
+ case JSONInValue:
+ goto value
+ case JSONInValueEnd:
+ goto valueEnd
+ case JSONInString:
+ goto string
+ case JSONInKey:
+ goto key
+ default:
+ panic("Invalid JSONInState")
+ }
+ valueStart: {
+ if len(in.structure) == 0 {
+ goto value
+ }
+ innermost := in.structure[len(in.structure) - 1]
+ switch innermost {
+ case JSONInMap:
+ goto mapValue
+ case JSONInArray:
+ goto arrayValue
+ default:
+ panic("Invalid JSONInStructure")
+ }
+ }
+ value: {
+ r, err := in.nextNonWsRune()
+ if err != nil {
+ panic("Missing value in JSON")
+ }
+ switch r {
+ case 'n':
+ in.requireString("ull")
+ in.pushActionBuffer(ActionReadValue)
+ if in.pushReadBuffer(walk.NewAtomNull()) {
+ in.state = JSONInValueEnd
+ return nil
+ }
+ goto valueEnd
+ case 'f':
+ in.requireString("alse")
+ in.pushActionBuffer(ActionReadValue)
+ if in.pushReadBuffer(walk.NewAtomBool(false)) {
+ in.state = JSONInValueEnd
+ return nil
+ }
+ goto valueEnd
+ case 't':
+ in.requireString("rue")
+ in.pushActionBuffer(ActionReadValue)
+ if in.pushReadBuffer(walk.NewAtomBool(true)) {
+ in.state = JSONInValueEnd
+ return nil
+ }
+ goto valueEnd
+ case '"':
+ in.pushActionBuffer(ActionReadValue)
+ if in.pushReadBuffer(walk.NewAtomStringTerminal()) {
+ in.state = JSONInString
+ return nil
+ }
+ goto string
+ case '{':
+ in.structure = append(in.structure, JSONInMap)
+ in.pushActionBuffer(ActionReadValue)
+ in.pushActionBuffer(ActionAppendPathNull)
+ if in.pushReadBuffer(walk.NewAtomTerminal(walk.MapBegin)) {
+ in.state = JSONInValueStart
+ return nil
+ }
+ goto mapValue
+ case '[':
+ in.structure = append(in.structure, JSONInArray)
+ in.pushActionBuffer(ActionReadValue)
+ in.pushActionBuffer(ActionAppendPathNull)
+ if in.pushReadBuffer(walk.NewAtomTerminal(walk.ArrayBegin)) {
+ in.state = JSONInValueStart
+ return nil
+ }
+ goto arrayValue
+ }
+ if isNumberRune(r) {
+ var builder strings.Builder
+ builder.WriteRune(r)
+ for {
+ r, _, err = in.reader.ReadRune()
+ if err != nil {
+ break
+ }
+ if !isNumberRune(r) {
+ in.reader.UnreadRune()
+ break
+ }
+ builder.WriteRune(r)
+ }
+ number, parseError := strconv.ParseFloat(builder.String(), 64)
+ if parseError != nil {
+ panic("Invalid number")
+ }
+ in.pushActionBuffer(ActionReadValue)
+ if in.pushReadBuffer(walk.NewAtomNumber(number)) {
+ in.state = JSONInValueEnd
+ return nil
+ }
+ goto valueEnd
+ }
+ panic("Invalid JSON value starting with: " + string(r))
+ }
+ string: {
+ r, _, err := in.reader.ReadRune()
+ if err != nil {
+ panic("Missing closing terminal in string input: " + err.Error())
+ }
+ if r == '"' {
+ if in.pushReadBuffer(walk.NewAtomStringTerminal()) {
+ in.state = JSONInValueEnd
+ return nil
+ }
+ goto valueEnd
+ }
+ if r == '\\' {
+ r, _, err = in.reader.ReadRune()
+ if err != nil {
+ panic("Missing rune after \\")
+ }
+ if in.pushReadBuffer(walk.NewAtomStringRune(r)) {
+ in.state = JSONInString
+ return nil
+ }
+ goto string
+ }
+ if in.pushReadBuffer(walk.NewAtomStringRune(r)) {
+ in.state = JSONInString
+ return nil
+ }
+ goto string
+ }
+ key: {
+ var full bool
+ for {
+ r, _, err := in.reader.ReadRune()
+ if err != nil {
+ panic("Missing closing terminal in string input: " + err.Error())
+ }
+ if r == '"' {
+ full = in.pushReadBuffer(walk.NewAtomStringTerminal())
+ break
+ }
+ if r == '\\' {
+ r, _, err = in.reader.ReadRune()
+ if err != nil {
+ panic("Missing rune after \\")
+ }
+ if in.pushReadBuffer(walk.NewAtomStringRune(r)) {
+ in.state = JSONInKey
+ return nil
+ }
+ continue
+ }
+ if in.pushReadBuffer(walk.NewAtomStringRune(r)) {
+ in.state = JSONInKey
+ return nil
+ }
+ continue
+ }
+ r, err := in.nextNonWsRune()
+ if err != nil {
+ panic("Expected : got: " + err.Error())
+ }
+ if r != ':' {
+ panic("Expected : after key")
+ }
+ if full {
+ in.state = JSONInValue
+ return nil
+ }
+ goto value
+ }
+ valueEnd: {
+ r, err := in.nextNonWsRune()
+ if err != nil {
+ in.state = JSONInValueEnd
+ return err
+ }
+ if len(in.structure) == 0 {
+ panic("More input after root JSON object ends")
+ }
+ innermost := in.structure[len(in.structure) - 1]
+ if innermost == JSONInMap && r == '}' {
+ in.structure = in.structure[:len(in.structure) - 1]
+ in.pushActionBuffer(ActionPopPath)
+ in.pushActionBuffer(ActionReadValue)
+ if in.pushReadBuffer(walk.NewAtomTerminal(walk.MapEnd)) {
+ in.state = JSONInValueEnd
+ return nil
+ }
+ goto valueEnd
+ } else if innermost == JSONInArray && r == ']' {
+ in.structure = in.structure[:len(in.structure) - 1]
+ in.pushActionBuffer(ActionPopPath)
+ in.pushActionBuffer(ActionReadValue)
+ if in.pushReadBuffer(walk.NewAtomTerminal(walk.ArrayEnd)) {
+ in.state = JSONInValueEnd
+ return nil
+ }
+ goto valueEnd
+ }
+ if r != ',' {
+ panic("Expected , after JSON value, found: \"" + string(r) + "\"")
+ }
+ goto valueStart
+ }
+ mapValue: {
+ in.pushActionBuffer(ActionPopPath)
+ r, err := in.nextNonWsRune()
+ if err != nil {
+ panic("Missing value inside object")
+ }
+ if r == '}' {
+ in.structure = in.structure[:len(in.structure) - 1]
+ in.pushActionBuffer(ActionReadValue)
+ if in.pushReadBuffer(walk.NewAtomTerminal(walk.MapEnd)) {
+ in.state = JSONInValueEnd
+ return nil
+ }
+ goto valueEnd
+ }
+ if r != '"' {
+ panic("Expected key found something else")
+ }
+ in.pushActionBuffer(ActionAppendPath)
+ if in.pushReadBuffer(walk.NewAtomStringTerminal()) {
+ in.state = JSONInKey
+ return nil
+ }
+ goto key
+ }
+ arrayValue: {
+ r, err := in.nextNonWsRune()
+ if err != nil {
+ panic("Missing value inside array")
+ }
+ if r == ']' {
+ in.structure = in.structure[:len(in.structure) - 1]
+ in.pushActionBuffer(ActionPopPath)
+ in.pushActionBuffer(ActionReadValue)
+ if in.pushReadBuffer(walk.NewAtomTerminal(walk.ArrayEnd)) {
+ in.state = JSONInValueEnd
+ return nil
+ }
+ goto valueEnd
+ }
+ in.reader.UnreadRune()
+ in.pushActionBuffer(ActionIncrementPath)
+ goto value
+ }
+}
diff --git a/json_tokens/write.go b/json_tokens/write.go
new file mode 100644
index 0000000..813f2f3
--- /dev/null
+++ b/json_tokens/write.go
@@ -0,0 +1,151 @@
+package json_tokens
+
+import (
+ "fmt"
+ "strings"
+ "bufio"
+ "main/walk"
+)
+
+func stringPathSegment(segment walk.PathSegment) string {
+ return fmt.Sprintf("%v", segment)
+}
+
+type JSONOutStructure int
+const (
+ JSONOutRoot JSONOutStructure = iota
+ JSONOutMap
+ JSONOutArray
+ JSONOutString
+ JSONOutValueEnd
+)
+
+type JSONOut struct {
+ structure []JSONOutStructure
+ writer *bufio.Writer
+}
+
+func (out *JSONOut) indent(adjust int) {
+ fmt.Fprint(out.writer, strings.Repeat("\t", len(out.structure) - 1 + adjust))
+}
+
+func (out *JSONOut) atomOut(key string, atom walk.Atom) {
+ state := out.structure[len(out.structure) - 1]
+ switch state {
+ case JSONOutRoot, JSONOutMap, JSONOutArray:
+ switch atom.Typ {
+ case walk.AtomNull, walk.AtomBool, walk.AtomNumber:
+ out.indent(0)
+ if state == JSONOutMap {
+ fmt.Fprintf(out.writer, "%q: ", key)
+ }
+ fmt.Fprint(out.writer, atom.String())
+ out.structure = append(out.structure, JSONOutValueEnd)
+ case walk.AtomStringTerminal:
+ out.indent(0)
+ if state == JSONOutMap {
+ fmt.Fprintf(out.writer, "%q: ", key)
+ }
+ fmt.Fprint(out.writer, "\"")
+ out.structure = append(out.structure, JSONOutString)
+ case walk.AtomTerminal:
+ switch atom.Terminal() {
+ case walk.MapBegin:
+ out.indent(0)
+ if state == JSONOutMap {
+ fmt.Fprintf(out.writer, "%q: ", key)
+ }
+ fmt.Fprint(out.writer, "{\n")
+ out.structure = append(out.structure, JSONOutMap)
+ case walk.ArrayBegin:
+ out.indent(0)
+ if state == JSONOutMap {
+ fmt.Fprintf(out.writer, "%q: ", key)
+ }
+ fmt.Fprint(out.writer, "[\n")
+ out.structure = append(out.structure, JSONOutArray)
+ case walk.MapEnd:
+ out.indent(-1)
+ if state != JSONOutMap {
+ panic("Map ended while not inside a map")
+ }
+ fmt.Fprint(out.writer, "}")
+ out.structure[len(out.structure) - 1] = JSONOutValueEnd
+ case walk.ArrayEnd:
+ out.indent(-1)
+ if state != JSONOutArray {
+ panic("Array ended while not inside a array")
+ }
+ fmt.Fprint(out.writer, "]")
+ out.structure[len(out.structure) - 1] = JSONOutValueEnd
+ default:
+ panic("Invalid TerminalValue")
+ }
+ default:
+ panic("Invalid AtomType in root value")
+ }
+ case JSONOutValueEnd:
+ out.structure = out.structure[:len(out.structure) - 1]
+ underState := out.structure[len(out.structure) - 1]
+ if underState == JSONOutMap && atom.Typ == walk.AtomTerminal && atom.Terminal() == walk.MapEnd {
+ fmt.Fprint(out.writer, "\n")
+ out.indent(-1)
+ fmt.Fprint(out.writer, "}")
+ out.structure[len(out.structure) - 1] = JSONOutValueEnd
+ } else if underState == JSONOutArray && atom.Typ == walk.AtomTerminal && atom.Terminal() == walk.ArrayEnd {
+ fmt.Fprint(out.writer, "\n")
+ out.indent(-1)
+ fmt.Fprint(out.writer, "]")
+ out.structure[len(out.structure) - 1] = JSONOutValueEnd
+ } else if underState == JSONOutRoot {
+ panic("Tried to output JSON after root value has concluded")
+ } else {
+ fmt.Fprint(out.writer, ",\n")
+ out.atomOut(key, atom)
+ }
+ case JSONOutString:
+ if atom.Typ == walk.AtomStringTerminal {
+ fmt.Fprint(out.writer, "\"")
+ out.structure[len(out.structure) - 1] = JSONOutValueEnd
+ } else {
+ fmt.Fprint(out.writer, atom.String())
+ }
+ default:
+ panic("Invalid JSONOutState")
+ }
+}
+
+func (out *JSONOut) Print(path walk.Path, values []walk.Atom) {
+ var segment walk.PathSegment
+ if len(path) > 0 {
+ segment = path[len(path) - 1]
+ }
+ segmentString := stringPathSegment(segment)
+ for _, atom := range values {
+ out.atomOut(segmentString, atom)
+ }
+}
+
+func (out *JSONOut) Write(item walk.WalkItem) error {
+ pathValues, err := walk.Compound(item.Path)
+ if err != nil {
+ return err
+ }
+ path := walk.PathFromWalkValues(pathValues)
+ out.Print(path, item.Value)
+ return nil
+}
+
+func (out *JSONOut) AssertDone() {
+ out.writer.Flush()
+ if len(out.structure) != 2 || out.structure[0] != JSONOutRoot || out.structure[1] != JSONOutValueEnd {
+ panic("Program ended with incomplete JSON output")
+ }
+}
+
+func NewJSONOut(writer *bufio.Writer) *JSONOut {
+ return &JSONOut {
+ structure: []JSONOutStructure{JSONOutRoot},
+ writer: writer,
+ }
+}
diff --git a/main/command.go b/main/command.go
index 63cc3b8..ef48596 100644
--- a/main/command.go
+++ b/main/command.go
@@ -13,12 +13,13 @@ type Command interface {
type PrintValueCommand struct {}
func (cmd PrintValueCommand) exec(state *ProgramState) {
- pathValues, err := walk.Compound(state.path)
+ err := state.out.Write(walk.WalkItem {
+ Path: state.path,
+ Value: state.value,
+ })
if err != nil {
- panic("Tried to convert invalid atoms to values")
+ panic("Error while outputting")
}
- path := walk.PathFromWalkValues(pathValues)
- state.out.Print(path, state.value)
state.pc++
}
func (cmd PrintValueCommand) String() string {
diff --git a/main/main.go b/main/main.go
index 55ed5b5..668253d 100644
--- a/main/main.go
+++ b/main/main.go
@@ -4,14 +4,15 @@ import (
"os"
"bufio"
"main/walk"
+ "main/json_tokens"
)
type Program []Command
type ProgramState struct {
path, value, xreg, yreg, zreg []walk.Atom
- in walk.JSONIn
- out walk.JSONOut
+ in walk.StredReader
+ out walk.StredWriter
program []Command
pc int
}
@@ -44,8 +45,8 @@ func main() {
stdout := bufio.NewWriter(os.Stdout)
state := ProgramState {
- in: walk.NewJSONIn(stdin),
- out: walk.NewJSONOut(stdout),
+ in: json_tokens.NewJSONIn(stdin),
+ out: json_tokens.NewJSONOut(stdout),
program: program,
}
@@ -61,12 +62,13 @@ func main() {
state.program[state.pc].exec(&state)
}
if !quiet {
- pathValues, err := walk.Compound(state.path)
+ err := state.out.Write(walk.WalkItem {
+ Path: state.path,
+ Value: state.value,
+ })
if err != nil {
- panic("Tried to convert invalid atoms to values")
+ panic("Error while outputting")
}
- path := walk.PathFromWalkValues(pathValues)
- state.out.Print(path, state.value)
}
}
diff --git a/walk/atom.go b/walk/atom.go
index 13ad2ff..dfe5fe4 100644
--- a/walk/atom.go
+++ b/walk/atom.go
@@ -43,12 +43,24 @@ func NewAtomNumber(v float64) Atom {
data: math.Float64bits(v),
}
}
+func (v Atom) Number() float64 {
+ if v.Typ != AtomNumber {
+ panic("Tried to use non-number as number")
+ }
+ return math.Float64frombits(v.data)
+}
func NewAtomTerminal(v ValueTerminal) Atom {
return Atom {
Typ: AtomTerminal,
data: uint64(v),
}
}
+func (v Atom) Terminal() ValueTerminal {
+ if v.Typ != AtomTerminal {
+ panic("Tried to use non-terminal as terminal")
+ }
+ return ValueTerminal(v.data)
+}
func NewAtomStringTerminal() Atom {
return Atom {
Typ: AtomStringTerminal,
diff --git a/walk/read.go b/walk/read.go
index bedb856..f25109c 100644
--- a/walk/read.go
+++ b/walk/read.go
@@ -1,491 +1,11 @@
package walk
-import (
- "bufio"
- "math"
- "strings"
- "strconv"
- "fmt"
-)
-
-type ReadAction int
-const (
- ActionReadValue ReadAction = iota
- ActionAppendPath
- ActionPopPath
- ActionIncrementPath
- ActionAppendPathNull
-)
-
-type JSONInStructure int
-const (
- JSONInMap JSONInStructure = iota
- JSONInArray
-)
-
-type JSONInState int
-const (
- JSONInValueEnd JSONInState = iota
- JSONInValue
- JSONInValueStart
- JSONInString
- JSONInKey
-)
-
-type JSONIn struct {
- path []Atom
- reader *bufio.Reader
- structure []JSONInStructure
- state JSONInState
- readBuffer []Atom
- readIndex int
- readBufferCapacity int
- actionBuffer []ReadAction
- actionIndex int
-}
-
-func NewJSONIn(reader *bufio.Reader) JSONIn {
- return JSONIn {
- path: make([]Atom, 0, 256),
- reader: reader,
- structure: []JSONInStructure{},
- state: JSONInValueStart,
- readBuffer: make([]Atom, 0, 256),
- readIndex: 0,
- readBufferCapacity: 256,
- actionBuffer: make([]ReadAction, 0, 256),
- actionIndex: 0,
- }
-}
-
-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)
- }
+type StredReader interface {
+ Read() (WalkItem, error)
+ AssertDone()
}
-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")
- }
-}
-
-// Returns the first full value of a list of atoms and also a boolean to indicate if there isn't a value at the beginning
-func firstValue(atoms []Atom) ([]Atom, bool) {
- if len(atoms) == 0 {
- return nil, true
- }
- if atoms[0].Typ != AtomStringTerminal {
- return atoms[0:1], false
- }
- i := 1
- for {
- if i == len(atoms) {
- return nil, true
- }
- if atoms[i].Typ == AtomStringTerminal {
- return atoms[0:i+1], false
- }
- i++
- }
-}
-
-func (in *JSONIn) readValue() []Atom {
- try:
- value, incomplete := firstValue(in.readBuffer[in.readIndex:])
- if incomplete {
- if in.readIndex == 0 {
- newReadBuffer := make([]Atom, len(in.readBuffer), in.readBufferCapacity * 2)
- in.readBufferCapacity *= 2
- copy(newReadBuffer, in.readBuffer)
- in.readBuffer = newReadBuffer
- in.fillReadBuffer()
- goto try
- }
- copy(in.readBuffer, in.readBuffer[in.readIndex:])
- in.readBuffer = in.readBuffer[:len(in.readBuffer) - in.readIndex]
- in.readIndex = 0
- copy(in.actionBuffer, in.actionBuffer[in.actionIndex:])
- in.actionBuffer = in.actionBuffer[:len(in.actionBuffer) - in.actionIndex]
- in.actionIndex = 0
- in.fillReadBuffer()
- goto try
- }
- in.readIndex += len(value)
- return value
-}
-
-func (in *JSONIn) Read() (WalkItem, error) {
- for {
- if in.actionIndex == len(in.actionBuffer) {
- in.actionIndex = 0
- in.readIndex = 0
- in.actionBuffer = in.actionBuffer[:0]
- in.readBuffer = in.readBuffer[:0]
- err := in.fillReadBuffer()
- if len(in.actionBuffer) == 0 {
- return WalkItem{}, err
- }
- }
- action := in.actionBuffer[in.actionIndex]
- in.actionIndex++
- switch action {
- case ActionReadValue:
- value := in.readValue()
- return WalkItem {
- Value: value,
- Path: in.path,
- }, nil
- case ActionAppendPath:
- value := in.readValue()
- in.path = append(in.path, value...)
- case ActionAppendPathNull:
- in.path = append(in.path, NewAtomNull())
- case ActionPopPath:
- in.popPath()
- case ActionIncrementPath:
- 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. Type: " + fmt.Sprintf("%v", prevIndex.Typ))
- }
- in.path[len(in.path) - 1] = prevIndex
- default:
- panic("Invalid ReadAction")
- }
- }
-}
-
-func (in *JSONIn) AssertDone() {
- if len(in.structure) != 0 || in.state != JSONInValueEnd || in.readIndex < len(in.readBuffer) {
- panic("Input ended on incomplete JSON root")
- }
-}
-
-func (in *JSONIn) pushReadBuffer(atom Atom) bool {
- in.readBuffer = append(in.readBuffer, atom)
- return len(in.readBuffer) == in.readBufferCapacity
-}
-
-func (in *JSONIn) pushActionBuffer(action ReadAction) {
- in.actionBuffer = append(in.actionBuffer, action)
-}
-
-// Appends to the readBuffer until it has reached capacity
-// Also appends to the actionBuffer as needed
-func (in *JSONIn) fillReadBuffer() error {
- switch in.state {
- case JSONInValueStart:
- goto valueStart
- case JSONInValue:
- goto value
- case JSONInValueEnd:
- goto valueEnd
- case JSONInString:
- goto string
- case JSONInKey:
- goto key
- default:
- panic("Invalid JSONInState")
- }
- valueStart: {
- if len(in.structure) == 0 {
- goto value
- }
- innermost := in.structure[len(in.structure) - 1]
- switch innermost {
- case JSONInMap:
- goto mapValue
- case JSONInArray:
- goto arrayValue
- default:
- panic("Invalid JSONInStructure")
- }
- }
- value: {
- r, err := in.nextNonWsRune()
- if err != nil {
- panic("Missing value in JSON")
- }
- switch r {
- case 'n':
- in.requireString("ull")
- in.pushActionBuffer(ActionReadValue)
- if in.pushReadBuffer(NewAtomNull()) {
- in.state = JSONInValueEnd
- return nil
- }
- goto valueEnd
- case 'f':
- in.requireString("alse")
- in.pushActionBuffer(ActionReadValue)
- if in.pushReadBuffer(NewAtomBool(false)) {
- in.state = JSONInValueEnd
- return nil
- }
- goto valueEnd
- case 't':
- in.requireString("rue")
- in.pushActionBuffer(ActionReadValue)
- if in.pushReadBuffer(NewAtomBool(true)) {
- in.state = JSONInValueEnd
- return nil
- }
- goto valueEnd
- case '"':
- in.pushActionBuffer(ActionReadValue)
- if in.pushReadBuffer(NewAtomStringTerminal()) {
- in.state = JSONInString
- return nil
- }
- goto string
- case '{':
- in.structure = append(in.structure, JSONInMap)
- in.pushActionBuffer(ActionReadValue)
- in.pushActionBuffer(ActionAppendPathNull)
- if in.pushReadBuffer(NewAtomTerminal(MapBegin)) {
- in.state = JSONInValueStart
- return nil
- }
- goto mapValue
- case '[':
- in.structure = append(in.structure, JSONInArray)
- in.pushActionBuffer(ActionReadValue)
- in.pushActionBuffer(ActionAppendPathNull)
- if in.pushReadBuffer(NewAtomTerminal(ArrayBegin)) {
- in.state = JSONInValueStart
- return nil
- }
- goto arrayValue
- }
- if isNumberRune(r) {
- var builder strings.Builder
- builder.WriteRune(r)
- for {
- r, _, err = in.reader.ReadRune()
- if err != nil {
- break
- }
- if !isNumberRune(r) {
- in.reader.UnreadRune()
- break
- }
- builder.WriteRune(r)
- }
- number, parseError := strconv.ParseFloat(builder.String(), 64)
- if parseError != nil {
- panic("Invalid number")
- }
- in.pushActionBuffer(ActionReadValue)
- if in.pushReadBuffer(NewAtomNumber(number)) {
- in.state = JSONInValueEnd
- return nil
- }
- goto valueEnd
- }
- panic("Invalid JSON value starting with: " + string(r))
- }
- string: {
- r, _, err := in.reader.ReadRune()
- if err != nil {
- panic("Missing closing terminal in string input: " + err.Error())
- }
- if r == '"' {
- if in.pushReadBuffer(NewAtomStringTerminal()) {
- in.state = JSONInValueEnd
- return nil
- }
- goto valueEnd
- }
- if r == '\\' {
- r, _, err = in.reader.ReadRune()
- if err != nil {
- panic("Missing rune after \\")
- }
- if in.pushReadBuffer(NewAtomStringRune(r)) {
- in.state = JSONInString
- return nil
- }
- goto string
- }
- if in.pushReadBuffer(NewAtomStringRune(r)) {
- in.state = JSONInString
- return nil
- }
- goto string
- }
- key: {
- var full bool
- for {
- r, _, err := in.reader.ReadRune()
- if err != nil {
- panic("Missing closing terminal in string input: " + err.Error())
- }
- if r == '"' {
- full = in.pushReadBuffer(NewAtomStringTerminal())
- break
- }
- if r == '\\' {
- r, _, err = in.reader.ReadRune()
- if err != nil {
- panic("Missing rune after \\")
- }
- if in.pushReadBuffer(NewAtomStringRune(r)) {
- in.state = JSONInKey
- return nil
- }
- continue
- }
- if in.pushReadBuffer(NewAtomStringRune(r)) {
- in.state = JSONInKey
- return nil
- }
- continue
- }
- r, err := in.nextNonWsRune()
- if err != nil {
- panic("Expected : got: " + err.Error())
- }
- if r != ':' {
- panic("Expected : after key")
- }
- if full {
- in.state = JSONInValue
- return nil
- }
- goto value
- }
- valueEnd: {
- r, err := in.nextNonWsRune()
- if err != nil {
- in.state = JSONInValueEnd
- return err
- }
- if len(in.structure) == 0 {
- panic("More input after root JSON object ends")
- }
- innermost := in.structure[len(in.structure) - 1]
- if innermost == JSONInMap && r == '}' {
- in.structure = in.structure[:len(in.structure) - 1]
- in.pushActionBuffer(ActionPopPath)
- in.pushActionBuffer(ActionReadValue)
- if in.pushReadBuffer(NewAtomTerminal(MapEnd)) {
- in.state = JSONInValueEnd
- return nil
- }
- goto valueEnd
- } else if innermost == JSONInArray && r == ']' {
- in.structure = in.structure[:len(in.structure) - 1]
- in.pushActionBuffer(ActionPopPath)
- in.pushActionBuffer(ActionReadValue)
- if in.pushReadBuffer(NewAtomTerminal(ArrayEnd)) {
- in.state = JSONInValueEnd
- return nil
- }
- goto valueEnd
- }
- if r != ',' {
- panic("Expected , after JSON value, found: \"" + string(r) + "\"")
- }
- goto valueStart
- }
- mapValue: {
- in.pushActionBuffer(ActionPopPath)
- r, err := in.nextNonWsRune()
- if err != nil {
- panic("Missing value inside object")
- }
- if r == '}' {
- in.structure = in.structure[:len(in.structure) - 1]
- in.pushActionBuffer(ActionReadValue)
- if in.pushReadBuffer(NewAtomTerminal(MapEnd)) {
- in.state = JSONInValueEnd
- return nil
- }
- goto valueEnd
- }
- if r != '"' {
- panic("Expected key found something else")
- }
- in.pushActionBuffer(ActionAppendPath)
- if in.pushReadBuffer(NewAtomStringTerminal()) {
- in.state = JSONInKey
- return nil
- }
- goto key
- }
- arrayValue: {
- r, err := in.nextNonWsRune()
- if err != nil {
- panic("Missing value inside array")
- }
- if r == ']' {
- in.structure = in.structure[:len(in.structure) - 1]
- in.pushActionBuffer(ActionPopPath)
- in.pushActionBuffer(ActionReadValue)
- if in.pushReadBuffer(NewAtomTerminal(ArrayEnd)) {
- in.state = JSONInValueEnd
- return nil
- }
- goto valueEnd
- }
- in.reader.UnreadRune()
- in.pushActionBuffer(ActionIncrementPath)
- goto value
- }
-}
+type StredWriter interface {
+ Write(WalkItem) error
+ AssertDone()
+} \ No newline at end of file
diff --git a/walk/walk.go b/walk/walk.go
index 6e86877..1073c67 100644
--- a/walk/walk.go
+++ b/walk/walk.go
@@ -1,18 +1,14 @@
package walk
import (
- "fmt"
"strings"
"math"
"unicode/utf8"
- "bufio"
)
// int or string
type PathSegment interface {}
-func stringPathSegment(segment PathSegment) string {
- return fmt.Sprintf("%v", segment)
-}
+
type Path []PathSegment
func (path Path) ToWalkValues() []Value {
var values []Value
@@ -49,135 +45,6 @@ type WalkItem struct {
Path []Atom
}
-type JSONOutStructure int
-const (
- JSONOutRoot JSONOutStructure = iota
- JSONOutMap
- JSONOutArray
- JSONOutString
- JSONOutValueEnd
-)
-
-type JSONOut struct {
- structure []JSONOutStructure
- writer *bufio.Writer
-}
-
-func (out *JSONOut) indent(adjust int) {
- fmt.Fprint(out.writer, strings.Repeat("\t", len(out.structure) - 1 + adjust))
-}
-
-func (out *JSONOut) atomOut(key string, atom Atom) {
- state := out.structure[len(out.structure) - 1]
- switch state {
- case JSONOutRoot, JSONOutMap, JSONOutArray:
- switch atom.Typ {
- case AtomNull, AtomBool, AtomNumber:
- out.indent(0)
- if state == JSONOutMap {
- fmt.Fprintf(out.writer, "%q: ", key)
- }
- fmt.Fprint(out.writer, atom.String())
- out.structure = append(out.structure, JSONOutValueEnd)
- case AtomStringTerminal:
- out.indent(0)
- if state == JSONOutMap {
- fmt.Fprintf(out.writer, "%q: ", key)
- }
- fmt.Fprint(out.writer, "\"")
- out.structure = append(out.structure, JSONOutString)
- case AtomTerminal:
- switch ValueTerminal(atom.data) {
- case MapBegin:
- out.indent(0)
- if state == JSONOutMap {
- fmt.Fprintf(out.writer, "%q: ", key)
- }
- fmt.Fprint(out.writer, "{\n")
- out.structure = append(out.structure, JSONOutMap)
- case ArrayBegin:
- out.indent(0)
- if state == JSONOutMap {
- fmt.Fprintf(out.writer, "%q: ", key)
- }
- fmt.Fprint(out.writer, "[\n")
- out.structure = append(out.structure, JSONOutArray)
- case MapEnd:
- out.indent(-1)
- if state != JSONOutMap {
- panic("Map ended while not inside a map")
- }
- fmt.Fprint(out.writer, "}")
- out.structure[len(out.structure) - 1] = JSONOutValueEnd
- case ArrayEnd:
- out.indent(-1)
- if state != JSONOutArray {
- panic("Array ended while not inside a array")
- }
- fmt.Fprint(out.writer, "]")
- out.structure[len(out.structure) - 1] = JSONOutValueEnd
- default:
- panic("Invalid TerminalValue")
- }
- default:
- panic("Invalid AtomType in root value")
- }
- case JSONOutValueEnd:
- out.structure = out.structure[:len(out.structure) - 1]
- underState := out.structure[len(out.structure) - 1]
- if underState == JSONOutMap && atom.Typ == AtomTerminal && ValueTerminal(atom.data) == MapEnd {
- fmt.Fprint(out.writer, "\n")
- out.indent(-1)
- fmt.Fprint(out.writer, "}")
- out.structure[len(out.structure) - 1] = JSONOutValueEnd
- } else if underState == JSONOutArray && atom.Typ == AtomTerminal && ValueTerminal(atom.data) == ArrayEnd {
- fmt.Fprint(out.writer, "\n")
- out.indent(-1)
- fmt.Fprint(out.writer, "]")
- out.structure[len(out.structure) - 1] = JSONOutValueEnd
- } else if underState == JSONOutRoot {
- panic("Tried to output JSON after root value has concluded")
- } else {
- fmt.Fprint(out.writer, ",\n")
- out.atomOut(key, atom)
- }
- case JSONOutString:
- if atom.Typ == AtomStringTerminal {
- fmt.Fprint(out.writer, "\"")
- out.structure[len(out.structure) - 1] = JSONOutValueEnd
- } else {
- fmt.Fprint(out.writer, atom.String())
- }
- default:
- panic("Invalid JSONOutState")
- }
-}
-
-func (out *JSONOut) Print(path Path, values []Atom) {
- var segment PathSegment
- if len(path) > 0 {
- segment = path[len(path) - 1]
- }
- segmentString := stringPathSegment(segment)
- for _, atom := range values {
- out.atomOut(segmentString, atom)
- }
-}
-
-func (out *JSONOut) AssertDone() {
- out.writer.Flush()
- if len(out.structure) != 2 || out.structure[0] != JSONOutRoot || out.structure[1] != JSONOutValueEnd {
- panic("Program ended with incomplete JSON output")
- }
-}
-
-func NewJSONOut(writer *bufio.Writer) JSONOut {
- return JSONOut {
- structure: []JSONOutStructure{JSONOutRoot},
- writer: writer,
- }
-}
-
func ConcatData(first []Atom, second []Atom) []Atom {
res := make([]Atom, 0, len(first) + len(second))
res = append(res, first...)