Mako/parser/tests/conditionals_test.go

320 lines
7.3 KiB
Go

package parser_test
import (
"strings"
"testing"
"git.sharkk.net/Sharkk/Mako/parser"
)
func TestBasicIfStatement(t *testing.T) {
input := `if true then
x = 1
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))
}
stmt, ok := program.Statements[0].(*parser.IfStatement)
if !ok {
t.Fatalf("expected IfStatement, got %T", program.Statements[0])
}
testBooleanLiteral(t, stmt.Condition, true)
if len(stmt.Body) != 1 {
t.Fatalf("expected 1 body statement, got %d", len(stmt.Body))
}
bodyStmt, ok := stmt.Body[0].(*parser.Assignment)
if !ok {
t.Fatalf("expected AssignStatement in body, got %T", stmt.Body[0])
}
// Check that Target is an Identifier
ident, ok := bodyStmt.Target.(*parser.Identifier)
if !ok {
t.Fatalf("expected Identifier for Target, got %T", bodyStmt.Target)
}
if ident.Value != "x" {
t.Errorf("expected identifier 'x', got %s", ident.Value)
}
}
func TestIfElseStatement(t *testing.T) {
input := `if false then
x = 1
else
x = 2
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))
}
stmt, ok := program.Statements[0].(*parser.IfStatement)
if !ok {
t.Fatalf("expected IfStatement, got %T", program.Statements[0])
}
testBooleanLiteral(t, stmt.Condition, false)
if len(stmt.Body) != 1 {
t.Fatalf("expected 1 body statement, got %d", len(stmt.Body))
}
if len(stmt.Else) != 1 {
t.Fatalf("expected 1 else statement, got %d", len(stmt.Else))
}
elseStmt, ok := stmt.Else[0].(*parser.Assignment)
if !ok {
t.Fatalf("expected AssignStatement in else, got %T", stmt.Else[0])
}
// Check that Name is an Identifier
ident, ok := elseStmt.Target.(*parser.Identifier)
if !ok {
t.Fatalf("expected Identifier for Name, got %T", elseStmt.Target)
}
if ident.Value != "x" {
t.Errorf("expected identifier 'x', got %s", ident.Value)
}
}
func TestIfElseIfElseStatement(t *testing.T) {
input := `if x then
a = 1
elseif y then
a = 2
elseif z then
a = 3
else
a = 4
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))
}
stmt, ok := program.Statements[0].(*parser.IfStatement)
if !ok {
t.Fatalf("expected IfStatement, got %T", program.Statements[0])
}
testIdentifier(t, stmt.Condition, "x")
if len(stmt.ElseIfs) != 2 {
t.Fatalf("expected 2 elseif clauses, got %d", len(stmt.ElseIfs))
}
testIdentifier(t, stmt.ElseIfs[0].Condition, "y")
testIdentifier(t, stmt.ElseIfs[1].Condition, "z")
if len(stmt.Else) != 1 {
t.Fatalf("expected 1 else statement, got %d", len(stmt.Else))
}
}
func TestConditionalWithMemberAccess(t *testing.T) {
input := `if table.flag then
arr[1] = "updated"
obj.nested.count = obj.nested.count + 1
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))
}
stmt, ok := program.Statements[0].(*parser.IfStatement)
if !ok {
t.Fatalf("expected IfStatement, got %T", program.Statements[0])
}
// Check condition is dot expression
dotExpr, ok := stmt.Condition.(*parser.DotExpression)
if !ok {
t.Fatalf("expected DotExpression condition, got %T", stmt.Condition)
}
if dotExpr.Key != "flag" {
t.Errorf("expected key 'flag', got %s", dotExpr.Key)
}
if len(stmt.Body) != 2 {
t.Fatalf("expected 2 body statements, got %d", len(stmt.Body))
}
// First assignment: arr[1] = "updated"
assign1, ok := stmt.Body[0].(*parser.Assignment)
if !ok {
t.Fatalf("expected AssignStatement, got %T", stmt.Body[0])
}
_, ok = assign1.Target.(*parser.IndexExpression)
if !ok {
t.Fatalf("expected IndexExpression for assignment target, got %T", assign1.Target)
}
// Second assignment: obj.nested.count = obj.nested.count + 1
assign2, ok := stmt.Body[1].(*parser.Assignment)
if !ok {
t.Fatalf("expected AssignStatement, got %T", stmt.Body[1])
}
_, ok = assign2.Target.(*parser.DotExpression)
if !ok {
t.Fatalf("expected DotExpression for assignment target, got %T", assign2.Target)
}
}
func TestConditionalExpressions(t *testing.T) {
input := `if 1 + 2 then
x = 3 * 4
end`
l := parser.NewLexer(input)
p := parser.NewParser(l)
program := p.ParseProgram()
checkParserErrors(t, p)
stmt := program.Statements[0].(*parser.IfStatement)
// Test condition is an infix expression
infix, ok := stmt.Condition.(*parser.InfixExpression)
if !ok {
t.Fatalf("expected InfixExpression condition, got %T", stmt.Condition)
}
if infix.Operator != "+" {
t.Errorf("expected '+' operator, got %s", infix.Operator)
}
// Test body has expression assignment
bodyStmt := stmt.Body[0].(*parser.Assignment)
bodyInfix, ok := bodyStmt.Value.(*parser.InfixExpression)
if !ok {
t.Fatalf("expected InfixExpression value, got %T", bodyStmt.Value)
}
if bodyInfix.Operator != "*" {
t.Errorf("expected '*' operator, got %s", bodyInfix.Operator)
}
}
func TestConditionalErrors(t *testing.T) {
tests := []struct {
input string
expectedError string
desc string
}{
{"if then end", "expected condition after 'if'", "missing condition"},
{"if true end", "expected 'end' to close if statement", "missing body"},
{"if true then x = 1", "expected 'end' to close if statement", "missing end"},
{"elseif true then end", "unexpected token 'elseif'", "elseif without if"},
{"if true then x = 1 elseif then end", "expected condition after 'elseif'", "missing elseif condition"},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
l := parser.NewLexer(tt.input)
p := parser.NewParser(l)
p.ParseProgram()
errors := p.Errors()
if len(errors) == 0 {
t.Fatalf("expected parsing errors, got none")
}
found := false
for _, err := range errors {
if strings.Contains(err.Message, tt.expectedError) {
found = true
break
}
}
if !found {
errorMsgs := make([]string, len(errors))
for i, err := range errors {
errorMsgs[i] = err.Message
}
t.Errorf("expected error containing %q, got %v", tt.expectedError, errorMsgs)
}
})
}
}
func TestConditionalStringRepresentation(t *testing.T) {
tests := []struct {
input string
contains []string
desc string
}{
{
`if true then
x = 1
end`,
[]string{"if true then", "x = 1", "end"},
"basic if statement",
},
{
`if x then
a = 1
else
a = 2
end`,
[]string{"if x then", "a = 1", "else", "a = 2", "end"},
"if else statement",
},
{
`if x then
a = 1
elseif y then
a = 2
end`,
[]string{"if x then", "a = 1", "elseif y then", "a = 2", "end"},
"if elseif statement",
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
l := parser.NewLexer(tt.input)
p := parser.NewParser(l)
program := p.ParseProgram()
checkParserErrors(t, p)
programStr := program.String()
for _, contain := range tt.contains {
if !strings.Contains(programStr, contain) {
t.Errorf("expected string representation to contain %q, got:\n%s", contain, programStr)
}
}
})
}
}