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..425124a 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 @@ -35,108 +36,31 @@ 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 "${CMAKE_CURRENT_BINARY_DIR}/p_${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) + 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() -# -# ========== 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 "negr_") + set_tests_properties("progs_runtime_${TEST_NAME}" PROPERTIES WILL_FAIL TRUE) + endif() +endforeach() 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"**. В ней будут лежать два скомпилированных бинарных файла: diff --git a/exl/00_variables.fzs b/exl/00_variables.fzs new file mode 100644 index 0000000..d0535c5 --- /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 help 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 are many different variable 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..060dac0 --- /dev/null +++ b/exl/01_conditions.fzs @@ -0,0 +1,30 @@ +// When you need to check some value +// a conditional statement might help you + +int valueToCheck = 2 + 2; +if (valueToCheck == 4) +{ + printline("Yay! 2 + 2 = 4"); +} + +// Conditional statement uses condition in parenthesis +// 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 look 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); + 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/hard-1.fzs b/exl/linked-list.fzs similarity index 100% rename from exl/hard-1.fzs rename to exl/linked-list.fzs 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/include/compiler/fzl_parser.h b/include/compiler/fzl_parser.h index f96c307..83c39da 100644 --- a/include/compiler/fzl_parser.h +++ b/include/compiler/fzl_parser.h @@ -18,6 +18,8 @@ typedef enum FZL_NODETYPE_CLASS, FZL_NODETYPE_FIELD, FZL_NODETYPE_CONSTRUCT, + FZL_NODETYPE_LOOP, + FZL_NODETYPE_BREAK, FZL_NODETYPE_ADD, FZL_NODETYPE_SUB, @@ -140,6 +142,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..db85fb0 100644 --- a/include/compiler/fzl_token.h +++ b/include/compiler/fzl_token.h @@ -14,6 +14,10 @@ typedef enum FZL_TOKTYPE_FUNCTION, // fn FZL_TOKTYPE_RETURN, // return + FZL_TOKTYPE_FOR, // for + FZL_TOKTYPE_WHILE, // while + FZL_TOKTYPE_BREAK, // break + 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/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/compiler/checkers/fzl_loop_check.c b/src/compiler/checkers/fzl_loop_check.c new file mode 100644 index 0000000..2ce2288 --- /dev/null +++ b/src/compiler/checkers/fzl_loop_check.c @@ -0,0 +1,229 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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) + return; + + check_tree[check_top++] = node->type; + + switch (node->type) + { + case FZL_NODETYPE_BREAK: + 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_BREAK: + 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, 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; + + fzl_sym_t sym; + bool sym_exists = retrieve_cond_sym(walker, node->loop.cond, &sym); + if (sym_exists) + { + if (sym.type >= FZL_TYPE_INT8) + { + bool value_changed = value_is_changed(node, &sym); + if (value_changed) + return false; + } + } + + // 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_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 e2d93b9..9b3a2e8 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) @@ -161,7 +158,9 @@ 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; } consume(parser); @@ -183,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; } @@ -199,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; } @@ -213,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)) { @@ -238,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; } @@ -258,10 +255,14 @@ 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), "'%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; } @@ -276,6 +277,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, @@ -327,13 +329,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)) { @@ -342,10 +338,12 @@ 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", - 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; } @@ -369,13 +367,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)) @@ -385,10 +377,12 @@ 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", - 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; } @@ -411,13 +405,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)) { @@ -426,10 +414,12 @@ 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", - 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; } @@ -462,10 +452,7 @@ fzl_node_t *parse_cond(fzl_parser_t *parser) right = parse_term(parser); if (!right) { - if (!plast_err[0]) - snprintf(plast_err, sizeof(plast_err), - "'%s': unfinished expression at %ld:%ld", op->filename, op->ln, - op->cn); + fzl_freeast(left); return NULL; } @@ -520,10 +507,7 @@ fzl_node_t *parse_and(fzl_parser_t *parser) right = parse_cond(parser); if (!right) { - if (!plast_err[0]) - snprintf(plast_err, sizeof(plast_err), - "'%s': unfinished expression at %ld:%ld", or->filename, or->ln, - or->cn); + fzl_freeast(left); return NULL; } @@ -554,10 +538,7 @@ fzl_node_t *parse_or(fzl_parser_t *parser) right = parse_and(parser); if (!right) { - if (!plast_err[0]) - snprintf(plast_err, sizeof(plast_err), - "'%s': unfinished expression at %ld:%ld", or->filename, or->ln, - or->cn); + fzl_freeast(left); return NULL; } @@ -581,8 +562,9 @@ 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", + "'%s': expected class name after 'new' keyword at %ld:%ld", last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); return NULL; } @@ -603,19 +585,24 @@ 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; } 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; @@ -643,20 +630,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; } @@ -697,6 +684,7 @@ fzl_node_t *parse_class(fzl_parser_t *parser) { snprintf(plast_err, sizeof(plast_err), "failed to resize fields array"); + fzl_freeast(node); free(methods); return NULL; } @@ -714,6 +702,7 @@ fzl_node_t *parse_class(fzl_parser_t *parser) { snprintf(plast_err, sizeof(plast_err), "failed to resize methods array"); + fzl_freeast(node); free(fields); return NULL; } @@ -726,6 +715,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; } @@ -744,9 +734,11 @@ 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); + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); free(fields); free(methods); return NULL; @@ -780,7 +772,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, @@ -830,6 +822,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); @@ -845,6 +838,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); @@ -853,7 +847,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; @@ -878,7 +875,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; } @@ -908,6 +908,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); @@ -917,14 +918,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); @@ -951,7 +959,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) { @@ -962,11 +973,15 @@ 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) { + 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); + "'%s': missing '}' after block at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); return NULL; } @@ -976,6 +991,204 @@ 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 || 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; +} + +// `break` keyword parsing +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); +} + // Statement parsing fzl_node_t *parse_statement(fzl_parser_t *parser) { @@ -994,12 +1207,23 @@ 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); + else if (is(parser, FZL_TOKTYPE_BREAK)) + res = parse_break(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; @@ -1079,6 +1303,7 @@ void fzl_freeast(fzl_node_t *root) { switch (root->type) { + case FZL_NODETYPE_BREAK: case FZL_NODETYPE_IDENTIFIER: case FZL_NODETYPE_LITERAL: break; @@ -1119,6 +1344,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..6d6db33 100644 --- a/src/compiler/fzl_reader.c +++ b/src/compiler/fzl_reader.c @@ -73,6 +73,14 @@ 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, "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 d4442f9..b1414c2 100644 --- a/src/compiler/fzl_walker.c +++ b/src/compiler/fzl_walker.c @@ -14,6 +14,9 @@ #include #include +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]; @@ -200,6 +203,146 @@ 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. 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(); + // size_t loop_begin_ip = walker->op_count; + + 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); + + // 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 = + // 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]); + + 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 +604,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 +883,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: @@ -1056,9 +1209,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 +1388,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); 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); diff --git a/src/vm/fzl_vm.c b/src/vm/fzl_vm.c index ce026e1..3537808 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,8 @@ #include char last_err[256]; +size_t last_jmp_ip = 0; +size_t total_jmp_on_same = 0; static char *expect_hdr = "FZL\0\1"; @@ -202,7 +205,7 @@ void print_object(fzl_vm_object_t *obj) printf(""); } - printf(">\n"); + printf(">"); } void print_err_data(fzl_vm_t *vm) @@ -273,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) { @@ -429,6 +432,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 <= @@ -666,8 +670,28 @@ int fzl_vm_run(fzl_vm_t *vm) "runtime error - expected JMP addr"); return -1; } + uint64_t jmp_ip = vm->ops[++vm->ip]; - vm->ip = jmp_ip - 1; + if (!allow_infinite) + { + 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; + last_jmp_ip = jmp_ip; break; } @@ -837,7 +861,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/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); 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/negr_infinite_for.fzs b/tests/programs/negr_infinite_for.fzs new file mode 100644 index 0000000..8a7eb77 --- /dev/null +++ b/tests/programs/negr_infinite_for.fzs @@ -0,0 +1,3 @@ +for (int i = 0; i < 5; i = i + 0) +{} + diff --git a/tests/programs/negr_infinite_while.fzs b/tests/programs/negr_infinite_while.fzs new file mode 100644 index 0000000..59c1072 --- /dev/null +++ b/tests/programs/negr_infinite_while.fzs @@ -0,0 +1,3 @@ +while (true) +{} + diff --git a/exl/base_math.fzs b/tests/programs/pos_arithmetic.fzs similarity index 70% rename from exl/base_math.fzs rename to tests/programs/pos_arithmetic.fzs index b7e15c7..ed1907e 100644 --- a/exl/base_math.fzs +++ b/tests/programs/pos_arithmetic.fzs @@ -1,7 +1,7 @@ +// Arithmetic precedence and operators. printline(2 + 2 * 2); printline(-1 + 1); printline(2 ^ 2); -printline(1 + 1 * 1 / 1); 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_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); +} + 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/exl/variables.fzs b/tests/programs/pos_variables.fzs similarity index 70% rename from exl/variables.fzs rename to tests/programs/pos_variables.fzs index 6ba588d..b9d3277 100644 --- a/exl/variables.fzs +++ b/tests/programs/pos_variables.fzs @@ -1,13 +1,13 @@ uint work_it = 21 * 2 ^ 2; -int make_it = 10; - -u64 do_it; +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); - -char make_us; -str floyd = "hello, Pink Floyd!"; printline(floyd); +printline(flag); 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); +