Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

xjst

Package Overview
Dependencies
Maintainers
2
Versions
162
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

xjst - npm Package Compare versions

Comparing version 0.2.1 to 0.2.3

docs/cli.html

11

benchmarks/suite.js

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc