@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
@@ -9,28 +9,67 @@ export class ExpressionParser {
9
9
  private lexer: ExpressionLexer;
10
10
  private currentToken: Token | null = null;
11
11
  private previousTokenValue: Token | null = null;
12
+ private readonly originalExpression: string;
13
+ private parseDepth: number = 0;
12
14
 
13
15
  constructor(expression: string) {
16
+ this.originalExpression = expression;
14
17
  this.lexer = new ExpressionLexer(expression);
15
18
  this.currentToken = this.lexer.nextToken();
16
19
  }
17
20
 
21
+ /**
22
+ * Create a detailed error message with context about where parsing failed
23
+ */
24
+ private createParserError(message: string): ExpressionEvaluationException {
25
+ const position = this.lexer.getPosition();
26
+ const contextStart = Math.max(0, position - 20);
27
+ const contextEnd = Math.min(this.originalExpression.length, position + 20);
28
+ const context = this.originalExpression.substring(contextStart, contextEnd);
29
+ const pointer = ' '.repeat(position - contextStart) + '^';
30
+
31
+ const errorDetails = [
32
+ `Parser Error: ${message}`,
33
+ `Expression: ${this.originalExpression}`,
34
+ `Position: ${position}`,
35
+ `Context: ...${context}...`,
36
+ ` ${pointer}`,
37
+ `Current token: ${this.currentToken ? `${this.currentToken.type}("${this.currentToken.value}")` : 'null'}`,
38
+ `Previous token: ${this.previousTokenValue ? `${this.previousTokenValue.type}("${this.previousTokenValue.value}")` : 'null'}`,
39
+ `Parse depth: ${this.parseDepth}`,
40
+ ].join('\n');
41
+
42
+ console.error('\n' + errorDetails + '\n');
43
+
44
+ return new ExpressionEvaluationException(
45
+ this.originalExpression,
46
+ `${message} at position ${position}`
47
+ );
48
+ }
49
+
18
50
  public parse(): Expression {
19
51
  if (!this.currentToken) {
20
- throw new ExpressionEvaluationException('', 'Empty expression');
52
+ throw this.createParserError('Empty expression');
21
53
  }
22
54
 
23
- const expr = this.parseExpression();
24
-
25
- // Ensure we consumed all tokens
26
- if (this.currentToken && this.currentToken.type !== TokenType.EOF) {
27
- throw new ExpressionEvaluationException(
28
- this.lexer.getPosition().toString(),
29
- `Unexpected token: ${this.currentToken.value} at position ${this.currentToken.startPos}`,
30
- );
31
- }
55
+ try {
56
+ const expr = this.parseExpression();
32
57
 
33
- return expr;
58
+ // Ensure we consumed all tokens
59
+ if (this.currentToken && this.currentToken.type !== TokenType.EOF) {
60
+ throw this.createParserError(
61
+ `Unexpected token "${this.currentToken.value}" - expected end of expression`
62
+ );
63
+ }
64
+
65
+ return expr;
66
+ } catch (error) {
67
+ // Re-throw with additional context if it's not already a detailed parser error
68
+ if (error instanceof ExpressionEvaluationException && !error.message.includes('Parser Error:')) {
69
+ throw this.createParserError(error.message);
70
+ }
71
+ throw error;
72
+ }
34
73
  }
35
74
 
36
75
  private parseExpression(): Expression {
@@ -304,18 +343,34 @@ export class ExpressionParser {
304
343
  // - "Context.a.b.c" -> Context . (a . (b . c))
305
344
  // - "Context.obj[\"key\"].value" -> Context . (obj["key"] . value)
306
345
  private parsePostfixRightSide(): Expression {
307
- // Expect an identifier (which may include STATIC bracket notation like obj["key"] or a[9])
308
- if (!this.currentToken || this.currentToken.type !== TokenType.IDENTIFIER) {
309
- throw new ExpressionEvaluationException(
310
- this.lexer.getPosition().toString(),
311
- 'Expected identifier after dot',
346
+ this.parseDepth++;
347
+
348
+ // Expect an identifier or number (for numeric property names like .123 or ObjectIds like .507f1f77bcf86cd799439011)
349
+ // IMPORTANT: This allows property paths like Page.kycs.123.individual to work
350
+ if (!this.currentToken ||
351
+ (this.currentToken.type !== TokenType.IDENTIFIER && this.currentToken.type !== TokenType.NUMBER)) {
352
+ this.parseDepth--;
353
+ throw this.createParserError(
354
+ `Expected identifier or number after dot, but found ${this.currentToken?.type || 'EOF'}`
312
355
  );
313
356
  }
314
357
 
315
358
  // The identifier token value might contain static bracket notation like "obj[\"key\"]" or "a[9]"
316
359
  // Or it might be a plain identifier if bracket content is dynamic
317
- const identifierValue = this.currentToken.value;
360
+ // For NUMBER tokens, we treat them as property names (e.g., .123 in Page.kycs.123.individual)
361
+ let identifierValue = this.currentToken.value;
318
362
  this.advance();
363
+
364
+ // IMPORTANT: Handle ObjectId-like values (e.g., 507f1f77bcf86cd799439011) and numeric IDs with underscores (e.g., 123_abc)
365
+ // The lexer splits these into NUMBER (507) + IDENTIFIER (f1f77bcf86cd799439011) or NUMBER (123) + IDENTIFIER (_abc)
366
+ // So if we just consumed a NUMBER token and the next token is an IDENTIFIER (without a DOT/operator between them),
367
+ // concatenate them to form the complete property name
368
+ if (this.currentToken &&
369
+ this.currentToken.type === TokenType.IDENTIFIER &&
370
+ /^[a-zA-Z_]/.test(this.currentToken.value)) { // Next token starts with a letter or underscore (not a dot or operator)
371
+ identifierValue += this.currentToken.value;
372
+ this.advance();
373
+ }
319
374
 
320
375
  // Check if the identifier contains static bracket notation
321
376
  const bracketIndex = identifierValue.indexOf('[');
@@ -344,7 +399,8 @@ export class ExpressionParser {
344
399
  const right = this.parsePostfixRightSide(); // Recursive call
345
400
  expr = new Expression('', expr, right, Operation.OBJECT_OPERATOR);
346
401
  }
347
-
402
+
403
+ this.parseDepth--;
348
404
  return expr;
349
405
  }
350
406
 
@@ -434,9 +490,8 @@ export class ExpressionParser {
434
490
  // - "Context.a[Page.id]" (dynamic) -> IDENTIFIER "Context.a" + LEFT_BRACKET + expression
435
491
  private parseIdentifierPath(): Expression {
436
492
  if (!this.currentToken || this.currentToken.type !== TokenType.IDENTIFIER) {
437
- throw new ExpressionEvaluationException(
438
- this.lexer.getPosition().toString(),
439
- 'Expected identifier',
493
+ throw this.createParserError(
494
+ `Expected identifier but found ${this.currentToken?.type || 'EOF'}`
440
495
  );
441
496
  }
442
497
 
@@ -496,9 +551,8 @@ export class ExpressionParser {
496
551
  return expr;
497
552
  }
498
553
 
499
- throw new ExpressionEvaluationException(
500
- this.lexer.getPosition().toString(),
501
- `Unexpected token: ${this.currentToken?.value || 'EOF'} at position ${this.currentToken?.startPos || this.lexer.getPosition()}`,
554
+ throw this.createParserError(
555
+ `Unexpected token in expression - expected number, string, identifier, or '(' but found ${this.currentToken?.type || 'EOF'}`
502
556
  );
503
557
  }
504
558
 
@@ -525,9 +579,8 @@ export class ExpressionParser {
525
579
 
526
580
  private expectToken(type: TokenType): Token {
527
581
  if (!this.currentToken || this.currentToken.type !== type) {
528
- throw new ExpressionEvaluationException(
529
- this.lexer.getPosition().toString(),
530
- `Expected ${type}, got ${this.currentToken?.type || 'EOF'}`,
582
+ throw this.createParserError(
583
+ `Expected ${type}, but found ${this.currentToken?.type || 'EOF'}${this.currentToken ? ` ("${this.currentToken.value}")` : ''}`
531
584
  );
532
585
  }
533
586
  const token = this.currentToken;
@@ -1,5 +1,7 @@
1
1
  export function duplicate(obj: any): any {
2
2
  if (!obj) return obj;
3
- if (typeof globalThis.structuredClone === 'function') return globalThis.structuredClone(obj);
3
+ if (typeof (globalThis as any).structuredClone === 'function') {
4
+ return (globalThis as any).structuredClone(obj);
5
+ }
4
6
  return JSON.parse(JSON.stringify(obj));
5
7
  }
package/src/index.ts CHANGED
@@ -80,3 +80,4 @@ export * from './engine/exception/KIRuntimeException';
80
80
  export * from './engine/runtime/expression/operators/unary';
81
81
  export * from './engine/runtime/expression/operators/binary';
82
82
  export * from './engine/runtime/expression/operators/ternary';
83
+ export * from './engine/dsl';