@fincity/kirun-js 3.2.2 → 3.4.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/__tests__/engine/function/system/array/ReverseTest.ts +84 -0
- package/__tests__/engine/function/system/string/ReverseTest.ts +51 -6
- package/__tests__/engine/runtime/KIRuntimeParallelTest.ts +200 -0
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/module.js +2 -2
- package/dist/module.js.map +1 -1
- package/dist/types.d.ts +18 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/engine/function/system/array/AddFirst.ts +1 -1
- package/src/engine/function/system/array/Delete.ts +5 -5
- package/src/engine/function/system/array/Fill.ts +2 -2
- package/src/engine/function/system/array/Insert.ts +1 -1
- package/src/engine/function/system/array/Reverse.ts +1 -1
- package/src/engine/function/system/string/RegionMatches.ts +1 -1
- package/src/engine/function/system/string/ReplaceAtGivenPosition.ts +6 -2
- package/src/engine/model/FunctionSignature.ts +36 -2
- package/src/engine/runtime/expression/ExpressionEvaluator.ts +6 -10
- package/src/engine/runtime/expression/exception/ExpressionEvaluationException.ts +15 -4
- package/src/engine/runtime/expression/tokenextractor/LiteralTokenValueExtractor.ts +62 -8
- package/src/engine/runtime/expression/tokenextractor/TokenValueExtractor.ts +3 -2
|
@@ -747,11 +747,9 @@ export class ExpressionEvaluator {
|
|
|
747
747
|
if (key.length > 2 && valuesMap.has(key))
|
|
748
748
|
workingStack.push(new ExpressionTokenValue(str, this.getValue(str, valuesMap)));
|
|
749
749
|
else {
|
|
750
|
-
let v: any;
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
} catch (err) {
|
|
754
|
-
// Check if this is a literal (number, string, boolean, null) with property access
|
|
750
|
+
let v: any = LiteralTokenValueExtractor.INSTANCE.tryGetValue(str);
|
|
751
|
+
if (LiteralTokenValueExtractor.isFailed(v)) {
|
|
752
|
+
// Not a valid literal — check if it's a literal with property access
|
|
755
753
|
// e.g., "2.val" should evaluate to undefined (accessing .val on number 2)
|
|
756
754
|
v = this.evaluateLiteralPropertyAccess(str);
|
|
757
755
|
}
|
|
@@ -774,11 +772,9 @@ export class ExpressionEvaluator {
|
|
|
774
772
|
const basePart = str.substring(0, dotIdx);
|
|
775
773
|
const propPart = str.substring(dotIdx + 1);
|
|
776
774
|
|
|
777
|
-
// Try to parse the base as a literal
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
baseValue = LiteralTokenValueExtractor.INSTANCE.getValue(basePart);
|
|
781
|
-
} catch (err) {
|
|
775
|
+
// Try to parse the base as a literal without throwing
|
|
776
|
+
const baseValue: any = LiteralTokenValueExtractor.INSTANCE.tryGetValue(basePart);
|
|
777
|
+
if (LiteralTokenValueExtractor.isFailed(baseValue)) {
|
|
782
778
|
// Not a valid literal, return the original string
|
|
783
779
|
return str;
|
|
784
780
|
}
|
|
@@ -1,14 +1,25 @@
|
|
|
1
|
-
import { StringFormatter } from '../../../util/string/StringFormatter';
|
|
2
|
-
|
|
3
1
|
export class ExpressionEvaluationException extends Error {
|
|
4
2
|
cause?: Error;
|
|
3
|
+
private readonly _expression: string;
|
|
4
|
+
private readonly _msg: string;
|
|
5
5
|
|
|
6
6
|
constructor(expression: string, message: string, err?: Error) {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
// Temporarily disable stack trace capture — this exception is used as control flow
|
|
8
|
+
// in hot paths, and V8 stack trace capture is very expensive.
|
|
9
|
+
const prevLimit = (Error as any).stackTraceLimit;
|
|
10
|
+
(Error as any).stackTraceLimit = 0;
|
|
11
|
+
super();
|
|
12
|
+
(Error as any).stackTraceLimit = prevLimit;
|
|
13
|
+
this._expression = expression;
|
|
14
|
+
this._msg = message;
|
|
9
15
|
this.cause = err;
|
|
10
16
|
}
|
|
11
17
|
|
|
18
|
+
// Lazy message formatting — only computed when actually read (e.g. logging)
|
|
19
|
+
override get message(): string {
|
|
20
|
+
return this._expression + ' : ' + this._msg;
|
|
21
|
+
}
|
|
22
|
+
|
|
12
23
|
public getCause(): Error | undefined {
|
|
13
24
|
return this.cause;
|
|
14
25
|
}
|
|
@@ -13,6 +13,11 @@ const KEYWORDS: Map<string, any> = new Map([
|
|
|
13
13
|
export class LiteralTokenValueExtractor extends TokenValueExtractor {
|
|
14
14
|
public static readonly INSTANCE: LiteralTokenValueExtractor = new LiteralTokenValueExtractor();
|
|
15
15
|
|
|
16
|
+
public static readonly FAILED: unique symbol = Symbol('LITERAL_PARSE_FAILED');
|
|
17
|
+
|
|
18
|
+
// Cache for number parsing: stores the parsed number, or FAILED symbol for non-numeric tokens
|
|
19
|
+
private static readonly numberCache: Map<string, number | symbol> = new Map();
|
|
20
|
+
|
|
16
21
|
protected getValueInternal(token: string): any {
|
|
17
22
|
if (StringUtil.isNullOrBlank(token)) return undefined;
|
|
18
23
|
|
|
@@ -27,20 +32,52 @@ export class LiteralTokenValueExtractor extends TokenValueExtractor {
|
|
|
27
32
|
return this.processNumbers(token);
|
|
28
33
|
}
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Non-throwing version of getValueInternal. Returns FAILED symbol instead of throwing.
|
|
37
|
+
* This is the hot-path method — avoids ExpressionEvaluationException creation entirely.
|
|
38
|
+
*/
|
|
39
|
+
private tryGetValueInternal(token: string): any {
|
|
40
|
+
if (StringUtil.isNullOrBlank(token)) return undefined;
|
|
41
|
+
|
|
42
|
+
token = token.trim();
|
|
43
|
+
|
|
44
|
+
if (KEYWORDS.has(token)) return KEYWORDS.get(token);
|
|
45
|
+
|
|
46
|
+
if (token.startsWith('"')) {
|
|
47
|
+
if (!token.endsWith('"')) return LiteralTokenValueExtractor.FAILED;
|
|
48
|
+
return token.substring(1, token.length - 1);
|
|
49
|
+
}
|
|
33
50
|
|
|
34
|
-
|
|
51
|
+
return this.tryProcessNumbers(token);
|
|
52
|
+
}
|
|
35
53
|
|
|
36
|
-
|
|
37
|
-
|
|
54
|
+
private processNumbers(token: string): any {
|
|
55
|
+
const result = this.tryProcessNumbers(token);
|
|
56
|
+
if (result === LiteralTokenValueExtractor.FAILED) {
|
|
38
57
|
throw new ExpressionEvaluationException(
|
|
39
58
|
token,
|
|
40
|
-
|
|
41
|
-
err,
|
|
59
|
+
'Unable to parse the literal or expression ' + token,
|
|
42
60
|
);
|
|
43
61
|
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Non-throwing number parser. Returns FAILED symbol for non-numeric tokens.
|
|
67
|
+
* Results are cached so each unique token is parsed at most once.
|
|
68
|
+
*/
|
|
69
|
+
private tryProcessNumbers(token: string): number | symbol {
|
|
70
|
+
const cached = LiteralTokenValueExtractor.numberCache.get(token);
|
|
71
|
+
if (cached !== undefined) return cached;
|
|
72
|
+
|
|
73
|
+
const v = Number(token);
|
|
74
|
+
if (Number.isNaN(v) || token.trim() === '') {
|
|
75
|
+
LiteralTokenValueExtractor.numberCache.set(token, LiteralTokenValueExtractor.FAILED);
|
|
76
|
+
return LiteralTokenValueExtractor.FAILED;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
LiteralTokenValueExtractor.numberCache.set(token, v);
|
|
80
|
+
return v;
|
|
44
81
|
}
|
|
45
82
|
|
|
46
83
|
private processString(token: string): any {
|
|
@@ -61,6 +98,23 @@ export class LiteralTokenValueExtractor extends TokenValueExtractor {
|
|
|
61
98
|
return undefined;
|
|
62
99
|
}
|
|
63
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Try to get a literal value without throwing or creating any exception objects.
|
|
103
|
+
* Returns FAILED symbol if not a valid literal.
|
|
104
|
+
*/
|
|
105
|
+
public tryGetValue(token: string): any {
|
|
106
|
+
const prefix = this.getPrefix();
|
|
107
|
+
if (prefix && !token.startsWith(prefix)) return LiteralTokenValueExtractor.FAILED;
|
|
108
|
+
return this.tryGetValueInternal(token);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Check if a result from tryGetValue indicates failure.
|
|
113
|
+
*/
|
|
114
|
+
public static isFailed(value: any): boolean {
|
|
115
|
+
return value === LiteralTokenValueExtractor.FAILED;
|
|
116
|
+
}
|
|
117
|
+
|
|
64
118
|
public getValueFromExtractors(token: string, maps: Map<string, TokenValueExtractor>): any {
|
|
65
119
|
if (maps.has(token + '.')) return maps.get(token + '.')?.getStore();
|
|
66
120
|
return this.getValue(token);
|
|
@@ -7,6 +7,7 @@ import { ExpressionEvaluationException } from '../exception/ExpressionEvaluation
|
|
|
7
7
|
export abstract class TokenValueExtractor {
|
|
8
8
|
public static readonly REGEX_SQUARE_BRACKETS: RegExp = /[\[\]]/;
|
|
9
9
|
public static readonly REGEX_DOT: RegExp = /(?<!\.)\.(?!\.)/;
|
|
10
|
+
private static readonly REGEX_INTEGER: RegExp = /^-?\d+$/;
|
|
10
11
|
|
|
11
12
|
// Cache for parsed paths to avoid repeated regex splits
|
|
12
13
|
private static pathCache: Map<string, string[]> = new Map();
|
|
@@ -177,7 +178,7 @@ export abstract class TokenValueExtractor {
|
|
|
177
178
|
|
|
178
179
|
// Only use fast path for pure integer strings (no range operators like '..')
|
|
179
180
|
// Note: parseInt('2..4', 10) incorrectly returns 2, so we need to validate first
|
|
180
|
-
if (
|
|
181
|
+
if (TokenValueExtractor.REGEX_INTEGER.test(segment)) {
|
|
181
182
|
const idx = Number.parseInt(segment, 10);
|
|
182
183
|
const actualIdx = idx < 0 ? element.length + idx : idx;
|
|
183
184
|
return actualIdx >= 0 && actualIdx < element.length ? element[actualIdx] : undefined;
|
|
@@ -188,7 +189,7 @@ export abstract class TokenValueExtractor {
|
|
|
188
189
|
if (typeof element === 'string') {
|
|
189
190
|
if (segment === 'length') return element.length;
|
|
190
191
|
// Only use fast path for pure integer strings
|
|
191
|
-
if (
|
|
192
|
+
if (TokenValueExtractor.REGEX_INTEGER.test(segment)) {
|
|
192
193
|
const idx = Number.parseInt(segment, 10);
|
|
193
194
|
const actualIdx = idx < 0 ? element.length + idx : idx;
|
|
194
195
|
return actualIdx >= 0 && actualIdx < element.length ? element[actualIdx] : undefined;
|