@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.
@@ -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