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:
agra
2026-06-21 14:41:34 +03:00
parent 6d1409bc1f
commit 66bdc70bf1
3357 changed files with 456 additions and 363 deletions

View File

@@ -0,0 +1,5 @@
#import "modules/std.sx";
main :: () -> i32 {
if false then 40 else 42
}

View File

@@ -0,0 +1,4 @@
#import "modules/std.sx";
main :: () {
print("Hello\n");
}

View File

@@ -0,0 +1,21 @@
#import "modules/std.sx";
main :: () -> i32 {
x := 42;
{
print("scope opened\n");
defer print("scope closed\n");
// define a inner variable x shadowing the one define in the outer scope(s)
x:= 6;
print("scoped x: {}\n", x); //expect 6
}
print("main x: {}\n", x); //expect 42
0
}
// ** stdout **
// scope opened
// scoped x: 6
// scope closed
// main x: 42
//

View File

@@ -0,0 +1,11 @@
#import "modules/std.sx";
main :: () -> i32 {
defer print("still here\n");
return 42;
}
// ** exit code **
// 42
// ** stdout **
// still here
//

View File

@@ -0,0 +1,9 @@
#import "modules/std.sx";
generate::() -> string {
return "print(\"hello from the other side\n\");";
}
main :: () {
#insert generate();
}

View File

@@ -0,0 +1,15 @@
std :: #import "modules/std.sx";
vec3 :: (x:f32, y:f32, z:f32) -> std.Vector(3, f32) {
.[x,y,z]
}
main :: () {
v1 := vec3(1,0,0);
v2 := vec3(0,0,1);
s := 0.5;
sum := (v1 - v2);// math.cross(v1, v2);
std.print("{}\n", sum);
}

View File

@@ -0,0 +1,50 @@
#import "modules/std.sx";
sumOf10 :: () -> i32 {
i:= 1;
s:=0;
while i <= 10 {
s+=i;
i+=1;
}
s
}
someSum :: #run sumOf10();
main :: () {
// Basic while loop: count to 5
i := 0;
while i < 5 {
i += 1;
}
print("count: {}\n", i);
// While with break
x := 1;
while x < 100 {
if x == 12 {
break;
}
x += 1;
}
print("break at: {}\n", x);
// While with continue: sum odd numbers 1-9
sum := 0;
j := 0;
while j < 10 {
j += 1;
// Skip even numbers
if j == 2 { continue; }
if j == 4 { continue; }
if j == 6 { continue; }
if j == 8 { continue; }
if j == 10 { continue; }
sum += j;
}
print("sum of odd 1-9: {}\n", sum);
print("sum {}", someSum);
}

View File

@@ -0,0 +1,18 @@
#import "modules/std.sx";
main :: () {
x:= 32;
y:= 40;
if 0 <= x <= 100 and 0 <= y <= 100 {
print("contained");
}
if 1000 > x > -100 and 0 <= y <= 100 {
print("contained");
}
if 1000 > x >= -100 and 0 <= y <= 100 {
print("contained");
}
}

View File

@@ -0,0 +1,35 @@
#import "modules/std.sx";
quick_sort :: (items: []$T) {
partition :: (items: []T, lo: i64, hi: i64) -> i64 {
pivot := items[hi];
i := lo - 1;
j := lo;
while j < hi {
if items[j] < pivot {
i += 1;
items[i], items[j] = items[j], items[i];
}
j += 1;
}
i += 1;
items[i], items[hi] = items[hi], items[i];
i
}
sort :: (items: []T, lo: i64, hi: i64) {
if lo < hi {
pi := partition(items, lo, hi);
sort(items, lo, pi - 1);
sort(items, pi + 1, hi);
}
}
sort(items, 0, items.len - 1);
}
main :: () {
arr : []i64 = .[333, 2, 3, 5, 2, 2, 3, 4, 5, 6, 6, 1];
quick_sort(arr);
print("{}\n", arr);
}

View File

@@ -0,0 +1,295 @@
#import "modules/std.sx";
// ============================================================
// Dot-shorthand tests: .identifier(args) unification
// Tests both tagged enum backward compat and struct static methods
// ============================================================
// --- Type declarations ---
Color :: enum { red; green; blue; }
Shape :: enum {
circle: f32;
rect: struct { w, h: f32; };
none;
}
Vec2 :: struct {
x: f32;
y: f32;
create :: (x: f32, y: f32) -> Vec2 { Vec2.{ x = x, y = y } }
zero :: () -> Vec2 { Vec2.{ x = 0.0, y = 0.0 } }
unit_x :: () -> Vec2 { Vec2.{ x = 1.0, y = 0.0 } }
add :: (a: Vec2, b: Vec2) -> Vec2 { Vec2.{ x = a.x + b.x, y = a.y + b.y } }
scale :: (v: Vec2, s: f32) -> Vec2 { Vec2.{ x = v.x * s, y = v.y * s } }
len :: (v: Vec2) -> i32 { xx (v.x + v.y) }
}
EdgeInsets :: struct {
top: f32;
right: f32;
bottom: f32;
left: f32;
all :: (v: f32) -> EdgeInsets { EdgeInsets.{ top = v, right = v, bottom = v, left = v } }
symmetric :: (h: f32, v: f32) -> EdgeInsets { EdgeInsets.{ top = v, right = h, bottom = v, left = h } }
horizontal :: (h: f32) -> EdgeInsets { EdgeInsets.{ top = 0.0, right = h, bottom = 0.0, left = h } }
}
Trio :: struct {
a: i32;
b: i32;
c: i32;
make :: (a: i32, b: i32, c: i32) -> Trio { Trio.{ a = a, b = b, c = c } }
sum :: (t: Trio) -> i32 { t.a + t.b + t.c }
}
Result :: enum {
ok: i32;
err: string;
}
main :: () {
// ============================================================
// SECTION 1: Tagged enum backward compatibility
// ============================================================
print("--- tagged enum compat ---\n");
// T1: .variant(payload) in typed variable declaration
{
sh : Shape = .circle(3.14);
print("T1: {}\n", sh.circle);
}
// T2: Bare .variant (no payload) in typed variable
{
sh : Shape = .none;
ms := if sh == {
case .circle: 1;
case .rect: 2;
case .none: 3;
};
print("T2: {}\n", ms);
}
// T3: .variant with struct payload
{
sh : Shape = .rect(.{ 5.0, 3.0 });
print("T3: {} {}\n", sh.rect.w, sh.rect.h);
}
// T4: Qualified Type.variant(payload) still works
{
sh := Shape.circle(2.71);
print("T4: {}\n", sh.circle);
}
// T5: Match with payload capture
{
sh : Shape = .circle(9.5);
if sh == {
case .circle: (r) { print("T5: {}\n", r); }
case .rect: (sz) { print("T5: rect\n"); }
case .none: print("T5: none\n");
}
}
// T6: Return .variant(payload) from function
{
make_shape :: (r: f32) -> Shape { .circle(r) }
sh := make_shape(4.2);
print("T6: {}\n", sh.circle);
}
// T7: Reassignment with .variant(payload) and bare .variant
{
sh : Shape = .circle(1.0);
print("T7a: {}\n", sh.circle);
sh = .rect(.{ 2.0, 3.0 });
print("T7b: {} {}\n", sh.rect.w, sh.rect.h);
sh = .none;
ms := if sh == {
case .circle: 1;
case .rect: 2;
case .none: 3;
};
print("T7c: {}\n", ms);
}
// T8: .variant(payload) as function argument (match-as-expression)
{
describe :: (sh: Shape) -> i32 {
if sh == {
case .circle: 10;
case .rect: 20;
case .none: 30;
}
}
print("T8a: {}\n", describe(.circle(7.0)));
print("T8b: {}\n", describe(.rect(.{ 3.0, 4.0 })));
print("T8c: {}\n", describe(.none));
}
// T9: Tagged enum with string payload
{
r : Result = .ok(42);
ms := if r == {
case .ok: (v) { v }
case .err: (e) { -1 }
};
print("T9: {}\n", ms);
}
// T10: Match as expression returning tagged enum
{
select :: (n: i32) -> Shape {
if n == {
case 0: .none;
case 1: .circle(1.0);
else: .rect(.{ 9.0, 9.0 });
}
}
print("T10a: {}\n", if select(0) == { case .none: 1; else: 0; });
print("T10b: {}\n", select(1).circle);
print("T10c: {}\n", select(2).rect.w);
}
// ============================================================
// SECTION 2: Struct static method shorthand
// ============================================================
print("--- struct static shorthand ---\n");
// S1: .method(args) as function argument (the motivating use case)
{
print_vec :: (v: Vec2) { print("S1: {} {}\n", v.x, v.y); }
print_vec(.create(3.0, 4.0));
}
// S2: .method(args) in typed variable declaration
{
v : Vec2 = .create(5.0, 6.0);
print("S2: {} {}\n", v.x, v.y);
}
// S3: Return .method(args) from function with return type
{
make_vec :: () -> Vec2 { .create(7.0, 8.0) }
v := make_vec();
print("S3: {} {}\n", v.x, v.y);
}
// S4: Zero-arg static method (factory)
{
print_vec :: (v: Vec2) { print("S4: {} {}\n", v.x, v.y); }
print_vec(.zero());
print_vec(.unit_x());
}
// S5: Three-arg static method (proves multi-arg works)
{
print_trio :: (t: Trio) { print("S5: {}\n", t.a + t.b + t.c); }
print_trio(.make(10, 20, 30));
}
// S6: Two-arg shorthand matching the EdgeInsets use case
{
apply_insets :: (ei: EdgeInsets) { print("S6: {} {} {} {}\n", ei.top, ei.right, ei.bottom, ei.left); }
apply_insets(.all(8.0));
apply_insets(.symmetric(16.0, 8.0));
apply_insets(.horizontal(12.0));
}
// S7: Result of .method() used in further computation
{
v : Vec2 = .create(3.0, 4.0);
print("S7: {}\n", Vec2.len(v));
}
// S8: Chained qualified + shorthand — ensure both work together
{
v1 := Vec2.create(1.0, 2.0);
print_vec :: (v: Vec2) { print("S8: {} {}\n", v.x, v.y); }
print_vec(.create(3.0, 4.0));
print("S8q: {} {}\n", v1.x, v1.y);
}
// S9: Static method taking struct of same type as args
{
v : Vec2 = .add(.create(1.0, 2.0), .create(3.0, 4.0));
print("S9: {} {}\n", v.x, v.y);
}
// S10: Static method + piped result
{
v := Vec2.create(2.0, 3.0) |> Vec2.scale(2.0);
print("S10: {} {}\n", v.x, v.y);
}
// ============================================================
// SECTION 3: Edge cases mixing both
// ============================================================
print("--- edge cases ---\n");
// E1: Both tagged enum and struct shorthand in same scope
{
sh : Shape = .circle(5.0);
v : Vec2 = .create(1.0, 2.0);
print("E1: {} {} {}\n", sh.circle, v.x, v.y);
}
// E2: Function taking both types — each resolves correctly
{
use_both :: (sh: Shape, v: Vec2) {
ms : i32 = 0;
if sh == { case .circle: (r) { ms = xx r; } else: {} }
print("E2: {} {} {}\n", ms, v.x, v.y);
}
use_both(.circle(9.0), .create(1.0, 2.0));
}
// E3: Bare .variant (no parens) as function arg
{
check_none :: (sh: Shape) -> i32 {
if sh == { case .none: 1; else: 0; }
}
print("E3: {}\n", check_none(.none));
}
// E4: Nested shorthand — .method takes a struct param created with shorthand
// (inner .create must resolve via the method's parameter type, not the outer type)
{
v : Vec2 = .add(Vec2.create(1.0, 2.0), Vec2.create(3.0, 4.0));
print("E4: {} {}\n", v.x, v.y);
}
// E5: Tagged enum .variant(payload) in match-as-expression
{
sh : Shape = .circle(42.0);
r : i32 = 0;
if sh == {
case .circle: (v) { r = xx v; }
case .rect: (sz) { r = xx sz.w; }
case .none: r = xx -1;
}
print("E5: {}\n", r);
}
// E6: Color enum (plain, not tagged) still works with bare .variant
{
c : Color = .green;
ci : i32 = xx c;
print("E6: {}\n", ci);
}
// E7: Struct shorthand in typed variable, then pass to function
{
ei : EdgeInsets = .symmetric(10.0, 20.0);
show :: (e: EdgeInsets) { print("E7: {} {}\n", e.top, e.left); }
show(ei);
}
print("=== DONE ===\n");
}

