Skip to content

Commit 9028460

Browse files
committed
feat(parser): simplify if/elif/else end syntax
- Introduce and update and so entire if/elif/else chains are closed by a single . - Adjust pretty printer () to format IfThenElse/IfChain with inner blocks and a single trailing . - Refresh parser and pretty-print tests plus README examples to reflect the revised control-flow syntax. - Confirm all unit tests pass with the new behavior.
1 parent a72e9d6 commit 9028460

5 files changed

Lines changed: 110 additions & 33 deletions

File tree

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,14 @@ RPython uses explicit, static types for function parameters and return values. V
111111
```text
112112
if score >= 90:
113113
grade = "A";
114-
end elif score >= 80:
114+
elif score >= 80:
115115
grade = "B";
116-
end else:
116+
else:
117117
grade = "C";
118118
end
119119
```
120120

121-
Each branch in an if-chain has its own block terminated by `end`. The `elif` and `else` keywords follow the preceding `end`.
121+
A single `end` closes the entire if-chain. The `elif` and `else` keywords introduce new branches without requiring separate `end` markers.
122122

123123
### Loops
124124

@@ -147,7 +147,7 @@ Functions require type annotations for parameters and return type.
147147
def factorial(n: Int) -> Int:
148148
if n <= 1:
149149
return 1;
150-
end else:
150+
else:
151151
return n * factorial(n - 1);
152152
end;
153153
end;

examples/hello_io.rpy

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ val score = 85;
4545
var _ = print("Score 85 gets grade: ");
4646
if score >= 90:
4747
var _ = print_line("A");
48-
end elif score >= 80:
48+
elif score >= 80:
4949
var _ = print_line("B");
50-
end elif score >= 70:
50+
elif score >= 70:
5151
var _ = print_line("C");
52-
end else:
52+
else:
5353
var _ = print_line("F");
5454
end;
5555

@@ -89,9 +89,9 @@ var _ = print("Skip evens: ");
8989
for k in [0, 1, 2, 3, 4, 5]:
9090
if k == 0:
9191
continue;
92-
end elif k == 2:
92+
elif k == 2:
9393
continue;
94-
end elif k == 4:
94+
elif k == 4:
9595
continue;
9696
end;
9797
var _ = print(k);
@@ -103,7 +103,7 @@ var _ = print_line("=== 9. Functions ===");
103103
def factorial(n: Int) -> Int:
104104
if n <= 1:
105105
return 1;
106-
end else:
106+
else:
107107
return n * factorial(n - 1);
108108
end;
109109
end;

