changeset 181:3ec2b2edb977

-- last work state before rewrite --
author Lewin Bormann <lbo@spheniscida.de>
date Fri, 27 Sep 2019 14:30:38 +0200
parents 6eb17b273514
children 8cd4314a144f
files src/built-ins.c src/built-ins_test.c src/eval.c src/eval.h src/eval_test.c src/func.h src/preprocess.c src/preprocess.h src/preprocess_test.c src/sizes_test.c src/types.h src/value.c src/value.h
diffstat 13 files changed, 387 insertions(+), 92 deletions(-) [+]
line wrap: on
line diff
--- a/src/built-ins.c	Thu Sep 12 08:25:27 2019 +0200
+++ b/src/built-ins.c	Fri Sep 27 14:30:38 2019 +0200
@@ -47,10 +47,11 @@
 };
 
 static const char* YBUILTIN_ENUM_STR[] = {
-    "BUILTIN:UNDEF", "BUILTIN:FOR",   "BUILTIN:LET",  "BUILTIN:DEFN",
-    "BUILTIN:+",     "BUILTIN:-",     "BUILTIN:*",    "BUILTIN:/",
-    "BUILTIN:IF",    "BUILTIN:PRINT", "BUILTIN:EQ",   "BUILTIN:LT",
-    "BUILTIN:CAR",   "BUILTIN:CDR",   "BUILTIN:PUSH", "BUILTIN:RAISE", "BUILTIN:RECOVER", "BUILTIN:_MKCLSR",
+    "BUILTIN:UNDEF",   "BUILTIN:FOR",     "BUILTIN:LET",  "BUILTIN:DEFN",
+    "BUILTIN:+",       "BUILTIN:-",       "BUILTIN:*",    "BUILTIN:/",
+    "BUILTIN:IF",      "BUILTIN:PRINT",   "BUILTIN:EQ",   "BUILTIN:LT",
+    "BUILTIN:CAR",     "BUILTIN:CDR",     "BUILTIN:PUSH", "BUILTIN:RAISE",
+    "BUILTIN:RECOVER", "BUILTIN:_MKCLSR",
 };
 
 /// Ownership of msg is transferred to this function, ownership of offending is
@@ -211,10 +212,35 @@
     if (ref->typ != YEXPR_REF) TYPEFAIL(LET, let, _pattern);
 
     assert(counter == 2);
-    yvalue_t newvalue = {
-        .typ = YVALUE_EXPR,
-        .value.expr = yeval_list_return_last(state, letlist, counter)};
-    yvalue_set(ref->value.ref, &newvalue);
+
+    // Add or update reference in local scope.
+
+    // Check if ref refers to a captured variable.
+    for (size_t i = 0; i < state->current_closure->captured.len; i++) {
+        ycaptured_t* candidate = YVEC_AT(&state->current_closure->captured, i, ycaptured_t);
+        if (yref_eq(&ref->value.ref, &candidate->ref)) {
+            yexpr_destroy(&candidate->expr);
+            candidate->expr = yeval_list_return_last(state, letlist, counter);
+            return yexpr_new();
+        }
+    }
+
+    // Else, find local slot created by previous `let`.
+    for (size_t i = state->locals.len-1; i > 0; i--) {
+        yeval_local_def_t* candidate = YVEC_AT(&state->locals, i, yeval_local_def_t);
+        if (yref_eq(&ref->value.ref, &candidate->ref)) {
+            // Found, update.
+            yexpr_destroy(&candidate->val);
+            candidate->val = yeval_list_return_last(state, letlist, counter);
+            return yexpr_new();
+        }
+        if (yref_type(&candidate->ref) == YREF_UNDEF)
+            break;
+    }
+
+    // Not found, push onto locals stack.
+    yeval_local_def_t new = {.ref  = ref->value.ref, .val = yeval_list_return_last(state, letlist, counter)};
+    YVEC_PUSH(&state->locals, &new);
     return yexpr_new();
 
 #undef _pattern
@@ -404,7 +430,11 @@
 }
 
 yexpr_t ybuiltin_fn_mkclsr(yeval_state_t* state) {
-#define _pattern "(_mkclsr function-ref)" // Returns a yexpr_t of type YEXPR_FUNC, with captured values.
+#define _pattern \
+    "(_mkclsr function-ref captured1 captured2 ...)"  // Sets function-ref to a
+                                                      // yexpr_t of type
+                                                      // YEXPR_FUNC, with
+                                                      // captured values.
     yexpr_t mkclsr;
     yexpr_t closure = yexpr_new();
 
@@ -412,17 +442,18 @@
     if (mkclsr.typ != YEXPR_LIST) BADEXPRFAIL(INTERNAL_MKCLSR, mkclsr);
     if (mkclsr.value.list.len != 2) BADEXPRFAIL(INTERNAL_MKCLSR, mkclsr);
 
-    yexpr_t *head = YVEC_AT(&mkclsr.value.list, 0, yexpr_t), *tail = YVEC_AT(&mkclsr.value.list, 1, yexpr_t);
-    if (head->typ != YEXPR_BUILTIN || head->value.builtin != YBUILTIN_INTERNAL_MKCLSR) BADEXPRFAIL(INTERNAL_MKCLSR, mkclsr);
+    yexpr_t *head = YVEC_AT(&mkclsr.value.list, 0, yexpr_t),
+            *tail = YVEC_AT(&mkclsr.value.list, 1, yexpr_t);
+    if (head->typ != YEXPR_BUILTIN ||
+        head->value.builtin != YBUILTIN_INTERNAL_MKCLSR)
+        BADEXPRFAIL(INTERNAL_MKCLSR, mkclsr);
     if (tail->typ != YEXPR_REF) BADEXPRFAIL(INTERNAL_MKCLSR, mkclsr);
 
-    yvalue_t *funcval = yvalue_get(tail->value.ref);
-    if (funcval->typ != YVALUE_FUNC) TYPEFAIL(INTERNAL_MKCLSR, mkclsr, _pattern);
-    yfunc_t *func = &funcval->value.func;
-    // Vector of yref_t.
-    yvec_t *to_capture = &func->captured;
+    yvalue_t* funcval = yvalue_get(tail->value.ref);
+    if (funcval->typ != YVALUE_FUNC)
+        TYPEFAIL(INTERNAL_MKCLSR, mkclsr, _pattern);
+    yfunc_t* func = &funcval->value.func;
 
-    does-not-compile
     // TODO: continue implementation
 #undef _pattern
 }
