(rontolisp) docs

サポートされない Common Lisp の機能

rontolisp は意図的に小さくした Common Lisp のサブセットで、3 つのバックエンド (インタプリタ、JVM、WASM)で同一に動作します。ランタイムのメタオブジェクト プロトコルなしで言語をそのままのバイトコードにコンパイルできるよう保つため、 完全な Common Lisp の機能の多くが意図的に省かれています。

このページでは 利用できない、または部分的にしか対応していないもの だけを 挙げます。利用できるものについては、 言語リファレンスを参照してください。

機能状況
リスタート利用可。デバッガ統合(break、*debugger-hook*)とコンディションとの関連付けはありません
&environmentdefmacro のラムダリストで受け付けますが、常に nil に束縛されます(マクロ展開環境オブジェクトは存在しません)。&whole は defmacro・destructuring-bind の双方で動作します
loop(拡張版)一部対応(後述)
CLOS一部対応(静的サブセット + 定義時 MOP サブセット)
defstruct の :include単一継承のみ。スロットのデフォルトを上書きする (:include parent (slot default) ...) は利用可能
declare / declaim / proclaim / the結果は変えない。WASM では配列の type 宣言が要素アクセサのエミットを誘導(モジュールが小さく速くなる)、それ以外では解析されるだけの no-op
typep / subtypep / coerce / concatenateリテラル(クオートされた)型指定子のみ。coerce の結果型は 'list / 'vector / 'string(または浮動小数点型)、concatenate はこの 3 つのシーケンス系統を構築
make-package / rename-package / delete-package / unintern / shadow(ランタイム)make-package、rename-package、delete-package、packagep、package-nicknames、find-all-symbols、do-all-symbols、apropos/apropos-list は利用可能(後述の二層)。shadow / shadowing-import / unintern はパッケージのメンバーテーブルを変更する。コンパイル済みバックエンドでメンバーテーブルを持つのはプログラム自身が作ったパッケージだけ
eval-whenprogn として扱う(フェーズの区別なし)
#:name普通のシンボルとして読まれ、gensym 的な新規性はない
*modules*利用不可(require/provide は利用可能)
複素数--no-gc 以外のすべてのバックエンドで利用可(数値タワー)
--no-gc での catch / throw / unwind-protect / 条件コンパイルエラー(他のバックエンドでは利用可能)

多値

values とその消費側は、ユーザ定義関数の 多値も含めて利用できます。Common Lisp からの残る相違点は次のとおりです:

  • 末尾以外の位置で values を呼んでから通常の値を返すプロデューサは 古い余剰値を残すことがあるため、values は結果位置で使ってください (消費側は受け取った値をクリアするので、余剰値が残るのは何も消費しない values 呼び出しだけです)。
  • funcall #'values(ファーストクラス値)はコンパイル済みプログラムでは 主値のみを返します。
  • 組み込みの #'name を渡した multiple-value-call はラッパーの固定 アリティのままです — それ以外の引数個数にはユーザ定義関数か lambda を 渡してください。
  • CL で副次値を持つ他の組み込み関数(decode-universal-time、比に対する truncate 系の剰余など)は単一値のままです — find-symbol と intern はアクセス可能性ステータスを、 macroexpand-1 / macroexpand は expanded-p を、 subtypep は valid-p を、 read-from-string は 停止インデックスを、read-line は missing-newline-p を返します。

非局所脱出

catch / throw、 block / return-from と tagbody / go は利用できますが、コンパイル済み バックエンドには 1 つの制限があります(インタプリタには影響しません):

  • go はレキシカルに囲む tagbody のタグのみを対象にできます。インタプリタは さらに関数呼び出しの境界を越える動的 go、つまり呼び出し元が確立したタグへの ジャンプもサポートします。ネストした lambda の内側から囲む関数のタグへ ジャンプする形 — handler-bind の ハンドラが go で保護対象のループを再開する形であり、quri の :lenient な パーセントデコードがまさにこれです — は lambda をまたぐ return-from と 同じく下位変換されます: そのタグで tagbody に再入して実行を続ける 非局所脱出になります。

lambda や flet/labels の関数をまたぐ return-from は Common Lisp と同じ ように振る舞います。lambda をまたぐ return-from と go、catch/throw、unwind-protect、 条件の捕捉はいずれも例外処理モードでコンパイルされます。--no-gc では catch/throw、unwind-protect と条件系のフォームはコンパイルエラーに なります。

リスタート

