Skip to content

Commit 37c981e

Browse files
committed
feat: add if-chain interpreter support and update documentation
Interpreter: - Add Statement::IfChain handler for if/elif/else chains README: - Clarify that RPython does not support comments in source code - Fix if/elif/else syntax examples (each branch needs own 'end') - Document semicolon requirement after 'end' for consecutive statements - Note that Maybe/Result constructors cannot be parsed from source yet - Update limitations list with comments and Maybe/Result parsing Examples: - Add comprehensive demo program (examples/hello_io.rpy) testing: variables, literals, arithmetic, comparisons, conditionals, while/for loops, break/continue, functions, and metabuiltins
1 parent 8432120 commit 37c981e

3 files changed

Lines changed: 213 additions & 42 deletions

File tree

README.md

Lines changed: 29 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,16 @@ The language surface resembles Python to lower the barrier for students, while t
4747
### Variables
4848

4949
```text
50-
val pi = 3.14159; # immutable binding
51-
var counter = 0; # mutable binding
52-
counter = counter + 1; # reassignment allowed for var
50+
val pi = 3.14159;
51+
var counter = 0;
52+
counter = counter + 1;
5353
```
5454

5555
- `val` declares an immutable variable; reassignment is a compile-time error.
5656
- `var` declares a mutable variable.
5757

58+
> **Note:** RPython does not support comments in source code. The examples in this README omit comments for accuracy.
59+
5860
### Literals
5961

6062
| Type | Example |
@@ -106,14 +108,14 @@ RPython uses explicit, static types for function parameters and return values. V
106108
```text
107109
if score >= 90:
108110
grade = "A";
109-
elif score >= 80:
111+
end elif score >= 80:
110112
grade = "B";
111-
else:
113+
end else:
112114
grade = "C";
113115
end
114116
```
115117

116-
All branches must be terminated with `end`.
118+
Each branch in an if-chain has its own block terminated by `end`. The `elif` and `else` keywords follow the preceding `end`.
117119

118120
### Loops
119121

@@ -142,14 +144,17 @@ Functions require type annotations for parameters and return type.
142144
def factorial(n: Int) -> Int:
143145
if n <= 1:
144146
return 1;
145-
end
146-
return n * factorial(n - 1);
147+
end else:
148+
return n * factorial(n - 1);
149+
end;
147150
end;
148151
149152
val result = factorial(5);
150-
asserttrue(result == 120, "5! should be 120");
153+
assertrue(result == 120, "5! should be 120");
151154
```
152155

156+
> **Syntax note:** Block statements (`if`, `while`, `for`, `def`) require a semicolon after the closing `end` when followed by additional statements at the same level.
157+
153158
### Lambdas
154159

155160
Anonymous functions can be assigned to variables or passed as arguments.
@@ -202,37 +207,21 @@ Metabuiltins are functions implemented in Rust and exposed to user code. They ha
202207

203208
## Error Handling
204209

205-
RPython provides two monadic types for representing optional or fallible values.
206-
207-
### `Maybe[T]`
210+
RPython provides two monadic types for representing optional or fallible values: `Maybe[T]` and `Result[Ok, Err]`.
208211

209-
```text
210-
val name = Just("Alice");
211-
val empty = Nothing;
212+
### Type Definitions
212213

213-
if isNothing(empty):
214-
print("No value");
215-
end
214+
- `Maybe[T]` — Optional value: `Just(value)` or `Nothing`
215+
- `Result[Ok, Err]` — Success/failure: `Ok(value)` or `Err(error)`
216216

