@tabnas/parser 0.2.0
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/LICENSE +20 -0
- package/README.md +75 -0
- package/dist/builtins.d.ts +4 -0
- package/dist/builtins.js +202 -0
- package/dist/builtins.js.map +1 -0
- package/dist/context.d.ts +64 -0
- package/dist/context.js +138 -0
- package/dist/context.js.map +1 -0
- package/dist/defaults.d.ts +3 -0
- package/dist/defaults.js +301 -0
- package/dist/defaults.js.map +1 -0
- package/dist/error.d.ts +51 -0
- package/dist/error.js +362 -0
- package/dist/error.js.map +1 -0
- package/dist/lexer.d.ts +85 -0
- package/dist/lexer.js +1202 -0
- package/dist/lexer.js.map +1 -0
- package/dist/parser.d.ts +15 -0
- package/dist/parser.js +141 -0
- package/dist/parser.js.map +1 -0
- package/dist/rules.d.ts +100 -0
- package/dist/rules.js +923 -0
- package/dist/rules.js.map +1 -0
- package/dist/tabnas.d.ts +103 -0
- package/dist/tabnas.js +387 -0
- package/dist/tabnas.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types.d.ts +527 -0
- package/dist/types.js +24 -0
- package/dist/types.js.map +1 -0
- package/dist/utility.d.ts +80 -0
- package/dist/utility.js +868 -0
- package/dist/utility.js.map +1 -0
- package/package.json +59 -0
package/dist/rules.js
ADDED
|
@@ -0,0 +1,923 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* Copyright (c) 2013-2026 Richard Rodger, MIT License */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.makeRuleSpec = exports.makeNoRule = exports.makeRule = exports.AltMatch = exports.RuleSpec = exports.Rule = void 0;
|
|
5
|
+
const types_1 = require("./types");
|
|
6
|
+
const utility_1 = require("./utility");
|
|
7
|
+
const error_1 = require("./error");
|
|
8
|
+
// A single application of a RuleSpec on the parser stack; lives through an "open" then "close" pass.
|
|
9
|
+
class Rule {
|
|
10
|
+
constructor(spec, ctx, node) {
|
|
11
|
+
this.i = -1; // Unique rule id within this parse run.
|
|
12
|
+
this.name = types_1.EMPTY; // Rule name (matches its RuleSpec).
|
|
13
|
+
this.node = null; // Value node this rule is building.
|
|
14
|
+
this.state = types_1.OPEN; // Current phase: open ('o') or close ('c').
|
|
15
|
+
this.n = Object.create(null); // Named counters tracked across the rule.
|
|
16
|
+
this.d = -1; // Stack depth at which this rule was pushed.
|
|
17
|
+
this.u = Object.create(null); // Custom user props (not propagated to children).
|
|
18
|
+
this.k = Object.create(null); // Custom keep props (propagated via push/replace).
|
|
19
|
+
this.bo = false; // Has before-open actions.
|
|
20
|
+
this.ao = false; // Has after-open actions.
|
|
21
|
+
this.bc = false; // Has before-close actions.
|
|
22
|
+
this.ac = false; // Has after-close actions.
|
|
23
|
+
this.oN = 0; // Count of tokens matched in the open phase.
|
|
24
|
+
this.cN = 0; // Count of tokens matched in the close phase.
|
|
25
|
+
this.need = 0; // Reserved counter for grammar/plugin use.
|
|
26
|
+
this.i = ctx.uI++; // Rule ids are unique only to the parse run.
|
|
27
|
+
this.name = spec.name;
|
|
28
|
+
this.spec = spec;
|
|
29
|
+
this.child = ctx.NORULE;
|
|
30
|
+
this.parent = ctx.NORULE;
|
|
31
|
+
this.prev = ctx.NORULE;
|
|
32
|
+
this.next = ctx.NORULE;
|
|
33
|
+
this._NOTOKEN = ctx.NOTOKEN;
|
|
34
|
+
this.o = [];
|
|
35
|
+
this.c = [];
|
|
36
|
+
this.node = node;
|
|
37
|
+
this.d = ctx.rsI;
|
|
38
|
+
this.bo = null != spec.def.bo;
|
|
39
|
+
this.ao = null != spec.def.ao;
|
|
40
|
+
this.bc = null != spec.def.bc;
|
|
41
|
+
this.ac = null != spec.def.ac;
|
|
42
|
+
}
|
|
43
|
+
// Legacy aliases for o[0], o[1], c[0], c[1] and the count fields.
|
|
44
|
+
// Maintained so existing grammar/plugin code that reads r.o0/r.o1/r.os
|
|
45
|
+
// (and r.c0/r.c1/r.cs) continues to work unchanged.
|
|
46
|
+
get o0() { return this.o[0] ?? this._NOTOKEN; }
|
|
47
|
+
set o0(v) { this.o[0] = v; }
|
|
48
|
+
get o1() { return this.o[1] ?? this._NOTOKEN; }
|
|
49
|
+
set o1(v) { this.o[1] = v; }
|
|
50
|
+
get c0() { return this.c[0] ?? this._NOTOKEN; }
|
|
51
|
+
set c0(v) { this.c[0] = v; }
|
|
52
|
+
get c1() { return this.c[1] ?? this._NOTOKEN; }
|
|
53
|
+
set c1(v) { this.c[1] = v; }
|
|
54
|
+
get os() { return this.oN; }
|
|
55
|
+
set os(v) { this.oN = v; }
|
|
56
|
+
get cs() { return this.cN; }
|
|
57
|
+
set cs(v) { this.cN = v; }
|
|
58
|
+
process(ctx, lex) {
|
|
59
|
+
let rule = this.spec.process(this, ctx, lex, this.state);
|
|
60
|
+
return rule;
|
|
61
|
+
}
|
|
62
|
+
eq(counter, limit = 0) {
|
|
63
|
+
let value = this.n[counter];
|
|
64
|
+
return null == value || value === limit;
|
|
65
|
+
}
|
|
66
|
+
lt(counter, limit = 0) {
|
|
67
|
+
let value = this.n[counter];
|
|
68
|
+
return null == value || value < limit;
|
|
69
|
+
}
|
|
70
|
+
gt(counter, limit = 0) {
|
|
71
|
+
let value = this.n[counter];
|
|
72
|
+
return null == value || value > limit;
|
|
73
|
+
}
|
|
74
|
+
lte(counter, limit = 0) {
|
|
75
|
+
let value = this.n[counter];
|
|
76
|
+
return null == value || value <= limit;
|
|
77
|
+
}
|
|
78
|
+
gte(counter, limit = 0) {
|
|
79
|
+
let value = this.n[counter];
|
|
80
|
+
return null == value || value >= limit;
|
|
81
|
+
}
|
|
82
|
+
toString() {
|
|
83
|
+
return '[Rule ' + this.name + '~' + this.i + ']';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.Rule = Rule;
|
|
87
|
+
const makeRule = (...params) => new Rule(...params);
|
|
88
|
+
exports.makeRule = makeRule;
|
|
89
|
+
const makeNoRule = (j, ctx) => makeRule(makeRuleSpec(j, ctx.cfg, {}), ctx);
|
|
90
|
+
exports.makeNoRule = makeNoRule;
|
|
91
|
+
// Result of matching one parse alternate against the current tokens (built from current tokens and AltSpec).
|
|
92
|
+
class AltMatch {
|
|
93
|
+
constructor() {
|
|
94
|
+
this.p = types_1.EMPTY; // Push rule (by name).
|
|
95
|
+
this.r = types_1.EMPTY; // Replace rule (by name).
|
|
96
|
+
this.b = 0; // Backtrack: move token position backward.
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
exports.AltMatch = AltMatch;
|
|
100
|
+
const makeAltMatch = (...params) => new AltMatch(...params);
|
|
101
|
+
const PALT = makeAltMatch(); // Only one alt object is created.
|
|
102
|
+
const EMPTY_ALT = makeAltMatch();
|
|
103
|
+
// Reusable definition of a rule: its open/close alternates, lifecycle actions, and collated token lookups.
|
|
104
|
+
class RuleSpec {
|
|
105
|
+
constructor(j, cfg, def) {
|
|
106
|
+
this.name = types_1.EMPTY; // Rule name (set by Parser.rule).
|
|
107
|
+
this.def = {
|
|
108
|
+
open: [], // Open-phase alternates.
|
|
109
|
+
close: [], // Close-phase alternates.
|
|
110
|
+
bo: [], // Before-open actions.
|
|
111
|
+
bc: [], // Before-close actions.
|
|
112
|
+
ao: [], // After-open actions.
|
|
113
|
+
ac: [], // After-close actions.
|
|
114
|
+
tcol: [], // Collated lookahead tins: [stateI][tokenI][tins].
|
|
115
|
+
fnref: {}, // Named function references (@name handlers).
|
|
116
|
+
};
|
|
117
|
+
this.ji = j;
|
|
118
|
+
this.cfg = cfg;
|
|
119
|
+
this.def = Object.assign(this.def, def);
|
|
120
|
+
// Null Alt entries are allowed and ignored as a convenience.
|
|
121
|
+
this.def.open = (this.def.open || []).filter((alt) => null != alt);
|
|
122
|
+
this.def.close = (this.def.close || []).filter((alt) => null != alt);
|
|
123
|
+
for (let alt of this.def.open) {
|
|
124
|
+
normalt(alt, types_1.OPEN, this);
|
|
125
|
+
}
|
|
126
|
+
for (let alt of this.def.close) {
|
|
127
|
+
normalt(alt, types_1.CLOSE, this);
|
|
128
|
+
}
|
|
129
|
+
const anames = ['bo', 'ao', 'bc', 'ac'];
|
|
130
|
+
for (let an of anames) {
|
|
131
|
+
for (let sa of (this.def[an] ?? [])) {
|
|
132
|
+
if ('object' === typeof sa) {
|
|
133
|
+
let sadef = sa;
|
|
134
|
+
this[an](sadef.append, sadef.action);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Convenience access to token Tins
|
|
140
|
+
tin(ref) {
|
|
141
|
+
return (0, utility_1.tokenize)(ref, this.cfg);
|
|
142
|
+
}
|
|
143
|
+
fnref(frm) {
|
|
144
|
+
Object.assign(this.def.fnref, frm);
|
|
145
|
+
// Auto-install reserved `@<rulename>-<phase>` handlers as state
|
|
146
|
+
// actions. Dedupe by function identity per phase: registering the
|
|
147
|
+
// same function twice (directly or via a later fnref() call that
|
|
148
|
+
// also passes it) installs only one action, but distinct functions
|
|
149
|
+
// for the same phase all install. This accommodates grammars that
|
|
150
|
+
// layer handlers (e.g. core sets a basic @list-bo, then a richer
|
|
151
|
+
// one replaces/augments it) while preventing the old bug where
|
|
152
|
+
// iterating the accumulated fnref map re-installed every
|
|
153
|
+
// previously-registered handler on every call.
|
|
154
|
+
const rn = this.name;
|
|
155
|
+
const fr = this.def.fnref;
|
|
156
|
+
const installed = (this.def.fnrefInstalled =
|
|
157
|
+
this.def.fnrefInstalled || new Map());
|
|
158
|
+
// Phases an `@<rule>-<phase>/replace` fnref has taken ownership of.
|
|
159
|
+
// Once a phase is replaced, the plain/prepend/append fnrefs for it are
|
|
160
|
+
// ignored so older handlers (still lingering in the accumulated fnref
|
|
161
|
+
// map) are not re-installed on subsequent fnref() calls or re-derivation.
|
|
162
|
+
const replaced = (this.def.fnrefReplaced =
|
|
163
|
+
this.def.fnrefReplaced || new Set());
|
|
164
|
+
const reserved = [`@${rn}-bo`, `@${rn}-ao`, `@${rn}-bc`, `@${rn}-ac`];
|
|
165
|
+
for (let base of reserved) {
|
|
166
|
+
let phaseSet = installed.get(base);
|
|
167
|
+
if (!phaseSet)
|
|
168
|
+
installed.set(base, phaseSet = new WeakSet());
|
|
169
|
+
const aname = base.replace(/^[^-]+-/, '');
|
|
170
|
+
// `/replace` clears all prior actions for this phase (from any
|
|
171
|
+
// plugin) and installs the replacement, then owns the phase.
|
|
172
|
+
const replaceFn = fr[base + '/replace'];
|
|
173
|
+
if (replaceFn) {
|
|
174
|
+
if (!replaced.has(base)) {
|
|
175
|
+
replaced.add(base);
|
|
176
|
+
this.def[aname].length = 0;
|
|
177
|
+
phaseSet = new WeakSet();
|
|
178
|
+
installed.set(base, phaseSet);
|
|
179
|
+
phaseSet.add(replaceFn);
|
|
180
|
+
this[aname](true, replaceFn);
|
|
181
|
+
}
|
|
182
|
+
continue; // phase owned by replace: skip prepend/append/plain
|
|
183
|
+
}
|
|
184
|
+
if (replaced.has(base))
|
|
185
|
+
continue;
|
|
186
|
+
const prependFn = fr[base + '/prepend'];
|
|
187
|
+
const appendFn = fr[base + '/append'] ?? fr[base];
|
|
188
|
+
if (prependFn && !phaseSet.has(prependFn)) {
|
|
189
|
+
phaseSet.add(prependFn);
|
|
190
|
+
this[aname](false, prependFn);
|
|
191
|
+
}
|
|
192
|
+
if (appendFn && !phaseSet.has(appendFn)) {
|
|
193
|
+
phaseSet.add(appendFn);
|
|
194
|
+
this[aname](true, appendFn);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return this;
|
|
198
|
+
}
|
|
199
|
+
add(rs, a, mods) {
|
|
200
|
+
let inject = mods?.append ? 'push' : 'unshift';
|
|
201
|
+
let aa = ((0, utility_1.isarr)(a) ? a : [a])
|
|
202
|
+
.filter((alt) => null != alt && 'object' === typeof alt)
|
|
203
|
+
.map((a) => normalt(a, rs, this));
|
|
204
|
+
let altState = 'o' === rs ? 'open' : 'close';
|
|
205
|
+
let alts = this.def[altState];
|
|
206
|
+
// `clear` empties the pre-existing alternates (from earlier plugins)
|
|
207
|
+
// before the new ones are injected — a later plugin can thus replace
|
|
208
|
+
// a rule's open/close alternates outright. Done before inject so the
|
|
209
|
+
// new alternates survive.
|
|
210
|
+
if (mods?.clear) {
|
|
211
|
+
alts.length = 0;
|
|
212
|
+
}
|
|
213
|
+
alts[inject](...aa);
|
|
214
|
+
alts = this.def[altState] = (0, utility_1.modlist)(alts, mods);
|
|
215
|
+
(0, utility_1.filterRules)(this, this.cfg);
|
|
216
|
+
this.norm();
|
|
217
|
+
return this;
|
|
218
|
+
}
|
|
219
|
+
open(a, mods) {
|
|
220
|
+
return this.add('o', a, mods);
|
|
221
|
+
}
|
|
222
|
+
close(a, mods) {
|
|
223
|
+
return this.add('c', a, mods);
|
|
224
|
+
}
|
|
225
|
+
action(append, step, state, action) {
|
|
226
|
+
let actions = this.def[step + state];
|
|
227
|
+
if (append) {
|
|
228
|
+
actions.push(action);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
actions.unshift(action);
|
|
232
|
+
}
|
|
233
|
+
return this;
|
|
234
|
+
}
|
|
235
|
+
bo(append, action) {
|
|
236
|
+
return this.action(action ? !!append : true, types_1.BEFORE, types_1.OPEN, 'string' === typeof append ? this.def.fnref[append] :
|
|
237
|
+
(action ?? append));
|
|
238
|
+
}
|
|
239
|
+
ao(append, action) {
|
|
240
|
+
return this.action(action ? !!append : true, types_1.AFTER, types_1.OPEN, 'string' === typeof append ? this.def.fnref[append] :
|
|
241
|
+
(action ?? append));
|
|
242
|
+
}
|
|
243
|
+
bc(append, action) {
|
|
244
|
+
return this.action(action ? !!append : true, types_1.BEFORE, types_1.CLOSE, 'string' === typeof append ? this.def.fnref[append] :
|
|
245
|
+
(action ?? append));
|
|
246
|
+
}
|
|
247
|
+
ac(append, action) {
|
|
248
|
+
return this.action(action ? !!append : true, types_1.AFTER, types_1.CLOSE, 'string' === typeof append ? this.def.fnref[append] :
|
|
249
|
+
(action ?? append));
|
|
250
|
+
}
|
|
251
|
+
clear() {
|
|
252
|
+
this.def.open.length = 0;
|
|
253
|
+
this.def.close.length = 0;
|
|
254
|
+
this.def.bo.length = 0;
|
|
255
|
+
this.def.ao.length = 0;
|
|
256
|
+
this.def.bc.length = 0;
|
|
257
|
+
this.def.ac.length = 0;
|
|
258
|
+
return this;
|
|
259
|
+
}
|
|
260
|
+
// Remove this rule's open alternates without touching close or the
|
|
261
|
+
// lifecycle actions. A later plugin can call this, then re-add, to
|
|
262
|
+
// replace the open alternates contributed by earlier plugins.
|
|
263
|
+
clearOpen() {
|
|
264
|
+
this.def.open.length = 0;
|
|
265
|
+
return this;
|
|
266
|
+
}
|
|
267
|
+
// Remove this rule's close alternates (see clearOpen).
|
|
268
|
+
clearClose() {
|
|
269
|
+
this.def.close.length = 0;
|
|
270
|
+
return this;
|
|
271
|
+
}
|
|
272
|
+
// Remove the registered lifecycle actions for the named phases (any of
|
|
273
|
+
// 'bo', 'ao', 'bc', 'ac'); with no arguments, clear all four. The
|
|
274
|
+
// fnref dedup/replace bookkeeping for those phases is reset too, so a
|
|
275
|
+
// subsequent fnref() re-installs cleanly. Alternates are untouched.
|
|
276
|
+
clearActions(...phases) {
|
|
277
|
+
const all = (0 < phases.length ? phases : ['bo', 'ao', 'bc', 'ac']);
|
|
278
|
+
const installed = this.def
|
|
279
|
+
.fnrefInstalled;
|
|
280
|
+
const replaced = this.def.fnrefReplaced;
|
|
281
|
+
for (const p of all) {
|
|
282
|
+
;
|
|
283
|
+
this.def[p].length = 0;
|
|
284
|
+
const base = `@${this.name}-${p}`;
|
|
285
|
+
installed && installed.delete(base);
|
|
286
|
+
replaced && replaced.delete(base);
|
|
287
|
+
}
|
|
288
|
+
return this;
|
|
289
|
+
}
|
|
290
|
+
norm() {
|
|
291
|
+
this.def.open.map((alt) => normalt(alt, types_1.OPEN, this));
|
|
292
|
+
this.def.close.map((alt) => normalt(alt, types_1.CLOSE, this));
|
|
293
|
+
// [stateI is o=0,c=1][tokenI is 0..maxS-1][tins]
|
|
294
|
+
const columns = [];
|
|
295
|
+
// Compute max lookahead depth declared across this rule's alts,
|
|
296
|
+
// per state. Generalizes the previous hard-coded 2-slot collation.
|
|
297
|
+
const maxS = (alts) => alts.reduce((m, a) => Math.max(m, a.sN || 0), 0);
|
|
298
|
+
const maxOpen = maxS(this.def.open);
|
|
299
|
+
const maxClose = maxS(this.def.close);
|
|
300
|
+
for (let tI = 0; tI < maxOpen; tI++) {
|
|
301
|
+
this.def.open.reduce(...collate(0, tI, columns));
|
|
302
|
+
}
|
|
303
|
+
for (let tI = 0; tI < maxClose; tI++) {
|
|
304
|
+
this.def.close.reduce(...collate(1, tI, columns));
|
|
305
|
+
}
|
|
306
|
+
// Ensure tcol[stateI] exists with enough slots so the lexer's
|
|
307
|
+
// tcol gating can always index `tcol[oc][tI]` safely for any tI
|
|
308
|
+
// the parser passes (bounded by this rule's own maxS).
|
|
309
|
+
columns[0] = columns[0] || [];
|
|
310
|
+
columns[1] = columns[1] || [];
|
|
311
|
+
for (let tI = 0; tI < maxOpen; tI++)
|
|
312
|
+
columns[0][tI] = columns[0][tI] || [];
|
|
313
|
+
for (let tI = 0; tI < maxClose; tI++)
|
|
314
|
+
columns[1][tI] = columns[1][tI] || [];
|
|
315
|
+
this.def.tcol = columns;
|
|
316
|
+
function collate(stateI, tokenI, columns) {
|
|
317
|
+
columns[stateI] = columns[stateI] || [];
|
|
318
|
+
let tins = (columns[stateI][tokenI] = columns[stateI][tokenI] || []);
|
|
319
|
+
return [
|
|
320
|
+
function (tins, alt) {
|
|
321
|
+
let resolved = alt.t && alt.t[tokenI];
|
|
322
|
+
if (resolved && 0 < resolved.length) {
|
|
323
|
+
let newtins = [...new Set(tins.concat(resolved))];
|
|
324
|
+
tins.length = 0;
|
|
325
|
+
tins.push(...newtins);
|
|
326
|
+
}
|
|
327
|
+
return tins;
|
|
328
|
+
},
|
|
329
|
+
tins,
|
|
330
|
+
];
|
|
331
|
+
}
|
|
332
|
+
return this;
|
|
333
|
+
}
|
|
334
|
+
process(rule, ctx, lex, state) {
|
|
335
|
+
ctx.log && ctx.log(utility_1.S.rule, ctx, rule, lex);
|
|
336
|
+
let is_open = state === 'o';
|
|
337
|
+
let next = is_open ? rule : ctx.NORULE;
|
|
338
|
+
let why = is_open ? 'O' : 'C';
|
|
339
|
+
let def = this.def;
|
|
340
|
+
// Match alternates for current state.
|
|
341
|
+
let alts = (is_open ? def.open : def.close);
|
|
342
|
+
// Handle "before" call.
|
|
343
|
+
let befores = is_open ? (rule.bo ? def.bo : null) : rule.bc ? def.bc : null;
|
|
344
|
+
if (befores) {
|
|
345
|
+
let bout = undefined;
|
|
346
|
+
for (let bI = 0; bI < befores.length; bI++) {
|
|
347
|
+
bout = befores[bI].call(this, rule, ctx, next, bout);
|
|
348
|
+
if (bout?.isToken && bout?.err) {
|
|
349
|
+
return this.bad(bout, rule, ctx, { is_open });
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Attempt to match one of the alts.
|
|
354
|
+
let alt = 0 < alts.length ? parse_alts(is_open, alts, lex, rule, ctx) : EMPTY_ALT;
|
|
355
|
+
// Custom alt handler.
|
|
356
|
+
if (alt.h) {
|
|
357
|
+
alt = alt.h(rule, ctx, alt, next) || alt;
|
|
358
|
+
why += 'H';
|
|
359
|
+
}
|
|
360
|
+
// Unconditional error.
|
|
361
|
+
if (alt.e) {
|
|
362
|
+
return this.bad(alt.e, rule, ctx, { is_open });
|
|
363
|
+
}
|
|
364
|
+
// Update counters.
|
|
365
|
+
if (alt.n) {
|
|
366
|
+
for (let cn in alt.n) {
|
|
367
|
+
rule.n[cn] =
|
|
368
|
+
// 0 reverts counter to 0.
|
|
369
|
+
0 === alt.n[cn]
|
|
370
|
+
? 0
|
|
371
|
+
: // First seen, set to 0.
|
|
372
|
+
(null == rule.n[cn]
|
|
373
|
+
? 0
|
|
374
|
+
: // Increment counter.
|
|
375
|
+
rule.n[cn]) + alt.n[cn];
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// Set custom properties
|
|
379
|
+
if (alt.u) {
|
|
380
|
+
rule.u = Object.assign(rule.u, alt.u);
|
|
381
|
+
}
|
|
382
|
+
if (alt.k) {
|
|
383
|
+
rule.k = Object.assign(rule.k, alt.k);
|
|
384
|
+
}
|
|
385
|
+
// Record consumed tokens (matched minus backtrack) on the v
|
|
386
|
+
// history BEFORE running alt actions, so an action that calls
|
|
387
|
+
// ctx.rewind sees the just-matched tokens on top of the stack.
|
|
388
|
+
// The lookahead-buffer shift itself still happens at the end of
|
|
389
|
+
// process() so non-action paths behave identically.
|
|
390
|
+
//
|
|
391
|
+
// ctx.vAbs is an absolute monotonic counter used as the mark
|
|
392
|
+
// value — it's decoupled from ctx.v.length so the ring-buffer
|
|
393
|
+
// cap can evict old tokens from the front without invalidating
|
|
394
|
+
// outstanding marks (marks older than the retained window will
|
|
395
|
+
// simply fail at rewind time with a clear error).
|
|
396
|
+
const _cons = rule[is_open ? 'oN' : 'cN'] - (alt.b || 0);
|
|
397
|
+
if (0 < _cons) {
|
|
398
|
+
// Move consumed tokens from ctx.t → ctx.v. Clear the tbuf slots
|
|
399
|
+
// so a ctx.rewind call inside the subsequent alt action can
|
|
400
|
+
// distinguish "token already in v" (NOTOKEN here; will be
|
|
401
|
+
// replayed from v) from "pre-lexed lookahead past consumed"
|
|
402
|
+
// (real token in tbuf; needs re-queuing to preserve state).
|
|
403
|
+
const NOTOKEN = ctx.NOTOKEN;
|
|
404
|
+
for (let i = 0; i < _cons; i++) {
|
|
405
|
+
ctx.v.push(ctx.t[i]);
|
|
406
|
+
ctx.t[i] = NOTOKEN;
|
|
407
|
+
}
|
|
408
|
+
;
|
|
409
|
+
ctx.vAbs += _cons;
|
|
410
|
+
// Amortised-O(1) ring-buffer cap: let v grow to twice the
|
|
411
|
+
// capacity, then splice its front back down. Batch-eviction
|
|
412
|
+
// makes each push O(1) on average even at the cap.
|
|
413
|
+
const cap = ctx.cfg.rewind.history;
|
|
414
|
+
if (cap !== Infinity && ctx.v.length > 2 * cap) {
|
|
415
|
+
ctx.v.splice(0, ctx.v.length - cap);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
// TODO: move after rule.next resolution
|
|
419
|
+
// (breaks Expr! - fix first)
|
|
420
|
+
// Action call.
|
|
421
|
+
if (alt.a) {
|
|
422
|
+
why += 'A';
|
|
423
|
+
let tout = alt.a(rule, ctx, alt);
|
|
424
|
+
if (tout && tout.isToken && tout.err) {
|
|
425
|
+
return this.bad(tout, rule, ctx, { is_open });
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
// Push a new rule onto the stack...
|
|
429
|
+
if (alt.p) {
|
|
430
|
+
ctx.rs[ctx.rsI++] = rule;
|
|
431
|
+
let rulespec = ctx.rsm[alt.p];
|
|
432
|
+
if (rulespec) {
|
|
433
|
+
next = rule.child = makeRule(rulespec, ctx, rule.node);
|
|
434
|
+
next.parent = rule;
|
|
435
|
+
next.n = { ...rule.n };
|
|
436
|
+
if (0 < Object.keys(rule.k).length) {
|
|
437
|
+
next.k = { ...rule.k };
|
|
438
|
+
}
|
|
439
|
+
why += 'P`' + alt.p + '`';
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
return this.bad(this.unknownRule(ctx.t0, alt.p), rule, ctx, { is_open });
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// ...or replace with a new rule.
|
|
446
|
+
else if (alt.r) {
|
|
447
|
+
let rulespec = ctx.rsm[alt.r];
|
|
448
|
+
if (rulespec) {
|
|
449
|
+
next = makeRule(rulespec, ctx, rule.node);
|
|
450
|
+
next.parent = rule.parent;
|
|
451
|
+
next.prev = rule;
|
|
452
|
+
next.n = { ...rule.n };
|
|
453
|
+
if (0 < Object.keys(rule.k).length) {
|
|
454
|
+
next.k = { ...rule.k };
|
|
455
|
+
}
|
|
456
|
+
why += 'R`' + alt.r + '`';
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
return this.bad(this.unknownRule(ctx.t0, alt.r), rule, ctx, { is_open });
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
// Pop closed rule off stack.
|
|
463
|
+
else if (!is_open) {
|
|
464
|
+
next = ctx.rs[--ctx.rsI] || ctx.NORULE;
|
|
465
|
+
}
|
|
466
|
+
// TODO: move action call here (alt.a)
|
|
467
|
+
// and set r.next = next, so that action has access to next
|
|
468
|
+
rule.next = next;
|
|
469
|
+
// Handle "after" call.
|
|
470
|
+
let afters = is_open ? (rule.ao ? def.ao : null) : rule.ac ? def.ac : null;
|
|
471
|
+
if (afters) {
|
|
472
|
+
let aout = undefined;
|
|
473
|
+
for (let aI = 0; aI < afters.length; aI++) {
|
|
474
|
+
aout = afters[aI](rule, ctx, next, aout);
|
|
475
|
+
if (aout?.isToken && aout?.err) {
|
|
476
|
+
return this.bad(aout, rule, ctx, { is_open });
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
next.why = why;
|
|
481
|
+
ctx.log && ctx.log(utility_1.S.node, ctx, rule, lex, next);
|
|
482
|
+
// Must be last as state change is for next process call.
|
|
483
|
+
if (types_1.OPEN === rule.state) {
|
|
484
|
+
rule.state = types_1.CLOSE;
|
|
485
|
+
}
|
|
486
|
+
// Backtrack reduces consumed token count.
|
|
487
|
+
let consumed = rule[is_open ? 'oN' : 'cN'] - (alt.b || 0);
|
|
488
|
+
if (consumed < 0)
|
|
489
|
+
consumed = 0;
|
|
490
|
+
if (0 < consumed) {
|
|
491
|
+
// Shift the lookahead buffer left by `consumed` slots, filling
|
|
492
|
+
// vacated tail positions with NOTOKEN so later alts re-fetch.
|
|
493
|
+
// (The corresponding v-history push ran before alt actions.)
|
|
494
|
+
const L = ctx.t.length;
|
|
495
|
+
for (let i = 0; i < L - consumed; i++)
|
|
496
|
+
ctx.t[i] = ctx.t[i + consumed];
|
|
497
|
+
for (let i = Math.max(0, L - consumed); i < L; i++)
|
|
498
|
+
ctx.t[i] = ctx.NOTOKEN;
|
|
499
|
+
}
|
|
500
|
+
return next;
|
|
501
|
+
}
|
|
502
|
+
bad(tkn, rule, ctx, parse) {
|
|
503
|
+
throw new error_1.TabnasError(tkn.err || utility_1.S.unexpected, {
|
|
504
|
+
...tkn.use,
|
|
505
|
+
state: parse.is_open ? utility_1.S.open : utility_1.S.close,
|
|
506
|
+
}, tkn, rule, ctx);
|
|
507
|
+
}
|
|
508
|
+
unknownRule(tkn, name) {
|
|
509
|
+
tkn.err = 'unknown_rule';
|
|
510
|
+
tkn.use = tkn.use || {};
|
|
511
|
+
tkn.use.rulename = name;
|
|
512
|
+
return tkn;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
exports.RuleSpec = RuleSpec;
|
|
516
|
+
const makeRuleSpec = (...params) => new RuleSpec(...params);
|
|
517
|
+
exports.makeRuleSpec = makeRuleSpec;
|
|
518
|
+
// First match wins.
|
|
519
|
+
// NOTE: input AltSpecs are used to build the Alt output.
|
|
520
|
+
function parse_alts(is_open, alts, lex, rule, ctx) {
|
|
521
|
+
let out = PALT;
|
|
522
|
+
out.b = 0; // Backtrack n tokens.
|
|
523
|
+
out.p = types_1.EMPTY; // Push named rule onto stack.
|
|
524
|
+
out.r = types_1.EMPTY; // Replace current rule with named rule.
|
|
525
|
+
out.n = undefined; // Increment named counters.
|
|
526
|
+
out.h = undefined; // Custom handler function.
|
|
527
|
+
out.a = undefined; // Rule action.
|
|
528
|
+
out.u = undefined; // Custom rule properties.
|
|
529
|
+
out.k = undefined; // Custom rule properties (propagated).
|
|
530
|
+
out.e = undefined; // Error token.
|
|
531
|
+
let alt = null;
|
|
532
|
+
let altI = 0;
|
|
533
|
+
let t = ctx.cfg.t;
|
|
534
|
+
let cond = true;
|
|
535
|
+
let bitAA = 1 << (t.AA - 1);
|
|
536
|
+
let IGNORE = ctx.cfg.tokenSetTins.IGNORE;
|
|
537
|
+
function next(r, alt, altI, tI) {
|
|
538
|
+
let tkn;
|
|
539
|
+
do {
|
|
540
|
+
tkn = lex.next(r, alt, altI, tI);
|
|
541
|
+
ctx.tC++;
|
|
542
|
+
} while (IGNORE[tkn.tin]);
|
|
543
|
+
return tkn;
|
|
544
|
+
}
|
|
545
|
+
// TODO: replace with lookup map
|
|
546
|
+
let len = alts.length;
|
|
547
|
+
const NOTOKEN = ctx.NOTOKEN;
|
|
548
|
+
const tbuf = ctx.t;
|
|
549
|
+
for (altI = 0; altI < len; altI++) {
|
|
550
|
+
alt = alts[altI];
|
|
551
|
+
// Number of positions that matched in this alt. Tracked so the
|
|
552
|
+
// rule can record exactly which tokens it consumed.
|
|
553
|
+
let matched = 0;
|
|
554
|
+
cond = true;
|
|
555
|
+
const S = alt.S;
|
|
556
|
+
const sN = alt.sN | 0;
|
|
557
|
+
// Iterate alt's lookahead positions. Each position is fetched
|
|
558
|
+
// lazily and only when the previous position matched, preserving
|
|
559
|
+
// the original 2-slot lazy behaviour for any N.
|
|
560
|
+
//
|
|
561
|
+
// A null entry in S[i] means "no Tin constraint at this position"
|
|
562
|
+
// (wildcard) - the token is still fetched and consumed, but the
|
|
563
|
+
// bit-field check is skipped. This matches the `s` docstring
|
|
564
|
+
// ("null if position matches any token") and prevents silently
|
|
565
|
+
// dropping the check at a later required position.
|
|
566
|
+
for (let i = 0; i < sN; i++) {
|
|
567
|
+
let tkn = tbuf[i];
|
|
568
|
+
if (null == tkn || NOTOKEN === tkn) {
|
|
569
|
+
tkn = tbuf[i] = next(rule, alt, altI, i);
|
|
570
|
+
}
|
|
571
|
+
const Si = S ? S[i] : null;
|
|
572
|
+
if (null != Si) {
|
|
573
|
+
const tin = tkn.tin;
|
|
574
|
+
const part = (tin / 31) | 0;
|
|
575
|
+
// bitAA lives in partition 0 (tin=AA=4). ORing it into the
|
|
576
|
+
// match mask for any partition other than 0 lets unrelated
|
|
577
|
+
// tokens in higher partitions collide with alts that merely
|
|
578
|
+
// set bit 3 of their own partition — a false positive. Apply
|
|
579
|
+
// bitAA only when testing a partition-0 token.
|
|
580
|
+
const aaBit = part === 0 ? bitAA : 0;
|
|
581
|
+
if (!(Si[part] & ((1 << ((tin % 31) - 1)) | aaBit))) {
|
|
582
|
+
cond = false;
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
matched = i + 1;
|
|
587
|
+
}
|
|
588
|
+
if (is_open) {
|
|
589
|
+
rule.oN = matched;
|
|
590
|
+
for (let i = 0; i < matched; i++)
|
|
591
|
+
rule.o[i] = tbuf[i];
|
|
592
|
+
// Clear trailing slots so stale matches from earlier alts are
|
|
593
|
+
// not observed via rule.o[i] / rule.o0 / rule.o1 accessors.
|
|
594
|
+
for (let i = matched; i < rule.o.length; i++)
|
|
595
|
+
rule.o[i] = NOTOKEN;
|
|
596
|
+
}
|
|
597
|
+
else {
|
|
598
|
+
rule.cN = matched;
|
|
599
|
+
for (let i = 0; i < matched; i++)
|
|
600
|
+
rule.c[i] = tbuf[i];
|
|
601
|
+
for (let i = matched; i < rule.c.length; i++)
|
|
602
|
+
rule.c[i] = NOTOKEN;
|
|
603
|
+
}
|
|
604
|
+
// Optional custom condition
|
|
605
|
+
if (cond && alt.c) {
|
|
606
|
+
cond = cond && alt.c(rule, ctx, out);
|
|
607
|
+
}
|
|
608
|
+
if (cond) {
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
alt = null;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
if (!cond) {
|
|
616
|
+
out.e = tbuf[0] ?? NOTOKEN;
|
|
617
|
+
}
|
|
618
|
+
if (alt) {
|
|
619
|
+
out.n = null != alt.n ? alt.n : out.n;
|
|
620
|
+
out.h = null != alt.h ? alt.h : out.h;
|
|
621
|
+
out.a = null != alt.a ? alt.a : out.a;
|
|
622
|
+
out.u = null != alt.u ? alt.u : out.u;
|
|
623
|
+
out.k = null != alt.k ? alt.k : out.k;
|
|
624
|
+
out.g = null != alt.g ? alt.g : out.g;
|
|
625
|
+
out.e = (alt.e && alt.e(rule, ctx, out)) || undefined;
|
|
626
|
+
out.p =
|
|
627
|
+
null != alt.p && false !== alt.p
|
|
628
|
+
? 'string' === typeof alt.p
|
|
629
|
+
? alt.p
|
|
630
|
+
: alt.p(rule, ctx, out)
|
|
631
|
+
: out.p;
|
|
632
|
+
out.r =
|
|
633
|
+
null != alt.r && false !== alt.r
|
|
634
|
+
? 'string' === typeof alt.r
|
|
635
|
+
? alt.r
|
|
636
|
+
: alt.r(rule, ctx, out)
|
|
637
|
+
: out.r;
|
|
638
|
+
out.b =
|
|
639
|
+
null != alt.b && false !== alt.b
|
|
640
|
+
? 'number' === typeof alt.b
|
|
641
|
+
? alt.b
|
|
642
|
+
: alt.b(rule, ctx, out)
|
|
643
|
+
: out.b;
|
|
644
|
+
}
|
|
645
|
+
let match = altI < alts.length;
|
|
646
|
+
ctx.log && ctx.log(utility_1.S.parse, ctx, rule, lex, match, cond, altI, alt, out);
|
|
647
|
+
return out;
|
|
648
|
+
}
|
|
649
|
+
const partify = (tins, part) => tins.filter((tin) => 31 * part <= tin && tin < 31 * (part + 1));
|
|
650
|
+
const bitify = (s, part) => s.reduce((bits, tin) => (1 << (tin - (31 * part + 1))) | bits, 0);
|
|
651
|
+
// Valid group-tag pattern: lowercase letter followed by one or more
|
|
652
|
+
// lowercase letters, digits, or hyphens. Enforced by normalt().
|
|
653
|
+
const GROUP_TAG_RE = /^[a-z][a-z0-9-]+$/;
|
|
654
|
+
// Normalize AltSpec (mutates).
|
|
655
|
+
function normalt(a, rs, r) {
|
|
656
|
+
// Ensure groups are a string[]
|
|
657
|
+
if (types_1.STRING === typeof a.g) {
|
|
658
|
+
a.g = a.g.split(/\s*,\s*/);
|
|
659
|
+
}
|
|
660
|
+
else if (null == a.g) {
|
|
661
|
+
a.g = [];
|
|
662
|
+
}
|
|
663
|
+
// Validate every group tag (reject empty and non-matching tags).
|
|
664
|
+
for (let tag of a.g) {
|
|
665
|
+
if (!GROUP_TAG_RE.test(tag)) {
|
|
666
|
+
throw new Error(`Grammar: invalid group tag "${tag}" ` +
|
|
667
|
+
`in rule ${r.name} (${rs}) — must match ${GROUP_TAG_RE}`);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
a.g = a.g.sort();
|
|
671
|
+
const aa = a;
|
|
672
|
+
if (!a.s || 0 === a.s.length) {
|
|
673
|
+
a.s = null;
|
|
674
|
+
aa.t = [];
|
|
675
|
+
aa.S = null;
|
|
676
|
+
aa.sN = 0;
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
const tinsify = (s) => {
|
|
680
|
+
const tins = s
|
|
681
|
+
.flat()
|
|
682
|
+
.map((n) => 'string' === typeof n ? n.split(/\s* +\s*/) : n)
|
|
683
|
+
.flat()
|
|
684
|
+
.map((n) => 'string' === typeof n ? (r.ji.tokenSet(n) ?? r.ji.token(n)) : n)
|
|
685
|
+
.flat()
|
|
686
|
+
.filter((tin) => 'number' === typeof tin);
|
|
687
|
+
return tins;
|
|
688
|
+
};
|
|
689
|
+
if ('string' === typeof a.s) {
|
|
690
|
+
a.s = a.s.split(/\s* +\s*/);
|
|
691
|
+
}
|
|
692
|
+
// Per-position resolved tins and bit-field match tables.
|
|
693
|
+
// alt.t[i] holds the Tin[] for position i (used by tcol collation);
|
|
694
|
+
// alt.S[i] holds the bit-packed lookup (null if position is empty,
|
|
695
|
+
// which should not normally occur - tinsify filters nulls).
|
|
696
|
+
const sN = a.s.length;
|
|
697
|
+
const t = new Array(sN);
|
|
698
|
+
const S = new Array(sN);
|
|
699
|
+
for (let i = 0; i < sN; i++) {
|
|
700
|
+
const tins = tinsify([a.s[i]]);
|
|
701
|
+
t[i] = tins;
|
|
702
|
+
// `#AA` is the ANY wildcard — a position whose tin list
|
|
703
|
+
// includes it must match every lexed token regardless of
|
|
704
|
+
// partition. Represent that by dropping to the existing
|
|
705
|
+
// `S[i] = null` sentinel ("no constraint"), bypassing the
|
|
706
|
+
// per-partition bitset check in parse_alts. The t[i] entry
|
|
707
|
+
// keeps the raw tin list so tcol collation still reflects
|
|
708
|
+
// what the user wrote.
|
|
709
|
+
const aaTin = r.ji.token('#AA');
|
|
710
|
+
if (aaTin != null && tins.includes(aaTin)) {
|
|
711
|
+
S[i] = null;
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
S[i] =
|
|
715
|
+
0 < tins.length
|
|
716
|
+
? new Array(Math.max(...tins.map((tin) => (1 + tin / 31) | 0)))
|
|
717
|
+
.fill(null)
|
|
718
|
+
.map((_, j) => j)
|
|
719
|
+
.map((part) => bitify(partify(tins, part), part))
|
|
720
|
+
: null;
|
|
721
|
+
}
|
|
722
|
+
aa.t = t;
|
|
723
|
+
aa.S = S;
|
|
724
|
+
aa.sN = sN;
|
|
725
|
+
}
|
|
726
|
+
if (!a.p) {
|
|
727
|
+
a.p = null;
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
resolveFunctionRef('push', rs, r, a, 'p');
|
|
731
|
+
}
|
|
732
|
+
if (!a.r) {
|
|
733
|
+
a.r = null;
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
resolveFunctionRef('replace', rs, r, a, 'r');
|
|
737
|
+
}
|
|
738
|
+
if (!a.b) {
|
|
739
|
+
a.b = null;
|
|
740
|
+
}
|
|
741
|
+
else {
|
|
742
|
+
resolveFunctionRef('back', rs, r, a, 'b');
|
|
743
|
+
}
|
|
744
|
+
if (!a.a) {
|
|
745
|
+
a.a = null;
|
|
746
|
+
}
|
|
747
|
+
else {
|
|
748
|
+
resolveFunctionRef('action', rs, r, a, 'a');
|
|
749
|
+
}
|
|
750
|
+
if (!a.h) {
|
|
751
|
+
a.h = null;
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
resolveFunctionRef('modify', rs, r, a, 'h');
|
|
755
|
+
}
|
|
756
|
+
if (!a.e) {
|
|
757
|
+
a.e = null;
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
resolveFunctionRef('error', rs, r, a, 'e');
|
|
761
|
+
}
|
|
762
|
+
if (!a.c) {
|
|
763
|
+
a.c = null;
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
const ct = typeof a.c;
|
|
767
|
+
if ('string' === ct) {
|
|
768
|
+
resolveFunctionRef('condition', rs, r, a, 'c');
|
|
769
|
+
}
|
|
770
|
+
else if ('function' === ct) {
|
|
771
|
+
if ('c' === a.c.name) {
|
|
772
|
+
(0, utility_1.defprop)(a.c, 'name', { value: 'ruleCond' });
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
else if ('object' === ct) {
|
|
776
|
+
const ac = a.c;
|
|
777
|
+
const conds = [];
|
|
778
|
+
const ruleprops = Object.keys(a.c);
|
|
779
|
+
for (let prop of ruleprops) {
|
|
780
|
+
const pspec = ac[prop];
|
|
781
|
+
if (null != pspec) {
|
|
782
|
+
if ('object' === typeof pspec) {
|
|
783
|
+
for (let co of Object.keys(pspec)) {
|
|
784
|
+
if (1 === COND_OPS[co]) {
|
|
785
|
+
conds.push(makeRuleCond(co, prop, pspec[co]));
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
else {
|
|
790
|
+
conds.push(makeRuleCond('$eq', prop, pspec));
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
if (0 === conds.length) {
|
|
795
|
+
delete a.c;
|
|
796
|
+
}
|
|
797
|
+
else if (1 === conds.length) {
|
|
798
|
+
a.c = conds[0];
|
|
799
|
+
}
|
|
800
|
+
else {
|
|
801
|
+
a.c = function conjunctCond(r, c, a) {
|
|
802
|
+
for (let cond of conds) {
|
|
803
|
+
let pass = cond(r, c, a);
|
|
804
|
+
if (false == pass) {
|
|
805
|
+
return false;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
return true;
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
else {
|
|
813
|
+
throw new Error('Grammar: invalid condition: ' + a.c);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return a;
|
|
817
|
+
}
|
|
818
|
+
function isfnref(v) {
|
|
819
|
+
return 'string' === typeof v && v.startsWith('@');
|
|
820
|
+
}
|
|
821
|
+
function resolveFunctionRef(fkind, rs, r, a, k) {
|
|
822
|
+
const val = a[k];
|
|
823
|
+
// An action (`a`) may be a list of refs/functions, run in order when
|
|
824
|
+
// the alt matches: the matched alt's own action first, then composed
|
|
825
|
+
// user actions. This lets a serialized, function-free grammar carry
|
|
826
|
+
// `a: ['@node$', '@my-user-action']` and resolve each by name. The
|
|
827
|
+
// array is collapsed here to a single function so `process()` keeps
|
|
828
|
+
// invoking one `alt.a`. Gated to `a` only — the other fields are
|
|
829
|
+
// single-valued.
|
|
830
|
+
if ('a' === k && Array.isArray(val)) {
|
|
831
|
+
if (0 === val.length) {
|
|
832
|
+
a[k] = null;
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
const fns = val.map((v) => {
|
|
836
|
+
if (isfnref(v)) {
|
|
837
|
+
const f = r.def.fnref[v];
|
|
838
|
+
if (null == f) {
|
|
839
|
+
throw new Error(`Grammar: unknown ${fkind} function reference: ` + v +
|
|
840
|
+
` for rule ${r.name} (${rs}) and alt ${a.s} (${a.g})`);
|
|
841
|
+
}
|
|
842
|
+
return f;
|
|
843
|
+
}
|
|
844
|
+
return v;
|
|
845
|
+
});
|
|
846
|
+
a[k] = function composedAction(rule, ctx, alt) {
|
|
847
|
+
let out;
|
|
848
|
+
for (const f of fns) {
|
|
849
|
+
out = f(rule, ctx, alt);
|
|
850
|
+
// Preserve the error-token short-circuit semantics of process().
|
|
851
|
+
if (out && out.isToken && out.err)
|
|
852
|
+
return out;
|
|
853
|
+
}
|
|
854
|
+
return out;
|
|
855
|
+
};
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
if (isfnref(val)) {
|
|
859
|
+
const func = r.def.fnref[val];
|
|
860
|
+
if (null == func) {
|
|
861
|
+
throw new Error(`Grammar: unknown ${fkind} function reference: ` + val +
|
|
862
|
+
` for rule ${r.name} (${rs}) and alt ${a.s} (${a.g})`);
|
|
863
|
+
}
|
|
864
|
+
a[k] = func;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
const COND_OPS = {
|
|
868
|
+
$eq: 1,
|
|
869
|
+
$ne: 1,
|
|
870
|
+
$lt: 1,
|
|
871
|
+
$lte: 1,
|
|
872
|
+
$gt: 1,
|
|
873
|
+
$gte: 1,
|
|
874
|
+
};
|
|
875
|
+
function makeRuleCond(co, prop, val) {
|
|
876
|
+
const path = prop.split('.');
|
|
877
|
+
if ('$eq' === co) {
|
|
878
|
+
return function ruleCond(r, _c, _a) {
|
|
879
|
+
const rval = (0, utility_1.getpath)(r, path);
|
|
880
|
+
return rval === val;
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
else if ('$ne' === co) {
|
|
884
|
+
return function ruleCond(r, _c, _a) {
|
|
885
|
+
const rval = (0, utility_1.getpath)(r, path);
|
|
886
|
+
return rval != val;
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
else if ('$lt' === co) {
|
|
890
|
+
return function ruleCond(r, _c, _a) {
|
|
891
|
+
const rval = (0, utility_1.getpath)(r, path);
|
|
892
|
+
return null == rval || rval < val;
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
else if ('$lte' === co) {
|
|
896
|
+
return function ruleCond(r, _c, _a) {
|
|
897
|
+
const rval = (0, utility_1.getpath)(r, path);
|
|
898
|
+
return null == rval || rval <= val;
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
else if ('$gt' === co) {
|
|
902
|
+
return function ruleCond(r, _c, _a) {
|
|
903
|
+
const rval = (0, utility_1.getpath)(r, path);
|
|
904
|
+
return null == rval || rval > val;
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
else if ('$gte' === co) {
|
|
908
|
+
return function ruleCond(r, _c, _a) {
|
|
909
|
+
const rval = (0, utility_1.getpath)(r, path);
|
|
910
|
+
return null == rval || rval >= val;
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
else if ('$exist' === co) {
|
|
914
|
+
return function ruleCond(r, _c, _a) {
|
|
915
|
+
const rval = (0, utility_1.getpath)(r, path);
|
|
916
|
+
return true === val ? null != rval : null == rval;
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
else {
|
|
920
|
+
throw new Error('Grammer: unknown comparison operator: ' + co);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
//# sourceMappingURL=rules.js.map
|