@halleyassist/rule-parser 1.0.10 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/package.json +1 -1
- package/src/RuleParser.ebnf.js +2 -2
- package/src/RuleParser.js +91 -20
- package/src/RuleParser.production.ebnf.js +1 -1
- package/src/RuleParser.production.js +91 -20
package/README.md
CHANGED
package/package.json
CHANGED
package/src/RuleParser.ebnf.js
CHANGED
|
@@ -47,10 +47,10 @@ fname ::= [a-zA-z0-9]+
|
|
|
47
47
|
fcall ::= fname WS* BEGIN_ARGUMENT WS* arguments? END_ARGUMENT
|
|
48
48
|
|
|
49
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
|
|
50
|
+
between_number_time ||= number_time ((WS+ "AND" WS+) | (WS* "-" WS*)) number_time (WS+ dow_range)?
|
|
51
51
|
between_tod ||= number_tod ((WS+ "AND" WS+)) number_tod (WS+ dow_range)?
|
|
52
52
|
between ||= "BETWEEN" WS+ (between_number | between_tod)
|
|
53
|
-
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"
|
|
54
54
|
dow_range ||= "ON" WS+ dow (WS+ "TO" WS+ dow)?
|
|
55
55
|
between_time_only ||= "BETWEEN" WS+ between_number_time
|
|
56
56
|
between_tod_only ||= "BETWEEN" WS+ between_tod
|
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,27 +91,22 @@ class RuleParser {
|
|
|
63
91
|
return ret
|
|
64
92
|
}
|
|
65
93
|
static _parseDowRange(dowRange) {
|
|
66
|
-
const dow = []
|
|
67
|
-
|
|
68
94
|
// dow_range can have 1 or 2 children (single day or range)
|
|
69
95
|
if (dowRange.children.length === 1) {
|
|
70
|
-
// Single day: ON MONDAY
|
|
71
|
-
|
|
96
|
+
// Single day: ON MONDAY - return just the day string
|
|
97
|
+
return { start: normalizeDow(dowRange.children[0].text), end: normalizeDow(dowRange.children[0].text) };
|
|
72
98
|
} else if (dowRange.children.length === 2) {
|
|
73
|
-
// Range: ON MONDAY TO
|
|
74
|
-
|
|
75
|
-
dow.push(dowRange.children[1].text.toLowerCase())
|
|
99
|
+
// Range: ON MONDAY TO FRIDAY - return both start and end days
|
|
100
|
+
return { start: normalizeDow(dowRange.children[0].text), end: normalizeDow(dowRange.children[1].text) };
|
|
76
101
|
} else {
|
|
77
|
-
throw new Error(`Invalid dow_range with ${dowRange.children.length} children`)
|
|
102
|
+
throw new Error(`Invalid dow_range with ${dowRange.children.length} children`);
|
|
78
103
|
}
|
|
79
|
-
|
|
80
|
-
return dow
|
|
81
104
|
}
|
|
82
105
|
static _addDowToTods(startTod, endTod, dowRange) {
|
|
83
106
|
if (dowRange && dowRange.type === 'dow_range') {
|
|
84
107
|
const dow = RuleParser._parseDowRange(dowRange)
|
|
85
|
-
startTod.dow = dow
|
|
86
|
-
endTod.dow = dow
|
|
108
|
+
startTod.dow = dow.start
|
|
109
|
+
endTod.dow = dow.end
|
|
87
110
|
}
|
|
88
111
|
}
|
|
89
112
|
static _parseTimePeriod(tp){
|
|
@@ -117,11 +140,13 @@ class RuleParser {
|
|
|
117
140
|
// time_period_ago_between has children[0] = number_time, children[1] = between_tod_only
|
|
118
141
|
const betweenTodOnly = tp.children[1]
|
|
119
142
|
const betweenTod = betweenTodOnly.children[0]
|
|
120
|
-
|
|
121
|
-
|
|
143
|
+
let startTod = RuleParser.__parseValue(betweenTod.children[0])
|
|
144
|
+
let endTod = RuleParser.__parseValue(betweenTod.children[1])
|
|
122
145
|
|
|
123
146
|
// Check if there's a dow_range at betweenTod.children[2]
|
|
124
147
|
if (betweenTod.children.length > 2) {
|
|
148
|
+
if(typeof startTod === 'number') startTod = {seconds: startTod, dow: null}
|
|
149
|
+
if(typeof endTod === 'number') endTod = {seconds: endTod, dow: null}
|
|
125
150
|
RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2])
|
|
126
151
|
}
|
|
127
152
|
|
|
@@ -130,21 +155,36 @@ class RuleParser {
|
|
|
130
155
|
case 'between_tod_only': {
|
|
131
156
|
// between_tod_only has children[0] = between_tod node
|
|
132
157
|
const betweenTod = tp.children[0]
|
|
133
|
-
|
|
134
|
-
|
|
158
|
+
let startTod = RuleParser.__parseValue(betweenTod.children[0])
|
|
159
|
+
let endTod = RuleParser.__parseValue(betweenTod.children[1])
|
|
135
160
|
|
|
136
161
|
// Check if there's a dow_range at betweenTod.children[2]
|
|
137
162
|
if (betweenTod.children.length > 2) {
|
|
163
|
+
if(typeof startTod === 'number') startTod = {seconds: startTod, dow: null}
|
|
164
|
+
if(typeof endTod === 'number') endTod = {seconds: endTod, dow: null}
|
|
138
165
|
RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2])
|
|
139
166
|
}
|
|
140
167
|
|
|
141
168
|
return ["TimePeriodBetween", startTod, endTod]
|
|
142
169
|
}
|
|
143
170
|
case 'between_time_only': {
|
|
144
|
-
//
|
|
145
|
-
const
|
|
146
|
-
const startValue = RuleParser.__parseValue(
|
|
147
|
-
const endValue = RuleParser.__parseValue(
|
|
171
|
+
// between_time_only has children[0] = between_number_time node
|
|
172
|
+
const betweenNumberTime = tp.children[0]
|
|
173
|
+
const startValue = RuleParser.__parseValue(betweenNumberTime.children[0])
|
|
174
|
+
const endValue = RuleParser.__parseValue(betweenNumberTime.children[1])
|
|
175
|
+
|
|
176
|
+
// Check if there's a dow_range at betweenNumberTime.children[2]
|
|
177
|
+
// If DOW filters are provided, append them as additional parameters
|
|
178
|
+
if (betweenNumberTime.children.length > 2 && betweenNumberTime.children[2].type === 'dow_range') {
|
|
179
|
+
const dow = RuleParser._parseDowRange(betweenNumberTime.children[2])
|
|
180
|
+
if (dow.start === dow.end) {
|
|
181
|
+
// Single day: ["TimePeriodBetween", start, end, "MONDAY"]
|
|
182
|
+
return ["TimePeriodBetween", startValue, endValue, dow.start]
|
|
183
|
+
} else {
|
|
184
|
+
// Range: ["TimePeriodBetween", start, end, "MONDAY", "FRIDAY"]
|
|
185
|
+
return ["TimePeriodBetween", startValue, endValue, dow.start, dow.end]
|
|
186
|
+
}
|
|
187
|
+
}
|
|
148
188
|
|
|
149
189
|
return ["TimePeriodBetween", startValue, endValue]
|
|
150
190
|
}
|
|
@@ -251,12 +291,43 @@ class RuleParser {
|
|
|
251
291
|
}
|
|
252
292
|
throw new Error(`Unknown arithmetic operand type ${type}`)
|
|
253
293
|
}
|
|
294
|
+
static _isConstantValue(expr){
|
|
295
|
+
// Check if an expression is a constant value
|
|
296
|
+
return Array.isArray(expr) && expr.length === 2 && expr[0] === 'Value' && typeof expr[1] === 'number'
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
static _evaluateConstantArithmetic(operator, leftValue, rightValue){
|
|
300
|
+
// Evaluate constant arithmetic operations at parse time
|
|
301
|
+
switch(operator){
|
|
302
|
+
case 'MathAdd':
|
|
303
|
+
return leftValue + rightValue
|
|
304
|
+
case 'MathSub':
|
|
305
|
+
return leftValue - rightValue
|
|
306
|
+
case 'MathMul':
|
|
307
|
+
return leftValue * rightValue
|
|
308
|
+
case 'MathDiv':
|
|
309
|
+
return leftValue / rightValue
|
|
310
|
+
case 'MathMod':
|
|
311
|
+
return leftValue % rightValue
|
|
312
|
+
default:
|
|
313
|
+
return null
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
254
317
|
static _parseArithmeticResult(result){
|
|
255
318
|
assert(result.children.length == 3)
|
|
256
319
|
const partA = RuleParser._parseArithmeticOperand(result.children[0])
|
|
257
320
|
const operatorFn = ArithmeticOperators[result.children[1].text]
|
|
258
321
|
const partB = RuleParser.__parseArithmeticResult(result, 2)
|
|
259
322
|
|
|
323
|
+
// Compile out constant expressions
|
|
324
|
+
if (RuleParser._isConstantValue(partA) && RuleParser._isConstantValue(partB)) {
|
|
325
|
+
const result = RuleParser._evaluateConstantArithmetic(operatorFn, partA[1], partB[1])
|
|
326
|
+
if (result !== null) {
|
|
327
|
+
return ['Value', result]
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
260
331
|
return [operatorFn, partA, partB]
|
|
261
332
|
}
|
|
262
333
|
|
|
@@ -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_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_time","bnf":[["number_time","%between_number_time16","number_time"]]},{"name":"%%
|
|
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,27 +91,22 @@ class RuleParser {
|
|
|
63
91
|
return ret
|
|
64
92
|
}
|
|
65
93
|
static _parseDowRange(dowRange) {
|
|
66
|
-
const dow = []
|
|
67
|
-
|
|
68
94
|
// dow_range can have 1 or 2 children (single day or range)
|
|
69
95
|
if (dowRange.children.length === 1) {
|
|
70
|
-
// Single day: ON MONDAY
|
|
71
|
-
|
|
96
|
+
// Single day: ON MONDAY - return just the day string
|
|
97
|
+
return { start: normalizeDow(dowRange.children[0].text), end: normalizeDow(dowRange.children[0].text) };
|
|
72
98
|
} else if (dowRange.children.length === 2) {
|
|
73
|
-
// Range: ON MONDAY TO
|
|
74
|
-
|
|
75
|
-
dow.push(dowRange.children[1].text.toLowerCase())
|
|
99
|
+
// Range: ON MONDAY TO FRIDAY - return both start and end days
|
|
100
|
+
return { start: normalizeDow(dowRange.children[0].text), end: normalizeDow(dowRange.children[1].text) };
|
|
76
101
|
} else {
|
|
77
|
-
throw new Error(`Invalid dow_range with ${dowRange.children.length} children`)
|
|
102
|
+
throw new Error(`Invalid dow_range with ${dowRange.children.length} children`);
|
|
78
103
|
}
|
|
79
|
-
|
|
80
|
-
return dow
|
|
81
104
|
}
|
|
82
105
|
static _addDowToTods(startTod, endTod, dowRange) {
|
|
83
106
|
if (dowRange && dowRange.type === 'dow_range') {
|
|
84
107
|
const dow = RuleParser._parseDowRange(dowRange)
|
|
85
|
-
startTod.dow = dow
|
|
86
|
-
endTod.dow = dow
|
|
108
|
+
startTod.dow = dow.start
|
|
109
|
+
endTod.dow = dow.end
|
|
87
110
|
}
|
|
88
111
|
}
|
|
89
112
|
static _parseTimePeriod(tp){
|
|
@@ -117,11 +140,13 @@ class RuleParser {
|
|
|
117
140
|
// time_period_ago_between has children[0] = number_time, children[1] = between_tod_only
|
|
118
141
|
const betweenTodOnly = tp.children[1]
|
|
119
142
|
const betweenTod = betweenTodOnly.children[0]
|
|
120
|
-
|
|
121
|
-
|
|
143
|
+
let startTod = RuleParser.__parseValue(betweenTod.children[0])
|
|
144
|
+
let endTod = RuleParser.__parseValue(betweenTod.children[1])
|
|
122
145
|
|
|
123
146
|
// Check if there's a dow_range at betweenTod.children[2]
|
|
124
147
|
if (betweenTod.children.length > 2) {
|
|
148
|
+
if(typeof startTod === 'number') startTod = {seconds: startTod, dow: null}
|
|
149
|
+
if(typeof endTod === 'number') endTod = {seconds: endTod, dow: null}
|
|
125
150
|
RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2])
|
|
126
151
|
}
|
|
127
152
|
|
|
@@ -130,21 +155,36 @@ class RuleParser {
|
|
|
130
155
|
case 'between_tod_only': {
|
|
131
156
|
// between_tod_only has children[0] = between_tod node
|
|
132
157
|
const betweenTod = tp.children[0]
|
|
133
|
-
|
|
134
|
-
|
|
158
|
+
let startTod = RuleParser.__parseValue(betweenTod.children[0])
|
|
159
|
+
let endTod = RuleParser.__parseValue(betweenTod.children[1])
|
|
135
160
|
|
|
136
161
|
// Check if there's a dow_range at betweenTod.children[2]
|
|
137
162
|
if (betweenTod.children.length > 2) {
|
|
163
|
+
if(typeof startTod === 'number') startTod = {seconds: startTod, dow: null}
|
|
164
|
+
if(typeof endTod === 'number') endTod = {seconds: endTod, dow: null}
|
|
138
165
|
RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2])
|
|
139
166
|
}
|
|
140
167
|
|
|
141
168
|
return ["TimePeriodBetween", startTod, endTod]
|
|
142
169
|
}
|
|
143
170
|
case 'between_time_only': {
|
|
144
|
-
//
|
|
145
|
-
const
|
|
146
|
-
const startValue = RuleParser.__parseValue(
|
|
147
|
-
const endValue = RuleParser.__parseValue(
|
|
171
|
+
// between_time_only has children[0] = between_number_time node
|
|
172
|
+
const betweenNumberTime = tp.children[0]
|
|
173
|
+
const startValue = RuleParser.__parseValue(betweenNumberTime.children[0])
|
|
174
|
+
const endValue = RuleParser.__parseValue(betweenNumberTime.children[1])
|
|
175
|
+
|
|
176
|
+
// Check if there's a dow_range at betweenNumberTime.children[2]
|
|
177
|
+
// If DOW filters are provided, append them as additional parameters
|
|
178
|
+
if (betweenNumberTime.children.length > 2 && betweenNumberTime.children[2].type === 'dow_range') {
|
|
179
|
+
const dow = RuleParser._parseDowRange(betweenNumberTime.children[2])
|
|
180
|
+
if (dow.start === dow.end) {
|
|
181
|
+
// Single day: ["TimePeriodBetween", start, end, "MONDAY"]
|
|
182
|
+
return ["TimePeriodBetween", startValue, endValue, dow.start]
|
|
183
|
+
} else {
|
|
184
|
+
// Range: ["TimePeriodBetween", start, end, "MONDAY", "FRIDAY"]
|
|
185
|
+
return ["TimePeriodBetween", startValue, endValue, dow.start, dow.end]
|
|
186
|
+
}
|
|
187
|
+
}
|
|
148
188
|
|
|
149
189
|
return ["TimePeriodBetween", startValue, endValue]
|
|
150
190
|
}
|
|
@@ -251,12 +291,43 @@ class RuleParser {
|
|
|
251
291
|
}
|
|
252
292
|
throw new Error(`Unknown arithmetic operand type ${type}`)
|
|
253
293
|
}
|
|
294
|
+
static _isConstantValue(expr){
|
|
295
|
+
// Check if an expression is a constant value
|
|
296
|
+
return Array.isArray(expr) && expr.length === 2 && expr[0] === 'Value' && typeof expr[1] === 'number'
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
static _evaluateConstantArithmetic(operator, leftValue, rightValue){
|
|
300
|
+
// Evaluate constant arithmetic operations at parse time
|
|
301
|
+
switch(operator){
|
|
302
|
+
case 'MathAdd':
|
|
303
|
+
return leftValue + rightValue
|
|
304
|
+
case 'MathSub':
|
|
305
|
+
return leftValue - rightValue
|
|
306
|
+
case 'MathMul':
|
|
307
|
+
return leftValue * rightValue
|
|
308
|
+
case 'MathDiv':
|
|
309
|
+
return leftValue / rightValue
|
|
310
|
+
case 'MathMod':
|
|
311
|
+
return leftValue % rightValue
|
|
312
|
+
default:
|
|
313
|
+
return null
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
254
317
|
static _parseArithmeticResult(result){
|
|
255
318
|
assert(result.children.length == 3)
|
|
256
319
|
const partA = RuleParser._parseArithmeticOperand(result.children[0])
|
|
257
320
|
const operatorFn = ArithmeticOperators[result.children[1].text]
|
|
258
321
|
const partB = RuleParser.__parseArithmeticResult(result, 2)
|
|
259
322
|
|
|
323
|
+
// Compile out constant expressions
|
|
324
|
+
if (RuleParser._isConstantValue(partA) && RuleParser._isConstantValue(partB)) {
|
|
325
|
+
const result = RuleParser._evaluateConstantArithmetic(operatorFn, partA[1], partB[1])
|
|
326
|
+
if (result !== null) {
|
|
327
|
+
return ['Value', result]
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
260
331
|
return [operatorFn, partA, partB]
|
|
261
332
|
}
|
|
262
333
|
|