View File

@@ -0,0 +1,16 @@
// `inline if COND { return E; }` followed by a fall-through final expression:
// when COND evaluates true at comptime, the body is spliced unconditionally
// and the trailing expression must NOT also be emitted into the same LLVM
// block (only one terminator per block).
#import "modules/std.sx";
#import "modules/build.sx";
do_it :: () -> bool {
inline if OS != .ios { return false; }
true
}
main :: () -> i32 {
if do_it() then 0 else 1
}

View File

@@ -0,0 +1,36 @@
// M1.0 — expression-bodied function declarations.
//
// sx's `=>` body form (already used for lambdas) extends to
// top-level and struct-member function declarations:
//
// name :: (params) -> RetType => expr;
//
// Pins three positions: module-top, struct method, niladic.
#import "modules/std.sx";
double :: (x: i32) -> i32 => x * 2;
sum :: (a: i32, b: i32) -> i32 => a + b;
answer :: () -> i32 => 42;
Point :: struct {
x: i32;
y: i32;
total :: (self: *Point) -> i32 => self.x + self.y;
scaled :: (self: *Point, by: i32) -> i32 => (self.x + self.y) * by;
}
main :: () -> i32 {
print("double: {}\n", double(7));
print("sum: {}\n", sum(3, 4));
print("answer: {}\n", answer());
p := Point.{ x = 10, y = 20 };
print("total: {}\n", p.total());
print("scaled: {}\n", p.scaled(3));
0
}

View File

@@ -0,0 +1,39 @@
// Range-based for loops: `for start..end (i) { }` (cursor optional, `end`
// exclusive) is a runtime counting loop; `inline for start..end (i) { }`
// is comptime-unrolled — the cursor is a compile-time constant each
// iteration, so `xs[i]` over a heterogeneous pack substitutes the concrete
// per-position element (this is what drives pack iteration).
#import "modules/std.sx";
Show :: protocol {
show :: (self: *Self) -> string;
}
A :: struct { x: i64; }
B :: struct { s: string; }
impl Show for A { show :: (self: *A) -> string => "A"; }
impl Show for B { show :: (self: *B) -> string => "B"; }
// Comptime-unrolled iteration over a pack; cursor `i` indexes the pack.
each :: (..xs: Show) -> void {
inline for 0..xs.len (i) {
print("[{}]={}\n", i, xs[i].show());
}
}
main :: () -> i32 {
// Runtime range, cursor used.
for 0..3 (i) { print("i={}\n", i); }
// Runtime range, no cursor — body runs `end - start` times.
n := 0;
for 0..5 { n = n + 1; }
print("n={}\n", n);
// Non-zero start.
for 2..5 (j) { print("j={}\n", j); }
// Inline unroll over a heterogeneous pack.
each(A.{ x = 1 }, B.{ s = "hi" }, A.{ x = 3 });
0
}

View File

@@ -0,0 +1,25 @@
// `for xs (*x)` binds each element by pointer — no per-element copy.
// Mutations write back, and a pointer subject matches through the deref.
#import "modules/std.sx";
Shape :: enum {
circle: f32;
none;
}
main :: () -> i32 {
// By-ref mutation writes back into the array (impossible with a value copy).
xs : [3]i64 = .[1, 2, 3];
for xs (*x) { x.* = x + 100; }
print("{} {} {}\n", xs[0], xs[1], xs[2]);
// Pointer subject matches through the deref; payload reads through the ref.
shapes : [2]Shape = .[.circle(2.0), .none];
for shapes (*s) {
if s == {
case .circle: (r) { print("circle {}\n", r); }
case .none: { print("none\n"); }
}
}
0
}

View File

@@ -0,0 +1,40 @@
// `for` over a `List(T)` (a `{ items: [*]T, len, cap }` struct): value capture,
// by-ref capture (mutates in place), iterating a `*List`, and a by-ref capture
// used as a value-receiver method call (auto-deref).
#import "modules/std.sx";
Box :: struct {
v: i64;
boxed :: (self: Box) -> i64 { self.v } // value receiver
}
sum_ptr :: (xs: *List(i64)) -> i64 {
total : i64 = 0;
for xs (n) { total = total + n; } // iterate through a *List
total
}
main :: () -> i32 {
xs := List(i64).{};
xs.append(10);
xs.append(20);
xs.append(30);
s : i64 = 0;
for xs (n) { s = s + n; } // value capture
print("sum {}\n", s); // 60
for xs (*n) { n.* = n + 100; } // by-ref: writes back
s = 0;
for xs (n) { s = s + n; }
print("sum2 {}\n", s); // 360
print("via ptr {}\n", sum_ptr(@xs)); // 360
bs := List(Box).{};
bs.append(.{ v = 7 });
bt : i64 = 0;
for bs (*b) { bt = bt + b.boxed(); } // *Box receiver, value-self method
print("boxes {}\n", bt); // 7
0
}

