@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 CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@halleyassist/rule-parser",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "The grammar for HalleyAssist rules",
5
- "main": "./src/RuleParser.production.js",
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,8 +0,0 @@
1
- env:
2
- node: true
3
- es2021: true
4
- extends: 'eslint:recommended'
5
- parserOptions:
6
- ecmaVersion: 12
7
- sourceType: module
8
- rules: {}
@@ -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)
@@ -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":[[{}]]}]
@@ -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
- });