blob: cab147243564577ba30886369e0a4fe3086268b0 [file] [log] [blame]
Sketch🕴️586f28e2026-02-28 19:14:46 +04001package evaluator
2
3import (
4 "math"
5 "matheval/ast"
6 "matheval/token"
Sketch🕴️2f999112026-02-28 20:58:17 +04007 "strings"
Sketch🕴️586f28e2026-02-28 19:14:46 +04008 "testing"
9)
10
Sketch🕴️2f999112026-02-28 20:58:17 +040011// --- Backward-compatible package-level Eval ---
12
Sketch🕴️586f28e2026-02-28 19:14:46 +040013func TestEvalNumberLit(t *testing.T) {
14 result, err := Eval(&ast.NumberLit{Value: 42.5})
15 if err != nil {
16 t.Fatalf("unexpected error: %v", err)
17 }
18 if result != 42.5 {
19 t.Fatalf("expected 42.5, got %v", result)
20 }
21}
22
23func TestEvalAddition(t *testing.T) {
24 node := &ast.BinaryExpr{
25 Op: token.Plus,
26 Left: &ast.NumberLit{Value: 1},
27 Right: &ast.NumberLit{Value: 2},
28 }
29 result, err := Eval(node)
30 if err != nil {
31 t.Fatalf("unexpected error: %v", err)
32 }
33 if result != 3 {
34 t.Fatalf("expected 3, got %v", result)
35 }
36}
37
38func TestEvalSubtraction(t *testing.T) {
39 node := &ast.BinaryExpr{
40 Op: token.Minus,
41 Left: &ast.NumberLit{Value: 10},
42 Right: &ast.NumberLit{Value: 4},
43 }
44 result, err := Eval(node)
45 if err != nil {
46 t.Fatalf("unexpected error: %v", err)
47 }
48 if result != 6 {
49 t.Fatalf("expected 6, got %v", result)
50 }
51}
52
53func TestEvalMultiplication(t *testing.T) {
54 node := &ast.BinaryExpr{
55 Op: token.Star,
56 Left: &ast.NumberLit{Value: 3},
57 Right: &ast.NumberLit{Value: 7},
58 }
59 result, err := Eval(node)
60 if err != nil {
61 t.Fatalf("unexpected error: %v", err)
62 }
63 if result != 21 {
64 t.Fatalf("expected 21, got %v", result)
65 }
66}
67
68func TestEvalDivision(t *testing.T) {
69 node := &ast.BinaryExpr{
70 Op: token.Slash,
71 Left: &ast.NumberLit{Value: 10},
72 Right: &ast.NumberLit{Value: 4},
73 }
74 result, err := Eval(node)
75 if err != nil {
76 t.Fatalf("unexpected error: %v", err)
77 }
78 if result != 2.5 {
79 t.Fatalf("expected 2.5, got %v", result)
80 }
81}
82
83func TestEvalDivisionByZero(t *testing.T) {
84 node := &ast.BinaryExpr{
85 Op: token.Slash,
86 Left: &ast.NumberLit{Value: 5},
87 Right: &ast.NumberLit{Value: 0},
88 }
89 _, err := Eval(node)
90 if err == nil {
91 t.Fatal("expected division by zero error")
92 }
93}
94
95func TestEvalNestedExpr(t *testing.T) {
96 // (1 + 2) * (8 / 4) = 3 * 2 = 6
97 node := &ast.BinaryExpr{
98 Op: token.Star,
99 Left: &ast.BinaryExpr{
100 Op: token.Plus,
101 Left: &ast.NumberLit{Value: 1},
102 Right: &ast.NumberLit{Value: 2},
103 },
104 Right: &ast.BinaryExpr{
105 Op: token.Slash,
106 Left: &ast.NumberLit{Value: 8},
107 Right: &ast.NumberLit{Value: 4},
108 },
109 }
110 result, err := Eval(node)
111 if err != nil {
112 t.Fatalf("unexpected error: %v", err)
113 }
114 if result != 6 {
115 t.Fatalf("expected 6, got %v", result)
116 }
117}
118
119func TestEvalDeeplyNested(t *testing.T) {
120 // ((2 + 3) * 4) - (10 / 5) = 20 - 2 = 18
121 node := &ast.BinaryExpr{
122 Op: token.Minus,
123 Left: &ast.BinaryExpr{
124 Op: token.Star,
125 Left: &ast.BinaryExpr{
126 Op: token.Plus,
127 Left: &ast.NumberLit{Value: 2},
128 Right: &ast.NumberLit{Value: 3},
129 },
130 Right: &ast.NumberLit{Value: 4},
131 },
132 Right: &ast.BinaryExpr{
133 Op: token.Slash,
134 Left: &ast.NumberLit{Value: 10},
135 Right: &ast.NumberLit{Value: 5},
136 },
137 }
138 result, err := Eval(node)
139 if err != nil {
140 t.Fatalf("unexpected error: %v", err)
141 }
142 if result != 18 {
143 t.Fatalf("expected 18, got %v", result)
144 }
145}
146
147func TestEvalDivisionByZeroInSubExpr(t *testing.T) {
148 // 1 + (2 / 0) — error should propagate
149 node := &ast.BinaryExpr{
150 Op: token.Plus,
151 Left: &ast.NumberLit{Value: 1},
152 Right: &ast.BinaryExpr{
153 Op: token.Slash,
154 Left: &ast.NumberLit{Value: 2},
155 Right: &ast.NumberLit{Value: 0},
156 },
157 }
158 _, err := Eval(node)
159 if err == nil {
160 t.Fatal("expected division by zero error from sub-expression")
161 }
162}
163
164func TestEvalFloatingPoint(t *testing.T) {
165 // 1.5 + 2.3 = 3.8
166 node := &ast.BinaryExpr{
167 Op: token.Plus,
168 Left: &ast.NumberLit{Value: 1.5},
169 Right: &ast.NumberLit{Value: 2.3},
170 }
171 result, err := Eval(node)
172 if err != nil {
173 t.Fatalf("unexpected error: %v", err)
174 }
175 if math.Abs(result-3.8) > 1e-12 {
176 t.Fatalf("expected 3.8, got %v", result)
177 }
178}
Sketch🕴️2f999112026-02-28 20:58:17 +0400179
180// --- Evaluator struct: Ident ---
181
182func TestEvaluator_Ident(t *testing.T) {
183 ev := New()
184 env := map[string]float64{"x": 7}
185 result, err := ev.Eval(&ast.Ident{Name: "x"}, env)
186 if err != nil {
187 t.Fatalf("unexpected error: %v", err)
188 }
189 if result != 7 {
190 t.Fatalf("expected 7, got %v", result)
191 }
192}
193
194func TestEvaluator_IdentUndefined(t *testing.T) {
195 ev := New()
196 _, err := ev.Eval(&ast.Ident{Name: "x"}, nil)
197 if err == nil {
198 t.Fatal("expected error for undefined variable")
199 }
200 if !strings.Contains(err.Error(), "undefined variable") {
201 t.Errorf("expected 'undefined variable' in error, got: %v", err)
202 }
203}
204
205func TestEvaluator_IdentInExpr(t *testing.T) {
206 ev := New()
207 env := map[string]float64{"x": 3, "y": 4}
208 // x + y
209 node := &ast.BinaryExpr{
210 Op: token.Plus,
211 Left: &ast.Ident{Name: "x"},
212 Right: &ast.Ident{Name: "y"},
213 }
214 result, err := ev.Eval(node, env)
215 if err != nil {
216 t.Fatalf("unexpected error: %v", err)
217 }
218 if result != 7 {
219 t.Fatalf("expected 7, got %v", result)
220 }
221}
222
223// --- Evaluator struct: Define + FuncCall ---
224
225func TestEvaluator_DefineAndCall(t *testing.T) {
226 ev := New()
227 // f(x) = x + 1
228 err := ev.Define(&ast.FuncDef{
229 Name: "f",
230 Params: []string{"x"},
231 Body: &ast.BinaryExpr{
232 Op: token.Plus,
233 Left: &ast.Ident{Name: "x"},
234 Right: &ast.NumberLit{Value: 1},
235 },
236 })
237 if err != nil {
238 t.Fatalf("unexpected error: %v", err)
239 }
240
241 // f(5) = 6
242 result, err := ev.Eval(&ast.FuncCall{
243 Name: "f",
244 Args: []ast.Node{&ast.NumberLit{Value: 5}},
245 }, nil)
246 if err != nil {
247 t.Fatalf("unexpected error: %v", err)
248 }
249 if result != 6 {
250 t.Fatalf("expected 6, got %v", result)
251 }
252}
253
254func TestEvaluator_DefineMultiParam(t *testing.T) {
255 ev := New()
256 // add(x, y) = x + y
257 err := ev.Define(&ast.FuncDef{
258 Name: "add",
259 Params: []string{"x", "y"},
260 Body: &ast.BinaryExpr{
261 Op: token.Plus,
262 Left: &ast.Ident{Name: "x"},
263 Right: &ast.Ident{Name: "y"},
264 },
265 })
266 if err != nil {
267 t.Fatalf("unexpected error: %v", err)
268 }
269
270 // add(3, 4) = 7
271 result, err := ev.Eval(&ast.FuncCall{
272 Name: "add",
273 Args: []ast.Node{
274 &ast.NumberLit{Value: 3},
275 &ast.NumberLit{Value: 4},
276 },
277 }, nil)
278 if err != nil {
279 t.Fatalf("unexpected error: %v", err)
280 }
281 if result != 7 {
282 t.Fatalf("expected 7, got %v", result)
283 }
284}
285
286func TestEvaluator_DefineRedefinitionError(t *testing.T) {
287 ev := New()
288 def := &ast.FuncDef{
289 Name: "f",
290 Params: []string{"x"},
291 Body: &ast.Ident{Name: "x"},
292 }
293 if err := ev.Define(def); err != nil {
294 t.Fatalf("unexpected error: %v", err)
295 }
296 err := ev.Define(def)
297 if err == nil {
298 t.Fatal("expected error for redefining function")
299 }
300 if !strings.Contains(err.Error(), "already defined") {
301 t.Errorf("expected 'already defined' in error, got: %v", err)
302 }
303}
304
305func TestEvaluator_UndefinedFunction(t *testing.T) {
306 ev := New()
307 _, err := ev.Eval(&ast.FuncCall{
308 Name: "f",
309 Args: []ast.Node{&ast.NumberLit{Value: 1}},
310 }, nil)
311 if err == nil {
312 t.Fatal("expected error for undefined function")
313 }
314 if !strings.Contains(err.Error(), "undefined function") {
315 t.Errorf("expected 'undefined function' in error, got: %v", err)
316 }
317}
318
319func TestEvaluator_WrongArgCount(t *testing.T) {
320 ev := New()
321 ev.Define(&ast.FuncDef{
322 Name: "f",
323 Params: []string{"x"},
324 Body: &ast.Ident{Name: "x"},
325 })
326 _, err := ev.Eval(&ast.FuncCall{
327 Name: "f",
328 Args: []ast.Node{
329 &ast.NumberLit{Value: 1},
330 &ast.NumberLit{Value: 2},
331 },
332 }, nil)
333 if err == nil {
334 t.Fatal("expected error for wrong argument count")
335 }
336 if !strings.Contains(err.Error(), "expects 1 arguments, got 2") {
337 t.Errorf("expected arg count error, got: %v", err)
338 }
339}
340
341func TestEvaluator_FuncCallInExpr(t *testing.T) {
342 ev := New()
343 // f(x) = x * 2
344 ev.Define(&ast.FuncDef{
345 Name: "f",
346 Params: []string{"x"},
347 Body: &ast.BinaryExpr{
348 Op: token.Star,
349 Left: &ast.Ident{Name: "x"},
350 Right: &ast.NumberLit{Value: 2},
351 },
352 })
353 // f(3) + 1 = 7
354 node := &ast.BinaryExpr{
355 Op: token.Plus,
356 Left: &ast.FuncCall{
357 Name: "f",
358 Args: []ast.Node{&ast.NumberLit{Value: 3}},
359 },
360 Right: &ast.NumberLit{Value: 1},
361 }
362 result, err := ev.Eval(node, nil)
363 if err != nil {
364 t.Fatalf("unexpected error: %v", err)
365 }
366 if result != 7 {
367 t.Fatalf("expected 7, got %v", result)
368 }
369}
370
371func TestEvaluator_NestedFuncCall(t *testing.T) {
372 ev := New()
373 // f(x) = x + 1
374 ev.Define(&ast.FuncDef{
375 Name: "f",
376 Params: []string{"x"},
377 Body: &ast.BinaryExpr{
378 Op: token.Plus,
379 Left: &ast.Ident{Name: "x"},
380 Right: &ast.NumberLit{Value: 1},
381 },
382 })
383 // f(f(1)) = f(2) = 3
384 node := &ast.FuncCall{
385 Name: "f",
386 Args: []ast.Node{
387 &ast.FuncCall{
388 Name: "f",
389 Args: []ast.Node{&ast.NumberLit{Value: 1}},
390 },
391 },
392 }
393 result, err := ev.Eval(node, nil)
394 if err != nil {
395 t.Fatalf("unexpected error: %v", err)
396 }
397 if result != 3 {
398 t.Fatalf("expected 3, got %v", result)
399 }
400}
401
402func TestEvaluator_CrossFunctionCall(t *testing.T) {
403 ev := New()
404 // double(x) = x * 2
405 ev.Define(&ast.FuncDef{
406 Name: "double",
407 Params: []string{"x"},
408 Body: &ast.BinaryExpr{
409 Op: token.Star,
410 Left: &ast.Ident{Name: "x"},
411 Right: &ast.NumberLit{Value: 2},
412 },
413 })
414 // quad(x) = double(double(x))
415 ev.Define(&ast.FuncDef{
416 Name: "quad",
417 Params: []string{"x"},
418 Body: &ast.FuncCall{
419 Name: "double",
420 Args: []ast.Node{
421 &ast.FuncCall{
422 Name: "double",
423 Args: []ast.Node{&ast.Ident{Name: "x"}},
424 },
425 },
426 },
427 })
428 // quad(3) = double(double(3)) = double(6) = 12
429 result, err := ev.Eval(&ast.FuncCall{
430 Name: "quad",
431 Args: []ast.Node{&ast.NumberLit{Value: 3}},
432 }, nil)
433 if err != nil {
434 t.Fatalf("unexpected error: %v", err)
435 }
436 if result != 12 {
437 t.Fatalf("expected 12, got %v", result)
438 }
439}
440
441func TestEvaluator_FuncNoParams(t *testing.T) {
442 ev := New()
443 // c() = 42
444 ev.Define(&ast.FuncDef{
445 Name: "c",
446 Params: []string{},
447 Body: &ast.NumberLit{Value: 42},
448 })
449 result, err := ev.Eval(&ast.FuncCall{
450 Name: "c",
451 Args: []ast.Node{},
452 }, nil)
453 if err != nil {
454 t.Fatalf("unexpected error: %v", err)
455 }
456 if result != 42 {
457 t.Fatalf("expected 42, got %v", result)
458 }
459}
460
461func TestEvaluator_ArgEvaluatedInCallerEnv(t *testing.T) {
462 ev := New()
463 // f(x) = x + 1
464 ev.Define(&ast.FuncDef{
465 Name: "f",
466 Params: []string{"x"},
467 Body: &ast.BinaryExpr{
468 Op: token.Plus,
469 Left: &ast.Ident{Name: "x"},
470 Right: &ast.NumberLit{Value: 1},
471 },
472 })
473 // Evaluate f(y) with y=10 in caller env
474 callerEnv := map[string]float64{"y": 10}
475 result, err := ev.Eval(&ast.FuncCall{
476 Name: "f",
477 Args: []ast.Node{&ast.Ident{Name: "y"}},
478 }, callerEnv)
479 if err != nil {
480 t.Fatalf("unexpected error: %v", err)
481 }
482 // f(10) = 11
483 if result != 11 {
484 t.Fatalf("expected 11, got %v", result)
485 }
486}