Skip to content

確かめて配る

ゲート、Gomamochi が断る書き方、バンドル、そしてまだできないこと。

ウィンドウなしの実行とゲート

PIXIE_SCRIPT が人の操作の代わりになります。 エンジンがツリーを組み、そこに書かれた手順のとおりに動かして、dump を出力します。

click:<label>      表示されている文字でボタンを押す
input:<text>       欄に打つ            submit    その欄で改行する
slide / select     つまみを動かす、選択肢を選ぶ
click@1:<label>    同じ文字のボタンの二つ目(n はツリー順に 0 から数える)
                   input@n:、submit@n、slide@n:、select@n: も同じ
key:<chord>        Shortcut に結ばれた打鍵
keydown:<key> / keyup:<key>    キーを押したままにする、離す
menu:<item>        メニュー項目を選ぶ  file:<path>   ダイアログの答え
drop:<path>        ウィンドウにファイルを落とす
hover:<i>          チャートの i 番目の点にポインタを載せる(n 番目のチャートなら hover@n:<i>)
hover:             ポインタを外す
advance:<ms>       時計を進める        theme:dark|light
dump               ツリーを出力            a11y   読み上げが読むものを出力

gomamochi gate は、一つのスクリプトでアプリを二回走らせます。 片方はインタプリタが動かすファイルで、もう片方は Go コンパイラがそのファイルから作ったバイナリです。 最後に、二つの記録を一バイトずつ比べます。

$ ./bin/gomamochi gate demo/counter.go --script "click:+1,dump,input:Momo"
GATE OK — 3 dump lines identical in both runs

ゲートが、Gomamochi の約束そのものです。 書いているあいだ見ているのはインタプリタで、配るのはコンパイラが作ったバイナリです。 ゲートは、その二つが一致することを言っています。 このページのほかの部分では、そのゲートを通るものをどう書くかを説明しています。 --fresh <path> は、実行のたびに指定したパスを消します。 ファイルやデータベースを使うアプリでも、二つの実行を同じ何もない状態から始められます。

Gomamochi が断る書き方

gomamochi check はアプリを読みます。 受け取れない書き方があれば、その行番号と、位置を指す ^ と、書き換え方を出力します。 実行、ビルド、ゲートの前には毎回走ります。 言うことがなければ何も出力しません。

$ ./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
        ^

チェックは二段階です。 一つ目はファイルだけを読むので、どこでも走ります。 二つ目は Go 自身の型チェッカです。 ツールチェーンのエクスポートデータを渡し、Go の型エラーを Go の言葉のまま出します。 型がわかって初めて決まることは、ここで加わります。 これが走るのは go がパスにあるときで、buildgate はどのみち go を使います。

解釈実行が、コンパイルした実行と同じようには扱えないものは次のとおりです。

  • minmax(Go 1.21)、数の上を回る range(1.22)、関数の上を回る range(1.23)。 インタプリタにはありません。 比較を自分で書くか、for i := 0; i < n; i++ と書きます。
  • 呼び出しの結果をそのまま Run に渡すこと。 いったん変数に入れて、その名前を渡します。
  • %Treflect。 インタプリタは、アプリ自身の型を別の名前で呼びます。
  • unsafe、cgo、//go:embed、標準ライブラリの外のモジュール。
  • クロージャに捕まえられた変数を書き換える、3 節の for

ビューにできないことは次のとおりです。 ビューは、何かが変わるたびに同じ状態から組み直されるからです。

  • アプリのフィールドを書き換えること、goroutine を起動すること。
  • 時計、環境、ファイル、ストリーム、ネットワーク、乱数、キーボードを読むこと。 タスクやタイマーを始めること。 音を鳴らすこと。
  • マップの上を range で回ること。

どれも、出力される文面ごと test/refuse/ に置いてあります。 だから、断りの文面が黙って変わることはありません。

リリース

$ ./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 は、cgo を切って Go コンパイラでファイルをコンパイルします。 できたバイナリは、システム自身のライブラリ以外は何もリンクしません。 エンジンは、そのバイナリの隣に置く共有ライブラリです。 エンジンを呼び出す部分が、まずその場所を探してロードします。 --release は symbol table を落とします。 --app はその二つを macOS のアプリケーションバンドルに包み、ad-hoc 署名をつけます。 アプリと同じ場所に <stem>.png<stem>.icns を置いておくと、それがアイコンになります。 このバンドルだけでプログラムが完結します。 Go もツールチェーンも入っていないマシンで、そのまま開きます。 Linux では、--app は代わりに AppDir を書きます。 --appimage はそれを一つのファイルにまとめ、--carry-libs はデスクトップのライブラリも一緒に運びます。 そのライブラリが入っていないかもしれないマシンのためです。

まだできないこと

  • インタプリタのライブラリは Go 1.22 のもので、言語はそこに届いていません。 minmax(1.21)、数の上を回る range(1.22)、関数の上を回る range(1.23)、1.23 以降に標準ライブラリへ足されたものはありません。 最初の三つは check が名前を挙げて断ります。 残りは、インタプリタ自身のエラーがその名前を出します。
  • 標準ライブラリの外のモジュールは、解釈実行がまだ読めません。 アプリは 1 ファイルです。
  • ドットインポートを使っているあいだは、パッケージが公開している名前(AppElementText など)をアプリの側で宣言できません。 パッケージに名前を付けてインポートすれば宣言できます。
  • recover した panic の文面は、二つの実行で違います。 %T が出力する名前も違います。 どちらも人に見せるものではありません。
  • 解釈実行は、回数の多いループではコンパイルした実行より二桁遅くなります。 インタプリタを挟んでいる分のコストです。 それでも、どちらのゲームも 1 フレームは 1 ミリ秒かかりません。
  • macOS と Linux です。 バンドルと AppDir はエンジンを含むので、小さなアプリでも 22 MB ほどになります。 Linux 向けのパッケージングは Yokan と同じ書き方ですが、まだ Linux のマシンでは試していません。