Compare commits

...

43 Commits

Author SHA1 Message Date
agra
59f0aa7716 std: restructure — std/ modules, namespace tail, std/xml.sx
allocators/fs/process/socket/log/trace/test move under modules/std/
(allocators.sx becomes std/mem.sx; the Allocator protocol moves into
the std.sx prelude, impls stay in mem.sx). New std/xml.sx holds
xml_escape as xml.escape. std.sx gains the carried namespace tail —
flat-importing std.sx now also provides mem./xml./log. — with the
remaining modules (fs/process/socket/json/cli/hash/test) deferred from
the tail until the global last-wins maps are fully own-wins (pulling
them into every closure collides bare names corpus-wide; they stay
direct imports: modules/std/fs.sx etc.). log.sx's internal emit
renamed log_emit (it clobbered consumer fns named emit program-wide).
bundle.sx uses xml.escape via the carried alias. Consumer import paths
swept mechanically; .ir snapshots recaptured for the larger std
closure. m3te + game build unchanged.
2026-06-11 06:10:59 +03:00
agra
ee00db849c lang: qualified namespace members in value position + alias carry
Two coupled capabilities on the road to the std restructure
(current/PLAN-STDLIB.md, issue 0114):

1. alias.Type.method() / alias.Type as a call head, alias.CONST, and
   alias.Enum.variant now resolve — previously only alias.fn() and
   type-position alias.Type worked. objectIsValue treats an
   alias-rooted field_access as a type head; the call path strips the
   alias to the existing Type.method machinery; lowerFieldAccess
   resolves alias.CONST pinned to the target module and alias.Enum.x
   as a typed enum literal; resolveTypeWithBindings resolves qualified
   type_exprs pinned to the target.

2. The carry rule: namespaceAliasTarget resolves an alias from the
   file's own edges first, then from DIRECT flat imports (one level),
   diagnosing two distinct carried targets as ambiguous. All qualified
   shapes work through a carried alias — the std.sx namespace tail
   (mem.GPA.init() etc.) is now expressible.

Regression: examples/0831-modules-namespace-alias-carry.sx (direct +
carried, all seven shapes).
2026-06-11 05:52:10 +03:00
agra
2025bb361b issues: file 0114 — namespace aliases leak transitively, collide first-wins
Found while probing the alias-carry design for the stdlib restructure
(plan in current/PLAN-STDLIB.md): qualified members register globally
with no per-importer gate, so an alias is usable any number of flat
hops away, and same-name registrations silently first-win. The carry
rule's one-level + ambiguity semantics fix both; repro and fix shape
in the issue.
2026-06-11 05:36:22 +03:00
agra
83ec2536af lang: catch/onfail error bindings take parens
try foo() catch (e) { }   // legal
try foo() catch e { }     // parse error with a migration hint

Same capture style as the for-loop. All four catch shapes keep working
with the parenthesized binding — block, bare-expression body, and the
== match sugar — and the no-binding forms are unchanged. onfail follows
the same rule (onfail (e) { }); its expression-cleanup form is
disambiguated by the paren-group-before-brace lookahead, so
onfail (f()); stays an expression cleanup.

AST unchanged; the printer renders the parens; the #run escape help
text updated. Corpus migrated (57 catch + 3 onfail bindings, in-source
parser test strings, specs incl. grammar rules, readme untouched —
no catch examples there).

Regression: examples/1157-diagnostics-catch-binding-needs-parens.sx;
re-captured stderr for 1010/1013/1037/1123 (migrated source echoed in
carets + help text).
2026-06-10 23:05:02 +03:00
agra
12149eb548 fix(0113): negated-literal global initializers fold as constants
globalInitValue had no unary_op arm, so g : s64 = -1; fell into the
catch-all 'must be initialized by a compile-time constant' even though
constExprValue already folds negate(literal) for the module-const
identifier route. The new arm routes through constExprValue and applies
the direct-literal rules to the folded value: checkIntLiteralFits on
ints (g : s8 = -300 gets the range diagnostic), and a negated float at
an integer global narrows only when integral (-4.0 folds to -4, -4.5
errors). Binary-op initializers keep the specific non-constant
diagnostic.

Regression: examples/0175-types-negative-literal-global.sx.
2026-06-10 22:39:52 +03:00
agra
67313e1dad fix(0112): out-of-range int literals error instead of silently wrapping
checkIntLiteralFits range-checks a literal against its integer target
(builtins + custom widths via intLiteralRange; width-64 types skip —
every representable literal is a legal bit pattern there) and diagnoses
with the type's range and an xx/cast hint. Wired into the .int_literal
arm (covers decls, assignments, call args, struct-literal fields),
lowerStructConstant, and globalInitValue.

A negated literal now folds to a single constant so -128 range-checks
as -128 rather than as an out-of-range +128 intermediate. An explicit
xx operand skips the check — truncation stays available on request
(cast(T) was already exempt: its value arg lowers without the target).

examples/0300-closures-lambda.sx pinned 133 wrapping to -3 through an
s3 param — the exact class this outlaws; updated to a fitting value.

Found during the fix and filed separately: issue 0113 (negated-literal
global initializers rejected as non-constant; pre-existing).

Regressions: examples/1156-diagnostics-int-literal-out-of-range.sx,
examples/0174-types-int-literal-boundaries.sx.
2026-06-10 22:28:24 +03:00
agra
fea5617e4e lang: slice ranges take the same bound markers as for-header ranges
xs[1..=3] (end inclusive), xs[0<..<4] (both exclusive), xs[..=2]
(prefix form with markers, implicit 0 start), xs[2<..] (open end,
exclusive start), and xs[..] (whole collection) — lowered as lo+1 /
hi+1 on the existing subslice op. Strings slice through the same path.
An explicit end marker requires an end expression, matching the
for-header rule.

Regression: examples/0052-basic-slice-range-bounds.sx.
2026-06-10 22:12:45 +03:00
agra
f513c11ea6 test(0051): pin expression bounds at either end of range tokens
x+2..=42 (expression start, 39 iterations summing 897),
x+2<..<x*21 (expressions both ends, 5..41), 0..x*3 (expression end).
Expression parsing stops at the range lexeme from either side, so any
expression works in either position — now pinned.
2026-06-10 22:02:34 +03:00
agra
fd14ab5694 lang: range bound markers — '=' inclusive / '<' exclusive on either side of '..'
Each side of '..' takes an optional bound marker, defaulting to
start-inclusive, end-exclusive (a..b == a=..<b; a..=b stays the short
end-inclusive spelling):

    for 0<..<N (i) { }   // 1 .. N-1   (both exclusive)
    for 0=..=N (i) { }   // 0 .. N     (both inclusive)
    for 0<..=N (i) { }   // 1 .. N
    for 0..<N  (i) { }   // 0 .. N-1   (explicit default)
    for xs, 2<.. (x, i)  // open range, exclusive start: i = 3, 4, ...

The nine lexemes are single tokens (maximal munch on '<'/'='/'..'), so
expression parsing never sees the leading marker as a comparison; '<',
'<<', '<=', '==', '=>' lex unchanged. An explicit end marker makes the
end expression mandatory; open forms are a.. / a<.. / a=... Works in
runtime, multi-iterable, and inline-for headers.

Regression: examples/0051-basic-for-range-bounds.sx (full matrix, open
start-marked ranges, comptime unroll, runtime bounds, lexer
non-regression); 1152's pinned message generalized.
2026-06-10 20:55:31 +03:00
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
agra
c640e88513 docs(specs): make defer semantics on break/continue/return exits explicit
The Defer section only said 'when the enclosing scope block exits', which
left the break/continue paths implicit — the exact ambiguity issue 0108
hid behind. State all three exit kinds and the break/continue-outside-loop
diagnostic.
2026-06-10 17:45:14 +03:00
agra
3cc34d54c1 fix(0108): break/continue run the loop body's pending defers
lowerBreak/lowerContinue emitted a bare br, and the enclosing block's
emitBlockDefers — seeing the terminator — discarded the pending entries
on the assumption a return had already drained them. The breaking
iteration's defers were silently skipped, leaking whatever the cleanup
released.

Lowering.loop_defer_base records the defer-stack height at each loop's
body start (while / for / range-for, saved and restored alongside
break_target); break/continue drain non-onfail entries down to it in
LIFO order via the non-truncating emitLoopExitDefers before branching.
Truncation stays with the lexical block exits — the same entries still
belong to the fall-through path after the branch containing the break.
break/continue outside a loop now diagnose instead of no-op'ing.

Regression: examples/0049-basic-defer-break-continue.sx (for and while,
break and continue, nested-block LIFO drain).
2026-06-10 17:43:58 +03:00
agra
bf47146085 fix(0110): for-over-array by-value fetch reads one element, not a full copy
lowerFor's by-value element fetch emitted index_get on the array VALUE;
the emitter realizes that as a whole-array spill to a stack temp + GEP,
per iteration — O(N^2) bytes copied per loop (and pre-0109 it also grew
the stack per iteration, segfaulting a [4096]s64 loop).

When the iterable is an array with addressable storage (and not deref'd
from a pointer, whose identifier alloca holds the pointer rather than
the array), the fetch is now index_gep on the storage + one element
load. Storage-less arrays keep the index_get fallback. The loaded
element remains a copy — mutating the capture does not write back.

Regression: examples/0048-basic-for-array-large.sx (sum over 4096
elements + by-value copy-guard).
2026-06-10 17:34:35 +03:00
agra
878c4226a6 fix(0109): hoist all per-instruction allocas to the function entry block
An alloca built at its use site re-executes on every pass through that
block, and LLVM reclaims allocas only at ret — so loop-body locals,
nested-loop index slots, and emitter spill temps (ig.tmp, sret slots, ABI
coercion temps, byval materialization) grew the stack per iteration and
long loops segfaulted on stack exhaustion.

