@halleyassist/rule-parser 1.0.18 → 1.0.19

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,3571 @@
1
+ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.RuleParser = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
2
+ "use strict";
3
+ // https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_Form
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ /*
6
+ syntax ::= RULE_EOL* rule+
7
+ rule ::= " "* "<" rule-name ">" " "* "::=" firstExpression otherExpression* " "* RULE_EOL+ " "*
8
+ firstExpression ::= " "* list
9
+ otherExpression ::= " "* "|" " "* list
10
+ RULE_EOL ::= "\r" | "\n"
11
+ list ::= term " "* list | term
12
+ term ::= literal | "<" rule-name ">"
13
+ literal ::= '"' RULE_CHARACTER1* '"' | "'" RULE_CHARACTER2* "'"
14
+ RULE_CHARACTER ::= " " | RULE_LETTER | RULE_DIGIT | RULE_SYMBOL
15
+ RULE_LETTER ::= "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"
16
+ RULE_DIGIT ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
17
+ RULE_SYMBOL ::= "-" | "_" | "!" | "#" | "$" | "%" | "&" | "(" | ")" | "*" | "+" | "," | "-" | "." | "/" | ":" | ";" | "<" | "=" | ">" | "?" | "@" | "[" | "\" | "]" | "^" | "_" | "`" | "{" | "|" | "}" | "~"
18
+ RULE_CHARACTER1 ::= RULE_CHARACTER | "'"
19
+ RULE_CHARACTER2 ::= RULE_CHARACTER | '"'
20
+ rule-name ::= RULE_LETTER RULE_CHAR*
21
+ RULE_CHAR ::= RULE_LETTER | RULE_DIGIT | "_" | "-"
22
+ */
23
+ const SemanticHelpers_1 = require("../SemanticHelpers");
24
+ const Parser_1 = require("../Parser");
25
+ var BNF;
26
+ (function (BNF) {
27
+ BNF.RULES = [
28
+ {
29
+ name: 'syntax',
30
+ bnf: [['RULE_EOL*', 'rule+']]
31
+ },
32
+ {
33
+ name: 'rule',
34
+ bnf: [
35
+ [
36
+ '" "*',
37
+ '"<"',
38
+ 'rule-name',
39
+ '">"',
40
+ '" "*',
41
+ '"::="',
42
+ 'firstExpression',
43
+ 'otherExpression*',
44
+ '" "*',
45
+ 'RULE_EOL+',
46
+ '" "*'
47
+ ]
48
+ ]
49
+ },
50
+ {
51
+ name: 'firstExpression',
52
+ bnf: [['" "*', 'list']]
53
+ },
54
+ {
55
+ name: 'otherExpression',
56
+ bnf: [['" "*', '"|"', '" "*', 'list']]
57
+ },
58
+ {
59
+ name: 'RULE_EOL',
60
+ bnf: [['"\\r"'], ['"\\n"']]
61
+ },
62
+ {
63
+ name: 'list',
64
+ bnf: [['term', '" "*', 'list'], ['term']]
65
+ },
66
+ {
67
+ name: 'term',
68
+ bnf: [['literal'], ['"<"', 'rule-name', '">"']]
69
+ },
70
+ {
71
+ name: 'literal',
72
+ bnf: [[`'"'`, 'RULE_CHARACTER1*', `'"'`], [`"'"`, 'RULE_CHARACTER2*', `"'"`]]
73
+ },
74
+ {
75
+ name: 'RULE_CHARACTER',
76
+ bnf: [['" "'], ['RULE_LETTER'], ['RULE_DIGIT'], ['RULE_SYMBOL']]
77
+ },
78
+ {
79
+ name: 'RULE_LETTER',
80
+ bnf: [
81
+ ['"A"'],
82
+ ['"B"'],
83
+ ['"C"'],
84
+ ['"D"'],
85
+ ['"E"'],
86
+ ['"F"'],
87
+ ['"G"'],
88
+ ['"H"'],
89
+ ['"I"'],
90
+ ['"J"'],
91
+ ['"K"'],
92
+ ['"L"'],
93
+ ['"M"'],
94
+ ['"N"'],
95
+ ['"O"'],
96
+ ['"P"'],
97
+ ['"Q"'],
98
+ ['"R"'],
99
+ ['"S"'],
100
+ ['"T"'],
101
+ ['"U"'],
102
+ ['"V"'],
103
+ ['"W"'],
104
+ ['"X"'],
105
+ ['"Y"'],
106
+ ['"Z"'],
107
+ ['"a"'],
108
+ ['"b"'],
109
+ ['"c"'],
110
+ ['"d"'],
111
+ ['"e"'],
112
+ ['"f"'],
113
+ ['"g"'],
114
+ ['"h"'],
115
+ ['"i"'],
116
+ ['"j"'],
117
+ ['"k"'],
118
+ ['"l"'],
119
+ ['"m"'],
120
+ ['"n"'],
121
+ ['"o"'],
122
+ ['"p"'],
123
+ ['"q"'],
124
+ ['"r"'],
125
+ ['"s"'],
126
+ ['"t"'],
127
+ ['"u"'],
128
+ ['"v"'],
129
+ ['"w"'],
130
+ ['"x"'],
131
+ ['"y"'],
132
+ ['"z"']
133
+ ]
134
+ },
135
+ {
136
+ name: 'RULE_DIGIT',
137
+ bnf: [['"0"'], ['"1"'], ['"2"'], ['"3"'], ['"4"'], ['"5"'], ['"6"'], ['"7"'], ['"8"'], ['"9"']]
138
+ },
139
+ {
140
+ name: 'RULE_SYMBOL',
141
+ bnf: [
142
+ ['"-"'],
143
+ ['"_"'],
144
+ ['"!"'],
145
+ ['"#"'],
146
+ ['"$"'],
147
+ ['"%"'],
148
+ ['"&"'],
149
+ ['"("'],
150
+ ['")"'],
151
+ ['"*"'],
152
+ ['"+"'],
153
+ ['","'],
154
+ ['"-"'],
155
+ ['"."'],
156
+ ['"/"'],
157
+ ['":"'],
158
+ ['";"'],
159
+ ['"<"'],
160
+ ['"="'],
161
+ ['">"'],
162
+ ['"?"'],
163
+ ['"@"'],
164
+ ['"["'],
165
+ ['"\\"'],
166
+ ['"]"'],
167
+ ['"^"'],
168
+ ['"_"'],
169
+ ['"`"'],
170
+ ['"{"'],
171
+ ['"|"'],
172
+ ['"}"'],
173
+ ['"~"']
174
+ ]
175
+ },
176
+ {
177
+ name: 'RULE_CHARACTER1',
178
+ bnf: [['RULE_CHARACTER'], [`"'"`]]
179
+ },
180
+ {
181
+ name: 'RULE_CHARACTER2',
182
+ bnf: [['RULE_CHARACTER'], [`'"'`]]
183
+ },
184
+ {
185
+ name: 'rule-name',
186
+ bnf: [['RULE_LETTER', 'RULE_CHAR*']]
187
+ },
188
+ {
189
+ name: 'RULE_CHAR',
190
+ bnf: [['RULE_LETTER'], ['RULE_DIGIT'], ['"_"'], ['"-"']]
191
+ }
192
+ ];
193
+ BNF.defaultParser = new Parser_1.Parser(BNF.RULES, { debug: false });
194
+ function getAllTerms(expr) {
195
+ let terms = (0, SemanticHelpers_1.findChildrenByType)(expr, 'term').map(term => {
196
+ return (0, SemanticHelpers_1.findChildrenByType)(term, 'literal').concat((0, SemanticHelpers_1.findChildrenByType)(term, 'rule-name'))[0].text;
197
+ });
198
+ for (const exprItem of (0, SemanticHelpers_1.findChildrenByType)(expr, 'list')) {
199
+ terms = terms.concat(getAllTerms(exprItem));
200
+ }
201
+ return terms;
202
+ }
203
+ function getRules(source, parser = BNF.defaultParser) {
204
+ let ast = parser.getAST(source);
205
+ if (!ast)
206
+ throw new Error('Could not parse ' + source);
207
+ if (ast.errors && ast.errors.length) {
208
+ throw ast.errors[0];
209
+ }
210
+ let rules = (0, SemanticHelpers_1.findChildrenByType)(ast, 'rule');
211
+ let ret = rules.map((rule) => {
212
+ let name = (0, SemanticHelpers_1.findChildrenByType)(rule, 'rule-name')[0].text;
213
+ let expressions = (0, SemanticHelpers_1.findChildrenByType)(rule, 'firstExpression').concat((0, SemanticHelpers_1.findChildrenByType)(rule, 'otherExpression'));
214
+ let bnf = [];
215
+ for (const expr of expressions) {
216
+ bnf.push(getAllTerms(expr));
217
+ }
218
+ return {
219
+ name: name,
220
+ bnf
221
+ };
222
+ });
223
+ if (!ret.some(x => x.name == 'EOL')) {
224
+ ret.push({
225
+ name: 'EOL',
226
+ bnf: [['"\\r\\n"', '"\\r"', '"\\n"']]
227
+ });
228
+ }
229
+ return ret;
230
+ }
231
+ BNF.getRules = getRules;
232
+ function Transform(source, subParser = BNF.defaultParser) {
233
+ return getRules(source.join(''), subParser);
234
+ }
235
+ BNF.Transform = Transform;
236
+ class Parser extends Parser_1.Parser {
237
+ constructor(source, options) {
238
+ const subParser = options && options.debugRulesParser === true ? new Parser_1.Parser(BNF.RULES, { debug: true }) : BNF.defaultParser;
239
+ super(getRules(source, subParser), options);
240
+ this.source = source;
241
+ }
242
+ emitSource() {
243
+ return this.source;
244
+ }
245
+ }
246
+ BNF.Parser = Parser;
247
+ })(BNF || (BNF = {}));
248
+ exports.default = BNF;
249
+
250
+ },{"../Parser":5,"../SemanticHelpers":7}],2:[function(require,module,exports){
251
+ "use strict";
252
+ // https://www.w3.org/TR/REC-xml/#NT-Name
253
+ // http://www.bottlecaps.de/rr/ui
254
+ Object.defineProperty(exports, "__esModule", { value: true });
255
+ // Grammar ::= Production*
256
+ // Production ::= NCName '::=' Choice
257
+ // NCName ::= [http://www.w3.org/TR/xml-names/#NT-NCName]
258
+ // Choice ::= SequenceOrDifference ( '|' SequenceOrDifference )*
259
+ // SequenceOrDifference ::= (Item ( '-' Item | Item* ))?
260
+ // Item ::= Primary ( '?' | '*' | '+' )?
261
+ // Primary ::= NCName | StringLiteral | CharCode | CharClass | '(' Choice ')'
262
+ // StringLiteral ::= '"' [^"]* '"' | "'" [^']* "'"
263
+ // CharCode ::= '#x' [0-9a-fA-F]+
264
+ // CharClass ::= '[' '^'? ( RULE_Char | CharCode | CharRange | CharCodeRange )+ ']'
265
+ // RULE_Char ::= [http://www.w3.org/TR/xml#NT-RULE_Char]
266
+ // CharRange ::= RULE_Char '-' ( RULE_Char - ']' )
267
+ // CharCodeRange ::= CharCode '-' CharCode
268
+ // RULE_WHITESPACE ::= RULE_S | Comment
269
+ // RULE_S ::= #x9 | #xA | #xD | #x20
270
+ // Comment ::= '/*' ( [^*] | '*'+ [^*/] )* '*'* '*/'
271
+ const TokenError_1 = require("../TokenError");
272
+ const Parser_1 = require("../Parser");
273
+ var BNF;
274
+ (function (BNF) {
275
+ BNF.RULES = [
276
+ {
277
+ name: 'Grammar',
278
+ bnf: [['RULE_S*', 'Attributes?', 'RULE_S*', '%Atomic*', 'EOF']]
279
+ },
280
+ {
281
+ name: '%Atomic',
282
+ bnf: [['Production', 'RULE_S*']],
283
+ fragment: true
284
+ },
285
+ {
286
+ name: 'Production',
287
+ bnf: [
288
+ [
289
+ 'NCName',
290
+ 'RULE_S*',
291
+ '"::="',
292
+ 'RULE_WHITESPACE*',
293
+ '%Choice',
294
+ 'RULE_WHITESPACE*',
295
+ 'Attributes?',
296
+ 'RULE_EOL+',
297
+ 'RULE_S*'
298
+ ]
299
+ ]
300
+ },
301
+ {
302
+ name: 'NCName',
303
+ bnf: [[/[a-zA-Z][a-zA-Z_0-9]*/]]
304
+ },
305
+ {
306
+ name: 'Attributes',
307
+ bnf: [['"{"', 'Attribute', '%Attributes*', 'RULE_S*', '"}"']]
308
+ },
309
+ {
310
+ name: '%Attributes',
311
+ bnf: [['RULE_S*', '","', 'Attribute']],
312
+ fragment: true
313
+ },
314
+ {
315
+ name: 'Attribute',
316
+ bnf: [['RULE_S*', 'NCName', 'RULE_WHITESPACE*', '"="', 'RULE_WHITESPACE*', 'AttributeValue']]
317
+ },
318
+ {
319
+ name: 'AttributeValue',
320
+ bnf: [['NCName'], [/[1-9][0-9]*/]]
321
+ },
322
+ {
323
+ name: '%Choice',
324
+ bnf: [['SequenceOrDifference', '%_Choice_1*']],
325
+ fragment: true
326
+ },
327
+ {
328
+ name: '%_Choice_1',
329
+ bnf: [['RULE_S*', '"|"', 'RULE_S*', 'SequenceOrDifference']],
330
+ fragment: true
331
+ },
332
+ {
333
+ name: 'SequenceOrDifference',
334
+ bnf: [['%Item', 'RULE_WHITESPACE*', '%_Item_1?']]
335
+ },
336
+ {
337
+ name: '%_Item_1',
338
+ bnf: [['Minus', '%Item'], ['%Item*']],
339
+ fragment: true
340
+ },
341
+ {
342
+ name: 'Minus',
343
+ bnf: [['"-"']]
344
+ },
345
+ {
346
+ name: '%Item',
347
+ bnf: [['RULE_WHITESPACE*', 'PrimaryPreDecoration?', '%Primary', 'PrimaryDecoration?']],
348
+ fragment: true
349
+ },
350
+ {
351
+ name: 'PrimaryDecoration',
352
+ bnf: [['"?"'], ['"*"'], ['"+"']]
353
+ },
354
+ {
355
+ name: 'PrimaryPreDecoration',
356
+ bnf: [['"&"'], ['"!"'], ['"~"']]
357
+ },
358
+ {
359
+ name: '%Primary',
360
+ bnf: [['NCName'], ['StringLiteral'], ['CharCode'], ['CharClass'], ['SubItem']],
361
+ fragment: true
362
+ },
363
+ {
364
+ name: 'SubItem',
365
+ bnf: [['"("', 'RULE_S*', '%Choice', 'RULE_S*', '")"']]
366
+ },
367
+ {
368
+ name: 'StringLiteral',
369
+ bnf: [[`'"'`, /[^"]*/, `'"'`], [`"'"`, /[^']*/, `"'"`]]
370
+ },
371
+ {
372
+ name: 'CharCode',
373
+ bnf: [['"#x"', /[0-9a-zA-Z]+/]]
374
+ },
375
+ {
376
+ name: 'CharClass',
377
+ bnf: [["'['", "'^'?", '%RULE_CharClass_1+', '"]"']]
378
+ },
379
+ {
380
+ name: '%RULE_CharClass_1',
381
+ bnf: [['CharCodeRange'], ['CharRange'], ['CharCode'], ['RULE_Char']],
382
+ fragment: true
383
+ },
384
+ {
385
+ name: 'RULE_Char',
386
+ bnf: [[/\x09/], [/\x0A/], [/\x0D/], [/[\x20-\x5c]/], [/[\x5e-\uD7FF]/], [/[\uE000-\uFFFD]/]]
387
+ },
388
+ {
389
+ name: 'CharRange',
390
+ bnf: [['RULE_Char', '"-"', 'RULE_Char']]
391
+ },
392
+ {
393
+ name: 'CharCodeRange',
394
+ bnf: [['CharCode', '"-"', 'CharCode']]
395
+ },
396
+ {
397
+ name: 'RULE_WHITESPACE',
398
+ bnf: [['%RULE_WHITESPACE_CHAR*'], ['Comment', 'RULE_WHITESPACE*']]
399
+ },
400
+ {
401
+ name: 'RULE_S',
402
+ bnf: [['RULE_WHITESPACE', 'RULE_S*'], ['RULE_EOL', 'RULE_S*']]
403
+ },
404
+ {
405
+ name: '%RULE_WHITESPACE_CHAR',
406
+ bnf: [[/\x09/], [/\x20/]],
407
+ fragment: true
408
+ },
409
+ {
410
+ name: 'Comment',
411
+ bnf: [['"/*"', '%RULE_Comment_Body*', '"*/"']]
412
+ },
413
+ {
414
+ name: '%RULE_Comment_Body',
415
+ bnf: [[/[^*]/], ['"*"+', /[^/]*/]],
416
+ fragment: true
417
+ },
418
+ {
419
+ name: 'RULE_EOL',
420
+ bnf: [[/\x0D/, /\x0A/], [/\x0A/], [/\x0D/]]
421
+ },
422
+ {
423
+ name: 'Link',
424
+ bnf: [["'['", 'Url', "']'"]]
425
+ },
426
+ {
427
+ name: 'Url',
428
+ bnf: [[/[^\x5D:/?#]/, '"://"', /[^\x5D#]+/, '%Url1?']]
429
+ },
430
+ {
431
+ name: '%Url1',
432
+ bnf: [['"#"', 'NCName']],
433
+ fragment: true
434
+ }
435
+ ];
436
+ BNF.defaultParser = new Parser_1.Parser(BNF.RULES, { debug: false });
437
+ const preDecorationRE = /^(!|&)/;
438
+ const decorationRE = /(\?|\+|\*)$/;
439
+ const subExpressionRE = /^%/;
440
+ function getBNFRule(name, parser) {
441
+ if (typeof name == 'string') {
442
+ let decoration = decorationRE.exec(name);
443
+ let preDecoration = preDecorationRE.exec(name);
444
+ let preDecorationText = preDecoration ? preDecoration[0] : '';
445
+ let decorationText = decoration ? decoration[0] + ' ' : '';
446
+ let subexpression = subExpressionRE.test(name);
447
+ if (subexpression) {
448
+ let lonely = isLonelyRule(name, parser);
449
+ if (lonely)
450
+ return preDecorationText + getBNFBody(name, parser) + decorationText;
451
+ return preDecorationText + '(' + getBNFBody(name, parser) + ')' + decorationText;
452
+ }
453
+ return name.replace(preDecorationRE, preDecorationText);
454
+ }
455
+ else {
456
+ return name.source
457
+ .replace(/\\(?:x|u)([a-zA-Z0-9]+)/g, '#x$1')
458
+ .replace(/\[\\(?:x|u)([a-zA-Z0-9]+)-\\(?:x|u)([a-zA-Z0-9]+)\]/g, '[#x$1-#x$2]');
459
+ }
460
+ }
461
+ /// Returns true if the rule is a string literal or regular expression without a descendant tree
462
+ function isLonelyRule(name, parser) {
463
+ let rule = (0, Parser_1.findRuleByName)(name, parser);
464
+ return (rule &&
465
+ rule.bnf.length == 1 &&
466
+ rule.bnf[0].length == 1 &&
467
+ (rule.bnf[0][0] instanceof RegExp || rule.bnf[0][0][0] == '"' || rule.bnf[0][0][0] == "'"));
468
+ }
469
+ function getBNFChoice(rules, parser) {
470
+ return rules.map(x => getBNFRule(x, parser)).join(' ');
471
+ }
472
+ function getBNFBody(name, parser) {
473
+ let rule = (0, Parser_1.findRuleByName)(name, parser);
474
+ if (rule)
475
+ return rule.bnf.map(x => getBNFChoice(x, parser)).join(' | ');
476
+ return 'RULE_NOT_FOUND {' + name + '}';
477
+ }
478
+ function emit(parser) {
479
+ let acumulator = [];
480
+ for (const l of parser.grammarRules) {
481
+ if (!/^%/.test(l.name)) {
482
+ let recover = l.recover ? ' { recoverUntil=' + l.recover + ' }' : '';
483
+ acumulator.push(l.name + ' ::= ' + getBNFBody(l.name, parser) + recover);
484
+ }
485
+ }
486
+ return acumulator.join('\n');
487
+ }
488
+ BNF.emit = emit;
489
+ function restar(total, resta) {
490
+ console.log('reberia restar ' + resta + ' a ' + total);
491
+ throw new Error('Difference not supported yet');
492
+ }
493
+ function convertRegex(txt) {
494
+ return new RegExp(txt
495
+ .replace(/#x([a-zA-Z0-9]{4})/g, '\\u$1')
496
+ .replace(/#x([a-zA-Z0-9]{3})/g, '\\u0$1')
497
+ .replace(/#x([a-zA-Z0-9]{2})/g, '\\x$1')
498
+ .replace(/#x([a-zA-Z0-9]{1})/g, '\\x0$1'));
499
+ }
500
+ function getSubItems(tmpRules, seq, parentName, optionIndex, parentAttributes, isSingleSequence = false) {
501
+ let anterior = null;
502
+ let bnfSeq = [];
503
+ const children = seq.children;
504
+ let subitemIndex = 0; // Track subitems within this sequence
505
+ let itemPosition = 0; // Track all items for position-based naming
506
+ for (let i = 0; i < children.length; i++) {
507
+ const x = children[i];
508
+ if (x.type == 'Minus') {
509
+ restar(anterior, x);
510
+ }
511
+ let decoration = children[i + 1];
512
+ decoration = (decoration && decoration.type == 'PrimaryDecoration' && decoration.text) || '';
513
+ let preDecoration = '';
514
+ if (anterior && anterior.type == 'PrimaryPreDecoration') {
515
+ preDecoration = anterior.text;
516
+ }
517
+ let pinned = preDecoration == '~' ? 1 : undefined;
518
+ if (pinned) {
519
+ preDecoration = '';
520
+ }
521
+ switch (x.type) {
522
+ case 'SubItem':
523
+ subitemIndex++;
524
+ itemPosition++; // Increment position for SubItems
525
+ let name;
526
+ // Build the fragment name by appending to parentName
527
+ const prefix = parentName.startsWith('%') ? '' : '%';
528
+ if (isSingleSequence) {
529
+ // Single sequence: use position-based naming
530
+ name = prefix + parentName + '[' + itemPosition + ']';
531
+ }
532
+ else {
533
+ // Multiple sequences: use option-based naming
534
+ if (subitemIndex === 1) {
535
+ name = prefix + parentName + '[' + optionIndex + ']';
536
+ }
537
+ else {
538
+ name = prefix + parentName + '[' + optionIndex + '][' + subitemIndex + ']';
539
+ }
540
+ }
541
+ createRule(tmpRules, x, name, parentAttributes);
542
+ bnfSeq.push(preDecoration + name + decoration);
543
+ break;
544
+ case 'NCName':
545
+ itemPosition++; // Increment position for NCNames
546
+ bnfSeq.push(preDecoration + x.text + decoration);
547
+ break;
548
+ case 'StringLiteral':
549
+ itemPosition++; // Increment position for StringLiterals
550
+ if (decoration || preDecoration || !/^['"/()a-zA-Z0-9&_.:=,+*\-\^\\]+$/.test(x.text)) {
551
+ bnfSeq.push(preDecoration + x.text + decoration);
552
+ }
553
+ else {
554
+ for (const c of x.text.slice(1, -1)) {
555
+ if (parentAttributes && parentAttributes["ignoreCase"] == "true" && /[a-zA-Z]/.test(c)) {
556
+ bnfSeq.push(new RegExp("[" + c.toUpperCase() + c.toLowerCase() + "]"));
557
+ }
558
+ else {
559
+ bnfSeq.push(new RegExp((0, Parser_1.escapeRegExp)(c)));
560
+ }
561
+ }
562
+ }
563
+ break;
564
+ case 'CharCode':
565
+ case 'CharClass':
566
+ itemPosition++; // Increment position for CharCode/CharClass
567
+ if (decoration || preDecoration) {
568
+ subitemIndex++;
569
+ let name;
570
+ // Build the fragment name by appending to parentName
571
+ const prefix = parentName.startsWith('%') ? '' : '%';
572
+ if (isSingleSequence) {
573
+ // Single sequence: use position-based naming
574
+ name = prefix + parentName + '[' + itemPosition + ']';
575
+ }
576
+ else {
577
+ // Multiple sequences: use option-based naming
578
+ if (subitemIndex === 1) {
579
+ name = prefix + parentName + '[' + optionIndex + ']';
580
+ }
581
+ else {
582
+ name = prefix + parentName + '[' + optionIndex + '][' + subitemIndex + ']';
583
+ }
584
+ }
585
+ let newRule = {
586
+ name,
587
+ bnf: [[convertRegex(x.text)]],
588
+ pinned
589
+ };
590
+ tmpRules.push(newRule);
591
+ bnfSeq.push(preDecoration + newRule.name + decoration);
592
+ }
593
+ else {
594
+ bnfSeq.push(convertRegex(x.text));
595
+ }
596
+ break;
597
+ case 'PrimaryPreDecoration':
598
+ case 'PrimaryDecoration':
599
+ break;
600
+ default:
601
+ throw new Error(' HOW SHOULD I PARSE THIS? ' + x.type + ' -> ' + JSON.stringify(x.text));
602
+ }
603
+ anterior = x;
604
+ }
605
+ return bnfSeq;
606
+ }
607
+ function createRule(tmpRules, token, name, parentAttributes = undefined) {
608
+ let attrNode = token.children.filter(x => x.type == 'Attributes')[0];
609
+ let attributes = {};
610
+ if (attrNode) {
611
+ for (const x of attrNode.children) {
612
+ let attrName = x.children.filter(x => x.type == 'NCName')[0].text;
613
+ if (attrName in attributes) {
614
+ throw new TokenError_1.TokenError('Duplicated attribute ' + attrName, x);
615
+ }
616
+ else {
617
+ attributes[attrName] = x.children.filter(x => x.type == 'AttributeValue')[0].text;
618
+ }
619
+ }
620
+ }
621
+ let sequences = token.children.filter(x => x.type == 'SequenceOrDifference');
622
+ // Determine if this rule has a single sequence (no alternatives)
623
+ const isSingleSequence = sequences.length === 1;
624
+ let bnf = sequences.map((s, optionIndex) => getSubItems(tmpRules, s, name, optionIndex + 1, parentAttributes ? parentAttributes : attributes, isSingleSequence));
625
+ let rule = {
626
+ name,
627
+ bnf
628
+ };
629
+ if (name.indexOf('%') == 0)
630
+ rule.fragment = true;
631
+ if (attributes['recoverUntil']) {
632
+ rule.recover = attributes['recoverUntil'];
633
+ if (rule.bnf.length > 1)
634
+ throw new TokenError_1.TokenError('only one-option productions are suitable for error recovering', token);
635
+ }
636
+ if ('pin' in attributes) {
637
+ let num = parseInt(attributes['pin']);
638
+ if (!isNaN(num)) {
639
+ rule.pinned = num;
640
+ }
641
+ if (rule.bnf.length > 1)
642
+ throw new TokenError_1.TokenError('only one-option productions are suitable for pinning', token);
643
+ }
644
+ if ('ws' in attributes) {
645
+ rule.implicitWs = attributes['ws'] != 'explicit';
646
+ }
647
+ else {
648
+ rule.implicitWs = null;
649
+ }
650
+ rule.fragment = rule.fragment || attributes['fragment'] == 'true';
651
+ rule.simplifyWhenOneChildren = attributes['simplifyWhenOneChildren'] == 'true';
652
+ tmpRules.push(rule);
653
+ }
654
+ function getRules(source, parser = BNF.defaultParser) {
655
+ let ast = parser.getAST(source);
656
+ if (!ast)
657
+ throw new Error('Could not parse ' + source);
658
+ if (ast.errors && ast.errors.length) {
659
+ throw ast.errors[0];
660
+ }
661
+ let implicitWs = null;
662
+ let attrNode = ast.children.filter(x => x.type == 'Attributes')[0];
663
+ let attributes = {};
664
+ if (attrNode) {
665
+ for (const x of attrNode.children) {
666
+ let attrName = x.children.filter(x => x.type == 'NCName')[0].text;
667
+ if (attrName in attributes) {
668
+ throw new TokenError_1.TokenError('Duplicated attribute ' + attrName, x);
669
+ }
670
+ else {
671
+ attributes[attrName] = x.children.filter(x => x.type == 'AttributeValue')[0].text;
672
+ }
673
+ }
674
+ }
675
+ implicitWs = attributes['ws'] == 'implicit';
676
+ let tmpRules = [];
677
+ ast.children.filter(x => x.type == 'Production').map((x) => {
678
+ let name = x.children.filter(x => x.type == 'NCName')[0].text;
679
+ createRule(tmpRules, x, name);
680
+ });
681
+ for (const rule of tmpRules) {
682
+ if (rule.implicitWs === null)
683
+ rule.implicitWs = implicitWs;
684
+ }
685
+ return tmpRules;
686
+ }
687
+ BNF.getRules = getRules;
688
+ function Transform(source, subParser = BNF.defaultParser) {
689
+ return getRules(source.join(''), subParser);
690
+ }
691
+ BNF.Transform = Transform;
692
+ class Parser extends Parser_1.Parser {
693
+ constructor(source, options) {
694
+ const subParser = options && options.debugRulesParser === true ? new Parser_1.Parser(BNF.RULES, { debug: true }) : BNF.defaultParser;
695
+ super(getRules(source, subParser), options);
696
+ }
697
+ emitSource() {
698
+ return emit(this);
699
+ }
700
+ }
701
+ BNF.Parser = Parser;
702
+ })(BNF || (BNF = {}));
703
+ exports.default = BNF;
704
+
705
+ },{"../Parser":5,"../TokenError":8}],3:[function(require,module,exports){
706
+ "use strict";
707
+ // https://www.w3.org/TR/REC-xml/#NT-Name
708
+ // http://www.bottlecaps.de/rr/ui
709
+ Object.defineProperty(exports, "__esModule", { value: true });
710
+ // Grammar ::= Production*
711
+ // Production ::= NCName '::=' Choice
712
+ // NCName ::= [http://www.w3.org/TR/xml-names/#NT-NCName]
713
+ // Choice ::= SequenceOrDifference ( '|' SequenceOrDifference )*
714
+ // SequenceOrDifference ::= (Item ( '-' Item | Item* ))?
715
+ // Item ::= Primary ( '?' | '*' | '+' )?
716
+ // Primary ::= NCName | StringLiteral | CharCode | CharClass | '(' Choice ')'
717
+ // StringLiteral ::= '"' [^"]* '"' | "'" [^']* "'"
718
+ // CharCode ::= '#x' [0-9a-fA-F]+
719
+ // CharClass ::= '[' '^'? ( RULE_Char | CharCode | CharRange | CharCodeRange )+ ']'
720
+ // RULE_Char ::= [http://www.w3.org/TR/xml#NT-RULE_Char]
721
+ // CharRange ::= RULE_Char '-' ( RULE_Char - ']' )
722
+ // CharCodeRange ::= CharCode '-' CharCode
723
+ // RULE_WHITESPACE ::= RULE_S | Comment
724
+ // RULE_S ::= #x9 | #xA | #xD | #x20
725
+ // Comment ::= '/*' ( [^*] | '*'+ [^*/] )* '*'* '*/'
726
+ const Parser_1 = require("../Parser");
727
+ var BNF;
728
+ (function (BNF) {
729
+ BNF.RULES = [
730
+ {
731
+ name: 'Grammar',
732
+ bnf: [['RULE_S*', '%Atomic*', 'EOF']]
733
+ },
734
+ {
735
+ name: '%Atomic',
736
+ bnf: [['Production', 'RULE_S*']],
737
+ fragment: true
738
+ },
739
+ {
740
+ name: 'Production',
741
+ bnf: [['NCName', 'RULE_S*', 'ProductionOperator', 'RULE_WHITESPACE*', 'Choice', 'RULE_WHITESPACE*', 'RULE_EOL+', 'RULE_S*']]
742
+ },
743
+ {
744
+ name: 'ProductionOperator',
745
+ bnf: [['"::="'], ['"||="']]
746
+ },
747
+ {
748
+ name: 'NCName',
749
+ bnf: [[/[a-zA-Z][a-zA-Z_0-9]*/]]
750
+ },
751
+ {
752
+ name: 'Choice',
753
+ bnf: [['SequenceOrDifference', '%_Choice_1*']],
754
+ fragment: true
755
+ },
756
+ {
757
+ name: '%_Choice_1',
758
+ bnf: [['RULE_WHITESPACE*', '"|"', 'RULE_WHITESPACE*', 'SequenceOrDifference']],
759
+ fragment: true
760
+ },
761
+ {
762
+ name: 'SequenceOrDifference',
763
+ bnf: [['Item', 'RULE_WHITESPACE*', '%_Item_1?']]
764
+ },
765
+ {
766
+ name: '%_Item_1',
767
+ bnf: [['Minus', 'Item'], ['Item*']],
768
+ fragment: true
769
+ },
770
+ {
771
+ name: 'Minus',
772
+ bnf: [['"-"']]
773
+ },
774
+ {
775
+ name: 'Item',
776
+ bnf: [['RULE_WHITESPACE*', '%Primary', 'PrimaryDecoration?']],
777
+ fragment: true
778
+ },
779
+ {
780
+ name: 'PrimaryDecoration',
781
+ bnf: [['"?"'], ['"*"'], ['"+"']]
782
+ },
783
+ {
784
+ name: 'DecorationName',
785
+ bnf: [['"ebnf://"', /[^\x5D#]+/]]
786
+ },
787
+ {
788
+ name: '%Primary',
789
+ bnf: [['NCName'], ['StringLiteral'], ['CharCode'], ['CharClass'], ['SubItem']],
790
+ fragment: true
791
+ },
792
+ {
793
+ name: 'SubItem',
794
+ bnf: [['"("', 'RULE_WHITESPACE*', 'Choice', 'RULE_WHITESPACE*', '")"']]
795
+ },
796
+ {
797
+ name: 'StringLiteral',
798
+ bnf: [[`'"'`, /[^"]*/, `'"'`], [`"'"`, /[^']*/, `"'"`]],
799
+ pinned: 1
800
+ },
801
+ {
802
+ name: 'CharCode',
803
+ bnf: [['"#x"', /[0-9a-zA-Z]+/]]
804
+ },
805
+ {
806
+ name: 'CharClass',
807
+ bnf: [["'['", "'^'?", '%RULE_CharClass_1+', '"]"']]
808
+ },
809
+ {
810
+ name: '%RULE_CharClass_1',
811
+ bnf: [['CharCodeRange'], ['CharRange'], ['CharCode'], ['RULE_Char']],
812
+ fragment: true
813
+ },
814
+ {
815
+ name: 'RULE_Char',
816
+ bnf: [[/\x09/], [/\x0A/], [/\x0D/], [/[\x20-\x5c]/], [/[\x5e-\uD7FF]/], [/[\uE000-\uFFFD]/]]
817
+ },
818
+ {
819
+ name: 'CharRange',
820
+ bnf: [['RULE_Char', '"-"', 'RULE_Char']]
821
+ },
822
+ {
823
+ name: 'CharCodeRange',
824
+ bnf: [['CharCode', '"-"', 'CharCode']]
825
+ },
826
+ {
827
+ name: 'RULE_WHITESPACE',
828
+ bnf: [['%RULE_WHITESPACE_CHAR*'], ['Comment', 'RULE_WHITESPACE*']]
829
+ },
830
+ {
831
+ name: 'RULE_S',
832
+ bnf: [['RULE_WHITESPACE', 'RULE_S*'], ['RULE_EOL', 'RULE_S*']]
833
+ },
834
+ {
835
+ name: '%RULE_WHITESPACE_CHAR',
836
+ bnf: [[/\x09/], [/\x20/]],
837
+ fragment: true
838
+ },
839
+ {
840
+ name: 'Comment',
841
+ bnf: [['"/*"', '%RULE_Comment_Body*', '"*/"']]
842
+ },
843
+ {
844
+ name: '%RULE_Comment_Body',
845
+ bnf: [['!"*/"', /[^*]/]],
846
+ fragment: true
847
+ },
848
+ {
849
+ name: 'RULE_EOL',
850
+ bnf: [[/\x0D/, /\x0A/], [/\x0A/], [/\x0D/]]
851
+ },
852
+ {
853
+ name: 'Link',
854
+ bnf: [["'['", 'Url', "']'"]]
855
+ },
856
+ {
857
+ name: 'Url',
858
+ bnf: [[/[^\x5D:/?#]/, '"://"', /[^\x5D#]+/, '%Url1?']]
859
+ },
860
+ {
861
+ name: '%Url1',
862
+ bnf: [['"#"', 'NCName']],
863
+ fragment: true
864
+ }
865
+ ];
866
+ BNF.defaultParser = new Parser_1.Parser(BNF.RULES, { debug: false });
867
+ const preDecorationRE = /^(!|&)/;
868
+ const decorationRE = /(\?|\+|\*)$/;
869
+ const subExpressionRE = /^%/;
870
+ function getBNFRule(name, parser) {
871
+ if (typeof name == 'string') {
872
+ if (preDecorationRE.test(name))
873
+ return '';
874
+ let subexpression = subExpressionRE.test(name);
875
+ if (subexpression) {
876
+ let decoration = decorationRE.exec(name);
877
+ let decorationText = decoration ? decoration[0] + ' ' : '';
878
+ let lonely = isLonelyRule(name, parser);
879
+ if (lonely)
880
+ return getBNFBody(name, parser) + decorationText;
881
+ return '(' + getBNFBody(name, parser) + ')' + decorationText;
882
+ }
883
+ return name;
884
+ }
885
+ else {
886
+ return name.source
887
+ .replace(/\\(?:x|u)([a-zA-Z0-9]+)/g, '#x$1')
888
+ .replace(/\[\\(?:x|u)([a-zA-Z0-9]+)-\\(?:x|u)([a-zA-Z0-9]+)\]/g, '[#x$1-#x$2]');
889
+ }
890
+ }
891
+ /// Returns true if the rule is a string literal or regular expression without a descendant tree
892
+ function isLonelyRule(name, parser) {
893
+ let rule = (0, Parser_1.findRuleByName)(name, parser);
894
+ return (rule &&
895
+ rule.bnf.length == 1 &&
896
+ rule.bnf[0].length == 1 &&
897
+ (rule.bnf[0][0] instanceof RegExp || rule.bnf[0][0][0] == '"' || rule.bnf[0][0][0] == "'"));
898
+ }
899
+ function getBNFChoice(rules, parser) {
900
+ return rules.map(x => getBNFRule(x, parser)).join(' ');
901
+ }
902
+ function getBNFBody(name, parser) {
903
+ let rule = (0, Parser_1.findRuleByName)(name, parser);
904
+ if (rule)
905
+ return rule.bnf.map(x => getBNFChoice(x, parser)).join(' | ');
906
+ return 'RULE_NOT_FOUND {' + name + '}';
907
+ }
908
+ function emit(parser) {
909
+ let acumulator = [];
910
+ for (const l of parser.grammarRules) {
911
+ if (!/^%/.test(l.name)) {
912
+ let recover = l.recover ? ' /* { recoverUntil=' + l.recover + ' } */' : '';
913
+ acumulator.push(l.name + ' ::= ' + getBNFBody(l.name, parser) + recover);
914
+ }
915
+ }
916
+ return acumulator.join('\n');
917
+ }
918
+ BNF.emit = emit;
919
+ function restar(total, resta) {
920
+ console.log('reberia restar ' + resta + ' a ' + total);
921
+ throw new Error('Difference not supported yet');
922
+ }
923
+ function convertRegex(txt, caseInsensitive = false) {
924
+ const pattern = txt
925
+ .replace(/#x([a-zA-Z0-9]{4})/g, '\\u$1')
926
+ .replace(/#x([a-zA-Z0-9]{3})/g, '\\u0$1')
927
+ .replace(/#x([a-zA-Z0-9]{2})/g, '\\x$1')
928
+ .replace(/#x([a-zA-Z0-9]{1})/g, '\\x0$1');
929
+ return new RegExp(pattern, caseInsensitive ? 'i' : '');
930
+ }
931
+ function getSubItems(tmpRules, seq, parentName, optionIndex, caseInsensitive = false, isSingleSequence = false) {
932
+ let anterior = null;
933
+ let bnfSeq = [];
934
+ const children = seq.children;
935
+ let subitemIndex = 0; // Track subitems within this sequence
936
+ let itemPosition = 0; // Track all items (including literals, NCNames, SubItems) for position-based naming
937
+ for (let i = 0; i < children.length; i++) {
938
+ const x = children[i];
939
+ if (x.type == 'Minus') {
940
+ restar(anterior, x);
941
+ }
942
+ let decoration = children[i + 1];
943
+ decoration = (decoration && decoration.type == 'PrimaryDecoration' && decoration.text) || '';
944
+ let preDecoration = '';
945
+ switch (x.type) {
946
+ case 'SubItem':
947
+ subitemIndex++;
948
+ itemPosition++; // Increment position for SubItems
949
+ let name;
950
+ // Build the fragment name by appending to parentName
951
+ const prefix = parentName.startsWith('%') ? '' : '%';
952
+ if (isSingleSequence) {
953
+ // Single sequence: use position-based naming
954
+ name = prefix + parentName + '[' + itemPosition + ']';
955
+ }
956
+ else {
957
+ // Multiple sequences: use option-based naming
958
+ if (subitemIndex === 1) {
959
+ name = prefix + parentName + '[' + optionIndex + ']';
960
+ }
961
+ else {
962
+ name = prefix + parentName + '[' + optionIndex + '][' + subitemIndex + ']';
963
+ }
964
+ }
965
+ createRule(tmpRules, x, name, caseInsensitive);
966
+ bnfSeq.push(preDecoration + name + decoration);
967
+ break;
968
+ case 'NCName':
969
+ itemPosition++; // Increment position for NCNames
970
+ bnfSeq.push(preDecoration + x.text + decoration);
971
+ break;
972
+ case 'StringLiteral':
973
+ itemPosition++; // Increment position for StringLiterals
974
+ if (caseInsensitive) {
975
+ // For case insensitive string literals, convert each character to a case-insensitive regex
976
+ const literalText = x.text.slice(1, -1); // Remove quotes
977
+ for (const c of literalText) {
978
+ if (/[a-zA-Z]/.test(c)) {
979
+ bnfSeq.push(new RegExp('[' + c.toUpperCase() + c.toLowerCase() + ']'));
980
+ }
981
+ else {
982
+ bnfSeq.push(new RegExp((0, Parser_1.escapeRegExp)(c)));
983
+ }
984
+ }
985
+ }
986
+ else {
987
+ bnfSeq.push(preDecoration + x.text + decoration);
988
+ }
989
+ break;
990
+ case 'CharCode':
991
+ case 'CharClass':
992
+ itemPosition++; // Increment position for CharCode/CharClass
993
+ if (decoration || preDecoration) {
994
+ subitemIndex++;
995
+ let name;
996
+ // Build the fragment name by appending to parentName
997
+ const prefix = parentName.startsWith('%') ? '' : '%';
998
+ if (isSingleSequence) {
999
+ // Single sequence: use position-based naming
1000
+ name = prefix + parentName + '[' + itemPosition + ']';
1001
+ }
1002
+ else {
1003
+ // Multiple sequences: use option-based naming
1004
+ if (subitemIndex === 1) {
1005
+ name = prefix + parentName + '[' + optionIndex + ']';
1006
+ }
1007
+ else {
1008
+ name = prefix + parentName + '[' + optionIndex + '][' + subitemIndex + ']';
1009
+ }
1010
+ }
1011
+ let newRule = {
1012
+ name,
1013
+ bnf: [[convertRegex(x.text, caseInsensitive)]]
1014
+ };
1015
+ tmpRules.push(newRule);
1016
+ bnfSeq.push(preDecoration + newRule.name + decoration);
1017
+ }
1018
+ else {
1019
+ bnfSeq.push(convertRegex(x.text, caseInsensitive));
1020
+ }
1021
+ break;
1022
+ case 'PrimaryDecoration':
1023
+ break;
1024
+ default:
1025
+ throw new Error(' HOW SHOULD I PARSE THIS? ' + x.type + ' -> ' + JSON.stringify(x.text));
1026
+ }
1027
+ anterior = x;
1028
+ }
1029
+ return bnfSeq;
1030
+ }
1031
+ function createRule(tmpRules, token, name, caseInsensitive = false) {
1032
+ // Check if this production uses the case insensitive operator ||=
1033
+ const operatorNode = token.children.find(x => x.type == 'ProductionOperator');
1034
+ const isCaseInsensitive = caseInsensitive || (operatorNode && operatorNode.text === '||=');
1035
+ let sequences = token.children.filter(x => x.type == 'SequenceOrDifference');
1036
+ // Determine if this rule has a single sequence (no alternatives)
1037
+ const isSingleSequence = sequences.length === 1;
1038
+ let bnf = sequences.map((s, optionIndex) => getSubItems(tmpRules, s, name, optionIndex + 1, isCaseInsensitive, isSingleSequence));
1039
+ let rule = {
1040
+ name,
1041
+ bnf
1042
+ };
1043
+ let recover = null;
1044
+ for (const x of bnf) {
1045
+ recover = recover || x['recover'];
1046
+ delete x['recover'];
1047
+ }
1048
+ if (name.indexOf('%') == 0)
1049
+ rule.fragment = true;
1050
+ if (recover)
1051
+ rule.recover = recover;
1052
+ tmpRules.push(rule);
1053
+ }
1054
+ function getRules(source, parser = BNF.defaultParser) {
1055
+ let ast = parser.getAST(source);
1056
+ if (!ast)
1057
+ throw new Error('Could not parse ' + source);
1058
+ if (ast.errors && ast.errors.length) {
1059
+ throw ast.errors[0];
1060
+ }
1061
+ let tmpRules = [];
1062
+ ast.children.filter(x => x.type == 'Production').map((x) => {
1063
+ let name = x.children.filter(x => x.type == 'NCName')[0].text;
1064
+ createRule(tmpRules, x, name);
1065
+ });
1066
+ return tmpRules;
1067
+ }
1068
+ BNF.getRules = getRules;
1069
+ function Transform(source, subParser = BNF.defaultParser) {
1070
+ return getRules(source.join(''), subParser);
1071
+ }
1072
+ BNF.Transform = Transform;
1073
+ class Parser extends Parser_1.Parser {
1074
+ constructor(source, options) {
1075
+ const subParser = options && options.debugRulesParser === true ? new Parser_1.Parser(BNF.RULES, { debug: true }) : BNF.defaultParser;
1076
+ super(getRules(source, subParser), options);
1077
+ }
1078
+ emitSource() {
1079
+ return emit(this);
1080
+ }
1081
+ }
1082
+ BNF.Parser = Parser;
1083
+ })(BNF || (BNF = {}));
1084
+ exports.default = BNF;
1085
+
1086
+ },{"../Parser":5}],4:[function(require,module,exports){
1087
+ "use strict";
1088
+ Object.defineProperty(exports, "__esModule", { value: true });
1089
+ exports.Custom = exports.W3C = exports.BNF = void 0;
1090
+ var BNF_1 = require("./BNF");
1091
+ Object.defineProperty(exports, "BNF", { enumerable: true, get: function () { return BNF_1.default; } });
1092
+ var W3CEBNF_1 = require("./W3CEBNF");
1093
+ Object.defineProperty(exports, "W3C", { enumerable: true, get: function () { return W3CEBNF_1.default; } });
1094
+ var Custom_1 = require("./Custom");
1095
+ Object.defineProperty(exports, "Custom", { enumerable: true, get: function () { return Custom_1.default; } });
1096
+
1097
+ },{"./BNF":1,"./Custom":2,"./W3CEBNF":3}],5:[function(require,module,exports){
1098
+ "use strict";
1099
+ // https://www.ics.uci.edu/~pattis/ICS-33/lectures/ebnf.pdf
1100
+ Object.defineProperty(exports, "__esModule", { value: true });
1101
+ exports.Parser = void 0;
1102
+ exports.readToken = readToken;
1103
+ exports.escapeRegExp = escapeRegExp;
1104
+ exports.parseRuleName = parseRuleName;
1105
+ exports.findRuleByName = findRuleByName;
1106
+ const UPPER_SNAKE_RE = /^[A-Z0-9_]+$/;
1107
+ const decorationRE = /(\?|\+|\*)$/;
1108
+ const preDecorationRE = /^(@|&|!)/;
1109
+ const WS_RULE = 'WS';
1110
+ const TokenError_1 = require("./TokenError");
1111
+ const ParsingError_1 = require("./ParsingError");
1112
+ function readToken(txt, expr) {
1113
+ let result = expr.exec(txt);
1114
+ if (result && result.index == 0) {
1115
+ if (result[0].length == 0 && expr.source.length > 0)
1116
+ return null;
1117
+ return {
1118
+ type: null,
1119
+ text: result[0],
1120
+ rest: txt.substr(result[0].length),
1121
+ start: 0,
1122
+ end: result[0].length - 1,
1123
+ fullText: result[0],
1124
+ errors: [],
1125
+ children: [],
1126
+ parent: null
1127
+ };
1128
+ }
1129
+ return null;
1130
+ }
1131
+ function escapeRegExp(str) {
1132
+ return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
1133
+ }
1134
+ function fixRest(token) {
1135
+ token.rest = '';
1136
+ if (token.children) {
1137
+ for (const c of token.children) {
1138
+ fixRest(c);
1139
+ }
1140
+ }
1141
+ }
1142
+ function fixPositions(token, start) {
1143
+ token.start += start;
1144
+ token.end += start;
1145
+ if (token.children) {
1146
+ for (const c of token.children) {
1147
+ fixPositions(c, token.start);
1148
+ }
1149
+ }
1150
+ }
1151
+ function agregateErrors(errors, token) {
1152
+ if (token.errors && token.errors.length) {
1153
+ for (const err of token.errors) {
1154
+ errors.push(err);
1155
+ }
1156
+ }
1157
+ if (token.children) {
1158
+ for (const tok of token.children) {
1159
+ agregateErrors(errors, tok);
1160
+ }
1161
+ }
1162
+ }
1163
+ function parseRuleName(name) {
1164
+ let postDecoration = decorationRE.exec(name);
1165
+ let preDecoration = preDecorationRE.exec(name);
1166
+ let postDecorationText = (postDecoration && postDecoration[0]) || '';
1167
+ let preDecorationText = (preDecoration && preDecoration[0]) || '';
1168
+ let out = {
1169
+ raw: name,
1170
+ name: name.replace(decorationRE, '').replace(preDecorationRE, ''),
1171
+ isOptional: postDecorationText == '?' || postDecorationText == '*',
1172
+ allowRepetition: postDecorationText == '+' || postDecorationText == '*',
1173
+ atLeastOne: postDecorationText == '+',
1174
+ lookupPositive: preDecorationText == '&',
1175
+ lookupNegative: preDecorationText == '!',
1176
+ pinned: preDecorationText == '@',
1177
+ lookup: false,
1178
+ isLiteral: false
1179
+ };
1180
+ out.isLiteral = out.name[0] == "'" || out.name[0] == '"';
1181
+ out.lookup = out.lookupNegative || out.lookupPositive;
1182
+ return out;
1183
+ }
1184
+ function findRuleByName(name, parser) {
1185
+ let parsed = parseRuleName(name);
1186
+ return parser.cachedRules[parsed.name] || null;
1187
+ }
1188
+ /// Removes all the nodes starting with 'RULE_'
1189
+ function stripRules(token, re) {
1190
+ if (token.children) {
1191
+ let localRules = token.children.filter(x => x.type && re.test(x.type));
1192
+ for (let i = 0; i < localRules.length; i++) {
1193
+ let indexOnChildren = token.children.indexOf(localRules[i]);
1194
+ if (indexOnChildren != -1) {
1195
+ token.children.splice(indexOnChildren, 1);
1196
+ }
1197
+ }
1198
+ for (const c of token.children) {
1199
+ stripRules(c, re);
1200
+ }
1201
+ }
1202
+ }
1203
+ const ignoreMissingRules = ['EOF'];
1204
+ class Parser {
1205
+ constructor(grammarRules, options) {
1206
+ this.grammarRules = grammarRules;
1207
+ this.options = options;
1208
+ this.furthestFailure = null;
1209
+ this.originalInput = '';
1210
+ this.parseStack = []; // Track parsing context
1211
+ this.cachedRules = {};
1212
+ this.debug = options ? options.debug === true : false;
1213
+ let errors = [];
1214
+ let neededRules = [];
1215
+ for (const rule of grammarRules) {
1216
+ let parsedName = parseRuleName(rule.name);
1217
+ if (parsedName.name in this.cachedRules) {
1218
+ errors.push('Duplicated rule ' + parsedName.name);
1219
+ continue;
1220
+ }
1221
+ else {
1222
+ this.cachedRules[parsedName.name] = rule;
1223
+ }
1224
+ if (!rule.bnf || !rule.bnf.length) {
1225
+ let error = 'Missing rule content, rule: ' + rule.name;
1226
+ if (errors.indexOf(error) == -1)
1227
+ errors.push(error);
1228
+ }
1229
+ else {
1230
+ for (const options of rule.bnf) {
1231
+ if (typeof options[0] === 'string') {
1232
+ let parsed = parseRuleName(options[0]);
1233
+ if (parsed.name == rule.name) {
1234
+ let error = 'Left recursion is not allowed, rule: ' + rule.name;
1235
+ if (errors.indexOf(error) == -1)
1236
+ errors.push(error);
1237
+ }
1238
+ }
1239
+ for (const option of options) {
1240
+ if (typeof option == 'string') {
1241
+ let name = parseRuleName(option);
1242
+ if (!name.isLiteral &&
1243
+ neededRules.indexOf(name.name) == -1 &&
1244
+ ignoreMissingRules.indexOf(name.name) == -1)
1245
+ neededRules.push(name.name);
1246
+ }
1247
+ }
1248
+ }
1249
+ }
1250
+ if (WS_RULE == rule.name)
1251
+ rule.implicitWs = false;
1252
+ if (rule.implicitWs) {
1253
+ if (neededRules.indexOf(WS_RULE) == -1)
1254
+ neededRules.push(WS_RULE);
1255
+ }
1256
+ if (rule.recover) {
1257
+ if (neededRules.indexOf(rule.recover) == -1)
1258
+ neededRules.push(rule.recover);
1259
+ }
1260
+ }
1261
+ for (const ruleName of neededRules) {
1262
+ if (!(ruleName in this.cachedRules)) {
1263
+ errors.push('Missing rule ' + ruleName);
1264
+ }
1265
+ }
1266
+ if (errors.length)
1267
+ throw new Error(errors.join('\n'));
1268
+ }
1269
+ getAST(txt, target) {
1270
+ if (!target) {
1271
+ target = this.grammarRules.filter(x => !x.fragment && x.name.indexOf('%') != 0)[0].name;
1272
+ }
1273
+ // Reset failure tracking for each new parse
1274
+ this.furthestFailure = null;
1275
+ this.originalInput = txt;
1276
+ this.parseStack = [];
1277
+ let result = this.parse(txt, target, 0, 0);
1278
+ if (result) {
1279
+ agregateErrors(result.errors, result);
1280
+ fixPositions(result, 0);
1281
+ // REMOVE ALL THE TAGS MATCHING /^%/
1282
+ stripRules(result, /^%/);
1283
+ if (!this.options || !this.options.keepUpperRules)
1284
+ stripRules(result, UPPER_SNAKE_RE);
1285
+ let rest = result.rest;
1286
+ if (rest) {
1287
+ new TokenError_1.TokenError('Unexpected end of input: \n' + rest, result);
1288
+ }
1289
+ fixRest(result);
1290
+ result.rest = rest;
1291
+ }
1292
+ else {
1293
+ // Parsing failed completely - throw ParsingError
1294
+ if (this.furthestFailure) {
1295
+ const position = this.calculatePosition(this.originalInput, this.furthestFailure.offset);
1296
+ const found = this.furthestFailure.found;
1297
+ // Extract parent-most rules first
1298
+ const parentMostRules = this.extractParentMostRules(this.furthestFailure.tree);
1299
+ // Build failure tree starting from the parent-most failing rules
1300
+ const failureTree = this.buildFailureTree(this.furthestFailure.tree, parentMostRules, this.furthestFailure.regexMap);
1301
+ throw new ParsingError_1.ParsingError('Failed to parse input', position, parentMostRules, found, failureTree);
1302
+ }
1303
+ else {
1304
+ // Fallback if no failure was tracked
1305
+ throw new ParsingError_1.ParsingError('Failed to parse input', { offset: 0, line: 1, column: 1 }, [target], txt.length > 0 ? txt.charAt(0) : 'end of input');
1306
+ }
1307
+ }
1308
+ return result;
1309
+ }
1310
+ emitSource() {
1311
+ return 'CANNOT EMIT SOURCE FROM BASE Parser';
1312
+ }
1313
+ calculatePosition(txt, offset) {
1314
+ let line = 1;
1315
+ let column = 1;
1316
+ for (let i = 0; i < offset && i < txt.length; i++) {
1317
+ // Handle \r\n as a single line ending
1318
+ if (txt[i] === '\r' && i + 1 < txt.length && txt[i + 1] === '\n') {
1319
+ line++;
1320
+ column = 1;
1321
+ i++; // Skip the \n
1322
+ }
1323
+ else if (txt[i] === '\n' || txt[i] === '\r') {
1324
+ line++;
1325
+ column = 1;
1326
+ }
1327
+ else {
1328
+ column++;
1329
+ }
1330
+ }
1331
+ return { offset, line, column };
1332
+ }
1333
+ recordFailure(offset, expected) {
1334
+ const expectedKey = expected instanceof RegExp ? expected.source : expected;
1335
+ if (!this.furthestFailure || offset > this.furthestFailure.offset) {
1336
+ // This is a new furthest failure
1337
+ let found;
1338
+ if (offset >= this.originalInput.length) {
1339
+ found = 'end of input';
1340
+ }
1341
+ else {
1342
+ // Extract a meaningful token/substring from the failure position
1343
+ const remaining = this.originalInput.substring(offset);
1344
+ // Skip whitespace using replace
1345
+ const trimmed = remaining.replace(/^\s+/, '');
1346
+ if (trimmed.length === 0) {
1347
+ found = 'end of input';
1348
+ }
1349
+ else {
1350
+ // Try to extract a word/token (alphanumeric + some symbols)
1351
+ const match = trimmed.match(/^[^\s\}\]\),;]+/);
1352
+ if (match && match[0].length > 0) {
1353
+ found = match[0];
1354
+ }
1355
+ else {
1356
+ // Fallback to first character after whitespace
1357
+ found = trimmed.charAt(0);
1358
+ }
1359
+ }
1360
+ }
1361
+ this.furthestFailure = {
1362
+ offset,
1363
+ expected: new Set([expectedKey]),
1364
+ found,
1365
+ tree: new Map(),
1366
+ regexMap: new Map()
1367
+ };
1368
+ // Store regex instance if it's a RegExp
1369
+ if (expected instanceof RegExp) {
1370
+ this.furthestFailure.regexMap.set(expected.source, expected);
1371
+ }
1372
+ this.recordParentChildRelationship(expected);
1373
+ this.furthestFailure.expected.add(expectedKey);
1374
+ }
1375
+ else if (offset === this.furthestFailure.offset) {
1376
+ // Same position, add to expected set
1377
+ this.furthestFailure.expected.add(expectedKey);
1378
+ // Store regex instance if it's a RegExp
1379
+ if (expected instanceof RegExp) {
1380
+ this.furthestFailure.regexMap.set(expected.source, expected);
1381
+ }
1382
+ this.recordParentChildRelationship(expected);
1383
+ }
1384
+ }
1385
+ recordParentChildRelationship(expected) {
1386
+ if (!this.furthestFailure)
1387
+ return;
1388
+ if (this.parseStack.length > 0) {
1389
+ const parent = this.parseStack[this.parseStack.length - 1];
1390
+ if (!this.furthestFailure.tree.has(parent)) {
1391
+ this.furthestFailure.tree.set(parent, new Set());
1392
+ }
1393
+ this.furthestFailure.tree.get(parent).add(expected);
1394
+ }
1395
+ else {
1396
+ // No parent, this is a top-level failure
1397
+ if (!this.furthestFailure.tree.has('__ROOT__')) {
1398
+ this.furthestFailure.tree.set('__ROOT__', new Set());
1399
+ }
1400
+ this.furthestFailure.tree.get('__ROOT__').add(expected);
1401
+ }
1402
+ }
1403
+ extractParentMostRules(tree) {
1404
+ // The "parent most failing option" is the rule we were trying to match
1405
+ // when all its alternatives failed. In the tree structure, this is typically
1406
+ // the direct child of the top-most parent that has alternatives.
1407
+ // Helper to get string name from value
1408
+ const getName = (value) => value instanceof RegExp ? value.source : value;
1409
+ // If we have __ROOT__, find its direct children that have alternatives
1410
+ if (tree.has('__ROOT__')) {
1411
+ const rootChildren = Array.from(tree.get('__ROOT__')).map(getName);
1412
+ // Return root children that have their own children (alternatives)
1413
+ const result = rootChildren.filter(child => tree.has(child) && tree.get(child).size > 0);
1414
+ if (result.length > 0) {
1415
+ return result;
1416
+ }
1417
+ // If none have children, return the root children themselves
1418
+ return rootChildren;
1419
+ }
1420
+ // Find the top-most parent (not a child of any other parent)
1421
+ const allChildren = new Set();
1422
+ const allParents = new Set();
1423
+ for (const [parent, children] of tree.entries()) {
1424
+ allParents.add(parent);
1425
+ for (const child of children) {
1426
+ allChildren.add(getName(child));
1427
+ }
1428
+ }
1429
+ const topMostParents = Array.from(allParents).filter(parent => !allChildren.has(parent));
1430
+ // For each top-most parent, get its direct children that have alternatives
1431
+ const result = [];
1432
+ for (const parent of topMostParents) {
1433
+ if (tree.has(parent)) {
1434
+ const children = Array.from(tree.get(parent));
1435
+ for (const child of children) {
1436
+ const childName = getName(child);
1437
+ if (tree.has(childName) && tree.get(childName).size > 0) {
1438
+ result.push(childName);
1439
+ }
1440
+ }
1441
+ }
1442
+ }
1443
+ if (result.length > 0) {
1444
+ return result;
1445
+ }
1446
+ // Fallback: return top-most parents
1447
+ if (topMostParents.length > 0) {
1448
+ return topMostParents;
1449
+ }
1450
+ // Last fallback: return all unique rules
1451
+ return Array.from(new Set([...allParents, ...allChildren]));
1452
+ }
1453
+ /**
1454
+ * Determines if a value represents a terminal/literal rather than a non-terminal rule.
1455
+ */
1456
+ isLiteralOrTerminal(value) {
1457
+ if (value instanceof RegExp) {
1458
+ return true;
1459
+ }
1460
+ // Check if it's a literal (starts with " or ')
1461
+ if (value.startsWith('"') || value.startsWith("'")) {
1462
+ return true;
1463
+ }
1464
+ // Check if it's a regex pattern (starts with [ or contains #x followed by hex digits)
1465
+ if (value.startsWith('[') || /\#x[0-9a-fA-F]/.test(value)) {
1466
+ return true;
1467
+ }
1468
+ return false;
1469
+ }
1470
+ /**
1471
+ * Extracts the expected value from a terminal/literal.
1472
+ * - For string literals (quoted), removes the quotes and returns the content
1473
+ * - For regex patterns, returns the RegExp instance
1474
+ */
1475
+ extractExpectedValue(value, regexMap) {
1476
+ if (value instanceof RegExp) {
1477
+ return value;
1478
+ }
1479
+ // If it's a string literal (starts with " or '), parse it to remove quotes
1480
+ if (value.startsWith('"')) {
1481
+ try {
1482
+ return JSON.parse(value);
1483
+ }
1484
+ catch (_a) {
1485
+ return value;
1486
+ }
1487
+ }
1488
+ else if (value.startsWith("'")) {
1489
+ // Single-quoted string - remove quotes
1490
+ return value.replace(/^'(.+)'$/, '$1').replace(/\\'/g, "'");
1491
+ }
1492
+ // Check if this is a regex pattern string that we have a RegExp instance for
1493
+ if (regexMap.has(value)) {
1494
+ return regexMap.get(value);
1495
+ }
1496
+ // For other terminals, return as-is
1497
+ return value;
1498
+ }
1499
+ buildFailureTree(tree, startRules, regexMap) {
1500
+ const buildNode = (value) => {
1501
+ const name = value instanceof RegExp ? value.source : value;
1502
+ const node = { name };
1503
+ if (tree.has(name)) {
1504
+ const children = Array.from(tree.get(name));
1505
+ if (children.length > 0) {
1506
+ // If this rule has exactly one child and that child is a terminal,
1507
+ // set expected to that terminal instead of creating children
1508
+ if (children.length === 1 && this.isLiteralOrTerminal(children[0])) {
1509
+ node.expected = this.extractExpectedValue(children[0], regexMap || new Map());
1510
+ }
1511
+ else {
1512
+ // Build child nodes
1513
+ const childNodes = children.map(child => buildNode(child));
1514
+ // Check if this node should be flattened:
1515
+ // If it has exactly one child AND that child has an expected value (is terminal)
1516
+ if (childNodes.length === 1 && childNodes[0].expected !== undefined && childNodes[0].children === undefined) {
1517
+ node.expected = childNodes[0].expected;
1518
+ }
1519
+ else {
1520
+ node.children = childNodes;
1521
+ }
1522
+ }
1523
+ }
1524
+ }
1525
+ else if (this.isLiteralOrTerminal(value)) {
1526
+ // If this is a terminal/literal with no children, set expected to itself
1527
+ node.expected = this.extractExpectedValue(value, regexMap || new Map());
1528
+ }
1529
+ return node;
1530
+ };
1531
+ // If startRules are provided, start from those rules
1532
+ if (startRules && startRules.length > 0) {
1533
+ return startRules.map(rule => buildNode(rule));
1534
+ }
1535
+ // Start from root if it exists, otherwise from parent-most rules
1536
+ if (tree.has('__ROOT__')) {
1537
+ const rootChildren = Array.from(tree.get('__ROOT__'));
1538
+ return rootChildren.map(child => buildNode(child));
1539
+ }
1540
+ // Find parent-most rules (rules that are not children of other rules)
1541
+ const allChildren = new Set();
1542
+ for (const children of tree.values()) {
1543
+ for (const child of children) {
1544
+ const childName = child instanceof RegExp ? child.source : child;
1545
+ allChildren.add(childName);
1546
+ }
1547
+ }
1548
+ const parentMost = Array.from(tree.keys()).filter(parent => !allChildren.has(parent));
1549
+ return parentMost.map(parent => buildNode(parent));
1550
+ }
1551
+ parse(txt, target, recursion = 0, offset = 0) {
1552
+ let out = null;
1553
+ let type = parseRuleName(target);
1554
+ let expr;
1555
+ let printable = this.debug && /*!isLiteral &*/ !UPPER_SNAKE_RE.test(type.name);
1556
+ printable &&
1557
+ console.log(new Array(recursion).join('│ ') + 'Trying to get ' + target + ' from ' + JSON.stringify(txt.split('\n')[0]));
1558
+ let realType = type.name;
1559
+ let targetLex = findRuleByName(type.name, this);
1560
+ if (type.name == 'EOF') {
1561
+ if (txt.length) {
1562
+ this.recordFailure(offset, 'EOF');
1563
+ return null;
1564
+ }
1565
+ else if (txt.length == 0) {
1566
+ return {
1567
+ type: 'EOF',
1568
+ text: '',
1569
+ rest: '',
1570
+ start: 0,
1571
+ end: 0,
1572
+ fullText: '',
1573
+ errors: [],
1574
+ children: [],
1575
+ parent: null
1576
+ };
1577
+ }
1578
+ }
1579
+ try {
1580
+ if (!targetLex && type.isLiteral) {
1581
+ let src = type.name.trim();
1582
+ if (src.startsWith('"')) {
1583
+ src = JSON.parse(src);
1584
+ }
1585
+ else if (src.startsWith("'")) {
1586
+ src = src.replace(/^'(.+)'$/, '$1').replace(/\\'/g, "'");
1587
+ }
1588
+ if (src === '') {
1589
+ return {
1590
+ type: '%%EMPTY%%',
1591
+ text: '',
1592
+ rest: txt,
1593
+ start: 0,
1594
+ end: 0,
1595
+ fullText: '',
1596
+ errors: [],
1597
+ children: [],
1598
+ parent: null
1599
+ };
1600
+ }
1601
+ expr = new RegExp(escapeRegExp(src));
1602
+ realType = null;
1603
+ }
1604
+ }
1605
+ catch (e) {
1606
+ if (e instanceof ReferenceError) {
1607
+ console.error(e);
1608
+ }
1609
+ this.recordFailure(offset, target);
1610
+ return null;
1611
+ }
1612
+ if (expr) {
1613
+ let result = readToken(txt, expr);
1614
+ if (result) {
1615
+ result.type = realType;
1616
+ return result;
1617
+ }
1618
+ else {
1619
+ // Literal or regex match failed
1620
+ this.recordFailure(offset, type.isLiteral ? type.name : target);
1621
+ }
1622
+ }
1623
+ else {
1624
+ let options = targetLex.bnf;
1625
+ if (options instanceof Array) {
1626
+ // Push this rule onto the parse stack
1627
+ this.parseStack.push(type.name);
1628
+ optionsLoop: for (const phases of options) {
1629
+ if (out)
1630
+ break;
1631
+ let pinned = null;
1632
+ let tmp = {
1633
+ type: type.name,
1634
+ text: '',
1635
+ children: [],
1636
+ end: 0,
1637
+ errors: [],
1638
+ fullText: '',
1639
+ parent: null,
1640
+ start: 0,
1641
+ rest: txt
1642
+ };
1643
+ if (targetLex.fragment)
1644
+ tmp.fragment = true;
1645
+ let tmpTxt = txt;
1646
+ let position = 0;
1647
+ let allOptional = phases.length > 0;
1648
+ let foundSomething = false;
1649
+ for (let i = 0; i < phases.length; i++) {
1650
+ if (typeof phases[i] == 'string') {
1651
+ let localTarget = parseRuleName(phases[i]);
1652
+ allOptional = allOptional && localTarget.isOptional;
1653
+ let got;
1654
+ let foundAtLeastOne = false;
1655
+ do {
1656
+ got = null;
1657
+ if (targetLex.implicitWs) {
1658
+ got = this.parse(tmpTxt, localTarget.name, recursion + 1, offset + position);
1659
+ if (!got) {
1660
+ let WS;
1661
+ do {
1662
+ WS = this.parse(tmpTxt, WS_RULE, recursion + 1, offset + position);
1663
+ if (WS) {
1664
+ tmp.text = tmp.text + WS.text;
1665
+ tmp.end = tmp.text.length;
1666
+ WS.parent = tmp;
1667
+ tmp.children.push(WS);
1668
+ tmpTxt = tmpTxt.substr(WS.text.length);
1669
+ position += WS.text.length;
1670
+ }
1671
+ else {
1672
+ break;
1673
+ }
1674
+ } while (WS && WS.text.length);
1675
+ }
1676
+ }
1677
+ got = got || this.parse(tmpTxt, localTarget.name, recursion + 1, offset + position);
1678
+ // rule ::= "true" ![a-zA-Z]
1679
+ // negative lookup, if it does not match, we should continue
1680
+ if (localTarget.lookupNegative) {
1681
+ if (got)
1682
+ continue optionsLoop; /* cancel this path */
1683
+ break;
1684
+ }
1685
+ if (localTarget.lookupPositive) {
1686
+ if (!got)
1687
+ continue optionsLoop;
1688
+ }
1689
+ if (!got) {
1690
+ if (localTarget.isOptional)
1691
+ break;
1692
+ if (localTarget.atLeastOne && foundAtLeastOne)
1693
+ break;
1694
+ // Record this failure for error reporting
1695
+ this.recordFailure(offset + position, localTarget.name);
1696
+ }
1697
+ if (got && targetLex.pinned == i + 1) {
1698
+ pinned = got;
1699
+ printable && console.log(new Array(recursion + 1).join('│ ') + '└─ ' + got.type + ' PINNED');
1700
+ }
1701
+ if (!got)
1702
+ got = this.parseRecovery(targetLex, tmpTxt, recursion + 1, offset + position);
1703
+ if (!got) {
1704
+ if (pinned) {
1705
+ out = tmp;
1706
+ got = {
1707
+ type: 'SyntaxError',
1708
+ text: tmpTxt,
1709
+ children: [],
1710
+ end: tmpTxt.length,
1711
+ errors: [],
1712
+ fullText: '',
1713
+ parent: null,
1714
+ start: 0,
1715
+ rest: ''
1716
+ };
1717
+ if (tmpTxt.length) {
1718
+ new TokenError_1.TokenError(`Unexpected end of input. Expecting ${localTarget.name} Got: ${tmpTxt}`, got);
1719
+ }
1720
+ else {
1721
+ new TokenError_1.TokenError(`Unexpected end of input. Missing ${localTarget.name}`, got);
1722
+ }
1723
+ printable &&
1724
+ console.log(new Array(recursion + 1).join('│ ') + '└─ ' + got.type + ' ' + JSON.stringify(got.text));
1725
+ }
1726
+ else {
1727
+ continue optionsLoop;
1728
+ }
1729
+ }
1730
+ foundAtLeastOne = true;
1731
+ foundSomething = true;
1732
+ if (got.type == '%%EMPTY%%') {
1733
+ break;
1734
+ }
1735
+ got.start += position;
1736
+ got.end += position;
1737
+ if (!localTarget.lookupPositive && got.type) {
1738
+ if (got.fragment) {
1739
+ if (got.children) {
1740
+ for (const x of got.children) {
1741
+ x.start += position;
1742
+ x.end += position;
1743
+ x.parent = tmp;
1744
+ tmp.children.push(x);
1745
+ }
1746
+ }
1747
+ }
1748
+ else {
1749
+ got.parent = tmp;
1750
+ tmp.children.push(got);
1751
+ }
1752
+ }
1753
+ if (localTarget.lookup)
1754
+ got.lookup = true;
1755
+ printable &&
1756
+ console.log(new Array(recursion + 1).join('│ ') + '└─ ' + got.type + ' ' + JSON.stringify(got.text));
1757
+ // Eat it from the input stream, only if it is not a lookup
1758
+ if (!localTarget.lookup && !got.lookup) {
1759
+ tmp.text = tmp.text + got.text;
1760
+ tmp.end = tmp.text.length;
1761
+ tmpTxt = tmpTxt.substr(got.text.length);
1762
+ position += got.text.length;
1763
+ }
1764
+ tmp.rest = tmpTxt;
1765
+ } while (got && localTarget.allowRepetition && tmpTxt.length && !got.lookup);
1766
+ } /* IS A REGEXP */
1767
+ else {
1768
+ let got = readToken(tmpTxt, phases[i]);
1769
+ if (!got) {
1770
+ this.recordFailure(offset + position, phases[i]);
1771
+ continue optionsLoop;
1772
+ }
1773
+ printable &&
1774
+ console.log(new Array(recursion + 1).join('│ ') + '└> ' + JSON.stringify(got.text) + phases[i].source);
1775
+ foundSomething = true;
1776
+ got.start += position;
1777
+ got.end += position;
1778
+ tmp.text = tmp.text + got.text;
1779
+ tmp.end = tmp.text.length;
1780
+ tmpTxt = tmpTxt.substr(got.text.length);
1781
+ position += got.text.length;
1782
+ tmp.rest = tmpTxt;
1783
+ }
1784
+ }
1785
+ if (foundSomething) {
1786
+ out = tmp;
1787
+ printable &&
1788
+ console.log(new Array(recursion).join('│ ') + '├<─┴< PUSHING ' + out.type + ' ' + JSON.stringify(out.text));
1789
+ }
1790
+ }
1791
+ // Pop this rule from the parse stack
1792
+ this.parseStack.pop();
1793
+ }
1794
+ if (out && targetLex.simplifyWhenOneChildren && out.children.length == 1) {
1795
+ out = out.children[0];
1796
+ }
1797
+ }
1798
+ if (!out) {
1799
+ printable && console.log(target + ' NOT RESOLVED FROM ' + txt);
1800
+ }
1801
+ return out;
1802
+ }
1803
+ parseRecovery(recoverableToken, tmpTxt, recursion, offset) {
1804
+ if (recoverableToken.recover && tmpTxt.length) {
1805
+ let printable = this.debug;
1806
+ printable &&
1807
+ console.log(new Array(recursion + 1).join('│ ') +
1808
+ 'Trying to recover until token ' +
1809
+ recoverableToken.recover +
1810
+ ' from ' +
1811
+ JSON.stringify(tmpTxt.split('\n')[0] + tmpTxt.split('\n')[1]));
1812
+ let tmp = {
1813
+ type: 'SyntaxError',
1814
+ text: '',
1815
+ children: [],
1816
+ end: 0,
1817
+ errors: [],
1818
+ fullText: '',
1819
+ parent: null,
1820
+ start: 0,
1821
+ rest: ''
1822
+ };
1823
+ let got;
1824
+ let currentOffset = offset;
1825
+ do {
1826
+ got = this.parse(tmpTxt, recoverableToken.recover, recursion + 1, currentOffset);
1827
+ if (got) {
1828
+ new TokenError_1.TokenError('Unexpected input: "' + tmp.text + `" Expecting: ${recoverableToken.name}`, tmp);
1829
+ break;
1830
+ }
1831
+ else {
1832
+ tmp.text = tmp.text + tmpTxt[0];
1833
+ tmp.end = tmp.text.length;
1834
+ tmpTxt = tmpTxt.substr(1);
1835
+ currentOffset++;
1836
+ }
1837
+ } while (!got && tmpTxt.length > 0);
1838
+ if (tmp.text.length > 0 && got) {
1839
+ printable && console.log(new Array(recursion + 1).join('│ ') + 'Recovered text: ' + JSON.stringify(tmp.text));
1840
+ return tmp;
1841
+ }
1842
+ }
1843
+ return null;
1844
+ }
1845
+ }
1846
+ exports.Parser = Parser;
1847
+ exports.default = Parser;
1848
+
1849
+ },{"./ParsingError":6,"./TokenError":8}],6:[function(require,module,exports){
1850
+ "use strict";
1851
+ Object.defineProperty(exports, "__esModule", { value: true });
1852
+ exports.ParsingError = void 0;
1853
+ class ParsingError extends Error {
1854
+ constructor(message, position, expected, found, failureTree) {
1855
+ super(message);
1856
+ this.name = 'ParsingError';
1857
+ this.position = position;
1858
+ this.expected = expected;
1859
+ this.found = found;
1860
+ this.failureTree = failureTree;
1861
+ // Maintain proper prototype chain for instanceof checks
1862
+ Object.setPrototypeOf(this, ParsingError.prototype);
1863
+ }
1864
+ toString() {
1865
+ const { line, column, offset } = this.position;
1866
+ let msg = `${this.name}: ${this.message}\n`;
1867
+ msg += ` at line ${line}, column ${column} (offset ${offset})\n`;
1868
+ msg += ` Expected: ${this.expected.join(', ')}\n`;
1869
+ msg += ` Found: ${this.found}`;
1870
+ return msg;
1871
+ }
1872
+ }
1873
+ exports.ParsingError = ParsingError;
1874
+
1875
+ },{}],7:[function(require,module,exports){
1876
+ "use strict";
1877
+ Object.defineProperty(exports, "__esModule", { value: true });
1878
+ exports.findChildrenByType = findChildrenByType;
1879
+ /**
1880
+ * Finds all the direct childs of a specifyed type
1881
+ */
1882
+ function findChildrenByType(token, type) {
1883
+ return token.children ? token.children.filter(x => x.type == type) : [];
1884
+ }
1885
+
1886
+ },{}],8:[function(require,module,exports){
1887
+ "use strict";
1888
+ Object.defineProperty(exports, "__esModule", { value: true });
1889
+ exports.TokenError = void 0;
1890
+ class TokenError extends Error {
1891
+ constructor(message, token) {
1892
+ super(message);
1893
+ this.message = message;
1894
+ this.token = token;
1895
+ if (token && token.errors)
1896
+ token.errors.push(this);
1897
+ else
1898
+ throw this;
1899
+ }
1900
+ inspect() {
1901
+ return 'SyntaxError: ' + this.message;
1902
+ }
1903
+ }
1904
+ exports.TokenError = TokenError;
1905
+
1906
+ },{}],9:[function(require,module,exports){
1907
+ "use strict";
1908
+ Object.defineProperty(exports, "__esModule", { value: true });
1909
+ exports.ParsingError = exports.TokenError = exports.Parser = void 0;
1910
+ var Parser_1 = require("./Parser");
1911
+ Object.defineProperty(exports, "Parser", { enumerable: true, get: function () { return Parser_1.Parser; } });
1912
+ var TokenError_1 = require("./TokenError");
1913
+ Object.defineProperty(exports, "TokenError", { enumerable: true, get: function () { return TokenError_1.TokenError; } });
1914
+ var ParsingError_1 = require("./ParsingError");
1915
+ Object.defineProperty(exports, "ParsingError", { enumerable: true, get: function () { return ParsingError_1.ParsingError; } });
1916
+ exports.Grammars = require("./Grammars");
1917
+
1918
+ },{"./Grammars":4,"./Parser":5,"./ParsingError":6,"./TokenError":8}],10:[function(require,module,exports){
1919
+ // Browser entry point that uses production files
1920
+ module.exports = require('./RuleParser.production')
1921
+
1922
+ },{"./RuleParser.production":12}],11:[function(require,module,exports){
1923
+ module.exports=[{"name":"statement_main","bnf":[["statement","EOF"]]},{"name":"logical_operator","bnf":[["AND"],["OR"]]},{"name":"%statement[2]","bnf":[["logical_operator","expression"]],"fragment":true},{"name":"statement","bnf":[["expression","%statement[2]*"]]},{"name":"expression","bnf":[["not_expression"],["standard_expression"],["parenthesis_expression"]]},{"name":"parenthesis_expression","bnf":[["BEGIN_PARENTHESIS","WS*","statement","WS*","END_PARENTHESIS"]]},{"name":"%not_expression[2]","bnf":[["result"],["parenthesis_expression"]],"fragment":true},{"name":"not_expression","bnf":[["NOT","%not_expression[2]"]]},{"name":"%standard_expression[2][1]","bnf":[["WS*","eq_approx"]],"fragment":true},{"name":"%standard_expression[2][2]","bnf":[["WS*","basic_rhs"]],"fragment":true},{"name":"%standard_expression[2][3][1]","bnf":[["WS+","IS"]],"fragment":true},{"name":"%standard_expression[2][3]","bnf":[["%standard_expression[2][3][1]?","WS+","between"]],"fragment":true},{"name":"%standard_expression[2][4]","bnf":[["WS+","in_expr"]],"fragment":true},{"name":"%standard_expression[2]","bnf":[["%standard_expression[2][1]"],["%standard_expression[2][2]"],["%standard_expression[2][3]"],["%standard_expression[2][4]"]],"fragment":true},{"name":"standard_expression","bnf":[["result","%standard_expression[2]?"]]},{"name":"basic_rhs","bnf":[["operator","WS*","result"]]},{"name":"eq_approx","bnf":[["eq_operator","WS*","\"~\"","WS*","result"]]},{"name":"PLUS","bnf":[["\"+\""]]},{"name":"MINUS","bnf":[["\"-\""]]},{"name":"MULTIPLY","bnf":[["\"*\""]]},{"name":"DIVIDE","bnf":[["\"/\""]]},{"name":"MODULUS","bnf":[["\"%\""]]},{"name":"DEFAULT_VAL","bnf":[["\"??\""]]},{"name":"arithmetic_operator","bnf":[["PLUS"],["MINUS"],["MULTIPLY"],["DIVIDE"],["MODULUS"],["DEFAULT_VAL"]]},{"name":"arithmetic_operand","bnf":[["fcall"],["number_time"],["number"]]},{"name":"%arithmetic_result[5]","bnf":[["arithmetic_result"],["arithmetic_operand"]],"fragment":true},{"name":"arithmetic_result","bnf":[["arithmetic_operand","WS*","arithmetic_operator","WS*","%arithmetic_result[5]"]]},{"name":"simple_result","bnf":[["fcall"],["value"]]},{"name":"result","bnf":[["arithmetic_result"],["simple_result"]]},{"name":"value","bnf":[["false"],["true"],["array"],["time_period"],["number_time"],["number"],["number_tod"],["string"]]},{"name":"BEGIN_ARRAY","bnf":[["WS*",/\x5B/,"WS*"]]},{"name":"BEGIN_OBJECT","bnf":[["WS*",/\x7B/,"WS*"]]},{"name":"END_ARRAY","bnf":[["WS*",/\x5D/,"WS*"]]},{"name":"END_OBJECT","bnf":[["WS*",/\x7D/,"WS*"]]},{"name":"NAME_SEPARATOR","bnf":[["WS*",/\x3A/,"WS*"]]},{"name":"VALUE_SEPARATOR","bnf":[["WS*",/\x2C/,"WS*"]]},{"name":"WS","bnf":[[/[\x20\x09\x0A\x0D]/]]},{"name":"operator","bnf":[["GTE"],["LTE"],["GT"],["LT"],["EQ"],["NEQ"]]},{"name":"eq_operator","bnf":[["EQ"],["NEQ"]]},{"name":"BEGIN_ARGUMENT","bnf":[["\"(\""]]},{"name":"END_ARGUMENT","bnf":[["\")\""]]},{"name":"BEGIN_PARENTHESIS","bnf":[["\"(\""]]},{"name":"END_PARENTHESIS","bnf":[["\")\""]]},{"name":"BEGIN_IN","bnf":[[/[Ii]/,/[Nn]/]]},{"name":"in_expr","bnf":[["BEGIN_IN","WS*","BEGIN_PARENTHESIS","WS*","arguments","END_PARENTHESIS"]]},{"name":"argument","bnf":[["statement","WS*"]]},{"name":"%arguments[2]","bnf":[["WS*","\",\"","WS*","argument"]],"fragment":true},{"name":"arguments","bnf":[["argument","%arguments[2]*"]]},{"name":"%fname[1]","bnf":[[/[a-zA-z0-9]/]]},{"name":"fname","bnf":[["%fname[1]+"]]},{"name":"fcall","bnf":[["fname","WS*","BEGIN_ARGUMENT","WS*","arguments?","END_ARGUMENT"]]},{"name":"%between_number[1]","bnf":[["number_time"],["number"]],"fragment":true},{"name":"%between_number[2][1]","bnf":[["WS+",/[Aa]/,/[Nn]/,/[Dd]/,"WS+"]],"fragment":true},{"name":"%between_number[2][2]","bnf":[["WS*",/\-/,"WS*"]],"fragment":true},{"name":"%between_number[2]","bnf":[["%between_number[2][1]"],["%between_number[2][2]"]],"fragment":true},{"name":"%between_number[3]","bnf":[["number_time"],["number"]],"fragment":true},{"name":"between_number","bnf":[["%between_number[1]","%between_number[2]","%between_number[3]"]]},{"name":"%between_number_time[2][1]","bnf":[["WS+",/[Aa]/,/[Nn]/,/[Dd]/,"WS+"]],"fragment":true},{"name":"%between_number_time[2][2]","bnf":[["WS*",/\-/,"WS*"]],"fragment":true},{"name":"%between_number_time[2]","bnf":[["%between_number_time[2][1]"],["%between_number_time[2][2]"]],"fragment":true},{"name":"%between_number_time[4]","bnf":[["WS+","dow_range"]],"fragment":true},{"name":"between_number_time","bnf":[["number_time","%between_number_time[2]","number_time","%between_number_time[4]?"]]},{"name":"%between_tod[2][1]","bnf":[["WS+",/[Aa]/,/[Nn]/,/[Dd]/,"WS+"]],"fragment":true},{"name":"%between_tod[2]","bnf":[["%between_tod[2][1]"]],"fragment":true},{"name":"%between_tod[4]","bnf":[["WS+","dow_range"]],"fragment":true},{"name":"between_tod","bnf":[["number_tod","%between_tod[2]","number_tod","%between_tod[4]?"]]},{"name":"%between[3]","bnf":[["between_number"],["between_tod"]],"fragment":true},{"name":"between","bnf":[[/[Bb]/,/[Ee]/,/[Tt]/,/[Ww]/,/[Ee]/,/[Ee]/,/[Nn]/,"WS+","%between[3]"]]},{"name":"dow","bnf":[[/[Mm]/,/[Oo]/,/[Nn]/,/[Dd]/,/[Aa]/,/[Yy]/],[/[Mm]/,/[Oo]/,/[Nn]/],[/[Tt]/,/[Uu]/,/[Ee]/,/[Ss]/,/[Dd]/,/[Aa]/,/[Yy]/],[/[Tt]/,/[Uu]/,/[Ee]/],[/[Ww]/,/[Ee]/,/[Dd]/,/[Nn]/,/[Ee]/,/[Ss]/,/[Dd]/,/[Aa]/,/[Yy]/],[/[Ww]/,/[Ee]/,/[Dd]/],[/[Tt]/,/[Hh]/,/[Uu]/,/[Rr]/,/[Ss]/,/[Dd]/,/[Aa]/,/[Yy]/],[/[Tt]/,/[Hh]/,/[Uu]/],[/[Tt]/,/[Hh]/,/[Uu]/,/[Rr]/],[/[Ff]/,/[Rr]/,/[Ii]/,/[Dd]/,/[Aa]/,/[Yy]/],[/[Ff]/,/[Rr]/,/[Ii]/],[/[Ss]/,/[Aa]/,/[Tt]/,/[Uu]/,/[Rr]/,/[Dd]/,/[Aa]/,/[Yy]/],[/[Ss]/,/[Aa]/,/[Tt]/],[/[Ss]/,/[Uu]/,/[Nn]/,/[Dd]/,/[Aa]/,/[Yy]/],[/[Ss]/,/[Uu]/,/[Nn]/]]},{"name":"%dow_range[4]","bnf":[["WS+",/[Tt]/,/[Oo]/,"WS+","dow"]],"fragment":true},{"name":"dow_range","bnf":[[/[Oo]/,/[Nn]/,"WS+","dow","%dow_range[4]?"]]},{"name":"between_time_only","bnf":[[/[Bb]/,/[Ee]/,/[Tt]/,/[Ww]/,/[Ee]/,/[Ee]/,/[Nn]/,"WS+","between_number_time"]]},{"name":"between_tod_only","bnf":[[/[Bb]/,/[Ee]/,/[Tt]/,/[Ww]/,/[Ee]/,/[Ee]/,/[Nn]/,"WS+","between_tod"]]},{"name":"%AND[1]","bnf":[["WS*",/&/,/&/,"WS*"]],"fragment":true},{"name":"%AND[2]","bnf":[["WS+",/[Aa]/,/[Nn]/,/[Dd]/,"WS+"]],"fragment":true},{"name":"AND","bnf":[["%AND[1]"],["%AND[2]"]]},{"name":"%OR[1]","bnf":[["WS*",/\|/,/\|/,"WS*"]],"fragment":true},{"name":"%OR[2]","bnf":[["WS+",/[Oo]/,/[Rr]/,"WS+"]],"fragment":true},{"name":"OR","bnf":[["%OR[1]"],["%OR[2]"]]},{"name":"AGO","bnf":[[/[Aa]/,/[Gg]/,/[Oo]/]]},{"name":"GT","bnf":[["\">\""]]},{"name":"LT","bnf":[["\"<\""]]},{"name":"GTE","bnf":[["\">=\""]]},{"name":"LTE","bnf":[["\"<=\""]]},{"name":"IS","bnf":[[/[Ii]/,/[Ss]/]]},{"name":"EQ","bnf":[["\"==\""],["\"=\""]]},{"name":"NEQ","bnf":[["\"!=\""]]},{"name":"%NOT[1]","bnf":[[/!/,"WS*"]],"fragment":true},{"name":"%NOT[2]","bnf":[[/[Nn]/,/[Oo]/,/[Tt]/,"WS+"]],"fragment":true},{"name":"NOT","bnf":[["%NOT[1]"],["%NOT[2]"]]},{"name":"false","bnf":[[/[Ff]/,/[Aa]/,/[Ll]/,/[Ss]/,/[Ee]/]]},{"name":"null","bnf":[[/[Nn]/,/[Uu]/,/[Ll]/,/[Ll]/]]},{"name":"true","bnf":[[/[Tt]/,/[Rr]/,/[Uu]/,/[Ee]/]]},{"name":"%array[2][2]","bnf":[["VALUE_SEPARATOR","value"]],"fragment":true},{"name":"%array[2]","bnf":[["value","%array[2][2]*"]],"fragment":true},{"name":"array","bnf":[["BEGIN_ARRAY","%array[2]?","END_ARRAY"]]},{"name":"unit","bnf":[[/[Ss]/,/[Ee]/,/[Cc]/,/[Oo]/,/[Nn]/,/[Dd]/,/[Ss]/],[/[Mm]/,/[Ii]/,/[Nn]/,/[Uu]/,/[Tt]/,/[Ee]/,/[Ss]/],[/[Hh]/,/[Oo]/,/[Uu]/,/[Rr]/,/[Ss]/],[/[Ww]/,/[Ee]/,/[Ee]/,/[Kk]/,/[Ss]/],[/[Dd]/,/[Aa]/,/[Yy]/,/[Ss]/],[/[Ss]/,/[Ee]/,/[Cc]/,/[Oo]/,/[Nn]/,/[Dd]/],[/[Mm]/,/[Ii]/,/[Nn]/,/[Uu]/,/[Tt]/,/[Ee]/],[/[Ww]/,/[Ee]/,/[Ee]/,/[Kk]/],[/[Hh]/,/[Oo]/,/[Uu]/,/[Rr]/],[/[Dd]/,/[Aa]/,/[Yy]/],[/[Mm]/,/[Ii]/,/[Nn]/,/[Ss]/],[/[Mm]/,/[Ii]/,/[Nn]/]]},{"name":"%number[2][1]","bnf":[[/[0-9]/]]},{"name":"%number[2]","bnf":[["%number[2][1]+"]],"fragment":true},{"name":"%number[3][2]","bnf":[[/[0-9]/]]},{"name":"%number[3]","bnf":[["\".\"","%number[3][2]+"]],"fragment":true},{"name":"%number[4][2]","bnf":[["\"-\""],["\"+\""]],"fragment":true},{"name":"%number[4][3][2]","bnf":[[/[0-9]/]]},{"name":"%number[4][3]","bnf":[["\"0\""],[/[1-9]/,"%number[4][3][2]*"]],"fragment":true},{"name":"%number[4]","bnf":[["\"e\"","%number[4][2]?","%number[4][3]"]],"fragment":true},{"name":"number","bnf":[["\"-\"?","%number[2]","%number[3]?","%number[4]?"]]},{"name":"number_time","bnf":[["number","WS+","unit"]]},{"name":"%number_tod[1][1]","bnf":[[/[0-9]/]]},{"name":"%number_tod[1]","bnf":[["%number_tod[1][1]+"]],"fragment":true},{"name":"%number_tod[3][1]","bnf":[[/[0-9]/]]},{"name":"%number_tod[3]","bnf":[["%number_tod[3][1]+"]],"fragment":true},{"name":"number_tod","bnf":[["%number_tod[1]","\":\"","%number_tod[3]"]]},{"name":"%time_period_ago[2]","bnf":[["WS+","number_time"]],"fragment":true},{"name":"time_period_ago","bnf":[["number_time","%time_period_ago[2]*","WS+","AGO"]]},{"name":"%time_period_ago_between[2]","bnf":[["WS+","number_time"]],"fragment":true},{"name":"time_period_ago_between","bnf":[["number_time","%time_period_ago_between[2]*","WS+","AGO","WS+","between_tod_only"]]},{"name":"time_period_const","bnf":[[/[Tt]/,/[Oo]/,/[Dd]/,/[Aa]/,/[Yy]/],["time_period_ago"]]},{"name":"time_period","bnf":[["time_period_ago_between"],["time_period_const"],["between_tod_only"],["between_time_only"]]},{"name":"%string[2][1]","bnf":[[/[\x20-\x21]/],[/[\x23-\x5B]/],[/[\x5D-\uFFFF]/]],"fragment":true},{"name":"%string[2][2]","bnf":[[/\x22/],[/\x5C/],[/\x2F/],[/\x62/],[/\x66/],[/\x6E/],[/\x72/],[/\x74/],[/\x75/,"HEXDIG","HEXDIG","HEXDIG","HEXDIG"]],"fragment":true},{"name":"%string[2]","bnf":[["%string[2][1]"],[/\x5C/,"%string[2][2]"]],"fragment":true},{"name":"string","bnf":[["'\"'","%string[2]*","'\"'"]]},{"name":"HEXDIG","bnf":[[/[a-fA-F0-9]/]]}]
1924
+ },{}],12:[function(require,module,exports){
1925
+ const {Parser} = require('ebnf/dist/Parser.js'), {ParsingError} = require('ebnf');
1926
+ let ParserRules = require('./RuleParser.production.ebnf.js');
1927
+ let ParserCache;
1928
+ const {ErrorAnalyzer} = require('./errors/ErrorAnalyzer');
1929
+ const ArithmeticOperators = {
1930
+ '+': 'MathAdd',
1931
+ '-': 'MathSub',
1932
+ '/': 'MathDiv',
1933
+ '*': 'MathMul',
1934
+ '%': 'MathMod',
1935
+ '??': 'Default'
1936
+ };
1937
+ const OperatorFn = {
1938
+ '>': 'Gt',
1939
+ '<': 'Lt',
1940
+ '>=': 'Gte',
1941
+ '<=': 'Lte',
1942
+ '==': 'Eq',
1943
+ '=': 'Eq',
1944
+ '!=': 'Neq'
1945
+ };
1946
+ const LogicalOperators = {
1947
+ '&&': 'And',
1948
+ 'AND': 'And',
1949
+ '||': 'Or',
1950
+ 'OR': 'Or'
1951
+ };
1952
+ const DOW_MAP = {
1953
+ 'MON': 'MONDAY',
1954
+ 'TUE': 'TUESDAY',
1955
+ 'WED': 'WEDNESDAY',
1956
+ 'THU': 'THURSDAY',
1957
+ 'THUR': 'THURSDAY',
1958
+ 'FRI': 'FRIDAY',
1959
+ 'SAT': 'SATURDAY',
1960
+ 'SUN': 'SUNDAY'
1961
+ };
1962
+ const VALID_DAYS = new Set([
1963
+ 'MONDAY',
1964
+ 'TUESDAY',
1965
+ 'WEDNESDAY',
1966
+ 'THURSDAY',
1967
+ 'FRIDAY',
1968
+ 'SATURDAY',
1969
+ 'SUNDAY'
1970
+ ]);
1971
+ const normalizeDow = text => {
1972
+ const upper = text.toUpperCase();
1973
+ if (upper in DOW_MAP) {
1974
+ return DOW_MAP[upper];
1975
+ }
1976
+ if (VALID_DAYS.has(upper)) {
1977
+ return upper;
1978
+ }
1979
+ throw new Error(`Invalid day of week: ${ text }`);
1980
+ };
1981
+ const Epsilon = 0.01;
1982
+ class RuleParser {
1983
+ static toAst(txt) {
1984
+ let ret;
1985
+ if (!ParserCache) {
1986
+ ParserCache = new Parser(ParserRules, { debug: false });
1987
+ }
1988
+ try {
1989
+ ret = ParserCache.getAST(txt.trim(), 'statement_main');
1990
+ } catch (e) {
1991
+ if (e instanceof ParsingError) {
1992
+ throw ErrorAnalyzer.analyzeParseFailure(txt, e);
1993
+ }
1994
+ throw e;
1995
+ }
1996
+ if (ret) {
1997
+ return ret.children[0];
1998
+ }
1999
+ throw ErrorAnalyzer.analyzeParseFailure(txt);
2000
+ }
2001
+ static _parseArgument(argument) {
2002
+ const child = argument.children[0];
2003
+ return RuleParser._buildExpressionGroup(child);
2004
+ }
2005
+ static _parseFcall(fcall) {
2006
+ const fname = fcall.children[0];
2007
+ const ret = [fname.text];
2008
+ if (fcall.children.length != 1) {
2009
+ const args = fcall.children[1];
2010
+ for (const a of args.children) {
2011
+ ret.push(RuleParser._parseArgument(a));
2012
+ }
2013
+ }
2014
+ return ret;
2015
+ }
2016
+ static _parseDowRange(dowRange) {
2017
+ if (dowRange.children.length === 1) {
2018
+ return {
2019
+ start: normalizeDow(dowRange.children[0].text),
2020
+ end: normalizeDow(dowRange.children[0].text)
2021
+ };
2022
+ } else if (dowRange.children.length === 2) {
2023
+ return {
2024
+ start: normalizeDow(dowRange.children[0].text),
2025
+ end: normalizeDow(dowRange.children[1].text)
2026
+ };
2027
+ } else {
2028
+ throw new Error(`Invalid dow_range with ${ dowRange.children.length } children`);
2029
+ }
2030
+ }
2031
+ static _addDowToTods(startTod, endTod, dowRange) {
2032
+ if (dowRange && dowRange.type === 'dow_range') {
2033
+ const dow = RuleParser._parseDowRange(dowRange);
2034
+ startTod.dow = dow.start;
2035
+ endTod.dow = dow.end;
2036
+ }
2037
+ }
2038
+ static _parseTimePeriod(tp) {
2039
+ switch (tp.type) {
2040
+ case 'time_period_const':
2041
+ if (tp.children && tp.children.length > 0 && tp.children[0].type === 'time_period_ago') {
2042
+ const timePeriodAgo = tp.children[0];
2043
+ let totalSeconds = 0;
2044
+ const components = [];
2045
+ for (const child of timePeriodAgo.children) {
2046
+ if (child.type === 'number_time') {
2047
+ const number = parseFloat(child.children[0].text);
2048
+ const unit = child.children[1].text.toUpperCase();
2049
+ components.push([
2050
+ number,
2051
+ unit
2052
+ ]);
2053
+ totalSeconds += RuleParser.__parseValue(child);
2054
+ }
2055
+ }
2056
+ if (components.length === 1) {
2057
+ return [
2058
+ 'TimePeriodConstAgo',
2059
+ components[0][0],
2060
+ components[0][1]
2061
+ ];
2062
+ } else {
2063
+ return [
2064
+ 'TimePeriodConstAgo',
2065
+ totalSeconds,
2066
+ 'SECONDS'
2067
+ ];
2068
+ }
2069
+ }
2070
+ return [
2071
+ 'TimePeriodConst',
2072
+ tp.text
2073
+ ];
2074
+ case 'time_period_ago_between': {
2075
+ let totalSeconds = 0;
2076
+ let betweenTodOnly = null;
2077
+ for (let i = 0; i < tp.children.length; i++) {
2078
+ if (tp.children[i].type === 'number_time') {
2079
+ totalSeconds += RuleParser.__parseValue(tp.children[i]);
2080
+ } else if (tp.children[i].type === 'between_tod_only') {
2081
+ betweenTodOnly = tp.children[i];
2082
+ }
2083
+ }
2084
+ if (!betweenTodOnly) {
2085
+ throw new Error('time_period_ago_between requires between_tod_only child');
2086
+ }
2087
+ const betweenTod = betweenTodOnly.children[0];
2088
+ let startTod = RuleParser.__parseValue(betweenTod.children[0]);
2089
+ let endTod = RuleParser.__parseValue(betweenTod.children[1]);
2090
+ if (betweenTod.children.length > 2) {
2091
+ RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2]);
2092
+ }
2093
+ return [
2094
+ 'TimePeriodBetweenAgo',
2095
+ totalSeconds,
2096
+ startTod,
2097
+ endTod
2098
+ ];
2099
+ }
2100
+ case 'between_tod_only': {
2101
+ const betweenTod = tp.children[0];
2102
+ let startTod = RuleParser.__parseValue(betweenTod.children[0]);
2103
+ let endTod = RuleParser.__parseValue(betweenTod.children[1]);
2104
+ if (betweenTod.children.length > 2) {
2105
+ if (typeof startTod === 'number')
2106
+ startTod = {
2107
+ seconds: startTod,
2108
+ dow: null
2109
+ };
2110
+ if (typeof endTod === 'number')
2111
+ endTod = {
2112
+ seconds: endTod,
2113
+ dow: null
2114
+ };
2115
+ RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2]);
2116
+ }
2117
+ return [
2118
+ 'TimePeriodBetween',
2119
+ startTod,
2120
+ endTod
2121
+ ];
2122
+ }
2123
+ case 'between_time_only': {
2124
+ const betweenNumberTime = tp.children[0];
2125
+ const startValue = RuleParser.__parseValue(betweenNumberTime.children[0]);
2126
+ const endValue = RuleParser.__parseValue(betweenNumberTime.children[1]);
2127
+ if (betweenNumberTime.children.length > 2 && betweenNumberTime.children[2].type === 'dow_range') {
2128
+ const dow = RuleParser._parseDowRange(betweenNumberTime.children[2]);
2129
+ if (dow.start === dow.end) {
2130
+ return [
2131
+ 'TimePeriodBetween',
2132
+ startValue,
2133
+ endValue,
2134
+ dow.start
2135
+ ];
2136
+ } else {
2137
+ return [
2138
+ 'TimePeriodBetween',
2139
+ startValue,
2140
+ endValue,
2141
+ dow.start,
2142
+ dow.end
2143
+ ];
2144
+ }
2145
+ }
2146
+ return [
2147
+ 'TimePeriodBetween',
2148
+ startValue,
2149
+ endValue
2150
+ ];
2151
+ }
2152
+ }
2153
+ }
2154
+ static __parseValue(child) {
2155
+ const type = child.type;
2156
+ switch (type) {
2157
+ case 'string': {
2158
+ const str = child.text;
2159
+ return str.slice(1, -1);
2160
+ }
2161
+ case 'number':
2162
+ return parseFloat(child.text);
2163
+ case 'number_tod': {
2164
+ const tokens = child.text.split(':');
2165
+ if (tokens.length !== 2) {
2166
+ throw new Error(`Invalid time of day, ${ child.text } should be ##:##`);
2167
+ }
2168
+ const hours = parseInt(tokens[0]);
2169
+ const minutes = parseInt(tokens[1]);
2170
+ const tod = hours * 100 + minutes;
2171
+ const ret = {
2172
+ hours,
2173
+ minutes,
2174
+ tod
2175
+ };
2176
+ if (!isNaN(tod) && ret.hours >= 0 && ret.hours < 24 && ret.minutes >= 0 && ret.minutes < 60) {
2177
+ return ret;
2178
+ }
2179
+ throw new Error(`Invalid time of day, ${ child.text } -> [${ tokens.join(', ') }] -> ${ hours }h${ minutes }m -> ${ tod }`);
2180
+ }
2181
+ case 'number_time': {
2182
+ const nt = child;
2183
+ const mult = parseFloat(nt.children[0].text);
2184
+ switch (nt.children[1].text.toUpperCase()) {
2185
+ case 'SECONDS':
2186
+ case 'SECOND':
2187
+ return mult;
2188
+ case 'MINUTES':
2189
+ case 'MINUTE':
2190
+ case 'MINS':
2191
+ case 'MIN':
2192
+ return mult * 60;
2193
+ case 'HOURS':
2194
+ case 'HOUR':
2195
+ return mult * 60 * 60;
2196
+ case 'DAYS':
2197
+ case 'DAY':
2198
+ return mult * 60 * 60 * 24;
2199
+ case 'WEEKS':
2200
+ case 'WEEK':
2201
+ return mult * 60 * 60 * 24 * 7;
2202
+ }
2203
+ throw new Error(`Invalid exponent ${ nt.children[1].text }`);
2204
+ }
2205
+ case 'true':
2206
+ return true;
2207
+ case 'false':
2208
+ return false;
2209
+ case 'array': {
2210
+ const ret = [];
2211
+ for (const c of child.children) {
2212
+ ret.push(RuleParser.__parseValue(c.children[0]));
2213
+ }
2214
+ return ret;
2215
+ }
2216
+ default:
2217
+ throw new Error(`Unknown value type ${ type }`);
2218
+ }
2219
+ }
2220
+ static _parseValue(value) {
2221
+ const child = value.children[0];
2222
+ const type = child.type;
2223
+ switch (type) {
2224
+ case 'time_period': {
2225
+ const tp = child.children[0];
2226
+ return RuleParser._parseTimePeriod(tp);
2227
+ }
2228
+ default:
2229
+ return [
2230
+ 'Value',
2231
+ RuleParser.__parseValue(child)
2232
+ ];
2233
+ }
2234
+ }
2235
+ static _parseSimpleResult(result) {
2236
+ const child = result.children[0];
2237
+ const type = child.type;
2238
+ switch (type) {
2239
+ case 'fcall':
2240
+ return RuleParser._parseFcall(child);
2241
+ case 'value':
2242
+ return RuleParser._parseValue(child);
2243
+ }
2244
+ return null;
2245
+ }
2246
+ static _parseArithmeticOperand(operand) {
2247
+ const child = operand.children[0];
2248
+ const type = child.type;
2249
+ switch (type) {
2250
+ case 'fcall':
2251
+ return RuleParser._parseFcall(child);
2252
+ case 'number':
2253
+ return [
2254
+ 'Value',
2255
+ parseFloat(child.text)
2256
+ ];
2257
+ case 'number_time':
2258
+ return [
2259
+ 'Value',
2260
+ RuleParser.__parseValue(child)
2261
+ ];
2262
+ }
2263
+ throw new Error(`Unknown arithmetic operand type ${ type }`);
2264
+ }
2265
+ static _isConstantValue(expr) {
2266
+ return Array.isArray(expr) && expr.length === 2 && expr[0] === 'Value';
2267
+ }
2268
+ static _isConstantNumberValue(expr) {
2269
+ return Array.isArray(expr) && expr.length === 2 && expr[0] === 'Value' && typeof expr[1] === 'number';
2270
+ }
2271
+ static _evaluateConstantArithmetic(operator, leftValue, rightValue) {
2272
+ switch (operator) {
2273
+ case 'MathAdd':
2274
+ return leftValue + rightValue;
2275
+ case 'MathSub':
2276
+ return leftValue - rightValue;
2277
+ case 'MathMul':
2278
+ return leftValue * rightValue;
2279
+ case 'MathDiv':
2280
+ return leftValue / rightValue;
2281
+ case 'MathMod':
2282
+ return leftValue % rightValue;
2283
+ default:
2284
+ return null;
2285
+ }
2286
+ }
2287
+ static _parseArithmeticResult(result) {
2288
+ const partA = RuleParser._parseArithmeticOperand(result.children[0]);
2289
+ const operatorFn = ArithmeticOperators[result.children[1].text];
2290
+ const partB = RuleParser.__parseArithmeticResult(result, 2);
2291
+ if (RuleParser._isConstantNumberValue(partA) && RuleParser._isConstantNumberValue(partB)) {
2292
+ const result = RuleParser._evaluateConstantArithmetic(operatorFn, partA[1], partB[1]);
2293
+ if (result !== null) {
2294
+ return [
2295
+ 'Value',
2296
+ result
2297
+ ];
2298
+ }
2299
+ }
2300
+ return [
2301
+ operatorFn,
2302
+ partA,
2303
+ partB
2304
+ ];
2305
+ }
2306
+ static __parseArithmeticResult(result, idx) {
2307
+ const child = result.children[idx];
2308
+ const type = child.type;
2309
+ switch (type) {
2310
+ case 'arithmetic_operand':
2311
+ return RuleParser._parseArithmeticOperand(child);
2312
+ case 'arithmetic_result':
2313
+ return RuleParser._parseArithmeticResult(child);
2314
+ }
2315
+ throw new Error(`Unknown arithmetic result node ${ type }`);
2316
+ }
2317
+ static __parseResult(result, idx) {
2318
+ const child = result.children[idx];
2319
+ const type = child.type;
2320
+ switch (type) {
2321
+ case 'simple_result':
2322
+ return RuleParser._parseSimpleResult(child);
2323
+ case 'arithmetic_result':
2324
+ return RuleParser._parseArithmeticResult(child);
2325
+ }
2326
+ throw new Error(`Unknown result node ${ type }`);
2327
+ }
2328
+ static _parseResult(result) {
2329
+ return RuleParser.__parseResult(result, 0);
2330
+ }
2331
+ static _parseStdExpression(expr) {
2332
+ switch (expr.children.length) {
2333
+ case 1:
2334
+ return RuleParser._parseResult(expr.children[0]);
2335
+ case 2: {
2336
+ const rhs = expr.children[1];
2337
+ switch (rhs.type) {
2338
+ case 'between_tod': {
2339
+ const startTod = RuleParser.__parseValue(rhs.children[0]);
2340
+ const endTod = RuleParser.__parseValue(rhs.children[1]);
2341
+ if (rhs.children.length > 2) {
2342
+ RuleParser._addDowToTods(startTod, endTod, rhs.children[2]);
2343
+ }
2344
+ return [
2345
+ 'Between',
2346
+ RuleParser._parseResult(expr.children[0]),
2347
+ [
2348
+ 'Value',
2349
+ startTod
2350
+ ],
2351
+ [
2352
+ 'Value',
2353
+ endTod
2354
+ ]
2355
+ ];
2356
+ }
2357
+ case 'between': {
2358
+ const betweenChild = rhs.children[0];
2359
+ if (betweenChild.type === 'between_tod') {
2360
+ const startTod = RuleParser.__parseValue(betweenChild.children[0]);
2361
+ const endTod = RuleParser.__parseValue(betweenChild.children[1]);
2362
+ if (betweenChild.children.length > 2) {
2363
+ RuleParser._addDowToTods(startTod, endTod, betweenChild.children[2]);
2364
+ }
2365
+ return [
2366
+ 'Between',
2367
+ RuleParser._parseResult(expr.children[0]),
2368
+ [
2369
+ 'Value',
2370
+ startTod
2371
+ ],
2372
+ [
2373
+ 'Value',
2374
+ endTod
2375
+ ]
2376
+ ];
2377
+ } else {
2378
+ return [
2379
+ 'Between',
2380
+ RuleParser._parseResult(expr.children[0]),
2381
+ [
2382
+ 'Value',
2383
+ RuleParser.__parseValue(betweenChild.children[0])
2384
+ ],
2385
+ [
2386
+ 'Value',
2387
+ RuleParser.__parseValue(betweenChild.children[1])
2388
+ ]
2389
+ ];
2390
+ }
2391
+ }
2392
+ case 'between_number':
2393
+ return [
2394
+ 'Between',
2395
+ RuleParser._parseResult(expr.children[0]),
2396
+ [
2397
+ 'Value',
2398
+ RuleParser.__parseValue(rhs.children[0].children[0])
2399
+ ],
2400
+ [
2401
+ 'Value',
2402
+ RuleParser.__parseValue(rhs.children[0].children[1])
2403
+ ]
2404
+ ];
2405
+ case 'basic_rhs':
2406
+ return [
2407
+ OperatorFn[rhs.children[0].text],
2408
+ RuleParser._parseResult(expr.children[0]),
2409
+ RuleParser._parseResult(rhs.children[1])
2410
+ ];
2411
+ case 'eq_approx': {
2412
+ const rhsValue = RuleParser._parseResult(rhs.children[1]);
2413
+ const ret = [
2414
+ 'Between',
2415
+ RuleParser._parseResult(expr.children[0]),
2416
+ [
2417
+ 'Value',
2418
+ rhsValue[1] - Epsilon
2419
+ ],
2420
+ [
2421
+ 'Value',
2422
+ rhsValue[1] + Epsilon
2423
+ ]
2424
+ ];
2425
+ if (rhs.children[0].text === '!=') {
2426
+ return [
2427
+ 'Not',
2428
+ ret
2429
+ ];
2430
+ }
2431
+ return ret;
2432
+ }
2433
+ case 'in_expr': {
2434
+ const args = rhs.children[0];
2435
+ const haystack = [];
2436
+ for (const a of args.children) {
2437
+ haystack.push(RuleParser._parseArgument(a));
2438
+ }
2439
+ const needle = RuleParser._parseResult(expr.children[0]);
2440
+ const allConstants = haystack.every(item => RuleParser._isConstantValue(item));
2441
+ let haystackExpr;
2442
+ if (allConstants) {
2443
+ const constantArray = haystack.map(item => item[1]);
2444
+ haystackExpr = [
2445
+ 'Value',
2446
+ constantArray
2447
+ ];
2448
+ } else {
2449
+ haystackExpr = [
2450
+ 'Array',
2451
+ ...haystack
2452
+ ];
2453
+ }
2454
+ return [
2455
+ 'ArrayIn',
2456
+ haystackExpr,
2457
+ needle
2458
+ ];
2459
+ }
2460
+ default:
2461
+ throw new Error(`unable to parse std expression, unknown rhs type ${ rhs.type }`);
2462
+ }
2463
+ }
2464
+ default:
2465
+ throw new Error(`unable to parse std expression, unknown number of children ${ expr.children.length }`);
2466
+ }
2467
+ }
2468
+ static buildLogical(members, fn) {
2469
+ return [
2470
+ fn,
2471
+ ...members
2472
+ ];
2473
+ }
2474
+ static _buildExpressionGroup(ast) {
2475
+ let ret = [];
2476
+ let currentLogical = null;
2477
+ for (const expr of ast.children) {
2478
+ if (expr.type == 'logical_operator') {
2479
+ const logicalOperator = expr.text.trim();
2480
+ const operatorFn = LogicalOperators[logicalOperator.toUpperCase()];
2481
+ if (currentLogical === null || currentLogical !== operatorFn) {
2482
+ if (ret.length > 1) {
2483
+ ret = [RuleParser.buildLogical(ret, currentLogical)];
2484
+ }
2485
+ currentLogical = operatorFn;
2486
+ }
2487
+ } else {
2488
+ ret.push(RuleParser._exprToIL(expr));
2489
+ }
2490
+ }
2491
+ if (ret.length == 0) {
2492
+ throw new Error('invalid rule');
2493
+ }
2494
+ if (ret.length == 1) {
2495
+ return ret[0];
2496
+ }
2497
+ return RuleParser.buildLogical(ret, currentLogical);
2498
+ }
2499
+ static _parseParenthesisExpression(expr) {
2500
+ return RuleParser._buildExpressionGroup(expr.children[0]);
2501
+ }
2502
+ static _exprToIL(expr) {
2503
+ const eInner = expr.children[0];
2504
+ switch (eInner.type) {
2505
+ case 'standard_expression':
2506
+ return RuleParser._parseStdExpression(eInner);
2507
+ case 'not_expression': {
2508
+ const child = eInner.children[0];
2509
+ let result;
2510
+ switch (child.type) {
2511
+ case 'parenthesis_expression':
2512
+ result = RuleParser._parseParenthesisExpression(child);
2513
+ break;
2514
+ default:
2515
+ result = RuleParser._parseResult(child);
2516
+ }
2517
+ return [
2518
+ 'Not',
2519
+ result
2520
+ ];
2521
+ }
2522
+ case 'parenthesis_expression':
2523
+ return RuleParser._parseParenthesisExpression(eInner);
2524
+ default:
2525
+ throw new Error(`unknown type of expression ${ eInner.type }`);
2526
+ }
2527
+ }
2528
+ static toIL(txt) {
2529
+ try {
2530
+ const ast = RuleParser.toAst(txt);
2531
+ if (!ast)
2532
+ throw new Error(`failed to parse ${ txt }`);
2533
+ return RuleParser._buildExpressionGroup(ast);
2534
+ } catch (e) {
2535
+ if (e.name === 'RuleParseError') {
2536
+ throw e;
2537
+ }
2538
+ if (e.message && e.message.includes('Invalid time of day')) {
2539
+ const match = e.message.match(/Invalid time of day[,:]?\s*([0-9:]+)/);
2540
+ const badTod = match ? match[1] : 'invalid';
2541
+ const {ParsingError} = require('ebnf');
2542
+ const {RuleParseError} = require('./errors/RuleParseError');
2543
+ const lines = txt.trim().split('\n');
2544
+ const position = {
2545
+ line: lines.length,
2546
+ column: lines[lines.length - 1].length + 1,
2547
+ offset: txt.trim().length
2548
+ };
2549
+ throw new RuleParseError('BAD_TOD', `Invalid time of day: ${ badTod }`, 'Time of day must be in HH:MM format with hours 0-23 and minutes 0-59, e.g. 08:30, 14:00, 23:59.', position, badTod, ['HH:MM'], txt.trim().substring(Math.max(0, txt.trim().length - 50)));
2550
+ }
2551
+ if (e.message && e.message.includes('Invalid day of week')) {
2552
+ const match = e.message.match(/Invalid day of week[,:]?\s*(\w+)/);
2553
+ const badDow = match ? match[1] : 'invalid';
2554
+ const {RuleParseError} = require('./errors/RuleParseError');
2555
+ const lines = txt.trim().split('\n');
2556
+ const position = {
2557
+ line: lines.length,
2558
+ column: lines[lines.length - 1].length + 1,
2559
+ offset: txt.trim().length
2560
+ };
2561
+ throw new RuleParseError('BAD_DOW', `Invalid day of week: ${ badDow }`, 'Valid days are: MONDAY/MON, TUESDAY/TUE, WEDNESDAY/WED, THURSDAY/THU, FRIDAY/FRI, SATURDAY/SAT, SUNDAY/SUN.', position, badDow, [
2562
+ 'MONDAY',
2563
+ 'TUESDAY',
2564
+ 'WEDNESDAY',
2565
+ 'THURSDAY',
2566
+ 'FRIDAY',
2567
+ 'SATURDAY',
2568
+ 'SUNDAY'
2569
+ ], txt.trim().substring(Math.max(0, txt.trim().length - 50)));
2570
+ }
2571
+ throw e;
2572
+ }
2573
+ }
2574
+ }
2575
+ module.exports = RuleParser;
2576
+ module.exports.ParsingError = require('ebnf').ParsingError;
2577
+ module.exports.RuleParseError = require('./errors/RuleParseError').RuleParseError;
2578
+ },{"./RuleParser.production.ebnf.js":11,"./errors/ErrorAnalyzer":13,"./errors/RuleParseError":14,"ebnf":9,"ebnf/dist/Parser.js":5}],13:[function(require,module,exports){
2579
+ const { ParsingError } = require('ebnf');
2580
+ const { RuleParseError } = require('./RuleParseError');
2581
+
2582
+ /**
2583
+ * Analyzes parsing errors and maps them to user-friendly error codes
2584
+ */
2585
+ class ErrorAnalyzer {
2586
+ /**
2587
+ * Analyze a ParsingError and return a user-friendly RuleParseError
2588
+ * @param {string} input - The input string that failed to parse
2589
+ * @param {ParsingError} parsingError - The ParsingError from the parser (optional)
2590
+ * @returns {RuleParseError} - A user-friendly error with error code
2591
+ */
2592
+ static analyzeParseFailure(input, parsingError = null) {
2593
+ const trimmedInput = input.trim();
2594
+
2595
+ // Check for empty or missing expression first (fast path)
2596
+ if (!trimmedInput) {
2597
+ const position = { line: 1, column: 1, offset: 0 };
2598
+ return new RuleParseError(
2599
+ "MISSING_EXPRESSION",
2600
+ "Empty or missing expression.",
2601
+ "Provide a valid rule expression.",
2602
+ position,
2603
+ "empty input",
2604
+ ["expression"],
2605
+ ""
2606
+ );
2607
+ }
2608
+
2609
+ // If we have a ParsingError, use its information
2610
+ if (parsingError && parsingError instanceof ParsingError) {
2611
+ return this._analyzeFromParsingError(input, parsingError);
2612
+ }
2613
+
2614
+ // Fallback to string-based analysis (for validation errors)
2615
+ const lines = trimmedInput.split('\n');
2616
+ const line = lines.length;
2617
+ const lastLine = lines[lines.length - 1] || '';
2618
+ const column = lastLine.length + 1;
2619
+ const offset = trimmedInput.length;
2620
+
2621
+ const position = { line, column, offset };
2622
+
2623
+ // Get snippet (last 50 chars or the whole input if shorter)
2624
+ const snippetStart = Math.max(0, trimmedInput.length - 50);
2625
+ const snippet = (snippetStart > 0 ? '...' : '') + trimmedInput.substring(snippetStart);
2626
+
2627
+ // Analyze the error pattern
2628
+ const errorInfo = this._detectErrorPattern(trimmedInput, position, snippet);
2629
+
2630
+ return new RuleParseError(
2631
+ errorInfo.code,
2632
+ errorInfo.message,
2633
+ errorInfo.hint,
2634
+ position,
2635
+ errorInfo.found,
2636
+ errorInfo.expected,
2637
+ snippet
2638
+ );
2639
+ }
2640
+
2641
+ /**
2642
+ * Analyze error using ParsingError exception data
2643
+ * @private
2644
+ */
2645
+ static _analyzeFromParsingError(input, err) {
2646
+ const position = err.position;
2647
+ const expected = err.expected || [];
2648
+ const found = err.found || '';
2649
+ const failureTree = err.failureTree || [];
2650
+
2651
+ // Get snippet around error position
2652
+ const snippetStart = Math.max(0, position.offset - 20);
2653
+ const snippetEnd = Math.min(input.length, position.offset + 30);
2654
+ const snippet = (snippetStart > 0 ? '...' : '') +
2655
+ input.substring(snippetStart, snippetEnd) +
2656
+ (snippetEnd < input.length ? '...' : '');
2657
+
2658
+ // Analyze what was expected to determine error type using failureTree
2659
+ const errorInfo = this._detectErrorFromFailureTree(input, position, expected, found, failureTree);
2660
+
2661
+ return new RuleParseError(
2662
+ errorInfo.code,
2663
+ errorInfo.message,
2664
+ errorInfo.hint,
2665
+ position,
2666
+ found,
2667
+ expected,
2668
+ snippet
2669
+ );
2670
+ }
2671
+
2672
+ /**
2673
+ * Detect error type from failureTree
2674
+ * @private
2675
+ */
2676
+ static _detectErrorFromFailureTree(input, position, expected, found, failureTree) {
2677
+ const beforeError = input.substring(0, position.offset);
2678
+
2679
+ // Helper to check if a name exists in the failure tree
2680
+ const hasFailure = (name) => {
2681
+ const search = (nodes) => {
2682
+ if (!Array.isArray(nodes)) return false;
2683
+ for (const node of nodes) {
2684
+ if (node.name === name) return true;
2685
+ if (node.children && search(node.children)) return true;
2686
+ }
2687
+ return false;
2688
+ };
2689
+ return search(failureTree);
2690
+ };
2691
+
2692
+ // Helper to check if a name exists at top level only
2693
+ const hasTopLevelFailure = (name) => {
2694
+ if (!Array.isArray(failureTree)) return false;
2695
+ return failureTree.some(node => node.name === name);
2696
+ };
2697
+
2698
+ // PRIORITY 1: Check for unterminated string FIRST (before any balance checks)
2699
+ // This must be checked early because unclosed strings can confuse other detections
2700
+ if (this._hasUnterminatedString(input)) {
2701
+ return {
2702
+ code: "UNTERMINATED_STRING",
2703
+ message: "Unterminated string literal.",
2704
+ hint: "String literals must be enclosed in double quotes, e.g. \"hello world\".",
2705
+ };
2706
+ }
2707
+
2708
+ // PRIORITY 2: Check for bad number format
2709
+ const badNum = this._findBadNumber(input);
2710
+ if (badNum) {
2711
+ return {
2712
+ code: "BAD_NUMBER",
2713
+ message: `Invalid number format: ${badNum}`,
2714
+ hint: "Numbers must be valid integers or decimals, e.g. 42, 3.14, -5, 1.5e10.",
2715
+ };
2716
+ }
2717
+
2718
+ // PRIORITY 3: Check for token-level issues (extra characters after valid tokens)
2719
+ // This must be checked before END_ARGUMENT/END_ARRAY to catch cases like WEDNESDAYY
2720
+ const tokenError = this._detectExtraCharactersAfterToken(input, position, beforeError, found);
2721
+ if (tokenError) {
2722
+ return tokenError;
2723
+ }
2724
+
2725
+ // PRIORITY 4: Check for specific parser contexts (function calls, arrays)
2726
+ // But first check if we have bracket/paren imbalance that's more specific
2727
+
2728
+ // Check bracket balance first for extra brackets
2729
+ const bracketBalance = this._checkBracketBalance(input);
2730
+ if (bracketBalance < 0) {
2731
+ // Extra closing bracket - this is more specific than function call context
2732
+ return {
2733
+ code: "UNMATCHED_BRACKET",
2734
+ message: "Extra closing bracket detected.",
2735
+ hint: "Every closing ']' must have a matching opening '['. Remove the extra closing bracket.",
2736
+ };
2737
+ }
2738
+
2739
+ // Check paren balance for certain cases
2740
+ const parenBalance = this._checkParenBalance(input);
2741
+ if (parenBalance < 0) {
2742
+ // Extra closing paren - this is more specific than function call context
2743
+ return {
2744
+ code: "UNMATCHED_PAREN",
2745
+ message: "Extra closing parenthesis detected.",
2746
+ hint: "Every closing ')' must have a matching opening '('. Remove the extra closing parenthesis.",
2747
+ };
2748
+ }
2749
+
2750
+ // Check if parser was expecting END_ARGUMENT (closing paren for function)
2751
+ // This indicates we're inside a function call context
2752
+ if (hasFailure('END_ARGUMENT')) {
2753
+ return {
2754
+ code: "BAD_FUNCTION_CALL",
2755
+ message: "Invalid function call syntax.",
2756
+ hint: "Function calls must have matching parentheses, e.g. func() or func(arg1, arg2).",
2757
+ };
2758
+ }
2759
+
2760
+ // Check if parser was expecting END_ARRAY (closing bracket)
2761
+ // This indicates we're inside an array context
2762
+ if (hasFailure('END_ARRAY')) {
2763
+ return {
2764
+ code: "BAD_ARRAY_SYNTAX",
2765
+ message: "Invalid array syntax.",
2766
+ hint: "Arrays must be enclosed in brackets with comma-separated values, e.g. [1, 2, 3].",
2767
+ };
2768
+ }
2769
+
2770
+ // PRIORITY 5: Check for dangling operators
2771
+
2772
+ // Check for dangling logical operator (check input pattern directly)
2773
+ if (found === "end of input") {
2774
+ const trimmed = input.trim();
2775
+ if (/&&\s*$/.test(trimmed)) {
2776
+ return {
2777
+ code: "DANGLING_LOGICAL_OPERATOR",
2778
+ message: "Logical operator '&&' at end of expression.",
2779
+ hint: "Logical operators (&&, ||, AND, OR) must be followed by an expression.",
2780
+ };
2781
+ }
2782
+ if (/\|\|\s*$/.test(trimmed)) {
2783
+ return {
2784
+ code: "DANGLING_LOGICAL_OPERATOR",
2785
+ message: "Logical operator '||' at end of expression.",
2786
+ hint: "Logical operators (&&, ||, AND, OR) must be followed by an expression.",
2787
+ };
2788
+ }
2789
+ if (/\bAND\b\s*$/i.test(trimmed)) {
2790
+ return {
2791
+ code: "DANGLING_LOGICAL_OPERATOR",
2792
+ message: "Logical operator 'AND' at end of expression.",
2793
+ hint: "Logical operators (&&, ||, AND, OR) must be followed by an expression.",
2794
+ };
2795
+ }
2796
+ if (/\bOR\b\s*$/i.test(trimmed)) {
2797
+ return {
2798
+ code: "DANGLING_LOGICAL_OPERATOR",
2799
+ message: "Logical operator 'OR' at end of expression.",
2800
+ hint: "Logical operators (&&, ||, AND, OR) must be followed by an expression.",
2801
+ };
2802
+ }
2803
+ }
2804
+
2805
+ // Check for BETWEEN - if we see BETWEEN in input with incomplete syntax
2806
+ if (/\bBETWEEN\b/i.test(input)) {
2807
+ // Check if BETWEEN is incomplete or missing AND
2808
+ if (found === "end of input" || /\bBETWEEN\s+\d+\s*$/i.test(beforeError.trim())) {
2809
+ return {
2810
+ code: "BAD_BETWEEN_SYNTAX",
2811
+ message: "Invalid BETWEEN syntax.",
2812
+ hint: "BETWEEN requires two values: 'expr BETWEEN value1 AND value2' or 'expr BETWEEN value1-value2'.",
2813
+ };
2814
+ }
2815
+ }
2816
+
2817
+ // Check if parser expected BEGIN_ARGUMENT - could be missing RHS after operator
2818
+ if (hasTopLevelFailure('BEGIN_ARGUMENT')) {
2819
+ const trimmed = beforeError.trim();
2820
+ // If found is a comparison operator, this is likely missing RHS
2821
+ if (found.match(/^[><=!]/)) {
2822
+ return {
2823
+ code: "MISSING_RHS_AFTER_OPERATOR",
2824
+ message: `Expected a value after '${found}'.`,
2825
+ hint: "Comparison operators must be followed by a value, e.g. temp > 25, name == \"bob\".",
2826
+ };
2827
+ }
2828
+
2829
+ // Also check if beforeError ends with comparison operator
2830
+ const compOps = ['>=', '<=', '==', '!=', '>', '<', '=', '==~', '!=~'];
2831
+ for (const op of compOps) {
2832
+ if (trimmed.endsWith(op)) {
2833
+ return {
2834
+ code: "MISSING_RHS_AFTER_OPERATOR",
2835
+ message: `Expected a value after '${op}'.`,
2836
+ hint: "Comparison operators must be followed by a value, e.g. temp > 25, name == \"bob\".",
2837
+ };
2838
+ }
2839
+ }
2840
+ }
2841
+
2842
+ // Check for value/result expected (could be missing RHS)
2843
+ if ((hasFailure('value') || hasFailure('result') || hasFailure('simple_result')) && found === "end of input") {
2844
+ // Check for comparison operator
2845
+ const trimmed = beforeError.trim();
2846
+ const compOps = ['>=', '<=', '==', '!=', '>', '<', '=', '==~', '!=~'];
2847
+
2848
+ for (const op of compOps) {
2849
+ if (trimmed.endsWith(op)) {
2850
+ return {
2851
+ code: "MISSING_RHS_AFTER_OPERATOR",
2852
+ message: `Expected a value after '${op}'.`,
2853
+ hint: "Comparison operators must be followed by a value, e.g. temp > 25, name == \"bob\".",
2854
+ };
2855
+ }
2856
+ }
2857
+ }
2858
+
2859
+ // PRIORITY 6: Final fallback - general balance checks for unclosed parens/brackets
2860
+ // (Note: extra closing parens/brackets were already checked in PRIORITY 4)
2861
+
2862
+ // Check for unclosed parentheses (only if not already handled)
2863
+ if (parenBalance > 0 && found === "end of input") {
2864
+ // Check if this looks like a function call context
2865
+ const looksLikeFunction = /[a-zA-Z_][a-zA-Z0-9_]*\s*\([^)]*$/.test(input);
2866
+ const message = looksLikeFunction
2867
+ ? "Unclosed parenthesis in function call."
2868
+ : "Unclosed parenthesis detected.";
2869
+
2870
+ return {
2871
+ code: "UNMATCHED_PAREN",
2872
+ message: message,
2873
+ hint: "Every opening '(' must have a matching closing ')'. Check your expression for missing closing parentheses.",
2874
+ };
2875
+ }
2876
+
2877
+ // Check for unclosed brackets (only if not already handled)
2878
+ if (bracketBalance > 0 && found === "end of input") {
2879
+ // Check if this looks like an array context
2880
+ const looksLikeArray = /\[[^\]]*$/.test(input);
2881
+ const message = looksLikeArray
2882
+ ? "Unclosed bracket in array."
2883
+ : "Unclosed bracket detected.";
2884
+
2885
+ return {
2886
+ code: "UNMATCHED_BRACKET",
2887
+ message: message,
2888
+ hint: "Every opening '[' must have a matching closing ']'. Check your array syntax.",
2889
+ };
2890
+ }
2891
+
2892
+ // Default to unexpected token
2893
+ const foundDesc = found === "end of input" ? "end of input" : `'${found}'`;
2894
+ return {
2895
+ code: "UNEXPECTED_TOKEN",
2896
+ message: `Unexpected ${foundDesc} at position ${position.offset}.`,
2897
+ hint: "Check your syntax. Common issues include missing operators, invalid characters, or malformed expressions.",
2898
+ };
2899
+ }
2900
+
2901
+ /**
2902
+ * Detect extra characters after a valid token (e.g., WEDNESDAYY has extra Y after WEDNESDAY)
2903
+ * @private
2904
+ */
2905
+ static _detectExtraCharactersAfterToken(input, position, beforeError, found) {
2906
+ // First, check for invalid day of week patterns
2907
+ // Pattern: "ON" followed by partial day name
2908
+ const onMatch = /\bON\s+(\w+)$/i.exec(beforeError);
2909
+ if (onMatch) {
2910
+ const partial = onMatch[1].toUpperCase();
2911
+ // Check if this looks like a partial day name or invalid day
2912
+ // Note: THUR is not included because it's not actually supported by the parser
2913
+ const validDays = ['MONDAY', 'MON', 'TUESDAY', 'TUE', 'WEDNESDAY', 'WED',
2914
+ 'THURSDAY', 'THU', 'FRIDAY', 'FRI', 'SATURDAY',
2915
+ 'SAT', 'SUNDAY', 'SUN'];
2916
+
2917
+ if (!validDays.includes(partial)) {
2918
+ // This might be part of an invalid day name
2919
+ // Combine with found to get the full attempted day name if found is alphabetic
2920
+ const fullAttempt = partial + (found && found !== "end of input" && found.match(/^[A-Z]/i) ? found : "");
2921
+
2922
+ // Check if it looks like a misspelled day
2923
+ const possibleMisspelling = validDays.some(day => {
2924
+ // Check for similarity (starts with same letter, length close)
2925
+ return day.startsWith(partial.charAt(0)) &&
2926
+ Math.abs(day.length - fullAttempt.length) <= 3;
2927
+ });
2928
+
2929
+ if (possibleMisspelling || fullAttempt.length >= 3) {
2930
+ return {
2931
+ code: "BAD_DOW",
2932
+ message: `Invalid day of week: ${fullAttempt}`,
2933
+ hint: "Valid days are: MONDAY/MON, TUESDAY/TUE, WEDNESDAY/WED, THURSDAY/THU, FRIDAY/FRI, SATURDAY/SAT, SUNDAY/SUN.",
2934
+ };
2935
+ }
2936
+ }
2937
+ }
2938
+
2939
+ // Check for time of day patterns right before error
2940
+ const todMatch = /(\d{1,2}):(-?\d{1,2})?$/.exec(beforeError);
2941
+ if (todMatch || (beforeError.match(/\d+:$/) && found && found.match(/^-?\d+/))) {
2942
+ // Looks like we're in a time of day context
2943
+ // Extract the full time attempt
2944
+ let fullTime = todMatch ? todMatch[0] : '';
2945
+ if (!todMatch && beforeError.match(/\d+:$/)) {
2946
+ // We have a number: before and digits after
2947
+ const num = beforeError.match(/\d+:$/)[0];
2948
+ const minutes = found.match(/^-?\d+/)[0];
2949
+ fullTime = num + minutes;
2950
+ }
2951
+
2952
+ // Check if it's a bad time
2953
+ const timePattern = /(\d{1,2}):(-?\d{1,2})/;
2954
+ const timeMatch = timePattern.exec(fullTime);
2955
+ if (timeMatch) {
2956
+ const hours = parseInt(timeMatch[1], 10);
2957
+ const minutes = parseInt(timeMatch[2], 10);
2958
+ if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
2959
+ return {
2960
+ code: "BAD_TOD",
2961
+ message: `Invalid time of day: ${timeMatch[0]}`,
2962
+ hint: "Time of day must be in HH:MM format with hours 0-23 and minutes 0-59, e.g. 08:30, 14:00, 23:59.",
2963
+ };
2964
+ }
2965
+ }
2966
+ }
2967
+
2968
+ // List of known valid tokens (keywords, days of week, etc.)
2969
+ const validTokens = [
2970
+ // Days of week
2971
+ 'MONDAY', 'MON', 'TUESDAY', 'TUE', 'WEDNESDAY', 'WED',
2972
+ 'THURSDAY', 'THU', 'THUR', 'FRIDAY', 'FRI', 'SATURDAY',
2973
+ 'SAT', 'SUNDAY', 'SUN',
2974
+ // Keywords
2975
+ 'BETWEEN', 'AND', 'OR', 'NOT', 'IN', 'ON'
2976
+ ];
2977
+
2978
+ // Check if any valid token appears right before the error position
2979
+ for (const token of validTokens) {
2980
+ if (beforeError.toUpperCase().endsWith(token)) {
2981
+ // Found a valid token right before error - check if we have extra characters
2982
+ if (found && found.length > 0 && found !== "end of input" && found.match(/^[A-Z]/i)) {
2983
+ // We have alphabetic characters after a valid token
2984
+ const extraChars = found.charAt(0);
2985
+ return {
2986
+ code: "UNEXPECTED_TOKEN",
2987
+ message: `Unexpected characters after '${token}'. Remove the extra '${extraChars}'.`,
2988
+ hint: `'${token}' is a valid keyword. Remove any extra characters that follow it.`,
2989
+ };
2990
+ }
2991
+ }
2992
+ }
2993
+
2994
+ return null;
2995
+ }
2996
+
2997
+ /**
2998
+ * Detect the error pattern in the input (fallback for non-ParsingError cases)
2999
+ * @private
3000
+ * @param {string} input - The input to analyze
3001
+ * @param {Object} position - Position information
3002
+ * @param {string} snippet - Code snippet
3003
+ * @returns {Object} Error information
3004
+ */
3005
+ static _detectErrorPattern(input, position, snippet) {
3006
+ // Check for unterminated string first (this affects other checks)
3007
+ if (this._hasUnterminatedString(input)) {
3008
+ return {
3009
+ code: "UNTERMINATED_STRING",
3010
+ message: "Unterminated string literal.",
3011
+ hint: "String literals must be enclosed in double quotes, e.g. \"hello world\".",
3012
+ found: "end of input",
3013
+ expected: ["\""]
3014
+ };
3015
+ }
3016
+
3017
+ // Check for dangling logical operators
3018
+ const danglingOp = this._findDanglingLogicalOperator(input);
3019
+ if (danglingOp) {
3020
+ return {
3021
+ code: "DANGLING_LOGICAL_OPERATOR",
3022
+ message: `Logical operator '${danglingOp}' at end of expression.`,
3023
+ hint: "Logical operators (&&, ||, AND, OR) must be followed by an expression.",
3024
+ found: danglingOp,
3025
+ expected: ["expression", "function call", "value"]
3026
+ };
3027
+ }
3028
+
3029
+ // Check for missing RHS after comparison operator
3030
+ const danglingCompOp = this._findDanglingComparisonOperator(input);
3031
+ if (danglingCompOp) {
3032
+ return {
3033
+ code: "MISSING_RHS_AFTER_OPERATOR",
3034
+ message: `Expected a value after '${danglingCompOp}'.`,
3035
+ hint: "Comparison operators must be followed by a value, e.g. temp > 25, name == \"bob\".",
3036
+ found: "end of input",
3037
+ expected: ["value", "function call", "number", "string"]
3038
+ };
3039
+ }
3040
+
3041
+ // Check for bad BETWEEN syntax
3042
+ if (this._hasBadBetweenSyntax(input)) {
3043
+ return {
3044
+ code: "BAD_BETWEEN_SYNTAX",
3045
+ message: "Invalid BETWEEN syntax.",
3046
+ hint: "BETWEEN requires two values: 'expr BETWEEN value1 AND value2' or 'expr BETWEEN value1-value2'.",
3047
+ found: "incomplete BETWEEN expression",
3048
+ expected: ["BETWEEN value1 AND value2"]
3049
+ };
3050
+ }
3051
+
3052
+ // Check for bad time of day (TOD) format
3053
+ const badTod = this._findBadTimeOfDay(input);
3054
+ if (badTod) {
3055
+ return {
3056
+ code: "BAD_TOD",
3057
+ message: `Invalid time of day: ${badTod.value}`,
3058
+ hint: "Time of day must be in HH:MM format with hours 0-23 and minutes 0-59, e.g. 08:30, 14:00, 23:59.",
3059
+ found: badTod.value,
3060
+ expected: ["HH:MM"]
3061
+ };
3062
+ }
3063
+
3064
+ // Check for bad day of week (DOW)
3065
+ const badDow = this._findBadDayOfWeek(input);
3066
+ if (badDow) {
3067
+ return {
3068
+ code: "BAD_DOW",
3069
+ message: `Invalid day of week: ${badDow}`,
3070
+ hint: "Valid days are: MONDAY/MON, TUESDAY/TUE, WEDNESDAY/WED, THURSDAY/THU, FRIDAY/FRI, SATURDAY/SAT, SUNDAY/SUN.",
3071
+ found: badDow,
3072
+ expected: ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"]
3073
+ };
3074
+ }
3075
+
3076
+ // Check for bad number format
3077
+ const badNumber = this._findBadNumber(input);
3078
+ if (badNumber) {
3079
+ return {
3080
+ code: "BAD_NUMBER",
3081
+ message: `Invalid number format: ${badNumber}`,
3082
+ hint: "Numbers must be valid integers or decimals, e.g. 42, 3.14, -5, 1.5e10.",
3083
+ found: badNumber,
3084
+ expected: ["valid number"]
3085
+ };
3086
+ }
3087
+
3088
+ // Check for bad function call syntax (more specific than general paren check)
3089
+ if (this._hasBadFunctionCall(input)) {
3090
+ return {
3091
+ code: "BAD_FUNCTION_CALL",
3092
+ message: "Invalid function call syntax.",
3093
+ hint: "Function calls must have matching parentheses, e.g. func() or func(arg1, arg2).",
3094
+ found: "incomplete function call",
3095
+ expected: [")"]
3096
+ };
3097
+ }
3098
+
3099
+ // Check for bad array syntax (more specific than general bracket check)
3100
+ if (this._hasBadArraySyntax(input)) {
3101
+ return {
3102
+ code: "BAD_ARRAY_SYNTAX",
3103
+ message: "Invalid array syntax.",
3104
+ hint: "Arrays must be enclosed in brackets with comma-separated values, e.g. [1, 2, 3].",
3105
+ found: "incomplete array",
3106
+ expected: ["]", ","]
3107
+ };
3108
+ }
3109
+
3110
+ // Check for unmatched parentheses (general check)
3111
+ const parenBalance = this._checkParenBalance(input);
3112
+ if (parenBalance > 0) {
3113
+ return {
3114
+ code: "UNMATCHED_PAREN",
3115
+ message: "Unclosed parenthesis detected.",
3116
+ hint: "Every opening '(' must have a matching closing ')'. Check your expression for missing closing parentheses.",
3117
+ found: "end of input",
3118
+ expected: [")"]
3119
+ };
3120
+ } else if (parenBalance < 0) {
3121
+ const closeIndex = this._findExtraCloseParen(input);
3122
+ return {
3123
+ code: "UNMATCHED_PAREN",
3124
+ message: "Extra closing parenthesis detected.",
3125
+ hint: "Every closing ')' must have a matching opening '('. Remove the extra closing parenthesis.",
3126
+ found: ")",
3127
+ expected: ["expression", "operator"]
3128
+ };
3129
+ }
3130
+
3131
+ // Check for unmatched brackets (general check)
3132
+ const bracketBalance = this._checkBracketBalance(input);
3133
+ if (bracketBalance > 0) {
3134
+ return {
3135
+ code: "UNMATCHED_BRACKET",
3136
+ message: "Unclosed bracket detected.",
3137
+ hint: "Every opening '[' must have a matching closing ']'. Check your array syntax.",
3138
+ found: "end of input",
3139
+ expected: ["]"]
3140
+ };
3141
+ } else if (bracketBalance < 0) {
3142
+ return {
3143
+ code: "UNMATCHED_BRACKET",
3144
+ message: "Extra closing bracket detected.",
3145
+ hint: "Every closing ']' must have a matching opening '['. Remove the extra closing bracket.",
3146
+ found: "]",
3147
+ expected: ["expression", "operator"]
3148
+ };
3149
+ }
3150
+
3151
+ // Check for multiple expressions without logical operators
3152
+ if (this._hasMultipleExpressionsWithoutOperator(input)) {
3153
+ return {
3154
+ code: "UNEXPECTED_TOKEN",
3155
+ message: "Multiple expressions without logical operator.",
3156
+ hint: "Use logical operators (&&, ||, AND, OR) to combine multiple expressions.",
3157
+ found: "expression",
3158
+ expected: ["&&", "||", "AND", "OR"]
3159
+ };
3160
+ }
3161
+
3162
+ // Default to unexpected token
3163
+ const found = this._getLastToken(input) || "unknown token";
3164
+ return {
3165
+ code: "UNEXPECTED_TOKEN",
3166
+ message: `Unexpected token: ${found}`,
3167
+ hint: "Check your syntax. Common issues include missing operators, invalid characters, or malformed expressions.",
3168
+ found: found,
3169
+ expected: ["valid expression"]
3170
+ };
3171
+ }
3172
+
3173
+ /**
3174
+ * Check if string is properly terminated
3175
+ * @private
3176
+ * @param {string} input - Input to check
3177
+ * @returns {boolean} True if string is unterminated
3178
+ */
3179
+ static _hasUnterminatedString(input) {
3180
+ let inString = false;
3181
+ let i = 0;
3182
+ while (i < input.length) {
3183
+ const char = input[i];
3184
+
3185
+ if (char === '"') {
3186
+ if (!inString) {
3187
+ inString = true;
3188
+ } else {
3189
+ // Check if this quote is escaped by counting preceding backslashes
3190
+ let backslashCount = 0;
3191
+ let j = i - 1;
3192
+ while (j >= 0 && input[j] === '\\') {
3193
+ backslashCount++;
3194
+ j--;
3195
+ }
3196
+ // If even number of backslashes (including 0), the quote is not escaped
3197
+ if (backslashCount % 2 === 0) {
3198
+ inString = false;
3199
+ }
3200
+ }
3201
+ }
3202
+ i++;
3203
+ }
3204
+ return inString;
3205
+ }
3206
+
3207
+ /**
3208
+ * Check parenthesis balance
3209
+ * @private
3210
+ * @param {string} input - Input to check
3211
+ * @returns {number} Balance (positive = unclosed, negative = extra closing)
3212
+ */
3213
+ static _checkParenBalance(input) {
3214
+ let balance = 0;
3215
+ let inString = false;
3216
+ let i = 0;
3217
+ while (i < input.length) {
3218
+ const char = input[i];
3219
+
3220
+ if (char === '"') {
3221
+ if (!inString) {
3222
+ inString = true;
3223
+ } else {
3224
+ // Check if this quote is escaped
3225
+ let backslashCount = 0;
3226
+ let j = i - 1;
3227
+ while (j >= 0 && input[j] === '\\') {
3228
+ backslashCount++;
3229
+ j--;
3230
+ }
3231
+ if (backslashCount % 2 === 0) {
3232
+ inString = false;
3233
+ }
3234
+ }
3235
+ }
3236
+
3237
+ // Only count parentheses outside of strings
3238
+ if (!inString) {
3239
+ if (char === '(') balance++;
3240
+ else if (char === ')') balance--;
3241
+ }
3242
+ i++;
3243
+ }
3244
+ return balance;
3245
+ }
3246
+
3247
+ /**
3248
+ * Find position of extra closing paren
3249
+ * @private
3250
+ */
3251
+ static _findExtraCloseParen(input) {
3252
+ let balance = 0;
3253
+ let inString = false;
3254
+ let i = 0;
3255
+ while (i < input.length) {
3256
+ const char = input[i];
3257
+
3258
+ if (char === '"') {
3259
+ if (!inString) {
3260
+ inString = true;
3261
+ } else {
3262
+ // Check if this quote is escaped
3263
+ let backslashCount = 0;
3264
+ let j = i - 1;
3265
+ while (j >= 0 && input[j] === '\\') {
3266
+ backslashCount++;
3267
+ j--;
3268
+ }
3269
+ if (backslashCount % 2 === 0) {
3270
+ inString = false;
3271
+ }
3272
+ }
3273
+ }
3274
+
3275
+ if (!inString) {
3276
+ if (char === '(') balance++;
3277
+ else if (char === ')') {
3278
+ balance--;
3279
+ if (balance < 0) return i;
3280
+ }
3281
+ }
3282
+ i++;
3283
+ }
3284
+ return -1;
3285
+ }
3286
+
3287
+ /**
3288
+ * Check bracket balance
3289
+ * @private
3290
+ * @param {string} input - Input to check
3291
+ * @returns {number} Balance (positive = unclosed, negative = extra closing)
3292
+ */
3293
+ static _checkBracketBalance(input) {
3294
+ let balance = 0;
3295
+ let inString = false;
3296
+ let i = 0;
3297
+ while (i < input.length) {
3298
+ const char = input[i];
3299
+
3300
+ if (char === '"') {
3301
+ if (!inString) {
3302
+ inString = true;
3303
+ } else {
3304
+ // Check if this quote is escaped
3305
+ let backslashCount = 0;
3306
+ let j = i - 1;
3307
+ while (j >= 0 && input[j] === '\\') {
3308
+ backslashCount++;
3309
+ j--;
3310
+ }
3311
+ if (backslashCount % 2 === 0) {
3312
+ inString = false;
3313
+ }
3314
+ }
3315
+ }
3316
+
3317
+ if (!inString) {
3318
+ if (char === '[') balance++;
3319
+ else if (char === ']') balance--;
3320
+ }
3321
+ i++;
3322
+ }
3323
+ return balance;
3324
+ }
3325
+
3326
+ static _findDanglingLogicalOperator(input) {
3327
+ const trimmed = input.trim();
3328
+ const logicalOps = [
3329
+ { pattern: /&&\s*$/, op: '&&' },
3330
+ { pattern: /\|\|\s*$/, op: '||' },
3331
+ { pattern: /\bAND\b\s*$/i, op: 'AND' },
3332
+ { pattern: /\bOR\b\s*$/i, op: 'OR' }
3333
+ ];
3334
+
3335
+ for (const { pattern, op } of logicalOps) {
3336
+ if (pattern.test(trimmed)) {
3337
+ return op;
3338
+ }
3339
+ }
3340
+ return null;
3341
+ }
3342
+
3343
+ static _findDanglingComparisonOperator(input) {
3344
+ const trimmed = input.trim();
3345
+ const compOps = ['>=', '<=', '==', '!=', '>', '<', '=', '==~', '!=~'];
3346
+
3347
+ for (const op of compOps) {
3348
+ const pattern = new RegExp(`${this._escapeRegex(op)}\\s*$`);
3349
+ if (pattern.test(trimmed)) {
3350
+ return op;
3351
+ }
3352
+ }
3353
+ return null;
3354
+ }
3355
+
3356
+ static _hasBadBetweenSyntax(input) {
3357
+ // Check if BETWEEN keyword exists but not followed by proper syntax
3358
+ if (/\bBETWEEN\b/i.test(input)) {
3359
+ // Check for valid BETWEEN patterns
3360
+ const hasValidBetweenAnd = /\bBETWEEN\s+\S+\s+AND\s+\S+/i.test(input);
3361
+ const hasValidBetweenDash = /\bBETWEEN\s+\S+-\s*\S+/i.test(input);
3362
+ return !hasValidBetweenAnd && !hasValidBetweenDash;
3363
+ }
3364
+ return false;
3365
+ }
3366
+
3367
+ static _hasBadFunctionCall(input) {
3368
+ // Only return true if:
3369
+ // 1. We have a function pattern (identifier followed by open paren)
3370
+ // 2. The parentheses are unbalanced
3371
+ // 3. There are NO array brackets (so we don't confuse arrays with function calls)
3372
+ // 4. The imbalance is specifically in a function call context
3373
+
3374
+ const funcPattern = /[a-zA-Z0-9]+\s*\(/;
3375
+ const hasArrayBracket = /\[/.test(input);
3376
+
3377
+ if (!funcPattern.test(input) || hasArrayBracket) {
3378
+ return false;
3379
+ }
3380
+
3381
+ const parenBalance = this._checkParenBalance(input);
3382
+ if (parenBalance > 0) {
3383
+ // We have unclosed parens in what looks like a function call
3384
+ // Make sure it's not just a general parenthesis expression
3385
+ // by checking if the first occurrence of ( is preceded by an identifier
3386
+ const firstParenMatch = input.match(/^[^(]*([a-zA-Z0-9]+)\s*\(/);
3387
+ if (firstParenMatch) {
3388
+ return true;
3389
+ }
3390
+ }
3391
+
3392
+ return false;
3393
+ }
3394
+
3395
+ static _hasBadArraySyntax(input) {
3396
+ // Only return true if:
3397
+ // 1. We have an array bracket [
3398
+ // 2. The brackets are unbalanced
3399
+ // 3. It's clearly in an array context (inside function args or standalone)
3400
+
3401
+ if (!/\[/.test(input)) {
3402
+ return false;
3403
+ }
3404
+
3405
+ const bracketBalance = this._checkBracketBalance(input);
3406
+ if (bracketBalance > 0) {
3407
+ // We have unclosed brackets - this is likely an array issue
3408
+ return true;
3409
+ }
3410
+
3411
+ return false;
3412
+ }
3413
+
3414
+ static _hasMultipleExpressionsWithoutOperator(input) {
3415
+ // Detect patterns like "A() B()" without && or || between them
3416
+ // Check if there's no logical operator between function calls
3417
+ const betweenPattern = /\)\s+(?!&&|\|\||AND|OR|BETWEEN)/i;
3418
+ if (betweenPattern.test(input)) {
3419
+ // Make sure the next thing after ) is an identifier (suggesting another expression)
3420
+ return /\)\s+[a-zA-Z]/.test(input);
3421
+ }
3422
+ return false;
3423
+ }
3424
+
3425
+ static _getLastToken(input) {
3426
+ const trimmed = input.trim();
3427
+ // Get last 20 characters or the whole string if shorter
3428
+ const lastPart = trimmed.substring(Math.max(0, trimmed.length - 20));
3429
+ return lastPart;
3430
+ }
3431
+
3432
+ /**
3433
+ * Check for invalid time of day format (e.g., 25:00, 12:60)
3434
+ * @private
3435
+ */
3436
+ static _findBadTimeOfDay(input) {
3437
+ // Look for patterns like HH:MM where hours or minutes are out of range or negative
3438
+ const todPattern = /\b(-?\d{1,2}):(-?\d{1,2})\b/g;
3439
+ let match;
3440
+
3441
+ while ((match = todPattern.exec(input)) !== null) {
3442
+ const hours = parseInt(match[1], 10);
3443
+ const minutes = parseInt(match[2], 10);
3444
+ const fullMatch = match[0];
3445
+
3446
+ // Check if hours or minutes are out of valid range or negative
3447
+ if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
3448
+ return { value: fullMatch, hours, minutes };
3449
+ }
3450
+ }
3451
+ return null;
3452
+ }
3453
+
3454
+ /**
3455
+ * Check for invalid day of week
3456
+ * @private
3457
+ */
3458
+ static _findBadDayOfWeek(input) {
3459
+ // Look for ON keyword followed by potential day name
3460
+ const onPattern = /\bON\s+([A-Z]+)/i;
3461
+ const match = onPattern.exec(input);
3462
+
3463
+ if (match) {
3464
+ const dayCandidate = match[1].toUpperCase();
3465
+ const validDays = ['MONDAY', 'MON', 'TUESDAY', 'TUE', 'WEDNESDAY', 'WED',
3466
+ 'THURSDAY', 'THU', 'THUR', 'FRIDAY', 'FRI', 'SATURDAY',
3467
+ 'SAT', 'SUNDAY', 'SUN'];
3468
+
3469
+ if (!validDays.includes(dayCandidate)) {
3470
+ return dayCandidate;
3471
+ }
3472
+ }
3473
+ return null;
3474
+ }
3475
+
3476
+ /**
3477
+ * Check for invalid number format (e.g., 1.2.3, 123abc)
3478
+ * @private
3479
+ */
3480
+ static _findBadNumber(input) {
3481
+ // Look for malformed numbers - multiple decimal points or numbers followed by letters
3482
+ const badNumberPatterns = [
3483
+ /\b\d+\.\d+\.\d+/, // Multiple decimal points like 1.2.3
3484
+ /\b\d+[a-zA-Z]+\d*/, // Numbers with letters mixed in like 123abc
3485
+ /\b\d+\.\d+[a-zA-Z]+/ // Decimals with letters like 1.5abc
3486
+ ];
3487
+
3488
+ for (const pattern of badNumberPatterns) {
3489
+ const match = pattern.exec(input);
3490
+ if (match) {
3491
+ return match[0];
3492
+ }
3493
+ }
3494
+ return null;
3495
+ }
3496
+
3497
+ static _escapeRegex(str) {
3498
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
3499
+ }
3500
+ }
3501
+
3502
+ module.exports = { ErrorAnalyzer };
3503
+
3504
+ },{"./RuleParseError":14,"ebnf":9}],14:[function(require,module,exports){
3505
+ /**
3506
+ * Represents a user-friendly rule parsing error with error codes
3507
+ */
3508
+ class RuleParseError extends Error {
3509
+ /**
3510
+ * Create a RuleParseError
3511
+ * @param {string} code - Error code for programmatic handling
3512
+ * @param {string} message - Human-readable error message
3513
+ * @param {string} hint - Helpful suggestion for fixing the error
3514
+ * @param {Object} position - Position information with line, column, and offset
3515
+ * @param {string} found - What was found at error position
3516
+ * @param {Array<string>} expected - What was expected
3517
+ * @param {string} snippet - Code snippet showing error location
3518
+ */
3519
+ constructor(code, message, hint, position, found, expected, snippet) {
3520
+ super(message);
3521
+ this.name = 'RuleParseError';
3522
+ this.code = code;
3523
+ this.hint = hint;
3524
+ this.line = position.line;
3525
+ this.column = position.column;
3526
+ this.offset = position.offset;
3527
+ this.found = found;
3528
+ this.expected = expected;
3529
+ this.snippet = snippet;
3530
+
3531
+ // Maintain proper prototype chain for instanceof checks
3532
+ Object.setPrototypeOf(this, RuleParseError.prototype);
3533
+ }
3534
+
3535
+ toString() {
3536
+ let msg = `${this.name} [${this.code}]: ${this.message}\n`;
3537
+ msg += ` at line ${this.line}, column ${this.column} (offset ${this.offset})\n`;
3538
+ if (this.snippet) {
3539
+ msg += ` ${this.snippet}\n`;
3540
+ }
3541
+ if (this.hint) {
3542
+ msg += ` Hint: ${this.hint}\n`;
3543
+ }
3544
+ if (this.found) {
3545
+ msg += ` Found: ${this.found}\n`;
3546
+ }
3547
+ if (this.expected && this.expected.length) {
3548
+ msg += ` Expected: ${this.expected.join(', ')}`;
3549
+ }
3550
+ return msg;
3551
+ }
3552
+
3553
+ toJSON() {
3554
+ return {
3555
+ code: this.code,
3556
+ message: this.message,
3557
+ hint: this.hint,
3558
+ line: this.line,
3559
+ column: this.column,
3560
+ offset: this.offset,
3561
+ found: this.found,
3562
+ expected: this.expected,
3563
+ snippet: this.snippet
3564
+ };
3565
+ }
3566
+ }
3567
+
3568
+ module.exports = { RuleParseError };
3569
+
3570
+ },{}]},{},[10])(10)
3571
+ });