Jlisp 0.2 Overview

Jlisp is a small, fast, scheme-like LISP interpreter, based on code released by Gregory J. Chaitin, under the GNU General Public License. Jlisp 0.2 is a significant upgrade over previous versions.

Why Jlisp?

The primary goal of Jlisp is easy extensibility of the LISP interpreter through external Java and LISP libraries.

Download

The Jlisp download is a single zip file containing:

  • A JAR file (jlisp.jar) containing all class binaries and optional LISP libraries.
  • All Java source code for Jlisp
  • Optional LISP libraries to extend the basic functionality of Jlisp and
  • A copy of the GNU General Public License, under which Jlisp is released.

Installation

Simply put the JAR file (jlisp.jar) in your classpath. If your Jlisp program uses the optional LISP libraries, this JAR file is searched for them.

Java package overview

The base path is net.thinksquared.jlisp. The main Jlisp classes are:

Loader
Used to run Jlisp as a standalone LISP interpreter.
Lisp
The LISP interpreter.
LispThread
Runs the LISP interpreter in a separate thread.
Library
Interface you implement to extend functionality of Jlisp.
string
Implements Library to add basic string handling.
io
Implements Library to add basic I/O support.
math
Implements Library to add basic math functions.
hash
Implements Library to add hash table capabilities to Jlisp.
Tokenizer
Used to parse a LISP input file.

Classes in the net.thinksquared.jlisp.test path are used to test various parts of Jlisp. They are not intended to be part of the normal releases, and may be removed in the future.

LISP library overview

The Jlisp JAR file contains LISP libraries that you can "import" to extend the functionality of Jlisp. You may view the source code for these LISP libraries within the "lisp" directory in the zip file. At present, the LISP libraries are:

common.lisp
Commonly used LISP functions.
matrix.lisp
Used to manipulate matrices in LISP.

Running Jlisp as a standalone LISP interpreter

You use the Loader class to run Jlisp in standalone mode:

  • Write your LISP program with any text editor, saving it to a file, say "main.lisp"
  • Use java -classpath jlisp.jar net.thinksquared.jlisp.Loader main.lisp

Extending Jlisp with the built-in Java libraries

Simply use the import keyword with the built-in Java library name:

            (import string)
            define mylist ( a b c d )
            display (cat mylist)
            

Extending Jlisp with LISP libraries

To use a LISP library, simply import with the LISP library file name: For example, to use the in function in the common.lisp library:

            (import common.lisp)
            define mylist ( a b c d )
            display ( in b mylist )
            

Writing your own Java libraries for Jlisp

The Library interface contains 2 functions, register() and apply(), which you have to implement. register() allows you to register new keywords with Jlisp, and when they are encountered in a LISP program, Jlisp calls your apply() allowing you to dispatch the call to the appropriate handler in your Java library. You may define as many new keywords as you wish in this way. Of course, you should avoid using built-in functions or keywords, because these may never be "overridden".

As an example, suppose you want to create a Java library function, callable from LISP, to print out a given S-expression to the screen. Let's call the LISP keyword "print". So, (print (hello world)) would display "(hello world)" on screen.

