@halleyassist/rule-parser 1.0.11 → 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,7 @@
1
1
  # rule-parser
2
2
 
3
3
  The grammar for HalleyAssist rules
4
+
5
+ ## Documentation
6
+
7
+ - [Built-in Functions](BUILTIN_FUNCTIONS.md) - Complete documentation of all built-in functions, operators, and data structures used in the rule parser
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@halleyassist/rule-parser",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "The grammar for HalleyAssist rules",
5
5
  "main": "src/RuleParser.production.js",
6
6
  "scripts": {
package/src/RuleParser.js CHANGED
@@ -91,25 +91,22 @@ class RuleParser {
91
91
  return ret
92
92
  }
93
93
  static _parseDowRange(dowRange) {
94
- const dow = [];
95
94
  // dow_range can have 1 or 2 children (single day or range)
96
95
  if (dowRange.children.length === 1) {
97
- // Single day: ON MONDAY
98
- dow.push(normalizeDow(dowRange.children[0].text));
96
+ // Single day: ON MONDAY - return just the day string
97
+ return { start: normalizeDow(dowRange.children[0].text), end: normalizeDow(dowRange.children[0].text) };
99
98
  } else if (dowRange.children.length === 2) {
100
- // Range: ON MONDAY TO WEDNESDAY
101
- dow.push(normalizeDow(dowRange.children[0].text));
102
- dow.push(normalizeDow(dowRange.children[1].text));
99
+ // Range: ON MONDAY TO FRIDAY - return both start and end days
100
+ return { start: normalizeDow(dowRange.children[0].text), end: normalizeDow(dowRange.children[1].text) };
103
101
  } else {
104
102
  throw new Error(`Invalid dow_range with ${dowRange.children.length} children`);
105
103
  }
106
- return dow;
107
104
  }
108
105
  static _addDowToTods(startTod, endTod, dowRange) {
109
106
  if (dowRange && dowRange.type === 'dow_range') {
110
107
  const dow = RuleParser._parseDowRange(dowRange)
111
- startTod.dow = dow
112
- endTod.dow = dow
108
+ startTod.dow = dow.start
109
+ endTod.dow = dow.end
113
110
  }
114
111
  }
115
112
  static _parseTimePeriod(tp){
@@ -143,11 +140,13 @@ class RuleParser {
143
140
  // time_period_ago_between has children[0] = number_time, children[1] = between_tod_only
144
141
  const betweenTodOnly = tp.children[1]
145
142
  const betweenTod = betweenTodOnly.children[0]
146
- const startTod = RuleParser.__parseValue(betweenTod.children[0])
147
- const endTod = RuleParser.__parseValue(betweenTod.children[1])
143
+ let startTod = RuleParser.__parseValue(betweenTod.children[0])
144
+ let endTod = RuleParser.__parseValue(betweenTod.children[1])
148
145
 
149
146
  // Check if there's a dow_range at betweenTod.children[2]
150
147
  if (betweenTod.children.length > 2) {
148
+ if(typeof startTod === 'number') startTod = {seconds: startTod, dow: null}
149
+ if(typeof endTod === 'number') endTod = {seconds: endTod, dow: null}
151
150
  RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2])
152
151
  }
153
152
 
@@ -156,11 +155,13 @@ class RuleParser {
156
155
  case 'between_tod_only': {
157
156
  // between_tod_only has children[0] = between_tod node
158
157
  const betweenTod = tp.children[0]
159
- const startTod = RuleParser.__parseValue(betweenTod.children[0])
160
- const endTod = RuleParser.__parseValue(betweenTod.children[1])
158
+ let startTod = RuleParser.__parseValue(betweenTod.children[0])
159
+ let endTod = RuleParser.__parseValue(betweenTod.children[1])
161
160
 
162
161
  // Check if there's a dow_range at betweenTod.children[2]
163
162
  if (betweenTod.children.length > 2) {
163
+ if(typeof startTod === 'number') startTod = {seconds: startTod, dow: null}
164
+ if(typeof endTod === 'number') endTod = {seconds: endTod, dow: null}
164
165
  RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2])
165
166
  }
166
167
 
@@ -176,8 +177,13 @@ class RuleParser {
176
177
  // If DOW filters are provided, append them as additional parameters
177
178
  if (betweenNumberTime.children.length > 2 && betweenNumberTime.children[2].type === 'dow_range') {
178
179
  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]
180
+ if (dow.start === dow.end) {
181
+ // Single day: ["TimePeriodBetween", start, end, "MONDAY"]
182
+ return ["TimePeriodBetween", startValue, endValue, dow.start]
183
+ } else {
184
+ // Range: ["TimePeriodBetween", start, end, "MONDAY", "FRIDAY"]
185
+ return ["TimePeriodBetween", startValue, endValue, dow.start, dow.end]
186
+ }
181
187
  }
182
188
 
183
189
  return ["TimePeriodBetween", startValue, endValue]
@@ -285,12 +291,43 @@ class RuleParser {
285
291
  }
286
292
  throw new Error(`Unknown arithmetic operand type ${type}`)
287
293
  }
