|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import {factoryDestination} from 'micromark-factory-destination' |
|
import {factoryLabel} from 'micromark-factory-label' |
|
import {factoryTitle} from 'micromark-factory-title' |
|
import {factoryWhitespace} from 'micromark-factory-whitespace' |
|
import {markdownLineEndingOrSpace} from 'micromark-util-character' |
|
import {push, splice} from 'micromark-util-chunked' |
|
import {normalizeIdentifier} from 'micromark-util-normalize-identifier' |
|
import {resolveAll} from 'micromark-util-resolve-all' |
|
import {codes} from 'micromark-util-symbol/codes.js' |
|
import {constants} from 'micromark-util-symbol/constants.js' |
|
import {types} from 'micromark-util-symbol/types.js' |
|
import {ok as assert} from 'uvu/assert' |
|
|
|
|
|
export const labelEnd = { |
|
name: 'labelEnd', |
|
tokenize: tokenizeLabelEnd, |
|
resolveTo: resolveToLabelEnd, |
|
resolveAll: resolveAllLabelEnd |
|
} |
|
|
|
|
|
const resourceConstruct = {tokenize: tokenizeResource} |
|
|
|
const referenceFullConstruct = {tokenize: tokenizeReferenceFull} |
|
|
|
const referenceCollapsedConstruct = {tokenize: tokenizeReferenceCollapsed} |
|
|
|
|
|
function resolveAllLabelEnd(events) { |
|
let index = -1 |
|
|
|
while (++index < events.length) { |
|
const token = events[index][1] |
|
|
|
if ( |
|
token.type === types.labelImage || |
|
token.type === types.labelLink || |
|
token.type === types.labelEnd |
|
) { |
|
|
|
events.splice(index + 1, token.type === types.labelImage ? 4 : 2) |
|
token.type = types.data |
|
index++ |
|
} |
|
} |
|
|
|
return events |
|
} |
|
|
|
|
|
function resolveToLabelEnd(events, context) { |
|
let index = events.length |
|
let offset = 0 |
|
|
|
let token |
|
|
|
let open |
|
|
|
let close |
|
|
|
let media |
|
|
|
|
|
while (index--) { |
|
token = events[index][1] |
|
|
|
if (open) { |
|
|
|
if ( |
|
token.type === types.link || |
|
(token.type === types.labelLink && token._inactive) |
|
) { |
|
break |
|
} |
|
|
|
|
|
|
|
if (events[index][0] === 'enter' && token.type === types.labelLink) { |
|
token._inactive = true |
|
} |
|
} else if (close) { |
|
if ( |
|
events[index][0] === 'enter' && |
|
(token.type === types.labelImage || token.type === types.labelLink) && |
|
!token._balanced |
|
) { |
|
open = index |
|
|
|
if (token.type !== types.labelLink) { |
|
offset = 2 |
|
break |
|
} |
|
} |
|
} else if (token.type === types.labelEnd) { |
|
close = index |
|
} |
|
} |
|
|
|
assert(open !== undefined, '`open` is supposed to be found') |
|
assert(close !== undefined, '`close` is supposed to be found') |
|
|
|
const group = { |
|
type: events[open][1].type === types.labelLink ? types.link : types.image, |
|
start: Object.assign({}, events[open][1].start), |
|
end: Object.assign({}, events[events.length - 1][1].end) |
|
} |
|
|
|
const label = { |
|
type: types.label, |
|
start: Object.assign({}, events[open][1].start), |
|
end: Object.assign({}, events[close][1].end) |
|
} |
|
|
|
const text = { |
|
type: types.labelText, |
|
start: Object.assign({}, events[open + offset + 2][1].end), |
|
end: Object.assign({}, events[close - 2][1].start) |
|
} |
|
|
|
media = [ |
|
['enter', group, context], |
|
['enter', label, context] |
|
] |
|
|
|
|
|
media = push(media, events.slice(open + 1, open + offset + 3)) |
|
|
|
|
|
media = push(media, [['enter', text, context]]) |
|
|
|
|
|
assert( |
|
context.parser.constructs.insideSpan.null, |
|
'expected `insideSpan.null` to be populated' |
|
) |
|
|
|
media = push( |
|
media, |
|
resolveAll( |
|
context.parser.constructs.insideSpan.null, |
|
events.slice(open + offset + 4, close - 3), |
|
context |
|
) |
|
) |
|
|
|
|
|
media = push(media, [ |
|
['exit', text, context], |
|
events[close - 2], |
|
events[close - 1], |
|
['exit', label, context] |
|
]) |
|
|
|
|
|
media = push(media, events.slice(close + 1)) |
|
|
|
|
|
media = push(media, [['exit', group, context]]) |
|
|
|
splice(events, open, events.length, media) |
|
|
|
return events |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function tokenizeLabelEnd(effects, ok, nok) { |
|
const self = this |
|
let index = self.events.length |
|
|
|
let labelStart |
|
|
|
let defined |
|
|
|
|
|
while (index--) { |
|
if ( |
|
(self.events[index][1].type === types.labelImage || |
|
self.events[index][1].type === types.labelLink) && |
|
!self.events[index][1]._balanced |
|
) { |
|
labelStart = self.events[index][1] |
|
break |
|
} |
|
} |
|
|
|
return start |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function start(code) { |
|
assert(code === codes.rightSquareBracket, 'expected `]`') |
|
|
|
|
|
if (!labelStart) { |
|
return nok(code) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (labelStart._inactive) { |
|
return labelEndNok(code) |
|
} |
|
|
|
defined = self.parser.defined.includes( |
|
normalizeIdentifier( |
|
self.sliceSerialize({start: labelStart.end, end: self.now()}) |
|
) |
|
) |
|
effects.enter(types.labelEnd) |
|
effects.enter(types.labelMarker) |
|
effects.consume(code) |
|
effects.exit(types.labelMarker) |
|
effects.exit(types.labelEnd) |
|
return after |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function after(code) { |
|
|
|
|
|
|
|
|
|
if (code === codes.leftParenthesis) { |
|
return effects.attempt( |
|
resourceConstruct, |
|
labelEndOk, |
|
defined ? labelEndOk : labelEndNok |
|
)(code) |
|
} |
|
|
|
|
|
if (code === codes.leftSquareBracket) { |
|
return effects.attempt( |
|
referenceFullConstruct, |
|
labelEndOk, |
|
defined ? referenceNotFull : labelEndNok |
|
)(code) |
|
} |
|
|
|
|
|
return defined ? labelEndOk(code) : labelEndNok(code) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function referenceNotFull(code) { |
|
return effects.attempt( |
|
referenceCollapsedConstruct, |
|
labelEndOk, |
|
labelEndNok |
|
)(code) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function labelEndOk(code) { |
|
|
|
return ok(code) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function labelEndNok(code) { |
|
labelStart._balanced = true |
|
return nok(code) |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function tokenizeResource(effects, ok, nok) { |
|
return resourceStart |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function resourceStart(code) { |
|
assert(code === codes.leftParenthesis, 'expected left paren') |
|
effects.enter(types.resource) |
|
effects.enter(types.resourceMarker) |
|
effects.consume(code) |
|
effects.exit(types.resourceMarker) |
|
return resourceBefore |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function resourceBefore(code) { |
|
return markdownLineEndingOrSpace(code) |
|
? factoryWhitespace(effects, resourceOpen)(code) |
|
: resourceOpen(code) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function resourceOpen(code) { |
|
if (code === codes.rightParenthesis) { |
|
return resourceEnd(code) |
|
} |
|
|
|
return factoryDestination( |
|
effects, |
|
resourceDestinationAfter, |
|
resourceDestinationMissing, |
|
types.resourceDestination, |
|
types.resourceDestinationLiteral, |
|
types.resourceDestinationLiteralMarker, |
|
types.resourceDestinationRaw, |
|
types.resourceDestinationString, |
|
constants.linkResourceDestinationBalanceMax |
|
)(code) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function resourceDestinationAfter(code) { |
|
return markdownLineEndingOrSpace(code) |
|
? factoryWhitespace(effects, resourceBetween)(code) |
|
: resourceEnd(code) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function resourceDestinationMissing(code) { |
|
return nok(code) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function resourceBetween(code) { |
|
if ( |
|
code === codes.quotationMark || |
|
code === codes.apostrophe || |
|
code === codes.leftParenthesis |
|
) { |
|
return factoryTitle( |
|
effects, |
|
resourceTitleAfter, |
|
nok, |
|
types.resourceTitle, |
|
types.resourceTitleMarker, |
|
types.resourceTitleString |
|
)(code) |
|
} |
|
|
|
return resourceEnd(code) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function resourceTitleAfter(code) { |
|
return markdownLineEndingOrSpace(code) |
|
? factoryWhitespace(effects, resourceEnd)(code) |
|
: resourceEnd(code) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function resourceEnd(code) { |
|
if (code === codes.rightParenthesis) { |
|
effects.enter(types.resourceMarker) |
|
effects.consume(code) |
|
effects.exit(types.resourceMarker) |
|
effects.exit(types.resource) |
|
return ok |
|
} |
|
|
|
return nok(code) |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function tokenizeReferenceFull(effects, ok, nok) { |
|
const self = this |
|
|
|
return referenceFull |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function referenceFull(code) { |
|
assert(code === codes.leftSquareBracket, 'expected left bracket') |
|
return factoryLabel.call( |
|
self, |
|
effects, |
|
referenceFullAfter, |
|
referenceFullMissing, |
|
types.reference, |
|
types.referenceMarker, |
|
types.referenceString |
|
)(code) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function referenceFullAfter(code) { |
|
return self.parser.defined.includes( |
|
normalizeIdentifier( |
|
self.sliceSerialize(self.events[self.events.length - 1][1]).slice(1, -1) |
|
) |
|
) |
|
? ok(code) |
|
: nok(code) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function referenceFullMissing(code) { |
|
return nok(code) |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function tokenizeReferenceCollapsed(effects, ok, nok) { |
|
return referenceCollapsedStart |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function referenceCollapsedStart(code) { |
|
|
|
assert(code === codes.leftSquareBracket, 'expected left bracket') |
|
effects.enter(types.reference) |
|
effects.enter(types.referenceMarker) |
|
effects.consume(code) |
|
effects.exit(types.referenceMarker) |
|
return referenceCollapsedOpen |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function referenceCollapsedOpen(code) { |
|
if (code === codes.rightSquareBracket) { |
|
effects.enter(types.referenceMarker) |
|
effects.consume(code) |
|
effects.exit(types.referenceMarker) |
|
effects.exit(types.reference) |
|
return ok |
|
} |
|
|
|
return nok(code) |
|
} |
|
} |
|
|