@@ -432,10 +463,24 @@
  *
  * TODO: write built-ins.
  */
-static const ybuiltin_fn YBUILTIN_FNS[] = {
-    ybuiltin_fn_undef, ybuiltin_fn_for,   ybuiltin_fn_let,   NULL,
-    ybuiltin_fn_plus,  ybuiltin_fn_minus, ybuiltin_fn_times, NULL,
-    ybuiltin_fn_if,    ybuiltin_fn_print, ybuiltin_fn_eq};
+static const ybuiltin_fn YBUILTIN_FNS[] = {ybuiltin_fn_undef,
+                                           ybuiltin_fn_for,
+                                           ybuiltin_fn_let,
+                                           NULL,
+                                           ybuiltin_fn_plus,
+                                           ybuiltin_fn_minus,
+                                           ybuiltin_fn_times,
+                                           NULL,
+                                           ybuiltin_fn_if,
+                                           ybuiltin_fn_print,
+                                           ybuiltin_fn_eq,
+                                           NULL,
+                                           NULL,
+                                           NULL,
+                                           NULL,
+                                           NULL,
+                                           NULL,
+                                           ybuiltin_fn_mkclsr};
 
 const char* ybuiltin_name(YBUILTIN_TYPE t) {
     assert(t * sizeof(const char*) < sizeof(YBUILTIN_ENUM_STR));
--- a/src/built-ins_test.c	Thu Sep 12 08:25:27 2019 +0200
+++ b/src/built-ins_test.c	Fri Sep 27 14:30:38 2019 +0200
@@ -50,6 +50,7 @@
     yexpr_debug(&program);
 
     yeval_state_t state = {.call_stack = YVEC_NEW(NULL, 0, yexpr_t)};
+    state.locals = YVEC_NEW(NULL, 0, yeval_local_def_t);
     YVEC_PUSH(&state.call_stack, for_expr);
 
     yexpr_t result = ybuiltin_run(YBUILTIN_FOR, &state);
@@ -58,6 +59,7 @@
     yexpr_debug(&result);
 
     yvec_destroy(&state.call_stack);
+    yvec_destroy(&state.locals);
     yexpr_destroy(&program);
     yexpr_destroy(&result);
     ystr_destroy(&input);
@@ -77,6 +79,7 @@
     yexpr_debug(for_expr);
 
     yeval_state_t state = {.call_stack = YVEC_NEW(NULL, 0, yexpr_t)};
+    state.locals = YVEC_NEW(NULL, 0, yeval_local_def_t);
     YVEC_PUSH(&state.call_stack, for_expr);
 
     yexpr_t result = ybuiltin_run(YBUILTIN_FOR, &state);
@@ -87,6 +90,7 @@
     assert(4 == YVEC_AT(&result.value.list, 0, yexpr_t)->value.n);
 
     yvec_destroy(&state.call_stack);
+    yvec_destroy(&state.locals);
     yexpr_destroy(&program);
     yexpr_destroy(&result);
     ystr_destroy(&input);
@@ -107,6 +111,7 @@
     yexpr_debug(for_expr);
 
     yeval_state_t state = {.call_stack = YVEC_NEW(NULL, 0, yexpr_t)};
+    state.locals = YVEC_NEW(NULL, 0, yeval_local_def_t);
     YVEC_PUSH(&state.call_stack, for_expr);
 
     yexpr_t result = ybuiltin_run(YBUILTIN_FOR, &state);
@@ -114,6 +119,7 @@
     assert(result.value.list.len == 0);
 
     yvec_destroy(&state.call_stack);
+    yvec_destroy(&state.locals);
     yexpr_destroy(&program);
     ystr_destroy(&input);
     yexpr_destroy(&result);
@@ -196,6 +202,7 @@
 
     yeval_state_t state;
     YVEC_INIT(&state.call_stack, 4, yexpr_t);
+    state.locals = YVEC_NEW(NULL, 0, yeval_local_def_t);
     YVEC_PUSH(&state.call_stack, let_expr);
     yexpr_t result = ybuiltin_run(YBUILTIN_LET, &state);
     assert(result.typ == YEXPR_UNDEF);
@@ -213,6 +220,7 @@
     yexpr_destroy(&program);
     yref_destroy(&my_var_ref);
     yvec_destroy(&state.call_stack);
+    yvec_destroy(&state.locals);
     ystr_destroy(&input);
     ystr_destroy(&error);
 }
@@ -231,6 +239,7 @@
     ypreprocess_resolve_builtins(&program);
 
     yeval_state_t state = {.call_stack = YVEC_NEW(NULL, 0, yexpr_t)};
+    state.locals = YVEC_NEW(NULL, 0, yeval_local_def_t);
     // Evaluate whole program
     yexpr_t result = yeval_list_return_last(&state, &program.value.list, 0);
 
@@ -242,6 +251,7 @@
     assert(0 == ystr_cmp_str(&inner->value.str, "cd"));
 
     yvec_destroy(&state.call_stack);
+    yvec_destroy(&state.locals);
     yexpr_destroy(&result);
     ystr_destroy(&error);
     ystr_destroy(&input);
@@ -269,6 +279,7 @@
 
     yeval_state_t state;
     YVEC_INIT(&state.call_stack, 4, yexpr_t);
