And here is a fantastic example of why macros are incredibly awesome, and why JS could really use macros. Let's suppose you wanted to generate the following DOM structure:
Now, you could of course write that out as a string and use innerHTML, etc. But what about event listeners? What if you want one of the node's attributes to vary depending on another node? Now you get into the verbosity that is the DOM. Let's start with the traditional way:
var div1 = document.createElement("div");
div1.setAttribute("test", "yes");
var div2 = document.createElement("div");
div2.setAttribute("foo", "bar");
var div3 = document.createElement("div");
div3.setAttribute("qux", "corge");
var div4 = document.createElement("div");
div4.setAttribute("corge", div1.getAttribute("test") + "no");
div2.appendChild(div3);
div2.appendChild(div4);
div1.appendChild(div2);
Okay, so we need to give each element a unique name... and we need to deal with accurate-but-verbose names like setAttribute... and this is all flat: it doesn't show the nested structure of the DOM. So when working on my Chrome extensions, I use a little library that provides a UI.create function:
UI.create("div", function (x) {
x.setAttribute("test", "yes");
x.appendChild(UI.create("div", function (self) {
self.setAttribute("foo", "bar");
self.appendChild(UI.create("div", function (self) {
self.setAttribute("qux", "corge");
}));
self.appendChild(UI.create("div", function (self) {
self.setAttribute("corge", x.getAttribute("test") + "no");
}));
}));
});
This shows the structure of the DOM, and due to lexical scope, I can give every element the same name if I want to. I consider this an improvement over the standard DOM, but it's still far too verbose...
But with my compiler, I wrote a "div" and "w/div" macro, so now I can write this:
(w/div x test "yes"
(div foo "bar"
(div qux "corge")
(div corge (+ (x.getAttribute "test") "no"))))
Well, gosh. Not only is this drastically shorter and easier to read... but it shows the DOM's nested structure. w/div lets you create a new div with a specific name (in this case, "x"), and div is just like w/div, except it prechooses the name "self" for you. You can think of them like rfn and afn, but for DOM elements.
And since this macro expands into the verbose DOM methods, I can do things like add event listeners. Thus, this method is by far the best: you get all the flexibility of the DOM, but with minimal verbosity.
I have started work on an Arc to JavaScript compiler. Unlike ArcLite, this does not use an interpreter written in JS. Instead, the Arc code is translated directly into a JavaScript string, which can then be evaluated in your favorite JS interpreter (V8 is quite fast).
I actually just started this today, so naturally it may be buggy and is missing a lot of functionality, but basic functions, function calls, and macros work:
Because this is a direct one-to-one compiler, I think it would be more accurate to think of it as a thin Arc-like syntax for JS, rather than as a port of Arc. Basically, this is just like CoffeeScript, but for Arc.
So, if you're used to Arc but have never programmed in JavaScript, certain things will trip you up. For starters, all numbers in JS are 64-bit floating point, there are no ints. Thus, Arc ints become JS floating points.
Also, strings in JS may differ somewhat from Arc strings, but they should be reasonably similar (both support \n and \t syntax, etc.) One difference is that JS supports both ' and " for strings, but Arc only supports "
Also, JS has a lot of things that evaluate to false: "", null, undefined, false, NaN, and 0
Arc only has nil, everything else is true.
---
I still need to get table notation working. Once that's done, you should be able to do stuff like this:
var foo = document["createElement"]("div");
foo["setAttribute"]("bar", "qux");
---
Because it's just a thin skin over JS and not a full port, the primary reason you would want to use arc2js is if you want to program in JS, but you like Arc syntax and macros.
Which reminds me. Because the compiler does not have access to the JS environment, there is a clean separation between JS and Arc code: Arc macros can call any Arc function, but JS code can only call JS code.
Unfortunately, that means the following won't work:
(= browser ...) ; code to detect what the browser is
(mac something ()
(case browser
ie ...
gecko ...
webkit ...
opera ...))
Because the macro "something" doesn't have access to the JS environment, it can't expand into different code depending on what the JS code does. It'll have to do the browser check at runtime.
Also, you may have noticed that the output includes the actual gensym name. This is unfortunate, but JS doesn't have gensyms, so it's the best we can do.
Notice how "w/div" and "w/element" don't appear at all in the JS: they're expanded away by the compiler. In case you're curious, the above code generates the following DOM structure:
<div>
<div foo="bar"></div>
</div>
---
Another nifty change I did was add in a couple optimizations for common cases. For instance, before, if you used this:
(def foo ()
(let a 50
(+ a 10)))
It would compile into this:
var foo = (function () {
return (function (a) {
return (a + 10);
})(50);
});
This is correct in all circumstances, but it's inefficient in the very common circumstance of a let inside a function. So I optimized it so it now compiles into this:
var foo = (function () {
var a = 50;
return (a + 10);
});
Aside from the superfluous parentheses, that code is just as fast as if you had manually wrote it! I also optimized `do` within functions:
Just added in optional and rest args for functions:
(fn (a b (o c 5))
(list a b c))
(function (a, b, c) {
c = c || 5;
return [a, b, c];
})
(fn args args)
(function () {
var args = Array.prototype.slice.call(arguments);
return args;
})
(fn (a b . c)
(list a b c))
(function (a, b) {
var c = Array.prototype.slice.call(arguments, 2);
return [a, b, c];
})
I think that's enough for one day. I'm not sure if a simple boolean test is enough for optional args. Perhaps they should test explicitly against undefined?
Oh yeah, and another neat thing: my compiler knows the difference between necessary and superfluous whitespace. By default it pretty-prints it, so the JavaScript looks nice. But you can use a mode that will minify it for you:
Also, I would like to note that some Arc macros work out of the box with my compiler. As already shown, `let` and `with` work, since they expand into functions. Another one that works fine is rfn/afn. And even ++ works:
(def foo ()
(let a 10
(++ a 5)
(+ a 30)))
var foo = (function () {
var a = 10;
a = (a + 5);
return (a + 30);
});