<- Back to shtanton's homepage
aboutsummaryrefslogtreecommitdiff
path: root/json_array
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 /json_array
parent551613765c9e60e2221ac920d2756b949e68f373 (diff)
downloadstred-go-3c34366bdd5d817a184d6b1c901d03a16b6faa4b.tar
Adds the json_array IO format
Diffstat (limited to 'json_array')
-rw-r--r--json_array/read.go118
-rw-r--r--json_array/write.go156
2 files changed, 274 insertions, 0 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()
+}