@halleyassist/rule-templater 0.0.8 → 0.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.
@@ -1,43 +1,33 @@
1
1
  (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.RuleTemplater = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
2
2
  module.exports=[{"name":"statement_main","bnf":[["statement","EOF"]]},{"name":"logical_operator","bnf":[["AND"],["OR"]]},{"name":"%statement[2]","bnf":[["logical_operator","expression"]],"fragment":true},{"name":"statement","bnf":[["expression","%statement[2]*"]]},{"name":"expression","bnf":[["not_expression"],["standard_expression"],["parenthesis_expression"]]},{"name":"parenthesis_expression","bnf":[["BEGIN_PARENTHESIS","WS*","statement","WS*","END_PARENTHESIS"]]},{"name":"%not_expression[2]","bnf":[["result"],["parenthesis_expression"]],"fragment":true},{"name":"not_expression","bnf":[["NOT","%not_expression[2]"]]},{"name":"%standard_expression[2][1]","bnf":[["WS*","eq_approx"]],"fragment":true},{"name":"%standard_expression[2][2]","bnf":[["WS*","basic_rhs"]],"fragment":true},{"name":"%standard_expression[2][3][1]","bnf":[["WS+","IS"]],"fragment":true},{"name":"%standard_expression[2][3]","bnf":[["%standard_expression[2][3][1]?","WS+","between"]],"fragment":true},{"name":"%standard_expression[2][4]","bnf":[["WS+","in_expr"]],"fragment":true},{"name":"%standard_expression[2]","bnf":[["%standard_expression[2][1]"],["%standard_expression[2][2]"],["%standard_expression[2][3]"],["%standard_expression[2][4]"]],"fragment":true},{"name":"standard_expression","bnf":[["result","%standard_expression[2]?"]]},{"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":"number_atom","bnf":[["number"]]},{"name":"number_time_atom","bnf":[["number_time"]]},{"name":"tod_atom","bnf":[["number_tod"]]},{"name":"dow_atom","bnf":[["dow"]]},{"name":"arithmetic_operand","bnf":[["fcall"],["number_time_atom"],["number_atom"]]},{"name":"%arithmetic_result[5]","bnf":[["arithmetic_result"],["arithmetic_operand"]],"fragment":true},{"name":"arithmetic_result","bnf":[["arithmetic_operand","WS*","arithmetic_operator","WS*","%arithmetic_result[5]"]]},{"name":"simple_result","bnf":[["fcall"],["value"]]},{"name":"result","bnf":[["arithmetic_result"],["simple_result"]]},{"name":"value_atom","bnf":[["false"],["true"],["array"],["time_period"],["number_time_atom"],["number_atom"],["tod_atom"],["string"]]},{"name":"value","bnf":[["value_atom"]]},{"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":"WS","bnf":[[/[\x20\x09\x0A\x0D]/]]},{"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":"BEGIN_IN","bnf":[[/[Ii]/,/[Nn]/]]},{"name":"in_expr","bnf":[["BEGIN_IN","WS*","BEGIN_PARENTHESIS","WS*","arguments","END_PARENTHESIS"]]},{"name":"argument","bnf":[["statement","WS*"]]},{"name":"%arguments[2]","bnf":[["WS*","\",\"","WS*","argument"]],"fragment":true},{"name":"arguments","bnf":[["argument","%arguments[2]*"]]},{"name":"%fname[1]","bnf":[[/[A-Za-z0-9]/]]},{"name":"fname","bnf":[["%fname[1]+"]]},{"name":"fcall","bnf":[["fname","WS*","BEGIN_ARGUMENT","WS*","arguments?","END_ARGUMENT"]]},{"name":"%between_dash_or_and[1]","bnf":[["WS+",/[Aa]/,/[Nn]/,/[Dd]/,"WS+"]],"fragment":true},{"name":"%between_dash_or_and[2]","bnf":[["WS*",/\-/,"WS*"]],"fragment":true},{"name":"between_dash_or_and","bnf":[["%between_dash_or_and[1]"],["%between_dash_or_and[2]"]]},{"name":"between_number_inner","bnf":[["number_atom"],["number_time_atom"]]},{"name":"between_number","bnf":[["between_number_inner","between_dash_or_and","between_number_inner"]]},{"name":"between_number_time_inner","bnf":[["number_time_atom"]]},{"name":"%between_number_time[4]","bnf":[["WS+","dow_range"]],"fragment":true},{"name":"between_number_time","bnf":[["between_number_time_inner","between_dash_or_and","between_number_time_inner","%between_number_time[4]?"]]},{"name":"between_tod_inner","bnf":[["tod_atom"]]},{"name":"%between_tod[2]","bnf":[["WS+",/[Aa]/,/[Nn]/,/[Dd]/,"WS+"]],"fragment":true},{"name":"%between_tod[4]","bnf":[["WS+","dow_range"]],"fragment":true},{"name":"between_tod","bnf":[["between_tod_inner","%between_tod[2]","between_tod_inner","%between_tod[4]?"]]},{"name":"%between[3]","bnf":[["between_number"],["between_tod"]],"fragment":true},{"name":"between","bnf":[[/[Bb]/,/[Ee]/,/[Tt]/,/[Ww]/,/[Ee]/,/[Ee]/,/[Nn]/,"WS+","%between[3]"]]},{"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_range_inner","bnf":[["dow_atom"]]},{"name":"%dow_range[4]","bnf":[["WS+",/[Tt]/,/[Oo]/,"WS+","dow_range_inner"]],"fragment":true},{"name":"dow_range","bnf":[[/[Oo]/,/[Nn]/,"WS+","dow_range_inner","%dow_range[4]?"]]},{"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":"between_time_only_atom","bnf":[["between_time_only"]]},{"name":"between_tod_only_atom","bnf":[["between_tod_only"]]},{"name":"%AND[1]","bnf":[["WS*",/&/,/&/,"WS*"]],"fragment":true},{"name":"%AND[2]","bnf":[["WS+",/[Aa]/,/[Nn]/,/[Dd]/,"WS+"]],"fragment":true},{"name":"AND","bnf":[["%AND[1]"],["%AND[2]"]]},{"name":"%OR[1]","bnf":[["WS*",/\|/,/\|/,"WS*"]],"fragment":true},{"name":"%OR[2]","bnf":[["WS+",/[Oo]/,/[Rr]/,"WS+"]],"fragment":true},{"name":"OR","bnf":[["%OR[1]"],["%OR[2]"]]},{"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":"%NOT[1]","bnf":[[/!/,"WS*"]],"fragment":true},{"name":"%NOT[2]","bnf":[[/[Nn]/,/[Oo]/,/[Tt]/,"WS+"]],"fragment":true},{"name":"NOT","bnf":[["%NOT[1]"],["%NOT[2]"]]},{"name":"false","bnf":[[/[Ff]/,/[Aa]/,/[Ll]/,/[Ss]/,/[Ee]/]]},{"name":"null","bnf":[[/[Nn]/,/[Uu]/,/[Ll]/,/[Ll]/]]},{"name":"true","bnf":[[/[Tt]/,/[Rr]/,/[Uu]/,/[Ee]/]]},{"name":"%array[2][2]","bnf":[["VALUE_SEPARATOR","value"]],"fragment":true},{"name":"%array[2]","bnf":[["value","%array[2][2]*"]],"fragment":true},{"name":"array","bnf":[["BEGIN_ARRAY","%array[2]?","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":"%number[2][1]","bnf":[[/[0-9]/]]},{"name":"%number[2]","bnf":[["%number[2][1]+"]],"fragment":true},{"name":"%number[3][2]","bnf":[[/[0-9]/]]},{"name":"%number[3]","bnf":[["\".\"","%number[3][2]+"]],"fragment":true},{"name":"%number[4][2]","bnf":[["\"-\""],["\"+\""]],"fragment":true},{"name":"%number[4][3][2]","bnf":[[/[0-9]/]]},{"name":"%number[4][3]","bnf":[["\"0\""],[/[1-9]/,"%number[4][3][2]*"]],"fragment":true},{"name":"%number[4]","bnf":[["\"e\"","%number[4][2]?","%number[4][3]"]],"fragment":true},{"name":"number","bnf":[["\"-\"?","%number[2]","%number[3]?","%number[4]?"]]},{"name":"number_time","bnf":[["number","WS+","unit"]]},{"name":"%number_tod[1][1]","bnf":[[/[0-9]/]]},{"name":"%number_tod[1]","bnf":[["%number_tod[1][1]+"]],"fragment":true},{"name":"%number_tod[3][1]","bnf":[[/[0-9]/]]},{"name":"%number_tod[3]","bnf":[["%number_tod[3][1]+"]],"fragment":true},{"name":"number_tod","bnf":[["%number_tod[1]","\":\"","%number_tod[3]"]]},{"name":"%time_period_ago[2]","bnf":[["WS+","number_time_atom"]],"fragment":true},{"name":"time_period_ago","bnf":[["number_time_atom","%time_period_ago[2]*","WS+","AGO"]]},{"name":"%time_period_ago_between[2]","bnf":[["WS+","number_time_atom"]],"fragment":true},{"name":"%time_period_ago_between[6]","bnf":[["between_time_only_atom"],["between_tod_only_atom"]],"fragment":true},{"name":"time_period_ago_between","bnf":[["number_time_atom","%time_period_ago_between[2]*","WS+","AGO","WS+","%time_period_ago_between[6]"]]},{"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":"%string[2][1]","bnf":[[/[\x20-\x21]/],[/[\x23-\x5B]/],[/[\x5D-\uFFFF]/]],"fragment":true},{"name":"%string[2][2]","bnf":[[/\x22/],[/\x5C/],[/\x2F/],[/\x62/],[/\x66/],[/\x6E/],[/\x72/],[/\x74/],[/\x75/,"HEXDIG","HEXDIG","HEXDIG","HEXDIG"]],"fragment":true},{"name":"%string[2]","bnf":[["%string[2][1]"],[/\x5C/,"%string[2][2]"]],"fragment":true},{"name":"string","bnf":[["'\"'","%string[2]*","'\"'"]]},{"name":"HEXDIG","bnf":[[/[a-fA-F0-9]/]]}]
3
3
  },{}],2:[function(require,module,exports){
4
- const {Parser} = require('ebnf/dist/Parser.js'),
5
- {ParsingError} = require('ebnf'),
6
- assert = require('assert'),
7
- RuleParseError = require('./errors/RuleParseError')
8
-
9
- let ParserRules = require('./RuleParser.production.ebnf.js')
4
+ const {Parser} = require('ebnf/dist/Parser.js'), {ParsingError} = require('ebnf'), RuleParseError = require('./errors/RuleParseError');
5
+ let ParserRules = require('./RuleParser.production.ebnf.js');
10
6
  let ParserCache;
11
-
12
- const { ErrorAnalyzer } = require('./errors/ErrorAnalyzer');
13
-
7
+ const {ErrorAnalyzer} = require('./errors/ErrorAnalyzer');
14
8
  const ArithmeticOperators = {
15
- "+": 'MathAdd',
16
- "-": 'MathSub',
17
- "/": 'MathDiv',
18
- "*": 'MathMul',
19
- "%": 'MathMod',
20
- "??": "Default"
21
- }
22
-
9
+ '+': 'MathAdd',
10
+ '-': 'MathSub',
11
+ '/': 'MathDiv',
12
+ '*': 'MathMul',
13
+ '%': 'MathMod',
14
+ '??': 'Default'
15
+ };
23
16
  const OperatorFn = {
24
- ">": "Gt",
25
- "<": "Lt",
26
- ">=": "Gte",
27
- "<=": "Lte",
28
- "==": "Eq",
29
- "=": "Eq",
30
- "!=": "Neq"
31
- }
32
-
17
+ '>': 'Gt',
18
+ '<': 'Lt',
19
+ '>=': 'Gte',
20
+ '<=': 'Lte',
21
+ '==': 'Eq',
22
+ '=': 'Eq',
23
+ '!=': 'Neq'
24
+ };
33
25
  const LogicalOperators = {
34
- "&&": 'And',
35
- "AND": 'And',
36
- "||": 'Or',
37
- "OR": 'Or',
38
- }
39
-
40
- // Map abbreviations to canonical uppercase full form
26
+ '&&': 'And',
27
+ 'AND': 'And',
28
+ '||': 'Or',
29
+ 'OR': 'Or'
30
+ };
41
31
  const DOW_MAP = {
42
32
  'MON': 'MONDAY',
43
33
  'TUE': 'TUESDAY',
@@ -46,654 +36,705 @@ const DOW_MAP = {
46
36
  'THUR': 'THURSDAY',
47
37
  'FRI': 'FRIDAY',
48
38
  'SAT': 'SATURDAY',
49
- 'SUN': 'SUNDAY',
39
+ 'SUN': 'SUNDAY'
50
40
  };
51
-
52
- // Valid full day names
53
- const VALID_DAYS = new Set(['MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY']);
54
-
55
- const normalizeDow = (text) => {
41
+ const VALID_DAYS = new Set([
42
+ 'MONDAY',
43
+ 'TUESDAY',
44
+ 'WEDNESDAY',
45
+ 'THURSDAY',
46
+ 'FRIDAY',
47
+ 'SATURDAY',
48
+ 'SUNDAY'
49
+ ]);
50
+ const normalizeDow = text => {
56
51
  const upper = text.toUpperCase();
57
- // Check if it's an abbreviation first
58
52
  if (upper in DOW_MAP) {
59
53
  return DOW_MAP[upper];
60
54
  }
61
- // Otherwise, check if it's a valid full name
62
55
  if (VALID_DAYS.has(upper)) {
63
56
  return upper;
64
57
  }
65
- throw new Error(`Invalid day of week: ${text}`);
58
+ throw new Error(`Invalid day of week: ${ text }`);
66
59
  };
67
-
68
- const Epsilon = 0.01
69
-
60
+ const Epsilon = 0.01;
70
61
  class RuleParser {
71
- static toAst(txt, parser = null){
72
- let ret
73
-
74
- if(!parser) {
75
- if(!ParserCache){
76
- ParserCache = new Parser(ParserRules, {debug: false})
62
+ static toAst(txt, parser = null) {
63
+ let ret;
64
+ if (!parser) {
65
+ if (!ParserCache) {
66
+ ParserCache = new Parser(ParserRules, { debug: false });
77
67
  }
78
- parser = ParserCache
68
+ parser = ParserCache;
79
69
  }
80
-
81
70
  try {
82
71
  ret = parser.getAST(txt.trim(), 'statement_main');
83
72
  } catch (e) {
84
- // If ebnf throws ParsingError, convert it to RuleParseError with helpful error code
85
73
  if (e instanceof ParsingError) {
86
74
  throw ErrorAnalyzer.analyzeParseFailure(txt, e);
87
75
  }
88
76
  throw e;
89
77
  }
90
-
91
- if(ret){
92
- return ret.children[0]
78
+ if (ret) {
79
+ return ret.children[0];
93
80
  }
94
-
95
- // If parsing failed without throwing (shouldn't happen with new ebnf), throw error
96
81
  throw ErrorAnalyzer.analyzeParseFailure(txt);
97
82
  }
98
- static _parseArgument(argument){
99
- assert(argument.type === 'argument')
100
- const child = argument.children[0]
101
- return RuleParser._buildExpressionGroup(child)
102
- }
103
- static _parseFcall(fcall){
104
- const fname = fcall.children[0]
105
- const ret = [fname.text]
106
- if(fcall.children.length != 1){
107
- const args = fcall.children[1]
108
- for(const a of args.children){
109
- ret.push(RuleParser._parseArgument(a))
83
+ static _parseArgument(argument) {
84
+ const child = argument.children[0];
85
+ return RuleParser._buildExpressionGroup(child);
86
+ }
87
+ static _parseFcall(fcall) {
88
+ const fname = fcall.children[0];
89
+ const ret = [fname.text];
90
+ if (fcall.children.length != 1) {
91
+ const args = fcall.children[1];
92
+ for (const a of args.children) {
93
+ ret.push(RuleParser._parseArgument(a));
110
94
  }
111
95
  }
112
- return ret
96
+ return ret;
113
97
  }
114
98
  static _parseDowRange(dowRange) {
115
- // dow_range can have 1 or 2 children (single day or range)
116
99
  if (dowRange.children.length === 1) {
117
- // Single day: ON MONDAY - return just the day string
118
- // New structure: dow_range -> dow_range_inner -> dow_atom -> dow
119
- const dayText = RuleParser.__parseValue(dowRange.children[0])
120
- return { start: dayText, end: dayText };
100
+ const dayText = RuleParser.__parseValue(dowRange.children[0]);
101
+ return {
102
+ start: dayText,
103
+ end: dayText
104
+ };
121
105
  } else if (dowRange.children.length === 2) {
122
- // Range: ON MONDAY TO FRIDAY - return both start and end days
123
- const startDay = RuleParser.__parseValue(dowRange.children[0])
124
- const endDay = RuleParser.__parseValue(dowRange.children[1])
125
- return { start: startDay, end: endDay };
106
+ const startDay = RuleParser.__parseValue(dowRange.children[0]);
107
+ const endDay = RuleParser.__parseValue(dowRange.children[1]);
108
+ return {
109
+ start: startDay,
110
+ end: endDay
111
+ };
126
112
  } else {
127
- throw new Error(`Invalid dow_range with ${dowRange.children.length} children`);
113
+ throw new Error(`Invalid dow_range with ${ dowRange.children.length } children`);
128
114
  }
129
115
  }
130
116
  static _addDowToTods(startTod, endTod, dowRange) {
131
117
  if (dowRange && dowRange.type === 'dow_range') {
132
- const dow = RuleParser._parseDowRange(dowRange)
133
- startTod.dow = dow.start
134
- endTod.dow = dow.end
135
- }
136
- }
137
- static _parseTimePeriod(tp){
138
- switch(tp.type){
139
- case 'time_period_const':
140
- // Check if this is a time_period_ago (has children)
141
- if (tp.children && tp.children.length > 0 && tp.children[0].type === 'time_period_ago') {
142
- const timePeriodAgo = tp.children[0]
143
- // Extract all number_time children and sum them up
144
- let totalSeconds = 0
145
- const components = []
146
- for (const child of timePeriodAgo.children) {
147
- if (child.type === 'number_time_atom') {
148
- // New structure: number_time_atom -> number_time -> number, unit
149
- const numberTime = child.children[0]
150
- const number = parseFloat(numberTime.children[0].text)
151
- const unit = numberTime.children[1].text.toUpperCase()
152
- components.push([number, unit])
153
- // Parse the value to get seconds
154
- totalSeconds += RuleParser.__parseValue(numberTime)
155
- }
156
- }
157
- // If there's only one component, use its number and unit
158
- // Otherwise, use the total in seconds and "SECONDS" as the unit
159
- if (components.length === 1) {
160
- return ["TimePeriodConstAgo", components[0][0], components[0][1]]
161
- } else {
162
- return ["TimePeriodConstAgo", totalSeconds, "SECONDS"]
118
+ const dow = RuleParser._parseDowRange(dowRange);
119
+ startTod.dow = dow.start;
120
+ endTod.dow = dow.end;
121
+ }
122
+ }
123
+ static _parseTimePeriod(tp) {
124
+ switch (tp.type) {
125
+ case 'time_period_const':
126
+ if (tp.children && tp.children.length > 0 && tp.children[0].type === 'time_period_ago') {
127
+ const timePeriodAgo = tp.children[0];
128
+ let totalSeconds = 0;
129
+ const components = [];
130
+ for (const child of timePeriodAgo.children) {
131
+ if (child.type === 'number_time_atom') {
132
+ const numberTime = child.children[0];
133
+ const number = parseFloat(numberTime.children[0].text);
134
+ const unit = numberTime.children[1].text.toUpperCase();
135
+ components.push([
136
+ number,
137
+ unit
138
+ ]);
139
+ totalSeconds += RuleParser.__parseValue(numberTime);
163
140
  }
164
141
  }
165
- return ["TimePeriodConst", tp.text]
166
- case 'time_period_ago_between': {
167
- // time_period_ago_between has: number_time_atom (WS+ number_time_atom)* WS+ AGO WS+ (between_time_only | between_tod_only)
168
- // We need to extract all number_time children and sum them up, then return TimePeriodBetweenAgo
169
- let totalSeconds = 0
170
- let betweenNode = null
171
- let isBetweenTod = false
172
-
173
- // Find all number_time_atom children and the between node (either between_time_only or between_tod_only)
142
+ if (components.length === 1) {
143
+ return [
144
+ 'TimePeriodConstAgo',
145
+ components[0][0],
146
+ components[0][1]
147
+ ];
148
+ } else {
149
+ return [
150
+ 'TimePeriodConstAgo',
151
+ totalSeconds,
152
+ 'SECONDS'
153
+ ];
154
+ }
155
+ }
156
+ return [
157
+ 'TimePeriodConst',
158
+ tp.text
159
+ ];
160
+ case 'time_period_ago_between': {
161
+ let totalSeconds = 0;
162
+ let betweenNode = null;
163
+ let isBetweenTod = false;
174
164
  for (let i = 0; i < tp.children.length; i++) {
175
165
  if (tp.children[i].type === 'number_time_atom') {
176
- // New structure: number_time_atom -> number_time
177
- totalSeconds += RuleParser.__parseValue(tp.children[i].children[0])
166
+ totalSeconds += RuleParser.__parseValue(tp.children[i].children[0]);
178
167
  } else if (tp.children[i].type === 'between_time_only' || tp.children[i].type === 'between_time_only_atom') {
179
- betweenNode = tp.children[i].type === 'between_time_only_atom' ? tp.children[i].children[0] : tp.children[i]
180
- isBetweenTod = false
168
+ betweenNode = tp.children[i].type === 'between_time_only_atom' ? tp.children[i].children[0] : tp.children[i];
169
+ isBetweenTod = false;
181
170
  } else if (tp.children[i].type === 'between_tod_only' || tp.children[i].type === 'between_tod_only_atom') {
182
- betweenNode = tp.children[i].type === 'between_tod_only_atom' ? tp.children[i].children[0] : tp.children[i]
183
- isBetweenTod = true
171
+ betweenNode = tp.children[i].type === 'between_tod_only_atom' ? tp.children[i].children[0] : tp.children[i];
172
+ isBetweenTod = true;
184
173
  }
185
174
  }
186
-
187
- // This should always be present based on the grammar, but check defensively
188
175
  if (!betweenNode) {
189
- throw new Error('time_period_ago_between requires between_time_only or between_tod_only child')
176
+ throw new Error('time_period_ago_between requires between_time_only or between_tod_only child');
190
177
  }
191
-
192
178
  if (isBetweenTod) {
193
- const betweenTod = betweenNode.children[0]
194
- // between_tod has inline separator, so: children[0] = first tod_inner, children[1] = second tod_inner, children[2] = optional dow_range
195
- let startTod = RuleParser.__parseValue(betweenTod.children[0])
196
- let endTod = RuleParser.__parseValue(betweenTod.children[1])
197
-
198
- // Check if there's a dow_range at betweenTod.children[2]
199
- // Note: startTod and endTod should always be objects from number_tod parsing
179
+ const betweenTod = betweenNode.children[0];
180
+ let startTod = RuleParser.__parseValue(betweenTod.children[0]);
181
+ let endTod = RuleParser.__parseValue(betweenTod.children[1]);
200
182
  if (betweenTod.children.length > 2) {
201
- RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2])
183
+ RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2]);
202
184
  }
203
-
204
- return ["TimePeriodBetweenAgo", totalSeconds, startTod, endTod]
185
+ return [
186
+ 'TimePeriodBetweenAgo',
187
+ totalSeconds,
188
+ startTod,
189
+ endTod
190
+ ];
205
191
  } else {
206
- const betweenNumberTime = betweenNode.children[0]
207
- // between_number_time has: children[0] = first time_inner, children[1] = separator, children[2] = second time_inner, children[3] = optional dow_range
208
- const startValue = RuleParser.__parseValue(betweenNumberTime.children[0])
209
- const endValue = RuleParser.__parseValue(betweenNumberTime.children[2])
210
-
211
- // Check if there's a dow_range at betweenNumberTime.children[3]
192
+ const betweenNumberTime = betweenNode.children[0];
193
+ const startValue = RuleParser.__parseValue(betweenNumberTime.children[0]);
194
+ const endValue = RuleParser.__parseValue(betweenNumberTime.children[2]);
212
195
  if (betweenNumberTime.children.length > 3 && betweenNumberTime.children[3].type === 'dow_range') {
213
- const dow = RuleParser._parseDowRange(betweenNumberTime.children[3])
196
+ const dow = RuleParser._parseDowRange(betweenNumberTime.children[3]);
214
197
  if (dow.start === dow.end) {
215
- // Single day: ["TimePeriodBetweenAgo", totalSeconds, start, end, "MONDAY"]
216
- return ["TimePeriodBetweenAgo", totalSeconds, startValue, endValue, dow.start]
198
+ return [
199
+ 'TimePeriodBetweenAgo',
200
+ totalSeconds,
201
+ startValue,
202
+ endValue,
203
+ dow.start
204
+ ];
217
205
  } else {
218
- // Range: ["TimePeriodBetweenAgo", totalSeconds, start, end, "MONDAY", "FRIDAY"]
219
- return ["TimePeriodBetweenAgo", totalSeconds, startValue, endValue, dow.start, dow.end]
206
+ return [
207
+ 'TimePeriodBetweenAgo',
208
+ totalSeconds,
209
+ startValue,
210
+ endValue,
211
+ dow.start,
212
+ dow.end
213
+ ];
220
214
  }
221
215
  }
222
-
223
- return ["TimePeriodBetweenAgo", totalSeconds, startValue, endValue]
216
+ return [
217
+ 'TimePeriodBetweenAgo',
218
+ totalSeconds,
219
+ startValue,
220
+ endValue
221
+ ];
224
222
  }
225
223
  }
226
- case 'between_tod_only': {
227
- // between_tod_only has children[0] = between_tod node
228
- const betweenTod = tp.children[0]
229
- // between_tod has inline separator, so: children[0] = first tod_inner, children[1] = second tod_inner, children[2] = optional dow_range
230
- let startTod = RuleParser.__parseValue(betweenTod.children[0])
231
- let endTod = RuleParser.__parseValue(betweenTod.children[1])
232
-
233
- // Check if there's a dow_range at betweenTod.children[2]
224
+ case 'between_tod_only': {
225
+ const betweenTod = tp.children[0];
226
+ let startTod = RuleParser.__parseValue(betweenTod.children[0]);
227
+ let endTod = RuleParser.__parseValue(betweenTod.children[1]);
234
228
  if (betweenTod.children.length > 2) {
235
- if(typeof startTod === 'number') startTod = {seconds: startTod, dow: null}
236
- if(typeof endTod === 'number') endTod = {seconds: endTod, dow: null}
237
- RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2])
229
+ if (typeof startTod === 'number')
230
+ startTod = {
231
+ seconds: startTod,
232
+ dow: null
233
+ };
234
+ if (typeof endTod === 'number')
235
+ endTod = {
236
+ seconds: endTod,
237
+ dow: null
238
+ };
239
+ RuleParser._addDowToTods(startTod, endTod, betweenTod.children[2]);
238
240
  }
239
-
240
- return ["TimePeriodBetween", startTod, endTod]
241
- }
242
- case 'between_time_only': {
243
- // between_time_only has children[0] = between_number_time node
244
- const betweenNumberTime = tp.children[0]
245
- // between_number_time has: children[0] = first time_inner, children[1] = separator, children[2] = second time_inner, children[3] = optional dow_range
246
- const startValue = RuleParser.__parseValue(betweenNumberTime.children[0])
247
- const endValue = RuleParser.__parseValue(betweenNumberTime.children[2])
248
-
249
- // Check if there's a dow_range at betweenNumberTime.children[3]
250
- // If DOW filters are provided, append them as additional parameters
241
+ return [
242
+ 'TimePeriodBetween',
243
+ startTod,
244
+ endTod
245
+ ];
246
+ }
247
+ case 'between_time_only': {
248
+ const betweenNumberTime = tp.children[0];
249
+ const startValue = RuleParser.__parseValue(betweenNumberTime.children[0]);
250
+ const endValue = RuleParser.__parseValue(betweenNumberTime.children[2]);
251
251
  if (betweenNumberTime.children.length > 3 && betweenNumberTime.children[3].type === 'dow_range') {
252
- const dow = RuleParser._parseDowRange(betweenNumberTime.children[3])
252
+ const dow = RuleParser._parseDowRange(betweenNumberTime.children[3]);
253
253
  if (dow.start === dow.end) {
254
- // Single day: ["TimePeriodBetween", start, end, "MONDAY"]
255
- return ["TimePeriodBetween", startValue, endValue, dow.start]
254
+ return [
255
+ 'TimePeriodBetween',
256
+ startValue,
257
+ endValue,
258
+ dow.start
259
+ ];
256
260
  } else {
257
- // Range: ["TimePeriodBetween", start, end, "MONDAY", "FRIDAY"]
258
- return ["TimePeriodBetween", startValue, endValue, dow.start, dow.end]
261
+ return [
262
+ 'TimePeriodBetween',
263
+ startValue,
264
+ endValue,
265
+ dow.start,
266
+ dow.end
267
+ ];
259
268
  }
260
269
  }
261
-
262
- return ["TimePeriodBetween", startValue, endValue]
263
- }
264
- }
265
- }
266
- static __parseValue(child){
267
- const type = child.type
268
- switch(type){
269
- case 'value': {
270
- // Arrays have value nodes as children - unwrap to get value_atom
271
- return RuleParser.__parseValue(child.children[0])
272
- }
273
- case 'value_atom': {
274
- // New layer: unwrap value_atom to get the actual atomic type
275
- return RuleParser.__parseValue(child.children[0])
276
- }
277
- case 'number_atom':
278
- case 'number_time_atom':
279
- case 'tod_atom':
280
- case 'dow_atom':
281
- case 'between_tod_inner':
282
- case 'between_number_inner':
283
- case 'between_number_time_inner':
284
- case 'dow_range_inner': {
285
- // New layer: unwrap atom wrappers to get the actual leaf nodes
286
- return RuleParser.__parseValue(child.children[0])
287
- }
288
- case 'string': {
289
- const str = child.text
290
- return str.slice(1, -1)
291
- }
292
- case 'number':
293
- return parseFloat(child.text)
294
- case 'number_tod': {
295
- const tokens = child.text.split(':')
270
+ return [
271
+ 'TimePeriodBetween',
272
+ startValue,
273
+ endValue
274
+ ];
275
+ }
276
+ }
277
+ }
278
+ static __parseValue(child) {
279
+ const type = child.type;
280
+ switch (type) {
281
+ case 'value': {
282
+ return RuleParser.__parseValue(child.children[0]);
283
+ }
284
+ case 'value_atom': {
285
+ return RuleParser.__parseValue(child.children[0]);
286
+ }
287
+ case 'number_atom':
288
+ case 'number_time_atom':
289
+ case 'tod_atom':
290
+ case 'dow_atom':
291
+ case 'between_tod_inner':
292
+ case 'between_number_inner':
293
+ case 'between_number_time_inner':
294
+ case 'dow_range_inner': {
295
+ return RuleParser.__parseValue(child.children[0]);
296
+ }
297
+ case 'string': {
298
+ const str = child.text;
299
+ return str.slice(1, -1);
300
+ }
301
+ case 'number':
302
+ return parseFloat(child.text);
303
+ case 'number_tod': {
304
+ const tokens = child.text.split(':');
296
305
  if (tokens.length !== 2) {
297
- throw new Error(`Invalid time of day, ${child.text} should be ##:##`)
306
+ throw new Error(`Invalid time of day, ${ child.text } should be ##:##`);
298
307
  }
299
- const hours = parseInt(tokens[0])
300
- const minutes = parseInt(tokens[1])
301
- const tod = hours * 100 + minutes
302
- const ret = { hours, minutes, tod }
308
+ const hours = parseInt(tokens[0]);
309
+ const minutes = parseInt(tokens[1]);
310
+ const tod = hours * 100 + minutes;
311
+ const ret = {
312
+ hours,
313
+ minutes,
314
+ tod
315
+ };
303
316
  if (!isNaN(tod) && ret.hours >= 0 && ret.hours < 24 && ret.minutes >= 0 && ret.minutes < 60) {
304
- return ret
317
+ return ret;
305
318
  }
306
- throw new Error(`Invalid time of day, ${child.text} -> [${tokens.join(', ')}] -> ${hours}h${minutes}m -> ${tod}`)
307
- }
308
- case 'number_time': {
309
- const nt = child
310
- const mult = parseFloat(nt.children[0].text)
311
- switch(nt.children[1].text.toUpperCase()){
312
- case 'SECONDS':
313
- case 'SECOND':
314
- return mult
315
- case 'MINUTES':
316
- case 'MINUTE':
317
- case 'MINS':
318
- case 'MIN':
319
- return mult * 60
320
- case 'HOURS':
321
- case 'HOUR':
322
- return mult * 60 * 60
323
- case 'DAYS':
324
- case 'DAY':
325
- return mult * 60 * 60 * 24
326
- case 'WEEKS':
327
- case 'WEEK':
328
- return mult * 60 * 60 * 24 * 7
319
+ throw new Error(`Invalid time of day, ${ child.text } -> [${ tokens.join(', ') }] -> ${ hours }h${ minutes }m -> ${ tod }`);
320
+ }
321
+ case 'number_time': {
322
+ const nt = child;
323
+ const mult = parseFloat(nt.children[0].text);
324
+ switch (nt.children[1].text.toUpperCase()) {
325
+ case 'SECONDS':
326
+ case 'SECOND':
327
+ return mult;
328
+ case 'MINUTES':
329
+ case 'MINUTE':
330
+ case 'MINS':
331
+ case 'MIN':
332
+ return mult * 60;
333
+ case 'HOURS':
334
+ case 'HOUR':
335
+ return mult * 60 * 60;
336
+ case 'DAYS':
337
+ case 'DAY':
338
+ return mult * 60 * 60 * 24;
339
+ case 'WEEKS':
340
+ case 'WEEK':
341
+ return mult * 60 * 60 * 24 * 7;
329
342
  }
330
- throw new Error(`Invalid exponent ${nt.children[1].text}`)
331
- }
332
- case 'true':
333
- return true
334
- case 'false':
335
- return false
336
- case 'array': {
337
- const ret = []
338
- for(const c of child.children){
339
- ret.push(RuleParser.__parseValue(c))
343
+ throw new Error(`Invalid exponent ${ nt.children[1].text }`);
344
+ }
345
+ case 'true':
346
+ return true;
347
+ case 'false':
348
+ return false;
349
+ case 'array': {
350
+ const ret = [];
351
+ for (const c of child.children) {
352
+ ret.push(RuleParser.__parseValue(c));
340
353
  }
341
354
  return ret;
342
355
  }
343
- case 'dow': {
344
- return normalizeDow(child.text)
356
+ case 'dow': {
357
+ return normalizeDow(child.text);
345
358
  }
346
- default:
347
- throw new Error(`Unknown value type ${type}`)
359
+ default:
360
+ throw new Error(`Unknown value type ${ type }`);
348
361
  }
349
362
  }
350
- static _parseValue(value){
351
- const child = value.children[0]
352
-
353
- const type = child.type
354
- switch(type){
355
- case 'value_atom': {
356
- // New layer: unwrap value_atom to get the actual atomic type
357
- const atomChild = child.children[0]
363
+ static _parseValue(value) {
364
+ const child = value.children[0];
365
+ const type = child.type;
366
+ switch (type) {
367
+ case 'value_atom': {
368
+ const atomChild = child.children[0];
358
369
  if (atomChild.type === 'time_period') {
359
- const tp = atomChild.children[0]
360
- return RuleParser._parseTimePeriod(tp)
370
+ const tp = atomChild.children[0];
371
+ return RuleParser._parseTimePeriod(tp);
361
372
  }
362
- return ['Value', RuleParser.__parseValue(atomChild)]
363
- }
364
- case 'time_period': {
365
- // Old structure (shouldn't happen with new EBNF)
366
- const tp = child.children[0]
367
- return RuleParser._parseTimePeriod(tp)
368
- }
369
- default:
370
- return ['Value', RuleParser.__parseValue(child)]
371
- }
372
- }
373
- static _parseSimpleResult(result){
374
- assert(result.children.length == 1)
375
- const child = result.children[0]
376
- const type = child.type
377
- switch(type){
378
- case 'fcall':
379
- return RuleParser._parseFcall(child)
380
- case 'value':
381
- return RuleParser._parseValue(child)
382
- }
383
- return null
384
- }
385
- static _parseArithmeticOperand(operand){
386
- assert(operand.children.length == 1)
387
- const child = operand.children[0]
388
- const type = child.type
389
- switch(type){
390
- case 'fcall':
391
- return RuleParser._parseFcall(child)
392
- case 'number_atom':
393
- case 'number_time_atom': {
394
- // New layer: unwrap atom wrappers to get the actual leaf nodes
395
- return ['Value', RuleParser.__parseValue(child.children[0])]
396
- }
397
- case 'number':
398
- return ['Value', parseFloat(child.text)]
399
- case 'number_time':
400
- return ['Value', RuleParser.__parseValue(child)]
401
- }
402
- throw new Error(`Unknown arithmetic operand type ${type}`)
403
- }
404
- static _isConstantValue(expr){
405
- // Check if an expression is a constant value (not just numbers, but any literal)
406
- return Array.isArray(expr) && expr.length === 2 && expr[0] === 'Value'
373
+ return [
374
+ 'Value',
375
+ RuleParser.__parseValue(atomChild)
376
+ ];
377
+ }
378
+ case 'time_period': {
379
+ const tp = child.children[0];
380
+ return RuleParser._parseTimePeriod(tp);
381
+ }
382
+ default:
383
+ return [
384
+ 'Value',
385
+ RuleParser.__parseValue(child)
386
+ ];
387
+ }
407
388
  }
408
-
409
- static _isConstantNumberValue(expr){
410
- // Check if an expression is a constant numeric value
411
- return Array.isArray(expr) && expr.length === 2 && expr[0] === 'Value' && typeof expr[1] === 'number'
389
+ static _parseSimpleResult(result) {
390
+ const child = result.children[0];
391
+ const type = child.type;
392
+ switch (type) {
393
+ case 'fcall':
394
+ return RuleParser._parseFcall(child);
395
+ case 'value':
396
+ return RuleParser._parseValue(child);
397
+ }
398
+ return null;
412
399
  }
413
-
414
- static _evaluateConstantArithmetic(operator, leftValue, rightValue){
415
- // Evaluate constant arithmetic operations at parse time
416
- switch(operator){
417
- case 'MathAdd':
418
- return leftValue + rightValue
419
- case 'MathSub':
420
- return leftValue - rightValue
421
- case 'MathMul':
422
- return leftValue * rightValue
423
- case 'MathDiv':
424
- return leftValue / rightValue
425
- case 'MathMod':
426
- return leftValue % rightValue
427
- default:
428
- return null
400
+ static _parseArithmeticOperand(operand) {
401
+ const child = operand.children[0];
402
+ const type = child.type;
403
+ switch (type) {
404
+ case 'fcall':
405
+ return RuleParser._parseFcall(child);
406
+ case 'number_atom':
407
+ case 'number_time_atom': {
408
+ return [
409
+ 'Value',
410
+ RuleParser.__parseValue(child.children[0])
411
+ ];
412
+ }
413
+ case 'number':
414
+ return [
415
+ 'Value',
416
+ parseFloat(child.text)
417
+ ];
418
+ case 'number_time':
419
+ return [
420
+ 'Value',
421
+ RuleParser.__parseValue(child)
422
+ ];
423
+ }
424
+ throw new Error(`Unknown arithmetic operand type ${ type }`);
425
+ }
426
+ static _isConstantValue(expr) {
427
+ return Array.isArray(expr) && expr.length === 2 && expr[0] === 'Value';
428
+ }
429
+ static _isConstantNumberValue(expr) {
430
+ return Array.isArray(expr) && expr.length === 2 && expr[0] === 'Value' && typeof expr[1] === 'number';
431
+ }
432
+ static _evaluateConstantArithmetic(operator, leftValue, rightValue) {
433
+ switch (operator) {
434
+ case 'MathAdd':
435
+ return leftValue + rightValue;
436
+ case 'MathSub':
437
+ return leftValue - rightValue;
438
+ case 'MathMul':
439
+ return leftValue * rightValue;
440
+ case 'MathDiv':
441
+ return leftValue / rightValue;
442
+ case 'MathMod':
443
+ return leftValue % rightValue;
444
+ default:
445
+ return null;
429
446
  }
430
447
  }
431
-
432
- static _parseArithmeticResult(result){
433
- assert(result.children.length == 3)
434
- const partA = RuleParser._parseArithmeticOperand(result.children[0])
435
- const operatorFn = ArithmeticOperators[result.children[1].text]
436
- const partB = RuleParser.__parseArithmeticResult(result, 2)
437
-
438
- // Compile out constant expressions
448
+ static _parseArithmeticResult(result) {
449
+ const partA = RuleParser._parseArithmeticOperand(result.children[0]);
450
+ const operatorFn = ArithmeticOperators[result.children[1].text];
451
+ const partB = RuleParser.__parseArithmeticResult(result, 2);
439
452
  if (RuleParser._isConstantNumberValue(partA) && RuleParser._isConstantNumberValue(partB)) {
440
- const result = RuleParser._evaluateConstantArithmetic(operatorFn, partA[1], partB[1])
453
+ const result = RuleParser._evaluateConstantArithmetic(operatorFn, partA[1], partB[1]);
441
454
  if (result !== null) {
442
- return ['Value', result]
455
+ return [
456
+ 'Value',
457
+ result
458
+ ];
443
459
  }
444
460
  }
445
-
446
- return [operatorFn, partA, partB]
447
- }
448
-
449
- static __parseArithmeticResult(result, idx){
450
- const child = result.children[idx]
451
- const type = child.type
452
- switch(type){
453
- case 'arithmetic_operand':
454
- return RuleParser._parseArithmeticOperand(child)
455
- case 'arithmetic_result':
456
- return RuleParser._parseArithmeticResult(child)
461
+ return [
462
+ operatorFn,
463
+ partA,
464
+ partB
465
+ ];
466
+ }
467
+ static __parseArithmeticResult(result, idx) {
468
+ const child = result.children[idx];
469
+ const type = child.type;
470
+ switch (type) {
471
+ case 'arithmetic_operand':
472
+ return RuleParser._parseArithmeticOperand(child);
473
+ case 'arithmetic_result':
474
+ return RuleParser._parseArithmeticResult(child);
457
475
  }
458
-
459
- throw new Error(`Unknown arithmetic result node ${type}`)
460
- }
461
-
462
- static __parseResult(result, idx){
463
- const child = result.children[idx]
464
- const type = child.type
465
- switch(type){
466
- case 'simple_result':
467
- return RuleParser._parseSimpleResult(child)
468
- case 'arithmetic_result':
469
- return RuleParser._parseArithmeticResult(child)
476
+ throw new Error(`Unknown arithmetic result node ${ type }`);
477
+ }
478
+ static __parseResult(result, idx) {
479
+ const child = result.children[idx];
480
+ const type = child.type;
481
+ switch (type) {
482
+ case 'simple_result':
483
+ return RuleParser._parseSimpleResult(child);
484
+ case 'arithmetic_result':
485
+ return RuleParser._parseArithmeticResult(child);
470
486
  }
471
-
472
- throw new Error(`Unknown result node ${type}`)
473
- }
474
- static _parseResult(result){
475
- assert(result.children.length == 1)
476
-
477
- return RuleParser.__parseResult(result, 0)
478
- }
479
- static _parseStdExpression(expr){
480
- assert(expr.type === 'standard_expression')
481
- switch(expr.children.length){
482
- case 1:
483
- return RuleParser._parseResult(expr.children[0])
484
- case 2: {
485
- const rhs = expr.children[1]
486
- switch(rhs.type){
487
- case 'between_tod': {
488
- // Direct between_tod (without wrapping between node)
489
- // between_tod has inline separator, so: children[0] = first tod_inner, children[1] = second tod_inner, children[2] = optional dow_range
490
- const startTod = RuleParser.__parseValue(rhs.children[0])
491
- const endTod = RuleParser.__parseValue(rhs.children[1])
492
-
493
- // Check if there's a dow_range (children[2])
487
+ throw new Error(`Unknown result node ${ type }`);
488
+ }
489
+ static _parseResult(result) {
490
+ return RuleParser.__parseResult(result, 0);
491
+ }
492
+ static _parseStdExpression(expr) {
493
+ switch (expr.children.length) {
494
+ case 1:
495
+ return RuleParser._parseResult(expr.children[0]);
496
+ case 2: {
497
+ const rhs = expr.children[1];
498
+ switch (rhs.type) {
499
+ case 'between_tod': {
500
+ const startTod = RuleParser.__parseValue(rhs.children[0]);
501
+ const endTod = RuleParser.__parseValue(rhs.children[1]);
494
502
  if (rhs.children.length > 2) {
495
- RuleParser._addDowToTods(startTod, endTod, rhs.children[2])
503
+ RuleParser._addDowToTods(startTod, endTod, rhs.children[2]);
496
504
  }
497
-
498
- return ['Between', RuleParser._parseResult(expr.children[0]), ['Value', startTod], ['Value', endTod]]
505
+ return [
506
+ 'Between',
507
+ RuleParser._parseResult(expr.children[0]),
508
+ [
509
+ 'Value',
510
+ startTod
511
+ ],
512
+ [
513
+ 'Value',
514
+ endTod
515
+ ]
516
+ ];
499
517
  }
500
- case 'between': {
501
- // between wraps either between_number or between_tod
502
- const betweenChild = rhs.children[0]
518
+ case 'between': {
519
+ const betweenChild = rhs.children[0];
503
520
  if (betweenChild.type === 'between_tod') {
504
- // between_tod has inline separator, so: children[0] = first tod_inner, children[1] = second tod_inner, children[2] = optional dow_range
505
- const startTod = RuleParser.__parseValue(betweenChild.children[0])
506
- const endTod = RuleParser.__parseValue(betweenChild.children[1])
507
-
508
- // Check if there's a dow_range (children[2])
521
+ const startTod = RuleParser.__parseValue(betweenChild.children[0]);
522
+ const endTod = RuleParser.__parseValue(betweenChild.children[1]);
509
523
  if (betweenChild.children.length > 2) {
510
- RuleParser._addDowToTods(startTod, endTod, betweenChild.children[2])
524
+ RuleParser._addDowToTods(startTod, endTod, betweenChild.children[2]);
511
525
  }
512
-
513
- return ['Between', RuleParser._parseResult(expr.children[0]), ['Value', startTod], ['Value', endTod]]
526
+ return [
527
+ 'Between',
528
+ RuleParser._parseResult(expr.children[0]),
529
+ [
530
+ 'Value',
531
+ startTod
532
+ ],
533
+ [
534
+ 'Value',
535
+ endTod
536
+ ]
537
+ ];
514
538
  } else {
515
- // between_number has: children[0] = first number_inner, children[1] = separator, children[2] = second number_inner
516
- return ['Between', RuleParser._parseResult(expr.children[0]), ['Value', RuleParser.__parseValue(betweenChild.children[0])], ['Value', RuleParser.__parseValue(betweenChild.children[2])]]
539
+ return [
540
+ 'Between',
541
+ RuleParser._parseResult(expr.children[0]),
542
+ [
543
+ 'Value',
544
+ RuleParser.__parseValue(betweenChild.children[0])
545
+ ],
546
+ [
547
+ 'Value',
548
+ RuleParser.__parseValue(betweenChild.children[2])
549
+ ]
550
+ ];
517
551
  }
518
552
  }
519
- case 'between_number':
520
- // between_number has: children[0] = first number_inner, children[1] = separator, children[2] = second number_inner
521
- return ['Between', RuleParser._parseResult(expr.children[0]), ['Value', RuleParser.__parseValue(rhs.children[0].children[0])], ['Value', RuleParser.__parseValue(rhs.children[0].children[2])]]
522
- case 'basic_rhs':
523
- return [OperatorFn[rhs.children[0].text], RuleParser._parseResult(expr.children[0]), RuleParser._parseResult(rhs.children[1])]
524
- case 'eq_approx': {
525
- const rhsValue = RuleParser._parseResult(rhs.children[1])
526
- assert(rhsValue[0] === 'Value')
527
- const ret = ['Between', RuleParser._parseResult(expr.children[0]), ['Value', rhsValue[1] - Epsilon], ['Value', rhsValue[1] + Epsilon]]
528
- if(rhs.children[0].text === '!='){
529
- return ['Not', ret]
553
+ case 'between_number':
554
+ return [
555
+ 'Between',
556
+ RuleParser._parseResult(expr.children[0]),
557
+ [
558
+ 'Value',
559
+ RuleParser.__parseValue(rhs.children[0].children[0])
560
+ ],
561
+ [
562
+ 'Value',
563
+ RuleParser.__parseValue(rhs.children[0].children[2])
564
+ ]
565
+ ];
566
+ case 'basic_rhs':
567
+ return [
568
+ OperatorFn[rhs.children[0].text],
569
+ RuleParser._parseResult(expr.children[0]),
570
+ RuleParser._parseResult(rhs.children[1])
571
+ ];
572
+ case 'eq_approx': {
573
+ const rhsValue = RuleParser._parseResult(rhs.children[1]);
574
+ const ret = [
575
+ 'Between',
576
+ RuleParser._parseResult(expr.children[0]),
577
+ [
578
+ 'Value',
579
+ rhsValue[1] - Epsilon
580
+ ],
581
+ [
582
+ 'Value',
583
+ rhsValue[1] + Epsilon
584
+ ]
585
+ ];
586
+ if (rhs.children[0].text === '!=') {
587
+ return [
588
+ 'Not',
589
+ ret
590
+ ];
530
591
  }
531
- return ret
592
+ return ret;
532
593
  }
533
- case 'in_expr': {
534
- // in_expr has children[0] = arguments node
535
- // ArrayIn now takes 2 parameters:
536
- // Parameter 1: haystack array (the array to be searched)
537
- // Parameter 2: the needle (the value to search for)
538
- const args = rhs.children[0]
539
- const haystack = []
540
- for(const a of args.children){
541
- haystack.push(RuleParser._parseArgument(a))
594
+ case 'in_expr': {
595
+ const args = rhs.children[0];
596
+ const haystack = [];
597
+ for (const a of args.children) {
598
+ haystack.push(RuleParser._parseArgument(a));
542
599
  }
543
- const needle = RuleParser._parseResult(expr.children[0])
544
-
545
- // Check if all haystack values are constants
546
- const allConstants = haystack.every(item => RuleParser._isConstantValue(item))
547
-
548
- let haystackExpr
600
+ const needle = RuleParser._parseResult(expr.children[0]);
601
+ const allConstants = haystack.every(item => RuleParser._isConstantValue(item));
602
+ let haystackExpr;
549
603
  if (allConstants) {
550
- // All constants: use ["Value", [array of constants]]
551
- const constantArray = haystack.map(item => item[1])
552
- haystackExpr = ['Value', constantArray]
604
+ const constantArray = haystack.map(item => item[1]);
605
+ haystackExpr = [
606
+ 'Value',
607
+ constantArray
608
+ ];
553
609
  } else {
554
- // Has non-constants: use ["Array", arg1, arg2, ...]
555
- haystackExpr = ['Array', ...haystack]
610
+ haystackExpr = [
611
+ 'Array',
612
+ ...haystack
613
+ ];
556
614
  }
557
-
558
- return ['ArrayIn', haystackExpr, needle]
615
+ return [
616
+ 'ArrayIn',
617
+ haystackExpr,
618
+ needle
619
+ ];
559
620
  }
560
-
561
- default:
562
- throw new Error(`unable to parse std expression, unknown rhs type ${rhs.type}`)
621
+ default:
622
+ throw new Error(`unable to parse std expression, unknown rhs type ${ rhs.type }`);
563
623
  }
564
624
  }
565
-
566
- default:
567
- throw new Error(`unable to parse std expression, unknown number of children ${expr.children.length}`)
568
- }
569
- }
570
- static buildLogical(members, fn){
571
- return [fn, ...members]
572
- }
573
- static _buildExpressionGroup(ast){
574
- let ret = []
575
- let currentLogical = null
576
- for(const expr of ast.children){
577
- if(expr.type == 'logical_operator') {
578
- const logicalOperator = expr.text.trim()
579
- const operatorFn = LogicalOperators[logicalOperator.toUpperCase()]
580
- assert(operatorFn, `Unknown logical operator ${logicalOperator}`)
581
- if(currentLogical === null || currentLogical !== operatorFn){
582
- if(ret.length > 1){
583
- ret = [RuleParser.buildLogical(ret, currentLogical)]
625
+ default:
626
+ throw new Error(`unable to parse std expression, unknown number of children ${ expr.children.length }`);
627
+ }
628
+ }
629
+ static buildLogical(members, fn) {
630
+ return [
631
+ fn,
632
+ ...members
633
+ ];
634
+ }
635
+ static _buildExpressionGroup(ast) {
636
+ let ret = [];
637
+ let currentLogical = null;
638
+ for (const expr of ast.children) {
639
+ if (expr.type == 'logical_operator') {
640
+ const logicalOperator = expr.text.trim();
641
+ const operatorFn = LogicalOperators[logicalOperator.toUpperCase()];
642
+ if (currentLogical === null || currentLogical !== operatorFn) {
643
+ if (ret.length > 1) {
644
+ ret = [RuleParser.buildLogical(ret, currentLogical)];
584
645
  }
585
- currentLogical = operatorFn
646
+ currentLogical = operatorFn;
586
647
  }
587
- }else{
588
- ret.push(RuleParser._exprToIL(expr))
589
- }
590
- }
591
- if(ret.length == 0){
592
- throw new Error('invalid rule')
593
- }
594
- if(ret.length == 1){
595
- return ret[0]
596
- }
597
- return RuleParser.buildLogical(ret, currentLogical)
598
- }
599
- static _parseParenthesisExpression(expr){
600
- return RuleParser._buildExpressionGroup(expr.children[0])
601
- }
602
- static _exprToIL(expr){
603
- assert(expr.type === 'expression')
604
- assert(expr.children.length === 1)
605
- const eInner = expr.children[0]
606
- switch(eInner.type){
607
- case 'standard_expression':
608
- return RuleParser._parseStdExpression(eInner)
609
- case 'not_expression': {
610
- const child = eInner.children[0]
611
- let result
612
- switch(child.type){
613
- case 'parenthesis_expression':
614
- result = RuleParser._parseParenthesisExpression(child)
615
- break;
616
- default:
617
- result = RuleParser._parseResult(child)
648
+ } else {
649
+ ret.push(RuleParser._exprToIL(expr));
650
+ }
651
+ }
652
+ if (ret.length == 0) {
653
+ throw new Error('invalid rule');
654
+ }
655
+ if (ret.length == 1) {
656
+ return ret[0];
657
+ }
658
+ return RuleParser.buildLogical(ret, currentLogical);
659
+ }
660
+ static _parseParenthesisExpression(expr) {
661
+ return RuleParser._buildExpressionGroup(expr.children[0]);
662
+ }
663
+ static _exprToIL(expr) {
664
+ const eInner = expr.children[0];
665
+ switch (eInner.type) {
666
+ case 'standard_expression':
667
+ return RuleParser._parseStdExpression(eInner);
668
+ case 'not_expression': {
669
+ const child = eInner.children[0];
670
+ let result;
671
+ switch (child.type) {
672
+ case 'parenthesis_expression':
673
+ result = RuleParser._parseParenthesisExpression(child);
674
+ break;
675
+ default:
676
+ result = RuleParser._parseResult(child);
618
677
  }
619
- return ['Not', result]
678
+ return [
679
+ 'Not',
680
+ result
681
+ ];
620
682
  }
621
- case 'parenthesis_expression':
622
- return RuleParser._parseParenthesisExpression(eInner)
623
- default:
624
- throw new Error(`unknown type of expression ${eInner.type}`)
683
+ case 'parenthesis_expression':
684
+ return RuleParser._parseParenthesisExpression(eInner);
685
+ default:
686
+ throw new Error(`unknown type of expression ${ eInner.type }`);
625
687
  }
626
688
  }
627
- static toIL(txt){
689
+ static toIL(txt) {
628
690
  try {
629
- const ast = RuleParser.toAst(txt)
630
- if(!ast) throw new Error(`failed to parse ${txt}`)
631
- return RuleParser._buildExpressionGroup(ast)
691
+ const ast = RuleParser.toAst(txt);
692
+ if (!ast)
693
+ throw new Error(`failed to parse ${ txt }`);
694
+ return RuleParser._buildExpressionGroup(ast);
632
695
  } catch (e) {
633
- // If it's already a RuleParseError, just re-throw it
634
696
  if (e.name === 'RuleParseError') {
635
697
  throw e;
636
698
  }
637
-
638
- // Check if it's a validation error we can map to a specific code
639
699
  if (e.message && e.message.includes('Invalid time of day')) {
640
- // Extract the invalid time from the error message
641
700
  const match = e.message.match(/Invalid time of day[,:]?\s*([0-9:]+)/);
642
701
  const badTod = match ? match[1] : 'invalid';
643
-
644
- // Calculate position (simplified - at end of input)
645
702
  const lines = txt.trim().split('\n');
646
703
  const position = {
647
704
  line: lines.length,
648
705
  column: lines[lines.length - 1].length + 1,
649
706
  offset: txt.trim().length
650
707
  };
651
-
652
- throw new RuleParseError(
653
- "BAD_TOD",
654
- `Invalid time of day: ${badTod}`,
655
- "Time of day must be in HH:MM format with hours 0-23 and minutes 0-59, e.g. 08:30, 14:00, 23:59.",
656
- position,
657
- badTod,
658
- ["HH:MM"],
659
- txt.trim().substring(Math.max(0, txt.trim().length - 50))
660
- );
708
+ throw new RuleParseError('BAD_TOD', `Invalid time of day: ${ badTod }`, 'Time of day must be in HH:MM format with hours 0-23 and minutes 0-59, e.g. 08:30, 14:00, 23:59.', position, badTod, ['HH:MM'], txt.trim().substring(Math.max(0, txt.trim().length - 50)));
661
709
  }
662
-
663
- // Check if it's a day of week error
664
710
  if (e.message && e.message.includes('Invalid day of week')) {
665
711
  const match = e.message.match(/Invalid day of week[,:]?\s*(\w+)/);
666
712
  const badDow = match ? match[1] : 'invalid';
667
-
668
713
  const lines = txt.trim().split('\n');
669
714
  const position = {
670
715
  line: lines.length,
671
716
  column: lines[lines.length - 1].length + 1,
672
717
  offset: txt.trim().length
673
718
  };
674
-
675
- throw new RuleParseError(
676
- "BAD_DOW",
677
- `Invalid day of week: ${badDow}`,
678
- "Valid days are: MONDAY/MON, TUESDAY/TUE, WEDNESDAY/WED, THURSDAY/THU, FRIDAY/FRI, SATURDAY/SAT, SUNDAY/SUN.",
679
- position,
680
- badDow,
681
- ["MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY", "SUNDAY"],
682
- txt.trim().substring(Math.max(0, txt.trim().length - 50))
683
- );
719
+ throw new RuleParseError('BAD_DOW', `Invalid day of week: ${ badDow }`, 'Valid days are: MONDAY/MON, TUESDAY/TUE, WEDNESDAY/WED, THURSDAY/THU, FRIDAY/FRI, SATURDAY/SAT, SUNDAY/SUN.', position, badDow, [
720
+ 'MONDAY',
721
+ 'TUESDAY',
722
+ 'WEDNESDAY',
723
+ 'THURSDAY',
724
+ 'FRIDAY',
725
+ 'SATURDAY',
726
+ 'SUNDAY'
727
+ ], txt.trim().substring(Math.max(0, txt.trim().length - 50)));
684
728
  }
685
-
686
- // For other errors, re-throw
687
729
  throw e;
688
730
  }
689
731
  }
690
732
  }
691
- module.exports = RuleParser
692
- module.exports.ParserRules = ParserRules
693
- module.exports.ParsingError = ParsingError
694
- module.exports.RuleParseError = RuleParseError
695
-
696
- },{"./RuleParser.production.ebnf.js":1,"./errors/ErrorAnalyzer":3,"./errors/RuleParseError":4,"assert":undefined,"ebnf":13,"ebnf/dist/Parser.js":9}],3:[function(require,module,exports){
733
+ module.exports = RuleParser;
734
+ module.exports.ParserRules = ParserRules;
735
+ module.exports.ParsingError = ParsingError;
736
+ module.exports.RuleParseError = RuleParseError;
737
+ },{"./RuleParser.production.ebnf.js":1,"./errors/ErrorAnalyzer":3,"./errors/RuleParseError":4,"ebnf":13,"ebnf/dist/Parser.js":9}],3:[function(require,module,exports){
697
738
  const { ParsingError } = require('ebnf');
698
739
  const RuleParseError = require('./RuleParseError');
699
740
 
@@ -3656,7 +3697,8 @@ for(const rule of TemplateGrammar){
3656
3697
  // Add template_value as an alternative to value_atom so templates can be parsed
3657
3698
  const valueAtomIdx = extendedGrammar.findIndex(r => r.name === 'value_atom');
3658
3699
  if (valueAtomIdx !== -1) {
3659
- extendedGrammar[valueAtomIdx].bnf.push(['template_value']);
3700
+ extendedGrammar[valueAtomIdx] = Object.assign({}, extendedGrammar[valueAtomIdx]);
3701
+ extendedGrammar[valueAtomIdx].bnf = extendedGrammar[valueAtomIdx].bnf.concat([['template_value']]);
3660
3702
  }
3661
3703
 
3662
3704
  // Export the parser rules for potential external use