@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@halleyassist/rule-parser",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "The grammar for HalleyAssist rules",
5
5
  "main": "src/RuleParser.production.js",
6
6
  "scripts": {
@@ -18,11 +18,12 @@ DIVIDE ::= "/"
18
18
  MODULUS ::= "%"
19
19
  DEFAULT_VAL ::= "??"
20
20
  arithmetic_operator ::= PLUS | MINUS | MULTIPLY | DIVIDE | MODULUS | DEFAULT_VAL
21
- arithmetic_result ::= simple_result WS* arithmetic_operator WS* ( arithmetic_result | simple_result )
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 | time_period | string
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
- between_number_only ||= "BETWEEN" WS+ between_number
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" | "second" | "minutes" | "minute" | "min" | "mins" | "min" | "hours" | "hour" | "days" | "day" | "weeks" | "week"
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
- time_period_const ||= "today"
77
- time_period ::= time_period_const | between_tod_only | between_number_only
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.toLowerCase())
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.toLowerCase())
75
- dow.push(dowRange.children[1].text.toLowerCase())
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 'between_number_only': {
107
- // between_number_only has children[0] = between_number node
108
- const betweenNumber = tp.children[0]
109
- const startValue = RuleParser.__parseValue(betweenNumber.children[0])
110
- const endValue = RuleParser.__parseValue(betweenNumber.children[1])
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 = this._parseSimpleResult(result.children[0])
290
+ const partA = RuleParser._parseArithmeticOperand(result.children[0])
206
291
  const operatorFn = ArithmeticOperators[result.children[1].text]
207
- const partB = this.__parseResult(result, 2)
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"],["simple_result"]],"fragment":true},{"name":"arithmetic_result","bnf":[["simple_result","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"],["number_time"],["number"],["number_tod"],["time_period"],["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_tod1617","bnf":[["WS+",/[Aa]/,/[Nn]/,/[Dd]/,"WS+"]],"fragment":true},{"name":"%between_tod16","bnf":[["%%between_tod1617"]],"fragment":true},{"name":"%between_tod18","bnf":[["WS+","dow_range"]],"fragment":true},{"name":"between_tod","bnf":[["number_tod","%between_tod16","number_tod","%between_tod18?"]]},{"name":"%between19","bnf":[["between_number"],["between_tod"]],"fragment":true},{"name":"between","bnf":[[/[Bb]/,/[Ee]/,/[Tt]/,/[Ww]/,/[Ee]/,/[Ee]/,/[Nn]/,"WS+","%between19"]]},{"name":"dow","bnf":[[/[Mm]/,/[Oo]/,/[Nn]/,/[Dd]/,/[Aa]/,/[Yy]/],[/[Tt]/,/[Uu]/,/[Ee]/,/[Ss]/,/[Dd]/,/[Aa]/,/[Yy]/],[/[Ww]/,/[Ee]/,/[Dd]/,/[Nn]/,/[Ee]/,/[Ss]/,/[Dd]/,/[Aa]/,/[Yy]/],[/[Tt]/,/[Hh]/,/[Uu]/,/[Rr]/,/[Ss]/,/[Dd]/,/[Aa]/,/[Yy]/],[/[Ff]/,/[Rr]/,/[Ii]/,/[Dd]/,/[Aa]/,/[Yy]/],[/[Ss]/,/[Aa]/,/[Tt]/,/[Uu]/,/[Rr]/,/[Dd]/,/[Aa]/,/[Yy]/],[/[Ss]/,/[Uu]/,/[Nn]/,/[Dd]/,/[Aa]/,/[Yy]/]]},{"name":"%dow_range20","bnf":[["WS+",/[Tt]/,/[Oo]/,"WS+","dow"]],"fragment":true},{"name":"dow_range","bnf":[[/[Oo]/,/[Nn]/,"WS+","dow","%dow_range20?"]]},{"name":"between_number_only","bnf":[[/[Bb]/,/[Ee]/,/[Tt]/,/[Ww]/,/[Ee]/,/[Ee]/,/[Nn]/,"WS+","between_number"]]},{"name":"between_tod_only","bnf":[[/[Bb]/,/[Ee]/,/[Tt]/,/[Ww]/,/[Ee]/,/[Ee]/,/[Nn]/,"WS+","between_tod"]]},{"name":"%AND21","bnf":[["WS*",/&/,/&/,"WS*"]],"fragment":true},{"name":"%AND22","bnf":[["WS+",/[Aa]/,/[Nn]/,/[Dd]/,"WS+"]],"fragment":true},{"name":"AND","bnf":[["%AND21"],["%AND22"]]},{"name":"%OR23","bnf":[["WS*",/\|/,/\|/,"WS*"]],"fragment":true},{"name":"%OR24","bnf":[["WS+",/[Oo]/,/[Rr]/,"WS+"]],"fragment":true},{"name":"OR","bnf":[["%OR23"],["%OR24"]]},{"name":"GT","bnf":[["\">\""]]},{"name":"LT","bnf":[["\"<\""]]},{"name":"GTE","bnf":[["\">=\""]]},{"name":"LTE","bnf":[["\"<=\""]]},{"name":"IS","bnf":[[/[Ii]/,/[Ss]/]]},{"name":"EQ","bnf":[["\"==\""],["\"=\""]]},{"name":"NEQ","bnf":[["\"!=\""]]},{"name":"%NOT25","bnf":[[/!/,"WS*"]],"fragment":true},{"name":"%NOT26","bnf":[[/[Nn]/,/[Oo]/,/[Tt]/,"WS+"]],"fragment":true},{"name":"NOT","bnf":[["%NOT25"],["%NOT26"]]},{"name":"false","bnf":[[/[Ff]/,/[Aa]/,/[Ll]/,/[Ss]/,/[Ee]/]]},{"name":"null","bnf":[[/[Nn]/,/[Uu]/,/[Ll]/,/[Ll]/]]},{"name":"true","bnf":[[/[Tt]/,/[Rr]/,/[Uu]/,/[Ee]/]]},{"name":"%%array2728","bnf":[["VALUE_SEPARATOR","value"]],"fragment":true},{"name":"%array27","bnf":[["value","%%array2728*"]],"fragment":true},{"name":"array","bnf":[["BEGIN_ARRAY","%array27?","END_ARRAY"]]},{"name":"unit","bnf":[[/[Ss]/,/[Ee]/,/[Cc]/,/[Oo]/,/[Nn]/,/[Dd]/,/[Ss]/],[/[Ss]/,/[Ee]/,/[Cc]/,/[Oo]/,/[Nn]/,/[Dd]/],[/[Mm]/,/[Ii]/,/[Nn]/,/[Uu]/,/[Tt]/,/[Ee]/,/[Ss]/],[/[Mm]/,/[Ii]/,/[Nn]/,/[Uu]/,/[Tt]/,/[Ee]/],[/[Mm]/,/[Ii]/,/[Nn]/],[/[Mm]/,/[Ii]/,/[Nn]/,/[Ss]/],[/[Mm]/,/[Ii]/,/[Nn]/],[/[Hh]/,/[Oo]/,/[Uu]/,/[Rr]/,/[Ss]/],[/[Hh]/,/[Oo]/,/[Uu]/,/[Rr]/],[/[Dd]/,/[Aa]/,/[Yy]/,/[Ss]/],[/[Dd]/,/[Aa]/,/[Yy]/],[/[Ww]/,/[Ee]/,/[Ee]/,/[Kk]/,/[Ss]/],[/[Ww]/,/[Ee]/,/[Ee]/,/[Kk]/]]},{"name":"%%number2930","bnf":[[/[0-9]/]]},{"name":"%number29","bnf":[["%%number2930+"]],"fragment":true},{"name":"%%number3132","bnf":[[/[0-9]/]]},{"name":"%number31","bnf":[["\".\"","%%number3132+"]],"fragment":true},{"name":"%%number3334","bnf":[["\"-\""],["\"+\""]],"fragment":true},{"name":"%%%number333536","bnf":[[/[0-9]/]]},{"name":"%%number3335","bnf":[["\"0\""],[/[1-9]/,"%%%number333536*"]],"fragment":true},{"name":"%number33","bnf":[["\"e\"","%%number3334?","%%number3335"]],"fragment":true},{"name":"number","bnf":[["\"-\"?","%number29","%number31?","%number33?"]]},{"name":"number_time","bnf":[["number","WS+","unit"]]},{"name":"%%number_tod3738","bnf":[[/[0-9]/]]},{"name":"%number_tod37","bnf":[["%%number_tod3738+"]],"fragment":true},{"name":"%%number_tod3940","bnf":[[/[0-9]/]]},{"name":"%number_tod39","bnf":[["%%number_tod3940+"]],"fragment":true},{"name":"number_tod","bnf":[["%number_tod37","\":\"","%number_tod39"]]},{"name":"time_period_const","bnf":[[/[Tt]/,/[Oo]/,/[Dd]/,/[Aa]/,/[Yy]/]]},{"name":"time_period","bnf":[["time_period_const"],["between_tod_only"],["between_number_only"]]},{"name":"%%string4142","bnf":[[/[\x20-\x21]/],[/[\x23-\x5B]/],[/[\x5D-\uFFFF]/]],"fragment":true},{"name":"%%string4143","bnf":[[/\x22/],[/\x5C/],[/\x2F/],[/\x62/],[/\x66/],[/\x6E/],[/\x72/],[/\x74/],[/\x75/,"HEXDIG","HEXDIG","HEXDIG","HEXDIG"]],"fragment":true},{"name":"%string41","bnf":[["%%string4142"],[/\x5C/,"%%string4143"]],"fragment":true},{"name":"string","bnf":[["'\"'","%string41*","'\"'"]]},{"name":"HEXDIG","bnf":[[/[a-fA-F0-9]/]]}]
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.toLowerCase())
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.toLowerCase())
75
- dow.push(dowRange.children[1].text.toLowerCase())
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 'between_number_only': {
107
- // between_number_only has children[0] = between_number node
108
- const betweenNumber = tp.children[0]
109
- const startValue = RuleParser.__parseValue(betweenNumber.children[0])
110
- const endValue = RuleParser.__parseValue(betweenNumber.children[1])
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 = this._parseSimpleResult(result.children[0])
290
+ const partA = RuleParser._parseArithmeticOperand(result.children[0])
206
291
  const operatorFn = ArithmeticOperators[result.children[1].text]
207
- const partB = this.__parseResult(result, 2)
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