@halleyassist/rule-parser 1.0.8 → 1.0.10

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.8",
3
+ "version": "1.0.10",
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 */
@@ -45,13 +46,18 @@ arguments ::= argument*
45
46
  fname ::= [a-zA-z0-9]+
46
47
  fcall ::= fname WS* BEGIN_ARGUMENT WS* arguments? END_ARGUMENT
47
48
 
48
- between_number ||= number ((WS+ "AND" WS+) | (WS* "-" WS*)) number
49
- between_tod ||= number_tod ((WS+ "AND" WS+)) number_tod
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
51
+ between_tod ||= number_tod ((WS+ "AND" WS+)) number_tod (WS+ dow_range)?
50
52
  between ||= "BETWEEN" WS+ (between_number | between_tod)
53
+ dow ||= "MONDAY" | "TUESDAY" | "WEDNESDAY" | "THURSDAY" | "FRIDAY" | "SATURDAY" | "SUNDAY"
54
+ dow_range ||= "ON" WS+ dow (WS+ "TO" WS+ dow)?
55
+ between_time_only ||= "BETWEEN" WS+ between_number_time
51
56
  between_tod_only ||= "BETWEEN" WS+ between_tod
52
57
 
53
58
  AND ||= (WS* "&&" WS*) | (WS+ "AND" WS+)
54
59
  OR ||= (WS* "||" WS*) | (WS+ "OR" WS+)
60
+ AGO ||= "AGO"
55
61
  GT ::= ">"
56
62
  LT ::= "<"
57
63
  GTE ::= ">="
@@ -65,13 +71,15 @@ null ||= "null"
65
71
  true ||= "TRUE"
66
72
  array ::= BEGIN_ARRAY (value (VALUE_SEPARATOR value)*)? END_ARRAY
67
73
 
68
- 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"
69
75
  number ::= "-"? ([0-9]+) ("." [0-9]+)? ("e" ( "-" | "+" )? ("0" | [1-9] [0-9]*))?
70
76
  number_time ::= number WS+ unit
71
77
  number_tod ::= ([0-9]+) ":" ([0-9]+)
72
78
 
73
- time_period_const ||= "today"
74
- time_period ::= time_period_const | between_tod_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
75
83
 
