Markdown parser, done right. 100% CommonMark support, extensions, syntax plugins & high speed https://markdown-it.github.io/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

159 lines
3.4 KiB

// Block lexer
'use strict';
var isEmpty = require('./helpers').isEmpty;
var skipEmptyLines = require('./helpers').skipEmptyLines;
var rules = [];
// `list` should be after `hr`, but before `heading`
rules.push(require('./lexer_block/code'));
rules.push(require('./lexer_block/fences'));
rules.push(require('./lexer_block/blockquote'));
rules.push(require('./lexer_block/hr'));
rules.push(require('./lexer_block/list'));
rules.push(require('./lexer_block/heading'));
rules.push(require('./lexer_block/lheading'));
rules.push(require('./lexer_block/table'));
rules.push(require('./lexer_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 Lexer class
//
function LexerBlock() {
this.rules = [];
this.rules_named = {};
for (var i = 0; i < rules.length; i++) {
this.after(null, rules[i]);
}
}
// Replace/delete lexer function
//
LexerBlock.prototype.at = function (name, fn) {
var index = findByName(name);
if (index === -1) {
throw new Error('Lexer 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;
};
// Add function to lexer chain before one with given name.
// Or add to start, if name not defined
//
LexerBlock.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('Lexer rule not found: ' + name);
}
this.rules.splice(index, 0, fn);
this.rules_named[functionName(fn)] = fn;
};
// Add function to lexer chain after one with given name.
// Or add to end, if name not defined
//
LexerBlock.prototype.after = function (name, fn) {
if (!name) {
this.rules.push(fn);
this.rules_named[functionName(fn)] = fn;
return;
}
var index = findByName(name);
if (index === -1) {
throw new Error('Lexer rule not found: ' + name);
}
this.rules.splice(index + 1, 0, fn);
this.rules_named[functionName(fn)] = fn;
};
// Generate tokens for input range
//
LexerBlock.prototype.tokenize = function (state, startLine, endLine) {
var ok, i,
rules = this.rules,
len = this.rules.length,
line = startLine,
hasEmptyLines = false;
while (line < endLine) {
line = skipEmptyLines(state, line, endLine);
state.line = line;
if (line >= endLine) { break; }
// Try all possible rules.
// On success, rule should:
//
// - update `state.line`
// - update `state.tokens`
// - return true
for (i = 0; i < len; i++) {
ok = rules[i](state, line, endLine, false);
if (ok) { break; }
}
if (!ok) { throw new Error('No matching rules found'); }
if (line === state.line) {
throw new Error('None of rules updated state.line');
}
line = state.line;
if (isEmpty(state, line)) {
hasEmptyLines = true;
line++;
// two empty lines should stop the parser
if (isEmpty(state, line + 1)) { break; }
}
}
state.tight = !hasEmptyLines;
};
module.exports = LexerBlock;