Lumen type‑checks familiar TypeScript syntax and compiles it straight to a small, dependency‑free executable. No VM, no interpreter, no Node.
curl -fsSL https://lumen-lang.org/install.sh | sh
// Call libm directly -- no bindings, no runtime.
// @link m
declare function sqrt(x: number): number;
function hypot(a: number, b: number): number {
using _ = defer(() => console.log("computed")); // runs at scope exit
return sqrt(a * a + b * b);
}
console.log(hypot(3.0, 4.0)); // computed → 5
Familiar syntax, fixed static types, no runtime, just a native executable.
interface Point {
x: int;
y: int;
}
function distanceSq(a: Point, b: Point): int {
let dx = a.x - b.x;
let dy = a.y - b.y;
return dx * dx + dy * dy;
}
let origin: Point = { x: 0, y: 0 };
let p: Point = { x: 3, y: 4 };
console.log(`distance² = ${distanceSq(origin, p)}`); // distance² = 25
A predictable static subset of TypeScript, no prototypes, no eval, no dynamic shapes.
int/i64/number/bool/string, float & hex/binary literals. Numeric promotion (int + number) and lossless int → i64 widening, just like TypeScript's single number.enums, and fixed‑shape tuples like [int, string]. Structural width subtyping: a wider record flows into a narrower one, so interface parameters accept any matching shape. Plus type aliases, discriminated and string‑literal unions, as assertions, as const, satisfies, and readonly arrays.if/else if, while, do, C‑style for, for…of (arrays, strings, Set, Map), for…in, switch (exhaustive over literal unions and enums), ternary, and labeled break/continue. The full arithmetic, bitwise, and shift set, with every compound‑assignment form (+=, &&=, ??=, <<=, **=, and more).T | null, optional ? fields, ??, optional chaining ?./?.[i]/?.()/?.length, and the non-null assertion x!. Flow narrowing across if/else, guard clauses (if (x == null) return), while, &&/||, and deep field paths (a.b.c != null) — plus discriminated-union complement narrowing.arguments object. Object shorthand { x } and computed keys, destructuring defaults in array ([a = 1]) and object ({ x = 1 }) patterns, and destructuring assignment ([a, b] = [b, a]).extends, super, public/private/protected, #private fields, static, readonly, and get/set. Fields infer their type from an initializer (count = 0); methods take default, optional, and rest parameters.Array<T>, monomorphized at compile time, no boxing. Type-parameter constraints (<T extends ...>) and defaults (<T = ...>).`hi ${name}` with string & numeric interpolation, and the methods you expect (split, slice, trim, includes, indexOf, replace, repeat, padStart, toUpperCase, and more). /pattern/ literals with .test(): anchored patterns compile to specialized native matchers at build time, ~3× faster than V8 on checks like semver and identifiers, and everything else falls back to a built-in engine, so every pattern works.Map<K, V> and Set<T>, both iterable with for…of, and the array methods you expect (map, filter, reduce, forEach, find, some, every, includes, indexOf, join). Plus Date.now(), Object.keys/Object.freeze, Array.isArray/Array.from/Array.of, JSON.stringify/parse<T>, parseInt/parseFloat, and the full Math/Number surface.try/catch/finally and throw, propagating across functions, methods, and constructors; readable .message, optional catch binding, and full stack traces on uncaught errors. Scope‑exit cleanup with defer (last‑in‑first‑out), and built‑in tests: test "…" { expect(…); } run with lumen test.async functions return Promise<T> and await resolves them on a fast native event loop, with setTimeout/setInterval and their clear* counterparts. Worker.run(fn) spawns a real, detached OS thread — genuine CPU parallelism, with no per‑thread interpreter overhead to pay. EventEmitter<T> is statically typed, one payload type per instance, and ~3.5× faster than Node's on a tight emit loop.string at compile time: Buffer.from/.alloc, utf8/hex/base64 encodings, .slice/.at/.equals. crypto.hmacSync and AES‑256‑GCM encryptSync/decryptSync both read and return it. Alongside net TCP sockets, an http client & concurrent server, process.stdin/stdout/stderr as streams, readline.question, and zlib gzip/deflate.import * as ns imports, and export … from re-exports, from a relative file or an https:// URL — a package is just a URL. declare function + // @link calls native C libraries, with scalars and strings marshalling across the boundary. It all compiles to a small, dependency‑free native binary.Compile errors say what's wrong and how to fix it — expected/got types, did‑you‑mean suggestions, caret underlines, and color. Uncaught runtime errors print a real stack trace with your file and line, even across imports.
$ lumen check main.ts
main.ts:4:1: error: 'add' expects 2 arguments, got 1
main.ts:6:1: error: `string` has no method 'toUperCase' — did you mean 'toUpperCase'?
main.ts:8:1: error: 'r' (`string | null`) may be null — check `!= null`
before reading '.length', or use optional chaining `?.length`
main.ts:9:1: error: `if` condition must be `boolean`, got `i32` —
truthiness is not supported; write `x != 0`
$ lumen run app.ts
util.ts:2:14: Uncaught Error: negative input
2 | if (n < 0) throw new Error("negative input")
| ^
at risky (util.ts:2:14)
at process (app.ts:7:3)
at <main> (app.ts:12:1)
$ lumen test app.ts
ok adds
FAIL rounds — expected 4, found 3
at app.ts:21
1 passed, 1 failed
curl -fsSL https://lumen-lang.org/install.sh | sh
Windows: download the .zip from the
releases page. Self-contained,
no other toolchain required.
Then scaffold a project and run it:
lumen init my-app
cd my-app
lumen compile main.ts && ./main
lumen init writes a starter main.ts plus the ambient
declarations and tsconfig.json, so the project is editor- and type-checker-clean
from the first keystroke.
For a live edit loop, lumen watch rebuilds and re-runs the program
whenever the entry file or any of its local imports changes:
lumen watch main.ts # rebuild and re-run on change
lumen watch --no-run main.ts # rebuild only