From 470bfecbd402ce288c2cd9d3119f331bcd40c7f0 Mon Sep 17 00:00:00 2001 From: Alex Kocharin Date: Wed, 10 Sep 2014 16:37:31 +0400 Subject: [PATCH] Better algorithm for parsing block quotes --- lib/lexer_block.js | 9 ---- lib/lexer_block/blockquote.js | 79 ++++++++++++----------------------- lib/lexer_block/fences.js | 6 +-- lib/lexer_block/lheading.js | 2 +- lib/lexer_block/paragraph.js | 7 +++- lib/parser.js | 4 +- 6 files changed, 36 insertions(+), 71 deletions(-) diff --git a/lib/lexer_block.js b/lib/lexer_block.js index 60e5eb9..7ea25f2 100644 --- a/lib/lexer_block.js +++ b/lib/lexer_block.js @@ -4,7 +4,6 @@ 'use strict'; -var isEmpty = require('./helpers').isEmpty; var skipEmptyLines = require('./helpers').skipEmptyLines; @@ -140,15 +139,7 @@ LexerBlock.prototype.tokenize = function (state, startLine, endLine) { 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; diff --git a/lib/lexer_block/blockquote.js b/lib/lexer_block/blockquote.js index 6dfa3a6..3481f10 100644 --- a/lib/lexer_block/blockquote.js +++ b/lib/lexer_block/blockquote.js @@ -3,13 +3,12 @@ 'use strict'; -var skipSpaces = require('../helpers').skipSpaces; -var getLines = require('../helpers').getLines; +var getLines = require('../helpers').getLines; +var isEmpty = require('../helpers').isEmpty; module.exports = function blockquote(state, startLine, endLine, silent) { - var nextLine, lastLineEmpty, subState, - rules_named = state.lexerBlock.rules_named, + var nextLine, subState, insideLines, lineMax, pos = state.bMarks[startLine] + state.tShift[startLine], max = state.eMarks[startLine]; @@ -25,63 +24,37 @@ module.exports = function blockquote(state, startLine, endLine, silent) { // so no point trying to find the end of it in silent mode if (silent) { return true; } - // check if we have an empty blockquote - pos = pos < max ? skipSpaces(state, pos) : pos; - lastLineEmpty = pos >= max; - - // Search the end of the block - // - // Block ends with either: - // 1. an empty line outside: - // ``` - // > test - // - // ``` - // 2. an empty line inside: - // ``` - // > - // test - // ``` - // 3. another tag - // ``` - // > test - // - - - - // ``` - for (nextLine = startLine + 1; nextLine < endLine; nextLine++) { + lineMax = state.lineMax; + insideLines = 1; + state.tokens.push({ type: 'blockquote_open' }); + for (nextLine = startLine + 1; nextLine < lineMax; ) { pos = state.bMarks[nextLine] + state.tShift[nextLine]; max = state.eMarks[nextLine]; - if (pos >= max) { - // Case 1: line is not inside the blockquote, and this line is empty. - break; + if (pos < max && state.src.charCodeAt(pos++) === 0x3E/* > */) { + if (nextLine < endLine) { + nextLine++; + insideLines++; + continue; + } else { + break; + } } - if (state.src.charCodeAt(pos++) === 0x3E/* > */) { - // This line is inside the blockquote. - pos = pos < max ? skipSpaces(state, pos) : pos; - lastLineEmpty = pos >= max; - continue; + if (insideLines === 0) { + break; } - // Case 2: line is not inside the blockquote, and the last line was empty. - if (lastLineEmpty) { break; } - - // Case 3: another tag found. - if (rules_named.fences(state, nextLine, endLine, true)) { break; } - if (rules_named.hr(state, nextLine, endLine, true)) { break; } - if (rules_named.list(state, nextLine, endLine, true)) { break; } - if (rules_named.heading(state, nextLine, endLine, true)) { break; } - if (rules_named.lheading(state, nextLine, endLine, true)) { 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; } + while (nextLine < lineMax) { + if (isEmpty(state, nextLine)) { break; } + nextLine++; + } + subState = state.clone(getLines(state, startLine, nextLine, true) + .replace(/^ {0,3}> ?/mg, '')); + state.lexerBlock.tokenize(subState, 0, insideLines); + nextLine = startLine = subState.line + startLine; + insideLines = 0; } - - state.tokens.push({ type: 'blockquote_open' }); - - subState = state.clone(getLines(state, startLine, nextLine, true).replace(/^ {0,3}> ?/mg, '')); - state.lexerBlock.tokenize(subState, 0, subState.lineMax); - state.tokens.push({ type: 'blockquote_close' }); state.line = nextLine; diff --git a/lib/lexer_block/fences.js b/lib/lexer_block/fences.js index 7d69a5c..d2fb239 100644 --- a/lib/lexer_block/fences.js +++ b/lib/lexer_block/fences.js @@ -40,9 +40,7 @@ module.exports = function fences(state, startLine, endLine, silent) { nextLine = startLine; for (;;) { - nextLine++; - - if (nextLine >= endLine) { + if (nextLine + 1 >= endLine) { // unclosed block should be autoclosed by end of document. // also block seems to be autoclosed by end of parent /*if (state.blkLevel === 0) { @@ -52,6 +50,8 @@ module.exports = function fences(state, startLine, endLine, silent) { break; } + nextLine++; + pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]; max = state.eMarks[nextLine]; diff --git a/lib/lexer_block/lheading.js b/lib/lexer_block/lheading.js index 4b1ad6f..b8d78f5 100644 --- a/lib/lexer_block/lheading.js +++ b/lib/lexer_block/lheading.js @@ -12,7 +12,7 @@ module.exports = function lheading(state, startLine, endLine, silent) { var marker, pos, max, next = startLine + 1; - if (next >= state.lineMax) { return false; } + if (next >= endLine) { return false; } // Scan next line if (state.tShift[next] > 3) { return false; } diff --git a/lib/lexer_block/paragraph.js b/lib/lexer_block/paragraph.js index 79d717c..61e4172 100644 --- a/lib/lexer_block/paragraph.js +++ b/lib/lexer_block/paragraph.js @@ -6,10 +6,13 @@ var isEmpty = require('../helpers').isEmpty; -module.exports = function paragraph(state, startLine, endLine) { - var nextLine = startLine + 1, +module.exports = function paragraph(state, startLine/*, endLine*/) { + var endLine, + nextLine = startLine + 1, rules_named = state.lexerBlock.rules_named; + endLine = state.lineMax; + // jump line-by-line until empty one or EOF while (nextLine < endLine && !isEmpty(state, nextLine)) { // Some tags can terminate paragraph without empty line. diff --git a/lib/parser.js b/lib/parser.js index 498bdca..ec229a4 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -74,9 +74,7 @@ Parser.prototype.render = function (src) { this.options ); - while (state.line < state.lineMax) { - state.lexerBlock.tokenize(state, state.line, state.lineMax); - } + state.lexerBlock.tokenize(state, state.line, state.lineMax); return this.renderer.render(state); };