feat: implement AST walker
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,34 @@
|
||||
#ifndef FZL_WALKER_H
|
||||
#define FZL_WALKER_H
|
||||
|
||||
#include <common/fzl_opcodes.h>
|
||||
#include <compiler/fzl_parser.h>
|
||||
#include <compiler/utils/fzl_const_pool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// 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
|
||||
@@ -0,0 +1,64 @@
|
||||
#ifndef FZL_CONST_SET_H
|
||||
#define FZL_CONST_SET_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// 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
|
||||
+16
-17
@@ -2,13 +2,13 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
#include <common/fzl_builtins.h>
|
||||
#include <common/fzl_opcodes.h>
|
||||
#include <compiler/fzl_parser.h>
|
||||
#include <compiler/fzl_walker.h>
|
||||
#include <compiler/utils/fzl_const_pool.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <compiler/fzl_debug.h>
|
||||
#include <compiler/fzl_parser.h>
|
||||
#include <compiler/fzl_reader.h>
|
||||
#include <compiler/fzl_walker.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
#include <compiler/utils/fzl_const_pool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user