217-
val unwrapped = name!; # unwrap: panics if Nothing
218-
```
217+
### Available Operations
219218

220-
### `Result[Ok, Err]`
221-
222-
```text
223-
def divide(a: Int, b: Int) -> Result[Int, String]:
224-
if b == 0:
225-
return Err("division by zero");
226-
end
227-
return Ok(a / b);
228-
end;
229-
230-
val result = divide(10, 2)?; # propagate Err automatically
231-
```
219+
- `isNothing(maybe)` — returns `True` if the value is `Nothing`
220+
- `isError(result)` — returns `True` if the value is `Err`
221+
- `unwrap(value)` — extracts the inner value (panics if `Nothing` or `Err`)
222+
- `tryUnwrap(value)` — extracts or propagates errors automatically
232223

233-
- `isError(result)` checks if a `Result` is an `Err`.
234-
- `isNothing(maybe)` checks if a `Maybe` is `Nothing`.
235-
- The `?` operator (propagate) returns early if the value is `Err`.
224+
> **Current limitation:** The parser does not yet support `Just()`, `Nothing`, `Ok()`, and `Err()` as expression syntax. These types exist in the AST and type system, and are used internally by the interpreter and type checker. Parser support for constructing these values from source code is planned for a future release.
236225
237226
---
238227

@@ -350,9 +339,11 @@ The interpreter reads from stdin and writes to stdout, making it suitable for au
350339

351340
1. **No module system:** all code lives in a single file.
352341
2. **No pattern matching:** ADT constructors can be built but not destructured.
353-
3. **No interactive REPL:** only file-based execution is supported.
354-
4. **Limited error messages:** parser and type checker errors are functional but not always user-friendly.
355-
5. **No tail-call optimization:** deep recursion may overflow the stack.
342+
3. **No comments:** the parser does not support `#` or any comment syntax.
343+
4. **No interactive REPL:** only file-based execution is supported.
344+
5. **Limited error messages:** parser and type checker errors are functional but not always user-friendly.
345+
6. **No tail-call optimization:** deep recursion may overflow the stack.
346+
7. **Maybe/Result constructors:** `Just()`, `Nothing`, `Ok()`, `Err()` cannot be parsed from source code yet.
356347

357348
---
358349

examples/hello_io.rpy

Lines changed: 151 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,151 @@
1-
var _ = print("Hello from R-Python");
2-
var _ = print("2 + 3 = ");
3-
var resultado = 2 + 3;
4-
var _ = print(resultado);
1+
var _ = print_line("=== RPython Demo ===");
2+
3+
var _ = print_line("=== 1. Variables ===");
4+
val pi = 3.14159;
5+
var counter = 1;
6+
var _ = print("pi = ");
7+
var _ = print_line(pi);
8+
var _ = print("counter = ");
9+
var _ = print_line(counter);
10+
11+
var _ = print_line("=== 2. Literals ===");
12+
var _ = print("Int: ");
13+
var _ = print_line(42);
14+
var _ = print("Real: ");
15+
var _ = print_line(3.14);
16+
var _ = print("String: ");
17+
var _ = print_line("hello");
18+
var _ = print("Bool: ");
19+
var _ = print_line(True);
20+
21+
var _ = print_line("=== 3. Arithmetic ===");
22+
var _ = print("10 + 5 = ");
23+
var _ = print_line(10 + 5);
24+
var _ = print("10 - 3 = ");
25+
var _ = print_line(10 - 3);
26+
var _ = print("6 * 7 = ");
27+
var _ = print_line(6 * 7);
28+
var _ = print("20 / 4 = ");
29+
var _ = print_line(20 / 4);
30+
31+
var _ = print_line("=== 4. Comparisons ===");
32+
var _ = print("5 == 5: ");
33+
var _ = print_line(5 == 5);
34+
var _ = print("10 > 3: ");
35+
var _ = print_line(10 > 3);
36+
var _ = print("True and True: ");
37+
var _ = print_line(True and True);
38+
var _ = print("False or True: ");
39+
var _ = print_line(False or True);
40+
var _ = print("not False: ");
41+
var _ = print_line(not False);
42+
43+
var _ = print_line("=== 5. Conditionals ===");
44+
val score = 85;
45+
var _ = print("Score 85 gets grade: ");
46+
if score >= 90:
47+
var _ = print_line("A");
48+
end elif score >= 80:
49+
var _ = print_line("B");
50+
end elif score >= 70:
51+
var _ = print_line("C");
52+
end else:
53+
var _ = print_line("F");
54+
end;
55+
56+
var _ = print_line("=== 6. While Loop ===");
57+
var _ = print("Counting: ");
58+
var i = 0;
59+
while i < 5:
60+
var _ = print(i);
61+
var _ = print(" ");
62+
i = i + 1;
63+
end;
64+
var _ = print_line("");
65+
66+
var _ = print_line("=== 7. For Loop ===");
67+
var _ = print("List items: ");
68+
val items = [10, 20, 30];
69+
for item in items:
70+
var _ = print(item);
71+
var _ = print(" ");
72+
end;
73+
var _ = print_line("");
74+
75+
var _ = print_line("=== 8. Break/Continue ===");
76+
var _ = print("Break at 3: ");
77+
var j = 0;
78+
while j < 10:
79+
if j == 3:
80+
break;
81+
end;
82+
var _ = print(j);
83+
var _ = print(" ");
84+
j = j + 1;
85+
end;
86+
var _ = print_line("");
87+
88+
var _ = print("Skip evens: ");
89+
for k in [0, 1, 2, 3, 4, 5]:
90+
if k == 0:
91+
continue;
92+
end elif k == 2:
93+
continue;
94+
end elif k == 4:
95+
continue;
96+
end;
97+
var _ = print(k);
98+
var _ = print(" ");
99+
end;
100+
var _ = print_line("");
101+
102+
var _ = print_line("=== 9. Functions ===");
103+
def factorial(n: Int) -> Int:
104+
if n <= 1:
105+
return 1;
106+
end else:
107+
return n * factorial(n - 1);
108+
end;
109+
end;
110+
111+
def add(a: Int, b: Int) -> Int:
112+
return a + b;
113+
end;
114+
115+
var _ = print("factorial(5) = ");
116+
var _ = print_line(factorial(5));
117+
var _ = print("add(17, 25) = ");
118+
var _ = print_line(add(17, 25));
119+
120+
var _ = print_line("=== 10. len() ===");
121+
var _ = print("len(hello) = ");
122+
var _ = print_line(len("hello"));
123+
var _ = print("len([1,2,3,4]) = ");
124+
var _ = print_line(len([1, 2, 3, 4]));
125+
126+
var _ = print_line("=== 11. Conversions ===");
127+
var _ = print("to_string(123) = ");
128+
var _ = print_line(to_string(123));
129+
var _ = print("to_int(456) = ");
130+
var _ = print_line(to_int("456"));
131+
var _ = print("to_real(42) = ");
132+
var _ = print_line(to_real(42));
133+
134+
var _ = print_line("=== 12. Fixed-point formatting ===");
135+
var _ = print("to_string_fixed(3.14159, 2) = ");
136+
var _ = print_line(to_string_fixed(3.14159, 2));
137+
138+
var _ = print_line("=== 13. String operations ===");
139+
var _ = print("str_concat: ");
140+
var _ = print_line(str_concat("Hello, ", "World!"));
141+
var _ = print("join([a,b,c], -) = ");
142+
var _ = print_line(join(["a", "b", "c"], "-"));
143+
144+
var _ = print_line("=== 14. Assertions ===");
145+
asserttrue(1 + 1 == 2, "1 + 1 should equal 2");
146+
assertfalse(1 > 2, "1 should not be greater than 2");
147+
asserteq(5 * 5, 25, "5 * 5 should equal 25");
148+
assertneq(10, 20, "10 should not equal 20");
149+
var _ = print_line("All assertions passed!");
150+
151+
var _ = print_line("=== Demo Complete ===");

