Files
sx/examples/0129-types-tuple-operators.sx
agra bdd0e96d78 feat(lang): block value requires no trailing ; (Rust-style)
A block's value is now its last statement ONLY when that statement is a
trailing expression with no `;`. A trailing `;` discards the value,
leaving the block void. This makes value-vs-statement explicit and lets
the compiler reject "this block was supposed to produce a value".

Compiler:
- Parser records `Block.produces_value` (last stmt is a no-`;` trailing
  expression) + `Block.discarded_semi` (the `;` that discarded a value),
  via `expectSemicolonAfter`. A trailing expression before `}` may now
  omit its `;` (previously a parse error). Match-arm and else-arm bodies
  are built value-producing regardless of the arm `;` (arms are exempt —
  the `;` is an arm terminator).
- Lowering: `lowerBlockValue` / the block-expr path / `inferExprType`
  respect `produces_value`. A value-position block that discards its value
  is a hard error (`lowerValueBody` for function bodies; the value-context
  `.block` path for if/else branches, `catch` bodies, value bindings,
  match arms). Pure-failable `-> !` bodies (value rides the error channel)
  and a value-if whose branches are void are handled without false errors.
- `defer`/`onfail` cleanup bodies lower as statements (void), so a
  trailing `;` there is fine.

Migration (behavior-preserving — output unchanged):
- stdlib + ~210 examples: dropped the trailing `;` on value-position last
  expressions. `format` now ends with an explicit `#insert "return
  result;"` (it relied on `#insert`-as-block-value, which `;` discards).
- Two `main :: () -> s32` examples that relied on the old silent
  default-return got an explicit trailing `0`.
- Rejection snapshots 0412 / 1013 regenerated (their quoted source lines
  lost a `;`); the diagnostics themselves are unchanged.

Docs/tests: specs.md "Block values" section; examples 0040 (rules) + 0041
(rejection); 3 parser unit tests. Filed issue 0066 (pre-existing
match-arm negated-literal phi-width quirk, surfaced not caused here).

Gates: zig build, zig build test, run_examples.sh -> 343 passed,
cross_compile.sh -> 7 passed (also refreshed its stale example names).
2026-06-02 09:23:50 +03:00

1201 lines
42 KiB
Plaintext

