846 lines
21 KiB
Go
846 lines
21 KiB
Go
package parser_test
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.sharkk.net/Sharkk/Mako/parser"
|
|
)
|
|
|
|
func TestNumericForLoop(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
variable string
|
|
hasStep bool
|
|
desc string
|
|
}{
|
|
{"for i = 1, 10 do\necho i\nend", "i", false, "basic numeric loop"},
|
|
{"for j = 0, 5, 2 do\nx = j\nend", "j", true, "numeric loop with step"},
|
|
{"for count = 1, 10, 2 do\necho count\nend", "count", true, "step loop"},
|
|
}
|
|
|
|
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)
|
|
|
|
if len(program.Statements) != 1 {
|
|
t.Fatalf("expected 1 statement, got %d", len(program.Statements))
|
|
}
|
|
|
|
stmt, ok := program.Statements[0].(*parser.ForStatement)
|
|
if !ok {
|
|
t.Fatalf("expected ForStatement, got %T", program.Statements[0])
|
|
}
|
|
|
|
if stmt.Variable.Value != tt.variable {
|
|
t.Errorf("expected variable %s, got %s", tt.variable, stmt.Variable.Value)
|
|
}
|
|
|
|
if stmt.Start == nil {
|
|
t.Error("expected start expression")
|
|
}
|
|
|
|
if stmt.End == nil {
|
|
t.Error("expected end expression")
|
|
}
|
|
|
|
if tt.hasStep && stmt.Step == nil {
|
|
t.Error("expected step expression")
|
|
}
|
|
|
|
if !tt.hasStep && stmt.Step != nil {
|
|
t.Error("unexpected step expression")
|
|
}
|
|
|
|
if len(stmt.Body) == 0 {
|
|
t.Error("expected non-empty body")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestForInLoop(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
hasKey bool
|
|
keyName string
|
|
valueName string
|
|
desc string
|
|
}{
|
|
{"for v in arr do\necho v\nend", false, "", "v", "single variable for-in"},
|
|
{"for k, v in table do\necho k\nend", true, "k", "v", "key-value for-in"},
|
|
{"for index, item in list do\necho item\nend", true, "index", "item", "descriptive names"},
|
|
}
|
|
|
|
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)
|
|
|
|
if len(program.Statements) != 1 {
|
|
t.Fatalf("expected 1 statement, got %d", len(program.Statements))
|
|
}
|
|
|
|
stmt, ok := program.Statements[0].(*parser.ForInStatement)
|
|
if !ok {
|
|
t.Fatalf("expected ForInStatement, got %T", program.Statements[0])
|
|
}
|
|
|
|
if tt.hasKey {
|
|
if stmt.Key == nil {
|
|
t.Error("expected key variable")
|
|
} else if stmt.Key.Value != tt.keyName {
|
|
t.Errorf("expected key %s, got %s", tt.keyName, stmt.Key.Value)
|
|
}
|
|
} else {
|
|
if stmt.Key != nil {
|
|
t.Error("unexpected key variable")
|
|
}
|
|
}
|
|
|
|
if stmt.Value == nil {
|
|
t.Error("expected value variable")
|
|
} else if stmt.Value.Value != tt.valueName {
|
|
t.Errorf("expected value %s, got %s", tt.valueName, stmt.Value.Value)
|
|
}
|
|
|
|
if stmt.Iterable == nil {
|
|
t.Error("expected iterable expression")
|
|
}
|
|
|
|
if len(stmt.Body) == 0 {
|
|
t.Error("expected non-empty body")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNestedForLoops(t *testing.T) {
|
|
input := `for i = 1, 3 do
|
|
for j = 1, 2 do
|
|
echo i + j
|
|
end
|
|
for k, v in table do
|
|
echo v
|
|
end
|
|
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))
|
|
}
|
|
|
|
outerLoop, ok := program.Statements[0].(*parser.ForStatement)
|
|
if !ok {
|
|
t.Fatalf("expected ForStatement, got %T", program.Statements[0])
|
|
}
|
|
|
|
if len(outerLoop.Body) != 2 {
|
|
t.Fatalf("expected 2 body statements, got %d", len(outerLoop.Body))
|
|
}
|
|
|
|
// First nested loop: numeric
|
|
innerNumeric, ok := outerLoop.Body[0].(*parser.ForStatement)
|
|
if !ok {
|
|
t.Fatalf("expected nested ForStatement, got %T", outerLoop.Body[0])
|
|
}
|
|
|
|
if innerNumeric.Variable.Value != "j" {
|
|
t.Errorf("expected variable j, got %s", innerNumeric.Variable.Value)
|
|
}
|
|
|
|
// Second nested loop: for-in
|
|
innerForIn, ok := outerLoop.Body[1].(*parser.ForInStatement)
|
|
if !ok {
|
|
t.Fatalf("expected nested ForInStatement, got %T", outerLoop.Body[1])
|
|
}
|
|
|
|
if innerForIn.Key.Value != "k" || innerForIn.Value.Value != "v" {
|
|
t.Error("expected key k and value v in nested for-in loop")
|
|
}
|
|
}
|
|
|
|
func TestForLoopWithComplexExpressions(t *testing.T) {
|
|
input := `for i = start, table.size, step + 1 do
|
|
for k, v in data[index] do
|
|
result[k] = v
|
|
end
|
|
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.ForStatement)
|
|
if !ok {
|
|
t.Fatalf("expected ForStatement, got %T", program.Statements[0])
|
|
}
|
|
|
|
// Test that complex expressions are parsed as expressions
|
|
if stmt.Start == nil || stmt.End == nil || stmt.Step == nil {
|
|
t.Error("expected all expressions to be non-nil")
|
|
}
|
|
|
|
// Test nested for-in with complex iterable
|
|
if len(stmt.Body) != 1 {
|
|
t.Fatalf("expected 1 body statement, got %d", len(stmt.Body))
|
|
}
|
|
|
|
nestedStmt, ok := stmt.Body[0].(*parser.ForInStatement)
|
|
if !ok {
|
|
t.Fatalf("expected nested ForInStatement, got %T", stmt.Body[0])
|
|
}
|
|
|
|
if nestedStmt.Iterable == nil {
|
|
t.Error("expected iterable expression")
|
|
}
|
|
}
|
|
|
|
func TestForLoopErrors(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expectedError string
|
|
desc string
|
|
}{
|
|
{"for do end", "expected identifier after 'for'", "missing variable"},
|
|
{"for i do end", "expected '=', ',' or 'in' after for loop variable", "missing assignment or in"},
|
|
{"for i = do end", "expected start expression in for loop", "missing start expression"},
|
|
{"for i = 1 do end", "expected ',' after start expression in for loop", "missing comma"},
|
|
{"for i = 1, do end", "expected end expression in for loop", "missing end expression"},
|
|
{"for i = 1, 10 end", "expected 'do' after for loop header", "missing do"},
|
|
{"for i = 1, 10 do", "expected 'end' to close for loop", "missing end"},
|
|
{"for i, do end", "expected identifier after ',' in for loop", "missing second variable"},
|
|
{"for i, j do end", "expected 'in' in for loop", "missing in keyword"},
|
|
{"for i, j in do end", "expected expression after 'in' in for loop", "missing iterable"},
|
|
{"for i in arr end", "expected 'do' after for loop header", "missing do in for-in"},
|
|
{"for i in arr do", "expected 'end' to close for loop", "missing end in for-in"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.desc, func(t *testing.T) {
|
|
l := parser.NewLexer(tt.input)
|
|
p := parser.NewParser(l)
|
|
p.ParseProgram()
|
|
|
|
if !p.HasErrors() {
|
|
t.Fatal("expected parsing errors")
|
|
}
|
|
|
|
errors := p.Errors()
|
|
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 TestForLoopStringRepresentation(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
contains []string
|
|
desc string
|
|
}{
|
|
{
|
|
"for i = 1, 10 do\necho i\nend",
|
|
[]string{"for i = 1.00, 10.00 do", "echo i", "end"},
|
|
"numeric for loop",
|
|
},
|
|
{
|
|
"for i = 1, 10, 2 do\nx = i\nend",
|
|
[]string{"for i = 1.00, 10.00, 2.00 do", "x = i", "end"},
|
|
"numeric for loop with step",
|
|
},
|
|
{
|
|
"for v in arr do\necho v\nend",
|
|
[]string{"for v in arr do", "echo v", "end"},
|
|
"single variable for-in",
|
|
},
|
|
{
|
|
"for k, v in table do\necho k\nend",
|
|
[]string{"for k, v in table do", "echo k", "end"},
|
|
"key-value for-in",
|
|
},
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestForLoopInMixedProgram(t *testing.T) {
|
|
input := `arr = {1, 2, 3}
|
|
total = 0
|
|
for i = 1, 3 do
|
|
total = total + arr[i]
|
|
end
|
|
echo total
|
|
|
|
for k, v in arr do
|
|
echo v
|
|
end`
|
|
|
|
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))
|
|
}
|
|
|
|
// First: table assignment
|
|
_, ok := program.Statements[0].(*parser.AssignStatement)
|
|
if !ok {
|
|
t.Fatalf("statement 0: expected AssignStatement, got %T", program.Statements[0])
|
|
}
|
|
|
|
// Second: variable assignment
|
|
_, ok = program.Statements[1].(*parser.AssignStatement)
|
|
if !ok {
|
|
t.Fatalf("statement 1: expected AssignStatement, got %T", program.Statements[1])
|
|
}
|
|
|
|
// Third: numeric for loop
|
|
forStmt, ok := program.Statements[2].(*parser.ForStatement)
|
|
if !ok {
|
|
t.Fatalf("statement 2: expected ForStatement, got %T", program.Statements[2])
|
|
}
|
|
|
|
if len(forStmt.Body) != 1 {
|
|
t.Errorf("expected 1 body statement in for loop, got %d", len(forStmt.Body))
|
|
}
|
|
|
|
// Fourth: echo statement
|
|
_, ok = program.Statements[3].(*parser.EchoStatement)
|
|
if !ok {
|
|
t.Fatalf("statement 3: expected EchoStatement, got %T", program.Statements[3])
|
|
}
|
|
|
|
// Fifth: for-in loop
|
|
forInStmt, ok := program.Statements[4].(*parser.ForInStatement)
|
|
if !ok {
|
|
t.Fatalf("statement 4: expected ForInStatement, got %T", program.Statements[4])
|
|
}
|
|
|
|
if len(forInStmt.Body) != 1 {
|
|
t.Errorf("expected 1 body statement in for-in loop, got %d", len(forInStmt.Body))
|
|
}
|
|
|
|
// Check that body contains echo statement
|
|
_, ok = forInStmt.Body[0].(*parser.EchoStatement)
|
|
if !ok {
|
|
t.Fatalf("for-in body: expected EchoStatement, got %T", forInStmt.Body[0])
|
|
}
|
|
}
|
|
|
|
func TestForLoopWithMemberAccess(t *testing.T) {
|
|
input := `for i = obj.start, obj.end, obj.step do
|
|
data[i] = result
|
|
result.items[i] = data[i]
|
|
end
|
|
|
|
for key, val in obj.items do
|
|
val.count = val.count + 1
|
|
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: numeric for with member access in bounds
|
|
forStmt, ok := program.Statements[0].(*parser.ForStatement)
|
|
if !ok {
|
|
t.Fatalf("statement 0: expected ForStatement, got %T", program.Statements[0])
|
|
}
|
|
|
|
// Check bounds are dot expressions
|
|
_, ok = forStmt.Start.(*parser.DotExpression)
|
|
if !ok {
|
|
t.Fatalf("expected DotExpression for start, got %T", forStmt.Start)
|
|
}
|
|
|
|
_, ok = forStmt.End.(*parser.DotExpression)
|
|
if !ok {
|
|
t.Fatalf("expected DotExpression for end, got %T", forStmt.End)
|
|
}
|
|
|
|
_, ok = forStmt.Step.(*parser.DotExpression)
|
|
if !ok {
|
|
t.Fatalf("expected DotExpression for step, got %T", forStmt.Step)
|
|
}
|
|
|
|
// Second: for-in with member access
|
|
forInStmt, ok := program.Statements[1].(*parser.ForInStatement)
|
|
if !ok {
|
|
t.Fatalf("statement 1: expected ForInStatement, got %T", program.Statements[1])
|
|
}
|
|
|
|
_, ok = forInStmt.Iterable.(*parser.DotExpression)
|
|
if !ok {
|
|
t.Fatalf("expected DotExpression for iterable, got %T", forInStmt.Iterable)
|
|
}
|
|
}
|
|
|
|
func TestBasicWhileLoop(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
description string
|
|
}{
|
|
{"while true do\necho \"hello\"\nend", "basic while with boolean"},
|
|
{"while x < 10 do\nx = x + 1\nend", "while with comparison"},
|
|
{"while count do\ncount = count - 1\nend", "while with identifier"},
|
|
{"while table.flag do\necho \"running\"\nend", "while with member access"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.description, func(t *testing.T) {
|
|
l := parser.NewLexer(tt.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.WhileStatement)
|
|
if !ok {
|
|
t.Fatalf("expected WhileStatement, got %T", program.Statements[0])
|
|
}
|
|
|
|
if stmt.Condition == nil {
|
|
t.Error("expected non-nil condition")
|
|
}
|
|
|
|
if len(stmt.Body) == 0 {
|
|
t.Error("expected non-empty body")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWhileLoopWithComplexConditions(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
description string
|
|
}{
|
|
{"while x + y < 10 do\necho x\nend", "arithmetic condition"},
|
|
{"while arr[i] != nil do\ni = i + 1\nend", "array access condition"},
|
|
{"while obj.count > 0 do\nobj.count = obj.count - 1\nend", "member access condition"},
|
|
{"while (a + b) * c == d do\necho \"match\"\nend", "grouped expression condition"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.description, func(t *testing.T) {
|
|
l := parser.NewLexer(tt.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.WhileStatement)
|
|
if !ok {
|
|
t.Fatalf("expected WhileStatement, got %T", program.Statements[0])
|
|
}
|
|
|
|
// Condition should be parsed as an expression
|
|
if stmt.Condition == nil {
|
|
t.Error("expected non-nil condition")
|
|
}
|
|
|
|
if len(stmt.Body) == 0 {
|
|
t.Error("expected non-empty body")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWhileLoopWithComplexBody(t *testing.T) {
|
|
input := `while running do
|
|
echo "processing"
|
|
if data[i] then
|
|
result = result + data[i]
|
|
i = i + 1
|
|
else
|
|
running = false
|
|
end
|
|
for j = 1, 3 do
|
|
echo j
|
|
end
|
|
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.WhileStatement)
|
|
if !ok {
|
|
t.Fatalf("expected WhileStatement, got %T", program.Statements[0])
|
|
}
|
|
|
|
if len(stmt.Body) != 3 {
|
|
t.Fatalf("expected 3 body statements, got %d", len(stmt.Body))
|
|
}
|
|
|
|
// First: echo statement
|
|
_, ok = stmt.Body[0].(*parser.EchoStatement)
|
|
if !ok {
|
|
t.Fatalf("body[0]: expected EchoStatement, got %T", stmt.Body[0])
|
|
}
|
|
|
|
// Second: if statement
|
|
_, ok = stmt.Body[1].(*parser.IfStatement)
|
|
if !ok {
|
|
t.Fatalf("body[1]: expected IfStatement, got %T", stmt.Body[1])
|
|
}
|
|
|
|
// Third: for statement
|
|
_, ok = stmt.Body[2].(*parser.ForStatement)
|
|
if !ok {
|
|
t.Fatalf("body[2]: expected ForStatement, got %T", stmt.Body[2])
|
|
}
|
|
}
|
|
|
|
func TestNestedWhileLoops(t *testing.T) {
|
|
input := `while outer do
|
|
while inner do
|
|
echo "nested"
|
|
inner = inner - 1
|
|
end
|
|
outer = outer - 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))
|
|
}
|
|
|
|
outerWhile, ok := program.Statements[0].(*parser.WhileStatement)
|
|
if !ok {
|
|
t.Fatalf("expected WhileStatement, got %T", program.Statements[0])
|
|
}
|
|
|
|
if len(outerWhile.Body) != 2 {
|
|
t.Fatalf("expected 2 body statements, got %d", len(outerWhile.Body))
|
|
}
|
|
|
|
// First body statement should be nested while
|
|
innerWhile, ok := outerWhile.Body[0].(*parser.WhileStatement)
|
|
if !ok {
|
|
t.Fatalf("expected nested WhileStatement, got %T", outerWhile.Body[0])
|
|
}
|
|
|
|
if len(innerWhile.Body) != 2 {
|
|
t.Fatalf("expected 2 inner body statements, got %d", len(innerWhile.Body))
|
|
}
|
|
|
|
// Second body statement should be assignment
|
|
_, ok = outerWhile.Body[1].(*parser.AssignStatement)
|
|
if !ok {
|
|
t.Fatalf("expected AssignStatement, got %T", outerWhile.Body[1])
|
|
}
|
|
}
|
|
|
|
func TestWhileLoopWithMemberAccess(t *testing.T) {
|
|
input := `while obj.running do
|
|
data[index] = data[index] + 1
|
|
index = index + 1
|
|
if index >= obj.size then
|
|
obj.running = false
|
|
end
|
|
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.WhileStatement)
|
|
if !ok {
|
|
t.Fatalf("expected WhileStatement, 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 != "running" {
|
|
t.Errorf("expected key 'running', got %s", dotExpr.Key)
|
|
}
|
|
|
|
if len(stmt.Body) != 3 {
|
|
t.Fatalf("expected 3 body statements, got %d", len(stmt.Body))
|
|
}
|
|
|
|
// First assignment: data[index] = ...
|
|
assign1, ok := stmt.Body[0].(*parser.AssignStatement)
|
|
if !ok {
|
|
t.Fatalf("expected AssignStatement, got %T", stmt.Body[0])
|
|
}
|
|
|
|
_, ok = assign1.Name.(*parser.IndexExpression)
|
|
if !ok {
|
|
t.Fatalf("expected IndexExpression for assignment target, got %T", assign1.Name)
|
|
}
|
|
}
|
|
|
|
func TestWhileLoopErrors(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expectedError string
|
|
desc string
|
|
}{
|
|
{"while do end", "expected condition after 'while'", "missing condition"},
|
|
{"while true end", "expected 'do' after while condition", "missing do"},
|
|
{"while true do", "expected 'end' to close while loop", "missing end"},
|
|
{"while + do end", "unexpected operator '+'", "invalid condition"},
|
|
{"while true do x =", "expected expression after assignment operator", "incomplete body"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.desc, func(t *testing.T) {
|
|
l := parser.NewLexer(tt.input)
|
|
p := parser.NewParser(l)
|
|
p.ParseProgram()
|
|
|
|
if !p.HasErrors() {
|
|
t.Fatal("expected parsing errors")
|
|
}
|
|
|
|
errors := p.Errors()
|
|
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 TestWhileLoopStringRepresentation(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
contains []string
|
|
desc string
|
|
}{
|
|
{
|
|
"while true do\necho \"hello\"\nend",
|
|
[]string{"while true do", "echo \"hello\"", "end"},
|
|
"basic while loop",
|
|
},
|
|
{
|
|
"while x < 10 do\nx = x + 1\nend",
|
|
[]string{"while (x < 10.00) do", "x = (x + 1.00)", "end"},
|
|
"while with condition and assignment",
|
|
},
|
|
{
|
|
"while obj.flag do\necho obj.value\nend",
|
|
[]string{"while obj.flag do", "echo obj.value", "end"},
|
|
"while with member access",
|
|
},
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWhileLoopInMixedProgram(t *testing.T) {
|
|
input := `counter = 0
|
|
total = 0
|
|
arr = {1, 2, 3, 4, 5}
|
|
|
|
while counter < 5 do
|
|
total = total + arr[counter]
|
|
counter = counter + 1
|
|
end
|
|
|
|
echo total
|
|
|
|
if total > 10 then
|
|
echo "sum is large"
|
|
end`
|
|
|
|
l := parser.NewLexer(input)
|
|
p := parser.NewParser(l)
|
|
program := p.ParseProgram()
|
|
checkParserErrors(t, p)
|
|
|
|
if len(program.Statements) != 6 {
|
|
t.Fatalf("expected 6 statements, got %d", len(program.Statements))
|
|
}
|
|
|
|
// First three: assignments
|
|
for i := 0; i < 3; i++ {
|
|
_, ok := program.Statements[i].(*parser.AssignStatement)
|
|
if !ok {
|
|
t.Fatalf("statement %d: expected AssignStatement, got %T", i, program.Statements[i])
|
|
}
|
|
}
|
|
|
|
// Fourth: while loop
|
|
whileStmt, ok := program.Statements[3].(*parser.WhileStatement)
|
|
if !ok {
|
|
t.Fatalf("statement 3: expected WhileStatement, got %T", program.Statements[3])
|
|
}
|
|
|
|
if len(whileStmt.Body) != 2 {
|
|
t.Errorf("expected 2 body statements in while loop, got %d", len(whileStmt.Body))
|
|
}
|
|
|
|
// Fifth: echo statement
|
|
_, ok = program.Statements[4].(*parser.EchoStatement)
|
|
if !ok {
|
|
t.Fatalf("statement 4: expected EchoStatement, got %T", program.Statements[4])
|
|
}
|
|
|
|
// Sixth: if statement
|
|
_, ok = program.Statements[5].(*parser.IfStatement)
|
|
if !ok {
|
|
t.Fatalf("statement 5: expected IfStatement, got %T", program.Statements[5])
|
|
}
|
|
}
|
|
|
|
func TestWhileLoopWithAllLoopTypes(t *testing.T) {
|
|
input := `while active do
|
|
for i = 1, 10 do
|
|
echo i
|
|
end
|
|
|
|
for k, v in data do
|
|
echo v
|
|
end
|
|
|
|
while inner do
|
|
inner = inner - 1
|
|
end
|
|
|
|
active = active - 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))
|
|
}
|
|
|
|
whileStmt, ok := program.Statements[0].(*parser.WhileStatement)
|
|
if !ok {
|
|
t.Fatalf("expected WhileStatement, got %T", program.Statements[0])
|
|
}
|
|
|
|
if len(whileStmt.Body) != 4 {
|
|
t.Fatalf("expected 4 body statements, got %d", len(whileStmt.Body))
|
|
}
|
|
|
|
// First: numeric for loop
|
|
_, ok = whileStmt.Body[0].(*parser.ForStatement)
|
|
if !ok {
|
|
t.Fatalf("body[0]: expected ForStatement, got %T", whileStmt.Body[0])
|
|
}
|
|
|
|
// Second: for-in loop
|
|
_, ok = whileStmt.Body[1].(*parser.ForInStatement)
|
|
if !ok {
|
|
t.Fatalf("body[1]: expected ForInStatement, got %T", whileStmt.Body[1])
|
|
}
|
|
|
|
// Third: nested while loop
|
|
_, ok = whileStmt.Body[2].(*parser.WhileStatement)
|
|
if !ok {
|
|
t.Fatalf("body[2]: expected WhileStatement, got %T", whileStmt.Body[2])
|
|
}
|
|
|
|
// Fourth: assignment
|
|
_, ok = whileStmt.Body[3].(*parser.AssignStatement)
|
|
if !ok {
|
|
t.Fatalf("body[3]: expected AssignStatement, got %T", whileStmt.Body[3])
|
|
}
|
|
}
|