Gray ストリーム (ユーザー定義ストリーム)
rontolisp は実装ネイティブの Gray サポートを公開する実際の処理系にならい、独自の小さな
Gray ストリーム拡張を同梱しています: ユーザークラスが rontolisp:fundamental-*-stream
基底クラスのいずれかを継承し rontolisp:stream-* 総称関数にメソッドを定義すると、
ストリームを取る組み込みはストリームハンドルの代わりにそのインスタンスを渡されたときに
これらのメソッドへディスパッチします。すべてのバックエンド
(インタープリタ、JVM、両 WASM) で動作します。
基底クラスは CL と同じ形の階層を成します: ルートに fundamental-stream、その下に
fundamental-input-stream / fundamental-output-stream、リーフとして
fundamental-character-input-stream / fundamental-character-output-stream /
fundamental-binary-input-stream / fundamental-binary-output-stream
(すべて rontolisp パッケージ)。
| 組み込み | ディスパッチ先 |
|---|---|
write-char | rontolisp:stream-write-char |
write-string, format (ストリーム宛先) | rontolisp:stream-write-string |
princ, prin1, print | 描画したテキストの rontolisp:stream-write-string (print はその後 stream-terpri) |
terpri | rontolisp:stream-terpri (デフォルトメソッドは stream-write-char で改行を書く) |
fresh-line | rontolisp:stream-fresh-line (デフォルトメソッド: stream-start-line-p でなければ stream-terpri) |
write-line | rontolisp:stream-write-string のあと rontolisp:stream-terpri |
force-output / finish-output / clear-output | rontolisp:stream-force-output / -finish-output / -clear-output (デフォルトメソッドは nil を返す) |
close | t を返す — 後述 |
write-byte | rontolisp:stream-write-byte |
read-byte | rontolisp:stream-read-byte |
read-char | rontolisp:stream-read-char |
read-char-no-hang | rontolisp:stream-read-char-no-hang (デフォルトメソッドは stream-read-char そのもの) |
peek-char | rontolisp:stream-peek-char (デフォルトメソッドは 1 文字読んで stream-unread-char で押し戻す)。peek-type の読み飛ばし形式はこれをループします |
unread-char | rontolisp:stream-unread-char (デフォルトメソッドはプロトコルが持つ 1 文字ぶんの押し戻しスロットに保管) |
read-line | rontolisp:stream-read-line (デフォルトメソッドは stream-read-char をループ) |
listen | rontolisp:stream-listen (デフォルトメソッドは nil を返す) |
open-stream-p | t を返します -- close と同じく、プログラムが所有できる名前です |
stream-element-type | character、バイナリ基底クラスなら (unsigned-byte 8)。両方を継承したクラス (バイバレントなストリーム) は character を返します。この答えはどちらのバッファを確保すべきかを示すものだからです。プログラムが所有できる名前です |
read-sequence / write-sequence | rontolisp:stream-read-sequence / -write-sequence (デフォルトメソッドは要素総称関数をループ) |
file-position | rontolisp:stream-file-position。2 引数形式は (setf rontolisp:stream-file-position) ライタ総称関数を呼ぶ |
文字出力ストリームは stream-write-char か stream-write-string のどちらか一方を
定義すれば十分です。それぞれ他方を使ったデフォルトメソッドを持つため、書いたほうから
残りの出力プロトコルが組み上がります (どちらも定義しないのが唯一の壊れた形で、2 つの
デフォルトが互いを呼び合います)。
さらに 2 つ、対応する組み込みは持たないものの行単位の演算子が参照する総称関数があります:
rontolisp:stream-line-column はストリームの現在の桁位置を返し、桁位置を追跡しない
ストリームでは nil (デフォルト) を返します。rontolisp:stream-start-line-p はそこから
答えます。桁位置のないストリームは行頭かどうかを判断できないため、fresh-line は常に
改行を書き込みます。rontolisp:stream-advance-to-column は直接呼ぶプログラムのために
プロトコルを補完します。
Gray ストリームを閉じると t を返し、他には何もしません — 解放するものがないからです。
解放するものがあるストリームは CL 本来の綴り、すなわち close 自身へのメソッドを
書きます:
このメソッドはすべてのバックエンドでディスパッチします。これを定義したプログラムが
close を完全に所有し、Gray のデフォルトは道を譲ります。
文字入力ストリームが定義すべきなのは stream-read-char ただ 1 つです
(バイナリなら stream-read-byte)。読み取り側の残りはすべてその上に書かれています:
stream-read-line と stream-read-sequence はそれをループし、
stream-read-char-no-hang はそれ自体で、stream-peek-char は 1 文字読んでから
stream-unread-char で押し戻します。stream-unread-char の既定メソッドは、その文字を
プロトコルが持つ 1 文字ぶんの押し戻しスロットに保管します。自前でソースを巻き戻せる
クラスは stream-unread-char を定義して押し戻しを自分で所有します — そのときスロットは
一度も書かれません。
読み取り側のメソッドはストリーム終端でキーワード :eof を返します。組み込みはそれを通常の
eof-error-p / eof-value 契約に翻訳します。stream-read-line
は末尾の部分行をその行として返します — :eof は「文字がまったく残っていない」ことを意味します。
stream-write-char だけを定義し、桁位置を追跡することで fresh-line が改行の要否を
判断できるストリーム:
file-position プロトコル付きのバイナリ入力ストリーム:
stream-read-char だけを定義した文字入力ストリームを、読み取りプロトコルの残りで
駆動する例です:
trivial-gray-streams シム
ポータブルなライブラリは処理系独自のプロトコルではなく
trivial-gray-streams
に対して書かれています。rontolisp はポータブル API を上のプロトコルへ適合させる組み込みの
trivial-gray-streams ASDF システムを同梱しています
(システムを参照):
trivial-gray-streams パッケージはすべての基底クラス
(trivial-gray-stream-mixin も含む) とすべての総称関数をミラーします。
stream-read-sequence / stream-write-sequence (stream sequence start end &key)、stream-file-position とその (setf ...) ライタ、そして出力系の
stream-line-column / stream-start-line-p / stream-terpri /
stream-fresh-line / stream-advance-to-column / stream-force-output /
stream-finish-output / stream-clear-output も含まれます — jzon の
:stream ライタ API はこの仕組みで動いており、fast-io や circular-streams
の定義するクラス形状もそのままロードできます。デフォルトは rontolisp プロトコルと同じ
ものなので、trivial-gray-streams:stream-write-char だけを定義したポータブルなクラスでも
上のすべての演算子に応答します。
制限
rontolisp:stream-advance-to-columnはプロトコル総称関数として存在しますが、 どの組み込みもディスパッチしません (formatの~Tは桁位置を参照しません)。- プロトコルの押し戻しは 1 ストリームにつき 1 文字だけを保持します。これは CL が
unread-charに約束している範囲そのものです。プロトコル自身のデフォルトを通る読み取りは すべてこれを消費しますが、stream-read-lineやstream-read-sequenceを丸ごと オーバーライドしたクラスは押し戻しを読み飛ばすので、そうしたクラスはstream-unread-charも定義してください。 input-stream-p/output-stream-pは、クラスごとの述語メソッドではなく、 インスタンスが継承している方向の基底クラスを答えます。fundamental-input-streamの子孫は前者にt、後者にnilを返し、fundamental-streamを直接継承した クラスはどちらにもnilを返します。どちらの名前にもメソッドを定義して 答えを自分のものにできます。- ストリームハンドル — ファイル、文字列入力ストリーム、ソケット — への
unread-charは、ハンドル側の押し戻しに文字を保管し、read-char/peek-char/read-lineがそれを消費します。保持できるのはプロトコル側と同じく 1 ストリームに つき 1 文字で、まだ埋まっている状態での 2 回目のunread-charは通知します。read-byte/read-sequence/readはこのセルを参照しません。 - 読み取り総称関数はプライマリ値のみを返します:
stream-read-lineに(values line missing-newline-p)のペアはなく、:eofが EOF の唯一のシグナルです。 - Gray インスタンスへの
listenはインタープリタと JVM で動作します。Preview 1 WASM バックエンドはあらゆるlisten呼び出しをコンパイル時に拒否します (Gray とは無関係の既存プラットフォーム制限)。 - 境界キーワード付きの
(write-string s instance :start ... :end ...)は境界をインスタンスへディスパッチしません。 - ディスパッチは組み込みの呼び出しサイトで起きます: 第一級値経由の
(funcall #'read-byte instance)はコンパイルバックエンドではディスパッチしません。