package parser_test import ( "strings" "testing" "git.sharkk.net/Sharkk/Mako/parser" ) func TestParsingErrors(t *testing.T) { tests := []struct { input string expectedError string line int column int }{ {"= 5", "assignment operator '=' without left-hand side identifier", 1, 1}, {"x =", "expected expression after assignment operator", 1, 3}, {"(1 + 2", "expected next token to be )", 1, 7}, {"+ 5", "unexpected operator '+'", 1, 1}, {"1 +", "expected expression after operator '+'", 1, 3}, {"@", "unexpected token '@'", 1, 1}, {"invalid@", "unexpected identifier", 1, 1}, {"{1, 2", "expected next token to be }", 1, 6}, {"{a =", "expected expression after assignment operator", 1, 4}, {"{a = 1,", "expected next token to be }", 1, 8}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { l := parser.NewLexer(tt.input) p := parser.NewParser(l) switch tt.input { case "(1 + 2", "+ 5", "1 +", "{1, 2", "{a =", "{a = 1,": p.ParseExpression(parser.LOWEST) default: 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 if err.Line != tt.line { t.Errorf("expected error at line %d, got line %d", tt.line, err.Line) } 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 TestErrorRecovery(t *testing.T) { input := `x = 42 = 5 y = "hello"` l := parser.NewLexer(input) p := parser.NewParser(l) program := p.ParseProgram() if !p.HasErrors() { t.Fatal("expected parsing errors") } errors := p.Errors() found := false for _, err := range errors { if strings.Contains(err.Message, "assignment operator '=' without left-hand side identifier") { found = true if err.Line != 2 { t.Errorf("expected error at line 2, got line %d", err.Line) } break } } if !found { t.Error("expected specific assignment error") } validStatements := 0 for _, stmt := range program.Statements { if stmt != nil { validStatements++ } } if validStatements < 2 { t.Errorf("expected at least 2 valid statements, got %d", validStatements) } } func TestErrorMessages(t *testing.T) { tests := []struct { input string expectedMessage string }{ {"= 5", "Parse error at line 1, column 1: assignment operator '=' without left-hand side identifier (near '=')"}, {"x =", "Parse error at line 1, column 3: expected expression after assignment operator (near '')"}, {"(", "Parse error at line 1, column 1: unexpected end of input (near '')"}, } for _, tt := range tests { t.Run(tt.input, 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() if len(errors) == 0 { t.Fatal("expected at least one error") } errorStr := errors[0].Error() if !strings.Contains(errorStr, "Parse error at line") { t.Errorf("expected formatted error message, got: %s", errorStr) } }) } } func TestEchoErrors(t *testing.T) { tests := []struct { input string expectedError string desc string }{ {"echo", "expected expression after 'echo'", "echo without expression"}, {"echo +", "unexpected operator '+'", "echo with invalid expression"}, {"echo (", "unexpected end of input", "echo with incomplete expression"}, } 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 TestTokenTypeStringWithEcho(t *testing.T) { tests := []struct { input string expectedMessage string }{ {"echo", "Parse error at line 1, column 1: expected expression after 'echo' (near '')"}, } for _, tt := range tests { t.Run(tt.input, 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() if len(errors) == 0 { t.Fatal("expected at least one error") } errorStr := errors[0].Error() if !strings.Contains(errorStr, "Parse error at line") { t.Errorf("expected formatted error message, got: %s", errorStr) } }) } }