Commits

Anonymous committed 768605a Merge with conflicts

Merge branch 'master' into constraints

Conflicts:
.gitignore
grunt.js
package.json
src/compile.js
src/grammar.js
src/macroexpand.js
src/nodes.js
src/typeinference.js
test/CompileSpec.js

Comments (0)

Files changed (38)

+lib/*.js
+
 node_modules
 dist
 docs/guide/_build/
 test/fixtures/**/*.js
 test/fixtures/**/*.js.map
 examples/*.js
-examples/*.js.map
-lib/*.js
 roy.brianmckenna.org
 roy.js
 roy-min.js
 *~
 *.swp
 *.roym
+*.js.map
+*.sublime*
 [submodule "misc/roy-mode"]
 	path = misc/roy-mode
 	url = https://github.com/folone/roy-mode.git
+[submodule "misc/vim-roy"]
+	path = misc/vim-roy
+	url = https://github.com/jb55/Vim-Roy.git
+node_modules
+dist
+docs/guide/_build/
+examples/*.js
+roy.brianmckenna.org
+roy.js
+roy-min.js
+.DS_Store
+*~
+*.swp
+*.roym
+*.js.map
+*.sublime*
+language: node_js
+node_js:
+  - 0.8
+  - 0.6
+# List of contributors in alphabetical order.
+# If you'd like to use your real name, email and/or website, please edit this file to do so.
+# Format is: Name <email> (website)
+
+afcowie
+alsonkemp
+alvivi
+Brian McKenna <brian@brianmckenna.org> (http://brianmckenna.org/)
+brow
+Constellation
+DamonOehlman
+dbousamra
+esehara
+gregwebs
+Hardy Jones <jones3.hardy@gmail.com> (https://github.com/joneshf)
+i80and
+Bill Casarin <bill@casarin.me> (https://github.com/jb55)
+jedws
+joseanpg
+kasperlanger
+miikka
+mpenet
+nickmeharry
+non
+paulmillr
+Alexander Solovyov <alexander@solovyov.net> (https://solovyov.net/)
+raimohanska
+rehno-lindeque
+stew
+taku0
+tokland
+twopoint718
+## Roy Change Log
+
+### 0.2.2
+##### 2013-06-29
+
+* Fix #182: TypeError: Cannot set property 'leadingComments' of undefined
+
+### 0.2.1
+##### 2013-06-29
+
+* Fix #180: Support for scientific notated numbers?
+* Fix evaluation of Roy code on website.
+* Move jison from dependencies to devDependencies.
+
+### 0.2.0
+##### 2013-06-29
+
+* **BREAKING CHANGE** Removed metaprogramming.
+* **BREAKING CHANGE** Changed array accessor from `!` to `@`.
+
+* Compiler rewritten as proper AST using escodegen.
+* New test suite.
+* Replaced interleaved with rigger.
+* Started using codemirror on the site.
+
+* Added boolean not `!`.
+* Added CompileSpec, TarjanSpec and TypeInferenceSpec to test suite.
+* Added dependencies (escodegen, source-map).
+* Added devDependencies (grunt, jasmine-node, rigger).
+* Added documentation for array access.
+* Added grunt support.
+* Added module (vim-roy).
+* Added scientific notation support.
+* Added shebang.
+* Added strings as keys for objects.
+* Added tests (match_expression_single_eval, monoid, object).
+* Added travis support.
+* Added type classes.
+
+* Removed devDependencies (interleave).
+* Removed example (macro).
+* Removed meta-programming example from site.
+
+* Updated dependencies (jison, unicode-categories).
+* Updated examples (defferedmonad, option).
+* Updated miscellaneous (chrome-extension, roy-mode, roy.tmbundle).
+* Updated prelude (nil, leftMonad, rightMonad).
+
+### 0.1.5
+##### 2012-03-19
+
+* Fixed typo in introduction documentation.
+
+* Added examples (module, node_module).
+* Added files (module, macroexpand, typegrammar).
+* Added module example to site.
+* Added strings, object type example, native types and regex to types documentation.
+* Added tests (accessors, coercing_native_to_any, conditionals, where).
+
+* Updated examples (data, option, structural).
+* Updated grammar (generic, function types, where).
+* Updated prelude (π).
+* Updated tests (deep_matching, tagged_unions).
+
+### 0.1.4
+##### 2012-01-23
+* **BREAKING CHANGE** lambda syntax `\[args] -> <body>` instead of `fn [args] = <body>`.
+* **BREAKING CHANGE** do syntax `<bound name> <- <expr>` instead of `bind <bound name> = <expr>`.
+
+* New features
+    * Optional type parameter lists for arguments.
+
+* Color console for repl.
+* Conversion from mercurial to git.
+* Converted examples to new lambda style (funcs, option, structural).
+* Converted tests to new syntax (functions, trace_monad).
+* Help in repl and binary.
+* Makefile supports extensions.
+* Renamed stdlib to prelude and extended.
+* Started documentation (index, introduction, types).
+
+* Added ajax and deferred examples to site.
+* Added chrome extension.
+* Added dependencies (unicode-categories).
+* Added examples (ajaxmonad, deferredmonad, fizzbuzz, sqrt, tracemonad).
+* Added MIT license.
+* Added tests (map, option_monad, unicode).
+
+* Updated devDependencies (interleave).
+* Updated examples (data, types).
+* Updated roy-mode and roy.tmbundle extensions.
+
+### 0.1.3
+##### 2011-11-19
+
+* Specific `jison` version.
+* Fixed fall-through bug in repl.
+
+### 0.1.2
+##### 2011-11-18
+
+* New features
+    * Compile-time meta-programming.
+    * Simple tagged unions.
+    * Pattern matching.
+    * Structural typing.
+    * Monad syntax.
+    * Not-horrible JS output.
+
+* Move from toy language to full fledged language.
+
+* Added examples (alias, data, macro, option, stdlib, structural).
+* Added stdlib.
+* Added website.
+* Added test suite (deep_matching, functions, primitive_types, tagged_unions, trace_monad).
+
+* Updated examples (funcs, types).
-.PHONY: deps site extension test
+.PHONY: deps site extension lint test
 
 all:
 	./node_modules/.bin/grunt
 	npm install
 	npm prune
 
-site: all bundle
+site: all
 	[ -e roy.brianmckenna.org ] || mkdir roy.brianmckenna.org
-	cp -r site/* roy.brianmckenna.org
-	cp -r examples roy.brianmckenna.org
-	cp package.json roy.brianmckenna.org
+	cp -r site/* roy.brianmckenna.org/
+	cp -r examples roy.brianmckenna.org/
+	cp node_modules/underscore/underscore-min.js roy.brianmckenna.org/
+	cp package.json roy.brianmckenna.org/
 	cp roy-min.js roy.brianmckenna.org/
 
 extension:
 
 # Tests
 
+lint:
+	./node_modules/.bin/grunt lint
+
 test:
 	./node_modules/.bin/grunt jasmine
-# Roy
+# Roy [![Build Status](https://travis-ci.org/puffnfresh/roy.png?branch=master)](https://travis-ci.org/puffnfresh/roy)
 Roy is a small functional language that compiles to JavaScript. It has a few main features:
 
 * Damas-Hindley-Milner type inference
 * Whitespace significant syntax
-* Compile-time meta-programming
 * Simple tagged unions
 * Pattern matching
 * Structural typing

docs/guide/introduction.rst

 
 * Damas-Hindley-Milner type inference
 * Whitespace significant syntax
-* Compile-time meta-programming
 * Simple tagged unions
 * Pattern matching
 * Structural typing

examples/macro.roy

-macro printArgLength =
-  console.log "Compiling to:" arguments.length
-  [| console.log &(arguments.length) |]
-
-printArgLength 1
-printArgLength 1 2
-printArgLength 1 2 3
             './lib/typeparser.js': './src/typegrammar.js',
             './lib/parser.js': './src/grammar.js'
         },
-        rigger: {
-            'roy.js': 'rigger-roy.js'
+        cjsify: {
+            'roy.js': {
+                entry: 'src/compile.js',
+                dir: __dirname,
+                options: {
+                    'export': 'roy',
+                    'ignoreMissing': true,
+                    'node': false
+                }
+            }
         },
         jasmine: {
             specs: {
         fs.writeFileSync(this.target, parser.generate());
     });
 
-    grunt.registerMultiTask('rigger', 'File concatentation by rigger.', function() {
-        var rigger = require('rigger'),
-            fs = require('fs'),
-            done = this.async(),
-            target = this.target;
+    grunt.registerMultiTask('cjsify', 'Bundling by commonjs-everywhere.', function() {
+        var cjsify = require('commonjs-everywhere').cjsify,
+            escodegen = require('escodegen'),
+            target = this.target,
+            ast = cjsify(this.data.entry, this.data.dir, this.data.options),
+            output = escodegen.generate(ast);
 
-        rigger(this.data, function(err, output) {
-            if(err) return grunt.log.error(err);
-            fs.writeFileSync(target, output);
-            done(true);
-        });
+        grunt.file.write(target, output);
     });
 
     grunt.registerMultiTask('jasmine', 'Testing by jasmine.', function() {
         // Not nice (necessary for jasmine-node's asyncSpecWait, etc)
         for(key in jasmine) if(jasmine[key] instanceof Function) global[key] = jasmine[key];
 
+        function onComplete(runner) {
+            if (runner.results().failedCount > 0) {
+                process.exit(1);
+                return;
+            }
+            done(true);
+        };
+
         jasmine.executeSpecsInFolder({
             specFolders: [specDir],
             regExpSpec: /Comp.*Spec\.js/,
-            onComplete: function(runner) {
-                if (runner.results().failedCount > 0) {
-                    grunt.log.error();
-                }
-                done(true);
-            },
+            onComplete: onComplete,
             isVerbose: true,
             showColors: true
         });
     });
 
-    grunt.registerTask('default', 'jison lint jasmine rigger min');
+    grunt.registerTask('default', 'jison lint jasmine cjsify min');
 };

misc/vim/ftdetect/roy.vim

-au BufRead,BufNewFile *.roy,*.lroy set ft=roy

misc/vim/indent/roy.vim

-" Vim indent file
-" Language:	Roy
-" Maintainer:	Bill Casarin <bill@casarin.ca>
-" Last Change:	April 24, 2012
-" Notes:        based on Bram Moneaar's indent file for vim
-
-"set nocindent
-"set smartindent
-set autoindent
-
-setlocal indentexpr=GetRoyIndent()
-setlocal indentkeys+==end,=else,=catch,=}
-
-" Only define the function onceo
-if exists("*GetRoyIndent")
-endif
-
-function! GetRoyIndent()
-  " Find a non-blank line above the current line.
-  let lnum = prevnonblank(v:lnum - 1)
-
-  " At the start of the file use zero indent.
-  if lnum == 0
-    return 0
-  endif
-
-  " Add a 'shiftwidth' after matched strings
-  let ind = indent(lnum)
-  let line = getline(lnum)
-
-  if match(line, '\.*{\s*$') >= 0
-    let ind += &sw
-  endif
-
-  if match(line, '\.*=\s*$') >= 0
-    let ind += &sw
-  endif
-
-  let i = match(line, '^\s*\(if\|where\|else\|do\)')
-  if i >= 0
-    let ind += &sw
-  endif
-
-  " Subtract a 'shiftwidth' on a end, catch, else and elseif
-  if getline(v:lnum) =~ '^\s*\(else\|}\)'
-    let ind -= &sw
-  endif
-
-  return ind
-endfunction

misc/vim/plugin/roy.vim

-" Vim filetype plugin file
-" Language:	Roy
-" Maintainer:	Bill Casarin <bill@casarin.ca>
-" Last Change: April 24, 2012  
-
-if exists("b:did_ftplugin")
-	finish
-endif
-
-let b:did_ftplugin = 1
-
-setlocal include="^\s*import\>"
-setlocal suffixesadd=.lroy,.roy,.roym
-setlocal comments=://
-setlocal commentstring=//%s
-setlocal define="^\s*macro\>"
-
-" Uncomment the following two lines to force julia source-code style
-set shiftwidth=2
-set expandtab
-
-if has("gui_win32")
-	let b:browsefilter = "Roy Source Files (*.roy)\t*.lroy\n"
-endif

misc/vim/syntax/roy.vim

-" Vim syntax file
-" Language: Roy (http://roy.brianmckenna.org/)
-" Maintainer: Shigeo Esehara, Bill Casarin <bill@casarin.ca>
-" Last Change: 2011.11.29
-
-if version < 600
-    syn clear
-elseif exists("b:current_syntax")
-    finish
-endif
-
-"keywords
-syn keyword royStatement bind
-syn keyword royStatement case
-syn keyword royStatement data
-syn keyword royStatement do 
-syn keyword royStatement else
-syn keyword royStatement if
-syn keyword royStatement macro 
-syn keyword royStatement match
-syn keyword royStatement return 
-syn keyword royStatement then
-syn keyword royStatement type
-syn keyword royStatement typeclass
-syn keyword royStatement instance
-syn keyword royStatement where
-syn keyword royStatement with
-
-syn keyword royConstant true false
-syn keyword royImport import export
-syn keyword royMacro macro
-
-" Types {{{
-syn keyword royType Boolean
-syn keyword royType Either
-syn keyword royType Function
-syn keyword royType Maybe
-syn keyword royType Native
-syn keyword royType Number
-syn keyword royType String
-" }}}
-
-"defined type or data
-syn keyword roySymbol let nextgroup=roySymbol skipwhite
-syn match   roySymbol  "\%(\%(let\s\)\s*\)\@<=\%([a-zA-Z0-9$_]\)*" contained
-syn match   royLiteralTok "[<>!=/\%\+\*\-&←λ→\\⇒]"
-syn match   royNumber "\<\d\+\>"
-syn match   royTypeVariable "#[a-zA-Z]\+"
-
-"Braces,Parens
-syn match   roySurround "[{}]"
-syn match   roySurround  "[()]"
-
-"Comment
-syn match  royComment "//.*$" display contains=royTodo,@Spell
-syn keyword royTodo   FIXME NOTE NOTES TODO XXX contained
-
-"String
-syn region  royStringD	       start=+"+  skip=+\\\\\|\\"+  end=+"\|$+
-syn region  royStringS	       start=+'+  skip=+\\\\\|\\'+  end=+'\|$+
-
-if version >= 508 || !exists("did_roy_syn_inits")
-  if version <= 508
-    let did_roy_syn_inits = 1
-    command -nargs=+ HiLink hi link <args>
-  else
-    command -nargs=+ HiLink hi def link <args>
-  endif
-  
-  HiLink royComment   Comment
-  HiLink royStringD    String
-  HiLink royStringS    String
-  HiLink royStatement Statement
-  HiLink roySymbol    Function
-  HiLink roySurround  Nothing
-  HiLink royConstant  Constant
-  HiLink royTodo      Todo
-  HiLink royType      Type
-  HiLink royImport    Include
-  HiLink royMacro     Macro
-  HiLink royLiteralTok Operator
-  HiLink royNumber       Number
-  HiLink royTypeVariable Type
-
-  delcommand HiLink
-endif
-
-
-let b:current_syntax = "roy"
     "compiler"
   ],
   "author": "Brian McKenna <brian@brianmckenna.org> (http://brianmckenna.org/)",
-  "version": "0.1.5",
+  "version": "0.2.2",
   "engines": {
     "node": ">=0.4.0"
   },
     "roy": "./roy"
   },
   "scripts": {
-    "install": "$(npm bin)/grunt jison",
-    "test": "$(npm bin)/grunt lint jison jasmine"
+    "prepublish": "$(npm bin)/grunt jison",
+    "test": "$(npm bin)/grunt jison lint jasmine"
   },
   "homepage": "http://roy.brianmckenna.org/",
   "dependencies": {
-    "jison": "0.2.7",
+    "escodegen": "0.0.22",
     "source-map": "0.1.8",
     "underscore": "1.4.3",
     "unicode-categories": "0.9.1"
   },
   "devDependencies": {
+    "jison": "0.2.7",
     "grunt": "0.3.15",
-    "rigger": "0.5.2",
+    "commonjs-everywhere": "~0.8.0",
     "jasmine-node": "1.5.0"
   }
 }

rigger-roy.js

-var roy = {};
-
-//= node_modules/underscore/underscore.js
-
-(function(){
-    var module = {"parent": true};
-    var modules = {"underscore": _};
-    var load = {};
-    var require = function(x){
-        if(!modules[x]) {
-            load[x](modules[x] = {})
-        }
-        return modules[x];
-    }
-
-    load["unicode-categories"] = function(exports) {
-        //= node_modules/unicode-categories/index.js
-    };
-
-    load["./compile"] = function(exports) {
-        //= src/compile.js
-    };
-    load["./lexer"] = function(exports) {
-        //= src/lexer.js
-    };
-    load["./nodes"] = function(exports) {
-        //= src/nodes.js
-    };
-    load["./modules"] = function(exports) {
-        //= src/modules.js
-    };
-    load["./macroexpand"] = function(exports) {
-        //= src/macroexpand.js
-    };
-    load["../lib/typeparser"] = function(exports) {
-        //= lib/typeparser.js
-    };
-    load["../lib/parser"] = function(exports) {
-        //= lib/parser.js
-    };
-    load["./typeinference"] = function(exports) {
-        //= src/typeinference.js
-    };
-    load["./types"] = function(exports) {
-        //= src/types.js
-    };
-    load["./tarjan"] = function(exports) {
-        //= src/tarjan.js
-    };
-    load["./freeVariables"] = function(exports) {
-        //= src/freeVariables.js
-    };
-
-    roy.lexer = require("./lexer");
-    roy.compile = require("./compile").compile;
-})();
 #!/usr/bin/env node
-require('./src/compile').main();
+require('./src/node-repl')();
     }
     </style>
     <script src="http://www.google-analytics.com/ga.js" async="async"></script>
+    <script src="underscore-min.js"></script>
     <script src="roy-min.js"></script>
     <script src="codemirror2/lib/codemirror.js"></script>
     <script src="codemirror2/lib/util/runmode.js"></script>
       <ul>
         <li><a href="http://en.wikipedia.org/wiki/Type_inference#Hindley.E2.80.93Milner_type_inference_algorithm">Damas-Hindley-Milner type inference</a></li>
         <li><a href="http://jashkenas.github.com/coffee-script/">Whitespace</a> <a href="http://www.haskell.org/haskellwiki/Haskell">significant</a> <a href="http://www.python.org/">syntax</a></li>
-        <li><a href="http://en.wikipedia.org/wiki/Metaprogramming">Compile-time meta-programming</a></li>
         <li><a href="http://en.wikipedia.org/wiki/Tagged_union">Simple tagged unions</a></li>
         <li><a href="http://en.wikipedia.org/wiki/Pattern_matching">Pattern matching</a></li>
         <li><a href="http://en.wikipedia.org/wiki/Structural_type_system">Structural typing</a></li>
         <li><a href="http://en.wikipedia.org/wiki/Monad_%28functional_programming%29#do-notation">Monad syntax</a></li>
+        <li><a href="http://en.wikipedia.org/wiki/Currying">Automatic currying</a></li>
+        <li><a href="http://en.wikipedia.org/wiki/Eager_evaluation">Eager evaluation</a></li>
       </ul>
       <p>Try the current version below. The code is on <a href="https://github.com/pufuwozu/roy">GitHub</a>. Follow <a href="http://twitter.com/roylangjs">@roylangjs</a> for news on development.</p>
       <h2>Try</h2>
         document.getElementById('output').innerHTML += args.join(' ') + '\n';
     };
     var newConsole = {};
-    // TODO: Properly include Underscore
     _.each(['log', 'info', 'warn', 'error', 'trace', 'assert'], function(f) {
         newConsole[f] = function() {
             log.apply(null, arguments);
 var typecheck = require('./typeinference').typecheck,
-    macroexpand = require('./macroexpand').macroexpand,
     loadModule = require('./modules').loadModule,
     exportType = require('./modules').exportType,
     types = require('./types'),
     lexer = require('./lexer'),
     parser = require('../lib/parser').parser,
     typeparser = require('../lib/typeparser').parser,
+    escodegen = require('escodegen'),
     _ = require('underscore');
 
 // Assigning the nodes to `parser.yy` allows the grammar to access the nodes from
     }
 };
 
-// Separate end comments from other expressions
-var splitComments = function(body) {
-    return _.reduceRight(body, function(accum, node) {
-        if(!accum.body.length && node instanceof nodes.Comment) {
-            accum.comments.unshift(node);
-            return accum;
-        }
-        accum.body.unshift(node);
-        return accum;
-    }, {
-        body: [],
-        comments: []
-    });
+var jsNodeIsExpression = function (node) {
+    return !! (/Expression$/.test(node.type) || node.type === 'Identifier' || node.type === 'Literal');
 };
 
-// Compile an abstract syntax tree (AST) node to JavaScript.
-var indent = 0;
-var getIndent = function(extra) {
-    if(!extra) {
-        extra = 0;
-    }
-    var spacing = "";
-    var i;
-    for(i = 0; i < indent + extra; i++) {
-        spacing += "    ";
+var jsNodeIsStatement = function (node) {
+    return !! (/Statement$/.test(node.type) || /Declaration$/.test(node.type));
+};
+
+var ensureJsASTStatement = function (node) {
+    if (jsNodeIsExpression(node)) {
+        return { type: "ExpressionStatement", expression: node };
     }
-    return spacing;
+    return node;
 };
-var joinIndent = function(args, extra) {
-    var lineIndent = "\n" + getIndent(extra);
-    var argIndent = args.join("\n" + getIndent(extra));
-    if(argIndent) {
-        return argIndent + lineIndent;
+var ensureJsASTStatements = function (nodes) {
+    if (typeof nodes.length !== "undefined") {
+        return _.map(
+            _.filter(nodes, function (x) {
+                // console.log("x:", x);
+                // console.log("typeof x:", typeof x);
+                return typeof x !== "undefined";
+            }),
+            ensureJsASTStatement
+        );
+    } else {
+        throw new Error("ensureJsASTStatements wasn't given an Array, got " + nodes + " (" + typeof nodes + ")");
     }
-    return "";
 };
-var pushIndent = function() {
-    indent++;
-    return getIndent();
+
+// Separate end comments from other expressions
+var splitComments = function(body) {
+    return _.reduceRight(body, function(accum, node) {
+        if(accum.length && node instanceof nodes.Comment) {
+            if (! accum[0].comments) {
+                accum[0].comments = [];
+            }
+            accum[0].comments.unshift(node);
+            return accum;
+        }
+        accum.unshift(node);
+        return accum;
+    }, []);
 };
-var popIndent = function() {
-    indent--;
-    return getIndent();
+// Ensure comments are attached to a statement where possible
+var liftComments = function (jsAst) {
+    var helper = function (node) {
+        var result, i, comments = [];
+        if (! (node && node.type)) {
+            // Break out early when we're not looking at a proper node
+            return [node, comments];
+        }
+        for (var key in node) if (node.hasOwnProperty(key)) {
+            if (key === "leadingComments" && jsNodeIsExpression(node)) {
+                // Lift comments from expressions
+                comments = comments.concat(node[key]);
+                delete node[key];
+            } else if (node[key] && node[key].type) {
+                // Recurse into child nodes...
+                result = helper(node[key]);
+                comments = comments.concat(result[1]);
+            } else if (node[key] && node[key].length) {
+                // ...and arrays of nodes
+                for (i = 0; i < node[key].length; i += 1) {
+                    result = helper(node[key][i]);
+                    node[key][i] = result[0];
+                    comments = comments.concat(result[1]);
+                }
+            }
+        }
+        if (jsNodeIsStatement(node) && comments.length) {
+            // Attach lifted comments to statement nodes
+            if (typeof node.leadingComments === "undefined") {
+                node.leadingComments = [];
+            }
+            node.leadingComments = node.leadingComments.concat(comments);
+            comments = [];
+        }
+        return [node, comments];
+    };
+    return helper(jsAst)[0];
 };
 
-var compileNodeWithEnv = function(n, env, opts) {
+var extraComments = [];
+
+var compileNodeWithEnvToJsAST = function(n, env, opts) {
     if(!opts) opts = {};
     var compileNode = function(n) {
-        return compileNodeWithEnv(n, env);
+        return compileNodeWithEnvToJsAST(n, env);
     };
-    return n.accept({
+    var result = n.accept({
+        // Top level file
+        visitModule: function() {
+            var nodes = _.map(splitComments(n.body), compileNode);
+            return {
+                type: "Program",
+                body: ensureJsASTStatements(nodes)
+            };
+        },
         // Function definition to JavaScript function.
         visitFunction: function() {
-            var getArgs = function(a) {
-                return _.map(a, function(v) {
-                    return v.name;
-                }).join(", ");
-            };
-            pushIndent();
-            var split = splitComments(n.value);
-            var compiledWhereDecls = _.map(n.whereDecls, compileNode);
-            var compiledNodeBody = _.map(split.body, compileNode);
-            var init = [];
-            if(compiledWhereDecls.length > 0) {
-                init.push(compiledWhereDecls.join(';\n' + getIndent()) + ';');
-            }
-            if(compiledNodeBody.length > 1) {
-                init.push(compiledNodeBody.slice(0, compiledNodeBody.length - 1).join(';\n' + getIndent()) + ';');
-            }
-            var lastString = compiledNodeBody[compiledNodeBody.length - 1];
-
-            var compiledEndComments = "";
-            if(split.comments.length) {
-                compiledEndComments = getIndent() + _.map(split.comments, compileNode).join("\n" + getIndent()) + "\n";
+            var body = {
+                type: "BlockStatement",
+                body: []
+            };
+            if (n.whereDecls.length) {
+                _.each(n.whereDecls, function (w) {
+                    body.body.push(compileNode(w));
+                });
             }
-            var functionString = "function(" + getArgs(n.args) + ") {\n" +
-                getIndent() + joinIndent(init) + "return " + lastString +
-                ";\n" + compiledEndComments + popIndent() + "}";
-            return '(' + functionString + ')';
+            var exprsWithoutComments = _.map(splitComments(n.body), compileNode);
+            exprsWithoutComments.push({
+                type: "ReturnStatement",
+                argument: exprsWithoutComments.pop()
+            });
+            body.body = ensureJsASTStatements(body.body.concat(exprsWithoutComments));
+            var func = {
+                type: "FunctionExpression",
+                id: null,
+                params: _.map(n.args, function (a) {
+                    return {
+                        type: "Identifier",
+                        name: a.name
+                    };
+                }),
+                body: body
+            };
+            return func;
         },
         visitIfThenElse: function() {
-            var compiledCondition = compileNode(n.condition);
-
-            var compileAppendSemicolon = function(n) {
-                return compileNode(n) + ';';
-            };
-
-            var ifTrue = splitComments(n.ifTrue);
-            var ifFalse = splitComments(n.ifFalse);
-
-            pushIndent();
-            pushIndent();
-
-            var compiledIfTrueInit = joinIndent(_.map(ifTrue.body.slice(0, ifTrue.body.length - 1), compileAppendSemicolon));
-            var compiledIfTrueLast = compileNode(ifTrue.body[ifTrue.body.length - 1]);
-            var compiledIfTrueEndComments = "";
-            if(ifTrue.comments.length) {
-                compiledIfTrueEndComments = getIndent() + _.map(ifTrue.comments, compileNode).join("\n" + getIndent()) + "\n";
+            var ifTrue = _.map(splitComments(n.ifTrue), compileNode);
+            if (ifTrue.length === 1) {
+                ifTrue = ifTrue[0];
+            } else if (ifTrue.length > 1) {
+                ifTrue.push({
+                    type: "ReturnStatement",
+                    argument: ifTrue.pop()
+                });
+                ifTrue = {
+                    type: "CallExpression",
+                    'arguments': [],
+                    callee: {
+                        type: "FunctionExpression",
+                        id: null,
+                        params: [],
+                        body: {
+                            type: "BlockStatement",
+                            body: ensureJsASTStatements(ifTrue)
+                        }
+                    }
+                };
             }
 
-            var compiledIfFalseInit = joinIndent(_.map(ifFalse.body.slice(0, ifFalse.body.length - 1), compileAppendSemicolon));
-            var compiledIfFalseLast = compileNode(ifFalse.body[ifFalse.body.length - 1]);
-            var compiledIfFalseEndComments = "";
-            if(ifFalse.comments.length) {
-                compiledIfFalseEndComments = getIndent() + _.map(ifFalse.comments, compileNode).join("\n" + getIndent()) + "\n";
+            var ifFalse = _.map(splitComments(n.ifFalse), compileNode);
+            if (ifFalse.length === 1) {
+                ifFalse = ifFalse[0];
+            } else if (ifFalse.length > 1) {
+                ifFalse.push({
+                    type: "ReturnStatement",
+                    argument: ifFalse.pop()
+                });
+                ifFalse = {
+                    type: "CallExpression",
+                    'arguments': [],
+                    callee: {
+                        type: "FunctionExpression",
+                        id: null,
+                        params: [],
+                        body: {
+                            type: "BlockStatement",
+                            body: ensureJsASTStatements(ifFalse)
+                        }
+                    }
+                };
             }
 
-            popIndent();
-            popIndent();
-
-            return "(function() {\n" +
-                getIndent(1) + "if(" + compiledCondition + ") {\n" +
-                getIndent(2) + compiledIfTrueInit + "return " + compiledIfTrueLast + ";\n" + compiledIfTrueEndComments +
-                getIndent(1) + "} else {\n" +
-                getIndent(2) + compiledIfFalseInit + "return " + compiledIfFalseLast + ";\n" + compiledIfFalseEndComments +
-                getIndent(1) + "}\n" +
-                getIndent() + "})()";
+            return {
+                type: "ConditionalExpression",
+                test: compileNode(n.condition),
+                consequent: ifTrue,
+                alternate: ifFalse
+            };
         },
         // Let binding to JavaScript variable.
         visitLet: function() {
-            pushIndent();
-            var last = compileNode(_.last(n.value));
-            popIndent();
-
-            if(n.value.length == 1) {
-                return "var " + n.name + " = " + last;
-            }
-
-            pushIndent();
-            var init = _.map(_.initial(n.value), compileNode);
-            popIndent();
-
-            return "var " + n.name + " = (function() {\n" + getIndent(1) + init.join(";\n" + getIndent(1)) + ";\n" + getIndent(1) + "return " + last + ";\n" + getIndent() + "})()";
+            return {
+                type: "VariableDeclaration",
+                kind: "var",
+                declarations: [{
+                    type: "VariableDeclarator",
+                    id: {
+                        type: "Identifier",
+                        name: n.name
+                    },
+                    init: compileNode(n.value)
+                }]
+            };
         },
         visitInstance: function() {
-            return "var " + n.name + " = " + compileNode(n.object);
+            return {
+                type: "VariableDeclaration",
+                kind: "var",
+                declarations: [{
+                    type: "VariableDeclarator",
+                    id: {
+                        type: "Identifier",
+                        name: n.name
+                    },
+                    init: compileNode(n.object)
+                }]
+            };
         },
         visitAssignment: function() {
-            return compileNode(n.name) + " = " + compileNode(n.value) + ";";
+            return {
+                type: "AssignmentExpression",
+                operator: "=",
+                left: compileNode(n.name),
+                right: compileNode(n.value)
+            };
         },
         visitData: function() {
-            var defs = _.map(n.tags, compileNode);
-            return defs.join(";\n");
-        },
-        visitExpression: function() {
-            // No need to retain parenthesis for operations of higher
-            // precendence in JS
-            if(n.value instanceof nodes.Function || n.value instanceof nodes.Call) {
-                return compileNode(n.value);
-            }
-            return '(' + compileNode(n.value) + ')';
-        },
-        visitReplacement: function() {
-            return n.value;
+            return {
+                type: "VariableDeclaration",
+                kind: "var",
+                declarations: _.map(n.tags, compileNode)
+            };
         },
-        visitQuoted: function() {
-            var serializeNode = {
-                visitReplacement: function(v) {
-                    return "new nodes.Replacement(" + compileNode(v.value) + ")";
-                },
-                visitIdentifier: function(v) {
-                    return "new nodes.Identifier(" + JSON.stringify(v.value) + ")";
-                },
-                visitAccess: function(v) {
-                    return "new nodes.Access(" + serialize(v.value) + ", " + JSON.stringify(v.property) + ")";
-                },
-                visitPropertyAccess: function(v) {
-                    return "new nodes.PropertyAccess(" + serialize(v.value) + ", " + JSON.stringify(v.property) + ")";
+        visitReturn: function() {
+            return {
+                type: "CallExpression",
+                callee: {
+                    type: "MemberExpression",
+                    computed: false,
+                    object: {
+                        type: "Identifier",
+                        name: "__monad__"
+                    },
+                    property: {
+                        type: "Identifier",
+                        name: "return"
+                    }
                 },
-                visitCall: function(v) {
-                    return "new nodes.Call(" + serialize(v.func) + ", [" + _.map(v.args, serialize).join(', ') + "])";
-                }
+                "arguments": [compileNode(n.value)]
             };
-            var serialize = function(v) {
-                return v.accept(serializeNode);
-            };
-            return serialize(n.value);
         },
-        visitDo: function() {
-            function compileReturnOrValue(node) {
-                if(node.isReturn)
-                    return "__monad__['return'](" + compileNode(node.value) + ')';
-
-                return compileNode(node);
-            }
-
-            return '(function(__monad__) { ' + _.reduceRight(_.initial(n.body), function(accum, node) {
-                if(!isStatement(node)) {
-                    return 'return __monad__.bind(' +
-                        compileReturnOrValue(node.value) +
-                        ', function(' + (node.isBind ? node.name : '') + ') { ' + accum + '; })';
-                }
-                return compileReturnOrValue(node) + '; ' + accum;
-            }, 'return ' + compileReturnOrValue(_.last(n.body))) + '})(' + compileNode(n.value) + ')';
-
-            /*var compiledInit = [];
-            var firstBind;
-            var lastBind;
-            var lastBindIndex = 0;
-            _.each(n.body, function(node, i) {
-                if(node instanceof nodes.Bind) {
-                    if(!lastBind) {
-                        firstBind = node;
-                    } else {
-                        lastBind.rest = n.body.slice(lastBindIndex + 1, i + 1);
+        visitBind: function() {
+            var body = _.map(n.rest.slice(0, n.rest.length - 1), compileNode);
+            body.push({
+                type: "ReturnStatement",
+                argument: compileNode(n.rest[n.rest.length - 1])
+            });
+            return {
+                type: "CallExpression",
+                callee: {
+                    type: "MemberExpression",
+                    computed: false,
+                    object: {
+                        type: "Identifier",
+                        name: "__monad__"
+                    },
+                    property: {
+                        type: "Identifier",
+                        name: "bind"
                     }
-                    lastBindIndex = i;
-                    lastBind = node;
-                } else {
-                    if(!lastBind) {
-                        compiledInit.push(compileNode(node));
+                },
+                "arguments": [compileNode(n.value), {
+                    type: "FunctionExpression",
+                    id: null,
+                    params: [{
+                        type: "Identifier",
+                        name: n.name
+                    }],
+                    body: {
+                        type: "BlockStatement",
+                        body: ensureJsASTStatements(body)
                     }
+                }]
+            };
+        },
+        visitDo: function() {
+            var monadDecl = {
+                type: "VariableDeclaration",
+                kind: "var",
+                declarations: [{
+                    type: "VariableDeclarator",
+                    id: {
+                        type: "Identifier",
+                        name: "__monad__"
+                    },
+                    init: compileNode(n.value)
+                }]
+            };
+            var body = {
+                type: "BlockStatement",
+                body: []
+            };
+            body.body = _.flatten([monadDecl, compiledInit, {
+                type: "ReturnStatement",
+                argument: compileNode(firstBind)
+            }]);
+            return {
+                type: "CallExpression",
+                "arguments": [],
+                callee: {
+                    type: "FunctionExpression",
+                    id: null,
+                    params: [],
+                    body: body
                 }
-            });
-            if(lastBind) {
-                lastBind.rest = n.body.slice(lastBindIndex + 1);
-            }
-            return "(function(__monad__){\n" + pushIndent() +
-                (!firstBind ? 'return ' : '') + compiledInit.join(';\n' + getIndent()) + '\n' + getIndent() +
-                (firstBind ? 'return ' + compileNode(firstBind) : '') + "\n" +
-                popIndent() + "})(" + compileNode(n.value) + ")";*/
+            };
         },
         visitTag: function() {
+            var tagName = {
+                type: "Identifier",
+                name: n.name
+            };
             var args = _.map(n.vars, function(v, i) {
-                return v.value + "_" + i;
+                return {
+                    type: "Identifier",
+                    name: v.value + "_" + i
+                };
             });
             var setters = _.map(args, function(v, i) {
-                return "this._" + i + " = " + v;
+                return { // "this._" + i + " = " + v;
+                    type: "ExpressionStatement",
+                    expression: {
+                        type: "AssignmentExpression",
+                        operator: "=",
+                        left: {
+                            type: "MemberExpression",
+                            computed: false,
+                            object: {
+                                type: "ThisExpression"
+                            },
+                            property: {
+                                type: "Identifier",
+                                name: "_" + i
+                            }
+                        },
+                        right: v
+                    }
+                };
             });
-            pushIndent();
-            var constructorString = "if(!(this instanceof " + n.name + ")) {\n" + getIndent(1) + "return new " + n.name + "(" + args.join(", ") + ");\n" + getIndent() + "}";
-            var settersString = (setters.length === 0 ? "" : "\n" + getIndent() + setters.join(";\n" + getIndent()) + ";");
-            popIndent();
-            return "var " + n.name + " = function(" + args.join(", ") + ") {\n" + getIndent(1) + constructorString + settersString + getIndent() + "\n}";
+            var constructorCheck = {
+                type: "IfStatement",
+                test: {
+                    type: "UnaryExpression",
+                    operator: "!",
+                    argument: {
+                        type: "BinaryExpression",
+                        operator: "instanceof",
+                        left: { type: "ThisExpression" },
+                        right: tagName
+                    }
+                },
+                consequent: {
+                    type: "BlockStatement",
+                    body: [{
+                        type: "ReturnStatement",
+                        argument: {
+                            type: "NewExpression",
+                            callee: tagName,
+                            'arguments': args
+                        }
+                    }]
+                },
+                alternate: null
+            };
+            setters.unshift(constructorCheck);
+            var constructorBody = {
+                type: "BlockStatement",
+                body: ensureJsASTStatements(setters)
+            };
+            return {
+                type: "VariableDeclarator",
+                id: tagName,
+                init: {
+                    type: "FunctionExpression",
+                    id: null,
+                    params: args,
+                    body: constructorBody
+                }
+            };
         },
         visitMatch: function() {
+            valuePlaceholder = '__match';
             var flatMap = function(a, f) {
                 return _.flatten(_.map(a, f));
             };
 
             var pathConditions = _.map(n.cases, function(c) {
                 var getVars = function(pattern, varPath) {
-                    return flatMap(pattern.vars, function(a, i) {
+                    var decls = flatMap(pattern.vars, function(a, i) {
                         var nextVarPath = varPath.slice();
                         nextVarPath.push(i);
 
                         return a.accept({
                             visitIdentifier: function() {
+
                                 if(a.value == '_') return [];
 
-                                var accessors = _.map(nextVarPath, function(x) {
-                                    return "._" + x;
-                                }).join('');
-                                return ["var " + a.value + " = " + compileNode(n.value) + accessors + ";"];
+                                var value = _.reduceRight(nextVarPath, function(structure, varPathName) {
+                                    return {
+                                        type: "MemberExpression",
+                                        computed: false,
+                                        object: structure,
+                                        property: { type: "Identifier", name: "_" + varPathName }
+                                    };
+                                }, { type: "Identifier", name: valuePlaceholder });
+                                return [{
+                                    type: "VariableDeclarator",
+                                    id: { type: "Identifier", name: a.value },
+                                    init: value
+                                }];
                             },
                             visitPattern: function() {
-                                return getVars(a, nextVarPath);
+                                return getVars(a, nextVarPath).declarations;
                             }
                         });
                     });
+                    if (decls.length) {
+                        return {
+                            type: "VariableDeclaration",
+                            kind: "var",
+                            declarations: decls
+                        };
+                    }
                 };
                 var vars = getVars(c.pattern, []);
 
                     });
                 };
                 var tagPaths = getTagPaths(c.pattern, []);
-                var compiledValue = compileNode(n.value);
-                var extraConditions = _.map(tagPaths, function(e) {
-                    return ' && ' + compiledValue + '._' + e.path.join('._') + ' instanceof ' + e.tag.value;
-                }).join('');
+                var makeCondition = function (e) {
+                    var pieces = _.reduceRight(e.path, function (structure, piece) {
+                        return {
+                            type: "MemberExpression",
+                            computed: false,
+                            object: structure,
+                            property: { type: "Identifier", name: "_" + piece }
+                        };
+                    }, { type: "Identifier", name: valuePlaceholder });
+                    return {
+                        type: "BinaryExpression",
+                        operator: "instanceof",
+                        left: pieces,
+                        right: { type: "Identifier", name: e.tag.value }
+                    };
+                };
+                var extraConditions = null;
+                if (tagPaths.length) {
+                    var lastCondition = makeCondition(tagPaths.pop());
+                    extraConditions = _.reduceRight(tagPaths, function(conditions, e) {
+                        return {
+                            type: "LogicalExpression",
+                            operator: "&&",
+                            left: e,
+                            right: conditions
+                        };
+                    }, lastCondition);
+                }
 
                 // More specific patterns need to appear first
                 // Need to sort by the length of the path
                 });
                 var maxPath = maxTagPath != -Infinity ? maxTagPath.path : [];
 
