About
JsLisp is a Lisp(1) Compiler(2) targeting Javascript(3)
In more detail...
(1) A Lisp
JsLisp is a Lisp-2, similar to Common Lisp but is not Common Lisp
- Symbols are case sensitive.
- No cons cells. Lists are represented using Javascript lists.
- Lists are first-class objects. push is a regular function.
- No numeric tower. Numbers are double-precision floats (they provide 53 bits of integer numeric accuracy).
- Dynamic and lexical variables.
- There can be both a function and a macro with the same name. Macro is used in explicit calls, function can be used for funcall/apply.
- NaN, null, undefined, false, 0 are present with their Javascript value semantic.
- No NIL/T. Truth values are Javscript true/false.
- defmacro, define-symbol-macro, macrolet, symbol-macrolet are present mimicking Common Lisp semantic. Quasiquoting.
- Read macros.
- Interpolated string literals.
- No loop macro for now.
(2) Compiler
JsLisp is a compile-only implementation and contains no interpreter, eval is implemented by compiling and executing. The core of the compiler is hand-written Javascript, everything else is written in JsLisp itself (boot.lisp).
Compiled functions are regular Javscript functions, callable from Javascript code (with the exception of keyword arguments) and they can call Javascript code.
Javascript inlining is possible and used heavily in the default runtime library. Name mangling is necessary when converting JsLisp symbol names to Javascript and vice versa (mangle x)/(demangle x).
Compiler does a few static checks at compile time:
- Unknown variables
- Unknown functions
- Wrong number of arguments or wrong keyword arguments in a static call
- Unused locals
- Map over a lambda with the wrong number of arguments
JsLisp also supports docstrings and doctests (i.e. tests embedded in the docstring, ensuring conformance).
(3) Targeting Javascript
No attempt is made to create human-readable idiomatic Javascript, generated code is not meant to be manually maintained.
Works with recent browsers and node.js.
Using node.js allows writing the server side and the client side of a web application in a single language. There is also an RPC module that facilitates the approach by avoiding any explicit binding (you can just define the functions in a module using rpc-defun and they will be callable by the client and will run in the server).
Any Javascript functions or methods can be called from JsLisp. JsLisp code can be called by Javascript or used as event handler.
License
I am not a lawyer and I just hope this is enough to leave me out of jail... if you think it's not, then please drop me a line.
The intention is that you can use JsLisp for whatever you want (commercial or not) provided you just don't pretend you did something that I did or that you blame me if you have any problem.
Copyright (c) 2012 by Andrea Griffini
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Examples
These are a few examples of JsLisp programs. Unless noted otherwise a recent HTML5 browser is required.
Naive Fibonacci computationshow-sourcerunMemoizationshow-sourcerun
An interactive 3d Rubik cubeshow-sourcerun
A pie chart buildershow-sourcerun
A dialog windowshow-sourcerun
A chess playing programshow-sourcerun
An hexagonal tiling pathfindershow-sourcerun
A Mandelbrot fractal explorershow-sourcerun
A Conway's game of life implementationshow-sourcerun
JsLisp <=> CL
In this sections the differences between Common Lisp and JsLisp will be explained in more detail. This is not meant to be a tutorial on Lisp and a fairly decent understanding of Lisp is assumed.
Main JsLisp philosophy
The main idea behind JsLisp was to create a Lisp system starting from Javascript (instead of starting from IBM 704 instruction set) and trying to become similar to Common Lisp up to the point where the Javascript machine below would like to start complaining about that.
So basically the target of JsLisp was to become "as close as reasonably possible to CL, but not closer". Over the time this target has shifted a bit and the aim is to use CL as a model only when the added value is understood and not just for following CL minor warts that have been caused by the history of its evolution.
Please also consider that current state of JsLisp is EXTREMELY flux and subject to change. Quite a few parts are departures from CL or tentative implementations that I'm not fully convinced about.
PLEASE NOTE THAT CURRENTLY I'M APPARENTLY THE ONLY USER OF THE LANGUAGE AND I JUST CHANGE THINGS AT MY WILL WITHOUT CARING ABOUT BACKWARD COMPATIBILITY.
IF YOU DECIDE TO USE IT FOR ANYTHING PLEASE BE SURE TO UNDERSTAND THAT I'M NOT NECESSARILY GOING TO HELP YOU ON THIS INSTABILITY ISSUE.
Note that of course I'm open about hearing any suggestion or critics... but I'm not currently willing to take to extra load of thinking to someone else using JsLisp. I am using it for some pet projects but if you do then please be sure you're also ready to pay the price (i.e. porting everything or eventually maintaining your clone of JsLisp).
In all honesty I don't refrain from making changes just because of my already existing codebase. At the moment however I won't stop because of your codebase either.
No CAR / CDR
In JsLisp there is no cons, no car and no cdr. I'm not joking. I'm not talking about just the name car/first or cdr/rest... but about the very concept of linked list made of cons cells that is typical of most if not all other Lisp dialects.
OK. Please start breathing again. This is not a fundamental departure from Lisp. Really.
There is nothing "essential" into using cons cells except a conceptual and formal minimality that represents a model neither our human brain nor the today computer brain finds particularly simple(*).
Don't get me wrong. I think this was a fantastic smart choice for the problem McCarthy was working on, but for both humans and most current computers the array is a much more fundamental block than a linked list. To understand a cons cell you need to conceive the idea of two parts stuck together, then why not thinking to several (any n >= 0) parts stuck together instead of building a chain?
JsLisp uses Javascript array objects to represent lists and lists are mutable first-class objects. push in JsLisp can a be simple function and doesn't need to be necessarily a macro. Moreover push and pop are defined to work like they naturally do in Javascript by adding and removing elements at the end.
There are of course both reverse and nreverse available, but they're much less needed than in Common Lisp because after pushing things in a list they're already in the correct order.
Sharing is still possible and happens often with nested list structures, but only at subtree level and not at the tail level. Still for example is not safe to assume that quasiquoting will return a fresh unshared tree structure.
For representing code and in many cases in which sharing is not essential this approach is much more efficient than using cons cells to build lists and it's also the way the underlying "hardware" works (Javascript).
Creating cells-based linked lists is of course possible for example using two-elements arrays and can be a sensible choice if the tail-sharing possibilities are playing an important role for your problem.
(*) Simplicity is used with the meaning of "obvious" and that doesn't need hard thinking or further explanations. For humans for example counting concept is simple, and Peano axiomatic constructions of finite ordinals is not, even if the latter is in a formal sense "simpler" than counting.
When a human thinks to a sequence of three objects, it's just that: three objects... the first, the second and the third. No one would think first to just a compound of two parts... one part with the interesting object and the other part a pointer to where to find another compound when the element isn't the last one.
For both humans and present day computers arrays are "simpler" than linked lists.
Lisp-3
In JsLisp there are three main values for a symbol: the value, the function and the macro. In JsLisp is therefore possible to have both a function and a macro sharing the same name.
The macro is used (if present) every time a static call form is compiled, while the function is used instead every time a (function ...) form is used to pass or store away the function.
Like in Common Lisp the call (symbol-function x) returns the global function associated with the symbol x (not considering lexical function bindings) and (function x) abbreviated as usual #'x returns the function binding of the unevaluated symbol x those introduced in a lexical scope.
In a similar way the function (symbol-macro x) returns the global macro associated to the symbol x (not considering lexical macros established by macrolet). The current macro binding associated with the unevaluated symbol x is accessible with (macro x) and there is no reader macro for this very infrequent need.
Something that is used quite often in boot.lisp is the macro defmacro/f that allows defining at the same time a macro and a function. Note that defmacro/f can be used however only for functions with a fixed number of arguments.
defmacro/f (defmacro/f square (x) (let ((y (gensym))) `(let ((,y ,x)) (* ,y ,y)))) ;; ==> square (square 12) ;; ==> 144 (macroexpand-1 '(square (+ x y))) ;; ==> (let ((G#27 (+ x y))) (* G#27 G#27)) (map #'square (range 10)) ;; ==> (0 1 4 9 16 25 36 49 64 81)
There is also an experimental defun/inline macro dual of defmacro/f that allows defining a function and a macro at the same time provided the function implementation.
defun/inline (defun/inline square (x) (* x x)) ;; ==> square (square 12) ;; ==> 144 (js-compile '(square 12)) ;; ==> "((144))" (js-compile '(square z)) WARNING: Undefined variable z WARNING: Undefined variable z ;; ==> "((((d$$z)*(d$$z))))" (defun cube (x) (* x x x)) ;; ==> cube (js-compile '(cube 33)) ;; ==> "f$$cube(33)"
Numbers
JsLisp doesn't provide the Common Lisp numeric tower.
Numbers are just Javascript numbers, i.e. double-precision floating point values that ensure unit-accurate integer computations "only" up to -/+ 9,007,199,254,740,992 (2 to the 53rd power, more than nine millions of billions).
Also all math functions are simple wrappers of Javascript math functions and therefore the rules about NaN and infinity results of computations are dictated down there (e.g. (/ 0) -> infinity, and (log -1) -> NaN).
Reader
JsLisp reader allows customization like Common Lisp, but using a different approach.
Parse functions take as only argument a "source" that is an object on which you can call either (current-char src) to get the current character or (next-char src) to return current character and advance to next at the same time.
To read from a string there is a predefined (make-source s) that returns a suitable scanner the string s.
Reader customization can be done mainly in two ways:
1) Defining a reader function for a character
Consider a classical example of customizing the reader so that lambda forms can be specified more succinctly:
this | is interpreted as this |
---|---|
{x -> (* x 2)} | (lambda (x) (* x 2)) |
{x y -> (+ x y)} | (lambda (x y) (+ x y)) |
{+ x 1} | (lambda (x) (+ x 1)) |
If an application is using a lot of anonymous functions this transformation could lead to a subjectively more readable code.
Reader customization example 1 (setf (reader "{") (lambda (src) (next-char src) (let ((form (parse-delimited-list src "}"))) (let ((i (index '-> form))) (if (>= i 0) `(lambda ,(slice form 0 i) ,@(slice form (1+ i))) `(lambda (x) ,form)))))) ;; ==> #CODE (parse-value "{x y -> (* x y)}") ;; ==> (lambda (x y) (* x y)) (map {* x x} (range 10)) ;; ==> (0 1 4 9 16 25 36 49 64 81)
2) Redefining (wrapping) the parse-value predefined function
The other option is replacing the parse-value function that is used every time JsLisp needs to read from a character source.
For example consider the same customization as before but using regular parenthesis instead of braces and having the choice about to transform the form in an anonymous function depending on the presence of a -> symbol at the form top level (of course the implicit x version will not be implemented).
Redefining parse-value (setf #'parse-value (let ((oldf #'parse-value)) (lambda (src) (let* ((x (funcall oldf src)) (ix (if (list? x) (index '-> x) -1))) (if (>= ix 0) `(lambda ,(slice x 0 ix) ,@(slice x (1+ ix))) x))))) ;; ==> #CODE (parse-value "(x y -> (+ x y))") ;; ==> (lambda (x y) (+ x y)) (map (x -> (* x x)) (range 10)) ;; ==> (0 1 4 9 16 25 36 49 64 81)
JsLisp <=> Javascript
Javascript syntax is of course very different from JsLisp default syntax.
In hand-written Javascript there are some typical patterns used to emulate language features that are not present natively (for example immediate calling of an anonymous function to be able to have a local scope).
In other cases however the workaround is way too annoying to be implemented manually in Javascript (due to the lack of macro capabilities) and the features provided by JsLisp are simply not used.
Block scope
In JsLisp you can define a variable that is visible only for a block of code, like in C/C++, without interfering with another variable with the same name in the containing block.
BLock scope // Javascript function foo () { var x = 1; for (var y = 0; y < 4; y++) { var x = y * y; // ... use x ... } // Here x content has been destroyed; no // matter how many var declarations you // put only one x per function is allocated. // The commonly used workaround is to use // // (function(){var x = y*y; ... })(); // // instead of just a block. } ;; JsLisp (defun foo () (let ((x 1)) (dotimes (y 4) (let ((x (* y y))) ;; ... use x ... )) ;; Here x content is still 1. The x variable ;; inside the loop is a different separate ;; value from the external one. ;; Every (let ...) form introduces a new ;; scope shielding unwanted changes. ))
Arguments
JsLisp supports &optional, &rest and &key arguments for functions. For both &optional and &key arguments it's possible to provide a default expression to be used. Note that default expressions for &optional and &key arguments are evaluated at each call (not like in Python for example) but only if the parameter value is not passed.
Differently from Common Lisp &rest and &key arguments cannot be combined (at the moment) and also neither &allow-other-keys nor allow-other-keys: are supported. Note also that the colon in keywords in JsLisp is at the END of the word, not at the beginning.
Optional/keyword/rest (defun foo (x &optional y (z 42)) (list x y z)) ;; ==> foo (foo 1) ;; ==> (1 undefined 42) (foo 1 2 3) ;; ==> (1 2 3) (defun bar (x &key y (z 42)) (list x y z)) ;; ==> bar (bar 1) ;; ==> (1 undefined 42) (bar 1 z: 99) ;; ==> (1 undefined 99) (bar 1 z: 99 y: 33) ;; ==> (1 33 99) (defun baz (x y &rest z) (list x y z)) ;; ==> baz (baz 1 2 3 4 5) ;; ==> (1 2 (3 4 5))
Static checks
JsLisp can perform a few static (compile-time) checks that help avoiding typos in programs. A few of these checks are about the use of undefined variables or wrong parameters when calling a function.
It is important to note that these checks are performed when the function containing the wrong call is compiled, not when (and no matter if) it's executed.
The checks are also performed at runtime and in that case the result is an error and not a warning.
Undefined variables >> (defun foo (x) (* x y)) WARNING: Undefined variable y = foo >> (foo 12) **ERROR**: ReferenceError: y is not defined >> (defvar z 99) = 99 >> (defun bar (x) (* x z)) = bar >> (bar 33) = 3267
Undefined functions >> (defun bar (x) (* x (foo (1+ x)))) WARNING: Undefined function foo = bar >> (bar 12) **ERROR**: ReferenceError: function foo is not defined >> (defun foo (x) (* x 2)) = foo >> (bar 12) = 312
Wrong parameters in static call >> (defun square (x) (* x x)) = square >> (defun bar () (square) (square 12) (square 1 2 3)) WARNING: Not enough arguments in (square) WARNING: Unexpected arguments in (square 1 2 3) = bar >> (defun foo (x &key y z) (list x y z)) = foo >> (defun baz () (foo) (foo 1) (foo 1 z: 2) (foo 1 k: 99)) WARNING: Not enough arguments in (foo) WARNING: Invalid keyword parameter k: in (foo 1 k: 99) = baz
Unused local names >> (defun foo (x y) (* x 12)) WARNING: Local name y defined but not used = foo >> (defun bar (x y) (declare (ignorable y)) (* x 12)) = bar
Map/lambda parameters count and arity mismatch >> (defun dilbert () (map (lambda () (random-int 10)) (range 10))) WARNING: function argument count doesn't match number of passed lists = dilbert >> (dilbert) = (9 9 9 9 9 9 9 9 9 9)
Javascript integration
JsLisp compiled functions are regular Javascript functions and can call or be called from Javascript. The only problems are
- Names of the functions defined in JsLisp are "mangled" because they may contain characters that cannot be used in Javascript. For example the JsLisp function circle-area will be seen from Javascript as f$$circle_area and the predefined JsLisp function 1+ is seen from Javascript as f$$$49$$43$.
- Keyword parameters cannot be easily passed from Javascript to JsLisp functions
Inline Javascript
It is possible to inline Javascript code from JsLisp, note however that the strings inlined must be string literals and not string expressions.
Dynamically building Javascript code by string manipulation is done often in macros (it's actually how the compiler is implemented). In "leaf" cases the output of a macro is a single js-code form.
Inline Javascript code (display (+ "Hello " (js-code "prompt('What is your name?')") ",\nhow's going?")) Hello Andrea, how's going? ;; ==> "Hello Andrea,\nhow's going?" (macroexpand-1 '(aref x i j k)) WARNING: Undefined variable x WARNING: Undefined variable i WARNING: Undefined variable j WARNING: Undefined variable k ;; ==> (js-code "(d$$x[d$$i][d$$j][d$$k])")
Reference
A reference generated from docstrings documenting all functions and macros defined can be obtained by clicking the following button:
In the subsections instead there will be a schematic per-topic description of how many parts of JsLisp are supposed to work.
Modules
JsLisp allows partitioning the global namespace in modules where different modules can use the same name with distinct meanings.
Importing a module simply sets a new current module name and loads a lisp source file. Then possibly adds to the current symbol alias table some or all of the exported names.
Modules ; This imports all exported names from the ; examples/raster module into the current ; *symbol-aliases* table. (import * from examples/raster) ("hline" "box" "clear" "frame" "line" ... ) 'hline examples/raster:hline ; This imports (loads) the module and assigns ; a nickname, but defines no symbol aliases (import graphics as g) ("rgb" "rgba" "css-color" "random-color" "with-canvas") (random-color) WARNING: Undefined function random-color **ERROR**: ReferenceError: function random-color is not defined (g:random-color) {"r":182,"g":132,"b":188} ; This imports only two specific names from ; the gui module (import (show set-style) from gui) ("set-style" "element-pos" "event-pos" ...) 'set-style gui:set-style 'element-pos element-pos ; All currently active symbol aliases are kept ; in the *symbol-aliases* map and are checked ; first by the reader when interpreting a symbol (map #'display (keys *symbol-aliases*)) !hline !box !clear !frame !line !bezier !ellipse !ellipse-frame !fill !show !set-style ("!hline" "!box" "!clear" "!frame" "!line" ...)
When defining a macro in a module that needs to intern symbols in the caller module the reader macro ,#<symbol> can be handy expanding to ,(intern (symbol-name '<symbol>)) thus interning the literal <symbol> in the current module when the macro expansion is being performed.
The utility function (symbol-module x) returns the module where a symbol has been interned.
Generic functions
JsLisp implements a generic dispatching methods for functions that allow several implementations to be accessed using a single name performing the required multiplex at runtime on general conditions.
Generic functions (defun fact (x) (* x (fact (1- x)))) ;; ==> fact (defmethod fact (x) (< x 2) 1) ;; ==> (fact (< x 2)) (map #'fact (range 10)) ;; ==> (1 1 2 6 24 120 720 5040 40320 362880) (defmethod fact (x) (symbol? x) #"{x}!") ;; ==> (fact (symbol? x)) (fact 'x) ;; ==> x! (fact 10) ;; ==> 3628800
The macro (defmethod name args test &rest body) is similar to defun but adds a test condition that identifies when the provided implementation is to be used. When this condition is not satisfied then the previous definition of the function is used instead.
OOP support
JsLisp implements supports for Object Oriented Programming paradigm using named JS objects and generic dispatching.
The (defobject name fields) defines a new object class with the given name and also defines:
- a constructor prefixed with "new-" accepting positional arguments
- a constructor prefixed with "make-" accepting keyword arguments
- a type-check function by appending "?" to the class name
Also each instance of an object provides a meta-field named "%class" that contains (as strings) the name of the class and the names of all defined fields.
Each field in the (defobject ...) form can be either a symbol or a list (<symbol> <default>) to specify values to be used instead of undefined if the field value is not passed to the constructor.
Methods are implemented using the generic function dispatcher of JsLisp (defmethod ...) and this allows both multimethods and EQL-specializations of CLOS.
Object Oriented Programming support (defobject animal (name)) ;; ==> animal (defobject dog (name)) ;; ==> dog (defmethod animal? (x) (dog? x) true) ;; ==> (animal? (dog? x)) (defobject cat (name)) ;; ==> cat (defmethod animal? (x) (cat? x) true) ;; ==> (animal? (cat? x)) (animal? (new-dog "fido")) ;; ==> true (defmethod run (x) (animal? x) (display ~"{x.name} is running")) ;; ==> (run (animal? x)) (defmethod sing (x) (dog? x) (display "woof!")) ;; ==> (sing (dog? x)) (defmethod sing (x) (cat? x) (display "meow!")) ;; ==> (sing (cat? x)) (defvar *tobi* (new-cat "tobi")) ;; ==> {"name":"tobi"} (defvar *fido* (new-dog "fido")) ;; ==> {"name":"fido"} (run *tobi*) tobi is running ;; ==> "tobi is running" (run *fido*) fido is running ;; ==> "fido is running" (defmethod run (x) (= x *tobi*) (display "no way ...")) ;; ==> (run (= x *tobi*)) (run (new-cat "sissi")) sissi is running ;; ==> "sissi is running" (run *tobi*) no way ... ;; ==> "no way ..." (sing *fido*) woof! ;; ==> "woof!" (sing *tobi*) meow! ;; ==> "meow!"
Treeshaker
JsLisp provides the ability to deploy a compiled program that contains only the code that can possibly be executed explicitly from its main function, leaving the code for all unused functions, macros and the compiler itself out.
Also the produced Javascript code is minimized and this provides a smaller download size, a faster startup and some protection of the source code.
The easiest way to use the deploy function is to invoke JsLisp from node command line program:
node jslisp.js deploy.lisp myprogram.lisp > myprogram.js
As an example consider the chess playing program:
filename | lines | bytes | startup(s) |
---|---|---|---|
examples/chessboard.lisp | 189 | 8818 | |
examples/chess.lisp | 843 | 31672 | |
gui.lisp | 399 | 16081 | |
graphics.lisp | 90 | 3966 | |
boot.lisp | 1874 | 72549 | |
jslisp.js | 1819 | 66050 | |
TOTAL | 5214 | 199136 | |
Download size (gzip) | 40993 | ||
Startup time | 1.513 | ||
deploy.lisp output | 1 | 33584 | |
Download size (gzip) | 7822 | ||
Startup time | 0.034 |
The compiled and minified version is a single line of Javascript with a total length of 33584 that gizipped is a download of 7822 bytes. The deployed file has no dependencies (except in the specific the PNG images for the chess pieces), just loading it in a page will pop-up the chessboard.
Give it a try!
JsLisp REPL can run in most recent web browsers.
This means there is no program to download and to install.
You can start a new fresh JsLisp session in a separate window by clicking the following button.
If you want to install JsLisp on your own computer unfortunately you need to use a local web server because for security reasons Ajax requests are not supported on local filesystem browsing.