blob: e44fe7dc38b77c0830c1fa796842b64ee8fb81e9 [file] [log] [blame]
Sketch🕴️586f28e2026-02-28 19:14:46 +04001package evaluator
2
3import (
4 "fmt"
5 "matheval/ast"
6 "matheval/token"
7)
8
Sketch🕴️2f999112026-02-28 20:58:17 +04009// Evaluator holds function definitions and evaluates AST nodes.
10type Evaluator struct {
11 funcs map[string]*ast.FuncDef
12}
13
14// New creates a new Evaluator with no defined functions.
15func New() *Evaluator {
16 return &Evaluator{funcs: make(map[string]*ast.FuncDef)}
17}
18
19// Define registers a function definition.
20// Returns an error if a function with the same name is already defined.
21func (ev *Evaluator) Define(def *ast.FuncDef) error {
22 if _, exists := ev.funcs[def.Name]; exists {
23 return fmt.Errorf("function %q already defined", def.Name)
24 }
25 ev.funcs[def.Name] = def
26 return nil
27}
28
29// Eval evaluates an AST node with the given variable environment.
30// env maps variable names to their values; nil is treated as empty.
31func (ev *Evaluator) Eval(node ast.Node, env map[string]float64) (float64, error) {
Sketch🕴️586f28e2026-02-28 19:14:46 +040032 switch n := node.(type) {
33 case *ast.NumberLit:
34 return n.Value, nil
35
Sketch🕴️2f999112026-02-28 20:58:17 +040036 case *ast.Ident:
37 if env != nil {
38 if val, ok := env[n.Name]; ok {
39 return val, nil
40 }
41 }
42 return 0, fmt.Errorf("undefined variable %q", n.Name)
43
Sketch🕴️586f28e2026-02-28 19:14:46 +040044 case *ast.BinaryExpr:
Sketch🕴️2f999112026-02-28 20:58:17 +040045 left, err := ev.Eval(n.Left, env)
Sketch🕴️586f28e2026-02-28 19:14:46 +040046 if err != nil {
47 return 0, err
48 }
Sketch🕴️2f999112026-02-28 20:58:17 +040049 right, err := ev.Eval(n.Right, env)
Sketch🕴️586f28e2026-02-28 19:14:46 +040050 if err != nil {
51 return 0, err
52 }
53
54 switch n.Op {
55 case token.Plus:
56 return left + right, nil
57 case token.Minus:
58 return left - right, nil
59 case token.Star:
60 return left * right, nil
61 case token.Slash:
62 if right == 0 {
63 return 0, fmt.Errorf("division by zero")
64 }
65 return left / right, nil
66 default:
67 return 0, fmt.Errorf("unknown operator: %v", n.Op)
68 }
69
Sketch🕴️2f999112026-02-28 20:58:17 +040070 case *ast.FuncCall:
71 def, ok := ev.funcs[n.Name]
72 if !ok {
73 return 0, fmt.Errorf("undefined function %q", n.Name)
74 }
75 if len(n.Args) != len(def.Params) {
76 return 0, fmt.Errorf("function %q expects %d arguments, got %d", n.Name, len(def.Params), len(n.Args))
77 }
78 // Evaluate arguments in caller's environment.
79 newEnv := make(map[string]float64, len(def.Params))
80 for i, param := range def.Params {
81 val, err := ev.Eval(n.Args[i], env)
82 if err != nil {
83 return 0, err
84 }
85 newEnv[param] = val
86 }
87 // Evaluate function body in new environment.
88 return ev.Eval(def.Body, newEnv)
89
Sketch🕴️586f28e2026-02-28 19:14:46 +040090 default:
91 return 0, fmt.Errorf("unknown node type: %T", node)
92 }
93}
Sketch🕴️2f999112026-02-28 20:58:17 +040094
95// Eval is a backward-compatible package-level function.
96// It evaluates an AST node without any variable/function context.
97func Eval(node ast.Node) (float64, error) {
98 return New().Eval(node, nil)
99}