blob: 31ddd3f2d978abacbb1ea3a697a3a3c14e8ed3f9 [file] [log] [blame]
Sketch🕴️afb61d02026-02-28 19:34:37 +04001package matheval_test
2
3import (
4 "math"
5 "strings"
6 "testing"
7
8 "matheval/evaluator"
9 "matheval/lexer"
10 "matheval/parser"
11)
12
13// eval is a helper that runs the full pipeline: lexer → parser → evaluator.
14func eval(t *testing.T, input string) float64 {
15 t.Helper()
16 tokens, err := lexer.Tokenize(input)
17 if err != nil {
18 t.Fatalf("lexer error for %q: %v", input, err)
19 }
20 tree, err := parser.Parse(tokens)
21 if err != nil {
22 t.Fatalf("parser error for %q: %v", input, err)
23 }
24 result, err := evaluator.Eval(tree)
25 if err != nil {
26 t.Fatalf("evaluator error for %q: %v", input, err)
27 }
28 return result
29}
30
31// evalErr is a helper that expects the full pipeline to return an error.
32func evalErr(t *testing.T, input string) error {
33 t.Helper()
34 tokens, err := lexer.Tokenize(input)
35 if err != nil {
36 return err
37 }
38 tree, err := parser.Parse(tokens)
39 if err != nil {
40 return err
41 }
42 _, err = evaluator.Eval(tree)
43 return err
44}
45
46func assertApprox(t *testing.T, input string, expected, got float64) {
47 t.Helper()
48 if math.Abs(expected-got) > 1e-9 {
49 t.Errorf("%q: expected %v, got %v", input, expected, got)
50 }
51}
52
53// --- Basic arithmetic ---
54
55func TestIntegration_SingleNumber(t *testing.T) {
56 assertApprox(t, "42", 42, eval(t, "42"))
57}
58
59func TestIntegration_DecimalNumber(t *testing.T) {
60 assertApprox(t, "3.14", 3.14, eval(t, "3.14"))
61}
62
63func TestIntegration_LeadingDot(t *testing.T) {
64 assertApprox(t, ".5", 0.5, eval(t, ".5"))
65}
66
67func TestIntegration_Addition(t *testing.T) {
68 assertApprox(t, "1 + 2", 3, eval(t, "1 + 2"))
69}
70
71func TestIntegration_Subtraction(t *testing.T) {
72 assertApprox(t, "10 - 4", 6, eval(t, "10 - 4"))
73}
74
75func TestIntegration_Multiplication(t *testing.T) {
76 assertApprox(t, "3 * 7", 21, eval(t, "3 * 7"))
77}
78
79func TestIntegration_Division(t *testing.T) {
80 assertApprox(t, "10 / 4", 2.5, eval(t, "10 / 4"))
81}
82
83// --- Precedence and associativity ---
84
85func TestIntegration_PrecedenceMulOverAdd(t *testing.T) {
86 // 2 + 3 * 4 = 2 + 12 = 14
87 assertApprox(t, "2 + 3 * 4", 14, eval(t, "2 + 3 * 4"))
88}
89
90func TestIntegration_PrecedenceDivOverSub(t *testing.T) {
91 // 10 - 6 / 3 = 10 - 2 = 8
92 assertApprox(t, "10 - 6 / 3", 8, eval(t, "10 - 6 / 3"))
93}
94
95func TestIntegration_LeftAssociativitySub(t *testing.T) {
96 // 10 - 3 - 2 = (10 - 3) - 2 = 5
97 assertApprox(t, "10 - 3 - 2", 5, eval(t, "10 - 3 - 2"))
98}
99
100func TestIntegration_LeftAssociativityDiv(t *testing.T) {
101 // 24 / 4 / 3 = (24 / 4) / 3 = 2
102 assertApprox(t, "24 / 4 / 3", 2, eval(t, "24 / 4 / 3"))
103}
104
105// --- Parentheses ---
106
107func TestIntegration_ParensOverridePrecedence(t *testing.T) {
108 // (2 + 3) * 4 = 20
109 assertApprox(t, "(2 + 3) * 4", 20, eval(t, "(2 + 3) * 4"))
110}
111
112func TestIntegration_NestedParens(t *testing.T) {
113 // ((1 + 2)) = 3
114 assertApprox(t, "((1 + 2))", 3, eval(t, "((1 + 2))"))
115}
116
117func TestIntegration_DeeplyNestedParens(t *testing.T) {
118 // ((((((1 + 2)))))) = 3
119 assertApprox(t, "((((((1 + 2))))))", 3, eval(t, "((((((1 + 2))))))"))
120}
121
122func TestIntegration_ParensOnBothSides(t *testing.T) {
123 // (1 + 2) * (3 + 4) = 3 * 7 = 21
124 assertApprox(t, "(1 + 2) * (3 + 4)", 21, eval(t, "(1 + 2) * (3 + 4)"))
125}
126
127func TestIntegration_ParensNestedComplex(t *testing.T) {
128 // ((2 + 3) * (4 - 1)) / 5 = (5 * 3) / 5 = 3
129 assertApprox(t, "((2 + 3) * (4 - 1)) / 5", 3, eval(t, "((2 + 3) * (4 - 1)) / 5"))
130}
131
132// --- Complex expressions ---
133
134func TestIntegration_AllOperators(t *testing.T) {
135 // 1 + 2 * 3 - 4 / 2 = 1 + 6 - 2 = 5
136 assertApprox(t, "1 + 2 * 3 - 4 / 2", 5, eval(t, "1 + 2 * 3 - 4 / 2"))
137}
138
139func TestIntegration_LongChainedAddition(t *testing.T) {
140 // 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 = 55
141 assertApprox(t, "1+2+3+4+5+6+7+8+9+10", 55, eval(t, "1+2+3+4+5+6+7+8+9+10"))
142}
143
144func TestIntegration_LongChainedMixed(t *testing.T) {
145 // 2 * 3 + 4 * 5 - 6 / 2 + 1 = 6 + 20 - 3 + 1 = 24
146 assertApprox(t, "2 * 3 + 4 * 5 - 6 / 2 + 1", 24, eval(t, "2 * 3 + 4 * 5 - 6 / 2 + 1"))
147}
148
149// --- Floating point ---
150
151func TestIntegration_FloatArithmetic(t *testing.T) {
152 // 1.5 + 2.5 = 4.0
153 assertApprox(t, "1.5 + 2.5", 4.0, eval(t, "1.5 + 2.5"))
154}
155
156func TestIntegration_FloatDivision(t *testing.T) {
157 // 7 / 2 = 3.5
158 assertApprox(t, "7 / 2", 3.5, eval(t, "7 / 2"))
159}
160
161func TestIntegration_FloatPrecision(t *testing.T) {
162 // 0.1 + 0.2 ≈ 0.3 (within tolerance)
163 assertApprox(t, "0.1 + 0.2", 0.3, eval(t, "0.1 + 0.2"))
164}
165
166// --- Whitespace variations ---
167
168func TestIntegration_NoSpaces(t *testing.T) {
169 assertApprox(t, "1+2*3", 7, eval(t, "1+2*3"))
170}
171
172func TestIntegration_ExtraSpaces(t *testing.T) {
173 assertApprox(t, " 1 + 2 ", 3, eval(t, " 1 + 2 "))
174}
175
176func TestIntegration_TabsAndSpaces(t *testing.T) {
177 assertApprox(t, "1\t+\t2", 3, eval(t, "1\t+\t2"))
178}
179
180// --- Error cases ---
181
182func TestIntegration_DivisionByZero(t *testing.T) {
183 err := evalErr(t, "1 / 0")
184 if err == nil {
185 t.Fatal("expected division by zero error")
186 }
187 if !strings.Contains(err.Error(), "division by zero") {
188 t.Errorf("expected 'division by zero' in error, got: %v", err)
189 }
190}
191
192func TestIntegration_DivisionByZeroInSubExpr(t *testing.T) {
193 err := evalErr(t, "1 + 2 / 0")
194 if err == nil {
195 t.Fatal("expected division by zero error")
196 }
197}
198
199func TestIntegration_InvalidCharacter(t *testing.T) {
200 err := evalErr(t, "1 @ 2")
201 if err == nil {
202 t.Fatal("expected error for invalid character")
203 }
204}
205
206func TestIntegration_MismatchedParenLeft(t *testing.T) {
207 err := evalErr(t, "(1 + 2")
208 if err == nil {
209 t.Fatal("expected error for missing closing paren")
210 }
211}
212
213func TestIntegration_MismatchedParenRight(t *testing.T) {
214 err := evalErr(t, "1 + 2)")
215 if err == nil {
216 t.Fatal("expected error for unexpected closing paren")
217 }
218}
219
220func TestIntegration_EmptyParens(t *testing.T) {
221 err := evalErr(t, "()")
222 if err == nil {
223 t.Fatal("expected error for empty parentheses")
224 }
225}
226
227func TestIntegration_TrailingOperator(t *testing.T) {
228 err := evalErr(t, "1 +")
229 if err == nil {
230 t.Fatal("expected error for trailing operator")
231 }
232}
233
234func TestIntegration_LeadingOperator(t *testing.T) {
235 err := evalErr(t, "* 1")
236 if err == nil {
237 t.Fatal("expected error for leading operator")
238 }
239}
240
241func TestIntegration_ConsecutiveOperators(t *testing.T) {
242 err := evalErr(t, "1 + * 2")
243 if err == nil {
244 t.Fatal("expected error for consecutive operators")
245 }
246}
247
248func TestIntegration_EmptyInput(t *testing.T) {
249 // Empty string should produce only EOF, parser should error
250 err := evalErr(t, "")
251 if err == nil {
252 t.Fatal("expected error for empty input")
253 }
254}
255
256func TestIntegration_ConsecutiveNumbers(t *testing.T) {
257 err := evalErr(t, "1 2")
258 if err == nil {
259 t.Fatal("expected error for consecutive numbers without operator")
260 }
261}