1
0
Fork 0
forked from fun/fun

More nested function fun! (0.40.1)

This commit is contained in:
Johannes Findeisen 2026-04-02 03:22:35 +02:00
commit cf320199ef
6 changed files with 369 additions and 15 deletions

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.10)
project(fun VERSION 0.40.0 LANGUAGES C) project(fun VERSION 0.40.1 LANGUAGES C)
set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_STANDARD_REQUIRED ON)

View file

@ -0,0 +1,177 @@
#!/usr/bin/env fun
/*
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-04-02
*/
/*
* Complex demonstration of nested functions that are local to their
* enclosing function, without any variable capture (no closures).
* All state is passed explicitly through parameters at each call.
*
* The example showcases:
* - Deeply nested helpers and multi-stage orchestration
* - Returning a nested function and using it later
* - Recursion implemented via a nested helper
* - Working with maps while threading state explicitly
*/
print("=== Complex nested functions (no captures) ===")
// 1) A small data pipeline on a user map, using local helpers
fun process_user(user)
// Ensure a key is present; if missing, fill with default
fun ensure_has(m, key, def)
if has(m, key)
return m
m[key] = def
return m
// Normalize optional fields
fun normalize_city(m)
if has(m, "city")
return m
m["city"] = "Unknown"
return m
// Add derived attributes explicitly via parameters (no function calls on RHS)
fun annotate(m)
a = m["age"]
// Inline age grouping without relying on an additional helper
g = ""
if a < 13
g = "child"
else if a < 20
g = "teen"
else if a < 65
g = "adult"
else
g = "senior"
m["group"] = g
return m
m1 = ensure_has(user, "name", "N/A")
m2 = ensure_has(m1, "age", 0)
m3 = normalize_city(m2)
m4 = annotate(m3)
return m4
u1 = {"name": "Ada", "age": 37}
u2 = process_user(u1)
print("process_user -> " + to_string(u2))
print("")
print("=== Returning a nested function (no captures) ===")
// 2) Return an inner function and use it later. The returned function
// still requires all the data it needs as parameters (no implicit capture).
fun math_suite()
fun addk(x, k)
return x + k
fun mulk(x, k)
return x * k
// Integer power via nested recursion helper
fun powi(x, n)
fun loop(acc, base, exp)
if exp == 0
return acc
return loop(acc * base, base, exp - 1)
return loop(1, x, n)
// Compose a small arithmetic chain explicitly. Break into simple steps to
// match parser expectations and avoid nested calls on the right-hand side.
fun apply_chain(x, k_a, k_b, n)
t1 = x + k_a
t2 = t1 * k_b
t3 = powi(t2, n)
return t3
return apply_chain
chain = math_suite()
print("chain(2, 3, 4, 2) -> expected ((2+3)*4)^2 = 400")
print(chain(2, 3, 4, 2))
print("")
print("=== Deep orchestration with 3-level nesting ===")
// 3) Multi-stage pipeline with explicit parameter threading through each level
fun orchestrate(a, b, c)
fun stage1(x, a_, b_, c_)
fun stage2(y, b2, c2)
fun stage3(z, c3)
// No capture: every value needed arrives via parameters
tmp = z + c3
return tmp * 2
return stage3(y + b2, c2)
return stage2(x + a_, b_, c_)
// Kick off with x = 0 and thread a, b, c explicitly
return stage1(0, a, b, c)
print("orchestrate(1, 2, 3) -> stage3((0+1)+2, 3) * 2 = (3+3)*2 = 12")
print(orchestrate(1, 2, 3))
print("")
print("=== Nested recursion: factorial via inner loop ===")
// 4) Factorial using an inner tail-recursive helper (no captures)
fun fact(n)
fun go(i, acc)
if i <= 1
return acc
return go(i - 1, acc * i)
return go(n, 1)
print("fact(6) -> expected 720")
print(fact(6))
print("")
print("=== Higher-order style without captures ===")
// 5) Higher-order-like usage where the "strategy" function receives
// all needed parameters explicitly.
fun reducer_sum_with_limit(x, limit)
if x > limit
return 0
return x
fun fold3(a, b, c, f, p1, p2, p3)
// Apply f to each and sum; f must accept (value, param)
s1 = f(a, p1)
s2 = f(b, p2)
s3 = f(c, p3)
return s1 + s2 + s3
print("fold3 with limit: (5<=10 ? 5 : 0) + (12<=10 ? 0 : 0) + (7<=10 ? 7 : 0) = 12")
print(fold3(5, 12, 7, reducer_sum_with_limit, 10, 10, 10))
/* Expected output:
=== Complex nested functions (no captures) ===
process_user -> {"age": 37, "city": "Unknown", "group": "adult", "name": "Ada"}
=== Returning a nested function (no captures) ===
chain(2, 3, 4, 2) -> expected ((2+3)*4)^2 = 400
400
=== Deep orchestration with 3-level nesting ===
orchestrate(1, 2, 3) -> stage3((0+1)+2, 3) * 2 = (3+3)*2 = 12
12
=== Nested recursion: factorial via inner loop ===
fact(6) -> expected 720
720
=== Higher-order style without captures ===
fold3 with limit: (5<=10 ? 5 : 0) + (12<=10 ? 0 : 0) + (7<=10 ? 7 : 0) = 12
12
*/

