subverge

The language in detail

How omega reads a line, what values look like inside, and how a name is looked up. Read omega first.

Lexical structure

A line is split into tokens on whitespace. Each token is, in this order of precedence:

Strings recognise \n \t \e \\ \", plus \w and \A, which are the bytes 0x80 and 0x81, reprogrammed into the font as omega and aleph. Unknown escapes keep the character after the backslash. Nesting of brackets is limited, past which the parse fails with the brackets are nested too deep. The whole line is parsed before any of it runs, so a parse error means nothing on the line happened.

Values

A value is 24 bytes, made of a type, a 64-bit integer and a pointer to a heap object. These are the types:

Type Payload Notes
integer the i64 when it fits in 64 bits
bignum heap object only when an integer outgrows 64 bits, see bignums
string heap object immutable bytes with a length, not NUL-dependent
quote heap object an array of values, immutable once built
symbol interned name 'x and "x" >sym are the same object
word interned name only appears inside quotes
ordinal heap object Cantor normal form, see how infinity works
cardinal the i64 n for ℵn, -n-1 for 2^ℵn

Heap objects belong to the tab's collector.

Evaluation

A quote is evaluated left to right. Literals push themselves. A word is resolved and run. :name pops one value and binds it in the tab's dictionary. ->name pops one value and either updates the innermost bound local called name or, if there's none, binds a new local. { a b c } pops as many values as names and binds them so that the last name gets the top.

When a word is run, it's resolved in this order: bound locals, innermost first; the tab's dictionary; root's dictionary; built-ins. A binding whose value is a quote is called. Any other bound value is pushed. A tab binding with no value, which is what forget creates, ends the search of the tab's dictionary and lookup carries on in root, so forgetting a tab definition uncovers whatever it shadowed.

Locals live on a single per-tab array of 256 entries, pushed when bound and popped when the binding quote returns. Lookup scans it from the top, which makes scoping dynamic: a callee sees its caller's locals. Exceeding 256 live locals fails with too many local names.

Call depth is limited, and exceeding it fails with this nests too deep. The data stack holds 1024 values.

Limits has the numbers, and how worlds work explains what happens after a line runs.