The Parenscript Common Lisp to JavaScript
                 compiler

  Vladimir Sedach <vsedach@gmail.com>
What Parenscript is not

   Not a full ANSI INCITS 226-1994 (the standard
    formerly known as X3J13) implementation in
    JavaScript
   Not a Lisp interpreter in JavaScript
   Not a framework, toolkit, UI or AJAX library
What Parenscript doesn't do

   No dependencies or run-time library
       Any JS code produced runs as-is
   No new data types
   No weird calling convention
       Any PS code can call any JS code directly and vice-
        versa
   No whole program compilation
       JS code can be produced incrementally
Where is Parenscript used?

   Web frameworks: BKNR, UCW, TPD2,
    Weblocks
   Libraries: cl-closure-template, Suave, css-lite,
    clouchdb, uri-template
   Many startups and commercial projects
Parenscript history

   March 2005, Manuel Odendahl announces
    initial release
   Swallowed up by the bese project in 2006
   Skysheet developers spin Parenscript out as
    separate project again in 2007, take over
    development, do lots of PR
Similar projects

   Not many options for Common Lisp:
       Jason Kantz's JSGEN (2006, s-exp over JS)
       Peter Seibel's Lispscript (2006?, s-exp over JS)
   Many more options for Scheme:
       Scheme2JS (2005?)
       Feeley and Gaudron's JSS
       Many others
   Moritz Heidkamp's survey of Lisp-in-JavaScript
    implementations lists 28!
Use cases

   Lisp at runtime
   No Lisp at runtime
Lisp at runtime

   Compile to inline JavaScript in HTML page
   Stream client-specific JavaScript to browser via
    XMLHttpRequest or page loads
   SWANK-based development environment
No Lisp at runtime

   Compile to static files sent to browser
       If done with a runtime, can use all of CL features for
        a build/deploy/cache system
   Compile to library to be used by other JS code
   Compile to JS client- and server-side (node.js)
    code at the same time
       Deploy JS only
       Parenscript used as static compiler for whole web
        stack
Implementation Techniques
What should JS code look like?

   Write CL, but debug JS
   Generated code has to be short, readable,
    idiomatic
   ”Optimization” in the context of this talk means
    ”more readable code”
   What about performance?
       Can only assume that JS compilers try to optimize
        for code written by humans (ie – readable and
        idiomatic code)
Statements vs Expressions

   In JavaScript, can transform any statement to
    an expression by wrapping it in a lambda
       Except for BREAK, CONTINUE, and RETURN
   Many optimizations to avoid lambda-wrapping
    possible, depending on combination of special
    forms
Function argument lists

   Full Common Lisp lambda lists in JavaScript
       In a way completely compatible with other
        JavaScript code
   ARGUMENTS is great
   NULL (argument provided) vs UNDEFINED
    (argument not provided)
   Keywords: f(”key2”, value2, ”key1”, value1);
       CL keywords and JS strings both self-evaluating
Macros

   Share the same representation (macro-
    function) in CL and PS compilers
   But have different namespaces
   Share same code between client (CL) and
    server (JS) even if implementation details
    completely different (ex – IO, graphics)
       Without modifying CL code
       PS macros can shadow CL functions
Data structures

   No car/cdr
   Many Common Lisp forms are sequence
    oriented (DOLIST, LOOP, etc.)
   Most idiomatic Common Lisp code uses these
    forms
   Most code turns out to care about sequences,
    not linked lists vs arrays, and works as-is with
    JS arrays
Namespace (Lisp-1 and Lisp-N)

   JavaScript is a Lisp-1
   Common Lisp is a Lisp-4
       With macros, Lisp-N
   Parenscript tries to be a Lisp-2 (separate
    variable and function namespaces)
   Code can be output unambiguously for lexically
    bound names (via α-renaming), but free
    variables referring to globals might cause
    conflicts
   No thought given to hygiene
Namespace (package system)

   All Parenscript symbols are CL symbols
   Packages can be given prefixes to namespace
    in JS:
       foo::bar => foo_bar
   Package system also used for obfuscation
    and/or minification
   Exports list used to produce JS libraries with
    minified internals and prefixed API names
Namespace (case and escape)

   CL readtable case-insensitive by default, but
    can be set to preserve case
   Parenscript recognizes mixed-case symbols
    and outputs them correctly
   Simple translation for case-insensitive ones:
       foo-bar => fooBar
   JS reserved chars escaped:
       foo* => foostar
