@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/utility.js
ADDED
|
@@ -0,0 +1,868 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* Copyright (c) 2013-2026 Richard Rodger, MIT License */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.values = exports.keys = exports.omap = exports.isarr = exports.entries = exports.defprop = exports.assign = exports.S = void 0;
|
|
5
|
+
exports.badlex = badlex;
|
|
6
|
+
exports.charset = charset;
|
|
7
|
+
exports.charsBitmap = charsBitmap;
|
|
8
|
+
exports.clean = clean;
|
|
9
|
+
exports.clone = clone;
|
|
10
|
+
exports.configure = configure;
|
|
11
|
+
exports.deep = deep;
|
|
12
|
+
exports.escre = escre;
|
|
13
|
+
exports.filterRules = filterRules;
|
|
14
|
+
exports.getpath = getpath;
|
|
15
|
+
exports.makelog = makelog;
|
|
16
|
+
exports.mesc = mesc;
|
|
17
|
+
exports.regexp = regexp;
|
|
18
|
+
exports.snip = snip;
|
|
19
|
+
exports.srcfmt = srcfmt;
|
|
20
|
+
exports.tokenize = tokenize;
|
|
21
|
+
exports.parserwrap = parserwrap;
|
|
22
|
+
exports.str = str;
|
|
23
|
+
exports.findTokenSet = findTokenSet;
|
|
24
|
+
exports.modlist = modlist;
|
|
25
|
+
exports.resolveFuncRefs = resolveFuncRefs;
|
|
26
|
+
const types_1 = require("./types");
|
|
27
|
+
const lexer_1 = require("./lexer");
|
|
28
|
+
const error_1 = require("./error");
|
|
29
|
+
// Null-safe object and array utilities
|
|
30
|
+
// TODO: should use proper types:
|
|
31
|
+
// https://github.com/microsoft/TypeScript/tree/main/src/lib
|
|
32
|
+
// Own enumerable keys of an object, or [] for null/undefined.
|
|
33
|
+
// keys({a:1}) // => ['a']; keys(null) // => []
|
|
34
|
+
const keys = (x) => (null == x ? [] : Object.keys(x));
|
|
35
|
+
exports.keys = keys;
|
|
36
|
+
// Own enumerable values of an object, or [] for null/undefined.
|
|
37
|
+
// values({a:1}) // => [1]; values(null) // => []
|
|
38
|
+
const values = (x) => null == x ? [] : Object.values(x);
|
|
39
|
+
exports.values = values;
|
|
40
|
+
// Own enumerable [key,value] pairs of an object, or [] for null/undefined.
|
|
41
|
+
// entries({a:1}) // => [['a',1]]; entries(null) // => []
|
|
42
|
+
const entries = (x) => (null == x ? [] : Object.entries(x));
|
|
43
|
+
exports.entries = entries;
|
|
44
|
+
// Merge sources into target, using a new {} when target is null/undefined.
|
|
45
|
+
// assign({a:1},{b:2}) // => {a:1,b:2}; assign(null,{b:2}) // => {b:2}
|
|
46
|
+
const assign = (x, ...r) => Object.assign(null == x ? {} : x, ...r);
|
|
47
|
+
exports.assign = assign;
|
|
48
|
+
// True if value is an array.
|
|
49
|
+
// isarr([1]) // => true; isarr({}) // => false
|
|
50
|
+
const isarr = (x) => Array.isArray(x);
|
|
51
|
+
exports.isarr = isarr;
|
|
52
|
+
// Alias for Object.defineProperty.
|
|
53
|
+
// defprop(o,'k',{value:1}) // => o (with o.k === 1)
|
|
54
|
+
const defprop = Object.defineProperty;
|
|
55
|
+
exports.defprop = defprop;
|
|
56
|
+
// Rebuild an object by mapping each [key,value] entry; an undefined new
|
|
57
|
+
// key drops the entry, and extra returned pairs set additional keys.
|
|
58
|
+
// omap({a:1}, ([k,v])=>['x',v*2]) // => {x:2}
|
|
59
|
+
const omap = (o, f) => {
|
|
60
|
+
return Object.entries(o || {}).reduce((o, e) => {
|
|
61
|
+
let me = f ? f(e) : e;
|
|
62
|
+
if (undefined === me[0]) {
|
|
63
|
+
delete o[e[0]];
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
o[me[0]] = me[1];
|
|
67
|
+
}
|
|
68
|
+
// Additional pairs set additional keys.
|
|
69
|
+
let i = 2;
|
|
70
|
+
while (undefined !== me[i]) {
|
|
71
|
+
o[me[i]] = me[i + 1];
|
|
72
|
+
i += 2;
|
|
73
|
+
}
|
|
74
|
+
return o;
|
|
75
|
+
}, {});
|
|
76
|
+
};
|
|
77
|
+
exports.omap = omap;
|
|
78
|
+
// TODO: remove!
|
|
79
|
+
// Interned string constants used throughout the engine (strict, and helps
|
|
80
|
+
// minification slightly).
|
|
81
|
+
const S = {
|
|
82
|
+
indent: '. ', // Source-dump indent unit.
|
|
83
|
+
logindent: ' ', // Debug-log indent unit.
|
|
84
|
+
space: ' ', // Single space.
|
|
85
|
+
gap: ' ', // Two-space gap (log joins).
|
|
86
|
+
Object: 'Object', // Constructor name "Object".
|
|
87
|
+
Array: 'Array', // Constructor name "Array".
|
|
88
|
+
object: 'object', // typeof "object".
|
|
89
|
+
string: 'string', // typeof "string".
|
|
90
|
+
function: 'function', // typeof "function".
|
|
91
|
+
unexpected: 'unexpected', // Default error code.
|
|
92
|
+
map: 'map', // Node kind: map.
|
|
93
|
+
list: 'list', // Node kind: list.
|
|
94
|
+
elem: 'elem', // List element label.
|
|
95
|
+
pair: 'pair', // Map pair label.
|
|
96
|
+
val: 'val', // Value label / start rule.
|
|
97
|
+
node: 'node', // Generic node label.
|
|
98
|
+
no_re_flags: types_1.EMPTY, // Empty RegExp flags.
|
|
99
|
+
unprintable: 'unprintable', // Error code: unprintable char.
|
|
100
|
+
invalid_ascii: 'invalid_ascii', // Error code: bad ASCII escape.
|
|
101
|
+
invalid_unicode: 'invalid_unicode', // Error code: bad unicode escape.
|
|
102
|
+
invalid_lex_state: 'invalid_lex_state', // Error code: bad lexer state.
|
|
103
|
+
unterminated_string: 'unterminated_string', // Error code: unclosed string.
|
|
104
|
+
unterminated_comment: 'unterminated_comment', // Error code: unclosed comment.
|
|
105
|
+
lex: 'lex', // Phase name: lex.
|
|
106
|
+
parse: 'parse', // Phase name: parse.
|
|
107
|
+
error: 'error', // Phase/label: error.
|
|
108
|
+
none: 'none', // Sentinel "none".
|
|
109
|
+
imp_map: 'imp,map', // Implicit map group tag.
|
|
110
|
+
imp_list: 'imp,list', // Implicit list group tag.
|
|
111
|
+
imp_null: 'imp,null', // Implicit null group tag.
|
|
112
|
+
end: 'end', // Marker: end.
|
|
113
|
+
open: 'open', // Rule state: open.
|
|
114
|
+
close: 'close', // Rule state: close.
|
|
115
|
+
rule: 'rule', // Label: rule.
|
|
116
|
+
stack: 'stack', // Label: rule stack.
|
|
117
|
+
nUll: 'null', // The literal string "null".
|
|
118
|
+
name: 'name', // Label: name.
|
|
119
|
+
make: 'make', // Label: make.
|
|
120
|
+
colon: ':', // Colon separator.
|
|
121
|
+
step: 'step', // Label: step.
|
|
122
|
+
};
|
|
123
|
+
exports.S = S;
|
|
124
|
+
// Idempotent normalization of options.
|
|
125
|
+
// See Config type for commentary.
|
|
126
|
+
function configure(tabnas, incfg, opts) {
|
|
127
|
+
const cfg = incfg || {};
|
|
128
|
+
cfg.t = cfg.t || {};
|
|
129
|
+
cfg.tI = cfg.tI || 1;
|
|
130
|
+
const t = (tn) => tokenize(tn, cfg);
|
|
131
|
+
// Standard tokens. These names should not be changed.
|
|
132
|
+
if (false !== opts.standard$) {
|
|
133
|
+
t('#BD'); // BAD
|
|
134
|
+
t('#ZZ'); // END
|
|
135
|
+
t('#UK'); // UNKNOWN
|
|
136
|
+
t('#AA'); // ANY
|
|
137
|
+
t('#SP'); // SPACE
|
|
138
|
+
t('#LN'); // LINE
|
|
139
|
+
t('#CM'); // COMMENT
|
|
140
|
+
t('#NR'); // NUMBER
|
|
141
|
+
t('#ST'); // STRING
|
|
142
|
+
t('#TX'); // TEXT
|
|
143
|
+
t('#VL'); // VALUE
|
|
144
|
+
}
|
|
145
|
+
cfg.safe = {
|
|
146
|
+
key: false === opts.safe?.key ? false : true,
|
|
147
|
+
};
|
|
148
|
+
cfg.fixed = {
|
|
149
|
+
lex: !!opts.fixed?.lex,
|
|
150
|
+
token: opts.fixed
|
|
151
|
+
? omap(clean(opts.fixed.token), ([name, src]) => [
|
|
152
|
+
src,
|
|
153
|
+
tokenize(name, cfg),
|
|
154
|
+
])
|
|
155
|
+
: {},
|
|
156
|
+
ref: undefined,
|
|
157
|
+
check: opts.fixed?.check,
|
|
158
|
+
};
|
|
159
|
+
cfg.fixed.ref = omap(cfg.fixed.token, ([tin, src]) => [
|
|
160
|
+
tin,
|
|
161
|
+
src,
|
|
162
|
+
]);
|
|
163
|
+
cfg.fixed.ref = Object.assign(cfg.fixed.ref, omap(cfg.fixed.ref, ([tin, src]) => [src, tin]));
|
|
164
|
+
cfg.match = {
|
|
165
|
+
lex: !!opts.match?.lex,
|
|
166
|
+
value: opts.match
|
|
167
|
+
? omap(clean(opts.match.value), ([name, spec]) => [
|
|
168
|
+
name,
|
|
169
|
+
spec,
|
|
170
|
+
])
|
|
171
|
+
: {},
|
|
172
|
+
token: opts.match
|
|
173
|
+
? omap(clean(opts.match.token), ([name, matcher]) => [
|
|
174
|
+
tokenize(name, cfg),
|
|
175
|
+
matcher,
|
|
176
|
+
])
|
|
177
|
+
: {},
|
|
178
|
+
check: opts.match?.check,
|
|
179
|
+
};
|
|
180
|
+
// Lookup tin directly from matcher
|
|
181
|
+
omap(cfg.match.token, ([tin, matcher]) => [
|
|
182
|
+
tin,
|
|
183
|
+
((matcher.tin$ = +tin), matcher),
|
|
184
|
+
]);
|
|
185
|
+
// Convert tokenSet tokens names to tins
|
|
186
|
+
const tokenSet = opts.tokenSet
|
|
187
|
+
? Object.keys(opts.tokenSet).reduce((a, n) => ((a[n] = opts.tokenSet[n]
|
|
188
|
+
.filter((x) => null != x)
|
|
189
|
+
.map((n) => t(n))),
|
|
190
|
+
a), {})
|
|
191
|
+
: {};
|
|
192
|
+
cfg.tokenSet = cfg.tokenSet || {};
|
|
193
|
+
entries(tokenSet).map((entry) => {
|
|
194
|
+
let name = entry[0];
|
|
195
|
+
let tinset = entry[1];
|
|
196
|
+
if (cfg.tokenSet[name]) {
|
|
197
|
+
cfg.tokenSet[name].length = 0;
|
|
198
|
+
cfg.tokenSet[name].push(...tinset);
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
cfg.tokenSet[name] = tinset;
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
// Lookup table for token tin in given tokenSet
|
|
205
|
+
cfg.tokenSetTins = entries(cfg.tokenSet).reduce((a, en) => ((a[en[0]] = a[en[0]] || {}),
|
|
206
|
+
en[1].map((tin) => (a[en[0]][tin] = true)),
|
|
207
|
+
a), {});
|
|
208
|
+
// The IGNORE tokenSet is special and should always exist, even if empty.
|
|
209
|
+
cfg.tokenSetTins.IGNORE = cfg.tokenSetTins.IGNORE || {};
|
|
210
|
+
cfg.space = {
|
|
211
|
+
lex: !!opts.space?.lex,
|
|
212
|
+
chars: charset(opts.space?.chars),
|
|
213
|
+
charsBitmap: charsBitmap(opts.space?.chars),
|
|
214
|
+
check: opts.space?.check,
|
|
215
|
+
};
|
|
216
|
+
cfg.line = {
|
|
217
|
+
lex: !!opts.line?.lex,
|
|
218
|
+
chars: charset(opts.line?.chars),
|
|
219
|
+
charsBitmap: charsBitmap(opts.line?.chars),
|
|
220
|
+
rowChars: charset(opts.line?.rowChars),
|
|
221
|
+
rowCharsBitmap: charsBitmap(opts.line?.rowChars),
|
|
222
|
+
single: !!opts.line?.single,
|
|
223
|
+
check: opts.line?.check,
|
|
224
|
+
};
|
|
225
|
+
cfg.text = {
|
|
226
|
+
lex: !!opts.text?.lex,
|
|
227
|
+
modify: (cfg.text?.modify || [])
|
|
228
|
+
.concat((opts.text?.modify ? [opts.text.modify] : []).flat())
|
|
229
|
+
.filter((m) => null != m),
|
|
230
|
+
check: opts.text?.check,
|
|
231
|
+
};
|
|
232
|
+
cfg.number = {
|
|
233
|
+
lex: !!opts.number?.lex,
|
|
234
|
+
hex: !!opts.number?.hex,
|
|
235
|
+
oct: !!opts.number?.oct,
|
|
236
|
+
bin: !!opts.number?.bin,
|
|
237
|
+
sep: null != opts.number?.sep && '' !== opts.number.sep,
|
|
238
|
+
exclude: opts.number?.exclude,
|
|
239
|
+
sepChar: opts.number?.sep,
|
|
240
|
+
check: opts.number?.check,
|
|
241
|
+
};
|
|
242
|
+
// NOTE: these are not value ending tokens
|
|
243
|
+
cfg.value = {
|
|
244
|
+
lex: !!opts.value?.lex,
|
|
245
|
+
def: entries(opts.value?.def || {}).reduce((a, e) => (null == e[1] || false === e[1] || e[1].match || (a[e[0]] = e[1]), a), {}),
|
|
246
|
+
// Pre-sort by name at configure time so iteration at lex time is
|
|
247
|
+
// deterministic across runtimes (does not depend on object key order).
|
|
248
|
+
defre: entries(opts.value?.def || {})
|
|
249
|
+
.filter(([, spec]) => spec && spec.match)
|
|
250
|
+
.map(([name, spec]) => ({
|
|
251
|
+
name,
|
|
252
|
+
val: spec.val,
|
|
253
|
+
match: spec.match,
|
|
254
|
+
consume: !!spec.consume,
|
|
255
|
+
}))
|
|
256
|
+
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)),
|
|
257
|
+
// TODO: just testing, move to a plugin for extended values
|
|
258
|
+
// 'undefined': { v: undefined },
|
|
259
|
+
// 'NaN': { v: NaN },
|
|
260
|
+
// 'Infinity': { v: Infinity },
|
|
261
|
+
// '+Infinity': { v: +Infinity },
|
|
262
|
+
// '-Infinity': { v: -Infinity },
|
|
263
|
+
};
|
|
264
|
+
cfg.rule = {
|
|
265
|
+
start: null == opts.rule?.start ? 'val' : opts.rule.start,
|
|
266
|
+
maxmul: null == opts.rule?.maxmul ? 3 : opts.rule.maxmul,
|
|
267
|
+
finish: !!opts.rule?.finish,
|
|
268
|
+
include: opts.rule?.include
|
|
269
|
+
? opts.rule.include.split(/\s*,+\s*/).filter((g) => '' !== g)
|
|
270
|
+
: [],
|
|
271
|
+
exclude: opts.rule?.exclude
|
|
272
|
+
? opts.rule.exclude.split(/\s*,+\s*/).filter((g) => '' !== g)
|
|
273
|
+
: [],
|
|
274
|
+
};
|
|
275
|
+
cfg.map = {
|
|
276
|
+
extend: !!opts.map?.extend,
|
|
277
|
+
merge: opts.map?.merge,
|
|
278
|
+
child: !!opts.map?.child,
|
|
279
|
+
};
|
|
280
|
+
cfg.list = {
|
|
281
|
+
property: !!opts.list?.property,
|
|
282
|
+
pair: !!opts.list?.pair,
|
|
283
|
+
child: !!opts.list?.child,
|
|
284
|
+
};
|
|
285
|
+
cfg.info = {
|
|
286
|
+
map: !!opts.info?.map,
|
|
287
|
+
list: !!opts.info?.list,
|
|
288
|
+
text: !!opts.info?.text,
|
|
289
|
+
marker: opts.info?.marker || '__info__',
|
|
290
|
+
};
|
|
291
|
+
let fixedSorted = Object.keys(cfg.fixed.token).sort((a, b) => b.length - a.length);
|
|
292
|
+
let fixedRE = fixedSorted.map((fixed) => escre(fixed)).join('|');
|
|
293
|
+
let commentStartRE = opts.comment?.lex
|
|
294
|
+
? (opts.comment.def ? values(opts.comment.def) : [])
|
|
295
|
+
.filter((c) => c && c.lex)
|
|
296
|
+
.map((c) => escre(c.start))
|
|
297
|
+
.join('|')
|
|
298
|
+
: '';
|
|
299
|
+
// End-marker RE part
|
|
300
|
+
let enderRE = [
|
|
301
|
+
'([',
|
|
302
|
+
escre(keys(charset(cfg.space.lex && cfg.space.chars, cfg.line.lex && cfg.line.chars)).join('')),
|
|
303
|
+
']',
|
|
304
|
+
('string' === typeof opts.ender
|
|
305
|
+
? opts.ender.split('')
|
|
306
|
+
: Array.isArray(opts.ender)
|
|
307
|
+
? opts.ender
|
|
308
|
+
: [])
|
|
309
|
+
.map((c) => '|' + escre(c))
|
|
310
|
+
.join(''),
|
|
311
|
+
'' === fixedRE ? '' : '|',
|
|
312
|
+
fixedRE,
|
|
313
|
+
'' === commentStartRE ? '' : '|',
|
|
314
|
+
commentStartRE,
|
|
315
|
+
'|$)', // EOF case
|
|
316
|
+
];
|
|
317
|
+
cfg.rePart = {
|
|
318
|
+
fixed: fixedRE,
|
|
319
|
+
ender: enderRE,
|
|
320
|
+
commentStart: commentStartRE,
|
|
321
|
+
};
|
|
322
|
+
// TODO: friendlier names
|
|
323
|
+
cfg.re = {
|
|
324
|
+
ender: regexp(null, ...enderRE),
|
|
325
|
+
// TODO: prebuild these using a property on matcher?
|
|
326
|
+
rowChars: regexp(null, escre(opts.line?.rowChars)),
|
|
327
|
+
columns: regexp(null, '[' + escre(opts.line?.chars) + ']', '(.*)$'),
|
|
328
|
+
};
|
|
329
|
+
cfg.lex = {
|
|
330
|
+
empty: !!opts.lex?.empty,
|
|
331
|
+
emptyResult: opts.lex?.emptyResult,
|
|
332
|
+
match: opts.lex?.match
|
|
333
|
+
? entries(opts.lex.match)
|
|
334
|
+
.reduce((list, entry) => {
|
|
335
|
+
let name = entry[0];
|
|
336
|
+
let matchspec = entry[1];
|
|
337
|
+
if (matchspec) {
|
|
338
|
+
let matcher = matchspec.make(cfg, opts);
|
|
339
|
+
if (matcher) {
|
|
340
|
+
matcher.matcher = name;
|
|
341
|
+
matcher.make = matchspec.make;
|
|
342
|
+
matcher.order = matchspec.order;
|
|
343
|
+
}
|
|
344
|
+
list.push(matcher);
|
|
345
|
+
}
|
|
346
|
+
return list;
|
|
347
|
+
}, [])
|
|
348
|
+
.filter((m) => null != m && false !== m && -1 < +m.order)
|
|
349
|
+
.sort((a, b) => a.order - b.order)
|
|
350
|
+
: [],
|
|
351
|
+
};
|
|
352
|
+
cfg.parse = {
|
|
353
|
+
prepare: values(opts.parse?.prepare),
|
|
354
|
+
};
|
|
355
|
+
cfg.debug = {
|
|
356
|
+
get_console: opts.debug?.get_console || (() => console),
|
|
357
|
+
maxlen: null == opts.debug?.maxlen ? 99 : opts.debug.maxlen,
|
|
358
|
+
print: {
|
|
359
|
+
config: !!opts.debug?.print?.config,
|
|
360
|
+
src: opts.debug?.print?.src,
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
cfg.error = opts.error ?? {};
|
|
364
|
+
cfg.errmsg = (opts.errmsg ?? { suffix: true });
|
|
365
|
+
cfg.hint = opts.hint ?? {};
|
|
366
|
+
// Apply any config modifiers (probably from plugins).
|
|
367
|
+
if (opts.config?.modify) {
|
|
368
|
+
keys(opts.config.modify).forEach((modifer) => opts.config.modify[modifer](cfg, opts));
|
|
369
|
+
}
|
|
370
|
+
// Debug the config - useful for plugin authors.
|
|
371
|
+
if (cfg.debug.print.config) {
|
|
372
|
+
cfg.debug.get_console().dir(cfg, { depth: null });
|
|
373
|
+
}
|
|
374
|
+
cfg.result = {
|
|
375
|
+
fail: [],
|
|
376
|
+
};
|
|
377
|
+
if (opts.result) {
|
|
378
|
+
cfg.result.fail = [...opts.result.fail];
|
|
379
|
+
}
|
|
380
|
+
// Parse-time consumed-token history cap (for ctx.rewind).
|
|
381
|
+
cfg.rewind = {
|
|
382
|
+
history: null == opts.rewind?.history ? Infinity : opts.rewind.history,
|
|
383
|
+
};
|
|
384
|
+
const optscolor = opts.color ?? {};
|
|
385
|
+
cfg.color = cfg.color ?? {};
|
|
386
|
+
cfg.color.active = optscolor.active ?? cfg.color.active ?? true;
|
|
387
|
+
cfg.color.reset = optscolor.reset ?? cfg.color.reset ?? '\x1b[0m';
|
|
388
|
+
cfg.color.hi = optscolor.hi ?? cfg.color.hi ?? '\x1b[91m';
|
|
389
|
+
cfg.color.lo = optscolor.lo ?? cfg.color.lo ?? '\x1b[2m';
|
|
390
|
+
cfg.color.line = optscolor.line ?? cfg.color.line ?? '\x1b[34m';
|
|
391
|
+
assign(tabnas.options, opts);
|
|
392
|
+
assign(tabnas.token, cfg.t);
|
|
393
|
+
assign(tabnas.tokenSet, cfg.tokenSet);
|
|
394
|
+
assign(tabnas.fixed, cfg.fixed.ref);
|
|
395
|
+
return cfg;
|
|
396
|
+
}
|
|
397
|
+
// Uniquely resolve or assign token by name (string) or identification number (Tin),
|
|
398
|
+
// returning the associated Tin (for the name) or name (for the Tin).
|
|
399
|
+
// tokenize('#AA', cfg) // => 4 (Tin); tokenize(4, cfg) // => '#AA'
|
|
400
|
+
function tokenize(ref, cfg, tabnas) {
|
|
401
|
+
let tokenmap = cfg.t;
|
|
402
|
+
let token = tokenmap[ref];
|
|
403
|
+
if (null == token && types_1.STRING === typeof ref) {
|
|
404
|
+
// The internal tI counter is a plain number; brand it to Tin
|
|
405
|
+
// here, the one place tins legitimately come from the counter.
|
|
406
|
+
token = cfg.tI++;
|
|
407
|
+
tokenmap[token] = ref;
|
|
408
|
+
tokenmap[ref] = token;
|
|
409
|
+
tokenmap[ref.substring(1)] = token;
|
|
410
|
+
if (null != tabnas) {
|
|
411
|
+
assign(tabnas.token, cfg.t);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return token;
|
|
415
|
+
}
|
|
416
|
+
// Find a tokenSet's tins by name (leading # optional); undefined if absent.
|
|
417
|
+
// findTokenSet('IGNORE', cfg) // => [tin,...]; findTokenSet('#IGNORE', cfg) // => [tin,...]
|
|
418
|
+
function findTokenSet(ref, cfg) {
|
|
419
|
+
let tokenSetMap = cfg.tokenSet;
|
|
420
|
+
let found = (tokenSetMap[ref] ??
|
|
421
|
+
('string' == typeof ref ? tokenSetMap[ref.replace(/#/g, '')] : undefined));
|
|
422
|
+
return found;
|
|
423
|
+
}
|
|
424
|
+
// Mark a string for escaping by `regexp` (wraps it as a String with esc=true).
|
|
425
|
+
// mesc('a.b').esc // => true
|
|
426
|
+
function mesc(s, _) {
|
|
427
|
+
return (_ = new String(s)), (_.esc = true), _;
|
|
428
|
+
}
|
|
429
|
+
// Construct a RegExp from parts; mesc-marked parts are regex-escaped first.
|
|
430
|
+
// NOTE: flags first allows concatenated parts to be rest.
|
|
431
|
+
// regexp(null, 'a', 'b').source // => 'ab'
|
|
432
|
+
function regexp(flags, ...parts) {
|
|
433
|
+
return new RegExp(parts
|
|
434
|
+
.map((p) => p.esc
|
|
435
|
+
? //p.replace(/[-\\|\]{}()[^$+*?.!=]/g, '\\$&')
|
|
436
|
+
escre(p.toString())
|
|
437
|
+
: p)
|
|
438
|
+
.join(types_1.EMPTY), null == flags ? '' : flags);
|
|
439
|
+
}
|
|
440
|
+
// Escape RegExp metacharacters (and tab/cr/lf) in a string; '' for null.
|
|
441
|
+
// escre('a.b') // => 'a\\.b'
|
|
442
|
+
function escre(s) {
|
|
443
|
+
return null == s
|
|
444
|
+
? ''
|
|
445
|
+
: s
|
|
446
|
+
.replace(/[-\\|\]{}()[^$+*?.!=]/g, '\\$&')
|
|
447
|
+
.replace(/\t/g, '\\t')
|
|
448
|
+
.replace(/\r/g, '\\r')
|
|
449
|
+
.replace(/\n/g, '\\n');
|
|
450
|
+
}
|
|
451
|
+
// Deep override for plain data. Mutates base object and array.
|
|
452
|
+
// Array merge by `over` index, `over` wins non-matching types, except:
|
|
453
|
+
// `undefined` always loses, `over` plain objects inject into functions,
|
|
454
|
+
// and `over` functions always win. Over always copied.
|
|
455
|
+
// deep({a:1}, {b:2}) // => {a:1, b:2}
|
|
456
|
+
function deep(base, ...rest) {
|
|
457
|
+
let base_isf = S.function === typeof base;
|
|
458
|
+
let base_iso = null != base && (S.object === typeof base || base_isf);
|
|
459
|
+
for (let over of rest) {
|
|
460
|
+
let over_isf = S.function === typeof over;
|
|
461
|
+
let over_iso = null != over && (S.object === typeof over || over_isf);
|
|
462
|
+
let over_ctor;
|
|
463
|
+
if (base_iso &&
|
|
464
|
+
over_iso &&
|
|
465
|
+
!over_isf &&
|
|
466
|
+
Array.isArray(base) === Array.isArray(over)) {
|
|
467
|
+
for (let k in over) {
|
|
468
|
+
base[k] = deep(base[k], over[k]);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
base =
|
|
473
|
+
undefined === over || types_1.SKIP === over
|
|
474
|
+
? base
|
|
475
|
+
: over_isf
|
|
476
|
+
? over
|
|
477
|
+
: over_iso
|
|
478
|
+
? S.function === typeof (over_ctor = over.constructor) &&
|
|
479
|
+
S.Object !== over_ctor.name &&
|
|
480
|
+
S.Array !== over_ctor.name
|
|
481
|
+
? over
|
|
482
|
+
: deep(Array.isArray(over) ? [] : {}, over)
|
|
483
|
+
: over;
|
|
484
|
+
base_isf = S.function === typeof base;
|
|
485
|
+
base_iso = null != base && (S.object === typeof base || base_isf);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return base;
|
|
489
|
+
}
|
|
490
|
+
// Wrap a Lex so that emitting the BAD token (BD) throws a TabnasError.
|
|
491
|
+
function badlex(lex, BD, ctx) {
|
|
492
|
+
let next = lex.next.bind(lex);
|
|
493
|
+
lex.next = (rule, alt, altI, tI) => {
|
|
494
|
+
let token = next(rule, alt, altI, tI);
|
|
495
|
+
if (BD === token.tin) {
|
|
496
|
+
let details = {};
|
|
497
|
+
if (null != token.use) {
|
|
498
|
+
details.use = token.use;
|
|
499
|
+
}
|
|
500
|
+
throw new error_1.TabnasError(token.why || S.unexpected, details, token, rule, ctx);
|
|
501
|
+
}
|
|
502
|
+
return token;
|
|
503
|
+
};
|
|
504
|
+
return lex;
|
|
505
|
+
}
|
|
506
|
+
// Special debug logging to console (use Tabnas('...', {log:N})).
|
|
507
|
+
// log:N -> console.dir to depth N
|
|
508
|
+
// log:-1 -> console.dir to depth 1, omitting objects (good summary!)
|
|
509
|
+
function makelog(ctx, meta) {
|
|
510
|
+
let trace = ctx.opts?.plugin?.debug?.trace;
|
|
511
|
+
if (meta || trace) {
|
|
512
|
+
if ('number' === typeof meta?.log || trace) {
|
|
513
|
+
let exclude_objects = false;
|
|
514
|
+
let logdepth = meta?.log;
|
|
515
|
+
if (-1 === logdepth || trace) {
|
|
516
|
+
logdepth = 1;
|
|
517
|
+
exclude_objects = true;
|
|
518
|
+
}
|
|
519
|
+
ctx.log = (...rest) => {
|
|
520
|
+
if (exclude_objects) {
|
|
521
|
+
let logstr = rest
|
|
522
|
+
.filter((item) => S.object != typeof item)
|
|
523
|
+
.map((item) => (S.function == typeof item ? item.name : item))
|
|
524
|
+
.join(S.gap);
|
|
525
|
+
ctx.cfg.debug.get_console().log(logstr);
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
ctx.cfg.debug.get_console().dir(rest, { depth: logdepth });
|
|
529
|
+
}
|
|
530
|
+
return undefined;
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
else if ('function' === typeof meta.log) {
|
|
534
|
+
ctx.log = meta.log;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return ctx.log;
|
|
538
|
+
}
|
|
539
|
+
// Build a source-formatter for debug output: a custom one if configured,
|
|
540
|
+
// else a JSON stringifier truncated to config.debug.maxlen.
|
|
541
|
+
function srcfmt(config) {
|
|
542
|
+
return 'function' === typeof config.debug.print.src
|
|
543
|
+
? config.debug.print.src
|
|
544
|
+
: (s) => {
|
|
545
|
+
let out = null == s
|
|
546
|
+
? types_1.EMPTY
|
|
547
|
+
: Array.isArray(s)
|
|
548
|
+
? JSON.stringify(s).replace(/]$/, entries(s)
|
|
549
|
+
.filter((en) => isNaN(en[0]))
|
|
550
|
+
.map((en, i) => (0 === i ? ', ' : '') +
|
|
551
|
+
en[0] +
|
|
552
|
+
': ' +
|
|
553
|
+
JSON.stringify(en[1])) + // Just one level of array props!
|
|
554
|
+
']')
|
|
555
|
+
: JSON.stringify(s);
|
|
556
|
+
out =
|
|
557
|
+
out.substring(0, config.debug.maxlen) +
|
|
558
|
+
(config.debug.maxlen < out.length ? '...' : types_1.EMPTY);
|
|
559
|
+
return out;
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
// Stringify any value to at most `len` chars, appending '...' when truncated.
|
|
563
|
+
// str({a:1}) // => '{"a":1}'; str('abcdef', 5) // => 'ab...'
|
|
564
|
+
function str(o, len = 44) {
|
|
565
|
+
let s;
|
|
566
|
+
try {
|
|
567
|
+
s = 'object' === typeof o ? JSON.stringify(o) : '' + o;
|
|
568
|
+
}
|
|
569
|
+
catch (e) {
|
|
570
|
+
s = '' + o;
|
|
571
|
+
}
|
|
572
|
+
return snip(len < s.length ? s.substring(0, len - 3) + '...' : s, len);
|
|
573
|
+
}
|
|
574
|
+
// First `len` chars of a value, with whitespace chars replaced by '.'; '' if undefined.
|
|
575
|
+
// snip('ab\ncd') // => 'ab.cd'; snip(undefined) // => ''
|
|
576
|
+
function snip(s, len = 5) {
|
|
577
|
+
return undefined === s
|
|
578
|
+
? ''
|
|
579
|
+
: ('' + s).substring(0, len).replace(/[\r\n\t]/g, '.');
|
|
580
|
+
}
|
|
581
|
+
// Deep clone a class instance, preserving its prototype.
|
|
582
|
+
// clone(new Foo()) // => new Foo()-equivalent (same prototype, deep-copied data)
|
|
583
|
+
function clone(class_instance) {
|
|
584
|
+
return deep(Object.create(Object.getPrototypeOf(class_instance)), class_instance);
|
|
585
|
+
}
|
|
586
|
+
// Build a char->code-point lookup map from strings/objects (false parts skipped).
|
|
587
|
+
// charset('ab') // => {a:97, b:98}
|
|
588
|
+
function charset(...parts) {
|
|
589
|
+
return null == parts
|
|
590
|
+
? {}
|
|
591
|
+
: parts
|
|
592
|
+
.filter((p) => false !== p)
|
|
593
|
+
.map((p) => ('object' === typeof p ? keys(p).join(types_1.EMPTY) : p))
|
|
594
|
+
.join(types_1.EMPTY)
|
|
595
|
+
.split(types_1.EMPTY)
|
|
596
|
+
.reduce((a, c) => ((a[c] = c.charCodeAt(0)), a), {});
|
|
597
|
+
}
|
|
598
|
+
// Bitmap form of a `charset`: a 256-byte Uint8Array with `1` at every
|
|
599
|
+
// index whose code-point appears in the input. Hot lexer loops index
|
|
600
|
+
// it directly off `src.charCodeAt(sI)` instead of doing a sparse object
|
|
601
|
+
// lookup keyed by a single-character string. Code-points >= 256 are
|
|
602
|
+
// dropped; callers that need full-unicode chars must keep the original
|
|
603
|
+
// `Chars` object as a fallback alongside the bitmap.
|
|
604
|
+
// charsBitmap('a')[97] // => 1
|
|
605
|
+
function charsBitmap(...parts) {
|
|
606
|
+
const out = new Uint8Array(256);
|
|
607
|
+
for (const p of parts) {
|
|
608
|
+
if (null == p || false === p)
|
|
609
|
+
continue;
|
|
610
|
+
const s = 'string' === typeof p ? p : keys(p).join(types_1.EMPTY);
|
|
611
|
+
for (let i = 0; i < s.length; i++) {
|
|
612
|
+
const cc = s.charCodeAt(i);
|
|
613
|
+
if (cc < 256)
|
|
614
|
+
out[cc] = 1;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return out;
|
|
618
|
+
}
|
|
619
|
+
// Remove all properties with values null or undefined. Note: mutates argument.
|
|
620
|
+
// clean({a:1, b:null}) // => {a:1}
|
|
621
|
+
function clean(o) {
|
|
622
|
+
for (let p in o) {
|
|
623
|
+
if (null == o[p]) {
|
|
624
|
+
delete o[p];
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return o;
|
|
628
|
+
}
|
|
629
|
+
// TODO: rename to filterAlts
|
|
630
|
+
function filterRules(rs, cfg) {
|
|
631
|
+
// Build a fresh def with cloned alt objects so callers (notably
|
|
632
|
+
// Parser.clone) can pass a parent rule-spec without leaking
|
|
633
|
+
// mutation back to the parent. Previously this function rewrote
|
|
634
|
+
// rs.def[rsn] in-place, which made child instances share filtered
|
|
635
|
+
// alt arrays with their parent and corrupted later parses on the
|
|
636
|
+
// parent.
|
|
637
|
+
const newDef = { ...rs.def };
|
|
638
|
+
const rsnames = ['open', 'close'];
|
|
639
|
+
for (const rsn of rsnames) {
|
|
640
|
+
newDef[rsn] = rs.def[rsn]
|
|
641
|
+
// Clone each alt while normalising `g` from string to string[].
|
|
642
|
+
.map((as) => ({
|
|
643
|
+
...as,
|
|
644
|
+
g: 'string' === typeof as.g
|
|
645
|
+
? (as.g || '').split(/\s*,+\s*/)
|
|
646
|
+
: as.g || [],
|
|
647
|
+
}))
|
|
648
|
+
// Keep alt if any group name matches, or if no includes were set.
|
|
649
|
+
.filter((as) => cfg.rule.include.reduce((a, g) => a || (null != as.g && -1 !== as.g.indexOf(g)), 0 === cfg.rule.include.length))
|
|
650
|
+
// Drop alt if any group name matches an exclude.
|
|
651
|
+
.filter((as) => cfg.rule.exclude.reduce((a, g) => a && (null == as.g || -1 === as.g.indexOf(g)), true));
|
|
652
|
+
}
|
|
653
|
+
// Preserve the RuleSpec prototype (it carries .open/.close/.fnref
|
|
654
|
+
// methods) by cloning via Object.create rather than spread.
|
|
655
|
+
const newRs = Object.create(Object.getPrototypeOf(rs));
|
|
656
|
+
Object.assign(newRs, rs);
|
|
657
|
+
newRs.def = newDef;
|
|
658
|
+
return newRs;
|
|
659
|
+
}
|
|
660
|
+
// Get or (when `val` given) set a dot-path property, creating intermediate
|
|
661
|
+
// objects; rejects __proto__ for prototype-pollution safety.
|
|
662
|
+
// prop({a:{b:1}}, 'a.b') // => 1; prop({}, 'a.b', 2) // => 2 (sets a.b)
|
|
663
|
+
function prop(obj, path, val) {
|
|
664
|
+
let root = obj;
|
|
665
|
+
try {
|
|
666
|
+
let parts = path.split('.');
|
|
667
|
+
let pn;
|
|
668
|
+
for (let pI = 0; pI < parts.length; pI++) {
|
|
669
|
+
pn = parts[pI];
|
|
670
|
+
if ('__proto__' === pn) {
|
|
671
|
+
throw new Error(pn);
|
|
672
|
+
}
|
|
673
|
+
if (pI < parts.length - 1) {
|
|
674
|
+
obj = obj[pn] = obj[pn] || {};
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (undefined !== val) {
|
|
678
|
+
if ('__proto__' === pn) {
|
|
679
|
+
throw new Error(pn);
|
|
680
|
+
}
|
|
681
|
+
obj[pn] = val;
|
|
682
|
+
}
|
|
683
|
+
return obj[pn];
|
|
684
|
+
}
|
|
685
|
+
catch (e) {
|
|
686
|
+
throw new Error('Cannot ' +
|
|
687
|
+
(undefined === val ? 'get' : 'set') +
|
|
688
|
+
' path ' +
|
|
689
|
+
path +
|
|
690
|
+
' on object: ' +
|
|
691
|
+
str(root) +
|
|
692
|
+
(undefined === val ? '' : ' to value: ' + str(val, 22)));
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
// Mutates list based on ListMods (delete-by-index, move pairs, then custom).
|
|
696
|
+
// modlist(['a','b','c'], {delete:[1]}) // => ['a','c']
|
|
697
|
+
function modlist(list, mods) {
|
|
698
|
+
if (mods && list) {
|
|
699
|
+
if (0 < list.length) {
|
|
700
|
+
// Delete before move so indexes still make sense, using null to preserve index.
|
|
701
|
+
if (mods.delete && 0 < mods.delete.length) {
|
|
702
|
+
for (let i = 0; i < mods.delete.length; i++) {
|
|
703
|
+
let mdI = mods.delete[i];
|
|
704
|
+
if (mdI < 0 ? -1 * mdI <= list.length : mdI < list.length) {
|
|
705
|
+
let dI = (list.length + mdI) % list.length;
|
|
706
|
+
list[dI] = null;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
// Format: [from,to, from,to, ...]
|
|
711
|
+
if (mods.move) {
|
|
712
|
+
for (let i = 0; i < mods.move.length; i += 2) {
|
|
713
|
+
let fromI = (list.length + mods.move[i]) % list.length;
|
|
714
|
+
let toI = (list.length + mods.move[i + 1]) % list.length;
|
|
715
|
+
let entry = list[fromI];
|
|
716
|
+
list.splice(fromI, 1);
|
|
717
|
+
list.splice(toI, 0, entry);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
// Filter out any deletes.
|
|
721
|
+
// return list.filter((a: AltSpec) => null != a)
|
|
722
|
+
let filtered = list.filter((entry) => null != entry);
|
|
723
|
+
if (filtered.length !== list.length) {
|
|
724
|
+
list.length = 0;
|
|
725
|
+
list.push(...filtered);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
// Custom modification of list.
|
|
729
|
+
if (mods.custom) {
|
|
730
|
+
let newlist = mods.custom(list);
|
|
731
|
+
if (null != newlist) {
|
|
732
|
+
list = newlist;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
return list;
|
|
737
|
+
}
|
|
738
|
+
// Wrap a parser's start() to convert a native JSON SyntaxError into a TabnasError.
|
|
739
|
+
function parserwrap(parser) {
|
|
740
|
+
return {
|
|
741
|
+
start: function (src, tabnas, meta, parent_ctx) {
|
|
742
|
+
try {
|
|
743
|
+
return parser.start(src, tabnas, meta, parent_ctx);
|
|
744
|
+
}
|
|
745
|
+
catch (ex) {
|
|
746
|
+
if ('SyntaxError' === ex.name) {
|
|
747
|
+
let loc = 0;
|
|
748
|
+
let row = 0;
|
|
749
|
+
let col = 0;
|
|
750
|
+
let tsrc = types_1.EMPTY;
|
|
751
|
+
let errloc = ex.message.match(/^Unexpected token (.) .*position\s+(\d+)/i);
|
|
752
|
+
if (errloc) {
|
|
753
|
+
tsrc = errloc[1];
|
|
754
|
+
loc = parseInt(errloc[2]);
|
|
755
|
+
row = src.substring(0, loc).replace(/[^\n]/g, types_1.EMPTY).length;
|
|
756
|
+
let cI = loc - 1;
|
|
757
|
+
while (-1 < cI && '\n' !== src.charAt(cI))
|
|
758
|
+
cI--;
|
|
759
|
+
col = Math.max(src.substring(cI, loc).length, 0);
|
|
760
|
+
}
|
|
761
|
+
let token = ex.token ||
|
|
762
|
+
(0, lexer_1.makeToken)('#UK', tokenize('#UK', tabnas.internal().config), undefined, tsrc, (0, lexer_1.makePoint)(tsrc.length, loc, ex.lineNumber || row, ex.columnNumber || col));
|
|
763
|
+
throw new error_1.TabnasError(ex.code || 'json', ex.details || {
|
|
764
|
+
msg: ex.message,
|
|
765
|
+
}, token, {},
|
|
766
|
+
// TODO: this smells
|
|
767
|
+
ex.ctx ||
|
|
768
|
+
{
|
|
769
|
+
uI: -1,
|
|
770
|
+
opts: tabnas.options,
|
|
771
|
+
cfg: tabnas.internal().config,
|
|
772
|
+
token: token,
|
|
773
|
+
meta,
|
|
774
|
+
src: () => src,
|
|
775
|
+
root: () => undefined,
|
|
776
|
+
plgn: () => tabnas.internal().plugins,
|
|
777
|
+
inst: () => tabnas,
|
|
778
|
+
rule: { name: 'no-rule' },
|
|
779
|
+
sub: {},
|
|
780
|
+
xs: -1,
|
|
781
|
+
v2: token,
|
|
782
|
+
v1: token,
|
|
783
|
+
t: [token, token], // TODO: t[1] should be end token
|
|
784
|
+
tC: -1,
|
|
785
|
+
kI: -1,
|
|
786
|
+
rs: [],
|
|
787
|
+
rsI: 0,
|
|
788
|
+
rsm: {},
|
|
789
|
+
n: {},
|
|
790
|
+
log: meta ? meta.log : undefined,
|
|
791
|
+
F: srcfmt(tabnas.internal().config),
|
|
792
|
+
u: {},
|
|
793
|
+
NORULE: { name: 'no-rule' },
|
|
794
|
+
NOTOKEN: { name: 'no-token' },
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
else {
|
|
798
|
+
throw ex;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
},
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
// Read a value at a dot-path (or path array); undefined if any segment is missing.
|
|
805
|
+
// getpath({a:{b:1}}, 'a.b') // => 1; getpath({}, 'a.b') // => undefined
|
|
806
|
+
function getpath(root, path) {
|
|
807
|
+
path = 'string' === typeof path ? path.split('.') : path;
|
|
808
|
+
let node = root;
|
|
809
|
+
for (let i = 0; i < path.length && null != node; i++) {
|
|
810
|
+
node = node[path[i]];
|
|
811
|
+
}
|
|
812
|
+
return node;
|
|
813
|
+
}
|
|
814
|
+
// Recursively resolve FuncRef strings in an options object to actual functions,
|
|
815
|
+
// and `@/pattern/flags` strings to RegExp instances.
|
|
816
|
+
// resolveFuncRefs({r:'@/a/i'}) // => {r: /a/i}; resolveFuncRefs('@@x') // => '@x'
|
|
817
|
+
function resolveFuncRefs(obj, ref) {
|
|
818
|
+
if (null == obj || 'object' !== typeof obj) {
|
|
819
|
+
if ('string' === typeof obj && '@' === obj[0]) {
|
|
820
|
+
// Escape: `@@` prefix produces a literal `@`-prefixed string.
|
|
821
|
+
if ('@' === obj[1]) {
|
|
822
|
+
return obj.substring(1);
|
|
823
|
+
}
|
|
824
|
+
// Sentinel: `@SKIP` resolves to the SKIP symbol (acts as undefined in deep merge).
|
|
825
|
+
if ('SKIP' === obj.substring(1)) {
|
|
826
|
+
return types_1.SKIP;
|
|
827
|
+
}
|
|
828
|
+
// Match `@/pattern/flags` — a JSON-serializable RegExp literal.
|
|
829
|
+
const m = obj.match(/^@\/(.*)\/([\w]*)$/);
|
|
830
|
+
if (m) {
|
|
831
|
+
return new RegExp(m[1], m[2]);
|
|
832
|
+
}
|
|
833
|
+
// Eager variant `@~/pattern/flags` — a RegExp matcher flagged
|
|
834
|
+
// `eager$`, which opts out of the lexer's tcol gating. Serialized
|
|
835
|
+
// grammars use this for match tokens that must fire even when the
|
|
836
|
+
// active rule's token column is narrower (e.g. ABNF case-
|
|
837
|
+
// insensitive literals). Placed after `@/.../` so a `@~/` string
|
|
838
|
+
// (char[1] === '~') cannot be captured by that branch.
|
|
839
|
+
const me = obj.match(/^@~\/(.*)\/([\w]*)$/);
|
|
840
|
+
if (me) {
|
|
841
|
+
const re = new RegExp(me[1], me[2]);
|
|
842
|
+
re.eager$ = true;
|
|
843
|
+
return re;
|
|
844
|
+
}
|
|
845
|
+
if (ref) {
|
|
846
|
+
const fn = ref[obj];
|
|
847
|
+
if ('function' === typeof fn) {
|
|
848
|
+
return fn;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return obj;
|
|
853
|
+
}
|
|
854
|
+
if (Array.isArray(obj)) {
|
|
855
|
+
return obj.map((item) => resolveFuncRefs(item, ref));
|
|
856
|
+
}
|
|
857
|
+
// Preserve non-plain objects (RegExp, Date, etc.) without recursion.
|
|
858
|
+
const ctor = obj.constructor;
|
|
859
|
+
if (ctor && 'Object' !== ctor.name) {
|
|
860
|
+
return obj;
|
|
861
|
+
}
|
|
862
|
+
const out = {};
|
|
863
|
+
for (const key of Object.keys(obj)) {
|
|
864
|
+
out[key] = resolveFuncRefs(obj[key], ref);
|
|
865
|
+
}
|
|
866
|
+
return out;
|
|
867
|
+
}
|
|
868
|
+
//# sourceMappingURL=utility.js.map
|