blob: 521455ba8b2dd81b9db24893bd98d18902abc6ed [file] [log] [blame]
package parser
import (
"matheval/ast"
"matheval/token"
"testing"
)
// helper: tokenize inline for concise tests
func tokens(toks ...token.Token) []token.Token {
return toks
}
func tok(typ token.Type, lit string, pos int) token.Token {
return token.Token{Type: typ, Literal: lit, Pos: pos}
}
// --- Success cases ---
func TestParseSingleNumber(t *testing.T) {
toks := tokens(
tok(token.Number, "42", 0),
tok(token.EOF, "", 2),
)
node, err := Parse(toks)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
num, ok := node.(*ast.NumberLit)
if !ok {
t.Fatalf("expected *ast.NumberLit, got %T", node)
}
if num.Value != 42 {
t.Fatalf("expected 42, got %f", num.Value)
}
}
func TestParseDecimalNumber(t *testing.T) {
toks := tokens(
tok(token.Number, "3.14", 0),
tok(token.EOF, "", 4),
)
node, err := Parse(toks)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
num, ok := node.(*ast.NumberLit)
if !ok {
t.Fatalf("expected *ast.NumberLit, got %T", node)
}
if num.Value != 3.14 {
t.Fatalf("expected 3.14, got %f", num.Value)
}
}
func TestParseAddition(t *testing.T) {
// 1 + 2
toks := tokens(
tok(token.Number, "1", 0),
tok(token.Plus, "+", 2),
tok(token.Number, "2", 4),
tok(token.EOF, "", 5),
)
node, err := Parse(toks)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expr, ok := node.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected *ast.BinaryExpr, got %T", node)
}
if expr.Op != token.Plus {
t.Fatalf("expected Plus, got %v", expr.Op)
}
assertNumber(t, expr.Left, 1)
assertNumber(t, expr.Right, 2)
}
func TestParseSubtraction(t *testing.T) {
// 5 - 3
toks := tokens(
tok(token.Number, "5", 0),
tok(token.Minus, "-", 2),
tok(token.Number, "3", 4),
tok(token.EOF, "", 5),
)
node, err := Parse(toks)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expr, ok := node.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected *ast.BinaryExpr, got %T", node)
}
if expr.Op != token.Minus {
t.Fatalf("expected Minus, got %v", expr.Op)
}
assertNumber(t, expr.Left, 5)
assertNumber(t, expr.Right, 3)
}
func TestParseMultiplication(t *testing.T) {
// 2 * 3
toks := tokens(
tok(token.Number, "2", 0),
tok(token.Star, "*", 2),
tok(token.Number, "3", 4),
tok(token.EOF, "", 5),
)
node, err := Parse(toks)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expr, ok := node.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected *ast.BinaryExpr, got %T", node)
}
if expr.Op != token.Star {
t.Fatalf("expected Star, got %v", expr.Op)
}
assertNumber(t, expr.Left, 2)
assertNumber(t, expr.Right, 3)
}
func TestParseDivision(t *testing.T) {
// 6 / 2
toks := tokens(
tok(token.Number, "6", 0),
tok(token.Slash, "/", 2),
tok(token.Number, "2", 4),
tok(token.EOF, "", 5),
)
node, err := Parse(toks)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expr, ok := node.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected *ast.BinaryExpr, got %T", node)
}
if expr.Op != token.Slash {
t.Fatalf("expected Slash, got %v", expr.Op)
}
assertNumber(t, expr.Left, 6)
assertNumber(t, expr.Right, 2)
}
func TestParsePrecedence(t *testing.T) {
// 1 + 2 * 3 → 1 + (2 * 3)
toks := tokens(
tok(token.Number, "1", 0),
tok(token.Plus, "+", 2),
tok(token.Number, "2", 4),
tok(token.Star, "*", 6),
tok(token.Number, "3", 8),
tok(token.EOF, "", 9),
)
node, err := Parse(toks)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Root should be Plus
expr, ok := node.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected *ast.BinaryExpr, got %T", node)
}
if expr.Op != token.Plus {
t.Fatalf("expected Plus at root, got %v", expr.Op)
}
assertNumber(t, expr.Left, 1)
// Right should be Star
right, ok := expr.Right.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected right to be *ast.BinaryExpr, got %T", expr.Right)
}
if right.Op != token.Star {
t.Fatalf("expected Star, got %v", right.Op)
}
assertNumber(t, right.Left, 2)
assertNumber(t, right.Right, 3)
}
func TestParsePrecedenceMulFirst(t *testing.T) {
// 2 * 3 + 1 → (2 * 3) + 1
toks := tokens(
tok(token.Number, "2", 0),
tok(token.Star, "*", 2),
tok(token.Number, "3", 4),
tok(token.Plus, "+", 6),
tok(token.Number, "1", 8),
tok(token.EOF, "", 9),
)
node, err := Parse(toks)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expr, ok := node.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected *ast.BinaryExpr, got %T", node)
}
if expr.Op != token.Plus {
t.Fatalf("expected Plus at root, got %v", expr.Op)
}
left, ok := expr.Left.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected left to be *ast.BinaryExpr, got %T", expr.Left)
}
if left.Op != token.Star {
t.Fatalf("expected Star, got %v", left.Op)
}
assertNumber(t, left.Left, 2)
assertNumber(t, left.Right, 3)
assertNumber(t, expr.Right, 1)
}
func TestParseLeftAssociativity(t *testing.T) {
// 1 - 2 - 3 → (1 - 2) - 3
toks := tokens(
tok(token.Number, "1", 0),
tok(token.Minus, "-", 2),
tok(token.Number, "2", 4),
tok(token.Minus, "-", 6),
tok(token.Number, "3", 8),
tok(token.EOF, "", 9),
)
node, err := Parse(toks)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Root: (1 - 2) - 3
expr, ok := node.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected *ast.BinaryExpr, got %T", node)
}
if expr.Op != token.Minus {
t.Fatalf("expected Minus at root, got %v", expr.Op)
}
assertNumber(t, expr.Right, 3)
left, ok := expr.Left.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected left to be *ast.BinaryExpr, got %T", expr.Left)
}
if left.Op != token.Minus {
t.Fatalf("expected Minus, got %v", left.Op)
}
assertNumber(t, left.Left, 1)
assertNumber(t, left.Right, 2)
}
func TestParseParentheses(t *testing.T) {
// (1 + 2) * 3
toks := tokens(
tok(token.LParen, "(", 0),
tok(token.Number, "1", 1),
tok(token.Plus, "+", 3),
tok(token.Number, "2", 5),
tok(token.RParen, ")", 6),
tok(token.Star, "*", 8),
tok(token.Number, "3", 10),
tok(token.EOF, "", 11),
)
node, err := Parse(toks)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expr, ok := node.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected *ast.BinaryExpr, got %T", node)
}
if expr.Op != token.Star {
t.Fatalf("expected Star at root, got %v", expr.Op)
}
assertNumber(t, expr.Right, 3)
left, ok := expr.Left.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected left to be *ast.BinaryExpr, got %T", expr.Left)
}
if left.Op != token.Plus {
t.Fatalf("expected Plus, got %v", left.Op)
}
assertNumber(t, left.Left, 1)
assertNumber(t, left.Right, 2)
}
func TestParseNestedParentheses(t *testing.T) {
// ((1 + 2))
toks := tokens(
tok(token.LParen, "(", 0),
tok(token.LParen, "(", 1),
tok(token.Number, "1", 2),
tok(token.Plus, "+", 4),
tok(token.Number, "2", 6),
tok(token.RParen, ")", 7),
tok(token.RParen, ")", 8),
tok(token.EOF, "", 9),
)
node, err := Parse(toks)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expr, ok := node.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected *ast.BinaryExpr, got %T", node)
}
if expr.Op != token.Plus {
t.Fatalf("expected Plus, got %v", expr.Op)
}
assertNumber(t, expr.Left, 1)
assertNumber(t, expr.Right, 2)
}
func TestParseComplexExpression(t *testing.T) {
// 1 + 2 * 3 - 4 / 2 → (1 + (2*3)) - (4/2)
toks := tokens(
tok(token.Number, "1", 0),
tok(token.Plus, "+", 2),
tok(token.Number, "2", 4),
tok(token.Star, "*", 5),
tok(token.Number, "3", 6),
tok(token.Minus, "-", 8),
tok(token.Number, "4", 10),
tok(token.Slash, "/", 11),
tok(token.Number, "2", 12),
tok(token.EOF, "", 13),
)
node, err := Parse(toks)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Root: (1 + (2*3)) - (4/2)
root, ok := node.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected *ast.BinaryExpr, got %T", node)
}
if root.Op != token.Minus {
t.Fatalf("expected Minus at root, got %v", root.Op)
}
// Left: 1 + (2*3)
left, ok := root.Left.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected left to be *ast.BinaryExpr, got %T", root.Left)
}
if left.Op != token.Plus {
t.Fatalf("expected Plus, got %v", left.Op)
}
assertNumber(t, left.Left, 1)
mul, ok := left.Right.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected *ast.BinaryExpr, got %T", left.Right)
}
if mul.Op != token.Star {
t.Fatalf("expected Star, got %v", mul.Op)
}
assertNumber(t, mul.Left, 2)
assertNumber(t, mul.Right, 3)
// Right: 4/2
div, ok := root.Right.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected right to be *ast.BinaryExpr, got %T", root.Right)
}
if div.Op != token.Slash {
t.Fatalf("expected Slash, got %v", div.Op)
}
assertNumber(t, div.Left, 4)
assertNumber(t, div.Right, 2)
}
// --- Error cases ---
func TestParseEmptyInput(t *testing.T) {
toks := tokens(
tok(token.EOF, "", 0),
)
_, err := Parse(toks)
if err == nil {
t.Fatal("expected error for empty input")
}
}
func TestParseMissingRParen(t *testing.T) {
// (1 + 2
toks := tokens(
tok(token.LParen, "(", 0),
tok(token.Number, "1", 1),
tok(token.Plus, "+", 3),
tok(token.Number, "2", 5),
tok(token.EOF, "", 6),
)
_, err := Parse(toks)
if err == nil {
t.Fatal("expected error for missing right paren")
}
}
func TestParseUnexpectedRParen(t *testing.T) {
// ) 1
toks := tokens(
tok(token.RParen, ")", 0),
tok(token.Number, "1", 2),
tok(token.EOF, "", 3),
)
_, err := Parse(toks)
if err == nil {
t.Fatal("expected error for unexpected right paren")
}
}
func TestParseTrailingOperator(t *testing.T) {
// 1 +
toks := tokens(
tok(token.Number, "1", 0),
tok(token.Plus, "+", 2),
tok(token.EOF, "", 3),
)
_, err := Parse(toks)
if err == nil {
t.Fatal("expected error for trailing operator")
}
}
func TestParseTrailingTokens(t *testing.T) {
// 1 2
toks := tokens(
tok(token.Number, "1", 0),
tok(token.Number, "2", 2),
tok(token.EOF, "", 3),
)
_, err := Parse(toks)
if err == nil {
t.Fatal("expected error for trailing tokens")
}
}
func TestParseConsecutiveOperators(t *testing.T) {
// 1 + * 2
toks := tokens(
tok(token.Number, "1", 0),
tok(token.Plus, "+", 2),
tok(token.Star, "*", 4),
tok(token.Number, "2", 6),
tok(token.EOF, "", 7),
)
_, err := Parse(toks)
if err == nil {
t.Fatal("expected error for consecutive operators")
}
}
func TestParseEmptyParens(t *testing.T) {
// ()
toks := tokens(
tok(token.LParen, "(", 0),
tok(token.RParen, ")", 1),
tok(token.EOF, "", 2),
)
_, err := Parse(toks)
if err == nil {
t.Fatal("expected error for empty parentheses")
}
}
// --- Helper ---
func assertNumber(t *testing.T, node ast.Node, expected float64) {
t.Helper()
num, ok := node.(*ast.NumberLit)
if !ok {
t.Fatalf("expected *ast.NumberLit, got %T", node)
}
if num.Value != expected {
t.Fatalf("expected %f, got %f", expected, num.Value)
}
}