Early June Update
Early June Update
It’s been a while since the last update, and a lot of small, sharp changes have piled up — most of them the language quietly settling into a more coherent shape. No single grand feature this time. Just a stretch of “make it say one thing one way,” and a couple of structural moves underneath. Here’s the roundup.
The surface is settling
The biggest visible change: kebab-case is legal in names, and the separators each do exactly one job now.
import std/io
std/io:print.ln("Hello, {{ name:s }}!")
~pub event delete { directory-name: string }
| my-branch b |> ... Three cleanups landed together there:
-is a name-char.my-branch,directory-name. Flows read like prose instead of like C. It lowers to_for the host target, so the Zig side still seesdirectory_name— the dash lives entirely in the Koru contract./is the namespace separator at the call site, not just in imports. You used toimport "std/io"with a slash and then callstd.io:with a dot — two spellings for one module. Now it’sstd/io:everywhere, matching the import and the file on disk (std/io.k).- Bare imports.
import std/io, notimport "std/io". A module path is an identifier-path, not a string; the quotes were borrowed ceremony.
Stacked up, every separator finally means one thing: / namespace, : module→symbol pivot, . member access, - word-glue, _ digit separator in numbers. One glyph, one job.
That sits on top of a few other surface changes from the same stretch:
- The
=>construct glyph. Construction is now marked explicitly —=>builds a value (a branch constructor) everywhere,=only opens a flow, and a barename { ... }is always an invocation. The parser stops guessing from layout; the glyph tells it. Multi-line branch constructors come along for free. .kfiles are pure Koru. A.kis the event contract and nothing else — no~, no host lines. The~leader only makes sense where you switch into Koru from a host language, and in a pure-Koru file there’s nothing to switch from..kz(Zig procs) and.kjs(JS procs) keep it.- Dollar-imports are gone.
$std/...retired in favor of plainstd/.... One import form. - Inline
|>is enforced. A trailing|>with the body on the next line is now a hard parse error. The chain operator never starts a line.
None of these is huge on its own. Together they’re the language pressing its own shape — removing second ways to say things, which is the whole posture here: greenfield, no users, so a duplicate form is pure debt.
Underneath: const blocks and typed bodies
Two structural moves landed that aren’t surface sugar.
const {} blocks now lower as a per-target template over the field parser. A module-level
const {
name: "Claude"
count: 42
} declares constants the rest of the file can interpolate directly — no host-side companion needed — and it lowers per target (Zig, JS) rather than being special-cased.
Values can carry a type, riding the same [ ] annotation slot the rest of the language uses:
const {
count: 42[i32]
size: 7[u8]
plain: 100
} 42[i32] is the value 42, typed i32; plain stays inferred. It’s an early seed of pulling more of the type surface into Koru itself.
And ProcDecl.body is now a typed Source, not a raw string. Proc bodies used to be opaque text forwarded to the host compiler. Making the body a typed Source is the groundwork for treating host code as a first-class payload instead of a blob — the same “source blocks are opaque payloads to userland” idea, now with a real type behind it.
The metalanguage layer
The templating and expression machinery got real plumbing:
- A liquid-style tree-walker plus a JSON parser landed, and with it the range path (
0..3) finally lowers correctly through the template layer. - Capture dissolved. Instead of a bespoke
~captureconstruct, an accumulator is just an effect-cell (! as acc) written to by ordinary events, with a struct-literal walker doing the rewrite. One fewer special form; capture becomes an application of primitives that already exist.
Cross-target and the harness
- JS keeps pace. Cross-target
std/argsand multi-branch terminal dispatch on the JS target — the same flow runs on Zig and JavaScript, dispatching the same branches. - The regression harness learned
ARGSargv passthrough, so a test can feed argv to both runtimes and compare a single expected output.
The suite got more honest
A quieter thread, but the one we care about most: several tests that were masked — passing for the wrong reason, or pinning behavior the language had outgrown — got flipped to honest positives. The inline-|> enforcement drove a suite-wide layout migration. Dead crud got dropped. A failing test you understand is worth more than a green one you don’t.
Where it stands
After all of it, the merged suite is at 606 / 653 in-scope passing — 92.8%, higher than before the busy stretch started, with the kebab foundation and the const-lowering work both landed and stepping on nothing.
The things still red are red on purpose — they’re the to-do list staked into the suite: the snake→kebab corpus migration, rejecting _ in names (the forcing function that makes kebab canonical), retiring println (a non-interpolating print.ln twin), and stage-aware stdlib facets so a comptime transform can’t be called from the pipeline that is the compiler.
A lot of small coherences. The shape’s getting cleaner.