View File

@@ -0,0 +1,67 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
Color :: enum { red; green; blue; }
main :: () {
// ========================================================
// 1. LITERALS
// ========================================================
print("=== 1. Literals ===\n");
// Integer literals
print("decimal: {}\n", 42);
print("hex: {}\n", 0xFF);
print("binary: {}\n", 0b1010);
// Float literal
pi := 3.14;
print("float: {}\n", pi);
// Explicit f64
big : f64 = 2.718281828;
print("f64: {}\n", big);
// Boolean literals
print("true: {}\n", true);
print("false: {}\n", false);
// String with escapes
print("escapes: hello\tworld\n");
// Multi-line string
ml := "line1
line2";
print("multiline: {}\n", ml);
// Heredoc string
hd := #string END
raw heredoc
END;
print("heredoc: {}\n", hd);
// Undefined with type
undef_val : i32 = ---;
undef_val = 77;
print("undef-then-set: {}\n", undef_val);
// Enum literal (context-inferred)
c : Color = .green;
print("enum-lit: {}\n", c);
// Null pointer
np : *i32 = null;
print("null-ptr: {}\n", np);
// String .len
slen := "hello";
print("string-len: {}\n", slen.len);
// Empty string .len
es := "";
print("empty-string: {}\n", es.len);
}

View File

@@ -0,0 +1,166 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
add :: (a: i32, b: i32) -> i32 { a + b }
mul :: (a: i32, b: i32) -> i32 { a * b }
// P4 edge: Chained default→default calls
Chained :: protocol {
base :: (self: *Self, msg: string) -> i32;
wrap :: (self: *Self, msg: string) -> i32 {
self.base(msg) + 1
}
double_wrap :: (self: *Self, msg: string) -> i32 {
self.wrap(msg) + self.wrap(msg)
}
}
main :: () {
// ========================================================
// 2. OPERATORS & PRECEDENCE
// ========================================================
print("=== 2. Operators ===\n");
// Arithmetic
print("add: {}\n", 3 + 4);
print("sub: {}\n", 10 - 3);
print("mul: {}\n", 6 * 7);
print("div: {}\n", 20 / 4);
print("mod: {}\n", 17 % 5);
print("neg: {}\n", -(5));
// Comparisons
print("eq: {}\n", 5 == 5);
print("neq: {}\n", 5 != 3);
print("lt: {}\n", 3 < 5);
print("gt: {}\n", 5 > 3);
print("le: {}\n", 5 <= 5);
print("ge: {}\n", 5 >= 3);
// Chained comparisons
v := 50;
print("chain: {}\n", 0 <= v <= 100);
print("chain-gt: {}\n", 100 > v > 0);
print("chain-mixed: {}\n", 100 > v >= 0);
// Equality chains
print("eq-chain: {}\n", 5 == 5 == 5);
print("eq-chain-f: {}\n", 5 == 5 == 6);
// Bitwise
print("band: {}\n", 0xFF & 0x0F);
print("bor: {}\n", 1 | 2 | 4);
// Bitwise XOR
print("bxor: {}\n", 0xFF ^ 0x0F);
print("bxor2: {}\n", 6 ^ 3);
// Bitwise NOT
print("bnot: {}\n", ~0);
print("bnot2: {}\n", ~1);
// Shifts
print("shl: {}\n", 1 << 4);
print("shr: {}\n", 256 >> 4);
print("shl2: {}\n", 3 << 3);
print("shr2: {}\n", 255 >> 1);
// Bitwise on variables
bv1 := 0xFF;
bv2 := 0x0F;
print("band-var: {}\n", bv1 & bv2);
bv3 := 1;
bv4 := 6;
print("bor-var: {}\n", bv3 | bv4);
print("bxor-var: {}\n", bv1 ^ bv2);
print("shl-var: {}\n", bv3 << 4);
print("shr-var: {}\n", bv1 >> 4);
print("bnot-var: {}\n", ~bv2);
// Bitwise compound assignment
bca := 0xFF;
bca &= 0x0F;
print("and-assign: {}\n", bca);
bco := 0x0F;
bco |= 0xF0;
print("or-assign: {}\n", bco);
bcx := 0xFF;
bcx ^= 0x0F;
print("xor-assign: {}\n", bcx);
bcs := 1;
bcs <<= 8;
print("shl-assign: {}\n", bcs);
bcr := 256;
bcr >>= 4;
print("shr-assign: {}\n", bcr);
// Modulo on variables
mv1 := 17;
mv2 := 5;
print("mod-var: {}\n", mv1 % mv2);
// Logical (short-circuit)
print("and: {}\n", true and true);
print("and-false: {}\n", true and false);
print("or: {}\n", false or true);
print("or-false: {}\n", false or false);
// Short-circuit verification
print("short-and: {}\n", false and true);
print("short-or: {}\n", true or false);
// Compound assignment
ca := 10;
ca += 5;
print("ca+=: {}\n", ca);
ca -= 3;
print("ca-=: {}\n", ca);
ca *= 2;
print("ca*=: {}\n", ca);
ca /= 6;
print("ca/=: {}\n", ca);
// Precedence
print("prec1: {}\n", 2 + 3 * 4);
print("prec2: {}\n", (2 + 3) * 4);
// xx explicit cast
big2 : f64 = 200.7;
small : u8 = xx big2;
print("xx-cast: {}\n", small);
// Implicit widening conversions
wu : u8 = 200;
ws : i64 = wu;
print("widen-u8-i64: {}\n", ws);
wi3 : i32 = 42;
wf : f64 = wi3;
print("widen-i32-f64: {}\n", wf);
wf32 : f32 = 1.5;
wf64 : f64 = wf32;
print("widen-f32-f64: {}\n", wf64);
wu2 : u8 = 100;
ws2 : i16 = wu2;
print("widen-u8-i16: {}\n", ws2);
// More xx narrowing
xl : i64 = 12345;
xs : i32 = xx xl;
print("xx-i64-i32: {}\n", xs);
xd : f64 = 1.5;
xf : f32 = xx xd;
print("xx-f64-f32: {}\n", xf);
xdf : f64 = 7.9;
xdi : i32 = xx xdf;
print("xx-f64-i32: {}\n", xdi);
}

View File

