AK-21/Graphite-Industrial-Intelligence
0
1/**2 * @import {3 * Construct,4 * Event,5 * Resolver,6 * State,7 * TokenizeContext,8 * Tokenizer,9 * Token10 * } from 'micromark-util-types'11 */12 13import { factoryDestination } from 'micromark-factory-destination';14import { factoryLabel } from 'micromark-factory-label';15import { factoryTitle } from 'micromark-factory-title';16import { factoryWhitespace } from 'micromark-factory-whitespace';17import { markdownLineEndingOrSpace } from 'micromark-util-character';18import { push, splice } from 'micromark-util-chunked';19import { normalizeIdentifier } from 'micromark-util-normalize-identifier';20import { resolveAll } from 'micromark-util-resolve-all';21/** @type {Construct} */22export const labelEnd = {23 name: 'labelEnd',24 resolveAll: resolveAllLabelEnd,25 resolveTo: resolveToLabelEnd,26 tokenize: tokenizeLabelEnd27};28 29/** @type {Construct} */30const resourceConstruct = {31 tokenize: tokenizeResource32};33/** @type {Construct} */34const referenceFullConstruct = {35 tokenize: tokenizeReferenceFull36};37/** @type {Construct} */38const referenceCollapsedConstruct = {39 tokenize: tokenizeReferenceCollapsed40};41 42/** @type {Resolver} */43function resolveAllLabelEnd(events) {44 let index = -1;45 /** @type {Array<Event>} */46 const newEvents = [];47 while (++index < events.length) {48 const token = events[index][1];49 newEvents.push(events[index]);50 if (token.type === "labelImage" || token.type === "labelLink" || token.type === "labelEnd") {51 // Remove the marker.52 const offset = token.type === "labelImage" ? 4 : 2;53 token.type = "data";54 index += offset;55 }56 }57 58 // If the events are equal, we don't have to copy newEvents to events59 if (events.length !== newEvents.length) {60 splice(events, 0, events.length, newEvents);61 }62 return events;63}64 65/** @type {Resolver} */66function resolveToLabelEnd(events, context) {67 let index = events.length;68 let offset = 0;69 /** @type {Token} */70 let token;71 /** @type {number | undefined} */72 let open;73 /** @type {number | undefined} */74 let close;75 /** @type {Array<Event>} */76 let media;77 78 // Find an opening.79 while (index--) {80 token = events[index][1];81 if (open) {82 // If we see another link, or inactive link label, we’ve been here before.83 if (token.type === "link" || token.type === "labelLink" && token._inactive) {84 break;85 }86 87 // Mark other link openings as inactive, as we can’t have links in88 // links.89 if (events[index][0] === 'enter' && token.type === "labelLink") {90 token._inactive = true;91 }92 } else if (close) {93 if (events[index][0] === 'enter' && (token.type === "labelImage" || token.type === "labelLink") && !token._balanced) {94 open = index;95 if (token.type !== "labelLink") {96 offset = 2;97 break;98 }99 }100 } else if (token.type === "labelEnd") {101 close = index;102 }103 }104 const group = {105 type: events[open][1].type === "labelLink" ? "link" : "image",106 start: {107 ...events[open][1].start108 },109 end: {110 ...events[events.length - 1][1].end111 }112 };113 const label = {114 type: "label",115 start: {116 ...events[open][1].start117 },118 end: {119 ...events[close][1].end120 }121 };122 const text = {123 type: "labelText",124 start: {125 ...events[open + offset + 2][1].end126 },127 end: {128 ...events[close - 2][1].start129 }130 };131 media = [['enter', group, context], ['enter', label, context]];132 133 // Opening marker.134 media = push(media, events.slice(open + 1, open + offset + 3));135 136 // Text open.137 media = push(media, [['enter', text, context]]);138 139 // Always populated by defaults.140 141 // Between.142 media = push(media, resolveAll(context.parser.constructs.insideSpan.null, events.slice(open + offset + 4, close - 3), context));143 144 // Text close, marker close, label close.145 media = push(media, [['exit', text, context], events[close - 2], events[close - 1], ['exit', label, context]]);146 147 // Reference, resource, or so.148 media = push(media, events.slice(close + 1));149 150 // Media close.151 media = push(media, [['exit', group, context]]);152 splice(events, open, events.length, media);153 return events;154}155 156/**157 * @this {TokenizeContext}158 * Context.159 * @type {Tokenizer}160 */161function tokenizeLabelEnd(effects, ok, nok) {162 const self = this;163 let index = self.events.length;164 /** @type {Token} */165 let labelStart;166 /** @type {boolean} */167 let defined;168 169 // Find an opening.170 while (index--) {171 if ((self.events[index][1].type === "labelImage" || self.events[index][1].type === "labelLink") && !self.events[index][1]._balanced) {172 labelStart = self.events[index][1];173 break;174 }175 }176 return start;177 178 /**179 * Start of label end.180 *181 * ```markdown182 * > | [a](b) c183 * ^184 * > | [a][b] c185 * ^186 * > | [a][] b187 * ^188 * > | [a] b189 * ```190 *191 * @type {State}192 */193 function start(code) {194 // If there is not an okay opening.195 if (!labelStart) {196 return nok(code);197 }198 199 // If the corresponding label (link) start is marked as inactive,200 // it means we’d be wrapping a link, like this:201 //202 // ```markdown203 // > | a [b [c](d) e](f) g.204 // ^205 // ```206 //207 // We can’t have that, so it’s just balanced brackets.208 if (labelStart._inactive) {209 return labelEndNok(code);210 }211 defined = self.parser.defined.includes(normalizeIdentifier(self.sliceSerialize({212 start: labelStart.end,213 end: self.now()214 })));215 effects.enter("labelEnd");216 effects.enter("labelMarker");217 effects.consume(code);218 effects.exit("labelMarker");219 effects.exit("labelEnd");220 return after;221 }222 223 /**224 * After `]`.225 *226 * ```markdown227 * > | [a](b) c228 * ^229 * > | [a][b] c230 * ^231 * > | [a][] b232 * ^233 * > | [a] b234 * ^235 * ```236 *237 * @type {State}238 */239 function after(code) {240 // Note: `markdown-rs` also parses GFM footnotes here, which for us is in241 // an extension.242 243 // Resource (`[asd](fgh)`)?244 if (code === 40) {245 return effects.attempt(resourceConstruct, labelEndOk, defined ? labelEndOk : labelEndNok)(code);246 }247 248 // Full (`[asd][fgh]`) or collapsed (`[asd][]`) reference?249 if (code === 91) {250 return effects.attempt(referenceFullConstruct, labelEndOk, defined ? referenceNotFull : labelEndNok)(code);251 }252 253 // Shortcut (`[asd]`) reference?254 return defined ? labelEndOk(code) : labelEndNok(code);255 }256 257 /**258 * After `]`, at `[`, but not at a full reference.259 *260 * > 👉 **Note**: we only get here if the label is defined.261 *262 * ```markdown263 * > | [a][] b264 * ^265 * > | [a] b266 * ^267 * ```268 *269 * @type {State}270 */271 function referenceNotFull(code) {272 return effects.attempt(referenceCollapsedConstruct, labelEndOk, labelEndNok)(code);273 }274 275 /**276 * Done, we found something.277 *278 * ```markdown279 * > | [a](b) c280 * ^281 * > | [a][b] c282 * ^283 * > | [a][] b284 * ^285 * > | [a] b286 * ^287 * ```288 *289 * @type {State}290 */291 function labelEndOk(code) {292 // Note: `markdown-rs` does a bunch of stuff here.293 return ok(code);294 }295 296 /**297 * Done, it’s nothing.298 *299 * There was an okay opening, but we didn’t match anything.300 *301 * ```markdown302 * > | [a](b c303 * ^304 * > | [a][b c305 * ^306 * > | [a] b307 * ^308 * ```309 *310 * @type {State}311 */312 function labelEndNok(code) {313 labelStart._balanced = true;314 return nok(code);315 }316}317 318/**319 * @this {TokenizeContext}320 * Context.321 * @type {Tokenizer}322 */323function tokenizeResource(effects, ok, nok) {324 return resourceStart;325 326 /**327 * At a resource.328 *329 * ```markdown330 * > | [a](b) c331 * ^332 * ```333 *334 * @type {State}335 */336 function resourceStart(code) {337 effects.enter("resource");338 effects.enter("resourceMarker");339 effects.consume(code);340 effects.exit("resourceMarker");341 return resourceBefore;342 }343 344 /**345 * In resource, after `(`, at optional whitespace.346 *347 * ```markdown348 * > | [a](b) c349 * ^350 * ```351 *352 * @type {State}353 */354 function resourceBefore(code) {355 return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceOpen)(code) : resourceOpen(code);356 }357 358 /**359 * In resource, after optional whitespace, at `)` or a destination.360 *361 * ```markdown362 * > | [a](b) c363 * ^364 * ```365 *366 * @type {State}367 */368 function resourceOpen(code) {369 if (code === 41) {370 return resourceEnd(code);371 }372 return factoryDestination(effects, resourceDestinationAfter, resourceDestinationMissing, "resourceDestination", "resourceDestinationLiteral", "resourceDestinationLiteralMarker", "resourceDestinationRaw", "resourceDestinationString", 32)(code);373 }374 375 /**376 * In resource, after destination, at optional whitespace.377 *378 * ```markdown379 * > | [a](b) c380 * ^381 * ```382 *383 * @type {State}384 */385 function resourceDestinationAfter(code) {386 return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceBetween)(code) : resourceEnd(code);387 }388 389 /**390 * At invalid destination.391 *392 * ```markdown393 * > | [a](<<) b394 * ^395 * ```396 *397 * @type {State}398 */399 function resourceDestinationMissing(code) {400 return nok(code);401 }402 403 /**404 * In resource, after destination and whitespace, at `(` or title.405 *406 * ```markdown407 * > | [a](b ) c408 * ^409 * ```410 *411 * @type {State}412 */413 function resourceBetween(code) {414 if (code === 34 || code === 39 || code === 40) {415 return factoryTitle(effects, resourceTitleAfter, nok, "resourceTitle", "resourceTitleMarker", "resourceTitleString")(code);416 }417 return resourceEnd(code);418 }419 420 /**421 * In resource, after title, at optional whitespace.422 *423 * ```markdown424 * > | [a](b "c") d425 * ^426 * ```427 *428 * @type {State}429 */430 function resourceTitleAfter(code) {431 return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceEnd)(code) : resourceEnd(code);432 }433 434 /**435 * In resource, at `)`.436 *437 * ```markdown438 * > | [a](b) d439 * ^440 * ```441 *442 * @type {State}443 */444 function resourceEnd(code) {445 if (code === 41) {446 effects.enter("resourceMarker");447 effects.consume(code);448 effects.exit("resourceMarker");449 effects.exit("resource");450 return ok;451 }452 return nok(code);453 }454}455 456/**457 * @this {TokenizeContext}458 * Context.459 * @type {Tokenizer}460 */461function tokenizeReferenceFull(effects, ok, nok) {462 const self = this;463 return referenceFull;464 465 /**466 * In a reference (full), at the `[`.467 *468 * ```markdown469 * > | [a][b] d470 * ^471 * ```472 *473 * @type {State}474 */475 function referenceFull(code) {476 return factoryLabel.call(self, effects, referenceFullAfter, referenceFullMissing, "reference", "referenceMarker", "referenceString")(code);477 }478 479 /**480 * In a reference (full), after `]`.481 *482 * ```markdown483 * > | [a][b] d484 * ^485 * ```486 *487 * @type {State}488 */489 function referenceFullAfter(code) {490 return self.parser.defined.includes(normalizeIdentifier(self.sliceSerialize(self.events[self.events.length - 1][1]).slice(1, -1))) ? ok(code) : nok(code);491 }492 493 /**494 * In reference (full) that was missing.495 *496 * ```markdown497 * > | [a][b d498 * ^499 * ```500 *501 * @type {State}502 */503 function referenceFullMissing(code) {504 return nok(code);505 }506}507 508/**509 * @this {TokenizeContext}510 * Context.511 * @type {Tokenizer}512 */513function tokenizeReferenceCollapsed(effects, ok, nok) {514 return referenceCollapsedStart;515 516 /**517 * In reference (collapsed), at `[`.518 *519 * > 👉 **Note**: we only get here if the label is defined.520 *521 * ```markdown522 * > | [a][] d523 * ^524 * ```525 *526 * @type {State}527 */528 function referenceCollapsedStart(code) {529 // We only attempt a collapsed label if there’s a `[`.530 531 effects.enter("reference");532 effects.enter("referenceMarker");533 effects.consume(code);534 effects.exit("referenceMarker");535 return referenceCollapsedOpen;536 }537 538 /**539 * In reference (collapsed), at `]`.540 *541 * > 👉 **Note**: we only get here if the label is defined.542 *543 * ```markdown544 * > | [a][] d545 * ^546 * ```547 *548 * @type {State}549 */550 function referenceCollapsedOpen(code) {551 if (code === 93) {552 effects.enter("referenceMarker");553 effects.consume(code);554 effects.exit("referenceMarker");555 effects.exit("reference");556 return ok;557 }558 return nok(code);559 }560}