/** internal * class ParserBlock * * Block-level tokenizer. **/ import Ruler from './ruler.mjs' import StateBlock from './rules_block/state_block.mjs' import r_table from './rules_block/table.mjs' import r_code from './rules_block/code.mjs' import r_fence from './rules_block/fence.mjs' import r_blockquote from './rules_block/blockquote.mjs' import r_hr from './rules_block/hr.mjs' import r_list from './rules_block/list.mjs' import r_reference from './rules_block/reference.mjs' import r_html_block from './rules_block/html_block.mjs' import r_heading from './rules_block/heading.mjs' import r_lheading from './rules_block/lheading.mjs' import r_paragraph from './rules_block/paragraph.mjs' const _rules = [ // First 2 params - rule name & source. Secondary array - list of rules, // which can be terminated by this one. ['table', r_table, ['paragraph', 'reference']], ['code', r_code], ['fence', r_fence, ['paragraph', 'reference', 'blockquote', 'list']], ['blockquote', r_blockquote, ['paragraph', 'reference', 'blockquote', 'list']], ['hr', r_hr, ['paragraph', 'reference', 'blockquote', 'list']], ['list', r_list, ['paragraph', 'reference', 'blockquote']], ['reference', r_reference], ['html_block', r_html_block, ['paragraph', 'reference', 'blockquote']], ['heading', r_heading, ['paragraph', 'reference', 'blockquote']], ['lheading', r_lheading], ['paragraph', r_paragraph] ] /** * new ParserBlock() **/ function ParserBlock () { /** * ParserBlock#ruler -> Ruler * * [[Ruler]] instance. Keep configuration of block rules. **/ this.ruler = new Ruler() for (let i = 0; i < _rules.length; i++) { this.ruler.push(_rules[i][0], _rules[i][1], { alt: (_rules[i][2] || []).slice() }) } } // Generate tokens for input range // ParserBlock.prototype.tokenize = function (state, startLine, endLine) { const rules = this.ruler.getRules('') const len = rules.length const maxNesting = state.md.options.maxNesting let line = startLine let hasEmptyLines = false while (line < endLine) { state.line = line = state.skipEmptyLines(line) if (line >= endLine) { break } // Termination condition for nested calls. // Nested calls currently used for blockquotes & lists if (state.sCount[line] < state.blkIndent) { break } // If nesting level exceeded - skip tail to the end. That's not ordinary // situation and we should not care about content. if (state.level >= maxNesting) { state.line = endLine break } // Try all possible rules. // On success, rule should: // // - update `state.line` // - update `state.tokens` // - return true const prevLine = state.line let ok = false for (let i = 0; i < len; i++) { ok = rules[i](state, line, endLine, false) if (ok) { if (prevLine >= state.line) { throw new Error("block rule didn't increment state.line") } break } } // this can only happen if user disables paragraph rule if (!ok) throw new Error('none of the block rules matched') // set state.tight if we had an empty line before current tag // i.e. latest empty line should not count state.tight = !hasEmptyLines // paragraph might "eat" one newline after it in nested lists if (state.isEmpty(state.line - 1)) { hasEmptyLines = true } line = state.line if (line < endLine && state.isEmpty(line)) { hasEmptyLines = true line++ state.line = line } } } /** * ParserBlock.parse(str, md, env, outTokens) * * Process input string and push block tokens into `outTokens` **/ ParserBlock.prototype.parse = function (src, md, env, outTokens) { if (!src) { return } const state = new this.State(src, md, env, outTokens) this.tokenize(state, state.line, state.lineMax) } ParserBlock.prototype.State = StateBlock export default ParserBlock