From 551613765c9e60e2221ac920d2756b949e68f373 Mon Sep 17 00:00:00 2001 From: Charlie Stanton Date: Fri, 12 May 2023 11:37:44 +0100 Subject: Move reading and writing of tokens into a separate package to prepare for other input and output formats --- json_tokens/read.go | 490 ++++++++++++++++++++++++++++++++++++++++++++++++++ json_tokens/write.go | 151 ++++++++++++++++ main/command.go | 9 +- main/main.go | 18 +- walk/atom.go | 12 ++ walk/read.go | 494 +-------------------------------------------------- walk/walk.go | 135 +------------- 7 files changed, 676 insertions(+), 633 deletions(-) create mode 100644 json_tokens/read.go create mode 100644 json_tokens/write.go 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...) -- cgit v1.2.3