@fincity/kirun-js 3.1.4 → 3.2.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.
Files changed (40) hide show
  1. package/__tests__/engine/dsl/GraphDebugTest.ts +316 -0
  2. package/__tests__/engine/runtime/expression/ExpressionParsingTest.ts +402 -14
  3. package/dist/index.js +15 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/module.js +15 -1
  6. package/dist/module.js.map +1 -1
  7. package/dist/types.d.ts +416 -0
  8. package/dist/types.d.ts.map +1 -1
  9. package/package.json +1 -1
  10. package/src/engine/dsl/DSLCompiler.ts +104 -0
  11. package/src/engine/dsl/index.ts +30 -0
  12. package/src/engine/dsl/lexer/DSLLexer.ts +518 -0
  13. package/src/engine/dsl/lexer/DSLToken.ts +74 -0
  14. package/src/engine/dsl/lexer/Keywords.ts +80 -0
  15. package/src/engine/dsl/lexer/LexerError.ts +37 -0
  16. package/src/engine/dsl/monaco/DSLFunctionProvider.ts +187 -0
  17. package/src/engine/dsl/parser/DSLParser.ts +1075 -0
  18. package/src/engine/dsl/parser/DSLParserError.ts +29 -0
  19. package/src/engine/dsl/parser/ast/ASTNode.ts +23 -0
  20. package/src/engine/dsl/parser/ast/ArgumentNode.ts +43 -0
  21. package/src/engine/dsl/parser/ast/ComplexValueNode.ts +22 -0
  22. package/src/engine/dsl/parser/ast/EventDeclNode.ts +27 -0
  23. package/src/engine/dsl/parser/ast/ExpressionNode.ts +33 -0
  24. package/src/engine/dsl/parser/ast/FunctionCallNode.ts +29 -0
  25. package/src/engine/dsl/parser/ast/FunctionDefNode.ts +37 -0
  26. package/src/engine/dsl/parser/ast/ParameterDeclNode.ts +25 -0
  27. package/src/engine/dsl/parser/ast/SchemaLiteralNode.ts +26 -0
  28. package/src/engine/dsl/parser/ast/SchemaNode.ts +23 -0
  29. package/src/engine/dsl/parser/ast/StatementNode.ts +41 -0
  30. package/src/engine/dsl/parser/ast/index.ts +14 -0
  31. package/src/engine/dsl/transformer/ASTToJSON.ts +378 -0
  32. package/src/engine/dsl/transformer/ExpressionHandler.ts +48 -0
  33. package/src/engine/dsl/transformer/JSONToText.ts +694 -0
  34. package/src/engine/dsl/transformer/SchemaTransformer.ts +110 -0
  35. package/src/engine/model/FunctionDefinition.ts +23 -0
  36. package/src/engine/runtime/expression/Expression.ts +5 -1
  37. package/src/engine/runtime/expression/ExpressionEvaluator.ts +152 -139
  38. package/src/engine/runtime/expression/ExpressionParser.ts +80 -27
  39. package/src/engine/util/duplicate.ts +3 -1
  40. package/src/index.ts +1 -0