The first step is to implement net.thinksquared.jlisp.Library in a class of our own (we'll call it "mylib", the convention being to use lowercase to name Java libraries that extend Jlisp):

   
            package com.mycompany.lisp;

            import java.util.Map;
            import net.thinksquared.jlisp.*;

            public class mylib implements Library{

                Sexp PRINT;

               	public void register(Map registry, Lisp runtime){
                    PRINT = runtime.atom("print");
                    registry.put(PRINT,this);                    
                }

                public Sexp apply(Sexp function, Sexp args){
                    if(function == PRINT) System.out.print(args.toS());            
                    return args;                    
                }
            }
            

The second step is to import our Java extension within our LISP programs. For example:

            (import com.mycompany.lisp.mylib)
            (print (hello world!))
            

NOTE: Notice how in apply() the arguments of the called LISP function are presented as a Sexp. This class represents S-expressions, and lists are represented as a binary tree, with Sexp.head() giving the first element of the list and Sexp.tail() giving the rest of the list (equivalent to car and cdr).

How to embed Jlisp in your Java projects

Once you know how to extend Jlisp functionality with either Java or LISP libraries, the problem of embedding Jlisp within your Java project boils down to: (a) Controlling execution of LISP programs from Java. (b) Allowing some communication between Java libraries.

(a) is easily accomplished using net.thinksquared.jlisp.LispThread. The constructor for this class takes a single String argument, which is the LISP program to be run. Upon instantiation, it parses the LISP program in a new thread. This thread is "frozen" initially. You may "freeze" or "unfreeze" it using the Java functions freeze() and unfreeze(). You may even freeze this thread from within LISP by calling (yield) at any point of the program. Note that (yield) only works in LISP programs run by net.thinksquared.jlisp.LispThread. As an example, consider:

   
            package com.mycompany.lisp;
            import net.thinksquared.jlisp.*;

            public class MyClass{

                String lispProgram = "loop 100000 display apply read";

               	public void runInteractiveLispInterpreter(){
                    
                    LispThread lt = new LispThread(lispProgram);
                    lt.unfreeze(); //start the LISP interpreter
                }
            }
            

In this example, after the program starts, LISP commands typed in by the user are evaluated and displayed on screen. If the user types in yield, then the interpreter grinds to a halt, but does not exit.

As for (b), communication between Java libraries (needed sometimes to initialize a Java library with information obtainable only during runtime) is done by calling Lisp.getLibrary(), usually in register(). This function gives a handle to the LISP runtime. You call getLibrary() on this runtime, with the class name of the Java library you want. getLibrary() returns an Object, which you must correctly cast to the right class.

As an example, suppose we wanted to extend our trivial "print" library in the earlier section, to include a function to print to a HTML file, but this depended on the presence of another library called com.mycompany.lisp.html. This might seem contrived, but it may happen in some projects. Suppose our new LISP function is print-html. The way to do this is:

   

                com.mycompany.lisp.html htmlPrinter = null;
                Sexp PRINT = HTML_PRINT= null;            

               	public void register(Map registry, Lisp runtime){
                    PRINT = runtime.atom("print");                               
                    registry.put(PRINT,this);

                    Object o = runtime.getLibrary("com.mycompany.lisp.html");
                    htmlPrinter = (o == null)? null:(com.mycompany.lisp.html) o;
                    if(htmlPrinter != null){
                        HTML_PRINT = runtime.atom("html-print");    
                        registry.put(HTML_PRINT,this);
                    }
                }

                public Sexp apply(String function, Sexp args){
                    if(function == PRINT){
                        System.out.print(args.toS());            
                    }else if(function == HTML_PRINT){
                        htmlPrinter.print(args);
                    }
                    return args;                    
                }enquiries@thinksquared.net

            

Syntax Guide

Defining functions

Use the "define" keyword to define a function, or bind a name to a list:

                define (add a b)
                    + a b

                display (add 2 3)
            

Jlisp supports flexible parameters

Use the "&rest" keyword to define a flexible parameter:

                define (add-prefix prefix &rest)
                    (mapcar &rest 'lambda(x)(cat prefix x))

                display (add-prefix ? a b c d e f)
            

returns (?a ?b ?c ?d ?e ?f)

The following sections are a compendium of LISP functions implemented so far, either in the Jlisp interpreter, "external" Java libraries or the optional LISP libraries.

Built-in functions and keywords:

' (quote)
Quotes lists within an S-expression
" (double quote)
Quotes S-expressions within an M-expression
-,+,*,>,<,<=,>=,=
Builtin arithmetic operators.
atom
atom <s-expression> Tests if the S-expression is an atom
append
Appends a list to another: append (a) (b) gives (a b)
apply
Evaluates an S-expression in the current environment (as opposed to eval, which runs code in a new environment): apply '(display (hello world)) displays (hello world) on screen
car
Returns the first element of a list: car (a b) returns a
cadr
Returns the second element of a list, and nil if there is no such element.
caddr
Returns the third element of a list, and nil if there is no such element.
cdr
Returns the list without the first element
cadr
Returns the second element of a list, and nil if there is no such element.
cat
Concatenates S-expressions to give an atom: cat hello (world) gives the atom helloworld
cons
Concatenates two lists to give a third
define
Used to define new functions or global variables:
define message (hello world) binds the atom message to (hello world)
define (echo msg) display x defines a function, echo to display the S-expression bound to the argument msg
eval
Evaluates an S-expression in a new environment. (cf apply)
fork
UNIMPLEMENTED! Starts a separate thread of execution. Proposed syntax: (fork <S-expression>)
if
Conditional. if <cond> <then> <else>
import
A keyword, used to extend Jlisp through the use of external Java libraries or LISP libraries. See the sections above for more detail.
lambda
Denotes the lambda function. (lambda (<arguments>)(<function-body>))(<values>)
let
Syntactic sugar for variable assignment. let x v e is "let x be v in expression e"
length
Counts the number of elements in a lispt
loop
Iteration: loop <count> <loop-body> . You may abort the loop using the keyword break
prog
Executes M-expressions prog (<m-expression>)
read
Reads in an S-expression from the standard input (usually keyboard input). read terminates and returns as soon as it encounters a line feed \n ("Enter")
setq
Sets the value of a variable. If the variable doesn't exist, a global one is created; if it exists, its value is set; if within a let, the temporary variable is set. (setq <variable> <value>)
setf
Sets the value of an evaluated variable. This differes from setq in that <variable> is also evaluated, before it is set.
size
Gives the number of characters in the string representation of a given S-expression.
size <s-expression>
try
Attempts to eval a given S-expression, but gives up after a "time" limit (strictly speaking, a limit on the number of evaluations performed recursively in order to evaluate the given S-expression). try <time-limit> <s-expression>. To indicate no time limit, pass the atom no_time_limit as a time limit. The return value is a pair, (success/failure <s-expression>). In the case of a failure, the returned S-expression is out_of_time This try is significantly different from Chaitin's original try.

Object Orientation:

defclass
Denotes a class definition: (defclass <classname> [<super-classname>] <function and variable declarations> )
def
Used like define, but only within a class, in order to define variables and functions.
call
Calls a function on an object or class: (call <handler> <function-name> <parameters>). <handler> can be a reference to an object, or a class name (you're calling a static function) or the current object, via the this keyword.
this
Used to refer to the current object, eg: (call this <function-name> <parameters>)
set
Sets a value on an object. May ONLY be use within a class definition. (set <variable-name> <value>)
get
Gets the value of a variable in an object. May ONLY be use within a class definition. (get <variable-name>)
new
Instantiates an object: (new <class-name>)
undefined
A return value indicating a function call failure.

String functions with (import string) :

Use (import string)

cat
Concatenates the arguments to form a new string. (cat <argument>+)
catx
Concatenates all arguments with the first one as a delimiter. (catx <delimiter> <argument>+)
match?
Returns "true" if the given regular expression matches the given string. (match? <string> <pattern>)
subst
Substitutes one string into another, whenever matched by the given regular expression.(subst <string> <pattern> <replace-with>)
split
Splits a string using a given regular exression. (split <string> <pattern>) Returns a list.
camel
Converts to "camel case". (camel 'my_doc) returns MyDoc
jcase
Converts to "Java" case. (jcase 'ab_c12e) returns abC12e
ucase
Converts to upper case. (ucase <string>)
read-file
Reads the given file into a single atom. The file is searched for in the given directory as well as on the current classpath. (read-file <filename>)
intern
Returns a canonical representation of the string. To enable Jlisp to use the Java garbage collector, all strings returned from this string library are non-canonical, meaning that they may not be used in "apply" for metaprogramming. "intern" makes them canonical (but not garbage collectable). (intern <string>)

I/O functions with (import io) :

exists?
Searches for a file, both on the given path and on all JAR files in the classpath. Returns "true" is it succeeds, "false" otherwise. (exists? <filename>)
open
Opens a new file: (open <filename>). Here, <filename> may be any S-expression.
close
Closes a file: (close <filename>)
print
Prints the String representation of the given S-expression to the file. Outer brackets are always removed: (print <filename> <s-expression>)
println
Similar to print, but outputs a UNIX linefeed (\n) at the end.

Math functions with (import math) :

^
Exponentiation: ^ 3 2 gives 9
abs
Absolute value of the argument.
asin
Gives the arc sine of the argument.
cos,sin
Cosine and sine of their respective arguments.
degrees
Converts radians to degrees.
div
Division. Note that division is NOT a built-in function.
ln
Gives the logarithm of its argument.
max
Gives the maximum of two arguments.
mod
(mod a b) gives the remainder after a is divided by b.
min
Gives the minimum of two arguments.
pi
A constant representing pi.
radians
Converts degrees to radians
rani
(rani N) gives a random integer between 0 and N.

Hash table functions with (import hash) :

hput
Creates a hash table, or puts elements into an existing hashtable. The two forms are:( hput <first element> ) to create a hash table with the given first element. ( hput <hashtable> <an-element> ) to add an element into an existing hash table
hget
Gets a value from a hash table given a key: ( hget <hashtable> <key> )
hdel
Deletes an element from a hash table given a key: ( hdel <hashtable> <key> )
hempty?
Checks if a hashtable is empty: ( hempty? <hashtable> ).
hfree
Releases resources associated with a hash table:( hfree <hashtable> ).

Common LISP functions with (import common.lisp):

and
Logical AND of 2 S-expressions.
or
Logical OR of 2 S-expressions.
xor
Logical XOR of 2 S-expressions.
in
Returns true if an element is in a list.
assoc
Returns an element in a LISP "hashmap". (assoc lst key return-pair). If the "hashmap" is my-map := ((company-name Thinksquared) (location Singapore) consultancy) then (assoc my-map location) returns Singapore but (assoc my-map location true) returns (location Singapore)
put
Puts a value into a list "hashmap". (put my-map lst key value)
del
Removes an entry from a list "hashmap". (del my-map lst key)
compact
Removes all "nil" entries in a list. (compact lst)
mapcar
Applies a function to a every argument in a list. (mapcar lst function) The function must be wrapped in a lambda. For example: (mapcar (1 2 3) 'lambda(x) * 2 x) returns (2 4 6).
If you have defined a function, say (my-func a b), where a and b are parameters then use: (mapcar lst 'lambda(x)(my-func x b)). The value of the argument "a" is bound to elements of the given list, whereas the value of "b" is depends on the current context:
    (import common.lisp)
    define (add a b)
        + a b

    let const 7
    display (mapcar (1 2 3) 'lambda(x)(add x const))
displays (8 9 10)
list
Returns a list of its arguments
catsp
Returns an atom formed out of concatenating all arguments with a "space" delimiter.

Matrix functions with (import matrix.lisp):

equal?
Checks if two matrices are equal.
add
Adds two matrices
subtract
Subtracts one matrix from another.
multiply
Returns the multiplication of two matrices.
hadamard
Returns the hadamard multiplication (elementwise multiplication) of two matrices.
trace
Returns the trace of a matrix
transpose
Returns the transpose of a matrix.

Participate!

If you'd like to help or if you've spotted a bug, just drop us a note at: dev@thinksquared.net. We welcome your participation!