diff --git a/CMakeLists.txt b/CMakeLists.txt index 71db2ff..129e922 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(fun VERSION 0.17.10 LANGUAGES C) +project(fun VERSION 0.18.0 LANGUAGES C) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/try_catch_finally.fun b/examples/try_catch_finally.fun new file mode 100755 index 0000000..37f53ba --- /dev/null +++ b/examples/try_catch_finally.fun @@ -0,0 +1,24 @@ +/* + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-03 + */ + +// Demonstration of try/catch/finally syntax support +// Note: current runtime does not yet implement exceptions; catch blocks are not executed. + +print("before try") +try + print("inside try body") +catch err + // This will not run yet (no exception raised, and runtime doesn't support throwing) + print("caught error: ") + print(err) +finally + print("finally always runs (syntactically)") +print("after try") diff --git a/examples/try_catch_with_error.fun b/examples/try_catch_with_error.fun new file mode 100755 index 0000000..0cf05be --- /dev/null +++ b/examples/try_catch_with_error.fun @@ -0,0 +1,29 @@ +/* + * This file is part of the Fun programming language. + * https://hanez.org/project/fun/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-10-03 + */ + +// Example: try/catch/finally with an intentional runtime error +// Note: The current runtime does not implement exceptions yet, so the catch +// block will not execute today. This example is prepared for when exceptions +// are supported. It also demonstrates the location of the error inside try. + +print("before try") +try + print("inside try: about to divide by zero") + number x = 1 / 0 // deliberate runtime error + print("this will not be reached due to the error above") +catch err + // Intended behavior (once exceptions are wired up): this block runs + // and 'err' contains an error object/stack trace + print("caught error: ") + print(err) +finally + print("finally runs regardless (syntactically, even without exceptions)") +print("after try") diff --git a/src/parser.c b/src/parser.c index 52dc3c6..09ce41f 100644 --- a/src/parser.c +++ b/src/parser.c @@ -3626,6 +3626,89 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, continue; } + /* try/catch/finally (syntax support; runtime exceptions not yet implemented) */ + if (starts_with(src, len, *pos, "try")) { + /* consume 'try' */ + *pos += 3; + /* end of header line */ + skip_to_eol(src, len, pos); + + /* parse try body at increased indent (if any) */ + int try_body_indent = 0; + size_t look_try = *pos; + if (read_line_start(src, len, &look_try, &try_body_indent) && try_body_indent > current_indent) { + parse_block(bc, src, len, pos, try_body_indent); + } else { + /* empty try body allowed */ + } + + /* Optional: catch and/or finally clauses at same indentation */ + int seen_catch = 0; + int seen_finally = 0; + for (;;) { + size_t look = *pos; + int look_indent = 0; + if (!read_line_start(src, len, &look, &look_indent)) break; /* EOF */ + if (look_indent != current_indent) break; /* different indentation -> stop */ + + if (!seen_catch && starts_with(src, len, look, "catch")) { + /* consume 'catch' */ + *pos = look + 5; + /* optional variable name */ + skip_spaces(src, len, pos); + char *ex_name = NULL; + size_t tmp = *pos; + if (read_identifier_into(src, len, &tmp, &ex_name)) { + *pos = tmp; + free(ex_name); + } + /* end of header line */ + skip_to_eol(src, len, pos); + + /* We currently don't have runtime exceptions: emit an unconditional jump over the catch body (so it's parsed but never executed) */ + int j_over = bytecode_add_instruction(bc, OP_JUMP, 0); + + /* parse catch body at increased indent (if any) */ + int catch_indent = 0; + size_t look_catch = *pos; + if (read_line_start(src, len, &look_catch, &catch_indent) && catch_indent > current_indent) { + parse_block(bc, src, len, pos, catch_indent); + } else { + /* empty catch body allowed */ + } + + /* patch jump to here (after catch body) */ + bytecode_set_operand(bc, j_over, bc->instr_count); + + seen_catch = 1; + continue; + } + + if (!seen_finally && starts_with(src, len, look, "finally")) { + /* consume 'finally' */ + *pos = look + 7; + /* end of header line */ + skip_to_eol(src, len, pos); + + /* parse finally body at increased indent (if any) */ + int finally_indent = 0; + size_t look_fin = *pos; + if (read_line_start(src, len, &look_fin, &finally_indent) && finally_indent > current_indent) { + parse_block(bc, src, len, pos, finally_indent); + } else { + /* empty finally body allowed */ + } + + seen_finally = 1; + continue; + } + + /* no recognized clause at this indentation */ + break; + } + continue; + } + /* otherwise: simple statement on this line */ parse_simple_statement(bc, src, len, pos); }