@rsconcept/domain 3.0.2 → 3.1.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.
Files changed (73) hide show
  1. package/dist/{analyzer-CzFaWR7w.d.ts → analyzer-B35QsI3L.d.ts} +2 -2
  2. package/dist/{ast-annotations-x4PJ_Lhc.d.ts → ast-annotations-DclU1Ser.d.ts} +2 -2
  3. package/dist/{calculator-uPaTV3hc.d.ts → calculator-B-1p712X.d.ts} +2 -2
  4. package/dist/{diagnostics-DKyMf6q-.d.ts → diagnostics-D4_B7rt2.d.ts} +3 -3
  5. package/dist/{diagnostics-D1Cp21nP.js → diagnostics-D_4TpAH4.js} +4 -5
  6. package/dist/{diagnostics-D1Cp21nP.js.map → diagnostics-D_4TpAH4.js.map} +1 -1
  7. package/dist/{error-B0EkIx4K.d.ts → error-S1JghCNN.d.ts} +30 -2
  8. package/dist/{index-CnuO4w8e.d.ts → index-D-w-bgei.d.ts} +4 -4
  9. package/dist/{index-DCO4mxiO.d.ts → index-DzvllU3P.d.ts} +4 -4
  10. package/dist/index.d.ts +6 -6
  11. package/dist/index.js +4 -4
  12. package/dist/library/diagnostics.d.ts +1 -1
  13. package/dist/library/diagnostics.js +1 -1
  14. package/dist/library/index.d.ts +2 -2
  15. package/dist/library/index.js +1 -1
  16. package/dist/library/oss-api.d.ts +1 -1
  17. package/dist/library/oss-api.js +2 -4
  18. package/dist/library/oss-api.js.map +1 -1
  19. package/dist/library/oss.js +1 -1
  20. package/dist/library/rsengine.d.ts +3 -3
  21. package/dist/library/rsengine.js +4 -3
  22. package/dist/library/rsengine.js.map +1 -1
  23. package/dist/library/rsform-api/index.d.ts +1 -1
  24. package/dist/library/rsform-api/index.js +1 -1
  25. package/dist/library/rsform.d.ts +1 -1
  26. package/dist/library/rsmodel-api.d.ts +3 -3
  27. package/dist/library/rsmodel-api.js +20 -4
  28. package/dist/library/rsmodel-api.js.map +1 -1
  29. package/dist/library/rsmodel.d.ts +1 -1
  30. package/dist/library/structure-planner.d.ts +2 -2
  31. package/dist/library/structure-planner.js +1 -1
  32. package/dist/parsing/lezer-tree.js.map +1 -1
  33. package/dist/rslang/api.js +9 -6
  34. package/dist/rslang/api.js.map +1 -1
  35. package/dist/rslang/ast-annotations.d.ts +1 -1
  36. package/dist/rslang/error.d.ts +2 -2
  37. package/dist/rslang/error.js +16 -1
  38. package/dist/rslang/error.js.map +1 -1
  39. package/dist/rslang/eval/calculator.d.ts +1 -1
  40. package/dist/rslang/eval/calculator.js +1 -1
  41. package/dist/rslang/eval/evaluation-cache.js +1 -3
  42. package/dist/rslang/eval/evaluation-cache.js.map +1 -1
  43. package/dist/rslang/eval/evaluator.d.ts +12 -2
  44. package/dist/rslang/eval/evaluator.js +42 -8
  45. package/dist/rslang/eval/evaluator.js.map +1 -1
  46. package/dist/rslang/eval/value-api.js +1 -1
  47. package/dist/rslang/eval/value.js +1 -1
  48. package/dist/rslang/index.d.ts +6 -6
  49. package/dist/rslang/index.js +3 -3
  50. package/dist/rslang/parser/normalize.js.map +1 -1
  51. package/dist/rslang/parser/parser.terms.js.map +1 -1
  52. package/dist/rslang/parser/syntax-errors/index.d.ts +1 -1
  53. package/dist/rslang/semantic/analyzer.d.ts +1 -1
  54. package/dist/rslang/semantic/arguments-extractor.js +6 -4
  55. package/dist/rslang/semantic/arguments-extractor.js.map +1 -1
  56. package/dist/rslang/semantic/type-auditor.d.ts +1 -1
  57. package/dist/rslang/semantic/type-auditor.js +20 -16
  58. package/dist/rslang/semantic/type-auditor.js.map +1 -1
  59. package/dist/rslang/semantic/typification-api.js +10 -9
  60. package/dist/rslang/semantic/typification-api.js.map +1 -1
  61. package/dist/rslang/semantic/typification-parser.js +4 -1
  62. package/dist/rslang/semantic/typification-parser.js.map +1 -1
  63. package/dist/rslang/semantic/value-auditor.d.ts +1 -1
  64. package/dist/{value-COprb2Je.js → value-BDISzQqI.js} +3 -2
  65. package/dist/{value-COprb2Je.js.map → value-BDISzQqI.js.map} +1 -1
  66. package/package.json +23 -22
  67. package/src/index.ts +1 -1
  68. package/src/library/rsmodel-api.ts +27 -0
  69. package/src/rslang/error.test.ts +23 -0
  70. package/src/rslang/error.ts +42 -0
  71. package/src/rslang/eval/evaluator.test.ts +56 -1
  72. package/src/rslang/eval/evaluator.ts +50 -7
  73. package/src/rslang/index.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rsconcept/domain",