+    state.locals = YVEC_NEW(NULL, 0, yeval_local_def_t);
     yexpr_t result = yeval_list_return_last(&state, &program.value.list, 0);
     assert(result.typ == YEXPR_LIST);
     yexpr_debug(&result);
@@ -283,6 +294,7 @@
     ystr_destroy(&input);
     ystr_destroy(&error);
     yvec_destroy(&state.call_stack);
+    yvec_destroy(&state.locals);
 }
 
 void test_builtin_print(void) {
@@ -308,6 +320,7 @@
 
     yeval_state_t state;
     YVEC_INIT(&state.call_stack, 4, yexpr_t);
+    state.locals = YVEC_NEW(NULL, 0, yeval_local_def_t);
 
     yexpr_t result = yeval_list_return_last(&state, &program.value.list, 0);
     assert(result.typ == YEXPR_UNDEF);
@@ -322,6 +335,7 @@
     ystr_destroy(&input);
     ystr_destroy(&error);
     yvec_destroy(&state.call_stack);
+    yvec_destroy(&state.locals);
 }
 
 int main(void) {
--- a/src/eval.c	Thu Sep 12 08:25:27 2019 +0200
+++ b/src/eval.c	Fri Sep 27 14:30:38 2019 +0200
@@ -86,6 +86,21 @@
                     state->call_stack.len - 1 - yref_cix(&expr->value.ref),
                     yexpr_t));
                 return result;
+            } else if (yref_type(&expr->value.ref) == YREF_LOCAL) {
+                // First look for locally defined variables, then for captured ones.
+                for (size_t i = state->locals.len - 1; i > 0; i--) {
+                    yeval_local_def_t* candidate = YVEC_AT(&state->locals, i, yeval_local_def_t);
+                    if (yref_type(&candidate->ref) == YREF_UNDEF)
+                        break;
+                    if (yref_eq(&expr->value.ref, &candidate->ref))
+                        return candidate->val;
+                }
+                // Look for captured variables.
+                for (size_t i = 0; i < state->current_closure->captured.len; i++) {
+                    ycaptured_t* candidate = YVEC_AT(&state->current_closure->captured, i, ycaptured_t);
+                    if (yref_eq(&expr->value.ref, &candidate->ref))
+                        return candidate->expr;
+                }
             }
             yvalue_t* val = yvalue_get(expr->value.ref);
             if (val == NULL) return yeval_undef_ref(&expr->value.ref);
@@ -115,6 +130,10 @@
                 return ybuiltin_run(first->value.builtin, state);
             }
 
+            // Push "separator" frame onto locals stack.
+            yeval_local_def_t sep = {.ref = yref_new_undef() };
+            YVEC_PUSH(&state->locals, &sep);
+
             // Is function call?
             if (first->typ == YEXPR_REF) {
                 yexpr_t ref = yeval(state, first, false);
@@ -122,16 +141,16 @@
                 if (ref.typ == YEXPR_REF) {
                     yvalue_t* val = yvalue_get(ref.value.ref);
                     if (val == NULL) {
-                        return yeval_undef_ref(&expr->value.ref);
+                        result = yeval_undef_ref(&expr->value.ref);
+                        goto clean_up;
                     }
                     // Call function.
                     if (val->typ == YVALUE_FUNC) {
                         result = yeval_call_func(state, &val->value.func, expr);
                         if (in_place) {
                             yexpr_destroy(&result);
-                            return result;
                         }
-                        return result;
+                        goto clean_up;
                     } else if (val->typ == YVALUE_EXPR) {
                         assert(val->typ == YVALUE_FUNC /* yeval() should have returned a copy of the expression if first element was a reference to an expression */);
                     }
@@ -150,16 +169,25 @@
                         yeval(state, elem, /* in_place= */ true);
                     yexpr_destroy(&elem_result);
                 }
-                return result;
+            } else {
+                // Store results; input list is copied shallowly, and assigned
+                // with copied values from yeval.
+                result.value.list = yvec_clone(&expr->value.list);
+                for (size_t i = 0; i < result.value.list.len; i++) {
+                    yexpr_t* elem = YVEC_AT(&result.value.list, i, yexpr_t);
+                    yexpr_t elem_result = yeval(state, elem, /* in_place= */ false);
+                    *elem = elem_result;
+                }
             }
 
