@halleyassist/rule-parser 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@halleyassist/rule-parser",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "The grammar for HalleyAssist rules",
5
5
  "main": "src/RuleParser.production.js",
6
6
  "scripts": {
@@ -45,9 +45,13 @@ arguments ::= argument*
45
45
  fname ::= [a-zA-z0-9]+
46
46
  fcall ::= fname WS* BEGIN_ARGUMENT WS* arguments? END_ARGUMENT
47
47
 
48
- between_number ||= number ((WS+ "AND" WS+) | (WS* "-" WS*)) number
49
- between_tod ||= number_tod ((WS+ "AND" WS+)) number_tod
48
+ between_number ||= (number_time | number) ((WS+ "AND" WS+) | (WS* "-" WS*)) (number_time | number)
49
+ between_tod ||= number_tod ((WS+ "AND" WS+)) number_tod (WS+ dow_range)?
50
50
  between ||= "BETWEEN" WS+ (between_number | between_tod)
51
+ dow ||= "MONDAY" | "TUESDAY" | "WEDNESDAY" | "THURSDAY" | "FRIDAY" | "SATURDAY" | "SUNDAY"
52
+ dow_range ||= "ON" WS+ dow (WS+ "TO" WS+ dow)?
53
+ between_number_only ||= "BETWEEN" WS+ between_number
54
+ between_tod_only ||= "BETWEEN" WS+ between_tod
51
55
 
52
56
  AND ||= (WS* "&&" WS*) | (WS+ "AND" WS+)
53
57
  OR ||= (WS* "||" WS*) | (WS+ "OR" WS+)
@@ -70,7 +74,7 @@ number_time ::= number WS+ unit
70
74
  number_tod ::= ([0-9]+) ":" ([0-9]+)
71
75
 
72
76
  time_period_const ||= "today"
73
- time_period ::= time_period_const | between
77
+ time_period ::= time_period_const | between_tod_only | between_number_only
74
78
 
75
79
  string ::= '"' (([#x20-#x21] | [#x23-#x5B] | [#x5D-#xFFFF]) | #x5C (#x22 | #x5C | #x2F | #x62 | #x66 | #x6E | #x72 | #x74 | #x75 HEXDIG HEXDIG HEXDIG HEXDIG))* '"'
76
80
  HEXDIG ::= [a-fA-F0-9]
package/src/RuleParser.js CHANGED
@@ -62,12 +62,55 @@ 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':
68
92
  return ["TimePeriodConst", tp.text]
69
- case 'between':
70
- return ["TimePeriodBetween", RuleParser.__parseValue(tp.children[0]?.children[0]), RuleParser.__parseValue(tp.children[0]?.children[1])]
93
+ case 'between_tod_only': {
94
+ // between_tod_only has children[0] = between_tod node
95
+ const betweenTod = tp.children[0]
96
+ const startTod = RuleParser.__parseValue(betweenTod.children[0])
97
+ const endTod = RuleParser.__parseValue(betweenTod.children[1])
98
+
99
+ // Check if there's a dow_range at betweenTod.children[2]
100
+ if (betweenTod.children.length > 2) {
101
+ RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2])
102
+ }
103
+
104
+ return ["TimePeriodBetween", startTod, endTod]
105
+ }
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])
111
+
112
+ return ["TimePeriodBetween", startValue, endValue]
113
+ }
71
114
  }
72
115
  }
