123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- /**
- * @typedef {import('micromark-util-types').Code} Code
- * @typedef {import('micromark-util-types').InitialConstruct} InitialConstruct
- * @typedef {import('micromark-util-types').Initializer} Initializer
- * @typedef {import('micromark-util-types').Resolver} Resolver
- * @typedef {import('micromark-util-types').State} State
- * @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext
- */
- export const resolver = {
- resolveAll: createResolver()
- }
- export const string = initializeFactory('string')
- export const text = initializeFactory('text')
- /**
- * @param {'string' | 'text'} field
- * @returns {InitialConstruct}
- */
- function initializeFactory(field) {
- return {
- tokenize: initializeText,
- resolveAll: createResolver(
- field === 'text' ? resolveAllLineSuffixes : undefined
- )
- }
- /**
- * @this {TokenizeContext}
- * @type {Initializer}
- */
- function initializeText(effects) {
- const self = this
- const constructs = this.parser.constructs[field]
- const text = effects.attempt(constructs, start, notText)
- return start
- /** @type {State} */
- function start(code) {
- return atBreak(code) ? text(code) : notText(code)
- }
- /** @type {State} */
- function notText(code) {
- if (code === null) {
- effects.consume(code)
- return
- }
- effects.enter('data')
- effects.consume(code)
- return data
- }
- /** @type {State} */
- function data(code) {
- if (atBreak(code)) {
- effects.exit('data')
- return text(code)
- }
- // Data.
- effects.consume(code)
- return data
- }
- /**
- * @param {Code} code
- * @returns {boolean}
- */
- function atBreak(code) {
- if (code === null) {
- return true
- }
- const list = constructs[code]
- let index = -1
- if (list) {
- // Always populated by defaults.
- while (++index < list.length) {
- const item = list[index]
- if (!item.previous || item.previous.call(self, self.previous)) {
- return true
- }
- }
- }
- return false
- }
- }
- }
- /**
- * @param {Resolver | undefined} [extraResolver]
- * @returns {Resolver}
- */
- function createResolver(extraResolver) {
- return resolveAllText
- /** @type {Resolver} */
- function resolveAllText(events, context) {
- let index = -1
- /** @type {number | undefined} */
- let enter
- // A rather boring computation (to merge adjacent `data` events) which
- // improves mm performance by 29%.
- while (++index <= events.length) {
- if (enter === undefined) {
- if (events[index] && events[index][1].type === 'data') {
- enter = index
- index++
- }
- } else if (!events[index] || events[index][1].type !== 'data') {
- // Don’t do anything if there is one data token.
- if (index !== enter + 2) {
- events[enter][1].end = events[index - 1][1].end
- events.splice(enter + 2, index - enter - 2)
- index = enter + 2
- }
- enter = undefined
- }
- }
- return extraResolver ? extraResolver(events, context) : events
- }
- }
- /**
- * A rather ugly set of instructions which again looks at chunks in the input
- * stream.
- * The reason to do this here is that it is *much* faster to parse in reverse.
- * And that we can’t hook into `null` to split the line suffix before an EOF.
- * To do: figure out if we can make this into a clean utility, or even in core.
- * As it will be useful for GFMs literal autolink extension (and maybe even
- * tables?)
- *
- * @type {Resolver}
- */
- function resolveAllLineSuffixes(events, context) {
- let eventIndex = 0 // Skip first.
- while (++eventIndex <= events.length) {
- if (
- (eventIndex === events.length ||
- events[eventIndex][1].type === 'lineEnding') &&
- events[eventIndex - 1][1].type === 'data'
- ) {
- const data = events[eventIndex - 1][1]
- const chunks = context.sliceStream(data)
- let index = chunks.length
- let bufferIndex = -1
- let size = 0
- /** @type {boolean | undefined} */
- let tabs
- while (index--) {
- const chunk = chunks[index]
- if (typeof chunk === 'string') {
- bufferIndex = chunk.length
- while (chunk.charCodeAt(bufferIndex - 1) === 32) {
- size++
- bufferIndex--
- }
- if (bufferIndex) break
- bufferIndex = -1
- }
- // Number
- else if (chunk === -2) {
- tabs = true
- size++
- } else if (chunk === -1) {
- // Empty
- } else {
- // Replacement character, exit.
- index++
- break
- }
- }
- if (size) {
- const token = {
- type:
- eventIndex === events.length || tabs || size < 2
- ? 'lineSuffix'
- : 'hardBreakTrailing',
- start: {
- line: data.end.line,
- column: data.end.column - size,
- offset: data.end.offset - size,
- _index: data.start._index + index,
- _bufferIndex: index
- ? bufferIndex
- : data.start._bufferIndex + bufferIndex
- },
- end: Object.assign({}, data.end)
- }
- data.end = Object.assign({}, token.start)
- if (data.start.offset === data.end.offset) {
- Object.assign(data, token)
- } else {
- events.splice(
- eventIndex,
- 0,
- ['enter', token, context],
- ['exit', token, context]
- )
- eventIndex += 2
- }
- }
- eventIndex++
- }
- }
- return events
- }
|