diff --git a/CMakeLists.txt b/CMakeLists.txt index 73f0dfd..4b60a09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,8 @@ add_executable(fzc src/compiler/fzl_reader.c src/compiler/fzl_parser.c src/compiler/fzl_debug.c + src/compiler/fzl_walker.c + src/compiler/utils/fzl_const_pool.c src/compiler/main.c) # VM diff --git a/include/common/fzl_builtins.h b/include/common/fzl_builtins.h new file mode 100644 index 0000000..678de31 --- /dev/null +++ b/include/common/fzl_builtins.h @@ -0,0 +1,9 @@ +#ifndef FZL_BUILTINS_H +#define FZL_BUILTINS_H + +typedef enum +{ + FZL_BUILTIN_PRINTLINE // Print message in arg with newline at end +} fzl_builtin_t; + +#endif // FZL_BUILTINS_H diff --git a/include/common/fzl_opcodes.h b/include/common/fzl_opcodes.h new file mode 100644 index 0000000..aab733f --- /dev/null +++ b/include/common/fzl_opcodes.h @@ -0,0 +1,12 @@ +#ifndef FZL_OPCODES_H +#define FZL_OPCODES_H + +// Available opcodes +typedef enum +{ + FZL_OPCODE_LOADCONST, // Load const into T (temp) reg + FZL_OPCODE_PUSHARG, // Push argument from T into args stack + FZL_OPCODE_CALL_BUILTIN // Call builtin function +} fzl_opcode_t; + +#endif // FZL_OPCODES_H diff --git a/include/compiler/fzl_walker.h b/include/compiler/fzl_walker.h new file mode 100644 index 0000000..0300873 --- /dev/null +++ b/include/compiler/fzl_walker.h @@ -0,0 +1,34 @@ +#ifndef FZL_WALKER_H +#define FZL_WALKER_H + +#include +#include +#include +#include + +// Walker descriptor +typedef struct +{ + fzl_node_t *root; + fzl_const_hash_pool_t *consts; + + uint64_t *ops; + size_t op_count; + size_t op_remainder; +} fzl_walker_t; + +// Allocate new walker +fzl_walker_t *fzl_mkwalker(fzl_node_t *root); + +// Walk throught the entire AST and build: +// 1. Constants table +// 2. Operations +int fzl_walk(fzl_walker_t *walker); + +// Get last error message +char *fzl_walker_geterror(); + +// Free walker +void fzl_freewalker(fzl_walker_t *walker); + +#endif // FZL_WALKER_H diff --git a/include/compiler/utils/fzl_const_pool.h b/include/compiler/utils/fzl_const_pool.h new file mode 100644 index 0000000..402a934 --- /dev/null +++ b/include/compiler/utils/fzl_const_pool.h @@ -0,0 +1,64 @@ +#ifndef FZL_CONST_SET_H +#define FZL_CONST_SET_H + +#include +#include +#include + +// Constant types +typedef enum +{ + FZL_CONSTTYPE_STRING +} fzl_consttype_t; + +// Constant descriptor +typedef struct +{ + fzl_consttype_t type; + + union + { + struct + { + const char *chars; + size_t length; + } string; + } as; +} fzl_const_t; + +typedef struct +{ + size_t pool_index; +} fzl_const_hash_slot_t; + +// Constant pool descriptor +typedef struct +{ + fzl_const_t *array; + size_t size; + size_t capacity; + + fzl_const_hash_slot_t *slots; + size_t slots_count; + size_t slots_capacity; +} fzl_const_hash_pool_t; + +// Compare two constants for equality +bool fzl_consts_equal(fzl_const_t a, fzl_const_t b); + +// Get hash of specified constant +static uint32_t fzl_hash_const(fzl_const_t c); + +// Allocate constants hash pool +fzl_const_hash_pool_t *fzl_mkconstpool(); + +// Resize the hash table +static void fzl_consts_pool_resize_hash(fzl_const_hash_pool_t *pool); + +// Push constant. Returns unique index +size_t fzl_consts_pool_add(fzl_const_hash_pool_t *pool, fzl_const_t c); + +// Free constants hash pool +void fzl_consts_pool_free(fzl_const_hash_pool_t *pool); + +#endif // FZL_CONST_SET_H diff --git a/src/compiler/fzl_parser.c b/src/compiler/fzl_parser.c index 0c1bbe6..b93ab88 100644 --- a/src/compiler/fzl_parser.c +++ b/src/compiler/fzl_parser.c @@ -2,13 +2,13 @@ #include #include -char last_err[256]; +char plast_err[256]; fzl_parser_t *fzl_mkparser(fzl_reader_ll_t *first) { if (!first) { - snprintf(last_err, sizeof(last_err), "token is null"); + snprintf(plast_err, sizeof(plast_err), "token is null"); return NULL; } @@ -81,7 +81,7 @@ fzl_node_t *parse_primary(fzl_parser_t *parser) return fzl_mknode(FZL_NODETYPE_LITERAL, tok); } - snprintf(last_err, sizeof(last_err), "failed to parse primary at %d", + snprintf(plast_err, sizeof(plast_err), "failed to parse primary at %d", peek(parser)->i0); return NULL; } @@ -126,7 +126,7 @@ fzl_node_t *parse_call(fzl_parser_t *parser) consume(parser); else if (peek(parser) && peek(parser)->type != FZL_TOKTYPE_RPAIR) { - snprintf(last_err, sizeof(last_err), + snprintf(plast_err, sizeof(plast_err), "expected ',' or ')' after arg list"); return NULL; } @@ -147,7 +147,7 @@ fzl_node_t *parse_expression(fzl_parser_t *parser, int precedence) else if (peek(parser) && peek(parser)->type == FZL_TOKTYPE_STR_LITERAL) return parse_primary(parser); - snprintf(last_err, sizeof(last_err), "failed to parse expression at %d", + snprintf(plast_err, sizeof(plast_err), "failed to parse expression at %d", peek(parser)->i0); return NULL; } @@ -184,7 +184,7 @@ fzl_node_t *parse_program(fzl_parser_t *parser) if (!first) { - snprintf(last_err, sizeof(last_err), "empty ;("); + snprintf(plast_err, sizeof(plast_err), "empty ;("); return NULL; } @@ -204,7 +204,7 @@ int fzl_parse(fzl_parser_t *parser) // --- Parse zone end --- -char *fzl_parser_geterror() { return last_err; } +char *fzl_parser_geterror() { return plast_err; } void fzl_freenode(fzl_node_t *node) { @@ -220,17 +220,16 @@ void fzl_freeast(fzl_node_t *root) while (root) { - if (root->type == FZL_NODETYPE_BLOCK) + if (root->type == FZL_NODETYPE_BLOCK || root->type == FZL_NODETYPE_CALL) { - for (size_t i = 0; i < root->list.entry_count; i++) - if (root->list.entries[i]) - fzl_freeast(root->list.entries[i]); - } - else if (root->type == FZL_NODETYPE_CALL) - { - for (size_t i = 0; i < root->list.entry_count; i++) - if (root->list.entries[i]) - fzl_freeast(root->list.entries[i]); + if (root->list.entries) + { + for (size_t i = 0; i < root->list.entry_count; i++) + if (root->list.entries[i]) + fzl_freeast(root->list.entries[i]); + + free(root->list.entries); + } } fzl_freenode(root); diff --git a/src/compiler/fzl_walker.c b/src/compiler/fzl_walker.c new file mode 100644 index 0000000..46ad2cd --- /dev/null +++ b/src/compiler/fzl_walker.c @@ -0,0 +1,168 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char wlast_err[256]; + +fzl_walker_t *fzl_mkwalker(fzl_node_t *root) +{ + if (!root) + { + snprintf(wlast_err, sizeof(wlast_err), "root node is null"); + return NULL; + } + + fzl_walker_t *walker = malloc(sizeof(fzl_walker_t)); + if (!walker) + { + snprintf(wlast_err, sizeof(wlast_err), "failed to allocated walker"); + return NULL; + } + + fzl_const_hash_pool_t *pool = fzl_mkconstpool(); + if (!pool) + { + snprintf(wlast_err, sizeof(wlast_err), "failed to create constants pool"); + return NULL; + } + + walker->root = root; + walker->consts = pool; + + walker->op_count = 0; + walker->op_remainder = 2; + walker->ops = malloc(walker->op_remainder * sizeof(uint64_t)); + + return walker; +} + +// --- Walking zone --- + +// Add const to consts table +size_t add_const(fzl_walker_t *walker, fzl_const_t c) +{ + if (!walker) + return false; + return fzl_consts_pool_add(walker->consts, c); +} + +// Resize ops if size is equals to capacity +void ops_resize_if_needed(fzl_walker_t *walker) +{ + if (!walker) + return; + + if (walker->op_count >= walker->op_remainder) + { + walker->op_remainder *= 2; + walker->ops = realloc(walker->ops, walker->op_remainder * sizeof(uint64_t)); + } +} + +// Push opcode to operations array +void push_op(fzl_walker_t *walker, fzl_opcode_t opcode) +{ + if (!walker) + return; + + ops_resize_if_needed(walker); + walker->ops[walker->op_count++] = (uint64_t)opcode; +} + +// Push 64-bit integer to operations array +void push_word(fzl_walker_t *walker, uint64_t word) +{ + if (!walker) + return; + + ops_resize_if_needed(walker); + walker->ops[walker->op_count++] = word; +} + +bool walk(fzl_walker_t *walker, fzl_node_t *node) +{ + if (!walker || !node) + return false; + + while (node != NULL) + { + switch (node->type) + { + case FZL_NODETYPE_BLOCK: + // NOTE: create block env here + if (node->list.entry_count > 0) + for (size_t i = 0; i < node->list.entry_count; i++) + walk(walker, node->list.entries[i]); + // NOTE: destroy block env here + break; + + case FZL_NODETYPE_CALL: + if (node->list.entry_count > 0) + { + for (size_t i = 0; i < node->list.entry_count; i++) + { + walk(walker, node->list.entries[i]); + push_op(walker, FZL_OPCODE_PUSHARG); + } + } + // NOTE: you need to implement dynamic names call + push_op(walker, FZL_OPCODE_CALL_BUILTIN); + + if (strcmp(node->token->value, "printline") == 0) + push_word(walker, FZL_BUILTIN_PRINTLINE); + else + { + snprintf(wlast_err, sizeof(wlast_err), "call to unknown function '%s'", + (char *)node->token->value); + return false; + } + break; + + case FZL_NODETYPE_LITERAL: + size_t n = add_const( + walker, (fzl_const_t){.type = FZL_CONSTTYPE_STRING, + .as.string.chars = node->token->value, + .as.string.length = node->token->value_sz}); + + push_op(walker, FZL_OPCODE_LOADCONST); + push_word(walker, n); + break; + } + + node = node->next; + } + + return true; +} + +// Walk entire AST recursevily +int fzl_walk(fzl_walker_t *walker) +{ + bool result = walk(walker, walker->root); + return !result && wlast_err[0] ? -1 : 0; +} + +// --- Walking zone end --- + +char *fzl_walker_geterror() { return wlast_err; } + +void fzl_freewalker(fzl_walker_t *walker) +{ + if (!walker) + return; + + fzl_consts_pool_free(walker->consts); + + if (walker->ops) + free(walker->ops); + + free(walker); +} diff --git a/src/compiler/main.c b/src/compiler/main.c index c53ef42..3407ec0 100644 --- a/src/compiler/main.c +++ b/src/compiler/main.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -155,12 +156,35 @@ int main(int argc, char **argv) { printf("ok!\n"); fzl_debug_print_ast(parser->root); + printf("-> walking AST..."); + } + + fzl_walker_t *walker = fzl_mkwalker(parser->root); + result = fzl_walk(walker); + if (result != 0) + { + printf("Error: %s\n", fzl_walker_geterror()); + return -1; + } + + if (args->verbose) + { + printf("ok!\n"); + + printf("--> Constants table:\n"); + for (size_t i = 0; i < walker->consts->size; i++) + printf("---> %s\n", walker->consts->array[i].as.string.chars); + + printf("--> Ops:\n"); + for (size_t i = 0; i < walker->op_count; i++) + printf("---> %lu\n", walker->ops[i]); } free(args); fzl_freereader(rd); fzl_freeparser(parser); fzl_freeast(parser->root); + fzl_freewalker(walker); printf("done.\n"); return 0; diff --git a/src/compiler/utils/fzl_const_pool.c b/src/compiler/utils/fzl_const_pool.c new file mode 100644 index 0000000..abb0d0b --- /dev/null +++ b/src/compiler/utils/fzl_const_pool.c @@ -0,0 +1,140 @@ +#include +#include +#include +#include +#include + +bool fzl_consts_equal(fzl_const_t a, fzl_const_t b) +{ + if (a.type != b.type) + return false; + + switch (a.type) + { + case FZL_CONSTTYPE_STRING: + { + if (a.as.string.length != b.as.string.length) + return false; + + return memcmp(a.as.string.chars, b.as.string.chars, a.as.string.length) == + 0; + } + } + + return false; +} + +static uint32_t fzl_hash_const(fzl_const_t c) +{ + uint32_t hash = 2166136261u; + const uint8_t *data = NULL; + size_t size = 0; + + switch (c.type) + { + case FZL_CONSTTYPE_STRING: + data = (const uint8_t *)&c.as.string.chars; + size = c.as.string.length; + break; + } + + for (size_t i = 0; i < size; i++) + { + hash ^= data[i]; + hash *= 16777619u; + } + + return hash; +} + +fzl_const_hash_pool_t *fzl_mkconstpool() +{ + fzl_const_hash_pool_t *pool = malloc(sizeof(fzl_const_hash_pool_t)); + + pool->size = 0; + pool->capacity = 8; + pool->array = malloc(pool->capacity * sizeof(fzl_const_t)); + + pool->slots_count = 0; + pool->slots_capacity = 16; + pool->slots = malloc(pool->slots_capacity * sizeof(fzl_const_hash_slot_t)); + for (int i = 0; i < pool->slots_capacity; i++) + pool->slots[i].pool_index = -1; + + return pool; +} + +static void fzl_consts_pool_resize_hash(fzl_const_hash_pool_t *pool) +{ + int old_capacity = pool->slots_capacity; + fzl_const_hash_slot_t *old_slots = pool->slots; + + pool->slots_capacity = old_capacity * 2; + pool->slots = malloc(pool->slots_capacity * sizeof(fzl_const_hash_slot_t)); + for (int i = 0; i < pool->slots_capacity; i++) + pool->slots[i].pool_index = -1; + pool->slots_count = 0; + + for (int i = 0; i < old_capacity; i++) + { + if (old_slots[i].pool_index == -1) + continue; + + int index_in_pool = old_slots[i].pool_index; + fzl_const_t c = pool->array[index_in_pool]; + uint32_t hash = fzl_hash_const(c); + + uint32_t bucket = hash & (pool->slots_capacity - 1); + while (pool->slots[bucket].pool_index != -1) + bucket = (bucket + 1) & (pool->slots_capacity - 1); + + pool->slots[bucket].pool_index = index_in_pool; + pool->slots_count++; + } + + free(old_slots); +} + +size_t fzl_consts_pool_add(fzl_const_hash_pool_t *pool, fzl_const_t c) +{ + if (pool->slots_count * 4 >= pool->slots_capacity * 3) + fzl_consts_pool_resize_hash(pool); + + uint32_t hash = fzl_hash_const(c); + uint32_t bucket = hash & (pool->slots_capacity - 1); + + while (pool->slots[bucket].pool_index != -1) + { + size_t existing_index = pool->slots[bucket].pool_index; + if (fzl_consts_equal(pool->array[existing_index], c)) + return existing_index; + + bucket = (bucket + 1) & (pool->slots_capacity - 1); + } + + if (pool->size >= pool->capacity) + { + pool->capacity *= 2; + pool->array = (fzl_const_t *)realloc(pool->array, + pool->capacity * sizeof(fzl_const_t)); + } + + size_t new_index = pool->size; + pool->array[new_index] = c; + pool->size++; + + pool->slots[bucket].pool_index = new_index; + pool->slots_count++; + + return new_index; +} + +void fzl_consts_pool_free(fzl_const_hash_pool_t *pool) +{ + if (!pool) + return; + + free(pool->array); + free(pool->slots); + free(pool); +}