Browse Source

Moved rules manager to separate class (block parser only)

pull/14/head
Vitaly Puzrin 10 years ago
parent
commit
7390503066
  1. 2
      benchmark/profile.js
  2. 115
      lib/parser_block.js
  3. 160
      lib/ruler.js
  4. 22
      lib/rules_block/blockquote.js
  5. 13
      lib/rules_block/list.js
  6. 23
      lib/rules_block/paragraph.js

2
benchmark/profile.js

@ -6,7 +6,7 @@ var Remarkable = require('../');
var md = new Remarkable(); var md = new Remarkable();
var data = fs.readFileSync(path.join(__dirname, '/samples/lorem1.txt', 'utf8')); var data = fs.readFileSync(path.join(__dirname, '/samples/lorem1.txt'), 'utf8');
for (var i = 0; i < 20000; i++) { for (var i = 0; i < 20000; i++) {
md.render(data); md.render(data);

115
lib/parser_block.js

@ -4,7 +4,9 @@
'use strict'; 'use strict';
var Ruler = require('./ruler');
var State = require('./rules_block/state_block'); var State = require('./rules_block/state_block');
var skipEmptyLines = require('./helpers').skipEmptyLines; var skipEmptyLines = require('./helpers').skipEmptyLines;
var isEmpty = require('./helpers').isEmpty; var isEmpty = require('./helpers').isEmpty;
@ -12,102 +14,39 @@ var isEmpty = require('./helpers').isEmpty;
var rules = []; var rules = [];
// `list` should be after `hr`, but before `heading` // `list` should be after `hr`, but before `heading`
rules.push(require('./rules_block/code')); rules.push([ require('./rules_block/code') ]);
rules.push(require('./rules_block/fences')); rules.push([ require('./rules_block/fences'), 'paragraph', 'blockquote', 'list' ]);
rules.push(require('./rules_block/blockquote')); rules.push([ require('./rules_block/blockquote'), 'paragraph', 'blockquote', 'list' ]);
rules.push(require('./rules_block/hr')); rules.push([ require('./rules_block/hr'), 'paragraph', 'blockquote', 'list' ]);
rules.push(require('./rules_block/list')); rules.push([ require('./rules_block/list'), 'paragraph', 'blockquote' ]);
rules.push(require('./rules_block/heading')); rules.push([ require('./rules_block/heading'), 'paragraph', 'blockquote' ]);
rules.push(require('./rules_block/lheading')); rules.push([ require('./rules_block/lheading') ]);
rules.push(require('./rules_block/htmlblock')); rules.push([ require('./rules_block/htmlblock'), 'paragraph', 'blockquote' ]);
rules.push(require('./rules_block/table')); rules.push([ require('./rules_block/table'), 'paragraph' ]);
rules.push(require('./rules_block/paragraph')); rules.push([ require('./rules_block/paragraph') ]);
function functionName(fn) {
var ret = fn.toString();
ret = ret.substr('function '.length);
ret = ret.substr(0, ret.indexOf('('));
return ret;
}
function findByName(self, name) {
for (var i = 0; i < self.rules.length; i++) {
if (functionName(self.rules[i]) === name) {
return i;
}
}
return -1;
}
// Block Parser class // Block Parser class
// //
function ParserBlock() { function ParserBlock() {
this.rules = []; this._rules = [];
this.rules_named = {}; this._rulesParagraphTerm = [];
this._rulesBlockquoteTerm = [];
for (var i = 0; i < rules.length; i++) { this._rulesListTerm = [];
this.after(null, rules[i]);
}
}
// Replace/delete parser function
//
ParserBlock.prototype.at = function (name, fn) {
var index = findByName(name);
if (index === -1) {
throw new Error('Parser rule not found: ' + name);
}
if (fn) {
this.rules[index] = fn;
} else {
this.rules = this.rules.slice(0, index).concat(this.rules.slice(index + 1));
}
this.rules_named[functionName(fn)] = fn;
};
this.ruler = new Ruler(this.rulesUpdate.bind(this));
// Add function to parser chain before one with given name. for (var i = 0; i < rules.length; i++) {
// Or add to start, if name not defined this.ruler.after(rules[i][0], rules[i].slice(1));
//
ParserBlock.prototype.before = function (name, fn) {
if (!name) {
this.rules.unshift(fn);
this.rules_named[functionName(fn)] = fn;
return;
} }
var index = findByName(name);
if (index === -1) {
throw new Error('Parser rule not found: ' + name);
} }
this.rules.splice(index, 0, fn);
this.rules_named[functionName(fn)] = fn;
};
// Add function to parser chain after one with given name. ParserBlock.prototype.rulesUpdate = function () {
// Or add to end, if name not defined this._rules = this.ruler.getRules();
// this._rulesParagraphTerm = this.ruler.getRules('paragraph');
ParserBlock.prototype.after = function (name, fn) { this._rulesBlockquoteTerm = this.ruler.getRules('blockquote');
if (!name) { this._rulesListTerm = this.ruler.getRules('list');
this.rules.push(fn);
this.rules_named[functionName(fn)] = fn;
return;
}
var index = findByName(name);
if (index === -1) {
throw new Error('Parser rule not found: ' + name);
}
this.rules.splice(index + 1, 0, fn);
this.rules_named[functionName(fn)] = fn;
}; };
@ -115,8 +54,8 @@ ParserBlock.prototype.after = function (name, fn) {
// //
ParserBlock.prototype.tokenize = function (state, startLine, endLine) { ParserBlock.prototype.tokenize = function (state, startLine, endLine) {
var ok, i, var ok, i,
rules = this.rules, rules = this._rules,
len = this.rules.length, len = this._rules.length,
line = startLine, line = startLine,
hasEmptyLines = false; hasEmptyLines = false;
@ -201,7 +140,6 @@ ParserBlock.prototype.parse = function (src, options, env) {
}); });
} }
state = new State( state = new State(
src, src,
this, this,
@ -213,7 +151,6 @@ ParserBlock.prototype.parse = function (src, options, env) {
this.tokenize(state, state.line, state.lineMax); this.tokenize(state, state.line, state.lineMax);
return state.tokens; return state.tokens;
}; };

160
lib/ruler.js

@ -0,0 +1,160 @@
// Ruler is helper class to build responsibility chains from parse rules.
// It allows:
//
// - easy stack rules chains
// - getting main chain and named chains content (as arrays of functions)
'use strict';
////////////////////////////////////////////////////////////////////////////////
// helpers
function _class(obj) { return Object.prototype.toString.call(obj); }
function isFunction(obj) { return _class(obj) === '[object Function]'; }
function functionName(fn) {
var ret = fn.toString();
ret = ret.substr('function '.length);
ret = ret.substr(0, ret.indexOf('('));
return ret;
}
////////////////////////////////////////////////////////////////////////////////
function Ruler(compileFn) {
this.compile = compileFn; // callback to call after each change
// List of added rules. Each element is:
//
// {
// name: XXX,
// fn: Function(),
// alt: [ name2, name3 ]
// }
//
this.rules = [];
}
// Find rule index by name
//
Ruler.prototype.find = function (name) {
for (var i = 0; i < this.rules.length; i++) {
if (this.rules[i].name === name) {
return i;
}
}
return -1;
};
// Replace/delete parser function
//
Ruler.prototype.at = function (name, fn, altNames) {
var index = this.find(name);
if (index === -1) {
throw new Error('Parser rule not found: ' + name);
}
if (isFunction(fn)) {
this.rules[index].fn = fn;
if (altNames) {
this.rules[index].alt = altNames;
}
} else {
this.rules = this.rules.slice(0, index).concat(this.rules.slice(index + 1));
}
this.compile();
};
// Add function to parser chain before one with given name.
// Or add to start, if name not defined
//
Ruler.prototype.before = function (name, fn, altNames) {
var index;
if (isFunction(name)) {
altNames = fn;
fn = name;
name = '';
}
if (!name) {
this.rules.unshift({
name: functionName(fn),
fn: fn,
alt: altNames || []
});
} else {
index = this.find(name);
if (index === -1) {
throw new Error('Parser rule not found: ' + name);
}
this.rules.splice(index, 0, fn);
}
this.compile();
};
// Add function to parser chain after one with given name.
// Or add to end, if name not defined
//
Ruler.prototype.after = function (name, fn, altNames) {
var index;
if (isFunction(name)) {
altNames = fn;
fn = name;
name = '';
}
if (!name) {
this.rules.push({
name: functionName(fn),
fn: fn,
alt: altNames || []
});
} else {
index = this.find(name);
if (index === -1) {
throw new Error('Parser rule not found: ' + name);
}
this.rules.splice(index + 1, 0, fn);
}
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) {
result.push(rule.fn);
});
return result;
}
this.rules.forEach(function (rule) {
if (rule.alt.indexOf(chainName) >= 0) {
result.push(rule.fn);
}
});
return result;
};
module.exports = Ruler;

