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

9.16 gauche.listener - Listener

Module: gauche.listener

This module provides a convenient way to enable multiple read-eval-print loop (repl) concurrently.

An obvious way to run multiple repls is to use threads; creating as many threads as sessions and calling read-eval-print-loop (see Eval and repl) from each thread. Nevertheless, sometimes single threaded implementation is preferred. For instance, you’re using a library which is not MT-safe, or your application already uses select/poll-based dispatching mechanism.

To implement repl in the single-threaded selection-base application, usually you register a handler that is called when data is available in the listening port. The handler reads the data and add them into a buffer. Then it examines if the data in the buffer consists a complete expression, and if so, it reads the expression from the buffer, evaluates it, then prints the result to the reporting port. The <listener> class in this module provides this handler mechanism, so all you need to do is to register the handler to your dispatching mechanism.

Note: it may also be desirable to buffer the output sometimes, but the current version doesn’t implement it.

Listener API

Class: <listener>

{gauche.listener} An object that maintains the state of a repl session. It has many external slots to customize its behavior. Those slot values can be set at construction time by using the keyword of the same name as the slot, or can be set by slot-set! afterwards. However, most of them should be set before calling listener-read-handler.

Instance Variable of <listener>: input-port

Specifies the input port from which the listener get the input. The default value is the current input port when the object is constructed.

Instance Variable of <listener>: output-port

Specifies the output port to which the listener output will go. The default value is the current output port when the object is constructed.

Instance Variable of <listener>: error-port

Specifies the output port to which the listener’s error messages will go. The default value is the current error port when the object is constructed.

Instance Variable of <listener>: reader

A procedure with no arguments. It should read a Scheme expression from the current input port when called. The default value is system’s read procedure.

Instance Variable of <listener>: evaluator

A procedure that takes two arguments, a Scheme expression and an environment specifier. It should evaluate the expression in the given environment and returns zero or more value(s). The default value is system’s eval procedure.

Instance Variable of <listener>: printer

A procedure that takes zero or more argument(s) and prints them out to the current output port. The default value is a procedure that prints each value by write, followed by a newline.

Instance Variable of <listener>: prompter

A procedure with no arguments. It should prints a prompt to the current output port. The output is flushed by the listener object so this procedure doesn’t need to care about it. The default procedure prints "listener> ".

Instance Variable of <listener>: environment

An environment specifier where the expressions will be evaluated. The default value is the value returned by (interaction-environment).

Instance Variable of <listener>: finalizer

A thunk that will be called when EOF is read from input-port. During the execution of finalizer, the current input, output and error ports are restored to the ones when listener-read-handler is called.

It can be #f if no such procedure is needed. The default value is #f.

Instance Variable of <listener>: error-handler

A procedure that takes one argument, an error exception. It is called when an error occurs during read-eval-print stage, with the same dynamic environment as the error is signaled. The default value is a procedure that simply prints the error exception by report-error.

Instance Variable of <listener>: fatal-handler

A procedure that takes one argument, an error exception. It is called when a fatal error occurred (see below for the precise definition). If this handler is called, you should assume you can no longer continue the listener session safely, even write messages to the client. This handler is to log such condition or to clean up the listener. During the execution of fatal-handler, the current input, output and error ports are restored to the ones when listener-read-handler is called.

If fatal-handler returns #f, finalizer is called afterwards. With this, you can implement a common cleanup work in finalizer. If fatal-handler returns a true value, finalizer will not be called.

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

{gauche.listener} Returns a thunk that is to be called when a data is available from input-port of the listener.

The returned thunk (read handler) does the following steps. Note that the first prompt is not printed by this procedure. See listener-show-prompt below.

  1. Reads available data from input-port and appends it to the listener’s internal buffer.
  2. Scans the buffer to see if it has a complete S-expression. If not, returns.
  3. Reads the S-expression from the buffer. The read data is removed from the buffer.
  4. Evaluates the S-expression, then prints the result to output-port.
  5. Prints the prompt by prompter procedure to output-port, then flush output-port.
  6. Repeats from 2.
Method: listener-show-prompt (listener <listener>)

{gauche.listener} Shows a prompt to the listener’s output port, by using listener’s prompter procedure. Usually you want to use this procedure to print the first prompt, for instance, when the client is connected to the listener socket.

Function: complete-sexp? str

{gauche.listener} Returns #t if str contains a complete S-expression. This utility procedure is exported as well, since it might be useful for other purposes.

Note that this procedure only checks syntax of the expressions, and doesn’t rule out erroneous expressions (such as containing invalid character name, unregistered SRFI-10 tag, etc.). This procedure may raise an error if the input contains ’#<’ character sequence.

Error handling

There are a few error situations the listener handles differently.

Listener example

The following code snippet opens a server socket, and opens a Scheme interactive session when a client is connected. (Note: this code is just for demonstration. Do not run this program on the machine accessible from outside network!)

(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