@rsconcept/domain 3.0.0 → 3.0.2

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 (85) hide show
  1. package/dist/{analyzer-fNhkwO-6.d.ts → analyzer-CzFaWR7w.d.ts} +4 -4
  2. package/dist/{api-Bf0f_uiI.d.ts → api-DgecYHjR.d.ts} +5 -3
  3. package/dist/{arguments-extractor-ngatANBI.d.ts → arguments-extractor-CXWRT5Ou.d.ts} +4 -4
  4. package/dist/{ast-C8sIpKdL.d.ts → ast-D-D6Rq3T.d.ts} +2 -3
  5. package/dist/{ast-annotations-CiIx8qBM.d.ts → ast-annotations-x4PJ_Lhc.d.ts} +4 -4
  6. package/dist/{calculator-DE89LtIM.d.ts → calculator-uPaTV3hc.d.ts} +5 -5
  7. package/dist/cctext/language-api.d.ts +0 -1
  8. package/dist/{diagnostics-ndGYbvgh.js → diagnostics-D1Cp21nP.js} +16 -3
  9. package/dist/{diagnostics-ndGYbvgh.js.map → diagnostics-D1Cp21nP.js.map} +1 -1
  10. package/dist/{diagnostics-DsEhmFh0.d.ts → diagnostics-DKyMf6q-.d.ts} +20 -6
  11. package/dist/{expression-generator-CoucLIkE.d.ts → expression-generator-CADzBw-C.d.ts} +3 -2
  12. package/dist/graph/index.d.ts +1 -0
  13. package/dist/index-CnuO4w8e.d.ts +13 -0
  14. package/dist/index-D5RahF1K.d.ts +2 -0
  15. package/dist/{index-DpiTybDB.d.ts → index-DCO4mxiO.d.ts} +9 -7
  16. package/dist/index-DntDUXW1.d.ts +1 -0
  17. package/dist/index-x0_Y5uPV.d.ts +2 -0
  18. package/dist/index.d.ts +13 -13
  19. package/dist/index.js +1 -1
  20. package/dist/{lezer-tree-iS7LpLBJ.d.ts → lezer-tree-BQZqBT5A.d.ts} +1 -2
  21. package/dist/library/diagnostics.d.ts +1 -1
  22. package/dist/library/diagnostics.js +1 -1
  23. package/dist/library/index.d.ts +2 -3
  24. package/dist/library/index.js +1 -1
  25. package/dist/library/oss-api.d.ts +11 -3
  26. package/dist/library/oss-api.js +45 -4
  27. package/dist/library/oss-api.js.map +1 -1
  28. package/dist/library/oss-layout-api.d.ts +0 -1
  29. package/dist/library/oss.d.ts +21 -16
  30. package/dist/library/oss.js +19 -13
  31. package/dist/library/oss.js.map +1 -1
  32. package/dist/library/rsengine.d.ts +6 -5
  33. package/dist/library/rsengine.js +1 -1
  34. package/dist/library/rsform-api/index.d.ts +1 -1
  35. package/dist/library/rsform-api/index.js +1 -1
  36. package/dist/library/rsform.d.ts +1 -1
  37. package/dist/library/rsmodel-api.d.ts +5 -5
  38. package/dist/library/rsmodel-api.js +3 -3
  39. package/dist/library/rsmodel-api.js.map +1 -1
  40. package/dist/library/rsmodel.d.ts +1 -1
  41. package/dist/library/structure-planner.d.ts +3 -3
  42. package/dist/library/structure-planner.js +1 -1
  43. package/dist/{parser-Bwd8LxJ1.d.ts → parser-DkejtiOR.d.ts} +1 -2
  44. package/dist/parsing/ast.d.ts +1 -1
  45. package/dist/parsing/index.d.ts +3 -2
  46. package/dist/parsing/lezer-tree.d.ts +1 -1
  47. package/dist/rslang/api.d.ts +2 -2
  48. package/dist/rslang/ast-annotations.d.ts +1 -1
  49. package/dist/rslang/eval/calculator.d.ts +1 -1
  50. package/dist/rslang/eval/evaluation-cache.d.ts +3 -3
  51. package/dist/rslang/eval/evaluator.d.ts +3 -3
  52. package/dist/rslang/eval/value-api.d.ts +1 -1
  53. package/dist/rslang/eval/value.d.ts +1 -1
  54. package/dist/rslang/index.d.ts +11 -10
  55. package/dist/rslang/labels.d.ts +3 -3
  56. package/dist/rslang/parser/expression-generator.d.ts +1 -1
  57. package/dist/rslang/parser/normalize.d.ts +2 -1
  58. package/dist/rslang/parser/parser.d.ts +1 -1
  59. package/dist/rslang/parser/parser.terms.d.ts +1 -40
  60. package/dist/rslang/parser/syntax-errors/index.d.ts +3 -3
  61. package/dist/rslang/semantic/analyzer.d.ts +1 -1
  62. package/dist/rslang/semantic/arguments-extractor.d.ts +1 -1
  63. package/dist/rslang/semantic/type-auditor.d.ts +3 -3
  64. package/dist/rslang/semantic/typification-api.d.ts +1 -2
  65. package/dist/rslang/semantic/typification-parser.d.ts +1 -1
  66. package/dist/rslang/semantic/typification.d.ts +1 -1
  67. package/dist/rslang/semantic/value-auditor.d.ts +2 -2
  68. package/dist/rslang/typification-graph.d.ts +1 -1
  69. package/dist/shared/index.d.ts +1 -0
  70. package/dist/{typification-graph-BY_oGT82.d.ts → typification-graph-CSxDt18X.d.ts} +2 -3
  71. package/dist/{typification-9lNu9R4k.d.ts → typification-hWUoWCba.d.ts} +2 -1
  72. package/dist/{typification-parser-Dytsv9CI.d.ts → typification-parser-DFxj4JiQ.d.ts} +2 -3
  73. package/dist/{value-BduA_ZGp.d.ts → value-XpJOVzMP.d.ts} +2 -1
  74. package/dist/{value-api-CXLl_zg0.d.ts → value-api-Dw8UkwCD.d.ts} +3 -4
  75. package/package.json +20 -21
  76. package/src/library/diagnostics.ts +16 -2
  77. package/src/library/oss-api.test.ts +100 -5
  78. package/src/library/oss-api.ts +71 -5
  79. package/src/library/oss.ts +22 -16
  80. package/src/library/rsmodel-api.test.ts +79 -0
  81. package/src/library/rsmodel-api.ts +3 -2
  82. package/dist/index-BKZ67WMa.d.ts +0 -1
  83. package/dist/index-BVVgDSdq.d.ts +0 -1
  84. package/dist/index-DmtQKWjk.d.ts +0 -1
  85. package/dist/index-_6s0AX1B.d.ts +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rsconcept/domain",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
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,37 +154,36 @@
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
- },
169
157
  "dependencies": {
170
158
  "@lezer/common": "^1.5.2",
171
159
  "@lezer/lr": "^1.4.10"
172
160
  },
