Ask HN: Designing a Parenscript alternative

13 points by evanrmurphy ↗ HN
Several projects already exist which compile established dialects of Lisp into JavaScript, including Parenscript [1], Scriptjure [2], Scheme2Js [3] and ArcScript [4]. I'm trying to determine if there'd be value in creating a new little Lisp dialect specifically designed for compiling into JavaScript. The goal would be to end up with something like CoffeeScript [5], only s-expression based so that Lisp-style macros could be supported.

I've started designing the language following this general process:

1. Imagine what JavaScript would look like if it were composed of s-expressions rather than C-style syntax

2. Permit irregularities where it would be a convenience boon to everyday JavaScript coding

I will post example code in the comment thread, and feedback is very welcome. Do you think this project has potential, and would you consider using it? Do you have any advice about writing or hosting the compiler?

---

[1] http://common-lisp.net/project/parenscript/

[2] https://github.com/arohner/scriptjure

[3] http://www-sop.inria.fr/indes/scheme2js/

[4] http://evanrmurphy.com:8080/arc2js

[5] http://jashkenas.github.com/coffee-script/

13 comments

[ 5.1 ms ] story [ 42.7 ms ] thread
Infix and unary operators are pretty straightforward, since they're just prefix versions of their JavaScript counterparts:

  (&& x y)    => x && y
  (|| x y)    => x || y
  (+ x y)     => x + y
  (- x y)     => x - y
  (* x y)     => x * y
  (/ x y)     => x / y
  (% x y)     => x % y
  (+= x y)    => x += y
  (-= x y)    => x -= y
  (*= x y)    => x *= y
  (/= x y)    => x /= y
  (%= x y)    => x %= y
  (= x y)     => x = y
  (== x y)    => x == y
  (=== x y)   => x === y
  (!= x y)    => x != y
  (!== x y)   => x !== y  
  (! x)       => !x
  (++ x)      => ++x
  (-- x)      => --x
  (. x y)     => x.y
While the dot operator's base form should be a prefixed s-expression like all the others, it may be awkward to use in practice:

  (. (. ($ "body") (addClass "hidden")) (hide))
  
  => $("body").addClass("hidden").hide();
Since the dot operator is so common in idiomatic JavaScript, I propose allowing it to be infixed as a special case:

  ; expands to the above prefixed version

  ($ "body").(addClass "hidden").(hide)
For blocks we use a wrapper function rather than the curly brace block so that lexical scoping for variables can be ensured:

  (do (= x 5)   => (function() {
      (++ x))        var x;
                     x = 5;
                     return ++x;
                   }).call(this); 
               
The ternary operator:

  (?: x y z)   => (x ? y : z)
Perhaps we can create a useful distinction between the ternary operator and if-statements by giving if implicit do (just as cond has implicit progn in Common Lisp), but I'm not sure what the most elegant solution is. Thoughts?

Functions:

  (function)              => (function() {});
  (function ())           => (function() {});
  (function ()            => function() {
    (+ 1 1))                   return 1 + 1;
                             };
  (= foo (function ()     => var foo;
           (+ 1 1)))         foo = function() {
                               return 1 + 1;
                             }
  
We could build a simple unhygienic macro system using the traditional quote ('), quasiquote (`), unquote (,) and unquote-splicing (,@) operators, as well as rest parameters (&). Here's how you might define a macro called unless:

  ; macro definition

  (mac unless (x & args)
    `(if (! ,x) ,@args))

  ; example usage

  (unless true
    (alert "Won't be executed"))

  ; expands to:

  (if (! true) 
    (alert "Won't be executed"))

  => if (!true) {
       alert("Won't be executed");
     }
More coming...
IMHO the ternary operator isn't of much use since there should be IF, returning values.
Arrays will be supported using one of these forms:

  '(1 2 3)      => [1, 2, 3]
  ([] 1 2 3)
  [1 2 3]
Objects with one of these:

  ({} a 1 b 2)  => {a: 1, b: 2}
  {a 1 b 2}
There's also the possibility of bringing ideas from David A. Wheeler's sweet-expressions (http://www.dwheeler.com/readable/). If curly braces are used for object literals, then the curly infix notation could not be borrowed, but there would still be an opportunity to borrow prefixed grouping symbols, e.g. f(x), and significant indentation:

  $("#something").click(function()
    alert("I was clicked!"))

  => $("#something").click(function() {
       alert("I was clicked!");
     });
"While the dot operator's base form should be a prefixed s-expression like all the others, it may be awkward to use in practice"

The solution isn't special syntax, it's macros. Check out Parenscript's chain:

http://common-lisp.net/project/parenscript/reference.html#ch...

"For blocks we use a wrapper function rather than the curly brace block so that lexical scoping for variables can be ensured."

In most cases you can generate more efficient code by doing rudimentary control flow analysis. Look at how RETURN-FROM is implemented in the current Parenscript repository version.

Also, speaking of lexical scoping don't forget that in JS loop variables only get a single binding. Scheme2JS uses a neat trick with with to implement the proper (per-iteration) binding (see this paper: http://www-sop.inria.fr/indes/scheme2js/files/icfp2006.pdf). It's the only useful use of with I've ever encountered.

"The ternary operator"

Don't. The #1 goal of any new Lisp to JS translator should be to eliminate the statement/expression dichotomy.

"We could build a simple unhygienic macro system using the traditional quote ('), quasiquote (`), unquote (,) and unquote-splicing (,@) operators, as well as rest parameters (&)."

Take it one step further and make quasiquoting be able to build arrays at runtime. This is something that Daniel Gackle (gruseom) suggested for Parenscript and I need to implement. I actually want to take it a step further still and have a matching system like fare-matcher (http://www.cliki.net/fare-matcher) based around quasi-quoting. If you write all your code this way, you can go a long way towards eliminating dependence on the specific data structures you use for representing s-expressions. Right now I suspect you can go all the way, and that it's a viable strategy for making Parenscript self-hosting without adding runtime support for car/cdr to JavaScript.

Speaking of runtime libraries, I think it's an implicit assumption you made already, but if you want to be like Coffeescript or Parenscript, you obviously need to avoid those.

I'm quite curious about the examples. Maybe you should prefix the title with "Ask HN:". Write it in something lispy.
(function() { var x; x = 5; return ++x; }).call(this);

This won't loop but just return 6.

do is intended to be like do in Arc or Clojure or progn in Common Lisp. It is not supposed to be a loop. Does this clarify or have I misunderstood your comment?
I'm a bit puzzled now as I don't see a real benefit comparing this to the existing "compilers". Even worse: there's no code base.
Thank you for all your feedback so far. Is there anything about the existing lisp-to-js compilers that you wish was different?

I've just added a piece about the dot operator to the examples. I don't think any of the existing compilers have tried this approach.

AFAIR some ppl already have played around with that dot-notation. But I must admit that I just tried Parenscript some three years ago and was completely pissed off because it didn't have return values for everything. I want my code to be portable. Didn't touch any of those other translators.
"But I must admit that I just tried Parenscript some three years ago and was completely pissed off because it didn't have return values for everything."

Yeah, we're still working on that (mostly because I'm stupid). The expression/statement dichotomy is a wily satan.