@pacem/pacem-foundation 1.1.0-sierpinski → 2.0.0-abel

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @pacem/pacem-foundation v1.1.0-sierpinski (https://js.pacem.it)
2
+ * @pacem/pacem-foundation v2.0.0-abel (https://js.pacem.it)
3
3
  * Pacem (https://pacem.it)
4
4
  * Licensed under Apache-2.0
5
5
  */
@@ -1778,6 +1778,600 @@
1778
1778
  };
1779
1779
  //}
1780
1780
 
1781
+ /** Token types shared by the built-in vocabularies (vocabularies may add their own). */
1782
+ var SyntaxTokenType;
1783
+ (function (SyntaxTokenType) {
1784
+ SyntaxTokenType["Text"] = "text";
1785
+ SyntaxTokenType["Space"] = "space";
1786
+ SyntaxTokenType["Newline"] = "newline";
1787
+ SyntaxTokenType["Comment"] = "comment";
1788
+ SyntaxTokenType["DocComment"] = "doc-comment";
1789
+ SyntaxTokenType["Keyword"] = "keyword";
1790
+ SyntaxTokenType["Identifier"] = "identifier";
1791
+ SyntaxTokenType["String"] = "string";
1792
+ SyntaxTokenType["Regex"] = "regex";
1793
+ SyntaxTokenType["Number"] = "number";
1794
+ SyntaxTokenType["Bracket"] = "bracket";
1795
+ SyntaxTokenType["Punctuation"] = "punctuation";
1796
+ SyntaxTokenType["Operator"] = "operator";
1797
+ SyntaxTokenType["Preprocessor"] = "preprocessor";
1798
+ SyntaxTokenType["Tag"] = "tag";
1799
+ SyntaxTokenType["TagName"] = "tagname";
1800
+ SyntaxTokenType["Attribute"] = "attribute";
1801
+ SyntaxTokenType["Entity"] = "entity";
1802
+ })(SyntaxTokenType || (SyntaxTokenType = {}));
1803
+
1804
+ //namespace Pacem.Compile {
1805
+ const registry = new Map();
1806
+ function normalize(name) {
1807
+ return (name ?? '').trim().toLowerCase();
1808
+ }
1809
+ /**
1810
+ * Registry of the {@link Syntax} vocabularies known to the {@link Scanner}, by (case-insensitive) name and aliases.
1811
+ * The built-in vocabularies (see {@link SyntaxName}) are registered upfront; more can be
1812
+ * registered at runtime, e.g. after fetching them as JSON.
1813
+ */
1814
+ class Syntaxes {
1815
+ /**
1816
+ * Registers (or replaces) a syntax under its name and aliases.
1817
+ * @param syntax The vocabulary to register.
1818
+ */
1819
+ static register(syntax) {
1820
+ if (syntax == null) {
1821
+ return;
1822
+ }
1823
+ const names = [syntax.name].concat(syntax.aliases ?? []);
1824
+ for (let name of names) {
1825
+ const key = normalize(name);
1826
+ registry.set(key, syntax);
1827
+ }
1828
+ }
1829
+ /**
1830
+ * Gets a registered syntax by name or alias.
1831
+ * @param nameOrAlias Case-insensitive name or alias (e.g. `'ts'`).
1832
+ * @returns The syntax, or `null` when unknown.
1833
+ */
1834
+ static get(nameOrAlias) {
1835
+ const key = normalize(nameOrAlias);
1836
+ return registry.get(key) ?? null;
1837
+ }
1838
+ /** Names (and aliases) of every registered syntax. */
1839
+ static names() {
1840
+ const keys = registry.keys();
1841
+ return Array.from(keys);
1842
+ }
1843
+ }
1844
+
1845
+ //namespace Pacem.Compile {
1846
+ const DEFAULT_START = 'root';
1847
+ const DEFAULT_KEYWORD_SOURCES = [SyntaxTokenType.Identifier];
1848
+ const DEFAULT_INSIGNIFICANT = [SyntaxTokenType.Space, SyntaxTokenType.Newline, SyntaxTokenType.Comment, SyntaxTokenType.DocComment];
1849
+ /** Consecutive zero-length transitions tolerated before a rule gets skipped (guards against push/pop loops). */
1850
+ const MAX_EMPTY_TRANSITIONS = 64;
1851
+ /** Base-syntax chain length tolerated before giving up (guards against `extends` cycles). */
1852
+ const MAX_EXTENDS_DEPTH = 16;
1853
+ function isCombined(item) {
1854
+ return item.combined != null;
1855
+ }
1856
+ const compiledCache = new WeakMap();
1857
+ function isIncludeRule(rule) {
1858
+ return 'include' in rule;
1859
+ }
1860
+ function toSet(values) {
1861
+ if (values == null || values.length === 0) {
1862
+ return null;
1863
+ }
1864
+ return new Set(values);
1865
+ }
1866
+ function uniqueFlags(flags) {
1867
+ const chars = flags.split('');
1868
+ const unique = new Set(chars);
1869
+ const array = Array.from(unique);
1870
+ return array.join('');
1871
+ }
1872
+ function mergeStates(own, base) {
1873
+ const merged = {};
1874
+ for (let name in base) {
1875
+ merged[name] = base[name].slice();
1876
+ }
1877
+ for (let name in own) {
1878
+ const baseRules = merged[name] ?? [];
1879
+ merged[name] = own[name].concat(baseRules);
1880
+ }
1881
+ return merged;
1882
+ }
1883
+ function mergeKeywords(own, base) {
1884
+ const merged = {};
1885
+ for (let type in base) {
1886
+ merged[type] = base[type].slice();
1887
+ }
1888
+ for (let type in own) {
1889
+ const baseWords = merged[type] ?? [];
1890
+ merged[type] = baseWords.concat(own[type]);
1891
+ }
1892
+ return merged;
1893
+ }
1894
+ /** Resolves the `extends` chain of `syntax` (own rules first, then the base ones). */
1895
+ function mergeSyntax(syntax, depth = 0) {
1896
+ const lexical = syntax.lexical ?? { states: {} };
1897
+ const own = {
1898
+ start: lexical.start,
1899
+ states: lexical.states ?? {},
1900
+ keywords: lexical.keywords ?? {},
1901
+ keywordSources: lexical.keywordSources,
1902
+ keywordExceptAfter: lexical.keywordExceptAfter,
1903
+ insignificant: lexical.insignificant
1904
+ };
1905
+ const baseSyntax = syntax.extends ? Syntaxes.get(syntax.extends) : null;
1906
+ if (baseSyntax == null || baseSyntax === syntax || depth >= MAX_EXTENDS_DEPTH) {
1907
+ return own;
1908
+ }
1909
+ const base = mergeSyntax(baseSyntax, depth + 1);
1910
+ return {
1911
+ start: own.start ?? base.start,
1912
+ states: mergeStates(own.states, base.states),
1913
+ keywords: mergeKeywords(own.keywords, base.keywords),
1914
+ keywordSources: own.keywordSources ?? base.keywordSources,
1915
+ keywordExceptAfter: own.keywordExceptAfter ?? base.keywordExceptAfter,
1916
+ insignificant: own.insignificant ?? base.insignificant
1917
+ };
1918
+ }
1919
+ /** Flattens `include` rules of a state (recursively, ignoring cycles). */
1920
+ function expandRules(states, stateName, visiting) {
1921
+ const rules = states[stateName] ?? [];
1922
+ if (visiting.has(stateName)) {
1923
+ return [];
1924
+ }
1925
+ visiting.add(stateName);
1926
+ const expanded = [];
1927
+ for (let rule of rules) {
1928
+ if (!isIncludeRule(rule)) {
1929
+ expanded.push(rule);
1930
+ continue;
1931
+ }
1932
+ const included = expandRules(states, rule.include, visiting);
1933
+ Array.prototype.push.apply(expanded, included);
1934
+ }
1935
+ visiting.delete(stateName);
1936
+ return expanded;
1937
+ }
1938
+ function compileCondition(condition) {
1939
+ if (condition == null) {
1940
+ return null;
1941
+ }
1942
+ return {
1943
+ previousTypes: toSet(condition.previousTypes),
1944
+ previousNotTypes: toSet(condition.previousNotTypes),
1945
+ previousTexts: toSet(condition.previousTexts),
1946
+ previousNotTexts: toSet(condition.previousNotTexts)
1947
+ };
1948
+ }
1949
+ function compileRule(rule) {
1950
+ const groupTypes = Array.isArray(rule.type) ? rule.type : null;
1951
+ const type = Array.isArray(rule.type) ? rule.type[0] : rule.type;
1952
+ // sticky and indices flags are the engine's business
1953
+ const userFlags = (rule.flags ?? '').replace(/[yd]/g, '');
1954
+ const ownFlags = uniqueFlags(userFlags);
1955
+ // the `d` flag yields the capture group offsets, needed to split a match into per-group tokens
1956
+ const flags = ownFlags + 'y' + (groupTypes ? 'd' : '');
1957
+ const regex = new RegExp(rule.match, flags);
1958
+ const until = rule.until ? new RegExp(rule.until, 'y') : null;
1959
+ const pop = rule.pop === true ? 1 : (typeof rule.pop === 'number' ? rule.pop : 0);
1960
+ const when = compileCondition(rule.when);
1961
+ return {
1962
+ regex, type, groupTypes, when, pop, until,
1963
+ source: rule.match,
1964
+ flags: ownFlags,
1965
+ next: rule.next ?? null,
1966
+ push: rule.push ?? null,
1967
+ embed: rule.embed ?? null,
1968
+ hasTransition: pop > 0 || !!rule.next || !!rule.push || !!rule.embed
1969
+ };
1970
+ }
1971
+ function compileKeywords(keywords) {
1972
+ const map = new Map();
1973
+ for (let type in keywords) {
1974
+ for (let word of keywords[type]) {
1975
+ map.set(word, type);
1976
+ }
1977
+ }
1978
+ return map;
1979
+ }
1980
+ /** Backreferences and named groups would break once renumbered/duplicated inside an alternation. */
1981
+ const UNMERGEABLE_SOURCE = /\\[1-9]|\\k<|\(\?<(?![=!])/;
1982
+ function countGroups(source, flags) {
1983
+ const probe = new RegExp(source + '|', flags);
1984
+ const match = probe.exec('');
1985
+ return match.length - 1;
1986
+ }
1987
+ function canMatchEmpty(source, flags) {
1988
+ const probe = new RegExp('^(?:' + source + ')$', flags);
1989
+ return probe.test('');
1990
+ }
1991
+ /** Whether a rule can be merged into a {@link CombinedSegment}. */
1992
+ function isMergeable(rule) {
1993
+ if (rule.when != null || UNMERGEABLE_SOURCE.test(rule.source)) {
1994
+ return false;
1995
+ }
1996
+ return !canMatchEmpty(rule.source, rule.flags);
1997
+ }
1998
+ function combine(rules) {
1999
+ if (rules.length === 1) {
2000
+ return { rule: rules[0], combined: null };
2001
+ }
2002
+ const groupIndices = [];
2003
+ let groupIndex = 1;
2004
+ for (let rule of rules) {
2005
+ groupIndices.push(groupIndex);
2006
+ const ownGroups = countGroups(rule.source, rule.flags);
2007
+ groupIndex += 1 + ownGroups;
2008
+ }
2009
+ const alternatives = rules.map(r => '(' + r.source + ')');
2010
+ const source = alternatives.join('|');
2011
+ const combined = new RegExp(source, rules[0].flags + 'y');
2012
+ return { rule: null, combined, rules, groupIndices };
2013
+ }
2014
+ /** Groups consecutive mergeable rules (sharing the same flags) into combined segments, keeping their order. */
2015
+ function segment(rules) {
2016
+ const segments = [];
2017
+ let pending = [];
2018
+ const flush = () => {
2019
+ if (pending.length > 0) {
2020
+ const combinedSegment = combine(pending);
2021
+ segments.push(combinedSegment);
2022
+ }
2023
+ pending = [];
2024
+ };
2025
+ for (let rule of rules) {
2026
+ if (!isMergeable(rule)) {
2027
+ flush();
2028
+ segments.push({ rule, combined: null });
2029
+ continue;
2030
+ }
2031
+ if (pending.length > 0 && pending[0].flags !== rule.flags) {
2032
+ flush();
2033
+ }
2034
+ pending.push(rule);
2035
+ }
2036
+ flush();
2037
+ return segments;
2038
+ }
2039
+ function compileSyntax(syntax) {
2040
+ const cached = compiledCache.get(syntax);
2041
+ if (cached != null) {
2042
+ return cached;
2043
+ }
2044
+ const merged = mergeSyntax(syntax);
2045
+ const states = new Map();
2046
+ for (let name in merged.states) {
2047
+ const visiting = new Set();
2048
+ const rules = expandRules(merged.states, name, visiting);
2049
+ const compiledRules = rules.map(compileRule);
2050
+ const segments = segment(compiledRules);
2051
+ states.set(name, segments);
2052
+ }
2053
+ const compiled = {
2054
+ name: syntax.name,
2055
+ start: merged.start ?? DEFAULT_START,
2056
+ states,
2057
+ keywords: compileKeywords(merged.keywords),
2058
+ keywordSources: new Set(merged.keywordSources ?? DEFAULT_KEYWORD_SOURCES),
2059
+ keywordExceptAfter: new Set(merged.keywordExceptAfter ?? []),
2060
+ insignificant: new Set(merged.insignificant ?? DEFAULT_INSIGNIFICANT)
2061
+ };
2062
+ compiledCache.set(syntax, compiled);
2063
+ return compiled;
2064
+ }
2065
+ function compileByName(name) {
2066
+ const syntax = Syntaxes.get(name);
2067
+ if (syntax == null) {
2068
+ return null;
2069
+ }
2070
+ return compileSyntax(syntax);
2071
+ }
2072
+ function meets(condition, previous) {
2073
+ const type = previous?.type ?? null, text = previous?.raw ?? null;
2074
+ if (condition.previousTypes != null && !condition.previousTypes.has(type)) {
2075
+ return false;
2076
+ }
2077
+ if (condition.previousNotTypes != null && condition.previousNotTypes.has(type)) {
2078
+ return false;
2079
+ }
2080
+ if (condition.previousTexts != null && !condition.previousTexts.has(text)) {
2081
+ return false;
2082
+ }
2083
+ return condition.previousNotTexts == null || !condition.previousNotTexts.has(text);
2084
+ }
2085
+ /** A single tokenization pass: holds the cursor, the state stack and the produced tokens. */
2086
+ class ScanRun {
2087
+ constructor(_source, root) {
2088
+ this._source = _source;
2089
+ this._tokens = [];
2090
+ /** Current scanning position. */
2091
+ this._position = 0;
2092
+ /** End of the already emitted tokens (characters between it and `_position` are pending unrecognized text). */
2093
+ this._emitted = 0;
2094
+ this._line = 1;
2095
+ this._column = 1;
2096
+ this._previous = null;
2097
+ this._emptyTransitions = 0;
2098
+ this._stack = [{ syntax: root, state: root.start, until: null }];
2099
+ }
2100
+ run() {
2101
+ const length = this._source.length;
2102
+ while (this._position < length) {
2103
+ this._step();
2104
+ }
2105
+ this._flushText();
2106
+ return this._tokens;
2107
+ }
2108
+ get _top() {
2109
+ const stack = this._stack;
2110
+ return stack[stack.length - 1];
2111
+ }
2112
+ _step() {
2113
+ const frame = this._top;
2114
+ if (this._popOnUntil(frame)) {
2115
+ return;
2116
+ }
2117
+ const found = this._findRule(frame);
2118
+ if (found == null) {
2119
+ // unrecognized character: accumulates into a `text` token
2120
+ this._position++;
2121
+ return;
2122
+ }
2123
+ this._flushText();
2124
+ this._emitMatch(found, frame.syntax);
2125
+ this._transition(found.rule, frame.syntax);
2126
+ const length = found.match[0].length;
2127
+ this._position += length;
2128
+ if (length > 0) {
2129
+ this._emptyTransitions = 0;
2130
+ }
2131
+ }
2132
+ /** Pops an embedded syntax as soon as its `until` pattern matches (without consuming input). */
2133
+ _popOnUntil(frame) {
2134
+ const until = frame.until;
2135
+ if (until == null || this._stack.length <= 1) {
2136
+ return false;
2137
+ }
2138
+ until.lastIndex = this._position;
2139
+ if (!until.test(this._source)) {
2140
+ return false;
2141
+ }
2142
+ this._flushText();
2143
+ this._stack.pop();
2144
+ return true;
2145
+ }
2146
+ _findRule(frame) {
2147
+ const segments = frame.syntax.states.get(frame.state) ?? [];
2148
+ for (let item of segments) {
2149
+ const found = isCombined(item) ? this._tryCombined(item) : this._trySingle(item.rule);
2150
+ if (found != null) {
2151
+ return found;
2152
+ }
2153
+ }
2154
+ return null;
2155
+ }
2156
+ _trySingle(rule) {
2157
+ const match = this._tryRule(rule);
2158
+ if (match == null) {
2159
+ return null;
2160
+ }
2161
+ return { rule, match };
2162
+ }
2163
+ /** Finds which merged rule matched (the first defined wrapping group), then re-runs it alone for its own groups. */
2164
+ _tryCombined(item) {
2165
+ const combined = item.combined;
2166
+ combined.lastIndex = this._position;
2167
+ const match = combined.exec(this._source);
2168
+ if (match == null) {
2169
+ return null;
2170
+ }
2171
+ const indices = item.groupIndices;
2172
+ for (let k = 0; k < indices.length; k++) {
2173
+ if (match[indices[k]] === undefined) {
2174
+ continue;
2175
+ }
2176
+ const rule = item.rules[k];
2177
+ return this._trySingle(rule);
2178
+ }
2179
+ return null;
2180
+ }
2181
+ _tryRule(rule) {
2182
+ if (rule.when != null && !meets(rule.when, this._previous)) {
2183
+ return null;
2184
+ }
2185
+ const regex = rule.regex;
2186
+ regex.lastIndex = this._position;
2187
+ const match = regex.exec(this._source);
2188
+ if (match == null) {
2189
+ return null;
2190
+ }
2191
+ if (match[0].length > 0) {
2192
+ return match;
2193
+ }
2194
+ // zero-length matches are only meaningful as (bounded) state transitions
2195
+ if (!rule.hasTransition || this._emptyTransitions >= MAX_EMPTY_TRANSITIONS) {
2196
+ return null;
2197
+ }
2198
+ this._emptyTransitions++;
2199
+ return match;
2200
+ }
2201
+ _emitMatch({ rule, match }, syntax) {
2202
+ const raw = match[0];
2203
+ if (rule.groupTypes == null) {
2204
+ this._append(rule.type, raw, syntax);
2205
+ return;
2206
+ }
2207
+ this._emitGroups(rule, match, syntax);
2208
+ }
2209
+ /** Splits a match into one token per (top-level) capture group; uncovered characters become `text` tokens. */
2210
+ _emitGroups(rule, match, syntax) {
2211
+ const indices = match.indices;
2212
+ const start = match.index, end = start + match[0].length, types = rule.groupTypes;
2213
+ let cursor = start;
2214
+ for (let i = 1; i < match.length; i++) {
2215
+ const range = indices[i];
2216
+ if (range == null || range[0] < cursor || range[1] === range[0]) {
2217
+ continue;
2218
+ }
2219
+ if (range[0] > cursor) {
2220
+ const gap = this._source.slice(cursor, range[0]);
2221
+ this._append(SyntaxTokenType.Text, gap, syntax);
2222
+ }
2223
+ const groupText = this._source.slice(range[0], range[1]);
2224
+ const groupType = types[i - 1] ?? types[types.length - 1];
2225
+ this._append(groupType, groupText, syntax);
2226
+ cursor = range[1];
2227
+ }
2228
+ if (cursor < end) {
2229
+ const rest = this._source.slice(cursor, end);
2230
+ this._append(SyntaxTokenType.Text, rest, syntax);
2231
+ }
2232
+ }
2233
+ _transition(rule, syntax) {
2234
+ const stack = this._stack;
2235
+ for (let i = 0; i < rule.pop && stack.length > 1; i++) {
2236
+ stack.pop();
2237
+ }
2238
+ if (rule.next != null) {
2239
+ this._top.state = rule.next;
2240
+ }
2241
+ if (rule.push != null) {
2242
+ stack.push({ syntax, state: rule.push, until: null });
2243
+ }
2244
+ if (rule.embed == null) {
2245
+ return;
2246
+ }
2247
+ const embedded = compileByName(rule.embed);
2248
+ if (embedded == null) {
2249
+ return;
2250
+ }
2251
+ stack.push({ syntax: embedded, state: embedded.start, until: rule.until });
2252
+ }
2253
+ /** Emits the pending unrecognized characters (if any) as a single `text` token. */
2254
+ _flushText() {
2255
+ if (this._emitted >= this._position) {
2256
+ return;
2257
+ }
2258
+ const text = this._source.slice(this._emitted, this._position);
2259
+ const syntax = this._top.syntax;
2260
+ this._append(SyntaxTokenType.Text, text, syntax);
2261
+ }
2262
+ _resolveType(type, raw, syntax) {
2263
+ if (!syntax.keywordSources.has(type)) {
2264
+ return type;
2265
+ }
2266
+ const previousText = this._previous?.raw;
2267
+ if (syntax.keywordExceptAfter.has(previousText)) {
2268
+ return type;
2269
+ }
2270
+ return syntax.keywords.get(raw) ?? type;
2271
+ }
2272
+ _append(type, raw, syntax) {
2273
+ const resolvedType = this._resolveType(type, raw, syntax);
2274
+ const token = {
2275
+ type: resolvedType, raw, text: raw,
2276
+ index: this._emitted,
2277
+ line: this._line,
2278
+ column: this._column,
2279
+ depth: this._stack.length - 1,
2280
+ syntax: syntax.name
2281
+ };
2282
+ this._tokens.push(token);
2283
+ this._advance(raw);
2284
+ if (!syntax.insignificant.has(resolvedType)) {
2285
+ this._previous = token;
2286
+ }
2287
+ }
2288
+ /** Moves the emitted offset and the line/column counters past `raw`. */
2289
+ _advance(raw) {
2290
+ this._emitted += raw.length;
2291
+ const lastNewline = raw.lastIndexOf('\n');
2292
+ if (lastNewline < 0) {
2293
+ this._column += raw.length;
2294
+ return;
2295
+ }
2296
+ for (let i = 0; i <= lastNewline; i++) {
2297
+ if (raw.charCodeAt(i) === 10) {
2298
+ this._line++;
2299
+ }
2300
+ }
2301
+ this._column = raw.length - lastNewline;
2302
+ }
2303
+ }
2304
+ // #endregion
2305
+ /** Used for unknown syntaxes: no rules, the whole source becomes a single `text` token. */
2306
+ const PLAIN_TEXT_SYNTAX = { name: SyntaxTokenType.Text, lexical: { states: {} } };
2307
+ /**
2308
+ * Language-agnostic lexical engine: tokenizes a source text according to a {@link Syntax} vocabulary.
2309
+ * - lossless: concatenating the tokens' `raw` reproduces the source exactly;
2310
+ * - linear: a single cursor and sticky regular expressions (compiled once per syntax);
2311
+ * - error tolerant: unrecognized characters become `text` tokens, it never throws.
2312
+ */
2313
+ class Scanner {
2314
+ /**
2315
+ * Tokenizes `source`.
2316
+ * @param source Source text.
2317
+ * @param syntax A {@link Syntax} or the name/alias of a registered one (see {@link Syntaxes}); an unknown one yields a single `text` token.
2318
+ * @returns The lossless, positioned token stream.
2319
+ */
2320
+ tokenize(source, syntax) {
2321
+ const text = source ?? '';
2322
+ const resolved = typeof syntax === 'string' ? Syntaxes.get(syntax) : syntax;
2323
+ const compiled = compileSyntax(resolved ?? PLAIN_TEXT_SYNTAX);
2324
+ const run = new ScanRun(text, compiled);
2325
+ return run.run();
2326
+ }
2327
+ }
2328
+
2329
+ //namespace Pacem.Compile {
2330
+ /** Default token type → CSS class mapping, matching the `code-*` classes styled by the theme (see `ui/markdown.css`). */
2331
+ const DefaultHighlightClasses = Object.freeze({
2332
+ 'keyword': 'code-keyword',
2333
+ 'preprocessor': 'code-keyword',
2334
+ 'string': 'code-string',
2335
+ 'regex': 'code-string',
2336
+ 'number': 'code-number',
2337
+ 'comment': 'code-comment',
2338
+ 'doc-comment': 'code-comment',
2339
+ 'tag': 'code-tag',
2340
+ 'tagname': 'code-tagname',
2341
+ 'attribute': 'code-attribute',
2342
+ });
2343
+ const HTML_ESCAPES = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' };
2344
+ function htmlEncode$1(text) {
2345
+ return text.replace(/[&<>"]/g, c => HTML_ESCAPES[c]);
2346
+ }
2347
+ /**
2348
+ * Renders a token stream (typically produced by the {@link Scanner}) as HTML: every token's `raw` text is HTML-encoded and,
2349
+ * when its type has a mapped class, wrapped in a `<span class="…">`.
2350
+ */
2351
+ class HighlightRenderer {
2352
+ /**
2353
+ * @param _classes Token type → CSS class mapping (defaults to {@link DefaultHighlightClasses}).
2354
+ */
2355
+ constructor(_classes = DefaultHighlightClasses) {
2356
+ this._classes = _classes;
2357
+ }
2358
+ render(tree) {
2359
+ let html = '';
2360
+ for (let token of tree ?? []) {
2361
+ html += this._renderToken(token);
2362
+ }
2363
+ return html;
2364
+ }
2365
+ _renderToken(token) {
2366
+ const encoded = htmlEncode$1(token.raw ?? '');
2367
+ const cssClass = this._classes[token.type];
2368
+ if (cssClass == null) {
2369
+ return encoded;
2370
+ }
2371
+ return `<span class="${cssClass}">${encoded}</span>`;
2372
+ }
2373
+ }
2374
+
1781
2375
  //namespace Pacem.Compile.Markdown {
1782
2376
  // #region HELPERS
1783
2377
  function htmlEncode(input) {
@@ -1795,6 +2389,84 @@
1795
2389
  }
1796
2390
  return input;
1797
2391
  }
2392
+ /** Splits a link/image destination into its url and optional title: `url "title"`, `url 'title'` or `url (title)`. */
2393
+ function splitUrlAndTitle(destination) {
2394
+ if (NullChecker.isNullOrEmpty(destination)) {
2395
+ return { url: destination };
2396
+ }
2397
+ const trimmed = destination.trim();
2398
+ const arr = /^(\S*)\s+(?:"([^"]*)"|'([^']*)'|\(([^)]*)\))\s*$/.exec(trimmed);
2399
+ if (arr == null) {
2400
+ return { url: trimmed };
2401
+ }
2402
+ const title = arr[2] ?? arr[3] ?? arr[4];
2403
+ return { url: arr[1], title };
2404
+ }
2405
+ function attributeEncode(input) {
2406
+ return (input ?? '').replace(/&(?!(?:#\d+|#x[\da-fA-F]+|\w+);)/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
2407
+ }
2408
+ /** Splits a table row into its (trimmed) cells: outer pipes are optional, `\|` is a literal pipe. */
2409
+ function splitTableCells(row) {
2410
+ let line = row.trim();
2411
+ if (line.startsWith('|')) {
2412
+ line = line.slice(1);
2413
+ }
2414
+ if (line.endsWith('|') && !line.endsWith('\\|')) {
2415
+ line = line.slice(0, -1);
2416
+ }
2417
+ const cells = line.split(/(?<!\\)\|/);
2418
+ return cells.map(c => c.trim().replace(/\\\|/g, '|'));
2419
+ }
2420
+ function tableAlignment(delimiter) {
2421
+ const left = delimiter.startsWith(':'), right = delimiter.endsWith(':');
2422
+ if (left && right) {
2423
+ return 'center';
2424
+ }
2425
+ if (right) {
2426
+ return 'right';
2427
+ }
2428
+ return left ? 'left' : null;
2429
+ }
2430
+ function indentationOf(line) {
2431
+ const arr = /^ */.exec(line);
2432
+ return arr[0].length;
2433
+ }
2434
+ /**
2435
+ * Splits a list block into its items, by indentation: a marker indented less than the current item's content
2436
+ * starts a sibling item; anything else (nested lists included) is item content, dedented by the content offset.
2437
+ */
2438
+ function splitListItems(raw) {
2439
+ const lines = raw.replace(/\t/g, ' ').split(/\r?\n/);
2440
+ const items = [];
2441
+ for (let line of lines) {
2442
+ const current = items[items.length - 1];
2443
+ const marker = LISTMARKER.exec(line);
2444
+ if (marker != null && (current == null || marker[1].length < current.indent)) {
2445
+ const contentIndent = marker[0].length;
2446
+ const firstLine = line.slice(contentIndent);
2447
+ items.push({ indent: contentIndent, lines: [firstLine] });
2448
+ continue;
2449
+ }
2450
+ if (current == null) {
2451
+ continue;
2452
+ }
2453
+ const dedent = Math.min(current.indent, indentationOf(line));
2454
+ const content = line.slice(dedent);
2455
+ current.lines.push(content);
2456
+ }
2457
+ return items.map(i => i.lines.join('\n').replace(/\s+$/, ''));
2458
+ }
2459
+ /**
2460
+ * Code meant for a syntax highlighter: `MarkdownService` escapes every `<` of its input (no html allowed), hence
2461
+ * `&lt;` is turned back into `<` (the highlighter encodes everything it renders anyway).
2462
+ */
2463
+ function decodeCode(code) {
2464
+ return (code ?? '').replace(/&lt;/g, '<');
2465
+ }
2466
+ /** Highlights fenced code blocks of the registered languages (see {@link Syntaxes}). */
2467
+ const scanner = new Scanner();
2468
+ /** Fenced code in this vocabulary also gets the `code-markup` class. */
2469
+ const MARKUP_SYNTAX = 'markup';
1798
2470
  function regexMatchIndex(arr) {
1799
2471
  return arr?.index || void 0;
1800
2472
  }
@@ -1822,27 +2494,51 @@
1822
2494
  }
1823
2495
  // #endregion
1824
2496
  const COMMENT = /(<!--)([\s\S]*?)(-->|$)/;
1825
- const HR = /^ {0,3}(={3,}|_{3,}|-{3,})(\r?\n|$)/;
2497
+ const HR = /^ {0,3}(={3,}|(?:_[ \t]*){3,}|(?:-[ \t]*){3,}|(?:\*[ \t]*){3,})(\r?\n|$)/;
1826
2498
  const HEADING = /^ {0,3}(#{1,6})\s+([^\n]+)(\r?\n|$)+/;
1827
2499
  const HEADING1 = /^ {0,3}([^\n]+)(\r?\n=+(\r?\n|$))+/;
1828
2500
  const HEADING2 = /^ {0,3}([^\n]+)(\r?\n-+(\r?\n|$))+/;
1829
2501
  const CODEBLOCK = /^ {4,}.+(\r?\n {4,}.+)*(\r?\n|$)+/;
1830
2502
  const FENCES = /^ {0,3}``` *([^\n]*)\r?\n(([\s\S](?!\r?\n {0,3}```))*.(\r?\n)+) {0,3}```(\r?\n|$)+/;
1831
2503
  const BLOCKQUOTE = /^ {0,3}> *([^\n]+)(\r?\n {0,3}> *([^\n]*))*(\r?\n|$)+/;
1832
- const ULIST = /^( {0,3}([-+]).*\r?\n|$)((?!(?: {0,3}(?:\d+[.\)]|\r?\n\S))).*(\r?\n|$))*/;
1833
- const OLIST = /^( {0,3}(\d+[.\)]).*\r?\n|$)((?!(?: {0,3}(?:[+-]|\r?\n\S))).*(\r?\n|$))*/;
1834
- const LISTITEM = /^(?:[-+*]|\d+[.\)])/m;
1835
- const PARAGRAPH = /^(?! {4,})(.+(\n(?! {0,3}```)(?! {0,3}([-+]|\d+[.\)]))(?! {4,})(?! {0,3}>).+)*)/;
2504
+ // (`*` bullets need a following space, not to be mistaken for emphasis; nested lists of the other kind are indented)
2505
+ const ULIST = /^( {0,3}([-+]|\*(?=[ \t])).*(\r?\n|$))((?!(?: ?\d+[.\)]|\r?\n\S)).*(\r?\n|$))*/;
2506
+ const OLIST = /^( {0,3}(\d+[.\)]).*(\r?\n|$))((?!(?: {0,2}(?:[-+]|\*[ \t])|\r?\n\S)).*(\r?\n|$))*/;
2507
+ /** A list item marker, with its indentation and the spaces following it. */
2508
+ const LISTMARKER = /^( *)([-+*]|\d+[.\)])([ \t]+|$)/;
2509
+ /** GFM task list item: `[ ]` or `[x]` at the beginning of the item. */
2510
+ const TASK = /^\[([ xX])\][ \t]+/;
2511
+ // (whitespace-only lines are blank lines: they never start nor continue a paragraph)
2512
+ const PARAGRAPH = /^(?! {4,})(?=[ \t]*\S)(.+(\n(?![ \t]*(?:\n|$))(?! {0,3}```)(?! {0,3}([-+]|\*[ \t]|\d+[.\)]))(?! {0,3}#{1,6}[ \t])(?! {4,})(?! {0,3}>).+)*)/;
1836
2513
  const REF = /^ {0,3}(!?)\[([^\]]+)\]: +(\S+)(?: +['"\(](.+)['"\)])?(\r?\n|$)+/;
1837
- const IMAGE_OR_LINK = /!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?(\[[^\[\]]*\]|\([^\(\)]*\))?/;
2514
+ // (an escaped `\[` never opens a link)
2515
+ const IMAGE_OR_LINK = /(?<!\\)!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?(\[[^\[\]]*\]|\([^\(\)]*\))?/;
1838
2516
  const EMPHASIZED_LINK = /(\*\*?|__?)(\[[^\]]*]\([^\)]+\))\1/;
1839
2517
  const CODE = /(?:(?<!\\)`)(.+?)(?:(?<!\\)`)/;
1840
- const EMPHASIZE = /(\*\*?|__?)((?!\1\s?)(?:(?!\1)[\S\s])*[^\s])\1/;
1841
- const STRIKE = /(~~?|__?)(?!\1\s?)([^\s][\s\S]*?(?!\1)[^\s])\1/;
2518
+ // (underscores never emphasize inside words: `snake_case_word`)
2519
+ const EMPHASIZE = /(\*\*?)((?!\1\s?)(?:(?!\1)[\S\s])*[^\s])\1|(?<![A-Za-z0-9])(__?)((?!\3\s?)(?:(?!\3)[\S\s])*[^\s])\3(?![A-Za-z0-9])/;
2520
+ // (underscores are emphasis markers only: `snake_case_word` must not strike anything through)
2521
+ const STRIKE = /(~~?)(?!\1\s?)([^\s][\s\S]*?(?!\1)[^\s])\1/;
1842
2522
  const QUOTE = /(:")(?!":\s?)([^\s][\s\S]*?(?!":)[^\s])":/;
1843
2523
  const SPACE = /^\s+/;
1844
- const LINEBREAK = / {2,}\r?\n/;
2524
+ /** Blank lines between blocks (it keeps the indentation of the next line, which matters to indented code blocks). */
2525
+ const BLOCK_SPACE = /^(?:[ \t]*\r?\n)+|^\s+/;
2526
+ const LINEBREAK = /(?: {2,}|\\)\r?\n/;
1845
2527
  const TEXT = /^[^\r\n]+/;
2528
+ /** Backslash escapes of ASCII punctuation (CommonMark); `\&lt;` is an escaped `<` already escaped by `MarkdownService`. */
2529
+ const ESCAPE = /\\(&lt;|[!"#$%&'()*+,\-./:;<=>?@\[\\\]^_`{|}~])/;
2530
+ /** Escaped characters that must stay text once rendered as html. */
2531
+ const ESCAPED_HTML = { '<': '&lt;', '&lt;': '&lt;', '&': '&amp;' };
2532
+ /** `<https://…>`, `<mailto:…>` and `<user@domain>` (the `<` may come escaped, e.g. by `MarkdownService`). */
2533
+ const AUTOLINK = /(?:<|&lt;)((?:https?|ftp|mailto):[^\s<>]+|[\w.+-]+@[\w-]+(?:\.[\w-]+)+)>/;
2534
+ /** GFM bare URLs (`https://…`, `www.…`), trailing punctuation excluded. */
2535
+ const BARE_URL = /(?<![\w\/\\(\[<"'=;&@])(?:https?:\/\/|www\.)[^\s<>]*[^\s<>.,:;"'!?)\]*_~]/;
2536
+ /** GFM footnote reference `[^id]`. */
2537
+ const FOOTNOTE_REF = /\[\^([^\]\s]+)\]/;
2538
+ /** GFM footnote definition `[^id]: text` (continuation lines indented). */
2539
+ const FOOTNOTE_DEF = /^ {0,3}\[\^([^\]\s]+)\]: *([^\r\n]*(?:\r?\n {2,}[^\r\n]+)*)(\r?\n|$)+/;
2540
+ /** GFM table: header row, delimiter row, then body rows (up to a blank line). */
2541
+ const TABLE = /^ {0,3}(\|?[^\r\n]*\|[^\r\n]*)\r?\n {0,3}(\|? *:?-+:? *(?:\| *:?-+:? *)*\|?) *(?:\r?\n|$)((?:(?! {0,3}(?:\r?\n|$))[^\r\n]*\|[^\r\n]*(?:\r?\n|$))*)/;
1846
2542
  var MARKDOWN_TYPES;
1847
2543
  (function (MARKDOWN_TYPES) {
1848
2544
  MARKDOWN_TYPES["Html"] = "html";
@@ -1867,6 +2563,11 @@
1867
2563
  MARKDOWN_TYPES["UnorderedList"] = "ul";
1868
2564
  MARKDOWN_TYPES["ListItem"] = "listitem";
1869
2565
  MARKDOWN_TYPES["Reference"] = "ref";
2566
+ MARKDOWN_TYPES["Table"] = "table";
2567
+ MARKDOWN_TYPES["TableRow"] = "tr";
2568
+ MARKDOWN_TYPES["TableCell"] = "td";
2569
+ MARKDOWN_TYPES["FootnoteReference"] = "fnref";
2570
+ MARKDOWN_TYPES["FootnoteDefinition"] = "fndef";
1870
2571
  })(MARKDOWN_TYPES || (MARKDOWN_TYPES = {}));
1871
2572
  var MARKDOWN_RULES;
1872
2573
  (function (MARKDOWN_RULES) {
@@ -1889,6 +2590,12 @@
1889
2590
  MARKDOWN_RULES[MARKDOWN_RULES["Quote"] = 16] = "Quote";
1890
2591
  MARKDOWN_RULES[MARKDOWN_RULES["Code"] = 17] = "Code";
1891
2592
  MARKDOWN_RULES[MARKDOWN_RULES["LineBreak"] = 18] = "LineBreak";
2593
+ MARKDOWN_RULES[MARKDOWN_RULES["FootnoteDefinition"] = 19] = "FootnoteDefinition";
2594
+ MARKDOWN_RULES[MARKDOWN_RULES["Table"] = 20] = "Table";
2595
+ MARKDOWN_RULES[MARKDOWN_RULES["Escape"] = 21] = "Escape";
2596
+ MARKDOWN_RULES[MARKDOWN_RULES["AutoLink"] = 22] = "AutoLink";
2597
+ MARKDOWN_RULES[MARKDOWN_RULES["FootnoteReference"] = 23] = "FootnoteReference";
2598
+ MARKDOWN_RULES[MARKDOWN_RULES["BareUrl"] = 24] = "BareUrl";
1892
2599
  // lowest priority
1893
2600
  MARKDOWN_RULES[MARKDOWN_RULES["Text"] = 254] = "Text";
1894
2601
  MARKDOWN_RULES[MARKDOWN_RULES["Space"] = 255] = "Space";
@@ -1909,7 +2616,8 @@
1909
2616
  type: MARKDOWN_TYPES.Html, index, raw: arr[0], text: arr[2]
1910
2617
  }];
1911
2618
  if (index > 0) {
1912
- const left = input.substr(0, index), tokens = lexer.tokenize(left, blockRules);
2619
+ // (the top-level rules: reference and footnote definitions may precede a comment)
2620
+ const left = input.substr(0, index), tokens = lexer.tokenize(left, topRules);
1913
2621
  Array.prototype.unshift.apply(output, tokens);
1914
2622
  }
1915
2623
  return output;
@@ -1924,6 +2632,16 @@
1924
2632
  }];
1925
2633
  })
