About LISC (LIghtweight Scheme on Caffeine) ------------------------------------------- LISC is a very lightweight, from scratch, fully functional scheme interpreter. It is very comparable to SILK (Scheme in Fifty Kilobytes), but tries to be more modular, faster, and more complete in terms of useful Scheme features, as well as providing a few useful enhancements outside the Scheme standard. In particular, LISC supports following scheme features: * Flonum (big integer and big decimal) support * Rational and complex number support * MacScheme like macro system (defined below) In addition, LISC provides the following non-standard extensions * First-class environments * Environment-relative addressing * First class errors * Data triggers * Module extensibility Comparing LISC to SILK (+ Advantage, - Disadvantage, +/- Tossup): - No continuation support (though perhaps escaping continuations will be supported in the future) - No java hooks (yet) - Larger source base (by 11k), and code-base (by 4k) + Arbitrary precision numbers + Rational support + Complex number support + Circular list support + Triggers + First class environments + Environment relative addressing + First class errors + Faster lookups + More modular design +/-? More code implemented in scheme itself Installation and usage ---------------------- 1) Unjar the distribution file. 2) Edit the lisc startup script for unix (lisc) or the lisc batch file for Dos/WIN (lisc.bat). Change LISC_LIB to point to the directory containing lisc.jar and the initialization files. If you want to tweak the stack size, edit MAX_STACK. 3) Run LISC by executing lisc or lisc.bat. LISC extensions --------------- First class Environments LISC provides the notion of first class environments and environment-relative addressing. At any time during execution of scheme code, the variable "this" is bound to the currently active environment. New environments can be created, and variables can be defined within specific environments stored in their own variables as first-class values. (new-env) => Environment Creates a new environment, with the current environment as the parent. This statement is equivalent to (new-env this) (new-env Env) Creates a new environment with Env as the parent environment. '() (the null or empty-list object) can be used to specify no parent environment. (env-parent Env) Returns the parent environment of the Env, or '() if Env has no parent. (env-bindings Env) Returns an association list of all the bindings in Env. The list is of the form '((sym . denoted_value) (sym . denoted_value) ...) Denoted values are represented by the first-class value #, which can be unwrapped into expressed values with "unbox". (set-env-parent! Env1 Env2) Sets the parent of Env1 to Env2 (set-env-bindings! Env1 Env2) Sets the bindings of Env1 to those of Env2 Environment relative addressing ------------------------------- In LISC, the character '.' is sacrificed as a possible variable character, in order to provide environment relative addressing. If an environment is stored in the variable 'foo', then 'foo.x' refers to the value of the variable 'x' bound in foo. Here is an example of using both first-class environments and environment relative addressing: > (define my-env (new-env)) > (define my-env.x 3) > (define my-env.y 'foo) > my-env # > myenv.x 3 > myenv.y foo > x # In addition, a new define form is in place which will make definitions in a given environment: (define-in ) It should be feasible to use both first-class environments and environment-relative addressing to implement Object Orientation in scheme, with a tidy and consistent interface that need not betray the functional nature of scheme. First-class Errors ------------------ LISC provides first-class handling of errors. Exceptions can be stored as opaque exception objects that have the printed form #. In addition, two functions are in place to provide exception handling: (try ) Attempts to evaluate and return its value. If an error occurs, that error is passed to the one-argument function , which can then handle the error. (throw ) Throws , which must be an exception object. This is generally used in catch statements to rethrow an error. Also, 'equal?'s behavior has been modified so that exceptions of the same type are considered to be true when tested with it. 'eq?' remains with its usual semantics, that is two exceptions are only eq? when they are the same exception object. Data Triggers ------------- LISC also provides the notion of data triggers. These are functions that can be installed on any defined variable that react whenever the value of that variable is reset. There are two types of data triggers, normal or 'filter' triggers, and side-effecting triggers. Normal triggers are installed with the 'define-trigger' procedure, where side-effecting triggers are installed with 'define-side-trigger'. All triggers are procedures that accept two arguments. The first is the old value of the variable being set, while the second is the variable's new value. (define-trigger sym proc) Define a trigger on the variable 'sym' (which must already exist) whose code is in 'proc'. The result of executing 'proc' with the before and after values of the variable is stored in the variable. There can only be one normal trigger. Repeated calls to define-trigger simply replace the old trigger function with the new one. (define-side-trigger sym name proc) Add a side-effecting trigger on 'sym' named 'name' whose code is in 'proc'. Like normal triggers, side triggers take the before and after value. However, the result of executing 'proc' is ignored. For that reason, for 'proc' to have any effect on the state of the interpreter, its actions must be side-effecting. (remove-side-trigger sym name) Remove the pre-existing side trigger named 'name' on the variable 'sym'. Here is an example of using data triggers: > (define a1 0) > (define a2 0) > (define sum-proc (lambda (before after) (set! sum (+ a1 a2)))) > (define-side-trigger a1 sum-proc) > (define-side-trigger a2 sum-proc) > (set! a1 5) > sum 5 > (set! a2 4) > sum 9 > ; Now we'll define a trigger on a1 that locks its value (define-trigger a1 (lambda (before after) after))) > (set! a1 'foo) > a1 5 > sum 9 Macros ------ LISC uses a MacScheme-like macro definition system. The keyword 'macro' is a built-in procedure that behaves identically to lambda, except that the result is re-evaluated, and if possible the results of the macro-transformation are stored in place of the macro (so later the code needn't be re-transformed). To get a feel for the macro system, take a look at the init.ss file for the definitions of common scheme functions as macros. Thanks to Peter Norvig for many of the macro-definitions included in LISC. Native extension modules ------------------------ LISC can be expanded by loading native code Modules that extend the abstract class Module. They can be loaded at runtime by executing (load-module "module classfile") where module classfile is the fully qualified name of a java class (i.e. "org.foo.MyModule"). (C) 2000 Scott G. Miller This software is distributed under the GNU General Public License. The included COPYING file that should have accompanied this distribution describes the terms of this license.