Comparing version 0.2.1 to 0.2.3
@@ -10,7 +10,10 @@ var benchmark = require('benchmark'), | ||
function render(input, file) { | ||
return typeof input.apply === 'function' ? input.apply : xjst.compile(input, file).apply; | ||
} | ||
exports.run = function(options) { | ||
function render(input, file) { | ||
return typeof input.apply === 'function' ? | ||
input.apply | ||
: | ||
xjst.compile(input, file, options).apply; | ||
}; | ||
exports.run = function(options) { | ||
console.log([ | ||
@@ -17,0 +20,0 @@ '', |
@@ -15,2 +15,8 @@ var xjst = exports, | ||
// Export engines | ||
xjst.engines = { | ||
'fullgen': require('./xjst/engines/fullgen'), | ||
'sort-group': require('./xjst/engines/sort-group') | ||
}; | ||
// Export compiler stuff | ||
@@ -17,0 +23,0 @@ xjst.parse = require('./xjst/compiler').parse; |
var xjst = require('../xjst'), | ||
fs = require('fs'); | ||
// | ||
// ### function run (options) | ||
// #### @options {Object} Compiler options | ||
// Compiles input stream or file and writes result to output stream or file | ||
// | ||
exports.run = function run(options) { | ||
var input = []; | ||
if (typeof options.input === 'string') { | ||
return finish(fs.readFileSync(options.input).toString()); | ||
} | ||
options.input.on('data', function(chunk) { | ||
@@ -19,12 +20,15 @@ input.push(chunk); | ||
options.input.resume(); | ||
function finish(source) { | ||
var out = xjst.generate(xjst.parse(source)); | ||
var out = xjst.generate(xjst.parse(source), options); | ||
if (typeof options.output === 'string') { | ||
return fs.writeFileSync(options.output, out); | ||
options.output.write(out); | ||
if (options.output === process.stdout) { | ||
options.output.write('\n'); | ||
} else { | ||
options.output.end(); | ||
} | ||
options.output.write(out); | ||
options.output.end('\n'); | ||
} | ||
}; |
@@ -11,24 +11,7 @@ var xjst = require('../xjst'), | ||
function getPredicateValues(templates) { | ||
var vals = {}; | ||
templates.forEach(function(t) { | ||
t[0].forEach(function(subMatch) { | ||
var p = subMatch[0], | ||
c = utils.stringify(subMatch[2]); | ||
vals[p] || (vals[p] = {}); | ||
vals[p][c] = subMatch[2]; | ||
}); | ||
}); | ||
Object.keys(vals).forEach(function(p) { | ||
vals[p] = Object.keys(vals[p]).map(function(key) { | ||
return vals[p][key]; | ||
}); | ||
}); | ||
return vals; | ||
}; | ||
// | ||
// ### function parse (code) | ||
// #### @code {String} XJST source | ||
// Returns AST for input string | ||
// | ||
exports.parse = function parse(code) { | ||
@@ -42,13 +25,13 @@ var tree = XJSTParser.matchAll(code, 'topLevel', undefined, | ||
exports.generate = function generate(templatesAndOther, options) { | ||
var templates = templatesAndOther[1], | ||
predicatesValues = getPredicateValues(templates), | ||
predicateIds, | ||
// | ||
// ### function generate (ast, options) | ||
// #### @ast {Array} XJST-specific AST | ||
// #### @options {Object} compiler options | ||
// Compile XJST template and return it's source code (in javascript) | ||
// | ||
exports.generate = function generate(ast, options) { | ||
var templates = ast[1], | ||
predicateMap = {}, | ||
predicateChilds = {}, | ||
merger = new utils.Merger(), | ||
merge = merger.merge.bind(merger), | ||
unique = new utils.Identifier(), | ||
hashs = {}, | ||
@@ -58,6 +41,11 @@ fns = {}; | ||
// Set default options | ||
options || (options = {}); | ||
if (!options) options = {}; | ||
// Wrap module to allow client-side usage | ||
if (options.wrap !== false) options.wrap = true; | ||
// Include long function names for merging templates on client-side | ||
if (options.merge !== true) options.merge = false; | ||
// Create predicate map : id => stringified AST | ||
templates.forEach(function(template) { | ||
@@ -71,67 +59,22 @@ template[0].forEach(function(predicate) { | ||
var predicateIds = Object.keys(predicateMap); | ||
var predicateIds = Object.keys(predicateMap), | ||
merges = {}; | ||
function traverse(i, j, predicMemo) { | ||
function addNode(node, memo) { | ||
if (node.state) { | ||
node.state = utils.join(node.state, memo, predicateMap, unique); | ||
} else { | ||
node.state = utils.join({}, memo, predicateMap, unique); | ||
} | ||
// Helper function, used for creating names for switch-tree nodes | ||
function fnName(o) { | ||
if (o.tag === 'unexpected') { | ||
o.id = 'e'; | ||
} | ||
if (options.merge && node.switch) { | ||
node.longId = utils.sha1(utils.stringify(node.state)); | ||
} | ||
return node; | ||
}; | ||
var template = templates[i]; | ||
if(!template) { | ||
return addNode( | ||
merge({ tag: 'unexpected', fn: true, stmt: ['throw', ['get', 'true']] }), | ||
predicMemo | ||
); | ||
}; | ||
var subMatch = template[0][j]; | ||
if(!subMatch) return addNode(merge({ stmt: template[1] }), predicMemo); | ||
var known = template[0].slice(j + 1).some(function(s) { | ||
var predicate = s[0], | ||
predicateConst = s[2]; | ||
return predicMemo[predicate] !== undefined && | ||
predicMemo[predicate] != utils.stringify(predicateConst); | ||
}); | ||
if (known) return traverse(i + 1, 0, predicMemo); | ||
var predicate = subMatch[0], | ||
predicateConst = subMatch[2]; | ||
if(predicMemo[predicate] !== undefined) { | ||
if(predicMemo[predicate] === utils.stringify(predicateConst)) { | ||
return traverse(i, j + 1, predicMemo); | ||
} else { | ||
return traverse(i + 1, 0, predicMemo); | ||
} | ||
if (options.merge) { | ||
return '_c.$' + o.id; | ||
} else { | ||
var result = {}; | ||
result.switch = subMatch[1]; | ||
result.cases = predicatesValues[predicate].map(function(v) { | ||
return [v, traverse(i, j, | ||
utils.cloneChanged(predicMemo, predicate, | ||
utils.stringify(v)))]; | ||
}); | ||
result.default = traverse(i, j, utils.cloneChanged(predicMemo, | ||
predicate, | ||
null)); | ||
return addNode(merge(result), predicMemo); | ||
return '$' + o.id; | ||
} | ||
}; | ||
} | ||
// Render subtree | ||
function serialize(o, tails, _parents) { | ||
var merges = {}; | ||
function serialize(o, tails, _parents) { | ||
// Returns a stringified path from root to current node | ||
function getParents() { | ||
@@ -141,13 +84,16 @@ return utils.stringify(_parents.map(function(parent) { | ||
})); | ||
}; | ||
} | ||
// Returns the path from root to current node + current node's id | ||
function parents() { | ||
return options.merge ? _parents.concat(o.longId || o.id) : _parents; | ||
}; | ||
} | ||
if(merges[o.id] !== undefined) { | ||
// If we already seen a node with the same id | ||
// Just call it by it's name | ||
if (merges[o.id] !== undefined) { | ||
tails[o.id] = o; | ||
if (options.merge && o.tag) { | ||
return [ | ||
'return _c.$', o.id, '.call(this,', | ||
'return ', fnName(o), '.call(this,', | ||
getParents(), | ||
@@ -157,3 +103,3 @@ ');' | ||
} else { | ||
return 'return _c.$' + o.id + '.call(this);'; | ||
return 'return ' + fnName(o) + '.call(this);'; | ||
} | ||
@@ -164,18 +110,26 @@ } | ||
if(o.switch) { | ||
if(o.cases.length == 1) { | ||
// If current is not a leaf (i.e. it's a switch) | ||
if (o['switch']) { | ||
// Generate a simple if/else statement if it has only one case | ||
if (o.cases.length == 1) { | ||
var c = o.cases[0]; | ||
res.push( | ||
'if(', | ||
XJSTCompiler.match(o.switch, 'trans'), | ||
'if (', | ||
XJSTCompiler.match(o['switch'], 'skipBraces'), | ||
' === ', | ||
XJSTCompiler.match(c[0], 'trans'), ') {\n', | ||
XJSTCompiler.match(c[0], 'skipBraces'), ') {\n', | ||
serialize(c[1], tails, parents()), | ||
'} else {\n', | ||
serialize(o.default, tails, parents()), | ||
serialize(o['default'], tails, parents()), | ||
'}\n' | ||
); | ||
// Generate multiple if/else if/else statements | ||
// TODO: determine optimal cases' length maximum | ||
} else if (o.cases.length < 32) { | ||
res.push('var __t = ', XJSTCompiler.match(o.switch, 'trans'), '; \n'); | ||
res.push( | ||
'var __t = ', | ||
XJSTCompiler.match(o['switch'], 'skipBraces'), | ||
'; \n' | ||
); | ||
@@ -185,3 +139,3 @@ o.cases.forEach(function(c, i) { | ||
res.push( | ||
' if (__t === ', XJSTCompiler.match(c[0], 'trans'), ') {\n', | ||
' if (__t === ', XJSTCompiler.match(c[0], 'skipBraces'), ') {\n', | ||
serialize(c[1], tails, parents()), | ||
@@ -193,13 +147,15 @@ ' } \n' | ||
' else {\n', | ||
serialize(o.default, tails, parents()), | ||
serialize(o['default'], tails, parents()), | ||
'}' | ||
); | ||
// Turn switch in the hashmap lookup | ||
} else { | ||
var hash = hashs[o.id] = { | ||
map: {}, | ||
default: serialize(o.default, tails, parents()) | ||
'default': serialize(o['default'], tails, parents()) | ||
}; | ||
o.cases.forEach(function(c) { | ||
hash.map[XJSTCompiler.match(c[0], 'trans')] = | ||
hash.map[XJSTCompiler.match(c[0], 'skipBraces')] = | ||
serialize(c[1], tails, parents()); | ||
@@ -209,7 +165,5 @@ }); | ||
res.push( | ||
'return (__h', o.id, '.map[', XJSTCompiler.match(o.switch, 'trans'), | ||
'] ||', '__h', o.id | ||
); | ||
res.push('["default"]).call(this)'); | ||
'return (__h', o.id, '.m[', | ||
XJSTCompiler.match(o['switch'], 'skipBraces'), | ||
'] ||', '__h', o.id, '.d).call(this)'); | ||
} | ||
@@ -219,16 +173,23 @@ | ||
res = ['return _c.$', o.id, '.call(this);']; | ||
res = ['return ', fnName(o), '.call(this);']; | ||
// Compile statement or wrap it into a function | ||
} else { | ||
var body = XJSTCompiler.match(o.stmt, 'trans') + ';\nreturn;'; | ||
var body = XJSTCompiler.match(o.stmt, 'skipBraces') + ';\nreturn;'; | ||
// We should wrap into a function, only if statement is big or | ||
// if we was directly asked to do this | ||
if (o.size > 1 || o.fn) { | ||
// Save function body | ||
fns[o.id] = { body: body, alt: o.longId }; | ||
// Tagged statements should be called with a parents list | ||
// (needed for client-side merging) | ||
if (o.tag) { | ||
res.push( | ||
'return _c.$', o.id, '.call(this,', | ||
'return ', fnName(o), '.call(this,', | ||
getParents(), ');' | ||
); | ||
} else { | ||
res.push('return _c.$', o.id, '.call(this);'); | ||
res.push('return ', fnName(o), '.call(this);'); | ||
} | ||
@@ -243,4 +204,5 @@ } else { | ||
// Get the first tail from the tails list (ordered by keys) | ||
function shiftTails(tails) { | ||
var ids = Object.keys(tails).sort(function(a, b) { return a - b }), | ||
var ids = Object.keys(tails).sort(function(a, b) { return a - b; }), | ||
res = tails[ids[0]]; | ||
@@ -253,11 +215,12 @@ | ||
// Count all nodes' id occurrences | ||
function detectJoins(o, joins) { | ||
if(o.id) { | ||
if(joins[o.id] !== undefined) { | ||
if (o.id) { | ||
if (joins[o.id] !== undefined) { | ||
joins[o.id]++; | ||
} else { | ||
joins[o.id] = 1; | ||
if(o.switch) { | ||
o.cases.forEach(function(c) { detectJoins(c[1], joins) }); | ||
detectJoins(o.default, joins); | ||
if (o['switch']) { | ||
o.cases.forEach(function(c) { detectJoins(c[1], joins); }); | ||
detectJoins(o['default'], joins); | ||
} | ||
@@ -270,2 +233,3 @@ } | ||
// Top serialization function ( @prog {AST} ) | ||
function serializeTop(prog) { | ||
@@ -276,5 +240,6 @@ var res = [], | ||
// Create labels for each join that was occurred more than once | ||
var labels = Object.keys(joins).filter(function(key) { | ||
return joins[key] !== 1; | ||
}).sort(function(a, b) { return b - a }); | ||
}).sort(function(a, b) { return b - a; }); | ||
@@ -285,7 +250,9 @@ labels.forEach(function(key) { | ||
// Start with one tail | ||
var tails = {}; | ||
tails[prog.id] = prog; | ||
// Main serialization loop | ||
var t, first = true, id; | ||
while(t = shiftTails(tails)) { | ||
while (t = shiftTails(tails)) { | ||
delete merges[id = t.id]; | ||
@@ -305,4 +272,5 @@ | ||
return [ | ||
'var _c = exports.config = {};', | ||
'exports.mergeWith = ', utils.mergeWith.toString(), ';', | ||
options.merge ? 'var _c = exports.config = {};' + | ||
// Insert .mergeWith template function | ||
'exports.mergeWith = ' + utils.mergeWith.toString() + ';' : '', | ||
'exports.apply = apply;', | ||
@@ -312,17 +280,10 @@ 'function apply() {' | ||
heads.reverse(), res, '};', | ||
// Insert all switches that was translated to the hashmaps | ||
Object.keys(hashs).map(function(id) { | ||
var res = ['var __h', id, ' = {\n']; | ||
res.push(' "map": {\n'); | ||
res.push(' "m": {\n'); | ||
var keys = Object.keys(hashs[id].map).reduce(function(acc, key) { | ||
if (/^\".*\"$/.test(key)) { | ||
acc.others.push(key); | ||
} else { | ||
acc.numeric.push(key); | ||
} | ||
return acc; | ||
}, { numeric: [], others: [] }); | ||
keys.others.forEach(function(key) { | ||
Object.keys(hashs[id].map).forEach(function(key) { | ||
res.push( | ||
@@ -336,50 +297,33 @@ ' ', key, | ||
res.push(' },\n'); | ||
res.push(' "default": function() {', hashs[id].default, '}'); | ||
res.push(' "d": function() {', hashs[id]['default'], '}'); | ||
res.push('};\n'); | ||
keys.numeric.forEach(function(key) { | ||
res.push( | ||
'__h', id, '.map[', key, '] = function() {', | ||
hashs[id].map[key], '};\n' | ||
); | ||
}); | ||
return res.join(''); | ||
}), | ||
// Insert all switches that was wrapped in the functions | ||
Object.keys(fns).map(function(id) { | ||
var fn = fns[id]; | ||
if (!fn.alt || !options.merge) { | ||
return [ | ||
'_c.$', id, ' = function() {\n', | ||
fn.body, | ||
'\n};' | ||
].join(''); | ||
var fn = fns[id], | ||
prefix; | ||
if (!options.merge) { | ||
prefix = 'function ' + fnName({id: id}) + '() {'; | ||
} else if (!fn.alt) { | ||
prefix = fnName({ id: id }) + ' = function() {'; | ||
} else { | ||
return [ | ||
'_c.$', fn.alt, ' = _c.$', id, | ||
' = function() {', fn.body, '};' | ||
].join(''); | ||
prefix = fnName({ id: fn.alt }) + ' = ' + | ||
fnName({ id: id }) + ' = function() {'; | ||
} | ||
return prefix + fn.body + '};'; | ||
}) | ||
).join(''); | ||
}; | ||
} | ||
function reduceTree(tree, fn, acc) { | ||
if (tree.switch) { | ||
acc = fn(acc, tree); | ||
// Optimizes `apply()` statements by redirecting recursive calls into the | ||
// middle of the tree | ||
function optimizeRecursion(tree) { | ||
acc = tree.cases.reduce(function(acc, c) { | ||
return reduceTree(c[1], fn, acc); | ||
}, acc); | ||
acc = reduceTree(tree.default, fn, acc); | ||
} else { | ||
acc = fn(acc, tree); | ||
} | ||
return acc; | ||
}; | ||
function optimizeRecursion(tree) { | ||
// Update predicates with data in local | ||
var applies = reduceTree(tree, function(acc, node) { | ||
// Traverse tree | ||
var applies = utils.reduceTree(tree, function(acc, node) { | ||
if (!node.stmt) return acc; | ||
@@ -390,5 +334,8 @@ if (node._visited) return acc; | ||
// Get locals and applies sequence: | ||
// local () { ... apply() ... } => ['localStart', 'apply', 'localEnd'] | ||
var seq = XJSTLocalAndApplyCompiler.match(node.stmt, 'topLevel'); | ||
if (seq.length === 0) return acc; | ||
// Create a state for each individual apply (based on the node's state) | ||
var locals = []; | ||
@@ -401,5 +348,8 @@ seq.forEach(function(op) { | ||
local.forEach(function(as) { | ||
// If local was setting not a constant value to the predicate | ||
// Remove all available information about it (predicate) | ||
if (as[2] === 'reset') { | ||
delete state[predicateMap[as[0]]]; | ||
} else { | ||
// Update cloned state | ||
state[predicateMap[as[0]]] = [utils.stringify(as[2])]; | ||
@@ -440,2 +390,3 @@ } | ||
// Go through all collected applies | ||
applies.forEach(function(apply) { | ||
@@ -448,8 +399,11 @@ // If nothing was changed before .apply call | ||
var result = reduceTree(tree, function(acc, node) { | ||
// Find a node with state nested into `apply`'s state | ||
var result = utils.reduceTree(tree, function(acc, node) { | ||
// Apply should not redirect to itself | ||
if (node.id === apply.node.id) return acc; | ||
// Compare states | ||
var score = utils.compareSets(apply.state, node.state); | ||
// Find the best match | ||
if (score !== null && score >= 0 && score < acc.score) { | ||
@@ -465,15 +419,31 @@ acc.score = score; | ||
// If the node is matching our condition - we should wrap it into a | ||
// function | ||
result.node.fn = true; | ||
apply.op.code = '_c.$' + result.node.id + '.call(this)'; | ||
// Mark apply as optimized | ||
apply.op.code = fnName(result.node) + '.call(this)'; | ||
}); | ||
return tree; | ||
}; | ||
} | ||
var body = [ | ||
XJSTCompiler.match(templatesAndOther[0], 'other'), | ||
serializeTop(optimizeRecursion(traverse(0, 0, {}, []))) | ||
]; | ||
var body; | ||
if (options['no-opt']) { | ||
// Just compile `template` to `if`, and `local` | ||
body = [ XJSTCompiler.match(ast, 'topLevel') ]; | ||
} else { | ||
// Optimize recursion and minimize comparisons tree | ||
var engine = xjst.engines[options.engine] || xjst.engines.fullgen, | ||
tree = optimizeRecursion(engine(templates, options, predicateMap)); | ||
// Finally render tree | ||
body = [ | ||
XJSTCompiler.match(ast[0], 'other'), | ||
serializeTop(tree) | ||
]; | ||
} | ||
// Wrap output for client-side usage | ||
var result; | ||
@@ -489,6 +459,18 @@ if (options.wrap) { | ||
return uglify.uglify.gen_code(uglify.parser.parse(result), | ||
{ beautify: true }); | ||
// Compress or beautify the output | ||
if (options.uglify) { | ||
return uglify(result); | ||
} else { | ||
return uglify.uglify.gen_code(uglify.parser.parse(result), | ||
{ beautify: true }); | ||
} | ||
}; | ||
// | ||
// ### function compile(code, filename, options) | ||
// #### @code {String} Input XJST source | ||
// #### @filename {String} Optional filename for better stack traces | ||
// #### @options {Object} Compilation options | ||
// Parses and compiles XJST template to javascript function | ||
// | ||
exports.compile = function compile(code, filename, options) { | ||
@@ -504,2 +486,3 @@ // XXX this is temporary fix to make API compatible | ||
// Compile and evaluate source | ||
var parsed = exports.parse(code), | ||
@@ -509,5 +492,7 @@ compiled = exports.generate(parsed, options), | ||
// Provide nice API by returning the function instead of object | ||
function render(locals) { | ||
return evaluated.apply.call(locals); | ||
}; | ||
} | ||
render.apply = evaluated.apply; | ||
@@ -514,0 +499,0 @@ render.mergeWith = evaluated.mergeWith; |
@@ -28,2 +28,739 @@ var ometajs_ = require('ometajs').globals;var StringBuffer = ometajs_.StringBuffer; | ||
var BSOMetaJSTranslator = ometajs_.BSOMetaJSTranslator; | ||
{} | ||
{ | ||
var ometajs = require("ometajs"), xjst = require("../../xjst"), utils = xjst["utils"], Identifier = utils["Identifier"], BSJSParser = ometajs["BSJSParser"], BSJSTranslator = ometajs["BSJSTranslator"], BSJSIdentity = ometajs["BSJSIdentity"]; | ||
var XJSTParser = exports.XJSTParser = objectThatDelegatesTo(BSJSParser, { | ||
isKeyword: function() { | ||
var $elf = this, _fromIdx = this.input.idx, x; | ||
return function() { | ||
x = this._apply("anything"); | ||
return this._pred(BSJSParser._isKeyword(x) || x === "local" || x === "apply" || x === "template"); | ||
}.call(this); | ||
}, | ||
primExprHd: function() { | ||
var $elf = this, _fromIdx = this.input.idx, a, b, r, st, a, st, r, expr; | ||
return this._or(function() { | ||
return function() { | ||
this._applyWithArgs("token", "local"); | ||
this._applyWithArgs("token", "("); | ||
a = this._apply("expr"); | ||
this._applyWithArgs("token", ")"); | ||
b = this._apply("expr"); | ||
r = this._applyWithArgs("localExpr", [ "local", a, b ]); | ||
return r; | ||
}.call(this); | ||
}, function() { | ||
return function() { | ||
this._applyWithArgs("token", "apply"); | ||
this._applyWithArgs("token", "("); | ||
this._applyWithArgs("token", ")"); | ||
st = this._applyWithArgs("applyStmt", [ "apply" ]); | ||
return st; | ||
}.call(this); | ||
}, function() { | ||
return function() { | ||
this._applyWithArgs("token", "apply"); | ||
this._applyWithArgs("token", "("); | ||
a = this._apply("expr"); | ||
this._applyWithArgs("token", ")"); | ||
st = this._applyWithArgs("applyStmt", [ "apply" ]); | ||
r = this._applyWithArgs("localExpr", [ "local", a, st ]); | ||
return r; | ||
}.call(this); | ||
}, function() { | ||
return function() { | ||
this._applyWithArgs("token", "apply"); | ||
expr = this._applyWithArgs("applyExpr", [ "apply" ]); | ||
return expr; | ||
}.call(this); | ||
}, function() { | ||
return BSJSParser._superApplyWithArgs(this, "primExprHd"); | ||
}); | ||
}, | ||
stmt: function() { | ||
var $elf = this, _fromIdx = this.input.idx, a, b, r, st, a, st, r; | ||
return this._or(function() { | ||
return function() { | ||
this._applyWithArgs("token", "local"); | ||
this._applyWithArgs("token", "("); | ||
a = this._apply("expr"); | ||
this._applyWithArgs("token", ")"); | ||
b = this._apply("stmt"); | ||
r = this._applyWithArgs("localStmt", [ "local", a, b ]); | ||
return r; | ||
}.call(this); | ||
}, function() { | ||
return function() { | ||
this._applyWithArgs("token", "apply"); | ||
this._applyWithArgs("token", "("); | ||
this._applyWithArgs("token", ")"); | ||
st = this._applyWithArgs("applyStmt", [ "apply" ]); | ||
return st; | ||
}.call(this); | ||
}, function() { | ||
return function() { | ||
this._applyWithArgs("token", "apply"); | ||
this._applyWithArgs("token", "("); | ||
a = this._apply("expr"); | ||
this._applyWithArgs("token", ")"); | ||
st = this._applyWithArgs("applyStmt", [ "apply" ]); | ||
r = this._applyWithArgs("localStmt", [ "local", a, st ]); | ||
return r; | ||
}.call(this); | ||
}, function() { | ||
return BSJSParser._superApplyWithArgs(this, "stmt"); | ||
}); | ||
}, | ||
template: function() { | ||
var $elf = this, _fromIdx = this.input.idx, m, b; | ||
return function() { | ||
this._applyWithArgs("token", "template"); | ||
this._applyWithArgs("token", "("); | ||
m = this._apply("expr"); | ||
this._applyWithArgs("token", ")"); | ||
b = this._apply("stmt"); | ||
return [ "template", m, b ]; | ||
}.call(this); | ||
}, | ||
applyStmt: function() { | ||
var $elf = this, _fromIdx = this.input.idx; | ||
return function() { | ||
this._form(function() { | ||
return this._applyWithArgs("exactly", "apply"); | ||
}); | ||
return [ "nhApplyStmt", {} ]; | ||
}.call(this); | ||
}, | ||
applyExpr: function() { | ||
var $elf = this, _fromIdx = this.input.idx; | ||
return function() { | ||
this._form(function() { | ||
return this._applyWithArgs("exactly", "apply"); | ||
}); | ||
return [ "nhApplyExpr" ]; | ||
}.call(this); | ||
}, | ||
localStmt: function() { | ||
var $elf = this, _fromIdx = this.input.idx, a, b; | ||
return function() { | ||
this._form(function() { | ||
return function() { | ||
this._applyWithArgs("exactly", "local"); | ||
a = this._apply("anything"); | ||
return b = this._apply("anything"); | ||
}.call(this); | ||
}); | ||
return [ "localStmt", a, b ]; | ||
}.call(this); | ||
}, | ||
localExpr: function() { | ||
var $elf = this, _fromIdx = this.input.idx, a, b; | ||
return function() { | ||
this._form(function() { | ||
return function() { | ||
this._applyWithArgs("exactly", "local"); | ||
a = this._apply("anything"); | ||
return b = this._apply("anything"); | ||
}.call(this); | ||
}); | ||
return [ "localExpr", a, b ]; | ||
}.call(this); | ||
}, | ||
topLevel: function() { | ||
var $elf = this, _fromIdx = this.input.idx, t, s, ts; | ||
return function() { | ||
ts = this._many1(function() { | ||
return this._or(function() { | ||
return function() { | ||
t = this._apply("template"); | ||
return t; | ||
}.call(this); | ||
}, function() { | ||
return function() { | ||
s = this._apply("srcElem"); | ||
return [ "stmt", s ]; | ||
}.call(this); | ||
}); | ||
}); | ||
this._apply("spaces"); | ||
this._apply("end"); | ||
return ts; | ||
}.call(this); | ||
} | ||
}); | ||
var XJSTTranslator = exports.XJSTTranslator = objectThatDelegatesTo(BSJSIdentity, { | ||
"const": function() { | ||
var $elf = this, _fromIdx = this.input.idx, s, n; | ||
return this._or(function() { | ||
return this._form(function() { | ||
return function() { | ||
this._applyWithArgs("exactly", "string"); | ||
return s = this._apply("anything"); | ||
}.call(this); | ||
}); | ||
}, function() { | ||
return this._form(function() { | ||
return function() { | ||
this._applyWithArgs("exactly", "number"); | ||
return n = this._apply("anything"); | ||
}.call(this); | ||
}); | ||
}); | ||
}, | ||
nhApplyStmt: function() { | ||
var $elf = this, _fromIdx = this.input.idx, p; | ||
return function() { | ||
p = this._apply("anything"); | ||
return [ "applyStmt", p ]; | ||
}.call(this); | ||
}, | ||
nhApplyExpr: function() { | ||
var $elf = this, _fromIdx = this.input.idx; | ||
return [ "nhApplyExpr" ]; | ||
}, | ||
localStmt: function() { | ||
var $elf = this, _fromIdx = this.input.idx, as, t; | ||
return function() { | ||
as = this._apply("localAsmts"); | ||
t = this._apply("trans"); | ||
return [ "begin" ].concat([ [ "localStart", XJSTTranslator._localToPred(this["identifier"], as) ] ], as[0], [ t ], as[1], [ [ "localEnd" ] ]); | ||
}.call(this); | ||
}, | ||
localExpr: function() { | ||
var $elf = this, _fromIdx = this.input.idx, as, t; | ||
return function() { | ||
as = this._apply("localAsmts"); | ||
t = this._apply("trans"); | ||
return function() { | ||
var prelude = [ [ "localStart", XJSTTranslator._localToPred(this["identifier"], as) ] ], result = XJSTTranslator._getLocalVar(this), $elf = this; | ||
as[0].forEach(function(e) { | ||
if (e[0] === "var") { | ||
e.slice(1).forEach(function(v) { | ||
$elf["_vars"].push([ v[0] ]); | ||
prelude.push([ "set", [ "get", v[0] ], v[1] ]); | ||
}); | ||
} else { | ||
prelude.push(e); | ||
} | ||
}); | ||
return [].concat(prelude, [ [ "set", result, t ] ], as[1], [ [ "localEnd" ], result ]).reduce(function(a, i) { | ||
return a ? [ "binop", ",", a, i ] : i; | ||
}); | ||
}.call(this); | ||
}.call(this); | ||
}, | ||
bindingToAsmt: function() { | ||
var $elf = this, _fromIdx = this.input.idx, k, v, r; | ||
return function() { | ||
this._form(function() { | ||
return function() { | ||
this._applyWithArgs("exactly", "binding"); | ||
k = this._apply("anything"); | ||
return v = this._apply("anything"); | ||
}.call(this); | ||
}); | ||
r = this._applyWithArgs("localAsmt", [ "set", [ "getp", [ "string", k ], [ "this" ] ], v ]); | ||
return r; | ||
}.call(this); | ||
}, | ||
localAsmts: function() { | ||
var $elf = this, _fromIdx = this.input.idx, as, e1, es, e2; | ||
return this._or(function() { | ||
return function() { | ||
this._form(function() { | ||
return function() { | ||
this._applyWithArgs("exactly", "json"); | ||
return as = this._many(function() { | ||
return this._apply("bindingToAsmt"); | ||
}); | ||
}.call(this); | ||
}); | ||
return function() { | ||
var es = []; | ||
as.forEach(function(a) { | ||
a.forEach(function(e, i) { | ||
es[i] = es[i] ? es[i].concat(e) : e; | ||
}); | ||
}); | ||
return es; | ||
}.call(this); | ||
}.call(this); | ||
}, function() { | ||
return function() { | ||
e1 = this._apply("localAsmt"); | ||
return e1; | ||
}.call(this); | ||
}, function() { | ||
return function() { | ||
this._form(function() { | ||
return function() { | ||
this._applyWithArgs("exactly", "binop"); | ||
this._applyWithArgs("exactly", ","); | ||
es = this._apply("localAsmts"); | ||
return e2 = this._apply("localAsmt"); | ||
}.call(this); | ||
}); | ||
return function() { | ||
es.forEach(function(e, i) { | ||
es[i] = e.concat(e2[i]); | ||
}); | ||
return es; | ||
}.call(this); | ||
}.call(this); | ||
}); | ||
}, | ||
localAsmt: function() { | ||
var $elf = this, _fromIdx = this.input.idx, n, k, o, p, v, props; | ||
return function() { | ||
this._form(function() { | ||
return function() { | ||
this._applyWithArgs("exactly", "set"); | ||
p = this._form(function() { | ||
return function() { | ||
switch (this._apply("anything")) { | ||
case "getp": | ||
return function() { | ||
k = this._apply("anything"); | ||
return o = this._apply("anything"); | ||
}.call(this); | ||
case "get": | ||
return n = this._apply("anything"); | ||
default: | ||
throw fail; | ||
} | ||
}.call(this); | ||
}); | ||
return v = this._apply("anything"); | ||
}.call(this); | ||
}); | ||
props = this._applyWithArgs("localProps", p); | ||
return function() { | ||
var lv = XJSTTranslator._getLocalVar(this), vars = [ [ "var" ].concat(props[1], [ [ lv[1], props[0] ] ]) ]; | ||
return [ vars.concat([ [ "set", props[0], v ] ]), [ [ "set", props[0], lv ] ], [ [ p, v ] ] ]; | ||
}.call(this); | ||
}.call(this); | ||
}, | ||
localProps: function() { | ||
var $elf = this, _fromIdx = this.input.idx, k, expr, k, o, expr, k, k, o, k, o, expr; | ||
return this._or(function() { | ||
return function() { | ||
expr = this._form(function() { | ||
return function() { | ||
this._applyWithArgs("exactly", "getp"); | ||
k = this._apply("const"); | ||
return this._form(function() { | ||
return this._applyWithArgs("exactly", "this"); | ||
}); | ||
}.call(this); | ||
}); | ||
return [ expr, [] ]; | ||
}.call(this); | ||
}, function() { | ||
return function() { | ||
expr = this._form(function() { | ||
return function() { | ||
this._applyWithArgs("exactly", "getp"); | ||
k = this._apply("const"); | ||
return this._form(function() { | ||
return function() { | ||
this._applyWithArgs("exactly", "get"); | ||
return o = this._apply("anything"); | ||
}.call(this); | ||
}); | ||
}.call(this); | ||
}); | ||
return [ expr, [] ]; | ||
}.call(this); | ||
}, function() { | ||
return function() { | ||
this._form(function() { | ||
return function() { | ||
this._applyWithArgs("exactly", "getp"); | ||
k = this._apply("anything"); | ||
return this._form(function() { | ||
return this._applyWithArgs("exactly", "this"); | ||
}); | ||
}.call(this); | ||
}); | ||
return function() { | ||
var v = XJSTTranslator._getLocalVar(this); | ||
return [ [ "getp", v, [ "this" ] ], [ [ v[1], k ] ] ]; | ||
}.call(this); | ||
}.call(this); | ||
}, function() { | ||
return function() { | ||
this._form(function() { | ||
return function() { | ||
this._applyWithArgs("exactly", "getp"); | ||
k = this._apply("const"); | ||
return o = this._apply("anything"); | ||
}.call(this); | ||
}); | ||
return function() { | ||
var v = XJSTTranslator._getLocalVar(this); | ||
return [ [ "getp", k, v ], [ [ v[1], o ] ] ]; | ||
}.call(this); | ||
}.call(this); | ||
}, function() { | ||
return function() { | ||
this._form(function() { | ||
return function() { | ||
this._applyWithArgs("exactly", "getp"); | ||
k = this._apply("anything"); | ||
return o = this._apply("anything"); | ||
}.call(this); | ||
}); | ||
return function() { | ||
var v1 = XJSTTranslator._getLocalVar(this), v2 = XJSTTranslator._getLocalVar(this); | ||
return [ [ "getp", v1, v2 ], [ [ v1[1], k ], [ v2[1], o ] ] ]; | ||
}.call(this); | ||
}.call(this); | ||
}, function() { | ||
return function() { | ||
expr = this._apply("anything"); | ||
return [ expr, [] ]; | ||
}.call(this); | ||
}); | ||
}, | ||
subMatch: function() { | ||
var $elf = this, _fromIdx = this.input.idx, e1, c, c, e2, e3; | ||
return this._or(function() { | ||
return function() { | ||
this._form(function() { | ||
return function() { | ||
this._applyWithArgs("exactly", "binop"); | ||
this._applyWithArgs("exactly", "==="); | ||
e1 = this._apply("anything"); | ||
return c = this._apply("const"); | ||
}.call(this); | ||
}); | ||
return [ e1, c ]; | ||
}.call(this); | ||
}, function() { | ||
return function() { | ||
this._form(function() { | ||
return function() { | ||
this._applyWithArgs("exactly", "binop"); | ||
this._applyWithArgs("exactly", "==="); | ||
c = this._apply("const"); | ||
return e2 = this._apply("anything"); | ||
}.call(this); | ||
}); | ||
return [ e2, c ]; | ||
}.call(this); | ||
}, function() { | ||
return function() { | ||
e3 = this._apply("anything"); | ||
return [ [ "unop", "!", e3 ], [ "get", "false" ] ]; | ||
}.call(this); | ||
}); | ||
}, | ||
expr2match: function() { | ||
var $elf = this, _fromIdx = this.input.idx, ms, m1, m2; | ||
return this._or(function() { | ||
return function() { | ||
this._form(function() { | ||
return function() { | ||
this._applyWithArgs("exactly", "binop"); | ||
this._applyWithArgs("exactly", "&&"); | ||
ms = this._apply("expr2match"); | ||
return m1 = this._apply("subMatch"); | ||
}.call(this); | ||
}); | ||
return function() { | ||
ms.push(m1); | ||
return ms; | ||
}.call(this); | ||
}.call(this); | ||
}, function() { | ||
return function() { | ||
m2 = this._apply("subMatch"); | ||
return [ m2 ]; | ||
}.call(this); | ||
}); | ||
}, | ||
template: function() { | ||
var $elf = this, _fromIdx = this.input.idx, m, b; | ||
return function() { | ||
m = this._apply("anything"); | ||
b = this._apply("trans"); | ||
return [ "template", [ XJSTTranslator.match(m, "expr2match"), b ] ]; | ||
}.call(this); | ||
}, | ||
stmt: function() { | ||
var $elf = this, _fromIdx = this.input.idx, s; | ||
return function() { | ||
s = this._apply("trans"); | ||
return [ "stmt", s ]; | ||
}.call(this); | ||
}, | ||
topLevel: function() { | ||
var $elf = this, _fromIdx = this.input.idx, ts; | ||
return function() { | ||
((function() { | ||
this["_vars"] = []; | ||
return this["identifier"] = new Identifier; | ||
})).call(this); | ||
ts = this._many(function() { | ||
return this._apply("trans"); | ||
}); | ||
return function() { | ||
if (this["_vars"]["length"]) { | ||
this["_vars"].unshift("var"); | ||
ts.unshift([ "stmt", this["_vars"] ]); | ||
} else { | ||
undefined; | ||
} | ||
return XJSTTranslator._splitTemplates(this["identifier"], ts); | ||
}.call(this); | ||
}.call(this); | ||
} | ||
}); | ||
XJSTTranslator["_getLocalIdCounter"] = 0; | ||
XJSTTranslator["_getLocalId"] = function() { | ||
return this["_getLocalIdCounter"]++; | ||
}; | ||
XJSTTranslator["_getLocalVar"] = function(p) { | ||
var id = this._getLocalId(); | ||
return [ "get", "__r" + id ]; | ||
}; | ||
XJSTTranslator["_localToPred"] = function(identifier, as) { | ||
return as[2].map(function(as) { | ||
as = [ identifier.identify(as[0]), as[0], as[1] ]; | ||
if (as[2][0] !== "string" && as[2][0] !== "number") { | ||
return [ as[0], as[1], "reset" ]; | ||
} else { | ||
return as; | ||
} | ||
}); | ||
}; | ||
XJSTTranslator["_splitTemplates"] = function(predicates, ts) { | ||
var templates = [], other = [], i; | ||
while (i = ts.shift()) { | ||
i[0] == "template" ? templates.unshift(i[1]) : other.push(i[1]); | ||
} | ||
return [ other, XJSTTranslator._identify(predicates, templates) ]; | ||
}; | ||
XJSTTranslator["_identify"] = function(predicates, templates) { | ||
templates.forEach(function(template) { | ||
template[0].forEach(function(subMatch) { | ||
subMatch.unshift(predicates.identify(subMatch[0])); | ||
}); | ||
}); | ||
return templates; | ||
}; | ||
var XJSTLocalAndApplyCompiler = exports.XJSTLocalAndApplyCompiler = objectThatDelegatesTo(BSJSIdentity, { | ||
applyStmt: function() { | ||
var $elf = this, _fromIdx = this.input.idx, p; | ||
return function() { | ||
p = this._apply("anything"); | ||
return function() { | ||
this["result"].push([ "apply", p ]); | ||
return [ "applyStmt", p ]; | ||
}.call(this); | ||
}.call(this); | ||
}, | ||
nhApplyExpr: function() { | ||
var $elf = this, _fromIdx = this.input.idx; | ||
return this._form(function() { | ||
return this._applyWithArgs("exactly", "nhApplyExpr"); | ||
}); | ||
}, | ||
localStart: function() { | ||
var $elf = this, _fromIdx = this.input.idx, as; | ||
return function() { | ||
as = this._apply("anything"); | ||
return function() { | ||
this["result"].push([ "localStart", as ]); | ||
return [ "localStart", as ]; | ||
}.call(this); | ||
}.call(this); | ||
}, | ||
localEnd: function() { | ||
var $elf = this, _fromIdx = this.input.idx; | ||
return function() { | ||
this["result"].push([ "localEnd" ]); | ||
return [ "localEnd" ]; | ||
}.call(this); | ||
}, | ||
topLevel: function() { | ||
var $elf = this, _fromIdx = this.input.idx, t; | ||
return function() { | ||
this["result"] = []; | ||
t = this._apply("trans"); | ||
return this["result"]; | ||
}.call(this); | ||
} | ||
}); | ||
var XJSTCompiler = exports.XJSTCompiler = objectThatDelegatesTo(BSJSTranslator, { | ||
applyStmt: function() { | ||
var $elf = this, _fromIdx = this.input.idx, param; | ||
return function() { | ||
param = this._apply("anything"); | ||
return param["code"] || "apply.call(this)"; | ||
}.call(this); | ||
}, | ||
nhApplyStmt: function() { | ||
var $elf = this, _fromIdx = this.input.idx, param; | ||
return function() { | ||
param = this._apply("anything"); | ||
return "apply()"; | ||
}.call(this); | ||
}, | ||
nhApplyExpr: function() { | ||
var $elf = this, _fromIdx = this.input.idx; | ||
return "apply"; | ||
}, | ||
localStmt: function() { | ||
var $elf = this, _fromIdx = this.input.idx, a, b; | ||
return function() { | ||
a = this._apply("trans"); | ||
b = this._apply("trans"); | ||
return "local (" + a + ") " + b + ";"; | ||
}.call(this); | ||
}, | ||
localExpr: function() { | ||
var $elf = this, _fromIdx = this.input.idx, a, b; | ||
return function() { | ||
a = this._apply("trans"); | ||
b = this._apply("trans"); | ||
return "local (" + a + ") " + b; | ||
}.call(this); | ||
}, | ||
localStart: function() { | ||
var $elf = this, _fromIdx = this.input.idx, as; | ||
return function() { | ||
as = this._apply("anything"); | ||
return '""'; | ||
}.call(this); | ||
}, | ||
localEnd: function() { | ||
var $elf = this, _fromIdx = this.input.idx; | ||
return '""'; | ||
}, | ||
subMatch: function() { | ||
var $elf = this, _fromIdx = this.input.idx, id, m, id, e, c; | ||
return this._or(function() { | ||
return function() { | ||
this._form(function() { | ||
return function() { | ||
id = this._apply("anything"); | ||
m = this._apply("trans"); | ||
return this._form(function() { | ||
return function() { | ||
this._applyWithArgs("exactly", "get"); | ||
return this._applyWithArgs("exactly", "true"); | ||
}.call(this); | ||
}); | ||
}.call(this); | ||
}); | ||
return m; | ||
}.call(this); | ||
}, function() { | ||
return function() { | ||
this._form(function() { | ||
return function() { | ||
id = this._apply("anything"); | ||
e = this._apply("trans"); | ||
return c = this._apply("trans"); | ||
}.call(this); | ||
}); | ||
return e + " === " + c; | ||
}.call(this); | ||
}); | ||
}, | ||
tMatch: function() { | ||
var $elf = this, _fromIdx = this.input.idx, m, ms; | ||
return this._or(function() { | ||
return function() { | ||
this._form(function() { | ||
return m = this._apply("subMatch"); | ||
}); | ||
return m; | ||
}.call(this); | ||
}, function() { | ||
return function() { | ||
this._form(function() { | ||
return ms = this._many1(function() { | ||
return this._apply("subMatch"); | ||
}); | ||
}); | ||
return ms.join(" && "); | ||
}.call(this); | ||
}); | ||
}, | ||
tBody: function() { | ||
var $elf = this, _fromIdx = this.input.idx, e; | ||
return function() { | ||
e = this._apply("trans"); | ||
return e; | ||
}.call(this); | ||
}, | ||
template: function() { | ||
var $elf = this, _fromIdx = this.input.idx, m, b; | ||
return function() { | ||
this._form(function() { | ||
return function() { | ||
m = this._apply("tMatch"); | ||
return b = this._apply("tBody"); | ||
}.call(this); | ||
}); | ||
return "if(" + m + ") {" + b + ";return}"; | ||
}.call(this); | ||
}, | ||
templates: function() { | ||
var $elf = this, _fromIdx = this.input.idx, ts; | ||
return function() { | ||
this._form(function() { | ||
return ts = this._many(function() { | ||
return this._apply("template"); | ||
}); | ||
}); | ||
return "exports.apply = apply;function apply(c) {\n" + ts.join("\n") + "\n};"; | ||
}.call(this); | ||
}, | ||
other: function() { | ||
var $elf = this, _fromIdx = this.input.idx, o; | ||
return function() { | ||
this._form(function() { | ||
return o = this._many(function() { | ||
return this._apply("trans"); | ||
}); | ||
}); | ||
return o.join(";"); | ||
}.call(this); | ||
}, | ||
skipBraces: function() { | ||
var $elf = this, _fromIdx = this.input.idx, e, e; | ||
return this._or(function() { | ||
return function() { | ||
this._form(function() { | ||
return function() { | ||
this._applyWithArgs("exactly", "begin"); | ||
return e = this._many(function() { | ||
return this._apply("skipBraces"); | ||
}); | ||
}.call(this); | ||
}); | ||
return e.join(";"); | ||
}.call(this); | ||
}, function() { | ||
return function() { | ||
e = this._apply("trans"); | ||
return e; | ||
}.call(this); | ||
}); | ||
}, | ||
topLevel: function() { | ||
var $elf = this, _fromIdx = this.input.idx, o, t; | ||
return function() { | ||
this._form(function() { | ||
return function() { | ||
o = this._apply("other"); | ||
return t = this._apply("templates"); | ||
}.call(this); | ||
}); | ||
return "(function(exports) {" + o + ";" + t + 'return exports})(typeof exports === "undefined"? {} : exports)'; | ||
}.call(this); | ||
} | ||
}); | ||
} |
var utils = exports, | ||
crypto = require('crypto'); | ||
// | ||
// ### function sha1 (value) | ||
// #### @value {String|Buffer} Input data | ||
// Returns hex digest of sha1 hash of input data | ||
// | ||
utils.sha1 = function sha1(value) { | ||
@@ -8,5 +13,31 @@ return crypto.createHash('sha1').update(value).digest('hex'); | ||
// | ||
// ### function hashName (value) | ||
// #### @value {String} Input data | ||
// Returns valid name for javascript variable (based on input's md5 hash) | ||
// | ||
utils.hashName = function hashName(value) { | ||
var hash = crypto.createHash('md5').update(value).digest('base64'); | ||
// Replace base64 specific chars | ||
hash = hash.replace(/\+/g, '$').replace(/\//g, '_'); | ||
// Remove padding | ||
hash = hash.replace(/=+$/g, ''); | ||
if (/^[0-9]/.test(hash)) { | ||
hash = '$' + hash; | ||
} | ||
return hash; | ||
}; | ||
// | ||
// ### function mergeWith (template) | ||
// #### @template {Object} Another template | ||
// Template function for compiled XJST | ||
// | ||
utils.mergeWith = function mergeWith(template) { | ||
var cache = {}; | ||
_c.$unexpected = function(parents) { | ||
_c.$e = function(parents) { | ||
var match = cache[parents[0]], | ||
@@ -34,63 +65,7 @@ fn; | ||
function Identifier () { | ||
this.counter = 0; | ||
this.cache = {}; | ||
} | ||
utils.Identifier = Identifier; | ||
Identifier.prototype.identify = function identify(o) { | ||
var cache = this.cache, | ||
key = JSON.stringify(o); | ||
if(cache[key] !== undefined) { | ||
return cache[key]; | ||
} else { | ||
return cache[key] = ++this.counter; | ||
} | ||
}; | ||
Identifier.prototype.generate = function() { | ||
return ++this.counter; | ||
}; | ||
utils.stringify = function stringify(v) { | ||
if (v === undefined) return 'undefined'; | ||
return typeof v !== 'string' ? JSON.stringify(v) : v; | ||
}; | ||
utils.join = function(result, a, values, unique) { | ||
Object.keys(a).forEach(function(key) { | ||
var matched = values[key], | ||
val; | ||
if (a[key] === null) { | ||
result[matched] = [unique.generate()]; | ||
return; | ||
} | ||
val = a[key] || 'undefined'; | ||
if (result[matched]) { | ||
var orig = val, | ||
exists = result[matched].some(function(v) { | ||
return v === orig; | ||
}); | ||
if (!exists) { | ||
result[matched].push(orig); | ||
result[matched].sort(); | ||
} | ||
} else { | ||
result[matched] = [val]; | ||
} | ||
}); | ||
return result; | ||
}; | ||
// | ||
// Check if source is nested in target | ||
// ### function compareArrays (source, target) | ||
// #### @source {Array} Source array | ||
// #### @target {Array} Target array | ||
// Check if target array has all elements from source | ||
// | ||
@@ -113,5 +88,8 @@ function compareArrays(source, target) { | ||
return reverse ? -1 : 1; | ||
}; | ||
} | ||
// | ||
// ### function compareSets (source, target) | ||
// #### @source {Object} Source state | ||
// #### @target {Object} Target state | ||
// Returns zero if sets are equal, null if sets are completely different, | ||
@@ -158,2 +136,9 @@ // positive value if source has all props/values of target, | ||
// | ||
// ### function clone (object, stringify) | ||
// #### @object {Object} Source object | ||
// #### @stringify {bool} Stringify values flag | ||
// Creates new Object instance and copies all properties from `object` to it | ||
// (Stringifies all properties' values if flag was set to true) | ||
// | ||
utils.clone = function clone(object, stringify) { | ||
@@ -170,2 +155,9 @@ var result = {}; | ||
// | ||
// ### function cloneChanged (object, key, value) | ||
// #### @object {Object} Source object | ||
// #### @key {String} Name of property to set | ||
// #### @value {String} Value of property | ||
// Clones object and sets property to `value` | ||
// | ||
utils.cloneChanged = function cloneChanged(object, key, value) { | ||
@@ -177,51 +169,78 @@ var result = utils.clone(object); | ||
return result; | ||
} | ||
function Merger() { | ||
this.cache = {}; | ||
this.idCounter = 1; | ||
}; | ||
utils.Merger = Merger; | ||
Merger.prototype.merge = function merge(obj) { | ||
var self = this, | ||
hash, | ||
size; | ||
// | ||
// ### function reduceTree (tree, fn, acc) | ||
// #### @tree {Array} AST | ||
// #### @fn {Function} Iterator function | ||
// #### @acc {any} Initial value | ||
// Traverses AST and calls `fn` for each node, replacing initial value with fn's | ||
// return value | ||
// | ||
utils.reduceTree = function reduceTree(tree, fn, acc) { | ||
if (tree['switch']) { | ||
acc = fn(acc, tree); | ||
if(obj.switch) { | ||
hash = ['switch ', JSON.stringify(obj.switch), '{']; | ||
size = 0; | ||
acc = tree.cases.reduce(function(acc, c) { | ||
return reduceTree(c[1], fn, acc); | ||
}, acc); | ||
acc = reduceTree(tree['default'], fn, acc); | ||
} else { | ||
acc = fn(acc, tree); | ||
} | ||
obj.cases.forEach(function (c){ | ||
c[1] = self.merge(c[1]); | ||
size += c[1].size; | ||
hash.push(c[1].hash, ' '); | ||
}); | ||
obj.default = self.merge(obj.default); | ||
size += obj.default.size; | ||
return acc; | ||
} | ||
hash.push(obj.default.hash, '}'); | ||
hash = utils.sha1(hash.join('')); | ||
} else { | ||
var json = JSON.stringify(obj.stmt); | ||
hash = obj.hash || utils.sha1(json); | ||
// | ||
// ### function Identifier () | ||
// Object identifier, can be used for enumerating set of objects | ||
// | ||
function Identifier () { | ||
this.counter = 0; | ||
this.cache = {}; | ||
} | ||
utils.Identifier = Identifier; | ||
// XXX Strange Euristics | ||
size = json.length / 3200; | ||
// | ||
// ### function identify (obj) | ||
// #### @obj {any} | ||
// Returns numeric identificator of passed object | ||
// | ||
Identifier.prototype.identify = function identify(o) { | ||
var cache = this.cache, | ||
key = JSON.stringify(o); | ||
if(cache[key] === undefined) { | ||
cache[key] = ++this.counter; | ||
} | ||
obj.size = size; | ||
return cache[key]; | ||
}; | ||
if(this.cache[hash] !== undefined) { | ||
obj.id = this.cache[hash].id; | ||
} else { | ||
this.cache[obj.hash = hash] = obj; | ||
obj.id = this.idCounter++; | ||
} | ||
// | ||
// ### function generate () | ||
// Bumps and returns unique counter | ||
// | ||
Identifier.prototype.generate = function() { | ||
return ++this.counter; | ||
}; | ||
if (obj.tag) obj.id = obj.tag; | ||
// | ||
// ### function stringify (v) | ||
// #### @v {any} | ||
// Converts value to string (via JSON.stringify) if it's not already string | ||
// (or undefined) | ||
// | ||
utils.stringify = function stringify(v) { | ||
if (v === undefined) return 'undefined'; | ||
return this.cache[hash]; | ||
return typeof v !== 'string' ? JSON.stringify(v) : v; | ||
}; | ||
// | ||
// ### function errorHandler (source) | ||
// #### @source {String} source code | ||
// Internal function. Should be moved to ometajs | ||
// | ||
utils.errorHandler = function errorHandler(source) { | ||
@@ -228,0 +247,0 @@ return function(m, i) { |
{ | ||
"name": "xjst", | ||
"description": "XSLT inspired JavaScript templates (with spices)", | ||
"version": "0.2.1", | ||
"version": "0.2.3", | ||
"homepage": "http://github.com/veged/xjst", | ||
@@ -23,3 +23,3 @@ "author": "Sergey Berezhnoy <veged@mail.ru> (http://github.com/veged)", | ||
"coa": "0.2.x", | ||
"ometajs": "2.1.x", | ||
"ometajs": "~ 2.1.2", | ||
"uglify-js": "1.1.x" | ||
@@ -30,6 +30,8 @@ }, | ||
"cliff": "0.1.x", | ||
"docco": "0.3.x", | ||
"microtime": "0.1.x", | ||
"nodeunit": "0.5.x", | ||
"q": "0.7.x", | ||
"microtime": "0.1.x", | ||
"watch": "0.3.x", | ||
"nodeunit": "0.5.x" | ||
"winston": "0.5.x" | ||
}, | ||
@@ -36,0 +38,0 @@ "engines": { |
@@ -8,4 +8,4 @@ ___ ___ _____ _______ _______ | ||
XJST is a performance oriented template engine implemented for [node.js](1). | ||
It's partially inspired by XSLT and built on [ometajs](2). | ||
XJST is a performance oriented template engine implemented for [node.js][1]. | ||
It's partially inspired by the XSLT and built on top of the [ometajs][2]. | ||
@@ -30,3 +30,3 @@ ## Installation | ||
XJST extends javascript syntax with following keywords: `template`, `local`, | ||
XJST extends javascript syntax with a following keywords: `template`, `local`, | ||
`apply`. | ||
@@ -43,4 +43,4 @@ | ||
Multiple `template` statements will be grouped to construct optimal conditions | ||
graph. Order of `template` statements matters, priority decreases from bottom to | ||
top. | ||
graph. Order of the `template` statements matters, the priority decreases from | ||
the bottom to the top. | ||
@@ -56,7 +56,7 @@ ### Local | ||
`local` allow you to make temporary changes to visible variable scope. Every | ||
assignment put inside parens will be reverted immediately after expression | ||
`local` allows you to make temporary changes to a visible variables scope. Every | ||
assignment put inside parens will be reverted immediately after the expression | ||
execution. | ||
You can make multiple assignments: | ||
You can make multiple assignments in the one statement: | ||
@@ -67,3 +67,3 @@ ```javascript | ||
Use `local` with block: | ||
Or use `local` with a block: | ||
@@ -74,3 +74,3 @@ ```javascript | ||
Or as expression: | ||
Or as an expression: | ||
@@ -98,7 +98,7 @@ ```javascript | ||
XJST is intended to be applied recursively to the same data, while making small | ||
reversible changes to it. `apply` keyword works exactly like local (applying | ||
changes in parens and reverting them after execution), but with small | ||
distinction - `apply` statement doesn't have a body, so it's just doing some | ||
changes to date and applying template to changed data (context will be | ||
preserved). | ||
reversible changes to it. `apply` keyword works exactly like a `local` (applying | ||
changes in the parens and reverting them after the execution), but with small | ||
distinction - `apply` doesn't have a body, so it's just doing some | ||
changes to the data and applying template recursively | ||
(the context will be preserved). | ||
@@ -126,6 +126,6 @@ ## CLI interface | ||
XJST takes all `template` statements and produces a tree with comparisons in | ||
XJST takes all the `template` statements and produces a tree with comparisons in | ||
nodes and `template`'s bodies in leafs. `apply` are handled and replaced by | ||
direct calls to tree's nodes (some of comparisons can be skipped, using | ||
context's state). | ||
direct calls to the tree's nodes (some of comparisons can be skipped, using | ||
known context's state). | ||
@@ -158,14 +158,15 @@ Input: | ||
Some technical details (in Russian) can be found in [doc/tech.ru.md](https://github.com/veged/xjst/blob/master/doc/tech.ru.md). | ||
Here is the [documented source][3]. | ||
Some technical details (in Russian) can be found in [doc/tech.ru.md][4]. | ||
#### Authors | ||
[Sergey Berezhnoy](https://github.com/veged), | ||
[Andrey Mischenko](https://github.com/druxa), | ||
[Fedor Indutny](https://github.com/indutny). | ||
* [Sergey Berezhnoy](https://github.com/veged), | ||
* [Andrey Mischenko](https://github.com/druxa), | ||
* [Fedor Indutny](https://github.com/indutny). | ||
#### Links | ||
[1] http://nodejs.org/ | ||
[2] https://github.com/veged/ometa-js | ||
[1]: http://nodejs.org/ | ||
[2]: https://github.com/veged/ometa-js | ||
[3]: http://veged.github.com/xjst/ | ||
[4]: https://github.com/veged/xjst/blob/master/doc/tech.ru.md |
var common = exports, | ||
assert = require('assert'), | ||
path = require('path'), | ||
@@ -6,7 +7,35 @@ fs = require('fs'), | ||
common.render = function(name) { | ||
common.render = function(name, options) { | ||
options || (options = {}); | ||
var filename = path.resolve(__dirname + '/../templates/' + name), | ||
template = fs.readFileSync(filename + '.xjst').toString(); | ||
return xjst.compile(template, name); | ||
var sg = xjst.compile(template, name, { | ||
engine: 'sort-group', | ||
merge: options.merge | ||
}), | ||
fg = xjst.compile(template, name, { | ||
engine: 'fullgen', | ||
merge: options.merge | ||
}); | ||
var apply = fg.apply; | ||
fg.apply = { | ||
call: function(context) { | ||
fg.apply = apply; | ||
var results = [ | ||
sg.apply.call(context), | ||
fg.apply.call(context) | ||
]; | ||
assert.deepEqual(results[0], results[1]); | ||
return results[0]; | ||
} | ||
}; | ||
return fg; | ||
}; |
@@ -25,6 +25,7 @@ var common = require('../fixtures/common'), | ||
var expected = fs.readFileSync(__dirname + '/../templates/bem-bl.html') | ||
.toString(); | ||
.toString(), | ||
rendered = common.render('bem-bl').apply.call(c); | ||
assert.equal(common.render('bem-bl').apply.call(c) + '\n', expected); | ||
assert.equal(rendered + '\n', expected); | ||
test.done(); | ||
}; |
@@ -5,4 +5,4 @@ var common = require('../fixtures/common'), | ||
exports['extend'] = function(test) { | ||
var a = common.render('extend-a'), | ||
b = common.render('extend-b'); | ||
var a = common.render('extend-a', { merge: true }), | ||
b = common.render('extend-b', { merge: true }); | ||
@@ -9,0 +9,0 @@ a.mergeWith(b); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
55
164
367451
8
2403
7
Updatedometajs@~ 2.1.2