# vla-lang
**The VLA language with nothing around it: forms, their expansion, the
grid, the view record, and the machine that steps a grid.**
Frazaro is a language for spreadsheet work: you write the steps of a
procedure as English sentences, and Frazaro checks every sentence, refuses
the ones it cannot read, in words, and carries out the rest. VLA is the
Lisp beneath that English, the form every sentence translates into before
anything is emitted or built. This crate is that language alone: the reader
and the printer of its forms, the macro expander, the head table that names
the core forms, the refusal catalogue's mechanism with the language's own
refusals, the sheet model with its A1 coordinates and the references read
out of a formula's text, the view record, one window of a sheet as the
lines a viewport draws from, and recalculation's mechanism, the formulas
of a grid computed in dependency order with a budget in cells. No English,
no file format, no clock, and no dependency. Built for WebAssembly, the
module's import section is empty.
[`frazaro-core`](https://crates.io/crates/frazaro-core) stands on this crate
and adds the bridges: the English engine with a proof per sentence, the VBA
and formula emitters, the OOXML and OpenDocument writer and readers.
[`frazaro`](https://crates.io/crates/frazaro) is the command line over both.
An engine that runs a grid as a game stands on this crate and never on the
core, which is why the language is a crate of its own. The Excel add-in in
the repository is the reference implementation; this crate is held to the
same corpus of tests, so a form means the same thing here as there and is
refused in the same words.
## What it does
- **Reads and prints VLA.** `reader::read_forms` takes a text to its forms,
each a symbol, a string, a number or a list with the line it came from;
`printer::write_datum` and `printer::write_pretty` write a form back, as
the add-in writes its goldens.
- **Expands macros.** `expand::expand_text` takes a program's text and a
prelude's, defines the prelude's macros and expands every form to a
fixpoint, refusing a malformed definition or a runaway expansion by name.
The prelude is a library the caller passes: the repository's
[scripts/prelude.vla](https://raw.githubusercontent.com/Spreadsheet-Company/Frazaro/main/scripts/prelude.vla)
is the standard one, and a program may carry its own definitions.
Twenty-seven primitives compute while a macro expands, over the data
written in the source: the list operations (`car`, `cdr`, `cons`,
`quote-if`, `cond` and their kin), arithmetic and comparison (`+expand`,
`-expand`, `*expand`, `quotient-expand`, `mod-expand`, `=expand` and the
rest), text joined or split into its characters (`&expand`,
`characters-expand`), and a cell's address written from a row and a
column or read back (`address-expand`, `row-expand`, `column-expand`,
`column-letters-expand`). A name that ends in `expand` is computed while
the macro expands; each refuses by name what it cannot compute.
- **Names the core forms.** `headtable::row` and `headtable::resolve_head_alias`
answer for every form the language has, its aliases and its arity, from
the table the add-in's own code exports.
- **Holds the grid.** `sheet::Workbook`, `sheet::Sheet`, `sheet::Cell` and
`sheet::Content`: sheets of cells, each a text, a number, a truth value or
a formula's text, with column widths, a style table and the row of the
sentence that wrote each cell. `Sheet::set_formula` fills a range with one
formula as Excel fills it, the references moved cell by cell.
- **Draws a number under a format's code.** `numfmt::Code::parse` takes a
code of Excel's, such as `0.00` or `▲0.00;▼0.00;=0.00`, its sections,
digit placeholders, thousands, thousandths, percents and literals, and
names whatever else a code holds that this version does not draw;
`Code::text` gives the text a value draws as under it, as Excel's
`Range.Text` does: the number's fifteen significant digits, rounded half
away from zero to the code's decimals. A style's `sheet::NumFmt` is
`General`, `Text` or a `Code`.
- **Reads references.** `refers::scan` finds every reference inside a
formula's text, cells, ranges, columns, rows, names, Tables, external
books and 3D spans, and names the calls it cannot read; `refers::r1c1`
renders a formula relative to its cell, `refers::shift_a1_references` is
the mover a fill uses, and `refers::quote_sheet` is the one sheet-quoting
rule.
- **Draws the view record.** `view::window_of` finds a sheet and a window
in a model, and `view::view_text` prints that window as lines: every
sheet, the window and the sheet's extent, its columns and formats, then
each cell's value or formula, the text a number draws under its format's
code, its format and the row of the sentence that wrote it. It is the
first implementation of `projection::Projection`, the
seam a second projection enters by. `view::view_text_valued` prints the
same record with each formula's computed value after it. The second,
`view::ValueGrid`, is the same record less its formula rows, the values a
host draws every frame without moving every formula's text:
`view::view_text_values` prints it.
- **Computes the grid.** `calc::Calc::new` reads every formula off a
`Workbook`, builds the dependency graph from the references and finds the
order, naming every cycle; `Calc::step` evaluates the next cells of that
order up to a budget in cells and says how far it got, so an engine can
run a grid at a frame rate and yield; `Calc::run` evaluates everything.
The parser reads Excel's operators and calls over the reference scanner,
with Excel's precedence and coercions and its seven error values as a
value kind. Functions enter through `calc::Library`, the seam: the
language registers the fourteen a game's rule needs on day one (`IF`,
`AND`, `OR`, `NOT`, `SUM`, `MIN`, `MAX`, `ABS`, `INT`, `MOD`, `ROW`,
`COLUMN`, `CHOOSE`, `SIN`), and a crate above registers more, as
`frazaro-core` registers Excel's library by measurement. A formula that
calls anything else is not computed, and says so naming the function; a
cell that reads it inherits the label. `Calc::first_refusal` is what an
engine's load asks: a cycle, a function outside the library, or a
construct the parser does not read, each refused by name from the
catalogue.
- **Runs a grid as a machine.** `machine::Machine` is a grid kept between
calls, for a game engine that steps it at a frame rate: `Machine::load`
reads the rows a view or a reflect prints back into a grid, the record's
inverse; `write` takes `cell`, `formula` and `derived` rows, all or none;
`step` computes every formula in dependency order with a budget in cells,
yields and resumes, and at the end of each frame copies every sheet's
values into its twin, `X.last`, a hidden sheet the next frame's formulas
read the previous frame from (`=Screen.last!B2+1`); `view` answers the
record with each formula's value, the values (the record less its formula
rows, what a host draws), the changes (the values' rows of the cells the
last frame changed, so that a host repaints those alone, or `whole` where
the machine cannot say), or the plane, a byte a cell. After each frame
`changed` says how many formula cells it changed the value of, to the
bit, `changed_on` how many of them on a sheet and `reached` how many it
computed, so that a host stepping a grid until a cascade settles stops
when nothing moves: a frame that changed nothing, with no write before
it, is followed by frames that change nothing. Each
formula is parsed once per shape (a filled formula, or cells whose R1C1
text is the same) and evaluated at each cell's offset. `census` prints
the plan as rows before a frame runs: each shape's cells, the levels of
the order it spans, the runs it is computed in and the path each of its
reads takes, so that a grid's cost is read ahead (`calc::Shapes::census`
prints a plan a door made of a workbook the same way). `machine::Handles`
keeps grids under handles from 1, never reused, sixteen at once. The C
surface over the four calls, `vla_load`, `vla_write`, `vla_step`,
`vla_view` and their companions, is compiled always and exported only
under the `c-abi` feature, so that no module built on this crate exports
a second door to its grid.
- **Refuses in words.** Every refusal is a `messages::Refusal`, a value
carrying the catalogue's id, number, source and text, never a panic.
## Using it from Rust
```text
cargo add vla-lang
```
```rust
use vla_lang::{expand, printer, reader};
fn main() {
let prelude = std::fs::read_to_string("prelude.vla").unwrap();
let program = "(when (> 1 0) (debug-print \"hello\"))\n";
// The program with its macros expanded, and how many applications it took.
match expand::expand_text(program, &prelude, true) {
Ok((expanded, fired)) => println!("{expanded}; {fired} applications"),
Err(r) => eprintln!("{} ({})", r, r.id),
}
// The same text read as forms and printed again, one datum a line.
for form in reader::read_forms(program).unwrap() {
println!("{}", printer::write_datum(&form));
}
}
```
## The contract
Bytes in, bytes out, nothing else. The crate has no dependencies, and
nothing in it reads a file, a clock, a socket or a random number at run
time. The two tables it needs, the head table and its half of the message
catalogue, are embedded at build time from `data/`, inside the crate,
exported from the add-in's own code and held to it by the repository's
checks. `vla_lang::VERSION` is the crate's version, which is the release's,
the add-in's and `frazaro-core`'s, one number for the language.
Every text the crate reads is answered or refused by name, never with a
panic, a stack overflow or a hang, so an engine can hand it a cartridge
from anyone. The limits are public constants, each refused by its own id:
- lists nested deeper than 64, in what the reader reads and in what the
expander writes (`reader::FORM_DEPTH_LIMIT`);
- a formula nested deeper than Excel's 64 levels, or longer than its
8,192 characters (`calc::formula::NESTING_LIMIT`, `LENGTH_LIMIT`);
- more than a million forms in what one macro application writes
(`expand::EXPANSION_SIZE_LIMIT`), since the crate copies a macro's
arguments into its template.
Real programs nest 10 deep at most and real formulas 6. Each stage is
tested at its limit on a 1 MiB stack, the tightest an engine is given.
```text
cargo build --release -p vla-lang --features c-abi --target wasm32-unknown-unknown
```
builds the crate as a WebAssembly module with the machine's C surface,
whose import section the repository's `tools/check_core_imports.ps1` holds
at zero entries, so that no engine built on it can reach a socket, a clock
or a file through the language, and whose exports
`tools/check_wasm_exports.ps1` holds to exactly its nine functions and its
memory.
## What comes next
Each is an item of the repository's roadmap (`docs/BETA_ROADMAP.md`), and
this page gains its section when the item closes:
- **Recalculation's next slices.** Dates, the text functions, the lookups,
dynamic arrays and spills, implicit intersection and `@` (KERNEL.8); the
vectorized evaluation of a shared formula, incremental recomputation and
content-addressed evaluation (KERNEL.20), which is what makes a frame of
a 320 by 200 grid fast: today the machine steps one in about a tenth of a
second natively. The first slice is here.
- **The corpus's expansion golden.** The corpus program expanded under the
prelude, written by the reference and reproduced here, beside the golden
of the expand-time primitives the crate is already held to.
- **An expander that shares what it walks.** A macro that walks a list
copies the rest of the list at each step today, so its cost grows with
the square of the list: a table generator's walk of 2,000 rows takes
about nine seconds natively (VLA.2).
## Where it comes from
The add-in in the repository's `src/` is the reference implementation of
the language, and the goldens it wrote under `scripts/` are what this crate
is held to, by the treaty in
[conformance/README.md](https://github.com/Spreadsheet-Company/Frazaro/blob/main/conformance/README.md):
the corpus program read, expanded and compiled byte for byte through the
core, and the reference goldens the add-in writes reproduced by this
crate's own tests. The crate follows the goldens and never leads them. It
was cut out of `frazaro-core` on 2026-10-08 (PORT.12) so that an engine
can stand on the language without the bridges.
The goldens and fixtures the tests read travel with the package, under
`testdata/`, so `cargo test` runs from the published crate. The repository
holds the copies to their sources byte for byte. The expander is held to
the add-in's expansion of 135 cases, every expand-time primitive and its
refusals among them (`tests/expand_golden.rs`). Beside the goldens, the
tests hold the language's laws over generated programs (`tests/laws.rs`)
and knock at every door with mutated text (`tests/strangers.rs`, and a
longer run with `--ignored`).
## Licence
Apache-2.0. The repository's `REUSE.toml` maps every file to its licence.