For Development HEAD DRAFTSearch (procedure/syntax/module):

9.40 gauche.vport - 仮想ポート

Module: gauche.vport

仮想ポートあるいは手続き的ポートとは、その振舞いを Scheme でプログラム可能なポートです。

このモジュールは 2 種類の仮想ポートを提供します。ひとつは、 完全仮想ポートで、すべての I/O 操作でユーザが提供する手続きが 呼出されるものです。もうひとつは、仮想バッファポートで、 I/O 操作は内部バッファ上で行われ、ユーザが提供する手続きは バッファを一杯にするかフラッシュする必要がある場合にのみ 呼出されます。

このモジュールはさらに、ユニフォームベクタにより バックアップされる仮想バッファポートも提供します。 これは仮想ポートの使用例でもあります。

完全仮想ポート

このタイプの仮想ポートは、<virtual-input-port> クラス および <virtual-output-port> クラスで実現されています。 適切なスロットに手続きをセットすることでポートの振舞いをカスタマイズ 可能です。

Class: <virtual-input-port>

{gauche.vport} このクラスのインスタンスは入力ポートとして使えます。 このポートの振舞いはインスタンスのスロットに設定された値に依存します。

まともな入力ポートとして動かすためには、少くとも、getb スロット あるいは getc スロットのどちらか一方は設定しなければなりません。 さもなければ、このポートはすべての入力要求に対して EOF を返します。

Instance Variable of <virtual-input-port>: getb

設定されているのなら、その値は、引数を取らない手続きでなければなりません。 バイナリ入力の要求のたびに、この手続きが呼ばれます。

この手続きは、0 から 255 までの正確な整数を返すか、#fあるいは EOF オブジェクトを返さなければなりません。整数を返す場合には、 それがこのポートから読みとられる値となります。それ以外の値を返す 場合は、このポートは EOF を返します。

このポートが文字入力を要求され、かつ、getc 手続きを持たない場合、 このポートはこの手続きを呼び(複数回の可能性もある)文字全体を構築します。

Instance Variable of <virtual-input-port>: getc

設定されているのなら、その値は、引数を取らない手続きでなければなりません。 文字入力の要求のたびに、この手続きが呼ばれます。

この手続きは文字を返すか、または#fあるいはEOFオブジェクトを返さなければ なりません。文字を返した場合には、それがこのポートから読みとられる値と なります。それ以外の値を返す場合は、このポートは EOF を返します。

このポートがバイナリ入力を要求され、かつ、getb 手続きを持たない場合、 このポートはこの手続きを呼び、文字をバイト列に変換し、それをこのポートから 読みだされる値として使います。

Instance Variable of <virtual-input-port>: gets

設定されているなら、その値は、正の正確な整数の引数をひとつだけとる手続き でなければなりません。ブロックバイナリ入力、たとえば、 read-uvectorなどが要求された場合に、呼出されます。

そしてそれは文字列、 不完全文字列、 #f、 EOF オブジェクトを 返さなくてはなりません. 文字列を返す場合は上記正の正確な整数によって 指定された大きさを越えてはなりません。空文字列、 #f、EOF を 返した場合はポートの終端にたどり着いたものとみなされます。 (注意: 文字列大きさは文字数でなく文字列が占めるバイト数で指定されます。)

この手続きは効率のためにあります。もし、この手続きが用意されて いなければ、このポートは、データブロックを準備するのに getbを 繰り返し呼びます。場合によっては、ブロック入力を用意するほうが はるかに効率的です。(たとえば、メモリチャンクのブロックからの読みだし を行なうような場合です。)

こうした利点を必要としないのなら、このスロットは未設定のままにして おいてもかまいません。

Instance Variable of <virtual-input-port>: ready

設定されているなら、その値は、真偽値をひとつだけとる手続きでなければ なりません。この手続きは、このポートに対して、char-ready? あるいは byte-ready? が呼ばれたときに呼出されます。設定した手続き が返す値が、これらの手続きの結果になります。

char-ready? が呼ばれたのなら、真偽値引数は #t です。 byte-ready? が呼ばれたのなら、真偽値引数は #f になります。

未設定なら、char-ready? および byte-ready? は このポートに対して常に、#t を返します。

Instance Variable of <virtual-input-port>: close

設定されているのなら、その値は引数を取らない手続きでなければなりません。 その手続きはこのポートがクローズされるときに呼びだされ、返り値は 捨てられます。このポートをクローズするときになんらかのアクションが 必要でないのなら、未設定にしておけます。