@@ -0,0 +1,201 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
main :: () {
// ========================================================
// 4. CONTROL FLOW
// ========================================================
print("=== 4. Control Flow ===\n");
// If-then-else (inline, as expression)
ite := if true then 1 else 2;
print("ite: {}\n", ite);
// If-then-else both branches
ie_a := if true then 10 else 20;
ie_b := if false then 10 else 20;
print("ite-both: {} {}\n", ie_a, ie_b);
// If block
if 1 < 2 {
print("if-block: yes\n");
}
// If without else (statement)
if false { print("should-not-print\n"); }
print("if-no-else: after\n");
// Nested if
nx := 10;
if nx > 5 {
if nx > 8 {
print("nested-if: deep\n");
}
}
// If-else-if chain
eiv := 2;
if eiv == 1 {
print("if-else-if: first\n");
} else if eiv == 2 {
print("if-else-if: second\n");
} else {
print("if-else-if: other\n");
}
// If block as expression
ibe := 10 + if true { 5 } else { 0 };
print("if-block-expr: {}\n", ibe);
// While basic
wi := 0;
while wi < 5 { wi += 1; }
print("while: {}\n", wi);
// While with false condition (never executes)
while false { print("should-not-print\n"); }
print("while-false: skipped\n");
// While with break
wb := 0;
while wb < 100 {
if wb == 7 { break; }
wb += 1;
}
print("while-break: {}\n", wb);
// While with continue
wsum := 0;
wc := 0;
while wc < 10 {
wc += 1;
if wc % 2 == 0 { continue; }
wsum += wc;
}
print("while-continue: {}\n", wsum);
// While sum 1..10
wsum2 := 0;
wi2 := 1;
while wi2 <= 10 {
wsum2 += wi2;
wi2 += 1;
}
print("while-sum: {}\n", wsum2);
// Nested while
nw_outer := 0;
nw_count := 0;
while nw_outer < 3 {
nw_inner := 0;
while nw_inner < 3 {
nw_count += 1;
nw_inner += 1;
}
nw_outer += 1;
}
print("nested-while: {}\n", nw_count);
// Nested while with break in inner
nb_outer := 0;
nb_icount := 0;
while nb_outer < 5 {
nb_i := 0;
while nb_i < 5 {
if nb_i == 1 { break; }
nb_i += 1;
}
nb_icount += nb_i;
nb_outer += 1;
if nb_outer == 2 { break; }
}
print("nested-break: {} {}\n", nb_outer, nb_icount);
// For loop basic
farr : [4]i32 = .[10, 20, 30, 40];
out("for:");
for farr (it) {
out(" ");
out(int_to_string(it));
}
out("\n");
// For with print
out("for-print:");
for farr (it) {
print(" {}", it);
}
out("\n");
// For with index
out("for-idx:");
for farr, 0.. (_, ix) {
out(" ");
out(int_to_string(ix));
}
out("\n");
// For with print two args
out("for-2arg:");
for farr, 0.. (it, ix) {
print(" {}@{}", it, ix);
}
out("\n");
// For with break
out("for-break:");
for farr (it) {
if it == 30 { break; }
print(" {}", it);
}
out("\n");
// For with continue
out("for-continue:");
for farr (it) {
if it == 20 { continue; }
print(" {}", it);
}
out("\n");
// For on slice
fsl : []i32 = .[10, 20, 30];
out("for-slice:");
for fsl (it) {
print(" {}", it);
}
out("\n");
// For on slice with index
out("for-slice-idx:");
for fsl, 0.. (it, ix) {
print(" {}:{}", ix, it);
}
out("\n");
// Nested for
nf_a : [2]i32 = .[0, 1];
nf_b : [2]i32 = .[0, 1];
out("for-nested:");
for nf_a (oa) {
for nf_b (ob) {
print(" ({},{})", oa, ob);
}
}
out("\n");
// For with break preserving index
fbi : [5]i32 = .[10, 20, 30, 40, 50];
fbi_idx := 0;
for fbi, 0.. (it, ix) {
if it == 30 { fbi_idx = ix; break; }
}
print("for-break-idx: {}\n", fbi_idx);
// Multiple print placeholders
print("multi: {} {} {}\n", 1, 2, 3);
}

View File

@@ -0,0 +1,106 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
add :: (a: i32, b: i32) -> i32 { a + b }
mul :: (a: i32, b: i32) -> i32 { a * b }
identity :: (x: $T) -> T { x }
pair_add :: (a: $T, b: $U) -> i64 {
cast(i64) a + cast(i64) b
}
typed_sum :: (..args: []i32) -> i32 {
result := 0;
for args (it) { result = result + it; }
result
}
apply :: (f: (i32, i32) -> i32, x: i32, y: i32) -> i32 {
f(x, y)
}
void_return :: () {
return;
}
implicit_return :: (x: i32) -> i32 {
x * 2
}
early_return :: (x: i32) -> i32 {
if x > 10 { return 99; }
x
}
main :: () {
// ========================================================
// 5. FUNCTIONS & DECLARATIONS
// ========================================================
print("=== 5. Functions ===\n");
// Constant binding
FORTY_TWO :: 42;
print("const: {}\n", FORTY_TWO);
// Typed constant
TYPED_PI : f64 : 3.14;
print("typed-const: {}\n", TYPED_PI);
// Variable with default init
di : i32;
print("default-init: {}\n", di);
// Implicit return
print("implicit-ret: {}\n", implicit_return(21));
// Explicit return
print("early-ret: {}\n", early_return(5));
print("early-ret2: {}\n", early_return(20));
// Void return
void_return();
print("void-return: ok\n");
// Generic — single param
print("generic-i32: {}\n", identity(42));
print("generic-f32: {}\n", identity(1.5));
print("generic-bool: {}\n", identity(true));
// Generic — multiple params
print("generic-multi: {}\n", pair_add(10, 20));
// Lambda
double :: (x: i32) => x * 2;
print("lambda: {}\n", double(7));
// Lambda with return type
halve :: (x: f32) -> f32 => x / 2.0;
print("lambda-ret: {}\n", halve(10.0));
// Local function (non-lambda)
local_add :: (a: i32, b: i32) -> i32 { a + b }
print("local-fn: {}\n", local_add(3, 4));
// Nested function calls
print("fn-nested: {}\n", add(mul(2, 3), mul(4, 5)));
// Variadic (typed)
print("varargs: {}\n", typed_sum(1, 2, 3, 4, 5));
// Spread
spread_arr : [3]i32 = .[10, 20, 30];
print("spread: {}\n", typed_sum(..spread_arr));
// Function pointers
fp : (i32, i32) -> i32 = add;
print("fp: {}\n", fp(3, 4));
fp = mul;
print("fp-reassign: {}\n", fp(3, 4));
print("fp-apply: {}\n", apply(add, 10, 20));
}

View File

@@ -0,0 +1,79 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
main :: () {
// ========================================================
// 6. SCOPING & DEFER
// ========================================================
print("=== 6. Scoping ===\n");
// Scope block with shadowing
sv := 100;
{
sv := 200;
print("inner: {}\n", sv);
}
print("outer: {}\n", sv);
// Shadow with different type
st_v := 42;
print("shadow-type: {}\n", st_v);
{
st_v := 3.14;
print("shadow-type: {}\n", st_v);
}
// Nested scopes (3 levels)
nv := 1;
{
nv := 2;
{
nv := 3;
print("nest3: {}\n", nv);
}
print("nest2: {}\n", nv);
}
print("nest1: {}\n", nv);
// Scope isolation
{ iso := 100; print("scope-isolate: {}\n", iso); }
// Reuse name after scope exit
sr := 1;
print("scope-reuse: {}\n", sr);
{ sr := 2; print("scope-reuse: {}\n", sr); }
print("scope-reuse: {}\n", sr);
// Multiple defers (LIFO order)
{
defer print("defer-c\n");
defer print("defer-b\n");
defer print("defer-a\n");
}
// Four defers
{
defer print("d1\n");
defer print("d2\n");
defer print("d3\n");
defer print("d4\n");
}
// Defer in nested scopes
{
defer print("outer-defer\n");
{
defer print("inner-defer\n");
}
}
// Defer in if block
if true {
defer print("defer-in-if: deferred\n");
print("defer-in-if: body\n");
}
}

View File

@@ -0,0 +1,127 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
Point :: struct { x, y: i32; }
Color :: enum { red; green; blue; }
Shape :: enum {
circle: f32;
rect: struct { w, h: f32; };
none;
}
main :: () {
// ========================================================
// 7. BUILT-IN FUNCTIONS
// ========================================================
print("=== 7. Builtins ===\n");
// out
out("out-ok\n");
// sqrt
print("sqrt: {}\n", sqrt(9.0));
print("sqrt-f64: {}\n", sqrt(16.0));
// size_of
print("sizeof-i32: {}\n", size_of(i32));
print("sizeof-f64: {}\n", size_of(f64));
print("sizeof-struct: {}\n", size_of(Point));
// align_of
print("alignof-u8: {}\n", align_of(u8));
print("alignof-i32: {}\n", align_of(i32));
print("alignof-i64: {}\n", align_of(i64));
print("alignof-struct: {}\n", align_of(Point));
// type_of + category matching
tv := 42;
ttype := type_of(tv);
if ttype == {
case int: print("typeof: int\n");
case float: print("typeof: float\n");
else: print("typeof: other\n");
}
// type_of — float
tf := 3.14;
if type_of(tf) == {
case float: print("typeof-float: float\n");
else: print("typeof-float: other\n");
}
// type_of — string
ts := "hello";
if type_of(ts) == {
case string: print("typeof-string: string\n");
else: print("typeof-string: other\n");
}
// type_of — bool
tb := true;
if type_of(tb) == {
case bool: print("typeof-bool: bool\n");
else: print("typeof-bool: other\n");
}
// type_of — struct
tst := Point.{ 1, 2 };
if type_of(tst) == {
case struct: print("typeof-struct: struct\n");
else: print("typeof-struct: other\n");
}
// type_of — enum
ten : Color = .red;
if type_of(ten) == {
case enum: print("typeof-enum: enum\n");
else: print("typeof-enum: other\n");
}
// type_name
print("typename: {}\n", type_name(Point));
// field_count on struct
print("fieldcount: {}\n", field_count(Point));
// field_count on enum
print("fieldcount-enum: {}\n", field_count(Color));
// field_name on struct
print("fieldname0: {}\n", field_name(Point, 0));
print("fieldname1: {}\n", field_name(Point, 1));
// field_name on enum
print("fieldname-enum0: {}\n", field_name(Color, 0));
print("fieldname-enum2: {}\n", field_name(Color, 2));
// field_value (use any_to_string to avoid sext-on-Any bug)
fv_pt := Point.{ 11, 22 };
out("fieldval0: ");
out(any_to_string(field_value(fv_pt, 0)));
out("\n");
out("fieldval1: ");
out(any_to_string(field_value(fv_pt, 1)));
out("\n");
// field_index on plain enum
fi_c : Color = .green;
print("fieldidx: {}\n", field_index(Color, fi_c));
// field_index on tagged enum
fi_sh : Shape = .circle(1.0);
print("fieldidx-tagged: {}\n", field_index(Shape, fi_sh));
fi_sh2 : Shape = .none;
print("fieldidx-tagged2: {}\n", field_index(Shape, fi_sh2));
// cast
cval : f64 = 3.7;
print("cast: {}\n", cast(i32) cval);
cv2 : i32 = 42;
print("cast-int-f64: {}\n", cast(f64) cv2);
}

