システム (asdf)
asdf パッケージは、Common Lisp のビルド機構である ASDF の限定的な API 互換サブセットを提供します:
複数ファイルのプロジェクトを NAME.asd ファイルに
asdf:defsystem で一度だけ記述すれば、
asdf:load-system がファイルを依存順に —
すべてのバックエンドで — ロードします。本物の ASDF は移植されていません
(CLOS、コンディションシステム、パスネーム API に依存しており、いずれもここには存在しません)。
代わりに .asd ファイルはプレーンなデータとして解析され、サポートされる defsystem
サブセットが load/require と同じ機構を駆動します。サブセット内に収まる .asd
はそのまま動作します。
| オペレータ | 用途 |
|---|---|
asdf:defsystem | システムの定義: :depends-on、:serial、:components |
asdf:load-system | システムのロード (依存が先、ファイルは順序どおり、冪等) |
プロジェクトの全体像
app/
my-app.asd
package.lisp
main.lisp
run.lisp
registry/base/
base.asd
base.lisp
;; app/my-app.asd
(defsystem :my-app
:version "0.1.0"
:depends-on (:base)
:serial t
:components ((:file "package")
(:file "main")))
;; app/package.lisp
(defpackage :my-app (:use :cl) (:export :run))
;; app/main.lisp
(in-package :my-app)
(defun run () (print (base:double 21)))
;; app/run.lisp
(asdf:load-system :my-app)
(my-app:run)
エントリファイルを実行またはコンパイルします。同じディレクティブが 4 つのバックエンドすべてで動作します:
rontolisp app/run.lisp --system-path registry/base # interpret
rontolisp app/run.lisp --system-path registry/base -o Prog.class # JVM
rontolisp app/run.lisp --system-path registry/base -o app.wasm # WASM
my-app.asd は run.lisp の隣で見つかり、依存システム :base は --system-path
経由で見つかります。コンパイルパスではシステム全体 (依存が先) がコンパイル時にプログラムへ
継ぎ足されます。コンパイル時 load インクルードとまったく同じ仕組みなので、JVM と WASM
のコンパイラはすべての defun をネイティブに認識します。
システム探索パス
asdf:load-system は NAME.asd を次の順で探します:
load-systemを実行しているファイルのディレクトリ (loadと同様)、--system-pathで指定したディレクトリ (PATHのようにプラットフォームのパス区切り文字で複数連結可)、- 環境変数
RONTOLISP_SOURCE_REGISTRYのディレクトリ (同じ形式)。
依存システムの .asd は、依存する側のシステムのディレクトリから探索が始まるため、
1 つのレジストリディレクトリに並んだ兄弟システムは互いを見つけられます。
quickload でダウンロードする
手動ダウンロードを省くには、ql:quickload
を使います。システム (とその依存) を本物の
Quicklisp ディストリビューションから取得し、上記の
仕組みでそのままロードします:
$ rontolisp
CL-USER> (ql:quickload "split-sequence")
(split-sequence)
CL-USER> (split-sequence:split-sequence #\, "a,b,c")
("a" "b" "c")
ダウンロードは Quicklisp の dist メタデータ (依存解決の systems.txt、tarball URL の
releases.txt) に基づきます。各リリースは展開され ~/.rontolisp/quicklisp/ 以下に
キャッシュされる (RONTOLISP_QUICKLISP_HOME で変更可能) ため、2 回目以降の
quickload はネットワーク I/O を行いません。ダウンロードはインタプリタ実行時または
コンパイル時に (Java 側で) 行われ、コンパイル済みプログラムはソースを内包していて
実行時にはフェッチしないので、ql:quickload は 4 バックエンドすべてで動作します。
ロード自体は asdf サブセットを経由するため、同じ制約が当てはまります — ダウンロード
できたライブラリでも、そのソースが下記のサポート範囲に収まっている場合にのみロード
できます。
dist を追加する (Ultralisp)
Quicklisp の dist 形式を話すディストリビューションは 1 つではなく、2 つ目として
よく使われるのが Ultralisp です (数分ごとに再構築される
ため、公開されたその日にライブラリが入ります)。これはオプトインで、プログラム
からは本物の Quicklisp と同じ呼び出し
ql-dist:install-dist で
追加します:
$ rontolisp
CL-USER> (ql-dist:install-dist "http://dist.ultralisp.org/" :prompt nil)
"ultralisp"
CL-USER> (ql:quickload "circular-buffer")
(circular-buffer)
フォームを書く場所がない起動 (rontolisp test SYSTEM、コンパイル対象のソースを
書き換えられないビルドスクリプト) では、コマンドラインで指定します —
--dist ultralisp、カンマ区切りで複数指定、または環境変数 RONTOLISP_DISTS。
どちらの経路でも、Quicklisp 形式の distinfo の URL を直接指定できます。
dist は追加した順に、システム単位で検索されます: ql:quickload は各システム
(と各依存) を、それを列挙している最初の dist から取得します。したがって dist の追加は
「Quicklisp にない名前が引けるようになる」だけで、他の取得元は変わりません。
quicklisp は明示的に指定しない限り先頭に入るので、両方の dist にあるライブラリで
Ultralisp 側を優先したい場合は --dist ultralisp,quicklisp と書きます。dist の
index は、実際にその dist まで検索が到達したときにだけダウンロードされ、各 dist は
自身の ~/.rontolisp/<dist>/ にキャッシュします (ベースは RONTOLISP_DIST_HOME、
quicklisp については従来どおり RONTOLISP_QUICKLISP_HOME が優先)。index は一度
取得すると永続的にキャッシュされるので、更新の速い dist を活かすには
ql:update-dist を使います:
$ rontolisp -e '(ql:update-dist "ultralisp")'
それ以外は同じです。ダウンロードはインタプリタ実行時またはコンパイル時に行われる ので、追加した dist も 4 バックエンドすべてで同じように動作しますし、ダウンロード したライブラリが下記のサポート範囲に収まっている必要がある点も変わりません。
サポート範囲 (と非サポート)
-
.asdファイルはデータとして解析されます:defsystem(裸またはasdf:修飾)、in-package/defpackageフォーム (スキップ)、register-system-packagesフォーム (「このパッケージはあのシステムにある」という対応を記録します — package-inferred-system がdefpackageの依存をシステム名に変換するときに 参照されます。それ以外では作用しません。パッケージは自身のdefpackageを 通じて見つかるからです)、そして純粋なリテラル/条件値を持つトップレベルdefparameter(解析時環境に評価されます) を書けます。#+/#-フィーチャ条件は動作し (ターゲットバックエンドのフィーチャーに対して評価されます。 データ型を参照)、#.リード時評価フォームはその値が使われる場所で解決されます: ロード対象を 決める句ではそのdefparameterに対して解決され ((:file #.*string-file*)の慣用形)、 解決できない場合はファイルと句を名指しするエラーになります。無視されるメタデータ (:long-description、:version、:performなど) とトップレベル (ASDF バージョンガード) では、評価されず、警告もなく捨てられます。 また:depends-onのエントリには(:feature EXPR DEP)を書け (フィーチャ式が成立するときだけ依存が追加されます)、(:version NAME "1.2.3")も書けます (素の依存として解決されます。バージョン制約は 検査されません — ここでは:versionオプションは無視されるメタデータなので、 比較する相手がありません)。トップレベルの(defmethod perform ...)フックは許容され 無視されます (それを実行するoperate機構はありません。他のメソッド名はエラーです)。 スーパークラスがドキュメントコンポーネントクラス (ASDF のdoc-file、または同じ ファイル内で先に宣言されたもの) であるトップレベルdefclassは、その名前を コンポーネント型として宣言します。そのエントリは順序付けには参加しますがソースを 提供しません (:static-fileと同様)。:doc-fileと:html-fileはdefclassなしで使えます。それ以外のトップレベルフォームはファイルを名指しするエラーです。 -
.asdはフィーチャーを宣言できます:defsystemの前にあるトップレベルの(eval-when (:load-toplevel :execute) (pushnew :my-feature *features*))(裸のpushnew/pushも可) は、そのファイル内でそれ以降に定義される全システムに 対してそのフィーチャーを宣言します — 各システムに:rontolisp-features (:my-feature)を書いたのと同じ効果です。この宣言はシステム 自身の:if-feature/(:feature ...)句と、そのコンポーネントファイルの読み取りに 効きます — つまり宣言を.asdの外へ運ぶ部分で、これはリーダ自身にはできません。 (同じ.asd内の#+/#-もその push を見ますが、それはリーダがソース自身の 宣言を扱うからです — データ型 を参照。) 依存システムには効きません (それぞれが自分で宣言します)。 situation が(:compile-toplevel)だけのeval-whenは無効です (ASDF は.asdを ロードするだけでコンパイルしません)。eval-when内のそれ以外のフォームは、その フォームを名指しするエラーです。 -
defsystemはメタデータオプション (無視。ただし素の文字列の:versionだけはasdf:component-versionが 読み返します)、:depends-on、:defsystem-depends-on(本物の ASDF が.asdの 読み込み中 にロードするシステム。同じ方法で検索され、:depends-onより先に ロードされ、システムの依存にはなりません。ここに書かれた組み込みシムはこのシステムに フィーチャーを宣言します —("trivial-features")はその句とコンポーネントファイルに 対して:unixと:little-endianを有効にします)、:serial、:pathname(全コンポーネントに前置されるリテラルなディレクトリ。ソースがsrc/にあるシステムは コンポーネント名を裸で書けます)、:file/:module/:static-fileエントリを持つ:componentsをサポートします。 コンポーネントには:if-feature exprを付けられます。フィーチャー式が成立しない 場合、そのコンポーネントのファイルは除外されますが (ライブラリが CLOS 専用 ファイルを(:or :sbcl ...)の後ろにゲートする方法)、依存順序内の位置は 維持されます。test-op 配線用のオプションは機構を持つ唯一の op です::in-order-to ((test-op (test-op ...)))と:perform (test-op (o c) ...)は 記録され、asdf:test-systemが 駆動します (それ以外の operation、修飾子つきのtest-op :afterメソッド、本体中の#.は従来どおり許容されて無視されます — 汎用のoperate機構は依然として ありません)。:versionの値は ASDF の(:read-file-form ...)間接参照を含む任意のリテラルフォームで構いません (検査されません — 記録されるのは素の文字列だけです)。それ以外 (:around-compileなど) は句を名指しするエラーです。 -
:class :package-inferred-systemをサポートします — ningle、rove、 array-operations が採用しているスタイルです。このクラスのシステムは:componentsを一切持ちません: サブシステム名がシステムのディレクトリ配下の ファイルパスになり (my-lib/mainはmain.lisp、my-lib/util/textはutil/text.lisp。システムが:pathnameを持つ場合はいずれもその配下)、 そのファイル自身のdefpackageが依存関係を表します —:use、:mix、:reexport、:use-reexport、:mix-reexportが挙げる全パッケージと、:import-from/:shadowing-import-fromの第 1 引数です。パッケージ名は システム名になります:register-system-packagesフォームが宣言していれば その名前、なければパッケージ名を小文字化したものです (clなどは除外されます)。 各ファイルは最初のパッケージ定義フォームまでしか読まれないので、defpackageの前に よくある(in-package #:cl-user)ヘッダがあっても問題ありません。defpackage/uiop:define-packageが 1 つも無いファイルはエラーです。これ以外の:classはサポートせず、:componentsも併記した package-inferred-system はエラーです。 -
同じシステムの 2 回目のロードは no-op です。循環する
:depends-onは検出して報告されます — 2 つのサブシステムのdefpackageが互いを指す形で書かれた循環も含みます。 -
コンパイルパスはリテラルなトップレベルの
(asdf:load-system NAME)を要求します。 インタプリタは実行時に計算された名前も受け付けます。どちらも末尾のキーワード オプション (:verbose nil、:force t、:silent t) を受理して無視します。 実在のライブラリは実行時にシステムをロードするときこれらを渡します。 コンパイルされたプログラムでネストした/計算されたload-system/ql:quickloadは、 システムがスプライス済みならnilを返し、そうでなければエラーを通知します — 実行時には何もロードできません。 -
コンポーネントメタオブジェクトは実行時に本物です。
asdf:find-systemはシステムごとに メモ化された CLOS インスタンス (呼び出し間でeq) を返します。クラスは本物です —asdf:component、asdf:child-component/asdf:parent-component、asdf:module、asdf:system、asdf:package-inferred-system、asdf:source-file、asdf:cl-source-file、asdf:static-file— ので、これらに 対するtypep、typecase、defmethodの特定化子はすべてのバックエンドで 動作します。リーダーasdf:component-name、asdf:component-version(素の文字列で書かれていた場合の:version文字列)、asdf:component-pathname、asdf:component-children(コンポーネントファイルごとに 1 つのcl-source-file、ロード順)、asdf:component-sideway-dependencies、asdf:component-parent、asdf:component-systemがモデルをたどり、asdf:registered-systemsが登録済みの全名前を列挙します。asdf:*user-cache*は external でnilです (fasl キャッシュはありません)。これは rove のシステム駆動テストランナーが読む コンポーネントモデルです。 -
asdf:test-systemは記録された test-op 配線を実行します。asdf:test-systemはシステムを ロードし、:in-order-toの test-op 連鎖をたどり、記録された各:perform (test-op (o c) ...)本体を component にシステムメタオブジェクトを束縛して 実行します。コンパイルパスではリテラルなトップレベルの呼び出しがテストシステムも スプライスします。 -
コンパイル時にシステムはツリーシェイキングされます。 ロードしたシステムが定義していても プログラムから到達しない関数・変数・定数は — クォートされたシンボルや文字列リテラル全体も 含めてソース中の名前を辿った上で —
.class/.wasmに含まれません。クラス・総称関数・ メソッド・コンディション・構造体も対象です(適用しうるインスタンスを到達可能なコードが 作れないメソッドも除かれます)。すべての定義を残すには--no-prune(または--dynamic) を付けてコンパイルしてください。唯一の帰結については JVM へのコンパイル を参照してください。 -
ロードコンテキスト変数は、全バックエンドでロード中のファイルを指します。 ファイルのロード中、
*load-pathname*はloadが呼ばれたときのパスを、*load-truename*はそれが解決されたパスを保持します。ファイルが終わると外側の ファイルの値に戻り、ロード外ではどちらもnilです — トップレベルで読んでも、 ロードが定義した関数をあとから呼んで読んでも同じです。コンポーネントは解決済み パスでロードされるため、どちらの変数もasdf:component-pathnameが返す値を保持します。これが、テストフレームワークがファイル内の定義とそれを所有する システムを対応付けられる理由です。コンパイルバックエンドでも同じで、差し込まれた ファイルのフォームは自分のロードコンテキストの下でコンパイルされます — そこでは 実行時に何もロードされないため、そうしなければ読む値がありません。*compile-file-pathname*と*compile-file-truename*は常にnilです (ここにはcompile-fileが存在しません)。ライブラリは自分のソースの隣にある データファイルを見つけるのに(or *compile-file-truename* *load-truename*)という定型句を使います。これは読み取り時にも働きます: この定型句が通常書かれる 形である#.フォーム — ソースの読み取り中にファイルを読むもの — は、全バックエンド で自分のファイルのロードコンテキストを見ます。ライブラリは隣に同梱したデータファイルを そのまま焼き込めます。
組み込みシムシステム
Quicklisp ライブラリの中には、rontolisp 側を知り得ない実装ごとのポータビリティレイヤに依存するものがあります。rontolisp
はそれらを組み込み ASDF システムとして同梱します: asdf:load-system/ql:quickload
(および実ライブラリの :depends-on) は、その名前をダウンロードせずバンドルされたシムに解決します。
| システム | シムが提供するもの |
|---|---|
usocket | rontolisp:tcp-* 上のソケット API (TCP ガイドを参照) |
trivial-gray-streams | ポータブルな Gray ストリームのクラス/総称関数 (バイナリ/入力クラスと trivial-gray-stream-mixin を含む基底クラス階層、読み書き/シーケンス総称関数、stream-file-position とその (setf ...) ライタ)。rontolisp 自身のプロトコル — ストリームを取る組み込みが CLOS インスタンスストリームに対してディスパッチする先 — へのアダプタ (Gray ストリームを参照) |
closer-mop | 実クラスメタオブジェクト(find-class / class-of の返り値)に対するクラスイントロスペクションのリーダー群: classp、class-slots、slot-definition-name/-initargs/-type/-readers/-initfunction、class-name、class-direct-superclasses、class-direct-slots、class-direct-subclasses、class-finalized-p、ensure-finalized。レガシーのタグシンボル指定子には従来通り (name declared-type) ペアを返す。フラットな closer-common-lisp 再エクスポートパッケージ(ニックネーム c2cl: cl 全体にこれらを重ねたもので、衝突は closer-mop が優先)は常に登録済みのため、(:use :closer-common-lisp) するパッケージが動作する |
flexi-streams | パススルーのストリーム (flexi ストリームは基底ストリームそのもの) に加え、本物のインメモリ・オクテットストリームの対: vector-stream クラス上の make-in-memory-input-stream と、make-in-memory-output-stream / get-output-stream-sequence (upstream と同じくストリームをリセットします)。Gray Streams を参照 |
babel | babel の 2 つの層それぞれにおける UTF-8 コーデック。日常的に使う層: babel:string-to-octets/octets-to-string (:start/:end/:errorp 付き)、babel:string-size-in-octets (:max も)、babel-encodings:*default-character-encoding* (:utf-8)、babel:list-character-encodings。その下のマッピング層は、オクテット列全体を一度に変換できずインクリメンタルにデコードするライブラリのためのものです: babel-encodings:get-character-encoding、enc-max-units-per-char、babel:*string-vector-mappings* に対する lookup-mapping、そこから得られる code-point-counter / octet-counter / decoder / encoder、そして babel:unicode-char 型。不正な入力は babel 自身のコンディションをシグナルし (babel:character-decoding-error とその葉である end-of-input-in-character、character-out-of-range、invalid-utf8-starter-byte、invalid-utf8-continuation-byte、overlong-utf8-sequence。符号化側は babel:character-encoding-error)、babel-encodings:*suppress-character-coding-errors* (すべての :errorp のデフォルト値の由来) が真ならシグナルの代わりに代替文字を返します。ここではエンコーディングもマッピングもエンコーディング名そのものです: 本物の babel は 20,000 行のテーブルから 40 種類以上のコードページを生成しますが、rontolisp の文字モデルは 1 つだけ (文字は Unicode コードポイントそのもので、外部表現は UTF-8) なので、シムはそのコーデックだけを実装し、:latin-1/:us-ascii は表現できるオクテットの範囲でコードポイント同一視として扱い、それ以外の :encoding はシグナルします — 誤った符号化のバイト列を黙って返すよりも |
float-features | IEEE 754 ビットプリミティブ上の single-float-bits/bits-single-float と double 版 (インタープリタ + JVM。WASM の数値モデルは 64 ビットのビットパターンを保持できない) |
bordeaux-threads (ニックネーム bt と bt2) | 1 つのシムが両方の API 名前空間を提供します。ロック関連のサブセット — make-lock、acquire-lock、release-lock、with-lock-held、*supports-threads-p* — は rontolisp:make-mutex などの上に、スレッド生成 — bt2:make-thread (:initial-bindings 付き)、join-thread、threadp、thread-alive-p、destroy-thread — は rontolisp:make-thread の上に実装されており、インタプリタと JVM では本物の仮想スレッドが生成されます。シングルスレッドの WASM バックエンドではスレッド生成の入口は呼び出し時にエラーを送出し、bt:*supports-threads-p* は nil です。make-lock は再入可能なロックを返し (上流のものは再入不可) — したがって make-recursive-lock と with-recursive-lock-held は同じオブジェクト・同じ展開になります — acquire-lock の :wait-p は無視されます — 獲得は常にブロックします。:initial-bindings の値フォームは quote フォームか自己評価値に限られます (それ以外は新スレッドの動的環境が必要になるためエラーを送出します) |
uiop | ASDF の移植性レイヤ。15 個のサブパッケージと 429 個のエクスポートとして登録されています。実装済みのものは uiop パッケージ を参照してください。これ以外のエクスポートは解決はされ、操作名とともに uiop:not-implemented-error をシグナルするので、名前を挙げているだけのライブラリも読み込めます |
swank | 依存しているライブラリをロードできるようにするためだけのスタブです: swank:create-server はエラーを送出し (「rontolisp はリモート REPL を提供できません」)、swank:stop-server は nil を返す何もしない関数です。本物の swank は SLIME のサーバ側であり、その .asd はデータとしての defsystem フロントエンドが読めないプログラムです -- スタブがないと (ql:quickload "clack") は SLIME の tarball を取得してそこで失敗します |
mgl-pax-bootstrap | mgl-pax でドキュメント化されたライブラリをロードできるようにする、mgl-pax パッケージ (ニックネーム pax) のスタブです (uuid の依存である trivial-utf-8 がハード依存しています。本物のシステムの .asd は defsystem-as-data のサブセット外のコンパイルフックである :around-compile を宣言しています)。pax:define-package は defpackage として働き、pax:defsection はセクション名を nil の変数として定義し、さらにセクションの (シンボル ロケーティブ) エントリを export します — mgl-pax のドキュメント化されたデフォルトであり、この種のライブラリが公開 API を export する方法です。PAX-World 登録ヘルパーは nil を返す何もしない関数です。ドキュメントは生成されません |
trivial-features | 本家 trivial-features が CFFI で調べて行うフィーチャー宣言を、調査なしで提供します: :defsystem-depends-on (または :depends-on) にこの名前を書くと、依存する側のシステム自身の句とコンポーネントファイルに対して :unix と :little-endian が宣言され、実行時には両方が *features* に push されます。:unix は、どのバックエンドでもファイル/パス/環境変数まわりが POSIX 形であり Windows ではないためです。:little-endian は、プログラムがマシンのレイアウトを見られる唯一の場所 (WASM のリニアメモリ、リアクタの :bytes 境界) が wasm 仕様でリトルエンディアンだからです。:64-bit は、どのバックエンドでも fixnum が64ビットであり、ポインタ相当の値はポインタが存在するところではすべて8バイトだからです — cffi の types.lisp はまさにこれを読んで :size の基底型を決めます。CPU 名 :x86-64 は引き続きありません — rontolisp には記述すべき命令セットの表面がありません — :32-bit も同様で、rontolisp がそれであることはありません。本家の .asd はそもそもここではロードできません: 認識できない処理系に対して (error "Sorry, your Lisp is not supported") で終わるためです |
trivial-garbage (ニックネーム tg) | GC ファイナライザを正直な no-op として提供します: tg:finalize は何も登録せずオブジェクトを返し、tg:cancel-finalization は nil を返す no-op です。tg:make-weak-hash-table は通常のハッシュテーブルを返し (:weakness と :weakness-matters は捨て、残りの引数は make-hash-table に渡します)、tg:hash-table-weakness は nil を返します: 弱参照は Common Lisp の内側からは観測できないため、違いはテーブルがより多くを保持することだけです。GC フックを公開するバックエンドは存在せず — そして Common Lisp はファイナライザの実行を何も保証しないため、準拠したコンシューマは実行されなくても動作しなければなりません。dbd-postgres (このシムのコンシューマ) への実際的な帰結: リークした prepared statement は接続が閉じるまで残ります。dbi:disconnect を明示的に呼んでください |
cl+ssl | あらゆる CL の HTTP クライアント (dexador、drakma など) が TLS に到達するために使うライブラリのクライアント側を、rontolisp:tls-upgrade の上に提供します — 本物の cl+ssl は OpenSSL への CFFI バインディングで、ここではロードできません。cl+ssl:make-ssl-client-stream は接続済みストリームを :hostname (必須) に対して TLS へアップグレードします。make-context :verify-mode + with-global-context + ssl-check-verify-p が検証モードを運ぶため、クライアントの「insecure」ノブ (例: dex:*not-verify-ssl*) はプリミティブの :insecure に到達します。ensure-initialized は no-op です。裏付けのないものは受け取って無視するのではなくエラーを通知します: クライアント証明書 (:key/:certificate/:password、および use-certificate-chain-file) と :verify-location の CA パス — 代わりに javax.net.ssl.trustStore システムプロパティでトラストストアを指定してください。インタプリタ、JVM、WASM --component バックエンドで動きます (そこでは tls-upgrade が wasi:tls@0.3.0-draft の上で動き、実行フラグに -S tls=y を追加します。非 nil の insecure ノブはエラーを通知します — ドラフトには検証のオプトアウトがないため)。WASM Preview 1 はコンパイルエラーのままです |
clack-handler-rontolisp | Clack のハンドラバックエンド: run/stop をエクスポートするパッケージ clack.handler.rontolisp で、Clack のアプリケーションプロトコルを rontolisp の組み込み HTTP サーバに橋渡しします。手動でロードすることはありません — (clack:clackup app :server :rontolisp) が実行時に名前で解決します (clack がパッケージ名から導出するドット区切りの綴り clack.handler.rontolisp でもこのシステムが応答します)。Clack ガイドを参照 |
clack-handler-reactor | ホスト駆動のリアクタ向けの Clack ハンドラバックエンド: Cloudflare Worker、ブラウザページ、node や JVM への埋め込みなど、リクエストを既にホスト側でパースし、プログラムにソケットを渡す代わりにエクスポートされた関数を呼ぶホストのためのものです。パッケージ clack.handler.reactor は run/stop に加えて、その下にある handle (アプリケーションと JSON リクエスト文字列を受け取り JSON レスポンス文字列を返す) と dispatch (clackup が格納したアプリケーションに対する同じ操作) をエクスポートします。上のバックエンドと同じく、(clack:clackup app :server :reactor) がドット区切りの綴りも含めて解決します。Clack ガイドを参照 |
シムは意図的に薄く作られています: ロード可能なライブラリが実際に呼ぶものだけを満たし、上流の完全な API は提供しません。
実際に何がロードできるか
現在、以下の実世界のライブラリが無改変でロードできます。バックエンド列は
それぞれの検証済み範囲を示します — 「4 つ全て」はインタプリタ、JVM、WASM
Preview 1、--component を意味します。特筆事項列はロードの特色と、
動作しないものをまとめています。
| ライブラリ | バックエンド | 特筆事項 |
|---|---|---|
| alexandria 1.0.1 | 4 つ全て | エコシステムで最も依存されているユーティリティライブラリを実物のソースからロードします。2 つのパッケージ (alexandria/alexandria-1 と alexandria-2) の両方が動作し、以下の依存を持つライブラリはすべてこれを取り込みます。まだ無いプリミティブに依存するメンバーは動きません: type= (subtypep の第 2 返り値)。format-symbol/ensure-symbol とシンボルを渡した ensure-function はインタプリタでのみ動作します (コンパイルバックエンドでは誤った値ではなくエラー)。shuffle/random-elt/gaussian-random は各バックエンド固有のエントロピーを引くため、出力はバックエンド間で比較できません |
| split-sequence v2.0.1 | 4 つ全て | 文字列とリストに対する API 全体。第 2 戻り値 (再開インデックス) も動作します。CLOS 専用の extended-sequence.lisp は :if-feature (:or :sbcl :abcl) でゲートされ自動的に除外されます |
| parse-number v1.8 | 4 つ全て | API 全体が整数・有理数・浮動小数点数・基数プレフィクス付きリテラル (#xFF、#3r12)・指数マーカーを扱い、invalid-number コンディションも意図した診断情報付きでシグナルされます |
| cl-utilities v1.2.4 | 4 つ全て | 公開 API 全体 — 独自の split-sequence、extremum ファミリー、read-delimited、expt-mod、collecting/with-collectors、自作マクロから使える with-unique-names/with-gensyms/once-only (3 段のネストバッククォート)、rotate-byte、copy-array、compose |
| cl-who v1.1.5 | 4 つ全て | (X)HTML 生成マクロ — with-html-output(-to-string) が属性・ネストしたタグ・ローカルな str/esc/fmt/htm 演算子を扱い、:xml と :html5 はどちらも正しくレンダリングされます。:indent (整形出力) は未対応で、出力モードの切り替えは (setf (html-mode) :html5) を使います: cl-who はモードをマクロ展開時に読み取るため、*html-mode* の実行時 let 再束縛は反映されません |
| cl-mustache 0.12.3 | 4 つ全て | Mustache テンプレートレンダラを無改変でロードします — 文字列テンプレートとファイルテンプレートの両方に対する render/render*、一度だけパースして何度でも描画する compile-template、レンダラに名前を束縛する define、:data/:partials を取る make-context。コンテキストは alist、ハッシュテーブル、あるいはそれらの連鎖です。セクション、反転セクション、パーシャル、動的パーシャル名 ({{>*name}})、ラムダセクションのいずれも描画できます。同梱の 194 ケースの mustache 仕様スイートは全バックエンドで 158/194 — SBCL が通すのと同一の集合であり、残る 36 件は上流の制約です (null の補間、ドット名によるコンテキストフレームの追加、および cl-mustache が対象とする 1.1.2 仕様より後に追加された 26 ケースの継承モジュール全体)。t/test-api.lisp は 20/20 です。存在しないパーシャルは use-value リスタートを提供しつつ partial-cant-be-found を signal するため、これを処理する成果物は WASM 両バックエンドで EH モードでコンパイルされます |
| assoc-utils | 4 つ全て | Alist ユーティリティの API 全体 — aget (setf 可能)、alist/plist/ハッシュの相互変換、remove-from-alist/delete-from-alistf、with-keys、alist-get、alist=、alistp |
| cl-base64 v3.4 | 4 つ全て | Base64。文字列・(unsigned-byte 8) 配列・整数に対して動作し、:columns の折り返しと :uri アルファベットも扱います。不正な入力文字は bad-base64-character を通知しますが、その :input/:position/:code スロットを読めるのはインタプリタのみです (コンパイル系バックエンドは素のコンディションを通知し、同じ handler-case で捕捉できます) |
| md5 v2.0.4 | 4 つ全て | MD5 (RFC 1321) — md5sum-sequence/md5sum-string とインクリメンタル API が、4 バックエンドすべてで RFC のテストベクタと一致します |
| chipz 0.8 | 4 つ全て | 展開 — gzip/zlib/deflate 形式に対する chipz:decompress (新しいベクタへ、渡したベクタへ、あるいは make-dstate による逐次処理)、および CRC32/Adler-32 のチェックサム API。inflate の状態機械は遷移のたびに #'ローカル関数 を構造体スロットに格納する labels であり、脱出に catch/throw を使うため、コンパイル成果物は常に WASM 両バックエンドで EH モードになります。bzip2 も一緒にロードされますが (decompress の typecase が bzip2-state を名指しするため)、こちらは未検証です。size-report/programs/zlib がこれを使っています |
| cl-ppcre v2.1.2 | 4 つ全て | Perl 互換正規表現を実物のソースからロードします — scan、scan-to-strings、split、regex-replace(-all)、all-matches、count-matches、do-scans/do-matches 系マクロ、register-groups-bind、quote-meta-chars、パースツリー正規表現、(?i) などのインラインモディファイア |
| cl-unicode 0.1.6 (cl-ppcre-unicode 込み) | インタプリタ | Unicode 文字データベースをライブラリとして提供します — general-category、script、code-block、unicode-name、character-named、property-test、has-binary-property、大文字小文字・数値のマッピング、4 種類の正規化形式。加えて cl-ppcre-unicode のリゾルバが入るので、正規表現の \\p{L}、\\p{Nd} などのプロパティエスケープが使えます。cl-unicode が挙げる 8 つのコンポーネントのうち 3 つはリリースに存在しません。本来の ASDF は、同梱の UCD テキストを解析してそれらをソースの隣に書き出す別システムを走らせて生成します。rontolisp はそのビルド段階を自前で行い、同じファイルから同じテーブルをロード時に導出するので、(ql:quickload "cl-unicode") だけで済みます。インタプリタ(およびネイティブバイナリ)専用です。テーブルは約 5 MB のデータで、1 つの .class が名前を持てる量を超えます(JVM バックエンドは定数プール上限で停止します)。WASM は 6.7 MB のモジュールを生成できますが、equalp ハッシュテーブルのキー配置が大文字小文字を区別するため、cl-unicode 自身のプロパティ検索がロード時に失敗します |
| str 0.21 | インタプリタ | 文字列ライブラリ — title-case/camel-case/snake-case/kebab-case をはじめとするケース変換一式(cl-change-case 経由)、split/join/words/lines、trim/pad/shorten、starts-with-p/ends-with-p/containsp、replace-all/replace-using、remove-punctuation、lettersp/alphanump/digitp の各述語。述語が Unicode 対応なのは cl-ppcre の \\p{L} エスケープを通るからで、そのため str は cl-unicode を引き込み、インタプリタ専用という制約も引き継ぎます |
| com.inuoe.jzon v1.1.4 | 4 つ全て | README のウォークスルーを含む JSON のパースと文字列化 — ハッシュテーブル / ベクタのラウンドトリップ、:key-fn、Gray ストリームによる :stream ライタ、jzon:writer、CLOS インスタンスの文字列化。依存システムは上記の組み込みシムに解決されます。3 つの数値リーフコンポーネント (eisel-lemire の float リーダと Schubfach の float プリンタ) は rontolisp ネイティブの float 演算による組み込みシムに置き換わります — float の出力は rontolisp 自身の最短ラウンドトリップ 10 進表現で、すべてのバックエンドで同一です — 。極端な指数のパースは正確な丸めから数 ulp ずれることがあります (絶対値 22 以下の 10 進指数はちょうど 1 回の丸め)。WASM バックエンドでは通常の注意点 (ハッシュテーブルの巡回順、非 ASCII \u エスケープ) が適用されます |
| ironclad v0.61 (SHA-2 / HMAC / PBKDF2 / HKDF / SCRAM / RSA のスライス) | 4 つ全て | 実物のソースからロードし、公開されている FIPS 180-2・RFC 4231・RFC 5869・RFC 7677 のテストベクタを再現します — PostgreSQL クライアントが認証に使う一連の流れ、SCRAM-SHA-256 のクライアントプルーフを端から端まで含みます。ダイジェストは SHA-224/256/384/512 と、その上の MAC・KDF (hmac、hmac-kdf、pbkdf2、pbkdf2-hash-password) です。RSA スタックも実物で、generate-key-pair、sign-message/verify-signature、encrypt-message/decrypt-message が PSS/OAEP の有無どちらでも使えます。ロードされるのはこのスライスのみです (ironclad 自身の .asd は実行可能なプログラムのため、同梱の代替がスライスを宣言します): 暗号方式 (cipher)、AEAD モード、RSA 以外の公開鍵アルゴリズム (DSA、ElGamal、楕円曲線、ed25519)、その他のダイジェスト系列は利用できず、要求すると呼び出し時に通知されます。prng.lisp は rontolisp:random-bytes 上の OS エントロピー表面だけに絞られるため、ノンス・RSA 鍵生成・PSS のソルトはどのバックエンドでも暗号論的に強いものの、:fortuna とシードファイル操作はありません。PBKDF2 はインタプリタではネイティブカーネルで動くため — 結果のバイト列は同じで、およそ 3 桁高速です — パスワードハッシュや SCRAM 認証がインタプリタ側の性能に縛られることはありません |
| uax-15 v0.1.3 | 4 つ全て | Unicode 正規化 (UAX #15) の 4 形式すべてを実物のソースからロードします。--system-path には 3 ディレクトリが必要です (uax-15、split-sequence、cl-ppcre を : で連結)。上流はロード時に同梱の 2.7 MB の Unicode テキストを解析してテーブルを構築します (インタプリタでは数分) が、rontolisp はコンパイル/ロード時に同じファイルから同じテーブルを導出し、各テーブルを最初に読まれた時点で構築します。正規化を計算する関数は上流のまま残るため、ロードはほぼ無償で、正規化しないプログラムは一切払いません。意図的な差は 1 点だけで、それは修正です: (unicode-letter-p #\A) は T を返します (実際のロードでは NIL。上流のループが #+utf-32 を読むため)。get-mapping は全バックエンドで通知しますが、上流の時点で壊れており呼び出し元はどこにもありません |
| quri v0.7.0 | 4 つ全て | URI ライブラリを、実物のソースから (ql:quickload "quri") でロードします — スキーム別構造体へのパース、各アクセサ、render-uri、merge-uris、uri-query-params、パーセントエンコーディング、公開サフィックス API、アドレス述語。依存する babel は組み込みの UTF-8 シムに解決されるため UTF-8 以外の :encoding はシグナルし、実効 TLD テーブルは同梱の 152 KB のリストから最初の読み取り時に構築するため (load-etld-data OTHER-FILE) は OTHER-FILE ではなくそのリストを読みます。:lenient なパーセントデコードは不正なエスケープを handler-bind のハンドラからの go で読み飛ばしますが、コンパイルバックエンドはこれを非局所脱出へ低位化するため 4 つ全てで同じ結果になります。--system-path には alexandria・split-sequence・cl-utilities・idna が必要です |
| local-time v1.0.6 | 4 つすべて | 日付/時刻ライブラリを、実際のソースから (ql:quickload "local-time") でロードします: encode-timestamp/decode-timestamp、now/today、unix 時刻と universal time の相互変換、parse-timestring、同梱のフォーマット全て (ISO 8601、RFC 3339、RFC 1123、asctime、ISO 週日付) とカスタムフォーマットリストに対する format-timestring、比較関数群、timestamp+/timestamp-/adjust-timestamp/timestamp-minimize-part、ユリウス日の 2 つ、print-object。依存は組み込みの uiop だけです。ホストにファイルシステムがあれば実際の TZif ゾーンファイルもロードできます — (local-time:define-timezone tokyo #p"/usr/share/zoneinfo/Asia/Tokyo" :load t) — *default-timezone* を設定するロード時の /etc/localtime 読み込みも同様に動作し、ファイルを読めない場合は +utc-zone+ にフォールバックします (--dir なしの WASM バックエンドがこれにあたります)。directory が入ったことで、reread-timezone-repository も 4 バックエンドすべてで同梱の zoneinfo/ ツリーを走査します。find-timezone-by-location-name で "Asia/Tokyo" などが解決できます。コンパイルバックエンドではリポジトリを明示的に渡してください ((local-time:reread-timezone-repository :timezone-repository "zoneinfo/"))。既定値はロード時に asdf:component-pathname を実行時 eval 経由で (フォールバックは *load-truename*) 計算されますが、コンパイルバックエンドではどちらも答えられず既定値が nil になるためです |
trivia (trivia.trivial ルート) | 4 つ全て | Optima 互換のパターンマッチングを、実ソースから (ql:quickload "trivia") でロードします — match/match*/ematch (失敗は match-error を通知)、定数 / 変数 / cons / list / list* / vector パターン、guard、or/and/not パターン、defpattern、構造体パターン (キーワード形と conc-name 形)、クラスパターン (キーワードスロット形と (class name (slot var)) 形)、(type spec) パターン。システム trivia は trivia.trivial — 拡張向けに上流自身が指定するベースシステム — にマップされるため、節は :trivial オプティマイザで実行されます: 意味論は同一で、balland2006 の節最適化 (iterate + type-i が必要) だけがありません。依存 (alexandria、lisp-namespace、closer-mop / trivial-cltl2 シム) も一緒にロードされます。インタプリタは評価のたびにマクロを再展開するため、ホットな match ループはコンパイル系バックエンド向きです |
| sxql | 4 つ全て | SQL ジェネレータを、改変なしの実ソースから (ql:quickload "sxql") でロードします — sxql:yield は SQL 文字列とバインド値リストを多値で返し、全バックエンドでバイト同一 (同じソースの SBCL とも同一) です: select と from/where (:and/:or/:in/:like を含む)、order-by (:desc、nulls)、limit/offset、left-join ... :on、set= を使う insert-into、update、delete-from、カラムオプション付き create-table (mito の deftable が出力する形)、drop-table、alter-table。依存 (trivia の trivia.trivial ルート、alexandria、cl-package-locks — 最後のものは実質 no-op のロックライブラリ) も一緒にロードされます。マクロ中心のライブラリの常として、ホットなクエリ構築はコンパイル系バックエンド向きです (インタプリタは評価のたびにマクロを再展開します)。yield とステートメントビルダの解説はO/R マッピングガイドにあります |
| esrap 0.19 | 4 つ全て | パックラット / PEG パーサを、改変なしの実ソースから (ql:quickload "esrap") で読み込みます。インライン式または名前付きルールに対する esrap:parse、:lambda / :destructure / :text 変換を伴う defrule、add-rule / make-instance 'esrap:rule、大文字小文字を区別しない (~ "lit") 終端、and / or / not / * / + / ? の連結、意味述語((oddp decimal))、:junk-allowed、そして正確なパースエラー報告(esrap:esrap-parse-error。そのテキストは SBCL 独自の Unicode 文字名を除いて SBCL とバイト単位で一致します)が動作します。パーサは純粋な計算なので Preview 1 WASM も対象です(ソケット不要、追加フラグも不要)。依存する alexandria と trivial-with-current-source-form も一緒に読み込まれます。esrap:trace-rule は存在しない break を、swank のインデントフックは set を必要としますが、どちらも呼ばない限り到達しません |
| postmodern v1.33.12 (MOP ビルド) | インタプリタ、JVM、WASM コンポーネント | PostgreSQL スタック (s-sql を含む) を、無改変の上流ソースから (ql:quickload "postmodern") でロードします: with-connection/connect とコネクションプール、S-SQL フォームまたは文字列に対する query/execute (結果スタイルは全て)、doquery、:reconnect/reset-prepared-statement リスタート付きのプリペアドステートメント、トランザクションとセーブポイント、execute-file、deftable。:postmodern-thread-safe は ON なのでロックは実際に直列化します。DAO 層が入っています: ビルドは :postmodern-use-mop を ON にしており、table.lisp が静的メタオブジェクトサブセットの上で無改変のままロードされます — :col-type/:keys/:table-name を持つ (defclass ... (:metaclass pomo:dao-class))、dao-table-definition、deftable の !dao-def、insert-dao/get-dao/update-dao/upsert-dao/delete-dao/save-dao/select-dao/query-dao、make-dao。メタクラスプロトコルは定義時に実行されるため、DAO クラスはリテラルなオプションを持つトップレベルの defclass でなければならず (実行時データからのクラス構築はエラーを通知)、finalize-inheritance は初回使用時ではなくクラス定義時に即座に実行されます — 定義エラーが早く表面化するだけで、結果は変わりません。接続には cl-postgres のソケット層が必要なため Preview 1 WASM は対象外で、--component にはさらに -S tcp=y -S inherit-network=y が必要です。s-sql の層だけ ((ql:quickload "s-sql")) はソケットを開かず、4 つ全てのバックエンドで同一の SQL を生成します |
| clack v2.1.0 (lack 同梱) | インタプリタ、JVM、WASM コンポーネント | Web アプリケーション環境を無改変の上流ソースから (ql:quickload "clack") でロードし、組み込みの clack-handler-rontolisp バックエンドで serve します — Clack ガイドを参照。lack 側もロードされます: lack:builder、(ironclad スライス上の) lack-util の generate-random-id、そして clackup のデフォルト :use-default-middlewares t がエンドツーエンドで通すバックトレースミドルウェア。clackup のデフォルト :use-thread t はインタプリタと JVM でアクセプタを本物のスレッド (rontolisp:make-thread) で実行します。WASM コンポーネントは代わりに wasmtime serve の下で serve します (ソケットはホストが所有)。Preview 1 WASM は設計上着信 TCP を持たないため、clackup は呼び出し時にエラーを送出します |
| tiny-routes v0.1.1 | 4 つ全て | Clack アプリケーション向けのルーティング層を無改変のソースから (ql:quickload "tiny-routes") でロードします — clack:clackup とルートを持つアプリケーションの間を埋める部品です。define-get/define-post/define-put/define-delete/define-any/define-route と define-routes、cl-ppcre 上の :id 形式のパステンプレート (:regex t で正規表現も)、path-parameter、with-request/with-path-parameters、wrap-request-body (Clack の :raw-body ストリーム)・wrap-query-parameters・wrap-request-predicate/-mapper・レスポンス系ラッパを繋ぐ pipe コンビネータ、そして ok/created/not-found/… のコンストラクタ一式。姉妹システム tiny-routes-middleware-cookie もロードでき (parse-cookie-header、write-set-cookie-header、wrap-request-cookies、wrap-response-cookies)、cl-cookie・quri・local-time・proc-parse を引き込みます。ルーティング自体は純粋な計算なので4 つ全てのバックエンドが対象です。ルートを serve するには clackup が要るため Preview 1 は外れます — Clack ガイドを参照。依存は cl-ppcre だけなので、ディスクからロードする場合 --system-path には 2 ディレクトリが必要です。テストシステムは fiveam を要求しますが、fiveam はロードできません。サイズ制約のあるコンパイル済みモジュール向けには ppcre 不要のオプトイン tiny-routes/lite があります — 直下の節を参照 |
| ningle v0.3.0 | 4 つ全て | Clack 上の「超マイクロフレームワーク」を無改変のソースから (ql:quickload "ningle") でロードします — 2 つめのルーティング層であり、tiny-routes とは書き方の違いではなく本質的にモデルが異なります。アプリケーションは CLOS のオブジェクトで ((make-instance 'ningle:app)、lack-component の一種)、各ルートは setf で追加し ((setf (ningle:route app "/x") controller))、コントローラは環境ではなくマッチしたパラメータを受け取り、関数でないコントローラはそのままレスポンスボディとして返されます。:name トークンと * スプラットのパステンプレート、:regexp t ルート、:method、:accept によるコンテントネゴシエーション、ユーザ定義の requirement ((setf (ningle:requirement app :key) fn)) — つまりパス以外の条件でルートを選べます — に加えて ningle:*request*/*response*/*session* スペシャル、ningle:context と with-context-variables、ningle:next-route、そして 404 をオーバーライドするメソッド ningle:not-found。ルータの myway と myway の map-set も一緒にロードされ、ningle が毎リクエスト読む lack のリクエストチェーン一式 (http-body、fast-http、smart-buffer、circular-streams、quri、yason、trivial-mimes) も同様です — コンパイル済みモジュールが同じルートの tiny-routes 版より一桁大きいのはこれが理由で、サイズ向けのオプトインはありません: myway は全ルールを cl-ppcre のスキャナにコンパイルするため、正規表現エンジンが実際に到達可能です。ルーティング自体は純粋な計算なので4 つ全てのバックエンドが対象です。ルートを serve するには clackup が要るため Preview 1 は外れます — Clack ガイドを参照 |
cl-dbi 0.11.1 (dbd-postgres のみ) | インタプリタ、JVM、WASM コンポーネント | データベース非依存インターフェイスを無改変ソースから (ql:quickload "dbd-postgres") でロードします: dbi:connect (ドライバはロード済みシステム上で解決されます — コンパイルされたプログラムは実行時にシステムをロードできないため、プログラム自身に ql:quickload を含める必要があります)、dbi:do-sql、dbi:prepare/execute/fetch/fetch-all、dbi:with-transaction (commit と rollback)、dbi:connect-cached、dbi:disconnect。:mysql と :sqlite3 ドライバは FFI を要するため存在しません。スレッド対応バックエンドではコネクションキャッシュはスレッド別です (bt2 シムの実ロックと rontolisp:current-thread 上の cache/thread.lisp)。シングルスレッドの WASM バックエンドは upstream 自身のスレッドなしキャッシュを使います。依存の trivial-garbage は上記の no-op ファイナライザシムに解決されるので、dbi:disconnect を明示的に呼んでください。ソケット制約は postmodern と同じです: Preview 1 WASM は対象外、コンポーネントは -S tcp=y -S inherit-network=y が必要です |
| mito 0.2.0 | インタプリタ、JVM、WASM コンポーネント | O/R マッパーを無改変ソースから (ql:quickload "mito") でロードします — システム全体 (mito-core + mito-migration + lack-middleware-mito) が入ります。詳細はO/R マッピングガイドを参照してください。DAO 層: connect-toplevel/disconnect-toplevel、deftable (静的メタオブジェクトプロトコル上の dao-table-class メタクラス — auto-pk :serial と :uuid、record-timestamps-mixin の created-at/updated-at)、table-definition、ensure-table-exists、create-dao/insert-dao/save-dao/delete-dao、find-dao、sxql 句付きの select-dao、object-id、retrieve-by-sql、execute-sql。マイグレーション層: migration-expressions と migrate-table によるクラスと実スキーマの差分は 3 バックエンドすべてで動作し、マイグレーションファイルを扱う generate-migrations / migrate はインタプリタ + JVM です (WASM バックエンドはディレクトリ作成とファイル削除の呼び出しをインポートしていないため、呼び出し時にエラーになります)。migrate は生成された .sql を esrap で読み直し、アドバイザリロックは chipz の CRC32 だけを切り出したスライスに乗ります。PostgreSQL のみ (dbd-postgres を明示的に quickload する必要があります)。他のメタクラス利用ライブラリと同様、deftable はリテラルオプションのトップレベルフォームである必要があります。既知のギャップ (いずれもガイドに記載): :conc-name アクセサは生成されません (slot-value は動作します)。sxql の SQL 関数オペレータ — (:count ...)、したがって mito:count-dao — はインタプリタ限定です。次の 2 つは SBCL でも同一に失敗する上流の不具合であり、ギャップではありません: :col-type のない :references 単独、および :initform を持つ NOT NULL カラムの追加。依存の uuid もロードされます (v1/v4 の生成はバックエンド自身のエントロピーを使います)。dissect のスタック内省は no-op インターフェイスです。ソケット制約は cl-dbi と同じです: Preview 1 WASM は対象外、コンポーネントは -S tcp=y -S inherit-network=y が必要です |
| rove v0.10.0 | 4 つ全て | テスティングフレームワークを無改変ソースから (ql:quickload "rove") でロードします。詳細はテストガイドを参照してください — deftest/testing/ok/ng/signals/outputs/expands/pass/fail/skip/failing/setup/teardown/defhook/diag が :spec (デフォルト) と :dot レポーターで動作し、エントリポイントも一式動作します: :package-inferred-system と素の defsystem テストシステムの両方に対する rove:run、run-test/run-tests、テストファイル末尾の run-suite。エラーを送出したテスト本体は実行を終わらせず記録された失敗になります (WASM バックエンドでは生トラップ — (car 1)、(/ 1 0) — は依然として実行を終わらせます)。rontolisp test TARGET (あるいは自前ランナー内の (uiop:quit (if (rove:run ...) 0 1))) は、どのバックエンドでも結果を CI の終了コードにします。依存の dissect は実ソースからロードされますが、スタック内省は空なので失敗レポートにバックトレースは出ません。またアサーション記述内のシンボルは、SBCL が裸で印字するところをパッケージ修飾付きで印字します — 詳細と実行コマンドはガイドを参照してください |
| jose | 4 つ全て | JSON Object Signing and Encryption (JWS/JWT) を無改変ソースから (ql:quickload "jose") でロードします — HS256/384/512、RS256/384/512、PS256/384/512、および署名なしの none に対する encode/decode/inspect-token と、登録済みクレームの検査 (iat/nbf/exp/jti および :issuer/:audience/:subject キーワード)。生成されるトークンは同一ソースでの SBCL とも、Python の hmac/hashlib ともバイト単位で一致します — HS256 のものは jose の README が掲載しているトークンそのものです。RSA 鍵は ironclad のオブジェクトなので、ironclad:generate-key-pair :rsa (あるいは同等のものを返す任意のパーサ) から供給できます。PS512 には 1040 ビット以上の法が必要ですが、これは ironclad 自身のアサーションであり本実装の制約ではありません。期限切れの exp、未来の nbf、署名検証の失敗はいずれも cerror 経由で通知されるため、continue する handler-bind を置けばそのままデコードできます — そしてこれらを処理するプログラムは WASM 両バックエンドで EH モードでコンパイルされます。上流自身の jose/tests/jwt スイートは rontolisp test で 4 バックエンドすべて green です。姉妹の jose/tests/jws はどの処理系でも実行できません — (:use #:pem) しており、pem は Quicklisp ディストリビューションに存在しないためです。Quicklisp キャッシュではなく vendored ソースからロードする場合、--system-path には8 つのディレクトリが必要です: jose、cl-json、ironclad、cl-base64、split-sequence、assoc-utils、alexandria、trivial-utf-8 |
| cffi | インタプリタ, JVM | Common Foreign Function Interface — エコシステムのCバインディングがすべて前提にしているライブラリ — を (ql:quickload "cffi") で本家のソースそのままロードします。Cライブラリガイドを参照。defcfun、foreign-funcall(可変長引数を含む)、define-foreign-library/use-foreign-library、with-foreign-object + mem-ref/mem-aref、defctype、defcstruct + foreign-slot-value、defcallback、defcvar、外部文字列まわり一式が動きます。構造体の値渡しは cffi-libffi なしで動きます — foreign function API が自分でレイアウトを組むためです。rontolisp が用意するのは cffi-sys バックエンド、cffi.asd の差し替え、src/strings.lisp の代替の3つで、残り11ファイルは本家のソースそのものです。cffi-grovel と cffi-libffi は中途半端にロードせず理由を述べて拒否されます。java -jar と、ネイティブバイナリ(登録済みの呼び出しシェイプのグリッドに対して動き、グリッド外のシェイプは登録に必要なメタデータエントリをひとつ名指ししてシグナルします)と、バインディングを埋め込んだコンパイル済み .class/.jar で動きます。どちらの WASM バックエンドにも foreign function API はないため、そちらへのコンパイルは恒久的に拒否されます |
サイズ向けオプトイン: tiny-routes/lite
(ql:quickload "tiny-routes/lite") は、同じ tiny-routes のソースツリーを 1
コンポーネントだけ差し替えてロードします — upstream では cl-ppcre のスキャナ
だった path-template.lisp を ppcre 不要のマッチャーに置き換え、:cl-ppcre
依存もあわせて落とします。これが存在する理由は、ルーティングが正規表現
エンジンを生かしたままにするからです: ルートのテンプレートはルート
構築時にスキャナへとコンパイルされるため、コンパイル済みモジュールでは
どんな tree-shaking も cl-ppcre を除去できず、サイズ制限のあるターゲット
ではそれがモジュールの大半を占めます —
ルーティング版 Worker の例
の計測では、フルシステムで 974,530 B raw、lite で 408,448 B。ルートも
応答もリクエスト単位で同一です。
この置換はテンプレートが何にマッチするかを決して変えません — フル システムと同一にマッチするか、ルート構築時に大きな音を立てて拒否するかの どちらかです:
- 受理: リテラル文字と
:nameトークンだけから成るテンプレート — トークンは:の後に英字または_、以降は英数字・_・-が続くもの で、テンプレート内のどこにでも置けます:/users/:id、/files/v:version、/pair/:a/:b。このサブセット内では lite マッチャーはフルシステムの意味論 を正確に再現します。貪欲なバックトラッキングも、upstream の貪欲な トークン名走査も含めてです (/a/:x-:yの最初のトークン名はx-に なります)。2 つのエンジンはテストスイートがテンプレート単位でピン留め しています。 - ルート構築時に拒否 (逃げ道を明示するエラー): 正規表現メタ文字 —
.\[](){}|^$*+?— を含む テンプレートと、すべての:regex tテンプレート。(:nameトークンを 含まないテンプレートは upstream でも正規表現にならずstring=で比較 されるため、そこにメタ文字があっても両システムとも問題ありません。)
素の (ql:quickload "tiny-routes") は手つかずのまま — cl-ppcre 込みの
無改変ライブラリ — で、2 つのシステムを 1 つのプログラムにロードすることは
どちらの順でも拒否されます (後からロードした側がマッチャーを黙って再定義
してしまうため)。tiny-routes/lite は Quicklisp のインデックスには
ありません。この名前は tiny-routes のリリースをダウンロードし、その
.asd に対して解決されます。
cl-ppcre のロードはこれまでで最大の機能バッチ — ローカル
(declare (special ...))、ジェネリック化された CLOS スロットアクセサ、
initialize-instance :after、&environment + get-setf-expansion、psetf、
(setf (subseq ...))、subst/search/copy-tree、降順・大文字小文字非区別の
文字比較 — を牽引しました。
uax-15 のロードは 2 番目に大きなバッチ — ASDF/UIOP のパス名プリミティブの
コンパイル時畳み込み、with-open-file で読む同梱データファイルの成果物への
インライン化、LOOP マクロの節単位への書き換え、WASM GC 文字列の背後にある
UTF-8 バイトモデル — を牽引しました。
alexandria のバッチは、他のすべてのライブラリが受け継いだものです。依存を持つ
ライブラリはすべて alexandria に依存しているからです:
defmacro/destructuring-bind の &whole、&rest/&body の後の分配パターン
(if-let)、lambda-list-keywords、do-external-symbols、パッケージ指定子付きの
intern、ハッシュテーブルの内部情報リーダ
(hash-table-test/-size/-rehash-size/-rehash-threshold)、mismatch、
arrayp、with-open-stream、第一級の値としての #'open — さらに mappend の
ために、複数リストを取る第一級の値としての #'mapcar。
そのうち 16 ライブラリの実行可能なデモ — バックエンド別の実行コマンドと期待
出力付き — は
examples/asdf/
にあります。
現時点でロードできるライブラリの目安は、おおよそ次の範囲に収まるものです:
素の defun/defmacro/defpackage コード、loop、values を末尾に持つ
関数への multiple-value-bind、サポート済みの型指定子による
check-type/etypecase、宣言 (パース済み no-op、deftype を含む)、CLOS
静的サブセット (単一ディスパッチの
defclass/defgeneric/defmethod/make-instance/slot-value、および
(defun (setf name) ...) setf 関数)、そして
コンディションとリスタートのシステム
(define-condition/handler-case/handler-bind/restart-case/
invoke-restart)、return-from のイディオム、そして動的 (スペシャル) 変数束縛 (defvar の
スペシャル変数に対する let/let*)。完全なメタオブジェクトプロトコルや
対話的デバッガ (break、*debugger-hook*) の上に
構築されたライブラリはまだロードできません
(未対応のCL機能を参照)。それ以外の場合の実用は、
自分自身の複数ファイル rontolisp プロジェクトの構成です — その .asd は
本物の ASDF でも読めるものになります。