This reference describes the implemented native runtime. Historical paths and internal version labels are retained where relevant. Later extension chapters supersede older limitations.
The TCP/DNS/verified-TLS-client extension is specified in SOCKETS.md. socket connect accepts validated DNS names as well as numeric hosts; socket tlsconnect $h host port timeout_ms returns a verified client connection usable by the same send/read/close operations. socket is now a reserved command/function name. Socket destinations must be declared scalar variables. No other existing command grammar is changed.
The current work-log additions (foreach, typed calls/results, assertions/test runner, UTC/fixed-offset dates, CSV, gated SQLite and exclusive file creation) are specified in WORKLOG.md, including new reserved names and compatibility boundaries.
The utility extension reference is UTILITIES.md: full syntax for CLI/stdin, typed JSON/maps, try/catch, streaming files/list/copy/rename, HTTP/S, and named parameters. The core language below remains compatible with the preserved pre-utilities interpreter.
Commands are case-sensitive. One instruction per physical line. Blank lines are ignored. A # at the start of a whitespace-delimited token starts a comment; abc#def is ordinary text. Spaces/tabs separate tokens. Double quotes group tokens, including ""; single quotes are ordinary characters. CRLF input is accepted.
Escapes, both inside and outside double quotes: \n, \r, \t, \\, \", \$, \%, \&, \#. Unknown/dangling escapes and unmatched double quotes are errors. Any quoted/escaped token is literal: "$x" prints $x, whereas $x reads the variable. There is no interpolation within an ordinary token (prefix$x is literal). Exception: math treats all tokens after its destination as expression source, even quoted ones, so math $x "$x + 1" works.
Names match [A-Za-z_][A-Za-z0-9_]* with at most 128 bytes. Keywords cannot be function names. Values are scalar strings, arrays of strings, or explicitly typed JSON values. Strings and indices are byte-based, not Unicode graphemes. Upper/lower affect ASCII letters only. Ordinary numbers use finite binary64 arithmetic; formatting preserves 17 significant digits, so decimal fractions can expose floating-point rounding. JSON numeric safety and conversion rules are in UTILITIES.md.
Structure and values
include relative/path.v
function name argc
instructions
kill name
Includes and function declarations are top-level only. include resolves against the including file's directory, not the current working directory. Absolute includes are also accepted. Includes are loaded before execution; cycles, missing files, and duplicate function definitions are errors. Repeated includes are not deduplicated. Functions can call functions defined later. main must exist and take zero arguments. kill name must match the declaration.
var x y # create local empty scalars; no $ on names
set $x value ... # concatenate zero or more values; no separator
print value ... # concatenate to stdout; no newline
echo value ... # exactly the same as print
name arg ... # direct call, discard return value
call $dest name arg ... # call, store scalar return value
return value ... # concatenate and return; bare return means ""
exit # end the entire script successfully
Declare every destination before use. Variables are local to each function invocation, not block-scoped, and are never implicitly global. Redeclaration is a no-op, preserving scalar/array contents. Undefined variables are errors. Assigning a scalar replaces an array and vice versa. Arrays cannot be printed or used as scalar values directly; use join/array get.
%1 is the first function argument, %2 the second, consistently in all value positions and math expressions. %0 and out-of-range indices are errors. Arguments are passed by value, not references. The work-log extension also preserves native arrays and typed JSON for named parameters and explicit returnvalue; ordinary scalar %N contexts and concatenating return are unchanged. See WORKLOG.md. Exact declared arity is enforced, including direct calls in unreachable code. Falling off a function returns "". return only leaves the current call; exit leaves all calls. Named declarations such as function add a b additionally initialize $a and $b, retaining the original positional arguments. CLI script arguments after -- are separate and accessed with args commands (zero-based indexing).
Conditions and loops
if left operator right
...
else # optional; once per if
...
end if
loop left operator right # pre-test, zero or more executions
...
end loop
do # post-test, at least one execution
...
while left operator right
for $i start end [step] # exclusive end; default step +1
...
end for
break # exit nearest lexically enclosing loop
continue # next iteration of nearest enclosing loop
Operators: == != < > <= >=. If both operands parse as complete finite signed/scientific decimal numbers, compare numerically; otherwise compare strings lexicographically. Empty strings are not numbers. A comparison is exactly three operands, not an expression; calculate first when needed. No Boolean and/or syntax.
for evaluates start/end/step once on entry. Step must be finite and nonzero. Positive steps iterate while current < end, negative steps while current > end. An initially empty range still assigns start. The loop variable is ordinary mutable local storage; end for reads its current numeric value, adds the captured step, stores it, then tests the bound. It remains incremented after normal termination. Overflow or an increment too small to make progress is an error. break leaves its current value. A continue in for increments; in loop it retests the header; in do it evaluates while.
point label
...
goto label
A point is recorded only when executed, per invocation. goto can target only an already visited point in the same lexical block nesting, and resumes immediately after it. It cannot enter/leave a loop or if block. Repeated visits update the point without growing storage. goto is not a general forward-jump feature. Cross-branch jumps at identical enclosing block nesting are possible; prefer structured control flow.
Math
math $dest expression
math $x 2 + 3 * (4 - 1)
math $x pow(%1,2) + abs($y)
Tokens are joined with spaces into expression source. Numeric references $name/%N are parsed as numeric operands, never pasted as executable expression fragments.
Precedence, highest first:
1. Parentheses and function application.
2. Power ^, right-associative (2^3^2 = 512).
3. Unary + - (-2^2 = -4, 2^-2 = 0.25).
4. * / %, left-associative.
5. + -, left-associative.
% as a binary operator is floating remainder (fmod), including fractional operands; in an operand position %N is a function argument. Negative remainder follows the dividend's sign. Division/remainder by zero, overflow, invalid domains, nonfinite numbers, extra trailing syntax, unknown functions, and missing parentheses are errors. Hexadecimal, nan, and inf are not numeric literals. Decimal notation supports 1, -1.5, .5, and 1e-3. Underflow reported by the decimal conversion is rejected, too.
Functions and exact arities:
min(a,b) max(a,b) pow(a,b)
abs(x) sqrt(x) floor(x) ceil(x) round(x)
round rounds halfway away from zero. This is a corrected evaluator, not bit-for-bit emulation of historical mathemu bugs; see LINEAGE.md.
Strings and arrays
All output destinations must already exist. text, needle, etc. below are single value tokens; quote spaces or build values using set.
| Command | Result / behavior |
|---|---|
len $dest text |
Byte length as decimal string. |
substr $dest text start length |
Zero-based start (0 through text length); length must be an integer from 0 through 1,048,576 inclusive, then is clipped to the remaining text. Larger length operands are rejected even when the resulting substring would be short. |
find $dest text needle |
First zero-based match or -1; empty needle matches at 0. |
replace $dest text needle replacement |
Replace all nonoverlapping occurrences, without reprocessing replacements; empty needle is an error. |
upper $dest text / lower $dest text |
ASCII case conversion. |
trim $dest text |
Remove leading/trailing ASCII space, tab, CR, LF, form feed, vertical tab. |
split $array text delimiter |
Create array using a literal nonempty delimiter; preserve empty fields, including trailing ones. Empty input produces one empty item. |
join $dest $array delimiter |
Join array items; an empty array gives an empty string. |
array create $array [value ...] |
Replace destination with a new array, possibly empty. |
array push $array value |
Append one item. |
array get $dest $array index |
Read item. |
array set $array index value |
Replace existing item; does not extend the array. |
array len $dest $array |
Number of items. |
array pop $dest $array |
Remove and return final item; empty array is an error. |
Array indices are nonnegative integers, zero-based and bounds-checked. Fractional or excessive indices are errors. Numeric integer arguments may use decimal scientific notation if exactly integral and in range. Operations may capture into their source variable; e.g. array get $a $a 0 replaces array a with that scalar item.
Environment, files, time, randomness
env get $dest NAME
env set NAME value
NAME and value can be literal or scalar references. Get checks the script-wide set overlay first, then the inherited environment; missing values become "". Set changes the overlay shared across functions, not the OS process environment/parent shell. Names must be nonempty, without = or NUL; values cannot contain NUL. This implementation needs only the standard library, not a platform-specific environment setter.
file read $dest path
file write path text
file append path text
file exists $dest path
All four require CLI --allow-files. Paths resolve against the process working directory, unlike includes. Reads/writes are binary; writes truncate and append creates as needed. Missing read files, nonregular read/write targets, nonexistent parent directories, and stream failures are errors. Exists returns 1 or 0 for filesystem entries (including directories), with status failures reported. Symlinks are followed. Directories are not created automatically. Reads are limited to 1 MiB; writes are limited per value and by a total run budget. Embedded NUL in file contents is preserved; it is forbidden in paths. Not a sandbox or atomic-write API.
clock $dest # elapsed milliseconds since interpreter construction
random seed integer # 0..4294967295; default seed 0
random $dest low high # inclusive signed 32-bit integer bounds
Clock uses std::chrono::steady_clock, not wall time; it may return equal values for adjacent calls. Random uses std::mt19937 plus explicit rejection mapping, so the same seed/ranges/call order produce the same integer sequence across standard-library implementations. Seed 42 followed by five draws in [1,6] yields 1 6 5 5 1. This generator is not cryptographically secure. Reversed or nonintegral ranges are errors. Bounds can span the full signed 32-bit range.
Diagnostics and unsupported behavior
Exit status is 0 on success (including exit), 1 on errors. Errors go to stderr as file:line: error: message; CLI errors use <cli>:0. Loading validates block structure, commands, and arities throughout every function before running main; undefined values and math/type/domain errors are checked when executed. Prior output/file writes are not rolled back on failure.
DLL (dll, dllimport) and historical --cgi requests remain unavailable. The new supervised CGI gateway is supported separately; see Apache CGI. No shell, classes, Unicode-aware string transforms, or arbitrary host extension calls are promised. Structured arguments/results, foreach, assertions/tests, CSV/date/SQLite and exclusive file creation are specified in WORKLOG.md. Bounded HTTP/S and recoverable try/catch are explicitly supported by the utility extension. See SAFETY.md and UTILITIES.md for limits and trust boundaries.