View File

@@ -0,0 +1,30 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
Point :: struct { x, y: i32; }
Shape :: enum {
circle: f32;
rect: struct { w, h: f32; };
none;
}
main :: () {
// ========================================================
// 19. LOCAL FUNCTION RETURNING STRUCT/ENUM
// ========================================================
print("=== 19. Local Fn Return ===\n");
{
local_pt :: () -> Point { Point.{42, 99} }
lp := local_pt();
print("local-struct: {} {}\n", lp.x, lp.y);
local_sh :: () -> Shape { .circle(2.5) }
ls := local_sh();
print("local-enum: {}\n", ls);
}
}

View File

@@ -0,0 +1,24 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
Point :: struct { x, y: i32; }
point_sum :: (p: Point) -> i32 { p.x + p.y }
// #run compile-time constants
main :: () {
// ========================================================
// 20. PIPE UFCS RETURN TYPE INFERENCE
// ========================================================
print("=== 20. UFCS Return Type ===\n");
{
p := Point.{3, 4};
print("direct: {}\n", point_sum(p));
print("ufcs: {}\n", p |> point_sum());
}
}

View File

@@ -0,0 +1,22 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
Point :: struct { x, y: i32; }
main :: () {
// ========================================================
// 22. IF-EXPRESSION RETURNING STRUCT
// ========================================================
print("=== 22. If-Struct ===\n");
{
flag := true;
p := if flag { Point.{10, 20} } else { Point.{30, 40} };
print("if-struct: {} {}\n", p.x, p.y);
q := if !flag { Point.{10, 20} } else { Point.{30, 40} };
print("else-struct: {} {}\n", q.x, q.y);
}
}

View File

@@ -0,0 +1,23 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
main :: () {
// ========================================================
// 24. STRING COMPARISON
// ========================================================
print("=== 24. String Comparison ===\n");
{
a := "hello";
b := "hello";
c := "world";
print("str-eq: {}\n", a == b);
print("str-neq: {}\n", a != c);
print("str-diff: {}\n", a == c);
empty := "";
print("empty-eq: {}\n", empty == "");
}
}

View File

@@ -0,0 +1,24 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
main :: () {
// ========================================================
// 25. ARRAY LOOP MUTATION
// ========================================================
print("=== 25. Array Loop Mutation ===\n");
{
arr : [4]i32 = .[0, 0, 0, 0];
i := 0;
while i < 4 {
arr[i] = xx (i + 1);
i += 1;
}
print("loop-fill: {} {} {} {}\n", arr[0], arr[1], arr[2], arr[3]);
arr[2] += 10;
print("compound: {}\n", arr[2]);
}
}

View File

@@ -0,0 +1,42 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
main :: () {
// --- UFCS Aliases & Pipe ---
{
print("=== UFCS Aliases ===\n");
num_sum :: (a: i64, b: i64) -> i64 { a + b }
sum :: ufcs num_sum;
print("{}\n", num_sum(40, 2)); // 42 — direct call
print("{}\n", sum(40, 2)); // 42 — alias direct call
print("{}\n", 40 |> sum(2)); // 42 — pipe UFCS via alias
print("{}\n", num_sum(40, 2)); // 42 — direct (was tuple full-splat)
print("{}\n", 40 |> sum(2)); // 42 — pipe (was tuple partial-splat)
compute :: (a: i64, b: i64, c: i64, d: i64) -> i64 { a + b * c - d }
calc :: ufcs compute;
print("{}\n", compute(1, 2, 3, 4)); // 1+2*3-4 = 3 (was tuple full-splat)
print("{}\n", compute(1, 2, 3, 4)); // same = 3 (was tuple partial-splat)
print("{}\n", 1 |> calc(2, 3, 4)); // same = 3 — pipe UFCS
// Tuple return type
swap :: (a: i64, b: i64) -> (i64, i64) { (b, a) }
s := swap(1, 2);
a := s.0;
b := s.1;
print("{}\n", a); // 2
print("{}\n", b); // 1
wrap :: (x: i64) -> (i64) { (x,) }
t := wrap(99);
print("{}\n", t.0); // 99
}
}

View File

@@ -0,0 +1,36 @@
#import "modules/std.sx";
#import "modules/math";
#import "modules/build.sx";
#import "modules/std/test.sx";
pkg :: #import "tests/fixtures/testpkg";
add :: (a: i32, b: i32) -> i32 { a + b }
main :: () {
// ── Trailing commas ──────────────────────────────────────────
print("=== Trailing Commas ===\n");
{
// Struct literal with trailing comma
Vec4 :: struct { x: f64; y: f64; z: f64; w: f64; }
v := Vec4.{
x = 1.0,
y = 2.0,
z = 3.0,
w = 4.0,
};
assert(v.x == 1.0);
assert(v.w == 4.0);
// Function call with trailing comma
add :: (a: i64, b: i64) -> i64 { return a + b; }
r := add(10, 20,);
assert(r == 30);
// Array literal with trailing comma
arr := i64.[1, 2, 3,];
assert(arr[2] == 3);
print("trailing commas ok\n");
}
}

View File

@@ -0,0 +1,34 @@
// Dead statements after a block-terminating statement (`return` / `raise`) are
// dropped instead of being emitted into the already-closed basic block.
// Regression (issue 0061): a bare `return X;` / `raise` mid-block closed the
// LLVM basic block but lowering kept emitting the trailing statements into it
// → "Terminator found in the middle of a basic block". The canonical failable
// closure form `{ raise error.X; return x; }` tripped this, blocking ERR E5.1.
//
// The fix must NOT over-reach: a CONDITIONAL `if cond { return }` (and the
// `inline if` pack form) leaves a fresh merge block, so its trailing statements
// must still run — exercised by `clamp` / `pick` below.
#import "modules/std.sx";
E :: error { Neg }
// dead `return 99;` after an unconditional return
const_one :: () -> i64 { return 1; return 99; }
// dead `return x;` after an unconditional raise (the failable closure shape)
always_raise :: (x: i64) -> (i64, !E) { raise error.Neg; return x; }
// guard: a conditional return must still fall through to the trailing return
clamp :: (x: i64) -> i64 { if x > 10 { return 10; } return x; }
main :: () -> i32 {
print("const_one={}\n", const_one()); // 1
print("raised={}\n", always_raise(5) catch (e) 0); // 0
print("clamp_hi={}\n", clamp(42)); // 10
print("clamp_lo={}\n", clamp(7)); // 7
// dead code after a `return` at main's own block level is dropped.
return 0;
print("unreachable\n")
}

View File

