@halleyassist/rule-parser 1.0.9 → 1.0.11
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/package.json +1 -1
- package/src/RuleParser.ebnf.js +12 -7
- package/src/RuleParser.js +113 -15
- package/src/RuleParser.production.ebnf.js +1 -1
- package/src/RuleParser.production.js +113 -15
package/package.json
CHANGED
package/src/RuleParser.ebnf.js
CHANGED
|
@@ -18,11 +18,12 @@ DIVIDE ::= "/"
|
|
|
18
18
|
MODULUS ::= "%"
|
|
19
19
|
DEFAULT_VAL ::= "??"
|
|
20
20
|
arithmetic_operator ::= PLUS | MINUS | MULTIPLY | DIVIDE | MODULUS | DEFAULT_VAL
|
|
21
|
-
|
|
21
|
+
arithmetic_operand ::= fcall | number_time | number
|
|
22
|
+
arithmetic_result ::= arithmetic_operand WS* arithmetic_operator WS* ( arithmetic_result | arithmetic_operand )
|
|
22
23
|
|
|
23
24
|
simple_result ::= fcall | value
|
|
24
25
|
result ::= arithmetic_result | simple_result
|
|
25
|
-
value ::= false | true | array | number_time | number | number_tod |
|
|
26
|
+
value ::= false | true | array | time_period | number_time | number | number_tod | string
|
|
26
27
|
BEGIN_ARRAY ::= WS* #x5B WS* /* [ left square bracket */
|
|
27
28
|
BEGIN_OBJECT ::= WS* #x7B WS* /* { left curly bracket */
|
|
28
29
|
END_ARRAY ::= WS* #x5D WS* /* ] right square bracket */
|
|
@@ -46,15 +47,17 @@ fname ::= [a-zA-z0-9]+
|
|
|
46
47
|
fcall ::= fname WS* BEGIN_ARGUMENT WS* arguments? END_ARGUMENT
|
|
47
48
|
|
|
48
49
|
between_number ||= (number_time | number) ((WS+ "AND" WS+) | (WS* "-" WS*)) (number_time | number)
|
|
50
|
+
between_number_time ||= number_time ((WS+ "AND" WS+) | (WS* "-" WS*)) number_time (WS+ dow_range)?
|
|
49
51
|
between_tod ||= number_tod ((WS+ "AND" WS+)) number_tod (WS+ dow_range)?
|
|
50
52
|
between ||= "BETWEEN" WS+ (between_number | between_tod)
|
|
51
|
-
dow ||= "MONDAY" | "TUESDAY" | "WEDNESDAY" | "THURSDAY" | "FRIDAY" | "SATURDAY" | "SUNDAY"
|
|
53
|
+
dow ||= "MONDAY" | "MON" | "TUESDAY" | "TUE" | "WEDNESDAY" | "WED" | "THURSDAY" | "THU" | "THUR" | "FRIDAY" | "FRI" | "SATURDAY" | "SAT" | "SUNDAY" | "SUN"
|
|
52
54
|
dow_range ||= "ON" WS+ dow (WS+ "TO" WS+ dow)?
|
|
53
|
-
|
|
55
|
+
between_time_only ||= "BETWEEN" WS+ between_number_time
|
|
54
56
|
between_tod_only ||= "BETWEEN" WS+ between_tod
|
|
55
57
|
|
|
56
58
|
AND ||= (WS* "&&" WS*) | (WS+ "AND" WS+)
|
|
57
59
|
OR ||= (WS* "||" WS*) | (WS+ "OR" WS+)
|
|
60
|
+
AGO ||= "AGO"
|
|
58
61
|
GT ::= ">"
|
|
59
62
|
LT ::= "<"
|
|
60
63
|
GTE ::= ">="
|
|
@@ -68,13 +71,15 @@ null ||= "null"
|
|
|
68
71
|
true ||= "TRUE"
|
|
69
72
|
array ::= BEGIN_ARRAY (value (VALUE_SEPARATOR value)*)? END_ARRAY
|
|
70
73
|
|
|
71
|
-
unit ||= "seconds" | "
|
|
74
|
+
unit ||= "seconds" | "minutes" | "hours" | "weeks" | "days" | "second" | "minute" | "week" | "hour" | "day" | "mins" | "min"
|
|
72
75
|
number ::= "-"? ([0-9]+) ("." [0-9]+)? ("e" ( "-" | "+" )? ("0" | [1-9] [0-9]*))?
|
|
73
76
|
number_time ::= number WS+ unit
|
|
74
77
|
number_tod ::= ([0-9]+) ":" ([0-9]+)
|
|
75
78
|
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
time_period_ago ||= number_time (WS+ number_time)* WS+ AGO
|
|
80
|
+
time_period_ago_between ||= number_time (WS+ number_time)* WS+ AGO WS+ between_tod_only
|
|
81
|
+
time_period_const ||= "today" | time_period_ago
|
|
82
|
+
time_period ::= time_period_ago_between | time_period_const | between_tod_only | between_time_only
|
|
78
83
|
|
|
79
84
|
string ::= '"' (([#x20-#x21] | [#x23-#x5B] | [#x5D-#xFFFF]) | #x5C (#x22 | #x5C | #x2F | #x62 | #x66 | #x6E | #x72 | #x74 | #x75 HEXDIG HEXDIG HEXDIG HEXDIG))* '"'
|
|
80
85
|
HEXDIG ::= [a-fA-F0-9]
|
package/src/RuleParser.js
CHANGED
|
@@ -30,6 +30,34 @@ const LogicalOperators = {
|
|
|
30
30
|
"OR": 'Or',
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// Map abbreviations to canonical uppercase full form
|
|
34
|
+
const DOW_MAP = {
|
|
35
|
+
'MON': 'MONDAY',
|
|
36
|
+
'TUE': 'TUESDAY',
|
|
37
|
+
'WED': 'WEDNESDAY',
|
|
38
|
+
'THU': 'THURSDAY',
|
|
39
|
+
'THUR': 'THURSDAY',
|
|
40
|
+
'FRI': 'FRIDAY',
|
|
41
|
+
'SAT': 'SATURDAY',
|
|
42
|
+
'SUN': 'SUNDAY',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Valid full day names
|
|
46
|
+
const VALID_DAYS = new Set(['MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY']);
|
|
47
|
+
|
|
48
|
+
const normalizeDow = (text) => {
|
|
49
|
+
const upper = text.toUpperCase();
|
|
50
|
+
// Check if it's an abbreviation first
|
|
51
|
+
if (upper in DOW_MAP) {
|
|
52
|
+
return DOW_MAP[upper];
|
|
53
|
+
}
|
|
54
|
+
// Otherwise, check if it's a valid full name
|
|
55
|
+
if (VALID_DAYS.has(upper)) {
|
|
56
|
+
return upper;
|
|
57
|
+
}
|
|
58
|
+
throw new Error(`Invalid day of week: ${text}`);
|
|
59
|
+
};
|
|
60
|
+
|
|
33
61
|
const Epsilon = 0.01
|
|
34
62
|
|
|
35
63
|
class RuleParser {
|
|
@@ -63,21 +91,19 @@ class RuleParser {
|
|
|
63
91
|
return ret
|
|
64
92
|
}
|
|
65
93
|
static _parseDowRange(dowRange) {
|
|
66
|
-
const dow = []
|
|
67
|
-
|
|
94
|
+
const dow = [];
|
|
68
95
|
// dow_range can have 1 or 2 children (single day or range)
|
|
69
96
|
if (dowRange.children.length === 1) {
|
|
70
97
|
// Single day: ON MONDAY
|
|
71
|
-
dow.push(dowRange.children[0].text
|
|
98
|
+
dow.push(normalizeDow(dowRange.children[0].text));
|
|
72
99
|
} else if (dowRange.children.length === 2) {
|
|
73
100
|
// Range: ON MONDAY TO WEDNESDAY
|
|
74
|
-
dow.push(dowRange.children[0].text
|
|
75
|
-
dow.push(dowRange.children[1].text
|
|
101
|
+
dow.push(normalizeDow(dowRange.children[0].text));
|
|
102
|
+
dow.push(normalizeDow(dowRange.children[1].text));
|
|
76
103
|
} else {
|
|
77
|
-
throw new Error(`Invalid dow_range with ${dowRange.children.length} children`)
|
|
104
|
+
throw new Error(`Invalid dow_range with ${dowRange.children.length} children`);
|
|
78
105
|
}
|
|
79
|
-
|
|
80
|
-
return dow
|
|
106
|
+
return dow;
|
|
81
107
|
}
|
|
82
108
|
static _addDowToTods(startTod, endTod, dowRange) {
|
|
83
109
|
if (dowRange && dowRange.type === 'dow_range') {
|
|
@@ -89,7 +115,44 @@ class RuleParser {
|
|
|
89
115
|
static _parseTimePeriod(tp){
|
|
90
116
|
switch(tp.type){
|
|
91
117
|
case 'time_period_const':
|
|
118
|
+
// Check if this is a time_period_ago (has children)
|
|
119
|
+
if (tp.children && tp.children.length > 0 && tp.children[0].type === 'time_period_ago') {
|
|
120
|
+
const timePeriodAgo = tp.children[0]
|
|
121
|
+
// Extract all number_time children and sum them up
|
|
122
|
+
let totalSeconds = 0
|
|
123
|
+
const components = []
|
|
124
|
+
for (const child of timePeriodAgo.children) {
|
|
125
|
+
if (child.type === 'number_time') {
|
|
126
|
+
const number = parseFloat(child.children[0].text)
|
|
127
|
+
const unit = child.children[1].text.toUpperCase()
|
|
128
|
+
components.push([number, unit])
|
|
129
|
+
// Parse the value to get seconds
|
|
130
|
+
totalSeconds += RuleParser.__parseValue(child)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// If there's only one component, use its number and unit
|
|
134
|
+
// Otherwise, use the total in seconds and "SECONDS" as the unit
|
|
135
|
+
if (components.length === 1) {
|
|
136
|
+
return ["TimePeriodConstAgo", components[0][0], components[0][1]]
|
|
137
|
+
} else {
|
|
138
|
+
return ["TimePeriodConstAgo", totalSeconds, "SECONDS"]
|
|
139
|
+
}
|
|
140
|
+
}
|
|
92
141
|
return ["TimePeriodConst", tp.text]
|
|
142
|
+
case 'time_period_ago_between': {
|
|
143
|
+
// time_period_ago_between has children[0] = number_time, children[1] = between_tod_only
|
|
144
|
+
const betweenTodOnly = tp.children[1]
|
|
145
|
+
const betweenTod = betweenTodOnly.children[0]
|
|
146
|
+
const startTod = RuleParser.__parseValue(betweenTod.children[0])
|
|
147
|
+
const endTod = RuleParser.__parseValue(betweenTod.children[1])
|
|
148
|
+
|
|
149
|
+
// Check if there's a dow_range at betweenTod.children[2]
|
|
150
|
+
if (betweenTod.children.length > 2) {
|
|
151
|
+
RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2])
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return ["TimePeriodBetween", startTod, endTod]
|
|
155
|
+
}
|
|
93
156
|
case 'between_tod_only': {
|
|
94
157
|
// between_tod_only has children[0] = between_tod node
|
|
95
158
|
const betweenTod = tp.children[0]
|
|
@@ -103,11 +166,19 @@ class RuleParser {
|
|
|
103
166
|
|
|
104
167
|
return ["TimePeriodBetween", startTod, endTod]
|
|
105
168
|
}
|
|
106
|
-
case '
|
|
107
|
-
//
|
|
108
|
-
const
|
|
109
|
-
const startValue = RuleParser.__parseValue(
|
|
110
|
-
const endValue = RuleParser.__parseValue(
|
|
169
|
+
case 'between_time_only': {
|
|
170
|
+
// between_time_only has children[0] = between_number_time node
|
|
171
|
+
const betweenNumberTime = tp.children[0]
|
|
172
|
+
const startValue = RuleParser.__parseValue(betweenNumberTime.children[0])
|
|
173
|
+
const endValue = RuleParser.__parseValue(betweenNumberTime.children[1])
|
|
174
|
+
|
|
175
|
+
// Check if there's a dow_range at betweenNumberTime.children[2]
|
|
176
|
+
// If DOW filters are provided, append them as additional parameters
|
|
177
|
+
if (betweenNumberTime.children.length > 2 && betweenNumberTime.children[2].type === 'dow_range') {
|
|
178
|
+
const dow = RuleParser._parseDowRange(betweenNumberTime.children[2])
|
|
179
|
+
// Append DOW as additional arguments: ["TimePeriodBetween", start, end, "MONDAY"] or ["TimePeriodBetween", start, end, "MONDAY", "FRIDAY"]
|
|
180
|
+
return ["TimePeriodBetween", startValue, endValue, ...dow]
|
|
181
|
+
}
|
|
111
182
|
|
|
112
183
|
return ["TimePeriodBetween", startValue, endValue]
|
|
113
184
|
}
|
|
@@ -200,15 +271,42 @@ class RuleParser {
|
|
|
200
271
|
}
|
|
201
272
|
return null
|
|
202
273
|
}
|
|
274
|
+
static _parseArithmeticOperand(operand){
|
|
275
|
+
assert(operand.children.length == 1)
|
|
276
|
+
const child = operand.children[0]
|
|
277
|
+
const type = child.type
|
|
278
|
+
switch(type){
|
|
279
|
+
case 'fcall':
|
|
280
|
+
return RuleParser._parseFcall(child)
|
|
281
|
+
case 'number':
|
|
282
|
+
return ['Value', parseFloat(child.text)]
|
|
283
|
+
case 'number_time':
|
|
284
|
+
return ['Value', RuleParser.__parseValue(child)]
|
|
285
|
+
}
|
|
286
|
+
throw new Error(`Unknown arithmetic operand type ${type}`)
|
|
287
|
+
}
|
|
203
288
|
static _parseArithmeticResult(result){
|
|
204
289
|
assert(result.children.length == 3)
|
|
205
|
-
const partA =
|
|
290
|
+
const partA = RuleParser._parseArithmeticOperand(result.children[0])
|
|
206
291
|
const operatorFn = ArithmeticOperators[result.children[1].text]
|
|
207
|
-
const partB =
|
|
292
|
+
const partB = RuleParser.__parseArithmeticResult(result, 2)
|
|
208
293
|
|
|
209
294
|
return [operatorFn, partA, partB]
|
|
210
295
|
}
|
|
211
296
|
|
|
297
|
+
static __parseArithmeticResult(result, idx){
|
|
298
|
+
const child = result.children[idx]
|
|
299
|
+
const type = child.type
|
|
300
|
+
switch(type){
|
|
301
|
+
case 'arithmetic_operand':
|
|
302
|
+
return RuleParser._parseArithmeticOperand(child)
|
|
303
|
+
case 'arithmetic_result':
|
|
304
|
+
return RuleParser._parseArithmeticResult(child)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
throw new Error(`Unknown arithmetic result node ${type}`)
|
|
308
|
+
}
|
|
309
|
+
|
|
212
310
|
static __parseResult(result, idx){
|
|
213
311
|
const child = result.children[idx]
|
|
214
312
|
const type = child.type
|
|
@@ -1 +1 @@
|
|
|
1
|
-
module.exports=[{"name":"statement_main","bnf":[["statement","EOF"]]},{"name":"logical_operator","bnf":[["AND"],["OR"]]},{"name":"%statement0","bnf":[["logical_operator","expression"]],"fragment":true},{"name":"statement","bnf":[["expression","%statement0*"]]},{"name":"expression","bnf":[["not_expression"],["standard_expression"],["parenthesis_expression"]]},{"name":"parenthesis_expression","bnf":[["BEGIN_PARENTHESIS","WS*","statement","WS*","END_PARENTHESIS"]]},{"name":"%not_expression1","bnf":[["result"],["parenthesis_expression"]],"fragment":true},{"name":"not_expression","bnf":[["NOT","%not_expression1"]]},{"name":"%%standard_expression23","bnf":[["WS*","eq_approx"]],"fragment":true},{"name":"%%standard_expression24","bnf":[["WS*","basic_rhs"]],"fragment":true},{"name":"%%%standard_expression256","bnf":[["WS+","IS"]],"fragment":true},{"name":"%%standard_expression25","bnf":[["%%%standard_expression256?","WS+","between"]],"fragment":true},{"name":"%standard_expression2","bnf":[["%%standard_expression23"],["%%standard_expression24"],["%%standard_expression25"]],"fragment":true},{"name":"standard_expression","bnf":[["result","%standard_expression2?"]]},{"name":"basic_rhs","bnf":[["operator","WS*","result"]]},{"name":"eq_approx","bnf":[["eq_operator","WS*","\"~\"","WS*","result"]]},{"name":"PLUS","bnf":[["\"+\""]]},{"name":"MINUS","bnf":[["\"-\""]]},{"name":"MULTIPLY","bnf":[["\"*\""]]},{"name":"DIVIDE","bnf":[["\"/\""]]},{"name":"MODULUS","bnf":[["\"%\""]]},{"name":"DEFAULT_VAL","bnf":[["\"??\""]]},{"name":"arithmetic_operator","bnf":[["PLUS"],["MINUS"],["MULTIPLY"],["DIVIDE"],["MODULUS"],["DEFAULT_VAL"]]},{"name":"%arithmetic_result7","bnf":[["arithmetic_result"],["
|
|
1
|
+
module.exports=[{"name":"statement_main","bnf":[["statement","EOF"]]},{"name":"logical_operator","bnf":[["AND"],["OR"]]},{"name":"%statement0","bnf":[["logical_operator","expression"]],"fragment":true},{"name":"statement","bnf":[["expression","%statement0*"]]},{"name":"expression","bnf":[["not_expression"],["standard_expression"],["parenthesis_expression"]]},{"name":"parenthesis_expression","bnf":[["BEGIN_PARENTHESIS","WS*","statement","WS*","END_PARENTHESIS"]]},{"name":"%not_expression1","bnf":[["result"],["parenthesis_expression"]],"fragment":true},{"name":"not_expression","bnf":[["NOT","%not_expression1"]]},{"name":"%%standard_expression23","bnf":[["WS*","eq_approx"]],"fragment":true},{"name":"%%standard_expression24","bnf":[["WS*","basic_rhs"]],"fragment":true},{"name":"%%%standard_expression256","bnf":[["WS+","IS"]],"fragment":true},{"name":"%%standard_expression25","bnf":[["%%%standard_expression256?","WS+","between"]],"fragment":true},{"name":"%standard_expression2","bnf":[["%%standard_expression23"],["%%standard_expression24"],["%%standard_expression25"]],"fragment":true},{"name":"standard_expression","bnf":[["result","%standard_expression2?"]]},{"name":"basic_rhs","bnf":[["operator","WS*","result"]]},{"name":"eq_approx","bnf":[["eq_operator","WS*","\"~\"","WS*","result"]]},{"name":"PLUS","bnf":[["\"+\""]]},{"name":"MINUS","bnf":[["\"-\""]]},{"name":"MULTIPLY","bnf":[["\"*\""]]},{"name":"DIVIDE","bnf":[["\"/\""]]},{"name":"MODULUS","bnf":[["\"%\""]]},{"name":"DEFAULT_VAL","bnf":[["\"??\""]]},{"name":"arithmetic_operator","bnf":[["PLUS"],["MINUS"],["MULTIPLY"],["DIVIDE"],["MODULUS"],["DEFAULT_VAL"]]},{"name":"arithmetic_operand","bnf":[["fcall"],["number_time"],["number"]]},{"name":"%arithmetic_result7","bnf":[["arithmetic_result"],["arithmetic_operand"]],"fragment":true},{"name":"arithmetic_result","bnf":[["arithmetic_operand","WS*","arithmetic_operator","WS*","%arithmetic_result7"]]},{"name":"simple_result","bnf":[["fcall"],["value"]]},{"name":"result","bnf":[["arithmetic_result"],["simple_result"]]},{"name":"value","bnf":[["false"],["true"],["array"],["time_period"],["number_time"],["number"],["number_tod"],["string"]]},{"name":"BEGIN_ARRAY","bnf":[["WS*",/\x5B/,"WS*"]]},{"name":"BEGIN_OBJECT","bnf":[["WS*",/\x7B/,"WS*"]]},{"name":"END_ARRAY","bnf":[["WS*",/\x5D/,"WS*"]]},{"name":"END_OBJECT","bnf":[["WS*",/\x7D/,"WS*"]]},{"name":"NAME_SEPARATOR","bnf":[["WS*",/\x3A/,"WS*"]]},{"name":"VALUE_SEPARATOR","bnf":[["WS*",/\x2C/,"WS*"]]},{"name":"%WS8","bnf":[[/[\x20\x09\x0A\x0D]/]]},{"name":"WS","bnf":[["%WS8+"]]},{"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":"%argument9","bnf":[["\",\"","WS*"]],"fragment":true},{"name":"argument","bnf":[["statement","WS*","%argument9?"]]},{"name":"arguments","bnf":[["argument*"]]},{"name":"%fname10","bnf":[[/[a-zA-z0-9]/]]},{"name":"fname","bnf":[["%fname10+"]]},{"name":"fcall","bnf":[["fname","WS*","BEGIN_ARGUMENT","WS*","arguments?","END_ARGUMENT"]]},{"name":"%between_number11","bnf":[["number_time"],["number"]],"fragment":true},{"name":"%%between_number1213","bnf":[["WS+",/[Aa]/,/[Nn]/,/[Dd]/,"WS+"]],"fragment":true},{"name":"%%between_number1214","bnf":[["WS*",/\-/,"WS*"]],"fragment":true},{"name":"%between_number12","bnf":[["%%between_number1213"],["%%between_number1214"]],"fragment":true},{"name":"%between_number15","bnf":[["number_time"],["number"]],"fragment":true},{"name":"between_number","bnf":[["%between_number11","%between_number12","%between_number15"]]},{"name":"%%between_number_time1617","bnf":[["WS+",/[Aa]/,/[Nn]/,/[Dd]/,"WS+"]],"fragment":true},{"name":"%%between_number_time1618","bnf":[["WS*",/\-/,"WS*"]],"fragment":true},{"name":"%between_number_time16","bnf":[["%%between_number_time1617"],["%%between_number_time1618"]],"fragment":true},{"name":"%between_number_time19","bnf":[["WS+","dow_range"]],"fragment":true},{"name":"between_number_time","bnf":[["number_time","%between_number_time16","number_time","%between_number_time19?"]]},{"name":"%%between_tod2021","bnf":[["WS+",/[Aa]/,/[Nn]/,/[Dd]/,"WS+"]],"fragment":true},{"name":"%between_tod20","bnf":[["%%between_tod2021"]],"fragment":true},{"name":"%between_tod22","bnf":[["WS+","dow_range"]],"fragment":true},{"name":"between_tod","bnf":[["number_tod","%between_tod20","number_tod","%between_tod22?"]]},{"name":"%between23","bnf":[["between_number"],["between_tod"]],"fragment":true},{"name":"between","bnf":[[/[Bb]/,/[Ee]/,/[Tt]/,/[Ww]/,/[Ee]/,/[Ee]/,/[Nn]/,"WS+","%between23"]]},{"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_range24","bnf":[["WS+",/[Tt]/,/[Oo]/,"WS+","dow"]],"fragment":true},{"name":"dow_range","bnf":[[/[Oo]/,/[Nn]/,"WS+","dow","%dow_range24?"]]},{"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":"%AND25","bnf":[["WS*",/&/,/&/,"WS*"]],"fragment":true},{"name":"%AND26","bnf":[["WS+",/[Aa]/,/[Nn]/,/[Dd]/,"WS+"]],"fragment":true},{"name":"AND","bnf":[["%AND25"],["%AND26"]]},{"name":"%OR27","bnf":[["WS*",/\|/,/\|/,"WS*"]],"fragment":true},{"name":"%OR28","bnf":[["WS+",/[Oo]/,/[Rr]/,"WS+"]],"fragment":true},{"name":"OR","bnf":[["%OR27"],["%OR28"]]},{"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":"%NOT29","bnf":[[/!/,"WS*"]],"fragment":true},{"name":"%NOT30","bnf":[[/[Nn]/,/[Oo]/,/[Tt]/,"WS+"]],"fragment":true},{"name":"NOT","bnf":[["%NOT29"],["%NOT30"]]},{"name":"false","bnf":[[/[Ff]/,/[Aa]/,/[Ll]/,/[Ss]/,/[Ee]/]]},{"name":"null","bnf":[[/[Nn]/,/[Uu]/,/[Ll]/,/[Ll]/]]},{"name":"true","bnf":[[/[Tt]/,/[Rr]/,/[Uu]/,/[Ee]/]]},{"name":"%%array3132","bnf":[["VALUE_SEPARATOR","value"]],"fragment":true},{"name":"%array31","bnf":[["value","%%array3132*"]],"fragment":true},{"name":"array","bnf":[["BEGIN_ARRAY","%array31?","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":"%%number3334","bnf":[[/[0-9]/]]},{"name":"%number33","bnf":[["%%number3334+"]],"fragment":true},{"name":"%%number3536","bnf":[[/[0-9]/]]},{"name":"%number35","bnf":[["\".\"","%%number3536+"]],"fragment":true},{"name":"%%number3738","bnf":[["\"-\""],["\"+\""]],"fragment":true},{"name":"%%%number373940","bnf":[[/[0-9]/]]},{"name":"%%number3739","bnf":[["\"0\""],[/[1-9]/,"%%%number373940*"]],"fragment":true},{"name":"%number37","bnf":[["\"e\"","%%number3738?","%%number3739"]],"fragment":true},{"name":"number","bnf":[["\"-\"?","%number33","%number35?","%number37?"]]},{"name":"number_time","bnf":[["number","WS+","unit"]]},{"name":"%%number_tod4142","bnf":[[/[0-9]/]]},{"name":"%number_tod41","bnf":[["%%number_tod4142+"]],"fragment":true},{"name":"%%number_tod4344","bnf":[[/[0-9]/]]},{"name":"%number_tod43","bnf":[["%%number_tod4344+"]],"fragment":true},{"name":"number_tod","bnf":[["%number_tod41","\":\"","%number_tod43"]]},{"name":"%time_period_ago45","bnf":[["WS+","number_time"]],"fragment":true},{"name":"time_period_ago","bnf":[["number_time","%time_period_ago45*","WS+","AGO"]]},{"name":"%time_period_ago_between46","bnf":[["WS+","number_time"]],"fragment":true},{"name":"time_period_ago_between","bnf":[["number_time","%time_period_ago_between46*","WS+","AGO","WS+","between_tod_only"]]},{"name":"time_period_const","bnf":[[/[Tt]/,/[Oo]/,/[Dd]/,/[Aa]/,/[Yy]/],["time_period_ago"]]},{"name":"time_period","bnf":[["time_period_ago_between"],["time_period_const"],["between_tod_only"],["between_time_only"]]},{"name":"%%string4748","bnf":[[/[\x20-\x21]/],[/[\x23-\x5B]/],[/[\x5D-\uFFFF]/]],"fragment":true},{"name":"%%string4749","bnf":[[/\x22/],[/\x5C/],[/\x2F/],[/\x62/],[/\x66/],[/\x6E/],[/\x72/],[/\x74/],[/\x75/,"HEXDIG","HEXDIG","HEXDIG","HEXDIG"]],"fragment":true},{"name":"%string47","bnf":[["%%string4748"],[/\x5C/,"%%string4749"]],"fragment":true},{"name":"string","bnf":[["'\"'","%string47*","'\"'"]]},{"name":"HEXDIG","bnf":[[/[a-fA-F0-9]/]]}]
|
|
@@ -30,6 +30,34 @@ const LogicalOperators = {
|
|
|
30
30
|
"OR": 'Or',
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// Map abbreviations to canonical uppercase full form
|
|
34
|
+
const DOW_MAP = {
|
|
35
|
+
'MON': 'MONDAY',
|
|
36
|
+
'TUE': 'TUESDAY',
|
|
37
|
+
'WED': 'WEDNESDAY',
|
|
38
|
+
'THU': 'THURSDAY',
|
|
39
|
+
'THUR': 'THURSDAY',
|
|
40
|
+
'FRI': 'FRIDAY',
|
|
41
|
+
'SAT': 'SATURDAY',
|
|
42
|
+
'SUN': 'SUNDAY',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Valid full day names
|
|
46
|
+
const VALID_DAYS = new Set(['MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY']);
|
|
47
|
+
|
|
48
|
+
const normalizeDow = (text) => {
|
|
49
|
+
const upper = text.toUpperCase();
|
|
50
|
+
// Check if it's an abbreviation first
|
|
51
|
+
if (upper in DOW_MAP) {
|
|
52
|
+
return DOW_MAP[upper];
|
|
53
|
+
}
|
|
54
|
+
// Otherwise, check if it's a valid full name
|
|
55
|
+
if (VALID_DAYS.has(upper)) {
|
|
56
|
+
return upper;
|
|
57
|
+
}
|
|
58
|
+
throw new Error(`Invalid day of week: ${text}`);
|
|
59
|
+
};
|
|
60
|
+
|
|
33
61
|
const Epsilon = 0.01
|
|
34
62
|
|
|
35
63
|
class RuleParser {
|
|
@@ -63,21 +91,19 @@ class RuleParser {
|
|
|
63
91
|
return ret
|
|
64
92
|
}
|
|
65
93
|
static _parseDowRange(dowRange) {
|
|
66
|
-
const dow = []
|
|
67
|
-
|
|
94
|
+
const dow = [];
|
|
68
95
|
// dow_range can have 1 or 2 children (single day or range)
|
|
69
96
|
if (dowRange.children.length === 1) {
|
|
70
97
|
// Single day: ON MONDAY
|
|
71
|
-
dow.push(dowRange.children[0].text
|
|
98
|
+
dow.push(normalizeDow(dowRange.children[0].text));
|
|
72
99
|
} else if (dowRange.children.length === 2) {
|
|
73
100
|
// Range: ON MONDAY TO WEDNESDAY
|
|
74
|
-
dow.push(dowRange.children[0].text
|
|
75
|
-
dow.push(dowRange.children[1].text
|
|
101
|
+
dow.push(normalizeDow(dowRange.children[0].text));
|
|
102
|
+
dow.push(normalizeDow(dowRange.children[1].text));
|
|
76
103
|
} else {
|
|
77
|
-
throw new Error(`Invalid dow_range with ${dowRange.children.length} children`)
|
|
104
|
+
throw new Error(`Invalid dow_range with ${dowRange.children.length} children`);
|
|
78
105
|
}
|
|
79
|
-
|
|
80
|
-
return dow
|
|
106
|
+
return dow;
|
|
81
107
|
}
|
|
82
108
|
static _addDowToTods(startTod, endTod, dowRange) {
|
|
83
109
|
if (dowRange && dowRange.type === 'dow_range') {
|
|
@@ -89,7 +115,44 @@ class RuleParser {
|
|
|
89
115
|
static _parseTimePeriod(tp){
|
|
90
116
|
switch(tp.type){
|
|
91
117
|
case 'time_period_const':
|
|
118
|
+
// Check if this is a time_period_ago (has children)
|
|
119
|
+
if (tp.children && tp.children.length > 0 && tp.children[0].type === 'time_period_ago') {
|
|
120
|
+
const timePeriodAgo = tp.children[0]
|
|
121
|
+
// Extract all number_time children and sum them up
|
|
122
|
+
let totalSeconds = 0
|
|
123
|
+
const components = []
|
|
124
|
+
for (const child of timePeriodAgo.children) {
|
|
125
|
+
if (child.type === 'number_time') {
|
|
126
|
+
const number = parseFloat(child.children[0].text)
|
|
127
|
+
const unit = child.children[1].text.toUpperCase()
|
|
128
|
+
components.push([number, unit])
|
|
129
|
+
// Parse the value to get seconds
|
|
130
|
+
totalSeconds += RuleParser.__parseValue(child)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// If there's only one component, use its number and unit
|
|
134
|
+
// Otherwise, use the total in seconds and "SECONDS" as the unit
|
|
135
|
+
if (components.length === 1) {
|
|
136
|
+
return ["TimePeriodConstAgo", components[0][0], components[0][1]]
|
|
137
|
+
} else {
|
|
138
|
+
return ["TimePeriodConstAgo", totalSeconds, "SECONDS"]
|
|
139
|
+
}
|
|
140
|
+
}
|
|
92
141
|
return ["TimePeriodConst", tp.text]
|
|
142
|
+
case 'time_period_ago_between': {
|
|
143
|
+
// time_period_ago_between has children[0] = number_time, children[1] = between_tod_only
|
|
144
|
+
const betweenTodOnly = tp.children[1]
|
|
145
|
+
const betweenTod = betweenTodOnly.children[0]
|
|
146
|
+
const startTod = RuleParser.__parseValue(betweenTod.children[0])
|
|
147
|
+
const endTod = RuleParser.__parseValue(betweenTod.children[1])
|
|
148
|
+
|
|
149
|
+
// Check if there's a dow_range at betweenTod.children[2]
|
|
150
|
+
if (betweenTod.children.length > 2) {
|
|
151
|
+
RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2])
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return ["TimePeriodBetween", startTod, endTod]
|
|
155
|
+
}
|
|
93
156
|
case 'between_tod_only': {
|
|
94
157
|
// between_tod_only has children[0] = between_tod node
|
|
95
158
|
const betweenTod = tp.children[0]
|
|
@@ -103,11 +166,19 @@ class RuleParser {
|
|
|
103
166
|
|
|
104
167
|
return ["TimePeriodBetween", startTod, endTod]
|
|
105
168
|
}
|
|
106
|
-
case '
|
|
107
|
-
//
|
|
108
|
-
const
|
|
109
|
-
const startValue = RuleParser.__parseValue(
|
|
110
|
-
const endValue = RuleParser.__parseValue(
|
|
169
|
+
case 'between_time_only': {
|
|
170
|
+
// between_time_only has children[0] = between_number_time node
|
|
171
|
+
const betweenNumberTime = tp.children[0]
|
|
172
|
+
const startValue = RuleParser.__parseValue(betweenNumberTime.children[0])
|
|
173
|
+
const endValue = RuleParser.__parseValue(betweenNumberTime.children[1])
|
|
174
|
+
|
|
175
|
+
// Check if there's a dow_range at betweenNumberTime.children[2]
|
|
176
|
+
// If DOW filters are provided, append them as additional parameters
|
|
177
|
+
if (betweenNumberTime.children.length > 2 && betweenNumberTime.children[2].type === 'dow_range') {
|
|
178
|
+
const dow = RuleParser._parseDowRange(betweenNumberTime.children[2])
|
|
179
|
+
// Append DOW as additional arguments: ["TimePeriodBetween", start, end, "MONDAY"] or ["TimePeriodBetween", start, end, "MONDAY", "FRIDAY"]
|
|
180
|
+
return ["TimePeriodBetween", startValue, endValue, ...dow]
|
|
181
|
+
}
|
|
111
182
|
|
|
112
183
|
return ["TimePeriodBetween", startValue, endValue]
|
|
113
184
|
}
|
|
@@ -200,15 +271,42 @@ class RuleParser {
|
|
|
200
271
|
}
|
|
201
272
|
return null
|
|
202
273
|
}
|
|
274
|
+
static _parseArithmeticOperand(operand){
|
|
275
|
+
assert(operand.children.length == 1)
|
|
276
|
+
const child = operand.children[0]
|
|
277
|
+
const type = child.type
|
|
278
|
+
switch(type){
|
|
279
|
+
case 'fcall':
|
|
280
|
+
return RuleParser._parseFcall(child)
|
|
281
|
+
case 'number':
|
|
282
|
+
return ['Value', parseFloat(child.text)]
|
|
283
|
+
case 'number_time':
|
|
284
|
+
return ['Value', RuleParser.__parseValue(child)]
|
|
285
|
+
}
|
|
286
|
+
throw new Error(`Unknown arithmetic operand type ${type}`)
|
|
287
|
+
}
|
|
203
288
|
static _parseArithmeticResult(result){
|
|
204
289
|
assert(result.children.length == 3)
|
|
205
|
-
const partA =
|
|
290
|
+
const partA = RuleParser._parseArithmeticOperand(result.children[0])
|
|
206
291
|
const operatorFn = ArithmeticOperators[result.children[1].text]
|
|
207
|
-
const partB =
|
|
292
|
+
const partB = RuleParser.__parseArithmeticResult(result, 2)
|
|
208
293
|
|
|
209
294
|
return [operatorFn, partA, partB]
|
|
210
295
|
}
|
|
211
296
|
|
|
297
|
+
static __parseArithmeticResult(result, idx){
|
|
298
|
+
const child = result.children[idx]
|
|
299
|
+
const type = child.type
|
|
300
|
+
switch(type){
|
|
301
|
+
case 'arithmetic_operand':
|
|
302
|
+
return RuleParser._parseArithmeticOperand(child)
|
|
303
|
+
case 'arithmetic_result':
|
|
304
|
+
return RuleParser._parseArithmeticResult(child)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
throw new Error(`Unknown arithmetic result node ${type}`)
|
|
308
|
+
}
|
|
309
|
+
|
|
212
310
|
static __parseResult(result, idx){
|
|
213
311
|
const child = result.children[idx]
|
|
214
312
|
const type = child.type
|