src/interpreter/statement_execute.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,39 @@ pub fn execute(stmt: Statement, env: &Environment<Expression>) -> Result<Computa
327327
}
328328
}
329329

330+
Statement::IfChain {
331+
branches,
332+
else_branch,
333+
} => {
334+
for (cond, body) in branches {
335+
let value = match eval(*cond, &new_env)? {
336+
ExpressionResult::Value(expr) => expr,
337+
ExpressionResult::Propagate(expr) => {
338+
return Ok(Computation::PropagateError(expr, new_env))
339+
}
340+
};
341+
match value {
342+
Expression::CTrue => {
343+
return match *body {
344+
Statement::Block(stmts) => execute_if_block(stmts, &new_env),
345+
_ => execute(*body, &new_env),
346+
};
347+
}
348+
Expression::CFalse => continue,
349+
_ => return Err("Condition must evaluate to a boolean".to_string()),
350+
}
351+
}
352+
// No branch matched, try else
353+
if let Some(else_stmt) = else_branch {
354+
match *else_stmt {
355+
Statement::Block(stmts) => execute_if_block(stmts, &new_env),
356+
_ => execute(*else_stmt, &new_env),
357+
}
358+
} else {
359+
Ok(Computation::Continue(new_env))
360+
}
361+
}
362+
330363
Statement::Block(stmts) => {
331364
// new_env.push(); <- removing push()
332365
let result = execute_block(stmts, &new_env);

0 commit comments

Comments
 (0)