Files
sx/examples/0415-protocols-protocols.sx
agra 116af2359e lang: multi-iterable for loops — drop ':', add '..=', open ranges, arrow bodies
The for header is now a comma-separated list of iterables with a
positional capture group and no ':' separator:

    for xs (x) { }                    // collection
    for 0..n (i) { }                  // range (end exclusive)
    for 1..=5 (a) { }                 // ..= inclusive end
    for xs, 0.. (x, i) { }            // index idiom (replaces (x, i))
    for xs, ys (x, y) { }             // parallel (zip) iteration
    for xs (x) => sum += x;           // arrow body (full statement)

First-iterable-wins: the first iterable's length drives the loop and
must be bounded; the other positions follow by their own cursors (a
non-first range's end is not consulted or evaluated; a shorter
non-first collection is read past its length on mismatch). The old
single-iterable index capture is replaced by the trailing open range.

Capture/call disambiguation is positional: the paren group immediately
before '{' or '=>' is the capture, every earlier top-level group is a
call. 'for zip(a, b) (x, y)' calls zip; 'for f(n) { }' reads (n) as
the capture and errors with a parenthesize/add-capture hint. The old
':' form errors with a migration hint.

Lowering is unified across forms: one cursor slot per position (ranges
start at their start, collections at 0), all advanced together, the
first position's bound terminating. inline for keeps the single
bounded comptime range.

Migrated the full corpus (examples, library modules, issue repros,
in-source test strings). New coverage: examples/0050 (the full feature
surface) and examples/1149-1155 (seven diagnostic faces). specs.md For
Loop section + grammar rewritten; readme teaser updated.
2026-06-10 20:30:55 +03:00

