@pacem/pacem-foundation 1.0.6-ricci → 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.
- package/dist/browser/pacem-foundation.js +1429 -87
- 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 +1630 -95
- package/dist/bundle/pacem-foundation.mjs.map +4 -4
- package/dist/docs/pacem-foundation.json +2763 -694
- 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 +1 -1
- 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 +256 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* @pacem/pacem-foundation
|
|
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 = { '&': '&', '<': '<', '>': '>', '"': '"' };
|
|
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, '&').replace(/"/g, '"').replace(/</g, '<');
|
|
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
|
+
* `<` is turned back into `<` (the highlighter encodes everything it renders anyway).
|
|
2462
|
+
*/
|
|
2463
|
+
function decodeCode(code) {
|
|
2464
|
+
return (code ?? '').replace(/</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,}
|
|
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
|
-
|
|
1833
|
-
const
|
|
1834
|
-
const
|
|
1835
|
-
|
|
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
|
-
|
|
2514
|
+
// (an escaped `\[` never opens a link)
|
|
2515
|
+
const IMAGE_OR_LINK = /(?<!\\)!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?(\[[^\[\]]*\]|\([^\(\)]*\))?/;
|
|
1838
2516
|
const EMPHASIZED_LINK = /(\*\*?|__?)(\[[^\]]*]\([^\)]+\))\1/;
|
|
1839
2517
|
const CODE = /(?:(?<!\\)`)(.+?)(?:(?<!\\)`)/;
|
|
1840
|
-
|
|
1841
|
-
const
|
|
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
|
-
|
|
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); `\<` is an escaped `<` already escaped by `MarkdownService`. */
|
|
2529
|
+
const ESCAPE = /\\(<|[!"#$%&'()*+,\-./:;<=>?@\[\\\]^_`{|}~])/;
|
|
2530
|
+
/** Escaped characters that must stay text once rendered as html. */
|
|
2531
|
+
const ESCAPED_HTML = { '<': '<', '<': '<', '&': '&' };
|
|
2532
|
+
/** `<https://…>`, `<mailto:…>` and `<user@domain>` (the `<` may come escaped, e.g. by `MarkdownService`). */
|
|
2533
|
+
const AUTOLINK = /(?:<|<)((?: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
|
-
|
|
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],
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
2127
|
-
[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
|
|
2131
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}>
|
|
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],
|
|
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
|
|
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],
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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">↩</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
|
-
|
|
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
|
-
|
|
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
|
|
3305
|
+
return this._renderListItem(token);
|
|
2312
3306
|
case MARKDOWN_TYPES.Link:
|
|
2313
3307
|
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)}]`;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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({
|