aboutsummaryrefslogtreecommitdiff
path: root/src/actualize.c
diff options
context:
space:
mode:
authorKimplul <kimi.h.kuparinen@gmail.com>2023-03-23 16:14:51 +0200
committerKimplul <kimi.h.kuparinen@gmail.com>2023-03-23 16:14:51 +0200
commit5b038666c3e911ffeb78a4d656044e5bd076705b (patch)
tree9b09b4f27c39ef6a0139a5eb9bfa5e98a250086c /src/actualize.c
parent70615379062cdd2e016f5095dfafd23a6dca148c (diff)
downloadek-5b038666c3e911ffeb78a4d656044e5bd076705b.tar.gz
ek-5b038666c3e911ffeb78a4d656044e5bd076705b.zip
integrate parser work
Diffstat (limited to 'src/actualize.c')
-rw-r--r--src/actualize.c1797
1 files changed, 1797 insertions, 0 deletions
diff --git a/src/actualize.c b/src/actualize.c
new file mode 100644
index 0000000..0148569
--- /dev/null
+++ b/src/actualize.c
@@ -0,0 +1,1797 @@
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <cu/actualize.h>
+#include <cu/compiler.h>
+#include <cu/debug.h>
+
+#define UNUSED(x) do { (void)(x); } while (0)
+
+struct act_stack {
+ struct ast_node *node;
+ struct act_stack *next;
+};
+
+enum act_flags {
+ ACT_IN_LOOP = (1 << 0),
+ ACT_IN_SWITCH = (1 << 1),
+ ACT_ONLY_TYPES = (1 << 2),
+ ACT_HAS_RETURN = (1 << 3),
+};
+
+struct act_state {
+ enum act_flags flags;
+
+ struct ast_node *last_var;
+ struct ast_node *cur_template;
+ struct ast_node *cur_proc;
+ struct act_stack *defer_stack;
+ struct act_stack *goto_stack;
+ struct act_stack *label_stack;
+};
+
+static void act_set_flags(struct act_state *state, enum act_flags flags)
+{
+ state->flags |= flags;
+}
+
+static enum act_flags act_flags(struct act_state *state, enum act_flags flags)
+{
+ return state->flags & flags;
+}
+
+/* TODO: this could be improved if we get a global void thing */
+static int is_void(struct ast_node *type)
+{
+ assert(type->node_type == AST_TYPE);
+ if (type->_type.kind != AST_TYPE_ID)
+ return 0;
+
+ struct ast_node *id = type->_type.id;
+ if (strcmp(id->_id.id, "void") != 0)
+ return 0;
+
+ return 1;
+}
+
+static struct ast_node *void_type()
+{
+ char *void_str = strdup("void");
+ if (!void_str) {
+ internal_error("couldn't allocate void string");
+ return NULL;
+ }
+
+ struct ast_node *void_id = gen_id(void_str);
+ if (!void_id) {
+ internal_error("couldn't allocate void id");
+ free(void_str);
+ return NULL;
+ }
+
+ struct ast_node *void_type = gen_type(AST_TYPE_ID, void_id, NULL, NULL);
+ if (!void_type) {
+ internal_error("couldn't allocate void type");
+ destroy_ast_node(void_id);
+ return NULL;
+ }
+
+ void_type->type = void_type;
+ return void_type;
+}
+
+static int push_defer(struct act_state *state, struct ast_node *expr)
+{
+ struct act_stack *new = calloc(1, sizeof(struct act_stack));
+ if (!new) {
+ internal_error("failed allocating defer");
+ return -1;
+ }
+
+ new->node = expr;
+ new->next = state->defer_stack;
+ state->defer_stack = new;
+ return 0;
+}
+
+static int push_label(struct act_state *state, struct ast_node *label)
+{
+ struct act_stack *new = calloc(1, sizeof(struct act_stack));
+ if (!new) {
+ internal_error("failed allocating label");
+ return -1;
+ }
+
+ new->node = label;
+ new->next = state->label_stack;
+ state->label_stack = new;
+ return 0;
+}
+
+static struct ast_node *find_label(struct act_state *state, struct ast_node *label)
+{
+ assert(label->node_type == AST_LABEL);
+ struct act_stack *prev = state->label_stack, *cur;
+ if (prev)
+ do {
+ cur = prev->next;
+ if (identical_ast_nodes(0, prev->node, label))
+ return prev->node;
+ } while ((prev = cur));
+
+ return NULL;
+}
+
+static int push_goto(struct act_state *state, struct ast_node *got)
+{
+ struct act_stack *new = calloc(1, sizeof(struct act_stack));
+ if (!new) {
+ internal_error("failed allocating goto");
+ return -1;
+ }
+
+ new->node = got;
+ new->next = state->goto_stack;
+ state->goto_stack = new;
+ return 0;
+}
+
+static struct ast_node *clone_defers(struct act_state *state, struct act_stack *to)
+{
+ struct act_stack *from = state->defer_stack;
+
+ /* maintain reverse order */
+ struct ast_node *defers = NULL, *prev = NULL;
+ while (from != to) {
+ struct ast_node *defer = clone_ast_node(from->node);
+ if (prev)
+ prev->next = defer;
+
+ if (!defers)
+ defers = defer;
+
+ prev = defer;
+ from = from->next;
+ }
+
+ return defers;
+}
+
+static void clear_stack(struct act_stack *from, struct act_stack *to)
+{
+ while (from != to) {
+ struct act_stack *next = from->next;
+ free(from);
+ from = next;
+ }
+}
+
+static void clear_defers(struct act_state *state, struct act_stack *to)
+{
+ struct act_stack *from = state->defer_stack;
+ clear_stack(from, to);
+ state->defer_stack = to;
+}
+
+static void clear_gotos(struct act_state *state, struct act_stack *to)
+{
+ struct act_stack *from = state->goto_stack;
+ clear_stack(from, to);
+ state->goto_stack = to;
+}
+
+static void clear_labels(struct act_state *state, struct act_stack *to)
+{
+ struct act_stack *from = state->label_stack;
+ clear_stack(from, to);
+ state->label_stack = to;
+}
+
+static void destroy_act_state(struct act_state *state)
+{
+ /* clears all defers */
+ clear_defers(state, NULL);
+ clear_labels(state, NULL);
+ clear_gotos(state, NULL);
+}
+
+static int actualize(struct act_state *state, struct scope *scope, struct ast_node *node);
+static int analyze(struct scope *scope, struct ast_node *tree);
+
+static int eval_const_if(struct scope *scope, struct ast_node *node)
+{
+ assert(node
+ && node->node_type == AST_IF
+ && ast_flags(node, AST_FLAG_CONST));
+
+ int eval = 0;
+ struct ast_node *cond = node->_if.cond;
+ struct ast_node *next = node->next;
+
+ switch (cond->node_type) {
+ /* for now just check if the variable exists, might change in
+ * the future */
+ case AST_ID: eval = file_scope_find_var(scope, cond) != NULL;
+ break;
+ default: semantic_error(scope->fctx, node,
+ "const if conditional at file scope unimplemented");
+ return -1;
+ }
+
+ destroy_ast_tree(cond);
+
+ if (eval) {
+ /* condition evaluated true, so keep the if block */
+ struct ast_node *body = node->_if.body;
+ ast_last_node(body)->next = node->next;
+
+ /* an if must have a body, otherwise the parser messed up */
+ assert(body);
+
+ destroy_ast_tree(node->_if.els);
+
+ *node = *body;
+ free(body);
+ return 0;
+ }
+
+ destroy_ast_tree(node->_if.body);
+
+ struct ast_node *els = node->_if.els;
+ if (els) {
+ *node = *els;
+ ast_last_node(els)->next = next;
+ free(els);
+ return 0;
+ }
+
+ if (next) {
+ /* copy the next node into our place */
+ *node = *next;
+ free(next);
+ return 0;
+ }
+
+ free(node);
+ return 0;
+}
+
+static int analyze_file_visibility(struct scope *scope, struct ast_node *node)
+{
+ assert(scope_flags(scope, SCOPE_FILE));
+
+ if (!node)
+ return 0;
+
+ int ret = 0;
+ node->scope = scope;
+
+ /* TODO: add error checking */
+ switch (node->node_type) {
+ case AST_PROC: {
+ ret |= scope_add_proc(scope, node);
+ break;
+ }
+
+ case AST_VAR: {
+ ret |= scope_add_var(scope, node);
+ break;
+ }
+
+ case AST_IMPORT: {
+ const char *file = node->_import.file;
+ ret |= process_file(&scope, ast_flags(node, AST_FLAG_PUBLIC), file);
+ destroy_ast_tree(node);
+ break;
+ }
+
+ case AST_IF: {
+ assert(ast_flags(node, AST_FLAG_CONST));
+ ret |= eval_const_if(scope, node);
+ if (ret)
+ return -1;
+
+ /* since a const if likely replaced the current node with
+ * something else, we have to analyze the replacement */
+ ret = analyze(scope, node);
+ break;
+ }
+
+ case AST_STRUCT: {
+ /* TODO: should structs be their own 'thing'? */
+ ret |= scope_add_type(scope, node);
+ break;
+ }
+
+ case AST_ENUM: {
+ ret |= scope_add_type(scope, node);
+ break;
+ }
+
+ case AST_ALIAS: {
+ ret |= scope_add_alias(scope, node);
+ break;
+ }
+
+ case AST_TEMPLATE: {
+ ret |= scope_add_type(scope, node);
+ break;
+ }
+
+ case AST_MACRO: {
+ ret |= scope_add_macro(scope, node);
+ break;
+ }
+
+ case AST_EMPTY: {
+ destroy_ast_tree(node);
+ break;
+ }
+
+ case AST_BLOCK: {
+ /* a block might be inserted by something, in which case jump
+ * down into it */
+ assert(ast_flags(node, AST_FLAG_UNHYGIENIC));
+ ret = analyze(scope, node->_block.body);
+ break;
+ }
+
+ default:
+ ret = -1;
+ semantic_error(scope->fctx, node, "unknown top element\n");
+ break;
+ };
+
+ return ret;
+}
+
+static int analyze(struct scope *scope, struct ast_node *tree)
+{
+ struct ast_node *node = tree, *next;
+ while (node) {
+ next = node->next;
+ node->next = NULL;
+ if (analyze_file_visibility(scope, node))
+ return -1;
+
+ node = next;
+ }
+
+ return 0;
+}
+
+/* I would be more happy with a system where proc signatures are actualized
+ * on demand, but the current scope implementation doesn't work that well for it.
+ * Relatively straight forward to implement some kind of actualization
+ * forwarding, the main issue is detecting duplicates. */
+static int analyze_procs(struct scope *scope)
+{
+ struct act_state state = {0};
+ act_set_flags(&state, ACT_ONLY_TYPES);
+ struct visible *procs = scope->procs;
+ if (procs)
+ do {
+ if (procs->owner != scope)
+ goto skip_actualize;
+
+ struct ast_node *proc = procs->node;
+ if (actualize(&state, scope, proc))
+ return -1;
+
+skip_actualize:
+ procs = procs->next;
+ } while (procs);
+
+ /* reinsert procs with actualized signatures, should make sure we don't
+ * have duplicates after all aliases etc. have been eliminated */
+ procs = scope->procs;
+ scope->procs = NULL;
+ if (procs)
+ do {
+ struct visible *next = procs->next;
+ if (procs->owner != scope) {
+ /* this is a reference, ignore it */
+ free(procs);
+ goto skip_add;
+ }
+ if (scope_add_existing_proc(scope, procs)) {
+ destroy_visible(scope, procs);
+ return -1;
+ }
+
+skip_add:
+ procs = next;
+ } while (procs);
+
+ /* repeat for all child scopes */
+ struct scope *child = scope->children;
+ while (child) {
+ if (analyze_procs(child))
+ return -1;
+
+ child = child->next;
+ }
+
+ return 0;
+}
+
+int analyze_root(struct scope *scope, struct ast_node *tree)
+{
+ scope_add_defaults(scope);
+ if (analyze(scope, tree))
+ return -1;
+
+ if (analyze_procs(scope))
+ return -1;
+
+ return 0;
+}
+
+int template_match(struct ast_node *a, struct ast_node *b)
+{
+ struct ast_node *a_act = NULL, *b_act = NULL;
+ if (a->_type.kind == AST_TYPE_TEMPLATE)
+ a_act = a->_type.template.actual;
+ else
+ a_act = a;
+
+
+ if (b->_type.kind == AST_TYPE_TEMPLATE)
+ b_act = b->_type.template.actual;
+ else
+ b_act = b;
+
+ return types_match(a_act, b_act);
+}
+
+int alias_match(struct ast_node *a, struct ast_node *b)
+{
+ struct ast_node *a_act = NULL, *b_act = NULL;
+ if (a->_type.kind == AST_TYPE_ALIAS)
+ a_act = a->_type.alias.actual;
+ else
+ a_act = a;
+
+
+ if (b->_type.kind == AST_TYPE_ALIAS)
+ b_act = b->_type.alias.actual;
+ else
+ b_act = b;
+
+ return types_match(a_act, b_act);
+}
+
+int types_match(struct ast_node *a, struct ast_node *b)
+{
+ if (!a && !b)
+ return 1;
+
+ if (!a)
+ return 0;
+
+ if (!b)
+ return 0;
+
+ assert(a->node_type == AST_TYPE);
+ assert(b->node_type == AST_TYPE);
+
+ /* handle special cases that should match even with different type kinds */
+ if (a->_type.kind == AST_TYPE_TEMPLATE || b->_type.kind == AST_TYPE_TEMPLATE) {
+ if (template_match(a, b))
+ return 1;
+ return 0;
+ }
+
+ if (a->_type.kind == AST_TYPE_ALIAS || b->_type.kind == AST_TYPE_ALIAS) {
+ if (alias_match(a, b))
+ return 1;
+ return 0;
+ }
+
+ if (a->_type.kind != b->_type.kind)
+ return 0;
+
+ /* from here on, we know that both types are identical */
+ if (!identical_ast_nodes(0, a, b))
+ return 0;
+
+ /* TODO: maybe implement some kind of recursion flag? */
+ return 1; // types_match(a->_type.next, b->_type.next);
+}
+
+static int _replace_id(struct ast_node *node, void *data)
+{
+ if (!node)
+ return 0;
+
+ if (node->node_type != AST_ID)
+ return ast_call_on(_replace_id, node, data);
+
+ struct ast_node **pair = data;
+ struct ast_node *id = pair[0];
+ struct ast_node *expr = pair[1];
+
+ if (!identical_ast_nodes(0, node, id))
+ return ast_call_on(_replace_id, node, data);
+
+ struct ast_node *clone = clone_ast_node(expr);
+ if (!clone) {
+ internal_error("failed cloning replacement expr");
+ return -1;
+ }
+
+ clone->next = node->next;
+ clone->scope = node->scope;
+ /* we know this is an ID node, so we know exactly what to free */
+ free((void *)node->_id.id);
+
+ *node = *clone;
+ free(clone);
+
+ /* a succesful replacement needs no futher replacements, I think */
+ return 0;
+}
+static int replace_id(struct ast_node *body, struct ast_node *id, struct ast_node *expr)
+{
+ struct ast_node *pair[2] = {id, expr};
+ return ast_call_on(_replace_id, body, pair);
+}
+
+static int actualize_macro(struct act_state *state,
+ struct scope *scope, struct ast_node *macro)
+{
+ UNUSED(state);
+ /* macro bodies, arguments, etc aren't expanded upon until the macro is
+ * called, so just try to add it to the local scope */
+ assert(macro && macro->node_type == AST_MACRO);
+ return scope_add_macro(scope, macro);
+}
+
+struct ast_node *extract_template(struct ast_node *type)
+{
+ if (!type)
+ return 0;
+
+ assert(type->node_type == AST_TYPE);
+ if (type->_type.kind == AST_TYPE_TEMPLATE)
+ return type;
+
+ return extract_template(type->_type.next);
+}
+
+struct ast_node *extract_base(struct ast_node *type)
+{
+ if (!type)
+ return 0;
+
+ assert(type->node_type == AST_TYPE);
+ if (is_templateable(type))
+ return type;
+
+ return extract_base(type->_type.next);
+}
+
+static void actualize_template_types(struct ast_node *params, struct ast_node *args)
+{
+ /* replace parameter types with argument types */
+ if (args)
+ do {
+ assert(params->type);
+ struct ast_node *template = extract_template(params->type);
+ if (template) {
+ /* at this point we know that the types will
+ * match, otherwise match_proc and friends
+ * fucked up */
+ struct ast_node *type = params->type;
+ struct ast_node *base = args->type;
+ while (type != template) {
+ type = type->_type.next;
+ base = base->_type.next;
+ assert(type);
+ assert(base);
+ }
+
+ template->_type.template.actual = base;
+ }
+ params = params->next;
+ args = args->next;
+ } while (args && params);
+
+ /* TODO: this might not hold in variadic functions... */
+ assert(!args && !params);
+}
+
+static int actualize_proc_call(struct act_state *state,
+ struct scope *scope, struct ast_node *call, struct ast_node *proc)
+{
+ /* proc has already been actualized, so we don't have to do anything */
+ if (ast_flags(proc, AST_FLAG_ACTUAL)) {
+ /* call node type is the return type */
+ struct ast_node *sign = proc->type;
+ assert(sign->node_type == AST_TYPE);
+
+ call->type = sign->_type.sign.ret;
+ return 0;
+ }
+
+ if (ast_flags(proc, AST_FLAG_VARIADIC)) {
+ semantic_error(scope->fctx, proc, "variadic procs not yet implemented");
+ return -1;
+ }
+
+ if (act_flags(state, ACT_ONLY_TYPES)) {
+ /* TODO: better cleanup */
+ /* at this point we're really only interested in the return
+ * type, but if it's a typeof expression we need to actualize
+ * the arguments as well, unfortunately */
+ struct ast_node *sign = clone_ast_node(proc->_proc.sign);
+ if (!sign) {
+ internal_error("failed cloning proc signature");
+ return -1;
+ }
+
+ if (scope_add_scratch(scope, sign)) {
+ internal_error("failed adding signature to scratch");
+ return -1;
+ }
+
+ struct scope *tmp = create_scope();
+ if (!tmp) {
+ internal_error("failed creating temporary signature scope");
+ return -1;
+ }
+
+ /* if the function is in some completely different file for
+ * example, we want to use that file's scope */
+ scope_add_scope(proc->scope, tmp);
+
+ struct ast_node *params = sign->_type.sign.params;
+ struct ast_node *args = call->_call.args;
+ actualize_template_types(params, args);
+
+ if (actualize(state, tmp, sign))
+ return -1;
+
+ call->type = sign->_type.sign.ret;
+ return 0;
+ }
+
+ /* clone procedure definition to
+ * replace template types with actual types and actualize it */
+ struct ast_node *def = clone_ast_node(proc);
+ if (!def) {
+ /* internal error */
+ internal_error("failed allocating actualization");
+ return -1;
+ }
+
+ struct ast_node *sign = def->_proc.sign;
+ struct ast_node *params = sign->_type.sign.params;
+ struct ast_node *args = call->_call.args;
+ actualize_template_types(params, args);
+
+ if (actualize(state, def->scope, def))
+ return -1;
+
+ call->type = sign->_type.sign.ret;
+ return 0;
+}
+
+static int actualize_macro_call(struct act_state *state,
+ struct scope *scope, struct ast_node *call,
+ struct ast_node *macro)
+{
+ assert(call->node_type == AST_CALL && macro->node_type == AST_MACRO);
+ if (ast_flags(macro, AST_FLAG_VARIADIC)) {
+ semantic_error(scope->fctx, macro, "variadic macros not yet implemented");
+ return -1;
+ }
+
+ struct ast_node *body = clone_ast_node(macro->_macro.body);
+ if (!body) {
+ internal_error("failed allocating body for macro call");
+ return -1;
+ }
+
+ body->scope = call->scope;
+ body->next = call->next;
+
+ struct ast_node *param = macro->_macro.params;
+ struct ast_node *arg = call->_call.args;
+
+ /* TODO: actual replacements */
+ while (param && arg) {
+ /* feels slightly hacky, but essentially replace each individual
+ * component in the arg list by breaking it out of the list
+ * temporarily. After the replacement, insert it back into the
+ * list so the cleanup is easier. */
+ struct ast_node *next_arg = arg->next;
+ arg->next = NULL;
+
+ if (replace_id(body, param, arg)) {
+ semantic_error(scope->fctx, call,
+ "failed replacing params with args");
+ destroy_ast_tree(body);
+ arg->next = next_arg;
+ return -1;
+ }
+
+ param = param->next;
+ arg = arg->next = next_arg;
+ }
+
+ /* variadic macros could just replace this bit with with replacing the
+ * rest of the list with the parameter? */
+ if (param || arg) {
+ /* TODO: internal error more like */
+ semantic_error(scope->fctx, call, "uneven number of arguments");
+ destroy_ast_tree(body);
+ return -1;
+ }
+
+ destroy_ast_tree(call->_call.id);
+ destroy_ast_tree(call->_call.args);
+ *call = *body;
+ free(body);
+ /* actualize the new content */
+ return actualize(state, scope, call);
+}
+
+static int actualize_call(struct act_state *state,
+ struct scope *scope, struct ast_node *call)
+{
+ assert(call && call->node_type == AST_CALL);
+
+ /* check that arguments exist, make sure they have types etc. */
+ int ret = actualize(state, scope, call->_call.args);
+ if (ret)
+ return ret;
+
+ /* this label implements a kind of on demand signature actualization,
+ * might work? */
+ /* if it does, I might flesh this out into its own procedure that
+ * reinserts the found proc after actualization, see if maybe that way
+ * we get rid of duplicates? */
+get_callable:
+ struct ast_node *callable = file_scope_resolve_call(scope, call);
+ if (!callable) {
+ char *str = call_str(call);
+ semantic_error(scope->fctx, call, "no such callable: %s", str);
+ free(str);
+ return -1;
+ }
+
+ if (act_flags(state, ACT_ONLY_TYPES)) {
+ if (callable->node_type == AST_PROC
+ && !ast_flags(callable->_proc.sign, AST_FLAG_ACTUAL)) {
+ /* since we're in types only mode, this should be fine */
+ if (actualize(state, scope, callable))
+ return -1;
+
+ goto get_callable;
+ }
+ }
+
+ if (callable->node_type == AST_PROC)
+ return actualize_proc_call(state, scope, call, callable);
+
+ if (callable->node_type == AST_MACRO)
+ return actualize_macro_call(state, scope, call, callable);
+
+ /* TODO: add lambdas and arrays */
+ semantic_error(scope->fctx, call,
+ "currently only procedures and macros are callable");
+ return -1;
+}
+
+static void warn_unused_labels(struct act_state *state, struct scope *scope)
+{
+ struct act_stack *labels = state->label_stack;
+ if (labels)
+ do {
+ struct ast_node *label = labels->node;
+ if (!ast_flags(label, AST_FLAG_ACTUAL))
+ semantic_warn(scope->fctx, label, "unused label");
+
+ } while ((labels = labels->next));
+}
+
+static int undefined_gotos(struct act_state *state, struct scope *scope)
+{
+ int ret = 0;
+ struct act_stack *gotos = state->goto_stack;
+ if (gotos)
+ do {
+ struct ast_node *got = gotos->node;
+ if (!ast_flags(got, AST_FLAG_ACTUAL)) {
+ semantic_warn(scope->fctx, got, "undefined label");
+ ret = -1;
+ }
+
+ } while ((gotos = gotos->next));
+
+ return ret;
+}
+
+/* TODO: add in some kind of subsystem for keeping track of which procedure
+ * we're in, what the last defined var is, possibly more? */
+static int actualize_proc(struct act_state *state,
+ struct scope *scope, struct ast_node *proc)
+{
+ /* actualize_proc is called on template procs as well, but I believe
+ * that's fine? */
+ assert(proc && proc->node_type == AST_PROC);
+ int ret = 0;
+ /* mark the function as initialized, assume previously allocated for us */
+ struct ast_node *actual = proc;
+ ast_set_flags(actual, AST_FLAG_INIT);
+ if (!act_flags(state, ACT_ONLY_TYPES)) {
+ ret = scope_add_actual(scope, actual);
+ if (ret) {
+ destroy_ast_tree(actual);
+ return ret;
+ }
+ }
+
+ struct ast_node *sign = actual->_proc.sign;
+ if (act_flags(state, ACT_ONLY_TYPES)) {
+ struct scope *param_scope = create_scope();
+ scope_add_scope(scope, param_scope);
+
+ /* procedure type is the signature */
+ sign->scope = param_scope;
+ actual->type = sign;
+
+ /* TODO: if we're actualizing the types here, how can we avoid
+ * duplicates in the scope proc list? Or can we at all? */
+
+ /* actualize types in signature */
+ /* note to self: this could likely be made more explicit, but
+ * essentially we only want to actualize the signature when
+ * we're in the analysis phase. After that, the call to the proc
+ * will initialize the types for us, so this step would
+ * overwrite the type info. I think, at least. */
+ return actualize(state, param_scope, sign);
+ }
+
+ /*
+ struct ast_node *params = sign->_type.sign.params;
+ while (params) {
+ if (params->_var.id)
+ if (scope_add_var(param_scope, params))
+ return -1;
+
+ params = params->next;
+ }
+ */
+
+ /* we don't have to maintain the same flags as the parent state, I don't
+ * think */
+ struct act_state new_state = {0};
+ new_state.cur_proc = actual;
+ /* actualize body */
+ ret |= actualize(&new_state, sign->scope, actual->_proc.body);
+ if (!act_flags(&new_state, ACT_HAS_RETURN)) {
+ if (!is_void(sign->_type.sign.ret)) {
+ semantic_error(scope->fctx, actual,
+ "no return with non-void return type");
+ ret = -1;
+ }
+ }
+ else if (ast_block_last(actual->_proc.body)->node_type != AST_RETURN) {
+ /* TODO: something more sophisticated than this */
+ semantic_warn(scope->fctx, actual,
+ "unable to determine explicit return for all branches");
+ }
+
+ warn_unused_labels(&new_state, scope);
+ if (undefined_gotos(&new_state, scope))
+ ret = -1;
+
+ destroy_act_state(&new_state);
+
+ /* TODO: should ret be checked between each stage? */
+ if (ret)
+ return ret;
+
+ printf("Actualized func:\n");
+ dump_ast(0, actual);
+
+ ast_set_flags(actual, AST_FLAG_ACTUAL);
+ /* we have successfully actualized the procedure */
+ return 0;
+}
+
+static int actualize_binop(struct act_state *state,
+ struct scope *scope, struct ast_node *binop)
+{
+ assert(binop && binop->node_type == AST_BINOP);
+
+ struct ast_node *left = binop->_binop.left;
+ struct ast_node *right = binop->_binop.right;
+
+ int ret = 0;
+ ret |= actualize(state, scope, left);
+ ret |= actualize(state, scope, right);
+ if (ret)
+ return ret;
+
+ if (!left->type) {
+ semantic_error(scope->fctx, binop, "unable to detect lefthand type");
+ return -1;
+ }
+
+ if (!right->type) {
+ semantic_error(scope->fctx, binop, "unable to detect righthand type");
+ return -1;
+ }
+
+ /* TODO: slightly unsure how I'll handle aliases, maybe the types should
+ * be actualized to the aliased values as well? */
+ if (!types_match(left->type, right->type)) {
+ char *left_type = type_str(left);
+ char *right_type = type_str(right);
+ semantic_error(scope->fctx, binop,
+ "type mismatch (%s vs %s)", left_type, right_type);
+ free(left_type);
+ free(right_type);
+ return -1;
+ }
+
+ /* TODO: also check template types, just because two templates collapse
+ * to the same actual type doesn't mean that the two template types
+ * should be allowed to operate on eachother */
+
+ /* types are the same, so the type of this expression is whichever */
+ binop->type = left->type;
+
+ return 0;
+}
+
+
+static int actualize_block(struct act_state *state,
+ struct scope *scope, struct ast_node *node)
+{
+ struct scope *block_scope = scope;
+ if (!ast_flags(node, AST_FLAG_UNHYGIENIC)) {
+ if (!(block_scope = create_scope())) {
+ /* internal error */
+ internal_error("failed to allocate block scope");
+ return -1;
+ }
+
+ scope_add_scope(scope, block_scope);
+ }
+
+ struct act_stack *defers = state->defer_stack;
+ if (actualize(state, block_scope, node->_block.body))
+ return -1;
+
+ /* the block type is the last statement in the block's type */
+ node->type = ast_last_node(node->_block.body)->type;
+ if (!node->type) {
+ semantic_error(scope->fctx, node, "unable to detect block type");
+ return -1;
+ }
+
+ /* TODO: currently defers are sort of duplicated after a return, unsure
+ * if they should be handled here or somewhere else */
+ if (state->defer_stack) {
+ node->_block.defers = clone_defers(state, defers);
+ if (!node->_block.defers) {
+ internal_error("failed cloning defers");
+ return -1;
+ }
+
+ clear_defers(state, defers);
+ }
+
+ return 0;
+}
+
+static int actualize_id(struct act_state *state,
+ struct scope *scope, struct ast_node *id)
+{
+ UNUSED(state);
+ assert(id && id->node_type == AST_ID);
+ struct ast_node *decl = file_scope_find(scope, id);
+ if (!decl) {
+ semantic_error(scope->fctx, id, "no such object");
+ return -1;
+ }
+
+ if (!decl->type) {
+ semantic_error(scope->fctx, id, "no type associated with object");
+ return -1;
+ }
+
+ id->type = decl->type;
+ return 0;
+}
+
+static int actualize_var(struct act_state *state,
+ struct scope *scope, struct ast_node *var)
+{
+ assert(var && var->node_type == AST_VAR);
+ struct ast_node *init = var->_var.init;
+ struct ast_node *type = var->_var.type;
+ /* one of these must be defined, otherwise the parser fucked up */
+ assert(type || init);
+
+ int ret = 0;
+ if (init)
+ ret |= actualize(state, scope, init);
+
+ if (type)
+ ret |= actualize(state, scope, type);
+
+
+ if (init && type) {
+ /* make sure the asked type and the actualized types match */
+ if (!types_match(init->type, type->type)) {
+ char *init_type = type_str(init);
+ char *req_type = type_str(type);
+ semantic_error(scope->fctx, var, "type mismatch (%s vs %s)",
+ req_type, init_type);
+ free(init_type);
+ free(req_type);
+ return -1;
+ }
+ }
+
+ if (init)
+ /* infer */
+ var->type = init->type;
+
+ if (type)
+ /* TODO: should there be some default value? */
+ /* declare */
+ var->type = type;
+
+ /* an unnamed var is a var in a signature that should not produce a
+ * warning on not being used (if I ever get around to adding those kinds
+ * of warnings) */
+ if (var->_var.id)
+ return scope_add_var(scope, var);
+
+ return 0;
+}
+
+#define ENTER_ACT()\
+enum act_flags old_flags = state->flags;\
+struct ast_node *old_template = state->cur_template;
+
+#define EXIT_ACT(r)\
+do {\
+ state->cur_template = old_template;\
+ state->flags = old_flags;\
+ return r;\
+} while (0);
+
+static int actualize_type(struct act_state *state,
+ struct scope *scope, struct ast_node *type)
+{
+ /* TODO: there's gotta be a better way to handle flags. Maybe macros
+ * like ENTER_ACTUALIZE and EXIT_ACTUALIZE? */
+ /* TODO: get rid of aliases, expand implements, fill out structures,
+ * etc. */
+ assert(type->node_type == AST_TYPE);
+ ENTER_ACT();
+ act_set_flags(state, ACT_ONLY_TYPES);
+
+ if (ast_flags(type, AST_FLAG_INIT) && !ast_flags(type, AST_FLAG_ACTUAL)) {
+ semantic_error(scope->fctx, type, "detected type loop");
+ EXIT_ACT(-1);
+ }
+
+ /* mark type initialize. We can detect type loops if this is set but
+ * AST_FLAG_ACTUAL isn't. */
+ ast_set_flags(type, AST_FLAG_INIT);
+ if (actualize(state, scope, type->_type.next)) {
+ EXIT_ACT(-1);
+ }
+
+ switch (type->_type.kind) {
+ case AST_TYPE_ID: {
+ /* type IDs can really only be aliases to something else, or if
+ * they're missing, void */
+ struct ast_node *id = type->_type.id;
+ if (!id)
+ /* no ID means void */
+ type->_type.id = gen_id(strdup("void"));
+
+ type->loc = id->loc;
+
+ struct ast_node *exists = file_scope_resolve_type(scope, type);
+ if (!exists) {
+ semantic_error(scope->fctx, type, "no such type");
+ EXIT_ACT(-1);
+ }
+
+ /* nothing to do, except maybe check that types are actually
+ * identical? */
+ if (exists->node_type == AST_TYPE)
+ break;
+
+ assert(exists->node_type == AST_ALIAS
+ || exists->node_type == AST_TEMPLATE
+ || exists->node_type == AST_STRUCT
+ || exists->node_type == AST_ENUM);
+ /* actualize whatever type we have on demand, either alias or
+ * template */
+ if (!ast_flags(exists, AST_FLAG_ACTUAL))
+ if (actualize(state, scope, exists))
+ EXIT_ACT(-1);
+
+ if (exists->node_type == AST_ALIAS) {
+ /* TODO: check if this is good enough */
+ destroy_ast_node(type->_type.id);
+ assert(type->_type.next == NULL);
+ type->_type.kind = AST_TYPE_ALIAS;
+ type->_type.alias.alias = exists;
+ type->_type.alias.actual = exists->_alias.type;
+ }
+ else if (exists->node_type == AST_TEMPLATE) {
+ destroy_ast_node(type->_type.id);
+ assert(type->_type.next == NULL);
+ type->_type.kind = AST_TYPE_TEMPLATE;
+ type->_type.template.template = exists;
+ /* this should be populated later */
+ type->_type.template.actual = NULL;
+ }
+ else if (exists->node_type == AST_STRUCT) {
+ /* I think, will still have to TODO: check */
+ destroy_ast_node(type->_type.id);
+ assert(type->_type.next == NULL);
+ type->_type.kind = AST_TYPE_STRUCT;
+ type->_type.struc.struc = exists;
+ type->_type.struc.impls
+ = clone_ast_node(exists->_struct.generics);
+ }
+ break;
+ }
+
+ case AST_TYPE_ARR:
+ /* TODO: expression should be expandable to integer constant */
+ break;
+
+ case AST_TYPE_TYPEOF: {
+ struct ast_node *expr = type->_type.typeo.expr;
+ /* TODO: expressions in top-level type declarations should
+ * probably be checked for, as we might not want to accidentally
+ * actualize procedure calls? */
+ if (actualize(state, scope, expr))
+ EXIT_ACT(-1);
+
+ /* I suspect there are more ways than this that we can fail, but
+ * this is a decent starting point */
+ if (expr->type->_type.kind == AST_TYPE_TYPEOF) {
+ semantic_error(scope->fctx, type,
+ "couldn't actualize type expression\n");
+ EXIT_ACT(-1);
+ }
+
+ assert(type->_type.next == NULL);
+ /* clone node to make sure we don't accidentally step on
+ * anyone's toes and double free anything */
+ struct ast_node *clone = clone_ast_node(expr->type);
+ *type = *clone;
+ free(clone);
+ destroy_ast_node(expr);
+ break;
+ }
+
+ case AST_TYPE_POINTER:
+ assert(type->_type.next);
+ type->loc = type->_type.next->loc;
+ break;
+
+ case AST_TYPE_SIGN: {
+ struct ast_node *params = type->_type.sign.params;
+ struct ast_node *ret = type->_type.sign.ret;
+
+ if (actualize(state, scope, params))
+ EXIT_ACT(-1);
+
+ if (!ret)
+ ret = type->_type.sign.ret = void_type();
+
+ if (actualize(state, scope, ret))
+ EXIT_ACT(-1);
+
+ break;
+ }
+
+ }
+
+ ast_set_flags(type, AST_FLAG_ACTUAL);
+
+ if (type->_type.kind == AST_TYPE_TYPEOF) {
+ semantic_error(scope->fctx, type, "couldn't convert expression to type");
+ EXIT_ACT(-1);
+ }
+
+ /* generally speaking */
+ EXIT_ACT(0);
+}
+
+static int actualize_empty(struct act_state *state,
+ struct scope *scope, struct ast_node *node)
+{
+ UNUSED(state);
+ /* TODO: converting to void is common enough that it might be worth
+ * creating a function for */
+ struct ast_node *void_id = gen_id(strdup("void"));
+ if (!void_id) {
+ internal_error("couldn't allocate type id for empty statement\n");
+ return -1;
+ }
+
+ node->type = gen_type(AST_TYPE_ID, void_id, NULL, NULL);
+ if (!node->type) {
+ scope_add_scratch(scope, void_id);
+ internal_error("couldn't allocate type for empty statement\n");
+ return -1;
+ }
+
+ scope_add_scratch(scope, node->type);
+ return 0;
+}
+
+static int integral_type(struct ast_node *type)
+{
+ assert(type->node_type == AST_TYPE);
+ if (type->_type.kind == AST_TYPE_ALIAS)
+ return integral_type(type->_type.alias.actual);
+
+ if (type->_type.kind != AST_TYPE_ID)
+ return 0;
+
+ /* here would be awesome with an enum of our base types */
+ const char *type_str = type->_type.id->_id.id;
+ if (strcmp(type_str, "u8"))
+ return 1;
+
+ if (strcmp(type_str, "u16"))
+ return 1;
+
+ if (strcmp(type_str, "u32"))
+ return 1;
+
+ if (strcmp(type_str, "u64"))
+ return 1;
+
+ if (strcmp(type_str, "i8"))
+ return 1;
+
+ if (strcmp(type_str, "i16"))
+ return 1;
+
+ if (strcmp(type_str, "i32"))
+ return 1;
+
+ if (strcmp(type_str, "i64"))
+ return 1;
+
+ if (strcmp(type_str, "usize"))
+ return 1;
+
+ if (strcmp(type_str, "isize"))
+ return 1;
+
+ if (strcmp(type_str, "f32"))
+ return 1;
+
+ if (strcmp(type_str, "f64"))
+ return 1;
+
+ return 0;
+}
+
+static int pointer_type(struct ast_node *type)
+{
+ assert(type->node_type == AST_TYPE);
+ return type->_type.kind == AST_TYPE_POINTER;
+}
+
+static int pointer_conversion(struct ast_node *a, struct ast_node *b)
+{
+ assert(a->node_type == AST_TYPE);
+ assert(b->node_type == AST_TYPE);
+ if (pointer_type(a)) {
+ if (b->_type.kind != AST_TYPE_ID)
+ return 0;
+
+ struct ast_node *id = b->_type.id;
+ if (strcmp(id->_id.id, "usize") == 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+static int actualize_cast(struct act_state *state,
+ struct scope *scope, struct ast_node *cast)
+{
+ assert(cast->node_type == AST_CAST);
+
+ struct ast_node *expr = cast->_cast.expr;
+ if (actualize(state, scope, expr))
+ return -1;
+
+ struct ast_node *type = cast->_cast.type;
+ if (actualize(state, scope, type))
+ return -1;
+
+ if (types_match(expr->type, type)) {
+ cast->type = type;
+ return 0;
+ }
+
+ if (integral_type(expr->type) && integral_type(type)) {
+ cast->type = type;
+ return 0;
+ }
+
+ if (pointer_type(expr->type) && pointer_type(type)) {
+ cast->type = type;
+ return 0;
+ }
+
+ if (pointer_conversion(expr->type, type)
+ || pointer_conversion(type, expr->type)) {
+ cast->type = type;
+ return 0;
+ }
+
+ /* TODO: arrays? lambdas? */
+
+ char *left_type = type_str(expr);
+ char *right_type = type_str(type);
+ semantic_error(scope->fctx, cast, "illegal cast (%s vs %s)",
+ left_type, right_type);
+ free(left_type);
+ free(right_type);
+ return -1;
+}
+
+static int actualize_const(struct act_state *state, struct scope *scope,
+ struct ast_node *cons)
+{
+ UNUSED(state);
+ assert(cons->node_type == AST_CONST);
+ if (cons->_const.kind == AST_CONST_INTEGER) {
+ /* error checking would be doog */
+ cons->type = gen_type(AST_TYPE_ID, gen_id(strdup("i64")),
+ NULL, NULL);
+ scope_add_scratch(scope, cons->type);
+ return 0;
+ }
+
+ semantic_error(scope->fctx, cons, "unimplemented constant");
+ return 0;
+}
+
+static int actualize_alias(struct act_state *state, struct scope *scope,
+ struct ast_node *alias)
+{
+ /* I shall have to think about things, as currently very deeply nested
+ * aliases might be a bit cumbersome to work with. Still, this works
+ * well enough I suppose. */
+ assert(alias->node_type == AST_ALIAS);
+ ast_set_flags(alias, AST_FLAG_INIT);
+ /* TODO: alias loops? */
+ if (actualize(state, scope, alias->_alias.type)) {
+ /* usually we don't want to output errors upon errors, but this
+ * is likely a useful message as it might show where a loop is
+ * occuring */
+ semantic_error(scope->fctx, alias, "failed actualizing alias");
+ return -1;
+ }
+
+ ast_set_flags(alias, AST_FLAG_ACTUAL);
+ return 0;
+}
+
+static int actualize_template(struct act_state *state, struct scope *scope,
+ struct ast_node *template)
+{
+ assert(template->node_type == AST_TEMPLATE);
+ ast_set_flags(template, AST_FLAG_ACTUAL);
+ ENTER_ACT();
+ state->cur_template = template;
+
+ /* will still have to figure out how I want to inform the actualizer
+ * that a some_type in a some_type just means whichever type we're
+ * testing for */
+ act_set_flags(state, ACT_ONLY_TYPES);
+ if (actualize(state, template->scope, template->_template.body)) {
+ semantic_error(scope->fctx, template, "failed actualizing template");
+ EXIT_ACT(-1);
+ }
+
+ /* TODO: check that all procs in the template have something to do with
+ * the type, either as an argument or as a return type or something */
+
+ /* TODO: implement supertemplates, i.e. adding some previous template to
+ * this template */
+
+ /* TODO: make sure that the template itself doesn't have duplicates */
+
+ template->type = template;
+ EXIT_ACT(0);
+}
+
+static int actualize_defer(struct act_state *state,
+ struct scope *scope, struct ast_node *node)
+{
+ struct ast_node *expr = node->_defer.expr;
+ /* TODO: should the actualization only happen when the defers are
+ * called? */
+ if (actualize(state, scope, expr))
+ return -1;
+
+ if (push_defer(state, expr))
+ return -1;
+
+ node->type = void_type();
+ scope_add_scratch(scope, node->type);
+ return 0;
+}
+
+static int actualize_return(struct act_state *state, struct scope *scope,
+ struct ast_node *node)
+{
+ act_set_flags(state, ACT_HAS_RETURN);
+ struct ast_node *expr = node->_return.expr;
+ if (expr) {
+ if (actualize(state, scope, expr))
+ return -1;
+
+ node->type = expr->type;
+ }
+ else {
+ node->type = void_type();
+ if (scope_add_scratch(scope, node->type))
+ return -1;
+ }
+
+ assert(state->cur_proc);
+ struct ast_node *cur_proc = state->cur_proc;
+ struct ast_node *ret = cur_proc->_proc.sign->_type.sign.ret;
+ if (!types_match(node->type, ret)) {
+ char *rt = type_str(ret);
+ char *et = type_str(node);
+ semantic_error(scope->fctx, node, "return type mismatch: %s vs %s",
+ rt, et);
+ free(rt);
+ free(et);
+ return -1;
+ }
+
+ if (state->defer_stack) {
+ node->_return.defers = clone_defers(state, NULL);
+ if (!node->_return.defers) {
+ internal_error("failed cloning return defers");
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/* Still slightly unsure if this works in all cases, but a good start
+ * nonetheless. */
+static void actualize_goto_defer(struct ast_node *got, struct ast_node *label)
+{
+ struct ast_node *goto_defers = got->_goto.defers;
+ struct ast_node *label_defers = label->_label.defers;
+ /* since we're dealing with singly linked lists, keep a reference to one
+ * node before the current goto defer. */
+ struct ast_node *prev_defer = NULL;
+
+ /* this goto has a defined label */
+ ast_set_flags(got, AST_FLAG_ACTUAL);
+ ast_set_flags(label, AST_FLAG_ACTUAL);
+
+ size_t goto_len = ast_list_len(goto_defers);
+ size_t label_len = ast_list_len(label_defers);
+
+ /* find first common defer statement */
+ while (goto_len > label_len) {
+ prev_defer = goto_defers;
+ goto_defers = goto_defers->next;
+ goto_len--;
+ }
+
+ while (label_len > goto_len) {
+ label_defers = label_defers->next;
+ label_len--;
+ }
+
+ /* TODO: possible optimisation could be maintaining references to the
+ * defer statements themselves, building up a sort of tree, allowing us
+ * to directly compare pointers, likely being a bit quicker. Still,
+ * unlikely that goto stuff would be a major bottleneck. */
+ while (!identical_ast_nodes(1, goto_defers, label_defers)) {
+ prev_defer = goto_defers;
+ label_defers = label_defers->next;
+ goto_defers = goto_defers->next;
+ }
+
+ /* only the defers below the common defer should be executed by the goto */
+ if (goto_defers) {
+ /* fuck, I actually need the one previous to this */
+ assert(prev_defer->next == goto_defers);
+ prev_defer->next = NULL;
+ destroy_ast_node(goto_defers);
+ }
+
+ /* nothing to do */
+}
+
+
+static int actualize_goto(struct act_state *state, struct scope *scope,
+ struct ast_node *node)
+{
+ assert(node->node_type == AST_GOTO);
+ push_goto(state, node);
+
+ /* clone all defers as we don't know where the label might be */
+ node->_goto.defers = clone_defers(state, NULL);
+ node->type = void_type();
+ scope_add_scratch(scope, node->type);
+
+ struct ast_node *label = find_label(state, node->_goto.label);
+ /* this is a jump backwards, i.e. we can already do it */
+ if (label)
+ actualize_goto_defer(node, label);
+
+ /* forward jump, handle stuff when we run into the corresponding label */
+ return 0;
+}
+
+static void actualize_goto_defers(struct act_state *state, struct ast_node *label)
+{
+ struct act_stack *prev = state->goto_stack, *cur;
+ if (prev)
+ do {
+ cur = prev->next;
+ struct ast_node *got = prev->node;
+ if (identical_ast_nodes(0, got->_goto.label, label))
+ actualize_goto_defer(got, label);
+
+ } while ((prev = cur));
+}
+
+static int actualize_label(struct act_state *state, struct scope *scope,
+ struct ast_node *node)
+{
+ assert(node->node_type == AST_LABEL);
+ struct ast_node *prev = find_label(state, node);
+ if (prev) {
+ semantic_error(scope->fctx, node, "label redefined");
+ semantic_info(scope->fctx, prev, "previous definition");
+ return -1;
+ }
+
+ push_label(state, node);
+ /* clone all defers */
+ node->_label.defers = clone_defers(state, NULL);
+ node->type = void_type();
+ scope_add_scratch(scope, node->type);
+
+ actualize_goto_defers(state, node);
+ return 0;
+}
+
+static int actualize_unop(struct act_state *state,
+ struct scope *scope, struct ast_node *node)
+{
+ assert(node->node_type == AST_UNOP);
+ struct ast_node *expr = node->_unop.expr;
+ if (actualize(state, scope, expr))
+ return -1;
+
+ /* generally speaking */
+ node->type = expr->type;
+
+ switch (node->_unop.op) {
+ case AST_DEREF: {
+ struct ast_node *type = expr->type;
+ if (type->_type.kind != AST_TYPE_POINTER) {
+ /* TODO: or array */
+ semantic_error(scope->fctx, expr,
+ "trying to dereference something that's not a pointer");
+ return -1;
+ }
+
+ node->type = type->_type.next;
+ assert(node->type);
+ break;
+ }
+
+ default: semantic_error(scope->fctx, node, "unimplemented unary operation");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int actualize_as(struct act_state *state,
+ struct scope *scope, struct ast_node *as)
+{
+ assert(as->node_type == AST_AS);
+ if (!act_flags(state, ACT_ONLY_TYPES)) {
+ semantic_error(scope->fctx, as, "as used outside of type context");
+ return -1;
+ }
+
+ struct ast_node *type = as->_as.type;
+ if (actualize(state, scope, type))
+ return -1;
+
+ as->type = type;
+ return 0;
+}
+
+static int actualize_struct(struct act_state *state,
+ struct scope *scope, struct ast_node *node)
+{
+ assert(node->node_type == AST_STRUCT);
+ struct ast_node *generics = node->_struct.generics;
+ /* TODO: some IDs should be handles as just placeholders, I think? */
+ if (actualize(state, scope, generics))
+ return -1;
+
+ struct ast_node *body = node->_struct.body;
+ if (actualize(state, scope, body))
+ return -1;
+
+ node->type = node;
+ return 0;
+}
+
+/* could maybe be renamed, but essentially dot in copper works as either
+ * -> or . in C, so allow structures or templates and single lever pointers to
+ * structures or templates. */
+static int has_members(struct ast_node *type)
+{
+ if (type->_type.kind == AST_TYPE_ALIAS)
+ return has_members(type->_type.alias.actual);
+
+ if (type->_type.kind == AST_TYPE_POINTER)
+ type = type->_type.next;
+
+ /* most likely */
+ if (type->_type.kind == AST_TYPE_STRUCT)
+ return 1;
+
+ if (type->_type.kind == AST_TYPE_TEMPLATE)
+ return 1;
+
+ return 0;
+}
+
+static int actualize_dot(struct act_state *state,
+ struct scope *scope, struct ast_node *node)
+{
+ assert(node->node_type == AST_DOT);
+ struct ast_node *expr = node->_dot.expr;
+ if (actualize(state, scope, expr))
+ return -1;
+
+ struct ast_node *id = node->_dot.id;
+ if (!has_members(expr->type)) {
+ char *tstr = type_str(expr->type);
+ semantic_error(scope->fctx, node, "%s does not have member %s",
+ tstr, id->_id.id);
+ free(tstr);
+ return -1;
+ }
+
+ /* TODO: actually look through stuff */
+ /* TODO: figure out how to match templated structures to actual */
+ return 0;
+}
+
+static int actualize(struct act_state *state, struct scope *scope, struct ast_node *node)
+{
+ int ret = 0;
+ if (!node)
+ return ret;
+
+ if (!node->scope)
+ node->scope = scope;
+
+ switch (node->node_type) {
+ case AST_PROC:
+ /* procedure definitions only allowed at file scope */
+ assert(scope_flags(scope, SCOPE_FILE));
+ ret |= actualize_proc(state, scope, node);
+ break;
+
+ case AST_TEMPLATE: ret |= actualize_template(state, scope, node); break;
+ case AST_ALIAS: ret |= actualize_alias(state, scope, node); break;
+ case AST_MACRO: ret |= actualize_macro(state, scope, node); break;
+ case AST_CALL: ret |= actualize_call(state, scope, node); break;
+ case AST_BINOP: ret |= actualize_binop(state, scope, node); break;
+ case AST_BLOCK: ret |= actualize_block(state, scope, node); break;
+ case AST_ID: ret |= actualize_id(state, scope, node); break;
+ case AST_VAR: ret |= actualize_var(state, scope, node); break;
+ case AST_TYPE: ret |= actualize_type(state, scope, node); break;
+ case AST_EMPTY: ret |= actualize_empty(state, scope, node); break;
+ case AST_CAST: ret |= actualize_cast(state, scope, node); break;
+ case AST_CONST: ret |= actualize_const(state, scope, node); break;
+ case AST_DEFER: ret |= actualize_defer(state, scope, node); break;
+ case AST_RETURN: ret |= actualize_return(state, scope, node); break;
+ case AST_GOTO: ret |= actualize_goto(state, scope, node); break;
+ case AST_LABEL: ret |= actualize_label(state, scope, node); break;
+ case AST_UNOP: ret |= actualize_unop(state, scope, node); break;
+ case AST_AS: ret |= actualize_as(state, scope, node); break;
+ case AST_STRUCT: ret |= actualize_struct(state, scope, node); break;
+ case AST_DOT: ret |= actualize_dot(state, scope, node); break;
+
+ default:
+ /* more like internal_error, maybe? */
+ semantic_error(scope->fctx, node, "unimplemented actualization");
+ break;
+ }
+
+ /* should it automatically handle next nodes or should it be saved to
+ * actualize_block or something? */
+ ret |= actualize(state, scope, node->next);
+ return ret;
+}
+
+int actualize_main(struct scope *root)
+{
+ /* should maybe try and figure out a shorthand for this */
+ /* especially if I try to find procs by signature as well */
+ struct ast_node main_id = {0};
+ main_id.node_type = AST_ID;
+ main_id._id.id = "main";
+
+ struct act_state state = {0};
+
+ /* skip checking signature for now */
+ struct ast_node *main = file_scope_find_override(root, &main_id);
+ if (!main) {
+ /* libraries are not really compilable... */
+ error("no main");
+ return -1;
+ }
+ int ret = actualize(&state, root, clone_ast_node(main));
+ destroy_act_state(&state);
+ return ret;
+}
+
+void replace_type(struct ast_node *type, struct ast_node *from, struct ast_node *to)
+{
+ if (!type)
+ return;
+
+ assert(type->node_type == AST_TYPE);
+ assert(from->node_type == AST_TYPE);
+ assert(to->node_type == AST_TYPE);
+
+ /* TODO: cleanup? */
+ if (types_match(type, from)) {
+ assert(type->_type.next == NULL);
+ struct ast_node *clone = clone_ast_node(to);
+ *type = *clone;
+ free(clone);
+ }
+
+ replace_type(type->_type.next, from, to);
+}
+
+void replace_param_types(struct ast_node *param, struct ast_node *param_type,
+ struct ast_node *arg_type)
+{
+ while (param) {
+ replace_type(param->type, param_type, arg_type);
+ param = param->next;
+ }
+}