@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.
- package/dist/__tests__/array-context.spec.d.ts +2 -0
- package/dist/__tests__/array-context.spec.d.ts.map +1 -0
- package/dist/__tests__/dependency-graph.spec.d.ts +2 -0
- package/dist/__tests__/dependency-graph.spec.d.ts.map +1 -0
- package/dist/__tests__/evaluate-complex.spec.d.ts +2 -0
- package/dist/__tests__/evaluate-complex.spec.d.ts.map +1 -0
- package/dist/__tests__/parse-formula.spec.d.ts +2 -0
- package/dist/__tests__/parse-formula.spec.d.ts.map +1 -0
- package/dist/__tests__/parser.spec.d.ts +2 -0
- package/dist/__tests__/parser.spec.d.ts.map +1 -0
- package/dist/__tests__/replace-dependencies.spec.d.ts +2 -0
- package/dist/__tests__/replace-dependencies.spec.d.ts.map +1 -0
- package/dist/__tests__/serialize-ast.spec.d.ts +2 -0
- package/dist/__tests__/serialize-ast.spec.d.ts.map +1 -0
- package/dist/__tests__/validate-syntax.spec.d.ts +2 -0
- package/dist/__tests__/validate-syntax.spec.d.ts.map +1 -0
- package/dist/dependency-graph.d.ts +17 -0
- package/dist/dependency-graph.d.ts.map +1 -0
- package/dist/editor/index.cjs +4 -19
- package/dist/editor/index.d.ts +8 -1
- package/dist/editor/index.d.ts.map +1 -0
- package/dist/editor/index.mjs +3 -0
- package/dist/formula-spec.cjs +808 -535
- package/dist/formula-spec.cjs.map +1 -1
- package/dist/formula-spec.d.ts +10 -5
- package/dist/formula-spec.d.ts.map +1 -0
- package/dist/formula-spec.mjs +1039 -0
- package/dist/formula-spec.mjs.map +1 -0
- package/dist/index.cjs +121 -131
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +10 -25
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +130 -0
- package/dist/index.mjs.map +1 -0
- package/dist/ohm/core/parser.d.ts +30 -0
- package/dist/ohm/core/parser.d.ts.map +1 -0
- package/dist/ohm/core/replace-dependencies.d.ts +3 -0
- package/dist/ohm/core/replace-dependencies.d.ts.map +1 -0
- package/dist/ohm/core/serialize-ast.d.ts +3 -0
- package/dist/ohm/core/serialize-ast.d.ts.map +1 -0
- package/dist/ohm/core/types.d.ts +78 -0
- package/dist/ohm/core/types.d.ts.map +1 -0
- package/dist/ohm/grammar/index.d.ts +3 -0
- package/dist/ohm/grammar/index.d.ts.map +1 -0
- package/dist/ohm/index.d.ts +8 -0
- package/dist/ohm/index.d.ts.map +1 -0
- package/dist/ohm/semantics/index.d.ts +2 -0
- package/dist/ohm/semantics/index.d.ts.map +1 -0
- package/dist/parse-formula.d.ts +9 -0
- package/dist/parse-formula.d.ts.map +1 -0
- package/dist/types.d.ts +52 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/validate-syntax-BGrLewnG.cjs +1502 -0
- package/dist/validate-syntax-BGrLewnG.cjs.map +1 -0
- package/dist/validate-syntax-CmqnFrsc.mjs +1421 -0
- package/dist/validate-syntax-CmqnFrsc.mjs.map +1 -0
- package/dist/validate-syntax.d.ts +9 -0
- package/dist/validate-syntax.d.ts.map +1 -0
- package/package.json +12 -11
- package/dist/chunk-4FIOGFOL.cjs +0 -1512
- package/dist/chunk-4FIOGFOL.cjs.map +0 -1
- package/dist/chunk-RMAHNB4D.js +0 -1482
- package/dist/chunk-RMAHNB4D.js.map +0 -1
- package/dist/editor/index.cjs.map +0 -1
- package/dist/editor/index.d.cts +0 -1
- package/dist/editor/index.js +0 -3
- package/dist/editor/index.js.map +0 -1
- package/dist/formula-spec.d.cts +0 -68
- package/dist/formula-spec.js +0 -765
- package/dist/formula-spec.js.map +0 -1
- package/dist/index-CPsOPCQ6.d.cts +0 -166
- package/dist/index-CPsOPCQ6.d.ts +0 -166
- package/dist/index.d.cts +0 -25
- package/dist/index.js +0 -111
- 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
|