From 98c3d8fbc924c62e2be571ed71b22053b9e8baa3 Mon Sep 17 00:00:00 2001
From: Kimplul <kimi.h.kuparinen@gmail.com>
Date: Fri, 20 Dec 2024 14:52:34 +0200
Subject: add enough type checking to compile uniq.fwd

---
 src/analyze.c | 298 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 297 insertions(+), 1 deletion(-)

(limited to 'src/analyze.c')

diff --git a/src/analyze.c b/src/analyze.c
index 6efd60f..900e771 100644
--- a/src/analyze.c
+++ b/src/analyze.c
@@ -4,11 +4,307 @@
 #include <fwd/analyze.h>
 #include <assert.h>
 
+struct state {
+};
+
+static int analyze(struct state *state, struct scope *scope, struct ast *node);
+static int analyze_list(struct state *state, struct scope *scope, struct ast *nodes);
+
+static int analyze_type(struct state *state, struct scope *scope, struct type *type);
+static int analyze_type_list(struct state *state, struct scope *scope, struct type *types);
+
+static int analyze_proc(struct state *state, struct scope *scope, struct ast *node)
+{
+	(void)state;
+	(void)scope;
+
+	struct scope *proc_scope = create_scope();
+	if (!proc_scope) {
+		internal_error("failed allocating proc scope");
+		return -1;
+	}
+
+	scope_add_scope(scope, proc_scope);
+
+	struct state proc_state = {};
+	if (analyze_list(&proc_state, proc_scope, proc_params(node)))
+		return -1;
+
+	struct type *callable = tgen_callable(NULL, node->loc);
+	if (!callable) {
+		internal_error("failed allocating proc type");
+		return -1;
+	}
+
+	foreach_node(param, proc_params(node)) {
+		type_append(&callable->t0, clone_type(param->t));
+	}
+
+	node->t = callable;
+
+	return analyze(&proc_state, proc_scope, proc_body(node));
+}
+
+static int analyze_unop(struct state *state, struct scope *scope, struct ast *node)
+{
+	assert(false);
+	return 0;
+}
+
+static int analyze_block(struct state *state, struct scope *scope, struct ast *node)
+{
+	struct scope *block_scope = create_scope();
+	if (!block_scope) {
+		internal_error("failed to allocate block scope");
+		return -1;
+	}
+
+	scope_add_scope(scope, block_scope);
+	if (analyze_list(state, block_scope, block_body(node)))
+		return -1;
+
+	node->t = ast_last(block_body(node))->t;
+	return 0;
+}
+
+static int analyze_var(struct state *state, struct scope *scope, struct ast *node)
+{
+	if (analyze_type(state, scope, var_type(node)))
+		return -1;
+
+	node->t = var_type(node);
+	return scope_add_var(scope, node);
+}
+
+static int analyze_let(struct state *state, struct scope *scope, struct ast *node)
+{
+	if (analyze(state, scope, let_var(node)))
+		return -1;
+
+	if (analyze(state, scope, let_expr(node)))
+		return -1;
+
+	struct type *l = (let_var(node))->t;
+	struct type *r = (let_expr(node))->t;
+	if (!types_match(l, r)) {
+		type_mismatch(scope, node, l, r);
+		return -1;
+	}
+
+	node->t = l;
+	return 0;
+}
+
+static int analyze_init(struct state *state, struct scope *scope, struct ast *node)
+{
+	if (analyze_type_list(state, scope, init_args(node)))
+		return -1;
+
+	if (analyze(state, scope, init_body(node)))
+		return -1;
+
+	/** @todo check that all parameters match, though that requires that the
+	 * type is defined in fwd and not in C++, so this might have to wait a
+	 * bit */
+	node->t = tgen_construct(init_id(node), init_args(node), node->loc);
+	if (!node->t) {
+		internal_error("failed allocating type for construct");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int analyze_call(struct state *state, struct scope *scope, struct ast *node)
+{
+	struct ast *expr = call_expr(node);
+	if (analyze(state, scope, expr))
+		return -1;
+
+	if (!is_callable(expr->t)) {
+		semantic_error(scope, node, "expected callable expression");
+		return -1;
+	}
+
+	struct type *types = callable_types(expr->t);
+	struct ast *args = call_args(node);
+	if (analyze_list(state, scope, args))
+		return -1;
+
+	size_t expected = type_list_len(types);
+	size_t got = ast_list_len(args);
+	if (expected != got) {
+		semantic_error(scope, node, "expected %d params, got %d", expected, got);
+		return -1;
+	}
+
+	struct type *type = types;
+	foreach_node(arg, args) {
+		if (!types_match(type, arg->t)) {
+			type_mismatch(scope, node, type, arg->t);
+			return -1;
+		}
+
+		type = type->n;
+	}
+
+	node->t = tgen_void(node->loc);
+	if (!node->t) {
+		internal_error("failed allocating void type for call");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int analyze_id(struct state *state, struct scope *scope, struct ast *node)
+{
+	struct ast *found = file_scope_find_symbol(scope, id_str(node));
+	if (!found) {
+		semantic_error(scope, node, "no symbol named \"%s\"", id_str(node));
+		return -1;
+	}
+
+	/* kind of hacky, functions are given their type before they've been
+	 * analyzed fully, this enables recursion */
+	if (!found->t && !ast_flags(found, AST_FLAG_ANALYZED)) {
+		assert(found->k == AST_PROC_DEF);
+		/* a proc def will use its own state and scope, but pass these
+		 * on in case the analysis wants to print errors or something
+		 */
+		if (analyze(state, scope, found))
+			return -1;
+	}
+
+	node->t = found->t;
+	return 0;
+}
+
+static int analyze_closure(struct state *state, struct scope *scope, struct ast *node)
+{
+	struct scope *closure_scope = create_scope();
+	if (!closure_scope) {
+		internal_error("failed allocating closure scope");
+		return -1;
+	}
+
+	scope_add_scope(scope, closure_scope);
+
+	if (analyze_list(state, closure_scope, closure_bindings(node)))
+		return -1;
+
+	if (analyze(state, closure_scope, closure_body(node)))
+		return -1;
+
+	struct type *callable = tgen_callable(NULL, node->loc);
+	if (!callable) {
+		internal_error("failed allocating closure type");
+		return -1;
+	}
+
+	/** @todo use analysis to figure out if this closure can be called
+	 * multiple times or just once */
+
+	foreach_node(binding, closure_bindings(node)) {
+		type_append(&callable->t0, clone_type(binding->t));
+	}
+
+	node->t = callable;
+	return 0;
+}
+
+static int analyze(struct state *state, struct scope *scope, struct ast *node)
+{
+	if (!node)
+		return 0;
+
+	if (ast_flags(node, AST_FLAG_ANALYZED)) {
+		assert(node->t);
+		return 0;
+	}
+
+	if (ast_flags(node, AST_FLAG_PREANALYZIS)) {
+		semantic_error(scope, node, "semantic loop detected");
+		return -1;
+	}
+
+	ast_set_flags(node, AST_FLAG_PREANALYZIS);
+
+	if (!node->scope)
+		node->scope = scope;
+
+	int ret = 0;
+	if (is_unop(node)) {
+		ret = analyze_unop(state, scope, node);
+		goto out;
+	}
+
+	switch (node->k) {
+	case AST_PROC_DEF:	ret = analyze_proc	(state, scope, node); break;
+	case AST_VAR_DEF:	ret = analyze_var	(state, scope, node); break;
+	case AST_BLOCK:		ret = analyze_block	(state, scope, node); break;
+	case AST_LET:		ret = analyze_let	(state, scope, node); break;
+	case AST_INIT:		ret = analyze_init	(state, scope, node); break;
+	case AST_CALL:		ret = analyze_call	(state, scope, node); break;
+	case AST_ID:		ret = analyze_id	(state, scope, node); break;
+	case AST_CLOSURE:	ret = analyze_closure	(state, scope, node); break;
+	default:
+		   internal_error("missing ast analysis");
+		   return -1;
+	}
+
+out:
+	if (ret)
+		return ret;
+
+	assert(node->t);
+	assert(node->scope);
+	ast_set_flags(node, AST_FLAG_ANALYZED);
+	return 0;
+}
+
+static int analyze_list(struct state *state, struct scope *scope, struct ast *nodes)
+{
+	foreach_node(node, nodes) {
+		if (analyze(state, scope, node))
+			return -1;
+	}
+
+	return 0;
+}
+
+static int analyze_type(struct state *state, struct scope *scope, struct type *type)
+{
+	/* for now, let's just say all types are fine as they are, specified by
+	 * the user. */
+	(void)state;
+	(void)scope;
+	(void)type;
+	return 0;
+}
+
+static int analyze_type_list(struct state *state, struct scope *scope, struct type *types)
+{
+	foreach_type(type, types) {
+		if (analyze_type(state, scope, type))
+			return -1;
+	}
+
+	return 0;
+}
+
 int analyze_root(struct scope *scope, struct ast *root)
 {
 	foreach_node(node, root) {
 		assert(node->k == AST_PROC_DEF);
-		scope_add_proc(scope, node);
+		if (scope_add_proc(scope, node))
+			return -1;
+	}
+
+	foreach_node(node, root) {
+		struct state state = {};
+		if (analyze(&state, scope, node))
+			return -1;
 	}
 
 	return 0;
-- 
cgit v1.2.3