73
116
  static __parseValue(child){
@@ -88,7 +131,7 @@ class RuleParser {
88
131
  const minutes = parseInt(tokens[1])
89
132
  const tod = hours * 100 + minutes
90
133
  const ret = { hours, minutes, tod }
91
- if (!isNaN(tod) && ret.hours >= 0 && ret.hours <= 24 && ret.minutes >= 0 && ret.minutes < 60) {
134
+ if (!isNaN(tod) && ret.hours >= 0 && ret.hours < 24 && ret.minutes >= 0 && ret.minutes < 60) {
92
135
  return ret
93
136
  }
94
137
  throw new Error(`Invalid time of day, ${child.text} -> [${tokens.join(', ')}] -> ${hours}h${minutes}m -> ${tod}`)
@@ -191,9 +234,39 @@ class RuleParser {
191
234
  case 2: {
192
235
  const rhs = expr.children[1]
193
236
  switch(rhs.type){
194
- case 'between_tod':
237
+ case 'between_tod': {
238
+ // Direct between_tod (without wrapping between node)
239
+ // between_tod has: children[0] = first tod, children[1] = second tod, children[2] = optional dow_range
240
+ const startTod = RuleParser.__parseValue(rhs.children[0])
241
+ const endTod = RuleParser.__parseValue(rhs.children[1])
242
+
243
+ // Check if there's a dow_range (children[2])
244
+ if (rhs.children.length > 2) {
245
+ RuleParser._addDowToTods(startTod, endTod, rhs.children[2])
246
+ }
247
+
248
+ return ['Between', RuleParser._parseResult(expr.children[0]), ['Value', startTod], ['Value', endTod]]
249
+ }
250
+ case 'between': {
251
+ // between wraps either between_number or between_tod
252
+ const betweenChild = rhs.children[0]
253
+ if (betweenChild.type === 'between_tod') {
254
+ // between_tod has: children[0] = first tod, children[1] = second tod, children[2] = optional dow_range
255
+ const startTod = RuleParser.__parseValue(betweenChild.children[0])
256
+ const endTod = RuleParser.__parseValue(betweenChild.children[1])
257
+
258
+ // Check if there's a dow_range (children[2])
259
+ if (betweenChild.children.length > 2) {
260
+ RuleParser._addDowToTods(startTod, endTod, betweenChild.children[2])
261
+ }
262
+
263
+ return ['Between', RuleParser._parseResult(expr.children[0]), ['Value', startTod], ['Value', endTod]]
264
+ } else {
265
+ // between_number - no dow support
266
+ return ['Between', RuleParser._parseResult(expr.children[0]), ['Value', RuleParser.__parseValue(betweenChild.children[0])], ['Value', RuleParser.__parseValue(betweenChild.children[1])]]
267
+ }
268
+ }
195
269
  case 'between_number':
196
- case 'between':
197
270
  return ['Between', RuleParser._parseResult(expr.children[0]), ['Value', RuleParser.__parseValue(rhs.children[0].children[0])], ['Value', RuleParser.__parseValue(rhs.children[0].children[1])]]
198
271
  case 'basic_rhs':
199
272
  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":"%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"]]},{"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_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]/]]}]
@@ -62,12 +62,55 @@ 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':
68
92
  return ["TimePeriodConst", tp.text]
69
- case 'between':
70
- return ["TimePeriodBetween", RuleParser.__parseValue(tp.children[0]?.children[0]), RuleParser.__parseValue(tp.children[0]?.children[1])]
93
+ case 'between_tod_only': {
94
+ // between_tod_only has children[0] = between_tod node
95
+ const betweenTod = tp.children[0]
96
+ const startTod = RuleParser.__parseValue(betweenTod.children[0])
97
+ const endTod = RuleParser.__parseValue(betweenTod.children[1])
98
+
99
+ // Check if there's a dow_range at betweenTod.children[2]
100
+ if (betweenTod.children.length > 2) {
101
+ RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2])
102
+ }
103
+
104
+ return ["TimePeriodBetween", startTod, endTod]
105
+ }
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])
111
+
112
+ return ["TimePeriodBetween", startValue, endValue]
113
+ }
71
114
  }
72
115
  }
73
116
  static __parseValue(child){
@@ -88,7 +131,7 @@ class RuleParser {
88
131
  const minutes = parseInt(tokens[1])
89
132
  const tod = hours * 100 + minutes
90
133
  const ret = { hours, minutes, tod }
91
- if (!isNaN(tod) && ret.hours >= 0 && ret.hours <= 24 && ret.minutes >= 0 && ret.minutes < 60) {
134
+ if (!isNaN(tod) && ret.hours >= 0 && ret.hours < 24 && ret.minutes >= 0 && ret.minutes < 60) {
92
135
  return ret
93
136
  }
94
137
  throw new Error(`Invalid time of day, ${child.text} -> [${tokens.join(', ')}] -> ${hours}h${minutes}m -> ${tod}`)
@@ -191,9 +234,39 @@ class RuleParser {
191
234
  case 2: {
192
235
  const rhs = expr.children[1]
193
236
  switch(rhs.type){
194
- case 'between_tod':
237
+ case 'between_tod': {
238
+ // Direct between_tod (without wrapping between node)
239
+ // between_tod has: children[0] = first tod, children[1] = second tod, children[2] = optional dow_range
240
+ const startTod = RuleParser.__parseValue(rhs.children[0])
241
+ const endTod = RuleParser.__parseValue(rhs.children[1])
242
+
243
+ // Check if there's a dow_range (children[2])
244
+ if (rhs.children.length > 2) {
245
+ RuleParser._addDowToTods(startTod, endTod, rhs.children[2])
246
+ }
247
+
248
+ return ['Between', RuleParser._parseResult(expr.children[0]), ['Value', startTod], ['Value', endTod]]
249
+ }
250
+ case 'between': {
251
+ // between wraps either between_number or between_tod
252
+ const betweenChild = rhs.children[0]
253
+ if (betweenChild.type === 'between_tod') {
254
+ // between_tod has: children[0] = first tod, children[1] = second tod, children[2] = optional dow_range
255
+ const startTod = RuleParser.__parseValue(betweenChild.children[0])
256
+ const endTod = RuleParser.__parseValue(betweenChild.children[1])
257
+
258
+ // Check if there's a dow_range (children[2])
259
+ if (betweenChild.children.length > 2) {
260
+ RuleParser._addDowToTods(startTod, endTod, betweenChild.children[2])
261
+ }
262
+
263
+ return ['Between', RuleParser._parseResult(expr.children[0]), ['Value', startTod], ['Value', endTod]]
264
+ } else {
265
+ // between_number - no dow support
266
+ return ['Between', RuleParser._parseResult(expr.children[0]), ['Value', RuleParser.__parseValue(betweenChild.children[0])], ['Value', RuleParser.__parseValue(betweenChild.children[1])]]
267
+ }
268
+ }
195
269
  case 'between_number':
196
- case 'between':
197
270
  return ['Between', RuleParser._parseResult(expr.children[0]), ['Value', RuleParser.__parseValue(rhs.children[0].children[0])], ['Value', RuleParser.__parseValue(rhs.children[0].children[1])]]
198
271
  case 'basic_rhs':
199
272
  return [OperatorFn[rhs.children[0].text], RuleParser._parseResult(expr.children[0]), RuleParser._parseResult(rhs.children[1])]