Mako/parser/tests/errors_test.go
2025-06-10 09:57:52 -05:00

213 lines
5.0 KiB
Go

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)
}
})
}
}