test: group examples into per-category folders
Move examples/*.sx and their expected/ snapshots into per-category subfolders (examples/<category>/...). Folder = leading filename token, with ffi-objc/ffi-jni kept whole; filenames are unchanged. The corpus runner and LSP sweep now discover each category's expected/ dir, while issues/ stays flat. Example 1058's repo-root-relative companion import is made file-relative. Path strings embedded in 164 snapshots were regenerated (path-only changes). Test-layout docs in CLAUDE.md updated.
This commit is contained in:
9
examples/closures/0300-closures-lambda.sx
Normal file
9
examples/closures/0300-closures-lambda.sx
Normal file
@@ -0,0 +1,9 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
main :: () {
|
||||
fx :: (s:i3) -> i3 {
|
||||
s
|
||||
}
|
||||
|
||||
print("{}\n", fx(-3));
|
||||
}
|
||||
28
examples/closures/0301-closures-fn-pointers.sx
Normal file
28
examples/closures/0301-closures-fn-pointers.sx
Normal file
@@ -0,0 +1,28 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
add :: (a: i32, b: i32) -> i32 { a + b }
|
||||
mul :: (a: i32, b: i32) -> i32 { a * b }
|
||||
|
||||
apply :: (f: (i32, i32) -> i32, x: i32, y: i32) -> i32 {
|
||||
f(x, y)
|
||||
}
|
||||
|
||||
main :: () {
|
||||
// Store function in variable
|
||||
fp : (i32, i32) -> i32 = add;
|
||||
print("fp(3,4) = {}\n", fp(3, 4));
|
||||
|
||||
// Reassign to different function
|
||||
fp = mul;
|
||||
print("fp(3,4) = {}\n", fp(3, 4));
|
||||
|
||||
// Pass function pointer as argument
|
||||
print("apply(add,5,6) = {}\n", apply(add, 5, 6));
|
||||
print("apply(mul,5,6) = {}\n", apply(mul, 5, 6));
|
||||
}
|
||||
|
||||
// ** stdout **
|
||||
//fp(3,4) = 7
|
||||
//fp(3,4) = 12
|
||||
//apply(add,5,6) = 11
|
||||
//apply(mul,5,6) = 30
|
||||
96
examples/closures/0302-closures-closures.sx
Normal file
96
examples/closures/0302-closures-closures.sx
Normal file
@@ -0,0 +1,96 @@
|
||||
#import "modules/std.sx";
|
||||
|
||||
// --- Closure Basics ---
|
||||
|
||||
// Factory: returns a new closure each time
|
||||
make_adder :: (n: i64) -> Closure(i64) -> i64 {
|
||||
return closure((x: i64) -> i64 => x + n);
|
||||
}
|
||||
|
||||
// Higher-order function: accepts any Closure(i64) -> i64
|
||||
apply :: (f: Closure(i64) -> i64, x: i64) -> i64 { return f(x); }
|
||||
|
||||
// Reduce: fold over a slice with a closure
|
||||
reduce :: (arr: []i64, f: Closure(i64, i64) -> i64, init: i64) -> i64 {
|
||||
acc := init;
|
||||
i : i64 = 0;
|
||||
while i < arr.len { acc = f(acc, arr[i]); i += 1; }
|
||||
return acc;
|
||||
}
|
||||
|
||||
// Auto-promoted bare function
|
||||
triple :: (x: i64) -> i64 { return x * 3; }
|
||||
|
||||
// Struct with optional closure callback
|
||||
Widget :: struct {
|
||||
name: string;
|
||||
on_update: ?Closure(i64) -> void;
|
||||
}
|
||||
|
||||
main :: () {
|
||||
// 1. Basic closure with capture
|
||||
offset := 100;
|
||||
add_offset := closure((x: i64) -> i64 => x + offset);
|
||||
|
||||
print("basic: {}\n", add_offset(42));
|
||||
|
||||
// 2. Capture by value (snapshot semantics)
|
||||
n := 10;
|
||||
snap := closure((x: i64) -> i64 => x + n);
|
||||
n = 999;
|
||||
print("snapshot: {}\n", snap(5));
|
||||
|
||||
// 3. Block-body closure with control flow
|
||||
clamp := closure((x: i64) -> i64 {
|
||||
if x < 0 { return 0; }
|
||||
if x > 100 { return 100; }
|
||||
return x;
|
||||
});
|
||||
print("clamp: {} {} {}\n", clamp(50), clamp(0 - 10), clamp(200));
|
||||
|
||||
// 4. Void closure with string capture
|
||||
tag := "INFO";
|
||||
logger := closure((msg: string) {
|
||||
print("[{}] {}\n", tag, msg);
|
||||
});
|
||||
logger("system ready");
|
||||
|
||||
// 5. Factory pattern
|
||||
add5 := make_adder(5);
|
||||
add10 := make_adder(10);
|
||||
print("factory: {} {}\n", add5(100), add10(100));
|
||||
|
||||
// 6. Auto-promotion: bare fn passed where Closure expected
|
||||
print("auto-promote: {}\n", apply(triple, 7));
|
||||
|
||||
// 7. Closure passed to higher-order function
|
||||
factor := 4;
|
||||
print("hof: {}\n", apply(closure((x: i64) -> i64 => x * factor), 10));
|
||||
|
||||
// 8. Reduce with closure
|
||||
nums : []i64 = .[1, 2, 3, 4, 5];
|
||||
total := reduce(nums, closure((acc: i64, x: i64) -> i64 => acc + x), 0);
|
||||
print("reduce: {}\n", total);
|
||||
|
||||
// 9. Closure captures closure
|
||||
inner := closure((x: i64) -> i64 => x + 10);
|
||||
outer := closure((x: i64) -> i64 => inner(x) * 2);
|
||||
print("compose: {}\n", outer(5));
|
||||
|
||||
// 10. Multiple closures from same scope
|
||||
base := 100;
|
||||
cl_add := closure((x: i64) -> i64 => x + base);
|
||||
cl_mul := closure((x: i64) -> i64 => x * base);
|
||||
print("multi: {} {}\n", cl_add(5), cl_mul(5));
|
||||
|
||||
// 11. Optional closures
|
||||
w1 := Widget.{ name = "slider", on_update = closure((val: i64) {
|
||||
print("widget: {} = {}\n", "slider", val);
|
||||
}) };
|
||||
w2 := Widget.{ name = "label", on_update = null };
|
||||
|
||||
if h := w1.on_update { h(42); }
|
||||
if h := w2.on_update { h(0); } else { print("widget: no handler\n"); }
|
||||
|
||||
print("=== DONE ===\n");
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Closure whose return type is a (non-`#inline`) protocol value — exercises
|
||||
// the indirect-call path where the result is a boxed protocol.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
MyProtocol :: protocol {
|
||||
get_value :: (self: *Self) -> i64;
|
||||
}
|
||||
|
||||
MyImpl :: struct { value: i64; }
|
||||
impl MyProtocol for MyImpl {
|
||||
get_value :: (self: *MyImpl) -> i64 { self.value }
|
||||
}
|
||||
|
||||
make_thing :: () -> MyProtocol {
|
||||
MyImpl.{ value = 42 }
|
||||
}
|
||||
|
||||
main :: () -> void {
|
||||
// Direct call works:
|
||||
v := make_thing();
|
||||
out("direct call works\n");
|
||||
|
||||
// Closure call crashes:
|
||||
c := closure(make_thing);
|
||||
result := c();
|
||||
out("closure call works\n");
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// A closure stored in a struct field receives sub-32-bit enum args
|
||||
// with the right tag, same as direct or protocol-dispatched calls.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Fmt :: enum { a; b; }
|
||||
|
||||
Ctx :: struct {
|
||||
on: Closure(Fmt) -> void;
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
c : Ctx = .{ on = (f: Fmt) => {
|
||||
n : i64 = xx f;
|
||||
print("cl f = {}\n", n);
|
||||
}};
|
||||
c.on(.b);
|
||||
c.on(.a);
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Invoking a Closure-typed struct field as `self.field()` from a
|
||||
// method whose receiver is `*Self`. The field access must auto-deref
|
||||
// the pointer before extracting the closure value.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Holder :: struct {
|
||||
cb: Closure() = ---;
|
||||
has: bool = false;
|
||||
|
||||
set :: (self: *Holder, fn: Closure()) {
|
||||
self.cb = fn;
|
||||
self.has = true;
|
||||
}
|
||||
|
||||
// Direct invocation through *self.
|
||||
call_direct :: (self: *Holder) {
|
||||
if self.has == false { return; }
|
||||
self.cb();
|
||||
}
|
||||
|
||||
// Hoist-then-call form — must agree with the direct form.
|
||||
call_hoisted :: (self: *Holder) {
|
||||
if self.has == false { return; }
|
||||
fn := self.cb;
|
||||
fn();
|
||||
}
|
||||
}
|
||||
|
||||
ticks : i32 = 0;
|
||||
|
||||
main :: () -> i32 {
|
||||
h : Holder = .{};
|
||||
h.set(() => { ticks += 1; });
|
||||
|
||||
h.call_direct();
|
||||
h.call_hoisted();
|
||||
|
||||
return ticks;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Phase 1.3 — the closure env-buffer heap-copy in `lowerLambda` must
|
||||
// dispatch through `context.allocator`, not `.heap_alloc` directly.
|
||||
// So when a `push Context.{ allocator = tracer }` block is active, a
|
||||
// capturing closure created inside it MUST allocate its env through
|
||||
// the tracker.
|
||||
//
|
||||
// Mirrors the shape of `130-xx-value-routes-through-context-allocator.sx`
|
||||
// for the protocol-erasure heap path — same Tracer, same install via
|
||||
// `push Context`, same `Tracer.count = 1` assertion. Different
|
||||
// allocation site (closure env vs xx-value heap copy).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Tracer :: struct {
|
||||
count: i64;
|
||||
|
||||
init :: () -> *Tracer {
|
||||
t : *Tracer = xx libc_malloc(size_of(Tracer));
|
||||
t.count = 0;
|
||||
t
|
||||
}
|
||||
}
|
||||
|
||||
impl Allocator for Tracer {
|
||||
alloc_bytes :: (self: *Tracer, size: i64) -> *void {
|
||||
self.count += 1;
|
||||
return libc_malloc(size);
|
||||
}
|
||||
dealloc_bytes :: (self: *Tracer, ptr: *void) {
|
||||
libc_free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
tracer := Tracer.init();
|
||||
push Context.{ allocator = xx tracer, data = null } {
|
||||
// Capturing closure. lowerLambda allocates an env struct on the
|
||||
// stack, copies the captures in, then heap-copies the env via
|
||||
// `allocViaContext` — which dispatches through the installed
|
||||
// tracer's `alloc`.
|
||||
captured : i64 = 100;
|
||||
add_capture := closure((y: i64) -> i64 => y + captured);
|
||||
_ = add_capture(1);
|
||||
}
|
||||
print("Tracer.count = {}\n", tracer.count);
|
||||
0
|
||||
}
|
||||
28
examples/closures/0307-closures-closure-contextual-params.sx
Normal file
28
examples/closures/0307-closures-closure-contextual-params.sx
Normal file
@@ -0,0 +1,28 @@
|
||||
// Step 2.5 — contextual typing for closure literals with N (heterogeneous)
|
||||
// params. An untyped lambda `(a, b, c) => ...` takes each param's type
|
||||
// positionally from the expected `Closure(T0, T1, T2) -> R` signature, in both
|
||||
// assignment and argument position. (Previously only the first param — or
|
||||
// all-same-typed params — resolved; trailing params silently defaulted to i64.)
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
// argument-position: lambda typed from the parameter's closure type.
|
||||
apply2 :: (f: Closure(i64, string) -> i64, x: i64, s: string) -> i64 {
|
||||
return f(x, s);
|
||||
}
|
||||
apply3 :: (f: Closure(i64, i64, string) -> i64, a: i64, b: i64, c: string) -> i64 {
|
||||
return f(a, b, c);
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
// assignment-position, mixed (i64, string) params — `b` is string.
|
||||
cb : Closure(i64, string) -> i64 = (a, b) => a + b.len;
|
||||
print("cb={}\n", cb(10, "hello")); // 10 + 5 = 15
|
||||
|
||||
// argument-position, 2 params.
|
||||
print("r={}\n", apply2((a, b) => a + b.len, 10, "hello")); // 15
|
||||
|
||||
// argument-position, 3 params (i64, i64, string).
|
||||
print("q={}\n", apply3((a, b, c) => a + b + c.len, 1, 2, "xyz")); // 1+2+3 = 6
|
||||
0
|
||||
}
|
||||
24
examples/closures/0308-closures-arrow-inferred-return.sx
Normal file
24
examples/closures/0308-closures-arrow-inferred-return.sx
Normal file
@@ -0,0 +1,24 @@
|
||||
// Regression (issue 0059): a function with NO explicit return type infers it
|
||||
// from the body, which references the function's own parameters. The inference
|
||||
// must see those params — before the fix they weren't in scope during
|
||||
// return-type resolution, so the inferred type came out `.unresolved` and tripped
|
||||
// the LLVM-emission guard ("unresolved type reached LLVM emission"). Whether it
|
||||
// slipped through used to depend on a same-named binding lingering from earlier
|
||||
// lowering. Covers the arrow (`=>`) and inferred-via-`return` forms, at top level
|
||||
// and as locals.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
dbl :: (x: i32) => x * 2; // top-level arrow, inferred return
|
||||
inc :: (x: i32) { return x + 1; } // top-level block, inferred via `return`
|
||||
|
||||
main :: () {
|
||||
print("{}\n", dbl(7)); // 14
|
||||
print("{}\n", inc(41)); // 42
|
||||
|
||||
tripl :: (x: i32) => x * 3; // local arrow, inferred return
|
||||
print("{}\n", tripl(4)); // 12
|
||||
|
||||
half :: (x: f32) => x / 2.0; // inferred float return
|
||||
print("{}\n", half(9.0)); // 4.500000
|
||||
}
|
||||
17
examples/closures/0309-closures-literal-as-bare-fn-param.sx
Normal file
17
examples/closures/0309-closures-literal-as-bare-fn-param.sx
Normal file
@@ -0,0 +1,17 @@
|
||||
// Regression (issue 0060): a closure LITERAL passed directly as a bare
|
||||
// function-type argument `(T) -> U` and then called inside the callee. The
|
||||
// closure's underlying function takes a hidden env arg that a bare fn-ptr slot
|
||||
// doesn't pass, so the compiler bridges a capture-free closure to the bare ABI
|
||||
// with a generated adapter. Both block and arrow bodies. (The working contrast
|
||||
// where the param is a `Closure(...)` type is examples/0302.)
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
apply :: (f: (i64) -> i64) -> i64 { return f(5); }
|
||||
twice :: (f: (i64) -> i64, x: i64) -> i64 { return f(f(x)); }
|
||||
|
||||
main :: () {
|
||||
print("block={}\n", apply(closure((x: i64) -> i64 { return x * 2; }))); // 10
|
||||
print("arrow={}\n", apply(closure((x: i64) -> i64 => x * 2))); // 10
|
||||
print("twice={}\n", twice(closure((x: i64) -> i64 => x + 3), 1)); // ((1+3)+3) = 7
|
||||
}
|
||||
22
examples/closures/0310-closures-closure-literal-in-defer.sx
Normal file
22
examples/closures/0310-closures-closure-literal-in-defer.sx
Normal file
@@ -0,0 +1,22 @@
|
||||
// Closure literal declared (and used) inside a `defer` body.
|
||||
//
|
||||
// Regression (issue 0073): this used to segfault lowering. A lambda inherited
|
||||
// the enclosing function's `func_defer_base`, so the lambda's `return` re-drained
|
||||
// the enclosing function's defers — and when the defer body itself declared the
|
||||
// lambda, that re-lowered the lambda forever (infinite recursion). A lambda now
|
||||
// opens its own defer window (like every other function-lowering entry).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
run :: () {
|
||||
defer {
|
||||
cb := (n: i32) -> i32 { return n * 2; };
|
||||
print("defer closure: {}\n", cb(21)); // 42, at scope exit
|
||||
}
|
||||
print("body\n");
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
run();
|
||||
return 0;
|
||||
}
|
||||
1
examples/closures/expected/0300-closures-lambda.exit
Normal file
1
examples/closures/expected/0300-closures-lambda.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
examples/closures/expected/0300-closures-lambda.stderr
Normal file
1
examples/closures/expected/0300-closures-lambda.stderr
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
examples/closures/expected/0300-closures-lambda.stdout
Normal file
1
examples/closures/expected/0300-closures-lambda.stdout
Normal file
@@ -0,0 +1 @@
|
||||
-3
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
16110
examples/closures/expected/0301-closures-fn-pointers.ir
Normal file
16110
examples/closures/expected/0301-closures-fn-pointers.ir
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
fp(3,4) = 7
|
||||
fp(3,4) = 12
|
||||
apply(add,5,6) = 11
|
||||
apply(mul,5,6) = 30
|
||||
1
examples/closures/expected/0302-closures-closures.exit
Normal file
1
examples/closures/expected/0302-closures-closures.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
1
examples/closures/expected/0302-closures-closures.stderr
Normal file
1
examples/closures/expected/0302-closures-closures.stderr
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
13
examples/closures/expected/0302-closures-closures.stdout
Normal file
13
examples/closures/expected/0302-closures-closures.stdout
Normal file
@@ -0,0 +1,13 @@
|
||||
basic: 142
|
||||
snapshot: 15
|
||||
clamp: 50 0 100
|
||||
[INFO] system ready
|
||||
factory: 105 110
|
||||
auto-promote: 21
|
||||
hof: 40
|
||||
reduce: 15
|
||||
compose: 30
|
||||
multi: 105 500
|
||||
widget: slider = 42
|
||||
widget: no handler
|
||||
=== DONE ===
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
direct call works
|
||||
closure call works
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
cl f = 1
|
||||
cl f = 0
|
||||
@@ -0,0 +1 @@
|
||||
2
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Tracer.count = 1
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
cb=15
|
||||
r=15
|
||||
q=6
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
14
|
||||
42
|
||||
12
|
||||
4.500000
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
block=10
|
||||
arrow=10
|
||||
twice=7
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
body
|
||||
defer closure: 42
|
||||
Reference in New Issue
Block a user