Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

lib: fix cloning of ast nodes (support regexps)

Introduce maps.
  • Loading branch information...
commit 63a39e9e69635e05fb153619140928eaefb5e091 1 parent cc95119
@indutny indutny authored
View
1  .gitignore
@@ -1,6 +1,7 @@
npm-debug.log
node_modules/
docs/
+benchmarks/templates/internal-*
v8.log
*-private.*
TODO
View
65 lib/xjst/compiler/body.js
@@ -1,6 +1,7 @@
var assert = require('assert');
var util = require('util');
var estraverse = require('estraverse');
+var xjst = require('../../xjst');
var Predicate = require('./predicate').Predicate;
@@ -23,38 +24,11 @@ GenericBody.prototype.render = function render(notShared) {
this.shared = this.compiler.registerBody(this);
if (!notShared && this.shared) {
- var res = { type: 'Identifier', name: '__$r' };
-
- // var __$r = block();
- // if (__$r !== __$ref) return __$r;
- return {
- apply: [{
- type: 'VariableDeclaration',
- kind: 'var',
- declarations: [{
- type: 'VariableDeclarator',
- id: res,
- init: {
- type: 'CallExpression',
- callee: this.compiler.getBodyName(this),
- arguments: [ this.compiler.ctx ]
- }
- }]
- }, {
- type: 'IfStatement',
- test: {
- type: 'BinaryExpression',
- operator: '!==',
- left: res,
- right: this.compiler.ref
- },
- consequent: {
- type: 'ReturnStatement',
- argument: res
- },
- alternate: null
- }]
- };
+ return this.compiler.checkRef({
+ type: 'CallExpression',
+ callee: this.compiler.getBodyName(this),
+ arguments: [ this.compiler.ctx ]
+ });
}
return this._render();
@@ -69,7 +43,7 @@ function Body(compiler, body, id, rolledOut) {
this.id = parent.id;
this.rolledOut = parent.rolledOut;
- this.body = JSON.parse(JSON.stringify(parent.body));
+ this.body = xjst.utils.cloneAst(parent.body);
this.applyNext = parent.applyNext;
this.applyFlag = parent.applyFlag;
@@ -227,16 +201,18 @@ Body.prototype.rollOutApply = function rollOutApply(type, changes) {
});
});
+ var noopt = {
+ type: 'CallExpression',
+ callee: {
+ type: 'Identifier',
+ name: 'applyc'
+ },
+ arguments: [ ctx ]
+ };
+
// No optimization - generate just `applyc()` call`
if (templates.length === this.compiler.program.templates.length) {
- return {
- type: 'CallExpression',
- callee: {
- type: 'Identifier',
- name: 'applyc'
- },
- arguments: [ ctx ]
- };
+ return noopt;
}
// Clone templates
@@ -253,6 +229,13 @@ Body.prototype.rollOutApply = function rollOutApply(type, changes) {
});
});
+ // Do not inline templates if result will be too big
+ var size = 0;
+ templates.forEach(function(template) {
+ size += template.getSize();
+ });
+ if (size > this.compiler.inlineableApplySize) return noopt;
+
var body = this.compiler._translate2({
templates: templates,
other: [],
View
3  lib/xjst/compiler/group.js
@@ -2,8 +2,11 @@ var assert = require('assert');
var util = require('util');
var GenericBody = require('./body').GenericBody;
+var Map = require('./map').Map;
function Group(compiler, pairs) {
+ if (pairs.length > 32) return new Map(compiler, pairs);
+
GenericBody.call(this, compiler);
assert(pairs.length > 0);
View
119 lib/xjst/compiler/index.js
@@ -19,13 +19,17 @@ function Compiler(options) {
this.scores = {};
this.idCount = 0;
this.bodyId = 1;
+ this.mapId = 1;
this.applyFlags = 0;
this.jailIndex = 0;
+ this.inlineableSize = 3000;
+ this.inlineableApplySize = 40000;
this.ctx = { type: 'Identifier', name: '__$ctx' };
this.ref = { type: 'Identifier', name: '__$ref' };
this.renderStack = [];
this.renderHistory = [];
this.sharedBodies = {};
+ this.maps = {};
this.program = null;
};
exports.Compiler = Compiler;
@@ -97,7 +101,12 @@ Compiler.prototype.translate = function translate(ast) {
var result = this._translate2(program, false);
- // 3. Add shared bodies to result
+ // 3. Add maps to result
+ // NOTE: this could possibly generate more shared bodies,
+ // so order is important here.
+ this.addMaps(result);
+
+ // 4. Add shared bodies to result
this.addBodies(result);
return result;
@@ -338,15 +347,21 @@ Compiler.prototype.revertChange = function revertChange() {
Compiler.prototype.registerBody = function registerBody(body) {
if (body.shareable &&
- (this.sharedBodies.hasOwnProperty(body.id) || body.getSize() > 6000)) {
- body.id = body.id === null ? this.bodyId++ : body.id;
- body.size = 0;
- this.sharedBodies[body.id] = body;
+ (this.sharedBodies.hasOwnProperty(body.id) ||
+ body.getSize() > this.inlineableSize)) {
+ this.shareBody(body);
return true;
}
return false;
};
+Compiler.prototype.shareBody = function shareBody(body) {
+ assert(body.shareable);
+ body.id = body.id === null ? this.bodyId++ : body.id;
+ body.size = 0;
+ this.sharedBodies[body.id] = body;
+};
+
Compiler.prototype.addBodies = function addBodies(result) {
var changed = true,
visited = {};
@@ -390,12 +405,80 @@ Compiler.prototype.addBodies = function addBodies(result) {
}
};
+Compiler.prototype.registerMap = function registerMap(map) {
+ if (map.id) return;
+ map.id = this.mapId++;
+ this.maps[map.id] = map;
+};
+
+Compiler.prototype.addMaps = function addMaps(result) {
+ Object.keys(this.maps).forEach(function(id) {
+ var map = this.maps[id];
+ result.body.push(map.getMap());
+ }, this);
+};
+
Compiler.prototype.getBodyName = function getBodyName(body) {
assert(body.shared);
assert(this.sharedBodies.hasOwnProperty(body.id));
return { type: 'Identifier', name: '__$b' + body.id };
};
+Compiler.prototype.getMapName = function getMapName(map) {
+ assert(this.maps.hasOwnProperty(map.id));
+ return { type: 'Identifier', name: '__$m' + map.id };
+};
+
+Compiler.prototype.renderArray = function renderArray(bodies) {
+ var size = 0,
+ out = { apply: [], other: [], init: [] };
+
+ bodies.forEach(function(body) {
+ size += body.getSize();
+ if (body.shareable && size >= this.inlineableSize) {
+ this.shareBody(body);
+ }
+
+ var ast = body.render();
+ if (ast.apply) out.apply = out.apply.concat(ast.apply);
+ if (ast.other) out.other = ast.other.concat(out.other);
+ if (ast.init) out.init = out.init.concat(ast.init);
+ }, this);
+
+ return out;
+};
+
+Compiler.prototype.checkRef = function checkRef(expr) {
+ var res = { type: 'Identifier', name: '__$r' };
+
+ // var __$r = expr
+ // if (__$r !== __$ref) return __$r;
+ return {
+ apply: [{
+ type: 'VariableDeclaration',
+ kind: 'var',
+ declarations: [{
+ type: 'VariableDeclarator',
+ id: res,
+ init: expr
+ }]
+ }, {
+ type: 'IfStatement',
+ test: {
+ type: 'BinaryExpression',
+ operator: '!==',
+ left: res,
+ right: this.ref
+ },
+ consequent: {
+ type: 'ReturnStatement',
+ argument: res
+ },
+ alternate: null
+ }]
+ };
+};
+
// Transform AST templates into readable form
Compiler.prototype.transformTemplates = function transformTemplates(template) {
var expr = template.expression,
@@ -632,24 +715,22 @@ Compiler.prototype.render = function render(program, bodyOnly) {
});
// Render each template
- program.templates.forEach(function(template) {
- var ast = template.render(true);
+ var out = this.renderArray(program.templates);
- /// Apply to the bottom
- if (ast.apply) applyBody = applyBody.concat(ast.apply);
+ /// Apply to the bottom
+ if (out.apply) applyBody = applyBody.concat(out.apply);
- // Other to the top
- if (ast.other) applyBody = ast.other.concat(applyBody);
+ // Other to the top
+ if (out.other) applyBody = out.other.concat(applyBody);
- // Initializers to the initializers array
- if (ast.init) {
- if (Array.isArray(ast.init)) {
- initializers.push.apply(initializers, ast.init);
- } else {
- initializers.push(ast.init);
- }
+ // Initializers to the initializers array
+ if (out.init) {
+ if (Array.isArray(out.init)) {
+ initializers.push.apply(initializers, out.init);
+ } else {
+ initializers.push(out.init);
}
- }, this);
+ }
if (bodyOnly) return applyBody;
View
107 lib/xjst/compiler/map.js
@@ -0,0 +1,107 @@
+var assert = require('assert');
+var util = require('util');
+
+var GenericBody = require('./body').GenericBody;
+
+function Map(compiler, pairs) {
+ GenericBody.call(this, compiler);
+ assert(pairs.length > 0);
+
+ this.shareable = false;
+ this.pairs = {};
+ this.predicate = pairs ? pairs[0].predicate.expr : null;
+
+ if (pairs) {
+ pairs.forEach(function(pair) {
+ pair.bodies.forEach(function(body) {
+ this.add(pair.predicate.expr, pair.predicate.value, body);
+ }, this);
+ }, this);
+ }
+
+ this.compiler.registerMap(this);
+};
+util.inherits(Map, GenericBody);
+exports.Map = Map;
+
+Map.prototype.getSize = function getSize() {
+ return 0;
+};
+
+Map.prototype.add = function add(predicate, value, body) {
+ assert(value.type === 'Literal' && typeof value.value === 'string');
+ if (this.predicate === null) this.predicate = predicate;
+
+ var valueId = this.compiler.getId(value);
+ if (!this.pairs[valueId]) {
+ this.pairs[valueId] = {
+ value: value,
+ bodies: [body]
+ };
+ } else {
+ this.pairs[valueId].bodies.push(body);
+ }
+};
+
+Map.prototype.getMap = function getMap() {
+ return {
+ type: 'VariableDeclaration',
+ kind: 'var',
+ declarations: [{
+ type: 'VariableDeclarator',
+ id: this.compiler.getMapName(this),
+ init: {
+ type: 'ObjectExpression',
+ properties: Object.keys(this.pairs).map(function(id) {
+ var pair = this.pairs[id];
+ var out = [];
+ pair.bodies.forEach(function(body) {
+ out = out.concat(body.render().apply);
+ });
+ out = out.concat({
+ type: 'ReturnStatement',
+ argument: this.compiler.ref
+ });
+ return {
+ type: 'Property',
+ key: pair.value,
+ value: {
+ type: 'FunctionExpression',
+ id: null,
+ params: [ this.compiler.ctx ],
+ defaults: [],
+ rest: null,
+ generator: false,
+ expression: false,
+ body: {
+ type: 'BlockStatement',
+ body: out
+ }
+ },
+ kind: 'init'
+ }
+ }, this)
+ }
+ }]
+ };
+};
+
+Map.prototype.render = function render() {
+ assert(this.predicate !== null);
+ var lookup = {
+ type: 'MemberExpression',
+ computed: true,
+ object: this.compiler.getMapName(this),
+ property: this.predicate
+ };
+ return this.compiler.checkRef({
+ type: 'ConditionalExpression',
+ test: lookup,
+ consequent: {
+ type: 'CallExpression',
+ callee: lookup,
+ arguments: [this.compiler.ctx]
+ },
+ alternate: this.compiler.ref
+ });
+};
View
6 lib/xjst/compiler/predicate.js
@@ -1,3 +1,5 @@
+var xjst = require('../../xjst');
+
// Predicate constructor
function Predicate(compiler, expr, value) {
this.compiler = compiler;
@@ -14,8 +16,8 @@ exports.Predicate = Predicate;
Predicate.prototype.clone = function clone() {
return new Predicate(this.compiler,
- JSON.parse(JSON.stringify(this.expr)),
- JSON.parse(JSON.stringify(this.value)));
+ xjst.utils.cloneAst(this.expr),
+ xjst.utils.cloneAst(this.value));
};
Predicate.prototype.getScore = function getScore() {
View
16 lib/xjst/utils.js
@@ -105,3 +105,19 @@ utils.run = function run(templates, context, ignore) {
}
return last.body();
};
+
+utils.cloneAst = function cloneAst(ast) {
+ if (ast === null || ast === undefined ||
+ typeof ast === 'number' || typeof ast === 'string' ||
+ typeof ast === 'boolean' || ast instanceof RegExp) {
+ return ast;
+ }
+ if (Array.isArray(ast)) return ast.map(cloneAst);
+
+ var res = {};
+ Object.keys(ast).forEach(function(key) {
+ res[key] = cloneAst(ast[key]);
+ })
+
+ return res;
+};
Please sign in to comment.
Something went wrong with that request. Please try again.