Status: pre-1.0 and under active development — APIs and syntax may change.
A dynamically-typed cross-platform scripting language with three backends: a bytecode VM, an LLVM JIT, and an ahead-of-time build that emits a standalone binary. Write scripts, CLI tools, machine learning, desktop apps, and games.
The stdlib, test runner, linter, formatter, debugger and docs are all in that one executable. Nothing else to install! Or try it in the browser first on the Playground.
Paste this into a terminal — it downloads culebra, writes an example to hello.cul, and runs that same source
file three ways: as a script, JIT-compiled, and built into a standalone binary.
curl -fsSL https://raw.githubusercontent.com/yhirose/culebra/master/install.sh | sh
culebra --version
cat > hello.cul <<'EOF'
let people = [
{ name: 'Taro', greeting: 'Konnichiwa' },
{ name: 'John', greeting: 'Hello' },
{ name: 'Ada', greeting: 'Bonjour' },
]
for p in people.sorted_by(|p| p.name) {
println("{p.greeting}, {p.name}!")
}
EOF
culebra hello.cul # the bytecode VM (default)
culebra --jit hello.cul # JIT
culebra build hello.cul -o hello && ./hello # AOT: compile once, ship the binary
cat hello.cul | culebra - # stdin (curl ... | culebra - also works)Optional — this one also touches your editor's config
(VSCode/Vim/Neovim) and writes AGENTS.md/CLAUDE.md into the
current directory, so it isn't bundled into the block above:
culebra initculebra init installs syntax highlighting and the debug adapter for
whichever of VSCode, Vim, or Neovim it finds on this machine, and adds
coding-agent instructions to AGENTS.md (or
CLAUDE.md/.github/copilot-instructions.md if one already exists) —
reopen hello.cul afterward and the highlighting is already on, and
Claude Code or another agent reading AGENTS.md already knows the
conventions.
For Linux (x86-64), swap the first line for culebra-linux-x64.tar.gz;
Windows and a permanent, system-wide install are below.
| Platform | Download |
|---|---|
| macOS (Apple Silicon) | culebra-macos-arm64.tar.gz |
| Linux (x86-64) | culebra-linux-x64.tar.gz |
| Windows (x86-64) | culebra-windows-x64.zip |
These always point at the newest release; culebra --version reports
which one you have. Extracting from the command line also avoids the
quarantine flag macOS puts on anything unpacked through Finder — the
binaries are unsigned. To put culebra on PATH permanently instead
of just for this shell:
sudo mv culebra-*/culebra /usr/local/bin/Or skip the manual steps — install.sh downloads the right archive for
your OS/arch, verifies its checksum, and installs it to /usr/local/bin
(macOS Apple Silicon and Linux x86-64 only; use CULEBRA_INSTALL_DIR to
change the destination):
curl -fsSL https://raw.githubusercontent.com/yhirose/culebra/master/install.sh | shmacOS also installs through a package manager:
brew install yhirose/culebra/culebraWindows and Linux have no package manager entry yet — use the download above.
Checksums and every release's notes are on the releases page.
- CLI script. Cold start in tens of milliseconds.
- Standalone binary.
culebra buildemits a single executable — nothing to install alongside it, no runtime dependencies. - Embedded library. Embeds the bytecode VM into a C++ host.
- Cross-platform. macOS / Linux / Windows; cross-compile to any LLVM target from any host.
Fast cold start, single-file programs, and a stdlib that is already in scope add up to a runtime that doesn't punish an agent for invoking it every turn.
- No
importlines for the stdlib.JSON,Http,Regex,PEG,FS,Tensor,SQLite,Search, and the rest are bound before the program runs. - One file, one program. Run it without a project layout.
- Ship the result.
culebra buildturns the script the agent just wrote into a binary it can hand back to the user.
A bare x = 1 is an immutable binding — reassigning it is an error;
write mut x = 1 for one you intend to change. No declaration keyword
for the common case.
The whole standard library is bound before the program runs — no package manager, no lockfile:
- Data. JSON (with optional JSONC comments and trailing commas),
CSV, TOML, XML,
.env, UUID, and SQLite — the amalgamation is compiled in, so there is no system library to install. - Text. Grapheme-aware regex, encodings (base64 / hex / url / HTML entities), SHA / MD5 digests and HMAC, gzip / deflate.
- System. Paths and whole-file I/O, streaming file handles, subprocesses, time, math, randomness, CLI argument parsing, and leveled structured logging.
- Network. An HTTP/HTTPS client and server (routes, static files, Server-Sent Events, WebSocket), plus raw TCP / UDP sockets and name resolution underneath.
- Concurrency. Isolates, channels,
Parallel, shared buffers, and Ctrl+C delivered as a channel message. - Terminal.
Term— color, cursor control, the alternate screen, and key/mouse input for TUIs, downsampled to whatever the terminal supports (and silent underNO_COLOR). - 3D.
Scene— a retained-mode 3D renderer for procedural geometry with physically based lighting. Experimental: opt-in (-DCULEBRA_ENABLE_SCENE=ON) and not in the released binaries. It builds on all three platforms; the window is exercised on macOS and Linux.
Tensor is a language primitive, not a library. Arithmetic is
operators over a lazy graph that Tensor.eval materializes; matmul is
.dot(). Ops run on the CPU (AVX2 / NEON kernels, Accelerate on
macOS) or the GPU (Metal on macOS, CUDA where nvcc built it in),
picked per op by size unless a Tensor.use_*() call pins one.
x = Tensor.from([[1.0, 2.0], [3.0, 4.0]])
y = x.dot(x.transpose())
Tensor.eval(y) # [[5.0, 11.0], [11.0, 25.0]]
Dir.embedded(name) hands a directory of files to your program with no
code change across backends: the VM and JIT read it live
from disk — edit a file, rerun — and culebra build bakes every byte
into the executable, so the shipped binary needs nothing alongside it.
It is one kind of Dir, read the same way as a directory on disk
(Dir.disk) or files held in memory (Dir.memory).
let assets = Dir.embedded("dist") # index.html, favicon.ico, ...
println(assets.exists("index.html")) # => true
println(assets.exists("favicon.ico")) # => true
A desktop GUI in web tech: a local HTTP server supplies the UI, the
OS's own WebView engine displays it, and culebra build ships the
whole thing — server, routes, and embedded assets — as one binary.
Desktop.run({
title: "Hello from culebra",
assets: Dir.embedded("dist"), # index.html, favicon.ico, ...
routes: fn (srv) {
srv.get("/api/hello", fn (req) {
"hi from the embedded server"
})
},
})
An immediate-mode 2D framebuffer — draw, present, poll input,
repeat — with sprites and text; sound is the Audio namespace (tones,
samples, music, synthesised PCM), which needs no window. It opens a real window on
macOS, Linux and Windows; a run that declares itself headless does the
same pixel work and displays nothing, which is how the tests and a
displayless server run it.
Canvas.run(160, 160, fn () {
Canvas.clear(Canvas.rgba(20, 24, 40))
Canvas.rect(20, 76, 8, 8, Canvas.rgba(220, 60, 60))
false # stop after one frame
})
- Pattern matching. Literals, type guards, destructuring, and rest
patterns, with optional
ifguards. - Sum types.
enum Shape { Circle(Float), Rect(Float, Float) }, generic where you need it, matched by variant or by enum. - Tuples and sets.
(3, 4)is immutable and hashable;Set(1, 2, 3)is an insertion-ordered set of unique values. - String interpolation.
"head={head}, rest={tail.size()}", with format specs, triple-quoted blocks that dedent, andre"…"regex literals. - Gradual typing. Annotations runtime-checked at boundaries; Union, Optional, Tuple, Trait and Generic all check today.
- UFCS. Free functions callable as methods (
x.f(y)≡f(x, y)). - Multiple dispatch. Free functions dispatch on argument types across VM / JIT / AOT.
- Traits. Built-in
Iterable/Iterator; user-defined traits with default methods. - Closures and
classform. Both supported, same encapsulation semantics. - Generators. A
fnwhose body containsyieldreturns an Iterator;yield fromdelegates to any iterable. - Algebraic effects.
effect fn/perform/handle … with, with multi-shotresume— one mechanism covering generators, exceptions, dependency injection, and backtracking search. - Decorators.
@exprbefore afnorclasswraps the declared value before it is bound. - Doctests in comments.
1 + 1 # => 2is an executable assertion underculebra test. - Structural assert diagnostics.
assert_eqprints a diff. - Defer / RAII. Scope-bound cleanup.
- Threaded concurrency. No
async/await.Isolate.spawnruns a closure on its own thread with its own heap, values cross by copy, andChannelcarries them — so two isolates cannot race on one object.SharedBuffer(fixed-layout, zero-copy) andShared(immutable, by reference) opt out of the copy where it matters.
Every subcommand below is part of the executable that runs a program.
| Command | What it does |
|---|---|
culebra test [paths...] |
run tests and the doctests written in comments |
culebra lint [paths...] |
report static problems without running the program |
culebra fmt [paths...] |
reformat source to the canonical style |
culebra dap |
speak the Debug Adapter Protocol over stdio (VSCode / Vim / Zed) |
culebra lsp |
serve diagnostics, hover and formatting to an editor over stdio |
culebra docs [topic] |
read and search the embedded reference docs |
culebra init |
set up this directory's AI agent instructions and this machine's editors |
culebra build <in.cul> -o <out> |
compile ahead-of-time into a standalone executable |
culebra wrap |
build an extended binary exposing your own C++ classes |
Details are in docs/tooling.md.
culebra build compiles a .cul source ahead-of-time into a
self-contained executable. No LLVM at runtime; tree-shaking drops
the ~500 runtime helpers a program doesn't reference. Tensor-free
programs also drop the Accelerate / Metal framework dependency.
culebra build path/to/script.cul -o ./out
./outThe link step runs the host's C++ compiler on macOS (Xcode's Command
Line Tools) and Linux (cc); on Windows the binary carries its own
linker, and culebra toolchain install fetches the mingw libraries it
links against. Everything else is inside the binary. Cross-compile is
also supported. Host requirements and binary sizes per feature axis are
in docs/deployment.md.
The bytecode VM embeds into a C++23 host: header-only, no LLVM, no culebra library to build or install. C++ and the script call each other, so a script can be what the host is configured — or extended — with.
#include <culebra.h>
int main() {
culebra::vm::Embed embed; // stdlib installed, traits registered
std::vector<double> readings{18.5, 41.2, 79.8}; // the host's own data
size_t call_count = 0;
// The argument and return types come from the signature; the host state
// the body reads and writes is an ordinary capture.
embed.define("temperature", [&](int64_t sensor) {
call_count++;
return readings.at(sensor);
}, {"sensor"});
embed.eval(R"(
config = {mut limit: 40.0}
fn too_hot(sensor) { temperature(sensor) > config.limit }
too_hot(0)
)");
// The session outlives the run, so the host reads what the script left,
// calls back into it, and writes into its values — an Object is a
// reference, not a copy.
auto config = embed.global("config");
auto limit = config["limit"].as<double>(); // 40.0
auto hot = embed.call("too_hot", 1).as<bool>(); // true: 41.2 > 40.0
config.set("limit", 50.0);
hot = embed.call("too_hot", 1).as<bool>(); // false, same script
// call_count is 3 by now: the script's own call, and the two above.
}Build it with the flags the engine itself reports:
c++ $(culebra embed-flags --cflags) host.cpp $(culebra embed-flags --libs) -o hostA failure in script code arrives as culebra::CulebraError, with the
kind, message and position the script's own catch sees.
docs/deployment.md
has the rest: the types define converts, the rest of the Value surface
over a script's Objects and Arrays, C++ classes through
culebra wrap,
the JIT path, threading, and interrupts.
- Two backends, one compiler. The bytecode VM and the JIT share one parser, AST, and bytecode compiler; both maintained, no plan to consolidate.
- Predictable threaded concurrency. No
async/await. Stack traces stay readable, the debugger sees every frame, and library authors don't write a colored version of every function. Targeted at services up to a few-thousand-connection ceiling — the shape SQLite, Redis, and most line-of-business backends already use. The reasoning is written out indocs/essays/concurrency.md. - Deterministic destruction, and no
weak. An object with adropproperty has it called when the last reference goes away — including for a reference cycle a scope leaves behind, so a back-reference to a parent needs no weak annotation. Reference counting drives the timing; a tracing collector backs it up for cycles. The reasoning is written out indocs/essays/memory.md. - Gradual typing without a compile step. Annotations are runtime-checked at boundaries; startup stays instant. Union, Optional, Tuple, Trait and Generic annotations are all in.
- Immutable by default, no declaration keyword. A bare
x = 1creates an immutable binding — reassigning it is an error. Usemutfor a variable you intend to change (mut x = 1; x = 2), somutmarks exactly what changes;letis an optional, explicit marker for the immutable form. The default binding is both safe and ceremony-free, and mutation is visible where it happens. - UFCS, not pipeline.
x.f(...)doubles as the resolution path for free functions over user types. - The stdlib needs no
import. Namespaces are bound before the program runs, so a one-file script has no import block. Splitting across files uses top-levelimport/export, which keeps the dependency graph known at parse time — what AOT bundling and tree-shaking need. - Batteries included. Everyday scripting needs ship in the binary, not as third-party packages.
- Pre-1.0. Releases follow semver's 0.x reading: a minor bump may break things. No package registry yet — download a binary or build it.
Culebra targets three forms of performance: fast startup, small binaries, and BLAS-class numerics.
- Tensor lands in the BLAS-bound cluster with numpy / Julia / PyTorch
CPU on the MNIST MLP. See
benchmarks/mnist/andbenchmarks/microgpt/. - The JIT is fastest on compute-bound loops.
- The bytecode VM is the tier-0 default: no JIT warmup, no LLVM dependency.
- The Culebra Handbook:
docs/handbook.md/ 日本語 - Language specification:
docs/language.md/ 日本語 - Standard library reference:
docs/stdlib.md/ 日本語 - Tooling — the test runner, linter, formatter, debug adapter, language
server and embedded docs (
culebra test/lint/fmt/dap/lsp/docs):docs/tooling.md/ 日本語 - Deployment — standalone binary build, embedding from C++, wrapping
C++ libraries (
culebra build/culebra wrap):docs/deployment.md/ 日本語 - Guides — task-oriented how-tos for building specific things with
culebra:
docs/guides/— currently parsing with PEG and building a desktop app - Quick guide — the syntax, the carried-over mistakes and the stdlib
signatures a program reaches for unprompted, condensed into one file;
written for an LLM prompt, and short enough for a human to read start
to finish:
docs/quick-guide.md/ 日本語 - Agent rules — the same starting point in the form a coding agent
reads, to append to
CLAUDE.md,.github/copilot-instructions.mdorAGENTS.md(culebra docs agent):docs/agent.md/ 日本語 - Essays — how a design decision was reached, at the length the
specification has no room for:
docs/essays/
Building from source and running the tests: CONTRIBUTING.md.