View file

@ -4,12 +4,18 @@
* This file is part of the Fun programming language. * This file is part of the Fun programming language.
* https://fun-lang.xyz/ * https://fun-lang.xyz/
* *
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-04-02
*/
/*
* Demonstrates nested functions that are only visible inside the * Demonstrates nested functions that are only visible inside the
* outer function where they are defined. This example avoids * outer function where they are defined. This example avoids
* capturing outer variables (closures) and instead passes values * capturing outer variables (closures) and instead passes values
* explicitly, which works with the current implementation. * explicitly, which works with the current implementation.
*
* Added: 2026-04-02
*/ */
print("=== Nested functions (local to outer function) ===") print("=== Nested functions (local to outer function) ===")
@ -19,18 +25,15 @@ fun outer(a, b)
// Defined inside outer; not visible as a global symbol // Defined inside outer; not visible as a global symbol
fun times3(x) fun times3(x)
return x * 3 return x * 3
end
// Another local helper; also only visible within outer // Another local helper; also only visible within outer
fun sum_plus1(x, y) fun sum_plus1(x, y)
return x + y + 1 return x + y + 1
end
// Use the local helpers // Use the local helpers
t1 = times3(a) t1 = times3(a)
t2 = times3(b) t2 = times3(b)
return sum_plus1(t1, t2) return sum_plus1(t1, t2)
end
print("outer(2, 5) -> expected 2*3 + 5*3 + 1 = 22") print("outer(2, 5) -> expected 2*3 + 5*3 + 1 = 22")
print(outer(2, 5)) print(outer(2, 5))
@ -46,19 +49,14 @@ fun demo_deep(n)
fun two(y) fun two(y)
fun three(z) fun three(z)
return z + 1 return z + 1
end
return three(y) + 1 return three(y) + 1
end
return two(x) + 1 return two(x) + 1
end
return one(n) return one(n)
end
print("demo_deep(4) -> expected 7") print("demo_deep(4) -> expected 7")
print(demo_deep(4)) print(demo_deep(4))
/* /* Expected output:
Expected output:
=== Nested functions (local to outer function) === === Nested functions (local to outer function) ===
outer(2, 5) -> expected 2*3 + 5*3 + 1 = 22 outer(2, 5) -> expected 2*3 + 5*3 + 1 = 22
22 22

View file

@ -0,0 +1,41 @@
#!/usr/bin/env fun
/*
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-04-02
*/
/*
* Demonstrates nested functions that are only visible inside the
* outer function where they are defined. This example avoids
* capturing outer variables (closures) and instead passes values
* explicitly, which works with the current implementation.
*/
print("=== Nested (no captures), explicit parameters ===")
fun outer(a, b)
fun mul3(x)
return x * 3
fun combine(x, y)
return x + y + 1
ax = mul3(a)
by = mul3(b)
return combine(ax, by)
print("outer(2, 5) -> expected 2*3 + 5*3 + 1 = 22")
print(outer(2, 5))
/* Expected output:
=== Nested (no captures), explicit parameters ===
outer(2, 5) -> expected 2*3 + 5*3 + 1 = 22
22
*/

