package walk import ( "bufio" "math" "strings" "strconv" "fmt" ) type ReadAction interface { String() string } type ActionReadValue struct {} func (_ ActionReadValue) String() string { return "read" } type ActionAppendPath struct { atoms []Atom } func (action ActionAppendPath) String() string { return fmt.Sprintf("append(%v)", action.atoms) } type ActionPopPath struct {} func (_ ActionPopPath) String() string { return "pop" } type ActionIncrementPath struct {} func (_ ActionIncrementPath) String() string { return "increment" } type JSONInStructure int const ( JSONInRoot JSONInStructure = iota JSONInMap JSONInArray JSONInString JSONInValueEnd ) type JSONIn struct { path []Atom reader *bufio.Reader structure []JSONInStructure 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{JSONInRoot}, 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) } } 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") } } func (in *JSONIn) readString(out []Atom) []Atom { // TODO: improve out = append(out, NewAtomStringTerminal()) for { r, _, err := in.reader.ReadRune() if err != nil { panic("Missing closing terminal in string input: " + err.Error()) } if r == '"' { break } if r == '\\' { r, _, err = in.reader.ReadRune() if err != nil { panic("Missing rune after \\") } if len(out) == cap(out) { newOut := make([]Atom, len(out), cap(out) * 2) copy(newOut, out) out = newOut } out = append(out, NewAtomStringRune(r)) continue } if len(out) == cap(out) { newOut := make([]Atom, len(out), cap(out) * 2) copy(newOut, out) out = newOut } out = append(out, NewAtomStringRune(r)) } out = append(out, NewAtomStringTerminal()) return out } // 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) Read() (WalkItem, error) { actionLoop: for { if in.actionIndex == len(in.actionBuffer) { in.actionIndex = 0 in.readIndex = 0 in.actionBuffer = in.actionBuffer[:0] in.readBuffer = in.readBuffer[:0] structure, err := in.fillReadBuffer(in.structure) in.structure = structure if len(in.actionBuffer) == 0 { return WalkItem{}, err } } action := in.actionBuffer[in.actionIndex] switch a := action.(type) { case ActionReadValue: 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 structure, _ := in.fillReadBuffer(in.structure) in.structure = structure continue actionLoop } 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 structure, _ := in.fillReadBuffer(in.structure) in.structure = structure continue actionLoop } in.readIndex += len(value) in.actionIndex++ return WalkItem { Value: value, Path: in.path, }, nil case ActionAppendPath: in.path = append(in.path, a.atoms...) 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") } in.actionIndex++ } } func (in *JSONIn) AssertDone() { if len(in.structure) != 1 || in.structure[0] != JSONInRoot { 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) } func (in *JSONIn) fillReadBuffer(structure []JSONInStructure) ([]JSONInStructure, error) { valueStart: { state := structure[len(structure) - 1] switch state { case JSONInString: structure = structure[:len(structure) - 1] goto string case JSONInValueEnd: structure = structure[:len(structure) - 1] goto valueEnd case JSONInMap: goto mapValue case JSONInArray: goto arrayValue case JSONInRoot: goto value } } value: { r, err := in.nextNonWsRune() if err != nil { return structure, err } switch r { case 'n': in.requireString("ull") in.pushActionBuffer(ActionReadValue{}) if in.pushReadBuffer(NewAtomNull()) { return append(structure, JSONInValueEnd), nil } goto valueEnd case 'f': in.requireString("alse") in.pushActionBuffer(ActionReadValue{}) if in.pushReadBuffer(NewAtomBool(false)) { return append(structure, JSONInValueEnd), nil } goto valueEnd case 't': in.requireString("rue") in.pushActionBuffer(ActionReadValue{}) if in.pushReadBuffer(NewAtomBool(true)) { return append(structure, JSONInValueEnd), nil } goto valueEnd case '"': in.pushActionBuffer(ActionReadValue{}) if in.pushReadBuffer(NewAtomStringTerminal()) { return append(structure, JSONInString), nil } goto string case '{': structure = append(structure, JSONInMap) in.pushActionBuffer(ActionReadValue{}) in.pushActionBuffer(ActionAppendPath {[]Atom{NewAtomNull()}}) if in.pushReadBuffer(NewAtomTerminal(MapBegin)) { return structure, nil } goto mapValue case '[': structure = append(structure, JSONInArray) in.pushActionBuffer(ActionReadValue{}) in.pushActionBuffer(ActionAppendPath {[]Atom{NewAtomNull()}}) if in.pushReadBuffer(NewAtomTerminal(ArrayBegin)) { return structure, 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)) { return append(structure, JSONInValueEnd), 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()) { return append(structure, JSONInValueEnd), nil } goto valueEnd } if r == '\\' { r, _, err = in.reader.ReadRune() if err != nil { panic("Missing rune after \\") } if in.pushReadBuffer(NewAtomStringRune(r)) { return append(structure, JSONInString), nil } goto string } if in.pushReadBuffer(NewAtomStringRune(r)) { return append(structure, JSONInString), nil } goto string } valueEnd: { r, err := in.nextNonWsRune() if err != nil { return structure, err } underState := structure[len(structure) - 1] if underState == JSONInRoot { panic("More input after root JSON object ends") } else if underState == JSONInMap && r == '}' { structure = structure[:len(structure) - 1] in.pushActionBuffer(ActionPopPath{}) in.pushActionBuffer(ActionReadValue{}) if in.pushReadBuffer(NewAtomTerminal(MapEnd)) { return append(structure, JSONInValueEnd), nil } goto valueEnd } else if underState == JSONInArray && r == ']' { structure = structure[:len(structure) - 1] in.pushActionBuffer(ActionPopPath{}) in.pushActionBuffer(ActionReadValue{}) if in.pushReadBuffer(NewAtomTerminal(ArrayEnd)) { return append(structure, JSONInValueEnd), 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 == '}' { structure = structure[:len(structure) - 1] in.pushActionBuffer(ActionReadValue{}) if in.pushReadBuffer(NewAtomTerminal(MapEnd)) { return append(structure, JSONInValueEnd), nil } goto valueEnd } if r != '"' { panic("Expected key found something else") } var keyAtoms []Atom keyAtoms = in.readString(keyAtoms) in.pushActionBuffer(ActionAppendPath {keyAtoms}) r, err = in.nextNonWsRune() if err != nil { panic("Expected : got: " + err.Error()) } if r != ':' { panic("Expected : after key") } goto value } arrayValue: { r, err := in.nextNonWsRune() if err != nil { panic("Missing value inside array") } if r == ']' { structure = structure[:len(structure) - 1] in.pushActionBuffer(ActionPopPath{}) in.pushActionBuffer(ActionReadValue{}) if in.pushReadBuffer(NewAtomTerminal(ArrayEnd)) { return append(structure, JSONInValueEnd), nil } goto valueEnd } in.reader.UnreadRune() in.pushActionBuffer(ActionIncrementPath{}) goto value } }