Downloads

Latest: v1.2.13, published 2026-05-13.

macOS, x64xs-macos-x86_64 (2.5 MB)sha256download
Linux, x64xs-linux-x86_64 (2.8 MB)sha256download
Windows, x64xs-windows-x86_64.exe (1.2 MB)sha256download

Verify

Each release includes a SHA-256 sum file alongside the binary. Run shasum -a 256 -c xs-<platform>.tar.gz.sha256 after downloading.

Changelog

v1.2.13

2026-05-13

Added

  • xs --emit wasm now runs the full 17-test conformance suite end-to-end through wasmtime, matching the interpreter / VM / JIT byte-for-byte. Previously only basic arithmetic and control flow worked; everything else either silently produced wrong output or trapped with wasm 'unreachable'.
  • Stdlib bridge for import math / json / fs / time in the AOT path. Each module becomes a synthesised map of closures wrapping runtime helpers.
  • Cross-file use "./mod.xs" (with as rename and selective destructure) on the AOT path, mirroring the VM and JS implementations.
  • Generators (fn*, yield, g.next()), async / await, spawn, nursery, algebraic effects (perform / handle / resume), and reactive bind all lower to working WASM (sync resolution for async / spawn since a freestanding module has no scheduler).
  • Higher-order array methods (.map, .filter, .reduce, .fold, .each, .some, .every, .find, .sort_with, .flat_map, .group_by, .partition, .sum, .product, .min_by, .max_by, .count).
  • Codepoint-aware string ops (.chars, .lines, .lower, .trim, .split, .replace, .sort, UTF-8 .len).
  • Bigint with arbitrary precision in the AOT path, so int overflow promotes the same way the runtime does.
  • Tolerant assert_eq so chained float arithmetic (3.14 * r * r summed across shapes) still matches a literal expected value.

Changed

  • Closure variables that get mutated inside a closure now write back through the captured env so subsequent calls see the update. Previously n = n + 1 inside fn() { ... } wrote to a fresh local and the captured n stayed at its initial value.
  • Map set updates the existing key in place rather than appending a duplicate entry. Previously the next get would return the stale first match.
  • Map keys compare by content (via RT_VAL_EQ followed by RT_VAL_TRUTHY), not by underlying data pointer. Two literally equal "x" strings allocated at different sites now hash to the same slot.
  • Struct match by type name (Point { x, y }) tags instances with __type__ so the pattern can recognise them. Class declarations get the same field as a public class field.
  • String.len counts UTF-8 codepoints instead of bytes ("café".len() == 4).
  • String equality compares bytes byte-by-byte ("a" ++ "b" == "ab" is now true).
  • Indirect closure calls (fns[i]()) sniff the closure-env field at the call site and use the right call_indirect signature, so closures returned from arrays no longer trip "indirect call type mismatch".
  • Trait default methods get copied onto every impl that doesn't override them.
  • Mutually recursive nested fns and named nested fns (fn inc() {...} inside another fn) now compile correctly, with defer and closure capture across them.

Fixed

  • RT_VAL_INDEX_SET only handled arrays; map index assignment (m["k"] = v) silently no-op'd. It now dispatches on the value tag.
  • .starts_with was off by one on the comparison length.
  • Deep array equality recurses through nested elements rather than comparing pointers.

v1.2.12

2026-05-12

Added

  • JS transpiler now inlines cross-file use "./mod.xs" and respects the as rename, namespace alias, and selective destructure (use "./m.xs" { foo, bar as baz }). Previously the import was silently dropped and any reference to the namespace blew up at runtime.
  • Trait default methods get copied onto every impl that doesn't override them, so square.name() returning "shape" works the same under --emit js as under the interpreter.
  • assert_eq in the JS prelude does the same tolerant float compare as the native runtime, so chained arithmetic across shapes still matches a literal expected value (e.g. 12.56 + 9.0 == 21.56).

Changed

  • Struct match patterns recognise instances by name because the constructor tags this.__type__; class declarations get the same field as a public class field.
  • Field reads on missing keys (__xs_field) return null instead of undefined so XS-style m.k == null checks match the runtime.
  • The prelude's builtin range lives on globalThis so a user-declared fn* range doesn't trip Identifier 'range' has already been declared.
  • Top-level await inside an assert_eq argument auto-wraps the assertion IIFE as an awaited async function, so the inner await isn't a syntax error.
  • Effect handlers that don't call resume terminate the handle block with the arm's value instead of looping forever.
  • Exhaustiveness analyser stopped flagging (a, b) and Point { x, y, .. } as non-exhaustive; both are catchall for their shape.