この手続きはファイナライザから呼ばれることもあり得ます。したがって、 この手続きは慎重に書く必要があります。後述のファイナライズに 関する注意を参照してください。

Instance Variable of <virtual-input-port>: seek

設定されているのなら、その値は、オフセット引数とそれがどこからかを示す引数 の 2つの引数をとる手続きでなければなりません。これらの引数の意味は、 port-seek (ポート共通の操作 参照) のそれと同じです。 この手続きは、次の読み出しが新しい位置から始められるように 内部のリードポインタを調整しなければなりません。そして 更新されたポインタ(ポート先頭からのバイトオフセット) を返さなければなりません。

未設定なら、このポートに対する port-seek および port-tell の呼び出しは #f になります。

この手続きは単に現在の位置を問合せるために、 offset として 0 を、whence として SEEK_CUR を与えて 呼び出すこともあることに注意してください。リードポインタの位置は知って いるが、動かすことができないという場合にも、この手続きを提供することが できます。上のような問合せには、現在位置を返し、そうでない場合には、 #f を返します。

Class: <virtual-output-port>

{gauche.vport} このクラスのインスタンスは出力ポートとして使えます。 このポートの振舞いはインスタンスのスロットに設定された値に依存します。

まともな出力ポートとして動かすためには、少くとも、putb スロット あるいは putc スロットのどちらか一方は設定しなければなりません。

Instance Variable of <virtual-output-port>: putb

設定されているなら、その値は、バイト値(0 から 255までの正確な整数)を ひとつだけ引数としてとる手続きでなければなりません。バイナリ出力 の要求のたびに、この手続きが呼ばれます。この手続きの返り値は無視 されます。

このスロットが未設定の場合、バイナリ出力を要求されると、このポートは <io-unit-error> エラーを発生させることがあります。

Instance Variable of <virtual-output-port>: putc

設定されているなら、それは文字をひとつ引数にとる手続きでなければなりません。 文字出力の要求があるたびにこの手続きがよばれます。この手続きの返り値は 無視されます。

このスロットが未設定であっても、putb スロットが設定されていれば、 この仮想ポートは文字をバイト列に展開してから、putb を呼びだします。

Instance Variable of <virtual-output-port>: puts

設定されていれば、その値は、文字列(不完全なものである可能性もある)を ひとつ引数としてとる手続きでなければなりません。この手続きの返り値は 無視されます。

これは、効率のためにあります。このスロットが未設定であれば、この 仮想ポートは、データのかたまりを出力するために、putb あるいは putc を繰り返し呼びます。もし、ひとかたまりの 出力を効率的に実行可能なコードであれば、この手続きを提供できます。

Instance Variable of <virtual-output-port>: flush

設定されていれば、その値は引数をとらない手続きでなければなりません。 ポートのフラッシュを要求されたときに呼ばれます(たとえば、flush がこのポートに対して呼ばれたとき、あるいは、このポートがクローズ されるとき)。

この手続きは、ポートが何らかのバッファリングをおこなうか、なんらかの 状態を持つような場合に便利です。ポートが状態をともなう操作を行わない のなら、これは未設定にしておけます。

この手続きはファイナライザから呼ばれることがあります。したがって、 特別な注意が必要です。後述のファイナライザに関する注意を 参照してください。

Instance Variable of <virtual-output-port>: close

<virtual-input-port>close スロットと同様です。

Instance Variable of <virtual-output-port>: seek

<virtual-input-port>seek スロットと同様です。

仮想バッファポート

このタイプの仮想ポートは <buffered-input-port> クラス および <buffered-output-port> クラスで実現されています。 適切なスロットに手続きを設定するこでポートの振舞いをカスタマイズ することができます。

これらのポートは、内部バッファを持っており、そのバッファを満たすか フラッシュするかの必要があるときにのみ、Scheme の手続きが呼ばれます。 通常、I/O毎に Scheme の手続きを呼ぶよりも遥かに効率がいいものです。 実際の内部バッファリング機構は、GaucheのファイルI/Oポートと同じです。

これらのポートはバッファとして u8vector を使います。詳細は、 ユニフォームベクタ を参照してください。

Class: <buffered-input-port>

{gauche.vport} このクラスのインスタンスは、入力ポートとして振舞います。 これは以下のようなインスタンススロットを持ちます。 意味のある入力ポートとして使うには少くとも、fill スロットを 設定しなければなりません。

