Disturb is a stack-based VM and language with C-like syntax that compiles to compact RPN bytecode.
This README now includes the full function reference.
Requirements:
gcc(or compatible C compiler)makelibffiheaders/libs for default/full builds (DISABLE_SYSTEM=0)
Build:
make
./disturb --helpOptional flags:
make DISABLE_SYSTEM=1Install:
make installInstall options:
- change output binary name:
make TARGET=disturb-dev - change install path:
make install PREFIX=$HOME/.local - stage install root (packaging):
make install DESTDIR=/tmp/pkgroot - change installed file name:
make install INSTALL_TARGET=disturb
Flag behavior summary:
DISABLE_SYSTEM=0(default/full): enables IO natives,import, and dynamic FFI calls (C.ffi.open/C.ffi.sym/C.ffi.bind/C.ffi.callback)DISABLE_SYSTEM=1(embedded): disables features that depend on OS/file-system integration (IO natives,import, dynamic FFI calls), while keeping FFI core (C,C.memory,C.typedef/C.struct) available
MSVC build (Windows):
cmake -S . -B build-msvc -A x64 -DDISABLE_SYSTEM=1
cmake --build build-msvc --config Release
.\build-msvc\Release\disturb.exe --helpNotes:
- The MSVC embedded profile can be validated with
DISABLE_SYSTEM=1(FFI core remains enabled). - To enable dynamic calls (
C.ffi.open/C.ffi.sym/C.ffi.bind) under MSVC, provide alibffibuild (headers +.lib/.dll) and configure CMake paths accordingly.
disturb [script.urb] [args...]
disturb --compile-bytecode script.urb output.bytecode
disturb --run-bytecode output.bytecode [args...]
disturb --help
Notes:
- No-arg execution starts the REPL.
--compile-bytecodeemits raw bytecode.--run-bytecoderuns raw bytecode.
MDisturb is a Disturb script that reads .md files and treats fenced code blocks (... or ~~~...~~~) as Disturb source.
Commands:
./disturb mdisturb.urb print docs.md
./disturb mdisturb.urb extract docs.md [output.urb]
./disturb mdisturb.urb run docs.mdBehavior:
print: prints the extracted Disturb source.extract: writes extracted source to.urb(docs.md -> docs.urbby default).run: evaluates extracted source directly witheval.- language labels on fences are ignored; all fenced code is considered Disturb.
- non-fenced Markdown blocks like headings, lists and tables are securely parsed into native Disturb AST constructs under the
global.mdtable.
Disturb provides a native helper mdGenerate(obj) which takes a global.md-style table and produces a Markdown string. This is useful for tools that programmatically manipulate and re-emit Markdown.
Example:
md = import("tests/cases/markdown_parser_data.md"),
md_text = mdGenerate(md),
println(md_text),
msg = "hello",
if msg.size > 0 { println(msg) }
Run:
./disturb file.urbCore behavior:
- assignment is global by default:
x = 1,is equivalent toglobal.x = 1, - missing globals/keys evaluate to
null globalis a real tableglobal.commonstores common methods- method calls bind
thisto the target (obj.fn())
Type names used by .type:
nullintfloatcharstringtablenativelambdaview(FFI views)
Statements are separated by commas; the final comma in a sequence is optional (you can drop the trailing comma before a closing brace or end-of-file).
Comments:
// line comment
/* block comment */
Blocks use { ... }.
Table literal also uses { ... }, while lambda literal uses (args){ ... }.
Supported literal forms:
- int:
1 - float:
3.14 - int suffix:
1i - unsigned suffix alias (stored as int):
1u - float suffix:
1f - char:
'a'(must be exactly one byte) - string:
"abc" - numeric list shorthand:
1 2 3 - table:
{a = 1, b = "x"}
Special global:
inf(positive infinity float)
List behavior:
- numeric lists are homogeneous (
intlist orfloatlist) - mixed numeric literals like
1 2.5become float lists - int arrays are raw byte buffers; a string literal is an int array accessed via the
.stringview
Numeric list shorthand example:
a = 1 2 3,
b = 4 5 6,
println(a + b),
println(-a),
False values:
null- numeric zero (
0,0.0)
Everything else is true.
+ - * / %- unary
-
!&&||
< <= > >=== !==== !==
& | ^ ~ << >>
=+= -= *= /= %= &= |= ^= <<= >>=?=(assign only if target isnull)++ --(prefix and postfix)
Operator semantics highlights:
- comparisons/logical ops return numeric booleans (
1or0) +concatenates when one side is string/char==compares by value===compares strict identity/type-level equality semantics- assignment/compound/inc-dec forms are expressions (usable inside larger expressions)
== / !=:
- numbers compare by numeric value (
1 == 1.0is true) - strings compare by content
- tables compare structurally (deep, cycle-safe)
- functions compare by identity
=== / !==:
- strict identity-sensitive equality
- useful for reference identity checks
Supported forms:
obj.keyobj["key"]obj[k]wherekis string-like keylist[i]arr.string[i]— byteiof an int array as a 1-byte string (char)arr.u8[i],arr.u16[i],arr.u32[i],arr.u64[i]— elementiinterpreted as that width
Rules:
- numeric indexing is 0-based
- out-of-range numeric index errors
- key indexing is for tables
arr.string[i]yields a single-byte string (char); assignment accepts a single-byte string or byte integer- raw
arr[i]on an int array returnssizeof(Int)-wide elements (8 bytes on 64-bit)
- top-level assignments write to
global - lambda bodies use local scope
localis available inside lambdas- globals are still reachable via
global.name
add = (a, b){ return a + b, }
Parameters:
- positional params
- default params:
(a, b = 10){ ... } - varargs:
(head, ...rest){ ... }(must be last) - missing non-default params become
null
Returns:
return expr,return,returnsnull
Calls:
- regular call:
add(1, 2) - method call with
this:obj.add(7) - table-call convention: if a table has a method with its own name, calling the table invokes it
Supported:
if x { ... }orif (x) { ... }(parentheses optional)if x { ... } else if y { ... } else { ... }while x { ... }(parentheses optional)for init, cond, step { ... }orfor (init, cond, step) { ... }(parentheses optional)each x in expr { ... }oreach (x in expr) { ... }(parentheses optional)switch x { ... }orswitch (x) { ... }(parentheses optional; usecaseanddefault)break,continue,- labels and
goto
Switch behavior:
- first matching case executes
- no fall-through
defaultruns when no case matches
Assignment shares references:
a = {x = 1, y = {z = 2}},
b = a,
b.y.z = 9,
println(a.y.z), // 9
Copy helpers:
clone()shallow copycopy()deep copy
c = a.clone(),
d = a.copy(),
Each entry exposes metadata fields:
.name.type.value.size.capacity
Common uses:
- inspect runtime shape:
println(x.type), - resize containers:
x.size = 10,,x.capacity = 32, - replace value while keeping identity slot:
x.value = {...},
.size semantics depend on the type/view:
table: number of entriesintarray (raw):total_bytes / sizeof(Int)floatarray (raw):total_bytes / sizeof(Float)arr.string.size:strlen(bytes up to first\0)arr.u8.size/arr.i8.size:total_bytes / 1arr.u16.size/arr.i16.size:total_bytes / 2arr.u32.size/arr.i32.size:total_bytes / 4arr.u64.size/arr.i64.size:total_bytes / 8arr.f32.size:total_bytes / 4arr.f64.size:total_bytes / 8
Important constraints:
.nameexpects string ornull.sizeexpects integer.capacityexpects numeric value
Disturb installs common functions in global.common, so they are callable as methods and as globals.
describeprintprintlnlenprettyclonecopytoInttoFloatgc
read(path)write(path, data)
importevalparseemitevalBytecode
appendadd sub mul div mod powsadd ssub smul sdiv smod spow(vector + scalar)min max abs floor ceil round sqrtsin cos tan asin acos atan log exp
slicesubstrsplitjoinupperlowertrimstartsWithendsWithreplacereplaceAllpapagaio
keysvalueshasdeletepushpopshiftunshiftinsertremove
describe(...)prints values with typed/literal style (e.g.[int x] [42]), followed by a newline.print(...)prints plain values without a trailing newline.println(...)prints plain values followed by a newline.- All three can be called as methods:
x.describe(),x.print(),x.println(). - With no arguments, they read the top of stack when available.
CLI arguments are exposed as globals:
arg_0,arg_1, ...argstableargc(int value)
Example:
println(argc),
println(args.pretty()),
println(arg_0),
import(path) behavior:
- if
pathends with.urb, loads that file directly - if
pathends with.md, extracts fenced code blocks and loads the extracted Disturb source directly - otherwise loads package entry:
path/<basename(path)>.urb - if package
.urbentry is missing, falls back topath/<basename(path)>.mdand extracts fenced code blocks - module runs in isolated VM
- module export is the top-level
returnvalue - loaded modules are cached by resolved path
Examples:
import("tests/modules/math.urb")import("tests/modules/math.md")import("tests/modules/pkg")-> loadstests/modules/pkg/pkg.urbexamples/import_markdown.urbdemonstrates direct.mdimport and package fallback to.md
Compile and run from source text:
bc = parse("println(1 + 2),"),
println(emit(bc)),
evalBytecode(bc),
Assembler/disassembler example is provided in:
examples/asm_lib.urb
Papagaio processing applies to string literals containing $.
Use \$ to keep literal $.
Source-level compile-time preprocessing is automatic for papagaio declarations
outside strings/comments ($pattern{...}{...}, $eval{...}).
Papagaio declarations inside string literals stay runtime behavior.
Examples:
examples/papagaio_preprocess_basic.urbexamples/papagaio_preprocess_capture.urbexamples/papagaio_preprocess_macro.urbexamples/papagaio_preprocess_mixed.urb
Supported patterns include:
$pattern{...}{...}$eval{...}
Runtime API:
papagaio(text)papagaio(text, pattern, replacement)
Papagaio runtime context is exposed under global.papagaio (for content and match access inside eval blocks).
Variables in patterns use the $name syntax for free-form capture. Modifiers constrain what a variable matches by appending $modifier after the variable name:
$name$modifier
| Modifier | Matches | Example |
|---|---|---|
$int |
digits, optional leading - |
$v$int matches 42, -7 |
$float |
digits, ., optional leading - |
$v$float matches 3.14, -0.5 |
$number |
same as $float |
$v$number matches 42, 3.14 |
$hex |
hex digits (0-9a-fA-F), x/X |
$v$hex matches 0xFF, AB12 |
$binary |
0, 1, b/B |
$v$binary matches 0b1010, 110 |
$percent |
digits, ., %, optional leading - |
$v$percent matches 99.9%, -5% |
| Modifier | Matches | Example |
|---|---|---|
$upper |
uppercase letters only | $v$upper matches ABC |
$lower |
lowercase letters only | $v$lower matches abc |
$capitalized |
first char upper, rest lower | $v$capitalized matches Hello |
$word |
alphabetic characters only | $v$word matches Hello |
$identifier |
alphanumeric + _, cannot start with digit |
$v$identifier matches my_var1 |
$path |
any non-whitespace characters | $v$path matches /usr/bin/local |
| Modifier | Description | Example |
|---|---|---|
$name$aliases{a, b, c} |
matches one of the listed alternatives and captures it | $fruit$aliases{apple, banana} |
$name$optional{text} |
optionally matches the literal text, captures it if found | $x$optional{the } |
$name$starts{prefix} |
captures content that must start with the given prefix | $v$starts{http} |
$name$ends{suffix} |
captures content that must end with the given suffix | $v$ends{.txt} |
// Type modifiers
println(papagaio("price is 42", "price is $v$int", "$v")), // 42
println(papagaio("temp 36.6", "temp $t$float", "$t")), // 36.6
println(papagaio("color: FF00AA", "color: $c$hex", "$c")), // FF00AA
// Text modifiers
println(papagaio("HELLO world", "$a$upper $b$lower", "$a-$b")), // HELLO-world
// Aliases (captures the matched alternative)
println(papagaio("i like apples", "i like $f$aliases{apples, oranges}", "$f")), // apples
// Optional (captures the text if present, empty string otherwise)
println(papagaio("hello world", "$g$optional{hello }world", "$g")), // hello
// Starts / Ends
println(papagaio("http://example.com", "$url$starts{http}", "$url")), // http://example.com
Manual GC helpers are under global.gc:
collect()free(value)sweep(value)new(size)debug()stats()
Runtime flags:
global.gc.keyintern = 0|1
Runtime C integration is exposed under global C:
- runtime-only type helpers:
C.typedef,C.enum,C.define,C.struct - FFI calls/integration:
C.ffi.* - memory/layout/view APIs:
C.memory.* - runtime/platform info:
C.info()
Dynamic foreign calls (C.ffi.open, C.ffi.sym, C.ffi.bind) require DISABLE_SYSTEM=0.
Main API:
C.info()C.typedef(name, type)C.enum(name, fields)C.define(name, value)C.struct(name, schema)C.defines(constants table)C.ffi.open(libPath[, flags])C.ffi.sym(libHandle, symbolName)C.ffi.symSelf(symbolName)C.ffi.close(libHandle)C.ffi.bind(ptr, "signature")C.ffi.callback("signature", lambda)C.ffi.auto(libOrProxy, sig)C.ffi.lib(path)C.ffi.global(lib, name, typeOrSchema)C.ffi.errno()C.ffi.dlerror()C.ffi.trace()/C.ffi.trace(0|1)C.memory.compile(schema)C.memory.new(schemaOrLayout)C.memory.struct(schemaOrLayout[, init])C.memory.free(ptr)C.memory.buffer(len)C.memory.string(ptr)/C.memory.string(ptr, len)C.memory.point(value)C.memory.valid(ptr)C.memory.read(ptr, type[, len])C.memory.write(ptr, type, value)C.memory.copy(dst, src, len)/C.memory.move(dst, src, len)/C.memory.zero(ptr, len)C.memory.offset(ptr, byteOffset)/C.memory.offset(ptr, index, elemTypeOrSchema)C.memory.cast(ptr, schemaOrLayout)C.memory.deref(ptr[, schemaOrType])C.memory.sizeof(schemaOrLayout)C.memory.alignof(schemaOrLayout)C.memory.offsetof(schemaOrLayout, "field.path")C.memory.view(ptr, schemaOrLayout[, totalSize])C.memory.viewArray(ptr, elemSpec, len)
Notes:
- Two-step loading is supported via
C.ffi.open+C.ffi.sym+C.ffi.bind. C.ffi.symSelf(name)resolves a symbol from the current process without an explicit library handle.C.ffi.errno()returns the current thread-local Cerrnovalue.C.ffi.dlerror()returns the last dynamic-loader error string ornull.C.ffi.open(path, flags)accepts bitwise-OR loader flags fromC.ffi.RTLD_LAZY,C.ffi.RTLD_NOW,C.ffi.RTLD_LOCAL,C.ffi.RTLD_GLOBAL,C.ffi.RTLD_NODELETE,C.ffi.RTLD_NOLOAD.C.memory.compile(schema)is optional for normal use.C.memory.view/sizeof/alignof/offsetof/newaccept either a schema table or a compiled layout handle; schema tables are auto-compiled and cached internally.C.memory.point(value)returns a numeric pointer for list/view data or existing pointer-like FFI values (nullmaps to0).C.memory.viewandC.memory.viewArrayexpose.byteSize; array views also expose.len.- packed/forced-align structs remain pointer-only for calls (by-value ABI limitation).
Signature struct typing:
- by-value struct:
struct(schema)(example:i32 sum(struct(outer))) - by-value union:
union(schema)(example:i32 inspect(union(bits))) - typed pointer:
pointer(schema)(example:void free_outer(pointer(outer))) - raw/generic pointer stays
void* - pointer depth in signatures must use nested
pointer(...)(example:pointer(pointer(i32))) - string-ish types:
string: marshals to/from Disturb stringscstring: raw C pointer semantics (useC.memory.string(ptr)when needed)
- optional ABI prefix in signatures:
abi(name)or bare ABI name (cdecl,stdcall,fastcall,thiscall,win64,unix64,sysv)
Schema composition:
- schema fields must be type strings
- use
struct(otherSchema),union(otherSchema), orpointer(otherSchema)inside field declarations - function pointer fields:
function(signature)(example:cb = "function(i32 cb(i32, i32))") - unions:
__meta = { union = 1 } - bitfields: use
"type:bits"(example:"uint8:3","uint32:5") - qualifiers accepted in schema/signatures:
const,volatile,restrict - variadic signatures accepted via
...inC.ffi.bind
Const behavior in views:
- warns and ignores write
Ownership:
C.memory.newreturns an owned pointer handle (GC releases memory if unreachable)C.memory.free(ptr)can free explicitely (works with owned handles and raw pointers)
Supported workflow:
- call C functions
- map pointers to callable functions
- build callbacks with scalar and by-value struct/union signatures
- compile C-like struct schemas
- create live pointer-backed struct views
- handle nested structs and fixed arrays in layouts
- by-value packed/forced-align schemas are still rejected (ABI-sensitive edge case)
Examples:
examples/guide/11_ffi_system.urbexamples/guide/14_ffi_struct_views_bind.urbexamples/guide/15_ffi_callbacks_varargs_buffers.urbexamples/ffi_view_struct.urbexamples/ffi_callbacks_varargs_buffers.urbexamples/ffi_fnptr_fields.urbexamples/ffi_auto_compile_optional.urb
Main language tests:
tests/run.shRun all examples:
tests/run_examples.shThis file documents runtime functions currently exposed in global.common and global.gc.
Calling style:
- Most functions support both forms:
- global:
fn(target, ...) - method:
target.fn(...)
- global:
- In method form,
targetbecomesthis.
Return conventions:
1/0are numeric booleans.- Some mutators are side-effect oriented and should not be relied on for return values.
- Prints values in typed/literal style (e.g.
[int x] [42]), followed by a newline. - If called with no arguments, prints top of stack (or
(stack empty)). - As a method, prints the receiver in typed style.
- Prints values in plain style without a trailing newline.
- If called with no arguments, prints top of stack.
- As a method, prints the receiver.
- Prints values in plain style followed by a newline.
- If called with no arguments, prints top of stack plus newline.
- As a method, prints the receiver followed by a newline.
- Returns logical length (same semantics as
.size). - Int arrays:
total_bytes / sizeof(Int)(native-width element count). - Float arrays:
total_bytes / sizeof(Float). - String literals /
.stringviews:strlen(bytes up to first\0). - Tables: entry count.
- Numeric list shorthand is valid:
a = 1 2 3,thenlen(a)returns3.
- Returns a formatted multiline representation.
- Returns shallow copy.
- Returns deep copy.
- Converts float list to int list.
- Errors if target is not float list.
- Converts int list to float list.
- Strings are converted byte-by-byte to float values.
- Errors if target is not int-based list.
pathmust be string.- Returns byte-string content.
- On failure, prints error and returns
null.
pathmust be string.datacan be string or any value (non-string is stringified).- Returns
1on success,0on failure.
pathmust be string.- Rules:
- if
.urb, load directly - otherwise load
path/<basename(path)>.urb
- if
- Executes module in isolated VM.
- Module export is top-level
returnvalue. - Uses module cache by resolved path.
sourcemust be string.- Executes source in current VM.
- Returns
null.
sourcemust be string.- Compiles source to bytecode bytes.
- Returns byte-string.
- If
xis bytecode bytes: returns disassembly text. - If
xis bytecode AST table: returns encoded bytes. - Otherwise errors.
- Executes bytecode bytes.
- Returns
null.
- Runs collection.
- Returns
null.
- Attempts immediate free and sets value to
nullentry. - Returns
1on success. - Fails for protected/shared entries.
- Marks value for reuse and resets entry to
null. - Returns
1on success. - Fails for protected entries.
- Creates a table with reserved capacity.
sizemust be non-negative integer.
- Prints reuse-pool diagnostics.
- Returns
null.
- Prints GC memory stats snapshot.
- Returns
null.
Runtime flags on gc object:
gc.keyintern = 0|1
Note:
gc.flush()exists internally but is not currently exposed as a field onglobal.gc.
All below support global and method form when applicable.
- String-only append.
- Mutates destination string in place.
- Numeric sum.
- Numeric subtraction chain.
- Numeric multiplication chain.
- Numeric division chain.
- Numeric modulo (
fmod).
- Exponentiation.
- Minimum value.
- Maximum value.
- Absolute value.
- Floor.
- Ceil.
- Round.
- Square root.
- Sine.
- Cosine.
- Tangent.
- Arc-sine.
- Arc-cosine.
- Arc-tangent.
- Natural logarithm.
- Exponential.
- Returns substring by half-open range
[start, end). - Accepts negative indexes.
- Clamps to valid range.
- Returns substring by start + length.
- Returns table of substrings.
- Empty delimiter splits into single-byte chunks.
- Stringifies each element and joins.
- Uppercases ASCII bytes.
- Lowercases ASCII bytes.
- Trims surrounding whitespace.
- Returns
1/0.
- Returns
1/0.
- Literal substring replacement (first occurrence only).
- Literal substring replacement (all occurrences).
- Runs Papagaio processing and returns transformed string.
- Returns table with key names as strings.
- Returns table with value references.
- For tables:
- string key lookup
- For list-like targets:
- numeric index check
- Returns
1/0.
- Deletes by key/index.
- Returns
1on delete,0if not found/out-of-range.
- Appends values to end.
- Table: appends entries.
- String: appends string bytes.
- Int list: appends integral numbers.
- Float list: appends numeric values.
- Side-effect oriented.
- Removes and returns last item.
- Table/string/int-list/float-list supported.
- Removes and returns first item.
- Table/string/int-list/float-list supported.
- Inserts values at front.
- Type rules mirror
push. - Side-effect oriented.
- Inserts at index.
- String/list/table supported.
- Side-effect oriented.
- Table:
- string key removes matching key and returns removed value
- integer index removes positional entry and returns value
- String/list:
- removes at numeric index and returns removed element
Runtime C integration is exposed under global C.
Primary APIs used by examples/tests:
C.info()C.typedef(name, type)C.enum(name, fields)C.define(name, value)C.struct(name, schema)C.ffi.open(libPath[, flags])C.ffi.sym(libHandle, symbolName)C.ffi.symSelf(symbolName)C.ffi.close(libHandle)C.ffi.bind(ptr, sig)C.ffi.callback(sig, lambda)(builds C callback pointer from lambda)C.ffi.auto(libOrProxy, sig)/C.ffi.lib(path)C.ffi.global(lib, name, typeOrSchema)C.ffi.errno()/C.ffi.dlerror()C.ffi.trace()/C.ffi.trace(0|1)C.memory.compile(schema)C.memory.new(schemaOrLayout)(allocates zeroed struct memory and returns owned pointer handle)C.memory.struct(schemaOrLayout[, init])C.memory.free(ptr)C.memory.buffer(len)(owned raw byte buffer)C.memory.string(ptr)/C.memory.string(ptr, len)C.memory.point(value)(returns numeric pointer; supports numeric/string lists, numeric views, and pointer-like FFI values)C.memory.valid(ptr)C.memory.read(ptr, type[, len])/C.memory.write(ptr, type, value)C.memory.copy(dst, src, len)/C.memory.move(dst, src, len)/C.memory.zero(ptr, len)C.memory.offset(ptr, byteOffset)/C.memory.offset(ptr, index, elemTypeOrSchema)C.memory.cast(ptr, schemaOrLayout)/C.memory.deref(ptr[, schemaOrType])C.memory.sizeof(schemaOrLayout)C.memory.alignof(schemaOrLayout)C.memory.offsetof(schemaOrLayout, "field.path")C.memory.view(ptr, schemaOrLayout[, totalSize])C.memory.viewArray(ptr, elemSpec, len)- loader flags for
C.ffi.open:C.ffi.RTLD_LAZY,C.ffi.RTLD_NOW,C.ffi.RTLD_LOCAL,C.ffi.RTLD_GLOBAL,C.ffi.RTLD_NODELETE,C.ffi.RTLD_NOLOAD C.memory.compile(schema)is optional in common flows; schema tables are auto-compiled/cached when passed toC.memory.view,C.memory.sizeof,C.memory.alignof,C.memory.offsetof, andC.memory.new.- signatures support:
struct(schema)(by-value struct),union(schema)(by-value union),pointer(schema)(typed pointer),void*(raw pointer), pointer depth viapointer(pointer(...)) - string-like types in signatures:
string: marshaled as Disturb stringcstring: raw C pointer value
- optional ABI prefix in signature:
abi(name)or bare ABI keyword (cdecl,stdcall,fastcall,thiscall,win64,unix64,sysv) - schema field declarations are strings only; compose with
"struct(name)","union(name)", and"pointer(name)" - function pointer fields:
"function(signature)"(example:"function(i32 cb(i32, i32))") - bitfields are declared as
"type:bits"(e.g.,"uint8:3") - unions are declared via
__meta = { union = 1 } - qualifiers accepted in signatures/schema strings:
const,volatile,restrict - view write behavior on
constfields/elements:- warns and ignores write
- variadic signatures are supported with
...(forC.ffi.bind) - callbacks support scalar and by-value struct/union signatures (callback variadics still unsupported)
See:
examples/guide/11_ffi_system.urbexamples/guide/14_ffi_struct_views_bind.urbexamples/guide/15_ffi_callbacks_varargs_buffers.urbtests/cases/ffi_view_struct.urbtests/cases/ffi_varargs.urbtests/cases/ffi_callbacks.urbtests/cases/ffi_auto_compile.urbtests/cases/ffi_buffers_strings.urbtests/cases/ffi_const_views.urb