@@ -0,0 +1,26 @@
// Free-function UFCS with a pointer first-param (issue 0063). `recv.fn(args)`
// on a value `recv` whose matching free function takes `*T` now takes the
// receiver's address (the same implicit address-of as a struct-defined method),
// so mutations through the pointer are visible. Also: a function reached ONLY
// via UFCS is lazily lowered (previously declared-but-never-emitted → undefined
// symbol at link).
#import "modules/std.sx";
Counter :: struct { n: i32; }
// FREE functions (defined outside the struct), pointer first param.
bump :: ufcs (c: *Counter) -> i32 { c.n += 1; return c.n; }
// reached ONLY via UFCS — must still be emitted.
reset :: ufcs (c: *Counter) { c.n = 0; }
main :: () -> i32 {
c := Counter.{ n = 10 };
a := c.bump(); // 11, mutates c
b := c.bump(); // 12
print("a={} b={} n={}\n", a, b, c.n); // a=11 b=12 n=12
c.reset(); // UFCS-only free fn
print("after reset n={}\n", c.n); // after reset n=0
return 0;
}

View File

@@ -0,0 +1,46 @@
// Block value rule: a block's value is 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 "forgot to produce a value".
//
// { … expr } → value is `expr`
// { … expr; } → void (value discarded)
//
// Match arms are exempt: the arm `;` is an arm terminator, so `case .x: expr;`
// still yields `expr` (only an explicit inner braced block follows the rule).
#import "modules/std.sx";
// Implicit return: trailing expression, no `;`.
double :: (n: i32) -> i32 { n * 2 }
// if/else as a value — each branch's last expression has no `;`.
sign :: (n: i32) -> i32 {
if n < 0 { -1 } else if n > 0 { 1 } else { 0 }
}
// A value-producing block bound to a name.
sum3 :: (a: i32, b: i32, c: i32) -> i32 {
t := { x := a + b; x + c }; // block value is `x + c`
t
}
// Match arms keep their `;` (exempt): the arm `;` is an arm terminator, so each
// arm still yields its expression as the match value.
classify :: (n: i32) -> i32 {
if n == {
case 0: 100;
case 1: 10;
else: 7;
}
}
main :: () -> i32 {
total : i32 = 0;
total = total + double(10); // 20
total = total + sign(-7); // -1
total = total + sum3(1, 2, 3); // 6
total = total + classify(1); // 10
print("block-value total: {}\n", total); // 20 - 1 + 6 + 10 = 35
total
}

View File

@@ -0,0 +1,13 @@
// Rejection counterpart to 0040: a value-position block whose last expression's
// value is discarded by a trailing `;` produces no value. A `-> T` function
// whose body ends that way is a compile error (it used to silently return a
// zero default). Drop the `;` to return the value, or use an explicit `return`.
#import "modules/std.sx";
// `n * 2;` discards the value → the function returns nothing.
double :: (n: i32) -> i32 {
n * 2;
}
main :: () -> i32 { double(5) }

View File

@@ -0,0 +1,25 @@
// A value-position block (`x := { … }`, a call argument, …) parses any
// statement form in its body — including a destructure decl — and yields its
// trailing expression as the value. Previously a braced value block routed
// through a restricted expression parser that rejected destructures with
// "expected ';'".
//
// Regression (issue 0065).
#import "modules/std.sx";
pair :: () -> (i32, i32) { (5, 7) }
main :: () -> i32 {
// destructure decl inside a value-bound block
sum := {
a, b := pair();
a + b // trailing expression → the block's value
};
print("sum: {}\n", sum); // 12
// block expression directly as a call argument
print("sq: {}\n", { x := 4; x * x }); // 16
sum
}

View File

@@ -0,0 +1,31 @@
// A value-position match (`if subject == { case … }`) returning a small
// integer type works when arms mix positive and negated literals: every arm
// value is lowered against the merge's result type, so the phi operands all
// share one width.
//
// Regression (issue 0066): a negated-literal arm (`else: -1`) previously
// lowered at a narrower width than the positive arms, tripping LLVM's
// "PHI node operands are not the same type as the result".
#import "modules/std.sx";
sign :: (n: i32) -> i32 {
if n == {
case 0: 0;
else: if n > 0 then 1 else -1;
}
}
classify :: (n: i32) -> i32 {
if n == {
case 0: 100;
case 1: 10;
else: -1;
}
}
main :: () -> i32 {
print("sign: {} {} {}\n", sign(-9), sign(0), sign(9)); // -1 0 1
print("classify: {} {} {}\n", classify(0), classify(1), classify(5)); // 100 10 -1
0
}

View File

@@ -0,0 +1,23 @@
// Call-site default-argument expansion (`appendDefaultArgs` / `expandCallDefaults`
// in lowerCall). A call that omits a trailing parameter with a default value
// has the default expression spliced in at the call site; an explicit argument
// overrides it. Two trailing defaults cover the "fill all remaining" path.
// Path-free IR (literal defaults) so the `.ir` snapshot is location-stable.
#import "modules/std.sx";
scale :: (n: i32, factor: i32 = 2) -> i32 { n * factor }
label :: (n: i32, prefix: string = "v", suffix: string = "!") -> i32 {
print("{}{}{}\n", prefix, n, suffix);
n
}
main :: () {
print("default: {}\n", scale(5));
print("explicit: {}\n", scale(5, 3));
_ = label(1); // both defaults filled
_ = label(2, "x"); // suffix default filled
_ = label(3, "y", "?"); // no defaults
}

View File

@@ -0,0 +1,53 @@
// String `==`/`!=` as an operand of a short-circuit `and`/`or`.
//
// A string compare lowers to its own multi-block memcmp sub-CFG, so the
// operand finishes in a later basic block than the one the short-circuit
// started in. The `and`/`or` merge PHI must take that actual block as the
// incoming predecessor.
//
// Regression (issue 0078): combining string equality with `and`/`or` used to
// emit invalid LLVM (`PHI node entries do not match predecessors!`).
#import "modules/std.sx";
Json :: enum {
str: string;
int_: i64;
null_;
}
main :: () {
a := "k";
b := "v";
// string == on both sides of `and`
and_tt := a == "k" and b == "v";
and_tf := a == "k" and b == "x";
and_ft := a == "z" and b == "v";
print("and: {} {} {}\n", and_tt, and_tf, and_ft);
// string == on both sides of `or`
or_ff := a == "z" or b == "x";
or_tf := a == "k" or b == "x";
or_ft := a == "z" or b == "v";
print("or: {} {} {}\n", or_ff, or_tf, or_ft);
// string == feeding both `and` and `or` in one expression
mixed := a == "k" and b == "v" or a == "z";
print("mixed: {}\n", mixed);
// string `!=` operands too
ne := a != "z" and b != "z";
print("ne: {}\n", ne);
// the larger shape: a match-expression value plus an enum-payload string
// == combined under `and`/`or`.
v : Json = .str("v");
kind := if v == {
case .str: 1;
case .int_: 2;
case .null_: 3;
}
ok := kind == 1 and v.str == "v";
bad := kind == 2 or v.str == "x";
print("payload: {} {}\n", ok, bad);
}

View File

@@ -0,0 +1,37 @@
// Integer `{}` formatting across the full signed/unsigned range.
//
// Regression (issue 0090): the `{}` formatter was i64-based — it negated
// the value to print the sign (so i64::MIN, whose magnitude is
// unrepresentable as a positive i64, rendered as a bare "-"), and it had
// no unsigned-aware path (so a u64 all-ones value printed as the i64
// reinterpretation, "-1"). Both extremes now render correctly: signed
// MIN prints all its digits, and unsigned integers print as unsigned
// decimal across all 64 bits.
#import "modules/std.sx";
main :: () {
// Signed extreme: magnitude is never negated, so MIN survives.
print("i64.min={}\n", i64.min);
print("i64.max={}\n", i64.max);
// Unsigned extreme: all 64 bits as unsigned decimal, not -1.
print("u64.max={}\n", u64.max);
// Spread across widths — signed.
print("i8.min={} i8.max={}\n", i8.min, i8.max);
print("i16.min={} i16.max={}\n", i16.min, i16.max);
print("i32.min={} i32.max={}\n", i32.min, i32.max);
// Spread across widths — unsigned (max is all-ones for that width).
print("u8.max={} u16.max={}\n", u8.max, u16.max);
print("u32.max={}\n", u32.max);
// Mins of unsigned widths and zero.
print("u8.min={} u64.min={} zero={}\n", u8.min, u64.min, 0);
// Ordinary signed/unsigned values still print correctly.
neg : i32 = -42;
pos : u32 = 4000000000;
print("neg={} pos={}\n", neg, pos);
}

View File

