balance_pairs.mjs 4.0 KB

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