-            // Store results; input list is copied shallowly, and assigned
-            // with copied values from yeval.
-            result.value.list = yvec_clone(&expr->value.list);
-            for (size_t i = 0; i < result.value.list.len; i++) {
-                yexpr_t* elem = YVEC_AT(&result.value.list, i, yexpr_t);
-                yexpr_t elem_result = yeval(state, elem, /* in_place= */ false);
-                *elem = elem_result;
+clean_up:
+            // Clean up locals.
+            for (size_t i = state->locals.len - 1; i > 0; i--) {
+                yeval_local_def_t* def = YVEC_AT(&state->locals, i, yeval_local_def_t);
+                if (yref_type(&def->ref) == YREF_UNDEF)
+                    break;
+                yexpr_destroy(&def->val);
+                yvec_pop(&state->locals, NULL);
             }
             return result;
         default:
--- a/src/eval.h	Thu Sep 12 08:25:27 2019 +0200
+++ b/src/eval.h	Fri Sep 27 14:30:38 2019 +0200
@@ -11,6 +11,12 @@
  * @{
  */
 
+typedef struct {
+    // if ref's type is YREF_UNDEF, this element marks a scope border.
+    yref_t ref;
+    yexpr_t val;
+} yeval_local_def_t;
+
 /// Evaluation state passed to invocations of `yeval`. There only exists one
 /// instance of this value per program, so per-eval-call information should be
 /// passed directly to yeval.
@@ -19,8 +25,10 @@
     /// a quick way (push/pop is fast on pre-allocated memory).
     yvec_t call_stack;
 
-    // TODO: Maybe move value and atom tables in here? For now, it doesn't make
-    // a difference.
+    /// Values captured by the closure we are currently evaluating.
+    yclosure_t* current_closure;
+    /// Values defined in functions. Grows; every scope has a contiguous sequence and different scopes are separated by YREF_UNDEF entries.
+    yvec_t /* <yeval_local_def> */ locals;
 } yeval_state_t;
 
 /**
--- a/src/eval_test.c	Thu Sep 12 08:25:27 2019 +0200
+++ b/src/eval_test.c	Fri Sep 27 14:30:38 2019 +0200
@@ -16,6 +16,7 @@
 
     yeval_state_t state;
     state.call_stack = YVEC_NEW(NULL, 4, yexpr_t);
+    state.locals = YVEC_NEW(NULL, 4, yeval_local_def_t);
     yexpr_t result = yeval_list_return_last(&state, &program.value.list, 0);
     assert(result.typ == YEXPR_LIST);
     assert(result.value.list.len == 0);
@@ -24,6 +25,7 @@
     ystr_destroy(&input);
     ystr_destroy(&error);
     yvec_destroy(&state.call_stack);
+    yvec_destroy(&state.locals);
     yexpr_destroy(&result);
 }
 
@@ -43,6 +45,7 @@
 
     yeval_state_t state;
     state.call_stack = YVEC_NEW(NULL, 4, yexpr_t);
+    state.locals = YVEC_NEW(NULL, 4, yeval_local_def_t);
     yexpr_t result = yeval_list_return_last(&state, &program.value.list, 0);
     assert(result.typ == YEXPR_EXCEPTION);
     assert(0 == ystr_cmp_str(&result.value.str,
@@ -54,6 +57,7 @@
     ystr_destroy(&input);
     ystr_destroy(&error);
     yvec_destroy(&state.call_stack);
+    yvec_destroy(&state.locals);
     yexpr_destroy(&result);
 }
 
@@ -73,6 +77,7 @@
 
     yeval_state_t state;
     state.call_stack = YVEC_NEW(NULL, 4, yexpr_t);
+    state.locals = YVEC_NEW(NULL, 4, yeval_local_def_t);
     yexpr_t result = yeval_list_return_last(&state, &program.value.list, 0);
     yexpr_debug(&result);
     assert(YEXPR_LIST == result.typ);
@@ -80,6 +85,7 @@
 
     yexpr_destroy(&program);
     yvec_destroy(&state.call_stack);
+    yvec_destroy(&state.locals);
     ystr_destroy(&error);
     yexpr_destroy(&result);
     ystr_destroy(&input);
@@ -100,10 +106,12 @@
 
     yeval_state_t state;
     state.call_stack = YVEC_NEW(NULL, 4, yexpr_t);
+    state.locals = YVEC_NEW(NULL, 4, yeval_local_def_t);
     yexpr_t result = yeval_list_return_last(&state, &program.value.list, 0);
     assert(YEXPR_NUMBER == result.typ && 4 == result.value.n);
 
     yexpr_destroy(&program);
+    yvec_destroy(&state.locals);
     yvec_destroy(&state.call_stack);
     ystr_destroy(&error);
     yexpr_destroy(&result);
@@ -125,11 +133,14 @@
 
     yeval_state_t state;
     state.call_stack = YVEC_NEW(NULL, 4, yexpr_t);
+    yvec_destroy(&state.locals);
+    state.locals = YVEC_NEW(NULL, 4, yeval_local_def_t);
     yexpr_t result = yeval_list_return_last(&state, &program.value.list, 0);
     assert(YEXPR_EXCEPTION == result.typ);
 
     yexpr_destroy(&program);
     yvec_destroy(&state.call_stack);
+    yvec_destroy(&state.locals);
     ystr_destroy(&error);
     yexpr_destroy(&result);
     ystr_destroy(&input);
--- a/src/func.h	Thu Sep 12 08:25:27 2019 +0200
+++ b/src/func.h	Fri Sep 27 14:30:38 2019 +0200
@@ -39,12 +39,6 @@
     /// Vector of yarg_desc_t values, each describing an argument, in order of
     /// appearance.
     yvec_t args;
-    /// Vector of yref_t values, each describing a value to be captured in
-    /// a closure. When defining the function, the interpreter should copy the
-    /// values of these references into the closure yexpr_t value (via the
-    /// to-be-implemented _mkclsr built-in). / When calling a function, the
-    /// captured values are pushed onto the stack before the function arguments.
-    yvec_t captured;
 } yfunc_t;
 
 /**
--- a/src/preprocess.c	Thu Sep 12 08:25:27 2019 +0200
+++ b/src/preprocess.c	Fri Sep 27 14:30:38 2019 +0200
@@ -132,14 +132,14 @@
     yvec_t refs;
 } ypreprocess_scope_t;
 
-static yref_t ypreprocess_find_or_create_ref(
-    ystr_t* symbol, yvec_t* /* of ypreprocess_scope_t* */ scope_stack) {
+static yref_t ypreprocess_find_or_create_ref(ystr_t* symbol,
+                                             ypreprocess_state_t* state) {
     // Search for symbol in stack, starting at top-most scope (enclosing list)
     // and working upwards.
-    for (size_t i = 0; i < scope_stack->len; i++) {
-        size_t index = scope_stack->len - 1 - i;
+    for (size_t i = 0; i < state->scopes.len; i++) {
+        size_t index = state->scopes.len - 1 - i;
         ypreprocess_scope_t* scope =
-            *YVEC_AT(scope_stack, index, ypreprocess_scope_t*);
+            *YVEC_AT(&state->scopes, index, ypreprocess_scope_t*);
 
         // If found: Add reference to closure captured list
         for (size_t ref = 0; ref < scope->refs.len; ref++) {
@@ -152,18 +152,52 @@
     }
 
     // No existing reference found! Create one in the current scope.
-    yref_t ref = yref_new();
+    yref_t ref = yref_new_local();
     // For all new references, we define them in the scope above the current
     // list (thus "- 2"), e.g. in let, defn
     ypreprocess_scope_t* topscope =
-        *YVEC_AT(scope_stack, scope_stack->len - 2, ypreprocess_scope_t*);
+        *YVEC_AT(&state->scopes, state->scopes.len - 2, ypreprocess_scope_t*);
     ypreprocess_existing_t newref = {.sym = ystr_clone(symbol), .ref = ref};
     YVEC_PUSH(&topscope->refs, &newref);
     return ref;
 }
 
-static void ypreprocess_refs_recursive(
-    yvec_t* /* of ypreprocess_scope_t* */ scope_stack, yexpr_t* expr) {
+/// Mark the given reference for capture in all function descriptions up to the
+/// function defining it. Returns true if a definition of the reference was
+/// found somewhere in the lexical stack.
+/// TODO: Merge with ypreprocess_find_or_create_ref().
+static bool ypreprocess_set_capture(yref_t ref, ypreprocess_state_t* state) {
+    // If we are at the top level definition, we don't need to look further up.
+    if (state->defn_scopes.len < 1) return false;
+
+    // Find innermost function defining the reference, and add it to the capture
+    // list of all functions inside it.
+    for (size_t i = state->defn_scopes.len - 1; i > 0; i--) {
+        size_t scopeix = *YVEC_AT(&state->defn_scopes, i, size_t);
+        yvec_t* defined_refs =
+            &YVEC_AT(&state->scopes, scopeix, ypreprocess_scope_t)->refs;
+
+        // Check if function defines this reference.
+        for (size_t j = 0; j < defined_refs->len; j++) {
+            ypreprocess_existing_t* candidate =
+                YVEC_AT(defined_refs, j, ypreprocess_existing_t);
+            if (yref_eq(&candidate->ref, &ref)) {
+                // Found function defining it! We are done here.
+                return true;
+            }
+        }
+        // This is not the defn defining the reference. Mark the reference to be
+        // captured by it.
+        assert(yref_type(&ref) == YREF_LOCAL || yref_type(&ref) == YREF_STACK);
+        yvec_t* captured = YVEC_AT(&state->capture, i, yvec_t);
+        YVEC_PUSH(captured, &ref);
+    }
+    return false;
+}
+
+/** Assign unique IDs to every reference. */
+static void ypreprocess_refs_recursive(ypreprocess_state_t* state,
+                                       yexpr_t* expr) {
     // Abort cheaply for types we don't want to resolve anyway.
     switch (expr->typ) {
         case YEXPR_UNDEF:
@@ -178,21 +212,25 @@
     }
 
     ypreprocess_scope_t scope;
-    ypreprocess_scope_t* scopep = &scope;
 
     switch (expr->typ) {
         case YEXPR_REF:
             if (yref_type(&expr->value.ref) == YREF_SYM) {
                 ystr_t* sym = &expr->value.ref.ref.sym;
-                yref_t resolved =
-                    ypreprocess_find_or_create_ref(sym, scope_stack);
+                yref_t resolved = ypreprocess_find_or_create_ref(sym, state);
+                bool found = ypreprocess_set_capture(resolved, state);
                 yexpr_set_ref(expr, resolved);
+            } else if (yref_type(&expr->value.ref) == YREF_STACK) {
+                bool found = ypreprocess_set_capture(expr->value.ref, state);
             }
             break;
         case YEXPR_LIST:
             // Create new scope for list.
             scope.refs = YVEC_NEW(NULL, 4, ypreprocess_existing_t);
-            YVEC_PUSH(scope_stack, &scopep);
+
+            ypreprocess_scope_t* scopep = &scope;
+            yvec_t* scope_stack = &state->scopes;
+            YVEC_PUSH(&state->scopes, &scopep);
 
             // Handle functions.
             if (expr->value.list.len >= 4) {
@@ -201,9 +239,20 @@
                     head->value.builtin == YBUILTIN_DEFN) {
                     // Compile and store function. Push symbol->reference
                     // mapping onto scope stack.
+
+                    // Set up closure support:
+                    // List of references that this function depends on.
+                    yvec_t captured;
+                    size_t stackix = scope_stack->len - 1;
+                    YVEC_INIT(&captured, 4, yref_t);
+
+                    YVEC_PUSH(&state->defn_scopes, &stackix);
+                    YVEC_PUSH(&state->capture, &captured);
+
                     yref_t new_ref;
                     // TODO: handle errors better
-                    bool compile_defn_ok = ypreprocess_compile_defn(expr, &new_ref);
+                    bool compile_defn_ok =
+                        ypreprocess_compile_defn(expr, &new_ref);
                     assert(compile_defn_ok);
                     yvalue_t* func = yvalue_get(new_ref);
                     assert(func != NULL);
@@ -215,13 +264,57 @@
                                          ypreprocess_scope_t*))
                                    ->refs,
                               &funcref);
-                    // TODO: Build in references to captured args here (for closures) and give to subsequent functions
+
                     yvec_t* func_body = &func->value.func.body;
                     for (size_t i = 0; i < func_body->len; i++) {
                         ypreprocess_refs_recursive(
-                            scope_stack, YVEC_AT(func_body, i, yexpr_t));
+                            state, YVEC_AT(func_body, i, yexpr_t));
                     }
 
+                    // Now we know which references are used by inner functions.
+                    // We can replace the `defn` list with a `(let name (_mkclsr ...))` expression.
+
+                    ypreprocess_find_or_create_ref(&func->value.func.name, state);
+
+                    yvec_t* to_capture = YVEC_AT(
+                        &state->capture, state->capture.len - 1, yvec_t);
+
+                    yvec_t mkclsr_list;
+                    YVEC_INIT(&mkclsr_list, 2 + to_capture->len, yexpr_t);
+                    yexpr_t mkclsr_bi = yexpr_new();
+                    yexpr_set_builtin(&mkclsr_bi, YBUILTIN_INTERNAL_MKCLSR);
+                    yexpr_t funcrefex = yexpr_new();
+                    yexpr_set_ref(&funcrefex, new_ref);
+
+                    YVEC_PUSH(&mkclsr_list, &mkclsr_bi);
+                    YVEC_PUSH(&mkclsr_list, &funcrefex);
+                    for (size_t i = 0; i < to_capture->len; i++) {
+                        yexpr_t refex = yexpr_new();
+                        yexpr_set_ref(&refex, *YVEC_AT(to_capture, i, yref_t));
+                        YVEC_PUSH(&mkclsr_list, &refex);
+                    }
+                    yexpr_t mkclsr = yexpr_new();
+                    yexpr_set_list(&mkclsr, mkclsr_list);
+
+                    yvec_t let_list;
+                    YVEC_INIT(&let_list, 3, yexpr_t);
+                    yexpr_t let_bi = yexpr_new();
+                    yexpr_set_builtin(&let_bi, YBUILTIN_LET);
+
+                    YVEC_PUSH(&let_list, &let_bi);
+                    yexpr_t let_expr = yexpr_new();
+                    yexpr_set_list(&let_expr, &let_list);
+
+                    *expr = mkclsr;
+
+                    yvec_pop(&state->defn_scopes, NULL);
+                    yvec_t capt;
+                    yvec_pop(&state->capture, &capt);
+                    yvec_destroy(&capt);
+
+                    fprintf(stderr, "defined function %s: ",
+                            ystr_str(&func->value.func.name));
+                    yexpr_debug(expr);
                     goto cleanup;
                 }
             }
@@ -231,12 +324,13 @@
             // Resolve list elements recursively.
             for (size_t i = 0; i < expr->value.list.len; i++) {
                 yexpr_t* elem = YVEC_AT(&expr->value.list, i, yexpr_t);
-                ypreprocess_refs_recursive(scope_stack, elem);
+                ypreprocess_refs_recursive(state, elem);
             }
 
         cleanup:
             // Clean up.
-            yvec_pop(scope_stack, NULL);
+            yvec_pop(&state->scopes, NULL);
+
             for (size_t i = 0; i < scope.refs.len; i++) {
                 ypreprocess_existing_t* ex =
                     YVEC_AT(&scope.refs, i, ypreprocess_existing_t);
@@ -250,25 +344,35 @@
     }
 }
 
