// 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;