about summary refs log tree commit diff
path: root/boot/lower.c
diff options
context:
space:
mode:
authorMel <mel@rnrd.eu>2026-05-03 23:51:27 +0200
committerMel <mel@rnrd.eu>2026-05-03 23:51:27 +0200
commita348f07b65a60226da53215d3767caf7688cb8b8 (patch)
tree4d20df2d264989535318bad10d5cacec7dddd3d9 /boot/lower.c
parent13f5c18441b34427722e4869cd8362b6c38075de (diff)
downloadcatskill-a348f07b65a60226da53215d3767caf7688cb8b8.tar.zst
catskill-a348f07b65a60226da53215d3767caf7688cb8b8.zip
IR lowering pass 1, function declarations
Signed-off-by: Mel <mel@rnrd.eu>
Diffstat (limited to 'boot/lower.c')
-rw-r--r--boot/lower.c186
1 files changed, 184 insertions, 2 deletions
diff --git a/boot/lower.c b/boot/lower.c
index 1fd1a7e..e8bf8d0 100644
--- a/boot/lower.c
+++ b/boot/lower.c
@@ -26,6 +26,48 @@
 
 #include "catboot.h"
 
+void
+lower_emit_error(struct Unit* unit, struct Span span, struct String message)
+{
+    struct Diagnostic d = {
+        .severity = DIAGNOSTIC_ERROR,
+        .span = span,
+        .message = message,
+    };
+    array_push(&unit->diagnostics, &d);
+    unit->had_error = true;
+}
+
+void
+lower_emit_error_c(struct Unit* unit, struct Span span, const ascii* message)
+{
+    lower_emit_error(unit, span, string_from_c_string(message));
+}
+
+bool
+lower_type_lookup_by_name(struct Unit* unit, struct String name, Type_Id* out_id)
+{
+    FOR_EACH_ARRAY(struct Type_Name_To_Id, mapping, &unit->types.by_name, {
+        if (string_equals(mapping.name, name)) {
+            *out_id = mapping.id;
+            return true;
+        }
+    })
+    return false;
+}
+
+bool
+lower_function_lookup_by_name(struct Unit* unit, struct String name, Function_Id* out_id)
+{
+    FOR_EACH_ARRAY(struct Function_Name_To_Id, mapping, &unit->functions.by_name, {
+        if (string_equals(mapping.name, name)) {
+            *out_id = mapping.id;
+            return true;
+        }
+    })
+    return false;
+}
+
 Type_Id
 lower_seed_primitive(struct Unit* unit, const ascii* name)
 {
@@ -40,11 +82,149 @@ lower_seed_primitive(struct Unit* unit, const ascii* name)
     return id;
 }
 