+                var body = [];
+                if (vars) {
+                    body.push(vars);
+                }
+                body.push({
+                    type: "ReturnStatement",
+                    argument: compileNode(c.value)
+                });
+                var test = {
+                    type: "BinaryExpression",
+                    operator: "instanceof",
+                    left: { type: "Identifier", name: valuePlaceholder },
+                    right: { type: "Identifier", name: c.pattern.tag.value }
+                };
+                if (extraConditions) {
+                    test = {
+                        type: "LogicalExpression",
+                        operator: "&&",
+                        left: test,
+                        right: extraConditions
+                    };
+                }
                 return {
                     path: maxPath,
-                    condition: "if(" + compiledValue + " instanceof " + c.pattern.tag.value +
-                        extraConditions + ") {\n" + getIndent(2) +
-                        joinIndent(vars, 2) + "return " + compileNode(c.value) +
-                        ";\n" + getIndent(1) + "}"
+                    condition: {
+                        type: "IfStatement",
+                        test: test,
+                        consequent: {
+                            type: "BlockStatement",
+                            body: ensureJsASTStatements(body)
+                        },
+                        alternate: null
+                    }
                 };
             });
 
                 return e.condition;
             });
 
-            return "(function() {\n" + getIndent(1) + cases.join(" else ") + "\n" + getIndent() + "})()";
+            return {
+                type: "CallExpression",
+                "arguments": [compileNode(n.value)],
+                callee: {
+                    type: "FunctionExpression",
+                    id: null,
+                    params: [{ type: "Identifier", name: valuePlaceholder }],
+                    body: {
+                        type: "BlockStatement",
+                        body: ensureJsASTStatements(cases)
+                    }
+                }
+            };
         },
         // Call to JavaScript call.
         visitCall: function() {
-            var typeClasses = '';
-            if(n.typeClassInstance) {
-                typeClasses = n.typeClassInstance + ', ';
-            }
-            if(n.func.value == 'import') {
-                return importModule(JSON.parse(n.args[0].value), env, opts);
+            var args = _.map(n.args, compileNode);
+            if (n.typeClassInstance) {
+                args.unshift({
+                    type: "Identifier",
+                    name: n.typeClassInstance
+                });
             }
-            return compileNode(n.func) + "(" + typeClasses + _.map(n.args, compileNode).join(", ") + ")";
+
+            return {
+                type: "CallExpression",
+                "arguments": args,
+                callee: compileNode(n.func)
+            };
+            // if(n.func.value == 'import') {
+            //     return importModule(JSON.parse(n.args[0].value), env, opts);
+            // }
         },
         visitPropertyAccess: function() {
-            return compileNode(n.value) + "." + n.property;
+            return {
+                type: "MemberExpression",
+                computed: false,
+                object: compileNode(n.value),
+                property: { type: "Identifier", name: n.property }
+            };
         },
         visitAccess: function() {
-            return compileNode(n.value) + "[" + compileNode(n.property) + "]";
+            return {
+                type: "MemberExpression",
+                computed: true,
+                object: compileNode(n.value),
+                property: compileNode(n.property)
+            };
         },
         visitUnaryBooleanOperator: function() {
-            return [n.name, compileNode(n.value)].join(" ");
+            return {
+                type: "UnaryExpression",
+                operator: n.name,
+                argument: compileNode(n.value)
+            };
         },
         visitBinaryGenericOperator: function() {
-            return [compileNode(n.left), n.name, compileNode(n.right)].join(" ");
+            return {
+                type: "BinaryExpression",
+                operator: n.name,
+                left: compileNode(n.left),
+                right: compileNode(n.right)
+            };
         },
         visitBinaryNumberOperator: function() {
-            return [compileNode(n.left), n.name, compileNode(n.right)].join(" ");
+            return {
+                type: "BinaryExpression",
+                operator: n.name,
+                left: compileNode(n.left),
+                right: compileNode(n.right)
+            };
         },
         visitBinaryBooleanOperator: function() {
-            return [compileNode(n.left), n.name, compileNode(n.right)].join(" ");
+            return {
+                type: "BinaryExpression",
+                operator: n.name,
+                left: compileNode(n.left),
+                right: compileNode(n.right)
+            };
         },
         visitBinaryStringOperator: function() {
-            return [compileNode(n.left), n.name, compileNode(n.right)].join(" ");
+            return {
+                type: "BinaryExpression",
+                operator: n.name,
+                left: compileNode(n.left),
+                right: compileNode(n.right)
+            };
         },
         visitWith: function() {
-            var args = compileNode(n.left) + ', ' + compileNode(n.right);
-            var inner = _.map(['__l__', '__r__'], function(name) {
-                return 'for(__n__ in ' + name + ') {\n' + getIndent(2) + '__o__[__n__] = ' + name + '[__n__];\n' + getIndent(1) + '}';
+            var copyLoop = function (varName) {
+                return {
+                    type: "ForInStatement",
+                    left: { type: "Identifier", name: "__n__" },
+                    right: { type: "Identifier", name: varName },
+                    body: {
+                        type: "BlockStatement",
+                        body: [{
+                            type: "ExpressionStatement",
+                            expression: {
+                                type: "AssignmentExpression",
+                                operator: "=",
+                                left: {
+                                    type: "MemberExpression",
+                                    computed: true,
+                                    object: { type: "Identifier", name: "__o__" },
+                                    property: { type: "Identifier", name: "__n__" }
+                                },
+                                right: {
+                                    type: "MemberExpression",
+                                    computed: true,
+                                    object: { type: "Identifier", name: varName },
+                                    property: { type: "Identifier", name: "__n__" }
+                                }
+                            }
+                        }]
+                    }
+                };
+            };
+            var funcBody = [];
+            funcBody.push({
+                type: "VariableDeclaration",
+                kind: "var",
+                declarations: [{
+                    type: "VariableDeclarator",
+                    id: { type: "Identifier", name: "__o__" },
+                    init: { type: "ObjectExpression", properties: [] }
+                }, {
+                    type: "VariableDeclarator",
+                    id: { type: "Identifier", name: "__n__" },
+                    init: null
+                }]
             });
-            return joinIndent(['(function(__l__, __r__) {', 'var __o__ = {}, __n__;'], 1) + joinIndent(inner, 1) + 'return __o__;\n' + getIndent() + '})(' + args + ')';
+            funcBody.push(copyLoop("__l__"));
+            funcBody.push(copyLoop("__r__"));
+            funcBody.push({
+                type: "ReturnStatement",
+                argument: { type: "Identifier", name: "__o__" }
+            });
+
+            return {
+                type: "CallExpression",
+                'arguments': _.map([n.left, n.right], compileNode),
+                callee: {
+                    type: "FunctionExpression",
+                    id: null,
+                    params: [
+                        { type: "Identifier", name: "__l__" },
+                        { type: "Identifier", name: "__r__" }
+                    ],
+                    body: { type: "BlockStatement", body: ensureJsASTStatement(funcBody) }
+                }
+            };
         },
         // Print all other nodes directly.
-        visitComment: function() {
-            return n.value;
-        },
         visitIdentifier: function() {
-            var typeClassAccessor = '';
             if(n.typeClassInstance) {
-                typeClassAccessor = n.typeClassInstance + '.';
+                return {
+                    type: "MemberExpression",
+                    computed: false,
+                    object: {
+                        type: "Identifier",
+                        name: n.typeClassInstance
+                    },
+                    property: {
+                        type: "Identifier",
+                        name: n.value
+                    }
+                };
             }
-            return typeClassAccessor + n.value;
+            return {
+                type: "Identifier",
+                name: n.value
+            };
         },
         visitNumber: function() {
-            return n.value;
+            return {
+                type: "Literal",
+                value: parseFloat(n.value)
+            };
         },
         visitString: function() {
-            return n.value;
+            /*jshint evil: true */
+            return {
+                type: "Literal",
+                value: eval(n.value)
+            };
         },
         visitBoolean: function() {
-            return n.value;
+            return {
+                type: "Literal",
+                value: n.value === "true"
+            };
         },
         visitUnit: function() {
-            return "null";
+            return {
+                type: "Literal",
+                value: null
+            };
         },
         visitArray: function() {
-            return '[' + _.map(n.values, compileNode).join(', ') + ']';
+            return {
+                type: "ArrayExpression",
+                elements: _.map(n.values, compileNode)
+            };
         },
         visitTuple: function() {
-            return '[' + _.map(n.values, compileNode).join(', ') + ']';
+            return {
+                type: "ArrayExpression",
+                elements: _.map(n.values, compileNode)
+            };
         },
         visitObject: function() {
-            var key;
-            var pairs = [];
-            pushIndent();
+            var cleanedKey, key, pairs = [];
+
             for(key in n.values) {
-                pairs.push("\"" + key + "\": " + compileNode(n.values[key]));
+                if (key[0] === "'" || key[0] === '"') {
+                    cleanedKey = String.prototype.slice.call(key, 1, key.length-1);
+                } else {
+                    cleanedKey = key;
+                }
+                pairs.push({
+                    type: "Property",
+                    key: {
+                        type: "Literal",
+                        value: cleanedKey
+                    },
+                    value: compileNode(n.values[key])
+                });
             }
-            return "{\n" + getIndent() + pairs.join(",\n" + getIndent()) + "\n" + popIndent() + "}";
+            return {
+                type: "ObjectExpression",
+                properties: pairs
+            };
         }
     });
+    if (typeof result === "undefined"){
+        if (n.comments && n.comments.length) {
+            extraComments = extraComments.concat(n.comments);
+        }
+    } else {
+        if (extraComments && extraComments.length) {
+            if (! (n.comments && n.comments.length)) {
+                n.comments = extraComments;
+            } else {
+                n.comments = extraComments.concat(n.comments);
+            }
+            extraComments = [];
+        }
+        result.leadingComments = _.map(n.comments, function (c) {
+            var lines = c.value.split(/\r\n|\r|\n/g);
+            return {
+                type: lines.length > 1 ? "Block" : "Line",
+                value: c.value
+            };
+        });
+    }
+    return result;
+};
+exports.compileNodeWithEnvToJsAST = compileNodeWithEnvToJsAST;
+var compileNodeWithEnv = function (n, env, opts) {
+    var ast = compileNodeWithEnvToJsAST(n, env, opts);
+    if (typeof ast === "string") {
+//        console.warn("Got AST already transformed into string: ", ast);
+        return ast;
+    } else if (typeof ast === "undefined") {
+        return "";
+    } else {
+        ast = liftComments(ast);
+        var generated = escodegen.generate(ensureJsASTStatement(ast), {comment: true});
+        return generated;
+    }
 };
 exports.compileNodeWithEnv = compileNodeWithEnv;
 
     // Parse the file to an AST.
     var tokens = lexer.tokenise(source);
     var ast = parser.parse(tokens);
