From e49ebba34611b41edd4741aab312d2acaf934a10 Mon Sep 17 00:00:00 2001 From: samsonium Date: Wed, 21 Oct 2026 04:00:03 +0300 Subject: [PATCH 01/24] feat: add new tests --- .gitignore | 1 + CMakeLists.txt | 117 ++++----------------- tests/programs/neg_missing_operand.fzs | 2 + tests/programs/neg_missing_semicolon.fzs | 3 + tests/programs/neg_unclosed_block.fzs | 4 + tests/programs/neg_unterminated_string.fzs | 2 + tests/programs/pos_arithmetic.fzs | 7 ++ tests/programs/pos_class.fzs | 25 +++++ tests/programs/pos_comments_strings.fzs | 5 + tests/programs/pos_conditions.fzs | 21 ++++ tests/programs/pos_functions.fzs | 21 ++++ tests/programs/pos_null.fzs | 23 ++++ tests/programs/pos_variables.fzs | 13 +++ 13 files changed, 145 insertions(+), 99 deletions(-) create mode 100644 tests/programs/neg_missing_operand.fzs create mode 100644 tests/programs/neg_missing_semicolon.fzs create mode 100644 tests/programs/neg_unclosed_block.fzs create mode 100644 tests/programs/neg_unterminated_string.fzs create mode 100644 tests/programs/pos_arithmetic.fzs create mode 100644 tests/programs/pos_class.fzs create mode 100644 tests/programs/pos_comments_strings.fzs create mode 100644 tests/programs/pos_conditions.fzs create mode 100644 tests/programs/pos_functions.fzs create mode 100644 tests/programs/pos_null.fzs create mode 100644 tests/programs/pos_variables.fzs diff --git a/.gitignore b/.gitignore index bd3a3a7..3cc2666 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ !/exl !/include !/src +!/tests # Files !/.clang-format diff --git a/CMakeLists.txt b/CMakeLists.txt index f5a00e5..0ec798a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,108 +35,27 @@ add_executable(fz src/vm/main.c) # -# ========== Compilation tests ========== +# ========== Tests ========== # enable_testing() -add_test( - NAME compile_hw - COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/hw.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/hw.fzs" -) -add_test( - NAME compile_print_empty - COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/print_empty.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/print_empty.fzs" -) -add_test( - NAME compile_problematic_hw - COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/problematic_hw.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/problematic_hw.fzs" -) -add_test( - NAME compile_base_math - COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/base_math.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/base_math.fzs" -) -add_test( - NAME compile_variables - COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/variables.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/variables.fzs" -) -add_test( - NAME compile_conditions - COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/conditions.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/conditions.fzs" -) -add_test( - NAME compile_fn - COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/fn.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/fn.fzs" -) -add_test( - NAME compile_comment - COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/comment.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/comment.fzs" -) -add_test( - NAME compile_class - COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/class.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/class.fzs" -) -add_test( - NAME compile_raii - COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/raii.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/raii.fzs" -) -add_test( - NAME compile_borrow - COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/borrow.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/borrow.fzs" -) -add_test( - NAME compile_hard_1 - COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/hard-1.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/exl/hard-1.fzs" -) +file(GLOB TEST_PROGS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "tests/programs/*.fzs") +foreach (TEST_FILE IN LISTS TEST_PROGS) + get_filename_component(TEST_NAME "${TEST_FILE}" NAME_WE) + + add_test( + NAME "progs_compile_${TEST_NAME}" + COMMAND fzc -o "p_${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME}.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/${TEST_FILE}" + ) + add_test( + NAME "progs_runtime_${TEST_NAME}" + COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/p_${TEST_NAME}.fzb" + ) -set_tests_properties(compile_problematic_hw PROPERTIES WILL_FAIL TRUE) - -# -# ========== Runtime tests ========== -# - -add_test( - NAME runtime_hw - COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/hw.fzb" -) -add_test( - NAME runtime_print_empty - COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/print_empty.fzb" -) -add_test( - NAME runtime_base_math - COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/base_math.fzb" -) -add_test( - NAME runtime_variables - COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/variables.fzb" -) -add_test( - NAME runtime_conditions - COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/conditions.fzb" -) -add_test( - NAME runtime_fn - COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/fn.fzb" -) -add_test( - NAME runtime_comment - COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/comment.fzb" -) -add_test( - NAME runtime_class - COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/class.fzb" -) -add_test( - NAME runtime_raii - COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/raii.fzb" -) -add_test( - NAME runtime_borrow - COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/borrow.fzb" -) -add_test( - NAME runtime_hard_1 - COMMAND fz "${CMAKE_CURRENT_BINARY_DIR}/hard-1.fzb" -) + if (TEST_NAME MATCHES "neg_") + set_tests_properties("progs_compile_${TEST_NAME}" PROPERTIES WILL_FAIL TRUE) + set_tests_properties("progs_runtime_${TEST_NAME}" PROPERTIES WILL_FAIL TRUE) + endif() +endforeach() diff --git a/tests/programs/neg_missing_operand.fzs b/tests/programs/neg_missing_operand.fzs new file mode 100644 index 0000000..5279332 --- /dev/null +++ b/tests/programs/neg_missing_operand.fzs @@ -0,0 +1,2 @@ +printline(1 + ); + diff --git a/tests/programs/neg_missing_semicolon.fzs b/tests/programs/neg_missing_semicolon.fzs new file mode 100644 index 0000000..3f84aae --- /dev/null +++ b/tests/programs/neg_missing_semicolon.fzs @@ -0,0 +1,3 @@ +int value = 42 +printline(value); + diff --git a/tests/programs/neg_unclosed_block.fzs b/tests/programs/neg_unclosed_block.fzs new file mode 100644 index 0000000..61a5439 --- /dev/null +++ b/tests/programs/neg_unclosed_block.fzs @@ -0,0 +1,4 @@ +if (true) +{ + printline("missing closing brace"); + diff --git a/tests/programs/neg_unterminated_string.fzs b/tests/programs/neg_unterminated_string.fzs new file mode 100644 index 0000000..9bcfd80 --- /dev/null +++ b/tests/programs/neg_unterminated_string.fzs @@ -0,0 +1,2 @@ +printline("unterminated string); + diff --git a/tests/programs/pos_arithmetic.fzs b/tests/programs/pos_arithmetic.fzs new file mode 100644 index 0000000..ed1907e --- /dev/null +++ b/tests/programs/pos_arithmetic.fzs @@ -0,0 +1,7 @@ +// Arithmetic precedence and operators. +printline(2 + 2 * 2); +printline(-1 + 1); +printline(2 ^ 2); +printline(4 % 2); +printline(10 / 2); + diff --git a/tests/programs/pos_class.fzs b/tests/programs/pos_class.fzs new file mode 100644 index 0000000..5e01704 --- /dev/null +++ b/tests/programs/pos_class.fzs @@ -0,0 +1,25 @@ +class Point +{ + int x; + int y; + + fn move(int dx, int dy) + { + this.x = this.x + dx; + this.y = this.y + dy; + } +} + +Point point = new Point; +point.x = 1; +point.y = 2; +point.move(9, 8); + +if (point.x == 10 && point.y == 10) + printline("class method passed"); +else +{ + printline("class method failed"); + exit(1); +} + diff --git a/tests/programs/pos_comments_strings.fzs b/tests/programs/pos_comments_strings.fzs new file mode 100644 index 0000000..7c12831 --- /dev/null +++ b/tests/programs/pos_comments_strings.fzs @@ -0,0 +1,5 @@ +// Line comments are ignored by the parser. +printline('single-quoted string');;;;; +printline(""); +printline("double-quoted string"); + diff --git a/tests/programs/pos_conditions.fzs b/tests/programs/pos_conditions.fzs new file mode 100644 index 0000000..981b441 --- /dev/null +++ b/tests/programs/pos_conditions.fzs @@ -0,0 +1,21 @@ +bool flag = true; + +if (!flag) +{ + printline("unexpected false"); + exit(1); +} +else if (2 + 2 >= 4) +{ + printline("condition passed"); +} +else +{ + printline("unexpected branch"); + exit(1); +} + +{ + printline("standalone block"); +} + diff --git a/tests/programs/pos_functions.fzs b/tests/programs/pos_functions.fzs new file mode 100644 index 0000000..7ec5cdb --- /dev/null +++ b/tests/programs/pos_functions.fzs @@ -0,0 +1,21 @@ +fn fib(int n) int +{ + if (n <= 1) + return n; + + return fib(n - 1) + fib(n - 2); +} + +fn sum(uint a, uint b) uint +{ + return a + b; +} + +if (fib(7) == 13 && sum(2, 2) == 4) + printline("functions passed"); +else +{ + printline("functions failed"); + exit(1); +} + diff --git a/tests/programs/pos_null.fzs b/tests/programs/pos_null.fzs new file mode 100644 index 0000000..877bdb2 --- /dev/null +++ b/tests/programs/pos_null.fzs @@ -0,0 +1,23 @@ +class Item +{ + int value; +} + +fn maybe_item(bool create) Item +{ + if (create) + return new Item; + + return null; +} + +Item empty = maybe_item(false); +Item present = maybe_item(true); +if (empty == null && present != null) + printline("null handling passed"); +else +{ + printline("null handling failed"); + exit(1); +} + diff --git a/tests/programs/pos_variables.fzs b/tests/programs/pos_variables.fzs new file mode 100644 index 0000000..b9d3277 --- /dev/null +++ b/tests/programs/pos_variables.fzs @@ -0,0 +1,13 @@ +uint work_it = 21 * 2 ^ 2; +int make_it = 10; +u64 do_it; +do_it = 15; +char make_us; +str floyd = "hello, Pink Floyd!"; +bool flag = true; + +printline(work_it); +printline(make_it + do_it); +printline(floyd); +printline(flag); + From 138d503bcea54436d4e144a06df714190edffadb Mon Sep 17 00:00:00 2001 From: samsonium Date: Wed, 21 Oct 2026 04:00:48 +0300 Subject: [PATCH 02/24] feat: add examples with descriptions --- exl/00_variables.fzs | 32 ++++++++++++++++++++++++++++++++ exl/01_conditions.fzs | 30 ++++++++++++++++++++++++++++++ exl/02_functions.fzs | 30 ++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 exl/00_variables.fzs create mode 100644 exl/01_conditions.fzs create mode 100644 exl/02_functions.fzs diff --git a/exl/00_variables.fzs b/exl/00_variables.fzs new file mode 100644 index 0000000..fe3b019 --- /dev/null +++ b/exl/00_variables.fzs @@ -0,0 +1,32 @@ +// There are two ways to work with variables +// 1. Declare variable: +str someString = "world, hello!"; + +// 2. Change variable value: +someString = "hello, world!"; + +// Variables helps you store values to use it later +int a = 2; +int b = a + a * a; +int c = a * b; + +printline("Now we got 12:"); +printline(c); + +// Also, there is many different types to help your program +// work with many things: +int signedInteger; +uint unsignedInteger +str string; +bool boolean; + +i8 integer1; // 8-byte int +i16 integer2; // 16-byte int +i32 integer3; // 32-byte int +i64 integer4; // 64-byte int + +u8 integer5; // unsigned 8-byte integer +u16 integer6; // unsigned 16-byte integer +u32 integer7; // unsigned 32-byte integer +u64 integer8; // unsigned 64-byte integer + diff --git a/exl/01_conditions.fzs b/exl/01_conditions.fzs new file mode 100644 index 0000000..1a73b9d --- /dev/null +++ b/exl/01_conditions.fzs @@ -0,0 +1,30 @@ +// When you need to check some value +// a conditional statements might help you + +int valueToCheck = 2 + 2; +if (valueToCheck == 4) +{ + printline("Yay! 2 + 2 = 4"); +} + +// Conditional statement uses condition in pairs +// after `if` keyword and executes command(s) after +// if condition turns out to be true + +// Otherwise, to execute block when condition turns +// out to be false, you can use `else` keyword: + +int age = 30; + +if (age <= 20) + printline("You're too young"); +else +{ + age = 20; + printline("You looks too young!"); +} + +// NOTE: If you need to execute only one statement +// inside `if` or `else` block, then the curly +// braces can be omitted. + diff --git a/exl/02_functions.fzs b/exl/02_functions.fzs new file mode 100644 index 0000000..1c4ea5b --- /dev/null +++ b/exl/02_functions.fzs @@ -0,0 +1,30 @@ +// If you have some code section which is +// duplicated in program several times +// then it means your program needs some +// functions! + +fn doSomeWork() +{ + printline("Some work"); +} + +// If the function makes some kind of +// calculations then you should specify +// the result type + +fn sum(int a, int b) int +{ + return a + b; +} + +// The `return` keyword can be used to +// actually return the value from the +// function. Also, you can specify list +// of arguments that you will use inside +// the function + +int first = 5; +int second = 6; +int result = sum(first, second); +printline(result); + From c8f3cd61f40f3acba74101c4752cdb0875e59dd3 Mon Sep 17 00:00:00 2001 From: samsonium Date: Wed, 21 Oct 2026 04:01:23 +0300 Subject: [PATCH 03/24] ref: add `build-release` make target for building c/vm without sanitizer libs --- Makefile | 8 +++++--- README.md | 2 +- README.ru.md | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 304ff91..4e4c8b9 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,11 @@ -.PHONY: configure build debug-c debug-vm test +.PHONY: build debug-c debug-vm test -configure: +build: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug + cmake --build build -build: configure +build-release: + cmake -S . -B build -DCMAKE_BUILD_TYPE=Release cmake --build build debug-c: build diff --git a/README.md b/README.md index 88fa784..c8c015b 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Working examples of code can be found in `exl/` directory. ## Building 1. Clone the repository: `git clone https://git.samsonium.dev/samsonium/fz-lang.git` -2. Build: `make build` +2. Build: `make build-release` After this, a "build" directory will appear in the project root. It will contain two executable files: diff --git a/README.ru.md b/README.ru.md index 57a9d3d..d1cc899 100644 --- a/README.ru.md +++ b/README.ru.md @@ -27,7 +27,7 @@ ## Инструкции по сборке 1. Склонировать проект: `git clone https://git.samsonium.dev/samsonium/fz-lang.git` -2. Собрать проект: `make build` +2. Собрать проект: `make build-release` После этого в корне появится директория **"build"**. В ней будут лежать два скомпилированных бинарных файла: From 160c6c6488786493316952463f41fdba472a4575 Mon Sep 17 00:00:00 2001 From: samsonium Date: Wed, 21 Oct 2026 04:30:16 +0300 Subject: [PATCH 04/24] fix: incorrect test file directory prefix --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ec798a..27e805a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,7 +46,7 @@ foreach (TEST_FILE IN LISTS TEST_PROGS) add_test( NAME "progs_compile_${TEST_NAME}" - COMMAND fzc -o "p_${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME}.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/${TEST_FILE}" + COMMAND fzc -o "${CMAKE_CURRENT_BINARY_DIR}/p_${TEST_NAME}.fzb" "${CMAKE_CURRENT_SOURCE_DIR}/${TEST_FILE}" ) add_test( NAME "progs_runtime_${TEST_NAME}" From 673a28fbc781dfaca09d28c4ee067d6a26896094 Mon Sep 17 00:00:00 2001 From: samsonium Date: Wed, 21 Oct 2026 04:31:02 +0300 Subject: [PATCH 05/24] fix: check for EOF on block parsing (closing brace) --- src/compiler/fzl_parser.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/compiler/fzl_parser.c b/src/compiler/fzl_parser.c index e2d93b9..4274af0 100644 --- a/src/compiler/fzl_parser.c +++ b/src/compiler/fzl_parser.c @@ -30,10 +30,7 @@ fzl_token_t this_tok = {.type = FZL_TOKTYPE_NAME, fzl_parser_t *fzl_mkparser(fzl_reader_ll_t *first) { if (!first) - { - snprintf(plast_err, sizeof(plast_err), "token is null"); return NULL; - } fzl_parser_t *parser = malloc(sizeof(fzl_parser_t)); if (!parser) @@ -962,7 +959,7 @@ fzl_node_t *parse_block(fzl_parser_t *parser) stmts[count++] = node; } fzl_token_t *rbrace = consume(parser); - if (rbrace && rbrace->type != FZL_TOKTYPE_RBRACE) + if (!rbrace || rbrace->type != FZL_TOKTYPE_RBRACE) { snprintf(plast_err, sizeof(plast_err), "'%s': missing '}' after block at %ld:%ld", rbrace->filename, From c7cce94119b874a5078521d960957b034a42744d Mon Sep 17 00:00:00 2001 From: samsonium Date: Wed, 21 Oct 2026 04:31:57 +0300 Subject: [PATCH 06/24] fix: add AST freeing on errors; add checks for `parser`, `walker` and `writer` are not NULL --- src/compiler/main.c | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/src/compiler/main.c b/src/compiler/main.c index d6b534b..dfb486c 100644 --- a/src/compiler/main.c +++ b/src/compiler/main.c @@ -96,11 +96,21 @@ int main(int argc, char **argv) } fzl_parser_t *parser = fzl_mkparser(rd->first); - result = fzl_parse(parser); + if (!parser) + { + printf("%sError%s: failed to allocate writer\n", FZL_CERROR, FZL_CRST); + fzl_freeoptions(opts); + fzl_freereader(rd); + fzl_freelinked(linked); + return -1; + } + + result = fzl_parse(parser); if (result != 0) { printf("%sError%s: %s\n", FZL_CERROR, FZL_CRST, fzl_parser_geterror()); fzl_freeoptions(opts); + fzl_freeast(parser->root); fzl_freeparser(parser); fzl_freereader(rd); fzl_freelinked(linked); @@ -116,12 +126,24 @@ int main(int argc, char **argv) } fzl_walker_t *walker = fzl_mkwalker(parser->root); - result = fzl_walk(walker); + if (!walker) + { + printf("%sError%s: failed to allocate writer\n", FZL_CERROR, FZL_CRST); + fzl_freeoptions(opts); + fzl_freeast(parser->root); + fzl_freeparser(parser); + fzl_freereader(rd); + fzl_freelinked(linked); + return -1; + } + + result = fzl_walk(walker); if (result != 0) { printf("%sError%s: %s\n", FZL_CERROR, FZL_CRST, fzl_walker_geterror()); fzl_freeoptions(opts); fzl_freewalker(walker); + fzl_freeast(parser->root); fzl_freeparser(parser); fzl_freereader(rd); fzl_freelinked(linked); @@ -162,13 +184,26 @@ int main(int argc, char **argv) } fzl_writer_t *writer = fzl_mkwriter(outpath); - result = fzl_write(writer, walker); + if (!writer) + { + printf("%sError%s: failed to allocate writer\n", FZL_CERROR, FZL_CRST); + fzl_freeoptions(opts); + fzl_freewalker(walker); + fzl_freeast(parser->root); + fzl_freeparser(parser); + fzl_freereader(rd); + fzl_freelinked(linked); + return -1; + } + + result = fzl_write(writer, walker); if (result != 0) { printf("%sError%s: %s\n", FZL_CERROR, FZL_CRST, fzl_writer_geterror()); fzl_freeoptions(opts); fzl_freewriter(writer); fzl_freewalker(walker); + fzl_freeast(parser->root); fzl_freeparser(parser); fzl_freereader(rd); fzl_freelinked(linked); From 4a366cb0ac15f06471553aa5e487d55220deb53a Mon Sep 17 00:00:00 2001 From: samsonium Date: Wed, 21 Oct 2026 04:42:41 +0300 Subject: [PATCH 07/24] fix: missing ';' in variables example --- exl/00_variables.fzs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exl/00_variables.fzs b/exl/00_variables.fzs index fe3b019..015a819 100644 --- a/exl/00_variables.fzs +++ b/exl/00_variables.fzs @@ -16,7 +16,7 @@ printline(c); // Also, there is many different types to help your program // work with many things: int signedInteger; -uint unsignedInteger +uint unsignedInteger; str string; bool boolean; From 73a2a50a0b1c78016034c76e07f4ea94294bebb1 Mon Sep 17 00:00:00 2001 From: samsonium Date: Wed, 21 Oct 2026 04:42:58 +0300 Subject: [PATCH 08/24] feat: add error message for variable redefenition attempt --- src/compiler/fzl_walker.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/compiler/fzl_walker.c b/src/compiler/fzl_walker.c index d4442f9..9c68106 100644 --- a/src/compiler/fzl_walker.c +++ b/src/compiler/fzl_walker.c @@ -1056,9 +1056,19 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) if (res != FZL_SYMTABLE_RESULT_OK) { fzl_token_t *tok = node->var.identifier->token; - snprintf(wlast_err, sizeof(wlast_err), - "'%s': cannot store '%s' at %ld:%ld", tok->filename, - (char *)tok->value, tok->ln, tok->cn); + if (res == FZL_SYMTABLE_RESULT_EXISTS) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': cannot redefine '%s' at %ld:%ld", tok->filename, + (char *)tok->value, tok->ln, tok->cn); + } + else + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': cannot store '%s' at %ld:%ld", tok->filename, + (char *)tok->value, tok->ln, tok->cn); + } + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; } @@ -1225,6 +1235,11 @@ int fzl_walk(fzl_walker_t *walker) push_op(walker, FZL_OPCODE_RM_ENVTABLE); fin_ctx(walker); + if (walker->ctx) + { + while (walker->ctx) + fin_ctx(walker); + } if (result.error && !wlast_err[0]) snprintf(wlast_err, sizeof(wlast_err), "%s", result.error_msg); From 0188991f4dcb7b9e978312dc1739c3bf68aab857 Mon Sep 17 00:00:00 2001 From: samsonium Date: Thu, 22 Oct 2026 03:39:04 +0300 Subject: [PATCH 09/24] feat: add new tests for loops --- tests/programs/neg_infinite_for.fzs | 3 +++ tests/programs/neg_infinite_while.fzs | 3 +++ tests/programs/pos_loop_with_break.fzs | 7 +++++++ tests/programs/pos_loops.fzs | 27 ++++++++++++++++++++++++++ 4 files changed, 40 insertions(+) create mode 100644 tests/programs/neg_infinite_for.fzs create mode 100644 tests/programs/neg_infinite_while.fzs create mode 100644 tests/programs/pos_loop_with_break.fzs create mode 100644 tests/programs/pos_loops.fzs diff --git a/tests/programs/neg_infinite_for.fzs b/tests/programs/neg_infinite_for.fzs new file mode 100644 index 0000000..1de7327 --- /dev/null +++ b/tests/programs/neg_infinite_for.fzs @@ -0,0 +1,3 @@ +for (int i = 0; i < 5; i = i - 1) +{} + diff --git a/tests/programs/neg_infinite_while.fzs b/tests/programs/neg_infinite_while.fzs new file mode 100644 index 0000000..59c1072 --- /dev/null +++ b/tests/programs/neg_infinite_while.fzs @@ -0,0 +1,3 @@ +while (true) +{} + diff --git a/tests/programs/pos_loop_with_break.fzs b/tests/programs/pos_loop_with_break.fzs new file mode 100644 index 0000000..a19f1a3 --- /dev/null +++ b/tests/programs/pos_loop_with_break.fzs @@ -0,0 +1,7 @@ +int i = 0; + +for (;;) +{ + break; +} + diff --git a/tests/programs/pos_loops.fzs b/tests/programs/pos_loops.fzs new file mode 100644 index 0000000..f126742 --- /dev/null +++ b/tests/programs/pos_loops.fzs @@ -0,0 +1,27 @@ +for (int i = 0; i < 10; i = i + 1) + printline("check"); + +int i = 0; +for (i = 0; i <= 5; i = i + 1) +{ + printline("i is:"); + printline(i); +} + +if (i < 5) +{ + printline("external declared `i` != 5"); + exit(1); +} + +while (i > 0) +{ + i = i - 1; +} + +if (i > 0) +{ + printline("decreased in while loop `i` > 0"); + exit(1); +} + From cc5bbd61f2994632c9829b6d7555f3b8e878d532 Mon Sep 17 00:00:00 2001 From: samsonium Date: Thu, 22 Oct 2026 18:24:28 +0300 Subject: [PATCH 10/24] feat: implement loops --- include/compiler/fzl_parser.h | 11 ++ include/compiler/fzl_token.h | 3 + include/compiler/fzl_walker.h | 20 ++- src/compiler/fzl_debug.c | 18 ++- src/compiler/fzl_parser.c | 241 ++++++++++++++++++++++++++++++++++ src/compiler/fzl_reader.c | 5 + src/compiler/fzl_walker.c | 102 +++++++++++++- 7 files changed, 396 insertions(+), 4 deletions(-) diff --git a/include/compiler/fzl_parser.h b/include/compiler/fzl_parser.h index f96c307..08d20ea 100644 --- a/include/compiler/fzl_parser.h +++ b/include/compiler/fzl_parser.h @@ -18,6 +18,7 @@ typedef enum FZL_NODETYPE_CLASS, FZL_NODETYPE_FIELD, FZL_NODETYPE_CONSTRUCT, + FZL_NODETYPE_LOOP, FZL_NODETYPE_ADD, FZL_NODETYPE_SUB, @@ -140,6 +141,16 @@ typedef struct fzl_node { struct fzl_node *name; } construct; + + // Loop + struct + { + struct fzl_node *init; + struct fzl_node *cond; + struct fzl_node *step; + + struct fzl_node *body; + } loop; }; } fzl_node_t; diff --git a/include/compiler/fzl_token.h b/include/compiler/fzl_token.h index a051cd0..37967bd 100644 --- a/include/compiler/fzl_token.h +++ b/include/compiler/fzl_token.h @@ -14,6 +14,9 @@ typedef enum FZL_TOKTYPE_FUNCTION, // fn FZL_TOKTYPE_RETURN, // return + FZL_TOKTYPE_FOR, // for + FZL_TOKTYPE_WHILE, // while + FZL_TOKTYPE_IF, // if FZL_TOKTYPE_ELSE, // else diff --git a/include/compiler/fzl_walker.h b/include/compiler/fzl_walker.h index a984e68..8947136 100644 --- a/include/compiler/fzl_walker.h +++ b/include/compiler/fzl_walker.h @@ -16,7 +16,8 @@ typedef enum FZL_WALK_CTX_TYPE_GLOBAL, FZL_WALK_CTX_TYPE_FN, FZL_WALK_CTX_TYPE_CLASS, - FZL_WALK_CTX_TYPE_ASSIGN + FZL_WALK_CTX_TYPE_ASSIGN, + FZL_WALK_CTX_TYPE_LOOP, } fzl_walk_ctx_type_t; // Internal walk function result @@ -36,8 +37,23 @@ typedef struct fzl_walk_ctx fzl_walk_ctx_type_t type; struct fzl_walk_ctx *parent; - fzl_node_t *fn; size_t initial_env_lvl; + union + { + // function info + struct + { + fzl_node_t *fn; + }; + + // loops `break`s writeback addrs + struct + { + size_t *breaks; + size_t breaks_count; + size_t breaks_remainder; + }; + }; } fzl_walk_ctx_t; // Walker descriptor diff --git a/src/compiler/fzl_debug.c b/src/compiler/fzl_debug.c index cbab185..0ba3d7f 100644 --- a/src/compiler/fzl_debug.c +++ b/src/compiler/fzl_debug.c @@ -31,6 +31,22 @@ void print_node(fzl_node_t *node, int level) switch (node->type) { + case FZL_NODETYPE_LOOP: + printf("%s Loop:\n", levelbuf); + printf("-%s Init:\n", levelbuf); + if (node->loop.init) + print_node(node->loop.init, level + 2); + printf("-%s Condition:\n", levelbuf); + if (node->loop.cond) + print_node(node->loop.cond, level + 2); + printf("-%s Step:\n", levelbuf); + if (node->loop.step) + print_node(node->loop.step, level + 2); + printf("-%s Body:\n", levelbuf); + if (node->loop.body) + print_node(node->loop.body, level + 2); + break; + case FZL_NODETYPE_CONSTRUCT: printf("%s Construct class '%s':\n", levelbuf, (char *)node->construct.name->token->value); @@ -169,7 +185,7 @@ void fzl_debug_print_ast(fzl_node_t *root) fzl_node_t *curr = root; while (curr != NULL) { - print_node(curr, 2); + print_node(curr, 3); curr = curr->next; } diff --git a/src/compiler/fzl_parser.c b/src/compiler/fzl_parser.c index 4274af0..afcedac 100644 --- a/src/compiler/fzl_parser.c +++ b/src/compiler/fzl_parser.c @@ -159,6 +159,7 @@ fzl_node_t *parse_primary(fzl_parser_t *parser) { snprintf(plast_err, sizeof(plast_err), "'%s': expected ')' at %ld:%ld", peek(parser)->filename, peek(parser)->ln, peek(parser)->cn); + fzl_freeast(node); return NULL; } consume(parser); @@ -256,6 +257,7 @@ fzl_node_t *parse_postfix(fzl_parser_t *parser) if (!rparen || rparen->type != FZL_TOKTYPE_RPAREN) { free(args); + fzl_freeast(left); snprintf(plast_err, sizeof(plast_err), "'%s': expected ')' after argument list at %ld:%ld", peek(parser)->filename, peek(parser)->ln, peek(parser)->cn); @@ -273,6 +275,7 @@ fzl_node_t *parse_postfix(fzl_parser_t *parser) { if (!is(parser, FZL_TOKTYPE_NAME)) { + fzl_freeast(left); snprintf(plast_err, sizeof(plast_err), "'%s': expected name after '.' at %ld:%ld", last_known_tok->filename, last_known_tok->ln, @@ -339,6 +342,7 @@ fzl_node_t *parse_power(fzl_parser_t *parser) right = parse_unary(parser); if (!right) { + fzl_freeast(left); if (!plast_err[0]) snprintf(plast_err, sizeof(plast_err), "'%s': failed to parse right side of power at %ld:%ld", @@ -382,6 +386,7 @@ fzl_node_t *parse_factor(fzl_parser_t *parser) right = parse_factor(parser); if (!right) { + fzl_freeast(left); if (!plast_err[0]) snprintf(plast_err, sizeof(plast_err), "'%s': unfinished factor (mul/div) expression at %ld:%ld", @@ -423,6 +428,7 @@ fzl_node_t *parse_term(fzl_parser_t *parser) right = parse_factor(parser); if (!right) { + fzl_freeast(left); if (!plast_err[0]) snprintf(plast_err, sizeof(plast_err), "'%s': unfinished term (add/sub) expression at %ld:%ld", @@ -459,6 +465,7 @@ fzl_node_t *parse_cond(fzl_parser_t *parser) right = parse_term(parser); if (!right) { + fzl_freeast(left); if (!plast_err[0]) snprintf(plast_err, sizeof(plast_err), "'%s': unfinished expression at %ld:%ld", op->filename, op->ln, @@ -517,6 +524,7 @@ fzl_node_t *parse_and(fzl_parser_t *parser) right = parse_cond(parser); if (!right) { + fzl_freeast(left); if (!plast_err[0]) snprintf(plast_err, sizeof(plast_err), "'%s': unfinished expression at %ld:%ld", or->filename, or->ln, @@ -551,6 +559,7 @@ fzl_node_t *parse_or(fzl_parser_t *parser) right = parse_and(parser); if (!right) { + fzl_freeast(left); if (!plast_err[0]) snprintf(plast_err, sizeof(plast_err), "'%s': unfinished expression at %ld:%ld", or->filename, or->ln, @@ -578,6 +587,7 @@ fzl_node_t *parse_new(fzl_parser_t *parser) if (objname->type != FZL_NODETYPE_IDENTIFIER && objname->type != FZL_NODETYPE_FIELD) { + fzl_freeast(objname); snprintf(plast_err, sizeof(plast_err), "'%s': expect class name after 'new' keyword at %ld:%ld", last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); @@ -604,6 +614,7 @@ fzl_node_t *parse_assignment(fzl_parser_t *parser) left->type != FZL_NODETYPE_DECLARATION && left->type != FZL_NODETYPE_FIELD) { + fzl_freeast(left); snprintf(plast_err, sizeof(plast_err), "'%s': cannot assign to '%s' at %ld:%ld", left->token->filename, (char *)left->token->value, asn->ln, asn->cn); @@ -612,7 +623,10 @@ fzl_node_t *parse_assignment(fzl_parser_t *parser) fzl_node_t *right = parse_assignment(parser); if (!right) + { + fzl_freeast(left); return NULL; + } fzl_node_t *node = fzl_mknode(FZL_NODETYPE_ASSIGN, asn); node->binary_op.left = left; @@ -692,6 +706,7 @@ fzl_node_t *parse_class(fzl_parser_t *parser) fields = realloc(fields, fremainder * sizeof(fzl_node_t *)); if (!fields) { + fzl_freeast(node); snprintf(plast_err, sizeof(plast_err), "failed to resize fields array"); free(methods); @@ -709,6 +724,7 @@ fzl_node_t *parse_class(fzl_parser_t *parser) methods = realloc(methods, mremainder * sizeof(fzl_node_t *)); if (!methods) { + fzl_freeast(node); snprintf(plast_err, sizeof(plast_err), "failed to resize methods array"); free(fields); @@ -723,6 +739,7 @@ fzl_node_t *parse_class(fzl_parser_t *parser) snprintf(plast_err, sizeof(plast_err), "failed to reallocate method args list"); free(fields); + fzl_freeast(node); return NULL; } @@ -741,6 +758,7 @@ fzl_node_t *parse_class(fzl_parser_t *parser) } else { + fzl_freeast(node); snprintf(plast_err, sizeof(plast_err), "'%s': unknown type of class member at %ld:%ld", node->token->filename, node->token->ln, node->token->cn); @@ -827,6 +845,7 @@ fzl_node_t *parse_function(fzl_parser_t *parser) fzl_token_t *rpar = consume(parser); if (!rpar || rpar->type != FZL_TOKTYPE_RPAREN) { + free(args); snprintf(plast_err, sizeof(plast_err), "'%s': expected ')' after argument list at %ld:%ld", last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); @@ -842,6 +861,7 @@ fzl_node_t *parse_function(fzl_parser_t *parser) if (!is(parser, FZL_TOKTYPE_LBRACE)) { + free(args); snprintf(plast_err, sizeof(plast_err), "'%s': expected '{' after function declaration at %ld:%ld", last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); @@ -850,7 +870,10 @@ fzl_node_t *parse_function(fzl_parser_t *parser) fzl_node_t *body = parse_statement(parser); if (!body) + { + free(args); return NULL; + } fzl_node_t *node = fzl_mknode(FZL_NODETYPE_FUNCTION, name); node->fn.args = args; @@ -875,7 +898,10 @@ fzl_node_t *parse_return(fzl_parser_t *parser) { fzl_node_t *retval = parse_expression(parser); if (!retval) + { + fzl_freeast(node); return NULL; + } node->ret.value = retval; } @@ -905,6 +931,7 @@ fzl_node_t *parse_if(fzl_parser_t *parser) fzl_token_t *rparen = consume(parser); if (rparen->type != FZL_TOKTYPE_RPAREN) { + fzl_freeast(condition); snprintf(plast_err, sizeof(plast_err), "'%s': expected ')' after condition at %ld:%ld", rparen->filename, rparen->ln, rparen->cn); @@ -914,14 +941,21 @@ fzl_node_t *parse_if(fzl_parser_t *parser) fzl_node_t *else_block = NULL; fzl_node_t *true_block = parse_statement(parser); if (!true_block) + { + fzl_freeast(condition); return NULL; + } if (is(parser, FZL_TOKTYPE_ELSE)) { consume(parser); // else else_block = parse_statement(parser); if (!else_block) + { + fzl_freeast(condition); + fzl_freeast(true_block); return NULL; + } } fzl_node_t *stmt_node = fzl_mknode(FZL_NODETYPE_CONDITIONAL, if_tok); @@ -948,7 +982,10 @@ fzl_node_t *parse_block(fzl_parser_t *parser) { fzl_node_t *node = parse_statement(parser); if (!node) + { + free(stmts); return NULL; + } if (remainder <= count) { @@ -961,6 +998,10 @@ fzl_node_t *parse_block(fzl_parser_t *parser) fzl_token_t *rbrace = consume(parser); if (!rbrace || rbrace->type != FZL_TOKTYPE_RBRACE) { + for (size_t i = 0; i < count; i++) + fzl_freeast(stmts[i]); + free(stmts); + snprintf(plast_err, sizeof(plast_err), "'%s': missing '}' after block at %ld:%ld", rbrace->filename, rbrace->ln, rbrace->cn); @@ -973,6 +1014,190 @@ fzl_node_t *parse_block(fzl_parser_t *parser) return node; } +// For-loop parsing +fzl_node_t *parse_for(fzl_parser_t *parser) +{ + fzl_token_t *tok = consume(parser); + fzl_token_t *lpar = consume(parser); + if (lpar->type != FZL_TOKTYPE_LPAREN) + { + snprintf(plast_err, sizeof(plast_err), + "'%s': expected '(' after for keyword at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + return NULL; + } + + fzl_node_t *start = NULL; + if (!is(parser, FZL_TOKTYPE_SEMICOLON)) + { + start = parse_expression(parser); + if (!start) + { + snprintf(plast_err, sizeof(plast_err), + "'%s': expected loop init or ';' at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); + return NULL; + } + + fzl_token_t *sc = consume(parser); + if (!sc || sc->type != FZL_TOKTYPE_SEMICOLON) + { + fzl_freeast(start); + snprintf(plast_err, sizeof(plast_err), + "'%s': missing ';' after init section at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); + return NULL; + } + } + else + consume(parser); + + fzl_node_t *condition = NULL; + if (!is(parser, FZL_TOKTYPE_SEMICOLON)) + { + condition = parse_expression(parser); + if (!condition) + { + fzl_freeast(start); + snprintf(plast_err, sizeof(plast_err), + "'%s': expected loop condition or ';' at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); + return NULL; + } + + fzl_token_t *sc = consume(parser); + if (!sc || sc->type != FZL_TOKTYPE_SEMICOLON) + { + fzl_freeast(start); + fzl_freeast(condition); + snprintf(plast_err, sizeof(plast_err), + "'%s': missing ';' after condition at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); + return NULL; + } + } + else + consume(parser); + + fzl_node_t *step = NULL; + if (!is(parser, FZL_TOKTYPE_RPAREN)) + { + step = parse_expression(parser); + if (!step) + { + fzl_freeast(start); + fzl_freeast(condition); + snprintf(plast_err, sizeof(plast_err), + "'%s': expected loop step end or ')' at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); + return NULL; + } + } + + fzl_token_t *rpar = consume(parser); + if (!rpar || rpar->type != FZL_TOKTYPE_RPAREN) + { + fzl_freeast(start); + fzl_freeast(condition); + fzl_freeast(step); + snprintf(plast_err, sizeof(plast_err), + "'%s': missing ')' after loop setup at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + return NULL; + } + + fzl_node_t *body = parse_statement(parser); + if (!body) + { + fzl_freeast(start); + fzl_freeast(condition); + fzl_freeast(step); + snprintf(plast_err, sizeof(plast_err), + "'%s': missing body of for loop at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + return NULL; + } + + fzl_node_t *node = fzl_mknode(FZL_NODETYPE_LOOP, tok); + node->loop.init = start; + node->loop.cond = condition; + node->loop.step = step; + node->loop.body = body; + + return node; +} + +// While-loop parsing +fzl_node_t *parse_while(fzl_parser_t *parser) +{ + fzl_token_t *tok = consume(parser); + fzl_token_t *lpar = consume(parser); + if (!lpar || lpar->type != FZL_TOKTYPE_LPAREN) + { + snprintf(plast_err, sizeof(plast_err), + "'%s': missing '(' after while keyword at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + return NULL; + } + + fzl_node_t *condition = parse_expression(parser); + if (!condition) + { + snprintf(plast_err, sizeof(plast_err), + "'%s': missing condition for 'while' at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + return NULL; + } + + fzl_token_t *rpar = consume(parser); + if (!rpar || rpar->type != FZL_TOKTYPE_RPAREN) + { + fzl_freeast(condition); + snprintf(plast_err, sizeof(plast_err), + "'%s': missing ')' after while loop condition at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + return NULL; + } + + fzl_node_t *body = parse_statement(parser); + if (!body) + { + fzl_freeast(condition); + snprintf(plast_err, sizeof(plast_err), + "'%s': missing while loop body at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + return NULL; + } + + fzl_node_t *node = fzl_mknode(FZL_NODETYPE_LOOP, tok); + node->loop.cond = condition; + node->loop.body = body; + + return node; +} + +// `while` and `for` loops +fzl_node_t *parse_loop(fzl_parser_t *parser) +{ + if (!peek(parser)) + return NULL; + + if (is(parser, FZL_TOKTYPE_FOR)) + return parse_for(parser); + else if (is(parser, FZL_TOKTYPE_WHILE)) + return parse_while(parser); + + snprintf(plast_err, sizeof(plast_err), + "'%s': unknown type of loop at %ld:%ld", last_known_tok->filename, + last_known_tok->ln, last_known_tok->cn); + return NULL; +} + // Statement parsing fzl_node_t *parse_statement(fzl_parser_t *parser) { @@ -991,6 +1216,8 @@ fzl_node_t *parse_statement(fzl_parser_t *parser) return parse_if(parser); else if (is(parser, FZL_TOKTYPE_LBRACE)) return parse_block(parser); + else if (is(parser, FZL_TOKTYPE_FOR) || is(parser, FZL_TOKTYPE_WHILE)) + return parse_loop(parser); if (!res) res = parse_expression(parser); @@ -1116,6 +1343,20 @@ void fzl_freeast(fzl_node_t *root) fzl_freeast(root->cond.else_block); break; + case FZL_NODETYPE_LOOP: + if (root->loop.init) + fzl_freeast(root->loop.init); + + if (root->loop.cond) + fzl_freeast(root->loop.cond); + + if (root->loop.step) + fzl_freeast(root->loop.step); + + if (root->loop.body) + fzl_freeast(root->loop.body); + break; + case FZL_NODETYPE_ADD: case FZL_NODETYPE_SUB: case FZL_NODETYPE_MUL: diff --git a/src/compiler/fzl_reader.c b/src/compiler/fzl_reader.c index 540d29c..dc37a24 100644 --- a/src/compiler/fzl_reader.c +++ b/src/compiler/fzl_reader.c @@ -73,6 +73,11 @@ fzl_token_t *parse_name(fzl_reader_t *reader) if (strcmp(value, "class") == 0) tok = fzl_mktoken(FZL_TOKTYPE_CLASS, l.line, l.col, value, size, l.filename); + else if (strcmp(value, "for") == 0) + tok = fzl_mktoken(FZL_TOKTYPE_FOR, l.line, l.col, value, size, l.filename); + else if (strcmp(value, "while") == 0) + tok = + fzl_mktoken(FZL_TOKTYPE_WHILE, l.line, l.col, value, size, l.filename); else if (strcmp(value, "new") == 0) tok = fzl_mktoken(FZL_TOKTYPE_NEW, l.line, l.col, value, size, l.filename); else if (strcmp(value, "fn") == 0) diff --git a/src/compiler/fzl_walker.c b/src/compiler/fzl_walker.c index 9c68106..bc269b5 100644 --- a/src/compiler/fzl_walker.c +++ b/src/compiler/fzl_walker.c @@ -200,6 +200,96 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) { switch (node->type) { + // NOTE: add new levels for infinite loop check: + // 1. Dead condition (f.e. `while (i < 5)` w/o `i` increment) + // 2. Step direction (f.e. `for (; i > 5; i = i - 1)`) + case FZL_NODETYPE_LOOP: + { + fzl_walk_ctx_t *newctx = nest_ctx(walker, FZL_WALK_CTX_TYPE_LOOP); + if (!newctx) + { + snprintf(wlast_err, sizeof(wlast_err), "failed to nest context"); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + newctx->initial_env_lvl = walker->sym_top - 1; + newctx->breaks_count = 0; + newctx->breaks_remainder = 2; + newctx->breaks = malloc(newctx->breaks_remainder * sizeof(size_t)); + + push_op(walker, FZL_OPCODE_MK_ENVTABLE); + walker->syms[walker->sym_top++] = fzl_mksymt(); + + if (node->loop.init) + { + fzl_walk_res_t res = walk(walker, node->loop.init); + if (res.error) + { + free(newctx->breaks); + return res; + } + } + + size_t start_ip = walker->op_count; + if (node->loop.cond) + { + fzl_walk_res_t res = walk(walker, node->loop.cond); + if (res.error) + { + free(newctx->breaks); + return res; + } + } + else + { + push_op(walker, FZL_OPCODE_PUSHNUM); + push_word(walker, 1); + } + + push_op(walker, FZL_OPCODE_JZ); + newctx->breaks[newctx->breaks_count++] = walker->op_count; + push_word(walker, 0); + + fzl_walk_res_t loop_body_res = walk(walker, node->loop.body); + if (loop_body_res.error) + { + free(newctx->breaks); + return loop_body_res; + } + + if (node->loop.step) + { + fzl_walk_res_t res = walk(walker, node->loop.step); + if (res.error) + { + free(newctx->breaks); + return res; + } + } + + push_op(walker, FZL_OPCODE_JMP); + push_word(walker, start_ip); + push_op(walker, FZL_OPCODE_RM_ENVTABLE); + + for (size_t i = walker->sym_top - 1; i > newctx->initial_env_lvl; i--) + fzl_freesymt(walker->syms[--walker->sym_top]); + + // backpatch `break`s + for (size_t i = 0; i < newctx->breaks_count; i++) + walker->ops[newctx->breaks[i]] = walker->op_count - 1; + + free(newctx->breaks); + if (!fin_ctx(walker)) + { + snprintf(wlast_err, sizeof(wlast_err), + "failed to finish loop context"); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + current_res = (fzl_walk_res_t){.type = FZL_TYPE_NULL, .error = false}; + break; + } + case FZL_NODETYPE_CLASS: { fzl_walk_ctx_t *newctx = nest_ctx(walker, FZL_WALK_CTX_TYPE_CLASS); @@ -461,7 +551,16 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) case FZL_NODETYPE_RETURN: { - if (!walker->ctx || walker->ctx->type != FZL_WALK_CTX_TYPE_FN) + fzl_walk_ctx_t *ctx = walker->ctx; + while (ctx) + { + if (ctx && ctx->type == FZL_WALK_CTX_TYPE_FN) + break; + + ctx = ctx->parent; + } + + if (!ctx || ctx->type != FZL_WALK_CTX_TYPE_FN) { snprintf( wlast_err, sizeof(wlast_err), @@ -731,6 +830,7 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) } fzl_freesymt(walker->syms[--walker->sym_top]); push_op(walker, FZL_OPCODE_RM_ENVTABLE); + current_res = (fzl_walk_res_t){.type = FZL_TYPE_NULL, .error = false}; break; case FZL_NODETYPE_CALL: From 5dde75523d5e451bda0cd9e47ea676b2f87bb28c Mon Sep 17 00:00:00 2001 From: samsonium Date: Thu, 22 Oct 2026 18:25:07 +0300 Subject: [PATCH 11/24] wip: working on loop infinity checker --- CMakeLists.txt | 1 + src/compiler/checkers/fzl_loop_check.c | 60 ++++++++++++++++++++++++++ src/compiler/fzl_walker.c | 12 ++++++ 3 files changed, 73 insertions(+) create mode 100644 src/compiler/checkers/fzl_loop_check.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 27e805a..f55220b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,7 @@ add_executable(fzc src/compiler/utils/fzl_linked.c src/compiler/utils/fzl_options.c src/compiler/utils/fzl_ctable.c + src/compiler/checkers/fzl_loop_check.c src/compiler/main.c) # Virtual machine diff --git a/src/compiler/checkers/fzl_loop_check.c b/src/compiler/checkers/fzl_loop_check.c new file mode 100644 index 0000000..8958292 --- /dev/null +++ b/src/compiler/checkers/fzl_loop_check.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +// Check for infinite loop +bool is_infinite_loop(fzl_walker_t *walker, size_t start_ip, size_t end_ip) +{ + if (!walker) + return false; + + end_ip = (end_ip >= walker->op_count) ? (walker->op_count - 1) : end_ip; + + // Can we determine an init block? + size_t maybe_init_start = start_ip + 1; + size_t maybe_init_end = start_ip + 6; + fzl_const_t *declared = NULL; + for (size_t i = maybe_init_start; i <= maybe_init_end; i++) + { + if (walker->ops[i] != FZL_OPCODE_STORE) + continue; + + declared = &walker->consts->array[walker->ops[i - 1]]; + break; + } + + if (declared) + { + fzl_sym_t sym; + bool found = false; + for (ssize_t i = walker->sym_top - 1; i >= 0; i--) + if ((found = fzl_symt_get(walker->syms[i], + (char *)declared->as.string.chars, &sym))) + break; + + if (!found) + return false; // suspicious + + if (sym.type >= FZL_TYPE_INT8) + { + } + } + + // At least has jump outside + for (size_t i = start_ip; i <= end_ip; i++) + { + if (walker->ops[i] != FZL_OPCODE_JMP) + continue; + + size_t jump_ip = walker->ops[++i]; + if (jump_ip >= end_ip) + return false; + } + + return true; +} diff --git a/src/compiler/fzl_walker.c b/src/compiler/fzl_walker.c index bc269b5..ee6aa64 100644 --- a/src/compiler/fzl_walker.c +++ b/src/compiler/fzl_walker.c @@ -14,6 +14,8 @@ #include #include +extern bool is_infinite_loop(fzl_walker_t *walker, size_t ls, size_t le); + char *null_name = ""; char wlast_err[256]; @@ -271,6 +273,16 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) push_word(walker, start_ip); push_op(walker, FZL_OPCODE_RM_ENVTABLE); + bool is_infinite = + is_infinite_loop(walker, start_ip, walker->op_count - 2); + if (is_infinite) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': infinite loop detected at %ld:%ld", + node->token->filename, node->token->ln, node->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + for (size_t i = walker->sym_top - 1; i > newctx->initial_env_lvl; i--) fzl_freesymt(walker->syms[--walker->sym_top]); From a20743a835e74fcdac9016ba733c2df3b1e0163a Mon Sep 17 00:00:00 2001 From: samsonium Date: Thu, 22 Oct 2026 20:24:48 +0300 Subject: [PATCH 12/24] feat: implement simple infinite loop checker --- src/compiler/checkers/fzl_loop_check.c | 220 ++++++++++++++++++++++--- src/compiler/fzl_walker.c | 7 +- tests/programs/neg_infinite_for.fzs | 2 +- 3 files changed, 202 insertions(+), 27 deletions(-) diff --git a/src/compiler/checkers/fzl_loop_check.c b/src/compiler/checkers/fzl_loop_check.c index 8958292..ab58bc4 100644 --- a/src/compiler/checkers/fzl_loop_check.c +++ b/src/compiler/checkers/fzl_loop_check.c @@ -6,42 +6,214 @@ #include #include #include +#include + +ssize_t check_top; +fzl_nodetype_t check_tree[256]; + +void ghost_walk_changed(fzl_node_t *node, bool *changed, fzl_sym_t *sym) +{ + if (!node || !changed || *changed) + return; + + check_tree[check_top++] = node->type; + + switch (node->type) + { + case FZL_NODETYPE_DECLARATION: + case FZL_NODETYPE_CONSTRUCT: + case FZL_NODETYPE_CLASS: + case FZL_NODETYPE_FUNCTION: + case FZL_NODETYPE_LITERAL: + break; + + case FZL_NODETYPE_UNARY_PLUS: + case FZL_NODETYPE_UNARY_MINUS: + case FZL_NODETYPE_NOT: + ghost_walk_changed(node->unary_op.operand, changed, sym); + break; + + case FZL_NODETYPE_ADD: + case FZL_NODETYPE_SUB: + case FZL_NODETYPE_MUL: + case FZL_NODETYPE_DIV: + case FZL_NODETYPE_POW: + case FZL_NODETYPE_MOD: + case FZL_NODETYPE_OR: + case FZL_NODETYPE_AND: + case FZL_NODETYPE_EQUAL: + case FZL_NODETYPE_NEQUAL: + case FZL_NODETYPE_LESS: + case FZL_NODETYPE_LESS_EQUAL: + case FZL_NODETYPE_GREATER: + case FZL_NODETYPE_GREATER_EQUAL: + case FZL_NODETYPE_ASSIGN: + ghost_walk_changed(node->binary_op.left, changed, sym); + ghost_walk_changed(node->binary_op.right, changed, sym); + break; + + case FZL_NODETYPE_FIELD: + ghost_walk_changed(node->memb.parent, changed, sym); + ghost_walk_changed(node->memb.field, changed, sym); + break; + + case FZL_NODETYPE_RETURN: + ghost_walk_changed(node->ret.value, changed, sym); + break; + + case FZL_NODETYPE_CONDITIONAL: + ghost_walk_changed(node->cond.condition, changed, sym); + ghost_walk_changed(node->cond.true_block, changed, sym); + ghost_walk_changed(node->cond.else_block, changed, sym); + break; + + case FZL_NODETYPE_LOOP: + ghost_walk_changed(node->loop.cond, changed, sym); + ghost_walk_changed(node->loop.step, changed, sym); + ghost_walk_changed(node->loop.body, changed, sym); + break; + + case FZL_NODETYPE_BLOCK: + for (size_t i = 0; i < node->list.entry_count; i++) + ghost_walk_changed(node->list.entries[i], changed, sym); + break; + + case FZL_NODETYPE_CALL: + ghost_walk_changed(node->call.callee, changed, sym); + for (size_t i = 0; i < node->call.args_count; i++) + ghost_walk_changed(node->call.args[i], changed, sym); + break; + + case FZL_NODETYPE_IDENTIFIER: + if (strcmp(node->token->value, sym->name) == 0) + { + *changed = true; + } + + if (*changed) + { + *changed = false; + for (ssize_t i = check_top - 1; i >= 0; i--) + { + fzl_nodetype_t cur_t = check_tree[i]; + if (cur_t == FZL_NODETYPE_ADD || cur_t == FZL_NODETYPE_SUB || + cur_t == FZL_NODETYPE_MUL || cur_t == FZL_NODETYPE_DIV || + cur_t == FZL_NODETYPE_POW || cur_t == FZL_NODETYPE_MOD || + cur_t == FZL_NODETYPE_UNARY_MINUS) + { + *changed = true; + break; + } + } + } + break; + } + + check_tree[--check_top] = 0; +} + +// Whether value changed in loop body +bool value_is_changed(fzl_node_t *node, fzl_sym_t *sym) +{ + if (!node || !sym) + return false; + + bool changed = false; + check_top = 0; + memset(check_tree, 0, sizeof(check_tree)); + ghost_walk_changed(node, &changed, sym); + + return changed; +} + +// Try to find symbol in loop condition node +bool retrieve_cond_sym(fzl_walker_t *walker, fzl_node_t *node, fzl_sym_t *sym) +{ + if (!walker || !node) + return false; + + switch (node->type) + { + case FZL_NODETYPE_BLOCK: + case FZL_NODETYPE_CALL: + case FZL_NODETYPE_LITERAL: + case FZL_NODETYPE_CONDITIONAL: + case FZL_NODETYPE_FUNCTION: + case FZL_NODETYPE_RETURN: + case FZL_NODETYPE_CLASS: + case FZL_NODETYPE_FIELD: + case FZL_NODETYPE_CONSTRUCT: + case FZL_NODETYPE_LOOP: + case FZL_NODETYPE_ASSIGN: + case FZL_NODETYPE_DECLARATION: + return false; + + case FZL_NODETYPE_UNARY_MINUS: + case FZL_NODETYPE_UNARY_PLUS: + case FZL_NODETYPE_NOT: + return retrieve_cond_sym(walker, node->unary_op.operand, sym); + + case FZL_NODETYPE_ADD: + case FZL_NODETYPE_SUB: + case FZL_NODETYPE_MUL: + case FZL_NODETYPE_DIV: + case FZL_NODETYPE_POW: + case FZL_NODETYPE_MOD: + case FZL_NODETYPE_AND: + case FZL_NODETYPE_OR: + case FZL_NODETYPE_LESS: + case FZL_NODETYPE_LESS_EQUAL: + case FZL_NODETYPE_GREATER: + case FZL_NODETYPE_GREATER_EQUAL: + case FZL_NODETYPE_EQUAL: + case FZL_NODETYPE_NEQUAL: + if (retrieve_cond_sym(walker, node->binary_op.left, sym)) + return true; + if (retrieve_cond_sym(walker, node->binary_op.right, sym)) + return true; + return false; + + case FZL_NODETYPE_IDENTIFIER: + { + bool exists; + for (ssize_t i = walker->sym_top - 1; i >= 0; i--) + { + if ((exists = fzl_symt_get(walker->syms[i], (char *)node->token->value, + sym))) + break; + } + return exists; + } + } + + return false; +} // Check for infinite loop -bool is_infinite_loop(fzl_walker_t *walker, size_t start_ip, size_t end_ip) +bool is_infinite_loop(fzl_walker_t *walker, fzl_node_t *node, size_t start_ip, + size_t end_ip) { if (!walker) return false; end_ip = (end_ip >= walker->op_count) ? (walker->op_count - 1) : end_ip; - // Can we determine an init block? - size_t maybe_init_start = start_ip + 1; - size_t maybe_init_end = start_ip + 6; - fzl_const_t *declared = NULL; - for (size_t i = maybe_init_start; i <= maybe_init_end; i++) + fzl_sym_t sym; + bool sym_exists = retrieve_cond_sym(walker, node->loop.cond, &sym); + if (sym_exists) { - if (walker->ops[i] != FZL_OPCODE_STORE) - continue; - - declared = &walker->consts->array[walker->ops[i - 1]]; - break; - } - - if (declared) - { - fzl_sym_t sym; - bool found = false; - for (ssize_t i = walker->sym_top - 1; i >= 0; i--) - if ((found = fzl_symt_get(walker->syms[i], - (char *)declared->as.string.chars, &sym))) - break; - - if (!found) - return false; // suspicious + // fzl_sym_t sym; + // bool found = false; + // for (ssize_t i = walker->sym_top - 1; i >= 0; i--) + // if ((found = fzl_symt_get(walker->syms[i], + // (char *)declared->as.string.chars, &sym))) + // break; if (sym.type >= FZL_TYPE_INT8) { + bool value_changed = value_is_changed(node, &sym); + if (value_changed) + return false; } } diff --git a/src/compiler/fzl_walker.c b/src/compiler/fzl_walker.c index ee6aa64..021bb53 100644 --- a/src/compiler/fzl_walker.c +++ b/src/compiler/fzl_walker.c @@ -14,7 +14,8 @@ #include #include -extern bool is_infinite_loop(fzl_walker_t *walker, size_t ls, size_t le); +extern bool is_infinite_loop(fzl_walker_t *walker, fzl_node_t *node, size_t ls, + size_t le); char *null_name = ""; char wlast_err[256]; @@ -221,6 +222,7 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) push_op(walker, FZL_OPCODE_MK_ENVTABLE); walker->syms[walker->sym_top++] = fzl_mksymt(); + size_t loop_begin_ip = walker->op_count; if (node->loop.init) { @@ -274,9 +276,10 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) push_op(walker, FZL_OPCODE_RM_ENVTABLE); bool is_infinite = - is_infinite_loop(walker, start_ip, walker->op_count - 2); + is_infinite_loop(walker, node, loop_begin_ip, walker->op_count - 2); if (is_infinite) { + free(newctx->breaks); snprintf(wlast_err, sizeof(wlast_err), "'%s': infinite loop detected at %ld:%ld", node->token->filename, node->token->ln, node->token->cn); diff --git a/tests/programs/neg_infinite_for.fzs b/tests/programs/neg_infinite_for.fzs index 1de7327..8a7eb77 100644 --- a/tests/programs/neg_infinite_for.fzs +++ b/tests/programs/neg_infinite_for.fzs @@ -1,3 +1,3 @@ -for (int i = 0; i < 5; i = i - 1) +for (int i = 0; i < 5; i = i + 0) {} From 0e319308a7bc9e51c3ec9ed775c61ebb365dc7c0 Mon Sep 17 00:00:00 2001 From: samsonium Date: Thu, 22 Oct 2026 20:25:48 +0300 Subject: [PATCH 13/24] wip: working on infinite loop handling in vm --- src/vm/fzl_vm.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/vm/fzl_vm.c b/src/vm/fzl_vm.c index ce026e1..f39b1cd 100644 --- a/src/vm/fzl_vm.c +++ b/src/vm/fzl_vm.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +14,11 @@ #include char last_err[256]; +size_t last_jmp_ip = 0; + +// NOTE: add `--max-iter` option for limit +size_t max_iter_limit = 100000; +size_t total_jmp_on_same = 0; static char *expect_hdr = "FZL\0\1"; @@ -667,7 +673,22 @@ int fzl_vm_run(fzl_vm_t *vm) return -1; } uint64_t jmp_ip = vm->ops[++vm->ip]; - vm->ip = jmp_ip - 1; + if (jmp_ip == last_jmp_ip) + total_jmp_on_same++; + else + total_jmp_on_same = 0; + + if (total_jmp_on_same > max_iter_limit) + { + snprintf(last_err, sizeof(last_err), + "runtime error - exceeded max loop jumps (infinite loop?)\n" + "If you thinks there's mistake, change jumps limit with " + "--max-iter=N"); + return -1; + } + + vm->ip = jmp_ip - 1; + last_jmp_ip = jmp_ip; break; } From b5ae959d14fbe54a4d9686dc954919b2f69326c0 Mon Sep 17 00:00:00 2001 From: samsonium Date: Thu, 22 Oct 2026 21:30:17 +0300 Subject: [PATCH 14/24] feat: add test name format for runtime-only error --- CMakeLists.txt | 4 ++++ .../programs/{neg_infinite_for.fzs => neg1_infinite_for.fzs} | 0 2 files changed, 4 insertions(+) rename tests/programs/{neg_infinite_for.fzs => neg1_infinite_for.fzs} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index f55220b..7e748e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,5 +58,9 @@ foreach (TEST_FILE IN LISTS TEST_PROGS) set_tests_properties("progs_compile_${TEST_NAME}" PROPERTIES WILL_FAIL TRUE) set_tests_properties("progs_runtime_${TEST_NAME}" PROPERTIES WILL_FAIL TRUE) endif() + + if (TEST_NAME MATCHES "neg1_") + set_tests_properties("progs_runtime_${TEST_NAME}" PROPERTIES WILL_FAIL TRUE) + endif() endforeach() diff --git a/tests/programs/neg_infinite_for.fzs b/tests/programs/neg1_infinite_for.fzs similarity index 100% rename from tests/programs/neg_infinite_for.fzs rename to tests/programs/neg1_infinite_for.fzs From 7cfb8efef43294b79c6194ea93a68d4e060b2dd8 Mon Sep 17 00:00:00 2001 From: samsonium Date: Thu, 22 Oct 2026 21:30:32 +0300 Subject: [PATCH 15/24] feat: implement `break` --- include/compiler/fzl_parser.h | 1 + include/compiler/fzl_token.h | 1 + src/compiler/checkers/fzl_loop_check.c | 9 ++--- src/compiler/fzl_parser.c | 10 ++++++ src/compiler/fzl_reader.c | 3 ++ src/compiler/fzl_walker.c | 49 ++++++++++++++++++++++---- 6 files changed, 60 insertions(+), 13 deletions(-) diff --git a/include/compiler/fzl_parser.h b/include/compiler/fzl_parser.h index 08d20ea..83c39da 100644 --- a/include/compiler/fzl_parser.h +++ b/include/compiler/fzl_parser.h @@ -19,6 +19,7 @@ typedef enum FZL_NODETYPE_FIELD, FZL_NODETYPE_CONSTRUCT, FZL_NODETYPE_LOOP, + FZL_NODETYPE_BREAK, FZL_NODETYPE_ADD, FZL_NODETYPE_SUB, diff --git a/include/compiler/fzl_token.h b/include/compiler/fzl_token.h index 37967bd..db85fb0 100644 --- a/include/compiler/fzl_token.h +++ b/include/compiler/fzl_token.h @@ -16,6 +16,7 @@ typedef enum FZL_TOKTYPE_FOR, // for FZL_TOKTYPE_WHILE, // while + FZL_TOKTYPE_BREAK, // break FZL_TOKTYPE_IF, // if FZL_TOKTYPE_ELSE, // else diff --git a/src/compiler/checkers/fzl_loop_check.c b/src/compiler/checkers/fzl_loop_check.c index ab58bc4..8e3966d 100644 --- a/src/compiler/checkers/fzl_loop_check.c +++ b/src/compiler/checkers/fzl_loop_check.c @@ -20,6 +20,7 @@ void ghost_walk_changed(fzl_node_t *node, bool *changed, fzl_sym_t *sym) switch (node->type) { + case FZL_NODETYPE_BREAK: case FZL_NODETYPE_DECLARATION: case FZL_NODETYPE_CONSTRUCT: case FZL_NODETYPE_CLASS: @@ -134,6 +135,7 @@ bool retrieve_cond_sym(fzl_walker_t *walker, fzl_node_t *node, fzl_sym_t *sym) switch (node->type) { + case FZL_NODETYPE_BREAK: case FZL_NODETYPE_BLOCK: case FZL_NODETYPE_CALL: case FZL_NODETYPE_LITERAL: @@ -202,13 +204,6 @@ bool is_infinite_loop(fzl_walker_t *walker, fzl_node_t *node, size_t start_ip, bool sym_exists = retrieve_cond_sym(walker, node->loop.cond, &sym); if (sym_exists) { - // fzl_sym_t sym; - // bool found = false; - // for (ssize_t i = walker->sym_top - 1; i >= 0; i--) - // if ((found = fzl_symt_get(walker->syms[i], - // (char *)declared->as.string.chars, &sym))) - // break; - if (sym.type >= FZL_TYPE_INT8) { bool value_changed = value_is_changed(node, &sym); diff --git a/src/compiler/fzl_parser.c b/src/compiler/fzl_parser.c index afcedac..010bc03 100644 --- a/src/compiler/fzl_parser.c +++ b/src/compiler/fzl_parser.c @@ -1198,6 +1198,13 @@ fzl_node_t *parse_loop(fzl_parser_t *parser) return NULL; } +// `break` keyword parsing +fzl_node_t *parse_break(fzl_parser_t *parser) +{ + fzl_token_t *tok = consume(parser); + return fzl_mknode(FZL_NODETYPE_BREAK, tok); +} + // Statement parsing fzl_node_t *parse_statement(fzl_parser_t *parser) { @@ -1218,6 +1225,8 @@ fzl_node_t *parse_statement(fzl_parser_t *parser) return parse_block(parser); else if (is(parser, FZL_TOKTYPE_FOR) || is(parser, FZL_TOKTYPE_WHILE)) return parse_loop(parser); + else if (is(parser, FZL_TOKTYPE_BREAK)) + res = parse_break(parser); if (!res) res = parse_expression(parser); @@ -1303,6 +1312,7 @@ void fzl_freeast(fzl_node_t *root) { switch (root->type) { + case FZL_NODETYPE_BREAK: case FZL_NODETYPE_IDENTIFIER: case FZL_NODETYPE_LITERAL: break; diff --git a/src/compiler/fzl_reader.c b/src/compiler/fzl_reader.c index dc37a24..6d6db33 100644 --- a/src/compiler/fzl_reader.c +++ b/src/compiler/fzl_reader.c @@ -78,6 +78,9 @@ fzl_token_t *parse_name(fzl_reader_t *reader) else if (strcmp(value, "while") == 0) tok = fzl_mktoken(FZL_TOKTYPE_WHILE, l.line, l.col, value, size, l.filename); + else if (strcmp(value, "break") == 0) + tok = + fzl_mktoken(FZL_TOKTYPE_BREAK, l.line, l.col, value, size, l.filename); else if (strcmp(value, "new") == 0) tok = fzl_mktoken(FZL_TOKTYPE_NEW, l.line, l.col, value, size, l.filename); else if (strcmp(value, "fn") == 0) diff --git a/src/compiler/fzl_walker.c b/src/compiler/fzl_walker.c index 021bb53..0f4de9c 100644 --- a/src/compiler/fzl_walker.c +++ b/src/compiler/fzl_walker.c @@ -203,9 +203,45 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) { switch (node->type) { + case FZL_NODETYPE_BREAK: + { + fzl_walk_ctx_t *ctx = walker->ctx; + while (ctx) + { + if (ctx->type == FZL_WALK_CTX_TYPE_LOOP) + break; + + ctx = ctx->parent; + } + + if (!ctx || ctx->type != FZL_WALK_CTX_TYPE_LOOP) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': usage of 'break' outsude the loop at %ld:%ld", + node->token->filename, node->token->ln, node->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + for (size_t i = walker->sym_top - 1; i > ctx->initial_env_lvl + 1; i--) + push_op(walker, FZL_OPCODE_RM_ENVTABLE); + + if (ctx->breaks_remainder <= ctx->breaks_count) + { + ctx->breaks_remainder *= 2; + ctx->breaks = + realloc(ctx->breaks, ctx->breaks_remainder * sizeof(size_t)); + } + + push_op(walker, FZL_OPCODE_JMP); + ctx->breaks[ctx->breaks_count++] = walker->op_count; + push_word(walker, 0); + + current_res = (fzl_walk_res_t){.type = FZL_TYPE_NULL, .error = false}; + break; + } + // NOTE: add new levels for infinite loop check: - // 1. Dead condition (f.e. `while (i < 5)` w/o `i` increment) - // 2. Step direction (f.e. `for (; i > 5; i = i - 1)`) + // 1. Step direction (f.e. `for (; i > 5; i = i - 1)`) case FZL_NODETYPE_LOOP: { fzl_walk_ctx_t *newctx = nest_ctx(walker, FZL_WALK_CTX_TYPE_LOOP); @@ -273,6 +309,11 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) push_op(walker, FZL_OPCODE_JMP); push_word(walker, start_ip); + + // backpatch `break`s + for (size_t i = 0; i < newctx->breaks_count; i++) + walker->ops[newctx->breaks[i]] = walker->op_count; + push_op(walker, FZL_OPCODE_RM_ENVTABLE); bool is_infinite = @@ -289,10 +330,6 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) for (size_t i = walker->sym_top - 1; i > newctx->initial_env_lvl; i--) fzl_freesymt(walker->syms[--walker->sym_top]); - // backpatch `break`s - for (size_t i = 0; i < newctx->breaks_count; i++) - walker->ops[newctx->breaks[i]] = walker->op_count - 1; - free(newctx->breaks); if (!fin_ctx(walker)) { From e3878047d70917692d5eff0ee3ea0c7589f78768 Mon Sep 17 00:00:00 2001 From: samsonium Date: Sun, 25 Oct 2026 01:53:46 +0300 Subject: [PATCH 16/24] upd: rename `neg1` tests (for negative in runtime) to `negr` --- CMakeLists.txt | 2 +- tests/programs/{neg1_infinite_for.fzs => negr_infinite_for.fzs} | 0 .../{neg_infinite_while.fzs => negr_infinite_while.fzs} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename tests/programs/{neg1_infinite_for.fzs => negr_infinite_for.fzs} (100%) rename tests/programs/{neg_infinite_while.fzs => negr_infinite_while.fzs} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e748e6..425124a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,7 +59,7 @@ foreach (TEST_FILE IN LISTS TEST_PROGS) set_tests_properties("progs_runtime_${TEST_NAME}" PROPERTIES WILL_FAIL TRUE) endif() - if (TEST_NAME MATCHES "neg1_") + if (TEST_NAME MATCHES "negr_") set_tests_properties("progs_runtime_${TEST_NAME}" PROPERTIES WILL_FAIL TRUE) endif() endforeach() diff --git a/tests/programs/neg1_infinite_for.fzs b/tests/programs/negr_infinite_for.fzs similarity index 100% rename from tests/programs/neg1_infinite_for.fzs rename to tests/programs/negr_infinite_for.fzs diff --git a/tests/programs/neg_infinite_while.fzs b/tests/programs/negr_infinite_while.fzs similarity index 100% rename from tests/programs/neg_infinite_while.fzs rename to tests/programs/negr_infinite_while.fzs From dbca3129aba57baec792609a2afdd8a5fe95c8c8 Mon Sep 17 00:00:00 2001 From: samsonium Date: Sun, 25 Oct 2026 01:54:07 +0300 Subject: [PATCH 17/24] feat: add "while-not-null" test --- src/vm/fzl_vm.c | 6 +++++- tests/programs/pos_while_not_null.fzs | 28 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 tests/programs/pos_while_not_null.fzs diff --git a/src/vm/fzl_vm.c b/src/vm/fzl_vm.c index f39b1cd..b3ab61c 100644 --- a/src/vm/fzl_vm.c +++ b/src/vm/fzl_vm.c @@ -208,7 +208,7 @@ void print_object(fzl_vm_object_t *obj) printf(""); } - printf(">\n"); + printf(">"); } void print_err_data(fzl_vm_t *vm) @@ -435,6 +435,7 @@ int fzl_vm_run(fzl_vm_t *vm) fzl_vm_object_t *obj = malloc(sizeof(fzl_vm_object_t)); obj->values = malloc(values_count * sizeof(fzl_value_t)); obj->values_count = values_count; + memset(obj->values, 0, values_count * sizeof(fzl_value_t)); fzl_vm_env_t *current_env = vm->estack[vm->etop - 1]; if (current_env->owned_objects_remainder <= @@ -858,7 +859,10 @@ int fzl_vm_run(fzl_vm_t *vm) else if (arg.type == FZL_TYPE_NULL) printf("\n"); else if (arg.type == FZL_TYPE_OBJECT) + { print_object(arg.as.obj); + printf("\n"); + } } else if (fn_id == FZL_BUILTIN_EXIT) { diff --git a/tests/programs/pos_while_not_null.fzs b/tests/programs/pos_while_not_null.fzs new file mode 100644 index 0000000..5100018 --- /dev/null +++ b/tests/programs/pos_while_not_null.fzs @@ -0,0 +1,28 @@ +class Some { + Some next; +} + +Some l4 = new Some; + +Some l3 = new Some; +l3.next = l4; + +Some l2 = new Some; +l2.next = l3; + +Some l1 = new Some; +l1.next = l2; + +Some root = new Some; +root.next = l1; + +Some obj = root; +while (obj != null) +{ + obj = obj.next; + printline("Now object is:"); + printline(obj); +} + +printline(obj); + From c4eb148d17aae23622df22f631166e46501dcc3c Mon Sep 17 00:00:00 2001 From: samsonium Date: Sun, 25 Oct 2026 01:54:49 +0300 Subject: [PATCH 18/24] ref: temporary comment out infinite loop check --- src/compiler/fzl_walker.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/compiler/fzl_walker.c b/src/compiler/fzl_walker.c index 0f4de9c..b1414c2 100644 --- a/src/compiler/fzl_walker.c +++ b/src/compiler/fzl_walker.c @@ -258,7 +258,7 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) push_op(walker, FZL_OPCODE_MK_ENVTABLE); walker->syms[walker->sym_top++] = fzl_mksymt(); - size_t loop_begin_ip = walker->op_count; + // size_t loop_begin_ip = walker->op_count; if (node->loop.init) { @@ -316,16 +316,17 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) push_op(walker, FZL_OPCODE_RM_ENVTABLE); - bool is_infinite = - is_infinite_loop(walker, node, loop_begin_ip, walker->op_count - 2); - if (is_infinite) - { - free(newctx->breaks); - snprintf(wlast_err, sizeof(wlast_err), - "'%s': infinite loop detected at %ld:%ld", - node->token->filename, node->token->ln, node->token->cn); - return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; - } + // bool is_infinite = + // is_infinite_loop(walker, node, loop_begin_ip, walker->op_count - + // 2); + // if (is_infinite) + // { + // free(newctx->breaks); + // snprintf(wlast_err, sizeof(wlast_err), + // "'%s': infinite loop detected at %ld:%ld", + // node->token->filename, node->token->ln, node->token->cn); + // return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + // } for (size_t i = walker->sym_top - 1; i > newctx->initial_env_lvl; i--) fzl_freesymt(walker->syms[--walker->sym_top]); From a37b8cfe6eddbc6fd273c3e99dec016d8381662e Mon Sep 17 00:00:00 2001 From: samsonium Date: Sun, 25 Oct 2026 01:56:07 +0300 Subject: [PATCH 19/24] ref: remove test examples --- exl/base_math.fzs | 7 ------- exl/borrow.fzs | 46 ------------------------------------------ exl/class.fzs | 36 --------------------------------- exl/comment.fzs | 3 --- exl/conditions.fzs | 20 ------------------ exl/fn.fzs | 32 ----------------------------- exl/hw.fzs | 2 -- exl/print_empty.fzs | 1 - exl/problematic_hw.fzs | 1 - exl/raii.fzs | 46 ------------------------------------------ exl/variables.fzs | 13 ------------ 11 files changed, 207 deletions(-) delete mode 100644 exl/base_math.fzs delete mode 100644 exl/borrow.fzs delete mode 100644 exl/class.fzs delete mode 100644 exl/comment.fzs delete mode 100644 exl/conditions.fzs delete mode 100644 exl/fn.fzs delete mode 100644 exl/hw.fzs delete mode 100644 exl/print_empty.fzs delete mode 100644 exl/problematic_hw.fzs delete mode 100644 exl/raii.fzs delete mode 100644 exl/variables.fzs diff --git a/exl/base_math.fzs b/exl/base_math.fzs deleted file mode 100644 index b7e15c7..0000000 --- a/exl/base_math.fzs +++ /dev/null @@ -1,7 +0,0 @@ -printline(2 + 2 * 2); -printline(-1 + 1); -printline(2 ^ 2); -printline(1 + 1 * 1 / 1); -printline(4 % 2); -printline(10 / 2); - diff --git a/exl/borrow.fzs b/exl/borrow.fzs deleted file mode 100644 index 80c3d38..0000000 --- a/exl/borrow.fzs +++ /dev/null @@ -1,46 +0,0 @@ -class A -{ - uint some; -} - -fn doSome(A a) A -{ - // NOTE: section below won't change `a.some` value - - if (a.some < 1) - { - a.some = 1; - return a; - } - - return a; -} - -fn doSomeNew() A -{ - if (1 == 1) - { - return new A; - } -} - -A a = new A; -a.some = 0; -if (a.some != 0) -{ - printline("Test failed (first a.some != 0)"); - exit(1); -} -else printline(a); - -a = doSome(a); -if (a.some != 1) -{ - printline("Test failed (2nd a.some != 1)"); - exit(1); -} -else printline(a); - -A b = doSomeNew(); -printline(b); - diff --git a/exl/class.fzs b/exl/class.fzs deleted file mode 100644 index a7ac0f5..0000000 --- a/exl/class.fzs +++ /dev/null @@ -1,36 +0,0 @@ -class Point -{ - int x; - int y; - - // Move point by [dx,dy] - fn move(int dx, int dy) { - this.x = this.x + dx; - this.y = this.y + dy; - } -} - -Point obj = new Point; -obj.x = 1; -obj.y = 2; - -printline("Current X,Y:"); -printline(obj.x); -printline(obj.y); - -// NOTE: WHY THE FUCK THIS FUCKING COMPILER REQUIRES ';' AFTER '='?! -// | -// v -obj.move(9, 8); -printline("X,Y after move:"); -printline(obj.x); -printline(obj.y); - -if (obj.x == 10 && obj.y == 10) - printline("Test passed"); -else -{ - printline("Test failed"); - exit(1); -} - diff --git a/exl/comment.fzs b/exl/comment.fzs deleted file mode 100644 index 0fd39ee..0000000 --- a/exl/comment.fzs +++ /dev/null @@ -1,3 +0,0 @@ -// I'm a comment -printline("Comment is skipped");;;;; - diff --git a/exl/conditions.fzs b/exl/conditions.fzs deleted file mode 100644 index a49e79d..0000000 --- a/exl/conditions.fzs +++ /dev/null @@ -1,20 +0,0 @@ -bool flag = true; - -if (!flag) -{ - printline("buhhh, flag is not true"); - exit(1); -} -else if (2 + 2 >= 4) -{ - printline ("two plus two is four"); -} -else { - printline("gotcha"); - exit(1); -} - -{ - printline("block after"); -} - diff --git a/exl/fn.fzs b/exl/fn.fzs deleted file mode 100644 index 2d6214c..0000000 --- a/exl/fn.fzs +++ /dev/null @@ -1,32 +0,0 @@ -fn fib(int n) int -{ - if (n <= 1) - return n; - - return fib(n - 1) + fib(n - 2); -} - -fn sum(uint a, uint b) uint -{ - return a + b; -} - -printline("Test #1: fibonacci"); -int result = fib(7); -if (result == 13) - printline("Recurse test passed"); -else -{ - printline("Recurse test failed"); - exit(1); -} - -printline("Test #2: sum"); -if (sum(2, 2) == 4) - printline("Sum test passed"); -else -{ - printline("Sum test failed"); - exit(1); -} - diff --git a/exl/hw.fzs b/exl/hw.fzs deleted file mode 100644 index 131b32e..0000000 --- a/exl/hw.fzs +++ /dev/null @@ -1,2 +0,0 @@ -printline('hello, world'); - diff --git a/exl/print_empty.fzs b/exl/print_empty.fzs deleted file mode 100644 index 67c1545..0000000 --- a/exl/print_empty.fzs +++ /dev/null @@ -1 +0,0 @@ -printline(""); diff --git a/exl/problematic_hw.fzs b/exl/problematic_hw.fzs deleted file mode 100644 index ec0ce93..0000000 --- a/exl/problematic_hw.fzs +++ /dev/null @@ -1 +0,0 @@ -printline("hello); diff --git a/exl/raii.fzs b/exl/raii.fzs deleted file mode 100644 index f9e2e83..0000000 --- a/exl/raii.fzs +++ /dev/null @@ -1,46 +0,0 @@ -class Animal -{ - str type; - str name; - int age; - - fn copy() Animal - { - Animal cp = new Animal; - cp.type = this.type; - cp.name = this.name; - cp.age = this.age; - - return cp; - } -} - -Animal a = new Animal; -a.type = "Cow"; -a.name = "Buryatka"; -a.age = 5; - -Animal b = a.copy(); -b.name = "Gavryusha"; -b.age = 10; - -printline(a); -printline(b); - -printline("Types are same:"); -printline(a.type == b.type); - -printline("Names are NOT the same"); -printline(a.name != b.name); - -printline("1st age less than 2nd"); -printline(a.age < b.age); - -if (a.type == b.type && a.name != b.name && a.age < b.age) - printline("Test passed"); -else -{ - printline("Test failed"); - exit(1); -} - diff --git a/exl/variables.fzs b/exl/variables.fzs deleted file mode 100644 index 6ba588d..0000000 --- a/exl/variables.fzs +++ /dev/null @@ -1,13 +0,0 @@ -uint work_it = 21 * 2 ^ 2; -int make_it = 10; - -u64 do_it; -do_it = 15; - -printline(work_it); -printline(make_it + do_it); - -char make_us; -str floyd = "hello, Pink Floyd!"; -printline(floyd); - From e00e60958950002d3ef9ef5f98fb21d1de58997d Mon Sep 17 00:00:00 2001 From: samsonium Date: Sun, 25 Oct 2026 01:56:20 +0300 Subject: [PATCH 20/24] ref: rename 'hard-1' test to 'linked-list' --- exl/{hard-1.fzs => linked-list.fzs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename exl/{hard-1.fzs => linked-list.fzs} (100%) diff --git a/exl/hard-1.fzs b/exl/linked-list.fzs similarity index 100% rename from exl/hard-1.fzs rename to exl/linked-list.fzs From 04fdd58e4f5458c4de20e0e0864558cc0e665ee6 Mon Sep 17 00:00:00 2001 From: samsonium Date: Sun, 25 Oct 2026 04:51:37 +0300 Subject: [PATCH 21/24] fix: fix typo in examples --- exl/00_variables.fzs | 4 ++-- exl/01_conditions.fzs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/exl/00_variables.fzs b/exl/00_variables.fzs index 015a819..d0535c5 100644 --- a/exl/00_variables.fzs +++ b/exl/00_variables.fzs @@ -5,7 +5,7 @@ str someString = "world, hello!"; // 2. Change variable value: someString = "hello, world!"; -// Variables helps you store values to use it later +// Variables help you store values to use it later int a = 2; int b = a + a * a; int c = a * b; @@ -13,7 +13,7 @@ int c = a * b; printline("Now we got 12:"); printline(c); -// Also, there is many different types to help your program +// Also, there are many different variable types to help your program // work with many things: int signedInteger; uint unsignedInteger; diff --git a/exl/01_conditions.fzs b/exl/01_conditions.fzs index 1a73b9d..060dac0 100644 --- a/exl/01_conditions.fzs +++ b/exl/01_conditions.fzs @@ -1,5 +1,5 @@ // When you need to check some value -// a conditional statements might help you +// a conditional statement might help you int valueToCheck = 2 + 2; if (valueToCheck == 4) @@ -7,7 +7,7 @@ if (valueToCheck == 4) printline("Yay! 2 + 2 = 4"); } -// Conditional statement uses condition in pairs +// Conditional statement uses condition in parenthesis // after `if` keyword and executes command(s) after // if condition turns out to be true @@ -21,7 +21,7 @@ if (age <= 20) else { age = 20; - printline("You looks too young!"); + printline("You look too young!"); } // NOTE: If you need to execute only one statement From 3bb60071bb2685b43c1cfe2e5274c2ff1fbfd658 Mon Sep 17 00:00:00 2001 From: samsonium Date: Sun, 25 Oct 2026 04:52:07 +0300 Subject: [PATCH 22/24] feat: add infinite loop control args --- include/vm/fzl_vm.h | 2 +- src/vm/fzl_vm.c | 34 +++++++++++++++------------- src/vm/main.c | 55 ++++++++++++++++++++++++++++++++++++--------- 3 files changed, 63 insertions(+), 28 deletions(-) diff --git a/include/vm/fzl_vm.h b/include/vm/fzl_vm.h index cdd820f..92fedd7 100644 --- a/include/vm/fzl_vm.h +++ b/include/vm/fzl_vm.h @@ -36,7 +36,7 @@ typedef struct fzl_vm_t *fzl_mkvm(const char *filepath); // Load and run code -int fzl_vm_run(fzl_vm_t *vm); +int fzl_vm_run(fzl_vm_t *vm, bool allow_infinite, uint64_t max_iter); // Get last VM error message char *fzl_vm_geterror(); diff --git a/src/vm/fzl_vm.c b/src/vm/fzl_vm.c index b3ab61c..3537808 100644 --- a/src/vm/fzl_vm.c +++ b/src/vm/fzl_vm.c @@ -14,10 +14,7 @@ #include char last_err[256]; -size_t last_jmp_ip = 0; - -// NOTE: add `--max-iter` option for limit -size_t max_iter_limit = 100000; +size_t last_jmp_ip = 0; size_t total_jmp_on_same = 0; static char *expect_hdr = "FZL\0\1"; @@ -279,7 +276,7 @@ void print_err_data(fzl_vm_t *vm) printf("\t> [%zd] %lu\n", i, vm->cstack[i]); } -int fzl_vm_run(fzl_vm_t *vm) +int fzl_vm_run(fzl_vm_t *vm, bool allow_infinite, uint64_t max_iter) { if (!vm) { @@ -673,19 +670,24 @@ int fzl_vm_run(fzl_vm_t *vm) "runtime error - expected JMP addr"); return -1; } - uint64_t jmp_ip = vm->ops[++vm->ip]; - if (jmp_ip == last_jmp_ip) - total_jmp_on_same++; - else - total_jmp_on_same = 0; - if (total_jmp_on_same > max_iter_limit) + uint64_t jmp_ip = vm->ops[++vm->ip]; + if (!allow_infinite) { - snprintf(last_err, sizeof(last_err), - "runtime error - exceeded max loop jumps (infinite loop?)\n" - "If you thinks there's mistake, change jumps limit with " - "--max-iter=N"); - return -1; + if (jmp_ip == last_jmp_ip) + total_jmp_on_same++; + else + total_jmp_on_same = 0; + + if (total_jmp_on_same > max_iter) + { + snprintf( + last_err, sizeof(last_err), + "runtime error - exceeded max loop jumps (infinite loop?)\n" + "If you thinks there's mistake, change jumps limit with " + "--max-iter=N"); + return -1; + } } vm->ip = jmp_ip - 1; diff --git a/src/vm/main.c b/src/vm/main.c index 8be35f6..62dbef8 100644 --- a/src/vm/main.c +++ b/src/vm/main.c @@ -1,5 +1,7 @@ +#include #include #include +#include #include #include #include @@ -10,14 +12,18 @@ typedef struct char *filepath; bool help; bool debug; + bool allow_infinite; + uint64_t max_iter; } run_args_t; void print_hint() { printf("Usage: fz [flags] \n"); printf("Flags:\n"); - printf("\t-h\t--help\tprint usage information\n"); - printf("\t-d\t--debug\tdisplay program opcodes\n"); + printf("\t-h\t--help\t\t\tprint usage information\n"); + printf("\t-d\t--debug\t\t\tdisplay program opcodes\n"); + printf("\t\t--allow-infinite\tallow infinite loops\n"); + printf("\t\t--max-iter N\t\trestrict loops to maximum N iterations\n"); } // Parse passed arguments and build args setup structure @@ -29,10 +35,12 @@ run_args_t *parse_args(int argc, char **argv) return NULL; } - run_args_t *args = malloc(sizeof(run_args_t)); - args->filepath = NULL; - args->help = false; - args->debug = false; + run_args_t *args = malloc(sizeof(run_args_t)); + args->filepath = NULL; + args->help = false; + args->debug = false; + args->allow_infinite = false; + args->max_iter = 100000; for (int i = 0; i < argc; i++) { @@ -46,9 +54,33 @@ run_args_t *parse_args(int argc, char **argv) } else if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--debug") == 0) args->debug = true; + else if (strcmp(argv[i], "--allow-infinite") == 0) + args->allow_infinite = true; + else if (strcmp(argv[i], "--max-iter") == 0) + { + char *n = argv[++i]; + if (!n || argc <= i || !(n[0] >= '0' && n[0] <= '9')) + { + printf("%sError%s: --max-iter options requires number after it\n", + FZL_CERROR, FZL_CRST); + free(args); + return NULL; + } + + uint64_t iters = strtol(n, NULL, 10); + if (iters < 1) + { + printf("%sError%s: '--max-iter 0' is not allowed\n", FZL_CERROR, + FZL_CRST); + free(args); + return NULL; + } + + args->max_iter = iters; + } else { - printf("Error: unknown flag '%s'.\n", argv[i]); + printf("%sError%s: unknown flag '%s'.\n", FZL_CERROR, FZL_CRST, argv[i]); printf("Use 'fz --help' to read the usage\n"); free(args); return NULL; @@ -57,7 +89,8 @@ run_args_t *parse_args(int argc, char **argv) if (argv[argc - 1][0] == '-') { - printf("Error: last argument MUST be a file path\n"); + printf("%sError%s: last argument MUST be a file path\n", FZL_CERROR, + FZL_CRST); printf("Use 'fz --help' for help\n"); free(args); return NULL; @@ -83,7 +116,7 @@ int main(int argc, char **argv) fzl_vm_t *vm = fzl_mkvm(args->filepath); if (!vm) { - printf("Error: %s\n", fzl_vm_geterror()); + printf("%sError%s: %s\n", FZL_CERROR, FZL_CRST, fzl_vm_geterror()); free(args); return -1; } @@ -92,12 +125,12 @@ int main(int argc, char **argv) fzl_vm_print_ops(vm); else { - int result = fzl_vm_run(vm); + int result = fzl_vm_run(vm, args->allow_infinite, args->max_iter); if (result != 0) { char *err = fzl_vm_geterror(); if (err && err[0]) - printf("Error: %s\n", err); + printf("%sError%s: %s\n", FZL_CERROR, FZL_CRST, err); free(args); fzl_freevm(vm); From c03fddf47f524ef238973d752a54d04d47052f9e Mon Sep 17 00:00:00 2001 From: samsonium Date: Sun, 25 Oct 2026 04:52:18 +0300 Subject: [PATCH 23/24] fix: fixes in compiler --- src/compiler/checkers/fzl_loop_check.c | 2 + src/compiler/fzl_parser.c | 122 +++++++++++-------------- 2 files changed, 56 insertions(+), 68 deletions(-) diff --git a/src/compiler/checkers/fzl_loop_check.c b/src/compiler/checkers/fzl_loop_check.c index 8e3966d..2ce2288 100644 --- a/src/compiler/checkers/fzl_loop_check.c +++ b/src/compiler/checkers/fzl_loop_check.c @@ -11,6 +11,8 @@ ssize_t check_top; fzl_nodetype_t check_tree[256]; +// Ghost walk AST tree (w/o adding opcodes) to check if +// variable used in condition is changed void ghost_walk_changed(fzl_node_t *node, bool *changed, fzl_sym_t *sym) { if (!node || !changed || *changed) diff --git a/src/compiler/fzl_parser.c b/src/compiler/fzl_parser.c index 010bc03..5203385 100644 --- a/src/compiler/fzl_parser.c +++ b/src/compiler/fzl_parser.c @@ -158,7 +158,8 @@ fzl_node_t *parse_primary(fzl_parser_t *parser) if (!is(parser, FZL_TOKTYPE_RPAREN)) { snprintf(plast_err, sizeof(plast_err), "'%s': expected ')' at %ld:%ld", - peek(parser)->filename, peek(parser)->ln, peek(parser)->cn); + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); fzl_freeast(node); return NULL; } @@ -181,11 +182,12 @@ fzl_node_t *parse_primary(fzl_parser_t *parser) { fzl_token_t *type = consume(parser); fzl_token_t *name = consume(parser); - if (name->type != FZL_TOKTYPE_NAME) + if (!name || name->type != FZL_TOKTYPE_NAME) { snprintf(plast_err, sizeof(plast_err), - "'%s': expected name after type at %ld:%ld", type->filename, - type->ln, type->cn); + "'%s': expected name after type at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); return NULL; } @@ -197,8 +199,8 @@ fzl_node_t *parse_primary(fzl_parser_t *parser) if (!plast_err[0]) snprintf(plast_err, sizeof(plast_err), - "'%s': failed to parse primary at %ld:%ld", peek(parser)->filename, - peek(parser)->ln, peek(parser)->cn); + "'%s': expected expression at %ld:%ld", last_known_tok->filename, + last_known_tok->ln, last_known_tok->cn); return NULL; } @@ -211,13 +213,7 @@ fzl_node_t *parse_postfix(fzl_parser_t *parser) fzl_node_t *left = parse_primary(parser); if (!left) - { - if (!plast_err[0]) - snprintf(plast_err, sizeof(plast_err), - "'%s': failed to parse postfix at %ld:%ld", - peek(parser)->filename, peek(parser)->ln, peek(parser)->cn); return NULL; - } if (is(parser, FZL_TOKTYPE_LPAREN) || is(parser, FZL_TOKTYPE_DOT)) { @@ -260,7 +256,8 @@ fzl_node_t *parse_postfix(fzl_parser_t *parser) fzl_freeast(left); snprintf(plast_err, sizeof(plast_err), "'%s': expected ')' after argument list at %ld:%ld", - peek(parser)->filename, peek(parser)->ln, peek(parser)->cn); + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); return NULL; } @@ -327,13 +324,7 @@ fzl_node_t *parse_power(fzl_parser_t *parser) fzl_node_t *right = NULL; if (!left) - { - if (!plast_err[0]) - snprintf(plast_err, sizeof(plast_err), - "'%s': failed to parse power at %ld:%ld", peek(parser)->filename, - peek(parser)->ln, peek(parser)->cn); return NULL; - } if (is(parser, FZL_TOKTYPE_CARET)) { @@ -345,8 +336,9 @@ fzl_node_t *parse_power(fzl_parser_t *parser) fzl_freeast(left); if (!plast_err[0]) snprintf(plast_err, sizeof(plast_err), - "'%s': failed to parse right side of power at %ld:%ld", - peek(parser)->filename, peek(parser)->ln, peek(parser)->cn); + "'%s': expected expression at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); return NULL; } @@ -370,13 +362,7 @@ fzl_node_t *parse_factor(fzl_parser_t *parser) fzl_node_t *right = NULL; if (!left) - { - if (!plast_err[0]) - snprintf(plast_err, sizeof(plast_err), - "'%s': invalid expression at %ld:%ld", peek(parser)->filename, - peek(parser)->ln, peek(parser)->cn); return NULL; - } while (is(parser, FZL_TOKTYPE_STAR) || is(parser, FZL_TOKTYPE_SLASH) || is(parser, FZL_TOKTYPE_PERCENT)) @@ -389,8 +375,9 @@ fzl_node_t *parse_factor(fzl_parser_t *parser) fzl_freeast(left); if (!plast_err[0]) snprintf(plast_err, sizeof(plast_err), - "'%s': unfinished factor (mul/div) expression at %ld:%ld", - left->token->filename, left->token->ln, left->token->cn); + "'%s': expected expression at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); return NULL; } @@ -413,13 +400,7 @@ fzl_node_t *parse_term(fzl_parser_t *parser) fzl_node_t *right = NULL; if (!left) - { - if (!plast_err[0]) - snprintf(plast_err, sizeof(plast_err), - "'%s': invalid expression at %ld:%ld", peek(parser)->filename, - peek(parser)->ln, peek(parser)->cn); return NULL; - } while (is(parser, FZL_TOKTYPE_PLUS) || is(parser, FZL_TOKTYPE_MINUS)) { @@ -431,8 +412,9 @@ fzl_node_t *parse_term(fzl_parser_t *parser) fzl_freeast(left); if (!plast_err[0]) snprintf(plast_err, sizeof(plast_err), - "'%s': unfinished term (add/sub) expression at %ld:%ld", - left->token->filename, left->token->ln, left->token->cn); + "'%s': expected expression at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); return NULL; } @@ -466,10 +448,6 @@ fzl_node_t *parse_cond(fzl_parser_t *parser) if (!right) { fzl_freeast(left); - if (!plast_err[0]) - snprintf(plast_err, sizeof(plast_err), - "'%s': unfinished expression at %ld:%ld", op->filename, op->ln, - op->cn); return NULL; } @@ -525,10 +503,6 @@ fzl_node_t *parse_and(fzl_parser_t *parser) if (!right) { fzl_freeast(left); - if (!plast_err[0]) - snprintf(plast_err, sizeof(plast_err), - "'%s': unfinished expression at %ld:%ld", or->filename, or->ln, - or->cn); return NULL; } @@ -560,10 +534,6 @@ fzl_node_t *parse_or(fzl_parser_t *parser) if (!right) { fzl_freeast(left); - if (!plast_err[0]) - snprintf(plast_err, sizeof(plast_err), - "'%s': unfinished expression at %ld:%ld", or->filename, or->ln, - or->cn); return NULL; } @@ -589,7 +559,7 @@ fzl_node_t *parse_new(fzl_parser_t *parser) { fzl_freeast(objname); snprintf(plast_err, sizeof(plast_err), - "'%s': expect class name after 'new' keyword at %ld:%ld", + "'%s': expected class name after 'new' keyword at %ld:%ld", last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); return NULL; } @@ -610,14 +580,15 @@ fzl_node_t *parse_assignment(fzl_parser_t *parser) if (is(parser, FZL_TOKTYPE_EQ)) { fzl_token_t *asn = consume(parser); - if (left->type != FZL_NODETYPE_IDENTIFIER && - left->type != FZL_NODETYPE_DECLARATION && - left->type != FZL_NODETYPE_FIELD) + if (!left || (left->type != FZL_NODETYPE_IDENTIFIER && + left->type != FZL_NODETYPE_DECLARATION && + left->type != FZL_NODETYPE_FIELD)) { fzl_freeast(left); snprintf(plast_err, sizeof(plast_err), - "'%s': cannot assign to '%s' at %ld:%ld", left->token->filename, - (char *)left->token->value, asn->ln, asn->cn); + "'%s': cannot assign to '%s' at %ld:%ld", + last_known_tok->filename, (char *)left->token->value, + last_known_tok->ln, last_known_tok->cn); return NULL; } @@ -654,20 +625,20 @@ fzl_node_t *parse_class(fzl_parser_t *parser) fzl_token_t *class = consume(parser); fzl_token_t *name = consume(parser); - if (name->type != FZL_TOKTYPE_NAME) + if (!name || name->type != FZL_TOKTYPE_NAME) { snprintf(plast_err, sizeof(plast_err), - "'%s': expected class name at %ld:%ld", name->filename, name->ln, - name->cn); + "'%s': expected class name at %ld:%ld", last_known_tok->filename, + last_known_tok->ln, last_known_tok->cn); return NULL; } fzl_token_t *lbrace = consume(parser); - if (lbrace->type != FZL_TOKTYPE_LBRACE) + if (!lbrace || lbrace->type != FZL_TOKTYPE_LBRACE) { snprintf(plast_err, sizeof(plast_err), - "'%s': expected '{' after class name at %ld:%ld", lbrace->filename, - lbrace->ln, lbrace->cn); + "'%s': expected '{' after class name at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); return NULL; } @@ -706,9 +677,9 @@ fzl_node_t *parse_class(fzl_parser_t *parser) fields = realloc(fields, fremainder * sizeof(fzl_node_t *)); if (!fields) { - fzl_freeast(node); snprintf(plast_err, sizeof(plast_err), "failed to resize fields array"); + fzl_freeast(node); free(methods); return NULL; } @@ -724,9 +695,9 @@ fzl_node_t *parse_class(fzl_parser_t *parser) methods = realloc(methods, mremainder * sizeof(fzl_node_t *)); if (!methods) { - fzl_freeast(node); snprintf(plast_err, sizeof(plast_err), "failed to resize methods array"); + fzl_freeast(node); free(fields); return NULL; } @@ -761,7 +732,8 @@ fzl_node_t *parse_class(fzl_parser_t *parser) fzl_freeast(node); snprintf(plast_err, sizeof(plast_err), "'%s': unknown type of class member at %ld:%ld", - node->token->filename, node->token->ln, node->token->cn); + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); free(fields); free(methods); return NULL; @@ -795,7 +767,7 @@ fzl_node_t *parse_function(fzl_parser_t *parser) return NULL; fzl_token_t *fn = consume(parser); - if (fn->type != FZL_TOKTYPE_FUNCTION) + if (!fn || fn->type != FZL_TOKTYPE_FUNCTION) { snprintf(plast_err, sizeof(plast_err), "'%s': expected keyword 'fn' at %ld:%ld", last_known_tok->filename, @@ -1003,8 +975,8 @@ fzl_node_t *parse_block(fzl_parser_t *parser) free(stmts); snprintf(plast_err, sizeof(plast_err), - "'%s': missing '}' after block at %ld:%ld", rbrace->filename, - rbrace->ln, rbrace->cn); + "'%s': missing '}' after block at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); return NULL; } @@ -1019,7 +991,7 @@ fzl_node_t *parse_for(fzl_parser_t *parser) { fzl_token_t *tok = consume(parser); fzl_token_t *lpar = consume(parser); - if (lpar->type != FZL_TOKTYPE_LPAREN) + if (!lpar || lpar->type != FZL_TOKTYPE_LPAREN) { snprintf(plast_err, sizeof(plast_err), "'%s': expected '(' after for keyword at %ld:%ld", @@ -1202,6 +1174,13 @@ fzl_node_t *parse_loop(fzl_parser_t *parser) fzl_node_t *parse_break(fzl_parser_t *parser) { fzl_token_t *tok = consume(parser); + if (!tok) + { + snprintf(plast_err, sizeof(plast_err), "'%s': EOF", + last_known_tok->filename); + return NULL; + } + return fzl_mknode(FZL_NODETYPE_BREAK, tok); } @@ -1231,8 +1210,15 @@ fzl_node_t *parse_statement(fzl_parser_t *parser) if (!res) res = parse_expression(parser); + if (!res) + { + fzl_freeast(res); + return NULL; + } + if (!is(parser, FZL_TOKTYPE_SEMICOLON)) { + fzl_freeast(res); snprintf(plast_err, sizeof(plast_err), "'%s': missing ';' at %ld:%ld", last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); return NULL; From 6d1f1ce66e423b9f3c27c6621b099c8ec9a183f0 Mon Sep 17 00:00:00 2001 From: samsonium Date: Sun, 25 Oct 2026 04:57:01 +0300 Subject: [PATCH 24/24] fix: memory leak in postfix parsing function --- src/compiler/fzl_parser.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/compiler/fzl_parser.c b/src/compiler/fzl_parser.c index 5203385..9b3a2e8 100644 --- a/src/compiler/fzl_parser.c +++ b/src/compiler/fzl_parser.c @@ -232,7 +232,10 @@ fzl_node_t *parse_postfix(fzl_parser_t *parser) fzl_node_t *arg = parse_expression(parser); if (!arg) { + for (size_t i = 0; i < args_count; i++) + fzl_freeast(args[i]); free(args); + fzl_freeast(left); return NULL; } @@ -252,6 +255,8 @@ fzl_node_t *parse_postfix(fzl_parser_t *parser) fzl_token_t *rparen = consume(parser); if (!rparen || rparen->type != FZL_TOKTYPE_RPAREN) { + for (size_t i = 0; i < args_count; i++) + fzl_freeast(args[i]); free(args); fzl_freeast(left); snprintf(plast_err, sizeof(plast_err),