Instance Variable of <buffered-input-port>: fill

設定されているなら、u8vector の引数を一つとる手続きでなければ なりません。そのベクタの最初からデータを満たさなければなりません。 要求されたものよりデータの残りが少ない場合には、 ベクタ全体を満たす必要はありません。しかしながら、 データが残っている場合には少くとも1バイトは満たさなければなりません。 もしデータがまだ利用可能になっていなければ、なにがしかのデータが利用可能に なるまで待たなければなりません。

この手続きは実際に満されたバイト数を返さなければなりません。ポートが EOFに達したことを示すために、0 または EOFオブジェクトを返すこともできます。

Instance Variable of <buffered-input-port>: ready

設定されているなら、引数をとらない手続きでなければなりません。 この手続きは、なにがしかの読み込み用データが利用可能になっていれば 真の値を返し、そうでなければ、#fを返します。完全仮想ポートとはちがい、 バイナリI/Oと文字I/Oを区別する必要はありません。

このスロットが未定義の場合、このポートは常にデータが利用可能になっている とみなされます。

Instance Variable of <buffered-input-port>: close

設定されていれば、引数をとらない手続きでなければなりません。この手続きは 仮想バッファポートがクローズされるときに呼出されます。ポートがクローズ されるときに、なにがしかのクリーンアップを行うのでなければ、設定する必要 はありません。

この手続きは、ファイナライザから呼ばれる可能性がありますので、特別な 注意が必要です。後述のファイナライズに関する注意書きを参照してください。

Instance Variable of <buffered-input-port>: filenum

設定されているなら、このポートの使っているファイルディスクリプタ番号(正確な非負整数) を返す手続きでなければなりません。この手続きは port-file-number をこのポートに対して呼出したときに呼ばれます。

そのようなファイルディスクリプタが存在していなければ、 #f を返すことができます。 あるいは、このスロットを未設定にしておくこともできます。

Instance Variable of <buffered-input-port>: seek

設定されているなら、オフセット引数とどこからかを示す引数の2つをとる 手続きでなければなりません。これは、前述の <virtual-input-port>のseek 手続きと同じように動作します。

この手続きはファイナライザから呼ばれる可能性があり、特別な注意が 必要です。後述のファイナライズに関する注意を参照してください。

これらのスロットの値以外に、当該ポートの内部バッファのサイズを設定する のにはmakeメソッドに:buffer-sizeというキーワード引数で非 負の正確な整数を渡します。:buffer-sizeが省略されるか、0 が渡さ れた場合にはシステムのデフォルトのバッファサイズ(たとえば、8kとか)が使 われます。:buffer-sizeはインスタンススロットではないので、バッ ファポートのインスタンスを生成したあとで設定することはできません。以下 は 64K のバッファを使うバッファの作り方の例です。

(make <buffered-input-port> :buffer-size 65536 :fill my-filler)
Class: <buffered-output-port>

{gauche.vport} このクラスのインスタンスは出力ポートとして振舞います。これは、以下のような インスタンススロットを持ちます。少くとも flush は設定しなければ なりません。

Instance Variable of <buffered-output-port>: flush

設定されているなら、u8vectorのバッファとフラグという2つの 引数をとる手続きでなければなりません。手続きはバッファ内のデータを どこかへ出力しなければならず、実際に出力したバイト数を返します。

フラグが偽なら、この手続きはバッファ全体よりも少い(ただし、最低でも 1バイト)の出力を行ってもかまいません。もしフラグが真ならば、この手続きは、 バッファの内容をすべて出力しなければなりません。

Instance Variable of <buffered-output-port>: close

<buffered-input-port>close スロットと同様です。

Instance Variable of <buffered-output-port>: filenum

<buffered-input-port>filenum スロットと同様です。

Instance Variable of <buffered-output-port>: seek

<buffered-input-port>seek スロットと同様です。

これらのスロットの値以外に、当該ポートの内部バッファのサイズを設定する のにはmakeメソッドに:buffer-sizeというキーワード引数で非 負の正確な整数を渡します。詳しくは前述 <buffered-input-port> の項を見てください。

ユニフォームベクタポート

以下の 2つの手続きは、ユニフォームベクタでバックアップされた、 バッファ入/出力ポートを返します。読み込み元のベクタあるいは 書き出し相手のベクタはどのようなユニフォームベクタであってもかまいませんが、 実際の入出力時にはu8vector にaliasされます (ユニフォームベクタの変換uvector-alias 参照)。

