@fincity/kirun-js 2.16.2 → 3.0.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.
@@ -10,11 +10,8 @@ import { FunctionSignature } from '../../../model/FunctionSignature';
10
10
  import { Parameter } from '../../../model/Parameter';
11
11
  import { Namespaces } from '../../../namespaces/Namespaces';
12
12
  import { ContextElement } from '../../../runtime/ContextElement';
13
- import { Expression } from '../../../runtime/expression/Expression';
14
13
  import { ExpressionEvaluator } from '../../../runtime/expression/ExpressionEvaluator';
15
- import { ExpressionToken } from '../../../runtime/expression/ExpressionToken';
16
- import { ExpressionTokenValue } from '../../../runtime/expression/ExpressionTokenValue';
17
- import { Operation } from '../../../runtime/expression/Operation';
14
+ import { TokenValueExtractor } from '../../../runtime/expression/tokenextractor/TokenValueExtractor';
18
15
  import { FunctionExecutionParameters } from '../../../runtime/FunctionExecutionParameters';
19
16
  import { isNullValue } from '../../../util/NullCheck';
20
17
  import { StringFormatter } from '../../../util/string/StringFormatter';
@@ -56,110 +53,264 @@ export class SetFunction extends AbstractFunction {
56
53
 
57
54
  let value: any = context?.getArguments()?.get(VALUE);
58
55
 
59
- const exp: Expression = new Expression(key);
60
-
61
- const contextToken: ExpressionToken = exp.getTokens().peekLast();
62
-
63
- if (
64
- !contextToken.getExpression().startsWith('Context') ||
65
- contextToken instanceof Expression ||
66
- (contextToken instanceof ExpressionTokenValue &&
67
- !(contextToken as ExpressionTokenValue)
68
- .getElement()
69
- .toString()
70
- .startsWith('Context'))
71
- ) {
56
+ // Use TokenValueExtractor.splitPath for consistent path parsing
57
+ const parts = TokenValueExtractor.splitPath(key);
58
+
59
+ if (parts.length < 1 || parts[0] !== 'Context') {
72
60
  throw new ExecutionException(
73
61
  StringFormatter.format('The context path $ is not a valid path in context', key),
74
62
  );
75
63
  }
76
64
 
77
- for (const op of exp.getOperations().toArray()) {
78
- if (op == Operation.ARRAY_OPERATOR || op == Operation.OBJECT_OPERATOR) continue;
65
+ // Evaluate any dynamic expressions in the path (e.g., Context.a[Steps.loop.index])
66
+ const evaluatedParts = this.evaluateDynamicParts(parts, context);
79
67
 
80
- throw new ExecutionException(
81
- StringFormatter.format(
82
- 'Expected a reference to the context location, but found an expression $',
83
- key,
84
- ),
85
- );
68
+ return this.modifyContextWithParts(context, key, value, evaluatedParts);
69
+ }
70
+
71
+ /**
72
+ * Evaluate any dynamic expressions in path parts
73
+ * E.g., "Context.a[Steps.loop.index]" where the index is dynamic
74
+ */
75
+ private evaluateDynamicParts(parts: string[], context: FunctionExecutionParameters): string[] {
76
+ const result: string[] = [];
77
+
78
+ for (const part of parts) {
79
+ // Check if this part contains dynamic bracket expressions
80
+ const evaluated = this.evaluateBracketExpressions(part, context);
81
+ result.push(evaluated);
86
82
  }
87
-
88
- for (let i = 0; i < exp.getTokens().size(); i++) {
89
- let ex = exp.getTokens().get(i);
90
- if (ex instanceof Expression)
91
- exp.getTokens().set(
92
- i,
93
- new ExpressionTokenValue(
94
- key,
95
- new ExpressionEvaluator(ex as Expression).evaluate(context.getValuesMap()),
96
- ),
97
- );
83
+
84
+ return result;
85
+ }
86
+
87
+ /**
88
+ * Evaluate bracket expressions in a path part
89
+ * E.g., "arr[Steps.loop.index]" -> "arr[0]" if Steps.loop.index evaluates to 0
90
+ */
91
+ private evaluateBracketExpressions(part: string, context: FunctionExecutionParameters): string {
92
+ // Find bracket expressions that need evaluation
93
+ let result = '';
94
+ let i = 0;
95
+
96
+ while (i < part.length) {
97
+ if (part[i] === '[') {
98
+ result += '[';
99
+ i++;
100
+
101
+ // Find the matching ]
102
+ let bracketContent = '';
103
+ let depth = 1;
104
+ let inQuote = false;
105
+ let quoteChar = '';
106
+
107
+ while (i < part.length && depth > 0) {
108
+ const ch = part[i];
109
+
110
+ if (inQuote) {
111
+ if (ch === quoteChar && part[i - 1] !== '\\') {
112
+ inQuote = false;
113
+ }
114
+ bracketContent += ch;
115
+ } else {
116
+ if (ch === '"' || ch === "'") {
117
+ inQuote = true;
118
+ quoteChar = ch;
119
+ bracketContent += ch;
120
+ } else if (ch === '[') {
121
+ depth++;
122
+ bracketContent += ch;
123
+ } else if (ch === ']') {
124
+ depth--;
125
+ if (depth > 0) bracketContent += ch;
126
+ } else {
127
+ bracketContent += ch;
128
+ }
129
+ }
130
+ i++;
131
+ }
132
+
133
+ // Check if bracket content is a static value (number or quoted string)
134
+ if (/^-?\d+$/.test(bracketContent) ||
135
+ (bracketContent.startsWith('"') && bracketContent.endsWith('"')) ||
136
+ (bracketContent.startsWith("'") && bracketContent.endsWith("'"))) {
137
+ result += bracketContent + ']';
138
+ } else {
139
+ // Dynamic expression - evaluate it
140
+ try {
141
+ const evaluator = new ExpressionEvaluator(bracketContent);
142
+ const evaluatedValue = evaluator.evaluate(context.getValuesMap());
143
+ result += String(evaluatedValue) + ']';
144
+ } catch (err) {
145
+ // If evaluation fails, keep original
146
+ result += bracketContent + ']';
147
+ }
148
+ }
149
+ } else {
150
+ result += part[i];
151
+ i++;
152
+ }
98
153
  }
99
- return this.modifyContext(context, key, value, exp);
154
+
155
+ return result;
100
156
  }
101
157
 
102
- private modifyContext(
158
+ private modifyContextWithParts(
103
159
  context: FunctionExecutionParameters,
104
160
  key: string,
105
161
  value: any,
106
- exp: Expression,
162
+ parts: string[],
107
163
  ): FunctionOutput {
108
- const tokens = exp.getTokens();
109
- tokens.removeLast();
110
- const ops = exp.getOperations();
111
- ops.removeLast();
112
- let ce: ContextElement | undefined = context
113
- .getContext()
114
- ?.get(tokens.removeLast().getExpression());
164
+ // parts[0] is "Context", parts[1] is the context element name
165
+ if (parts.length < 2) {
166
+ throw new KIRuntimeException(
167
+ StringFormatter.format("Context path '$' is too short", key),
168
+ );
169
+ }
170
+
171
+ // Get the first segment after "Context" - this should be a context element key
172
+ // The segment may contain bracket notation like "a[0]" which we need to parse
173
+ const firstSegment = parts[1];
174
+ const firstSegmentParts = this.parseBracketSegments(firstSegment);
175
+ const contextKey = firstSegmentParts[0];
176
+
177
+ let ce: ContextElement | undefined = context.getContext()?.get(contextKey);
115
178
 
116
179
  if (isNullValue(ce)) {
117
180
  throw new KIRuntimeException(
118
- StringFormatter.format("Context doesn't have any element with name '$' ", key),
181
+ StringFormatter.format("Context doesn't have any element with name '$' ", contextKey),
119
182
  );
120
183
  }
121
184
 
122
- if (ops.isEmpty()) {
185
+ // If we just have "Context.a" with no further path
186
+ if (parts.length === 2 && firstSegmentParts.length === 1) {
123
187
  ce!.setElement(value);
124
188
  return new FunctionOutput([EventResult.outputOf(new Map())]);
125
189
  }
126
190
 
127
191
  let el: any = ce!.getElement();
128
-
129
- let op = ops.removeLast();
130
- let token = tokens.removeLast();
131
- let mem =
132
- token instanceof ExpressionTokenValue
133
- ? (token as ExpressionTokenValue).getElement()
134
- : token.getExpression();
135
-
192
+
193
+ // Initialize element if null
136
194
  if (isNullValue(el)) {
137
- el = op == Operation.OBJECT_OPERATOR ? {} : [];
195
+ // Determine if first access is array or object
196
+ const nextIsArray = firstSegmentParts.length > 1
197
+ ? this.isArrayIndex(firstSegmentParts[1])
198
+ : (parts.length > 2 ? this.isArrayAccess(parts[2]) : false);
199
+ el = nextIsArray ? [] : {};
138
200
  ce!.setElement(el);
139
201
  }
140
202
 
141
- while (!ops.isEmpty()) {
142
- if (op == Operation.OBJECT_OPERATOR) {
143
- el = this.getDataFromObject(el, mem, ops.peekLast());
203
+ // Collect all path segments (including bracket notation within segments)
204
+ const allSegments: { value: string; isArray: boolean }[] = [];
205
+
206
+ // Process remaining parts of the first segment (after context key)
207
+ for (let j = 1; j < firstSegmentParts.length; j++) {
208
+ allSegments.push({
209
+ value: this.stripQuotes(firstSegmentParts[j]),
210
+ isArray: this.isArrayIndex(firstSegmentParts[j])
211
+ });
212
+ }
213
+
214
+ // Process remaining parts (parts[2], parts[3], etc.)
215
+ for (let i = 2; i < parts.length; i++) {
216
+ const segmentParts = this.parseBracketSegments(parts[i]);
217
+ for (const seg of segmentParts) {
218
+ allSegments.push({
219
+ value: this.stripQuotes(seg),
220
+ isArray: this.isArrayIndex(seg)
221
+ });
222
+ }
223
+ }
224
+
225
+ // Navigate to the parent of the final element
226
+ for (let i = 0; i < allSegments.length - 1; i++) {
227
+ const segment = allSegments[i];
228
+ const nextSegment = allSegments[i + 1];
229
+
230
+ if (segment.isArray) {
231
+ el = this.getDataFromArray(el, segment.value, nextSegment.isArray);
144
232
  } else {
145
- el = this.getDataFromArray(el, mem, ops.peekLast());
233
+ el = this.getDataFromObject(el, segment.value, nextSegment.isArray);
146
234
  }
147
-
148
- op = ops.removeLast();
149
- token = tokens.removeLast();
150
- mem =
151
- token instanceof ExpressionTokenValue
152
- ? (token as ExpressionTokenValue).getElement()
153
- : token.getExpression();
154
235
  }
155
-
156
- if (op == Operation.OBJECT_OPERATOR) this.putDataInObject(el, mem, value);
157
- else this.putDataInArray(el, mem, value);
236
+
237
+ // Set the final value
238
+ const lastSegment = allSegments[allSegments.length - 1];
239
+ if (lastSegment.isArray) {
240
+ this.putDataInArray(el, lastSegment.value, value);
241
+ } else {
242
+ this.putDataInObject(el, lastSegment.value, value);
243
+ }
158
244
 
159
245
  return new FunctionOutput([EventResult.outputOf(new Map())]);
160
246
  }
247
+
248
+ /**
249
+ * Parse bracket segments from a path part
250
+ * E.g., "arr[0]" -> ["arr", "0"], "obj" -> ["obj"]
251
+ */
252
+ private parseBracketSegments(part: string): string[] {
253
+ const segments: string[] = [];
254
+ let start = 0;
255
+ let i = 0;
256
+
257
+ while (i < part.length) {
258
+ if (part[i] === '[') {
259
+ if (i > start) {
260
+ segments.push(part.substring(start, i));
261
+ }
262
+ // Find matching ]
263
+ let end = i + 1;
264
+ let inQuote = false;
265
+ let quoteChar = '';
266
+ while (end < part.length) {
267
+ if (inQuote) {
268
+ if (part[end] === quoteChar && part[end - 1] !== '\\') {
269
+ inQuote = false;
270
+ }
271
+ } else {
272
+ if (part[end] === '"' || part[end] === "'") {
273
+ inQuote = true;
274
+ quoteChar = part[end];
275
+ } else if (part[end] === ']') {
276
+ break;
277
+ }
278
+ }
279
+ end++;
280
+ }
281
+ segments.push(part.substring(i + 1, end));
282
+ start = end + 1;
283
+ i = start;
284
+ } else {
285
+ i++;
286
+ }
287
+ }
288
+
289
+ if (start < part.length) {
290
+ segments.push(part.substring(start));
291
+ }
292
+
293
+ return segments.length > 0 ? segments : [part];
294
+ }
295
+
296
+ private isArrayIndex(segment: string): boolean {
297
+ return /^-?\d+$/.test(segment);
298
+ }
299
+
300
+ private isArrayAccess(part: string): boolean {
301
+ // Check if the part starts with bracket notation or is a pure number
302
+ return part.startsWith('[') || this.isArrayIndex(part);
303
+ }
304
+
305
+ private stripQuotes(segment: string): string {
306
+ if ((segment.startsWith('"') && segment.endsWith('"')) ||
307
+ (segment.startsWith("'") && segment.endsWith("'"))) {
308
+ return segment.substring(1, segment.length - 1);
309
+ }
310
+ return segment;
311
+ }
161
312
 
162
- private getDataFromArray(el: any, mem: string, nextOp: Operation): any {
313
+ private getDataFromArray(el: any, mem: string, nextIsArray: boolean): any {
163
314
  if (!Array.isArray(el))
164
315
  throw new KIRuntimeException(
165
316
  StringFormatter.format('Expected an array but found $', el),
@@ -178,13 +329,13 @@ export class SetFunction extends AbstractFunction {
178
329
  let je = el[index];
179
330
 
180
331
  if (isNullValue(je)) {
181
- je = nextOp == Operation.OBJECT_OPERATOR ? {} : [];
332
+ je = nextIsArray ? [] : {};
182
333
  el[index] = je;
183
334
  }
184
335
  return je;
185
336
  }
186
337
 
187
- private getDataFromObject(el: any, mem: string, nextOp: Operation): any {
338
+ private getDataFromObject(el: any, mem: string, nextIsArray: boolean): any {
188
339
  if (Array.isArray(el) || typeof el !== 'object')
189
340
  throw new KIRuntimeException(
190
341
  StringFormatter.format('Expected an object but found $', el),
@@ -193,7 +344,7 @@ export class SetFunction extends AbstractFunction {
193
344
  let je = el[mem];
194
345
 
195
346
  if (isNullValue(je)) {
196
- je = nextOp == Operation.OBJECT_OPERATOR ? {} : [];
347
+ je = nextIsArray ? [] : {};
197
348
  el[mem] = je;
198
349
  }
199
350
  return je;