@revisium/formula 0.8.0 → 0.9.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.
Files changed (75) hide show
  1. package/dist/__tests__/array-context.spec.d.ts +2 -0
  2. package/dist/__tests__/array-context.spec.d.ts.map +1 -0
  3. package/dist/__tests__/dependency-graph.spec.d.ts +2 -0
  4. package/dist/__tests__/dependency-graph.spec.d.ts.map +1 -0
  5. package/dist/__tests__/evaluate-complex.spec.d.ts +2 -0
  6. package/dist/__tests__/evaluate-complex.spec.d.ts.map +1 -0
  7. package/dist/__tests__/parse-formula.spec.d.ts +2 -0
  8. package/dist/__tests__/parse-formula.spec.d.ts.map +1 -0
  9. package/dist/__tests__/parser.spec.d.ts +2 -0
  10. package/dist/__tests__/parser.spec.d.ts.map +1 -0
  11. package/dist/__tests__/replace-dependencies.spec.d.ts +2 -0
  12. package/dist/__tests__/replace-dependencies.spec.d.ts.map +1 -0
  13. package/dist/__tests__/serialize-ast.spec.d.ts +2 -0
  14. package/dist/__tests__/serialize-ast.spec.d.ts.map +1 -0
  15. package/dist/__tests__/validate-syntax.spec.d.ts +2 -0
  16. package/dist/__tests__/validate-syntax.spec.d.ts.map +1 -0
  17. package/dist/dependency-graph.d.ts +17 -0
  18. package/dist/dependency-graph.d.ts.map +1 -0
  19. package/dist/editor/index.cjs +4 -19
  20. package/dist/editor/index.d.ts +8 -1
  21. package/dist/editor/index.d.ts.map +1 -0
  22. package/dist/editor/index.mjs +3 -0
  23. package/dist/formula-spec.cjs +808 -535
  24. package/dist/formula-spec.cjs.map +1 -1
  25. package/dist/formula-spec.d.ts +10 -5
  26. package/dist/formula-spec.d.ts.map +1 -0
  27. package/dist/formula-spec.mjs +1039 -0
  28. package/dist/formula-spec.mjs.map +1 -0
  29. package/dist/index.cjs +121 -131
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.ts +10 -25
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.mjs +130 -0
  34. package/dist/index.mjs.map +1 -0
  35. package/dist/ohm/core/parser.d.ts +30 -0
  36. package/dist/ohm/core/parser.d.ts.map +1 -0
  37. package/dist/ohm/core/replace-dependencies.d.ts +3 -0
  38. package/dist/ohm/core/replace-dependencies.d.ts.map +1 -0
  39. package/dist/ohm/core/serialize-ast.d.ts +3 -0
  40. package/dist/ohm/core/serialize-ast.d.ts.map +1 -0
  41. package/dist/ohm/core/types.d.ts +78 -0
  42. package/dist/ohm/core/types.d.ts.map +1 -0
  43. package/dist/ohm/grammar/index.d.ts +3 -0
  44. package/dist/ohm/grammar/index.d.ts.map +1 -0
  45. package/dist/ohm/index.d.ts +8 -0
  46. package/dist/ohm/index.d.ts.map +1 -0
  47. package/dist/ohm/semantics/index.d.ts +2 -0
  48. package/dist/ohm/semantics/index.d.ts.map +1 -0
  49. package/dist/parse-formula.d.ts +9 -0
  50. package/dist/parse-formula.d.ts.map +1 -0
  51. package/dist/types.d.ts +52 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/validate-syntax-BGrLewnG.cjs +1502 -0
  54. package/dist/validate-syntax-BGrLewnG.cjs.map +1 -0
  55. package/dist/validate-syntax-CmqnFrsc.mjs +1421 -0
  56. package/dist/validate-syntax-CmqnFrsc.mjs.map +1 -0
  57. package/dist/validate-syntax.d.ts +9 -0
  58. package/dist/validate-syntax.d.ts.map +1 -0
  59. package/package.json +12 -11
  60. package/dist/chunk-4FIOGFOL.cjs +0 -1512
  61. package/dist/chunk-4FIOGFOL.cjs.map +0 -1
  62. package/dist/chunk-RMAHNB4D.js +0 -1482
  63. package/dist/chunk-RMAHNB4D.js.map +0 -1
  64. package/dist/editor/index.cjs.map +0 -1
  65. package/dist/editor/index.d.cts +0 -1
  66. package/dist/editor/index.js +0 -3
  67. package/dist/editor/index.js.map +0 -1
  68. package/dist/formula-spec.d.cts +0 -68
  69. package/dist/formula-spec.js +0 -765
  70. package/dist/formula-spec.js.map +0 -1
  71. package/dist/index-CPsOPCQ6.d.cts +0 -166
  72. package/dist/index-CPsOPCQ6.d.ts +0 -166
  73. package/dist/index.d.cts +0 -25
  74. package/dist/index.js +0 -111
  75. package/dist/index.js.map +0 -1
