An embeddable orchestration scripting language for robotics and physical systems, in pure C99.
Implements urbiscript — a prototype-based, parallel-by-default, event-driven language designed for coordinating sensors, actuators, and reactive control loops on fast underlying code. Sits above C/C++ control loops the way Lua sits above game engines: handles concurrency, time, events, and cancellation as first-class primitives instead of patterns the developer has to construct by hand.
Status: release candidate, tagged v0.13.6-consistency — a final pre-release hardening pass is in progress before the stable freeze. The language is complete — separators-encode-concurrency (; | , &), the reactive trio (at / whenever / waituntil), first-class tags with stop / block / freeze, prototype OOP, and try / catch / finally — backed by a tracing-free bytecode VM, incremental tri-color GC, and a cooperative scheduler with a 4-state urbi_step driver. ROS2 integration ships via a host rcl/Fast-DDS backend (with a documented micro-ROS-on-MCU path). Conformance: ~80–85% of the in-scope legacy urbiscript 2.x surface, 100% pass-rate on the implemented surface — see docs/release/conformance-report.md for the coverage breakdown and the intentional divergences. ABI 0/23/7, freezing to 1/0/0 at the stable tag; wire v1.9 / 0x19.
make # build liburbi.a + the urbi binary
echo "1 + 2" | ./build/host/urbi -i # -> [..........] 3
./build/host/urbi -i # interactive REPLEmbedding a VM in your own C program is one header and a handful of calls — see the embedding guide. Porting to a new MCU is covered by the ports guide and the worked ports under examples/ (Pico, ESP32-S3, STM32F4); a fresh clone builds all three via make clone-build-demo-check (see docs/release/clone-build-demo.md).
- Pure C99, single library, zero external dependencies
- Builds with
make— no CMake, no autotools, no bootstrap - Target footprint: < 400 KB flash on Cortex-M class MCUs
- Host-pluggable allocator, I/O sink, time source, panic handler
- No global state — multiple VM instances coexist, fully isolated
- Bytecode / source split: embedded targets can omit the compiler
- BSD-3-Clause throughout
| Target | Status | CI gate | Runtime smoke | Hardware evidence |
|---|---|---|---|---|
| Linux x86_64 (host) | shipped | host-test matrix | n/a | n/a |
| Raspberry Pi Pico (RP2040 / Cortex-M0+) | shipped | cross-pico + cross-pico-repl | none | yes — see docs/release/hardware-validation.md |
| ESP32-S3 (Xtensa LX7, ESP-IDF v6.0.1) | shipped | cross-esp32s3 | none | yes — eye_demo bring-up |
| STM32F4 (Cortex-M4F) | shipped | cross-stm32f4 | none | yes — Mandelbrot demo |
| ARM Cortex-M7 (generic) | shipped | cross-arm | none | n/a — archive build only |
| RISC-V rv32imc (generic) | shipped | cross-riscv | none | n/a — archive build only |
| STM32H7 | planned | n/a | n/a | n/a — see ROADMAP |
| ESP32-C3 | planned | n/a | n/a | n/a — see ROADMAP |
makeProduces build/host/liburbi.a. All build variants (release, debug, sanitizers, cross-compiles) land in build/<TARGET>/ subtrees — see CONTRIBUTING.md for the full list. The public API is spread across <urbi/types.h>, <urbi/urbi.h>, <urbi/gc.h>, <urbi/sched.h>, and <urbi/object.h> — VM lifecycle, chunk loading, strand spawn / step driver, ISR-safe event injection, realm globals, GC primitives, and the object surface. The headers are self-contained: external consumers using only -Iinclude resolve cleanly without internal includes. See docs/embedding-guide.md for the full embedding contract (host integration patterns, FreeRTOS pattern, REPL service).
Build the urbi binary:
make urbi-bin # produces build/host/urbiInteractive session:
./build/host/urbi -i
1 + 2
[00000001] 3
5 / 2
[00000012] 2.5Evaluate a single expression:
./build/host/urbi -e "1 + 2"
3Run a script:
./build/host/urbi script.urbDisassemble:
./build/host/urbi --dump-bytecode -e "1 + 2 * 3"See ./build/host/urbi --help for the full flag list.
Opt-in subsystem (build with URBI_ENABLE_REPL=1): NDJSON line-protocol REPL over TCP / Unix socket / UART, with bearer-token auth, per-session output isolation, and 9 introspection ops. Builds two extra host binaries: urbi-server (headless) and urbi-send (client).
Build:
make URBI_ENABLE_REPL=1 # liburbi.a with REPL support
make urbi-server-bin URBI_ENABLE_REPL=1 # build/host/urbi-server
make urbi-send-bin URBI_ENABLE_REPL=1 # build/host/urbi-sendStart a server on loopback (no token needed):
./build/host/urbi-server --port 54000From a second shell, send one-shot ops:
./build/host/urbi-send eval "1 + 2" # → 3
./build/host/urbi-send introspect coros # → JSON list of strands
./build/host/urbi-send --tail eval "every(1s) { echo 'tick' }"Exposing the server on a LAN interface requires --token:
./build/host/urbi-server --bind 0.0.0.0 --port 54000 --token "$(openssl rand -hex 16)"
./build/host/urbi-send --host robot.local:54000 --token "$TOK" eval "Robot.battery"Embedders can also combine local linenoise REPL + network service in one process via the urbi --listen flag, or start the service programmatically with urbi_repl_serve from <urbi/repl.h>. See docs/embedding-guide.md §12 (REPL Service) and docs/internals/repl-service.md for the full API + wire-protocol reference.
include/urbi/ public C API headers (urbi.h, gc.h, sched.h, object.h, ...)
src/
├── chunk/ bytecode + UProto + UChunkIO
├── emit/ compiler emit
├── event/ UEvent + native event registration
├── gc/ incremental GC + barriers
├── lex/ lexer
├── object/ UObject + UShape + UIC + UChunkInstance
├── parse/ parser
├── realm/ URealm + lobby + per-realm globals
├── repl/ REPL service + transports + listener
├── runtime/ UCallFrame + UUpvalCell + unwind + cleanup
├── sched/ cooperative scheduler + UStrand
├── stdlib/ baked stdlib + Object/List/Dict/etc.
├── tag/ UTag
├── value/ UValue + intern + arena
├── vm/ dispatch loop + OP_* handlers
└── watcher/ UWatcher + install/eval/drain/spawn
tools/ host binaries (urbi, urbi-server, urbi-send) + vendored linenoise
Subsystem-directory layout under src/; each subsystem is a
self-contained set of translation units. The tools/ directory
contains host binaries and vendored linenoise — neither is part of
liburbi.a.
CONTRIBUTING.md— build, test, cross-compile, and contribution how-tosdocs/STYLE.md— code-level style decisions (naming, const-correctness, error model, initialization, headers, tests)
BSD-3-Clause. See LICENSE.