package parser_test import ( "testing" "git.sharkk.net/Sharkk/Mako/parser" ) func TestPrefixExpressions(t *testing.T) { tests := []struct { input string operator string value any }{ {"-5", "-", 5.0}, {"-x", "-", "x"}, {"-true", "-", true}, {"-(1 + 2)", "-", "(1.00 + 2.00)"}, {"not true", "not", true}, {"not false", "not", false}, {"not x", "not", "x"}, {"not (a == b)", "not", "(a == b)"}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { l := parser.NewLexer(tt.input) p := parser.NewParser(l) expr := p.ParseExpression(parser.LOWEST) checkParserErrors(t, p) prefix, ok := expr.(*parser.PrefixExpression) if !ok { t.Fatalf("expected PrefixExpression, got %T", expr) } if prefix.Operator != tt.operator { t.Errorf("expected operator %s, got %s", tt.operator, prefix.Operator) } switch expected := tt.value.(type) { case float64: testNumberLiteral(t, prefix.Right, expected) case string: if expected == "x" { testIdentifier(t, prefix.Right, expected) } else { // It's an expression string if prefix.Right.String() != expected { t.Errorf("expected %s, got %s", expected, prefix.Right.String()) } } case bool: testBooleanLiteral(t, prefix.Right, expected) } }) } } func TestLogicalExpressions(t *testing.T) { tests := []struct { input string leftValue any operator string rightValue any }{ {"true and false", true, "and", false}, {"false or true", false, "or", true}, {"x and y", "x", "and", "y"}, {"a or b", "a", "or", "b"}, {"success and valid", "success", "and", "valid"}, {"error or fallback", "error", "or", "fallback"}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { l := parser.NewLexer(tt.input) p := parser.NewParser(l) expr := p.ParseExpression(parser.LOWEST) checkParserErrors(t, p) infix, ok := expr.(*parser.InfixExpression) if !ok { t.Fatalf("expected InfixExpression, got %T", expr) } if infix.Operator != tt.operator { t.Errorf("expected operator %s, got %s", tt.operator, infix.Operator) } // Test left operand switch leftVal := tt.leftValue.(type) { case string: testIdentifier(t, infix.Left, leftVal) case bool: testBooleanLiteral(t, infix.Left, leftVal) } // Test right operand switch rightVal := tt.rightValue.(type) { case string: testIdentifier(t, infix.Right, rightVal) case bool: testBooleanLiteral(t, infix.Right, rightVal) } }) } } func TestComparisonExpressions(t *testing.T) { tests := []struct { input string leftValue any operator string rightValue any }{ {"1 == 1", 1.0, "==", 1.0}, {"1 != 2", 1.0, "!=", 2.0}, {"x < y", "x", "<", "y"}, {"a > b", "a", ">", "b"}, {"5 <= 10", 5.0, "<=", 10.0}, {"10 >= 5", 10.0, ">=", 5.0}, {"true == false", true, "==", false}, {"nil != x", nil, "!=", "x"}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { l := parser.NewLexer(tt.input) p := parser.NewParser(l) expr := p.ParseExpression(parser.LOWEST) checkParserErrors(t, p) infix, ok := expr.(*parser.InfixExpression) if !ok { t.Fatalf("expected InfixExpression, got %T", expr) } if infix.Operator != tt.operator { t.Errorf("expected operator %s, got %s", tt.operator, infix.Operator) } // Test left operand switch leftVal := tt.leftValue.(type) { case float64: testNumberLiteral(t, infix.Left, leftVal) case string: testIdentifier(t, infix.Left, leftVal) case bool: testBooleanLiteral(t, infix.Left, leftVal) case nil: testNilLiteral(t, infix.Left) } // Test right operand switch rightVal := tt.rightValue.(type) { case float64: testNumberLiteral(t, infix.Right, rightVal) case string: testIdentifier(t, infix.Right, rightVal) case bool: testBooleanLiteral(t, infix.Right, rightVal) case nil: testNilLiteral(t, infix.Right) } }) } } func TestInfixExpressions(t *testing.T) { tests := []struct { input string leftValue any operator string rightValue any }{ {"5 + 5", 5.0, "+", 5.0}, {"5 - 5", 5.0, "-", 5.0}, {"5 * 5", 5.0, "*", 5.0}, {"5 / 5", 5.0, "/", 5.0}, {"true + false", true, "+", false}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { l := parser.NewLexer(tt.input) p := parser.NewParser(l) expr := p.ParseExpression(parser.LOWEST) checkParserErrors(t, p) testInfixExpression(t, expr, tt.leftValue, tt.operator, tt.rightValue) }) } } func TestOperatorPrecedence(t *testing.T) { tests := []struct { input string expected string }{ // Arithmetic precedence {"1 + 2 * 3", "(1.00 + (2.00 * 3.00))"}, {"2 * 3 + 4", "((2.00 * 3.00) + 4.00)"}, {"(1 + 2) * 3", "((1.00 + 2.00) * 3.00)"}, {"1 + 2 - 3", "((1.00 + 2.00) - 3.00)"}, {"2 * 3 / 4", "((2.00 * 3.00) / 4.00)"}, // Prefix with arithmetic {"-1 + 2", "((-1.00) + 2.00)"}, {"-(1 + 2)", "(-(1.00 + 2.00))"}, {"-x * 2", "((-x) * 2.00)"}, {"not true", "(not true)"}, {"not x", "(not x)"}, // Logical operator precedence {"true or false and true", "(true or (false and true))"}, {"false and true or false", "((false and true) or false)"}, {"a or b and c", "(a or (b and c))"}, {"x and y or z", "((x and y) or z)"}, // Logical with comparison {"a == b and c != d", "((a == b) and (c != d))"}, {"x < y or z > w", "((x < y) or (z > w))"}, {"not a == b", "((not a) == b)"}, {"not x and y", "((not x) and y)"}, // Logical with arithmetic {"a + b and c * d", "((a + b) and (c * d))"}, {"x or y + z", "(x or (y + z))"}, {"not a + b", "((not a) + b)"}, // Comparison precedence {"1 + 2 == 3", "((1.00 + 2.00) == 3.00)"}, {"1 * 2 < 3 + 4", "((1.00 * 2.00) < (3.00 + 4.00))"}, {"a + b != c * d", "((a + b) != (c * d))"}, {"x <= y + z", "(x <= (y + z))"}, {"a * b >= c / d", "((a * b) >= (c / d))"}, // Comparison chaining {"a == b != c", "((a == b) != c)"}, {"x < y <= z", "((x < y) <= z)"}, // Member access with operators {"table.key + 1", "(table.key + 1.00)"}, {"arr[0] * 2", "(arr[0.00] * 2.00)"}, {"obj.x == obj.y", "(obj.x == obj.y)"}, {"-table.value", "(-table.value)"}, {"not obj.active", "(not obj.active)"}, {"obj.x and obj.y", "(obj.x and obj.y)"}, // Complex combinations {"-x + y * z == a.b", "(((-x) + (y * z)) == a.b)"}, {"(a + b) * c <= d[0]", "(((a + b) * c) <= d[0.00])"}, {"not success and attempts > 0 or fallback", "(((not success) and (attempts > 0.00)) or fallback)"}, {"a == b and c > d or e != f", "(((a == b) and (c > d)) or (e != f))"}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { l := parser.NewLexer(tt.input) p := parser.NewParser(l) expr := p.ParseExpression(parser.LOWEST) checkParserErrors(t, p) if expr.String() != tt.expected { t.Errorf("expected %s, got %s", tt.expected, expr.String()) } }) } } func TestComplexLogicalExpressions(t *testing.T) { tests := []struct { input string desc string }{ {"not (a and b)", "negated grouped logical"}, {"a and b or c and d", "mixed logical operators"}, {"success and valid or error and retry", "complex boolean logic"}, {"not failed and attempts < max_attempts", "logical with comparison"}, {"(ready or waiting) and not cancelled", "grouped logical with negation"}, {"x > 0 and y > 0 and z > 0", "multiple and conditions"}, {"error or warning or info", "multiple or conditions"}, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { l := parser.NewLexer(tt.input) p := parser.NewParser(l) expr := p.ParseExpression(parser.LOWEST) checkParserErrors(t, p) if expr == nil { t.Error("expected non-nil expression") } // Verify the expression can be converted to string (basic sanity check) result := expr.String() if result == "" { t.Error("expected non-empty string representation") } }) } } func TestComplexExpressionsWithComparisons(t *testing.T) { tests := []struct { input string desc string }{ {"x + 1 == y * 2", "arithmetic comparison"}, {"table.count > arr[0] + 5", "member access comparison"}, {"-value <= max", "prefix comparison"}, {"(a + b) != (c - d)", "grouped comparison"}, {"obj.x < obj.y and obj.y > obj.z", "multiple comparisons with logical"}, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { l := parser.NewLexer(tt.input) p := parser.NewParser(l) expr := p.ParseExpression(parser.LOWEST) checkParserErrors(t, p) if expr == nil { t.Error("expected non-nil expression") } }) } } func TestAssignmentExpressions(t *testing.T) { tests := []struct { input string targetName string value any isDeclaration bool desc string }{ {"(x = 5)", "x", 5.0, true, "simple assignment"}, {"(y = true)", "y", true, true, "boolean assignment"}, {"(name = \"test\")", "name", "test", true, "string assignment"}, {"(count = nil)", "count", nil, true, "nil assignment"}, {"(result = x + 1)", "result", "(x + 1.00)", true, "expression assignment"}, {"(flag = not active)", "flag", "(not active)", true, "prefix expression assignment"}, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { l := parser.NewLexer(tt.input) p := parser.NewParser(l) expr := p.ParseExpression(parser.LOWEST) checkParserErrors(t, p) assignExpr, ok := expr.(*parser.Assignment) if !ok { t.Fatalf("expected AssignExpression, got %T", expr) } // Test target name ident, ok := assignExpr.Target.(*parser.Identifier) if !ok { t.Fatalf("expected Identifier for assignment target, got %T", assignExpr.Target) } if ident.Value != tt.targetName { t.Errorf("expected target name %s, got %s", tt.targetName, ident.Value) } // Test assignment value switch expected := tt.value.(type) { case float64: testNumberLiteral(t, assignExpr.Value, expected) case string: if expected == "test" { testStringLiteral(t, assignExpr.Value, expected) } else { // It's an expression string if assignExpr.Value.String() != expected { t.Errorf("expected value %s, got %s", expected, assignExpr.Value.String()) } } case bool: testBooleanLiteral(t, assignExpr.Value, expected) case nil: testNilLiteral(t, assignExpr.Value) } // Test declaration flag if assignExpr.IsDeclaration != tt.isDeclaration { t.Errorf("expected IsDeclaration %v, got %v", tt.isDeclaration, assignExpr.IsDeclaration) } }) } } func TestAssignmentExpressionWithComplexExpressions(t *testing.T) { tests := []struct { input string desc string }{ {"(result = func(a, b))", "function call assignment"}, {"(value = table[key])", "index expression assignment"}, {"(prop = obj.field)", "dot expression assignment"}, {"(sum = a + b * c)", "complex arithmetic assignment"}, {"(valid = x > 0 and y < 10)", "logical expression assignment"}, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { l := parser.NewLexer(tt.input) p := parser.NewParser(l) expr := p.ParseExpression(parser.LOWEST) checkParserErrors(t, p) assignExpr, ok := expr.(*parser.Assignment) if !ok { t.Fatalf("expected AssignExpression, got %T", expr) } if assignExpr.Target == nil { t.Error("expected non-nil assignment target") } if assignExpr.Value == nil { t.Error("expected non-nil assignment value") } // Verify string representation result := assignExpr.String() if result == "" { t.Error("expected non-empty string representation") } }) } } func TestAssignmentExpressionErrors(t *testing.T) { tests := []struct { input string expectedErr string desc string }{ {"(x =)", "expected expression after assignment operator", "missing value"}, {"(= 5)", "unexpected assignment operator", "missing target"}, {"(x = )", "expected expression after assignment operator", "empty value"}, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { l := parser.NewLexer(tt.input) p := parser.NewParser(l) expr := p.ParseExpression(parser.LOWEST) if p.HasErrors() { errors := p.ErrorStrings() found := false for _, err := range errors { if containsSubstring(err, tt.expectedErr) { found = true break } } if !found { t.Errorf("expected error containing '%s', got %v", tt.expectedErr, errors) } } else { t.Errorf("expected parse error for input '%s'", tt.input) } if expr != nil { t.Errorf("expected nil expression for invalid input, got %T", expr) } }) } }