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{}, ) }