文法リファレンス
通常のJavaScriptに stage {} と $() を追加し、Code値を
run()で実行する実験的な計算モデルです。
stage の本体はその場では実行されず、次の段階で使うコードとして保持されます。
stage { ... }
-
ブロックを内部のESTree ASTとして保持するCode値を作ります。ネストした
stage は、さらに次の段階まで評価されません。
$(expression)
-
ひとつ外側のstageで式を評価し、結果を現在構築中のコードへ埋め込みます。
数値・文字列・真偽値・配列・単純なオブジェクトはリテラルASTになり、Code値は文字列化せずASTのまま接合されます。
stage {} の外側にある $ は普通のJavaScript識別子です。
run(code[, environment])
-
Code値を1段実行します。自由変数がある場合は、第2引数のobjectまたは現在の実行環境から与えます。
Code以外の値と不足した自由変数は実行前に拒否されます。
Code.simpleTco(code)
-
名前付き関数式の直接自己末尾呼び出しを、一時変数による引数更新と
stack-safeな
whileループへ変換します。対応subset外の関数は明示的に拒否します。
評価されるタイミング
| 位置 | 扱い |
| 通常のJavaScript | 現在のstageで実行します。 |
stage { ... } | 本体を実行せず、次段のCode値を作ります。 |
$(...) | 現在のstageで評価し、その結果を内側のコードへ埋め込みます。 |
run(code) | Code値を現在のstageで実行し、最終式の値を返します。 |
ネストした stage | 次のstage操作までそのまま残ります。 |
例
const n = 3;
stage {
const k = $(n * 2);
stage {
console.log($(k) + x);
}
}
最初の段階で $(n * 2) が 6になり、次の段階で $(k) が
6になります。x はL0まで残る自由変数です。
Code値の構造を調べる
Code値はopaqueで、生のESTree ASTや内部の束縛表を公開しません。式を変換するときは
Code.matchを使います。分解された左右の項もCode値なので、自由変数や捕捉した束縛を失わずに
$()で再び接合できます。
function swap(code) {
return Code.match(code, {
binary(operator, left, right) {
if (operator !== "+") throw new Error("+ only");
return stage { $(right) + $(left) };
},
fallback(kind) {
throw new Error("unsupported: " + kind);
}
});
}
literal、identifier、binary、unary、
conditional、call、memberを扱えます。
対応しない式はAST種別名と式全体のCode値をfallbackへ渡します。
安全性と現在の制約
- 式の位置へ埋め込むCode値は、単一の式を含む必要があります。
$() は1段外側へ戻る操作です。$$() のような多段escapeは未定義です。
- Code値は自由変数、捕捉した束縛ID、制御要求をメタデータとして持ちます。
- 公開メタデータは読み取り専用です。生ASTと捕捉値を保持する内部の束縛表にはアクセスできません。
runはCode contractを検査し、必要な自由変数が不足している場合はコードを実行しません。
- ASTの接合では名前ではなく束縛IDを保存するため、接合先の同名変数に捕獲されません。
- 消滅するローカルscopeへの参照と、stageをまたいだ不正な
return・break・continue を生成時に拒否します。
- top-level
await は、明示的なasync stageをまだ持たないため拒否します。生成コード内のasync関数では使用できます。