@coldsmirk/abacus-core 0.4.1 → 0.6.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/dist/index.js CHANGED
@@ -1,4 +1,3 @@
1
- //#region src/engine/errors.ts
2
1
  var ExpressionError = class extends Error {
3
2
  expression;
4
3
  constructor(message, expression, cause) {
@@ -13,8 +12,42 @@ var ExpressionNotReadyError = class extends ExpressionError {
13
12
  this.name = "ExpressionNotReadyError";
14
13
  }
15
14
  };
16
- //#endregion
17
- //#region src/internal/predicates.ts
15
+ function isAscii(source) {
16
+ for (let index = 0; index < source.length; index += 1) if (source.codePointAt(index) > 127) return false;
17
+ return true;
18
+ }
19
+ function utf8ByteLength(codePoint) {
20
+ if (codePoint < 128) return 1;
21
+ if (codePoint < 2048) return 2;
22
+ return codePoint < 65536 ? 3 : 4;
23
+ }
24
+ function utf8OffsetConverter(source) {
25
+ if (isAscii(source)) return (byteOffset) => Math.max(0, Math.min(byteOffset, source.length));
26
+ const byteStarts = [];
27
+ const unitStarts = [];
28
+ let bytes = 0;
29
+ let units = 0;
30
+ for (const char of source) {
31
+ byteStarts.push(bytes);
32
+ unitStarts.push(units);
33
+ bytes += utf8ByteLength(char.codePointAt(0));
34
+ units += char.length;
35
+ }
36
+ byteStarts.push(bytes);
37
+ unitStarts.push(units);
38
+ return (byteOffset) => {
39
+ if (byteOffset <= 0) return 0;
40
+ if (byteOffset >= bytes) return source.length;
41
+ let low = 0;
42
+ let high = byteStarts.length - 1;
43
+ while (low < high) {
44
+ const middle = low + high + 1 >> 1;
45
+ if (byteStarts[middle] <= byteOffset) low = middle;
46
+ else high = middle - 1;
47
+ }
48
+ return unitStarts[low];
49
+ };
50
+ }
18
51
  function isUndefined(value) {
19
52
  return value === void 0;
20
53
  }
@@ -30,13 +63,13 @@ function isNullish(value) {
30
63
  function isRecord(value) {
31
64
  return typeof value === "object" && value !== null && !Array.isArray(value);
32
65
  }
33
- //#endregion
34
- //#region src/engine/loader.ts
35
66
  let enginePromise = null;
36
67
  let engineSync = null;
37
68
  let engineError = null;
38
69
  let configuredInput;
39
70
  let typeContextCache = null;
71
+ let loadEpoch = 0;
72
+ let lastLoad = null;
40
73
  function evaluateSafely(expression, run) {
41
74
  try {
42
75
  return run();
@@ -56,9 +89,13 @@ function configureEngine(options) {
56
89
  function loadEngine() {
57
90
  if (enginePromise) return enginePromise;
58
91
  engineError = null;
59
- enginePromise = (async () => {
92
+ const epoch = loadEpoch;
93
+ const predecessor = lastLoad;
94
+ const input = configuredInput;
95
+ const load = (async () => {
96
+ if (predecessor) await predecessor.catch(() => void 0);
60
97
  const zen = await import("@gorules/zen-engine-wasm");
61
- await zen.default(isUndefined(configuredInput) ? void 0 : { module_or_path: configuredInput });
98
+ await zen.default(isUndefined(input) ? void 0 : { module_or_path: input });
62
99
  const toVariableType = (type) => zen.VariableType.fromJson(type);
63
100
  const ensureTypeContext = (variables) => {
64
101
  if (typeContextCache && typeContextCache.variables === variables) return typeContextCache;
@@ -87,9 +124,19 @@ function loadEngine() {
87
124
  analyze: (variables, source, unary) => {
88
125
  const context = ensureTypeContext(variables);
89
126
  const rawSpans = unary ? context.handle.typeCheckUnary(source) : context.handle.typeCheck(source);
127
+ if (!Array.isArray(rawSpans)) return {
128
+ rootKind: context.rootKind,
129
+ spans: []
130
+ };
131
+ const toUtf16 = utf8OffsetConverter(source);
90
132
  return {
91
133
  rootKind: context.rootKind,
92
- spans: Array.isArray(rawSpans) ? rawSpans : []
134
+ spans: rawSpans.map((span) => {
135
+ return {
136
+ ...span,
137
+ span: [toUtf16(span.span[0]), toUtf16(span.span[1])]
138
+ };
139
+ })
93
140
  };
94
141
  },
95
142
  satisfies: (actual, expected) => {
@@ -107,12 +154,17 @@ function loadEngine() {
107
154
  },
108
155
  isReady: () => zen.isReady()
109
156
  });
110
- engineSync = engine;
157
+ if (epoch === loadEpoch) engineSync = engine;
111
158
  return engine;
112
- })().catch((error) => {
113
- enginePromise = null;
114
- engineError = new ExpressionError(loadFailureMessage(), void 0, error);
115
- throw engineError;
159
+ })();
160
+ lastLoad = load;
161
+ enginePromise = load.catch((error) => {
162
+ const failure = new ExpressionError(loadFailureMessage(), void 0, error);
163
+ if (epoch === loadEpoch) {
164
+ enginePromise = null;
165
+ engineError = failure;
166
+ }
167
+ throw failure;
116
168
  });
117
169
  return enginePromise;
118
170
  }
@@ -127,6 +179,7 @@ function getEngineSync() {
127
179
  return engineSync;
128
180
  }
129
181
  function resetEngine() {
182
+ loadEpoch += 1;
130
183
  typeContextCache?.handle.free();
131
184
  typeContextCache = null;
132
185
  enginePromise = null;
@@ -134,17 +187,180 @@ function resetEngine() {
134
187
  engineError = null;
135
188
  configuredInput = void 0;
136
189
  }
137
- //#endregion
138
- //#region src/internal/env.ts
139
190
  const isDev = detectDev();
140
191
  function detectDev() {
141
192
  if (typeof process === "undefined") return false;
142
193
  return process.env ? process.env.NODE_ENV !== "production" : false;
143
194
  }
144
- //#endregion
145
- //#region src/condition/subject.ts
195
+ const U64_MAX = 18446744073709551615n;
196
+ const I64_MIN_MAGNITUDE = 9223372036854775808n;
197
+ const ZEN_DECIMAL_MANTISSA_LIMIT = 2n ** 96n;
198
+ const ZEN_DECIMAL_MANTISSA_LIMIT_TEXT = String(ZEN_DECIMAL_MANTISSA_LIMIT);
199
+ const DECIMAL_SIGN_BIT = 2n ** 95n;
200
+ const EXCESS_PRECISION_LIMIT = 2n ** 52n;
201
+ const NUMBER_TEXT_PATTERN = /^(?<integer>\d+)(?:\.(?<fraction>\d+))?(?:e(?<exponent>[+-]\d+))?$/;
202
+ const UNSIGNED_INTEGER_TEXT_PATTERN = /^\d+$/;
203
+ function isZenUnsignedIntegerText(value) {
204
+ if (!UNSIGNED_INTEGER_TEXT_PATTERN.test(value)) return false;
205
+ const normalized = value.replace(/^0+(?=\d)/, "");
206
+ return normalized.length < ZEN_DECIMAL_MANTISSA_LIMIT_TEXT.length || normalized.length === ZEN_DECIMAL_MANTISSA_LIMIT_TEXT.length && normalized < ZEN_DECIMAL_MANTISSA_LIMIT_TEXT;
207
+ }
208
+ function powerOfTen(exponent) {
209
+ return Number(`1e${exponent}`);
210
+ }
211
+ function normalize(digits, exponent) {
212
+ let d = digits;
213
+ let e = exponent;
214
+ while (d !== 0n && d % 10n === 0n) {
215
+ d /= 10n;
216
+ e += 1;
217
+ }
218
+ return d === 0n ? {
219
+ digits: 0n,
220
+ exponent: 0
221
+ } : {
222
+ digits: d,
223
+ exponent: e
224
+ };
225
+ }
226
+ function parseAsSerdeJson(text) {
227
+ const match = NUMBER_TEXT_PATTERN.exec(text);
228
+ if (match?.groups === void 0) return null;
229
+ const { integer, fraction, exponent } = match.groups;
230
+ let significand = 0n;
231
+ let scale = 0;
232
+ let overflowed = false;
233
+ for (const digit of integer) {
234
+ if (!overflowed && significand * 10n + BigInt(digit) > U64_MAX) overflowed = true;
235
+ if (overflowed) scale += 1;
236
+ else significand = significand * 10n + BigInt(digit);
237
+ }
238
+ if (fraction === void 0 && exponent === void 0 && !overflowed) return {
239
+ kind: "int",
240
+ int: significand,
241
+ f64: 0
242
+ };
243
+ if (fraction !== void 0) for (const digit of fraction) {
244
+ if (overflowed || significand * 10n + BigInt(digit) > U64_MAX) break;
245
+ significand = significand * 10n + BigInt(digit);
246
+ scale -= 1;
247
+ }
248
+ if (exponent !== void 0) scale += Number(exponent);
249
+ let value = Number(significand);
250
+ let remaining = scale;
251
+ for (;;) {
252
+ if (Math.abs(remaining) <= 308) {
253
+ value = remaining >= 0 ? value * powerOfTen(remaining) : value / powerOfTen(-remaining);
254
+ break;
255
+ }
256
+ if (value === 0) break;
257
+ if (remaining >= 0) return null;
258
+ value /= 1e308;
259
+ remaining += 308;
260
+ }
261
+ return Number.isFinite(value) ? {
262
+ kind: "f64",
263
+ int: 0n,
264
+ f64: value
265
+ } : null;
266
+ }
267
+ function decimalFromF64(value) {
268
+ if (value === 0) return {
269
+ digits: 0n,
270
+ exponent: 0
271
+ };
272
+ const view = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(8));
273
+ view.setFloat64(0, value);
274
+ const raw = view.getBigUint64(0);
275
+ const biasedExponent = Number(raw >> 52n & 2047n);
276
+ const fractionBits = raw & 4503599627370495n;
277
+ let bits = biasedExponent === 0 ? fractionBits : fractionBits | 1n << 52n;
278
+ const exponent2 = (biasedExponent === 0 ? 1 : biasedExponent) - 1023 - 52;
279
+ let exponent5 = -exponent2;
280
+ let exponent10 = exponent2;
281
+ while (exponent5 > 0) if ((bits & 1n) === 0n) {
282
+ exponent10 += 1;
283
+ exponent5 -= 1;
284
+ bits >>= 1n;
285
+ } else {
286
+ exponent5 -= 1;
287
+ const timesFive = bits * 5n;
288
+ if (timesFive < ZEN_DECIMAL_MANTISSA_LIMIT) bits = timesFive;
289
+ else {
290
+ exponent10 += 1;
291
+ bits >>= 1n;
292
+ }
293
+ }
294
+ while (exponent5 < 0) if ((bits & DECIMAL_SIGN_BIT) === 0n) {
295
+ exponent10 -= 1;
296
+ exponent5 += 1;
297
+ bits <<= 1n;
298
+ } else if (exponent10 * 2 > -exponent5) return null;
299
+ else {
300
+ exponent5 += 1;
301
+ bits /= 5n;
302
+ }
303
+ while (exponent10 > 0) {
304
+ const timesTen = bits * 10n;
305
+ if (timesTen >= ZEN_DECIMAL_MANTISSA_LIMIT) return null;
306
+ bits = timesTen;
307
+ exponent10 -= 1;
308
+ }
309
+ while (exponent10 < -28) {
310
+ const remainder = bits % 10n;
311
+ bits /= 10n;
312
+ exponent10 += 1;
313
+ if (bits === 0n) exponent10 = 0;
314
+ else if (remainder >= 5n) bits += 1n;
315
+ }
316
+ while (exponent10 < 0 && bits >= EXCESS_PRECISION_LIMIT) {
317
+ const remainder = bits % 10n;
318
+ bits /= 10n;
319
+ exponent10 += 1;
320
+ if (remainder >= 5n) bits += 1n;
321
+ }
322
+ while (exponent10 < 0 && bits % 10n === 0n) {
323
+ bits /= 10n;
324
+ exponent10 += 1;
325
+ }
326
+ return {
327
+ digits: bits,
328
+ exponent: exponent10
329
+ };
330
+ }
331
+ function contextDecimal(value) {
332
+ const magnitude = Math.abs(value);
333
+ if (magnitude === 0) return {
334
+ digits: 0n,
335
+ exponent: 0
336
+ };
337
+ const parsed = parseAsSerdeJson(String(magnitude));
338
+ if (parsed === null) return null;
339
+ if (parsed.kind === "int") {
340
+ if (value >= 0 || parsed.int <= I64_MIN_MAGNITUDE) return normalize(parsed.int, 0);
341
+ const viaF64 = decimalFromF64(Number(parsed.int));
342
+ return viaF64 === null ? null : normalize(viaF64.digits, viaF64.exponent);
343
+ }
344
+ const converted = decimalFromF64(parsed.f64);
345
+ return converted === null ? null : normalize(converted.digits, converted.exponent);
346
+ }
347
+ function literalDecimal(value) {
348
+ const match = NUMBER_TEXT_PATTERN.exec(String(Math.abs(value)));
349
+ if (match?.groups === void 0) return null;
350
+ const { integer, fraction = "", exponent = "0" } = match.groups;
351
+ return normalize(BigInt(integer + fraction), Number(exponent) - fraction.length);
352
+ }
353
+ function isZenConsistentNumber(value) {
354
+ if (!Number.isFinite(value)) return false;
355
+ const literal = literalDecimal(value);
356
+ if (literal === null || literal.digits >= ZEN_DECIMAL_MANTISSA_LIMIT) return false;
357
+ const context = contextDecimal(value);
358
+ return context !== null && literal.digits === context.digits && literal.exponent === context.exponent;
359
+ }
146
360
  const SUBJECT_PATTERN = /^[A-Z_$][\w$]*(?:\.[A-Z_$][\w$]*|\[\d+\])*$/i;
147
- const ZEN_RESERVED_WORDS = new Set([
361
+ const SUBJECT_IDENTIFIER_PATTERN = /[A-Z_$][\w$]*/gi;
362
+ const SUBJECT_INDEX_PATTERN = /\[(?<index>\d+)\]/g;
363
+ const ZEN_ROOT_RESERVED_WORDS = new Set([
148
364
  "and",
149
365
  "or",
150
366
  "not",
@@ -153,18 +369,28 @@ const ZEN_RESERVED_WORDS = new Set([
153
369
  "false",
154
370
  "null"
155
371
  ]);
372
+ const ZEN_MEMBER_RESERVED_WORDS = new Set(["true", "false"]);
156
373
  function isIdentifierPath(subject) {
157
- return SUBJECT_PATTERN.test(subject) && (subject.match(/[A-Z_$][\w$]*/gi) ?? []).every((segment) => !ZEN_RESERVED_WORDS.has(segment));
374
+ if (!SUBJECT_PATTERN.test(subject)) return false;
375
+ const [root, ...members] = subject.match(SUBJECT_IDENTIFIER_PATTERN) ?? [];
376
+ if (root === void 0 || ZEN_ROOT_RESERVED_WORDS.has(root) || members.some((member) => ZEN_MEMBER_RESERVED_WORDS.has(member))) return false;
377
+ for (const match of subject.matchAll(SUBJECT_INDEX_PATTERN)) if (match.groups?.index === void 0 || !isZenUnsignedIntegerText(match.groups.index)) return false;
378
+ return true;
379
+ }
380
+ function isZenRepresentableNumber(value) {
381
+ return isZenConsistentNumber(value);
158
382
  }
159
- //#endregion
160
- //#region src/condition/compile.ts
161
383
  function toZenLiteral(value) {
162
384
  if (isNullish(value)) return "null";
163
385
  if (typeof value === "number") {
164
- if (!Number.isFinite(value)) throw new ExpressionError(`Number ${String(value)} has no ZEN literal representation`);
386
+ if (!isZenRepresentableNumber(value)) throw new ExpressionError(`Number ${String(value)} has no faithful ZEN literal representation`);
387
+ return String(value);
388
+ }
389
+ if (typeof value === "bigint") {
390
+ if (value >= ZEN_DECIMAL_MANTISSA_LIMIT || -value >= ZEN_DECIMAL_MANTISSA_LIMIT) throw new ExpressionError(`Bigint ${String(value)} has no ZEN literal representation`);
165
391
  return String(value);
166
392
  }
167
- if (typeof value === "boolean" || typeof value === "bigint") return String(value);
393
+ if (typeof value === "boolean") return String(value);
168
394
  if (isString(value)) return encodeZenString(value);
169
395
  if (isArray(value)) return `[${value.map((item) => toZenLiteral(item)).join(", ")}]`;
170
396
  throw new ExpressionError(`Value of type "${typeof value}" has no ZEN literal representation`);
@@ -172,14 +398,17 @@ function toZenLiteral(value) {
172
398
  function encodeZenString(value) {
173
399
  const hasSingle = value.includes("'");
174
400
  const hasDouble = value.includes("\"");
175
- if (hasSingle && hasDouble) throw new ExpressionError("String contains both single and double quotes and has no ZEN literal representation");
176
- return hasSingle ? `"${value}"` : `'${value}'`;
401
+ const hasBacktick = value.includes("`");
402
+ if (!hasSingle) return `'${value}'`;
403
+ if (!hasDouble) return `"${value}"`;
404
+ if (!hasBacktick) return `\`${value}\``;
405
+ throw new ExpressionError("String contains every ZEN raw-string delimiter and has no literal representation");
177
406
  }
178
407
  function toArrayLiteral(value) {
179
408
  return isArray(value) ? toZenLiteral(value) : `[${toZenLiteral(value)}]`;
180
409
  }
181
410
  function zenIsEmpty(subject) {
182
- return `(${subject} == null or (type(${subject}) == 'string' and len(trim(${subject})) == 0) or (type(${subject}) == 'array' and len(${subject}) == 0))`;
411
+ return `(${subject} == null or (type(${subject}) == 'string' and len(trim(${subject})) == 0) or (type(${subject}) == 'array' and len(${subject}) == 0) or (type(${subject}) == 'object' and len(keys(${subject})) == 0))`;
183
412
  }
184
413
  function compileFieldCondition(subject, operator, value) {
185
414
  switch (operator) {
@@ -260,8 +489,6 @@ function reportEvaluationFailure(engine, expression, error) {
260
489
  async function selectBranch(branches, context) {
261
490
  return selectBranchWith(branches, context, await loadEngine());
262
491
  }
263
- //#endregion
264
- //#region src/condition/types.ts
265
492
  const CONDITION_OPERATORS = [
266
493
  "eq",
267
494
  "ne",
@@ -297,9 +524,83 @@ const CONDITION_OPERATOR_ARITIES = {
297
524
  function conditionOperatorArity(operator) {
298
525
  return CONDITION_OPERATOR_ARITIES[operator];
299
526
  }
300
- //#endregion
301
- //#region src/condition/compile-tree.ts
527
+ const MAX_CONDITION_TREE_DEPTH = 64;
528
+ const CONDITION_TREE_OPERATORS = CONDITION_OPERATORS;
529
+ const nodeIdRealm = Math.random().toString(36).slice(2, 7);
530
+ let nodeIdCounter = 0;
531
+ function newConditionNodeId() {
532
+ nodeIdCounter += 1;
533
+ return `cn-${nodeIdRealm}-${nodeIdCounter}`;
534
+ }
535
+ function emptyConditionGroup() {
536
+ return {
537
+ kind: "group",
538
+ id: newConditionNodeId(),
539
+ op: "and",
540
+ items: []
541
+ };
542
+ }
543
+ function assertConditionTreeStructure(tree) {
544
+ if (!isRecord(tree) || tree.kind !== "group") throw new ExpressionError("Condition tree root must be a group");
545
+ const ancestors = /* @__PURE__ */ new Set();
546
+ const stack = [{
547
+ group: tree,
548
+ depth: 0,
549
+ leaving: false
550
+ }];
551
+ while (stack.length > 0) {
552
+ const frame = stack.pop();
553
+ const { group } = frame;
554
+ if (frame.leaving) {
555
+ ancestors.delete(group);
556
+ continue;
557
+ }
558
+ if (frame.depth > 64) throw new ExpressionError(`Condition tree exceeds the maximum depth of 64`);
559
+ if (ancestors.has(group)) throw new ExpressionError("Condition tree contains a cycle");
560
+ if (group.op !== "and" && group.op !== "or") throw new ExpressionError(`Unsupported group operator: ${String(group.op)}`);
561
+ if (!isArray(group.items)) throw new ExpressionError("Condition tree group items must be an array");
562
+ ancestors.add(group);
563
+ stack.push({
564
+ ...frame,
565
+ leaving: true
566
+ });
567
+ for (let index = group.items.length - 1; index >= 0; index -= 1) {
568
+ const node = group.items[index];
569
+ if (!isRecord(node) || node.kind !== "group" && node.kind !== "rule") throw new ExpressionError("Condition tree contains an invalid node");
570
+ if (node.kind === "group") stack.push({
571
+ group: node,
572
+ depth: frame.depth + 1,
573
+ leaving: false
574
+ });
575
+ }
576
+ }
577
+ }
578
+ function withRuleId(rule) {
579
+ return rule.id === void 0 ? {
580
+ ...rule,
581
+ id: newConditionNodeId()
582
+ } : rule;
583
+ }
584
+ function withGroupIds(group) {
585
+ const items = group.items.map((item) => item.kind === "group" ? withGroupIds(item) : withRuleId(item));
586
+ if (group.id !== void 0 && items.every((item, index) => item === group.items[index])) return group;
587
+ return {
588
+ ...group,
589
+ id: group.id ?? newConditionNodeId(),
590
+ items
591
+ };
592
+ }
593
+ const normalizedTrees = /* @__PURE__ */ new WeakMap();
594
+ function ensureConditionNodeIds(tree) {
595
+ assertConditionTreeStructure(tree);
596
+ const cached = normalizedTrees.get(tree);
597
+ if (cached) return cached;
598
+ const normalized = withGroupIds(tree);
599
+ normalizedTrees.set(tree, normalized);
600
+ return normalized;
601
+ }
302
602
  function compileConditionTree(tree) {
603
+ assertConditionTreeStructure(tree);
303
604
  const normalized = normalizeNode(tree);
304
605
  return normalized === null ? "" : emitNode(normalized, true);
305
606
  }
@@ -316,9 +617,16 @@ function normalizeNode(node) {
316
617
  }
317
618
  function emitNode(node, topLevel) {
318
619
  if (node.kind === "rule") return compileRule(node);
319
- const joined = node.items.map((item) => emitNode(item, false)).join(node.op === "and" ? " and " : " or ");
620
+ const joined = node.items.map((item) => emitNode(item, false)).join(groupJoiner(node.op));
320
621
  return topLevel ? joined : `(${joined})`;
321
622
  }
623
+ function groupJoiner(op) {
624
+ switch (op) {
625
+ case "and": return " and ";
626
+ case "or": return " or ";
627
+ default: throw new ExpressionError(`Unsupported group operator: ${String(op)}`);
628
+ }
629
+ }
322
630
  function compileRule(rule) {
323
631
  if (!matchesOperatorArity(rule)) return null;
324
632
  return compileCondition({
@@ -341,8 +649,6 @@ function isConditionScalar(value) {
341
649
  function isScalarArray(value) {
342
650
  return isArray(value) && value.every((item) => isConditionScalar(item));
343
651
  }
344
- //#endregion
345
- //#region src/condition/lift-tree.ts
346
652
  const TWO_CHAR_PUNCTUATION = new Set([
347
653
  "==",
348
654
  "!=",
@@ -365,6 +671,10 @@ const IDENT_PART = /[\w$]/;
365
671
  const DIGIT = /\d/;
366
672
  const NUMBER_PATTERN = /^\d+(?:\.\d+)?(?:e[+-]?\d+)?/i;
367
673
  const INTEGER_PATTERN = /^\d+$/;
674
+ function parseCanonicalNumber(text) {
675
+ const value = Number(text);
676
+ return String(value) === text && isZenRepresentableNumber(value) ? value : null;
677
+ }
368
678
  const COMPARISON_OPERATORS = {
369
679
  "==": "eq",
370
680
  "!=": "ne",
@@ -378,7 +688,6 @@ const CALL_OPERATORS = {
378
688
  startsWith: "starts_with",
379
689
  endsWith: "ends_with"
380
690
  };
381
- const MAX_GROUP_DEPTH = 64;
382
691
  function liftConditionTree(expression) {
383
692
  const tokens = tokenize(expression);
384
693
  if (tokens === null || tokens.length === 0) return null;
@@ -424,8 +733,10 @@ function liftConditionTree(expression) {
424
733
  return token.value;
425
734
  }
426
735
  if (token.kind === "number") {
736
+ const value = parseCanonicalNumber(token.value);
737
+ if (value === null) return null;
427
738
  pos += 1;
428
- return Number(token.value);
739
+ return value;
429
740
  }
430
741
  if (token.kind === "ident") {
431
742
  if (token.value === "true") {
@@ -441,8 +752,10 @@ function liftConditionTree(expression) {
441
752
  if (token.kind === "punct" && token.value === "-") {
442
753
  const digits = peek(1);
443
754
  if (digits === void 0 || digits.kind !== "number") return null;
755
+ const value = parseCanonicalNumber(digits.value);
756
+ if (value === null || value === 0 || !isZenRepresentableNumber(-value)) return null;
444
757
  pos += 2;
445
- return -Number(digits.value);
758
+ return -value;
446
759
  }
447
760
  return null;
448
761
  }
@@ -471,6 +784,7 @@ function liftConditionTree(expression) {
471
784
  if (right === null || !consumePunct(")")) return null;
472
785
  return {
473
786
  kind: "rule",
787
+ id: newConditionNodeId(),
474
788
  left,
475
789
  operator,
476
790
  right
@@ -483,6 +797,7 @@ function liftConditionTree(expression) {
483
797
  if (right === null || !consumePunct(")")) return null;
484
798
  return {
485
799
  kind: "rule",
800
+ id: newConditionNodeId(),
486
801
  left,
487
802
  operator: "not_in",
488
803
  right
@@ -514,6 +829,7 @@ function liftConditionTree(expression) {
514
829
  pos += expected.length;
515
830
  return {
516
831
  kind: "rule",
832
+ id: newConditionNodeId(),
517
833
  left: read.path,
518
834
  operator
519
835
  };
@@ -549,6 +865,7 @@ function liftConditionTree(expression) {
549
865
  const right = parseArray();
550
866
  return right === null ? null : {
551
867
  kind: "rule",
868
+ id: newConditionNodeId(),
552
869
  left,
553
870
  operator: "in",
554
871
  right
@@ -561,6 +878,7 @@ function liftConditionTree(expression) {
561
878
  const right = parseLiteral();
562
879
  return right === null ? null : {
563
880
  kind: "rule",
881
+ id: newConditionNodeId(),
564
882
  left,
565
883
  operator,
566
884
  right
@@ -572,7 +890,7 @@ function liftConditionTree(expression) {
572
890
  const emptiness = tryEmptiness();
573
891
  if (emptiness !== null) return emptiness;
574
892
  if (isPunct(peek(), "(")) {
575
- if (depth >= MAX_GROUP_DEPTH) return null;
893
+ if (depth >= 64) return null;
576
894
  pos += 1;
577
895
  const inner = parseGroup(depth + 1);
578
896
  if (inner === null || !consumePunct(")")) return null;
@@ -602,6 +920,7 @@ function liftConditionTree(expression) {
602
920
  }
603
921
  return items.length === 1 ? items[0] : {
604
922
  kind: "group",
923
+ id: newConditionNodeId(),
605
924
  op: op ?? "and",
606
925
  items
607
926
  };
@@ -640,6 +959,7 @@ function readPath(tokens, from) {
640
959
  function asGroup(node) {
641
960
  return node.kind === "group" ? node : {
642
961
  kind: "group",
962
+ id: newConditionNodeId(),
643
963
  op: "and",
644
964
  items: [node]
645
965
  };
@@ -656,7 +976,7 @@ function tokenize(input) {
656
976
  index += 1;
657
977
  continue;
658
978
  }
659
- if (char === "'" || char === "\"") {
979
+ if (char === "'" || char === "\"" || char === "`") {
660
980
  const end = input.indexOf(char, index + 1);
661
981
  if (end === -1) return null;
662
982
  tokens.push({
@@ -707,11 +1027,6 @@ function tokenize(input) {
707
1027
  }
708
1028
  return tokens;
709
1029
  }
710
- //#endregion
711
- //#region src/condition/tree-types.ts
712
- const CONDITION_TREE_OPERATORS = CONDITION_OPERATORS;
713
- //#endregion
714
- //#region src/engine/evaluate.ts
715
1030
  async function evaluate(expression, context) {
716
1031
  await loadEngine();
717
1032
  return evaluateSync(expression, context);
@@ -726,8 +1041,6 @@ function evaluateSync(expression, context) {
726
1041
  function evaluateUnarySync(expression, context) {
727
1042
  return getEngineSync().evaluateUnary(expression, context);
728
1043
  }
729
- //#endregion
730
- //#region src/engine/messages.ts
731
1044
  const COMPLETION_INFO_ZH = {
732
1045
  "Returns the length of variable": "返回变量的长度",
733
1046
  "Checks if variable contains a needle": "检查变量是否包含指定元素",
@@ -846,41 +1159,53 @@ const zhCNMessages = {
846
1159
  expectedType: (expectedType, actualType) => `期望 \`${expectedType}\`,实际为 \`${actualType}\`。`
847
1160
  };
848
1161
  const localeRegistry = new Map([["en-US", enMessages], ["zh-CN", zhCNMessages]]);
1162
+ let activeBaseMessages = enMessages;
849
1163
  let activeMessages = enMessages;
1164
+ const messageListeners = /* @__PURE__ */ new Set();
850
1165
  function registerExpressionLocale(locale, messages) {
851
1166
  localeRegistry.set(locale, messages);
852
1167
  }
853
1168
  function configureExpressionMessages({ locale, messages }) {
854
- const base = locale === void 0 ? activeMessages : localeRegistry.get(locale) ?? activeMessages;
855
- activeMessages = messages === void 0 ? base : {
1169
+ const base = locale === void 0 ? activeBaseMessages : localeRegistry.get(locale) ?? activeBaseMessages;
1170
+ const nextMessages = messages === void 0 ? base : {
856
1171
  ...base,
857
1172
  ...messages
858
1173
  };
1174
+ activeBaseMessages = base;
1175
+ if (nextMessages === activeMessages) return;
1176
+ activeMessages = nextMessages;
1177
+ for (const listener of messageListeners) listener(activeMessages);
859
1178
  }
860
1179
  function getExpressionMessages() {
861
1180
  return activeMessages;
862
1181
  }
863
- //#endregion
864
- //#region src/engine/intellisense.ts
1182
+ function subscribeExpressionMessages(listener) {
1183
+ messageListeners.add(listener);
1184
+ return () => {
1185
+ messageListeners.delete(listener);
1186
+ };
1187
+ }
865
1188
  function parseOffset(text) {
866
1189
  const trimmed = text?.trim();
867
1190
  return trimmed ? Number(trimmed) : NaN;
868
1191
  }
869
1192
  function extractPosition(message) {
870
- const segments = message.split(" at ");
871
- const last = segments.length <= 1 ? void 0 : segments.at(-1);
872
- if (last === void 0) return null;
873
- const [left, right] = last.replace("(", "").replace(")", "").split(", ");
874
- const from = parseOffset(left);
875
- if (Number.isNaN(from)) return null;
876
- const to = parseOffset(right);
877
- return [from, Number.isNaN(to) ? from : to];
1193
+ const positionPattern = / at (?:\(\s*(?<rangeFrom>\d+)\s*,\s*(?<rangeTo>\d+)\s*\)|(?<point>\d+))(?=\s*(?:;|$))/g;
1194
+ let position = null;
1195
+ for (const match of message.matchAll(positionPattern)) {
1196
+ const from = parseOffset(match.groups?.rangeFrom ?? match.groups?.point);
1197
+ const to = parseOffset(match.groups?.rangeTo);
1198
+ position = [from, Number.isNaN(to) ? from : to];
1199
+ }
1200
+ return position;
878
1201
  }
879
1202
  function normalizeDiagnostic(raw, source) {
880
1203
  if (raw === null || raw === void 0) return null;
881
1204
  const errorType = isRecord(raw) && typeof raw.type === "string" ? raw.type : void 0;
882
1205
  const message = isRecord(raw) && typeof raw.source === "string" ? raw.source : String(raw);
883
- const [from, to] = extractPosition(message) ?? [0, source.length];
1206
+ const bytePosition = extractPosition(message);
1207
+ const toUtf16 = utf8OffsetConverter(source);
1208
+ const [from, to] = bytePosition === null ? [0, source.length] : [toUtf16(bytePosition[0]), toUtf16(bytePosition[1])];
884
1209
  return {
885
1210
  from,
886
1211
  to,
@@ -932,8 +1257,6 @@ async function satisfiesType(actual, expected) {
932
1257
  function satisfiesTypeSync(actual, expected) {
933
1258
  return getEngineSync().satisfies(actual, expected);
934
1259
  }
935
- //#endregion
936
- //#region src/engine/template.ts
937
1260
  const HOLE_PATTERN = /\{\{(?<expression>[^{}]*)\}\}/g;
938
1261
  function parseTemplateHoles(source) {
939
1262
  const holes = [];
@@ -994,5 +1317,293 @@ async function getTemplateDiagnostics(source) {
994
1317
  await loadEngine();
995
1318
  return getTemplateDiagnosticsSync(source);
996
1319
  }
997
- //#endregion
998
- export { CONDITION_OPERATORS, CONDITION_TREE_OPERATORS, ExpressionError, ExpressionNotReadyError, analyzeTemplate, analyzeTemplateSync, analyzeTypes, analyzeTypesSync, compileBranch, compileCondition, compileConditionTree, compileGroup, conditionOperatorArity, configureEngine, configureExpressionMessages, enMessages, evaluate, evaluateSync, evaluateUnary, evaluateUnarySync, getCompletionItems, getCompletionItemsSync, getDiagnostics, getDiagnosticsSync, getEngineError, getEngineSync, getExpressionMessages, getTemplateDiagnostics, getTemplateDiagnosticsSync, isEngineReady, liftConditionTree, loadEngine, parseTemplateHoles, registerExpressionLocale, resetEngine, satisfiesType, satisfiesTypeSync, selectBranch, selectBranchWith, templateHoleAt, toZenLiteral, zhCNMessages };
1320
+ function fieldType(field) {
1321
+ switch (field.type) {
1322
+ case "string": return "String";
1323
+ case "number": return "Number";
1324
+ case "integer": return "Number";
1325
+ case "boolean": return "Bool";
1326
+ case "object": return { Object: fieldRecord(field.children) };
1327
+ case "array": return { Array: field.items === null ? "Any" : fieldType(field.items) };
1328
+ case "any": return "Any";
1329
+ }
1330
+ }
1331
+ function fieldRecord(fields) {
1332
+ return Object.fromEntries(fields.filter((field) => field.name.trim() !== "" || field.preserveBlankName === true).map((field) => [field.name, fieldType(field)]));
1333
+ }
1334
+ function schemaTreeToExpressionType(tree) {
1335
+ return { Object: fieldRecord(tree.fields) };
1336
+ }
1337
+ function mergeSchemas(a, b) {
1338
+ if (a.type === "object" && b.type === "object") {
1339
+ const left = a.properties ?? {};
1340
+ const right = b.properties ?? {};
1341
+ const entries = [];
1342
+ const keys = new Set([...Object.keys(left), ...Object.keys(right)]);
1343
+ for (const key of keys) {
1344
+ const hasLeft = Object.hasOwn(left, key);
1345
+ const hasRight = Object.hasOwn(right, key);
1346
+ entries.push([key, hasLeft && hasRight ? mergeSchemas(left[key], right[key]) : hasLeft ? left[key] : right[key]]);
1347
+ }
1348
+ const properties = Object.fromEntries(entries);
1349
+ return Object.keys(properties).length > 0 ? {
1350
+ type: "object",
1351
+ properties
1352
+ } : { type: "object" };
1353
+ }
1354
+ if (a.type === "array" && b.type === "array") return a.items !== void 0 && b.items !== void 0 ? {
1355
+ type: "array",
1356
+ items: mergeSchemas(a.items, b.items)
1357
+ } : { type: "array" };
1358
+ return JSON.stringify(a) === JSON.stringify(b) ? a : {};
1359
+ }
1360
+ function inferSchema(sample) {
1361
+ if (sample === null) return {};
1362
+ if (Array.isArray(sample)) {
1363
+ if (sample.length === 0) return { type: "array" };
1364
+ return {
1365
+ type: "array",
1366
+ items: sample.map((element) => inferSchema(element)).reduce((left, right) => mergeSchemas(left, right))
1367
+ };
1368
+ }
1369
+ if (typeof sample === "string") return { type: "string" };
1370
+ if (typeof sample === "number") return { type: "number" };
1371
+ if (typeof sample === "boolean") return { type: "boolean" };
1372
+ const properties = Object.fromEntries(Object.entries(sample).map(([key, value]) => [key, inferSchema(value)]));
1373
+ return Object.keys(properties).length > 0 ? {
1374
+ type: "object",
1375
+ properties
1376
+ } : { type: "object" };
1377
+ }
1378
+ const SCHEMA_DIALECT_2020_12 = "https://json-schema.org/draft/2020-12/schema";
1379
+ const fieldIdRealm = Math.random().toString(36).slice(2, 7);
1380
+ let fieldIdCounter = 0;
1381
+ function newSchemaTreeField(overrides = {}) {
1382
+ fieldIdCounter += 1;
1383
+ return {
1384
+ id: `sf-${fieldIdRealm}-${fieldIdCounter}`,
1385
+ name: "",
1386
+ type: "string",
1387
+ required: false,
1388
+ description: "",
1389
+ children: [],
1390
+ items: null,
1391
+ ...overrides
1392
+ };
1393
+ }
1394
+ const SCALAR_TYPES = new Set([
1395
+ "string",
1396
+ "number",
1397
+ "integer",
1398
+ "boolean"
1399
+ ]);
1400
+ const ROOT_KEYS = new Set([
1401
+ "$schema",
1402
+ "type",
1403
+ "properties",
1404
+ "required",
1405
+ "description"
1406
+ ]);
1407
+ const SUBSCHEMA_KEYS = new Set([
1408
+ "type",
1409
+ "properties",
1410
+ "required",
1411
+ "items",
1412
+ "description"
1413
+ ]);
1414
+ var Unsupported = class extends Error {
1415
+ issue;
1416
+ constructor(issue) {
1417
+ super(issue.code);
1418
+ this.issue = issue;
1419
+ }
1420
+ };
1421
+ function asPlainObject(value) {
1422
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
1423
+ }
1424
+ function checkKeys(schema, allowed) {
1425
+ for (const key of Object.keys(schema)) if (!allowed.has(key)) throw new Unsupported({
1426
+ code: "unsupported-keyword",
1427
+ keyword: key
1428
+ });
1429
+ }
1430
+ function parseObjectFields(schema) {
1431
+ const properties = schema.properties === void 0 ? null : asPlainObject(schema.properties);
1432
+ if (schema.properties !== void 0 && properties === null) throw new Unsupported({ code: "invalid-properties" });
1433
+ const fields = [];
1434
+ const entries = Object.entries(properties ?? {});
1435
+ for (const [name, subschema] of entries) fields.push({
1436
+ ...parseSubschema(subschema, name),
1437
+ name,
1438
+ ...name.trim() === "" && { preserveBlankName: true }
1439
+ });
1440
+ if (schema.required !== void 0) {
1441
+ if (!Array.isArray(schema.required)) throw new Unsupported({ code: "invalid-required" });
1442
+ for (const entry of schema.required) {
1443
+ if (typeof entry !== "string") throw new Unsupported({ code: "invalid-required" });
1444
+ const field = fields.find((candidate) => candidate.name === entry);
1445
+ if (field === void 0) throw new Unsupported({
1446
+ code: "unknown-required-field",
1447
+ field: entry
1448
+ });
1449
+ field.required = true;
1450
+ }
1451
+ }
1452
+ return fields;
1453
+ }
1454
+ function parseDescription(schema) {
1455
+ if (schema.description === void 0) return "";
1456
+ if (typeof schema.description !== "string") throw new Unsupported({ code: "invalid-description" });
1457
+ return schema.description;
1458
+ }
1459
+ function ensureAbsent(schema, key, holder) {
1460
+ if (schema[key] !== void 0) throw new Unsupported({
1461
+ code: "misplaced-keyword",
1462
+ keyword: key,
1463
+ holder
1464
+ });
1465
+ }
1466
+ function parseSubschema(value, name) {
1467
+ const schema = asPlainObject(value);
1468
+ if (schema === null) throw new Unsupported({
1469
+ code: "invalid-field-definition",
1470
+ field: name
1471
+ });
1472
+ checkKeys(schema, SUBSCHEMA_KEYS);
1473
+ const { type } = schema;
1474
+ if (type !== void 0 && typeof type !== "string") throw new Unsupported({
1475
+ code: "invalid-field-type",
1476
+ field: name
1477
+ });
1478
+ const description = parseDescription(schema);
1479
+ if (type === "object" || type === void 0 && (schema.properties !== void 0 || schema.required !== void 0)) {
1480
+ ensureAbsent(schema, "items", "array");
1481
+ return newSchemaTreeField({
1482
+ type: "object",
1483
+ description,
1484
+ children: parseObjectFields(schema),
1485
+ explicitType: type === "object"
1486
+ });
1487
+ }
1488
+ if (type === "array" || type === void 0 && schema.items !== void 0) {
1489
+ ensureAbsent(schema, "properties", "object");
1490
+ ensureAbsent(schema, "required", "object");
1491
+ return newSchemaTreeField({
1492
+ type: "array",
1493
+ description,
1494
+ items: schema.items === void 0 ? null : parseSubschema(schema.items, name),
1495
+ explicitType: type === "array"
1496
+ });
1497
+ }
1498
+ if (type === void 0) {
1499
+ ensureAbsent(schema, "items", "array");
1500
+ return newSchemaTreeField({
1501
+ type: "any",
1502
+ description
1503
+ });
1504
+ }
1505
+ if (!SCALAR_TYPES.has(type)) throw new Unsupported({
1506
+ code: "unsupported-field-type",
1507
+ field: name,
1508
+ type
1509
+ });
1510
+ ensureAbsent(schema, "properties", "object");
1511
+ ensureAbsent(schema, "required", "object");
1512
+ ensureAbsent(schema, "items", "array");
1513
+ return newSchemaTreeField({
1514
+ type,
1515
+ description
1516
+ });
1517
+ }
1518
+ function parseSchemaTree(text) {
1519
+ if (text.trim() === "") return {
1520
+ ok: true,
1521
+ tree: {
1522
+ fields: [],
1523
+ dialect: false,
1524
+ description: ""
1525
+ }
1526
+ };
1527
+ let parsed;
1528
+ try {
1529
+ parsed = JSON.parse(text);
1530
+ } catch {
1531
+ return {
1532
+ ok: false,
1533
+ issue: { code: "invalid-json" }
1534
+ };
1535
+ }
1536
+ try {
1537
+ const root = asPlainObject(parsed);
1538
+ if (root === null) throw new Unsupported({ code: "root-not-object" });
1539
+ checkKeys(root, ROOT_KEYS);
1540
+ const dialect = root.$schema !== void 0;
1541
+ if (dialect && root.$schema !== "https://json-schema.org/draft/2020-12/schema") throw new Unsupported({ code: "unsupported-dialect" });
1542
+ if (root.type !== void 0 && root.type !== "object") throw new Unsupported({ code: "root-not-object" });
1543
+ return {
1544
+ ok: true,
1545
+ tree: {
1546
+ fields: parseObjectFields(root),
1547
+ dialect,
1548
+ description: parseDescription(root),
1549
+ explicitType: root.type === "object"
1550
+ }
1551
+ };
1552
+ } catch (error) {
1553
+ if (error instanceof Unsupported) return {
1554
+ ok: false,
1555
+ issue: error.issue
1556
+ };
1557
+ throw error;
1558
+ }
1559
+ }
1560
+ function serializeObjectBody(fields) {
1561
+ const named = fields.filter((field) => field.name.trim() !== "" || field.preserveBlankName === true);
1562
+ const finalByName = /* @__PURE__ */ new Map();
1563
+ const finalFields = [];
1564
+ const body = {};
1565
+ for (const field of named) finalByName.set(field.name, field);
1566
+ finalByName.forEach((field) => {
1567
+ finalFields.push(field);
1568
+ });
1569
+ if (finalFields.length > 0) body.properties = Object.fromEntries(finalFields.map((field) => [field.name, serializeField(field)]));
1570
+ const required = finalFields.filter((field) => field.required).map((field) => field.name);
1571
+ if (required.length > 0) body.required = required;
1572
+ return body;
1573
+ }
1574
+ function serializeField(field) {
1575
+ const description = field.description === "" ? {} : { description: field.description };
1576
+ switch (field.type) {
1577
+ case "any": return { ...description };
1578
+ case "object": return {
1579
+ ...field.explicitType !== false && { type: "object" },
1580
+ ...description,
1581
+ ...serializeObjectBody(field.children)
1582
+ };
1583
+ case "array": {
1584
+ const type = field.explicitType === false ? {} : { type: "array" };
1585
+ return field.items === null || field.items.type === "any" && field.items.description === "" ? {
1586
+ ...type,
1587
+ ...description
1588
+ } : {
1589
+ ...type,
1590
+ ...description,
1591
+ items: serializeField(field.items)
1592
+ };
1593
+ }
1594
+ default: return {
1595
+ type: field.type,
1596
+ ...description
1597
+ };
1598
+ }
1599
+ }
1600
+ function serializeSchemaTree(tree) {
1601
+ const root = {
1602
+ ...tree.dialect && { $schema: "https://json-schema.org/draft/2020-12/schema" },
1603
+ ...tree.explicitType !== false && { type: "object" },
1604
+ ...tree.description !== "" && { description: tree.description },
1605
+ ...serializeObjectBody(tree.fields)
1606
+ };
1607
+ return `${JSON.stringify(root, null, 2)}\n`;
1608
+ }
1609
+ export { CONDITION_OPERATORS, CONDITION_TREE_OPERATORS, ExpressionError, ExpressionNotReadyError, MAX_CONDITION_TREE_DEPTH, SCHEMA_DIALECT_2020_12, analyzeTemplate, analyzeTemplateSync, analyzeTypes, analyzeTypesSync, compileBranch, compileCondition, compileConditionTree, compileGroup, conditionOperatorArity, configureEngine, configureExpressionMessages, emptyConditionGroup, enMessages, ensureConditionNodeIds, evaluate, evaluateSync, evaluateUnary, evaluateUnarySync, getCompletionItems, getCompletionItemsSync, getDiagnostics, getDiagnosticsSync, getEngineError, getEngineSync, getExpressionMessages, getTemplateDiagnostics, getTemplateDiagnosticsSync, inferSchema, isEngineReady, isZenRepresentableNumber, liftConditionTree, loadEngine, newConditionNodeId, newSchemaTreeField, parseSchemaTree, parseTemplateHoles, registerExpressionLocale, resetEngine, satisfiesType, satisfiesTypeSync, schemaTreeToExpressionType, selectBranch, selectBranchWith, serializeSchemaTree, subscribeExpressionMessages, templateHoleAt, toZenLiteral, zhCNMessages };