Browse Source

Better algorithm for parsing block quotes

pull/14/head
Alex Kocharin 11 years ago
committed by Vitaly Puzrin
parent
commit
470bfecbd4
  1. 9
      lib/lexer_block.js
  2. 75
      lib/lexer_block/blockquote.js
  3. 6
      lib/lexer_block/fences.js
  4. 2
      lib/lexer_block/lheading.js
  5. 7
      lib/lexer_block/paragraph.js
  6. 2
      lib/parser.js

9
lib/lexer_block.js

@ -4,7 +4,6 @@
'use strict'; 'use strict';
var isEmpty = require('./helpers').isEmpty;
var skipEmptyLines = require('./helpers').skipEmptyLines; var skipEmptyLines = require('./helpers').skipEmptyLines;
@ -140,15 +139,7 @@ LexerBlock.prototype.tokenize = function (state, startLine, endLine) {
if (line === state.line) { if (line === state.line) {
throw new Error('None of rules updated state.line'); throw new Error('None of rules updated state.line');
} }
line = 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; state.tight = !hasEmptyLines;

75
lib/lexer_block/blockquote.js

@ -3,13 +3,12 @@
'use strict'; '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) { module.exports = function blockquote(state, startLine, endLine, silent) {
var nextLine, lastLineEmpty, subState, var nextLine, subState, insideLines, lineMax,
rules_named = state.lexerBlock.rules_named,
pos = state.bMarks[startLine] + state.tShift[startLine], pos = state.bMarks[startLine] + state.tShift[startLine],
max = state.eMarks[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 // so no point trying to find the end of it in silent mode
if (silent) { return true; } if (silent) { return true; }
// check if we have an empty blockquote lineMax = state.lineMax;
pos = pos < max ? skipSpaces(state, pos) : pos; insideLines = 1;
lastLineEmpty = pos >= max; state.tokens.push({ type: 'blockquote_open' });
for (nextLine = startLine + 1; nextLine < lineMax; ) {
// 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++) {
pos = state.bMarks[nextLine] + state.tShift[nextLine]; pos = state.bMarks[nextLine] + state.tShift[nextLine];
max = state.eMarks[nextLine]; max = state.eMarks[nextLine];
if (pos >= max) { if (pos < max && state.src.charCodeAt(pos++) === 0x3E/* > */) {
// Case 1: line is not inside the blockquote, and this line is empty. if (nextLine < endLine) {
nextLine++;
insideLines++;
continue;
} else {
break; break;
} }
if (state.src.charCodeAt(pos++) === 0x3E/* > */) {
// This line is inside the blockquote.
pos = pos < max ? skipSpaces(state, pos) : pos;
lastLineEmpty = pos >= max;
continue;
} }
// Case 2: line is not inside the blockquote, and the last line was empty. if (insideLines === 0) {
if (lastLineEmpty) { break; } 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; }
} }
state.tokens.push({ type: 'blockquote_open' }); while (nextLine < lineMax) {
if (isEmpty(state, nextLine)) { break; }
subState = state.clone(getLines(state, startLine, nextLine, true).replace(/^ {0,3}> ?/mg, '')); nextLine++;
state.lexerBlock.tokenize(subState, 0, subState.lineMax); }
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_close' }); state.tokens.push({ type: 'blockquote_close' });
state.line = nextLine; state.line = nextLine;

6
lib/lexer_block/fences.js

@ -40,9 +40,7 @@ module.exports = function fences(state, startLine, endLine, silent) {
nextLine = startLine; nextLine = startLine;
for (;;) { for (;;) {
nextLine++; if (nextLine + 1 >= endLine) {
if (nextLine >= endLine) {
// unclosed block should be autoclosed by end of document. // unclosed block should be autoclosed by end of document.
// also block seems to be autoclosed by end of parent // also block seems to be autoclosed by end of parent
/*if (state.blkLevel === 0) { /*if (state.blkLevel === 0) {
@ -52,6 +50,8 @@ module.exports = function fences(state, startLine, endLine, silent) {
break; break;
} }
nextLine++;
pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]; pos = mem = state.bMarks[nextLine] + state.tShift[nextLine];
max = state.eMarks[nextLine]; max = state.eMarks[nextLine];

2
lib/lexer_block/lheading.js

@ -12,7 +12,7 @@ module.exports = function lheading(state, startLine, endLine, silent) {
var marker, pos, max, var marker, pos, max,
next = startLine + 1; next = startLine + 1;
if (next >= state.lineMax) { return false; } if (next >= endLine) { return false; }
// Scan next line // Scan next line
if (state.tShift[next] > 3) { return false; } if (state.tShift[next] > 3) { return false; }

7
lib/lexer_block/paragraph.js

@ -6,10 +6,13 @@
var isEmpty = require('../helpers').isEmpty; var isEmpty = require('../helpers').isEmpty;
module.exports = function paragraph(state, startLine, endLine) { module.exports = function paragraph(state, startLine/*, endLine*/) {
var nextLine = startLine + 1, var endLine,
nextLine = startLine + 1,
rules_named = state.lexerBlock.rules_named; rules_named = state.lexerBlock.rules_named;
endLine = state.lineMax;
// jump line-by-line until empty one or EOF // jump line-by-line until empty one or EOF
while (nextLine < endLine && !isEmpty(state, nextLine)) { while (nextLine < endLine && !isEmpty(state, nextLine)) {
// Some tags can terminate paragraph without empty line. // Some tags can terminate paragraph without empty line.

2
lib/parser.js

@ -74,9 +74,7 @@ Parser.prototype.render = function (src) {
this.options 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); return this.renderer.render(state);
}; };

Loading…
Cancel
Save