diff options
| author | Charlie Stanton <charlie@shtanton.xyz> | 2023-07-19 11:57:59 +0100 | 
|---|---|---|
| committer | Charlie Stanton <charlie@shtanton.xyz> | 2023-07-19 11:57:59 +0100 | 
| commit | 8cf10efe3b5a1bcc70bc6e5590ee63fd5eb00c5b (patch) | |
| tree | 7a16883c17c2bdcc49b2f9d4f333dfc76c66248f /json | |
| parent | 3c34366bdd5d817a184d6b1c901d03a16b6faa4b (diff) | |
| download | stred-go-8cf10efe3b5a1bcc70bc6e5590ee63fd5eb00c5b.tar | |
Huge refactor to a more value based system, doing away with terminals. Also introduces unit testing
Diffstat (limited to 'json')
| -rw-r--r-- | json/read.go | 288 | ||||
| -rw-r--r-- | json/write.go | 202 | 
2 files changed, 490 insertions, 0 deletions
| diff --git a/json/read.go b/json/read.go new file mode 100644 index 0000000..6a68467 --- /dev/null +++ b/json/read.go @@ -0,0 +1,288 @@ +package json + +import ( +	"bufio" +	"main/walk" +	"strings" +	"strconv" +	"errors" +) + +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 == '.' +} + +type JSONReaderStructure int +const ( +	JSONReaderStructureArray JSONReaderStructure = iota +	JSONReaderStructureMap +) + +type JSONReaderState int +const ( +	JSONReaderStateValue JSONReaderState = iota +	JSONReaderStateValueEnd +) + +func NewJSONReader(reader *bufio.Reader) *JSONReader { +	return &JSONReader { +		path: nil, +		structure: nil, +		state: JSONReaderStateValue, +		reader: reader, +	} +} + +type JSONReader struct { +	path []walk.Value +	structure []JSONReaderStructure +	state JSONReaderState +	reader *bufio.Reader +} + +func (reader *JSONReader) Read() (walk.WalkItem, error) { +	switch reader.state { +	case JSONReaderStateValue: +		if len(reader.structure) == 0 { +			path := reader.clonePath() +			value, err := reader.readValue() +			if err != nil { +				panic("Missing JSON input") +			} +			return walk.WalkItem { +				Path: path, +				Value: []walk.Value{value}, +			}, nil +		} +		switch reader.structure[len(reader.structure) - 1] { +		case JSONReaderStructureArray: +			r, err := reader.nextNonWsRune() +			if err != nil { +				panic("Missing rest of array") +			} +			if r == ']' { +				reader.structure = reader.structure[:len(reader.structure) - 1] +				reader.path = reader.path[:len(reader.path) - 1] +				reader.state = JSONReaderStateValueEnd +				return reader.Read() +			} +			reader.reader.UnreadRune() +			prevIndex := reader.path[len(reader.path) - 1].(walk.NumberScalar) +			reader.path[len(reader.path) - 1] = prevIndex + 1 +			path := reader.clonePath() +			value, err := reader.readValue() +			if err != nil { +				panic("Missing value in array") +			} +			return walk.WalkItem { +				Path: path, +				Value: []walk.Value{value}, +			}, nil +		case JSONReaderStructureMap: +			r, err := reader.nextNonWsRune() +			if err != nil { +				panic("Reached EOF inside JSON map") +			} +			if r == '}' { +				reader.structure = reader.structure[:len(reader.structure) - 1] +				reader.path = reader.path[:len(reader.path) - 1] +				reader.state = JSONReaderStateValueEnd +				return reader.Read() +			} +			if r != '"' { +				panic("Expected key in map, found something else") +			} +			key := reader.readString() +			reader.path[len(reader.path) - 1] = walk.StringStructure(key) +			r, err = reader.nextNonWsRune() +			if err != nil { +				panic("Reached EOF after map key") +			} +			if r != ':' { +				panic("Expected : after map key, found something else") +			} +			path := reader.clonePath() +			value, err := reader.readValue() +			if err != nil { +				panic("Missing value in map") +			} +			return walk.WalkItem { +				Path: path, +				Value: []walk.Value{value}, +			}, nil +		default: +			panic("Invalid JSONReaderStructure") +		} +	case JSONReaderStateValueEnd: +		if len(reader.structure) == 0 { +			_, err := reader.nextNonWsRune() +			if err == nil { +				panic("input continues after JSON value") +			} +			return walk.WalkItem{}, errors.New("eof") +		} +		switch reader.structure[len(reader.structure) - 1] { +		case JSONReaderStructureArray: +			r, err := reader.nextNonWsRune() +			if err != nil { +				panic("Missing end of array") +			} +			if r == ']' { +				reader.path = reader.path[:len(reader.path) - 1] +				reader.structure = reader.structure[:len(reader.structure) - 1] +				reader.state = JSONReaderStateValueEnd +				return reader.Read() +			} +			if r != ',' { +				panic("Missing , after array value") +			} +			reader.state = JSONReaderStateValue +			return reader.Read() +		case JSONReaderStructureMap: +			r, err := reader.nextNonWsRune() +			if err != nil { +				panic("Missing end of map") +			} +			if r == '}' { +				reader.path = reader.path[:len(reader.path) - 1] +				reader.structure = reader.structure[:len(reader.structure) - 1] +				reader.state = JSONReaderStateValueEnd +				return reader.Read() +			} +			if r != ',' { +				panic("Missing , after map value") +			} +			reader.state = JSONReaderStateValue +			return reader.Read() +		default: +			panic("Invalid JSONReaderStructure") +		} +	default: +		panic("Invalid JSONReaderState") +	} +} + +func (reader *JSONReader) readValue() (walk.Value, error) { +	r, err := reader.nextNonWsRune() +	if err != nil { +		panic("Missing value in JSON") +	} +	switch r { +		case 'n': +			reader.requireString("ull") +			reader.state = JSONReaderStateValueEnd +			return walk.NullScalar{}, nil +		case 'f': +			reader.requireString("alse") +			reader.state = JSONReaderStateValueEnd +			return walk.BoolScalar(false), nil +		case 't': +			reader.requireString("rue") +			reader.state = JSONReaderStateValueEnd +			return walk.BoolScalar(true), nil +		case '"': +			v := reader.readString() +			reader.state = JSONReaderStateValueEnd +			return walk.StringStructure(v), nil +		case '{': +			reader.state = JSONReaderStateValue +			reader.structure = append(reader.structure, JSONReaderStructureMap) +			reader.path = append(reader.path, walk.StringStructure("")) +			return walk.MapStructure(make(map[string]walk.Value)), nil +		case '[': +			reader.state = JSONReaderStateValue +			reader.structure = append(reader.structure, JSONReaderStructureArray) +			reader.path = append(reader.path, walk.NumberScalar(-1)) +			return walk.ArrayStructure{}, nil +	} +	if isNumberRune(r) { +		var builder strings.Builder +		builder.WriteRune(r) +		for { +			r, _, err = reader.reader.ReadRune() +			if err != nil { +				break +			} +			if !isNumberRune(r) { +				reader.reader.UnreadRune() +				break +			} +			builder.WriteRune(r) +		} +		number, parseError := strconv.ParseFloat(builder.String(), 64) +		if parseError != nil { +			panic("Invalid number") +		} +		reader.state = JSONReaderStateValueEnd +		return walk.NumberScalar(number), nil +	} +	panic("Invalid JSON value starting with: " + string(r)) +} + +func (reader *JSONReader) readString() string { +	var builder strings.Builder +	for { +		r, _, err := reader.reader.ReadRune() +		if err != nil { +			panic("Missing rest of string") +		} +		if r == '"' { +			break +		} +		if r == '\\' { +			r, _, err = reader.reader.ReadRune() +			if err != nil { +				panic("Missing rune after \\") +			} +			builder.WriteRune(r) +			continue +		} +		builder.WriteRune(r) +	} +	return builder.String() +} + +func (reader *JSONReader) nextNonWsRune() (rune, error) { +	for { +		r, _, err := reader.reader.ReadRune() +		if err != nil { +			return 0, err +		} +		if !isWhitespace(r) { +			return r, nil +		} +	} +} + +func (reader *JSONReader) requireString(criteria string) { +	for _, r := range criteria { +		reader.require(r) +	} +} + +func (reader *JSONReader) require(criterion rune) { +	r, _, err := reader.reader.ReadRune() +	if err != nil { +		panic("Error while reading required rune: " + err.Error()) +	} +	if r != criterion { +		panic("Required rune not read") +	} +} + +func (reader *JSONReader) clonePath() []walk.Value { +	return append([]walk.Value{}, reader.path...) +} + +func (reader *JSONReader) AssertDone() { +	// TODO +} diff --git a/json/write.go b/json/write.go new file mode 100644 index 0000000..d024a56 --- /dev/null +++ b/json/write.go @@ -0,0 +1,202 @@ +package json + +import ( +	"bufio" +	"fmt" +	"main/walk" +) + +func isNumber(value walk.Value) bool { +	_, isFloat := value.(walk.NumberScalar) +	return isFloat +} + +func isString(value walk.Value) bool { +	_, isString := value.(walk.StringStructure) +	return isString +} + +func segmentEqual(left walk.Value, right walk.Value) bool { +	switch left := left.(type) { +	case walk.NumberScalar: +		_, isNumber := right.(walk.NumberScalar) +		return isNumber +	case walk.StringStructure: +		right, isString := right.(walk.StringStructure) +		return isString && left == right +	default: +		panic("Invalid path segment type") +	} +} + +type JSONWriterState int +const ( +	JSONWriterStateBeforeValue JSONWriterState = iota +	JSONWriterStateAfterValue JSONWriterState = iota +	JSONWriterStateInArray +	JSONWriterStateInMap +) + +func NewJSONWriter(writer *bufio.Writer) *JSONWriter { +	return &JSONWriter { +		path: nil, +		writer: writer, +		state: JSONWriterStateBeforeValue, +	} +} + +type JSONWriter struct { +	path []walk.Value +	writer *bufio.Writer +	state JSONWriterState +} + +func (writer *JSONWriter) Write(item walk.WalkItem) error { +	path := item.Path +	for _, value := range item.Value { +		err := writer.write(path, value) +		if err != nil { +			return err +		} +	} +	return nil +} + +func (writer *JSONWriter) indent(level int) { +	for i := 0; i < level; i += 1 { +		writer.writer.WriteRune('\t') +	} +} + +func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error { +	diversionPoint := len(writer.path) +	for diversionPoint < len(writer.path) && diversionPoint < len(targetPath) && segmentEqual(writer.path[diversionPoint], targetPath[diversionPoint]) { +		diversionPoint += 1 +	} +	 +	switch writer.state { +	case JSONWriterStateBeforeValue: +		goto beforeValue +	case JSONWriterStateAfterValue: +		goto afterValue +	case JSONWriterStateInMap: +		goto inMap +	case JSONWriterStateInArray: +		goto inArray +	default: +		panic("Invalid JSONWriterState") +	} + +	beforeValue: { +		switch value := value.(type) { +		case walk.NullScalar: +			writer.writer.WriteString("null") +			writer.state = JSONWriterStateAfterValue +			return nil +		case walk.BoolScalar: +			if value { +				writer.writer.WriteString("true") +			} else { +				writer.writer.WriteString("false") +			} +			writer.state = JSONWriterStateAfterValue +			return nil +		case walk.NumberScalar: +			writer.writer.WriteString(fmt.Sprintf("%v", value)) +			writer.state = JSONWriterStateAfterValue +			return nil +		case walk.StringStructure: +			writer.writer.WriteString(fmt.Sprintf("%q", value)) +			writer.state = JSONWriterStateAfterValue +			return nil +		case walk.ArrayStructure: +			// TODO: write the contents of the structures +			writer.writer.WriteString("[\n") +			writer.state = JSONWriterStateInArray +			return nil +		case walk.MapStructure: +			writer.writer.WriteString("{\n") +			writer.state = JSONWriterStateInMap +			return nil +		default: +			panic("Invalid value type") +		} +	} + +	afterValue: { +		if len(writer.path) == 0 { +			writer.writer.WriteRune('\n') +			goto beforeValue +		} +		switch writer.path[len(writer.path) - 1].(type) { +		case walk.NumberScalar: +			// TODO: second part of this condition might be redundant +			if len(writer.path) - 1 <= diversionPoint && len(targetPath) >= len(writer.path) && isNumber(targetPath[len(writer.path) - 1]) { +				writer.writer.WriteString(",\n") +				writer.path = writer.path[:len(writer.path) - 1] +				goto inArray +			} else { +				writer.writer.WriteString("\n") +				writer.indent(len(writer.path) - 1) +				writer.writer.WriteString("]") +				writer.path = writer.path[:len(writer.path) - 1] +				goto afterValue +			} +		case walk.StringStructure: +			if len(writer.path) -1 <= diversionPoint && len(targetPath) >= len(writer.path) && isString(targetPath[len(writer.path) - 1]) { +				writer.writer.WriteString(",\n") +				writer.path = writer.path[:len(writer.path) - 1] +				goto inMap +			} else { +				writer.writer.WriteString("\n") +				writer.indent(len(writer.path) - 1) +				writer.writer.WriteString("}") +				writer.path = writer.path[:len(writer.path) - 1] +				goto afterValue +			} +		default: +			panic("Invalid path segment type") +		} +	} + +	inMap: { +		if len(writer.path) <= diversionPoint && len(targetPath) > len(writer.path) && isString(targetPath[len(writer.path)]) { +			writer.indent(len(writer.path) + 1) +			writer.writer.WriteString(fmt.Sprintf("%q: ", targetPath[len(writer.path)].(walk.StringStructure))) +			writer.path = append(writer.path, targetPath[len(writer.path)].(walk.StringStructure)) +			goto beforeValue +		} else { +			writer.writer.WriteString("\n}") +			goto afterValue +		} +	} + +	inArray: { +		if len(writer.path) <= diversionPoint && len(targetPath) > len(writer.path) && isNumber(targetPath[len(writer.path)]) { +			writer.indent(len(writer.path) + 1) +			writer.path = append(writer.path, walk.NumberScalar(0)) +			goto beforeValue +		} else { +			writer.writer.WriteString("\n]") +			goto afterValue +		} +	} +} + +func (writer *JSONWriter) AssertDone() { +	for i := len(writer.path) - 1; i >= 0; i -= 1 { +		switch writer.path[i].(type) { +		case walk.NumberScalar: +			writer.writer.WriteString("\n") +			writer.indent(i) +			writer.writer.WriteString("]") +		case walk.StringStructure: +			writer.writer.WriteString("\n") +			writer.indent(i) +			writer.writer.WriteString("}") +		default: +			panic("Invalid path segment type") +		} +	} +	writer.writer.Flush() +} | 
