diff options
| -rw-r--r-- | json_tokens/read.go | 490 | ||||
| -rw-r--r-- | json_tokens/write.go | 151 | ||||
| -rw-r--r-- | main/command.go | 9 | ||||
| -rw-r--r-- | main/main.go | 18 | ||||
| -rw-r--r-- | walk/atom.go | 12 | ||||
| -rw-r--r-- | walk/read.go | 494 | ||||
| -rw-r--r-- | walk/walk.go | 135 | 
7 files changed, 676 insertions, 633 deletions
| 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...) | 
