Verify and ship
The gate, what Gomamochi refuses, the bundle, and what does not work yet.
Headless runs and the gate
PIXIE_SCRIPT replaces the person. The engine builds the tree, drives
it with the steps, and prints the dumps:
click:<label> press a button by the label it shows
input:<text> type into a field submit press enter in it
slide / select move a slider, pick an option
click@1:<label> the second button with that label (n counts from 0, in tree order)
and the same for input@n:, submit@n, slide@n:, select@n:
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 dialog
drop:<path> a file dragged onto the window
hover:<i> the pointer on a chart's i-th point (hover@n:<i> for the n-th chart)
hover: the pointer leaves it
advance:<ms> move the clock theme:dark|light
dump print the tree a11y print what a reader reads
gomamochi gate runs the app twice with one script — the file under
the interpreter, and the binary the Go compiler built from it — and
compares the two transcripts byte for byte.
$ ./bin/gomamochi gate demo/counter.go --script "click:+1,dump,input:Momo"
GATE OK — 3 dump lines identical in both runs
The gate is the promise. Here it says that the interpreter, which is
what you were looking at while writing, agrees with the compiler,
which is what ships: everything else in this page is a way of writing
something the gate can keep. --fresh <path> deletes a path before
each run, so an app that keeps a file or a database starts both runs
from the same nothing.
What Gomamochi refuses
gomamochi check reads the app and names what it cannot take, with the
line, a caret, and the rewrite. It runs before every run, build and
gate, and prints nothing when there is nothing to say.
$ ./bin/gomamochi check demo/broken.go
demo/broken.go:8:2: Gomamochi cannot take this — a view only reads. Move the write into a handler — the closure on a button, or a method the app calls from one
a.n += 1
^
Two layers. The first reads the file alone, and runs everywhere. The
second is Go's own type checker, fed the toolchain's export data, which
speaks Go's type errors in Go's words and adds what only a type can
decide; it runs when go is on the path, which build and gate
need anyway.
What the interpreted run cannot run as the compiled one does:
minandmax(Go 1.21), andrangeover a number (1.22) or a function (1.23), which the interpreter does not have. Write the comparison out, orfor i := 0; i < n; i++.- The app handed to
Runstraight from a call. Give it a name first. %T, andreflect: the interpreter names the app's own types differently.unsafe, cgo,//go:embed, and a module outside the standard library.- A three-clause loop that writes its own variable while a closure captures it.
What a view may not do, because it is built again from the same state whenever anything changes:
- Write a field of the app, or start a goroutine.
- Read the clock, the environment, a file, a stream, the network, a random number, or the keyboard; start work or a timer; play a sound.
rangeover a map.
Each of those has a fixture under test/refuse/ holding the message
it prints, so a refusal cannot quietly change its wording.
Shipping
$ ./bin/gomamochi build demo/todo.go --release --app
built: demo/.gate/todo/todo (1.9 MB)
bundle: demo/dist/todo.app (22.3 MB)
build compiles the file with the Go compiler, with cgo off, into a
binary that links nothing but the system's own library; the engine is
a shared library that rides beside it, and the door looks there first.
--release drops the symbol table. --app wraps the two in a macOS
application bundle, ad-hoc signed, with <stem>.png or <stem>.icns
beside the app as its icon; the bundle is the whole program, and opens
on a machine with neither Go nor the toolchain installed. On Linux
--app writes an AppDir instead, --appimage packs it into one file,
and --carry-libs makes it carry the desktop's libraries too, for a
machine that may not have them.
What does not work yet
- The interpreter's library is Go 1.22's, and its language is short of
that:
minandmax(1.21),rangeover a number (1.22) or a function (1.23), and what the library gained in 1.23 and later are not there;checknames the first three, and the interpreter's own error names the rest. - A module outside the standard library cannot be read by the interpreted run yet, and an app is one file.
- Under the dot import, an app cannot declare a name the package
exports —
App,Element,Textand the rest. Import the package under a name and it can. - A recovered panic's message is worded differently in the two runs,
and
%Tprints a different name; neither is a thing to show a person. - The interpreted run is slower than the compiled one by two orders of magnitude on tight loops, which is what an interpreter costs; a frame of either game is still under a millisecond of it.
- macOS and Linux. The bundle and the AppDir carry the engine, so even a small app weighs about 22 MB; the Linux packaging is written the way Yokan's is and has not yet been run on a Linux machine.