Step 5: Add Evaluator struct with env, Define, FuncCall support; backward-compat Eval preserved
diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go
index 447adbe..e44fe7d 100644
--- a/evaluator/evaluator.go
+++ b/evaluator/evaluator.go
@@ -6,19 +6,47 @@
 	"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) {
+// Evaluator holds function definitions and evaluates AST nodes.
+type Evaluator struct {
+	funcs map[string]*ast.FuncDef
+}
+
+// New creates a new Evaluator with no defined functions.
+func New() *Evaluator {
+	return &Evaluator{funcs: make(map[string]*ast.FuncDef)}
+}
+
+// Define registers a function definition.
+// Returns an error if a function with the same name is already defined.
+func (ev *Evaluator) Define(def *ast.FuncDef) error {
+	if _, exists := ev.funcs[def.Name]; exists {
+		return fmt.Errorf("function %q already defined", def.Name)
+	}
+	ev.funcs[def.Name] = def
+	return nil
+}
+
+// Eval evaluates an AST node with the given variable environment.
+// env maps variable names to their values; nil is treated as empty.
+func (ev *Evaluator) Eval(node ast.Node, env map[string]float64) (float64, error) {
 	switch n := node.(type) {
 	case *ast.NumberLit:
 		return n.Value, nil
 
+	case *ast.Ident:
+		if env != nil {
+			if val, ok := env[n.Name]; ok {
+				return val, nil
+			}
+		}
+		return 0, fmt.Errorf("undefined variable %q", n.Name)
+
 	case *ast.BinaryExpr:
-		left, err := Eval(n.Left)
+		left, err := ev.Eval(n.Left, env)
 		if err != nil {
 			return 0, err
 		}
-		right, err := Eval(n.Right)
+		right, err := ev.Eval(n.Right, env)
 		if err != nil {
 			return 0, err
 		}
@@ -39,7 +67,33 @@
 			return 0, fmt.Errorf("unknown operator: %v", n.Op)
 		}
 
+	case *ast.FuncCall:
+		def, ok := ev.funcs[n.Name]
+		if !ok {
+			return 0, fmt.Errorf("undefined function %q", n.Name)
+		}
+		if len(n.Args) != len(def.Params) {
+			return 0, fmt.Errorf("function %q expects %d arguments, got %d", n.Name, len(def.Params), len(n.Args))
+		}
+		// Evaluate arguments in caller's environment.
+		newEnv := make(map[string]float64, len(def.Params))
+		for i, param := range def.Params {
+			val, err := ev.Eval(n.Args[i], env)
+			if err != nil {
+				return 0, err
+			}
+			newEnv[param] = val
+		}
+		// Evaluate function body in new environment.
+		return ev.Eval(def.Body, newEnv)
+
 	default:
 		return 0, fmt.Errorf("unknown node type: %T", node)
 	}
 }
+
+// Eval is a backward-compatible package-level function.
+// It evaluates an AST node without any variable/function context.
+func Eval(node ast.Node) (float64, error) {
+	return New().Eval(node, nil)
+}