76
84
  string ::= '"' (([#x20-#x21] | [#x23-#x5B] | [#x5D-#xFFFF]) | #x5C (#x22 | #x5C | #x2F | #x62 | #x66 | #x6E | #x72 | #x74 | #x75 HEXDIG HEXDIG HEXDIG HEXDIG))* '"'
77
85
  HEXDIG ::= [a-fA-F0-9]
package/src/RuleParser.js CHANGED
@@ -62,12 +62,92 @@ class RuleParser {
62
62
  }
63
63
  return ret
64
64
  }
65
+ static _parseDowRange(dowRange) {
66
+ const dow = []
67
+
68
+ // dow_range can have 1 or 2 children (single day or range)
69
+ if (dowRange.children.length === 1) {
70
+ // Single day: ON MONDAY
71
+ dow.push(dowRange.children[0].text.toLowerCase())
72
+ } else if (dowRange.children.length === 2) {
73
+ // Range: ON MONDAY TO WEDNESDAY
74
+ dow.push(dowRange.children[0].text.toLowerCase())
75
+ dow.push(dowRange.children[1].text.toLowerCase())
76
+ } else {
77
+ throw new Error(`Invalid dow_range with ${dowRange.children.length} children`)
78
+ }
79
+
80
+ return dow
81
+ }
82
+ static _addDowToTods(startTod, endTod, dowRange) {
83
+ if (dowRange && dowRange.type === 'dow_range') {
84
+ const dow = RuleParser._parseDowRange(dowRange)
85
+ startTod.dow = dow
86
+ endTod.dow = dow
87
+ }
88
+ }
65
89
  static _parseTimePeriod(tp){
66
90
  switch(tp.type){
67
91
  case 'time_period_const':
92
+ // Check if this is a time_period_ago (has children)
93
+ if (tp.children && tp.children.length > 0 && tp.children[0].type === 'time_period_ago') {
94
+ const timePeriodAgo = tp.children[0]
95
+ // Extract all number_time children and sum them up
96
+ let totalSeconds = 0
97
+ const components = []
98
+ for (const child of timePeriodAgo.children) {
99
+ if (child.type === 'number_time') {
100
+ const number = parseFloat(child.children[0].text)
101
+ const unit = child.children[1].text.toUpperCase()
102
+ components.push([number, unit])
103
+ // Parse the value to get seconds
104
+ totalSeconds += RuleParser.__parseValue(child)
105
+ }
106
+ }
107
+ // If there's only one component, use its number and unit
108
+ // Otherwise, use the total in seconds and "SECONDS" as the unit
109
+ if (components.length === 1) {
110
+ return ["TimePeriodConstAgo", components[0][0], components[0][1]]
111
+ } else {
112
+ return ["TimePeriodConstAgo", totalSeconds, "SECONDS"]
113
+ }
114
+ }
68
115
  return ["TimePeriodConst", tp.text]
69
- case 'between_tod_only':
70
- return ["TimePeriodBetween", RuleParser.__parseValue(tp.children[0]?.children[0]), RuleParser.__parseValue(tp.children[0]?.children[1])]
116
+ case 'time_period_ago_between': {
117
+ // time_period_ago_between has children[0] = number_time, children[1] = between_tod_only
118
+ const betweenTodOnly = tp.children[1]
119
+ const betweenTod = betweenTodOnly.children[0]
120
+ const startTod = RuleParser.__parseValue(betweenTod.children[0])
121
+ const endTod = RuleParser.__parseValue(betweenTod.children[1])
122
+
123
+ // Check if there's a dow_range at betweenTod.children[2]
124
+ if (betweenTod.children.length > 2) {
125
+ RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2])
126
+ }
127
+
128
+ return ["TimePeriodBetween", startTod, endTod]
129
+ }
130
+ case 'between_tod_only': {
131
+ // between_tod_only has children[0] = between_tod node
132
+ const betweenTod = tp.children[0]
133
+ const startTod = RuleParser.__parseValue(betweenTod.children[0])
134
+ const endTod = RuleParser.__parseValue(betweenTod.children[1])
135
+
136
+ // Check if there's a dow_range at betweenTod.children[2]
137
+ if (betweenTod.children.length > 2) {
138
+ RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2])
139
+ }
140
+
141
+ return ["TimePeriodBetween", startTod, endTod]
142
+ }
143
+ case 'between_time_only': {
144
+ // between_number_only has children[0] = between_number node
145
+ const betweenNumber = tp.children[0]
146
+ const startValue = RuleParser.__parseValue(betweenNumber.children[0])
147
+ const endValue = RuleParser.__parseValue(betweenNumber.children[1])
148
+
149
+ return ["TimePeriodBetween", startValue, endValue]
150
+ }
71
151
  }
72
152
  }
73
153
  static __parseValue(child){
@@ -157,15 +237,42 @@ class RuleParser {
157
237
  }
158
238
  return null
159
239
  }
240
+ static _parseArithmeticOperand(operand){
241
+ assert(operand.children.length == 1)
242
+ const child = operand.children[0]
243
+ const type = child.type
244
+ switch(type){
245
+ case 'fcall':
246
+ return RuleParser._parseFcall(child)
247
+ case 'number':
248
+ return ['Value', parseFloat(child.text)]
249
+ case 'number_time':
250
+ return ['Value', RuleParser.__parseValue(child)]
251
+ }
252
+ throw new Error(`Unknown arithmetic operand type ${type}`)
253
+ }
160
254
  static _parseArithmeticResult(result){
161
255
  assert(result.children.length == 3)
162
- const partA = this._parseSimpleResult(result.children[0])
256
+ const partA = RuleParser._parseArithmeticOperand(result.children[0])
163
257
  const operatorFn = ArithmeticOperators[result.children[1].text]
164
- const partB = this.__parseResult(result, 2)
258
+ const partB = RuleParser.__parseArithmeticResult(result, 2)
165
259
 
166
260
  return [operatorFn, partA, partB]
167
261
  }
168
262
 
