Browse Source

Move nested delimiter info to opening token instead of inline state

pull/599/head
Alex Kocharin 5 years ago
parent
commit
07a62c6c75
  1. 115
      lib/rules_inline/balance_pairs.js
  2. 28
      lib/rules_inline/emphasis.js
  3. 30
      lib/rules_inline/state_inline.js
  4. 26
      lib/rules_inline/strikethrough.js
  5. 7
      test/fixtures/markdown-it/commonmark_extras.txt
  6. 15
      test/fixtures/markdown-it/strikethrough.txt

115
lib/rules_inline/balance_pairs.js

@ -3,53 +3,106 @@
'use strict'; 'use strict';
module.exports = function link_pairs(state) { function processDelimiters(state, delimiters) {
var i, j, lastDelim, currDelim, var closerIdx, openerIdx, closer, opener, minOpenerIdx, newMinOpenerIdx,
delimiters = state.delimiters, isOddMatch, lastJump,
max = state.delimiters.length; openersBottom = {},
max = delimiters.length;
for (closerIdx = 0; closerIdx < max; closerIdx++) {
closer = delimiters[closerIdx];
// Length is only used for emphasis-specific "rule of 3",
// if it's not defined (in strikethrough or 3rd party plugins),
// we can default it to 0 to disable those checks.
//
closer.length = closer.length || 0;
if (!closer.close) continue;
for (i = 0; i < max; i++) { // Previously calculated lower bounds (previous fails)
lastDelim = delimiters[i]; // for each marker and each delimiter length modulo 3.
if (!openersBottom.hasOwnProperty(closer.marker)) {
openersBottom[closer.marker] = [ -1, -1, -1 ];
}
minOpenerIdx = openersBottom[closer.marker][closer.length % 3];
newMinOpenerIdx = -1;
if (!lastDelim.close) { continue; } openerIdx = closerIdx - closer.jump - 1;
j = i - lastDelim.jump - 1; for (; openerIdx > minOpenerIdx; openerIdx -= opener.jump + 1) {
opener = delimiters[openerIdx];
while (j >= 0) { if (opener.marker !== closer.marker) continue;
currDelim = delimiters[j];
if (currDelim.open && if (newMinOpenerIdx === -1) newMinOpenerIdx = openerIdx;
currDelim.marker === lastDelim.marker &&
currDelim.end < 0 &&
currDelim.level === lastDelim.level) {
var odd_match = false; if (opener.open &&
opener.end < 0 &&
opener.level === closer.level) {
// typeofs are for backward compatibility with plugins isOddMatch = false;
if ((currDelim.close || lastDelim.open) &&
typeof currDelim.length !== 'undefined' &&
typeof lastDelim.length !== 'undefined') {
// from spec: // from spec:
// sum of the lengths [...] must not be a multiple of 3 //
// unless both lengths are multiples of 3 // If one of the delimiters can both open and close emphasis, then the
if ((currDelim.length + lastDelim.length) % 3 === 0) { // sum of the lengths of the delimiter runs containing the opening and
if (currDelim.length % 3 !== 0 || lastDelim.length % 3 !== 0) { // closing delimiters must not be a multiple of 3 unless both lengths
odd_match = true; // are multiples of 3.
//
if (opener.close || closer.open) {
if ((opener.length + closer.length) % 3 === 0) {
if (opener.length % 3 !== 0 || closer.length % 3 !== 0) {
isOddMatch = true;
} }
} }
} }
if (!odd_match) { if (!isOddMatch) {
lastDelim.jump = i - j; // If previous delimiter cannot be an opener, we can safely skip
lastDelim.open = false; // the entire sequence in future checks. This is required to make
currDelim.end = i; // sure algorithm has linear complexity (see *_*_*_*_*_... case).
currDelim.jump = 0; //
lastJump = openerIdx > 0 && !delimiters[openerIdx - 1].open ?
delimiters[openerIdx - 1].jump + 1 :
0;
closer.jump = closerIdx - openerIdx + lastJump;
closer.open = false;
opener.end = closerIdx;
opener.jump = lastJump;
opener.close = false;
newMinOpenerIdx = -1;
break; break;
} }
} }
}
if (newMinOpenerIdx !== -1) {
// If match for this delimiter run failed, we want to set lower bound for
// future lookups. This is required to make sure algorithm has linear
// complexity.
//
// See details here:
// https://github.com/commonmark/cmark/issues/178#issuecomment-270417442
//
openersBottom[closer.marker][(closer.length || 0) % 3] = newMinOpenerIdx;
}
}
}
module.exports = function link_pairs(state) {
var curr,
tokens_meta = state.tokens_meta,
max = state.tokens_meta.length;
processDelimiters(state, state.delimiters);
j -= currDelim.jump + 1; for (curr = 0; curr < max; curr++) {
if (tokens_meta[curr] && tokens_meta[curr].delimiters) {
processDelimiters(state, tokens_meta[curr].delimiters);
} }
} }
}; };

28
lib/rules_inline/emphasis.js