3
- "version": "3.0.2",
3
+ "version": "3.1.1",
4
4
  "description": "Shared TypeScript domain for RS language, RSForm, RSModel, RSEngine, OSS, and concept-text helpers.",
5
5
  "license": "MIT",
6
6
  "author": "IRBorisov",
@@ -154,36 +154,37 @@
154
154
  "README.md",
155
155
  "LICENSE"
156
156
  ],
157
+ "scripts": {
158
+ "generate": "lezer-generator src/rslang/parser/rslang.grammar -o src/rslang/parser/parser.ts",
159
+ "build": "tsdown",
160
+ "dev": "tsdown --watch",
161
+ "typecheck": "tsc -p tsconfig.json --noEmit",
162
+ "test": "vitest run",
163
+ "lint": "eslint . --report-unused-disable-directives --max-warnings 0",
164
+ "lintFix": "eslint . --report-unused-disable-directives --max-warnings 0 --fix",
165
+ "format": "prettier --write \"src/**/*.ts\" \"*.{ts,js}\"",
166
+ "format:check": "prettier --check \"src/**/*.ts\" \"*.{ts,js}\"",
167
+ "prepublishOnly": "pnpm run build"
168
+ },
157
169
  "dependencies": {
158
170
  "@lezer/common": "^1.5.2",
159
171
  "@lezer/lr": "^1.4.10"
160
172
  },
161
173
  "devDependencies": {
162
174
  "@lezer/generator": "^1.8.0",
163
- "@types/node": "^26.1.1",
164
- "@typescript-eslint/parser": "^8.63.0",
165
- "eslint": "^9.39.5",
166
- "eslint-plugin-simple-import-sort": "^13.0.0",
167
- "globals": "^17.7.0",
168
- "prettier": "^3.9.5",
169
- "tsdown": "^0.22.4",
175
+ "@types/node": "^26.4.0",
176
+ "@typescript-eslint/parser": "^8.69.0",
177
+ "eslint": "^10.9.1",
178
+ "eslint-plugin-simple-import-sort": "^14.0.0",
179
+ "globals": "^17.11.0",
180
+ "prettier": "^3.9.6",
181
+ "tsdown": "^0.22.14",
170
182
  "typescript": "^6.0.3",
171
- "typescript-eslint": "^8.63.0",
183
+ "typescript-eslint": "^8.69.0",
172
184
  "unrun": "^0.3.1",
173
- "vitest": "^4.1.10"
185
+ "vitest": "^4.1.11"
174
186
  },
175
187
  "publishConfig": {
176
188
  "access": "public"
177
- },
178
- "scripts": {
179
- "generate": "lezer-generator src/rslang/parser/rslang.grammar -o src/rslang/parser/parser.ts",
180
- "build": "tsdown",
181
- "dev": "tsdown --watch",
182
- "typecheck": "tsc -p tsconfig.json --noEmit",
183
- "test": "vitest run",
184
- "lint": "eslint . --report-unused-disable-directives --max-warnings 0",
185
- "lintFix": "eslint . --report-unused-disable-directives --max-warnings 0 --fix",
186
- "format": "prettier --write \"src/**/*.ts\" \"*.{ts,js}\"",
187
- "format:check": "prettier --check \"src/**/*.ts\" \"*.{ts,js}\""
188
189
  }
189
- }
190
+ }
package/src/index.ts CHANGED
@@ -81,7 +81,7 @@ export {
81
81
  } from './parsing/ast';
