parser_block.mjs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. /** internal
  2. * class ParserBlock
  3. *
  4. * Block-level tokenizer.
  5. **/
  6. import Ruler from './ruler.mjs'
  7. import StateBlock from './rules_block/state_block.mjs'
  8. import r_table from './rules_block/table.mjs'
  9. import r_code from './rules_block/code.mjs'
  10. import r_fence from './rules_block/fence.mjs'
  11. import r_blockquote from './rules_block/blockquote.mjs'
  12. import r_hr from './rules_block/hr.mjs'
  13. import r_list from './rules_block/list.mjs'
  14. import r_reference from './rules_block/reference.mjs'
  15. import r_html_block from './rules_block/html_block.mjs'
  16. import r_heading from './rules_block/heading.mjs'
  17. import r_lheading from './rules_block/lheading.mjs'
  18. import r_paragraph from './rules_block/paragraph.mjs'
  19. const _rules = [
  20. // First 2 params - rule name & source. Secondary array - list of rules,
  21. // which can be terminated by this one.
  22. ['table', r_table, ['paragraph', 'reference']],
  23. ['code', r_code],
  24. ['fence', r_fence, ['paragraph', 'reference', 'blockquote', 'list']],
  25. ['blockquote', r_blockquote, ['paragraph', 'reference', 'blockquote', 'list']],
  26. ['hr', r_hr, ['paragraph', 'reference', 'blockquote', 'list']],
  27. ['list', r_list, ['paragraph', 'reference', 'blockquote']],
  28. ['reference', r_reference],
  29. ['html_block', r_html_block, ['paragraph', 'reference', 'blockquote']],
  30. ['heading', r_heading, ['paragraph', 'reference', 'blockquote']],
  31. ['lheading', r_lheading],
  32. ['paragraph', r_paragraph]
  33. ]
  34. /**
  35. * new ParserBlock()
  36. **/
  37. function ParserBlock () {
  38. /**
  39. * ParserBlock#ruler -> Ruler
  40. *
  41. * [[Ruler]] instance. Keep configuration of block rules.
  42. **/
  43. this.ruler = new Ruler()
  44. for (let i = 0; i < _rules.length; i++) {
  45. this.ruler.push(_rules[i][0], _rules[i][1], { alt: (_rules[i][2] || []).slice() })
  46. }
  47. }
  48. // Generate tokens for input range
  49. //
  50. ParserBlock.prototype.tokenize = function (state, startLine, endLine) {
  51. const rules = this.ruler.getRules('')
  52. const len = rules.length
  53. const maxNesting = state.md.options.maxNesting
  54. let line = startLine
  55. let hasEmptyLines = false
  56. while (line < endLine) {
  57. state.line = line = state.skipEmptyLines(line)
  58. if (line >= endLine) { break }
  59. // Termination condition for nested calls.
  60. // Nested calls currently used for blockquotes & lists
  61. if (state.sCount[line] < state.blkIndent) { break }
  62. // If nesting level exceeded - skip tail to the end. That's not ordinary
  63. // situation and we should not care about content.
  64. if (state.level >= maxNesting) {
  65. state.line = endLine
  66. break
  67. }
  68. // Try all possible rules.
  69. // On success, rule should:
  70. //
  71. // - update `state.line`
  72. // - update `state.tokens`
  73. // - return true
  74. const prevLine = state.line
  75. let ok = false
  76. for (let i = 0; i < len; i++) {
  77. ok = rules[i](state, line, endLine, false)
  78. if (ok) {
  79. if (prevLine >= state.line) {
  80. throw new Error("block rule didn't increment state.line")
  81. }
  82. break
  83. }
  84. }
  85. // this can only happen if user disables paragraph rule
  86. if (!ok) throw new Error('none of the block rules matched')
  87. // set state.tight if we had an empty line before current tag
  88. // i.e. latest empty line should not count
  89. state.tight = !hasEmptyLines
  90. // paragraph might "eat" one newline after it in nested lists
  91. if (state.isEmpty(state.line - 1)) {
  92. hasEmptyLines = true
  93. }
  94. line = state.line
  95. if (line < endLine && state.isEmpty(line)) {
  96. hasEmptyLines = true
  97. line++
  98. state.line = line
  99. }
  100. }
  101. }
  102. /**
  103. * ParserBlock.parse(str, md, env, outTokens)
  104. *
  105. * Process input string and push block tokens into `outTokens`
  106. **/
  107. ParserBlock.prototype.parse = function (src, md, env, outTokens) {
  108. if (!src) { return }
  109. const state = new this.State(src, md, env, outTokens)
  110. this.tokenize(state, state.line, state.lineMax)
  111. }
  112. ParserBlock.prototype.State = StateBlock
  113. export default ParserBlock