Jmacro
JMacro is a library for the programmatic generation of Javascript
code. It is designed to be multipurpose -- it is useful whether you
are writing nearly vanilla Javascript or you are programmatically
generating Javascript either in an ad-hoc fashion or as the backend to
a compiler or EDSL.
It provides support for hygienic names, as well as sharing of names between the generated Javascript and embedded Haskell antiquotation.
The jmacro package also provides a standalone executable, which may be used to compile JMacro syntax directly to Javascript.
JMacro provides a simple, lightweight quasiquoted syntax that is mainly compatible with standard Javascript. Most Javascript code in the wild can be used as JMacro code with no or minimal modification. However, JMacro extends Javascript syntax in a number of Haskell-friendly ways, including whitespace function application and single character lambdas. Syntax is statically checked at compile time. JMacro expressions may contain antiquoted Haskell code. This code may generate further JMacro code, or it may generate any of a range of standard Haskell types, which are able to be marshalled into JMacro through typeclass methods.
Jmacro is available from hackage: http://hackage.haskell.org/package/jmacro
A source repository is at: https://hub.darcs.net/gershomb/jmacro
Key Features
Syntax Checking
JMacro provides compile-time guarantees of syntactic correctness. Any jmacro code which compiles will necessarily produce syntactically correct Javascript. JMacro supports Javascript's literal regexes, and regex literals are checked statically at compile time as well.
Note that jmacro itself is untyped, which means that the generated javascript may not be typesafe. This is a feature, as a great deal of useful javascript code is either hard to type, or simply not statically typeable. Take, for example, the commonly used jQuery library. Depending on the string argument to the $ function, the returned value can vary over a range of types.
Functional Syntax
Jmacro provides a mixed-functional syntax. Nearly all valid javascript code is also valid jmacro code. Code from the documentation of an existing library can be copied and pasted directly into Haskell as a jmacro block. However, jmacro also provides a great deal of syntactic sugar that allows code to be written in a considerably more functional style, akin to Haskell or ML. Notably, anonymous functions may be introduced with a single backslash, and function application may be denoted by simple whitespace. Hence, this is valid jmacro describing a function which doubles a number:
\x -> x + x
Hygienic Names, Proper Scoping
It is common, when generating Javascript, to want to compose blocks of code which operate on different parts of a page. These blocks of code may find it necessary to introduce top-level variables for sharing state. Typically, this is resolved by placing these blocks of code within an anonymous function to provide namespacing. When two blocks of jmacro code are composed, however, variable declarations within each will *automatically* be named in the generated code so as not to overlap. This additionally allows jmacro to provide proper block-level scoping, which does not exist in standard Javascript.
Thus, in the following example, the two declarations of `var x` produce *different* identifiers in the generated javascript:
> renderJs $ [$jmacro|var x;] `mappend` [$jmacro|var x;]
var jmId_0;
var jmId_1;
The ability to provide hygienic names becomes particularly useful when coupled with antiquotation. Within a Haskell program, quoted jmacro code blocks may contain antiquotations, which themselves contain Haskell code. The antiquoted Haskell code may either produce a jmacro expression or statement itself, or it may produce a Haskell value which can be marshalled to jmacro (i.e., is an instance of the ToJExpr class).
Marshalling a number:
> let foo = 5 in renderJs [$jmacro|var x = `(foo)`;|]
var jmId_0;
jmId_0 = 5;
Marshalling a string:
> let foo = "hello" in renderJs [$jmacro|var x = `(foo)`;|]
var jmId_0;
jmId_0 = "hello";
Passing in another expression:
> let foo = [$jmacroE|alert "yo"|] in renderJs [$jmacro|var x = `(foo)`;|]
var jmId_0;
jmId_0 = alert("yo");
Antiquoted Haskell code has full access to all identifiers in scope within the enclosing jmacro block. This allows for the construction of macros:
> let foo = \str -> [$jmacroE|alert `(str)`|] in renderJs [$jmacro|var x = "hello"; var y = `(foo x)`;|]
var jmId_0;
jmId_0 = "hello";
var jmId_1;
jmId_1 = alert(jmId_0);
Usage
{-# LANGUAGE QuasiQuotes #-}
import Language.Javascript.JMacro
import Data.Monoid
-- The jmacro quasiquoter produces a statement or block of statements
-- (i.e., things which may be executed -- something of type JStat).
test :: JStat
test = [$jmacro|
// function declarations produce absolute (not hygienic) names
// functional style declarations return the value of an expression
// note that this is not curried, and thus cannot be partially applied.
fun add x y -> x + y;
// this function is explicitly curried, and can/must be partially applied
fun addTwo x -> \y -> x + y;
// imperative style declarations execute a block of statements
fun mult x y {
var tmpAns = x * y;
return tmpAns;
};
// variable declarations produce hygienic names
var x = "this will be renamed";
// we may "escape" hygiene by using bangs
var !y = "this will really be named y";
// functions can be declared using anonymous lambda syntax
// and then assigned hygienic names
var anonAdd = \x y -> x + y;
// anonymous functions can also be written in imperative style
var anonMult = \x y {return (x * y);};
// functions may be invoked with parenthesis
var addThenMult = mult(2,add(2,3));
// functions may also be invoked with juxtaposition, as in Haskell
var multThenAdd = add 2 (mult 2 3);
|]
-- The jmacroE quasiquoter produces expressions (i.e. things which
-- have *values*, something of type JExpr).
testE = [$jmacroE|addThenMult + multThenAdd|]
-- Antiquotations in an expression position can contain JExprs (but not JStats)
testAQ = [$jmacro|var x = `(testE)`;|]
-- Antiquotations in a statement position can contain either
-- (but JExprs which are not function invocation will be discarded)
testAQ2 = [$jmacro|`(test)`; `(testE)`;|]
-- Statements may be composed as monoids. (Expressions may not be).
testCompose = test `mappend` test
-- JMacro blocks are rendered to Text.PrettyPrint.HughesPJ Docs.
-- These Docs can be formatted more carefully, or simply
-- converted to Strings via Show.
renderedJavascript :: String
renderedJavascript = show $ renderJs test
-- See the Language.Javascript.JMacro.Prelude module for further examples.
Additional Syntax Extensions
JMacro now has a few extra syntax extensions.
Infix Application
JMacro has an infix application operator, and a reversed infix application operator. foo <| bar <| baz
desugars to foo(bar(baz))
. Meanwhile, foo |> bar |> baz
desugars to to baz(bar(foo))
. Since Javascript functions are not curried by convention, these functions are slightly less useful than one might hope. However, an explicit Javascript curry function is easy enough to write.
Destructuring Bind
JMacro features an enhanced destructuring bind, in function and variable declarations, as well as lambda abstractions. The syntax handles lists, maps, and "@" patterns. Maps are denoted with oxford braces ( {| ... |}
) for syntactic clarity. An infix cons (as :|) is also available in both the left and right hand sides of expressions.
fun foo [x,y] -> x + y;
fun someMap@foo{| lbl:val, someotherlbl: anotherVal |} {return [someMap, val]}
var x = \[a,b] -> a;
var [x,y] = foo(bar);
var (x:|xs) = 1 : 2 : 3 : [];