From 645120eda61d2ad7bd9ecbc56dc0991b8b00cef3 Mon Sep 17 00:00:00 2001 From: samsonium Date: Sat, 17 Oct 2026 19:24:03 +0300 Subject: [PATCH 01/14] feat: implement objects moving and borrowing --- .gdbinit | 5 + CMakeLists.txt | 16 ++++ exl/borrow.fzs | 26 ++++++ exl/raii.fzs | 34 +++++++ include/common/fzl_opcodes.h | 13 ++- include/vm/fzl_vm_env.h | 5 + src/compiler/fzl_walker.c | 162 +++++++++++++++++++++++++-------- src/vm/fzl_vm.c | 172 ++++++++++++++++++++++++++++++++++- src/vm/fzl_vm_debug.c | 20 ++++ src/vm/fzl_vm_env.c | 20 ++++ 10 files changed, 432 insertions(+), 41 deletions(-) create mode 100644 exl/borrow.fzs create mode 100644 exl/raii.fzs diff --git a/.gdbinit b/.gdbinit index 56124a5..fe3e786 100644 --- a/.gdbinit +++ b/.gdbinit @@ -15,4 +15,9 @@ skip fzl_env_lookup skip fzl_env_contains skip fzl_env_storevalue skip fzl_env_writevalue +skip fzl_mkctable +skip fzl_freectable +skip tok_to_type +skip nest_ctx +skip fin_ctx diff --git a/CMakeLists.txt b/CMakeLists.txt index 925fd71..1cab213 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,6 +70,14 @@ 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" +) set_tests_properties(compile_problematic_hw PROPERTIES WILL_FAIL TRUE) @@ -109,4 +117,12 @@ 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" +) diff --git a/exl/borrow.fzs b/exl/borrow.fzs new file mode 100644 index 0000000..15658b7 --- /dev/null +++ b/exl/borrow.fzs @@ -0,0 +1,26 @@ +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; + // } + a.some = 1; + + return a; +} + +A a = new A; +a.some = 0; +printline(a); + +a = doSome(a); +printline(a); + diff --git a/exl/raii.fzs b/exl/raii.fzs new file mode 100644 index 0000000..998d06e --- /dev/null +++ b/exl/raii.fzs @@ -0,0 +1,34 @@ +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(); +a.name = "Gavryusha"; +a.age = 10; + +printline(a); +printline(b); + +if (a.type == b.type && a.name != b.name && a.age < b.age) + printline("Test passed"); +else + printline("Test failed"); + diff --git a/include/common/fzl_opcodes.h b/include/common/fzl_opcodes.h index a0d8ac3..35e26a0 100644 --- a/include/common/fzl_opcodes.h +++ b/include/common/fzl_opcodes.h @@ -21,9 +21,10 @@ typedef enum FZL_OPCODE_CALL = 0xF30001, // Call function FZL_OPCODE_RET = 0xF30002, // Return from function - FZL_OPCODE_ALLOC_OBJECT = 0xFA0001, // Allocate N slots - FZL_OPCODE_GET_FIELD = 0xFA0002, // Get field N of an object at dstack - FZL_OPCODE_SET_FIELD = 0xFA0003, // Set field N of an object at dstack + FZL_OPCODE_ALLOC_OBJECT = 0xFA0001, // Allocate N slots + FZL_OPCODE_GET_FIELD = 0xFA0002, // Get field N of an object at dstack + FZL_OPCODE_SET_FIELD = 0xFA0003, // Set field N of an object at dstack + FZL_OPCODE_ESCAPE_OBJECT = 0xFA0004, // Make parent envtable as owner FZL_OPCODE_ADD = 0xA00001, // Sum two numbers in dstack FZL_OPCODE_SUB = 0xA00002, // Subract two numbers in dstack @@ -40,6 +41,12 @@ typedef enum FZL_OPCODE_CMP_GT = 0xB00005, // Greater than FZL_OPCODE_CMP_GTEQ = 0xB00006, // Greater than or equal + FZL_OPCODE_CMP_EQ_STR = 0xB10001, // Strings equality + FZL_OPCODE_CMP_NEQ_STR = 0xB10002, // Strings non-equality + + FZL_OPCODE_CMP_EQ_OBJ = 0xB20001, // Objects equality (by pointer) + FZL_OPCODE_CMP_NEQ_OBJ = 0xB20002, // Objects non-equality (by pointer) + FZL_OPCODE_AND = 0xC00001, // A && B FZL_OPCODE_OR = 0xC00002, // A || B diff --git a/include/vm/fzl_vm_env.h b/include/vm/fzl_vm_env.h index 3bd1ecc..6c7b139 100644 --- a/include/vm/fzl_vm_env.h +++ b/include/vm/fzl_vm_env.h @@ -4,6 +4,7 @@ #include #include #include +#include typedef enum { @@ -27,6 +28,10 @@ typedef struct size_t remainder; fzl_vm_env_entry_t *table; + + fzl_vm_object_t **owned_objects; + size_t owned_objects_remainder; + size_t owned_objects_count; } fzl_vm_env_t; // Allocate env table diff --git a/src/compiler/fzl_walker.c b/src/compiler/fzl_walker.c index 05206b8..a09a9b9 100644 --- a/src/compiler/fzl_walker.c +++ b/src/compiler/fzl_walker.c @@ -65,6 +65,9 @@ fzl_walker_t *fzl_mkwalker(fzl_node_t *root) // Convert type token to `fzl_type_t` const fzl_type_t tok_to_type(fzl_token_t *tok) { + if (tok->type == FZL_TOKTYPE_NAME) + return FZL_TYPE_OBJECT; + if (strcmp((char *)tok->value, "i8") == 0) return FZL_TYPE_INT8; if (strcmp((char *)tok->value, "i16") == 0) @@ -98,8 +101,14 @@ const fzl_type_t tok_to_type(fzl_token_t *tok) return FZL_TYPE_NULL; } -static bool is_compatible(fzl_type_t t1, fzl_type_t t2) +static bool is_compatible(fzl_type_t t1, fzl_type_t t2, char *c1, char *c2) { + if (t1 == FZL_TYPE_OBJECT || t2 == FZL_TYPE_OBJECT) + { + return t1 == FZL_TYPE_OBJECT && t2 == FZL_TYPE_OBJECT && + (strcmp(c1, c2) == 0); + } + if (t1 == t2) return true; @@ -369,6 +378,7 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) fzl_symtable_res_t putres = fzl_symt_put(walker->syms[walker->sym_top - 1], node->fn.name->value, fntype, walker->op_count); + if (putres != FZL_SYMTABLE_RESULT_OK) { if (putres == FZL_SYMTABLE_RESULT_EXISTS) @@ -382,7 +392,23 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) "'%s': failed to store name '%s'", node->fn.name->filename, (char *)node->fn.name->value); - return (fzl_walk_res_t){.error = true}; + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + if (fntype == FZL_TYPE_OBJECT) + { + fzl_symtable_res_t updres = + fzl_symt_setobjclass(walker->syms[walker->sym_top - 1], + node->fn.name->value, node->fn.type->value); + if (updres != FZL_SYMTABLE_RESULT_OK) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': failed to assign object type to function return " + "type at %ld:%ld", + node->fn.type->filename, node->fn.type->ln, + node->fn.type->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } } // NOTE: add sym_top limit check @@ -430,7 +456,12 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) for (ssize_t i = walker->sym_top - 1; i >= lvl; i--) fzl_freesymt(walker->syms[--walker->sym_top]); - current_res = (fzl_walk_res_t){.type = fntype, .error = false}; + char *retclass = NULL; + if (fntype == FZL_TYPE_OBJECT && node->fn.type) + retclass = node->fn.type->value; + + current_res = (fzl_walk_res_t){ + .type = fntype, .error = false, .classname = retclass}; break; } @@ -445,14 +476,15 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; } + bool returning_object = false; if (node->ret.value) { fzl_walk_res_t retres = walk(walker, node->ret.value); if (retres.error) return retres; - if (!is_compatible(retres.type, - tok_to_type(walker->ctx->fn->fn.type))) + if (!is_compatible(retres.type, tok_to_type(walker->ctx->fn->fn.type), + retres.classname, walker->ctx->fn->fn.type->value)) { snprintf( wlast_err, sizeof(wlast_err), @@ -462,14 +494,23 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; } - current_res = (fzl_walk_res_t){.type = retres.type, .error = false}; + if (retres.type == FZL_TYPE_OBJECT) + returning_object = true; + + current_res = (fzl_walk_res_t){.type = retres.type, + .error = false, + .classname = retres.classname}; } else current_res = (fzl_walk_res_t){.type = FZL_TYPE_NULL, .error = false}; for (size_t i = 0; i < (walker->sym_top - walker->ctx->initial_env_lvl); i++) + { + if (returning_object) + push_op(walker, FZL_OPCODE_ESCAPE_OBJECT); push_op(walker, FZL_OPCODE_RM_ENVTABLE); + } push_op(walker, FZL_OPCODE_RET); break; } @@ -500,7 +541,8 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) if (right.error) return right; - if (!is_compatible(left.type, right.type)) + if (!is_compatible(left.type, right.type, left.classname, + right.classname)) { snprintf(wlast_err, sizeof(wlast_err), "'%s': incompatible types at %ld:%ld", node->token->filename, @@ -508,31 +550,72 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; } - switch (node->type) + if (left.type == FZL_TYPE_STRING && right.type == FZL_TYPE_STRING) { - case FZL_NODETYPE_EQUAL: - push_op(walker, FZL_OPCODE_CMP_EQ); - break; - case FZL_NODETYPE_NEQUAL: - push_op(walker, FZL_OPCODE_CMP_NEQ); - break; - case FZL_NODETYPE_LESS: - push_op(walker, FZL_OPCODE_CMP_LT); - break; - case FZL_NODETYPE_LESS_EQUAL: - push_op(walker, FZL_OPCODE_CMP_LTEQ); - break; - case FZL_NODETYPE_GREATER: - push_op(walker, FZL_OPCODE_CMP_GT); - break; - case FZL_NODETYPE_GREATER_EQUAL: - push_op(walker, FZL_OPCODE_CMP_GTEQ); - break; - default: - break; + switch (node->type) + { + case FZL_NODETYPE_EQUAL: + push_op(walker, FZL_OPCODE_CMP_EQ_STR); + break; + + case FZL_NODETYPE_NEQUAL: + push_op(walker, FZL_OPCODE_CMP_NEQ_STR); + break; + + default: + snprintf(wlast_err, sizeof(wlast_err), + "'%s': unsupported strings operation at %ld:%ld", + node->token->filename, node->token->ln, node->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + } + else if (left.type == FZL_TYPE_OBJECT && right.type == FZL_TYPE_OBJECT) + { + switch (node->type) + { + case FZL_NODETYPE_EQUAL: + push_op(walker, FZL_OPCODE_CMP_EQ_OBJ); + break; + + case FZL_NODETYPE_NEQUAL: + push_op(walker, FZL_OPCODE_CMP_NEQ_OBJ); + break; + + default: + snprintf(wlast_err, sizeof(wlast_err), + "'%s': unsupported object operation at %ld:%ld", + node->token->filename, node->token->ln, node->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + } + else + { + switch (node->type) + { + case FZL_NODETYPE_EQUAL: + push_op(walker, FZL_OPCODE_CMP_EQ); + break; + case FZL_NODETYPE_NEQUAL: + push_op(walker, FZL_OPCODE_CMP_NEQ); + break; + case FZL_NODETYPE_LESS: + push_op(walker, FZL_OPCODE_CMP_LT); + break; + case FZL_NODETYPE_LESS_EQUAL: + push_op(walker, FZL_OPCODE_CMP_LTEQ); + break; + case FZL_NODETYPE_GREATER: + push_op(walker, FZL_OPCODE_CMP_GT); + break; + case FZL_NODETYPE_GREATER_EQUAL: + push_op(walker, FZL_OPCODE_CMP_GTEQ); + break; + default: + break; + } } - current_res = (fzl_walk_res_t){.type = left.type, .error = false}; + current_res = (fzl_walk_res_t){.type = FZL_TYPE_BOOL, .error = false}; break; } @@ -547,7 +630,8 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) if (right.error) return right; - if (!is_compatible(left.type, right.type)) + if (!is_compatible(left.type, right.type, left.classname, + right.classname)) { snprintf(wlast_err, sizeof(wlast_err), "'%s': incompatible types at %ld:%ld", node->token->filename, @@ -560,7 +644,7 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) else push_op(walker, FZL_OPCODE_OR); - current_res = (fzl_walk_res_t){.type = left.type, .error = false}; + current_res = (fzl_walk_res_t){.type = FZL_TYPE_BOOL, .error = false}; break; } @@ -717,7 +801,8 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) push_op(walker, FZL_OPCODE_CALL); push_word(walker, res.ip); - current_res = (fzl_walk_res_t){.type = res.type, .error = false}; + current_res = (fzl_walk_res_t){ + .type = res.type, .error = false, .classname = res.classname}; } else if (node->call.callee->type == FZL_NODETYPE_FIELD) { @@ -810,7 +895,8 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) push_op(walker, FZL_OPCODE_CALL); push_word(walker, res.ip); - current_res = (fzl_walk_res_t){.type = res.type, .error = false}; + current_res = (fzl_walk_res_t){ + .type = res.type, .error = false, .classname = res.classname}; } } break; @@ -972,7 +1058,9 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) push_op(walker, FZL_OPCODE_LOADCONST); push_word(walker, const_n); - current_res = (fzl_walk_res_t){.type = FZL_TYPE_NULL, .error = false}; + current_res = (fzl_walk_res_t){.type = FZL_TYPE_NULL, + .error = false, + .classname = node->var.type->value}; break; } @@ -1016,7 +1104,8 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; } - if (!is_compatible(sym.type, rres.type)) + if (!is_compatible(sym.type, rres.type, sym.classname, + rres.classname)) { fzl_token_t *tok = node->binary_op.right->token; snprintf(wlast_err, sizeof(wlast_err), @@ -1091,7 +1180,8 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; } - current_res = (fzl_walk_res_t){.type = rres.type, .error = false}; + current_res = (fzl_walk_res_t){ + .type = rres.type, .error = false, .classname = rres.classname}; break; } } diff --git a/src/vm/fzl_vm.c b/src/vm/fzl_vm.c index 7c22d8f..5f7539a 100644 --- a/src/vm/fzl_vm.c +++ b/src/vm/fzl_vm.c @@ -110,6 +110,43 @@ fzl_vm_t *fzl_mkvm(const char *filepath) return vm; } +void print_object(fzl_vm_object_t *obj) +{ + if (!obj) + { + printf("\n"); + return; + } + + printf("obj<"); + for (size_t i = 0; i < obj->values_count; i++) + { + fzl_value_t arg = obj->values[i]; + + if (i > 0) + printf(", "); + + if (arg.type == FZL_TYPE_STRING) + printf("str(%s)", arg.as.s.value); + else if (arg.type == FZL_TYPE_CHAR) + printf("char(%c)", arg.as.c); + else if (arg.type == FZL_TYPE_INT) + printf("int(%ld)", arg.as.i); + else if (arg.type == FZL_TYPE_UINT) + printf("uint(%lu)", arg.as.u); + else if (arg.type == FZL_TYPE_BOOL) + printf("bool(%b)", arg.as.b); + else if (arg.type == FZL_TYPE_NULL) + printf(""); + else if (arg.type == FZL_TYPE_OBJECT) + print_object(arg.as.obj); + else + printf(""); + } + + printf(">\n"); +} + int fzl_vm_run(fzl_vm_t *vm) { if (!vm) @@ -125,6 +162,125 @@ int fzl_vm_run(fzl_vm_t *vm) switch (opcode) { + case FZL_OPCODE_CMP_EQ_STR: + { + if (vm->dtop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected two values in data stack"); + return -1; + } + fzl_value_t left = vm->dstack[--vm->dtop]; + fzl_value_t right = vm->dstack[--vm->dtop]; + + int result = strcmp(left.as.s.value, right.as.s.value); + vm->dstack[vm->dtop++] = + (fzl_value_t){.type = FZL_TYPE_BOOL, .as.b = result == 0}; + break; + } + + case FZL_OPCODE_CMP_NEQ_STR: + { + if (vm->dtop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected two values in data stack"); + return -1; + } + fzl_value_t left = vm->dstack[--vm->dtop]; + fzl_value_t right = vm->dstack[--vm->dtop]; + + int result = strcmp(left.as.s.value, right.as.s.value); + vm->dstack[vm->dtop++] = + (fzl_value_t){.type = FZL_TYPE_BOOL, .as.b = result != 0}; + break; + } + + case FZL_OPCODE_CMP_EQ_OBJ: + { + if (vm->dtop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected two values in data stack"); + return -1; + } + fzl_value_t left = vm->dstack[--vm->dtop]; + fzl_value_t right = vm->dstack[--vm->dtop]; + + vm->dstack[vm->dtop++] = (fzl_value_t){ + .type = FZL_TYPE_BOOL, .as.b = &left.as.obj == &right.as.obj}; + break; + } + + case FZL_OPCODE_CMP_NEQ_OBJ: + { + if (vm->dtop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected two values in data stack"); + return -1; + } + fzl_value_t left = vm->dstack[--vm->dtop]; + fzl_value_t right = vm->dstack[--vm->dtop]; + + vm->dstack[vm->dtop++] = (fzl_value_t){ + .type = FZL_TYPE_BOOL, .as.b = &left.as.obj != &right.as.obj}; + break; + } + + case FZL_OPCODE_ESCAPE_OBJECT: + { + if (vm->dtop < 1) + { + snprintf(last_err, sizeof(last_err), + "runtime error - missing value in data stack"); + return -1; + } + fzl_value_t obj = vm->dstack[--vm->dtop]; + if (obj.type != FZL_TYPE_OBJECT) + { + snprintf(last_err, sizeof(last_err), + "runtime error - cannot escape non-object type"); + return -1; + } + + fzl_vm_env_t *parent = vm->estack[vm->etop - 2]; + fzl_vm_env_t *current = vm->estack[vm->etop - 1]; + if (!parent || vm->etop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - cannot escape object from global env"); + return -1; + } + + bool found = false; + for (size_t i = 0; i < current->owned_objects_count; i++) + { + if (obj.as.obj == current->owned_objects[i]) + { + found = true; + current->owned_objects[i] = NULL; + break; + } + } + + if (found) + { + if (parent->owned_objects_remainder <= parent->owned_objects_count) + { + parent->owned_objects_remainder *= 2; + parent->owned_objects = + realloc(parent->owned_objects, parent->owned_objects_remainder * + sizeof(fzl_vm_object_t *)); + } + + parent->owned_objects[parent->owned_objects_count++] = obj.as.obj; + } + vm->dstack[vm->dtop++] = obj; + + break; + } + case FZL_OPCODE_ALLOC_OBJECT: { uint64_t values_count = vm->ops[++vm->ip]; @@ -132,7 +288,17 @@ int fzl_vm_run(fzl_vm_t *vm) obj->values = malloc(values_count * sizeof(fzl_value_t)); obj->values_count = values_count; - vm->objects[vm->objects_count++] = obj; + fzl_vm_env_t *current_env = vm->estack[vm->etop - 1]; + if (current_env->owned_objects_remainder <= + current_env->owned_objects_count) + { + current_env->owned_objects_remainder *= 2; + current_env->owned_objects = realloc( + current_env->owned_objects, + current_env->owned_objects_remainder * sizeof(fzl_vm_object_t *)); + } + + current_env->owned_objects[current_env->owned_objects_count++] = obj; vm->dstack[vm->dtop++] = (fzl_value_t){.type = FZL_TYPE_OBJECT, .as.obj = obj}; break; @@ -260,8 +426,8 @@ int fzl_vm_run(fzl_vm_t *vm) return -1; } - fzl_value_t rlt = vm->dstack[--vm->dtop]; fzl_value_t llt = vm->dstack[--vm->dtop]; + fzl_value_t rlt = vm->dstack[--vm->dtop]; vm->dstack[vm->dtop++] = (fzl_value_t){.type = FZL_TYPE_BOOL, .as.b = llt.as.u < rlt.as.u}; break; @@ -474,6 +640,8 @@ int fzl_vm_run(fzl_vm_t *vm) printf("%b\n", arg.as.b); else if (arg.type == FZL_TYPE_NULL) printf("\n"); + else if (arg.type == FZL_TYPE_OBJECT) + print_object(arg.as.obj); } else { diff --git a/src/vm/fzl_vm_debug.c b/src/vm/fzl_vm_debug.c index 9f3f3ef..26404ad 100644 --- a/src/vm/fzl_vm_debug.c +++ b/src/vm/fzl_vm_debug.c @@ -20,6 +20,26 @@ void fzl_vm_print_ops(fzl_vm_t *vm) switch (vm->ops[i]) { + case FZL_OPCODE_CMP_EQ_STR: + printf("CMP_EQ_STR\n"); + break; + + case FZL_OPCODE_CMP_NEQ_STR: + printf("CMP_NEQ_STR\n"); + break; + + case FZL_OPCODE_CMP_EQ_OBJ: + printf("CMP_EQ_OBJ\n"); + break; + + case FZL_OPCODE_CMP_NEQ_OBJ: + printf("CMP_NEQ_OBJ\n"); + break; + + case FZL_OPCODE_ESCAPE_OBJECT: + printf("ESCAPE_OBJ\n"); + break; + case FZL_OPCODE_ALLOC_OBJECT: printf("ALLOC_OBJECT: %lu fields\n", vm->ops[++i]); break; diff --git a/src/vm/fzl_vm_env.c b/src/vm/fzl_vm_env.c index 2ca4ede..1abe7d1 100644 --- a/src/vm/fzl_vm_env.c +++ b/src/vm/fzl_vm_env.c @@ -1,3 +1,4 @@ +#include "vm/fzl_vm_object.h" #include #include #include @@ -15,6 +16,11 @@ fzl_vm_env_t *fzl_mkenv() env->size = 0; env->table = malloc(env->remainder * sizeof(fzl_vm_env_entry_t)); + env->owned_objects_remainder = 2; + env->owned_objects_count = 0; + env->owned_objects = + malloc(env->owned_objects_remainder * sizeof(fzl_vm_object_t *)); + return env; } @@ -96,5 +102,19 @@ void fzl_freeenv(fzl_vm_env_t *env) if (env->table) free(env->table); + if (env->owned_objects) + { + for (size_t i = 0; i < env->owned_objects_count; i++) + { + if (!env->owned_objects[i]) + continue; + + free(env->owned_objects[i]->values); + free(env->owned_objects[i]); + } + + free(env->owned_objects); + } + free(env); } From f1e97b94a4a427fb43b8d5f5df3c7470fe4b2c96 Mon Sep 17 00:00:00 2001 From: samsonium Date: Sat, 17 Oct 2026 20:20:10 +0300 Subject: [PATCH 02/14] fix: fix toke->value assign on name-not-found error --- src/compiler/fzl_walker.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/fzl_walker.c b/src/compiler/fzl_walker.c index a09a9b9..783c629 100644 --- a/src/compiler/fzl_walker.c +++ b/src/compiler/fzl_walker.c @@ -1097,7 +1097,7 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) if (!found) { - fzl_token_t *tok = node->binary_op.left->token->value; + fzl_token_t *tok = node->binary_op.left->token; snprintf(wlast_err, sizeof(wlast_err), "'%s': name '%s' is not exists at %ld:%ld", tok->filename, (char *)tok->value, tok->ln, tok->cn); From b48c1965093783ca9b458e66872db2a737d7a161 Mon Sep 17 00:00:00 2001 From: samsonium Date: Sat, 17 Oct 2026 20:20:54 +0300 Subject: [PATCH 03/14] upd: new test in borrow-move example --- exl/borrow.fzs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/exl/borrow.fzs b/exl/borrow.fzs index 15658b7..660be89 100644 --- a/exl/borrow.fzs +++ b/exl/borrow.fzs @@ -17,6 +17,14 @@ fn doSome(A a) A return a; } +fn doSomeNew() A +{ + if (1 == 1) + { + return new A; + } +} + A a = new A; a.some = 0; printline(a); @@ -24,3 +32,6 @@ printline(a); a = doSome(a); printline(a); +b = doSomeNew(); +printline(b); + From ce13d24985163275abc3e7bd42f0cbd48a58c481 Mon Sep 17 00:00:00 2001 From: samsonium Date: Sat, 17 Oct 2026 20:21:08 +0300 Subject: [PATCH 04/14] upd: optimize gdbinit script --- .gdbinit | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/.gdbinit b/.gdbinit index fe3e786..e22660a 100644 --- a/.gdbinit +++ b/.gdbinit @@ -2,21 +2,13 @@ skip peek skip consume skip next skip is -skip fzl_mknode -skip fzl_mktoken -skip fzl_symt_put -skip fzl_symt_get +skip -rfunction fzl_mk* +skip -rfunction fzl_free* +skip -rfunction fzl_symt* skip add_const skip push_op skip push_word -skip fzl_mkenv -skip fzl_freeenv -skip fzl_env_lookup -skip fzl_env_contains -skip fzl_env_storevalue -skip fzl_env_writevalue -skip fzl_mkctable -skip fzl_freectable +skip -rfunction fzl_env* skip tok_to_type skip nest_ctx skip fin_ctx From 8c33464656ef7dd874431e5770b1b4835e3022fe Mon Sep 17 00:00:00 2001 From: samsonium Date: Sat, 17 Oct 2026 21:23:50 +0300 Subject: [PATCH 05/14] feat: add `null` as value token --- include/compiler/fzl_token.h | 1 + src/compiler/fzl_parser.c | 15 ++++----------- src/compiler/fzl_reader.c | 5 ++++- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/include/compiler/fzl_token.h b/include/compiler/fzl_token.h index 30b5f81..a051cd0 100644 --- a/include/compiler/fzl_token.h +++ b/include/compiler/fzl_token.h @@ -34,6 +34,7 @@ typedef enum FZL_TOKTYPE_STR_LITERAL, // "" FZL_TOKTYPE_NUM_LITERAL, // 123 FZL_TOKTYPE_BOOL_LITERAL, // true + FZL_TOKTYPE_NULL_LITERAL, // null FZL_TOKTYPE_DAMPERSAND, // && FZL_TOKTYPE_DVBAR, // || diff --git a/src/compiler/fzl_parser.c b/src/compiler/fzl_parser.c index 8c6f8ba..43154fc 100644 --- a/src/compiler/fzl_parser.c +++ b/src/compiler/fzl_parser.c @@ -140,17 +140,10 @@ fzl_node_t *parse_primary(fzl_parser_t *parser) if (!parser || !peek(parser)) return NULL; - if (is(parser, FZL_TOKTYPE_STR_LITERAL)) - { - fzl_token_t *tok = consume(parser); - return fzl_mknode(FZL_NODETYPE_LITERAL, tok); - } - else if (is(parser, FZL_TOKTYPE_NUM_LITERAL)) - { - fzl_token_t *tok = consume(parser); - return fzl_mknode(FZL_NODETYPE_LITERAL, tok); - } - else if (is(parser, FZL_TOKTYPE_BOOL_LITERAL)) + if (is(parser, FZL_TOKTYPE_STR_LITERAL) || + is(parser, FZL_TOKTYPE_NUM_LITERAL) || + is(parser, FZL_TOKTYPE_BOOL_LITERAL) || + is(parser, FZL_TOKTYPE_NULL_LITERAL)) { fzl_token_t *tok = consume(parser); return fzl_mknode(FZL_NODETYPE_LITERAL, tok); diff --git a/src/compiler/fzl_reader.c b/src/compiler/fzl_reader.c index bc4e3e5..683fb3f 100644 --- a/src/compiler/fzl_reader.c +++ b/src/compiler/fzl_reader.c @@ -88,13 +88,16 @@ fzl_token_t *parse_name(fzl_reader_t *reader) else if (strcmp(value, "true") == 0 || strcmp(value, "false") == 0) tok = fzl_mktoken(FZL_TOKTYPE_BOOL_LITERAL, l.line, l.col, value, size, l.filename); + else if (strcmp(value, "null") == 0) + tok = fzl_mktoken(FZL_TOKTYPE_NULL_LITERAL, l.line, l.col, value, size, + l.filename); else if (strcmp(value, "str") == 0 || strcmp(value, "char") == 0 || strcmp(value, "bool") == 0 || strcmp(value, "uint") == 0 || strcmp(value, "int") == 0 || strcmp(value, "i8") == 0 || strcmp(value, "i16") == 0 || strcmp(value, "i32") == 0 || strcmp(value, "i64") == 0 || strcmp(value, "u8") == 0 || strcmp(value, "u16") == 0 || strcmp(value, "u32") == 0 || - strcmp(value, "u64") == 0 || strcmp(value, "null") == 0) + strcmp(value, "u64") == 0) tok = fzl_mktoken(FZL_TOKTYPE_TYPE, l.line, l.col, value, size, l.filename); else tok = fzl_mktoken(FZL_TOKTYPE_NAME, l.line, l.col, value, size, l.filename); From c0c90f4f1ae77f511fcd76e60878b4e1dfddfa25 Mon Sep 17 00:00:00 2001 From: samsonium Date: Sat, 17 Oct 2026 21:24:21 +0300 Subject: [PATCH 06/14] fix: clear fn return type value --- src/compiler/fzl_parser.c | 2 +- src/compiler/fzl_walker.c | 135 +++++++++++++++++--------------------- 2 files changed, 63 insertions(+), 74 deletions(-) diff --git a/src/compiler/fzl_parser.c b/src/compiler/fzl_parser.c index 43154fc..5e403c0 100644 --- a/src/compiler/fzl_parser.c +++ b/src/compiler/fzl_parser.c @@ -815,7 +815,7 @@ fzl_node_t *parse_function(fzl_parser_t *parser) return NULL; } - fzl_token_t *type; + fzl_token_t *type = NULL; if (is(parser, FZL_TOKTYPE_TYPE) || is(parser, FZL_TOKTYPE_NAME)) type = consume(parser); diff --git a/src/compiler/fzl_walker.c b/src/compiler/fzl_walker.c index 783c629..6095168 100644 --- a/src/compiler/fzl_walker.c +++ b/src/compiler/fzl_walker.c @@ -14,6 +14,7 @@ #include #include +char *null_name = ""; char wlast_err[256]; fzl_walker_t *fzl_mkwalker(fzl_node_t *root) @@ -105,8 +106,11 @@ static bool is_compatible(fzl_type_t t1, fzl_type_t t2, char *c1, char *c2) { if (t1 == FZL_TYPE_OBJECT || t2 == FZL_TYPE_OBJECT) { - return t1 == FZL_TYPE_OBJECT && t2 == FZL_TYPE_OBJECT && - (strcmp(c1, c2) == 0); + bool classes_compatible = strcmp(c1, c2) == 0 || + strcmp(c1, null_name) == 0 || + strcmp(c2, null_name) == 0; + + return t1 == FZL_TYPE_OBJECT && t2 == FZL_TYPE_OBJECT && classes_compatible; } if (t1 == t2) @@ -290,44 +294,34 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) case FZL_NODETYPE_FIELD: { - fzl_sym_t sym; - bool exists = false; - for (ssize_t i = walker->sym_top - 1; i >= 0; i--) - if ((exists = fzl_symt_get(walker->syms[i], - node->memb.parent->token->value, &sym))) - break; + fzl_walk_res_t parent_res = walk(walker, node->memb.parent); + if (parent_res.error) + return parent_res; - if (!exists) + if (parent_res.type != FZL_TYPE_OBJECT || !parent_res.classname) { - snprintf(wlast_err, sizeof(wlast_err), - "'%s': cannot find symbol '%s' at %ld:%ld", - node->token->filename, (char *)node->token->value, - node->token->ln, node->token->cn); - return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; - } - - if (!sym.classname) - { - snprintf(wlast_err, sizeof(wlast_err), - "'%s': '%s' is not an object (at %ld:%ld)", - node->token->filename, sym.name, node->token->ln, - node->token->cn); + fzl_token_t *tok = node->memb.parent->token; + snprintf( + wlast_err, sizeof(wlast_err), + "'%s': cannot access field '%s' on non-object type at %ld:%ld", + tok->filename, (char *)node->memb.field->token->value, tok->ln, + tok->cn); return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; } fzl_ctable_t *ct = NULL; for (size_t i = 0; i < walker->classes_count; i++) { - if (strcmp(sym.classname, walker->classes[i]->name) == 0) + if (strcmp(parent_res.classname, walker->classes[i]->name) == 0) { ct = walker->classes[i]; break; } } - fzl_token_t *tok = node->memb.parent->token; if (!ct) { + fzl_token_t *tok = node->memb.parent->token; snprintf(wlast_err, sizeof(wlast_err), "'%s': access to unknown class '%s' at %ld:%ld", tok->filename, (char *)tok->value, tok->ln, tok->cn); @@ -351,20 +345,17 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) { snprintf(wlast_err, sizeof(wlast_err), "'%s': cannot find field '%s' in class '%s' at %ld:%ld", - ftok->filename, (char *)ftok->value, (char *)tok->value, + ftok->filename, (char *)ftok->value, parent_res.classname, ftok->ln, ftok->cn); return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; } - fzl_walk_res_t parent_res = walk(walker, node->memb.parent); - if (parent_res.error) - return parent_res; - push_op(walker, FZL_OPCODE_GET_FIELD); push_word(walker, ctf.slot_index); - current_res = (fzl_walk_res_t){ - .type = ctf.type, .error = false, .classname = sym.classname}; + current_res = (fzl_walk_res_t){.type = ctf.type, + .error = false, + .classname = parent_res.classname}; break; } @@ -483,7 +474,8 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) if (retres.error) return retres; - if (!is_compatible(retres.type, tok_to_type(walker->ctx->fn->fn.type), + fzl_node_t *fn_node = walker->ctx->fn; + if (!is_compatible(retres.type, tok_to_type(fn_node->fn.type), retres.classname, walker->ctx->fn->fn.type->value)) { snprintf( @@ -807,80 +799,68 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) else if (node->call.callee->type == FZL_NODETYPE_FIELD) { fzl_node_t *parent_node = node->call.callee->memb.parent; + fzl_node_t *method_node = node->call.callee->memb.field; - fzl_sym_t res; - bool found = false; - for (ssize_t i = walker->sym_top - 1; i >= 0; i--) - if ((found = fzl_symt_get(walker->syms[i], - parent_node->token->value, &res))) - break; + fzl_walk_res_t parent_res = walk(walker, parent_node); + if (parent_res.error) + return parent_res; - if (!found) + if (parent_res.type != FZL_TYPE_OBJECT || !parent_res.classname) { snprintf(wlast_err, sizeof(wlast_err), - "'%s': access to unknown object '%s' at %ld:%ld", - node->token->filename, (char *)parent_node->token->value, - node->token->ln, node->token->cn); + "'%s': cannot call method on non-object type at %ld:%ld", + parent_node->token->filename, parent_node->token->ln, + parent_node->token->cn); return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; } - if (res.type != FZL_TYPE_OBJECT || !res.classname) - { - snprintf(wlast_err, sizeof(wlast_err), - "'%s': name '%s' is not an object (at %ld:%ld)", - parent_node->token->filename, - (char *)parent_node->token->value, - parent_node->token->ln, parent_node->token->cn); - return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; - } - - found = false; + char *classname = parent_res.classname; + bool class_exists = false; for (size_t i = 0; i < walker->classes_count; i++) { - if (strcmp(walker->classes[i]->name, res.classname) == 0) + if (strcmp(classname, walker->classes[i]->name) == 0) { - found = true; + class_exists = true; break; } } - if (!found) + if (!class_exists) { snprintf(wlast_err, sizeof(wlast_err), - "'%s': class '%s' not found at %ld:%ld", - node->token->filename, res.classname, node->token->ln, - node->token->cn); + "'%s': class '%s' not found (at %ld:%ld)", + parent_node->token->filename, classname, + parent_node->token->ln, parent_node->token->cn); return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; } - char *classname = res.classname; - size_t classlen = strlen(res.classname); - size_t fnlen = strlen(node->call.callee->token->value); + size_t classlen = strlen(classname); + size_t fnlen = strlen(method_node->token->value); char *methodname = malloc(classlen + fnlen + 2); - strcpy(methodname, res.classname); + strcpy(methodname, classname); strcpy((methodname + classlen + 1), node->call.callee->token->value); methodname[classlen] = '_'; methodname[classlen + fnlen + 1] = 0; - found = false; + fzl_sym_t fn_sym; + bool found_method = false; for (ssize_t i = walker->sym_top - 1; i >= 0; i--) - if ((found = fzl_symt_get(walker->syms[i], methodname, &res))) + if ((found_method = + fzl_symt_get(walker->syms[i], methodname, &fn_sym))) break; - if (!found) + if (!found_method) { snprintf(wlast_err, sizeof(wlast_err), "'%s': class '%s' has no method '%s' at %ld:%ld", node->token->filename, classname, (char *)node->call.callee->token->value, node->token->ln, node->token->cn); + free(methodname); return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; } - - fzl_walk_res_t parent = walk(walker, parent_node); - if (parent.error) - return parent; + free(methodname); if (node->call.args_count > 0) { @@ -893,10 +873,11 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) } push_op(walker, FZL_OPCODE_CALL); - push_word(walker, res.ip); + push_word(walker, fn_sym.ip); - current_res = (fzl_walk_res_t){ - .type = res.type, .error = false, .classname = res.classname}; + current_res = (fzl_walk_res_t){.type = fn_sym.type, + .error = false, + .classname = fn_sym.classname}; } } break; @@ -931,6 +912,14 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) push_word(walker, val); current_res = (fzl_walk_res_t){.type = FZL_TYPE_BOOL, .error = false}; } + else if (node->token->type == FZL_TOKTYPE_NULL_LITERAL) + { + fzl_token_t *tok = node->token; + push_op(walker, FZL_OPCODE_PUSHNUM); + push_word(walker, 0); + current_res = (fzl_walk_res_t){ + .type = FZL_TYPE_OBJECT, .error = false, .classname = null_name}; + } break; case FZL_NODETYPE_ADD: From 244dd333722f12997c05d80d246bc21351f45308 Mon Sep 17 00:00:00 2001 From: samsonium Date: Sat, 17 Oct 2026 21:55:52 +0300 Subject: [PATCH 07/14] feat: new debug print on exception! --- src/vm/fzl_vm.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/src/vm/fzl_vm.c b/src/vm/fzl_vm.c index 5f7539a..5c5efd5 100644 --- a/src/vm/fzl_vm.c +++ b/src/vm/fzl_vm.c @@ -147,6 +147,55 @@ void print_object(fzl_vm_object_t *obj) printf(">\n"); } +void print_err_data(fzl_vm_t *vm) +{ + printf("===== ERROR =====\n"); + printf("> Caused on IP: %zu\n", vm->ip); + + printf("\n> Data stack:\n"); + for (ssize_t i = vm->dtop - 1; i >= 0; i--) + { + printf("\t> [%d] ", vm->dstack[i].type); + switch (vm->dstack[i].type) + { + case FZL_TYPE_OBJECT: + printf("ref: %p\n", vm->dstack[i].as.obj); + break; + case FZL_TYPE_STRING: + printf("str: '%s'\n", vm->dstack[i].as.s.value); + break; + case FZL_TYPE_NULL: + printf("\n"); + break; + default: + printf("%lu\n", vm->dstack[i].as.u); + break; + } + } + + printf("\n> Environments:\n"); + for (ssize_t i = vm->etop - 1; i >= 0; i--) + { + fzl_vm_env_t *env = vm->estack[i]; + printf("\t> [%zd] %p:\n", i, env); + + printf("\t\t> Owned objects (%zu/%zu):\n", env->owned_objects_count, + env->owned_objects_remainder); + for (size_t j = 0; j < env->owned_objects_count; j++) + printf("\t\t\t> ref: %p (entries: %zu)\n", env->owned_objects[j], + env->owned_objects[j]->values_count); + + printf("\t\t> Local entries (%zu/%zu):\n", env->size, env->remainder); + for (size_t j = 0; j < env->size; j++) + printf("\t\t\t> [%d] %s = %lu\n", env->table[j].value.type, + env->table[j].name, env->table[j].value.as.u); + } + + printf("\n> Call stack:\n"); + for (ssize_t i = vm->ctop - 1; i >= 0; i--) + printf("\t> [%zd] %lu\n", i, vm->cstack[i]); +} + int fzl_vm_run(fzl_vm_t *vm) { if (!vm) @@ -168,6 +217,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected two values in data stack"); + print_err_data(vm); return -1; } fzl_value_t left = vm->dstack[--vm->dtop]; @@ -185,6 +235,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected two values in data stack"); + print_err_data(vm); return -1; } fzl_value_t left = vm->dstack[--vm->dtop]; @@ -202,6 +253,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected two values in data stack"); + print_err_data(vm); return -1; } fzl_value_t left = vm->dstack[--vm->dtop]; @@ -218,6 +270,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected two values in data stack"); + print_err_data(vm); return -1; } fzl_value_t left = vm->dstack[--vm->dtop]; @@ -234,6 +287,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - missing value in data stack"); + print_err_data(vm); return -1; } fzl_value_t obj = vm->dstack[--vm->dtop]; @@ -241,6 +295,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - cannot escape non-object type"); + print_err_data(vm); return -1; } @@ -250,6 +305,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - cannot escape object from global env"); + print_err_data(vm); return -1; } @@ -310,6 +366,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected value in data stack"); + print_err_data(vm); return -1; } @@ -321,6 +378,7 @@ int fzl_vm_run(fzl_vm_t *vm) last_err, sizeof(last_err), "runtime error - cannot access field #%lu of non-object type", field_slot); + print_err_data(vm); return -1; } @@ -329,6 +387,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - cannot access field #%lu", field_slot); + print_err_data(vm); return -1; } @@ -342,6 +401,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected two values in data stack"); + print_err_data(vm); return -1; } @@ -354,6 +414,7 @@ int fzl_vm_run(fzl_vm_t *vm) last_err, sizeof(last_err), "runtime error - cannot access field #%lu of non-object type", field_slot); + print_err_data(vm); return -1; } @@ -362,6 +423,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - cannot access field #%lu", field_slot); + print_err_data(vm); return -1; } @@ -383,6 +445,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - no place to jump"); + print_err_data(vm); return -1; } @@ -395,6 +458,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected two values in data stack"); + print_err_data(vm); return -1; } @@ -409,6 +473,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected two values in data stack"); + print_err_data(vm); return -1; } @@ -423,6 +488,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected two values in data stack"); + print_err_data(vm); return -1; } @@ -437,6 +503,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected two values in data stack"); + print_err_data(vm); return -1; } @@ -451,6 +518,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected two values in data stack"); + print_err_data(vm); return -1; } @@ -465,6 +533,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected two values in data stack"); + print_err_data(vm); return -1; } @@ -479,6 +548,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected value in data stack"); + print_err_data(vm); return -1; } @@ -492,6 +562,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected value in data stack"); + print_err_data(vm); return -1; } @@ -510,6 +581,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected two values in data stack"); + print_err_data(vm); return -1; } @@ -524,6 +596,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected two values in data stack"); + print_err_data(vm); return -1; } @@ -538,6 +611,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected var name in data stack"); + print_err_data(vm); return -1; } @@ -552,6 +626,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected 2 values in data stack"); + print_err_data(vm); return -1; } @@ -567,21 +642,25 @@ int fzl_vm_run(fzl_vm_t *vm) case FZL_VM_ENV_WRESULT_ERR: snprintf(last_err, sizeof(last_err), "runtime error - failed to write variable"); + print_err_data(vm); return -1; case FZL_VM_ENV_WRESULT_ERR_EXISTS: snprintf(last_err, sizeof(last_err), "runtime error - redefenition of '%s'", writevar.as.s.value); + print_err_data(vm); return -1; case FZL_VM_ENV_WRESULT_ERR_NOT_FOUND: snprintf(last_err, sizeof(last_err), "runtime error - cannot find name '%s'", writevar.as.s.value); + print_err_data(vm); return -1; default: + print_err_data(vm); return -1; } } @@ -605,6 +684,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - LOOKUP_ENV: empty data stack"); + print_err_data(vm); return -1; } @@ -647,6 +727,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - undefined builtin %lu", fn_id); + print_err_data(vm); return -1; } } @@ -658,6 +739,7 @@ int fzl_vm_run(fzl_vm_t *vm) if (vm->consts_count <= cn) { snprintf(last_err, sizeof(last_err), "runtime error - unknown const"); + print_err_data(vm); return -1; } fzl_const_t c = vm->consts[cn]; @@ -673,6 +755,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected 2 values in stack"); + print_err_data(vm); return -1; } fzl_value_t add1 = vm->dstack[--vm->dtop]; @@ -686,6 +769,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected 2 values in stack"); + print_err_data(vm); return -1; } fzl_value_t sub2 = vm->dstack[--vm->dtop]; @@ -699,6 +783,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected 2 values in stack"); + print_err_data(vm); return -1; } fzl_value_t mul1 = vm->dstack[--vm->dtop]; @@ -712,6 +797,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected 2 values in stack"); + print_err_data(vm); return -1; } fzl_value_t div1 = vm->dstack[--vm->dtop]; @@ -725,6 +811,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected 2 values in stack"); + print_err_data(vm); return -1; } fzl_value_t pow2 = vm->dstack[--vm->dtop]; @@ -745,6 +832,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected 2 values in stack"); + print_err_data(vm); return -1; } fzl_value_t mod1 = vm->dstack[--vm->dtop]; @@ -758,6 +846,7 @@ int fzl_vm_run(fzl_vm_t *vm) { snprintf(last_err, sizeof(last_err), "runtime error - expected value in stack"); + print_err_data(vm); return -1; } fzl_value_t neg_n = vm->dstack[--vm->dtop]; @@ -784,6 +873,7 @@ int fzl_vm_run(fzl_vm_t *vm) default: snprintf(last_err, sizeof(last_err), "runtime error - unknown opcode %u", opcode); + print_err_data(vm); return -1; } From 82b6cc3346f6ae578bc99b4aa37ae34a1484e8af Mon Sep 17 00:00:00 2001 From: samsonium Date: Sat, 17 Oct 2026 21:56:08 +0300 Subject: [PATCH 08/14] feat: add support for null-values --- src/vm/fzl_vm.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/vm/fzl_vm.c b/src/vm/fzl_vm.c index 5c5efd5..9c8f041 100644 --- a/src/vm/fzl_vm.c +++ b/src/vm/fzl_vm.c @@ -281,6 +281,7 @@ int fzl_vm_run(fzl_vm_t *vm) break; } + // NOTE: make recursive owning transfer for nested objects! case FZL_OPCODE_ESCAPE_OBJECT: { if (vm->dtop < 1) @@ -291,7 +292,7 @@ int fzl_vm_run(fzl_vm_t *vm) return -1; } fzl_value_t obj = vm->dstack[--vm->dtop]; - if (obj.type != FZL_TYPE_OBJECT) + if (obj.type != FZL_TYPE_OBJECT && obj.type < FZL_TYPE_INT8) { snprintf(last_err, sizeof(last_err), "runtime error - cannot escape non-object type"); @@ -310,13 +311,16 @@ int fzl_vm_run(fzl_vm_t *vm) } bool found = false; - for (size_t i = 0; i < current->owned_objects_count; i++) + if (obj.as.u != 0) { - if (obj.as.obj == current->owned_objects[i]) + for (size_t i = 0; i < current->owned_objects_count; i++) { - found = true; - current->owned_objects[i] = NULL; - break; + if (obj.as.obj == current->owned_objects[i]) + { + found = true; + current->owned_objects[i] = NULL; + break; + } } } From d33aa57f8e825c016d0f11c263a1b187223d45eb Mon Sep 17 00:00:00 2001 From: samsonium Date: Sat, 17 Oct 2026 21:57:04 +0300 Subject: [PATCH 09/14] feat: add first hard example Hard example tests all actual language features. For now its: - Classes - Methods - Recursive calls - Object move and borrowing --- exl/hard-1.fzs | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 exl/hard-1.fzs diff --git a/exl/hard-1.fzs b/exl/hard-1.fzs new file mode 100644 index 0000000..077d26e --- /dev/null +++ b/exl/hard-1.fzs @@ -0,0 +1,61 @@ +class Node +{ + int value; + Node next; + + fn append(int val) Node + { + if (this.next == null) + { + Node tail = new Node; + tail.value = val; + tail.next = null; + this.next = tail; + return this; + } + + this.next = this.next.append(val); + return this; + } + + fn sum() int + { + if (this.next == null) + return this.value; + + return this.value + this.next.sum(); + } + + fn printList() + { + printline(this.value); + if (this.next != null) + this.next.printList(); + } +} + +fn createList(int count) Node +{ + if (count <= 0) + return null; + + Node head = new Node; + head.value = count; + head.next = createList(count - 1); + return head; +} + +printline("===== Test 1: factory & recursive escaping ====="); +Node list = createList(3); +printline("Elements:"); +list.printList(); + +printline("===== Test 2: mutation & methods ====="); +int total = list.sum(); +printline("Sum of elements:"); +printline(total); + +if (total == 16) + printline("Tests passed!"); +else printline("Tests failed"); + From 0c8c206677f3e51f8323a31e7e9e7f521bed48fc Mon Sep 17 00:00:00 2001 From: samsonium Date: Sun, 18 Oct 2026 17:37:18 +0300 Subject: [PATCH 10/14] upd: update tests --- CMakeLists.txt | 8 ++++++++ exl/borrow.fzs | 2 +- exl/hard-1.fzs | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1cab213..f25345b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,6 +78,10 @@ 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" +) set_tests_properties(compile_problematic_hw PROPERTIES WILL_FAIL TRUE) @@ -125,4 +129,8 @@ 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" +) diff --git a/exl/borrow.fzs b/exl/borrow.fzs index 660be89..3a538a9 100644 --- a/exl/borrow.fzs +++ b/exl/borrow.fzs @@ -32,6 +32,6 @@ printline(a); a = doSome(a); printline(a); -b = doSomeNew(); +A b = doSomeNew(); printline(b); diff --git a/exl/hard-1.fzs b/exl/hard-1.fzs index 077d26e..9588901 100644 --- a/exl/hard-1.fzs +++ b/exl/hard-1.fzs @@ -51,6 +51,7 @@ printline("Elements:"); list.printList(); printline("===== Test 2: mutation & methods ====="); +list = list.append(10); int total = list.sum(); printline("Sum of elements:"); printline(total); From dce64e8b4dd34a5ba8b6d231a92ccded9527d7ad Mon Sep 17 00:00:00 2001 From: samsonium Date: Sun, 18 Oct 2026 17:38:49 +0300 Subject: [PATCH 11/14] fix+ref: small objects rework - Add nested objects escape on ESCAPE opcode - Stabilize objects work in heap --- include/vm/fzl_vm.h | 3 - src/vm/fzl_vm.c | 162 +++++++++++++++++++++++++++----------------- src/vm/fzl_vm_env.c | 4 +- 3 files changed, 102 insertions(+), 67 deletions(-) diff --git a/include/vm/fzl_vm.h b/include/vm/fzl_vm.h index 9d0075d..cdd820f 100644 --- a/include/vm/fzl_vm.h +++ b/include/vm/fzl_vm.h @@ -30,9 +30,6 @@ typedef struct // Environment stack fzl_vm_env_t *estack[256]; size_t etop; - - fzl_vm_object_t *objects[256]; - size_t objects_count; } fzl_vm_t; // Allocate FZL VM diff --git a/src/vm/fzl_vm.c b/src/vm/fzl_vm.c index 9c8f041..1cce290 100644 --- a/src/vm/fzl_vm.c +++ b/src/vm/fzl_vm.c @@ -25,20 +25,18 @@ fzl_vm_t *fzl_mkvm(const char *filepath) return NULL; } - vm->consts = NULL; - vm->consts_count = 0; - vm->ops = NULL; - vm->ops_count = 0; - vm->ip = 0; - vm->dtop = 0; - vm->ctop = 0; - vm->etop = 0; - vm->objects_count = 0; + vm->consts = NULL; + vm->consts_count = 0; + vm->ops = NULL; + vm->ops_count = 0; + vm->ip = 0; + vm->dtop = 0; + vm->ctop = 0; + vm->etop = 0; memset(vm->dstack, 0, sizeof(vm->dstack)); memset(vm->cstack, 0, sizeof(vm->cstack)); memset(vm->estack, 0, sizeof(vm->estack)); - memset(vm->objects, 0, sizeof(vm->objects)); FILE *fd = fopen(filepath, "rb"); if (!fd) @@ -110,6 +108,57 @@ fzl_vm_t *fzl_mkvm(const char *filepath) return vm; } +// Escape objects with nested objects check +static void escape_recursive(fzl_vm_env_t *current, fzl_vm_env_t *parent, + fzl_vm_object_t *obj) +{ + if (!current || !parent || !obj) + return; + + bool found_in_current = false; + for (size_t i = 0; i < current->owned_objects_count; i++) + { + if (current->owned_objects[i] == obj) + { + current->owned_objects[i] = NULL; + found_in_current = true; + break; + } + } + + if (found_in_current) + { + bool found_in_parent = false; + for (size_t i = 0; i < parent->owned_objects_count; i++) + { + if (parent->owned_objects[i] == obj) + { + found_in_parent = true; + break; + } + } + + if (!found_in_parent) + { + if (parent->owned_objects_remainder <= parent->owned_objects_count) + { + parent->owned_objects_remainder *= 2; + parent->owned_objects = + realloc(parent->owned_objects, parent->owned_objects_remainder * + sizeof(fzl_vm_object_t *)); + } + + parent->owned_objects[parent->owned_objects_count++] = obj; + } + } + + for (size_t i = 0; i < obj->values_count; i++) + { + if (obj->values[i].type == FZL_TYPE_OBJECT && obj->values[i].as.obj != NULL) + escape_recursive(current, parent, obj->values[i].as.obj); + } +} + void print_object(fzl_vm_object_t *obj) { if (!obj) @@ -153,25 +202,32 @@ void print_err_data(fzl_vm_t *vm) printf("> Caused on IP: %zu\n", vm->ip); printf("\n> Data stack:\n"); - for (ssize_t i = vm->dtop - 1; i >= 0; i--) + if (vm->dtop > 0) { - printf("\t> [%d] ", vm->dstack[i].type); - switch (vm->dstack[i].type) + for (ssize_t i = vm->dtop - 1; i >= 0; i--) { - case FZL_TYPE_OBJECT: - printf("ref: %p\n", vm->dstack[i].as.obj); - break; - case FZL_TYPE_STRING: - printf("str: '%s'\n", vm->dstack[i].as.s.value); - break; - case FZL_TYPE_NULL: - printf("\n"); - break; - default: - printf("%lu\n", vm->dstack[i].as.u); - break; + printf("\t> [%d] ", vm->dstack[i].type); + switch (vm->dstack[i].type) + { + case FZL_TYPE_OBJECT: + printf("ref: %p\n", vm->dstack[i].as.obj); + break; + case FZL_TYPE_STRING: + printf("str: '%s'\n", vm->dstack[i].as.s.value); + break; + case FZL_TYPE_NULL: + printf("\n"); + break; + default: + printf("%lu\n", vm->dstack[i].as.u); + break; + } } } + else + { + printf("\tEmpty\n"); + } printf("\n> Environments:\n"); for (ssize_t i = vm->etop - 1; i >= 0; i--) @@ -187,8 +243,20 @@ void print_err_data(fzl_vm_t *vm) printf("\t\t> Local entries (%zu/%zu):\n", env->size, env->remainder); for (size_t j = 0; j < env->size; j++) - printf("\t\t\t> [%d] %s = %lu\n", env->table[j].value.type, - env->table[j].name, env->table[j].value.as.u); + { + switch (env->table[j].value.type) + { + case FZL_TYPE_OBJECT: + printf("\t\t\t> [%d] %s = ref %p\n", env->table[j].value.type, + env->table[j].name, env->table[j].value.as.obj); + break; + + default: + printf("\t\t\t> [%d] %s = %lu\n", env->table[j].value.type, + env->table[j].name, env->table[j].value.as.u); + break; + } + } } printf("\n> Call stack:\n"); @@ -260,7 +328,7 @@ int fzl_vm_run(fzl_vm_t *vm) fzl_value_t right = vm->dstack[--vm->dtop]; vm->dstack[vm->dtop++] = (fzl_value_t){ - .type = FZL_TYPE_BOOL, .as.b = &left.as.obj == &right.as.obj}; + .type = FZL_TYPE_BOOL, .as.b = left.as.obj == right.as.obj}; break; } @@ -277,11 +345,10 @@ int fzl_vm_run(fzl_vm_t *vm) fzl_value_t right = vm->dstack[--vm->dtop]; vm->dstack[vm->dtop++] = (fzl_value_t){ - .type = FZL_TYPE_BOOL, .as.b = &left.as.obj != &right.as.obj}; + .type = FZL_TYPE_BOOL, .as.b = left.as.obj != right.as.obj}; break; } - // NOTE: make recursive owning transfer for nested objects! case FZL_OPCODE_ESCAPE_OBJECT: { if (vm->dtop < 1) @@ -302,7 +369,7 @@ int fzl_vm_run(fzl_vm_t *vm) fzl_vm_env_t *parent = vm->estack[vm->etop - 2]; fzl_vm_env_t *current = vm->estack[vm->etop - 1]; - if (!parent || vm->etop < 2) + if (vm->etop < 2) { snprintf(last_err, sizeof(last_err), "runtime error - cannot escape object from global env"); @@ -310,32 +377,7 @@ int fzl_vm_run(fzl_vm_t *vm) return -1; } - bool found = false; - if (obj.as.u != 0) - { - for (size_t i = 0; i < current->owned_objects_count; i++) - { - if (obj.as.obj == current->owned_objects[i]) - { - found = true; - current->owned_objects[i] = NULL; - break; - } - } - } - - if (found) - { - if (parent->owned_objects_remainder <= parent->owned_objects_count) - { - parent->owned_objects_remainder *= 2; - parent->owned_objects = - realloc(parent->owned_objects, parent->owned_objects_remainder * - sizeof(fzl_vm_object_t *)); - } - - parent->owned_objects[parent->owned_objects_count++] = obj.as.obj; - } + escape_recursive(current, parent, obj.as.obj); vm->dstack[vm->dtop++] = obj; break; @@ -900,12 +942,6 @@ void fzl_freevm(fzl_vm_t *vm) free((void *)vm->consts[i].as.string.chars); } - for (size_t i = 0; i < vm->objects_count; i++) - { - free(vm->objects[i]->values); - free(vm->objects[i]); - } - free(vm->consts); free(vm->ops); free(vm); diff --git a/src/vm/fzl_vm_env.c b/src/vm/fzl_vm_env.c index 1abe7d1..a7c98e6 100644 --- a/src/vm/fzl_vm_env.c +++ b/src/vm/fzl_vm_env.c @@ -109,7 +109,9 @@ void fzl_freeenv(fzl_vm_env_t *env) if (!env->owned_objects[i]) continue; - free(env->owned_objects[i]->values); + if (env->owned_objects[i]->values) + free(env->owned_objects[i]->values); + free(env->owned_objects[i]); } From f0e641928da54263035c7ba67baad5de201f19c4 Mon Sep 17 00:00:00 2001 From: samsonium Date: Sun, 18 Oct 2026 18:40:48 +0300 Subject: [PATCH 12/14] upd: add `exit` on error in examples --- exl/borrow.fzs | 25 +++++++++++++++++-------- exl/class.fzs | 6 +++++- exl/conditions.fzs | 4 ++++ exl/fn.fzs | 12 ++++++++++-- exl/hard-1.fzs | 6 +++++- exl/raii.fzs | 16 ++++++++++++++-- 6 files changed, 55 insertions(+), 14 deletions(-) diff --git a/exl/borrow.fzs b/exl/borrow.fzs index 3a538a9..80c3d38 100644 --- a/exl/borrow.fzs +++ b/exl/borrow.fzs @@ -7,12 +7,11 @@ fn doSome(A a) A { // NOTE: section below won't change `a.some` value - // if (a.some < 1) - // { - // a.some = 1; - // return a; - // } - a.some = 1; + if (a.some < 1) + { + a.some = 1; + return a; + } return a; } @@ -27,10 +26,20 @@ fn doSomeNew() A A a = new A; a.some = 0; -printline(a); +if (a.some != 0) +{ + printline("Test failed (first a.some != 0)"); + exit(1); +} +else printline(a); a = doSome(a); -printline(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 index f73a12d..a7ac0f5 100644 --- a/exl/class.fzs +++ b/exl/class.fzs @@ -28,5 +28,9 @@ printline(obj.y); if (obj.x == 10 && obj.y == 10) printline("Test passed"); -else printline("Test failed"); +else +{ + printline("Test failed"); + exit(1); +} diff --git a/exl/conditions.fzs b/exl/conditions.fzs index b7c5a83..a49e79d 100644 --- a/exl/conditions.fzs +++ b/exl/conditions.fzs @@ -1,13 +1,17 @@ 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); } { diff --git a/exl/fn.fzs b/exl/fn.fzs index dde5dad..2d6214c 100644 --- a/exl/fn.fzs +++ b/exl/fn.fzs @@ -15,10 +15,18 @@ printline("Test #1: fibonacci"); int result = fib(7); if (result == 13) printline("Recurse test passed"); -else printline("Recurse test failed"); +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"); +else +{ + printline("Sum test failed"); + exit(1); +} diff --git a/exl/hard-1.fzs b/exl/hard-1.fzs index 9588901..e6d1ba3 100644 --- a/exl/hard-1.fzs +++ b/exl/hard-1.fzs @@ -58,5 +58,9 @@ printline(total); if (total == 16) printline("Tests passed!"); -else printline("Tests failed"); +else +{ + printline("Tests failed"); + exit(1); +} diff --git a/exl/raii.fzs b/exl/raii.fzs index 998d06e..f9e2e83 100644 --- a/exl/raii.fzs +++ b/exl/raii.fzs @@ -21,14 +21,26 @@ a.name = "Buryatka"; a.age = 5; Animal b = a.copy(); -a.name = "Gavryusha"; -a.age = 10; +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); +} From c0df5fc9d4c70e6d7f70a7494ad5a0b55d1d1712 Mon Sep 17 00:00:00 2001 From: samsonium Date: Sun, 18 Oct 2026 18:41:49 +0300 Subject: [PATCH 13/14] feat: add `exit` builtin function --- include/common/fzl_builtins.h | 3 ++- src/compiler/fzl_walker.c | 29 +++++++++++++++++++++++++++-- src/vm/fzl_vm.c | 7 ++++++- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/include/common/fzl_builtins.h b/include/common/fzl_builtins.h index 678de31..a24c6bd 100644 --- a/include/common/fzl_builtins.h +++ b/include/common/fzl_builtins.h @@ -3,7 +3,8 @@ typedef enum { - FZL_BUILTIN_PRINTLINE // Print message in arg with newline at end + FZL_BUILTIN_PRINTLINE, // Print message in arg with newline at end + FZL_BUILTIN_EXIT, // Exit program with code specified in arg } fzl_builtin_t; #endif // FZL_BUILTINS_H diff --git a/src/compiler/fzl_walker.c b/src/compiler/fzl_walker.c index 6095168..6537e88 100644 --- a/src/compiler/fzl_walker.c +++ b/src/compiler/fzl_walker.c @@ -754,6 +754,30 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) } current_res = (fzl_walk_res_t){.type = FZL_TYPE_NULL, .error = false}; } + else if (strcmp(node->token->value, "exit") == 0) + { + if (node->call.args_count > 0) + { + for (size_t i = 0; i < node->call.args_count; i++) + { + fzl_walk_res_t res = walk(walker, node->call.args[i]); + if (res.error) + return res; + } + + push_op(walker, FZL_OPCODE_CALL_BUILTIN); + push_word(walker, FZL_BUILTIN_EXIT); + if (node->call.args_count < 1) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': 'exit' call with zero arguments at %ld:%ld", + node->token->filename, node->token->ln, node->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + current_res = + (fzl_walk_res_t){.type = FZL_TYPE_NULL, .error = false}; + } + } else { if (node->call.callee->type == FZL_NODETYPE_IDENTIFIER) @@ -778,8 +802,9 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) if (!found) { snprintf(wlast_err, sizeof(wlast_err), - "call to unknown function '%s'", - (char *)node->token->value); + "'%s': call to unknown function '%s' at %ld:%ld", + node->token->filename, (char *)node->token->value, + node->token->ln, node->token->cn); return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; } diff --git a/src/vm/fzl_vm.c b/src/vm/fzl_vm.c index 1cce290..d9f1d02 100644 --- a/src/vm/fzl_vm.c +++ b/src/vm/fzl_vm.c @@ -538,8 +538,8 @@ int fzl_vm_run(fzl_vm_t *vm) return -1; } - fzl_value_t llt = vm->dstack[--vm->dtop]; fzl_value_t rlt = vm->dstack[--vm->dtop]; + fzl_value_t llt = vm->dstack[--vm->dtop]; vm->dstack[vm->dtop++] = (fzl_value_t){.type = FZL_TYPE_BOOL, .as.b = llt.as.u < rlt.as.u}; break; @@ -769,6 +769,11 @@ int fzl_vm_run(fzl_vm_t *vm) else if (arg.type == FZL_TYPE_OBJECT) print_object(arg.as.obj); } + else if (fn_id == FZL_BUILTIN_EXIT) + { + fzl_value_t arg = vm->dstack[--vm->dtop]; + exit(arg.as.i); + } else { snprintf(last_err, sizeof(last_err), From 801b28e27d522b1259ce8662103950ef8b24a9e6 Mon Sep 17 00:00:00 2001 From: samsonium Date: Sun, 18 Oct 2026 18:42:02 +0300 Subject: [PATCH 14/14] feat: implement address deferencing --- include/common/fzl_opcodes.h | 1 + src/compiler/fzl_walker.c | 1 + src/vm/fzl_vm.c | 50 +++++++++++++++++++++++++++++++++--- src/vm/fzl_vm_debug.c | 4 +++ 4 files changed, 53 insertions(+), 3 deletions(-) diff --git a/include/common/fzl_opcodes.h b/include/common/fzl_opcodes.h index 35e26a0..1484134 100644 --- a/include/common/fzl_opcodes.h +++ b/include/common/fzl_opcodes.h @@ -25,6 +25,7 @@ typedef enum FZL_OPCODE_GET_FIELD = 0xFA0002, // Get field N of an object at dstack FZL_OPCODE_SET_FIELD = 0xFA0003, // Set field N of an object at dstack FZL_OPCODE_ESCAPE_OBJECT = 0xFA0004, // Make parent envtable as owner + FZL_OPCODE_DEREFERENCE = 0xFA0005, // Dereference address FZL_OPCODE_ADD = 0xA00001, // Sum two numbers in dstack FZL_OPCODE_SUB = 0xA00002, // Subract two numbers in dstack diff --git a/src/compiler/fzl_walker.c b/src/compiler/fzl_walker.c index 6537e88..a99af45 100644 --- a/src/compiler/fzl_walker.c +++ b/src/compiler/fzl_walker.c @@ -942,6 +942,7 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) fzl_token_t *tok = node->token; push_op(walker, FZL_OPCODE_PUSHNUM); push_word(walker, 0); + push_op(walker, FZL_OPCODE_DEREFERENCE); current_res = (fzl_walk_res_t){ .type = FZL_TYPE_OBJECT, .error = false, .classname = null_name}; } diff --git a/src/vm/fzl_vm.c b/src/vm/fzl_vm.c index d9f1d02..3b13ee7 100644 --- a/src/vm/fzl_vm.c +++ b/src/vm/fzl_vm.c @@ -279,6 +279,37 @@ int fzl_vm_run(fzl_vm_t *vm) switch (opcode) { + case FZL_OPCODE_DEREFERENCE: + { + if (vm->dtop < 1) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected value in data stack"); + print_err_data(vm); + return -1; + } + + fzl_value_t addr = vm->dstack[--vm->dtop]; + if (addr.type < FZL_TYPE_INT8) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected address"); + print_err_data(vm); + return -1; + } + + if (addr.as.u == 0) + { + vm->dstack[vm->dtop++] = + (fzl_value_t){.type = FZL_TYPE_OBJECT, .as.obj = NULL}; + break; + } + + vm->dstack[vm->dtop++] = (fzl_value_t){ + .type = FZL_TYPE_OBJECT, .as.obj = (fzl_vm_object_t *)addr.as.u}; + break; + } + case FZL_OPCODE_CMP_EQ_STR: { if (vm->dtop < 2) @@ -359,7 +390,7 @@ int fzl_vm_run(fzl_vm_t *vm) return -1; } fzl_value_t obj = vm->dstack[--vm->dtop]; - if (obj.type != FZL_TYPE_OBJECT && obj.type < FZL_TYPE_INT8) + if (obj.type != FZL_TYPE_OBJECT) { snprintf(last_err, sizeof(last_err), "runtime error - cannot escape non-object type"); @@ -679,8 +710,21 @@ int fzl_vm_run(fzl_vm_t *vm) fzl_value_t writevar = vm->dstack[--vm->dtop]; fzl_value_t value = vm->dstack[--vm->dtop]; - fzl_vm_env_wresult_t wresult = fzl_env_writevalue( - vm->estack[vm->etop - 1], (char *)writevar.as.s.value, value); + char *wrname = (char *)writevar.as.s.value; + bool wrfound; + fzl_value_t wrenv; + ssize_t wrlvl = -1; + for (ssize_t i = vm->etop - 1; i >= 0; i--) + { + if ((wrfound = fzl_env_lookup(vm->estack[i], wrname, &wrenv))) + { + wrlvl = i; + break; + } + } + + fzl_vm_env_wresult_t wresult = + fzl_env_writevalue(vm->estack[wrlvl], (char *)wrname, value); if (wresult != FZL_VM_ENV_WRESULT_OK) { switch (wresult) diff --git a/src/vm/fzl_vm_debug.c b/src/vm/fzl_vm_debug.c index 26404ad..eea0ef2 100644 --- a/src/vm/fzl_vm_debug.c +++ b/src/vm/fzl_vm_debug.c @@ -20,6 +20,10 @@ void fzl_vm_print_ops(fzl_vm_t *vm) switch (vm->ops[i]) { + case FZL_OPCODE_DEREFERENCE: + printf("DEREFERENCE\n"); + break; + case FZL_OPCODE_CMP_EQ_STR: printf("CMP_EQ_STR\n"); break;