+// turns a tree-side type expression into an ir Type_Ref. for now this only
+// resolves simple named types (primitives + user-named types already in the
+// table); compound and structural shapes get deferred to later commits.
+struct Type_Ref
+lower_intern_type_ref(struct Unit* unit, struct Tree_Type* tree_type)
+{
+    struct Type_Ref ref = {
+        .type_id = unit->types.primitive_void_id,
+        .mods = array_new(enum Type_Modifier, 4),
+    };
+
+    if (!tree_type || tree_type->type == TREE_TYPE_NONE) return ref;
+
+    switch (tree_type->type) {
+    case TREE_TYPE_NAME: {
+        Type_Id id;
+        if (lower_type_lookup_by_name(unit, tree_type->value.name.name, &id)) {
+            ref.type_id = id;
+        } else {
+            lower_emit_error(
+                unit, tree_type->span,
+                string_concatenate(
+                    ARG_ASCII, "undefined type '", ARG_STRING, tree_type->value.name.name,
+                    ARG_ASCII, "'", ARG_END));
+        }
+        return ref;
+    }
+    default:
+        // compound types (T?, [T], (T1, T2), &T, etc.) arrive in a later commit.
+        lower_emit_error_c(
+            unit, tree_type->span, "unimplemented: only simple named types are supported for now");
+        return ref;
+    }
+}
+
+void
+lower_declare_function(
+    struct Unit* unit, struct String name, struct Tree_Expression* fn_expr, struct Span span)
+{
+    Function_Id existing;
+    if (lower_function_lookup_by_name(unit, name, &existing)) {
+        lower_emit_error(
+            unit, span,
+            string_concatenate(
+                ARG_ASCII, "duplicate function '", ARG_STRING, name, ARG_ASCII, "'", ARG_END));
+        return;
+    }
+
+    Function_Id id = array_length(&unit->functions.entries);
+    struct Function* fn = function_new(id, name);
+
+    fn->is_main = string_equals_c_str(name, "main");
+    fn->return_type = lower_intern_type_ref(unit, fn_expr->value.function.header.return_type);
+    fn->params = array_new(struct Param, 16);
+
+    bool variadic = false;
+    FOR_EACH (
+        struct Tree_Type*, param_type, fn_expr->value.function.header.parameters_type_and_name) {
+        struct Param p = {
+            .name = param_type->value_name,
+            .type = lower_intern_type_ref(unit, param_type),
+        };
+        array_push(&fn->params, &p);
+        if (param_type->variadic) variadic = true;
+    }
+    fn->variadic = variadic;
+    fn->main_takes_args = fn->is_main && array_length(&fn->params) > 0;
+    fn->ast_body = &fn_expr->value.function.body;
+    fn->body = nil;
+
+    array_push(&unit->functions.entries, &fn);
+
+    struct Function_Name_To_Id mapping = { .name = name, .id = id };
+    array_push(&unit->functions.by_name, &mapping);
+}
+
+// extract `name = fun (...) ret { ... }` from a top-level statement,
+// or return false if it's not that shape.
+bool
+lower_match_function_decl(
+    struct Tree_Statement* stmt, struct String* out_name, struct Tree_Expression** out_fn_expr)
+{
+    if (stmt->kind != TREE_STATEMENT_EXPRESSION) return false;
+    struct Tree_Expression* expr = stmt->value.expression.inner;
+    if (!expr || expr->kind != TREE_EXPRESSION_BINARY_OPERATION) return false;
+    if (expr->value.binary_operator.operation != BINARY_ASSIGN) return false;
+
+    struct Tree_Expression* lhs = expr->value.binary_operator.left_operand;
+    struct Tree_Expression* rhs = expr->value.binary_operator.right_operand;
+    if (!lhs || lhs->kind != TREE_EXPRESSION_NAME) return false;
+    if (!rhs || rhs->kind != TREE_EXPRESSION_FUNCTION) return false;
+
+    *out_name = lhs->value.name.name;
+    *out_fn_expr = rhs;
+    return true;
+}
+
+bool
+lower_match_type_decl(struct Tree_Statement* stmt)
+{
+    if (stmt->kind != TREE_STATEMENT_EXPRESSION) return false;
+    struct Tree_Expression* expr = stmt->value.expression.inner;
+    if (!expr || expr->kind != TREE_EXPRESSION_BINARY_OPERATION) return false;
+    if (expr->value.binary_operator.operation != BINARY_ASSIGN) return false;
+
+    struct Tree_Expression* lhs = expr->value.binary_operator.left_operand;
+    struct Tree_Expression* rhs = expr->value.binary_operator.right_operand;
+    if (!lhs || lhs->kind != TREE_EXPRESSION_NAME) return false;
+    if (!rhs || rhs->kind != TREE_EXPRESSION_TYPE) return false;
+
+    return true;
+}
+
+// pass 1 walks every top-level statement and collects declarations into
+// the unit's tables. function bodies stay as raw ast for pass 2 to lower.
+void
+lower_pass_1(struct Unit* unit, struct Tree* tree)
+{
+    FOR_EACH (struct Tree_Statement*, stmt, tree->top_level_statements) {
+        struct String name;
+        struct Tree_Expression* fn_expr;
+        if (lower_match_function_decl(stmt, &name, &fn_expr)) {
+            lower_declare_function(unit, name, fn_expr, stmt->span);
+            continue;
+        }
+
+        if (lower_match_type_decl(stmt)) {
+            lower_emit_error_c(unit, stmt->span, "unimplemented: type declarations");
+            continue;
+        }
+
+        if (stmt->kind == TREE_STATEMENT_PRAGMA) {
+            lower_emit_error_c(unit, stmt->span, "unimplemented: top-level pragmas");
+            continue;
+        }
+
+        lower_emit_error_c(unit, stmt->span, "unsupported top-level statement");
+    }
+}
+
 void
 lower_tree(struct Tree* tree, struct Source_File source, struct Unit* unit)
 {
-    // TODO: implement!
-    (void)tree;
     (void)source;
 
     unit->types.entries = array_new(struct Type*, 256);
@@ -67,4 +247,6 @@ lower_tree(struct Tree* tree, struct Source_File source, struct Unit* unit)
     unit->types.primitive_byte_id = lower_seed_primitive(unit, "byte");
     unit->types.primitive_ascii_id = lower_seed_primitive(unit, "ascii");
     unit->types.primitive_void_id = lower_seed_primitive(unit, "void");
+
+    if (tree) lower_pass_1(unit, tree);
 }