diff options
author | Charlie Stanton <charlie@shtanton.xyz> | 2023-05-12 14:25:32 +0100 |
---|---|---|
committer | Charlie Stanton <charlie@shtanton.xyz> | 2023-05-12 14:25:32 +0100 |
commit | 3c34366bdd5d817a184d6b1c901d03a16b6faa4b (patch) | |
tree | 0a084781caf7f062b52d8c0a7481e87afb7d5caa /json_array | |
parent | 551613765c9e60e2221ac920d2756b949e68f373 (diff) | |
download | stred-go-3c34366bdd5d817a184d6b1c901d03a16b6faa4b.tar |
Adds the json_array IO format
Diffstat (limited to 'json_array')
-rw-r--r-- | json_array/read.go | 118 | ||||
-rw-r--r-- | json_array/write.go | 156 |
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() +} |