22
lib/rules_block/blockquote.js

@ -7,8 +7,8 @@ var skipSpaces = require('../helpers').skipSpaces;
module.exports = function blockquote(state, startLine, endLine, silent) { module.exports = function blockquote(state, startLine, endLine, silent) {
var nextLine, lastLineEmpty, oldTShift, oldBMarks, i, oldIndent, oldListMode, var nextLine, lastLineEmpty, oldTShift, oldBMarks, oldIndent, oldListMode,
rules_named = state.parser.rules_named, terminatorRules = state.parser._rulesBlockquoteTerm, i, l, terminate,
pos = state.bMarks[startLine] + state.tShift[startLine], pos = state.bMarks[startLine] + state.tShift[startLine],
max = state.eMarks[startLine]; max = state.eMarks[startLine];
@ -88,16 +88,14 @@ module.exports = function blockquote(state, startLine, endLine, silent) {
if (lastLineEmpty) { break; } if (lastLineEmpty) { break; }
// Case 3: another tag found. // Case 3: another tag found.
if (rules_named.fences(state, nextLine, endLine, true)) { break; } terminate = false;
if (rules_named.hr(state, nextLine, endLine, true)) { break; } for (i = 0, l = terminatorRules.length; i < l; i++) {
if (rules_named.list(state, nextLine, endLine, true)) { break; } if (terminatorRules[i](state, nextLine, endLine, true)) {
if (rules_named.heading(state, nextLine, endLine, true)) { break; } terminate = true;
// setex header can't interrupt paragraph break;
// if (rules_named.lheading(state, nextLine, endLine, true)) { break; } }
if (rules_named.blockquote(state, nextLine, endLine, true)) { break; } }
if (rules_named.table(state, nextLine, endLine, true)) { break; } if (terminate) { break; }
//if (rules_named.tag(state, nextLine, endLine, true)) { break; }
//if (rules_named.def(state, nextLine, endLine, true)) { break; }
oldBMarks.push(state.bMarks[nextLine]); oldBMarks.push(state.bMarks[nextLine]);
oldTShift.push(state.tShift[nextLine]); oldTShift.push(state.tShift[nextLine]);

13
lib/rules_block/list.js

@ -90,7 +90,7 @@ module.exports = function list(state, startLine, endLine, silent) {
contentStart, contentStart,
listTokIdx, listTokIdx,
prevEmptyEnd, prevEmptyEnd,
rules_named = state.parser.rules_named; terminatorRules = state.parser._rulesListTerm, i, l, terminate;
// Detect list type and position after marker // Detect list type and position after marker
if ((posAfterMarker = skipOrderedListMarker(state, startLine)) >= 0) { if ((posAfterMarker = skipOrderedListMarker(state, startLine)) >= 0) {
@ -210,9 +210,14 @@ module.exports = function list(state, startLine, endLine, silent) {
if (state.bqMarks[nextLine] < state.bqLevel) { break; } if (state.bqMarks[nextLine] < state.bqLevel) { break; }
// fail if terminating block found // fail if terminating block found
if (rules_named.fences(state, nextLine, endLine, true)) { break; } terminate = false;
if (rules_named.blockquote(state, nextLine, endLine, true)) { break; } for (i = 0, l = terminatorRules.length; i < l; i++) {
if (rules_named.hr(state, nextLine, endLine, true)) { break; } if (terminatorRules[i](state, nextLine, endLine, true)) {
terminate = true;
break;
}
}
if (terminate) { break; }
// fail if list has another type // fail if list has another type
if (isOrdered) { if (isOrdered) {

23
lib/rules_block/paragraph.js

@ -9,9 +9,9 @@ var parseRef = require('../parser_ref');
module.exports = function paragraph(state, startLine/*, endLine*/) { module.exports = function paragraph(state, startLine/*, endLine*/) {
var endLine, content, pos, var endLine, content, pos, terminate, i, l,
nextLine = startLine + 1, nextLine = startLine + 1,
rules_named = state.parser.rules_named; terminatorRules = state.parser._rulesParagraphTerm;
endLine = state.lineMax; endLine = state.lineMax;
@ -22,17 +22,14 @@ module.exports = function paragraph(state, startLine/*, endLine*/) {
if (state.tShift[nextLine] - state.blkIndent > 3) { continue; } if (state.tShift[nextLine] - state.blkIndent > 3) { continue; }
// Some tags can terminate paragraph without empty line. // Some tags can terminate paragraph without empty line.
if (rules_named.fences(state, nextLine, endLine, true)) { break; } terminate = false;
if (rules_named.hr(state, nextLine, endLine, true)) { break; } for (i = 0, l = terminatorRules.length; i < l; i++) {
if (rules_named.list(state, nextLine, endLine, true)) { break; } if (terminatorRules[i](state, nextLine, endLine, true)) {
if (rules_named.heading(state, nextLine, endLine, true)) { break; } terminate = true;
// setex header can't interrupt paragraph break;
// if (rules_named.lheading(state, nextLine, endLine, true)) { break; } }
if (rules_named.blockquote(state, nextLine, endLine, true)) { break; } }
if (rules_named.htmlblock(state, nextLine, endLine, true)) { break; } if (terminate) { break; }
if (rules_named.table(state, nextLine, endLine, true)) { break; }
//if (rules_named.tag(state, nextLine, endLine, true)) { break; }
//if (rules_named.def(state, nextLine, endLine, true)) { break; }
} }
content = getLines(state, startLine, nextLine, state.blkIndent, false).trim(); content = getLines(state, startLine, nextLine, state.blkIndent, false).trim();

Loading…
Cancel
Save