From 2b741deb18692fea109729aeb0ca38dd907b5251 Mon Sep 17 00:00:00 2001 From: Vitaly Puzrin Date: Thu, 30 Oct 2014 06:59:14 +0300 Subject: [PATCH] Isolated rules management & rules cache build --- lib/linkifier.js | 11 +---- lib/parser_block.js | 19 ++------ lib/parser_inline.js | 18 +++---- lib/ruler.js | 88 ++++++++++++++++++++++------------- lib/rules_block/blockquote.js | 5 +- lib/rules_block/list.js | 3 +- lib/rules_block/paragraph.js | 28 ++++++----- lib/typographer.js | 11 +---- 8 files changed, 92 insertions(+), 91 deletions(-) diff --git a/lib/linkifier.js b/lib/linkifier.js index 310b0bd..e28c196 100644 --- a/lib/linkifier.js +++ b/lib/linkifier.js @@ -13,11 +13,9 @@ var _rules = [ function Linkifier() { - this._rules = []; - this.options = {}; - this.ruler = new Ruler(this.rulesUpdate.bind(this)); + this.ruler = new Ruler(); for (var i = 0; i < _rules.length; i++) { this.ruler.push(_rules[i][0], _rules[i][1]); @@ -25,11 +23,6 @@ function Linkifier() { } -Linkifier.prototype.rulesUpdate = function () { - this._rules = this.ruler.getRules(); -}; - - Linkifier.prototype.set = function (options) { assign(this.options, options); }; @@ -38,7 +31,7 @@ Linkifier.prototype.set = function (options) { Linkifier.prototype.process = function (state) { var i, l, rules; - rules = this._rules; + rules = this.ruler.getRules(''); for (i = 0, l = rules.length; i < l; i++) { rules[i](this, state); diff --git a/lib/parser_block.js b/lib/parser_block.js index 3297c20..923b358 100644 --- a/lib/parser_block.js +++ b/lib/parser_block.js @@ -25,12 +25,7 @@ var _rules = [ // Block Parser class // function ParserBlock() { - this._rules = []; - this._rulesParagraphTerm = []; - this._rulesBlockquoteTerm = []; - this._rulesListTerm = []; - - this.ruler = new Ruler(this.rulesUpdate.bind(this)); + this.ruler = new Ruler(); for (var i = 0; i < _rules.length; i++) { this.ruler.push(_rules[i][0], _rules[i][1], { alt: (_rules[i][2] || []).slice() }); @@ -38,20 +33,12 @@ function ParserBlock() { } -ParserBlock.prototype.rulesUpdate = function () { - this._rules = this.ruler.getRules(); - this._rulesParagraphTerm = this.ruler.getRules('paragraph'); - this._rulesBlockquoteTerm = this.ruler.getRules('blockquote'); - this._rulesListTerm = this.ruler.getRules('list'); -}; - - // Generate tokens for input range // ParserBlock.prototype.tokenize = function (state, startLine, endLine) { var ok, i, - rules = this._rules, - len = this._rules.length, + rules = this.ruler.getRules(''), + len = rules.length, line = startLine, hasEmptyLines = false; diff --git a/lib/parser_inline.js b/lib/parser_inline.js index b7f4dc2..5a0c2f5 100644 --- a/lib/parser_inline.js +++ b/lib/parser_inline.js @@ -47,7 +47,6 @@ function validateLink(url) { // Inline Parser class // function ParserInline() { - this._rules = []; // Rule to skip pure text // - '{}$%@+=:' reserved for extentions @@ -57,7 +56,7 @@ function ParserInline() { // If you need to restrict it - override this with your validator. this.validateLink = validateLink; - this.ruler = new Ruler(this.rulesUpdate.bind(this)); + this.ruler = new Ruler(); for (var i = 0; i < _rules.length; i++) { this.ruler.push(_rules[i][0], _rules[i][1]); @@ -65,17 +64,13 @@ function ParserInline() { } -ParserInline.prototype.rulesUpdate = function () { - this._rules = this.ruler.getRules(); -}; - - // Skip single token by running all rules in validation mode; // returns `true` if any rule reported success // ParserInline.prototype.skipToken = function (state) { var i, cached_pos, pos = state.pos, - len = this._rules.length; + rules = this.ruler.getRules(''), + len = rules.length; if ((cached_pos = state.cacheGet(pos)) > 0) { state.pos = cached_pos; @@ -83,7 +78,7 @@ ParserInline.prototype.skipToken = function (state) { } for (i = 0; i < len; i++) { - if (this._rules[i](state, true)) { + if (rules[i](state, true)) { state.cacheSet(pos, state.pos); return; } @@ -98,7 +93,8 @@ ParserInline.prototype.skipToken = function (state) { // ParserInline.prototype.tokenize = function (state) { var ok, i, - len = this._rules.length, + rules = this.ruler.getRules(''), + len = rules.length, end = state.posMax; while (state.pos < end) { @@ -111,7 +107,7 @@ ParserInline.prototype.tokenize = function (state) { // - return true for (i = 0; i < len; i++) { - ok = this._rules[i](state, false); + ok = rules[i](state, false); if (ok) { break; } } diff --git a/lib/ruler.js b/lib/ruler.js index 964a90c..5862655 100644 --- a/lib/ruler.js +++ b/lib/ruler.js @@ -9,9 +9,7 @@ //////////////////////////////////////////////////////////////////////////////// -function Ruler(compileFn) { - this.compile = compileFn; // callback to call after each change - +function Ruler() { // List of added rules. Each element is: // // { @@ -22,6 +20,13 @@ function Ruler(compileFn) { // } // this.rules = []; + + // Cached rule chains. + // + // First level - chain name, '' for default. + // Second level - diginal anchor for fast filtering by charcodes. + // + this.cache = null; } @@ -47,7 +52,7 @@ Ruler.prototype.at = function (name, fn, options) { this.rules[index].fn = fn; this.rules[index].alt = opt.alt || []; - this.compile(); + this.cache = null; }; @@ -66,7 +71,7 @@ Ruler.prototype.before = function (beforeName, ruleName, fn, options) { alt: opt.alt || [] }); - this.compile(); + this.cache = null; }; @@ -85,7 +90,7 @@ Ruler.prototype.after = function (afterName, ruleName, fn, options) { alt: opt.alt || [] }); - this.compile(); + this.cache = null; }; // Add rule to the end of chain. @@ -100,30 +105,7 @@ Ruler.prototype.push = function (ruleName, fn, options) { alt: opt.alt || [] }); - this.compile(); -}; - - -// Get rules list as array of functions. By default returns main chain -// -Ruler.prototype.getRules = function (chainName) { - var result = []; - - if (!chainName) { - this.rules.forEach(function (rule) { - if (rule.enabled) { - result.push(rule.fn); - } - }); - return result; - } - - this.rules.forEach(function (rule) { - if (rule.alt.indexOf(chainName) >= 0 && rule.enabled) { - result.push(rule.fn); - } - }); - return result; + this.cache = null; }; @@ -151,7 +133,7 @@ Ruler.prototype.enable = function (list, strict) { }, this); - this.compile(); + this.cache = null; }; @@ -171,8 +153,50 @@ Ruler.prototype.disable = function (list) { }, this); - this.compile(); + this.cache = null; +}; + + +// Build rules lookup cache +// +Ruler.prototype.compile = function () { + var self = this; + var chains = [ '' ]; + + // collect unique names + self.rules.forEach(function (rule) { + if (!rule.enabled) { return; } + + rule.alt.forEach(function (altName) { + if (chains.indexOf(altName) < 0) { + chains.push(altName); + } + }); + }); + + self.cache = {}; + + chains.forEach(function (chain) { + self.cache[chain] = []; + self.rules.forEach(function (rule) { + if (!rule.enabled) { return; } + + if (chain && rule.alt.indexOf(chain) < 0) { return; } + + self.cache[chain].push(rule.fn); + }); + }); }; +// Get rules list as array of functions. +// +Ruler.prototype.getRules = function (chainName) { + if (this.cache === null) { + this.compile(); + } + + return this.cache[chainName]; +}; + module.exports = Ruler; diff --git a/lib/rules_block/blockquote.js b/lib/rules_block/blockquote.js index 5fc53e2..ca7b026 100644 --- a/lib/rules_block/blockquote.js +++ b/lib/rules_block/blockquote.js @@ -5,7 +5,8 @@ module.exports = function blockquote(state, startLine, endLine, silent) { var nextLine, lastLineEmpty, oldTShift, oldBMarks, oldIndent, oldParentType, lines, - terminatorRules = state.parser._rulesBlockquoteTerm, i, l, terminate, + terminatorRules, + i, l, terminate, pos = state.bMarks[startLine] + state.tShift[startLine], max = state.eMarks[startLine]; @@ -36,6 +37,8 @@ module.exports = function blockquote(state, startLine, endLine, silent) { oldTShift = [ state.tShift[startLine] ]; state.tShift[startLine] = pos - state.bMarks[startLine]; + terminatorRules = state.parser.ruler.getRules('blockquote'); + // Search the end of the block // // Block ends with either: diff --git a/lib/rules_block/list.js b/lib/rules_block/list.js index 7b157ae..23e4e1b 100644 --- a/lib/rules_block/list.js +++ b/lib/rules_block/list.js @@ -102,7 +102,7 @@ module.exports = function list(state, startLine, endLine, silent) { listLines, itemLines, tight = true, - terminatorRules = state.parser._rulesListTerm, + terminatorRules, i, l, terminate; // Detect list type and position after marker @@ -150,6 +150,7 @@ module.exports = function list(state, startLine, endLine, silent) { nextLine = startLine; prevEmptyEnd = false; + terminatorRules = state.parser.ruler.getRules('list'); while (nextLine < endLine) { contentStart = state.skipSpaces(posAfterMarker); diff --git a/lib/rules_block/paragraph.js b/lib/rules_block/paragraph.js index 76afd7c..2ebac81 100644 --- a/lib/rules_block/paragraph.js +++ b/lib/rules_block/paragraph.js @@ -9,25 +9,29 @@ var parseRef = require('../parser_ref'); module.exports = function paragraph(state, startLine/*, endLine*/) { var endLine, content, pos, terminate, i, l, nextLine = startLine + 1, - terminatorRules = state.parser._rulesParagraphTerm; + terminatorRules; endLine = state.lineMax; // jump line-by-line until empty one or EOF - for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { - // this would be a code block normally, but after paragraph - // it's considered a lazy continuation regardless of what's there - if (state.tShift[nextLine] - state.blkIndent > 3) { continue; } + if (nextLine < endLine && !state.isEmpty(nextLine)) { + terminatorRules = state.parser.ruler.getRules('paragraph'); - // Some tags can terminate paragraph without empty line. - terminate = false; - for (i = 0, l = terminatorRules.length; i < l; i++) { - if (terminatorRules[i](state, nextLine, endLine, true)) { - terminate = true; - break; + for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { + // this would be a code block normally, but after paragraph + // it's considered a lazy continuation regardless of what's there + if (state.tShift[nextLine] - state.blkIndent > 3) { continue; } + + // Some tags can terminate paragraph without empty line. + terminate = false; + for (i = 0, l = terminatorRules.length; i < l; i++) { + if (terminatorRules[i](state, nextLine, endLine, true)) { + terminate = true; + break; + } } + if (terminate) { break; } } - if (terminate) { break; } } content = state.getLines(startLine, nextLine, state.blkIndent, false).trim(); diff --git a/lib/typographer.js b/lib/typographer.js index 72da807..e2107c9 100644 --- a/lib/typographer.js +++ b/lib/typographer.js @@ -18,11 +18,9 @@ var _rules = [ function Typographer() { - this._rules = []; - this.options = {}; - this.ruler = new Ruler(this.rulesUpdate.bind(this)); + this.ruler = new Ruler(); for (var i = 0; i < _rules.length; i++) { this.ruler.push(_rules[i][0], _rules[i][1]); @@ -30,11 +28,6 @@ function Typographer() { } -Typographer.prototype.rulesUpdate = function () { - this._rules = this.ruler.getRules(); -}; - - Typographer.prototype.set = function (options) { assign(this.options, options); }; @@ -43,7 +36,7 @@ Typographer.prototype.set = function (options) { Typographer.prototype.process = function (state) { var i, l, rules; - rules = this._rules; + rules = this.ruler.getRules(''); for (i = 0, l = rules.length; i < l; i++) { rules[i](this, state);