@@ -0,0 +1,1421 @@
1
+ import * as ohm from "ohm-js";
2
+
3
+ //#region src/ohm/grammar/index.ts
4
+ const grammarText = String.raw`Formula {
5
+ Expression = Ternary
6
+
7
+ // Ternary: condition ? then : else
8
+ Ternary
9
+ = LogicalOr "?" Ternary ":" Ternary -- ternary
10
+ | LogicalOr
11
+
12
+ // Logical OR: a || b
13
+ LogicalOr
14
+ = LogicalOr "||" LogicalAnd -- or
15
+ | LogicalAnd
16
+
17
+ // Logical AND: a && b
18
+ LogicalAnd
19
+ = LogicalAnd "&&" Equality -- and
20
+ | Equality
21
+
22
+ // Equality: a == b, a != b
23
+ Equality
24
+ = Equality "==" Comparison -- eq
25
+ | Equality "!=" Comparison -- neq
26
+ | Comparison
27
+
28
+ // Comparison: a > b, a < b, a >= b, a <= b
29
+ Comparison
30
+ = Comparison ">=" Additive -- gte
31
+ | Comparison "<=" Additive -- lte
32
+ | Comparison ">" Additive -- gt
33
+ | Comparison "<" Additive -- lt
34
+ | Additive
35
+
36
+ // Additive: a + b, a - b
37
+ Additive
38
+ = Additive "+" Multiplicative -- plus
39
+ | Additive "-" Multiplicative -- minus
40
+ | Multiplicative
41
+
42
+ // Multiplicative: a * b, a / b, a % b
43
+ Multiplicative
44
+ = Multiplicative "*" Unary -- times
45
+ | Multiplicative "/" Unary -- div
46
+ | Multiplicative "%" Unary -- mod
47
+ | Unary
48
+
49
+ // Unary: -a, !a
50
+ Unary
51
+ = "-" Unary -- neg
52
+ | "!" Unary -- not
53
+ | Postfix
54
+
55
+ // Postfix: function calls, property access, array access
56
+ Postfix
57
+ = Postfix "(" Arguments? ")" -- call
58
+ | Postfix "." identifier -- property
59
+ | Postfix "[" Expression "]" -- index
60
+ | Postfix "[" "*" "]" -- wildcard
61
+ | Primary
62
+
63
+ // Arguments for function calls
64
+ Arguments
65
+ = Expression ("," Expression)*
66
+
67
+ // Primary expressions
68
+ Primary
69
+ = "(" Expression ")" -- paren
70
+ | "[" string "]" -- bracketedField
71
+ | number
72
+ | string
73
+ | boolean
74
+ | null
75
+ | rootPath
76
+ | relativePath
77
+ | contextToken
78
+ | identifier
79
+
80
+ // Literals
81
+ number
82
+ = "-"? digit+ "." digit+ -- float
83
+ | "-"? digit+ -- int
84
+
85
+ string
86
+ = "\"" doubleStringChar* "\""
87
+ | "'" singleStringChar* "'"
88
+
89
+ doubleStringChar
90
+ = ~("\"" | "\\") any -- regular
91
+ | "\\" any -- escape
92
+
93
+ singleStringChar
94
+ = ~("'" | "\\") any -- regular
95
+ | "\\" any -- escape
96
+
97
+ boolean
98
+ = "true" ~identifierPart -- true
99
+ | "false" ~identifierPart -- false
100
+
101
+ null = "null" ~identifierPart
102
+
103
+ // Identifiers and paths
104
+ identifier = ~reserved identifierStart identifierPart*
105
+
106
+ identifierStart = letter | "_"
107
+ identifierPart = letter | digit | "_"
108
+
109
+ // Special paths
110
+ rootPath = "/" identifierPart+
111
+
112
+ relativePathPrefix = ".." "/"
113
+ relativePath = relativePathPrefix+ identifierPart+
114
+
115
+ contextToken
116
+ = "@" contextPath -- at
117
+ | "#" contextPath -- hash
118
+
119
+ contextPath
120
+ = "parent" "." contextPath -- parent
121
+ | "root" "." contextPathEnd -- root
122
+ | contextPathEnd -- simple
123
+
124
+ contextPathEnd = identifierPart+
125
+
126
+ // Reserved words (cannot be used as identifiers)
127
+ reserved
128
+ = ("true" | "false" | "null") ~identifierPart
129
+
130
+ // Whitespace (implicit, Ohm handles automatically)
131
+ }`;
132
+ const grammar = ohm.grammar(grammarText);
133
+
134
+ //#endregion
135
+ //#region src/ohm/semantics/index.ts
136
+ function isSafePropertyName(name) {
137
+ return name !== "__proto__";
138
+ }
139
+ function childrenToAST(children) {
140
+ return children.filter((c) => "toAST" in c).map((c) => c.toAST());
141
+ }
142
+ function childrenDependencies(children) {
143
+ return children.filter((c) => "dependencies" in c).flatMap((c) => c.dependencies());
144
+ }
145
+ function childrenFeatures(children) {
146
+ return children.filter((c) => "features" in c).flatMap((c) => c.features());
147
+ }
148
+ const semantics = grammar.createSemantics();
149
+ semantics.addOperation("toAST", {
150
+ Expression(e) {
151
+ return e.toAST();
152
+ },
153
+ Ternary_ternary(cond, _q, cons, _c, alt) {
154
+ return {
155
+ type: "TernaryOp",
156
+ condition: cond.toAST(),
157
+ consequent: cons.toAST(),
158
+ alternate: alt.toAST()
159
+ };
160
+ },
161
+ Ternary(e) {
162
+ return e.toAST();
163
+ },
164
+ LogicalOr_or(left, _op, right) {
165
+ return {
166
+ type: "BinaryOp",
167
+ op: "||",
168
+ left: left.toAST(),
169
+ right: right.toAST()
170
+ };
171
+ },
172
+ LogicalOr(e) {
173
+ return e.toAST();
174
+ },
175
+ LogicalAnd_and(left, _op, right) {
176
+ return {
177
+ type: "BinaryOp",
178
+ op: "&&",
179
+ left: left.toAST(),
180
+ right: right.toAST()
181
+ };
182
+ },
183
+ LogicalAnd(e) {
184
+ return e.toAST();
185
+ },
186
+ Equality_eq(left, _op, right) {
187
+ return {
188
+ type: "BinaryOp",
189
+ op: "==",
190
+ left: left.toAST(),
191
+ right: right.toAST()
192
+ };
193
+ },
194
+ Equality_neq(left, _op, right) {
195
+ return {
196
+ type: "BinaryOp",
197
+ op: "!=",
198
+ left: left.toAST(),
199
+ right: right.toAST()
200
+ };
201
+ },
202
+ Equality(e) {
203
+ return e.toAST();
204
+ },
205
+ Comparison_gte(left, _op, right) {
206
+ return {
207
+ type: "BinaryOp",
208
+ op: ">=",
209
+ left: left.toAST(),
210
+ right: right.toAST()
211
+ };
212
+ },
213
+ Comparison_lte(left, _op, right) {
214
+ return {
215
+ type: "BinaryOp",
216
+ op: "<=",
217
+ left: left.toAST(),
218
+ right: right.toAST()
219
+ };
220
+ },
221
+ Comparison_gt(left, _op, right) {
222
+ return {
223
+ type: "BinaryOp",
224
+ op: ">",
225
+ left: left.toAST(),
226
+ right: right.toAST()
227
+ };
228
+ },
229
+ Comparison_lt(left, _op, right) {
230
+ return {
231
+ type: "BinaryOp",
232
+ op: "<",
233
+ left: left.toAST(),
234
+ right: right.toAST()
235
+ };
236
+ },
237
+ Comparison(e) {
238
+ return e.toAST();
239
+ },
240
+ Additive_plus(left, _op, right) {
241
+ return {
242
+ type: "BinaryOp",
243
+ op: "+",
244
+ left: left.toAST(),
245
+ right: right.toAST()
246
+ };
247
+ },
248
+ Additive_minus(left, _op, right) {
249
+ return {
250
+ type: "BinaryOp",
251
+ op: "-",
252
+ left: left.toAST(),
253
+ right: right.toAST()
254
+ };
255
+ },
256
+ Additive(e) {
257
+ return e.toAST();
258
+ },
259
+ Multiplicative_times(left, _op, right) {
260
+ return {
261
+ type: "BinaryOp",
262
+ op: "*",
263
+ left: left.toAST(),
264
+ right: right.toAST()
265
+ };
266
+ },
267
+ Multiplicative_div(left, _op, right) {
268
+ return {
269
+ type: "BinaryOp",
270
+ op: "/",
271
+ left: left.toAST(),
272
+ right: right.toAST()
273
+ };
274
+ },
275
+ Multiplicative_mod(left, _op, right) {
276
+ return {
277
+ type: "BinaryOp",
278
+ op: "%",
279
+ left: left.toAST(),
280
+ right: right.toAST()
281
+ };
282
+ },
283
+ Multiplicative(e) {
284
+ return e.toAST();
285
+ },
286
+ Unary_neg(_op, expr) {
287
+ return {
288
+ type: "UnaryOp",
289
+ op: "-",
290
+ argument: expr.toAST()
291
+ };
292
+ },
293
+ Unary_not(_op, expr) {
294
+ return {
295
+ type: "UnaryOp",
296
+ op: "!",
297
+ argument: expr.toAST()
298
+ };
299
+ },
300
+ Unary(e) {
301
+ return e.toAST();
302
+ },
303
+ Postfix_call(callee, _lp, args, _rp) {
304
+ const firstArg = args.children[0];
305
+ const argList = firstArg ? firstArg.toAST() : [];
306
+ return {
307
+ type: "CallExpression",
308
+ callee: callee.toAST(),
309
+ arguments: Array.isArray(argList) ? argList : [argList]
310
+ };
311
+ },
312
+ Postfix_property(obj, _dot, prop) {
313
+ return {
314
+ type: "MemberExpression",
315
+ object: obj.toAST(),
316
+ property: prop.sourceString
317
+ };
318
+ },
319
+ Postfix_index(obj, _lb, index, _rb) {
320
+ const indexAST = index.toAST();
321
+ if (indexAST.type === "StringLiteral") return {
322
+ type: "BracketedMemberExpression",
323
+ object: obj.toAST(),
324
+ property: indexAST.value
325
+ };
326
+ return {
327
+ type: "IndexExpression",
328
+ object: obj.toAST(),
329
+ index: indexAST
330
+ };
331
+ },
332
+ Postfix_wildcard(obj, _lb, _star, _rb) {
333
+ return {
334
+ type: "WildcardExpression",
335
+ object: obj.toAST()
336
+ };
337
+ },
338
+ Postfix(e) {
339
+ return e.toAST();
340
+ },
341
+ Arguments(first, _comma, rest) {
342
+ return [first.toAST(), ...rest.children.map((c) => c.toAST())];
343
+ },
344
+ Primary_paren(_lp, expr, _rp) {
345
+ return expr.toAST();
346
+ },
347
+ Primary_bracketedField(_lb, str, _rb) {
348
+ return {
349
+ type: "BracketedIdentifier",
350
+ name: str.toAST().value
351
+ };
352
+ },
353
+ Primary(e) {
354
+ return e.toAST();
355
+ },
356
+ number_float(_neg, _int, _dot, _frac) {
357
+ return {
358
+ type: "NumberLiteral",
359
+ value: Number.parseFloat(this.sourceString)
360
+ };
361
+ },
362
+ number_int(_neg, _digits) {
363
+ return {
364
+ type: "NumberLiteral",
365
+ value: Number.parseInt(this.sourceString, 10)
366
+ };
367
+ },
368
+ string(_open, chars, _close) {
369
+ return {
370
+ type: "StringLiteral",
371
+ value: chars.sourceString.replaceAll(/\\(.)/g, "$1")
372
+ };
373
+ },
374
+ boolean_true(_) {
375
+ return {
376
+ type: "BooleanLiteral",
377
+ value: true
378
+ };
379
+ },
380
+ boolean_false(_) {
381
+ return {
382
+ type: "BooleanLiteral",
383
+ value: false
384
+ };
385
+ },
386
+ null(_) {
387
+ return { type: "NullLiteral" };
388
+ },
389
+ identifier(_start, _rest) {
390
+ return {
391
+ type: "Identifier",
392
+ name: this.sourceString
393
+ };
394
+ },
395
+ rootPath(_slash, _path) {
396
+ return {
397
+ type: "RootPath",
398
+ path: this.sourceString
399
+ };
400
+ },
401
+ relativePath(_dotSlashes, _parts) {
402
+ return {
403
+ type: "RelativePath",
404
+ path: this.sourceString
405
+ };
406
+ },
407
+ contextToken_at(_at, _path) {
408
+ return {
409
+ type: "ContextToken",
410
+ name: this.sourceString
411
+ };
412
+ },
413
+ contextToken_hash(_hash, _path) {
414
+ return {
415
+ type: "ContextToken",
416
+ name: this.sourceString
417
+ };
418
+ },
419
+ _iter(...children) {
420
+ return childrenToAST(children);
421
+ },
422
+ _terminal() {
423
+ return {
424
+ type: "Identifier",
425
+ name: this.sourceString
426
+ };
427
+ }
428
+ });
429
+ semantics.addOperation("dependencies", {
430
+ identifier(_start, _rest) {
431
+ return [this.sourceString];
432
+ },
433
+ Primary_bracketedField(_lb, _str, _rb) {
434
+ return [this.sourceString];
435
+ },
436
+ rootPath(_slash, _path) {
437
+ return [this.sourceString];
438
+ },
439
+ relativePath(_dotSlashes, _parts) {
440
+ return [this.sourceString];
441
+ },
442
+ contextToken_at(_at, _path) {
443
+ return [];
444
+ },
445
+ contextToken_hash(_hash, _path) {
446
+ return [];
447
+ },
448
+ Postfix_property(obj, _dot, prop) {
449
+ const objDeps = obj.dependencies();
450
+ if (objDeps.length === 1) return [`${objDeps[0]}.${prop.sourceString}`];
451
+ return objDeps;
452
+ },
453
+ Postfix_index(obj, _lb, index, _rb) {
454
+ const objDeps = obj.dependencies();
455
+ const indexNode = index.toAST();
456
+ if (indexNode.type === "StringLiteral") {
457
+ const strValue = indexNode.value;
458
+ const quote = this.sourceString.includes("\"") ? "\"" : "'";
459
+ if (objDeps.length === 1) return [`${objDeps[0]}[${quote}${strValue}${quote}]`];
460
+ return objDeps;
461
+ }
462
+ const getNumericIndex = (node) => {
463
+ if (node.type === "NumberLiteral") return node.value;
464
+ if (node.type === "UnaryOp" && node.op === "-" && node.argument.type === "NumberLiteral") return -node.argument.value;
465
+ return null;
466
+ };
467
+ const numericIndex = getNumericIndex(indexNode);
468
+ if (objDeps.length === 1 && numericIndex !== null) return [`${objDeps[0]}[${numericIndex}]`];
469
+ return [...objDeps, ...index.dependencies()];
470
+ },
471
+ Postfix_wildcard(obj, _lb, _star, _rb) {
472
+ const objDeps = obj.dependencies();
473
+ if (objDeps.length === 1) return [`${objDeps[0]}[*]`];
474
+ return objDeps;
475
+ },
476
+ Postfix_call(callee, _lp, args, _rp) {
477
+ const calleeDeps = callee.dependencies();
478
+ const calleeAST = callee.toAST();
479
+ const argDeps = childrenDependencies(args.children);
480
+ if (calleeAST.type === "Identifier") return argDeps;
481
+ return [...calleeDeps, ...argDeps];
482
+ },
483
+ number_float(_neg, _int, _dot, _frac) {
484
+ return [];
485
+ },
486
+ number_int(_neg, _digits) {
487
+ return [];
488
+ },
489
+ string(_open, _chars, _close) {
490
+ return [];
491
+ },
492
+ boolean_true(_) {
493
+ return [];
494
+ },
495
+ boolean_false(_) {
496
+ return [];
497
+ },
498
+ null(_) {
499
+ return [];
500
+ },
501
+ _nonterminal(...children) {
502
+ return childrenDependencies(children);
503
+ },
504
+ _iter(...children) {
505
+ return childrenDependencies(children);
506
+ },
507
+ _terminal() {
508
+ return [];
509
+ }
510
+ });
511
+ const ARRAY_FUNCTIONS = new Set([
512
+ "sum",
513
+ "avg",
514
+ "count",
515
+ "first",
516
+ "last",
517
+ "join",
518
+ "includes"
519
+ ]);
520
+ semantics.addOperation("features", {
521
+ Primary_bracketedField(_lb, _str, _rb) {
522
+ return ["bracket_notation"];
523
+ },
524
+ rootPath(_slash, _path) {
525
+ const path = this.sourceString;
526
+ const features = ["root_path"];
527
+ if (path.includes(".")) features.push("nested_path");
528
+ return features;
529
+ },
530
+ relativePath(_dotSlashes, _parts) {
531
+ const path = this.sourceString;
532
+ const features = ["relative_path"];
533
+ if (path.replace(/^(\.\.\/)+/, "").includes(".")) features.push("nested_path");
534
+ return features;
535
+ },
536
+ contextToken_at(_at, _path) {
537
+ return ["context_token"];
538
+ },
539
+ contextToken_hash(_hash, _path) {
540
+ return ["context_token"];
541
+ },
542
+ Postfix_property(obj, _dot, _prop) {
543
+ return [...obj.features(), "nested_path"];
544
+ },
545
+ Postfix_index(obj, _lb, index, _rb) {
546
+ const objFeatures = obj.features();
547
+ const indexFeatures = index.features();
548
+ if (index.toAST().type === "StringLiteral") return [
549
+ ...objFeatures,
550
+ ...indexFeatures,
551
+ "bracket_notation"
552
+ ];
553
+ return [
554
+ ...objFeatures,
555
+ ...indexFeatures,
556
+ "array_index"
557
+ ];
558
+ },
559
+ Postfix_wildcard(obj, _lb, _star, _rb) {
560
+ return [...obj.features(), "array_wildcard"];
561
+ },
562
+ Postfix_call(callee, _lp, args, _rp) {
563
+ const calleeAST = callee.toAST();
564
+ const argFeatures = childrenFeatures(args.children);
565
+ if (calleeAST.type === "Identifier" && ARRAY_FUNCTIONS.has(calleeAST.name.toLowerCase())) return [...argFeatures, "array_function"];
566
+ return argFeatures;
567
+ },
568
+ _nonterminal(...children) {
569
+ return childrenFeatures(children);
570
+ },
571
+ _iter(...children) {
572
+ return childrenFeatures(children);
573
+ },
574
+ _terminal() {
575
+ return [];
576
+ }
577
+ });
578
+ const BUILTINS = {
579
+ and: (a, b) => Boolean(a) && Boolean(b),
580
+ or: (a, b) => Boolean(a) || Boolean(b),
581
+ not: (a) => !a,
582
+ concat: (...args) => args.map(String).join(""),
583
+ upper: (s) => String(s).toUpperCase(),
584
+ lower: (s) => String(s).toLowerCase(),
585
+ trim: (s) => String(s).trim(),
586
+ left: (s, n) => String(s).slice(0, Math.max(0, Math.floor(Number(n)))),
587
+ right: (s, n) => {
588
+ const str = String(s);
589
+ const count = Math.max(0, Math.floor(Number(n)));
590
+ return count === 0 ? "" : str.slice(-count);
591
+ },
592
+ replace: (s, search, repl) => String(s).replace(String(search), String(repl)),
593
+ tostring: String,
594
+ length: (s) => {
595
+ if (Array.isArray(s)) return s.length;
596
+ if (typeof s === "string") return s.length;
597
+ if (s !== null && typeof s === "object") return Object.keys(s).length;
598
+ return String(s).length;
599
+ },
600
+ contains: (s, search) => String(s).includes(String(search)),
601
+ startswith: (s, search) => String(s).startsWith(String(search)),
602
+ endswith: (s, search) => String(s).endsWith(String(search)),
603
+ join: (arr, sep) => {
604
+ if (!Array.isArray(arr)) return "";
605
+ if (sep === void 0 || sep === null) return arr.join(",");
606
+ if (typeof sep !== "string" && typeof sep !== "number") return arr.join(",");
607
+ return arr.join(String(sep));
608
+ },
609
+ tonumber: Number,
610
+ toboolean: Boolean,
611
+ isnull: (v) => v === null || v === void 0,
612
+ coalesce: (...args) => args.find((v) => v !== null && v !== void 0) ?? null,
613
+ round: (n, dec) => {
614
+ const factor = 10 ** (dec === void 0 ? 0 : Number(dec));
615
+ return Math.round(Number(n) * factor) / factor;
616
+ },
617
+ floor: (n) => Math.floor(Number(n)),
618
+ ceil: (n) => Math.ceil(Number(n)),
619
+ abs: (n) => Math.abs(Number(n)),
620
+ sqrt: (n) => Math.sqrt(Number(n)),
621
+ pow: (base, exp) => Math.pow(Number(base), Number(exp)),
622
+ min: (...args) => args.length === 0 ? NaN : Math.min(...args.map(Number)),
623
+ max: (...args) => args.length === 0 ? NaN : Math.max(...args.map(Number)),
624
+ log: (n) => Math.log(Number(n)),
625
+ log10: (n) => Math.log10(Number(n)),
626
+ exp: (n) => Math.exp(Number(n)),
627
+ sign: (n) => Math.sign(Number(n)),
628
+ sum: (arr) => Array.isArray(arr) ? arr.reduce((a, b) => a + Number(b), 0) : 0,
629
+ avg: (arr) => Array.isArray(arr) && arr.length > 0 ? arr.reduce((a, b) => a + Number(b), 0) / arr.length : 0,
630
+ count: (arr) => Array.isArray(arr) ? arr.length : 0,
631
+ first: (arr) => Array.isArray(arr) ? arr[0] : void 0,
632
+ last: (arr) => Array.isArray(arr) ? arr.at(-1) : void 0,
633
+ includes: (arr, val) => Array.isArray(arr) ? arr.includes(val) : false,
634
+ if: (cond, ifTrue, ifFalse) => cond ? ifTrue : ifFalse
635
+ };
636
+ function getByPath(obj, path) {
637
+ const parts = path.split(".");
638
+ let current = obj;
639
+ for (const part of parts) {
640
+ if (current === null || current === void 0) return void 0;
641
+ current = current[part];
642
+ }
643
+ return current;
644
+ }
645
+ semantics.addOperation("eval(ctx)", {
646
+ Expression(e) {
647
+ return e.eval(this.args.ctx);
648
+ },
649
+ Ternary_ternary(cond, _q, cons, _c, alt) {
650
+ return cond.eval(this.args.ctx) ? cons.eval(this.args.ctx) : alt.eval(this.args.ctx);
651
+ },
652
+ Ternary(e) {
653
+ return e.eval(this.args.ctx);
654
+ },
655
+ LogicalOr_or(left, _op, right) {
656
+ return left.eval(this.args.ctx) || right.eval(this.args.ctx);
657
+ },
658
+ LogicalOr(e) {
659
+ return e.eval(this.args.ctx);
660
+ },
661
+ LogicalAnd_and(left, _op, right) {
662
+ return left.eval(this.args.ctx) && right.eval(this.args.ctx);
663
+ },
664
+ LogicalAnd(e) {
665
+ return e.eval(this.args.ctx);
666
+ },
667
+ Equality_eq(left, _op, right) {
668
+ return left.eval(this.args.ctx) == right.eval(this.args.ctx);
669
+ },
670
+ Equality_neq(left, _op, right) {
671
+ return left.eval(this.args.ctx) != right.eval(this.args.ctx);
672
+ },
673
+ Equality(e) {
674
+ return e.eval(this.args.ctx);
675
+ },
676
+ Comparison_gte(left, _op, right) {
677
+ return left.eval(this.args.ctx) >= right.eval(this.args.ctx);
678
+ },
679
+ Comparison_lte(left, _op, right) {
680
+ return left.eval(this.args.ctx) <= right.eval(this.args.ctx);
681
+ },
682
+ Comparison_gt(left, _op, right) {
683
+ return left.eval(this.args.ctx) > right.eval(this.args.ctx);
684
+ },
685
+ Comparison_lt(left, _op, right) {
686
+ return left.eval(this.args.ctx) < right.eval(this.args.ctx);
687
+ },
688
+ Comparison(e) {
689
+ return e.eval(this.args.ctx);
690
+ },
691
+ Additive_plus(left, _op, right) {
692
+ const l = left.eval(this.args.ctx);
693
+ const r = right.eval(this.args.ctx);
694
+ if (typeof l === "string" || typeof r === "string") return String(l) + String(r);
695
+ return l + r;
696
+ },
697
+ Additive_minus(left, _op, right) {
698
+ return left.eval(this.args.ctx) - right.eval(this.args.ctx);
699
+ },
700
+ Additive(e) {
701
+ return e.eval(this.args.ctx);
702
+ },
703
+ Multiplicative_times(left, _op, right) {
704
+ return left.eval(this.args.ctx) * right.eval(this.args.ctx);
705
+ },
706
+ Multiplicative_div(left, _op, right) {
707
+ return left.eval(this.args.ctx) / right.eval(this.args.ctx);
708
+ },
709
+ Multiplicative_mod(left, _op, right) {
710
+ return left.eval(this.args.ctx) % right.eval(this.args.ctx);
711
+ },
712
+ Multiplicative(e) {
713
+ return e.eval(this.args.ctx);
714
+ },
715
+ Unary_neg(_op, expr) {
716
+ return -expr.eval(this.args.ctx);
717
+ },
718
+ Unary_not(_op, expr) {
719
+ return !expr.eval(this.args.ctx);
720
+ },
721
+ Unary(e) {
722
+ return e.eval(this.args.ctx);
723
+ },
724
+ Postfix_call(callee, _lp, args, _rp) {
725
+ const calleeAST = callee.toAST();
726
+ const getArgValues = () => {
727
+ const argsNode = args.children[0];
728
+ if (!argsNode) return [];
729
+ return argsNode.eval(this.args.ctx);
730
+ };
731
+ if (calleeAST.type === "Identifier") {
732
+ const builtinFn = BUILTINS[calleeAST.name.toLowerCase()];
733
+ if (builtinFn) return builtinFn(...getArgValues());
734
+ }
735
+ const fn = callee.eval(this.args.ctx);
736
+ if (typeof fn === "function") return fn(...getArgValues());
737
+ const calleeName = calleeAST.type === "Identifier" ? calleeAST.name : "expression";
738
+ throw new Error(`'${calleeName}' is not a function`);
739
+ },
740
+ Postfix_property(obj, _dot, prop) {
741
+ return obj.eval(this.args.ctx)?.[prop.sourceString];
742
+ },
743
+ Postfix_index(obj, _lb, index, _rb) {
744
+ const objVal = obj.eval(this.args.ctx);
745
+ const idx = index.eval(this.args.ctx);
746
+ if (typeof idx === "string") {
747
+ if (!isSafePropertyName(idx)) return;
748
+ return objVal?.[idx];
749
+ }
750
+ const numIdx = idx;
751
+ if (numIdx < 0) return objVal?.[objVal.length + numIdx];
752
+ return objVal?.[numIdx];
753
+ },
754
+ Postfix_wildcard(obj, _lb, _star, _rb) {
755
+ return obj.eval(this.args.ctx);
756
+ },
757
+ Postfix(e) {
758
+ return e.eval(this.args.ctx);
759
+ },
760
+ Arguments(first, _comma, rest) {
761
+ return [first.eval(this.args.ctx), ...rest.children.map((c) => c.eval(this.args.ctx))];
762
+ },
763
+ Primary_paren(_lp, expr, _rp) {
764
+ return expr.eval(this.args.ctx);
765
+ },
766
+ Primary_bracketedField(_lb, str, _rb) {
767
+ const fieldName = str.eval(this.args.ctx);
768
+ if (!isSafePropertyName(fieldName)) return;
769
+ return this.args.ctx[fieldName];
770
+ },
771
+ Primary(e) {
772
+ return e.eval(this.args.ctx);
773
+ },
774
+ number_float(_neg, _int, _dot, _frac) {
775
+ return Number.parseFloat(this.sourceString);
776
+ },
777
+ number_int(_neg, _digits) {
778
+ return Number.parseInt(this.sourceString, 10);
779
+ },
780
+ string(_open, chars, _close) {
781
+ return chars.sourceString.replaceAll(/\\(.)/g, "$1");
782
+ },
783
+ boolean_true(_) {
784
+ return true;
785
+ },
786
+ boolean_false(_) {
787
+ return false;
788
+ },
789
+ null(_) {
790
+ return null;
791
+ },
792
+ identifier(_start, _rest) {
793
+ const name = this.sourceString;
794
+ return this.args.ctx[name];
795
+ },
796
+ rootPath(_slash, _path) {
797
+ const fullPath = this.sourceString;
798
+ if (fullPath in this.args.ctx) return this.args.ctx[fullPath];
799
+ const parts = fullPath.slice(1).split(".");
800
+ const firstPart = parts[0];
801
+ if (!firstPart) return void 0;
802
+ const rootKey = "/" + firstPart;
803
+ let value = this.args.ctx[rootKey];
804
+ for (let i = 1; i < parts.length; i++) {
805
+ if (value === null || value === void 0) return void 0;
806
+ const part = parts[i];
807
+ if (!part) continue;
808
+ value = value[part];
809
+ }
810
+ return value;
811
+ },
812
+ relativePath(_dotSlashes, _parts) {
813
+ const fullPath = this.sourceString;
814
+ if (fullPath in this.args.ctx) return this.args.ctx[fullPath];
815
+ const path = fullPath.replace(/^(\.\.\/)+/, "");
816
+ return getByPath(this.args.ctx, path);
817
+ },
818
+ contextToken_at(_at, _path) {
819
+ return this.args.ctx[this.sourceString];
820
+ },
821
+ contextToken_hash(_hash, _path) {
822
+ return this.args.ctx[this.sourceString];
823
+ },
824
+ _nonterminal(...children) {
825
+ const ctx = this.args.ctx;
826
+ for (const child of children) if ("eval" in child) return child.eval(ctx);
827
+ },
828
+ _iter(...children) {
829
+ return children.map((c) => c.eval(this.args.ctx));
830
+ },
831
+ _terminal() {}
832
+ });
833
+
834
+ //#endregion
835
+ //#region src/ohm/core/parser.ts
836
+ function detectMinVersion(features) {
837
+ if (features.includes("context_token")) return "1.2";
838
+ if (features.length > 0) return "1.1";
839
+ return "1.0";
840
+ }
841
+ function parseFormula(expression) {
842
+ const trimmed = expression.trim();
843
+ if (!trimmed) throw new Error("Empty expression");
844
+ const matchResult = grammar.match(trimmed);
845
+ if (matchResult.failed()) throw new Error(matchResult.message ?? "Parse error");
846
+ const adapter = semantics(matchResult);
847
+ const ast = adapter.toAST();
848
+ const dependencies = [...new Set(adapter.dependencies())];
849
+ const allFeatures = adapter.features();
850
+ const features = [...new Set(allFeatures)];
851
+ return {
852
+ ast,
853
+ dependencies,
854
+ features,
855
+ minVersion: detectMinVersion(features)
856
+ };
857
+ }
858
+ function validateSyntax(expression) {
859
+ const trimmed = expression.trim();
860
+ if (!trimmed) return {
861
+ isValid: false,
862
+ error: "Empty expression"
863
+ };
864
+ const matchResult = grammar.match(trimmed);
865
+ if (matchResult.failed()) {
866
+ const pos = matchResult.getRightmostFailurePosition?.();
867
+ return {
868
+ isValid: false,
869
+ error: matchResult.message ?? "Parse error",
870
+ position: pos
871
+ };
872
+ }
873
+ return { isValid: true };
874
+ }
875
+ function evaluate(expression, context) {
876
+ const trimmed = expression.trim();
877
+ if (!trimmed) throw new Error("Empty expression");
878
+ const matchResult = grammar.match(trimmed);
879
+ if (matchResult.failed()) throw new Error(matchResult.message ?? "Parse error");
880
+ const safeContext = {};
881
+ for (const [key, value] of Object.entries(context)) if (typeof value !== "function") safeContext[key] = value;
882
+ return semantics(matchResult).eval(safeContext);
883
+ }
884
+ function parseCurrentPath(currentPath) {
885
+ const segments = [];
886
+ let current = "";
887
+ let inBracket = false;
888
+ for (const char of currentPath) if (char === "[") {
889
+ inBracket = true;
890
+ current += char;
891
+ } else if (char === "]") {
892
+ inBracket = false;
893
+ current += char;
894
+ } else if (char === "." && !inBracket) {
895
+ if (current) {
896
+ segments.push(current);
897
+ current = "";
898
+ }
899
+ } else current += char;
900
+ if (current) segments.push(current);
901
+ return segments;
902
+ }
903
+ function getValueByPath(data, path) {
904
+ const segments = parseCurrentPath(path);
905
+ let current = data;
906
+ for (const segment of segments) {
907
+ if (current === null || current === void 0) return;
908
+ if (typeof current !== "object") return;
909
+ const bracketMatch = /^([^[]+)\[(\d+)\]$/.exec(segment);
910
+ if (bracketMatch?.[1] && bracketMatch[2]) {
911
+ const fieldName = bracketMatch[1];
912
+ const index = Number.parseInt(bracketMatch[2], 10);
913
+ const arr = current[fieldName];
914
+ if (!Array.isArray(arr)) return;
915
+ current = arr[index];
916
+ } else current = current[segment];
917
+ }
918
+ return current;
919
+ }
920
+ function extractRootField(fieldPath) {
921
+ const dotIndex = fieldPath.indexOf(".");
922
+ const bracketIndex = fieldPath.indexOf("[");
923
+ if (dotIndex === -1 && bracketIndex === -1) return fieldPath;
924
+ if (dotIndex === -1) return fieldPath.slice(0, bracketIndex);
925
+ if (bracketIndex === -1) return fieldPath.slice(0, dotIndex);
926
+ return fieldPath.slice(0, Math.min(dotIndex, bracketIndex));
927
+ }
928
+ function countParentLevels(path) {
929
+ let count = 0;
930
+ let remaining = path;
931
+ while (remaining.startsWith("../")) {
932
+ count++;
933
+ remaining = remaining.slice(3);
934
+ }
935
+ return count;
936
+ }
937
+ function getPathAfterParents(path) {
938
+ return path.replace(/^(\.\.\/)+/, "");
939
+ }
940
+ function resolveRelativePath(rootData, currentPath, relativePath) {
941
+ const parentLevels = countParentLevels(relativePath);
942
+ const fieldPath = getPathAfterParents(relativePath);
943
+ if (!currentPath) return getValueByPath(rootData, fieldPath);
944
+ const pathSegments = parseCurrentPath(currentPath);
945
+ const targetLevel = pathSegments.length - parentLevels;
946
+ if (targetLevel <= 0) return getValueByPath(rootData, fieldPath);
947
+ const basePath = pathSegments.slice(0, targetLevel).join(".");
948
+ return getValueByPath(rootData, basePath ? `${basePath}.${fieldPath}` : fieldPath);
949
+ }
950
+ function extractRelativeBase(relativePath) {
951
+ const pathAfterParents = getPathAfterParents(relativePath);
952
+ const baseField = extractRootField(pathAfterParents);
953
+ return relativePath.slice(0, relativePath.length - pathAfterParents.length) + baseField;
954
+ }
955
+ function buildArrayContextTokens(arrayContext) {
956
+ if (!arrayContext || arrayContext.levels.length === 0) return {};
957
+ const tokens = {};
958
+ const levels = arrayContext.levels;
959
+ const buildLevelTokens = (level, prefix) => {
960
+ tokens[`${prefix}index`] = level.index;
961
+ tokens[`${prefix}length`] = level.length;
962
+ tokens[`${prefix}first`] = level.index === 0;
963
+ tokens[`${prefix}last`] = level.index === level.length - 1;
964
+ };
965
+ const buildNeighborTokens = (level, prefix) => {
966
+ tokens[`${prefix}prev`] = level.prev;
967
+ tokens[`${prefix}next`] = level.next;
968
+ };
969
+ if (levels[0]) {
970
+ buildLevelTokens(levels[0], "#");
971
+ buildNeighborTokens(levels[0], "@");
972
+ }
973
+ let parentPrefix = "#parent.";
974
+ let atParentPrefix = "@parent.";
975
+ for (let i = 1; i < levels.length; i++) {
976
+ const level = levels[i];
977
+ if (level) {
978
+ buildLevelTokens(level, parentPrefix);
979
+ buildNeighborTokens(level, atParentPrefix);
980
+ }
981
+ parentPrefix = "#parent." + parentPrefix.slice(1);
982
+ atParentPrefix = "@parent." + atParentPrefix.slice(1);
983
+ }
984
+ const rootLevel = levels.at(-1);
985
+ if (rootLevel) {
986
+ tokens["#root.index"] = rootLevel.index;
987
+ tokens["#root.length"] = rootLevel.length;
988
+ tokens["#root.first"] = rootLevel.index === 0;
989
+ tokens["#root.last"] = rootLevel.index === rootLevel.length - 1;
990
+ tokens["@root.prev"] = rootLevel.prev;
991
+ tokens["@root.next"] = rootLevel.next;
992
+ }
993
+ return tokens;
994
+ }
995
+ function buildPathReferences(rootData, dependencies, currentPath) {
996
+ const refs = {};
997
+ for (const dep of dependencies) if (dep.startsWith("/")) {
998
+ const rootField = extractRootField(dep.slice(1));
999
+ const contextKey = "/" + rootField;
1000
+ if (!(contextKey in refs)) refs[contextKey] = getValueByPath(rootData, rootField);
1001
+ } else if (dep.startsWith("../")) {
1002
+ const contextKey = extractRelativeBase(dep);
1003
+ if (!(contextKey in refs)) refs[contextKey] = resolveRelativePath(rootData, currentPath, contextKey);
1004
+ }
1005
+ return refs;
1006
+ }
1007
+ function evaluateWithContext(expression, options) {
1008
+ const { rootData, itemData, currentPath, arrayContext } = options;
1009
+ const trimmed = expression.trim();
1010
+ if (!trimmed) throw new Error("Empty expression");
1011
+ const pathRefs = buildPathReferences(rootData, parseFormula(trimmed).dependencies, currentPath);
1012
+ const arrayTokens = buildArrayContextTokens(arrayContext);
1013
+ return evaluate(trimmed, {
1014
+ ...rootData,
1015
+ ...itemData,
1016
+ ...pathRefs,
1017
+ ...arrayTokens
1018
+ });
1019
+ }
1020
+ const ARITHMETIC_OPS = new Set([
1021
+ "+",
1022
+ "-",
1023
+ "*",
1024
+ "/",
1025
+ "%"
1026
+ ]);
1027
+ const COMPARISON_OPS = new Set([
1028
+ "<",
1029
+ ">",
1030
+ "<=",
1031
+ ">=",
1032
+ "==",
1033
+ "!="
1034
+ ]);
1035
+ const LOGICAL_OPS = new Set([
1036
+ "&&",
1037
+ "||",
1038
+ "!"
1039
+ ]);
1040
+ const NUMERIC_FUNCTIONS = new Set([
1041
+ "round",
1042
+ "floor",
1043
+ "ceil",
1044
+ "abs",
1045
+ "sqrt",
1046
+ "pow",
1047
+ "min",
1048
+ "max",
1049
+ "log",
1050
+ "log10",
1051
+ "exp",
1052
+ "sign",
1053
+ "sum",
1054
+ "avg",
1055
+ "count",
1056
+ "tonumber",
1057
+ "length"
1058
+ ]);
1059
+ const STRING_FUNCTIONS = new Set([
1060
+ "concat",
1061
+ "upper",
1062
+ "lower",
1063
+ "trim",
1064
+ "left",
1065
+ "right",
1066
+ "replace",
1067
+ "tostring",
1068
+ "join"
1069
+ ]);
1070
+ const BOOLEAN_FUNCTIONS = new Set([
1071
+ "and",
1072
+ "or",
1073
+ "not",
1074
+ "contains",
1075
+ "startswith",
1076
+ "endswith",
1077
+ "isnull",
1078
+ "toboolean",
1079
+ "includes"
1080
+ ]);
1081
+ function getFieldType(path, fieldTypes) {
1082
+ const schemaType = fieldTypes[path.split(".")[0]?.split("[")[0] || path];
1083
+ if (schemaType === "number") return "number";
1084
+ if (schemaType === "string") return "string";
1085
+ if (schemaType === "boolean") return "boolean";
1086
+ return "unknown";
1087
+ }
1088
+ function inferLiteralType(node) {
1089
+ switch (node.type) {
1090
+ case "NumberLiteral": return "number";
1091
+ case "BooleanLiteral": return "boolean";
1092
+ case "StringLiteral": return "string";
1093
+ case "NullLiteral": return "unknown";
1094
+ default: return null;
1095
+ }
1096
+ }
1097
+ function inferBinaryOpType(node, fieldTypes) {
1098
+ const { op } = node;
1099
+ if (op === "+") {
1100
+ const leftType = inferTypeFromAST(node.left, fieldTypes);
1101
+ const rightType = inferTypeFromAST(node.right, fieldTypes);
1102
+ if (leftType === "string" || rightType === "string") return "string";
1103
+ if (leftType === "unknown" || rightType === "unknown") return "unknown";
1104
+ return "number";
1105
+ }
1106
+ if (ARITHMETIC_OPS.has(op)) return "number";
1107
+ if (COMPARISON_OPS.has(op)) return "boolean";
1108
+ if (LOGICAL_OPS.has(op)) return "boolean";
1109
+ return "unknown";
1110
+ }
1111
+ function inferCallExpressionType(node, fieldTypes) {
1112
+ const funcName = node.callee.type === "Identifier" ? node.callee.name.toLowerCase() : "";
1113
+ if (NUMERIC_FUNCTIONS.has(funcName)) return "number";
1114
+ if (STRING_FUNCTIONS.has(funcName)) return "string";
1115
+ if (BOOLEAN_FUNCTIONS.has(funcName)) return "boolean";
1116
+ if (funcName === "if" && node.arguments.length >= 3) {
1117
+ const thenArg = node.arguments[1];
1118
+ const elseArg = node.arguments[2];
1119
+ if (thenArg && elseArg) {
1120
+ const thenType = inferTypeFromAST(thenArg, fieldTypes);
1121
+ if (thenType === inferTypeFromAST(elseArg, fieldTypes)) return thenType;
1122
+ }
1123
+ }
1124
+ return "unknown";
1125
+ }
1126
+ function inferTypeFromAST(node, fieldTypes) {
1127
+ const literalType = inferLiteralType(node);
1128
+ if (literalType !== null) return literalType;
1129
+ switch (node.type) {
1130
+ case "Identifier": return getFieldType(node.name, fieldTypes);
1131
+ case "RootPath":
1132
+ case "RelativePath":
1133
+ case "ContextToken":
1134
+ case "IndexExpression":
1135
+ case "WildcardExpression": return "unknown";
1136
+ case "MemberExpression": {
1137
+ const objectType = inferTypeFromAST(node.object, fieldTypes);
1138
+ if (objectType !== "unknown") return objectType;
1139
+ if (node.object.type === "Identifier") return getFieldType(`${node.object.name}.${node.property}`, fieldTypes);
1140
+ return "unknown";
1141
+ }
1142
+ case "BinaryOp": return inferBinaryOpType(node, fieldTypes);
1143
+ case "UnaryOp":
1144
+ if (node.op === "-") return "number";
1145
+ if (node.op === "!") return "boolean";
1146
+ return "unknown";
1147
+ case "TernaryOp": {
1148
+ const thenType = inferTypeFromAST(node.consequent, fieldTypes);
1149
+ return thenType === inferTypeFromAST(node.alternate, fieldTypes) ? thenType : "unknown";
1150
+ }
1151
+ case "CallExpression": return inferCallExpressionType(node, fieldTypes);
1152
+ default: return "unknown";
1153
+ }
1154
+ }
1155
+ function inferFormulaType(expression, fieldTypes = {}) {
1156
+ const trimmed = expression.trim();
1157
+ if (!trimmed) return "unknown";
1158
+ try {
1159
+ const { ast } = parseFormula(trimmed);
1160
+ return inferTypeFromAST(ast, fieldTypes);
1161
+ } catch {
1162
+ return "unknown";
1163
+ }
1164
+ }
1165
+
1166
+ //#endregion
1167
+ //#region src/ohm/core/serialize-ast.ts
1168
+ function escapeDoubleQuoted(value) {
1169
+ let result = "";
1170
+ for (const char of value) switch (char) {
1171
+ case "\\":
1172
+ result += String.raw`\\`;
1173
+ break;
1174
+ case "\"":
1175
+ result += String.raw`\"`;
1176
+ break;
1177
+ case "\n":
1178
+ result += String.raw`\n`;
1179
+ break;
1180
+ case "\r":
1181
+ result += String.raw`\r`;
1182
+ break;
1183
+ case " ":
1184
+ result += String.raw`\t`;
1185
+ break;
1186
+ default: result += char;
1187
+ }
1188
+ return result;
1189
+ }
1190
+ const PRECEDENCE = {
1191
+ "||": 1,
1192
+ "&&": 2,
1193
+ "==": 3,
1194
+ "!=": 3,
1195
+ ">": 4,
1196
+ "<": 4,
1197
+ ">=": 4,
1198
+ "<=": 4,
1199
+ "+": 5,
1200
+ "-": 5,
1201
+ "*": 6,
1202
+ "/": 6,
1203
+ "%": 6
1204
+ };
1205
+ function serializeBinaryOp(node) {
1206
+ const prec = PRECEDENCE[node.op] ?? 0;
1207
+ const wrapLeft = (child) => {
1208
+ const s = serializeNode(child);
1209
+ if (child.type === "BinaryOp") {
1210
+ if ((PRECEDENCE[child.op] ?? 0) < prec) return `(${s})`;
1211
+ }
1212
+ if (child.type === "TernaryOp") return `(${s})`;
1213
+ return s;
1214
+ };
1215
+ const wrapRight = (child) => {
1216
+ const s = serializeNode(child);
1217
+ if (child.type === "BinaryOp") {
1218
+ if ((PRECEDENCE[child.op] ?? 0) <= prec) return `(${s})`;
1219
+ }
1220
+ if (child.type === "TernaryOp") return `(${s})`;
1221
+ return s;
1222
+ };
1223
+ return `${wrapLeft(node.left)} ${node.op} ${wrapRight(node.right)}`;
1224
+ }
1225
+ function serializeNode(node) {
1226
+ switch (node.type) {
1227
+ case "NumberLiteral": return String(node.value);
1228
+ case "StringLiteral": return `"${escapeDoubleQuoted(node.value)}"`;
1229
+ case "BooleanLiteral": return String(node.value);
1230
+ case "NullLiteral": return "null";
1231
+ case "Identifier": return node.name;
1232
+ case "BracketedIdentifier": return `["${escapeDoubleQuoted(node.name)}"]`;
1233
+ case "RootPath": return node.path;
1234
+ case "RelativePath": return node.path;
1235
+ case "ContextToken": return node.name;
1236
+ case "BinaryOp": return serializeBinaryOp(node);
1237
+ case "UnaryOp": return `${node.op}${serializeUnaryArgument(node)}`;
1238
+ case "TernaryOp": return `${serializeTernaryCondition(node.condition)} ? ${serializeNode(node.consequent)} : ${serializeNode(node.alternate)}`;
1239
+ case "CallExpression": return `${serializeNode(node.callee)}(${node.arguments.map(serializeNode).join(", ")})`;
1240
+ case "MemberExpression": return `${serializePostfixObject(node.object)}.${node.property}`;
1241
+ case "BracketedMemberExpression": return `${serializePostfixObject(node.object)}["${escapeDoubleQuoted(node.property)}"]`;
1242
+ case "IndexExpression": return `${serializePostfixObject(node.object)}[${serializeNode(node.index)}]`;
1243
+ case "WildcardExpression": return `${serializePostfixObject(node.object)}[*]`;
1244
+ }
1245
+ }
1246
+ function serializeUnaryArgument(node) {
1247
+ const s = serializeNode(node.argument);
1248
+ if (node.argument.type === "BinaryOp" || node.argument.type === "TernaryOp") return `(${s})`;
1249
+ return s;
1250
+ }
1251
+ function serializeTernaryCondition(node) {
1252
+ const s = serializeNode(node);
1253
+ if (node.type === "TernaryOp") return `(${s})`;
1254
+ return s;
1255
+ }
1256
+ function serializePostfixObject(node) {
1257
+ const s = serializeNode(node);
1258
+ if (node.type === "BinaryOp" || node.type === "TernaryOp" || node.type === "UnaryOp") return `(${s})`;
1259
+ return s;
1260
+ }
1261
+ function serializeAst(ast) {
1262
+ return serializeNode(ast);
1263
+ }
1264
+
1265
+ //#endregion
1266
+ //#region src/ohm/core/replace-dependencies.ts
1267
+ function collectDependencyPath(node) {
1268
+ switch (node.type) {
1269
+ case "Identifier": return node.name;
1270
+ case "BracketedIdentifier": return `["${node.name}"]`;
1271
+ case "RootPath": return node.path;
1272
+ case "RelativePath": return node.path;
1273
+ case "MemberExpression": {
1274
+ const objPath = collectDependencyPath(node.object);
1275
+ if (objPath === null) return null;
1276
+ return `${objPath}.${node.property}`;
1277
+ }
1278
+ case "BracketedMemberExpression": {
1279
+ const objPath = collectDependencyPath(node.object);
1280
+ if (objPath === null) return null;
1281
+ const quote = "\"";
1282
+ return `${objPath}[${quote}${node.property}${quote}]`;
1283
+ }
1284
+ case "IndexExpression": {
1285
+ const objPath = collectDependencyPath(node.object);
1286
+ if (objPath === null) return null;
1287
+ const numericIndex = getNumericIndex(node.index);
1288
+ if (numericIndex !== null) return `${objPath}[${numericIndex}]`;
1289
+ return null;
1290
+ }
1291
+ case "WildcardExpression": {
1292
+ const objPath = collectDependencyPath(node.object);
1293
+ if (objPath === null) return null;
1294
+ return `${objPath}[*]`;
1295
+ }
1296
+ default: return null;
1297
+ }
1298
+ }
1299
+ function getNumericIndex(node) {
1300
+ if (node.type === "NumberLiteral") return node.value;
1301
+ if (node.type === "UnaryOp" && node.op === "-" && node.argument.type === "NumberLiteral") return -node.argument.value;
1302
+ return null;
1303
+ }
1304
+ function findMatch(node, replacements) {
1305
+ const fullPath = collectDependencyPath(node);
1306
+ if (fullPath !== null) {
1307
+ const value = replacements[fullPath];
1308
+ if (value !== void 0) return value;
1309
+ }
1310
+ return null;
1311
+ }
1312
+ function pathToAst(path) {
1313
+ const { ast } = parseFormula(path);
1314
+ return ast;
1315
+ }
1316
+ function replaceNode(node, replacements) {
1317
+ const replacement = findMatch(node, replacements);
1318
+ if (replacement !== null) return pathToAst(replacement);
1319
+ switch (node.type) {
1320
+ case "NumberLiteral":
1321
+ case "StringLiteral":
1322
+ case "BooleanLiteral":
1323
+ case "NullLiteral":
1324
+ case "Identifier":
1325
+ case "BracketedIdentifier":
1326
+ case "RootPath":
1327
+ case "RelativePath":
1328
+ case "ContextToken": return node;
1329
+ case "BinaryOp": return {
1330
+ ...node,
1331
+ left: replaceNode(node.left, replacements),
1332
+ right: replaceNode(node.right, replacements)
1333
+ };
1334
+ case "UnaryOp": return {
1335
+ ...node,
1336
+ argument: replaceNode(node.argument, replacements)
1337
+ };
1338
+ case "TernaryOp": return {
1339
+ ...node,
1340
+ condition: replaceNode(node.condition, replacements),
1341
+ consequent: replaceNode(node.consequent, replacements),
1342
+ alternate: replaceNode(node.alternate, replacements)
1343
+ };
1344
+ case "CallExpression": {
1345
+ const isSimpleFunctionCall = node.callee.type === "Identifier";
1346
+ return {
1347
+ ...node,
1348
+ callee: isSimpleFunctionCall ? node.callee : replaceNode(node.callee, replacements),
1349
+ arguments: node.arguments.map((arg) => replaceNode(arg, replacements))
1350
+ };
1351
+ }
1352
+ case "MemberExpression":
1353
+ case "BracketedMemberExpression":
1354
+ case "WildcardExpression": return {
1355
+ ...node,
1356
+ object: replaceNode(node.object, replacements)
1357
+ };
1358
+ case "IndexExpression": return {
1359
+ ...node,
1360
+ object: replaceNode(node.object, replacements),
1361
+ index: replaceNode(node.index, replacements)
1362
+ };
1363
+ }
1364
+ }
1365
+ function replaceDependencies(ast, replacements) {
1366
+ return replaceNode(ast, replacements);
1367
+ }
1368
+
1369
+ //#endregion
1370
+ //#region src/parse-formula.ts
1371
+ /**
1372
+ * Parse a formula expression string
1373
+ *
1374
+ * @param expression - Formula expression string
1375
+ * @returns Parsed expression with dependencies and version info
1376
+ *
1377
+ * @example
1378
+ * parseExpression('price * 1.1')
1379
+ * // { expression: 'price * 1.1', dependencies: ['price'], minVersion: '1.0', features: [] }
1380
+ *
1381
+ * parseExpression('stats.damage * multiplier')
1382
+ * // { expression: '...', dependencies: ['stats.damage', 'multiplier'], minVersion: '1.1', features: ['nested_path'] }
1383
+ */
1384
+ function parseExpression(expression) {
1385
+ const result = parseFormula(expression);
1386
+ return {
1387
+ expression,
1388
+ dependencies: result.dependencies,
1389
+ minVersion: result.minVersion,
1390
+ features: result.features
1391
+ };
1392
+ }
1393
+
1394
+ //#endregion
1395
+ //#region src/validate-syntax.ts
1396
+ /**
1397
+ * Validate formula expression syntax
1398
+ *
1399
+ * @param expression - Formula expression string
1400
+ * @returns Validation result with error details if invalid
1401
+ *
1402
+ * @example
1403
+ * validateFormulaSyntax('price * 1.1')
1404
+ * // { isValid: true }
1405
+ *
1406
+ * validateFormulaSyntax('price * (1.1')
1407
+ * // { isValid: false, error: 'Unclosed (', position: 8 }
1408
+ */
1409
+ function validateFormulaSyntax(expression) {
1410
+ const result = validateSyntax(expression);
1411
+ if (result.isValid) return { isValid: true };
1412
+ return {
1413
+ isValid: false,
1414
+ error: result.error,
1415
+ position: result.position
1416
+ };
1417
+ }
1418
+
1419
+ //#endregion
1420
+ export { evaluate as a, parseFormula as c, serializeAst as i, validateSyntax as l, parseExpression as n, evaluateWithContext as o, replaceDependencies as r, inferFormulaType as s, validateFormulaSyntax as t };
1421
+ //# sourceMappingURL=validate-syntax-CmqnFrsc.mjs.map