balance_pairs.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  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. for (closerIdx = 0; closerIdx < max; closerIdx++) {
  10. closer = delimiters[closerIdx];
  11. // Length is only used for emphasis-specific "rule of 3",
  12. // if it's not defined (in strikethrough or 3rd party plugins),
  13. // we can default it to 0 to disable those checks.
  14. //
  15. closer.length = closer.length || 0;
  16. if (!closer.close) continue;
  17. // Previously calculated lower bounds (previous fails)
  18. // for each marker and each delimiter length modulo 3.
  19. if (!openersBottom.hasOwnProperty(closer.marker)) {
  20. openersBottom[closer.marker] = [ -1, -1, -1 ];
  21. }
  22. minOpenerIdx = openersBottom[closer.marker][closer.length % 3];
  23. newMinOpenerIdx = -1;
  24. openerIdx = closerIdx - closer.jump - 1;
  25. for (; openerIdx > minOpenerIdx; openerIdx -= opener.jump + 1) {
  26. opener = delimiters[openerIdx];
  27. if (opener.marker !== closer.marker) continue;
  28. if (newMinOpenerIdx === -1) newMinOpenerIdx = openerIdx;
  29. if (opener.open &&
  30. opener.end < 0 &&
  31. opener.level === closer.level) {
  32. isOddMatch = false;
  33. // from spec:
  34. //
  35. // If one of the delimiters can both open and close emphasis, then the
  36. // sum of the lengths of the delimiter runs containing the opening and
  37. // closing delimiters must not be a multiple of 3 unless both lengths
  38. // are multiples of 3.
  39. //
  40. if (opener.close || closer.open) {
  41. if ((opener.length + closer.length) % 3 === 0) {
  42. if (opener.length % 3 !== 0 || closer.length % 3 !== 0) {
  43. isOddMatch = true;
  44. }
  45. }
  46. }
  47. if (!isOddMatch) {
  48. // If previous delimiter cannot be an opener, we can safely skip
  49. // the entire sequence in future checks. This is required to make
  50. // sure algorithm has linear complexity (see *_*_*_*_*_... case).
  51. //
  52. lastJump = openerIdx > 0 && !delimiters[openerIdx - 1].open ?
  53. delimiters[openerIdx - 1].jump + 1 :
  54. 0;
  55. closer.jump = closerIdx - openerIdx + lastJump;
  56. closer.open = false;
  57. opener.end = closerIdx;
  58. opener.jump = lastJump;
  59. opener.close = false;
  60. newMinOpenerIdx = -1;
  61. break;
  62. }
  63. }
  64. }
  65. if (newMinOpenerIdx !== -1) {
  66. // If match for this delimiter run failed, we want to set lower bound for
  67. // future lookups. This is required to make sure algorithm has linear
  68. // complexity.
  69. //
  70. // See details here:
  71. // https://github.com/commonmark/cmark/issues/178#issuecomment-270417442
  72. //
  73. openersBottom[closer.marker][(closer.length || 0) % 3] = newMinOpenerIdx;
  74. }
  75. }
  76. }
  77. module.exports = function link_pairs(state) {
  78. var curr,
  79. tokens_meta = state.tokens_meta,
  80. max = state.tokens_meta.length;
  81. processDelimiters(state, state.delimiters);
  82. for (curr = 0; curr < max; curr++) {
  83. if (tokens_meta[curr] && tokens_meta[curr].delimiters) {
  84. processDelimiters(state, tokens_meta[curr].delimiters);
  85. }
  86. }
  87. };