blob: cab147243564577ba30886369e0a4fe3086268b0 [file] [log] [blame]
package evaluator
import (
"math"
"matheval/ast"
"matheval/token"
"strings"
"testing"
)
// --- Backward-compatible package-level Eval ---
func TestEvalNumberLit(t *testing.T) {
result, err := Eval(&ast.NumberLit{Value: 42.5})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 42.5 {
t.Fatalf("expected 42.5, got %v", result)
}
}
func TestEvalAddition(t *testing.T) {
node := &ast.BinaryExpr{
Op: token.Plus,
Left: &ast.NumberLit{Value: 1},
Right: &ast.NumberLit{Value: 2},
}
result, err := Eval(node)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 3 {
t.Fatalf("expected 3, got %v", result)
}
}
func TestEvalSubtraction(t *testing.T) {
node := &ast.BinaryExpr{
Op: token.Minus,
Left: &ast.NumberLit{Value: 10},
Right: &ast.NumberLit{Value: 4},
}
result, err := Eval(node)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 6 {
t.Fatalf("expected 6, got %v", result)
}
}
func TestEvalMultiplication(t *testing.T) {
node := &ast.BinaryExpr{
Op: token.Star,
Left: &ast.NumberLit{Value: 3},
Right: &ast.NumberLit{Value: 7},
}
result, err := Eval(node)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 21 {
t.Fatalf("expected 21, got %v", result)
}
}
func TestEvalDivision(t *testing.T) {
node := &ast.BinaryExpr{
Op: token.Slash,
Left: &ast.NumberLit{Value: 10},
Right: &ast.NumberLit{Value: 4},
}
result, err := Eval(node)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 2.5 {
t.Fatalf("expected 2.5, got %v", result)
}
}
func TestEvalDivisionByZero(t *testing.T) {
node := &ast.BinaryExpr{
Op: token.Slash,
Left: &ast.NumberLit{Value: 5},
Right: &ast.NumberLit{Value: 0},
}
_, err := Eval(node)
if err == nil {
t.Fatal("expected division by zero error")
}
}
func TestEvalNestedExpr(t *testing.T) {
// (1 + 2) * (8 / 4) = 3 * 2 = 6
node := &ast.BinaryExpr{
Op: token.Star,
Left: &ast.BinaryExpr{
Op: token.Plus,
Left: &ast.NumberLit{Value: 1},
Right: &ast.NumberLit{Value: 2},
},
Right: &ast.BinaryExpr{
Op: token.Slash,
Left: &ast.NumberLit{Value: 8},
Right: &ast.NumberLit{Value: 4},
},
}
result, err := Eval(node)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 6 {
t.Fatalf("expected 6, got %v", result)
}
}
func TestEvalDeeplyNested(t *testing.T) {
// ((2 + 3) * 4) - (10 / 5) = 20 - 2 = 18
node := &ast.BinaryExpr{
Op: token.Minus,
Left: &ast.BinaryExpr{
Op: token.Star,
Left: &ast.BinaryExpr{
Op: token.Plus,
Left: &ast.NumberLit{Value: 2},
Right: &ast.NumberLit{Value: 3},
},
Right: &ast.NumberLit{Value: 4},
},
Right: &ast.BinaryExpr{
Op: token.Slash,
Left: &ast.NumberLit{Value: 10},
Right: &ast.NumberLit{Value: 5},
},
}
result, err := Eval(node)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 18 {
t.Fatalf("expected 18, got %v", result)
}
}
func TestEvalDivisionByZeroInSubExpr(t *testing.T) {
// 1 + (2 / 0) — error should propagate
node := &ast.BinaryExpr{
Op: token.Plus,
Left: &ast.NumberLit{Value: 1},
Right: &ast.BinaryExpr{
Op: token.Slash,
Left: &ast.NumberLit{Value: 2},
Right: &ast.NumberLit{Value: 0},
},
}
_, err := Eval(node)
if err == nil {
t.Fatal("expected division by zero error from sub-expression")
}
}
func TestEvalFloatingPoint(t *testing.T) {
// 1.5 + 2.3 = 3.8
node := &ast.BinaryExpr{
Op: token.Plus,
Left: &ast.NumberLit{Value: 1.5},
Right: &ast.NumberLit{Value: 2.3},
}
result, err := Eval(node)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if math.Abs(result-3.8) > 1e-12 {
t.Fatalf("expected 3.8, got %v", result)
}
}
// --- Evaluator struct: Ident ---
func TestEvaluator_Ident(t *testing.T) {
ev := New()
env := map[string]float64{"x": 7}
result, err := ev.Eval(&ast.Ident{Name: "x"}, env)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 7 {
t.Fatalf("expected 7, got %v", result)
}
}
func TestEvaluator_IdentUndefined(t *testing.T) {
ev := New()
_, err := ev.Eval(&ast.Ident{Name: "x"}, nil)
if err == nil {
t.Fatal("expected error for undefined variable")
}
if !strings.Contains(err.Error(), "undefined variable") {
t.Errorf("expected 'undefined variable' in error, got: %v", err)
}
}
func TestEvaluator_IdentInExpr(t *testing.T) {
ev := New()
env := map[string]float64{"x": 3, "y": 4}
// x + y
node := &ast.BinaryExpr{
Op: token.Plus,
Left: &ast.Ident{Name: "x"},
Right: &ast.Ident{Name: "y"},
}
result, err := ev.Eval(node, env)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 7 {
t.Fatalf("expected 7, got %v", result)
}
}
// --- Evaluator struct: Define + FuncCall ---
func TestEvaluator_DefineAndCall(t *testing.T) {
ev := New()
// f(x) = x + 1
err := ev.Define(&ast.FuncDef{
Name: "f",
Params: []string{"x"},
Body: &ast.BinaryExpr{
Op: token.Plus,
Left: &ast.Ident{Name: "x"},
Right: &ast.NumberLit{Value: 1},
},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// f(5) = 6
result, err := ev.Eval(&ast.FuncCall{
Name: "f",
Args: []ast.Node{&ast.NumberLit{Value: 5}},
}, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 6 {
t.Fatalf("expected 6, got %v", result)
}
}
func TestEvaluator_DefineMultiParam(t *testing.T) {
ev := New()
// add(x, y) = x + y
err := ev.Define(&ast.FuncDef{
Name: "add",
Params: []string{"x", "y"},
Body: &ast.BinaryExpr{
Op: token.Plus,
Left: &ast.Ident{Name: "x"},
Right: &ast.Ident{Name: "y"},
},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// add(3, 4) = 7
result, err := ev.Eval(&ast.FuncCall{
Name: "add",
Args: []ast.Node{
&ast.NumberLit{Value: 3},
&ast.NumberLit{Value: 4},
},
}, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 7 {
t.Fatalf("expected 7, got %v", result)
}
}
func TestEvaluator_DefineRedefinitionError(t *testing.T) {
ev := New()
def := &ast.FuncDef{
Name: "f",
Params: []string{"x"},
Body: &ast.Ident{Name: "x"},
}
if err := ev.Define(def); err != nil {
t.Fatalf("unexpected error: %v", err)
}
err := ev.Define(def)
if err == nil {
t.Fatal("expected error for redefining function")
}
if !strings.Contains(err.Error(), "already defined") {
t.Errorf("expected 'already defined' in error, got: %v", err)
}
}
func TestEvaluator_UndefinedFunction(t *testing.T) {
ev := New()
_, err := ev.Eval(&ast.FuncCall{
Name: "f",
Args: []ast.Node{&ast.NumberLit{Value: 1}},
}, nil)
if err == nil {
t.Fatal("expected error for undefined function")
}
if !strings.Contains(err.Error(), "undefined function") {
t.Errorf("expected 'undefined function' in error, got: %v", err)
}
}
func TestEvaluator_WrongArgCount(t *testing.T) {
ev := New()
ev.Define(&ast.FuncDef{
Name: "f",
Params: []string{"x"},
Body: &ast.Ident{Name: "x"},
})
_, err := ev.Eval(&ast.FuncCall{
Name: "f",
Args: []ast.Node{
&ast.NumberLit{Value: 1},
&ast.NumberLit{Value: 2},
},
}, nil)
if err == nil {
t.Fatal("expected error for wrong argument count")
}
if !strings.Contains(err.Error(), "expects 1 arguments, got 2") {
t.Errorf("expected arg count error, got: %v", err)
}
}
func TestEvaluator_FuncCallInExpr(t *testing.T) {
ev := New()
// f(x) = x * 2
ev.Define(&ast.FuncDef{
Name: "f",
Params: []string{"x"},
Body: &ast.BinaryExpr{
Op: token.Star,
Left: &ast.Ident{Name: "x"},
Right: &ast.NumberLit{Value: 2},
},
})
// f(3) + 1 = 7
node := &ast.BinaryExpr{
Op: token.Plus,
Left: &ast.FuncCall{
Name: "f",
Args: []ast.Node{&ast.NumberLit{Value: 3}},
},
Right: &ast.NumberLit{Value: 1},
}
result, err := ev.Eval(node, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 7 {
t.Fatalf("expected 7, got %v", result)
}
}
func TestEvaluator_NestedFuncCall(t *testing.T) {
ev := New()
// f(x) = x + 1
ev.Define(&ast.FuncDef{
Name: "f",
Params: []string{"x"},
Body: &ast.BinaryExpr{
Op: token.Plus,
Left: &ast.Ident{Name: "x"},
Right: &ast.NumberLit{Value: 1},
},
})
// f(f(1)) = f(2) = 3
node := &ast.FuncCall{
Name: "f",
Args: []ast.Node{
&ast.FuncCall{
Name: "f",
Args: []ast.Node{&ast.NumberLit{Value: 1}},
},
},
}
result, err := ev.Eval(node, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 3 {
t.Fatalf("expected 3, got %v", result)
}
}
func TestEvaluator_CrossFunctionCall(t *testing.T) {
ev := New()
// double(x) = x * 2
ev.Define(&ast.FuncDef{
Name: "double",
Params: []string{"x"},
Body: &ast.BinaryExpr{
Op: token.Star,
Left: &ast.Ident{Name: "x"},
Right: &ast.NumberLit{Value: 2},
},
})
// quad(x) = double(double(x))
ev.Define(&ast.FuncDef{
Name: "quad",
Params: []string{"x"},
Body: &ast.FuncCall{
Name: "double",
Args: []ast.Node{
&ast.FuncCall{
Name: "double",
Args: []ast.Node{&ast.Ident{Name: "x"}},
},
},
},
})
// quad(3) = double(double(3)) = double(6) = 12
result, err := ev.Eval(&ast.FuncCall{
Name: "quad",
Args: []ast.Node{&ast.NumberLit{Value: 3}},
}, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 12 {
t.Fatalf("expected 12, got %v", result)
}
}
func TestEvaluator_FuncNoParams(t *testing.T) {
ev := New()
// c() = 42
ev.Define(&ast.FuncDef{
Name: "c",
Params: []string{},
Body: &ast.NumberLit{Value: 42},
})
result, err := ev.Eval(&ast.FuncCall{
Name: "c",
Args: []ast.Node{},
}, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != 42 {
t.Fatalf("expected 42, got %v", result)
}
}
func TestEvaluator_ArgEvaluatedInCallerEnv(t *testing.T) {
ev := New()
// f(x) = x + 1
ev.Define(&ast.FuncDef{
Name: "f",
Params: []string{"x"},
Body: &ast.BinaryExpr{
Op: token.Plus,
Left: &ast.Ident{Name: "x"},
Right: &ast.NumberLit{Value: 1},
},
})
// Evaluate f(y) with y=10 in caller env
callerEnv := map[string]float64{"y": 10}
result, err := ev.Eval(&ast.FuncCall{
Name: "f",
Args: []ast.Node{&ast.Ident{Name: "y"}},
}, callerEnv)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
// f(10) = 11
if result != 11 {
t.Fatalf("expected 11, got %v", result)
}
}