@briza/illogical 2.0.0 → 2.0.2

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.
Files changed (41) hide show
  1. package/lib/illogical.cjs +6 -8
  2. package/lib/illogical.esm.js +6 -8
  3. package/package.json +21 -14
  4. package/readme.md +54 -848
  5. /package/{lib/types → types}/common/evaluable.d.ts +0 -0
  6. /package/{lib/types → types}/common/type-check.d.ts +0 -0
  7. /package/{lib/types → types}/common/util.d.ts +0 -0
  8. /package/{lib/types → types}/expression/arithmetic/divide.d.ts +0 -0
  9. /package/{lib/types → types}/expression/arithmetic/index.d.ts +0 -0
  10. /package/{lib/types → types}/expression/arithmetic/isSimplifiedArithmeticExpression.d.ts +0 -0
  11. /package/{lib/types → types}/expression/arithmetic/multiply.d.ts +0 -0
  12. /package/{lib/types → types}/expression/arithmetic/operateWithExpectedDecimals.d.ts +0 -0
  13. /package/{lib/types → types}/expression/arithmetic/subtract.d.ts +0 -0
  14. /package/{lib/types → types}/expression/arithmetic/sum.d.ts +0 -0
  15. /package/{lib/types → types}/expression/comparison/eq.d.ts +0 -0
  16. /package/{lib/types → types}/expression/comparison/ge.d.ts +0 -0
  17. /package/{lib/types → types}/expression/comparison/gt.d.ts +0 -0
  18. /package/{lib/types → types}/expression/comparison/in.d.ts +0 -0
  19. /package/{lib/types → types}/expression/comparison/index.d.ts +0 -0
  20. /package/{lib/types → types}/expression/comparison/le.d.ts +0 -0
  21. /package/{lib/types → types}/expression/comparison/lt.d.ts +0 -0
  22. /package/{lib/types → types}/expression/comparison/ne.d.ts +0 -0
  23. /package/{lib/types → types}/expression/comparison/not-in.d.ts +0 -0
  24. /package/{lib/types → types}/expression/comparison/overlap.d.ts +0 -0
  25. /package/{lib/types → types}/expression/comparison/prefix.d.ts +0 -0
  26. /package/{lib/types → types}/expression/comparison/present.d.ts +0 -0
  27. /package/{lib/types → types}/expression/comparison/suffix.d.ts +0 -0
  28. /package/{lib/types → types}/expression/comparison/undefined.d.ts +0 -0
  29. /package/{lib/types → types}/expression/logical/and.d.ts +0 -0
  30. /package/{lib/types → types}/expression/logical/index.d.ts +0 -0
  31. /package/{lib/types → types}/expression/logical/nor.d.ts +0 -0
  32. /package/{lib/types → types}/expression/logical/not.d.ts +0 -0
  33. /package/{lib/types → types}/expression/logical/or.d.ts +0 -0
  34. /package/{lib/types → types}/expression/logical/xor.d.ts +0 -0
  35. /package/{lib/types → types}/index.d.ts +0 -0
  36. /package/{lib/types → types}/operand/collection.d.ts +0 -0
  37. /package/{lib/types → types}/operand/index.d.ts +0 -0
  38. /package/{lib/types → types}/operand/reference.d.ts +0 -0
  39. /package/{lib/types → types}/operand/value.d.ts +0 -0
  40. /package/{lib/types → types}/parser/index.d.ts +0 -0
  41. /package/{lib/types → types}/parser/options.d.ts +0 -0
