From 6d099a60608fcc94a320c422190f775a4a2eaf4a Mon Sep 17 00:00:00 2001 From: Animemchik <109554523+Animemchik@users.noreply.github.com> Date: Mon, 20 Jul 2026 11:55:43 +0400 Subject: [PATCH] Hotfix --- .../examples/semantics/classes_and_when.pypp | 6 +-- .../examples/semantics/control_flow.pypp | 26 ++++++------- py-plus-plus/examples/syntax/functions.pypp | 2 +- py-plus-plus/pyplusplus/ast_nodes.py | 2 + py-plus-plus/pyplusplus/compiler.py | 14 +++++++ py-plus-plus/pyplusplus/parser.py | 31 +++++++++++---- py-plus-plus/tests/test_parser.py | 39 ++++++++++++++++++- py-plus-plus/tmp_for_else_test.py | 13 +++++++ 8 files changed, 107 insertions(+), 26 deletions(-) create mode 100644 py-plus-plus/tmp_for_else_test.py diff --git a/py-plus-plus/examples/semantics/classes_and_when.pypp b/py-plus-plus/examples/semantics/classes_and_when.pypp index 12bdc90..d147dcf 100644 --- a/py-plus-plus/examples/semantics/classes_and_when.pypp +++ b/py-plus-plus/examples/semantics/classes_and_when.pypp @@ -8,11 +8,11 @@ class Counter { if (new_value < 0) { raise ValueError("Value cannot be negative"); } - value = new_value; + return new_value; } - func increment() { - value = value + 1; + func increment(self) { + self.value = self.value + 1; } } diff --git a/py-plus-plus/examples/semantics/control_flow.pypp b/py-plus-plus/examples/semantics/control_flow.pypp index dcfe74b..93fb3fc 100644 --- a/py-plus-plus/examples/semantics/control_flow.pypp +++ b/py-plus-plus/examples/semantics/control_flow.pypp @@ -16,16 +16,16 @@ while (x > 0) { x = x - 1; } else { ## Executes when the while loop finishes normally without break. - print(`"finished while loop"`); + print("finished while loop"); } -print(`"sum 1..5 = {sum}"`); +print("sum 1..5 = {sum}"); for (i in [1, 2, 3]) { if (i == 2) { - print(`"found {i}"`); + print("found {i}"); continue; } - print(`"for item = {i}"`); + print("for item = {i}"); } else { ## Executes only if the for loop did not break. print("for loop completed normally"); @@ -38,16 +38,16 @@ match (value) { case 2: print("two"); case 3: - print(`"three"`); + print("three"); case _: print("other"); } try { x = 10 / 2; - print(`"division result = {x}"`); + print("division result = {x}"); } except ZeroDivisionError as err { - print(`"error: {err}"`); + print("error: {err}"); } else { print("no exception occurred"); } finally { @@ -57,16 +57,16 @@ try { ## Lambda-style functions square = func(x) { - return x * x; + x * x }; -print(`"square(6) = {square(6)}"`); +print("square(6) = {square(6)}"); make_adder = func(x) { - return func(y) { - return x + y; - }; + func(y) { + x + y + } }; -print(`"add 10 + 5 = {make_adder(10)(5)}"`); +print("add 10 + 5 = {make_adder(10)(5)}"); ## Inheritance example diff --git a/py-plus-plus/examples/syntax/functions.pypp b/py-plus-plus/examples/syntax/functions.pypp index 3b56307..c08e32b 100644 --- a/py-plus-plus/examples/syntax/functions.pypp +++ b/py-plus-plus/examples/syntax/functions.pypp @@ -9,6 +9,6 @@ result = add(3, 4); print("3 + 4 =", result); ## Lambda-style anonymous function using func syntax -make_multiplier = func(x) { return func(y) { return x * y; }; }; +make_multiplier = func(x) { func(y) { x * y } }; mult2 = make_multiplier(2); print("2 * 5 =", mult2(5)); diff --git a/py-plus-plus/pyplusplus/ast_nodes.py b/py-plus-plus/pyplusplus/ast_nodes.py index cb39ff5..7fccbec 100644 --- a/py-plus-plus/pyplusplus/ast_nodes.py +++ b/py-plus-plus/pyplusplus/ast_nodes.py @@ -144,10 +144,12 @@ class For(Stmt): """ `for` loop statement over an iterable. Example: `for item in collection { ... }`. + Supports optional `else` clause. """ target: ForLoopTarget iter: Expr body: BodyStmt + orelse: BodyStmt | None = None @dataclass(frozen=True) diff --git a/py-plus-plus/pyplusplus/compiler.py b/py-plus-plus/pyplusplus/compiler.py index 25115f0..d2267d4 100644 --- a/py-plus-plus/pyplusplus/compiler.py +++ b/py-plus-plus/pyplusplus/compiler.py @@ -460,6 +460,19 @@ def _rename_macro_names(node: Node, rename_map: Dict[str, str]) -> Node: node.body, statements, ) + statements = [] + for stmt in _body_to_list(node.orelse): + renamed_stmt = _rename_macro_names(stmt, rename_map) + if isinstance(renamed_stmt, list): + statements.extend(renamed_stmt) + elif isinstance(renamed_stmt, Stmt): + statements.append(renamed_stmt) + else: + raise TypeError(f"Expected Stmt or List[Stmt], got {type(renamed_stmt).__name__}") + orelse_stmt = _build_optional_body_stmt( + node.orelse, + statements, + ) if node.orelse is not None else None target = _rename_macro_names(target, rename_map) if not isinstance(target, ForLoopTarget): @@ -470,6 +483,7 @@ def _rename_macro_names(node: Node, rename_map: Dict[str, str]) -> Node: target=target, iter=iter, body=body_stmt, + orelse=orelse_stmt, ) if isinstance(node, With): alias = node.alias diff --git a/py-plus-plus/pyplusplus/parser.py b/py-plus-plus/pyplusplus/parser.py index 2af0d83..b007703 100644 --- a/py-plus-plus/pyplusplus/parser.py +++ b/py-plus-plus/pyplusplus/parser.py @@ -595,7 +595,11 @@ def parse_for(self) -> For: iterable = self.parse_expression() self.expect(TokenKind.RPAREN) body = self.parse_body() - return For(target=target, iter=iterable, body=body) + orelse = None + if self.match(TokenKind.KEYWORD, "else"): + self.advance() + orelse = self.parse_body() + return For(target=target, iter=iterable, body=body, orelse=orelse) def parse_while(self) -> While: """ @@ -2069,19 +2073,30 @@ def parse_trailer(self, node: Expr) -> Expr: node = Subscript(value=node, slice=slice_expr) continue if self.match(TokenKind.OP, "<"): + saved_pos = self.pos self.advance() elements: List[Expr] = [] + valid_generic = True if not self.match(TokenKind.OP, ">"): - elements.append(self.parse_expression()) - while self.match(TokenKind.COMMA): + try: + elements.append(self.parse_expression()) + except SyntaxError: + valid_generic = False + while valid_generic and self.match(TokenKind.COMMA): self.advance() if self.match(TokenKind.OP, ">"): break - elements.append(self.parse_expression()) - self.expect(TokenKind.OP, ">") - slice_expr = TupleExpr(elements=elements) if len(elements) != 1 else elements[0] - node = Subscript(value=node, slice=slice_expr) - continue + try: + elements.append(self.parse_expression()) + except SyntaxError: + valid_generic = False + break + if valid_generic and self.match(TokenKind.OP, ">"): + self.advance() + slice_expr = TupleExpr(elements=elements) if len(elements) != 1 else elements[0] + node = Subscript(value=node, slice=slice_expr) + continue + self.pos = saved_pos if self.match(TokenKind.DOT): self.advance() attr = self.expect(TokenKind.NAME).value diff --git a/py-plus-plus/tests/test_parser.py b/py-plus-plus/tests/test_parser.py index b51d9d4..e8deda3 100644 --- a/py-plus-plus/tests/test_parser.py +++ b/py-plus-plus/tests/test_parser.py @@ -1,7 +1,7 @@ from __future__ import annotations from pyplusplus.parser import parse_pyplusplus -from pyplusplus.ast_nodes import Assign, AugAssign, Await, Call, Constant, FromUse, Lambda, Match, MatchAs, MatchCase, MatchValue, Name, BinOp, Return, FunctionDef, BlockStmt, Subscript, TupleExpr, UnaryOp, ExprStmt +from pyplusplus.ast_nodes import Assign, AugAssign, Await, Call, Compare, Constant, FromUse, For, Lambda, ListExpr, Match, MatchAs, MatchCase, MatchValue, Name, BinOp, Return, FunctionDef, BlockStmt, Subscript, TupleExpr, UnaryOp, ExprStmt def test_parse_pyplusplus_simple_expression() -> None: source = "x = 1 + 2\n" @@ -49,6 +49,43 @@ def test_parse_pyplusplus_complex_expression() -> None: assert node.value.right.op == "/" +def test_parse_pyplusplus_comparison_after_name() -> None: + source = "result = (new_value < 0);\n" + tree = parse_pyplusplus(source) + node = tree.body[0] + + assert isinstance(node, Assign) + assert isinstance(node.value, Compare) + assert isinstance(node.value.left, Name) + assert node.value.left.id == "new_value" + assert node.value.ops == ["<"] + assert isinstance(node.value.comparators[0], Constant) + assert node.value.comparators[0].value == 0 + + +def test_parse_pyplusplus_for_else() -> None: + source = ( + 'sum = 0;\n' + 'for (i in [1, 2, 3]) {\n' + ' sum = sum + i;\n' + '} else {\n' + ' sum = sum + 10;\n' + '}\n' + ) + tree = parse_pyplusplus(source) + node = tree.body[1] + + assert isinstance(node, For) + assert isinstance(node.target, Name) + assert node.target.id == "i" + assert isinstance(node.iter, ListExpr) + assert node.orelse is not None + assert isinstance(node.orelse, BlockStmt) + assert len(node.orelse.body) == 1 + assert isinstance(node.orelse.body[0], Assign) + assert node.orelse.body[0].target.id == "sum" + + def test_parse_pyplusplus_complex_expression_2() -> None: source = "~(5 & 3) | (8 ^ 12) << 2\n" tree = parse_pyplusplus(source) diff --git a/py-plus-plus/tmp_for_else_test.py b/py-plus-plus/tmp_for_else_test.py new file mode 100644 index 0000000..13d1b9f --- /dev/null +++ b/py-plus-plus/tmp_for_else_test.py @@ -0,0 +1,13 @@ +from pyplusplus.parser import parse_pyplusplus +from pyplusplus.compiler import execute_pyplusplus + +source = '''sum = 0; +for (i in [1, 2, 3]) { + sum = sum + i; +} else { + sum = sum + 10; +} +print(sum); +''' +module = parse_pyplusplus(source) +execute_pyplusplus(module)