Arc Forumnew | comments | leaders | submitlogin
Try Arc->Javascript (evanrmurphy.com)
8 points by evanrmurphy 5012 days ago | 19 comments


3 points by rocketnia 5012 days ago | link

Right away, I have a couple of things that I'd sorta like to see implemented (if you don't mind). On the other hand, it might just be that you haven't gotten around to them yet.

1) Strings aren't escaped, which makes them pretty unhygienic (albeit technically livable).

  "'"
  ''';
  
  "\\'"
  '\'';
This could be handled just by being more careful and running the string through a filter function before printing it out as compiled JavaScript. Note that the function would probably be defined within the scope of the private 'nest-lev variable so that it could put in the right number of backslashes.

2) Your 'compose isn't a metafn (and neither are 'andf and 'complement, but 'compose is the main one).

  (do:a b)
  (function(){var g5870=arraylist(arguments);return (function(){return apply(a,g5870);})['call'](this);})(b);
  
  (do (a b))
  (function(){return a(b);})['call'](this);
  
  (a:do b)
  (function(){var g5871=arraylist(arguments);return a(apply(do,g5871));})(b);
  
  (a (do b))
  a((function(){return b;})['call'](this));
This could probably be handled by putting yet another 'if case in 'js1. You could call out to metafn expansion utilities (like arc.arc's 'setforms does) or just hardwire the metafns into the 'if branch (like arc.scm's 'ac does).

Anyway, good stuff. ^_^ I especially like how arc.js is generated by js.arc. XD

-----

1 point by evanrmurphy 5011 days ago | link

Sorry about the lack of string escaping (that's awfully annoying! ^_^). It should be working now, at least for the common cases:

  "'"   ->  '\'';
  "\'"  ->  '\'';
Thanks for the input. I'm studying the metafn problem.

Edit: Actually backslash chars appear to escape incorrectly still. It's doing "\\'" -> '\\'' instead of "\\'" -> '\\\''. Working on it... Update: fixed.

-----

1 point by rocketnia 5011 days ago | link

Ack, there's something missing from that implementation: Nested string handling.

This is working well:

  "\\"
  '\\';
If you use 'quote to compile a string without escapes, you're fine:

  '"foo"
  '\'foo\'';
If you do the same thing where the only things to escape are quotes, you're fine, 'cause you call 'js-q, which uses 'nest-lev:

  '"'"
  '\'\\\'\'';
However, with other escaped things you're in trouble:

  '"\\"
  '\'\\\'';
I suggest putting 'js-charesc where 'js-q is now and having it use 'nest-lev directly. You might not even need 'js-q, since it could just be (js-charesc #\').

-----

1 point by evanrmurphy 5011 days ago | link

I'm not sure that's incorrect. Are you suggesting it should compile to this?

  '\'\\\\\'';
In Chrome's Javascript console, '\\'; and '\'\\\''; evaluate the same except for the extra pair of quotes.

-----

2 points by rocketnia 5011 days ago | link

Right. I'm going to use [] as makeshift quotes. The JavaScript ['\'\\\\\''] evaluates to ['\\'], which evaluates to [\]. The JavaScript ['\'\\\''] evaluates to ['\'], which evaluates to an error, 'cause it's an unterminated string.

-----

3 points by evanrmurphy 5011 days ago | link

OK. Now I'm going to use [] to illustrate how the Walls of Shame closed in on me as I read your last comment:

  [     :)     ]
    [   :|   ]
      [ :-o ]
        [:(]
You were right. ^_^

-----

1 point by evanrmurphy 5011 days ago | link

Could you help me to better understand the metafn issue? Those (do:a b) examples... what should they compile to? Does what you're talking about affect semantics, or just readability and performance?

-----

2 points by rocketnia 5011 days ago | link

When Arc compiles (a:b c), it compiles ((compose a b) c). When it compiles ((compose a b) c), it compiles (a (b c)). So in Arc, (do:a b) and (do (a b)) are basically equivalent, and so are (a:do b) and (a (do b)), where 'do is just an example macro. Since 'do is a macro, the equivalence does involve semantics; your transformation of (a:do b) calls a function named "do", when that function may not actually be defined.

Anyway, I'm just trying to make sure I inform you of stuff you may have overlooked. Once you know what I'm talking about regarding 'compose, it's up to you to decide whether you actually want to preserve an (a:b c)-(a (b c)) equivalence. ^_^ I am quite a fan of it, 'cause it saves some nontrivial edits (adding a closing parenthesis in a distant place and reindenting), but it's not sacred or anything.

-----

1 point by evanrmurphy 5011 days ago | link

Ah, I think I understand now. I had seen this comment from arc.arc:

  ; Composes in functional position are transformed away by ac.
And your examples show how 'compose gets transformed away, but I was having trouble visualizing a case where that wouldn't happen. Now it seems obvious to me: if you have (compose a b), rather than ((compose a b) c), then you can't transform compose away, because something is needed to make a and b act like one function.

-----

1 point by evanrmurphy 5012 days ago | link

A simple front-end to this compiler I've been working on. The main (and only?) deviation from arc syntax has to do with object/list refs versus function calls. Because of the difficulty I had disambiguating the (x y) form, for the moment each case gets its own form as follows.

Function calls:

  (fncall x y) -> x(y);           ; verbose
  (x y)                           ; succinct
   x.y
  (fncall x 'y) -> x(y);          ; verbose quoted
  (x 'y)                          ; succinct quoted
   x!y
Object refs:

  (objref x y) -> x(y);             ; verbose
                                    ; no succinct form for no quotes
  (objref x 'y) -> x('y');          ; verbose quoted
  (x `y)                            ; succint quoted
   x.`y
List refs:

  (listref x y)  -> car(nthcdr(y,x));    ; verbose
  (listref x 'y) -> car(nthcdr('y',x));  ; verbose quoted
                                         ; no succinct forms for listref
Suggestions on how to improve this situation are welcome. I greatly dislike the lack of short forms for listref and non-quoted objref. The only perk is that since Javascript arrays are objects and numbers don't mind being quoted, you get array access for free:

  (objref (array 'a 'b) 0)  -> ['a','b'][0];
  ((array 'a 'b) `0)                                ; same as above

  (let a (array 'a 'b)      -> (function(a){
    a.`0)                        return a[0];
                               })['call'](this,['a','b']);

  (.`0 (array 'a 'b))       -> (function(_){        ; using get ssyntax
                                 return _[0];
                               })(['a','b']);

-----

1 point by evanrmurphy 5012 days ago | link

I forgot to mention macros. You won't be able to play with them on the demo page. In the current model, you define macros in Arc using js-mac and functions in Javascript using the compiler (with the (js `(def ...)) form). To clarify:

- Everything is written inside js.arc [1].

- js.arc has the axioms defined with defs and most of arc.arc's macros with js-mac;

- it also defops arc.js, which has most of arc.arc's functions defined in Javascript [2].

[1] http://evanrmurphy.com/js.arc, also defines some in-progress Javascript utilities (mostly DOM-manipulation) and the demo page itself toward the bottom.

[2] http://evanrmurphy.com/arc.js

-----

3 points by fallintothis 5011 days ago | link

I finally looked over your code a bit. Thoughts:

- Harumph. Looking back, I notice my ssexpand-all doesn't strictly work right. Instead of considering the actual cases, I just special-cased quotes and quoted unquotes. Technically, these are wrong.

  arc> (ssexpand-all '`(a:b:c d))
  (quasiquote ((compose a b c) d))
  arc> (ssexpand-all '(fn (x:y) x:y))
  (fn ((compose x y)) (compose x y))
  arc> (ssexpand-all '(assign x:y z))
  (assign (compose x y) z)
ssexpand-all should have a similar shape to macex-all, though it's more complicated and probably doesn't help much.

  (def imap (f xs)
    (when xs
      (if (acons xs)
          (cons (f (car xs)) (imap f (cdr xs)))
          (f xs))))

  (def on-qq (f expr)
    (list 'quasiquote
          ((afn (level x)
             (if (is level 0)
                  (f x)
                 (atom x)
                  x
                 (is (car x) 'quasiquote)
                  (list car.x (self (+ level 1) cadr.x))
                 (in (car x) 'unquote 'unquote-splicing)
                  (list car.x (self (- level 1) cadr.x))
                  (imap [self level _] x)))
           1 (cadr expr))))

  (def ssexpand-all (expr)
    (if (ssyntax expr)
        (let expanded (ssexpand expr)
          (if (is expanded expr)
              expr
              (ssexpand-all expanded)))
        (check expr
               atom
               (case (car expr)
                 quasiquote (on-qq ssexpand-all expr)
                 fn         `(fn ,(cadr expr) ,@(imap ssexpand-all (cddr expr)))
                 assign     `(assign ,(expr 1) ,(ssexpand-all (expr 2)))
                 quote      expr
                            (imap ssexpand-all expr)))))
Bah. It feels like I've written this code about a million times. Gross.

- I notice you wind up copy/pasting a lot of arc.arc definitions, particularly for macros. Couldn't it be more general? I.e., add a clause like

  (isa (car s) 'mac)   (js (macex1 s))
to js1. I figure I'd want any ol' macro to expand, just so I could reuse existing code. Plus, this gives you all those verbatim arc.arc definitions for free. But you'd still want js-macs, since there are certain macros that shouldn't "spill over". E.g., the non-verbatim arc.arc definitions need to be tweaked for Javascript, but shouldn't be globally overwritten. You could make js-macs take priority over regular macros by checking for them first in js1, and still reduce copy/paste work. Would anything break from this?

- Really, the point about macros expands into a broader one. My first inclination is to model the compiler on ac.scm; once that's working, you get arc.arc & libs.arc for free just by cranking the compiler over them. Keyword collisions could be mitigated as in ac.scm, which prepends underscores to variable names.

  arc> x
  Error: "reference to undefined identifier: _x"
But this "compiler-oriented" approach might not work well. There are the Javascript keywords that you want to use from Arc without them getting treated like variables (mainly the reserved words). It's interesting because this project isn't just an Arc-to-Javascript compiler, but also a DSL for Javascript in Arc. So there needs to be some sort of balance.

There are more "overlap-y" examples like while. It's implemented as a macro in arc.arc, but you'd probably want to compile it down to Javascript's while instead of tail-recursive function calls. Also, some features don't line up quite right, like first-class continuations. Though you could compile them into Javascript (that'd be cool!), a big use for them is simply

  (catch
    (while t
      (throw)))
which in more idiomatic (and probably more efficient) Javascript would be a while/break. So it isn't just calls to something specific like while, but also entire patterns of code that overlap. In the limit, you have Arc compile to complex Javascript, but I don't know how well Javascript handles as a target language for some of the crazier features like continuations.

I'm curious if you've tried this approach at all and, if so, how it panned out (I've never tried anything like it myself, so I wouldn't know).

Good work so far!

-----

1 point by evanrmurphy 5011 days ago | link

As I was going through copy-pasting and tweaking definitions from arc.arc, I thought several times that there should be a smart way to automate the process. (And it is true that a large portion of the defs and macs are verbatim.)

For a long time I didn't have js-mac and wasn't using js-def. Each macro got a handwritten js-<insert name> function and its own branch in js1's if; each function was handwritten in Javascript. [http://arclanguage.org/item?id=11918] I finally got smart enough to start using the partially bootstrapped compiler, and abstracting away the verbatim definitions should follow from that.

You could make js-macs take priority over regular macros by checking for them first in js1, and still reduce copy/paste work. Would anything break from this?

I do think that will be the way to do it, and it shouldn't break anything.

It's interesting because this project isn't just an Arc-to-Javascript compiler, but also a DSL for Javascript in Arc.

I knew this was true but hadn't yet found a way to articulate it. Thanks for the good choice of words.

There are more "overlap-y" examples like while. It's implemented as a macro in arc.arc, but you'd probably want to compile it down to Javascript's while instead of tail-recursive function calls.

Yes. Since resources are so precious in the browser and Javascript doesn't optimize tail calls, this will probably be an important refinement. As a bonus, it may make the compiled js more readable, since (while foo bar) would be generating something like while(foo){bar;}, rather than:

  (function(g3701){
    return g3701=(function(g3702){
      return (g3702?function(){bar;return g3701(foo);})['call'](this):nil);
    });
  })['call'](this,nil)(foo);
Also, some features don't line up quite right, like first-class continuations...

Your idea of compiling the common case to while/break sounds fine, but wouldn't try/catch be suitable as well? (I haven't made enough use of either first-class continuations or Javascript's try/catch to know yet.) There have been some cases I've dealt with already where the features didn't quite line up: rest/optional parms for functions, conses/lists vs. arrays and nil vs. false/null/undefined.

Thanks for the valuable feedback.

-----

1 point by rocketnia 5011 days ago | link

(fallintothis) It's interesting because this project isn't just an Arc-to-Javascript compiler, but also a DSL for Javascript in Arc.

(evanrmurphy) I knew this was true but hadn't yet found a way to articulate it. Thanks for the good choice of words.

I was also impressed by that choice of words. XD

-

wouldn't try/catch be suitable as well?

That's what I would use. That way a continuation could be called from within a (do ...) boundary.

-----

1 point by evanrmurphy 5012 days ago | link

What would be a good name for this project? I've considered:

  Arc->Javascript
  arc2js
  js.arc + arc.js
  JarcScript       ; if jazzdev doesn't mind :)
Also, I'm thinking of posting this on HN. Any suggestions on how to go about presenting it there?

-----

2 points by evanrmurphy 5011 days ago | link

garply may have taken the best name for his related project: arcscript. Wish I'd thought of that first. :)

Update: Another candidate is tojs, in the vein of aw's tojson and fromjson. I may be using these 3 programs together a lot for ajax.

-----

2 points by garply 5010 days ago | link

Please feel free to use arcscript. I only dumped that file into the repo because I had it lying around and thought someone might have use for it. You got around to completing a nice functional JS compiler implementation before I did and I'm glad you did. Feel free to upload on top of my file :)

-----

1 point by evanrmurphy 5010 days ago | link

Wow, thank you very much.

-----

1 point by fallintothis 5011 days ago | link

I think js.arc or arc2js work well, for what it's worth.

-----