<- Back to shtanton's homepage
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharlie Stanton <charlie@shtanton.xyz>2023-05-12 14:25:32 +0100
committerCharlie Stanton <charlie@shtanton.xyz>2023-05-12 14:25:32 +0100
commit3c34366bdd5d817a184d6b1c901d03a16b6faa4b (patch)
tree0a084781caf7f062b52d8c0a7481e87afb7d5caa
parent551613765c9e60e2221ac920d2756b949e68f373 (diff)
downloadstred-go-3c34366bdd5d817a184d6b1c901d03a16b6faa4b.tar
Adds the json_array IO format
-rw-r--r--json_array/read.go118
-rw-r--r--json_array/write.go156
-rw-r--r--main/main.go6
-rw-r--r--walk/atom.go12
4 files changed, 289 insertions, 3 deletions
diff --git a/json_array/read.go b/json_array/read.go
new file mode 100644
index 0000000..6334197
--- /dev/null
+++ b/json_array/read.go
@@ -0,0 +1,118 @@
+package json_array
+
+import (
+ "main/walk"
+ "encoding/json"
+ "errors"
+ "bufio"
+)
+
+type state int
+const (
+ stateStart state = iota
+ stateValueStart
+ stateEnd
+ stateDead
+)
+
+func atomiseValue(value interface{}) []walk.Atom {
+ switch v := value.(type) {
+ case nil:
+ return []walk.Atom{walk.NewAtomNull()}
+ case bool:
+ return []walk.Atom{walk.NewAtomBool(v)}
+ case float64:
+ return []walk.Atom{walk.NewAtomNumber(v)}
+ case string:
+ atoms := []walk.Atom{walk.NewAtomStringTerminal()}
+ for _, r := range v {
+ atoms = append(atoms, walk.NewAtomStringRune(r))
+ }
+ atoms = append(atoms, walk.NewAtomStringTerminal())
+ return atoms
+ case []interface{}:
+ atoms := []walk.Atom{walk.NewAtomTerminal(walk.ArrayBegin)}
+ for _, element := range v {
+ atoms = append(atoms, atomiseValue(element)...)
+ }
+ atoms = append(atoms, walk.NewAtomTerminal(walk.ArrayEnd))
+ return atoms
+ case map[string]interface{}:
+ atoms := []walk.Atom{walk.NewAtomTerminal(walk.MapBegin)}
+ for key, element := range v {
+ atoms = append(atoms, atomiseValue(key)...)
+ atoms = append(atoms, atomiseValue(element)...)
+ }
+ atoms = append(atoms, walk.NewAtomTerminal(walk.MapEnd))
+ return atoms
+ default:
+ panic("Invalid JSON value type")
+ }
+}
+
+func NewJSONArrayReader(reader *bufio.Reader) *JSONArrayReader {
+ return &JSONArrayReader {
+ decoder: json.NewDecoder(reader),
+ state: stateStart,
+ index: 0,
+ }
+}
+
+type JSONArrayReader struct {
+ decoder *json.Decoder
+ state state
+ index int
+}
+
+func (in *JSONArrayReader) Read() (walk.WalkItem, error) {
+ restart:
+ switch in.state {
+ case stateStart:
+ arrayStart, err := in.decoder.Token()
+ if err != nil {
+ panic("Error reading start of JSON array")
+ }
+ delim, isDelim := arrayStart.(json.Delim)
+ if !isDelim || delim != '[' {
+ panic("JSON input is not an array!")
+ }
+ in.state = stateValueStart
+ goto restart
+ case stateValueStart:
+ if !in.decoder.More() {
+ in.state = stateEnd
+ goto restart
+ }
+ var m interface{}
+ err := in.decoder.Decode(&m)
+ if err != nil {
+ panic("Error decoding array value")
+ }
+ in.index += 1
+ return walk.WalkItem {
+ Path: []walk.Atom{walk.NewAtomNumber(float64(in.index - 1))},
+ Value: atomiseValue(m),
+ }, nil
+ case stateEnd:
+ arrayEnd, err := in.decoder.Token()
+ if err != nil {
+ panic("Error reading end of JSON array")
+ }
+ delim, isDelim := arrayEnd.(json.Delim)
+ if !isDelim || delim != ']' {
+ panic("JSON array wasn't ended")
+ }
+ in.state = stateDead
+ return walk.WalkItem{}, errors.New("eof")
+ case stateDead:
+ return walk.WalkItem{}, errors.New("eof")
+ default:
+ panic("Unreachable!!!")
+ }
+}
+
+func (in *JSONArrayReader) AssertDone() {
+ if in.state != stateDead || in.decoder.More() {
+ panic("More JSON after array value")
+ }
+}
diff --git a/json_array/write.go b/json_array/write.go
new file mode 100644
index 0000000..4d202c4
--- /dev/null
+++ b/json_array/write.go
@@ -0,0 +1,156 @@
+package json_array
+
+import (
+ "bufio"
+ "strings"
+ "main/walk"
+ "encoding/json"
+)
+
+func assembleValue(atoms []walk.Atom) (interface{}, []walk.Atom) {
+ if len(atoms) == 0 {
+ panic("Missing JSON value in output")
+ }
+ switch atoms[0].Typ {
+ case walk.AtomNull:
+ return nil, atoms[1:]
+ case walk.AtomBool:
+ return atoms[0].Bool(), atoms[1:]
+ case walk.AtomNumber:
+ return atoms[0].Number(), atoms[1:]
+ case walk.AtomStringTerminal:
+ var builder strings.Builder
+ atoms = atoms[1:]
+ for {
+ if len(atoms) == 0 {
+ panic("Missing closing string terminal")
+ }
+ if atoms[0].Typ == walk.AtomStringTerminal {
+ break
+ }
+ if atoms[0].Typ != walk.AtomStringRune {
+ panic("Non string rune atom inside string")
+ }
+ builder.WriteRune(atoms[0].StringRune())
+ atoms = atoms[1:]
+ }
+ atoms = atoms[1:]
+ return builder.String(), atoms
+ case walk.AtomStringRune:
+ panic("String rune used outside of string terminals")
+ case walk.AtomTerminal:
+ terminal := atoms[0].Terminal()
+ switch terminal {
+ case walk.ArrayEnd, walk.MapEnd:
+ panic("Tried to extract value from end terminal")
+ case walk.ArrayBegin:
+ var arr []interface{}
+ var element interface{}
+ atoms = atoms[1:]
+ for {
+ if len(atoms) == 0 {
+ panic("Missing array end terminal")
+ }
+ if atoms[0].Typ == walk.AtomTerminal && atoms[0].Terminal() == walk.ArrayEnd {
+ atoms = atoms[1:]
+ break
+ }
+ element, atoms = assembleValue(atoms)
+ arr = append(arr, element)
+ }
+ return arr, atoms
+ case walk.MapBegin:
+ obj := make(map[string]interface{})
+ var key interface{}
+ var element interface{}
+ atoms = atoms[1:]
+ for {
+ if len(atoms) == 0 {
+ panic("Missing map end terminal")
+ }
+ if atoms[0].Typ == walk.AtomTerminal && atoms[0].Terminal() == walk.MapEnd {
+ atoms = atoms[1:]
+ break
+ }
+ key, atoms = assembleValue(atoms)
+ element, atoms = assembleValue(atoms)
+ keyString, keyIsString := key.(string)
+ if !keyIsString {
+ panic("Key is not string")
+ }
+ obj[keyString] = element
+ }
+ return obj, atoms
+ default:
+ panic("Invalid terminal")
+ }
+ default:
+ panic("Invalid atom")
+ }
+}
+
+func outputValue(atoms []walk.Atom, writer *bufio.Writer) {
+ if len(atoms) == 0 {
+ return
+ }
+ value, atoms := assembleValue(atoms)
+ if len(atoms) != 0 {
+ panic("Tried to output more than one JSON value")
+ }
+ bytes, err := json.MarshalIndent(value, "\t", "\t")
+ if err != nil {
+ panic("Error marshalling json into bytes")
+ }
+ _, err = writer.Write(bytes)
+ if err != nil {
+ panic("Error writing value")
+ }
+}
+
+type writerState int
+const (
+ writerStateStart writerState = iota
+ writerStateValue
+)
+
+func NewJSONArrayWriter(writer *bufio.Writer) *JSONArrayWriter {
+ return &JSONArrayWriter {
+ writer: writer,
+ state: writerStateStart,
+ }
+}
+
+type JSONArrayWriter struct {
+ writer *bufio.Writer
+ state writerState
+}
+
+func (out *JSONArrayWriter) Write(item walk.WalkItem) error {
+ switch out.state {
+ case writerStateStart:
+ _, err := out.writer.WriteString("[\n\t")
+ if err != nil {
+ panic("Error outputting [ at beginning of array")
+ }
+ outputValue(item.Value, out.writer)
+ out.state = writerStateValue
+ return nil
+ case writerStateValue:
+ _, err := out.writer.WriteString(",\n\t")
+ if err != nil {
+ panic("Error outputting comma at the end of a value")
+ }
+ outputValue(item.Value, out.writer)
+ return nil
+ default:
+ panic("Invalid writer state")
+ }
+}
+
+func (out *JSONArrayWriter) AssertDone() {
+ if out.state == writerStateStart {
+ out.writer.WriteString("[")
+ }
+ out.writer.WriteString("\n]")
+ out.writer.Flush()
+}
diff --git a/main/main.go b/main/main.go
index 668253d..a506954 100644
--- a/main/main.go
+++ b/main/main.go
@@ -4,7 +4,7 @@ import (
"os"
"bufio"
"main/walk"
- "main/json_tokens"
+ "main/json_array"
)
type Program []Command
@@ -45,8 +45,8 @@ func main() {
stdout := bufio.NewWriter(os.Stdout)
state := ProgramState {
- in: json_tokens.NewJSONIn(stdin),
- out: json_tokens.NewJSONOut(stdout),
+ in: json_array.NewJSONArrayReader(stdin),
+ out: json_array.NewJSONArrayWriter(stdout),
program: program,
}
diff --git a/walk/atom.go b/walk/atom.go
index dfe5fe4..299c39d 100644
--- a/walk/atom.go
+++ b/walk/atom.go
@@ -37,6 +37,12 @@ func NewAtomBool(v bool) Atom {
}
}
}
+func (v Atom) Bool() bool {
+ if v.Typ != AtomBool {
+ panic("Tried to use non-bool as bool")
+ }
+ return v.data == 1
+}
func NewAtomNumber(v float64) Atom {
return Atom {
Typ: AtomNumber,
@@ -73,6 +79,12 @@ func NewAtomStringRune(v rune) Atom {
data: uint64(v),
}
}
+func (v Atom) StringRune() rune {
+ if v.Typ != AtomStringRune {
+ panic("Tried to use non-stringrune as stringrune")
+ }
+ return rune(v.data)
+}
func (v Atom) String() string {
switch v.Typ {
case AtomNull: