Files
fz-lang/src/compiler/fzl_walker.c
T

1387 lines
44 KiB
C

#include <common/fzl_builtins.h>
#include <common/fzl_opcodes.h>
#include <common/fzl_types.h>
#include <compiler/fzl_parser.h>
#include <compiler/fzl_token.h>
#include <compiler/fzl_walker.h>
#include <compiler/utils/fzl_const_pool.h>
#include <compiler/utils/fzl_ctable.h>
#include <compiler/utils/fzl_symtable.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
extern bool is_infinite_loop(fzl_walker_t *walker, fzl_node_t *node, size_t ls,
size_t le);
char *null_name = "<null>";
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));
walker->sym_top = 0;
walker->syms[walker->sym_top++] = fzl_mksymt();
if (!walker->syms[walker->sym_top - 1])
{
snprintf(wlast_err, sizeof(wlast_err), "failed to create global symtable");
return NULL;
}
walker->ctx = NULL;
walker->classes_count = 0;
memset(walker->classes, 0, sizeof(walker->classes));
return walker;
}
// --- Walking zone ---
// Convert type token to `fzl_type_t`
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)
return FZL_TYPE_INT16;
if (strcmp((char *)tok->value, "i32") == 0)
return FZL_TYPE_INT32;
if (strcmp((char *)tok->value, "i64") == 0)
return FZL_TYPE_INT64;
if (strcmp((char *)tok->value, "u8") == 0)
return FZL_TYPE_UINT8;
if (strcmp((char *)tok->value, "u16") == 0)
return FZL_TYPE_UINT16;
if (strcmp((char *)tok->value, "u32") == 0)
return FZL_TYPE_UINT32;
if (strcmp((char *)tok->value, "u64") == 0)
return FZL_TYPE_UINT64;
if (strcmp((char *)tok->value, "int") == 0)
return FZL_TYPE_INT;
if (strcmp((char *)tok->value, "uint") == 0)
return FZL_TYPE_UINT;
if (strcmp((char *)tok->value, "bool") == 0)
return FZL_TYPE_BOOL;
if (strcmp((char *)tok->value, "str") == 0)
return FZL_TYPE_STRING;
if (strcmp((char *)tok->value, "char") == 0)
return FZL_TYPE_CHAR;
return FZL_TYPE_NULL;
}
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)
{
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)
return true;
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)
{
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;
}
fzl_walk_res_t walk(fzl_walker_t *walker, fzl_node_t *node)
{
if (!walker || !node)
return (fzl_walk_res_t){.error = true, .error_msg = "walker is null"};
fzl_walk_res_t current_res;
while (node != NULL)
{
switch (node->type)
{
// NOTE: add new levels for infinite loop check:
// 1. Dead condition (f.e. `while (i < 5)` w/o `i` increment)
// 2. Step direction (f.e. `for (; i > 5; i = i - 1)`)
case FZL_NODETYPE_LOOP:
{
fzl_walk_ctx_t *newctx = nest_ctx(walker, FZL_WALK_CTX_TYPE_LOOP);
if (!newctx)
{
snprintf(wlast_err, sizeof(wlast_err), "failed to nest context");
return (fzl_walk_res_t){.error = true, .error_msg = wlast_err};
}
newctx->initial_env_lvl = walker->sym_top - 1;
newctx->breaks_count = 0;
newctx->breaks_remainder = 2;
newctx->breaks = malloc(newctx->breaks_remainder * sizeof(size_t));
push_op(walker, FZL_OPCODE_MK_ENVTABLE);
walker->syms[walker->sym_top++] = fzl_mksymt();
size_t loop_begin_ip = walker->op_count;
if (node->loop.init)
{
fzl_walk_res_t res = walk(walker, node->loop.init);
if (res.error)
{
free(newctx->breaks);
return res;
}
}
size_t start_ip = walker->op_count;
if (node->loop.cond)
{
fzl_walk_res_t res = walk(walker, node->loop.cond);
if (res.error)
{
free(newctx->breaks);
return res;
}
}
else
{
push_op(walker, FZL_OPCODE_PUSHNUM);
push_word(walker, 1);
}
push_op(walker, FZL_OPCODE_JZ);
newctx->breaks[newctx->breaks_count++] = walker->op_count;
push_word(walker, 0);
fzl_walk_res_t loop_body_res = walk(walker, node->loop.body);
if (loop_body_res.error)
{
free(newctx->breaks);
return loop_body_res;
}
if (node->loop.step)
{
fzl_walk_res_t res = walk(walker, node->loop.step);
if (res.error)
{
free(newctx->breaks);
return res;
}
}
push_op(walker, FZL_OPCODE_JMP);
push_word(walker, start_ip);
push_op(walker, FZL_OPCODE_RM_ENVTABLE);
bool is_infinite =
is_infinite_loop(walker, node, loop_begin_ip, walker->op_count - 2);
if (is_infinite)
{
free(newctx->breaks);
snprintf(wlast_err, sizeof(wlast_err),
"'%s': infinite loop detected at %ld:%ld",
node->token->filename, node->token->ln, node->token->cn);
return (fzl_walk_res_t){.error = true, .error_msg = wlast_err};
}
for (size_t i = walker->sym_top - 1; i > newctx->initial_env_lvl; i--)
fzl_freesymt(walker->syms[--walker->sym_top]);
// backpatch `break`s
for (size_t i = 0; i < newctx->breaks_count; i++)
walker->ops[newctx->breaks[i]] = walker->op_count - 1;
free(newctx->breaks);
if (!fin_ctx(walker))
{
snprintf(wlast_err, sizeof(wlast_err),
"failed to finish loop context");
return (fzl_walk_res_t){.error = true, .error_msg = wlast_err};
}
current_res = (fzl_walk_res_t){.type = FZL_TYPE_NULL, .error = false};
break;
}
case FZL_NODETYPE_CLASS:
{
fzl_walk_ctx_t *newctx = nest_ctx(walker, FZL_WALK_CTX_TYPE_CLASS);
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;
if (fn->fn.name->value)
free(fn->fn.name->value);
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_walk_res_t parent_res = walk(walker, node->memb.parent);
if (parent_res.error)
return parent_res;
if (parent_res.type != FZL_TYPE_OBJECT || !parent_res.classname)
{
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(parent_res.classname, walker->classes[i]->name) == 0)
{
ct = walker->classes[i];
break;
}
}
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);
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, parent_res.classname,
ftok->ln, ftok->cn);
return (fzl_walk_res_t){.error = true, .error_msg = wlast_err};
}
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 = parent_res.classname};
break;
}
case FZL_NODETYPE_FUNCTION:
{
push_op(walker, FZL_OPCODE_JMP);
size_t skip_ip = walker->op_count;
push_word(walker, (uint64_t)0);
fzl_type_t fntype = tok_to_type(node->fn.type);
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)
snprintf(
wlast_err, sizeof(wlast_err),
"'%s': cannot define already declared name '%s' at %ld:%ld",
node->token->filename, (char *)node->fn.name->value,
node->token->ln, node->token->cn);
else
snprintf(wlast_err, sizeof(wlast_err),
"'%s': failed to store name '%s'", node->fn.name->filename,
(char *)node->fn.name->value);
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
ssize_t lvl = walker->sym_top;
walker->syms[walker->sym_top++] = fzl_mksymt();
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);
for (ssize_t i = node->fn.args_count - 1; i >= 0; i--)
{
fzl_walk_res_t res = walk(walker, node->fn.args[i]);
if (res.error)
return res;
push_op(walker, FZL_OPCODE_WRITE);
}
fzl_walk_res_t bodyres = walk(walker, node->fn.body);
if (bodyres.error)
return bodyres;
// NOTE: ghost walk (w/o opcodes write) for arg check should be there in
// near future
push_op(walker, FZL_OPCODE_RM_ENVTABLE);
push_op(walker, FZL_OPCODE_RET);
walker->ops[skip_ip] = walker->op_count;
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]);
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;
}
case FZL_NODETYPE_RETURN:
{
fzl_walk_ctx_t *ctx = walker->ctx;
while (ctx)
{
if (ctx && ctx->type == FZL_WALK_CTX_TYPE_FN)
break;
ctx = ctx->parent;
}
if (!ctx || ctx->type != FZL_WALK_CTX_TYPE_FN)
{
snprintf(
wlast_err, sizeof(wlast_err),
"'%s': return cannot be used outside of functions (at %ld:%ld)",
node->token->filename, node->token->ln, node->token->cn);
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;
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(
wlast_err, sizeof(wlast_err),
"'%s': incompatible types between return and function type at "
"%ld:%ld",
node->token->filename, node->token->ln, node->token->cn);
return (fzl_walk_res_t){.error = true, .error_msg = wlast_err};
}
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;
}
case FZL_NODETYPE_NOT:
{
fzl_walk_res_t res = walk(walker, node->unary_op.operand);
if (res.error)
return res;
push_op(walker, FZL_OPCODE_NOT);
current_res = (fzl_walk_res_t){.type = FZL_TYPE_BOOL, .error = false};
break;
}
case FZL_NODETYPE_EQUAL:
case FZL_NODETYPE_NEQUAL:
case FZL_NODETYPE_LESS:
case FZL_NODETYPE_LESS_EQUAL:
case FZL_NODETYPE_GREATER:
case FZL_NODETYPE_GREATER_EQUAL:
{
fzl_walk_res_t left = walk(walker, node->binary_op.left);
if (left.error)
return left;
fzl_walk_res_t right = walk(walker, node->binary_op.right);
if (right.error)
return right;
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,
node->token->ln, node->token->cn);
return (fzl_walk_res_t){.error = true, .error_msg = wlast_err};
}
if (left.type == FZL_TYPE_STRING && right.type == FZL_TYPE_STRING)
{
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 = FZL_TYPE_BOOL, .error = false};
break;
}
case FZL_NODETYPE_OR:
case FZL_NODETYPE_AND:
{
fzl_walk_res_t left = walk(walker, node->binary_op.left);
if (left.error)
return left;
fzl_walk_res_t right = walk(walker, node->binary_op.right);
if (right.error)
return right;
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,
node->token->ln, node->token->cn);
return (fzl_walk_res_t){.error = true, .error_msg = wlast_err};
}
if (node->type == FZL_NODETYPE_AND)
push_op(walker, FZL_OPCODE_AND);
else
push_op(walker, FZL_OPCODE_OR);
current_res = (fzl_walk_res_t){.type = FZL_TYPE_BOOL, .error = false};
break;
}
case FZL_NODETYPE_CONDITIONAL:
{
fzl_walk_res_t cres = walk(walker, node->cond.condition);
if (cres.error)
return cres;
push_op(walker, FZL_OPCODE_JZ);
size_t jz_if = walker->op_count;
push_word(walker, (uint64_t)0);
fzl_walk_res_t tblock_res = walk(walker, node->cond.true_block);
if (tblock_res.error)
return tblock_res;
push_op(walker, FZL_OPCODE_JMP);
size_t jmp_else = walker->op_count;
push_word(walker, (uint64_t)0);
walker->ops[jz_if] = walker->op_count;
if (node->cond.else_block)
{
fzl_walk_res_t fblock_res = walk(walker, node->cond.else_block);
if (fblock_res.error)
return fblock_res;
}
walker->ops[jmp_else] = walker->op_count;
current_res = (fzl_walk_res_t){.type = FZL_TYPE_NULL, .error = false};
break;
}
case FZL_NODETYPE_UNARY_PLUS:
{
fzl_walk_res_t res = walk(walker, node->unary_op.operand);
if (res.error)
return res;
current_res = res;
break;
}
case FZL_NODETYPE_IDENTIFIER:
{
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});
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->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};
}
push_op(walker, FZL_OPCODE_LOADCONST);
push_word(walker, n);
push_op(walker, FZL_OPCODE_LOOKUP_ENV);
current_res = (fzl_walk_res_t){
.type = sym.type, .error = false, .classname = sym.classname};
break;
}
case FZL_NODETYPE_BLOCK:
walker->syms[walker->sym_top++] = fzl_mksymt();
push_op(walker, FZL_OPCODE_MK_ENVTABLE);
if (node->list.entry_count > 0)
for (size_t i = 0; i < node->list.entry_count; i++)
{
fzl_walk_res_t res = walk(walker, node->list.entries[i]);
if (res.error)
return res;
}
fzl_freesymt(walker->syms[--walker->sym_top]);
push_op(walker, FZL_OPCODE_RM_ENVTABLE);
current_res = (fzl_walk_res_t){.type = FZL_TYPE_NULL, .error = false};
break;
case FZL_NODETYPE_CALL:
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)
{
snprintf(wlast_err, sizeof(wlast_err),
"'%s': 'printline' 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 (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)
{
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;
}
}
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),
"'%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};
}
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, .classname = res.classname};
}
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_walk_res_t parent_res = walk(walker, parent_node);
if (parent_res.error)
return parent_res;
if (parent_res.type != FZL_TYPE_OBJECT || !parent_res.classname)
{
snprintf(wlast_err, sizeof(wlast_err),
"'%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};
}
char *classname = parent_res.classname;
bool class_exists = false;
for (size_t i = 0; i < walker->classes_count; i++)
{
if (strcmp(classname, walker->classes[i]->name) == 0)
{
class_exists = true;
break;
}
}
if (!class_exists)
{
snprintf(wlast_err, sizeof(wlast_err),
"'%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};
}
size_t classlen = strlen(classname);
size_t fnlen = strlen(method_node->token->value);
char *methodname = malloc(classlen + fnlen + 2);
strcpy(methodname, classname);
strcpy((methodname + classlen + 1),
node->call.callee->token->value);
methodname[classlen] = '_';
methodname[classlen + fnlen + 1] = 0;
fzl_sym_t fn_sym;
bool found_method = false;
for (ssize_t i = walker->sym_top - 1; i >= 0; i--)
if ((found_method =
fzl_symt_get(walker->syms[i], methodname, &fn_sym)))
break;
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};
}
free(methodname);
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, fn_sym.ip);
current_res = (fzl_walk_res_t){.type = fn_sym.type,
.error = false,
.classname = fn_sym.classname};
}
}
break;
case FZL_NODETYPE_LITERAL:
if (node->token->type == FZL_TOKTYPE_STR_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);
current_res =
(fzl_walk_res_t){.type = FZL_TYPE_STRING, .error = false};
}
else if (node->token->type == FZL_TOKTYPE_NUM_LITERAL)
{
push_op(walker, FZL_OPCODE_PUSHNUM);
uint64_t num = strtol(node->token->value, NULL, 10);
push_word(walker, num);
current_res = (fzl_walk_res_t){.type = FZL_TYPE_INT, .error = false};
}
else if (node->token->type == FZL_TOKTYPE_BOOL_LITERAL)
{
fzl_token_t *tok = node->token;
uint64_t val = strcmp(tok->value, "true") == 0 ? 1 : 0;
push_op(walker, FZL_OPCODE_PUSHBOOL);
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)
{
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};
}
break;
case FZL_NODETYPE_ADD:
case FZL_NODETYPE_SUB:
case FZL_NODETYPE_MUL:
case FZL_NODETYPE_DIV:
case FZL_NODETYPE_POW:
case FZL_NODETYPE_MOD:
{
fzl_walk_res_t lres = walk(walker, node->binary_op.left);
if (lres.error)
return lres;
fzl_walk_res_t rres = walk(walker, node->binary_op.right);
if (rres.error)
return rres;
if (lres.type < FZL_TYPE_INT8)
{
fzl_token_t *ltok = node->binary_op.left->token;
snprintf(wlast_err, sizeof(wlast_err),
"'%s': left operand at %ld:%ld is not compatible to integer "
"types",
ltok->filename, ltok->ln, ltok->cn);
return (fzl_walk_res_t){.error = true, .error_msg = wlast_err};
}
if (rres.type < FZL_TYPE_INT8)
{
fzl_token_t *rtok = node->binary_op.right->token;
snprintf(wlast_err, sizeof(wlast_err),
"'%s': right operand at %ld:%ld is not compatible to "
"integer types",
rtok->filename, rtok->ln, rtok->cn);
return (fzl_walk_res_t){.error = true, .error_msg = wlast_err};
}
switch (node->type)
{
case FZL_NODETYPE_ADD:
push_op(walker, FZL_OPCODE_ADD);
break;
case FZL_NODETYPE_SUB:
push_op(walker, FZL_OPCODE_SUB);
break;
case FZL_NODETYPE_MUL:
push_op(walker, FZL_OPCODE_MUL);
break;
case FZL_NODETYPE_DIV:
push_op(walker, FZL_OPCODE_DIV);
break;
case FZL_NODETYPE_POW:
push_op(walker, FZL_OPCODE_POW);
break;
case FZL_NODETYPE_MOD:
push_op(walker, FZL_OPCODE_MOD);
break;
default:
break;
}
current_res = (fzl_walk_res_t){.type = lres.type, .error = false};
break;
}
case FZL_NODETYPE_UNARY_MINUS:
{
fzl_walk_res_t res = walk(walker, node->unary_op.operand);
if (res.error)
return res;
if (res.type < FZL_TYPE_INT8)
{
fzl_token_t *tok = node->unary_op.operand->token;
snprintf(wlast_err, sizeof(wlast_err),
"'%s': incompatible with unary operator type at %ld:%ld",
tok->filename, tok->ln, tok->cn);
return (fzl_walk_res_t){.error = true, .error_msg = wlast_err};
}
push_op(walker, FZL_OPCODE_NEG);
current_res = (fzl_walk_res_t){.type = res.type, .error = false};
break;
}
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,
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;
if (res == FZL_SYMTABLE_RESULT_EXISTS)
{
snprintf(wlast_err, sizeof(wlast_err),
"'%s': cannot redefine '%s' at %ld:%ld", tok->filename,
(char *)tok->value, tok->ln, tok->cn);
}
else
{
snprintf(wlast_err, sizeof(wlast_err),
"'%s': cannot store '%s' at %ld:%ld", tok->filename,
(char *)tok->value, tok->ln, tok->cn);
}
return (fzl_walk_res_t){.error = true, .error_msg = wlast_err};
}
fzl_token_t *id_tok = node->var.identifier->token;
size_t const_n = add_const(
walker, (fzl_const_t){.type = FZL_CONSTTYPE_STRING,
.as.string.chars = id_tok->value,
.as.string.length = id_tok->value_sz});
push_op(walker, FZL_OPCODE_LOADCONST);
push_word(walker, const_n);
push_op(walker, FZL_OPCODE_STORE);
push_op(walker, FZL_OPCODE_LOADCONST);
push_word(walker, const_n);
current_res = (fzl_walk_res_t){.type = FZL_TYPE_NULL,
.error = false,
.classname = node->var.type->value};
break;
}
case FZL_NODETYPE_ASSIGN:
{
fzl_walk_res_t rres = walk(walker, node->binary_op.right);
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)
{
fzl_sym_t sym;
bool found = false;
for (ssize_t i = walker->sym_top - 1; i >= 0; i--)
if ((found =
fzl_symt_get(walker->syms[i],
node->binary_op.left->token->value, &sym)))
break;
if (!found)
{
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);
return (fzl_walk_res_t){.error = true, .error_msg = wlast_err};
}
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),
"'%s': R-Value is not compatible with L-Value at %ld:%ld",
tok->filename, tok->ln, tok->cn);
return (fzl_walk_res_t){.error = true, .error_msg = wlast_err};
}
size_t const_n = add_const(
walker, (fzl_const_t){.type = FZL_CONSTTYPE_STRING,
.as.string.chars = sym.name,
.as.string.length =
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 = NULL;
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 = NULL;
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};
}
current_res = (fzl_walk_res_t){
.type = rres.type, .error = false, .classname = rres.classname};
break;
}
}
node = node->next;
}
return current_res;
}
// Walk entire AST recursevily
int fzl_walk(fzl_walker_t *walker)
{
if (!walker)
{
snprintf(wlast_err, sizeof(wlast_err), "walker is null");
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 (walker->ctx)
{
while (walker->ctx)
fin_ctx(walker);
}
if (result.error && !wlast_err[0])
snprintf(wlast_err, sizeof(wlast_err), "%s", result.error_msg);
return result.error ? -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);
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);
}