@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.
@@ -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, new Expression(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 = new Expression(this.expression);
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
- let ops: LinkedList<Operation> = exp.getOperations();
202
- let tokens: LinkedList<ExpressionToken> = exp.getTokens();
203
-
204
- while (!ops.isEmpty()) {
205
- let operator: Operation = ops.pop();
206
- let token: ExpressionToken = tokens.pop();
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
- tokens.push(
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.processObjectOrArrayOperator(valuesMap, ops, tokens, operator, token);
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 = tokens.pop();
219
- const token3: ExpressionToken = tokens.pop();
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
- tokens.push(this.applyTernaryOperation(operator, v1, v2, v3));
515
+ workingStack.push(this.applyTernaryOperation(operator, v1, v2, v3));
224
516
  } else {
225
- const token2: ExpressionToken = tokens.pop();
517
+ const token2: ExpressionToken = popToken();
226
518
  let v1 = this.getValueFromToken(valuesMap, token2);
227
519
  let v2 = this.getValueFromToken(valuesMap, token);
228
- tokens.push(this.applyBinaryOperation(operator, v1, v2));
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 (tokens.isEmpty())
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 (tokens.size() != 1)
534
+ if (workingStack.length !== 1)
238
535
  throw new ExecutionException(
239
- StringFormatter.format('Expression : $ evaluated multiple values $', exp, tokens),
536
+ StringFormatter.format('Expression : $ evaluated multiple values $', exp, workingStack),
240
537
  );
241
538
 
242
- const token: ExpressionToken = tokens.get(0);
539
+ const token: ExpressionToken = workingStack[0];
243
540
  if (token instanceof ExpressionTokenValue) return token.getElement();
244
- else if (!(token instanceof Expression)) return this.getValueFromToken(valuesMap, token);
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 processObjectOrArrayOperator(
545
+ private processObjectOrArrayOperatorIndexed(
252
546
  valuesMap: Map<string, TokenValueExtractor>,
253
- ops: LinkedList<Operation>,
254
- tokens: LinkedList<ExpressionToken>,
255
- operator?: Operation,
256
- token?: ExpressionToken,
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: LinkedList<ExpressionToken> = new LinkedList();
259
- const objOperations: LinkedList<Operation> = new LinkedList();
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
- token = tokens.isEmpty() ? undefined : tokens.pop();
274
- operator = ops.isEmpty() ? undefined : ops.pop();
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
- if (operator) ops.push(operator);
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
- let objToken: ExpressionToken = objTokens.pop();
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 = new Date().getTime() + '' + Math.round(Math.random() * 1000);
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
- let sb: StringBuilder = new StringBuilder(
302
- objToken instanceof ExpressionTokenValue
303
- ? objToken.getTokenValue()
304
- : objToken.toString(),
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 (!objTokens.isEmpty()) {
308
- objToken = objTokens.pop();
309
- operator = objOperations.pop();
310
- sb.append(operator.getOperator()).append(
311
- objToken instanceof ExpressionTokenValue
312
- ? objToken.getTokenValue()
313
- : objToken.toString(),
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
- tokens.push(new ExpressionTokenValue(str, this.getValue(str, valuesMap)));
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
- tokens.push(new ExpressionTokenValue(str, v));
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[] = token.split(TokenValueExtractor.REGEX_DOT);
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
  }