You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
350 lines
8.3 KiB
350 lines
8.3 KiB
var labelEnd = { |
|
name: 'labelEnd', |
|
tokenize: tokenizeLabelEnd, |
|
resolveTo: resolveToLabelEnd, |
|
resolveAll: resolveAllLabelEnd |
|
} |
|
export default labelEnd |
|
|
|
import assert from 'assert' |
|
import codes from '../character/codes.mjs' |
|
import markdownLineEndingOrSpace from '../character/markdown-line-ending-or-space.mjs' |
|
import constants from '../constant/constants.mjs' |
|
import types from '../constant/types.mjs' |
|
import chunkedPush from '../util/chunked-push.mjs' |
|
import chunkedSplice from '../util/chunked-splice.mjs' |
|
import normalizeIdentifier from '../util/normalize-identifier.mjs' |
|
import resolveAll from '../util/resolve-all.mjs' |
|
import shallow from '../util/shallow.mjs' |
|
import destinationFactory from './factory-destination.mjs' |
|
import labelFactory from './factory-label.mjs' |
|
import titleFactory from './factory-title.mjs' |
|
import whitespaceFactory from './factory-whitespace.mjs' |
|
|
|
var resourceConstruct = {tokenize: tokenizeResource} |
|
var fullReferenceConstruct = {tokenize: tokenizeFullReference} |
|
var collapsedReferenceConstruct = {tokenize: tokenizeCollapsedReference} |
|
|
|
function resolveAllLabelEnd(events) { |
|
var index = -1 |
|
var token |
|
|
|
while (++index < events.length) { |
|
token = events[index][1] |
|
|
|
if ( |
|
!token._used && |
|
(token.type === types.labelImage || |
|
token.type === types.labelLink || |
|
token.type === types.labelEnd) |
|
) { |
|
// Remove the marker. |
|
events.splice(index + 1, token.type === types.labelImage ? 4 : 2) |
|
token.type = types.data |
|
index++ |
|
} |
|
} |
|
|
|
return events |
|
} |
|
|
|
function resolveToLabelEnd(events, context) { |
|
var index = events.length |
|
var offset = 0 |
|
var group |
|
var label |
|
var text |
|
var token |
|
var open |
|
var close |
|
var media |
|
|
|
// Find an opening. |
|
while (index--) { |
|
token = events[index][1] |
|
|
|
if (open) { |
|
// If we see another link, or inactive link label, we’ve been here before. |
|
if ( |
|
token.type === types.link || |
|
(token.type === types.labelLink && token._inactive) |
|
) { |
|
break |
|
} |
|
|
|
// Mark other link openings as inactive, as we can’t have links in |
|
// links. |
|
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 |
|
} |
|
} |
|
|
|
group = { |
|
type: events[open][1].type === types.labelLink ? types.link : types.image, |
|
start: shallow(events[open][1].start), |
|
end: shallow(events[events.length - 1][1].end) |
|
} |
|
|
|
label = { |
|
type: types.label, |
|
start: shallow(events[open][1].start), |
|
end: shallow(events[close][1].end) |
|
} |
|
|
|
text = { |
|
type: types.labelText, |
|
start: shallow(events[open + offset + 2][1].end), |
|
end: shallow(events[close - 2][1].start) |
|
} |
|
|
|
media = [ |
|
['enter', group, context], |
|
['enter', label, context] |
|
] |
|
|
|
// Opening marker. |
|
media = chunkedPush(media, events.slice(open + 1, open + offset + 3)) |
|
|
|
// Text open. |
|
media = chunkedPush(media, [['enter', text, context]]) |
|
|
|
// Between. |
|
media = chunkedPush( |
|
media, |
|
resolveAll( |
|
context.parser.constructs.insideSpan.null, |
|
events.slice(open + offset + 4, close - 3), |
|
context |
|
) |
|
) |
|
|
|
// Text close, marker close, label close. |
|
media = chunkedPush(media, [ |
|
['exit', text, context], |
|
events[close - 2], |
|
events[close - 1], |
|
['exit', label, context] |
|
]) |
|
|
|
// Reference, resource, or so. |
|
media = chunkedPush(media, events.slice(close + 1)) |
|
|
|
// Media close. |
|
media = chunkedPush(media, [['exit', group, context]]) |
|
|
|
chunkedSplice(events, open, events.length, media) |
|
|
|
return events |
|
} |
|
|
|
function tokenizeLabelEnd(effects, ok, nok) { |
|
var self = this |
|
var index = self.events.length |
|
var labelStart |
|
var defined |
|
|
|
// Find an opening. |
|
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) |
|
} |
|
|
|
// It’s a balanced bracket, but contains a link. |
|
if (labelStart._inactive) return balanced(code) |
|
defined = |
|
self.parser.defined.indexOf( |
|
normalizeIdentifier( |
|
self.sliceSerialize({start: labelStart.end, end: self.now()}) |
|
) |
|
) > -1 |
|
effects.enter(types.labelEnd) |
|
effects.enter(types.labelMarker) |
|
effects.consume(code) |
|
effects.exit(types.labelMarker) |
|
effects.exit(types.labelEnd) |
|
return afterLabelEnd |
|
} |
|
|
|
function afterLabelEnd(code) { |
|
// Resource: `[asd](fgh)`. |
|
if (code === codes.leftParenthesis) { |
|
return effects.attempt( |
|
resourceConstruct, |
|
ok, |
|
defined ? ok : balanced |
|
)(code) |
|
} |
|
|
|
// Collapsed (`[asd][]`) or full (`[asd][fgh]`) reference? |
|
if (code === codes.leftSquareBracket) { |
|
return effects.attempt( |
|
fullReferenceConstruct, |
|
ok, |
|
defined |
|
? effects.attempt(collapsedReferenceConstruct, ok, balanced) |
|
: balanced |
|
)(code) |
|
} |
|
|
|
// Shortcut reference: `[asd]`? |
|
return defined ? ok(code) : balanced(code) |
|
} |
|
|
|
function balanced(code) { |
|
labelStart._balanced = true |
|
return nok(code) |
|
} |
|
} |
|
|
|
function tokenizeResource(effects, ok, nok) { |
|
return start |
|
|
|
function start(code) { |
|
assert.equal(code, codes.leftParenthesis, 'expected left paren') |
|
effects.enter(types.resource) |
|
effects.enter(types.resourceMarker) |
|
effects.consume(code) |
|
effects.exit(types.resourceMarker) |
|
return whitespaceFactory(effects, open) |
|
} |
|
|
|
function open(code) { |
|
if (code === codes.rightParenthesis) { |
|
return end(code) |
|
} |
|
|
|
return destinationFactory( |
|
effects, |
|
destinationAfter, |
|
nok, |
|
types.resourceDestination, |
|
types.resourceDestinationLiteral, |
|
types.resourceDestinationLiteralMarker, |
|
types.resourceDestinationRaw, |
|
types.resourceDestinationString, |
|
constants.linkResourceDestinationBalanceMax |
|
)(code) |
|
} |
|
|
|
function destinationAfter(code) { |
|
return markdownLineEndingOrSpace(code) |
|
? whitespaceFactory(effects, between)(code) |
|
: end(code) |
|
} |
|
|
|
function between(code) { |
|
if ( |
|
code === codes.quotationMark || |
|
code === codes.apostrophe || |
|
code === codes.leftParenthesis |
|
) { |
|
return titleFactory( |
|
effects, |
|
whitespaceFactory(effects, end), |
|
nok, |
|
types.resourceTitle, |
|
types.resourceTitleMarker, |
|
types.resourceTitleString |
|
)(code) |
|
} |
|
|
|
return end(code) |
|
} |
|
|
|
function end(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 tokenizeFullReference(effects, ok, nok) { |
|
var self = this |
|
|
|
return start |
|
|
|
function start(code) { |
|
assert.equal(code, codes.leftSquareBracket, 'expected left bracket') |
|
return labelFactory.call( |
|
self, |
|
effects, |
|
afterLabel, |
|
nok, |
|
types.reference, |
|
types.referenceMarker, |
|
types.referenceString |
|
)(code) |
|
} |
|
|
|
function afterLabel(code) { |
|
return self.parser.defined.indexOf( |
|
normalizeIdentifier( |
|
self.sliceSerialize(self.events[self.events.length - 1][1]).slice(1, -1) |
|
) |
|
) < 0 |
|
? nok(code) |
|
: ok(code) |
|
} |
|
} |
|
|
|
function tokenizeCollapsedReference(effects, ok, nok) { |
|
return start |
|
|
|
function start(code) { |
|
assert.equal(code, codes.leftSquareBracket, 'expected left bracket') |
|
effects.enter(types.reference) |
|
effects.enter(types.referenceMarker) |
|
effects.consume(code) |
|
effects.exit(types.referenceMarker) |
|
return open |
|
} |
|
|
|
function open(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) |
|
} |
|
}
|
|
|