about summary refs log tree commit diff
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
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>
-rw-r--r--boot/ir.c23
-rw-r--r--boot/lower.c256
-rw-r--r--boot/parse.c4
-rw-r--r--boot/transpile.c17
-rw-r--r--boot/visit/ir.c23
5 files changed, 276 insertions, 47 deletions
diff --git a/boot/ir.c b/boot/ir.c
index 6ea055e..d8d8aa2 100644
--- a/boot/ir.c
+++ b/boot/ir.c
@@ -294,12 +294,21 @@ struct Expression_Sizeof_Operator
     struct Type_Ref target;
 };
 
+// one argument of a call, tagged with the parameter slot it fills.
+struct Call_Argument
+{
+    struct Expression* value;
+    // slot decides the final emission order.
+    // slots that are higher than the parameter count of a function
+    // are always counted as variadic extras, and an error for non-variadics.
+    uint slot;
+};
+
 struct Expression_Call
 {
     struct Expression* subject;
-    // call arguments are positional, named and out-of-order
-    // arguments get re-ordered until they are correct.
-    Array(struct Expression*) arguments;
+    // arguments of call in written order, the inner slot decides final ordering.
+    Array(struct Call_Argument) arguments;
 };
 
 struct Expression_Member
@@ -459,6 +468,10 @@ enum Lower_Error_Kind
     LOWER_ERROR_UNSUPPORTED_TOP_LEVEL,
     LOWER_ERROR_UNKNOWN_LOOP_STYLE,
     LOWER_ERROR_UNKNOWN_COMPOUND_ASSIGN,
+    LOWER_ERROR_UNKNOWN_NAMED_ARGUMENT,
+    LOWER_ERROR_DUPLICATE_ARGUMENT,
+    LOWER_ERROR_TOO_MANY_ARGUMENTS,
+    LOWER_ERROR_NAMED_ARGUMENT_ON_UNKNOWN_CALLEE,
     LOWER_ERROR_UNIMPLEMENTED,
 };
 
@@ -466,7 +479,7 @@ struct Lower_Error
 {
     enum Lower_Error_Kind kind;
     struct Span span;
-    
+
     // per-error details, meaning depends on each error kind!
     struct String name;
     struct String detail;
@@ -625,7 +638,7 @@ ir_make_sizeof(struct Type_Ref target, struct Span span)
 }
 
 struct Expression*
-ir_make_call(struct Expression* subject, Array(struct Expression*) arguments, struct Span span)
+ir_make_call(struct Expression* subject, Array(struct Call_Argument) arguments, struct Span span)
 {
     union Expression_Value v = { 0 };
     v.call.subject = subject;
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);
 }
 
diff --git a/boot/parse.c b/boot/parse.c
index 2b5d859..1cf08fd 100644
--- a/boot/parse.c
+++ b/boot/parse.c
@@ -407,6 +407,7 @@ parser_function_header_node(struct Parser* p, struct Parser_Error* error)
         CHECK_RETURN(parser_need(p, TOKEN_ROUND_OPEN, error), struct Tree_Function_Header);
 
     struct Tree_Function_Header header = { 0 };
+    struct Tree_Type* tail = nil;
     while (!parser_probe(p, TOKEN_ROUND_CLOSE)) {
         // TODO: correctly output parameter spans
         bool variadic = false;
@@ -432,7 +433,8 @@ parser_function_header_node(struct Parser* p, struct Parser_Error* error)
         if (!header.parameters_type_and_name)
             header.parameters_type_and_name = type;
         else
-            header.parameters_type_and_name->next = type;
+            tail->next = type;
+        tail = type;
 
         if (parser_probe(p, TOKEN_COMMA)) parser_next(p);
     }
diff --git a/boot/transpile.c b/boot/transpile.c
index 9d910b3..d5a718a 100644
--- a/boot/transpile.c
+++ b/boot/transpile.c
@@ -437,12 +437,19 @@ transpile_visit_expression_call(struct Visit* visit, struct Expression* expressi
 
     transpile_emit_expression(visit, expression->value.call.subject, TRANSPILE_PRECEDENCE_POSTFIX);
     TRANSPILE_WRITE("(");
+    Array(struct Call_Argument) args = expression->value.call.arguments;
+    uint max_slot = 0;
+    FOR_EACH_ARRAY (struct Call_Argument, a, &args) {
+        if (a->slot > max_slot) max_slot = a->slot;
+    }
     bool first = true;
-    FOR_EACH_ARRAY (struct Expression*, argument, &expression->value.call.arguments) {
-        if (!first) TRANSPILE_WRITE(", ");
-        // call arguments are comma-separated, each fresh from precedence 0.
-        transpile_emit_expression(visit, *argument, 0);
-        first = false;
+    for (uint s = 0; s <= max_slot; ++s) {
+        FOR_EACH_ARRAY (struct Call_Argument, a, &args) {
+            if (a->slot != s) continue;
+            if (!first) TRANSPILE_WRITE(", ");
+            transpile_emit_expression(visit, a->value, 0);
+            first = false;
+        }
     }
     TRANSPILE_WRITE(")");
 }
diff --git a/boot/visit/ir.c b/boot/visit/ir.c
index 5317276..6748384 100644
--- a/boot/visit/ir.c
+++ b/boot/visit/ir.c
@@ -321,8 +321,8 @@ void
 walk_expression_call(struct Visit* visit, struct Expression* expr)
 {
     VISIT(visit_expression, expr->value.call.subject);
-    FOR_EACH_ARRAY (struct Expression*, argument, &expr->value.call.arguments)
-        VISIT(visit_expression, *argument);
+    FOR_EACH_ARRAY (struct Call_Argument, argument, &expr->value.call.arguments)
+        VISIT(visit_expression, argument->value);
 }
 
 void
@@ -859,9 +859,10 @@ printer_visit_expression_call(struct Visit* visit, struct Expression* expr)
     PRINTER_PREAMBLE
     PRINT("(call ");
     VISIT(visit_expression, expr->value.call.subject);
-    FOR_EACH_ARRAY (struct Expression*, argument, &expr->value.call.arguments) {
-        PRINT(" ");
-        VISIT(visit_expression, *argument);
+    FOR_EACH_ARRAY (struct Call_Argument, argument, &expr->value.call.arguments) {
+        PRINT(" (slot %lu ", argument->slot);
+        VISIT(visit_expression, argument->value);
+        PRINT(")");
     }
     PRINT(")");
 }
@@ -996,6 +997,18 @@ printer_visit_lower_error(struct Visit* visit, struct Lower_Error* error)
     case LOWER_ERROR_UNKNOWN_COMPOUND_ASSIGN:
         PRINT("(unknown-compound-assign)");
         return;
+    case LOWER_ERROR_UNKNOWN_NAMED_ARGUMENT:
+        PRINT("(unknown-named-argument %s)", string_c_str(error->name));
+        return;
+    case LOWER_ERROR_DUPLICATE_ARGUMENT:
+        PRINT("(duplicate-argument %s)", string_c_str(error->name));
+        return;
+    case LOWER_ERROR_TOO_MANY_ARGUMENTS:
+        PRINT("(too-many-arguments)");
+        return;
+    case LOWER_ERROR_NAMED_ARGUMENT_ON_UNKNOWN_CALLEE:
+        PRINT("(named-argument-on-unknown-callee)");
+        return;
     case LOWER_ERROR_UNIMPLEMENTED:
         PRINT("(unimplemented \"%s\")", string_c_str(error->detail));
         return;