173
161
  "devDependencies": {
174
162
  "@lezer/generator": "^1.8.0",
175
- "@types/node": "^26.1.0",
176
- "@typescript-eslint/parser": "^8.0.1",
177
- "eslint": "^9.39.4",
163
+ "@types/node": "^26.1.1",
164
+ "@typescript-eslint/parser": "^8.63.0",
165
+ "eslint": "^9.39.5",
178
166
  "eslint-plugin-simple-import-sort": "^13.0.0",
179
167
  "globals": "^17.7.0",
180
- "prettier": "^3.9.4",
181
- "tsdown": "^0.22.3",
168
+ "prettier": "^3.9.5",
169
+ "tsdown": "^0.22.4",
182
170
  "typescript": "^6.0.3",
183
- "typescript-eslint": "^8.62.1",
171
+ "typescript-eslint": "^8.63.0",
184
172
  "unrun": "^0.3.1",
185
- "vitest": "^4.1.9"
173
+ "vitest": "^4.1.10"
186
174
  },
187
175
  "publishConfig": {
188
176
  "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}\""
189
188
  }
190
- }
189
+ }
@@ -19,7 +19,7 @@ export const DiagnosticKind = {
19
19
  } as const;
20
20
  export type DiagnosticKind = (typeof DiagnosticKind)[keyof typeof DiagnosticKind];
