コンパイルされた read/load の制限
JVM クラスや WASM モジュールに埋め込まれるリーダーは、インタプリタのリーダーと同じ構文を解析します。整数、多倍長整数(JVM)、浮動小数点数、文字列、シンボル、nil/t、リスト(ドット対を含む)、'quote、#'function、比(1/3)、基数付き整数(#x10/#o17/#b101)、文字リテラル(#\a、#\Space など)、ベクタ(#(1 2))、ランク n 配列(#2A((1 2) (3 4)))、ビットベクタ(#*101。フロントエンドと同じく一般ベクタとして読まれます)、packed 浮動小数点配列(#f(...)/#d(...))、構造体リテラル(#S(NAME :SLOT value ...))、パス名リテラル(#P"dir/file"。その名前文字列を保持するパス名の値として読まれます)、; 行コメント、ネスト可能な #| ... |# ブロックコメントです。どの # ディスパッチにも該当しないトークンはソースと同じくシンボルとして読まれます(#foo はシンボル #FOO)。
# の 3 形式だけは恒久的な例外です。read 時に評価器やフィーチャー集合を必要とするためで、#. の read 時評価、#+/#- のフィーチャー条件、#n=/#n# のリーダーラベルが該当します。コンパイルされたリーダーはこれらを誤読する代わりに捕捉可能なエラーをシグナルします(インタプリタのランタイム read はフロントエンドと同様に #. と #+/#- を解決し、ラベルも読みます)。
#S(...) のデータはコンパイルされたプログラムが定義する構造体タイプに対して解決されます。データが省略したスロットは nil の initform ならその値を取り、単純な定数の initform(数、文字列、文字、シンボル、クォートされたリスト、ネストしたリテラル)は焼き込まれた印字形から再読取りされ、その集合の外の initform は誤った値を黙って代入する代わりにシグナルします。
すべてのバックエンドにおいて、read は標準入力の 1 行から 1 つの S 式を解析します。空行とコメントのみの行はスキップされ(データを含む行が現れるまで読み込みを続けます)、EOF では nil を返し、フォームは 1 行に収まる必要があります。
WASM のリーダーは手書きのパーサーで、数値とエラーメッセージがより狭い仕様です。
- 整数は任意の大きさで正確に解析されます。 整数・基数付きトークンはフロントエンドと同じボックス化整数のティアを通って昇格します(31 ビットの fixnum 範囲を超える値はボックス化整数に、符号付き 64 ビット範囲を超える値はリム表現の多倍長整数になります)。比の成分は 31 ビットのままです。
- 浮動小数点数に指数表記はありません。 10 進トークン(先頭の
-または+は省略可、数字、1 つの.、例:1.0、-2.5、.5、5.)はf64裏付けの浮動小数点数に解析されます。指数表記(1e3)はサポートされず、ドットが 2 つあるトークンや数字以外を含むトークン(例:1.2.3、foo.bar)はシンボルのままになります。 - エラーメッセージは固定文です。 リーダーエラーは(
handler-caseで捕捉可能に)シグナルされますが、メッセージは対象の名前を埋め込まない固定テキストです — JVM とインタプリタはフロントエンドの正確なメッセージを持ちます。 - シンボルのインターンは実行時に裏付けられます。 コンパイルされたプログラムに現れるシンボルは、コンパイルされた
evalが使うのと同じオフセットに解決されます。実行時にのみ現れるシンボル(例: ロードしたファイル内のラムダのパラメータ)は実行時テーブルにインターンされるため、同じシンボルが繰り返し現れても一貫性が保たれます。 loadには事前オープン済みディレクトリが必要です。 WASI のpath_openでファイルを開き、パスは事前オープン済みディレクトリに対して解決されます。相対パスは最初の 1 つ(fd 3)に対して、絶対パスは名前がその最長の接頭辞になる事前オープン済みディレクトリに対して解決されます。いずれの場合も--dirを付けて実行してください。絶対パスをカバーする--dirが必要です(--dir .は名前が.のディレクトリを事前オープンするため、どの絶対パスもカバーしません)。
backquote テンプレートとソース自身のフィーチャー宣言(データ型を参照)はフロントエンドの read 時構文で、コンパイル出力のランタイムリーダーは解決しません。read/read-from-string/計算された load で実行時に読み込まれるファイルでそれらを使うことはできません(*features* 変数自体は普通のスペシャル変数でどこでも使えます。ランタイムリーダーがしないのは、読んでいるテキスト中の push をそのテキスト自身の #+ に反映することです)(#| ... |# ブロックコメントはスキップされます。#+/#- は前述の通りシグナルします)。
require/provide はコンパイルされたバックエンドではコンパイル時ディレクティブであるため、コンパイル出力のランタイム load には理解されません。計算されたパスやネストした load で実行時に読み込まれるファイルにそれらを含めることはできません(コンパイル時に消費されるリテラルなトップレベルの require/provide のみが機能します)— ランタイムロードされたファイルのパッケージディレクティブと同じ制限です(パッケージを参照)。
read-from-string は同じ実行時リーダーを再利用するため、コンパイルされたバックエンドでは read と同じ構文を解析し、印字して読める種類の値なら (read-from-string (prin1-to-string x)) はすべてのバックエンドで往復します。parse-integer はリーダーから独立しており、3 つのバックエンドすべてで動作します。その :start/:end キーワードはインタプリタ専用で、コンパイルされたバックエンドではキーワード名はリテラルでなければなりません。どちらも 3 つのバックエンドすべてで第一級の値(#'parse-integer、#'read-from-string)としても利用できます。単一引数のラッパー経由で渡されるため、キーワード/オプション引数は関数値を通しては利用できません。