JavaScript dot operator

   Causes confusion
       Some people think it's ok to use it for namespacing
   foo.bar is really (getprop foo 'bar)
   What about foo.bar.baz(foobar)?
       (funcall (getprop (getprop foo 'bar) 'baz) foobar)
   Macros to the rescue:
       (funcall (@ foo bar baz) foobar)
JavaScript dot operator 2

   Why not (foo.bar.baz foobar)?
   Parenscript 1 did it in compilation step
       Turns out this doesn't work well with package
        system, breaks symbol macros
       Symbol macros used for a lot of things in
        Parenscript 2 compiler
   Possible to do it in the reader
       But ”.” is already reserved for dotted lists
       Dotted lists are evil; remove them from Lisp
Lexical scoping
   Done by using α-renaming to declare variables
    in nearest function scope:
      (lambda ()                function () {
       (let ((x 1))                 var x = 1;
        (let ((x 2))                var x1 = 2;
          x)))                      return x1;
                                }
Lexical scoping: loops

   For variables in loops, JS creates a shared
    binding for all iterations, breaking closures
   WITH trick from Scheme2JS:
      (dotimes (i 10)         for (var i = 0; i < 10; i += 1) {
       (lambda () (+ i 1)))    with ({i : i}) {
                                 function () {
                                       return i + 1;
                              };};};
Dynamic scoping

   TRY/FINALLY to set a global variable
    (let ((*foo* 1))   var FOO_TMPSTACK7;
      (+ *foo* 2))     try {
                           FOO_TMPSTACK7 = FOO;
                           FOO = 1;
                           return FOO + 2;
                       }
                       finally {
                         FOO = FOO_TMPSTACK7;
                       };
Multiple value return

   ARGUMENTS has 'callee' property (function)
   'callee' has 'caller' property (function)
   Functions are JS objects, can attach arbitrary
    properties to them
   To return MV:
       arguments.callee.caller.mv = [val2, ...]
   To receive MV:
       others_vals = arguments.callee.mv
Multiple value return 2

   Actually requires more cleanup code than this.
   arguments.callee works even when calling
    to/from functions that don't know about
    Parenscript (a global ”return value arg”
    wouldn't)
   Non-lexical returns (see next slide) with multiple
    values easier to implement (but aren't yet)
Control transfer

   JavaScript only supports RETURNing from
    innermost function, BREAKing out of innermost
    block or loop
   Can implement CL's BLOCK/RETURN-FROM
    and CATCH/THROW using TRY-CATCH-
    FINALLY in general case
   Optimizations possible depending on special
    forms, whether a return value is wanted
Tools and future developments
CLOS

   Red Daly's PSOS library provides CLOS and
    Common Lisp's condition and resumable
    exception system
       Not part of core Parenscript because it requires run-
        time library and introduces new types
   Possible to build up a full Common Lisp in
    JavaScript this way using Parenscript as a
    cross-compiler
Development environments

   SWANK-based
       slime-proxy
   Non-SWANK
       Numen (V8 only, to be released)
Future directions

   JS implementations have mostly caught on
    speed-wise
       But have not, and most likely will not, catch up in
        efficient memory consumption
   Would like a full Common Lisp implementation
    in JS on the server (V8, Rhino, etc.) and web
    browser (Firefox, Chrome, etc.)

The Parenscript Common Lisp to JavaScript compiler

  • 1.
    The Parenscript CommonLisp to JavaScript compiler Vladimir Sedach <vsedach@gmail.com>
  • 2.
    What Parenscript isnot  Not a full ANSI INCITS 226-1994 (the standard formerly known as X3J13) implementation in JavaScript  Not a Lisp interpreter in JavaScript  Not a framework, toolkit, UI or AJAX library
  • 3.
    What Parenscript doesn'tdo  No dependencies or run-time library  Any JS code produced runs as-is  No new data types  No weird calling convention  Any PS code can call any JS code directly and vice- versa  No whole program compilation  JS code can be produced incrementally
  • 4.
    Where is Parenscriptused?  Web frameworks: BKNR, UCW, TPD2, Weblocks  Libraries: cl-closure-template, Suave, css-lite, clouchdb, uri-template  Many startups and commercial projects
  • 5.
    Parenscript history  March 2005, Manuel Odendahl announces initial release  Swallowed up by the bese project in 2006  Skysheet developers spin Parenscript out as separate project again in 2007, take over development, do lots of PR
  • 6.
    Similar projects  Not many options for Common Lisp:  Jason Kantz's JSGEN (2006, s-exp over JS)  Peter Seibel's Lispscript (2006?, s-exp over JS)  Many more options for Scheme:  Scheme2JS (2005?)  Feeley and Gaudron's JSS  Many others  Moritz Heidkamp's survey of Lisp-in-JavaScript implementations lists 28!
  • 7.
    Use cases  Lisp at runtime  No Lisp at runtime
  • 8.
    Lisp at runtime  Compile to inline JavaScript in HTML page  Stream client-specific JavaScript to browser via XMLHttpRequest or page loads  SWANK-based development environment
  • 9.
    No Lisp atruntime  Compile to static files sent to browser  If done with a runtime, can use all of CL features for a build/deploy/cache system  Compile to library to be used by other JS code  Compile to JS client- and server-side (node.js) code at the same time  Deploy JS only  Parenscript used as static compiler for whole web stack
  • 10.
  • 11.
    What should JScode look like?  Write CL, but debug JS  Generated code has to be short, readable, idiomatic  ”Optimization” in the context of this talk means ”more readable code”  What about performance?  Can only assume that JS compilers try to optimize for code written by humans (ie – readable and idiomatic code)
  • 12.
    Statements vs Expressions  In JavaScript, can transform any statement to an expression by wrapping it in a lambda  Except for BREAK, CONTINUE, and RETURN  Many optimizations to avoid lambda-wrapping possible, depending on combination of special forms
  • 13.
    Function argument lists  Full Common Lisp lambda lists in JavaScript  In a way completely compatible with other JavaScript code  ARGUMENTS is great  NULL (argument provided) vs UNDEFINED (argument not provided)  Keywords: f(”key2”, value2, ”key1”, value1);  CL keywords and JS strings both self-evaluating
  • 14.
    Macros  Share the same representation (macro- function) in CL and PS compilers  But have different namespaces  Share same code between client (CL) and server (JS) even if implementation details completely different (ex – IO, graphics)  Without modifying CL code  PS macros can shadow CL functions
  • 15.
    Data structures  No car/cdr  Many Common Lisp forms are sequence oriented (DOLIST, LOOP, etc.)  Most idiomatic Common Lisp code uses these forms  Most code turns out to care about sequences, not linked lists vs arrays, and works as-is with JS arrays
  • 16.
    Namespace (Lisp-1 andLisp-N)  JavaScript is a Lisp-1  Common Lisp is a Lisp-4  With macros, Lisp-N  Parenscript tries to be a Lisp-2 (separate variable and function namespaces)  Code can be output unambiguously for lexically bound names (via α-renaming), but free variables referring to globals might cause conflicts  No thought given to hygiene
  • 17.
    Namespace (package system)  All Parenscript symbols are CL symbols  Packages can be given prefixes to namespace in JS:  foo::bar => foo_bar  Package system also used for obfuscation and/or minification  Exports list used to produce JS libraries with minified internals and prefixed API names
  • 18.
    Namespace (case andescape)  CL readtable case-insensitive by default, but can be set to preserve case  Parenscript recognizes mixed-case symbols and outputs them correctly  Simple translation for case-insensitive ones:  foo-bar => fooBar  JS reserved chars escaped:  foo* => foostar
  • 19.
    JavaScript dot operator  Causes confusion  Some people think it's ok to use it for namespacing  foo.bar is really (getprop foo 'bar)  What about foo.bar.baz(foobar)?  (funcall (getprop (getprop foo 'bar) 'baz) foobar)  Macros to the rescue:  (funcall (@ foo bar baz) foobar)
  • 20.
    JavaScript dot operator2  Why not (foo.bar.baz foobar)?  Parenscript 1 did it in compilation step  Turns out this doesn't work well with package system, breaks symbol macros  Symbol macros used for a lot of things in Parenscript 2 compiler  Possible to do it in the reader  But ”.” is already reserved for dotted lists  Dotted lists are evil; remove them from Lisp
  • 21.
    Lexical scoping  Done by using α-renaming to declare variables in nearest function scope: (lambda () function () { (let ((x 1)) var x = 1; (let ((x 2)) var x1 = 2; x))) return x1; }
  • 22.
    Lexical scoping: loops  For variables in loops, JS creates a shared binding for all iterations, breaking closures  WITH trick from Scheme2JS: (dotimes (i 10) for (var i = 0; i < 10; i += 1) { (lambda () (+ i 1))) with ({i : i}) { function () { return i + 1; };};};
  • 23.
    Dynamic scoping  TRY/FINALLY to set a global variable (let ((*foo* 1)) var FOO_TMPSTACK7; (+ *foo* 2)) try { FOO_TMPSTACK7 = FOO; FOO = 1; return FOO + 2; } finally { FOO = FOO_TMPSTACK7; };
  • 24.
    Multiple value return  ARGUMENTS has 'callee' property (function)  'callee' has 'caller' property (function)  Functions are JS objects, can attach arbitrary properties to them  To return MV:  arguments.callee.caller.mv = [val2, ...]  To receive MV:  others_vals = arguments.callee.mv
  • 25.
    Multiple value return2  Actually requires more cleanup code than this.  arguments.callee works even when calling to/from functions that don't know about Parenscript (a global ”return value arg” wouldn't)  Non-lexical returns (see next slide) with multiple values easier to implement (but aren't yet)
  • 26.
    Control transfer  JavaScript only supports RETURNing from innermost function, BREAKing out of innermost block or loop  Can implement CL's BLOCK/RETURN-FROM and CATCH/THROW using TRY-CATCH- FINALLY in general case  Optimizations possible depending on special forms, whether a return value is wanted
  • 27.
    Tools and futuredevelopments
  • 28.
    CLOS  Red Daly's PSOS library provides CLOS and Common Lisp's condition and resumable exception system  Not part of core Parenscript because it requires run- time library and introduces new types  Possible to build up a full Common Lisp in JavaScript this way using Parenscript as a cross-compiler
  • 29.
    Development environments  SWANK-based  slime-proxy  Non-SWANK  Numen (V8 only, to be released)
  • 30.
    Future directions  JS implementations have mostly caught on speed-wise  But have not, and most likely will not, catch up in efficient memory consumption  Would like a full Common Lisp implementation in JS on the server (V8, Rhino, etc.) and web browser (Firefox, Chrome, etc.)