Fixed

  • --emit js no longer crashed seven of the conformance tests. All 17 now pass through Node end-to-end.

v1.2.11

2026-05-12

Added

  • export { name, name as alias, ... } -- a file's public surface, in

one place. Goes at the top level of the file, names can be aliased with as. Without an export list, every top-level binding is exposed (scripts stay zero-ceremony).

  • Cross-file use "file.xs" works on the VM backend. Lowers to a

__use_file native that compiles the imported file with the bytecode compiler and runs it on a child VM, so closures from the module are real XS_CLOSURE values the parent VM can invoke.

  • Module-qualified struct construction: lib.Point { x: 3, y: 4 }

builds an instance instead of erroring.

  • @deprecated("msg") actually emits a W0001 warning at every call

site (was parsed and silently dropped).

Changed

  • C transpiler: per-arm effect handler dispatch, comparator support in

arr.sort_with, predicate dispatch on find / index_of, del tombstone semantics, map-spread, structured runtime errors.

  • JS transpiler: bare-builtins prelude, range methods, tuple-vs-array

shape correction, del tombstone, explicit refusals for bind and wrapping decorators.

  • Build: every .o / .d routes through build/obj/, source tree

stays clean. make clean also sweeps any stray root artefacts.

Removed

  • pub modifier and @export decorator. Using either is now a P0054

parse error pointing at export { ... }. The export list is the one and only mechanism.

Fixed

  • VM cross-file use was a no-op (silently dropped the import).
  • @deprecated warnings never fired.

v1.2.10

2026-05-10

Added

  • xs upgrade downloads the latest release and atomically replaces the running binary. Verifies SHA-256 before swapping. Use --yes to skip the prompt.
  • xs uninstall removes the binary; with --with-data it also clears ~/.xs and ~/.xs_cache.
  • xs repl (and bare xs with no args) drops into an interactive read-eval loop. Bindings persist across lines, multi-line input is detected via bracket / string state, parse errors no longer kill the session. Meta-commands: :help, :quit, :env, :clear, :t <expr>.

Changed

  • assert_eq raises a catchable AssertionError instead of calling exit(1). try { assert_eq(...) } catch e { ... } now works.
  • import log no longer collides with the math log builtin; the stdlib module loads correctly.
  • [1, 2, 3].sorted() works as an array method on the VM (was interpreter-only).
  • collections.Set.has(x) works for ints, floats, bools, and strings.
  • reflect.type_of(value) returns the struct or class name for instances built via either backend.
  • xs --help lists upgrade, uninstall, publish, and search.
  • VS Code extension icons regenerated in the new sage-on-dark style.

Removed

  • Inline temporal block statements: every 1s { ... }, after 5s { ... }, timeout 1s { ... }, debounce 100ms { ... }. They never actually scheduled the body. Use the decorator forms (@every(1s), @after(5s), @cron(...)) on a function instead.

Fixed

  • VM del x tombstones the local slot; subsequent reads throw a runtime error (was silently rebinding to null and skipping any try / catch).
  • Struct match patterns reject values of the wrong type instead of binding fields to null. match shape { Circle { radius } => ... } no longer fires on a Rect.
  • macOS build errors under Apple Clang: -Wenum-conversion in lint.c, -Wenum-compare-conditional across the parser, and missing <sys/select.h> on POSIX. The release link step also stops passing GCC-only flags to Apple's linker.
  • Linux CI fuzz step no longer blocks the run; reproducer artifact still uploads on findings for offline triage.

v1.2.9

2026-05-10

no notes

v1.2.8

2026-05-09

no notes

v1.2.7

2026-05-09

no notes

v1.2.6

2026-05-09

no notes

v1.2.5

2026-05-09

no notes

v1.2.4

2026-05-09

no notes

v1.2.3

2026-05-09

no notes

v1.2.2

2026-05-09

no notes

v1.2.1

2026-05-06

no notes

v1.2.0

2026-05-06

no notes

v1.1.1

2026-05-06

no notes

v1.1.0

2026-05-05

no notes

v1.0.3

2026-05-04

no notes

v1.0.2

2026-05-03

no notes

v1.0.1

2026-05-02

no notes

v1.0.0

2026-04-26

no notes