Step 6: Implement evaluator with TDD
- Eval(ast.Node) (float64, error) - recursive AST walker
- Handles all 4 operators: +, -, *, /
- Division by zero returns error
- Error propagation from sub-expressions
- 10 unit tests covering: single number, all operators, nested/deep
expressions, division by zero (direct and in sub-expr), floats
diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go
new file mode 100644
index 0000000..447adbe
--- /dev/null
+++ b/evaluator/evaluator.go
@@ -0,0 +1,45 @@
+package evaluator
+
+import (
+ "fmt"
+ "matheval/ast"
+ "matheval/token"
+)
+
+// Eval evaluates an AST node and returns the result.
+// Returns an error on division by zero.
+func Eval(node ast.Node) (float64, error) {
+ switch n := node.(type) {
+ case *ast.NumberLit:
+ return n.Value, nil
+
+ case *ast.BinaryExpr:
+ left, err := Eval(n.Left)
+ if err != nil {
+ return 0, err
+ }
+ right, err := Eval(n.Right)
+ if err != nil {
+ return 0, err
+ }
+
+ switch n.Op {
+ case token.Plus:
+ return left + right, nil
+ case token.Minus:
+ return left - right, nil
+ case token.Star:
+ return left * right, nil
+ case token.Slash:
+ if right == 0 {
+ return 0, fmt.Errorf("division by zero")
+ }
+ return left / right, nil
+ default:
+ return 0, fmt.Errorf("unknown operator: %v", n.Op)
+ }
+
+ default:
+ return 0, fmt.Errorf("unknown node type: %T", node)
+ }
+}
diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go
new file mode 100644
index 0000000..411463c
--- /dev/null
+++ b/evaluator/evaluator_test.go
@@ -0,0 +1,175 @@
+package evaluator
+
+import (
+ "math"
+ "matheval/ast"
+ "matheval/token"
+ "testing"
+)
+
+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)
+ }
+}