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 | |
| parent | 551613765c9e60e2221ac920d2756b949e68f373 (diff) | |
| download | stred-go-3c34366bdd5d817a184d6b1c901d03a16b6faa4b.tar | |
Adds the json_array IO format
| -rw-r--r-- | json_array/read.go | 118 | ||||
| -rw-r--r-- | json_array/write.go | 156 | ||||
| -rw-r--r-- | main/main.go | 6 | ||||
| -rw-r--r-- | walk/atom.go | 12 | 
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: | 