294
+ static _isConstantValue(expr){
295
+ // Check if an expression is a constant value
296
+ return Array.isArray(expr) && expr.length === 2 && expr[0] === 'Value' && typeof expr[1] === 'number'
297
+ }
298
+
299
+ static _evaluateConstantArithmetic(operator, leftValue, rightValue){
300
+ // Evaluate constant arithmetic operations at parse time
301
+ switch(operator){
302
+ case 'MathAdd':
303
+ return leftValue + rightValue
304
+ case 'MathSub':
305
+ return leftValue - rightValue
306
+ case 'MathMul':
307
+ return leftValue * rightValue
308
+ case 'MathDiv':
309
+ return leftValue / rightValue
310
+ case 'MathMod':
311
+ return leftValue % rightValue
312
+ default:
313
+ return null
314
+ }
315
+ }
316
+
288
317
  static _parseArithmeticResult(result){
289
318
  assert(result.children.length == 3)
290
319
  const partA = RuleParser._parseArithmeticOperand(result.children[0])
291
320
  const operatorFn = ArithmeticOperators[result.children[1].text]
292
321
  const partB = RuleParser.__parseArithmeticResult(result, 2)
293
322
 
323
+ // Compile out constant expressions
324
+ if (RuleParser._isConstantValue(partA) && RuleParser._isConstantValue(partB)) {
325
+ const result = RuleParser._evaluateConstantArithmetic(operatorFn, partA[1], partB[1])
326
+ if (result !== null) {
327
+ return ['Value', result]
328
+ }
329
+ }
330
+
294
331
  return [operatorFn, partA, partB]
295
332
  }
296
333
 
@@ -91,25 +91,22 @@ class RuleParser {
91
91
  return ret
92
92
  }
93
93
  static _parseDowRange(dowRange) {
94
- const dow = [];
95
94
  // dow_range can have 1 or 2 children (single day or range)
96
95
  if (dowRange.children.length === 1) {
97
- // Single day: ON MONDAY
98
- dow.push(normalizeDow(dowRange.children[0].text));
96
+ // Single day: ON MONDAY - return just the day string
97
+ return { start: normalizeDow(dowRange.children[0].text), end: normalizeDow(dowRange.children[0].text) };
99
98
  } else if (dowRange.children.length === 2) {
100
- // Range: ON MONDAY TO WEDNESDAY
101
- dow.push(normalizeDow(dowRange.children[0].text));
102
- dow.push(normalizeDow(dowRange.children[1].text));
99
+ // Range: ON MONDAY TO FRIDAY - return both start and end days
100
+ return { start: normalizeDow(dowRange.children[0].text), end: normalizeDow(dowRange.children[1].text) };
103
101
  } else {
104
102
  throw new Error(`Invalid dow_range with ${dowRange.children.length} children`);
105
103
  }
106
- return dow;
107
104
  }
108
105
  static _addDowToTods(startTod, endTod, dowRange) {
109
106
  if (dowRange && dowRange.type === 'dow_range') {
110
107
  const dow = RuleParser._parseDowRange(dowRange)
111
- startTod.dow = dow
112
- endTod.dow = dow
108
+ startTod.dow = dow.start
109
+ endTod.dow = dow.end
113
110
  }
114
111
  }
