about summary refs log tree commit diff
path: root/boot/lower.c
diff options
context:
space:
mode:
authorMel <mel@rnrd.eu>2026-05-24 20:01:38 +0200
committerMel <mel@rnrd.eu>2026-05-24 20:01:38 +0200
commitf028bc7ef3101facfa004c21f3aa5feb3dc6f107 (patch)
tree3458a0ec69c9552354cb7d3dde38d8f6edd0ad14 /boot/lower.c
parent99d57398863ab1734a9f81c2d336f82dab556383 (diff)
downloadcatskill-f028bc7ef3101facfa004c21f3aa5feb3dc6f107.tar.zst
catskill-f028bc7ef3101facfa004c21f3aa5feb3dc6f107.zip
Support named-unnamed call and construction arguments and fields with final resolved order
Signed-off-by: Mel <mel@rnrd.eu>
Diffstat (limited to 'boot/lower.c')
-rw-r--r--boot/lower.c256
1 files changed, 225 insertions, 31 deletions
diff --git a/boot/lower.c b/boot/lower.c
index c72ad97..f36a124 100644
--- a/boot/lower.c
+++ b/boot/lower.c
@@ -161,6 +161,19 @@ lower_error_to_diagnostic(struct Lower_Error* err, struct Unit* unit, struct Sou
     case LOWER_ERROR_UNKNOWN_COMPOUND_ASSIGN:
         d.message = string_from_static_c_string("unknown compound assignment operator");
         return d;
+    case LOWER_ERROR_UNKNOWN_NAMED_ARGUMENT:
+        d.message = string_format("no parameter named '%S'", STR(err->name));
+        return d;
+    case LOWER_ERROR_DUPLICATE_ARGUMENT:
+        d.message = string_format("argument for '%S' supplied more than once", STR(err->name));
+        return d;
+    case LOWER_ERROR_TOO_MANY_ARGUMENTS:
+        d.message = string_from_static_c_string("too many arguments");
+        return d;
+    case LOWER_ERROR_NAMED_ARGUMENT_ON_UNKNOWN_CALLEE:
+        d.message =
+            string_from_static_c_string("named arguments only work on functions we can see");
+        return d;
     case LOWER_ERROR_UNIMPLEMENTED:
         d.message = string_format("unimplemented: %S", STR(err->detail));
         return d;
@@ -903,32 +916,216 @@ lower_expression_binary_operation(struct Lower_Context* ctx, struct Tree_Express
         lower_expression(ctx, tree_expr->value.binary_operator.right_operand), tree_expr->span);
 }
 