618 lines
14 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; }
Color :: enum { red; green; blue; }
Shape :: enum {
circle: f32;
rect: struct { w, h: f32; };
none;
}
Overlay :: union {
f: f32;
i: s32;
}
Vec2 :: union {
data: [2]f32;
struct { x, y: f32; };
}
Defaults :: struct {
a: s32;
b: s32 = 99;
c: s32 = ---;
}
OptNode :: struct {
value: s32;
next: ?s32;
}
OptInner :: struct { val: s32; }
OptOuter :: struct { inner: ?OptInner; }
MyFloat :: f64;
Perms :: enum flags { read; write; execute; }
Status :: enum u8 { ok; err; timeout; }
WindowFlags :: enum flags u32 { vsync :: 64; resizable :: 4; hidden :: 128; }
// --- Top-level functions ---
add :: (a: s32, b: s32) -> s32 { a + b }
mul :: (a: s32, b: s32) -> s32 { a * b }
identity :: (x: $T) -> T { x }
pair_add :: (a: $T, b: $U) -> s64 {
cast(s64) a + cast(s64) b
}
typed_sum :: (..args: []s32) -> s32 {
result := 0;
for args (it) { result = result + it; }
result
}
apply :: (f: (s32, s32) -> s32, x: s32, y: s32) -> s32 {
f(x, y)
}
void_return :: () {
return;
}
implicit_return :: (x: s32) -> s32 {
x * 2
}
early_return :: (x: s32) -> s32 {
if x > 10 { return 99; }
x
}
vec3 :: (x: f32, y: f32, z: f32) -> Vector(3, f32) {
.[x, y, z]
}
point_sum :: (p: Point) -> s32 { p.x + p.y }
// #run compile-time constants
// #run compile-time constants
CT_VAL :: #run add(10, 15);
CT_MUL :: #run mul(6, 7);
CT_CHAIN :: #run add(CT_VAL, 5);
// #run compile-time optional tests
// #run compile-time optional tests
ct_opt_coalesce :: () -> s32 {
x: ?s32 = 42;
y: ?s32 = null;
return (x ?? 0) + (y ?? 99);
}
ct_opt_unwrap :: () -> s32 {
x: ?s32 = 77;
return x!;
}
ct_opt_guard :: () -> s32 {
x: ?s32 = 10;
if x == null { return -1; }
return x;
}
CT_OPT_COALESCE :: #run ct_opt_coalesce();
CT_OPT_UNWRAP :: #run ct_opt_unwrap();
CT_OPT_GUARD :: #run ct_opt_guard();
// #insert helpers
// #insert helpers
gen_code :: () -> string {
return "print(\"insert-ok\\n\");";
}
gen_val :: () -> string {
return "print(\"insert-gen: {}\\n\", 42);";
}
// --- Error handling (failable functions: sets, raise/try/catch/or/onfail) ---
SmokeErr :: error { Empty, BadDigit, Overflow }
// value-carrying, named set: raise three tags or succeed
// value-carrying, named set: raise three tags or succeed
sm_parse :: (n: s32) -> (s32, !SmokeErr) {
if n < 0 { raise error.BadDigit; }
if n == 0 { raise error.Empty; }
if n > 99 { raise error.Overflow; }
return n * 2;
}
// pure failable, inferred set (ad-hoc tag minted into `!`)
// pure failable, inferred set (ad-hoc tag minted into `!`)
sm_check :: (ok: bool) -> ! {
if !ok { raise error.NotReady; }
return;
}
// multi-value, inferred set: `try` propagates; the SCC pass absorbs SmokeErr
// multi-value, inferred set: `try` propagates; the SCC pass absorbs SmokeErr
sm_pair :: (a: s32, b: s32) -> (s32, s32, !) {
x := try sm_parse(a);
y := try sm_parse(b);
return (x, y);
}
// `catch` block that diverges (logs the tag, then returns a fallback)
// `catch` block that diverges (logs the tag, then returns a fallback)
sm_or_default :: (n: s32) -> s32 {
return sm_parse(n) catch e {
print(" logged {}\n", e);
return -1;
};
}
// `onfail` + `defer` interleave: cleanup runs only on the error path
// `onfail` + `defer` interleave: cleanup runs only on the error path
sm_acquire :: (fail: bool) -> (s32, !) {
defer print(" smoke defer A\n");
onfail print(" smoke onfail B\n");
if fail { raise error.Acquire; }
return 7;
}
// `or`-chain: try a, fall to try b; propagate if both fail
// `or`-chain: try a, fall to try b; propagate if both fail
sm_first :: (a: s32, b: s32) -> (s32, !) {
v := try sm_parse(a) or try sm_parse(b);
return v;
}
// --- Foreign function binding ---
// --- Foreign function binding ---
libc :: #library "c";
c_abs :: (n: s32) -> s32 #foreign libc "abs";
// --- Protocol declarations (Phase 1: static dispatch only) ---
Counter :: protocol {
inc :: ();
get :: () -> s32;
}
Summable :: protocol {
sum :: () -> s32;
}
SimpleCounter :: struct { val: s32; }
impl Counter for SimpleCounter {
inc :: (self: *SimpleCounter) { self.val += 1; }
get :: (self: *SimpleCounter) -> s32 { self.val }
}
impl Summable for Point {
sum :: (self: *Point) -> s32 { self.x + self.y }
}
// Phase 2: #inline protocol for dynamic dispatch
// Phase 2: #inline protocol for dynamic dispatch
Adder :: protocol #inline {
add :: (n: s32);
value :: () -> s32;
}
Accumulator :: struct {
total: s32;
}
impl Adder for Accumulator {
add :: (self: *Accumulator, n: s32) { self.total += n; }
value :: (self: *Accumulator) -> s32 { self.total }
}
Doubler :: struct { val: s32; }
impl Adder for Doubler {
add :: (self: *Doubler, n: s32) { self.val = self.val + n + n; }
value :: (self: *Doubler) -> s32 { self.val }
}
// Phase 4: default methods
// Phase 4: default methods
Repeater :: protocol {
say :: (msg: string);
say_twice :: (msg: string) {
self.say(msg);
self.say(msg);
}
}
Printer :: struct { count: s32; }
impl Repeater for Printer {
say :: (self: *Printer, msg: string) {
self.count += 1;
out(msg);
}
}
// P4 edge: Chained default→default calls
// 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)
}
}
ChainImpl :: struct { val: s32; }
impl Chained for ChainImpl {
base :: (self: *ChainImpl, msg: string) -> s32 {
self.val += 1;
msg.len
}
}
// Phase 5: Self type
// Phase 5: Self type
Eq :: protocol {
eq :: (other: Self) -> bool;
}
impl Eq for Point {
eq :: (self: *Point, other: Point) -> bool {
self.x == other.x and self.y == other.y
}
}
Cloneable :: protocol {
clone :: () -> Self;
}
impl Cloneable for Point {
clone :: (self: *Point) -> Point {
Point.{ x = self.x, y = self.y }
}
}
impl Eq for s64 {
eq :: (self: *s64, other: s64) -> bool {
self.* == other
}
}
// Phase 6: Generic constraints
// Phase 6: Generic constraints
are_equal :: ($T: Type/Eq, a: T, b: T) -> bool {
a.eq(b)
}
Hashable :: protocol {
hash :: () -> s64;
}
impl Hashable for Point {
hash :: (self: *Point) -> s64 {
xx self.x * 31 + xx self.y
}
}
eq_and_hash :: ($T: Type/Eq/Hashable, a: T, b: T) -> bool {
if a.hash() != b.hash() { return false; }
a.eq(b)
}
// P6.4: inline constraint syntax ($T/Protocol)
// P6.4: inline constraint syntax ($T/Protocol)
sum_of_inline :: (a: $T/Summable, b: T) -> s32 {
a.sum() + b.sum()
}
// Phase 7: Generic struct impls
// Phase 7: Generic struct impls
Pair :: struct ($T: Type) {
a: T;
b: T;
}
impl Summable for Pair($T) {
sum :: (self: *Pair(T)) -> s32 {
xx self.a + xx self.b
}
}
// P6.5: Struct type param constraints
// P6.5: Struct type param constraints
SumBox :: struct ($T: Type/Summable) {
val: T;
}
// ============================================================
// Struct constants test
// ============================================================
// Struct constants test
Phys :: struct {
x, y: f32;
GRAVITY :f32: 9.81;
MAX_SPEED :: 100;
}
// Init block test struct
// Init block test struct
Builder :: struct {
total: s32;
count: s32;
add :: (self: *Builder, val: s32) {
self.total += val;
self.count += 1;
}
}
// Global variable for address-of test
// Global variable for address-of test
g_smoke_val : s32 = 42;
write_to_ptr :: (p: *s32) {
p.* = 99;
}
main :: () {
// ========================================================
// PROTOCOLS (Phase 1: static dispatch)
// ========================================================
print("=== Protocols ===\n");
// P1.1: Basic protocol + impl, direct call on concrete type
{
sc := SimpleCounter.{ val = 0 };
sc.inc();
sc.inc();
sc.inc();
print("P1.1: {}\n", sc.get());
}
// P1.2: impl in separate scope (retroactive conformance)
{
p := Point.{ x = 10, y = 20 };
print("P1.2: {}\n", p.sum());
}
// P2.1: #inline protocol — xx conversion + dynamic dispatch
{
acc := Accumulator.{ total = 0 };
a : Adder = xx @acc;
a.add(10);
a.add(20);
a.add(12);
print("P2.1: {}\n", a.value());
}
// P2.2: pass protocol value to function
{
use_adder :: (a: Adder, n: s32) -> s32 {
a.add(n);
a.value()
}
acc := Accumulator.{ total = 100 };
result := use_adder(xx @acc, 50);
print("P2.2: {}\n", result);
}
// P2.3: different impls through same protocol type
{
acc := Accumulator.{ total = 0 };
dbl := Doubler.{ val = 0 };
a1 : Adder = xx @acc;
a2 : Adder = xx @dbl;
a1.add(5);
a2.add(5);
print("P2.3: {} {}\n", a1.value(), a2.value());
}
// P3.1: vtable-pointer protocol (default, no #inline)
{
sc := SimpleCounter.{ val = 0 };
c : Counter = xx @sc;
c.inc();
c.inc();
c.inc();
c.inc();
c.inc();
print("P3.1: {}\n", c.get());
}
// P3.2: vtable protocol passed to function
{
use_counter :: (c: Counter) -> s32 {
c.inc();
c.inc();
c.get()
}
sc := SimpleCounter.{ val = 10 };
result := use_counter(xx @sc);
print("P3.2: {}\n", result);
}
// P4.1: default method calls required method (static dispatch)
{
pr := Printer.{ count = 0 };
pr.say_twice("hi ");
print("\nP4.1: {}\n", pr.count);
}
// P4.2: default method via dynamic dispatch (vtable)
{
pr := Printer.{ count = 0 };
r : Repeater = xx @pr;
r.say_twice("yo ");
print("\nP4.2: {}\n", pr.count);
}
// P4.3: chained default→default calls via vtable
{
ci := ChainImpl.{ val = 0 };
ch : Chained = xx @ci;
// double_wrap calls wrap twice, wrap calls base once each
// base("hi") returns 2 (len), wrap adds 1 → 3, double_wrap = 3 + 3 = 6
result := ch.double_wrap("hi");
// base was called 2 times (once per wrap call)
print("P4.3: {} {}\n", result, ci.val);
}
// P5.1: Self type in protocol — static dispatch
{
p1 := Point.{ x = 1, y = 2 };
p2 := Point.{ x = 1, y = 2 };
p3 := Point.{ x = 3, y = 4 };
print("P5.1: {} {}\n", p1.eq(p2), p1.eq(p3));
}
// P5.2: Self in return position
{
p := Point.{ x = 10, y = 20 };
p2 := p.clone();
print("P5.2: {} {}\n", p2.x, p2.y);
}
// P5.5: impl for primitive type
{
x := 42;
y := 42;
z := 99;
r1 := x.eq(y);
r2 := x.eq(z);
print("P5.5: {} {}\n", r1, r2);
}
// P5.3: Self with dynamic dispatch (erased to *void)
{
p1 := Point.{ x = 1, y = 2 };
p2 := Point.{ x = 1, y = 2 };
p3 := Point.{ x = 3, y = 4 };
e : Eq = xx p1;
print("P5.3: {} {}\n", e.eq(p2), e.eq(p3));
}
// P6.1: Single constraint — constrained generic function
{
p1 := Point.{ x = 1, y = 2 };
p2 := Point.{ x = 1, y = 2 };
p3 := Point.{ x = 3, y = 4 };
print("P6.1: {} {}\n", are_equal(p1, p2), are_equal(p1, p3));
}
// P6.2: Constraint with primitive type
{
print("P6.2: {} {}\n", are_equal(42, 42), are_equal(42, 99));
}
// P6.3: Multiple constraints
{
p1 := Point.{ x = 1, y = 2 };
p2 := Point.{ x = 1, y = 2 };
p3 := Point.{ x = 3, y = 4 };
print("P6.3: {} {}\n", eq_and_hash(p1, p2), eq_and_hash(p1, p3));
}
// P6.4: inline constraint syntax ($T/Protocol)
{
// sum_of_inline uses $T/Summable inline (not $T: Type/Summable)
p1 := Point.{ x = 10, y = 20 };
p2 := Point.{ x = 3, y = 7 };
print("P6.4: {}\n", sum_of_inline(p1, p2));
}
// P6.5: Struct type param constraints ($T: Type/Summable)
{
box := SumBox(Point).{ val = Point.{ x = 5, y = 15 } };
print("P6.5: {}\n", box.val.sum());
}
// P7.1: impl for generic struct
{
p := Pair(s32).{ a = 10, b = 20 };
print("P7.1: {}\n", p.sum());
}
// P7.2: generic struct impl with different type arg
{
p1 := Pair(s32).{ a = 3, b = 7 };
p2 := Pair(s64).{ a = 100, b = 200 };
print("P7.2: {} {}\n", p1.sum(), p2.sum());
}
// P2.4: xx in function return position (tested in standalone test_return.sx)
// Covered by: make_adder :: (acc: *Accumulator) -> Adder { xx acc; }
// P2.6: protocol values in arrays
{
acc := Accumulator.{ total = 0 };
dbl := Doubler.{ val = 0 };
adders : [2]Adder = .[xx @acc, xx @dbl];
i := 0;
while i < 2 {
adders[i].add(5);
i += 1;
}
print("P2.6: {} {}\n", acc.total, dbl.val);
}
// P2.7: xx on inline struct literal (no intermediate variable)
{
use_adder :: (a: Adder) -> s32 { a.add(10); a.value() }
result := use_adder(xx Accumulator.{ total = 5 });
print("P2.7: {}\n", result);
}
// P3.3: xx on inline struct literal with vtable protocol
{
use_counter :: (c: Counter) -> s32 { c.inc(); c.inc(); c.get() }
result := use_counter(xx SimpleCounter.{ val = 100 });
print("P3.3: {}\n", result);
}
}