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.AssignStatement) if !ok { t.Fatalf("expected AssignStatement in body, got %T", stmt.Body[0]) } // Check that Name is an Identifier ident, ok := bodyStmt.Name.(*parser.Identifier) if !ok { t.Fatalf("expected Identifier for Name, got %T", bodyStmt.Name) } 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.AssignStatement) if !ok { t.Fatalf("expected AssignStatement in else, got %T", stmt.Else[0]) } // Check that Name is an Identifier ident, ok := elseStmt.Name.(*parser.Identifier) if !ok { t.Fatalf("expected Identifier for Name, got %T", elseStmt.Name) } 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.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) } // Second assignment: obj.nested.count = obj.nested.count + 1 assign2, ok := stmt.Body[1].(*parser.AssignStatement) if !ok { t.Fatalf("expected AssignStatement, got %T", stmt.Body[1]) } _, ok = assign2.Name.(*parser.DotExpression) if !ok { t.Fatalf("expected DotExpression for assignment target, got %T", assign2.Name) } } 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.AssignStatement) 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) } } }) } }