@briza/illogical 2.0.1 → 2.1.0
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/lib/illogical.cjs +3165 -177
- package/lib/illogical.esm.js +3165 -177
- package/package.json +37 -12
- package/readme.md +57 -846
- package/types/bytecode/compiler.d.ts +29 -0
- package/types/bytecode/evaluable.d.ts +14 -0
- package/types/bytecode/get-bytecode.d.ts +1 -0
- package/types/bytecode/interpreter.d.ts +10 -0
- package/types/bytecode/opcodes.d.ts +51 -0
- package/types/bytecode/operateWithExpectedDecimals.d.ts +1 -0
- package/types/bytecode/refs.d.ts +73 -0
- package/types/bytecode/simplifier.d.ts +16 -0
- package/types/common/type-check.d.ts +2 -2
- package/types/index.d.ts +5 -1
- package/types/parser/options.d.ts +12 -2
package/readme.md
CHANGED
|
@@ -1,82 +1,40 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<!-- PROJECT LOGO -->
|
|
3
|
+
<br />
|
|
4
|
+
<a href="https://github.com/briza-insurance/illogical">
|
|
5
|
+
<img src="specs/header.png" alt="illogical Header">
|
|
6
|
+
</a>
|
|
7
|
+
<br />
|
|
8
|
+
<div align="center">
|
|
9
|
+
<h3 align="center">illogical</h3>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
[](https://github.com/briza-insurance/illogical/actions?branch=master)
|
|
13
|
+
[](https://badge.fury.io/js/@briza%2Fillogical)
|
|
14
|
+
[](https://packagephobia.com/result?p=@briza/illogical)
|
|
15
|
+

|
|
16
|
+

|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div align="center">
|
|
21
|
+
<p>
|
|
22
|
+
<br />
|
|
23
|
+
JSON DSL for expressing and evaluating business rules. Underwriters use illogical to model business rules for their question sets, enabling distributors to render great user experiences.
|
|
24
|
+
</p>
|
|
25
|
+
</div>
|
|
2
26
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## About
|
|
6
|
-
|
|
7
|
-
This project has been developed to provide a shared conditional logic between front-end and back-end code, stored in JSON or in any other data serialization format.
|
|
8
|
-
|
|
9
|
-
> Code documentation could be found here: https://briza-insurance.github.io/illogical/index.html.
|
|
10
|
-
|
|
11
|
-
> The library is being build as **CommonJS** module and **ESM**.
|
|
27
|
+
---
|
|
12
28
|
|
|
13
|
-
|
|
29
|
+
This project is designed such that business rules can be shared between the front-end and back-end, serialized in JSON.
|
|
14
30
|
|
|
15
|
-
|
|
16
|
-
npm install -D @briza/illogical
|
|
17
|
-
```
|
|
31
|
+
## 🚀 Get Started
|
|
18
32
|
|
|
19
33
|
```sh
|
|
20
|
-
|
|
34
|
+
# install illogical
|
|
35
|
+
npm install @briza/illogical
|
|
21
36
|
```
|
|
22
37
|
|
|
23
|
-
**Table of Content**
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
- [Basic Usage](#basic-usage)
|
|
28
|
-
- [Evaluate](#evaluate)
|
|
29
|
-
- [Statement](#statement)
|
|
30
|
-
- [Parse](#parse)
|
|
31
|
-
- [Evaluable Function](#evaluable-function)
|
|
32
|
-
- [Simplify](#simplify)
|
|
33
|
-
- [Working with Expressions](#working-with-expressions)
|
|
34
|
-
- [Evaluation Data Context](#evaluation-data-context)
|
|
35
|
-
- [Operand Types](#operand-types)
|
|
36
|
-
- [Value](#value)
|
|
37
|
-
- [Reference](#reference)
|
|
38
|
-
- [Collection](#collection)
|
|
39
|
-
- [Comparison Expressions](#comparison-expressions)
|
|
40
|
-
- [Equal](#equal)
|
|
41
|
-
- [Not Equal](#not-equal)
|
|
42
|
-
- [Greater Than](#greater-than)
|
|
43
|
-
- [Greater Than or Equal](#greater-than-or-equal)
|
|
44
|
-
- [Less Than](#less-than)
|
|
45
|
-
- [Less Than or Equal](#less-than-or-equal)
|
|
46
|
-
- [In](#in)
|
|
47
|
-
- [Not In](#not-in)
|
|
48
|
-
- [Prefix](#prefix)
|
|
49
|
-
- [Suffix](#suffix)
|
|
50
|
-
- [Overlap](#overlap)
|
|
51
|
-
- [Undefined](#undefined)
|
|
52
|
-
- [Present](#present)
|
|
53
|
-
- [Logical Expressions](#logical-expressions)
|
|
54
|
-
- [And](#and)
|
|
55
|
-
- [Or](#or)
|
|
56
|
-
- [Nor](#nor)
|
|
57
|
-
- [Xor](#xor)
|
|
58
|
-
- [Not](#not)
|
|
59
|
-
- [Arithmetic Expressions](#arithmetic-expressions)
|
|
60
|
-
- [Divide](#divide)
|
|
61
|
-
- [Multiply](#multiply)
|
|
62
|
-
- [Subtract](#subtract)
|
|
63
|
-
- [Sum](#sum)
|
|
64
|
-
- [Engine Options](#engine-options)
|
|
65
|
-
- [Parser Options](#parser-options)
|
|
66
|
-
- [Reference Predicate](#reference-predicate)
|
|
67
|
-
- [Reference Transform](#reference-transform)
|
|
68
|
-
- [Operator Mapping](#operator-mapping)
|
|
69
|
-
- [Breaking Changes](#breaking-changes)
|
|
70
|
-
- [v1.2.0](#v120)
|
|
71
|
-
- [v1.4.2](#v142)
|
|
72
|
-
- [Contributing](#contributing)
|
|
73
|
-
- [Pull Request Process](#pull-request-process)
|
|
74
|
-
- [License](#license)
|
|
75
|
-
|
|
76
|
-
---
|
|
77
|
-
|
|
78
|
-
## Basic Usage
|
|
79
|
-
|
|
80
38
|
```js
|
|
81
39
|
// Import the illogical engine
|
|
82
40
|
import Engine from '@briza/illogical'
|
|
@@ -84,811 +42,64 @@ import Engine from '@briza/illogical'
|
|
|
84
42
|
// Create a new instance of the engine
|
|
85
43
|
const engine = new Engine()
|
|
86
44
|
|
|
87
|
-
// Evaluate the raw expression
|
|
88
|
-
const result = engine.evaluate(['==', 5, 5])
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
> For advanced usage, please [Engine Options](#engine-options).
|
|
92
|
-
|
|
93
|
-
### Evaluate
|
|
94
|
-
|
|
95
|
-
Evaluate comparison or logical expression as TRUE or FALSE result:
|
|
96
|
-
|
|
97
|
-
`engine.evaluate(`[Comparison Expression](#comparison-expressions) or [Logical Expression](#logical-expressions), [Evaluation Data Context](#evaluation-data-context)`)` => `boolean`
|
|
98
|
-
|
|
99
|
-
> Data context is optional.
|
|
100
|
-
|
|
101
|
-
**Example**
|
|
102
|
-
|
|
103
|
-
```js
|
|
104
|
-
// Comparison expression
|
|
105
|
-
engine.evaluate(['==', 5, 5])
|
|
106
|
-
engine.evaluate(['==', 'circle', 'circle'])
|
|
107
|
-
engine.evaluate(['==', true, true])
|
|
108
|
-
engine.evaluate(['==', '$name', 'peter'], { name: 'peter' })
|
|
109
|
-
engine.evaluate(['UNDEFINED', '$RefA'], {})
|
|
110
|
-
|
|
111
|
-
// Logical expression
|
|
112
|
-
engine.evaluate(['AND', ['==', 5, 5], ['==', 10, 10]])
|
|
113
|
-
engine.evaluate(['AND', ['==', 'circle', 'circle'], ['==', 10, 10]])
|
|
114
|
-
engine.evaluate(['OR', ['==', '$name', 'peter'], ['==', 5, 10]], {
|
|
115
|
-
name: 'peter',
|
|
116
|
-
})
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
### Statement
|
|
120
|
-
|
|
121
|
-
Get expression string representation:
|
|
122
|
-
|
|
123
|
-
`engine.statement(`[Comparison Expression](#comparison-expressions) or [Logical Expression](#logical-expressions)`)` => `string`
|
|
124
|
-
|
|
125
|
-
**Example**
|
|
126
|
-
|
|
127
|
-
```js
|
|
128
|
-
/* Comparison expression */
|
|
129
|
-
|
|
130
|
-
engine.statement(['==', 5, 5])
|
|
131
|
-
// (5 == 5)
|
|
132
|
-
|
|
133
|
-
engine.statement(['==', 'circle', 'circle'])
|
|
134
|
-
// ("circle" == "circle")
|
|
135
|
-
|
|
136
|
-
engine.statement(['==', true, true])
|
|
137
|
-
// (true == true)
|
|
138
|
-
|
|
139
|
-
engine.statement(['==', '$name', 'peter'], { name: 'peter' })
|
|
140
|
-
// ({name} == "peter")
|
|
141
|
-
|
|
142
|
-
engine.statement(['UNDEFINED', '$RefA'])
|
|
143
|
-
// ({RefA} is UNDEFINED)
|
|
144
|
-
|
|
145
|
-
/* Logical expression */
|
|
146
|
-
|
|
147
|
-
engine.statement(['AND', ['==', 5, 5], ['==', 10, 10]])
|
|
148
|
-
// ((5 == 5) AND (10 == 10))
|
|
149
|
-
|
|
150
|
-
engine.statement(['AND', ['==', 'circle', 'circle'], ['==', 10, 10]])
|
|
151
|
-
// (("circle" == "circle") AND (10 == 10))
|
|
152
|
-
|
|
153
|
-
engine.statement(['OR', ['==', '$name', 'peter'], ['==', 5, 10]], {
|
|
154
|
-
name: 'peter',
|
|
155
|
-
})
|
|
156
|
-
// (({name} == "peter") OR (5 == 10))
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### Parse
|
|
160
|
-
|
|
161
|
-
Parse the expression into a evaluable object, i.e. it returns the parsed self-evaluable condition expression.
|
|
162
|
-
|
|
163
|
-
`engine.parse(`[Comparison Expression](#comparison-expressions) or [Logical Expression](#logical-expressions)`)` => `evaluable`
|
|
164
|
-
|
|
165
|
-
#### Evaluate Function
|
|
166
|
-
|
|
167
|
-
- `evaluable.evaluate(context)` please see [Evaluation Data Context](#evaluation-data-context).
|
|
168
|
-
- `evaluable.toString()` please see [Statement](#statement).
|
|
169
|
-
|
|
170
|
-
**Example**
|
|
171
|
-
|
|
172
|
-
```js
|
|
173
|
-
let evaluable = engine.parse(['==', '$name', 'peter'])
|
|
174
|
-
|
|
175
|
-
evaluable.evaluate({ name: 'peter' }) // true
|
|
176
|
-
|
|
177
|
-
evaluable.toString()
|
|
178
|
-
// ({name} == "peter")
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
### Simplify
|
|
182
|
-
|
|
183
|
-
Simplifies an expression with a given context. This is useful when you already have some of
|
|
184
|
-
the properties of context and wants to try to evaluate the expression.
|
|
185
|
-
|
|
186
|
-
**Example**
|
|
187
|
-
|
|
188
|
-
```js
|
|
189
|
-
engine.simplify(['AND', ['==', '$a', 10], ['==', '$b', 20]], { a: 10 }) // ['==', '$b', 20]
|
|
190
|
-
|
|
191
|
-
engine.simplify(['AND', ['==', '$a', 10], ['==', '$b', 20]], { a: 20 }) // false
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
Values not found in the context will cause the parent operand not to be evaluated and returned
|
|
195
|
-
as part of the simplified expression.
|
|
196
|
-
|
|
197
|
-
In some situations we might want to evaluate the expression even if referred value is not
|
|
198
|
-
present. You can provide a list of keys that will be strictly evaluated even if they are not
|
|
199
|
-
present in the context.
|
|
200
|
-
|
|
201
|
-
**Example**
|
|
202
|
-
|
|
203
|
-
```js
|
|
204
|
-
engine.simplify(
|
|
205
|
-
['AND', ['==', '$a', 10], ['==', '$b', 20]],
|
|
206
|
-
{ a: 10 },
|
|
207
|
-
['b'] // '$b' will be evaluated to undefined.
|
|
208
|
-
) // false
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
Alternatively we might want to do the opposite and strictly evaluate the expression for all referred
|
|
212
|
-
values not present in the context except for a specified list of optional keys.
|
|
213
|
-
|
|
214
|
-
**Example**
|
|
215
|
-
|
|
216
|
-
```js
|
|
217
|
-
engine.simplify(
|
|
218
|
-
['OR', ['==', '$a', 10], ['==', '$b', 20], ['==', '$c', 20]],
|
|
219
|
-
{ c: 10 },
|
|
220
|
-
undefined,
|
|
221
|
-
['b'] // except for '$b' everything not in context will be evaluated to undefined.
|
|
222
|
-
) // ['==', '$b', 20]
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
## Working with Expressions
|
|
226
|
-
|
|
227
|
-
### Evaluation Data Context
|
|
228
|
-
|
|
229
|
-
The evaluation data context is used to provide the expression with variable references, i.e. this allows for the dynamic expressions. The data context is object with properties used as the references keys, and its values as reference values.
|
|
230
|
-
|
|
231
|
-
> Valid reference values: object, string, number, boolean, string[], number[].
|
|
232
|
-
|
|
233
|
-
To reference the nested reference, please use "." delimiter, e.g.:
|
|
234
|
-
`$address.city`
|
|
235
|
-
|
|
236
|
-
If the key of the nested reference includes the "." delimiter, please wrap the whole key with backticks `` ` ``, e.g.:
|
|
237
|
-
`` $address.`city.code` `` can reference the object
|
|
238
|
-
|
|
239
|
-
```javascript
|
|
240
|
-
{
|
|
241
|
-
address: {
|
|
242
|
-
'city.code': 'TOR'
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
``$address.`city.code`[0]`` can reference the object
|
|
248
|
-
|
|
249
|
-
```javascript
|
|
250
|
-
{
|
|
251
|
-
address: {
|
|
252
|
-
'city.code': ['TOR']
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
when the value of the nested reference is an array.
|
|
258
|
-
|
|
259
|
-
#### Accessing Array Element:
|
|
260
|
-
|
|
261
|
-
`$options[1]`
|
|
262
|
-
|
|
263
|
-
#### Accessing Array Element via Reference:
|
|
264
|
-
|
|
265
|
-
`$options[{index}]`
|
|
266
|
-
|
|
267
|
-
- The **index** reference is resolved within the data context as an array index.
|
|
268
|
-
|
|
269
|
-
#### Nested Referencing
|
|
270
|
-
|
|
271
|
-
`$address.{segment}`
|
|
272
|
-
|
|
273
|
-
- The **segment** reference is resolved within the data context as a property key.
|
|
274
|
-
|
|
275
|
-
#### Composite Reference Key
|
|
276
|
-
|
|
277
|
-
`$shape{shapeType}`
|
|
278
|
-
|
|
279
|
-
- The **shapeType** reference is resolved within the data context, and inserted into the outer reference key.
|
|
280
|
-
- E.g. **shapeType** is resolved as "**B**" and would compose the **$shapeB** outer reference.
|
|
281
|
-
- This resolution could be n-nested.
|
|
282
|
-
|
|
283
|
-
#### Data Type Casting
|
|
284
|
-
|
|
285
|
-
`$payment.amount.(Type)`
|
|
286
|
-
|
|
287
|
-
Cast the given data context into the desired data type before being used as an operand in the evaluation.
|
|
288
|
-
|
|
289
|
-
> Note: If the conversion is invalid, then a warning message is being logged.
|
|
290
|
-
|
|
291
|
-
Supported data type conversions:
|
|
292
|
-
|
|
293
|
-
- .(String): cast a given reference to String.
|
|
294
|
-
- .(Number): cast a given reference to Number.
|
|
295
|
-
|
|
296
|
-
**Example**
|
|
297
|
-
|
|
298
|
-
```js
|
|
299
45
|
// Data context
|
|
300
46
|
const ctx = {
|
|
301
47
|
name: 'peter',
|
|
302
|
-
country: 'canada',
|
|
303
48
|
age: 21,
|
|
304
|
-
options: [1, 2, 3],
|
|
305
49
|
address: {
|
|
306
50
|
city: 'Toronto',
|
|
307
51
|
country: 'Canada',
|
|
308
52
|
},
|
|
309
|
-
index: 2,
|
|
310
|
-
segment: 'city',
|
|
311
|
-
shapeA: 'box',
|
|
312
|
-
shapeB: 'circle',
|
|
313
|
-
shapeType: 'B',
|
|
314
53
|
}
|
|
315
54
|
|
|
316
55
|
// Evaluate an expression in the given data context
|
|
317
56
|
engine.evaluate(['>', '$age', 20], ctx) // true
|
|
318
57
|
|
|
319
|
-
//
|
|
58
|
+
// Accessing a property
|
|
320
59
|
engine.evaluate(['==', '$address.city', 'Toronto'], ctx) // true
|
|
321
60
|
|
|
322
|
-
// Accessing Array Element
|
|
323
|
-
engine.evaluate(['==', '$options[1]', 2], ctx) // true
|
|
324
|
-
|
|
325
|
-
// Accessing Array Element via Reference
|
|
326
|
-
engine.evaluate(['==', '$options[{index}]', 3], ctx) // true
|
|
327
|
-
|
|
328
|
-
// Nested Referencing
|
|
329
|
-
engine.evaluate(['==', '$address.{segment}', 'Toronto'], ctx) // true
|
|
330
|
-
|
|
331
|
-
// Composite Reference Key
|
|
332
|
-
engine.evaluate(['==', '$shape{shapeType}', 'circle'], ctx) // true
|
|
333
|
-
|
|
334
61
|
// Data Type Casting
|
|
335
62
|
engine.evaluate(['==', '$age.(String)', '21'], ctx) // true
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
### Operand Types
|
|
339
|
-
|
|
340
|
-
The [Comparison Expression](#comparison-expression) expect operands to be one of the below:
|
|
341
|
-
|
|
342
|
-
#### Value
|
|
343
|
-
|
|
344
|
-
Simple value types: string, number, boolean.
|
|
345
|
-
|
|
346
|
-
**Example**
|
|
347
|
-
|
|
348
|
-
```js
|
|
349
|
-
;['==', 5, 5][('==', 'circle', 'circle')][('==', true, true)]
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
#### Reference
|
|
353
|
-
|
|
354
|
-
The reference operand value is resolved from the [Evaluation Data Context](#evaluation-data-context), where the the operands name is used as key in the context.
|
|
355
|
-
|
|
356
|
-
The reference operand must be prefixed with `$` symbol, e.g.: `$name`. This might be customized via [Reference Predicate Parser Option](#reference-predicate).
|
|
357
|
-
|
|
358
|
-
**Example**
|
|
359
|
-
|
|
360
|
-
| Expression | Data Context |
|
|
361
|
-
| ----------------------------- | ------------------------------------- |
|
|
362
|
-
| `['==', '$age', 21]` | `{age: 21}` |
|
|
363
|
-
| `['==', 'circle', '$shape'] ` | `{shape: 'circle'}` |
|
|
364
|
-
| `['==', '$visible', true]` | `{visible: true}` |
|
|
365
|
-
| `['==', '$circle', '$shape']` | `{circle: 'circle', shape: 'circle'}` |
|
|
366
|
-
|
|
367
|
-
#### Collection
|
|
368
|
-
|
|
369
|
-
The operand could be an array mixed from [Value](#value) and [Reference](#reference).
|
|
370
|
-
|
|
371
|
-
**Example**
|
|
372
|
-
|
|
373
|
-
| Expression | Data Context |
|
|
374
|
-
| ---------------------------------------- | ----------------------------------- |
|
|
375
|
-
| `['IN', [1, 2], 1]` | `{}` |
|
|
376
|
-
| `['IN', 'circle', ['$shapeA', $shapeB] ` | `{shapeA: 'circle', shapeB: 'box'}` |
|
|
377
|
-
| `['IN', [$number, 5], 5]` | `{number: 3}` |
|
|
378
|
-
|
|
379
|
-
### Comparison Expressions
|
|
380
|
-
|
|
381
|
-
#### Equal
|
|
382
|
-
|
|
383
|
-
Expression format: `["==", `[Left Operand](#operand-types), [Right Operand](#operand-types)`]`.
|
|
384
|
-
|
|
385
|
-
> Valid operand types: string, number, boolean.
|
|
386
|
-
|
|
387
|
-
```json
|
|
388
|
-
["==", 5, 5]
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
```js
|
|
392
|
-
engine.evaluate(['==', 5, 5]) // true
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
#### Not Equal
|
|
396
|
-
|
|
397
|
-
Expression format: `["!=", `[Left Operand](#operand-types), [Right Operand](#operand-types)`]`.
|
|
398
|
-
|
|
399
|
-
> Valid operand types: string, number, boolean.
|
|
400
|
-
|
|
401
|
-
```json
|
|
402
|
-
["!=", "circle", "square"]
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
```js
|
|
406
|
-
engine.evaluate(['!=', 'circle', 'square']) // true
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
#### Greater Than
|
|
410
|
-
|
|
411
|
-
Expression format: `[">", `[Left Operand](#operand-types), [Right Operand](#operand-types)`]`.
|
|
412
|
-
|
|
413
|
-
> Valid operand types: number, string.
|
|
414
|
-
|
|
415
|
-
- String comparison only supports ISO-8601 formatted dates.
|
|
416
|
-
|
|
417
|
-
```json
|
|
418
|
-
[">", 10, 5]
|
|
419
|
-
[">", "2023-01-01", "2022-12-31"]
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
```js
|
|
423
|
-
engine.evaluate(['>', 10, 5]) // true
|
|
424
|
-
engine.evaluate(['>', '2023-01-01', '2022-12-31']) // true
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
#### Greater Than or Equal
|
|
428
|
-
|
|
429
|
-
Expression format: `[">=", `[Left Operand](#operand-types), [Right Operand](#operand-types)`]`.
|
|
430
|
-
|
|
431
|
-
> Valid operand types: number, string.
|
|
432
|
-
|
|
433
|
-
- String comparison only supports ISO-8601 formatted dates.
|
|
434
|
-
|
|
435
|
-
```json
|
|
436
|
-
[">=", 5, 5]
|
|
437
|
-
[">=", "2023-01-01", "2023-01-01"]
|
|
438
|
-
```
|
|
439
|
-
|
|
440
|
-
```js
|
|
441
|
-
engine.evaluate(['>=', 5, 5]) // true
|
|
442
|
-
engine.evaluate(['>=', '2023-01-01', '2023-01-01']) // true
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
#### Less Than
|
|
446
|
-
|
|
447
|
-
Expression format: `["<", `[Left Operand](#operand-types), [Right Operand](#operand-types)`]`.
|
|
448
|
-
|
|
449
|
-
> Valid operand types: number, string.
|
|
450
|
-
|
|
451
|
-
- String comparison only supports ISO-8601 formatted dates.
|
|
452
|
-
|
|
453
|
-
```json
|
|
454
|
-
["<", 5, 10]
|
|
455
|
-
["<", "2022-12-31", "2023-01-01"]
|
|
456
|
-
```
|
|
457
|
-
|
|
458
|
-
```js
|
|
459
|
-
engine.evaluate(['<', 5, 10]) // true
|
|
460
|
-
engine.evaluate(['<', '2022-12-31', '2023-01-01']) // true
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
#### Less Than or Equal
|
|
464
|
-
|
|
465
|
-
Expression format: `["<=", `[Left Operand](#operand-types), [Right Operand](#operand-types)`]`.
|
|
466
|
-
|
|
467
|
-
> Valid operand types: number, string.
|
|
468
|
-
|
|
469
|
-
- String comparison only supports ISO-8601 formatted dates.
|
|
470
|
-
|
|
471
|
-
```json
|
|
472
|
-
["<=", 5, 5]
|
|
473
|
-
["<=", "2023-01-01", "2023-01-01"]
|
|
474
|
-
```
|
|
475
|
-
|
|
476
|
-
```js
|
|
477
|
-
engine.evaluate(['<=', 5, 5]) // true
|
|
478
|
-
engine.evaluate(['<=', '2023-01-01', '2023-01-01']) // true
|
|
479
|
-
```
|
|
480
|
-
|
|
481
|
-
#### In
|
|
482
|
-
|
|
483
|
-
Expression format: `["IN", `[Left Operand](#operand-types), [Right Operand](#operand-types)`]`.
|
|
484
|
-
|
|
485
|
-
> Valid operand types: number and number[] or string and string[].
|
|
486
|
-
|
|
487
|
-
```json
|
|
488
|
-
["IN", 5, [1,2,3,4,5]]
|
|
489
|
-
["IN", ["circle", "square", "triangle"], "square"]
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
```js
|
|
493
|
-
engine.evaluate(['IN', 5, [1, 2, 3, 4, 5]]) // true
|
|
494
|
-
engine.evaluate(['IN', ['circle', 'square', 'triangle'], 'square']) // true
|
|
495
|
-
```
|
|
496
|
-
|
|
497
|
-
#### Not In
|
|
498
|
-
|
|
499
|
-
Expression format: `["NOT IN", `[Left Operand](#operand-types), [Right Operand](#operand-types)`]`.
|
|
500
|
-
|
|
501
|
-
> Valid operand types: number and number[] or string and string[].
|
|
502
|
-
|
|
503
|
-
```json
|
|
504
|
-
["IN", 10, [1,2,3,4,5]]
|
|
505
|
-
["IN", ["circle", "square", "triangle"], "oval"]
|
|
506
|
-
```
|
|
507
|
-
|
|
508
|
-
```js
|
|
509
|
-
engine.evaluate(['NOT IN', 10, [1, 2, 3, 4, 5]]) // true
|
|
510
|
-
engine.evaluate(['NOT IN', ['circle', 'square', 'triangle'], 'oval']) // true
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
#### Prefix
|
|
514
|
-
|
|
515
|
-
Expression format: `["PREFIX", `[Left Operand](#operand-types), [Right Operand](#operand-types)`]`.
|
|
516
|
-
|
|
517
|
-
> Valid operand types: string.
|
|
518
|
-
|
|
519
|
-
- Left operand is the PREFIX term.
|
|
520
|
-
- Right operand is the tested word.
|
|
521
|
-
|
|
522
|
-
```json
|
|
523
|
-
["PREFIX", "hemi", "hemisphere"]
|
|
524
|
-
```
|
|
525
|
-
|
|
526
|
-
```js
|
|
527
|
-
engine.evaluate(['PREFIX', 'hemi', 'hemisphere']) // true
|
|
528
|
-
engine.evaluate(['PREFIX', 'hemi', 'sphere']) // false
|
|
529
|
-
```
|
|
530
|
-
|
|
531
|
-
#### Suffix
|
|
532
|
-
|
|
533
|
-
Expression format: `["SUFFIX", `[Left Operand](#operand-types), [Right Operand](#operand-types)`]`.
|
|
534
|
-
|
|
535
|
-
> Valid operand types: string.
|
|
536
|
-
|
|
537
|
-
- Left operand is the tested word.
|
|
538
|
-
- Right operand is the SUFFIX term.
|
|
539
|
-
|
|
540
|
-
```json
|
|
541
|
-
["SUFFIX", "establishment", "ment"]
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
```js
|
|
545
|
-
engine.evaluate(['SUFFIX', 'establishment', 'ment']) // true
|
|
546
|
-
engine.evaluate(['SUFFIX', 'establish', 'ment']) // false
|
|
547
|
-
```
|
|
548
|
-
|
|
549
|
-
#### Overlap
|
|
550
|
-
|
|
551
|
-
Expression format: `["OVERLAP", `[Left Operand](#operand-types), [Right Operand](#operand-types)`]`.
|
|
552
|
-
|
|
553
|
-
> Valid operand types number[] or string[].
|
|
554
|
-
|
|
555
|
-
```json
|
|
556
|
-
["OVERLAP", [1, 2], [1, 2, 3, 4, 5]]
|
|
557
|
-
["OVERLAP", ["circle", "square", "triangle"], ["square"]]
|
|
558
|
-
```
|
|
559
|
-
|
|
560
|
-
```js
|
|
561
|
-
engine.evaluate(['OVERLAP', [1, 2, 6], [1, 2, 3, 4, 5]]) // true
|
|
562
|
-
engine.evaluate([
|
|
563
|
-
'OVERLAP',
|
|
564
|
-
['circle', 'square', 'triangle'],
|
|
565
|
-
['square', 'oval'],
|
|
566
|
-
]) // true
|
|
567
|
-
```
|
|
568
|
-
|
|
569
|
-
#### Undefined
|
|
570
|
-
|
|
571
|
-
Expression format: `["UNDEFINED", `[Reference Operand](#reference)`]`.
|
|
572
|
-
|
|
573
|
-
```json
|
|
574
|
-
["UNDEFINED", "$RefA"]
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
```js
|
|
578
|
-
engine.evaluate(['UNDEFINED', 'RefA'], {}) // true
|
|
579
|
-
engine.evaluate(['UNDEFINED', 'RefA'], { RefA: undefined }) // true
|
|
580
|
-
engine.evaluate(['UNDEFINED', 'RefA'], { RefA: 10 }) // false
|
|
581
|
-
```
|
|
582
|
-
|
|
583
|
-
#### Present
|
|
584
|
-
|
|
585
|
-
Evaluates as FALSE when the operand is UNDEFINED or NULL.
|
|
586
|
-
|
|
587
|
-
Expression format: `["PRESENT", `[Reference Operand](#reference)`]`.
|
|
588
|
-
|
|
589
|
-
```json
|
|
590
|
-
["PRESENT", "$RefA"]
|
|
591
|
-
```
|
|
592
|
-
|
|
593
|
-
```js
|
|
594
|
-
engine.evaluate(['PRESENT', 'RefA'], {}) // false
|
|
595
|
-
engine.evaluate(['PRESENT', 'RefA'], { RefA: undefined }) // false
|
|
596
|
-
engine.evaluate(['PRESENT', 'RefA'], { RefA: null }) // false
|
|
597
|
-
engine.evaluate(['PRESENT', 'RefA'], { RefA: 10 }) // true
|
|
598
|
-
engine.evaluate(['PRESENT', 'RefA'], { RefA: false }) // true
|
|
599
|
-
engine.evaluate(['PRESENT', 'RefA'], { RefA: 0 }) // true
|
|
600
|
-
```
|
|
601
|
-
|
|
602
|
-
### Logical Expressions
|
|
603
|
-
|
|
604
|
-
#### And
|
|
605
|
-
|
|
606
|
-
The logical AND operator (&&) returns the boolean value TRUE if both operands are TRUE and returns FALSE otherwise.
|
|
607
|
-
|
|
608
|
-
Expression format: `["AND", Left Operand 1, Right Operand 2, ... , Right Operand N]`.
|
|
609
|
-
|
|
610
|
-
> Valid operand types: [Comparison Expression](#comparison-expressions) or [Nested Logical Expression](#logical-expressions).
|
|
611
|
-
|
|
612
|
-
```json
|
|
613
|
-
["AND", ["==", 5, 5], ["==", 10, 10]]
|
|
614
|
-
```
|
|
615
|
-
|
|
616
|
-
```js
|
|
617
|
-
engine.evaluate(['AND', ['==', 5, 5], ['==', 10, 10]]) // true
|
|
618
|
-
```
|
|
619
|
-
|
|
620
|
-
#### Or
|
|
621
|
-
|
|
622
|
-
The logical OR operator (||) returns the boolean value TRUE if either or both operands is TRUE and returns FALSE otherwise.
|
|
623
|
-
|
|
624
|
-
Expression format: `["OR", Left Operand 1, Right Operand 2, ... , Right Operand N]`.
|
|
625
|
-
|
|
626
|
-
> Valid operand types: [Comparison Expression](#comparison-expressions) or [Nested Logical Expression](#logical-expressions).
|
|
627
|
-
|
|
628
|
-
```json
|
|
629
|
-
["OR", ["==", 5, 5], ["==", 10, 5]]
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
```js
|
|
633
|
-
engine.evaluate(['OR', ['==', 5, 5], ['==', 10, 5]]) // true
|
|
634
|
-
```
|
|
635
|
-
|
|
636
|
-
#### Nor
|
|
637
|
-
|
|
638
|
-
The logical NOR operator returns the boolean value TRUE if both operands are FALSE and returns FALSE otherwise.
|
|
639
|
-
|
|
640
|
-
Expression format: `["NOR", Left Operand 1, Right Operand 2, ... , Right Operand N]`
|
|
641
|
-
|
|
642
|
-
> Valid operand types: [Comparison Expression](#comparison-expressions) or [Nested Logical Expression](#logical-expressions).
|
|
643
|
-
|
|
644
|
-
```json
|
|
645
|
-
["NOR", ["==", 5, 1], ["==", 10, 5]]
|
|
646
|
-
```
|
|
647
|
-
|
|
648
|
-
```js
|
|
649
|
-
engine.evaluate(['NOR', ['==', 5, 1], ['==', 10, 5]]) // true
|
|
650
|
-
```
|
|
651
|
-
|
|
652
|
-
#### Xor
|
|
653
63
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
Expression format: `["XOR", Left Operand 1, Right Operand 2, ... , Right Operand N]`
|
|
657
|
-
|
|
658
|
-
> Valid operand types: [Comparison Expression](#comparison-expressions) or [Nested Logical Expression](#logical-expressions).
|
|
659
|
-
|
|
660
|
-
```json
|
|
661
|
-
["XOR", ["==", 5, 5], ["==", 10, 5]]
|
|
662
|
-
```
|
|
663
|
-
|
|
664
|
-
```js
|
|
665
|
-
engine.evaluate(['XOR', ['==', 5, 5], ['==', 10, 5]]) // true
|
|
666
|
-
```
|
|
667
|
-
|
|
668
|
-
```json
|
|
669
|
-
["XOR", ["==", 5, 5], ["==", 10, 10]]
|
|
670
|
-
```
|
|
671
|
-
|
|
672
|
-
```js
|
|
673
|
-
engine.evaluate(['XOR', ['==', 5, 5], ['==', 10, 10]]) // false
|
|
674
|
-
```
|
|
675
|
-
|
|
676
|
-
#### Not
|
|
677
|
-
|
|
678
|
-
The logical NOT operator returns the boolean value TRUE if the operand is FALSE, TRUE otherwise.
|
|
679
|
-
|
|
680
|
-
Expression format: `["NOT", Operand]`
|
|
681
|
-
|
|
682
|
-
> Valid operand types: [Comparison Expression](#comparison-expressions) or [Nested Logical Expression](#logical-expressions).
|
|
683
|
-
|
|
684
|
-
```json
|
|
685
|
-
["NOT", ["==", 5, 5]]
|
|
686
|
-
```
|
|
687
|
-
|
|
688
|
-
```js
|
|
689
|
-
engine.evaluate(['NOT', ['==', 5, 5]]) // true
|
|
690
|
-
```
|
|
691
|
-
|
|
692
|
-
### Arithmetic Expressions
|
|
693
|
-
|
|
694
|
-
Arithmetic Expressions are not supported as root level expressions since they must evaluate to a boolean. But it can be used nested within [Comparisson Expressions](#comparison-expressions).
|
|
695
|
-
|
|
696
|
-
#### Division
|
|
697
|
-
|
|
698
|
-
The arithmetical operator for division produces the quotient of its operands where the left-most operand is the dividend and the subsequent one is the divisor, done from left to right.
|
|
699
|
-
|
|
700
|
-
Expression format: `["/", First Operand, Second Operand, ... , Nth Operand]`.
|
|
701
|
-
|
|
702
|
-
> Valid operand types: [Arithmetic Expressions](#arithmetic-expressions) or [Operands](#operand-types).
|
|
703
|
-
|
|
704
|
-
```json
|
|
705
|
-
["==", ["/", 100, 10], 10]
|
|
706
|
-
```
|
|
707
|
-
|
|
708
|
-
```js
|
|
709
|
-
engine.evaluate(['==', ['/', 100, 10], 10]) // true
|
|
710
|
-
```
|
|
711
|
-
|
|
712
|
-
#### Multiplication
|
|
713
|
-
|
|
714
|
-
The arithmetical operator for multiplication produces the product of the operands.
|
|
715
|
-
|
|
716
|
-
Expression format: `["*", First Operand, Second Operand, ... , Nth Operand]`.
|
|
717
|
-
|
|
718
|
-
> Valid operand types: [Arithmetic Expressions](#arithmetic-expressions) or [Operands](#operand-types).
|
|
719
|
-
|
|
720
|
-
```json
|
|
721
|
-
["==", ["*", 100, 10], 10]
|
|
722
|
-
```
|
|
723
|
-
|
|
724
|
-
```js
|
|
725
|
-
engine.evaluate(['==', ['*', 10, 10], 100]) // true
|
|
726
|
-
```
|
|
727
|
-
|
|
728
|
-
#### Subtraction
|
|
729
|
-
|
|
730
|
-
The arithmetical operator for subtraction subtracts the operands, producing their difference.
|
|
731
|
-
|
|
732
|
-
Expression format: `["-", First Operand, Second Operand, ... , Nth Operand]`.
|
|
733
|
-
|
|
734
|
-
> Valid operand types: [Arithmetic Expressions](#arithmetic-expressions) or [Operands](#operand-types).
|
|
735
|
-
|
|
736
|
-
```json
|
|
737
|
-
["==", ["-", 20, 10], 10]
|
|
738
|
-
```
|
|
739
|
-
|
|
740
|
-
```js
|
|
741
|
-
engine.evaluate(['==', ['-', 20, 10], 10]) // true
|
|
742
|
-
```
|
|
743
|
-
|
|
744
|
-
#### Addition
|
|
745
|
-
|
|
746
|
-
The arithmetical operator for addition produces the sum of the operands.
|
|
747
|
-
|
|
748
|
-
Expression format: `["+", First Operand, Second Operand, ... , Nth Operand]`.
|
|
749
|
-
|
|
750
|
-
> Valid operand types: [Arithmetic Expressions](#arithmetic-expressions) or [Operands](#operand-types).
|
|
751
|
-
|
|
752
|
-
```json
|
|
753
|
-
["==", ["+", 5, 5], 10]
|
|
64
|
+
// Evaluate a logical expression
|
|
65
|
+
engine.evaluate(['AND', ['>', '$age', 20], ['==', '$name', 'peter']]) // true
|
|
754
66
|
```
|
|
755
67
|
|
|
756
|
-
|
|
757
|
-
engine.evaluate(['==', ['+', 5, 5], 10]) // true
|
|
758
|
-
```
|
|
68
|
+
## 🖼️ Resources
|
|
759
69
|
|
|
760
|
-
|
|
70
|
+
Understand supported expressions:
|
|
761
71
|
|
|
762
|
-
|
|
72
|
+
- [Comparison Expressions](./specs/comparison-expressions.md)
|
|
73
|
+
- [Logical Expressions](./specs/logical-expressions.md)
|
|
74
|
+
- [Arithmetic Expressions](./specs/arithmetic-expressions.md)
|
|
75
|
+
- [Evaluation Data Context](./specs/evaluation-data-context.md)
|
|
76
|
+
- [Operand Types](./specs/operand-types.md)
|
|
763
77
|
|
|
764
|
-
|
|
78
|
+
Learn about usages:
|
|
765
79
|
|
|
766
|
-
|
|
80
|
+
- [Evaluate](./specs/evaluate.md)
|
|
81
|
+
- [Statement](./specs/statement.md)
|
|
82
|
+
- [Parse](./specs/parse.md)
|
|
83
|
+
- [Simplify](./specs/simplify.md)
|
|
767
84
|
|
|
768
|
-
|
|
769
|
-
// Import the illogical engine
|
|
770
|
-
import Engine from '@briza/illogical'
|
|
771
|
-
|
|
772
|
-
// Create a new instance of the engine
|
|
773
|
-
const opts = {
|
|
774
|
-
referencePredicate: (operand) => operand.startsWith('$'),
|
|
775
|
-
}
|
|
776
|
-
const engine = new Engine(opts)
|
|
777
|
-
```
|
|
778
|
-
|
|
779
|
-
#### Reference Predicate
|
|
780
|
-
|
|
781
|
-
A function used to determine if the operand is a reference type, otherwise evaluated as a static value.
|
|
782
|
-
|
|
783
|
-
```typescript
|
|
784
|
-
referencePredicate: (operand: string) => boolean
|
|
785
|
-
```
|
|
85
|
+
Customize the engine and the documentation:
|
|
786
86
|
|
|
787
|
-
|
|
87
|
+
- [Engine Options](./specs/engine.md)
|
|
88
|
+
- [Code Documentation](https://briza-insurance.github.io/illogical/index.html)
|
|
788
89
|
|
|
789
|
-
|
|
790
|
-
- `false` = value type
|
|
90
|
+
Bytecode evaluation and debugging tools:
|
|
791
91
|
|
|
792
|
-
|
|
92
|
+
- [Bytecode Evaluator](./specs/bytecode-evaluator.md)
|
|
93
|
+
- [Debugger Tools](./specs/debugger-tools.md)
|
|
793
94
|
|
|
794
|
-
|
|
95
|
+
## 📖 Changelog
|
|
795
96
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
A function used to transform the operand into the reference annotation stripped form. I.e. remove any annotation used to detect the reference type. E.g. "$Reference" => "Reference".
|
|
799
|
-
|
|
800
|
-
```typescript
|
|
801
|
-
referenceTransform: (operand: string) => string
|
|
802
|
-
```
|
|
803
|
-
|
|
804
|
-
> **Default reference transform:**
|
|
805
|
-
> It removes the `$` symbol at the begging of the operand name.
|
|
806
|
-
|
|
807
|
-
#### Operator Mapping
|
|
808
|
-
|
|
809
|
-
Mapping of the operators. The key is unique operator key, and the value is the key used to represent the given operator in the raw expression.
|
|
810
|
-
|
|
811
|
-
```typescript
|
|
812
|
-
operatorMapping: Map<symbol, string>
|
|
813
|
-
```
|
|
814
|
-
|
|
815
|
-
**Default operator mapping:**
|
|
816
|
-
|
|
817
|
-
```typescript
|
|
818
|
-
// Comparison
|
|
819
|
-
[OPERATOR_EQ, '=='],
|
|
820
|
-
[OPERATOR_NE, '!='],
|
|
821
|
-
[OPERATOR_GT, '>'],
|
|
822
|
-
[OPERATOR_GE, '>='],
|
|
823
|
-
[OPERATOR_LT, '<'],
|
|
824
|
-
[OPERATOR_LE, '<='],
|
|
825
|
-
[OPERATOR_IN, 'IN'],
|
|
826
|
-
[OPERATOR_NOT_IN, 'NOT IN'],
|
|
827
|
-
[OPERATOR_PREFIX, 'PREFIX'],
|
|
828
|
-
[OPERATOR_SUFFIX, 'SUFFIX'],
|
|
829
|
-
[OPERATOR_OVERLAP, 'OVERLAP'],
|
|
830
|
-
[OPERATOR_UNDEFINED, 'UNDEFINED'],
|
|
831
|
-
[OPERATOR_PRESENT, 'PRESENT'],
|
|
832
|
-
// Logical
|
|
833
|
-
[OPERATOR_AND, 'AND'],
|
|
834
|
-
[OPERATOR_OR, 'OR'],
|
|
835
|
-
[OPERATOR_NOR, 'NOR'],
|
|
836
|
-
[OPERATOR_XOR, 'XOR'],
|
|
837
|
-
[OPERATOR_NOT, 'NOT'],
|
|
838
|
-
// Arithmetic
|
|
839
|
-
[OPERATOR_SUM, '+'],
|
|
840
|
-
[OPERATOR_SUBTRACT, '-'],
|
|
841
|
-
[OPERATOR_MULTIPLY, '*'],
|
|
842
|
-
[OPERATOR_DIVIDE, '/'],
|
|
843
|
-
```
|
|
844
|
-
|
|
845
|
-
> The operator keys are unique symbols which could be imported from the engine package:
|
|
846
|
-
|
|
847
|
-
```js
|
|
848
|
-
import {
|
|
849
|
-
OPERATOR_EQ,
|
|
850
|
-
OPERATOR_NE,
|
|
851
|
-
OPERATOR_GT,
|
|
852
|
-
OPERATOR_GE,
|
|
853
|
-
OPERATOR_LT,
|
|
854
|
-
OPERATOR_LE,
|
|
855
|
-
OPERATOR_IN,
|
|
856
|
-
OPERATOR_NOT_IN,
|
|
857
|
-
OPERATOR_PREFIX,
|
|
858
|
-
OPERATOR_SUFFIX,
|
|
859
|
-
OPERATOR_OVERLAP,
|
|
860
|
-
OPERATOR_UNDEFINED,
|
|
861
|
-
OPERATOR_PRESENT,
|
|
862
|
-
OPERATOR_AND,
|
|
863
|
-
OPERATOR_OR,
|
|
864
|
-
OPERATOR_NOR,
|
|
865
|
-
OPERATOR_XOR,
|
|
866
|
-
OPERATOR_NOT,
|
|
867
|
-
OPERATOR_DIVIDE,
|
|
868
|
-
OPERATOR_MULTIPLY,
|
|
869
|
-
OPERATOR_SUBTRACT,
|
|
870
|
-
OPERATOR_SUM,
|
|
871
|
-
} from '@briza/illogical'
|
|
872
|
-
```
|
|
873
|
-
|
|
874
|
-
## Breaking Changes
|
|
875
|
-
|
|
876
|
-
### v1.4.2
|
|
877
|
-
|
|
878
|
-
- Change on `@babel/env` preset to target `> 1%, node 12` this will remove some polyfills that were causing performance
|
|
879
|
-
problems in some projects.
|
|
880
|
-
|
|
881
|
-
### v1.2.0
|
|
882
|
-
|
|
883
|
-
- Removed **strict** mode from the Engine constructor options.
|
|
884
|
-
`const engine = new Engine(strictMode, opts);` -> `const engine = new Engine(opts);`
|
|
885
|
-
|
|
886
|
-
---
|
|
97
|
+
See [changelog.md](./changelog.md).
|
|
887
98
|
|
|
888
|
-
## Contributing
|
|
99
|
+
## 🤝 Contributing
|
|
889
100
|
|
|
890
|
-
See [contributing.md](
|
|
101
|
+
See [contributing.md](./contributing.md).
|
|
891
102
|
|
|
892
|
-
## License
|
|
103
|
+
## 📜 License
|
|
893
104
|
|
|
894
|
-
Illogical is released under the MIT license. See [license.txt](
|
|
105
|
+
Illogical is released under the MIT license. See [license.txt](./license.txt) for details.
|