parser_inline.mjs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. /** internal
  2. * class ParserInline
  3. *
  4. * Tokenizes paragraph content.
  5. **/
  6. import Ruler from './ruler.mjs'
  7. import StateInline from './rules_inline/state_inline.mjs'
  8. import r_text from './rules_inline/text.mjs'
  9. import r_linkify from './rules_inline/linkify.mjs'
  10. import r_newline from './rules_inline/newline.mjs'
  11. import r_escape from './rules_inline/escape.mjs'
  12. import r_backticks from './rules_inline/backticks.mjs'
  13. import r_strikethrough from './rules_inline/strikethrough.mjs'
  14. import r_emphasis from './rules_inline/emphasis.mjs'
  15. import r_link from './rules_inline/link.mjs'
  16. import r_image from './rules_inline/image.mjs'
  17. import r_autolink from './rules_inline/autolink.mjs'
  18. import r_html_inline from './rules_inline/html_inline.mjs'
  19. import r_entity from './rules_inline/entity.mjs'
  20. import r_balance_pairs from './rules_inline/balance_pairs.mjs'
  21. import r_fragments_join from './rules_inline/fragments_join.mjs'
  22. // Parser rules
  23. const _rules = [
  24. ['text', r_text],
  25. ['linkify', r_linkify],
  26. ['newline', r_newline],
  27. ['escape', r_escape],
  28. ['backticks', r_backticks],
  29. ['strikethrough', r_strikethrough.tokenize],
  30. ['emphasis', r_emphasis.tokenize],
  31. ['link', r_link],
  32. ['image', r_image],
  33. ['autolink', r_autolink],
  34. ['html_inline', r_html_inline],
  35. ['entity', r_entity]
  36. ]
  37. // `rule2` ruleset was created specifically for emphasis/strikethrough
  38. // post-processing and may be changed in the future.
  39. //
  40. // Don't use this for anything except pairs (plugins working with `balance_pairs`).
  41. //
  42. const _rules2 = [
  43. ['balance_pairs', r_balance_pairs],
  44. ['strikethrough', r_strikethrough.postProcess],
  45. ['emphasis', r_emphasis.postProcess],
  46. // rules for pairs separate '**' into its own text tokens, which may be left unused,
  47. // rule below merges unused segments back with the rest of the text
  48. ['fragments_join', r_fragments_join]
  49. ]
  50. /**
  51. * new ParserInline()
  52. **/
  53. function ParserInline () {
  54. /**
  55. * ParserInline#ruler -> Ruler
  56. *
  57. * [[Ruler]] instance. Keep configuration of inline rules.
  58. **/
  59. this.ruler = new Ruler()
  60. for (let i = 0; i < _rules.length; i++) {
  61. this.ruler.push(_rules[i][0], _rules[i][1])
  62. }
  63. /**
  64. * ParserInline#ruler2 -> Ruler
  65. *
  66. * [[Ruler]] instance. Second ruler used for post-processing
  67. * (e.g. in emphasis-like rules).
  68. **/
  69. this.ruler2 = new Ruler()
  70. for (let i = 0; i < _rules2.length; i++) {
  71. this.ruler2.push(_rules2[i][0], _rules2[i][1])
  72. }
  73. }
  74. // Skip single token by running all rules in validation mode;
  75. // returns `true` if any rule reported success
  76. //
  77. ParserInline.prototype.skipToken = function (state) {
  78. const pos = state.pos
  79. const rules = this.ruler.getRules('')
  80. const len = rules.length
  81. const maxNesting = state.md.options.maxNesting
  82. const cache = state.cache
  83. if (typeof cache[pos] !== 'undefined') {
  84. state.pos = cache[pos]
  85. return
  86. }
  87. let ok = false
  88. if (state.level < maxNesting) {
  89. for (let i = 0; i < len; i++) {
  90. // Increment state.level and decrement it later to limit recursion.
  91. // It's harmless to do here, because no tokens are created. But ideally,
  92. // we'd need a separate private state variable for this purpose.
  93. //
  94. state.level++
  95. ok = rules[i](state, true)
  96. state.level--
  97. if (ok) {
  98. if (pos >= state.pos) { throw new Error("inline rule didn't increment state.pos") }
  99. break
  100. }
  101. }
  102. } else {
  103. // Too much nesting, just skip until the end of the paragraph.
  104. //
  105. // NOTE: this will cause links to behave incorrectly in the following case,
  106. // when an amount of `[` is exactly equal to `maxNesting + 1`:
  107. //
  108. // [[[[[[[[[[[[[[[[[[[[[foo]()
  109. //
  110. // TODO: remove this workaround when CM standard will allow nested links
  111. // (we can replace it by preventing links from being parsed in
  112. // validation mode)
  113. //
  114. state.pos = state.posMax
  115. }
  116. if (!ok) { state.pos++ }
  117. cache[pos] = state.pos
  118. }
  119. // Generate tokens for input range
  120. //
  121. ParserInline.prototype.tokenize = function (state) {
  122. const rules = this.ruler.getRules('')
  123. const len = rules.length
  124. const end = state.posMax
  125. const maxNesting = state.md.options.maxNesting
  126. while (state.pos < end) {
  127. // Try all possible rules.
  128. // On success, rule should:
  129. //
  130. // - update `state.pos`
  131. // - update `state.tokens`
  132. // - return true
  133. const prevPos = state.pos
  134. let ok = false
  135. if (state.level < maxNesting) {
  136. for (let i = 0; i < len; i++) {
  137. ok = rules[i](state, false)
  138. if (ok) {
  139. if (prevPos >= state.pos) { throw new Error("inline rule didn't increment state.pos") }
  140. break
  141. }
  142. }
  143. }
  144. if (ok) {
  145. if (state.pos >= end) { break }
  146. continue
  147. }
  148. state.pending += state.src[state.pos++]
  149. }
  150. if (state.pending) {
  151. state.pushPending()
  152. }
  153. }
  154. /**
  155. * ParserInline.parse(str, md, env, outTokens)
  156. *
  157. * Process input string and push inline tokens into `outTokens`
  158. **/
  159. ParserInline.prototype.parse = function (str, md, env, outTokens) {
  160. const state = new this.State(str, md, env, outTokens)
  161. this.tokenize(state)
  162. const rules = this.ruler2.getRules('')
  163. const len = rules.length
  164. for (let i = 0; i < len; i++) {
  165. rules[i](state)
  166. }
  167. }
  168. ParserInline.prototype.State = StateInline
  169. export default ParserInline