82
82
  export { type CMSyntaxNode, printTree } from './parsing/lezer-tree';
83
83
  export { readErrorAnnotation, readTypeAnnotation } from './rslang/ast-annotations';
84
- export { RSErrorCode, type RSErrorDescription } from './rslang/error';
84
+ export { type EvalStackFrame, formatEvalCallStack,RSErrorCode, type RSErrorDescription } from './rslang/error';
85
85
  export { type CalculatorEvaluateOptions, type CalculatorResult, RSCalculator } from './rslang/eval/calculator';
86
86
  export { makeValuePath, TUPLE_ID, type Value, type ValuePath } from './rslang/eval/value';
87
87
  export { printValue } from './rslang/eval/value-api';
@@ -71,6 +71,11 @@ export function generateRandomValue(
71
71
  return tuple(factors);
72
72
  }
73
73
  case TypeID.collection: {
74
+ // Empty set is a valid sample only when the element domain itself is generatable.
75
+ // When the count happens to be 0 we would otherwise skip sampling and miss empty basics.
76
+ if (!canGenerateRandomValue(type.base, basics, cstByAlias)) {
77
+ return null;
78
+ }
74
79
  const result: Value[] = [];
75
80
  const randomElementsCount = Math.floor(Math.random() * (setElementsCount + 1));
76
81
  for (let i = 0; i < randomElementsCount; i++) {
@@ -331,6 +336,28 @@ export function addValueElement(
331
336
 
332
337
  // ========= Internal functions ==========
333
338
 
339
+ function canGenerateRandomValue(
340
+ type: Typification,
341
+ basics: Map<number, BasicBinding>,
342
+ cstByAlias: Map<string, { id: number }>
343
+ ): boolean {
344
+ switch (type.typeID) {
345
+ case TypeID.integer:
346
+ return true;
347
+ case TypeID.basic: {
348
+ const cst = cstByAlias.get(type.baseID);
349
+ const binding = cst ? basics.get(cst.id) : undefined;
350
+ return Object.keys(binding ?? {}).length > 0;
351
+ }
352
+ case TypeID.tuple:
353
+ return type.factors.every(factor => canGenerateRandomValue(factor, basics, cstByAlias));
354
+ case TypeID.collection:
355
+ return canGenerateRandomValue(type.base, basics, cstByAlias);
356
+ case TypeID.anyTypification:
357
+ return false;
358
+ }
359
+ }
360
+
334
361
  function countBaseElements(cst: ModelEvalFields, engine: RSEngine): number {
335
362
  if (!isBasicConcept(cst.cst_type)) {
336
363
  return 0;
@@ -0,0 +1,23 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { type EvalStackFrame,formatEvalCallStack } from './error';
4
+
5
+ describe('formatEvalCallStack', () => {
6
+ it('returns empty string for missing or empty stack', () => {
7
+ expect(formatEvalCallStack(undefined)).toBe('');
8
+ expect(formatEvalCallStack([])).toBe('');
9
+ });
10
+
11
+ it('formats callee aliases from outermost call to innermost fault', () => {
12
+ const stack: EvalStackFrame[] = [
13
+ { alias: 'F12', from: 10, to: 12 },
14
+ { alias: 'F5', from: 8, to: 14 }
15
+ ];
16
+ expect(formatEvalCallStack(stack)).toBe('F5 → F12');
17
+ });
18
+
19
+ it('formats a single nested call', () => {
20
+ const stack: EvalStackFrame[] = [{ alias: 'F9', from: 7, to: 8 }];
21
+ expect(formatEvalCallStack(stack)).toBe('F9');
22
+ });
23
+ });
@@ -16,15 +16,57 @@ export interface RSErrorInfo {
16
16
  params?: readonly string[];
17
17
  }
18
18
 
19
+ /**
20
+ * One frame of an evaluation call chain.
21
+ *
22
+ * Spans are relative to the formal definition of {@link alias}.
23
+ * Frames are ordered innermost-first: fault locus, then each caller outward.
24
+ * The root expression under evaluation is not a frame — its highlight is
25
+ * {@link RSErrorDescription.from}/{@link RSErrorDescription.to}; {@link RSErrorDescription.stack}
26
+ * is absent when the fault is in that root expression.
27
+ */
28
+ export interface EvalStackFrame {
29
+ /** Constituent whose formal definition contains this span. */
30
+ alias: string;
31
+ /** Half-open span `[from, to)` in that definition. */
32
+ from: number;
33
+ to: number;
34
+ }
35
+
19
36
  /** Represents RSLang expression error description. */
20
37
  export interface RSErrorDescription extends RSErrorInfo {
21
38
  from: number;
22
39
  to: number;
40
+ /**
41
+ * Evaluation call chain when the fault is inside a called `F#`/`P#` body.
42
+ * Absent for parse/semantic errors and for evaluation errors in the root expression.
43
+ */
44
+ stack?: readonly EvalStackFrame[];
23
45
  }
24
46
 
25
47
  /** Error reporter function type. */
26
48
  export type ErrorReporter = (error: RSErrorDescription) => void;
27
49
 
50
+ /**
51
+ * Formats an evaluation call chain for display (`F5 → F12`).
52
+ *
53
+ * Uses callee aliases from outermost call to innermost fault container.
54
+ * Returns an empty string when there is no nested call to show.
55
+ */
56
+ export function formatEvalCallStack(stack: readonly EvalStackFrame[] | undefined): string {
57
+ if (!stack || stack.length === 0) {
58
+ return '';
59
+ }
60
+ const aliases: string[] = [];
61
+ for (let index = stack.length - 1; index >= 0; index--) {
62
+ const frame = stack[index];
63
+ if (frame) {
64
+ aliases.push(frame.alias);
65
+ }
66
+ }
67
+ return aliases.join(' → ');
68
+ }
69
+
28
70
  /** Represents RSLang expression error types. */
29
71
  export const RSErrorCode = {
30
72
  unknownSyntax: 0x8400, // 33792
@@ -308,11 +308,66 @@ describe('Calculator', () => {
308
308
  expect(errors[0]).toMatchObject({
309
309
  code: RSErrorCode.iterateInfinity,
310
310
  from: callSite!.from,
311
- to: callSite!.to
311
+ to: callSite!.to,
312
+ stack: [{ alias: 'F9', from: zOffset, to: zOffset + 1 }]
312
313
  });
314
+ expect(errors[0]?.stack).toHaveLength(1);
313
315
  expect(readErrorAnnotation(callSite!)).toMatchObject({ code: RSErrorCode.iterateInfinity });
314
316
  expect(readErrorAnnotation(innerErrorNode!)).toBeNull();
315
317
  });
318
+
319
+ it('keeps nested call surface span in the root expression and records the full stack', () => {
320
+ const innerExpr = '[a∈X1] debool(∅)';
321
+ const midExpr = '[a∈X1] F8[a]';
322
+ treeContext.set('F8', buildAST(innerExpr));
323
+ treeContext.set('F7', buildAST(midExpr));
324
+ const mainAst = buildAST('F7[X1]');
325
+ const outerCall = findFirstNode(mainAst, TokenID.NT_FUNC_CALL);
326
+ expect(outerCall).not.toBeNull();
327
+
328
+ const midAst = treeContext.get('F7')!;
329
+ const midCall = findFirstNode(midAst, TokenID.NT_FUNC_CALL);
330
+ expect(midCall).not.toBeNull();
331
+
332
+ const deboolArgFrom = innerExpr.indexOf('∅');
333
+ calculator.run(mainAst, error => errors.push(error), true);
334
+
335
+ expect(errors).toHaveLength(1);
336
+ expect(errors[0]).toMatchObject({
337
+ code: RSErrorCode.calcInvalidDebool,
338
+ from: outerCall!.from,
339
+ to: outerCall!.to,
340
+ stack: [
341
+ { alias: 'F8', from: deboolArgFrom, to: deboolArgFrom + 1 },
342
+ { alias: 'F7', from: midCall!.from, to: midCall!.to }
343
+ ]
344
+ });
345
+ expect(errors[0]?.stack).toHaveLength(2);
346
+ expect(readErrorAnnotation(outerCall!)).toMatchObject({ code: RSErrorCode.calcInvalidDebool });
347
+ });
348
+
349
+ it('distinguishes which call site failed when the same function is used twice', () => {
350
+ treeContext.set('F8', buildAST('[a∈ℬ(X1)] debool(a)'));
351
+ const mainAst = buildAST('F8[{1}] ∪ F8[X1]');
352
+ const callSites: AstNode[] = [];
353
+ visitAstDFS(mainAst, node => {
354
+ if (node.typeID === TokenID.NT_FUNC_CALL) {
355
+ callSites.push(node);
356
+ }
357
+ });
358
+ expect(callSites).toHaveLength(2);
359
+
360
+ calculator.run(mainAst, error => errors.push(error));
361
+
362
+ expect(errors).toHaveLength(1);
363
+ expect(errors[0]).toMatchObject({
364
+ code: RSErrorCode.calcInvalidDebool,
365
+ from: callSites[1].from,
366
+ to: callSites[1].to,
367
+ stack: [{ alias: 'F8' }]
368
+ });
369
+ expect(errors[0]?.stack).toHaveLength(1);
370
+ });
316
371
  });
317
372
 
318
373
  describe('evaluation cache', () => {
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { type AstNode, getNodeIndices, getNodeText } from '../../parsing';
6
6
  import { annotateError } from '../ast-annotations';
7
- import { type ErrorReporter, RSErrorCode } from '../error';
7
+ import { type ErrorReporter, type EvalStackFrame, RSErrorCode } from '../error';
8
8
  import { labelRSLangNode } from '../labels';
9
9
  import { TokenID } from '../parser/token';
10
10
 
@@ -95,16 +95,59 @@ export class Evaluator {
95
95
  this.iterationCounter = 0;
96
96
  }
97
97
 
98
- private errorNode(node: AstNode): AstNode {
99
- if (this.callSiteStack.length > 0) {
100
- return this.callSiteStack[this.callSiteStack.length - 1];
98
+ /**
99
+ * Surface highlight target in the root expression under evaluation.
100
+ * Nested call sites live in callee ASTs — only the outermost call is in the root.
101
+ */
102
+ private surfaceErrorNode(fault: AstNode): AstNode {
103
+ return this.callSiteStack[0] ?? fault;
104
+ }
105
+
106
+ /**
107
+ * Builds innermost-first frames for called `F#`/`P#` bodies only.
108
+ * The root expression is represented by the surface `from`/`to`, not a stack frame.
109
+ * Returns `undefined` when the fault is in the root expression (no function call).
110
+ */
111
+ private buildEvalStack(fault: AstNode): readonly EvalStackFrame[] | undefined {
112
+ if (this.callSiteStack.length === 0) {
113
+ return undefined;
114
+ }
115
+ const frames: EvalStackFrame[] = [];
116
+ const innermostCall = this.callSiteStack[this.callSiteStack.length - 1];
117
+ if (!innermostCall) {
118
+ return undefined;
119
+ }
120
+ frames.push({
121
+ alias: getNodeText(innermostCall.children[0]),
122
+ from: fault.from,
123
+ to: fault.to
124
+ });
125
+ // Call sites at index ≥ 1 live in a callee body; index 0 is the root call site.
126
+ for (let index = this.callSiteStack.length - 1; index >= 1; index--) {
127
+ const callSite = this.callSiteStack[index];
128
+ const outerCall = this.callSiteStack[index - 1];
129
+ if (!callSite || !outerCall) {
130
+ continue;
131
+ }
132
+ frames.push({
133
+ alias: getNodeText(outerCall.children[0]),
134
+ from: callSite.from,
135
+ to: callSite.to
136
+ });
101
137
  }
102
- return node;
138
+ return frames;
103
139
  }
104
140
 
105
141
  private onError(code: RSErrorCode, node: AstNode, params?: string[]): null {
106
- const target = this.errorNode(node);
107
- this.reporter?.({ code, from: target.from, to: target.to, params });
142
+ const target = this.surfaceErrorNode(node);
143
+ const stack = this.buildEvalStack(node);
144
+ this.reporter?.({
145
+ code,
146
+ from: target.from,
147
+ to: target.to,
148
+ ...(params !== undefined ? { params } : {}),
149
+ ...(stack !== undefined ? { stack } : {})
150
+ });
108
151
  if (this.annotateErrors) {
109
152
  annotateError(target, code, params);
110
153
  }
@@ -1,5 +1,5 @@
1
1
  export { readErrorAnnotation, readTypeAnnotation } from './ast-annotations';
2
- export { RSErrorCode, type RSErrorDescription } from './error';
2
+ export { type EvalStackFrame, formatEvalCallStack,RSErrorCode, type RSErrorDescription } from './error';
3
3
  export { type CalculatorEvaluateOptions, type CalculatorResult, RSCalculator } from './eval/calculator';
4
4
  export { makeValuePath, type Value, type ValuePath } from './eval/value';
5
5
  export { printValue } from './eval/value-api';