about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--boot/ir.c101
-rw-r--r--boot/lower.c171
-rw-r--r--boot/transpile.c116
-rw-r--r--boot/visit/ir.c51
4 files changed, 348 insertions, 91 deletions
diff --git a/boot/ir.c b/boot/ir.c
index 164d9a4..f86d63f 100644
--- a/boot/ir.c
+++ b/boot/ir.c
@@ -137,6 +137,13 @@ struct Param
     struct Type_Ref type;
 };
 
+// one captured local in a closing function.
+struct Capture
+{
+    struct String name;
+    struct Type_Ref type; // referenced by capture
+};
+
 // minimal lowered statement set.
 enum Statement_Kind
 {
@@ -239,7 +246,7 @@ enum Expression_Kind
     EXPRESSION_FLOAT_LITERAL,
     EXPRESSION_STRING_LITERAL,
     EXPRESSION_BOOLEAN_LITERAL,
-    EXPRESSION_NAME,
+    EXPRESSION_REFERENCE,
     EXPRESSION_UNARY_OPERATION,
     EXPRESSION_BINARY_OPERATION,
     EXPRESSION_SIZEOF_OPERATION,
@@ -249,7 +256,6 @@ enum Expression_Kind
     EXPRESSION_CAST,
     EXPRESSION_CONSTRUCT,
     EXPRESSION_INCREMENT_DECREMENT,
-    EXPRESSION_FUNCTION_REF,
 };
 
 struct Expression_Integer_Literal
@@ -272,11 +278,56 @@ struct Expression_Bool_Literal
     bool value;
 };
 
-struct Expression_Name
+enum Reference_Kind
+{
+    REFERENCE_NONE,
+    REFERENCE_NAME,
+    REFERENCE_FUNCTION,
+    REFERENCE_CAPTURE,
+};
+
+// a simple, by-name reference to some language object,
+// requiring no special treatment.
+struct Reference_Name
 {
     struct String name;
 };
 
+// a reference to a function, either a named, thin one,
+// or a fat, synthetic one.
+// when a reference points to a thin named function (basically any
+// normal function that's not a closure) and is not a simple, direct call,
+// we will have to call out to a wrapping thunk, to match the fat
+// calling convention of closures.
+struct Reference_Function
+{
+    Function_Id function_id;
+    // matching closure type for the function's signature.
+    Type_Id closure_type_id;
+};
+
+// a reference to a captured local from another, enclosing
+// scope within a closure function.
+// will be gathered from the closure state.
+struct Reference_Capture
+{
+    struct String name;
+};
+
+union Reference_Value
+{
+    struct Reference_Name name;
+    struct Reference_Function function;
+    struct Reference_Capture capture;
+};
+
+// a reference to some other language object.
+struct Expression_Reference
+{
+    enum Reference_Kind kind;
+    union Reference_Value value;
+};
+
 struct Expression_Unary_Operator
 {
     enum Unary_Operation operation;
@@ -356,21 +407,13 @@ struct Expression_Increment_Decrement
     struct Expression* subject;
 };
 
-// reference to a known catskill function in a value context.
-struct Expression_Function_Ref
-{
-    Function_Id function_id;
-    // matching synthetic closure type for the function's signature.
-    Type_Id closure_type_id;
-};
-
 union Expression_Value
 {
     struct Expression_Integer_Literal integer_literal;
     struct Expression_Float_Literal float_literal;
     struct Expression_String_Literal string_literal;
     struct Expression_Bool_Literal bool_literal;
-    struct Expression_Name name;
+    struct Expression_Reference reference;
     struct Expression_Unary_Operator unary_operator;
     struct Expression_Binary_Operator binary_operator;
     struct Expression_Sizeof_Operator sizeof_operator;
@@ -380,7 +423,6 @@ union Expression_Value
     struct Expression_Cast cast;
     struct Expression_Construct construct;
     struct Expression_Increment_Decrement increment_decrement;
-    struct Expression_Function_Ref function_ref;
 };
 
 struct Expression
@@ -420,6 +462,12 @@ struct Function
     // synthesized lazily on first reference in value context, or 0.
     Type_Id closure_type_id;
 
