Difference between revisions of "Jmacro"

From HaskellWiki
Jump to navigation Jump to search
Line 76: Line 76:
 
-- (i.e., things which may be executed -- something of type JStat).
 
-- (i.e., things which may be executed -- something of type JStat).
   
--J
 
 
test :: JStat
 
test :: JStat
 
test = [$jmacro|
 
test = [$jmacro|

Revision as of 15:42, 9 July 2010


Jmacro is a library for the programmatic generation of Javascript code. It can be used to directly write Javascript, and as a backend for combinator libraries and domain specific languages which target Javascript. The jmacro package also provides a standalone executable, which may be used to compile jmacro syntax directly to Javascript.

Jmacro is available from hackage: http://hackage.haskell.org/package/jmacro

A source repository is available from patch-tag: http://patch-tag.com/r/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;

Antiquotation, Marshalling and Unmarshalling, Shared Scope

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
           fun add 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 adjunction, 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.