balance_pairs.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. // For each opening emphasis-like marker find a matching closing one
  2. //
  3. 'use strict';
  4. function processDelimiters(state, delimiters) {
  5. var closerIdx, openerIdx, closer, opener, minOpenerIdx, newMinOpenerIdx,
  6. isOddMatch, lastJump,
  7. openersBottom = {},
  8. max = delimiters.length;
  9. if (!max) return;
  10. // headerIdx is the first delimiter of the current (where closer is) delimiter run
  11. var headerIdx = 0;
  12. var lastTokenIdx = -2; // needs any value lower than -1
  13. var jumps = [];
  14. for (closerIdx = 0; closerIdx < max; closerIdx++) {
  15. closer = delimiters[closerIdx];
  16. jumps.push(0);
  17. // markers belong to same delimiter run if:
  18. // - they have adjacent tokens
  19. // - AND markers are the same
  20. //
  21. if (delimiters[headerIdx].marker !== closer.marker || lastTokenIdx !== closer.token - 1) {
  22. headerIdx = closerIdx;
  23. }
  24. lastTokenIdx = closer.token;
  25. // Length is only used for emphasis-specific "rule of 3",
  26. // if it's not defined (in strikethrough or 3rd party plugins),
  27. // we can default it to 0 to disable those checks.
  28. //
  29. closer.length = closer.length || 0;
  30. if (!closer.close) continue;
  31. // Previously calculated lower bounds (previous fails)
  32. // for each marker, each delimiter length modulo 3,
  33. // and for whether this closer can be an opener;
  34. // https://github.com/commonmark/cmark/commit/34250e12ccebdc6372b8b49c44fab57c72443460
  35. if (!openersBottom.hasOwnProperty(closer.marker)) {
  36. openersBottom[closer.marker] = [ -1, -1, -1, -1, -1, -1 ];
  37. }
  38. minOpenerIdx = openersBottom[closer.marker][(closer.open ? 3 : 0) + (closer.length % 3)];
  39. openerIdx = headerIdx - jumps[headerIdx] - 1;
  40. newMinOpenerIdx = openerIdx;
  41. for (; openerIdx > minOpenerIdx; openerIdx -= jumps[openerIdx] + 1) {
  42. opener = delimiters[openerIdx];
  43. if (opener.marker !== closer.marker) continue;
  44. if (opener.open && opener.end < 0) {
  45. isOddMatch = false;
  46. // from spec:
  47. //
  48. // If one of the delimiters can both open and close emphasis, then the
  49. // sum of the lengths of the delimiter runs containing the opening and
  50. // closing delimiters must not be a multiple of 3 unless both lengths
  51. // are multiples of 3.
  52. //
  53. if (opener.close || closer.open) {
  54. if ((opener.length + closer.length) % 3 === 0) {
  55. if (opener.length % 3 !== 0 || closer.length % 3 !== 0) {
  56. isOddMatch = true;
  57. }
  58. }
  59. }
  60. if (!isOddMatch) {
  61. // If previous delimiter cannot be an opener, we can safely skip
  62. // the entire sequence in future checks. This is required to make
  63. // sure algorithm has linear complexity (see *_*_*_*_*_... case).
  64. //
  65. lastJump = openerIdx > 0 && !delimiters[openerIdx - 1].open ?
  66. jumps[openerIdx - 1] + 1 :
  67. 0;
  68. jumps[closerIdx] = closerIdx - openerIdx + lastJump;
  69. jumps[openerIdx] = lastJump;
  70. closer.open = false;
  71. opener.end = closerIdx;
  72. opener.close = false;
  73. newMinOpenerIdx = -1;
  74. // treat next token as start of run,
  75. // it optimizes skips in **<...>**a**<...>** pathological case
  76. lastTokenIdx = -2;
  77. break;
  78. }
  79. }
  80. }
  81. if (newMinOpenerIdx !== -1) {
  82. // If match for this delimiter run failed, we want to set lower bound for
  83. // future lookups. This is required to make sure algorithm has linear
  84. // complexity.
  85. //
  86. // See details here:
  87. // https://github.com/commonmark/cmark/issues/178#issuecomment-270417442
  88. //
  89. openersBottom[closer.marker][(closer.open ? 3 : 0) + ((closer.length || 0) % 3)] = newMinOpenerIdx;
  90. }
  91. }
  92. }
  93. module.exports = function link_pairs(state) {
  94. var curr,
  95. tokens_meta = state.tokens_meta,
  96. max = state.tokens_meta.length;
  97. processDelimiters(state, state.delimiters);
  98. for (curr = 0; curr < max; curr++) {
  99. if (tokens_meta[curr] && tokens_meta[curr].delimiters) {
  100. processDelimiters(state, tokens_meta[curr].delimiters);
  101. }
  102. }
  103. };