@halleyassist/rule-parser 0.1.3 → 0.1.5
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 +7 -3
- package/src/RuleParser.production.ebnf.js +1 -0
- package/src/RuleParser.production.js +284 -0
- package/.eslintrc.yml +0 -8
- package/.github/workflows/build.yml +0 -73
- package/bin/package.js +0 -11
- package/src/RuleParser.ebnf.json +0 -1
- package/test/rule_parse_test.js +0 -214
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@halleyassist/rule-parser",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "The grammar for HalleyAssist rules",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "src/RuleParser.production.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "mocha"
|
|
8
8
|
},
|
|
@@ -26,5 +26,9 @@
|
|
|
26
26
|
"publishConfig": {
|
|
27
27
|
"access": "public",
|
|
28
28
|
"registry": "https://registry.npmjs.org/"
|
|
29
|
-
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"src/*",
|
|
32
|
+
"index.js"
|
|
33
|
+
]
|
|
30
34
|
}
|
|
@@ -0,0 +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","arguments?","END_ARGUMENT"]]},{"name":"%%%between_number111213","bnf":[["\"and\""],["\"AND\""]],"fragment":true},{"name":"%%between_number1112","bnf":[["WS+","%%%between_number111213","WS+"]],"fragment":true},{"name":"%%between_number1114","bnf":[["WS*","\"-\"","WS*"]],"fragment":true},{"name":"%between_number11","bnf":[["%%between_number1112"],["%%between_number1114"]],"fragment":true},{"name":"between_number","bnf":[["number","%between_number11","number"]]},{"name":"%%%between_tod151617","bnf":[["\"and\""],["\"AND\""]],"fragment":true},{"name":"%%between_tod1516","bnf":[["WS+","%%%between_tod151617","WS+"]],"fragment":true},{"name":"%between_tod15","bnf":[["%%between_tod1516"]],"fragment":true},{"name":"between_tod","bnf":[["number_tod","%between_tod15","number_tod"]]},{"name":"%between18","bnf":[["\"between\""],["\"BETWEEN\""]],"fragment":true},{"name":"%between19","bnf":[["between_number"],["between_tod"]],"fragment":true},{"name":"between","bnf":[["%between18","WS+","%between19"]]},{"name":"%AND20","bnf":[["WS*","\"&&\"","WS*"]],"fragment":true},{"name":"%%AND2122","bnf":[["\"AND\""],["\"and\""]],"fragment":true},{"name":"%AND21","bnf":[["WS+","%%AND2122","WS+"]],"fragment":true},{"name":"AND","bnf":[["%AND20"],["%AND21"]]},{"name":"%OR23","bnf":[["WS*","\"||\"","WS*"]],"fragment":true},{"name":"%%OR2425","bnf":[["\"OR\""],["\"or\""]],"fragment":true},{"name":"%OR24","bnf":[["WS+","%%OR2425","WS+"]],"fragment":true},{"name":"OR","bnf":[["%OR23"],["%OR24"]]},{"name":"GT","bnf":[["\">\""]]},{"name":"LT","bnf":[["\"<\""]]},{"name":"GTE","bnf":[["\">=\""]]},{"name":"LTE","bnf":[["\"<=\""]]},{"name":"IS","bnf":[["\"is\""],["\"IS\""]]},{"name":"EQ","bnf":[["\"==\""],["\"=\""]]},{"name":"NEQ","bnf":[["\"!=\""]]},{"name":"%NOT26","bnf":[["\"!\"","WS*"]],"fragment":true},{"name":"%NOT27","bnf":[["\"not\"","WS+"]],"fragment":true},{"name":"NOT","bnf":[["%NOT26"],["%NOT27"]]},{"name":"false","bnf":[["\"false\""],["\"FALSE\""]]},{"name":"null","bnf":[["\"null\""],["\"NULL\""]]},{"name":"true","bnf":[["\"true\""],["\"TRUE\""]]},{"name":"%%array2829","bnf":[["VALUE_SEPARATOR","value"]],"fragment":true},{"name":"%array28","bnf":[["value","%%array2829*"]],"fragment":true},{"name":"array","bnf":[["BEGIN_ARRAY","%array28?","END_ARRAY"]]},{"name":"unit","bnf":[["\"seconds\""],["\"second\""],["\"minutes\""],["\"minute\""],["\"min\""],["\"mins\""],["\"min\""],["\"hours\""],["\"hour\""],["\"days\""],["\"day\""],["\"weeks\""],["\"week\""]]},{"name":"%%number3031","bnf":[[/[0-9]/]]},{"name":"%number30","bnf":[["%%number3031+"]],"fragment":true},{"name":"%%number3233","bnf":[[/[0-9]/]]},{"name":"%number32","bnf":[["\".\"","%%number3233+"]],"fragment":true},{"name":"%%number3435","bnf":[["\"e\""],["\"E\""]],"fragment":true},{"name":"%%number3436","bnf":[["\"-\""],["\"+\""]],"fragment":true},{"name":"%%%number343738","bnf":[[/[0-9]/]]},{"name":"%%number3437","bnf":[["\"0\""],[/[1-9]/,"%%%number343738*"]],"fragment":true},{"name":"%number34","bnf":[["%%number3435","%%number3436?","%%number3437"]],"fragment":true},{"name":"number","bnf":[["\"-\"?","%number30","%number32?","%number34?"]]},{"name":"number_time","bnf":[["number","WS+","unit"]]},{"name":"%%number_tod3940","bnf":[[/[0-9]/]]},{"name":"%number_tod39","bnf":[["%%number_tod3940+"]],"fragment":true},{"name":"%%number_tod4142","bnf":[[/[0-9]/]]},{"name":"%number_tod41","bnf":[["%%number_tod4142+"]],"fragment":true},{"name":"number_tod","bnf":[["%number_tod39","\":\"","%number_tod41"]]},{"name":"time_period_const","bnf":[["\"today\""]]},{"name":"time_period","bnf":[["time_period_const"],["between"]]},{"name":"%%string4344","bnf":[[/[\x20-\x21]/],[/[\x23-\x5B]/],[/[\x5D-\uFFFF]/]],"fragment":true},{"name":"%%string4345","bnf":[[/\x22/],[/\x5C/],[/\x2F/],[/\x62/],[/\x66/],[/\x6E/],[/\x72/],[/\x74/],[/\x75/,"HEXDIG","HEXDIG","HEXDIG","HEXDIG"]],"fragment":true},{"name":"%string43","bnf":[["%%string4344"],[/\x5C/,"%%string4345"]],"fragment":true},{"name":"string","bnf":[["'\"'","%string43*","'\"'"]]},{"name":"HEXDIG","bnf":[[/[a-fA-F0-9]/]]}]
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
const { Parser } = require('ebnf'),
|
|
2
|
+
assert = require('assert')
|
|
3
|
+
|
|
4
|
+
let ParserRules = require('./RuleParser.production.ebnf.js')
|
|
5
|
+
let ParserCache;
|
|
6
|
+
|
|
7
|
+
const ArithmeticOperators = {
|
|
8
|
+
"+": 'MathAdd',
|
|
9
|
+
"-": 'MathSub',
|
|
10
|
+
"/": 'MathDiv',
|
|
11
|
+
"*": 'MathMul',
|
|
12
|
+
"%": 'MathMod',
|
|
13
|
+
"??": "Default"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const OperatorFn = {
|
|
17
|
+
">": "Gt",
|
|
18
|
+
"<": "Lt",
|
|
19
|
+
">=": "Gte",
|
|
20
|
+
"<=": "Lte",
|
|
21
|
+
"==": "Eq",
|
|
22
|
+
"=": "Eq",
|
|
23
|
+
"!=": "Neq"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const LogicalOperators = {
|
|
27
|
+
"&&": 'And',
|
|
28
|
+
"AND": 'And',
|
|
29
|
+
"and": 'And',
|
|
30
|
+
"||": 'Or',
|
|
31
|
+
"OR": 'Or',
|
|
32
|
+
"or": 'Or',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const Epsilon = 0.01
|
|
36
|
+
|
|
37
|
+
class RuleParser {
|
|
38
|
+
static toAst(txt){
|
|
39
|
+
let ret
|
|
40
|
+
|
|
41
|
+
if(!ParserCache){
|
|
42
|
+
ParserCache = new Parser(ParserRules, {debug: false})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
ret = ParserCache.getAST(txt, 'statement_main');
|
|
46
|
+
|
|
47
|
+
if(ret){
|
|
48
|
+
return ret.children[0]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
static _parseArgument(argument){
|
|
52
|
+
assert(argument.type === 'argument')
|
|
53
|
+
const child = argument.children[0]
|
|
54
|
+
return RuleParser._buildExpressionGroup(child)
|
|
55
|
+
}
|
|
56
|
+
static _parseFcall(fcall){
|
|
57
|
+
const fname = fcall.children[0]
|
|
58
|
+
const ret = [fname.text]
|
|
59
|
+
if(fcall.children.length != 1){
|
|
60
|
+
const args = fcall.children[1]
|
|
61
|
+
for(const a of args.children){
|
|
62
|
+
ret.push(RuleParser._parseArgument(a))
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return ret
|
|
66
|
+
}
|
|
67
|
+
static _parseTimePeriod(tp){
|
|
68
|
+
switch(tp.type){
|
|
69
|
+
case 'time_period_const':
|
|
70
|
+
return ["TimePeriodConst", tp.text]
|
|
71
|
+
case 'between':
|
|
72
|
+
return ["TimePeriodBetween", RuleParser.__parseValue(tp.children[0]?.children[0]), RuleParser.__parseValue(tp.children[0]?.children[1])]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
static __parseValue(child){
|
|
76
|
+
const type = child.type
|
|
77
|
+
switch(type){
|
|
78
|
+
case 'string': {
|
|
79
|
+
const str = child.text
|
|
80
|
+
return str.slice(1, -1)
|
|
81
|
+
}
|
|
82
|
+
case 'number':
|
|
83
|
+
return parseFloat(child.text)
|
|
84
|
+
case 'number_tod': {
|
|
85
|
+
const tokens = child.text.split(':')
|
|
86
|
+
if (tokens.length !== 2) {
|
|
87
|
+
throw new Error(`Invalid time of day, ${child.text} should be ##:##`)
|
|
88
|
+
}
|
|
89
|
+
const hours = parseInt(tokens[0])
|
|
90
|
+
const minutes = parseInt(tokens[1])
|
|
91
|
+
const tod = hours * 100 + minutes
|
|
92
|
+
const ret = { hours, minutes, tod }
|
|
93
|
+
if (!isNaN(tod) && ret.hours >= 0 && ret.hours <= 24 && ret.minutes >= 0 && ret.minutes < 60) {
|
|
94
|
+
return ret
|
|
95
|
+
}
|
|
96
|
+
throw new Error(`Invalid time of day, ${child.text} -> [${tokens.join(', ')}] -> ${hours}h${minutes}m -> ${tod}`)
|
|
97
|
+
}
|
|
98
|
+
case 'number_time': {
|
|
99
|
+
const nt = child
|
|
100
|
+
const mult = parseFloat(nt.children[0].text)
|
|
101
|
+
switch(nt.children[1].text){
|
|
102
|
+
case 'seconds':
|
|
103
|
+
case 'second':
|
|
104
|
+
return mult
|
|
105
|
+
case 'minutes':
|
|
106
|
+
case 'minute':
|
|
107
|
+
case 'mins':
|
|
108
|
+
case 'min':
|
|
109
|
+
return mult * 60
|
|
110
|
+
case 'hours':
|
|
111
|
+
case 'hour':
|
|
112
|
+
return mult * 60 * 60
|
|
113
|
+
case 'days':
|
|
114
|
+
case 'day':
|
|
115
|
+
return mult * 60 * 60 * 24
|
|
116
|
+
case 'weeks':
|
|
117
|
+
case 'week':
|
|
118
|
+
return mult * 60 * 60 * 24 * 7
|
|
119
|
+
}
|
|
120
|
+
throw new Error(`Invalid exponent ${nt.children[1].text}`)
|
|
121
|
+
}
|
|
122
|
+
case 'true':
|
|
123
|
+
return true
|
|
124
|
+
case 'false':
|
|
125
|
+
return false
|
|
126
|
+
case 'array': {
|
|
127
|
+
const ret = []
|
|
128
|
+
for(const c of child.children){
|
|
129
|
+
ret.push(RuleParser.__parseValue(c.children[0]))
|
|
130
|
+
}
|
|
131
|
+
return ret;
|
|
132
|
+
}
|
|
133
|
+
default:
|
|
134
|
+
throw new Error(`Unknown value type ${type}`)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
static _parseValue(value){
|
|
138
|
+
const child = value.children[0]
|
|
139
|
+
|
|
140
|
+
const type = child.type
|
|
141
|
+
switch(type){
|
|
142
|
+
case 'time_period': {
|
|
143
|
+
const tp = child.children[0]
|
|
144
|
+
return RuleParser._parseTimePeriod(tp)
|
|
145
|
+
}
|
|
146
|
+
default:
|
|
147
|
+
return ['Value', RuleParser.__parseValue(child)]
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
static _parseSimpleResult(result){
|
|
151
|
+
assert(result.children.length == 1)
|
|
152
|
+
const child = result.children[0]
|
|
153
|
+
const type = child.type
|
|
154
|
+
switch(type){
|
|
155
|
+
case 'fcall':
|
|
156
|
+
return RuleParser._parseFcall(child)
|
|
157
|
+
case 'value':
|
|
158
|
+
return RuleParser._parseValue(child)
|
|
159
|
+
}
|
|
160
|
+
return null
|
|
161
|
+
}
|
|
162
|
+
static _parseArithmeticResult(result){
|
|
163
|
+
assert(result.children.length == 3)
|
|
164
|
+
const partA = this._parseSimpleResult(result.children[0])
|
|
165
|
+
const operatorFn = ArithmeticOperators[result.children[1].text]
|
|
166
|
+
const partB = this.__parseResult(result, 2)
|
|
167
|
+
|
|
168
|
+
return [operatorFn, partA, partB]
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
static __parseResult(result, idx){
|
|
172
|
+
const child = result.children[idx]
|
|
173
|
+
const type = child.type
|
|
174
|
+
switch(type){
|
|
175
|
+
case 'simple_result':
|
|
176
|
+
return RuleParser._parseSimpleResult(child)
|
|
177
|
+
case 'arithmetic_result':
|
|
178
|
+
return RuleParser._parseArithmeticResult(child)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
throw new Error(`Unknown result node ${type}`)
|
|
182
|
+
}
|
|
183
|
+
static _parseResult(result){
|
|
184
|
+
assert(result.children.length == 1)
|
|
185
|
+
|
|
186
|
+
return RuleParser.__parseResult(result, 0)
|
|
187
|
+
}
|
|
188
|
+
static _parseStdExpression(expr){
|
|
189
|
+
assert(expr.type === 'standard_expression')
|
|
190
|
+
switch(expr.children.length){
|
|
191
|
+
case 1:
|
|
192
|
+
return RuleParser._parseResult(expr.children[0])
|
|
193
|
+
case 2: {
|
|
194
|
+
const rhs = expr.children[1]
|
|
195
|
+
switch(rhs.type){
|
|
196
|
+
case 'between_tod':
|
|
197
|
+
case 'between_number':
|
|
198
|
+
case 'between':
|
|
199
|
+
return ['Between', RuleParser._parseResult(expr.children[0]), ['Value', RuleParser.__parseValue(rhs.children[0].children[0])], ['Value', RuleParser.__parseValue(rhs.children[0].children[1])]]
|
|
200
|
+
case 'basic_rhs':
|
|
201
|
+
return [OperatorFn[rhs.children[0].text], RuleParser._parseResult(expr.children[0]), RuleParser._parseResult(rhs.children[1])]
|
|
202
|
+
case 'eq_approx': {
|
|
203
|
+
const rhsValue = RuleParser._parseResult(rhs.children[1])
|
|
204
|
+
assert(rhsValue[0] === 'Value')
|
|
205
|
+
const ret = ['Between', RuleParser._parseResult(expr.children[0]), ['Value', rhsValue[1] - Epsilon], ['Value', rhsValue[1] + Epsilon]]
|
|
206
|
+
if(rhs.children[0].text === '!='){
|
|
207
|
+
return ['Not', ret]
|
|
208
|
+
}
|
|
209
|
+
return ret
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
default:
|
|
213
|
+
throw new Error(`unable to parse std expression, unknown rhs type ${rhs.type}`)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
default:
|
|
218
|
+
throw new Error(`unable to parse std expression, unknown number of children ${expr.children.length}`)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
static buildLogical(members, fn){
|
|
222
|
+
return [fn, ...members]
|
|
223
|
+
}
|
|
224
|
+
static _buildExpressionGroup(ast){
|
|
225
|
+
let ret = []
|
|
226
|
+
let currentLogical = null
|
|
227
|
+
for(const expr of ast.children){
|
|
228
|
+
if(expr.type == 'logical_operator') {
|
|
229
|
+
const logicalOperator = expr.text.trim()
|
|
230
|
+
const operatorFn = LogicalOperators[logicalOperator]
|
|
231
|
+
assert(operatorFn, `Unknown logical operator ${logicalOperator}`)
|
|
232
|
+
if(currentLogical === null || currentLogical !== operatorFn){
|
|
233
|
+
if(ret.length > 1){
|
|
234
|
+
ret = [RuleParser.buildLogical(ret, currentLogical)]
|
|
235
|
+
}
|
|
236
|
+
currentLogical = operatorFn
|
|
237
|
+
}
|
|
238
|
+
}else{
|
|
239
|
+
ret.push(RuleParser._exprToIL(expr))
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if(ret.length == 0){
|
|
243
|
+
throw new Error('invalid rule')
|
|
244
|
+
}
|
|
245
|
+
if(ret.length == 1){
|
|
246
|
+
return ret[0]
|
|
247
|
+
}
|
|
248
|
+
return RuleParser.buildLogical(ret, currentLogical)
|
|
249
|
+
}
|
|
250
|
+
static _parseParenthesisExpression(expr){
|
|
251
|
+
return RuleParser._buildExpressionGroup(expr.children[0])
|
|
252
|
+
}
|
|
253
|
+
static _exprToIL(expr){
|
|
254
|
+
assert(expr.type === 'expression')
|
|
255
|
+
assert(expr.children.length === 1)
|
|
256
|
+
const eInner = expr.children[0]
|
|
257
|
+
switch(eInner.type){
|
|
258
|
+
case 'standard_expression':
|
|
259
|
+
return RuleParser._parseStdExpression(eInner)
|
|
260
|
+
case 'not_expression': {
|
|
261
|
+
const child = eInner.children[0]
|
|
262
|
+
let result
|
|
263
|
+
switch(child.type){
|
|
264
|
+
case 'parenthesis_expression':
|
|
265
|
+
result = RuleParser._parseParenthesisExpression(child)
|
|
266
|
+
break;
|
|
267
|
+
default:
|
|
268
|
+
result = RuleParser._parseResult(child)
|
|
269
|
+
}
|
|
270
|
+
return ['Not', result]
|
|
271
|
+
}
|
|
272
|
+
case 'parenthesis_expression':
|
|
273
|
+
return RuleParser._parseParenthesisExpression(eInner)
|
|
274
|
+
default:
|
|
275
|
+
throw new Error(`unknown type of expression ${eInner.type}`)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
static toIL(txt){
|
|
279
|
+
const ast = RuleParser.toAst(txt)
|
|
280
|
+
if(!ast) throw new Error(`failed to parse ${txt}`)
|
|
281
|
+
return RuleParser._buildExpressionGroup(ast)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
module.exports = RuleParser
|
package/.eslintrc.yml
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
name: Build
|
|
2
|
-
on:
|
|
3
|
-
push:
|
|
4
|
-
|
|
5
|
-
jobs:
|
|
6
|
-
check_eslint:
|
|
7
|
-
runs-on: ubuntu-latest
|
|
8
|
-
steps:
|
|
9
|
-
- uses: actions/checkout@v2
|
|
10
|
-
- name: Install modules
|
|
11
|
-
run: |
|
|
12
|
-
sudo npm install -g eslint@8
|
|
13
|
-
- run: eslint index.js --ext .js,.jsx,.ts,.tsx
|
|
14
|
-
test:
|
|
15
|
-
runs-on: ubuntu-latest
|
|
16
|
-
steps:
|
|
17
|
-
- name: Checkout repository
|
|
18
|
-
uses: actions/checkout@master
|
|
19
|
-
- name: Set up Node.js
|
|
20
|
-
uses: actions/setup-node@master
|
|
21
|
-
with:
|
|
22
|
-
node-version: 17
|
|
23
|
-
- run: |
|
|
24
|
-
npm install
|
|
25
|
-
- run: |
|
|
26
|
-
npm run-script test
|
|
27
|
-
npm:
|
|
28
|
-
name: npm-publish
|
|
29
|
-
if: "github.event_name == 'push'"
|
|
30
|
-
runs-on: ubuntu-latest
|
|
31
|
-
needs: [check_eslint,test]
|
|
32
|
-
steps:
|
|
33
|
-
- name: Checkout repository
|
|
34
|
-
uses: actions/checkout@master
|
|
35
|
-
- name: Set up Node.js
|
|
36
|
-
uses: actions/setup-node@master
|
|
37
|
-
with:
|
|
38
|
-
node-version: 16
|
|
39
|
-
registry-url: 'https://registry.npmjs.org'
|
|
40
|
-
- name: Install modules
|
|
41
|
-
run: |
|
|
42
|
-
npm install
|
|
43
|
-
- name: Test (Development)
|
|
44
|
-
run: npm test
|
|
45
|
-
- name: Build
|
|
46
|
-
run: node bin/package.js
|
|
47
|
-
- name: Test (Production)
|
|
48
|
-
run: npm test
|
|
49
|
-
- run: |
|
|
50
|
-
VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,' | sed -e 's/^v//')
|
|
51
|
-
jq '.version="'"$VERSION"'" | .main="./src/RuleParser.production.js"' package.json > /tmp/a
|
|
52
|
-
|
|
53
|
-
mv /tmp/a package.json
|
|
54
|
-
- run: |
|
|
55
|
-
# npm login --scope=@halleyassist --registry=https://registry.npmjs.org/
|
|
56
|
-
npm whoami
|
|
57
|
-
env:
|
|
58
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
|
|
59
|
-
- run: |
|
|
60
|
-
npm publish
|
|
61
|
-
env:
|
|
62
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
|
|
63
|
-
if: "startsWith(github.ref, 'refs/tags/v')"
|
|
64
|
-
- uses: actions/setup-node@v2
|
|
65
|
-
with:
|
|
66
|
-
node-version: 16
|
|
67
|
-
registry-url: https://npm.pkg.github.com/
|
|
68
|
-
if: "startsWith(github.ref, 'refs/tags/v')"
|
|
69
|
-
- run: |
|
|
70
|
-
npm publish
|
|
71
|
-
env:
|
|
72
|
-
NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
|
73
|
-
if: "startsWith(github.ref, 'refs/tags/v')"
|
package/bin/package.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
const fs = require('fs')
|
|
2
|
-
|
|
3
|
-
const ParserRules = require('../src/RuleParser.ebnf.js')
|
|
4
|
-
const rules = JSON.stringify(ParserRules)
|
|
5
|
-
|
|
6
|
-
fs.writeFileSync('src/RuleParser.ebnf.json', rules)
|
|
7
|
-
|
|
8
|
-
const ruleParserJs = fs.readFileSync('src/RuleParser.js', 'utf8')
|
|
9
|
-
const ruleParserJsFixed = ruleParserJs.replace("require('./RuleParser.ebnf.js')", 'require(\'./RuleParser.ebnf.json\')')
|
|
10
|
-
|
|
11
|
-
fs.writeFileSync('src/RuleParser.production.js', ruleParserJsFixed)
|
package/src/RuleParser.ebnf.json
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
[{"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*",{},"WS*"]]},{"name":"BEGIN_OBJECT","bnf":[["WS*",{},"WS*"]]},{"name":"END_ARRAY","bnf":[["WS*",{},"WS*"]]},{"name":"END_OBJECT","bnf":[["WS*",{},"WS*"]]},{"name":"NAME_SEPARATOR","bnf":[["WS*",{},"WS*"]]},{"name":"VALUE_SEPARATOR","bnf":[["WS*",{},"WS*"]]},{"name":"%WS8","bnf":[[{}]]},{"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":[[{}]]},{"name":"fname","bnf":[["%fname10+"]]},{"name":"fcall","bnf":[["fname","WS*","BEGIN_ARGUMENT","arguments?","END_ARGUMENT"]]},{"name":"%%%between_number111213","bnf":[["\"and\""],["\"AND\""]],"fragment":true},{"name":"%%between_number1112","bnf":[["WS+","%%%between_number111213","WS+"]],"fragment":true},{"name":"%%between_number1114","bnf":[["WS*","\"-\"","WS*"]],"fragment":true},{"name":"%between_number11","bnf":[["%%between_number1112"],["%%between_number1114"]],"fragment":true},{"name":"between_number","bnf":[["number","%between_number11","number"]]},{"name":"%%%between_tod151617","bnf":[["\"and\""],["\"AND\""]],"fragment":true},{"name":"%%between_tod1516","bnf":[["WS+","%%%between_tod151617","WS+"]],"fragment":true},{"name":"%between_tod15","bnf":[["%%between_tod1516"]],"fragment":true},{"name":"between_tod","bnf":[["number_tod","%between_tod15","number_tod"]]},{"name":"%between18","bnf":[["\"between\""],["\"BETWEEN\""]],"fragment":true},{"name":"%between19","bnf":[["between_number"],["between_tod"]],"fragment":true},{"name":"between","bnf":[["%between18","WS+","%between19"]]},{"name":"%AND20","bnf":[["WS*","\"&&\"","WS*"]],"fragment":true},{"name":"%%AND2122","bnf":[["\"AND\""],["\"and\""]],"fragment":true},{"name":"%AND21","bnf":[["WS+","%%AND2122","WS+"]],"fragment":true},{"name":"AND","bnf":[["%AND20"],["%AND21"]]},{"name":"%OR23","bnf":[["WS*","\"||\"","WS*"]],"fragment":true},{"name":"%%OR2425","bnf":[["\"OR\""],["\"or\""]],"fragment":true},{"name":"%OR24","bnf":[["WS+","%%OR2425","WS+"]],"fragment":true},{"name":"OR","bnf":[["%OR23"],["%OR24"]]},{"name":"GT","bnf":[["\">\""]]},{"name":"LT","bnf":[["\"<\""]]},{"name":"GTE","bnf":[["\">=\""]]},{"name":"LTE","bnf":[["\"<=\""]]},{"name":"IS","bnf":[["\"is\""],["\"IS\""]]},{"name":"EQ","bnf":[["\"==\""],["\"=\""]]},{"name":"NEQ","bnf":[["\"!=\""]]},{"name":"%NOT26","bnf":[["\"!\"","WS*"]],"fragment":true},{"name":"%NOT27","bnf":[["\"not\"","WS+"]],"fragment":true},{"name":"NOT","bnf":[["%NOT26"],["%NOT27"]]},{"name":"false","bnf":[["\"false\""],["\"FALSE\""]]},{"name":"null","bnf":[["\"null\""],["\"NULL\""]]},{"name":"true","bnf":[["\"true\""],["\"TRUE\""]]},{"name":"%%array2829","bnf":[["VALUE_SEPARATOR","value"]],"fragment":true},{"name":"%array28","bnf":[["value","%%array2829*"]],"fragment":true},{"name":"array","bnf":[["BEGIN_ARRAY","%array28?","END_ARRAY"]]},{"name":"unit","bnf":[["\"seconds\""],["\"second\""],["\"minutes\""],["\"minute\""],["\"min\""],["\"mins\""],["\"min\""],["\"hours\""],["\"hour\""],["\"days\""],["\"day\""],["\"weeks\""],["\"week\""]]},{"name":"%%number3031","bnf":[[{}]]},{"name":"%number30","bnf":[["%%number3031+"]],"fragment":true},{"name":"%%number3233","bnf":[[{}]]},{"name":"%number32","bnf":[["\".\"","%%number3233+"]],"fragment":true},{"name":"%%number3435","bnf":[["\"e\""],["\"E\""]],"fragment":true},{"name":"%%number3436","bnf":[["\"-\""],["\"+\""]],"fragment":true},{"name":"%%%number343738","bnf":[[{}]]},{"name":"%%number3437","bnf":[["\"0\""],[{},"%%%number343738*"]],"fragment":true},{"name":"%number34","bnf":[["%%number3435","%%number3436?","%%number3437"]],"fragment":true},{"name":"number","bnf":[["\"-\"?","%number30","%number32?","%number34?"]]},{"name":"number_time","bnf":[["number","WS+","unit"]]},{"name":"%%number_tod3940","bnf":[[{}]]},{"name":"%number_tod39","bnf":[["%%number_tod3940+"]],"fragment":true},{"name":"%%number_tod4142","bnf":[[{}]]},{"name":"%number_tod41","bnf":[["%%number_tod4142+"]],"fragment":true},{"name":"number_tod","bnf":[["%number_tod39","\":\"","%number_tod41"]]},{"name":"time_period_const","bnf":[["\"today\""]]},{"name":"time_period","bnf":[["time_period_const"],["between"]]},{"name":"%%string4344","bnf":[[{}],[{}],[{}]],"fragment":true},{"name":"%%string4345","bnf":[[{}],[{}],[{}],[{}],[{}],[{}],[{}],[{}],[{},"HEXDIG","HEXDIG","HEXDIG","HEXDIG"]],"fragment":true},{"name":"%string43","bnf":[["%%string4344"],[{},"%%string4345"]],"fragment":true},{"name":"string","bnf":[["'\"'","%string43*","'\"'"]]},{"name":"HEXDIG","bnf":[[{}]]}]
|
package/test/rule_parse_test.js
DELETED
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
const
|
|
2
|
-
RuleParser = require("../"),
|
|
3
|
-
{ expect } = require('chai')
|
|
4
|
-
|
|
5
|
-
describe("RuleParser", function () {
|
|
6
|
-
it("parse basic", function () {
|
|
7
|
-
expect(RuleParser.toIL("1 > 2")).to.be.eql(["Gt", ["Value", 1], ["Value", 2]])
|
|
8
|
-
expect(RuleParser.toIL("AFunction(1,2) > 3")).to.be.eql(["Gt", ["AFunction", ["Value", 1], ["Value", 2]], ["Value", 3]])
|
|
9
|
-
expect(RuleParser.toIL("AFunction(1,2)")).to.be.eql(["AFunction", ["Value", 1], ["Value", 2]])
|
|
10
|
-
expect(RuleParser.toIL("AFunction(\"string\",2) == \"string\"")).to.be.eql(["Eq", ["AFunction", ["Value", "string"], ["Value", 2]], ["Value", "string"]])
|
|
11
|
-
})
|
|
12
|
-
it("parse logic expression", function () {
|
|
13
|
-
expect(RuleParser.toIL("AFunction()")).to.be.eql(["AFunction"])
|
|
14
|
-
expect(RuleParser.toIL("!AFunction()")).to.be.eql(["Not", ["AFunction"]])
|
|
15
|
-
expect(RuleParser.toIL("! AFunction()")).to.be.eql(["Not", ["AFunction"]])
|
|
16
|
-
expect(RuleParser.toIL("not AFunction()")).to.be.eql(["Not", ["AFunction"]])
|
|
17
|
-
})
|
|
18
|
-
it("parse function no args", function () {
|
|
19
|
-
expect(RuleParser.toIL("AFunction() > 3")).to.be.eql(["Gt", ["AFunction"], ["Value", 3]])
|
|
20
|
-
})
|
|
21
|
-
it("parse number starting with 0", function () {
|
|
22
|
-
expect(RuleParser.toIL("AFunction() > 03")).to.be.eql(["Gt", ["AFunction"], ["Value", 3]])
|
|
23
|
-
})
|
|
24
|
-
it("should throw if additional junk", function () {
|
|
25
|
-
expect(function () {
|
|
26
|
-
RuleParser.toIL("AFunction() > 3 abc")
|
|
27
|
-
}).to.throw()
|
|
28
|
-
})
|
|
29
|
-
it("parse array", function () {
|
|
30
|
-
expect(RuleParser.toIL("AFunction([1,2])")).to.be.eql(["AFunction", ["Value", [1, 2]]])
|
|
31
|
-
})
|
|
32
|
-
it("should be able to parse all basic comparison operators", function () {
|
|
33
|
-
const operators = [
|
|
34
|
-
'==', '=', "!=", ">=", ">", "<=", '<'
|
|
35
|
-
]
|
|
36
|
-
for (const op of operators) {
|
|
37
|
-
const expression1 = `RoomDuration("Room 1") ${op} 5`
|
|
38
|
-
RuleParser.toIL(expression1)
|
|
39
|
-
const expression2 = `RoomDuration("Room 1")${op}5`
|
|
40
|
-
RuleParser.toIL(expression2)
|
|
41
|
-
}
|
|
42
|
-
})
|
|
43
|
-
it("should be able to parse the between operator", function () {
|
|
44
|
-
const expression1 = `RoomDuration("Room 1") between 1 and 5`
|
|
45
|
-
expect(RuleParser.toIL(expression1)).to.be.eql(['Between', ["RoomDuration", ["Value", "Room 1"]], ["Value", 1], ["Value", 5]])
|
|
46
|
-
})
|
|
47
|
-
it("should be able to parse the equals approximately operator", function () {
|
|
48
|
-
const expression1 = `RoomDuration("Room 1") == ~1`
|
|
49
|
-
expect(RuleParser.toIL(expression1)).to.be.eql([
|
|
50
|
-
'Between',
|
|
51
|
-
['RoomDuration', ['Value', 'Room 1']],
|
|
52
|
-
['Value', 0.99],
|
|
53
|
-
['Value', 1.01]
|
|
54
|
-
])
|
|
55
|
-
})
|
|
56
|
-
it("should be able to parse the approximately not equals operator", function () {
|
|
57
|
-
const expression1 = `RoomDuration("Room 1") !=~1`
|
|
58
|
-
expect(RuleParser.toIL(expression1)).to.be.eql([
|
|
59
|
-
'Not', ['Between',
|
|
60
|
-
['RoomDuration', ['Value', 'Room 1']],
|
|
61
|
-
['Value', 0.99],
|
|
62
|
-
['Value', 1.01]
|
|
63
|
-
]])
|
|
64
|
-
})
|
|
65
|
-
it("should be able to parse all time units", function () {
|
|
66
|
-
const unit = [
|
|
67
|
-
'second', 'minute', 'hour', 'day', 'week'
|
|
68
|
-
]
|
|
69
|
-
for (const u of unit) {
|
|
70
|
-
const expression1 = `RoomDuration(1 ${u})`
|
|
71
|
-
RuleParser.toIL(expression1)
|
|
72
|
-
const expression2 = `RoomDuration(1 ${u}s)`
|
|
73
|
-
RuleParser.toIL(expression2)
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
it("should be able to parse all time tp constants", function () {
|
|
77
|
-
const unit = [
|
|
78
|
-
'today'/*, "yesterday"*/
|
|
79
|
-
]
|
|
80
|
-
for (const u of unit) {
|
|
81
|
-
const expression1 = `RoomDuration(${u})`
|
|
82
|
-
const il = RuleParser.toIL(expression1)
|
|
83
|
-
expect(il).to.be.eql(['RoomDuration', ['TimePeriodConst', u]])
|
|
84
|
-
}
|
|
85
|
-
})
|
|
86
|
-
it("should be able to parse tp between", function () {
|
|
87
|
-
const expression1 = `RoomDuration(BETWEEN 01:00 and 24:00)`
|
|
88
|
-
const il = RuleParser.toIL(expression1)
|
|
89
|
-
const oneAm = {hours: 1, minutes: 0, tod: 100}
|
|
90
|
-
const midnight = {hours: 24, minutes: 0, tod: 2400}
|
|
91
|
-
expect(il).to.be.eql(['RoomDuration', ['TimePeriodBetween', oneAm, midnight]])
|
|
92
|
-
})
|
|
93
|
-
it("should be able to parse parenthesis", function () {
|
|
94
|
-
const expression1 = `(A()==2 and B()==1) and (C(2) && D(1))`
|
|
95
|
-
const il = RuleParser.toIL(expression1)
|
|
96
|
-
expect(il).to.be.eql([
|
|
97
|
-
'And',
|
|
98
|
-
['And', ['Eq', ["A"], ["Value", 2]], ['Eq', ["B"], ["Value", 1]]],
|
|
99
|
-
['And', ['C', ["Value", 2]], ['D', ["Value", 1]]]
|
|
100
|
-
])
|
|
101
|
-
})
|
|
102
|
-
it("should be able to parse not expressions (1)", function () {
|
|
103
|
-
const expression1 = `!D(1)`
|
|
104
|
-
let il = RuleParser.toIL(expression1)
|
|
105
|
-
expect(il).to.be.eql(['Not', ['D', ['Value', 1]]])
|
|
106
|
-
const expression2 = `! D(1)&& !D(2)`
|
|
107
|
-
il = RuleParser.toIL(expression2)
|
|
108
|
-
expect(il).to.be.eql(['And', ['Not', ['D', ['Value', 1]]], ['Not', ['D', ['Value', 2]]]])
|
|
109
|
-
const expression3 = `(! D(1) && !D(3)) && !D(2)`
|
|
110
|
-
il = RuleParser.toIL(expression3)
|
|
111
|
-
expect(il).to.be.eql([
|
|
112
|
-
'And',
|
|
113
|
-
['And', ['Not', ['D', ['Value', 1]]], ['Not', ['D', ['Value', 3]]]],
|
|
114
|
-
['Not', ['D', ['Value', 2]]]
|
|
115
|
-
])
|
|
116
|
-
})
|
|
117
|
-
it("should be able to parse not expressions (2)", function () {
|
|
118
|
-
const expression1 = `(A()==2 and B()==1) and (C(2) && !D(1))`
|
|
119
|
-
const il = RuleParser.toIL(expression1)
|
|
120
|
-
expect(il).to.be.eql([
|
|
121
|
-
'And',
|
|
122
|
-
['And', ['Eq', ["A"], ["Value", 2]], ['Eq', ["B"], ["Value", 1]]],
|
|
123
|
-
['And', ['C', ["Value", 2]], ['Not', ['D', ["Value", 1]]]]
|
|
124
|
-
])
|
|
125
|
-
})
|
|
126
|
-
it("should be able to parse not expressions (3)", function () {
|
|
127
|
-
const expression1 = `(A()==2 and B()==1) and ! (C(2) && D(1))`
|
|
128
|
-
const il = RuleParser.toIL(expression1)
|
|
129
|
-
expect(il).to.be.eql([
|
|
130
|
-
'And',
|
|
131
|
-
['And', ['Eq', ["A"], ["Value", 2]], ['Eq', ["B"], ["Value", 1]]],
|
|
132
|
-
['Not', ['And', ['C', ["Value", 2]], ['D', ["Value", 1]]]]
|
|
133
|
-
])
|
|
134
|
-
})
|
|
135
|
-
it("should be able to parse or expressions (1)", function () {
|
|
136
|
-
const expression1 = `(A()==2 and B()==1) or (C(2) && D(1))`
|
|
137
|
-
const il = RuleParser.toIL(expression1)
|
|
138
|
-
expect(il).to.be.eql([
|
|
139
|
-
'Or',
|
|
140
|
-
['And', ['Eq', ["A"], ["Value", 2]], ['Eq', ["B"], ["Value", 1]]],
|
|
141
|
-
['And', ['C', ["Value", 2]], ['D', ["Value", 1]]]
|
|
142
|
-
])
|
|
143
|
-
})
|
|
144
|
-
it("should be able to parse or expressions (2)", function () {
|
|
145
|
-
const expression1 = `(A()==2 or B()==1) and (C(2) || D(1))`
|
|
146
|
-
const il = RuleParser.toIL(expression1)
|
|
147
|
-
expect(il).to.be.eql([
|
|
148
|
-
'And',
|
|
149
|
-
['Or', ['Eq', ["A"], ["Value", 2]], ['Eq', ["B"], ["Value", 1]]],
|
|
150
|
-
['Or', ['C', ["Value", 2]], ['D', ["Value", 1]]]
|
|
151
|
-
])
|
|
152
|
-
})
|
|
153
|
-
it("should be able to parse or expressions (3)", function () {
|
|
154
|
-
const expression1 = `A() or !(C(2) && D(1))`
|
|
155
|
-
const il = RuleParser.toIL(expression1)
|
|
156
|
-
expect(il).to.be.eql([
|
|
157
|
-
'Or',
|
|
158
|
-
["A"],
|
|
159
|
-
['Not', ['And', ['C', ["Value", 2]], ['D', ["Value", 1]]]]
|
|
160
|
-
])
|
|
161
|
-
})
|
|
162
|
-
it("should be able to parse or expressions (4)", function () {
|
|
163
|
-
const expression1 = `A() and !(C(2) || D(1))`
|
|
164
|
-
const il = RuleParser.toIL(expression1)
|
|
165
|
-
expect(il).to.be.eql([
|
|
166
|
-
'And',
|
|
167
|
-
["A"],
|
|
168
|
-
['Not', ['Or', ['C', ["Value", 2]], ['D', ["Value", 1]]]]
|
|
169
|
-
])
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
it("should be able to parse real or expressions (1)", function () {
|
|
173
|
-
const expression1 = "HasCapableSensor(\"weight\") && !((TimeOfDay() BETWEEN 0800 AND 1200 && Event(\"kind\") == \"measurement\" && EventHasData(\"weight\") && TimeLastTrueSet(\"slot1\")) || TimeLastTrueCheck(\"slot1\") < 5 minutes)"
|
|
174
|
-
const il = RuleParser.toIL(expression1)
|
|
175
|
-
expect(il).to.be.eql(
|
|
176
|
-
["And",
|
|
177
|
-
["HasCapableSensor", ["Value", "weight"]],
|
|
178
|
-
["Not", ["Or",
|
|
179
|
-
["And", ["Between", ["TimeOfDay"], ["Value", 800], ["Value", 1200]], ["Eq", ["Event", ["Value", "kind"]], ["Value", "measurement"]], ["EventHasData", ["Value", "weight"]], ["TimeLastTrueSet", ["Value", "slot1"]]],
|
|
180
|
-
["Lt", ["TimeLastTrueCheck", ["Value", "slot1"]], ["Value", 300]]
|
|
181
|
-
]]])
|
|
182
|
-
})
|
|
183
|
-
it("should be able to parse arithmetic (1)", function () {
|
|
184
|
-
const expression1 = `A() + D(1) == 2`
|
|
185
|
-
const il = RuleParser.toIL(expression1)
|
|
186
|
-
expect(il).to.be.eql(["Eq",["MathAdd",["A"],["D",["Value",1]]],["Value",2]])
|
|
187
|
-
})
|
|
188
|
-
it("should be able to parse arithmetic (2)", function () {
|
|
189
|
-
const expression1 = `A() + D(1) * 4 == 2 * A()`
|
|
190
|
-
const il = RuleParser.toIL(expression1)
|
|
191
|
-
expect(il).to.be.eql(["Eq",["MathAdd",["A"],["MathMul",["D",["Value",1]],["Value",4]]],["MathMul",["Value",2],["A"]]])
|
|
192
|
-
})
|
|
193
|
-
it("should be able to parse arithmetic (3)", function () {
|
|
194
|
-
const expression1 = `A() + D(1)`
|
|
195
|
-
const il = RuleParser.toIL(expression1)
|
|
196
|
-
expect(il).to.be.eql(["MathAdd",["A"],["D",["Value",1]]])
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
it("should be able to parse && in arguments", function () {
|
|
200
|
-
const expression1 = `A(B() && C())`
|
|
201
|
-
const il = RuleParser.toIL(expression1)
|
|
202
|
-
expect(il).to.be.eql(["A",["And",["B"],["C"]]])
|
|
203
|
-
})
|
|
204
|
-
it("should be able to parse default value operator", function () {
|
|
205
|
-
const expression1 = `A() ?? 1`
|
|
206
|
-
const il = RuleParser.toIL(expression1)
|
|
207
|
-
expect(il).to.be.eql(["Default",["A"],["Value",1]])
|
|
208
|
-
})
|
|
209
|
-
it("should be able to parse default value operator in an expression", function () {
|
|
210
|
-
const expression1 = `A() ?? 1 < 20`
|
|
211
|
-
const il = RuleParser.toIL(expression1)
|
|
212
|
-
expect(il).to.be.eql(["Lt",["Default",["A"],["Value",1]],["Value",20]])
|
|
213
|
-
})
|
|
214
|
-
});
|