263
+ static __parseArithmeticResult(result, idx){
264
+ const child = result.children[idx]
265
+ const type = child.type
266
+ switch(type){
267
+ case 'arithmetic_operand':
268
+ return RuleParser._parseArithmeticOperand(child)
269
+ case 'arithmetic_result':
270
+ return RuleParser._parseArithmeticResult(child)
271
+ }
272
+
273
+ throw new Error(`Unknown arithmetic result node ${type}`)
274
+ }
275
+
169
276
  static __parseResult(result, idx){
170
277
  const child = result.children[idx]
171
278
  const type = child.type
@@ -191,9 +298,39 @@ class RuleParser {
191
298
  case 2: {
192
299
  const rhs = expr.children[1]
193
300
  switch(rhs.type){
194
- case 'between_tod':
301
+ case 'between_tod': {
302
+ // Direct between_tod (without wrapping between node)
303
+ // between_tod has: children[0] = first tod, children[1] = second tod, children[2] = optional dow_range
304
+ const startTod = RuleParser.__parseValue(rhs.children[0])
305
+ const endTod = RuleParser.__parseValue(rhs.children[1])
306
+
307
+ // Check if there's a dow_range (children[2])
308
+ if (rhs.children.length > 2) {
309
+ RuleParser._addDowToTods(startTod, endTod, rhs.children[2])
310
+ }
311
+
312
+ return ['Between', RuleParser._parseResult(expr.children[0]), ['Value', startTod], ['Value', endTod]]
313
+ }
314
+ case 'between': {
315
+ // between wraps either between_number or between_tod
316
+ const betweenChild = rhs.children[0]
317
+ if (betweenChild.type === 'between_tod') {
318
+ // between_tod has: children[0] = first tod, children[1] = second tod, children[2] = optional dow_range
319
+ const startTod = RuleParser.__parseValue(betweenChild.children[0])
320
+ const endTod = RuleParser.__parseValue(betweenChild.children[1])
321
+
322
+ // Check if there's a dow_range (children[2])
323
+ if (betweenChild.children.length > 2) {
324
+ RuleParser._addDowToTods(startTod, endTod, betweenChild.children[2])
325
+ }
326
+
327
+ return ['Between', RuleParser._parseResult(expr.children[0]), ['Value', startTod], ['Value', endTod]]
328
+ } else {
329
+ // between_number - no dow support
330
+ return ['Between', RuleParser._parseResult(expr.children[0]), ['Value', RuleParser.__parseValue(betweenChild.children[0])], ['Value', RuleParser.__parseValue(betweenChild.children[1])]]
331
+ }
332
+ }
195
333
  case 'between_number':
196
- case 'between':
197
334
  return ['Between', RuleParser._parseResult(expr.children[0]), ['Value', RuleParser.__parseValue(rhs.children[0].children[0])], ['Value', RuleParser.__parseValue(rhs.children[0].children[1])]]
198
335
  case 'basic_rhs':
199
336
  return [OperatorFn[rhs.children[0].text], RuleParser._parseResult(expr.children[0]), RuleParser._parseResult(rhs.children[1])]
@@ -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_number1112","bnf":[["WS+",/[Aa]/,/[Nn]/,/[Dd]/,"WS+"]],"fragment":true},{"name":"%%between_number1113","bnf":[["WS*",/\-/,"WS*"]],"fragment":true},{"name":"%between_number11","bnf":[["%%between_number1112"],["%%between_number1113"]],"fragment":true},{"name":"between_number","bnf":[["number","%between_number11","number"]]},{"name":"%%between_tod1415","bnf":[["WS+",/[Aa]/,/[Nn]/,/[Dd]/,"WS+"]],"fragment":true},{"name":"%between_tod14","bnf":[["%%between_tod1415"]],"fragment":true},{"name":"between_tod","bnf":[["number_tod","%between_tod14","number_tod"]]},{"name":"%between16","bnf":[["between_number"],["between_tod"]],"fragment":true},{"name":"between","bnf":[[/[Bb]/,/[Ee]/,/[Tt]/,/[Ww]/,/[Ee]/,/[Ee]/,/[Nn]/,"WS+","%between16"]]},{"name":"between_tod_only","bnf":[[/[Bb]/,/[Ee]/,/[Tt]/,/[Ww]/,/[Ee]/,/[Ee]/,/[Nn]/,"WS+","between_tod"]]},{"name":"%AND17","bnf":[["WS*",/&/,/&/,"WS*"]],"fragment":true},{"name":"%AND18","bnf":[["WS+",/[Aa]/,/[Nn]/,/[Dd]/,"WS+"]],"fragment":true},{"name":"AND","bnf":[["%AND17"],["%AND18"]]},{"name":"%OR19","bnf":[["WS*",/\|/,/\|/,"WS*"]],"fragment":true},{"name":"%OR20","bnf":[["WS+",/[Oo]/,/[Rr]/,"WS+"]],"fragment":true},{"name":"OR","bnf":[["%OR19"],["%OR20"]]},{"name":"GT","bnf":[["\">\""]]},{"name":"LT","bnf":[["\"<\""]]},{"name":"GTE","bnf":[["\">=\""]]},{"name":"LTE","bnf":[["\"<=\""]]},{"name":"IS","bnf":[[/[Ii]/,/[Ss]/]]},{"name":"EQ","bnf":[["\"==\""],["\"=\""]]},{"name":"NEQ","bnf":[["\"!=\""]]},{"name":"%NOT21","bnf":[[/!/,"WS*"]],"fragment":true},{"name":"%NOT22","bnf":[[/[Nn]/,/[Oo]/,/[Tt]/,"WS+"]],"fragment":true},{"name":"NOT","bnf":[["%NOT21"],["%NOT22"]]},{"name":"false","bnf":[[/[Ff]/,/[Aa]/,/[Ll]/,/[Ss]/,/[Ee]/]]},{"name":"null","bnf":[[/[Nn]/,/[Uu]/,/[Ll]/,/[Ll]/]]},{"name":"true","bnf":[[/[Tt]/,/[Rr]/,/[Uu]/,/[Ee]/]]},{"name":"%%array2324","bnf":[["VALUE_SEPARATOR","value"]],"fragment":true},{"name":"%array23","bnf":[["value","%%array2324*"]],"fragment":true},{"name":"array","bnf":[["BEGIN_ARRAY","%array23?","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":"%%number2526","bnf":[[/[0-9]/]]},{"name":"%number25","bnf":[["%%number2526+"]],"fragment":true},{"name":"%%number2728","bnf":[[/[0-9]/]]},{"name":"%number27","bnf":[["\".\"","%%number2728+"]],"fragment":true},{"name":"%%number2930","bnf":[["\"-\""],["\"+\""]],"fragment":true},{"name":"%%%number293132","bnf":[[/[0-9]/]]},{"name":"%%number2931","bnf":[["\"0\""],[/[1-9]/,"%%%number293132*"]],"fragment":true},{"name":"%number29","bnf":[["\"e\"","%%number2930?","%%number2931"]],"fragment":true},{"name":"number","bnf":[["\"-\"?","%number25","%number27?","%number29?"]]},{"name":"number_time","bnf":[["number","WS+","unit"]]},{"name":"%%number_tod3334","bnf":[[/[0-9]/]]},{"name":"%number_tod33","bnf":[["%%number_tod3334+"]],"fragment":true},{"name":"%%number_tod3536","bnf":[[/[0-9]/]]},{"name":"%number_tod35","bnf":[["%%number_tod3536+"]],"fragment":true},{"name":"number_tod","bnf":[["%number_tod33","\":\"","%number_tod35"]]},{"name":"time_period_const","bnf":[[/[Tt]/,/[Oo]/,/[Dd]/,/[Aa]/,/[Yy]/]]},{"name":"time_period","bnf":[["time_period_const"],["between_tod_only"]]},{"name":"%%string3738","bnf":[[/[\x20-\x21]/],[/[\x23-\x5B]/],[/[\x5D-\uFFFF]/]],"fragment":true},{"name":"%%string3739","bnf":[[/\x22/],[/\x5C/],[/\x2F/],[/\x62/],[/\x66/],[/\x6E/],[/\x72/],[/\x74/],[/\x75/,"HEXDIG","HEXDIG","HEXDIG","HEXDIG"]],"fragment":true},{"name":"%string37","bnf":[["%%string3738"],[/\x5C/,"%%string3739"]],"fragment":true},{"name":"string","bnf":[["'\"'","%string37*","'\"'"]]},{"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_time","bnf":[["number_time","%between_number_time16","number_time"]]},{"name":"%%between_tod1920","bnf":[["WS+",/[Aa]/,/[Nn]/,/[Dd]/,"WS+"]],"fragment":true},{"name":"%between_tod19","bnf":[["%%between_tod1920"]],"fragment":true},{"name":"%between_tod21","bnf":[["WS+","dow_range"]],"fragment":true},{"name":"between_tod","bnf":[["number_tod","%between_tod19","number_tod","%between_tod21?"]]},{"name":"%between22","bnf":[["between_number"],["between_tod"]],"fragment":true},{"name":"between","bnf":[[/[Bb]/,/[Ee]/,/[Tt]/,/[Ww]/,/[Ee]/,/[Ee]/,/[Nn]/,"WS+","%between22"]]},{"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_range23","bnf":[["WS+",/[Tt]/,/[Oo]/,"WS+","dow"]],"fragment":true},{"name":"dow_range","bnf":[[/[Oo]/,/[Nn]/,"WS+","dow","%dow_range23?"]]},{"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":"%AND24","bnf":[["WS*",/&/,/&/,"WS*"]],"fragment":true},{"name":"%AND25","bnf":[["WS+",/[Aa]/,/[Nn]/,/[Dd]/,"WS+"]],"fragment":true},{"name":"AND","bnf":[["%AND24"],["%AND25"]]},{"name":"%OR26","bnf":[["WS*",/\|/,/\|/,"WS*"]],"fragment":true},{"name":"%OR27","bnf":[["WS+",/[Oo]/,/[Rr]/,"WS+"]],"fragment":true},{"name":"OR","bnf":[["%OR26"],["%OR27"]]},{"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":"%NOT28","bnf":[[/!/,"WS*"]],"fragment":true},{"name":"%NOT29","bnf":[[/[Nn]/,/[Oo]/,/[Tt]/,"WS+"]],"fragment":true},{"name":"NOT","bnf":[["%NOT28"],["%NOT29"]]},{"name":"false","bnf":[[/[Ff]/,/[Aa]/,/[Ll]/,/[Ss]/,/[Ee]/]]},{"name":"null","bnf":[[/[Nn]/,/[Uu]/,/[Ll]/,/[Ll]/]]},{"name":"true","bnf":[[/[Tt]/,/[Rr]/,/[Uu]/,/[Ee]/]]},{"name":"%%array3031","bnf":[["VALUE_SEPARATOR","value"]],"fragment":true},{"name":"%array30","bnf":[["value","%%array3031*"]],"fragment":true},{"name":"array","bnf":[["BEGIN_ARRAY","%array30?","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":"%%number3233","bnf":[[/[0-9]/]]},{"name":"%number32","bnf":[["%%number3233+"]],"fragment":true},{"name":"%%number3435","bnf":[[/[0-9]/]]},{"name":"%number34","bnf":[["\".\"","%%number3435+"]],"fragment":true},{"name":"%%number3637","bnf":[["\"-\""],["\"+\""]],"fragment":true},{"name":"%%%number363839","bnf":[[/[0-9]/]]},{"name":"%%number3638","bnf":[["\"0\""],[/[1-9]/,"%%%number363839*"]],"fragment":true},{"name":"%number36","bnf":[["\"e\"","%%number3637?","%%number3638"]],"fragment":true},{"name":"number","bnf":[["\"-\"?","%number32","%number34?","%number36?"]]},{"name":"number_time","bnf":[["number","WS+","unit"]]},{"name":"%%number_tod4041","bnf":[[/[0-9]/]]},{"name":"%number_tod40","bnf":[["%%number_tod4041+"]],"fragment":true},{"name":"%%number_tod4243","bnf":[[/[0-9]/]]},{"name":"%number_tod42","bnf":[["%%number_tod4243+"]],"fragment":true},{"name":"number_tod","bnf":[["%number_tod40","\":\"","%number_tod42"]]},{"name":"%time_period_ago44","bnf":[["WS+","number_time"]],"fragment":true},{"name":"time_period_ago","bnf":[["number_time","%time_period_ago44*","WS+","AGO"]]},{"name":"%time_period_ago_between45","bnf":[["WS+","number_time"]],"fragment":true},{"name":"time_period_ago_between","bnf":[["number_time","%time_period_ago_between45*","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":"%%string4647","bnf":[[/[\x20-\x21]/],[/[\x23-\x5B]/],[/[\x5D-\uFFFF]/]],"fragment":true},{"name":"%%string4648","bnf":[[/\x22/],[/\x5C/],[/\x2F/],[/\x62/],[/\x66/],[/\x6E/],[/\x72/],[/\x74/],[/\x75/,"HEXDIG","HEXDIG","HEXDIG","HEXDIG"]],"fragment":true},{"name":"%string46","bnf":[["%%string4647"],[/\x5C/,"%%string4648"]],"fragment":true},{"name":"string","bnf":[["'\"'","%string46*","'\"'"]]},{"name":"HEXDIG","bnf":[[/[a-fA-F0-9]/]]}]
@@ -62,12 +62,92 @@ class RuleParser {
62
62
  }
63
63
  return ret
64
64
  }
65
+ static _parseDowRange(dowRange) {
66
+ const dow = []
67
+
68
+ // dow_range can have 1 or 2 children (single day or range)
69
+ if (dowRange.children.length === 1) {
70
+ // Single day: ON MONDAY
71
+ dow.push(dowRange.children[0].text.toLowerCase())
72
+ } else if (dowRange.children.length === 2) {
73
+ // Range: ON MONDAY TO WEDNESDAY
74
+ dow.push(dowRange.children[0].text.toLowerCase())
75
+ dow.push(dowRange.children[1].text.toLowerCase())
76
+ } else {
77
+ throw new Error(`Invalid dow_range with ${dowRange.children.length} children`)
78
+ }
79
+
80
+ return dow
81
+ }
82
+ static _addDowToTods(startTod, endTod, dowRange) {
83
+ if (dowRange && dowRange.type === 'dow_range') {
84
+ const dow = RuleParser._parseDowRange(dowRange)
85
+ startTod.dow = dow
86
+ endTod.dow = dow
87
+ }
88
+ }
65
89
  static _parseTimePeriod(tp){
66
90
  switch(tp.type){
67
91
  case 'time_period_const':
92
+ // Check if this is a time_period_ago (has children)
93
+ if (tp.children && tp.children.length > 0 && tp.children[0].type === 'time_period_ago') {
94
+ const timePeriodAgo = tp.children[0]
95
+ // Extract all number_time children and sum them up
96
+ let totalSeconds = 0
97
+ const components = []
98
+ for (const child of timePeriodAgo.children) {
99
+ if (child.type === 'number_time') {
100
+ const number = parseFloat(child.children[0].text)
101
+ const unit = child.children[1].text.toUpperCase()
102
+ components.push([number, unit])
103
+ // Parse the value to get seconds
104
+ totalSeconds += RuleParser.__parseValue(child)
105
+ }
106
+ }
107
+ // If there's only one component, use its number and unit
108
+ // Otherwise, use the total in seconds and "SECONDS" as the unit
109
+ if (components.length === 1) {
110
+ return ["TimePeriodConstAgo", components[0][0], components[0][1]]
111
+ } else {
112
+ return ["TimePeriodConstAgo", totalSeconds, "SECONDS"]
113
+ }
114
+ }
68
115
  return ["TimePeriodConst", tp.text]
69
- case 'between_tod_only':
70
- return ["TimePeriodBetween", RuleParser.__parseValue(tp.children[0]?.children[0]), RuleParser.__parseValue(tp.children[0]?.children[1])]
116
+ case 'time_period_ago_between': {
117
+ // time_period_ago_between has children[0] = number_time, children[1] = between_tod_only
118
+ const betweenTodOnly = tp.children[1]
119
+ const betweenTod = betweenTodOnly.children[0]
120
+ const startTod = RuleParser.__parseValue(betweenTod.children[0])
121
+ const endTod = RuleParser.__parseValue(betweenTod.children[1])
122
+
123
+ // Check if there's a dow_range at betweenTod.children[2]
124
+ if (betweenTod.children.length > 2) {
125
+ RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2])
126
+ }
127
+
128
+ return ["TimePeriodBetween", startTod, endTod]
129
+ }
130
+ case 'between_tod_only': {
131
+ // between_tod_only has children[0] = between_tod node
132
+ const betweenTod = tp.children[0]
133
+ const startTod = RuleParser.__parseValue(betweenTod.children[0])
134
+ const endTod = RuleParser.__parseValue(betweenTod.children[1])
135
+
136
+ // Check if there's a dow_range at betweenTod.children[2]
137
+ if (betweenTod.children.length > 2) {
138
+ RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2])
139
+ }
140
+
141
+ return ["TimePeriodBetween", startTod, endTod]
142
+ }
143
+ case 'between_time_only': {
144
+ // between_number_only has children[0] = between_number node
145
+ const betweenNumber = tp.children[0]
146
+ const startValue = RuleParser.__parseValue(betweenNumber.children[0])
147
+ const endValue = RuleParser.__parseValue(betweenNumber.children[1])
148
+
149
+ return ["TimePeriodBetween", startValue, endValue]
150
+ }
71
151
  }
72
152
  }
73
153
  static __parseValue(child){
@@ -157,15 +237,42 @@ class RuleParser {
157
237
  }
158
238
  return null
159
239
  }
240
+ static _parseArithmeticOperand(operand){
241
+ assert(operand.children.length == 1)
242
+ const child = operand.children[0]
243
+ const type = child.type
244
+ switch(type){
245
+ case 'fcall':
246
+ return RuleParser._parseFcall(child)
247
+ case 'number':
248
+ return ['Value', parseFloat(child.text)]
249
+ case 'number_time':
250
+ return ['Value', RuleParser.__parseValue(child)]
251
+ }
252
+ throw new Error(`Unknown arithmetic operand type ${type}`)
253
+ }
160
254
  static _parseArithmeticResult(result){
161
255
  assert(result.children.length == 3)
162
- const partA = this._parseSimpleResult(result.children[0])
256
+ const partA = RuleParser._parseArithmeticOperand(result.children[0])
163
257
  const operatorFn = ArithmeticOperators[result.children[1].text]
164
- const partB = this.__parseResult(result, 2)
258
+ const partB = RuleParser.__parseArithmeticResult(result, 2)
165
259
 
166
260
  return [operatorFn, partA, partB]
167
261
  }
168
262
 
263
+ static __parseArithmeticResult(result, idx){
264
+ const child = result.children[idx]
265
+ const type = child.type
266
+ switch(type){
267
+ case 'arithmetic_operand':
268
+ return RuleParser._parseArithmeticOperand(child)
269
+ case 'arithmetic_result':
270
+ return RuleParser._parseArithmeticResult(child)
271
+ }
272
+
273
+ throw new Error(`Unknown arithmetic result node ${type}`)
274
+ }
275
+
169
276
  static __parseResult(result, idx){
170
277
  const child = result.children[idx]
171
278
  const type = child.type
@@ -191,9 +298,39 @@ class RuleParser {
191
298
  case 2: {
192
299
  const rhs = expr.children[1]
193
300
  switch(rhs.type){
194
- case 'between_tod':
301
+ case 'between_tod': {
302
+ // Direct between_tod (without wrapping between node)
303
+ // between_tod has: children[0] = first tod, children[1] = second tod, children[2] = optional dow_range
304
+ const startTod = RuleParser.__parseValue(rhs.children[0])
305
+ const endTod = RuleParser.__parseValue(rhs.children[1])
306
+
307
+ // Check if there's a dow_range (children[2])
308
+ if (rhs.children.length > 2) {
309
+ RuleParser._addDowToTods(startTod, endTod, rhs.children[2])
310
+ }
311
+
312
+ return ['Between', RuleParser._parseResult(expr.children[0]), ['Value', startTod], ['Value', endTod]]
313
+ }
314
+ case 'between': {
315
+ // between wraps either between_number or between_tod
316
+ const betweenChild = rhs.children[0]
317
+ if (betweenChild.type === 'between_tod') {
318
+ // between_tod has: children[0] = first tod, children[1] = second tod, children[2] = optional dow_range
319
+ const startTod = RuleParser.__parseValue(betweenChild.children[0])
320
+ const endTod = RuleParser.__parseValue(betweenChild.children[1])
321
+
322
+ // Check if there's a dow_range (children[2])
323
+ if (betweenChild.children.length > 2) {
324
+ RuleParser._addDowToTods(startTod, endTod, betweenChild.children[2])
325
+ }
326
+
327
+ return ['Between', RuleParser._parseResult(expr.children[0]), ['Value', startTod], ['Value', endTod]]
328
+ } else {
329
+ // between_number - no dow support
330
+ return ['Between', RuleParser._parseResult(expr.children[0]), ['Value', RuleParser.__parseValue(betweenChild.children[0])], ['Value', RuleParser.__parseValue(betweenChild.children[1])]]
331
+ }
332
+ }
195
333
  case 'between_number':
196
- case 'between':
197
334
  return ['Between', RuleParser._parseResult(expr.children[0]), ['Value', RuleParser.__parseValue(rhs.children[0].children[0])], ['Value', RuleParser.__parseValue(rhs.children[0].children[1])]]
198
335
  case 'basic_rhs':
199
336
  return [OperatorFn[rhs.children[0].text], RuleParser._parseResult(expr.children[0]), RuleParser._parseResult(rhs.children[1])]