+    // locals captured from enclosing function scopes, filled during body
+    // lowering. (only valid for stateful lambdas)
+    Array(struct Capture) captures;
+    // synthetic structure containing all captured locals. (only stateful lambdas)
+    Type_Id capture_state_type_id;
+
     // tree-side header which produced this function node.
     // required for synthesizing the closure type after function is lowered.
     struct Tree_Function_Header* ast_header;
@@ -428,6 +476,11 @@ struct Function
     // to collect all top-declarations.
     struct Tree_Block* ast_body;
     struct Block* body;
+
+    // has this function been processed to completion already?
+    // synthetic lambdas get their bodies lowered directly when encountered,
+    // unlike other function declarations, to get access to the current local stack.
+    bool completed;
 };
 
 struct Type_Hash_To_Id
@@ -561,6 +614,7 @@ function_new(Function_Id id, struct String name)
     *function = (struct Function){
         .id = id,
         .name = name,
+        .captures = array_new(struct Capture, 4),
     };
     return function;
 }
@@ -636,8 +690,9 @@ struct Expression*
 ir_make_name(struct String name, struct Span span)
 {
     union Expression_Value v = { 0 };
-    v.name.name = name;
-    return expression_new(EXPRESSION_NAME, v, span);
+    v.reference.kind = REFERENCE_NAME;
+    v.reference.value.name.name = name;
+    return expression_new(EXPRESSION_REFERENCE, v, span);
 }
 
 struct Expression*
@@ -729,12 +784,22 @@ ir_make_increment_decrement(
 }
 
 struct Expression*
+ir_make_capture_ref(struct String name, struct Span span)
+{
+    union Expression_Value v = { 0 };
+    v.reference.kind = REFERENCE_CAPTURE;
+    v.reference.value.capture.name = name;
+    return expression_new(EXPRESSION_REFERENCE, v, span);
+}
+
+struct Expression*
 ir_make_function_ref(Function_Id function_id, Type_Id closure_type_id, struct Span span)
 {
     union Expression_Value v = { 0 };
-    v.function_ref.function_id = function_id;
-    v.function_ref.closure_type_id = closure_type_id;
-    return expression_new(EXPRESSION_FUNCTION_REF, v, span);
+    v.reference.kind = REFERENCE_FUNCTION;
+    v.reference.value.function.function_id = function_id;
+    v.reference.value.function.closure_type_id = closure_type_id;
+    return expression_new(EXPRESSION_REFERENCE, v, span);
 }
 
 struct Type_Ref
diff --git a/boot/lower.c b/boot/lower.c
index ba83b39..81f39a5 100644
--- a/boot/lower.c
+++ b/boot/lower.c
@@ -33,6 +33,18 @@ struct Local_Variable
     struct Type_Ref type; // TODO: type-check & fill out empty
 };
 
+// result of looking up a local that may turn out to be a capture for
+// the function we are currently inside.
+struct Lower_Local_Lookup_Capture
+{
+    // was the local found in an enclosing function?
+    bool captured;
+    // the captured local's declared type
+    struct Type_Ref type;
+    // which function does this capture belong to?
+    struct Function* captured_into;
+};
+
 // what kind of scope is this?
 // declared on every lexical scope so we can make sure that the scopes are balanced.
 enum Scope_Type
@@ -65,6 +77,14 @@ struct Scope
 {
     enum Scope_Type type;
     Array(struct Local_Variable) variables;
+
+    // function this scope is lexically contained in.
+    // for function scopes, references the function itself,
+    // for block scopes, references the enclosing function.
+    // used to check whether references cross function scope boundary,
+    // signifying a closure capture.
+    struct Function* containing_function;
+
     // TODO: add local closure functions & local types.
 };
 
@@ -668,6 +688,46 @@ lower_function_closure_type(struct Lower_Context* ctx, struct Function* fn)
     return ref.type_id;
 }
 
