@halleyassist/rule-templater 0.0.1 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +231 -231
- package/dist/rule-templater.browser.js +2453 -0
- package/index.d.ts +91 -91
- package/index.js +0 -0
- package/package.json +47 -47
- package/src/RuleTemplate.ebnf.js +34 -27
- package/src/RuleTemplate.production.ebnf.js +1 -0
- package/src/RuleTemplater.browser.js +0 -0
- package/src/RuleTemplater.js +348 -339
- package/src/RuleTemplater.production.js +349 -0
- package/src/TemplateFilters.js +62 -62
|
@@ -0,0 +1,2453 @@
|
|
|
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.RuleTemplater = 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
|
+
const {Grammars} = require('ebnf');
|
|
3
|
+
|
|
4
|
+
const grammar = `
|
|
5
|
+
statement_main ::= statement EOF
|
|
6
|
+
logical_operator ||= AND | OR
|
|
7
|
+
statement ::= expression (logical_operator expression)*
|
|
8
|
+
expression ::= not_expression | standard_expression | parenthesis_expression
|
|
9
|
+
parenthesis_expression ::= BEGIN_PARENTHESIS WS* statement WS* END_PARENTHESIS
|
|
10
|
+
not_expression ||= NOT (result | parenthesis_expression)
|
|
11
|
+
standard_expression ||= result ((WS* eq_approx) | (WS* basic_rhs) | ((WS+ IS)? WS+ between) | (WS+ in_expr))?
|
|
12
|
+
basic_rhs ::= operator WS* result
|
|
13
|
+
eq_approx ::= eq_operator WS* "~" WS* result
|
|
14
|
+
|
|
15
|
+
PLUS ::= "+"
|
|
16
|
+
MINUS ::= "-"
|
|
17
|
+
MULTIPLY ::= "*"
|
|
18
|
+
DIVIDE ::= "/"
|
|
19
|
+
MODULUS ::= "%"
|
|
20
|
+
DEFAULT_VAL ::= "??"
|
|
21
|
+
arithmetic_operator ::= PLUS | MINUS | MULTIPLY | DIVIDE | MODULUS | DEFAULT_VAL
|
|
22
|
+
|
|
23
|
+
number_atom ::= number
|
|
24
|
+
number_time_atom ::= number_time
|
|
25
|
+
tod_atom ::= number_tod
|
|
26
|
+
dow_atom ::= dow
|
|
27
|
+
|
|
28
|
+
arithmetic_operand ::= fcall | number_time_atom | number_atom
|
|
29
|
+
arithmetic_result ::= arithmetic_operand WS* arithmetic_operator WS* (arithmetic_result | arithmetic_operand)
|
|
30
|
+
|
|
31
|
+
simple_result ::= fcall | value
|
|
32
|
+
result ::= arithmetic_result | simple_result
|
|
33
|
+
|
|
34
|
+
value_atom ::= false | true | array | time_period | number_time_atom | number_atom | tod_atom | string
|
|
35
|
+
value ::= value_atom
|
|
36
|
+
|
|
37
|
+
BEGIN_ARRAY ::= WS* #x5B WS* /* [ */
|
|
38
|
+
BEGIN_OBJECT ::= WS* #x7B WS* /* { */
|
|
39
|
+
END_ARRAY ::= WS* #x5D WS* /* ] */
|
|
40
|
+
END_OBJECT ::= WS* #x7D WS* /* } */
|
|
41
|
+
NAME_SEPARATOR ::= WS* #x3A WS* /* : */
|
|
42
|
+
VALUE_SEPARATOR ::= WS* #x2C WS* /* , */
|
|
43
|
+
WS ::= [#x20#x09#x0A#x0D]
|
|
44
|
+
|
|
45
|
+
operator ::= GTE | LTE | GT | LT | EQ | NEQ
|
|
46
|
+
eq_operator ::= EQ | NEQ
|
|
47
|
+
|
|
48
|
+
BEGIN_ARGUMENT ::= "("
|
|
49
|
+
END_ARGUMENT ::= ")"
|
|
50
|
+
BEGIN_PARENTHESIS ::= "("
|
|
51
|
+
END_PARENTHESIS ::= ")"
|
|
52
|
+
|
|
53
|
+
BEGIN_IN ||= "IN"
|
|
54
|
+
in_expr ::= BEGIN_IN WS* BEGIN_PARENTHESIS WS* arguments END_PARENTHESIS
|
|
55
|
+
|
|
56
|
+
argument ::= statement WS*
|
|
57
|
+
arguments ::= argument (WS* "," WS* argument)*
|
|
58
|
+
fname ::= [A-Za-z0-9]+
|
|
59
|
+
fcall ::= fname WS* BEGIN_ARGUMENT WS* arguments? END_ARGUMENT
|
|
60
|
+
|
|
61
|
+
between_dash_or_and ||= (WS+ "AND" WS+) | (WS* "-" WS*)
|
|
62
|
+
|
|
63
|
+
between_number_inner ::= number_atom | number_time_atom
|
|
64
|
+
between_number ||= between_number_inner between_dash_or_and between_number_inner
|
|
65
|
+
|
|
66
|
+
between_number_time_inner ::= number_time_atom
|
|
67
|
+
between_number_time ||= between_number_time_inner between_dash_or_and between_number_time_inner (WS+ dow_range)?
|
|
68
|
+
|
|
69
|
+
between_tod_inner ::= tod_atom
|
|
70
|
+
between_tod ||= between_tod_inner (WS+ "AND" WS+) between_tod_inner (WS+ dow_range)?
|
|
71
|
+
|
|
72
|
+
between ||= "BETWEEN" WS+ (between_number | between_tod)
|
|
73
|
+
|
|
74
|
+
dow ||= "MONDAY" | "MON" | "TUESDAY" | "TUE" | "WEDNESDAY" | "WED" | "THURSDAY" | "THU" | "THUR" | "FRIDAY" | "FRI" | "SATURDAY" | "SAT" | "SUNDAY" | "SUN"
|
|
75
|
+
|
|
76
|
+
dow_range_inner ::= dow_atom
|
|
77
|
+
dow_range ||= "ON" WS+ dow_range_inner (WS+ "TO" WS+ dow_range_inner)?
|
|
78
|
+
|
|
79
|
+
between_time_only ||= "BETWEEN" WS+ between_number_time
|
|
80
|
+
between_tod_only ||= "BETWEEN" WS+ between_tod
|
|
81
|
+
|
|
82
|
+
AND ||= (WS* "&&" WS*) | (WS+ "AND" WS+)
|
|
83
|
+
OR ||= (WS* "||" WS*) | (WS+ "OR" WS+)
|
|
84
|
+
AGO ||= "AGO"
|
|
85
|
+
GT ::= ">"
|
|
86
|
+
LT ::= "<"
|
|
87
|
+
GTE ::= ">="
|
|
88
|
+
LTE ::= "<="
|
|
89
|
+
IS ||= "is"
|
|
90
|
+
EQ ::= "==" | "="
|
|
91
|
+
NEQ ::= "!="
|
|
92
|
+
NOT ||= ("!" WS*) | ("not" WS+)
|
|
93
|
+
|
|
94
|
+
false ||= "FALSE"
|
|
95
|
+
null ||= "null"
|
|
96
|
+
true ||= "TRUE"
|
|
97
|
+
|
|
98
|
+
array ::= BEGIN_ARRAY (value (VALUE_SEPARATOR value)*)? END_ARRAY
|
|
99
|
+
|
|
100
|
+
unit ||= "seconds" | "minutes" | "hours" | "weeks" | "days" | "second" | "minute" | "week" | "hour" | "day" | "mins" | "min"
|
|
101
|
+
|
|
102
|
+
number ::= "-"? ([0-9]+) ("." [0-9]+)? ("e" ("-" | "+")? ("0" | [1-9] [0-9]*))?
|
|
103
|
+
number_time ::= number WS+ unit
|
|
104
|
+
number_tod ::= ([0-9]+) ":" ([0-9]+)
|
|
105
|
+
|
|
106
|
+
time_period_ago ||= number_time_atom (WS+ number_time_atom)* WS+ AGO
|
|
107
|
+
time_period_ago_between ||= number_time_atom (WS+ number_time_atom)* WS+ AGO WS+ between_tod_only
|
|
108
|
+
time_period_const ||= "today" | time_period_ago
|
|
109
|
+
time_period ::= time_period_ago_between | time_period_const | between_tod_only | between_time_only
|
|
110
|
+
|
|
111
|
+
string ::= '"' (([#x20-#x21] | [#x23-#x5B] | [#x5D-#xFFFF]) | #x5C (#x22 | #x5C | #x2F | #x62 | #x66 | #x6E | #x72 | #x74 | #x75 HEXDIG HEXDIG HEXDIG HEXDIG))* '"'
|
|
112
|
+
HEXDIG ::= [a-fA-F0-9]
|
|
113
|
+
`
|
|
114
|
+
|
|
115
|
+
module.exports = Grammars.W3C.getRules(grammar);
|
|
116
|
+
|
|
117
|
+
},{"ebnf":10}],2:[function(require,module,exports){
|
|
118
|
+
"use strict";
|
|
119
|
+
// https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_Form
|
|
120
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
121
|
+
/*
|
|
122
|
+
syntax ::= RULE_EOL* rule+
|
|
123
|
+
rule ::= " "* "<" rule-name ">" " "* "::=" firstExpression otherExpression* " "* RULE_EOL+ " "*
|
|
124
|
+
firstExpression ::= " "* list
|
|
125
|
+
otherExpression ::= " "* "|" " "* list
|
|
126
|
+
RULE_EOL ::= "\r" | "\n"
|
|
127
|
+
list ::= term " "* list | term
|
|
128
|
+
term ::= literal | "<" rule-name ">"
|
|
129
|
+
literal ::= '"' RULE_CHARACTER1* '"' | "'" RULE_CHARACTER2* "'"
|
|
130
|
+
RULE_CHARACTER ::= " " | RULE_LETTER | RULE_DIGIT | RULE_SYMBOL
|
|
131
|
+
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"
|
|
132
|
+
RULE_DIGIT ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
|
|
133
|
+
RULE_SYMBOL ::= "-" | "_" | "!" | "#" | "$" | "%" | "&" | "(" | ")" | "*" | "+" | "," | "-" | "." | "/" | ":" | ";" | "<" | "=" | ">" | "?" | "@" | "[" | "\" | "]" | "^" | "_" | "`" | "{" | "|" | "}" | "~"
|
|
134
|
+
RULE_CHARACTER1 ::= RULE_CHARACTER | "'"
|
|
135
|
+
RULE_CHARACTER2 ::= RULE_CHARACTER | '"'
|
|
136
|
+
rule-name ::= RULE_LETTER RULE_CHAR*
|
|
137
|
+
RULE_CHAR ::= RULE_LETTER | RULE_DIGIT | "_" | "-"
|
|
138
|
+
*/
|
|
139
|
+
const SemanticHelpers_1 = require("../SemanticHelpers");
|
|
140
|
+
const Parser_1 = require("../Parser");
|
|
141
|
+
var BNF;
|
|
142
|
+
(function (BNF) {
|
|
143
|
+
BNF.RULES = [
|
|
144
|
+
{
|
|
145
|
+
name: 'syntax',
|
|
146
|
+
bnf: [['RULE_EOL*', 'rule+']]
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: 'rule',
|
|
150
|
+
bnf: [
|
|
151
|
+
[
|
|
152
|
+
'" "*',
|
|
153
|
+
'"<"',
|
|
154
|
+
'rule-name',
|
|
155
|
+
'">"',
|
|
156
|
+
'" "*',
|
|
157
|
+
'"::="',
|
|
158
|
+
'firstExpression',
|
|
159
|
+
'otherExpression*',
|
|
160
|
+
'" "*',
|
|
161
|
+
'RULE_EOL+',
|
|
162
|
+
'" "*'
|
|
163
|
+
]
|
|
164
|
+
]
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: 'firstExpression',
|
|
168
|
+
bnf: [['" "*', 'list']]
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: 'otherExpression',
|
|
172
|
+
bnf: [['" "*', '"|"', '" "*', 'list']]
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'RULE_EOL',
|
|
176
|
+
bnf: [['"\\r"'], ['"\\n"']]
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: 'list',
|
|
180
|
+
bnf: [['term', '" "*', 'list'], ['term']]
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: 'term',
|
|
184
|
+
bnf: [['literal'], ['"<"', 'rule-name', '">"']]
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: 'literal',
|
|
188
|
+
bnf: [[`'"'`, 'RULE_CHARACTER1*', `'"'`], [`"'"`, 'RULE_CHARACTER2*', `"'"`]]
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: 'RULE_CHARACTER',
|
|
192
|
+
bnf: [['" "'], ['RULE_LETTER'], ['RULE_DIGIT'], ['RULE_SYMBOL']]
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: 'RULE_LETTER',
|
|
196
|
+
bnf: [
|
|
197
|
+
['"A"'],
|
|
198
|
+
['"B"'],
|
|
199
|
+
['"C"'],
|
|
200
|
+
['"D"'],
|
|
201
|
+
['"E"'],
|
|
202
|
+
['"F"'],
|
|
203
|
+
['"G"'],
|
|
204
|
+
['"H"'],
|
|
205
|
+
['"I"'],
|
|
206
|
+
['"J"'],
|
|
207
|
+
['"K"'],
|
|
208
|
+
['"L"'],
|
|
209
|
+
['"M"'],
|
|
210
|
+
['"N"'],
|
|
211
|
+
['"O"'],
|
|
212
|
+
['"P"'],
|
|
213
|
+
['"Q"'],
|
|
214
|
+
['"R"'],
|
|
215
|
+
['"S"'],
|
|
216
|
+
['"T"'],
|
|
217
|
+
['"U"'],
|
|
218
|
+
['"V"'],
|
|
219
|
+
['"W"'],
|
|
220
|
+
['"X"'],
|
|
221
|
+
['"Y"'],
|
|
222
|
+
['"Z"'],
|
|
223
|
+
['"a"'],
|
|
224
|
+
['"b"'],
|
|
225
|
+
['"c"'],
|
|
226
|
+
['"d"'],
|
|
227
|
+
['"e"'],
|
|
228
|
+
['"f"'],
|
|
229
|
+
['"g"'],
|
|
230
|
+
['"h"'],
|
|
231
|
+
['"i"'],
|
|
232
|
+
['"j"'],
|
|
233
|
+
['"k"'],
|
|
234
|
+
['"l"'],
|
|
235
|
+
['"m"'],
|
|
236
|
+
['"n"'],
|
|
237
|
+
['"o"'],
|
|
238
|
+
['"p"'],
|
|
239
|
+
['"q"'],
|
|
240
|
+
['"r"'],
|
|
241
|
+
['"s"'],
|
|
242
|
+
['"t"'],
|
|
243
|
+
['"u"'],
|
|
244
|
+
['"v"'],
|
|
245
|
+
['"w"'],
|
|
246
|
+
['"x"'],
|
|
247
|
+
['"y"'],
|
|
248
|
+
['"z"']
|
|
249
|
+
]
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
name: 'RULE_DIGIT',
|
|
253
|
+
bnf: [['"0"'], ['"1"'], ['"2"'], ['"3"'], ['"4"'], ['"5"'], ['"6"'], ['"7"'], ['"8"'], ['"9"']]
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: 'RULE_SYMBOL',
|
|
257
|
+
bnf: [
|
|
258
|
+
['"-"'],
|
|
259
|
+
['"_"'],
|
|
260
|
+
['"!"'],
|
|
261
|
+
['"#"'],
|
|
262
|
+
['"$"'],
|
|
263
|
+
['"%"'],
|
|
264
|
+
['"&"'],
|
|
265
|
+
['"("'],
|
|
266
|
+
['")"'],
|
|
267
|
+
['"*"'],
|
|
268
|
+
['"+"'],
|
|
269
|
+
['","'],
|
|
270
|
+
['"-"'],
|
|
271
|
+
['"."'],
|
|
272
|
+
['"/"'],
|
|
273
|
+
['":"'],
|
|
274
|
+
['";"'],
|
|
275
|
+
['"<"'],
|
|
276
|
+
['"="'],
|
|
277
|
+
['">"'],
|
|
278
|
+
['"?"'],
|
|
279
|
+
['"@"'],
|
|
280
|
+
['"["'],
|
|
281
|
+
['"\\"'],
|
|
282
|
+
['"]"'],
|
|
283
|
+
['"^"'],
|
|
284
|
+
['"_"'],
|
|
285
|
+
['"`"'],
|
|
286
|
+
['"{"'],
|
|
287
|
+
['"|"'],
|
|
288
|
+
['"}"'],
|
|
289
|
+
['"~"']
|
|
290
|
+
]
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
name: 'RULE_CHARACTER1',
|
|
294
|
+
bnf: [['RULE_CHARACTER'], [`"'"`]]
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
name: 'RULE_CHARACTER2',
|
|
298
|
+
bnf: [['RULE_CHARACTER'], [`'"'`]]
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
name: 'rule-name',
|
|
302
|
+
bnf: [['RULE_LETTER', 'RULE_CHAR*']]
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
name: 'RULE_CHAR',
|
|
306
|
+
bnf: [['RULE_LETTER'], ['RULE_DIGIT'], ['"_"'], ['"-"']]
|
|
307
|
+
}
|
|
308
|
+
];
|
|
309
|
+
BNF.defaultParser = new Parser_1.Parser(BNF.RULES, { debug: false });
|
|
310
|
+
function getAllTerms(expr) {
|
|
311
|
+
let terms = (0, SemanticHelpers_1.findChildrenByType)(expr, 'term').map(term => {
|
|
312
|
+
return (0, SemanticHelpers_1.findChildrenByType)(term, 'literal').concat((0, SemanticHelpers_1.findChildrenByType)(term, 'rule-name'))[0].text;
|
|
313
|
+
});
|
|
314
|
+
for (const exprItem of (0, SemanticHelpers_1.findChildrenByType)(expr, 'list')) {
|
|
315
|
+
terms = terms.concat(getAllTerms(exprItem));
|
|
316
|
+
}
|
|
317
|
+
return terms;
|
|
318
|
+
}
|
|
319
|
+
function getRules(source, parser = BNF.defaultParser) {
|
|
320
|
+
let ast = parser.getAST(source);
|
|
321
|
+
if (!ast)
|
|
322
|
+
throw new Error('Could not parse ' + source);
|
|
323
|
+
if (ast.errors && ast.errors.length) {
|
|
324
|
+
throw ast.errors[0];
|
|
325
|
+
}
|
|
326
|
+
let rules = (0, SemanticHelpers_1.findChildrenByType)(ast, 'rule');
|
|
327
|
+
let ret = rules.map((rule) => {
|
|
328
|
+
let name = (0, SemanticHelpers_1.findChildrenByType)(rule, 'rule-name')[0].text;
|
|
329
|
+
let expressions = (0, SemanticHelpers_1.findChildrenByType)(rule, 'firstExpression').concat((0, SemanticHelpers_1.findChildrenByType)(rule, 'otherExpression'));
|
|
330
|
+
let bnf = [];
|
|
331
|
+
for (const expr of expressions) {
|
|
332
|
+
bnf.push(getAllTerms(expr));
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
name: name,
|
|
336
|
+
bnf
|
|
337
|
+
};
|
|
338
|
+
});
|
|
339
|
+
if (!ret.some(x => x.name == 'EOL')) {
|
|
340
|
+
ret.push({
|
|
341
|
+
name: 'EOL',
|
|
342
|
+
bnf: [['"\\r\\n"', '"\\r"', '"\\n"']]
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
return ret;
|
|
346
|
+
}
|
|
347
|
+
BNF.getRules = getRules;
|
|
348
|
+
function Transform(source, subParser = BNF.defaultParser) {
|
|
349
|
+
return getRules(source.join(''), subParser);
|
|
350
|
+
}
|
|
351
|
+
BNF.Transform = Transform;
|
|
352
|
+
class Parser extends Parser_1.Parser {
|
|
353
|
+
constructor(source, options) {
|
|
354
|
+
const subParser = options && options.debugRulesParser === true ? new Parser_1.Parser(BNF.RULES, { debug: true }) : BNF.defaultParser;
|
|
355
|
+
super(getRules(source, subParser), options);
|
|
356
|
+
this.source = source;
|
|
357
|
+
}
|
|
358
|
+
emitSource() {
|
|
359
|
+
return this.source;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
BNF.Parser = Parser;
|
|
363
|
+
})(BNF || (BNF = {}));
|
|
364
|
+
exports.default = BNF;
|
|
365
|
+
|
|
366
|
+
},{"../Parser":6,"../SemanticHelpers":8}],3:[function(require,module,exports){
|
|
367
|
+
"use strict";
|
|
368
|
+
// https://www.w3.org/TR/REC-xml/#NT-Name
|
|
369
|
+
// http://www.bottlecaps.de/rr/ui
|
|
370
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
371
|
+
// Grammar ::= Production*
|
|
372
|
+
// Production ::= NCName '::=' Choice
|
|
373
|
+
// NCName ::= [http://www.w3.org/TR/xml-names/#NT-NCName]
|
|
374
|
+
// Choice ::= SequenceOrDifference ( '|' SequenceOrDifference )*
|
|
375
|
+
// SequenceOrDifference ::= (Item ( '-' Item | Item* ))?
|
|
376
|
+
// Item ::= Primary ( '?' | '*' | '+' )?
|
|
377
|
+
// Primary ::= NCName | StringLiteral | CharCode | CharClass | '(' Choice ')'
|
|
378
|
+
// StringLiteral ::= '"' [^"]* '"' | "'" [^']* "'"
|
|
379
|
+
// CharCode ::= '#x' [0-9a-fA-F]+
|
|
380
|
+
// CharClass ::= '[' '^'? ( RULE_Char | CharCode | CharRange | CharCodeRange )+ ']'
|
|
381
|
+
// RULE_Char ::= [http://www.w3.org/TR/xml#NT-RULE_Char]
|
|
382
|
+
// CharRange ::= RULE_Char '-' ( RULE_Char - ']' )
|
|
383
|
+
// CharCodeRange ::= CharCode '-' CharCode
|
|
384
|
+
// RULE_WHITESPACE ::= RULE_S | Comment
|
|
385
|
+
// RULE_S ::= #x9 | #xA | #xD | #x20
|
|
386
|
+
// Comment ::= '/*' ( [^*] | '*'+ [^*/] )* '*'* '*/'
|
|
387
|
+
const TokenError_1 = require("../TokenError");
|
|
388
|
+
const Parser_1 = require("../Parser");
|
|
389
|
+
var BNF;
|
|
390
|
+
(function (BNF) {
|
|
391
|
+
BNF.RULES = [
|
|
392
|
+
{
|
|
393
|
+
name: 'Grammar',
|
|
394
|
+
bnf: [['RULE_S*', 'Attributes?', 'RULE_S*', '%Atomic*', 'EOF']]
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
name: '%Atomic',
|
|
398
|
+
bnf: [['Production', 'RULE_S*']],
|
|
399
|
+
fragment: true
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
name: 'Production',
|
|
403
|
+
bnf: [
|
|
404
|
+
[
|
|
405
|
+
'NCName',
|
|
406
|
+
'RULE_S*',
|
|
407
|
+
'"::="',
|
|
408
|
+
'RULE_WHITESPACE*',
|
|
409
|
+
'%Choice',
|
|
410
|
+
'RULE_WHITESPACE*',
|
|
411
|
+
'Attributes?',
|
|
412
|
+
'RULE_EOL+',
|
|
413
|
+
'RULE_S*'
|
|
414
|
+
]
|
|
415
|
+
]
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
name: 'NCName',
|
|
419
|
+
bnf: [[/[a-zA-Z][a-zA-Z_0-9]*/]]
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
name: 'Attributes',
|
|
423
|
+
bnf: [['"{"', 'Attribute', '%Attributes*', 'RULE_S*', '"}"']]
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
name: '%Attributes',
|
|
427
|
+
bnf: [['RULE_S*', '","', 'Attribute']],
|
|
428
|
+
fragment: true
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
name: 'Attribute',
|
|
432
|
+
bnf: [['RULE_S*', 'NCName', 'RULE_WHITESPACE*', '"="', 'RULE_WHITESPACE*', 'AttributeValue']]
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
name: 'AttributeValue',
|
|
436
|
+
bnf: [['NCName'], [/[1-9][0-9]*/]]
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
name: '%Choice',
|
|
440
|
+
bnf: [['SequenceOrDifference', '%_Choice_1*']],
|
|
441
|
+
fragment: true
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
name: '%_Choice_1',
|
|
445
|
+
bnf: [['RULE_S*', '"|"', 'RULE_S*', 'SequenceOrDifference']],
|
|
446
|
+
fragment: true
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
name: 'SequenceOrDifference',
|
|
450
|
+
bnf: [['%Item', 'RULE_WHITESPACE*', '%_Item_1?']]
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
name: '%_Item_1',
|
|
454
|
+
bnf: [['Minus', '%Item'], ['%Item*']],
|
|
455
|
+
fragment: true
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
name: 'Minus',
|
|
459
|
+
bnf: [['"-"']]
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
name: '%Item',
|
|
463
|
+
bnf: [['RULE_WHITESPACE*', 'PrimaryPreDecoration?', '%Primary', 'PrimaryDecoration?']],
|
|
464
|
+
fragment: true
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
name: 'PrimaryDecoration',
|
|
468
|
+
bnf: [['"?"'], ['"*"'], ['"+"']]
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
name: 'PrimaryPreDecoration',
|
|
472
|
+
bnf: [['"&"'], ['"!"'], ['"~"']]
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
name: '%Primary',
|
|
476
|
+
bnf: [['NCName'], ['StringLiteral'], ['CharCode'], ['CharClass'], ['SubItem']],
|
|
477
|
+
fragment: true
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
name: 'SubItem',
|
|
481
|
+
bnf: [['"("', 'RULE_S*', '%Choice', 'RULE_S*', '")"']]
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
name: 'StringLiteral',
|
|
485
|
+
bnf: [[`'"'`, /[^"]*/, `'"'`], [`"'"`, /[^']*/, `"'"`]]
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
name: 'CharCode',
|
|
489
|
+
bnf: [['"#x"', /[0-9a-zA-Z]+/]]
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
name: 'CharClass',
|
|
493
|
+
bnf: [["'['", "'^'?", '%RULE_CharClass_1+', '"]"']]
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
name: '%RULE_CharClass_1',
|
|
497
|
+
bnf: [['CharCodeRange'], ['CharRange'], ['CharCode'], ['RULE_Char']],
|
|
498
|
+
fragment: true
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
name: 'RULE_Char',
|
|
502
|
+
bnf: [[/\x09/], [/\x0A/], [/\x0D/], [/[\x20-\x5c]/], [/[\x5e-\uD7FF]/], [/[\uE000-\uFFFD]/]]
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
name: 'CharRange',
|
|
506
|
+
bnf: [['RULE_Char', '"-"', 'RULE_Char']]
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
name: 'CharCodeRange',
|
|
510
|
+
bnf: [['CharCode', '"-"', 'CharCode']]
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
name: 'RULE_WHITESPACE',
|
|
514
|
+
bnf: [['%RULE_WHITESPACE_CHAR*'], ['Comment', 'RULE_WHITESPACE*']]
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
name: 'RULE_S',
|
|
518
|
+
bnf: [['RULE_WHITESPACE', 'RULE_S*'], ['RULE_EOL', 'RULE_S*']]
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
name: '%RULE_WHITESPACE_CHAR',
|
|
522
|
+
bnf: [[/\x09/], [/\x20/]],
|
|
523
|
+
fragment: true
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
name: 'Comment',
|
|
527
|
+
bnf: [['"/*"', '%RULE_Comment_Body*', '"*/"']]
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
name: '%RULE_Comment_Body',
|
|
531
|
+
bnf: [[/[^*]/], ['"*"+', /[^/]*/]],
|
|
532
|
+
fragment: true
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
name: 'RULE_EOL',
|
|
536
|
+
bnf: [[/\x0D/, /\x0A/], [/\x0A/], [/\x0D/]]
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
name: 'Link',
|
|
540
|
+
bnf: [["'['", 'Url', "']'"]]
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
name: 'Url',
|
|
544
|
+
bnf: [[/[^\x5D:/?#]/, '"://"', /[^\x5D#]+/, '%Url1?']]
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
name: '%Url1',
|
|
548
|
+
bnf: [['"#"', 'NCName']],
|
|
549
|
+
fragment: true
|
|
550
|
+
}
|
|
551
|
+
];
|
|
552
|
+
BNF.defaultParser = new Parser_1.Parser(BNF.RULES, { debug: false });
|
|
553
|
+
const preDecorationRE = /^(!|&)/;
|
|
554
|
+
const decorationRE = /(\?|\+|\*)$/;
|
|
555
|
+
const subExpressionRE = /^%/;
|
|
556
|
+
function getBNFRule(name, parser) {
|
|
557
|
+
if (typeof name == 'string') {
|
|
558
|
+
let decoration = decorationRE.exec(name);
|
|
559
|
+
let preDecoration = preDecorationRE.exec(name);
|
|
560
|
+
let preDecorationText = preDecoration ? preDecoration[0] : '';
|
|
561
|
+
let decorationText = decoration ? decoration[0] + ' ' : '';
|
|
562
|
+
let subexpression = subExpressionRE.test(name);
|
|
563
|
+
if (subexpression) {
|
|
564
|
+
let lonely = isLonelyRule(name, parser);
|
|
565
|
+
if (lonely)
|
|
566
|
+
return preDecorationText + getBNFBody(name, parser) + decorationText;
|
|
567
|
+
return preDecorationText + '(' + getBNFBody(name, parser) + ')' + decorationText;
|
|
568
|
+
}
|
|
569
|
+
return name.replace(preDecorationRE, preDecorationText);
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
return name.source
|
|
573
|
+
.replace(/\\(?:x|u)([a-zA-Z0-9]+)/g, '#x$1')
|
|
574
|
+
.replace(/\[\\(?:x|u)([a-zA-Z0-9]+)-\\(?:x|u)([a-zA-Z0-9]+)\]/g, '[#x$1-#x$2]');
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
/// Returns true if the rule is a string literal or regular expression without a descendant tree
|
|
578
|
+
function isLonelyRule(name, parser) {
|
|
579
|
+
let rule = (0, Parser_1.findRuleByName)(name, parser);
|
|
580
|
+
return (rule &&
|
|
581
|
+
rule.bnf.length == 1 &&
|
|
582
|
+
rule.bnf[0].length == 1 &&
|
|
583
|
+
(rule.bnf[0][0] instanceof RegExp || rule.bnf[0][0][0] == '"' || rule.bnf[0][0][0] == "'"));
|
|
584
|
+
}
|
|
585
|
+
function getBNFChoice(rules, parser) {
|
|
586
|
+
return rules.map(x => getBNFRule(x, parser)).join(' ');
|
|
587
|
+
}
|
|
588
|
+
function getBNFBody(name, parser) {
|
|
589
|
+
let rule = (0, Parser_1.findRuleByName)(name, parser);
|
|
590
|
+
if (rule)
|
|
591
|
+
return rule.bnf.map(x => getBNFChoice(x, parser)).join(' | ');
|
|
592
|
+
return 'RULE_NOT_FOUND {' + name + '}';
|
|
593
|
+
}
|
|
594
|
+
function emit(parser) {
|
|
595
|
+
let acumulator = [];
|
|
596
|
+
for (const l of parser.grammarRules) {
|
|
597
|
+
if (!/^%/.test(l.name)) {
|
|
598
|
+
let recover = l.recover ? ' { recoverUntil=' + l.recover + ' }' : '';
|
|
599
|
+
acumulator.push(l.name + ' ::= ' + getBNFBody(l.name, parser) + recover);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return acumulator.join('\n');
|
|
603
|
+
}
|
|
604
|
+
BNF.emit = emit;
|
|
605
|
+
function restar(total, resta) {
|
|
606
|
+
console.log('reberia restar ' + resta + ' a ' + total);
|
|
607
|
+
throw new Error('Difference not supported yet');
|
|
608
|
+
}
|
|
609
|
+
function convertRegex(txt) {
|
|
610
|
+
return new RegExp(txt
|
|
611
|
+
.replace(/#x([a-zA-Z0-9]{4})/g, '\\u$1')
|
|
612
|
+
.replace(/#x([a-zA-Z0-9]{3})/g, '\\u0$1')
|
|
613
|
+
.replace(/#x([a-zA-Z0-9]{2})/g, '\\x$1')
|
|
614
|
+
.replace(/#x([a-zA-Z0-9]{1})/g, '\\x0$1'));
|
|
615
|
+
}
|
|
616
|
+
function getSubItems(tmpRules, seq, parentName, optionIndex, parentAttributes, isSingleSequence = false) {
|
|
617
|
+
let anterior = null;
|
|
618
|
+
let bnfSeq = [];
|
|
619
|
+
const children = seq.children;
|
|
620
|
+
let subitemIndex = 0; // Track subitems within this sequence
|
|
621
|
+
let itemPosition = 0; // Track all items for position-based naming
|
|
622
|
+
for (let i = 0; i < children.length; i++) {
|
|
623
|
+
const x = children[i];
|
|
624
|
+
if (x.type == 'Minus') {
|
|
625
|
+
restar(anterior, x);
|
|
626
|
+
}
|
|
627
|
+
let decoration = children[i + 1];
|
|
628
|
+
decoration = (decoration && decoration.type == 'PrimaryDecoration' && decoration.text) || '';
|
|
629
|
+
let preDecoration = '';
|
|
630
|
+
if (anterior && anterior.type == 'PrimaryPreDecoration') {
|
|
631
|
+
preDecoration = anterior.text;
|
|
632
|
+
}
|
|
633
|
+
let pinned = preDecoration == '~' ? 1 : undefined;
|
|
634
|
+
if (pinned) {
|
|
635
|
+
preDecoration = '';
|
|
636
|
+
}
|
|
637
|
+
switch (x.type) {
|
|
638
|
+
case 'SubItem':
|
|
639
|
+
subitemIndex++;
|
|
640
|
+
itemPosition++; // Increment position for SubItems
|
|
641
|
+
let name;
|
|
642
|
+
// Build the fragment name by appending to parentName
|
|
643
|
+
const prefix = parentName.startsWith('%') ? '' : '%';
|
|
644
|
+
if (isSingleSequence) {
|
|
645
|
+
// Single sequence: use position-based naming
|
|
646
|
+
name = prefix + parentName + '[' + itemPosition + ']';
|
|
647
|
+
}
|
|
648
|
+
else {
|
|
649
|
+
// Multiple sequences: use option-based naming
|
|
650
|
+
if (subitemIndex === 1) {
|
|
651
|
+
name = prefix + parentName + '[' + optionIndex + ']';
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
name = prefix + parentName + '[' + optionIndex + '][' + subitemIndex + ']';
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
createRule(tmpRules, x, name, parentAttributes);
|
|
658
|
+
bnfSeq.push(preDecoration + name + decoration);
|
|
659
|
+
break;
|
|
660
|
+
case 'NCName':
|
|
661
|
+
itemPosition++; // Increment position for NCNames
|
|
662
|
+
bnfSeq.push(preDecoration + x.text + decoration);
|
|
663
|
+
break;
|
|
664
|
+
case 'StringLiteral':
|
|
665
|
+
itemPosition++; // Increment position for StringLiterals
|
|
666
|
+
if (decoration || preDecoration || !/^['"/()a-zA-Z0-9&_.:=,+*\-\^\\]+$/.test(x.text)) {
|
|
667
|
+
bnfSeq.push(preDecoration + x.text + decoration);
|
|
668
|
+
}
|
|
669
|
+
else {
|
|
670
|
+
for (const c of x.text.slice(1, -1)) {
|
|
671
|
+
if (parentAttributes && parentAttributes["ignoreCase"] == "true" && /[a-zA-Z]/.test(c)) {
|
|
672
|
+
bnfSeq.push(new RegExp("[" + c.toUpperCase() + c.toLowerCase() + "]"));
|
|
673
|
+
}
|
|
674
|
+
else {
|
|
675
|
+
bnfSeq.push(new RegExp((0, Parser_1.escapeRegExp)(c)));
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
break;
|
|
680
|
+
case 'CharCode':
|
|
681
|
+
case 'CharClass':
|
|
682
|
+
itemPosition++; // Increment position for CharCode/CharClass
|
|
683
|
+
if (decoration || preDecoration) {
|
|
684
|
+
subitemIndex++;
|
|
685
|
+
let name;
|
|
686
|
+
// Build the fragment name by appending to parentName
|
|
687
|
+
const prefix = parentName.startsWith('%') ? '' : '%';
|
|
688
|
+
if (isSingleSequence) {
|
|
689
|
+
// Single sequence: use position-based naming
|
|
690
|
+
name = prefix + parentName + '[' + itemPosition + ']';
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
// Multiple sequences: use option-based naming
|
|
694
|
+
if (subitemIndex === 1) {
|
|
695
|
+
name = prefix + parentName + '[' + optionIndex + ']';
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
name = prefix + parentName + '[' + optionIndex + '][' + subitemIndex + ']';
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
let newRule = {
|
|
702
|
+
name,
|
|
703
|
+
bnf: [[convertRegex(x.text)]],
|
|
704
|
+
pinned
|
|
705
|
+
};
|
|
706
|
+
tmpRules.push(newRule);
|
|
707
|
+
bnfSeq.push(preDecoration + newRule.name + decoration);
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
bnfSeq.push(convertRegex(x.text));
|
|
711
|
+
}
|
|
712
|
+
break;
|
|
713
|
+
case 'PrimaryPreDecoration':
|
|
714
|
+
case 'PrimaryDecoration':
|
|
715
|
+
break;
|
|
716
|
+
default:
|
|
717
|
+
throw new Error(' HOW SHOULD I PARSE THIS? ' + x.type + ' -> ' + JSON.stringify(x.text));
|
|
718
|
+
}
|
|
719
|
+
anterior = x;
|
|
720
|
+
}
|
|
721
|
+
return bnfSeq;
|
|
722
|
+
}
|
|
723
|
+
function createRule(tmpRules, token, name, parentAttributes = undefined) {
|
|
724
|
+
let attrNode = token.children.filter(x => x.type == 'Attributes')[0];
|
|
725
|
+
let attributes = {};
|
|
726
|
+
if (attrNode) {
|
|
727
|
+
for (const x of attrNode.children) {
|
|
728
|
+
let attrName = x.children.filter(x => x.type == 'NCName')[0].text;
|
|
729
|
+
if (attrName in attributes) {
|
|
730
|
+
throw new TokenError_1.TokenError('Duplicated attribute ' + attrName, x);
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
attributes[attrName] = x.children.filter(x => x.type == 'AttributeValue')[0].text;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
let sequences = token.children.filter(x => x.type == 'SequenceOrDifference');
|
|
738
|
+
// Determine if this rule has a single sequence (no alternatives)
|
|
739
|
+
const isSingleSequence = sequences.length === 1;
|
|
740
|
+
let bnf = sequences.map((s, optionIndex) => getSubItems(tmpRules, s, name, optionIndex + 1, parentAttributes ? parentAttributes : attributes, isSingleSequence));
|
|
741
|
+
let rule = {
|
|
742
|
+
name,
|
|
743
|
+
bnf
|
|
744
|
+
};
|
|
745
|
+
if (name.indexOf('%') == 0)
|
|
746
|
+
rule.fragment = true;
|
|
747
|
+
if (attributes['recoverUntil']) {
|
|
748
|
+
rule.recover = attributes['recoverUntil'];
|
|
749
|
+
if (rule.bnf.length > 1)
|
|
750
|
+
throw new TokenError_1.TokenError('only one-option productions are suitable for error recovering', token);
|
|
751
|
+
}
|
|
752
|
+
if ('pin' in attributes) {
|
|
753
|
+
let num = parseInt(attributes['pin']);
|
|
754
|
+
if (!isNaN(num)) {
|
|
755
|
+
rule.pinned = num;
|
|
756
|
+
}
|
|
757
|
+
if (rule.bnf.length > 1)
|
|
758
|
+
throw new TokenError_1.TokenError('only one-option productions are suitable for pinning', token);
|
|
759
|
+
}
|
|
760
|
+
if ('ws' in attributes) {
|
|
761
|
+
rule.implicitWs = attributes['ws'] != 'explicit';
|
|
762
|
+
}
|
|
763
|
+
else {
|
|
764
|
+
rule.implicitWs = null;
|
|
765
|
+
}
|
|
766
|
+
rule.fragment = rule.fragment || attributes['fragment'] == 'true';
|
|
767
|
+
rule.simplifyWhenOneChildren = attributes['simplifyWhenOneChildren'] == 'true';
|
|
768
|
+
tmpRules.push(rule);
|
|
769
|
+
}
|
|
770
|
+
function getRules(source, parser = BNF.defaultParser) {
|
|
771
|
+
let ast = parser.getAST(source);
|
|
772
|
+
if (!ast)
|
|
773
|
+
throw new Error('Could not parse ' + source);
|
|
774
|
+
if (ast.errors && ast.errors.length) {
|
|
775
|
+
throw ast.errors[0];
|
|
776
|
+
}
|
|
777
|
+
let implicitWs = null;
|
|
778
|
+
let attrNode = ast.children.filter(x => x.type == 'Attributes')[0];
|
|
779
|
+
let attributes = {};
|
|
780
|
+
if (attrNode) {
|
|
781
|
+
for (const x of attrNode.children) {
|
|
782
|
+
let attrName = x.children.filter(x => x.type == 'NCName')[0].text;
|
|
783
|
+
if (attrName in attributes) {
|
|
784
|
+
throw new TokenError_1.TokenError('Duplicated attribute ' + attrName, x);
|
|
785
|
+
}
|
|
786
|
+
else {
|
|
787
|
+
attributes[attrName] = x.children.filter(x => x.type == 'AttributeValue')[0].text;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
implicitWs = attributes['ws'] == 'implicit';
|
|
792
|
+
let tmpRules = [];
|
|
793
|
+
ast.children.filter(x => x.type == 'Production').map((x) => {
|
|
794
|
+
let name = x.children.filter(x => x.type == 'NCName')[0].text;
|
|
795
|
+
createRule(tmpRules, x, name);
|
|
796
|
+
});
|
|
797
|
+
for (const rule of tmpRules) {
|
|
798
|
+
if (rule.implicitWs === null)
|
|
799
|
+
rule.implicitWs = implicitWs;
|
|
800
|
+
}
|
|
801
|
+
return tmpRules;
|
|
802
|
+
}
|
|
803
|
+
BNF.getRules = getRules;
|
|
804
|
+
function Transform(source, subParser = BNF.defaultParser) {
|
|
805
|
+
return getRules(source.join(''), subParser);
|
|
806
|
+
}
|
|
807
|
+
BNF.Transform = Transform;
|
|
808
|
+
class Parser extends Parser_1.Parser {
|
|
809
|
+
constructor(source, options) {
|
|
810
|
+
const subParser = options && options.debugRulesParser === true ? new Parser_1.Parser(BNF.RULES, { debug: true }) : BNF.defaultParser;
|
|
811
|
+
super(getRules(source, subParser), options);
|
|
812
|
+
}
|
|
813
|
+
emitSource() {
|
|
814
|
+
return emit(this);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
BNF.Parser = Parser;
|
|
818
|
+
})(BNF || (BNF = {}));
|
|
819
|
+
exports.default = BNF;
|
|
820
|
+
|
|
821
|
+
},{"../Parser":6,"../TokenError":9}],4:[function(require,module,exports){
|
|
822
|
+
"use strict";
|
|
823
|
+
// https://www.w3.org/TR/REC-xml/#NT-Name
|
|
824
|
+
// http://www.bottlecaps.de/rr/ui
|
|
825
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
826
|
+
// Grammar ::= Production*
|
|
827
|
+
// Production ::= NCName '::=' Choice
|
|
828
|
+
// NCName ::= [http://www.w3.org/TR/xml-names/#NT-NCName]
|
|
829
|
+
// Choice ::= SequenceOrDifference ( '|' SequenceOrDifference )*
|
|
830
|
+
// SequenceOrDifference ::= (Item ( '-' Item | Item* ))?
|
|
831
|
+
// Item ::= Primary ( '?' | '*' | '+' )?
|
|
832
|
+
// Primary ::= NCName | StringLiteral | CharCode | CharClass | '(' Choice ')'
|
|
833
|
+
// StringLiteral ::= '"' [^"]* '"' | "'" [^']* "'"
|
|
834
|
+
// CharCode ::= '#x' [0-9a-fA-F]+
|
|
835
|
+
// CharClass ::= '[' '^'? ( RULE_Char | CharCode | CharRange | CharCodeRange )+ ']'
|
|
836
|
+
// RULE_Char ::= [http://www.w3.org/TR/xml#NT-RULE_Char]
|
|
837
|
+
// CharRange ::= RULE_Char '-' ( RULE_Char - ']' )
|
|
838
|
+
// CharCodeRange ::= CharCode '-' CharCode
|
|
839
|
+
// RULE_WHITESPACE ::= RULE_S | Comment
|
|
840
|
+
// RULE_S ::= #x9 | #xA | #xD | #x20
|
|
841
|
+
// Comment ::= '/*' ( [^*] | '*'+ [^*/] )* '*'* '*/'
|
|
842
|
+
const Parser_1 = require("../Parser");
|
|
843
|
+
var BNF;
|
|
844
|
+
(function (BNF) {
|
|
845
|
+
BNF.RULES = [
|
|
846
|
+
{
|
|
847
|
+
name: 'Grammar',
|
|
848
|
+
bnf: [['RULE_S*', '%Atomic*', 'EOF']]
|
|
849
|
+
},
|
|
850
|
+
{
|
|
851
|
+
name: '%Atomic',
|
|
852
|
+
bnf: [['Production', 'RULE_S*']],
|
|
853
|
+
fragment: true
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
name: 'Production',
|
|
857
|
+
bnf: [['NCName', 'RULE_S*', 'ProductionOperator', 'RULE_WHITESPACE*', 'Choice', 'RULE_WHITESPACE*', 'RULE_EOL+', 'RULE_S*']]
|
|
858
|
+
},
|
|
859
|
+
{
|
|
860
|
+
name: 'ProductionOperator',
|
|
861
|
+
bnf: [['"::="'], ['"||="']]
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
name: 'NCName',
|
|
865
|
+
bnf: [[/[a-zA-Z][a-zA-Z_0-9]*/]]
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
name: 'Choice',
|
|
869
|
+
bnf: [['SequenceOrDifference', '%_Choice_1*']],
|
|
870
|
+
fragment: true
|
|
871
|
+
},
|
|
872
|
+
{
|
|
873
|
+
name: '%_Choice_1',
|
|
874
|
+
bnf: [['RULE_WHITESPACE*', '"|"', 'RULE_WHITESPACE*', 'SequenceOrDifference']],
|
|
875
|
+
fragment: true
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
name: 'SequenceOrDifference',
|
|
879
|
+
bnf: [['Item', 'RULE_WHITESPACE*', '%_Item_1?']]
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
name: '%_Item_1',
|
|
883
|
+
bnf: [['Minus', 'Item'], ['Item*']],
|
|
884
|
+
fragment: true
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
name: 'Minus',
|
|
888
|
+
bnf: [['"-"']]
|
|
889
|
+
},
|
|
890
|
+
{
|
|
891
|
+
name: 'Item',
|
|
892
|
+
bnf: [['RULE_WHITESPACE*', '%Primary', 'PrimaryDecoration?']],
|
|
893
|
+
fragment: true
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
name: 'PrimaryDecoration',
|
|
897
|
+
bnf: [['"?"'], ['"*"'], ['"+"']]
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
name: 'DecorationName',
|
|
901
|
+
bnf: [['"ebnf://"', /[^\x5D#]+/]]
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
name: '%Primary',
|
|
905
|
+
bnf: [['NCName'], ['StringLiteral'], ['CharCode'], ['CharClass'], ['SubItem']],
|
|
906
|
+
fragment: true
|
|
907
|
+
},
|
|
908
|
+
{
|
|
909
|
+
name: 'SubItem',
|
|
910
|
+
bnf: [['"("', 'RULE_WHITESPACE*', 'Choice', 'RULE_WHITESPACE*', '")"']]
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
name: 'StringLiteral',
|
|
914
|
+
bnf: [[`'"'`, /[^"]*/, `'"'`], [`"'"`, /[^']*/, `"'"`]],
|
|
915
|
+
pinned: 1
|
|
916
|
+
},
|
|
917
|
+
{
|
|
918
|
+
name: 'CharCode',
|
|
919
|
+
bnf: [['"#x"', /[0-9a-zA-Z]+/]]
|
|
920
|
+
},
|
|
921
|
+
{
|
|
922
|
+
name: 'CharClass',
|
|
923
|
+
bnf: [["'['", "'^'?", '%RULE_CharClass_1+', '"]"']]
|
|
924
|
+
},
|
|
925
|
+
{
|
|
926
|
+
name: '%RULE_CharClass_1',
|
|
927
|
+
bnf: [['CharCodeRange'], ['CharRange'], ['CharCode'], ['RULE_Char']],
|
|
928
|
+
fragment: true
|
|
929
|
+
},
|
|
930
|
+
{
|
|
931
|
+
name: 'RULE_Char',
|
|
932
|
+
bnf: [[/\x09/], [/\x0A/], [/\x0D/], [/[\x20-\x5c]/], [/[\x5e-\uD7FF]/], [/[\uE000-\uFFFD]/]]
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
name: 'CharRange',
|
|
936
|
+
bnf: [['RULE_Char', '"-"', 'RULE_Char']]
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
name: 'CharCodeRange',
|
|
940
|
+
bnf: [['CharCode', '"-"', 'CharCode']]
|
|
941
|
+
},
|
|
942
|
+
{
|
|
943
|
+
name: 'RULE_WHITESPACE',
|
|
944
|
+
bnf: [['%RULE_WHITESPACE_CHAR*'], ['Comment', 'RULE_WHITESPACE*']]
|
|
945
|
+
},
|
|
946
|
+
{
|
|
947
|
+
name: 'RULE_S',
|
|
948
|
+
bnf: [['RULE_WHITESPACE', 'RULE_S*'], ['RULE_EOL', 'RULE_S*']]
|
|
949
|
+
},
|
|
950
|
+
{
|
|
951
|
+
name: '%RULE_WHITESPACE_CHAR',
|
|
952
|
+
bnf: [[/\x09/], [/\x20/]],
|
|
953
|
+
fragment: true
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
name: 'Comment',
|
|
957
|
+
bnf: [['"/*"', '%RULE_Comment_Body*', '"*/"']]
|
|
958
|
+
},
|
|
959
|
+
{
|
|
960
|
+
name: '%RULE_Comment_Body',
|
|
961
|
+
bnf: [['!"*/"', /[^*]/]],
|
|
962
|
+
fragment: true
|
|
963
|
+
},
|
|
964
|
+
{
|
|
965
|
+
name: 'RULE_EOL',
|
|
966
|
+
bnf: [[/\x0D/, /\x0A/], [/\x0A/], [/\x0D/]]
|
|
967
|
+
},
|
|
968
|
+
{
|
|
969
|
+
name: 'Link',
|
|
970
|
+
bnf: [["'['", 'Url', "']'"]]
|
|
971
|
+
},
|
|
972
|
+
{
|
|
973
|
+
name: 'Url',
|
|
974
|
+
bnf: [[/[^\x5D:/?#]/, '"://"', /[^\x5D#]+/, '%Url1?']]
|
|
975
|
+
},
|
|
976
|
+
{
|
|
977
|
+
name: '%Url1',
|
|
978
|
+
bnf: [['"#"', 'NCName']],
|
|
979
|
+
fragment: true
|
|
980
|
+
}
|
|
981
|
+
];
|
|
982
|
+
BNF.defaultParser = new Parser_1.Parser(BNF.RULES, { debug: false });
|
|
983
|
+
const preDecorationRE = /^(!|&)/;
|
|
984
|
+
const decorationRE = /(\?|\+|\*)$/;
|
|
985
|
+
const subExpressionRE = /^%/;
|
|
986
|
+
function getBNFRule(name, parser) {
|
|
987
|
+
if (typeof name == 'string') {
|
|
988
|
+
if (preDecorationRE.test(name))
|
|
989
|
+
return '';
|
|
990
|
+
let subexpression = subExpressionRE.test(name);
|
|
991
|
+
if (subexpression) {
|
|
992
|
+
let decoration = decorationRE.exec(name);
|
|
993
|
+
let decorationText = decoration ? decoration[0] + ' ' : '';
|
|
994
|
+
let lonely = isLonelyRule(name, parser);
|
|
995
|
+
if (lonely)
|
|
996
|
+
return getBNFBody(name, parser) + decorationText;
|
|
997
|
+
return '(' + getBNFBody(name, parser) + ')' + decorationText;
|
|
998
|
+
}
|
|
999
|
+
return name;
|
|
1000
|
+
}
|
|
1001
|
+
else {
|
|
1002
|
+
return name.source
|
|
1003
|
+
.replace(/\\(?:x|u)([a-zA-Z0-9]+)/g, '#x$1')
|
|
1004
|
+
.replace(/\[\\(?:x|u)([a-zA-Z0-9]+)-\\(?:x|u)([a-zA-Z0-9]+)\]/g, '[#x$1-#x$2]');
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
/// Returns true if the rule is a string literal or regular expression without a descendant tree
|
|
1008
|
+
function isLonelyRule(name, parser) {
|
|
1009
|
+
let rule = (0, Parser_1.findRuleByName)(name, parser);
|
|
1010
|
+
return (rule &&
|
|
1011
|
+
rule.bnf.length == 1 &&
|
|
1012
|
+
rule.bnf[0].length == 1 &&
|
|
1013
|
+
(rule.bnf[0][0] instanceof RegExp || rule.bnf[0][0][0] == '"' || rule.bnf[0][0][0] == "'"));
|
|
1014
|
+
}
|
|
1015
|
+
function getBNFChoice(rules, parser) {
|
|
1016
|
+
return rules.map(x => getBNFRule(x, parser)).join(' ');
|
|
1017
|
+
}
|
|
1018
|
+
function getBNFBody(name, parser) {
|
|
1019
|
+
let rule = (0, Parser_1.findRuleByName)(name, parser);
|
|
1020
|
+
if (rule)
|
|
1021
|
+
return rule.bnf.map(x => getBNFChoice(x, parser)).join(' | ');
|
|
1022
|
+
return 'RULE_NOT_FOUND {' + name + '}';
|
|
1023
|
+
}
|
|
1024
|
+
function emit(parser) {
|
|
1025
|
+
let acumulator = [];
|
|
1026
|
+
for (const l of parser.grammarRules) {
|
|
1027
|
+
if (!/^%/.test(l.name)) {
|
|
1028
|
+
let recover = l.recover ? ' /* { recoverUntil=' + l.recover + ' } */' : '';
|
|
1029
|
+
acumulator.push(l.name + ' ::= ' + getBNFBody(l.name, parser) + recover);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
return acumulator.join('\n');
|
|
1033
|
+
}
|
|
1034
|
+
BNF.emit = emit;
|
|
1035
|
+
function restar(total, resta) {
|
|
1036
|
+
console.log('reberia restar ' + resta + ' a ' + total);
|
|
1037
|
+
throw new Error('Difference not supported yet');
|
|
1038
|
+
}
|
|
1039
|
+
function convertRegex(txt, caseInsensitive = false) {
|
|
1040
|
+
const pattern = txt
|
|
1041
|
+
.replace(/#x([a-zA-Z0-9]{4})/g, '\\u$1')
|
|
1042
|
+
.replace(/#x([a-zA-Z0-9]{3})/g, '\\u0$1')
|
|
1043
|
+
.replace(/#x([a-zA-Z0-9]{2})/g, '\\x$1')
|
|
1044
|
+
.replace(/#x([a-zA-Z0-9]{1})/g, '\\x0$1');
|
|
1045
|
+
return new RegExp(pattern, caseInsensitive ? 'i' : '');
|
|
1046
|
+
}
|
|
1047
|
+
function getSubItems(tmpRules, seq, parentName, optionIndex, caseInsensitive = false, isSingleSequence = false) {
|
|
1048
|
+
let anterior = null;
|
|
1049
|
+
let bnfSeq = [];
|
|
1050
|
+
const children = seq.children;
|
|
1051
|
+
let subitemIndex = 0; // Track subitems within this sequence
|
|
1052
|
+
let itemPosition = 0; // Track all items (including literals, NCNames, SubItems) for position-based naming
|
|
1053
|
+
for (let i = 0; i < children.length; i++) {
|
|
1054
|
+
const x = children[i];
|
|
1055
|
+
if (x.type == 'Minus') {
|
|
1056
|
+
restar(anterior, x);
|
|
1057
|
+
}
|
|
1058
|
+
let decoration = children[i + 1];
|
|
1059
|
+
decoration = (decoration && decoration.type == 'PrimaryDecoration' && decoration.text) || '';
|
|
1060
|
+
let preDecoration = '';
|
|
1061
|
+
switch (x.type) {
|
|
1062
|
+
case 'SubItem':
|
|
1063
|
+
subitemIndex++;
|
|
1064
|
+
itemPosition++; // Increment position for SubItems
|
|
1065
|
+
let name;
|
|
1066
|
+
// Build the fragment name by appending to parentName
|
|
1067
|
+
const prefix = parentName.startsWith('%') ? '' : '%';
|
|
1068
|
+
if (isSingleSequence) {
|
|
1069
|
+
// Single sequence: use position-based naming
|
|
1070
|
+
name = prefix + parentName + '[' + itemPosition + ']';
|
|
1071
|
+
}
|
|
1072
|
+
else {
|
|
1073
|
+
// Multiple sequences: use option-based naming
|
|
1074
|
+
if (subitemIndex === 1) {
|
|
1075
|
+
name = prefix + parentName + '[' + optionIndex + ']';
|
|
1076
|
+
}
|
|
1077
|
+
else {
|
|
1078
|
+
name = prefix + parentName + '[' + optionIndex + '][' + subitemIndex + ']';
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
createRule(tmpRules, x, name, caseInsensitive);
|
|
1082
|
+
bnfSeq.push(preDecoration + name + decoration);
|
|
1083
|
+
break;
|
|
1084
|
+
case 'NCName':
|
|
1085
|
+
itemPosition++; // Increment position for NCNames
|
|
1086
|
+
bnfSeq.push(preDecoration + x.text + decoration);
|
|
1087
|
+
break;
|
|
1088
|
+
case 'StringLiteral':
|
|
1089
|
+
itemPosition++; // Increment position for StringLiterals
|
|
1090
|
+
if (caseInsensitive) {
|
|
1091
|
+
// For case insensitive string literals, convert each character to a case-insensitive regex
|
|
1092
|
+
const literalText = x.text.slice(1, -1); // Remove quotes
|
|
1093
|
+
for (const c of literalText) {
|
|
1094
|
+
if (/[a-zA-Z]/.test(c)) {
|
|
1095
|
+
bnfSeq.push(new RegExp('[' + c.toUpperCase() + c.toLowerCase() + ']'));
|
|
1096
|
+
}
|
|
1097
|
+
else {
|
|
1098
|
+
bnfSeq.push(new RegExp((0, Parser_1.escapeRegExp)(c)));
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
else {
|
|
1103
|
+
bnfSeq.push(preDecoration + x.text + decoration);
|
|
1104
|
+
}
|
|
1105
|
+
break;
|
|
1106
|
+
case 'CharCode':
|
|
1107
|
+
case 'CharClass':
|
|
1108
|
+
itemPosition++; // Increment position for CharCode/CharClass
|
|
1109
|
+
if (decoration || preDecoration) {
|
|
1110
|
+
subitemIndex++;
|
|
1111
|
+
let name;
|
|
1112
|
+
// Build the fragment name by appending to parentName
|
|
1113
|
+
const prefix = parentName.startsWith('%') ? '' : '%';
|
|
1114
|
+
if (isSingleSequence) {
|
|
1115
|
+
// Single sequence: use position-based naming
|
|
1116
|
+
name = prefix + parentName + '[' + itemPosition + ']';
|
|
1117
|
+
}
|
|
1118
|
+
else {
|
|
1119
|
+
// Multiple sequences: use option-based naming
|
|
1120
|
+
if (subitemIndex === 1) {
|
|
1121
|
+
name = prefix + parentName + '[' + optionIndex + ']';
|
|
1122
|
+
}
|
|
1123
|
+
else {
|
|
1124
|
+
name = prefix + parentName + '[' + optionIndex + '][' + subitemIndex + ']';
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
let newRule = {
|
|
1128
|
+
name,
|
|
1129
|
+
bnf: [[convertRegex(x.text, caseInsensitive)]]
|
|
1130
|
+
};
|
|
1131
|
+
tmpRules.push(newRule);
|
|
1132
|
+
bnfSeq.push(preDecoration + newRule.name + decoration);
|
|
1133
|
+
}
|
|
1134
|
+
else {
|
|
1135
|
+
bnfSeq.push(convertRegex(x.text, caseInsensitive));
|
|
1136
|
+
}
|
|
1137
|
+
break;
|
|
1138
|
+
case 'PrimaryDecoration':
|
|
1139
|
+
break;
|
|
1140
|
+
default:
|
|
1141
|
+
throw new Error(' HOW SHOULD I PARSE THIS? ' + x.type + ' -> ' + JSON.stringify(x.text));
|
|
1142
|
+
}
|
|
1143
|
+
anterior = x;
|
|
1144
|
+
}
|
|
1145
|
+
return bnfSeq;
|
|
1146
|
+
}
|
|
1147
|
+
function createRule(tmpRules, token, name, caseInsensitive = false) {
|
|
1148
|
+
// Check if this production uses the case insensitive operator ||=
|
|
1149
|
+
const operatorNode = token.children.find(x => x.type == 'ProductionOperator');
|
|
1150
|
+
const isCaseInsensitive = caseInsensitive || (operatorNode && operatorNode.text === '||=');
|
|
1151
|
+
let sequences = token.children.filter(x => x.type == 'SequenceOrDifference');
|
|
1152
|
+
// Determine if this rule has a single sequence (no alternatives)
|
|
1153
|
+
const isSingleSequence = sequences.length === 1;
|
|
1154
|
+
let bnf = sequences.map((s, optionIndex) => getSubItems(tmpRules, s, name, optionIndex + 1, isCaseInsensitive, isSingleSequence));
|
|
1155
|
+
let rule = {
|
|
1156
|
+
name,
|
|
1157
|
+
bnf
|
|
1158
|
+
};
|
|
1159
|
+
let recover = null;
|
|
1160
|
+
for (const x of bnf) {
|
|
1161
|
+
recover = recover || x['recover'];
|
|
1162
|
+
delete x['recover'];
|
|
1163
|
+
}
|
|
1164
|
+
if (name.indexOf('%') == 0)
|
|
1165
|
+
rule.fragment = true;
|
|
1166
|
+
if (recover)
|
|
1167
|
+
rule.recover = recover;
|
|
1168
|
+
tmpRules.push(rule);
|
|
1169
|
+
}
|
|
1170
|
+
function getRules(source, parser = BNF.defaultParser) {
|
|
1171
|
+
let ast = parser.getAST(source);
|
|
1172
|
+
if (!ast)
|
|
1173
|
+
throw new Error('Could not parse ' + source);
|
|
1174
|
+
if (ast.errors && ast.errors.length) {
|
|
1175
|
+
throw ast.errors[0];
|
|
1176
|
+
}
|
|
1177
|
+
let tmpRules = [];
|
|
1178
|
+
ast.children.filter(x => x.type == 'Production').map((x) => {
|
|
1179
|
+
let name = x.children.filter(x => x.type == 'NCName')[0].text;
|
|
1180
|
+
createRule(tmpRules, x, name);
|
|
1181
|
+
});
|
|
1182
|
+
return tmpRules;
|
|
1183
|
+
}
|
|
1184
|
+
BNF.getRules = getRules;
|
|
1185
|
+
function Transform(source, subParser = BNF.defaultParser) {
|
|
1186
|
+
return getRules(source.join(''), subParser);
|
|
1187
|
+
}
|
|
1188
|
+
BNF.Transform = Transform;
|
|
1189
|
+
class Parser extends Parser_1.Parser {
|
|
1190
|
+
constructor(source, options) {
|
|
1191
|
+
const subParser = options && options.debugRulesParser === true ? new Parser_1.Parser(BNF.RULES, { debug: true }) : BNF.defaultParser;
|
|
1192
|
+
super(getRules(source, subParser), options);
|
|
1193
|
+
}
|
|
1194
|
+
emitSource() {
|
|
1195
|
+
return emit(this);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
BNF.Parser = Parser;
|
|
1199
|
+
})(BNF || (BNF = {}));
|
|
1200
|
+
exports.default = BNF;
|
|
1201
|
+
|
|
1202
|
+
},{"../Parser":6}],5:[function(require,module,exports){
|
|
1203
|
+
"use strict";
|
|
1204
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1205
|
+
exports.Custom = exports.W3C = exports.BNF = void 0;
|
|
1206
|
+
var BNF_1 = require("./BNF");
|
|
1207
|
+
Object.defineProperty(exports, "BNF", { enumerable: true, get: function () { return BNF_1.default; } });
|
|
1208
|
+
var W3CEBNF_1 = require("./W3CEBNF");
|
|
1209
|
+
Object.defineProperty(exports, "W3C", { enumerable: true, get: function () { return W3CEBNF_1.default; } });
|
|
1210
|
+
var Custom_1 = require("./Custom");
|
|
1211
|
+
Object.defineProperty(exports, "Custom", { enumerable: true, get: function () { return Custom_1.default; } });
|
|
1212
|
+
|
|
1213
|
+
},{"./BNF":2,"./Custom":3,"./W3CEBNF":4}],6:[function(require,module,exports){
|
|
1214
|
+
"use strict";
|
|
1215
|
+
// https://www.ics.uci.edu/~pattis/ICS-33/lectures/ebnf.pdf
|
|
1216
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1217
|
+
exports.Parser = void 0;
|
|
1218
|
+
exports.readToken = readToken;
|
|
1219
|
+
exports.escapeRegExp = escapeRegExp;
|
|
1220
|
+
exports.parseRuleName = parseRuleName;
|
|
1221
|
+
exports.findRuleByName = findRuleByName;
|
|
1222
|
+
const UPPER_SNAKE_RE = /^[A-Z0-9_]+$/;
|
|
1223
|
+
const decorationRE = /(\?|\+|\*)$/;
|
|
1224
|
+
const preDecorationRE = /^(@|&|!)/;
|
|
1225
|
+
const WS_RULE = 'WS';
|
|
1226
|
+
const TokenError_1 = require("./TokenError");
|
|
1227
|
+
const ParsingError_1 = require("./ParsingError");
|
|
1228
|
+
function readToken(txt, expr) {
|
|
1229
|
+
let result = expr.exec(txt);
|
|
1230
|
+
if (result && result.index == 0) {
|
|
1231
|
+
if (result[0].length == 0 && expr.source.length > 0)
|
|
1232
|
+
return null;
|
|
1233
|
+
return {
|
|
1234
|
+
type: null,
|
|
1235
|
+
text: result[0],
|
|
1236
|
+
rest: txt.substr(result[0].length),
|
|
1237
|
+
start: 0,
|
|
1238
|
+
end: result[0].length - 1,
|
|
1239
|
+
fullText: result[0],
|
|
1240
|
+
errors: [],
|
|
1241
|
+
children: [],
|
|
1242
|
+
parent: null
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
return null;
|
|
1246
|
+
}
|
|
1247
|
+
function escapeRegExp(str) {
|
|
1248
|
+
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
|
|
1249
|
+
}
|
|
1250
|
+
function fixRest(token) {
|
|
1251
|
+
token.rest = '';
|
|
1252
|
+
if (token.children) {
|
|
1253
|
+
for (const c of token.children) {
|
|
1254
|
+
fixRest(c);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
function fixPositions(token, start) {
|
|
1259
|
+
token.start += start;
|
|
1260
|
+
token.end += start;
|
|
1261
|
+
if (token.children) {
|
|
1262
|
+
for (const c of token.children) {
|
|
1263
|
+
fixPositions(c, token.start);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
function agregateErrors(errors, token) {
|
|
1268
|
+
if (token.errors && token.errors.length) {
|
|
1269
|
+
for (const err of token.errors) {
|
|
1270
|
+
errors.push(err);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
if (token.children) {
|
|
1274
|
+
for (const tok of token.children) {
|
|
1275
|
+
agregateErrors(errors, tok);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
function parseRuleName(name) {
|
|
1280
|
+
let postDecoration = decorationRE.exec(name);
|
|
1281
|
+
let preDecoration = preDecorationRE.exec(name);
|
|
1282
|
+
let postDecorationText = (postDecoration && postDecoration[0]) || '';
|
|
1283
|
+
let preDecorationText = (preDecoration && preDecoration[0]) || '';
|
|
1284
|
+
let out = {
|
|
1285
|
+
raw: name,
|
|
1286
|
+
name: name.replace(decorationRE, '').replace(preDecorationRE, ''),
|
|
1287
|
+
isOptional: postDecorationText == '?' || postDecorationText == '*',
|
|
1288
|
+
allowRepetition: postDecorationText == '+' || postDecorationText == '*',
|
|
1289
|
+
atLeastOne: postDecorationText == '+',
|
|
1290
|
+
lookupPositive: preDecorationText == '&',
|
|
1291
|
+
lookupNegative: preDecorationText == '!',
|
|
1292
|
+
pinned: preDecorationText == '@',
|
|
1293
|
+
lookup: false,
|
|
1294
|
+
isLiteral: false
|
|
1295
|
+
};
|
|
1296
|
+
out.isLiteral = out.name[0] == "'" || out.name[0] == '"';
|
|
1297
|
+
out.lookup = out.lookupNegative || out.lookupPositive;
|
|
1298
|
+
return out;
|
|
1299
|
+
}
|
|
1300
|
+
function findRuleByName(name, parser) {
|
|
1301
|
+
let parsed = parseRuleName(name);
|
|
1302
|
+
return parser.cachedRules[parsed.name] || null;
|
|
1303
|
+
}
|
|
1304
|
+
/// Removes all the nodes starting with 'RULE_'
|
|
1305
|
+
function stripRules(token, re) {
|
|
1306
|
+
if (token.children) {
|
|
1307
|
+
let localRules = token.children.filter(x => x.type && re.test(x.type));
|
|
1308
|
+
for (let i = 0; i < localRules.length; i++) {
|
|
1309
|
+
let indexOnChildren = token.children.indexOf(localRules[i]);
|
|
1310
|
+
if (indexOnChildren != -1) {
|
|
1311
|
+
token.children.splice(indexOnChildren, 1);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
for (const c of token.children) {
|
|
1315
|
+
stripRules(c, re);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
const ignoreMissingRules = ['EOF'];
|
|
1320
|
+
class Parser {
|
|
1321
|
+
constructor(grammarRules, options) {
|
|
1322
|
+
this.grammarRules = grammarRules;
|
|
1323
|
+
this.options = options;
|
|
1324
|
+
this.furthestFailure = null;
|
|
1325
|
+
this.originalInput = '';
|
|
1326
|
+
this.parseStack = []; // Track parsing context
|
|
1327
|
+
this.cachedRules = {};
|
|
1328
|
+
this.debug = options ? options.debug === true : false;
|
|
1329
|
+
let errors = [];
|
|
1330
|
+
let neededRules = [];
|
|
1331
|
+
for (const rule of grammarRules) {
|
|
1332
|
+
let parsedName = parseRuleName(rule.name);
|
|
1333
|
+
if (parsedName.name in this.cachedRules) {
|
|
1334
|
+
errors.push('Duplicated rule ' + parsedName.name);
|
|
1335
|
+
continue;
|
|
1336
|
+
}
|
|
1337
|
+
else {
|
|
1338
|
+
this.cachedRules[parsedName.name] = rule;
|
|
1339
|
+
}
|
|
1340
|
+
if (!rule.bnf || !rule.bnf.length) {
|
|
1341
|
+
let error = 'Missing rule content, rule: ' + rule.name;
|
|
1342
|
+
if (errors.indexOf(error) == -1)
|
|
1343
|
+
errors.push(error);
|
|
1344
|
+
}
|
|
1345
|
+
else {
|
|
1346
|
+
for (const options of rule.bnf) {
|
|
1347
|
+
if (typeof options[0] === 'string') {
|
|
1348
|
+
let parsed = parseRuleName(options[0]);
|
|
1349
|
+
if (parsed.name == rule.name) {
|
|
1350
|
+
let error = 'Left recursion is not allowed, rule: ' + rule.name;
|
|
1351
|
+
if (errors.indexOf(error) == -1)
|
|
1352
|
+
errors.push(error);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
for (const option of options) {
|
|
1356
|
+
if (typeof option == 'string') {
|
|
1357
|
+
let name = parseRuleName(option);
|
|
1358
|
+
if (!name.isLiteral &&
|
|
1359
|
+
neededRules.indexOf(name.name) == -1 &&
|
|
1360
|
+
ignoreMissingRules.indexOf(name.name) == -1)
|
|
1361
|
+
neededRules.push(name.name);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
if (WS_RULE == rule.name)
|
|
1367
|
+
rule.implicitWs = false;
|
|
1368
|
+
if (rule.implicitWs) {
|
|
1369
|
+
if (neededRules.indexOf(WS_RULE) == -1)
|
|
1370
|
+
neededRules.push(WS_RULE);
|
|
1371
|
+
}
|
|
1372
|
+
if (rule.recover) {
|
|
1373
|
+
if (neededRules.indexOf(rule.recover) == -1)
|
|
1374
|
+
neededRules.push(rule.recover);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
for (const ruleName of neededRules) {
|
|
1378
|
+
if (!(ruleName in this.cachedRules)) {
|
|
1379
|
+
errors.push('Missing rule ' + ruleName);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
if (errors.length)
|
|
1383
|
+
throw new Error(errors.join('\n'));
|
|
1384
|
+
}
|
|
1385
|
+
getAST(txt, target) {
|
|
1386
|
+
if (!target) {
|
|
1387
|
+
target = this.grammarRules.filter(x => !x.fragment && x.name.indexOf('%') != 0)[0].name;
|
|
1388
|
+
}
|
|
1389
|
+
// Reset failure tracking for each new parse
|
|
1390
|
+
this.furthestFailure = null;
|
|
1391
|
+
this.originalInput = txt;
|
|
1392
|
+
this.parseStack = [];
|
|
1393
|
+
let result = this.parse(txt, target, 0, 0);
|
|
1394
|
+
if (result) {
|
|
1395
|
+
agregateErrors(result.errors, result);
|
|
1396
|
+
fixPositions(result, 0);
|
|
1397
|
+
// REMOVE ALL THE TAGS MATCHING /^%/
|
|
1398
|
+
stripRules(result, /^%/);
|
|
1399
|
+
if (!this.options || !this.options.keepUpperRules)
|
|
1400
|
+
stripRules(result, UPPER_SNAKE_RE);
|
|
1401
|
+
let rest = result.rest;
|
|
1402
|
+
if (rest) {
|
|
1403
|
+
new TokenError_1.TokenError('Unexpected end of input: \n' + rest, result);
|
|
1404
|
+
}
|
|
1405
|
+
fixRest(result);
|
|
1406
|
+
result.rest = rest;
|
|
1407
|
+
}
|
|
1408
|
+
else {
|
|
1409
|
+
// Parsing failed completely - throw ParsingError
|
|
1410
|
+
if (this.furthestFailure) {
|
|
1411
|
+
const position = this.calculatePosition(this.originalInput, this.furthestFailure.offset);
|
|
1412
|
+
const found = this.furthestFailure.found;
|
|
1413
|
+
// Extract parent-most rules first
|
|
1414
|
+
const parentMostRules = this.extractParentMostRules(this.furthestFailure.tree);
|
|
1415
|
+
// Build failure tree starting from the parent-most failing rules
|
|
1416
|
+
const failureTree = this.buildFailureTree(this.furthestFailure.tree, parentMostRules, this.furthestFailure.regexMap);
|
|
1417
|
+
throw new ParsingError_1.ParsingError('Failed to parse input', position, parentMostRules, found, failureTree);
|
|
1418
|
+
}
|
|
1419
|
+
else {
|
|
1420
|
+
// Fallback if no failure was tracked
|
|
1421
|
+
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');
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
return result;
|
|
1425
|
+
}
|
|
1426
|
+
emitSource() {
|
|
1427
|
+
return 'CANNOT EMIT SOURCE FROM BASE Parser';
|
|
1428
|
+
}
|
|
1429
|
+
calculatePosition(txt, offset) {
|
|
1430
|
+
let line = 1;
|
|
1431
|
+
let column = 1;
|
|
1432
|
+
for (let i = 0; i < offset && i < txt.length; i++) {
|
|
1433
|
+
// Handle \r\n as a single line ending
|
|
1434
|
+
if (txt[i] === '\r' && i + 1 < txt.length && txt[i + 1] === '\n') {
|
|
1435
|
+
line++;
|
|
1436
|
+
column = 1;
|
|
1437
|
+
i++; // Skip the \n
|
|
1438
|
+
}
|
|
1439
|
+
else if (txt[i] === '\n' || txt[i] === '\r') {
|
|
1440
|
+
line++;
|
|
1441
|
+
column = 1;
|
|
1442
|
+
}
|
|
1443
|
+
else {
|
|
1444
|
+
column++;
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
return { offset, line, column };
|
|
1448
|
+
}
|
|
1449
|
+
recordFailure(offset, expected) {
|
|
1450
|
+
const expectedKey = expected instanceof RegExp ? expected.source : expected;
|
|
1451
|
+
if (!this.furthestFailure || offset > this.furthestFailure.offset) {
|
|
1452
|
+
// This is a new furthest failure
|
|
1453
|
+
let found;
|
|
1454
|
+
if (offset >= this.originalInput.length) {
|
|
1455
|
+
found = 'end of input';
|
|
1456
|
+
}
|
|
1457
|
+
else {
|
|
1458
|
+
// Extract a meaningful token/substring from the failure position
|
|
1459
|
+
const remaining = this.originalInput.substring(offset);
|
|
1460
|
+
// Skip whitespace using replace
|
|
1461
|
+
const trimmed = remaining.replace(/^\s+/, '');
|
|
1462
|
+
if (trimmed.length === 0) {
|
|
1463
|
+
found = 'end of input';
|
|
1464
|
+
}
|
|
1465
|
+
else {
|
|
1466
|
+
// Try to extract a word/token (alphanumeric + some symbols)
|
|
1467
|
+
const match = trimmed.match(/^[^\s\}\]\),;]+/);
|
|
1468
|
+
if (match && match[0].length > 0) {
|
|
1469
|
+
found = match[0];
|
|
1470
|
+
}
|
|
1471
|
+
else {
|
|
1472
|
+
// Fallback to first character after whitespace
|
|
1473
|
+
found = trimmed.charAt(0);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
this.furthestFailure = {
|
|
1478
|
+
offset,
|
|
1479
|
+
expected: new Set([expectedKey]),
|
|
1480
|
+
found,
|
|
1481
|
+
tree: new Map(),
|
|
1482
|
+
regexMap: new Map()
|
|
1483
|
+
};
|
|
1484
|
+
// Store regex instance if it's a RegExp
|
|
1485
|
+
if (expected instanceof RegExp) {
|
|
1486
|
+
this.furthestFailure.regexMap.set(expected.source, expected);
|
|
1487
|
+
}
|
|
1488
|
+
this.recordParentChildRelationship(expected);
|
|
1489
|
+
this.furthestFailure.expected.add(expectedKey);
|
|
1490
|
+
}
|
|
1491
|
+
else if (offset === this.furthestFailure.offset) {
|
|
1492
|
+
// Same position, add to expected set
|
|
1493
|
+
this.furthestFailure.expected.add(expectedKey);
|
|
1494
|
+
// Store regex instance if it's a RegExp
|
|
1495
|
+
if (expected instanceof RegExp) {
|
|
1496
|
+
this.furthestFailure.regexMap.set(expected.source, expected);
|
|
1497
|
+
}
|
|
1498
|
+
this.recordParentChildRelationship(expected);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
recordParentChildRelationship(expected) {
|
|
1502
|
+
if (!this.furthestFailure)
|
|
1503
|
+
return;
|
|
1504
|
+
if (this.parseStack.length > 0) {
|
|
1505
|
+
const parent = this.parseStack[this.parseStack.length - 1];
|
|
1506
|
+
if (!this.furthestFailure.tree.has(parent)) {
|
|
1507
|
+
this.furthestFailure.tree.set(parent, new Set());
|
|
1508
|
+
}
|
|
1509
|
+
this.furthestFailure.tree.get(parent).add(expected);
|
|
1510
|
+
}
|
|
1511
|
+
else {
|
|
1512
|
+
// No parent, this is a top-level failure
|
|
1513
|
+
if (!this.furthestFailure.tree.has('__ROOT__')) {
|
|
1514
|
+
this.furthestFailure.tree.set('__ROOT__', new Set());
|
|
1515
|
+
}
|
|
1516
|
+
this.furthestFailure.tree.get('__ROOT__').add(expected);
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
extractParentMostRules(tree) {
|
|
1520
|
+
// The "parent most failing option" is the rule we were trying to match
|
|
1521
|
+
// when all its alternatives failed. In the tree structure, this is typically
|
|
1522
|
+
// the direct child of the top-most parent that has alternatives.
|
|
1523
|
+
// Helper to get string name from value
|
|
1524
|
+
const getName = (value) => value instanceof RegExp ? value.source : value;
|
|
1525
|
+
// If we have __ROOT__, find its direct children that have alternatives
|
|
1526
|
+
if (tree.has('__ROOT__')) {
|
|
1527
|
+
const rootChildren = Array.from(tree.get('__ROOT__')).map(getName);
|
|
1528
|
+
// Return root children that have their own children (alternatives)
|
|
1529
|
+
const result = rootChildren.filter(child => tree.has(child) && tree.get(child).size > 0);
|
|
1530
|
+
if (result.length > 0) {
|
|
1531
|
+
return result;
|
|
1532
|
+
}
|
|
1533
|
+
// If none have children, return the root children themselves
|
|
1534
|
+
return rootChildren;
|
|
1535
|
+
}
|
|
1536
|
+
// Find the top-most parent (not a child of any other parent)
|
|
1537
|
+
const allChildren = new Set();
|
|
1538
|
+
const allParents = new Set();
|
|
1539
|
+
for (const [parent, children] of tree.entries()) {
|
|
1540
|
+
allParents.add(parent);
|
|
1541
|
+
for (const child of children) {
|
|
1542
|
+
allChildren.add(getName(child));
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
const topMostParents = Array.from(allParents).filter(parent => !allChildren.has(parent));
|
|
1546
|
+
// For each top-most parent, get its direct children that have alternatives
|
|
1547
|
+
const result = [];
|
|
1548
|
+
for (const parent of topMostParents) {
|
|
1549
|
+
if (tree.has(parent)) {
|
|
1550
|
+
const children = Array.from(tree.get(parent));
|
|
1551
|
+
for (const child of children) {
|
|
1552
|
+
const childName = getName(child);
|
|
1553
|
+
if (tree.has(childName) && tree.get(childName).size > 0) {
|
|
1554
|
+
result.push(childName);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
if (result.length > 0) {
|
|
1560
|
+
return result;
|
|
1561
|
+
}
|
|
1562
|
+
// Fallback: return top-most parents
|
|
1563
|
+
if (topMostParents.length > 0) {
|
|
1564
|
+
return topMostParents;
|
|
1565
|
+
}
|
|
1566
|
+
// Last fallback: return all unique rules
|
|
1567
|
+
return Array.from(new Set([...allParents, ...allChildren]));
|
|
1568
|
+
}
|
|
1569
|
+
/**
|
|
1570
|
+
* Determines if a value represents a terminal/literal rather than a non-terminal rule.
|
|
1571
|
+
*/
|
|
1572
|
+
isLiteralOrTerminal(value) {
|
|
1573
|
+
if (value instanceof RegExp) {
|
|
1574
|
+
return true;
|
|
1575
|
+
}
|
|
1576
|
+
// Check if it's a literal (starts with " or ')
|
|
1577
|
+
if (value.startsWith('"') || value.startsWith("'")) {
|
|
1578
|
+
return true;
|
|
1579
|
+
}
|
|
1580
|
+
// Check if it's a regex pattern (starts with [ or contains #x followed by hex digits)
|
|
1581
|
+
if (value.startsWith('[') || /\#x[0-9a-fA-F]/.test(value)) {
|
|
1582
|
+
return true;
|
|
1583
|
+
}
|
|
1584
|
+
return false;
|
|
1585
|
+
}
|
|
1586
|
+
/**
|
|
1587
|
+
* Extracts the expected value from a terminal/literal.
|
|
1588
|
+
* - For string literals (quoted), removes the quotes and returns the content
|
|
1589
|
+
* - For regex patterns, returns the RegExp instance
|
|
1590
|
+
*/
|
|
1591
|
+
extractExpectedValue(value, regexMap) {
|
|
1592
|
+
if (value instanceof RegExp) {
|
|
1593
|
+
return value;
|
|
1594
|
+
}
|
|
1595
|
+
// If it's a string literal (starts with " or '), parse it to remove quotes
|
|
1596
|
+
if (value.startsWith('"')) {
|
|
1597
|
+
try {
|
|
1598
|
+
return JSON.parse(value);
|
|
1599
|
+
}
|
|
1600
|
+
catch (_a) {
|
|
1601
|
+
return value;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
else if (value.startsWith("'")) {
|
|
1605
|
+
// Single-quoted string - remove quotes
|
|
1606
|
+
return value.replace(/^'(.+)'$/, '$1').replace(/\\'/g, "'");
|
|
1607
|
+
}
|
|
1608
|
+
// Check if this is a regex pattern string that we have a RegExp instance for
|
|
1609
|
+
if (regexMap.has(value)) {
|
|
1610
|
+
return regexMap.get(value);
|
|
1611
|
+
}
|
|
1612
|
+
// For other terminals, return as-is
|
|
1613
|
+
return value;
|
|
1614
|
+
}
|
|
1615
|
+
buildFailureTree(tree, startRules, regexMap) {
|
|
1616
|
+
const buildNode = (value) => {
|
|
1617
|
+
const name = value instanceof RegExp ? value.source : value;
|
|
1618
|
+
const node = { name };
|
|
1619
|
+
if (tree.has(name)) {
|
|
1620
|
+
const children = Array.from(tree.get(name));
|
|
1621
|
+
if (children.length > 0) {
|
|
1622
|
+
// If this rule has exactly one child and that child is a terminal,
|
|
1623
|
+
// set expected to that terminal instead of creating children
|
|
1624
|
+
if (children.length === 1 && this.isLiteralOrTerminal(children[0])) {
|
|
1625
|
+
node.expected = this.extractExpectedValue(children[0], regexMap || new Map());
|
|
1626
|
+
}
|
|
1627
|
+
else {
|
|
1628
|
+
// Build child nodes
|
|
1629
|
+
const childNodes = children.map(child => buildNode(child));
|
|
1630
|
+
// Check if this node should be flattened:
|
|
1631
|
+
// If it has exactly one child AND that child has an expected value (is terminal)
|
|
1632
|
+
if (childNodes.length === 1 && childNodes[0].expected !== undefined && childNodes[0].children === undefined) {
|
|
1633
|
+
node.expected = childNodes[0].expected;
|
|
1634
|
+
}
|
|
1635
|
+
else {
|
|
1636
|
+
node.children = childNodes;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
else if (this.isLiteralOrTerminal(value)) {
|
|
1642
|
+
// If this is a terminal/literal with no children, set expected to itself
|
|
1643
|
+
node.expected = this.extractExpectedValue(value, regexMap || new Map());
|
|
1644
|
+
}
|
|
1645
|
+
return node;
|
|
1646
|
+
};
|
|
1647
|
+
// If startRules are provided, start from those rules
|
|
1648
|
+
if (startRules && startRules.length > 0) {
|
|
1649
|
+
return startRules.map(rule => buildNode(rule));
|
|
1650
|
+
}
|
|
1651
|
+
// Start from root if it exists, otherwise from parent-most rules
|
|
1652
|
+
if (tree.has('__ROOT__')) {
|
|
1653
|
+
const rootChildren = Array.from(tree.get('__ROOT__'));
|
|
1654
|
+
return rootChildren.map(child => buildNode(child));
|
|
1655
|
+
}
|
|
1656
|
+
// Find parent-most rules (rules that are not children of other rules)
|
|
1657
|
+
const allChildren = new Set();
|
|
1658
|
+
for (const children of tree.values()) {
|
|
1659
|
+
for (const child of children) {
|
|
1660
|
+
const childName = child instanceof RegExp ? child.source : child;
|
|
1661
|
+
allChildren.add(childName);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
const parentMost = Array.from(tree.keys()).filter(parent => !allChildren.has(parent));
|
|
1665
|
+
return parentMost.map(parent => buildNode(parent));
|
|
1666
|
+
}
|
|
1667
|
+
parse(txt, target, recursion = 0, offset = 0) {
|
|
1668
|
+
let out = null;
|
|
1669
|
+
let type = parseRuleName(target);
|
|
1670
|
+
let expr;
|
|
1671
|
+
let printable = this.debug && /*!isLiteral &*/ !UPPER_SNAKE_RE.test(type.name);
|
|
1672
|
+
printable &&
|
|
1673
|
+
console.log(new Array(recursion).join('│ ') + 'Trying to get ' + target + ' from ' + JSON.stringify(txt.split('\n')[0]));
|
|
1674
|
+
let realType = type.name;
|
|
1675
|
+
let targetLex = findRuleByName(type.name, this);
|
|
1676
|
+
if (type.name == 'EOF') {
|
|
1677
|
+
if (txt.length) {
|
|
1678
|
+
this.recordFailure(offset, 'EOF');
|
|
1679
|
+
return null;
|
|
1680
|
+
}
|
|
1681
|
+
else if (txt.length == 0) {
|
|
1682
|
+
return {
|
|
1683
|
+
type: 'EOF',
|
|
1684
|
+
text: '',
|
|
1685
|
+
rest: '',
|
|
1686
|
+
start: 0,
|
|
1687
|
+
end: 0,
|
|
1688
|
+
fullText: '',
|
|
1689
|
+
errors: [],
|
|
1690
|
+
children: [],
|
|
1691
|
+
parent: null
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
try {
|
|
1696
|
+
if (!targetLex && type.isLiteral) {
|
|
1697
|
+
let src = type.name.trim();
|
|
1698
|
+
if (src.startsWith('"')) {
|
|
1699
|
+
src = JSON.parse(src);
|
|
1700
|
+
}
|
|
1701
|
+
else if (src.startsWith("'")) {
|
|
1702
|
+
src = src.replace(/^'(.+)'$/, '$1').replace(/\\'/g, "'");
|
|
1703
|
+
}
|
|
1704
|
+
if (src === '') {
|
|
1705
|
+
return {
|
|
1706
|
+
type: '%%EMPTY%%',
|
|
1707
|
+
text: '',
|
|
1708
|
+
rest: txt,
|
|
1709
|
+
start: 0,
|
|
1710
|
+
end: 0,
|
|
1711
|
+
fullText: '',
|
|
1712
|
+
errors: [],
|
|
1713
|
+
children: [],
|
|
1714
|
+
parent: null
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1717
|
+
expr = new RegExp(escapeRegExp(src));
|
|
1718
|
+
realType = null;
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
catch (e) {
|
|
1722
|
+
if (e instanceof ReferenceError) {
|
|
1723
|
+
console.error(e);
|
|
1724
|
+
}
|
|
1725
|
+
this.recordFailure(offset, target);
|
|
1726
|
+
return null;
|
|
1727
|
+
}
|
|
1728
|
+
if (expr) {
|
|
1729
|
+
let result = readToken(txt, expr);
|
|
1730
|
+
if (result) {
|
|
1731
|
+
result.type = realType;
|
|
1732
|
+
return result;
|
|
1733
|
+
}
|
|
1734
|
+
else {
|
|
1735
|
+
// Literal or regex match failed
|
|
1736
|
+
this.recordFailure(offset, type.isLiteral ? type.name : target);
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
else {
|
|
1740
|
+
let options = targetLex.bnf;
|
|
1741
|
+
if (options instanceof Array) {
|
|
1742
|
+
// Push this rule onto the parse stack
|
|
1743
|
+
this.parseStack.push(type.name);
|
|
1744
|
+
optionsLoop: for (const phases of options) {
|
|
1745
|
+
if (out)
|
|
1746
|
+
break;
|
|
1747
|
+
let pinned = null;
|
|
1748
|
+
let tmp = {
|
|
1749
|
+
type: type.name,
|
|
1750
|
+
text: '',
|
|
1751
|
+
children: [],
|
|
1752
|
+
end: 0,
|
|
1753
|
+
errors: [],
|
|
1754
|
+
fullText: '',
|
|
1755
|
+
parent: null,
|
|
1756
|
+
start: 0,
|
|
1757
|
+
rest: txt
|
|
1758
|
+
};
|
|
1759
|
+
if (targetLex.fragment)
|
|
1760
|
+
tmp.fragment = true;
|
|
1761
|
+
let tmpTxt = txt;
|
|
1762
|
+
let position = 0;
|
|
1763
|
+
let allOptional = phases.length > 0;
|
|
1764
|
+
let foundSomething = false;
|
|
1765
|
+
for (let i = 0; i < phases.length; i++) {
|
|
1766
|
+
if (typeof phases[i] == 'string') {
|
|
1767
|
+
let localTarget = parseRuleName(phases[i]);
|
|
1768
|
+
allOptional = allOptional && localTarget.isOptional;
|
|
1769
|
+
let got;
|
|
1770
|
+
let foundAtLeastOne = false;
|
|
1771
|
+
do {
|
|
1772
|
+
got = null;
|
|
1773
|
+
if (targetLex.implicitWs) {
|
|
1774
|
+
got = this.parse(tmpTxt, localTarget.name, recursion + 1, offset + position);
|
|
1775
|
+
if (!got) {
|
|
1776
|
+
let WS;
|
|
1777
|
+
do {
|
|
1778
|
+
WS = this.parse(tmpTxt, WS_RULE, recursion + 1, offset + position);
|
|
1779
|
+
if (WS) {
|
|
1780
|
+
tmp.text = tmp.text + WS.text;
|
|
1781
|
+
tmp.end = tmp.text.length;
|
|
1782
|
+
WS.parent = tmp;
|
|
1783
|
+
tmp.children.push(WS);
|
|
1784
|
+
tmpTxt = tmpTxt.substr(WS.text.length);
|
|
1785
|
+
position += WS.text.length;
|
|
1786
|
+
}
|
|
1787
|
+
else {
|
|
1788
|
+
break;
|
|
1789
|
+
}
|
|
1790
|
+
} while (WS && WS.text.length);
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
got = got || this.parse(tmpTxt, localTarget.name, recursion + 1, offset + position);
|
|
1794
|
+
// rule ::= "true" ![a-zA-Z]
|
|
1795
|
+
// negative lookup, if it does not match, we should continue
|
|
1796
|
+
if (localTarget.lookupNegative) {
|
|
1797
|
+
if (got)
|
|
1798
|
+
continue optionsLoop; /* cancel this path */
|
|
1799
|
+
break;
|
|
1800
|
+
}
|
|
1801
|
+
if (localTarget.lookupPositive) {
|
|
1802
|
+
if (!got)
|
|
1803
|
+
continue optionsLoop;
|
|
1804
|
+
}
|
|
1805
|
+
if (!got) {
|
|
1806
|
+
if (localTarget.isOptional)
|
|
1807
|
+
break;
|
|
1808
|
+
if (localTarget.atLeastOne && foundAtLeastOne)
|
|
1809
|
+
break;
|
|
1810
|
+
// Record this failure for error reporting
|
|
1811
|
+
this.recordFailure(offset + position, localTarget.name);
|
|
1812
|
+
}
|
|
1813
|
+
if (got && targetLex.pinned == i + 1) {
|
|
1814
|
+
pinned = got;
|
|
1815
|
+
printable && console.log(new Array(recursion + 1).join('│ ') + '└─ ' + got.type + ' PINNED');
|
|
1816
|
+
}
|
|
1817
|
+
if (!got)
|
|
1818
|
+
got = this.parseRecovery(targetLex, tmpTxt, recursion + 1, offset + position);
|
|
1819
|
+
if (!got) {
|
|
1820
|
+
if (pinned) {
|
|
1821
|
+
out = tmp;
|
|
1822
|
+
got = {
|
|
1823
|
+
type: 'SyntaxError',
|
|
1824
|
+
text: tmpTxt,
|
|
1825
|
+
children: [],
|
|
1826
|
+
end: tmpTxt.length,
|
|
1827
|
+
errors: [],
|
|
1828
|
+
fullText: '',
|
|
1829
|
+
parent: null,
|
|
1830
|
+
start: 0,
|
|
1831
|
+
rest: ''
|
|
1832
|
+
};
|
|
1833
|
+
if (tmpTxt.length) {
|
|
1834
|
+
new TokenError_1.TokenError(`Unexpected end of input. Expecting ${localTarget.name} Got: ${tmpTxt}`, got);
|
|
1835
|
+
}
|
|
1836
|
+
else {
|
|
1837
|
+
new TokenError_1.TokenError(`Unexpected end of input. Missing ${localTarget.name}`, got);
|
|
1838
|
+
}
|
|
1839
|
+
printable &&
|
|
1840
|
+
console.log(new Array(recursion + 1).join('│ ') + '└─ ' + got.type + ' ' + JSON.stringify(got.text));
|
|
1841
|
+
}
|
|
1842
|
+
else {
|
|
1843
|
+
continue optionsLoop;
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
foundAtLeastOne = true;
|
|
1847
|
+
foundSomething = true;
|
|
1848
|
+
if (got.type == '%%EMPTY%%') {
|
|
1849
|
+
break;
|
|
1850
|
+
}
|
|
1851
|
+
got.start += position;
|
|
1852
|
+
got.end += position;
|
|
1853
|
+
if (!localTarget.lookupPositive && got.type) {
|
|
1854
|
+
if (got.fragment) {
|
|
1855
|
+
if (got.children) {
|
|
1856
|
+
for (const x of got.children) {
|
|
1857
|
+
x.start += position;
|
|
1858
|
+
x.end += position;
|
|
1859
|
+
x.parent = tmp;
|
|
1860
|
+
tmp.children.push(x);
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
else {
|
|
1865
|
+
got.parent = tmp;
|
|
1866
|
+
tmp.children.push(got);
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
if (localTarget.lookup)
|
|
1870
|
+
got.lookup = true;
|
|
1871
|
+
printable &&
|
|
1872
|
+
console.log(new Array(recursion + 1).join('│ ') + '└─ ' + got.type + ' ' + JSON.stringify(got.text));
|
|
1873
|
+
// Eat it from the input stream, only if it is not a lookup
|
|
1874
|
+
if (!localTarget.lookup && !got.lookup) {
|
|
1875
|
+
tmp.text = tmp.text + got.text;
|
|
1876
|
+
tmp.end = tmp.text.length;
|
|
1877
|
+
tmpTxt = tmpTxt.substr(got.text.length);
|
|
1878
|
+
position += got.text.length;
|
|
1879
|
+
}
|
|
1880
|
+
tmp.rest = tmpTxt;
|
|
1881
|
+
} while (got && localTarget.allowRepetition && tmpTxt.length && !got.lookup);
|
|
1882
|
+
} /* IS A REGEXP */
|
|
1883
|
+
else {
|
|
1884
|
+
let got = readToken(tmpTxt, phases[i]);
|
|
1885
|
+
if (!got) {
|
|
1886
|
+
this.recordFailure(offset + position, phases[i]);
|
|
1887
|
+
continue optionsLoop;
|
|
1888
|
+
}
|
|
1889
|
+
printable &&
|
|
1890
|
+
console.log(new Array(recursion + 1).join('│ ') + '└> ' + JSON.stringify(got.text) + phases[i].source);
|
|
1891
|
+
foundSomething = true;
|
|
1892
|
+
got.start += position;
|
|
1893
|
+
got.end += position;
|
|
1894
|
+
tmp.text = tmp.text + got.text;
|
|
1895
|
+
tmp.end = tmp.text.length;
|
|
1896
|
+
tmpTxt = tmpTxt.substr(got.text.length);
|
|
1897
|
+
position += got.text.length;
|
|
1898
|
+
tmp.rest = tmpTxt;
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
if (foundSomething) {
|
|
1902
|
+
out = tmp;
|
|
1903
|
+
printable &&
|
|
1904
|
+
console.log(new Array(recursion).join('│ ') + '├<─┴< PUSHING ' + out.type + ' ' + JSON.stringify(out.text));
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
// Pop this rule from the parse stack
|
|
1908
|
+
this.parseStack.pop();
|
|
1909
|
+
}
|
|
1910
|
+
if (out && targetLex.simplifyWhenOneChildren && out.children.length == 1) {
|
|
1911
|
+
out = out.children[0];
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
if (!out) {
|
|
1915
|
+
printable && console.log(target + ' NOT RESOLVED FROM ' + txt);
|
|
1916
|
+
}
|
|
1917
|
+
return out;
|
|
1918
|
+
}
|
|
1919
|
+
parseRecovery(recoverableToken, tmpTxt, recursion, offset) {
|
|
1920
|
+
if (recoverableToken.recover && tmpTxt.length) {
|
|
1921
|
+
let printable = this.debug;
|
|
1922
|
+
printable &&
|
|
1923
|
+
console.log(new Array(recursion + 1).join('│ ') +
|
|
1924
|
+
'Trying to recover until token ' +
|
|
1925
|
+
recoverableToken.recover +
|
|
1926
|
+
' from ' +
|
|
1927
|
+
JSON.stringify(tmpTxt.split('\n')[0] + tmpTxt.split('\n')[1]));
|
|
1928
|
+
let tmp = {
|
|
1929
|
+
type: 'SyntaxError',
|
|
1930
|
+
text: '',
|
|
1931
|
+
children: [],
|
|
1932
|
+
end: 0,
|
|
1933
|
+
errors: [],
|
|
1934
|
+
fullText: '',
|
|
1935
|
+
parent: null,
|
|
1936
|
+
start: 0,
|
|
1937
|
+
rest: ''
|
|
1938
|
+
};
|
|
1939
|
+
let got;
|
|
1940
|
+
let currentOffset = offset;
|
|
1941
|
+
do {
|
|
1942
|
+
got = this.parse(tmpTxt, recoverableToken.recover, recursion + 1, currentOffset);
|
|
1943
|
+
if (got) {
|
|
1944
|
+
new TokenError_1.TokenError('Unexpected input: "' + tmp.text + `" Expecting: ${recoverableToken.name}`, tmp);
|
|
1945
|
+
break;
|
|
1946
|
+
}
|
|
1947
|
+
else {
|
|
1948
|
+
tmp.text = tmp.text + tmpTxt[0];
|
|
1949
|
+
tmp.end = tmp.text.length;
|
|
1950
|
+
tmpTxt = tmpTxt.substr(1);
|
|
1951
|
+
currentOffset++;
|
|
1952
|
+
}
|
|
1953
|
+
} while (!got && tmpTxt.length > 0);
|
|
1954
|
+
if (tmp.text.length > 0 && got) {
|
|
1955
|
+
printable && console.log(new Array(recursion + 1).join('│ ') + 'Recovered text: ' + JSON.stringify(tmp.text));
|
|
1956
|
+
return tmp;
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
return null;
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
exports.Parser = Parser;
|
|
1963
|
+
exports.default = Parser;
|
|
1964
|
+
|
|
1965
|
+
},{"./ParsingError":7,"./TokenError":9}],7:[function(require,module,exports){
|
|
1966
|
+
"use strict";
|
|
1967
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1968
|
+
exports.ParsingError = void 0;
|
|
1969
|
+
class ParsingError extends Error {
|
|
1970
|
+
constructor(message, position, expected, found, failureTree) {
|
|
1971
|
+
super(message);
|
|
1972
|
+
this.name = 'ParsingError';
|
|
1973
|
+
this.position = position;
|
|
1974
|
+
this.expected = expected;
|
|
1975
|
+
this.found = found;
|
|
1976
|
+
this.failureTree = failureTree;
|
|
1977
|
+
// Maintain proper prototype chain for instanceof checks
|
|
1978
|
+
Object.setPrototypeOf(this, ParsingError.prototype);
|
|
1979
|
+
}
|
|
1980
|
+
toString() {
|
|
1981
|
+
const { line, column, offset } = this.position;
|
|
1982
|
+
let msg = `${this.name}: ${this.message}\n`;
|
|
1983
|
+
msg += ` at line ${line}, column ${column} (offset ${offset})\n`;
|
|
1984
|
+
msg += ` Expected: ${this.expected.join(', ')}\n`;
|
|
1985
|
+
msg += ` Found: ${this.found}`;
|
|
1986
|
+
return msg;
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
exports.ParsingError = ParsingError;
|
|
1990
|
+
|
|
1991
|
+
},{}],8:[function(require,module,exports){
|
|
1992
|
+
"use strict";
|
|
1993
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1994
|
+
exports.findChildrenByType = findChildrenByType;
|
|
1995
|
+
/**
|
|
1996
|
+
* Finds all the direct childs of a specifyed type
|
|
1997
|
+
*/
|
|
1998
|
+
function findChildrenByType(token, type) {
|
|
1999
|
+
return token.children ? token.children.filter(x => x.type == type) : [];
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
},{}],9:[function(require,module,exports){
|
|
2003
|
+
"use strict";
|
|
2004
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2005
|
+
exports.TokenError = void 0;
|
|
2006
|
+
class TokenError extends Error {
|
|
2007
|
+
constructor(message, token) {
|
|
2008
|
+
super(message);
|
|
2009
|
+
this.message = message;
|
|
2010
|
+
this.token = token;
|
|
2011
|
+
if (token && token.errors)
|
|
2012
|
+
token.errors.push(this);
|
|
2013
|
+
else
|
|
2014
|
+
throw this;
|
|
2015
|
+
}
|
|
2016
|
+
inspect() {
|
|
2017
|
+
return 'SyntaxError: ' + this.message;
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
exports.TokenError = TokenError;
|
|
2021
|
+
|
|
2022
|
+
},{}],10:[function(require,module,exports){
|
|
2023
|
+
"use strict";
|
|
2024
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2025
|
+
exports.ParsingError = exports.TokenError = exports.Parser = void 0;
|
|
2026
|
+
var Parser_1 = require("./Parser");
|
|
2027
|
+
Object.defineProperty(exports, "Parser", { enumerable: true, get: function () { return Parser_1.Parser; } });
|
|
2028
|
+
var TokenError_1 = require("./TokenError");
|
|
2029
|
+
Object.defineProperty(exports, "TokenError", { enumerable: true, get: function () { return TokenError_1.TokenError; } });
|
|
2030
|
+
var ParsingError_1 = require("./ParsingError");
|
|
2031
|
+
Object.defineProperty(exports, "ParsingError", { enumerable: true, get: function () { return ParsingError_1.ParsingError; } });
|
|
2032
|
+
exports.Grammars = require("./Grammars");
|
|
2033
|
+
|
|
2034
|
+
},{"./Grammars":5,"./Parser":6,"./ParsingError":7,"./TokenError":9}],11:[function(require,module,exports){
|
|
2035
|
+
module.exports=[{"name":"TEMPLATE_BEGIN","bnf":[["\"${\""]]},{"name":"TEMPLATE_END","bnf":[["\"}\""]]},{"name":"PIPE","bnf":[["\"|\""]]},{"name":"%IDENT[2]","bnf":[[/[A-Za-z0-9_]/]]},{"name":"IDENT","bnf":[[/[A-Za-z_]/,"%IDENT[2]*"]]},{"name":"DOT","bnf":[["\".\""]]},{"name":"template_value","bnf":[["TEMPLATE_BEGIN","WS*","template_expr","WS*","TEMPLATE_END"]]},{"name":"%template_expr[2]","bnf":[["WS*","template_pipe","WS*","template_filter_call"]],"fragment":true},{"name":"template_expr","bnf":[["template_path","%template_expr[2]*"]]},{"name":"template_pipe","bnf":[["PIPE"]]},{"name":"%template_path[2]","bnf":[["WS*","DOT","WS*","IDENT"]],"fragment":true},{"name":"template_path","bnf":[["IDENT","%template_path[2]*"]]},{"name":"%template_filter_call[2]","bnf":[["WS*","BEGIN_ARGUMENT","WS*","template_filter_args?","WS*","END_ARGUMENT"]],"fragment":true},{"name":"template_filter_call","bnf":[["template_filter_name","%template_filter_call[2]?"]]},{"name":"template_filter_name","bnf":[["IDENT"]]},{"name":"%template_filter_args[2]","bnf":[["WS*","\",\"","WS*","template_filter_arg"]],"fragment":true},{"name":"template_filter_args","bnf":[["template_filter_arg","%template_filter_args[2]*"]]},{"name":"template_filter_arg","bnf":[["value"],["template_value"]]},{"name":"number_atom","bnf":[["number"],["template_value"]]},{"name":"number_time_atom","bnf":[["number_time"],["template_value"]]},{"name":"tod_atom","bnf":[["number_tod"],["template_value"]]},{"name":"dow_atom","bnf":[["dow"],["template_value"]]},{"name":"between_time_only_atom","bnf":[["between_time_only"],["template_value"]]},{"name":"between_tod_only_atom","bnf":[["between_tod_only"],["template_value"]]}]
|
|
2036
|
+
},{}],12:[function(require,module,exports){
|
|
2037
|
+
module.exports = require('./RuleTemplater.production.js');
|
|
2038
|
+
},{"./RuleTemplater.production.js":13}],13:[function(require,module,exports){
|
|
2039
|
+
// Note: We import the internal RuleParser.ebnf to extend the grammar with template rules.
|
|
2040
|
+
// This creates coupling to the internal structure of @halleyassist/rule-parser.
|
|
2041
|
+
const RuleParserRules = require('@halleyassist/rule-parser/src/RuleParser.ebnf'),
|
|
2042
|
+
TemplateGrammar = require('./RuleTemplate.production.ebnf.js'),
|
|
2043
|
+
TemplateFilters = require('./TemplateFilters'),
|
|
2044
|
+
{Parser} = require('ebnf');
|
|
2045
|
+
|
|
2046
|
+
let ParserCache = null;
|
|
2047
|
+
|
|
2048
|
+
const VariableTypes = [
|
|
2049
|
+
'string',
|
|
2050
|
+
'number',
|
|
2051
|
+
'boolean',
|
|
2052
|
+
'object',
|
|
2053
|
+
'time period',
|
|
2054
|
+
'time value',
|
|
2055
|
+
'string array',
|
|
2056
|
+
'number array',
|
|
2057
|
+
'boolean array',
|
|
2058
|
+
'object array'
|
|
2059
|
+
]
|
|
2060
|
+
|
|
2061
|
+
const AllowedTypeMapping = {
|
|
2062
|
+
'string': ['string_atom', 'string_concat'],
|
|
2063
|
+
'number': ['number_atom', 'math_expr'],
|
|
2064
|
+
'boolean': ['boolean_atom', 'boolean_expr'],
|
|
2065
|
+
'time period': ['time_period_atom'],
|
|
2066
|
+
'time value': ['time_value_atom', 'tod_atom'],
|
|
2067
|
+
'string array': ['string_array'],
|
|
2068
|
+
'number array': ['number_array'],
|
|
2069
|
+
'boolean array': ['boolean_array'],
|
|
2070
|
+
'object': ['object_atom'],
|
|
2071
|
+
'object array': ['object_array']
|
|
2072
|
+
};
|
|
2073
|
+
|
|
2074
|
+
// Merge the base grammar with template-specific grammar rules
|
|
2075
|
+
const extendedGrammar = [...RuleParserRules]
|
|
2076
|
+
for(const rule of TemplateGrammar){
|
|
2077
|
+
const idx = extendedGrammar.findIndex(r => r.name === rule.name);
|
|
2078
|
+
if(idx !== -1){
|
|
2079
|
+
extendedGrammar[idx] = rule;
|
|
2080
|
+
} else {
|
|
2081
|
+
extendedGrammar.push(rule);
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
// Add template_value as an alternative to value_atom so templates can be parsed
|
|
2086
|
+
const valueAtomIdx = extendedGrammar.findIndex(r => r.name === 'value_atom');
|
|
2087
|
+
if (valueAtomIdx !== -1) {
|
|
2088
|
+
extendedGrammar[valueAtomIdx].bnf.push(['template_value']);
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
// Export the parser rules for potential external use
|
|
2092
|
+
const ParserRules = extendedGrammar;
|
|
2093
|
+
|
|
2094
|
+
class RuleTemplate {
|
|
2095
|
+
constructor(ruleTemplateText, ast) {
|
|
2096
|
+
this.ruleTemplateText = ruleTemplateText;
|
|
2097
|
+
this.ast = ast;
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
/**
|
|
2101
|
+
* Parse a rule template string and return a RuleTemplate instance
|
|
2102
|
+
* @param {string} ruleTemplate - The template string to parse
|
|
2103
|
+
* @returns {RuleTemplate} Instance with AST and template text
|
|
2104
|
+
*/
|
|
2105
|
+
static parse(ruleTemplate){
|
|
2106
|
+
if(!ParserCache){
|
|
2107
|
+
ParserCache = new Parser(ParserRules, {debug: false})
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
const ast = ParserCache.getAST(ruleTemplate.trim(), 'statement_main');
|
|
2111
|
+
return new RuleTemplate(ruleTemplate, ast);
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
/**
|
|
2115
|
+
* Extract variables from the template using the AST
|
|
2116
|
+
* @returns {Array} Array of {name, filters: []} objects
|
|
2117
|
+
*/
|
|
2118
|
+
extractVariables(){
|
|
2119
|
+
const variables = [];
|
|
2120
|
+
const seen = new Set();
|
|
2121
|
+
|
|
2122
|
+
const traverse = (node) => {
|
|
2123
|
+
if (!node) return;
|
|
2124
|
+
|
|
2125
|
+
// Check if this is a template_value node
|
|
2126
|
+
if (node.type === 'template_value') {
|
|
2127
|
+
// Extract the variable information
|
|
2128
|
+
const varInfo = this._extractVariableFromNode(node);
|
|
2129
|
+
if (varInfo && !seen.has(varInfo.name)) {
|
|
2130
|
+
seen.add(varInfo.name);
|
|
2131
|
+
variables.push(varInfo);
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
// Traverse children
|
|
2136
|
+
if (node.children) {
|
|
2137
|
+
for (const child of node.children) {
|
|
2138
|
+
traverse(child);
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
};
|
|
2142
|
+
|
|
2143
|
+
traverse(this.ast);
|
|
2144
|
+
return variables;
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
/**
|
|
2148
|
+
* Extract variable name and filters from a template_value AST node
|
|
2149
|
+
* @private
|
|
2150
|
+
*/
|
|
2151
|
+
_extractVariableFromNode(node) {
|
|
2152
|
+
if (node.type !== 'template_value') return null;
|
|
2153
|
+
|
|
2154
|
+
// Find the template_expr child
|
|
2155
|
+
const templateExpr = node.children?.find(c => c.type === 'template_expr');
|
|
2156
|
+
if (!templateExpr) return null;
|
|
2157
|
+
|
|
2158
|
+
// Extract the path (variable name)
|
|
2159
|
+
const templatePath = templateExpr.children?.find(c => c.type === 'template_path');
|
|
2160
|
+
if (!templatePath || !templatePath.text) return null;
|
|
2161
|
+
|
|
2162
|
+
const name = templatePath.text.trim();
|
|
2163
|
+
|
|
2164
|
+
// Extract filters
|
|
2165
|
+
const filters = [];
|
|
2166
|
+
for (const child of templateExpr.children || []) {
|
|
2167
|
+
if (child.type === 'template_filter_call') {
|
|
2168
|
+
const filterName = this._extractFilterName(child);
|
|
2169
|
+
if (filterName) {
|
|
2170
|
+
filters.push(filterName);
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
return { name, filters };
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
/**
|
|
2179
|
+
* Extract filter name from template_filter_call node
|
|
2180
|
+
* @private
|
|
2181
|
+
*/
|
|
2182
|
+
_extractFilterName(node) {
|
|
2183
|
+
const filterNameNode = node.children?.find(c => c.type === 'template_filter_name');
|
|
2184
|
+
if (!filterNameNode || !filterNameNode.text) return null;
|
|
2185
|
+
|
|
2186
|
+
return filterNameNode.text.trim();
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
/**
|
|
2190
|
+
* Validate variable types against the AST
|
|
2191
|
+
* @param {Object} variables - Object mapping variable names to {type} objects
|
|
2192
|
+
* @returns {Object} Object with validation results: {valid: boolean, errors: []}
|
|
2193
|
+
*/
|
|
2194
|
+
validate(variables) {
|
|
2195
|
+
if (!variables || typeof variables !== 'object') {
|
|
2196
|
+
return {
|
|
2197
|
+
valid: false,
|
|
2198
|
+
errors: ['Variables must be provided as an object']
|
|
2199
|
+
};
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
const errors = [];
|
|
2203
|
+
const extractedVars = this.extractVariables();
|
|
2204
|
+
|
|
2205
|
+
for (const varInfo of extractedVars) {
|
|
2206
|
+
const varName = varInfo.name;
|
|
2207
|
+
|
|
2208
|
+
// Check if variable is provided
|
|
2209
|
+
if (!variables.hasOwnProperty(varName)) {
|
|
2210
|
+
errors.push(`Variable '${varName}' not provided in variables object`);
|
|
2211
|
+
continue;
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
const varData = variables[varName];
|
|
2215
|
+
if (typeof varData !== 'object') {
|
|
2216
|
+
errors.push(`Variable '${varName}' must be an object`);
|
|
2217
|
+
continue;
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
const { type } = varData;
|
|
2221
|
+
|
|
2222
|
+
// Validate type if provided
|
|
2223
|
+
if (type && !VariableTypes.includes(type)) {
|
|
2224
|
+
errors.push(`Invalid variable type '${type}' for variable '${varName}'`);
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
return {
|
|
2229
|
+
valid: errors.length === 0,
|
|
2230
|
+
errors
|
|
2231
|
+
};
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
/**
|
|
2235
|
+
* Prepare the template by replacing variables with their values
|
|
2236
|
+
* Rebuilds from AST by iterating through children
|
|
2237
|
+
* @param {Object} variables - Object mapping variable names to {value, type} objects
|
|
2238
|
+
* @returns {string} The prepared rule string
|
|
2239
|
+
*/
|
|
2240
|
+
prepare(variables){
|
|
2241
|
+
if (!variables || typeof variables !== 'object') {
|
|
2242
|
+
throw new Error('Variables must be provided as an object');
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
// Rebuild the rule string from AST
|
|
2246
|
+
return this._rebuildFromAST(this.ast, variables);
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
/**
|
|
2250
|
+
* Rebuild rule string from AST node, replacing template_value nodes with variable values
|
|
2251
|
+
* @private
|
|
2252
|
+
* @param {Object} node - AST node
|
|
2253
|
+
* @param {Object} variables - Object mapping variable names to {value, type} objects
|
|
2254
|
+
* @returns {string} Rebuilt string
|
|
2255
|
+
*/
|
|
2256
|
+
_rebuildFromAST(node, variables) {
|
|
2257
|
+
if (!node) return '';
|
|
2258
|
+
|
|
2259
|
+
// If this is a template_value node, replace it with the computed value
|
|
2260
|
+
if (node.type === 'template_value') {
|
|
2261
|
+
return this._computeTemplateReplacement(node, variables);
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
// If node has no children, it's a leaf - return its text
|
|
2265
|
+
if (!node.children || node.children.length === 0) {
|
|
2266
|
+
return node.text || '';
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
// Node has children - rebuild by iterating through children and preserving gaps
|
|
2270
|
+
let result = '';
|
|
2271
|
+
const originalText = node.text || '';
|
|
2272
|
+
let lastEnd = node.start || 0;
|
|
2273
|
+
|
|
2274
|
+
for (const child of node.children) {
|
|
2275
|
+
// Add any text between the last child's end and this child's start (gaps/syntax)
|
|
2276
|
+
if (child.start !== undefined && child.start > lastEnd) {
|
|
2277
|
+
result += originalText.substring(lastEnd - (node.start || 0), child.start - (node.start || 0));
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
// Add the child's rebuilt text
|
|
2281
|
+
result += this._rebuildFromAST(child, variables);
|
|
2282
|
+
|
|
2283
|
+
// Update lastEnd to this child's end position
|
|
2284
|
+
if (child.end !== undefined) {
|
|
2285
|
+
lastEnd = child.end;
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
// Add any remaining text after the last child
|
|
2290
|
+
if (node.end !== undefined && lastEnd < node.end) {
|
|
2291
|
+
result += originalText.substring(lastEnd - (node.start || 0), node.end - (node.start || 0));
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
return result;
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
/**
|
|
2298
|
+
* Compute the replacement value for a template_value node
|
|
2299
|
+
* @private
|
|
2300
|
+
* @param {Object} node - template_value AST node
|
|
2301
|
+
* @param {Object} variables - Object mapping variable names to {value, type} objects
|
|
2302
|
+
* @returns {string} Replacement string
|
|
2303
|
+
*/
|
|
2304
|
+
_computeTemplateReplacement(node, variables) {
|
|
2305
|
+
const templateInfo = this._extractVariableFromNode(node);
|
|
2306
|
+
if (!templateInfo) {
|
|
2307
|
+
throw new Error(`Failed to extract variable information from template node`);
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
const varName = templateInfo.name;
|
|
2311
|
+
|
|
2312
|
+
// Validate variable is provided
|
|
2313
|
+
if (!variables.hasOwnProperty(varName)) {
|
|
2314
|
+
throw new Error(`Variable '${varName}' not provided in variables object`);
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
const varData = variables[varName];
|
|
2318
|
+
if (typeof varData !== 'object' || !varData.hasOwnProperty('value')) {
|
|
2319
|
+
throw new Error(`Variable '${varName}' must be an object with 'value' property`);
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
let { value, type } = varData;
|
|
2323
|
+
|
|
2324
|
+
// Require type property for all variables
|
|
2325
|
+
if (!varData.hasOwnProperty('type')) {
|
|
2326
|
+
throw new Error(`Variable '${varName}' must have a 'type' property`);
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
// Validate type
|
|
2330
|
+
if (!VariableTypes.includes(type)) {
|
|
2331
|
+
throw new Error(`Invalid variable type '${type}' for variable '${varName}'`);
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
// Apply filters if present
|
|
2335
|
+
if (templateInfo.filters && templateInfo.filters.length > 0) {
|
|
2336
|
+
for (const filterName of templateInfo.filters) {
|
|
2337
|
+
if (!TemplateFilters[filterName]) {
|
|
2338
|
+
throw new Error(`Unknown filter '${filterName}'`);
|
|
2339
|
+
}
|
|
2340
|
+
value = TemplateFilters[filterName](value);
|
|
2341
|
+
}
|
|
2342
|
+
// After applying filters, the result is already a string representation
|
|
2343
|
+
return String(value);
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
// Convert value to string representation based on type
|
|
2347
|
+
if (type === 'string') {
|
|
2348
|
+
// Escape backslashes first, then quotes in string values.
|
|
2349
|
+
// Order is critical: escaping backslashes first prevents double-escaping.
|
|
2350
|
+
// E.g., "test\" becomes "test\\" then "test\\\"" (correct)
|
|
2351
|
+
// If reversed, "test\" would become "test\\"" then "test\\\\"" (incorrect)
|
|
2352
|
+
return `"${String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
2353
|
+
} else if (type === 'number') {
|
|
2354
|
+
return String(value);
|
|
2355
|
+
} else if (type === 'boolean') {
|
|
2356
|
+
return value ? 'true' : 'false';
|
|
2357
|
+
} else {
|
|
2358
|
+
// Default behavior - just insert the value as-is
|
|
2359
|
+
return String(value);
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
/**
|
|
2364
|
+
* Helper method to validate if an AST node matches a variable type
|
|
2365
|
+
* @param {Object} astNode - The AST node to validate
|
|
2366
|
+
* @param {string} variableType - The expected variable type
|
|
2367
|
+
* @returns {boolean} True if valid, false otherwise
|
|
2368
|
+
*/
|
|
2369
|
+
static validateVariableNode(astNode, variableType) {
|
|
2370
|
+
if (!astNode || !astNode.type) {
|
|
2371
|
+
return false;
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
const allowedTypes = AllowedTypeMapping[variableType];
|
|
2375
|
+
if (!allowedTypes) {
|
|
2376
|
+
return false;
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
return allowedTypes.includes(astNode.type);
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
// Export the class and parser rules
|
|
2384
|
+
module.exports = RuleTemplate;
|
|
2385
|
+
module.exports.ParserRules = ParserRules;
|
|
2386
|
+
module.exports.VariableTypes = VariableTypes;
|
|
2387
|
+
module.exports.TemplateFilters = TemplateFilters;
|
|
2388
|
+
},{"./RuleTemplate.production.ebnf.js":11,"./TemplateFilters":14,"@halleyassist/rule-parser/src/RuleParser.ebnf":1,"ebnf":10}],14:[function(require,module,exports){
|
|
2389
|
+
/*
|
|
2390
|
+
Template filters are functions that transform variable values.
|
|
2391
|
+
They are applied in the template syntax as ${variable|filter} or ${variable|filter1|filter2}
|
|
2392
|
+
*/
|
|
2393
|
+
const TemplateFilters = {
|
|
2394
|
+
// Convert value to JSON string representation
|
|
2395
|
+
string: value => JSON.stringify(String(value)),
|
|
2396
|
+
|
|
2397
|
+
// Convert to uppercase
|
|
2398
|
+
upper: value => String(value).toUpperCase(),
|
|
2399
|
+
|
|
2400
|
+
// Convert to lowercase
|
|
2401
|
+
lower: value => String(value).toLowerCase(),
|
|
2402
|
+
|
|
2403
|
+
// Capitalize first letter
|
|
2404
|
+
capitalize: value => {
|
|
2405
|
+
const str = String(value);
|
|
2406
|
+
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
2407
|
+
},
|
|
2408
|
+
|
|
2409
|
+
// Convert to title case
|
|
2410
|
+
title: value => {
|
|
2411
|
+
return String(value).split(' ')
|
|
2412
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
2413
|
+
.join(' ');
|
|
2414
|
+
},
|
|
2415
|
+
|
|
2416
|
+
// Trim whitespace
|
|
2417
|
+
trim: value => String(value).trim(),
|
|
2418
|
+
|
|
2419
|
+
// Convert to number
|
|
2420
|
+
number: value => Number(value),
|
|
2421
|
+
|
|
2422
|
+
// Convert to boolean
|
|
2423
|
+
boolean: value => {
|
|
2424
|
+
if (typeof value === 'boolean') return value;
|
|
2425
|
+
if (typeof value === 'string') {
|
|
2426
|
+
const lower = value.toLowerCase();
|
|
2427
|
+
if (lower === 'true' || lower === '1' || lower === 'yes') return true;
|
|
2428
|
+
if (lower === 'false' || lower === '0' || lower === 'no') return false;
|
|
2429
|
+
}
|
|
2430
|
+
return Boolean(value);
|
|
2431
|
+
},
|
|
2432
|
+
|
|
2433
|
+
// Convert to absolute value (for numbers)
|
|
2434
|
+
abs: value => Math.abs(Number(value)),
|
|
2435
|
+
|
|
2436
|
+
// Round number
|
|
2437
|
+
round: value => Math.round(Number(value)),
|
|
2438
|
+
|
|
2439
|
+
// Floor number
|
|
2440
|
+
floor: value => Math.floor(Number(value)),
|
|
2441
|
+
|
|
2442
|
+
// Ceil number
|
|
2443
|
+
ceil: value => Math.ceil(Number(value)),
|
|
2444
|
+
|
|
2445
|
+
// Default value if empty/null/undefined
|
|
2446
|
+
default: (value, defaultValue = '') => {
|
|
2447
|
+
return (value === null || value === undefined || value === '') ? defaultValue : value;
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
module.exports = TemplateFilters;
|
|
2452
|
+
},{}]},{},[12])(12)
|
|
2453
|
+
});
|