/** internal * class ParserInline * * Tokenizes paragraph content. **/ import Ruler from './ruler.mjs' import StateInline from './rules_inline/state_inline.mjs' import r_text from './rules_inline/text.mjs' import r_linkify from './rules_inline/linkify.mjs' import r_newline from './rules_inline/newline.mjs' import r_escape from './rules_inline/escape.mjs' import r_backticks from './rules_inline/backticks.mjs' import r_strikethrough from './rules_inline/strikethrough.mjs' import r_emphasis from './rules_inline/emphasis.mjs' import r_link from './rules_inline/link.mjs' import r_image from './rules_inline/image.mjs' import r_autolink from './rules_inline/autolink.mjs' import r_html_inline from './rules_inline/html_inline.mjs' import r_entity from './rules_inline/entity.mjs' import r_balance_pairs from './rules_inline/balance_pairs.mjs' import r_fragments_join from './rules_inline/fragments_join.mjs' // Parser rules const _rules = [ ['text', r_text], ['linkify', r_linkify], ['newline', r_newline], ['escape', r_escape], ['backticks', r_backticks], ['strikethrough', r_strikethrough.tokenize], ['emphasis', r_emphasis.tokenize], ['link', r_link], ['image', r_image], ['autolink', r_autolink], ['html_inline', r_html_inline], ['entity', r_entity] ] // `rule2` ruleset was created specifically for emphasis/strikethrough // post-processing and may be changed in the future. // // Don't use this for anything except pairs (plugins working with `balance_pairs`). // const _rules2 = [ ['balance_pairs', r_balance_pairs], ['strikethrough', r_strikethrough.postProcess], ['emphasis', r_emphasis.postProcess], // rules for pairs separate '**' into its own text tokens, which may be left unused, // rule below merges unused segments back with the rest of the text ['fragments_join', r_fragments_join] ] /** * new ParserInline() **/ function ParserInline () { /** * ParserInline#ruler -> Ruler * * [[Ruler]] instance. Keep configuration of inline rules. **/ this.ruler = new Ruler() for (let i = 0; i < _rules.length; i++) { this.ruler.push(_rules[i][0], _rules[i][1]) } /** * ParserInline#ruler2 -> Ruler * * [[Ruler]] instance. Second ruler used for post-processing * (e.g. in emphasis-like rules). **/ this.ruler2 = new Ruler() for (let i = 0; i < _rules2.length; i++) { this.ruler2.push(_rules2[i][0], _rules2[i][1]) } } // Skip single token by running all rules in validation mode; // returns `true` if any rule reported success // ParserInline.prototype.skipToken = function (state) { const pos = state.pos const rules = this.ruler.getRules('') const len = rules.length const maxNesting = state.md.options.maxNesting const cache = state.cache if (typeof cache[pos] !== 'undefined') { state.pos = cache[pos] return } let ok = false if (state.level < maxNesting) { for (let i = 0; i < len; i++) { // Increment state.level and decrement it later to limit recursion. // It's harmless to do here, because no tokens are created. But ideally, // we'd need a separate private state variable for this purpose. // state.level++ ok = rules[i](state, true) state.level-- if (ok) { if (pos >= state.pos) { throw new Error("inline rule didn't increment state.pos") } break } } } else { // Too much nesting, just skip until the end of the paragraph. // // NOTE: this will cause links to behave incorrectly in the following case, // when an amount of `[` is exactly equal to `maxNesting + 1`: // // [[[[[[[[[[[[[[[[[[[[[foo]() // // TODO: remove this workaround when CM standard will allow nested links // (we can replace it by preventing links from being parsed in // validation mode) // state.pos = state.posMax } if (!ok) { state.pos++ } cache[pos] = state.pos } // Generate tokens for input range // ParserInline.prototype.tokenize = function (state) { const rules = this.ruler.getRules('') const len = rules.length const end = state.posMax const maxNesting = state.md.options.maxNesting while (state.pos < end) { // Try all possible rules. // On success, rule should: // // - update `state.pos` // - update `state.tokens` // - return true const prevPos = state.pos let ok = false if (state.level < maxNesting) { for (let i = 0; i < len; i++) { ok = rules[i](state, false) if (ok) { if (prevPos >= state.pos) { throw new Error("inline rule didn't increment state.pos") } break } } } if (ok) { if (state.pos >= end) { break } continue } state.pending += state.src[state.pos++] } if (state.pending) { state.pushPending() } } /** * ParserInline.parse(str, md, env, outTokens) * * Process input string and push inline tokens into `outTokens` **/ ParserInline.prototype.parse = function (str, md, env, outTokens) { const state = new this.State(str, md, env, outTokens) this.tokenize(state) const rules = this.ruler2.getRules('') const len = rules.length for (let i = 0; i < len; i++) { rules[i](state) } } ParserInline.prototype.State = StateInline export default ParserInline