package/lib/illogical.cjs CHANGED
@@ -56,7 +56,7 @@ function isObject(value) {
56
56
  if (value === null || value === undefined) {
57
57
  return false;
58
58
  }
59
- if (typeof value !== 'object' || (value === null || value === void 0 ? void 0 : value.constructor) !== Object) {
59
+ if (typeof value !== 'object' || value?.constructor !== Object) {
60
60
  return false;
61
61
  }
62
62
  return true;
@@ -364,7 +364,7 @@ class Operand {
364
364
  * Get the strict representation.
365
365
  */
366
366
  toString() {
367
- throw new Error('not implemented exception');
367
+ return '[Operand]';
368
368
  }
369
369
  }
370
370
 
@@ -478,10 +478,9 @@ function parseKeyComponents(key) {
478
478
  const keys = [];
479
479
  const parseResult = keyWithArrayIndexRegex.exec(unwrappedKey);
480
480
  if (parseResult) {
481
- var _parseResult$groups$c, _parseResult$groups, _parseResult$groups2;
482
- const extractedKey = parseBacktickWrappedKey((_parseResult$groups$c = parseResult === null || parseResult === void 0 || (_parseResult$groups = parseResult.groups) === null || _parseResult$groups === void 0 ? void 0 : _parseResult$groups.currentKey) !== null && _parseResult$groups$c !== void 0 ? _parseResult$groups$c : unwrappedKey);
481
+ const extractedKey = parseBacktickWrappedKey(parseResult?.groups?.currentKey ?? unwrappedKey);
483
482
  keys.push(extractedKey);
484
- const rawIndexes = parseResult === null || parseResult === void 0 || (_parseResult$groups2 = parseResult.groups) === null || _parseResult$groups2 === void 0 ? void 0 : _parseResult$groups2.indexes;
483
+ const rawIndexes = parseResult?.groups?.indexes;
485
484
  if (rawIndexes) {
486
485
  for (const indexResult of rawIndexes.matchAll(arrayIndexRegex)) {
487
486
  keys.push(parseInt(indexResult[1]));
@@ -542,7 +541,7 @@ function complexValueLookup(ctx, key) {
542
541
  if (!keys) {
543
542
  return undefined;
544
543
  }
545
- return simpleValueLookup(keys !== null && keys !== void 0 ? keys : [])(ctx);
544
+ return simpleValueLookup(keys ?? [])(ctx);
546
545
  }
547
546
  let DataType = /*#__PURE__*/function (DataType) {
548
547
  DataType["Number"] = "Number";
@@ -604,8 +603,7 @@ class Reference extends Operand {
604
603
  * {@link Evaluable.simplify}
605
604
  */
606
605
  simplify(ctx, strictKeys, optionalKeys) {
607
- var _this$getKeys;
608
- const [key] = (_this$getKeys = this.getKeys(ctx)) !== null && _this$getKeys !== void 0 ? _this$getKeys : [];
606
+ const [key] = this.getKeys(ctx) ?? [];
609
607
  if (ctx[key] !== undefined) {
610
608
  return this.evaluate(ctx);
611
609
  }
@@ -52,7 +52,7 @@ function isObject(value) {
52
52
  if (value === null || value === undefined) {
53
53
  return false;
54
54
  }
55
- if (typeof value !== 'object' || (value === null || value === void 0 ? void 0 : value.constructor) !== Object) {
55
+ if (typeof value !== 'object' || value?.constructor !== Object) {
56
56
  return false;
57
57
  }
58
58
  return true;
@@ -360,7 +360,7 @@ class Operand {
360
360
  * Get the strict representation.
361
361
  */
362
362
  toString() {
363
- throw new Error('not implemented exception');
363
+ return '[Operand]';
364
364
  }
365
365
  }
366
366
 
@@ -474,10 +474,9 @@ function parseKeyComponents(key) {
474
474
  const keys = [];
475
475
  const parseResult = keyWithArrayIndexRegex.exec(unwrappedKey);
476
476
  if (parseResult) {
477
- var _parseResult$groups$c, _parseResult$groups, _parseResult$groups2;
478
- const extractedKey = parseBacktickWrappedKey((_parseResult$groups$c = parseResult === null || parseResult === void 0 || (_parseResult$groups = parseResult.groups) === null || _parseResult$groups === void 0 ? void 0 : _parseResult$groups.currentKey) !== null && _parseResult$groups$c !== void 0 ? _parseResult$groups$c : unwrappedKey);
477
+ const extractedKey = parseBacktickWrappedKey(parseResult?.groups?.currentKey ?? unwrappedKey);
479
478
  keys.push(extractedKey);
480
- const rawIndexes = parseResult === null || parseResult === void 0 || (_parseResult$groups2 = parseResult.groups) === null || _parseResult$groups2 === void 0 ? void 0 : _parseResult$groups2.indexes;
479
+ const rawIndexes = parseResult?.groups?.indexes;
481
480
  if (rawIndexes) {
482
481
  for (const indexResult of rawIndexes.matchAll(arrayIndexRegex)) {
483
482
  keys.push(parseInt(indexResult[1]));
@@ -538,7 +537,7 @@ function complexValueLookup(ctx, key) {
538
537
  if (!keys) {
539
538
  return undefined;
540
539
  }
541
- return simpleValueLookup(keys !== null && keys !== void 0 ? keys : [])(ctx);
540
+ return simpleValueLookup(keys ?? [])(ctx);
542
541
  }
543
542
  let DataType = /*#__PURE__*/function (DataType) {
544
543
  DataType["Number"] = "Number";
@@ -600,8 +599,7 @@ class Reference extends Operand {
600
599
  * {@link Evaluable.simplify}
601
600
  */
602
601
  simplify(ctx, strictKeys, optionalKeys) {
603
- var _this$getKeys;
604
- const [key] = (_this$getKeys = this.getKeys(ctx)) !== null && _this$getKeys !== void 0 ? _this$getKeys : [];
602
+ const [key] = this.getKeys(ctx) ?? [];
605
603
  if (ctx[key] !== undefined) {
606
604
  return this.evaluate(ctx);
607
605
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@briza/illogical",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "A micro conditional javascript engine used to parse the raw logical and comparison expressions, evaluate the expression in the given data context, and provide access to a text form of the given expressions.",
5
5
  "type": "module",
6
6
  "main": "./lib/illogical.cjs",
@@ -11,7 +11,8 @@
11
11
  "require": "./lib/illogical.cjs"
12
12
  },
13
13
  "files": [
14
- "lib/"
14
+ "lib/",
15
+ "types/"
15
16
  ],
16
17
  "author": {
17
18
  "name": "David Horak",
@@ -20,15 +21,21 @@
20
21
  "license": "MIT",
21
22
  "scripts": {
22
23
  "type-check": "tsc --noEmit",
23
- "build:types": "tsc --emitDeclarationOnly",
24
+ "build:types": "tsc --project tsconfig.build.json --emitDeclarationOnly",
24
25
  "build:js": "rollup -c",
25
- "build": "rm -rf types && npm run build:types && npm run build:js",
26
+ "build": "rm -rf lib types && npm run build:types && npm run build:js",
26
27
  "docs": "typedoc src && git checkout docs/.nojekyll",
27
- "test": "jest",
28
+ "test": "node --import tsx --test \"src/**/*.test.ts\"",
29
+ "test:sample-conditions": "node --import tsx --test --test-reporter=spec \"src/__test__/unit/sample-conditions.test.ts\"",
30
+ "test:coverage": "node --import tsx --test --experimental-test-coverage --test-reporter=spec --test-reporter-destination=stdout --test-reporter=lcov --test-reporter-destination=coverage.lcov \"src/**/*.test.ts\"",
28
31
  "lint": "eslint --max-warnings 0 \"src/**/*.{ts,js}\"",
29
32
  "lint:fix": "eslint --max-warnings 0 \"src/**/*.{ts,js}\" --fix",
30
33
  "prepublishOnly": "npm run test && npm run build",
31
- "check-licenses": "license-checker --summary --excludePrivatePackages --onlyAllow \"MIT;MIT OR X11;Apache-2.0;ISC;BSD-3-Clause;BSD-2-Clause;CC-BY-4.0;Public Domain;BSD;CC-BY-3.0;CC0-1.0;Python-2.0;BlueOak-1.0.0;Unlicense\""
34
+ "check-licenses": "license-checker --summary --excludePrivatePackages --onlyAllow \"MIT;MIT OR X11;Apache-2.0;ISC;BSD-3-Clause;BSD-2-Clause;CC-BY-4.0;Public Domain;BSD;CC-BY-3.0;CC0-1.0;Python-2.0;BlueOak-1.0.0;Unlicense\"",
35
+ "bench:sample:oop:evaluate": "npm run build && node --import tsx src/benchmark/evaluate.ts --cases conditions/sample-conditions --out benchmark/results-sample-evaluate-oop.json",
36
+ "bench:sample:oop:simplify": "npm run build && node --import tsx src/benchmark/simplify.ts --cases conditions/sample-conditions --out benchmark/results-sample-simplify-oop.json",
37
+ "bench:synthetic:oop:evaluate": "npm run build && node --import tsx src/benchmark/evaluate.ts --cases conditions/synthetic-conditions --out benchmark/results-synthetic-evaluate-oop.json",
38
+ "bench:synthetic:oop:simplify": "npm run build && node --import tsx src/benchmark/simplify.ts --cases conditions/synthetic-conditions --out benchmark/results-synthetic-simplify-oop.json"
32
39
  },
33
40
  "repository": {
34
41
  "type": "git",
@@ -51,11 +58,11 @@
51
58
  "@babel/preset-typescript": "^7.28.5",
52
59
  "@eslint/eslintrc": "^3.3.3",
53
60
  "@eslint/js": "^9.39.2",
54
- "@rollup/plugin-babel": "^6.1.0",
55
- "@rollup/plugin-commonjs": "^26.0.3",
61
+ "@rollup/plugin-babel": "^7.0.0",
62
+ "@rollup/plugin-commonjs": "^29.0.2",
56
63
  "@rollup/plugin-eslint": "^9.1.0",
57
- "@rollup/plugin-node-resolve": "^15.3.1",
58
- "@types/jest": "^29.5.14",
64
+ "@rollup/plugin-node-resolve": "^16.0.3",
65
+ "@types/node": "^25.5.0",
59
66
  "@typescript-eslint/eslint-plugin": "^8.54.0",
60
67
  "@typescript-eslint/parser": "^8.54.0",
61
68
  "eslint": "^9.39.2",
@@ -66,12 +73,12 @@
66
73
  "eslint-plugin-prettier": "^5.5.5",
67
74
  "eslint-plugin-promise": "^7.2.1",
68
75
  "eslint-plugin-simple-import-sort": "^12.1.1",
69
- "jest": "^29.7.0",
70
76
  "license-checker": "^25.0.1",
71
77
  "prettier": "^3.6.2",
72
78
  "rollup": "^4.52.5",
73
- "ts-jest": "^29.4.5",
74
- "typedoc": "^0.26.11",
75
- "typescript": "^5.6.3"
79
+ "tinybench": "^6.0.0",
80
+ "tsx": "^4.19.1",
81
+ "typedoc": "^0.28.18",
82
+ "typescript": "^6.0.2"
76
83
  }
77
84
  }
package/readme.md CHANGED
@@ -1,82 +1,40 @@
1
- # illogical
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
+ [![build status](https://github.com/briza-insurance/illogical/actions/workflows/test.yml/badge.svg)](https://github.com/briza-insurance/illogical/actions?branch=master)
13
+ [![npm version](https://badge.fury.io/js/@briza%2Fillogical.svg?icon=si%3Anpm)](https://badge.fury.io/js/@briza%2Fillogical)
14
+ [![install size](https://packagephobia.com/badge?p=@briza/illogical)](https://packagephobia.com/result?p=@briza/illogical)
15
+ ![zero dependencies](https://img.shields.io/badge/0-dependencies-green)
16
+ ![npm downloads](https://img.shields.io/npm/dm/%40briza%2Fillogical)
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
- A micro conditional javascript engine used to parse the raw logical and comparison expressions, evaluate the expression in the given data context, and provide access to a text form of the given expressions.
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
- ## Installation via NPM or Yarn
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
- ```sh
16
- npm install -D @briza/illogical
17
- ```
31
+ ## 🚀 Get Started
18
32
 
19
33
  ```sh
20
- yarn add @briza/illogical -D
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,59 @@ 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
- // Evaluate an expression in the given data context
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
63
 
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"]
64
+ // Evaluate a logical expression
65
+ engine.evaluate(['AND', ['>', '$age', 20], ['==', '$name', 'peter']]) // true
524
66
  ```
525
67
 
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.
68
+ ## 🖼️ Resources
539
69
 
540
- ```json
541
- ["SUFFIX", "establishment", "ment"]
542
- ```
70
+ Understand supported expressions:
543
71
 
544
- ```js
545
- engine.evaluate(['SUFFIX', 'establishment', 'ment']) // true
546
- engine.evaluate(['SUFFIX', 'establish', 'ment']) // false
547
- ```
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)
548
77
 
549
- #### Overlap
78
+ Learn about usages:
550
79
 
551
- Expression format: `["OVERLAP", `[Left Operand](#operand-types), [Right Operand](#operand-types)`]`.
80
+ - [Evaluate](./specs/evaluate.md)
81
+ - [Statement](./specs/statement.md)
82
+ - [Parse](./specs/parse.md)
83
+ - [Simplify](./specs/simplify.md)
552
84
 
553
- > Valid operand types number[] or string[].
85
+ Customize the engine and the documentation:
554
86
 
555
- ```json
556
- ["OVERLAP", [1, 2], [1, 2, 3, 4, 5]]
557
- ["OVERLAP", ["circle", "square", "triangle"], ["square"]]
558
- ```
87
+ - [Engine Options](./specs/engine.md)
88
+ - [Code Documentation](https://briza-insurance.github.io/illogical/index.html)
559
89
 
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
- ```
90
+ ## 📖 Changelog
568
91
 
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
-
654
- The logical NOR operator returns the boolean value TRUE if both operands are FALSE and returns FALSE otherwise.
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]
754
- ```
755
-
756
- ```js
757
- engine.evaluate(['==', ['+', 5, 5], 10]) // true
758
- ```
759
-
760
- ## Engine Options
761
-
762
- ### Parser Options
763
-
764
- Below described, are individual options object properties which could be used individually. Any missing options will be substituted with the default options.
765
-
766
- **Usage**
767
-
768
- ```js
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
- ```
786
-
787
- **Return value:**
788
-
789
- - `true` = reference type
790
- - `false` = value type
791
-
792
- **Default reference predicate:**
793
-
794
- > The `$` symbol at the begging of the operand is used to predicate the reference type., E.g. `$State`, `$Country`.
795
-
796
- #### Reference Transform
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
- ---
92
+ See [changelog.md](./changelog.md).
887
93
 
888
- ## Contributing
94
+ ## 🤝 Contributing
889
95
 
890
- See [contributing.md](https://github.com/briza-insurance/illogical/blob/master/contributing.md).
96
+ See [contributing.md](./contributing.md).
891
97
 
892
- ## License
98
+ ## 📜 License
893
99
 
894
- Illogical is released under the MIT license. See [license.txt](https://github.com/briza-insurance/illogical/blob/master/license.txt).
100
+ Illogical is released under the MIT license. See [license.txt](./license.txt) for details.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes