ParenScript

I actually have a lot of real work to do, so I had to quit my “opensource-programming” procrastination as fast as possible. That’s why I rushed the release of my Lisp to Javascript “compiler” ParenScript. Compiler is actually a very big word, as ParenScript doesn’t do much more than correctly indent a Lisp expression representing Javascript code (it does a few optimization on the way, though). I had at first written a very hackish implementation of the compiler, with an undefinite intermediary representation somehow consisting of strings and lists of strings. After I got the mess working, I decided to rewrite the compiler in a clean way. ParenScript in the version 0.1.0 actually is a lot cleaner, a few people may even understand its inner workings, but there still is a lot to do. It has been given next to none testing in a real setup. The indentation is a bit weird sometimes, and could be given a lift up. The error handling is very minimal.

However, as rushed as I was to release ParenScript, I put the whole day into its manual, and I think it has turned out to be quite nice. Here is the ParenScript webpage, the software, the manual and the language reference. ParenScript it also installable using ASDF-INSTALL, just type

(asdf-install:install 'parenscript)

and it should install fine.

To give you a little idea of what ParenScript does, here is a ParenScript program and the generated JavaScript:

(defun apply-effect () 
   (when (and document.all photoslider.filters) 
      (let ((trans photoslider.filters.reveal-trans)) 
          (setf (slot-value trans ’*Transition) 
                (floor (* (random) 23))) 
          (trans.stop) 
          (trans.apply))))

The generated JavaScript code looks like this:

function applyEffect() { 
   if (document.all && photoslider.filters) { 
      var trans = photoslider.filters.revealTrans; 
      trans.Transition = Math.floor(Math.random() * 23); 
      trans.stop(); 
      trans.apply(); 
   } 
} 

From the feature list:

  • Supports all the expressions and statements of EcmaScript 262
  • Converts Lisp symbols to java-style camelcase (taken from linj)
  • Lisp-style iteration with DO, DOTIMES, DOEACH, DOLIST
  • Complete macro environment with MACROLET, SYMBOL-MACROLET
  • Uses the Lisp reader for reader macros
  • ParenScript HTML generator for embedding HTML in JavaScript code (not more ugly document.writes)
  • Allows for recursive compilation of ParenScript (generate Javascript in Javascript in Javascript…)
  • Lispy CSS syntax

UPDATE: I’ve blogged about the javascript compiler before. In english here: javascript experiences, and in german here: lisp to CSS, javascript, so eine geile sprache, javascript expressions , javascript compiler and lisp to javascript.

This entry was posted in English, Lispstaub. Bookmark the permalink.

17 Responses to ParenScript

  1. Anonymous says:

    Insanely cool!

  2. Anonymous says:

    What is the license for ParenScript?

  3. manuel says:

    It’s BSD. I am a tad busy right now, but I will make a new release next week, with an updated manual, a LICENSE file, and a lot more examples 🙂

  4. Ivan Toshkov says:

    I found a bug in the dotimes expansion. It increments the counter by two, because

    (dotimes (i a)
    …)

    expands to

    for (var i = 0; i != args.length; i = i++) {
    …
    }

    This has the unfortunate efect that if the list has even elements this will loop forever.

    The fix is rather trivial, though it generates i = i + 1, rather than i++ :

    — js.lisp~ 2005-03-14 23:44:19.000000000 +0200
    +++ js.lisp 2005-05-04 18:30:45.166825686 +0300
    @@ -942,7 +942,7 @@
    (defjsmacro dotimes (iter &rest body)
    (let ((var (first iter))
    (times (second iter)))
    – `(do ((,var 0 (++ ,var)))
    + `(do ((,var 0 (1+ ,var)))
    ((= ,var ,times))
    ,@body)))

  5. Ivan Toshkov says:

    Actually, the problem is that `i = i++’ leaves `i’ unchanged.

    I’ve put this bug report and the patch at http://www.cliki.net/parenscript

  6. mike says:

    i found i like the style of your blog, and i also have used html tools or JavaScript tools at http://www.yaodownload.com/web-authoring/ , such as HTML to AnyCode Converter , it can translate some HTML code to JavaScript, ASP or other scripts.

  7. Hexstream says:

    Maybe I’m crazy, but mike’s comment reads as “clever” spam.

    ?

  8. manuel says:

    Yeah you’re right, slipped pas the spam recognition. 🙂

  9. chiz says:

    Good post. You make some great points that most people do not fully understand.

    “ParenScript in the version 0.1.0 actually is a lot cleaner, a few people may even understand its inner workings, but there still is a lot to do. It has been given next to none testing in a real setup. The indentation is a bit weird sometimes, and could be given a lift up. The error handling is very minimal.”

    I like how you explained that. Very helpful. Thanks.

  10. Voice of Reason says:

    This is an interesting hack. Since you posted this, the project has been scrapped and replaced with another project called “Parenthetix”, which departs radically from the original design. Parenthetix is much more Java-like (or, should I say, much more Pascal-like). It has semi-colons and a complicated grammar. Imperative programming style is much more pervasive in Parenthetix than it was in ParenScript. ParenScript can at least be adapted for functional programming, even though it doesn’t do it out of the box.

    I don’t care for Parenthetix because it takes away the promise that ParenScript had: To make JavaScript more like Lisp.

    With a few macros on the Lisp side, and a few support functions, you can eliminate many of the situations where ParenScript forces you into imperative programming. For example, the following creates a ParenScript macro that allows you to define functions that behave like Lisp functions (that is, they evaluate to a certain value, without an explicit “return” statement… the -f suffix stands for “functional”):

    (defmacro+ps defun-f (name lambda-list &rest body)
    `(defun ,name ,lambda-list
    ,@(reform-body body)))

    ;; The macro requires this function:

    (defun reform-body (body)
    (if (= (length body) 1)
    `(,(append `(return ,(car body))))
    (append (all-but-last body)
    `(,(append ‘(return) (last body))))))

    ;; There’s also a lambda-f, let-f, and progn-f, but I’ll leave them out for brevity.

    ;; You can also have a genuine Lisp “cond” (expands into
    ;; a hornet’s nest of if’s, but you get the right semantics):
    (defun cond-expand (conditions)
    (if conditions
    (if (equalp (caar conditions) t)
    (cadar conditions)
    `(if ,(caar conditions)
    ,(cadar conditions)
    ,(cond-expand (cdr conditions))))))

    (js:defjsmacro cond (&rest conditions)
    (cond-expand conditions))

    ;; JavaScript has a “random”, and ParenScript uses it,
    ;; but it has different semantics from the CL function
    ;; with the same name. This macro fixes that (the
    ;; random-state argument is ignored):
    (js:defjsmacro random (num &optional random-state)
    `(*math.floor (* (*math.random) ,num)))

    I’ve spent some time trying to get a small CL program I wrote while learning Lisp to compile into runnable JavaScript with ParenScript. Certain CL functions can be implemented in ParenScript itself, such as car, cdr, find-if, nth, nthcdr,
    and assoc.

    The nth, car, and cdr functions have special semantics when it comes to NIL (namely, (= (nth n nil) nil)
    evaluates to true, whereas (= (aref nil n) nil) is an error), and therefore, the AREF function is not equivalent to them.

    I’ve written a half-assed version of equalp. At least I _think_ it works.

  11. Voice of Reason says:

    Forgot to include the definnition of ALL-BUT-LAST:

    (defun all-but-last (a-list)
    (if (cdr a-list)
    (cons (car a-list) (all-but-last (cdr a-list)))
    nil))

  12. Evaine says:

    Good post. You make some great points that most people do not fully understand.

    “I actually have a lot of real work to do, so I had to quit my “opensource-programming” procrastination as fast as possible. That’s why I rushed the release of my Lisp to Javascript “compiler” ParenScript. Compiler is actually a very big word, as ParenScript doesn’t do much more than correctly indent a Lisp expression representing Javascript code (it does a few optimization on the way, though). I had at first written a very hackish implementation of the compiler, with an undefinite intermediary representation somehow consisting of strings and lists of strings. After I got the mess working, I decided to rewrite the compiler in a clean way. ParenScript in the version 0.1.0 actually is a lot cleaner, a few people may even understand its inner workings, but there still is a lot to do. It has been given next to none testing in a real setup. The indentation is a bit weird sometimes, and could be given a lift up. The error handling is very minimal.”

    I like how you explained that. Very helpful. Thanks.

  13. battery says:

    […] Unfortunately, this isn’t the first time I’ve had this problem. Enter Six Apart. When signed up and tested, I couldn’t leave comments on my own brand new TypePad blog (tdavid.typepad.com). This was around Christmas time two years ago and it took a week or so to find out that the reason my comments were being blocked was because makeyougohmm.com was listed on an old spam service. […]

  14. laptop batteries says:

    e focus on bring the best acer btp-550 battery sales to our customers and strong desire our customers can buy their satisfaction acer btp-550 laptop battery. We are committed to providing customers with high-quality, high-capacity and low price btp-550 laptop batteries.

  15. Saniya says:

    Excellent Post, thanx for sharing the same.. Will keep on reading the post 😀

    Stumbled your post .. cheers

Leave a Reply

Your email address will not be published. Required fields are marked *