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

9.16 gauche.listener - リスナー

Module: gauche.listener

このモジュールは、 複数のread-eval-printループ(repl)を並行して動作させるのに便利な機能を提供します。

複数のreplを実現する自明な方法は、セッション毎にスレッドを生成して 各スレッド上でread-eval-print-loop (eval と repl参照)を 呼ぶことですが、シングルスレッドでの実装が好ましい場合もあります。 例えばアプリケーションがMT-safeでないライブラリに大きく依存していたり、 既にアプリケーションがselectやpollをベースにしたディスパッチメカニズムを 持っているような場合です。

シングルスレッドのselectionベースのアプリケーションでreplを実装するには、 通常、listenしているポートにデータが到着した時に呼ばれるハンドラを登録します。 ハンドラはポートからデータを読み、内部バッファに追加します。 そして内部バッファをスキャンし、データが完全な式を構成した場合は それをS式として読みだし、評価して結果をポートに流します。 このモジュールの<listener>クラスはこのハンドラのメカニズムを提供するので、 アプリケーション側はそれを自分のディスパッチメカニズムに登録するだけで済みます。

註:場合によっては、出力もバッファリングする必要があるかもしれませんが、 現在はそれは実装されていません。

Listener API

Class: <listener>

{gauche.listener} Replセッションの状態を維持するオブジェクトです。 オブジェクトのふるまいをカスタマイズするためにいくつもの外部スロットがあります。 これらのスロットの値は、スロットの名前と同名のキーワード引数を用いて オブジェクトの構築時に指定することもできますし、オブジェクト構築後に slot-set!で設定することもできますが、listener-read-handler を呼ぶ前に確定していなければなりません。

Instance Variable of <listener>: input-port

リスナーが入力を受けとる入力ポートを指定します。デフォルト値は、 オブジェクトが構築された時のカレント入力ポートです。

Instance Variable of <listener>: output-port

リスナーが出力をおこなう出力ポートを指定します。デフォルト値は、 オブジェクトが構築された時のカレント出力ポートです。

Instance Variable of <listener>: error-port

リスナーのエラーメッセージを出力するポートを指定します。デフォルト値は、 オブジェクトが構築された時のカレントエラー出力ポートです。

Instance Variable of <listener>: reader

引数を取らない手続きです。呼び出し時のカレント入力ポートからScheme式を 読み込まなければなりません。デフォルト値はシステムのread手続きです。

Instance Variable of <listener>: evaluator

Scheme式と環境指定子のふたつの引数をとる手続きです。 式を与えられた環境で評価し、0個以上の値を返さなければなりません。 デフォルト値はシステムのeval手続きです。

Instance Variable of <listener>: printer

0以上の引数をとり、カレント出力ポートに印字する手続きです。 デフォルト値は、各値をwriteで印字したのち、改行する手続きです。

Instance Variable of <listener>: prompter

引数をとらない手続きです。カレント出力ポートにプロンプトを 印字しなければなりません。リスナーは、この手続きが面倒を見なくてよいように、 出力をフラッシュします。 デフォルトの手続きは "listener> " を印字します。

Instance Variable of <listener>: environment

式を評価する環境指定子です。デフォルト値は(interaction-environment)が 返す値です。

Instance Variable of <listener>: finalizer

input-portからEOFが読み込まれたときに呼び出されるサンクです。 finalizer実行中は、現在の入力、出力、エラー出力ポートは、 listener-read-handlerが呼ばれたときのものに戻っています。

このような手続きが必要ない場合は#fにしておいて構いません。 デフォルトの値は#fです。

Instance Variable of <listener>: error-handler

エラー例外をひとつ引数としてとる手続きです。read-eval-printの最中に エラーが発生すると、エラーシグナルが発生すのと同じ動的環境で、 呼び出されます。 デフォルト値はエラー例外をreport-errorを使って印字する手続きです。

Instance Variable of <listener>: fatal-handler

エラー例外をひとつの引数としてとる手続です。 fatalエラー(詳しい定義については後述)が発生したときに呼び出され ます。このハンドラが呼び出された場合、リスナーセッションは安全に続ける ことはできないと思ってください。クライアントへのメッセージを書くこと もできません。このハンドラはこういう状況でログを残したりリスナーの クリーンアップするためのものです。fatal-handlerを実行中は現在の 入力、出力、エラー出力はlistener-read-handlerが呼ばれたときのも のに戻ります。

fatal-handler#fを返す場合、finalizerは後で呼ばれ ます。これを利用してfinalizerで共通のクリーンアップを実行するよ うな実装ができます。fatal-handlerが真の値を返した場合には、 finalizerは呼ばれません。

Method: listener-read-handler (listener <listener>)

{gauche.listener} リスナーのinput-portから読み込んだデータが正しい時に 呼ばれるサンクを返します。

返されたサンク(readハンドラ)は、以下のように実行されます。 この手続きは最初のプロンプトを印字しないことに 注意してください。これについては後述のlistener-show-promptを 参照してください。

  1. input-portにあるデータを読み、これをリスナーの内部バッファに 連結する。
  2. バッファをスキャンして、それが完結したS式であるかを確かめ、 完結していなければ、リターンする。
  3. バッファからそのS式を読み込み、そのバッファからは そのデータを除去する。
  4. そのS式を評価して、結果をoutput-portへ印字する。 output-port.
  5. プロンプタ手続きを使って、プロンプトをoutput-portに印字したのち、 output-portをフラッシュする。
  6. 2から繰り返す。
Method: listener-show-prompt (listener <listener>)

{gauche.listener} リスナーの出力ポートにプロンプトを表示します。表示にはリスナーの プロンプタ手続きを使います。通常この手続きは最初のプロンプトを 印字するために使います。たとえば、クライアントがリスナーソケットに 接続してきたときです。

Function: complete-sexp? str

{gauche.listener} もし、strが完結したS式を含んでいれば#tを返します。 このユーティリティ手続きは、他の目的にも有用なので、 他の手続きといっしょにエクスポートしてあります。

この手続きは構文のチェックをするだけで、(不正な文字名を含んだり、 登録されていないSRFI-10のタグを含むなど)誤りのある式を判別するわけでは ないことに注意してください。この手続きは入力が’#<’文字の シーケンスを含むと、エラーを発生させます。

エラー処理

エラー状況によりリスナーのエラー処理法が異ります。

リスナーの例

以下のコード断片は、サーバソケットをオープンし、クライアントが 接続してきたときに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))))


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