115
112
  static _parseTimePeriod(tp){
@@ -143,11 +140,13 @@ class RuleParser {
143
140
  // time_period_ago_between has children[0] = number_time, children[1] = between_tod_only
144
141
  const betweenTodOnly = tp.children[1]
145
142
  const betweenTod = betweenTodOnly.children[0]
146
- const startTod = RuleParser.__parseValue(betweenTod.children[0])
147
- const endTod = RuleParser.__parseValue(betweenTod.children[1])
143
+ let startTod = RuleParser.__parseValue(betweenTod.children[0])
144
+ let endTod = RuleParser.__parseValue(betweenTod.children[1])
148
145
 
149
146
  // Check if there's a dow_range at betweenTod.children[2]
150
147
  if (betweenTod.children.length > 2) {
148
+ if(typeof startTod === 'number') startTod = {seconds: startTod, dow: null}
149
+ if(typeof endTod === 'number') endTod = {seconds: endTod, dow: null}
151
150
  RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2])
152
151
  }
153
152
 
@@ -156,11 +155,13 @@ class RuleParser {
156
155
  case 'between_tod_only': {
157
156
  // between_tod_only has children[0] = between_tod node
158
157
  const betweenTod = tp.children[0]
159
- const startTod = RuleParser.__parseValue(betweenTod.children[0])
160
- const endTod = RuleParser.__parseValue(betweenTod.children[1])
158
+ let startTod = RuleParser.__parseValue(betweenTod.children[0])
159
+ let endTod = RuleParser.__parseValue(betweenTod.children[1])
161
160
 
162
161
  // Check if there's a dow_range at betweenTod.children[2]
163
162
  if (betweenTod.children.length > 2) {
163
+ if(typeof startTod === 'number') startTod = {seconds: startTod, dow: null}
164
+ if(typeof endTod === 'number') endTod = {seconds: endTod, dow: null}
164
165
  RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2])
165
166
  }
166
167
 
@@ -176,8 +177,13 @@ class RuleParser {
176
177
  // If DOW filters are provided, append them as additional parameters
177
178
  if (betweenNumberTime.children.length > 2 && betweenNumberTime.children[2].type === 'dow_range') {
178
179
  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]
180
+ if (dow.start === dow.end) {
181
+ // Single day: ["TimePeriodBetween", start, end, "MONDAY"]
182
+ return ["TimePeriodBetween", startValue, endValue, dow.start]
183
+ } else {
184
+ // Range: ["TimePeriodBetween", start, end, "MONDAY", "FRIDAY"]
185
+ return ["TimePeriodBetween", startValue, endValue, dow.start, dow.end]
186
+ }
181
187
  }
182
188
 
183
189
  return ["TimePeriodBetween", startValue, endValue]
@@ -285,12 +291,43 @@ class RuleParser {
285
291
  }
286
292
  throw new Error(`Unknown arithmetic operand type ${type}`)
287
293
  }
294
+ static _isConstantValue(expr){
295
+ // Check if an expression is a constant value
296
+ return Array.isArray(expr) && expr.length === 2 && expr[0] === 'Value' && typeof expr[1] === 'number'
297
+ }
298
+
299
+ static _evaluateConstantArithmetic(operator, leftValue, rightValue){
300
+ // Evaluate constant arithmetic operations at parse time
301
+ switch(operator){
302
+ case 'MathAdd':
303
+ return leftValue + rightValue
304
+ case 'MathSub':
305
+ return leftValue - rightValue
306
+ case 'MathMul':
307
+ return leftValue * rightValue
308
+ case 'MathDiv':
309
+ return leftValue / rightValue
310
+ case 'MathMod':
311
+ return leftValue % rightValue
312
+ default:
313
+ return null
314
+ }
315
+ }
316
+
288
317
  static _parseArithmeticResult(result){
289
318
  assert(result.children.length == 3)
290
319
  const partA = RuleParser._parseArithmeticOperand(result.children[0])
291
320
  const operatorFn = ArithmeticOperators[result.children[1].text]
292
321
  const partB = RuleParser.__parseArithmeticResult(result, 2)
293
322
 
323
+ // Compile out constant expressions
324
+ if (RuleParser._isConstantValue(partA) && RuleParser._isConstantValue(partB)) {
325
+ const result = RuleParser._evaluateConstantArithmetic(operatorFn, partA[1], partB[1])
326
+ if (result !== null) {
327
+ return ['Value', result]
328
+ }
329
+ }
330
+
294
331
  return [operatorFn, partA, partB]
295
332
  }
296
333