@@ -0,0 +1,26 @@
// Loop-body locals reuse one stack slot per frame: a body-declared local
// (and every compiler temp) must not grow the stack per iteration, so
// million-iteration loops run in constant stack. Covers body locals,
// nested loops (the inner loop's hidden index slot), and element reads.
// Regression (issue 0109): allocas were emitted at their use site, so each
// iteration re-executed them — LLVM only reclaims allocas at `ret`, and
// these loops segfaulted on stack exhaustion.
#import "modules/std.sx";
main :: () -> i32 {
sum := 0;
for 0..1000000 (i) {
buf : [128]i64 = ---;
buf[0] = i;
sum += buf[0];
}
print("sum={}\n", sum);
n := 0;
for 0..3000000 (i) {
for 0..1 (j) { n += 1; }
}
print("n={}\n", n);
0
}

View File

@@ -0,0 +1,24 @@
// Collection-form `for` over an array, by-value capture: each iteration
// reads ONE element from the array's storage (GEP + load), and the capture
// stays a copy — mutating it never writes back to the array.
// Regression (issue 0110): the element fetch was `index_get` on the array
// VALUE, spilling a full copy of the array to a stack temp per iteration —
// O(N²) bytes copied, and (pre-0109) per-iteration stack growth that made
// this 4096-element loop segfault.
#import "modules/std.sx";
main :: () -> i32 {
arr : [4096]i64 = ---;
i := 0;
while i < 4096 { arr[i] = i; i += 1; }
sum := 0;
for arr (x) { sum += x; }
print("sum={}\n", sum);
// By-value capture is a copy: mutating it leaves the array untouched.
small : [3]i64 = .[10, 20, 30];
for small (x) { x += 100; }
print("copy-guard: {} {} {}\n", small[0], small[1], small[2]);
0
}

View File

@@ -0,0 +1,46 @@
// `defer` runs on EVERY exit from the loop body's scope — fall-through,
// `break`, and `continue` alike (LIFO, including entries from nested blocks
// between the loop and the jump). Covers `for` ranges and `while`.
// Regression (issue 0108): break/continue emitted a bare branch and the
// breaking iteration's defers were silently skipped.
#import "modules/std.sx";
main :: () -> i32 {
for 0..3 (i) {
defer print("cleanup {}\n", i);
if i == 1 { break; }
print("body {}\n", i);
}
print("after break loop\n");
for 0..3 (i) {
defer print("c2 {}\n", i);
if i == 1 { continue; }
print("b2 {}\n", i);
}
print("done\n");
i := 0;
while i < 3 {
defer print("w{}\n", i);
i += 1;
if i == 2 { continue; }
if i == 3 { break; }
print("wbody{}\n", i);
}
print("while done\n");
// A break inside a nested block drains the nested block's defers AND the
// loop body's, in LIFO order.
for 0..2 (j) {
defer print("outer {}\n", j);
{
defer print("inner {}\n", j);
if j == 0 { break; }
}
print("unreached\n");
}
print("nested done\n");
0
}

View File

@@ -0,0 +1,54 @@
#import "modules/std.sx";
pair_sum :: (xs: []i64, ys: []i64) -> i64 {
total := 0;
for xs, ys (x, y) { total += x * y; }
total
}
make :: () -> [3]i64 {
r : [3]i64 = .[7, 8, 9];
r
}
main :: () -> i32 {
// Agra's example: a 1..5 inclusive, b open-ended following along.
for 1..=5, 0.. (a, b) { print("{}:{} ", a, b); }
print("\n");
// Index idiom replacing the old (x, i) form.
xs : [3]i64 = .[10, 20, 30];
for xs, 0.. (x, i) { print("[{}]={} ", i, x); }
print("\n");
// Parallel slices.
a4 : [4]i64 = .[1, 2, 3, 4];
b4 : [4]i64 = .[10, 20, 30, 40];
print("dot={}\n", pair_sum(a4, b4));
// Arrow bodies.
s := 0;
for 0..4 (i) => s += i;
print("arrow-range s={}\n", s);
t := 0;
for xs (x) => t += x;
print("arrow-coll t={}\n", t);
// Call iterable + capture (first group = args, last group = capture).
for make() (v) { print("v{} ", v); }
print("\n");
// No-capture call iterable via leading-group escape.
n := 0;
for (make()) { n += 1; }
print("escape n={}\n", n);
// Three-way zip: two collections + cursor.
for a4, b4, 100.. (p, q, k) { print("{}/{}/{} ", p, q, k); }
print("\n");
// By-ref capture in a multi-iterable header.
for a4, 0.. (*p, i) { p.* += i; }
print("after ref: {} {} {} {}\n", a4[0], a4[1], a4[2], a4[3]);
0
}

View File

@@ -0,0 +1,63 @@
// Range bound markers: each side of `..` takes `=` (inclusive) or `<`
// (exclusive); defaults are start-inclusive, end-exclusive (`a..b` == `a=..<b`).
// Covers the full matrix, open ranges with start markers, comptime unrolling,
// runtime bounds, arbitrary expressions at EITHER end (expression parsing
// stops at the range token), and that `<` / `<<` comparisons still lex
// normally.
#import "modules/std.sx";
main :: () -> i32 {
for 0<..<5 (i) { print("{} ", i); }
print("| 0<..<5\n");
for 0=..=5 (i) { print("{} ", i); }
print("| 0=..=5\n");
for 0<..=5 (i) { print("{} ", i); }
print("| 0<..=5\n");
for 0=..<5 (i) { print("{} ", i); }
print("| 0=..<5\n");
for 0..<5 (i) { print("{} ", i); }
print("| 0..<5\n");
for 0..=5 (i) { print("{} ", i); }
print("| 0..=5\n");
// Exclusive-start open range following a bounded first iterable.
xs : [3]i64 = .[10, 20, 30];
for xs, 2<.. (x, i) { print("{}@{} ", x, i); }
print("| xs, 2<..\n");
// Explicit inclusive-start open form (synonym of `5..`).
for xs, 5=.. (x, i) { print("{}@{} ", x, i); }
print("| xs, 5=..\n");
// Comptime-unrolled with markers.
s := 0;
inline for 0<..=3 (i) { s += i; }
print("inline 0<..=3 sum={}\n", s);
// Runtime bounds with markers.
lo := 1;
hi := 4;
for lo<..=hi (i) { print("{} ", i); }
print("| lo<..=hi\n");
// Arbitrary expressions at either end of the range token.
x := 2;
n := 0;
sum := 0;
for x+2..=42 (e) { n += 1; sum += e; } // expression start: 4 .. 42
print("x+2..=42: n={} sum={}\n", n, sum);
n2 := 0;
for x+2<..<x*21 (e) => n2 += 1; // both ends: 5 .. 41
print("x+2<..<x*21: n2={}\n", n2);
n3 := 0;
for 0..x*3 (i) => n3 += 1; // expression end: 0 .. 5
print("0..x*3: n3={}\n", n3);
// Comparison operators still lex normally.
a := 3;
if a < 5 { print("cmp ok\n"); }
b := a << 1;
print("shl={}\n", b);
0
}

View File

@@ -0,0 +1,32 @@
// Slice range bound markers — same matrix as for-header ranges: each side
// of `..` takes `=` (inclusive) or `<` (exclusive), defaults 0-inclusive
// start / exclusive end. Prefix form takes markers too ([..=2], [<..3]);
// [..] is the whole slice; bounds are arbitrary expressions; strings slice
// through the same path.
#import "modules/std.sx";
dump :: (s: []i64, tag: string) {
print("{}: ", tag);
for s (v) { print("{} ", v); }
print("(len {})\n", s.len);
}
main :: () -> i32 {
xs : [6]i64 = .[10, 11, 12, 13, 14, 15];
full : []i64 = xs[0..6];
dump(full[1..=3], "1..=3"); // 11 12 13
dump(full[0<..<4], "0<..<4"); // 11 12 13
dump(full[..=2], "..=2"); // 10 11 12
dump(full[<..3], "<..3"); // 11 12
dump(full[2<..], "2<.."); // 13 14 15
dump(full[..], ".."); // all six
x := 3;
dump(full[x-1..=x+1], "x-1..=x+1"); // 12 13 14
s := "abcdef";
print("str 1..=3: {}\n", s[1..=3]); // bcd
print("str 0<..<4: {}\n", s[0<..<4]); // bcd
0
}

View File

