@fincity/kirun-js 2.15.1 → 2.16.1
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/__tests__/engine/function/system/MakeTest.ts +317 -0
- package/__tests__/engine/runtime/KIRuntimeTest.ts +2 -1
- package/__tests__/engine/runtime/KIRuntimeVarArgsTest.ts +101 -0
- package/__tests__/engine/runtime/expression/ExpressionEvaluationTest.ts +66 -0
- package/__tests__/engine/runtime/expression/ExpressionEvaluatorStringLiteralTest.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/module.js +1 -1
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +6 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/engine/function/system/Make.ts +83 -0
- package/src/engine/repository/KIRunFunctionRepository.ts +2 -0
- package/src/engine/runtime/KIRuntime.ts +189 -98
- package/src/engine/runtime/expression/Expression.ts +37 -5
- package/src/engine/runtime/expression/ExpressionEvaluator.ts +360 -55
- package/src/engine/runtime/expression/tokenextractor/ExpressionInternalValueExtractor.ts +2 -1
- package/src/engine/runtime/expression/tokenextractor/TokenValueExtractor.ts +100 -18
- package/src/engine/runtime/graph/ExecutionGraph.ts +9 -0
- package/src/engine/runtime/graph/GraphVertex.ts +9 -0
- package/src/engine/runtime/tokenextractor/ArgumentsTokenValueExtractor.ts +2 -1
- package/src/engine/runtime/tokenextractor/ContextTokenValueExtractor.ts +2 -1
- package/src/engine/runtime/tokenextractor/OutputMapTokenValueExtractor.ts +1 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ExecutionException } from '../../exception/ExecutionException';
|
|
2
2
|
import { LinkedList } from '../../util/LinkedList';
|
|
3
|
-
import { StringBuilder } from '../../util/string/StringBuilder';
|
|
4
3
|
import { StringFormatter } from '../../util/string/StringFormatter';
|
|
5
4
|
import { ExpressionEvaluationException } from './exception/ExpressionEvaluationException';
|
|
6
5
|
import { Expression } from './Expression';
|
|
@@ -49,6 +48,21 @@ import { TernaryOperator } from './operators/ternary/TernaryOperator';
|
|
|
49
48
|
import { ExpressionInternalValueExtractor } from './tokenextractor/ExpressionInternalValueExtractor';
|
|
50
49
|
|
|
51
50
|
export class ExpressionEvaluator {
|
|
51
|
+
// Static cache for parsed expressions to avoid re-parsing the same expression
|
|
52
|
+
private static expressionCache: Map<string, Expression> = new Map();
|
|
53
|
+
|
|
54
|
+
// Counter for generating unique keys (faster than Date.now() + random)
|
|
55
|
+
private static keyCounter = 0;
|
|
56
|
+
|
|
57
|
+
private static getCachedExpression(expressionString: string): Expression {
|
|
58
|
+
let exp = ExpressionEvaluator.expressionCache.get(expressionString);
|
|
59
|
+
if (!exp) {
|
|
60
|
+
exp = new Expression(expressionString);
|
|
61
|
+
ExpressionEvaluator.expressionCache.set(expressionString, exp);
|
|
62
|
+
}
|
|
63
|
+
return exp;
|
|
64
|
+
}
|
|
65
|
+
|
|
52
66
|
private static readonly UNARY_OPERATORS_MAP: Map<Operation, UnaryOperator> = new Map([
|
|
53
67
|
[Operation.UNARY_BITWISE_COMPLEMENT, new BitwiseComplementOperator()],
|
|
54
68
|
[Operation.UNARY_LOGICAL_NOT, new LogicalNotOperator()],
|
|
@@ -94,6 +108,226 @@ export class ExpressionEvaluator {
|
|
|
94
108
|
ExpressionEvaluator.UNARY_OPERATORS_MAP.keys(),
|
|
95
109
|
);
|
|
96
110
|
|
|
111
|
+
// ==================== FAST PATH DETECTION CACHES ====================
|
|
112
|
+
|
|
113
|
+
// Expression pattern types for fast path routing
|
|
114
|
+
private static readonly PATTERN_UNKNOWN = 0;
|
|
115
|
+
private static readonly PATTERN_LITERAL = 1;
|
|
116
|
+
private static readonly PATTERN_SIMPLE_PATH = 2;
|
|
117
|
+
private static readonly PATTERN_SIMPLE_ARRAY_ACCESS = 3;
|
|
118
|
+
private static readonly PATTERN_SIMPLE_COMPARISON = 4;
|
|
119
|
+
private static readonly PATTERN_SIMPLE_TERNARY = 5;
|
|
120
|
+
|
|
121
|
+
// Cache for expression pattern detection
|
|
122
|
+
private static patternCache: Map<string, number> = new Map();
|
|
123
|
+
|
|
124
|
+
// Regex patterns for fast detection (compiled once)
|
|
125
|
+
private static readonly LITERAL_TRUE = 'true';
|
|
126
|
+
private static readonly LITERAL_FALSE = 'false';
|
|
127
|
+
private static readonly LITERAL_NULL = 'null';
|
|
128
|
+
private static readonly LITERAL_UNDEFINED = 'undefined';
|
|
129
|
+
private static readonly NUMBER_REGEX = /^-?\d+(\.\d+)?$/;
|
|
130
|
+
private static readonly SINGLE_QUOTE_STRING_REGEX = /^'([^'\\]|\\.)*'$/;
|
|
131
|
+
private static readonly DOUBLE_QUOTE_STRING_REGEX = /^"([^"\\]|\\.)*"$/;
|
|
132
|
+
|
|
133
|
+
// Simple path regex: Store.path.to.value or Store.path[0].value (no nested expressions)
|
|
134
|
+
private static readonly SIMPLE_PATH_REGEX = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*|\[\d+\])*$/;
|
|
135
|
+
|
|
136
|
+
// Detect expression pattern type for fast path routing
|
|
137
|
+
private static detectPattern(exp: Expression): number {
|
|
138
|
+
const expStr = exp.getExpression();
|
|
139
|
+
|
|
140
|
+
// Check cache
|
|
141
|
+
const cached = ExpressionEvaluator.patternCache.get(expStr);
|
|
142
|
+
if (cached !== undefined) return cached;
|
|
143
|
+
|
|
144
|
+
let pattern = ExpressionEvaluator.PATTERN_UNKNOWN;
|
|
145
|
+
|
|
146
|
+
// 1. Check for literals (no parsing needed)
|
|
147
|
+
if (expStr === ExpressionEvaluator.LITERAL_TRUE ||
|
|
148
|
+
expStr === ExpressionEvaluator.LITERAL_FALSE ||
|
|
149
|
+
expStr === ExpressionEvaluator.LITERAL_NULL ||
|
|
150
|
+
expStr === ExpressionEvaluator.LITERAL_UNDEFINED ||
|
|
151
|
+
ExpressionEvaluator.NUMBER_REGEX.test(expStr) ||
|
|
152
|
+
ExpressionEvaluator.SINGLE_QUOTE_STRING_REGEX.test(expStr) ||
|
|
153
|
+
ExpressionEvaluator.DOUBLE_QUOTE_STRING_REGEX.test(expStr)) {
|
|
154
|
+
pattern = ExpressionEvaluator.PATTERN_LITERAL;
|
|
155
|
+
}
|
|
156
|
+
// 2. Check for simple path (must have dot, no complex operators)
|
|
157
|
+
// Exclude expressions with range operator '..' or nested expressions '{{'
|
|
158
|
+
else if (expStr.includes('.') && !expStr.includes('{{') && !expStr.includes('..')) {
|
|
159
|
+
const ops = exp.getOperationsArray();
|
|
160
|
+
const tokens = exp.getTokensArray();
|
|
161
|
+
|
|
162
|
+
// Must have only OBJECT_OPERATOR or ARRAY_OPERATOR
|
|
163
|
+
let isSimplePath = ops.length > 0;
|
|
164
|
+
for (const op of ops) {
|
|
165
|
+
if (op !== Operation.OBJECT_OPERATOR && op !== Operation.ARRAY_OPERATOR) {
|
|
166
|
+
isSimplePath = false;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// No tokens can be nested expressions
|
|
172
|
+
if (isSimplePath) {
|
|
173
|
+
for (const token of tokens) {
|
|
174
|
+
if (token instanceof Expression) {
|
|
175
|
+
isSimplePath = false;
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (isSimplePath) {
|
|
182
|
+
pattern = ExpressionEvaluator.PATTERN_SIMPLE_PATH;
|
|
183
|
+
} else {
|
|
184
|
+
// 3. Check for simple ternary: condition ? value1 : value2
|
|
185
|
+
pattern = ExpressionEvaluator.detectTernaryOrComparison(exp, ops);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Check for range operator expressions (need full evaluation)
|
|
189
|
+
else if (expStr.includes('..')) {
|
|
190
|
+
pattern = ExpressionEvaluator.PATTERN_UNKNOWN;
|
|
191
|
+
}
|
|
192
|
+
// 4. Check ternary/comparison for expressions without dots
|
|
193
|
+
else if (!expStr.includes('{{')) {
|
|
194
|
+
const ops = exp.getOperationsArray();
|
|
195
|
+
pattern = ExpressionEvaluator.detectTernaryOrComparison(exp, ops);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
ExpressionEvaluator.patternCache.set(expStr, pattern);
|
|
199
|
+
return pattern;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Detect simple ternary or comparison patterns
|
|
203
|
+
private static detectTernaryOrComparison(exp: Expression, ops: Operation[]): number {
|
|
204
|
+
const tokens = exp.getTokensArray();
|
|
205
|
+
|
|
206
|
+
// Check for nested expressions in tokens
|
|
207
|
+
for (const token of tokens) {
|
|
208
|
+
if (token instanceof Expression) {
|
|
209
|
+
return ExpressionEvaluator.PATTERN_UNKNOWN;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Check for simple ternary: exactly one CONDITIONAL_TERNARY_OPERATOR and simple tokens
|
|
214
|
+
if (ops.length === 1 && ops[0] === Operation.CONDITIONAL_TERNARY_OPERATOR) {
|
|
215
|
+
return ExpressionEvaluator.PATTERN_SIMPLE_TERNARY;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Check for simple comparison: exactly one EQUAL or NOT_EQUAL
|
|
219
|
+
if (ops.length === 1 && (ops[0] === Operation.EQUAL || ops[0] === Operation.NOT_EQUAL)) {
|
|
220
|
+
return ExpressionEvaluator.PATTERN_SIMPLE_COMPARISON;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return ExpressionEvaluator.PATTERN_UNKNOWN;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ==================== FAST PATH EVALUATORS ====================
|
|
227
|
+
|
|
228
|
+
// Fast path for literals
|
|
229
|
+
private static evaluateLiteral(expStr: string): any {
|
|
230
|
+
if (expStr === ExpressionEvaluator.LITERAL_TRUE) return true;
|
|
231
|
+
if (expStr === ExpressionEvaluator.LITERAL_FALSE) return false;
|
|
232
|
+
if (expStr === ExpressionEvaluator.LITERAL_NULL) return null;
|
|
233
|
+
if (expStr === ExpressionEvaluator.LITERAL_UNDEFINED) return undefined;
|
|
234
|
+
|
|
235
|
+
// Number
|
|
236
|
+
if (ExpressionEvaluator.NUMBER_REGEX.test(expStr)) {
|
|
237
|
+
return expStr.includes('.') ? Number.parseFloat(expStr) : Number.parseInt(expStr, 10);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Quoted string - just remove quotes (no escape processing to match original behavior)
|
|
241
|
+
// The LiteralTokenValueExtractor also doesn't process escapes for non-standard sequences
|
|
242
|
+
if (ExpressionEvaluator.SINGLE_QUOTE_STRING_REGEX.test(expStr)) {
|
|
243
|
+
return expStr.slice(1, -1);
|
|
244
|
+
}
|
|
245
|
+
if (ExpressionEvaluator.DOUBLE_QUOTE_STRING_REGEX.test(expStr)) {
|
|
246
|
+
return expStr.slice(1, -1);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Fast path for simple paths (Store.path.to.value)
|
|
253
|
+
private evaluateSimplePath(exp: Expression, valuesMap: Map<string, TokenValueExtractor>): any {
|
|
254
|
+
const pathStr = exp.getExpression();
|
|
255
|
+
const dotIdx = pathStr.indexOf('.');
|
|
256
|
+
if (dotIdx === -1) return undefined;
|
|
257
|
+
|
|
258
|
+
const prefix = pathStr.substring(0, dotIdx + 1);
|
|
259
|
+
const extractor = valuesMap.get(prefix);
|
|
260
|
+
if (!extractor) return undefined;
|
|
261
|
+
|
|
262
|
+
return extractor.getValue(pathStr);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Fast path for simple comparisons (path = value, path != value)
|
|
266
|
+
private evaluateSimpleComparison(
|
|
267
|
+
exp: Expression,
|
|
268
|
+
valuesMap: Map<string, TokenValueExtractor>
|
|
269
|
+
): any {
|
|
270
|
+
const ops = exp.getOperationsArray();
|
|
271
|
+
const tokens = exp.getTokensArray();
|
|
272
|
+
|
|
273
|
+
if (tokens.length !== 2 || ops.length !== 1) return undefined;
|
|
274
|
+
|
|
275
|
+
const v1 = this.getTokenValue(tokens[1], valuesMap);
|
|
276
|
+
const v2 = this.getTokenValue(tokens[0], valuesMap);
|
|
277
|
+
|
|
278
|
+
if (ops[0] === Operation.EQUAL) {
|
|
279
|
+
return v1 == v2;
|
|
280
|
+
} else if (ops[0] === Operation.NOT_EQUAL) {
|
|
281
|
+
return v1 != v2;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Fast path for simple ternary (condition ? value1 : value2)
|
|
288
|
+
private evaluateSimpleTernary(
|
|
289
|
+
exp: Expression,
|
|
290
|
+
valuesMap: Map<string, TokenValueExtractor>
|
|
291
|
+
): any {
|
|
292
|
+
const tokens = exp.getTokensArray();
|
|
293
|
+
|
|
294
|
+
if (tokens.length !== 3) return undefined;
|
|
295
|
+
|
|
296
|
+
const condition = this.getTokenValue(tokens[2], valuesMap);
|
|
297
|
+
const trueValue = this.getTokenValue(tokens[1], valuesMap);
|
|
298
|
+
const falseValue = this.getTokenValue(tokens[0], valuesMap);
|
|
299
|
+
|
|
300
|
+
return condition ? trueValue : falseValue;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Helper to get value from a token (for fast paths)
|
|
304
|
+
private getTokenValue(token: ExpressionToken, valuesMap: Map<string, TokenValueExtractor>): any {
|
|
305
|
+
if (token instanceof ExpressionTokenValue) {
|
|
306
|
+
return token.getElement();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const tokenStr = token.getExpression();
|
|
310
|
+
|
|
311
|
+
// Check if it's a literal
|
|
312
|
+
const literalVal = ExpressionEvaluator.evaluateLiteral(tokenStr);
|
|
313
|
+
if (literalVal !== undefined || tokenStr === 'undefined' || tokenStr === 'null') {
|
|
314
|
+
return literalVal;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Check if it's a path
|
|
318
|
+
const dotIdx = tokenStr.indexOf('.');
|
|
319
|
+
if (dotIdx !== -1) {
|
|
320
|
+
const prefix = tokenStr.substring(0, dotIdx + 1);
|
|
321
|
+
const extractor = valuesMap.get(prefix);
|
|
322
|
+
if (extractor) {
|
|
323
|
+
return extractor.getValue(tokenStr);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Fall back to literal extractor
|
|
328
|
+
return LiteralTokenValueExtractor.INSTANCE.getValue(tokenStr);
|
|
329
|
+
}
|
|
330
|
+
|
|
97
331
|
private expression: string;
|
|
98
332
|
private exp?: Expression;
|
|
99
333
|
private internalTokenValueExtractor: ExpressionInternalValueExtractor =
|
|
@@ -113,8 +347,34 @@ export class ExpressionEvaluator {
|
|
|
113
347
|
this.expression,
|
|
114
348
|
valuesMap,
|
|
115
349
|
);
|
|
350
|
+
|
|
116
351
|
this.expression = tuple.getT1();
|
|
117
352
|
this.exp = tuple.getT2();
|
|
353
|
+
|
|
354
|
+
// Detect pattern type for fast path routing
|
|
355
|
+
const pattern = ExpressionEvaluator.detectPattern(this.exp);
|
|
356
|
+
|
|
357
|
+
// Fast path 1: Literals (true, false, numbers, strings)
|
|
358
|
+
if (pattern === ExpressionEvaluator.PATTERN_LITERAL) {
|
|
359
|
+
return ExpressionEvaluator.evaluateLiteral(this.expression);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Fast path 2: Simple paths (Store.path.to.value)
|
|
363
|
+
if (pattern === ExpressionEvaluator.PATTERN_SIMPLE_PATH) {
|
|
364
|
+
return this.evaluateSimplePath(this.exp, valuesMap);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Fast path 3: Simple comparison (path = value)
|
|
368
|
+
if (pattern === ExpressionEvaluator.PATTERN_SIMPLE_COMPARISON) {
|
|
369
|
+
return this.evaluateSimpleComparison(this.exp, valuesMap);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Fast path 4: Simple ternary (condition ? value1 : value2)
|
|
373
|
+
if (pattern === ExpressionEvaluator.PATTERN_SIMPLE_TERNARY) {
|
|
374
|
+
return this.evaluateSimpleTernary(this.exp, valuesMap);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Full evaluation path for complex expressions
|
|
118
378
|
valuesMap = new Map(valuesMap.entries());
|
|
119
379
|
valuesMap.set(
|
|
120
380
|
this.internalTokenValueExtractor.getPrefix(),
|
|
@@ -158,7 +418,7 @@ export class ExpressionEvaluator {
|
|
|
158
418
|
|
|
159
419
|
let newExpression = this.replaceNestingExpression(expression, valuesMap, tuples);
|
|
160
420
|
|
|
161
|
-
return new Tuple2(newExpression,
|
|
421
|
+
return new Tuple2(newExpression, ExpressionEvaluator.getCachedExpression(newExpression));
|
|
162
422
|
}
|
|
163
423
|
|
|
164
424
|
private replaceNestingExpression(
|
|
@@ -188,7 +448,7 @@ export class ExpressionEvaluator {
|
|
|
188
448
|
}
|
|
189
449
|
|
|
190
450
|
public getExpression(): Expression {
|
|
191
|
-
if (!this.exp) this.exp =
|
|
451
|
+
if (!this.exp) this.exp = ExpressionEvaluator.getCachedExpression(this.expression);
|
|
192
452
|
|
|
193
453
|
return this.exp;
|
|
194
454
|
}
|
|
@@ -198,65 +458,105 @@ export class ExpressionEvaluator {
|
|
|
198
458
|
}
|
|
199
459
|
|
|
200
460
|
private evaluateExpression(exp: Expression, valuesMap: Map<string, TokenValueExtractor>): any {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
461
|
+
// Use cached arrays for fast evaluation (no LinkedList traversal)
|
|
462
|
+
const opsArray: Operation[] = exp.getOperationsArray();
|
|
463
|
+
const tokensSource: ExpressionToken[] = exp.getTokensArray();
|
|
464
|
+
const workingStack: ExpressionToken[] = [];
|
|
465
|
+
|
|
466
|
+
// Context for tracking indices - passed to helper methods
|
|
467
|
+
const ctx = { opIdx: 0, srcIdx: 0 };
|
|
468
|
+
|
|
469
|
+
// Pop from working stack first (LIFO for results), then from source array
|
|
470
|
+
const popToken = (): ExpressionToken => {
|
|
471
|
+
if (workingStack.length > 0) {
|
|
472
|
+
return workingStack.pop()!;
|
|
473
|
+
}
|
|
474
|
+
return tokensSource[ctx.srcIdx++];
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
// Pop operation from source array
|
|
478
|
+
const popOp = (): Operation | undefined => {
|
|
479
|
+
if (ctx.opIdx >= opsArray.length) return undefined;
|
|
480
|
+
return opsArray[ctx.opIdx++];
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
// Peek at next operation without consuming
|
|
484
|
+
const peekOp = (): Operation | undefined => {
|
|
485
|
+
if (ctx.opIdx >= opsArray.length) return undefined;
|
|
486
|
+
return opsArray[ctx.opIdx];
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
// Check if there are more tokens available
|
|
490
|
+
const hasMoreTokens = (): boolean => {
|
|
491
|
+
return workingStack.length > 0 || ctx.srcIdx < tokensSource.length;
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
while (ctx.opIdx < opsArray.length) {
|
|
495
|
+
let operator: Operation = popOp()!;
|
|
496
|
+
let token: ExpressionToken = popToken();
|
|
207
497
|
|
|
208
498
|
if (ExpressionEvaluator.UNARY_OPERATORS_MAP_KEY_SET.has(operator)) {
|
|
209
|
-
|
|
499
|
+
workingStack.push(
|
|
210
500
|
this.applyUnaryOperation(operator, this.getValueFromToken(valuesMap, token)),
|
|
211
501
|
);
|
|
212
502
|
} else if (
|
|
213
503
|
operator == Operation.OBJECT_OPERATOR ||
|
|
214
504
|
operator == Operation.ARRAY_OPERATOR
|
|
215
505
|
) {
|
|
216
|
-
this.
|
|
506
|
+
this.processObjectOrArrayOperatorIndexed(
|
|
507
|
+
valuesMap, opsArray, tokensSource, workingStack, ctx, operator, token, popToken, popOp, peekOp, hasMoreTokens
|
|
508
|
+
);
|
|
217
509
|
} else if (operator == Operation.CONDITIONAL_TERNARY_OPERATOR) {
|
|
218
|
-
const token2: ExpressionToken =
|
|
219
|
-
const token3: ExpressionToken =
|
|
510
|
+
const token2: ExpressionToken = popToken();
|
|
511
|
+
const token3: ExpressionToken = popToken();
|
|
220
512
|
let v1 = this.getValueFromToken(valuesMap, token3);
|
|
221
513
|
let v2 = this.getValueFromToken(valuesMap, token2);
|
|
222
514
|
let v3 = this.getValueFromToken(valuesMap, token);
|
|
223
|
-
|
|
515
|
+
workingStack.push(this.applyTernaryOperation(operator, v1, v2, v3));
|
|
224
516
|
} else {
|
|
225
|
-
const token2: ExpressionToken =
|
|
517
|
+
const token2: ExpressionToken = popToken();
|
|
226
518
|
let v1 = this.getValueFromToken(valuesMap, token2);
|
|
227
519
|
let v2 = this.getValueFromToken(valuesMap, token);
|
|
228
|
-
|
|
520
|
+
workingStack.push(this.applyBinaryOperation(operator, v1, v2));
|
|
229
521
|
}
|
|
230
522
|
}
|
|
523
|
+
|
|
524
|
+
// Collect remaining source tokens
|
|
525
|
+
while (ctx.srcIdx < tokensSource.length) {
|
|
526
|
+
workingStack.push(tokensSource[ctx.srcIdx++]);
|
|
527
|
+
}
|
|
231
528
|
|
|
232
|
-
if (
|
|
529
|
+
if (workingStack.length === 0)
|
|
233
530
|
throw new ExecutionException(
|
|
234
531
|
StringFormatter.format('Expression : $ evaluated to null', exp),
|
|
235
532
|
);
|
|
236
533
|
|
|
237
|
-
if (
|
|
534
|
+
if (workingStack.length !== 1)
|
|
238
535
|
throw new ExecutionException(
|
|
239
|
-
StringFormatter.format('Expression : $ evaluated multiple values $', exp,
|
|
536
|
+
StringFormatter.format('Expression : $ evaluated multiple values $', exp, workingStack),
|
|
240
537
|
);
|
|
241
538
|
|
|
242
|
-
const token: ExpressionToken =
|
|
539
|
+
const token: ExpressionToken = workingStack[0];
|
|
243
540
|
if (token instanceof ExpressionTokenValue) return token.getElement();
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
throw new ExecutionException(
|
|
247
|
-
StringFormatter.format('Expression : $ evaluated to $', exp, tokens.get(0)),
|
|
248
|
-
);
|
|
541
|
+
if (token instanceof Expression) return this.evaluateExpression(token, valuesMap);
|
|
542
|
+
return this.getValueFromToken(valuesMap, token);
|
|
249
543
|
}
|
|
250
544
|
|
|
251
|
-
private
|
|
545
|
+
private processObjectOrArrayOperatorIndexed(
|
|
252
546
|
valuesMap: Map<string, TokenValueExtractor>,
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
547
|
+
opsArray: Operation[],
|
|
548
|
+
tokensSource: ExpressionToken[],
|
|
549
|
+
workingStack: ExpressionToken[],
|
|
550
|
+
ctx: { opIdx: number; srcIdx: number },
|
|
551
|
+
operator: Operation | undefined,
|
|
552
|
+
token: ExpressionToken | undefined,
|
|
553
|
+
popToken: () => ExpressionToken,
|
|
554
|
+
popOp: () => Operation | undefined,
|
|
555
|
+
peekOp: () => Operation | undefined,
|
|
556
|
+
hasMoreTokens: () => boolean,
|
|
257
557
|
): void {
|
|
258
|
-
const objTokens:
|
|
259
|
-
const objOperations:
|
|
558
|
+
const objTokens: ExpressionToken[] = [];
|
|
559
|
+
const objOperations: Operation[] = [];
|
|
260
560
|
|
|
261
561
|
if (!operator || !token) return;
|
|
262
562
|
|
|
@@ -270,8 +570,9 @@ export class ExpressionEvaluator {
|
|
|
270
570
|
),
|
|
271
571
|
);
|
|
272
572
|
else if (token) objTokens.push(token);
|
|
273
|
-
|
|
274
|
-
|
|
573
|
+
|
|
574
|
+
token = hasMoreTokens() ? popToken() : undefined;
|
|
575
|
+
operator = popOp();
|
|
275
576
|
} while (operator == Operation.OBJECT_OPERATOR || operator == Operation.ARRAY_OPERATOR);
|
|
276
577
|
|
|
277
578
|
if (token) {
|
|
@@ -285,40 +586,42 @@ export class ExpressionEvaluator {
|
|
|
285
586
|
else objTokens.push(token);
|
|
286
587
|
}
|
|
287
588
|
|
|
288
|
-
|
|
589
|
+
// If we consumed an operator that's not OBJECT/ARRAY, put the index back
|
|
590
|
+
if (operator !== undefined) {
|
|
591
|
+
ctx.opIdx--;
|
|
592
|
+
}
|
|
289
593
|
|
|
290
|
-
|
|
594
|
+
// Process collected tokens and operations (in reverse order since we used push)
|
|
595
|
+
let objTokenIdx = objTokens.length - 1;
|
|
596
|
+
let objOpIdx = objOperations.length - 1;
|
|
597
|
+
|
|
598
|
+
let objToken: ExpressionToken = objTokens[objTokenIdx--];
|
|
291
599
|
|
|
292
600
|
if (
|
|
293
601
|
objToken instanceof ExpressionTokenValue &&
|
|
294
602
|
typeof objToken.getTokenValue() === 'object'
|
|
295
603
|
) {
|
|
296
|
-
const key =
|
|
604
|
+
const key = '_k' + (ExpressionEvaluator.keyCounter++);
|
|
297
605
|
this.internalTokenValueExtractor.addValue(key, objToken.getTokenValue());
|
|
298
606
|
objToken = new ExpressionToken(ExpressionInternalValueExtractor.PREFIX + key);
|
|
299
607
|
}
|
|
300
608
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
);
|
|
609
|
+
// Use string concatenation instead of StringBuilder (V8 optimizes this well)
|
|
610
|
+
let str: string = objToken instanceof ExpressionTokenValue
|
|
611
|
+
? objToken.getTokenValue()
|
|
612
|
+
: objToken.toString();
|
|
306
613
|
|
|
307
|
-
while (
|
|
308
|
-
objToken = objTokens
|
|
309
|
-
operator = objOperations
|
|
310
|
-
|
|
311
|
-
objToken
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
);
|
|
315
|
-
if (operator == Operation.ARRAY_OPERATOR) sb.append(']');
|
|
614
|
+
while (objTokenIdx >= 0) {
|
|
615
|
+
objToken = objTokens[objTokenIdx--];
|
|
616
|
+
operator = objOperations[objOpIdx--];
|
|
617
|
+
const tokenVal = objToken instanceof ExpressionTokenValue
|
|
618
|
+
? objToken.getTokenValue()
|
|
619
|
+
: objToken.toString();
|
|
620
|
+
str = str + operator!.getOperator() + tokenVal + (operator == Operation.ARRAY_OPERATOR ? ']' : '');
|
|
316
621
|
}
|
|
317
|
-
|
|
318
|
-
let str: string = sb.toString();
|
|
319
622
|
let key: string = str.substring(0, str.indexOf('.') + 1);
|
|
320
623
|
if (key.length > 2 && valuesMap.has(key))
|
|
321
|
-
|
|
624
|
+
workingStack.push(new ExpressionTokenValue(str, this.getValue(str, valuesMap)));
|
|
322
625
|
else {
|
|
323
626
|
let v: any;
|
|
324
627
|
try {
|
|
@@ -326,7 +629,7 @@ export class ExpressionEvaluator {
|
|
|
326
629
|
} catch (err) {
|
|
327
630
|
v = str;
|
|
328
631
|
}
|
|
329
|
-
|
|
632
|
+
workingStack.push(new ExpressionTokenValue(str, v));
|
|
330
633
|
}
|
|
331
634
|
}
|
|
332
635
|
|
|
@@ -434,3 +737,5 @@ export class ExpressionEvaluator {
|
|
|
434
737
|
return LiteralTokenValueExtractor.INSTANCE.getValueFromExtractors(path, valuesMap);
|
|
435
738
|
}
|
|
436
739
|
}
|
|
740
|
+
|
|
741
|
+
|
|
@@ -10,13 +10,14 @@ export class ExpressionInternalValueExtractor extends TokenValueExtractor {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
public getValueInternal(token: string): any {
|
|
13
|
-
let parts: string[] =
|
|
13
|
+
let parts: string[] = TokenValueExtractor.splitPath(token);
|
|
14
14
|
|
|
15
15
|
let key: string = parts[1];
|
|
16
16
|
let bIndex: number = key.indexOf('[');
|
|
17
17
|
let fromIndex = 2;
|
|
18
18
|
if (bIndex != -1) {
|
|
19
19
|
key = parts[1].substring(0, bIndex);
|
|
20
|
+
parts = [...parts]; // Copy since we're modifying
|
|
20
21
|
parts[1] = parts[1].substring(bIndex);
|
|
21
22
|
fromIndex = 1;
|
|
22
23
|
}
|