Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions py-plus-plus/examples/semantics/classes_and_when.pypp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down
26 changes: 13 additions & 13 deletions py-plus-plus/examples/semantics/control_flow.pypp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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 {
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion py-plus-plus/examples/syntax/functions.pypp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
2 changes: 2 additions & 0 deletions py-plus-plus/pyplusplus/ast_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions py-plus-plus/pyplusplus/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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
Expand Down
31 changes: 23 additions & 8 deletions py-plus-plus/pyplusplus/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
"""
Expand Down Expand Up @@ -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
Expand Down
39 changes: 38 additions & 1 deletion py-plus-plus/tests/test_parser.py
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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)
Expand Down
13 changes: 13 additions & 0 deletions py-plus-plus/tmp_for_else_test.py
Original file line number Diff line number Diff line change
@@ -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)