Skip to content
orelangPublic

About

A natively compiled, Go-like language with TypeScript syntax. Written in Ore, built on LLVM.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

{Ore}: familiar language, closer to the machine. A natively compiled, Go-like language with TypeScript syntax.

Written in Ore Latest release Platforms: Linux on x86-64 and arm64, macOS on arm64 Release build

Getting started · Specification · Architecture · Roadmap · Changelog

curl -fsSL https://raw.githubusercontent.com/orelang/ore/main/install.sh | sh   # or: brew install orelang/tap/ore

Ore keeps the syntax of TypeScript and gives it native semantics: real i32, u64 and f32 types, untyped numeric constants, explicit conversions, nominal classes, structural interfaces, tagged unions with narrowing, errors as values, generics by monomorphization, green threads with channels and a garbage collector. Programs compile through LLVM to a single native binary.

function* ids() {
    for (let id = 1; ; id++) yield id
}

async function price(id: int): Promise<u64 | Error> {
    await time.delay(10 * time.millisecond)
    return id % 3 !== 0 ? u64(id) * 100 : new Error(`no price for item ${id}`)
}

// Five prices at once: the awaits overlap on the event loop.
const prices = await Promise.all(
    ids()
        .take(5)
        .map((id) => price(id))
        .toArray(),
)
let total: u64 = 0
for (const p of prices) {
    if (p instanceof Error) console.error(p.message)
    else total += p
}
console.log(`total: ${total}`)
$ ore build prices.ore && ./prices
no price for item 3
total: 1200

How it works

  • Compiler. Ore's own parser reads TypeScript syntax into a tree with the node kinds of the TypeScript parser; the checker resolves names and types and lowers the program to a typed IR, with generics instantiated per type, closures with explicit captures, tagged unions and interface tables. Escape analysis keeps objects that never leave their function on the stack, and release builds inline small functions. The IR becomes LLVM IR, which clang compiles together with the runtime into one native binary.
  • Runtime. Mostly written in Ore and compiled with the program, so it inlines into user code: strings, arrays, maps, number formatting, JSON, and the M:N scheduler of green threads (stackful, on a pool of OS threads, with the context switch in assembly), channels and select, and a netpoller on kqueue or epoll, printing, panics, files and sockets. Only libc is outside Ore, the garbage collector included: a mark-sweep collector in the shape of Go's, with per-thread allocation from size-classed spans.
  • Tooling. One ore binary builds, runs, checks, tests and formats code and serves the language server. Debug builds keep stack traces for panics.
  • Platforms. Linux on x86-64 and arm64, macOS on arm64.

The specification describes the language, the architecture the implementation, the roadmap what comes next; docs lists them all.

The language at a glance

Numbers are machine numbers. i8…i64, u8…u64, f32, f64 (int is i64, number is f64). Literals adapt to their type; types never mix without a conversion, integers wrap and divide as integers.

let n = 7      // int
let x = 7.0    // f64
console.log(n / 2, x / 2, f64(n) / 2)    // 3 3.5 3.5
n + x          // error: Operator '+' cannot be applied to types 'i64' and 'f64'. Convert one side explicitly
const b: u8 = 256                        // error: Constant 256 overflows 'u8' (range 0..255).

Strings are UTF-8 bytes. Immutable, with the JavaScript methods on byte offsets; for...of walks runes.

const word = "héllo"
let runes = 0
for (const rune of word) runes++
console.log(word.length, runes, word.slice(0, 3), word.replaceAll("l", "L"))   // 6 5 hé héLLo

Structural interfaces, nominal classes. Any class or object with the members fits an interface, fields included. A class may extend one class; its methods are virtual.

interface Named {
    readonly name: string
}

class User {
    constructor(
        readonly name: string,
        private password: string,
    ) {}
}

const greet = (n: Named) => `hello, ${n.name}`
console.log(greet(new User("Ada", "secret")), greet({ name: "literal" }))   // hello, Ada hello, literal

Unions narrow, and switch is exhaustive. T | null, discriminated unions, typeof, instanceof, ?. and ??.

type Shape = { kind: "circle"; r: f64 } | { kind: "rect"; w: f64; h: f64 }

function area(s: Shape): f64 {
    switch (s.kind) {
        case "circle":
            return Math.PI * s.r * s.r
        case "rect":
            return s.w * s.h
    }
}

const found = ["ada", "grace"].find((n) => n.startsWith("g"))   // string | null
console.log(area({ kind: "rect", w: 2, h: 3 }), found?.toUpperCase() ?? "nobody")   // 6 GRACE