@@ -0,0 +1,378 @@
1
+ import {
2
+ ArgumentNode,
3
+ ComplexValueNode,
4
+ EventDeclNode,
5
+ ExpressionNode,
6
+ FunctionDefNode,
7
+ ParameterDeclNode,
8
+ SchemaLiteralNode,
9
+ StatementNode,
10
+ } from '../parser/ast';
11
+ import { SchemaTransformer } from './SchemaTransformer';
12
+
13
+ /**
14
+ * AST to JSON Transformer
15
+ * Converts parsed AST to FunctionDefinition JSON
16
+ */
17
+ export class ASTToJSONTransformer {
18
+ /**
19
+ * Transform AST to FunctionDefinition JSON
20
+ * Only includes non-empty/non-default fields
21
+ */
22
+ public transform(ast: FunctionDefNode): any {
23
+ const json: any = {
24
+ name: ast.name,
25
+ };
26
+
27
+ // Only add namespace if not empty
28
+ if (ast.namespace) {
29
+ json.namespace = ast.namespace;
30
+ }
31
+
32
+ // Only add parameters if not empty
33
+ const parameters = this.transformParameters(ast.parameters);
34
+ if (Object.keys(parameters).length > 0) {
35
+ json.parameters = parameters;
36
+ }
37
+
38
+ // Only add events if not empty
39
+ const events = this.transformEvents(ast.events);
40
+ if (Object.keys(events).length > 0) {
41
+ json.events = events;
42
+ }
43
+
44
+ // Always add steps
45
+ json.steps = this.transformSteps(ast.logic);
46
+
47
+ return json;
48
+ }
49
+
50
+ /**
51
+ * Transform parameters
52
+ */
53
+ private transformParameters(params: ParameterDeclNode[]): any {
54
+ const result: any = {};
55
+ for (const param of params) {
56
+ result[param.name] = {
57
+ parameterName: param.name,
58
+ schema: SchemaTransformer.transform(param.schema.schemaSpec),
59
+ variableArgument: false,
60
+ type: 'EXPRESSION',
61
+ };
62
+ }
63
+ return result;
64
+ }
65
+
66
+ /**
67
+ * Transform events
68
+ */
69
+ private transformEvents(events: EventDeclNode[]): any {
70
+ const result: any = {};
71
+ for (const event of events) {
72
+ result[event.name] = {
73
+ name: event.name,
74
+ parameters: this.transformEventParameters(event.parameters),
75
+ };
76
+ }
77
+ return result;
78
+ }
79
+
80
+ /**
81
+ * Transform event parameters (these are schemas, not Parameter objects)
82
+ */
83
+ private transformEventParameters(params: ParameterDeclNode[]): any {
84
+ const result: any = {};
85
+ for (const param of params) {
86
+ result[param.name] = SchemaTransformer.transform(param.schema.schemaSpec);
87
+ }
88
+ return result;
89
+ }
90
+
91
+ /**
92
+ * Transform statements (top-level and nested)
93
+ */
94
+ private transformSteps(statements: StatementNode[]): any {
95
+ const result: any = {};
96
+ for (const stmt of statements) {
97
+ result[stmt.statementName] = this.transformStatement(stmt);
98
+ }
99
+ return result;
100
+ }
101
+
102
+ /**
103
+ * Transform single statement
104
+ * Only includes non-empty/non-default fields
105
+ */
106
+ private transformStatement(stmt: StatementNode): any {
107
+ const json: any = {
108
+ statementName: stmt.statementName,
109
+ namespace: stmt.functionCall.namespace,
110
+ name: stmt.functionCall.name,
111
+ };
112
+
113
+ // Only add parameterMap if not empty
114
+ const parameterMap = this.transformParameterMap(stmt.functionCall.argumentsMap);
115
+ if (Object.keys(parameterMap).length > 0) {
116
+ json.parameterMap = parameterMap;
117
+ }
118
+
119
+ // Only add dependentStatements if not empty
120
+ const dependentStatements = this.createDependentStatementsMap(stmt);
121
+ if (Object.keys(dependentStatements).length > 0) {
122
+ json.dependentStatements = dependentStatements;
123
+ }
124
+
125
+ // Only add executeIftrue if not empty
126
+ const executeIftrue = this.createExecuteIfMap(stmt.executeIfSteps);
127
+ if (Object.keys(executeIftrue).length > 0) {
128
+ json.executeIftrue = executeIftrue;
129
+ }
130
+
131
+ // Only add comment if not empty
132
+ if (stmt.comment && stmt.comment.trim()) {
133
+ json.comment = stmt.comment;
134
+ }
135
+
136
+ return json;
137
+ }
138
+
139
+ /**
140
+ * Create dependentStatements map from AFTER clause and nested blocks
141
+ */
142
+ private createDependentStatementsMap(stmt: StatementNode): any {
143
+ const result: any = {};
144
+
145
+ // Add explicit AFTER dependencies
146
+ for (const stepRef of stmt.afterSteps) {
147
+ result[stepRef] = true; // true = depends on this step
148
+ }
149
+
150
+ // Add implicit dependencies from nested blocks
151
+ // Nested statements depend on their parent's specific events
152
+ for (const [blockName, nestedStmts] of stmt.nestedBlocks) {
153
+ // Create dependency key based on block name
154
+ // e.g., "Steps.loop.iteration", "Steps.if.true"
155
+ const dependencyKey = `Steps.${stmt.statementName}.${blockName}`;
156
+
157
+ // Mark nested statements as depending on this block
158
+ // (These will be added when we transform nested statements)
159
+ }
160
+
161
+ return result;
162
+ }
163
+
164
+ /**
165
+ * Create executeIftrue map from IF clause
166
+ */
167
+ private createExecuteIfMap(executeIfSteps: string[]): any {
168
+ const result: any = {};
169
+ for (const stepRef of executeIfSteps) {
170
+ result[stepRef] = true;
171
+ }
172
+ return result;
173
+ }
174
+
175
+ /**
176
+ * Transform parameter map (function arguments)
177
+ * Skips parameters with empty values
178
+ * Supports multi-value parameters
179
+ */
180
+ private transformParameterMap(argsMap: Map<string, ArgumentNode>): any {
181
+ const result: any = {};
182
+
183
+ for (const [paramName, arg] of argsMap) {
184
+ // Skip parameters with empty values
185
+ if (this.isEmptyArgument(arg)) {
186
+ continue;
187
+ }
188
+
189
+ result[paramName] = {};
190
+
191
+ // Handle multi-value parameters
192
+ if (arg.isMultiValue()) {
193
+ for (let i = 0; i < arg.values.length; i++) {
194
+ const paramRef = this.transformArgumentValue(arg.values[i], i + 1);
195
+ result[paramName][paramRef.key] = paramRef;
196
+ }
197
+ } else {
198
+ const paramRef = this.transformArgumentValue(arg.value, 1);
199
+ result[paramName][paramRef.key] = paramRef;
200
+ }
201
+ }
202
+
203
+ return result;
204
+ }
205
+
206
+ /**
207
+ * Check if an argument is empty (no value provided)
208
+ * Note: Empty expressions and undefined values are valid and should NOT be filtered out
209
+ * Only filter out parser artifacts like stray delimiters
210
+ */
211
+ private isEmptyArgument(arg: ArgumentNode): boolean {
212
+ if (arg.value instanceof ExpressionNode) {
213
+ const expr = arg.value.expressionText.trim();
214
+ // Only filter out parser artifacts (stray delimiters)
215
+ // Empty expressions ('') are valid and should be preserved
216
+ return expr === ',' || expr === ')';
217
+ }
218
+ // Both null and undefined are valid values and should be preserved
219
+ // ComplexValueNode is never "empty" - it always has a value (even if null/undefined)
220
+ return false;
221
+ }
222
+
223
+ /**
224
+ * Transform single argument value to ParameterReference
225
+ * @param value The argument value (expression, complex value, or schema literal)
226
+ * @param order The order index for multi-value parameters (1-based)
227
+ */
228
+ private transformArgumentValue(
229
+ value: ExpressionNode | ComplexValueNode | SchemaLiteralNode,
230
+ order: number,
231
+ ): any {
232
+ const key = this.generateUUID();
233
+
234
+ if (value instanceof ExpressionNode) {
235
+ return {
236
+ key,
237
+ type: 'EXPRESSION',
238
+ expression: value.expressionText,
239
+ value: undefined,
240
+ order: order,
241
+ };
242
+ } else if (value instanceof ComplexValueNode) {
243
+ return {
244
+ key,
245
+ type: 'VALUE',
246
+ value: value.value,
247
+ expression: undefined,
248
+ order: order,
249
+ };
250
+ } else if (value instanceof SchemaLiteralNode) {
251
+ // Schema literal with optional default value
252
+ const schema = SchemaTransformer.transform(value.schema.schemaSpec);
253
+
254
+ if (value.defaultValue) {
255
+ // Has default value - evaluate it
256
+ return {
257
+ key,
258
+ type: 'VALUE',
259
+ value: this.evaluateDefaultValue(value.defaultValue, schema),
260
+ expression: undefined,
261
+ order: order,
262
+ };
263
+ } else {
264
+ // No default value - just pass the schema
265
+ return {
266
+ key,
267
+ type: 'VALUE',
268
+ value: schema,
269
+ expression: undefined,
270
+ order: order,
271
+ };
272
+ }
273
+ }
274
+
275
+ // Fallback
276
+ return {
277
+ key,
278
+ type: 'VALUE',
279
+ value: null,
280
+ expression: undefined,
281
+ order: order,
282
+ };
283
+ }
284
+
285
+ /**
286
+ * Evaluate default value for schema literal
287
+ */
288
+ private evaluateDefaultValue(defaultValueExpr: ExpressionNode, schema: any): any {
289
+ const exprText = defaultValueExpr.expressionText.trim();
290
+
291
+ // Try to parse as literal value
292
+ if (exprText === '[]') {
293
+ return [];
294
+ }
295
+ if (exprText === '{}') {
296
+ return {};
297
+ }
298
+ if (exprText === 'null') {
299
+ return null;
300
+ }
301
+ if (exprText === 'true') {
302
+ return true;
303
+ }
304
+ if (exprText === 'false') {
305
+ return false;
306
+ }
307
+
308
+ // Try to parse as number
309
+ const num = parseFloat(exprText);
310
+ if (!isNaN(num)) {
311
+ return num;
312
+ }
313
+
314
+ // Try to parse as string literal
315
+ if (
316
+ (exprText.startsWith('"') && exprText.endsWith('"')) ||
317
+ (exprText.startsWith("'") && exprText.endsWith("'"))
318
+ ) {
319
+ return exprText.slice(1, -1);
320
+ }
321
+
322
+ // Otherwise, return as-is (might be an expression)
323
+ return exprText;
324
+ }
325
+
326
+ /**
327
+ * Generate UUID for parameter reference keys
328
+ */
329
+ private generateUUID(): string {
330
+ // Simple UUID v4 generation
331
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
332
+ const r = (Math.random() * 16) | 0;
333
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
334
+ return v.toString(16);
335
+ });
336
+ }
337
+
338
+ /**
339
+ * Flatten nested blocks into steps
340
+ * This adds all nested statements to the top-level steps object
341
+ * and sets their dependentStatements appropriately
342
+ */
343
+ public flattenNestedBlocks(ast: FunctionDefNode): void {
344
+ const allStatements: StatementNode[] = [...ast.logic];
345
+
346
+ // Process each top-level statement
347
+ for (const stmt of ast.logic) {
348
+ this.collectNestedStatements(stmt, allStatements);
349
+ }
350
+
351
+ // Update ast.logic with all statements (flattened)
352
+ ast.logic = allStatements;
353
+ }
354
+
355
+ /**
356
+ * Recursively collect nested statements
357
+ * NOTE: Nesting is implicit (derived from expression analysis)
358
+ * We do NOT add block dependencies to dependentStatements
359
+ * Only explicit AFTER clauses become dependentStatements
360
+ */
361
+ private collectNestedStatements(stmt: StatementNode, allStatements: StatementNode[]): void {
362
+ for (const [, nestedStmts] of stmt.nestedBlocks) {
363
+ for (const nestedStmt of nestedStmts) {
364
+ // Do NOT add implicit block dependency - nesting is derived from expressions
365
+ // Only explicit AFTER clauses should become dependentStatements
366
+
367
+ // Add to all statements
368
+ allStatements.push(nestedStmt);
369
+
370
+ // Recursively process this statement's nested blocks
371
+ this.collectNestedStatements(nestedStmt, allStatements);
372
+ }
373
+ }
374
+
375
+ // Clear nested blocks after flattening
376
+ stmt.nestedBlocks.clear();
377
+ }
378
+ }
@@ -0,0 +1,48 @@
1
+ import { Expression } from '../../runtime/expression/Expression';
2
+
3
+ /**
4
+ * Expression Handler
5
+ * Utilities for working with KIRun expressions
6
+ */
7
+ export class ExpressionHandler {
8
+ /**
9
+ * Parse expression text using KIRun Expression parser
10
+ */
11
+ public static parse(expressionText: string): Expression {
12
+ return new Expression(expressionText);
13
+ }
14
+
15
+ /**
16
+ * Validate expression syntax
17
+ */
18
+ public static validate(expressionText: string): boolean {
19
+ try {
20
+ new Expression(expressionText);
21
+ return true;
22
+ } catch {
23
+ return false;
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Check if a value is an expression (has isExpression flag)
29
+ */
30
+ public static isExpression(value: any): boolean {
31
+ return (
32
+ value &&
33
+ typeof value === 'object' &&
34
+ value.isExpression === true &&
35
+ typeof value.value === 'string'
36
+ );
37
+ }
38
+
39
+ /**
40
+ * Extract expression text from value object
41
+ */
42
+ public static extractExpressionText(value: any): string | null {
43
+ if (this.isExpression(value)) {
44
+ return value.value;
45
+ }
46
+ return null;
47
+ }
48
+ }