@pacem/pacem-foundation 1.1.0-sierpinski → 2.0.0-babbage
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.
- package/dist/browser/pacem-foundation.js +1441 -92
- package/dist/browser/pacem-foundation.js.map +1 -1
- package/dist/browser/pacem-foundation.min.js +2 -2
- package/dist/bundle/pacem-foundation.min.mjs +11 -8
- package/dist/bundle/pacem-foundation.mjs +1642 -100
- package/dist/bundle/pacem-foundation.mjs.map +4 -4
- package/dist/docs/pacem-foundation.json +2889 -677
- package/dist/esm/array-extensions.js +1 -1
- package/dist/esm/arrays.js +1 -1
- package/dist/esm/blobs.js +1 -1
- package/dist/esm/browser.js +1 -1
- package/dist/esm/colors.js +1 -1
- package/dist/esm/dates.js +1 -1
- package/dist/esm/deepmerger.js +1 -1
- package/dist/esm/geom.js +1 -1
- package/dist/esm/history-svc.js +1 -1
- package/dist/esm/imaging.js +13 -6
- package/dist/esm/index-compile-markdown.js +1 -1
- package/dist/esm/index-compile.js +6 -1
- package/dist/esm/index-iife.js +1 -1
- package/dist/esm/index-root.js +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/keygenerator.js +1 -1
- package/dist/esm/matrix2d.js +1 -1
- package/dist/esm/null-checker.js +1 -1
- package/dist/esm/number-extensions.js +1 -1
- package/dist/esm/numbers.js +1 -1
- package/dist/esm/rect.js +1 -1
- package/dist/esm/strings.js +1 -1
- package/package.json +1 -1
- package/typings/index.d.ts +270 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* @pacem/pacem-foundation
|
|
2
|
+
* @pacem/pacem-foundation v2.0.0-babbage (https://js.pacem.it)
|
|
3
3
|
* Pacem (https://pacem.it)
|
|
4
4
|
* Licensed under Apache-2.0
|
|
5
5
|
*/
|
|
@@ -1425,11 +1425,8 @@
|
|
|
1425
1425
|
if (width <= 0 || height <= 0 || isNaN(width) || isNaN(height)) {
|
|
1426
1426
|
throw new Error('Invalid target size');
|
|
1427
1427
|
}
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
}
|
|
1431
|
-
const el = await self.createImageBitmap(img);
|
|
1432
|
-
const origWidth = el.width, origHeight = el.height;
|
|
1428
|
+
const el = await Imaging._toDrawable(img);
|
|
1429
|
+
const origWidth = el.width || width, origHeight = el.height || height;
|
|
1433
1430
|
const cnv = new OffscreenCanvas(origWidth, origHeight);
|
|
1434
1431
|
const ctx = cnv.getContext('2d');
|
|
1435
1432
|
ctx.imageSmoothingEnabled =
|
|
@@ -1451,6 +1448,16 @@
|
|
|
1451
1448
|
const retval = await cnv.convertToBlob({ quality, type });
|
|
1452
1449
|
return retval;
|
|
1453
1450
|
}
|
|
1451
|
+
/** Turns a URL or a `Blob` into a drawable source, while passing an already drawable source through untouched. */
|
|
1452
|
+
static async _toDrawable(img) {
|
|
1453
|
+
if (typeof img === 'string') {
|
|
1454
|
+
img = await Imaging.loadImage(img);
|
|
1455
|
+
}
|
|
1456
|
+
if (!(img instanceof Blob)) {
|
|
1457
|
+
return img;
|
|
1458
|
+
}
|
|
1459
|
+
return await self.createImageBitmap(img);
|
|
1460
|
+
}
|
|
1454
1461
|
}
|
|
1455
1462
|
// }
|
|
1456
1463
|
|
|
@@ -1778,6 +1785,600 @@
|
|
|
1778
1785
|
};
|
|
1779
1786
|
//}
|
|
1780
1787
|
|
|
1788
|
+
/** Token types shared by the built-in vocabularies (vocabularies may add their own). */
|
|
1789
|
+
var SyntaxTokenType;
|
|
1790
|
+
(function (SyntaxTokenType) {
|
|
1791
|
+
SyntaxTokenType["Text"] = "text";
|
|
1792
|
+
SyntaxTokenType["Space"] = "space";
|
|
1793
|
+
SyntaxTokenType["Newline"] = "newline";
|
|
1794
|
+
SyntaxTokenType["Comment"] = "comment";
|
|
1795
|
+
SyntaxTokenType["DocComment"] = "doc-comment";
|
|
1796
|
+
SyntaxTokenType["Keyword"] = "keyword";
|
|
1797
|
+
SyntaxTokenType["Identifier"] = "identifier";
|
|
1798
|
+
SyntaxTokenType["String"] = "string";
|
|
1799
|
+
SyntaxTokenType["Regex"] = "regex";
|
|
1800
|
+
SyntaxTokenType["Number"] = "number";
|
|
1801
|
+
SyntaxTokenType["Bracket"] = "bracket";
|
|
1802
|
+
SyntaxTokenType["Punctuation"] = "punctuation";
|
|
1803
|
+
SyntaxTokenType["Operator"] = "operator";
|
|
1804
|
+
SyntaxTokenType["Preprocessor"] = "preprocessor";
|
|
1805
|
+
SyntaxTokenType["Tag"] = "tag";
|
|
1806
|
+
SyntaxTokenType["TagName"] = "tagname";
|
|
1807
|
+
SyntaxTokenType["Attribute"] = "attribute";
|
|
1808
|
+
SyntaxTokenType["Entity"] = "entity";
|
|
1809
|
+
})(SyntaxTokenType || (SyntaxTokenType = {}));
|
|
1810
|
+
|
|
1811
|
+
//namespace Pacem.Compile {
|
|
1812
|
+
const registry = new Map();
|
|
1813
|
+
function normalize(name) {
|
|
1814
|
+
return (name ?? '').trim().toLowerCase();
|
|
1815
|
+
}
|
|
1816
|
+
/**
|
|
1817
|
+
* Registry of the {@link Syntax} vocabularies known to the {@link Scanner}, by (case-insensitive) name and aliases.
|
|
1818
|
+
* The built-in vocabularies (see {@link SyntaxName}) are registered upfront; more can be
|
|
1819
|
+
* registered at runtime, e.g. after fetching them as JSON.
|
|
1820
|
+
*/
|
|
1821
|
+
class Syntaxes {
|
|
1822
|
+
/**
|
|
1823
|
+
* Registers (or replaces) a syntax under its name and aliases.
|
|
1824
|
+
* @param syntax The vocabulary to register.
|
|
1825
|
+
*/
|
|
1826
|
+
static register(syntax) {
|
|
1827
|
+
if (syntax == null) {
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
const names = [syntax.name].concat(syntax.aliases ?? []);
|
|
1831
|
+
for (let name of names) {
|
|
1832
|
+
const key = normalize(name);
|
|
1833
|
+
registry.set(key, syntax);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Gets a registered syntax by name or alias.
|
|
1838
|
+
* @param nameOrAlias Case-insensitive name or alias (e.g. `'ts'`).
|
|
1839
|
+
* @returns The syntax, or `null` when unknown.
|
|
1840
|
+
*/
|
|
1841
|
+
static get(nameOrAlias) {
|
|
1842
|
+
const key = normalize(nameOrAlias);
|
|
1843
|
+
return registry.get(key) ?? null;
|
|
1844
|
+
}
|
|
1845
|
+
/** Names (and aliases) of every registered syntax. */
|
|
1846
|
+
static names() {
|
|
1847
|
+
const keys = registry.keys();
|
|
1848
|
+
return Array.from(keys);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
//namespace Pacem.Compile {
|
|
1853
|
+
const DEFAULT_START = 'root';
|
|
1854
|
+
const DEFAULT_KEYWORD_SOURCES = [SyntaxTokenType.Identifier];
|
|
1855
|
+
const DEFAULT_INSIGNIFICANT = [SyntaxTokenType.Space, SyntaxTokenType.Newline, SyntaxTokenType.Comment, SyntaxTokenType.DocComment];
|
|
1856
|
+
/** Consecutive zero-length transitions tolerated before a rule gets skipped (guards against push/pop loops). */
|
|
1857
|
+
const MAX_EMPTY_TRANSITIONS = 64;
|
|
1858
|
+
/** Base-syntax chain length tolerated before giving up (guards against `extends` cycles). */
|
|
1859
|
+
const MAX_EXTENDS_DEPTH = 16;
|
|
1860
|
+
function isCombined(item) {
|
|
1861
|
+
return item.combined != null;
|
|
1862
|
+
}
|
|
1863
|
+
const compiledCache = new WeakMap();
|
|
1864
|
+
function isIncludeRule(rule) {
|
|
1865
|
+
return 'include' in rule;
|
|
1866
|
+
}
|
|
1867
|
+
function toSet(values) {
|
|
1868
|
+
if (values == null || values.length === 0) {
|
|
1869
|
+
return null;
|
|
1870
|
+
}
|
|
1871
|
+
return new Set(values);
|
|
1872
|
+
}
|
|
1873
|
+
function uniqueFlags(flags) {
|
|
1874
|
+
const chars = flags.split('');
|
|
1875
|
+
const unique = new Set(chars);
|
|
1876
|
+
const array = Array.from(unique);
|
|
1877
|
+
return array.join('');
|
|
1878
|
+
}
|
|
1879
|
+
function mergeStates(own, base) {
|
|
1880
|
+
const merged = {};
|
|
1881
|
+
for (let name in base) {
|
|
1882
|
+
merged[name] = base[name].slice();
|
|
1883
|
+
}
|
|
1884
|
+
for (let name in own) {
|
|
1885
|
+
const baseRules = merged[name] ?? [];
|
|
1886
|
+
merged[name] = own[name].concat(baseRules);
|
|
1887
|
+
}
|
|
1888
|
+
return merged;
|
|
1889
|
+
}
|
|
1890
|
+
function mergeKeywords(own, base) {
|
|
1891
|
+
const merged = {};
|
|
1892
|
+
for (let type in base) {
|
|
1893
|
+
merged[type] = base[type].slice();
|
|
1894
|
+
}
|
|
1895
|
+
for (let type in own) {
|
|
1896
|
+
const baseWords = merged[type] ?? [];
|
|
1897
|
+
merged[type] = baseWords.concat(own[type]);
|
|
1898
|
+
}
|
|
1899
|
+
return merged;
|
|
1900
|
+
}
|
|
1901
|
+
/** Resolves the `extends` chain of `syntax` (own rules first, then the base ones). */
|
|
1902
|
+
function mergeSyntax(syntax, depth = 0) {
|
|
1903
|
+
const lexical = syntax.lexical ?? { states: {} };
|
|
1904
|
+
const own = {
|
|
1905
|
+
start: lexical.start,
|
|
1906
|
+
states: lexical.states ?? {},
|
|
1907
|
+
keywords: lexical.keywords ?? {},
|
|
1908
|
+
keywordSources: lexical.keywordSources,
|
|
1909
|
+
keywordExceptAfter: lexical.keywordExceptAfter,
|
|
1910
|
+
insignificant: lexical.insignificant
|
|
1911
|
+
};
|
|
1912
|
+
const baseSyntax = syntax.extends ? Syntaxes.get(syntax.extends) : null;
|
|
1913
|
+
if (baseSyntax == null || baseSyntax === syntax || depth >= MAX_EXTENDS_DEPTH) {
|
|
1914
|
+
return own;
|
|
1915
|
+
}
|
|
1916
|
+
const base = mergeSyntax(baseSyntax, depth + 1);
|
|
1917
|
+
return {
|
|
1918
|
+
start: own.start ?? base.start,
|
|
1919
|
+
states: mergeStates(own.states, base.states),
|
|
1920
|
+
keywords: mergeKeywords(own.keywords, base.keywords),
|
|
1921
|
+
keywordSources: own.keywordSources ?? base.keywordSources,
|
|
1922
|
+
keywordExceptAfter: own.keywordExceptAfter ?? base.keywordExceptAfter,
|
|
1923
|
+
insignificant: own.insignificant ?? base.insignificant
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
/** Flattens `include` rules of a state (recursively, ignoring cycles). */
|
|
1927
|
+
function expandRules(states, stateName, visiting) {
|
|
1928
|
+
const rules = states[stateName] ?? [];
|
|
1929
|
+
if (visiting.has(stateName)) {
|
|
1930
|
+
return [];
|
|
1931
|
+
}
|
|
1932
|
+
visiting.add(stateName);
|
|
1933
|
+
const expanded = [];
|
|
1934
|
+
for (let rule of rules) {
|
|
1935
|
+
if (!isIncludeRule(rule)) {
|
|
1936
|
+
expanded.push(rule);
|
|
1937
|
+
continue;
|
|
1938
|
+
}
|
|
1939
|
+
const included = expandRules(states, rule.include, visiting);
|
|
1940
|
+
Array.prototype.push.apply(expanded, included);
|
|
1941
|
+
}
|
|
1942
|
+
visiting.delete(stateName);
|
|
1943
|
+
return expanded;
|
|
1944
|
+
}
|
|
1945
|
+
function compileCondition(condition) {
|
|
1946
|
+
if (condition == null) {
|
|
1947
|
+
return null;
|
|
1948
|
+
}
|
|
1949
|
+
return {
|
|
1950
|
+
previousTypes: toSet(condition.previousTypes),
|
|
1951
|
+
previousNotTypes: toSet(condition.previousNotTypes),
|
|
1952
|
+
previousTexts: toSet(condition.previousTexts),
|
|
1953
|
+
previousNotTexts: toSet(condition.previousNotTexts)
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
function compileRule(rule) {
|
|
1957
|
+
const groupTypes = Array.isArray(rule.type) ? rule.type : null;
|
|
1958
|
+
const type = Array.isArray(rule.type) ? rule.type[0] : rule.type;
|
|
1959
|
+
// sticky and indices flags are the engine's business
|
|
1960
|
+
const userFlags = (rule.flags ?? '').replace(/[yd]/g, '');
|
|
1961
|
+
const ownFlags = uniqueFlags(userFlags);
|
|
1962
|
+
// the `d` flag yields the capture group offsets, needed to split a match into per-group tokens
|
|
1963
|
+
const flags = ownFlags + 'y' + (groupTypes ? 'd' : '');
|
|
1964
|
+
const regex = new RegExp(rule.match, flags);
|
|
1965
|
+
const until = rule.until ? new RegExp(rule.until, 'y') : null;
|
|
1966
|
+
const pop = rule.pop === true ? 1 : (typeof rule.pop === 'number' ? rule.pop : 0);
|
|
1967
|
+
const when = compileCondition(rule.when);
|
|
1968
|
+
return {
|
|
1969
|
+
regex, type, groupTypes, when, pop, until,
|
|
1970
|
+
source: rule.match,
|
|
1971
|
+
flags: ownFlags,
|
|
1972
|
+
next: rule.next ?? null,
|
|
1973
|
+
push: rule.push ?? null,
|
|
1974
|
+
embed: rule.embed ?? null,
|
|
1975
|
+
hasTransition: pop > 0 || !!rule.next || !!rule.push || !!rule.embed
|
|
1976
|
+
};
|
|
1977
|
+
}
|
|
1978
|
+
function compileKeywords(keywords) {
|
|
1979
|
+
const map = new Map();
|
|
1980
|
+
for (let type in keywords) {
|
|
1981
|
+
for (let word of keywords[type]) {
|
|
1982
|
+
map.set(word, type);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
return map;
|
|
1986
|
+
}
|
|
1987
|
+
/** Backreferences and named groups would break once renumbered/duplicated inside an alternation. */
|
|
1988
|
+
const UNMERGEABLE_SOURCE = /\\[1-9]|\\k<|\(\?<(?![=!])/;
|
|
1989
|
+
function countGroups(source, flags) {
|
|
1990
|
+
const probe = new RegExp(source + '|', flags);
|
|
1991
|
+
const match = probe.exec('');
|
|
1992
|
+
return match.length - 1;
|
|
1993
|
+
}
|
|
1994
|
+
function canMatchEmpty(source, flags) {
|
|
1995
|
+
const probe = new RegExp('^(?:' + source + ')$', flags);
|
|
1996
|
+
return probe.test('');
|
|
1997
|
+
}
|
|
1998
|
+
/** Whether a rule can be merged into a {@link CombinedSegment}. */
|
|
1999
|
+
function isMergeable(rule) {
|
|
2000
|
+
if (rule.when != null || UNMERGEABLE_SOURCE.test(rule.source)) {
|
|
2001
|
+
return false;
|
|
2002
|
+
}
|
|
2003
|
+
return !canMatchEmpty(rule.source, rule.flags);
|
|
2004
|
+
}
|
|
2005
|
+
function combine(rules) {
|
|
2006
|
+
if (rules.length === 1) {
|
|
2007
|
+
return { rule: rules[0], combined: null };
|
|
2008
|
+
}
|
|
2009
|
+
const groupIndices = [];
|
|
2010
|
+
let groupIndex = 1;
|
|
2011
|
+
for (let rule of rules) {
|
|
2012
|
+
groupIndices.push(groupIndex);
|
|
2013
|
+
const ownGroups = countGroups(rule.source, rule.flags);
|
|
2014
|
+
groupIndex += 1 + ownGroups;
|
|
2015
|
+
}
|
|
2016
|
+
const alternatives = rules.map(r => '(' + r.source + ')');
|
|
2017
|
+
const source = alternatives.join('|');
|
|
2018
|
+
const combined = new RegExp(source, rules[0].flags + 'y');
|
|
2019
|
+
return { rule: null, combined, rules, groupIndices };
|
|
2020
|
+
}
|
|
2021
|
+
/** Groups consecutive mergeable rules (sharing the same flags) into combined segments, keeping their order. */
|
|
2022
|
+
function segment(rules) {
|
|
2023
|
+
const segments = [];
|
|
2024
|
+
let pending = [];
|
|
2025
|
+
const flush = () => {
|
|
2026
|
+
if (pending.length > 0) {
|
|
2027
|
+
const combinedSegment = combine(pending);
|
|
2028
|
+
segments.push(combinedSegment);
|
|
2029
|
+
}
|
|
2030
|
+
pending = [];
|
|
2031
|
+
};
|
|
2032
|
+
for (let rule of rules) {
|
|
2033
|
+
if (!isMergeable(rule)) {
|
|
2034
|
+
flush();
|
|
2035
|
+
segments.push({ rule, combined: null });
|
|
2036
|
+
continue;
|
|
2037
|
+
}
|
|
2038
|
+
if (pending.length > 0 && pending[0].flags !== rule.flags) {
|
|
2039
|
+
flush();
|
|
2040
|
+
}
|
|
2041
|
+
pending.push(rule);
|
|
2042
|
+
}
|
|
2043
|
+
flush();
|
|
2044
|
+
return segments;
|
|
2045
|
+
}
|
|
2046
|
+
function compileSyntax(syntax) {
|
|
2047
|
+
const cached = compiledCache.get(syntax);
|
|
2048
|
+
if (cached != null) {
|
|
2049
|
+
return cached;
|
|
2050
|
+
}
|
|
2051
|
+
const merged = mergeSyntax(syntax);
|
|
2052
|
+
const states = new Map();
|
|
2053
|
+
for (let name in merged.states) {
|
|
2054
|
+
const visiting = new Set();
|
|
2055
|
+
const rules = expandRules(merged.states, name, visiting);
|
|
2056
|
+
const compiledRules = rules.map(compileRule);
|
|
2057
|
+
const segments = segment(compiledRules);
|
|
2058
|
+
states.set(name, segments);
|
|
2059
|
+
}
|
|
2060
|
+
const compiled = {
|
|
2061
|
+
name: syntax.name,
|
|
2062
|
+
start: merged.start ?? DEFAULT_START,
|
|
2063
|
+
states,
|
|
2064
|
+
keywords: compileKeywords(merged.keywords),
|
|
2065
|
+
keywordSources: new Set(merged.keywordSources ?? DEFAULT_KEYWORD_SOURCES),
|
|
2066
|
+
keywordExceptAfter: new Set(merged.keywordExceptAfter ?? []),
|
|
2067
|
+
insignificant: new Set(merged.insignificant ?? DEFAULT_INSIGNIFICANT)
|
|
2068
|
+
};
|
|
2069
|
+
compiledCache.set(syntax, compiled);
|
|
2070
|
+
return compiled;
|
|
2071
|
+
}
|
|
2072
|
+
function compileByName(name) {
|
|
2073
|
+
const syntax = Syntaxes.get(name);
|
|
2074
|
+
if (syntax == null) {
|
|
2075
|
+
return null;
|
|
2076
|
+
}
|
|
2077
|
+
return compileSyntax(syntax);
|
|
2078
|
+
}
|
|
2079
|
+
function meets(condition, previous) {
|
|
2080
|
+
const type = previous?.type ?? null, text = previous?.raw ?? null;
|
|
2081
|
+
if (condition.previousTypes != null && !condition.previousTypes.has(type)) {
|
|
2082
|
+
return false;
|
|
2083
|
+
}
|
|
2084
|
+
if (condition.previousNotTypes != null && condition.previousNotTypes.has(type)) {
|
|
2085
|
+
return false;
|
|
2086
|
+
}
|
|
2087
|
+
if (condition.previousTexts != null && !condition.previousTexts.has(text)) {
|
|
2088
|
+
return false;
|
|
2089
|
+
}
|
|
2090
|
+
return condition.previousNotTexts == null || !condition.previousNotTexts.has(text);
|
|
2091
|
+
}
|
|
2092
|
+
/** A single tokenization pass: holds the cursor, the state stack and the produced tokens. */
|
|
2093
|
+
class ScanRun {
|
|
2094
|
+
constructor(_source, root) {
|
|
2095
|
+
this._source = _source;
|
|
2096
|
+
this._tokens = [];
|
|
2097
|
+
/** Current scanning position. */
|
|
2098
|
+
this._position = 0;
|
|
2099
|
+
/** End of the already emitted tokens (characters between it and `_position` are pending unrecognized text). */
|
|
2100
|
+
this._emitted = 0;
|
|
2101
|
+
this._line = 1;
|
|
2102
|
+
this._column = 1;
|
|
2103
|
+
this._previous = null;
|
|
2104
|
+
this._emptyTransitions = 0;
|
|
2105
|
+
this._stack = [{ syntax: root, state: root.start, until: null }];
|
|
2106
|
+
}
|
|
2107
|
+
run() {
|
|
2108
|
+
const length = this._source.length;
|
|
2109
|
+
while (this._position < length) {
|
|
2110
|
+
this._step();
|
|
2111
|
+
}
|
|
2112
|
+
this._flushText();
|
|
2113
|
+
return this._tokens;
|
|
2114
|
+
}
|
|
2115
|
+
get _top() {
|
|
2116
|
+
const stack = this._stack;
|
|
2117
|
+
return stack[stack.length - 1];
|
|
2118
|
+
}
|
|
2119
|
+
_step() {
|
|
2120
|
+
const frame = this._top;
|
|
2121
|
+
if (this._popOnUntil(frame)) {
|
|
2122
|
+
return;
|
|
2123
|
+
}
|
|
2124
|
+
const found = this._findRule(frame);
|
|
2125
|
+
if (found == null) {
|
|
2126
|
+
// unrecognized character: accumulates into a `text` token
|
|
2127
|
+
this._position++;
|
|
2128
|
+
return;
|
|
2129
|
+
}
|
|
2130
|
+
this._flushText();
|
|
2131
|
+
this._emitMatch(found, frame.syntax);
|
|
2132
|
+
this._transition(found.rule, frame.syntax);
|
|
2133
|
+
const length = found.match[0].length;
|
|
2134
|
+
this._position += length;
|
|
2135
|
+
if (length > 0) {
|
|
2136
|
+
this._emptyTransitions = 0;
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
/** Pops an embedded syntax as soon as its `until` pattern matches (without consuming input). */
|
|
2140
|
+
_popOnUntil(frame) {
|
|
2141
|
+
const until = frame.until;
|
|
2142
|
+
if (until == null || this._stack.length <= 1) {
|
|
2143
|
+
return false;
|
|
2144
|
+
}
|
|
2145
|
+
until.lastIndex = this._position;
|
|
2146
|
+
if (!until.test(this._source)) {
|
|
2147
|
+
return false;
|
|
2148
|
+
}
|
|
2149
|
+
this._flushText();
|
|
2150
|
+
this._stack.pop();
|
|
2151
|
+
return true;
|
|
2152
|
+
}
|
|
2153
|
+
_findRule(frame) {
|
|
2154
|
+
const segments = frame.syntax.states.get(frame.state) ?? [];
|
|
2155
|
+
for (let item of segments) {
|
|
2156
|
+
const found = isCombined(item) ? this._tryCombined(item) : this._trySingle(item.rule);
|
|
2157
|
+
if (found != null) {
|
|
2158
|
+
return found;
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
return null;
|
|
2162
|
+
}
|
|
2163
|
+
_trySingle(rule) {
|
|
2164
|
+
const match = this._tryRule(rule);
|
|
2165
|
+
if (match == null) {
|
|
2166
|
+
return null;
|
|
2167
|
+
}
|
|
2168
|
+
return { rule, match };
|
|
2169
|
+
}
|
|
2170
|
+
/** Finds which merged rule matched (the first defined wrapping group), then re-runs it alone for its own groups. */
|
|
2171
|
+
_tryCombined(item) {
|
|
2172
|
+
const combined = item.combined;
|
|
2173
|
+
combined.lastIndex = this._position;
|
|
2174
|
+
const match = combined.exec(this._source);
|
|
2175
|
+
if (match == null) {
|
|
2176
|
+
return null;
|
|
2177
|
+
}
|
|
2178
|
+
const indices = item.groupIndices;
|
|
2179
|
+
for (let k = 0; k < indices.length; k++) {
|
|
2180
|
+
if (match[indices[k]] === undefined) {
|
|
2181
|
+
continue;
|
|
2182
|
+
}
|
|
2183
|
+
const rule = item.rules[k];
|
|
2184
|
+
return this._trySingle(rule);
|
|
2185
|
+
}
|
|
2186
|
+
return null;
|
|
2187
|
+
}
|
|
2188
|
+
_tryRule(rule) {
|
|
2189
|
+
if (rule.when != null && !meets(rule.when, this._previous)) {
|
|
2190
|
+
return null;
|
|
2191
|
+
}
|
|
2192
|
+
const regex = rule.regex;
|
|
2193
|
+
regex.lastIndex = this._position;
|
|
2194
|
+
const match = regex.exec(this._source);
|
|
2195
|
+
if (match == null) {
|
|
2196
|
+
return null;
|
|
2197
|
+
}
|
|
2198
|
+
if (match[0].length > 0) {
|
|
2199
|
+
return match;
|
|
2200
|
+
}
|
|
2201
|
+
// zero-length matches are only meaningful as (bounded) state transitions
|
|
2202
|
+
if (!rule.hasTransition || this._emptyTransitions >= MAX_EMPTY_TRANSITIONS) {
|
|
2203
|
+
return null;
|
|
2204
|
+
}
|
|
2205
|
+
this._emptyTransitions++;
|
|
2206
|
+
return match;
|
|
2207
|
+
}
|
|
2208
|
+
_emitMatch({ rule, match }, syntax) {
|
|
2209
|
+
const raw = match[0];
|
|
2210
|
+
if (rule.groupTypes == null) {
|
|
2211
|
+
this._append(rule.type, raw, syntax);
|
|
2212
|
+
return;
|
|
2213
|
+
}
|
|
2214
|
+
this._emitGroups(rule, match, syntax);
|
|
2215
|
+
}
|
|
2216
|
+
/** Splits a match into one token per (top-level) capture group; uncovered characters become `text` tokens. */
|
|
2217
|
+
_emitGroups(rule, match, syntax) {
|
|
2218
|
+
const indices = match.indices;
|
|
2219
|
+
const start = match.index, end = start + match[0].length, types = rule.groupTypes;
|
|
2220
|
+
let cursor = start;
|
|
2221
|
+
for (let i = 1; i < match.length; i++) {
|
|
2222
|
+
const range = indices[i];
|
|
2223
|
+
if (range == null || range[0] < cursor || range[1] === range[0]) {
|
|
2224
|
+
continue;
|
|
2225
|
+
}
|
|
2226
|
+
if (range[0] > cursor) {
|
|
2227
|
+
const gap = this._source.slice(cursor, range[0]);
|
|
2228
|
+
this._append(SyntaxTokenType.Text, gap, syntax);
|
|
2229
|
+
}
|
|
2230
|
+
const groupText = this._source.slice(range[0], range[1]);
|
|
2231
|
+
const groupType = types[i - 1] ?? types[types.length - 1];
|
|
2232
|
+
this._append(groupType, groupText, syntax);
|
|
2233
|
+
cursor = range[1];
|
|
2234
|
+
}
|
|
2235
|
+
if (cursor < end) {
|
|
2236
|
+
const rest = this._source.slice(cursor, end);
|
|
2237
|
+
this._append(SyntaxTokenType.Text, rest, syntax);
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
_transition(rule, syntax) {
|
|
2241
|
+
const stack = this._stack;
|
|
2242
|
+
for (let i = 0; i < rule.pop && stack.length > 1; i++) {
|
|
2243
|
+
stack.pop();
|
|
2244
|
+
}
|
|
2245
|
+
if (rule.next != null) {
|
|
2246
|
+
this._top.state = rule.next;
|
|
2247
|
+
}
|
|
2248
|
+
if (rule.push != null) {
|
|
2249
|
+
stack.push({ syntax, state: rule.push, until: null });
|
|
2250
|
+
}
|
|
2251
|
+
if (rule.embed == null) {
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
const embedded = compileByName(rule.embed);
|
|
2255
|
+
if (embedded == null) {
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
stack.push({ syntax: embedded, state: embedded.start, until: rule.until });
|
|
2259
|
+
}
|
|
2260
|
+
/** Emits the pending unrecognized characters (if any) as a single `text` token. */
|
|
2261
|
+
_flushText() {
|
|
2262
|
+
if (this._emitted >= this._position) {
|
|
2263
|
+
return;
|
|
2264
|
+
}
|
|
2265
|
+
const text = this._source.slice(this._emitted, this._position);
|
|
2266
|
+
const syntax = this._top.syntax;
|
|
2267
|
+
this._append(SyntaxTokenType.Text, text, syntax);
|
|
2268
|
+
}
|
|
2269
|
+
_resolveType(type, raw, syntax) {
|
|
2270
|
+
if (!syntax.keywordSources.has(type)) {
|
|
2271
|
+
return type;
|
|
2272
|
+
}
|
|
2273
|
+
const previousText = this._previous?.raw;
|
|
2274
|
+
if (syntax.keywordExceptAfter.has(previousText)) {
|
|
2275
|
+
return type;
|
|
2276
|
+
}
|
|
2277
|
+
return syntax.keywords.get(raw) ?? type;
|
|
2278
|
+
}
|
|
2279
|
+
_append(type, raw, syntax) {
|
|
2280
|
+
const resolvedType = this._resolveType(type, raw, syntax);
|
|
2281
|
+
const token = {
|
|
2282
|
+
type: resolvedType, raw, text: raw,
|
|
2283
|
+
index: this._emitted,
|
|
2284
|
+
line: this._line,
|
|
2285
|
+
column: this._column,
|
|
2286
|
+
depth: this._stack.length - 1,
|
|
2287
|
+
syntax: syntax.name
|
|
2288
|
+
};
|
|
2289
|
+
this._tokens.push(token);
|
|
2290
|
+
this._advance(raw);
|
|
2291
|
+
if (!syntax.insignificant.has(resolvedType)) {
|
|
2292
|
+
this._previous = token;
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
/** Moves the emitted offset and the line/column counters past `raw`. */
|
|
2296
|
+
_advance(raw) {
|
|
2297
|
+
this._emitted += raw.length;
|
|
2298
|
+
const lastNewline = raw.lastIndexOf('\n');
|
|
2299
|
+
if (lastNewline < 0) {
|
|
2300
|
+
this._column += raw.length;
|
|
2301
|
+
return;
|
|
2302
|
+
}
|
|
2303
|
+
for (let i = 0; i <= lastNewline; i++) {
|
|
2304
|
+
if (raw.charCodeAt(i) === 10) {
|
|
2305
|
+
this._line++;
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
this._column = raw.length - lastNewline;
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
// #endregion
|
|
2312
|
+
/** Used for unknown syntaxes: no rules, the whole source becomes a single `text` token. */
|
|
2313
|
+
const PLAIN_TEXT_SYNTAX = { name: SyntaxTokenType.Text, lexical: { states: {} } };
|
|
2314
|
+
/**
|
|
2315
|
+
* Language-agnostic lexical engine: tokenizes a source text according to a {@link Syntax} vocabulary.
|
|
2316
|
+
* - lossless: concatenating the tokens' `raw` reproduces the source exactly;
|
|
2317
|
+
* - linear: a single cursor and sticky regular expressions (compiled once per syntax);
|
|
2318
|
+
* - error tolerant: unrecognized characters become `text` tokens, it never throws.
|
|
2319
|
+
*/
|
|
2320
|
+
class Scanner {
|
|
2321
|
+
/**
|
|
2322
|
+
* Tokenizes `source`.
|
|
2323
|
+
* @param source Source text.
|
|
2324
|
+
* @param syntax A {@link Syntax} or the name/alias of a registered one (see {@link Syntaxes}); an unknown one yields a single `text` token.
|
|
2325
|
+
* @returns The lossless, positioned token stream.
|
|
2326
|
+
*/
|
|
2327
|
+
tokenize(source, syntax) {
|
|
2328
|
+
const text = source ?? '';
|
|
2329
|
+
const resolved = typeof syntax === 'string' ? Syntaxes.get(syntax) : syntax;
|
|
2330
|
+
const compiled = compileSyntax(resolved ?? PLAIN_TEXT_SYNTAX);
|
|
2331
|
+
const run = new ScanRun(text, compiled);
|
|
2332
|
+
return run.run();
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
//namespace Pacem.Compile {
|
|
2337
|
+
/** Default token type → CSS class mapping, matching the `code-*` classes styled by the theme (see `ui/markdown.css`). */
|
|
2338
|
+
const DefaultHighlightClasses = Object.freeze({
|
|
2339
|
+
'keyword': 'code-keyword',
|
|
2340
|
+
'preprocessor': 'code-keyword',
|
|
2341
|
+
'string': 'code-string',
|
|
2342
|
+
'regex': 'code-string',
|
|
2343
|
+
'number': 'code-number',
|
|
2344
|
+
'comment': 'code-comment',
|
|
2345
|
+
'doc-comment': 'code-comment',
|
|
2346
|
+
'tag': 'code-tag',
|
|
2347
|
+
'tagname': 'code-tagname',
|
|
2348
|
+
'attribute': 'code-attribute',
|
|
2349
|
+
});
|
|
2350
|
+
const HTML_ESCAPES = { '&': '&', '<': '<', '>': '>', '"': '"' };
|
|
2351
|
+
function htmlEncode$1(text) {
|
|
2352
|
+
return text.replace(/[&<>"]/g, c => HTML_ESCAPES[c]);
|
|
2353
|
+
}
|
|
2354
|
+
/**
|
|
2355
|
+
* Renders a token stream (typically produced by the {@link Scanner}) as HTML: every token's `raw` text is HTML-encoded and,
|
|
2356
|
+
* when its type has a mapped class, wrapped in a `<span class="…">`.
|
|
2357
|
+
*/
|
|
2358
|
+
class HighlightRenderer {
|
|
2359
|
+
/**
|
|
2360
|
+
* @param _classes Token type → CSS class mapping (defaults to {@link DefaultHighlightClasses}).
|
|
2361
|
+
*/
|
|
2362
|
+
constructor(_classes = DefaultHighlightClasses) {
|
|
2363
|
+
this._classes = _classes;
|
|
2364
|
+
}
|
|
2365
|
+
render(tree) {
|
|
2366
|
+
let html = '';
|
|
2367
|
+
for (let token of tree ?? []) {
|
|
2368
|
+
html += this._renderToken(token);
|
|
2369
|
+
}
|
|
2370
|
+
return html;
|
|
2371
|
+
}
|
|
2372
|
+
_renderToken(token) {
|
|
2373
|
+
const encoded = htmlEncode$1(token.raw ?? '');
|
|
2374
|
+
const cssClass = this._classes[token.type];
|
|
2375
|
+
if (cssClass == null) {
|
|
2376
|
+
return encoded;
|
|
2377
|
+
}
|
|
2378
|
+
return `<span class="${cssClass}">${encoded}</span>`;
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
|
|
1781
2382
|
//namespace Pacem.Compile.Markdown {
|
|
1782
2383
|
// #region HELPERS
|
|
1783
2384
|
function htmlEncode(input) {
|
|
@@ -1795,6 +2396,84 @@
|
|
|
1795
2396
|
}
|
|
1796
2397
|
return input;
|
|
1797
2398
|
}
|
|
2399
|
+
/** Splits a link/image destination into its url and optional title: `url "title"`, `url 'title'` or `url (title)`. */
|
|
2400
|
+
function splitUrlAndTitle(destination) {
|
|
2401
|
+
if (NullChecker.isNullOrEmpty(destination)) {
|
|
2402
|
+
return { url: destination };
|
|
2403
|
+
}
|
|
2404
|
+
const trimmed = destination.trim();
|
|
2405
|
+
const arr = /^(\S*)\s+(?:"([^"]*)"|'([^']*)'|\(([^)]*)\))\s*$/.exec(trimmed);
|
|
2406
|
+
if (arr == null) {
|
|
2407
|
+
return { url: trimmed };
|
|
2408
|
+
}
|
|
2409
|
+
const title = arr[2] ?? arr[3] ?? arr[4];
|
|
2410
|
+
return { url: arr[1], title };
|
|
2411
|
+
}
|
|
2412
|
+
function attributeEncode(input) {
|
|
2413
|
+
return (input ?? '').replace(/&(?!(?:#\d+|#x[\da-fA-F]+|\w+);)/g, '&').replace(/"/g, '"').replace(/</g, '<');
|
|
2414
|
+
}
|
|
2415
|
+
/** Splits a table row into its (trimmed) cells: outer pipes are optional, `\|` is a literal pipe. */
|
|
2416
|
+
function splitTableCells(row) {
|
|
2417
|
+
let line = row.trim();
|
|
2418
|
+
if (line.startsWith('|')) {
|
|
2419
|
+
line = line.slice(1);
|
|
2420
|
+
}
|
|
2421
|
+
if (line.endsWith('|') && !line.endsWith('\\|')) {
|
|
2422
|
+
line = line.slice(0, -1);
|
|
2423
|
+
}
|
|
2424
|
+
const cells = line.split(/(?<!\\)\|/);
|
|
2425
|
+
return cells.map(c => c.trim().replace(/\\\|/g, '|'));
|
|
2426
|
+
}
|
|
2427
|
+
function tableAlignment(delimiter) {
|
|
2428
|
+
const left = delimiter.startsWith(':'), right = delimiter.endsWith(':');
|
|
2429
|
+
if (left && right) {
|
|
2430
|
+
return 'center';
|
|
2431
|
+
}
|
|
2432
|
+
if (right) {
|
|
2433
|
+
return 'right';
|
|
2434
|
+
}
|
|
2435
|
+
return left ? 'left' : null;
|
|
2436
|
+
}
|
|
2437
|
+
function indentationOf(line) {
|
|
2438
|
+
const arr = /^ */.exec(line);
|
|
2439
|
+
return arr[0].length;
|
|
2440
|
+
}
|
|
2441
|
+
/**
|
|
2442
|
+
* Splits a list block into its items, by indentation: a marker indented less than the current item's content
|
|
2443
|
+
* starts a sibling item; anything else (nested lists included) is item content, dedented by the content offset.
|
|
2444
|
+
*/
|
|
2445
|
+
function splitListItems(raw) {
|
|
2446
|
+
const lines = raw.replace(/\t/g, ' ').split(/\r?\n/);
|
|
2447
|
+
const items = [];
|
|
2448
|
+
for (let line of lines) {
|
|
2449
|
+
const current = items[items.length - 1];
|
|
2450
|
+
const marker = LISTMARKER.exec(line);
|
|
2451
|
+
if (marker != null && (current == null || marker[1].length < current.indent)) {
|
|
2452
|
+
const contentIndent = marker[0].length;
|
|
2453
|
+
const firstLine = line.slice(contentIndent);
|
|
2454
|
+
items.push({ indent: contentIndent, lines: [firstLine] });
|
|
2455
|
+
continue;
|
|
2456
|
+
}
|
|
2457
|
+
if (current == null) {
|
|
2458
|
+
continue;
|
|
2459
|
+
}
|
|
2460
|
+
const dedent = Math.min(current.indent, indentationOf(line));
|
|
2461
|
+
const content = line.slice(dedent);
|
|
2462
|
+
current.lines.push(content);
|
|
2463
|
+
}
|
|
2464
|
+
return items.map(i => i.lines.join('\n').replace(/\s+$/, ''));
|
|
2465
|
+
}
|
|
2466
|
+
/**
|
|
2467
|
+
* Code meant for a syntax highlighter: `MarkdownService` escapes every `<` of its input (no html allowed), hence
|
|
2468
|
+
* `<` is turned back into `<` (the highlighter encodes everything it renders anyway).
|
|
2469
|
+
*/
|
|
2470
|
+
function decodeCode(code) {
|
|
2471
|
+
return (code ?? '').replace(/</g, '<');
|
|
2472
|
+
}
|
|
2473
|
+
/** Highlights fenced code blocks of the registered languages (see {@link Syntaxes}). */
|
|
2474
|
+
const scanner = new Scanner();
|
|
2475
|
+
/** Fenced code in this vocabulary also gets the `code-markup` class. */
|
|
2476
|
+
const MARKUP_SYNTAX = 'markup';
|
|
1798
2477
|
function regexMatchIndex(arr) {
|
|
1799
2478
|
return arr?.index || void 0;
|
|
1800
2479
|
}
|
|
@@ -1822,27 +2501,51 @@
|
|
|
1822
2501
|
}
|
|
1823
2502
|
// #endregion
|
|
1824
2503
|
const COMMENT = /(<!--)([\s\S]*?)(-->|$)/;
|
|
1825
|
-
const HR = /^ {0,3}(={3,}|_{3,}
|
|
2504
|
+
const HR = /^ {0,3}(={3,}|(?:_[ \t]*){3,}|(?:-[ \t]*){3,}|(?:\*[ \t]*){3,})(\r?\n|$)/;
|
|
1826
2505
|
const HEADING = /^ {0,3}(#{1,6})\s+([^\n]+)(\r?\n|$)+/;
|
|
1827
2506
|
const HEADING1 = /^ {0,3}([^\n]+)(\r?\n=+(\r?\n|$))+/;
|
|
1828
2507
|
const HEADING2 = /^ {0,3}([^\n]+)(\r?\n-+(\r?\n|$))+/;
|
|
1829
2508
|
const CODEBLOCK = /^ {4,}.+(\r?\n {4,}.+)*(\r?\n|$)+/;
|
|
1830
2509
|
const FENCES = /^ {0,3}``` *([^\n]*)\r?\n(([\s\S](?!\r?\n {0,3}```))*.(\r?\n)+) {0,3}```(\r?\n|$)+/;
|
|
1831
2510
|
const BLOCKQUOTE = /^ {0,3}> *([^\n]+)(\r?\n {0,3}> *([^\n]*))*(\r?\n|$)+/;
|
|
1832
|
-
|
|
1833
|
-
const
|
|
1834
|
-
const
|
|
1835
|
-
|
|
2511
|
+
// (`*` bullets need a following space, not to be mistaken for emphasis; nested lists of the other kind are indented)
|
|
2512
|
+
const ULIST = /^( {0,3}([-+]|\*(?=[ \t])).*(\r?\n|$))((?!(?: ?\d+[.\)]|\r?\n\S)).*(\r?\n|$))*/;
|
|
2513
|
+
const OLIST = /^( {0,3}(\d+[.\)]).*(\r?\n|$))((?!(?: {0,2}(?:[-+]|\*[ \t])|\r?\n\S)).*(\r?\n|$))*/;
|
|
2514
|
+
/** A list item marker, with its indentation and the spaces following it. */
|
|
2515
|
+
const LISTMARKER = /^( *)([-+*]|\d+[.\)])([ \t]+|$)/;
|
|
2516
|
+
/** GFM task list item: `[ ]` or `[x]` at the beginning of the item. */
|
|
2517
|
+
const TASK = /^\[([ xX])\][ \t]+/;
|
|
2518
|
+
// (whitespace-only lines are blank lines: they never start nor continue a paragraph)
|
|
2519
|
+
const PARAGRAPH = /^(?! {4,})(?=[ \t]*\S)(.+(\n(?![ \t]*(?:\n|$))(?! {0,3}```)(?! {0,3}([-+]|\*[ \t]|\d+[.\)]))(?! {0,3}#{1,6}[ \t])(?! {4,})(?! {0,3}>).+)*)/;
|
|
1836
2520
|
const REF = /^ {0,3}(!?)\[([^\]]+)\]: +(\S+)(?: +['"\(](.+)['"\)])?(\r?\n|$)+/;
|
|
1837
|
-
|
|
2521
|
+
// (an escaped `\[` never opens a link)
|
|
2522
|
+
const IMAGE_OR_LINK = /(?<!\\)!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?(\[[^\[\]]*\]|\([^\(\)]*\))?/;
|
|
1838
2523
|
const EMPHASIZED_LINK = /(\*\*?|__?)(\[[^\]]*]\([^\)]+\))\1/;
|
|
1839
2524
|
const CODE = /(?:(?<!\\)`)(.+?)(?:(?<!\\)`)/;
|
|
1840
|
-
|
|
1841
|
-
const
|
|
2525
|
+
// (underscores never emphasize inside words: `snake_case_word`)
|
|
2526
|
+
const EMPHASIZE = /(\*\*?)((?!\1\s?)(?:(?!\1)[\S\s])*[^\s])\1|(?<![A-Za-z0-9])(__?)((?!\3\s?)(?:(?!\3)[\S\s])*[^\s])\3(?![A-Za-z0-9])/;
|
|
2527
|
+
// (underscores are emphasis markers only: `snake_case_word` must not strike anything through)
|
|
2528
|
+
const STRIKE = /(~~?)(?!\1\s?)([^\s][\s\S]*?(?!\1)[^\s])\1/;
|
|
1842
2529
|
const QUOTE = /(:")(?!":\s?)([^\s][\s\S]*?(?!":)[^\s])":/;
|
|
1843
2530
|
const SPACE = /^\s+/;
|
|
1844
|
-
|
|
2531
|
+
/** Blank lines between blocks (it keeps the indentation of the next line, which matters to indented code blocks). */
|
|
2532
|
+
const BLOCK_SPACE = /^(?:[ \t]*\r?\n)+|^\s+/;
|
|
2533
|
+
const LINEBREAK = /(?: {2,}|\\)\r?\n/;
|
|
1845
2534
|
const TEXT = /^[^\r\n]+/;
|
|
2535
|
+
/** Backslash escapes of ASCII punctuation (CommonMark); `\<` is an escaped `<` already escaped by `MarkdownService`. */
|
|
2536
|
+
const ESCAPE = /\\(<|[!"#$%&'()*+,\-./:;<=>?@\[\\\]^_`{|}~])/;
|
|
2537
|
+
/** Escaped characters that must stay text once rendered as html. */
|
|
2538
|
+
const ESCAPED_HTML = { '<': '<', '<': '<', '&': '&' };
|
|
2539
|
+
/** `<https://…>`, `<mailto:…>` and `<user@domain>` (the `<` may come escaped, e.g. by `MarkdownService`). */
|
|
2540
|
+
const AUTOLINK = /(?:<|<)((?:https?|ftp|mailto):[^\s<>]+|[\w.+-]+@[\w-]+(?:\.[\w-]+)+)>/;
|
|
2541
|
+
/** GFM bare URLs (`https://…`, `www.…`), trailing punctuation excluded. */
|
|
2542
|
+
const BARE_URL = /(?<![\w\/\\(\[<"'=;&@])(?:https?:\/\/|www\.)[^\s<>]*[^\s<>.,:;"'!?)\]*_~]/;
|
|
2543
|
+
/** GFM footnote reference `[^id]`. */
|
|
2544
|
+
const FOOTNOTE_REF = /\[\^([^\]\s]+)\]/;
|
|
2545
|
+
/** GFM footnote definition `[^id]: text` (continuation lines indented). */
|
|
2546
|
+
const FOOTNOTE_DEF = /^ {0,3}\[\^([^\]\s]+)\]: *([^\r\n]*(?:\r?\n {2,}[^\r\n]+)*)(\r?\n|$)+/;
|
|
2547
|
+
/** GFM table: header row, delimiter row, then body rows (up to a blank line). */
|
|
2548
|
+
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
2549
|
var MARKDOWN_TYPES;
|
|
1847
2550
|
(function (MARKDOWN_TYPES) {
|
|
1848
2551
|
MARKDOWN_TYPES["Html"] = "html";
|
|
@@ -1867,6 +2570,11 @@
|
|
|
1867
2570
|
MARKDOWN_TYPES["UnorderedList"] = "ul";
|
|
1868
2571
|
MARKDOWN_TYPES["ListItem"] = "listitem";
|
|
1869
2572
|
MARKDOWN_TYPES["Reference"] = "ref";
|
|
2573
|
+
MARKDOWN_TYPES["Table"] = "table";
|
|
2574
|
+
MARKDOWN_TYPES["TableRow"] = "tr";
|
|
2575
|
+
MARKDOWN_TYPES["TableCell"] = "td";
|
|
2576
|
+
MARKDOWN_TYPES["FootnoteReference"] = "fnref";
|
|
2577
|
+
MARKDOWN_TYPES["FootnoteDefinition"] = "fndef";
|
|
1870
2578
|
})(MARKDOWN_TYPES || (MARKDOWN_TYPES = {}));
|
|
1871
2579
|
var MARKDOWN_RULES;
|
|
1872
2580
|
(function (MARKDOWN_RULES) {
|
|
@@ -1889,6 +2597,12 @@
|
|
|
1889
2597
|
MARKDOWN_RULES[MARKDOWN_RULES["Quote"] = 16] = "Quote";
|
|
1890
2598
|
MARKDOWN_RULES[MARKDOWN_RULES["Code"] = 17] = "Code";
|
|
1891
2599
|
MARKDOWN_RULES[MARKDOWN_RULES["LineBreak"] = 18] = "LineBreak";
|
|
2600
|
+
MARKDOWN_RULES[MARKDOWN_RULES["FootnoteDefinition"] = 19] = "FootnoteDefinition";
|
|
2601
|
+
MARKDOWN_RULES[MARKDOWN_RULES["Table"] = 20] = "Table";
|
|
2602
|
+
MARKDOWN_RULES[MARKDOWN_RULES["Escape"] = 21] = "Escape";
|
|
2603
|
+
MARKDOWN_RULES[MARKDOWN_RULES["AutoLink"] = 22] = "AutoLink";
|
|
2604
|
+
MARKDOWN_RULES[MARKDOWN_RULES["FootnoteReference"] = 23] = "FootnoteReference";
|
|
2605
|
+
MARKDOWN_RULES[MARKDOWN_RULES["BareUrl"] = 24] = "BareUrl";
|
|
1892
2606
|
// lowest priority
|
|
1893
2607
|
MARKDOWN_RULES[MARKDOWN_RULES["Text"] = 254] = "Text";
|
|
1894
2608
|
MARKDOWN_RULES[MARKDOWN_RULES["Space"] = 255] = "Space";
|
|
@@ -1909,7 +2623,8 @@
|
|
|
1909
2623
|
type: MARKDOWN_TYPES.Html, index, raw: arr[0], text: arr[2]
|
|
1910
2624
|
}];
|
|
1911
2625
|
if (index > 0) {
|
|
1912
|
-
|
|
2626
|
+
// (the top-level rules: reference and footnote definitions may precede a comment)
|
|
2627
|
+
const left = input.substr(0, index), tokens = lexer.tokenize(left, topRules);
|
|
1913
2628
|
Array.prototype.unshift.apply(output, tokens);
|
|
1914
2629
|
}
|
|
1915
2630
|
return output;
|
|
@@ -1924,6 +2639,16 @@
|
|
|
1924
2639
|
}];
|
|
1925
2640
|
})
|
|
1926
2641
|
},
|
|
2642
|
+
// footnote definition (rendered at the end of the document, see HtmlRenderer)
|
|
2643
|
+
[MARKDOWN_RULES.FootnoteDefinition]: {
|
|
2644
|
+
exec: (input, lexer) => match(input, FOOTNOTE_DEF, arr => {
|
|
2645
|
+
const raw = arr[0], id = normalizeReference(arr[1]), text = arr[2].replace(/\r?\n {2,}/g, ' ');
|
|
2646
|
+
return [{
|
|
2647
|
+
type: MARKDOWN_TYPES.FootnoteDefinition, index: regexMatchIndex(arr), raw, text, id,
|
|
2648
|
+
tokens: lexer.tokenize(text, inlineRules)
|
|
2649
|
+
}];
|
|
2650
|
+
})
|
|
2651
|
+
},
|
|
1927
2652
|
};
|
|
1928
2653
|
const blockExtra = [], inlineExtra = [], codeExtra = {};
|
|
1929
2654
|
for (let r of extra) {
|
|
@@ -1982,21 +2707,61 @@
|
|
|
1982
2707
|
// link/image
|
|
1983
2708
|
[MARKDOWN_RULES.ImageOrLink]: {
|
|
1984
2709
|
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],
|
|
2710
|
+
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);
|
|
2711
|
+
// `(url "title")`
|
|
2712
|
+
const { url, title } = splitUrlAndTitle(destination);
|
|
2713
|
+
const tokens = lexer.tokenize(text, inlineRules);
|
|
1986
2714
|
if (image) {
|
|
1987
2715
|
return [{
|
|
1988
2716
|
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)
|
|
2717
|
+
raw, text, src: url, title, ref, tokens
|
|
1998
2718
|
}];
|
|
1999
2719
|
}
|
|
2720
|
+
return [{
|
|
2721
|
+
index, type: url ? MARKDOWN_TYPES.Link : MARKDOWN_TYPES.LinkLike,
|
|
2722
|
+
raw, text, href: url, title, ref, tokens
|
|
2723
|
+
}];
|
|
2724
|
+
})
|
|
2725
|
+
},
|
|
2726
|
+
// backslash escapes: the escaped character is plain text
|
|
2727
|
+
[MARKDOWN_RULES.Escape]: {
|
|
2728
|
+
exec: (input, lexer) => inlineMatch(input, ESCAPE, lexer, arr => {
|
|
2729
|
+
const text = ESCAPED_HTML[arr[1]] ?? arr[1];
|
|
2730
|
+
return [{
|
|
2731
|
+
index: regexMatchIndex(arr), raw: arr[0], text, type: MARKDOWN_TYPES.Text
|
|
2732
|
+
}];
|
|
2733
|
+
})
|
|
2734
|
+
},
|
|
2735
|
+
// <https://…>, <user@domain>
|
|
2736
|
+
[MARKDOWN_RULES.AutoLink]: {
|
|
2737
|
+
exec: (input, lexer) => inlineMatch(input, AUTOLINK, lexer, arr => {
|
|
2738
|
+
const text = arr[1];
|
|
2739
|
+
const isEmail = !/^[a-z]+:/i.test(text);
|
|
2740
|
+
const href = isEmail ? 'mailto:' + text : text;
|
|
2741
|
+
return [{
|
|
2742
|
+
index: regexMatchIndex(arr), raw: arr[0], text, href, type: MARKDOWN_TYPES.Link,
|
|
2743
|
+
tokens: [{ type: MARKDOWN_TYPES.Text, raw: text, text }]
|
|
2744
|
+
}];
|
|
2745
|
+
})
|
|
2746
|
+
},
|
|
2747
|
+
// bare https://… or www.… urls
|
|
2748
|
+
[MARKDOWN_RULES.BareUrl]: {
|
|
2749
|
+
exec: (input, lexer) => inlineMatch(input, BARE_URL, lexer, arr => {
|
|
2750
|
+
const text = arr[0];
|
|
2751
|
+
const href = /^www\./i.test(text) ? 'http://' + text : text;
|
|
2752
|
+
return [{
|
|
2753
|
+
index: regexMatchIndex(arr), raw: text, text, href, type: MARKDOWN_TYPES.Link,
|
|
2754
|
+
tokens: [{ type: MARKDOWN_TYPES.Text, raw: text, text }]
|
|
2755
|
+
}];
|
|
2756
|
+
})
|
|
2757
|
+
},
|
|
2758
|
+
// [^footnote]
|
|
2759
|
+
[MARKDOWN_RULES.FootnoteReference]: {
|
|
2760
|
+
exec: (input, lexer) => inlineMatch(input, FOOTNOTE_REF, lexer, arr => {
|
|
2761
|
+
const id = normalizeReference(arr[1]);
|
|
2762
|
+
return [{
|
|
2763
|
+
index: regexMatchIndex(arr), raw: arr[0], text: arr[1], id, type: MARKDOWN_TYPES.FootnoteReference
|
|
2764
|
+
}];
|
|
2000
2765
|
})
|
|
2001
2766
|
},
|
|
2002
2767
|
// code
|
|
@@ -2024,10 +2789,12 @@
|
|
|
2024
2789
|
// italic/bold
|
|
2025
2790
|
[MARKDOWN_RULES.Emphasize]: {
|
|
2026
2791
|
exec: (input, lexer) => inlineMatch(input, EMPHASIZE, lexer, arr => {
|
|
2027
|
-
|
|
2792
|
+
// `*` (groups 1-2) or word-bounded `_` (groups 3-4) markers
|
|
2793
|
+
const marker = arr[1] ?? arr[3], text = arr[2] ?? arr[4];
|
|
2794
|
+
if (NullChecker.isNullOrEmpty(text)) {
|
|
2028
2795
|
return null;
|
|
2029
2796
|
}
|
|
2030
|
-
const type =
|
|
2797
|
+
const type = marker.length > 1 ? MARKDOWN_TYPES.Bold : MARKDOWN_TYPES.Italic;
|
|
2031
2798
|
return [{
|
|
2032
2799
|
index: regexMatchIndex(arr),
|
|
2033
2800
|
type, raw: arr[0], text, tokens: lexer.tokenize(text, inlineRules)
|
|
@@ -2096,7 +2863,20 @@
|
|
|
2096
2863
|
inPlaceInlineExec(input, output, lexer);
|
|
2097
2864
|
return output;
|
|
2098
2865
|
};
|
|
2099
|
-
|
|
2866
|
+
// explicit priorities: footnote references before `[links]`, escapes before emphasis, bare urls last
|
|
2867
|
+
const inlineRules = [
|
|
2868
|
+
inline[MARKDOWN_RULES.FootnoteReference],
|
|
2869
|
+
inline[MARKDOWN_RULES.EmphasizedLink],
|
|
2870
|
+
inline[MARKDOWN_RULES.ImageOrLink],
|
|
2871
|
+
inline[MARKDOWN_RULES.Escape],
|
|
2872
|
+
inline[MARKDOWN_RULES.AutoLink],
|
|
2873
|
+
inline[MARKDOWN_RULES.Emphasize],
|
|
2874
|
+
inline[MARKDOWN_RULES.StrikeThrough],
|
|
2875
|
+
inline[MARKDOWN_RULES.Quote],
|
|
2876
|
+
inline[MARKDOWN_RULES.Code],
|
|
2877
|
+
inline[MARKDOWN_RULES.LineBreak],
|
|
2878
|
+
inline[MARKDOWN_RULES.BareUrl],
|
|
2879
|
+
];
|
|
2100
2880
|
Array.prototype.push.apply(inlineRules, inlineExtra);
|
|
2101
2881
|
inlineRules.push(inlinelowpriority[MARKDOWN_RULES.Text], inlinelowpriority[MARKDOWN_RULES.Space]);
|
|
2102
2882
|
// #endregion
|
|
@@ -2115,7 +2895,12 @@
|
|
|
2115
2895
|
// paragraph
|
|
2116
2896
|
[MARKDOWN_RULES.Paragraph]: {
|
|
2117
2897
|
exec: (input, lexer) => match(input, PARAGRAPH, arr => {
|
|
2118
|
-
|
|
2898
|
+
// leading whitespace of paragraph lines is not content (CommonMark)
|
|
2899
|
+
const raw = arr[0], text = raw.replace(/^[ \t]+/gm, '');
|
|
2900
|
+
// whitespace-only lines are blank lines, not (empty) paragraphs
|
|
2901
|
+
if (text.trim().length === 0) {
|
|
2902
|
+
return null;
|
|
2903
|
+
}
|
|
2119
2904
|
return [{
|
|
2120
2905
|
type: MARKDOWN_TYPES.Paragraph, raw, text,
|
|
2121
2906
|
// recursion (inline rules only)
|
|
@@ -2123,12 +2908,57 @@
|
|
|
2123
2908
|
}];
|
|
2124
2909
|
})
|
|
2125
2910
|
},
|
|
2126
|
-
//
|
|
2127
|
-
[MARKDOWN_RULES.Space]:
|
|
2911
|
+
// blank lines (the indentation of the following line is kept: it matters to indented code blocks)
|
|
2912
|
+
[MARKDOWN_RULES.Space]: {
|
|
2913
|
+
exec: (input) => match(input, BLOCK_SPACE, arr => {
|
|
2914
|
+
const raw = arr[0];
|
|
2915
|
+
return [{
|
|
2916
|
+
type: MARKDOWN_TYPES.Space, raw, text: raw
|
|
2917
|
+
}];
|
|
2918
|
+
})
|
|
2919
|
+
}
|
|
2128
2920
|
};
|
|
2129
2921
|
const tokenizeListItem = (text, lexer) => {
|
|
2130
|
-
const
|
|
2131
|
-
|
|
2922
|
+
const needsBlocks = /^ {0,3}(`{3,}|$|>|[-+*]\s|\d+[.\)]|#{1,6}\s|\|)/m.test(text);
|
|
2923
|
+
if (!needsBlocks) {
|
|
2924
|
+
return lexer.tokenize(text, inlineRules);
|
|
2925
|
+
}
|
|
2926
|
+
const tokens = lexer.tokenize(text, blockRules);
|
|
2927
|
+
// tight items (no blank lines) keep their first paragraph unwrapped, e.g. `<li>a<ul>…</ul></li>`
|
|
2928
|
+
const first = tokens[0];
|
|
2929
|
+
const tight = !/\n[ \t]*\n/.test(text);
|
|
2930
|
+
if (!tight || first?.type !== MARKDOWN_TYPES.Paragraph) {
|
|
2931
|
+
return tokens;
|
|
2932
|
+
}
|
|
2933
|
+
const inner = first.tokens ?? [];
|
|
2934
|
+
const rest = tokens.slice(1);
|
|
2935
|
+
return inner.concat(rest);
|
|
2936
|
+
};
|
|
2937
|
+
const tokenizeListItems = (raw, lexer) => {
|
|
2938
|
+
const items = splitListItems(raw);
|
|
2939
|
+
const nonEmpty = items.filter(i => !NullChecker.isNullOrEmpty(i));
|
|
2940
|
+
return nonEmpty.map(item => {
|
|
2941
|
+
// GFM task list item: `[ ]`/`[x]`
|
|
2942
|
+
const task = TASK.exec(item);
|
|
2943
|
+
const text = task ? item.slice(task[0].length) : item;
|
|
2944
|
+
const tokens = tokenizeListItem(text, lexer);
|
|
2945
|
+
if (task == null) {
|
|
2946
|
+
return { type: MARKDOWN_TYPES.ListItem, raw: item, text, tokens };
|
|
2947
|
+
}
|
|
2948
|
+
const checked = task[1] !== ' ';
|
|
2949
|
+
return { type: MARKDOWN_TYPES.ListItem, raw: item, text, tokens, task: true, checked };
|
|
2950
|
+
});
|
|
2951
|
+
};
|
|
2952
|
+
const tokenizeTableRow = (cells, align, header, lexer) => {
|
|
2953
|
+
const tokens = align.map((cellAlign, i) => {
|
|
2954
|
+
// missing cells are empty, exceeding ones are ignored (GFM)
|
|
2955
|
+
const text = cells[i] ?? '';
|
|
2956
|
+
const cellTokens = lexer.tokenize(text, inlineRules);
|
|
2957
|
+
return { type: MARKDOWN_TYPES.TableCell, raw: text, text, header, align: cellAlign, tokens: cellTokens };
|
|
2958
|
+
});
|
|
2959
|
+
const raw = cells.join(' | ');
|
|
2960
|
+
const row = { type: MARKDOWN_TYPES.TableRow, raw, text: raw, header, tokens };
|
|
2961
|
+
return row;
|
|
2132
2962
|
};
|
|
2133
2963
|
const block = {
|
|
2134
2964
|
// hr
|
|
@@ -2143,7 +2973,8 @@
|
|
|
2143
2973
|
// headings
|
|
2144
2974
|
[MARKDOWN_RULES.Heading]: {
|
|
2145
2975
|
exec: (input, lexer) => match(input, HEADING, arr => {
|
|
2146
|
-
|
|
2976
|
+
// optional closing sequence: `## Title ##`
|
|
2977
|
+
const text = arr[2].replace(/[ \t]+#+[ \t]*$/, '');
|
|
2147
2978
|
return [{
|
|
2148
2979
|
type: MARKDOWN_TYPES.Heading, raw: arr[0], text, level: arr[1].length, ref: normalizeReference(text),
|
|
2149
2980
|
tokens: lexer.tokenize(text, inlineRules)
|
|
@@ -2171,60 +3002,86 @@
|
|
|
2171
3002
|
// codeblock
|
|
2172
3003
|
[MARKDOWN_RULES.CodeBlock]: {
|
|
2173
3004
|
exec: (input, lexer) => match(input, CODEBLOCK, arr => {
|
|
2174
|
-
|
|
3005
|
+
// indented code: 4 spaces of indentation are not part of the code
|
|
3006
|
+
const raw = arr[0], code = raw.replace(/^ {4}/gm, '').replace(/\s+$/, ''), text = htmlEncode(code);
|
|
3007
|
+
// whitespace-only indented lines are blank lines, not code
|
|
3008
|
+
if (code.length === 0) {
|
|
3009
|
+
return null;
|
|
3010
|
+
}
|
|
2175
3011
|
return [{
|
|
2176
3012
|
type: MARKDOWN_TYPES.CodeBlock, raw, text,
|
|
2177
|
-
tokens: lexer.tokenize(text,
|
|
3013
|
+
tokens: lexer.tokenize(text, getLangGrammar(''))
|
|
2178
3014
|
}];
|
|
2179
3015
|
})
|
|
2180
3016
|
},
|
|
2181
3017
|
// codeblock
|
|
2182
3018
|
[MARKDOWN_RULES.Fences]: {
|
|
2183
3019
|
exec: (input, lexer) => match(input, FENCES, arr => {
|
|
2184
|
-
|
|
3020
|
+
// the info string may carry more than the language (e.g. ```js title)
|
|
3021
|
+
const info = (arr[1] ?? '').trim(), words = info.split(/\s+/), lang = words[0].toLowerCase();
|
|
3022
|
+
// custom `code` rules (see ExtraRule) win over the registered vocabularies
|
|
3023
|
+
const syntax = lang in codeExtra ? null : Syntaxes.get(lang);
|
|
3024
|
+
if (syntax == null) {
|
|
3025
|
+
const text = htmlEncode(arr[2]);
|
|
3026
|
+
return [{
|
|
3027
|
+
type: MARKDOWN_TYPES.CodeBlock, raw: arr[0], text, lang,
|
|
3028
|
+
tokens: lexer.tokenize(text, getLangGrammar(lang))
|
|
3029
|
+
}];
|
|
3030
|
+
}
|
|
3031
|
+
// highlighted by the language-agnostic Scanner (see HtmlRenderer)
|
|
3032
|
+
const code = decodeCode(arr[2]);
|
|
3033
|
+
const tokens = scanner.tokenize(code, syntax);
|
|
2185
3034
|
return [{
|
|
2186
|
-
type: MARKDOWN_TYPES.CodeBlock, raw: arr[0], text, lang,
|
|
2187
|
-
tokens: lexer.tokenize(text, getLangGrammar(lang))
|
|
3035
|
+
type: MARKDOWN_TYPES.CodeBlock, raw: arr[0], text: code, lang, syntax: syntax.name, highlighted: true, tokens
|
|
2188
3036
|
}];
|
|
2189
3037
|
})
|
|
2190
3038
|
},
|
|
2191
3039
|
// blockquote
|
|
2192
3040
|
[MARKDOWN_RULES.BlockQuote]: {
|
|
2193
3041
|
exec: (input, lexer) => match(input, BLOCKQUOTE, arr => {
|
|
2194
|
-
const raw = arr[0], text = raw.replace(/^ {0,3}>
|
|
3042
|
+
const raw = arr[0], text = raw.replace(/^ {0,3}> ?/gm, '');
|
|
2195
3043
|
return [{
|
|
2196
3044
|
type: MARKDOWN_TYPES.BlockQuote, raw, text,
|
|
2197
|
-
// recursion
|
|
2198
|
-
tokens: lexer.tokenize(text,
|
|
2199
|
-
lowpriority[MARKDOWN_RULES.Paragraph],
|
|
2200
|
-
lowpriority[MARKDOWN_RULES.Space]
|
|
2201
|
-
])
|
|
3045
|
+
// recursion: any block (nested quotes, lists, code…)
|
|
3046
|
+
tokens: lexer.tokenize(text, blockRules)
|
|
2202
3047
|
}];
|
|
2203
3048
|
})
|
|
2204
3049
|
},
|
|
2205
3050
|
// lists
|
|
2206
3051
|
[MARKDOWN_RULES.OrderedList]: {
|
|
2207
3052
|
exec: (input, lexer) => match(input, OLIST, arr => {
|
|
2208
|
-
const raw = arr[0],
|
|
3053
|
+
const raw = arr[0], startMatch = /\d+/.exec(raw), start = parseInt(startMatch[0]), tokens = tokenizeListItems(raw, lexer);
|
|
2209
3054
|
return [{
|
|
2210
|
-
type: MARKDOWN_TYPES.OrderedList, start
|
|
2211
|
-
// avoid paragraphs if not strictly needed
|
|
2212
|
-
return {
|
|
2213
|
-
type: MARKDOWN_TYPES.ListItem, raw: i, text: i, tokens: tokenizeListItem(i, lexer)
|
|
2214
|
-
};
|
|
2215
|
-
})
|
|
3055
|
+
type: MARKDOWN_TYPES.OrderedList, start, raw, text: raw, tokens
|
|
2216
3056
|
}];
|
|
2217
3057
|
})
|
|
2218
3058
|
},
|
|
2219
3059
|
[MARKDOWN_RULES.UnorderedList]: {
|
|
2220
3060
|
exec: (input, lexer) => match(input, ULIST, arr => {
|
|
2221
|
-
const raw = arr[0],
|
|
3061
|
+
const raw = arr[0], tokens = tokenizeListItems(raw, lexer);
|
|
2222
3062
|
return [{
|
|
2223
|
-
type: MARKDOWN_TYPES.UnorderedList, raw, text: raw, tokens
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
3063
|
+
type: MARKDOWN_TYPES.UnorderedList, raw, text: raw, tokens
|
|
3064
|
+
}];
|
|
3065
|
+
})
|
|
3066
|
+
},
|
|
3067
|
+
// GFM tables
|
|
3068
|
+
[MARKDOWN_RULES.Table]: {
|
|
3069
|
+
exec: (input, lexer) => match(input, TABLE, arr => {
|
|
3070
|
+
const headers = splitTableCells(arr[1]), delimiters = splitTableCells(arr[2]);
|
|
3071
|
+
// the delimiter row must match the header row
|
|
3072
|
+
if (headers.length !== delimiters.length) {
|
|
3073
|
+
return null;
|
|
3074
|
+
}
|
|
3075
|
+
const align = delimiters.map(tableAlignment);
|
|
3076
|
+
const bodyLines = arr[3].split(/\r?\n/).filter(l => l.trim().length > 0);
|
|
3077
|
+
const headerRow = tokenizeTableRow(headers, align, true, lexer);
|
|
3078
|
+
const bodyRows = bodyLines.map(line => {
|
|
3079
|
+
const cells = splitTableCells(line);
|
|
3080
|
+
return tokenizeTableRow(cells, align, false, lexer);
|
|
3081
|
+
});
|
|
3082
|
+
const rows = [headerRow].concat(bodyRows);
|
|
3083
|
+
return [{
|
|
3084
|
+
type: MARKDOWN_TYPES.Table, raw: arr[0], text: arr[0], align, tokens: rows
|
|
2228
3085
|
}];
|
|
2229
3086
|
})
|
|
2230
3087
|
}
|
|
@@ -2233,13 +3090,16 @@
|
|
|
2233
3090
|
Array.prototype.push.apply(blockRules, blockExtra);
|
|
2234
3091
|
blockRules.push(lowpriority[MARKDOWN_RULES.Paragraph], lowpriority[MARKDOWN_RULES.Space]);
|
|
2235
3092
|
// #endregion
|
|
2236
|
-
|
|
3093
|
+
const topRules = [
|
|
2237
3094
|
// comment first
|
|
2238
3095
|
inert[MARKDOWN_RULES.Comment],
|
|
3096
|
+
// (before references: `[^id]: …` would match them too)
|
|
3097
|
+
inert[MARKDOWN_RULES.FootnoteDefinition],
|
|
2239
3098
|
inert[MARKDOWN_RULES.Reference]
|
|
2240
3099
|
].concat(
|
|
2241
3100
|
// block rules contain recursive inline rules (at least)
|
|
2242
3101
|
blockRules);
|
|
3102
|
+
return topRules;
|
|
2243
3103
|
}
|
|
2244
3104
|
// #region HTML RENDERER HELPER
|
|
2245
3105
|
// meant to help in unnecessary spaces replacement.
|
|
@@ -2248,11 +3108,29 @@
|
|
|
2248
3108
|
function trim(input) {
|
|
2249
3109
|
return input?.replace(TRIM_INSIDE, '\n').trim();
|
|
2250
3110
|
}
|
|
2251
|
-
|
|
3111
|
+
function titleAttribute(title) {
|
|
3112
|
+
if (NullChecker.isNullOrEmpty(title)) {
|
|
3113
|
+
return '';
|
|
3114
|
+
}
|
|
3115
|
+
const encoded = attributeEncode(title);
|
|
3116
|
+
return ` title="${encoded}"`;
|
|
3117
|
+
}
|
|
3118
|
+
function alignAttribute(align) {
|
|
3119
|
+
if (align == null) {
|
|
3120
|
+
return '';
|
|
3121
|
+
}
|
|
3122
|
+
return ` style="text-align: ${align}"`;
|
|
3123
|
+
}
|
|
2252
3124
|
/** Renders a Markdown token tree, as produced by the grammar returned by {@link getGrammar}, into HTML markup. */
|
|
2253
3125
|
class HtmlRenderer {
|
|
2254
3126
|
constructor(_extraItemRender = (token, __) => token?.text || '') {
|
|
2255
3127
|
this._extraItemRender = _extraItemRender;
|
|
3128
|
+
/** Link/image reference definitions of the whole document (they may be referenced from any depth). */
|
|
3129
|
+
this.#references = [];
|
|
3130
|
+
/** Footnote definitions by id, and the order of their first reference (their numbering). */
|
|
3131
|
+
this.#footnotes = new Map();
|
|
3132
|
+
this.#footnoteOrder = [];
|
|
3133
|
+
this.#highlighter = new HighlightRenderer();
|
|
2256
3134
|
this._renderContent = (token) => {
|
|
2257
3135
|
const children = token.tokens;
|
|
2258
3136
|
if (NullChecker.isNullOrEmpty(children)) {
|
|
@@ -2261,10 +3139,144 @@
|
|
|
2261
3139
|
return this._render(children);
|
|
2262
3140
|
};
|
|
2263
3141
|
}
|
|
3142
|
+
/** Link/image reference definitions of the whole document (they may be referenced from any depth). */
|
|
3143
|
+
#references;
|
|
3144
|
+
/** Footnote definitions by id, and the order of their first reference (their numbering). */
|
|
3145
|
+
#footnotes;
|
|
3146
|
+
#footnoteOrder;
|
|
3147
|
+
#highlighter;
|
|
3148
|
+
/** Collects the reference and footnote definitions of the whole tree. */
|
|
3149
|
+
_collectDefinitions(tree) {
|
|
3150
|
+
for (let token of tree ?? []) {
|
|
3151
|
+
if (token?.type === MARKDOWN_TYPES.Reference) {
|
|
3152
|
+
this.#references.push(token);
|
|
3153
|
+
}
|
|
3154
|
+
if (token?.type === MARKDOWN_TYPES.FootnoteDefinition && !this.#footnotes.has(token['id'])) {
|
|
3155
|
+
this.#footnotes.set(token['id'], token);
|
|
3156
|
+
}
|
|
3157
|
+
this._collectDefinitions(token?.tokens);
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
_findReference(ref, image) {
|
|
3161
|
+
if (NullChecker.isNullOrEmpty(ref)) {
|
|
3162
|
+
return undefined;
|
|
3163
|
+
}
|
|
3164
|
+
const key = ref.toLowerCase();
|
|
3165
|
+
return this.#references.find(t => (t.image === true) === image && t.id?.toLowerCase() === key);
|
|
3166
|
+
}
|
|
3167
|
+
_renderCodeBlock(token) {
|
|
3168
|
+
const R = CARRIAGE_RETURN_PLACEHOLDER;
|
|
3169
|
+
const lang = token['lang'];
|
|
3170
|
+
if (token['highlighted'] !== true) {
|
|
3171
|
+
const classAttribute = lang ? (' class="' + lang + '"') : '';
|
|
3172
|
+
const content = this._renderContent(token);
|
|
3173
|
+
return `<pre>${R}<code${classAttribute}>${content}</code>${R}</pre>${R}`;
|
|
3174
|
+
}
|
|
3175
|
+
// `code-highlighted` (and `code-markup`) let the theme style any registered language
|
|
3176
|
+
const classes = [lang, 'code-highlighted'];
|
|
3177
|
+
if (token['syntax'] === MARKUP_SYNTAX) {
|
|
3178
|
+
classes.push('code-markup');
|
|
3179
|
+
}
|
|
3180
|
+
const classList = attributeEncode(classes.join(' '));
|
|
3181
|
+
const highlighted = this.#highlighter.render(token.tokens);
|
|
3182
|
+
return `<pre>${R}<code class="${classList}">${highlighted}</code>${R}</pre>${R}`;
|
|
3183
|
+
}
|
|
3184
|
+
_renderImage(token) {
|
|
3185
|
+
let src = token.src, title = token.title;
|
|
3186
|
+
if (!src) {
|
|
3187
|
+
const reference = this._findReference(token.ref, true);
|
|
3188
|
+
src = reference?.url;
|
|
3189
|
+
title = reference?.title;
|
|
3190
|
+
}
|
|
3191
|
+
if (!src) {
|
|
3192
|
+
const content = this._renderContent(token);
|
|
3193
|
+
return `![${content}]`;
|
|
3194
|
+
}
|
|
3195
|
+
const titleAttr = titleAttribute(title);
|
|
3196
|
+
return `<img src="${src}" alt="${token.text}"${titleAttr} />`;
|
|
3197
|
+
}
|
|
3198
|
+
_renderLink(token) {
|
|
3199
|
+
let href = token.href, title = token.title;
|
|
3200
|
+
if (!href) {
|
|
3201
|
+
const reference = this._findReference(token.ref, false);
|
|
3202
|
+
href = reference?.url;
|
|
3203
|
+
title = reference?.title;
|
|
3204
|
+
}
|
|
3205
|
+
const content = this._renderContent(token);
|
|
3206
|
+
if (!href) {
|
|
3207
|
+
return `[${content}]`;
|
|
3208
|
+
}
|
|
3209
|
+
const titleAttr = titleAttribute(title);
|
|
3210
|
+
return `<a href="${href}"${titleAttr}>${content}</a>`;
|
|
3211
|
+
}
|
|
3212
|
+
_renderListItem(token) {
|
|
3213
|
+
const R = CARRIAGE_RETURN_PLACEHOLDER;
|
|
3214
|
+
const content = this._renderContent(token);
|
|
3215
|
+
if (token['task'] !== true) {
|
|
3216
|
+
return `\t<li>${content}</li>${R}`;
|
|
3217
|
+
}
|
|
3218
|
+
const checked = token['checked'] === true ? ' checked' : '';
|
|
3219
|
+
return `\t<li class="task-list-item"><input type="checkbox" disabled${checked} /> ${content}</li>${R}`;
|
|
3220
|
+
}
|
|
3221
|
+
_renderTableRow(row) {
|
|
3222
|
+
const R = CARRIAGE_RETURN_PLACEHOLDER;
|
|
3223
|
+
let cells = '';
|
|
3224
|
+
for (let cell of row.tokens ?? []) {
|
|
3225
|
+
const tag = cell['header'] === true ? 'th' : 'td';
|
|
3226
|
+
const align = alignAttribute(cell['align']);
|
|
3227
|
+
const content = this._renderContent(cell);
|
|
3228
|
+
cells += `<${tag}${align}>${content}</${tag}>`;
|
|
3229
|
+
}
|
|
3230
|
+
return `<tr>${cells}</tr>${R}`;
|
|
3231
|
+
}
|
|
3232
|
+
_renderTable(token) {
|
|
3233
|
+
const R = CARRIAGE_RETURN_PLACEHOLDER;
|
|
3234
|
+
const rows = token.tokens ?? [];
|
|
3235
|
+
const headerRows = rows.filter(r => r['header'] === true);
|
|
3236
|
+
const bodyRows = rows.filter(r => r['header'] !== true);
|
|
3237
|
+
const head = headerRows.map(r => this._renderTableRow(r)).join('');
|
|
3238
|
+
let html = `<table>${R}<thead>${R}${head}</thead>${R}`;
|
|
3239
|
+
if (bodyRows.length > 0) {
|
|
3240
|
+
const body = bodyRows.map(r => this._renderTableRow(r)).join('');
|
|
3241
|
+
html += `<tbody>${R}${body}</tbody>${R}`;
|
|
3242
|
+
}
|
|
3243
|
+
return html + `</table>${R}`;
|
|
3244
|
+
}
|
|
3245
|
+
_renderFootnoteReference(token) {
|
|
3246
|
+
const id = token['id'];
|
|
3247
|
+
if (!this.#footnotes.has(id)) {
|
|
3248
|
+
return `[^${token.text}]`;
|
|
3249
|
+
}
|
|
3250
|
+
const order = this.#footnoteOrder;
|
|
3251
|
+
const isFirst = !order.includes(id);
|
|
3252
|
+
if (isFirst) {
|
|
3253
|
+
order.push(id);
|
|
3254
|
+
}
|
|
3255
|
+
const number = order.indexOf(id) + 1;
|
|
3256
|
+
const anchorId = isFirst ? ` id="fnref-${id}"` : '';
|
|
3257
|
+
return `<sup class="footnote-ref"><a href="#fn-${id}"${anchorId}>${number}</a></sup>`;
|
|
3258
|
+
}
|
|
3259
|
+
/** Renders the referenced footnotes, in order of first reference (definitions may reference further footnotes). */
|
|
3260
|
+
_renderFootnotes() {
|
|
3261
|
+
const order = this.#footnoteOrder;
|
|
3262
|
+
if (order.length === 0) {
|
|
3263
|
+
return '';
|
|
3264
|
+
}
|
|
3265
|
+
const R = CARRIAGE_RETURN_PLACEHOLDER;
|
|
3266
|
+
let items = '';
|
|
3267
|
+
for (let i = 0; i < order.length; i++) {
|
|
3268
|
+
const id = order[i];
|
|
3269
|
+
const definition = this.#footnotes.get(id);
|
|
3270
|
+
const content = this._renderContent(definition);
|
|
3271
|
+
items += `<li id="fn-${id}">${content} <a href="#fnref-${id}" class="footnote-backref">↩</a></li>${R}`;
|
|
3272
|
+
}
|
|
3273
|
+
return `<section class="footnotes">${R}<ol>${R}${items}</ol>${R}</section>${R}`;
|
|
3274
|
+
}
|
|
2264
3275
|
_rendeItem(token, allTokens) {
|
|
2265
3276
|
const r = this._renderContent, R = CARRIAGE_RETURN_PLACEHOLDER;
|
|
2266
3277
|
switch (token?.type) {
|
|
2267
3278
|
case MARKDOWN_TYPES.Reference:
|
|
3279
|
+
case MARKDOWN_TYPES.FootnoteDefinition:
|
|
2268
3280
|
return '';
|
|
2269
3281
|
case MARKDOWN_TYPES.Html:
|
|
2270
3282
|
return token.raw;
|
|
@@ -2273,8 +3285,7 @@
|
|
|
2273
3285
|
case MARKDOWN_TYPES.Bold:
|
|
2274
3286
|
return `<b>${r(token)}</b>`;
|
|
2275
3287
|
case MARKDOWN_TYPES.CodeBlock:
|
|
2276
|
-
|
|
2277
|
-
return `<pre>${R}<code${(lang ? (' class="' + lang + '"') : '')}>${r(token)}</code>${R}</pre>${R}`;
|
|
3288
|
+
return this._renderCodeBlock(token);
|
|
2278
3289
|
case MARKDOWN_TYPES.Code:
|
|
2279
3290
|
return `<code>${r(token)}</code>`;
|
|
2280
3291
|
case MARKDOWN_TYPES.HorizontalRule:
|
|
@@ -2284,17 +3295,7 @@
|
|
|
2284
3295
|
return `<h${hlevel} id="${hid}">${r(token)}</h${hlevel}>${R}`;
|
|
2285
3296
|
case MARKDOWN_TYPES.Image:
|
|
2286
3297
|
case MARKDOWN_TYPES.ImageLike:
|
|
2287
|
-
|
|
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)}]`;
|
|
3298
|
+
return this._renderImage(token);
|
|
2298
3299
|
case MARKDOWN_TYPES.Italic:
|
|
2299
3300
|
return `<i>${r(token)}</i>`;
|
|
2300
3301
|
case MARKDOWN_TYPES.StrikeThrough:
|
|
@@ -2308,20 +3309,14 @@
|
|
|
2308
3309
|
case MARKDOWN_TYPES.UnorderedList:
|
|
2309
3310
|
return `<ul>\n${r(token)}</ul>${R}`;
|
|
2310
3311
|
case MARKDOWN_TYPES.ListItem:
|
|
2311
|
-
return
|
|
3312
|
+
return this._renderListItem(token);
|
|
2312
3313
|
case MARKDOWN_TYPES.Link:
|
|
2313
3314
|
case MARKDOWN_TYPES.LinkLike:
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
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)}]`;
|
|
3315
|
+
return this._renderLink(token);
|
|
3316
|
+
case MARKDOWN_TYPES.Table:
|
|
3317
|
+
return this._renderTable(token);
|
|
3318
|
+
case MARKDOWN_TYPES.FootnoteReference:
|
|
3319
|
+
return this._renderFootnoteReference(token);
|
|
2325
3320
|
case MARKDOWN_TYPES.Paragraph:
|
|
2326
3321
|
return `<p>${r(token)}</p>${R}`;
|
|
2327
3322
|
case MARKDOWN_TYPES.Text:
|
|
@@ -2338,7 +3333,13 @@
|
|
|
2338
3333
|
return acc;
|
|
2339
3334
|
}
|
|
2340
3335
|
render(tree) {
|
|
2341
|
-
|
|
3336
|
+
this.#references = [];
|
|
3337
|
+
this.#footnotes.clear();
|
|
3338
|
+
this.#footnoteOrder = [];
|
|
3339
|
+
this._collectDefinitions(tree);
|
|
3340
|
+
const body = this._render(tree);
|
|
3341
|
+
const footnotes = this._renderFootnotes();
|
|
3342
|
+
return trim(body + footnotes);
|
|
2342
3343
|
}
|
|
2343
3344
|
}
|
|
2344
3345
|
/** Renders a Markdown token tree as a JSON array of tokens, mainly useful for debugging/inspection. */
|
|
@@ -2380,12 +3381,13 @@
|
|
|
2380
3381
|
return JSON.parse(json);
|
|
2381
3382
|
}
|
|
2382
3383
|
parse(input, grammarOrRenderer = getGrammar(), renderer = new HtmlRenderer()) {
|
|
3384
|
+
// line endings are normalized: the grammar expects `\n` (`.` doesn't match `\r`: CRLF paragraphs would split by line)
|
|
3385
|
+
const normalized = (input ?? '').replace(/\r\n?/g, '\n');
|
|
2383
3386
|
if (isRenderer(grammarOrRenderer)) {
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
else {
|
|
2387
|
-
return super.parse(input, grammarOrRenderer, renderer);
|
|
3387
|
+
const grammar = getGrammar();
|
|
3388
|
+
return super.parse(normalized, grammar, grammarOrRenderer);
|
|
2388
3389
|
}
|
|
3390
|
+
return super.parse(normalized, grammarOrRenderer, renderer);
|
|
2389
3391
|
}
|
|
2390
3392
|
}
|
|
2391
3393
|
//}
|
|
@@ -2395,10 +3397,357 @@
|
|
|
2395
3397
|
Parser: Parser
|
|
2396
3398
|
});
|
|
2397
3399
|
|
|
3400
|
+
//namespace Pacem.Compile {
|
|
3401
|
+
/** After these, a `/` starts a division (not a regular expression literal). */
|
|
3402
|
+
const DIVISION_AFTER_TYPES = ['identifier', 'number', 'string', 'regex'];
|
|
3403
|
+
const DIVISION_AFTER_TEXTS = [')', ']', '}', 'this', 'super', 'null', 'undefined', 'true', 'false', '++', '--'];
|
|
3404
|
+
const JAVASCRIPT_KEYWORDS = [
|
|
3405
|
+
// JavaScript
|
|
3406
|
+
'async', 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do',
|
|
3407
|
+
'else', 'enum', 'export', 'extends', 'false', 'finally', 'for', 'from', 'function', 'get', 'if', 'import', 'in',
|
|
3408
|
+
'instanceof', 'let', 'new', 'null', 'of', 'return', 'set', 'static', 'super', 'switch', 'this', 'throw', 'true',
|
|
3409
|
+
'try', 'typeof', 'undefined', 'var', 'void', 'while', 'with', 'yield',
|
|
3410
|
+
// TypeScript
|
|
3411
|
+
'abstract', 'accessor', 'any', 'as', 'asserts', 'bigint', 'boolean', 'declare', 'implements', 'infer',
|
|
3412
|
+
'interface', 'is', 'keyof', 'module', 'namespace', 'never', 'number', 'object', 'override', 'package', 'private',
|
|
3413
|
+
'protected', 'public', 'readonly', 'satisfies', 'string', 'symbol', 'type', 'unique', 'unknown'
|
|
3414
|
+
];
|
|
3415
|
+
/** Rules shared by every "code" state (top level, template expressions, nested braces). */
|
|
3416
|
+
const CODE_RULES$1 = [
|
|
3417
|
+
{ match: String.raw `/\*\*(?!/)[\s\S]*?(?:\*/|$)`, type: 'doc-comment' },
|
|
3418
|
+
{ match: String.raw `/\*[\s\S]*?(?:\*/|$)`, type: 'comment' },
|
|
3419
|
+
{ match: String.raw `//[^\r\n]*`, type: 'comment' },
|
|
3420
|
+
{ match: String.raw `\r\n|[\r\n
]`, type: 'newline' },
|
|
3421
|
+
{ match: String.raw `[ \t\f\v ]+`, type: 'space' },
|
|
3422
|
+
{ match: '`', type: 'string', push: 'template' },
|
|
3423
|
+
{ match: String.raw `"(?:\\[\s\S]|[^"\\\r\n])*"?`, type: 'string' },
|
|
3424
|
+
{ match: String.raw `'(?:\\[\s\S]|[^'\\\r\n])*'?`, type: 'string' },
|
|
3425
|
+
{
|
|
3426
|
+
// regular expression literal (only where a division can't be)
|
|
3427
|
+
match: String.raw `/(?![*/])(?:\\.|\[(?:\\.|[^\]\\\r\n])*\]|[^/\\\[\r\n])+/[a-z]*`, type: 'regex',
|
|
3428
|
+
when: { previousNotTypes: DIVISION_AFTER_TYPES, previousNotTexts: DIVISION_AFTER_TEXTS }
|
|
3429
|
+
},
|
|
3430
|
+
{ 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' },
|
|
3431
|
+
{ match: String.raw `#?[A-Za-z_$ -][\w$ -]*`, type: 'identifier' },
|
|
3432
|
+
{ match: String.raw `[()\[\]{}]`, type: 'bracket' },
|
|
3433
|
+
{ match: String.raw `\?\.(?!\d)|\.\.\.|[;,.]`, type: 'punctuation' },
|
|
3434
|
+
{ match: String.raw `>>>=|<<=|>>=|>>>|\*\*=|&&=|\|\|=|\?\?=|===|!==|=>|\*\*|\+\+|--|<<|>>|&&|\|\||\?\?|[-+*/%&|^<>!=]=?|[~?:@]`, type: 'operator' },
|
|
3435
|
+
];
|
|
3436
|
+
/**
|
|
3437
|
+
* JavaScript vocabulary, TypeScript superset: comments (JSDoc as `doc-comment`), strings, template literals with nested
|
|
3438
|
+
* `${…}` expressions, regular expression literals (told from divisions by the previous significant token), numbers
|
|
3439
|
+
* (hex/binary/octal/BigInt/separators), identifiers (`#private` included), keywords (JS + TS, not after `.`/`?.`),
|
|
3440
|
+
* brackets, punctuation and operators.
|
|
3441
|
+
*/
|
|
3442
|
+
const JavaScriptSyntax = {
|
|
3443
|
+
name: 'javascript',
|
|
3444
|
+
aliases: ['js', 'mjs', 'cjs', 'jsx', 'typescript', 'ts', 'tsx', 'script', 'ecmascript'],
|
|
3445
|
+
lexical: {
|
|
3446
|
+
start: 'root',
|
|
3447
|
+
states: {
|
|
3448
|
+
root: CODE_RULES$1,
|
|
3449
|
+
template: [
|
|
3450
|
+
{ match: '`', type: 'string', pop: true },
|
|
3451
|
+
{ match: String.raw `\$\{`, type: 'punctuation', push: 'templateExpression' },
|
|
3452
|
+
{ match: String.raw `(?:\\[\s\S]|\$(?!\{)|[^\\` + '`' + String.raw `$])+`, type: 'string' },
|
|
3453
|
+
],
|
|
3454
|
+
templateExpression: [
|
|
3455
|
+
{ match: String.raw `\}`, type: 'punctuation', pop: true },
|
|
3456
|
+
{ match: String.raw `\{`, type: 'bracket', push: 'braces' },
|
|
3457
|
+
{ include: 'root' },
|
|
3458
|
+
],
|
|
3459
|
+
braces: [
|
|
3460
|
+
{ match: String.raw `\}`, type: 'bracket', pop: true },
|
|
3461
|
+
{ match: String.raw `\{`, type: 'bracket', push: 'braces' },
|
|
3462
|
+
{ include: 'root' },
|
|
3463
|
+
],
|
|
3464
|
+
},
|
|
3465
|
+
keywords: { keyword: JAVASCRIPT_KEYWORDS },
|
|
3466
|
+
keywordExceptAfter: ['.', '?.'],
|
|
3467
|
+
}
|
|
3468
|
+
};
|
|
3469
|
+
|
|
3470
|
+
//namespace Pacem.Compile {
|
|
3471
|
+
/** Member accesses (`obj.#x`, `obj?.x`) are never Pacem binding tokens. */
|
|
3472
|
+
const NOT_AFTER_MEMBER_ACCESS = { previousNotTexts: ['.', '?.'] };
|
|
3473
|
+
/**
|
|
3474
|
+
* Pacem binding-expression vocabulary (the content of `{{ … }}` attributes): JavaScript plus the special tokens
|
|
3475
|
+
* resolved by core's `Expression`:
|
|
3476
|
+
* - `binding-host`: `:host` (followed by `.`);
|
|
3477
|
+
* - `binding-host-property`: `::name`;
|
|
3478
|
+
* - `binding-item`: `^item`, `^^item`, … (repeater item, n levels up);
|
|
3479
|
+
* - `binding-index`: `^index`, `^^index`, …;
|
|
3480
|
+
* - `binding-element`: `#id` (element reference);
|
|
3481
|
+
* - `binding-this`: `$this` / `this` (the bound element).
|
|
3482
|
+
*/
|
|
3483
|
+
const PacemBindingSyntax = {
|
|
3484
|
+
name: 'pacem-binding',
|
|
3485
|
+
aliases: ['pacem', 'binding'],
|
|
3486
|
+
extends: 'javascript',
|
|
3487
|
+
lexical: {
|
|
3488
|
+
states: {
|
|
3489
|
+
root: [
|
|
3490
|
+
{ match: String.raw `:host(?=\.)`, type: 'binding-host', when: NOT_AFTER_MEMBER_ACCESS },
|
|
3491
|
+
{ match: String.raw `::[A-Za-z_$][\w$]*`, type: 'binding-host-property' },
|
|
3492
|
+
{ match: String.raw `\^+item(?![\w$])`, type: 'binding-item' },
|
|
3493
|
+
{ match: String.raw `\^+index(?![\w$])`, type: 'binding-index' },
|
|
3494
|
+
{ match: String.raw `#[A-Za-z_][\w]*`, type: 'binding-element', when: NOT_AFTER_MEMBER_ACCESS },
|
|
3495
|
+
{ match: String.raw `\$?this(?![\w$])`, type: 'binding-this', when: NOT_AFTER_MEMBER_ACCESS },
|
|
3496
|
+
]
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
};
|
|
3500
|
+
|
|
3501
|
+
//namespace Pacem.Compile {
|
|
3502
|
+
const NEWLINE_RULE = { match: String.raw `\r\n|[\r\n]`, type: 'newline' };
|
|
3503
|
+
/** Attribute list of an opening tag. */
|
|
3504
|
+
const ATTRIBUTE_RULES = [
|
|
3505
|
+
NEWLINE_RULE,
|
|
3506
|
+
{ match: String.raw `[ \t\f]+`, type: 'space' },
|
|
3507
|
+
{ match: '"', type: 'string', push: 'doubleQuoted' },
|
|
3508
|
+
{ match: "'", type: 'string', push: 'singleQuoted' },
|
|
3509
|
+
{ match: String.raw `[^\s=/>"']+`, type: 'attribute' },
|
|
3510
|
+
{ match: '=', type: 'operator' },
|
|
3511
|
+
{ match: '/', type: 'punctuation' },
|
|
3512
|
+
];
|
|
3513
|
+
/** A Pacem binding (`{{ … }}`) inside an attribute value: embeds the `pacem-binding` vocabulary up to `}}`. */
|
|
3514
|
+
const BINDING_RULES = [
|
|
3515
|
+
{ match: String.raw `\{\{`, type: 'punctuation', embed: 'pacem-binding', until: String.raw `\}\}` },
|
|
3516
|
+
{ match: String.raw `\}\}`, type: 'punctuation' },
|
|
3517
|
+
];
|
|
3518
|
+
/**
|
|
3519
|
+
* Markup (HTML/XML) vocabulary: comments, doctypes/CDATA, tags (`tag`/`tagname`), attributes, quoted values (Pacem
|
|
3520
|
+
* `{{ … }}` bindings tokenized with `pacem-binding`), entities, text, and `<script>` bodies tokenized with `javascript`.
|
|
3521
|
+
*/
|
|
3522
|
+
const MarkupSyntax = {
|
|
3523
|
+
name: 'markup',
|
|
3524
|
+
aliases: ['html', 'htm', 'xml', 'xhtml', 'svg'],
|
|
3525
|
+
lexical: {
|
|
3526
|
+
start: 'root',
|
|
3527
|
+
states: {
|
|
3528
|
+
root: [
|
|
3529
|
+
{ match: String.raw `<!--[\s\S]*?(?:-->|$)`, type: 'comment' },
|
|
3530
|
+
{ match: String.raw `<![^>]*>?`, type: 'tag' },
|
|
3531
|
+
{ match: String.raw `(<\/)([\w:.-]+)(\s*>?)`, type: ['tag', 'tagname', 'tag'] },
|
|
3532
|
+
{ match: String.raw `(<)(script)(?![\w:.-])`, flags: 'i', type: ['tag', 'tagname'], push: 'scriptTag' },
|
|
3533
|
+
{ match: String.raw `(<)([\w:.-]+)`, type: ['tag', 'tagname'], push: 'tag' },
|
|
3534
|
+
{ match: String.raw `&(?:#\d+|#[xX][\da-fA-F]+|\w+);`, type: 'entity' },
|
|
3535
|
+
NEWLINE_RULE,
|
|
3536
|
+
{ match: String.raw `[^<&\r\n]+`, type: 'text' },
|
|
3537
|
+
],
|
|
3538
|
+
tag: [
|
|
3539
|
+
{ match: String.raw `/?>`, type: 'tag', pop: true },
|
|
3540
|
+
{ include: 'attributes' },
|
|
3541
|
+
],
|
|
3542
|
+
scriptTag: [
|
|
3543
|
+
{ match: '/>', type: 'tag', pop: true },
|
|
3544
|
+
// the script body is tokenized as JavaScript, up to the closing tag
|
|
3545
|
+
{ match: '>', type: 'tag', pop: true, embed: 'javascript', until: String.raw `<\/script` },
|
|
3546
|
+
{ include: 'attributes' },
|
|
3547
|
+
],
|
|
3548
|
+
attributes: ATTRIBUTE_RULES,
|
|
3549
|
+
doubleQuoted: [
|
|
3550
|
+
{ match: '"', type: 'string', pop: true },
|
|
3551
|
+
...BINDING_RULES,
|
|
3552
|
+
{ match: String.raw `(?:[^"{}]|\{(?!\{)|\}(?!\}))+`, type: 'string' },
|
|
3553
|
+
],
|
|
3554
|
+
singleQuoted: [
|
|
3555
|
+
{ match: "'", type: 'string', pop: true },
|
|
3556
|
+
...BINDING_RULES,
|
|
3557
|
+
{ match: String.raw `(?:[^'{}]|\{(?!\{)|\}(?!\}))+`, type: 'string' },
|
|
3558
|
+
],
|
|
3559
|
+
},
|
|
3560
|
+
insignificant: ['space', 'newline', 'comment', 'text']
|
|
3561
|
+
}
|
|
3562
|
+
};
|
|
3563
|
+
|
|
3564
|
+
//namespace Pacem.Compile {
|
|
3565
|
+
const CSHARP_KEYWORDS = [
|
|
3566
|
+
'abstract', 'add', 'alias', 'and', 'args', 'as', 'ascending', 'async', 'await', 'base', 'bool', 'break', 'by',
|
|
3567
|
+
'byte', 'case', 'catch', 'char', 'checked', 'class', 'const', 'continue', 'decimal', 'default', 'delegate',
|
|
3568
|
+
'descending', 'do', 'double', 'dynamic', 'else', 'enum', 'equals', 'event', 'explicit', 'extern', 'false', 'file',
|
|
3569
|
+
'finally', 'fixed', 'float', 'for', 'foreach', 'from', 'get', 'global', 'goto', 'group', 'if', 'implicit', 'in',
|
|
3570
|
+
'init', 'int', 'interface', 'internal', 'into', 'is', 'join', 'let', 'lock', 'long', 'managed', 'nameof',
|
|
3571
|
+
'namespace', 'new', 'nint', 'not', 'notnull', 'nuint', 'null', 'object', 'on', 'operator', 'or', 'orderby', 'out',
|
|
3572
|
+
'override', 'params', 'partial', 'private', 'protected', 'public', 'readonly', 'record', 'ref', 'remove',
|
|
3573
|
+
'required', 'return', 'sbyte', 'scoped', 'sealed', 'select', 'set', 'short', 'sizeof', 'stackalloc', 'static',
|
|
3574
|
+
'string', 'struct', 'switch', 'this', 'throw', 'true', 'try', 'typeof', 'uint', 'ulong', 'unchecked', 'unmanaged',
|
|
3575
|
+
'unsafe', 'ushort', 'using', 'value', 'var', 'virtual', 'void', 'volatile', 'when', 'where', 'while', 'with',
|
|
3576
|
+
'yield'
|
|
3577
|
+
];
|
|
3578
|
+
/** Rules shared by every "code" state (top level, interpolations, nested braces). */
|
|
3579
|
+
const CODE_RULES = [
|
|
3580
|
+
{ match: String.raw `///[^\r\n]*`, type: 'doc-comment' },
|
|
3581
|
+
{ match: String.raw `//[^\r\n]*`, type: 'comment' },
|
|
3582
|
+
{ match: String.raw `/\*[\s\S]*?(?:\*/|$)`, type: 'comment' },
|
|
3583
|
+
// preprocessor directives, at the beginning of a line
|
|
3584
|
+
{ match: String.raw `(?<=^[ \t]*|[\r\n][ \t]*)#[ \t]*[a-z]+[^\r\n]*`, type: 'preprocessor' },
|
|
3585
|
+
{ match: String.raw `\r\n|[\r\n]`, type: 'newline' },
|
|
3586
|
+
{ match: String.raw `[ \t\f\v\u00A0\uFEFF]+`, type: 'space' },
|
|
3587
|
+
// raw string literals ("""…""", optionally interpolated)
|
|
3588
|
+
{ match: String.raw `\$*("{3,})[\s\S]*?\1`, type: 'string' },
|
|
3589
|
+
{ match: String.raw `\$@"|@\$"`, type: 'string', push: 'interpolatedVerbatim' },
|
|
3590
|
+
{ match: String.raw `\$"`, type: 'string', push: 'interpolated' },
|
|
3591
|
+
{ match: String.raw `@"(?:[^"]|"")*"?`, type: 'string' },
|
|
3592
|
+
{ match: String.raw `"(?:\\.|[^"\\\r\n])*"?`, type: 'string' },
|
|
3593
|
+
{ match: String.raw `'(?:\\.|[^'\\\r\n])*'?`, type: 'string' },
|
|
3594
|
+
{ match: String.raw `0[xX][\da-fA-F_]+[uUlL]*|0[bB][01_]+[uUlL]*|(?:\d[\d_]*\.?[\d_]*|\.\d[\d_]*)(?:[eE][+-]?\d+)?[fFdDmMuUlL]*`, type: 'number' },
|
|
3595
|
+
{ match: String.raw `@?[A-Za-z_\u00A0-\uFFFF][\w\u00A0-\uFFFF]*`, type: 'identifier' },
|
|
3596
|
+
{ match: String.raw `[()\[\]{}]`, type: 'bracket' },
|
|
3597
|
+
{ match: String.raw `\?\.|::|[;,.]`, type: 'punctuation' },
|
|
3598
|
+
{ match: String.raw `\?\?=|<<=|>>>=|>>=|>>>|=>|==|!=|<=|>=|&&|\|\||\?\?|\+\+|--|->|<<|[-+*/%&|^<>!=]=?|[~?:]`, type: 'operator' },
|
|
3599
|
+
];
|
|
3600
|
+
/**
|
|
3601
|
+
* C# vocabulary: XML doc comments (`doc-comment`), comments, preprocessor directives, strings (regular, verbatim, raw,
|
|
3602
|
+
* interpolated with nested `{…}` expressions), chars, numbers (with suffixes), identifiers (`@verbatim` included),
|
|
3603
|
+
* keywords (contextual ones included, not after `.`/`?.`), brackets, punctuation and operators.
|
|
3604
|
+
*/
|
|
3605
|
+
const CSharpSyntax = {
|
|
3606
|
+
name: 'csharp',
|
|
3607
|
+
aliases: ['cs', 'c#', 'c-sharp', 'dotnet'],
|
|
3608
|
+
lexical: {
|
|
3609
|
+
start: 'root',
|
|
3610
|
+
states: {
|
|
3611
|
+
root: CODE_RULES,
|
|
3612
|
+
interpolated: [
|
|
3613
|
+
{ match: '"', type: 'string', pop: true },
|
|
3614
|
+
{ match: String.raw `\{\{|\}\}`, type: 'string' },
|
|
3615
|
+
{ match: String.raw `\{`, type: 'punctuation', push: 'interpolation' },
|
|
3616
|
+
{ match: String.raw `(?:\\.|[^"\\{}\r\n])+`, type: 'string' },
|
|
3617
|
+
],
|
|
3618
|
+
interpolatedVerbatim: [
|
|
3619
|
+
{ match: '""', type: 'string' },
|
|
3620
|
+
{ match: '"', type: 'string', pop: true },
|
|
3621
|
+
{ match: String.raw `\{\{|\}\}`, type: 'string' },
|
|
3622
|
+
{ match: String.raw `\{`, type: 'punctuation', push: 'interpolation' },
|
|
3623
|
+
{ match: String.raw `[^"{}]+`, type: 'string' },
|
|
3624
|
+
],
|
|
3625
|
+
interpolation: [
|
|
3626
|
+
{ match: String.raw `\}`, type: 'punctuation', pop: true },
|
|
3627
|
+
{ match: String.raw `\{`, type: 'bracket', push: 'braces' },
|
|
3628
|
+
{ include: 'root' },
|
|
3629
|
+
],
|
|
3630
|
+
braces: [
|
|
3631
|
+
{ match: String.raw `\}`, type: 'bracket', pop: true },
|
|
3632
|
+
{ match: String.raw `\{`, type: 'bracket', push: 'braces' },
|
|
3633
|
+
{ include: 'root' },
|
|
3634
|
+
],
|
|
3635
|
+
},
|
|
3636
|
+
keywords: { keyword: CSHARP_KEYWORDS },
|
|
3637
|
+
keywordExceptAfter: ['.', '?.'],
|
|
3638
|
+
}
|
|
3639
|
+
};
|
|
3640
|
+
|
|
3641
|
+
//namespace Pacem.Compile {
|
|
3642
|
+
const C_KEYWORDS = [
|
|
3643
|
+
// C
|
|
3644
|
+
'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do', 'double', 'else', 'enum', 'extern', 'float',
|
|
3645
|
+
'for', 'goto', 'if', 'inline', 'int', 'long', 'register', 'restrict', 'return', 'short', 'signed', 'sizeof',
|
|
3646
|
+
'static', 'struct', 'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile', 'while', 'bool', 'true', 'false',
|
|
3647
|
+
'NULL', '_Bool', '_Complex', '_Imaginary', '_Alignas', '_Alignof', '_Atomic', '_Generic', '_Noreturn',
|
|
3648
|
+
'_Static_assert', '_Thread_local',
|
|
3649
|
+
// C++
|
|
3650
|
+
'alignas', 'alignof', 'and', 'and_eq', 'asm', 'bitand', 'bitor', 'catch', 'char8_t', 'char16_t', 'char32_t',
|
|
3651
|
+
'class', 'co_await', 'co_return', 'co_yield', 'compl', 'concept', 'consteval', 'constexpr', 'constinit',
|
|
3652
|
+
'const_cast', 'decltype', 'delete', 'dynamic_cast', 'explicit', 'export', 'final', 'friend', 'mutable',
|
|
3653
|
+
'namespace', 'new', 'noexcept', 'not', 'not_eq', 'nullptr', 'operator', 'or', 'or_eq', 'override', 'private',
|
|
3654
|
+
'protected', 'public', 'reinterpret_cast', 'requires', 'static_assert', 'static_cast', 'template', 'this',
|
|
3655
|
+
'thread_local', 'throw', 'try', 'typeid', 'typename', 'using', 'virtual', 'wchar_t', 'xor', 'xor_eq'
|
|
3656
|
+
];
|
|
3657
|
+
/**
|
|
3658
|
+
* C/C++ vocabulary: comments (`/** … *\/` and `///` as `doc-comment`), preprocessor directives (with their `<header>`),
|
|
3659
|
+
* strings (raw `R"x(…)x"` and prefixed ones included), chars, numbers (with suffixes/separators), identifiers, keywords
|
|
3660
|
+
* (C and C++), brackets, punctuation and operators.
|
|
3661
|
+
*/
|
|
3662
|
+
const CSyntax = {
|
|
3663
|
+
name: 'c',
|
|
3664
|
+
aliases: ['cpp', 'c++', 'cc', 'cxx', 'h', 'hpp', 'hxx', 'objc'],
|
|
3665
|
+
lexical: {
|
|
3666
|
+
start: 'root',
|
|
3667
|
+
states: {
|
|
3668
|
+
root: [
|
|
3669
|
+
{ match: String.raw `/\*\*(?!/)[\s\S]*?(?:\*/|$)|///[^\r\n]*`, type: 'doc-comment' },
|
|
3670
|
+
{ match: String.raw `/\*[\s\S]*?(?:\*/|$)`, type: 'comment' },
|
|
3671
|
+
{ match: String.raw `//[^\r\n]*`, type: 'comment' },
|
|
3672
|
+
// preprocessor directives, at the beginning of a line (line continuations included)
|
|
3673
|
+
{ match: String.raw `(?<=^[ \t]*|[\r\n][ \t]*)#[ \t]*\w+(?:\\\r?\n|[^\r\n])*`, type: 'preprocessor' },
|
|
3674
|
+
{ match: String.raw `\r\n|[\r\n]`, type: 'newline' },
|
|
3675
|
+
{ match: String.raw `[ \t\f\v]+`, type: 'space' },
|
|
3676
|
+
{ match: String.raw `(?:u8|u|U|L)?R"([^()\\\s]{0,16})\([\s\S]*?\)\1"`, type: 'string' },
|
|
3677
|
+
{ match: String.raw `(?:u8|u|U|L)?"(?:\\[\s\S]|[^"\\\r\n])*"?`, type: 'string' },
|
|
3678
|
+
{ match: String.raw `(?:u8|u|U|L)?'(?:\\.|[^'\\\r\n])*'?`, type: 'string' },
|
|
3679
|
+
{ match: String.raw `0[xX][\da-fA-F']+[uUlL]*|0[bB][01']+[uUlL]*|(?:\d[\d']*\.?[\d']*|\.\d[\d']*)(?:[eEpP][+-]?\d+)?[fFlLuU]*`, type: 'number' },
|
|
3680
|
+
{ match: String.raw `[A-Za-z_][\w]*`, type: 'identifier' },
|
|
3681
|
+
{ match: String.raw `[()\[\]{}]`, type: 'bracket' },
|
|
3682
|
+
{ match: String.raw `::|\.\.\.|->\*?|\.\*?|[;,]`, type: 'punctuation' },
|
|
3683
|
+
{ match: String.raw `<=>|<<=|>>=|<<|>>|\+\+|--|&&|\|\||[-+*/%&|^<>!=]=?|[~?:#]`, type: 'operator' },
|
|
3684
|
+
],
|
|
3685
|
+
},
|
|
3686
|
+
keywords: { keyword: C_KEYWORDS },
|
|
3687
|
+
keywordExceptAfter: ['.', '->', '::'],
|
|
3688
|
+
}
|
|
3689
|
+
};
|
|
3690
|
+
|
|
3691
|
+
//namespace Pacem.Compile {
|
|
3692
|
+
/**
|
|
3693
|
+
* JSON vocabulary (JSONC and JSON5-ish tolerant: comments, single quotes and trailing commas are recognized):
|
|
3694
|
+
* object keys are `attribute`s, values `string`/`number`/`keyword` (`true`, `false`, `null`).
|
|
3695
|
+
*/
|
|
3696
|
+
const JsonSyntax = {
|
|
3697
|
+
name: 'json',
|
|
3698
|
+
aliases: ['jsonc', 'json5', 'webmanifest'],
|
|
3699
|
+
lexical: {
|
|
3700
|
+
start: 'root',
|
|
3701
|
+
states: {
|
|
3702
|
+
root: [
|
|
3703
|
+
{ match: String.raw `/\*[\s\S]*?(?:\*/|$)`, type: 'comment' },
|
|
3704
|
+
{ match: String.raw `//[^\r\n]*`, type: 'comment' },
|
|
3705
|
+
{ match: String.raw `\r\n|[\r\n]`, type: 'newline' },
|
|
3706
|
+
{ match: String.raw `[ \t\f\v ]+`, type: 'space' },
|
|
3707
|
+
// keys: strings followed by a colon
|
|
3708
|
+
{ match: String.raw `(?:"(?:\\.|[^"\\\r\n])*"|'(?:\\.|[^'\\\r\n])*')(?=\s*:)`, type: 'attribute' },
|
|
3709
|
+
{ match: String.raw `"(?:\\.|[^"\\\r\n])*"?|'(?:\\.|[^'\\\r\n])*'?`, type: 'string' },
|
|
3710
|
+
{ match: String.raw `-?(?:0[xX][\da-fA-F]+|(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)|-?Infinity|NaN`, type: 'number' },
|
|
3711
|
+
{ match: String.raw `[A-Za-z_$][\w$]*`, type: 'identifier' },
|
|
3712
|
+
{ match: String.raw `[\[\]{}]`, type: 'bracket' },
|
|
3713
|
+
{ match: String.raw `[:,]`, type: 'punctuation' },
|
|
3714
|
+
],
|
|
3715
|
+
},
|
|
3716
|
+
keywords: { keyword: ['true', 'false', 'null'] },
|
|
3717
|
+
}
|
|
3718
|
+
};
|
|
3719
|
+
|
|
3720
|
+
//namespace Pacem.Compile {
|
|
3721
|
+
/** The built-in vocabularies, one per {@link SyntaxName} (enforced by the type). */
|
|
3722
|
+
const BUILTIN_SYNTAXES = {
|
|
3723
|
+
'javascript': JavaScriptSyntax,
|
|
3724
|
+
'pacem-binding': PacemBindingSyntax,
|
|
3725
|
+
'markup': MarkupSyntax,
|
|
3726
|
+
'csharp': CSharpSyntax,
|
|
3727
|
+
'c': CSyntax,
|
|
3728
|
+
'json': JsonSyntax
|
|
3729
|
+
};
|
|
3730
|
+
// available as soon as the compile module loads
|
|
3731
|
+
for (let name in BUILTIN_SYNTAXES) {
|
|
3732
|
+
const syntax = BUILTIN_SYNTAXES[name];
|
|
3733
|
+
Syntaxes.register(syntax);
|
|
3734
|
+
}
|
|
3735
|
+
|
|
2398
3736
|
var indexCompile = /*#__PURE__*/Object.freeze({
|
|
2399
3737
|
__proto__: null,
|
|
3738
|
+
CSharpSyntax: CSharpSyntax,
|
|
3739
|
+
CSyntax: CSyntax,
|
|
3740
|
+
DefaultHighlightClasses: DefaultHighlightClasses,
|
|
3741
|
+
HighlightRenderer: HighlightRenderer,
|
|
3742
|
+
JavaScriptSyntax: JavaScriptSyntax,
|
|
3743
|
+
JsonSyntax: JsonSyntax,
|
|
2400
3744
|
Markdown: indexCompileMarkdown,
|
|
2401
|
-
|
|
3745
|
+
MarkupSyntax: MarkupSyntax,
|
|
3746
|
+
PacemBindingSyntax: PacemBindingSyntax,
|
|
3747
|
+
Parser: Parser$1,
|
|
3748
|
+
Scanner: Scanner,
|
|
3749
|
+
get SyntaxTokenType () { return SyntaxTokenType; },
|
|
3750
|
+
Syntaxes: Syntaxes
|
|
2402
3751
|
});
|
|
2403
3752
|
|
|
2404
3753
|
var Output = /*#__PURE__*/Object.freeze({
|