Skip to content
Browse files

please read the full description :D

fixes:
1. stopped reusing ctx names
2. passing config information around only as long as necessary now
3. template inheritence works much better now (as in... at all)

new features:
1. asynchronous callback driven interface, allows for lazy loading of templates
2. on_not_found callback hook - a better mechanism will come soon
3. horrible task queue - might be replaced with an event emitter?
  • Loading branch information...
1 parent 4b97aad commit 18cef878f51459e25d9199903b240db3506c828c @deoxxa committed Jan 16, 2012
Showing with 199 additions and 179 deletions.
  1. +131 −142 lib/compiler.js
  2. +68 −37 lib/context.js
View
273 lib/compiler.js
@@ -4,16 +4,19 @@ var uglify = require("uglify-js").uglify,
module.exports = Compiler;
function Compiler() {
- this.seq = 0;
}
-Compiler.prototype.make_ast = function(input) {
+Compiler.prototype.make_ast = function(input, config) {
var self = this;
+ if (!config) { config = {seq: 0, names: []}; }
+
+ config.names.push("ctx_" + config.seq++);
+
var extends_tags = this.join_arrays(input.filter(function(node) {
return node.type === "extends";
}).map(function(node) {
- return self.compile_node(node);
+ return self.compile_node(node, config);
}).filter(function(node) {
return !!node;
}));
@@ -26,281 +29,267 @@ Compiler.prototype.make_ast = function(input) {
var nodes = this.join_arrays(input.filter(function(node) {
return allowed ? (allowed.indexOf(node.type) !== -1) : true;
}).map(function(node) {
- return self.compile_node(node);
+ return self.compile_node(node, config);
}).filter(function(node) {
return !!node;
}));
- return this.join_arrays([
+ var res = this.join_arrays([
[
["var", [
- ["o", ["string", ""]],
- ["e", ["name", extends_tags.length ? "false" : "true"]],
- ]],
- ["stat", [
- "assign",
- true,
- ["name", "ctx"],
- ["call", ["dot", ["name", "ctx"], "create_child"], []],
+ [config.names[config.names.length-1], ["call", ["dot", ["name", "ctx"], "create_child"], []]],
+ ["q", ["call", ["dot", ["name", config.names[config.names.length-1]], "create_queue"], [["name", "cb"]]]],
]],
],
nodes,
extends_tags,
[
- ["stat", [
- "assign",
- true,
- ["name", "ctx"],
- ["call", ["dot", ["name", "ctx"], "leave_parent"], []],
- ]],
- ["return", ["name", "o"]],
+ ["call", ["dot", ["name", "q"], "run"], []],
],
]);
+
+ config.names.pop();
+
+ return res;
};
-Compiler.prototype.compile = function(input) {
- return uglify.gen_code(["toplevel", this.make_ast(input)], {beautify: true, indent_start: 2, indent_level: 2});
+Compiler.prototype.compile = function(input, config) {
+ return uglify.gen_code(["toplevel", this.make_ast(input, config)], {beautify: true, indent_start: 2, indent_level: 2});
};
Compiler.prototype.join_arrays = function(arrays) {
return Array.prototype.concat.apply([], arrays);
};
-Compiler.prototype.compile_node = function(node) {
+Compiler.prototype.compile_node = function(node, config) {
switch (node.type) {
// Output tags
- case "raw": return this.compile_raw(node);
- case "print": return this.compile_print(node);
- case "include": return this.compile_include(node);
+ case "raw": return this.compile_raw(node, config);
+ case "print": return this.compile_print(node, config);
+ case "include": return this.compile_include(node, config);
// Logic tags
- case "if": return this.compile_if(node);
- case "for": return this.compile_for(node);
- case "extends": return this.compile_extends(node);
- case "block": return this.compile_block(node);
+ case "if": return this.compile_if(node, config);
+ case "for": return this.compile_for(node, config);
+ case "extends": return this.compile_extends(node, config);
+ case "block": return this.compile_block(node, config);
}
- console.log("Can't compile node type: " + node.type);
- console.log(node);
-
return;
};
-Compiler.prototype.compile_nodes = function(nodes) {
+Compiler.prototype.compile_nodes = function(nodes, config) {
var self = this;
- return this.join_arrays(nodes.map(function(node) { return self.compile_node(node); }));
+ return this.join_arrays(nodes.map(function(node) { return self.compile_node(node, config); }));
};
-Compiler.prototype.compile_raw = function(node) {
+Compiler.prototype.compile_raw = function(node, config) {
return [[
- "stat",
+ "call",
+ ["dot", ["name", "q"], "push"],
[
- "assign", "+",
- ["name", "o"],
["string", node.data],
],
]];
};
-Compiler.prototype.compile_print = function(node) {
+Compiler.prototype.compile_print = function(node, config) {
return [[
- "stat",
+ "call",
+ ["dot", ["name", "q"], "push"],
[
- "assign", "+",
- ["name", "o"],
- this.compile_expression(node.expression),
+ this.compile_expression(node.expression, config),
],
]];
};
-Compiler.prototype.compile_if = function(node) {
+Compiler.prototype.compile_if = function(node, config) {
var tree = [
- "if", this.compile_condition(node.condition),
- ["block", this.compile_nodes(node.action)],
+ "if", this.compile_condition(node.condition, config),
+ ["block", this.compile_nodes(node.action, config)],
];
var top = tree;
if (node.elsifs) {
for (i in node.elsifs) {
var l = node.elsifs[i];
top.push([
- "if", this.compile_condition(l.condition),
- ["block", this.compile_nodes(l.action)],
+ "if", this.compile_condition(l.condition, config),
+ ["block", this.compile_nodes(l.action, config)],
]);
top = top[top.length - 1];
}
}
if (node.else) {
- top.push(["block", this.compile_nodes(node.else)]);
+ top.push(["block", this.compile_nodes(node.else, config)]);
}
return [tree];
};
-Compiler.prototype.compile_for = function(node) {
- var tmp_key_name = "_tmp_key_" + this.seq++,
- tmp_obj_name = "_tmp_obj_" + this.seq++;
+Compiler.prototype.compile_for = function(node, config) {
+ var tmp_key_name = "_tmp_key_" + config.seq++,
+ tmp_obj_name = "_tmp_obj_" + config.seq++;
- return [
- ["var", [
- [tmp_key_name, ["string", ""]],
- [tmp_obj_name, this.compile_expression(node.config.source)],
- ]],
- [
- "for-in",
- ["name", [tmp_key_name]],
- ["name", [tmp_key_name]],
- ["name", [tmp_obj_name]],
- ["block", [].concat(
+ var ast = [];
+
+ ast.push(["var", [
+ [tmp_key_name, ["string", ""]],
+ [tmp_obj_name, this.compile_expression(node.config.source, config)],
+ ]]);
+
+ config.names.push("ctx_" + config.seq++);
+
+ ast.push([
+ "for-in",
+ ["name", [tmp_key_name]],
+ ["name", [tmp_key_name]],
+ ["name", [tmp_obj_name]],
+ ["block", [
+ ["stat", ["call", ["function", null, [], [].concat(
[
- ["stat", [
- "assign",
- true,
- ["name", "ctx"],
- ["call",
- ["dot", ["name", "ctx"], "create_child"],
+ ["var", [
+ [config.names[config.names.length-1], ["call",
+ ["dot", ["name", config.names[config.names.length-2]], "create_child"],
[
["object", [
[node.config.value.name, ["sub", ["name", tmp_obj_name], ["name", tmp_key_name]]],
[node.config.key || "key", ["name", tmp_key_name]],
]],
],
- ],
+ ]],
]],
],
- this.compile_nodes(node.action),
- [
- ["stat", [
- "assign",
- true,
- ["name", "ctx"],
- ["call", ["dot", ["name", "ctx"], "leave_parent"], []],
- ]],
- ]
- )],
- ],
- ];
-};
+ this.compile_nodes(node.action, config)
+ )]]],
+ ]],
+ ]);
-Compiler.prototype.compile_include = function(node) {
- var self = this;
+ config.names.pop();
- return [[
- "stat",
- [
- "assign",
- "+",
- ["name", "o"],
- [
- "call",
- ["dot", ["name", "ctx"], "render"],
- [this.compile_expression(node.expression)],
- ],
- ],
- ]];
+ return ast;
};
-Compiler.prototype.compile_extends = function(node) {
+Compiler.prototype.compile_include = function(node, config) {
var self = this;
return [
- ["stat", [
- "assign",
- "+",
- ["name", "o"],
+ ["call", ["dot", ["name", "q"], "push"],
+ [
+ ["function", null, ["cb"],
[
- "call",
- ["dot", ["name", "ctx"], "render"],
- [this.compile_expression(node.expression)],
- ],
+ ["call", ["dot", ["name", config.names[config.names.length-1]], "render"],
+ [
+ this.compile_expression(node.expression, config),
+ ["name", "cb"],
+ ]],
+ ]],
]],
];
};
-Compiler.prototype.compile_block = function(node) {
+Compiler.prototype.compile_extends = function(node, config) {
var self = this;
+ config.extending = true;
+
return [
- ["call",
- ["dot", ["name", "ctx"], "add_block"],
+ ["call", ["dot", ["name", "q"], "push"],
+ [
+ ["function", null, ["cb"],
[
- this.compile_expression(node.expression),
- ["function", null, ["ctx"], this.make_ast(node.content)],
- ]
- ],
- ["if", ["name", "e"], ["block", [
- ["stat", [
- "assign",
- "+",
- ["name", "o"],
+ ["call", ["dot", ["name", config.names[config.names.length-1]], "render"],
[
- "call",
- ["dot", ["name", "ctx"], "call_block"],
- [this.compile_expression(node.expression)],
- ],
+ this.compile_expression(node.expression, config),
+ ["name", "cb"],
+ ]],
]],
- ]]],
+ ]],
];
};
-Compiler.prototype.compile_condition = function(input) {
+Compiler.prototype.compile_block = function(node, config) {
+ var self = this;
+
+ var ast = [];
+
+ ast.push(["call", ["dot", ["name", config.names[config.names.length-1]], "add_block"], [
+ this.compile_expression(node.expression, config),
+ ["function", null, ["ctx", "cb"], this.make_ast(node.content, config)],
+ ]]);
+
+ if (!config.extending) {
+ ast.push(["call", ["dot", ["name", "q"], "push"], [
+ ["function", null, ["cb"], [
+ ["call", ["dot", ["name", config.names[config.names.length-1]], "call_block"], [
+ this.compile_expression(node.expression, config),
+ ["name", "cb"],
+ ]],
+ ]],
+ ]]);
+ }
+
+ return ast;
+};
+
+Compiler.prototype.compile_condition = function(input, config) {
if (input.comparison) {
return [
"binary", input.comparison.type,
- this.compile_expression(input.source),
- this.compile_expression(input.comparison.expression),
+ this.compile_expression(input.source, config),
+ this.compile_expression(input.comparison.expression, config),
];
} else {
- return this.compile_expression(input.source);
+ return this.compile_expression(input.source, config);
}
};
-Compiler.prototype.compile_expression = function(input) {
+Compiler.prototype.compile_expression = function(input, config) {
var self = this;
var result;
switch (input.source.type) {
- case "path": { result = this.compile_path(input.source); break; }
- case "string": { result = this.compile_string(input.source); break; }
- case "number": { result = this.compile_number(input.source); break; }
- case "function": { result = this.compile_function(input.source); break; }
+ case "path": { result = this.compile_path(input.source, config); break; }
+ case "string": { result = this.compile_string(input.source, config); break; }
+ case "number": { result = this.compile_number(input.source, config); break; }
+ case "function": { result = this.compile_function(input.source, null, config); break; }
}
input.filters.forEach(function(f) {
- result = self.compile_function(f, result);
+ result = self.compile_function(f, result, config);
});
return result;
};
-Compiler.prototype.compile_string = function(input) {
+Compiler.prototype.compile_string = function(input, config) {
return ["string", input.data];
};
-Compiler.prototype.compile_number = function(input) {
+Compiler.prototype.compile_number = function(input, config) {
return ["num", input.data];
};
-Compiler.prototype.compile_path = function(input) {
+Compiler.prototype.compile_path = function(input, config) {
return [
"call",
- ["dot", ["name", "ctx"], "get_value"],
+ ["dot", ["name", config.names[config.names.length-1]], "get_value"],
[
["array", input.parts.map(function(p) { return ["string", p]; })],
],
];
};
-Compiler.prototype.compile_function = function(input, argument) {
+Compiler.prototype.compile_function = function(input, argument, config) {
var self = this;
return [
"call",
- ["dot", ["name", "ctx"], "call_function"],
+ ["dot", ["name", config.names[config.names.length-1]], "call_function"],
[
["string", input.name.name],
argument || ["name", "null"],
- ["array", input.arguments.map(function(a) { return self.compile_expression(a); })],
+ ["array", input.arguments.map(function(a) { return self.compile_expression(a, config); })],
],
];
};
View
105 lib/context.js
@@ -1,3 +1,39 @@
+function Q(done) {
+ this.jobs = [];
+ this.j = this.jobs;
+ this.result = "";
+ this.done = done;
+}
+
+Q.prototype.run = function() {
+ var self = this;
+
+ if (this.jobs.length == 0) {
+ return this.done(null, this.result);
+ }
+
+ var job = this.jobs.shift();
+
+ if (typeof job == "string") {
+ this.result += job;
+ this.run();
+ } else if (typeof job == "function") {
+ job(function(err, data) {
+ if (typeof data == "string") {
+ self.result += data;
+ }
+
+ self.run();
+ });
+ } else {
+ this.run();
+ }
+};
+
+Q.prototype.push = function(e) {
+ this.jobs.push(e);
+}
+
module.exports = Context;
function Context(vars, parent) {
@@ -6,40 +42,21 @@ function Context(vars, parent) {
this.templates = {};
this.blocks = {};
this.parent = parent;
- this.children = [];
}
+Context.prototype.create_queue = function(done) {
+ return new Q(done);
+};
+
Context.prototype.create_child = function(vars) {
var tmp = new Context(vars, this);
- this.children.push(tmp);
return tmp;
};
-/*
- * These are currently unused - they may be useful in future
- *
-Context.prototype.current_child = function() {
- return this.children.length ? this.children[this.children.length-1] : null;
-};
-
-Context.prototype.deepest_child = function() {
- if (!this.children.length) {
- return this;
- }
-
- return this.current_child().deepest_child();
-};
-*/
-
Context.prototype.chain = function() {
return this.parent ? this.parent.chain().concat([this]) : [this];
};
-Context.prototype.leave_parent = function() {
- this.parent.children.pop();
- return this.parent;
-};
-
Context.prototype.add_function = function(name, fn) {
this.functions[name] = fn;
@@ -68,13 +85,15 @@ Context.prototype.add_template = function(name, fun) {
return this;
};
-Context.prototype.get_template = function(name) {
+Context.prototype.get_template = function(name, cb) {
if (typeof this.templates[name] !== "undefined") {
- return this.templates[name];
+ return cb(null, this.templates[name]);
+ } else if (typeof this.on_not_found == "function") {
+ return this.on_not_found(name, cb);
} else if (this.parent) {
- return this.parent.get_template(name);
+ return this.parent.get_template(name, cb);
} else {
- return null;
+ return cb(Error("Couldn't load template"));
}
};
@@ -94,22 +113,34 @@ Context.prototype.get_block = function(name) {
}
};
-Context.prototype.call_block = function(name) {
- var block = null, chain = this.chain();
-
- chain.forEach(function(link) {
- if (!block) { block = link.get_block(name); }
- });
+Context.prototype.call_block = function(name, cb) {
+ var block = null, current = this, chain = [];
+ while (current.parent) {
+ chain.unshift(current);
+ current = current.parent;
+ }
+ while (block === null && chain.length) {
+ block = chain[0].get_block(name);
+ chain.shift();
+ }
if (block) {
- return block(this);
+ return block(this, cb);
} else {
- return null;
+ return cb(Error("Couldn't find block: " + name));
}
};
-Context.prototype.render = function(name) {
- return this.get_template(name)(this);
+Context.prototype.render = function(name, cb) {
+ var self = this;
+
+ this.get_template(name, function(err, fn) {
+ if (err) {
+ return cb(err);
+ }
+
+ fn(self, cb);
+ });
};
Context.prototype.get_value = function(path) {

0 comments on commit 18cef87

Please sign in to comment.
Something went wrong with that request. Please try again.