The ucode language
A small scripting language for Linux systems.
ECMAScript syntax, an embeddable C core, JSON as a native datatype, and a template engine built into the interpreter. Synchronous and dependency-light — OpenWrt's firewall and web interface are written in it.
# preinstalled on OpenWrt 22.03 and later
$ ucode -e 'printf("%J\n", { hello: "world", nums: [ 1, 2, 3 ] })'
{ "hello": "world", "nums": [ 1, 2, 3 ] }
A quick tour
Four things ucode does well — the examples marked run ▶ execute in your browser on a WebAssembly build of the interpreter, same VM, no server.
let passwd = `
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
`;
let users = map(
filter(split(trim(passwd), "\n"), (l) => length(l) > 0),
(l) => {
let f = split(l, ":");
return { name: f[0], uid: int(f[2]), home: f[5], shell: f[6] };
}
);
printf("%-8s %4s %-10s %s\n", "NAME", "UID", "HOME", "SHELL");
for (let u in users)
printf("%-8s %4d %-10s %s\n", u.name, u.uid, u.home, u.shell);
NAME UID HOME SHELL
root 0 /root /bin/bash
daemon 1 /usr/sbin /usr/sbin/nologin
The core builtins carry Perl's vocabulary — map, filter,
split, printf — and everything above is core: no imports, no classes,
no wrapper objects.
let rule = { name: "allow-dhcp", proto: "udp", ports: [ 67, 68 ] };
/* any value serialises with one format specifier... */
printf("%J\n", rule);
/* ...and JSON text parses back with the json() builtin */
let conf = json('{ "log": 64, "zones": [ "lan", "wan" ] }');
printf("%.4J\n", conf);
{ "name": "allow-dhcp", "proto": "udp", "ports": [ 67, 68 ] }
{
"log": 64,
"zones": [
"lan",
"wan"
]
}
Values are JSON-shaped, so json() parses and %J
serialises — one specifier, anywhere a format string works. Precision pretty-prints
(%.4J), empty precision indents with tabs (%.J).
$ cat hosts.tpl
127.0.0.1 localhost
{% for (let h in ["router", "switch"]): %}
10.1.0.1 {{ h }}
{% endfor %}
$ utpl -T hosts.tpl
127.0.0.1 localhost
10.1.0.1 router
10.1.0.1 switch
Template mode is part of the interpreter, not a library: expression and statement tags embedded in plain text, with whitespace control. This is what renders OpenWrt's nftables firewall ruleset.
#include <ucode/compiler.h>
#include <ucode/lib.h>
#include <ucode/vm.h>
static uc_value_t *
twice(uc_vm_t *vm, size_t nargs)
{
return ucv_int64_new(2 * ucv_int64_get(uc_fn_arg(0)));
}
int main(int argc, char **argv)
{
uc_parse_config_t config = { .raw_mode = true };
char *error = NULL;
uc_source_t *src = uc_source_new_file(argv[1]);
uc_program_t *prog = uc_compile(&config, src, &error);
uc_source_put(src);
uc_vm_t vm = { 0 };
uc_vm_init(&vm, &config);
uc_stdlib_load(uc_vm_scope_get(&vm));
/* expose a native function to the script */
ucv_object_add(uc_vm_scope_get(&vm), "twice",
ucv_cfunction_new("twice", twice));
uc_vm_execute(&vm, prog, NULL);
uc_program_put(prog);
uc_vm_free(&vm);
return 0;
}
$ cc -O2 host.c -o host -lucode
$ cat hello.uc
printf("twice(21) = %d\n", twice(21));
$ ./host hello.uc
twice(21) = 42
libucode links into any C program: compile, load the standard library,
register native functions, run. The same VM powers rpcd and uhttpd;
the C API covers values, exceptions, resource types and precompiled bytecode.
Design goals
ucode began as the template processor for OpenWrt's nftables firewall and grew into a general system scripting language. The goals it was designed around still hold.
syntaxECMAScript, minus the machinery
let, const, arrow functions, template literals, try/catch — familiar ground for JavaScript-experienced developers, and existing editor tooling keeps working.
dataJSON as a native datatype
Values are JSON-shaped — null, boolean, integer, double, string, array, object — so ubus calls, config files and HTTP payloads need no glue code.
numbersReal 64-bit integers
Distinct integer and double types with a guaranteed 64-bit integer range. No surprises when ids, timestamps and byte counts show up.
stdlibPerl-flavoured builtins
substr, index, splice, sprintf, split, join, trim — the core functions are functions, not methods on wrapper objects. 74 of them in the core alone.
regexPOSIX regular expressions
match, replace and wildcard are built in, compiled by the host libc's ERE engine — patterns behave like the rest of the system does.
flowSynchronous by design
One flow of control. Concurrency, when needed, lives in the uloop event loop module — not in the language, not in your head.
embedA C library first
libucode is a library before it is a program: a documented C API for values, VM state, native functions, resource types and precompiled bytecode.
modulesLoadable native modules
Each extension builds as an optional shared object, probed at configure time — the feature set is a build-time choice, not a runtime tax.
In production
ucode ships preinstalled with OpenWrt 22.03 and later. These projects run on it or embed it.
Latest commits
Live from the ucode-lang/ucode repository — recent work on master, no changelog to go stale.
The manual
A comprehensive ucode programming manual — covering the language, the standard library, the C embedding API and deployment in the wild.
Read the manual- IThe language — values, operators, functions, templates
- IIThe standard library — core builtins and every module
- IIINetworking & system integration — sockets, netlink, ubus
- IVEmbedding ucode in C — the API, walked line by line
- VDeployment & ecosystem — uhttpd, rpcd, firewall4, LuCI
Getting it
OpenWrt
opkg install ucode
Preinstalled on OpenWrt 22.03 and later.
Debian
apt-get install build-essential devscripts \
debhelper libjson-c-dev cmake pkg-config
git clone https://github.com/ucode-lang/ucode.git
cd ucode && dpkg-buildpackage -b -us -uc
Packaging recipes ship in-tree.
From source
git clone https://github.com/ucode-lang/ucode.git
cd ucode
cmake -DUBUS_SUPPORT=OFF -DUCI_SUPPORT=OFF \
-DULOOP_SUPPORT=OFF .
make
json-c is the only hard dependency; OpenWrt-specific modules toggle off.