コンパイルされた 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 はフロントエンドと同様に #. と #+/#- を解決し、ラベルも読みます)。#+/#- については read と、文字列以外の引数も渡す read-from-string 呼び出しが例外で、すべてのバックエンドで実行時の *features* に対してガードを自分で解決します。シグナルするのは引数 1 つの read-from-string だけです。
#S(...) のデータはコンパイルされたプログラムが定義する構造体タイプに対して解決されます。データが省略したスロットは nil の initform ならその値を取り、単純な定数の initform(数、文字列、文字、シンボル、クォートされたリスト、ネストしたリテラル)は焼き込まれた印字形から再読取りされ、その集合の外の initform は誤った値を黙って代入する代わりにシグナルします。
すべてのバックエンドにおいて、read はちょうど 1 つの S 式分の文字だけを消費し、ストリームをその直後の位置に残します。データムの前の空白とコメントはスキップされ、データムは複数行にまたがれ、同じ行にある 2 つ目のデータムは失われず、EOF では eof-error-p が明示的な nil でない限り end-of-file をシグナルします(既定は t)。nil の場合のみ eof-value を返します。インタプリタのランタイムリーダーは不正トークンにも厳格で、ドットのみのトークン(..)、#:a:b、#<、#n* の過不足は reader-error をシグナルします。一方コンパイルされたリーダーはこれらの形状をシンボルとして読みます。read は read-char / unread-char / read-from-string の上に rontolisp で書かれているため、受け付ける構文は各バックエンドの read-from-string とまったく同じです(上記の例外と後述のバッククォートの注意も含みます)。
WASM のリーダーは手書きのパーサーで、数値とエラーメッセージがより狭い仕様です。
- 整数と比は任意の大きさで正確に解析されます。 整数・基数付きトークンはフロントエンドと同じボックス化整数のティアを通って昇格し(31 ビットの fixnum 範囲を超える値はボックス化整数に、符号付き 64 ビット範囲を超える値はリム表現の多倍長整数になります)、比のトークンの両辺も同じように昇格します(
3000000000/7はその比として読まれます)。数字の後に末尾の.だけが続くトークン(5.、-5.、+5.、123456789012345678901234567890.)は、Common Lisp と同じく 10 進整数として読まれます。 - 浮動小数点数はフロントエンドの指数マーカー付きで解析されます。 10 進トークン(先頭の
-または+は省略可、数字、高々 1 つの.(指数が続かない限り末尾にはできません)、少なくとも 1 桁の数字、例:1.0、-2.5、.5)は、Common Lisp の指数接辞(マーカーe/s/f/d/lの大文字・小文字、任意の符号、指数数字少なくとも 1 桁 —1e3、2E3、1d-8、1.5f3、.5e2、1.e5)の有無にかかわらずf64裏付けの浮動小数点数に解析されます。ドットが 2 つあるトークン、マーカーの後に指数数字が続かないトークン、その他の数字以外を含むトークン(例:1.2.3、foo.bar、1e、1e+)はシンボルのままです(そのシンボルでは符号も名前の一部です。+5xはシンボル+5Xです)。値はトークンが表す 10 進数に最も近い double で、フロントエンドのDouble.parseDoubleが読む値と一致します(非正規化数と丸めの同点を含む)。double の範囲を超える大きさはInfinityまたは0.0になります。 - エラーメッセージは固定文です。 リーダーエラーは(
handler-caseで捕捉可能に)シグナルされますが、メッセージは対象の名前を埋め込まない固定テキストです — JVM とインタプリタはフロントエンドの正確なメッセージを持ちます。 - シンボルのインターンは実行時に裏付けられます。 コンパイルされたプログラムに現れるシンボルは、コンパイルされた
evalが使うのと同じオフセットに解決されます。実行時にのみ現れるシンボル(例: ロードしたファイル内のラムダのパラメータ)は実行時テーブルにインターンされるため、同じシンボルが繰り返し現れても一貫性が保たれます。 loadには事前オープン済みディレクトリが必要です。 WASI のpath_openでファイルを開き、パスは事前オープン済みディレクトリに対して解決されます。相対パスは最初の 1 つ(fd 3)に対して、絶対パスは名前がその最長の接頭辞になる事前オープン済みディレクトリに対して解決されます。いずれの場合も--dirを付けて実行してください。絶対パスをカバーする--dirが必要です(--dir .は名前が.のディレクトリを事前オープンするため、どの絶対パスもカバーしません)。..を含むパスは、カバーする事前オープン済みディレクトリのうち名前が最短のものに対して解決されます。事前オープン済みディレクトリはそこから出る..を拒否するためです。相対パスが最初のディレクトリより上に出られるのは、ホストがそのディレクトリを絶対パスの名前で事前オープンしたとき(--dir "$PWD::$PWD" --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)) はすべてのバックエンドで往復します。第 2 の値である停止インデックスはすべてのバックエンドで返されます。*read-suppress* を尊重するのはインタプリタのリーダーだけです。コンパイル出力のランタイムリーダーに抑制モードはありません。parse-integer はリーダーから独立しています。どちらもすべてのバックエンドでキーワード引数とオプション引数を含めて動作し、第一級の値(#'parse-integer、#'read-from-string)としても使えます。呼び出しではキーワード名はリテラルでなければなりません。