General Documentation
PS3I
Revision: 1.20
Christian Queinnec
Université Paris 6 --- Pierre et Marie Curie
LIP6, 4 place Jussieu, 75252 Paris Cedex
France -- Email: Christian.Queinnec@lip6.fr
This document presents PS3I, the Persistent Server-Side Scheme
Interpreter. PS3I is a nearly
R4RS
-compliant Scheme
implementation, written in Java, multi-users, multi-threaded and aimed
to run on (Web-)servers (as servlets). PS3I is pronounced
``psssssee'', has been written by
Christian Queinnec
and is available under
Gnu General Public License
.
1 History
In August 1996, I designed
Jaja
as a small runtime library,
written in Java. This runtime was needed to run programs compiled by a
Scheme to Java compiler obtained by retargetting a Scheme to C
compiler described in [Que96a]. Slightly after, I expanded
Jaja in order to also offer a Scheme interpreter written directly in
Java (and not obtained through compilation since I wanted to teach the
straight implementation of such an interpreter). This was the first
public appearance of Jaja (whose name was coined by
Emmanuel Chailloux
: this name is a slang word for
wine (usually not necessarily of a good quality)). Jaja could be used
as a text-only interpreter and as an applet.
In August 1997, I added some new features: Jaja uses ``worlds'' to
mean global environments. Multiple users may concurrently run a single
(re-entrant) Jaja interpreter, without interference, within their own
world. First, they ask for the creation of a world then, they send
expressions to be evaluated in that world. The Jaja evaluation engine
may be used via RMI so multiple users may also share a remote
world. On the side of the language, I added some primitives for
concurrency (fork and suicide, see
[QD93]), for anomaly handling (monitor and
diagnose see [Que96a]), dynamic variables (mainly to
implement the dynamically-scoped functions that deal with the current
input/output ports), a tower of macroexpanders (see
[Que96b]), etc. All these things went in the distribution
undocumented. On the back side, continuations only had a dynamic
extent and Jaja still offers a very reduced tower of numbers.
During the year 1999, I was designing and programming an
educational CDROM to teach the
C programming language and I wanted to introduce the notion of
``path''. A path specifies which lectures notes and which exercises
the student must read or perform. Since it was possible (and often
suggested) to divert from the path (i.e., browse other pages, use a
search engine or surf the web) I wanted to offer, in the navigation
bar, a ``next'' button allowing the student to be reset on his/her
path in just one click. When I realize that this ``next'' button was
no more than a disguised invocation of the continuation of the path, I
had a perfect solution. It allowed me to specify a path as a Scheme
program and use the full power of the language. In other words, I was
able to offer sophisticated paths not only sequential or conditional
(a quizz may be viewed as a kind of conditional form) but also random
or, freshly adapted or, history based paths, see [Que00b])
In February 1999, I then decide to write a new Scheme interpreter with
non-restricted continuations in order to use continuations for a the
next version of the CDROM [CQS00, Que00a]. This
interpreter had to run as a servlet in the server hence its name of
PS3I. I only reuse the code of the read function from Jaja
and, inspired by
SILK
from Peter Norvig, I decided to
make Scheme and Java more intimate. This turns to be an excellent idea
as may be seen in the boot file. PS3I is the name of the new
distribution and this report is the associated documentation.
2 How to get and run PS3I
The PS3I system is available via its
home page
. PS3I is
available as a
jar file
containing source,
compiled, javadoc and other documentation (such as the present
one). PS3I requires other GPL packages to run, these are:
Alternatively, you may download PS3I with these additional
packages bundled in a
single jar
.
After download and since there is more than one way to use
PS3I, please continue reading.
2.1 Textual mode
You may use PS3I as a text-only single-user interpreter that
evaluates the expressions from its standard input stream and prints
the results on its standard output stream. This interpreter stops when
the input stream ends.
The PS3I system requires at least Java 1.2 to run (due to the
immature nature of serialization as offered by Java 1.1). You may
start a textual version of PS3I with one of the two next commands
(see appendix A for options). While tested under
Unix, it should probably be invoked similarly under DOS.
java -cp ps3i+.jar fr.lip6.qnc.ps3i.TopLevel
java -jar ps3i+.jar
If you use the raw ps3i.jar, it is required that the
additional packages are available, for instance, in your
CLASSPATH environment variable. This will look (in
bash) as (where /JavaStuff is where you
downloaded these additional package):
export CLASSPATH=${CLASSPATH}:/JavaStuff/gnu.regexp-1.0.8/classes
export CLASSPATH=${CLASSPATH}:/JavaStuff/java-getopt-1.0.8.jar
export CLASSPATH=${CLASSPATH}:`pwd`/ps3i-latest.jar
java fr.lip6.qnc.ps3i.TopLevel
Without the right command options, you only have a bare interpreter
that only knows of special forms and some rare technical functions.
? cons
*** PS3I Anomaly[fr.lip6.qnc.ps3i.EvaluationAnomaly](UnboundVariable)
Culprits:
[0] cons
? ((lambda (x) x) 1)
= 1
With the right options (see Section A), you may
have access to the full R4RS language including concurrent features
(see Section 3). If you evaluate a command that forks
threads, final values will be printed as soon as they are obtained by
the toplevel. The toplevel will restart a new iteration when all
threads of the preceding iteration are finished. For instance:
PS3I? (* 2 (fork 3 4))
PS3I= 6
PS3I= 8
PS3I?
The interpreter ends when there is nothing left to read (CTRL D, on
Unix, may help there).
2.2 GUI mode
A second way to use the PS3I system is to use it from an applet.
This part will be completed.
I initially thought of adapting the applet of Jaja but prefer to
devote time to the servlet mode.
2.3 Servlet mode
The PS3I system may also be run in a servlet. Some servlets along
which the PS3I and the LAML servlets, are provided
in the PS3I distribution. These servlets comply with the
servlet 2.2 specification
, they were tested with
Tomcat 3.0.
The LAML servlet serves pages written in Scheme. They are
recognized since they are stored as files with the .laml
extension.
The PS3I servlet offers a web-based interactive Scheme
interpreter.
More on servlets in Section 5.
3 PS3I Features
PS3I is a regular Scheme interpreter, nearly fully R4RS compliant
(nearly since I did not take care to verify). It offers
non-restricted continuations (it is written in Java with explicit
continuations i.e., in CPS style). PS3I is a pure interpreter that
does not compile expressions before evaluating them therefore it is
not very fast. Another major source of inefficiency comes from the
binding with Java which uses a lot the Java reflection facilities.
There are at least two kind of predefined procedures in the Scheme
programming language. Some procedures such as map or
display may be entirely written in Scheme, others are written
directly in the language used for the implementation of the
interpreter. This may occur for various reasons: some primitives are
the predicates, constructors or accessors of some particular data types
(for instance vector?, vector, vector-ref,
vector-set!), other primitives must be hard-coded to be not
completely unefficient (this is the case for arithmetic functions for
instance), yet others manage second-class entities (dynamic
environments for instance).
I decided to write only a few primitives in Java. The read
function is able to convert characters into S-expressions, it has to
know about symbols, pairs, vectors etc. For bootstrap reasons, you
must be able to read the Scheme definitions of the functions
you miss so read is written in Java (and in fact, this is the
only piece of code I reused from Jaja).
This attitude finally yields a very tiny Scheme interpreter where most
of the initial library is defined in Scheme (the predefined primitive
environment only contains current-output-port,
current-input-port,
dynamic, dynamic-bind,
thread+offspring-get, thread+offspring-put,
current-world,
load, eval, expand,
diagnose, apply, call/cc and some
other arithmetic functions). This approach is reminiscent of the
MetaVLisp project led by Emmanuel Saint-James circa 1986.
3.1 PS3I primitives types
All Java values are legal first-class PS3I values. Conversely, as
in Silk, Scheme values should be, as much as possible, represented by
Java values. Table 1 shows how Scheme values are
mapped onto Java values.
() |
null |
symbol |
fr.lip6.qnc.ps3i.Symbol |
pair |
fr.lip6.qnc.ps3i.Pair |
integer |
java.lang.Integer |
real |
java.lang.Double |
boolean |
java.lang.Boolean |
string |
java.lang.String or java.lang.StringBuffer |
vector |
java.lang.Object[] |
procedure |
fr.lip6.qnc.ps3i.Invokable |
continuation |
fr.lip6.qnc.ps3i.Continuable |
output port |
java.io.Writer |
input port |
fr.lip6.qnc.ps3i.SexprReadable |
Table 1 : PS
3I values wrt Java classes/interfaces
Note that, in Table 1, strings have two alternate
representations (Silk chose char[] instead). This is not a
problem since most Java methods may equally take String or
StringBuffer as arguments. It also allows to turn implicitly quoted
strings (such as "foo") into String whereas
constructed strings (via make-string for instance) are
instance of StringBuffer. These strings do have a lot more
properties and capabilities than pure Scheme strings but this not
forbidden by R4RS.
Note also that the interpreter uses interfaces for functions and
continuations, this allow the user to create new classes implementing
these interfaces whose instances are recognized as regular functions
or continuations.
When started, PS3I may be told to read some boot files where it
finds missing features. Currently the boot file (the
fr/lip6/qnc/ps3i/ps3i.scm file) contains 216 functions
including a traditional macro-expander. Among these functions are
functions that simply name a Java method. A new syntax,
#@kind:name@ or
#@kind:name(name,name...)@,
has been invented to name Java entities and to make direct use of
them. The value of such an expression is something you may invoke from
Scheme (technically this is an instance of the Invokable
interface). The kind may be one of instanceof,
send, new, newArray, field,
method, staticMethod (case is insensitive for this
parameter). The kind parameter specifies what type of invokable
function will be returned: a predicate, a method invoker, a
constructor, an accessor (a reader or a writer depending on the
arity), a method or a static method.
Here is an excerpt from the standard boot file:
(set! boolean? #@instanceof:java.lang.Boolean@)
(set! cons #@new:fr.lip6.qnc.ps3i.Pair@)
(set! car #@method:fr.lip6.qnc.ps3i.Pair.getCar@)
(set! eq? #@staticMethod:fr.lip6.qnc.ps3i.Function.eq@)
(set! eof-object?
((lambda (eof)
(lambda (x) (eq? x eof)) )
(#@field:fr.lip6.qnc.ps3i.SexprReadable.EOF@) ) )
The first definitions are simple. The eq? predicate is
defined as a particular PS3I method because I don't know of any
standard method in Java that corresponds to the ==
operator. The eq method just wraps this Java test. The last
definition extracts, from an interface, a constant that signifies the
end of a file.
When a method is overloaded, you may name the classes of the arguments
in order to precise which method you want. For instance, since there
are more than one method named write in the
java.io.Writer class, the boot file contains:
(set! display
(lambda (o . port)
((lambda (port)
(#@method:java.io.Writer.write(java.lang.String)@
port (if (null? o)
"()"
(#@method:java.lang.Object.toString()@ o) ) ) )
(if (pair? port) (car port) (current-output-port)) ) ) )
Currently, among primitive functions written in Java are
integer->char, char->integer, make-string,
open-input-file, open-output-file, eq?,
=, <, +, -, *,
/, modulo, remainder.
All other functions are defined in Scheme in the boot file. Half of
them use the #@...@ syntax.
3.1.1 The #@...@ syntax
Here is the complete description of the #@...@
syntax. This syntax is equivalent to a symbol whose value is the
intended Java object. The first word (before the colon) is
case-insensitive; it specifies the type of the intended Java
object. This word may only be one of
instanceof, new, newArray,
field, method, staticMethod or
send.
After the colon are the parameters naming the intended Java
object. Most often this is a fully qualified class or method name. The
relevant class will be automatically loaded on the fly. If the name is
ambiguous in case of overloaded methods for instance, then the fully
qualified names of the class of the arguments are required for
disambiguation. Any ambiguity in the #@...@ syntax yields
a read error that is, these syntaxes are resolved at
read-time (and not at run-time).
field |
fully qualified (static or not) field name |
instanceof |
fully qualified class name |
new |
fully qualified class name |
fully qualified argument class names |
newArray |
fully qualified class name |
method |
fully qualified method name |
fully qualified argument class names |
staticMethod |
fully qualified static method name |
fully qualified argument class names |
send |
method name |
The #@field:...@ syntax names a (possibly static) field
of a class. If the field is static then its value is a nullary
accessor function that returns the value of the specified static
field. If the field is not static then its value is a unary accessor
function that takes an instance and returns the value of the specified
field.
The #@instanceof:...@ syntax names a unary predicate that
recognizes whether its argument belongs to the specified class.
The #@new:...@ syntax names a constructor function that
will create one instance of the specified class. The generated
constructor has the arity of the specified constructor. If the
constructor is overloaded then it is necessary to disambiguate it
otherwise a read-time error will be raised.
The #@newArray:...@ syntax names a constructor of an
array of the specified class. The generated constructor is unary and
takes the size of the array to create as argument.
The #@method:...@ syntax names a non static method from a
fully qualified class. The generated function has the arity of the
specified method plus one. It takes as first argument the instance
that will act as the receiver and as next arguments the arguments of
the method. If the method is overloaded then it is necessary to
disambiguate it otherwise a read-time error will be raised.
Pay attention to the following Java peculiarity. Say A is a class
with a method m and B is a subclass of A overriding m. Suppose
b to be an instance of B then what is the meaning of the following
expression ?
(#@method:A.m@ b)
This form will in fact apply B.m since the reflection API does not
allow to directly invoke a precise method on an instance. The m
message is sent to b which in turn invokes the most specialized
method that is B.m.
The #@staticMethod:...@ syntax names a static method from
a fully qualified class. The generated function has the arity of the
specified static method. If the static method is overloaded then it is
necessary to disambiguate it otherwise a read-time error will
be raised.
The #@send:...@ syntax takes a message name as second
parameter. It generates a function that will send the specified
message to a Java object with the other arguments as arguments. The
precise (non static) method to invoke is looked up at run-time (this
will raise an exception if no such method is found) according to the
actual classes of the arguments.
Here is an example:
(set! substring
(lambda (s start end)
(if (#@instanceof:java.lang.String@ s)
(#@send:substring@ s start end)
(if (#@instanceof:java.lang.StringBuffer@ s)
(#@send:substring@ s start end)
(if (#@instanceof:fr.lip6.qnc.ps3i.ConcatenatedString@ o)
(#@send:substring@ s start end)
(error (quote substring) "Not a string" s) ) ) ) ) )
Disambiguation is problematic. Consider for instance a method
foo(int) from a foo(Integer) method. The
reflection layer confuses them.
3.1.2 The Silk syntax
Silk introduced another very good idea described in
[AHN00] that of using the
Java dot in Scheme
names. Scheme
symbols with a dot are interpreted specially. A slightly different
version was adopted in PS3I and was first implemented by
Arnaud Thiefaine and Benoit Blancard.
3.2 Hints about the implementation
PS3I also introduces some important interfaces that helps
interfacing Scheme and Java (see also the javadoc generated
documentation in the doc subdirectory). The main interfaces are:
-
the World interface defines a global environment.
- the Evaluation interface defines a unit of
evaluation. You create it after a world, you stuff it with as
many programs you want, then you start the evaluation of all
these gathered expressions. Evaluation are somewhat
similar to the groups of [MQ97].
A running Evaluation is listened to by
EvaluationListeners.
- the EvaluationListener interface specifies how one may
monitor an Evaluation. Such a listener is notified of
any finally computed values, any raised anomalies as well as
when the evaluation is finished. An
EvaluationListener is a generalization of a
toplevel in presence of concurrency.
- the SexprReadable interface specifies input streams
that are to be read as sequences of S-expressions.
Other interfaces are more related to the evaluation process and the
Scheme entities that contribute to it. These interfaces allow to
change many aspects of the implementation while not touching the
implementation of the interpretation process.
-
the Evaluable interface specifies how a Java
object may be eval-uated in parameter position or
perform-ed in functional position.
- the Invokable interface specifies how a Java object may
be invoked when in functional position.
- the Continuable interface specifies how a Java object
may be considered as a continuation and can be
resume-d,
- the Environment interface specifies how a Java object
may be considered as a lexical environment through which you
can lookup or update variables, you may also
extend an environment binding variables and values.
3.3 How to use PS3I as a component
If you want to put PS3I to work as a component, here is the basic
theory.
-
At the beginning, you have nothing but an intepreter that only
knows of special forms and some rare functions.
- You create a World: a BaseWorld or a
HandyWorld, the latter is easier but the former
allows much more precise handling. You may create more than
one World: they are normally separate (except if you
do weird things such as sharing global
environments). Worlds are serializable hence possibly
persistent.
A World represents a global environment.
- You create an Evaluation in that World with
the createEvaluation method. An Evaluation
is not serializable: it only contains some threads (paused or
awaked) to evaluate some programs. These threads are created
as you add S-expressions to evaluate, files to load, streams
to evaluate to the Evaluation. You may create more
than one Evaluation in one World.
An Evaluation represents a bunch of transient
activities (Java threads) that should run together. Many
different Evaluations may run concurrently in a same
World.
- You create an EvaluationListener to listen to the
previous Evaluation. You may attach more than one
listener to a given Evaluation. An Evaluation is
listened to by an EvaluationListener that will be
notified of any resulting value, uncaught exception or
exhaustion of threads (i.e., when no more threads are running
in the Evaluation).
- You finally run the Evaluation and get the
results via the EvaluationListener. This will run the
accumulated threads stored in the Evaluation.
I will use a pool of threads soon to lessen the cost of
threads creation.
The basic theory is complex due to the conjunct presence of
continuations, concurrency and toplevel. You may alternatively use a
HandyWorld that offers methods with reasonable defaults that
allow you to easily evaluate expressions in a World without
taking care of EvaluationListeners listening to
Evaluations.
4 The PS3I language
PS3I roughly offers R4RS as well as some supplementary features.
4.1 Additional special forms
-
(uninitialized-let (variable ...) body)
This form allows to create uninitialized local variables whose scope
is the body of the form. If an uninitialized variable is read before
being assigned, an anomaly is diagnosed. This special form allows to
implement letrec as a regular macro (otherwise
letrec must be hard-coded as a special form).
- (fork expression ...)
This form creates as many new threads as there are expressions.
All these expressions are evaluated with the continuation of the
fork form thus providing the ability of a generator. For
instance, (+ 1 (fork 10 11)) returns concurrently (not
necessarily in this order) 11 and 12. The fork special form
creates concurrency that may be ended with the suicide
function.
- (monitor handler expression ...)
See [Que96a] for details.
This form evaluates its first parameter (a binary function known as an
anomaly handler) then evaluates the remaining expressions under the
protection of that handler. An anomaly may be raised via the
diagnose binary function which takes a value (the anomaly)
and a boolean telling whether or not the anomaly is continuable. The
two arguments of the diagnose function are given to the
closer (in the current dynamic environment) handler established vith
monitor. This invokation is protected by the next
dynamically encloding handler. The initial handler is provided by
PS3I.
If the handler returns and the anomaly is continuable, the returned
value becomes the value of the diagnose call. If the handler
returns and the anomaly is not continuable, an anomaly is signalled to
the next anomaly handler.
4.2 Additional functions
Some primitive non-R4RS functions exist as well.
-
(suicide) kills the current thread. New threads are
created with the fork special form.
- (dynamic name) This function looks, in the
current dynamic environment, for the value associated to
name (most often a symbol but not necessarily so).
- (dynamic-bind name value thunk)
This function binds its first argument (most often a symbol) to
its second argument during the invocation of its third argument:
a thunk. The dynamic binding only exists while the thunk is
invoked. Continuations capture the dynamic environment. Current
input and output ports are stored as dynamic bindings. Dynamic
bindings are immutable (there is no dynamic-set!
form but you may bind a name to a mutable value).
- (eval value) evaluates value in the
global environment. This evaluation takes place in the current
World. This is raw eval function that does not expand
its argument first. If you want to evaluate an expanded program,
expand it yourself with the expand function.
- (expand value) expands a program with the
current expander. By default, this is the identity but the usual
boot file (the ps3i.scm file) redefines the expander.
- (load url) reads the content of an URL, gathers
the expressions in a single begin form, expands it with
the current expander then evaluates it.
- (diagnose anomaly continuability)
raises an anomaly and specifies as well whether the anomaly is
continuable or not. See the monitor special form above.
- (current-world) returns the current World within
which the current Evaluation takes place.
- (thread+offspring-get key default-value)
Another type of scope exists besides lexical and dynamic scope:
this is the thread+offspring scope. If you place a value under a
key in that scope (with thread+offspring-put), you may
retrieve it anytime in the same thread or anytime in any spawned
thread of the offspring, see [Que00b] for
details. If the key is not bound, the
default-value is returned instead.
- (thread+offspring-put key value) associates a
value to a key in the thread+offspring scope. This environment
is mutable.
4.3 File inclusion
A curious syntax exists that is processed at read-time. When the
read function reads something like #`url`
then this token is replaced by the content of the url. This
syntax is convenient to share included files.
Some R4RS primitives do not exist in all contexts. For instance,
reading and printing depend on the presence of standard input/output
ports which are initially set up in the dynamic environment. These
ports may be absent in some contexts (in the LAML servlet for
instance). An input port should be a SexprReadable stream,
an output port is a simple Writer stream and printing is done with the
toString method.
Some functions such as load take an URL that may be
relative. A configurable way to decode that relative URL exists.
To be described.
5 Servlets
You need to configure your servlet container to accept the PS3I
servlets and this depends on your servlet container.
I just tell how to do it with Tomcat 3.0. First download
Tomcat 3.0 and install it.
In the directory served by Tomcat, you will find a WEB-INF
directory that contains a web.xml file and a lib
subdirectory. Place the ps3i.jar file in that lib
subdirectory then edit the web.xml file. There are two things
to specify:
-
introduce the servlets and their properties file,
- tell which pathnames trigger them.
For Tomcat 3.0, these lines have to be inserted in the
web.xml file in between the <web-app>
tags.
<servlet>
<servlet-name>LamlPage</servlet-name>
<servlet-class>fr.lip6.qnc.ps3i.servlet.LAMLServlet</servlet-class>
<description>Scheme Server Pages</description>
<init-param>
<param-name>config.file</param-name>
<param-value><!-- tell where is the properties file --></param-value>
<description>The configuration for Laml pages</description>
</init-param>
</servlet>
<servlet>
<servlet-name>PS3IServlet</servlet-name>
<servlet-class>fr.lip6.qnc.ps3i.servlet.PS3IServlet</servlet-class>
<description>Web Interactive Scheme Interpreter</description>
<init-param>
<param-name>config.file</param-name>
<param-value><!-- tell where is the properties file --></param-value>
<description>The configuration for the PS3I interpreter</description>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>LamlPage</servlet-name>
<url-pattern>*.laml</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>PS3IServlet</servlet-name>
<url-pattern>/ps3i/*</url-pattern>
</servlet-mapping>
Starts the servlet container and check these servlets with the
following URLs:
http://127.0.0.1:8080/ps3i/
http://127.0.0.1:8080/fr/lip6/qnc/ps3i/servlet/demo/demo.laml
Adapt those URLs to the machine (127.0.0.1) where you run your servlet
container and to the port the servlet container is listening to
(8080).
5.1 The LAML servlet
The LAML servlet (standing for Lisp Abstracted Markup Language
[Nør99] a concept invented by
Kurt Nørmark
) serves files whose suffix is .laml. These
files are written in Scheme and generates HTML. They are the
equivalent of
Java Server PagesTM
except for the language which is
Scheme and the evaluation mode which is interpretation.
All LAML pages are evaluated in a fresh World. This World pre-loads
the R4RS environment plus the laml.scm utilities that nearly
defines all the basic usual HTML tags. Look at this file if you want
to add your own tags. The World specified by the way the servlet is
configured is only built once afterthat it is made immutable and
cloned for every served page.
Here is a small example of a LAML file where you see the overall
style. HTML tags are produced via functions taking texts maybe
preceded by some options (a keyword (that is, a symbol whose name ends
with a colon) followed by a value). Texts may be any Java values that
may be turned into a String.
(let ((the-title "Hello LAML"))
(html
(head (title the-title))
(body bgcolor: 'beige
(h1 the-title)
"Hello. "
(p align: 'center
(a href: "http://videoc.lip6.fr/"
"See the VideoC Project." ) ) ) ) )
Similarly to JSP, some predefined Scheme global variables are preset.
These are resp. request, response, page,
world, session, config, out and
application to access the current request (and its parameters
filled in by a form for instance), response (to set some additional
headers for instance), the current servlet generating the page, the
PS3I current world, the session object (to store information
relative to the user), the servlet configuration object, the output
stream and the servlet context object.
LAML pages are interpreted rather than compiled to Java as JSP are.
It is therefore easier to tune up LAML pages since they are reloaded
at every change (their expanded Scheme code is cached to improve
speed).
5.2 The XML servlet
This servlet serves files with an .xml extension as if
they were written in Scheme. This is based on the fact that an XML
file has the structure of an S-expression with merrily-named
parentheses. First the file is read as an S-expression, then that
S-expression is evaluated and yield an HTML string that it sent back
to the HTTP client. Pretty simple, is not it ?
The XML servlet first creates an immutable World according to the way
it is configured. This immutable World is made immutable then cloned
for every served pages. This World should contain the libraries that
are required to process the S-expressions.
An XML paragraph starting with an <tag> and ending with
the appropriate tag is read as an S-expression whose car is the symbol
tag. If there are some attributes in a tag, the name of the
attribute is converted into a keyword (a symbol with a trailing colon)
followed by the value of the attribute. Here follows an example of a
conversion.
<start a="12 3">
<in><foo>bar</foo>
<foo a="blah" b="blah">foo</foo></in>
</start>
This xml snippet is converted into the rather compact S-expression:
(start a: "12 3"
(in (foo "bar")
(foo a: "blah" b: "blah") ) )
5.3 The PS3I servlet
This servlet provides the PS3I interpreter as a servlet. It
analyses URLs and evaluates an expression or resumes a continuation as
told. Suppose that this servlet is triggered by the /ps3i
path then the two following URLs are possible:
-
/ps3i/eval should be accompanied with a parameter named
expression that contains a textual sequence of Scheme
expresssions. These expressions are read, gathered in a
begin form then evaluated.
- /ps3i/resume should be accompanied with at least one
parameter named continuation that hold the name of a
continuation stored in the session object associated to the
current user (no exchange of continuations between users for
now). An additional parameter may be present: the
value parameter tells which value to resume the
continuation with otherwise the continuation is resumed with
the current request object.
Parameters may be set in line (if they are not too long) in the URL
(submitted via the GET method) as follows:
http://localhost:8080/ps3i/eval?expression=(list+1+2)
They may be more conveniently sent (via the POST method) from a form
(use a LAML page for that).
For any evaluation request, a fresh World is created with the R4RS
environment. For a resumption request, the world of the creator of the
continuation is re-used therefore it is possible to share information
through the global environment of the current World (or in the
continuation itself). It is possible to use the continuation more than
once even in parallel, see [Que00b] for more theoretical
considerations.
A demonstration is present in the distribution, just ask for the
/ps3i url.
5.4 A Commented Example
The following example uses LAML pages as well as the PS3I servlet.
It may be obtained with the following URL (details may
change across distributions):
http://127.0.0.1:19991/fr/lip6/qnc/ps3i/servlet/intro.laml
When connecting to the PS3I servlet, the first screen may be seen
on Figure 1. You have to wait a little for the
predefined libraries to be loaded.
Just hit the ``eval'' button to start the evaluation. The evaluation
is a regular factorial except that
-
it requests the number on which to apply the factorial function,
- the factorial of 1 is defined to be read from the user,
- a trace is produced before and after evaluation.
You should then get Figure 2.
Suppose you want to know the factorial of 7, you just type that number
and resume the evaluation with the ``resume'' button. You should
obtain a third screen similar to the one in Figure 3.
Note now the accumulated printing produced by the trace. It is now
time to tell PS3I what is the value of (fact 1). Just for
fun, 2 is typed and the ``resume'' button is hit. This yields to
Figure 4.
The final result is 10080. Note the accumulated printing produced by
the final trace.
So far, this example is simple but since PS3I uses full
continuations you may use the ``back'' button of your browser and
return to Figure 3, change your answer and watch for
the new answer. Furthermore if you use the old Mosaic browser or MS
IExplorer, you may clone the final window, perform ``back'' on it, and
``back, back'' on the cloned window then, with a really quick mouse
move, click ``resume'' on the two windows: you now have two concurrent
requests in the server. Therefore your browser is a new special form
(alike
fork) that creates concurrency. Of course the ``stop'' button
is kind of analog to the suicide function. In both cases,
concurrency and task creation/destruction is a consequence of the
features of your browser. Never thought of a browser as a concurrency
operator !?
I also defend that these uses of continuations form very good examples
of what continuations are. Moreover, the second example is the only
interesting case I have been looking for years of a continuation which
is invoked more than once in an understandable way (by me and by
students).
To have full continuations on the server-side is really desirable for
several reasons.
-
to have continuations gives a default behavior to the server
when users come back and resumes old computations,
- to have a fork special form along with continuations
allow to protect the code from the weird effects of
browser-induced concurrency or multiple returns,
- to have first-class continuations allows the programmer to
reason at the right level and write whatever behavior is
thought useful. It is possible to always accept the last
answer to a question and kill the threads that depend on
previous answers. It is possible to only accept the first
answer and ignore any other answer. These behaviors may be
appropriately wrapped inside macros.
6 Conclusions
PS3I is not fast but offers full continuations, persistent global
environments, servlet support, Scheme server pages, etc. See the demos
bundled with PS3I!
Références
- [AHN00]
-
Kenneth R Anderson, Timothy J Hickey, and Peter Norvig.
Silk -- a playful blend of scheme and java.
In Matthias Felleisen, editor, Proceedings of the Workshop on
Scheme and Functional Programming, number Rice University COMP TR00-368,
pages 13--22, Montréal (Canada), September 2000.
- [CQS00]
-
Claire Cazes, Christian Queinnec, and Chantal Steinberg.
Enseignement du langage C à l'aide d'un cédérom et d'un
site -- Mise en oeuvre et observation.
Troyes (France), Atelier TICE, October 2000.
- [MQ97]
-
Luc Moreau and Christian Queinnec.
Design and semantics of quantum: a language to control resource
consumption in distributed computing.
In Usenix Conference on Domain Specific Language, DSL'97, pages
183--197, Santa-Barbara (California, USA), October 1997.
- [Nør99]
-
Kurt Nørmark.
Using lisp as a markup language -- the laml approach.
In European Lisp User Group Meeting, Amsterdam, Holland, 1999.
- [QD93]
-
Christian Queinnec and David De Roure.
Design of a concurrent and distributed language.
In Robert H Halstead Jr and Takayasu Ito, editors, Parallel
Symbolic Computing: Languages, Systems, and Applications, (US/Japan Workshop
Proceedings), volume Lecture Notes in Computer Science 748, pages 234--259,
Boston (Massachussetts USA), October 1993.
- [Que96a]
-
Christian Queinnec.
Lisp in Small Pieces.
Cambridge University Press, 1996.
- [Que96b]
-
Christian Queinnec.
Macroexpansion reflective tower.
In Gregor Kiczales, editor, Proceedings of the Reflection'96
Conference, pages 93--104, San Francisco (California, USA), April 1996.
- [Que00a]
-
Christian Queinnec.
Enseignement du langage C à l'aide d'un cédérom et d'un
site -- Architecture logicielle.
In Colloque international -- Technologie de l'Information et de
la Communication dans les Enseignements d'ingénieurs et dans l'industrie --
TICE 2000, pages 93--102, Troyes (France), October 2000. CNED.
- [Que00b]
-
Christian Queinnec.
The influence of browsers on evaluators or, continuations to program
web servers.
In ICFP '2000 -- International Conference on Functional
Programming, pages 23--33, Montreal (Canada), September 2000.
A Textual command options
When the PS3I interpreter is run in textual mode, several command
options are available to customize its behavior. Options are processed
with the gnu.getopt package from
Aaron M. Renn
that is, options may appear
anywhere, they will be handled first.
The textual version of PS3I creates a single World where all
successive expressions are evaluated.
-
-r name specifies a properties file to
use. A reasonable default is the PS3I.properties
file. More than one property file may be loaded.
- -t worldName specifies the title of the World to
create. This name is used to determine the files to load in
the initial global environment. These files are specified as
URLs in the properties files under a key starting with
worldName followed by .prelude. and
ending with 0 then 1 etc. See the
``Customization'' subsection below.
- -w url specifies a serialized World to restart
with.
- -W fileName specifies a file where to serialize
the PS3I World to save at exit time. Use -w to
resurrect this serialized World.
There are a number of other options to trace evaluation, they will
probably disappear as they stand currently. Options are processed from
left to right.
Non option command arguments are considered as URLs that should be
loaded initially by the interpreter from left to right. For instance,
if you don't like the default boot file
(fr/lip6/qnc/ps3i/ps3i.scm that defines most of Scheme R4RS
features), you may load your own by telling:
java fr.lip6.qnc.ps3i.TopLevel file:my/boot/file.scm
A.1 Examples
Some examples are in order (I'll vary the way java is started with
-cp or -jar). The following command starts a bare
PS3I:
java -cp ps3i-latest.jar fr.lip6.qnc.ps3i.TopLevel
Pay attention to the fact that the bare interpreter knows nearly no
primitives (see Section 4) and only has a definition of
expand that behaves as the identity (this variable will be
patched at boot-time with a more interesting definition).
The next command starts a sort-of R4RS PS3I:
java -jar ps3i.jar -t PS3I.textual -r PS3I.properties
B Customization
The PS3I system may be customized with a Configuration. This file
may define the prompts, the boot files, the banners and the error
messages. These messages may be tailored for different language. See
the PS3I.properties file at the root of
ps3i.jar. If you translate it into another language, send me
your translation so I can add it to a future release.
The PS3I system has been written to be highly customizable. You
may define of course new functions in Scheme on the fly, you may
invoke Java methods from any class (this will load the class if
needed), you will even (soon) adjoin new special forms on the fly!
Here is the beginning of the ps3i.properties file:
01 PS3I.search.path.0=disk <%user.dir%><%file.separator%>
02 PS3I.textual.prelude.0=file:fr/lip6/qnc/ps3i/ps3i.scm
03 PS3I.textual.prelude.1=file:fr/lip6/qnc/ps3i/nought.scm
04 toplevel.promptin=PS3I?
05 toplevel.promptout=PS3I=
06 toplevel.bannerin=PS3I: The Persistent Server-Side Scheme Interpreter
07 toplevel.bannerout=;;; end of PS3I
08 # Customized error messages:
09 UnboundVariable=The variable {0} has no value
Suppose that the name of the World is PS3I then properties
prefixed with a ``PS3I.'' prefix will customize the interpreter.
Line 1, the PS3I.search.path properties customize where to
search relatives pathnames. See the
documentation of
the Configuration package for details. Here it says that
relative filenames should be searched in the user's HOME directory.
Lines 2-3 specifies the files to be pre-loaded in the current
World.
Lines 4-7 specifies the prompts and banners of the intepreter.
Line 9 shows how one error message may be customized. There are a
number of error messages!
Ce document a été traduit de LATEX par
HEVEA.