320 lines
7.3 KiB
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)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|