From 8cf10efe3b5a1bcc70bc6e5590ee63fd5eb00c5b Mon Sep 17 00:00:00 2001 From: Charlie Stanton Date: Wed, 19 Jul 2023 11:57:59 +0100 Subject: Huge refactor to a more value based system, doing away with terminals. Also introduces unit testing --- subex/main_test.go | 442 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 442 insertions(+) create mode 100644 subex/main_test.go (limited to 'subex/main_test.go') diff --git a/subex/main_test.go b/subex/main_test.go new file mode 100644 index 0000000..f0350d2 --- /dev/null +++ b/subex/main_test.go @@ -0,0 +1,442 @@ +package subex + +import ( + "testing" + "main/walk" + "fmt" + "strings" +) + +func buildTransducer(subex string) Transducer { + lexer := NewStringRuneReader(subex) + ast := Parse(lexer) + transducer := CompileTransducer(ast) + return transducer +} + +func fatalMismatch(t *testing.T, path walk.ValueList, message string) { + var sep string + var builder strings.Builder + for _, segment := range path { + builder.WriteString(sep) + builder.WriteString(segment.Debug()) + sep = "." + } + builder.WriteString(": ") + builder.WriteString(message) + t.Fatal(builder.String()) +} + +func expectEqual(t *testing.T, path walk.ValueList, output walk.Value, expected walk.Value) { + switch expected := expected.(type) { + case walk.NullScalar: + _, isNull := output.(walk.NullScalar) + if !isNull { + fatalMismatch(t, path, fmt.Sprintf("expected null, found %s", output.Debug())) + } + case walk.BoolScalar: + b, isBool := output.(walk.BoolScalar) + if !isBool { + fatalMismatch(t, path, fmt.Sprintf("expected boolean, found %s", output.Debug())) + } + if expected != b { + fatalMismatch(t, path, fmt.Sprintf("expected %s, found %s", expected.Debug(), b.Debug())) + } + case walk.NumberScalar: + n, isNumber := output.(walk.NumberScalar) + if !isNumber { + fatalMismatch(t, path, fmt.Sprintf("expected number, found %s", output.Debug())) + } + if expected != n { + fatalMismatch(t, path, fmt.Sprintf("expected %s, found %s", expected.Debug(), n.Debug())) + } + case walk.StringStructure: + s, isString := output.(walk.StringStructure) + if !isString { + fatalMismatch(t, path, fmt.Sprintf("expected string, found %s", output.Debug())) + } + if s != expected { + fatalMismatch(t, path, fmt.Sprintf("expected %s, found %s", expected.Debug(), s.Debug())) + } + case walk.ArrayStructure: + array, isArray := output.(walk.ArrayStructure) + if !isArray { + fatalMismatch(t, path, fmt.Sprintf("expected array, found %s", output.Debug())) + } + if len(array) != len(expected) { + fatalMismatch(t, path, fmt.Sprintf("Expected array length %d, found %d", len(expected), len(array))) + } + for i, value := range expected { + expectEqual(t, append(path, walk.NumberScalar(i)), array[i], value) + } + case walk.MapStructure: + m, isMap := output.(walk.MapStructure) + if !isMap { + fatalMismatch(t, path, fmt.Sprintf("expected map, found %s", output.Debug())) + } + for key, expected := range expected { + value, hasValue := m[key] + if !hasValue { + fatalMismatch(t, path, fmt.Sprintf("expected map to have key %s, but it doesn't", key)) + } + expectEqual(t, append(path, walk.StringStructure(key)), value, expected) + } + for key := range m { + _, hasValue := expected[key] + if !hasValue { + fatalMismatch(t, path, fmt.Sprintf("Didn't expect map to have key %s, but it does", key)) + } + } + default: + panic("Expected contains an invalid value") + } +} + +func expectOutput(t *testing.T, transducer Transducer, input walk.ValueList, expected walk.ValueList) { + output, err := RunTransducer(transducer, input) + + if err { + t.Fatalf("Error") + } + + if len(output) != len(expected) { + t.Fatalf("Output has incorrect length. Expected %d, got %d", len(expected), len(output)) + } + + for i, value := range output { + expectEqual(t, walk.ValueList{walk.NumberScalar(i)}, value, expected[i]) + } +} + +func expectReject(t *testing.T, transducer Transducer, input walk.ValueList) { + _, err := RunTransducer(transducer, input) + + if !err { + t.Fatalf("Expected transducer to error, but it accepted input: %v", input) + } +} + +func TestSimpleProcessInput(t *testing.T) { + states := []SubexBranch{{ + state: SubexCopyState { + next: SubexNoneState{}, + filter: anyValueFilter{}, + }, + aux: auxiliaryState { + outputStack: OutputStack { + head: walk.ValueList{}, + tail: nil, + }, + store: nil, + nesting: 0, + }, + }} + + input := walk.ValueList{ + walk.NumberScalar(2), + } + + states = processInput(states, walk.NewValueIter(input), 0) + + if len(states) != 1 { + t.Fatalf("States has wrong length") + } + + accepting := states[0].accepting() + + if len(accepting) != 1 { + t.Fatalf("Wrong number of accepting branches") + } + + values, isValues := accepting[0].head.(walk.ValueList) + + if !isValues { + t.Fatalf("Output is not a value list") + } + + if len(values) != 1 { + t.Fatalf("Output has wrong length") + } + + if values[0] != walk.NumberScalar(2) { + t.Fatalf("Outputted the wrong value") + } +} + +func TestTopAppendFromEmpty(t *testing.T) { + output := OutputStack { + head: walk.ValueList{}, + tail: nil, + } + + output = topAppend(output, []walk.Value{walk.NumberScalar(1), walk.NumberScalar(2)}) + + values, isValues := output.head.(walk.ValueList) + + if !isValues { + t.Fatalf("head is not values") + } + + if len(values) != 2 { + t.Fatalf("values has the wrong length") + } + + if values[0] != walk.NumberScalar(1) || values[1] != walk.NumberScalar(2) { + t.Fatalf("output has the wrong values") + } +} + +func TestArrayPriority1(t *testing.T) { + expectOutput( + t, + buildTransducer(":[.$_]|."), + walk.ValueList{ + walk.ArrayStructure{ + walk.NumberScalar(5), + }, + }, + walk.ValueList{ + walk.ArrayStructure{}, + }, + ) +} + +func TestArrayPriority2(t *testing.T) { + expectOutput( + t, + buildTransducer(".|:[.$_]"), + walk.ValueList{ + walk.ArrayStructure{ + walk.NumberScalar(5), + }, + }, + walk.ValueList{ + walk.ArrayStructure{ + walk.NumberScalar(5), + }, + }, + ) +} + +func TestDropSecondArrayElement(t *testing.T) { + expectOutput( + t, + buildTransducer(":[.(.$_)(.{-0})]"), + walk.ValueList{ + walk.ArrayStructure{ + walk.NumberScalar(1), + walk.NumberScalar(2), + walk.NumberScalar(3), + walk.NumberScalar(4), + }, + }, + walk.ValueList{ + walk.ArrayStructure{ + walk.NumberScalar(1), + walk.NumberScalar(3), + walk.NumberScalar(4), + }, + }, + ) +} + +func TestDropSecondElement(t *testing.T) { + expectOutput( + t, + buildTransducer(".(.$_)(.{-0})"), + walk.ValueList{ + walk.NumberScalar(1), + walk.NumberScalar(2), + walk.NumberScalar(3), + walk.NumberScalar(4), + }, + walk.ValueList{ + walk.NumberScalar(1), + walk.NumberScalar(3), + walk.NumberScalar(4), + }, + ) +} + +func TestCopyManyValues(t *testing.T) { + expectOutput( + t, + buildTransducer(".{-0}"), + walk.ValueList{ + walk.NumberScalar(1), + walk.NumberScalar(2), + walk.NumberScalar(3), + walk.NumberScalar(4), + }, + walk.ValueList{ + walk.NumberScalar(1), + walk.NumberScalar(2), + walk.NumberScalar(3), + walk.NumberScalar(4), + }, + ) +} + +func TestCopyTwoValues(t *testing.T) { + expectOutput( + t, + buildTransducer(".."), + walk.ValueList{ + walk.NumberScalar(1), + walk.NumberScalar(2), + }, + walk.ValueList{ + walk.NumberScalar(1), + walk.NumberScalar(2), + }, + ) +} + +func TestCopyValue(t *testing.T) { + expectOutput( + t, + buildTransducer("."), + walk.ValueList{ + walk.NumberScalar(1), + }, + walk.ValueList{ + walk.NumberScalar(1), + }, + ) +} + +func TestSimpleArrayEntry(t *testing.T) { + expectOutput( + t, + buildTransducer(":[..]"), + walk.ValueList{ + walk.ArrayStructure{ + walk.NumberScalar(1), + walk.NumberScalar(2), + }, + }, + walk.ValueList{ + walk.ArrayStructure{ + walk.NumberScalar(1), + walk.NumberScalar(2), + }, + }, + ) +} + +func TestArrayEntrySum(t *testing.T) { + expectOutput( + t, + buildTransducer(":[%{-0}+]"), + walk.ValueList{ + walk.ArrayStructure{ + walk.NumberScalar(1), + walk.NumberScalar(7), + walk.NumberScalar(8), + walk.NumberScalar(3), + }, + }, + walk.ValueList{ + walk.ArrayStructure{ + walk.NumberScalar(19), + }, + }, + ) +} + +func TestStringEmptyMatch(t *testing.T) { + expectOutput( + t, + buildTransducer("~\"\""), + walk.ValueList{ + walk.StringStructure(""), + }, + walk.ValueList{ + walk.StringStructure(""), + }, + ) +} + +func TestStringSimpleMatch(t *testing.T) { + expectOutput( + t, + buildTransducer("~\"hello\""), + walk.ValueList{ + walk.StringStructure("hello"), + }, + walk.ValueList{ + walk.StringStructure("hello"), + }, + ) +} + +func TestDiscardString(t *testing.T) { + expectOutput( + t, + buildTransducer("~\"test\"$_."), + walk.ValueList{ + walk.StringStructure("test"), + walk.NumberScalar(2), + }, + walk.ValueList{ + walk.NumberScalar(2), + }, + ) +} + +func TestStringThenValue(t *testing.T) { + expectOutput( + t, + buildTransducer("~\"test\"."), + walk.ValueList{ + walk.StringStructure("test"), + walk.NumberScalar(2), + }, + walk.ValueList{ + walk.StringStructure("test"), + walk.NumberScalar(2), + }, + ) +} + +func TestCutStringFromStart(t *testing.T) { + //transducer := buildTransducer("~\"test\"$_(.{-0})") + lexer := NewStringRuneReader("~\"test\"$_(.{-0})") + ast := Parse(lexer) + t.Log(ast) + transducer := CompileTransducer(ast) + + expectOutput( + t, + transducer, + walk.ValueList{ + walk.StringStructure("test"), + walk.NumberScalar(2), + walk.StringStructure("test"), + }, + walk.ValueList{ + walk.NumberScalar(2), + walk.StringStructure("test"), + }, + ) + expectOutput( + t, + transducer, + walk.ValueList{ + walk.StringStructure("test"), + }, + walk.ValueList{}, + ) + expectReject( + t, + transducer, + walk.ValueList{ + walk.StringStructure("yeet"), + }, + ) + expectReject( + t, + transducer, + walk.ValueList{}, + ) +} -- cgit v1.2.3