Mako/parser/tests/parser_test.go
2025-06-11 23:08:34 -05:00

371 lines
9.3 KiB
Go

package parser_test
import (
"testing"
"git.sharkk.net/Sharkk/Mako/parser"
)
func TestProgram(t *testing.T) {
input := `x = 42
y = "hello"
z = true + false`
l := parser.NewLexer(input)
p := parser.NewParser(l)
program := p.ParseProgram()
checkParserErrors(t, p)
if len(program.Statements) != 3 {
t.Fatalf("expected 3 statements, got %d", len(program.Statements))
}
expectedIdentifiers := []string{"x", "y", "z"}
for i, expectedIdent := range expectedIdentifiers {
stmt, ok := program.Statements[i].(*parser.Assignment)
if !ok {
t.Fatalf("statement %d: expected AssignStatement, got %T", i, program.Statements[i])
}
ident, ok := stmt.Target.(*parser.Identifier)
if !ok {
t.Fatalf("statement %d: expected Identifier for Name, got %T", i, stmt.Target)
}
if ident.Value != expectedIdent {
t.Errorf("statement %d: expected identifier %s, got %s", i, expectedIdent, ident.Value)
}
}
}
func TestMixedStatements(t *testing.T) {
input := `x = 42
if x then
y = "hello"
if true then
z = {1, 2, 3}
end
end
arr = {a = 1, b = 2}`
l := parser.NewLexer(input)
p := parser.NewParser(l)
program := p.ParseProgram()
checkParserErrors(t, p)
if len(program.Statements) != 3 {
t.Fatalf("expected 3 statements, got %d", len(program.Statements))
}
// First statement: assignment
stmt1, ok := program.Statements[0].(*parser.Assignment)
if !ok {
t.Fatalf("statement 0: expected AssignStatement, got %T", program.Statements[0])
}
ident1, ok := stmt1.Target.(*parser.Identifier)
if !ok {
t.Fatalf("expected Identifier for Name, got %T", stmt1.Target)
}
if ident1.Value != "x" {
t.Errorf("expected identifier 'x', got %s", ident1.Value)
}
// Second statement: if statement
stmt2, ok := program.Statements[1].(*parser.IfStatement)
if !ok {
t.Fatalf("statement 1: expected IfStatement, got %T", program.Statements[1])
}
if len(stmt2.Body) != 2 {
t.Errorf("expected 2 body statements, got %d", len(stmt2.Body))
}
// Third statement: table assignment
stmt3, ok := program.Statements[2].(*parser.Assignment)
if !ok {
t.Fatalf("statement 2: expected AssignStatement, got %T", program.Statements[2])
}
table, ok := stmt3.Value.(*parser.TableLiteral)
if !ok {
t.Fatalf("expected TableLiteral value, got %T", stmt3.Value)
}
if table.IsArray() {
t.Error("expected hash table, got array")
}
}
func TestMemberAccessProgram(t *testing.T) {
input := `table = {key = "value", nested = {inner = 42}}
table.key = "new value"
table["key"] = "bracket syntax"
table.nested.inner = 100
echo table[table.key]`
l := parser.NewLexer(input)
p := parser.NewParser(l)
program := p.ParseProgram()
checkParserErrors(t, p)
if len(program.Statements) != 5 {
t.Fatalf("expected 5 statements, got %d", len(program.Statements))
}
// Second statement: dot assignment
stmt2, ok := program.Statements[1].(*parser.Assignment)
if !ok {
t.Fatalf("statement 1: expected AssignStatement, got %T", program.Statements[1])
}
_, ok = stmt2.Target.(*parser.DotExpression)
if !ok {
t.Fatalf("expected DotExpression for assignment target, got %T", stmt2.Target)
}
// Third statement: bracket assignment
stmt3, ok := program.Statements[2].(*parser.Assignment)
if !ok {
t.Fatalf("statement 2: expected AssignStatement, got %T", program.Statements[2])
}
_, ok = stmt3.Target.(*parser.IndexExpression)
if !ok {
t.Fatalf("expected IndexExpression for assignment target, got %T", stmt3.Target)
}
// Fourth statement: chained dot assignment
stmt4, ok := program.Statements[3].(*parser.Assignment)
if !ok {
t.Fatalf("statement 3: expected AssignStatement, got %T", program.Statements[3])
}
_, ok = stmt4.Target.(*parser.DotExpression)
if !ok {
t.Fatalf("expected DotExpression for assignment target, got %T", stmt4.Target)
}
// Fifth statement: echo with nested access
stmt5, ok := program.Statements[4].(*parser.EchoStatement)
if !ok {
t.Fatalf("statement 4: expected EchoStatement, got %T", program.Statements[4])
}
_, ok = stmt5.Value.(*parser.IndexExpression)
if !ok {
t.Fatalf("expected IndexExpression in echo, got %T", stmt5.Value)
}
}
func TestNestedConditionals(t *testing.T) {
input := `if a then
if b then
x = 1
elseif c then
x = 2
else
x = 3
end
y = 4
elseif d then
z = 5
else
w = 6
end`
l := parser.NewLexer(input)
p := parser.NewParser(l)
program := p.ParseProgram()
checkParserErrors(t, p)
if len(program.Statements) != 1 {
t.Fatalf("expected 1 statement, got %d", len(program.Statements))
}
outerIf, ok := program.Statements[0].(*parser.IfStatement)
if !ok {
t.Fatalf("expected IfStatement, got %T", program.Statements[0])
}
// Outer if should have 2 body statements: nested if and assignment
if len(outerIf.Body) != 2 {
t.Fatalf("expected 2 body statements, got %d", len(outerIf.Body))
}
// First body statement should be nested if
nestedIf, ok := outerIf.Body[0].(*parser.IfStatement)
if !ok {
t.Fatalf("expected nested IfStatement, got %T", outerIf.Body[0])
}
// Nested if should have 1 elseif and 1 else
if len(nestedIf.ElseIfs) != 1 {
t.Errorf("expected 1 elseif in nested if, got %d", len(nestedIf.ElseIfs))
}
if len(nestedIf.Else) != 1 {
t.Errorf("expected 1 else statement in nested if, got %d", len(nestedIf.Else))
}
// Outer if should have 1 elseif and 1 else
if len(outerIf.ElseIfs) != 1 {
t.Errorf("expected 1 elseif in outer if, got %d", len(outerIf.ElseIfs))
}
if len(outerIf.Else) != 1 {
t.Errorf("expected 1 else statement in outer if, got %d", len(outerIf.Else))
}
}
func TestComplexExpressions(t *testing.T) {
input := `result = (0xFF + 0b1010) * 1e2
if result then
table = {
hex = 0xDEAD,
bin = 0b1111,
sci = 1.5e-3,
str = [[multiline
string]]
}
end`
l := parser.NewLexer(input)
p := parser.NewParser(l)
program := p.ParseProgram()
checkParserErrors(t, p)
if len(program.Statements) != 2 {
t.Fatalf("expected 2 statements, got %d", len(program.Statements))
}
// First statement: complex expression assignment
stmt1, ok := program.Statements[0].(*parser.Assignment)
if !ok {
t.Fatalf("expected AssignStatement, got %T", program.Statements[0])
}
// Should be a complex infix expression
_, ok = stmt1.Value.(*parser.InfixExpression)
if !ok {
t.Fatalf("expected InfixExpression, got %T", stmt1.Value)
}
// Second statement: if with table assignment
stmt2, ok := program.Statements[1].(*parser.IfStatement)
if !ok {
t.Fatalf("expected IfStatement, got %T", program.Statements[1])
}
if len(stmt2.Body) != 1 {
t.Fatalf("expected 1 body statement, got %d", len(stmt2.Body))
}
bodyStmt, ok := stmt2.Body[0].(*parser.Assignment)
if !ok {
t.Fatalf("expected AssignStatement in body, got %T", stmt2.Body[0])
}
table, ok := bodyStmt.Value.(*parser.TableLiteral)
if !ok {
t.Fatalf("expected TableLiteral, got %T", bodyStmt.Value)
}
if len(table.Pairs) != 4 {
t.Errorf("expected 4 table pairs, got %d", len(table.Pairs))
}
}
func TestMixedStatementsWithEcho(t *testing.T) {
input := `x = 42
echo x
if x then
echo "found x"
end
echo {result = x}`
l := parser.NewLexer(input)
p := parser.NewParser(l)
program := p.ParseProgram()
checkParserErrors(t, p)
if len(program.Statements) != 4 {
t.Fatalf("expected 4 statements, got %d", len(program.Statements))
}
// First: assignment
_, ok := program.Statements[0].(*parser.Assignment)
if !ok {
t.Fatalf("statement 0: expected AssignStatement, got %T", program.Statements[0])
}
// Second: echo
echo1, ok := program.Statements[1].(*parser.EchoStatement)
if !ok {
t.Fatalf("statement 1: expected EchoStatement, got %T", program.Statements[1])
}
testIdentifier(t, echo1.Value, "x")
// Third: if statement with echo in body
ifStmt, ok := program.Statements[2].(*parser.IfStatement)
if !ok {
t.Fatalf("statement 2: expected IfStatement, got %T", program.Statements[2])
}
if len(ifStmt.Body) != 1 {
t.Fatalf("expected 1 body statement, got %d", len(ifStmt.Body))
}
bodyEcho, ok := ifStmt.Body[0].(*parser.EchoStatement)
if !ok {
t.Fatalf("if body: expected EchoStatement, got %T", ifStmt.Body[0])
}
testStringLiteral(t, bodyEcho.Value, "found x")
// Fourth: echo with table
echo2, ok := program.Statements[3].(*parser.EchoStatement)
if !ok {
t.Fatalf("statement 3: expected EchoStatement, got %T", program.Statements[3])
}
_, ok = echo2.Value.(*parser.TableLiteral)
if !ok {
t.Fatalf("expected TableLiteral in echo, got %T", echo2.Value)
}
}
func TestModuloOperator(t *testing.T) {
input := `result = 10 % 3`
l := parser.NewLexer(input)
p := parser.NewParser(l)
program := p.ParseProgram()
checkParserErrors(t, p)
if len(program.Statements) != 1 {
t.Fatalf("expected 1 statement, got %d", len(program.Statements))
}
stmt, ok := program.Statements[0].(*parser.Assignment)
if !ok {
t.Fatalf("expected AssignStatement, got %T", program.Statements[0])
}
infix, ok := stmt.Value.(*parser.InfixExpression)
if !ok {
t.Fatalf("expected InfixExpression, got %T", stmt.Value)
}
if infix.Operator != "%" {
t.Errorf("expected operator '%%', got %s", infix.Operator)
}
leftLit, ok := infix.Left.(*parser.NumberLiteral)
if !ok {
t.Fatalf("expected NumberLiteral on left, got %T", infix.Left)
}
if leftLit.Value != 10.0 {
t.Errorf("expected left value 10, got %f", leftLit.Value)
}
rightLit, ok := infix.Right.(*parser.NumberLiteral)
if !ok {
t.Fatalf("expected NumberLiteral on right, got %T", infix.Right)
}
if rightLit.Value != 3.0 {
t.Errorf("expected right value 3, got %f", rightLit.Value)
}
}