gauche.listener
- リスナー ¶このモジュールは、 複数のread-eval-printループ(repl)を並行して動作させるのに便利な機能を提供します。
複数のreplを実現する自明な方法は、セッション毎にスレッドを生成して
各スレッド上でread-eval-print-loop
(eval と repl参照)を
呼ぶことですが、シングルスレッドでの実装が好ましい場合もあります。
例えばアプリケーションがMT-safeでないライブラリに大きく依存していたり、
既にアプリケーションがselectやpollをベースにしたディスパッチメカニズムを
持っているような場合です。
シングルスレッドのselectionベースのアプリケーションでreplを実装するには、
通常、listenしているポートにデータが到着した時に呼ばれるハンドラを登録します。
ハンドラはポートからデータを読み、内部バッファに追加します。
そして内部バッファをスキャンし、データが完全な式を構成した場合は
それをS式として読みだし、評価して結果をポートに流します。
このモジュールの<listener>
クラスはこのハンドラのメカニズムを提供するので、
アプリケーション側はそれを自分のディスパッチメカニズムに登録するだけで済みます。
註:場合によっては、出力もバッファリングする必要があるかもしれませんが、 現在はそれは実装されていません。
{gauche.listener
}
Replセッションの状態を維持するオブジェクトです。
オブジェクトのふるまいをカスタマイズするためにいくつもの外部スロットがあります。
これらのスロットの値は、スロットの名前と同名のキーワード引数を用いて
オブジェクトの構築時に指定することもできますし、オブジェクト構築後に
slot-set!
で設定することもできますが、listener-read-handler
を呼ぶ前に確定していなければなりません。
<listener>
: input-port ¶リスナーが入力を受けとる入力ポートを指定します。デフォルト値は、 オブジェクトが構築された時のカレント入力ポートです。
<listener>
: output-port ¶リスナーが出力をおこなう出力ポートを指定します。デフォルト値は、 オブジェクトが構築された時のカレント出力ポートです。
<listener>
: error-port ¶リスナーのエラーメッセージを出力するポートを指定します。デフォルト値は、 オブジェクトが構築された時のカレントエラー出力ポートです。
<listener>
: reader ¶引数を取らない手続きです。呼び出し時のカレント入力ポートからScheme式を
読み込まなければなりません。デフォルト値はシステムのread
手続きです。
<listener>
: evaluator ¶Scheme式と環境指定子のふたつの引数をとる手続きです。
式を与えられた環境で評価し、0個以上の値を返さなければなりません。
デフォルト値はシステムのeval
手続きです。
<listener>
: printer ¶0以上の引数をとり、カレント出力ポートに印字する手続きです。
デフォルト値は、各値をwrite
で印字したのち、改行する手続きです。
<listener>
: prompter ¶引数をとらない手続きです。カレント出力ポートにプロンプトを
印字しなければなりません。リスナーは、この手続きが面倒を見なくてよいように、
出力をフラッシュします。
デフォルトの手続きは "listener> "
を印字します。
<listener>
: environment ¶式を評価する環境指定子です。デフォルト値は(interaction-environment)
が
返す値です。
<listener>
: finalizer ¶input-port
からEOFが読み込まれたときに呼び出されるサンクです。
finalizer実行中は、現在の入力、出力、エラー出力ポートは、
listener-read-handler
が呼ばれたときのものに戻っています。
このような手続きが必要ない場合は#f
にしておいて構いません。
デフォルトの値は#f
です。
<listener>
: error-handler ¶エラー例外をひとつ引数としてとる手続きです。read-eval-printの最中に
エラーが発生すると、エラーシグナルが発生すのと同じ動的環境で、
呼び出されます。
デフォルト値はエラー例外をreport-error
を使って印字する手続きです。
<listener>
: fatal-handler ¶エラー例外をひとつの引数としてとる手続です。
fatalエラー(詳しい定義については後述)が発生したときに呼び出され
ます。このハンドラが呼び出された場合、リスナーセッションは安全に続ける
ことはできないと思ってください。クライアントへのメッセージを書くこと
もできません。このハンドラはこういう状況でログを残したりリスナーの
クリーンアップするためのものです。fatal-handlerを実行中は現在の
入力、出力、エラー出力はlistener-read-handler
が呼ばれたときのも
のに戻ります。
fatal-handlerが#f
を返す場合、finalizerは後で呼ばれ
ます。これを利用してfinalizerで共通のクリーンアップを実行するよ
うな実装ができます。fatal-handlerが真の値を返した場合には、
finalizerは呼ばれません。
{gauche.listener
}
リスナーのinput-port
から読み込んだデータが正しい時に
呼ばれるサンクを返します。
返されたサンク(readハンドラ)は、以下のように実行されます。
この手続きは最初のプロンプトを印字しないことに
注意してください。これについては後述のlistener-show-prompt
を
参照してください。
input-port
にあるデータを読み、これをリスナーの内部バッファに
連結する。
output-port
へ印字する。
output-port
.
output-port
に印字したのち、
output-port
をフラッシュする。
{gauche.listener
}
リスナーの出力ポートにプロンプトを表示します。表示にはリスナーの
プロンプタ手続きを使います。通常この手続きは最初のプロンプトを
印字するために使います。たとえば、クライアントがリスナーソケットに
接続してきたときです。
{gauche.listener
}
もし、strが完結したS式を含んでいれば#t
を返します。
このユーティリティ手続きは、他の目的にも有用なので、
他の手続きといっしょにエクスポートしてあります。
この手続きは構文のチェックをするだけで、(不正な文字名を含んだり、
登録されていないSRFI-10のタグを含むなど)誤りのある式を判別するわけでは
ないことに注意してください。この手続きは入力が’#<
’文字の
シーケンスを含むと、エラーを発生させます。
エラー状況によりリスナーのエラー処理法が異ります。
SIGPIPE
シグナルがあがった場合
(3) error-handler を実行中に処理未設定エラーが発生した場合。
このような状況が起こると、fatal-handlerが与えられていればそれが
呼ばれます。fatal-handlerが#f
を返すか、
fatal-handlerが与えられていなければ、finalizerが呼ばれます。
gauche.listener
のユーザが
自ら処理しなければなりません。
一般的にはこのような状況はプログラムのバグだと考えるべきです。したがっ て、想定されたエラーとしてfatal-handlerおよびfinalizer内で 捕捉されるべきです。
以下のコード断片は、サーバソケットをオープンし、クライアントが 接続してきたときにSchemeの対話的セッションをオープンするというものです。 (註: このコードはdemo用のコードです。決して、外のネットワークから アクセス可能なマシン上で走らせてはいけません!)
(use gauche.net) (use gauche.selector) (use gauche.listener) (define (scheme-server port) (let ((selector (make <selector>)) (server (make-server-socket 'inet port :reuse-addr? #t)) (cid 0)) (define (accept-handler sock flag) (let* ((client (socket-accept server)) (id cid) (input (socket-input-port client :buffering :none)) (output (socket-output-port client)) (finalize (lambda () (selector-delete! selector input #f #f) (socket-close client) (format #t "client #~a disconnected\n" id))) (listener (make <listener> :input-port input :output-port output :error-port output :prompter (lambda () (format #t "client[~a]> " id)) :finalizer finalize)) (handler (listener-read-handler listener)) ) (format #t "client #~a from ~a\n" cid (socket-address client)) (inc! cid) (listener-show-prompt listener) (selector-add! selector input (lambda _ (handler)) '(r)))) (selector-add! selector (socket-fd server) accept-handler '(r)) (format #t "scheme server started on port ~s\n" port) (do () (#f) (selector-select selector))))