Skip to content

Commit e75cf13

Browse files
authored
Parser Improvements for Expressions (#57)
* Update version number for pypi * fix: Improve parser handling of parenthesized expressions and unary operations - Fix parsing of basic parenthesized expressions like '2 * (3 + 4)' - Add proper handling of unary minus operations - Update grammar rules to maintain correct operator precedence - Add comprehensive tests for expression evaluation Key changes: - Add LPAREN expression RPAREN to expression rule - Simplify primary_expr rule and fix handling of parentheses - Add dedicated uminus handling in evaluate_operation - Add TestExpressionEvaluation class with test cases for: - Nested unary operations - Complex nested expressions - Parenthesized expressions - Operator precedence
1 parent 9a1abfe commit e75cf13

File tree

4 files changed

+130
-10
lines changed

4 files changed

+130
-10
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
55

66
[project]
77
name = "think-lang"
8-
version = "0.1.9.rc2"
8+
version = "0.1.9.rc3"
99
description = "Think - A language for learning computational thinking"
1010
readme = "README.md"
1111
authors = [

tests/test_integration.py

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,4 +185,113 @@ def test_data_processing(self, interpreter, parser, capture_output):
185185
interpreter.execute(ast)
186186

187187
assert 4 == interpreter.state['result'][0]
188-
assert 5 == interpreter.state['result'][1]
188+
assert 5 == interpreter.state['result'][1]
189+
190+
class TestExpressionEvaluation:
191+
def test_nested_unary_operations(self, interpreter, parser, capture_output):
192+
"""Test handling of nested unary minus operations."""
193+
code = '''
194+
objective "Test nested unary operations"
195+
task "UnaryOps":
196+
step "Calculate":
197+
double_neg = - -42
198+
triple_neg = - - -17
199+
mixed = - -3.14
200+
print(double_neg)
201+
print(triple_neg)
202+
print(mixed)
203+
run "UnaryOps"'''
204+
205+
ast = parser.parse(code)
206+
interpreter.execute(ast)
207+
208+
assert interpreter.state['double_neg'] == 42
209+
assert interpreter.state['triple_neg'] == -17
210+
assert pytest.approx(interpreter.state['mixed']) == 3.14
211+
212+
def test_complex_nested_expressions(self, interpreter, parser, capture_output):
213+
"""Test evaluation of complex nested expressions."""
214+
code = '''
215+
objective "Test complex expressions"
216+
task "ComplexExpr":
217+
step "Calculate":
218+
a = 5
219+
b = 3
220+
c = 8
221+
d = 2
222+
nested1 = (a + b) * (c - d)
223+
nested2 = ((a * b) + c) / (d + 1)
224+
print(nested1)
225+
print(nested2)
226+
run "ComplexExpr"'''
227+
228+
ast = parser.parse(code)
229+
interpreter.execute(ast)
230+
231+
# (5 + 3) * (8 - 2) = 8 * 6 = 48
232+
assert interpreter.state['nested1'] == 48
233+
# ((5 * 3) + 8) / (2 + 1) = (15 + 8) / 3 = 23/3
234+
assert pytest.approx(interpreter.state['nested2']) == 7.666666666666667
235+
236+
def test_parenthesized_expressions(self, interpreter, parser, capture_output):
237+
"""Test evaluation of expressions with explicit parentheses."""
238+
code = '''
239+
objective "Test parenthesized expressions"
240+
task "ParenExpr":
241+
step "Calculate":
242+
simple = 2 * (3 + 4)
243+
complex = (1 + 2) * (3 - 4) / (5 + 6)
244+
nested = (((1 + 2) * 3) - 4)
245+
print(simple)
246+
print(complex)
247+
print(nested)
248+
run "ParenExpr"'''
249+
250+
ast = parser.parse(code)
251+
interpreter.execute(ast)
252+
253+
# 2 * (3 + 4) = 2 * 7 = 14
254+
assert interpreter.state['simple'] == 14
255+
# (1 + 2) * (3 - 4) / (5 + 6) = 3 * (-1) / 11 ≈ -0.2727
256+
assert pytest.approx(interpreter.state['complex']) == -0.2727272727272727
257+
# (((1 + 2) * 3) - 4) = ((3 * 3) - 4) = (9 - 4) = 5
258+
assert interpreter.state['nested'] == 5
259+
260+
def test_operator_precedence(self, interpreter, parser, capture_output):
261+
"""Test proper handling of operator precedence."""
262+
code = '''
263+
objective "Test operator precedence"
264+
task "Precedence":
265+
step "Calculate":
266+
mul_add = 2 + 3 * 4
267+
div_add = 10 + 15 / 3
268+
mixed = 2 * 3 + 4 * 5 / 2 - 1
269+
print(mul_add)
270+
print(div_add)
271+
print(mixed)
272+
run "Precedence"'''
273+
274+
ast = parser.parse(code)
275+
interpreter.execute(ast)
276+
277+
# 2 + 3 * 4 = 2 + 12 = 14 (not 20)
278+
assert interpreter.state['mul_add'] == 14
279+
# 10 + 15 / 3 = 10 + 5 = 15 (not 8.33...)
280+
assert interpreter.state['div_add'] == 15
281+
# 2 * 3 + 4 * 5 / 2 - 1 = 6 + 20/2 - 1 = 6 + 10 - 1 = 15
282+
assert interpreter.state['mixed'] == 15
283+
284+
def test_basic_parentheses(self, interpreter, parser, capture_output):
285+
"""Test basic parenthesized expression."""
286+
code = '''
287+
objective "Test basic parentheses"
288+
task "ParenExpr":
289+
step "Calculate":
290+
result = 2 * (3 + 4)
291+
print(result)
292+
run "ParenExpr"'''
293+
294+
ast = parser.parse(code)
295+
interpreter.execute(ast)
296+
297+
assert interpreter.state['result'] == 14

think/interpreter.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,10 +409,15 @@ def evaluate_expression(self, expr):
409409

410410
def evaluate_operation(self, operation):
411411
"""Evaluate a mathematical or logical operation"""
412-
left = self.evaluate_expression(operation['left'])
413-
right = self.evaluate_expression(operation['right'])
414412
op = operation['operator']
413+
414+
if op == 'uminus':
415+
operand = self.evaluate_expression(operation['operand'])
416+
return -operand
415417

418+
left = self.evaluate_expression(operation['left'])
419+
right = self.evaluate_expression(operation['right'])
420+
416421
# Handle string literals - extract actual string values
417422
if isinstance(left, dict) and left.get('type') == 'string_literal':
418423
left = left['value']

think/parser.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -417,23 +417,24 @@ def p_expression(self, p):
417417
"""
418418
expression : arithmetic_expr
419419
| comparison_expr
420+
| LPAREN expression RPAREN
420421
"""
421-
p[0] = p[1]
422+
if len(p) == 2:
423+
p[0] = p[1]
424+
else:
425+
p[0] = p[2]
422426

423427
def p_primary_expr(self, p):
424428
"""
425429
primary_expr : IDENTIFIER
426430
| NUMBER
427-
| numeric_literal
428431
| STRING
429432
| BOOL
430433
| FLOAT
431-
| MINUS FLOAT
432434
| list
433435
| function_call
434436
| LPAREN expression RPAREN
435437
| dict_literal
436-
| index_expression
437438
"""
438439
if len(p) == 2:
439440
if isinstance(p[1], str) and hasattr(p[1], 'type'):
@@ -442,6 +443,8 @@ def p_primary_expr(self, p):
442443
p[0] = {'type': 'string_literal', 'value': p[1]}
443444
else:
444445
p[0] = p[1]
446+
elif len(p) == 3:
447+
p[0] = -p[2]
445448
else:
446449
p[0] = p[2]
447450

@@ -479,8 +482,11 @@ def p_arithmetic_expr(self, p):
479482
"""
480483
if len(p) == 2:
481484
p[0] = p[1]
482-
else:
483-
p[0] = {'type': 'operation', 'left': p[1], 'operator': p[2], 'right': p[3]}
485+
elif len(p) == 4:
486+
if p[1] == '(':
487+
p[0] = p[2]
488+
else:
489+
p[0] = {'type': 'operation', 'left': p[1], 'operator': p[2], 'right': p[3]}
484490

485491
def p_comparison_expr(self, p):
486492
"""

0 commit comments

Comments
 (0)