diff options
| author | Charlie Stanton <charlie@shtanton.xyz> | 2024-03-25 14:35:09 +0000 | 
|---|---|---|
| committer | Charlie Stanton <charlie@shtanton.xyz> | 2024-03-25 14:35:09 +0000 | 
| commit | c6c1c67402f89c94fee040c2c740d96e61cab669 (patch) | |
| tree | d829c7990dd327b543fe943dc5b01752fa5ccdea | |
| parent | 81dcb87b2158f625ca10a20df5a93a42bbcaf26b (diff) | |
| download | stred-go-c6c1c67402f89c94fee040c2c740d96e61cab669.tar | |
Finish implementing new JSON writer
| -rw-r--r-- | json/write.go | 467 | ||||
| -rw-r--r-- | json/write_test.go | 141 | 
2 files changed, 355 insertions, 253 deletions
| diff --git a/json/write.go b/json/write.go index 97b3f4e..c2a220e 100644 --- a/json/write.go +++ b/json/write.go @@ -4,32 +4,8 @@ import (  	"bufio"  	"fmt"  	"main/walk" -	// "text/scanner"  ) -func isInt(segment walk.PathSegment) bool { -	_, isInt := segment.(int) -	return isInt -} - -func isString(segment walk.PathSegment) bool { -	_, isString := segment.(string) -	return isString -} - -func segmentEqual(left walk.PathSegment, right walk.PathSegment) bool { -	switch left := left.(type) { -	case int: -		_, isInt := right.(int) -		return isInt -	case string: -		right, isString := right.(string) -		return isString && left == right -	default: -		panic("Invalid path segment type") -	} -} -  type JSONWriterState int  const (  	JSONWriterStateBeforeValue JSONWriterState = iota @@ -232,275 +208,260 @@ func (writer *JSONWriter) navigateTo(keepLen int, path []walk.PathSegment, state  	}  } -func (writer *JSONWriter) Write(value walk.Value) error { -	return nil -} +func (writer *JSONWriter) inMapAt(keepLen int, path []walk.PathSegment) bool { +	if keepLen < len(path) { +		return false +	} -func (writer *JSONWriter) pathWrite(value walk.Value, path []walk.PathSegment) error { -	switch value := value.(type) { -	case walk.NullValue: -		return writer.write(path, value) -	case walk.BoolValue: -		return writer.write(path, value) -	case walk.NumberValue: -		return writer.write(path, value) -	case walk.StringValue: -		return writer.write(path, value) -	case walk.ArrayValue: -		if len(value) == 0 { -			return writer.write(path, value) -		} else { -			for _, element := range value { -				err := writer.pathWrite(element.Value, append(path, element.Index)) -				if err != nil { -					return err -				} -			} -			return nil -		} -	case walk.MapValue: -		if len(value) == 0 { -			return writer.write(path, value) -		} else { -			for _, element := range value { -				err := writer.pathWrite(element.Value, append(path, element.Key)) -				if err != nil { -					return err -				} -			} -			return nil -		} -	case walk.RuneValue: -		panic("Cannot write rune value") -	default: -		panic("Unrecognised value type") +	if keepLen == len(writer.path) { +		return writer.state == JSONWriterStateInMap  	} + +	_, isString := writer.path[keepLen].(string) +	return isString  } -func (writer *JSONWriter) indent(level int) { -	for i := 0; i < level; i += 1 { -		writer.writer.WriteRune('\t') +func (writer *JSONWriter) inArrayAt(keepLen int, path []walk.PathSegment) bool { +	if keepLen < len(path) { +		return false +	} + +	if keepLen == len(writer.path) { +		return writer.state == JSONWriterStateInArray  	} + +	_, isInt := writer.path[keepLen].(int) +	return isInt  } -func (writer *JSONWriter) write(targetPath []walk.PathSegment, value walk.Value) error { -	diversionPoint := 0 -	for diversionPoint < len(writer.path) && diversionPoint < len(targetPath) && segmentEqual(writer.path[diversionPoint], targetPath[diversionPoint]) { -		diversionPoint += 1 +func countPrefix(current []walk.PathSegment, target []walk.PathSegment) int { +	for i, c := range current { +		if i >= len(target) || c != target[i] { +			return i +		}  	} +	return len(current) +} -	switch writer.state { -	case JSONWriterStateBeforeValue: -		goto beforeValue -	case JSONWriterStateAfterValue: -		goto afterValue -	case JSONWriterStateInArray: -		goto inArray -	case JSONWriterStateInMap: -		goto inMap +/* +{ +	"a": { +		"b": { +			"c": null +		}, +		"d": [], +		"e": {}, +		"f": {"g": 5}  	} +} -	beforeValue: { -		if diversionPoint < len(writer.path) { -			panic("Writing a value before doing a necessary leave") -		} -		if diversionPoint < len(targetPath) { -			segment := targetPath[diversionPoint] -			switch segment.(type) { -			case int: -				writer.writer.WriteString("[\n") -				goto inArray -			case string: -				writer.writer.WriteString("{\n") -				goto inMap -			default: -				panic("Invalid path segment") -			} -		} +write(true, true, [], {"a": ...}) +	write(true, true, ["a"], {"b": ..., "d": [], "e": {}, "f": ...}) +		write(true, false, ["a", "b"], {"c": null}) +			write(true, false, ["a", "b", "c"], null) +				navigateTo(["a", "b", "c"], BeforeValue) +			> null +		> } +	> , "d": +		write(false, false, ["a", "d"], []) +		> [] +	> , "e": +		write(false, false, ["a", "e"], {}) +		> {} +	> , "f": +		write(false, true, ["a", "f"], {"g": 5}) +		> {"g": +			write(false, true, ["a", "f", "g"], 5) +			> 5 +			path = ["a", "f", "g"] +			state = AfterValue +*/ -		switch value := value.(type) { -		case walk.NullValue: -			writer.writer.WriteString("null") +/* +{ +	"a": {} +} + +write(true, true, [], {"a": {}}) +	write(true, true, ["a"], {}) +		if not in map at ["a"] +			navigateTo(["a"], InMap) +*/ + +/* +{ +	"a": {}, +	"b": null, +	"c": {} +} + +write(true, true, [], {...}) +	write(true, false, ["a"], {}) +		if in map at ["a"] +			navigateTo(["a"], AfterValue) +		else +			navigateTo(["a"], BeforeValue) +			> {} +> , "b": +	write(false, false, ["b"], null) +	> null +> , "c": +	write(false, true, ["c"], {}) +	> { +	path = ["c"] +	state = InMap +*/ + +func (writer *JSONWriter) write(first bool, last bool, path []walk.PathSegment, value walk.Value) { +	switch value := value.(type) { +	case walk.NullValue: +		if first { +			keepLen := countPrefix(writer.path, path) +			writer.navigateTo(keepLen, path[keepLen:], JSONWriterStateBeforeValue) +		} +		writer.writer.WriteString("null") +		if last { +			writer.path = path  			writer.state = JSONWriterStateAfterValue -			return nil -		case walk.BoolValue: -			if value { -				writer.writer.WriteString("true") -			} else { -				writer.writer.WriteString("false") -			} +		} +	case walk.BoolValue: +		if first { +			keepLen := countPrefix(writer.path, path) +			writer.navigateTo(keepLen, path[keepLen:], JSONWriterStateBeforeValue) +		} +		if value { +			writer.writer.WriteString("true") +		} else { +			writer.writer.WriteString("false") +		} +		if last { +			writer.path = path  			writer.state = JSONWriterStateAfterValue -			return nil -		case walk.NumberValue: -			writer.writer.WriteString(fmt.Sprintf("%v", value)) +		} +	case walk.NumberValue: +		if first { +			keepLen := countPrefix(writer.path, path) +			writer.navigateTo(keepLen, path[keepLen:], JSONWriterStateBeforeValue) +		} +		fmt.Fprintf(writer.writer, "%v", value) +		if last { +			writer.path = path  			writer.state = JSONWriterStateAfterValue -			return nil -		case walk.StringValue: -			writer.writer.WriteString(fmt.Sprintf("%q", value)) +		} +	case walk.StringValue: +		if first { +			keepLen := countPrefix(writer.path, path) +			writer.navigateTo(keepLen, path[keepLen:], JSONWriterStateBeforeValue) +		} +		fmt.Fprintf(writer.writer, "%q", value) +		if last { +			writer.path = path  			writer.state = JSONWriterStateAfterValue -			return nil -		case walk.ArrayValue: -			// TODO: write the contents of the structures -			writer.writer.WriteString("[\n") -			writer.state = JSONWriterStateInArray -			return nil -		case walk.MapValue: -			writer.writer.WriteString("{\n") -			writer.state = JSONWriterStateInMap -			return nil -		default: -			panic("Invalid value type")  		} -	} - -	afterValue: { -		if diversionPoint < len(writer.path) { -			if diversionPoint == len(writer.path) - 1 && diversionPoint < len(targetPath) { -				segment := writer.path[diversionPoint] -				switch segment.(type) { -				case int: -					_, isNumber := targetPath[diversionPoint].(walk.NumberValue) -					if isNumber { -						writer.writer.WriteString(",\n") -						writer.path = writer.path[:diversionPoint] -						goto inArray +	case walk.ArrayValue: +		if len(value) == 0 { +			if first { +				if last { +					keepLen := countPrefix(writer.path, path) +					if !writer.inArrayAt(keepLen, path[keepLen:]) { +						writer.navigateTo(keepLen, path[keepLen:], JSONWriterStateInArray)  					} -				case string: -					_, isString := targetPath[diversionPoint].(walk.StringValue) -					if isString { -						writer.writer.WriteString(",\n") -						writer.path = writer.path[:diversionPoint] -						goto inMap +				} else { +					keepLen := countPrefix(writer.path, path) +					if writer.inArrayAt(keepLen, path[keepLen:]) { +						writer.navigateTo(keepLen, path[keepLen:], JSONWriterStateAfterValue) +					} else { +						writer.navigateTo(keepLen, path[keepLen:], JSONWriterStateBeforeValue) +						writer.writer.WriteString("[]")  					} -				default: -					panic("Invalid segment type") +				} +			} else { +				if last { +					writer.writer.WriteRune('[') +					writer.path = path +					writer.state = JSONWriterStateInArray +				} else { +					writer.writer.WriteString("[]")  				}  			} - -			writer.writer.WriteString("\n") -			switch writer.path[len(writer.path) - 1].(type) { -			case int: -				writer.path = writer.path[:len(writer.path) - 1] -				goto inArray -			case string: -				writer.path = writer.path[:len(writer.path) - 1] -				goto inMap -			default: -				panic("Invalid segment type") +		} else { +			if !first { +				writer.writer.WriteRune('[')  			} -		} - -		// TODO: handle len(writer.path) == 0 -		if diversionPoint < len(targetPath) { -			if len(writer.path) == 0 { -				writer.writer.WriteString("\n") -				goto beforeValue +			for i, el := range value { +				if i != 0 { +					writer.writer.WriteRune(',') +				} +				writer.write(first && i == 0, last && i == len(value) - 1, append(path, el.Index), el.Value)  			} -			segment := writer.path[diversionPoint - 1] -			writer.writer.WriteString(",") -			diversionPoint-- -			writer.path = writer.path[:diversionPoint] -			switch segment.(type) { -			case int: -				goto inArray -			case string: -				goto inMap -			default: -				panic("Invalid segment type") +			if !last { +				writer.writer.WriteRune(']')  			}  		} - -		if len(writer.path) == 0 { -			writer.writer.WriteString("\n") -			goto beforeValue -		} -		segment := writer.path[diversionPoint - 1] -		writer.writer.WriteString(",\n") -		diversionPoint-- -		writer.path = writer.path[:diversionPoint] -		switch segment.(type) { -		case int: -			goto inArray -		case string: -			goto inMap -		default: -			panic("Invalid segment type") -		} -	} - -	inArray: { -		if diversionPoint < len(writer.path) { -			writer.indent(len(writer.path)) -			writer.writer.WriteString("]") -			goto afterValue -		} - -		if diversionPoint < len(targetPath) { -			switch s := targetPath[diversionPoint].(type) { -			case int: -				writer.path = append(writer.path, s) -				diversionPoint++ -				writer.indent(len(writer.path)) -				goto beforeValue -			case string: -				writer.indent(len(writer.path)) -				writer.writer.WriteString("]") -				goto afterValue -			default: -				panic("Invalid segment type") +	case walk.MapValue: +		if len(value) == 0 { +			if first { +				if last { +					keepLen := countPrefix(writer.path, path) +					if !writer.inMapAt(keepLen, path[keepLen:]) { +						writer.navigateTo(keepLen, path[keepLen:], JSONWriterStateInMap) +					} +				} else { +					keepLen := countPrefix(writer.path, path) +					if writer.inMapAt(keepLen, path[keepLen:]) { +						writer.navigateTo(keepLen, path[keepLen:], JSONWriterStateAfterValue) +					} else { +						writer.navigateTo(keepLen, path[keepLen:], JSONWriterStateBeforeValue) +						writer.writer.WriteString("{}") +					} +				} +			} else { +				if last { +					writer.writer.WriteRune('{') +					writer.path = path +					writer.state = JSONWriterStateInMap +				} else { +					writer.writer.WriteString("{}") +				}  			} -		} - -		writer.indent(len(writer.path)) -		writer.writer.WriteString("]") -		goto afterValue -	} - -	inMap: { -		if diversionPoint < len(writer.path) { -			writer.indent(len(writer.path)) -			writer.writer.WriteString("}") -			goto afterValue -		} - -		if diversionPoint < len(targetPath) { -			switch s := targetPath[diversionPoint].(type) { -			case int: -				writer.indent(len(writer.path)) -				writer.writer.WriteString("}") -				goto afterValue -			case string: -				writer.path = append(writer.path, s) -				diversionPoint++ -				writer.indent(len(writer.path)) -				writer.writer.WriteString(fmt.Sprintf("%q: ", s)) -				goto beforeValue +		} else { +			if !first { +				writer.writer.WriteRune('{') +			} +			for i, el := range value { +				if i != 0 { +					writer.writer.WriteRune(',') +				} +				if i != 0 || !first { +					fmt.Fprintf(writer.writer, "%q:", el.Key) +				} +				writer.write(first && i == 0, last && i == len(value) - 1, append(path, el.Key), el.Value) +			} +			if !last { +				writer.writer.WriteRune('}')  			}  		} - -		writer.indent(len(writer.path)) -		writer.writer.WriteString("}") -		goto afterValue  	}  } +func (writer *JSONWriter) Write(value walk.Value) error { +	writer.write(true, true, nil, value) +	return nil +} +  func (writer *JSONWriter) AssertDone() {  	switch writer.state {  	case JSONWriterStateInArray: -		writer.writer.WriteString("]") +		writer.writer.WriteRune(']')  	case JSONWriterStateInMap: -		writer.writer.WriteString("}") +		writer.writer.WriteRune('}')  	}  	for i := len(writer.path) - 1; i >= 0; i -= 1 {  		switch writer.path[i].(type) {  		case int: -			writer.writer.WriteString("\n") -			writer.indent(i) -			writer.writer.WriteString("]") +			writer.writer.WriteRune(']')  		case string: -			writer.writer.WriteString("\n") -			writer.indent(i) -			writer.writer.WriteString("}") +			writer.writer.WriteRune('}')  		default:  			panic("Invalid path segment type")  		} diff --git a/json/write_test.go b/json/write_test.go index 508ccfa..05b228e 100644 --- a/json/write_test.go +++ b/json/write_test.go @@ -103,3 +103,144 @@ func TestNavigateTo(t *testing.T) {  		}  	}  } + +func TestWrite(t *testing.T) { +	type testCase struct { +		values []walk.Value +		expected string +	} + +	tests := []testCase { +		{ +			values: []walk.Value { +				walk.MapValue {{ +					Key: "a", +					Value: walk.MapValue {{ +						Key: "b", +						Value: walk.StringValue("c"), +					}}, +				}}, +			}, +			expected: `{"a":{"b":"c"}}`, +		}, +		{ +			values: []walk.Value { +				walk.MapValue {{ +					Key: "a", +					Value: walk.MapValue {{ +						Key: "b", +						Value: walk.StringValue("c"), +					}}, +				}}, +				walk.MapValue {{ +					Key: "a", +					Value: walk.MapValue {{ +						Key: "d", +						Value: walk.NullValue{}, +					}}, +				}}, +				walk.MapValue {{ +					Key: "a", +					Value: walk.MapValue{}, +				}}, +				walk.MapValue {{ +					Key: "a", +					Value: walk.MapValue {{ +						Key: "e", +						Value: walk.NumberValue(-4.3), +					}}, +				}}, +			}, +			expected: `{"a":{"b":"c","d":null,"e":-4.3}}`, +		}, +		{ +			values: []walk.Value { +				walk.MapValue {{ +					Key: "a", +					Value: walk.MapValue{{ +						Key: "aa", +						Value: walk.StringValue("aav"), +					}}, +				}}, +				walk.MapValue {{ +					Key: "a", +					Value: walk.MapValue{}, +				}, { +					Key: "b", +					Value: walk.MapValue {{ +						Key: "bb", +						Value: walk.StringValue("bbv"), +					}}, +				}, { +					Key: "c", +					Value: walk.MapValue{}, +				}}, +				walk.MapValue {{ +					Key: "c", +					Value: walk.MapValue {{ +						Key: "cc", +						Value: walk.StringValue("ccv"), +					}}, +				}}, +			}, +			expected: `{"a":{"aa":"aav"},"b":{"bb":"bbv"},"c":{"cc":"ccv"}}`, +		}, +		{ +			values: []walk.Value { +				walk.ArrayValue {{ +					Index: 0, +					Value: walk.ArrayValue {{ +						Index: 5, +						Value: walk.NumberValue(100), +					}}, +				}}, +				walk.ArrayValue {{ +					Index: 0, +					Value: walk.ArrayValue{}, +				}, { +					Index: 0, +					Value: walk.ArrayValue{}, +				}}, +				walk.ArrayValue {{ +					Index: 0, +					Value: walk.NullValue{}, +				}, { +					Index: 0, +					Value: walk.ArrayValue{}, +				}}, +				walk.ArrayValue {{ +					Index: 0, +					Value: walk.ArrayValue{}, +				}}, +				walk.ArrayValue {{ +					Index: 0, +					Value: walk.ArrayValue {{ +						Index: 200, +						Value: walk.NumberValue(200), +					}}, +				}}, +			}, +			expected: `[[100],[],null,[200]]`, +		}, +	} + +	for i, test := range tests { +		var writer strings.Builder +		jsonWriter := &JSONWriter { +			path: nil, +			writer: bufio.NewWriter(&writer), +			state: JSONWriterStateBeforeValue, +		} + +		for _, value := range test.values { +			jsonWriter.Write(value) +		} +		jsonWriter.AssertDone() + +		jsonWriter.writer.Flush() +		res := writer.String() +		if res != test.expected { +			t.Errorf(`Test %d: Expected '%s' found '%s'`, i, test.expected, res) +		} +	} +} | 
