@jesscss/scss-parser 2.0.0-alpha.7 → 2.0.0-alpha.8
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/README.md +20 -3
- package/lib/functional-parser.cjs +176 -14
- package/lib/functional-parser.js +176 -14
- package/lib/grammar.cjs +44760 -39981
- package/lib/grammar.js +44760 -39981
- package/package.json +6 -6
- package/src/grammar.ts +41 -11
package/README.md
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
# @jesscss/scss-parser
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An SCSS grammar for Jess, layered on the CSS base parser — **experimental, and not the focus of the Less alpha.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
> **Status: experimental / roadmap.** Part of [Jess](https://github.com/jesscss/jess), which *is* Less.js v5. Only `.less` rendering ships today. SCSS is **not a goal of this phase** — this parser is early and exists to seed the future **"Sass+"** dialect (roadmap milestone #2, after Less.js). Don't rely on it for production; prefer [`@jesscss/css-parser`](https://www.npmjs.com/package/@jesscss/css-parser) / [`@jesscss/less-parser`](https://www.npmjs.com/package/@jesscss/less-parser). [Report bugs](https://github.com/jesscss/jess/issues); docs live at [jesscss.github.io](https://jesscss.github.io/).
|
|
6
|
+
|
|
7
|
+
## What it is
|
|
8
|
+
|
|
9
|
+
The SCSS grammar is the shared CSS grammar plus an SCSS delta:
|
|
10
|
+
`scssGrammar = compose([cssGrammar, <SCSS delta>])`, layered on the spec-aligned
|
|
11
|
+
CSS base in `@jesscss/css-parser` and built on
|
|
12
|
+
[parseman](https://www.npmjs.com/package/parseman) — **the fastest
|
|
13
|
+
general-purpose JavaScript parser** in its
|
|
14
|
+
[published benchmarks](https://matthew-dean.github.io/parseman/guide/benchmarks)
|
|
15
|
+
(see `@jesscss/css-parser` for figures and engineering details).
|
|
16
|
+
|
|
17
|
+
The current goal is **parse coverage**: the surface `$`-variable / SCSS syntax
|
|
18
|
+
should parse into a tree, but not every Sass/SCSS feature is necessarily
|
|
19
|
+
*evaluated*. This is the least-mature of the Jess parsers (the `.jess` parser
|
|
20
|
+
trails it), and the seed for the roadmap "Sass+" dialect rather than a shipped
|
|
21
|
+
Sass replacement. The language roadmap is ordered: **Now Less.js → Next Sass+ →
|
|
22
|
+
Final `.jess`.**
|
|
6
23
|
|
|
7
24
|
Two ways to use it:
|
|
8
25
|
|
|
@@ -129,4 +146,4 @@ type BuildHost = (
|
|
|
129
146
|
|
|
130
147
|
## Part of Jess
|
|
131
148
|
|
|
132
|
-
This package is developed as part of [Jess](https://github.com/jesscss/jess).
|
|
149
|
+
This package is developed as part of [Jess](https://github.com/jesscss/jess), the Less.js v5 rewrite. SCSS is the least-mature Jess dialect and is not the focus of the Less alpha — it seeds the roadmap "Sass+" dialect. For production use, prefer `@jesscss/css-parser` / `@jesscss/less-parser`. Licensed MIT.
|
|
@@ -2,7 +2,7 @@ const require_grammar = require("./grammar.cjs");
|
|
|
2
2
|
let _jesscss_less_parser_jess = require("@jesscss/less-parser/jess");
|
|
3
3
|
let _jesscss_css_parser_jess = require("@jesscss/css-parser/jess");
|
|
4
4
|
let _jesscss_core = require("@jesscss/core");
|
|
5
|
-
//#region
|
|
5
|
+
//#region ../../../parser-thing/dist/index.js
|
|
6
6
|
function union(a, b) {
|
|
7
7
|
if (a.kind === "any" || b.kind === "any") return { kind: "any" };
|
|
8
8
|
if (a.kind === "empty") return b;
|
|
@@ -88,6 +88,48 @@ function sequenceFirstSet(parsers) {
|
|
|
88
88
|
}
|
|
89
89
|
return fs;
|
|
90
90
|
}
|
|
91
|
+
function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
|
|
92
|
+
if (seen.has(p)) return any();
|
|
93
|
+
seen.add(p);
|
|
94
|
+
const fs = (c) => firstSetOf(c, seen);
|
|
95
|
+
const d = p._def;
|
|
96
|
+
switch (d.tag) {
|
|
97
|
+
case "literal":
|
|
98
|
+
case "regex":
|
|
99
|
+
case "keywords": return p._meta.firstSet;
|
|
100
|
+
case "lazy": try {
|
|
101
|
+
return fs(d.thunk());
|
|
102
|
+
} catch {
|
|
103
|
+
return any();
|
|
104
|
+
}
|
|
105
|
+
case "choice": {
|
|
106
|
+
let out = empty();
|
|
107
|
+
for (const arm of d.parsers) out = union(out, fs(arm));
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
case "sequence": {
|
|
111
|
+
let out = empty();
|
|
112
|
+
for (const term of d.parsers) {
|
|
113
|
+
out = union(out, fs(term));
|
|
114
|
+
if (!matchesEmpty(term)) return out;
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
case "oneOrMore":
|
|
119
|
+
case "many":
|
|
120
|
+
case "optional":
|
|
121
|
+
case "transform":
|
|
122
|
+
case "label":
|
|
123
|
+
case "trivia":
|
|
124
|
+
case "token":
|
|
125
|
+
case "node":
|
|
126
|
+
case "grammar":
|
|
127
|
+
case "expect": return fs(d.parser);
|
|
128
|
+
case "sepBy": return fs(d.parser);
|
|
129
|
+
case "skip": return fs(d.main);
|
|
130
|
+
default: return p._meta.firstSet;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
91
133
|
function mergeRanges(ranges) {
|
|
92
134
|
if (ranges.length === 0) return [];
|
|
93
135
|
const sorted = [...ranges].sort((a, b) => a.lo - b.lo);
|
|
@@ -178,7 +220,8 @@ function saveCstMark(ctx) {
|
|
|
178
220
|
raw: cstRawLen(ctx),
|
|
179
221
|
tlog: cstTlLen(ctx),
|
|
180
222
|
leaves: cstLeavesLen(ctx),
|
|
181
|
-
fields: ctx._fields?.length ?? 0
|
|
223
|
+
fields: ctx._fields?.length ?? 0,
|
|
224
|
+
errors: ctx._errors?.length ?? 0
|
|
182
225
|
};
|
|
183
226
|
}
|
|
184
227
|
function rollbackBufList(b, keyMulti, keySingle, len) {
|
|
@@ -194,6 +237,7 @@ function rollbackBufList(b, keyMulti, keySingle, len) {
|
|
|
194
237
|
if (len === 0) b[keySingle] = void 0;
|
|
195
238
|
}
|
|
196
239
|
function rollbackCstCapture(ctx, mark) {
|
|
240
|
+
if (ctx._errors && mark.errors !== void 0) ctx._errors.length = mark.errors;
|
|
197
241
|
const b = ctx._cstBuf;
|
|
198
242
|
if (b) {
|
|
199
243
|
rollbackBufList(b, "raw", "rawSingle", mark.raw);
|
|
@@ -757,11 +801,74 @@ function firstSetFromRegex(source) {
|
|
|
757
801
|
canMatchNewline: firstCanBeNewline(ast)
|
|
758
802
|
};
|
|
759
803
|
}
|
|
804
|
+
function firstSetSentinel(fs) {
|
|
805
|
+
if (fs.kind !== "ranges" || fs.ranges.length === 0) return null;
|
|
806
|
+
const ranges = fs.ranges;
|
|
807
|
+
return {
|
|
808
|
+
_tag: "firstSetSentinel",
|
|
809
|
+
_meta: {
|
|
810
|
+
firstSet: fs,
|
|
811
|
+
canMatchNewline: false,
|
|
812
|
+
isTrivia: false
|
|
813
|
+
},
|
|
814
|
+
_def: { tag: "unknown" },
|
|
815
|
+
parse(input, pos) {
|
|
816
|
+
const code = pos < input.length ? input.codePointAt(pos) : void 0;
|
|
817
|
+
if (code !== void 0) {
|
|
818
|
+
for (const r of ranges) if (code >= r.lo && code <= r.hi) return {
|
|
819
|
+
ok: true,
|
|
820
|
+
value: null,
|
|
821
|
+
span: {
|
|
822
|
+
start: pos,
|
|
823
|
+
end: pos
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
return {
|
|
828
|
+
ok: false,
|
|
829
|
+
expected: [],
|
|
830
|
+
span: {
|
|
831
|
+
start: pos,
|
|
832
|
+
end: pos
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
function deriveExpected(c) {
|
|
839
|
+
const def = c._def;
|
|
840
|
+
switch (def.tag) {
|
|
841
|
+
case "literal": return [JSON.stringify(def.value)];
|
|
842
|
+
case "regex": return [`/${def.source}/`];
|
|
843
|
+
case "keywords": return def.words.map((w) => JSON.stringify(w));
|
|
844
|
+
case "label": return [def.label];
|
|
845
|
+
case "choice": return def.parsers.flatMap(deriveExpected);
|
|
846
|
+
case "sequence": return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
|
|
847
|
+
case "node":
|
|
848
|
+
case "grammar":
|
|
849
|
+
case "trivia":
|
|
850
|
+
case "token":
|
|
851
|
+
case "optional":
|
|
852
|
+
case "many":
|
|
853
|
+
case "oneOrMore":
|
|
854
|
+
case "transform":
|
|
855
|
+
case "not": return deriveExpected(def.parser);
|
|
856
|
+
case "lazy": {
|
|
857
|
+
const name = c._ruleName;
|
|
858
|
+
try {
|
|
859
|
+
return deriveExpected(def.thunk());
|
|
860
|
+
} catch {
|
|
861
|
+
return name ? [name] : [];
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
default: return [];
|
|
865
|
+
}
|
|
866
|
+
}
|
|
760
867
|
function choice(...args) {
|
|
761
868
|
const parsers = args.map((a) => "gate" in a ? a.combinator : a);
|
|
762
869
|
const gates = args.map((a) => "gate" in a ? a.gate : null);
|
|
763
870
|
const hasGates = gates.some((g) => g !== null);
|
|
764
|
-
const disjoint =
|
|
871
|
+
const disjoint = areDisjoint(parsers.map((p) => p._meta.firstSet)) && parsers.every((p) => !matchesEmpty(p));
|
|
765
872
|
let combined = { kind: "empty" };
|
|
766
873
|
for (const p of parsers) combined = union(combined, p._meta.firstSet);
|
|
767
874
|
const meta = {
|
|
@@ -798,15 +905,24 @@ function choice(...args) {
|
|
|
798
905
|
const expected = [];
|
|
799
906
|
if (disjoint && pos < input.length) {
|
|
800
907
|
const code = input.codePointAt(pos);
|
|
801
|
-
let
|
|
802
|
-
if (
|
|
803
|
-
for (
|
|
804
|
-
|
|
908
|
+
let idx = code < 128 ? asciiDispatch[code] : -1;
|
|
909
|
+
if (idx < 0) {
|
|
910
|
+
for (let i = 0; i < parsers.length; i++) if (inFirstSet(code, parsers[i]._meta.firstSet)) {
|
|
911
|
+
idx = i;
|
|
805
912
|
break;
|
|
806
913
|
}
|
|
807
914
|
}
|
|
808
|
-
if (
|
|
809
|
-
const
|
|
915
|
+
if (idx >= 0) {
|
|
916
|
+
const gate = gates[idx];
|
|
917
|
+
if (gate && !gate(ctx.state)) return {
|
|
918
|
+
ok: false,
|
|
919
|
+
expected: deriveExpected(parsers[idx]),
|
|
920
|
+
span: {
|
|
921
|
+
start: pos,
|
|
922
|
+
end: pos
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
const result = parsers[idx].parse(input, pos, ctx);
|
|
810
926
|
if (result.ok) return result;
|
|
811
927
|
expected.push(...result.expected);
|
|
812
928
|
return {
|
|
@@ -1026,11 +1142,11 @@ function areDisjoint(sets) {
|
|
|
1026
1142
|
return true;
|
|
1027
1143
|
}
|
|
1028
1144
|
function buildAsciiDispatch(parsers) {
|
|
1029
|
-
const table = Array(128).fill(
|
|
1030
|
-
for (
|
|
1031
|
-
const fs =
|
|
1145
|
+
const table = Array(128).fill(-1);
|
|
1146
|
+
for (let i = 0; i < parsers.length; i++) {
|
|
1147
|
+
const fs = parsers[i]._meta.firstSet;
|
|
1032
1148
|
if (fs.kind !== "ranges") continue;
|
|
1033
|
-
for (const { lo, hi } of fs.ranges) for (let code = Math.max(0, lo); code <= Math.min(127, hi); code++) table[code] =
|
|
1149
|
+
for (const { lo, hi } of fs.ranges) for (let code = Math.max(0, lo); code <= Math.min(127, hi); code++) table[code] = i;
|
|
1034
1150
|
}
|
|
1035
1151
|
return table;
|
|
1036
1152
|
}
|
|
@@ -1201,6 +1317,7 @@ function saveTriviaMark(ctx) {
|
|
|
1201
1317
|
tlog: m.tlog,
|
|
1202
1318
|
leaves: m.leaves,
|
|
1203
1319
|
fields: m.fields,
|
|
1320
|
+
errors: m.errors,
|
|
1204
1321
|
log: ctx._triviaLog ? ctx._triviaLog.length : 0
|
|
1205
1322
|
};
|
|
1206
1323
|
}
|
|
@@ -1209,7 +1326,8 @@ function rollbackTrivia(ctx, mark) {
|
|
|
1209
1326
|
raw: mark.raw,
|
|
1210
1327
|
tlog: mark.tlog,
|
|
1211
1328
|
leaves: mark.leaves,
|
|
1212
|
-
fields: mark.fields
|
|
1329
|
+
fields: mark.fields,
|
|
1330
|
+
errors: mark.errors
|
|
1213
1331
|
});
|
|
1214
1332
|
if (ctx._triviaLog) ctx._triviaLog.length = mark.log;
|
|
1215
1333
|
}
|
|
@@ -1474,11 +1592,55 @@ function sequence(...parsers) {
|
|
|
1474
1592
|
tag: "sequence",
|
|
1475
1593
|
parsers
|
|
1476
1594
|
};
|
|
1595
|
+
let followSentinels;
|
|
1596
|
+
function parseTolerant(input, pos, ctx) {
|
|
1597
|
+
followSentinels ??= parsers.map((_, i) => {
|
|
1598
|
+
return firstSetSentinel(parsers.slice(i + 1).reduce((acc, p) => union(acc, firstSetOf(p)), { kind: "empty" }));
|
|
1599
|
+
});
|
|
1600
|
+
const values = def.valueUnused ? void 0 : [];
|
|
1601
|
+
let cur = pos;
|
|
1602
|
+
const inheritedSync = ctx._sync;
|
|
1603
|
+
try {
|
|
1604
|
+
for (let i = 0; i < parsers.length; i++) {
|
|
1605
|
+
ctx._sync = followSentinels[i] ?? inheritedSync;
|
|
1606
|
+
if (ctx.trivia && i > 0) {
|
|
1607
|
+
const mark = saveTriviaMark(ctx);
|
|
1608
|
+
let scanEnd;
|
|
1609
|
+
if (needsDeferredTriviaCommit(ctx)) {
|
|
1610
|
+
const scan = scanTrivia(input, cur, ctx);
|
|
1611
|
+
scan.commit();
|
|
1612
|
+
scanEnd = scan.end;
|
|
1613
|
+
} else scanEnd = advanceTrivia(input, cur, ctx);
|
|
1614
|
+
const result2 = parsers[i].parse(input, scanEnd, ctx);
|
|
1615
|
+
if (!result2.ok) return result2;
|
|
1616
|
+
if (result2.span.end > scanEnd) cur = result2.span.end;
|
|
1617
|
+
else rollbackTrivia(ctx, mark);
|
|
1618
|
+
if (values !== void 0) values.push(result2.value);
|
|
1619
|
+
continue;
|
|
1620
|
+
}
|
|
1621
|
+
const result = parsers[i].parse(input, cur, ctx);
|
|
1622
|
+
if (!result.ok) return result;
|
|
1623
|
+
if (values !== void 0) values.push(result.value);
|
|
1624
|
+
cur = result.span.end;
|
|
1625
|
+
}
|
|
1626
|
+
} finally {
|
|
1627
|
+
ctx._sync = inheritedSync;
|
|
1628
|
+
}
|
|
1629
|
+
return {
|
|
1630
|
+
ok: true,
|
|
1631
|
+
value: values ?? void 0,
|
|
1632
|
+
span: {
|
|
1633
|
+
start: pos,
|
|
1634
|
+
end: cur
|
|
1635
|
+
}
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1477
1638
|
return {
|
|
1478
1639
|
_tag: "sequence",
|
|
1479
1640
|
_meta: meta,
|
|
1480
1641
|
_def: def,
|
|
1481
1642
|
parse(input, pos, ctx) {
|
|
1643
|
+
if (ctx._tolerant) return parseTolerant(input, pos, ctx);
|
|
1482
1644
|
const values = def.valueUnused ? void 0 : [];
|
|
1483
1645
|
let cur = pos;
|
|
1484
1646
|
for (let i = 0; i < parsers.length; i++) {
|
package/lib/functional-parser.js
CHANGED
|
@@ -2,7 +2,7 @@ import { scssGrammar } from "./grammar.js";
|
|
|
2
2
|
import { LessGrammar } from "@jesscss/less-parser/jess";
|
|
3
3
|
import { runFunctionalParse, spannedComponents } from "@jesscss/css-parser/jess";
|
|
4
4
|
import { Ampersand, Any, AtRule, AtRuleStatement, Call, Collection, ComplexSelector, Condition, Declaration, Expression, Extend, ExtendFlag, F_VISIBLE, For, Func, INTERPOLATION_PLACEHOLDER, If, Interpolated, InterpolatedSelector, JsImport, List, Log, Mixin, N, Nil, Node, Operation, Paren, Quoted, Reference, Rest, Rules, Ruleset, SelectorCapture, SelectorList, Sequence, StyleImport, Url, VarDeclaration, While, isNode, sourceSpanOf } from "@jesscss/core";
|
|
5
|
-
//#region
|
|
5
|
+
//#region ../../../parser-thing/dist/index.js
|
|
6
6
|
function union(a, b) {
|
|
7
7
|
if (a.kind === "any" || b.kind === "any") return { kind: "any" };
|
|
8
8
|
if (a.kind === "empty") return b;
|
|
@@ -88,6 +88,48 @@ function sequenceFirstSet(parsers) {
|
|
|
88
88
|
}
|
|
89
89
|
return fs;
|
|
90
90
|
}
|
|
91
|
+
function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
|
|
92
|
+
if (seen.has(p)) return any();
|
|
93
|
+
seen.add(p);
|
|
94
|
+
const fs = (c) => firstSetOf(c, seen);
|
|
95
|
+
const d = p._def;
|
|
96
|
+
switch (d.tag) {
|
|
97
|
+
case "literal":
|
|
98
|
+
case "regex":
|
|
99
|
+
case "keywords": return p._meta.firstSet;
|
|
100
|
+
case "lazy": try {
|
|
101
|
+
return fs(d.thunk());
|
|
102
|
+
} catch {
|
|
103
|
+
return any();
|
|
104
|
+
}
|
|
105
|
+
case "choice": {
|
|
106
|
+
let out = empty();
|
|
107
|
+
for (const arm of d.parsers) out = union(out, fs(arm));
|
|
108
|
+
return out;
|
|
109
|
+
}
|
|
110
|
+
case "sequence": {
|
|
111
|
+
let out = empty();
|
|
112
|
+
for (const term of d.parsers) {
|
|
113
|
+
out = union(out, fs(term));
|
|
114
|
+
if (!matchesEmpty(term)) return out;
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
case "oneOrMore":
|
|
119
|
+
case "many":
|
|
120
|
+
case "optional":
|
|
121
|
+
case "transform":
|
|
122
|
+
case "label":
|
|
123
|
+
case "trivia":
|
|
124
|
+
case "token":
|
|
125
|
+
case "node":
|
|
126
|
+
case "grammar":
|
|
127
|
+
case "expect": return fs(d.parser);
|
|
128
|
+
case "sepBy": return fs(d.parser);
|
|
129
|
+
case "skip": return fs(d.main);
|
|
130
|
+
default: return p._meta.firstSet;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
91
133
|
function mergeRanges(ranges) {
|
|
92
134
|
if (ranges.length === 0) return [];
|
|
93
135
|
const sorted = [...ranges].sort((a, b) => a.lo - b.lo);
|
|
@@ -178,7 +220,8 @@ function saveCstMark(ctx) {
|
|
|
178
220
|
raw: cstRawLen(ctx),
|
|
179
221
|
tlog: cstTlLen(ctx),
|
|
180
222
|
leaves: cstLeavesLen(ctx),
|
|
181
|
-
fields: ctx._fields?.length ?? 0
|
|
223
|
+
fields: ctx._fields?.length ?? 0,
|
|
224
|
+
errors: ctx._errors?.length ?? 0
|
|
182
225
|
};
|
|
183
226
|
}
|
|
184
227
|
function rollbackBufList(b, keyMulti, keySingle, len) {
|
|
@@ -194,6 +237,7 @@ function rollbackBufList(b, keyMulti, keySingle, len) {
|
|
|
194
237
|
if (len === 0) b[keySingle] = void 0;
|
|
195
238
|
}
|
|
196
239
|
function rollbackCstCapture(ctx, mark) {
|
|
240
|
+
if (ctx._errors && mark.errors !== void 0) ctx._errors.length = mark.errors;
|
|
197
241
|
const b = ctx._cstBuf;
|
|
198
242
|
if (b) {
|
|
199
243
|
rollbackBufList(b, "raw", "rawSingle", mark.raw);
|
|
@@ -757,11 +801,74 @@ function firstSetFromRegex(source) {
|
|
|
757
801
|
canMatchNewline: firstCanBeNewline(ast)
|
|
758
802
|
};
|
|
759
803
|
}
|
|
804
|
+
function firstSetSentinel(fs) {
|
|
805
|
+
if (fs.kind !== "ranges" || fs.ranges.length === 0) return null;
|
|
806
|
+
const ranges = fs.ranges;
|
|
807
|
+
return {
|
|
808
|
+
_tag: "firstSetSentinel",
|
|
809
|
+
_meta: {
|
|
810
|
+
firstSet: fs,
|
|
811
|
+
canMatchNewline: false,
|
|
812
|
+
isTrivia: false
|
|
813
|
+
},
|
|
814
|
+
_def: { tag: "unknown" },
|
|
815
|
+
parse(input, pos) {
|
|
816
|
+
const code = pos < input.length ? input.codePointAt(pos) : void 0;
|
|
817
|
+
if (code !== void 0) {
|
|
818
|
+
for (const r of ranges) if (code >= r.lo && code <= r.hi) return {
|
|
819
|
+
ok: true,
|
|
820
|
+
value: null,
|
|
821
|
+
span: {
|
|
822
|
+
start: pos,
|
|
823
|
+
end: pos
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
return {
|
|
828
|
+
ok: false,
|
|
829
|
+
expected: [],
|
|
830
|
+
span: {
|
|
831
|
+
start: pos,
|
|
832
|
+
end: pos
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
function deriveExpected(c) {
|
|
839
|
+
const def = c._def;
|
|
840
|
+
switch (def.tag) {
|
|
841
|
+
case "literal": return [JSON.stringify(def.value)];
|
|
842
|
+
case "regex": return [`/${def.source}/`];
|
|
843
|
+
case "keywords": return def.words.map((w) => JSON.stringify(w));
|
|
844
|
+
case "label": return [def.label];
|
|
845
|
+
case "choice": return def.parsers.flatMap(deriveExpected);
|
|
846
|
+
case "sequence": return def.parsers.length > 0 ? deriveExpected(def.parsers[0]) : [];
|
|
847
|
+
case "node":
|
|
848
|
+
case "grammar":
|
|
849
|
+
case "trivia":
|
|
850
|
+
case "token":
|
|
851
|
+
case "optional":
|
|
852
|
+
case "many":
|
|
853
|
+
case "oneOrMore":
|
|
854
|
+
case "transform":
|
|
855
|
+
case "not": return deriveExpected(def.parser);
|
|
856
|
+
case "lazy": {
|
|
857
|
+
const name = c._ruleName;
|
|
858
|
+
try {
|
|
859
|
+
return deriveExpected(def.thunk());
|
|
860
|
+
} catch {
|
|
861
|
+
return name ? [name] : [];
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
default: return [];
|
|
865
|
+
}
|
|
866
|
+
}
|
|
760
867
|
function choice(...args) {
|
|
761
868
|
const parsers = args.map((a) => "gate" in a ? a.combinator : a);
|
|
762
869
|
const gates = args.map((a) => "gate" in a ? a.gate : null);
|
|
763
870
|
const hasGates = gates.some((g) => g !== null);
|
|
764
|
-
const disjoint =
|
|
871
|
+
const disjoint = areDisjoint(parsers.map((p) => p._meta.firstSet)) && parsers.every((p) => !matchesEmpty(p));
|
|
765
872
|
let combined = { kind: "empty" };
|
|
766
873
|
for (const p of parsers) combined = union(combined, p._meta.firstSet);
|
|
767
874
|
const meta = {
|
|
@@ -798,15 +905,24 @@ function choice(...args) {
|
|
|
798
905
|
const expected = [];
|
|
799
906
|
if (disjoint && pos < input.length) {
|
|
800
907
|
const code = input.codePointAt(pos);
|
|
801
|
-
let
|
|
802
|
-
if (
|
|
803
|
-
for (
|
|
804
|
-
|
|
908
|
+
let idx = code < 128 ? asciiDispatch[code] : -1;
|
|
909
|
+
if (idx < 0) {
|
|
910
|
+
for (let i = 0; i < parsers.length; i++) if (inFirstSet(code, parsers[i]._meta.firstSet)) {
|
|
911
|
+
idx = i;
|
|
805
912
|
break;
|
|
806
913
|
}
|
|
807
914
|
}
|
|
808
|
-
if (
|
|
809
|
-
const
|
|
915
|
+
if (idx >= 0) {
|
|
916
|
+
const gate = gates[idx];
|
|
917
|
+
if (gate && !gate(ctx.state)) return {
|
|
918
|
+
ok: false,
|
|
919
|
+
expected: deriveExpected(parsers[idx]),
|
|
920
|
+
span: {
|
|
921
|
+
start: pos,
|
|
922
|
+
end: pos
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
const result = parsers[idx].parse(input, pos, ctx);
|
|
810
926
|
if (result.ok) return result;
|
|
811
927
|
expected.push(...result.expected);
|
|
812
928
|
return {
|
|
@@ -1026,11 +1142,11 @@ function areDisjoint(sets) {
|
|
|
1026
1142
|
return true;
|
|
1027
1143
|
}
|
|
1028
1144
|
function buildAsciiDispatch(parsers) {
|
|
1029
|
-
const table = Array(128).fill(
|
|
1030
|
-
for (
|
|
1031
|
-
const fs =
|
|
1145
|
+
const table = Array(128).fill(-1);
|
|
1146
|
+
for (let i = 0; i < parsers.length; i++) {
|
|
1147
|
+
const fs = parsers[i]._meta.firstSet;
|
|
1032
1148
|
if (fs.kind !== "ranges") continue;
|
|
1033
|
-
for (const { lo, hi } of fs.ranges) for (let code = Math.max(0, lo); code <= Math.min(127, hi); code++) table[code] =
|
|
1149
|
+
for (const { lo, hi } of fs.ranges) for (let code = Math.max(0, lo); code <= Math.min(127, hi); code++) table[code] = i;
|
|
1034
1150
|
}
|
|
1035
1151
|
return table;
|
|
1036
1152
|
}
|
|
@@ -1201,6 +1317,7 @@ function saveTriviaMark(ctx) {
|
|
|
1201
1317
|
tlog: m.tlog,
|
|
1202
1318
|
leaves: m.leaves,
|
|
1203
1319
|
fields: m.fields,
|
|
1320
|
+
errors: m.errors,
|
|
1204
1321
|
log: ctx._triviaLog ? ctx._triviaLog.length : 0
|
|
1205
1322
|
};
|
|
1206
1323
|
}
|
|
@@ -1209,7 +1326,8 @@ function rollbackTrivia(ctx, mark) {
|
|
|
1209
1326
|
raw: mark.raw,
|
|
1210
1327
|
tlog: mark.tlog,
|
|
1211
1328
|
leaves: mark.leaves,
|
|
1212
|
-
fields: mark.fields
|
|
1329
|
+
fields: mark.fields,
|
|
1330
|
+
errors: mark.errors
|
|
1213
1331
|
});
|
|
1214
1332
|
if (ctx._triviaLog) ctx._triviaLog.length = mark.log;
|
|
1215
1333
|
}
|
|
@@ -1474,11 +1592,55 @@ function sequence(...parsers) {
|
|
|
1474
1592
|
tag: "sequence",
|
|
1475
1593
|
parsers
|
|
1476
1594
|
};
|
|
1595
|
+
let followSentinels;
|
|
1596
|
+
function parseTolerant(input, pos, ctx) {
|
|
1597
|
+
followSentinels ??= parsers.map((_, i) => {
|
|
1598
|
+
return firstSetSentinel(parsers.slice(i + 1).reduce((acc, p) => union(acc, firstSetOf(p)), { kind: "empty" }));
|
|
1599
|
+
});
|
|
1600
|
+
const values = def.valueUnused ? void 0 : [];
|
|
1601
|
+
let cur = pos;
|
|
1602
|
+
const inheritedSync = ctx._sync;
|
|
1603
|
+
try {
|
|
1604
|
+
for (let i = 0; i < parsers.length; i++) {
|
|
1605
|
+
ctx._sync = followSentinels[i] ?? inheritedSync;
|
|
1606
|
+
if (ctx.trivia && i > 0) {
|
|
1607
|
+
const mark = saveTriviaMark(ctx);
|
|
1608
|
+
let scanEnd;
|
|
1609
|
+
if (needsDeferredTriviaCommit(ctx)) {
|
|
1610
|
+
const scan = scanTrivia(input, cur, ctx);
|
|
1611
|
+
scan.commit();
|
|
1612
|
+
scanEnd = scan.end;
|
|
1613
|
+
} else scanEnd = advanceTrivia(input, cur, ctx);
|
|
1614
|
+
const result2 = parsers[i].parse(input, scanEnd, ctx);
|
|
1615
|
+
if (!result2.ok) return result2;
|
|
1616
|
+
if (result2.span.end > scanEnd) cur = result2.span.end;
|
|
1617
|
+
else rollbackTrivia(ctx, mark);
|
|
1618
|
+
if (values !== void 0) values.push(result2.value);
|
|
1619
|
+
continue;
|
|
1620
|
+
}
|
|
1621
|
+
const result = parsers[i].parse(input, cur, ctx);
|
|
1622
|
+
if (!result.ok) return result;
|
|
1623
|
+
if (values !== void 0) values.push(result.value);
|
|
1624
|
+
cur = result.span.end;
|
|
1625
|
+
}
|
|
1626
|
+
} finally {
|
|
1627
|
+
ctx._sync = inheritedSync;
|
|
1628
|
+
}
|
|
1629
|
+
return {
|
|
1630
|
+
ok: true,
|
|
1631
|
+
value: values ?? void 0,
|
|
1632
|
+
span: {
|
|
1633
|
+
start: pos,
|
|
1634
|
+
end: cur
|
|
1635
|
+
}
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1477
1638
|
return {
|
|
1478
1639
|
_tag: "sequence",
|
|
1479
1640
|
_meta: meta,
|
|
1480
1641
|
_def: def,
|
|
1481
1642
|
parse(input, pos, ctx) {
|
|
1643
|
+
if (ctx._tolerant) return parseTolerant(input, pos, ctx);
|
|
1482
1644
|
const values = def.valueUnused ? void 0 : [];
|
|
1483
1645
|
let cur = pos;
|
|
1484
1646
|
for (let i = 0; i < parsers.length; i++) {
|