@halleyassist/rule-templater 0.0.17 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -3,9 +3,14 @@ export interface VariablePosition {
3
3
  end: number;
4
4
  }
5
5
 
6
+ export interface TemplateFilterCall {
7
+ name: string;
8
+ args: any[];
9
+ }
10
+
6
11
  export interface VariableInfo {
7
12
  name: string;
8
- filters: string[];
13
+ filters: Array<string | TemplateFilterCall>;
9
14
  positions: VariablePosition[];
10
15
  }
11
16
 
@@ -134,6 +139,8 @@ export class GeneralTemplate {
134
139
 
135
140
  extractVariables(): VariableInfo[];
136
141
 
142
+ validate(): ValidationResult;
143
+
137
144
  prepare(variables: Variables): string;
138
145
  }
139
146
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@halleyassist/rule-templater",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "The grammar for HalleyAssist rules",
5
5
  "main": "src/RuleTemplate.production.js",
6
6
  "browser": "./dist/rule-templater.browser.js",
@@ -1,5 +1,7 @@
1
1
  const TemplateFilters = require('./TemplateFilters');
2
2
 
3
+ const FILTER_PATTERN = /([A-Za-z_][A-Za-z0-9_]*)(?:\((.*)\))?$/;
4
+
3
5
  class GeneralTemplate {
4
6
  constructor(templateText) {
5
7
  this.templateText = templateText;
@@ -52,6 +54,33 @@ class GeneralTemplate {
52
54
  return this.getVariables();
53
55
  }
54
56
 
57
+ validate() {
58
+ const errors = [];
59
+ const warnings = [];
60
+ const seenFilterErrors = new Set();
61
+
62
+ for (const variableInfo of this.getVariables()) {
63
+ for (const filter of variableInfo.filters || []) {
64
+ const filterName = typeof filter === 'string' ? filter : filter?.name;
65
+ if (filterName && TemplateFilters[filterName]) {
66
+ continue;
67
+ }
68
+
69
+ const errorMessage = `Unknown filter '${filterName || filter}' for variable '${variableInfo.name}'`;
70
+ if (!seenFilterErrors.has(errorMessage)) {
71
+ errors.push(errorMessage);
72
+ seenFilterErrors.add(errorMessage);
73
+ }
74
+ }
75
+ }
76
+
77
+ return {
78
+ valid: errors.length === 0,
79
+ errors,
80
+ warnings
81
+ };
82
+ }
83
+
55
84
  prepare(variables) {
56
85
  if (!variables || typeof variables !== 'object') {
57
86
  throw new Error('Variables must be provided as an object');
@@ -76,12 +105,15 @@ class GeneralTemplate {
76
105
  varData = Object.assign({}, varData);
77
106
 
78
107
  if (parsedExpression.filters && parsedExpression.filters.length > 0) {
79
- for (const filterName of parsedExpression.filters) {
80
- if (!TemplateFilters[filterName]) {
81
- throw new Error(`Unknown filter '${filterName}'`);
108
+ for (const filter of parsedExpression.filters) {
109
+ const filterName = typeof filter === 'string' ? filter : filter?.name;
110
+ const filterArgs = typeof filter === 'string' ? [] : (Array.isArray(filter?.args) ? filter.args : []);
111
+
112
+ if (!filterName || !TemplateFilters[filterName]) {
113
+ throw new Error(`Unknown filter '${filterName || filter}'`);
82
114
  }
83
115
 
84
- TemplateFilters[filterName](varData);
116
+ TemplateFilters[filterName](varData, ...filterArgs);
85
117
  }
86
118
  }
87
119
 
@@ -101,21 +133,69 @@ class GeneralTemplate {
101
133
 
102
134
  return {
103
135
  name: segments[0],
104
- filters: segments.slice(1)
136
+ filters: segments.slice(1).map(segment => this._parseFilter(segment))
137
+ };
138
+ }
139
+
140
+ _parseFilter(segment) {
141
+ const match = segment.match(FILTER_PATTERN);
142
+ if (!match) {
143
+ return {
144
+ name: segment,
145
+ args: []
146
+ };
147
+ }
148
+
149
+ const [, name, rawArgs] = match;
150
+ return {
151
+ name,
152
+ args: this._parseFilterArgs(rawArgs)
105
153
  };
106
154
  }
107
155
 
156
+ _parseFilterArgs(rawArgs) {
157
+ if (!rawArgs || !rawArgs.trim()) {
158
+ return [];
159
+ }
160
+
161
+ return rawArgs.split(',').map(arg => this._parseFilterArgValue(arg.trim()));
162
+ }
163
+
164
+ _parseFilterArgValue(rawArg) {
165
+ if ((rawArg.startsWith('"') && rawArg.endsWith('"')) || (rawArg.startsWith("'") && rawArg.endsWith("'"))) {
166
+ return rawArg.slice(1, -1);
167
+ }
168
+
169
+ if (rawArg === 'true') {
170
+ return true;
171
+ }
172
+
173
+ if (rawArg === 'false') {
174
+ return false;
175
+ }
176
+
177
+ if (rawArg === 'null') {
178
+ return null;
179
+ }
180
+
181
+ if (rawArg !== '' && !Number.isNaN(Number(rawArg))) {
182
+ return Number(rawArg);
183
+ }
184
+
185
+ return rawArg;
186
+ }
187
+
108
188
  _serializeVariable(varData) {
109
189
  if (varData.value === null || varData.value === undefined) {
110
190
  return '';
111
191
  }
112
192
 
113
- if (varData.type === 'time period' || varData.type === 'time period ago') {
114
- let ret = `${varData.value.from} TO ${varData.value.to}`;
115
- if (varData.value.ago) {
116
- ret += ` AGO ${varData.value.ago[0]} ${varData.value.ago[1]}`;
117
- }
118
- return ret;
193
+ if (varData.type === 'time period') {
194
+ return `BETWEEN ${varData.value.from} AND ${varData.value.to}`;
195
+ }
196
+
197
+ if (varData.type === 'time period ago') {
198
+ return `${varData.value.ago[0]} ${varData.value.ago[1]} AGO BETWEEN ${varData.value.from} AND ${varData.value.to}`;
119
199
  }
120
200
 
121
201
  return String(varData.value);
@@ -23,18 +23,9 @@ const grammar = `
23
23
 
24
24
  template_filter_arg ::= value | template_value
25
25
 
26
- number_atom ::= number | template_value
27
- number_time_atom ::= number_time | template_value WS+ unit | template_value
28
- tod_atom ::= number_tod | template_value
29
- dow_atom ::= dow | template_value
30
- between_time_only_atom ::= between_time_only | template_value
31
- between_tod_only_atom ::= between_tod_only | template_value
32
-
33
26
  string_atom ::= string
34
27
  boolean_atom ::= false | true
35
28
  time_value_atom ::= number_tod
36
- time_period_atom ::= time_value_atom WS* "TO" WS* time_value_atom
37
- time_period_ago_atom ::= time_value_atom WS* "TO" WS* time_value_atom WS+ AGO WS+ number WS+ unit
38
29
 
39
30
  object_atom ::= json_object
40
31
  json_value ::= string | number | false | true | null | json_array | json_object
@@ -28,7 +28,7 @@ const AllowedTypeMapping = {
28
28
  'number': ['number_atom', 'math_expr'],
29
29
  'boolean': ['boolean_atom', 'boolean_expr'],
30
30
  'time period': ['time_period_atom'],
31
- 'time period ago': ['time_period_atom'],
31
+ 'time period ago': ['time_period_ago_atom'],
32
32
  'time value': ['time_value_atom', 'tod_atom'],
33
33
  'number time': ['number_atom'],
34
34
  'string array': ['string_array'],
@@ -49,12 +49,71 @@ for(const rule of TemplateGrammar){
49
49
  }
50
50
  }
51
51
 
52
- // Add template_value as an alternative to value_atom so templates can be parsed
53
- const valueAtomIdx = extendedGrammar.findIndex(r => r.name === 'value_atom');
54
- if (valueAtomIdx !== -1) {
55
- extendedGrammar[valueAtomIdx] = Object.assign({}, extendedGrammar[valueAtomIdx]);
56
- extendedGrammar[valueAtomIdx].bnf = extendedGrammar[valueAtomIdx].bnf.concat([['template_value']]);
57
- }
52
+ const cloneRule = (ruleName) => {
53
+ const idx = extendedGrammar.findIndex(rule => rule.name === ruleName);
54
+ if (idx === -1) {
55
+ return null;
56
+ }
57
+
58
+ extendedGrammar[idx] = Object.assign({}, extendedGrammar[idx], {
59
+ bnf: extendedGrammar[idx].bnf.map(alt => Array.isArray(alt) ? alt.slice() : alt)
60
+ });
61
+
62
+ return extendedGrammar[idx];
63
+ };
64
+
65
+ const appendAlternative = (ruleName, alternative) => {
66
+ const rule = cloneRule(ruleName);
67
+ if (!rule) {
68
+ return;
69
+ }
70
+
71
+ const exists = rule.bnf.some(existing => JSON.stringify(existing) === JSON.stringify(alternative));
72
+ if (!exists) {
73
+ rule.bnf.push(alternative);
74
+ }
75
+ };
76
+
77
+ const replaceRule = (ruleName, bnf) => {
78
+ const idx = extendedGrammar.findIndex(rule => rule.name === ruleName);
79
+ if (idx === -1) {
80
+ extendedGrammar.push({name: ruleName, bnf});
81
+ return;
82
+ }
83
+
84
+ extendedGrammar[idx] = Object.assign({}, extendedGrammar[idx], {
85
+ bnf: bnf.map(alt => alt.slice())
86
+ });
87
+ };
88
+
89
+ appendAlternative('number_atom', ['template_value']);
90
+ appendAlternative('number_time_atom', ['template_value', 'WS+', 'unit']);
91
+ appendAlternative('number_time_atom', ['template_value']);
92
+ appendAlternative('tod_atom', ['template_value']);
93
+ appendAlternative('dow_atom', ['template_value']);
94
+ appendAlternative('between_time_only_atom', ['template_value']);
95
+ appendAlternative('between_tod_only_atom', ['template_value']);
96
+ appendAlternative('string_atom', ['template_value']);
97
+ appendAlternative('boolean_atom', ['template_value']);
98
+ appendAlternative('time_value_atom', ['template_value']);
99
+ appendAlternative('time_period_atom', ['template_value']);
100
+ appendAlternative('time_period_ago_atom', ['template_value']);
101
+ appendAlternative('object_atom', ['template_value']);
102
+ appendAlternative('string_array', ['template_value']);
103
+ appendAlternative('number_array', ['template_value']);
104
+ appendAlternative('boolean_array', ['template_value']);
105
+ appendAlternative('object_array', ['template_value']);
106
+
107
+ replaceRule('argument', [
108
+ ['number_time_atom', 'WS*'],
109
+ ['statement', 'WS*']
110
+ ]);
111
+
112
+ replaceRule('simple_result', [
113
+ ['fcall'],
114
+ ['number_time_atom'],
115
+ ['value']
116
+ ]);
58
117
 
59
118
  // Export the parser rules for potential external use
60
119
  const ParserRules = extendedGrammar;
@@ -185,11 +244,13 @@ class RuleTemplate {
185
244
 
186
245
  // Extract filters
187
246
  const filters = [];
247
+ const filterCalls = [];
188
248
  for (const child of templateExpr.children || []) {
189
249
  if (child.type === 'template_filter_call') {
190
- const filterName = this._extractFilterName(child);
191
- if (filterName) {
192
- filters.push(filterName);
250
+ const filterCall = this._extractFilterCall(child);
251
+ if (filterCall) {
252
+ filters.push(filterCall.name);
253
+ filterCalls.push(filterCall);
193
254
  }
194
255
  }
195
256
  }
@@ -198,18 +259,84 @@ class RuleTemplate {
198
259
  const start = node.start;
199
260
  const end = node.end;
200
261
 
201
- return { name, filters, start, end };
262
+ return { name, filters, filterCalls, start, end };
202
263
  }
203
264
 
204
265
  /**
205
- * Extract filter name from template_filter_call node
266
+ * Extract filter call from template_filter_call node
206
267
  * @private
207
268
  */
208
- _extractFilterName(node) {
269
+ _extractFilterCall(node) {
209
270
  const filterNameNode = node.children?.find(c => c.type === 'template_filter_name');
210
271
  if (!filterNameNode || !filterNameNode.text) return null;
211
-
212
- return filterNameNode.text.trim();
272
+
273
+ const argsNode = node.children?.find(c => c.type === 'template_filter_args');
274
+
275
+ return {
276
+ name: filterNameNode.text.trim(),
277
+ args: this._extractFilterArgs(argsNode)
278
+ };
279
+ }
280
+
281
+ _extractFilterArgs(node) {
282
+ if (!node || !Array.isArray(node.children)) {
283
+ return [];
284
+ }
285
+
286
+ return node.children
287
+ .filter(child => child.type === 'template_filter_arg')
288
+ .map(child => this._extractFilterArgValue(child));
289
+ }
290
+
291
+ _extractFilterArgValue(node) {
292
+ if (!node || !Array.isArray(node.children) || node.children.length === 0) {
293
+ return this._normalizeFilterArgText(node?.text?.trim() || '');
294
+ }
295
+
296
+ const child = node.children[0];
297
+ if (!child) {
298
+ return this._normalizeFilterArgText(node.text?.trim() || '');
299
+ }
300
+
301
+ if (child.type === 'value' && Array.isArray(child.children) && child.children.length > 0) {
302
+ return this._extractFilterArgValue(child);
303
+ }
304
+
305
+ if (child.type === 'string') {
306
+ try {
307
+ return JSON.parse(child.text);
308
+ } catch (error) {
309
+ return this._normalizeFilterArgText(child.text);
310
+ }
311
+ }
312
+
313
+ if (child.type === 'number') {
314
+ return Number(child.text);
315
+ }
316
+
317
+ if (child.type === 'true') {
318
+ return true;
319
+ }
320
+
321
+ if (child.type === 'false') {
322
+ return false;
323
+ }
324
+
325
+ if (child.type === 'null') {
326
+ return null;
327
+ }
328
+
329
+ return this._normalizeFilterArgText(child.text?.trim() || node.text?.trim() || '');
330
+ }
331
+
332
+ _normalizeFilterArgText(text) {
333
+ const normalizedText = String(text).trim();
334
+
335
+ if ((normalizedText.startsWith('"') && normalizedText.endsWith('"')) || (normalizedText.startsWith("'") && normalizedText.endsWith("'"))) {
336
+ return normalizedText.slice(1, -1);
337
+ }
338
+
339
+ return normalizedText;
213
340
  }
214
341
 
215
342
  /**
@@ -229,10 +356,31 @@ class RuleTemplate {
229
356
 
230
357
  const errors = [];
231
358
  const warnings = [];
232
- const extractedVars = this.extractVariables();
359
+ const extractedVars = this._extractTemplateVariables();
360
+ const seenVariables = new Set();
361
+ const seenFilterErrors = new Set();
233
362
 
234
363
  for (const varInfo of extractedVars) {
235
364
  const varName = varInfo.name;
365
+
366
+ for (const filter of (varInfo.filterCalls || varInfo.filters || [])) {
367
+ const filterName = typeof filter === 'string' ? filter : filter?.name;
368
+ if (filterName && TemplateFilters[filterName]) {
369
+ continue;
370
+ }
371
+
372
+ const errorMessage = `Unknown filter '${filterName || filter}' for variable '${varName}'`;
373
+ if (!seenFilterErrors.has(errorMessage)) {
374
+ errors.push(errorMessage);
375
+ seenFilterErrors.add(errorMessage);
376
+ }
377
+ }
378
+
379
+ if (seenVariables.has(varName)) {
380
+ continue;
381
+ }
382
+
383
+ seenVariables.add(varName);
236
384
 
237
385
  // Check if variable is provided
238
386
  if (!variables.hasOwnProperty(varName)) {
@@ -262,6 +410,23 @@ class RuleTemplate {
262
410
  }
263
411
  }
264
412
 
413
+ const canValidatePreparedRule = errors.length === 0
414
+ && Array.from(seenVariables).every(varName => {
415
+ const varData = variables[varName];
416
+ return varData
417
+ && typeof varData === 'object'
418
+ && Object.prototype.hasOwnProperty.call(varData, 'type')
419
+ && Object.prototype.hasOwnProperty.call(varData, 'value');
420
+ });
421
+
422
+ if (canValidatePreparedRule) {
423
+ try {
424
+ RuleParser.toAst(this.prepare(variables));
425
+ } catch (error) {
426
+ errors.push(`Prepared rule is invalid: ${error.message}`);
427
+ }
428
+ }
429
+
265
430
  if (functionBlob && typeof functionBlob.validate === 'function') {
266
431
  for (const functionCall of this._extractFunctionCalls()) {
267
432
  warnings.push(...functionBlob.validate(functionCall.name, functionCall.arguments));
@@ -305,6 +470,30 @@ class RuleTemplate {
305
470
  return functionCalls;
306
471
  }
307
472
 
473
+ _extractTemplateVariables() {
474
+ const variables = [];
475
+
476
+ const traverse = (node) => {
477
+ if (!node) return;
478
+
479
+ if (node.type === 'template_value') {
480
+ const variableInfo = this._extractVariableFromNode(node);
481
+ if (variableInfo) {
482
+ variables.push(variableInfo);
483
+ }
484
+ }
485
+
486
+ if (node.children) {
487
+ for (const child of node.children) {
488
+ traverse(child);
489
+ }
490
+ }
491
+ };
492
+
493
+ traverse(this.ast);
494
+ return variables;
495
+ }
496
+
308
497
  /**
309
498
  * Prepare the template by replacing variables with their values
310
499
  * Rebuilds from AST by iterating through children
@@ -407,12 +596,15 @@ class RuleTemplate {
407
596
 
408
597
  // Apply filters if present
409
598
  if (templateInfo.filters && templateInfo.filters.length > 0) {
410
- for (const filterName of templateInfo.filters) {
411
- if (!TemplateFilters[filterName]) {
412
- throw new Error(`Unknown filter '${filterName}'`);
599
+ for (const filter of (templateInfo.filterCalls || templateInfo.filters)) {
600
+ const filterName = typeof filter === 'string' ? filter : filter?.name;
601
+ const filterArgs = typeof filter === 'string' ? [] : (Array.isArray(filter?.args) ? filter.args : []);
602
+
603
+ if (!filterName || !TemplateFilters[filterName]) {
604
+ throw new Error(`Unknown filter '${filterName || filter}'`);
413
605
  }
414
-
415
- TemplateFilters[filterName](varData);
606
+
607
+ TemplateFilters[filterName](varData, ...filterArgs);
416
608
  }
417
609
  }
418
610
 
@@ -443,12 +635,12 @@ class RuleTemplate {
443
635
  return value ? 'true' : 'false';
444
636
  }
445
637
 
446
- if (type === 'time period' || type === 'time period ago') {
447
- let ret = `${value.from} TO ${value.to}`;
448
- if(value.ago) {
449
- ret += ` AGO ${value.ago[0]} ${value.ago[1]}`;
450
- }
451
- return ret;
638
+ if (type === 'time period') {
639
+ return `BETWEEN ${value.from} AND ${value.to}`;
640
+ }
641
+
642
+ if (type === 'time period ago') {
643
+ return `${value.ago[0]} ${value.ago[1]} AGO BETWEEN ${value.from} AND ${value.to}`;
452
644
  }
453
645
 
454
646
  if (type === 'object' || type === 'string array' || type === 'number array' || type === 'boolean array' || type === 'object array') {
@@ -1 +1 @@
1
- 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","WS+","unit"],["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"]]},{"name":"string_atom","bnf":[["string"]]},{"name":"boolean_atom","bnf":[["false"],["true"]]},{"name":"time_value_atom","bnf":[["number_tod"]]},{"name":"time_period_atom","bnf":[["time_value_atom","WS*","\"TO\"","WS*","time_value_atom"]]},{"name":"time_period_ago_atom","bnf":[["time_value_atom","WS*","\"TO\"","WS*","time_value_atom","WS+","AGO","WS+","number","WS+","unit"]]},{"name":"object_atom","bnf":[["json_object"]]},{"name":"json_value","bnf":[["string"],["number"],["false"],["true"],["null"],["json_array"],["json_object"]]},{"name":"json_member","bnf":[["string","NAME_SEPARATOR","json_value"]]},{"name":"%json_object[2][2]","bnf":[["VALUE_SEPARATOR","json_member"]],"fragment":true},{"name":"%json_object[2]","bnf":[["json_member","%json_object[2][2]*"]],"fragment":true},{"name":"json_object","bnf":[["BEGIN_OBJECT","%json_object[2]?","END_OBJECT"]]},{"name":"%json_array[2][2]","bnf":[["VALUE_SEPARATOR","json_value"]],"fragment":true},{"name":"%json_array[2]","bnf":[["json_value","%json_array[2][2]*"]],"fragment":true},{"name":"json_array","bnf":[["BEGIN_ARRAY","%json_array[2]?","END_ARRAY"]]},{"name":"%string_array[2][2]","bnf":[["VALUE_SEPARATOR","string"]],"fragment":true},{"name":"%string_array[2]","bnf":[["string","%string_array[2][2]*"]],"fragment":true},{"name":"string_array","bnf":[["BEGIN_ARRAY","%string_array[2]?","END_ARRAY"]]},{"name":"%number_array[2][2]","bnf":[["VALUE_SEPARATOR","number"]],"fragment":true},{"name":"%number_array[2]","bnf":[["number","%number_array[2][2]*"]],"fragment":true},{"name":"number_array","bnf":[["BEGIN_ARRAY","%number_array[2]?","END_ARRAY"]]},{"name":"%boolean_array[2][2]","bnf":[["VALUE_SEPARATOR","boolean_atom"]],"fragment":true},{"name":"%boolean_array[2]","bnf":[["boolean_atom","%boolean_array[2][2]*"]],"fragment":true},{"name":"boolean_array","bnf":[["BEGIN_ARRAY","%boolean_array[2]?","END_ARRAY"]]},{"name":"%object_array[2][2]","bnf":[["VALUE_SEPARATOR","json_object"]],"fragment":true},{"name":"%object_array[2]","bnf":[["json_object","%object_array[2][2]*"]],"fragment":true},{"name":"object_array","bnf":[["BEGIN_ARRAY","%object_array[2]?","END_ARRAY"]]}]
1
+ 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":"string_atom","bnf":[["string"]]},{"name":"boolean_atom","bnf":[["false"],["true"]]},{"name":"time_value_atom","bnf":[["number_tod"]]},{"name":"object_atom","bnf":[["json_object"]]},{"name":"json_value","bnf":[["string"],["number"],["false"],["true"],["null"],["json_array"],["json_object"]]},{"name":"json_member","bnf":[["string","NAME_SEPARATOR","json_value"]]},{"name":"%json_object[2][2]","bnf":[["VALUE_SEPARATOR","json_member"]],"fragment":true},{"name":"%json_object[2]","bnf":[["json_member","%json_object[2][2]*"]],"fragment":true},{"name":"json_object","bnf":[["BEGIN_OBJECT","%json_object[2]?","END_OBJECT"]]},{"name":"%json_array[2][2]","bnf":[["VALUE_SEPARATOR","json_value"]],"fragment":true},{"name":"%json_array[2]","bnf":[["json_value","%json_array[2][2]*"]],"fragment":true},{"name":"json_array","bnf":[["BEGIN_ARRAY","%json_array[2]?","END_ARRAY"]]},{"name":"%string_array[2][2]","bnf":[["VALUE_SEPARATOR","string"]],"fragment":true},{"name":"%string_array[2]","bnf":[["string","%string_array[2][2]*"]],"fragment":true},{"name":"string_array","bnf":[["BEGIN_ARRAY","%string_array[2]?","END_ARRAY"]]},{"name":"%number_array[2][2]","bnf":[["VALUE_SEPARATOR","number"]],"fragment":true},{"name":"%number_array[2]","bnf":[["number","%number_array[2][2]*"]],"fragment":true},{"name":"number_array","bnf":[["BEGIN_ARRAY","%number_array[2]?","END_ARRAY"]]},{"name":"%boolean_array[2][2]","bnf":[["VALUE_SEPARATOR","boolean_atom"]],"fragment":true},{"name":"%boolean_array[2]","bnf":[["boolean_atom","%boolean_array[2][2]*"]],"fragment":true},{"name":"boolean_array","bnf":[["BEGIN_ARRAY","%boolean_array[2]?","END_ARRAY"]]},{"name":"%object_array[2][2]","bnf":[["VALUE_SEPARATOR","json_object"]],"fragment":true},{"name":"%object_array[2]","bnf":[["json_object","%object_array[2][2]*"]],"fragment":true},{"name":"object_array","bnf":[["BEGIN_ARRAY","%object_array[2]?","END_ARRAY"]]}]