Errors are values. No throw: a fallible function returns T | Error, and the checker makes you look before you use the result.

function parseAge(text: string): int | Error {
    const n = Number.parseInt(text)
    if (n === null || n < 0) return new Error(`bad age '${text}'`)
    return n
}

const age = parseAge("forty")
if (age instanceof Error) console.log(age.message)   // bad age 'forty'
else console.log(age + 1)                            // age is an int here
parseAge("1") + 1   // error: Operator '+' cannot be applied to type 'Error | i64'.

Generics are compiled per type, with inferred type arguments and extends constraints.

function last<T>(items: T[]): T | null {
    return items.length > 0 ? items[items.length - 1] : null
}

console.log(last([1, 2, 3]), last(["x", "y"]), last<f64>([]))   // 3 y null

Closures, defaults, destructuring; closures capture variables by reference.

function counter(start = 0): () => int {
    let n = start
    return () => ++n
}

const next = counter(10)
next()
const { x, y } = { x: next(), y: 1 }
console.log(x, y, [3, 1, 2].map((v) => v * x).join(" "))   // 12 1 36 12 24

Resources with using. A value declared with using is disposed at the end of its block, on every path out of it: return, break, continue or the end of a loop iteration. Disposing calls its [Symbol.dispose]() method, so files, locks and connections clean up after themselves. The syntax is the using declaration of TC39's Explicit Resource Management proposal, as shipped in TypeScript 5.2.

class Section {
    constructor(readonly name: string) {
        console.log("enter", name)
    }

    [Symbol.dispose](): void {
        console.log("leave", this.name)
    }
}

for (const name of ["a", "b"]) {
    using s = new Section(name)
    console.log("work in", s.name)
}   // enter a, work in a, leave a, enter b, work in b, leave b

Typed JSON. Code for each type is generated at compile time; a mismatch is an Error that says where.

type Config = { name: string; port: int; tags: string[] }

const config = JSON.parse<Config>(`{"name": "api", "port": "80", "tags": []}`)
if (config instanceof Error) console.log(config.message)   // JSON: expected an integer, got a string at .port
console.log(JSON.stringify({ name: "api", port: 80, tags: ["v1"] }))   // {"name":"api","port":80,"tags":["v1"]}

Regular expressions. JavaScript's syntax and semantics, lookbehind and the flags d, u and v included, with an engine written in Ore; a bad literal is a compile-time error.

const date = /(?<year>\d{4})-(?<month>\d{2})/
const m = date.exec("released 2026-10")
if (m !== null) console.log(m.groups.get("year"), m.index)   // 2026 9
console.log("a-b_c".replace(/[-_]/g, " "), "x1y22z".split(/\d+/).join(","))   // a b c x,y,z
console.log("$42 or 17".match(/(?<=\$)\d+/)!.at(0), /\p{Script=Greek}+/u.exec("Ωμέγα")!.at(0))   // 42 Ωμέγα

Green threads and channels. Tasks run in parallel on all CPUs and talk through channels and select; waiting parks the task, not the OS thread. ore:sync adds Mutex and WaitGroup, ore:net TCP, ore:crypto SHA-256 and UUIDs, ore:path paths; time has clocks and durations in nanoseconds.

const results = new Chan<int>()
for (let i = 1; i <= 3; i++) task.spawn(() => results.send(i * i))
let sum = 0
for (let i = 0; i < 3; i++) sum += results.receive() ?? 0   // 14

task.select([
    results.onReceive((v) => console.log("got", v)),
    task.after(100 * time.millisecond, () => console.log("timeout")),
])

C, and the machine, when needed. declare function calls C directly; unsafe has raw memory, atomics and inline assembly, enough to write the runtime itself in Ore (unsafe code).

declare function strlen(s: string): u64
console.log(strlen("ore"))   // 3, from libc

const counter = unsafe.allocBytes(8)
unsafe.atomicAdd<i64>(counter, 5)
console.log(unsafe.atomicLoad<i64>(counter), unsafe.sizeOf<string>())   // 5 16

Scripts, built-ins in namespaces. A program is the top-level code of its files, with no main. Built-ins live in namespaces: console.log, process.args, Math.sqrt, fs.readFile; the only bare globals are JavaScript's own, as setTimeout, URL and structuredClone.

