Mako/parser/tests/expressions_test.go
2025-06-10 11:12:46 -05:00

226 lines
5.4 KiB
Go

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)"},
}
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 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)"},
// 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)"},
// Complex combinations
{"-x + y * z == a.b", "(((-x) + (y * z)) == a.b)"},
{"(a + b) * c <= d[0]", "(((a + b) * c) <= d[0.00])"},
}
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 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 && obj.y > obj.z", "multiple comparisons"}, // Note: && not implemented yet
}
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)
// Skip && test since it's not implemented
if tt.input == "obj.x < obj.y && obj.y > obj.z" {
return
}
checkParserErrors(t, p)
if expr == nil {
t.Error("expected non-nil expression")
}
})
}
}