-    ast = macroexpand(ast, env, opts);
 
     // Typecheck the AST. Any type errors will throw an exception.
-    var resultType = typecheck(ast);
+    var resultType = typecheck(ast.body, env, aliases);
 
     // Export types
-    ast = _.map(ast, function(n) {
+    ast.body = _.map(ast.body, function(n) {
         if(n instanceof nodes.Call && n.func.value == 'export') {
             return exportType(n.args[0], env, opts.exported, opts.nodejs);
         }
         return n;
     });
 
-    var output = [];
-
-    if(!opts.nodejs) {
-        output.push("(function() {");
-    }
-
-    if(opts.strict) {
-        output.push('"use strict";');
-    }
-
-    var outputLine = output.length + 1;
-    _.each(ast, function(v) {
-        var compiled = compileNodeWithEnv(v, env, opts),
-            j, lineCount;
-
-        if(compiled) {
-            lineCount = compiled.split('\n').length;
-
-            if(opts.sourceMap && v.lineno > 1) {
-                opts.sourceMap.addMapping({
-                    source: opts.filename,
-                    original: {
-                        line: v.lineno,
-                        column: 0
-                    },
-                    generated: {
-                        line: outputLine,
-                        column: 0
-                    }
-                });
-            }
-            outputLine += lineCount;
-
-            output.push(compiled + (v instanceof nodes.Comment ? '' : ';'));
-        }
-    });
-
-    if(!opts.nodejs) {
-        output.push("})();");
-    }
-
-    // Add a newline at the end
-    output.push("");
-
-    return {type: resultType, output: output.join('\n')};
-}
-exports.compile = compile;
-
-var getSandbox = function() {
-    var sandbox = {require: require, exports: exports};
-
-    var name;
-    for(name in global) {
-        sandbox[name] = global[name];
-    }
-
-    return sandbox;
-};
-
-var getFileContents = function(filename) {
-    var fs = require('fs'),
-        exts = ["", ".roy", ".lroy"],
-        filenames = _.map(exts, function(ext){
-            return filename + ext;
-        }),
-        foundFilename,
-        source,
-        err,
-        i;
-
-    // Check to see if an extension is specified, if so, don't bother
-    // checking others
-    if (/\..+$/.test(filename)) {
-        source = fs.readFileSync(filename, 'utf8');
-        filenames = [filename];
-    } else {
-        foundFilename = _.find(filenames, function(filename) {
-            return fs.existsSync(filename);
-        });
-        if(foundFilename) {
-            source = fs.readFileSync(foundFilename, 'utf8');
-        }
-    }
-
-    if(!source) {
-        throw new Error("File(s) not found: " + filenames.join(", "));
-    }
-
-    return source;
-};
-
-var nodeRepl = function(opts) {
-    var readline = require('readline'),
-        path = require('path'),
-        vm = require('vm'),
-        prettyPrint = require('./prettyprint').prettyPrint;
-
-    var stdout = process.stdout;
-    var stdin = process.openStdin();
-    var repl = readline.createInterface(stdin, stdout);
-
-    var env = {};
-    var sources = {};
-    var aliases = {};
-    var sandbox = getSandbox();
-
-    // Prologue
-    console.log("Roy: " + opts.info.description);
-    console.log(opts.info.author);
-    console.log(":? for help");
-
-    var colorLog = function(color) {
-        var args = [].slice.call(arguments, 1);
-
-        if(opts.colorConsole) {
-            args[0] = '\u001b[' + color + 'm' + args[0];
-            args[args.length - 1] = args[args.length - 1] + '\u001b[0m';
-        }
-
-        console.log.apply(console, args);
-    };
-
-    // Include the standard library
-    var fs = require('fs');
-    //var prelude = fs.readFileSync(path.dirname(__dirname) + '/lib/prelude.roy', 'utf8');
-    //vm.runInNewContext(compile(prelude, env, {}, {nodejs: true}).output, sandbox, 'eval');
-    repl.setPrompt('roy> ');
-    repl.on('close', function() {
-        stdin.destroy();
-    });
-    repl.on('line', function(line) {
-        var compiled;
-        var output;
-
-        var filename;
-        var source;
-
-        var tokens;
-        var ast;
-
-        // Check for a "metacommand"
-        // e.g. ":q" or ":l test.roy"
-        var metacommand = line.replace(/^\s+/, '').split(' ');
-        try {
-            switch(metacommand[0]) {
-            case ":q":
-                // Exit
-                process.exit();
-                break;
-            case ":l":
-                // Load
-                filename = metacommand[1];
-                source = getFileContents(filename);
-                compiled = compile(source, env, aliases, {nodejs: true, filename: ".", run: true});
-                break;
-            case ":t":
-                if(metacommand[1] in env) {
-                    console.log(env[metacommand[1]].toString());
-                } else {
-                    colorLog(33, metacommand[1], "is not defined.");
-                }
-                break;
-            case ":s":
-                // Source
-                if(sources[metacommand[1]]) {
-                    colorLog(33, metacommand[1], "=", prettyPrint(sources[metacommand[1]]));
-                } else {
-                    if(metacommand[1]){
-                        colorLog(33, metacommand[1], "is not defined.");
-                    }else{
-                        console.log("Usage :s command ");
-                        console.log(":s [identifier] :: show original code about identifier.");
-                    }
-                }
-                break;
-            case ":?":
-                // Help
-                colorLog(32, "Commands available from the prompt");
-                console.log(":l -- load and run an external file");
-                console.log(":q -- exit REPL");
-                console.log(":s -- show original code about identifier");
-                console.log(":t -- show the type of the identifier");
-                console.log(":? -- show help");
-                break;
-            default:
-                // The line isn't a metacommand
-
-                // Remember the source if it's a binding
-                tokens = lexer.tokenise(line);
-                ast = parser.parse(tokens);
-                ast[0].accept({
-                    visitLet: function(n) {
-                        sources[n.name] = n.value;
+    var jsAst = liftComments(compileNodeWithEnvToJsAST(ast, env, opts));
+    if (!opts.nodejs) {
+        jsAst.body = [{
+            type: "ExpressionStatement",
+            expression: {
+                type: "CallExpression",
+                'arguments': [],
+                callee: {
+                    type: "FunctionExpression",
+                    id: null,
+                    params: [],
+                    body: {
+                        type: "BlockStatement",
+                        body: jsAst.body
                     }
-                });
-
-                // Just eval it
-                compiled = compile(line, env, aliases, {nodejs: true, filename: ".", run: true});
-                break;
-            }
-
-            if(compiled) {
-                output = vm.runInNewContext(compiled.output, sandbox, 'eval');
-
-                if(typeof output != 'undefined') {
-                    colorLog(32, (typeof output == 'object' ? JSON.stringify(output) : output) + " : " + compiled.type);
                 }
             }
-        } catch(e) {
-            colorLog(31, (e.stack || e.toString()));
-        }
-        repl.prompt();
-    });
-    repl.prompt();
-};
-
-var writeModule = function(env, exported, filename) {
-    var fs = require('fs');
-
-    var moduleOutput = _.map(exported, function(v, k) {
-        if(v instanceof types.TagType) {
-            return 'type ' + v.toString().replace(/#/g, '');
-        }
-        return k + ': ' + v.toString();
-    }).join('\n') + '\n';
-    fs.writeFile(filename, moduleOutput, 'utf8');
-};
-
-var importModule = function(name, env, opts) {
-    var addTypesToEnv = function(moduleTypes) {
-        _.each(moduleTypes, function(v, k) {
-            var dataType = [new types.TagNameType(k)];
-            _.each(function() {
-                dataType.push(new types.Variable());
-            });
-            env[k] = new types.TagType(dataType);
-        });
-    };
-
-    var moduleTypes;
-    if(opts.nodejs) {
-        // Need to convert to absolute paths for the CLI
-        if(opts.run) {
-            var path = require("path");
-            name = path.resolve(path.dirname(opts.filename), name);
-        }
-
-        moduleTypes = loadModule(name, opts);
-        addTypesToEnv(moduleTypes.types);
-        var variable = name.substr(name.lastIndexOf("/") + 1);
-        env[variable] = new types.Variable();
-        var props = {};
-        _.each(moduleTypes.env, function(v, k) {
-            props[k] = nodeToType(v, env, {});
-        });
-        env[variable] = new types.ObjectType(props);
-
-        console.log("Using sync CommonJS module:", name);
-
-        return variable + " = require(" + JSON.stringify(name) + ")";
-    } else {
-        moduleTypes = loadModule(name, opts);
-        addTypesToEnv(moduleTypes.types);
-        _.each(moduleTypes.env, function(v, k) {
-            env[k] = nodeToType(v, env, {});
-        });
-
-        if(console) console.log("Using browser module:", name);
-
-        return "";
-    }
-};
-
-var processFlags = function(argv, opts) {
-    if(argv.length === 0) {
-        nodeRepl(opts);
-        return;
-    }
-
-    switch(argv[0]) {
-    case "-v":
-    case "--version":
-        console.log("Roy: " + opts.info.description);
-        console.log(opts.info.version);
-        process.exit();
-         break;
-    case "-h":
-    case "--help":
-        console.log("Roy: " + opts.info.description + "\n");
-        console.log("-b --browser   : wrap the output in a top level function");
-        console.log("-c --color     : colorful REPL mode");
-        console.log("-h --help      : show this help");
-        console.log("-p             : run without prelude (standard library)");
-        console.log("-r [file]      : run Roy-code without JavaScript output");
-        console.log("-s --stdio     : read script from stdin and output to stdout");
-        console.log("-v --version   : show current version");
-        return;
-    case "-s":
-    case "--stdio":
-        var source = '';
-        process.stdin.resume();
-        process.stdin.setEncoding('utf8');
-        process.stdin.on('data', function(data) {
-            source += data;
-        });
-        process.stdin.on('end', function() {
-            console.log(compile(source, null, null, opts).output);
-        });
-        return;
-    case "-p":
-        opts.includePrelude = false;
-        /* falls through */
-    case "-r":
-        opts.run = true;
-        argv.shift();
-        break;
-    case "-b":
-    case "--browser":
-        opts.nodejs = false;
-        argv.shift();
-        break;
-    case "-c":
-    case "--color":
-        opts.colorConsole = true;
-        nodeRepl(opts);
-        return;
-    default:
-        runRoy(argv, opts);
-        return;
+        }];
     }
 
-    processFlags(argv, opts);
-};
-
-var runRoy = function(argv, opts) {
-    var fs = require('fs');
-    var path = require('path');
-    var vm = require('vm');
-
-    var extensions = /\.l?roy$/;
-    var literateExtension = /\.lroy$/;
-
-    var exported;
-    var env = {};
-    var aliases = {};
-    var sandbox = getSandbox();
-
-    if(opts.run) {
-        // Include the standard library
-        if(opts.includePrelude) {
-            argv.unshift(path.dirname(__dirname) + '/lib/prelude.roy');
-        }
-    } else {
-        var modules = [];
-        if(!argv.length || argv[0] != 'lib/prelude.roy') {
-            modules.push(path.dirname(__dirname) + '/lib/prelude');
-        }
-        _.each(modules, function(module) {
-            var moduleTypes = loadModule(module, {filename: '.'});
-            _.each(moduleTypes.env, function(v, k) {
-                env[k] = new types.Variable();
-                env[k] = nodeToType(v, env, aliases);
-            });
+    if (opts.strict) {
+        jsAst.body.unshift({
+            type: "ExpressionStatement",
+            expression: {
+                type: "Literal",
+                value: "use strict"
+            }
         });
     }
 
-    _.each(argv, function(filename) {
-        // Read the file content.
-        var source = getFileContents(filename);
-
-        if(filename.match(literateExtension)) {
-            // Strip out the Markdown.
-            source = source.match(/^ {4,}.+$/mg).join('\n').replace(/^ {4}/gm, '');
-        } else {
-            console.assert(filename.match(extensions), 'Filename must end with ".roy" or ".lroy"');
-        }
-
-        exported = {};
-        var outputPath = filename.replace(extensions, '.js');
-        var SourceMapGenerator = require('source-map').SourceMapGenerator;
-        var sourceMap = new SourceMapGenerator({file: path.basename(outputPath)});
-
-        var compiled = compile(source, env, aliases, {
-            nodejs: opts.nodejs,
-            filename: filename,
-            run: opts.run,
-            exported: exported,
-            sourceMap: sourceMap
-        });
-        if(opts.run) {
-            // Execute the JavaScript output.
-            output = vm.runInNewContext(compiled.output, sandbox, 'eval');
-        } else {
-            // Write the JavaScript output.
-            fs.writeFile(outputPath, compiled.output + '//@ sourceMappingURL=' + path.basename(outputPath) + '.map\n', 'utf8');
-            fs.writeFile(outputPath + '.map', sourceMap.toString(), 'utf8');
-            writeModule(env, exported, filename.replace(extensions, '.roym'));
-        }
-    });
-};
-
-var main = function() {
-    var argv = process.argv.slice(2);
-
-    // Roy package information
-    var fs = require('fs');
-    var path = require('path');
-
-    // Meta-commands configuration
-    var opts = {
-        colorConsole: false,
-        info: JSON.parse(fs.readFileSync(path.dirname(__dirname) + '/package.json', 'utf8')),
-        nodejs: true,
-        run: false,
-        includePrelude: true
+    return {
+        type: resultType,
+        output: escodegen.generate(
+            ensureJsASTStatement(jsAst),
+            {
+                comment: true
+            }
+        )
     };
-
-    processFlags(argv, opts);
-};
-exports.main = main;
-
-if(exports && !module.parent) {