UNPKG

bem-xjst

Version:

Declarative Template Engine for the browser and server

268 lines (212 loc) 6.3 kB
var tree = require('./tree'); var PropertyMatch = tree.PropertyMatch; var AddMatch = tree.AddMatch; var WrapMatch = tree.WrapMatch; var ExtendMatch = tree.ExtendMatch; var CustomMatch = tree.CustomMatch; function MatchNested(template, pred) { this.template = template; this.keys = pred.key; this.value = pred.value; } MatchNested.prototype.exec = function(context) { var val = context; var keys = this.keys; for (var i = 0; i < keys.length - 1; i++) { val = val[keys[i]]; if (!val) return false; } val = val[keys[i]]; if (this.value === true) return val !== undefined && val !== '' && val !== false && val !== null; return String(val) === this.value; }; function MatchCustom(template, pred) { this.template = template; this.body = pred.body; } MatchCustom.prototype.exec = function(context) { return this.body.call(context, context, context.ctx); }; function MatchWrap(template) { this.template = template; this.wrap = null; } MatchWrap.prototype.exec = function(context) { var res = this.wrap !== context.ctx; this.wrap = context.ctx; return res; }; function MatchExtend(template) { this.template = template; this.wrap = null; } MatchExtend.prototype.exec = function(context) { var res = this.ext !== context.ctx; this.ext = context.ctx; return res; }; function AddWrap(template, pred) { this.template = template; this.key = pred.key; this.value = pred.value; } AddWrap.prototype.exec = function(context) { return context[this.key] === this.value; }; function MatchTemplate(mode, template) { this.mode = mode; this.predicates = new Array(template.predicates.length); this.body = template.body; var postpone = []; for (var i = 0, j = 0; i < this.predicates.length; i++, j++) { var pred = template.predicates[i]; if (pred instanceof PropertyMatch) { this.predicates[j] = new MatchNested(this, pred); } else if (pred instanceof ExtendMatch) { j--; postpone.push(new MatchExtend(this)); } else if (pred instanceof AddMatch) { this.predicates[j] = new AddWrap(this, pred); } else if (pred instanceof CustomMatch) { this.predicates[j] = new MatchCustom(this, pred); // Push MatchWrap later, they should not be executed first. // Otherwise they will set flag too early, and body might not be executed } else if (pred instanceof WrapMatch) { j--; postpone.push(new MatchWrap(this)); } else { // Skip j--; } } // Insert late predicates for (var i = 0; i < postpone.length; i++, j++) this.predicates[j] = postpone[i]; if (this.predicates.length !== j) this.predicates.length = j; } exports.MatchTemplate = MatchTemplate; function Match(entity, modeName) { this.entity = entity; this.modeName = modeName; this.bemxjst = this.entity.bemxjst; this.templates = []; // applyNext mask this.mask = [ 0 ]; // We are going to create copies of mask for nested `applyNext()` this.maskSize = 0; this.maskOffset = 0; this.count = 0; this.depth = -1; this.thrownError = null; } exports.Match = Match; Match.prototype.prepend = function(other) { this.templates = other.templates.concat(this.templates); this.count += other.count; while (Math.ceil(this.count / 31) > this.mask.length) this.mask.push(0); this.maskSize = this.mask.length; }; Match.prototype.push = function(template) { this.templates.push(new MatchTemplate(this, template)); this.count++; if (Math.ceil(this.count / 31) > this.mask.length) this.mask.push(0); this.maskSize = this.mask.length; }; Match.prototype.tryCatch = function(fn, ctx) { try { return fn.call(ctx, ctx, ctx.ctx); } catch (e) { this.thrownError = e; if (this.modeName) { this.thrownError.ctx = ctx; this.thrownError.name = 'BEMXJST ERROR'; var classBuilder = this.entity.bemxjst.classBuilder; var cause = e.stack.split('\n')[1]; this.thrownError.message = 'Template error in mode ' + this.modeName + ' in block ' + classBuilder.build(ctx.ctx.block, ctx.ctx.elem) + '\n ' + e.message + '\n'; this.thrownError.stack = this.thrownError.name + ': ' + this.thrownError.message + ' ' + cause + '\n' + e.stack; } } }; Match.prototype.exec = function(context) { var save = this.checkDepth(); var template; var bitIndex = this.maskOffset; var mask = this.mask[bitIndex]; var bit = 1; for (var i = 0; i < this.count; i++) { if ((mask & bit) === 0) { template = this.templates[i]; for (var j = 0; j < template.predicates.length; j++) { var pred = template.predicates[j]; /* jshint maxdepth : false */ if (!pred.exec(context)) break; } // All predicates matched! if (j === template.predicates.length) break; } if (bit === 0x40000000) { bitIndex++; mask = this.mask[bitIndex]; bit = 1; } else { bit <<= 1; } } if (i === this.count) { this.restoreDepth(save); if (this.modeName === 'mods') return context.mods; if (this.modeName === 'elemMods') return context.elemMods; return context.ctx[this.modeName]; } var oldMask = mask; var oldMatch = this.bemxjst.match; this.mask[bitIndex] |= bit; this.bemxjst.match = this; this.thrownError = null; var out; if (typeof template.body === 'function') out = this.tryCatch(template.body, context); else out = template.body; this.mask[bitIndex] = oldMask; this.bemxjst.match = oldMatch; this.restoreDepth(save); var e = this.thrownError; if (e !== null) { this.thrownError = null; throw e; } return out; }; Match.prototype.checkDepth = function() { if (this.depth === -1) { this.depth = this.bemxjst.depth; return -1; } if (this.bemxjst.depth === this.depth) return this.depth; var depth = this.depth; this.depth = this.bemxjst.depth; this.maskOffset += this.maskSize; while (this.mask.length < this.maskOffset + this.maskSize) this.mask.push(0); return depth; }; Match.prototype.restoreDepth = function(depth) { if (depth !== -1 && depth !== this.depth) this.maskOffset -= this.maskSize; this.depth = depth; };