gauche.listener
- 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.
{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
.
<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.
<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.
<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.
<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.
<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.
<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.
<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> "
.
<listener>
: environment ¶An environment specifier where the expressions will be
evaluated. The default value is the value returned by
(interaction-environment)
.
<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
.
<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
.
<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.
{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.
input-port
and appends it to the
listener’s internal buffer.
output-port
.
output-port
,
then flush output-port
.
{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.
{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.
There are a few error situations the listener handles differently.
SIGPIPE
signal is raised during writing to
output-port, or (3) an unhandled error occurred during executing
error-handler.
When this situation happens, the fatal-handler is called
if it is given. If fatal-handler returns #f
, or
fatal-handler isn’t given, finalizer is also called.
gauche.listener
.
Generally this situation should be considered as a bug of the program; you should make sure to catch foreseeable errors within fatal-handler and finalizer.
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))))