-// Initial invocation must be of type YEXPR_LIST.
+// Initial invocation must be of type YEXPR_LIST and represent the top-level
+// program.
 void ypreprocess_refs(yexpr_t* expr) {
+    ypreprocess_state_t state;
     yvec_t scope_stack = YVEC_NEW(NULL, 16, ypreprocess_scope_t*);
-    ypreprocess_refs_recursive(&scope_stack, expr);
+    state.scopes = scope_stack;
+    state.capture = YVEC_NEW(NULL, 16, yvec_t);
+    state.defn_scopes = YVEC_NEW(NULL, 16, size_t);
+
+    ypreprocess_refs_recursive(&state, expr);
     assert(scope_stack.len == 0);
     yvec_destroy(&scope_stack);
 }
 
-void ypreprocess_refs_repl(yexpr_t* repl_expr, yvec_t** scope_stack) {
-    if (*scope_stack == NULL) {
-        *scope_stack = malloc(sizeof(yvec_t));
-        **scope_stack = YVEC_NEW(NULL, 16, ypreprocess_scope_t*);
+void ypreprocess_refs_repl(yexpr_t* repl_expr, ypreprocess_state_t** state) {
+    if (*state == NULL) {
+        *state = malloc(sizeof(ypreprocess_state_t));
+        yvec_t scope_stack = YVEC_NEW(NULL, 16, ypreprocess_scope_t*);
+        (*state)->scopes = scope_stack;
+        (*state)->capture = YVEC_NEW(NULL, 16, yvec_t);
+        (*state)->defn_scopes = YVEC_NEW(NULL, 16, size_t);
+
         ypreprocess_scope_t* root = malloc(sizeof(ypreprocess_scope_t));
         root->refs = YVEC_NEW(NULL, 4, ypreprocess_existing_t);
-        YVEC_PUSH(*scope_stack, &root);
+        YVEC_PUSH(&(*state)->scopes, &root);
     }
     assert(YEXPR_LIST == repl_expr->typ);
     for (size_t i = 0; i < repl_expr->value.list.len; i++) {
         yexpr_t* elem = YVEC_AT(&repl_expr->value.list, i, yexpr_t);
-        ypreprocess_refs_recursive(*scope_stack, elem);
+        ypreprocess_refs_recursive(*state, elem);
     }
 }
--- a/src/preprocess.h	Thu Sep 12 08:25:27 2019 +0200
+++ b/src/preprocess.h	Fri Sep 27 14:30:38 2019 +0200
@@ -10,6 +10,21 @@
  * @{
  */
 
+typedef struct {
+    yvec_t /* <ypreprocess_scope_t> */ scopes;
+
+    // The following two vecs are in "lock-step", each element corresponds to
+    // the same index in the other.
+
+    /// Each `defn` pushes a new vector onto this stack. It contains the index
+    /// of the scope frame belonging to that function.
+    yvec_t /* <size_t> */ defn_scopes;
+    /// Each `defn` pushes a new vector onto this stack. Every time a reference
+    /// is encountered, it is pushed onto all stacks down to the function
+    /// defining it. yref_ts must be of type YREF_LOCAL.
+    yvec_t /* <yvec_t<yref_t>> */ capture;
+} ypreprocess_state_t;
+
 /**
  * @brief Apply preprocessing stages, listed as individual functions below.
  */
@@ -29,7 +44,7 @@
  */
 void ypreprocess_refs(yexpr_t* expr);
 
-void ypreprocess_refs_repl(yexpr_t* repl_expr, yvec_t** scope_stack);
+void ypreprocess_refs_repl(yexpr_t* repl_expr, ypreprocess_state_t** state);
 /**
  * @}
  */
--- a/src/preprocess_test.c	Thu Sep 12 08:25:27 2019 +0200
+++ b/src/preprocess_test.c	Fri Sep 27 14:30:38 2019 +0200
@@ -43,6 +43,7 @@
 
     yeval_state_t state;
     state.call_stack = YVEC_NEW(NULL, 0, yexpr_t);
+    state.locals = YVEC_NEW(NULL, 0, yeval_local_def_t);
     yexpr_t result = yeval_list_return_last(&state, &program.value.list, 0);
     assert(result.typ == YEXPR_NUMBER);
     assert(252 == result.value.n);
@@ -51,6 +52,7 @@
     ystr_destroy(&input);
     yexpr_destroy(&program);
     yvec_destroy(&state.call_stack);
+    yvec_destroy(&state.locals);
     yexpr_destroy(&result);
 }
 
@@ -77,6 +79,7 @@
 
     yeval_state_t state;
     state.call_stack = YVEC_NEW(NULL, 0, yexpr_t);
+    state.locals = YVEC_NEW(NULL, 0, yeval_local_def_t);
     yexpr_t result = yeval_list_return_last(&state, &program.value.list, 0);
     assert(result.typ == YEXPR_NUMBER);
     assert(45 == result.value.n);
@@ -86,11 +89,32 @@
     ystr_destroy(&input);
     ystr_destroy(&error);
     yvec_destroy(&state.call_stack);
+    yvec_destroy(&state.locals);
+}
+
+void test_preprocess_closure(void) {
+    fprintf(stderr, "test_preprocess_closure ==============\n");
+
+    ystr_t input = ystr_new("(defn outer (x) (defn inner () (+ x 1)))");
+    yexpr_t program = yexpr_new();
+    ystr_t error = ystr_new(NULL);
+    assert(yparse_str(&input, &program, &error));
+
+    yexpr_debug(&program);
+    ypreprocess_resolve_builtins(&program);
+    yexpr_debug(&program);
+    ypreprocess_refs(&program);
+    yexpr_debug(&program);
+
+    yexpr_destroy(&program);
+    ystr_destroy(&input);
+    ystr_destroy(&error);
 }
 
 int main(void) {
     test_preprocess_defn();
     test_preprocess_refs();
+    test_preprocess_closure();
     fprintf(stderr, "global destroy ===========\n");
     yvalue_free_all();
     yatom_free_all();
--- a/src/sizes_test.c	Thu Sep 12 08:25:27 2019 +0200
+++ b/src/sizes_test.c	Fri Sep 27 14:30:38 2019 +0200
@@ -33,7 +33,7 @@
     assert(8 == sizeof(yatom_t));
     assert(24 == sizeof(yexpr_t));
     assert(16 == sizeof(yref_t));
-    assert(64 == sizeof(yfunc_t));
+    assert(48 == sizeof(yfunc_t));
     assert(32 == sizeof(yclosure_t));
 }
 
--- a/src/types.h	Thu Sep 12 08:25:27 2019 +0200
+++ b/src/types.h	Fri Sep 27 14:30:38 2019 +0200
@@ -18,15 +18,17 @@
 
 /**
  * @brief Type of a reference: Symbolic or numeric.
- *
- * Symbolic references can be resolved to a numeric one, creating a "value slot"
- * (see `value.h`).
  */
 typedef enum {
+    /// An uninitialized reference.
     YREF_UNDEF = 0,
+    /// A reference into the global value table - deprecated.
     YREF_ID = 1,
+    /// An unresolved reference consisting of a string in the `ref.sym` field.
     YREF_SYM = 2,
+    /// A stack-relative reference
     YREF_STACK = 3,
+    YREF_LOCAL = 4,
 } YREF_TYPE;
 
 /**
@@ -42,20 +44,11 @@
         ystr_t sym;
         /// An ID pointing to a slot in the values table (see value.c)
         yvalue_id_t id;
-        /// An index relative to the top of the call_stack, describing a
-        /// function argument.
-        uint64_t cix;
+        /// An ID pointing to a position on the call stack.
+        yvalue_id_t sid;
     } ref;
 } yref_t;
 
-typedef struct {
-    /// Reference to function code.
-    yref_t id;
-    /// List of yexpr_t in the order of the `captured` field of the function
-    /// pointed to by id.
-    yvec_t captured;
-} yclosure_t;
-
 /**
  * @brief A yexpr_t can be a builtin (which looks like an ID), described by the
  * `builtin` field.
@@ -90,7 +83,7 @@
     YBUILTIN_RAISE = 15,
     YBUILTIN_RECOVER = 16,
 
-    YBUILTIN_INTERNAL_MKCLSR = 100,
+    YBUILTIN_INTERNAL_MKCLSR = 17,
 } YBUILTIN_TYPE;
 
 /**
@@ -121,6 +114,13 @@
     YEXPR_EXCEPTION = 8,
 } YEXPR_TYPE;
 
+typedef struct {
+    /// Reference to function code.
+    yref_t id;
+    /// List of ycaptured_t.
+    yvec_t captured;
+} yclosure_t;
+
 /**
  * A value. Can be either a fundamental value (number, string, atom) or a list
  * of values.
@@ -149,6 +149,12 @@
 
 typedef struct yexpr_t yexpr_t;
 
+/// Mapping of ID -> value.
+typedef struct {
+    yref_t ref;
+    yexpr_t expr;
+} ycaptured_t;
+
 /**
  * @}
  */
--- a/src/value.c	Thu Sep 12 08:25:27 2019 +0200
+++ b/src/value.c	Fri Sep 27 14:30:38 2019 +0200
@@ -30,6 +30,12 @@
 //  1. Every symbol has a corresponding place in the value table once it has
 //  been created.
 
+// Marker bytes to distinguish yref_t without an extra field.
+static const yvalue_id_t YVALUE_ID_MARKER = 0xff00000000000000;
+static const yvalue_id_t YVALUE_CALL_STACK_MARKER = 0xfe00000000000000;
+static const yvalue_id_t YVALUE_LOCAL_MARKER = 0xfd00000000000000;
+static const yvalue_id_t YVALUE_INVALID_MARKER = 0xee00000000000000;
+
 static bool yvalue_table_initialized = false;
 static struct hsearch_data yvalue_name_table;
 static yvec_t yvalue_table;  // vector of yvalue_t
@@ -37,10 +43,7 @@
 // TODO: Enable garbage collection/reuse instead of monotonic counter. (e.g.
 // reference-counting in different table)
 static yvalue_id_t yvalue_counter = YVALUE_COUNTER_OFFSET;
-
-// Marker bytes to distinguish yref_t without an extra field.
-static const yvalue_id_t YVALUE_ID_MARKER = 0xff00000000000000;
-static const uint64_t YVALUE_CIX_MARKER = 0xfe00000000000000;
+static yvalue_id_t yvalue_local_counter = YVALUE_LOCAL_MARKER;
 
 static inline void yvalue_init_tables(void) {
     if (yvalue_table_initialized) return;
@@ -58,9 +61,15 @@
     if ((ref->ref.id & YVALUE_ID_MARKER) == YVALUE_ID_MARKER) {
         return YREF_ID;
     }
-    if ((ref->ref.id & YVALUE_CIX_MARKER) == YVALUE_CIX_MARKER) {
+    if ((ref->ref.id & YVALUE_CALL_STACK_MARKER) == YVALUE_CALL_STACK_MARKER) {
         return YREF_STACK;
     }
+    if ((ref->ref.id & YVALUE_LOCAL_MARKER) == YVALUE_LOCAL_MARKER) {
+        return YREF_LOCAL;
+    }
+    if ((ref->ref.id & YVALUE_INVALID_MARKER) == YVALUE_INVALID_MARKER) {
+        return YREF_UNDEF;
+    }
     return YREF_SYM;
 }
 
@@ -88,7 +97,23 @@
 }
 
 yref_t yref_new_cix(size_t ix) {
-    yref_t ref = {.ref.cix = ix | YVALUE_CIX_MARKER};
+    yref_t ref = {.ref.id = ix | YVALUE_CALL_STACK_MARKER};
+    return ref;
+}
+
+yref_t yref_new_local(void) {
+    yref_t ref = {.ref.id = yvalue_local_counter++};
+    return ref;
+}
+
+yref_t yref_new_local_from_id(yvalue_id_t id) {
+    yref_t ref = {.ref.id = id};
+    assert(yref_type(&ref) == YREF_LOCAL);
+    return ref;
+}
+
+yref_t yref_new_undef(void) {
+    yref_t ref = {.ref.id = YVALUE_INVALID_MARKER};
     return ref;
 }
 
@@ -99,15 +124,16 @@
         case YREF_SYM:
             return 0 == ystr_cmp(&a->ref.sym, &b->ref.sym);
         case YREF_ID:
+        case YREF_LOCAL:
             return a->ref.id == b->ref.id;
         case YREF_STACK:
-            return a->ref.cix == b->ref.cix;
+            return a->ref.sid == b->ref.sid;
         default:
             return false;
     }
 }
 
