@axi-engine/expressions 0.2.1
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/README.md +179 -0
- package/dist/index.d.mts +469 -0
- package/dist/index.d.ts +469 -0
- package/dist/index.js +319 -0
- package/dist/index.mjs +281 -0
- package/package.json +41 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AndExpressionHandler: () => AndExpressionHandler,
|
|
24
|
+
ChanceExpressionHandler: () => ChanceExpressionHandler,
|
|
25
|
+
ComparisonExpressionHandler: () => ComparisonExpressionHandler,
|
|
26
|
+
ExistsExpressionHandler: () => ExistsExpressionHandler,
|
|
27
|
+
ExpressionEvaluator: () => ExpressionEvaluator,
|
|
28
|
+
InExpressionHandler: () => InExpressionHandler,
|
|
29
|
+
LiteralExpressionHandler: () => LiteralExpressionHandler,
|
|
30
|
+
NotExpressionHandler: () => NotExpressionHandler,
|
|
31
|
+
OrExpressionHandler: () => OrExpressionHandler,
|
|
32
|
+
createExpressionEvaluator: () => createExpressionEvaluator,
|
|
33
|
+
isArithmeticOperand: () => isArithmeticOperand,
|
|
34
|
+
isOperand: () => isOperand,
|
|
35
|
+
isReferenceOperand: () => isReferenceOperand,
|
|
36
|
+
isValueOperand: () => isValueOperand,
|
|
37
|
+
resolveOperand: () => resolveOperand,
|
|
38
|
+
resolveOperandAsScalar: () => resolveOperandAsScalar
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(index_exports);
|
|
41
|
+
|
|
42
|
+
// src/guards.ts
|
|
43
|
+
var import_utils = require("@axi-engine/utils");
|
|
44
|
+
function isValueOperand(val) {
|
|
45
|
+
return !(0, import_utils.isNullOrUndefined)(val) && typeof val === "object" && "value" in val;
|
|
46
|
+
}
|
|
47
|
+
function isReferenceOperand(val) {
|
|
48
|
+
return !(0, import_utils.isNullOrUndefined)(val) && typeof val === "object" && "path" in val;
|
|
49
|
+
}
|
|
50
|
+
function isArithmeticOperand(val) {
|
|
51
|
+
return !(0, import_utils.isNullOrUndefined)(val) && typeof val === "object" && "arithmetic" in val;
|
|
52
|
+
}
|
|
53
|
+
function isOperand(val) {
|
|
54
|
+
return isValueOperand(val) || isReferenceOperand(val) || isArithmeticOperand(val);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/resolve-operand.ts
|
|
58
|
+
var import_utils2 = require("@axi-engine/utils");
|
|
59
|
+
function resolveOperand(op, source) {
|
|
60
|
+
if (isValueOperand(op)) {
|
|
61
|
+
return op.value;
|
|
62
|
+
}
|
|
63
|
+
if (isReferenceOperand(op)) {
|
|
64
|
+
return source.get(op.path);
|
|
65
|
+
}
|
|
66
|
+
if (isArithmeticOperand(op)) {
|
|
67
|
+
const leftVal = resolveOperand(op.arithmetic.left, source);
|
|
68
|
+
const rightVal = resolveOperand(op.arithmetic.right, source);
|
|
69
|
+
(0, import_utils2.throwIf)(
|
|
70
|
+
!(0, import_utils2.isNumber)(leftVal) || !(0, import_utils2.isNumber)(rightVal),
|
|
71
|
+
`Arithmetic operations require number operands, but got ${typeof leftVal} and ${typeof rightVal}.`
|
|
72
|
+
);
|
|
73
|
+
switch (op.arithmetic.op) {
|
|
74
|
+
case "+":
|
|
75
|
+
return Number(leftVal) + Number(rightVal);
|
|
76
|
+
case "-":
|
|
77
|
+
return Number(leftVal) - Number(rightVal);
|
|
78
|
+
case "*":
|
|
79
|
+
return Number(leftVal) * Number(rightVal);
|
|
80
|
+
case "/":
|
|
81
|
+
return Number(leftVal) / Number(rightVal);
|
|
82
|
+
default:
|
|
83
|
+
(0, import_utils2.throwIf)(true, `Unknown arithmetic operator: ${op.arithmetic.op}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
(0, import_utils2.throwIf)(true, `Unknown operand type: ${JSON.stringify(op)}`);
|
|
87
|
+
return void 0;
|
|
88
|
+
}
|
|
89
|
+
function resolveOperandAsScalar(op, source) {
|
|
90
|
+
const value = resolveOperand(op, source);
|
|
91
|
+
(0, import_utils2.throwIf)(
|
|
92
|
+
!(0, import_utils2.isScalar)(value),
|
|
93
|
+
`Expected a scalar value (string, number, boolean), but got ${typeof value}.`
|
|
94
|
+
);
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/expression-evaluator.ts
|
|
99
|
+
var import_utils3 = require("@axi-engine/utils");
|
|
100
|
+
var ExpressionEvaluator = class {
|
|
101
|
+
/** @internal A map of registered expression handlers. */
|
|
102
|
+
handlers = /* @__PURE__ */ new Map();
|
|
103
|
+
/**
|
|
104
|
+
* Registers a new `ExpressionHandler` with the evaluator.
|
|
105
|
+
* This is the primary mechanism for extending the expression language with
|
|
106
|
+
* custom logic and new expression types.
|
|
107
|
+
*
|
|
108
|
+
* @param handler The `ExpressionHandler` instance to register.
|
|
109
|
+
* @throws {Error} Throws an error if a handler for the same expression type
|
|
110
|
+
* is already registered.
|
|
111
|
+
*/
|
|
112
|
+
register(handler) {
|
|
113
|
+
(0, import_utils3.throwIf)(this.handlers.has(handler.type), `Expression handler for: '${handler.type}' expression already registered`);
|
|
114
|
+
this.handlers.set(handler.type, handler);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Resolves a given expression against a data source.
|
|
118
|
+
*
|
|
119
|
+
* This is the main entry point for the evaluation process. It identifies the
|
|
120
|
+
* expression type by its key, finds the corresponding handler, creates the
|
|
121
|
+
* evaluation context, and delegates the evaluation task to the handler.
|
|
122
|
+
*
|
|
123
|
+
* @param expression The expression object to evaluate.
|
|
124
|
+
* @param data The `DataSource` to be used for resolving any `ReferenceOperand`s
|
|
125
|
+
* within the expression tree.
|
|
126
|
+
* @returns A promise that resolves to `true` or `false` based on the
|
|
127
|
+
* evaluation result.
|
|
128
|
+
*/
|
|
129
|
+
async resolve(expression, data) {
|
|
130
|
+
const key = (0, import_utils3.firstKeyOf)(expression);
|
|
131
|
+
const handler = this.handlers.get(key);
|
|
132
|
+
(0, import_utils3.throwIfEmpty)(handler, `Can't find expression handler for: '${key}' expression`);
|
|
133
|
+
const context = {
|
|
134
|
+
resolve: (expression2) => this.resolve(expression2, data),
|
|
135
|
+
source: () => data
|
|
136
|
+
};
|
|
137
|
+
return handler.resolve(expression, context);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// src/handlers/and-expression-handler.ts
|
|
142
|
+
var AndExpressionHandler = class {
|
|
143
|
+
type = "and";
|
|
144
|
+
async resolve(exp, context) {
|
|
145
|
+
const res = [];
|
|
146
|
+
for (let childExp of exp.and) {
|
|
147
|
+
res.push(await context.resolve(childExp));
|
|
148
|
+
}
|
|
149
|
+
return exp.and.length === res.filter((val) => val).length;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// src/handlers/chance-expression-handler.ts
|
|
154
|
+
var import_utils4 = require("@axi-engine/utils");
|
|
155
|
+
var ChanceExpressionHandler = class {
|
|
156
|
+
type = "chance";
|
|
157
|
+
/**
|
|
158
|
+
* Resolves the `chance` expression.
|
|
159
|
+
*
|
|
160
|
+
* The method first resolves the operand to a scalar value. It supports both
|
|
161
|
+
* numbers (e.g., `50`) and strings (e.g., `"50"`, `"50%"`), which are parsed
|
|
162
|
+
* into a numeric percentage. It then generates a random integer from 0 to 99
|
|
163
|
+
* and returns `true` if this random number is less than the resolved chance value.
|
|
164
|
+
*
|
|
165
|
+
* @param exp The `ChanceExpression` object to resolve.
|
|
166
|
+
* @param context The context for the expression evaluation, providing access to the data source.
|
|
167
|
+
* @returns {Promise<boolean>} A promise that resolves to `true` if the random roll
|
|
168
|
+
* succeeds, and `false` otherwise.
|
|
169
|
+
* @throws {Error} If the operand resolves to a value that cannot be parsed into
|
|
170
|
+
* a number (e.g., a boolean or a non-numeric string).
|
|
171
|
+
*/
|
|
172
|
+
async resolve(exp, context) {
|
|
173
|
+
const resolvedValue = resolveOperandAsScalar(exp.chance, context.source());
|
|
174
|
+
let numericValue;
|
|
175
|
+
if ((0, import_utils4.isNumber)(resolvedValue)) {
|
|
176
|
+
numericValue = resolvedValue;
|
|
177
|
+
} else if ((0, import_utils4.isString)(resolvedValue)) {
|
|
178
|
+
const parsed = parseFloat(resolvedValue.replace("%", "").trim());
|
|
179
|
+
(0, import_utils4.throwIf)(isNaN(parsed), `Chance value as a string must be a valid number, but got '${resolvedValue}'.`);
|
|
180
|
+
numericValue = parsed;
|
|
181
|
+
} else {
|
|
182
|
+
(0, import_utils4.throwIf)(true, `Chance value must be a number or a string, but got a boolean.`);
|
|
183
|
+
}
|
|
184
|
+
const randomRoll = (0, import_utils4.randInt)(0, 100);
|
|
185
|
+
return randomRoll < numericValue;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// src/handlers/comparision-expression-handler.ts
|
|
190
|
+
var ComparisonExpressionHandler = class {
|
|
191
|
+
type = "comparison";
|
|
192
|
+
async resolve(exp, context) {
|
|
193
|
+
const left = resolveOperandAsScalar(exp.comparison.left, context.source());
|
|
194
|
+
const right = resolveOperandAsScalar(exp.comparison.right, context.source());
|
|
195
|
+
switch (exp.comparison.op) {
|
|
196
|
+
case "==":
|
|
197
|
+
return left === right;
|
|
198
|
+
case "<=":
|
|
199
|
+
return left <= right;
|
|
200
|
+
case "<":
|
|
201
|
+
return left < right;
|
|
202
|
+
case ">=":
|
|
203
|
+
return left >= right;
|
|
204
|
+
case ">":
|
|
205
|
+
return left > right;
|
|
206
|
+
case "!=":
|
|
207
|
+
return left !== right;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// src/handlers/exists-expression-handler.ts
|
|
213
|
+
var ExistsExpressionHandler = class {
|
|
214
|
+
type = "exists";
|
|
215
|
+
async resolve(exp, context) {
|
|
216
|
+
return context.source().has(exp.exists);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// src/handlers/in-expression-handler.ts
|
|
221
|
+
var import_utils5 = require("@axi-engine/utils");
|
|
222
|
+
var InExpressionHandler = class {
|
|
223
|
+
type = "in";
|
|
224
|
+
/**
|
|
225
|
+
* Resolves the `in` expression.
|
|
226
|
+
*
|
|
227
|
+
* The method performs the following steps:
|
|
228
|
+
* 1. Resolves the `value` operand to a scalar.
|
|
229
|
+
* 2. Obtains the source array, which can be a literal array from the expression
|
|
230
|
+
* or the result of resolving the `array` operand.
|
|
231
|
+
* 3. Ensures the source is a valid array.
|
|
232
|
+
* 4. Resolves every item within the source array to a scalar value.
|
|
233
|
+
* 5. Checks if the value from step 1 is included in the resolved array from step 4.
|
|
234
|
+
*
|
|
235
|
+
* @param exp The `InExpression` object to resolve.
|
|
236
|
+
* @param context The context for the expression evaluation, providing the data source.
|
|
237
|
+
* @returns {Promise<boolean>} A promise that resolves to `true` if the value is found
|
|
238
|
+
* in the array, and `false` otherwise.
|
|
239
|
+
* @throws {Error} If the source for the array does not resolve to an array.
|
|
240
|
+
* @throws {Error} If any operand within the process fails to resolve correctly.
|
|
241
|
+
*/
|
|
242
|
+
async resolve(exp, context) {
|
|
243
|
+
const value = resolveOperandAsScalar(exp.in.value, context.source());
|
|
244
|
+
const rawArray = Array.isArray(exp.in.array) ? exp.in.array : resolveOperand(exp.in.array, context.source());
|
|
245
|
+
(0, import_utils5.throwIf)(
|
|
246
|
+
!Array.isArray(rawArray),
|
|
247
|
+
`The 'in' expression requires an array, but the provided source resolved to ${typeof rawArray}.`
|
|
248
|
+
);
|
|
249
|
+
const typedArray = rawArray;
|
|
250
|
+
const resolvedArray = typedArray.map(
|
|
251
|
+
(item) => (0, import_utils5.isScalar)(item) ? item : resolveOperandAsScalar(item, context.source())
|
|
252
|
+
);
|
|
253
|
+
return resolvedArray.includes(value);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// src/handlers/literal-expression-handler.ts
|
|
258
|
+
var LiteralExpressionHandler = class {
|
|
259
|
+
type = "literal";
|
|
260
|
+
async resolve(exp, _context) {
|
|
261
|
+
return exp.literal;
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// src/handlers/not-expression-handler.ts
|
|
266
|
+
var NotExpressionHandler = class {
|
|
267
|
+
type = "not";
|
|
268
|
+
async resolve(exp, context) {
|
|
269
|
+
return !await context.resolve(exp.not);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// src/handlers/or-expression-handler.ts
|
|
274
|
+
var OrExpressionHandler = class {
|
|
275
|
+
type = "or";
|
|
276
|
+
async resolve(exp, context) {
|
|
277
|
+
const res = [];
|
|
278
|
+
for (let childExp of exp.or) {
|
|
279
|
+
res.push(await context.resolve(childExp));
|
|
280
|
+
}
|
|
281
|
+
return res.filter((val) => val).length > 0;
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// src/setup.ts
|
|
286
|
+
function createExpressionEvaluator(additionalHandlers) {
|
|
287
|
+
const evaluator = new ExpressionEvaluator();
|
|
288
|
+
evaluator.register(new AndExpressionHandler());
|
|
289
|
+
evaluator.register(new ChanceExpressionHandler());
|
|
290
|
+
evaluator.register(new ComparisonExpressionHandler());
|
|
291
|
+
evaluator.register(new ExistsExpressionHandler());
|
|
292
|
+
evaluator.register(new InExpressionHandler());
|
|
293
|
+
evaluator.register(new LiteralExpressionHandler());
|
|
294
|
+
evaluator.register(new NotExpressionHandler());
|
|
295
|
+
evaluator.register(new OrExpressionHandler());
|
|
296
|
+
if (additionalHandlers) {
|
|
297
|
+
additionalHandlers.forEach((handler) => evaluator.register(handler));
|
|
298
|
+
}
|
|
299
|
+
return evaluator;
|
|
300
|
+
}
|
|
301
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
302
|
+
0 && (module.exports = {
|
|
303
|
+
AndExpressionHandler,
|
|
304
|
+
ChanceExpressionHandler,
|
|
305
|
+
ComparisonExpressionHandler,
|
|
306
|
+
ExistsExpressionHandler,
|
|
307
|
+
ExpressionEvaluator,
|
|
308
|
+
InExpressionHandler,
|
|
309
|
+
LiteralExpressionHandler,
|
|
310
|
+
NotExpressionHandler,
|
|
311
|
+
OrExpressionHandler,
|
|
312
|
+
createExpressionEvaluator,
|
|
313
|
+
isArithmeticOperand,
|
|
314
|
+
isOperand,
|
|
315
|
+
isReferenceOperand,
|
|
316
|
+
isValueOperand,
|
|
317
|
+
resolveOperand,
|
|
318
|
+
resolveOperandAsScalar
|
|
319
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// src/guards.ts
|
|
2
|
+
import { isNullOrUndefined } from "@axi-engine/utils";
|
|
3
|
+
function isValueOperand(val) {
|
|
4
|
+
return !isNullOrUndefined(val) && typeof val === "object" && "value" in val;
|
|
5
|
+
}
|
|
6
|
+
function isReferenceOperand(val) {
|
|
7
|
+
return !isNullOrUndefined(val) && typeof val === "object" && "path" in val;
|
|
8
|
+
}
|
|
9
|
+
function isArithmeticOperand(val) {
|
|
10
|
+
return !isNullOrUndefined(val) && typeof val === "object" && "arithmetic" in val;
|
|
11
|
+
}
|
|
12
|
+
function isOperand(val) {
|
|
13
|
+
return isValueOperand(val) || isReferenceOperand(val) || isArithmeticOperand(val);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/resolve-operand.ts
|
|
17
|
+
import { isNumber, isScalar, throwIf } from "@axi-engine/utils";
|
|
18
|
+
function resolveOperand(op, source) {
|
|
19
|
+
if (isValueOperand(op)) {
|
|
20
|
+
return op.value;
|
|
21
|
+
}
|
|
22
|
+
if (isReferenceOperand(op)) {
|
|
23
|
+
return source.get(op.path);
|
|
24
|
+
}
|
|
25
|
+
if (isArithmeticOperand(op)) {
|
|
26
|
+
const leftVal = resolveOperand(op.arithmetic.left, source);
|
|
27
|
+
const rightVal = resolveOperand(op.arithmetic.right, source);
|
|
28
|
+
throwIf(
|
|
29
|
+
!isNumber(leftVal) || !isNumber(rightVal),
|
|
30
|
+
`Arithmetic operations require number operands, but got ${typeof leftVal} and ${typeof rightVal}.`
|
|
31
|
+
);
|
|
32
|
+
switch (op.arithmetic.op) {
|
|
33
|
+
case "+":
|
|
34
|
+
return Number(leftVal) + Number(rightVal);
|
|
35
|
+
case "-":
|
|
36
|
+
return Number(leftVal) - Number(rightVal);
|
|
37
|
+
case "*":
|
|
38
|
+
return Number(leftVal) * Number(rightVal);
|
|
39
|
+
case "/":
|
|
40
|
+
return Number(leftVal) / Number(rightVal);
|
|
41
|
+
default:
|
|
42
|
+
throwIf(true, `Unknown arithmetic operator: ${op.arithmetic.op}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
throwIf(true, `Unknown operand type: ${JSON.stringify(op)}`);
|
|
46
|
+
return void 0;
|
|
47
|
+
}
|
|
48
|
+
function resolveOperandAsScalar(op, source) {
|
|
49
|
+
const value = resolveOperand(op, source);
|
|
50
|
+
throwIf(
|
|
51
|
+
!isScalar(value),
|
|
52
|
+
`Expected a scalar value (string, number, boolean), but got ${typeof value}.`
|
|
53
|
+
);
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/expression-evaluator.ts
|
|
58
|
+
import {
|
|
59
|
+
throwIf as throwIf2,
|
|
60
|
+
throwIfEmpty,
|
|
61
|
+
firstKeyOf
|
|
62
|
+
} from "@axi-engine/utils";
|
|
63
|
+
var ExpressionEvaluator = class {
|
|
64
|
+
/** @internal A map of registered expression handlers. */
|
|
65
|
+
handlers = /* @__PURE__ */ new Map();
|
|
66
|
+
/**
|
|
67
|
+
* Registers a new `ExpressionHandler` with the evaluator.
|
|
68
|
+
* This is the primary mechanism for extending the expression language with
|
|
69
|
+
* custom logic and new expression types.
|
|
70
|
+
*
|
|
71
|
+
* @param handler The `ExpressionHandler` instance to register.
|
|
72
|
+
* @throws {Error} Throws an error if a handler for the same expression type
|
|
73
|
+
* is already registered.
|
|
74
|
+
*/
|
|
75
|
+
register(handler) {
|
|
76
|
+
throwIf2(this.handlers.has(handler.type), `Expression handler for: '${handler.type}' expression already registered`);
|
|
77
|
+
this.handlers.set(handler.type, handler);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Resolves a given expression against a data source.
|
|
81
|
+
*
|
|
82
|
+
* This is the main entry point for the evaluation process. It identifies the
|
|
83
|
+
* expression type by its key, finds the corresponding handler, creates the
|
|
84
|
+
* evaluation context, and delegates the evaluation task to the handler.
|
|
85
|
+
*
|
|
86
|
+
* @param expression The expression object to evaluate.
|
|
87
|
+
* @param data The `DataSource` to be used for resolving any `ReferenceOperand`s
|
|
88
|
+
* within the expression tree.
|
|
89
|
+
* @returns A promise that resolves to `true` or `false` based on the
|
|
90
|
+
* evaluation result.
|
|
91
|
+
*/
|
|
92
|
+
async resolve(expression, data) {
|
|
93
|
+
const key = firstKeyOf(expression);
|
|
94
|
+
const handler = this.handlers.get(key);
|
|
95
|
+
throwIfEmpty(handler, `Can't find expression handler for: '${key}' expression`);
|
|
96
|
+
const context = {
|
|
97
|
+
resolve: (expression2) => this.resolve(expression2, data),
|
|
98
|
+
source: () => data
|
|
99
|
+
};
|
|
100
|
+
return handler.resolve(expression, context);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// src/handlers/and-expression-handler.ts
|
|
105
|
+
var AndExpressionHandler = class {
|
|
106
|
+
type = "and";
|
|
107
|
+
async resolve(exp, context) {
|
|
108
|
+
const res = [];
|
|
109
|
+
for (let childExp of exp.and) {
|
|
110
|
+
res.push(await context.resolve(childExp));
|
|
111
|
+
}
|
|
112
|
+
return exp.and.length === res.filter((val) => val).length;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// src/handlers/chance-expression-handler.ts
|
|
117
|
+
import { isNumber as isNumber2, isString, randInt, throwIf as throwIf3 } from "@axi-engine/utils";
|
|
118
|
+
var ChanceExpressionHandler = class {
|
|
119
|
+
type = "chance";
|
|
120
|
+
/**
|
|
121
|
+
* Resolves the `chance` expression.
|
|
122
|
+
*
|
|
123
|
+
* The method first resolves the operand to a scalar value. It supports both
|
|
124
|
+
* numbers (e.g., `50`) and strings (e.g., `"50"`, `"50%"`), which are parsed
|
|
125
|
+
* into a numeric percentage. It then generates a random integer from 0 to 99
|
|
126
|
+
* and returns `true` if this random number is less than the resolved chance value.
|
|
127
|
+
*
|
|
128
|
+
* @param exp The `ChanceExpression` object to resolve.
|
|
129
|
+
* @param context The context for the expression evaluation, providing access to the data source.
|
|
130
|
+
* @returns {Promise<boolean>} A promise that resolves to `true` if the random roll
|
|
131
|
+
* succeeds, and `false` otherwise.
|
|
132
|
+
* @throws {Error} If the operand resolves to a value that cannot be parsed into
|
|
133
|
+
* a number (e.g., a boolean or a non-numeric string).
|
|
134
|
+
*/
|
|
135
|
+
async resolve(exp, context) {
|
|
136
|
+
const resolvedValue = resolveOperandAsScalar(exp.chance, context.source());
|
|
137
|
+
let numericValue;
|
|
138
|
+
if (isNumber2(resolvedValue)) {
|
|
139
|
+
numericValue = resolvedValue;
|
|
140
|
+
} else if (isString(resolvedValue)) {
|
|
141
|
+
const parsed = parseFloat(resolvedValue.replace("%", "").trim());
|
|
142
|
+
throwIf3(isNaN(parsed), `Chance value as a string must be a valid number, but got '${resolvedValue}'.`);
|
|
143
|
+
numericValue = parsed;
|
|
144
|
+
} else {
|
|
145
|
+
throwIf3(true, `Chance value must be a number or a string, but got a boolean.`);
|
|
146
|
+
}
|
|
147
|
+
const randomRoll = randInt(0, 100);
|
|
148
|
+
return randomRoll < numericValue;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// src/handlers/comparision-expression-handler.ts
|
|
153
|
+
var ComparisonExpressionHandler = class {
|
|
154
|
+
type = "comparison";
|
|
155
|
+
async resolve(exp, context) {
|
|
156
|
+
const left = resolveOperandAsScalar(exp.comparison.left, context.source());
|
|
157
|
+
const right = resolveOperandAsScalar(exp.comparison.right, context.source());
|
|
158
|
+
switch (exp.comparison.op) {
|
|
159
|
+
case "==":
|
|
160
|
+
return left === right;
|
|
161
|
+
case "<=":
|
|
162
|
+
return left <= right;
|
|
163
|
+
case "<":
|
|
164
|
+
return left < right;
|
|
165
|
+
case ">=":
|
|
166
|
+
return left >= right;
|
|
167
|
+
case ">":
|
|
168
|
+
return left > right;
|
|
169
|
+
case "!=":
|
|
170
|
+
return left !== right;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// src/handlers/exists-expression-handler.ts
|
|
176
|
+
var ExistsExpressionHandler = class {
|
|
177
|
+
type = "exists";
|
|
178
|
+
async resolve(exp, context) {
|
|
179
|
+
return context.source().has(exp.exists);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// src/handlers/in-expression-handler.ts
|
|
184
|
+
import { isScalar as isScalar2, throwIf as throwIf4 } from "@axi-engine/utils";
|
|
185
|
+
var InExpressionHandler = class {
|
|
186
|
+
type = "in";
|
|
187
|
+
/**
|
|
188
|
+
* Resolves the `in` expression.
|
|
189
|
+
*
|
|
190
|
+
* The method performs the following steps:
|
|
191
|
+
* 1. Resolves the `value` operand to a scalar.
|
|
192
|
+
* 2. Obtains the source array, which can be a literal array from the expression
|
|
193
|
+
* or the result of resolving the `array` operand.
|
|
194
|
+
* 3. Ensures the source is a valid array.
|
|
195
|
+
* 4. Resolves every item within the source array to a scalar value.
|
|
196
|
+
* 5. Checks if the value from step 1 is included in the resolved array from step 4.
|
|
197
|
+
*
|
|
198
|
+
* @param exp The `InExpression` object to resolve.
|
|
199
|
+
* @param context The context for the expression evaluation, providing the data source.
|
|
200
|
+
* @returns {Promise<boolean>} A promise that resolves to `true` if the value is found
|
|
201
|
+
* in the array, and `false` otherwise.
|
|
202
|
+
* @throws {Error} If the source for the array does not resolve to an array.
|
|
203
|
+
* @throws {Error} If any operand within the process fails to resolve correctly.
|
|
204
|
+
*/
|
|
205
|
+
async resolve(exp, context) {
|
|
206
|
+
const value = resolveOperandAsScalar(exp.in.value, context.source());
|
|
207
|
+
const rawArray = Array.isArray(exp.in.array) ? exp.in.array : resolveOperand(exp.in.array, context.source());
|
|
208
|
+
throwIf4(
|
|
209
|
+
!Array.isArray(rawArray),
|
|
210
|
+
`The 'in' expression requires an array, but the provided source resolved to ${typeof rawArray}.`
|
|
211
|
+
);
|
|
212
|
+
const typedArray = rawArray;
|
|
213
|
+
const resolvedArray = typedArray.map(
|
|
214
|
+
(item) => isScalar2(item) ? item : resolveOperandAsScalar(item, context.source())
|
|
215
|
+
);
|
|
216
|
+
return resolvedArray.includes(value);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// src/handlers/literal-expression-handler.ts
|
|
221
|
+
var LiteralExpressionHandler = class {
|
|
222
|
+
type = "literal";
|
|
223
|
+
async resolve(exp, _context) {
|
|
224
|
+
return exp.literal;
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// src/handlers/not-expression-handler.ts
|
|
229
|
+
var NotExpressionHandler = class {
|
|
230
|
+
type = "not";
|
|
231
|
+
async resolve(exp, context) {
|
|
232
|
+
return !await context.resolve(exp.not);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// src/handlers/or-expression-handler.ts
|
|
237
|
+
var OrExpressionHandler = class {
|
|
238
|
+
type = "or";
|
|
239
|
+
async resolve(exp, context) {
|
|
240
|
+
const res = [];
|
|
241
|
+
for (let childExp of exp.or) {
|
|
242
|
+
res.push(await context.resolve(childExp));
|
|
243
|
+
}
|
|
244
|
+
return res.filter((val) => val).length > 0;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// src/setup.ts
|
|
249
|
+
function createExpressionEvaluator(additionalHandlers) {
|
|
250
|
+
const evaluator = new ExpressionEvaluator();
|
|
251
|
+
evaluator.register(new AndExpressionHandler());
|
|
252
|
+
evaluator.register(new ChanceExpressionHandler());
|
|
253
|
+
evaluator.register(new ComparisonExpressionHandler());
|
|
254
|
+
evaluator.register(new ExistsExpressionHandler());
|
|
255
|
+
evaluator.register(new InExpressionHandler());
|
|
256
|
+
evaluator.register(new LiteralExpressionHandler());
|
|
257
|
+
evaluator.register(new NotExpressionHandler());
|
|
258
|
+
evaluator.register(new OrExpressionHandler());
|
|
259
|
+
if (additionalHandlers) {
|
|
260
|
+
additionalHandlers.forEach((handler) => evaluator.register(handler));
|
|
261
|
+
}
|
|
262
|
+
return evaluator;
|
|
263
|
+
}
|
|
264
|
+
export {
|
|
265
|
+
AndExpressionHandler,
|
|
266
|
+
ChanceExpressionHandler,
|
|
267
|
+
ComparisonExpressionHandler,
|
|
268
|
+
ExistsExpressionHandler,
|
|
269
|
+
ExpressionEvaluator,
|
|
270
|
+
InExpressionHandler,
|
|
271
|
+
LiteralExpressionHandler,
|
|
272
|
+
NotExpressionHandler,
|
|
273
|
+
OrExpressionHandler,
|
|
274
|
+
createExpressionEvaluator,
|
|
275
|
+
isArithmeticOperand,
|
|
276
|
+
isOperand,
|
|
277
|
+
isReferenceOperand,
|
|
278
|
+
isValueOperand,
|
|
279
|
+
resolveOperand,
|
|
280
|
+
resolveOperandAsScalar
|
|
281
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@axi-engine/expressions",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/axijs/engine.git",
|
|
9
|
+
"directory": "packages/expressions"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"axi-engine",
|
|
13
|
+
"typescript",
|
|
14
|
+
"gamedev"
|
|
15
|
+
],
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"module": "./dist/index.mjs",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.mjs",
|
|
23
|
+
"require": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"test": "vitest run --root ../../ src/",
|
|
28
|
+
"build": "tsup",
|
|
29
|
+
"prebuild": "npm test",
|
|
30
|
+
"docs": "typedoc src/index.ts --out docs/api --options ../../typedoc.json"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@axi-engine/utils": "^0.2.3"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@axi-engine/utils": "^0.2.3"
|
|
40
|
+
}
|
|
41
|
+
}
|