Verify and ship
The gate is the promise. Everything else in this tour is a way of writing something the gate can keep.
A run with no person in it
PIXIE_SCRIPT replaces the person. The engine builds the tree, drives
it with the steps, and prints what the steps asked for.
| Step | What it does |
|---|---|
click:<label> |
press a button by the label it shows |
input:<text> |
type into a field |
submit |
press enter in it |
slide:<n> |
move a slider |
select:<option> |
pick an option in a chooser |
click@1:<label> |
the second button with that label; n counts from 0 in tree order, and input@n:, submit@n, slide@n:, select@n: count the same way |
key:<chord> |
a keystroke bound to a shortcut |
keydown:<key> / keyup:<key> |
hold a key down, let it up |
menu:<item> |
pick a menu item |
file:<path> |
answer a file dialog |
drop:<path> |
a file dragged onto the window |
hover:<i> / hover: |
the pointer on a chart's i-th point (hover@n:<i> for the n-th chart), and the pointer leaving |
advance:<ms> |
move the clock |
theme:dark / theme:light |
switch the palette |
dump |
print the element tree |
a11y |
print what a screen reader is handed |
mem |
print how many objects the engine is holding |
Where several elements answer the same description, @n picks one:
click@1:done presses the second button labelled done, and
input@0: types into the first field. Steps are separated by commas,
and a comma inside a step's own text is written \,.
dump is the interesting one. It prints the element tree as text —
the same bytes a run prints at its start and end — so a script that
dumps in the middle is comparing the middle of a run, not only its
ending.
The gate
wakakusa gate runs the app twice with one script — CRuby through the
door, and the compiled binary through the same C ABI linked in — and
compares the two transcripts byte for byte.
$ wakakusa gate demo/counter.rb --script "click:+1,dump,input:Momo"
GATE OK — 3 dump lines identical in both runs
script: click:+1,dump,input:Momo
emitted: demo/.gate/counter.c
binary: demo/.gate/counter (16.2 MB)
When they differ, it says where, line by line:
An app that keeps a file or a database has to start both runs from the
same nothing, which is what --fresh is for. It deletes the path
before each run, and it is repeatable:
$ wakakusa gate demo/ledger.rb --fresh demo/.gate/ledger.db \
--script "click:reset,input@0:o'brien,input@1:250,click:food,dump"
wakakusa check runs before every gate and every build, so a refusal
arrives before a compiler is ever started.
What Wakakusa refuses is that list.
tools/gate_all.sh is the sweep: every demo, then every complete
example in both language tours and on this site. It is what a change to
the vocabulary, the door or the engine has to pass.
What the gate proves, and what it does not
It proves the two runs of your app draw the same screens under the steps you gave it. It does not prove the steps covered the app — an interaction nobody scripted is an interaction nobody compared.
What it does not have to prove is that the interpreted run is right about Ruby: that run is CRuby. So a green gate means the compiled run — a second implementation of Ruby, with a standard library of its own — agrees with the real interpreter on the paths the script walked, and where they part, CRuby is the side that is right by definition. The few places where the two are not the same program are named on The two runs.
Shipping
$ wakakusa build demo/todo.rb --release --app
built: demo/.gate/todo (12.3 MB)
bundle: demo/dist/todo.app (12.5 MB)
not gate-checked — `gate` with a script proves the two runs agree
--release drops the symbol table, which is a fifth of the file.
--app wraps the binary in a macOS application bundle under dist/,
ad-hoc signed and double-clickable, with <stem>.icns or <stem>.png
beside the app as its icon.
The binary carries the engine and the compiled Ruby and links nothing but the system's own libraries, so the bundle is the whole program: it opens on a machine with neither Ruby nor the compiler installed.
wakakusa translate writes the C the compiled run is built from, at
any point along the way — it is the same file the gate keeps beside
the binary.
The numbers
Measured on macOS/arm64, with the shared build directory warm.
| What | Value |
|---|---|
wakakusa check |
0.1 s |
| a headless run that prints the screen | 0.7 s |
| the compiler's C output | under 10 ms, about 120 KB |
cc link of the compiled run |
0.29 s |
| the compiled binary | 16.2 MB |
the shipped binary (--release) |
12.3 MB |
the application bundle (--app) |
12.5 MB |
| launch to a window on screen | under 0.3 s |
| one gate round, engine already built | 2.1 s |
What does not work yet
- A seeded
Randomis not the same generator in the two runs, so a program that wants one sequence in both writes the generator itself. The two games do, in six lines of arithmetic. - Three shapes an app has to be written in, because the compiler
cannot yet take the others: state on the app object rather than in
globals, a handler as a literal block (or a proc of no arguments
through a keyword), and a list grown by copying rather than by
list + [item]. Each is refused by name, with the rewrite in the message — What Wakakusa refuses. checkdoes not see everything the compiler gets wrong. It names those three and five more; the gate is still what catches the rest.- A thread the app starts for itself runs in both, but how far it
gets by a given moment is not something the two runs agree about —
which is what
taskis for. - macOS and Linux.
--appis a macOS shape and names itself off it; the binarybuildwrites is native on both. It carries the engine it draws with, so even a small app weighs about 12 MB.