What is left out on purpose: any, undefined, var, ==, throw, enum, namespace, prototype, eval, delete (but of a dictionary's entries). See differences from TypeScript.

Getting started

On Linux (x86-64, arm64) and macOS (arm64):

curl -fsSL https://raw.githubusercontent.com/orelang/ore/main/install.sh | sh

or brew install orelang/tap/ore. A release carries its LLVM and lld; the script installs what linking needs from the system, puts ore in ~/.ore/bin and adds it to the PATH. On a terminal it shows each step and asks before replacing an installed ore or editing shell startup files; without one (CI) it asks nothing. Options go after sh -s --: --version v0.3.0, --home <dir> (instead of ~/.ore), --yes, --no-modify-path, --verbose, --help (or ORE_VERSION, ORE_HOME, ORE_YES=1, ORE_NO_MODIFY_PATH=1, ORE_VERBOSE=1). ore update updates it to the latest release.

From source, the compiler (written in Ore) builds itself and checks that it reproduces itself; it needs LLVM 19 or newer and lld (apt install llvm-22-dev lld-22, brew install llvm lld), and starts from the previous release, or from the seed (the last commit with the compiler in TypeScript, built once with Bun):

ORE=~/.ore/bin/ore ci/bootstrap.sh dist   # dist/ore

Then:

ore init my-app          # package.json and src/main.ore
cd my-app
ore run                  # build and run
ore run --watch          # and again whenever a file of the program changes
ore build --release      # an optimized binary named after the package
ore check                # report errors without building
ore test                 # run the tests in *.test.ore files
ore test -t "math > add" # only the tests whose names match
ore fmt                  # format .ore files: 4 spaces, no semicolons
ore doc src/lib.ore      # the API of a module from its exports and JSDoc (--md, --json)

On a terminal, ore prints in color: error labels, the carets under an error, the commands of ore help, the marks of ore test. NO_COLOR=1 turns colors off, FORCE_COLOR=1 (or 2 for 256 colors, 3 for 24-bit) turns them on even when the output is not a terminal; otherwise they follow COLORTERM and TERM. Piped output has no colors.

A single file works too: ore run hello.ore. A debug build of a small program takes about 0.3 s (LLVM through its C API on every CPU, linked with lld), a release build about 1.4 s. LLVM's object files are kept in a build cache (~/.cache/ore; ore cache prints its size, ore cache clean empties it): another build compiles again only the source files that changed, the runtime once per compiler, and a release build of an unchanged program skips LLVM.

dist/ore is the compiler written in Ore (compiler); it rebuilds itself with ore build compiler/src/main.ore -o ore --release.

Projects and packages

An Ore project is an npm package. Its package.json names the entry module in main, and dependencies are npm packages containing .ore files, installed with npm (or bun, pnpm, yarn) into node_modules and locked in package-lock.json as usual.

Modules import each other like TypeScript: import { x } from "./util", import * as m from "./m", import { y } from "some-package". Only exported declarations are visible to other modules. Standard modules use the ore: prefix:

import { test, expect } from "ore:test"
import { parseSize } from "./size"

test("parses sizes", () => {
    expect(parseSize("12", 1)).toBe(12)
})

Native code is declared in package.json, also by dependencies:

{ "ore": { "link": ["sqlite3"], "sources": ["native/glue.c"] } }

Editor support

Both extensions run ore lsp from PATH. The language server checks each open document as you type: errors with their codes and a link to their page in docs/errors, hover with the signature and the JSDoc comment, go to definition, completion after ., references and rename.

Zed. Run zed: install dev extension and select editors/zed (requires Rust installed with rustup). Another binary can be set in Zed settings:

{ "lsp": { "ore": { "binary": { "path": "/path/to/ore", "arguments": ["lsp"] } } } }

VS Code. Install "Ore" from the Visual Studio Marketplace, or the .vsix of a release (code --install-extension ore-vscode-vX.Y.Z.vsix), or build it:

cd editors/vscode
npm install
npm run package                      # ore-<version>.vsix
code --install-extension ore-*.vsix

Another binary can be set with the ore.path setting.

Performance

bench/ holds 24 programs, each written in Ore, C, Go and TypeScript (run by Bun and Node), from compute kernels to everyday programs with maps, strings, JSON, regular expressions, files, tasks and channels. Results per machine are in bench/results/; the latest: Xeon E5-2699 v4, 16 threads, Linux (2026-10-16, Ore 0.4.0 and later).

In short, against the fastest of the five in each benchmark:

  • Fastest of the five in map, tasks and (with C) fib; within 1.1x of the fastest in dispatch, json, mandelbrot, matmul and spectral-norm.
  • Compute (fib, mandelbrot, matmul, spectral-norm, nqueens, fannkuch, sha256, nbody): within 1.0–1.5x of C, level with Go or ahead, and ahead of Bun and Node on all but fannkuch.
  • Tasks and channels: 100000 tasks in 59 ms (Go 67 ms, Bun 138 ms, Node 251 ms); channels 1.5x of Go, 4.7x ahead of Bun and Node.
  • Objects: structs 1.26x of C and ahead of Go (small immutable classes are values); binary-trees, closures and strings 1.2–1.6x of the fastest.
  • Behind: wordcount (4.1x of C), sort (2.2x), bigint (2.4x of Go), fileio (2.4x of C), knucleotide (1.75x of C).
  • Memory: 1.3–2.5x of C and Go on most benchmarks, 10–50x less than Bun and Node.
  • GC pauses: on a 16-thread machine, 0.065 ms and 0.01 ms (median) per collection, at worst 0.2 ms in all, against Go's 0.14 + 0.05 ms and 0.43 ms.

Status

Phase
1. Numbers, functions, control flow, first binary done
1.5. Language server and Zed extension done
2. Strings, arrays, garbage collector done
3. Object types, classes, interfaces done
4. Unions, narrowing, errors, generics, Map done
5. Closures, array methods, using done
6. Modules, packages, standard library, C interop, ore test, ore fmt done
7. Concurrency: tasks, Chan<T>, select on green threads done
7.5. Multi-threaded scheduler, blocking I/O handoff, ore:sync done
8. Performance: escape analysis, inlining, LLVM backend, low-level layer (unsafe, directives) done
8.5. Language completeness: destructuring, default and rest parameters, generic methods, getters and setters, JSON done
9. Runtime in Ore: no C left; only libc outside Ore done
10. Compiler in Ore: the whole toolchain (ore command, LLVM through its C API, tests), bootstrapped; no TypeScript left done
11. Own garbage collector: mark-sweep with parallel and concurrent marking, a precise heap, a pacer (done); precise maps, sets and channels, concurrent sweeping (roadmap) in progress
11.5. TypeScript gaps: tuples, spreads, static, overloads, optional properties, unknown, computed types, regular expressions, … done
12. Promises and async/await: the JavaScript event loop on fibers, timers, task.microtask, generators and async generators done
13. Web APIs: URL, Blob, Headers, FormData, events, messages, streams, timers, ore:tls (done); fetch, an HTTP server, WebSocket (roadmap) in progress

Known limitations

  • Tasks are not preempted: a task that computes without waiting keeps its OS thread. Data races are not detected.
  • A file operation occupies a whole OS thread while it runs (the network does not).
  • A task has a fixed stack (1 MiB, 8 MiB for the top-level code; ORE_STACK sets it): deeper recursion panics with stack overflow.
  • A generic function or class that is never used is parsed but not checked.

Development

ci/test.sh                       # every test, with dist/ore (see tests/README.md)
ORE_BLESS=1 ci/test.sh run/      # regenerate expected outputs of tests/run
bench/run.sh                     # Ore against C, Go, Bun and Node (see bench/README.md)
tests/bench/run.sh               # time the micro-benchmarks in tests/bench/ (release builds)
ci/test-linux.sh                 # the tests in Linux containers (arm64 and amd64), with Docker
ci/stress.sh                     # run the concurrent programs many times

The tests run on Linux (x86-64 and arm64) and macOS (arm64).

Every directory below has a README with the details.

Path Contents
compiler The compiler, written in Ore (dist/ore): the ore command, parser, checker, IR, optimizer, LLVM backend through LLVM's C API, build driver, formatter, language server
runtime/core The runtime written in Ore (ore:runtime/...), part of every program
runtime/std Built-in declarations (builtin.d.ore) and standard modules (ore:test, ore:sync, ore:net, ore:crypto, ore:path)
tests/run Programs with expected output, by topic; a directory with main.ore is one program
tests/diag Programs with expected diagnostics (// error: ...), by topic
tests/async Async programs, with expected output recorded from Bun
bench Benchmarks against C, Go, Bun and Node, and their results
tests/bench Micro-benchmarks of the runtime
tests/runner The test runner (ci/test.sh), written in Ore
editors/zed Zed extension (GPL-3.0-or-later: it carries Zed's TypeScript queries)
editors/vscode VS Code extension
ci/ Bootstrap, tests, stress tests, packaging and releases
docs The specification, a page per diagnostic code, the architecture and the roadmap (index)

License

Ore is licensed under either of the MIT license or the Apache License, Version 2.0, at your option.

Contributions are accepted under the Contributor License Agreement, which assigns their copyright to the Ore Team (AUTHORS).

The Zed extension in editors/zed is the exception: it carries Zed's TypeScript queries and is licensed under the GPL-3.0-or-later.

About

A natively compiled, Go-like language with TypeScript syntax. Written in Ore, built on LLVM.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages