@halleyassist/rule-templater 0.0.7 → 0.0.9

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.
@@ -1,122 +1,1732 @@
1
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
- between_time_only_atom ::= between_time_only
82
- between_tod_only_atom ::= between_tod_only
83
-
84
- AND ||= (WS* "&&" WS*) | (WS+ "AND" WS+)
85
- OR ||= (WS* "||" WS*) | (WS+ "OR" WS+)
86
- AGO ||= "AGO"
87
- GT ::= ">"
88
- LT ::= "<"
89
- GTE ::= ">="
90
- LTE ::= "<="
91
- IS ||= "is"
92
- EQ ::= "==" | "="
93
- NEQ ::= "!="
94
- NOT ||= ("!" WS*) | ("not" WS+)
95
-
96
- false ||= "FALSE"
97
- null ||= "null"
98
- true ||= "TRUE"
99
-
100
- array ::= BEGIN_ARRAY (value (VALUE_SEPARATOR value)*)? END_ARRAY
101
-
102
- unit ||= "seconds" | "minutes" | "hours" | "weeks" | "days" | "second" | "minute" | "week" | "hour" | "day" | "mins" | "min"
103
-
104
- number ::= "-"? ([0-9]+) ("." [0-9]+)? ("e" ("-" | "+")? ("0" | [1-9] [0-9]*))?
105
- number_time ::= number WS+ unit
106
- number_tod ::= ([0-9]+) ":" ([0-9]+)
107
-
108
- time_period_ago ||= number_time_atom (WS+ number_time_atom)* WS+ AGO
109
- time_period_ago_between ||= number_time_atom (WS+ number_time_atom)* WS+ AGO WS+ (between_time_only_atom | between_tod_only_atom)
110
- time_period_const ||= "today" | time_period_ago
111
- time_period ::= time_period_ago_between | time_period_const | between_tod_only | between_time_only
2
+ module.exports=[{"name":"statement_main","bnf":[["statement","EOF"]]},{"name":"logical_operator","bnf":[["AND"],["OR"]]},{"name":"%statement[2]","bnf":[["logical_operator","expression"]],"fragment":true},{"name":"statement","bnf":[["expression","%statement[2]*"]]},{"name":"expression","bnf":[["not_expression"],["standard_expression"],["parenthesis_expression"]]},{"name":"parenthesis_expression","bnf":[["BEGIN_PARENTHESIS","WS*","statement","WS*","END_PARENTHESIS"]]},{"name":"%not_expression[2]","bnf":[["result"],["parenthesis_expression"]],"fragment":true},{"name":"not_expression","bnf":[["NOT","%not_expression[2]"]]},{"name":"%standard_expression[2][1]","bnf":[["WS*","eq_approx"]],"fragment":true},{"name":"%standard_expression[2][2]","bnf":[["WS*","basic_rhs"]],"fragment":true},{"name":"%standard_expression[2][3][1]","bnf":[["WS+","IS"]],"fragment":true},{"name":"%standard_expression[2][3]","bnf":[["%standard_expression[2][3][1]?","WS+","between"]],"fragment":true},{"name":"%standard_expression[2][4]","bnf":[["WS+","in_expr"]],"fragment":true},{"name":"%standard_expression[2]","bnf":[["%standard_expression[2][1]"],["%standard_expression[2][2]"],["%standard_expression[2][3]"],["%standard_expression[2][4]"]],"fragment":true},{"name":"standard_expression","bnf":[["result","%standard_expression[2]?"]]},{"name":"basic_rhs","bnf":[["operator","WS*","result"]]},{"name":"eq_approx","bnf":[["eq_operator","WS*","\"~\"","WS*","result"]]},{"name":"PLUS","bnf":[["\"+\""]]},{"name":"MINUS","bnf":[["\"-\""]]},{"name":"MULTIPLY","bnf":[["\"*\""]]},{"name":"DIVIDE","bnf":[["\"/\""]]},{"name":"MODULUS","bnf":[["\"%\""]]},{"name":"DEFAULT_VAL","bnf":[["\"??\""]]},{"name":"arithmetic_operator","bnf":[["PLUS"],["MINUS"],["MULTIPLY"],["DIVIDE"],["MODULUS"],["DEFAULT_VAL"]]},{"name":"number_atom","bnf":[["number"]]},{"name":"number_time_atom","bnf":[["number_time"]]},{"name":"tod_atom","bnf":[["number_tod"]]},{"name":"dow_atom","bnf":[["dow"]]},{"name":"arithmetic_operand","bnf":[["fcall"],["number_time_atom"],["number_atom"]]},{"name":"%arithmetic_result[5]","bnf":[["arithmetic_result"],["arithmetic_operand"]],"fragment":true},{"name":"arithmetic_result","bnf":[["arithmetic_operand","WS*","arithmetic_operator","WS*","%arithmetic_result[5]"]]},{"name":"simple_result","bnf":[["fcall"],["value"]]},{"name":"result","bnf":[["arithmetic_result"],["simple_result"]]},{"name":"value_atom","bnf":[["false"],["true"],["array"],["time_period"],["number_time_atom"],["number_atom"],["tod_atom"],["string"]]},{"name":"value","bnf":[["value_atom"]]},{"name":"BEGIN_ARRAY","bnf":[["WS*",/\x5B/,"WS*"]]},{"name":"BEGIN_OBJECT","bnf":[["WS*",/\x7B/,"WS*"]]},{"name":"END_ARRAY","bnf":[["WS*",/\x5D/,"WS*"]]},{"name":"END_OBJECT","bnf":[["WS*",/\x7D/,"WS*"]]},{"name":"NAME_SEPARATOR","bnf":[["WS*",/\x3A/,"WS*"]]},{"name":"VALUE_SEPARATOR","bnf":[["WS*",/\x2C/,"WS*"]]},{"name":"WS","bnf":[[/[\x20\x09\x0A\x0D]/]]},{"name":"operator","bnf":[["GTE"],["LTE"],["GT"],["LT"],["EQ"],["NEQ"]]},{"name":"eq_operator","bnf":[["EQ"],["NEQ"]]},{"name":"BEGIN_ARGUMENT","bnf":[["\"(\""]]},{"name":"END_ARGUMENT","bnf":[["\")\""]]},{"name":"BEGIN_PARENTHESIS","bnf":[["\"(\""]]},{"name":"END_PARENTHESIS","bnf":[["\")\""]]},{"name":"BEGIN_IN","bnf":[[/[Ii]/,/[Nn]/]]},{"name":"in_expr","bnf":[["BEGIN_IN","WS*","BEGIN_PARENTHESIS","WS*","arguments","END_PARENTHESIS"]]},{"name":"argument","bnf":[["statement","WS*"]]},{"name":"%arguments[2]","bnf":[["WS*","\",\"","WS*","argument"]],"fragment":true},{"name":"arguments","bnf":[["argument","%arguments[2]*"]]},{"name":"%fname[1]","bnf":[[/[A-Za-z0-9]/]]},{"name":"fname","bnf":[["%fname[1]+"]]},{"name":"fcall","bnf":[["fname","WS*","BEGIN_ARGUMENT","WS*","arguments?","END_ARGUMENT"]]},{"name":"%between_dash_or_and[1]","bnf":[["WS+",/[Aa]/,/[Nn]/,/[Dd]/,"WS+"]],"fragment":true},{"name":"%between_dash_or_and[2]","bnf":[["WS*",/\-/,"WS*"]],"fragment":true},{"name":"between_dash_or_and","bnf":[["%between_dash_or_and[1]"],["%between_dash_or_and[2]"]]},{"name":"between_number_inner","bnf":[["number_atom"],["number_time_atom"]]},{"name":"between_number","bnf":[["between_number_inner","between_dash_or_and","between_number_inner"]]},{"name":"between_number_time_inner","bnf":[["number_time_atom"]]},{"name":"%between_number_time[4]","bnf":[["WS+","dow_range"]],"fragment":true},{"name":"between_number_time","bnf":[["between_number_time_inner","between_dash_or_and","between_number_time_inner","%between_number_time[4]?"]]},{"name":"between_tod_inner","bnf":[["tod_atom"]]},{"name":"%between_tod[2]","bnf":[["WS+",/[Aa]/,/[Nn]/,/[Dd]/,"WS+"]],"fragment":true},{"name":"%between_tod[4]","bnf":[["WS+","dow_range"]],"fragment":true},{"name":"between_tod","bnf":[["between_tod_inner","%between_tod[2]","between_tod_inner","%between_tod[4]?"]]},{"name":"%between[3]","bnf":[["between_number"],["between_tod"]],"fragment":true},{"name":"between","bnf":[[/[Bb]/,/[Ee]/,/[Tt]/,/[Ww]/,/[Ee]/,/[Ee]/,/[Nn]/,"WS+","%between[3]"]]},{"name":"dow","bnf":[[/[Mm]/,/[Oo]/,/[Nn]/,/[Dd]/,/[Aa]/,/[Yy]/],[/[Mm]/,/[Oo]/,/[Nn]/],[/[Tt]/,/[Uu]/,/[Ee]/,/[Ss]/,/[Dd]/,/[Aa]/,/[Yy]/],[/[Tt]/,/[Uu]/,/[Ee]/],[/[Ww]/,/[Ee]/,/[Dd]/,/[Nn]/,/[Ee]/,/[Ss]/,/[Dd]/,/[Aa]/,/[Yy]/],[/[Ww]/,/[Ee]/,/[Dd]/],[/[Tt]/,/[Hh]/,/[Uu]/,/[Rr]/,/[Ss]/,/[Dd]/,/[Aa]/,/[Yy]/],[/[Tt]/,/[Hh]/,/[Uu]/],[/[Tt]/,/[Hh]/,/[Uu]/,/[Rr]/],[/[Ff]/,/[Rr]/,/[Ii]/,/[Dd]/,/[Aa]/,/[Yy]/],[/[Ff]/,/[Rr]/,/[Ii]/],[/[Ss]/,/[Aa]/,/[Tt]/,/[Uu]/,/[Rr]/,/[Dd]/,/[Aa]/,/[Yy]/],[/[Ss]/,/[Aa]/,/[Tt]/],[/[Ss]/,/[Uu]/,/[Nn]/,/[Dd]/,/[Aa]/,/[Yy]/],[/[Ss]/,/[Uu]/,/[Nn]/]]},{"name":"dow_range_inner","bnf":[["dow_atom"]]},{"name":"%dow_range[4]","bnf":[["WS+",/[Tt]/,/[Oo]/,"WS+","dow_range_inner"]],"fragment":true},{"name":"dow_range","bnf":[[/[Oo]/,/[Nn]/,"WS+","dow_range_inner","%dow_range[4]?"]]},{"name":"between_time_only","bnf":[[/[Bb]/,/[Ee]/,/[Tt]/,/[Ww]/,/[Ee]/,/[Ee]/,/[Nn]/,"WS+","between_number_time"]]},{"name":"between_tod_only","bnf":[[/[Bb]/,/[Ee]/,/[Tt]/,/[Ww]/,/[Ee]/,/[Ee]/,/[Nn]/,"WS+","between_tod"]]},{"name":"between_time_only_atom","bnf":[["between_time_only"]]},{"name":"between_tod_only_atom","bnf":[["between_tod_only"]]},{"name":"%AND[1]","bnf":[["WS*",/&/,/&/,"WS*"]],"fragment":true},{"name":"%AND[2]","bnf":[["WS+",/[Aa]/,/[Nn]/,/[Dd]/,"WS+"]],"fragment":true},{"name":"AND","bnf":[["%AND[1]"],["%AND[2]"]]},{"name":"%OR[1]","bnf":[["WS*",/\|/,/\|/,"WS*"]],"fragment":true},{"name":"%OR[2]","bnf":[["WS+",/[Oo]/,/[Rr]/,"WS+"]],"fragment":true},{"name":"OR","bnf":[["%OR[1]"],["%OR[2]"]]},{"name":"AGO","bnf":[[/[Aa]/,/[Gg]/,/[Oo]/]]},{"name":"GT","bnf":[["\">\""]]},{"name":"LT","bnf":[["\"<\""]]},{"name":"GTE","bnf":[["\">=\""]]},{"name":"LTE","bnf":[["\"<=\""]]},{"name":"IS","bnf":[[/[Ii]/,/[Ss]/]]},{"name":"EQ","bnf":[["\"==\""],["\"=\""]]},{"name":"NEQ","bnf":[["\"!=\""]]},{"name":"%NOT[1]","bnf":[[/!/,"WS*"]],"fragment":true},{"name":"%NOT[2]","bnf":[[/[Nn]/,/[Oo]/,/[Tt]/,"WS+"]],"fragment":true},{"name":"NOT","bnf":[["%NOT[1]"],["%NOT[2]"]]},{"name":"false","bnf":[[/[Ff]/,/[Aa]/,/[Ll]/,/[Ss]/,/[Ee]/]]},{"name":"null","bnf":[[/[Nn]/,/[Uu]/,/[Ll]/,/[Ll]/]]},{"name":"true","bnf":[[/[Tt]/,/[Rr]/,/[Uu]/,/[Ee]/]]},{"name":"%array[2][2]","bnf":[["VALUE_SEPARATOR","value"]],"fragment":true},{"name":"%array[2]","bnf":[["value","%array[2][2]*"]],"fragment":true},{"name":"array","bnf":[["BEGIN_ARRAY","%array[2]?","END_ARRAY"]]},{"name":"unit","bnf":[[/[Ss]/,/[Ee]/,/[Cc]/,/[Oo]/,/[Nn]/,/[Dd]/,/[Ss]/],[/[Mm]/,/[Ii]/,/[Nn]/,/[Uu]/,/[Tt]/,/[Ee]/,/[Ss]/],[/[Hh]/,/[Oo]/,/[Uu]/,/[Rr]/,/[Ss]/],[/[Ww]/,/[Ee]/,/[Ee]/,/[Kk]/,/[Ss]/],[/[Dd]/,/[Aa]/,/[Yy]/,/[Ss]/],[/[Ss]/,/[Ee]/,/[Cc]/,/[Oo]/,/[Nn]/,/[Dd]/],[/[Mm]/,/[Ii]/,/[Nn]/,/[Uu]/,/[Tt]/,/[Ee]/],[/[Ww]/,/[Ee]/,/[Ee]/,/[Kk]/],[/[Hh]/,/[Oo]/,/[Uu]/,/[Rr]/],[/[Dd]/,/[Aa]/,/[Yy]/],[/[Mm]/,/[Ii]/,/[Nn]/,/[Ss]/],[/[Mm]/,/[Ii]/,/[Nn]/]]},{"name":"%number[2][1]","bnf":[[/[0-9]/]]},{"name":"%number[2]","bnf":[["%number[2][1]+"]],"fragment":true},{"name":"%number[3][2]","bnf":[[/[0-9]/]]},{"name":"%number[3]","bnf":[["\".\"","%number[3][2]+"]],"fragment":true},{"name":"%number[4][2]","bnf":[["\"-\""],["\"+\""]],"fragment":true},{"name":"%number[4][3][2]","bnf":[[/[0-9]/]]},{"name":"%number[4][3]","bnf":[["\"0\""],[/[1-9]/,"%number[4][3][2]*"]],"fragment":true},{"name":"%number[4]","bnf":[["\"e\"","%number[4][2]?","%number[4][3]"]],"fragment":true},{"name":"number","bnf":[["\"-\"?","%number[2]","%number[3]?","%number[4]?"]]},{"name":"number_time","bnf":[["number","WS+","unit"]]},{"name":"%number_tod[1][1]","bnf":[[/[0-9]/]]},{"name":"%number_tod[1]","bnf":[["%number_tod[1][1]+"]],"fragment":true},{"name":"%number_tod[3][1]","bnf":[[/[0-9]/]]},{"name":"%number_tod[3]","bnf":[["%number_tod[3][1]+"]],"fragment":true},{"name":"number_tod","bnf":[["%number_tod[1]","\":\"","%number_tod[3]"]]},{"name":"%time_period_ago[2]","bnf":[["WS+","number_time_atom"]],"fragment":true},{"name":"time_period_ago","bnf":[["number_time_atom","%time_period_ago[2]*","WS+","AGO"]]},{"name":"%time_period_ago_between[2]","bnf":[["WS+","number_time_atom"]],"fragment":true},{"name":"%time_period_ago_between[6]","bnf":[["between_time_only_atom"],["between_tod_only_atom"]],"fragment":true},{"name":"time_period_ago_between","bnf":[["number_time_atom","%time_period_ago_between[2]*","WS+","AGO","WS+","%time_period_ago_between[6]"]]},{"name":"time_period_const","bnf":[[/[Tt]/,/[Oo]/,/[Dd]/,/[Aa]/,/[Yy]/],["time_period_ago"]]},{"name":"time_period","bnf":[["time_period_ago_between"],["time_period_const"],["between_tod_only"],["between_time_only"]]},{"name":"%string[2][1]","bnf":[[/[\x20-\x21]/],[/[\x23-\x5B]/],[/[\x5D-\uFFFF]/]],"fragment":true},{"name":"%string[2][2]","bnf":[[/\x22/],[/\x5C/],[/\x2F/],[/\x62/],[/\x66/],[/\x6E/],[/\x72/],[/\x74/],[/\x75/,"HEXDIG","HEXDIG","HEXDIG","HEXDIG"]],"fragment":true},{"name":"%string[2]","bnf":[["%string[2][1]"],[/\x5C/,"%string[2][2]"]],"fragment":true},{"name":"string","bnf":[["'\"'","%string[2]*","'\"'"]]},{"name":"HEXDIG","bnf":[[/[a-fA-F0-9]/]]}]
3
+ },{}],2:[function(require,module,exports){
4
+ const {Parser} = require('ebnf/dist/Parser.js'), {ParsingError} = require('ebnf'), RuleParseError = require('./errors/RuleParseError');
5
+ let ParserRules = require('./RuleParser.production.ebnf.js');
6
+ let ParserCache;
7
+ const {ErrorAnalyzer} = require('./errors/ErrorAnalyzer');
8
+ const ArithmeticOperators = {
9
+ '+': 'MathAdd',
10
+ '-': 'MathSub',
11
+ '/': 'MathDiv',
12
+ '*': 'MathMul',
13
+ '%': 'MathMod',
14
+ '??': 'Default'
15
+ };
16
+ const OperatorFn = {
17
+ '>': 'Gt',
18
+ '<': 'Lt',
19
+ '>=': 'Gte',
20
+ '<=': 'Lte',
21
+ '==': 'Eq',
22
+ '=': 'Eq',
23
+ '!=': 'Neq'
24
+ };
25
+ const LogicalOperators = {
26
+ '&&': 'And',
27
+ 'AND': 'And',
28
+ '||': 'Or',
29
+ 'OR': 'Or'
30
+ };
31
+ const DOW_MAP = {
32
+ 'MON': 'MONDAY',
33
+ 'TUE': 'TUESDAY',
34
+ 'WED': 'WEDNESDAY',
35
+ 'THU': 'THURSDAY',
36
+ 'THUR': 'THURSDAY',
37
+ 'FRI': 'FRIDAY',
38
+ 'SAT': 'SATURDAY',
39
+ 'SUN': 'SUNDAY'
40
+ };
41
+ const VALID_DAYS = new Set([
42
+ 'MONDAY',
43
+ 'TUESDAY',
44
+ 'WEDNESDAY',
45
+ 'THURSDAY',
46
+ 'FRIDAY',
47
+ 'SATURDAY',
48
+ 'SUNDAY'
49
+ ]);
50
+ const normalizeDow = text => {
51
+ const upper = text.toUpperCase();
52
+ if (upper in DOW_MAP) {
53
+ return DOW_MAP[upper];
54
+ }
55
+ if (VALID_DAYS.has(upper)) {
56
+ return upper;
57
+ }
58
+ throw new Error(`Invalid day of week: ${ text }`);
59
+ };
60
+ const Epsilon = 0.01;
61
+ class RuleParser {
62
+ static toAst(txt, parser = null) {
63
+ let ret;
64
+ if (!parser) {
65
+ if (!ParserCache) {
66
+ ParserCache = new Parser(ParserRules, { debug: false });
67
+ }
68
+ parser = ParserCache;
69
+ }
70
+ try {
71
+ ret = parser.getAST(txt.trim(), 'statement_main');
72
+ } catch (e) {
73
+ if (e instanceof ParsingError) {
74
+ throw ErrorAnalyzer.analyzeParseFailure(txt, e);
75
+ }
76
+ throw e;
77
+ }
78
+ if (ret) {
79
+ return ret.children[0];
80
+ }
81
+ throw ErrorAnalyzer.analyzeParseFailure(txt);
82
+ }
83
+ static _parseArgument(argument) {
84
+ const child = argument.children[0];
85
+ return RuleParser._buildExpressionGroup(child);
86
+ }
87
+ static _parseFcall(fcall) {
88
+ const fname = fcall.children[0];
89
+ const ret = [fname.text];
90
+ if (fcall.children.length != 1) {
91
+ const args = fcall.children[1];
92
+ for (const a of args.children) {
93
+ ret.push(RuleParser._parseArgument(a));
94
+ }
95
+ }
96
+ return ret;
97
+ }
98
+ static _parseDowRange(dowRange) {
99
+ if (dowRange.children.length === 1) {
100
+ const dayText = RuleParser.__parseValue(dowRange.children[0]);
101
+ return {
102
+ start: dayText,
103
+ end: dayText
104
+ };
105
+ } else if (dowRange.children.length === 2) {
106
+ const startDay = RuleParser.__parseValue(dowRange.children[0]);
107
+ const endDay = RuleParser.__parseValue(dowRange.children[1]);
108
+ return {
109
+ start: startDay,
110
+ end: endDay
111
+ };
112
+ } else {
113
+ throw new Error(`Invalid dow_range with ${ dowRange.children.length } children`);
114
+ }
115
+ }
116
+ static _addDowToTods(startTod, endTod, dowRange) {
117
+ if (dowRange && dowRange.type === 'dow_range') {
118
+ const dow = RuleParser._parseDowRange(dowRange);
119
+ startTod.dow = dow.start;
120
+ endTod.dow = dow.end;
121
+ }
122
+ }
123
+ static _parseTimePeriod(tp) {
124
+ switch (tp.type) {
125
+ case 'time_period_const':
126
+ if (tp.children && tp.children.length > 0 && tp.children[0].type === 'time_period_ago') {
127
+ const timePeriodAgo = tp.children[0];
128
+ let totalSeconds = 0;
129
+ const components = [];
130
+ for (const child of timePeriodAgo.children) {
131
+ if (child.type === 'number_time_atom') {
132
+ const numberTime = child.children[0];
133
+ const number = parseFloat(numberTime.children[0].text);
134
+ const unit = numberTime.children[1].text.toUpperCase();
135
+ components.push([
136
+ number,
137
+ unit
138
+ ]);
139
+ totalSeconds += RuleParser.__parseValue(numberTime);
140
+ }
141
+ }
142
+ if (components.length === 1) {
143
+ return [
144
+ 'TimePeriodConstAgo',
145
+ components[0][0],
146
+ components[0][1]
147
+ ];
148
+ } else {
149
+ return [
150
+ 'TimePeriodConstAgo',
151
+ totalSeconds,
152
+ 'SECONDS'
153
+ ];
154
+ }
155
+ }
156
+ return [
157
+ 'TimePeriodConst',
158
+ tp.text
159
+ ];
160
+ case 'time_period_ago_between': {
161
+ let totalSeconds = 0;
162
+ let betweenNode = null;
163
+ let isBetweenTod = false;
164
+ for (let i = 0; i < tp.children.length; i++) {
165
+ if (tp.children[i].type === 'number_time_atom') {
166
+ totalSeconds += RuleParser.__parseValue(tp.children[i].children[0]);
167
+ } else if (tp.children[i].type === 'between_time_only' || tp.children[i].type === 'between_time_only_atom') {
168
+ betweenNode = tp.children[i].type === 'between_time_only_atom' ? tp.children[i].children[0] : tp.children[i];
169
+ isBetweenTod = false;
170
+ } else if (tp.children[i].type === 'between_tod_only' || tp.children[i].type === 'between_tod_only_atom') {
171
+ betweenNode = tp.children[i].type === 'between_tod_only_atom' ? tp.children[i].children[0] : tp.children[i];
172
+ isBetweenTod = true;
173
+ }
174
+ }
175
+ if (!betweenNode) {
176
+ throw new Error('time_period_ago_between requires between_time_only or between_tod_only child');
177
+ }
178
+ if (isBetweenTod) {
179
+ const betweenTod = betweenNode.children[0];
180
+ let startTod = RuleParser.__parseValue(betweenTod.children[0]);
181
+ let endTod = RuleParser.__parseValue(betweenTod.children[1]);
182
+ if (betweenTod.children.length > 2) {
183
+ RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2]);
184
+ }
185
+ return [
186
+ 'TimePeriodBetweenAgo',
187
+ totalSeconds,
188
+ startTod,
189
+ endTod
190
+ ];
191
+ } else {
192
+ const betweenNumberTime = betweenNode.children[0];
193
+ const startValue = RuleParser.__parseValue(betweenNumberTime.children[0]);
194
+ const endValue = RuleParser.__parseValue(betweenNumberTime.children[2]);
195
+ if (betweenNumberTime.children.length > 3 && betweenNumberTime.children[3].type === 'dow_range') {
196
+ const dow = RuleParser._parseDowRange(betweenNumberTime.children[3]);
197
+ if (dow.start === dow.end) {
198
+ return [
199
+ 'TimePeriodBetweenAgo',
200
+ totalSeconds,
201
+ startValue,
202
+ endValue,
203
+ dow.start
204
+ ];
205
+ } else {
206
+ return [
207
+ 'TimePeriodBetweenAgo',
208
+ totalSeconds,
209
+ startValue,
210
+ endValue,
211
+ dow.start,
212
+ dow.end
213
+ ];
214
+ }
215
+ }
216
+ return [
217
+ 'TimePeriodBetweenAgo',
218
+ totalSeconds,
219
+ startValue,
220
+ endValue
221
+ ];
222
+ }
223
+ }
224
+ case 'between_tod_only': {
225
+ const betweenTod = tp.children[0];
226
+ let startTod = RuleParser.__parseValue(betweenTod.children[0]);
227
+ let endTod = RuleParser.__parseValue(betweenTod.children[1]);
228
+ if (betweenTod.children.length > 2) {
229
+ if (typeof startTod === 'number')
230
+ startTod = {
231
+ seconds: startTod,
232
+ dow: null
233
+ };
234
+ if (typeof endTod === 'number')
235
+ endTod = {
236
+ seconds: endTod,
237
+ dow: null
238
+ };
239
+ RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2]);
240
+ }
241
+ return [
242
+ 'TimePeriodBetween',
243
+ startTod,
244
+ endTod
245
+ ];
246
+ }
247
+ case 'between_time_only': {
248
+ const betweenNumberTime = tp.children[0];
249
+ const startValue = RuleParser.__parseValue(betweenNumberTime.children[0]);
250
+ const endValue = RuleParser.__parseValue(betweenNumberTime.children[2]);
251
+ if (betweenNumberTime.children.length > 3 && betweenNumberTime.children[3].type === 'dow_range') {
252
+ const dow = RuleParser._parseDowRange(betweenNumberTime.children[3]);
253
+ if (dow.start === dow.end) {
254
+ return [
255
+ 'TimePeriodBetween',
256
+ startValue,
257
+ endValue,
258
+ dow.start
259
+ ];
260
+ } else {
261
+ return [
262
+ 'TimePeriodBetween',
263
+ startValue,
264
+ endValue,
265
+ dow.start,
266
+ dow.end
267
+ ];
268
+ }
269
+ }
270
+ return [
271
+ 'TimePeriodBetween',
272
+ startValue,
273
+ endValue
274
+ ];
275
+ }
276
+ }
277
+ }
278
+ static __parseValue(child) {
279
+ const type = child.type;
280
+ switch (type) {
281
+ case 'value': {
282
+ return RuleParser.__parseValue(child.children[0]);
283
+ }
284
+ case 'value_atom': {
285
+ return RuleParser.__parseValue(child.children[0]);
286
+ }
287
+ case 'number_atom':
288
+ case 'number_time_atom':
289
+ case 'tod_atom':
290
+ case 'dow_atom':
291
+ case 'between_tod_inner':
292
+ case 'between_number_inner':
293
+ case 'between_number_time_inner':
294
+ case 'dow_range_inner': {
295
+ return RuleParser.__parseValue(child.children[0]);
296
+ }
297
+ case 'string': {
298
+ const str = child.text;
299
+ return str.slice(1, -1);
300
+ }
301
+ case 'number':
302
+ return parseFloat(child.text);
303
+ case 'number_tod': {
304
+ const tokens = child.text.split(':');
305
+ if (tokens.length !== 2) {
306
+ throw new Error(`Invalid time of day, ${ child.text } should be ##:##`);
307
+ }
308
+ const hours = parseInt(tokens[0]);
309
+ const minutes = parseInt(tokens[1]);
310
+ const tod = hours * 100 + minutes;
311
+ const ret = {
312
+ hours,
313
+ minutes,
314
+ tod
315
+ };
316
+ if (!isNaN(tod) && ret.hours >= 0 && ret.hours < 24 && ret.minutes >= 0 && ret.minutes < 60) {
317
+ return ret;
318
+ }
319
+ throw new Error(`Invalid time of day, ${ child.text } -> [${ tokens.join(', ') }] -> ${ hours }h${ minutes }m -> ${ tod }`);
320
+ }
321
+ case 'number_time': {
322
+ const nt = child;
323
+ const mult = parseFloat(nt.children[0].text);
324
+ switch (nt.children[1].text.toUpperCase()) {
325
+ case 'SECONDS':
326
+ case 'SECOND':
327
+ return mult;
328
+ case 'MINUTES':
329
+ case 'MINUTE':
330
+ case 'MINS':
331
+ case 'MIN':
332
+ return mult * 60;
333
+ case 'HOURS':
334
+ case 'HOUR':
335
+ return mult * 60 * 60;
336
+ case 'DAYS':
337
+ case 'DAY':
338
+ return mult * 60 * 60 * 24;
339
+ case 'WEEKS':
340
+ case 'WEEK':
341
+ return mult * 60 * 60 * 24 * 7;
342
+ }
343
+ throw new Error(`Invalid exponent ${ nt.children[1].text }`);
344
+ }
345
+ case 'true':
346
+ return true;
347
+ case 'false':
348
+ return false;
349
+ case 'array': {
350
+ const ret = [];
351
+ for (const c of child.children) {
352
+ ret.push(RuleParser.__parseValue(c));
353
+ }
354
+ return ret;
355
+ }
356
+ case 'dow': {
357
+ return normalizeDow(child.text);
358
+ }
359
+ default:
360
+ throw new Error(`Unknown value type ${ type }`);
361
+ }
362
+ }
363
+ static _parseValue(value) {
364
+ const child = value.children[0];
365
+ const type = child.type;
366
+ switch (type) {
367
+ case 'value_atom': {
368
+ const atomChild = child.children[0];
369
+ if (atomChild.type === 'time_period') {
370
+ const tp = atomChild.children[0];
371
+ return RuleParser._parseTimePeriod(tp);
372
+ }
373
+ return [
374
+ 'Value',
375
+ RuleParser.__parseValue(atomChild)
376
+ ];
377
+ }
378
+ case 'time_period': {
379
+ const tp = child.children[0];
380
+ return RuleParser._parseTimePeriod(tp);
381
+ }
382
+ default:
383
+ return [
384
+ 'Value',
385
+ RuleParser.__parseValue(child)
386
+ ];
387
+ }
388
+ }
389
+ static _parseSimpleResult(result) {
390
+ const child = result.children[0];
391
+ const type = child.type;
392
+ switch (type) {
393
+ case 'fcall':
394
+ return RuleParser._parseFcall(child);
395
+ case 'value':
396
+ return RuleParser._parseValue(child);
397
+ }
398
+ return null;
399
+ }
400
+ static _parseArithmeticOperand(operand) {
401
+ const child = operand.children[0];
402
+ const type = child.type;
403
+ switch (type) {
404
+ case 'fcall':
405
+ return RuleParser._parseFcall(child);
406
+ case 'number_atom':
407
+ case 'number_time_atom': {
408
+ return [
409
+ 'Value',
410
+ RuleParser.__parseValue(child.children[0])
411
+ ];
412
+ }
413
+ case 'number':
414
+ return [
415
+ 'Value',
416
+ parseFloat(child.text)
417
+ ];
418
+ case 'number_time':
419
+ return [
420
+ 'Value',
421
+ RuleParser.__parseValue(child)
422
+ ];
423
+ }
424
+ throw new Error(`Unknown arithmetic operand type ${ type }`);
425
+ }
426
+ static _isConstantValue(expr) {
427
+ return Array.isArray(expr) && expr.length === 2 && expr[0] === 'Value';
428
+ }
429
+ static _isConstantNumberValue(expr) {
430
+ return Array.isArray(expr) && expr.length === 2 && expr[0] === 'Value' && typeof expr[1] === 'number';
431
+ }
432
+ static _evaluateConstantArithmetic(operator, leftValue, rightValue) {
433
+ switch (operator) {
434
+ case 'MathAdd':
435
+ return leftValue + rightValue;
436
+ case 'MathSub':
437
+ return leftValue - rightValue;
438
+ case 'MathMul':
439
+ return leftValue * rightValue;
440
+ case 'MathDiv':
441
+ return leftValue / rightValue;
442
+ case 'MathMod':
443
+ return leftValue % rightValue;
444
+ default:
445
+ return null;
446
+ }
447
+ }
448
+ static _parseArithmeticResult(result) {
449
+ const partA = RuleParser._parseArithmeticOperand(result.children[0]);
450
+ const operatorFn = ArithmeticOperators[result.children[1].text];
451
+ const partB = RuleParser.__parseArithmeticResult(result, 2);
452
+ if (RuleParser._isConstantNumberValue(partA) && RuleParser._isConstantNumberValue(partB)) {
453
+ const result = RuleParser._evaluateConstantArithmetic(operatorFn, partA[1], partB[1]);
454
+ if (result !== null) {
455
+ return [
456
+ 'Value',
457
+ result
458
+ ];
459
+ }
460
+ }
461
+ return [
462
+ operatorFn,
463
+ partA,
464
+ partB
465
+ ];
466
+ }
467
+ static __parseArithmeticResult(result, idx) {
468
+ const child = result.children[idx];
469
+ const type = child.type;
470
+ switch (type) {
471
+ case 'arithmetic_operand':
472
+ return RuleParser._parseArithmeticOperand(child);
473
+ case 'arithmetic_result':
474
+ return RuleParser._parseArithmeticResult(child);
475
+ }
476
+ throw new Error(`Unknown arithmetic result node ${ type }`);
477
+ }
478
+ static __parseResult(result, idx) {
479
+ const child = result.children[idx];
480
+ const type = child.type;
481
+ switch (type) {
482
+ case 'simple_result':
483
+ return RuleParser._parseSimpleResult(child);
484
+ case 'arithmetic_result':
485
+ return RuleParser._parseArithmeticResult(child);
486
+ }
487
+ throw new Error(`Unknown result node ${ type }`);
488
+ }
489
+ static _parseResult(result) {
490
+ return RuleParser.__parseResult(result, 0);
491
+ }
492
+ static _parseStdExpression(expr) {
493
+ switch (expr.children.length) {
494
+ case 1:
495
+ return RuleParser._parseResult(expr.children[0]);
496
+ case 2: {
497
+ const rhs = expr.children[1];
498
+ switch (rhs.type) {
499
+ case 'between_tod': {
500
+ const startTod = RuleParser.__parseValue(rhs.children[0]);
501
+ const endTod = RuleParser.__parseValue(rhs.children[1]);
502
+ if (rhs.children.length > 2) {
503
+ RuleParser._addDowToTods(startTod, endTod, rhs.children[2]);
504
+ }
505
+ return [
506
+ 'Between',
507
+ RuleParser._parseResult(expr.children[0]),
508
+ [
509
+ 'Value',
510
+ startTod
511
+ ],
512
+ [
513
+ 'Value',
514
+ endTod
515
+ ]
516
+ ];
517
+ }
518
+ case 'between': {
519
+ const betweenChild = rhs.children[0];
520
+ if (betweenChild.type === 'between_tod') {
521
+ const startTod = RuleParser.__parseValue(betweenChild.children[0]);
522
+ const endTod = RuleParser.__parseValue(betweenChild.children[1]);
523
+ if (betweenChild.children.length > 2) {
524
+ RuleParser._addDowToTods(startTod, endTod, betweenChild.children[2]);
525
+ }
526
+ return [
527
+ 'Between',
528
+ RuleParser._parseResult(expr.children[0]),
529
+ [
530
+ 'Value',
531
+ startTod
532
+ ],
533
+ [
534
+ 'Value',
535
+ endTod
536
+ ]
537
+ ];
538
+ } else {
539
+ return [
540
+ 'Between',
541
+ RuleParser._parseResult(expr.children[0]),
542
+ [
543
+ 'Value',
544
+ RuleParser.__parseValue(betweenChild.children[0])
545
+ ],
546
+ [
547
+ 'Value',
548
+ RuleParser.__parseValue(betweenChild.children[2])
549
+ ]
550
+ ];
551
+ }
552
+ }
553
+ case 'between_number':
554
+ return [
555
+ 'Between',
556
+ RuleParser._parseResult(expr.children[0]),
557
+ [
558
+ 'Value',
559
+ RuleParser.__parseValue(rhs.children[0].children[0])
560
+ ],
561
+ [
562
+ 'Value',
563
+ RuleParser.__parseValue(rhs.children[0].children[2])
564
+ ]
565
+ ];
566
+ case 'basic_rhs':
567
+ return [
568
+ OperatorFn[rhs.children[0].text],
569
+ RuleParser._parseResult(expr.children[0]),
570
+ RuleParser._parseResult(rhs.children[1])
571
+ ];
572
+ case 'eq_approx': {
573
+ const rhsValue = RuleParser._parseResult(rhs.children[1]);
574
+ const ret = [
575
+ 'Between',
576
+ RuleParser._parseResult(expr.children[0]),
577
+ [
578
+ 'Value',
579
+ rhsValue[1] - Epsilon
580
+ ],
581
+ [
582
+ 'Value',
583
+ rhsValue[1] + Epsilon
584
+ ]
585
+ ];
586
+ if (rhs.children[0].text === '!=') {
587
+ return [
588
+ 'Not',
589
+ ret
590
+ ];
591
+ }
592
+ return ret;
593
+ }
594
+ case 'in_expr': {
595
+ const args = rhs.children[0];
596
+ const haystack = [];
597
+ for (const a of args.children) {
598
+ haystack.push(RuleParser._parseArgument(a));
599
+ }
600
+ const needle = RuleParser._parseResult(expr.children[0]);
601
+ const allConstants = haystack.every(item => RuleParser._isConstantValue(item));
602
+ let haystackExpr;
603
+ if (allConstants) {
604
+ const constantArray = haystack.map(item => item[1]);
605
+ haystackExpr = [
606
+ 'Value',
607
+ constantArray
608
+ ];
609
+ } else {
610
+ haystackExpr = [
611
+ 'Array',
612
+ ...haystack
613
+ ];
614
+ }
615
+ return [
616
+ 'ArrayIn',
617
+ haystackExpr,
618
+ needle
619
+ ];
620
+ }
621
+ default:
622
+ throw new Error(`unable to parse std expression, unknown rhs type ${ rhs.type }`);
623
+ }
624
+ }
625
+ default:
626
+ throw new Error(`unable to parse std expression, unknown number of children ${ expr.children.length }`);
627
+ }
628
+ }
629
+ static buildLogical(members, fn) {
630
+ return [
631
+ fn,
632
+ ...members
633
+ ];
634
+ }
635
+ static _buildExpressionGroup(ast) {
636
+ let ret = [];
637
+ let currentLogical = null;
638
+ for (const expr of ast.children) {
639
+ if (expr.type == 'logical_operator') {
640
+ const logicalOperator = expr.text.trim();
641
+ const operatorFn = LogicalOperators[logicalOperator.toUpperCase()];
642
+ if (currentLogical === null || currentLogical !== operatorFn) {
643
+ if (ret.length > 1) {
644
+ ret = [RuleParser.buildLogical(ret, currentLogical)];
645
+ }
646
+ currentLogical = operatorFn;
647
+ }
648
+ } else {
649
+ ret.push(RuleParser._exprToIL(expr));
650
+ }
651
+ }
652
+ if (ret.length == 0) {
653
+ throw new Error('invalid rule');
654
+ }
655
+ if (ret.length == 1) {
656
+ return ret[0];
657
+ }
658
+ return RuleParser.buildLogical(ret, currentLogical);
659
+ }
660
+ static _parseParenthesisExpression(expr) {
661
+ return RuleParser._buildExpressionGroup(expr.children[0]);
662
+ }
663
+ static _exprToIL(expr) {
664
+ const eInner = expr.children[0];
665
+ switch (eInner.type) {
666
+ case 'standard_expression':
667
+ return RuleParser._parseStdExpression(eInner);
668
+ case 'not_expression': {
669
+ const child = eInner.children[0];
670
+ let result;
671
+ switch (child.type) {
672
+ case 'parenthesis_expression':
673
+ result = RuleParser._parseParenthesisExpression(child);
674
+ break;
675
+ default:
676
+ result = RuleParser._parseResult(child);
677
+ }
678
+ return [
679
+ 'Not',
680
+ result
681
+ ];
682
+ }
683
+ case 'parenthesis_expression':
684
+ return RuleParser._parseParenthesisExpression(eInner);
685
+ default:
686
+ throw new Error(`unknown type of expression ${ eInner.type }`);
687
+ }
688
+ }
689
+ static toIL(txt) {
690
+ try {
691
+ const ast = RuleParser.toAst(txt);
692
+ if (!ast)
693
+ throw new Error(`failed to parse ${ txt }`);
694
+ return RuleParser._buildExpressionGroup(ast);
695
+ } catch (e) {
696
+ if (e.name === 'RuleParseError') {
697
+ throw e;
698
+ }
699
+ if (e.message && e.message.includes('Invalid time of day')) {
700
+ const match = e.message.match(/Invalid time of day[,:]?\s*([0-9:]+)/);
701
+ const badTod = match ? match[1] : 'invalid';
702
+ const lines = txt.trim().split('\n');
703
+ const position = {
704
+ line: lines.length,
705
+ column: lines[lines.length - 1].length + 1,
706
+ offset: txt.trim().length
707
+ };
708
+ throw new RuleParseError('BAD_TOD', `Invalid time of day: ${ badTod }`, 'Time of day must be in HH:MM format with hours 0-23 and minutes 0-59, e.g. 08:30, 14:00, 23:59.', position, badTod, ['HH:MM'], txt.trim().substring(Math.max(0, txt.trim().length - 50)));
709
+ }
710
+ if (e.message && e.message.includes('Invalid day of week')) {
711
+ const match = e.message.match(/Invalid day of week[,:]?\s*(\w+)/);
712
+ const badDow = match ? match[1] : 'invalid';
713
+ const lines = txt.trim().split('\n');
714
+ const position = {
715
+ line: lines.length,
716
+ column: lines[lines.length - 1].length + 1,
717
+ offset: txt.trim().length
718
+ };
719
+ throw new RuleParseError('BAD_DOW', `Invalid day of week: ${ badDow }`, 'Valid days are: MONDAY/MON, TUESDAY/TUE, WEDNESDAY/WED, THURSDAY/THU, FRIDAY/FRI, SATURDAY/SAT, SUNDAY/SUN.', position, badDow, [
720
+ 'MONDAY',
721
+ 'TUESDAY',
722
+ 'WEDNESDAY',
723
+ 'THURSDAY',
724
+ 'FRIDAY',
725
+ 'SATURDAY',
726
+ 'SUNDAY'
727
+ ], txt.trim().substring(Math.max(0, txt.trim().length - 50)));
728
+ }
729
+ throw e;
730
+ }
731
+ }
732
+ }
733
+ module.exports = RuleParser;
734
+ module.exports.ParserRules = ParserRules;
735
+ module.exports.ParsingError = ParsingError;
736
+ module.exports.RuleParseError = RuleParseError;
737
+ },{"./RuleParser.production.ebnf.js":1,"./errors/ErrorAnalyzer":3,"./errors/RuleParseError":4,"ebnf":13,"ebnf/dist/Parser.js":9}],3:[function(require,module,exports){
738
+ const { ParsingError } = require('ebnf');
739
+ const RuleParseError = require('./RuleParseError');
112
740
 
113
- string ::= '"' (([#x20-#x21] | [#x23-#x5B] | [#x5D-#xFFFF]) | #x5C (#x22 | #x5C | #x2F | #x62 | #x66 | #x6E | #x72 | #x74 | #x75 HEXDIG HEXDIG HEXDIG HEXDIG))* '"'
114
- HEXDIG ::= [a-fA-F0-9]
115
- `
741
+ /**
742
+ * Analyzes parsing errors and maps them to user-friendly error codes
743
+ */
744
+ class ErrorAnalyzer {
745
+ /**
746
+ * Analyze a ParsingError and return a user-friendly RuleParseError
747
+ * @param {string} input - The input string that failed to parse
748
+ * @param {ParsingError} parsingError - The ParsingError from the parser (optional)
749
+ * @returns {RuleParseError} - A user-friendly error with error code
750
+ */
751
+ static analyzeParseFailure(input, parsingError = null) {
752
+ const trimmedInput = input.trim();
753
+
754
+ // Check for empty or missing expression first (fast path)
755
+ if (!trimmedInput) {
756
+ const position = { line: 1, column: 1, offset: 0 };
757
+ return new RuleParseError(
758
+ "MISSING_EXPRESSION",
759
+ "Empty or missing expression.",
760
+ "Provide a valid rule expression.",
761
+ position,
762
+ "empty input",
763
+ ["expression"],
764
+ ""
765
+ );
766
+ }
767
+
768
+ // If we have a ParsingError, use its information
769
+ if (parsingError && parsingError instanceof ParsingError) {
770
+ return this._analyzeFromParsingError(input, parsingError);
771
+ }
772
+
773
+ // Fallback to string-based analysis (for validation errors)
774
+ const lines = trimmedInput.split('\n');
775
+ const line = lines.length;
776
+ const lastLine = lines[lines.length - 1] || '';
777
+ const column = lastLine.length + 1;
778
+ const offset = trimmedInput.length;
779
+
780
+ const position = { line, column, offset };
781
+
782
+ // Get snippet (last 50 chars or the whole input if shorter)
783
+ const snippetStart = Math.max(0, trimmedInput.length - 50);
784
+ const snippet = (snippetStart > 0 ? '...' : '') + trimmedInput.substring(snippetStart);
785
+
786
+ // Analyze the error pattern
787
+ const errorInfo = this._detectErrorPattern(trimmedInput, position, snippet);
788
+
789
+ return new RuleParseError(
790
+ errorInfo.code,
791
+ errorInfo.message,
792
+ errorInfo.hint,
793
+ position,
794
+ errorInfo.found,
795
+ errorInfo.expected,
796
+ snippet
797
+ );
798
+ }
116
799
 
117
- module.exports = Grammars.W3C.getRules(grammar);
800
+ /**
801
+ * Analyze error using ParsingError exception data
802
+ * @private
803
+ */
804
+ static _analyzeFromParsingError(input, err) {
805
+ const position = err.position;
806
+ const expected = err.expected || [];
807
+ const found = err.found || '';
808
+ const failureTree = err.failureTree || [];
809
+
810
+ // Get snippet around error position
811
+ const snippetStart = Math.max(0, position.offset - 20);
812
+ const snippetEnd = Math.min(input.length, position.offset + 30);
813
+ const snippet = (snippetStart > 0 ? '...' : '') +
814
+ input.substring(snippetStart, snippetEnd) +
815
+ (snippetEnd < input.length ? '...' : '');
816
+
817
+ // Analyze what was expected to determine error type using failureTree
818
+ const errorInfo = this._detectErrorFromFailureTree(input, position, expected, found, failureTree);
819
+
820
+ return new RuleParseError(
821
+ errorInfo.code,
822
+ errorInfo.message,
823
+ errorInfo.hint,
824
+ position,
825
+ found,
826
+ expected,
827
+ snippet
828
+ );
829
+ }
118
830
 
119
- },{"ebnf":10}],2:[function(require,module,exports){
831
+ /**
832
+ * Detect error type from failureTree
833
+ * @private
834
+ */
835
+ static _detectErrorFromFailureTree(input, position, expected, found, failureTree) {
836
+ const beforeError = input.substring(0, position.offset);
837
+
838
+ // Helper to check if a name exists in the failure tree
839
+ const hasFailure = (name) => {
840
+ const search = (nodes) => {
841
+ if (!Array.isArray(nodes)) return false;
842
+ for (const node of nodes) {
843
+ if (node.name === name) return true;
844
+ if (node.children && search(node.children)) return true;
845
+ }
846
+ return false;
847
+ };
848
+ return search(failureTree);
849
+ };
850
+
851
+ // Helper to check if a name exists at top level only
852
+ const hasTopLevelFailure = (name) => {
853
+ if (!Array.isArray(failureTree)) return false;
854
+ return failureTree.some(node => node.name === name);
855
+ };
856
+
857
+ // PRIORITY 1: Check for unterminated string FIRST (before any balance checks)
858
+ // This must be checked early because unclosed strings can confuse other detections
859
+ if (this._hasUnterminatedString(input)) {
860
+ return {
861
+ code: "UNTERMINATED_STRING",
862
+ message: "Unterminated string literal.",
863
+ hint: "String literals must be enclosed in double quotes, e.g. \"hello world\".",
864
+ };
865
+ }
866
+
867
+ // PRIORITY 2: Check for bad number format
868
+ const badNum = this._findBadNumber(input);
869
+ if (badNum) {
870
+ return {
871
+ code: "BAD_NUMBER",
872
+ message: `Invalid number format: ${badNum}`,
873
+ hint: "Numbers must be valid integers or decimals, e.g. 42, 3.14, -5, 1.5e10.",
874
+ };
875
+ }
876
+
877
+ // PRIORITY 3: Check for token-level issues (extra characters after valid tokens)
878
+ // This must be checked before END_ARGUMENT/END_ARRAY to catch cases like WEDNESDAYY
879
+ const tokenError = this._detectExtraCharactersAfterToken(input, position, beforeError, found);
880
+ if (tokenError) {
881
+ return tokenError;
882
+ }
883
+
884
+ // PRIORITY 4: Check for specific parser contexts (function calls, arrays)
885
+ // But first check if we have bracket/paren imbalance that's more specific
886
+
887
+ // Check bracket balance first for extra brackets
888
+ const bracketBalance = this._checkBracketBalance(input);
889
+ if (bracketBalance < 0) {
890
+ // Extra closing bracket - this is more specific than function call context
891
+ return {
892
+ code: "UNMATCHED_BRACKET",
893
+ message: "Extra closing bracket detected.",
894
+ hint: "Every closing ']' must have a matching opening '['. Remove the extra closing bracket.",
895
+ };
896
+ }
897
+
898
+ // Check paren balance for certain cases
899
+ const parenBalance = this._checkParenBalance(input);
900
+ if (parenBalance < 0) {
901
+ // Extra closing paren - this is more specific than function call context
902
+ return {
903
+ code: "UNMATCHED_PAREN",
904
+ message: "Extra closing parenthesis detected.",
905
+ hint: "Every closing ')' must have a matching opening '('. Remove the extra closing parenthesis.",
906
+ };
907
+ }
908
+
909
+ // Check if parser was expecting END_ARGUMENT (closing paren for function)
910
+ // This indicates we're inside a function call context
911
+ if (hasFailure('END_ARGUMENT')) {
912
+ return {
913
+ code: "BAD_FUNCTION_CALL",
914
+ message: "Invalid function call syntax.",
915
+ hint: "Function calls must have matching parentheses, e.g. func() or func(arg1, arg2).",
916
+ };
917
+ }
918
+
919
+ // Check if parser was expecting END_ARRAY (closing bracket)
920
+ // This indicates we're inside an array context
921
+ if (hasFailure('END_ARRAY')) {
922
+ return {
923
+ code: "BAD_ARRAY_SYNTAX",
924
+ message: "Invalid array syntax.",
925
+ hint: "Arrays must be enclosed in brackets with comma-separated values, e.g. [1, 2, 3].",
926
+ };
927
+ }
928
+
929
+ // PRIORITY 5: Check for dangling operators
930
+
931
+ // Check for dangling logical operator (check input pattern directly)
932
+ if (found === "end of input") {
933
+ const trimmed = input.trim();
934
+ if (/&&\s*$/.test(trimmed)) {
935
+ return {
936
+ code: "DANGLING_LOGICAL_OPERATOR",
937
+ message: "Logical operator '&&' at end of expression.",
938
+ hint: "Logical operators (&&, ||, AND, OR) must be followed by an expression.",
939
+ };
940
+ }
941
+ if (/\|\|\s*$/.test(trimmed)) {
942
+ return {
943
+ code: "DANGLING_LOGICAL_OPERATOR",
944
+ message: "Logical operator '||' at end of expression.",
945
+ hint: "Logical operators (&&, ||, AND, OR) must be followed by an expression.",
946
+ };
947
+ }
948
+ if (/\bAND\b\s*$/i.test(trimmed)) {
949
+ return {
950
+ code: "DANGLING_LOGICAL_OPERATOR",
951
+ message: "Logical operator 'AND' at end of expression.",
952
+ hint: "Logical operators (&&, ||, AND, OR) must be followed by an expression.",
953
+ };
954
+ }
955
+ if (/\bOR\b\s*$/i.test(trimmed)) {
956
+ return {
957
+ code: "DANGLING_LOGICAL_OPERATOR",
958
+ message: "Logical operator 'OR' at end of expression.",
959
+ hint: "Logical operators (&&, ||, AND, OR) must be followed by an expression.",
960
+ };
961
+ }
962
+ }
963
+
964
+ // Check for BETWEEN - if we see BETWEEN in input with incomplete syntax
965
+ if (/\bBETWEEN\b/i.test(input)) {
966
+ // Check if BETWEEN is incomplete or missing AND
967
+ if (found === "end of input" || /\bBETWEEN\s+\d+\s*$/i.test(beforeError.trim())) {
968
+ return {
969
+ code: "BAD_BETWEEN_SYNTAX",
970
+ message: "Invalid BETWEEN syntax.",
971
+ hint: "BETWEEN requires two values: 'expr BETWEEN value1 AND value2' or 'expr BETWEEN value1-value2'.",
972
+ };
973
+ }
974
+ }
975
+
976
+ // Check if parser expected BEGIN_ARGUMENT - could be missing RHS after operator
977
+ if (hasTopLevelFailure('BEGIN_ARGUMENT')) {
978
+ const trimmed = beforeError.trim();
979
+ // If found is a comparison operator, this is likely missing RHS
980
+ if (found.match(/^[><=!]/)) {
981
+ return {
982
+ code: "MISSING_RHS_AFTER_OPERATOR",
983
+ message: `Expected a value after '${found}'.`,
984
+ hint: "Comparison operators must be followed by a value, e.g. temp > 25, name == \"bob\".",
985
+ };
986
+ }
987
+
988
+ // Also check if beforeError ends with comparison operator
989
+ const compOps = ['>=', '<=', '==', '!=', '>', '<', '=', '==~', '!=~'];
990
+ for (const op of compOps) {
991
+ if (trimmed.endsWith(op)) {
992
+ return {
993
+ code: "MISSING_RHS_AFTER_OPERATOR",
994
+ message: `Expected a value after '${op}'.`,
995
+ hint: "Comparison operators must be followed by a value, e.g. temp > 25, name == \"bob\".",
996
+ };
997
+ }
998
+ }
999
+ }
1000
+
1001
+ // Check for value/result expected (could be missing RHS)
1002
+ if ((hasFailure('value') || hasFailure('result') || hasFailure('simple_result')) && found === "end of input") {
1003
+ // Check for comparison operator
1004
+ const trimmed = beforeError.trim();
1005
+ const compOps = ['>=', '<=', '==', '!=', '>', '<', '=', '==~', '!=~'];
1006
+
1007
+ for (const op of compOps) {
1008
+ if (trimmed.endsWith(op)) {
1009
+ return {
1010
+ code: "MISSING_RHS_AFTER_OPERATOR",
1011
+ message: `Expected a value after '${op}'.`,
1012
+ hint: "Comparison operators must be followed by a value, e.g. temp > 25, name == \"bob\".",
1013
+ };
1014
+ }
1015
+ }
1016
+ }
1017
+
1018
+ // PRIORITY 6: Final fallback - general balance checks for unclosed parens/brackets
1019
+ // (Note: extra closing parens/brackets were already checked in PRIORITY 4)
1020
+
1021
+ // Check for unclosed parentheses (only if not already handled)
1022
+ if (parenBalance > 0 && found === "end of input") {
1023
+ // Check if this looks like a function call context
1024
+ const looksLikeFunction = /[a-zA-Z_][a-zA-Z0-9_]*\s*\([^)]*$/.test(input);
1025
+ const message = looksLikeFunction
1026
+ ? "Unclosed parenthesis in function call."
1027
+ : "Unclosed parenthesis detected.";
1028
+
1029
+ return {
1030
+ code: "UNMATCHED_PAREN",
1031
+ message: message,
1032
+ hint: "Every opening '(' must have a matching closing ')'. Check your expression for missing closing parentheses.",
1033
+ };
1034
+ }
1035
+
1036
+ // Check for unclosed brackets (only if not already handled)
1037
+ if (bracketBalance > 0 && found === "end of input") {
1038
+ // Check if this looks like an array context
1039
+ const looksLikeArray = /\[[^\]]*$/.test(input);
1040
+ const message = looksLikeArray
1041
+ ? "Unclosed bracket in array."
1042
+ : "Unclosed bracket detected.";
1043
+
1044
+ return {
1045
+ code: "UNMATCHED_BRACKET",
1046
+ message: message,
1047
+ hint: "Every opening '[' must have a matching closing ']'. Check your array syntax.",
1048
+ };
1049
+ }
1050
+
1051
+ // Default to unexpected token
1052
+ const foundDesc = found === "end of input" ? "end of input" : `'${found}'`;
1053
+ return {
1054
+ code: "UNEXPECTED_TOKEN",
1055
+ message: `Unexpected ${foundDesc} at position ${position.offset}.`,
1056
+ hint: "Check your syntax. Common issues include missing operators, invalid characters, or malformed expressions.",
1057
+ };
1058
+ }
1059
+
1060
+ /**
1061
+ * Detect extra characters after a valid token (e.g., WEDNESDAYY has extra Y after WEDNESDAY)
1062
+ * @private
1063
+ */
1064
+ static _detectExtraCharactersAfterToken(input, position, beforeError, found) {
1065
+ // First, check for invalid day of week patterns
1066
+ // Pattern: "ON" followed by partial day name
1067
+ const onMatch = /\bON\s+(\w+)$/i.exec(beforeError);
1068
+ if (onMatch) {
1069
+ const partial = onMatch[1].toUpperCase();
1070
+ // Check if this looks like a partial day name or invalid day
1071
+ // Note: THUR is not included because it's not actually supported by the parser
1072
+ const validDays = ['MONDAY', 'MON', 'TUESDAY', 'TUE', 'WEDNESDAY', 'WED',
1073
+ 'THURSDAY', 'THU', 'FRIDAY', 'FRI', 'SATURDAY',
1074
+ 'SAT', 'SUNDAY', 'SUN'];
1075
+
1076
+ if (!validDays.includes(partial)) {
1077
+ // This might be part of an invalid day name
1078
+ // Combine with found to get the full attempted day name if found is alphabetic
1079
+ const fullAttempt = partial + (found && found !== "end of input" && found.match(/^[A-Z]/i) ? found : "");
1080
+
1081
+ // Check if it looks like a misspelled day
1082
+ const possibleMisspelling = validDays.some(day => {
1083
+ // Check for similarity (starts with same letter, length close)
1084
+ return day.startsWith(partial.charAt(0)) &&
1085
+ Math.abs(day.length - fullAttempt.length) <= 3;
1086
+ });
1087
+
1088
+ if (possibleMisspelling || fullAttempt.length >= 3) {
1089
+ return {
1090
+ code: "BAD_DOW",
1091
+ message: `Invalid day of week: ${fullAttempt}`,
1092
+ hint: "Valid days are: MONDAY/MON, TUESDAY/TUE, WEDNESDAY/WED, THURSDAY/THU, FRIDAY/FRI, SATURDAY/SAT, SUNDAY/SUN.",
1093
+ };
1094
+ }
1095
+ }
1096
+ }
1097
+
1098
+ // Check for time of day patterns right before error
1099
+ const todMatch = /(\d{1,2}):(-?\d{1,2})?$/.exec(beforeError);
1100
+ if (todMatch || (beforeError.match(/\d+:$/) && found && found.match(/^-?\d+/))) {
1101
+ // Looks like we're in a time of day context
1102
+ // Extract the full time attempt
1103
+ let fullTime = todMatch ? todMatch[0] : '';
1104
+ if (!todMatch && beforeError.match(/\d+:$/)) {
1105
+ // We have a number: before and digits after
1106
+ const num = beforeError.match(/\d+:$/)[0];
1107
+ const minutes = found.match(/^-?\d+/)[0];
1108
+ fullTime = num + minutes;
1109
+ }
1110
+
1111
+ // Check if it's a bad time
1112
+ const timePattern = /(\d{1,2}):(-?\d{1,2})/;
1113
+ const timeMatch = timePattern.exec(fullTime);
1114
+ if (timeMatch) {
1115
+ const hours = parseInt(timeMatch[1], 10);
1116
+ const minutes = parseInt(timeMatch[2], 10);
1117
+ if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
1118
+ return {
1119
+ code: "BAD_TOD",
1120
+ message: `Invalid time of day: ${timeMatch[0]}`,
1121
+ hint: "Time of day must be in HH:MM format with hours 0-23 and minutes 0-59, e.g. 08:30, 14:00, 23:59.",
1122
+ };
1123
+ }
1124
+ }
1125
+ }
1126
+
1127
+ // List of known valid tokens (keywords, days of week, etc.)
1128
+ const validTokens = [
1129
+ // Days of week
1130
+ 'MONDAY', 'MON', 'TUESDAY', 'TUE', 'WEDNESDAY', 'WED',
1131
+ 'THURSDAY', 'THU', 'THUR', 'FRIDAY', 'FRI', 'SATURDAY',
1132
+ 'SAT', 'SUNDAY', 'SUN',
1133
+ // Keywords
1134
+ 'BETWEEN', 'AND', 'OR', 'NOT', 'IN', 'ON'
1135
+ ];
1136
+
1137
+ // Check if any valid token appears right before the error position
1138
+ for (const token of validTokens) {
1139
+ if (beforeError.toUpperCase().endsWith(token)) {
1140
+ // Found a valid token right before error - check if we have extra characters
1141
+ if (found && found.length > 0 && found !== "end of input" && found.match(/^[A-Z]/i)) {
1142
+ // We have alphabetic characters after a valid token
1143
+ const extraChars = found.charAt(0);
1144
+ return {
1145
+ code: "UNEXPECTED_TOKEN",
1146
+ message: `Unexpected characters after '${token}'. Remove the extra '${extraChars}'.`,
1147
+ hint: `'${token}' is a valid keyword. Remove any extra characters that follow it.`,
1148
+ };
1149
+ }
1150
+ }
1151
+ }
1152
+
1153
+ return null;
1154
+ }
1155
+
1156
+ /**
1157
+ * Detect the error pattern in the input (fallback for non-ParsingError cases)
1158
+ * @private
1159
+ * @param {string} input - The input to analyze
1160
+ * @param {Object} position - Position information
1161
+ * @param {string} snippet - Code snippet
1162
+ * @returns {Object} Error information
1163
+ */
1164
+ static _detectErrorPattern(input, position, snippet) {
1165
+ // Check for unterminated string first (this affects other checks)
1166
+ if (this._hasUnterminatedString(input)) {
1167
+ return {
1168
+ code: "UNTERMINATED_STRING",
1169
+ message: "Unterminated string literal.",
1170
+ hint: "String literals must be enclosed in double quotes, e.g. \"hello world\".",
1171
+ found: "end of input",
1172
+ expected: ["\""]
1173
+ };
1174
+ }
1175
+
1176
+ // Check for dangling logical operators
1177
+ const danglingOp = this._findDanglingLogicalOperator(input);
1178
+ if (danglingOp) {
1179
+ return {
1180
+ code: "DANGLING_LOGICAL_OPERATOR",
1181
+ message: `Logical operator '${danglingOp}' at end of expression.`,
1182
+ hint: "Logical operators (&&, ||, AND, OR) must be followed by an expression.",
1183
+ found: danglingOp,
1184
+ expected: ["expression", "function call", "value"]
1185
+ };
1186
+ }
1187
+
1188
+ // Check for missing RHS after comparison operator
1189
+ const danglingCompOp = this._findDanglingComparisonOperator(input);
1190
+ if (danglingCompOp) {
1191
+ return {
1192
+ code: "MISSING_RHS_AFTER_OPERATOR",
1193
+ message: `Expected a value after '${danglingCompOp}'.`,
1194
+ hint: "Comparison operators must be followed by a value, e.g. temp > 25, name == \"bob\".",
1195
+ found: "end of input",
1196
+ expected: ["value", "function call", "number", "string"]
1197
+ };
1198
+ }
1199
+
1200
+ // Check for bad BETWEEN syntax
1201
+ if (this._hasBadBetweenSyntax(input)) {
1202
+ return {
1203
+ code: "BAD_BETWEEN_SYNTAX",
1204
+ message: "Invalid BETWEEN syntax.",
1205
+ hint: "BETWEEN requires two values: 'expr BETWEEN value1 AND value2' or 'expr BETWEEN value1-value2'.",
1206
+ found: "incomplete BETWEEN expression",
1207
+ expected: ["BETWEEN value1 AND value2"]
1208
+ };
1209
+ }
1210
+
1211
+ // Check for bad time of day (TOD) format
1212
+ const badTod = this._findBadTimeOfDay(input);
1213
+ if (badTod) {
1214
+ return {
1215
+ code: "BAD_TOD",
1216
+ message: `Invalid time of day: ${badTod.value}`,
1217
+ hint: "Time of day must be in HH:MM format with hours 0-23 and minutes 0-59, e.g. 08:30, 14:00, 23:59.",
1218
+ found: badTod.value,
1219
+ expected: ["HH:MM"]
1220
+ };
1221
+ }
1222
+
1223
+ // Check for bad day of week (DOW)
1224
+ const badDow = this._findBadDayOfWeek(input);
1225
+ if (badDow) {
1226
+ return {
1227
+ code: "BAD_DOW",
1228
+ message: `Invalid day of week: ${badDow}`,
1229
+ hint: "Valid days are: MONDAY/MON, TUESDAY/TUE, WEDNESDAY/WED, THURSDAY/THU, FRIDAY/FRI, SATURDAY/SAT, SUNDAY/SUN.",
1230
+ found: badDow,
1231
+ expected: ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"]
1232
+ };
1233
+ }
1234
+
1235
+ // Check for bad number format
1236
+ const badNumber = this._findBadNumber(input);
1237
+ if (badNumber) {
1238
+ return {
1239
+ code: "BAD_NUMBER",
1240
+ message: `Invalid number format: ${badNumber}`,
1241
+ hint: "Numbers must be valid integers or decimals, e.g. 42, 3.14, -5, 1.5e10.",
1242
+ found: badNumber,
1243
+ expected: ["valid number"]
1244
+ };
1245
+ }
1246
+
1247
+ // Check for bad function call syntax (more specific than general paren check)
1248
+ if (this._hasBadFunctionCall(input)) {
1249
+ return {
1250
+ code: "BAD_FUNCTION_CALL",
1251
+ message: "Invalid function call syntax.",
1252
+ hint: "Function calls must have matching parentheses, e.g. func() or func(arg1, arg2).",
1253
+ found: "incomplete function call",
1254
+ expected: [")"]
1255
+ };
1256
+ }
1257
+
1258
+ // Check for bad array syntax (more specific than general bracket check)
1259
+ if (this._hasBadArraySyntax(input)) {
1260
+ return {
1261
+ code: "BAD_ARRAY_SYNTAX",
1262
+ message: "Invalid array syntax.",
1263
+ hint: "Arrays must be enclosed in brackets with comma-separated values, e.g. [1, 2, 3].",
1264
+ found: "incomplete array",
1265
+ expected: ["]", ","]
1266
+ };
1267
+ }
1268
+
1269
+ // Check for unmatched parentheses (general check)
1270
+ const parenBalance = this._checkParenBalance(input);
1271
+ if (parenBalance > 0) {
1272
+ return {
1273
+ code: "UNMATCHED_PAREN",
1274
+ message: "Unclosed parenthesis detected.",
1275
+ hint: "Every opening '(' must have a matching closing ')'. Check your expression for missing closing parentheses.",
1276
+ found: "end of input",
1277
+ expected: [")"]
1278
+ };
1279
+ } else if (parenBalance < 0) {
1280
+ const closeIndex = this._findExtraCloseParen(input);
1281
+ return {
1282
+ code: "UNMATCHED_PAREN",
1283
+ message: "Extra closing parenthesis detected.",
1284
+ hint: "Every closing ')' must have a matching opening '('. Remove the extra closing parenthesis.",
1285
+ found: ")",
1286
+ expected: ["expression", "operator"]
1287
+ };
1288
+ }
1289
+
1290
+ // Check for unmatched brackets (general check)
1291
+ const bracketBalance = this._checkBracketBalance(input);
1292
+ if (bracketBalance > 0) {
1293
+ return {
1294
+ code: "UNMATCHED_BRACKET",
1295
+ message: "Unclosed bracket detected.",
1296
+ hint: "Every opening '[' must have a matching closing ']'. Check your array syntax.",
1297
+ found: "end of input",
1298
+ expected: ["]"]
1299
+ };
1300
+ } else if (bracketBalance < 0) {
1301
+ return {
1302
+ code: "UNMATCHED_BRACKET",
1303
+ message: "Extra closing bracket detected.",
1304
+ hint: "Every closing ']' must have a matching opening '['. Remove the extra closing bracket.",
1305
+ found: "]",
1306
+ expected: ["expression", "operator"]
1307
+ };
1308
+ }
1309
+
1310
+ // Check for multiple expressions without logical operators
1311
+ if (this._hasMultipleExpressionsWithoutOperator(input)) {
1312
+ return {
1313
+ code: "UNEXPECTED_TOKEN",
1314
+ message: "Multiple expressions without logical operator.",
1315
+ hint: "Use logical operators (&&, ||, AND, OR) to combine multiple expressions.",
1316
+ found: "expression",
1317
+ expected: ["&&", "||", "AND", "OR"]
1318
+ };
1319
+ }
1320
+
1321
+ // Default to unexpected token
1322
+ const found = this._getLastToken(input) || "unknown token";
1323
+ return {
1324
+ code: "UNEXPECTED_TOKEN",
1325
+ message: `Unexpected token: ${found}`,
1326
+ hint: "Check your syntax. Common issues include missing operators, invalid characters, or malformed expressions.",
1327
+ found: found,
1328
+ expected: ["valid expression"]
1329
+ };
1330
+ }
1331
+
1332
+ /**
1333
+ * Check if string is properly terminated
1334
+ * @private
1335
+ * @param {string} input - Input to check
1336
+ * @returns {boolean} True if string is unterminated
1337
+ */
1338
+ static _hasUnterminatedString(input) {
1339
+ let inString = false;
1340
+ let i = 0;
1341
+ while (i < input.length) {
1342
+ const char = input[i];
1343
+
1344
+ if (char === '"') {
1345
+ if (!inString) {
1346
+ inString = true;
1347
+ } else {
1348
+ // Check if this quote is escaped by counting preceding backslashes
1349
+ let backslashCount = 0;
1350
+ let j = i - 1;
1351
+ while (j >= 0 && input[j] === '\\') {
1352
+ backslashCount++;
1353
+ j--;
1354
+ }
1355
+ // If even number of backslashes (including 0), the quote is not escaped
1356
+ if (backslashCount % 2 === 0) {
1357
+ inString = false;
1358
+ }
1359
+ }
1360
+ }
1361
+ i++;
1362
+ }
1363
+ return inString;
1364
+ }
1365
+
1366
+ /**
1367
+ * Check parenthesis balance
1368
+ * @private
1369
+ * @param {string} input - Input to check
1370
+ * @returns {number} Balance (positive = unclosed, negative = extra closing)
1371
+ */
1372
+ static _checkParenBalance(input) {
1373
+ let balance = 0;
1374
+ let inString = false;
1375
+ let i = 0;
1376
+ while (i < input.length) {
1377
+ const char = input[i];
1378
+
1379
+ if (char === '"') {
1380
+ if (!inString) {
1381
+ inString = true;
1382
+ } else {
1383
+ // Check if this quote is escaped
1384
+ let backslashCount = 0;
1385
+ let j = i - 1;
1386
+ while (j >= 0 && input[j] === '\\') {
1387
+ backslashCount++;
1388
+ j--;
1389
+ }
1390
+ if (backslashCount % 2 === 0) {
1391
+ inString = false;
1392
+ }
1393
+ }
1394
+ }
1395
+
1396
+ // Only count parentheses outside of strings
1397
+ if (!inString) {
1398
+ if (char === '(') balance++;
1399
+ else if (char === ')') balance--;
1400
+ }
1401
+ i++;
1402
+ }
1403
+ return balance;
1404
+ }
1405
+
1406
+ /**
1407
+ * Find position of extra closing paren
1408
+ * @private
1409
+ */
1410
+ static _findExtraCloseParen(input) {
1411
+ let balance = 0;
1412
+ let inString = false;
1413
+ let i = 0;
1414
+ while (i < input.length) {
1415
+ const char = input[i];
1416
+
1417
+ if (char === '"') {
1418
+ if (!inString) {
1419
+ inString = true;
1420
+ } else {
1421
+ // Check if this quote is escaped
1422
+ let backslashCount = 0;
1423
+ let j = i - 1;
1424
+ while (j >= 0 && input[j] === '\\') {
1425
+ backslashCount++;
1426
+ j--;
1427
+ }
1428
+ if (backslashCount % 2 === 0) {
1429
+ inString = false;
1430
+ }
1431
+ }
1432
+ }
1433
+
1434
+ if (!inString) {
1435
+ if (char === '(') balance++;
1436
+ else if (char === ')') {
1437
+ balance--;
1438
+ if (balance < 0) return i;
1439
+ }
1440
+ }
1441
+ i++;
1442
+ }
1443
+ return -1;
1444
+ }
1445
+
1446
+ /**
1447
+ * Check bracket balance
1448
+ * @private
1449
+ * @param {string} input - Input to check
1450
+ * @returns {number} Balance (positive = unclosed, negative = extra closing)
1451
+ */
1452
+ static _checkBracketBalance(input) {
1453
+ let balance = 0;
1454
+ let inString = false;
1455
+ let i = 0;
1456
+ while (i < input.length) {
1457
+ const char = input[i];
1458
+
1459
+ if (char === '"') {
1460
+ if (!inString) {
1461
+ inString = true;
1462
+ } else {
1463
+ // Check if this quote is escaped
1464
+ let backslashCount = 0;
1465
+ let j = i - 1;
1466
+ while (j >= 0 && input[j] === '\\') {
1467
+ backslashCount++;
1468
+ j--;
1469
+ }
1470
+ if (backslashCount % 2 === 0) {
1471
+ inString = false;
1472
+ }
1473
+ }
1474
+ }
1475
+
1476
+ if (!inString) {
1477
+ if (char === '[') balance++;
1478
+ else if (char === ']') balance--;
1479
+ }
1480
+ i++;
1481
+ }
1482
+ return balance;
1483
+ }
1484
+
1485
+ static _findDanglingLogicalOperator(input) {
1486
+ const trimmed = input.trim();
1487
+ const logicalOps = [
1488
+ { pattern: /&&\s*$/, op: '&&' },
1489
+ { pattern: /\|\|\s*$/, op: '||' },
1490
+ { pattern: /\bAND\b\s*$/i, op: 'AND' },
1491
+ { pattern: /\bOR\b\s*$/i, op: 'OR' }
1492
+ ];
1493
+
1494
+ for (const { pattern, op } of logicalOps) {
1495
+ if (pattern.test(trimmed)) {
1496
+ return op;
1497
+ }
1498
+ }
1499
+ return null;
1500
+ }
1501
+
1502
+ static _findDanglingComparisonOperator(input) {
1503
+ const trimmed = input.trim();
1504
+ const compOps = ['>=', '<=', '==', '!=', '>', '<', '=', '==~', '!=~'];
1505
+
1506
+ for (const op of compOps) {
1507
+ const pattern = new RegExp(`${this._escapeRegex(op)}\\s*$`);
1508
+ if (pattern.test(trimmed)) {
1509
+ return op;
1510
+ }
1511
+ }
1512
+ return null;
1513
+ }
1514
+
1515
+ static _hasBadBetweenSyntax(input) {
1516
+ // Check if BETWEEN keyword exists but not followed by proper syntax
1517
+ if (/\bBETWEEN\b/i.test(input)) {
1518
+ // Check for valid BETWEEN patterns
1519
+ const hasValidBetweenAnd = /\bBETWEEN\s+\S+\s+AND\s+\S+/i.test(input);
1520
+ const hasValidBetweenDash = /\bBETWEEN\s+\S+-\s*\S+/i.test(input);
1521
+ return !hasValidBetweenAnd && !hasValidBetweenDash;
1522
+ }
1523
+ return false;
1524
+ }
1525
+
1526
+ static _hasBadFunctionCall(input) {
1527
+ // Only return true if:
1528
+ // 1. We have a function pattern (identifier followed by open paren)
1529
+ // 2. The parentheses are unbalanced
1530
+ // 3. There are NO array brackets (so we don't confuse arrays with function calls)
1531
+ // 4. The imbalance is specifically in a function call context
1532
+
1533
+ const funcPattern = /[a-zA-Z0-9]+\s*\(/;
1534
+ const hasArrayBracket = /\[/.test(input);
1535
+
1536
+ if (!funcPattern.test(input) || hasArrayBracket) {
1537
+ return false;
1538
+ }
1539
+
1540
+ const parenBalance = this._checkParenBalance(input);
1541
+ if (parenBalance > 0) {
1542
+ // We have unclosed parens in what looks like a function call
1543
+ // Make sure it's not just a general parenthesis expression
1544
+ // by checking if the first occurrence of ( is preceded by an identifier
1545
+ const firstParenMatch = input.match(/^[^(]*([a-zA-Z0-9]+)\s*\(/);
1546
+ if (firstParenMatch) {
1547
+ return true;
1548
+ }
1549
+ }
1550
+
1551
+ return false;
1552
+ }
1553
+
1554
+ static _hasBadArraySyntax(input) {
1555
+ // Only return true if:
1556
+ // 1. We have an array bracket [
1557
+ // 2. The brackets are unbalanced
1558
+ // 3. It's clearly in an array context (inside function args or standalone)
1559
+
1560
+ if (!/\[/.test(input)) {
1561
+ return false;
1562
+ }
1563
+
1564
+ const bracketBalance = this._checkBracketBalance(input);
1565
+ if (bracketBalance > 0) {
1566
+ // We have unclosed brackets - this is likely an array issue
1567
+ return true;
1568
+ }
1569
+
1570
+ return false;
1571
+ }
1572
+
1573
+ static _hasMultipleExpressionsWithoutOperator(input) {
1574
+ // Detect patterns like "A() B()" without && or || between them
1575
+ // Check if there's no logical operator between function calls
1576
+ const betweenPattern = /\)\s+(?!&&|\|\||AND|OR|BETWEEN)/i;
1577
+ if (betweenPattern.test(input)) {
1578
+ // Make sure the next thing after ) is an identifier (suggesting another expression)
1579
+ return /\)\s+[a-zA-Z]/.test(input);
1580
+ }
1581
+ return false;
1582
+ }
1583
+
1584
+ static _getLastToken(input) {
1585
+ const trimmed = input.trim();
1586
+ // Get last 20 characters or the whole string if shorter
1587
+ const lastPart = trimmed.substring(Math.max(0, trimmed.length - 20));
1588
+ return lastPart;
1589
+ }
1590
+
1591
+ /**
1592
+ * Check for invalid time of day format (e.g., 25:00, 12:60)
1593
+ * @private
1594
+ */
1595
+ static _findBadTimeOfDay(input) {
1596
+ // Look for patterns like HH:MM where hours or minutes are out of range or negative
1597
+ const todPattern = /\b(-?\d{1,2}):(-?\d{1,2})\b/g;
1598
+ let match;
1599
+
1600
+ while ((match = todPattern.exec(input)) !== null) {
1601
+ const hours = parseInt(match[1], 10);
1602
+ const minutes = parseInt(match[2], 10);
1603
+ const fullMatch = match[0];
1604
+
1605
+ // Check if hours or minutes are out of valid range or negative
1606
+ if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
1607
+ return { value: fullMatch, hours, minutes };
1608
+ }
1609
+ }
1610
+ return null;
1611
+ }
1612
+
1613
+ /**
1614
+ * Check for invalid day of week
1615
+ * @private
1616
+ */
1617
+ static _findBadDayOfWeek(input) {
1618
+ // Look for ON keyword followed by potential day name
1619
+ const onPattern = /\bON\s+([A-Z]+)/i;
1620
+ const match = onPattern.exec(input);
1621
+
1622
+ if (match) {
1623
+ const dayCandidate = match[1].toUpperCase();
1624
+ const validDays = ['MONDAY', 'MON', 'TUESDAY', 'TUE', 'WEDNESDAY', 'WED',
1625
+ 'THURSDAY', 'THU', 'THUR', 'FRIDAY', 'FRI', 'SATURDAY',
1626
+ 'SAT', 'SUNDAY', 'SUN'];
1627
+
1628
+ if (!validDays.includes(dayCandidate)) {
1629
+ return dayCandidate;
1630
+ }
1631
+ }
1632
+ return null;
1633
+ }
1634
+
1635
+ /**
1636
+ * Check for invalid number format (e.g., 1.2.3, 123abc)
1637
+ * @private
1638
+ */
1639
+ static _findBadNumber(input) {
1640
+ // Look for malformed numbers - multiple decimal points or numbers followed by letters
1641
+ const badNumberPatterns = [
1642
+ /\b\d+\.\d+\.\d+/, // Multiple decimal points like 1.2.3
1643
+ /\b\d+[a-zA-Z]+\d*/, // Numbers with letters mixed in like 123abc
1644
+ /\b\d+\.\d+[a-zA-Z]+/ // Decimals with letters like 1.5abc
1645
+ ];
1646
+
1647
+ for (const pattern of badNumberPatterns) {
1648
+ const match = pattern.exec(input);
1649
+ if (match) {
1650
+ return match[0];
1651
+ }
1652
+ }
1653
+ return null;
1654
+ }
1655
+
1656
+ static _escapeRegex(str) {
1657
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1658
+ }
1659
+ }
1660
+
1661
+ module.exports = { ErrorAnalyzer };
1662
+
1663
+ },{"./RuleParseError":4,"ebnf":13}],4:[function(require,module,exports){
1664
+ /**
1665
+ * Represents a user-friendly rule parsing error with error codes
1666
+ */
1667
+ class RuleParseError extends Error {
1668
+ /**
1669
+ * Create a RuleParseError
1670
+ * @param {string} code - Error code for programmatic handling
1671
+ * @param {string} message - Human-readable error message
1672
+ * @param {string} hint - Helpful suggestion for fixing the error
1673
+ * @param {Object} position - Position information with line, column, and offset
1674
+ * @param {string} found - What was found at error position
1675
+ * @param {Array<string>} expected - What was expected
1676
+ * @param {string} snippet - Code snippet showing error location
1677
+ */
1678
+ constructor(code, message, hint, position, found, expected, snippet) {
1679
+ super(message);
1680
+ this.name = 'RuleParseError';
1681
+ this.code = code;
1682
+ this.hint = hint;
1683
+ this.line = position.line;
1684
+ this.column = position.column;
1685
+ this.offset = position.offset;
1686
+ this.found = found;
1687
+ this.expected = expected;
1688
+ this.snippet = snippet;
1689
+
1690
+ // Maintain proper prototype chain for instanceof checks
1691
+ Object.setPrototypeOf(this, RuleParseError.prototype);
1692
+ }
1693
+
1694
+ toString() {
1695
+ let msg = `${this.name} [${this.code}]: ${this.message}\n`;
1696
+ msg += ` at line ${this.line}, column ${this.column} (offset ${this.offset})\n`;
1697
+ if (this.snippet) {
1698
+ msg += ` ${this.snippet}\n`;
1699
+ }
1700
+ if (this.hint) {
1701
+ msg += ` Hint: ${this.hint}\n`;
1702
+ }
1703
+ if (this.found) {
1704
+ msg += ` Found: ${this.found}\n`;
1705
+ }
1706
+ if (this.expected && this.expected.length) {
1707
+ msg += ` Expected: ${this.expected.join(', ')}`;
1708
+ }
1709
+ return msg;
1710
+ }
1711
+
1712
+ toJSON() {
1713
+ return {
1714
+ code: this.code,
1715
+ message: this.message,
1716
+ hint: this.hint,
1717
+ line: this.line,
1718
+ column: this.column,
1719
+ offset: this.offset,
1720
+ found: this.found,
1721
+ expected: this.expected,
1722
+ snippet: this.snippet
1723
+ };
1724
+ }
1725
+ }
1726
+
1727
+ module.exports = RuleParseError;
1728
+
1729
+ },{}],5:[function(require,module,exports){
120
1730
  "use strict";
121
1731
  // https://en.wikipedia.org/wiki/Backus%E2%80%93Naur_Form
122
1732
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -365,7 +1975,7 @@ var BNF;
365
1975
  })(BNF || (BNF = {}));
366
1976
  exports.default = BNF;
367
1977
 
368
- },{"../Parser":6,"../SemanticHelpers":8}],3:[function(require,module,exports){
1978
+ },{"../Parser":9,"../SemanticHelpers":11}],6:[function(require,module,exports){
369
1979
  "use strict";
370
1980
  // https://www.w3.org/TR/REC-xml/#NT-Name
371
1981
  // http://www.bottlecaps.de/rr/ui
@@ -820,7 +2430,7 @@ var BNF;
820
2430
  })(BNF || (BNF = {}));
821
2431
  exports.default = BNF;
822
2432
 
823
- },{"../Parser":6,"../TokenError":9}],4:[function(require,module,exports){
2433
+ },{"../Parser":9,"../TokenError":12}],7:[function(require,module,exports){
824
2434
  "use strict";
825
2435
  // https://www.w3.org/TR/REC-xml/#NT-Name
826
2436
  // http://www.bottlecaps.de/rr/ui
@@ -1201,7 +2811,7 @@ var BNF;
1201
2811
  })(BNF || (BNF = {}));
1202
2812
  exports.default = BNF;
1203
2813
 
1204
- },{"../Parser":6}],5:[function(require,module,exports){
2814
+ },{"../Parser":9}],8:[function(require,module,exports){
1205
2815
  "use strict";
1206
2816
  Object.defineProperty(exports, "__esModule", { value: true });
1207
2817
  exports.Custom = exports.W3C = exports.BNF = void 0;
@@ -1212,7 +2822,7 @@ Object.defineProperty(exports, "W3C", { enumerable: true, get: function () { ret
1212
2822
  var Custom_1 = require("./Custom");
1213
2823
  Object.defineProperty(exports, "Custom", { enumerable: true, get: function () { return Custom_1.default; } });
1214
2824
 
1215
- },{"./BNF":2,"./Custom":3,"./W3CEBNF":4}],6:[function(require,module,exports){
2825
+ },{"./BNF":5,"./Custom":6,"./W3CEBNF":7}],9:[function(require,module,exports){
1216
2826
  "use strict";
1217
2827
  // https://www.ics.uci.edu/~pattis/ICS-33/lectures/ebnf.pdf
1218
2828
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -1964,7 +3574,7 @@ class Parser {
1964
3574
  exports.Parser = Parser;
1965
3575
  exports.default = Parser;
1966
3576
 
1967
- },{"./ParsingError":7,"./TokenError":9}],7:[function(require,module,exports){
3577
+ },{"./ParsingError":10,"./TokenError":12}],10:[function(require,module,exports){
1968
3578
  "use strict";
1969
3579
  Object.defineProperty(exports, "__esModule", { value: true });
1970
3580
  exports.ParsingError = void 0;
@@ -1990,7 +3600,7 @@ class ParsingError extends Error {
1990
3600
  }
1991
3601
  exports.ParsingError = ParsingError;
1992
3602
 
1993
- },{}],8:[function(require,module,exports){
3603
+ },{}],11:[function(require,module,exports){
1994
3604
  "use strict";
1995
3605
  Object.defineProperty(exports, "__esModule", { value: true });
1996
3606
  exports.findChildrenByType = findChildrenByType;
@@ -2001,7 +3611,7 @@ function findChildrenByType(token, type) {
2001
3611
  return token.children ? token.children.filter(x => x.type == type) : [];
2002
3612
  }
2003
3613
 
2004
- },{}],9:[function(require,module,exports){
3614
+ },{}],12:[function(require,module,exports){
2005
3615
  "use strict";
2006
3616
  Object.defineProperty(exports, "__esModule", { value: true });
2007
3617
  exports.TokenError = void 0;
@@ -2021,7 +3631,7 @@ class TokenError extends Error {
2021
3631
  }
2022
3632
  exports.TokenError = TokenError;
2023
3633
 
2024
- },{}],10:[function(require,module,exports){
3634
+ },{}],13:[function(require,module,exports){
2025
3635
  "use strict";
2026
3636
  Object.defineProperty(exports, "__esModule", { value: true });
2027
3637
  exports.ParsingError = exports.TokenError = exports.Parser = void 0;
@@ -2033,17 +3643,17 @@ var ParsingError_1 = require("./ParsingError");
2033
3643
  Object.defineProperty(exports, "ParsingError", { enumerable: true, get: function () { return ParsingError_1.ParsingError; } });
2034
3644
  exports.Grammars = require("./Grammars");
2035
3645
 
2036
- },{"./Grammars":5,"./Parser":6,"./ParsingError":7,"./TokenError":9}],11:[function(require,module,exports){
3646
+ },{"./Grammars":8,"./Parser":9,"./ParsingError":10,"./TokenError":12}],14:[function(require,module,exports){
2037
3647
  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"]]}]
2038
- },{}],12:[function(require,module,exports){
3648
+ },{}],15:[function(require,module,exports){
2039
3649
  module.exports = require('./RuleTemplater.production.js');
2040
- },{"./RuleTemplater.production.js":13}],13:[function(require,module,exports){
2041
- // Note: We import the internal RuleParser.ebnf to extend the grammar with template rules.
2042
- // This creates coupling to the internal structure of @halleyassist/rule-parser.
2043
- const RuleParserRules = require('@halleyassist/rule-parser/src/RuleParser.ebnf'),
2044
- TemplateGrammar = require('./RuleTemplate.production.ebnf.js'),
3650
+ },{"./RuleTemplater.production.js":16}],16:[function(require,module,exports){
3651
+ // Note: We are coupled closely with the ebnf grammar structure of rule-parser
3652
+ const TemplateGrammar = require('./RuleTemplate.production.ebnf.js'),
2045
3653
  TemplateFilters = require('./TemplateFilters'),
2046
- {Parser} = require('ebnf');
3654
+ RuleParser = require('@halleyassist/rule-parser'),
3655
+ RuleParserRules = RuleParser.ParserRules,
3656
+ {Parser} = require('ebnf');
2047
3657
 
2048
3658
  let ParserCache = null;
2049
3659
 
@@ -2109,7 +3719,7 @@ class RuleTemplate {
2109
3719
  ParserCache = new Parser(ParserRules, {debug: false})
2110
3720
  }
2111
3721
 
2112
- const ast = ParserCache.getAST(ruleTemplate.trim(), 'statement_main');
3722
+ const ast = RuleParser.toAst(ruleTemplate.trim(), ParserCache);
2113
3723
  return new RuleTemplate(ruleTemplate, ast);
2114
3724
  }
2115
3725
 
@@ -2445,7 +4055,7 @@ module.exports = RuleTemplate;
2445
4055
  module.exports.ParserRules = ParserRules;
2446
4056
  module.exports.VariableTypes = VariableTypes;
2447
4057
  module.exports.TemplateFilters = TemplateFilters;
2448
- },{"./RuleTemplate.production.ebnf.js":11,"./TemplateFilters":14,"@halleyassist/rule-parser/src/RuleParser.ebnf":1,"ebnf":10}],14:[function(require,module,exports){
4058
+ },{"./RuleTemplate.production.ebnf.js":14,"./TemplateFilters":17,"@halleyassist/rule-parser":2,"ebnf":13}],17:[function(require,module,exports){
2449
4059
  /*
2450
4060
  Template filters are functions that transform variable values.
2451
4061
  They are applied in the template syntax as ${variable|filter} or ${variable|filter1|filter2}
@@ -2509,5 +4119,5 @@ const TemplateFilters = {
2509
4119
  }
2510
4120
 
2511
4121
  module.exports = TemplateFilters;
2512
- },{}]},{},[12])(12)
4122
+ },{}]},{},[15])(15)
2513
4123
  });