UNPKG

60.1 kBJavaScriptView Raw
1// https://github.com/substack/deep-freeze/blob/master/index.js
2/** @param {any} obj */
3function deepFreeze(obj) {
4 Object.freeze(obj);
5
6 var objIsFunction = typeof obj === 'function';
7
8 Object.getOwnPropertyNames(obj).forEach(function(prop) {
9 if (Object.hasOwnProperty.call(obj, prop)
10 && obj[prop] !== null
11 && (typeof obj[prop] === "object" || typeof obj[prop] === "function")
12 // IE11 fix: https://github.com/highlightjs/highlight.js/issues/2318
13 // TODO: remove in the future
14 && (objIsFunction ? prop !== 'caller' && prop !== 'callee' && prop !== 'arguments' : true)
15 && !Object.isFrozen(obj[prop])) {
16 deepFreeze(obj[prop]);
17 }
18 });
19
20 return obj;
21}
22
23class Response {
24 /**
25 * @param {CompiledMode} mode
26 */
27 constructor(mode) {
28 // eslint-disable-next-line no-undefined
29 if (mode.data === undefined) mode.data = {};
30
31 this.data = mode.data;
32 }
33
34 ignoreMatch() {
35 this.ignore = true;
36 }
37}
38
39/**
40 * @param {string} value
41 * @returns {string}
42 */
43function escapeHTML(value) {
44 return value
45 .replace(/&/g, '&')
46 .replace(/</g, '&lt;')
47 .replace(/>/g, '&gt;')
48 .replace(/"/g, '&quot;')
49 .replace(/'/g, '&#x27;');
50}
51
52/**
53 * performs a shallow merge of multiple objects into one
54 *
55 * @template T
56 * @param {T} original
57 * @param {Record<string,any>[]} objects
58 * @returns {T} a single new object
59 */
60function inherit(original, ...objects) {
61 /** @type Record<string,any> */
62 var result = {};
63
64 for (const key in original) {
65 result[key] = original[key];
66 }
67 objects.forEach(function(obj) {
68 for (const key in obj) {
69 result[key] = obj[key];
70 }
71 });
72 return /** @type {T} */ (result);
73}
74
75/* Stream merging */
76
77/**
78 * @typedef Event
79 * @property {'start'|'stop'} event
80 * @property {number} offset
81 * @property {Node} node
82 */
83
84/**
85 * @param {Node} node
86 */
87function tag(node) {
88 return node.nodeName.toLowerCase();
89}
90
91/**
92 * @param {Node} node
93 */
94function nodeStream(node) {
95 /** @type Event[] */
96 var result = [];
97 (function _nodeStream(node, offset) {
98 for (var child = node.firstChild; child; child = child.nextSibling) {
99 if (child.nodeType === 3) {
100 offset += child.nodeValue.length;
101 } else if (child.nodeType === 1) {
102 result.push({
103 event: 'start',
104 offset: offset,
105 node: child
106 });
107 offset = _nodeStream(child, offset);
108 // Prevent void elements from having an end tag that would actually
109 // double them in the output. There are more void elements in HTML
110 // but we list only those realistically expected in code display.
111 if (!tag(child).match(/br|hr|img|input/)) {
112 result.push({
113 event: 'stop',
114 offset: offset,
115 node: child
116 });
117 }
118 }
119 }
120 return offset;
121 })(node, 0);
122 return result;
123}
124
125/**
126 * @param {any} original - the original stream
127 * @param {any} highlighted - stream of the highlighted source
128 * @param {string} value - the original source itself
129 */
130function mergeStreams(original, highlighted, value) {
131 var processed = 0;
132 var result = '';
133 var nodeStack = [];
134
135 function selectStream() {
136 if (!original.length || !highlighted.length) {
137 return original.length ? original : highlighted;
138 }
139 if (original[0].offset !== highlighted[0].offset) {
140 return (original[0].offset < highlighted[0].offset) ? original : highlighted;
141 }
142
143 /*
144 To avoid starting the stream just before it should stop the order is
145 ensured that original always starts first and closes last:
146
147 if (event1 == 'start' && event2 == 'start')
148 return original;
149 if (event1 == 'start' && event2 == 'stop')
150 return highlighted;
151 if (event1 == 'stop' && event2 == 'start')
152 return original;
153 if (event1 == 'stop' && event2 == 'stop')
154 return highlighted;
155
156 ... which is collapsed to:
157 */
158 return highlighted[0].event === 'start' ? original : highlighted;
159 }
160
161 /**
162 * @param {Node} node
163 */
164 function open(node) {
165 /** @param {Attr} attr */
166 function attr_str(attr) {
167 return ' ' + attr.nodeName + '="' + escapeHTML(attr.value) + '"';
168 }
169 // @ts-ignore
170 result += '<' + tag(node) + [].map.call(node.attributes, attr_str).join('') + '>';
171 }
172
173 /**
174 * @param {Node} node
175 */
176 function close(node) {
177 result += '</' + tag(node) + '>';
178 }
179
180 /**
181 * @param {Event} event
182 */
183 function render(event) {
184 (event.event === 'start' ? open : close)(event.node);
185 }
186
187 while (original.length || highlighted.length) {
188 var stream = selectStream();
189 result += escapeHTML(value.substring(processed, stream[0].offset));
190 processed = stream[0].offset;
191 if (stream === original) {
192 /*
193 On any opening or closing tag of the original markup we first close
194 the entire highlighted node stack, then render the original tag along
195 with all the following original tags at the same offset and then
196 reopen all the tags on the highlighted stack.
197 */
198 nodeStack.reverse().forEach(close);
199 do {
200 render(stream.splice(0, 1)[0]);
201 stream = selectStream();
202 } while (stream === original && stream.length && stream[0].offset === processed);
203 nodeStack.reverse().forEach(open);
204 } else {
205 if (stream[0].event === 'start') {
206 nodeStack.push(stream[0].node);
207 } else {
208 nodeStack.pop();
209 }
210 render(stream.splice(0, 1)[0]);
211 }
212 }
213 return result + escapeHTML(value.substr(processed));
214}
215
216var utils = /*#__PURE__*/Object.freeze({
217 __proto__: null,
218 escapeHTML: escapeHTML,
219 inherit: inherit,
220 nodeStream: nodeStream,
221 mergeStreams: mergeStreams
222});
223
224/**
225 * @typedef {object} Renderer
226 * @property {(text: string) => void} addText
227 * @property {(node: Node) => void} openNode
228 * @property {(node: Node) => void} closeNode
229 * @property {() => string} value
230 */
231
232/** @typedef {{kind?: string, sublanguage?: boolean}} Node */
233/** @typedef {{walk: (r: Renderer) => void}} Tree */
234/** */
235
236const SPAN_CLOSE = '</span>';
237
238/**
239 * Determines if a node needs to be wrapped in <span>
240 *
241 * @param {Node} node */
242const emitsWrappingTags = (node) => {
243 return !!node.kind;
244};
245
246/** @type {Renderer} */
247class HTMLRenderer {
248 /**
249 * Creates a new HTMLRenderer
250 *
251 * @param {Tree} parseTree - the parse tree (must support `walk` API)
252 * @param {{classPrefix: string}} options
253 */
254 constructor(parseTree, options) {
255 this.buffer = "";
256 this.classPrefix = options.classPrefix;
257 parseTree.walk(this);
258 }
259
260 /**
261 * Adds texts to the output stream
262 *
263 * @param {string} text */
264 addText(text) {
265 this.buffer += escapeHTML(text);
266 }
267
268 /**
269 * Adds a node open to the output stream (if needed)
270 *
271 * @param {Node} node */
272 openNode(node) {
273 if (!emitsWrappingTags(node)) return;
274
275 let className = node.kind;
276 if (!node.sublanguage) {
277 className = `${this.classPrefix}${className}`;
278 }
279 this.span(className);
280 }
281
282 /**
283 * Adds a node close to the output stream (if needed)
284 *
285 * @param {Node} node */
286 closeNode(node) {
287 if (!emitsWrappingTags(node)) return;
288
289 this.buffer += SPAN_CLOSE;
290 }
291
292 /**
293 * returns the accumulated buffer
294 */
295 value() {
296 return this.buffer;
297 }
298
299 // helpers
300
301 /**
302 * Builds a span element
303 *
304 * @param {string} className */
305 span(className) {
306 this.buffer += `<span class="${className}">`;
307 }
308}
309
310/** @typedef {{kind?: string, sublanguage?: boolean, children: Node[]} | string} Node */
311/** @typedef {{kind?: string, sublanguage?: boolean, children: Node[]} } DataNode */
312/** */
313
314class TokenTree {
315 constructor() {
316 /** @type DataNode */
317 this.rootNode = { children: [] };
318 this.stack = [this.rootNode];
319 }
320
321 get top() {
322 return this.stack[this.stack.length - 1];
323 }
324
325 get root() { return this.rootNode; }
326
327 /** @param {Node} node */
328 add(node) {
329 this.top.children.push(node);
330 }
331
332 /** @param {string} kind */
333 openNode(kind) {
334 /** @type Node */
335 const node = { kind, children: [] };
336 this.add(node);
337 this.stack.push(node);
338 }
339
340 closeNode() {
341 if (this.stack.length > 1) {
342 return this.stack.pop();
343 }
344 // eslint-disable-next-line no-undefined
345 return undefined;
346 }
347
348 closeAllNodes() {
349 while (this.closeNode());
350 }
351
352 toJSON() {
353 return JSON.stringify(this.rootNode, null, 4);
354 }
355
356 /**
357 * @typedef { import("./html_renderer").Renderer } Renderer
358 * @param {Renderer} builder
359 */
360 walk(builder) {
361 // this does not
362 return this.constructor._walk(builder, this.rootNode);
363 // this works
364 // return TokenTree._walk(builder, this.rootNode);
365 }
366
367 /**
368 * @param {Renderer} builder
369 * @param {Node} node
370 */
371 static _walk(builder, node) {
372 if (typeof node === "string") {
373 builder.addText(node);
374 } else if (node.children) {
375 builder.openNode(node);
376 node.children.forEach((child) => this._walk(builder, child));
377 builder.closeNode(node);
378 }
379 return builder;
380 }
381
382 /**
383 * @param {Node} node
384 */
385 static _collapse(node) {
386 if (typeof node === "string") return;
387 if (!node.children) return;
388
389 if (node.children.every(el => typeof el === "string")) {
390 // node.text = node.children.join("");
391 // delete node.children;
392 node.children = [node.children.join("")];
393 } else {
394 node.children.forEach((child) => {
395 TokenTree._collapse(child);
396 });
397 }
398 }
399}
400
401/**
402 Currently this is all private API, but this is the minimal API necessary
403 that an Emitter must implement to fully support the parser.
404
405 Minimal interface:
406
407 - addKeyword(text, kind)
408 - addText(text)
409 - addSublanguage(emitter, subLanguageName)
410 - finalize()
411 - openNode(kind)
412 - closeNode()
413 - closeAllNodes()
414 - toHTML()
415
416*/
417
418/**
419 * @implements {Emitter}
420 */
421class TokenTreeEmitter extends TokenTree {
422 /**
423 * @param {*} options
424 */
425 constructor(options) {
426 super();
427 this.options = options;
428 }
429
430 /**
431 * @param {string} text
432 * @param {string} kind
433 */
434 addKeyword(text, kind) {
435 if (text === "") { return; }
436
437 this.openNode(kind);
438 this.addText(text);
439 this.closeNode();
440 }
441
442 /**
443 * @param {string} text
444 */
445 addText(text) {
446 if (text === "") { return; }
447
448 this.add(text);
449 }
450
451 /**
452 * @param {Emitter & {root: DataNode}} emitter
453 * @param {string} name
454 */
455 addSublanguage(emitter, name) {
456 /** @type DataNode */
457 const node = emitter.root;
458 node.kind = name;
459 node.sublanguage = true;
460 this.add(node);
461 }
462
463 toHTML() {
464 const renderer = new HTMLRenderer(this, this.options);
465 return renderer.value();
466 }
467
468 finalize() {
469 return true;
470 }
471}
472
473/**
474 * @param {string} value
475 * @returns {RegExp}
476 * */
477function escape(value) {
478 return new RegExp(value.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'), 'm');
479}
480
481/**
482 * @param {RegExp | string } re
483 * @returns {string}
484 */
485function source(re) {
486 if (!re) return null;
487 if (typeof re === "string") return re;
488
489 return re.source;
490}
491
492/**
493 * @param {...(RegExp | string) } args
494 * @returns {string}
495 */
496function concat(...args) {
497 const joined = args.map((x) => source(x)).join("");
498 return joined;
499}
500
501/**
502 * @param {RegExp} re
503 * @returns {number}
504 */
505function countMatchGroups(re) {
506 return (new RegExp(re.toString() + '|')).exec('').length - 1;
507}
508
509/**
510 * Does lexeme start with a regular expression match at the beginning
511 * @param {RegExp} re
512 * @param {string} lexeme
513 */
514function startsWith(re, lexeme) {
515 var match = re && re.exec(lexeme);
516 return match && match.index === 0;
517}
518
519// join logically computes regexps.join(separator), but fixes the
520// backreferences so they continue to match.
521// it also places each individual regular expression into it's own
522// match group, keeping track of the sequencing of those match groups
523// is currently an exercise for the caller. :-)
524/**
525 * @param {(string | RegExp)[]} regexps
526 * @param {string} separator
527 * @returns {string}
528 */
529function join(regexps, separator = "|") {
530 // backreferenceRe matches an open parenthesis or backreference. To avoid
531 // an incorrect parse, it additionally matches the following:
532 // - [...] elements, where the meaning of parentheses and escapes change
533 // - other escape sequences, so we do not misparse escape sequences as
534 // interesting elements
535 // - non-matching or lookahead parentheses, which do not capture. These
536 // follow the '(' with a '?'.
537 var backreferenceRe = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;
538 var numCaptures = 0;
539 var ret = '';
540 for (var i = 0; i < regexps.length; i++) {
541 numCaptures += 1;
542 var offset = numCaptures;
543 var re = source(regexps[i]);
544 if (i > 0) {
545 ret += separator;
546 }
547 ret += "(";
548 while (re.length > 0) {
549 var match = backreferenceRe.exec(re);
550 if (match == null) {
551 ret += re;
552 break;
553 }
554 ret += re.substring(0, match.index);
555 re = re.substring(match.index + match[0].length);
556 if (match[0][0] === '\\' && match[1]) {
557 // Adjust the backreference.
558 ret += '\\' + String(Number(match[1]) + offset);
559 } else {
560 ret += match[0];
561 if (match[0] === '(') {
562 numCaptures++;
563 }
564 }
565 }
566 ret += ")";
567 }
568 return ret;
569}
570
571// Common regexps
572const IDENT_RE = '[a-zA-Z]\\w*';
573const UNDERSCORE_IDENT_RE = '[a-zA-Z_]\\w*';
574const NUMBER_RE = '\\b\\d+(\\.\\d+)?';
575const C_NUMBER_RE = '(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)'; // 0x..., 0..., decimal, float
576const BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b...
577const RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~';
578
579/**
580* @param { Partial<Mode> & {binary?: string | RegExp} } opts
581*/
582const SHEBANG = (opts = {}) => {
583 const beginShebang = /^#![ ]*\//;
584 if (opts.binary) {
585 opts.begin = concat(
586 beginShebang,
587 /.*\b/,
588 opts.binary,
589 /\b.*/);
590 }
591 return inherit({
592 className: 'meta',
593 begin: beginShebang,
594 end: /$/,
595 relevance: 0,
596 /** @type {ModeCallback} */
597 "on:begin": (m, resp) => {
598 if (m.index !== 0) resp.ignoreMatch();
599 }
600 }, opts);
601};
602
603// Common modes
604const BACKSLASH_ESCAPE = {
605 begin: '\\\\[\\s\\S]', relevance: 0
606};
607const APOS_STRING_MODE = {
608 className: 'string',
609 begin: '\'',
610 end: '\'',
611 illegal: '\\n',
612 contains: [BACKSLASH_ESCAPE]
613};
614const QUOTE_STRING_MODE = {
615 className: 'string',
616 begin: '"',
617 end: '"',
618 illegal: '\\n',
619 contains: [BACKSLASH_ESCAPE]
620};
621const PHRASAL_WORDS_MODE = {
622 begin: /\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/
623};
624/**
625 * Creates a comment mode
626 *
627 * @param {string | RegExp} begin
628 * @param {string | RegExp} end
629 * @param {Mode | {}} [modeOptions]
630 * @returns {Partial<Mode>}
631 */
632const COMMENT = function(begin, end, modeOptions = {}) {
633 var mode = inherit(
634 {
635 className: 'comment',
636 begin,
637 end,
638 contains: []
639 },
640 modeOptions
641 );
642 mode.contains.push(PHRASAL_WORDS_MODE);
643 mode.contains.push({
644 className: 'doctag',
645 begin: '(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):',
646 relevance: 0
647 });
648 return mode;
649};
650const C_LINE_COMMENT_MODE = COMMENT('//', '$');
651const C_BLOCK_COMMENT_MODE = COMMENT('/\\*', '\\*/');
652const HASH_COMMENT_MODE = COMMENT('#', '$');
653const NUMBER_MODE = {
654 className: 'number',
655 begin: NUMBER_RE,
656 relevance: 0
657};
658const C_NUMBER_MODE = {
659 className: 'number',
660 begin: C_NUMBER_RE,
661 relevance: 0
662};
663const BINARY_NUMBER_MODE = {
664 className: 'number',
665 begin: BINARY_NUMBER_RE,
666 relevance: 0
667};
668const CSS_NUMBER_MODE = {
669 className: 'number',
670 begin: NUMBER_RE + '(' +
671 '%|em|ex|ch|rem' +
672 '|vw|vh|vmin|vmax' +
673 '|cm|mm|in|pt|pc|px' +
674 '|deg|grad|rad|turn' +
675 '|s|ms' +
676 '|Hz|kHz' +
677 '|dpi|dpcm|dppx' +
678 ')?',
679 relevance: 0
680};
681const REGEXP_MODE = {
682 // this outer rule makes sure we actually have a WHOLE regex and not simply
683 // an expression such as:
684 //
685 // 3 / something
686 //
687 // (which will then blow up when regex's `illegal` sees the newline)
688 begin: /(?=\/[^/\n]*\/)/,
689 contains: [{
690 className: 'regexp',
691 begin: /\//,
692 end: /\/[gimuy]*/,
693 illegal: /\n/,
694 contains: [
695 BACKSLASH_ESCAPE,
696 {
697 begin: /\[/,
698 end: /\]/,
699 relevance: 0,
700 contains: [BACKSLASH_ESCAPE]
701 }
702 ]
703 }]
704};
705const TITLE_MODE = {
706 className: 'title',
707 begin: IDENT_RE,
708 relevance: 0
709};
710const UNDERSCORE_TITLE_MODE = {
711 className: 'title',
712 begin: UNDERSCORE_IDENT_RE,
713 relevance: 0
714};
715const METHOD_GUARD = {
716 // excludes method names from keyword processing
717 begin: '\\.\\s*' + UNDERSCORE_IDENT_RE,
718 relevance: 0
719};
720
721/**
722 * Adds end same as begin mechanics to a mode
723 *
724 * Your mode must include at least a single () match group as that first match
725 * group is what is used for comparison
726 * @param {Partial<Mode>} mode
727 */
728const END_SAME_AS_BEGIN = function(mode) {
729 return Object.assign(mode,
730 {
731 /** @type {ModeCallback} */
732 'on:begin': (m, resp) => { resp.data._beginMatch = m[1]; },
733 /** @type {ModeCallback} */
734 'on:end': (m, resp) => { if (resp.data._beginMatch !== m[1]) resp.ignoreMatch(); }
735 });
736};
737
738var MODES = /*#__PURE__*/Object.freeze({
739 __proto__: null,
740 IDENT_RE: IDENT_RE,
741 UNDERSCORE_IDENT_RE: UNDERSCORE_IDENT_RE,
742 NUMBER_RE: NUMBER_RE,
743 C_NUMBER_RE: C_NUMBER_RE,
744 BINARY_NUMBER_RE: BINARY_NUMBER_RE,
745 RE_STARTERS_RE: RE_STARTERS_RE,
746 SHEBANG: SHEBANG,
747 BACKSLASH_ESCAPE: BACKSLASH_ESCAPE,
748 APOS_STRING_MODE: APOS_STRING_MODE,
749 QUOTE_STRING_MODE: QUOTE_STRING_MODE,
750 PHRASAL_WORDS_MODE: PHRASAL_WORDS_MODE,
751 COMMENT: COMMENT,
752 C_LINE_COMMENT_MODE: C_LINE_COMMENT_MODE,
753 C_BLOCK_COMMENT_MODE: C_BLOCK_COMMENT_MODE,
754 HASH_COMMENT_MODE: HASH_COMMENT_MODE,
755 NUMBER_MODE: NUMBER_MODE,
756 C_NUMBER_MODE: C_NUMBER_MODE,
757 BINARY_NUMBER_MODE: BINARY_NUMBER_MODE,
758 CSS_NUMBER_MODE: CSS_NUMBER_MODE,
759 REGEXP_MODE: REGEXP_MODE,
760 TITLE_MODE: TITLE_MODE,
761 UNDERSCORE_TITLE_MODE: UNDERSCORE_TITLE_MODE,
762 METHOD_GUARD: METHOD_GUARD,
763 END_SAME_AS_BEGIN: END_SAME_AS_BEGIN
764});
765
766// keywords that should have no default relevance value
767var COMMON_KEYWORDS = 'of and for in not or if then'.split(' ');
768
769// compilation
770
771/**
772 * Compiles a language definition result
773 *
774 * Given the raw result of a language definition (Language), compiles this so
775 * that it is ready for highlighting code.
776 * @param {Language} language
777 * @returns {CompiledLanguage}
778 */
779function compileLanguage(language) {
780 /**
781 * Builds a regex with the case sensativility of the current language
782 *
783 * @param {RegExp | string} value
784 * @param {boolean} [global]
785 */
786 function langRe(value, global) {
787 return new RegExp(
788 source(value),
789 'm' + (language.case_insensitive ? 'i' : '') + (global ? 'g' : '')
790 );
791 }
792
793 /**
794 Stores multiple regular expressions and allows you to quickly search for
795 them all in a string simultaneously - returning the first match. It does
796 this by creating a huge (a|b|c) regex - each individual item wrapped with ()
797 and joined by `|` - using match groups to track position. When a match is
798 found checking which position in the array has content allows us to figure
799 out which of the original regexes / match groups triggered the match.
800
801 The match object itself (the result of `Regex.exec`) is returned but also
802 enhanced by merging in any meta-data that was registered with the regex.
803 This is how we keep track of which mode matched, and what type of rule
804 (`illegal`, `begin`, end, etc).
805 */
806 class MultiRegex {
807 constructor() {
808 this.matchIndexes = {};
809 // @ts-ignore
810 this.regexes = [];
811 this.matchAt = 1;
812 this.position = 0;
813 }
814
815 // @ts-ignore
816 addRule(re, opts) {
817 opts.position = this.position++;
818 // @ts-ignore
819 this.matchIndexes[this.matchAt] = opts;
820 this.regexes.push([opts, re]);
821 this.matchAt += countMatchGroups(re) + 1;
822 }
823
824 compile() {
825 if (this.regexes.length === 0) {
826 // avoids the need to check length every time exec is called
827 // @ts-ignore
828 this.exec = () => null;
829 }
830 const terminators = this.regexes.map(el => el[1]);
831 this.matcherRe = langRe(join(terminators), true);
832 this.lastIndex = 0;
833 }
834
835 /** @param {string} s */
836 exec(s) {
837 this.matcherRe.lastIndex = this.lastIndex;
838 const match = this.matcherRe.exec(s);
839 if (!match) { return null; }
840
841 // eslint-disable-next-line no-undefined
842 const i = match.findIndex((el, i) => i > 0 && el !== undefined);
843 // @ts-ignore
844 const matchData = this.matchIndexes[i];
845 // trim off any earlier non-relevant match groups (ie, the other regex
846 // match groups that make up the multi-matcher)
847 match.splice(0, i);
848
849 return Object.assign(match, matchData);
850 }
851 }
852
853 /*
854 Created to solve the key deficiently with MultiRegex - there is no way to
855 test for multiple matches at a single location. Why would we need to do
856 that? In the future a more dynamic engine will allow certain matches to be
857 ignored. An example: if we matched say the 3rd regex in a large group but
858 decided to ignore it - we'd need to started testing again at the 4th
859 regex... but MultiRegex itself gives us no real way to do that.
860
861 So what this class creates MultiRegexs on the fly for whatever search
862 position they are needed.
863
864 NOTE: These additional MultiRegex objects are created dynamically. For most
865 grammars most of the time we will never actually need anything more than the
866 first MultiRegex - so this shouldn't have too much overhead.
867
868 Say this is our search group, and we match regex3, but wish to ignore it.
869
870 regex1 | regex2 | regex3 | regex4 | regex5 ' ie, startAt = 0
871
872 What we need is a new MultiRegex that only includes the remaining
873 possibilities:
874
875 regex4 | regex5 ' ie, startAt = 3
876
877 This class wraps all that complexity up in a simple API... `startAt` decides
878 where in the array of expressions to start doing the matching. It
879 auto-increments, so if a match is found at position 2, then startAt will be
880 set to 3. If the end is reached startAt will return to 0.
881
882 MOST of the time the parser will be setting startAt manually to 0.
883 */
884 class ResumableMultiRegex {
885 constructor() {
886 // @ts-ignore
887 this.rules = [];
888 // @ts-ignore
889 this.multiRegexes = [];
890 this.count = 0;
891
892 this.lastIndex = 0;
893 this.regexIndex = 0;
894 }
895
896 // @ts-ignore
897 getMatcher(index) {
898 if (this.multiRegexes[index]) return this.multiRegexes[index];
899
900 const matcher = new MultiRegex();
901 this.rules.slice(index).forEach(([re, opts]) => matcher.addRule(re, opts));
902 matcher.compile();
903 this.multiRegexes[index] = matcher;
904 return matcher;
905 }
906
907 considerAll() {
908 this.regexIndex = 0;
909 }
910
911 // @ts-ignore
912 addRule(re, opts) {
913 this.rules.push([re, opts]);
914 if (opts.type === "begin") this.count++;
915 }
916
917 /** @param {string} s */
918 exec(s) {
919 const m = this.getMatcher(this.regexIndex);
920 m.lastIndex = this.lastIndex;
921 const result = m.exec(s);
922 if (result) {
923 this.regexIndex += result.position + 1;
924 if (this.regexIndex === this.count) { // wrap-around
925 this.regexIndex = 0;
926 }
927 }
928
929 // this.regexIndex = 0;
930 return result;
931 }
932 }
933
934 /**
935 * Given a mode, builds a huge ResumableMultiRegex that can be used to walk
936 * the content and find matches.
937 *
938 * @param {CompiledMode} mode
939 * @returns {ResumableMultiRegex}
940 */
941 function buildModeRegex(mode) {
942 const mm = new ResumableMultiRegex();
943
944 mode.contains.forEach(term => mm.addRule(term.begin, { rule: term, type: "begin" }));
945
946 if (mode.terminator_end) {
947 mm.addRule(mode.terminator_end, { type: "end" });
948 }
949 if (mode.illegal) {
950 mm.addRule(mode.illegal, { type: "illegal" });
951 }
952
953 return mm;
954 }
955
956 // TODO: We need negative look-behind support to do this properly
957 /**
958 * Skip a match if it has a preceding or trailing dot
959 *
960 * This is used for `beginKeywords` to prevent matching expressions such as
961 * `bob.keyword.do()`. The mode compiler automatically wires this up as a
962 * special _internal_ 'on:begin' callback for modes with `beginKeywords`
963 * @param {RegExpMatchArray} match
964 * @param {CallbackResponse} response
965 */
966 function skipIfhasPrecedingOrTrailingDot(match, response) {
967 const before = match.input[match.index - 1];
968 const after = match.input[match.index + match[0].length];
969 if (before === "." || after === ".") {
970 response.ignoreMatch();
971 }
972 }
973
974 /** skip vs abort vs ignore
975 *
976 * @skip - The mode is still entered and exited normally (and contains rules apply),
977 * but all content is held and added to the parent buffer rather than being
978 * output when the mode ends. Mostly used with `sublanguage` to build up
979 * a single large buffer than can be parsed by sublanguage.
980 *
981 * - The mode begin ands ends normally.
982 * - Content matched is added to the parent mode buffer.
983 * - The parser cursor is moved forward normally.
984 *
985 * @abort - A hack placeholder until we have ignore. Aborts the mode (as if it
986 * never matched) but DOES NOT continue to match subsequent `contains`
987 * modes. Abort is bad/suboptimal because it can result in modes
988 * farther down not getting applied because an earlier rule eats the
989 * content but then aborts.
990 *
991 * - The mode does not begin.
992 * - Content matched by `begin` is added to the mode buffer.
993 * - The parser cursor is moved forward accordingly.
994 *
995 * @ignore - Ignores the mode (as if it never matched) and continues to match any
996 * subsequent `contains` modes. Ignore isn't technically possible with
997 * the current parser implementation.
998 *
999 * - The mode does not begin.
1000 * - Content matched by `begin` is ignored.
1001 * - The parser cursor is not moved forward.
1002 */
1003
1004 /**
1005 * Compiles an individual mode
1006 *
1007 * This can raise an error if the mode contains certain detectable known logic
1008 * issues.
1009 * @param {Mode} mode
1010 * @param {CompiledMode | null} [parent]
1011 * @returns {CompiledMode | never}
1012 */
1013 function compileMode(mode, parent) {
1014 const cmode = /** @type CompiledMode */ (mode);
1015 if (mode.compiled) return cmode;
1016 mode.compiled = true;
1017
1018 // __beforeBegin is considered private API, internal use only
1019 mode.__beforeBegin = null;
1020
1021 mode.keywords = mode.keywords || mode.beginKeywords;
1022
1023 let kw_pattern = null;
1024 if (typeof mode.keywords === "object") {
1025 kw_pattern = mode.keywords.$pattern;
1026 delete mode.keywords.$pattern;
1027 }
1028
1029 if (mode.keywords) {
1030 mode.keywords = compileKeywords(mode.keywords, language.case_insensitive);
1031 }
1032
1033 // both are not allowed
1034 if (mode.lexemes && kw_pattern) {
1035 throw new Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");
1036 }
1037
1038 // `mode.lexemes` was the old standard before we added and now recommend
1039 // using `keywords.$pattern` to pass the keyword pattern
1040 cmode.keywordPatternRe = langRe(mode.lexemes || kw_pattern || /\w+/, true);
1041
1042 if (parent) {
1043 if (mode.beginKeywords) {
1044 // for languages with keywords that include non-word characters checking for
1045 // a word boundary is not sufficient, so instead we check for a word boundary
1046 // or whitespace - this does no harm in any case since our keyword engine
1047 // doesn't allow spaces in keywords anyways and we still check for the boundary
1048 // first
1049 mode.begin = '\\b(' + mode.beginKeywords.split(' ').join('|') + ')(?=\\b|\\s)';
1050 mode.__beforeBegin = skipIfhasPrecedingOrTrailingDot;
1051 }
1052 if (!mode.begin) mode.begin = /\B|\b/;
1053 cmode.beginRe = langRe(mode.begin);
1054 if (mode.endSameAsBegin) mode.end = mode.begin;
1055 if (!mode.end && !mode.endsWithParent) mode.end = /\B|\b/;
1056 if (mode.end) cmode.endRe = langRe(mode.end);
1057 cmode.terminator_end = source(mode.end) || '';
1058 if (mode.endsWithParent && parent.terminator_end) {
1059 cmode.terminator_end += (mode.end ? '|' : '') + parent.terminator_end;
1060 }
1061 }
1062 if (mode.illegal) cmode.illegalRe = langRe(mode.illegal);
1063 // eslint-disable-next-line no-undefined
1064 if (mode.relevance === undefined) mode.relevance = 1;
1065 if (!mode.contains) mode.contains = [];
1066
1067 mode.contains = [].concat(...mode.contains.map(function(c) {
1068 return expand_or_clone_mode(c === 'self' ? mode : c);
1069 }));
1070 mode.contains.forEach(function(c) { compileMode(/** @type Mode */ (c), cmode); });
1071
1072 if (mode.starts) {
1073 compileMode(mode.starts, parent);
1074 }
1075
1076 cmode.matcher = buildModeRegex(cmode);
1077 return cmode;
1078 }
1079
1080 // self is not valid at the top-level
1081 if (language.contains && language.contains.includes('self')) {
1082 throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");
1083 }
1084 return compileMode(/** @type Mode */ (language));
1085}
1086
1087/**
1088 * Determines if a mode has a dependency on it's parent or not
1089 *
1090 * If a mode does have a parent dependency then often we need to clone it if
1091 * it's used in multiple places so that each copy points to the correct parent,
1092 * where-as modes without a parent can often safely be re-used at the bottom of
1093 * a mode chain.
1094 *
1095 * @param {Mode | null} mode
1096 * @returns {boolean} - is there a dependency on the parent?
1097 * */
1098function dependencyOnParent(mode) {
1099 if (!mode) return false;
1100
1101 return mode.endsWithParent || dependencyOnParent(mode.starts);
1102}
1103
1104/**
1105 * Expands a mode or clones it if necessary
1106 *
1107 * This is necessary for modes with parental dependenceis (see notes on
1108 * `dependencyOnParent`) and for nodes that have `variants` - which must then be
1109 * exploded into their own individual modes at compile time.
1110 *
1111 * @param {Mode} mode
1112 * @returns {Mode | Mode[]}
1113 * */
1114function expand_or_clone_mode(mode) {
1115 if (mode.variants && !mode.cached_variants) {
1116 mode.cached_variants = mode.variants.map(function(variant) {
1117 return inherit(mode, { variants: null }, variant);
1118 });
1119 }
1120
1121 // EXPAND
1122 // if we have variants then essentially "replace" the mode with the variants
1123 // this happens in compileMode, where this function is called from
1124 if (mode.cached_variants) {
1125 return mode.cached_variants;
1126 }
1127
1128 // CLONE
1129 // if we have dependencies on parents then we need a unique
1130 // instance of ourselves, so we can be reused with many
1131 // different parents without issue
1132 if (dependencyOnParent(mode)) {
1133 return inherit(mode, { starts: mode.starts ? inherit(mode.starts) : null });
1134 }
1135
1136 if (Object.isFrozen(mode)) {
1137 return inherit(mode);
1138 }
1139
1140 // no special dependency issues, just return ourselves
1141 return mode;
1142}
1143
1144/***********************************************
1145 Keywords
1146***********************************************/
1147
1148/**
1149 * Given raw keywords from a language definition, compile them.
1150 *
1151 * @param {string | Record<string,string>} rawKeywords
1152 * @param {boolean} case_insensitive
1153 */
1154function compileKeywords(rawKeywords, case_insensitive) {
1155 /** @type KeywordDict */
1156 var compiled_keywords = {};
1157
1158 if (typeof rawKeywords === 'string') { // string
1159 splitAndCompile('keyword', rawKeywords);
1160 } else {
1161 Object.keys(rawKeywords).forEach(function(className) {
1162 splitAndCompile(className, rawKeywords[className]);
1163 });
1164 }
1165 return compiled_keywords;
1166
1167 // ---
1168
1169 /**
1170 * Compiles an individual list of keywords
1171 *
1172 * Ex: "for if when while|5"
1173 *
1174 * @param {string} className
1175 * @param {string} keywordList
1176 */
1177 function splitAndCompile(className, keywordList) {
1178 if (case_insensitive) {
1179 keywordList = keywordList.toLowerCase();
1180 }
1181 keywordList.split(' ').forEach(function(keyword) {
1182 var pair = keyword.split('|');
1183 compiled_keywords[pair[0]] = [className, scoreForKeyword(pair[0], pair[1])];
1184 });
1185 }
1186}
1187
1188/**
1189 * Returns the proper score for a given keyword
1190 *
1191 * Also takes into account comment keywords, which will be scored 0 UNLESS
1192 * another score has been manually assigned.
1193 * @param {string} keyword
1194 * @param {string} [providedScore]
1195 */
1196function scoreForKeyword(keyword, providedScore) {
1197 // manual scores always win over common keywords
1198 // so you can force a score of 1 if you really insist
1199 if (providedScore) {
1200 return Number(providedScore);
1201 }
1202
1203 return commonKeyword(keyword) ? 0 : 1;
1204}
1205
1206/**
1207 * Determines if a given keyword is common or not
1208 *
1209 * @param {string} keyword */
1210function commonKeyword(keyword) {
1211 return COMMON_KEYWORDS.includes(keyword.toLowerCase());
1212}
1213
1214var version = "10.1.2";
1215
1216/*
1217Syntax highlighting with language autodetection.
1218https://highlightjs.org/
1219*/
1220
1221const escape$1 = escapeHTML;
1222const inherit$1 = inherit;
1223
1224const { nodeStream: nodeStream$1, mergeStreams: mergeStreams$1 } = utils;
1225const NO_MATCH = Symbol("nomatch");
1226
1227/**
1228 * @param {any} hljs - object that is extended (legacy)
1229 * @returns {HLJSApi}
1230 */
1231const HLJS = function(hljs) {
1232 // Convenience variables for build-in objects
1233 /** @type {unknown[]} */
1234 var ArrayProto = [];
1235
1236 // Global internal variables used within the highlight.js library.
1237 /** @type {Record<string, Language>} */
1238 var languages = Object.create(null);
1239 /** @type {Record<string, string>} */
1240 var aliases = Object.create(null);
1241 /** @type {HLJSPlugin[]} */
1242 var plugins = [];
1243
1244 // safe/production mode - swallows more errors, tries to keep running
1245 // even if a single syntax or parse hits a fatal error
1246 var SAFE_MODE = true;
1247 var fixMarkupRe = /(^(<[^>]+>|\t|)+|\n)/gm;
1248 var LANGUAGE_NOT_FOUND = "Could not find the language '{}', did you forget to load/include a language module?";
1249 /** @type {Language} */
1250 const PLAINTEXT_LANGUAGE = { disableAutodetect: true, name: 'Plain text', contains: [] };
1251
1252 // Global options used when within external APIs. This is modified when
1253 // calling the `hljs.configure` function.
1254 /** @type HLJSOptions */
1255 var options = {
1256 noHighlightRe: /^(no-?highlight)$/i,
1257 languageDetectRe: /\blang(?:uage)?-([\w-]+)\b/i,
1258 classPrefix: 'hljs-',
1259 tabReplace: null,
1260 useBR: false,
1261 languages: null,
1262 // beta configuration options, subject to change, welcome to discuss
1263 // https://github.com/highlightjs/highlight.js/issues/1086
1264 __emitter: TokenTreeEmitter
1265 };
1266
1267 /* Utility functions */
1268
1269 /**
1270 * Tests a language name to see if highlighting should be skipped
1271 * @param {string} languageName
1272 */
1273 function shouldNotHighlight(languageName) {
1274 return options.noHighlightRe.test(languageName);
1275 }
1276
1277 /**
1278 * @param {HighlightedHTMLElement} block - the HTML element to determine language for
1279 */
1280 function blockLanguage(block) {
1281 var classes = block.className + ' ';
1282
1283 classes += block.parentNode ? block.parentNode.className : '';
1284
1285 // language-* takes precedence over non-prefixed class names.
1286 const match = options.languageDetectRe.exec(classes);
1287 if (match) {
1288 var language = getLanguage(match[1]);
1289 if (!language) {
1290 console.warn(LANGUAGE_NOT_FOUND.replace("{}", match[1]));
1291 console.warn("Falling back to no-highlight mode for this block.", block);
1292 }
1293 return language ? match[1] : 'no-highlight';
1294 }
1295
1296 return classes
1297 .split(/\s+/)
1298 .find((_class) => shouldNotHighlight(_class) || getLanguage(_class));
1299 }
1300
1301 /**
1302 * Core highlighting function.
1303 *
1304 * @param {string} languageName - the language to use for highlighting
1305 * @param {string} code - the code to highlight
1306 * @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail
1307 * @param {Mode} [continuation] - current continuation mode, if any
1308 *
1309 * @returns {HighlightResult} Result - an object that represents the result
1310 * @property {string} language - the language name
1311 * @property {number} relevance - the relevance score
1312 * @property {string} value - the highlighted HTML code
1313 * @property {string} code - the original raw code
1314 * @property {Mode} top - top of the current mode stack
1315 * @property {boolean} illegal - indicates whether any illegal matches were found
1316 */
1317 function highlight(languageName, code, ignoreIllegals, continuation) {
1318 /** @type {{ code: string, language: string, result?: any }} */
1319 var context = {
1320 code,
1321 language: languageName
1322 };
1323 // the plugin can change the desired language or the code to be highlighted
1324 // just be changing the object it was passed
1325 fire("before:highlight", context);
1326
1327 // a before plugin can usurp the result completely by providing it's own
1328 // in which case we don't even need to call highlight
1329 var result = context.result ?
1330 context.result :
1331 _highlight(context.language, context.code, ignoreIllegals, continuation);
1332
1333 result.code = context.code;
1334 // the plugin can change anything in result to suite it
1335 fire("after:highlight", result);
1336
1337 return result;
1338 }
1339
1340 /**
1341 * private highlight that's used internally and does not fire callbacks
1342 *
1343 * @param {string} languageName - the language to use for highlighting
1344 * @param {string} code - the code to highlight
1345 * @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail
1346 * @param {Mode} [continuation] - current continuation mode, if any
1347 */
1348 function _highlight(languageName, code, ignoreIllegals, continuation) {
1349 var codeToHighlight = code;
1350
1351 /**
1352 * Return keyword data if a match is a keyword
1353 * @param {CompiledMode} mode - current mode
1354 * @param {RegExpMatchArray} match - regexp match data
1355 * @returns {KeywordData | false}
1356 */
1357 function keywordData(mode, match) {
1358 var matchText = language.case_insensitive ? match[0].toLowerCase() : match[0];
1359 return Object.prototype.hasOwnProperty.call(mode.keywords, matchText) && mode.keywords[matchText];
1360 }
1361
1362 function processKeywords() {
1363 if (!top.keywords) {
1364 emitter.addText(mode_buffer);
1365 return;
1366 }
1367
1368 let last_index = 0;
1369 top.keywordPatternRe.lastIndex = 0;
1370 let match = top.keywordPatternRe.exec(mode_buffer);
1371 let buf = "";
1372
1373 while (match) {
1374 buf += mode_buffer.substring(last_index, match.index);
1375 const data = keywordData(top, match);
1376 if (data) {
1377 const [kind, keywordRelevance] = data;
1378 emitter.addText(buf);
1379 buf = "";
1380
1381 relevance += keywordRelevance;
1382 emitter.addKeyword(match[0], kind);
1383 } else {
1384 buf += match[0];
1385 }
1386 last_index = top.keywordPatternRe.lastIndex;
1387 match = top.keywordPatternRe.exec(mode_buffer);
1388 }
1389 buf += mode_buffer.substr(last_index);
1390 emitter.addText(buf);
1391 }
1392
1393 function processSubLanguage() {
1394 if (mode_buffer === "") return;
1395 /** @type HighlightResult */
1396 var result = null;
1397
1398 if (typeof top.subLanguage === 'string') {
1399 if (!languages[top.subLanguage]) {
1400 emitter.addText(mode_buffer);
1401 return;
1402 }
1403 result = _highlight(top.subLanguage, mode_buffer, true, continuations[top.subLanguage]);
1404 continuations[top.subLanguage] = result.top;
1405 } else {
1406 result = highlightAuto(mode_buffer, top.subLanguage.length ? top.subLanguage : null);
1407 }
1408
1409 // Counting embedded language score towards the host language may be disabled
1410 // with zeroing the containing mode relevance. Use case in point is Markdown that
1411 // allows XML everywhere and makes every XML snippet to have a much larger Markdown
1412 // score.
1413 if (top.relevance > 0) {
1414 relevance += result.relevance;
1415 }
1416 emitter.addSublanguage(result.emitter, result.language);
1417 }
1418
1419 function processBuffer() {
1420 if (top.subLanguage != null) {
1421 processSubLanguage();
1422 } else {
1423 processKeywords();
1424 }
1425 mode_buffer = '';
1426 }
1427
1428 /**
1429 * @param {Mode} mode - new mode to start
1430 */
1431 function startNewMode(mode) {
1432 if (mode.className) {
1433 emitter.openNode(mode.className);
1434 }
1435 top = Object.create(mode, { parent: { value: top } });
1436 return top;
1437 }
1438
1439 /**
1440 * @param {CompiledMode } mode - the mode to potentially end
1441 * @param {RegExpMatchArray} match - the latest match
1442 * @param {string} matchPlusRemainder - match plus remainder of content
1443 * @returns {CompiledMode | void} - the next mode, or if void continue on in current mode
1444 */
1445 function endOfMode(mode, match, matchPlusRemainder) {
1446 let matched = startsWith(mode.endRe, matchPlusRemainder);
1447
1448 if (matched) {
1449 if (mode["on:end"]) {
1450 const resp = new Response(mode);
1451 mode["on:end"](match, resp);
1452 if (resp.ignore) matched = false;
1453 }
1454
1455 if (matched) {
1456 while (mode.endsParent && mode.parent) {
1457 mode = mode.parent;
1458 }
1459 return mode;
1460 }
1461 }
1462 // even if on:end fires an `ignore` it's still possible
1463 // that we might trigger the end node because of a parent mode
1464 if (mode.endsWithParent) {
1465 return endOfMode(mode.parent, match, matchPlusRemainder);
1466 }
1467 }
1468
1469 /**
1470 * Handle matching but then ignoring a sequence of text
1471 *
1472 * @param {string} lexeme - string containing full match text
1473 */
1474 function doIgnore(lexeme) {
1475 if (top.matcher.regexIndex === 0) {
1476 // no more regexs to potentially match here, so we move the cursor forward one
1477 // space
1478 mode_buffer += lexeme[0];
1479 return 1;
1480 } else {
1481 // no need to move the cursor, we still have additional regexes to try and
1482 // match at this very spot
1483 continueScanAtSamePosition = true;
1484 return 0;
1485 }
1486 }
1487
1488 /**
1489 * Handle the start of a new potential mode match
1490 *
1491 * @param {EnhancedMatch} match - the current match
1492 * @returns {number} how far to advance the parse cursor
1493 */
1494 function doBeginMatch(match) {
1495 var lexeme = match[0];
1496 var new_mode = match.rule;
1497
1498 const resp = new Response(new_mode);
1499 // first internal before callbacks, then the public ones
1500 const beforeCallbacks = [new_mode.__beforeBegin, new_mode["on:begin"]];
1501 for (const cb of beforeCallbacks) {
1502 if (!cb) continue;
1503 cb(match, resp);
1504 if (resp.ignore) return doIgnore(lexeme);
1505 }
1506
1507 if (new_mode && new_mode.endSameAsBegin) {
1508 new_mode.endRe = escape(lexeme);
1509 }
1510
1511 if (new_mode.skip) {
1512 mode_buffer += lexeme;
1513 } else {
1514 if (new_mode.excludeBegin) {
1515 mode_buffer += lexeme;
1516 }
1517 processBuffer();
1518 if (!new_mode.returnBegin && !new_mode.excludeBegin) {
1519 mode_buffer = lexeme;
1520 }
1521 }
1522 startNewMode(new_mode);
1523 // if (mode["after:begin"]) {
1524 // let resp = new Response(mode);
1525 // mode["after:begin"](match, resp);
1526 // }
1527 return new_mode.returnBegin ? 0 : lexeme.length;
1528 }
1529
1530 /**
1531 * Handle the potential end of mode
1532 *
1533 * @param {RegExpMatchArray} match - the current match
1534 */
1535 function doEndMatch(match) {
1536 var lexeme = match[0];
1537 var matchPlusRemainder = codeToHighlight.substr(match.index);
1538
1539 var end_mode = endOfMode(top, match, matchPlusRemainder);
1540 if (!end_mode) { return NO_MATCH; }
1541
1542 var origin = top;
1543 if (origin.skip) {
1544 mode_buffer += lexeme;
1545 } else {
1546 if (!(origin.returnEnd || origin.excludeEnd)) {
1547 mode_buffer += lexeme;
1548 }
1549 processBuffer();
1550 if (origin.excludeEnd) {
1551 mode_buffer = lexeme;
1552 }
1553 }
1554 do {
1555 if (top.className) {
1556 emitter.closeNode();
1557 }
1558 if (!top.skip && !top.subLanguage) {
1559 relevance += top.relevance;
1560 }
1561 top = top.parent;
1562 } while (top !== end_mode.parent);
1563 if (end_mode.starts) {
1564 if (end_mode.endSameAsBegin) {
1565 end_mode.starts.endRe = end_mode.endRe;
1566 }
1567 startNewMode(end_mode.starts);
1568 }
1569 return origin.returnEnd ? 0 : lexeme.length;
1570 }
1571
1572 function processContinuations() {
1573 var list = [];
1574 for (var current = top; current !== language; current = current.parent) {
1575 if (current.className) {
1576 list.unshift(current.className);
1577 }
1578 }
1579 list.forEach(item => emitter.openNode(item));
1580 }
1581
1582 /** @type {{type?: MatchType, index?: number, rule?: Mode}}} */
1583 var lastMatch = {};
1584
1585 /**
1586 * Process an individual match
1587 *
1588 * @param {string} textBeforeMatch - text preceeding the match (since the last match)
1589 * @param {EnhancedMatch} [match] - the match itself
1590 */
1591 function processLexeme(textBeforeMatch, match) {
1592 var lexeme = match && match[0];
1593
1594 // add non-matched text to the current mode buffer
1595 mode_buffer += textBeforeMatch;
1596
1597 if (lexeme == null) {
1598 processBuffer();
1599 return 0;
1600 }
1601
1602 // we've found a 0 width match and we're stuck, so we need to advance
1603 // this happens when we have badly behaved rules that have optional matchers to the degree that
1604 // sometimes they can end up matching nothing at all
1605 // Ref: https://github.com/highlightjs/highlight.js/issues/2140
1606 if (lastMatch.type === "begin" && match.type === "end" && lastMatch.index === match.index && lexeme === "") {
1607 // spit the "skipped" character that our regex choked on back into the output sequence
1608 mode_buffer += codeToHighlight.slice(match.index, match.index + 1);
1609 if (!SAFE_MODE) {
1610 /** @type {AnnotatedError} */
1611 const err = new Error('0 width match regex');
1612 err.languageName = languageName;
1613 err.badRule = lastMatch.rule;
1614 throw err;
1615 }
1616 return 1;
1617 }
1618 lastMatch = match;
1619
1620 if (match.type === "begin") {
1621 return doBeginMatch(match);
1622 } else if (match.type === "illegal" && !ignoreIllegals) {
1623 // illegal match, we do not continue processing
1624 /** @type {AnnotatedError} */
1625 const err = new Error('Illegal lexeme "' + lexeme + '" for mode "' + (top.className || '<unnamed>') + '"');
1626 err.mode = top;
1627 throw err;
1628 } else if (match.type === "end") {
1629 var processed = doEndMatch(match);
1630 if (processed !== NO_MATCH) {
1631 return processed;
1632 }
1633 }
1634
1635 // edge case for when illegal matches $ (end of line) which is technically
1636 // a 0 width match but not a begin/end match so it's not caught by the
1637 // first handler (when ignoreIllegals is true)
1638 if (match.type === "illegal" && lexeme === "") {
1639 // advance so we aren't stuck in an infinite loop
1640 return 1;
1641 }
1642
1643 // infinite loops are BAD, this is a last ditch catch all. if we have a
1644 // decent number of iterations yet our index (cursor position in our
1645 // parsing) still 3x behind our index then something is very wrong
1646 // so we bail
1647 if (iterations > 100000 && iterations > match.index * 3) {
1648 const err = new Error('potential infinite loop, way more iterations than matches');
1649 throw err;
1650 }
1651
1652 /*
1653 Why might be find ourselves here? Only one occasion now. An end match that was
1654 triggered but could not be completed. When might this happen? When an `endSameasBegin`
1655 rule sets the end rule to a specific match. Since the overall mode termination rule that's
1656 being used to scan the text isn't recompiled that means that any match that LOOKS like
1657 the end (but is not, because it is not an exact match to the beginning) will
1658 end up here. A definite end match, but when `doEndMatch` tries to "reapply"
1659 the end rule and fails to match, we wind up here, and just silently ignore the end.
1660
1661 This causes no real harm other than stopping a few times too many.
1662 */
1663
1664 mode_buffer += lexeme;
1665 return lexeme.length;
1666 }
1667
1668 var language = getLanguage(languageName);
1669 if (!language) {
1670 console.error(LANGUAGE_NOT_FOUND.replace("{}", languageName));
1671 throw new Error('Unknown language: "' + languageName + '"');
1672 }
1673
1674 var md = compileLanguage(language);
1675 var result = '';
1676 /** @type {CompiledMode} */
1677 var top = continuation || md;
1678 /** @type Record<string,Mode> */
1679 var continuations = {}; // keep continuations for sub-languages
1680 var emitter = new options.__emitter(options);
1681 processContinuations();
1682 var mode_buffer = '';
1683 var relevance = 0;
1684 var index = 0;
1685 var iterations = 0;
1686 var continueScanAtSamePosition = false;
1687
1688 try {
1689 top.matcher.considerAll();
1690
1691 for (;;) {
1692 iterations++;
1693 if (continueScanAtSamePosition) {
1694 // only regexes not matched previously will now be
1695 // considered for a potential match
1696 continueScanAtSamePosition = false;
1697 } else {
1698 top.matcher.lastIndex = index;
1699 top.matcher.considerAll();
1700 }
1701 const match = top.matcher.exec(codeToHighlight);
1702 // console.log("match", match[0], match.rule && match.rule.begin)
1703 if (!match) break;
1704
1705 const beforeMatch = codeToHighlight.substring(index, match.index);
1706 const processedCount = processLexeme(beforeMatch, match);
1707 index = match.index + processedCount;
1708 }
1709 processLexeme(codeToHighlight.substr(index));
1710 emitter.closeAllNodes();
1711 emitter.finalize();
1712 result = emitter.toHTML();
1713
1714 return {
1715 relevance: relevance,
1716 value: result,
1717 language: languageName,
1718 illegal: false,
1719 emitter: emitter,
1720 top: top
1721 };
1722 } catch (err) {
1723 if (err.message && err.message.includes('Illegal')) {
1724 return {
1725 illegal: true,
1726 illegalBy: {
1727 msg: err.message,
1728 context: codeToHighlight.slice(index - 100, index + 100),
1729 mode: err.mode
1730 },
1731 sofar: result,
1732 relevance: 0,
1733 value: escape$1(codeToHighlight),
1734 emitter: emitter
1735 };
1736 } else if (SAFE_MODE) {
1737 return {
1738 illegal: false,
1739 relevance: 0,
1740 value: escape$1(codeToHighlight),
1741 emitter: emitter,
1742 language: languageName,
1743 top: top,
1744 errorRaised: err
1745 };
1746 } else {
1747 throw err;
1748 }
1749 }
1750 }
1751
1752 /**
1753 * returns a valid highlight result, without actually doing any actual work,
1754 * auto highlight starts with this and it's possible for small snippets that
1755 * auto-detection may not find a better match
1756 * @param {string} code
1757 * @returns {HighlightResult}
1758 */
1759 function justTextHighlightResult(code) {
1760 const result = {
1761 relevance: 0,
1762 emitter: new options.__emitter(options),
1763 value: escape$1(code),
1764 illegal: false,
1765 top: PLAINTEXT_LANGUAGE
1766 };
1767 result.emitter.addText(code);
1768 return result;
1769 }
1770
1771 /**
1772 Highlighting with language detection. Accepts a string with the code to
1773 highlight. Returns an object with the following properties:
1774
1775 - language (detected language)
1776 - relevance (int)
1777 - value (an HTML string with highlighting markup)
1778 - second_best (object with the same structure for second-best heuristically
1779 detected language, may be absent)
1780
1781 @param {string} code
1782 @param {Array<string>} [languageSubset]
1783 @returns {AutoHighlightResult}
1784 */
1785 function highlightAuto(code, languageSubset) {
1786 languageSubset = languageSubset || options.languages || Object.keys(languages);
1787 var result = justTextHighlightResult(code);
1788 var secondBest = result;
1789 languageSubset.filter(getLanguage).filter(autoDetection).forEach(function(name) {
1790 var current = _highlight(name, code, false);
1791 current.language = name;
1792 if (current.relevance > secondBest.relevance) {
1793 secondBest = current;
1794 }
1795 if (current.relevance > result.relevance) {
1796 secondBest = result;
1797 result = current;
1798 }
1799 });
1800 if (secondBest.language) {
1801 // second_best (with underscore) is the expected API
1802 result.second_best = secondBest;
1803 }
1804 return result;
1805 }
1806
1807 /**
1808 Post-processing of the highlighted markup:
1809
1810 - replace TABs with something more useful
1811 - replace real line-breaks with '<br>' for non-pre containers
1812
1813 @param {string} html
1814 @returns {string}
1815 */
1816 function fixMarkup(html) {
1817 if (!(options.tabReplace || options.useBR)) {
1818 return html;
1819 }
1820
1821 return html.replace(fixMarkupRe, match => {
1822 if (match === '\n') {
1823 return options.useBR ? '<br>' : match;
1824 } else if (options.tabReplace) {
1825 return match.replace(/\t/g, options.tabReplace);
1826 }
1827 return match;
1828 });
1829 }
1830
1831 /**
1832 * Builds new class name for block given the language name
1833 *
1834 * @param {string} prevClassName
1835 * @param {string} [currentLang]
1836 * @param {string} [resultLang]
1837 */
1838 function buildClassName(prevClassName, currentLang, resultLang) {
1839 var language = currentLang ? aliases[currentLang] : resultLang;
1840 var result = [prevClassName.trim()];
1841
1842 if (!prevClassName.match(/\bhljs\b/)) {
1843 result.push('hljs');
1844 }
1845
1846 if (!prevClassName.includes(language)) {
1847 result.push(language);
1848 }
1849
1850 return result.join(' ').trim();
1851 }
1852
1853 /**
1854 * Applies highlighting to a DOM node containing code. Accepts a DOM node and
1855 * two optional parameters for fixMarkup.
1856 *
1857 * @param {HighlightedHTMLElement} element - the HTML element to highlight
1858 */
1859 function highlightBlock(element) {
1860 /** @type HTMLElement */
1861 let node = null;
1862 const language = blockLanguage(element);
1863
1864 if (shouldNotHighlight(language)) return;
1865
1866 fire("before:highlightBlock",
1867 { block: element, language: language });
1868
1869 if (options.useBR) {
1870 node = document.createElement('div');
1871 node.innerHTML = element.innerHTML.replace(/\n/g, '').replace(/<br[ /]*>/g, '\n');
1872 } else {
1873 node = element;
1874 }
1875 const text = node.textContent;
1876 const result = language ? highlight(language, text, true) : highlightAuto(text);
1877
1878 const originalStream = nodeStream$1(node);
1879 if (originalStream.length) {
1880 const resultNode = document.createElement('div');
1881 resultNode.innerHTML = result.value;
1882 result.value = mergeStreams$1(originalStream, nodeStream$1(resultNode), text);
1883 }
1884 result.value = fixMarkup(result.value);
1885
1886 fire("after:highlightBlock", { block: element, result: result });
1887
1888 element.innerHTML = result.value;
1889 element.className = buildClassName(element.className, language, result.language);
1890 element.result = {
1891 language: result.language,
1892 // TODO: remove with version 11.0
1893 re: result.relevance,
1894 relavance: result.relevance
1895 };
1896 if (result.second_best) {
1897 element.second_best = {
1898 language: result.second_best.language,
1899 // TODO: remove with version 11.0
1900 re: result.second_best.relevance,
1901 relavance: result.second_best.relevance
1902 };
1903 }
1904 }
1905
1906 /**
1907 * Updates highlight.js global options with the passed options
1908 *
1909 * @param {{}} userOptions
1910 */
1911 function configure(userOptions) {
1912 options = inherit$1(options, userOptions);
1913 }
1914
1915 /**
1916 * Highlights to all <pre><code> blocks on a page
1917 *
1918 * @type {Function & {called?: boolean}}
1919 */
1920 const initHighlighting = () => {
1921 if (initHighlighting.called) return;
1922 initHighlighting.called = true;
1923
1924 var blocks = document.querySelectorAll('pre code');
1925 ArrayProto.forEach.call(blocks, highlightBlock);
1926 };
1927
1928 // Higlights all when DOMContentLoaded fires
1929 function initHighlightingOnLoad() {
1930 // @ts-ignore
1931 window.addEventListener('DOMContentLoaded', initHighlighting, false);
1932 }
1933
1934 /**
1935 * Register a language grammar module
1936 *
1937 * @param {string} languageName
1938 * @param {LanguageFn} languageDefinition
1939 */
1940 function registerLanguage(languageName, languageDefinition) {
1941 var lang = null;
1942 try {
1943 lang = languageDefinition(hljs);
1944 } catch (error) {
1945 console.error("Language definition for '{}' could not be registered.".replace("{}", languageName));
1946 // hard or soft error
1947 if (!SAFE_MODE) { throw error; } else { console.error(error); }
1948 // languages that have serious errors are replaced with essentially a
1949 // "plaintext" stand-in so that the code blocks will still get normal
1950 // css classes applied to them - and one bad language won't break the
1951 // entire highlighter
1952 lang = PLAINTEXT_LANGUAGE;
1953 }
1954 // give it a temporary name if it doesn't have one in the meta-data
1955 if (!lang.name) lang.name = languageName;
1956 languages[languageName] = lang;
1957 lang.rawDefinition = languageDefinition.bind(null, hljs);
1958
1959 if (lang.aliases) {
1960 registerAliases(lang.aliases, { languageName });
1961 }
1962 }
1963
1964 /**
1965 * @returns {string[]} List of language internal names
1966 */
1967 function listLanguages() {
1968 return Object.keys(languages);
1969 }
1970
1971 /**
1972 intended usage: When one language truly requires another
1973
1974 Unlike `getLanguage`, this will throw when the requested language
1975 is not available.
1976
1977 @param {string} name - name of the language to fetch/require
1978 @returns {Language | never}
1979 */
1980 function requireLanguage(name) {
1981 var lang = getLanguage(name);
1982 if (lang) { return lang; }
1983
1984 var err = new Error('The \'{}\' language is required, but not loaded.'.replace('{}', name));
1985 throw err;
1986 }
1987
1988 /**
1989 * @param {string} name - name of the language to retrieve
1990 * @returns {Language | undefined}
1991 */
1992 function getLanguage(name) {
1993 name = (name || '').toLowerCase();
1994 return languages[name] || languages[aliases[name]];
1995 }
1996
1997 /**
1998 *
1999 * @param {string|string[]} aliasList - single alias or list of aliases
2000 * @param {{languageName: string}} opts
2001 */
2002 function registerAliases(aliasList, { languageName }) {
2003 if (typeof aliasList === 'string') {
2004 aliasList = [aliasList];
2005 }
2006 aliasList.forEach(alias => { aliases[alias] = languageName; });
2007 }
2008
2009 /**
2010 * Determines if a given language has auto-detection enabled
2011 * @param {string} name - name of the language
2012 */
2013 function autoDetection(name) {
2014 var lang = getLanguage(name);
2015 return lang && !lang.disableAutodetect;
2016 }
2017
2018 /**
2019 * @param {HLJSPlugin} plugin
2020 */
2021 function addPlugin(plugin) {
2022 plugins.push(plugin);
2023 }
2024
2025 /**
2026 *
2027 * @param {PluginEvent} event
2028 * @param {any} args
2029 */
2030 function fire(event, args) {
2031 var cb = event;
2032 plugins.forEach(function(plugin) {
2033 if (plugin[cb]) {
2034 plugin[cb](args);
2035 }
2036 });
2037 }
2038
2039 /* Interface definition */
2040
2041 Object.assign(hljs, {
2042 highlight,
2043 highlightAuto,
2044 fixMarkup,
2045 highlightBlock,
2046 configure,
2047 initHighlighting,
2048 initHighlightingOnLoad,
2049 registerLanguage,
2050 listLanguages,
2051 getLanguage,
2052 registerAliases,
2053 requireLanguage,
2054 autoDetection,
2055 inherit: inherit$1,
2056 addPlugin
2057 });
2058
2059 hljs.debugMode = function() { SAFE_MODE = false; };
2060 hljs.safeMode = function() { SAFE_MODE = true; };
2061 hljs.versionString = version;
2062
2063 for (const key in MODES) {
2064 // @ts-ignore
2065 if (typeof MODES[key] === "object") {
2066 // @ts-ignore
2067 deepFreeze(MODES[key]);
2068 }
2069 }
2070
2071 // merge all the modes/regexs into our main object
2072 Object.assign(hljs, MODES);
2073
2074 return hljs;
2075};
2076
2077// export an "instance" of the highlighter
2078var highlight = HLJS({});
2079
2080module.exports = highlight;