#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
pkg :: #import "modules/testpkg";
Point :: struct { x, y: s32; }
OptNode :: struct {
value: s32;
next: ?s32;
}
OptInner :: struct { val: s32; }
OptOuter :: struct { inner: ?OptInner; }
add :: (a: s32, b: s32) -> s32 { a + b }
mul :: (a: s32, b: s32) -> s32 { a * b }
identity :: (x: $T) -> T { x }
apply :: (f: (s32, s32) -> s32, x: s32, y: s32) -> s32 {
f(x, y)
}
// P4 edge: Chained default→default calls
Chained :: protocol {
base :: (msg: string) -> s32;
wrap :: (msg: string) -> s32 {
self.base(msg) + 1
}
double_wrap :: (msg: string) -> s32 {
self.wrap(msg) + self.wrap(msg)
}
}
main :: () {
// --- Tuple Operators ---
{
print("=== Tuple Operators ===\n");
// Equality
print("{}\n", (1, 2) == (1, 2)); // true
print("{}\n", (1, 2) == (1, 3)); // false
print("{}\n", (1, 2) != (1, 3)); // true
print("{}\n", (1, 2) != (1, 2)); // false
// Concatenation
c := (1, 2) + (3, 4);
print("{}\n", c.0); // 1
print("{}\n", c.1); // 2
print("{}\n", c.2); // 3
print("{}\n", c.3); // 4
// Repetition
r := (1, 2) * 3;
print("{}\n", r.0); // 1
print("{}\n", r.1); // 2
print("{}\n", r.2); // 1
print("{}\n", r.3); // 2
print("{}\n", r.4); // 1
print("{}\n", r.5); // 2
// Lexicographic comparison
print("{}\n", (1, 2) < (1, 3)); // true
print("{}\n", (1, 3) < (1, 2)); // false
print("{}\n", (1, 2) < (1, 2)); // false
print("{}\n", (1, 2) <= (1, 2)); // true
print("{}\n", (2, 0) > (1, 9)); // true
print("{}\n", (1, 2) >= (1, 2)); // true
// Membership
print("{}\n", 2 in (1, 2, 3)); // true
print("{}\n", 5 in (1, 2, 3)); // false
}
// --- Directory imports ---
{
print("--- directory imports ---\n");
print("{}\n", pkg.add(3, 4)); // 7
print("{}\n", pkg.mul(5, 6)); // 30
print("{}\n", pkg.hello()); // hello from testpkg
print("{}\n", pkg.cwd_greet()); // cwd-import-ok
}
// --- Pipe operator ---
{
print("--- pipe operator ---\n");
// Basic: a |> f(b) → f(a, b)
print("{}\n", 3 |> pkg.add(4)); // 7
print("{}\n", 5 |> pkg.mul(6)); // 30
// Chaining: a |> f(b) |> g(c) → g(f(a, b), c)
print("{}\n", 3 |> pkg.add(4) |> pkg.mul(2)); // 14
// With non-namespaced functions
print("{}\n", "hello" |> concat(" world")); // hello world
// Chained string ops
print("{}\n", "piped" |> concat(" ok") |> concat("!")); // piped ok!
}
// ── alloc_slice ──────────────────────────────────────────
{
items := alloc_slice(s64, 5);
items[0] = 10;
items[1] = 20;
items[2] = 30;
items[3] = 40;
items[4] = 50;
print("alloc len: {}\n", items.len); // alloc len: 5
print("alloc[0]: {}\n", items[0]); // alloc[0]: 10
print("alloc[4]: {}\n", items[4]); // alloc[4]: 50
// alloc_slice with u8
bytes := alloc_slice(u8, 3);
bytes[0] = 65;
bytes[1] = 66;
bytes[2] = 67;
print("bytes len: {}\n", bytes.len); // bytes len: 3
}
// ========================================================
// ALLOCATORS
// ========================================================
print("--- allocators ---\n");
// ── GPA ─────────────────────────────────────────────────
{
gpa := GPA.init();
a : Allocator = xx gpa;
p1 := a.alloc(64);
p2 := a.alloc(128);
print("gpa allocs: {}\n", gpa.alloc_count); // gpa allocs: 2
a.dealloc(p1);
a.dealloc(p2);
print("gpa final: {}\n", gpa.alloc_count); // gpa final: 0
}
// ── Arena backed by GPA (multi-chunk) ───────────────────
{
gpa3 := GPA.init();
arena := Arena.init(xx gpa3, 32);
a : Allocator = xx arena;
// First chunk fits 80 usable bytes
a1 := a.alloc(40);
a2 := a.alloc(40);
// Counts: just the first chunk = 1. Arena.init returns the
// state by value; the local IS the Arena struct, no parent
// allocation for the state itself.
print("arena chunks: {}\n", gpa3.alloc_count); // arena chunks: 1
// Overflow → new chunk
a3 := a.alloc(16);
print("arena overflow: {}\n", gpa3.alloc_count); // arena overflow: 2
// Verify memory works across chunks
p1 : [*]u8 = xx a1;
p3 : [*]u8 = xx a3;
p1[0] = 42;
p3[0] = 99;
print("arena a1: {}\n", p1[0]); // arena a1: 42
print("arena a3: {}\n", p3[0]); // arena a3: 99
// Reset retains the first chunk
arena.reset();
print("arena reset idx: {}\n", arena.end_index); // arena reset idx: 0
print("arena reset gpa: {}\n", gpa3.alloc_count); // arena reset gpa: 1
// Deinit frees all chunks (caller's local is the state — no
// dealloc of the struct itself).
arena.deinit();
print("arena deinit: {}\n", gpa3.alloc_count); // arena deinit: 0
}
// ── BufAlloc from stack array ───────────────────────────
{
stack_buf : [128]u8 = ---;
buf := BufAlloc.init(@stack_buf[0], 128);
a : Allocator = xx buf;
b1 := a.alloc(24);
b2 := a.alloc(24);
print("buf pos: {}\n", buf.pos); // buf pos: 48
b3 := a.alloc(200);
b3_i : s64 = xx b3;
print("buf overflow: {}\n", b3_i); // buf overflow: 0
buf.reset();
print("buf reset: {}\n", buf.pos); // buf reset: 0
}
{
if 1 == (1,) {
print("1 == (1)\n");
}
if (1,) == (1) {
print("(1) == 1\n");
}
if (1,) == 1 {
print("1 == 1\n");
}
}
// ========================================================
// OPTIONALS
// ========================================================
print("--- optionals ---\n");
// Basic optional creation and null
{
x: ?s32 = 42;
y: ?s32 = null;
print("opt x: {}\n", x); // opt x: 42
print("opt y: {}\n", y); // opt y: null
}
// Force unwrap
{
x: ?s32 = 10;
val := x!;
print("unwrap: {}\n", val); // unwrap: 10
}
// Null coalescing
{
x: ?s32 = 42;
y: ?s32 = null;
a := x ?? 0;
b := y ?? 99;
print("coalesce a: {}\n", a); // coalesce a: 42
print("coalesce b: {}\n", b); // coalesce b: 99
// Chained ?? (right-associative): a ?? b ?? c
z: ?s32 = null;
c := x ?? y ?? 0;
d := z ?? y ?? 99;
e := z ?? z ?? 0;
print("chained ?? c: {}\n", c); // chained ?? c: 42
print("chained ?? d: {}\n", d); // chained ?? d: 99
print("chained ?? e: {}\n", e); // chained ?? e: 0
}
// If-binding (safe unwrap)
{
x: ?s32 = 7;
y: ?s32 = null;
if val := x {
print("if-bind x: {}\n", val); // if-bind x: 7
}
if val := y {
print("if-bind y: should not print\n");
} else {
print("if-bind y: none\n"); // if-bind y: none
}
}
// Pattern matching on optionals
{
check :: (v: ?s32) -> s32 {
return if v == {
case .some: (val) { val }
case .none: { 0 }
};
}
a: ?s32 = 55;
b: ?s32 = null;
print("match some: {}\n", check(a)); // match some: 55
print("match none: {}\n", check(b)); // match none: 0
}
// Optional with implicit wrapping
{
opt_wrap :: (n: s32) -> ?s32 {
if n > 0 {
return n;
}
return null;
}
r1 := opt_wrap(5);
r2 := opt_wrap(0);
print("wrap pos: {}\n", r1); // wrap pos: 5
print("wrap neg: {}\n", r2); // wrap neg: null
}
// Struct field defaults for ?T
{
n := OptNode.{ value = 10 };
print("opt field default: {}\n", n.next); // opt field default: null
m := OptNode.{ value = 20, next = 42 };
print("opt field set: {}\n", m.next); // opt field set: 42
}
// ?T as function parameter
{
opt_process :: (val: ?s32) -> s32 {
return val ?? 0;
}
a: ?s32 = 42;
b: ?s32 = null;
print("opt param a: {}\n", opt_process(a)); // opt param a: 42
print("opt param b: {}\n", opt_process(b)); // opt param b: 0
print("opt param 7: {}\n", opt_process(7)); // opt param 7: 7
}
// Assignment to optional variable (f32 → ?f32)
{
iw: ?f32 = null;
w: f32 = 42.5;
iw = w;
print("opt reassign: {}\n", iw ?? 0.0); // opt reassign: 42.5
// Assignment of computed value to optional
iw2: ?f32 = null;
a: ?f32 = 10.0;
if v := a { iw2 = v + 5.0; }
print("opt compute assign: {}\n", iw2 ?? 0.0); // opt compute assign: 15.0
// Re-assign optional back to null
iw2 = null;
print("opt re-null: {}\n", iw2 ?? 99.0); // opt re-null: 99.0
}
// Generic function with ?T return
{
first_pos :: ($T: Type, a: T, b: T) -> ?T {
if a > 0 { return a; }
if b > 0 { return b; }
return null;
}
print("generic opt 1: {}\n", first_pos(s32, 5, 10)); // generic opt 1: 5
print("generic opt 2: {}\n", first_pos(s32, 0, 7)); // generic opt 2: 7
print("generic opt 3: {}\n", first_pos(s32, 0, 0)); // generic opt 3: null
}
// Optional chaining (?.)
{
p: ?OptNode = OptNode.{ value = 10, next = 20 };
q: ?OptNode = null;
print("chain some: {}\n", p?.value ?? 0); // chain some: 10
print("chain none: {}\n", q?.value ?? 0); // chain none: 0
print("chain print: {}\n", p?.next); // chain print: 20
print("chain null: {}\n", q?.next); // chain null: null
// Chained: obj.field?.field
o1 := OptOuter.{ inner = OptInner.{ val = 99 } };
o2 := OptOuter.{ inner = null };
print("deep chain 1: {}\n", o1.inner?.val ?? 0); // deep chain 1: 99
print("deep chain 2: {}\n", o2.inner?.val ?? 0); // deep chain 2: 0
}
// Flow-sensitive narrowing
{
x: ?s32 = 42;
y: ?s32 = null;
// if x != null → x is narrowed to s32
if x != null {
print("narrow x: {}\n", x); // narrow x: 42
}
// if y != null → not entered
if y != null {
print("should not print\n");
} else {
print("narrow y else: null\n"); // narrow y else: null
}
// if x == null ... else → else-branch narrowed
if x == null {
print("should not print\n");
} else {
print("narrow else x: {}\n", x); // narrow else x: 42
}
}
// Guard narrowing
{
guard_fn :: (v: ?s32) -> s32 {
if v == null { return 0; }
return v;
}
print("guard some: {}\n", guard_fn(42)); // guard some: 42
print("guard none: {}\n", guard_fn(null)); // guard none: 0
}
// Compound narrowing: && chains
{
a: ?s32 = 10;
b: ?s32 = 20;
c: ?s32 = null;
if a != null and b != null {
print("and both: {} {}\n", a, b); // and both: 10 20
}
if a != null and c != null {
print("should not print\n");
} else {
print("and one null\n"); // and one null
}
}
// Compound guard narrowing: || chains
{
guard2 :: (a: ?s32, b: ?s32) -> s32 {
if a == null or b == null { return 0; }
return a + b;
}
print("or guard: {}\n", guard2(3, 4)); // or guard: 7
print("or guard null: {}\n", guard2(3, null)); // or guard null: 0
}
// Nested if narrowing
{
a: ?s32 = 10;
b: ?s32 = 20;
if a != null {
if b != null {
print("nested narrow: {} {}\n", a, b); // nested narrow: 10 20
}
}
}
// Guard narrowing used in loop
{
guard_loop :: (v: ?s32) -> s32 {
if v == null { return 0; }
sum := 0;
i := 0;
while i < v {
sum = sum + 1;
i = i + 1;
}
return sum;
}
print("guard loop: {}\n", guard_loop(3)); // guard loop: 3
}
// --- block-body lambdas ---
{
// block-body lambda with return type
clamp := (x: s64, lo: s64, hi: s64) -> s64 {
if x < lo { return lo; }
if x > hi { return hi; }
return x;
};
print("block-lambda: {}\n", clamp(50, 0, 100)); // block-lambda: 50
print("block-lambda: {}\n", clamp(-10, 0, 100)); // block-lambda: 0
print("block-lambda: {}\n", clamp(999, 0, 100)); // block-lambda: 100
// block-body lambda without return type annotation
greet := (name: string) {
print("hello {}\n", name);
};
greet("block"); // hello block
}
// --- named params in function types ---
{
// Named params are documentation only — ignored for type identity
apply_named :: (f: (x: s32, y: s32) -> s32, a: s32, b: s32) -> s32 {
return f(a, b);
}
add :: (a: s32, b: s32) -> s32 { return a + b; }
print("named-fn-type: {}\n", apply_named(add, 3, 4)); // named-fn-type: 7
}
// --- xx on function pointers ---
{
MyEnv :: struct { n: s32; }
typed_fn :: (e: *MyEnv, x: s32) -> s32 {
return x + e.n;
}
// xx cast: (*MyEnv, s32) -> s32 → (*void, s32) -> s32
f : (*void, s32) -> s32 = xx typed_fn;
env := MyEnv.{ n = 100 };
print("xx-fnptr: {}\n", f(xx @env, 42)); // xx-fnptr: 142
}
// --- closure type: construct and access fields ---
{
dummy_fn :: (env: *void, x: s32) -> s32 {
return x * 2;
}
fn_ptr : *void = xx dummy_fn;
null_env : *void = xx 0;
c : Closure(s32) -> s32 = .{ fn_ptr = fn_ptr, env = null_env };
print("closure-type: fn_ptr-nonnull={}\n", c.fn_ptr != null_env);
print("closure-type: env-null={}\n", c.env == null_env);
}
// --- closure calling convention ---
{
Env :: struct { n: s32; }
impl_fn :: (env: *void, x: s32) -> s32 {
e : *Env = xx env;
return x + e.n;
}
env := Env.{ n = 5 };
fn_ptr : *void = xx impl_fn;
env_ptr : *void = xx @env;
c : Closure(s32) -> s32 = .{ fn_ptr = fn_ptr, env = env_ptr };
print("closure-call: {}\n", c(10));
}
// --- auto-promotion: bare fn → Closure ---
{
double :: (x: s32) -> s32 { return x * 2; }
apply :: (f: Closure(s32) -> s32, x: s32) -> s32 { return f(x); }
print("auto-promote: {}\n", apply(double, 10));
// Named function to Closure variable
f : Closure(s32) -> s32 = double;
print("auto-promote-var: {}\n", f(5));
}
// --- closure() intrinsic ---
{
// capture scalar
n := 42;
f := closure((x: s32) => x + n);
print("closure-capture: {}\n", f(10));
// capture by value is a snapshot
m := 5;
g := closure((x: s32) => x + m);
m = 100;
print("closure-snapshot: {}\n", g(10));
// no captures (null env)
h := closure((x: s32) => x * 2);
print("closure-nocap: {}\n", h(7));
// multiple captures
a := 10;
b := 20;
multi := closure((x: s32) => x + a + b);
print("closure-multi: {}\n", multi(3));
// block-body closure with return
offset := 50;
clamp := closure((x: s64) -> s64 {
if x < 0 { return 0; }
if x > 100 { return 100; }
return x + offset;
});
r1 : s64 = clamp(10);
r2 : s64 = clamp(0 - 5);
r3 : s64 = clamp(999);
print("closure-block: {}\n", r1);
print("closure-block: {}\n", r2);
print("closure-block: {}\n", r3);
// void closure
tag := "LOG";
logger := closure((msg: string) {
print("[{}] {}\n", tag, msg);
});
logger("hello");
// pass closure to higher-order function
dbl :: (x: s32) -> s32 { return x * 2; }
apply_cl :: (f2: Closure(s32) -> s32, x: s32) -> s32 { return f2(x); }
factor : s32 = 3;
print("closure-hof: {}\n", apply_cl(closure((x: s32) -> s32 => x * factor), 10));
// auto-promoted bare fn passed alongside closures
print("closure-hof-bare: {}\n", apply_cl(dbl, 10));
// C5.A2: capture f32
scale := 2.5;
f_f32 := closure((x: f32) -> f32 => x * scale);
print("closure-f32: {}\n", f_f32(4.0));
// C5.A3: capture bool
verbose := true;
f_bool := closure((msg: string) {
if verbose { print("closure-bool: {}\n", msg); }
});
f_bool("hello");
// C5.B3: two params
base : s32 = 100;
f_2p := closure((x: s32, y: s32) -> s32 => x + y + base);
print("closure-2p: {}\n", f_2p(3, 4));
// C5.B4: three params
bias : s32 = 1;
f_3p := closure((a: s32, b: s32, c2: s32) -> s32 => a + b + c2 + bias);
print("closure-3p: {}\n", f_3p(10, 20, 30));
// C5.B5: mixed param types (string + s32)
extra : s32 = 5;
f_mix := closure((name: string, age: s32) {
print("closure-mix: {} is {}\n", name, age + extra);
});
f_mix("Alice", 30);
// C5.C3: return bool
threshold : s32 = 100;
f_rbool := closure((x: s32) -> bool { return x > threshold; });
print("closure-rbool: {} {}\n", f_rbool(50), f_rbool(200));
// C5.D3: reduce / fold
reduce :: (arr: []s32, f3: Closure(s32, s32) -> s32, init: s32) -> s32 {
acc := init;
i : s64 = 0;
while i < arr.len { acc = f3(acc, arr[i]); i += 1; }
return acc;
}
r_nums : []s32 = .[1, 2, 3, 4, 5];
r_bonus : s32 = 100;
r_total := reduce(r_nums, closure((acc: s32, x: s32) -> s32 => acc + x), r_bonus);
print("closure-reduce: {}\n", r_total);
// C5.G1: factory function
make_adder :: (n: s32) -> Closure(s32) -> s32 {
return closure((x: s32) -> s32 => x + n);
}
add5 := make_adder(5);
add10 := make_adder(10);
print("closure-factory: {} {}\n", add5(100), add10(100));
// C5.A5: capture struct
origin := Point.{ x = 10, y = 20 };
f_st := closure(() {
print("closure-struct: {} {}\n", origin.x, origin.y);
});
f_st();
// C5.H1: closure captures another closure
inner_n := 10;
inner_cl := closure((x: s64) -> s64 => x + inner_n);
outer_cl := closure((x: s64) -> s64 => inner_cl(x) * 2);
print("closure-compose: {}\n", outer_cl(5));
// C5.M7: multiple closures from same scope capture independently
shared : s32 = 10;
cl_a := closure((x: s32) -> s32 => x + shared);
cl_b := closure((x: s32) -> s32 => x * shared);
print("closure-indep: {} {}\n", cl_a(5), cl_b(5));
// C6: optional closures
f_none : ?Closure(s64) -> s64 = null;
if h := f_none {
print("should not print: {}\n", h(1));
} else {
print("opt-closure: none\n");
}
opt_n := 10;
f_some : ?Closure(s64) -> s64 = closure((x: s64) -> s64 => x + opt_n);
if h := f_some {
print("opt-closure: {}\n", h(5));
} else {
print("should not print\n");
}
// Struct with optional closure callback
Btn :: struct { label: string; on_click: ?Closure(s64) -> void; }
btn_x := 99;
btn_cl := closure((id: s64) {
print("opt-closure-btn: {} {}\n", id, btn_x);
});
btn1 := Btn.{ label = "OK", on_click = btn_cl };
btn2 := Btn.{ label = "Cancel", on_click = null };
if h := btn1.on_click { h(1); }
if h := btn2.on_click { h(2); } else { print("opt-closure-btn: null\n"); }
// C5.A6: capture pointer (shared mutable state)
count_a6 : s32 = 0;
p_a6 := @count_a6;
inc_fn := closure(() { p_a6.* += 1; });
inc_fn(); inc_fn(); inc_fn();
print("closure-ptr: {}\n", count_a6);
// C5.A9: capture enum value (as s32 tag)
c_a9 : s32 = 2; // simulate enum tag
f_a9 := closure(() -> s32 => c_a9);
print("closure-enum: {}\n", f_a9());
// C5.C4: return string
tag_c4 := "INFO";
f_c4 := closure((msg: string) -> string => format("[{}] {}", tag_c4, msg));
print("closure-rstr: {}\n", f_c4("ok"));
// C5.C5: return struct
off_c5 := Point.{ x = 10, y = 20 };
f_c5 := closure((p: Point) -> Point => Point.{ x = p.x + off_c5.x, y = p.y + off_c5.y });
res_c5 := f_c5(Point.{ x = 1, y = 2 });
print("closure-rstruct: {} {}\n", res_c5.x, res_c5.y);
// C5.G2: factory with multiple captures
make_linear :: (m: s32, b: s32) -> Closure(s32) -> s32 {
return closure((x: s32) -> s32 => m * x + b);
}
lin := make_linear(3, 7);
print("closure-linear: {}\n", lin(10));
// C5.G3: factory returning clamper
make_clamper :: (lo: s32, hi: s32) -> Closure(s32) -> s32 {
return closure((x: s32) -> s32 {
if x < lo { return lo; }
if x > hi { return hi; }
return x;
});
}
clamp_fn := make_clamper(0, 255);
cv1 : s32 = xx -10;
cv2 : s32 = 100;
cv3 : s32 = 999;
print("closure-clamp: {} {} {}\n", clamp_fn(cv1), clamp_fn(cv2), clamp_fn(cv3));
// C5.H2: compose
compose :: (f_h2: Closure(s32) -> s32, g_h2: Closure(s32) -> s32) -> Closure(s32) -> s32 {
return closure((x: s32) -> s32 => f_h2(g_h2(x)));
}
one_h2 : s32 = 1;
two_h2 : s32 = 2;
add1_h2 := closure((x: s32) -> s32 => x + one_h2);
mul2_h2 := closure((x: s32) -> s32 => x * two_h2);
composed := compose(mul2_h2, add1_h2);
print("closure-compose2: {}\n", composed(5));
// C5.H3: chain of closures
ch_k1 : s32 = 1;
ch_k2 : s32 = 2;
ch_k10 : s32 = 10;
ch_a := closure((x: s32) -> s32 => x + ch_k1);
ch_b := closure((x: s32) -> s32 => ch_a(x) * ch_k2);
ch_c := closure((x: s32) -> s32 => ch_b(x) + ch_k10);
print("closure-chain: {}\n", ch_c(5));
// C5.D1: map
map_cl :: (arr: [*]s32, cnt: s64, f_map: Closure(s32) -> s32, result: [*]s32) {
i := 0;
while i < cnt { result[i] = f_map(arr[i]); i += 1; }
}
map_src : [5]s32 = .[1, 2, 3, 4, 5];
map_dst : [5]s32 = .[0, 0, 0, 0, 0];
factor_d1 : s32 = 3;
map_cl(xx @map_src, 5, closure((x: s32) -> s32 => x * factor_d1), xx @map_dst);
print("closure-map: {} {} {} {} {}\n", map_dst[0], map_dst[1], map_dst[2], map_dst[3], map_dst[4]);
// C5.D2: filter
filter_cl :: (arr: [*]s32, cnt: s64, pred: Closure(s32) -> bool, result: [*]s32) -> s64 {
j := 0;
i := 0;
while i < cnt {
if pred(arr[i]) { result[j] = arr[i]; j += 1; }
i += 1;
}
return j;
}
min_val : s32 = 3;
filt_dst : [5]s32 = .[0, 0, 0, 0, 0];
kept := filter_cl(xx @map_src, 5, closure((x: s32) -> bool => x >= min_val), xx @filt_dst);
print("closure-filter: {} [{} {} {}]\n", kept, filt_dst[0], filt_dst[1], filt_dst[2]);
// C5.D4: sort comparator (bubble sort)
sort_cl :: (arr: [*]s32, cnt: s64, less: Closure(s32, s32) -> bool) {
i := 0;
while i < cnt {
j := 0;
while j < cnt - 1 - i {
if less(arr[j + 1], arr[j]) {
tmp := arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
j += 1;
}
i += 1;
}
}
sort_arr : [5]s32 = .[5, 3, 1, 4, 2];
descending := true;
sort_cl(xx @sort_arr, 5, closure((a: s32, b: s32) -> bool {
if descending { return a > b; }
return a < b;
}));
print("closure-sort: {} {} {} {} {}\n", sort_arr[0], sort_arr[1], sort_arr[2], sort_arr[3], sort_arr[4]);
// C5.D5: for_each with index
for_each_cl :: (arr: [*]s32, cnt: s64, f_fe: Closure(s32, s64) -> void) {
i : s64 = 0;
while i < cnt { f_fe(arr[i], i); i += 1; }
}
fe_label := "item";
fe_arr : [3]s32 = .[10, 20, 30];
for_each_cl(xx @fe_arr, 3, closure((val: s32, idx: s64) {
print("closure-fe: {} {}={}\n", fe_label, idx, val);
}));
// C5.D6: find
find_cl :: (arr: [*]s32, cnt: s64, pred_f: Closure(s32) -> bool) -> s64 {
i : s64 = 0;
while i < cnt {
if pred_f(arr[i]) { return i; }
i += 1;
}
return -1;
}
target : s32 = 30;
found_idx := find_cl(xx @fe_arr, 3, closure((x: s32) -> bool => x == target));
print("closure-find: {}\n", found_idx);
// C5.D7: any
any_cl :: (arr: [*]s32, cnt: s64, pred_a: Closure(s32) -> bool) -> bool {
i : s64 = 0;
while i < cnt {
if pred_a(arr[i]) { return true; }
i += 1;
}
return false;
}
has_big := any_cl(xx @fe_arr, 3, closure((x: s32) -> bool => x > 100));
has_20 := any_cl(xx @fe_arr, 3, closure((x: s32) -> bool => x == 20));
print("closure-any: {} {}\n", has_big, has_20);
// C5.E4: auto-promotion in struct field assignment
Widget :: struct { transform: Closure(s32) -> s32; }
negate_fn :: (x: s32) -> s32 { return 0 - x; }
w_e4 := Widget.{ transform = negate_fn };
print("closure-struct-field: {}\n", w_e4.transform(5));
// C5.F1: single closure callback in struct
Button :: struct {
label: string;
on_press: Closure(s32) -> void;
}
btn_x2 := 99;
btn_cb := closure((id: s32) {
print("closure-btn: {} {}\n", id, btn_x2);
});
btn3 := Button.{ label = "OK", on_press = btn_cb };
btn3.on_press(1);
// C5.J1: stateful counter via pointer capture
state_j1 : s32 = 0;
p_j1 := @state_j1;
inc_j1 := closure(() -> s32 { p_j1.* += 1; return p_j1.*; });
print("closure-counter: {} {} {}\n", inc_j1(), inc_j1(), inc_j1());
// C5.J2: stateful accumulator
state_j2 : s32 = 100;
p_j2 := @state_j2;
acc_j2 := closure((x: s32) -> s32 { p_j2.* += x; return p_j2.*; });
print("closure-acc: {} {}\n", acc_j2(5), acc_j2(10));
// C5.K2: block-body with local variables and loops
base_k2 : s32 = 100;
sum_fn := closure((items: [*]s32, cnt: s64) -> s32 {
total : s32 = 0;
i : s64 = 0;
while i < cnt {
total += items[i];
i += 1;
}
return total + base_k2;
});
k2_arr : [5]s32 = .[1, 2, 3, 4, 5];
print("closure-loop: {}\n", sum_fn(xx @k2_arr, 5));
// C5.M3: reassigning a closure variable
n_m3 : s32 = 1;
f_m3 := closure((x: s32) -> s32 => x + n_m3);
print("closure-reassign: {}\n", f_m3(10));
m_m3 : s32 = 2;
f_m3 = closure((x: s32) -> s32 => x * m_m3);
print("closure-reassign: {}\n", f_m3(10));
// C5.M6b: snapshot verified with struct capture
pt_m6 := Point.{ x = 5, y = 10 };
f_m6 := closure(() -> s32 => pt_m6.x + pt_m6.y);
pt_m6 = Point.{ x = 99, y = 99 };
print("closure-snapstruct: {}\n", f_m6());
// C5.M2: closure capturing auto-promoted closure
double_m2 :: (x: s32) -> s32 { return x * 2; }
base_m2 : Closure(s32) -> s32 = double_m2;
n_m2 : s32 = 1;
f_m2 := closure((x: s32) -> s32 => base_m2(x) + n_m2);
print("closure-cap-promoted: {}\n", f_m2(5));
// C5.M5: immediately invoked closure (via temp var)
n_m5 : s32 = 5;
iife := closure((x: s32) -> s32 => x + n_m5);
result_m5 := iife(10);
print("closure-iife: {}\n", result_m5);
// C5.F2: optional callback (none)
Toggle :: struct { on_change: ?Closure(bool) -> void; }
t_f2 := Toggle.{ on_change = null };
if h := t_f2.on_change { h(true); } else { print("closure-toggle: none\n"); }
// C5.F3: optional callback (some)
t_f3_cb := closure((enabled: bool) { print("closure-toggle: {}\n", enabled); });
t_f3 := Toggle.{ on_change = t_f3_cb };
if h := t_f3.on_change { h(true); }
// C5.F5: callback receiving caller context
Panel :: struct {
title: string;
on_resize: Closure(string, s32, s32) -> void;
}
p_f5_cb := closure((title: string, w: s32, h: s32) {
print("closure-panel: {} {}x{}\n", title, w, h);
});
p_f5 := Panel.{ title = "main", on_resize = p_f5_cb };
p_f5.on_resize(p_f5.title, 800, 600);
// C5.E6: protocol value passed through multiple function calls
step3 :: (a: Allocator) -> *void { a.alloc(8) }
step2 :: (a: Allocator) -> *void { step3(a) }
step1 :: (a: Allocator) -> *void { step2(a) }
gpa_e6 := GPA.init();
a_e6 : Allocator = xx gpa_e6;
ptr_e6 := step1(a_e6);
print("closure-chain-call: {}\n", ptr_e6 != null);
a_e6.dealloc(ptr_e6);
// C5.I1: creating closures in a loop (each captures different value)
// TEMPORARILY DISABLED — closure-in-loop causes infinite loop (index_gep element size issue?)
// cl_arr : [5]Closure(s32) -> s32 = ---;
// i_loop := 0;
// while i_loop < 5 {
// val_loop : s32 = xx (i_loop * 10);
// cl_arr[i_loop] = closure((x: s32) -> s32 => x + val_loop);
// i_loop += 1;
// }
// I2: calling closures from array
// tmp_cl := cl_arr[0]; print("closure-loop-0: {}\n", tmp_cl(1));
// tmp_cl = cl_arr[1]; print("closure-loop-1: {}\n", tmp_cl(1));
// tmp_cl = cl_arr[4]; print("closure-loop-4: {}\n", tmp_cl(1));
// C5.M4: closure in conditional expression (via temp var)
use_fast := true;
k_fast : s32 = 2;
k_slow : s32 = 10;
f_fast := closure((x: s32) -> s32 => x * k_fast);
f_slow := closure((x: s32) -> s32 => x + k_slow);
f_cond : Closure(s32) -> s32 = if use_fast then f_fast else f_slow;
print("closure-cond: {}\n", f_cond(5));
// C5.F4: multiple callbacks on one struct
Form :: struct {
on_submit: ?Closure() -> void;
on_cancel: ?Closure() -> void;
}
msg_f4 := "submitted";
sub_cb := closure(() { print("closure-form: {}\n", msg_f4); });
form_f4 := Form.{ on_submit = sub_cb, on_cancel = null };
if h := form_f4.on_submit { h(); }
if h := form_f4.on_cancel { h(); } else { print("closure-form: no cancel\n"); }
// C5.L3: auto-promoted closure env is null (no free needed)
double_l3 :: (x: s32) -> s32 { return x * 2; }
f_l3 : Closure(s32) -> s32 = double_l3;
print("closure-null-env: {}\n", f_l3.env == null);
// C5.A7: capture slice (fat pointer like string)
sl_a7 : [3]s32 = .[10, 20, 30];
ptr_a7 : [*]s32 = xx @sl_a7;
f_a7 := closure((i: s64) -> s32 => ptr_a7[i]);
print("closure-slice: {} {} {}\n", f_a7(0), f_a7(1), f_a7(2));
// C5.L1: arena bulk free (closures allocated on arena, freed in bulk)
gpa_l1 := GPA.init();
arena_l1 := Arena.init(xx gpa_l1, 4096);
push Context.{ allocator = xx arena_l1 } {
n_l1 : s32 = 5;
f_l1 := closure((x: s32) -> s32 => x + n_l1);
print("closure-arena: {}\n", f_l1(10));
}
arena_l1.deinit();
// C5.L2: GPA manual free (verify env alloc/dealloc)
gpa_l2 := GPA.init();
a_l2 : Allocator = xx gpa_l2;
n_l2 : s32 = 7;
result_l2 : s32 = 0;
push Context.{ allocator = a_l2 } {
f_l2 := closure((x: s32) -> s32 => x + n_l2);
result_l2 = f_l2(10);
a_l2.dealloc(f_l2.env);
}
print("closure-gpa: {} allocs={}\n", result_l2, gpa_l2.alloc_count);
// C5.A10: capture optional
val_a10 : ?s32 = 42;
f_a10 := closure(() -> s32 {
if v := val_a10 { return v; }
return 0;
});
print("closure-opt: {}\n", f_a10());
// C5.C6: return optional
limit_c6 : s32 = 100;
f_c6 := closure((x: s32) -> ?s32 {
if x > limit_c6 { return null; }
return x;
});
r1_c6 := f_c6(50);
r2_c6 := f_c6(200);
if v := r1_c6 { print("closure-ropt: {}\n", v); }
if v := r2_c6 { print("should-not-print\n"); } else { print("closure-ropt: none\n"); }
// C5.M8: array of closures with mixed origins
double_m8 :: (x: s32) -> s32 { return x * 2; }
n_m8 : s32 = 10;
fns_m8 : [3]Closure(s32) -> s32 = ---;
fns_m8[0] = double_m8; // auto-promoted
fns_m8[1] = closure((x: s32) -> s32 => x + n_m8); // captured
fns_m8[2] = closure((x: s32) -> s32 => x * x); // no capture
tmp_m8 := fns_m8[0]; print("closure-mixed: {}\n", tmp_m8(5));
tmp_m8 = fns_m8[1]; print("closure-mixed: {}\n", tmp_m8(5));
tmp_m8 = fns_m8[2]; print("closure-mixed: {}\n", tmp_m8(5));
// C5.E1: independent closures from same factory (each has own env)
mk_e1 :: (n: s32) -> Closure(s32) -> s32 {
return closure((x: s32) -> s32 => x * n);
}
f1_e1 := mk_e1(2);
f2_e1 := mk_e1(3);
f3_e1 := mk_e1(4);
print("closure-factory-indep: {} {} {}\n", f1_e1(10), f2_e1(10), f3_e1(10));
// C5.E2: deep chain — closure capturing closure capturing closure
v_e2 : s32 = 1;
k2_e2 : s32 = 2;
k100_e2 : s32 = 100;
f0_e2 := closure((x: s32) -> s32 => x + v_e2);
f1_e2 := closure((x: s32) -> s32 => f0_e2(x) * k2_e2);
f2_e2 := closure((x: s32) -> s32 => f1_e2(x) + k100_e2);
print("closure-deep-chain: {}\n", f2_e2(10));
// C5.E3: many captures (stress env struct)
c1_e3 : s32 = 1;
c2_e3 : s32 = 2;
c3_e3 : s32 = 3;
c4_e3 : s32 = 4;
c5_e3 : s32 = 5;
c6_e3 : s32 = 6;
c7_e3 : s32 = 7;
c8_e3 : s32 = 8;
big_env := closure(() -> s32 => c1_e3 + c2_e3 + c3_e3 + c4_e3 + c5_e3 + c6_e3 + c7_e3 + c8_e3);
print("closure-8cap: {}\n", big_env());
// C5.E5: closure with many parameters (4 params)
multi_param := closure((a: s32, b: s32, c: s32, d: s32) -> s32 => a + b + c + d);
a_e5 : s32 = 1; b_e5 : s32 = 2; c_e5 : s32 = 3; d_e5 : s32 = 4;
print("closure-4param: {}\n", multi_param(a_e5, b_e5, c_e5, d_e5));
// C5.E7: two closures sharing the same captured pointer
shared : s32 = 0;
shared_p := @shared;
inc_shared := closure(() { shared_p.* += 1; });
add5_shared := closure(() { shared_p.* += 5; });
inc_shared();
add5_shared();
inc_shared();
print("closure-shared-ptr: {}\n", shared);
// C5.E8: closure with f64 arithmetic
pi_e8 : f64 = 3.14159;
area_fn := closure((r: f64) -> f64 => pi_e8 * r * r);
a_e8 := area_fn(10.0);
print("closure-f64: {}\n", a_e8 > 314.0);
// C5.E9: zero-capture closure (env should be null, like auto-promoted)
no_cap := closure((x: s32) -> s32 => x * x);
print("closure-zerocap: {} {}\n", no_cap(7), no_cap.env == null);
// C5.E10: closure capturing and calling struct method
pt_e10 := Point.{ x = 3, y = 4 };
p_e10 := @pt_e10;
get_xy := closure(() -> s32 => p_e10.x + p_e10.y);
print("closure-struct-method: {}\n", get_xy());
// C5.E11: multiple closures from same factory with different captures
fns_e11 : [3]Closure(s32) -> s32 = ---;
i_e11 := 0;
while i_e11 < 3 {
multiplier : s32 = xx (i_e11 + 1);
fns_e11[i_e11] = closure((x: s32) -> s32 => x * multiplier);
i_e11 += 1;
}
t_e11 := fns_e11[0]; print("closure-multi-factory: {}\n", t_e11(10));
t_e11 = fns_e11[1]; print("closure-multi-factory: {}\n", t_e11(10));
t_e11 = fns_e11[2]; print("closure-multi-factory: {}\n", t_e11(10));
// C5.E12: closure capturing bool
flag_e12 := true;
check_fn := closure((x: s32) -> bool {
if flag_e12 { return x > 0; }
return x < 0;
});
pos_e12 : s32 = 5;
neg_e12 : s32 = xx -3;
print("closure-bool-cap: {} {}\n", check_fn(pos_e12), check_fn(neg_e12));
// C5.E13: closure as argument to another closure
apply_fn := closure((f_app: Closure(s32) -> s32, val: s32) -> s32 => f_app(val));
k_e13 : s32 = 100;
inner_fn := closure((x: s32) -> s32 => x + k_e13);
print("closure-as-arg: {}\n", apply_fn(inner_fn, 42));
// C5.E14: closure capturing string and formatting
prefix_e14 := "hello";
greet_fn := closure((name: string) -> string => format("{} {}", prefix_e14, name));
print("closure-strfmt: {}\n", greet_fn("world"));
// C5.E15: reassigning shared pointer target between closure calls
val_e15 : s32 = 10;
p_e15 := @val_e15;
read_fn := closure(() -> s32 => p_e15.*);
print("closure-ptr-before: {}\n", read_fn());
val_e15 = 42;
print("closure-ptr-after: {}\n", read_fn());
// C5.E16: closure returning negative value
off_e16 : s32 = 100;
neg_fn := closure((x: s32) -> s32 => x - off_e16);
val_e16 : s32 = 30;
print("closure-neg: {}\n", neg_fn(val_e16));
// C5.E17: closure with protocol value capture (#inline protocol)
gpa_e17 := GPA.init();
a_e17 : Allocator = xx gpa_e17;
alloc_fn := closure((size: s64) -> *void => a_e17.alloc(size));
ptr_e17 := alloc_fn(32);
print("closure-proto-cap: {}\n", ptr_e17 != null);
a_e17.dealloc(ptr_e17);
// C5.E18: chained factory — compose two factories
make_scaler :: (factor: s32) -> Closure(s32) -> s32 {
return closure((x: s32) -> s32 => x * factor);
}
make_offset :: (off: s32) -> Closure(s32) -> s32 {
return closure((x: s32) -> s32 => x + off);
}
s_fn := make_scaler(3);
o_fn := make_offset(7);
// manually compose: scale then offset
print("closure-chain-factory: {}\n", o_fn(s_fn(10)));
// C5.E19: closure in while loop condition helper
threshold : s32 = 50;
above_fn := closure((x: s32) -> bool => x >= threshold);
vals_e19 : [5]s32 = .[10, 30, 50, 70, 90];
count_above : s32 = 0;
idx_e19 : s64 = 0;
while idx_e19 < 5 {
if above_fn(vals_e19[idx_e19]) { count_above += 1; }
idx_e19 += 1;
}
print("closure-while-cond: {}\n", count_above);
// ---- Inferred closure parameter types ----
// CI.1: inferred params from typed variable
f_ci1 : Closure(s32, s32) -> s32 = closure((a, b) => a + b);
a_ci1 : s32 = 3;
b_ci1 : s32 = 4;
print("closure-infer: {}\n", f_ci1(a_ci1, b_ci1));
// CI.2: inferred params from function argument
apply_ci :: (f: Closure(s32) -> s32, x: s32) -> s32 { return f(x); }
k_ci : s32 = 10;
v_ci : s32 = 5;
print("closure-infer-arg: {}\n", apply_ci(closure((x) => x + k_ci), v_ci));
// CI.3: inferred with block body
h_ci : Closure(s32, s32) -> s32 = closure((a, b) { return a * b; });
print("closure-infer-block: {}\n", h_ci(a_ci1, b_ci1));
// CI.4: inferred with captures
cap_ci : s32 = 100;
f_ci4 : Closure(s32) -> s32 = closure((x) => x + cap_ci);
print("closure-infer-cap: {}\n", f_ci4(v_ci));
// CI.5: inferred in factory return
mk_ci :: (n: s32) -> Closure(s32) -> s32 { return closure((x) => x * n); }
f_ci5 := mk_ci(7);
print("closure-infer-factory: {}\n", f_ci5(v_ci));
// CI.6: inferred with higher-order (closure taking closure)
compose_ci :: (f: Closure(s32) -> s32, g: Closure(s32) -> s32) -> Closure(s32) -> s32 {
return closure((x: s32) -> s32 => f(g(x)));
}
one_ci : s32 = 1;
two_ci : s32 = 2;
c_ci := compose_ci(closure((x) => x + one_ci), closure((x) => x * two_ci));
print("closure-infer-compose: {}\n", c_ci(v_ci));
// CI.7: inferred void return
msg_ci := "infer-void";
cb_ci : Closure(s32) -> void = closure((x) { print("closure-{}: {}\n", msg_ci, x); });
cb_ci(42);
}
}