21
21
 
22
- /** Schema and model diagnostic codes (same numeric format as {@link RSErrorCode}). */
22
+ /** Schema, model, and substitution-table diagnostic codes (same numeric format as {@link RSErrorCode}). */
23
23
  export const RSDiagnosticCode = {
24
24
  schemaHomonym: 0x8901,
25
25
  schemaFormalDuplicate: 0x8902,
@@ -31,7 +31,21 @@ export const RSDiagnosticCode = {
31
31
  modelEmpty: 0x8910,
32
32
  modelAxiomFalse: 0x8911,
33
33
  modelInvalidData: 0x8912,
34
- modelEvalFail: 0x8913
34
+ modelEvalFail: 0x8913,
35
+
36
+ substitutionInvalidIDs: 0x8920,
37
+ substitutionIncorrectCst: 0x8921,
38
+ substitutionInvalidClasses: 0x8922,
39
+ substitutionInvalidBasic: 0x8923,
40
+ substitutionInvalidConstant: 0x8924,
41
+ substitutionTypificationCycle: 0x8925,
42
+ substitutionBaseNotSet: 0x8926,
43
+ substitutionUnequalTypification: 0x8927,
44
+ /** Non-blocking warning: formal definitions differ after mapping. */
45
+ substitutionUnequalExpressions: 0x2928,
46
+ substitutionUnequalArgsCount: 0x8929,
47
+ substitutionUnequalArgs: 0x892a,
48
+ substitutionInvalidNominal: 0x892b
35
49
  } as const;
36
50
  export type RSDiagnosticCode = (typeof RSDiagnosticCode)[keyof typeof RSDiagnosticCode];
37
51
 
@@ -2,14 +2,16 @@ import { describe, expect, it } from 'vitest';
2
2
 
3
3
  import { Graph } from '../graph';
4
4
 
5
+ import { SubstitutionErrorType } from './oss';
5
6
  import { SubstitutionValidator } from './oss-api';
6
7
  import { type Constituenta, CstClass, CstType, type RSForm } from './rsform';
7
8
 
8
9
  function mockConstituenta(
9
- partial: Pick<Constituenta, 'id' | 'alias' | 'definition_formal' | 'cst_class' | 'cst_type'>
10
+ partial: Pick<Constituenta, 'id' | 'alias' | 'definition_formal' | 'cst_class' | 'cst_type'> &
11
+ Partial<Pick<Constituenta, 'schema'>>
10
12
  ): Constituenta {
11
13
  return {
12
- schema: 790,
14
+ schema: partial.schema ?? 790,
13
15
  crucial: false,
14
16
  convention: '',
15
17
  definition_raw: '',
@@ -37,11 +39,16 @@ function mockConstituenta(
37
39
  };
38
40
  }
39
41
 
40
- function mockSchema(items: Constituenta[]): RSForm {
42
+ function mockSchema(items: Constituenta[], edges: number[][] = [], id = 790, alias = 'S1'): RSForm {
41
43
  const graph = new Graph(items.map(item => [item.id]));
44
+ for (const edge of edges) {
45
+ if (edge.length === 2) {
46
+ graph.addEdge(edge[0], edge[1]);
47
+ }
48
+ }
42
49
  return {
43
- id: 790,
44
- alias: 'S1',
50
+ id,
51
+ alias,
45
52
  items,
46
53
  graph,
47
54
  cstByAlias: new Map(items.map(item => [item.alias, item]))
@@ -72,4 +79,92 @@ describe('SubstitutionValidator', () => {
72
79
  expect(() => validator.validate()).not.toThrow();
73
80
  expect(validator.validate()).toBe(true);
74
81
  });
82
+
83
+ it('reports definitionCycle when identification closes a formal dependency loop', () => {
84
+ // D16 := F2[D15] ⇒ edge D15 → D16; identifying D15 → D16 yields D16 := F2[D16]
85
+ const schema = mockSchema(
86
+ [
87
+ mockConstituenta({
88
+ id: 1,
89
+ alias: 'X1',
90
+ cst_class: CstClass.BASIC,
91
+ cst_type: CstType.BASE,
92
+ definition_formal: ''
93
+ }),
94
+ mockConstituenta({
95
+ id: 2,
96
+ alias: 'F2',
97
+ cst_class: CstClass.DERIVED,
98
+ cst_type: CstType.FUNCTION,
99
+ definition_formal: '[α∈X1] α'
100
+ }),
101
+ mockConstituenta({
102
+ id: 3,
103
+ alias: 'D15',
104
+ cst_class: CstClass.DERIVED,
105
+ cst_type: CstType.TERM,
106
+ definition_formal: 'X1'
107
+ }),
108
+ mockConstituenta({
109
+ id: 4,
110
+ alias: 'D16',
111
+ cst_class: CstClass.DERIVED,
112
+ cst_type: CstType.TERM,
113
+ definition_formal: 'F2[D15]'
114
+ })
115
+ ],
116
+ [
117
+ [2, 4],
118
+ [3, 4]
119
+ ]
120
+ );
121
+
122
+ const validator = new SubstitutionValidator([schema], [{ original: 3, substitution: 4 }]);
123
+
124
+ expect(validator.validate()).toBe(false);
125
+ expect(validator.errors).toEqual([
126
+ {
127
+ code: SubstitutionErrorType.definitionCycle,
128
+ params: ['D16 → D16']
129
+ }
130
+ ]);
131
+ expect(SubstitutionErrorType.definitionCycle).toBe(0x8906);
132
+ });
133
+
134
+ it('allows identification that does not create a definition cycle', () => {
135
+ const schema = mockSchema(
136
+ [
137
+ mockConstituenta({
138
+ id: 1,
139
+ alias: 'X1',
140
+ cst_class: CstClass.BASIC,
141
+ cst_type: CstType.BASE,
142
+ definition_formal: ''
143
+ }),
144
+ mockConstituenta({
145
+ id: 2,
146
+ alias: 'D1',
147
+ cst_class: CstClass.DERIVED,
148
+ cst_type: CstType.TERM,
149
+ definition_formal: 'X1'
150
+ }),
151
+ mockConstituenta({
152
+ id: 3,
153
+ alias: 'D2',
154
+ cst_class: CstClass.DERIVED,
155
+ cst_type: CstType.TERM,
156
+ definition_formal: 'X1'
157
+ })
158
+ ],
159
+ [
160
+ [1, 2],
161
+ [1, 3]
162
+ ]
163
+ );
164
+
165
+ const validator = new SubstitutionValidator([schema], [{ original: 2, substitution: 3 }]);
166
+
167
+ expect(validator.validate()).toBe(true);
168
+ expect(validator.errors.every(error => error.code !== SubstitutionErrorType.definitionCycle)).toBe(true);
169
+ });
75
170
  });
@@ -7,6 +7,7 @@ import { type AliasMapping, applyAliasMapping, applyTypificationMapping, isSetTy
7
7
  import { labelType } from '../rslang/labels';
8
8
  import { extractBases } from '../rslang/semantic/typification-api';
9
9
 
10
+ import { formatDependencyCycle } from './diagnostics';
10
11
  import { type LibraryItem } from './library';
11
12
  import { NodeType, type OperationSchema, type SubstitutionErrorDescription, SubstitutionErrorType } from './oss';
12
13
  import { type Constituenta, CstClass, CstType, type RSForm, type Substitution } from './rsform';
@@ -101,7 +102,10 @@ export class SubstitutionValidator {
101
102
  if (!this.checkTypes()) {
102
103
  return false;
103
104
  }
104
- if (!this.checkCycles()) {
105
+ if (!this.checkTypificationCycles()) {
106
+ return false;
107
+ }
108
+ if (!this.checkDefinitionCycles()) {
105
109
  return false;
106
110
  }
107
111
  if (!this.checkSubstitutions()) {
@@ -238,7 +242,11 @@ export class SubstitutionValidator {
238
242
  return true;
239
243
  }
240
244
 
241
- private checkCycles(): boolean {
245
+ /**
246
+ * Detect cycles among base/constant sets implied by typifications of non-base
247
+ * constituents under the substitution table.
248
+ */
249
+ private checkTypificationCycles(): boolean {
242
250
  const graph = new Graph();
243
251
  for (const schema of this.schemas) {
244
252
  for (const cst of schema.items) {
@@ -286,6 +294,64 @@ export class SubstitutionValidator {
286
294
  return true;
287
295
  }
288
296
 
297
+ /**
298
+ * Detect logical cycles in formal definitions after applying the substitution table
299
+ * (e.g. D16 := F2[D15] with identification D15 → D16 yields D16 := F2[D16]).
300
+ */
301
+ private checkDefinitionCycles(): boolean {
302
+ const graph = new Graph();
303
+ for (const schema of this.schemas) {
304
+ for (const [id, node] of schema.graph.nodes) {
305
+ graph.addNode(id);
306
+ for (const output of node.outputs) {
307
+ graph.addEdge(id, output);
308
+ }
309
+ }
310
+ }
311
+
312
+ const deleted = new Map<number, number>();
313
+ for (const item of this.substitutions) {
314
+ deleted.set(item.original, item.substitution);
315
+ }
316
+ const resolve = (id: number): number => deleted.get(id) ?? id;
317
+
318
+ const result = new Graph();
319
+ for (const id of graph.nodes.keys()) {
320
+ if (!deleted.has(id)) {
321
+ result.addNode(id);
322
+ }
323
+ }
324
+ for (const substitutionId of deleted.values()) {
325
+ result.addNode(substitutionId);
326
+ }
327
+
328
+ for (const [id, node] of graph.nodes) {
329
+ for (const output of node.outputs) {
330
+ if (deleted.has(output)) {
331
+ continue;
332
+ }
333
+ result.addEdge(resolve(id), output);
334
+ }
335
+ }
336
+
337
+ const cycle = result.findCycle();
338
+ if (cycle !== null) {
339
+ const aliases = cycle.map(id => {
340
+ const cst = this.cstByID.get(id);
341
+ if (!cst) {
342
+ return String(id);
343
+ }
344
+ if (this.schemas.length > 1) {
345
+ const schema = this.schemaByID.get(cst.schema);
346
+ return schema ? `[${schema.alias}]-${cst.alias}` : cst.alias;
347
+ }
348
+ return cst.alias;
349
+ });
350
+ return this.reportError(SubstitutionErrorType.definitionCycle, [formatDependencyCycle(aliases)]);
351
+ }
352
+ return true;
353
+ }
354
+
289
355
  private checkSubstitutions(): boolean {
290
356
  const baseMappings = this.prepareBaseMappings();
291
357
  const typeMappings = this.calculateSubstituteMappings(baseMappings);
@@ -407,10 +473,10 @@ export class SubstitutionValidator {
407
473
  return expression1.replace(/\s+/g, '') === expression2.replace(/\s+/g, '');
408
474
  }
409
475
 
410
- private reportError(errorType: SubstitutionErrorType, params: string[]): boolean {
476
+ private reportError(code: SubstitutionErrorType, params: string[]): boolean {
411
477
  this.errors.push({
412
- errorType: errorType,
413
- params: params
478
+ code,
479
+ params
414
480
  });
415
481
  return false;
416
482
  }
@@ -4,6 +4,7 @@
4
4
 
5
5
  import { type Graph } from '../graph';
6
6
 
7
+ import { RSDiagnosticCode } from './diagnostics';
7
8
  import { type LibraryItem } from './library';
8
9
  import { type NodePosition, type OssLayout } from './oss-layout';
9
10
 
@@ -126,25 +127,30 @@ export interface OperationSchema extends LibraryItem {
126
127
  itemByNodeID: Map<string, OssItem>;
127
128
  }
128
129
 
129
- /** Represents substitution error description. */
130
+ /** Represents substitution error description (same code space as {@link RSDiagnosticCode}). */
130
131
  export interface SubstitutionErrorDescription {
131
- errorType: SubstitutionErrorType;
132
+ code: SubstitutionErrorType;
132
133
  params: string[];
133
134
  }
134
135
 
135
- /** Represents Substitution table error types. */
136
+ /**
137
+ * Substitution-table error codes — aliases into {@link RSDiagnosticCode}.
138
+ * Logical definition cycles reuse {@link RSDiagnosticCode.schemaDependencyCycle}.
139
+ */
136
140
  export const SubstitutionErrorType = {
137
- invalidIDs: 0,
138
- incorrectCst: 1,
139
- invalidClasses: 2,
140
- invalidBasic: 3,
141
- invalidConstant: 4,
142
- typificationCycle: 5,
143
- baseSubstitutionNotSet: 6,
144
- unequalTypification: 7,
145
- unequalExpressions: 8,
146
- unequalArgsCount: 9,
147
- unequalArgs: 10,
148
- invalidNominal: 11
149
- } as const;
141
+ invalidIDs: RSDiagnosticCode.substitutionInvalidIDs,
142
+ incorrectCst: RSDiagnosticCode.substitutionIncorrectCst,
143
+ invalidClasses: RSDiagnosticCode.substitutionInvalidClasses,
144
+ invalidBasic: RSDiagnosticCode.substitutionInvalidBasic,
145
+ invalidConstant: RSDiagnosticCode.substitutionInvalidConstant,
146
+ typificationCycle: RSDiagnosticCode.substitutionTypificationCycle,
147
+ baseSubstitutionNotSet: RSDiagnosticCode.substitutionBaseNotSet,
148
+ unequalTypification: RSDiagnosticCode.substitutionUnequalTypification,
149
+ unequalExpressions: RSDiagnosticCode.substitutionUnequalExpressions,
150
+ unequalArgsCount: RSDiagnosticCode.substitutionUnequalArgsCount,
151
+ unequalArgs: RSDiagnosticCode.substitutionUnequalArgs,
152
+ invalidNominal: RSDiagnosticCode.substitutionInvalidNominal,
153
+ /** Same code as constituent schema dependency cycle. */
154
+ definitionCycle: RSDiagnosticCode.schemaDependencyCycle
155
+ } as const satisfies Record<string, (typeof RSDiagnosticCode)[keyof typeof RSDiagnosticCode]>;
150
156
  export type SubstitutionErrorType = (typeof SubstitutionErrorType)[keyof typeof SubstitutionErrorType];
@@ -0,0 +1,79 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ import { type Value } from '../rslang';
4
+ import { compare, TUPLE_ID } from '../rslang/eval/value';
5
+ import { isTupleValue } from '../rslang/eval/value-api';
6
+ import { basic, bool, IntegerT, tuple } from '../rslang/semantic/typification';
7
+
8
+ import { type BasicBinding } from './rsmodel';
9
+ import { generateRandomValue } from './rsmodel-api';
10
+
11
+ function hasUniqueElements(value: Value[]): boolean {
12
+ const sorted = [...value].sort(compare);
13
+ for (let i = 1; i < sorted.length; i++) {
14
+ if (compare(sorted[i - 1], sorted[i]) === 0) {
15
+ return false;
16
+ }
17
+ }
18
+ return true;
19
+ }
20
+
21
+ describe('generateRandomValue', () => {
22
+ afterEach(() => {
23
+ vi.restoreAllMocks();
24
+ });
25
+
26
+ it('returns unique elements for collections even when samples collide', () => {
27
+ const basics = new Map<number, BasicBinding>([[10, { 1: 'x1', 2: 'x2' }]]);
28
+ const cstByAlias = new Map([['X1', { id: 10 }]]);
29
+ const collectionType = bool(basic('X1'));
30
+
31
+ // Force: size=5, then always pick the same base element id=1.
32
+ const random = vi.spyOn(Math, 'random');
33
+ random
34
+ .mockReturnValueOnce(0.99) // -> 5 elements
35
+ .mockReturnValue(0); // -> always ids[0] === 1
36
+
37
+ const value = generateRandomValue(collectionType, basics, cstByAlias, 5);
38
+ expect(Array.isArray(value)).toBe(true);
39
+ expect(value).toEqual([1]);
40
+ expect(hasUniqueElements(value as Value[])).toBe(true);
41
+ });
42
+
43
+ it('returns unique nested sets for tuple of collections ℬ(X1)×ℬ(C1)', () => {
44
+ const basics = new Map<number, BasicBinding>([
45
+ [10, { 1: 'x1', 2: 'x2' }],
46
+ [20, { 0: 'c0', 1: 'c1', 2: 'c2' }]
47
+ ]);
48
+ const cstByAlias = new Map([
49
+ ['X1', { id: 10 }],
50
+ ['C1', { id: 20 }]
51
+ ]);
52
+ const type = tuple([bool(basic('X1')), bool(basic('C1'))]);
53
+
54
+ for (let attempt = 0; attempt < 30; attempt++) {
55
+ const value = generateRandomValue(type, basics, cstByAlias, 10);
56
+ expect(isTupleValue(value)).toBe(true);
57
+ const tupleValue = value as Value[];
58
+ expect(tupleValue[0]).toBe(TUPLE_ID);
59
+ expect(Array.isArray(tupleValue[1])).toBe(true);
60
+ expect(Array.isArray(tupleValue[2])).toBe(true);
61
+ expect(hasUniqueElements(tupleValue[1] as Value[])).toBe(true);
62
+ expect(hasUniqueElements(tupleValue[2] as Value[])).toBe(true);
63
+ }
64
+ });
65
+
66
+ it('returns null when a basic domain is empty', () => {
67
+ const basics = new Map<number, BasicBinding>();
68
+ const cstByAlias = new Map([['X1', { id: 10 }]]);
69
+ expect(generateRandomValue(basic('X1'), basics, cstByAlias)).toBeNull();
70
+ expect(generateRandomValue(bool(basic('X1')), basics, cstByAlias)).toBeNull();
71
+ });
72
+
73
+ it('generates integers in range', () => {
74
+ const value = generateRandomValue(IntegerT, new Map(), new Map());
75
+ expect(typeof value).toBe('number');
76
+ expect(value as number).toBeGreaterThanOrEqual(-100);
77
+ expect(value as number).toBeLessThanOrEqual(100);
78
+ });
79
+ });
@@ -1,5 +1,5 @@
1
1
  import { type ExpressionType, TypeID, type Typification, type Value } from '../rslang';
2
- import { compare, tuple, TUPLE_ID, VALUE_TRUE, type ValuePath } from '../rslang/eval/value';
2
+ import { compare, set, tuple, TUPLE_ID, VALUE_TRUE, type ValuePath } from '../rslang/eval/value';
3
3
  import { extractValue, isTupleValue, makeDefaultValue, setNestedValue } from '../rslang/eval/value-api';
4
4
  import { type EchelonCollection } from '../rslang/semantic/typification';
5
5
 
@@ -80,7 +80,8 @@ export function generateRandomValue(
80
80
  }
81
81
  result.push(sample);
82
82
  }
83
- return result;
83
+ // Sets must be unique; sampling with replacement can produce duplicates.
84
+ return set(result);
84
85
  }
85
86
  case TypeID.anyTypification:
86
87
  return null;
@@ -1 +0,0 @@
1
- export { };
@@ -1 +0,0 @@
1
- export { };
@@ -1 +0,0 @@
1
- export { };
@@ -1 +0,0 @@
1
- export { };