Mako/tests/parser_test.go
2025-05-06 15:55:55 -05:00

346 lines
8.5 KiB
Go

package tests
import (
"testing"
assert "git.sharkk.net/Go/Assert"
"git.sharkk.net/Sharkk/Mako/lexer"
"git.sharkk.net/Sharkk/Mako/parser"
)
func TestParseVariableStatement(t *testing.T) {
input := `x = 5;`
lex := lexer.New(input)
p := parser.New(lex)
program := p.ParseProgram()
assert.Equal(t, 0, len(p.Errors()))
assert.Equal(t, 1, len(program.Statements))
stmt, ok := program.Statements[0].(*parser.VariableStatement)
assert.True(t, ok)
assert.Equal(t, "x", stmt.Name.Value)
// Test the expression value
numLit, ok := stmt.Value.(*parser.NumberLiteral)
assert.True(t, ok)
assert.Equal(t, 5.0, numLit.Value)
}
func TestParseEchoStatement(t *testing.T) {
input := `echo "hello";`
lex := lexer.New(input)
p := parser.New(lex)
program := p.ParseProgram()
assert.Equal(t, 0, len(p.Errors()))
assert.Equal(t, 1, len(program.Statements))
stmt, ok := program.Statements[0].(*parser.EchoStatement)
assert.True(t, ok)
strLit, ok := stmt.Value.(*parser.StringLiteral)
assert.True(t, ok)
assert.Equal(t, "hello", strLit.Value)
}
func TestParseTableLiteral(t *testing.T) {
input := `table = { name = "John", age = 30 };`
lex := lexer.New(input)
p := parser.New(lex)
program := p.ParseProgram()
assert.Equal(t, 0, len(p.Errors()))
assert.Equal(t, 1, len(program.Statements))
stmt, ok := program.Statements[0].(*parser.VariableStatement)
assert.True(t, ok)
assert.Equal(t, "table", stmt.Name.Value)
tableLit, ok := stmt.Value.(*parser.TableLiteral)
assert.True(t, ok)
assert.Equal(t, 2, len(tableLit.Pairs))
// Check that the table has the expected keys and values
// We need to find the entries in the pairs map
foundName := false
foundAge := false
for key, value := range tableLit.Pairs {
if ident, ok := key.(*parser.Identifier); ok && ident.Value == "name" {
foundName = true
strLit, ok := value.(*parser.StringLiteral)
assert.True(t, ok)
assert.Equal(t, "John", strLit.Value)
}
if ident, ok := key.(*parser.Identifier); ok && ident.Value == "age" {
foundAge = true
numLit, ok := value.(*parser.NumberLiteral)
assert.True(t, ok)
assert.Equal(t, 30.0, numLit.Value)
}
}
assert.True(t, foundName)
assert.True(t, foundAge)
}
func TestParseIndexExpression(t *testing.T) {
input := `table["key"];`
lex := lexer.New(input)
p := parser.New(lex)
program := p.ParseProgram()
assert.Equal(t, 0, len(p.Errors()))
assert.Equal(t, 1, len(program.Statements))
exprStmt, ok := program.Statements[0].(*parser.ExpressionStatement)
assert.True(t, ok)
indexExpr, ok := exprStmt.Expression.(*parser.IndexExpression)
assert.True(t, ok)
ident, ok := indexExpr.Left.(*parser.Identifier)
assert.True(t, ok)
assert.Equal(t, "table", ident.Value)
strLit, ok := indexExpr.Index.(*parser.StringLiteral)
assert.True(t, ok)
assert.Equal(t, "key", strLit.Value)
}
func TestParseInfixExpression(t *testing.T) {
tests := []struct {
input string
leftValue float64
operator string
rightValue float64
}{
{"5 + 5;", 5, "+", 5},
{"5 - 5;", 5, "-", 5},
{"5 * 5;", 5, "*", 5},
{"5 / 5;", 5, "/", 5},
{"5 < 5;", 5, "<", 5},
{"5 > 5;", 5, ">", 5},
{"5 == 5;", 5, "==", 5},
{"5 != 5;", 5, "!=", 5},
{"5 <= 5;", 5, "<=", 5},
{"5 >= 5;", 5, ">=", 5},
}
for _, tt := range tests {
lex := lexer.New(tt.input)
p := parser.New(lex)
program := p.ParseProgram()
assert.Equal(t, 0, len(p.Errors()))
assert.Equal(t, 1, len(program.Statements))
exprStmt, ok := program.Statements[0].(*parser.ExpressionStatement)
assert.True(t, ok)
infixExpr, ok := exprStmt.Expression.(*parser.InfixExpression)
assert.True(t, ok)
leftLit, ok := infixExpr.Left.(*parser.NumberLiteral)
assert.True(t, ok)
assert.Equal(t, tt.leftValue, leftLit.Value)
assert.Equal(t, tt.operator, infixExpr.Operator)
rightLit, ok := infixExpr.Right.(*parser.NumberLiteral)
assert.True(t, ok)
assert.Equal(t, tt.rightValue, rightLit.Value)
}
}
func TestParsePrefixExpression(t *testing.T) {
tests := []struct {
input string
operator string
value float64
}{
{"-5;", "-", 5},
}
for _, tt := range tests {
lex := lexer.New(tt.input)
p := parser.New(lex)
program := p.ParseProgram()
assert.Equal(t, 0, len(p.Errors()))
assert.Equal(t, 1, len(program.Statements))
exprStmt, ok := program.Statements[0].(*parser.ExpressionStatement)
assert.True(t, ok)
prefixExpr, ok := exprStmt.Expression.(*parser.PrefixExpression)
assert.True(t, ok)
assert.Equal(t, tt.operator, prefixExpr.Operator)
rightLit, ok := prefixExpr.Right.(*parser.NumberLiteral)
assert.True(t, ok)
assert.Equal(t, tt.value, rightLit.Value)
}
}
func TestParseBooleanLiteral(t *testing.T) {
tests := []struct {
input string
value bool
}{
{"true;", true},
{"false;", false},
}
for _, tt := range tests {
lex := lexer.New(tt.input)
p := parser.New(lex)
program := p.ParseProgram()
assert.Equal(t, 0, len(p.Errors()))
assert.Equal(t, 1, len(program.Statements))
exprStmt, ok := program.Statements[0].(*parser.ExpressionStatement)
assert.True(t, ok)
boolLit, ok := exprStmt.Expression.(*parser.BooleanLiteral)
assert.True(t, ok)
assert.Equal(t, tt.value, boolLit.Value)
}
}
func TestParseIfExpression(t *testing.T) {
input := `if x < 10 then
echo "x is less than 10";
else
echo "x is not less than 10";
end`
lex := lexer.New(input)
p := parser.New(lex)
program := p.ParseProgram()
assert.Equal(t, 0, len(p.Errors()))
assert.Equal(t, 1, len(program.Statements))
exprStmt, ok := program.Statements[0].(*parser.ExpressionStatement)
assert.True(t, ok)
ifExpr, ok := exprStmt.Expression.(*parser.IfExpression)
assert.True(t, ok)
// Check condition
condition, ok := ifExpr.Condition.(*parser.InfixExpression)
assert.True(t, ok)
assert.Equal(t, "<", condition.Operator)
// Check consequence
assert.Equal(t, 1, len(ifExpr.Consequence.Statements))
consEchoStmt, ok := ifExpr.Consequence.Statements[0].(*parser.EchoStatement)
assert.True(t, ok)
consStrLit, ok := consEchoStmt.Value.(*parser.StringLiteral)
assert.True(t, ok)
assert.Equal(t, "x is less than 10", consStrLit.Value)
// Check alternative
assert.NotNil(t, ifExpr.Alternative)
assert.Equal(t, 1, len(ifExpr.Alternative.Statements))
altEchoStmt, ok := ifExpr.Alternative.Statements[0].(*parser.EchoStatement)
assert.True(t, ok)
altStrLit, ok := altEchoStmt.Value.(*parser.StringLiteral)
assert.True(t, ok)
assert.Equal(t, "x is not less than 10", altStrLit.Value)
}
func TestParseElseIfExpression(t *testing.T) {
input := `if x < 10 then
echo "x is less than 10";
elseif x < 20 then
echo "x is less than 20";
else
echo "x is not less than 20";
end`
lex := lexer.New(input)
p := parser.New(lex)
program := p.ParseProgram()
assert.Equal(t, 0, len(p.Errors()))
assert.Equal(t, 1, len(program.Statements))
exprStmt, ok := program.Statements[0].(*parser.ExpressionStatement)
assert.True(t, ok)
ifExpr, ok := exprStmt.Expression.(*parser.IfExpression)
assert.True(t, ok)
// Check that we have an alternative block
assert.NotNil(t, ifExpr.Alternative)
assert.Equal(t, 1, len(ifExpr.Alternative.Statements))
// The alternative should contain another IfExpression (the elseif)
altExprStmt, ok := ifExpr.Alternative.Statements[0].(*parser.ExpressionStatement)
assert.True(t, ok)
nestedIfExpr, ok := altExprStmt.Expression.(*parser.IfExpression)
assert.True(t, ok)
// Check nested if condition
condition, ok := nestedIfExpr.Condition.(*parser.InfixExpression)
assert.True(t, ok)
assert.Equal(t, "<", condition.Operator)
rightLit, ok := condition.Right.(*parser.NumberLiteral)
assert.True(t, ok)
assert.Equal(t, 20.0, rightLit.Value)
// Check that the nested if has an alternative (the else)
assert.NotNil(t, nestedIfExpr.Alternative)
}
func TestParseLogicalOperators(t *testing.T) {
tests := []struct {
input string
operator string
}{
{"true and false;", "and"},
{"true or false;", "or"},
{"not true;", "not"},
}
for _, tt := range tests {
lex := lexer.New(tt.input)
p := parser.New(lex)
program := p.ParseProgram()
assert.Equal(t, 0, len(p.Errors()))
assert.Equal(t, 1, len(program.Statements))
exprStmt, ok := program.Statements[0].(*parser.ExpressionStatement)
assert.True(t, ok)
if tt.operator == "not" {
// Test prefix NOT
prefixExpr, ok := exprStmt.Expression.(*parser.PrefixExpression)
assert.True(t, ok)
assert.Equal(t, tt.operator, prefixExpr.Operator)
} else {
// Test infix AND/OR
infixExpr, ok := exprStmt.Expression.(*parser.InfixExpression)
assert.True(t, ok)
assert.Equal(t, tt.operator, infixExpr.Operator)
}
}
}