-uint64_t yref_cix(yref_t* ref) { return ref->ref.cix ^ YVALUE_CIX_MARKER; }
+uint64_t yref_cix(yref_t* ref) { return ref->ref.id ^ YVALUE_CALL_STACK_MARKER; }
 
 yref_t yref_clone(yref_t* ref) {
     switch (yref_type(ref)) {
@@ -116,6 +142,8 @@
             return yref_new_c(ystr_str(&ref->ref.sym));
         case YREF_ID:
         case YREF_STACK:
+        case YREF_LOCAL:
+        case YREF_UNDEF:
             return *ref;
         default:
             assert(yref_type(ref) == YREF_SYM || yref_type(ref) == YREF_ID ||
@@ -152,6 +180,10 @@
         ystr_t s = ystr_new(NULL);
         ystr_build(&s, "id:<stackrel:%lld>", -(int64_t)yref_cix(ref));
         return s;
+    } else if (typ == YREF_LOCAL) {
+        ystr_t s = ystr_new(NULL);
+        ystr_build(&s, "id:<local:%lld>", ref->ref.sid);
+        return s;
     }
     return ystr_new("id:INVALID");
 }
@@ -221,7 +253,6 @@
     yvalue_init_tables();
     YREF_TYPE typ = yref_type(ref);
 
-    if (typ == YREF_ID) return;
     if (typ == YREF_SYM) {
         yvalue_id_t id = yvalue_resolve_or_create_symbol(&ref->ref.sym);
         // CAREFUL: This may cause double-frees if `ref` is copied from another
--- a/src/value.h	Thu Sep 12 08:25:27 2019 +0200
+++ b/src/value.h	Fri Sep 27 14:30:38 2019 +0200
@@ -78,6 +78,21 @@
 yref_t yref_new_cix(size_t ix);
 
 /**
+ * Create a new unique "local" reference.
+ */
+yref_t yref_new_local(void);
+
+/**
+ * Create a local reference from a value. `id` must have been generated by yref_new_local().
+ */
+yref_t yref_new_local_from_id(yvalue_id_t id);
+
+/**
+ * Create a reference of type YREF_UNDEF.
+ */
+yref_t yref_new_undef(void);
+
+/**
  * Return the stack-relative index of ref.
  */
 uint64_t yref_cix(yref_t* ref);