diff options
| author | Charlie Stanton <charlie@shtanton.xyz> | 2023-04-25 14:20:20 +0100 | 
|---|---|---|
| committer | Charlie Stanton <charlie@shtanton.xyz> | 2023-04-25 14:20:20 +0100 | 
| commit | 96a10b92057319b2c30782ea19f5427a333e6bc3 (patch) | |
| tree | b34fc1d164e7ed8331d31718ee0d0e2c8e2aee15 | |
| parent | 6a4e25b1c691846185e9dd698c1558981089738c (diff) | |
| download | stred-go-96a10b92057319b2c30782ea19f5427a333e6bc3.tar | |
Separates JSON parsing code into its own file
| -rw-r--r-- | walk/read.go | 285 | ||||
| -rw-r--r-- | walk/walk.go | 278 | 
2 files changed, 285 insertions, 278 deletions
| diff --git a/walk/read.go b/walk/read.go new file mode 100644 index 0000000..6f3acfe --- /dev/null +++ b/walk/read.go @@ -0,0 +1,285 @@ +package walk + +import ( +	"bufio" +	"math" +	"strings" +	"strconv" +) + +type JSONInStructure int +const ( +	JSONInRoot JSONInStructure = iota +	JSONInMap +	JSONInArray +	JSONInValueEnd +) + +type JSONIn struct { +	path []Atom +	reader *bufio.Reader +	structure []JSONInStructure +} + +func NewJSONIn(reader *bufio.Reader) JSONIn { +	return JSONIn { +		path: make([]Atom, 0, 256), +		reader: reader, +		structure: []JSONInStructure{JSONInRoot}, +	} +} + +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 +} + +func (in *JSONIn) Read() (WalkItem, error) { +	restart: +	// TODO: Escaping +	// TODO: Don't allow trailing commas +	// TODO: Proper float parsing with e and stuff +	r, err := in.nextNonWsRune() +	if err != nil { +		return WalkItem {}, err +	} +	state := in.structure[len(in.structure) - 1] +	switch state { +		case JSONInMap: +			in.popPath() +			if r == '}' { +				in.structure[len(in.structure) - 1] = JSONInValueEnd +				return WalkItem { +					Value: []Atom{NewAtomTerminal(MapEnd)}, +					Path: in.path, +				}, nil +			} +			if r != '"' { +				panic("Expected key, found something else") +			} +			in.path = in.readString(in.path) +			r, err = in.nextNonWsRune() +			if err != nil { +				panic("Expected : got: " + err.Error()) +			} +			if r != ':' { +				panic("Expected : after key") +			} +			r, err = in.nextNonWsRune() +			if err != nil { +				panic("Missing map value after key: " + err.Error()) +			} +		case JSONInArray: +			if r == ']' { +				in.structure[len(in.structure) - 1] = JSONInValueEnd +				in.popPath() +				return WalkItem { +					Value: []Atom{NewAtomTerminal(ArrayEnd)}, +					Path: in.path, +				}, nil +			} +			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") +			} +			in.path[len(in.path) - 1] = prevIndex +		case JSONInRoot: +		case JSONInValueEnd: +			in.structure = in.structure[:len(in.structure) - 1] +			underState := in.structure[len(in.structure) - 1] +			if underState == JSONInRoot { +				panic("More input after root JSON object ends") +			} else if underState == JSONInMap && r == '}' { +				in.structure[len(in.structure) - 1] = JSONInValueEnd +				in.popPath() +				return WalkItem { +					Value: []Atom{NewAtomTerminal(MapEnd)}, +					Path: in.path, +				}, nil +			} else if underState == JSONInArray && r == ']' { +				in.structure[len(in.structure) - 1] = JSONInValueEnd +				in.popPath() +				return WalkItem { +					Value: []Atom{NewAtomTerminal(ArrayEnd)}, +					Path: in.path, +				}, nil +			} +			if r != ',' { +				panic("Expected , after JSON value, found: \"" + string(r) + "\"") +			} +			goto restart +		default: +			panic("Invalid JSONIn state") +	} +	switch r { +		case 'n': +			in.requireString("ull") +			in.structure = append(in.structure, JSONInValueEnd) +			return WalkItem { +				Value: []Atom{NewAtomNull()}, +				Path: in.path, +			}, nil +		case 'f': +			in.requireString("alse") +			in.structure = append(in.structure, JSONInValueEnd) +			return WalkItem { +				Value: []Atom{NewAtomBool(false)}, +				Path: in.path, +			}, nil +		case 't': +			in.requireString("rue") +			in.structure = append(in.structure, JSONInValueEnd) +			return WalkItem { +				Value: []Atom{NewAtomBool(true)}, +				Path: in.path, +			}, nil +		case '"': +			value := make([]Atom, 0, 64) +			value = in.readString(value) +			in.structure = append(in.structure, JSONInValueEnd) +			return WalkItem { +				Value: value, +				Path: in.path, +			}, nil +		case '{': +			in.structure = append(in.structure, JSONInMap) +			in.path = append(in.path, NewAtomNull()) +			return WalkItem { +				Value: []Atom{NewAtomTerminal(MapBegin)}, +				Path: in.path[:len(in.path) - 1], +			}, nil +		case '[': +			in.structure = append(in.structure, JSONInArray) +			in.path = append(in.path, NewAtomNull()) +			return WalkItem { +				Value: []Atom{NewAtomTerminal(ArrayBegin)}, +				Path: in.path[:len(in.path) - 1], +			}, nil +	} +	if isNumberRune(r) { +		var builder strings.Builder +		builder.WriteRune(r) +		for { +			r, _, err = in.reader.ReadRune() +			if err != nil || !isNumberRune(r) { +				break +			} +			builder.WriteRune(r) +		} +		in.reader.UnreadRune() +		number, parseError := strconv.ParseFloat(builder.String(), 64) +		if parseError != nil { +			panic("Invalid number") +		} +		in.structure = append(in.structure, JSONInValueEnd) +		return WalkItem { +			Value: []Atom{NewAtomNumber(number)}, +			Path: in.path, +		}, nil +	} +	panic("Invalid JSON value") +} + +func (in *JSONIn) AssertDone() { +	if len(in.structure) != 2 || in.structure[0] != JSONInRoot || in.structure[1] != JSONInValueEnd { +		panic("Input ended on incomplete JSON root") +	} +} diff --git a/walk/walk.go b/walk/walk.go index 20eac38..6e86877 100644 --- a/walk/walk.go +++ b/walk/walk.go @@ -6,7 +6,6 @@ import (  	"math"  	"unicode/utf8"  	"bufio" -	"strconv"  )  // int or string @@ -50,283 +49,6 @@ type WalkItem struct {  	Path []Atom  } -type JSONInStructure int -const ( -	JSONInRoot JSONInStructure = iota -	JSONInMap -	JSONInArray -	JSONInValueEnd -) - -type JSONIn struct { -	path []Atom -	reader *bufio.Reader -	structure []JSONInStructure -} - -func NewJSONIn(reader *bufio.Reader) JSONIn { -	return JSONIn { -		path: make([]Atom, 0, 256), -		reader: reader, -		structure: []JSONInStructure{JSONInRoot}, -	} -} - -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 -} - -func (in *JSONIn) Read() (WalkItem, error) { -	restart: -	// TODO: Escaping -	// TODO: Don't allow trailing commas -	// TODO: Proper float parsing with e and stuff -	r, err := in.nextNonWsRune() -	if err != nil { -		return WalkItem {}, err -	} -	state := in.structure[len(in.structure) - 1] -	switch state { -		case JSONInMap: -			in.popPath() -			if r == '}' { -				in.structure[len(in.structure) - 1] = JSONInValueEnd -				return WalkItem { -					Value: []Atom{NewAtomTerminal(MapEnd)}, -					Path: in.path, -				}, nil -			} -			if r != '"' { -				panic("Expected key, found something else") -			} -			in.path = in.readString(in.path) -			r, err = in.nextNonWsRune() -			if err != nil { -				panic("Expected : got: " + err.Error()) -			} -			if r != ':' { -				panic("Expected : after key") -			} -			r, err = in.nextNonWsRune() -			if err != nil { -				panic("Missing map value after key: " + err.Error()) -			} -		case JSONInArray: -			if r == ']' { -				in.structure[len(in.structure) - 1] = JSONInValueEnd -				in.popPath() -				return WalkItem { -					Value: []Atom{NewAtomTerminal(ArrayEnd)}, -					Path: in.path, -				}, nil -			} -			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") -			} -			in.path[len(in.path) - 1] = prevIndex -		case JSONInRoot: -		case JSONInValueEnd: -			in.structure = in.structure[:len(in.structure) - 1] -			underState := in.structure[len(in.structure) - 1] -			if underState == JSONInRoot { -				panic("More input after root JSON object ends") -			} else if underState == JSONInMap && r == '}' { -				in.structure[len(in.structure) - 1] = JSONInValueEnd -				in.popPath() -				return WalkItem { -					Value: []Atom{NewAtomTerminal(MapEnd)}, -					Path: in.path, -				}, nil -			} else if underState == JSONInArray && r == ']' { -				in.structure[len(in.structure) - 1] = JSONInValueEnd -				in.popPath() -				return WalkItem { -					Value: []Atom{NewAtomTerminal(ArrayEnd)}, -					Path: in.path, -				}, nil -			} -			if r != ',' { -				panic("Expected , after JSON value, found: \"" + string(r) + "\"") -			} -			goto restart -		default: -			panic("Invalid JSONIn state") -	} -	switch r { -		case 'n': -			in.requireString("ull") -			in.structure = append(in.structure, JSONInValueEnd) -			return WalkItem { -				Value: []Atom{NewAtomNull()}, -				Path: in.path, -			}, nil -		case 'f': -			in.requireString("alse") -			in.structure = append(in.structure, JSONInValueEnd) -			return WalkItem { -				Value: []Atom{NewAtomBool(false)}, -				Path: in.path, -			}, nil -		case 't': -			in.requireString("rue") -			in.structure = append(in.structure, JSONInValueEnd) -			return WalkItem { -				Value: []Atom{NewAtomBool(true)}, -				Path: in.path, -			}, nil -		case '"': -			value := make([]Atom, 0, 64) -			value = in.readString(value) -			in.structure = append(in.structure, JSONInValueEnd) -			return WalkItem { -				Value: value, -				Path: in.path, -			}, nil -		case '{': -			in.structure = append(in.structure, JSONInMap) -			in.path = append(in.path, NewAtomNull()) -			return WalkItem { -				Value: []Atom{NewAtomTerminal(MapBegin)}, -				Path: in.path[:len(in.path) - 1], -			}, nil -		case '[': -			in.structure = append(in.structure, JSONInArray) -			in.path = append(in.path, NewAtomNull()) -			return WalkItem { -				Value: []Atom{NewAtomTerminal(ArrayBegin)}, -				Path: in.path[:len(in.path) - 1], -			}, nil -	} -	if isNumberRune(r) { -		var builder strings.Builder -		builder.WriteRune(r) -		for { -			r, _, err = in.reader.ReadRune() -			if err != nil || !isNumberRune(r) { -				break -			} -			builder.WriteRune(r) -		} -		in.reader.UnreadRune() -		number, parseError := strconv.ParseFloat(builder.String(), 64) -		if parseError != nil { -			panic("Invalid number") -		} -		in.structure = append(in.structure, JSONInValueEnd) -		return WalkItem { -			Value: []Atom{NewAtomNumber(number)}, -			Path: in.path, -		}, nil -	} -	panic("Invalid JSON value") -} - -func (in *JSONIn) AssertDone() { -	if len(in.structure) != 2 || in.structure[0] != JSONInRoot || in.structure[1] != JSONInValueEnd { -		panic("Input ended on incomplete JSON root") -	} -} -  type JSONOutStructure int  const (  	JSONOutRoot JSONOutStructure = iota | 