src/parser/parser_stmt.rs

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,11 @@ fn parse_if_else_statement(input: &str) -> IResult<&str, Statement> {
124124
tuple((
125125
keyword(IF_KEYWORD),
126126
preceded(multispace0, parse_expression),
127-
parse_block,
128-
opt(preceded(
129-
tuple((multispace0, keyword(ELSE_KEYWORD))),
130-
parse_block,
131-
)),
127+
parse_inner_block,
128+
opt(preceded(keyword(ELSE_KEYWORD), parse_inner_block)),
129+
preceded(multispace0, keyword(END_KEYWORD)),
132130
)),
133-
|(_, cond, then_block, else_block)| {
131+
|(_, cond, then_block, else_block, _)| {
134132
Statement::IfThenElse(
135133
Box::new(cond),
136134
Box::new(then_block),
@@ -143,13 +141,14 @@ fn parse_if_else_statement(input: &str) -> IResult<&str, Statement> {
143141
pub fn parse_if_chain_statement(input: &str) -> IResult<&str, Statement> {
144142
let (input_after_if, _) = keyword(IF_KEYWORD)(input)?;
145143
let (input_after_expr, cond_if) = parse_expression(input_after_if)?;
146-
let (input_after_block, block_if) = parse_block(input_after_expr)?;
144+
let (input_after_block, block_if) = parse_inner_block(input_after_expr)?;
147145

148146
let mut branches = vec![(Box::new(cond_if), Box::new(block_if))];
149147
let mut current_input = input_after_block;
150148

151149
loop {
152-
let result = tuple((keyword(ELIF_KEYWORD), parse_expression, parse_block))(current_input);
150+
let result =
151+
tuple((keyword(ELIF_KEYWORD), parse_expression, parse_inner_block))(current_input);
153152
match result {
154153
Ok((next_input, (_, cond_elif, block_elif))) => {
155154
branches.push((Box::new(cond_elif), Box::new(block_elif)));
@@ -158,9 +157,11 @@ pub fn parse_if_chain_statement(input: &str) -> IResult<&str, Statement> {
158157
Err(_) => break,
159158
}
160159
}
161-
let (input, else_branch) = opt(preceded(keyword(ELSE_KEYWORD), parse_block))(current_input)?;
160+
let (input_after_else, else_branch) =
161+
opt(preceded(keyword(ELSE_KEYWORD), parse_inner_block))(current_input)?;
162+
let (input_final, _) = preceded(multispace0, keyword(END_KEYWORD))(input_after_else)?;
162163
Ok((
163-
input,
164+
input_final,
164165
Statement::IfChain {
165166
branches,
166167
else_branch: else_branch.map(Box::new),
@@ -412,6 +413,31 @@ pub fn parse_block(input: &str) -> IResult<&str, Statement> {
412413
)(input)
413414
}
414415

416+
/// Parses a block without consuming `end`. Used for if/elif/else branches
417+
/// where only the final `end` closes the entire construct.
418+
pub fn parse_inner_block(input: &str) -> IResult<&str, Statement> {
419+
map(
420+
tuple((
421+
char::<&str, Error<&str>>(COLON_CHAR),
422+
multispace0,
423+
separated_list0(
424+
delimited(
425+
multispace0,
426+
char::<&str, Error<&str>>(SEMICOLON_CHAR),
427+
multispace0,
428+
),
429+
parse_statement,
430+
),
431+
opt(preceded(
432+
multispace0,
433+
char::<&str, Error<&str>>(SEMICOLON_CHAR),
434+
)),
435+
multispace0,
436+
)),
437+
|(_, _, stmts, _, _)| Statement::Block(stmts),
438+
)(input)
439+
}
440+
415441
pub fn parse_formal_argument(input: &str) -> IResult<&str, FormalArgument> {
416442
map(
417443
tuple((
@@ -552,7 +578,8 @@ mod tests {
552578
assert_eq!(parsed_if_only, expected_if_only);
553579

554580
// Cenário 2: Um "if" com "else", mas sem "elif".
555-
let input_if_else = "if False: x = 1; end else: y = 2; end";
581+
// New syntax: only one `end` at the very end
582+
let input_if_else = "if False: x = 1; else: y = 2; end";
556583
let expected_if_else = Statement::IfChain {
557584
branches: vec![(
558585
Box::new(Expression::CFalse),
@@ -570,7 +597,8 @@ mod tests {
570597
assert_eq!(parsed_if_else, expected_if_else);
571598

572599
// Cenário 3: "if", um "elif", e um "else".
573-
let input_if_elif_else = "if a: x = 1; end elif b: y = 2; end else: z = 3; end";
600+
// New syntax: only one `end` at the very end
601+
let input_if_elif_else = "if a: x = 1; elif b: y = 2; else: z = 3; end";
574602
let expected_if_elif_else = Statement::IfChain {
575603
branches: vec![
576604
(
@@ -597,7 +625,8 @@ mod tests {
597625
assert_eq!(parsed_if_elif_else, expected_if_elif_else);
598626

599627
// Cenário 4: "if" com múltiplos "elif" e sem "else".
600-
let input_multi_elif = "if a: x=1; end elif b: y=2; end elif c: z=3; end";
628+
// New syntax: only one `end` at the very end
629+
let input_multi_elif = "if a: x=1; elif b: y=2; elif c: z=3; end";
601630
let expected_multi_elif = Statement::IfChain {
602631
branches: vec![
603632
(

src/pretty_print/pretty_statements.rs

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ fn join(sep: Rc<Doc>, docs: Vec<Rc<Doc>>) -> Rc<Doc> {
1212
.unwrap_or_else(nil)
1313
}
1414

15+
/// Formats an inner block (without the trailing `end`).
16+
/// Used for if/elif/else branches where only one `end` closes the entire construct.
17+
fn inner_block_to_doc(stmts: &[Statement]) -> Rc<Doc> {
18+
let stmts_doc: Vec<Rc<Doc>> = stmts.iter().map(|s| s.to_doc()).collect();
19+
concat(
20+
text(" :"),
21+
nest(4, concat(hardline(), join(hardline(), stmts_doc))),
22+
)
23+
}
24+
1525
/// Implementa a conversão de nós de `Statement` da AST para a representação `Doc`.
1626
/// Cada variante do `enum Statement` é mapeada para um layout de formatação específico.
1727
impl ToDoc for Statement {
@@ -63,42 +73,80 @@ impl ToDoc for Statement {
6373
)
6474
}
6575
// Formata estruturas `if-then-else`.
76+
// Uses inner blocks (no `end`) and adds one `end` at the very end.
6677
Statement::IfThenElse(cond, then_branch, else_branch) => {
78+
// Extract statements from the then block
79+
let then_stmts = match then_branch.as_ref() {
80+
Statement::Block(stmts) => stmts,
81+
_ => return concat(text("if "), concat(cond.to_doc(), then_branch.to_doc())),
82+
};
83+
6784
let mut doc = concat(
6885
text("if "),
69-
concat(cond.to_doc(), concat(text(" "), then_branch.to_doc())),
86+
concat(cond.to_doc(), inner_block_to_doc(then_stmts)),
7087
);
88+
7189
// Adiciona a cláusula `else` apenas se ela existir.
7290
if let Some(else_b) = else_branch {
73-
doc = concat(doc, concat(text(" else "), else_b.to_doc()));
91+
let else_stmts = match else_b.as_ref() {
92+
Statement::Block(stmts) => stmts,
93+
_ => return concat(doc, concat(hardline(), text("end"))),
94+
};
95+
doc = concat(
96+
doc,
97+
concat(
98+
concat(hardline(), text("else")),
99+
inner_block_to_doc(else_stmts),
100+
),
101+
);
74102
}
75-
doc
103+
// Add the final `end`
104+
concat(doc, concat(hardline(), text("end")))
76105
}
77106
// Cadeia de if/elif/else: branches: Vec<(cond, bloco)>, else_branch opcional
107+
// Uses inner blocks and adds one `end` at the very end.
78108
Statement::IfChain {
79109
branches,
80110
else_branch,
81111
} => {
112+
// Helper to extract statements from a block
113+
fn get_stmts(block: &Statement) -> &[Statement] {
114+
match block {
115+
Statement::Block(stmts) => stmts,
116+
_ => &[],
117+
}
118+
}
119+
82120
// Primeiro branch usa 'if', subsequentes usam 'elif'
83121
let mut iter = branches.iter();
84122
if let Some((first_cond, first_block)) = iter.next() {
123+
let first_stmts = get_stmts(first_block.as_ref());
85124
let mut acc = concat(
86125
text("if "),
87-
concat(first_cond.to_doc(), concat(text(" "), first_block.to_doc())),
126+
concat(first_cond.to_doc(), inner_block_to_doc(first_stmts)),
88127
);
89128
for (cond, block) in iter {
129+
let block_stmts = get_stmts(block.as_ref());
90130
acc = concat(
91131
acc,
92132
concat(
93-
text(" elif "),
94-
concat(cond.to_doc(), concat(text(" "), block.to_doc())),
133+
concat(hardline(), text("elif ")),
134+
concat(cond.to_doc(), inner_block_to_doc(block_stmts)),
95135
),
96136
);
97137
}
98138
if let Some(else_b) = else_branch {
99-
acc = concat(acc, concat(text(" else "), else_b.to_doc()));
139+
let else_stmts = get_stmts(else_b.as_ref());
140+
acc = concat(
141+
acc,
142+
concat(
143+
concat(hardline(), text("else")),
144+
inner_block_to_doc(else_stmts),
145+
),
146+
);
100147
}
101-
acc
148+
// Add the final `end`
149+
concat(acc, concat(hardline(), text("end")))
102150
} else {
103151
// Sem branches: devolve bloco vazio
104152
text("")

tests/parser_tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,8 @@ mod statement_tests {
210210
assert_eq!(rest, "");
211211
assert_eq!(result, expected);
212212

213-
// Test with else
214-
let input = "if x > 0: y = 1; end else: y = 2; end";
213+
// Test with else (new syntax: one `end` at the very end)
214+
let input = "if x > 0: y = 1; else: y = 2; end";
215215
let expected = Statement::IfThenElse(
216216
Box::new(Expression::GT(
217217
Box::new(Expression::Var("x".to_string())),

0 commit comments

Comments
 (0)