@ -42,10 +42,6 @@ module.exports.tokenize = function emphasis(state, silent) {
// //
token: state.tokens.length - 1, token: state.tokens.length - 1,
// Token level.
//
level: state.level,
// If this delimiter is matched as a valid opener, `end` will be // If this delimiter is matched as a valid opener, `end` will be
// equal to its position, otherwise it's `-1`. // equal to its position, otherwise it's `-1`.
// //
@ -65,17 +61,14 @@ module.exports.tokenize = function emphasis(state, silent) {
}; };
// Walk through delimiter list and replace text tokens with tags function postProcess(state, delimiters) {
//
module.exports.postProcess = function emphasis(state) {
var i, var i,
startDelim, startDelim,
endDelim, endDelim,
token, token,
ch, ch,
isStrong, isStrong,
delimiters = state.delimiters, max = delimiters.length;
max = state.delimiters.length;
for (i = max - 1; i >= 0; i--) { for (i = max - 1; i >= 0; i--) {
startDelim = delimiters[i]; startDelim = delimiters[i];
@ -124,4 +117,21 @@ module.exports.postProcess = function emphasis(state) {
i--; i--;
} }
} }
}
// Walk through delimiter list and replace text tokens with tags
//
module.exports.postProcess = function emphasis(state) {
var curr,
tokens_meta = state.tokens_meta,
max = state.tokens_meta.length;
postProcess(state, state.delimiters);
for (curr = 0; curr < max; curr++) {
if (tokens_meta[curr] && tokens_meta[curr].delimiters) {
postProcess(state, tokens_meta[curr].delimiters);
}
}
}; };

30
lib/rules_inline/state_inline.js

@ -14,6 +14,7 @@ function StateInline(src, md, env, outTokens) {
this.env = env; this.env = env;
this.md = md; this.md = md;
this.tokens = outTokens; this.tokens = outTokens;
this.tokens_meta = Array(outTokens.length);
this.pos = 0; this.pos = 0;
this.posMax = this.src.length; this.posMax = this.src.length;
@ -21,10 +22,15 @@ function StateInline(src, md, env, outTokens) {
this.pending = ''; this.pending = '';
this.pendingLevel = 0; this.pendingLevel = 0;
this.cache = {}; // Stores { start: end } pairs. Useful for backtrack // Stores { start: end } pairs. Useful for backtrack
// optimization of pairs parse (emphasis, strikes). // optimization of pairs parse (emphasis, strikes).
this.cache = {};
this.delimiters = []; // Emphasis-like delimiters // List of emphasis-like delimiters for current tag
this.delimiters = [];
// Stack of delimiter lists for upper level tags
this._prev_delimiters = [];
} }
@ -49,13 +55,27 @@ StateInline.prototype.push = function (type, tag, nesting) {
} }
var token = new Token(type, tag, nesting); var token = new Token(type, tag, nesting);
var token_meta = null;
if (nesting < 0) {
// closing tag
this.level--;
this.delimiters = this._prev_delimiters.pop();
}
if (nesting < 0) this.level--; // closing tag
token.level = this.level; token.level = this.level;
if (nesting > 0) this.level++; // opening tag
if (nesting > 0) {
// opening tag
this.level++;
this._prev_delimiters.push(this.delimiters);
this.delimiters = [];
token_meta = { delimiters: this.delimiters };
}
this.pendingLevel = this.level; this.pendingLevel = this.level;
this.tokens.push(token); this.tokens.push(token);
this.tokens_meta.push(token_meta);
return token; return token;
}; };

26
lib/rules_inline/strikethrough.js

@ -32,9 +32,9 @@ module.exports.tokenize = function strikethrough(state, silent) {
state.delimiters.push({ state.delimiters.push({
marker: marker, marker: marker,
length: 0, // disable "rule of 3" length checks meant for emphasis
jump: i, jump: i,
token: state.tokens.length - 1, token: state.tokens.length - 1,
level: state.level,
end: -1, end: -1,
open: scanned.can_open, open: scanned.can_open,
close: scanned.can_close close: scanned.can_close
@ -47,16 +47,13 @@ module.exports.tokenize = function strikethrough(state, silent) {
}; };
// Walk through delimiter list and replace text tokens with tags function postProcess(state, delimiters) {
//
module.exports.postProcess = function strikethrough(state) {
var i, j, var i, j,
startDelim, startDelim,
endDelim, endDelim,
token, token,
loneMarkers = [], loneMarkers = [],
delimiters = state.delimiters, max = delimiters.length;
max = state.delimiters.length;
for (i = 0; i < max; i++) { for (i = 0; i < max; i++) {
startDelim = delimiters[i]; startDelim = delimiters[i];
@ -114,4 +111,21 @@ module.exports.postProcess = function strikethrough(state) {
state.tokens[i] = token; state.tokens[i] = token;
} }
} }
}
// Walk through delimiter list and replace text tokens with tags
//
module.exports.postProcess = function strikethrough(state) {
var curr,
tokens_meta = state.tokens_meta,
max = state.tokens_meta.length;
postProcess(state, state.delimiters);
for (curr = 0; curr < max; curr++) {
if (tokens_meta[curr] && tokens_meta[curr].delimiters) {
postProcess(state, tokens_meta[curr].delimiters);
}
}
}; };

7
test/fixtures/markdown-it/commonmark_extras.txt

@ -103,6 +103,13 @@ _(hai)_.
<p><em>(hai)</em>.</p> <p><em>(hai)</em>.</p>
. .
Regression test, should not match emphasis markers in different link tags:
.
[*b]() [c*]()
.
<p><a href="">*b</a> <a href="">c*</a></p>
.
Those are two separate blockquotes: Those are two separate blockquotes:
. .
- > foo - > foo

15
test/fixtures/markdown-it/strikethrough.txt

@ -84,6 +84,14 @@ foo ~~ bar ~~ baz
. .
Should parse strikethrough within link tags:
.
[~~foo~~]()
.
<p><a href=""><s>foo</s></a></p>
.
Newline should be considered a whitespace: Newline should be considered a whitespace:
. .
~~test ~~test
@ -112,3 +120,10 @@ a~~"foo"~~
. .
<p>a~~“foo”~~</p> <p>a~~“foo”~~</p>
. .
Coverage: single tilde
.
~a~
.
<p>~a~</p>
.

Loading…
Cancel
Save