pack/unpack(binary.pack - バイナリデータのパック参照) と一緒に使うと バイナリのデータ構造をパースしたり、構築したりするのに便利です。 また、仮想ポートの使い方の例でもあります。実装法に興味があれば、 ソースツリーの gauche/vport.scm(あるいは ext/vport/vport.scm) を読んでください。

Function: open-input-uvector uvector

{gauche.vport} 与えられたユニフォームベクタ uvectorの最初からその内容を読む 入力ポートを返します。読み込み動作が、uvectorの終端に到達したら EOFが返されます。シーク操作も実装されています。

Function: open-input-bytevector u8vector

[R7RS base] {gauche.vport} open-input-uvectorと似ていますが、引数はu8vectorでなければなりません。 R7RS baseの手続きです。

Function: open-output-uvector :optional uvector :key extendable

{gauche.vport} 与えられた uvector をデータの格納に使うような出力ポートを返します。

uvectorが全て埋まった後、何が起きるかは、extendable引数によって 変わります。もし偽の値(デフォルト)では、更に送られてきたデータは黙って捨てられます。 extendableが真の値であった場合、追加のデータを保持するため 格納領域は自動的に拡張されます。

extendableに真の値を与えた場合、貯められたデータを取り出すには、 下で説明するget-output-uvectorを使ってください。渡したuvector 引数には溢れたデータは格納されないので。

特別な場合として、uvector引数を省略することができます。 この場合、データストレージとしてu8vectorが使われます。 uvector引数を省略した場合、extendable引数を 与えることはできませんが、これは真の値を与えたものとみなされます。 でないと意味がないからです。get-output-uvectorで 貯められたデータを取り出すことができます。

ポートに対してシーク操作も使うことができます。 ただし、whenceにSEEK_ENDを与えた場合の解釈が、extendableかどうかで 変わることに注意してください。extendableなポートでは、 末尾は「それまでに書き込まれたデータのうちもっとも大きなオフセット」の次に 設定されます。uvectorポートを開いてすぐに1バイト書き込んだ状態では、 uvector引数にどんなに大きなベクタを渡していたとしても、SEEK_END の基準点は2バイト目です。一方、extendableでないポートでは、 末尾はそれまでに書き込んだデータによらず、常に与えたuvectorの 末尾の次になります。その場合は、末尾やそれ以降にシークすることはできません (port-seekSEEK_ENDを渡す時、オフセットは必ず負数でなければ なりません)。

Function: open-output-bytevector

[R7RS base] {gauche.vport} open-output-uvectorを引数なしで呼ぶのと同じです。 必要に応じて伸長するu8vectorをバッファに使います。 R7RS baseの手続きです。

Function: get-output-uvector port :key shared

{gauche.vport} portopen-output-uvectorで作られたポートであるなら、 これまでに蓄えられたデータをuvectorとして返します。 portがそうでない場合は#fが返されます。

返り値のベクタは、open-output-uvectorに渡したuvector と同じ型で、実際に書き込まれたデータの最後尾までの範囲になります。 それはopen-output-uvectorに渡されたuvectorより小さいかも しれませんし、ポート作成時にextendableに真の値が渡されていた場合は 元のuvectorよりも大きいかもしれません。

uvectorの型がs8vectoru8vector以外である場合、 書き込まれたデータのうち完全な要素に足りない部分は結果に含まれません。 例えば、s32vectorでポートを作り7バイト書き込んでから get-output-uvectorを呼ぶと、返されるs32vectorは 1要素になります。最後の3バイトは完全な32bit整数を構成しないからです。

デフォルトでは、返り値のベクタは新たにアロケートされたものです。 しかしsharedキーワード引数に真の値を渡すと、可能ならば、 portが使っている格納領域をそのまま参照するベクタが返されます。 このオプションによってコピーを減らすことができますが、 後でportをシークして前に戻って再書き込みすると、 返されたベクタの内容が書き換わってしまう可能性があることに注意してください。

Function: get-output-bytevector port

[R7RS base] {gauche.vport} バイトベクタ出力ポートに出力されたデータをu8vectorとして取り出して返します。 portopen-output-bytevectoropen-output-uvectorで作られたものでなければなりません。 R7RS baseの手続きです。

リストポート

以下の手続きは、文字またはオクテットのリストを入力ポートのソースとして利用するものです。 ある意味、port->list系の手続き(入力ユーティリティ手続き参照)や port->char-lseq系の手続き(遅延シーケンス参照)の逆であると 言えます。