@@ -0,0 +1,35 @@
// Free-function dot-calls are OPT-IN. Two opt-in spellings:
// name :: ufcs (params) { body } — the fn itself is dot-callable
// name :: ufcs target; — dot-callable (renaming) alias
// A plain fn is callable directly or via `|>` only (see 1166 for the
// rejection). Generic ufcs fns dispatch through normal monomorphization,
// and the plan-side return type binds from the receiver (structured
// params like `[]$T` included).
#import "modules/std.sx";
bump :: (x: i64) -> i64 { x + 1 }
bump2 :: ufcs (x: i64) -> i64 { x + 2 }
bump3 :: ufcs bump;
Counter :: struct { n: i64; }
inc :: ufcs (c: *Counter, by: i64) -> i64 { c.n += by; c.n }
gfirst :: ufcs (xs: []$T) -> T { xs[0] }
main :: () {
f : i64 = 40;
print("marked: {}\n", f.bump2()); // 42
print("alias: {}\n", f.bump3()); // 41
print("direct: {}\n", bump(f)); // 41 — plain fn, direct
print("pipe: {}\n", f |> bump()); // 41 — plain fn, pipe
print("marked-direct: {}\n", bump2(f)); // 42 — marked fn callable directly
c := Counter.{ n = 10 };
print("ptr-recv: {}\n", c.inc(5)); // 15 — auto address-of receiver
arr := .[7, 8, 9];
xs : []i64 = arr;
print("generic-dot: {}\n", xs.gfirst()); // 7
print("generic-direct: {}\n", gfirst(xs)); // 7 — plan types it i64, not a T stub
}

View File

@@ -0,0 +1,33 @@
// Trailing parameter defaults fill on method and ufcs dot-calls (the
// receiver-prepending dispatch paths), matching bare-call expansion (0044);
// a `#caller_location` default and a slice variadic keep their flexible
// arity under the call-arity check.
// Regression (issue 0123).
#import "modules/std.sx";
Point :: struct {
x: i64;
scaled :: (self: Point, k: i64 = 2) -> i64 { return self.x * k; }
}
bump :: ufcs (p: Point, by: i64 = 10) -> i64 { return p.x + by; }
sum_var :: (..xs: []i64) -> i64 {
t := 0;
for xs (x) { t = t + x; }
return t;
}
here :: (loc: Source_Location = #caller_location) -> i64 { return loc.line; }
main :: () {
p := Point.{ x = 5 };
print("{}\n", p.scaled()); // default filled on method dispatch
print("{}\n", p.scaled(3)); // explicit overrides
print("{}\n", p.bump()); // default filled on ufcs dispatch
print("{}\n", p.bump(1));
print("{}\n", sum_var()); // variadic: zero args
print("{}\n", sum_var(1, 2, 3)); // variadic: many args
print("{}\n", here() > 0); // #caller_location default
}

View File

@@ -0,0 +1,45 @@
// Large (64KB+) stack arrays compile and are accessed in place: `---`
// emits no initializer store, and element reads GEP the array's storage
// instead of loading the whole array as a value.
//
// Regression (issue 0124): both whole-aggregate shapes — the undef
// store from `---` and `index_get` on the loaded array value —
// scalarized into one SelectionDAG node per element and segfaulted
// `sx build` at [65536]u8.
//
// Results print via out/int_to_string: `{}` formatting would pull the
// any_to_string dispatcher, whose array arms materialize every interned
// array type BY VALUE — the separate issue 0125.
#import "modules/std.sx";
checksum :: () -> i64 {
buf : [65536]u8 = ---;
i := 0;
while i < 65536 {
buf[i] = xx (i % 251);
i += 1;
}
sum := 0;
i = 0;
while i < 65536 {
sum += xx buf[i];
i += 1;
}
return sum;
}
big :: () -> i64 {
buf : [131072]i64 = ---;
buf[0] = 11;
buf[131071] = 31;
return buf[0] + buf[131071];
}
main :: () -> i32 {
out(int_to_string(checksum()));
out("\n");
out(int_to_string(big()));
out("\n");
return 0;
}

View File

@@ -0,0 +1,27 @@
// Interning a large (~64KB) array type and using `{}` formatting elsewhere must
// NOT scalarize into an O(N) SelectionDAG (which crashed `sx build` / made
// `sx run` take ~12s). The array Any-unbox formats via a SLICE VIEW of its
// storage — no whole-array load.
//
// Regression (issue 0125): `any_to_string`'s `case array:` arm used to do
// `array_to_string(cast(type) val)`, loading the whole [65536]u8 by value and
// reading each element off the loaded aggregate. Now the dispatcher builds a
// `{ptr,len}` slice view of the payload pointer and formats that — output is
// identical (`[a, b, c]`), and a large unrelated array type costs nothing.
#import "modules/std.sx";
f :: () {
buf : [65536]u8 = ---;
buf[0] = 65; // 'A'
out(string.{ ptr = @buf[0], len = 1 });
out("\n");
}
main :: () -> i32 {
f();
print("{}\n", 5); // an int format — unaffected by the big array
small : [3]i64 = .[7, 8, 9];
print("{}\n", small); // array format still renders the element list
return 0;
}

View File

@@ -0,0 +1 @@
42

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
Hello

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,4 @@
scope opened
scoped x: 6
scope closed
main x: 42

View File

@@ -0,0 +1 @@
42

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
still here

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
hello from the other side

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
[1.000000, 0.000000, -1.000000]

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,4 @@
count: 5
break at: 12
sum of odd 1-9: 25
sum 55

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
containedcontainedcontained

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
[1, 2, 2, 2, 3, 3, 4, 5, 5, 6, 6, 333]

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,41 @@
--- tagged enum compat ---
T1: 3.140000
T2: 3
T3: 5.000000 3.000000
T4: 2.710000
T5: 9.500000
T6: 4.199999
T7a: 1.000000
T7b: 2.000000 3.000000
T7c: 3
T8a: 10
T8b: 20
T8c: 30
T9: 42
T10a: 1
T10b: 1.000000
T10c: 9.000000
--- struct static shorthand ---
S1: 3.000000 4.000000
S2: 5.000000 6.000000
S3: 7.000000 8.000000
S4: 0.000000 0.000000
S4: 1.000000 0.000000
S5: 60
S6: 8.000000 8.000000 8.000000 8.000000
S6: 8.000000 16.000000 8.000000 16.000000
S6: 0.000000 12.000000 0.000000 12.000000
S7: 7
S8: 3.000000 4.000000
S8q: 1.000000 2.000000
S9: 4.000000 6.000000
S10: 4.000000 6.000000
--- edge cases ---
E1: 5.000000 1.000000 2.000000
E2: 9 1.000000 2.000000
E3: 1
E4: 4.000000 6.000000
E5: 42
E6: 1
E7: 20.000000 10.000000
=== DONE ===

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,5 @@
double: 14
sum: 7
answer: 42
total: 30
scaled: 90

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,10 @@
i=0
i=1
i=2
n=5
j=2
j=3
j=4
[0]=A
[1]=B
[2]=A

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,3 @@
101 102 103
circle 2.000000
none

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,4 @@
sum 60
sum2 360
via ptr 360
boxes 7

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,18 @@
=== 1. Literals ===
decimal: 42
hex: 255
binary: 10
float: 3.140000
f64: 2.718281
true: true
false: false
escapes: hello world
multiline: line1
line2
heredoc: raw heredoc
undef-then-set: 77
enum-lit: .green
null-ptr: null
string-len: 5
empty-string: 0

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,60 @@
=== 2. Operators ===
add: 7
sub: 7
mul: 42
div: 5
mod: 2
neg: -5
eq: true
neq: true
lt: true
gt: true
le: true
ge: true
chain: true
chain-gt: true
chain-mixed: true
eq-chain: true
eq-chain-f: false
band: 15
bor: 7
bxor: 240
bxor2: 5
bnot: -1
bnot2: -2
shl: 16
shr: 16
shl2: 24
shr2: 127
band-var: 15
bor-var: 7
bxor-var: 240
shl-var: 16
shr-var: 15
bnot-var: -16
and-assign: 15
or-assign: 255
xor-assign: 240
shl-assign: 256
shr-assign: 16
mod-var: 2
and: true
and-false: false
or: true
or-false: false
short-and: false
short-or: true
ca+=: 15
ca-=: 12
ca*=: 24
ca/=: 4
prec1: 14
prec2: 20
xx-cast: 200
widen-u8-i64: 200
widen-i32-f64: 42.000000
widen-f32-f64: 1.500000
widen-u8-i16: 100
xx-i64-i32: 12345
xx-f64-f32: 1.500000
xx-f64-i32: 7

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

Some files were not shown because too many files have changed in this diff Show More