Lisp Front-End to JavaScript Engine

by Dmitry Nizhegorodov, (c) 2007.

This page features Lisp-to-Javascript translator and runtime library and contains various interactive "pads" to experiment with the system.

Use the code in the text area as-is, or modify it, or replace it. Press "read lisp" to see the result of reading the code in, "lisp -> JS" to see the result of conversion of it to JavaScript, "eval lisp" to execute it.

now, why we need all this lisp functionality: list processing.

note that lisp functionality remains very much the same if JS is used directly, and JS's expressiveness is very much similar to lisp. Thus, one can do list processing using JS code invoking list library functions car, cdr, list, append, mapcar, consp, lispt, etc and also possibly using x.car, x.cdr, new Cons, x.constructor == Cons where appropriate for efficiency.

However there is one area where lisp syntax wins big. It is lisp's ability to process code as plain S-Exprs. Lisp macros showcase this synergy between lisp's list processing and code manipulation like nothing else.

Adding the ingredients enabling macros - &rest in argument lists, the backquote and its comma and @ syntax, the defmacro facility, the macroexpand function - was not very difficult. The result is a smmall and fast yet capable macro system. A few examples are on the following pad:

Some answers regards what this project is and is not

This system utilizes classical Lisp S-expressions (a.k.a. S-Exprs) composed from pairs known as CONS cells. However, this is not a definition of an abstract Lisp machine nor a particular Lisp dialect nor an environment that defines the semantics of Lisp code evaluation. Instead this is a S-Expr-based front-end to the host JavaScript environment. The semantics of ECMAScript/JavaScript as defined in ECMA-262 is the key to understanding the flavor of this Lisp system. See here for details.

Considering the above, it is pretty clear that this project is not expected to run some existing Lisp or Scheme code "as is". Some porting is required; many primitives may be missing, some behave differently.

In a nutshell, this project implements a flavor of lisp featuring the following.

Scoping rules are implied by the workings of execution contexts from section 10 of ECMA-262. For instance, lisp closures are lexical (with the smallest lexical scope being a JS function definition), clean and are pretty efficient in comparison to the rest of the JS runtime.

Cons cells are represented as JavaScript objects with 2 properties, car and cdr. Nil is translated to JavaScript undefined. Defun is translated to JavaScript function definition, and due to that, has scope of the corresponding active context. Define and defvar is translated to JavaScript var statement and also has scope of the corresponding active context. Left-hand side of assignment may contain arbitrarily expressions as part of property access expressions.

Atoms in lisp is everything between blanks and list delimiters. and in this system atoms can be used creatively. Specifically, it is possible to intentionally write expressions having complex JavaScript expressions as atoms, One example is a.b.c. Another, contrived one is a.b.c.d-x+y+z[?1:2][++p*q--/*r*/]. Casing is preserved, so (qUote x 1) translates to qUote(x,1).

Symbols have historically played an important role in traditional Lisp systems, providing "objects" with identity and properties and an ability to reify as variables. We've reduced this to two aspects: symbols appearing in translated code can map to JavaScript identifiers or expressions; Symbols (or any atoms) appearing inside of quoted expressions map to JavaScript strings.

Using the System

There are 3 main ways this system can be used.

One is to experiment and have fun with S-expressions, JavaScript and their mix, as demonstrated by the interactive pads seen above on this page.

Take a look at the source of this HTML page. The lisp system is initialized with a script include tag. After that, global object Lisp contains the lisp functions and the translator as its properties. The lisp functions are then exported to become available as toplevel definitions with Lisp.use_package(). One can skip this step, using Lisp as name qualifier, which sometimes can be more appropriate.

The code that drive the interactive pads is not part of the lisp system. Yet the code shows some handy ways to invoke the translator and perhaps is reusable as well.

The second way of using the system is to code Web2.0, AJAX-ian apps in lisp using macros to capture various web code patterns, expanding it into production JavaScript code to be be consequently embedded/included into HTML pages.

The third one is to maintain code in lisp format, perhaps on the server, having that transparently expanded to JavaScript or perhaps into complex HTML content on demand. One example is given below.

HTML with embedded Lisp code

Use what is provided by default, or insert some other HTML code into the textarea below. HTML may contain any elements including JavaScript code. The page can contain Lisp code between and markers as shown below. Lisp code will be translated to JavaScript when buttons are pressed,

Source

Anyone can obtain and use the sources non-commercial purpose.
The core of the system: lisp.js)
The library of advanced control primitives and functions: lisp-lib.js)

Advanced functions and primitives are optional

The code from lisp-lib.js can be loaded independently from lisp.js by need. The interactive pad below has button "Load Lib" that does that.

Runtime Performance estimate: Classic BROWSE benchmark

BROWSE is one of the list-processing benchmarks from the classic Gabriels Lisp benchmark suite. It extensively manipulates AI-like data structures perfroming symbolic pattern matching on lists stored in symbol properties. Back in late 1980s, BROWSE was a non-trivial load even for most advanced, stare-of-the-art compiling and optimizing Lisp systems running on the best hardware of that time. Only supercomputers ran it in a matter of seconds. Most workstations and mainframes ran it in 2-3 mins. You can test the perfromance of your late 00s "workstation" righ now:
If your computer is not hopelesly obsolete, your measure is likely under 1 min. This means you ran this AI benchmark translated to javascript, interpreted, with CAR and CDR implemented as JS properties, inside your favaorite web browser, and it was FASTER than on Lisp workstations or mainframes avaialble only to top AI researhers 20 years ago. Wow.

P.S. This article could have a subtitle: "CONScript: ECMAScript serves Lisp". CONS, from "construct", is classical Lisp function creating the most fundamental Lisp object, a cons pair, a building block of Lisp lists. One can use the library of cons-related Lisp functions such as mapcar, maplist and append directly in Javascript code. CONScript (puns intended) is a system drafting ECMAScript engine to serve as lisp runtime. Macros are why it's fun.

Author: Dmitry Nizhegorodov (dmitrynizh@hotmail.com). My other projects and articles