@coldsmirk/abacus-core 0.4.1 → 0.5.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/README.md +19 -10
- package/dist/index.cjs +354 -46
- package/dist/index.d.cts +92 -40
- package/dist/index.d.ts +92 -40
- package/dist/index.js +350 -47
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
//#region src/engine/errors.ts
|
|
3
2
|
var ExpressionError = class extends Error {
|
|
4
3
|
expression;
|
|
5
4
|
constructor(message, expression, cause) {
|
|
@@ -14,8 +13,42 @@ var ExpressionNotReadyError = class extends ExpressionError {
|
|
|
14
13
|
this.name = "ExpressionNotReadyError";
|
|
15
14
|
}
|
|
16
15
|
};
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
function isAscii(source) {
|
|
17
|
+
for (let index = 0; index < source.length; index += 1) if (source.codePointAt(index) > 127) return false;
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
function utf8ByteLength(codePoint) {
|
|
21
|
+
if (codePoint < 128) return 1;
|
|
22
|
+
if (codePoint < 2048) return 2;
|
|
23
|
+
return codePoint < 65536 ? 3 : 4;
|
|
24
|
+
}
|
|
25
|
+
function utf8OffsetConverter(source) {
|
|
26
|
+
if (isAscii(source)) return (byteOffset) => Math.max(0, Math.min(byteOffset, source.length));
|
|
27
|
+
const byteStarts = [];
|
|
28
|
+
const unitStarts = [];
|
|
29
|
+
let bytes = 0;
|
|
30
|
+
let units = 0;
|
|
31
|
+
for (const char of source) {
|
|
32
|
+
byteStarts.push(bytes);
|
|
33
|
+
unitStarts.push(units);
|
|
34
|
+
bytes += utf8ByteLength(char.codePointAt(0));
|
|
35
|
+
units += char.length;
|
|
36
|
+
}
|
|
37
|
+
byteStarts.push(bytes);
|
|
38
|
+
unitStarts.push(units);
|
|
39
|
+
return (byteOffset) => {
|
|
40
|
+
if (byteOffset <= 0) return 0;
|
|
41
|
+
if (byteOffset >= bytes) return source.length;
|
|
42
|
+
let low = 0;
|
|
43
|
+
let high = byteStarts.length - 1;
|
|
44
|
+
while (low < high) {
|
|
45
|
+
const middle = low + high + 1 >> 1;
|
|
46
|
+
if (byteStarts[middle] <= byteOffset) low = middle;
|
|
47
|
+
else high = middle - 1;
|
|
48
|
+
}
|
|
49
|
+
return unitStarts[low];
|
|
50
|
+
};
|
|
51
|
+
}
|
|
19
52
|
function isUndefined(value) {
|
|
20
53
|
return value === void 0;
|
|
21
54
|
}
|
|
@@ -31,13 +64,13 @@ function isNullish(value) {
|
|
|
31
64
|
function isRecord(value) {
|
|
32
65
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33
66
|
}
|
|
34
|
-
//#endregion
|
|
35
|
-
//#region src/engine/loader.ts
|
|
36
67
|
let enginePromise = null;
|
|
37
68
|
let engineSync = null;
|
|
38
69
|
let engineError = null;
|
|
39
70
|
let configuredInput;
|
|
40
71
|
let typeContextCache = null;
|
|
72
|
+
let loadEpoch = 0;
|
|
73
|
+
let lastLoad = null;
|
|
41
74
|
function evaluateSafely(expression, run) {
|
|
42
75
|
try {
|
|
43
76
|
return run();
|
|
@@ -57,9 +90,13 @@ function configureEngine(options) {
|
|
|
57
90
|
function loadEngine() {
|
|
58
91
|
if (enginePromise) return enginePromise;
|
|
59
92
|
engineError = null;
|
|
60
|
-
|
|
93
|
+
const epoch = loadEpoch;
|
|
94
|
+
const predecessor = lastLoad;
|
|
95
|
+
const input = configuredInput;
|
|
96
|
+
const load = (async () => {
|
|
97
|
+
if (predecessor) await predecessor.catch(() => void 0);
|
|
61
98
|
const zen = await import("@gorules/zen-engine-wasm");
|
|
62
|
-
await zen.default(isUndefined(
|
|
99
|
+
await zen.default(isUndefined(input) ? void 0 : { module_or_path: input });
|
|
63
100
|
const toVariableType = (type) => zen.VariableType.fromJson(type);
|
|
64
101
|
const ensureTypeContext = (variables) => {
|
|
65
102
|
if (typeContextCache && typeContextCache.variables === variables) return typeContextCache;
|
|
@@ -88,9 +125,19 @@ function loadEngine() {
|
|
|
88
125
|
analyze: (variables, source, unary) => {
|
|
89
126
|
const context = ensureTypeContext(variables);
|
|
90
127
|
const rawSpans = unary ? context.handle.typeCheckUnary(source) : context.handle.typeCheck(source);
|
|
128
|
+
if (!Array.isArray(rawSpans)) return {
|
|
129
|
+
rootKind: context.rootKind,
|
|
130
|
+
spans: []
|
|
131
|
+
};
|
|
132
|
+
const toUtf16 = utf8OffsetConverter(source);
|
|
91
133
|
return {
|
|
92
134
|
rootKind: context.rootKind,
|
|
93
|
-
spans:
|
|
135
|
+
spans: rawSpans.map((span) => {
|
|
136
|
+
return {
|
|
137
|
+
...span,
|
|
138
|
+
span: [toUtf16(span.span[0]), toUtf16(span.span[1])]
|
|
139
|
+
};
|
|
140
|
+
})
|
|
94
141
|
};
|
|
95
142
|
},
|
|
96
143
|
satisfies: (actual, expected) => {
|
|
@@ -108,12 +155,17 @@ function loadEngine() {
|
|
|
108
155
|
},
|
|
109
156
|
isReady: () => zen.isReady()
|
|
110
157
|
});
|
|
111
|
-
engineSync = engine;
|
|
158
|
+
if (epoch === loadEpoch) engineSync = engine;
|
|
112
159
|
return engine;
|
|
113
|
-
})()
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
160
|
+
})();
|
|
161
|
+
lastLoad = load;
|
|
162
|
+
enginePromise = load.catch((error) => {
|
|
163
|
+
const failure = new ExpressionError(loadFailureMessage(), void 0, error);
|
|
164
|
+
if (epoch === loadEpoch) {
|
|
165
|
+
enginePromise = null;
|
|
166
|
+
engineError = failure;
|
|
167
|
+
}
|
|
168
|
+
throw failure;
|
|
117
169
|
});
|
|
118
170
|
return enginePromise;
|
|
119
171
|
}
|
|
@@ -128,6 +180,7 @@ function getEngineSync() {
|
|
|
128
180
|
return engineSync;
|
|
129
181
|
}
|
|
130
182
|
function resetEngine() {
|
|
183
|
+
loadEpoch += 1;
|
|
131
184
|
typeContextCache?.handle.free();
|
|
132
185
|
typeContextCache = null;
|
|
133
186
|
enginePromise = null;
|
|
@@ -135,16 +188,178 @@ function resetEngine() {
|
|
|
135
188
|
engineError = null;
|
|
136
189
|
configuredInput = void 0;
|
|
137
190
|
}
|
|
138
|
-
//#endregion
|
|
139
|
-
//#region src/internal/env.ts
|
|
140
191
|
const isDev = detectDev();
|
|
141
192
|
function detectDev() {
|
|
142
193
|
if (typeof process === "undefined") return false;
|
|
143
194
|
return process.env ? process.env.NODE_ENV !== "production" : false;
|
|
144
195
|
}
|
|
145
|
-
|
|
146
|
-
|
|
196
|
+
const U64_MAX = 18446744073709551615n;
|
|
197
|
+
const I64_MIN_MAGNITUDE = 9223372036854775808n;
|
|
198
|
+
const ZEN_DECIMAL_MANTISSA_LIMIT = 2n ** 96n;
|
|
199
|
+
const ZEN_DECIMAL_MANTISSA_LIMIT_TEXT = String(ZEN_DECIMAL_MANTISSA_LIMIT);
|
|
200
|
+
const DECIMAL_SIGN_BIT = 2n ** 95n;
|
|
201
|
+
const EXCESS_PRECISION_LIMIT = 2n ** 52n;
|
|
202
|
+
const NUMBER_TEXT_PATTERN = /^(?<integer>\d+)(?:\.(?<fraction>\d+))?(?:e(?<exponent>[+-]\d+))?$/;
|
|
203
|
+
const UNSIGNED_INTEGER_TEXT_PATTERN = /^\d+$/;
|
|
204
|
+
function isZenUnsignedIntegerText(value) {
|
|
205
|
+
if (!UNSIGNED_INTEGER_TEXT_PATTERN.test(value)) return false;
|
|
206
|
+
const normalized = value.replace(/^0+(?=\d)/, "");
|
|
207
|
+
return normalized.length < ZEN_DECIMAL_MANTISSA_LIMIT_TEXT.length || normalized.length === ZEN_DECIMAL_MANTISSA_LIMIT_TEXT.length && normalized < ZEN_DECIMAL_MANTISSA_LIMIT_TEXT;
|
|
208
|
+
}
|
|
209
|
+
function powerOfTen(exponent) {
|
|
210
|
+
return Number(`1e${exponent}`);
|
|
211
|
+
}
|
|
212
|
+
function normalize(digits, exponent) {
|
|
213
|
+
let d = digits;
|
|
214
|
+
let e = exponent;
|
|
215
|
+
while (d !== 0n && d % 10n === 0n) {
|
|
216
|
+
d /= 10n;
|
|
217
|
+
e += 1;
|
|
218
|
+
}
|
|
219
|
+
return d === 0n ? {
|
|
220
|
+
digits: 0n,
|
|
221
|
+
exponent: 0
|
|
222
|
+
} : {
|
|
223
|
+
digits: d,
|
|
224
|
+
exponent: e
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function parseAsSerdeJson(text) {
|
|
228
|
+
const match = NUMBER_TEXT_PATTERN.exec(text);
|
|
229
|
+
if (match?.groups === void 0) return null;
|
|
230
|
+
const { integer, fraction, exponent } = match.groups;
|
|
231
|
+
let significand = 0n;
|
|
232
|
+
let scale = 0;
|
|
233
|
+
let overflowed = false;
|
|
234
|
+
for (const digit of integer) {
|
|
235
|
+
if (!overflowed && significand * 10n + BigInt(digit) > U64_MAX) overflowed = true;
|
|
236
|
+
if (overflowed) scale += 1;
|
|
237
|
+
else significand = significand * 10n + BigInt(digit);
|
|
238
|
+
}
|
|
239
|
+
if (fraction === void 0 && exponent === void 0 && !overflowed) return {
|
|
240
|
+
kind: "int",
|
|
241
|
+
int: significand,
|
|
242
|
+
f64: 0
|
|
243
|
+
};
|
|
244
|
+
if (fraction !== void 0) for (const digit of fraction) {
|
|
245
|
+
if (overflowed || significand * 10n + BigInt(digit) > U64_MAX) break;
|
|
246
|
+
significand = significand * 10n + BigInt(digit);
|
|
247
|
+
scale -= 1;
|
|
248
|
+
}
|
|
249
|
+
if (exponent !== void 0) scale += Number(exponent);
|
|
250
|
+
let value = Number(significand);
|
|
251
|
+
let remaining = scale;
|
|
252
|
+
for (;;) {
|
|
253
|
+
if (Math.abs(remaining) <= 308) {
|
|
254
|
+
value = remaining >= 0 ? value * powerOfTen(remaining) : value / powerOfTen(-remaining);
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
if (value === 0) break;
|
|
258
|
+
if (remaining >= 0) return null;
|
|
259
|
+
value /= 1e308;
|
|
260
|
+
remaining += 308;
|
|
261
|
+
}
|
|
262
|
+
return Number.isFinite(value) ? {
|
|
263
|
+
kind: "f64",
|
|
264
|
+
int: 0n,
|
|
265
|
+
f64: value
|
|
266
|
+
} : null;
|
|
267
|
+
}
|
|
268
|
+
function decimalFromF64(value) {
|
|
269
|
+
if (value === 0) return {
|
|
270
|
+
digits: 0n,
|
|
271
|
+
exponent: 0
|
|
272
|
+
};
|
|
273
|
+
const view = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(8));
|
|
274
|
+
view.setFloat64(0, value);
|
|
275
|
+
const raw = view.getBigUint64(0);
|
|
276
|
+
const biasedExponent = Number(raw >> 52n & 2047n);
|
|
277
|
+
const fractionBits = raw & 4503599627370495n;
|
|
278
|
+
let bits = biasedExponent === 0 ? fractionBits : fractionBits | 1n << 52n;
|
|
279
|
+
const exponent2 = (biasedExponent === 0 ? 1 : biasedExponent) - 1023 - 52;
|
|
280
|
+
let exponent5 = -exponent2;
|
|
281
|
+
let exponent10 = exponent2;
|
|
282
|
+
while (exponent5 > 0) if ((bits & 1n) === 0n) {
|
|
283
|
+
exponent10 += 1;
|
|
284
|
+
exponent5 -= 1;
|
|
285
|
+
bits >>= 1n;
|
|
286
|
+
} else {
|
|
287
|
+
exponent5 -= 1;
|
|
288
|
+
const timesFive = bits * 5n;
|
|
289
|
+
if (timesFive < ZEN_DECIMAL_MANTISSA_LIMIT) bits = timesFive;
|
|
290
|
+
else {
|
|
291
|
+
exponent10 += 1;
|
|
292
|
+
bits >>= 1n;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
while (exponent5 < 0) if ((bits & DECIMAL_SIGN_BIT) === 0n) {
|
|
296
|
+
exponent10 -= 1;
|
|
297
|
+
exponent5 += 1;
|
|
298
|
+
bits <<= 1n;
|
|
299
|
+
} else if (exponent10 * 2 > -exponent5) return null;
|
|
300
|
+
else {
|
|
301
|
+
exponent5 += 1;
|
|
302
|
+
bits /= 5n;
|
|
303
|
+
}
|
|
304
|
+
while (exponent10 > 0) {
|
|
305
|
+
const timesTen = bits * 10n;
|
|
306
|
+
if (timesTen >= ZEN_DECIMAL_MANTISSA_LIMIT) return null;
|
|
307
|
+
bits = timesTen;
|
|
308
|
+
exponent10 -= 1;
|
|
309
|
+
}
|
|
310
|
+
while (exponent10 < -28) {
|
|
311
|
+
const remainder = bits % 10n;
|
|
312
|
+
bits /= 10n;
|
|
313
|
+
exponent10 += 1;
|
|
314
|
+
if (bits === 0n) exponent10 = 0;
|
|
315
|
+
else if (remainder >= 5n) bits += 1n;
|
|
316
|
+
}
|
|
317
|
+
while (exponent10 < 0 && bits >= EXCESS_PRECISION_LIMIT) {
|
|
318
|
+
const remainder = bits % 10n;
|
|
319
|
+
bits /= 10n;
|
|
320
|
+
exponent10 += 1;
|
|
321
|
+
if (remainder >= 5n) bits += 1n;
|
|
322
|
+
}
|
|
323
|
+
while (exponent10 < 0 && bits % 10n === 0n) {
|
|
324
|
+
bits /= 10n;
|
|
325
|
+
exponent10 += 1;
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
digits: bits,
|
|
329
|
+
exponent: exponent10
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function contextDecimal(value) {
|
|
333
|
+
const magnitude = Math.abs(value);
|
|
334
|
+
if (magnitude === 0) return {
|
|
335
|
+
digits: 0n,
|
|
336
|
+
exponent: 0
|
|
337
|
+
};
|
|
338
|
+
const parsed = parseAsSerdeJson(String(magnitude));
|
|
339
|
+
if (parsed === null) return null;
|
|
340
|
+
if (parsed.kind === "int") {
|
|
341
|
+
if (value >= 0 || parsed.int <= I64_MIN_MAGNITUDE) return normalize(parsed.int, 0);
|
|
342
|
+
const viaF64 = decimalFromF64(Number(parsed.int));
|
|
343
|
+
return viaF64 === null ? null : normalize(viaF64.digits, viaF64.exponent);
|
|
344
|
+
}
|
|
345
|
+
const converted = decimalFromF64(parsed.f64);
|
|
346
|
+
return converted === null ? null : normalize(converted.digits, converted.exponent);
|
|
347
|
+
}
|
|
348
|
+
function literalDecimal(value) {
|
|
349
|
+
const match = NUMBER_TEXT_PATTERN.exec(String(Math.abs(value)));
|
|
350
|
+
if (match?.groups === void 0) return null;
|
|
351
|
+
const { integer, fraction = "", exponent = "0" } = match.groups;
|
|
352
|
+
return normalize(BigInt(integer + fraction), Number(exponent) - fraction.length);
|
|
353
|
+
}
|
|
354
|
+
function isZenConsistentNumber(value) {
|
|
355
|
+
if (!Number.isFinite(value)) return false;
|
|
356
|
+
const literal = literalDecimal(value);
|
|
357
|
+
if (literal === null || literal.digits >= ZEN_DECIMAL_MANTISSA_LIMIT) return false;
|
|
358
|
+
const context = contextDecimal(value);
|
|
359
|
+
return context !== null && literal.digits === context.digits && literal.exponent === context.exponent;
|
|
360
|
+
}
|
|
147
361
|
const SUBJECT_PATTERN = /^[A-Z_$][\w$]*(?:\.[A-Z_$][\w$]*|\[\d+\])*$/i;
|
|
362
|
+
const SUBJECT_INDEX_PATTERN = /\[(?<index>\d+)\]/g;
|
|
148
363
|
const ZEN_RESERVED_WORDS = new Set([
|
|
149
364
|
"and",
|
|
150
365
|
"or",
|
|
@@ -155,17 +370,24 @@ const ZEN_RESERVED_WORDS = new Set([
|
|
|
155
370
|
"null"
|
|
156
371
|
]);
|
|
157
372
|
function isIdentifierPath(subject) {
|
|
158
|
-
|
|
373
|
+
if (!SUBJECT_PATTERN.test(subject) || (subject.match(/[A-Z_$][\w$]*/gi) ?? []).some((segment) => ZEN_RESERVED_WORDS.has(segment))) return false;
|
|
374
|
+
for (const match of subject.matchAll(SUBJECT_INDEX_PATTERN)) if (match.groups?.index === void 0 || !isZenUnsignedIntegerText(match.groups.index)) return false;
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
function isZenRepresentableNumber(value) {
|
|
378
|
+
return isZenConsistentNumber(value);
|
|
159
379
|
}
|
|
160
|
-
//#endregion
|
|
161
|
-
//#region src/condition/compile.ts
|
|
162
380
|
function toZenLiteral(value) {
|
|
163
381
|
if (isNullish(value)) return "null";
|
|
164
382
|
if (typeof value === "number") {
|
|
165
|
-
if (!
|
|
383
|
+
if (!isZenRepresentableNumber(value)) throw new ExpressionError(`Number ${String(value)} has no faithful ZEN literal representation`);
|
|
384
|
+
return String(value);
|
|
385
|
+
}
|
|
386
|
+
if (typeof value === "bigint") {
|
|
387
|
+
if (value >= ZEN_DECIMAL_MANTISSA_LIMIT || -value >= ZEN_DECIMAL_MANTISSA_LIMIT) throw new ExpressionError(`Bigint ${String(value)} has no ZEN literal representation`);
|
|
166
388
|
return String(value);
|
|
167
389
|
}
|
|
168
|
-
if (typeof value === "boolean"
|
|
390
|
+
if (typeof value === "boolean") return String(value);
|
|
169
391
|
if (isString(value)) return encodeZenString(value);
|
|
170
392
|
if (isArray(value)) return `[${value.map((item) => toZenLiteral(item)).join(", ")}]`;
|
|
171
393
|
throw new ExpressionError(`Value of type "${typeof value}" has no ZEN literal representation`);
|
|
@@ -261,8 +483,6 @@ function reportEvaluationFailure(engine, expression, error) {
|
|
|
261
483
|
async function selectBranch(branches, context) {
|
|
262
484
|
return selectBranchWith(branches, context, await loadEngine());
|
|
263
485
|
}
|
|
264
|
-
//#endregion
|
|
265
|
-
//#region src/condition/types.ts
|
|
266
486
|
const CONDITION_OPERATORS = [
|
|
267
487
|
"eq",
|
|
268
488
|
"ne",
|
|
@@ -298,9 +518,83 @@ const CONDITION_OPERATOR_ARITIES = {
|
|
|
298
518
|
function conditionOperatorArity(operator) {
|
|
299
519
|
return CONDITION_OPERATOR_ARITIES[operator];
|
|
300
520
|
}
|
|
301
|
-
|
|
302
|
-
|
|
521
|
+
const MAX_CONDITION_TREE_DEPTH = 64;
|
|
522
|
+
const CONDITION_TREE_OPERATORS = CONDITION_OPERATORS;
|
|
523
|
+
const nodeIdRealm = Math.random().toString(36).slice(2, 7);
|
|
524
|
+
let nodeIdCounter = 0;
|
|
525
|
+
function newConditionNodeId() {
|
|
526
|
+
nodeIdCounter += 1;
|
|
527
|
+
return `cn-${nodeIdRealm}-${nodeIdCounter}`;
|
|
528
|
+
}
|
|
529
|
+
function emptyConditionGroup() {
|
|
530
|
+
return {
|
|
531
|
+
kind: "group",
|
|
532
|
+
id: newConditionNodeId(),
|
|
533
|
+
op: "and",
|
|
534
|
+
items: []
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
function assertConditionTreeStructure(tree) {
|
|
538
|
+
if (!isRecord(tree) || tree.kind !== "group") throw new ExpressionError("Condition tree root must be a group");
|
|
539
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
540
|
+
const stack = [{
|
|
541
|
+
group: tree,
|
|
542
|
+
depth: 0,
|
|
543
|
+
leaving: false
|
|
544
|
+
}];
|
|
545
|
+
while (stack.length > 0) {
|
|
546
|
+
const frame = stack.pop();
|
|
547
|
+
const { group } = frame;
|
|
548
|
+
if (frame.leaving) {
|
|
549
|
+
ancestors.delete(group);
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
if (frame.depth > 64) throw new ExpressionError(`Condition tree exceeds the maximum depth of 64`);
|
|
553
|
+
if (ancestors.has(group)) throw new ExpressionError("Condition tree contains a cycle");
|
|
554
|
+
if (group.op !== "and" && group.op !== "or") throw new ExpressionError(`Unsupported group operator: ${String(group.op)}`);
|
|
555
|
+
if (!isArray(group.items)) throw new ExpressionError("Condition tree group items must be an array");
|
|
556
|
+
ancestors.add(group);
|
|
557
|
+
stack.push({
|
|
558
|
+
...frame,
|
|
559
|
+
leaving: true
|
|
560
|
+
});
|
|
561
|
+
for (let index = group.items.length - 1; index >= 0; index -= 1) {
|
|
562
|
+
const node = group.items[index];
|
|
563
|
+
if (!isRecord(node) || node.kind !== "group" && node.kind !== "rule") throw new ExpressionError("Condition tree contains an invalid node");
|
|
564
|
+
if (node.kind === "group") stack.push({
|
|
565
|
+
group: node,
|
|
566
|
+
depth: frame.depth + 1,
|
|
567
|
+
leaving: false
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
function withRuleId(rule) {
|
|
573
|
+
return rule.id === void 0 ? {
|
|
574
|
+
...rule,
|
|
575
|
+
id: newConditionNodeId()
|
|
576
|
+
} : rule;
|
|
577
|
+
}
|
|
578
|
+
function withGroupIds(group) {
|
|
579
|
+
const items = group.items.map((item) => item.kind === "group" ? withGroupIds(item) : withRuleId(item));
|
|
580
|
+
if (group.id !== void 0 && items.every((item, index) => item === group.items[index])) return group;
|
|
581
|
+
return {
|
|
582
|
+
...group,
|
|
583
|
+
id: group.id ?? newConditionNodeId(),
|
|
584
|
+
items
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
const normalizedTrees = /* @__PURE__ */ new WeakMap();
|
|
588
|
+
function ensureConditionNodeIds(tree) {
|
|
589
|
+
assertConditionTreeStructure(tree);
|
|
590
|
+
const cached = normalizedTrees.get(tree);
|
|
591
|
+
if (cached) return cached;
|
|
592
|
+
const normalized = withGroupIds(tree);
|
|
593
|
+
normalizedTrees.set(tree, normalized);
|
|
594
|
+
return normalized;
|
|
595
|
+
}
|
|
303
596
|
function compileConditionTree(tree) {
|
|
597
|
+
assertConditionTreeStructure(tree);
|
|
304
598
|
const normalized = normalizeNode(tree);
|
|
305
599
|
return normalized === null ? "" : emitNode(normalized, true);
|
|
306
600
|
}
|
|
@@ -317,9 +611,16 @@ function normalizeNode(node) {
|
|
|
317
611
|
}
|
|
318
612
|
function emitNode(node, topLevel) {
|
|
319
613
|
if (node.kind === "rule") return compileRule(node);
|
|
320
|
-
const joined = node.items.map((item) => emitNode(item, false)).join(node.op
|
|
614
|
+
const joined = node.items.map((item) => emitNode(item, false)).join(groupJoiner(node.op));
|
|
321
615
|
return topLevel ? joined : `(${joined})`;
|
|
322
616
|
}
|
|
617
|
+
function groupJoiner(op) {
|
|
618
|
+
switch (op) {
|
|
619
|
+
case "and": return " and ";
|
|
620
|
+
case "or": return " or ";
|
|
621
|
+
default: throw new ExpressionError(`Unsupported group operator: ${String(op)}`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
323
624
|
function compileRule(rule) {
|
|
324
625
|
if (!matchesOperatorArity(rule)) return null;
|
|
325
626
|
return compileCondition({
|
|
@@ -342,8 +643,6 @@ function isConditionScalar(value) {
|
|
|
342
643
|
function isScalarArray(value) {
|
|
343
644
|
return isArray(value) && value.every((item) => isConditionScalar(item));
|
|
344
645
|
}
|
|
345
|
-
//#endregion
|
|
346
|
-
//#region src/condition/lift-tree.ts
|
|
347
646
|
const TWO_CHAR_PUNCTUATION = new Set([
|
|
348
647
|
"==",
|
|
349
648
|
"!=",
|
|
@@ -366,6 +665,10 @@ const IDENT_PART = /[\w$]/;
|
|
|
366
665
|
const DIGIT = /\d/;
|
|
367
666
|
const NUMBER_PATTERN = /^\d+(?:\.\d+)?(?:e[+-]?\d+)?/i;
|
|
368
667
|
const INTEGER_PATTERN = /^\d+$/;
|
|
668
|
+
function parseCanonicalNumber(text) {
|
|
669
|
+
const value = Number(text);
|
|
670
|
+
return String(value) === text && isZenRepresentableNumber(value) ? value : null;
|
|
671
|
+
}
|
|
369
672
|
const COMPARISON_OPERATORS = {
|
|
370
673
|
"==": "eq",
|
|
371
674
|
"!=": "ne",
|
|
@@ -379,7 +682,6 @@ const CALL_OPERATORS = {
|
|
|
379
682
|
startsWith: "starts_with",
|
|
380
683
|
endsWith: "ends_with"
|
|
381
684
|
};
|
|
382
|
-
const MAX_GROUP_DEPTH = 64;
|
|
383
685
|
function liftConditionTree(expression) {
|
|
384
686
|
const tokens = tokenize(expression);
|
|
385
687
|
if (tokens === null || tokens.length === 0) return null;
|
|
@@ -425,8 +727,10 @@ function liftConditionTree(expression) {
|
|
|
425
727
|
return token.value;
|
|
426
728
|
}
|
|
427
729
|
if (token.kind === "number") {
|
|
730
|
+
const value = parseCanonicalNumber(token.value);
|
|
731
|
+
if (value === null) return null;
|
|
428
732
|
pos += 1;
|
|
429
|
-
return
|
|
733
|
+
return value;
|
|
430
734
|
}
|
|
431
735
|
if (token.kind === "ident") {
|
|
432
736
|
if (token.value === "true") {
|
|
@@ -442,8 +746,10 @@ function liftConditionTree(expression) {
|
|
|
442
746
|
if (token.kind === "punct" && token.value === "-") {
|
|
443
747
|
const digits = peek(1);
|
|
444
748
|
if (digits === void 0 || digits.kind !== "number") return null;
|
|
749
|
+
const value = parseCanonicalNumber(digits.value);
|
|
750
|
+
if (value === null || value === 0 || !isZenRepresentableNumber(-value)) return null;
|
|
445
751
|
pos += 2;
|
|
446
|
-
return -
|
|
752
|
+
return -value;
|
|
447
753
|
}
|
|
448
754
|
return null;
|
|
449
755
|
}
|
|
@@ -472,6 +778,7 @@ function liftConditionTree(expression) {
|
|
|
472
778
|
if (right === null || !consumePunct(")")) return null;
|
|
473
779
|
return {
|
|
474
780
|
kind: "rule",
|
|
781
|
+
id: newConditionNodeId(),
|
|
475
782
|
left,
|
|
476
783
|
operator,
|
|
477
784
|
right
|
|
@@ -484,6 +791,7 @@ function liftConditionTree(expression) {
|
|
|
484
791
|
if (right === null || !consumePunct(")")) return null;
|
|
485
792
|
return {
|
|
486
793
|
kind: "rule",
|
|
794
|
+
id: newConditionNodeId(),
|
|
487
795
|
left,
|
|
488
796
|
operator: "not_in",
|
|
489
797
|
right
|
|
@@ -515,6 +823,7 @@ function liftConditionTree(expression) {
|
|
|
515
823
|
pos += expected.length;
|
|
516
824
|
return {
|
|
517
825
|
kind: "rule",
|
|
826
|
+
id: newConditionNodeId(),
|
|
518
827
|
left: read.path,
|
|
519
828
|
operator
|
|
520
829
|
};
|
|
@@ -550,6 +859,7 @@ function liftConditionTree(expression) {
|
|
|
550
859
|
const right = parseArray();
|
|
551
860
|
return right === null ? null : {
|
|
552
861
|
kind: "rule",
|
|
862
|
+
id: newConditionNodeId(),
|
|
553
863
|
left,
|
|
554
864
|
operator: "in",
|
|
555
865
|
right
|
|
@@ -562,6 +872,7 @@ function liftConditionTree(expression) {
|
|
|
562
872
|
const right = parseLiteral();
|
|
563
873
|
return right === null ? null : {
|
|
564
874
|
kind: "rule",
|
|
875
|
+
id: newConditionNodeId(),
|
|
565
876
|
left,
|
|
566
877
|
operator,
|
|
567
878
|
right
|
|
@@ -573,7 +884,7 @@ function liftConditionTree(expression) {
|
|
|
573
884
|
const emptiness = tryEmptiness();
|
|
574
885
|
if (emptiness !== null) return emptiness;
|
|
575
886
|
if (isPunct(peek(), "(")) {
|
|
576
|
-
if (depth >=
|
|
887
|
+
if (depth >= 64) return null;
|
|
577
888
|
pos += 1;
|
|
578
889
|
const inner = parseGroup(depth + 1);
|
|
579
890
|
if (inner === null || !consumePunct(")")) return null;
|
|
@@ -603,6 +914,7 @@ function liftConditionTree(expression) {
|
|
|
603
914
|
}
|
|
604
915
|
return items.length === 1 ? items[0] : {
|
|
605
916
|
kind: "group",
|
|
917
|
+
id: newConditionNodeId(),
|
|
606
918
|
op: op ?? "and",
|
|
607
919
|
items
|
|
608
920
|
};
|
|
@@ -641,6 +953,7 @@ function readPath(tokens, from) {
|
|
|
641
953
|
function asGroup(node) {
|
|
642
954
|
return node.kind === "group" ? node : {
|
|
643
955
|
kind: "group",
|
|
956
|
+
id: newConditionNodeId(),
|
|
644
957
|
op: "and",
|
|
645
958
|
items: [node]
|
|
646
959
|
};
|
|
@@ -708,11 +1021,6 @@ function tokenize(input) {
|
|
|
708
1021
|
}
|
|
709
1022
|
return tokens;
|
|
710
1023
|
}
|
|
711
|
-
//#endregion
|
|
712
|
-
//#region src/condition/tree-types.ts
|
|
713
|
-
const CONDITION_TREE_OPERATORS = CONDITION_OPERATORS;
|
|
714
|
-
//#endregion
|
|
715
|
-
//#region src/engine/evaluate.ts
|
|
716
1024
|
async function evaluate(expression, context) {
|
|
717
1025
|
await loadEngine();
|
|
718
1026
|
return evaluateSync(expression, context);
|
|
@@ -727,8 +1035,6 @@ function evaluateSync(expression, context) {
|
|
|
727
1035
|
function evaluateUnarySync(expression, context) {
|
|
728
1036
|
return getEngineSync().evaluateUnary(expression, context);
|
|
729
1037
|
}
|
|
730
|
-
//#endregion
|
|
731
|
-
//#region src/engine/messages.ts
|
|
732
1038
|
const COMPLETION_INFO_ZH = {
|
|
733
1039
|
"Returns the length of variable": "返回变量的长度",
|
|
734
1040
|
"Checks if variable contains a needle": "检查变量是否包含指定元素",
|
|
@@ -861,8 +1167,6 @@ function configureExpressionMessages({ locale, messages }) {
|
|
|
861
1167
|
function getExpressionMessages() {
|
|
862
1168
|
return activeMessages;
|
|
863
1169
|
}
|
|
864
|
-
//#endregion
|
|
865
|
-
//#region src/engine/intellisense.ts
|
|
866
1170
|
function parseOffset(text) {
|
|
867
1171
|
const trimmed = text?.trim();
|
|
868
1172
|
return trimmed ? Number(trimmed) : NaN;
|
|
@@ -881,7 +1185,9 @@ function normalizeDiagnostic(raw, source) {
|
|
|
881
1185
|
if (raw === null || raw === void 0) return null;
|
|
882
1186
|
const errorType = isRecord(raw) && typeof raw.type === "string" ? raw.type : void 0;
|
|
883
1187
|
const message = isRecord(raw) && typeof raw.source === "string" ? raw.source : String(raw);
|
|
884
|
-
const
|
|
1188
|
+
const bytePosition = extractPosition(message);
|
|
1189
|
+
const toUtf16 = utf8OffsetConverter(source);
|
|
1190
|
+
const [from, to] = bytePosition === null ? [0, source.length] : [toUtf16(bytePosition[0]), toUtf16(bytePosition[1])];
|
|
885
1191
|
return {
|
|
886
1192
|
from,
|
|
887
1193
|
to,
|
|
@@ -933,8 +1239,6 @@ async function satisfiesType(actual, expected) {
|
|
|
933
1239
|
function satisfiesTypeSync(actual, expected) {
|
|
934
1240
|
return getEngineSync().satisfies(actual, expected);
|
|
935
1241
|
}
|
|
936
|
-
//#endregion
|
|
937
|
-
//#region src/engine/template.ts
|
|
938
1242
|
const HOLE_PATTERN = /\{\{(?<expression>[^{}]*)\}\}/g;
|
|
939
1243
|
function parseTemplateHoles(source) {
|
|
940
1244
|
const holes = [];
|
|
@@ -995,11 +1299,11 @@ async function getTemplateDiagnostics(source) {
|
|
|
995
1299
|
await loadEngine();
|
|
996
1300
|
return getTemplateDiagnosticsSync(source);
|
|
997
1301
|
}
|
|
998
|
-
//#endregion
|
|
999
1302
|
exports.CONDITION_OPERATORS = CONDITION_OPERATORS;
|
|
1000
1303
|
exports.CONDITION_TREE_OPERATORS = CONDITION_TREE_OPERATORS;
|
|
1001
1304
|
exports.ExpressionError = ExpressionError;
|
|
1002
1305
|
exports.ExpressionNotReadyError = ExpressionNotReadyError;
|
|
1306
|
+
exports.MAX_CONDITION_TREE_DEPTH = MAX_CONDITION_TREE_DEPTH;
|
|
1003
1307
|
exports.analyzeTemplate = analyzeTemplate;
|
|
1004
1308
|
exports.analyzeTemplateSync = analyzeTemplateSync;
|
|
1005
1309
|
exports.analyzeTypes = analyzeTypes;
|
|
@@ -1011,7 +1315,9 @@ exports.compileGroup = compileGroup;
|
|
|
1011
1315
|
exports.conditionOperatorArity = conditionOperatorArity;
|
|
1012
1316
|
exports.configureEngine = configureEngine;
|
|
1013
1317
|
exports.configureExpressionMessages = configureExpressionMessages;
|
|
1318
|
+
exports.emptyConditionGroup = emptyConditionGroup;
|
|
1014
1319
|
exports.enMessages = enMessages;
|
|
1320
|
+
exports.ensureConditionNodeIds = ensureConditionNodeIds;
|
|
1015
1321
|
exports.evaluate = evaluate;
|
|
1016
1322
|
exports.evaluateSync = evaluateSync;
|
|
1017
1323
|
exports.evaluateUnary = evaluateUnary;
|
|
@@ -1026,8 +1332,10 @@ exports.getExpressionMessages = getExpressionMessages;
|
|
|
1026
1332
|
exports.getTemplateDiagnostics = getTemplateDiagnostics;
|
|
1027
1333
|
exports.getTemplateDiagnosticsSync = getTemplateDiagnosticsSync;
|
|
1028
1334
|
exports.isEngineReady = isEngineReady;
|
|
1335
|
+
exports.isZenRepresentableNumber = isZenRepresentableNumber;
|
|
1029
1336
|
exports.liftConditionTree = liftConditionTree;
|
|
1030
1337
|
exports.loadEngine = loadEngine;
|
|
1338
|
+
exports.newConditionNodeId = newConditionNodeId;
|
|
1031
1339
|
exports.parseTemplateHoles = parseTemplateHoles;
|
|
1032
1340
|
exports.registerExpressionLocale = registerExpressionLocale;
|
|
1033
1341
|
exports.resetEngine = resetEngine;
|