diff --git a/js/WikiTextCompiler.js b/js/WikiTextCompiler.js
new file mode 100644
index 000000000..385db9c52
--- /dev/null
+++ b/js/WikiTextCompiler.js
@@ -0,0 +1,316 @@
+/*\
+title: js/WikiTextCompiler.js
+
+\*/
+(function(){
+
+/*jslint node: true */
+"use strict";
+
+var ArgParser = require("./ArgParser.js").ArgParser,
+ utils = require("./Utils.js"),
+ util = require("util");
+
+var WikiTextCompiler = function(store,title,parser) {
+ this.parser = parser;
+ this.store = store;
+ this.title = title;
+};
+
+WikiTextCompiler.prototype.compile = function(type,treenode) {
+ if(type === "text/html") {
+ return this.compileAsHtml(treenode);
+ } else if(type === "text/plain") {
+ return this.compileAsHtml(treenode);
+ } else {
+ return null;
+ }
+};
+
+WikiTextCompiler.prototype.compileAsHtml = function(treenode) {
+ var me = this,
+ output = [],
+ compileSubTree;
+ var pushString = function(s) {
+ var last = output[output.length-1];
+ if(output.length > 0 && last.type === "StringLiterals") {
+ last.value.push(s);
+ } else if (output.length > 0 && last.type === "StringLiteral") {
+ last.type = "StringLiterals";
+ last.value = [last.value,s];
+ } else {
+ output.push({type: "StringLiteral", value: s});
+ }
+ };
+ var compileElement = function(element, options) {
+ options = options || {};
+ var tagBits = [element.type];
+ if(element.attributes) {
+ for(var a in element.attributes) {
+ var r = element.attributes[a];
+ if(a === "style") {
+ var s = [];
+ for(var t in r) {
+ s.push(t + ":" + r[t] + ";");
+ }
+ r = s.join("");
+ }
+ tagBits.push(a + "=\"" + utils.htmlEncode(r) + "\"");
+ }
+ }
+ pushString("<" + tagBits.join(" ") + (options.selfClosing ? " /" : ""));
+ if(options.insertAfterAttributes) {
+ output.push(options.insertAfterAttributes);
+ }
+ pushString(">");
+ if(!options.selfClosing) {
+ if(element.children) {
+ compileSubTree(element.children);
+ }
+ pushString("" + element.type + ">");
+ }
+ };
+ var compileMacroCall = function(name,params) {
+ var macro = me.store.macros[name];
+ if(macro) {
+ var args = new ArgParser(params,{defaultName: "anon"}),
+ paramsProps = {};
+ var insertParam = function(name,arg) {
+ if(arg.evaluated) {
+ var prog = me.store.sandbox.parse(arg.string);
+ paramsProps[m] = prog.elements[0];
+ } else {
+ paramsProps[m] = {type: "StringLiteral", value: arg.string};
+ }
+ };
+ for(var m in macro.params) {
+ var param = macro.params[m];
+ if("byPos" in param && args.byPos[param.byPos]) {
+ insertParam(m,args.byPos[param.byPos].v);
+ } else if("byName" in param) {
+ var arg = args.getValueByName(m);
+ if(!arg && param.byName === "default") {
+ arg = args.getValueByName("anon");
+ }
+ if(arg) {
+ insertParam(m,arg);
+ }
+ }
+ }
+ var macroCall = {
+ type: "FunctionCall",
+ name: {
+ type: "Function",
+ name: null,
+ params: ["params"],
+ elements: []},
+ "arguments": [ {
+ type: "ObjectLiteral",
+ properties: []
+ }]
+ };
+ macroCall.name.elements = macro.code["text/html"].elements;
+ for(m in paramsProps) {
+ macroCall["arguments"][0].properties.push({
+ type: "PropertyAssignment",
+ name: m,
+ value: paramsProps[m]
+ });
+ }
+ output.push(macroCall);
+ } else {
+ pushString("Unknown macro '" + name + "'");
+ }
+ };
+ compileSubTree = function(tree) {
+ for(var t=0; t