@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.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
- //#endregion
18
- //#region src/internal/predicates.ts
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
- enginePromise = (async () => {
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(configuredInput) ? void 0 : { module_or_path: configuredInput });
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: Array.isArray(rawSpans) ? rawSpans : []
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
- })().catch((error) => {
114
- enginePromise = null;
115
- engineError = new ExpressionError(loadFailureMessage(), void 0, error);
116
- throw engineError;
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,17 +188,180 @@ 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
- //#endregion
146
- //#region src/condition/subject.ts
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;
148
- const ZEN_RESERVED_WORDS = new Set([
362
+ const SUBJECT_IDENTIFIER_PATTERN = /[A-Z_$][\w$]*/gi;
363
+ const SUBJECT_INDEX_PATTERN = /\[(?<index>\d+)\]/g;
364
+ const ZEN_ROOT_RESERVED_WORDS = new Set([
149
365
  "and",
150
366
  "or",
151
367
  "not",
@@ -154,18 +370,28 @@ const ZEN_RESERVED_WORDS = new Set([
154
370
  "false",
155
371
  "null"
156
372
  ]);
373
+ const ZEN_MEMBER_RESERVED_WORDS = new Set(["true", "false"]);
157
374
  function isIdentifierPath(subject) {
158
- return SUBJECT_PATTERN.test(subject) && (subject.match(/[A-Z_$][\w$]*/gi) ?? []).every((segment) => !ZEN_RESERVED_WORDS.has(segment));
375
+ if (!SUBJECT_PATTERN.test(subject)) return false;
376
+ const [root, ...members] = subject.match(SUBJECT_IDENTIFIER_PATTERN) ?? [];
377
+ if (root === void 0 || ZEN_ROOT_RESERVED_WORDS.has(root) || members.some((member) => ZEN_MEMBER_RESERVED_WORDS.has(member))) return false;
378
+ for (const match of subject.matchAll(SUBJECT_INDEX_PATTERN)) if (match.groups?.index === void 0 || !isZenUnsignedIntegerText(match.groups.index)) return false;
379
+ return true;
380
+ }
381
+ function isZenRepresentableNumber(value) {
382
+ return isZenConsistentNumber(value);
159
383
  }
160
- //#endregion
161
- //#region src/condition/compile.ts
162
384
  function toZenLiteral(value) {
163
385
  if (isNullish(value)) return "null";
164
386
  if (typeof value === "number") {
165
- if (!Number.isFinite(value)) throw new ExpressionError(`Number ${String(value)} has no ZEN literal representation`);
387
+ if (!isZenRepresentableNumber(value)) throw new ExpressionError(`Number ${String(value)} has no faithful ZEN literal representation`);
388
+ return String(value);
389
+ }
390
+ if (typeof value === "bigint") {
391
+ if (value >= ZEN_DECIMAL_MANTISSA_LIMIT || -value >= ZEN_DECIMAL_MANTISSA_LIMIT) throw new ExpressionError(`Bigint ${String(value)} has no ZEN literal representation`);
166
392
  return String(value);
167
393
  }
168
- if (typeof value === "boolean" || typeof value === "bigint") return String(value);
394
+ if (typeof value === "boolean") return String(value);
169
395
  if (isString(value)) return encodeZenString(value);
170
396
  if (isArray(value)) return `[${value.map((item) => toZenLiteral(item)).join(", ")}]`;
171
397
  throw new ExpressionError(`Value of type "${typeof value}" has no ZEN literal representation`);
@@ -173,14 +399,17 @@ function toZenLiteral(value) {
173
399
  function encodeZenString(value) {
174
400
  const hasSingle = value.includes("'");
175
401
  const hasDouble = value.includes("\"");
176
- if (hasSingle && hasDouble) throw new ExpressionError("String contains both single and double quotes and has no ZEN literal representation");
177
- return hasSingle ? `"${value}"` : `'${value}'`;
402
+ const hasBacktick = value.includes("`");
403
+ if (!hasSingle) return `'${value}'`;
404
+ if (!hasDouble) return `"${value}"`;
405
+ if (!hasBacktick) return `\`${value}\``;
406
+ throw new ExpressionError("String contains every ZEN raw-string delimiter and has no literal representation");
178
407
  }
179
408
  function toArrayLiteral(value) {
180
409
  return isArray(value) ? toZenLiteral(value) : `[${toZenLiteral(value)}]`;
181
410
  }
182
411
  function zenIsEmpty(subject) {
183
- return `(${subject} == null or (type(${subject}) == 'string' and len(trim(${subject})) == 0) or (type(${subject}) == 'array' and len(${subject}) == 0))`;
412
+ 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))`;
184
413
  }
185
414
  function compileFieldCondition(subject, operator, value) {
186
415
  switch (operator) {
@@ -261,8 +490,6 @@ function reportEvaluationFailure(engine, expression, error) {
261
490
  async function selectBranch(branches, context) {
262
491
  return selectBranchWith(branches, context, await loadEngine());
263
492
  }
264
- //#endregion
265
- //#region src/condition/types.ts
266
493
  const CONDITION_OPERATORS = [
267
494
  "eq",
268
495
  "ne",
@@ -298,9 +525,83 @@ const CONDITION_OPERATOR_ARITIES = {
298
525
  function conditionOperatorArity(operator) {
299
526
  return CONDITION_OPERATOR_ARITIES[operator];
300
527
  }
301
- //#endregion
302
- //#region src/condition/compile-tree.ts
528
+ const MAX_CONDITION_TREE_DEPTH = 64;
529
+ const CONDITION_TREE_OPERATORS = CONDITION_OPERATORS;
530
+ const nodeIdRealm = Math.random().toString(36).slice(2, 7);
531
+ let nodeIdCounter = 0;
532
+ function newConditionNodeId() {
533
+ nodeIdCounter += 1;
534
+ return `cn-${nodeIdRealm}-${nodeIdCounter}`;
535
+ }
536
+ function emptyConditionGroup() {
537
+ return {
538
+ kind: "group",
539
+ id: newConditionNodeId(),
540
+ op: "and",
541
+ items: []
542
+ };
543
+ }
544
+ function assertConditionTreeStructure(tree) {
545
+ if (!isRecord(tree) || tree.kind !== "group") throw new ExpressionError("Condition tree root must be a group");
546
+ const ancestors = /* @__PURE__ */ new Set();
547
+ const stack = [{
548
+ group: tree,
549
+ depth: 0,
550
+ leaving: false
551
+ }];
552
+ while (stack.length > 0) {
553
+ const frame = stack.pop();
554
+ const { group } = frame;
555
+ if (frame.leaving) {
556
+ ancestors.delete(group);
557
+ continue;
558
+ }
559
+ if (frame.depth > 64) throw new ExpressionError(`Condition tree exceeds the maximum depth of 64`);
560
+ if (ancestors.has(group)) throw new ExpressionError("Condition tree contains a cycle");
561
+ if (group.op !== "and" && group.op !== "or") throw new ExpressionError(`Unsupported group operator: ${String(group.op)}`);
562
+ if (!isArray(group.items)) throw new ExpressionError("Condition tree group items must be an array");
563
+ ancestors.add(group);
564
+ stack.push({
565
+ ...frame,
566
+ leaving: true
567
+ });
568
+ for (let index = group.items.length - 1; index >= 0; index -= 1) {
569
+ const node = group.items[index];
570
+ if (!isRecord(node) || node.kind !== "group" && node.kind !== "rule") throw new ExpressionError("Condition tree contains an invalid node");
571
+ if (node.kind === "group") stack.push({
572
+ group: node,
573
+ depth: frame.depth + 1,
574
+ leaving: false
575
+ });
576
+ }
577
+ }
578
+ }
579
+ function withRuleId(rule) {
580
+ return rule.id === void 0 ? {
581
+ ...rule,
582
+ id: newConditionNodeId()
583
+ } : rule;
584
+ }
585
+ function withGroupIds(group) {
586
+ const items = group.items.map((item) => item.kind === "group" ? withGroupIds(item) : withRuleId(item));
587
+ if (group.id !== void 0 && items.every((item, index) => item === group.items[index])) return group;
588
+ return {
589
+ ...group,
590
+ id: group.id ?? newConditionNodeId(),
591
+ items
592
+ };
593
+ }
594
+ const normalizedTrees = /* @__PURE__ */ new WeakMap();
595
+ function ensureConditionNodeIds(tree) {
596
+ assertConditionTreeStructure(tree);
597
+ const cached = normalizedTrees.get(tree);
598
+ if (cached) return cached;
599
+ const normalized = withGroupIds(tree);
600
+ normalizedTrees.set(tree, normalized);
601
+ return normalized;
602
+ }
303
603
  function compileConditionTree(tree) {
604
+ assertConditionTreeStructure(tree);
304
605
  const normalized = normalizeNode(tree);
305
606
  return normalized === null ? "" : emitNode(normalized, true);
306
607
  }
@@ -317,9 +618,16 @@ function normalizeNode(node) {
317
618
  }
318
619
  function emitNode(node, topLevel) {
319
620
  if (node.kind === "rule") return compileRule(node);
320
- const joined = node.items.map((item) => emitNode(item, false)).join(node.op === "and" ? " and " : " or ");
621
+ const joined = node.items.map((item) => emitNode(item, false)).join(groupJoiner(node.op));
321
622
  return topLevel ? joined : `(${joined})`;
322
623
  }
624
+ function groupJoiner(op) {
625
+ switch (op) {
626
+ case "and": return " and ";
627
+ case "or": return " or ";
628
+ default: throw new ExpressionError(`Unsupported group operator: ${String(op)}`);
629
+ }
630
+ }
323
631
  function compileRule(rule) {
324
632
  if (!matchesOperatorArity(rule)) return null;
325
633
  return compileCondition({
@@ -342,8 +650,6 @@ function isConditionScalar(value) {
342
650
  function isScalarArray(value) {
343
651
  return isArray(value) && value.every((item) => isConditionScalar(item));
344
652
  }
345
- //#endregion
346
- //#region src/condition/lift-tree.ts
347
653
  const TWO_CHAR_PUNCTUATION = new Set([
348
654
  "==",
349
655
  "!=",
@@ -366,6 +672,10 @@ const IDENT_PART = /[\w$]/;
366
672
  const DIGIT = /\d/;
367
673
  const NUMBER_PATTERN = /^\d+(?:\.\d+)?(?:e[+-]?\d+)?/i;
368
674
  const INTEGER_PATTERN = /^\d+$/;
675
+ function parseCanonicalNumber(text) {
676
+ const value = Number(text);
677
+ return String(value) === text && isZenRepresentableNumber(value) ? value : null;
678
+ }
369
679
  const COMPARISON_OPERATORS = {
370
680
  "==": "eq",
371
681
  "!=": "ne",
@@ -379,7 +689,6 @@ const CALL_OPERATORS = {
379
689
  startsWith: "starts_with",
380
690
  endsWith: "ends_with"
381
691
  };
382
- const MAX_GROUP_DEPTH = 64;
383
692
  function liftConditionTree(expression) {
384
693
  const tokens = tokenize(expression);
385
694
  if (tokens === null || tokens.length === 0) return null;
@@ -425,8 +734,10 @@ function liftConditionTree(expression) {
425
734
  return token.value;
426
735
  }
427
736
  if (token.kind === "number") {
737
+ const value = parseCanonicalNumber(token.value);
738
+ if (value === null) return null;
428
739
  pos += 1;
429
- return Number(token.value);
740
+ return value;
430
741
  }
431
742
  if (token.kind === "ident") {
432
743
  if (token.value === "true") {
@@ -442,8 +753,10 @@ function liftConditionTree(expression) {
442
753
  if (token.kind === "punct" && token.value === "-") {
443
754
  const digits = peek(1);
444
755
  if (digits === void 0 || digits.kind !== "number") return null;
756
+ const value = parseCanonicalNumber(digits.value);
757
+ if (value === null || value === 0 || !isZenRepresentableNumber(-value)) return null;
445
758
  pos += 2;
446
- return -Number(digits.value);
759
+ return -value;
447
760
  }
448
761
  return null;
449
762
  }
@@ -472,6 +785,7 @@ function liftConditionTree(expression) {
472
785
  if (right === null || !consumePunct(")")) return null;
473
786
  return {
474
787
  kind: "rule",
788
+ id: newConditionNodeId(),
475
789
  left,
476
790
  operator,
477
791
  right
@@ -484,6 +798,7 @@ function liftConditionTree(expression) {
484
798
  if (right === null || !consumePunct(")")) return null;
485
799
  return {
486
800
  kind: "rule",
801
+ id: newConditionNodeId(),
487
802
  left,
488
803
  operator: "not_in",
489
804
  right
@@ -515,6 +830,7 @@ function liftConditionTree(expression) {
515
830
  pos += expected.length;
516
831
  return {
517
832
  kind: "rule",
833
+ id: newConditionNodeId(),
518
834
  left: read.path,
519
835
  operator
520
836
  };
@@ -550,6 +866,7 @@ function liftConditionTree(expression) {
550
866
  const right = parseArray();
551
867
  return right === null ? null : {
552
868
  kind: "rule",
869
+ id: newConditionNodeId(),
553
870
  left,
554
871
  operator: "in",
555
872
  right
@@ -562,6 +879,7 @@ function liftConditionTree(expression) {
562
879
  const right = parseLiteral();
563
880
  return right === null ? null : {
564
881
  kind: "rule",
882
+ id: newConditionNodeId(),
565
883
  left,
566
884
  operator,
567
885
  right
@@ -573,7 +891,7 @@ function liftConditionTree(expression) {
573
891
  const emptiness = tryEmptiness();
574
892
  if (emptiness !== null) return emptiness;
575
893
  if (isPunct(peek(), "(")) {
576
- if (depth >= MAX_GROUP_DEPTH) return null;
894
+ if (depth >= 64) return null;
577
895
  pos += 1;
578
896
  const inner = parseGroup(depth + 1);
579
897
  if (inner === null || !consumePunct(")")) return null;
@@ -603,6 +921,7 @@ function liftConditionTree(expression) {
603
921
  }
604
922
  return items.length === 1 ? items[0] : {
605
923
  kind: "group",
924
+ id: newConditionNodeId(),
606
925
  op: op ?? "and",
607
926
  items
608
927
  };
@@ -641,6 +960,7 @@ function readPath(tokens, from) {
641
960
  function asGroup(node) {
642
961
  return node.kind === "group" ? node : {
643
962
  kind: "group",
963
+ id: newConditionNodeId(),
644
964
  op: "and",
645
965
  items: [node]
646
966
  };
@@ -657,7 +977,7 @@ function tokenize(input) {
657
977
  index += 1;
658
978
  continue;
659
979
  }
660
- if (char === "'" || char === "\"") {
980
+ if (char === "'" || char === "\"" || char === "`") {
661
981
  const end = input.indexOf(char, index + 1);
662
982
  if (end === -1) return null;
663
983
  tokens.push({
@@ -708,11 +1028,6 @@ function tokenize(input) {
708
1028
  }
709
1029
  return tokens;
710
1030
  }
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
1031
  async function evaluate(expression, context) {
717
1032
  await loadEngine();
718
1033
  return evaluateSync(expression, context);
@@ -727,8 +1042,6 @@ function evaluateSync(expression, context) {
727
1042
  function evaluateUnarySync(expression, context) {
728
1043
  return getEngineSync().evaluateUnary(expression, context);
729
1044
  }
730
- //#endregion
731
- //#region src/engine/messages.ts
732
1045
  const COMPLETION_INFO_ZH = {
733
1046
  "Returns the length of variable": "返回变量的长度",
734
1047
  "Checks if variable contains a needle": "检查变量是否包含指定元素",
@@ -847,41 +1160,53 @@ const zhCNMessages = {
847
1160
  expectedType: (expectedType, actualType) => `期望 \`${expectedType}\`,实际为 \`${actualType}\`。`
848
1161
  };
849
1162
  const localeRegistry = new Map([["en-US", enMessages], ["zh-CN", zhCNMessages]]);
1163
+ let activeBaseMessages = enMessages;
850
1164
  let activeMessages = enMessages;
1165
+ const messageListeners = /* @__PURE__ */ new Set();
851
1166
  function registerExpressionLocale(locale, messages) {
852
1167
  localeRegistry.set(locale, messages);
853
1168
  }
854
1169
  function configureExpressionMessages({ locale, messages }) {
855
- const base = locale === void 0 ? activeMessages : localeRegistry.get(locale) ?? activeMessages;
856
- activeMessages = messages === void 0 ? base : {
1170
+ const base = locale === void 0 ? activeBaseMessages : localeRegistry.get(locale) ?? activeBaseMessages;
1171
+ const nextMessages = messages === void 0 ? base : {
857
1172
  ...base,
858
1173
  ...messages
859
1174
  };
1175
+ activeBaseMessages = base;
1176
+ if (nextMessages === activeMessages) return;
1177
+ activeMessages = nextMessages;
1178
+ for (const listener of messageListeners) listener(activeMessages);
860
1179
  }
861
1180
  function getExpressionMessages() {
862
1181
  return activeMessages;
863
1182
  }
864
- //#endregion
865
- //#region src/engine/intellisense.ts
1183
+ function subscribeExpressionMessages(listener) {
1184
+ messageListeners.add(listener);
1185
+ return () => {
1186
+ messageListeners.delete(listener);
1187
+ };
1188
+ }
866
1189
  function parseOffset(text) {
867
1190
  const trimmed = text?.trim();
868
1191
  return trimmed ? Number(trimmed) : NaN;
869
1192
  }
870
1193
  function extractPosition(message) {
871
- const segments = message.split(" at ");
872
- const last = segments.length <= 1 ? void 0 : segments.at(-1);
873
- if (last === void 0) return null;
874
- const [left, right] = last.replace("(", "").replace(")", "").split(", ");
875
- const from = parseOffset(left);
876
- if (Number.isNaN(from)) return null;
877
- const to = parseOffset(right);
878
- return [from, Number.isNaN(to) ? from : to];
1194
+ const positionPattern = / at (?:\(\s*(?<rangeFrom>\d+)\s*,\s*(?<rangeTo>\d+)\s*\)|(?<point>\d+))(?=\s*(?:;|$))/g;
1195
+ let position = null;
1196
+ for (const match of message.matchAll(positionPattern)) {
1197
+ const from = parseOffset(match.groups?.rangeFrom ?? match.groups?.point);
1198
+ const to = parseOffset(match.groups?.rangeTo);
1199
+ position = [from, Number.isNaN(to) ? from : to];
1200
+ }
1201
+ return position;
879
1202
  }
880
1203
  function normalizeDiagnostic(raw, source) {
881
1204
  if (raw === null || raw === void 0) return null;
882
1205
  const errorType = isRecord(raw) && typeof raw.type === "string" ? raw.type : void 0;
883
1206
  const message = isRecord(raw) && typeof raw.source === "string" ? raw.source : String(raw);
884
- const [from, to] = extractPosition(message) ?? [0, source.length];
1207
+ const bytePosition = extractPosition(message);
1208
+ const toUtf16 = utf8OffsetConverter(source);
1209
+ const [from, to] = bytePosition === null ? [0, source.length] : [toUtf16(bytePosition[0]), toUtf16(bytePosition[1])];
885
1210
  return {
886
1211
  from,
887
1212
  to,
@@ -933,8 +1258,6 @@ async function satisfiesType(actual, expected) {
933
1258
  function satisfiesTypeSync(actual, expected) {
934
1259
  return getEngineSync().satisfies(actual, expected);
935
1260
  }
936
- //#endregion
937
- //#region src/engine/template.ts
938
1261
  const HOLE_PATTERN = /\{\{(?<expression>[^{}]*)\}\}/g;
939
1262
  function parseTemplateHoles(source) {
940
1263
  const holes = [];
@@ -995,11 +1318,301 @@ async function getTemplateDiagnostics(source) {
995
1318
  await loadEngine();
996
1319
  return getTemplateDiagnosticsSync(source);
997
1320
  }
998
- //#endregion
1321
+ function fieldType(field) {
1322
+ switch (field.type) {
1323
+ case "string": return "String";
1324
+ case "number": return "Number";
1325
+ case "integer": return "Number";
1326
+ case "boolean": return "Bool";
1327
+ case "object": return { Object: fieldRecord(field.children) };
1328
+ case "array": return { Array: field.items === null ? "Any" : fieldType(field.items) };
1329
+ case "any": return "Any";
1330
+ }
1331
+ }
1332
+ function fieldRecord(fields) {
1333
+ return Object.fromEntries(fields.filter((field) => field.name.trim() !== "" || field.preserveBlankName === true).map((field) => [field.name, fieldType(field)]));
1334
+ }
1335
+ function schemaTreeToExpressionType(tree) {
1336
+ return { Object: fieldRecord(tree.fields) };
1337
+ }
1338
+ function mergeSchemas(a, b) {
1339
+ if (a.type === "object" && b.type === "object") {
1340
+ const left = a.properties ?? {};
1341
+ const right = b.properties ?? {};
1342
+ const entries = [];
1343
+ const keys = new Set([...Object.keys(left), ...Object.keys(right)]);
1344
+ for (const key of keys) {
1345
+ const hasLeft = Object.hasOwn(left, key);
1346
+ const hasRight = Object.hasOwn(right, key);
1347
+ entries.push([key, hasLeft && hasRight ? mergeSchemas(left[key], right[key]) : hasLeft ? left[key] : right[key]]);
1348
+ }
1349
+ const properties = Object.fromEntries(entries);
1350
+ return Object.keys(properties).length > 0 ? {
1351
+ type: "object",
1352
+ properties
1353
+ } : { type: "object" };
1354
+ }
1355
+ if (a.type === "array" && b.type === "array") return a.items !== void 0 && b.items !== void 0 ? {
1356
+ type: "array",
1357
+ items: mergeSchemas(a.items, b.items)
1358
+ } : { type: "array" };
1359
+ return JSON.stringify(a) === JSON.stringify(b) ? a : {};
1360
+ }
1361
+ function inferSchema(sample) {
1362
+ if (sample === null) return {};
1363
+ if (Array.isArray(sample)) {
1364
+ if (sample.length === 0) return { type: "array" };
1365
+ return {
1366
+ type: "array",
1367
+ items: sample.map((element) => inferSchema(element)).reduce((left, right) => mergeSchemas(left, right))
1368
+ };
1369
+ }
1370
+ if (typeof sample === "string") return { type: "string" };
1371
+ if (typeof sample === "number") return { type: "number" };
1372
+ if (typeof sample === "boolean") return { type: "boolean" };
1373
+ const properties = Object.fromEntries(Object.entries(sample).map(([key, value]) => [key, inferSchema(value)]));
1374
+ return Object.keys(properties).length > 0 ? {
1375
+ type: "object",
1376
+ properties
1377
+ } : { type: "object" };
1378
+ }
1379
+ const SCHEMA_DIALECT_2020_12 = "https://json-schema.org/draft/2020-12/schema";
1380
+ const fieldIdRealm = Math.random().toString(36).slice(2, 7);
1381
+ let fieldIdCounter = 0;
1382
+ function newSchemaTreeField(overrides = {}) {
1383
+ fieldIdCounter += 1;
1384
+ return {
1385
+ id: `sf-${fieldIdRealm}-${fieldIdCounter}`,
1386
+ name: "",
1387
+ type: "string",
1388
+ required: false,
1389
+ description: "",
1390
+ children: [],
1391
+ items: null,
1392
+ ...overrides
1393
+ };
1394
+ }
1395
+ const SCALAR_TYPES = new Set([
1396
+ "string",
1397
+ "number",
1398
+ "integer",
1399
+ "boolean"
1400
+ ]);
1401
+ const ROOT_KEYS = new Set([
1402
+ "$schema",
1403
+ "type",
1404
+ "properties",
1405
+ "required",
1406
+ "description"
1407
+ ]);
1408
+ const SUBSCHEMA_KEYS = new Set([
1409
+ "type",
1410
+ "properties",
1411
+ "required",
1412
+ "items",
1413
+ "description"
1414
+ ]);
1415
+ var Unsupported = class extends Error {
1416
+ issue;
1417
+ constructor(issue) {
1418
+ super(issue.code);
1419
+ this.issue = issue;
1420
+ }
1421
+ };
1422
+ function asPlainObject(value) {
1423
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
1424
+ }
1425
+ function checkKeys(schema, allowed) {
1426
+ for (const key of Object.keys(schema)) if (!allowed.has(key)) throw new Unsupported({
1427
+ code: "unsupported-keyword",
1428
+ keyword: key
1429
+ });
1430
+ }
1431
+ function parseObjectFields(schema) {
1432
+ const properties = schema.properties === void 0 ? null : asPlainObject(schema.properties);
1433
+ if (schema.properties !== void 0 && properties === null) throw new Unsupported({ code: "invalid-properties" });
1434
+ const fields = [];
1435
+ const entries = Object.entries(properties ?? {});
1436
+ for (const [name, subschema] of entries) fields.push({
1437
+ ...parseSubschema(subschema, name),
1438
+ name,
1439
+ ...name.trim() === "" && { preserveBlankName: true }
1440
+ });
1441
+ if (schema.required !== void 0) {
1442
+ if (!Array.isArray(schema.required)) throw new Unsupported({ code: "invalid-required" });
1443
+ for (const entry of schema.required) {
1444
+ if (typeof entry !== "string") throw new Unsupported({ code: "invalid-required" });
1445
+ const field = fields.find((candidate) => candidate.name === entry);
1446
+ if (field === void 0) throw new Unsupported({
1447
+ code: "unknown-required-field",
1448
+ field: entry
1449
+ });
1450
+ field.required = true;
1451
+ }
1452
+ }
1453
+ return fields;
1454
+ }
1455
+ function parseDescription(schema) {
1456
+ if (schema.description === void 0) return "";
1457
+ if (typeof schema.description !== "string") throw new Unsupported({ code: "invalid-description" });
1458
+ return schema.description;
1459
+ }
1460
+ function ensureAbsent(schema, key, holder) {
1461
+ if (schema[key] !== void 0) throw new Unsupported({
1462
+ code: "misplaced-keyword",
1463
+ keyword: key,
1464
+ holder
1465
+ });
1466
+ }
1467
+ function parseSubschema(value, name) {
1468
+ const schema = asPlainObject(value);
1469
+ if (schema === null) throw new Unsupported({
1470
+ code: "invalid-field-definition",
1471
+ field: name
1472
+ });
1473
+ checkKeys(schema, SUBSCHEMA_KEYS);
1474
+ const { type } = schema;
1475
+ if (type !== void 0 && typeof type !== "string") throw new Unsupported({
1476
+ code: "invalid-field-type",
1477
+ field: name
1478
+ });
1479
+ const description = parseDescription(schema);
1480
+ if (type === "object" || type === void 0 && (schema.properties !== void 0 || schema.required !== void 0)) {
1481
+ ensureAbsent(schema, "items", "array");
1482
+ return newSchemaTreeField({
1483
+ type: "object",
1484
+ description,
1485
+ children: parseObjectFields(schema),
1486
+ explicitType: type === "object"
1487
+ });
1488
+ }
1489
+ if (type === "array" || type === void 0 && schema.items !== void 0) {
1490
+ ensureAbsent(schema, "properties", "object");
1491
+ ensureAbsent(schema, "required", "object");
1492
+ return newSchemaTreeField({
1493
+ type: "array",
1494
+ description,
1495
+ items: schema.items === void 0 ? null : parseSubschema(schema.items, name),
1496
+ explicitType: type === "array"
1497
+ });
1498
+ }
1499
+ if (type === void 0) {
1500
+ ensureAbsent(schema, "items", "array");
1501
+ return newSchemaTreeField({
1502
+ type: "any",
1503
+ description
1504
+ });
1505
+ }
1506
+ if (!SCALAR_TYPES.has(type)) throw new Unsupported({
1507
+ code: "unsupported-field-type",
1508
+ field: name,
1509
+ type
1510
+ });
1511
+ ensureAbsent(schema, "properties", "object");
1512
+ ensureAbsent(schema, "required", "object");
1513
+ ensureAbsent(schema, "items", "array");
1514
+ return newSchemaTreeField({
1515
+ type,
1516
+ description
1517
+ });
1518
+ }
1519
+ function parseSchemaTree(text) {
1520
+ if (text.trim() === "") return {
1521
+ ok: true,
1522
+ tree: {
1523
+ fields: [],
1524
+ dialect: false,
1525
+ description: ""
1526
+ }
1527
+ };
1528
+ let parsed;
1529
+ try {
1530
+ parsed = JSON.parse(text);
1531
+ } catch {
1532
+ return {
1533
+ ok: false,
1534
+ issue: { code: "invalid-json" }
1535
+ };
1536
+ }
1537
+ try {
1538
+ const root = asPlainObject(parsed);
1539
+ if (root === null) throw new Unsupported({ code: "root-not-object" });
1540
+ checkKeys(root, ROOT_KEYS);
1541
+ const dialect = root.$schema !== void 0;
1542
+ if (dialect && root.$schema !== "https://json-schema.org/draft/2020-12/schema") throw new Unsupported({ code: "unsupported-dialect" });
1543
+ if (root.type !== void 0 && root.type !== "object") throw new Unsupported({ code: "root-not-object" });
1544
+ return {
1545
+ ok: true,
1546
+ tree: {
1547
+ fields: parseObjectFields(root),
1548
+ dialect,
1549
+ description: parseDescription(root),
1550
+ explicitType: root.type === "object"
1551
+ }
1552
+ };
1553
+ } catch (error) {
1554
+ if (error instanceof Unsupported) return {
1555
+ ok: false,
1556
+ issue: error.issue
1557
+ };
1558
+ throw error;
1559
+ }
1560
+ }
1561
+ function serializeObjectBody(fields) {
1562
+ const named = fields.filter((field) => field.name.trim() !== "" || field.preserveBlankName === true);
1563
+ const finalByName = /* @__PURE__ */ new Map();
1564
+ const finalFields = [];
1565
+ const body = {};
1566
+ for (const field of named) finalByName.set(field.name, field);
1567
+ finalByName.forEach((field) => {
1568
+ finalFields.push(field);
1569
+ });
1570
+ if (finalFields.length > 0) body.properties = Object.fromEntries(finalFields.map((field) => [field.name, serializeField(field)]));
1571
+ const required = finalFields.filter((field) => field.required).map((field) => field.name);
1572
+ if (required.length > 0) body.required = required;
1573
+ return body;
1574
+ }
1575
+ function serializeField(field) {
1576
+ const description = field.description === "" ? {} : { description: field.description };
1577
+ switch (field.type) {
1578
+ case "any": return { ...description };
1579
+ case "object": return {
1580
+ ...field.explicitType !== false && { type: "object" },
1581
+ ...description,
1582
+ ...serializeObjectBody(field.children)
1583
+ };
1584
+ case "array": {
1585
+ const type = field.explicitType === false ? {} : { type: "array" };
1586
+ return field.items === null || field.items.type === "any" && field.items.description === "" ? {
1587
+ ...type,
1588
+ ...description
1589
+ } : {
1590
+ ...type,
1591
+ ...description,
1592
+ items: serializeField(field.items)
1593
+ };
1594
+ }
1595
+ default: return {
1596
+ type: field.type,
1597
+ ...description
1598
+ };
1599
+ }
1600
+ }
1601
+ function serializeSchemaTree(tree) {
1602
+ const root = {
1603
+ ...tree.dialect && { $schema: "https://json-schema.org/draft/2020-12/schema" },
1604
+ ...tree.explicitType !== false && { type: "object" },
1605
+ ...tree.description !== "" && { description: tree.description },
1606
+ ...serializeObjectBody(tree.fields)
1607
+ };
1608
+ return `${JSON.stringify(root, null, 2)}\n`;
1609
+ }
999
1610
  exports.CONDITION_OPERATORS = CONDITION_OPERATORS;
1000
1611
  exports.CONDITION_TREE_OPERATORS = CONDITION_TREE_OPERATORS;
1001
1612
  exports.ExpressionError = ExpressionError;
1002
1613
  exports.ExpressionNotReadyError = ExpressionNotReadyError;
1614
+ exports.MAX_CONDITION_TREE_DEPTH = MAX_CONDITION_TREE_DEPTH;
1615
+ exports.SCHEMA_DIALECT_2020_12 = SCHEMA_DIALECT_2020_12;
1003
1616
  exports.analyzeTemplate = analyzeTemplate;
1004
1617
  exports.analyzeTemplateSync = analyzeTemplateSync;
1005
1618
  exports.analyzeTypes = analyzeTypes;
@@ -1011,7 +1624,9 @@ exports.compileGroup = compileGroup;
1011
1624
  exports.conditionOperatorArity = conditionOperatorArity;
1012
1625
  exports.configureEngine = configureEngine;
1013
1626
  exports.configureExpressionMessages = configureExpressionMessages;
1627
+ exports.emptyConditionGroup = emptyConditionGroup;
1014
1628
  exports.enMessages = enMessages;
1629
+ exports.ensureConditionNodeIds = ensureConditionNodeIds;
1015
1630
  exports.evaluate = evaluate;
1016
1631
  exports.evaluateSync = evaluateSync;
1017
1632
  exports.evaluateUnary = evaluateUnary;
@@ -1025,16 +1640,24 @@ exports.getEngineSync = getEngineSync;
1025
1640
  exports.getExpressionMessages = getExpressionMessages;
1026
1641
  exports.getTemplateDiagnostics = getTemplateDiagnostics;
1027
1642
  exports.getTemplateDiagnosticsSync = getTemplateDiagnosticsSync;
1643
+ exports.inferSchema = inferSchema;
1028
1644
  exports.isEngineReady = isEngineReady;
1645
+ exports.isZenRepresentableNumber = isZenRepresentableNumber;
1029
1646
  exports.liftConditionTree = liftConditionTree;
1030
1647
  exports.loadEngine = loadEngine;
1648
+ exports.newConditionNodeId = newConditionNodeId;
1649
+ exports.newSchemaTreeField = newSchemaTreeField;
1650
+ exports.parseSchemaTree = parseSchemaTree;
1031
1651
  exports.parseTemplateHoles = parseTemplateHoles;
1032
1652
  exports.registerExpressionLocale = registerExpressionLocale;
1033
1653
  exports.resetEngine = resetEngine;
1034
1654
  exports.satisfiesType = satisfiesType;
1035
1655
  exports.satisfiesTypeSync = satisfiesTypeSync;
1656
+ exports.schemaTreeToExpressionType = schemaTreeToExpressionType;
1036
1657
  exports.selectBranch = selectBranch;
1037
1658
  exports.selectBranchWith = selectBranchWith;
1659
+ exports.serializeSchemaTree = serializeSchemaTree;
1660
+ exports.subscribeExpressionMessages = subscribeExpressionMessages;
1038
1661
  exports.templateHoleAt = templateHoleAt;
1039
1662
  exports.toZenLiteral = toZenLiteral;
1040
1663
  exports.zhCNMessages = zhCNMessages;