1926
2634
  },
2635
+ // footnote definition (rendered at the end of the document, see HtmlRenderer)
2636
+ [MARKDOWN_RULES.FootnoteDefinition]: {
2637
+ exec: (input, lexer) => match(input, FOOTNOTE_DEF, arr => {
2638
+ const raw = arr[0], id = normalizeReference(arr[1]), text = arr[2].replace(/\r?\n {2,}/g, ' ');
2639
+ return [{
2640
+ type: MARKDOWN_TYPES.FootnoteDefinition, index: regexMatchIndex(arr), raw, text, id,
2641
+ tokens: lexer.tokenize(text, inlineRules)
2642
+ }];
2643
+ })
2644
+ },
1927
2645
  };
1928
2646
  const blockExtra = [], inlineExtra = [], codeExtra = {};
1929
2647
  for (let r of extra) {
@@ -1982,21 +2700,61 @@
1982
2700
  // link/image
1983
2701
  [MARKDOWN_RULES.ImageOrLink]: {
1984
2702
  exec: (input, lexer) => inlineMatch(input, IMAGE_OR_LINK, lexer, arr => {
1985
- const index = regexMatchIndex(arr), raw = arr[0], image = raw.charAt(0) === '!', text = arr[1], url = extractUrl(arr[2]), ref = normalizeReference(extractReference(arr[2]) ?? text);
2703
+ const index = regexMatchIndex(arr), raw = arr[0], image = raw.charAt(0) === '!', text = arr[1], destination = extractUrl(arr[2]), reference = extractReference(arr[2]) ?? text, ref = normalizeReference(reference);
2704
+ // `(url "title")`
2705
+ const { url, title } = splitUrlAndTitle(destination);
2706
+ const tokens = lexer.tokenize(text, inlineRules);
1986
2707
  if (image) {
1987
2708
  return [{
1988
2709
  index, type: url ? MARKDOWN_TYPES.Image : MARKDOWN_TYPES.ImageLike,
1989
- raw, text, src: url, ref,
1990
- tokens: lexer.tokenize(text, inlineRules)
1991
- }];
1992
- }
1993
- else {
1994
- return [{
1995
- index, type: url ? MARKDOWN_TYPES.Link : MARKDOWN_TYPES.LinkLike,
1996
- raw, text, href: url, ref,
1997
- tokens: lexer.tokenize(text, inlineRules)
2710
+ raw, text, src: url, title, ref, tokens
1998
2711
  }];
1999
2712
  }
2713
+ return [{
2714
+ index, type: url ? MARKDOWN_TYPES.Link : MARKDOWN_TYPES.LinkLike,
2715
+ raw, text, href: url, title, ref, tokens
2716
+ }];
2717
+ })
2718
+ },
2719
+ // backslash escapes: the escaped character is plain text
2720
+ [MARKDOWN_RULES.Escape]: {
2721
+ exec: (input, lexer) => inlineMatch(input, ESCAPE, lexer, arr => {
2722
+ const text = ESCAPED_HTML[arr[1]] ?? arr[1];
2723
+ return [{
2724
+ index: regexMatchIndex(arr), raw: arr[0], text, type: MARKDOWN_TYPES.Text
2725
+ }];
2726
+ })
2727
+ },
2728
+ // <https://…>, <user@domain>
2729
+ [MARKDOWN_RULES.AutoLink]: {
2730
+ exec: (input, lexer) => inlineMatch(input, AUTOLINK, lexer, arr => {
2731
+ const text = arr[1];
2732
+ const isEmail = !/^[a-z]+:/i.test(text);
2733
+ const href = isEmail ? 'mailto:' + text : text;
2734
+ return [{
2735
+ index: regexMatchIndex(arr), raw: arr[0], text, href, type: MARKDOWN_TYPES.Link,
2736
+ tokens: [{ type: MARKDOWN_TYPES.Text, raw: text, text }]
2737
+ }];
2738
+ })
2739
+ },
2740
+ // bare https://… or www.… urls
2741
+ [MARKDOWN_RULES.BareUrl]: {
2742
+ exec: (input, lexer) => inlineMatch(input, BARE_URL, lexer, arr => {
2743
+ const text = arr[0];
2744
+ const href = /^www\./i.test(text) ? 'http://' + text : text;
2745
+ return [{
2746
+ index: regexMatchIndex(arr), raw: text, text, href, type: MARKDOWN_TYPES.Link,
2747
+ tokens: [{ type: MARKDOWN_TYPES.Text, raw: text, text }]
2748
+ }];
2749
+ })
2750
+ },
2751
+ // [^footnote]
2752
+ [MARKDOWN_RULES.FootnoteReference]: {
2753
+ exec: (input, lexer) => inlineMatch(input, FOOTNOTE_REF, lexer, arr => {
2754
+ const id = normalizeReference(arr[1]);
2755
+ return [{
2756
+ index: regexMatchIndex(arr), raw: arr[0], text: arr[1], id, type: MARKDOWN_TYPES.FootnoteReference
2757
+ }];
2000
2758
  })
2001
2759
  },
2002
2760
  // code
@@ -2024,10 +2782,12 @@
2024
2782
  // italic/bold
2025
2783
  [MARKDOWN_RULES.Emphasize]: {
2026
2784
  exec: (input, lexer) => inlineMatch(input, EMPHASIZE, lexer, arr => {
2027
- if (arr.length < 3 || NullChecker.isNullOrEmpty(arr[2])) {
2785
+ // `*` (groups 1-2) or word-bounded `_` (groups 3-4) markers
2786
+ const marker = arr[1] ?? arr[3], text = arr[2] ?? arr[4];
2787
+ if (NullChecker.isNullOrEmpty(text)) {
2028
2788
  return null;
2029
2789
  }
2030
- const type = arr[1].length > 1 ? MARKDOWN_TYPES.Bold : MARKDOWN_TYPES.Italic, text = arr[2];
2790
+ const type = marker.length > 1 ? MARKDOWN_TYPES.Bold : MARKDOWN_TYPES.Italic;
2031
2791
  return [{
2032
2792
  index: regexMatchIndex(arr),
2033
2793
  type, raw: arr[0], text, tokens: lexer.tokenize(text, inlineRules)
@@ -2096,7 +2856,20 @@
2096
2856
  inPlaceInlineExec(input, output, lexer);
2097
2857
  return output;
2098
2858
  };
2099
- const inlineRules = Object.values(inline);
2859
+ // explicit priorities: footnote references before `[links]`, escapes before emphasis, bare urls last
2860
+ const inlineRules = [
2861
+ inline[MARKDOWN_RULES.FootnoteReference],
2862
+ inline[MARKDOWN_RULES.EmphasizedLink],
2863
+ inline[MARKDOWN_RULES.ImageOrLink],
2864
+ inline[MARKDOWN_RULES.Escape],
2865
+ inline[MARKDOWN_RULES.AutoLink],
2866
+ inline[MARKDOWN_RULES.Emphasize],
2867
+ inline[MARKDOWN_RULES.StrikeThrough],
2868
+ inline[MARKDOWN_RULES.Quote],
2869
+ inline[MARKDOWN_RULES.Code],
2870
+ inline[MARKDOWN_RULES.LineBreak],
2871
+ inline[MARKDOWN_RULES.BareUrl],
2872
+ ];
2100
2873
  Array.prototype.push.apply(inlineRules, inlineExtra);
2101
2874
  inlineRules.push(inlinelowpriority[MARKDOWN_RULES.Text], inlinelowpriority[MARKDOWN_RULES.Space]);
2102
2875
  // #endregion
@@ -2115,7 +2888,12 @@
2115
2888
  // paragraph
2116
2889
  [MARKDOWN_RULES.Paragraph]: {
2117
2890
  exec: (input, lexer) => match(input, PARAGRAPH, arr => {
2118
- const raw = arr[0], text = raw;
2891
+ // leading whitespace of paragraph lines is not content (CommonMark)
2892
+ const raw = arr[0], text = raw.replace(/^[ \t]+/gm, '');
2893
+ // whitespace-only lines are blank lines, not (empty) paragraphs
2894
+ if (text.trim().length === 0) {
2895
+ return null;
2896
+ }
2119
2897
  return [{
2120
2898
  type: MARKDOWN_TYPES.Paragraph, raw, text,
2121
2899
  // recursion (inline rules only)
@@ -2123,12 +2901,57 @@
2123
2901
  }];
2124
2902
  })
2125
2903
  },
2126
- // sparse lines
2127
- [MARKDOWN_RULES.Space]: inlinelowpriority[MARKDOWN_RULES.Space]
2904
+ // blank lines (the indentation of the following line is kept: it matters to indented code blocks)
2905
+ [MARKDOWN_RULES.Space]: {
2906
+ exec: (input) => match(input, BLOCK_SPACE, arr => {
2907
+ const raw = arr[0];
2908
+ return [{
2909
+ type: MARKDOWN_TYPES.Space, raw, text: raw
2910
+ }];
2911
+ })
2912
+ }
2128
2913
  };
2129
2914
  const tokenizeListItem = (text, lexer) => {
2130
- const grammar = !/^ {0,3}(`{3,}|$|>|[+-]\s|\d+[.\)])/m.test(text) ? inlineRules : blockRules;
2131
- return lexer.tokenize(text, grammar);
2915
+ const needsBlocks = /^ {0,3}(`{3,}|$|>|[-+*]\s|\d+[.\)]|#{1,6}\s|\|)/m.test(text);
2916
+ if (!needsBlocks) {
2917
+ return lexer.tokenize(text, inlineRules);
2918
+ }
2919
+ const tokens = lexer.tokenize(text, blockRules);
2920
+ // tight items (no blank lines) keep their first paragraph unwrapped, e.g. `<li>a<ul>…</ul></li>`
2921
+ const first = tokens[0];
2922
+ const tight = !/\n[ \t]*\n/.test(text);
2923
+ if (!tight || first?.type !== MARKDOWN_TYPES.Paragraph) {
2924
+ return tokens;
2925
+ }
2926
+ const inner = first.tokens ?? [];
2927
+ const rest = tokens.slice(1);
2928
+ return inner.concat(rest);
2929
+ };
2930
+ const tokenizeListItems = (raw, lexer) => {
2931
+ const items = splitListItems(raw);
2932
+ const nonEmpty = items.filter(i => !NullChecker.isNullOrEmpty(i));
2933
+ return nonEmpty.map(item => {
2934
+ // GFM task list item: `[ ]`/`[x]`
2935
+ const task = TASK.exec(item);
2936
+ const text = task ? item.slice(task[0].length) : item;
2937
+ const tokens = tokenizeListItem(text, lexer);
2938
+ if (task == null) {
2939
+ return { type: MARKDOWN_TYPES.ListItem, raw: item, text, tokens };
2940
+ }
2941
+ const checked = task[1] !== ' ';
2942
+ return { type: MARKDOWN_TYPES.ListItem, raw: item, text, tokens, task: true, checked };
2943
+ });
2944
+ };
2945
+ const tokenizeTableRow = (cells, align, header, lexer) => {
2946
+ const tokens = align.map((cellAlign, i) => {
2947
+ // missing cells are empty, exceeding ones are ignored (GFM)
2948
+ const text = cells[i] ?? '';
2949
+ const cellTokens = lexer.tokenize(text, inlineRules);
2950
+ return { type: MARKDOWN_TYPES.TableCell, raw: text, text, header, align: cellAlign, tokens: cellTokens };
2951
+ });
2952
+ const raw = cells.join(' | ');
2953
+ const row = { type: MARKDOWN_TYPES.TableRow, raw, text: raw, header, tokens };
2954
+ return row;
2132
2955
  };
2133
2956
  const block = {
2134
2957
  // hr
@@ -2143,7 +2966,8 @@
2143
2966
  // headings
2144
2967
  [MARKDOWN_RULES.Heading]: {
2145
2968
  exec: (input, lexer) => match(input, HEADING, arr => {
2146
- const text = arr[2];
2969
+ // optional closing sequence: `## Title ##`
2970
+ const text = arr[2].replace(/[ \t]+#+[ \t]*$/, '');
2147
2971
  return [{
2148
2972
  type: MARKDOWN_TYPES.Heading, raw: arr[0], text, level: arr[1].length, ref: normalizeReference(text),
2149
2973
  tokens: lexer.tokenize(text, inlineRules)
@@ -2171,60 +2995,86 @@
2171
2995
  // codeblock
2172
2996
  [MARKDOWN_RULES.CodeBlock]: {
2173
2997
  exec: (input, lexer) => match(input, CODEBLOCK, arr => {
2174
- const raw = arr[0], text = htmlEncode(raw);
2998
+ // indented code: 4 spaces of indentation are not part of the code
2999
+ const raw = arr[0], code = raw.replace(/^ {4}/gm, '').replace(/\s+$/, ''), text = htmlEncode(code);
3000
+ // whitespace-only indented lines are blank lines, not code
3001
+ if (code.length === 0) {
3002
+ return null;
3003
+ }
2175
3004
  return [{
2176
3005
  type: MARKDOWN_TYPES.CodeBlock, raw, text,
2177
- tokens: lexer.tokenize(text, [])
3006
+ tokens: lexer.tokenize(text, getLangGrammar(''))
2178
3007
  }];
2179
3008
  })
2180
3009
  },
2181
3010
  // codeblock
2182
3011
  [MARKDOWN_RULES.Fences]: {
2183
3012
  exec: (input, lexer) => match(input, FENCES, arr => {
2184
- const text = htmlEncode(arr[2]), lang = arr[1]?.toLowerCase();
3013
+ // the info string may carry more than the language (e.g. ```js title)
3014
+ const info = (arr[1] ?? '').trim(), words = info.split(/\s+/), lang = words[0].toLowerCase();
3015
+ // custom `code` rules (see ExtraRule) win over the registered vocabularies
3016
+ const syntax = lang in codeExtra ? null : Syntaxes.get(lang);
3017
+ if (syntax == null) {
3018
+ const text = htmlEncode(arr[2]);
3019
+ return [{
3020
+ type: MARKDOWN_TYPES.CodeBlock, raw: arr[0], text, lang,
3021
+ tokens: lexer.tokenize(text, getLangGrammar(lang))
3022
+ }];
3023
+ }
3024
+ // highlighted by the language-agnostic Scanner (see HtmlRenderer)
3025
+ const code = decodeCode(arr[2]);
3026
+ const tokens = scanner.tokenize(code, syntax);
2185
3027
  return [{
2186
- type: MARKDOWN_TYPES.CodeBlock, raw: arr[0], text, lang,
2187
- tokens: lexer.tokenize(text, getLangGrammar(lang))
3028
+ type: MARKDOWN_TYPES.CodeBlock, raw: arr[0], text: code, lang, syntax: syntax.name, highlighted: true, tokens
2188
3029
  }];
2189
3030
  })
2190
3031
  },
2191
3032
  // blockquote
2192
3033
  [MARKDOWN_RULES.BlockQuote]: {
2193
3034
  exec: (input, lexer) => match(input, BLOCKQUOTE, arr => {
2194
- const raw = arr[0], text = raw.replace(/^ {0,3}> */gm, '');
3035
+ const raw = arr[0], text = raw.replace(/^ {0,3}> ?/gm, '');
2195
3036
  return [{
2196
3037
  type: MARKDOWN_TYPES.BlockQuote, raw, text,
2197
- // recursion
2198
- tokens: lexer.tokenize(text, [
2199
- lowpriority[MARKDOWN_RULES.Paragraph],
2200
- lowpriority[MARKDOWN_RULES.Space]
2201
- ])
3038
+ // recursion: any block (nested quotes, lists, code…)
3039
+ tokens: lexer.tokenize(text, blockRules)
2202
3040
  }];
2203
3041
  })
2204
3042
  },
2205
3043
  // lists
2206
3044
  [MARKDOWN_RULES.OrderedList]: {
2207
3045
  exec: (input, lexer) => match(input, OLIST, arr => {
2208
- const raw = arr[0], items = raw.replace(/^ {0,3}/gm, '').split(LISTITEM).filter(i => !NullChecker.isNullOrEmpty(i)).map(i => i.trim());
3046
+ const raw = arr[0], startMatch = /\d+/.exec(raw), start = parseInt(startMatch[0]), tokens = tokenizeListItems(raw, lexer);
2209
3047
  return [{
2210
- type: MARKDOWN_TYPES.OrderedList, start: parseInt(/\d/.exec(arr[0])[0]), raw, text: raw, tokens: items.map(i => {
2211
- // avoid paragraphs if not strictly needed
2212
- return {
2213
- type: MARKDOWN_TYPES.ListItem, raw: i, text: i, tokens: tokenizeListItem(i, lexer)
2214
- };
2215
- })
3048
+ type: MARKDOWN_TYPES.OrderedList, start, raw, text: raw, tokens
2216
3049
  }];
2217
3050
  })
2218
3051
  },
2219
3052
  [MARKDOWN_RULES.UnorderedList]: {
2220
3053
  exec: (input, lexer) => match(input, ULIST, arr => {
2221
- const raw = arr[0], items = raw.replace(/^ {0,3}/gm, '').split(LISTITEM).filter(i => !NullChecker.isNullOrEmpty(i)).map(i => i.trim());
3054
+ const raw = arr[0], tokens = tokenizeListItems(raw, lexer);
3055
+ return [{
3056
+ type: MARKDOWN_TYPES.UnorderedList, raw, text: raw, tokens
3057
+ }];
3058
+ })
3059
+ },
3060
+ // GFM tables
3061
+ [MARKDOWN_RULES.Table]: {
3062
+ exec: (input, lexer) => match(input, TABLE, arr => {
3063
+ const headers = splitTableCells(arr[1]), delimiters = splitTableCells(arr[2]);
3064
+ // the delimiter row must match the header row
3065
+ if (headers.length !== delimiters.length) {
3066
+ return null;
3067
+ }
3068
+ const align = delimiters.map(tableAlignment);
3069
+ const bodyLines = arr[3].split(/\r?\n/).filter(l => l.trim().length > 0);
3070
+ const headerRow = tokenizeTableRow(headers, align, true, lexer);
3071
+ const bodyRows = bodyLines.map(line => {
3072
+ const cells = splitTableCells(line);
3073
+ return tokenizeTableRow(cells, align, false, lexer);
3074
+ });
3075
+ const rows = [headerRow].concat(bodyRows);
2222
3076
  return [{
2223
- type: MARKDOWN_TYPES.UnorderedList, raw, text: raw, tokens: items.map(i => {
2224
- return {
2225
- type: MARKDOWN_TYPES.ListItem, raw: i, text: i, tokens: tokenizeListItem(i, lexer)
2226
- };
2227
- })
3077
+ type: MARKDOWN_TYPES.Table, raw: arr[0], text: arr[0], align, tokens: rows
2228
3078
  }];
2229
3079
  })
2230
3080
  }
@@ -2233,13 +3083,16 @@
2233
3083
  Array.prototype.push.apply(blockRules, blockExtra);
2234
3084
  blockRules.push(lowpriority[MARKDOWN_RULES.Paragraph], lowpriority[MARKDOWN_RULES.Space]);
2235
3085
  // #endregion
2236
- return [
3086
+ const topRules = [
2237
3087
  // comment first
2238
3088
  inert[MARKDOWN_RULES.Comment],
3089
+ // (before references: `[^id]: …` would match them too)
3090
+ inert[MARKDOWN_RULES.FootnoteDefinition],
2239
3091
  inert[MARKDOWN_RULES.Reference]
2240
3092
  ].concat(
2241
3093
  // block rules contain recursive inline rules (at least)
2242
3094
  blockRules);
3095
+ return topRules;
2243
3096
  }
2244
3097
  // #region HTML RENDERER HELPER
2245
3098
  // meant to help in unnecessary spaces replacement.
@@ -2248,11 +3101,29 @@
2248
3101
  function trim(input) {
2249
3102
  return input?.replace(TRIM_INSIDE, '\n').trim();
2250
3103
  }
2251
- // #endregion
3104
+ function titleAttribute(title) {
3105
+ if (NullChecker.isNullOrEmpty(title)) {
3106
+ return '';
3107
+ }
3108
+ const encoded = attributeEncode(title);
3109
+ return ` title="${encoded}"`;
3110
+ }
3111
+ function alignAttribute(align) {
3112
+ if (align == null) {
3113
+ return '';
3114
+ }
3115
+ return ` style="text-align: ${align}"`;
3116
+ }
2252
3117
  /** Renders a Markdown token tree, as produced by the grammar returned by {@link getGrammar}, into HTML markup. */
2253
3118
  class HtmlRenderer {
2254
3119
  constructor(_extraItemRender = (token, __) => token?.text || '') {
2255
3120
  this._extraItemRender = _extraItemRender;
3121
+ /** Link/image reference definitions of the whole document (they may be referenced from any depth). */
3122
+ this.#references = [];
3123
+ /** Footnote definitions by id, and the order of their first reference (their numbering). */
3124
+ this.#footnotes = new Map();
3125
+ this.#footnoteOrder = [];
3126
+ this.#highlighter = new HighlightRenderer();
2256
3127
  this._renderContent = (token) => {
2257
3128
  const children = token.tokens;
2258
3129
  if (NullChecker.isNullOrEmpty(children)) {
@@ -2261,10 +3132,144 @@
2261
3132
  return this._render(children);
2262
3133
  };
2263
3134
  }
3135
+ /** Link/image reference definitions of the whole document (they may be referenced from any depth). */
3136
+ #references;
3137
+ /** Footnote definitions by id, and the order of their first reference (their numbering). */
3138
+ #footnotes;
3139
+ #footnoteOrder;
3140
+ #highlighter;
3141
+ /** Collects the reference and footnote definitions of the whole tree. */
3142
+ _collectDefinitions(tree) {
3143
+ for (let token of tree ?? []) {
3144
+ if (token?.type === MARKDOWN_TYPES.Reference) {
3145
+ this.#references.push(token);
3146
+ }
3147
+ if (token?.type === MARKDOWN_TYPES.FootnoteDefinition && !this.#footnotes.has(token['id'])) {
3148
+ this.#footnotes.set(token['id'], token);
3149
+ }
3150
+ this._collectDefinitions(token?.tokens);
3151
+ }
3152
+ }
3153
+ _findReference(ref, image) {
3154
+ if (NullChecker.isNullOrEmpty(ref)) {
3155
+ return undefined;
3156
+ }
3157
+ const key = ref.toLowerCase();
3158
+ return this.#references.find(t => (t.image === true) === image && t.id?.toLowerCase() === key);
3159
+ }
3160
+ _renderCodeBlock(token) {
3161
+ const R = CARRIAGE_RETURN_PLACEHOLDER;
3162
+ const lang = token['lang'];
3163
+ if (token['highlighted'] !== true) {
3164
+ const classAttribute = lang ? (' class="' + lang + '"') : '';
3165
+ const content = this._renderContent(token);
3166
+ return `<pre>${R}<code${classAttribute}>${content}</code>${R}</pre>${R}`;
3167
+ }
3168
+ // `code-highlighted` (and `code-markup`) let the theme style any registered language
3169
+ const classes = [lang, 'code-highlighted'];
3170
+ if (token['syntax'] === MARKUP_SYNTAX) {
3171
+ classes.push('code-markup');
3172
+ }
3173
+ const classList = attributeEncode(classes.join(' '));
3174
+ const highlighted = this.#highlighter.render(token.tokens);
3175
+ return `<pre>${R}<code class="${classList}">${highlighted}</code>${R}</pre>${R}`;
3176
+ }
3177
+ _renderImage(token) {
3178
+ let src = token.src, title = token.title;
3179
+ if (!src) {
3180
+ const reference = this._findReference(token.ref, true);
3181
+ src = reference?.url;
3182
+ title = reference?.title;
3183
+ }
3184
+ if (!src) {
3185
+ const content = this._renderContent(token);
3186
+ return `![${content}]`;
3187
+ }
3188
+ const titleAttr = titleAttribute(title);
3189
+ return `<img src="${src}" alt="${token.text}"${titleAttr} />`;
3190
+ }
3191
+ _renderLink(token) {
3192
+ let href = token.href, title = token.title;
3193
+ if (!href) {
3194
+ const reference = this._findReference(token.ref, false);
3195
+ href = reference?.url;
3196
+ title = reference?.title;
3197
+ }
3198
+ const content = this._renderContent(token);
3199
+ if (!href) {
3200
+ return `[${content}]`;
3201
+ }
3202
+ const titleAttr = titleAttribute(title);
3203
+ return `<a href="${href}"${titleAttr}>${content}</a>`;
3204
+ }
3205
+ _renderListItem(token) {
3206
+ const R = CARRIAGE_RETURN_PLACEHOLDER;
3207
+ const content = this._renderContent(token);
3208
+ if (token['task'] !== true) {
3209
+ return `\t<li>${content}</li>${R}`;
3210
+ }
3211
+ const checked = token['checked'] === true ? ' checked' : '';
3212
+ return `\t<li class="task-list-item"><input type="checkbox" disabled${checked} /> ${content}</li>${R}`;
3213
+ }
3214
+ _renderTableRow(row) {
3215
+ const R = CARRIAGE_RETURN_PLACEHOLDER;
3216
+ let cells = '';
3217
+ for (let cell of row.tokens ?? []) {
3218
+ const tag = cell['header'] === true ? 'th' : 'td';
3219
+ const align = alignAttribute(cell['align']);
3220
+ const content = this._renderContent(cell);
3221
+ cells += `<${tag}${align}>${content}</${tag}>`;
3222
+ }
3223
+ return `<tr>${cells}</tr>${R}`;
3224
+ }
3225
+ _renderTable(token) {
3226
+ const R = CARRIAGE_RETURN_PLACEHOLDER;
3227
+ const rows = token.tokens ?? [];
3228
+ const headerRows = rows.filter(r => r['header'] === true);
3229
+ const bodyRows = rows.filter(r => r['header'] !== true);
3230
+ const head = headerRows.map(r => this._renderTableRow(r)).join('');
3231
+ let html = `<table>${R}<thead>${R}${head}</thead>${R}`;
3232
+ if (bodyRows.length > 0) {
3233
+ const body = bodyRows.map(r => this._renderTableRow(r)).join('');
3234
+ html += `<tbody>${R}${body}</tbody>${R}`;
3235
+ }
3236
+ return html + `</table>${R}`;
3237
+ }
3238
+ _renderFootnoteReference(token) {
3239
+ const id = token['id'];
3240
+ if (!this.#footnotes.has(id)) {
3241
+ return `[^${token.text}]`;
3242
+ }
3243
+ const order = this.#footnoteOrder;
3244
+ const isFirst = !order.includes(id);
3245
+ if (isFirst) {
3246
+ order.push(id);
3247
+ }
3248
+ const number = order.indexOf(id) + 1;
3249
+ const anchorId = isFirst ? ` id="fnref-${id}"` : '';
3250
+ return `<sup class="footnote-ref"><a href="#fn-${id}"${anchorId}>${number}</a></sup>`;
3251
+ }
3252
+ /** Renders the referenced footnotes, in order of first reference (definitions may reference further footnotes). */
3253
+ _renderFootnotes() {
3254
+ const order = this.#footnoteOrder;
3255
+ if (order.length === 0) {
3256
+ return '';
3257
+ }
3258
+ const R = CARRIAGE_RETURN_PLACEHOLDER;
3259
+ let items = '';
3260
+ for (let i = 0; i < order.length; i++) {
3261
+ const id = order[i];
3262
+ const definition = this.#footnotes.get(id);
3263
+ const content = this._renderContent(definition);
3264
+ items += `<li id="fn-${id}">${content} <a href="#fnref-${id}" class="footnote-backref">&#8617;</a></li>${R}`;
3265
+ }
3266
+ return `<section class="footnotes">${R}<ol>${R}${items}</ol>${R}</section>${R}`;
3267
+ }
2264
3268
  _rendeItem(token, allTokens) {
2265
3269
  const r = this._renderContent, R = CARRIAGE_RETURN_PLACEHOLDER;
2266
3270
  switch (token?.type) {
2267
3271
  case MARKDOWN_TYPES.Reference:
3272
+ case MARKDOWN_TYPES.FootnoteDefinition:
2268
3273
  return '';
2269
3274
  case MARKDOWN_TYPES.Html:
2270
3275
  return token.raw;
@@ -2273,8 +3278,7 @@
2273
3278
  case MARKDOWN_TYPES.Bold:
2274
3279
  return `<b>${r(token)}</b>`;
2275
3280
  case MARKDOWN_TYPES.CodeBlock:
2276
- const lang = token['lang'];
2277
- return `<pre>${R}<code${(lang ? (' class="' + lang + '"') : '')}>${r(token)}</code>${R}</pre>${R}`;
3281
+ return this._renderCodeBlock(token);
2278
3282
  case MARKDOWN_TYPES.Code:
2279
3283
  return `<code>${r(token)}</code>`;
2280
3284
  case MARKDOWN_TYPES.HorizontalRule:
@@ -2284,17 +3288,7 @@
2284
3288
  return `<h${hlevel} id="${hid}">${r(token)}</h${hlevel}>${R}`;
2285
3289
  case MARKDOWN_TYPES.Image:
2286
3290
  case MARKDOWN_TYPES.ImageLike:
2287
- const itk = token;
2288
- let isrc = itk.src, ititle = '';
2289
- const alt = itk.text, iref = itk.ref;
2290
- if (!isrc && iref) {
2291
- const ireftk = allTokens.find(t => t.type === MARKDOWN_TYPES.Reference && t['image'] === true && t['id']?.toLowerCase() === iref.toLowerCase());
2292
- if (ireftk) {
2293
- isrc = ireftk.url;
2294
- ititle = ireftk.title ?? '';
2295
- }
2296
- }
2297
- return isrc ? `<img src="${isrc}" alt="${alt}"${(ititle ? (' title="' + ititle + '"') : '')} />` : `![${r(token)}]`;
3291
+ return this._renderImage(token);
2298
3292
  case MARKDOWN_TYPES.Italic:
2299
3293
  return `<i>${r(token)}</i>`;
2300
3294
  case MARKDOWN_TYPES.StrikeThrough:
@@ -2308,20 +3302,14 @@
2308
3302
  case MARKDOWN_TYPES.UnorderedList:
2309
3303
  return `<ul>\n${r(token)}</ul>${R}`;
2310
3304
  case MARKDOWN_TYPES.ListItem:
2311
- return `\t<li>${r(token)}</li>${R}`;
3305
+ return this._renderListItem(token);
2312
3306
  case MARKDOWN_TYPES.Link:
2313
3307
  case MARKDOWN_TYPES.LinkLike:
2314
- const atk = token;
2315
- let ahref = atk.href, atitle = '';
2316
- const aref = atk.ref;
2317
- if (!ahref && aref) {
2318
- const areftk = allTokens.find(t => t.type === MARKDOWN_TYPES.Reference && t['image'] !== true && t['id']?.toLowerCase() === aref.toLowerCase());
2319
- if (areftk) {
2320
- ahref = areftk.url;
2321
- atitle = areftk.title ?? '';
2322
- }
2323
- }
2324
- return ahref ? `<a href="${ahref}"${(atitle ? (' title="' + atitle + '"') : '')}>${r(token)}</a>` : `[${r(token)}]`;
3308
+ return this._renderLink(token);
3309
+ case MARKDOWN_TYPES.Table:
3310
+ return this._renderTable(token);
3311
+ case MARKDOWN_TYPES.FootnoteReference:
3312
+ return this._renderFootnoteReference(token);
2325
3313
  case MARKDOWN_TYPES.Paragraph:
2326
3314
  return `<p>${r(token)}</p>${R}`;
2327
3315
  case MARKDOWN_TYPES.Text:
@@ -2338,7 +3326,13 @@
2338
3326
  return acc;
2339
3327
  }
2340
3328
  render(tree) {
2341
- return trim(this._render(tree));
3329
+ this.#references = [];
3330
+ this.#footnotes.clear();
3331
+ this.#footnoteOrder = [];
3332
+ this._collectDefinitions(tree);
3333
+ const body = this._render(tree);
3334
+ const footnotes = this._renderFootnotes();
3335
+ return trim(body + footnotes);
2342
3336
  }
2343
3337
  }
2344
3338
  /** Renders a Markdown token tree as a JSON array of tokens, mainly useful for debugging/inspection. */
@@ -2380,12 +3374,13 @@
2380
3374
  return JSON.parse(json);
2381
3375
  }
2382
3376
  parse(input, grammarOrRenderer = getGrammar(), renderer = new HtmlRenderer()) {
3377
+ // line endings are normalized: the grammar expects `\n` (`.` doesn't match `\r`: CRLF paragraphs would split by line)
3378
+ const normalized = (input ?? '').replace(/\r\n?/g, '\n');
2383
3379
  if (isRenderer(grammarOrRenderer)) {
2384
- return super.parse(input, getGrammar(), grammarOrRenderer);
2385
- }
2386
- else {
2387
- return super.parse(input, grammarOrRenderer, renderer);
3380
+ const grammar = getGrammar();
3381
+ return super.parse(normalized, grammar, grammarOrRenderer);
2388
3382
  }
3383
+ return super.parse(normalized, grammarOrRenderer, renderer);
2389
3384
  }
2390
3385
  }
2391
3386
  //}
@@ -2395,10 +3390,357 @@
2395
3390
  Parser: Parser
2396
3391
  });
2397
3392
 
3393
+ //namespace Pacem.Compile {
3394
+ /** After these, a `/` starts a division (not a regular expression literal). */
3395
+ const DIVISION_AFTER_TYPES = ['identifier', 'number', 'string', 'regex'];
3396
+ const DIVISION_AFTER_TEXTS = [')', ']', '}', 'this', 'super', 'null', 'undefined', 'true', 'false', '++', '--'];
3397
+ const JAVASCRIPT_KEYWORDS = [
3398
+ // JavaScript
3399
+ 'async', 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do',
3400
+ 'else', 'enum', 'export', 'extends', 'false', 'finally', 'for', 'from', 'function', 'get', 'if', 'import', 'in',
3401
+ 'instanceof', 'let', 'new', 'null', 'of', 'return', 'set', 'static', 'super', 'switch', 'this', 'throw', 'true',
3402
+ 'try', 'typeof', 'undefined', 'var', 'void', 'while', 'with', 'yield',
3403
+ // TypeScript
3404
+ 'abstract', 'accessor', 'any', 'as', 'asserts', 'bigint', 'boolean', 'declare', 'implements', 'infer',
3405
+ 'interface', 'is', 'keyof', 'module', 'namespace', 'never', 'number', 'object', 'override', 'package', 'private',
3406
+ 'protected', 'public', 'readonly', 'satisfies', 'string', 'symbol', 'type', 'unique', 'unknown'
3407
+ ];
3408
+ /** Rules shared by every "code" state (top level, template expressions, nested braces). */
3409
+ const CODE_RULES$1 = [
3410
+ { match: String.raw `/\*\*(?!/)[\s\S]*?(?:\*/|$)`, type: 'doc-comment' },
3411
+ { match: String.raw `/\*[\s\S]*?(?:\*/|$)`, type: 'comment' },
3412
+ { match: String.raw `//[^\r\n]*`, type: 'comment' },
3413
+ { match: String.raw `\r\n|[\r\n

]`, type: 'newline' },
3414
+ { match: String.raw `[ \t\f\v ]+`, type: 'space' },
3415
+ { match: '`', type: 'string', push: 'template' },
3416
+ { match: String.raw `"(?:\\[\s\S]|[^"\\\r\n])*"?`, type: 'string' },
3417
+ { match: String.raw `'(?:\\[\s\S]|[^'\\\r\n])*'?`, type: 'string' },
3418
+ {
3419
+ // regular expression literal (only where a division can't be)
3420
+ match: String.raw `/(?![*/])(?:\\.|\[(?:\\.|[^\]\\\r\n])*\]|[^/\\\[\r\n])+/[a-z]*`, type: 'regex',
3421
+ when: { previousNotTypes: DIVISION_AFTER_TYPES, previousNotTexts: DIVISION_AFTER_TEXTS }
3422
+ },
3423
+ { match: String.raw `0[xX][\da-fA-F_]+n?|0[bB][01_]+n?|0[oO][0-7_]+n?|(?:\d[\d_]*\.?[\d_]*|\.\d[\d_]*)(?:[eE][+-]?\d[\d_]*)?n?`, type: 'number' },
3424
+ { match: String.raw `#?[A-Za-z_$ -￿][\w$ -￿]*`, type: 'identifier' },
3425
+ { match: String.raw `[()\[\]{}]`, type: 'bracket' },
3426
+ { match: String.raw `\?\.(?!\d)|\.\.\.|[;,.]`, type: 'punctuation' },
3427
+ { match: String.raw `>>>=|<<=|>>=|>>>|\*\*=|&&=|\|\|=|\?\?=|===|!==|=>|\*\*|\+\+|--|<<|>>|&&|\|\||\?\?|[-+*/%&|^<>!=]=?|[~?:@]`, type: 'operator' },
3428
+ ];
3429
+ /**
3430
+ * JavaScript vocabulary, TypeScript superset: comments (JSDoc as `doc-comment`), strings, template literals with nested
3431
+ * `${…}` expressions, regular expression literals (told from divisions by the previous significant token), numbers
3432
+ * (hex/binary/octal/BigInt/separators), identifiers (`#private` included), keywords (JS + TS, not after `.`/`?.`),
3433
+ * brackets, punctuation and operators.
3434
+ */
3435
+ const JavaScriptSyntax = {
3436
+ name: 'javascript',
3437
+ aliases: ['js', 'mjs', 'cjs', 'jsx', 'typescript', 'ts', 'tsx', 'script', 'ecmascript'],
3438
+ lexical: {
3439
+ start: 'root',
3440
+ states: {
3441
+ root: CODE_RULES$1,
3442
+ template: [
3443
+ { match: '`', type: 'string', pop: true },
3444
+ { match: String.raw `\$\{`, type: 'punctuation', push: 'templateExpression' },
3445
+ { match: String.raw `(?:\\[\s\S]|\$(?!\{)|[^\\` + '`' + String.raw `$])+`, type: 'string' },
3446
+ ],
3447
+ templateExpression: [
3448
+ { match: String.raw `\}`, type: 'punctuation', pop: true },
3449
+ { match: String.raw `\{`, type: 'bracket', push: 'braces' },
3450
+ { include: 'root' },
3451
+ ],
3452
+ braces: [
3453
+ { match: String.raw `\}`, type: 'bracket', pop: true },
3454
+ { match: String.raw `\{`, type: 'bracket', push: 'braces' },
3455
+ { include: 'root' },
3456
+ ],
3457
+ },
3458
+ keywords: { keyword: JAVASCRIPT_KEYWORDS },
3459
+ keywordExceptAfter: ['.', '?.'],
3460
+ }
3461
+ };
3462
+
3463
+ //namespace Pacem.Compile {
3464
+ /** Member accesses (`obj.#x`, `obj?.x`) are never Pacem binding tokens. */
3465
+ const NOT_AFTER_MEMBER_ACCESS = { previousNotTexts: ['.', '?.'] };
3466
+ /**
3467
+ * Pacem binding-expression vocabulary (the content of `{{ … }}` attributes): JavaScript plus the special tokens
3468
+ * resolved by core's `Expression`:
3469
+ * - `binding-host`: `:host` (followed by `.`);
3470
+ * - `binding-host-property`: `::name`;
3471
+ * - `binding-item`: `^item`, `^^item`, … (repeater item, n levels up);
3472
+ * - `binding-index`: `^index`, `^^index`, …;
3473
+ * - `binding-element`: `#id` (element reference);
3474
+ * - `binding-this`: `$this` / `this` (the bound element).
3475
+ */
3476
+ const PacemBindingSyntax = {
3477
+ name: 'pacem-binding',
3478
+ aliases: ['pacem', 'binding'],
3479
+ extends: 'javascript',
3480
+ lexical: {
3481
+ states: {
3482
+ root: [
3483
+ { match: String.raw `:host(?=\.)`, type: 'binding-host', when: NOT_AFTER_MEMBER_ACCESS },
3484
+ { match: String.raw `::[A-Za-z_$][\w$]*`, type: 'binding-host-property' },
3485
+ { match: String.raw `\^+item(?![\w$])`, type: 'binding-item' },
3486
+ { match: String.raw `\^+index(?![\w$])`, type: 'binding-index' },
3487
+ { match: String.raw `#[A-Za-z_][\w]*`, type: 'binding-element', when: NOT_AFTER_MEMBER_ACCESS },
3488
+ { match: String.raw `\$?this(?![\w$])`, type: 'binding-this', when: NOT_AFTER_MEMBER_ACCESS },
3489
+ ]
3490
+ }
3491
+ }
3492
+ };
3493
+
3494
+ //namespace Pacem.Compile {
3495
+ const NEWLINE_RULE = { match: String.raw `\r\n|[\r\n]`, type: 'newline' };
3496
+ /** Attribute list of an opening tag. */
3497
+ const ATTRIBUTE_RULES = [
3498
+ NEWLINE_RULE,
3499
+ { match: String.raw `[ \t\f]+`, type: 'space' },
3500
+ { match: '"', type: 'string', push: 'doubleQuoted' },
3501
+ { match: "'", type: 'string', push: 'singleQuoted' },
3502
+ { match: String.raw `[^\s=/>"']+`, type: 'attribute' },
3503
+ { match: '=', type: 'operator' },
3504
+ { match: '/', type: 'punctuation' },
3505
+ ];
3506
+ /** A Pacem binding (`{{ … }}`) inside an attribute value: embeds the `pacem-binding` vocabulary up to `}}`. */
3507
+ const BINDING_RULES = [
3508
+ { match: String.raw `\{\{`, type: 'punctuation', embed: 'pacem-binding', until: String.raw `\}\}` },
3509
+ { match: String.raw `\}\}`, type: 'punctuation' },
3510
+ ];
3511
+ /**
3512
+ * Markup (HTML/XML) vocabulary: comments, doctypes/CDATA, tags (`tag`/`tagname`), attributes, quoted values (Pacem
3513
+ * `{{ … }}` bindings tokenized with `pacem-binding`), entities, text, and `<script>` bodies tokenized with `javascript`.
3514
+ */
3515
+ const MarkupSyntax = {
3516
+ name: 'markup',
3517
+ aliases: ['html', 'htm', 'xml', 'xhtml', 'svg'],
3518
+ lexical: {
3519
+ start: 'root',
3520
+ states: {
3521
+ root: [
3522
+ { match: String.raw `<!--[\s\S]*?(?:-->|$)`, type: 'comment' },
3523
+ { match: String.raw `<![^>]*>?`, type: 'tag' },
3524
+ { match: String.raw `(<\/)([\w:.-]+)(\s*>?)`, type: ['tag', 'tagname', 'tag'] },
3525
+ { match: String.raw `(<)(script)(?![\w:.-])`, flags: 'i', type: ['tag', 'tagname'], push: 'scriptTag' },
3526
+ { match: String.raw `(<)([\w:.-]+)`, type: ['tag', 'tagname'], push: 'tag' },
3527
+ { match: String.raw `&(?:#\d+|#[xX][\da-fA-F]+|\w+);`, type: 'entity' },
3528
+ NEWLINE_RULE,
3529
+ { match: String.raw `[^<&\r\n]+`, type: 'text' },
3530
+ ],
3531
+ tag: [
3532
+ { match: String.raw `/?>`, type: 'tag', pop: true },
3533
+ { include: 'attributes' },
3534
+ ],
3535
+ scriptTag: [
3536
+ { match: '/>', type: 'tag', pop: true },
3537
+ // the script body is tokenized as JavaScript, up to the closing tag
3538
+ { match: '>', type: 'tag', pop: true, embed: 'javascript', until: String.raw `<\/script` },
3539
+ { include: 'attributes' },
3540
+ ],
3541
+ attributes: ATTRIBUTE_RULES,
3542
+ doubleQuoted: [
3543
+ { match: '"', type: 'string', pop: true },
3544
+ ...BINDING_RULES,
3545
+ { match: String.raw `(?:[^"{}]|\{(?!\{)|\}(?!\}))+`, type: 'string' },
3546
+ ],
3547
+ singleQuoted: [
3548
+ { match: "'", type: 'string', pop: true },
3549
+ ...BINDING_RULES,
3550
+ { match: String.raw `(?:[^'{}]|\{(?!\{)|\}(?!\}))+`, type: 'string' },
3551
+ ],
3552
+ },
3553
+ insignificant: ['space', 'newline', 'comment', 'text']
3554
+ }
3555
+ };
3556
+
3557
+ //namespace Pacem.Compile {
3558
+ const CSHARP_KEYWORDS = [
3559
+ 'abstract', 'add', 'alias', 'and', 'args', 'as', 'ascending', 'async', 'await', 'base', 'bool', 'break', 'by',
3560
+ 'byte', 'case', 'catch', 'char', 'checked', 'class', 'const', 'continue', 'decimal', 'default', 'delegate',
3561
+ 'descending', 'do', 'double', 'dynamic', 'else', 'enum', 'equals', 'event', 'explicit', 'extern', 'false', 'file',
3562
+ 'finally', 'fixed', 'float', 'for', 'foreach', 'from', 'get', 'global', 'goto', 'group', 'if', 'implicit', 'in',
3563
+ 'init', 'int', 'interface', 'internal', 'into', 'is', 'join', 'let', 'lock', 'long', 'managed', 'nameof',
3564
+ 'namespace', 'new', 'nint', 'not', 'notnull', 'nuint', 'null', 'object', 'on', 'operator', 'or', 'orderby', 'out',
3565
+ 'override', 'params', 'partial', 'private', 'protected', 'public', 'readonly', 'record', 'ref', 'remove',
3566
+ 'required', 'return', 'sbyte', 'scoped', 'sealed', 'select', 'set', 'short', 'sizeof', 'stackalloc', 'static',
3567
+ 'string', 'struct', 'switch', 'this', 'throw', 'true', 'try', 'typeof', 'uint', 'ulong', 'unchecked', 'unmanaged',
3568
+ 'unsafe', 'ushort', 'using', 'value', 'var', 'virtual', 'void', 'volatile', 'when', 'where', 'while', 'with',
3569
+ 'yield'
3570
+ ];
3571
+ /** Rules shared by every "code" state (top level, interpolations, nested braces). */
3572
+ const CODE_RULES = [
3573
+ { match: String.raw `///[^\r\n]*`, type: 'doc-comment' },
3574
+ { match: String.raw `//[^\r\n]*`, type: 'comment' },
3575
+ { match: String.raw `/\*[\s\S]*?(?:\*/|$)`, type: 'comment' },
3576
+ // preprocessor directives, at the beginning of a line
3577
+ { match: String.raw `(?<=^[ \t]*|[\r\n][ \t]*)#[ \t]*[a-z]+[^\r\n]*`, type: 'preprocessor' },
3578
+ { match: String.raw `\r\n|[\r\n]`, type: 'newline' },
3579
+ { match: String.raw `[ \t\f\v\u00A0\uFEFF]+`, type: 'space' },
3580
+ // raw string literals ("""…""", optionally interpolated)
3581
+ { match: String.raw `\$*("{3,})[\s\S]*?\1`, type: 'string' },
3582
+ { match: String.raw `\$@"|@\$"`, type: 'string', push: 'interpolatedVerbatim' },
3583
+ { match: String.raw `\$"`, type: 'string', push: 'interpolated' },
3584
+ { match: String.raw `@"(?:[^"]|"")*"?`, type: 'string' },
3585
+ { match: String.raw `"(?:\\.|[^"\\\r\n])*"?`, type: 'string' },
3586
+ { match: String.raw `'(?:\\.|[^'\\\r\n])*'?`, type: 'string' },
3587
+ { match: String.raw `0[xX][\da-fA-F_]+[uUlL]*|0[bB][01_]+[uUlL]*|(?:\d[\d_]*\.?[\d_]*|\.\d[\d_]*)(?:[eE][+-]?\d+)?[fFdDmMuUlL]*`, type: 'number' },
3588
+ { match: String.raw `@?[A-Za-z_\u00A0-\uFFFF][\w\u00A0-\uFFFF]*`, type: 'identifier' },
3589
+ { match: String.raw `[()\[\]{}]`, type: 'bracket' },
3590
+ { match: String.raw `\?\.|::|[;,.]`, type: 'punctuation' },
3591
+ { match: String.raw `\?\?=|<<=|>>>=|>>=|>>>|=>|==|!=|<=|>=|&&|\|\||\?\?|\+\+|--|->|<<|[-+*/%&|^<>!=]=?|[~?:]`, type: 'operator' },
3592
+ ];
3593
+ /**
3594
+ * C# vocabulary: XML doc comments (`doc-comment`), comments, preprocessor directives, strings (regular, verbatim, raw,
3595
+ * interpolated with nested `{…}` expressions), chars, numbers (with suffixes), identifiers (`@verbatim` included),
3596
+ * keywords (contextual ones included, not after `.`/`?.`), brackets, punctuation and operators.
3597
+ */
3598
+ const CSharpSyntax = {
3599
+ name: 'csharp',
3600
+ aliases: ['cs', 'c#', 'c-sharp', 'dotnet'],
3601
+ lexical: {
3602
+ start: 'root',
3603
+ states: {
3604
+ root: CODE_RULES,
3605
+ interpolated: [
3606
+ { match: '"', type: 'string', pop: true },
3607
+ { match: String.raw `\{\{|\}\}`, type: 'string' },
3608
+ { match: String.raw `\{`, type: 'punctuation', push: 'interpolation' },
3609
+ { match: String.raw `(?:\\.|[^"\\{}\r\n])+`, type: 'string' },
3610
+ ],
3611
+ interpolatedVerbatim: [
3612
+ { match: '""', type: 'string' },
3613
+ { match: '"', type: 'string', pop: true },
3614
+ { match: String.raw `\{\{|\}\}`, type: 'string' },
3615
+ { match: String.raw `\{`, type: 'punctuation', push: 'interpolation' },
3616
+ { match: String.raw `[^"{}]+`, type: 'string' },
3617
+ ],
3618
+ interpolation: [
3619
+ { match: String.raw `\}`, type: 'punctuation', pop: true },
3620
+ { match: String.raw `\{`, type: 'bracket', push: 'braces' },
3621
+ { include: 'root' },
3622
+ ],
3623
+ braces: [
3624
+ { match: String.raw `\}`, type: 'bracket', pop: true },
3625
+ { match: String.raw `\{`, type: 'bracket', push: 'braces' },
3626
+ { include: 'root' },
3627
+ ],
3628
+ },
3629
+ keywords: { keyword: CSHARP_KEYWORDS },
3630
+ keywordExceptAfter: ['.', '?.'],
3631
+ }
3632
+ };
3633
+
3634
+ //namespace Pacem.Compile {
3635
+ const C_KEYWORDS = [
3636
+ // C
3637
+ 'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do', 'double', 'else', 'enum', 'extern', 'float',
3638
+ 'for', 'goto', 'if', 'inline', 'int', 'long', 'register', 'restrict', 'return', 'short', 'signed', 'sizeof',
3639
+ 'static', 'struct', 'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile', 'while', 'bool', 'true', 'false',
3640
+ 'NULL', '_Bool', '_Complex', '_Imaginary', '_Alignas', '_Alignof', '_Atomic', '_Generic', '_Noreturn',
3641
+ '_Static_assert', '_Thread_local',
3642
+ // C++
3643
+ 'alignas', 'alignof', 'and', 'and_eq', 'asm', 'bitand', 'bitor', 'catch', 'char8_t', 'char16_t', 'char32_t',
3644
+ 'class', 'co_await', 'co_return', 'co_yield', 'compl', 'concept', 'consteval', 'constexpr', 'constinit',
3645
+ 'const_cast', 'decltype', 'delete', 'dynamic_cast', 'explicit', 'export', 'final', 'friend', 'mutable',
3646
+ 'namespace', 'new', 'noexcept', 'not', 'not_eq', 'nullptr', 'operator', 'or', 'or_eq', 'override', 'private',
3647
+ 'protected', 'public', 'reinterpret_cast', 'requires', 'static_assert', 'static_cast', 'template', 'this',
3648
+ 'thread_local', 'throw', 'try', 'typeid', 'typename', 'using', 'virtual', 'wchar_t', 'xor', 'xor_eq'
3649
+ ];
3650
+ /**
3651
+ * C/C++ vocabulary: comments (`/** … *\/` and `///` as `doc-comment`), preprocessor directives (with their `<header>`),
3652
+ * strings (raw `R"x(…)x"` and prefixed ones included), chars, numbers (with suffixes/separators), identifiers, keywords
3653
+ * (C and C++), brackets, punctuation and operators.
3654
+ */
3655
+ const CSyntax = {
3656
+ name: 'c',
3657
+ aliases: ['cpp', 'c++', 'cc', 'cxx', 'h', 'hpp', 'hxx', 'objc'],
3658
+ lexical: {
3659
+ start: 'root',
3660
+ states: {
3661
+ root: [
3662
+ { match: String.raw `/\*\*(?!/)[\s\S]*?(?:\*/|$)|///[^\r\n]*`, type: 'doc-comment' },
3663
+ { match: String.raw `/\*[\s\S]*?(?:\*/|$)`, type: 'comment' },
3664
+ { match: String.raw `//[^\r\n]*`, type: 'comment' },
3665
+ // preprocessor directives, at the beginning of a line (line continuations included)
3666
+ { match: String.raw `(?<=^[ \t]*|[\r\n][ \t]*)#[ \t]*\w+(?:\\\r?\n|[^\r\n])*`, type: 'preprocessor' },
3667
+ { match: String.raw `\r\n|[\r\n]`, type: 'newline' },
3668
+ { match: String.raw `[ \t\f\v]+`, type: 'space' },
3669
+ { match: String.raw `(?:u8|u|U|L)?R"([^()\\\s]{0,16})\([\s\S]*?\)\1"`, type: 'string' },
3670
+ { match: String.raw `(?:u8|u|U|L)?"(?:\\[\s\S]|[^"\\\r\n])*"?`, type: 'string' },
3671
+ { match: String.raw `(?:u8|u|U|L)?'(?:\\.|[^'\\\r\n])*'?`, type: 'string' },
3672
+ { match: String.raw `0[xX][\da-fA-F']+[uUlL]*|0[bB][01']+[uUlL]*|(?:\d[\d']*\.?[\d']*|\.\d[\d']*)(?:[eEpP][+-]?\d+)?[fFlLuU]*`, type: 'number' },
3673
+ { match: String.raw `[A-Za-z_][\w]*`, type: 'identifier' },
3674
+ { match: String.raw `[()\[\]{}]`, type: 'bracket' },
3675
+ { match: String.raw `::|\.\.\.|->\*?|\.\*?|[;,]`, type: 'punctuation' },
3676
+ { match: String.raw `<=>|<<=|>>=|<<|>>|\+\+|--|&&|\|\||[-+*/%&|^<>!=]=?|[~?:#]`, type: 'operator' },
3677
+ ],
3678
+ },
3679
+ keywords: { keyword: C_KEYWORDS },
3680
+ keywordExceptAfter: ['.', '->', '::'],
3681
+ }
3682
+ };
3683
+
3684
+ //namespace Pacem.Compile {
3685
+ /**
3686
+ * JSON vocabulary (JSONC and JSON5-ish tolerant: comments, single quotes and trailing commas are recognized):
3687
+ * object keys are `attribute`s, values `string`/`number`/`keyword` (`true`, `false`, `null`).
3688
+ */
3689
+ const JsonSyntax = {
3690
+ name: 'json',
3691
+ aliases: ['jsonc', 'json5', 'webmanifest'],
3692
+ lexical: {
3693
+ start: 'root',
3694
+ states: {
3695
+ root: [
3696
+ { match: String.raw `/\*[\s\S]*?(?:\*/|$)`, type: 'comment' },
3697
+ { match: String.raw `//[^\r\n]*`, type: 'comment' },
3698
+ { match: String.raw `\r\n|[\r\n]`, type: 'newline' },
3699
+ { match: String.raw `[ \t\f\v ]+`, type: 'space' },
3700
+ // keys: strings followed by a colon
3701
+ { match: String.raw `(?:"(?:\\.|[^"\\\r\n])*"|'(?:\\.|[^'\\\r\n])*')(?=\s*:)`, type: 'attribute' },
3702
+ { match: String.raw `"(?:\\.|[^"\\\r\n])*"?|'(?:\\.|[^'\\\r\n])*'?`, type: 'string' },
3703
+ { match: String.raw `-?(?:0[xX][\da-fA-F]+|(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)|-?Infinity|NaN`, type: 'number' },
3704
+ { match: String.raw `[A-Za-z_$][\w$]*`, type: 'identifier' },
3705
+ { match: String.raw `[\[\]{}]`, type: 'bracket' },
3706
+ { match: String.raw `[:,]`, type: 'punctuation' },
3707
+ ],
3708
+ },
3709
+ keywords: { keyword: ['true', 'false', 'null'] },
3710
+ }
3711
+ };
3712
+
3713
+ //namespace Pacem.Compile {
3714
+ /** The built-in vocabularies, one per {@link SyntaxName} (enforced by the type). */
3715
+ const BUILTIN_SYNTAXES = {
3716
+ 'javascript': JavaScriptSyntax,
3717
+ 'pacem-binding': PacemBindingSyntax,
3718
+ 'markup': MarkupSyntax,
3719
+ 'csharp': CSharpSyntax,
3720
+ 'c': CSyntax,
3721
+ 'json': JsonSyntax
3722
+ };
3723
+ // available as soon as the compile module loads
3724
+ for (let name in BUILTIN_SYNTAXES) {
3725
+ const syntax = BUILTIN_SYNTAXES[name];
3726
+ Syntaxes.register(syntax);
3727
+ }
3728
+
2398
3729
  var indexCompile = /*#__PURE__*/Object.freeze({
2399
3730
  __proto__: null,
3731
+ CSharpSyntax: CSharpSyntax,
3732
+ CSyntax: CSyntax,
3733
+ DefaultHighlightClasses: DefaultHighlightClasses,
3734
+ HighlightRenderer: HighlightRenderer,
3735
+ JavaScriptSyntax: JavaScriptSyntax,
3736
+ JsonSyntax: JsonSyntax,
2400
3737
  Markdown: indexCompileMarkdown,
2401
- Parser: Parser$1
3738
+ MarkupSyntax: MarkupSyntax,
3739
+ PacemBindingSyntax: PacemBindingSyntax,
3740
+ Parser: Parser$1,
3741
+ Scanner: Scanner,
3742
+ get SyntaxTokenType () { return SyntaxTokenType; },
3743
+ Syntaxes: Syntaxes
2402
3744
  });
2403
3745
 
2404
3746
  var Output = /*#__PURE__*/Object.freeze({