+// find the index of `name` in `names`, or `array_length(names)` if absent.
+uint
+lower_find_slot_by_name(Array(struct String) names, struct String name)
+{
+    uint count = array_length(&names);
+    for (uint j = 0; j < count; ++j) {
+        if (string_equals(*array_at(struct String, &names, j), name)) return j;
+    }
+    return count;
+}
+
+// find the index of the leftmost unclaimed slot in `claimed`,
+// or its length if no slot is free.
+uint
+lower_find_leftmost_unclaimed(Array(bool) claimed)
+{
+    uint count = array_length(&claimed);
+    for (uint j = 0; j < count; ++j) {
+        if (!*array_at(bool, &claimed, j)) return j;
+    }
+    return count;
+}
+
+// resolve a call's argument list. named args claim their named slot,
+// positional args fill the leftmost unclaimed slot. extras (positionals
+// past the last slot) land in slots `>= param_count` only when the
+// callee is variadic.
+bool
+lower_resolve_call_arguments(
+    struct Lower_Context* ctx, struct Span call_span, struct Tree_Argument_Group* group,
+    Array(struct String) param_names, bool variadic, Array(struct Call_Argument) * out_arguments)
+{
+    uint param_count = array_length(&param_names);
+    *out_arguments = array_new(struct Call_Argument, 16);
+
+    Array(bool) claimed = array_new(bool, param_count > 0 ? param_count : 1);
+    for (uint i = 0; i < param_count; ++i) {
+        bool f = false;
+        array_push(&claimed, &f);
+    }
+    uint extra_slot = param_count;
+    bool ok = true;
+
+    uint i = 0;
+    FOR_EACH (struct Tree_Expression*, arg, group->arguments) {
+        struct String name = string_empty();
+        if (i < array_length(&group->argument_names))
+            name = *array_at(struct String, &group->argument_names, i);
+        ++i;
+
+        uint slot;
+        if (name.length > 0) {
+            uint match = lower_find_slot_by_name(param_names, name);
+            if (match == param_count) {
+                lower_push_error(
+                    ctx->unit,
+                    (struct Lower_Error){
+                        .kind = LOWER_ERROR_UNKNOWN_NAMED_ARGUMENT,
+                        .span = call_span,
+                        .name = name });
+                ok = false;
+                continue;
+            }
+            if (*array_at(bool, &claimed, match)) {
+                lower_push_error(
+                    ctx->unit,
+                    (struct Lower_Error){
+                        .kind = LOWER_ERROR_DUPLICATE_ARGUMENT, .span = call_span, .name = name });
+                ok = false;
+                continue;
+            }
+            *array_at(bool, &claimed, match) = true;
+            slot = match;
+        } else {
+            uint match = lower_find_leftmost_unclaimed(claimed);
+            if (match == param_count) {
+                if (!variadic) {
+                    lower_push_error(
+                        ctx->unit,
+                        (struct Lower_Error){
+                            .kind = LOWER_ERROR_TOO_MANY_ARGUMENTS, .span = call_span });
+                    ok = false;
+                    continue;
+                }
+                slot = extra_slot++;
+            } else {
+                *array_at(bool, &claimed, match) = true;
+                slot = match;
+            }
+        }
+
+        struct Call_Argument tagged = { .value = lower_expression(ctx, arg), .slot = slot };
+        array_push(out_arguments, &tagged);
+    }
+
+    return ok;
+}
+
+// resolve a struct construction's field list. same rule as calls, but the
+// slot here is the target field's name since the transpiler emits via c
+// designated initializers and call-site order is fine.
+bool
+lower_resolve_construct_fields(
+    struct Lower_Context* ctx, struct Span call_span, struct Tree_Argument_Group* group,
+    Array(struct String) field_names, Array(struct Construct_Field) * out_fields)
+{
+    uint field_count = array_length(&field_names);
+    *out_fields = array_new(struct Construct_Field, 16);
+
+    Array(bool) claimed = array_new(bool, field_count > 0 ? field_count : 1);
+    for (uint i = 0; i < field_count; ++i) {
+        bool f = false;
+        array_push(&claimed, &f);
+    }
+    bool ok = true;
+
+    uint i = 0;
+    FOR_EACH (struct Tree_Expression*, arg, group->arguments) {
+        struct String name = string_empty();
+        if (i < array_length(&group->argument_names))
+            name = *array_at(struct String, &group->argument_names, i);
+        ++i;
+
+        struct String resolved;
+        if (name.length > 0) {
+            uint match = lower_find_slot_by_name(field_names, name);
+            if (match == field_count) {
+                lower_push_error(
+                    ctx->unit,
+                    (struct Lower_Error){
+                        .kind = LOWER_ERROR_UNKNOWN_NAMED_ARGUMENT,
+                        .span = call_span,
+                        .name = name });
+                ok = false;
+                continue;
+            }
+            if (*array_at(bool, &claimed, match)) {
+                lower_push_error(
+                    ctx->unit,
+                    (struct Lower_Error){
+                        .kind = LOWER_ERROR_DUPLICATE_ARGUMENT, .span = call_span, .name = name });
+                ok = false;
+                continue;
+            }
+            *array_at(bool, &claimed, match) = true;
+            resolved = name;
+        } else {
+            uint match = lower_find_leftmost_unclaimed(claimed);
+            if (match == field_count) {
+                lower_push_error(
+                    ctx->unit,
+                    (struct Lower_Error){
+                        .kind = LOWER_ERROR_TOO_MANY_ARGUMENTS, .span = call_span });
+                ok = false;
+                continue;
+            }
+            *array_at(bool, &claimed, match) = true;
+            resolved = *array_at(struct String, &field_names, match);
+        }
+
+        struct Construct_Field field = { .name = resolved, .value = lower_expression(ctx, arg) };
+        array_push(out_fields, &field);
+    }
+
+    return ok;
+}
+
 struct Expression*
 lower_expression_call(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
 {
     struct Tree_Argument_Group* group = &tree_expr->value.call.argument_group;
+    struct Tree_Expression* subject_tree = tree_expr->value.call.subject;
+
+    // only catskill-side functions are in the function table, calls to libc
+    // and the like go through as positional with no named args allowed.
+    struct Function* callee = nil;
+    if (subject_tree && subject_tree->kind == TREE_EXPRESSION_NAME) {
+        Function_Id fn_id;
+        if (lower_function_lookup_by_name(ctx->unit, subject_tree->value.name.name, &fn_id))
+            callee = *array_at(struct Function*, &ctx->unit->functions.entries, fn_id);
+    }
 
-    // TODO: named arguments need lookup against the callee's parameter
-    // table to reorder, we only handle positional arguments today.
-    FOR_EACH_ARRAY (struct String, name, &group->argument_names) {
-        if (name->length > 0) {
-            lower_push_error(
-                ctx->unit,
-                (struct Lower_Error){
-                    .kind = LOWER_ERROR_UNIMPLEMENTED,
-                    .span = tree_expr->span,
-                    .detail = string_from_static_c_string("named call arguments"),
-                });
-            return nil;
+    struct Expression* subject = lower_expression(ctx, subject_tree);
+    Array(struct Call_Argument) arguments;
+
+    if (callee) {
+        Array(struct String) param_names = array_new(struct String, array_length(&callee->params));
+        FOR_EACH_ARRAY (struct Param, p, &callee->params) array_push(&param_names, &p->name);
+
+        lower_resolve_call_arguments(
+            ctx, tree_expr->span, group, param_names, callee->variadic, &arguments);
+    } else {
+        FOR_EACH_ARRAY (struct String, name, &group->argument_names) {
+            if (name->length > 0) {
+                lower_push_error(
+                    ctx->unit,
+                    (struct Lower_Error){
+                        .kind = LOWER_ERROR_NAMED_ARGUMENT_ON_UNKNOWN_CALLEE,
+                        .span = tree_expr->span });
+                return nil;
+            }
+        }
+        arguments = array_new(struct Call_Argument, 16);
+        uint slot = 0;
+        FOR_EACH (struct Tree_Expression*, arg, group->arguments) {
+            struct Call_Argument tagged = { .value = lower_expression(ctx, arg), .slot = slot++ };
+            array_push(&arguments, &tagged);
         }
     }
 
-    struct Expression* subject = lower_expression(ctx, tree_expr->value.call.subject);
-    Array(struct Expression*) arguments = array_new(struct Expression*, 16);
-    FOR_EACH (struct Tree_Expression*, arg, group->arguments) {
-        struct Expression* lowered = lower_expression(ctx, arg);
-        array_push(&arguments, &lowered);
-    }
     return ir_make_call(subject, arguments, tree_expr->span);
 }
 
@@ -957,20 +1154,17 @@ lower_expression_construct(struct Lower_Context* ctx, struct Tree_Expression* tr
         return nil;
     }
 
-    Array(struct Construct_Field) fields = array_new(struct Construct_Field, 16);
-    struct Tree_Argument_Group* group = &tree_expr->value.construct.argument_group;
-    uint i = 0;
-    FOR_EACH (struct Tree_Expression*, arg, group->arguments) {
-        struct String name = string_empty();
-        if (i < array_length(&group->argument_names))
-            name = *array_at(struct String, &group->argument_names, i);
-        struct Construct_Field field = {
-            .name = name,
-            .value = lower_expression(ctx, arg),
-        };
-        array_push(&fields, &field);
-        ++i;
+    struct Type* type = *array_at(struct Type*, &ctx->unit->types.entries, type_id);
+    Array(struct String) field_names = array_new(struct String, 16);
+    if (type->kind == TYPE_STRUCTURE) {
+        FOR_EACH_ARRAY (struct Field, f, &type->value.structure.fields)
+            array_push(&field_names, &f->name);
     }
+
+    Array(struct Construct_Field) fields;
+    lower_resolve_construct_fields(
+        ctx, tree_expr->span, &tree_expr->value.construct.argument_group, field_names, &fields);
+
     return ir_make_construct(type_id, fields, tree_expr->span);
 }