コンディションシステムはリスタート層まで揃っています: handler-bind のハンドラは巻き戻しの 前にシグナル点で実行され、restart-case / restart-bind / with-simple-restart が リスタートを確立し、find-restart / invoke-restart / compute-restarts / muffle-warning / abort / continue がそれらを駆動します。 cerror は継続可能です。欠けているのは 対話的デバッガです: break と *debugger-hook* は存在せず、リスタートの :report は保存されるだけで描画されず、:interactive 関数も実行されません。 またリスタートはコンディションと関連付けられません (find-restart/compute-restarts の省略可能なコンディション引数は無視されます)。 check-type / assert / ccase は依然として store-value リスタートを 提供せずにエラーを通知します。--no-gc ではリスタートフォームは主フォームへ 退化します(そのバックエンドにはコンディションオブジェクトがありません)。 wasm-GC バックエンドで捕捉できるのはシグナルされたコンディションのみです — ランタイムトラップは依然として中断させます。

loop マクロ

拡張版 loop の限定的なサブセットが利用でき ます。対応している節はそのページに一覧があり、分配束縛、並行 and、アナフォリック な it、loop-finish、thereis/always/never も含まれます。対象外は named(およびそれが名付ける return-from)です。また、分配束縛のパターンは ラムダリストキーワードを解釈せず(&optional などはエラーにならず通常の変数と して束縛されます)、being はハッシュテーブルを駆動しますが、パッケージ形式 (being the external-symbols of ...)はランタイムの intern テーブルが存在 しないため、解析はされるものの空のシーケンスを反復します。

構造体とオブジェクト

defstruct は :include による継承を 単一継承の形でのみサポートします。スロットの上書きは利用可能です: (:include parent (slot new-default) ...) は継承したスロットのデフォルトを 子のレイアウトでのみ差し替え、インデックスは継承したまま保つため、親のアクセサから そのまま読めます。インスタンスは標準の #S(...) 構文で印字され、#S(...) リテラルはインスタンスとして読み戻されます — ソース中でも、すべてのバックエンド のランタイム read / read-from-string を通しても(コンパイルされたリーダーは フロントエンドと同等で、#.、#n=/#n#、引数 1 つの read-from-string の #+/#- だけはシグナルします)。 (:print-object fn) / (:print-function fn) オプションを持つ構造体は 代わりにその関数を通して印字されます。どちらのオプションもサポートしています。

CLOS は静的なサブセットです (defclass、第 1 引数で ディスパッチする defgeneric / defmethod、リテラルのクォートされた 名前を取る make-instance と slot-value)。:initform なしで書かれた スロットは CL と同様に未束縛で始まります: slot-boundp がそれを報告し、 slot-makunbound が元に戻し、 読み取りは unbound-slot をシグナルします。 change-class はインスタンスのクラスを その場で変更し(対象は実行時のシンボルやクラスメタオブジェクトでも可)、 initialize-instance / reinitialize-instance / shared-initialize はユーザ メソッドなしでも呼び出せ、関数値にもなります — CL と同様、システムデフォルトが 指定された initarg を格納し、インスタンスでない引数には no-applicable-method を シグナルします。 定義時 MOP サブセットが入っています: find-class と class-of は実物の standard-class メタオブジェクトを返し、 allocate-instance が動作し、 クラスオプション (:metaclass M) は定義時にクラス定義プロトコルを実行します (defclass 参照)— これが postmodern の DAO 層を無改変でロードする仕組みです。多重継承も動作します (クラス優先順位リスト、スーパークラス間のスロットマージ)。対象外: 実行時のクラス構築 (計算されたデータからの ensure-class、トップレベル以外の defclass、 add-method、compute-applicable-methods、クラス再定義、 update-instance-for-different-class)— コンパイルされたプログラムのクラスとメソッドの集合はコンパイル時に固定されます。

ユーザー定義パッケージ

