@revisium/formula 0.4.0 → 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/chunk-GOMUE724.cjs +1114 -0
- package/dist/chunk-GOMUE724.cjs.map +1 -0
- package/dist/chunk-LFEHEGBL.js +1086 -0
- package/dist/chunk-LFEHEGBL.js.map +1 -0
- package/dist/editor/index.cjs +4 -13
- package/dist/editor/index.d.cts +1 -1
- package/dist/editor/index.d.ts +1 -1
- package/dist/editor/index.js +1 -2
- package/dist/formula-spec.cjs +0 -2
- package/dist/formula-spec.cjs.map +1 -1
- package/dist/formula-spec.js +0 -2
- package/dist/formula-spec.js.map +1 -1
- package/dist/{index-JZFJ9oT6.d.cts → index-PFKKFfeI.d.cts} +70 -38
- package/dist/{index-JZFJ9oT6.d.ts → index-PFKKFfeI.d.ts} +70 -38
- package/dist/index.cjs +116 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +109 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-AGBOCJGV.cjs +0 -1266
- package/dist/chunk-AGBOCJGV.cjs.map +0 -1
- package/dist/chunk-FDIJPOVQ.js +0 -1252
- package/dist/chunk-FDIJPOVQ.js.map +0 -1
- package/dist/chunk-PZ5AY32C.js +0 -9
- package/dist/chunk-PZ5AY32C.js.map +0 -1
- package/dist/chunk-Q7SFCCGT.cjs +0 -11
- package/dist/chunk-Q7SFCCGT.cjs.map +0 -1
|
@@ -0,0 +1,1086 @@
|
|
|
1
|
+
import * as ohm from 'ohm-js';
|
|
2
|
+
|
|
3
|
+
// src/ohm/grammar/index.ts
|
|
4
|
+
var 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
|
+
| number
|
|
71
|
+
| string
|
|
72
|
+
| boolean
|
|
73
|
+
| null
|
|
74
|
+
| rootPath
|
|
75
|
+
| relativePath
|
|
76
|
+
| contextToken
|
|
77
|
+
| identifier
|
|
78
|
+
|
|
79
|
+
// Literals
|
|
80
|
+
number
|
|
81
|
+
= "-"? digit+ "." digit+ -- float
|
|
82
|
+
| "-"? digit+ -- int
|
|
83
|
+
|
|
84
|
+
string
|
|
85
|
+
= "\"" doubleStringChar* "\""
|
|
86
|
+
| "'" singleStringChar* "'"
|
|
87
|
+
|
|
88
|
+
doubleStringChar
|
|
89
|
+
= ~("\"" | "\\") any -- regular
|
|
90
|
+
| "\\" any -- escape
|
|
91
|
+
|
|
92
|
+
singleStringChar
|
|
93
|
+
= ~("'" | "\\") any -- regular
|
|
94
|
+
| "\\" any -- escape
|
|
95
|
+
|
|
96
|
+
boolean
|
|
97
|
+
= "true" ~identifierPart -- true
|
|
98
|
+
| "false" ~identifierPart -- false
|
|
99
|
+
|
|
100
|
+
null = "null" ~identifierPart
|
|
101
|
+
|
|
102
|
+
// Identifiers and paths
|
|
103
|
+
identifier = ~reserved identifierStart identifierPart*
|
|
104
|
+
|
|
105
|
+
identifierStart = letter | "_"
|
|
106
|
+
identifierPart = letter | digit | "_"
|
|
107
|
+
|
|
108
|
+
// Special paths
|
|
109
|
+
rootPath = "/" identifierPart+
|
|
110
|
+
|
|
111
|
+
relativePathPrefix = ".." "/"
|
|
112
|
+
relativePath = relativePathPrefix+ identifierPart+
|
|
113
|
+
|
|
114
|
+
contextToken
|
|
115
|
+
= "@" identifierPart+ -- at
|
|
116
|
+
| "#" identifierPart+ -- hash
|
|
117
|
+
|
|
118
|
+
// Reserved words (cannot be used as identifiers)
|
|
119
|
+
reserved
|
|
120
|
+
= ("true" | "false" | "null") ~identifierPart
|
|
121
|
+
|
|
122
|
+
// Whitespace (implicit, Ohm handles automatically)
|
|
123
|
+
}`;
|
|
124
|
+
var grammar2 = ohm.grammar(grammarText);
|
|
125
|
+
|
|
126
|
+
// src/ohm/semantics/index.ts
|
|
127
|
+
function childrenToAST(children) {
|
|
128
|
+
return children.filter((c) => "toAST" in c).map((c) => c.toAST());
|
|
129
|
+
}
|
|
130
|
+
function childrenDependencies(children) {
|
|
131
|
+
return children.filter((c) => "dependencies" in c).flatMap((c) => c.dependencies());
|
|
132
|
+
}
|
|
133
|
+
function childrenFeatures(children) {
|
|
134
|
+
return children.filter((c) => "features" in c).flatMap((c) => c.features());
|
|
135
|
+
}
|
|
136
|
+
var semantics = grammar2.createSemantics();
|
|
137
|
+
semantics.addOperation("toAST", {
|
|
138
|
+
Expression(e) {
|
|
139
|
+
return e.toAST();
|
|
140
|
+
},
|
|
141
|
+
Ternary_ternary(cond, _q, cons, _c, alt) {
|
|
142
|
+
return {
|
|
143
|
+
type: "TernaryOp",
|
|
144
|
+
condition: cond.toAST(),
|
|
145
|
+
consequent: cons.toAST(),
|
|
146
|
+
alternate: alt.toAST()
|
|
147
|
+
};
|
|
148
|
+
},
|
|
149
|
+
Ternary(e) {
|
|
150
|
+
return e.toAST();
|
|
151
|
+
},
|
|
152
|
+
LogicalOr_or(left, _op, right) {
|
|
153
|
+
return {
|
|
154
|
+
type: "BinaryOp",
|
|
155
|
+
op: "||",
|
|
156
|
+
left: left.toAST(),
|
|
157
|
+
right: right.toAST()
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
LogicalOr(e) {
|
|
161
|
+
return e.toAST();
|
|
162
|
+
},
|
|
163
|
+
LogicalAnd_and(left, _op, right) {
|
|
164
|
+
return {
|
|
165
|
+
type: "BinaryOp",
|
|
166
|
+
op: "&&",
|
|
167
|
+
left: left.toAST(),
|
|
168
|
+
right: right.toAST()
|
|
169
|
+
};
|
|
170
|
+
},
|
|
171
|
+
LogicalAnd(e) {
|
|
172
|
+
return e.toAST();
|
|
173
|
+
},
|
|
174
|
+
Equality_eq(left, _op, right) {
|
|
175
|
+
return {
|
|
176
|
+
type: "BinaryOp",
|
|
177
|
+
op: "==",
|
|
178
|
+
left: left.toAST(),
|
|
179
|
+
right: right.toAST()
|
|
180
|
+
};
|
|
181
|
+
},
|
|
182
|
+
Equality_neq(left, _op, right) {
|
|
183
|
+
return {
|
|
184
|
+
type: "BinaryOp",
|
|
185
|
+
op: "!=",
|
|
186
|
+
left: left.toAST(),
|
|
187
|
+
right: right.toAST()
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
Equality(e) {
|
|
191
|
+
return e.toAST();
|
|
192
|
+
},
|
|
193
|
+
Comparison_gte(left, _op, right) {
|
|
194
|
+
return {
|
|
195
|
+
type: "BinaryOp",
|
|
196
|
+
op: ">=",
|
|
197
|
+
left: left.toAST(),
|
|
198
|
+
right: right.toAST()
|
|
199
|
+
};
|
|
200
|
+
},
|
|
201
|
+
Comparison_lte(left, _op, right) {
|
|
202
|
+
return {
|
|
203
|
+
type: "BinaryOp",
|
|
204
|
+
op: "<=",
|
|
205
|
+
left: left.toAST(),
|
|
206
|
+
right: right.toAST()
|
|
207
|
+
};
|
|
208
|
+
},
|
|
209
|
+
Comparison_gt(left, _op, right) {
|
|
210
|
+
return {
|
|
211
|
+
type: "BinaryOp",
|
|
212
|
+
op: ">",
|
|
213
|
+
left: left.toAST(),
|
|
214
|
+
right: right.toAST()
|
|
215
|
+
};
|
|
216
|
+
},
|
|
217
|
+
Comparison_lt(left, _op, right) {
|
|
218
|
+
return {
|
|
219
|
+
type: "BinaryOp",
|
|
220
|
+
op: "<",
|
|
221
|
+
left: left.toAST(),
|
|
222
|
+
right: right.toAST()
|
|
223
|
+
};
|
|
224
|
+
},
|
|
225
|
+
Comparison(e) {
|
|
226
|
+
return e.toAST();
|
|
227
|
+
},
|
|
228
|
+
Additive_plus(left, _op, right) {
|
|
229
|
+
return {
|
|
230
|
+
type: "BinaryOp",
|
|
231
|
+
op: "+",
|
|
232
|
+
left: left.toAST(),
|
|
233
|
+
right: right.toAST()
|
|
234
|
+
};
|
|
235
|
+
},
|
|
236
|
+
Additive_minus(left, _op, right) {
|
|
237
|
+
return {
|
|
238
|
+
type: "BinaryOp",
|
|
239
|
+
op: "-",
|
|
240
|
+
left: left.toAST(),
|
|
241
|
+
right: right.toAST()
|
|
242
|
+
};
|
|
243
|
+
},
|
|
244
|
+
Additive(e) {
|
|
245
|
+
return e.toAST();
|
|
246
|
+
},
|
|
247
|
+
Multiplicative_times(left, _op, right) {
|
|
248
|
+
return {
|
|
249
|
+
type: "BinaryOp",
|
|
250
|
+
op: "*",
|
|
251
|
+
left: left.toAST(),
|
|
252
|
+
right: right.toAST()
|
|
253
|
+
};
|
|
254
|
+
},
|
|
255
|
+
Multiplicative_div(left, _op, right) {
|
|
256
|
+
return {
|
|
257
|
+
type: "BinaryOp",
|
|
258
|
+
op: "/",
|
|
259
|
+
left: left.toAST(),
|
|
260
|
+
right: right.toAST()
|
|
261
|
+
};
|
|
262
|
+
},
|
|
263
|
+
Multiplicative_mod(left, _op, right) {
|
|
264
|
+
return {
|
|
265
|
+
type: "BinaryOp",
|
|
266
|
+
op: "%",
|
|
267
|
+
left: left.toAST(),
|
|
268
|
+
right: right.toAST()
|
|
269
|
+
};
|
|
270
|
+
},
|
|
271
|
+
Multiplicative(e) {
|
|
272
|
+
return e.toAST();
|
|
273
|
+
},
|
|
274
|
+
Unary_neg(_op, expr) {
|
|
275
|
+
return { type: "UnaryOp", op: "-", argument: expr.toAST() };
|
|
276
|
+
},
|
|
277
|
+
Unary_not(_op, expr) {
|
|
278
|
+
return { type: "UnaryOp", op: "!", argument: expr.toAST() };
|
|
279
|
+
},
|
|
280
|
+
Unary(e) {
|
|
281
|
+
return e.toAST();
|
|
282
|
+
},
|
|
283
|
+
Postfix_call(callee, _lp, args, _rp) {
|
|
284
|
+
const firstArg = args.children[0];
|
|
285
|
+
const argList = firstArg ? firstArg.toAST() : [];
|
|
286
|
+
return {
|
|
287
|
+
type: "CallExpression",
|
|
288
|
+
callee: callee.toAST(),
|
|
289
|
+
arguments: Array.isArray(argList) ? argList : [argList]
|
|
290
|
+
};
|
|
291
|
+
},
|
|
292
|
+
Postfix_property(obj, _dot, prop) {
|
|
293
|
+
return {
|
|
294
|
+
type: "MemberExpression",
|
|
295
|
+
object: obj.toAST(),
|
|
296
|
+
property: prop.sourceString
|
|
297
|
+
};
|
|
298
|
+
},
|
|
299
|
+
Postfix_index(obj, _lb, index, _rb) {
|
|
300
|
+
return {
|
|
301
|
+
type: "IndexExpression",
|
|
302
|
+
object: obj.toAST(),
|
|
303
|
+
index: index.toAST()
|
|
304
|
+
};
|
|
305
|
+
},
|
|
306
|
+
Postfix_wildcard(obj, _lb, _star, _rb) {
|
|
307
|
+
return {
|
|
308
|
+
type: "WildcardExpression",
|
|
309
|
+
object: obj.toAST()
|
|
310
|
+
};
|
|
311
|
+
},
|
|
312
|
+
Postfix(e) {
|
|
313
|
+
return e.toAST();
|
|
314
|
+
},
|
|
315
|
+
// Returns an array of arguments, not a single ASTNode
|
|
316
|
+
// @ts-expect-error - intentionally returns array for function arguments
|
|
317
|
+
Arguments(first, _comma, rest) {
|
|
318
|
+
return [first.toAST(), ...rest.children.map((c) => c.toAST())];
|
|
319
|
+
},
|
|
320
|
+
Primary_paren(_lp, expr, _rp) {
|
|
321
|
+
return expr.toAST();
|
|
322
|
+
},
|
|
323
|
+
Primary(e) {
|
|
324
|
+
return e.toAST();
|
|
325
|
+
},
|
|
326
|
+
number_float(_neg, _int, _dot, _frac) {
|
|
327
|
+
return {
|
|
328
|
+
type: "NumberLiteral",
|
|
329
|
+
value: Number.parseFloat(this.sourceString)
|
|
330
|
+
};
|
|
331
|
+
},
|
|
332
|
+
number_int(_neg, _digits) {
|
|
333
|
+
return {
|
|
334
|
+
type: "NumberLiteral",
|
|
335
|
+
value: Number.parseInt(this.sourceString, 10)
|
|
336
|
+
};
|
|
337
|
+
},
|
|
338
|
+
string(_open, chars, _close) {
|
|
339
|
+
const raw = chars.sourceString;
|
|
340
|
+
return { type: "StringLiteral", value: raw.replaceAll(/\\(.)/g, "$1") };
|
|
341
|
+
},
|
|
342
|
+
boolean_true(_) {
|
|
343
|
+
return { type: "BooleanLiteral", value: true };
|
|
344
|
+
},
|
|
345
|
+
boolean_false(_) {
|
|
346
|
+
return { type: "BooleanLiteral", value: false };
|
|
347
|
+
},
|
|
348
|
+
null(_) {
|
|
349
|
+
return { type: "NullLiteral" };
|
|
350
|
+
},
|
|
351
|
+
identifier(_start, _rest) {
|
|
352
|
+
return { type: "Identifier", name: this.sourceString };
|
|
353
|
+
},
|
|
354
|
+
rootPath(_slash, _path) {
|
|
355
|
+
return { type: "RootPath", path: this.sourceString };
|
|
356
|
+
},
|
|
357
|
+
relativePath(_dotSlashes, _parts) {
|
|
358
|
+
return { type: "RelativePath", path: this.sourceString };
|
|
359
|
+
},
|
|
360
|
+
contextToken_at(_at, _name) {
|
|
361
|
+
return { type: "ContextToken", name: this.sourceString };
|
|
362
|
+
},
|
|
363
|
+
contextToken_hash(_hash, _name) {
|
|
364
|
+
return { type: "ContextToken", name: this.sourceString };
|
|
365
|
+
},
|
|
366
|
+
// @ts-expect-error - _iter returns array for iteration nodes
|
|
367
|
+
_iter(...children) {
|
|
368
|
+
return childrenToAST(children);
|
|
369
|
+
},
|
|
370
|
+
_terminal() {
|
|
371
|
+
return { type: "Identifier", name: this.sourceString };
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
semantics.addOperation("dependencies", {
|
|
375
|
+
identifier(_start, _rest) {
|
|
376
|
+
return [this.sourceString];
|
|
377
|
+
},
|
|
378
|
+
rootPath(_slash, _path) {
|
|
379
|
+
return [this.sourceString];
|
|
380
|
+
},
|
|
381
|
+
relativePath(_dotSlashes, _parts) {
|
|
382
|
+
return [this.sourceString];
|
|
383
|
+
},
|
|
384
|
+
contextToken_at(_at, _name) {
|
|
385
|
+
return [];
|
|
386
|
+
},
|
|
387
|
+
contextToken_hash(_hash, _name) {
|
|
388
|
+
return [];
|
|
389
|
+
},
|
|
390
|
+
Postfix_property(obj, _dot, prop) {
|
|
391
|
+
const objDeps = obj.dependencies();
|
|
392
|
+
if (objDeps.length === 1) {
|
|
393
|
+
return [`${objDeps[0]}.${prop.sourceString}`];
|
|
394
|
+
}
|
|
395
|
+
return objDeps;
|
|
396
|
+
},
|
|
397
|
+
Postfix_index(obj, _lb, index, _rb) {
|
|
398
|
+
const objDeps = obj.dependencies();
|
|
399
|
+
const indexNode = index.toAST();
|
|
400
|
+
const getNumericIndex = (node) => {
|
|
401
|
+
if (node.type === "NumberLiteral") {
|
|
402
|
+
return node.value;
|
|
403
|
+
}
|
|
404
|
+
if (node.type === "UnaryOp" && node.op === "-" && node.argument.type === "NumberLiteral") {
|
|
405
|
+
return -node.argument.value;
|
|
406
|
+
}
|
|
407
|
+
return null;
|
|
408
|
+
};
|
|
409
|
+
const numericIndex = getNumericIndex(indexNode);
|
|
410
|
+
if (objDeps.length === 1 && numericIndex !== null) {
|
|
411
|
+
return [`${objDeps[0]}[${numericIndex}]`];
|
|
412
|
+
}
|
|
413
|
+
return [...objDeps, ...index.dependencies()];
|
|
414
|
+
},
|
|
415
|
+
Postfix_wildcard(obj, _lb, _star, _rb) {
|
|
416
|
+
const objDeps = obj.dependencies();
|
|
417
|
+
if (objDeps.length === 1) {
|
|
418
|
+
return [`${objDeps[0]}[*]`];
|
|
419
|
+
}
|
|
420
|
+
return objDeps;
|
|
421
|
+
},
|
|
422
|
+
Postfix_call(callee, _lp, args, _rp) {
|
|
423
|
+
const calleeDeps = callee.dependencies();
|
|
424
|
+
const calleeAST = callee.toAST();
|
|
425
|
+
const argDeps = childrenDependencies(args.children);
|
|
426
|
+
if (calleeAST.type === "Identifier") {
|
|
427
|
+
return argDeps;
|
|
428
|
+
}
|
|
429
|
+
return [...calleeDeps, ...argDeps];
|
|
430
|
+
},
|
|
431
|
+
number_float(_neg, _int, _dot, _frac) {
|
|
432
|
+
return [];
|
|
433
|
+
},
|
|
434
|
+
number_int(_neg, _digits) {
|
|
435
|
+
return [];
|
|
436
|
+
},
|
|
437
|
+
string(_open, _chars, _close) {
|
|
438
|
+
return [];
|
|
439
|
+
},
|
|
440
|
+
boolean_true(_) {
|
|
441
|
+
return [];
|
|
442
|
+
},
|
|
443
|
+
boolean_false(_) {
|
|
444
|
+
return [];
|
|
445
|
+
},
|
|
446
|
+
null(_) {
|
|
447
|
+
return [];
|
|
448
|
+
},
|
|
449
|
+
_nonterminal(...children) {
|
|
450
|
+
return childrenDependencies(children);
|
|
451
|
+
},
|
|
452
|
+
_iter(...children) {
|
|
453
|
+
return childrenDependencies(children);
|
|
454
|
+
},
|
|
455
|
+
_terminal() {
|
|
456
|
+
return [];
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
var ARRAY_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
460
|
+
"sum",
|
|
461
|
+
"avg",
|
|
462
|
+
"count",
|
|
463
|
+
"first",
|
|
464
|
+
"last",
|
|
465
|
+
"join",
|
|
466
|
+
"includes"
|
|
467
|
+
]);
|
|
468
|
+
semantics.addOperation("features", {
|
|
469
|
+
rootPath(_slash, _path) {
|
|
470
|
+
const path = this.sourceString;
|
|
471
|
+
const features = ["root_path"];
|
|
472
|
+
if (path.includes(".")) {
|
|
473
|
+
features.push("nested_path");
|
|
474
|
+
}
|
|
475
|
+
return features;
|
|
476
|
+
},
|
|
477
|
+
relativePath(_dotSlashes, _parts) {
|
|
478
|
+
const path = this.sourceString;
|
|
479
|
+
const features = ["relative_path"];
|
|
480
|
+
const withoutPrefix = path.replace(/^(\.\.\/)+/, "");
|
|
481
|
+
if (withoutPrefix.includes(".")) {
|
|
482
|
+
features.push("nested_path");
|
|
483
|
+
}
|
|
484
|
+
return features;
|
|
485
|
+
},
|
|
486
|
+
contextToken_at(_at, _name) {
|
|
487
|
+
return ["context_token"];
|
|
488
|
+
},
|
|
489
|
+
contextToken_hash(_hash, _name) {
|
|
490
|
+
return ["context_token"];
|
|
491
|
+
},
|
|
492
|
+
Postfix_property(obj, _dot, _prop) {
|
|
493
|
+
const objFeatures = obj.features();
|
|
494
|
+
return [...objFeatures, "nested_path"];
|
|
495
|
+
},
|
|
496
|
+
Postfix_index(obj, _lb, index, _rb) {
|
|
497
|
+
const objFeatures = obj.features();
|
|
498
|
+
const indexFeatures = index.features();
|
|
499
|
+
return [...objFeatures, ...indexFeatures, "array_index"];
|
|
500
|
+
},
|
|
501
|
+
Postfix_wildcard(obj, _lb, _star, _rb) {
|
|
502
|
+
const objFeatures = obj.features();
|
|
503
|
+
return [...objFeatures, "array_wildcard"];
|
|
504
|
+
},
|
|
505
|
+
Postfix_call(callee, _lp, args, _rp) {
|
|
506
|
+
const calleeAST = callee.toAST();
|
|
507
|
+
const argFeatures = childrenFeatures(args.children);
|
|
508
|
+
if (calleeAST.type === "Identifier" && ARRAY_FUNCTIONS.has(calleeAST.name.toLowerCase())) {
|
|
509
|
+
return [...argFeatures, "array_function"];
|
|
510
|
+
}
|
|
511
|
+
return argFeatures;
|
|
512
|
+
},
|
|
513
|
+
_nonterminal(...children) {
|
|
514
|
+
return childrenFeatures(children);
|
|
515
|
+
},
|
|
516
|
+
_iter(...children) {
|
|
517
|
+
return childrenFeatures(children);
|
|
518
|
+
},
|
|
519
|
+
_terminal() {
|
|
520
|
+
return [];
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
var BUILTINS = {
|
|
524
|
+
// Logical
|
|
525
|
+
and: (a, b) => Boolean(a) && Boolean(b),
|
|
526
|
+
or: (a, b) => Boolean(a) || Boolean(b),
|
|
527
|
+
not: (a) => !a,
|
|
528
|
+
// String
|
|
529
|
+
concat: (...args) => args.map(String).join(""),
|
|
530
|
+
upper: (s) => String(s).toUpperCase(),
|
|
531
|
+
lower: (s) => String(s).toLowerCase(),
|
|
532
|
+
trim: (s) => String(s).trim(),
|
|
533
|
+
left: (s, n) => String(s).slice(0, Math.max(0, Math.floor(Number(n)))),
|
|
534
|
+
right: (s, n) => {
|
|
535
|
+
const str = String(s);
|
|
536
|
+
const count = Math.max(0, Math.floor(Number(n)));
|
|
537
|
+
return count === 0 ? "" : str.slice(-count);
|
|
538
|
+
},
|
|
539
|
+
replace: (s, search, repl) => String(s).replace(String(search), String(repl)),
|
|
540
|
+
tostring: String,
|
|
541
|
+
length: (s) => {
|
|
542
|
+
if (Array.isArray(s)) return s.length;
|
|
543
|
+
if (typeof s === "string") return s.length;
|
|
544
|
+
if (s !== null && typeof s === "object") return Object.keys(s).length;
|
|
545
|
+
return String(s).length;
|
|
546
|
+
},
|
|
547
|
+
contains: (s, search) => String(s).includes(String(search)),
|
|
548
|
+
startswith: (s, search) => String(s).startsWith(String(search)),
|
|
549
|
+
endswith: (s, search) => String(s).endsWith(String(search)),
|
|
550
|
+
join: (arr, sep) => {
|
|
551
|
+
if (!Array.isArray(arr)) return "";
|
|
552
|
+
if (sep === void 0 || sep === null) return arr.join(",");
|
|
553
|
+
if (typeof sep !== "string" && typeof sep !== "number") {
|
|
554
|
+
return arr.join(",");
|
|
555
|
+
}
|
|
556
|
+
return arr.join(String(sep));
|
|
557
|
+
},
|
|
558
|
+
// Numeric
|
|
559
|
+
tonumber: Number,
|
|
560
|
+
toboolean: Boolean,
|
|
561
|
+
isnull: (v) => v === null || v === void 0,
|
|
562
|
+
coalesce: (...args) => args.find((v) => v !== null && v !== void 0) ?? null,
|
|
563
|
+
round: (n, dec) => {
|
|
564
|
+
const factor = 10 ** (dec === void 0 ? 0 : Number(dec));
|
|
565
|
+
return Math.round(Number(n) * factor) / factor;
|
|
566
|
+
},
|
|
567
|
+
floor: (n) => Math.floor(Number(n)),
|
|
568
|
+
ceil: (n) => Math.ceil(Number(n)),
|
|
569
|
+
abs: (n) => Math.abs(Number(n)),
|
|
570
|
+
sqrt: (n) => Math.sqrt(Number(n)),
|
|
571
|
+
pow: (base, exp) => Math.pow(Number(base), Number(exp)),
|
|
572
|
+
min: (...args) => args.length === 0 ? Number.NaN : Math.min(...args.map(Number)),
|
|
573
|
+
max: (...args) => args.length === 0 ? Number.NaN : Math.max(...args.map(Number)),
|
|
574
|
+
log: (n) => Math.log(Number(n)),
|
|
575
|
+
log10: (n) => Math.log10(Number(n)),
|
|
576
|
+
exp: (n) => Math.exp(Number(n)),
|
|
577
|
+
sign: (n) => Math.sign(Number(n)),
|
|
578
|
+
// Array
|
|
579
|
+
sum: (arr) => Array.isArray(arr) ? arr.reduce((a, b) => a + Number(b), 0) : 0,
|
|
580
|
+
avg: (arr) => Array.isArray(arr) && arr.length > 0 ? arr.reduce((a, b) => a + Number(b), 0) / arr.length : 0,
|
|
581
|
+
count: (arr) => Array.isArray(arr) ? arr.length : 0,
|
|
582
|
+
first: (arr) => Array.isArray(arr) ? arr[0] : void 0,
|
|
583
|
+
last: (arr) => Array.isArray(arr) ? arr.at(-1) : void 0,
|
|
584
|
+
includes: (arr, val) => Array.isArray(arr) ? arr.includes(val) : false,
|
|
585
|
+
// Conditional
|
|
586
|
+
if: (cond, ifTrue, ifFalse) => cond ? ifTrue : ifFalse
|
|
587
|
+
};
|
|
588
|
+
function getByPath(obj, path) {
|
|
589
|
+
const parts = path.split(".");
|
|
590
|
+
let current = obj;
|
|
591
|
+
for (const part of parts) {
|
|
592
|
+
if (current === null || current === void 0) return void 0;
|
|
593
|
+
current = current[part];
|
|
594
|
+
}
|
|
595
|
+
return current;
|
|
596
|
+
}
|
|
597
|
+
semantics.addOperation("eval(ctx)", {
|
|
598
|
+
Expression(e) {
|
|
599
|
+
return e.eval(this.args.ctx);
|
|
600
|
+
},
|
|
601
|
+
Ternary_ternary(cond, _q, cons, _c, alt) {
|
|
602
|
+
return cond.eval(this.args.ctx) ? cons.eval(this.args.ctx) : alt.eval(this.args.ctx);
|
|
603
|
+
},
|
|
604
|
+
Ternary(e) {
|
|
605
|
+
return e.eval(this.args.ctx);
|
|
606
|
+
},
|
|
607
|
+
LogicalOr_or(left, _op, right) {
|
|
608
|
+
return left.eval(this.args.ctx) || right.eval(this.args.ctx);
|
|
609
|
+
},
|
|
610
|
+
LogicalOr(e) {
|
|
611
|
+
return e.eval(this.args.ctx);
|
|
612
|
+
},
|
|
613
|
+
LogicalAnd_and(left, _op, right) {
|
|
614
|
+
return left.eval(this.args.ctx) && right.eval(this.args.ctx);
|
|
615
|
+
},
|
|
616
|
+
LogicalAnd(e) {
|
|
617
|
+
return e.eval(this.args.ctx);
|
|
618
|
+
},
|
|
619
|
+
Equality_eq(left, _op, right) {
|
|
620
|
+
return left.eval(this.args.ctx) == right.eval(this.args.ctx);
|
|
621
|
+
},
|
|
622
|
+
Equality_neq(left, _op, right) {
|
|
623
|
+
return left.eval(this.args.ctx) != right.eval(this.args.ctx);
|
|
624
|
+
},
|
|
625
|
+
Equality(e) {
|
|
626
|
+
return e.eval(this.args.ctx);
|
|
627
|
+
},
|
|
628
|
+
Comparison_gte(left, _op, right) {
|
|
629
|
+
return left.eval(this.args.ctx) >= right.eval(this.args.ctx);
|
|
630
|
+
},
|
|
631
|
+
Comparison_lte(left, _op, right) {
|
|
632
|
+
return left.eval(this.args.ctx) <= right.eval(this.args.ctx);
|
|
633
|
+
},
|
|
634
|
+
Comparison_gt(left, _op, right) {
|
|
635
|
+
return left.eval(this.args.ctx) > right.eval(this.args.ctx);
|
|
636
|
+
},
|
|
637
|
+
Comparison_lt(left, _op, right) {
|
|
638
|
+
return left.eval(this.args.ctx) < right.eval(this.args.ctx);
|
|
639
|
+
},
|
|
640
|
+
Comparison(e) {
|
|
641
|
+
return e.eval(this.args.ctx);
|
|
642
|
+
},
|
|
643
|
+
Additive_plus(left, _op, right) {
|
|
644
|
+
const l = left.eval(this.args.ctx);
|
|
645
|
+
const r = right.eval(this.args.ctx);
|
|
646
|
+
if (typeof l === "string" || typeof r === "string") {
|
|
647
|
+
return String(l) + String(r);
|
|
648
|
+
}
|
|
649
|
+
return l + r;
|
|
650
|
+
},
|
|
651
|
+
Additive_minus(left, _op, right) {
|
|
652
|
+
return left.eval(this.args.ctx) - right.eval(this.args.ctx);
|
|
653
|
+
},
|
|
654
|
+
Additive(e) {
|
|
655
|
+
return e.eval(this.args.ctx);
|
|
656
|
+
},
|
|
657
|
+
Multiplicative_times(left, _op, right) {
|
|
658
|
+
return left.eval(this.args.ctx) * right.eval(this.args.ctx);
|
|
659
|
+
},
|
|
660
|
+
Multiplicative_div(left, _op, right) {
|
|
661
|
+
return left.eval(this.args.ctx) / right.eval(this.args.ctx);
|
|
662
|
+
},
|
|
663
|
+
Multiplicative_mod(left, _op, right) {
|
|
664
|
+
return left.eval(this.args.ctx) % right.eval(this.args.ctx);
|
|
665
|
+
},
|
|
666
|
+
Multiplicative(e) {
|
|
667
|
+
return e.eval(this.args.ctx);
|
|
668
|
+
},
|
|
669
|
+
Unary_neg(_op, expr) {
|
|
670
|
+
return -expr.eval(this.args.ctx);
|
|
671
|
+
},
|
|
672
|
+
Unary_not(_op, expr) {
|
|
673
|
+
return !expr.eval(this.args.ctx);
|
|
674
|
+
},
|
|
675
|
+
Unary(e) {
|
|
676
|
+
return e.eval(this.args.ctx);
|
|
677
|
+
},
|
|
678
|
+
Postfix_call(callee, _lp, args, _rp) {
|
|
679
|
+
const calleeAST = callee.toAST();
|
|
680
|
+
const getArgValues = () => {
|
|
681
|
+
const argsNode = args.children[0];
|
|
682
|
+
if (!argsNode) {
|
|
683
|
+
return [];
|
|
684
|
+
}
|
|
685
|
+
return argsNode.eval(this.args.ctx);
|
|
686
|
+
};
|
|
687
|
+
if (calleeAST.type === "Identifier") {
|
|
688
|
+
const fnName = calleeAST.name.toLowerCase();
|
|
689
|
+
const builtinFn = BUILTINS[fnName];
|
|
690
|
+
if (builtinFn) {
|
|
691
|
+
return builtinFn(...getArgValues());
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
const fn = callee.eval(this.args.ctx);
|
|
695
|
+
if (typeof fn === "function") {
|
|
696
|
+
return fn(...getArgValues());
|
|
697
|
+
}
|
|
698
|
+
const calleeName = calleeAST.type === "Identifier" ? calleeAST.name : "expression";
|
|
699
|
+
throw new Error(`'${calleeName}' is not a function`);
|
|
700
|
+
},
|
|
701
|
+
Postfix_property(obj, _dot, prop) {
|
|
702
|
+
const objVal = obj.eval(this.args.ctx);
|
|
703
|
+
return objVal?.[prop.sourceString];
|
|
704
|
+
},
|
|
705
|
+
Postfix_index(obj, _lb, index, _rb) {
|
|
706
|
+
const objVal = obj.eval(this.args.ctx);
|
|
707
|
+
const idx = index.eval(this.args.ctx);
|
|
708
|
+
if (idx < 0) {
|
|
709
|
+
return objVal?.[objVal.length + idx];
|
|
710
|
+
}
|
|
711
|
+
return objVal?.[idx];
|
|
712
|
+
},
|
|
713
|
+
Postfix_wildcard(obj, _lb, _star, _rb) {
|
|
714
|
+
return obj.eval(this.args.ctx);
|
|
715
|
+
},
|
|
716
|
+
Postfix(e) {
|
|
717
|
+
return e.eval(this.args.ctx);
|
|
718
|
+
},
|
|
719
|
+
Arguments(first, _comma, rest) {
|
|
720
|
+
return [
|
|
721
|
+
first.eval(this.args.ctx),
|
|
722
|
+
...rest.children.map((c) => c.eval(this.args.ctx))
|
|
723
|
+
];
|
|
724
|
+
},
|
|
725
|
+
Primary_paren(_lp, expr, _rp) {
|
|
726
|
+
return expr.eval(this.args.ctx);
|
|
727
|
+
},
|
|
728
|
+
Primary(e) {
|
|
729
|
+
return e.eval(this.args.ctx);
|
|
730
|
+
},
|
|
731
|
+
number_float(_neg, _int, _dot, _frac) {
|
|
732
|
+
return Number.parseFloat(this.sourceString);
|
|
733
|
+
},
|
|
734
|
+
number_int(_neg, _digits) {
|
|
735
|
+
return Number.parseInt(this.sourceString, 10);
|
|
736
|
+
},
|
|
737
|
+
string(_open, chars, _close) {
|
|
738
|
+
return chars.sourceString.replaceAll(/\\(.)/g, "$1");
|
|
739
|
+
},
|
|
740
|
+
boolean_true(_) {
|
|
741
|
+
return true;
|
|
742
|
+
},
|
|
743
|
+
boolean_false(_) {
|
|
744
|
+
return false;
|
|
745
|
+
},
|
|
746
|
+
null(_) {
|
|
747
|
+
return null;
|
|
748
|
+
},
|
|
749
|
+
identifier(_start, _rest) {
|
|
750
|
+
const name = this.sourceString;
|
|
751
|
+
return this.args.ctx[name];
|
|
752
|
+
},
|
|
753
|
+
rootPath(_slash, _path) {
|
|
754
|
+
const fullPath = this.sourceString;
|
|
755
|
+
if (fullPath in this.args.ctx) {
|
|
756
|
+
return this.args.ctx[fullPath];
|
|
757
|
+
}
|
|
758
|
+
const parts = fullPath.slice(1).split(".");
|
|
759
|
+
const firstPart = parts[0];
|
|
760
|
+
if (!firstPart) return void 0;
|
|
761
|
+
const rootKey = "/" + firstPart;
|
|
762
|
+
let value = this.args.ctx[rootKey];
|
|
763
|
+
for (let i = 1; i < parts.length; i++) {
|
|
764
|
+
if (value === null || value === void 0) return void 0;
|
|
765
|
+
const part = parts[i];
|
|
766
|
+
if (!part) continue;
|
|
767
|
+
value = value[part];
|
|
768
|
+
}
|
|
769
|
+
return value;
|
|
770
|
+
},
|
|
771
|
+
relativePath(_dotSlashes, _parts) {
|
|
772
|
+
const fullPath = this.sourceString;
|
|
773
|
+
if (fullPath in this.args.ctx) {
|
|
774
|
+
return this.args.ctx[fullPath];
|
|
775
|
+
}
|
|
776
|
+
const path = fullPath.replace(/^(\.\.\/)+/, "");
|
|
777
|
+
return getByPath(this.args.ctx, path);
|
|
778
|
+
},
|
|
779
|
+
contextToken_at(_at, _name) {
|
|
780
|
+
return this.args.ctx[this.sourceString];
|
|
781
|
+
},
|
|
782
|
+
contextToken_hash(_hash, _name) {
|
|
783
|
+
return this.args.ctx[this.sourceString];
|
|
784
|
+
},
|
|
785
|
+
_nonterminal(...children) {
|
|
786
|
+
const ctx = this.args.ctx;
|
|
787
|
+
for (const child of children) {
|
|
788
|
+
if ("eval" in child) {
|
|
789
|
+
return child.eval(ctx);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return void 0;
|
|
793
|
+
},
|
|
794
|
+
_iter(...children) {
|
|
795
|
+
return children.map((c) => c.eval(this.args.ctx));
|
|
796
|
+
},
|
|
797
|
+
_terminal() {
|
|
798
|
+
return void 0;
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
// src/ohm/core/parser.ts
|
|
803
|
+
function parseFormula(expression) {
|
|
804
|
+
const trimmed = expression.trim();
|
|
805
|
+
if (!trimmed) {
|
|
806
|
+
throw new Error("Empty expression");
|
|
807
|
+
}
|
|
808
|
+
const matchResult = grammar2.match(trimmed);
|
|
809
|
+
if (matchResult.failed()) {
|
|
810
|
+
throw new Error(matchResult.message ?? "Parse error");
|
|
811
|
+
}
|
|
812
|
+
const adapter = semantics(matchResult);
|
|
813
|
+
const ast = adapter.toAST();
|
|
814
|
+
const dependencies = [...new Set(adapter.dependencies())];
|
|
815
|
+
const allFeatures = adapter.features();
|
|
816
|
+
const features = [...new Set(allFeatures)];
|
|
817
|
+
const minVersion = features.length > 0 ? "1.1" : "1.0";
|
|
818
|
+
return { ast, dependencies, features, minVersion };
|
|
819
|
+
}
|
|
820
|
+
function validateSyntax(expression) {
|
|
821
|
+
const trimmed = expression.trim();
|
|
822
|
+
if (!trimmed) {
|
|
823
|
+
return { isValid: false, error: "Empty expression" };
|
|
824
|
+
}
|
|
825
|
+
const matchResult = grammar2.match(trimmed);
|
|
826
|
+
if (matchResult.failed()) {
|
|
827
|
+
const pos = matchResult.getRightmostFailurePosition?.();
|
|
828
|
+
return {
|
|
829
|
+
isValid: false,
|
|
830
|
+
error: matchResult.message ?? "Parse error",
|
|
831
|
+
position: pos
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
return { isValid: true };
|
|
835
|
+
}
|
|
836
|
+
function evaluate(expression, context) {
|
|
837
|
+
const trimmed = expression.trim();
|
|
838
|
+
if (!trimmed) {
|
|
839
|
+
throw new Error("Empty expression");
|
|
840
|
+
}
|
|
841
|
+
const matchResult = grammar2.match(trimmed);
|
|
842
|
+
if (matchResult.failed()) {
|
|
843
|
+
throw new Error(matchResult.message ?? "Parse error");
|
|
844
|
+
}
|
|
845
|
+
const safeContext = {};
|
|
846
|
+
for (const [key, value] of Object.entries(context)) {
|
|
847
|
+
if (typeof value !== "function") {
|
|
848
|
+
safeContext[key] = value;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
return semantics(matchResult).eval(safeContext);
|
|
852
|
+
}
|
|
853
|
+
function getValueByPath(data, path) {
|
|
854
|
+
const segments = path.split(".");
|
|
855
|
+
let current = data;
|
|
856
|
+
for (const segment of segments) {
|
|
857
|
+
if (current === null || current === void 0) {
|
|
858
|
+
return void 0;
|
|
859
|
+
}
|
|
860
|
+
if (typeof current !== "object") {
|
|
861
|
+
return void 0;
|
|
862
|
+
}
|
|
863
|
+
current = current[segment];
|
|
864
|
+
}
|
|
865
|
+
return current;
|
|
866
|
+
}
|
|
867
|
+
function extractRootField(fieldPath) {
|
|
868
|
+
const dotIndex = fieldPath.indexOf(".");
|
|
869
|
+
const bracketIndex = fieldPath.indexOf("[");
|
|
870
|
+
if (dotIndex === -1 && bracketIndex === -1) {
|
|
871
|
+
return fieldPath;
|
|
872
|
+
}
|
|
873
|
+
if (dotIndex === -1) {
|
|
874
|
+
return fieldPath.slice(0, bracketIndex);
|
|
875
|
+
}
|
|
876
|
+
if (bracketIndex === -1) {
|
|
877
|
+
return fieldPath.slice(0, dotIndex);
|
|
878
|
+
}
|
|
879
|
+
return fieldPath.slice(0, Math.min(dotIndex, bracketIndex));
|
|
880
|
+
}
|
|
881
|
+
function buildPathReferences(rootData, dependencies) {
|
|
882
|
+
const refs = {};
|
|
883
|
+
for (const dep of dependencies) {
|
|
884
|
+
if (dep.startsWith("/")) {
|
|
885
|
+
const fieldPath = dep.slice(1);
|
|
886
|
+
const rootField = extractRootField(fieldPath);
|
|
887
|
+
const contextKey = "/" + rootField;
|
|
888
|
+
if (!(contextKey in refs)) {
|
|
889
|
+
refs[contextKey] = getValueByPath(rootData, rootField);
|
|
890
|
+
}
|
|
891
|
+
} else if (dep.startsWith("../")) {
|
|
892
|
+
const fieldPath = dep.slice(3);
|
|
893
|
+
refs[dep] = getValueByPath(rootData, fieldPath);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
return refs;
|
|
897
|
+
}
|
|
898
|
+
function evaluateWithContext(expression, options) {
|
|
899
|
+
const { rootData, itemData } = options;
|
|
900
|
+
const trimmed = expression.trim();
|
|
901
|
+
if (!trimmed) {
|
|
902
|
+
throw new Error("Empty expression");
|
|
903
|
+
}
|
|
904
|
+
const parsed = parseFormula(trimmed);
|
|
905
|
+
const pathRefs = buildPathReferences(rootData, parsed.dependencies);
|
|
906
|
+
const context = {
|
|
907
|
+
...rootData,
|
|
908
|
+
...itemData,
|
|
909
|
+
...pathRefs
|
|
910
|
+
};
|
|
911
|
+
return evaluate(trimmed, context);
|
|
912
|
+
}
|
|
913
|
+
var ARITHMETIC_OPS = /* @__PURE__ */ new Set(["+", "-", "*", "/", "%"]);
|
|
914
|
+
var COMPARISON_OPS = /* @__PURE__ */ new Set(["<", ">", "<=", ">=", "==", "!="]);
|
|
915
|
+
var LOGICAL_OPS = /* @__PURE__ */ new Set(["&&", "||", "!"]);
|
|
916
|
+
var NUMERIC_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
917
|
+
"round",
|
|
918
|
+
"floor",
|
|
919
|
+
"ceil",
|
|
920
|
+
"abs",
|
|
921
|
+
"sqrt",
|
|
922
|
+
"pow",
|
|
923
|
+
"min",
|
|
924
|
+
"max",
|
|
925
|
+
"log",
|
|
926
|
+
"log10",
|
|
927
|
+
"exp",
|
|
928
|
+
"sign",
|
|
929
|
+
"sum",
|
|
930
|
+
"avg",
|
|
931
|
+
"count",
|
|
932
|
+
"tonumber",
|
|
933
|
+
"length"
|
|
934
|
+
]);
|
|
935
|
+
var STRING_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
936
|
+
"concat",
|
|
937
|
+
"upper",
|
|
938
|
+
"lower",
|
|
939
|
+
"trim",
|
|
940
|
+
"left",
|
|
941
|
+
"right",
|
|
942
|
+
"replace",
|
|
943
|
+
"tostring",
|
|
944
|
+
"join"
|
|
945
|
+
]);
|
|
946
|
+
var BOOLEAN_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
947
|
+
"and",
|
|
948
|
+
"or",
|
|
949
|
+
"not",
|
|
950
|
+
"contains",
|
|
951
|
+
"startswith",
|
|
952
|
+
"endswith",
|
|
953
|
+
"isnull",
|
|
954
|
+
"toboolean",
|
|
955
|
+
"includes"
|
|
956
|
+
]);
|
|
957
|
+
function getFieldType(path, fieldTypes) {
|
|
958
|
+
const rootField = path.split(".")[0]?.split("[")[0] || path;
|
|
959
|
+
const schemaType = fieldTypes[rootField];
|
|
960
|
+
if (schemaType === "number") return "number";
|
|
961
|
+
if (schemaType === "string") return "string";
|
|
962
|
+
if (schemaType === "boolean") return "boolean";
|
|
963
|
+
return "unknown";
|
|
964
|
+
}
|
|
965
|
+
function inferLiteralType(node) {
|
|
966
|
+
switch (node.type) {
|
|
967
|
+
case "NumberLiteral":
|
|
968
|
+
return "number";
|
|
969
|
+
case "BooleanLiteral":
|
|
970
|
+
return "boolean";
|
|
971
|
+
case "StringLiteral":
|
|
972
|
+
return "string";
|
|
973
|
+
case "NullLiteral":
|
|
974
|
+
return "unknown";
|
|
975
|
+
default:
|
|
976
|
+
return null;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
function inferBinaryOpType(node, fieldTypes) {
|
|
980
|
+
const { op } = node;
|
|
981
|
+
if (op === "+") {
|
|
982
|
+
const leftType = inferTypeFromAST(node.left, fieldTypes);
|
|
983
|
+
const rightType = inferTypeFromAST(node.right, fieldTypes);
|
|
984
|
+
if (leftType === "string" || rightType === "string") return "string";
|
|
985
|
+
if (leftType === "unknown" || rightType === "unknown") return "unknown";
|
|
986
|
+
return "number";
|
|
987
|
+
}
|
|
988
|
+
if (ARITHMETIC_OPS.has(op)) return "number";
|
|
989
|
+
if (COMPARISON_OPS.has(op)) return "boolean";
|
|
990
|
+
if (LOGICAL_OPS.has(op)) return "boolean";
|
|
991
|
+
return "unknown";
|
|
992
|
+
}
|
|
993
|
+
function inferCallExpressionType(node, fieldTypes) {
|
|
994
|
+
const funcName = node.callee.type === "Identifier" ? node.callee.name.toLowerCase() : "";
|
|
995
|
+
if (NUMERIC_FUNCTIONS.has(funcName)) return "number";
|
|
996
|
+
if (STRING_FUNCTIONS.has(funcName)) return "string";
|
|
997
|
+
if (BOOLEAN_FUNCTIONS.has(funcName)) return "boolean";
|
|
998
|
+
if (funcName === "if" && node.arguments.length >= 3) {
|
|
999
|
+
const thenArg = node.arguments[1];
|
|
1000
|
+
const elseArg = node.arguments[2];
|
|
1001
|
+
if (thenArg && elseArg) {
|
|
1002
|
+
const thenType = inferTypeFromAST(thenArg, fieldTypes);
|
|
1003
|
+
const elseType = inferTypeFromAST(elseArg, fieldTypes);
|
|
1004
|
+
if (thenType === elseType) return thenType;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
return "unknown";
|
|
1008
|
+
}
|
|
1009
|
+
function inferTypeFromAST(node, fieldTypes) {
|
|
1010
|
+
const literalType = inferLiteralType(node);
|
|
1011
|
+
if (literalType !== null) return literalType;
|
|
1012
|
+
switch (node.type) {
|
|
1013
|
+
case "Identifier":
|
|
1014
|
+
return getFieldType(node.name, fieldTypes);
|
|
1015
|
+
case "RootPath":
|
|
1016
|
+
case "RelativePath":
|
|
1017
|
+
case "ContextToken":
|
|
1018
|
+
case "IndexExpression":
|
|
1019
|
+
case "WildcardExpression":
|
|
1020
|
+
return "unknown";
|
|
1021
|
+
case "MemberExpression": {
|
|
1022
|
+
const objectType = inferTypeFromAST(node.object, fieldTypes);
|
|
1023
|
+
if (objectType !== "unknown") return objectType;
|
|
1024
|
+
if (node.object.type === "Identifier") {
|
|
1025
|
+
return getFieldType(`${node.object.name}.${node.property}`, fieldTypes);
|
|
1026
|
+
}
|
|
1027
|
+
return "unknown";
|
|
1028
|
+
}
|
|
1029
|
+
case "BinaryOp":
|
|
1030
|
+
return inferBinaryOpType(node, fieldTypes);
|
|
1031
|
+
case "UnaryOp": {
|
|
1032
|
+
if (node.op === "-") return "number";
|
|
1033
|
+
if (node.op === "!") return "boolean";
|
|
1034
|
+
return "unknown";
|
|
1035
|
+
}
|
|
1036
|
+
case "TernaryOp": {
|
|
1037
|
+
const thenType = inferTypeFromAST(node.consequent, fieldTypes);
|
|
1038
|
+
const elseType = inferTypeFromAST(node.alternate, fieldTypes);
|
|
1039
|
+
return thenType === elseType ? thenType : "unknown";
|
|
1040
|
+
}
|
|
1041
|
+
case "CallExpression":
|
|
1042
|
+
return inferCallExpressionType(node, fieldTypes);
|
|
1043
|
+
default:
|
|
1044
|
+
return "unknown";
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
function inferFormulaType(expression, fieldTypes = {}) {
|
|
1048
|
+
const trimmed = expression.trim();
|
|
1049
|
+
if (!trimmed) {
|
|
1050
|
+
return "unknown";
|
|
1051
|
+
}
|
|
1052
|
+
try {
|
|
1053
|
+
const { ast } = parseFormula(trimmed);
|
|
1054
|
+
return inferTypeFromAST(ast, fieldTypes);
|
|
1055
|
+
} catch {
|
|
1056
|
+
return "unknown";
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// src/parse-formula.ts
|
|
1061
|
+
function parseExpression(expression) {
|
|
1062
|
+
const result = parseFormula(expression);
|
|
1063
|
+
return {
|
|
1064
|
+
expression,
|
|
1065
|
+
dependencies: result.dependencies,
|
|
1066
|
+
minVersion: result.minVersion,
|
|
1067
|
+
features: result.features
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// src/validate-syntax.ts
|
|
1072
|
+
function validateFormulaSyntax(expression) {
|
|
1073
|
+
const result = validateSyntax(expression);
|
|
1074
|
+
if (result.isValid) {
|
|
1075
|
+
return { isValid: true };
|
|
1076
|
+
}
|
|
1077
|
+
return {
|
|
1078
|
+
isValid: false,
|
|
1079
|
+
error: result.error,
|
|
1080
|
+
position: result.position
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
export { evaluate, evaluateWithContext, inferFormulaType, parseExpression, parseFormula, validateFormulaSyntax, validateSyntax };
|
|
1085
|
+
//# sourceMappingURL=chunk-LFEHEGBL.js.map
|
|
1086
|
+
//# sourceMappingURL=chunk-LFEHEGBL.js.map
|