Function: open-input-char-list char-list
Function: open-input-byte-list byte-list

{gauche.vport} 与えられた文字もしくはオクテットのリストをデータ源とする 入力ポートを作って返します。

(read (open-input-char-list '(#\a #\b)))
 ⇒ ab
Function: get-remaining-input-list port

{gauche.vport} portopen-input-char-listopen-input-byte-listで 作られたものであった場合、ポート中のまだ読まれていない部分のリストを返します。 既に全てのデータが読まれていたり、ポートが open-input-char-listopen-input-byte-listで作られたもので なかった場合は、空リストが返されます。

注意: Gaucheは一つのポートからのバイナリ読み出しと文字読み出しを混ぜて使うことを 許しています。文字のリストで作られたリストポートからバイトを読み出したり、 あるいはpeek-byteした場合、ひとつ文字がデータ源から取り出され、 バイト列に分解されてポート内部の一時バッファに置かれます。 その状態でこの手続きを呼び出した場合、バッファ内のバイト列は戻り値には含まれません。

ジェネレータポート

以下の手続きは、文字あるいはオクテットのジェネレータ 入力ポートのソースとして利用するものです。 ある意味、port->char-generator系の手続き (ジェネレータの生成参照)の逆であると言えます。

Function: open-input-char-generator cgen
Function: open-input-byte-generator bgen

{gauche.vport} 与えられたジェネレータをデータ源とする入力ポートを作って 返します。cgenは文字を生成するジェネレータ、 bgenはバイト(オクテット、0から255の間の正確な整数)を 生成するジェネレータでなけばなりません。 ジェネレータが想定される値以外のものを返した場合はエラーが通知されます。

(read (open-input-char-generator (string->generator "foo")))
 ⇒ foo

ジェネレータは本質的に副作用に依存しているオブジェクトなので、 この手続きにcgenbgenを渡したら、その後で それらのジェネレータに触ってはなりません。 もしそれらのジェネレータを使ってしまった場合、以降の動作は未定義となります。

Function: get-remaining-input-generator port

{gauche.vport} portopen-input-char-generatoropen-input-byte-generatorで作られたものであった場合、 残りのデータを生成するジェネレータを返します。 ポートが既にデータを全て消費していた場合は、空のジェネレータが返されます。

残りのデータを生成するジェネレータを取り出したら、 ジェネレータポートからはも読み出してはなりません。 内部状態を共有しているため、予期せぬ結果がもたらされることになるでしょう。 副作用に頼らず安全な振る舞いが必要な場合は、遅延シーケンスと リストポートを使うのが良いでしょう。

アキュムレータポート

アキュムレータはジェネレータの双対です。値を一回にひとつづつ受け取る手続きで、 値の末端はEOFで示されます。 アキュムレータの操作についてはscheme.generator - R7RSジェネレータを参照してください。

以下の手続きは、文字またはバイトを受け取るアキュムレータを出力ポートに変換します。

Function: open-output-char-accumulator acc

{gauche.vport} accは文字を受け取るアキュムレータです。 出力される文字をaccに送る出力ポートを返します。 出力ポートがクローズされると、accにEOFが渡されます。

註: 返される出力ポートにバイナリ出力を行った場合の動作は未定義です。

Function: open-output-char-accumulator acc

{gauche.vport} accはバイト(オクテット)を受け取るアキュムレータです。 出力されるバイトをaccに送る出力ポートを返します。 出力ポートがクローズされると、accにEOFが渡されます。

文字が出力された場合は、Gaucheの内部エンコーディングにしたがってオクテット列に 変換されてaccに送られます。

Function: open-output-accumulator acc

{gauche.vport} accはバイト(オクテット)、文字、文字列を受け取るアキュムレータです。 出力データをaccに送る出力ポートを返します。 出力ポートがクローズされると、accにEOFが渡されます。

ファイナライズに関する注意

クローズされていない仮想ポートがガベージ・コレクションされると、 クローズ手続きが呼ばれます(仮想バッファポートの場合は、 フラッシュ手続きがクローズ手続きの前に呼ばれます)。これは、 そのポートのファイナライザによって行われます。これはガベージ・コレクション 処理の一部ですから(Scheme 手続きそのものはガーベッジ・コレクタの メインパートの外側で呼ばれているのですが、それでも)特別な注意が必要です。



For Development HEAD DRAFTSearch (procedure/syntax/module):
DRAFT