culebra
A dynamically-typed scripting language that runs three ways from one source: bytecode VM, LLVM JIT, and AOT compiler. culebra is a beautiful island in Puerto Rico. 🏝️
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}!") }
One executable, every tool
The same binary that runs your program also builds, tests, lints,
formats, and documents it.
build,
test,
lint,
fmt,
dap,
docs,
serve,
init, and
wrap
are all built in — nothing else to install.
The standard library is baked in too: JSON, HTTP, Regex, PEG, FS, Tensor, SQLite, Canvas, Search, and more are bound before your program runs. No package manager. No lockfile.
Try culebra on your machine
Paste this into a terminal on macOS (Apple Silicon) or Linux
(x86-64) — it installs culebra and writes a one-line program to
hello.cul, then runs that same source file three ways:
as a script, JIT-compiled, and built into a standalone binary — all
three print the same line.
curl -fsSL https://raw.githubusercontent.com/yhirose/culebra/master/install.sh | sh
culebra --version
cat > hello.cul <<'EOF'
println("Hello!")
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
Windows and Homebrew are in the README.
Set up your dev environment
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 init
culebra 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.
For the things you want to make
A desktop app
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"
})
},
})
That's the whole app — a local server, your UI (baked in — see Embedded assets in the README), and the Desktop/Webview that shows it, shipped as one native binary. See Building a desktop app for more.
A retro game
Sprites, collision, chiptune audio — all Canvas, running live in your browser — source. Click to play. Ported from Brendan Hansknecht's Roc/WASM-4 demo (art by Luke DeVault), from Luke Boswell's roc-wasm4 platform, UPL-1.0 licensed.
A neural network
Handwritten-digit recognition, end to end in culebra: a small MLP trained with the built-in Tensor autograd (training script), the pad and the probability chart drawn with Canvas — source. Draw a digit.
A C++ host
#include <culebra.h> int main() { culebra::vm::Embed embed; std::vector<double> readings{18.5, 41.2, 79.8}; // the host's own data embed.define("temperature", [&](int64_t s) { return readings.at(s); }); embed.eval(R"( config = {mut limit: 40.0} fn too_hot(s) { temperature(s) > config.limit } )"); auto hot = embed.call("too_hot", 1).as<bool>(); // 41.2 > 40.0 embed.global("config").set("limit", 50.0); // into the script's own value std::println("{} {}", hot, embed.call("too_hot", 1).as<bool>()); // true false }
c++ $(culebra embed-flags --cflags) host.cpp \
$(culebra embed-flags --libs) -o host
No LLVM dependency — the bytecode VM drops straight into a C++23 host through a minimal session API, and the engine reports the flags to build against it with. See Embedding in a C++ host in the README, and deployment.md for what those flags are, the JIT path and threading.