View file

@ -15,8 +15,6 @@
fun outer(x) fun outer(x)
fun inner(y) fun inner(y)
return y * 2 return y * 2
end
return inner(x) + inner(3) return inner(x) + inner(3)
end
print(outer(5)) print(outer(5))

View file

@ -54,6 +54,11 @@
/* from parser_utils.c */ /* from parser_utils.c */
extern char *preprocess_includes_with_path(const char *src, const char *current_path); extern char *preprocess_includes_with_path(const char *src, const char *current_path);
/* Forward declarations for helpers used before their definitions */
static void skip_to_eol(const char *src, size_t len, size_t *pos);
static int read_line_start(const char *src, size_t len, size_t *pos, int *out_indent);
static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, int current_indent);
/* ---- parser error state ---- */ /* ---- parser error state ---- */
static const char *g_current_source_path = NULL; /* for propagating filename into nested bytecodes */ static const char *g_current_source_path = NULL; /* for propagating filename into nested bytecodes */
static int g_has_error = 0; static int g_has_error = 0;
@ -219,6 +224,22 @@ typedef struct {
static LocalEnv *g_locals = NULL; static LocalEnv *g_locals = NULL;
/* --- nested function environment tracking (for no-capture enforcement) --- */
static LocalEnv *g_func_env_stack[64];
static int g_func_env_depth = 0; /* number of valid outer env entries */
static int name_in_outer_envs(const char *name) {
if (g_func_env_depth <= 0) return 0;
for (int d = g_func_env_depth - 1; d >= 0; --d) {
LocalEnv *e = g_func_env_stack[d];
if (!e) continue;
for (int i = 0; i < e->count; ++i) {
if (e->names[i] && strcmp(e->names[i], name) == 0) return 1;
}
}
return 0;
}
/* loop context for break/continue patching */ /* loop context for break/continue patching */
typedef struct LoopCtx { typedef struct LoopCtx {
int break_jumps[64]; int break_jumps[64];
@ -256,6 +277,94 @@ static int emit_expression(Bytecode *bc, const char *src, size_t len, size_t *po
static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) { static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) {
skip_spaces(src, len, pos); skip_spaces(src, len, pos);
/* anonymous function literal: fn(arg, ...) <newline> indented-body end */
if (*pos + 2 < len && starts_with(src, len, *pos, "fn") && (src[*pos + 2] == '(' || src[*pos + 2] == ' ' || src[*pos + 2] == '\t')) {
*pos += 2;
skip_spaces(src, len, pos);
if (!consume_char(src, len, pos, '(')) {
parser_fail(*pos, "Expected '(' after 'fn'");
return 0;
}
/* build locals from parameters */
LocalEnv env = {{0}, {0}, 0};
LocalEnv *prev = g_locals;
int saved_depth = g_func_env_depth;
/* push outer env for no-capture checks */
if (prev != NULL) {
if (g_func_env_depth >= (int)(sizeof(g_func_env_stack) / sizeof(g_func_env_stack[0]))) {
parser_fail(*pos, "Too many nested functions (env stack overflow)");
return 0;
}
g_func_env_stack[g_func_env_depth++] = prev;
}
g_locals = &env;
skip_spaces(src, len, pos);
if (*pos < len && src[*pos] != ')') {
for (;;) {
char *pname = NULL;
if (!read_identifier_into(src, len, pos, &pname)) {
parser_fail(*pos, "Expected parameter name");
g_locals = prev;
g_func_env_depth = saved_depth;
return 0;
}
if (local_find(pname) >= 0) {
parser_fail(*pos, "Duplicate parameter name '%s'", pname);
free(pname);
g_locals = prev;
g_func_env_depth = saved_depth;
return 0;
}
local_add(pname);
free(pname);
skip_spaces(src, len, pos);
if (*pos < len && src[*pos] == ',') {
(*pos)++;
skip_spaces(src, len, pos);
continue;
}
break;
}
}
if (!consume_char(src, len, pos, ')')) {
parser_fail(*pos, "Expected ')' after parameter list");
g_locals = prev;
g_func_env_depth = saved_depth;
return 0;
}
/* end header line */
skip_to_eol(src, len, pos);
/* compile body into separate Bytecode */
Bytecode *fn_bc = bytecode_new();
if (fn_bc) {
if (fn_bc->name) free((void *)fn_bc->name);
fn_bc->name = strdup("<fn>");
if (fn_bc->source_file) free((void *)fn_bc->source_file);
if (g_current_source_path) fn_bc->source_file = strdup(g_current_source_path);
}
/* parse body at increased indent if present */
int body_indent = 0;
size_t look_body = *pos;
if (read_line_start(src, len, &look_body, &body_indent) && body_indent > 0) {
parse_block(fn_bc, src, len, pos, body_indent);
} else {
/* empty body ok */
}
bytecode_add_instruction(fn_bc, OP_RETURN, 0);
/* restore env stack */
g_locals = prev;
g_func_env_depth = saved_depth;
int fci = bytecode_add_constant(bc, make_function(fn_bc));
bytecode_add_instruction(bc, OP_LOAD_CONST, fci);
return 1;
}
/* parenthesized */ /* parenthesized */
if (*pos < len && src[*pos] == '(') { if (*pos < len && src[*pos] == '(') {
(*pos)++; /* '(' */ (*pos)++; /* '(' */
@ -4386,6 +4495,12 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
} }
/* push function value first */ /* push function value first */
if (local_idx < 0 && name_in_outer_envs(name)) {
const char *fname = (bc && bc->name) ? bc->name : "<function>";
parser_fail(*pos, "Nested function '%s' cannot access outer local '%s'. Pass it as a parameter instead.", fname, name);
free(name);
return 0;
}
if (local_idx >= 0) { if (local_idx >= 0) {
bytecode_add_instruction(bc, OP_LOAD_LOCAL, local_idx); bytecode_add_instruction(bc, OP_LOAD_LOCAL, local_idx);
} else { } else {
@ -4541,6 +4656,12 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
free(name); free(name);
return 1; return 1;
} else { } else {
if (local_idx < 0 && name_in_outer_envs(name)) {
const char *fname = (bc && bc->name) ? bc->name : "<function>";
parser_fail(*pos, "Nested function '%s' cannot access outer local '%s'. Pass it as a parameter instead.", fname, name);
free(name);
return 0;
}
if (local_idx >= 0) { if (local_idx >= 0) {
bytecode_add_instruction(bc, OP_LOAD_LOCAL, local_idx); bytecode_add_instruction(bc, OP_LOAD_LOCAL, local_idx);
} else { } else {
@ -6621,8 +6742,17 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos,
} }
/* build locals from parameters */ /* build locals from parameters */
LocalEnv env = {{0}, 0}; LocalEnv env = {{0}, {0}, 0};
LocalEnv *prev = g_locals; LocalEnv *prev = g_locals;
int saved_depth = g_func_env_depth;
if (prev != NULL) {
if (g_func_env_depth >= (int)(sizeof(g_func_env_stack) / sizeof(g_func_env_stack[0]))) {
parser_fail(*pos, "Too many nested functions (env stack overflow)");
free(fname);
return;
}
g_func_env_stack[g_func_env_depth++] = prev;
}
g_locals = &env; g_locals = &env;
skip_spaces(src, len, pos); skip_spaces(src, len, pos);
@ -6713,7 +6843,16 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos,
free(fname); free(fname);
return; return;
} }
/* We want sibling inner functions to be able to call this nested function
* without closures. Strategy: also expose it as a global symbol so that
* inner functions (compiled into separate frames) can resolve the name
* via global lookup. Keep the local binding for the outer function body. */
/* Duplicate the function value so we can store to both local and global. */
bytecode_add_instruction(bc, OP_DUP, 0);
bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx);
/* Bind a global under the same name for call resolution inside deeper nested functions. */
int gsym = sym_index(fname);
bytecode_add_instruction(bc, OP_STORE_GLOBAL, gsym);
/* restore current env (will be reset to prev below) */ /* restore current env (will be reset to prev below) */
g_locals = save_env; g_locals = save_env;
} else { } else {
@ -6722,6 +6861,7 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos,
} }
g_locals = prev; g_locals = prev;
g_func_env_depth = saved_depth;
free(fname); free(fname);
continue; continue;
} }