+// synthesize the per-lambda state struct that carries pointers to each
+// captured local.
+// each captured local is stored by reference.
+// NOTE(mel): the transpiler pass will output this as a normal structure,
+// does it make sense to distinguish these state types as something else?
+// it muddles distinguishing a type that the user wrote, and a type we synthesized.
+Type_Id
+lower_function_capture_state_type(struct Lower_Context* ctx, struct Function* fn)
+{
+    if (fn->capture_state_type_id != 0) return fn->capture_state_type_id;
+    if (array_length(&fn->captures) == 0) return 0;
+
+    Type_Id id = array_length(&ctx->unit->types.entries);
+    struct String name = lower_synthesize_type_name(ctx);
+    struct Type* type = type_new(id, TYPE_STRUCTURE, name, fn->ast_header->span);
+    type->synthetic = true;
+    type->depends_on = array_new(Type_Id, 8);
+    type->value.structure.fields = array_new(struct Field, 8);
+
+    FOR_EACH_ARRAY (struct Capture, cap, &fn->captures) {
+        // each field carries a pointer to the captured local. peel any
+        // existing modifiers off the captured type and prepend a reference.
+        // TODO: correctly nest all modifiers here!
+        struct Type_Ref ptr = {
+            .type_id = cap->type.type_id,
+            .mods = array_new(enum Type_Modifier, 4),
+        };
+        enum Type_Modifier ref_mod = TYPE_MOD_REFERENCE;
+        array_push(&ptr.mods, &ref_mod);
+        FOR_EACH_ARRAY (enum Type_Modifier, mod, &cap->type.mods) array_push(&ptr.mods, mod);
+
+        struct Field f = { .name = cap->name, .type = ptr };
+        array_push(&type->value.structure.fields, &f);
+    }
+
+    array_push(&ctx->unit->types.entries, &type);
+    fn->capture_state_type_id = id;
+    return id;
+}
+
 void
 lower_fill_type_body(struct Lower_Context* ctx, struct Type* type, struct Tree_Type* tree_type)
 {
@@ -858,6 +918,12 @@ struct Block* lower_block(struct Lower_Context* ctx, struct Tree_Block* tree_blo
 struct Statement* lower_statement(struct Lower_Context* ctx, struct Tree_Statement* tree_stmt);
 struct Expression* lower_expression(struct Lower_Context* ctx, struct Tree_Expression* tree_expr);
 bool lower_name_is_function_typed_local(struct Lower_Context* ctx, struct String name);
+struct Lower_Local_Lookup_Capture
+lower_local_lookup_capturing(struct Lower_Context* ctx, struct String name);
+void lower_record_capture(struct Function* fn, struct String name, struct Type_Ref type);
+void lower_push_scope_function(struct Lower_Context* ctx, struct Function* owner);
+void lower_pop_scope(struct Lower_Context* ctx, enum Scope_Type expected);
+void lower_declare_local(struct Lower_Context* ctx, struct String name, struct Type_Ref type);
 
 struct Expression*
 lower_expression_integer_literal(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
@@ -890,16 +956,31 @@ lower_expression_boolean_literal(struct Lower_Context* ctx, struct Tree_Expressi
 struct Expression*
 lower_expression_name(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
 {
+    struct String name = tree_expr->value.name.name;
+
     // names that resolve to a catskill function become a function reference.
     Function_Id fn_id;
-    if (lower_function_lookup_by_name(ctx->unit, tree_expr->value.name.name, &fn_id)) {
+    if (lower_function_lookup_by_name(ctx->unit, name, &fn_id)) {
         // since this path is only taken outside direct call-positions,
         // the function will need a closure type to wrap it as a value.
         struct Function* fn = *array_at(struct Function*, &ctx->unit->functions.entries, fn_id);
         Type_Id closure_type_id = lower_function_closure_type(ctx, fn);
         return ir_make_function_ref(fn_id, closure_type_id, tree_expr->span);
     }
-    return ir_make_name(tree_expr->value.name.name, tree_expr->span);
+
+    // locals that were defined in an upper function scope are captured.
+    struct Lower_Local_Lookup_Capture lookup = lower_local_lookup_capturing(ctx, name);
+    if (lookup.captured) {
+        lower_record_capture(lookup.captured_into, name, lookup.type);
+        return ir_make_capture_ref(name, tree_expr->span);
+    }
+
+    // unresolved names will continue being bare names.
+    // this is required to account for all non-language objects that might
+    // come from c, or the runtime, or some c-header include we can't see.
+    // the backend will note any name reference which is still unresolved
+    // after taking every source into account.
+    return ir_make_name(name, tree_expr->span);
 }
 
 // any groups are discarded in the lowered representation, their presence
@@ -1239,9 +1320,22 @@ lower_expression_function(struct Lower_Context* ctx, struct Tree_Expression* tre
     fn->synthetic = true;
     lower_fill_function_signature(ctx, fn, tree_expr);
 
+    // lower the body inline so name lookups inside see the enclosing
+    // function's scope and can detect captures.
+    lower_push_scope_function(ctx, fn);
+    FOR_EACH_ARRAY (struct Param, param, &fn->params)
+        lower_declare_local(ctx, param->name, param->type);
+    fn->body = lower_block(ctx, fn->ast_body);
+    lower_pop_scope(ctx, SCOPE_TYPE_FUNCTION);
+
+    fn->completed = true; // mark the function as fully lowered.
+
     // a function literal is always in value context, we can synthesize
     // the closure type eagerly right here.
     Type_Id closure_type_id = lower_function_closure_type(ctx, fn);
+    // if the body captured anything, also synthesize the state type.
+    lower_function_capture_state_type(ctx, fn);
+
     return ir_make_function_ref(id, closure_type_id, tree_expr->span);
 }
 
@@ -1312,6 +1406,15 @@ lower_expression(struct Lower_Context* ctx, struct Tree_Expression* tree_expr)
     }
 }
 
+// look up the function whose body we are currently inside. (if there is one)
+struct Function*
+lower_current_function(struct Lower_Context* ctx)
+{
+    if (ctx->scope_stack.length == 0) return nil;
+    struct Scope* top = array_at(struct Scope, &ctx->scope_stack, ctx->scope_stack.length - 1);
+    return top->containing_function;
+}
+
 // push a fresh lexical scope onto the lowering stack.
 // do not forget to also pop this scope once we leave it!
 void
@@ -1320,6 +1423,21 @@ lower_push_scope(struct Lower_Context* ctx, enum Scope_Type type)
     struct Scope s = {
         .type = type,
         .variables = array_new(struct Local_Variable, 16),
+        // block scopes inherit the function from their upper scope
+        .containing_function = lower_current_function(ctx),
+    };
+    array_push(&ctx->scope_stack, &s);
+}
+
+// push a function scope.
+void
+lower_push_scope_function(struct Lower_Context* ctx, struct Function* owner)
+{
+    struct Scope s = {
+        .type = SCOPE_TYPE_FUNCTION,
+        .variables = array_new(struct Local_Variable, 16),
+        // the owner of this scope is the function that created it
+        .containing_function = owner,
     };
     array_push(&ctx->scope_stack, &s);
 }
@@ -1387,6 +1505,44 @@ lower_local_lookup(struct Lower_Context* ctx, struct String name, struct Type_Re
     return false;
 }
 
+// walk the scope stack looking for a local, which could be captured.
+// if the local was declared in some enclosing function that's not us,
+// the local becomes captured by our closure function.
+struct Lower_Local_Lookup_Capture
+lower_local_lookup_capturing(struct Lower_Context* ctx, struct String name)
+{
+    struct Function* current = lower_current_function(ctx);
+
+    for (uint i = ctx->scope_stack.length; i > 0; --i) {
+        struct Scope* scope = array_at(struct Scope, &ctx->scope_stack, i - 1);
+        FOR_EACH_ARRAY (struct Local_Variable, var, &scope->variables) {
+            if (!string_equals(var->name, name)) continue;
+
+            bool from_enclosing = current != nil && scope->containing_function != current;
+            return (struct Lower_Local_Lookup_Capture){
+                .captured = from_enclosing,
+                .type = var->type,
+                .captured_into = from_enclosing ? current : nil,
+            };
+        }
+    }
+
+    // no capture.
+    return (struct Lower_Local_Lookup_Capture){ 0 };
+}
+
+// add a captured name to a function's capture list.
+void
+lower_record_capture(struct Function* fn, struct String name, struct Type_Ref type)
+{
+    // prevent duplication, closed-over locals are often referenced multiple times.
+    FOR_EACH_ARRAY (struct Capture, c, &fn->captures) {
+        if (string_equals(c->name, name)) return;
+    }
+    struct Capture cap = { .name = name, .type = type };
+    array_push(&fn->captures, &cap);
+}
+
 // is the named local a function-typed value?
 // (used to decide direct vs. indirect call dispatch when the call subject is a bare name.)
 bool
@@ -1735,18 +1891,21 @@ void
 lower_pass_2(struct Lower_Context* ctx)
 {
     FOR_EACH_ARRAY (struct Function*, fn, &ctx->unit->functions.entries) {
+        // skip functions which have already been processed.
+        // synthetic lambdas get lowered eagerly when encountered to gain
+        // access to the local stack at their location.
+        if ((*fn)->completed) continue;
         if (!(*fn)->ast_body) continue;
 
-        // push a function-level scope on top of the persistent top-level
+        // push a function-level scope on top of the persistent top-level unit
         // scope, holding the parameter names.
-        // the function body will also push a new block scope,
-        // separating the parameters and the local declarations.
-        lower_push_scope(ctx, SCOPE_TYPE_FUNCTION);
+        lower_push_scope_function(ctx, *fn);
         FOR_EACH_ARRAY (struct Param, param, &(*fn)->params) {
             lower_declare_local(ctx, param->name, param->type);
         }
 
         (*fn)->body = lower_block(ctx, (*fn)->ast_body);
+        (*fn)->completed = true;
 
         lower_pop_scope(ctx, SCOPE_TYPE_FUNCTION);
     }
diff --git a/boot/transpile.c b/boot/transpile.c
index 9b8f420..ac480cb 100644
--- a/boot/transpile.c
+++ b/boot/transpile.c
@@ -331,12 +331,15 @@ transpile_function_emit_signature(struct Visit* visit, struct Function* function
 {
     TRANSPILE_PREAMBLE
 
+    bool has_captures = function->capture_state_type_id != 0;
+
     VISIT(visit_type_ref, &function->return_type);
     TRANSPILE_WRITE(" %s(", transpile_function_c_name(function));
     bool need_separator = false;
     if (function->synthetic) {
-        // synthetic functions implicitly take in state
-        TRANSPILE_WRITE("void* state");
+        // synthetic functions implicitly take in state.
+        // TODO(mel): can we avoid state_void?
+        TRANSPILE_WRITE("void* %s", has_captures ? "state_void" : "state");
         need_separator = true;
     }
 
@@ -386,13 +389,18 @@ transpile_visit_function_body(struct Visit* visit, struct Function* function)
         return;
     }
 
-    // add a void cast for the implicit state argument, in case synthetic
-    // function does not need to use it
     if (function->synthetic) {
         TRANSPILE_WRITE(" {\n");
         transpiler->indent_level++;
         transpile_emit_indent(transpiler);
-        TRANSPILE_WRITE("(void)state;\n");
+        if (function->capture_state_type_id != 0) {
+            // TODO(mel): can we avoid this?
+            struct Type* state_type =
+                transpile_type_at(transpiler, function->capture_state_type_id);
+            TRANSPILE_WRITE("struct %s* state = state_void;\n", string_c_str(state_type->name));
+        } else {
+            TRANSPILE_WRITE("(void)state;\n");
+        }
         FOR_EACH_ARRAY (struct Statement*, statement, &function->body->statements)
             VISIT(visit_statement, *statement);
         transpiler->indent_level--;
@@ -459,11 +467,65 @@ transpile_visit_expression_boolean_literal(struct Visit* visit, struct Expressio
     TRANSPILE_WRITE("%s", expression->value.bool_literal.value ? "true" : "false");
 }
 
+// emit a reference.
+// the reference's inner kind. callers in subject-of-call position
+// short-circuit FUNCTION references before reaching this handler.
 void
-transpile_visit_expression_name(struct Visit* visit, struct Expression* expression)
+transpile_visit_expression_reference(struct Visit* visit, struct Expression* expression)
 {
     TRANSPILE_PREAMBLE
-    TRANSPILE_WRITE("%s", string_c_str(expression->value.name.name));
+    struct Expression_Reference* ref = &expression->value.reference;
+    switch (ref->kind) {
+    case REFERENCE_NAME:
+        TRANSPILE_WRITE("%s", string_c_str(ref->value.name.name));
+        return;
+
+    case REFERENCE_CAPTURE:
+        TRANSPILE_WRITE("(*state->%s)", string_c_str(ref->value.capture.name));
+        return;
+
+    case REFERENCE_FUNCTION: {
+        struct Function* fn = *array_at(
+            struct Function*, &transpiler->unit->functions.entries,
+            ref->value.function.function_id);
+        struct Type* closure_type =
+            transpile_type_at(transpiler, ref->value.function.closure_type_id);
+
+        const ascii* call_name;
+        struct String thunk_name = string_empty();
+        if (fn->synthetic) {
+            // synthetic lambdas already carry the fat signature
+            call_name = transpile_function_c_name(fn);
+        } else {
+            // named functions go through their thunk converting their
+            // thin calling convention, to the fat lambda convention.
+            thunk_name = transpile_function_thunk_name(fn);
+            call_name = string_c_str(thunk_name);
+        }
+
+        TRANSPILE_WRITE("(struct %s){ .state = ", string_c_str(closure_type->name));
+        if (fn->capture_state_type_id != 0) {
+            // closure with closed-over state, construct a state value.
+            struct Type* state_type = transpile_type_at(transpiler, fn->capture_state_type_id);
+            TRANSPILE_WRITE("&(struct %s){ ", string_c_str(state_type->name));
+            bool first = true;
+            FOR_EACH_ARRAY (struct Capture, cap, &fn->captures) {
+                if (!first) TRANSPILE_WRITE(", ");
+                TRANSPILE_WRITE(".%s = &%s", string_c_str(cap->name), string_c_str(cap->name));
+                first = false;
+            }
+            TRANSPILE_WRITE(" }");
+        } else {
+            TRANSPILE_WRITE("nil");
+        }
+        TRANSPILE_WRITE(", .call = %s }", call_name);
+        return;
+    }
+
+    case REFERENCE_NONE:
+        TRANSPILE_WRITE("/* invalid reference */");
+        return;
+    }
 }
 
 void
@@ -533,11 +595,14 @@ transpile_visit_expression_call(struct Visit* visit, struct Expression* expressi
     struct Expression* subject = expression->value.call.subject;
     bool is_indirect = expression->value.call.is_indirect;
 
-    if (!is_indirect && subject && subject->kind == EXPRESSION_FUNCTION_REF) {
+    bool subject_is_function_ref =
+        subject && subject->kind == EXPRESSION_REFERENCE
+        && subject->value.reference.kind == REFERENCE_FUNCTION;
+    if (!is_indirect && subject_is_function_ref) {
         // direct calls emit the subject bare
         struct Function* fn = *array_at(
             struct Function*, &transpiler->unit->functions.entries,
-            subject->value.function_ref.function_id);
+            subject->value.reference.value.function.function_id);
         TRANSPILE_WRITE("%s", transpile_function_c_name(fn));
     } else if (is_indirect) {
         // route through the fat closure value's call slot, passing state
@@ -620,36 +685,6 @@ transpile_visit_expression_construct(struct Visit* visit, struct Expression* exp
 }
 
 void
-transpile_visit_expression_function_ref(struct Visit* visit, struct Expression* expression)
-{
-    TRANSPILE_PREAMBLE
-
-    struct Function* fn = *array_at(
-        struct Function*, &transpiler->unit->functions.entries,
-        expression->value.function_ref.function_id);
-
-    // we only get here when the function reference is explicitly a *value*,
-    // any direct calls are filtered out earlier.
-    struct Type* closure_type =
-        transpile_type_at(transpiler, expression->value.function_ref.closure_type_id);
-
-    const ascii* call_name;
-    struct String thunk_name = string_empty();
-    if (fn->synthetic) {
-        // synthetic functions are already fat-by-default, can call directly
-        call_name = transpile_function_c_name(fn);
-    } else {
-        // thin named functions go through their generated thunk to match the
-        // fat calling convention
-        thunk_name = transpile_function_thunk_name(fn);
-        call_name = string_c_str(thunk_name);
-    }
-
-    TRANSPILE_WRITE(
-        "(struct %s){ .state = nil, .call = %s }", string_c_str(closure_type->name), call_name);
-}
-
-void
 transpile_visit_expression_increment_decrement(struct Visit* visit, struct Expression* expression)
 {
     TRANSPILE_PREAMBLE
@@ -839,7 +874,7 @@ struct Visit_Table transpile_body_visit_functions = {
     .visit_expression_float_literal = transpile_visit_expression_float_literal,
     .visit_expression_string_literal = transpile_visit_expression_string_literal,
     .visit_expression_boolean_literal = transpile_visit_expression_boolean_literal,
-    .visit_expression_name = transpile_visit_expression_name,
+    .visit_expression_reference = transpile_visit_expression_reference,
     .visit_expression_unary_operation = transpile_visit_expression_unary_operation,
     .visit_expression_binary_operation = transpile_visit_expression_binary_operation,
     .visit_expression_sizeof_operation = transpile_visit_expression_sizeof_operation,
@@ -849,7 +884,6 @@ struct Visit_Table transpile_body_visit_functions = {
     .visit_expression_cast = transpile_visit_expression_cast,
     .visit_expression_construct = transpile_visit_expression_construct,
     .visit_expression_increment_decrement = transpile_visit_expression_increment_decrement,
-    .visit_expression_function_ref = transpile_visit_expression_function_ref,
 };
 
 // walk a lowered translation unit and emit c source into the transpiler's output.
diff --git a/boot/visit/ir.c b/boot/visit/ir.c
index fa17d9c..ca32a1e 100644
--- a/boot/visit/ir.c
+++ b/boot/visit/ir.c
@@ -53,7 +53,7 @@ struct Visit_Table
     void (*visit_expression_float_literal)(struct Visit* visitor, struct Expression* expr);
     void (*visit_expression_string_literal)(struct Visit* visitor, struct Expression* expr);
     void (*visit_expression_boolean_literal)(struct Visit* visitor, struct Expression* expr);
-    void (*visit_expression_name)(struct Visit* visitor, struct Expression* expr);
+    void (*visit_expression_reference)(struct Visit* visitor, struct Expression* expr);
     void (*visit_expression_unary_operation)(struct Visit* visitor, struct Expression* expr);
     void (*visit_expression_binary_operation)(struct Visit* visitor, struct Expression* expr);
     void (*visit_expression_sizeof_operation)(struct Visit* visitor, struct Expression* expr);
@@ -63,7 +63,6 @@ struct Visit_Table
     void (*visit_expression_cast)(struct Visit* visitor, struct Expression* expr);
     void (*visit_expression_construct)(struct Visit* visitor, struct Expression* expr);
     void (*visit_expression_increment_decrement)(struct Visit* visitor, struct Expression* expr);
-    void (*visit_expression_function_ref)(struct Visit* visitor, struct Expression* expr);
 
     void (*visit_block)(struct Visit* visitor, struct Block* block);
     void (*visit_type_ref)(struct Visit* visitor, struct Type_Ref* ref);
@@ -260,8 +259,8 @@ walk_expression(struct Visit* visit, struct Expression* expr)
     case EXPRESSION_BOOLEAN_LITERAL:
         VISIT(visit_expression_boolean_literal, expr);
         break;
-    case EXPRESSION_NAME:
-        VISIT(visit_expression_name, expr);
+    case EXPRESSION_REFERENCE:
+        VISIT(visit_expression_reference, expr);
         break;
     case EXPRESSION_UNARY_OPERATION:
         VISIT(visit_expression_unary_operation, expr);
@@ -290,9 +289,6 @@ walk_expression(struct Visit* visit, struct Expression* expr)
     case EXPRESSION_INCREMENT_DECREMENT:
         VISIT(visit_expression_increment_decrement, expr);
         break;
-    case EXPRESSION_FUNCTION_REF:
-        VISIT(visit_expression_function_ref, expr);
-        break;
     case EXPRESSION_NONE:
         break;
     default:
@@ -304,7 +300,7 @@ WALK_LEAF_FUNCTION(walk_expression_integer_literal, struct Expression*);
 WALK_LEAF_FUNCTION(walk_expression_float_literal, struct Expression*);
 WALK_LEAF_FUNCTION(walk_expression_string_literal, struct Expression*);
 WALK_LEAF_FUNCTION(walk_expression_boolean_literal, struct Expression*);
-WALK_LEAF_FUNCTION(walk_expression_name, struct Expression*);
+WALK_LEAF_FUNCTION(walk_expression_reference, struct Expression*);
 
 void
 walk_expression_unary_operation(struct Visit* visit, struct Expression* expr)
@@ -366,8 +362,6 @@ walk_expression_increment_decrement(struct Visit* visit, struct Expression* expr
     VISIT(visit_expression, expr->value.increment_decrement.subject);
 }
 
-WALK_LEAF_FUNCTION(walk_expression_function_ref, struct Expression*);
-
 void
 walk_block(struct Visit* visit, struct Block* block)
 {
@@ -409,7 +403,7 @@ struct Visit_Table walk_functions = {
     .visit_expression_float_literal = walk_expression_float_literal,
     .visit_expression_string_literal = walk_expression_string_literal,
     .visit_expression_boolean_literal = walk_expression_boolean_literal,
-    .visit_expression_name = walk_expression_name,
+    .visit_expression_reference = walk_expression_reference,
     .visit_expression_unary_operation = walk_expression_unary_operation,
     .visit_expression_binary_operation = walk_expression_binary_operation,
     .visit_expression_sizeof_operation = walk_expression_sizeof_operation,
@@ -419,7 +413,6 @@ struct Visit_Table walk_functions = {
     .visit_expression_cast = walk_expression_cast,
     .visit_expression_construct = walk_expression_construct,
     .visit_expression_increment_decrement = walk_expression_increment_decrement,
-    .visit_expression_function_ref = walk_expression_function_ref,
 
     .visit_block = walk_block,
     .visit_type_ref = walk_type_ref,
@@ -836,10 +829,27 @@ printer_visit_expression_boolean_literal(struct Visit* visit, struct Expression*
 }
 
 void
-printer_visit_expression_name(struct Visit* visit, struct Expression* expr)
+printer_visit_expression_reference(struct Visit* visit, struct Expression* expr)
 {
     PRINTER_PREAMBLE
-    PRINT("(name %s)", string_c_str(expr->value.name.name));
+    struct Expression_Reference* ref = &expr->value.reference;
+    switch (ref->kind) {
+    case REFERENCE_NAME:
+        PRINT("(name %s)", string_c_str(ref->value.name.name));
+        break;
+    case REFERENCE_FUNCTION: {
+        struct Function* fn = *array_at(
+            struct Function*, &p->unit->functions.entries, ref->value.function.function_id);
+        PRINT("(fn-ref %s)", string_c_str(fn->name));
+        break;
+    }
+    case REFERENCE_CAPTURE:
+        PRINT("(capture %s)", string_c_str(ref->value.capture.name));
+        break;
+    case REFERENCE_NONE:
+        PRINT("(reference none)");
+        break;
+    }
 }
 
 void
@@ -950,16 +960,6 @@ printer_visit_expression_increment_decrement(struct Visit* visit, struct Express
 }
 
 void
-printer_visit_expression_function_ref(struct Visit* visit, struct Expression* expr)
-{
-    PRINTER_PREAMBLE
-
-    struct Function* fn = *array_at(
-        struct Function*, &p->unit->functions.entries, expr->value.function_ref.function_id);
-    PRINT("(fn-ref %s)", string_c_str(fn->name));
-}
-
-void
 printer_visit_block(struct Visit* visit, struct Block* block)
 {
     PRINTER_PREAMBLE
@@ -1087,7 +1087,7 @@ struct Visit_Table printer_visit_functions = {
     .visit_expression_float_literal = printer_visit_expression_float_literal,
     .visit_expression_string_literal = printer_visit_expression_string_literal,
     .visit_expression_boolean_literal = printer_visit_expression_boolean_literal,
-    .visit_expression_name = printer_visit_expression_name,
+    .visit_expression_reference = printer_visit_expression_reference,
     .visit_expression_unary_operation = printer_visit_expression_unary_operation,
     .visit_expression_binary_operation = printer_visit_expression_binary_operation,
     .visit_expression_sizeof_operation = printer_visit_expression_sizeof_operation,
@@ -1097,7 +1097,6 @@ struct Visit_Table printer_visit_functions = {
     .visit_expression_cast = printer_visit_expression_cast,
     .visit_expression_construct = printer_visit_expression_construct,
     .visit_expression_increment_decrement = printer_visit_expression_increment_decrement,
-    .visit_expression_function_ref = printer_visit_expression_function_ref,
 
     .visit_block = printer_visit_block,
     .visit_type_ref = printer_visit_type_ref,