
Compiling Lisp to JavaScript in 350 Lines of Haskell - allenleein
https://gilmi.me/blog/post/2016/10/14/lisp-to-js
======
dan-robertson
Converting “lisp” to JavaScript in the sense of this article is basically
trivial (but then it was done in only 350 lines of Haskell).

Compiling, say, Common Lisp to JavaScript is _hard_. There are “easy” steps
(in the sense that they can be ignored or done a stupid way at first) like
dealing with numbers, namespaces, packages, (I.e. the symbol foo can name a
function and a variable, amongst other things, and the symbols #:foo and #:foo
are not equal).

There are the “boring” steps like doing argument parsing (and harder is
working out how to make it efficient)

There are the hard steps like working out how to compile lexical goto and
return-from, eval-when

There are the very hard steps necessary for macros like having to reimplement
a macroexpander (for macrolet and friends) and then realising that this is
insufficient as one also needs to implement environment and such (but this can
be done by a macro to slightly modify macros, assuming that this compiler is
also implemented in Common Lisp). But this still is insufficient. Consider the
following legal Common Lisp:

    
    
      (defun make-adder (number)
        (lambda (x) (+ x number))
      (defmacro macro-+ (a b)
        `(funcall ,(make-adder a) ,b))
      (defun foo (y) (macro-+ 3 y))
    

So the body of foo expands into:

    
    
      (funcall #<lambda ...> y)
    

And one has no way to compile this host-system function to JavaScript and
hence to expand macros one needs to be able to interpret Common Lisp and be
able to convert any object into JavaScript source code (and keep in mind that
the source code needs to ensure that what we’re identical in the CL are
identical when the JS is compiled, and that closed-over variables are also
properly dealt with in the sense that if two different functions close over
the same variable in CL then they need to be compiled into JS functions that
close over the same variable, or at least behave the same as the CL functions.

Having to interpret does at least solve problems like having numbers behave
the same way in macros as at runtime.

And then there is CLOS which is a bit of a pain to bootstrap (and must be
bootstrapped twice of course).

To compile CL to JS one essentially needs three CL compiler (backends), one
for macros in the first compile, one for compiling to JS and one for compiling
and linking in the code that is compiled at runtime in the JS VM.

~~~
mtreis86
> and must be bootstrapped twice of course

Would you mind elaborating?

~~~
dan-robertson
I suppose I really mean that one needs to bootstrap at compile time and a way
to transfer the compile time objects into runtime (ie into source to make
objects at run time). The hierarchy is a bit of a pain to make at runtime as
it is quite circular. Eg standard-class is a class, that is an instance of
(some subclass of) class, and class is an instance of standard-class.

------
tsuberim
Cool stuff. I wrote a simple Hindley-Milner type language interpreter in
Haskell inspired by Haskell a while back. Consider using Parsec as a parser
combinator library.

------
agumonkey
next reading: common lisp to pascal using a pretty printer
[https://www.reddit.com/r/lisp/comments/1totwx/some_useful_li...](https://www.reddit.com/r/lisp/comments/1totwx/some_useful_lisp_algorithms_part_2_pretty/)

------
jahewson
350 lines of Haskell.

~~~
joshumax
The original title was "Compiling lisp to JavaScript in 350 lines from
Scratch" \-- For anyone actually interested in a lisp interpreter written in
Scratch, there's
[https://scratch.mit.edu/projects/159487877/](https://scratch.mit.edu/projects/159487877/)

