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. 🏝️

example.cul
Playground ↗
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.

quickstart.sh
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.sh
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.