From 3f21434cd18e29097f4dbc1aa80a52dc651f6a69 Mon Sep 17 00:00:00 2001 From: hanez Date: Sun, 14 Sep 2025 03:42:11 +0200 Subject: [PATCH] else and else-if support. --- ...pression_test.fun => expressions_test.fun} | 19 +-- examples/have_fun.fun | 7 ++ examples/hello_world.fun | 7 -- examples/if_else_test.fun | 24 ++++ history/0.1.0-print.fun | 2 +- ...fiers-simple-assignments-globals-print.fun | 6 +- src/parser.c | 114 ++++++++++++++---- 7 files changed, 133 insertions(+), 46 deletions(-) rename examples/{expression_test.fun => expressions_test.fun} (56%) mode change 100644 => 100755 create mode 100755 examples/have_fun.fun delete mode 100755 examples/hello_world.fun create mode 100755 examples/if_else_test.fun diff --git a/examples/expression_test.fun b/examples/expressions_test.fun old mode 100644 new mode 100755 similarity index 56% rename from examples/expression_test.fun rename to examples/expressions_test.fun index 643cabc..c2a4125 --- a/examples/expression_test.fun +++ b/examples/expressions_test.fun @@ -1,8 +1,8 @@ #!/usr/bin/env fun -// Feature test for current Fun parser/VM. +// Expressions test: arithmetic, precedence, comparisons, logical ops, unary, parentheses. -print("=== Feature Test Start ===") +print("=== Expressions test start ===") // Typed declarations number a = 2 @@ -13,7 +13,10 @@ boolean flag = true // Arithmetic and precedence number sum = a + b * 5 - 1 -print(sum) // expect 2 + 3 * 5 - 1 = 16 +print(sum) // expect 2 + 3*5 - 1 = 16 + +number calc = (a + b) * (b - a + 1) +print(calc) // expect (2+3)*(3-2+1) = 5*2 = 10 // Unary operators number neg = -n @@ -31,10 +34,8 @@ print(true || false) // expect 1 (true) print(n > 5) // expect 1 print(s != "") // expect 1 -// Indentation-based nested if blocks (two spaces) -if (n >= 10) - print(n) // expect 10 - if (a + b == 5) - print(42) // expect 42 +// Mixed logical/relational +print(a < b && n >= 10) // expect 1 +print((a + b) == 5 && flag) // expect 1 -print("=== Feature Test End ===") +print("=== Expressions test end ===") diff --git a/examples/have_fun.fun b/examples/have_fun.fun new file mode 100755 index 0000000..af8bb43 --- /dev/null +++ b/examples/have_fun.fun @@ -0,0 +1,7 @@ +#!/usr/bin/env fun + +/* + * Have fun! in Fun + */ + + print("Hello, fun!") diff --git a/examples/hello_world.fun b/examples/hello_world.fun deleted file mode 100755 index 98f6b4b..0000000 --- a/examples/hello_world.fun +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env fun - -/* - * Hello world in Fun - */ - - print("Hello, world!") diff --git a/examples/if_else_test.fun b/examples/if_else_test.fun new file mode 100755 index 0000000..47a5588 --- /dev/null +++ b/examples/if_else_test.fun @@ -0,0 +1,24 @@ +#!/usr/bin/env fun + +// Test else / else if chains and nested if-blocks (two-space indentation) + +print("=== if/else-if/else test ===") + +number n = 42 + +if (n < 0) + print("neg") +else if (n == 0) + print("zero") +else if (n < 10) + print("small") +else + print("big") // expect: big + +// Nested if inside a true branch +if (n >= 10) + print(n) // expect: 42 + if (n == 42) + print("answer") // expect: answer + +print("=== if/else-if/else done ===") diff --git a/history/0.1.0-print.fun b/history/0.1.0-print.fun index f7cf60e..6c14f77 100755 --- a/history/0.1.0-print.fun +++ b/history/0.1.0-print.fun @@ -1 +1 @@ -print("Hello, world!") +print("Have, fun!") diff --git a/history/0.2.0-shebang-comments-strings-integers-booleans-identifiers-simple-assignments-globals-print.fun b/history/0.2.0-shebang-comments-strings-integers-booleans-identifiers-simple-assignments-globals-print.fun index 0fca767..800f1e6 100755 --- a/history/0.2.0-shebang-comments-strings-integers-booleans-identifiers-simple-assignments-globals-print.fun +++ b/history/0.2.0-shebang-comments-strings-integers-booleans-identifiers-simple-assignments-globals-print.fun @@ -1,9 +1,9 @@ #!/usr/bin/env fun /* - * Hello, world! + * Have, fun! */ -print("Hello, world!") +print("Have, fun!") -// Bye, world! +// Bye, no fun! diff --git a/src/parser.c b/src/parser.c index 20ca8e0..df4b1fe 100644 --- a/src/parser.c +++ b/src/parser.c @@ -629,36 +629,98 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, /* at same indent -> parse statement */ if (starts_with(src, len, *pos, "if")) { - *pos += 2; - /* require at least one space before condition if present */ - skip_spaces(src, len, pos); - /* condition: a single expression (booleans/ident/number/string) */ - if (!emit_expression(bc, src, len, pos)) { - /* no condition -> treat as false */ - int ci = bytecode_add_constant(bc, make_int(0)); - bytecode_add_instruction(bc, OP_LOAD_CONST, ci); - } - /* end of condition: ignore any trailing until EOL */ - skip_to_eol(src, len, pos); + int end_jumps[64]; + int end_count = 0; - /* conditional jump over the block */ - int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); - - /* parse nested block if next line is indented */ - size_t after_if = *pos; - int next_indent = 0; - if (read_line_start(src, len, pos, &next_indent)) { - if (next_indent > current_indent) { - /* parse body at increased indent */ - parse_block(bc, src, len, pos, next_indent); + for (;;) { + /* consume 'if' or 'else if' condition */ + if (starts_with(src, len, *pos, "if")) { + *pos += 2; } else { - /* empty if-body; keep *pos at start of that line (already set) */ + /* for 'else if' we arrive here with *pos already after 'if' */ + } + + /* require at least one space before condition if present */ + skip_spaces(src, len, pos); + if (!emit_expression(bc, src, len, pos)) { + /* no condition -> treat as false */ + int ci = bytecode_add_constant(bc, make_int(0)); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + } + /* end of condition: ignore any trailing until EOL */ + skip_to_eol(src, len, pos); + + /* conditional jump over this clause's block */ + int jmp_false = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + + /* parse nested block if next line is indented */ + int next_indent = 0; + size_t after_hdr = *pos; + if (read_line_start(src, len, pos, &next_indent)) { + if (next_indent > current_indent) { + /* parse body at increased indent */ + parse_block(bc, src, len, pos, next_indent); + } else { + /* empty body; keep *pos at start of that line */ + } + } else { + /* EOF -> empty body */ + } + + /* after body, unconditionally jump to end of the whole chain */ + int jmp_end = bytecode_add_instruction(bc, OP_JUMP, 0); + if (end_count < (int)(sizeof(end_jumps) / sizeof(end_jumps[0]))) { + end_jumps[end_count++] = jmp_end; + } else { + parser_fail(*pos, "Too many chained else/if clauses"); + return; + } + + /* patch false-jump target to start of next clause (or fallthrough) */ + bytecode_set_operand(bc, jmp_false, bc->instr_count); + + /* look for else or else if at the same indentation */ + size_t look = *pos; + int look_indent = 0; + if (!read_line_start(src, len, &look, &look_indent)) { + /* EOF: break and patch end jumps */ + break; + } + if (look_indent != current_indent) { + /* dedent or deeper indent means no 'else' clause here */ + break; + } + if (starts_with(src, len, look, "else")) { + /* consume 'else' */ + *pos = look + 4; + skip_spaces(src, len, pos); + + if (starts_with(src, len, *pos, "if")) { + /* else if -> consume 'if' token and continue loop to parse condition */ + *pos += 2; + continue; + } else { + /* plain else: parse its block and finish the chain */ + skip_to_eol(src, len, pos); + int else_indent = 0; + if (read_line_start(src, len, pos, &else_indent) && else_indent > current_indent) { + parse_block(bc, src, len, pos, else_indent); + } else { + /* empty else-body */ + } + /* end of chain after else */ + break; + } + } else { + /* next line is not an else/else if */ + break; } - } else { - /* EOF -> empty body */ } - /* patch jump to here */ - bytecode_set_operand(bc, jmp_false, bc->instr_count); + + /* patch all end-of-clause jumps to the end of the chain */ + for (int i = 0; i < end_count; ++i) { + bytecode_set_operand(bc, end_jumps[i], bc->instr_count); + } continue; }