diff --git a/.gdbinit b/.gdbinit index b48dd6a..56124a5 100644 --- a/.gdbinit +++ b/.gdbinit @@ -2,4 +2,17 @@ skip peek skip consume skip next skip is +skip fzl_mknode +skip fzl_mktoken +skip fzl_symt_put +skip fzl_symt_get +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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 9bee88a..925fd71 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ add_executable(fzc src/compiler/utils/fzl_symtable.c src/compiler/utils/fzl_linked.c src/compiler/utils/fzl_options.c + src/compiler/utils/fzl_ctable.c src/compiler/main.c) # Virtual machine diff --git a/README.md b/README.md index e965f7c..da6c7ab 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ At the moment, the language has the following capabilities: - `str` - strings - `bool` - boolean - Assignments +- Functions +- Classes and objects Working examples of code can be found in `exl/` directory. diff --git a/README.ru.md b/README.ru.md index 28d32c5..0280ea0 100644 --- a/README.ru.md +++ b/README.ru.md @@ -9,7 +9,7 @@ На данный момент язык имеет: - Встроенную функцию `printline` для вывода аргумента в stdout -- Строковый и числовой литералы +- Строковый, булев и числовой литералы - Арифметические операции: сложение, вычитание, умножение, деление, модуло, возведение в степень - Декларация переменных со следующими типами: - `uint`, `int` - целые числа размером с машинное слово @@ -18,6 +18,8 @@ - `str` - строки - `bool` - булевы - Присваивание +- Функции +- Классы и объекты Примеры кода лежат в директории `exl/`. diff --git a/exl/class.fzs b/exl/class.fzs index 84c7787..f73a12d 100644 --- a/exl/class.fzs +++ b/exl/class.fzs @@ -10,9 +10,6 @@ class Point } } -// NOTE: WHY THE FUCK THIS FUCKING COMPILER REQUIRES ';' AFTER '='?! -// | -// v Point obj = new Point; obj.x = 1; obj.y = 2; @@ -21,6 +18,9 @@ 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); diff --git a/exl/fn.fzs b/exl/fn.fzs index e919ada..dde5dad 100644 --- a/exl/fn.fzs +++ b/exl/fn.fzs @@ -1,4 +1,4 @@ -int fib(int n) +fn fib(int n) int { if (n <= 1) return n; @@ -6,7 +6,7 @@ int fib(int n) return fib(n - 1) + fib(n - 2); } -uint sum(uint a, uint b) +fn sum(uint a, uint b) uint { return a + b; } diff --git a/include/common/fzl_opcodes.h b/include/common/fzl_opcodes.h index 6938b80..a0d8ac3 100644 --- a/include/common/fzl_opcodes.h +++ b/include/common/fzl_opcodes.h @@ -21,6 +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_ADD = 0xA00001, // Sum two numbers in dstack FZL_OPCODE_SUB = 0xA00002, // Subract two numbers in dstack FZL_OPCODE_MUL = 0xA00003, // Multiply two numbers in dstack diff --git a/include/common/fzl_types.h b/include/common/fzl_types.h index 6e85456..346cb96 100644 --- a/include/common/fzl_types.h +++ b/include/common/fzl_types.h @@ -4,6 +4,7 @@ typedef enum { FZL_TYPE_NULL, + FZL_TYPE_OBJECT, FZL_TYPE_STRING, FZL_TYPE_CHAR, diff --git a/include/common/fzl_value.h b/include/common/fzl_value.h index df8d68c..c6e2db4 100644 --- a/include/common/fzl_value.h +++ b/include/common/fzl_value.h @@ -13,6 +13,8 @@ typedef struct union { + void *obj; + struct { const char *value; diff --git a/include/compiler/fzl_parser.h b/include/compiler/fzl_parser.h index 3be82e5..f96c307 100644 --- a/include/compiler/fzl_parser.h +++ b/include/compiler/fzl_parser.h @@ -17,6 +17,7 @@ typedef enum FZL_NODETYPE_RETURN, FZL_NODETYPE_CLASS, FZL_NODETYPE_FIELD, + FZL_NODETYPE_CONSTRUCT, FZL_NODETYPE_ADD, FZL_NODETYPE_SUB, @@ -133,6 +134,12 @@ typedef struct fzl_node struct fzl_node *parent; struct fzl_node *field; } memb; + + // Object construct + struct + { + struct fzl_node *name; + } construct; }; } fzl_node_t; diff --git a/include/compiler/fzl_walker.h b/include/compiler/fzl_walker.h index 7b5ee8a..a984e68 100644 --- a/include/compiler/fzl_walker.h +++ b/include/compiler/fzl_walker.h @@ -5,10 +5,20 @@ #include #include #include +#include #include #include #include +// Type of context +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_t; + // Internal walk function result typedef struct { @@ -16,11 +26,16 @@ typedef struct char *error_msg; bool error; + + char *classname; } fzl_walk_res_t; // Function context descriptor -typedef struct +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; } fzl_walk_ctx_t; @@ -39,6 +54,9 @@ typedef struct size_t sym_top; fzl_walk_ctx_t *ctx; + + fzl_ctable_t *classes[64]; + size_t classes_count; } fzl_walker_t; // Allocate new walker diff --git a/include/compiler/utils/fzl_ctable.h b/include/compiler/utils/fzl_ctable.h new file mode 100644 index 0000000..edfa495 --- /dev/null +++ b/include/compiler/utils/fzl_ctable.h @@ -0,0 +1,30 @@ +#ifndef FZL_CTABLE_H +#define FZL_CTABLE_H + +#include +#include + +// Class field descriptor +typedef struct +{ + char *name; + size_t slot_index; + fzl_type_t type; +} fzl_ctable_field_t; + +// Class table descriptor +typedef struct +{ + char *name; + fzl_ctable_field_t *fields; + size_t fields_count; + size_t fields_remainder; +} fzl_ctable_t; + +// Allocate class table +fzl_ctable_t *fzl_mkctable(char *name); + +// Free class table +void fzl_freectable(fzl_ctable_t *ct); + +#endif // FZL_CTABLE_H diff --git a/include/compiler/utils/fzl_symtable.h b/include/compiler/utils/fzl_symtable.h index 1a591a9..ebaacdc 100644 --- a/include/compiler/utils/fzl_symtable.h +++ b/include/compiler/utils/fzl_symtable.h @@ -10,7 +10,8 @@ typedef enum { FZL_SYMTABLE_RESULT_OK, FZL_SYMTABLE_RESULT_ERR, - FZL_SYMTABLE_RESULT_EXISTS + FZL_SYMTABLE_RESULT_EXISTS, + FZL_SYMTABLE_RESULT_NOT_OBJECT } fzl_symtable_res_t; // Symbols table entry descriptor @@ -19,6 +20,8 @@ typedef struct char *name; fzl_type_t type; size_t ip; + + char *classname; } fzl_sym_t; // Symbols table descriptor @@ -37,6 +40,10 @@ fzl_symtable_t *fzl_mksymt(); fzl_symtable_res_t fzl_symt_put(fzl_symtable_t *symt, char *name, fzl_type_t type, uint64_t ip); +// Assign class name to object type +fzl_symtable_res_t fzl_symt_setobjclass(fzl_symtable_t *symt, char *name, + char *classname); + // Get entry from specified symtable bool fzl_symt_get(fzl_symtable_t *symt, char *name, fzl_sym_t *res); diff --git a/include/vm/fzl_vm.h b/include/vm/fzl_vm.h index e772fdd..9d0075d 100644 --- a/include/vm/fzl_vm.h +++ b/include/vm/fzl_vm.h @@ -6,6 +6,7 @@ #include #include #include +#include // VM descriptor typedef struct @@ -29,6 +30,9 @@ 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/include/vm/fzl_vm_object.h b/include/vm/fzl_vm_object.h new file mode 100644 index 0000000..67cbff2 --- /dev/null +++ b/include/vm/fzl_vm_object.h @@ -0,0 +1,14 @@ +#ifndef FZL_VM_OBJECT_H +#define FZL_VM_OBJECT_H + +#include +#include + +// Object descriptor +typedef struct +{ + fzl_value_t *values; + size_t values_count; +} fzl_vm_object_t; + +#endif // FZL_VM_OBJECT_H diff --git a/src/compiler/fzl_debug.c b/src/compiler/fzl_debug.c index c11f9c2..2a78659 100644 --- a/src/compiler/fzl_debug.c +++ b/src/compiler/fzl_debug.c @@ -32,6 +32,27 @@ void print_node(fzl_node_t *node, int level) switch (node->type) { + case FZL_NODETYPE_CONSTRUCT: + printf("%s Construct class '%s':\n", levelbuf, + (char *)node->construct.name->token->value); + break; + + case FZL_NODETYPE_CLASS: + printf("%s Class '%s':\n", levelbuf, (char *)node->token->value); + printf("-%s Fields:\n", levelbuf); + for (size_t i = 0; i < node->class.fields_count; i++) + print_node(node->class.fields[i], level + 2); + printf("-%s Methods:\n", levelbuf); + for (size_t i = 0; i < node->class.methods_count; i++) + print_node(node->class.methods[i], level + 2); + break; + + case FZL_NODETYPE_FIELD: + printf("%s Field '%s' access:\n", levelbuf, + (char *)node->memb.field->token->value); + print_node(node->memb.parent, level + 1); + break; + case FZL_NODETYPE_DECLARATION: printf("%s Declare variable with type %d:\n", levelbuf, node->var.type->type); @@ -112,6 +133,8 @@ void print_node(fzl_node_t *node, int level) print_node(node->ret.value, level + 1); break; + case FZL_NODETYPE_AND: + case FZL_NODETYPE_OR: case FZL_NODETYPE_EQUAL: case FZL_NODETYPE_NEQUAL: case FZL_NODETYPE_LESS: diff --git a/src/compiler/fzl_parser.c b/src/compiler/fzl_parser.c index ccf99f9..8c6f8ba 100644 --- a/src/compiler/fzl_parser.c +++ b/src/compiler/fzl_parser.c @@ -72,8 +72,9 @@ static fzl_token_t *next(fzl_parser_t *parser) // Consume token at cursor and move to the next static fzl_token_t *consume(fzl_parser_t *parser) { - if (!parser || !parser->cursor) + if (!parser || !parser->cursor || !parser->cursor->token) return NULL; + fzl_token_t *tok = parser->cursor->token; parser->cursor = parser->cursor->next; return tok; @@ -222,71 +223,75 @@ fzl_node_t *parse_postfix(fzl_parser_t *parser) return NULL; } - if (is(parser, FZL_TOKTYPE_LPAREN)) + if (is(parser, FZL_TOKTYPE_LPAREN) || is(parser, FZL_TOKTYPE_DOT)) { - while (is(parser, FZL_TOKTYPE_LPAREN)) + while (is(parser, FZL_TOKTYPE_LPAREN) || is(parser, FZL_TOKTYPE_DOT)) { - consume(parser); + fzl_token_t *post = consume(parser); - size_t args_capacity = 2; - size_t args_count = 0; - fzl_node_t **args = malloc(args_capacity * sizeof(fzl_node_t *)); - - while (!is(parser, FZL_TOKTYPE_RPAREN)) + if (post->type == FZL_TOKTYPE_LPAREN) { - fzl_node_t *arg = parse_expression(parser); - if (!arg) + size_t args_capacity = 2; + size_t args_count = 0; + fzl_node_t **args = malloc(args_capacity * sizeof(fzl_node_t *)); + + while (!is(parser, FZL_TOKTYPE_RPAREN)) + { + fzl_node_t *arg = parse_expression(parser); + if (!arg) + { + free(args); + return NULL; + } + + if (args_capacity <= args_count) + { + args_capacity *= 2; + args = realloc(args, args_capacity * sizeof(fzl_node_t)); + } + + args[args_count] = arg; + args_count++; + + if (is(parser, FZL_TOKTYPE_COMMA)) + consume(parser); + } + + fzl_token_t *rparen = consume(parser); + if (!rparen || rparen->type != FZL_TOKTYPE_RPAREN) { free(args); + snprintf(plast_err, sizeof(plast_err), + "'%s': expected ')' after argument list at %ld:%ld", + peek(parser)->filename, peek(parser)->ln, peek(parser)->cn); return NULL; } - if (args_capacity <= args_count) + fzl_node_t *node = fzl_mknode(FZL_NODETYPE_CALL, left->token); + node->call.callee = left; + node->call.args = args; + node->call.args_count = args_count; + + left = node; + } + else if (post->type == FZL_TOKTYPE_DOT) + { + if (!is(parser, FZL_TOKTYPE_NAME)) { - args_capacity *= 2; - args = realloc(args, args_capacity * sizeof(fzl_node_t)); + snprintf(plast_err, sizeof(plast_err), + "'%s': expected name after '.' at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, + last_known_tok->cn); + return NULL; } - args[args_count] = arg; - args_count++; + fzl_token_t *name = consume(parser); + fzl_node_t *node = fzl_mknode(FZL_NODETYPE_FIELD, name); + node->memb.parent = left; + node->memb.field = fzl_mknode(FZL_NODETYPE_IDENTIFIER, name); - if (is(parser, FZL_TOKTYPE_COMMA)) - consume(parser); + left = node; } - - fzl_token_t *rparen = consume(parser); - if (!rparen || rparen->type != FZL_TOKTYPE_RPAREN) - { - free(args); - snprintf(plast_err, sizeof(plast_err), - "'%s': expected ')' after argument list at %ld:%ld", - peek(parser)->filename, peek(parser)->ln, peek(parser)->cn); - return NULL; - } - - fzl_node_t *node = fzl_mknode(FZL_NODETYPE_CALL, left->token); - node->call.callee = left; - node->call.args = args; - node->call.args_count = args_count; - - left = node; - } - } - else if (is(parser, FZL_TOKTYPE_DOT)) - { - while (is(parser, FZL_TOKTYPE_DOT)) - { - consume(parser); - - fzl_node_t *right = parse_primary(parser); - if (!right) - return NULL; - - fzl_node_t *node = fzl_mknode(FZL_NODETYPE_FIELD, right->token); - node->memb.parent = left; - node->memb.field = right; - - left = node; } } @@ -567,10 +572,33 @@ fzl_node_t *parse_or(fzl_parser_t *parser) return left; } +// Object construct parsing +fzl_node_t *parse_new(fzl_parser_t *parser) +{ + if (!is(parser, FZL_TOKTYPE_NEW)) + return parse_or(parser); + + fzl_token_t *new = consume(parser); + fzl_node_t *objname = parse_or(parser); + if (objname->type != FZL_NODETYPE_IDENTIFIER && + objname->type != FZL_NODETYPE_FIELD) + { + snprintf(plast_err, sizeof(plast_err), + "'%s': expect class name after 'new' keyword at %ld:%ld", + last_known_tok->filename, last_known_tok->ln, last_known_tok->cn); + return NULL; + } + + fzl_node_t *node = fzl_mknode(FZL_NODETYPE_CONSTRUCT, new); + node->construct.name = objname; + + return node; +} + // Assignments parsing fzl_node_t *parse_assignment(fzl_parser_t *parser) { - fzl_node_t *left = parse_or(parser); + fzl_node_t *left = parse_new(parser); if (!left) return NULL; @@ -1090,6 +1118,15 @@ void fzl_freeast(fzl_node_t *root) fzl_freenode(root->class.methods[i]); free(root->class.methods); } + else if (root->type == FZL_NODETYPE_CONSTRUCT) + { + fzl_freenode(root->construct.name); + } + else if (root->type == FZL_NODETYPE_FIELD) + { + fzl_freenode(root->memb.field); + fzl_freenode(root->memb.parent); + } root = root->next; free(root); diff --git a/src/compiler/fzl_walker.c b/src/compiler/fzl_walker.c index eb46f75..05206b8 100644 --- a/src/compiler/fzl_walker.c +++ b/src/compiler/fzl_walker.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -52,7 +53,9 @@ fzl_walker_t *fzl_mkwalker(fzl_node_t *root) return NULL; } - walker->ctx = NULL; + walker->ctx = NULL; + walker->classes_count = 0; + memset(walker->classes, 0, sizeof(walker->classes)); return walker; } @@ -103,6 +106,36 @@ static bool is_compatible(fzl_type_t t1, fzl_type_t t2) return t1 >= FZL_TYPE_INT8 && t2 >= FZL_TYPE_INT8; } +// Add one layer of ctx +fzl_walk_ctx_t *nest_ctx(fzl_walker_t *walker, fzl_walk_ctx_type_t type) +{ + if (!walker) + return NULL; + + fzl_walk_ctx_t *newctx = malloc(sizeof(fzl_walk_ctx_t)); + if (!newctx) + return NULL; + + newctx->type = type; + newctx->parent = walker->ctx; + walker->ctx = newctx; + + return walker->ctx; +} + +// Go outside current ctx and then clean it +fzl_walk_ctx_t *fin_ctx(fzl_walker_t *walker) +{ + if (!walker) + return NULL; + + fzl_walk_ctx_t *curr = walker->ctx; + walker->ctx = walker->ctx->parent; + + free(curr); + return walker->ctx; +} + // Add const to consts table size_t add_const(fzl_walker_t *walker, fzl_const_t c) { @@ -154,6 +187,178 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) { switch (node->type) { + case FZL_NODETYPE_CLASS: + { + fzl_walk_ctx_t *newctx = nest_ctx(walker, FZL_WALK_CTX_TYPE_CLASS); + if (!newctx) + { + snprintf(wlast_err, sizeof(wlast_err), "failed to nest context"); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + newctx->fn = node; + newctx->initial_env_lvl = walker->sym_top; + + walker->classes[walker->classes_count] = + fzl_mkctable(node->class.name->value); + fzl_ctable_t *class = walker->classes[walker->classes_count++]; + class->name = node->class.name->value; + + for (size_t i = 0; i < node->class.fields_count; i++) + { + if (class->fields_remainder <= class->fields_count) + { + class->fields_remainder *= 2; + class->fields = + realloc(class->fields, + class->fields_remainder * sizeof(fzl_ctable_field_t)); + } + + class->fields[class->fields_count] = (fzl_ctable_field_t){ + .name = node->class.fields[i]->var.identifier->token->value, + .type = tok_to_type(node->class.fields[i]->var.type), + .slot_index = class->fields_count++}; + } + + for (size_t i = 0; i < node->class.methods_count; i++) + { + fzl_node_t *fn = node->class.methods[i]; + + size_t classname_len = strlen(class->name); + size_t fnname_len = strlen(fn->fn.name->value); + char *method_name = malloc(classname_len + fnname_len + 2); + + strcpy(method_name, class->name); + method_name[classname_len] = '_'; + strcpy((method_name + classname_len + 1), fn->fn.name->value); + method_name[classname_len + fnname_len + 1] = 0; + + fn->fn.name->value = method_name; + fzl_walk_res_t fnres = walk(walker, fn); + if (fnres.error) + return fnres; + } + + if (!fin_ctx(walker)) + { + snprintf(wlast_err, sizeof(wlast_err), "failed to clean 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_CONSTRUCT: + { + fzl_node_t *classname = node->construct.name; + fzl_ctable_t *ct = NULL; + for (size_t i = 0; i < walker->classes_count; i++) + { + if (strcmp((char *)classname->token->value, + walker->classes[i]->name) == 0) + { + ct = walker->classes[i]; + break; + } + } + if (!ct) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': class '%s' not found (at %ld:%ld)", + classname->token->filename, (char *)classname->token->value, + classname->token->ln, classname->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + push_op(walker, FZL_OPCODE_ALLOC_OBJECT); + push_word(walker, ct->fields_count); + + current_res = (fzl_walk_res_t){ + .type = FZL_TYPE_OBJECT, .error = false, .classname = ct->name}; + break; + } + + 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; + + if (!exists) + { + 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); + 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) + { + ct = walker->classes[i]; + break; + } + } + + fzl_token_t *tok = node->memb.parent->token; + if (!ct) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': access to unknown class '%s' at %ld:%ld", + tok->filename, (char *)tok->value, tok->ln, tok->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + fzl_token_t *ftok = node->memb.field->token; + fzl_ctable_field_t ctf; + bool field_exists = false; + for (size_t i = 0; i < ct->fields_count; i++) + { + if (strcmp(ftok->value, ct->fields[i].name) == 0) + { + field_exists = true; + ctf = ct->fields[i]; + break; + } + } + + if (!field_exists) + { + 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->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}; + break; + } + case FZL_NODETYPE_FUNCTION: { push_op(walker, FZL_OPCODE_JMP); @@ -183,9 +388,17 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) // NOTE: add sym_top limit check size_t lvl = walker->sym_top; walker->syms[walker->sym_top++] = fzl_mksymt(); - walker->ctx = malloc(sizeof(fzl_walk_ctx_t)); - walker->ctx->fn = node; - walker->ctx->initial_env_lvl = lvl; + + fzl_walk_ctx_t *newctx = nest_ctx(walker, FZL_WALK_CTX_TYPE_FN); + if (!newctx) + { + snprintf(wlast_err, sizeof(wlast_err), "failed to nest context"); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + newctx->type = FZL_WALK_CTX_TYPE_FN; + newctx->fn = node; + newctx->initial_env_lvl = lvl; push_op(walker, FZL_OPCODE_MK_ENVTABLE); @@ -205,18 +418,25 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) // near future push_op(walker, FZL_OPCODE_RM_ENVTABLE); + push_op(walker, FZL_OPCODE_RET); walker->ops[skip_ip] = walker->op_count; - walker->ctx = NULL; // NOTE: think about nested function defs (not now) + if (!fin_ctx(walker)) + { + snprintf(wlast_err, sizeof(wlast_err), "failed to clean context"); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + 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}; break; } case FZL_NODETYPE_RETURN: { - if (!walker->ctx) + if (!walker->ctx || walker->ctx->type != FZL_WALK_CTX_TYPE_FN) { snprintf( wlast_err, sizeof(wlast_err), @@ -415,7 +635,8 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) push_word(walker, n); push_op(walker, FZL_OPCODE_LOOKUP_ENV); - current_res = (fzl_walk_res_t){.type = sym.type, .error = false}; + current_res = (fzl_walk_res_t){ + .type = sym.type, .error = false, .classname = sym.classname}; break; } @@ -434,18 +655,18 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) break; case FZL_NODETYPE_CALL: - 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; - } - } - if (strcmp(node->token->value, "printline") == 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_PRINTLINE); if (node->call.args_count < 1) @@ -459,32 +680,138 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) } else { - 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], node->token->value, &res))) - break; - - if (!found) + if (node->call.callee->type == FZL_NODETYPE_IDENTIFIER) { - snprintf(wlast_err, sizeof(wlast_err), - "call to unknown function '%s'", - (char *)node->token->value); - return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; - } + 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; + } + } - if (!res.ip) + 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], node->token->value, &res))) + break; + + if (!found) + { + snprintf(wlast_err, sizeof(wlast_err), + "call to unknown function '%s'", + (char *)node->token->value); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + if (!res.ip) + { + snprintf(wlast_err, sizeof(wlast_err), + "cannot jump to address 0x0"); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + push_op(walker, FZL_OPCODE_CALL); + push_word(walker, res.ip); + + current_res = (fzl_walk_res_t){.type = res.type, .error = false}; + } + else if (node->call.callee->type == FZL_NODETYPE_FIELD) { - snprintf(wlast_err, sizeof(wlast_err), - "cannot jump to address 0x0"); - return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + fzl_node_t *parent_node = node->call.callee->memb.parent; + + 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; + + if (!found) + { + 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); + 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; + for (size_t i = 0; i < walker->classes_count; i++) + { + if (strcmp(walker->classes[i]->name, res.classname) == 0) + { + found = true; + break; + } + } + + if (!found) + { + 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); + 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); + char *methodname = malloc(classlen + fnlen + 2); + strcpy(methodname, res.classname); + strcpy((methodname + classlen + 1), + node->call.callee->token->value); + methodname[classlen] = '_'; + methodname[classlen + fnlen + 1] = 0; + + found = false; + for (ssize_t i = walker->sym_top - 1; i >= 0; i--) + if ((found = fzl_symt_get(walker->syms[i], methodname, &res))) + break; + + if (!found) + { + 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); + 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; + + 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); + push_word(walker, res.ip); + + current_res = (fzl_walk_res_t){.type = res.type, .error = false}; } - - push_op(walker, FZL_OPCODE_CALL); - push_word(walker, res.ip); - - current_res = (fzl_walk_res_t){.type = res.type, .error = false}; } break; @@ -604,9 +931,33 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) case FZL_NODETYPE_DECLARATION: { + fzl_ctable_t *ct = NULL; + for (size_t i = 0; i < walker->classes_count; i++) + { + if (strcmp(node->var.type->value, walker->classes[i]->name) == 0) + { + ct = walker->classes[i]; + break; + } + } + fzl_symtable_res_t res = fzl_symt_put( walker->syms[walker->sym_top - 1], - node->var.identifier->token->value, tok_to_type(node->var.type), 0); + node->var.identifier->token->value, + ct != NULL ? FZL_TYPE_OBJECT : tok_to_type(node->var.type), 0); + + if (ct) + fzl_symt_setobjclass(walker->syms[walker->sym_top - 1], + node->var.identifier->token->value, ct->name); + + 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); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } fzl_token_t *id_tok = node->var.identifier->token; size_t const_n = add_const( @@ -631,11 +982,20 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) if (rres.error) return rres; + fzl_walk_ctx_t *newctx = nest_ctx(walker, FZL_WALK_CTX_TYPE_ASSIGN); + if (!newctx) + { + snprintf(wlast_err, sizeof(wlast_err), "failed to nest context"); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + if (node->binary_op.left->type == FZL_NODETYPE_DECLARATION) { fzl_walk_res_t lres = walk(walker, node->binary_op.left); if (lres.error) return lres; + + push_op(walker, FZL_OPCODE_WRITE); } else if (node->binary_op.left->type == FZL_NODETYPE_IDENTIFIER) { @@ -672,9 +1032,65 @@ fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node) node->binary_op.left->token->value_sz}); push_op(walker, FZL_OPCODE_LOADCONST); push_word(walker, const_n); + push_op(walker, FZL_OPCODE_WRITE); + } + else if (node->binary_op.left->type == FZL_NODETYPE_FIELD) + { + fzl_node_t *field_node = node->binary_op.left; + fzl_walk_res_t obj_res = walk(walker, field_node->memb.parent); + if (obj_res.error) + return obj_res; + + fzl_ctable_t *ct; + for (size_t i = 0; i < walker->classes_count; i++) + { + if (strcmp(obj_res.classname, walker->classes[i]->name) == 0) + { + ct = walker->classes[i]; + break; + } + } + + if (!ct) + { + snprintf(wlast_err, sizeof(wlast_err), + "'%s': cannot find class '%s' at %ld:%ld", + field_node->token->filename, obj_res.classname, + field_node->token->ln, field_node->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + fzl_ctable_field_t *ctf; + for (size_t i = 0; i < ct->fields_count; i++) + { + if (strcmp(field_node->memb.field->token->value, + ct->fields[i].name) == 0) + { + ctf = &ct->fields[i]; + break; + } + } + + if (!ctf) + { + fzl_node_t *fn = field_node->memb.field; + snprintf(wlast_err, sizeof(wlast_err), + "'%s': class '%s' has no field '%s' at %ld:%ld", + fn->token->filename, obj_res.classname, + (char *)fn->token->value, fn->token->ln, fn->token->cn); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; + } + + push_op(walker, FZL_OPCODE_SET_FIELD); + push_word(walker, ctf->slot_index); + } + + if (!fin_ctx(walker)) + { + snprintf(wlast_err, sizeof(wlast_err), "failed to clean context"); + return (fzl_walk_res_t){.error = true, .error_msg = wlast_err}; } - push_op(walker, FZL_OPCODE_WRITE); current_res = (fzl_walk_res_t){.type = rres.type, .error = false}; break; } @@ -695,10 +1111,14 @@ int fzl_walk(fzl_walker_t *walker) return -1; } + nest_ctx(walker, FZL_WALK_CTX_TYPE_GLOBAL); + push_op(walker, FZL_OPCODE_MK_ENVTABLE); fzl_walk_res_t result = walk(walker, walker->root); push_op(walker, FZL_OPCODE_RM_ENVTABLE); + fin_ctx(walker); + if (result.error && !wlast_err[0]) snprintf(wlast_err, sizeof(wlast_err), "%s", result.error_msg); @@ -722,5 +1142,8 @@ void fzl_freewalker(fzl_walker_t *walker) while (walker->sym_top > 0) fzl_freesymt(walker->syms[--walker->sym_top]); + for (size_t i = 0; i < walker->classes_count; i++) + fzl_freectable(walker->classes[i]); + free(walker); } diff --git a/src/compiler/utils/fzl_ctable.c b/src/compiler/utils/fzl_ctable.c new file mode 100644 index 0000000..1b7edab --- /dev/null +++ b/src/compiler/utils/fzl_ctable.c @@ -0,0 +1,29 @@ +#include +#include +#include + +fzl_ctable_t *fzl_mkctable(char *name) +{ + fzl_ctable_t *ctable = malloc(sizeof(fzl_ctable_t)); + if (!ctable) + return NULL; + + memset(ctable, 0, sizeof(fzl_ctable_t)); + ctable->name = name; + ctable->fields_remainder = 2; + ctable->fields = + malloc(ctable->fields_remainder * sizeof(fzl_ctable_field_t)); + + return ctable; +} + +void fzl_freectable(fzl_ctable_t *ct) +{ + if (!ct) + return; + + if (ct->fields) + free(ct->fields); + + free(ct); +} diff --git a/src/compiler/utils/fzl_symtable.c b/src/compiler/utils/fzl_symtable.c index 262958e..a1c5f5d 100644 --- a/src/compiler/utils/fzl_symtable.c +++ b/src/compiler/utils/fzl_symtable.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -37,10 +38,31 @@ fzl_symtable_res_t fzl_symt_put(fzl_symtable_t *symt, char *name, } symt->symbols[symt->size++] = - (fzl_sym_t){.name = name, .type = type, .ip = ip}; + (fzl_sym_t){.name = name, .type = type, .ip = ip, .classname = NULL}; return FZL_SYMTABLE_RESULT_OK; } +fzl_symtable_res_t fzl_symt_setobjclass(fzl_symtable_t *symt, char *name, + char *classname) +{ + if (!symt) + return FZL_SYMTABLE_RESULT_ERR; + + for (size_t i = 0; i < symt->size; i++) + { + if (strcmp(symt->symbols[i].name, name) == 0) + { + if (symt->symbols[i].type != FZL_TYPE_OBJECT) + return FZL_SYMTABLE_RESULT_NOT_OBJECT; + + symt->symbols[i].classname = classname; + return FZL_SYMTABLE_RESULT_OK; + } + } + + return FZL_SYMTABLE_RESULT_ERR; +} + bool fzl_symt_get(fzl_symtable_t *symt, char *name, fzl_sym_t *res) { if (!symt) diff --git a/src/vm/fzl_vm.c b/src/vm/fzl_vm.c index 899c82b..7573e24 100644 --- a/src/vm/fzl_vm.c +++ b/src/vm/fzl_vm.c @@ -1,4 +1,3 @@ -#include "vm/fzl_vm_env.h" #include #include #include @@ -10,6 +9,8 @@ #include #include #include +#include +#include char last_err[256]; @@ -24,18 +25,20 @@ 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->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; 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) @@ -122,6 +125,84 @@ int fzl_vm_run(fzl_vm_t *vm) switch (opcode) { + case FZL_OPCODE_ALLOC_OBJECT: + { + uint64_t values_count = vm->ops[++vm->ip]; + 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; + + vm->objects[vm->objects_count++] = obj; + vm->dstack[vm->dtop++] = + (fzl_value_t){.type = FZL_TYPE_OBJECT, .as.obj = obj}; + break; + } + + case FZL_OPCODE_GET_FIELD: + { + if (vm->dtop < 1) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected value in data stack"); + return -1; + } + + uint64_t field_slot = vm->ops[++vm->ip]; + fzl_value_t val = vm->dstack[--vm->dtop]; + if (val.type != FZL_TYPE_OBJECT) + { + snprintf( + last_err, sizeof(last_err), + "runtime error - cannot access field #%lu of non-object type", + field_slot); + return -1; + } + + fzl_vm_object_t *obj = val.as.obj; + if (obj->values_count <= field_slot) + { + snprintf(last_err, sizeof(last_err), + "runtime error - cannot access field #%lu", field_slot); + return -1; + } + + vm->dstack[vm->dtop++] = obj->values[field_slot]; + break; + } + + case FZL_OPCODE_SET_FIELD: + { + if (vm->dtop < 2) + { + snprintf(last_err, sizeof(last_err), + "runtime error - expected two values in data stack"); + return -1; + } + + uint64_t field_slot = vm->ops[++vm->ip]; + fzl_value_t objval = vm->dstack[--vm->dtop]; + fzl_value_t wrval = vm->dstack[--vm->dtop]; + if (objval.type != FZL_TYPE_OBJECT) + { + snprintf( + last_err, sizeof(last_err), + "runtime error - cannot access field #%lu of non-object type", + field_slot); + return -1; + } + + fzl_vm_object_t *obj = objval.as.obj; + if (obj->values_count <= field_slot) + { + snprintf(last_err, sizeof(last_err), + "runtime error - cannot access field #%lu", field_slot); + return -1; + } + + obj->values[field_slot] = wrval; + break; + } + case FZL_OPCODE_CALL: { uint64_t ip = vm->ops[++vm->ip]; diff --git a/src/vm/fzl_vm_debug.c b/src/vm/fzl_vm_debug.c index 11727a0..9f3f3ef 100644 --- a/src/vm/fzl_vm_debug.c +++ b/src/vm/fzl_vm_debug.c @@ -20,15 +20,27 @@ void fzl_vm_print_ops(fzl_vm_t *vm) switch (vm->ops[i]) { + case FZL_OPCODE_ALLOC_OBJECT: + printf("ALLOC_OBJECT: %lu fields\n", vm->ops[++i]); + break; + + case FZL_OPCODE_GET_FIELD: + printf("GET_FIELD #%lu\n", vm->ops[++i]); + break; + + case FZL_OPCODE_SET_FIELD: + printf("SET_FIELD #%lu\n", vm->ops[++i]); + break; + case FZL_OPCODE_CALL: { size_t ip = vm->ops[++i]; - printf("Call IP: %lu\n", ip); + printf("CALL IP: %lu\n", ip); break; } case FZL_OPCODE_RET: - printf("Return\n"); + printf("RET\n"); break; case FZL_OPCODE_LOADCONST: @@ -77,7 +89,7 @@ void fzl_vm_print_ops(fzl_vm_t *vm) case FZL_OPCODE_DIV: case FZL_OPCODE_POW: case FZL_OPCODE_MOD: - printf("Math opcode\n"); + printf("MATH\n"); break; case FZL_OPCODE_AND: @@ -129,7 +141,7 @@ void fzl_vm_print_ops(fzl_vm_t *vm) break; default: - printf("\n"); + printf("\n"); } } }