defpackage は :use、:export、 :nicknames、:import-from、:shadowing-import-from、:shadow、:intern を サポートする read/コンパイル時ディレクティブです(:documentation/:size は 受理されるが無視されます)。トップレベルでない defpackage は、それを囲む フォームが実行されたときにパッケージを登録します(インタープリタのみ)。 use-package、 unuse-package、 export、unexport、 import は in-package と同じ読み込み/ コンパイル時ディレクティブとして存在します: リテラルなトップレベル呼び出しは それ以降のフォームに対して全バックエンドで効果を持ち、実行時に計算される 呼び出しはインタープリタのみで動作します。実行時に作成するパッケージが第二層です: make-package は空パッケージを作成します(名前は大文字化、:use 項目は既知のパッケージでなければなりません)、 rename-package は改名し(ニックネームを置換)、 delete-package は削除します。失敗は捕捉可能な package-error を signal し、原因の指示子は package-error-package で取り出せます。読込/compile 時パッケージ(組込みと、コンパイルされたプログラムの defpackage の成果物)は実行時に不変です -- 改名も削除も signal します(コンパイル済みバックエンドがそれに解決しているため)。インタープリタでは defpackage の成果物は実行時層に入るため、他と同様に改名も削除もできます。問い合わせ系は両層に届きます: packagep、 package-nicknames、 find-all-symbols、 do-all-symbols、 apropos / apropos-list。コンパイル済みバックエンドはコンパイル時に焼き込んだテーブルと、そのプログラム自身が作ったパッケージから答えます。 shadow、 shadowing-import、 unintern はパッケージのメンバーテーブルを変更します。コンパイル済みバックエンドでは読込/compile 時パッケージは凍結されているため、そこでは何も変えずに t(unintern は nil)を返します。 問い合わせ系は本物です: find-package、 package-name、 list-all-packages、 package-use-list、 package-used-by-list、 package-shadowing-symbols。 コンパイル済みバックエンドはコンパイル時に焼き込まれたテーブルと、 そのプログラム自身が作ったパッケージから答えます。 複数の使用先パッケージが同じ名前を export している場合、 コンフリクトをシグナルする代わりに :use 順で最初のパッケージが優先されます。

動的(special)変数

let/let*、progv、および special と 同名のパラメータによる動的束縛はすべてのバックエンドでサポートされ、どの脱出でも 直前の束縛に復元されます: 通常の復帰、束縛の外側のハンドラで捕捉されるエラー、 catch/throw、return/return-from(lambda、flet、labels の関数からの ものを含む)、go、そして go や return-from で抜ける handler-bind のハンドラです。--no-gc バックエンドはトップレベルの defvar を拒否します。

数値タワー

rontolisp は整数(任意精度の bignum を含む)、比(1/3)、倍精度浮動小数点数、 複素数(#C(1 2)。部分は有理数か浮動小数点数)をサポートします。負の数の 平方根は複素数の根を返します。

--no-gc バックエンドには複素数の表現がなく、複素数のリテラルや構築はコンパイル エラーになります。

その他の省略事項

  • ラムダリスト: 拡張された defmacro のラムダリスト(&whole、&optional、 &key、&aux、入れ子の分配パターン)は destructuring-bind を経由します。 これは意図的に寛容で、引数の不足は nil、余剰は無視となりエラーになりません。 また funcall/apply 経由の呼び出しでは関数の物理パラメータは 10 個までです。
  • ユーザーマクロはコンパイル済みプログラムの実行時 eval では認識されず、 その eval がランタイムに構築する lambda はラムダリストキーワードを 解釈しません(コンパイル済み eval の制限を参照)。
  • プリティプリンタは、行が十分に広いものとしてのテキストを生成しますが、レイアウトは変えません。rontolisp のストリームは桁位置を持たないため、論理ブロックが折り返すことはなく、条件付き改行(pprint-newline の :linear / :fill / :miser、format の ~_ / ~:_ / ~@_ / ~i)はすべて何もせず、*print-right-margin* / *print-miser-width* / *print-lines* は受理のみで無視されます。行を分けるのは (pprint-newline :mandatory) と ~:@_ だけです。その他の *print-* 変数はすべて存在し、プリンタが実際に行う動作そのものの値を保持します。既定値以外を束縛しても効果がないというだけです(*print-escape* / *print-readably* / *print-pretty* と *print-case* は例外で、実際に効きます)。*print-case* はプリンタが綴るシンボルの大小文字を変換しますが、構造体・CLOS インスタンス・ハッシュテーブル・階数が 1 以外の配列に入れ子になったシンボルは格納された綴りのままです(リーダのケース)。通常の印字操作は *print-pprint-dispatch* を参照しません。エントリが効くのは、プログラム自身がエントリ関数を呼ぶ箇所です。
  • #. の read 時評価は .asd ファイル内では警告付きでスキップされます。
  • 組み込みマクロの名前(cond、case、when、setf、push など)は 再定義できません。

この一覧はすべてを網羅したものではありません。rontolisp は完全な標準ではなく、 焦点を絞ったコアを実装しています。