New LLVMEmitter.buildEntryAlloca inserts after existing entry-block
allocas and restores the builder position; every LLVMBuildAlloca site
reachable during instruction emission now routes through it.
Initialization stores stay at the use site (per-iteration re-init is
unchanged), and entry slots become mem2reg-promotable. The 35 .ir
snapshot diffs are pure alloca position moves (type multisets verified
identical per file).

Regression: examples/0047-basic-loop-local-stack-reuse.sx (segfaulted
pre-fix on both the 1M-iteration body-local loop and the 3M-iteration
nested loop).
2026-06-10 17:27:11 +03:00
agra
e81780e32e fix(0111): unannotated decl literals no longer adopt the fn return type
lowerVarDecl (unannotated) and lowerDestructureDecl now clear target_type
around the initializer lowering: a declaration without annotation provides
no target, so int/float literals take their spec defaults (s64/f64) instead
of the enclosing function's implicit-return type (x := 0 in a -> s8 fn was
s8; big := 3000000000 in -> s32 silently wrapped to -1294967296).

Regression: examples/0173-types-int-literal-default-s64.sx. The remaining
explicit-annotation wrap (x : s8 = 300) is filed as issue 0112.
2026-06-10 17:21:44 +03:00
agra
2b8041a828 cleanup: drop resolved-issue citations from src comments
Sweep all src/**.zig comments that cite resolved issues (issue NNNN /
fix-NNNN / KB-N): the invariant or mechanism each comment states is
kept; the historical citation is dropped, per the no-conclusion-comments
rule. Pure-history parentheticals are removed outright. References to
the 16 still-open issues (0030, 0041-0056) are untouched, as are test
NAMES carrying regression provenance (matching the sanctioned
"Regression (issue NNNN)" example-header convention).

Also removes the issues/0019-import-non-transitive-c-scope/ fixture dir
— the issue is superseded and its behavior is covered by
examples/0706-modules-import-non-transitive.sx (the .md writeup stays).
issues/0030's repro .sx stays: that issue is an open feature request.

Gate: zig build OK; zig build test 426/426; run_examples 541/0; zero
expected/ snapshot churn.
2026-06-10 16:34:17 +03:00
agra
8b2a6598a9 drop gitflow 2026-06-10 16:27:16 +03:00
agra
5b304a29c1 fix(KB-9): move Lowering/ProgramIndex maps off page_allocator defaults
16 Lowering map fields and 8 ProgramIndex map fields were declared with
`= ....init(std.heap.page_allocator)` field defaults that init() never
replaced — every instance really allocated page-at-a-time outside the
compilation allocator, invisible to leak checking and never reclaimed.

All 24 now init explicitly with the compilation allocator (module.alloc
/ the init alloc param), which is arena-backed in both the driver
(main's arena) and the test suites (per-test arenas), so backing is
reclaimed at teardown. ProgramIndex's struct doc no longer claims the
page_allocator defaults.

Six lower.test.zig tests that constructed Module with bare
std.testing.allocator leaked once the checker could finally see these
maps; they now use the same per-test ArenaAllocator idiom as the rest
of the file and the facade test suites.

Gate: zig build OK; zig build test 426/426 (6/6 steps, leak-clean);
run_examples 541/0; zero expected/ snapshot churn.
2026-06-10 16:19:52 +03:00
agra
82500931ce cleanup(B-review): fix stale section banners, merge alias block, prune imports
Review follow-up to the ARCH-B split (comment/import hygiene only, no
code changes):

- Section banners that travelled to the wrong file with the B1-B8 cuts
  are reworded to describe the section that actually follows (e.g.
  stmt.zig's trailing "Expression lowering", expr.zig's "Control flow"
  before lowerChainedComparison) or deleted where nothing follows
  (4 trailing-at-EOF banners). ffi.zig's facade note no longer claims
  the IMP builders "stay here" (they live in lower/objc_class.zig);
  protocol.zig's namespace-lookup banner now points at
  pack.zig:resolvePackProjection for the orchestrator.
- lower.zig's two lower/expr.zig alias blocks (B8.1 + B8.2 appends)
  merged into one.
- 448 unused header decls pruned from the 15 lower/*.zig files (each
  had inherited lower.zig's full import block; pruned to fixpoint so
  cascading type-extraction consts went too).

Gate: zig build OK; zig build test 426/426; run_examples 541/0; zero
expected/ snapshot churn.
2026-06-10 16:09:24 +03:00
agra
a7822d73cc Merge branch 'arch-b' (phase B8: lower/expr.zig + lower/closure.zig) 2026-06-10 14:43:04 +03:00
agra
89b3789973 refactor(B8.3): move lambdas + captures to lower/closure.zig
Verbatim relocation of the 5-method closure cluster (lowerLambda,
bare-fn trampoline, closure-to-bare-fn adapter, capture collection, env
sizing) into src/ir/lower/closure.zig. 5 aliases on Lowering keep all
call sites unchanged. Method pub-flip: typeAlignBytes.

Resolves the B7.1 flag: CaptureInfo relocates from lower/call.zig to
lower/closure.zig (its domain home, next to collectCaptures); the
Lowering type alias is repointed so external references are unchanged.

Gate: zig build OK; zig build test 426/426; run_examples 541/0; zero
expected/ snapshot churn.
2026-06-10 14:43:04 +03:00
agra
35e35adb0a refactor(B8.2): move expr core + operators to lower/expr.zig
Verbatim relocation of the 8-method core expression cluster (lowerExpr
dispatch, ref-capture pointee, binary ops, tuple ops/lex-compare/
membership, chained comparison, emitCmp) appended to
src/ir/lower/expr.zig. 8 aliases on Lowering keep all call sites
unchanged.

Method pub-flips: isArithOperand, isBitwiseOperand, isOrderingOperand,
lowerLambda, binOpSymbol. expr.zig reaches arithResultType,
exprIsFailable, binOpSymbol via Lowering-namespace alias consts.

Gate: zig build OK; zig build test 426/426; run_examples 541/0; zero
expected/ snapshot churn.
2026-06-10 14:40:44 +03:00
agra
0592c9dc97 refactor(B8.1): move expression literals/field/index to lower/expr.zig
Verbatim relocation of the 30-method expression cluster (struct/array/
tuple/enum/tagged-enum literals, init blocks, field access on values and
types, optional chains, numeric limits, indexing, slicing, deref, force
unwrap, null coalesce) into src/ir/lower/expr.zig — one contiguous
1,372-line cut. 30 aliases on Lowering keep all call sites unchanged.

Nested StructConstInfo stays on Lowering (field type of
struct_const_map), flipped pub and reached via an alias const, alongside
headNameOfCallee.

Gate: zig build OK; zig build test 426/426; run_examples 541/0; zero
expected/ snapshot churn.
2026-06-10 14:38:11 +03:00
agra
1790d808cc Merge branch 'arch-b' (phase B7: lower/call.zig + lower/pack.zig + lower/generic.zig) 2026-06-10 14:35:52 +03:00
agra
ef4059bdde refactor(B7.3): move generics emission to lower/generic.zig
Verbatim relocation of the 39-method generics cluster (function/struct/
union monomorphisation + instantiation, type-arg resolution and
matching, type-name formatting/mangling, type-category tags, head-gate
author selection, value-param resolution + diagnostics) plus four
single-home nested types (HeadTemplate, HeadName, HeadTypeGate,
GenericStructMethod) into src/ir/lower/generic.zig. 39 aliases on
Lowering keep all call sites unchanged.

Method pub-flips: packResolver, resolveVectorLane. generic.zig reaches
five relocated/static helpers via Lowering-namespace alias consts
(inferExprType, isNamedTypeKind, resolveBuiltin, structMethodFn,
typeFnAuthor).

Gate: zig build OK; zig build test 426/426; run_examples 541/0; zero
expected/ snapshot churn.
2026-06-10 14:35:52 +03:00
agra
6b07346d98 refactor(B7.2): move pack/variadic lowering to lower/pack.zig
Verbatim relocation of the 19-method pack cluster (pack element/value
projection, spread refs, pack indexing + diagnostics, pack-to-slice,
variadic arg lowering/packing, pack-slice materialisation, pack-fn
calls + monomorphisation, pack projection resolution, isPackFn/
isPackParam predicates) into src/ir/lower/pack.zig. 19 aliases on
Lowering keep all call sites unchanged.

ProjectionPosition and PackProjection move from lower/protocol.zig to
lower/pack.zig (their domain home, next to resolvePackProjection);
the Lowering type aliases are repointed, so external references
(lower.test.zig) are unchanged.

Method pub-flip: findReturnValueType.

Gate: zig build OK; zig build test 426/426; run_examples 541/0; zero
expected/ snapshot churn.
2026-06-10 14:30:34 +03:00
agra
54db29e60a refactor(B7.1): move call lowering to lower/call.zig
Verbatim relocation of the 18-method call cluster (lowerCall moved
whole, context diagnostics, foreign-call helper, builtin/function
resolution, generic + runtime-dispatch calls, reflection calls + guards,
default-arg expansion, call param typing) into src/ir/lower/call.zig.
18 fn aliases keep all call sites unchanged.

CaptureInfo (closure-domain type that sat inside the run) travelled and
is re-exposed via a Lowering type alias; candidate to relocate to
lower/closure.zig in B8.3.

Method pub-flips: callResolver, createBareFnTrampoline,
ensureGenericInstanceMethodLowered, fixupMethodReceiver,
getStructTypeName, isStaticTypeArg, lowerPackFnCall, packSpreadRefs,
packVariadicCallArgs, refCapturePointee, resolveParamTypeInSource,
typeSizeBytes, headNameOfCallee.

Gate: zig build OK; zig build test 426/426; run_examples 541/0; zero
expected/ snapshot churn.
2026-06-10 14:27:20 +03:00
agra
c38dd67b83 Merge branch 'arch-b' (phase B6: lower/ffi.zig + lower/objc_class.zig) 2026-06-10 14:24:13 +03:00
agra
b163c4a3fc refactor(B6.2): move Obj-C defined-class emission to lower/objc_class.zig
Verbatim relocation of the 23-method defined-class cluster (IMP/property
emission: class/alloc/static/dealloc IMPs, property getters/setters +
ARC runtime decls, defined-state field access, property/method chain
lookup, string-constant globals) plus the single-home
ObjcDefinedStateField type into src/ir/lower/objc_class.zig. 23 aliases
on Lowering keep all call sites (incl. expr_typer.zig facade and
lower/stmt.zig) unchanged. Zero pub-flips — all callees were already
public from earlier steps.

Gate: zig build OK; zig build test 426/426; run_examples 541/0; all 37
.ir snapshots byte-identical, zero expected/ churn.
2026-06-10 14:14:49 +03:00
agra
638a048cc5 refactor(B6.1): move Obj-C/JNI call lowering to lower/ffi.zig
Verbatim relocation of the 24-method FFI cluster (selector/class-object
interning, FFI intrinsic + JNI calls, foreign instance/static method
lowering, super calls, foreign-class registration, Obj-C defined-class
method registration, JNI env TLS fids, JNI main-stub synthesis) plus
the file-scope jniMapParamType (no alias needed — all callers moved)
into src/ir/lower/ffi.zig. 24 aliases on Lowering keep all call sites
unchanged.

Method pub-flips: emitObjcDefinedAllocAndInit, findForeignMethodInChain.

Gate: zig build OK; zig build test 426/426; run_examples 541/0; all 37
.ir snapshots byte-identical, zero expected/ churn.
2026-06-10 14:03:21 +03:00
agra
1af80a4c38 Merge branch 'arch-b' (phase B5: lower/protocol.zig + lower/coerce.zig) 2026-06-10 14:00:01 +03:00
agra
e884e87f80 refactor(B5.2): move coercions to lower/coerce.zig
Verbatim relocation of the 19-method coercion cluster (lowerXX, user
conversions, protocol erasure, default-value construction, zero values,
coerceToType implicit/explicit ladder, C-variadic promotion, call-arg
coercion) plus the nested single-home CoerceMode enum into
src/ir/lower/coerce.zig. 19 aliases on Lowering keep all call sites
unchanged.

Method pub-flip: prependCtxIfNeeded. ParamImplEntry stays a Lowering
nested type (field type of param_impl_map) and is reached via an alias
const.

Gate: zig build OK; zig build test 426/426; run_examples 541/0; zero
expected/ snapshot churn.
2026-06-10 13:58:09 +03:00
agra
8990bd4978 refactor(B5.1): move protocol emission to lower/protocol.zig
Verbatim relocation of the 13-method protocol cluster (protocol decl
registration, param-protocol instantiation, thunk creation, vtable
globals, protocol-value construction, dispatch emission, impl lookup)
into src/ir/lower/protocol.zig. 13 fn aliases on Lowering keep all call
sites unchanged.

Two pub nested types travelled with the run (ProjectionPosition,
PackProjection) and are re-exposed via Lowering type aliases; they are
pack-domain types and may relocate to lower/pack.zig in B7.2.

Method pub-flips: allocViaContext, callForeign, genericInstanceMethod,
monomorphizeFunction.

Gate: zig build OK; zig build test 426/426; run_examples 541/0; zero
expected/ snapshot churn.
2026-06-10 13:54:59 +03:00
agra
c10aaa1482 Merge branch 'arch-b' (phase B4: lower/decl.zig + lower/nominal.zig) 2026-06-10 13:52:11 +03:00
agra
5928d9f067 refactor(B4.2): move nominal-type registration to lower/nominal.zig
Verbatim relocation of the 23-method nominal-type cluster (struct/enum/
union/error-set registration, anon-type qualification, nominal-id
stamping, shadow-slot reservation, named-type interning, generic struct
templates + alias registration) plus the nested ShadowTypeDecl union
into src/ir/lower/nominal.zig. 23 aliases on Lowering keep all call
sites unchanged.

Method pub-flip: instantiateGenericStruct. nominal.zig reaches
VisibleStructAuthor and structDeclOfRaw (both relocated to decl.zig in
B4.1) via Lowering-namespace alias consts.

Gate: zig build OK; zig build test 426/426; run_examples 541/0; zero
expected/ snapshot churn.
2026-06-10 13:51:15 +03:00
agra
13f5fc57c1 refactor(B4.1): move decl scan + fn-lowering entry to lower/decl.zig
Verbatim relocation of the 52-method declaration cluster (lowerRoot,
scanDecls, entry-point checks, const/global registration, forward-alias
resolution, declareFunction + qualified registration, visibility,
lazy/deferred function lowering, same-name author selection) plus four
nested types (BareCallee, SelectedFunc, TypeHeadResolution,
VisibleStructAuthor) and the file-scope isExportedEntryName (all callers
moved with it) into src/ir/lower/decl.zig. 52 fn aliases + 3 type
aliases on Lowering keep all call sites (incl. calls.zig facade)
unchanged.

Method pub-flips: assertInstanceMapsCoincide, emitDefaultContextGlobal,
headFnLeak, initializerDescription, instantiateTypeFunction,
lookupObjcDefinedClassForMethod, lowerObjcDefinedClassMethods,
registerForeignClassDecl, registerGenericStructAlias,
registerNamespacedForeignClasses, reserveShadowSlot,
resolveTypeCallWithBindings, selectGenericStructHead,
synthesizeJniMainStubs, typeResolver, hasComptimeParams, isPlainFreeFn,
topLevelTypeDecl, isPackFn. Nested-type exposure: FnBodyReentry (+
enter/restore), ShadowTypeDecl.key/name/isGeneric.

Gate: zig build OK; zig build test 426/426; run_examples 541/0; zero
expected/ snapshot churn.
2026-06-10 13:47:41 +03:00
agra
1efafdbdf7 Merge branch 'arch-b' (phase B3: lower/stmt.zig + lower/control_flow.zig) 2026-06-10 13:43:19 +03:00
agra
8adfc1dd50 refactor(B3.2): move control flow to lower/control_flow.zig
Verbatim relocation of the 14-method control-flow cluster (if/while/
for/match lowering incl. comptime-inline variants, break/continue, block
plumbing: freshBlock, freshBlockWithParams, currentBlockHasTerminator,
ensureTerminator) into src/ir/lower/control_flow.zig. 14 aliases on
Lowering keep all call sites unchanged.

Method pub-flips: computeHasImpl, headTypeGate, inferMatchResultType,
resolveTypeCategoryTags, isTypeCategoryMatch. Unqualified references in
moved bodies (ComptimeValue nested type, isTypeCategoryMatch static)
resolved via file-scope alias consts — bodies stay verbatim.

Gate: zig build OK; zig build test 426/426; run_examples 541/0; zero
expected/ snapshot churn.
2026-06-10 13:18:07 +03:00
agra
3dbd3ce072 refactor(B3.1): move statement lowering to lower/stmt.zig
Verbatim relocation of the 24-method statement cluster (block/stmt
dispatch, var/const/local-fn decls, return, assignment + compound ops,
multi-assign/destructure, push, defer/onfail/cleanup) plus the nested
single-home FieldLvalue type into src/ir/lower/stmt.zig. 24 aliases on
Lowering keep all call sites unchanged.

Method pub-flips: buildDefaultValue, buildProtocolErasure,
diagPackAsValue, diagnoseMissingContext, emitError, emitFieldError,
ensureTerminator, getExprAlloca, getJniEnvTlFids, isPackName,
lazyLowerFunction, lowerObjcDefinedStateForObj, lowerObjcPropertySetter,
recordLocalTypeName, registerEnumDecl, registerErrorSetDecl,
registerStructDecl, registerUnionDecl, zeroValue.

Gate: zig build OK; zig build test 426/426; run_examples 541/0; zero
expected/ snapshot churn.
2026-06-10 13:14:51 +03:00
agra
de6e826d21 Merge branch 'arch-b' (phase B2: lower/comptime.zig) 2026-06-10 13:12:18 +03:00
agra
856299ce36 refactor(B2.1): move comptime hooks + const folding to lower/comptime.zig
Verbatim relocation of the 26-method comptime cluster (comptime eval
hooks, #insert, comptime calls/deps/substitution, source-const folding
and module-const selection) plus the three nested const-selection types
(SelectedConst, ConstAuthor, ConstSourcePin) into src/ir/lower/
comptime.zig. 26 fn aliases + SelectedConst type alias on Lowering keep
all call sites unchanged.

Shared file-scope helpers stay in lower.zig per the helpers-stay-home
rule, now pub: ConstFoldFrame, constFoldFrameContains, SourceConstCtx.
Method pub-flips: findVariantIndex, putGlobal, tryLowerAsExpr,
lowerVariadicArgs, resolver, setCurrentSourceFile, diagNonIntegralNarrow,
lowerStmt, stampCallerSource, resolveParamType, resolveReturnType.

Gate: zig build OK; zig build test 426/426; run_examples 541/0; zero
expected/ snapshot churn.
2026-06-10 13:11:33 +03:00
agra
91d01d0048 Merge branch 'arch-b' (phase B1: lower/error.zig) 2026-06-10 13:05:43 +03:00
agra
b240810b6f refactor(B1.1): move error-channel lowering to lower/error.zig
Verbatim relocation of the 53-method error cluster (error typing,
raise/failable, try/catch/or, inferred-set convergence, trace runtime
hooks) out of the Lowering struct into src/ir/lower/error.zig as free
functions taking *Lowering. Each gets a pub-const alias on Lowering, so
every call site compiles unchanged (decl-alias method resolution).

Pub-flips (callees now referenced cross-file): lowerExpr, coerceToType,
freshBlock, freshBlockWithParams, emitErrorCleanup,
currentBlockHasTerminator, lowerBlock, lowerBlockValue.

Gate: zig build OK; zig build test 426/426; run_examples 541/0; zero
expected/ snapshot churn.
2026-06-10 12:58:46 +03:00
304 changed files with 42180 additions and 36944 deletions

View File

@@ -1,87 +0,0 @@
name: Build
on:
push:
branches: [master]
tags: ['v*']
pull_request:
branches: [master]
jobs:
build-linux:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Install Zig
uses: mlugg/setup-zig@v2
with:
version: master
- name: Install LLVM 18
run: sudo apt-get update && sudo apt-get install -y llvm-18-dev gcc
- name: Build
run: zig build -Dllvm-prefix=/usr/lib/llvm-18
- name: Test
run: zig build test -Dllvm-prefix=/usr/lib/llvm-18 --summary all
- name: Package
run: tar czf sx-linux-x86_64.tar.gz -C zig-out/bin .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: sx-linux-x86_64
path: zig-out/bin/
- name: Release
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@v2
with:
files: sx-linux-x86_64.tar.gz
build-windows:
runs-on: windows-latest
env:
LLVM_PREFIX: C:\LLVM\llvm-18.1.8-windows-amd64-msvc17-msvcrt
steps:
- uses: actions/checkout@v4
- name: Install Zig
uses: mlugg/setup-zig@v2
with:
version: master
- name: Download LLVM 18
run: |
Invoke-WebRequest -Uri "https://github.com/vovkos/llvm-package-windows/releases/download/llvm-18.1.8/llvm-18.1.8-windows-amd64-msvc17-msvcrt.7z" -OutFile "$env:TEMP\llvm.7z"
7z x "$env:TEMP\llvm.7z" -oC:\LLVM
- name: Setup MSVC
uses: ilammy/msvc-dev-cmd@v1
- name: Build
shell: cmd
run: zig build -Dstatic-llvm -Dllvm-prefix=%LLVM_PREFIX% -Dtarget=x86_64-windows-msvc
- name: Test
shell: cmd
continue-on-error: true
run: zig build test -Dstatic-llvm -Dllvm-prefix=%LLVM_PREFIX% -Dtarget=x86_64-windows-msvc --summary all
- name: Package
run: Compress-Archive -Path zig-out\bin\* -DestinationPath sx-windows-x86_64.zip
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: sx-windows-x86_64
path: zig-out/bin/
- name: Release
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@v2
with:
files: sx-windows-x86_64.zip

View File

@@ -1,5 +1,5 @@
// Range-based for loops: `for start..end: (i) { }` (cursor optional, `end`
// exclusive) is a runtime counting loop; `inline for start..end: (i) { }`
// 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).
@@ -16,14 +16,14 @@ 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) {
inline for 0..xs.len (i) {
print("[{}]={}\n", i, xs[i].show());
}
}
main :: () -> s32 {
// Runtime range, cursor used.
for 0..3: (i) { print("i={}\n", i); }
for 0..3 (i) { print("i={}\n", i); }
// Runtime range, no cursor — body runs `end - start` times.
n := 0;
@@ -31,7 +31,7 @@ main :: () -> s32 {
print("n={}\n", n);
// Non-zero start.
for 2..5: (j) { print("j={}\n", j); }
for 2..5 (j) { print("j={}\n", j); }
// Inline unroll over a heterogeneous pack.
each(A.{ x = 1 }, B.{ s = "hi" }, A.{ x = 3 });

View File

@@ -1,4 +1,4 @@
// `for xs: (*x)` binds each element by pointer — no per-element copy.
// `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";
@@ -10,12 +10,12 @@ Shape :: enum {
main :: () -> s32 {
// By-ref mutation writes back into the array (impossible with a value copy).
xs : [3]s64 = .[1, 2, 3];
for xs: (*x) { x.* = x + 100; }
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) {
for shapes (*s) {
if s == {
case .circle: (r) { print("circle {}\n", r); }
case .none: { print("none\n"); }

View File

@@ -10,7 +10,7 @@ Box :: struct {
sum_ptr :: (xs: *List(s64)) -> s64 {
total : s64 = 0;
for xs: (n) { total = total + n; } // iterate through a *List
for xs (n) { total = total + n; } // iterate through a *List
total
}
@@ -21,12 +21,12 @@ main :: () -> s32 {
xs.append(30);
s : s64 = 0;
for xs: (n) { s = s + n; } // value capture
for xs (n) { s = s + n; } // value capture
print("sum {}\n", s); // 60
for xs: (*n) { n.* = n + 100; } // by-ref: writes back
for xs (*n) { n.* = n + 100; } // by-ref: writes back
s = 0;
for xs: (n) { s = s + n; }
for xs (n) { s = s + n; }
print("sum2 {}\n", s); // 360
print("via ptr {}\n", sum_ptr(@xs)); // 360
@@ -34,7 +34,7 @@ main :: () -> s32 {
bs := List(Box).{};
bs.append(.{ v = 7 });
bt : s64 = 0;
for bs: (*b) { bt = bt + b.boxed(); } // *Box receiver, value-self method
for bs (*b) { bt = bt + b.boxed(); } // *Box receiver, value-self method
print("boxes {}\n", bt); // 7
0
}

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
Color :: enum { red; green; blue; }

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
add :: (a: s32, b: s32) -> s32 { a + b }

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
main :: () {
@@ -118,7 +118,7 @@ main :: () {
// For loop basic
farr : [4]s32 = .[10, 20, 30, 40];
out("for:");
for farr: (it) {
for farr (it) {
out(" ");
out(int_to_string(it));
}
@@ -126,14 +126,14 @@ main :: () {
// For with print
out("for-print:");
for farr: (it) {
for farr (it) {
print(" {}", it);
}
out("\n");
// For with index
out("for-idx:");
for farr: (_, ix) {
for farr, 0.. (_, ix) {
out(" ");
out(int_to_string(ix));
}
@@ -141,14 +141,14 @@ main :: () {
// For with print two args
out("for-2arg:");
for farr: (it, ix) {
for farr, 0.. (it, ix) {
print(" {}@{}", it, ix);
}
out("\n");
// For with break
out("for-break:");
for farr: (it) {
for farr (it) {
if it == 30 { break; }
print(" {}", it);
}
@@ -156,7 +156,7 @@ main :: () {
// For with continue
out("for-continue:");
for farr: (it) {
for farr (it) {
if it == 20 { continue; }
print(" {}", it);
}
@@ -165,14 +165,14 @@ main :: () {
// For on slice
fsl : []s32 = .[10, 20, 30];
out("for-slice:");
for fsl: (it) {
for fsl (it) {
print(" {}", it);
}
out("\n");
// For on slice with index
out("for-slice-idx:");
for fsl: (it, ix) {
for fsl, 0.. (it, ix) {
print(" {}:{}", ix, it);
}
out("\n");
@@ -181,8 +181,8 @@ main :: () {
nf_a : [2]s32 = .[0, 1];
nf_b : [2]s32 = .[0, 1];
out("for-nested:");
for nf_a: (oa) {
for nf_b: (ob) {
for nf_a (oa) {
for nf_b (ob) {
print(" ({},{})", oa, ob);
}
}
@@ -191,7 +191,7 @@ main :: () {
// For with break preserving index
fbi : [5]s32 = .[10, 20, 30, 40, 50];
fbi_idx := 0;
for fbi: (it, ix) {
for fbi, 0.. (it, ix) {
if it == 30 { fbi_idx = ix; break; }
}
print("for-break-idx: {}\n", fbi_idx);

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
add :: (a: s32, b: s32) -> s32 { a + b }
@@ -16,7 +16,7 @@ pair_add :: (a: $T, b: $U) -> s64 {
typed_sum :: (..args: []s32) -> s32 {
result := 0;
for args: (it) { result = result + it; }
for args (it) { result = result + it; }
result
}

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
main :: () {

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
Point :: struct { x, y: s32; }

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
Point :: struct { x, y: s32; }

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
Point :: struct { x, y: s32; }

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
Point :: struct { x, y: s32; }

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
main :: () {

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
main :: () {

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
main :: () {

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
add :: (a: s32, b: s32) -> s32 { a + b }

View File

@@ -24,7 +24,7 @@ clamp :: (x: s64) -> s64 { if x > 10 { return 10; } return x; }
main :: () -> s32 {
print("const_one={}\n", const_one()); // 1
print("raised={}\n", always_raise(5) catch e 0); // 0
print("raised={}\n", always_raise(5) catch (e) 0); // 0
print("clamp_hi={}\n", clamp(42)); // 10
print("clamp_lo={}\n", clamp(7)); // 7

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 :: () -> s32 {
sum := 0;
for 0..1000000 (i) {
buf : [128]s64 = ---;
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 :: () -> s32 {
arr : [4096]s64 = ---;
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]s64 = .[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 :: () -> s32 {
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: []s64, ys: []s64) -> s64 {
total := 0;
for xs, ys (x, y) { total += x * y; }
total
}
make :: () -> [3]s64 {
r : [3]s64 = .[7, 8, 9];
r
}
main :: () -> s32 {
// 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]s64 = .[10, 20, 30];
for xs, 0.. (x, i) { print("[{}]={} ", i, x); }
print("\n");
// Parallel slices.
a4 : [4]s64 = .[1, 2, 3, 4];
b4 : [4]s64 = .[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 :: () -> s32 {
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]s64 = .[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: []s64, tag: string) {
print("{}: ", tag);
for s (v) { print("{} ", v); }
print("(len {})\n", s.len);
}
main :: () -> s32 {
xs : [6]s64 = .[10, 11, 12, 13, 14, 15];
full : []s64 = 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

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
Point :: struct { x, y: s32; }

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
Perms :: enum flags { read; write; execute; }

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
main :: () {

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
Point :: struct { x, y: s32; }
@@ -16,7 +16,7 @@ main :: () {
spts : [2]Point = .[Point.{1, 2}, Point.{3, 4}];
spt2 := spts[1];
print("arr-struct-x: {}\n", spt2.x);
for spts: (it) {
for spts (it) {
print("for-struct: {}\n", it);
}
}

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
main :: () {

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
main :: () {

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
main :: () {

View File

@@ -1,8 +1,8 @@
#import "modules/std.sx";
#import "modules/allocators.sx"; // `Allocator` is non-transitive: name it, import it.
#import "modules/std/mem.sx"; // `Allocator` is non-transitive: name it, import it.
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
Point :: struct { x, y: s32; }

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
// ============================================================

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
Point :: struct { x, y: s32; }

View File

@@ -63,6 +63,6 @@ main :: () {
mk : Make(N, s64) = ---; mk[2] = 33; print("vp.typefn.expr: len={} v={}\n", mk.len, mk[2]);
// inline-for bound — expr const (3) and integral float (4)
s := 0; inline for 0..N: (i) { s += i; } print("for.expr: {}\n", s); // 0+1+2 = 3
t := 0; inline for 0..F: (i) { t += i; } print("for.float: {}\n", t); // 0+1+2+3 = 6
s := 0; inline for 0..N (i) { s += i; } print("for.expr: {}\n", s); // 0+1+2 = 3
t := 0; inline for 0..F (i) { t += i; } print("for.float: {}\n", t); // 0+1+2+3 = 6
}

View File

@@ -35,7 +35,7 @@ main :: () -> s32 {
// for capture + index names
xs := [3]s64.{ 10, 20, 30 };
for xs: (`bool, `u16) {
for xs, 0.. (`bool, `u16) {
print("for = {} @ {}\n", `bool, `u16);
}

View File

@@ -0,0 +1,42 @@
// Integer literals default to s64 regardless of context: an unannotated
// `x := <int literal>` local stays s64 even inside a function whose return
// type is a narrower integer (the implicit-return target must not type the
// body's declarations), and a large literal initializer keeps its value.
// Also covers destructure decls (`a, b := ...`), which share the same rule.
// Regression (issue 0111): these locals adopted the enclosing fn's return
// type (s32/s8), silently wrapping `big := 3000000000` to -1294967296.
#import "modules/std.sx";
f :: () -> s32 {
x := 0;
print("f.x: {}\n", type_name(type_of(x)));
0
}
g :: () -> s8 {
x := 0;
print("g.x: {}\n", type_name(type_of(x)));
0
}
big_host :: () -> s32 {
big := 3000000000;
print("big: {} = {}\n", type_name(type_of(big)), big);
0
}
d_host :: () -> s32 {
a, b := (1, 2);
print("a: {} b: {}\n", type_name(type_of(a)), type_name(type_of(b)));
0
}
main :: () {
f();
g();
big_host();
d_host();
x := 0;
print("main.x: {}\n", type_name(type_of(x)));
}

View File

@@ -0,0 +1,22 @@
// Boundary and exemption cases for the int-literal fits-check: extreme
// in-range values compile (incl. negated literals via the constant fold);
// width-64 types accept any representable literal; explicit `xx` / `cast`
// still truncate on request; literal call args check against param types.
#import "modules/std.sx";
clamp_s8 :: (v: s8) -> s8 { v }
main :: () {
a : s8 = -128;
b : s8 = 127;
c : u8 = 0;
d : u8 = 255;
e : u64 = 0x7FFFFFFFFFFFFFFF;
f : u32 = 0xFFFFFFFF;
g : s16 = -32768;
h : s8 = xx 300; // explicit truncation stays legal
i := cast(s8) 300; // cast form too
j : s8 = clamp_s8(-5);
print("{} {} {} {} {} {} {} {} {} {}\n", a, b, c, d, e, f, g, h, i, j);
}

View File

@@ -0,0 +1,16 @@
// A negated literal is a compile-time constant for a global initializer:
// ints serialize directly, an integral negative float narrows into an
// integer global (non-integral errors), and boundary values fit exactly.
// Out-of-range negatives get the literal fits-check, not "non-constant".
// Regression (issue 0113): `g : s64 = -1;` was rejected as not a
// compile-time constant (globalInitValue had no unary_op arm).
#import "modules/std.sx";
g1 : s64 = -1;
g2 : s64 = -4.0;
g3 : s8 = -128;
main :: () {
print("{} {} {}\n", g1, g2, g3);
}

View File

@@ -5,5 +5,5 @@ main :: () {
s
}
print("{}\n", fx(133));
print("{}\n", fx(-3));
}

View File

@@ -12,7 +12,7 @@
// first 4 bytes of malloc'd memory interpreted as `s32` (= 0 → null).
#import "modules/std.sx";
#import "modules/allocators.sx";
#import "modules/std/mem.sx";
main :: () -> s32 {
gpa := GPA.init();

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
Point :: struct { x, y: s32; }
@@ -61,7 +61,7 @@ pair_add :: (a: $T, b: $U) -> s64 {
typed_sum :: (..args: []s32) -> s32 {
result := 0;
for args: (it) { result = result + it; }
for args (it) { result = result + it; }
result
}
@@ -169,7 +169,7 @@ sm_pair :: (a: s32, b: s32) -> (s32, s32, !) {
// `catch` block that diverges (logs the tag, then returns a fallback)
sm_or_default :: (n: s32) -> s32 {
return sm_parse(n) catch e {
return sm_parse(n) catch (e) {
print(" logged {}\n", e);
return -1;
};

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
Point :: struct { x, y: s32; }

View File

@@ -2,14 +2,14 @@
sum :: (..args: []s32) -> s32 {
result := 0;
for args: (it) {
for args (it) {
result = result + it;
}
result
}
print_all :: (..args: []s32) {
for args: (it) {
for args (it) {
out(int_to_string(it));
out(" ");
}
@@ -26,7 +26,7 @@ main :: () -> s32 {
out(int_to_string(sum(..arr)));
out("\n");
for arr: (it) {
for arr (it) {
out(int_to_string(it));
out(" ");
}

View File

@@ -7,7 +7,7 @@ Point :: struct {
// Print all arguments — accepts any type, dispatches via type-switch
print_any :: (..args: []Any) {
for args: (it) {
for args (it) {
type := type_of(it);
if type == {
case int: out(int_to_string(cast(s32) it));

View File

@@ -6,7 +6,7 @@
// constant (a literal, or an `inline for` cursor). A runtime index (here a
// `while`-loop counter) must produce a clear diagnostic, not the confusing
// "unresolved 'args'" the slice-index fall-through used to give. To walk a
// pack, use `inline for 0..args.len: (i) { ... }`, which unrolls so each
// pack, use `inline for 0..args.len (i) { ... }`, which unrolls so each
// `args[i]` is a comptime index.
#import "modules/std.sx";

View File

@@ -19,7 +19,7 @@
// match the impl's protocol type-args exactly.
#import "modules/std.sx";
#import "modules/allocators.sx";
#import "modules/std/mem.sx";
// User-defined parameterised protocol + an impl, so has_impl can
// confirm parameterised matching works with a known-true case.

View File

@@ -14,7 +14,7 @@ sink :: (v: s64) -> void { _ = v; }
storage :: (..xs: Show) -> void { y := xs; _ = y; } // A: store
call :: (..xs: Show) -> void { sink(xs); } // B: pass to a call
ret :: (..xs: Show) -> s64 { return xs; } // C: return
iter :: (..xs: Show) -> void { for xs : (x) { _ = x; } } // D: runtime iterate
iter :: (..xs: Show) -> void { for xs (x) { _ = x; } } // D: runtime iterate
main :: () -> s32 {
storage(A.{});

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
add :: (a: s32, b: s32) -> s32 { a + b }

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
main :: () {

View File

@@ -14,10 +14,10 @@ M :: 3;
main :: () {
s := 0;
inline for 0..M: (i) { s += i; }
inline for 0..M (i) { s += i; }
print("sum 0..M = {}\n", s); // 0 + 1 + 2 = 3
t := 0;
inline for 0..(M + 1): (i) { t += i; }
inline for 0..(M + 1) (i) { t += i; }
print("sum 0..(M+1) = {}\n", t); // 0 + 1 + 2 + 3 = 6
}

View File

@@ -9,6 +9,6 @@ M :: 3.0;
main :: () {
s := 0;
inline for 0..M: (i) { s += i; }
inline for 0..M (i) { s += i; }
print("sum 0..M = {}\n", s); // 0 + 1 + 2 = 3
}

View File

@@ -11,14 +11,14 @@
main :: () {
s := 0;
inline for -2..1: (i) { s += i; }
inline for -2..1 (i) { s += i; }
print("inline for -2..1 sum = {}\n", s); // -2 + -1 + 0 = -3
r := 0;
for -2..1: (i) { r += i; }
for -2..1 (i) { r += i; }
print("for -2..1 sum = {}\n", r); // -2 + -1 + 0 = -3 (runtime parity)
e := 0;
inline for 0..(-2.0): (i) { e += i; }
inline for 0..(-2.0) (i) { e += i; }
print("inline for 0..(-2.0) sum = {}\n", e); // empty range -> 0 iterations
}

View File

@@ -13,7 +13,7 @@
#import "modules/std.sx";
#import "modules/std/hash.sx";
#import "modules/fs.sx";
#import "modules/std/fs.sx";
// 112-byte NIST multi-block vector — long enough that a 64-byte split is
// a genuine block boundary and a 30-byte split lands mid-block.

View File

@@ -18,9 +18,9 @@
// and freed in one `deinit`; the writer path allocates nothing.
#import "modules/std.sx";
#import "modules/allocators.sx"; // `Allocator` is non-transitive: name it, import it.
#import "modules/std/mem.sx"; // `Allocator` is non-transitive: name it, import it.
#import "modules/std/json.sx";
#import "modules/fs.sx";
#import "modules/std/fs.sx";
// The exact document the writer must produce (insertion order, escaping).
EXPECT :: "{\"name\":\"a\\\"b\\n\",\"tab\":\"x\\ty\",\"bs\":\"c\\\\d\",\"ctrl\":\"\\u0001\",\"n\":-7,\"zero\":0,\"pos\":7,\"min\":-9223372036854775808,\"max\":9223372036854775807,\"ok\":true,\"nil\":null,\"xs\":[1,-2,3],\"nested\":{\"k\":\"v\"}}";

View File

@@ -20,7 +20,7 @@
// `JsonParseError` variant on the error channel, never a bogus value.
#import "modules/std.sx";
#import "modules/allocators.sx"; // `Allocator` is non-transitive: name it, import it.
#import "modules/std/mem.sx"; // `Allocator` is non-transitive: name it, import it.
#import "modules/std/json.sx";
// Canonical document: no insignificant whitespace, escapes in the writer's

View File

@@ -23,7 +23,7 @@
// and decoded strings go through `alloc`, and the writer allocates nothing.
#import "modules/std.sx";
#import "modules/allocators.sx"; // `Allocator` is non-transitive: name it, import it.
#import "modules/std/mem.sx"; // `Allocator` is non-transitive: name it, import it.
#import "modules/std/json.sx";
// The writer's EXACT output for the PART A document (insertion order,

View File

@@ -19,7 +19,7 @@
#import "modules/std.sx";
#import "modules/std/cli.sx";
log :: #import "modules/log.sx";
log :: #import "modules/std/log.sx";
report :: (label: string, ok: bool) {
if ok { print("{}: ok\n", label); } else { print("{}: FAIL\n", label); }

View File

@@ -12,7 +12,7 @@
// independent identities.
#import "modules/std.sx";
#import "modules/allocators.sx"; // `Allocator` is non-transitive: name it, import it.
#import "modules/std/mem.sx"; // `Allocator` is non-transitive: name it, import it.
// `cli` is imported BOTH flat (so its types — `FlagSpec` / `Command` / `Diag` —
// are bare-visible) AND namespaced (so the same-name `cli.parse` stays a
// distinct qualified identity from `json.parse`). Post-E1 a bare reference to a

View File

@@ -4,7 +4,7 @@
combine :: (x: s64, y: s64) -> s64 { return x + y; }
pick :: (..xs: []s64) -> s64 {
result := 0;
for xs: (it) { result = result + it; }
for xs (it) { result = result + it; }
result
}
from_a_combine :: () -> s64 { return combine(10, 20); }

View File

@@ -4,7 +4,7 @@
// pack, pick subtracts its two fixed args.
combine :: (..xs: []s64) -> s64 {
result := 0;
for xs: (it) { result = result + it; }
for xs (it) { result = result + it; }
result
}
pick :: (a: s64, b: s64) -> s64 { return b - a; }

View File

@@ -3,7 +3,7 @@
// that protocol. Exercises save/restore of the boxed context across the push.
#import "modules/std.sx";
#import "modules/allocators.sx";
#import "modules/std/mem.sx";
main :: () -> void {
arena := Arena.init(context.allocator, 4096);

View File

@@ -9,7 +9,7 @@
// 3. Static method with inline xx: `T.init(xx p, ...)` ← used to crash
#import "modules/std.sx";
#import "modules/allocators.sx";
#import "modules/std/mem.sx";
Box :: struct {
parent: Allocator;

View File

@@ -4,7 +4,7 @@
// whether the recovery happens BEFORE or AFTER the first dispatch.
#import "modules/std.sx";
#import "modules/allocators.sx"; // `Allocator` is non-transitive: name it, import it.
#import "modules/std/mem.sx"; // `Allocator` is non-transitive: name it, import it.
main :: () -> s32 {
gpa := GPA.init();

View File

@@ -9,7 +9,7 @@
// the operand's storage, so it never allocates and never reaches this
// path. See specs.md §3 — Protocol value ownership and lifetime.
#import "modules/std.sx";
#import "modules/allocators.sx"; // `Allocator` is non-transitive: name it, import it.
#import "modules/std/mem.sx"; // `Allocator` is non-transitive: name it, import it.
Tracer :: struct {
count: s64;

View File

@@ -8,7 +8,7 @@
// and the local would stay at zero. With Option 3 the local sees the
// increments because they ARE the local.
#import "modules/std.sx";
#import "modules/allocators.sx";
#import "modules/std/mem.sx";
main :: () -> s32 {
gpa := GPA.init();

View File

@@ -1,7 +1,7 @@
#import "modules/std.sx";
#import "modules/math/math.sx";
#import "modules/compiler.sx";
#import "modules/test.sx";
#import "modules/std/test.sx";
pkg :: #import "modules/testpkg";
main :: () {

View File

@@ -21,7 +21,7 @@ IoErr :: error { Disk }
main :: () -> s32 {
fail_own := closure(() -> !IoErr { raise error.Disk; });
fail_own() catch e {
fail_own() catch (e) {
if e == error.Disk { print("own=Disk\n"); }
};
d := dep_err();

View File

@@ -0,0 +1,20 @@
// Namespace aliases are module surface: a `ns :: #import` declared by a
// module is usable by that module's DIRECT flat importers (the carry rule —
// no `pub` keyword). Covers every qualified shape through BOTH a direct and
// a carried alias: plain fn, struct static method + instance method, type
// annotation, enum variant, module const, generic struct.
#import "modules/std.sx";
#import "0831-modules-namespace-alias-carry/facade.sx";
main :: () {
print("{} ", r.helper()); // plain fn (carried)
t := r.Thing.init(); // static method
print("{} ", t.get());
x : r.Thing = r.Thing.init(); // type annotation
print("{} ", x.v);
print("{} ", r.LIMIT); // module const
print("{} ", r.Color.green); // enum variant
b := r.Box(s64).{ item = 3 }; // generic struct
print("{}\n", b.item);
}

View File

@@ -0,0 +1 @@
r :: #import "rich.sx";

View File

@@ -0,0 +1,9 @@
Thing :: struct {
v: s64;
init :: () -> Thing { Thing.{ v = 5 } }
get :: (self: *Thing) -> s64 { self.v }
}
Color :: enum { red; green; }
LIMIT :s64: 99;
Box :: struct ($T: Type) { item: T; }
helper :: () -> s64 { 7 }

View File

@@ -20,7 +20,7 @@ must :: (n: s32) -> !E {
// Diverging body — returns from `classify` on error.
classify :: (n: s32) -> s32 {
must(n) catch e {
must(n) catch (e) {
if e == error.Bad { return 1; }
if e == error.Empty { return 2; }
return 9;
@@ -28,9 +28,9 @@ classify :: (n: s32) -> s32 {
return 0; // must(n) succeeded
}
// Match-body form — sugar for `catch e { if e == { case ... } }`.
// Match-body form — sugar for `catch (e) { if e == { case ... } }`.
mclassify :: (n: s32) -> s32 {
must(n) catch e == {
must(n) catch (e) == {
case .Bad: return 11;
case .Empty: return 22;
else: return 99;
@@ -41,7 +41,7 @@ mclassify :: (n: s32) -> s32 {
// Selective handle + re-raise (failable enclosing fn; `raise e` is the
// variable form). Swallows Bad → success; re-raises everything else.
handle_some :: (n: s32) -> !E {
must(n) catch e {
must(n) catch (e) {
if e == error.Bad { return; } // swallow → success
raise e; // re-raise the rest
};
@@ -50,7 +50,7 @@ handle_some :: (n: s32) -> !E {
main :: () -> s32 {
r : s32 = 0;
must(-1) catch e { if e == error.Bad { r = r + 1; } }; // Bad → +1
must(-1) catch (e) { if e == error.Bad { r = r + 1; } }; // Bad → +1
must(5) catch { r = r + 100; }; // success → body skipped
r = r + classify(0); // Empty → 2
r = r + classify(8); // success → 0

View File

@@ -8,6 +8,6 @@
plain :: () -> s32 { return 0; }
main :: () -> s32 {
plain() catch e { return 1; }; // error: operand has type s32 (not failable)
plain() catch (e) { return 1; }; // error: operand has type s32 (not failable)
return 0;
}

View File

@@ -2,7 +2,7 @@
// the consumer side of the error-channel tuple ABI). `try f()` on a
// `-> (T, !E)` callee binds the value slot on success and propagates the error
// on failure (a pure-failable caller returns the tag; a value-carrying caller
// returns `{undef, tag}`). `f() catch e BODY` yields the value slot on success
// returns `{undef, tag}`). `f() catch (e) BODY` yields the value slot on success
// or the handler body's value on failure, merged through a block parameter.
// The producer side is `examples/228-value-failable.sx`.
@@ -31,12 +31,12 @@ relay :: (n: s32) -> !E {
// value-carrying `catch`, bare-expression fallback.
safe :: (n: s32) -> s32 {
return parse(n) catch e 0;
return parse(n) catch (e) 0;
}
// value-carrying `catch`, match-body value.
classify :: (n: s32) -> s32 {
return parse(n) catch e == {
return parse(n) catch (e) == {
case .Bad: 1;
case .Empty: 2;
else: 3

View File

@@ -14,6 +14,6 @@ parse :: (n: s32) -> (s32, !E) {
}
main :: () -> s32 {
x := parse(-1) catch e { print("oops\n") }; // error: body yields no value
x := parse(-1) catch (e) { print("oops\n") }; // error: body yields no value
return x;
}

View File

@@ -2,7 +2,7 @@
// (ERR step E1.7). Unlike `defer` (which runs on every exit), `onfail` fires
// on an error exit — a `raise` or a propagating `try` — and is skipped on
// success. On an error exit `defer` and `onfail` run interleaved in reverse
// declaration order. `onfail e { … }` binds the in-flight error tag.
// declaration order. `onfail (e) { … }` binds the in-flight error tag.
// (Per-attempt-`try` gating and `or`-chain absorption refine this in E2.4b.)
#import "modules/std.sx";
@@ -25,7 +25,7 @@ run :: (n: s32) -> !E {
// `onfail e` binds the tag.
classify :: (n: s32) -> !E {
onfail e { if e == error.Bad { print("cleanup: bad\n"); } }
onfail (e) { if e == error.Bad { print("cleanup: bad\n"); } }
if n < 0 { raise error.Bad; }
return;
}

View File

@@ -26,13 +26,13 @@ inc :: (n: s32) -> (s32, s32, !E) {
// Multi-value `catch`, bare-expression tuple fallback (absorbs the failure).
safe :: (n: s32) -> s32 {
v, b := parse(n) catch e (40, 50);
v, b := parse(n) catch (e) (40, 50);
return v + b;
}
// Multi-value `catch` match-body — per-tag dispatch, each arm a value-tuple.
classify :: (n: s32) -> s32 {
v, b := parse(n) catch e == {
v, b := parse(n) catch (e) == {
case .Bad: (1, 1);
case .Empty: (2, 2);
else: (9, 9);

View File

@@ -17,8 +17,8 @@ g :: () -> !E { return; }
f :: () -> !E {
defer { return; } // ERROR: return in defer body
onfail { try g(); } // ERROR: try in onfail body
defer { for 0..1: (i) { break; } } // ERROR: break in defer body (transitive through loop)
onfail e { if e == error.Bad { continue; } } // ERROR: continue in onfail body
defer { for 0..1 (i) { break; } } // ERROR: break in defer body (transitive through loop)
onfail (e) { if e == error.Bad { continue; } } // ERROR: continue in onfail body
try g();
return;
}

View File

@@ -20,7 +20,7 @@ main :: () -> s32 {
print("a={} b={}\n", a, b); // a=BadDigit b=Overflow
// A tag bound by `catch` interpolates too (diverging handler).
v := parse(0) catch e {
v := parse(0) catch (e) {
print("parse failed with {}\n", e); // parse failed with Empty
return 0;
};

View File

@@ -28,13 +28,13 @@ main :: () -> s32 {
// cleared when the handler completes (a non-diverging exit), not on entry.
// So inside the handler the frames are still visible (here: the `raise` in
// `fail` + the `try fail` propagation in `propagate` = 2 frames)...
propagate(-1) catch e {
propagate(-1) catch (e) {
print("in catch: len={}\n", sx_trace_len()); // 2 (handler sees the chain)
};
print("after catch: len={}\n", sx_trace_len()); // 0 (absorbed at handler exit)
// A success leaves the buffer empty (nothing pushed).
propagate(1) catch e { };
propagate(1) catch (e) { };
print("after success: len={}\n", sx_trace_len()); // 0
return 0;
}

View File

@@ -1,4 +1,4 @@
// Error return-trace formatting (ERR step E3.3). `library/modules/trace.sx`
// Error return-trace formatting (ERR step E3.3). `library/modules/std/trace.sx`
// reads the trace buffer (E3.1, populated by E3.2's raise/try push wiring) and
// renders it. `trace.print_current()` writes the trace to stderr; the catch
// handler sees the full chain because the absorption clear fires at handler
@@ -9,7 +9,7 @@
// snapshot shows the trace lines interleaved with the `print` (stdout) lines.
#import "modules/std.sx";
trace :: #import "modules/trace.sx";
trace :: #import "modules/std/trace.sx";
// Buffer length probe (the runtime symbol; public read API is the trace module).
sx_trace_len :: () -> u32 #foreign;
@@ -27,7 +27,7 @@ mid :: (n: s32) -> !E {
}
main :: () -> s32 {
mid(-1) catch e {
mid(-1) catch (e) {
print("[stdout] caught {}\n", e); // tag name via the always-linked table
trace.print_current(); // [stderr] the 2-frame trace
};

View File

@@ -30,8 +30,8 @@ main :: () -> (s32, !E) {
r = r + (try fa(0) or try fa(7)); // a fails → b succeeds → 7
r = r + (try fa(0) or try fa(0) or try fa(3)); // first two fail → third → +3 = 10
r = r + (fa(0) or fa(0) or 96); // bare chain + value terminator → +96 = 106
r = r + ((try fa(0) or try fa(0)) catch e 5); // both fail → catch handler → +5 = 111
r = r + ((try fa(0) or try fa(9)) catch e 0); // second succeeds → catch skipped → +9 = 120
r = r + ((try fa(0) or try fa(0)) catch (e) 5); // both fail → catch handler → +5 = 111
r = r + ((try fa(0) or try fa(9)) catch (e) 0); // second succeeds → catch skipped → +9 = 120
try fv(0) or try fv(1); // void chain: first fails → second succeeds

View File

@@ -12,7 +12,7 @@
// single stdout line. Expected exit code: 0.
#import "modules/std.sx";
log :: #import "modules/log.sx";
log :: #import "modules/std/log.sx";
probe :: () -> s32 {
if is_comptime() { return 1; } // comptime interpreter path

View File

@@ -5,7 +5,7 @@
// still appears despite `_exit` skipping the stdio flush.) Expected exit: 42.
#import "modules/std.sx";
proc :: #import "modules/process.sx";
proc :: #import "modules/std/process.sx";
main :: () -> s32 {
print("starting\n");

View File

@@ -4,7 +4,7 @@
// Built on `process.exit`. Expected exit code: 1.
#import "modules/std.sx";
proc :: #import "modules/process.sx";
proc :: #import "modules/std/process.sx";
main :: () -> s32 {
proc.assert(2 + 2 == 4, "arithmetic"); // passes → no-op

View File

@@ -6,7 +6,7 @@
// resolution, so only names print today. Expected exit: 0.
#import "modules/std.sx";
trace :: #import "modules/trace.sx";
trace :: #import "modules/std/trace.sx";
probe :: () {
trace.print_interpreter_frames(); // dumps the chain: __run_0 → inner → probe

View File

@@ -7,7 +7,7 @@
// Expected exit: 0 (the error is caught; the trace is printed during the build).
#import "modules/std.sx";
trace :: #import "modules/trace.sx";
trace :: #import "modules/std/trace.sx";
TErr :: error { Bad };
@@ -20,7 +20,7 @@ mid :: () -> !TErr {
}
probe :: () {
mid() catch e {
mid() catch (e) {
print("comptime caught {}\n", e);
trace.print_current();
};

View File

@@ -31,7 +31,7 @@ sm_pair :: (a: s32, b: s32) -> (s32, s32, !) {
// catch with a diverging block body
sm_or_default :: (n: s32) -> s32 {
return sm_parse(n) catch e {
return sm_parse(n) catch (e) {
print(" logged {}\n", e);
return -1;
};
@@ -61,7 +61,7 @@ sm_run :: (cb: Closure(s32) -> (s32, !SmokeErr), n: s32) -> (s32, !SmokeErr) {
// bare fn-type param: a NON-failable closure literal widens into the failable
// slot (the ∅-widening adapter wraps `{value, 0}`)
sm_widen :: (cb: (s32) -> (s32, !SmokeErr), n: s32) -> s32 {
return cb(n) catch e -1;
return cb(n) catch (e) -1;
}
// generic ($T) value-carrying failable composition, monomorphized per call
@@ -81,11 +81,11 @@ main :: () {
if err2 == error.BadDigit { print("got: {}\n", err2); }
// catch — bare-expr body
ce := sm_parse(0) catch e 100;
ce := sm_parse(0) catch (e) 100;
print("catch-expr: {}\n", ce);
// catch — match-body per-tag dispatch
cm := sm_parse(200) catch e == {
cm := sm_parse(200) catch (e) == {
case .Overflow: 1;
case .Empty: 2;
else: 3;
@@ -105,9 +105,9 @@ main :: () {
if !gerr { print("or-chain: {}\n", g); }
// multi-value failable consumed by catch (tuple body)
p, q := sm_pair(0, 3) catch e (0, 0);
p, q := sm_pair(0, 3) catch (e) (0, 0);
print("pair-catch: {} {}\n", p, q);
p2, q2 := sm_pair(4, 5) catch e (0, 0);
p2, q2 := sm_pair(4, 5) catch (e) (0, 0);
print("pair-ok: {} {}\n", p2, q2);
// pure failable: absorb with no-binding catch
@@ -120,15 +120,15 @@ main :: () {
iv, ierr := sm_acquire(false);
// composition: inline failable closure literal through a Closure(...) param
cl := sm_run(closure((x: s32) -> (s32, !SmokeErr) { if x < 0 { raise error.BadDigit; } return x * 2; }), 6) catch e -1;
cl := sm_run(closure((x: s32) -> (s32, !SmokeErr) { if x < 0 { raise error.BadDigit; } return x * 2; }), 6) catch (e) -1;
print("closure-run: {}\n", cl); // 12
print("closure-run-err: {}\n", sm_run(closure((x: s32) -> (s32, !SmokeErr) { raise error.Empty; }), 1) catch e -9); // -9
print("closure-run-err: {}\n", sm_run(closure((x: s32) -> (s32, !SmokeErr) { raise error.Empty; }), 1) catch (e) -9); // -9
// non-failable closure literal widened into the failable bare slot
print("widen: {}\n", sm_widen(closure((x: s32) -> s32 => x + 1), 9)); // 10
// generic failable composition (monomorphized at s32)
print("wrap: {}\n", sm_wrap(s32, closure(() -> (s32, !SmokeErr) { return 42; })) catch e 0); // 42
print("wrap: {}\n", sm_wrap(s32, closure(() -> (s32, !SmokeErr) { return 42; })) catch (e) 0); // 42
print("errors ok\n");
}

View File

@@ -20,10 +20,10 @@ guard :: (ok: bool) -> !E {
}
ok_v :: #run parse(5); // success → 10 (value, error stripped)
caught :: #run parse(-1) catch e 99; // Bad → 99
caught :: #run parse(-1) catch (e) 99; // Bad → 99
ored :: #run parse(0) or 55; // Empty → 55
#run guard(false) catch e { }; // onfail fires during the comptime unwind
#run guard(false) catch (e) { }; // onfail fires during the comptime unwind
main :: () -> s32 {
print("ok={} caught={} ored={}\n", ok_v, caught, ored);

View File

@@ -10,13 +10,13 @@
E :: error { Neg }
runwith :: (cb: Closure(s64) -> (s64, !E), n: s64) -> s64 { return cb(n) catch e -1; }
runwith :: (cb: Closure(s64) -> (s64, !E), n: s64) -> s64 { return cb(n) catch (e) -1; }
main :: () -> s32 {
// block-body and arrow-body failable closures, called directly
m := closure((x: s64) -> (s64, !E) { if x < 0 { raise error.Neg; } return x * 2; });
n := closure((x: s64) -> (s64, !E) => x + 1);
print("{} {} {} {}\n", m(5) catch e 0, m(-1) catch e 99, m(-1) or 7, n(40) catch e 0); // 10 99 7 41
print("{} {} {} {}\n", m(5) catch (e) 0, m(-1) catch (e) 99, m(-1) or 7, n(40) catch (e) 0); // 10 99 7 41
// failable closure passed as a Closure(...) parameter
print("param ok={} err={}\n", runwith(m, 5), runwith(m, -1)); // 10 -1

View File

@@ -12,7 +12,7 @@
E :: error { Neg }
bare :: (cb: (s64) -> (s64, !E), n: s64) -> s64 { return cb(n) catch e -1; }
bare :: (cb: (s64) -> (s64, !E), n: s64) -> s64 { return cb(n) catch (e) -1; }
chain :: (cb: Closure(s64) -> (s64, !E), n: s64) -> (s64, !E) { return try cb(n); }
dbl :: (x: s64) -> (s64, !E) { if x < 0 { raise error.Neg; } return x * 2; }
@@ -25,8 +25,8 @@ main :: () -> s32 {
// Closure(...) param, try-propagated, then caught at the call site
print("chain ok={} err={}\n",
chain(closure((x: s64) -> (s64, !E) => x + 6), 4) catch e 0, // 10
chain(closure((x: s64) -> (s64, !E) { raise error.Neg; }), 1) catch e 0); // 0
chain(closure((x: s64) -> (s64, !E) => x + 6), 4) catch (e) 0, // 10
chain(closure((x: s64) -> (s64, !E) { raise error.Neg; }), 1) catch (e) 0); // 0
// NON-failable closure literal widened into the failable bare slot
print("widen={}\n", bare(closure((x: s64) -> s64 => x + 1), 9)); // 10

View File

@@ -24,13 +24,13 @@ main :: () -> s32 {
handlers.append(closure((x: s32) -> (s32, !) { if x == 0 { raise error.Other; } return x + 100; }));
// success paths
print("ok0={}\n", dispatch(handlers.items[0], 5) catch e 0); // 10
print("ok1={}\n", dispatch(handlers.items[1], 7) catch e 0); // 107
print("ok0={}\n", dispatch(handlers.items[0], 5) catch (e) 0); // 10
print("ok1={}\n", dispatch(handlers.items[1], 7) catch (e) 0); // 107
// failure paths: each closure raises its own tag, which propagates
// through `try` and is absorbed by the call-site `catch` fallback
print("err0={}\n", dispatch(handlers.items[0], -1) catch e -1); // raised Negative → -1
print("err1={}\n", dispatch(handlers.items[1], 0) catch e -2); // raised Other → -2
print("err0={}\n", dispatch(handlers.items[0], -1) catch (e) -1); // raised Negative → -1
print("err1={}\n", dispatch(handlers.items[1], 0) catch (e) -2); // raised Other → -2
}
return 0;
}

View File

@@ -19,7 +19,7 @@ main :: () -> s32 {
handlers : List(Closure(s32) -> (s32, !)) = .{};
handlers.append(closure((x: s32) -> (s32, !) { if x < 0 { raise error.Negative; } return x; }));
handlers.append(closure((x: s32) -> (s32, !) { if x == 0 { raise error.Other; } return x; }));
print("r={}\n", reject(handlers.items[0], 5) catch e 0);
print("r={}\n", reject(handlers.items[0], 5) catch (e) 0);
}
return 0;
}

View File

@@ -9,7 +9,7 @@
E :: error { Neg }
take :: (cb: Closure(s32) -> (s32, !E), x: s32) -> s32 { return cb(x) catch e -1; }
take :: (cb: Closure(s32) -> (s32, !E), x: s32) -> s32 { return cb(x) catch (e) -1; }
main :: () -> s32 {
// `-> s32` (non-failable) but the body raises → lambda-specific hint:

View File

@@ -13,7 +13,7 @@ wrap :: ($T: Type, f: Closure() -> (T, !E)) -> (T, !E) { return try f(); }
main :: () -> s32 {
// success, consumed by catch
print("catch={}\n", wrap(s32, closure(() -> (s32, !E) { return 7; })) catch e -1); // 7
print("catch={}\n", wrap(s32, closure(() -> (s32, !E) { return 7; })) catch (e) -1); // 7
// success, consumed by destructure (binds value + error slot); the value
// slot is read only under an `if !err` guard (ERR E1.8 path-sensitivity)
@@ -21,9 +21,9 @@ main :: () -> s32 {
if !err { print("destr={} ok=true\n", r); } // destr=9 ok=true
// failure path: the raised tag propagates through the generic `try`
print("fail={}\n", wrap(s32, closure(() -> (s32, !E) { raise error.Bad; }) ) catch e -1); // -1
print("fail={}\n", wrap(s32, closure(() -> (s32, !E) { raise error.Bad; }) ) catch (e) -1); // -1
// a second monomorphization at a different T
print("u8={}\n", wrap(u8, closure(() -> (u8, !E) { return 200; })) catch e 0); // 200
print("u8={}\n", wrap(u8, closure(() -> (u8, !E) { return 200; })) catch (e) 0); // 200
return 0;
}

View File

@@ -13,7 +13,7 @@
E :: error { Z }
bare :: (cb: (s64) -> s64, n: s64) -> s64 { return cb(n); }
baref :: (cb: (s64) -> (s64, !E), n: s64) -> s64 { return cb(n) catch e -1; }
baref :: (cb: (s64) -> (s64, !E), n: s64) -> s64 { return cb(n) catch (e) -1; }
main :: () -> s32 {
inc := closure((x: s64) -> s64 => x + 1); // capture-free closure var

View File

@@ -53,7 +53,7 @@ main :: () -> s32 {
// (4) early-return / raise helpers
total = total + guarded(4); // +40
total = total + guarded(-1); // -1
total = total + (relay(2) catch e 0); // parse(2)=20 → +1 = 21
total = total + (relay(2) catch (e) 0); // parse(2)=20 → +1 = 21
print("liveness total: {}\n", total); // 50+70+30+40-1+21 = 210
return total;

View File

@@ -13,7 +13,7 @@ recover :: () -> (s32, !E) { raise error.Bad; }
work :: (n: s32) -> !E {
defer print("defer: always\n"); // plain cleanup
onfail { failing() catch e print("onfail: caught (catch)\n"); } // catch absorbs
onfail { failing() catch (e) print("onfail: caught (catch)\n"); } // catch absorbs
onfail { x := recover() or 7; print("onfail: x={} (or)\n", x); } // or-value absorbs
if n < 0 { raise error.Bad; }
return;

View File

@@ -16,7 +16,7 @@ run :: () {
defer {
v, e := probe(); // destructure decl
if !e { print("defer: v={}\n", v); } // value live under the guard
failing() catch x print("defer: caught\n"); // catch-statement absorbs
failing() catch (x) print("defer: caught\n"); // catch-statement absorbs
}
print("body\n");
}

View File

@@ -37,7 +37,7 @@ work :: () {
if !err { print("defer closure: v={}\n", v); } // E1.8: live under guard
try failing();
};
emit() catch e print("defer closure: raised\n");
emit() catch (e) print("defer closure: raised\n");
}
print("body\n");
}

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