std:hikari/repl — 対話評価セッション
標準ライブラリモジュール (../index.md)。本書中の裸の §N は本書の節を指す。言語の意味論は ../../language-spec.md、REPL(hikari 引数なし起動)の仕様は ../../repl.md を参照。
import repl := "std:hikari/repl" は 対話評価セッション Session のコンストラクター session と、継続入力判定 needs_more を持つ namespace object を repl に束縛する。REPL・ノートブック的ツール・埋め込みスクリプト評価のように「Hikari ソース文字列を 1 入力ずつ、環境を持ち越しながら評価する」プログラムを Hikari 自身で書くための面である。処理系同梱の REPL のポリシー層もこのモジュールの上で self-host される (../../repl.md の実装構成)。
hikari/ は処理系自身の能力(Hikari ソースを扱う機能群)のファミリであり、本モジュールはその対話評価部を担う。汎用のメタプログラミング面(AST の公開・quote 相当)ではない。公開するのは「評価と、その結果についての事実」だけで、構文木そのものには触れられない。
std:hikari/repl を import すること自体が「このコードは動的評価能力を使う」ことの可視化になる (../index.md の能力宣言)。動的に評価されるソースの内側は hikari check の静的検査が届かない領域であることに注意。
import repl := "std:hikari/repl" s := repl.session! s.eval("x := 10")! r := s.eval("x * 2")! r.text #> "20" r.is_error #> false repl.needs_more "[1, 2," #> true (角括弧が未閉)
以下では import repl := "std:hikari/repl" で束縛したものとして記す。
| 名前 | 形 | 効果 | 意味 |
|---|---|---|---|
session |
session() |
なし | 新しい対話評価セッション Session を返す (同期) |
needs_more |
needs_more(src) |
なし | src の開き括弧 ( [ { が末尾で閉じていなければ true (同期・純粋) |
Session のメソッド:
| 名前 | 形 | 効果 | 意味 |
|---|---|---|---|
eval |
s.eval(src) |
全効果 | src を 1 入力として評価し、Future (EvalResult §3) を返す |
check |
s.check(src) |
なし | src を評価せず検査し、診断 (List(String)) を返す (同期) |
1. Session — 永続環境と入力の扱い
session() は prelude 束縛済みの独立した新規環境を持つ Session を返す。環境はセッション全体で共有され、前の eval で束縛した名前を次の eval から参照できる (REPL の「評価ループ」と同じ)。
- 入力の擬似ファイル名は
<repl>で、エラーメッセージの Position 表記にもこの名前が現れる (../../repl.md と同一)。 - 入力は平坦な宣言と文の列として解釈され、束縛はセッション env に入る (../../repl.md のパーサーモードと同一)。
- セッション環境の
print/inputの入出力先は、呼び出し元プログラムの入出力を継承する。端末で動く Hikari プログラムが作ったセッションは端末へ、ブラウザー (wasm) では捕捉バッファーへ書く。 - セッション内の
importは呼び出し元と同じモジュール解決に従う (std:*は常に可。ファイル import は実行環境に従い、ブラウザーでは不可)。
Session は std:array の Array と同じ不透明値型であり、環境の中身を値として取り出す手段は持たない。観測はすべて eval / check の返す事実を通して行う。
Session は非 Copyable である (../../language-spec.md の複製の節)。評価状態を持つ可変ハンドルで、作り直しても同じものにならないためである。copy は panic し、spawn / std:parallel の捕獲検査も弾く。
Session はAtMostOnce の多重度型である (../../language-spec.md の多重度型の節)。明示的な終了操作を持たないため Consuming メソッドは無く、多重度は別名を作らせないためだけに働く。
2. eval — fork 同型の評価と Future
s.eval(src) は fork と同型の別フローで src を 1 root 評価として実行し、Future を返す。! で待つと EvalResult (§3) が得られる。呼び出し側は待機中にスケジューラーへ譲る (../../language-spec.md §10.1) ため、Hikari プログラムの評価中から呼んでも dead-lock しない — 処理系同梱 REPL 自身がこの経路で動く。
eval は全効果 (Io / Fs / Net / Proc / Time / Rand / Mut / Susp) を持つ (../../language-spec.md §17.9)。評価する綴りは実行時にしか分からず、そのソースが何を呼ぶかを静的に絞れないためである。効果は呼び出し地点に帰属するので、s.eval(src) を含む関数は結果型にその効果を宣言しない限り undeclared-effect になる。効果の上限を静的に狭められない唯一の口であり、eval を呼ぶコードは効果の観点で純粋になれない。session / check / needs_more は評価しないので効果を持たない。
- ユーザーコードのエラーは Future のエラーではない。src の評価が panic (エラー値) で終わっても
evalのFutureは正常に resolve し、事実はEvalResultのis_error/textに載る。呼び出し側 (REPL ループ) はエラーを表示してループを継続できる。s.eval(src)!自体がエラーになるのはevalの使い方のエラー (引数がStringでない等) だけである。 - 同一
Sessionへの複数のevalは到着順に逐次実行される。先行の評価が完了するまで後続は開始されない (環境の一貫性)。 - 1 回の
evalは 1 つの root 評価であり、src 内で fork したフローの未 await panic 診断などはファイル実行 1 回と同じ扱いを受ける。
3. EvalResult — 事実のレコード
eval の resolve 値は次のレコードである。表示すべきかの判断 (ポリシー) は含まない — std:hikari/repl は事実を返し、REPL の「unit と束縛文は表示しない」等の規則は呼び出し側 (self-host コアや利用者のコード) が実装する。
| フィールド | 型 | 意味 |
|---|---|---|
text |
String |
結果の表示用文字列。値は inspect 表記、エラーはトレース付き診断。パース失敗時は "" |
is_error |
Bool |
入力がエラーに終わったか (評価結果がエラー値、またはパース失敗) |
is_unit |
Bool |
評価結果が unit () か。文が 1 つも無い入力 (空・空白・コメントのみ) も true |
is_binding |
Bool |
入力の末尾文が束縛宣言・代入・分解代入か (REPL が評価値を表示しない対象。../../repl.md の評価ループ 5) |
parse_errors |
List(String) |
パースエラー (<repl>:<line>:<col>: <msg> 形式)。非空のとき評価は行われず、text = "" / is_error = true / is_unit = false / is_binding = false |
REPL の既定表示規則は、このレコードの上で次のように書ける:
r := s.eval(line)! if (r.parse_errors.length! > 0) { r.parse_errors.each { e | eprintln e } } { if r.is_error { eprintln r.text } { # unit と束縛文は表示しない when (r.is_unit || r.is_binding).not! { println r.text } } }
4. check — 実行しない検査
s.check(src) は src を評価せず、パースエラーと静的型検査 (../../static-analysis.md §2) の診断を List(String) (<repl>:<line>:<col>: <msg> 形式、../../check.md と同じ表記) で返す。診断が無ければ空 List。純粋・同期 (Future ではない)。
REPL の検査ポリシー (../../repl.md「静的型検査」) は「check が非空ならその行を評価しない」というポリシーとして self-host コアが実装する。検査は 1 入力単位であり、前の eval で束縛した名前の型情報は参照されない (健全側の制限)。
参照するのはセッション束縛の名前と可変性だけである。したがって s.eval("x := 1") の後の s.check("x = 2") は immutable-assign を、s.check("x := 2") は already-declared を返す (../../repl.md「セッション束縛と再宣言」)。check は評価しないので、束縛表は check 自身によっては伸びない。
5. needs_more — 継続入力判定
needs_more(src) は src の ( [ { の net depth を数え、末尾で開き括弧が閉じていなければ true を返す。文字列リテラル・${...} 補間の内側では字句規則上、改行と括弧が禁止されるため、この素朴な計数で十分である (../../repl.md のプロンプト)。閉じすぎ (depth が負) は継続にせず false — パースエラーとして報告させる。
将来: パーサーが「入力末尾 (EOF) に起因する失敗」を通常の構文エラーと区別して返すようになった場合、本関数はその判定へ置き換える余地がある。判定が広がる方向 (未閉の match アームなども継続になる) の互換性変更として扱う。
6. エラー
session / eval / check / needs_more への引数の型・個数のエラーは、他の std モジュールと同じく呼び出し位置のエラー値になる (std:math §6 と同型)。eval の評価対象のエラーは §2 のとおり EvalResult の事実であり、この節のエラーとは層が異なる。