blob: 99b0e327d1070cdc493ccdd77f4c540eec59ca07 [file] [log] [blame]
package parser
import (
"matheval/ast"
"matheval/token"
"testing"
)
// --- Parse (backward compatibility) ---
func TestParse_SingleNumber(t *testing.T) {
tokens := []token.Token{
{Type: token.Number, Literal: "42", Pos: 0},
{Type: token.EOF, Literal: "", Pos: 2},
}
node, err := Parse(tokens)
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 %v", num.Value)
}
}
func TestParse_BinaryExpr(t *testing.T) {
tokens := []token.Token{
{Type: token.Number, Literal: "1", Pos: 0},
{Type: token.Plus, Literal: "+", Pos: 2},
{Type: token.Number, Literal: "2", Pos: 4},
{Type: token.EOF, Literal: "", Pos: 5},
}
node, err := Parse(tokens)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
bin, ok := node.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected *ast.BinaryExpr, got %T", node)
}
if bin.Op != token.Plus {
t.Fatalf("expected Plus, got %v", bin.Op)
}
}
// --- factor: Ident ---
func TestParse_Ident(t *testing.T) {
tokens := []token.Token{
{Type: token.Ident, Literal: "x", Pos: 0},
{Type: token.EOF, Literal: "", Pos: 1},
}
node, err := Parse(tokens)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
ident, ok := node.(*ast.Ident)
if !ok {
t.Fatalf("expected *ast.Ident, got %T", node)
}
if ident.Name != "x" {
t.Fatalf("expected 'x', got %q", ident.Name)
}
}
func TestParse_IdentInExpr(t *testing.T) {
// x + 1
tokens := []token.Token{
{Type: token.Ident, Literal: "x", Pos: 0},
{Type: token.Plus, Literal: "+", Pos: 2},
{Type: token.Number, Literal: "1", Pos: 4},
{Type: token.EOF, Literal: "", Pos: 5},
}
node, err := Parse(tokens)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
bin, ok := node.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected *ast.BinaryExpr, got %T", node)
}
left, ok := bin.Left.(*ast.Ident)
if !ok {
t.Fatalf("expected left to be *ast.Ident, got %T", bin.Left)
}
if left.Name != "x" {
t.Fatalf("expected 'x', got %q", left.Name)
}
}
// --- factor: FuncCall ---
func TestParse_FuncCallNoArgs(t *testing.T) {
// f()
tokens := []token.Token{
{Type: token.Ident, Literal: "f", Pos: 0},
{Type: token.LParen, Literal: "(", Pos: 1},
{Type: token.RParen, Literal: ")", Pos: 2},
{Type: token.EOF, Literal: "", Pos: 3},
}
node, err := Parse(tokens)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
fc, ok := node.(*ast.FuncCall)
if !ok {
t.Fatalf("expected *ast.FuncCall, got %T", node)
}
if fc.Name != "f" {
t.Fatalf("expected 'f', got %q", fc.Name)
}
if len(fc.Args) != 0 {
t.Fatalf("expected 0 args, got %d", len(fc.Args))
}
}
func TestParse_FuncCallOneArg(t *testing.T) {
// f(42)
tokens := []token.Token{
{Type: token.Ident, Literal: "f", Pos: 0},
{Type: token.LParen, Literal: "(", Pos: 1},
{Type: token.Number, Literal: "42", Pos: 2},
{Type: token.RParen, Literal: ")", Pos: 4},
{Type: token.EOF, Literal: "", Pos: 5},
}
node, err := Parse(tokens)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
fc, ok := node.(*ast.FuncCall)
if !ok {
t.Fatalf("expected *ast.FuncCall, got %T", node)
}
if fc.Name != "f" {
t.Fatalf("expected 'f', got %q", fc.Name)
}
if len(fc.Args) != 1 {
t.Fatalf("expected 1 arg, got %d", len(fc.Args))
}
arg, ok := fc.Args[0].(*ast.NumberLit)
if !ok {
t.Fatalf("expected arg to be *ast.NumberLit, got %T", fc.Args[0])
}
if arg.Value != 42 {
t.Fatalf("expected 42, got %v", arg.Value)
}
}
func TestParse_FuncCallMultiArgs(t *testing.T) {
// f(1, 2, 3)
tokens := []token.Token{
{Type: token.Ident, Literal: "f", Pos: 0},
{Type: token.LParen, Literal: "(", Pos: 1},
{Type: token.Number, Literal: "1", Pos: 2},
{Type: token.Comma, Literal: ",", Pos: 3},
{Type: token.Number, Literal: "2", Pos: 5},
{Type: token.Comma, Literal: ",", Pos: 6},
{Type: token.Number, Literal: "3", Pos: 8},
{Type: token.RParen, Literal: ")", Pos: 9},
{Type: token.EOF, Literal: "", Pos: 10},
}
node, err := Parse(tokens)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
fc, ok := node.(*ast.FuncCall)
if !ok {
t.Fatalf("expected *ast.FuncCall, got %T", node)
}
if len(fc.Args) != 3 {
t.Fatalf("expected 3 args, got %d", len(fc.Args))
}
}
func TestParse_FuncCallExprArgs(t *testing.T) {
// f(1+2, 3*4)
tokens := []token.Token{
{Type: token.Ident, Literal: "f", Pos: 0},
{Type: token.LParen, Literal: "(", Pos: 1},
{Type: token.Number, Literal: "1", Pos: 2},
{Type: token.Plus, Literal: "+", Pos: 3},
{Type: token.Number, Literal: "2", Pos: 4},
{Type: token.Comma, Literal: ",", Pos: 5},
{Type: token.Number, Literal: "3", Pos: 7},
{Type: token.Star, Literal: "*", Pos: 8},
{Type: token.Number, Literal: "4", Pos: 9},
{Type: token.RParen, Literal: ")", Pos: 10},
{Type: token.EOF, Literal: "", Pos: 11},
}
node, err := Parse(tokens)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
fc, ok := node.(*ast.FuncCall)
if !ok {
t.Fatalf("expected *ast.FuncCall, got %T", node)
}
if len(fc.Args) != 2 {
t.Fatalf("expected 2 args, got %d", len(fc.Args))
}
// First arg: 1+2
_, ok = fc.Args[0].(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected first arg to be *ast.BinaryExpr, got %T", fc.Args[0])
}
}
func TestParse_FuncCallInExpr(t *testing.T) {
// f(1) + 2
tokens := []token.Token{
{Type: token.Ident, Literal: "f", Pos: 0},
{Type: token.LParen, Literal: "(", Pos: 1},
{Type: token.Number, Literal: "1", Pos: 2},
{Type: token.RParen, Literal: ")", Pos: 3},
{Type: token.Plus, Literal: "+", Pos: 5},
{Type: token.Number, Literal: "2", Pos: 7},
{Type: token.EOF, Literal: "", Pos: 8},
}
node, err := Parse(tokens)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
bin, ok := node.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected *ast.BinaryExpr, got %T", node)
}
_, ok = bin.Left.(*ast.FuncCall)
if !ok {
t.Fatalf("expected left to be *ast.FuncCall, got %T", bin.Left)
}
}
func TestParse_FuncCallMissingRParen(t *testing.T) {
// f(1
tokens := []token.Token{
{Type: token.Ident, Literal: "f", Pos: 0},
{Type: token.LParen, Literal: "(", Pos: 1},
{Type: token.Number, Literal: "1", Pos: 2},
{Type: token.EOF, Literal: "", Pos: 3},
}
_, err := Parse(tokens)
if err == nil {
t.Fatal("expected error for missing closing paren in func call")
}
}
// --- ParseLine: expression statement ---
func TestParseLine_ExprStmt(t *testing.T) {
// "1 + 2"
tokens := []token.Token{
{Type: token.Number, Literal: "1", Pos: 0},
{Type: token.Plus, Literal: "+", Pos: 2},
{Type: token.Number, Literal: "2", Pos: 4},
{Type: token.EOF, Literal: "", Pos: 5},
}
stmt, err := ParseLine(tokens)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
es, ok := stmt.(*ast.ExprStmt)
if !ok {
t.Fatalf("expected *ast.ExprStmt, got %T", stmt)
}
_, ok = es.Expr.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected expr to be *ast.BinaryExpr, got %T", es.Expr)
}
}
func TestParseLine_ExprStmtFuncCall(t *testing.T) {
// "f(1)"
tokens := []token.Token{
{Type: token.Ident, Literal: "f", Pos: 0},
{Type: token.LParen, Literal: "(", Pos: 1},
{Type: token.Number, Literal: "1", Pos: 2},
{Type: token.RParen, Literal: ")", Pos: 3},
{Type: token.EOF, Literal: "", Pos: 4},
}
stmt, err := ParseLine(tokens)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
es, ok := stmt.(*ast.ExprStmt)
if !ok {
t.Fatalf("expected *ast.ExprStmt, got %T", stmt)
}
_, ok = es.Expr.(*ast.FuncCall)
if !ok {
t.Fatalf("expected expr to be *ast.FuncCall, got %T", es.Expr)
}
}
// --- ParseLine: function definition ---
func TestParseLine_FuncDefSingleParam(t *testing.T) {
// "f(x) = x + 1"
tokens := []token.Token{
{Type: token.Ident, Literal: "f", Pos: 0},
{Type: token.LParen, Literal: "(", Pos: 1},
{Type: token.Ident, Literal: "x", Pos: 2},
{Type: token.RParen, Literal: ")", Pos: 3},
{Type: token.Equals, Literal: "=", Pos: 5},
{Type: token.Ident, Literal: "x", Pos: 7},
{Type: token.Plus, Literal: "+", Pos: 9},
{Type: token.Number, Literal: "1", Pos: 11},
{Type: token.EOF, Literal: "", Pos: 12},
}
stmt, err := ParseLine(tokens)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
fd, ok := stmt.(*ast.FuncDef)
if !ok {
t.Fatalf("expected *ast.FuncDef, got %T", stmt)
}
if fd.Name != "f" {
t.Fatalf("expected name 'f', got %q", fd.Name)
}
if len(fd.Params) != 1 || fd.Params[0] != "x" {
t.Fatalf("expected params [x], got %v", fd.Params)
}
// Body should be x + 1
bin, ok := fd.Body.(*ast.BinaryExpr)
if !ok {
t.Fatalf("expected body to be *ast.BinaryExpr, got %T", fd.Body)
}
if bin.Op != token.Plus {
t.Fatalf("expected Plus in body, got %v", bin.Op)
}
}
func TestParseLine_FuncDefMultiParam(t *testing.T) {
// "add(x, y) = x + y"
tokens := []token.Token{
{Type: token.Ident, Literal: "add", Pos: 0},
{Type: token.LParen, Literal: "(", Pos: 3},
{Type: token.Ident, Literal: "x", Pos: 4},
{Type: token.Comma, Literal: ",", Pos: 5},
{Type: token.Ident, Literal: "y", Pos: 7},
{Type: token.RParen, Literal: ")", Pos: 8},
{Type: token.Equals, Literal: "=", Pos: 10},
{Type: token.Ident, Literal: "x", Pos: 12},
{Type: token.Plus, Literal: "+", Pos: 14},
{Type: token.Ident, Literal: "y", Pos: 16},
{Type: token.EOF, Literal: "", Pos: 17},
}
stmt, err := ParseLine(tokens)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
fd, ok := stmt.(*ast.FuncDef)
if !ok {
t.Fatalf("expected *ast.FuncDef, got %T", stmt)
}
if fd.Name != "add" {
t.Fatalf("expected name 'add', got %q", fd.Name)
}
if len(fd.Params) != 2 || fd.Params[0] != "x" || fd.Params[1] != "y" {
t.Fatalf("expected params [x y], got %v", fd.Params)
}
}
func TestParseLine_FuncDefNoParams(t *testing.T) {
// "c() = 42"
tokens := []token.Token{
{Type: token.Ident, Literal: "c", Pos: 0},
{Type: token.LParen, Literal: "(", Pos: 1},
{Type: token.RParen, Literal: ")", Pos: 2},
{Type: token.Equals, Literal: "=", Pos: 4},
{Type: token.Number, Literal: "42", Pos: 6},
{Type: token.EOF, Literal: "", Pos: 8},
}
stmt, err := ParseLine(tokens)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
fd, ok := stmt.(*ast.FuncDef)
if !ok {
t.Fatalf("expected *ast.FuncDef, got %T", stmt)
}
if fd.Name != "c" {
t.Fatalf("expected name 'c', got %q", fd.Name)
}
if len(fd.Params) != 0 {
t.Fatalf("expected 0 params, got %d", len(fd.Params))
}
}
// --- ParseLine: error cases ---
func TestParseLine_Empty(t *testing.T) {
tokens := []token.Token{
{Type: token.EOF, Literal: "", Pos: 0},
}
_, err := ParseLine(tokens)
if err == nil {
t.Fatal("expected error for empty input")
}
}
func TestParseLine_FuncDefMissingBody(t *testing.T) {
// "f(x) ="
tokens := []token.Token{
{Type: token.Ident, Literal: "f", Pos: 0},
{Type: token.LParen, Literal: "(", Pos: 1},
{Type: token.Ident, Literal: "x", Pos: 2},
{Type: token.RParen, Literal: ")", Pos: 3},
{Type: token.Equals, Literal: "=", Pos: 5},
{Type: token.EOF, Literal: "", Pos: 6},
}
_, err := ParseLine(tokens)
if err == nil {
t.Fatal("expected error for missing function body")
}
}
func TestParseLine_FuncDefBadParams(t *testing.T) {
// "f(1) = 2" — params must be identifiers
tokens := []token.Token{
{Type: token.Ident, Literal: "f", Pos: 0},
{Type: token.LParen, Literal: "(", Pos: 1},
{Type: token.Number, Literal: "1", Pos: 2},
{Type: token.RParen, Literal: ")", Pos: 3},
{Type: token.Equals, Literal: "=", Pos: 5},
{Type: token.Number, Literal: "2", Pos: 7},
{Type: token.EOF, Literal: "", Pos: 8},
}
_, err := ParseLine(tokens)
if err == nil {
t.Fatal("expected error for numeric parameter in func def")
}
}
func TestParseLine_FuncDefTrailingTokens(t *testing.T) {
// "f(x) = x 1" — extra token after body
tokens := []token.Token{
{Type: token.Ident, Literal: "f", Pos: 0},
{Type: token.LParen, Literal: "(", Pos: 1},
{Type: token.Ident, Literal: "x", Pos: 2},
{Type: token.RParen, Literal: ")", Pos: 3},
{Type: token.Equals, Literal: "=", Pos: 5},
{Type: token.Ident, Literal: "x", Pos: 7},
{Type: token.Number, Literal: "1", Pos: 9},
{Type: token.EOF, Literal: "", Pos: 10},
}
_, err := ParseLine(tokens)
if err == nil {
t.Fatal("expected error for trailing tokens after function body")
}
}