CL-JavaScript

JavaScript is the new BASIC—a universal scripting language. CL-JavaScript allows you to add user scripting to your Common Lisp application without requiring your poor users to learn Common Lisp. It is a JavaScript to Common Lisp translator, runtime, and standard library. We are ECMAScript 3 compatible, with some of the ECMAScript 5 extensions.

By using the Lisp compiler to compile JavaScript (and by using some clever tricks for the things that Lisp normally isn't good at), this implementation manages to be faster than most of the 2009 generation of JavaScript engines. The new generation (V8, Jägermonkey) beats it by, depending on the benchmark, a factor 4 or so.

Contents

News

14-03-2012: Version 0.12.03: Fix CLISP incompatibility.

09-01-2012: Version 0.12.01: Follow changes in parse-js, add Function.prototype.bind, getter/setter support, small fixes.

08-12-2010: Version 0.10.12: Implements ECMA5-style array, string, and Object constructor methods, as well as a JSON object. Rough support for CommonJS modules. Adds correct length (arity) properties to function objects.

15-11-2010: Version 0.10.11: The first release on the new project page. Our API can be considered more or less stable now, and there are no serious gaps left in our ECMAScript 3 support.

15-11-2010: It seems I have finally documented this library.

Code

CL-JavaScript was created by Alan Pavičić, Iva Jurišić, and Marijn Haverbeke. It is released under a MIT-style licence.

Development takes place on github. Any releases we make are listed under News. The latest release is always linked from cl-javascript.tgz.

Dependencies

CL-JavaScript depends on parse-js, CL-PPCRE, and optionally local-time (you won't have a Date object if your ASDF can not find local-time).

Because emulating IEEE 754 floating point special values (NaN, Infinity) in software is painfully inefficient, CL-JavaScript includes some non-portable code to directly use machine floats in SBCL and Allegro Common Lisp. There is fallback code present for other implementations, but since the developers don't develop on those, that might not be very well tested. It is recommended, if you really want to use the system on another implementation, to try and add native float support for that implementation.

Support

When you have a problem, please either open an issue on github, or send the maintainer an e-mail.

Quickstart

First, load the system:

cl-user> (asdf:oos 'asdf:load-op :cl-js)
cl-user> (use-package :cl-js)

We can start a JavaScript REPL to convince ourselves that yes, we really do have JavaScript in our Common Lisp.

cl-user> (js-repl)
JS repl (#q to quit)
> 1 + 1
2
> function fac(x) { return x <= 1 ? x : x * fac(x - 1); }
[object Object]
> fac(10)
3628800
> #q
cl-user>

Well, that seems to work. Next up: defining our own library.

cl-user> (defparameter *mylib* (empty-lib))
cl-user> (add-to-lib *mylib*
           (.func "plusOne" (x) (1+ (to-number x)))
           (.object "numbers"
             (.value "one" 1)
             (.value "two" "2")))
cl-user> (with-js-env (*mylib*)
           (run-js "plusOne(numbers.two)"))
3

Note the to-number call. This will invoke JavaScripts number-conversion. For a library like this, you are, of course, better off just doing something like this:

cl-user> (run-js "
  function plusOne(x){return x + 1;}
  var numbers = {one: 1, two: '2'};")

But, in general, what you want to do is write glue code, providing a JavaScript API for your application. For this, the library does its best to provide a practical interface for defining JavaScript environments.

Reference

Running Code

function run-js (code &key (compile t) wrap-parse-errors optimize)

Runs the given code. code can be a string or a stream. compile and optimize determine whether the code should be compiled, and if, whether it should be optimized, before it is run. When wrap-parse-errors is given, parse errors are wrapped in js-condition objects, and can be caught by JavaScript catch forms.

function run-js-file (file &key (compile t) wrap-parse-errors optimize external-format)

Runs the code from the given file. The keyword arguments are passed through to run-js, except for external-format, which is passed to open.

function js-repl (&key (handle-errors t))

Starts an interactive JavaScript REPL. If handle-errors is t, all errors will be caught and printed. If it is nil, all errors are let through. If it has any other value, only errors of type js-condition are handled and printed.

JavaScript Values

Values in a JavaScript environment are represented as follows:

null and undefined are the Lisp keywords :null and :undefined. The type js-null is provided, which includes both these values. js-null is also a predicate function (shortcut for (typep x 'js-null)).

Booleans are Lisp booleans (nil and t).

Numbers are represented as Lisp numbers (integers and double-floats). On implementations where no support for representing NaN and Infinity as floats has been added, these are represented by the values :NaN, :Inf, and :-Inf. The js-number type helps abstract this—matching only numbers on those implementations where no keywords are needed, and both numbers and these three keywords on others.

The macros nan, infinity, and -infinity are provided to create special number values. The predicate is-nan can be used to check whether a value is NaN.

Strings are plain Lisp strings.

Objects are a custom struct type—js-obj. The js-func and js-array types are subtypes of this.

function js-obj (&optional prototype type)

Creates a JavaScript object. Optionally, a prototype id (more about that later) or prototype object, and a type (as in define-js-obj) can be given.

function js-prop (obj propname)

Retrieves a property from an object. A setf variant is provided for setting properties.

function js-array (vector)

Creates a new JavaScript array. vector must be an adjustable vector with a fill pointer.

function js-array-length (array)

Retrieve the length of a JavaScript array.

function js-aref (array index)

Access an element in an array. There is a setf variant as well.

macro js-call (func this &rest args)

Call a JavaScript function value.

macro js-method (object name &rest args)

Call a method in a JavaScript object.

macro js-func (args &body body)

Creates a JavaScript function object from a lambda-like specification. Inside the body this will be bound, in addition to the specified arguments. The argument list is mangled to conform to JavaScript calling conventions—each paramter will become optional, with an implicit default of :undefined, unless you specify your own default. A &rest clause is allowed, but &key and &optional can't be used.

Exceptions

JavaScript exceptions are raised as Lisp conditions of the js-condition type. A JavaScript catch block will catch these (and only these).

method js-condition-value (condition)

Returns the JavaScript value associated with the given condition.

function js-error (type message &rest args)

Raises a JavaScript error (value of type Error) . type must be prototype id (:error, :type-error, :syntax-error, :range-error, :uri-error, and :eval-error are provided by the standard lib). message can be a format string into which args will be interpolated.

The Environment

variable *env*

The variable that holds the current environment. Starts out unbound (though run-js and js-repl will give it a default value automatically when they find it unbound).

macro with-js-env ((&rest libraries) &body body)

Runs body with *env* bound to a fresh environment, which was created by loading the standard library plus the given libraries.

function create-env (&rest libraries)

Creates a new environment, loading the given libraries.

function add-to-env (env &rest libraries)

Extends env with the given libraries, then returns it.

Utilities

macro void (&body body)

Executes the body, returns :undefined.

function to-string (value)
function to-number (value)
function to-integer (value)
function to-boolean (value)

Invokes the standard JavaScript type conversion algorithm on the given value.

Library Definition

CL-JavaScript works with first-class libraries. These are specifications of a set of variables, prototypes, and constructors that can be instantiated into an environment to make their definitions available there.

function empty-lib (&optional name)

Returns a fresh, empty library specification object. The name is only used for the printed representation of the object.

macro add-to-lib (lib &body body)

Add the definitions found in body to the given library.

Defining the content of a library is done with a family of macros starting with a period. These all take a &body in which lists starting with a keyword can be used to set options. For example:

(.object "Math"
  (:slot-default :noenum)
  (.value "E" (exp 1)))

Here, the :slot-default option is given, causing all slots defined in the Math object to not be enumerable.

All defining forms that allow slots to be defined inside of them accept the :slot-default option. All forms that define slots accept the :slot option. Both of these expect a list of keywords (:enum, :noenum, :ro, :rw, :del, :nodel) which specify slot properties (enumerabe, read-only, and deletable). Properties that are not specified are inherited from the context (as an under-the-covers special variable). By default, properties are enumerable, read-write, and deletable, except in prototypes, where they are not enumerable.

macro .prototype (id &body body)

Creates a new prototype and associates it with the given ID. All non-option forms appearing in the body are evaluated, and can add properties to the prototpe. A :parent option (which should hold a prototype-id) can be used to make this prototype inherit from another prototype.

macro .constructor (name args &body body)

Declares a constructor with the given argument list (interpreted as in js-func) and body. A :prototype option may appear in the body, and is used to determine what prototype objects created with this constructor should get. If it holds a keyword, that is the ID of the prototype to use, if it holds a list of slot definitions, a new prototype object is created and given those slots.

If this constructor should not create regular objects, you can give it a :type option containing the name of a type defined with define-js-obj. When the constructor is invoked with new, you will then get an object of that type as this variable, rather than a plain object.

Finally, a :properties option can be passed, within which properties for the constructor itself can be defined (as in String.fromCharCode).

macro .value (name &body value)

Defines a simple value property. When this appears at the top level, it defines a global variable. When it appears inside another form, it adds a property to that definition.

macro .object (name &body body)

This defines an object property. The body contains property definitons for this object, and optionally a :parent option, as in .prototype, to give the object a specific prototype.

macro .func (name args &body body)

Adds a function (top-level) or method. args is an argument list as in js-func. The given body becomes the body of the function. A :properties option can be used to give the function object itself properties.

macro .active (name &body body)

This macro is used to add 'active' properties to objects. Active properties are things that can be approached like regular properties, but execute some function when read or written. The :read and :write options can be used to specify the bodies of the functions, like this:

(.active "preciousProperty"
  (:read () "my precious!")
  (:write (value) (js-error :error "How dare you touch my precious!")))

(The argument lists are compulsory, even though they are always the same.)

macro .active-r (name &body body)

This is a shortcut for an .active property with only a :read entry (meaning writes to the slot will be ignored).

The following two macros should not be used inside library definitions, but at the top level (they are global in their effect).

macro define-js-obj (name &body slots)

Defines a struct type fit for holding JavaScript object values. The way to use this is to specify the type name you use here as the :type option of a .constructor form, and then fill in your custom slots in this constructor.

macro integrate-type (specializer &body options)

A type defined by define-js-obj is a 'real' JavaScript object, to which clients can add properties. Sometimes, it is preferable to use Lisp objects 'as they are', because wrapping is too expensive. This macro allows you to do that.

To be able to use a value as a JavaScript value, a bunch of methods have to specialized on it, so that JavaScript operations (typeof, String(x)) will know what to do with it.

specializer should be a valid method specializer that can be used to recognize the type you want to integrate.

All options appearing under this macro can take either the form of a single value, or an argument list (of a single symbol) and then a body. The :string option determines how the type is converted to string (default is "[object Object]"). The :number option converts to numbers (default NaN). The :boolean option to booleans (default true). typeof is used to determine the string returned by the typeof operator (default "foreign"). When a :proto-id form is given, it is used to locate a prototype in which properties for these values are looked up.

An example:

(integrate-type complex
  (:number (val) (realpart val))
  (:string (val) (format nil "~a+~ai" (realpart val) (imagpart val)))
  (:typeof "complex")
  (:proto-id :number)) ;; or add a custom prototype

/* ... and in your library ... */
(.func "complex" (real imag)
  (complex (to-number real) (to-number imag)))

Provided Libraries

variable *printlib*

A tiny library value containing only a print function, which will write its arguments to *standard-output*.

function requirelib (hook)

When called, returns a library object that implements the CommonJS-style require operator. hook should be a funcallable object which, given a string, verifies that string as a module specifier, and returns a pathname under which the module text can be found. Or, if you need modules that aren't simply files, it can return two values—a canonical module identifier (must be comparable with equal) and a function that, given this identifier, returns either a stream or a string containing the module's text.

A trivial hook (without error-checking or safety) could simply do (merge-pathnames spec "/my/script/dir/x.js"). If you do want to do error-checking, use js-error to complain when a specifier is not acceptable.

Note that CL-JavaScript's implementation of CommonJS modules does not sandbox the modules in any serious way—it simply wraps them in a function. This means that direct, var-less assignments will create top-level variables, and the module can mangle existing values (say, Object.prototype) all it wants. For well-behaved modules, this shouldn't be an issue.