@rsconcept/domain 1.8.0 → 2.0.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.
- package/dist/{analyzer-DpE0qknt.d.ts → analyzer-iqAXyROE.d.ts} +2 -2
- package/dist/{arguments-extractor-Dd1lFom9.d.ts → arguments-extractor-ngatANBI.d.ts} +2 -2
- package/dist/{ast-annotations-CZCzs9Uk.d.ts → ast-annotations-D6KZDu12.d.ts} +2 -2
- package/dist/{calculator-Dt03dsnd.d.ts → calculator-k8BGIKmj.d.ts} +2 -2
- package/dist/diagnostics-Clo3mkba.d.ts +279 -0
- package/dist/diagnostics-DYMvTSev.js +532 -0
- package/dist/diagnostics-DYMvTSev.js.map +1 -0
- package/dist/index.d.ts +14 -15
- package/dist/index.js +7 -6
- package/dist/library/diagnostics.d.ts +2 -0
- package/dist/library/diagnostics.js +2 -0
- package/dist/library/index.d.ts +15 -6
- package/dist/library/index.js +41 -13
- package/dist/library/index.js.map +1 -1
- package/dist/library/oss-api.d.ts +1 -1
- package/dist/library/oss-api.js +1 -1
- package/dist/library/rsengine.d.ts +2 -3
- package/dist/library/rsengine.js +1 -1
- package/dist/library/rsform-api.d.ts +4 -4
- package/dist/library/rsform-api.js +1 -404
- package/dist/library/rsform.d.ts +2 -198
- package/dist/library/rsform.js.map +1 -1
- package/dist/library/rsmodel-api.d.ts +2 -3
- package/dist/library/rsmodel-api.js +2 -4
- package/dist/library/rsmodel-api.js.map +1 -1
- package/dist/library/rsmodel.d.ts +2 -46
- package/dist/library/structure-planner.d.ts +2 -2
- package/dist/library/structure-planner.js +1 -1
- package/dist/rslang/api.d.ts +1 -1
- package/dist/rslang/api.js +2 -1
- package/dist/rslang/api.js.map +1 -1
- package/dist/rslang/ast-annotations.d.ts +1 -1
- package/dist/rslang/eval/calculator.d.ts +1 -1
- package/dist/rslang/eval/evaluator.js +2 -2
- package/dist/rslang/eval/value-api.d.ts +1 -1
- package/dist/rslang/index.d.ts +8 -8
- package/dist/rslang/index.js +2 -2
- package/dist/rslang/labels.d.ts +4 -2
- package/dist/rslang/labels.js +8 -2
- package/dist/rslang/labels.js.map +1 -1
- package/dist/rslang/parser/syntax-errors/index.d.ts +1 -1
- package/dist/rslang/parser/syntax-errors/index.js +1 -1
- package/dist/rslang/semantic/analyzer.d.ts +1 -1
- package/dist/rslang/semantic/analyzer.js +3 -3
- package/dist/rslang/semantic/arguments-extractor.d.ts +1 -1
- package/dist/rslang/semantic/arguments-extractor.js +1 -1
- package/dist/rslang/semantic/type-auditor.d.ts +1 -1
- package/dist/rslang/semantic/type-auditor.js +4 -4
- package/dist/rslang/semantic/type-auditor.js.map +1 -1
- package/dist/rslang/semantic/typification-api.d.ts +1 -1
- package/dist/rslang/semantic/typification-parser.d.ts +1 -1
- package/dist/rslang/semantic/typification.d.ts +2 -2
- package/dist/rslang/semantic/typification.js +14 -6
- package/dist/rslang/semantic/typification.js.map +1 -1
- package/dist/rslang/semantic/value-auditor.js +2 -2
- package/dist/rslang/typification-graph.d.ts +1 -1
- package/dist/{syntax-errors-B6c2vsan.js → syntax-errors-D_VV5hAM.js} +3 -3
- package/dist/{syntax-errors-B6c2vsan.js.map → syntax-errors-D_VV5hAM.js.map} +1 -1
- package/dist/{typification-DDuv88ir.d.ts → typification-9lNu9R4k.d.ts} +8 -4
- package/dist/{typification-graph-8VdK6_Fh.d.ts → typification-graph-BY_oGT82.d.ts} +2 -2
- package/dist/{typification-parser-DHO42ZRc.d.ts → typification-parser-Dytsv9CI.d.ts} +2 -2
- package/dist/{value-api-C2X4Pom5.d.ts → value-api-CXLl_zg0.d.ts} +2 -2
- package/package.json +5 -1
- package/src/index.ts +8 -0
- package/src/library/diagnostics.ts +188 -0
- package/src/library/index.ts +9 -0
- package/src/library/oss-api.test.ts +1 -2
- package/src/library/rsform-api.ts +12 -17
- package/src/library/rsform.ts +4 -2
- package/src/library/rsmodel-api.ts +2 -8
- package/src/library/template-instantiation.test.ts +123 -5
- package/src/library/template-instantiation.ts +42 -12
- package/src/rslang/api.ts +2 -1
- package/src/rslang/labels.ts +10 -1
- package/src/rslang/semantic/type-auditor.test.ts +25 -6
- package/src/rslang/semantic/type-auditor.ts +2 -1
- package/src/rslang/semantic/typification-api.test.ts +21 -5
- package/src/rslang/semantic/typification-parser.test.ts +6 -1
- package/src/rslang/semantic/typification.ts +16 -6
- package/dist/library/rsform-api.js.map +0 -1
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
import { basic, bool, constant, type EchelonFunctional, isTypification } from '../rslang/semantic/typification';
|
|
24
24
|
import { applyPath } from '../rslang/semantic/typification-api';
|
|
25
25
|
|
|
26
|
+
import { hasCstDiagnostic, RSDiagnosticCode } from './diagnostics';
|
|
26
27
|
import { type LibraryItem } from './library';
|
|
27
28
|
import {
|
|
28
29
|
type ArgumentValue,
|
|
@@ -62,21 +63,9 @@ const CST_TYPE_TO_CLASS: Record<CstType, CstClass> = {
|
|
|
62
63
|
|
|
63
64
|
/** Checks if {@link Constituenta} is a schema issue. */
|
|
64
65
|
export function isSchemaIssue(cst: Constituenta): boolean {
|
|
65
|
-
|
|
66
|
-
cst.
|
|
67
|
-
|
|
68
|
-
cst.status === CstStatus.INCORRECT ||
|
|
69
|
-
cst.status === CstStatus.INCALCULABLE ||
|
|
70
|
-
cst.is_type_mismatch
|
|
71
|
-
) {
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
if (isBasicConcept(cst.cst_type) && !isLogical(cst.cst_type)) {
|
|
75
|
-
if (!cst.convention || !cst.term_resolved) {
|
|
76
|
-
return true;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return false;
|
|
66
|
+
return (
|
|
67
|
+
(cst.diagnostics?.length ?? 0) > 0 || cst.status === CstStatus.INCORRECT || cst.status === CstStatus.INCALCULABLE
|
|
68
|
+
);
|
|
80
69
|
}
|
|
81
70
|
|
|
82
71
|
/** Infers the status of an expression based on parsing and value information. */
|
|
@@ -489,8 +478,14 @@ export function calculateSchemaStats(target: RSForm): RSFormStats {
|
|
|
489
478
|
step_complexity: items.reduce((sum, cst) => sum + calculateStepComplexity(cst), 0),
|
|
490
479
|
|
|
491
480
|
count_problematic: items.reduce((sum, cst) => sum + (isSchemaIssue(cst) ? 1 : 0), 0),
|
|
492
|
-
count_homonyms: items.reduce(
|
|
493
|
-
|
|
481
|
+
count_homonyms: items.reduce(
|
|
482
|
+
(sum, cst) => sum + (hasCstDiagnostic(cst, RSDiagnosticCode.schemaHomonym) ? 1 : 0),
|
|
483
|
+
0
|
|
484
|
+
),
|
|
485
|
+
count_formal_duplicates: items.reduce(
|
|
486
|
+
(sum, cst) => sum + (hasCstDiagnostic(cst, RSDiagnosticCode.schemaFormalDuplicate) ? 1 : 0),
|
|
487
|
+
0
|
|
488
|
+
),
|
|
494
489
|
count_missing_convention: items.reduce((sum, cst) => sum + (isMissingConvention(cst) ? 1 : 0), 0),
|
|
495
490
|
count_type_mismatch: items.reduce((sum, cst) => sum + (cst.is_type_mismatch ? 1 : 0), 0),
|
|
496
491
|
|
package/src/library/rsform.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { type Graph } from '../graph';
|
|
6
6
|
import { type AnalysisBase, type ExpressionType, type RSLangAnalyzer, type TypePath } from '../rslang';
|
|
7
7
|
|
|
8
|
+
import { type CstDiagnostic } from './diagnostics';
|
|
8
9
|
import { type CurrentVersion, type LibraryItem, type LibraryItemReference, type VersionInfo } from './library';
|
|
9
10
|
|
|
10
11
|
/** Represents {@link Constituenta} type. */
|
|
@@ -93,10 +94,11 @@ export interface Constituenta {
|
|
|
93
94
|
/** Controls whether base concepts expose property values or full values */
|
|
94
95
|
value_is_property: boolean;
|
|
95
96
|
|
|
96
|
-
homonyms: number[];
|
|
97
|
-
formalDuplicates: number[];
|
|
98
97
|
analysis: AnalysisBase;
|
|
99
98
|
|
|
99
|
+
/** Schema-kind {@link CstDiagnostic} list, populated during schema loading. */
|
|
100
|
+
diagnostics: CstDiagnostic[];
|
|
101
|
+
|
|
100
102
|
/** Typification used for dependents, model values, and display (manual when it overrides computed). */
|
|
101
103
|
effectiveType: ExpressionType | null;
|
|
102
104
|
|
|
@@ -3,6 +3,7 @@ import { compare, tuple, TUPLE_ID, VALUE_TRUE, type ValuePath } from '../rslang/
|
|
|
3
3
|
import { extractValue, isTupleValue, makeDefaultValue, setNestedValue } from '../rslang/eval/value-api';
|
|
4
4
|
import { type EchelonCollection } from '../rslang/semantic/typification';
|
|
5
5
|
|
|
6
|
+
import { modelStatusCstDiagnostic } from './diagnostics';
|
|
6
7
|
import { type RSEngine } from './rsengine';
|
|
7
8
|
import { type Constituenta, CstType, type RSForm } from './rsform';
|
|
8
9
|
import { calculateSchemaStats, isBaseSet, isBasicConcept } from './rsform-api';
|
|
@@ -125,14 +126,7 @@ export function calculateModelStats(schema: RSForm, engine: RSEngine, _engineGen
|
|
|
125
126
|
|
|
126
127
|
/** Checks whether evaluation status contributes to model status issues. */
|
|
127
128
|
export function isModelIssue(engine: RSEngine, cst: Constituenta): boolean {
|
|
128
|
-
|
|
129
|
-
const isBasic = isBasicConcept(cst.cst_type);
|
|
130
|
-
return (
|
|
131
|
-
status === EvalStatus.EVAL_FAIL ||
|
|
132
|
-
status === EvalStatus.AXIOM_FALSE ||
|
|
133
|
-
status === EvalStatus.INVALID_DATA ||
|
|
134
|
-
(isBasic && status === EvalStatus.EMPTY)
|
|
135
|
-
);
|
|
129
|
+
return modelStatusCstDiagnostic(engine.getCstStatus(cst.id), cst.cst_type) !== null;
|
|
136
130
|
}
|
|
137
131
|
|
|
138
132
|
/** Evaluate if {@link CstType} is interpretable. */
|
|
@@ -19,8 +19,7 @@ function mockCst(partial: Pick<Constituenta, 'id' | 'alias' | 'cst_type'> & Part
|
|
|
19
19
|
term_forms: [],
|
|
20
20
|
typification_manual: '',
|
|
21
21
|
value_is_property: false,
|
|
22
|
-
|
|
23
|
-
formalDuplicates: [],
|
|
22
|
+
diagnostics: [],
|
|
24
23
|
analysis: { success: true } as Constituenta['analysis'],
|
|
25
24
|
effectiveType: null,
|
|
26
25
|
is_type_mismatch: false,
|
|
@@ -219,17 +218,82 @@ describe('planTemplateInstantiation', () => {
|
|
|
219
218
|
expect(plan.items[0].definition_formal).not.toContain('F42[');
|
|
220
219
|
});
|
|
221
220
|
|
|
222
|
-
it('
|
|
221
|
+
it('instantiates bank dependency when target alias collides but definition differs', () => {
|
|
222
|
+
const schemaWithUnrelatedF42 = mockSchema([
|
|
223
|
+
...targetSchema.items,
|
|
224
|
+
mockCst({
|
|
225
|
+
id: 12,
|
|
226
|
+
alias: 'F42',
|
|
227
|
+
cst_type: CstType.FUNCTION,
|
|
228
|
+
definition_formal: '[α∈R1] α'
|
|
229
|
+
})
|
|
230
|
+
]);
|
|
231
|
+
|
|
232
|
+
const plan = planTemplateInstantiation({
|
|
233
|
+
targetSchema: schemaWithUnrelatedF42,
|
|
234
|
+
templateItems,
|
|
235
|
+
prototype: p30,
|
|
236
|
+
userArgs: [{ alias: 'σ', typification: 'ℬ((R1×R1)×R1)', value: 'S2' }],
|
|
237
|
+
mainItem: {
|
|
238
|
+
alias: 'A2',
|
|
239
|
+
cst_type: CstType.PREDICATE,
|
|
240
|
+
crucial: false,
|
|
241
|
+
convention: '',
|
|
242
|
+
definition_formal: p30.definition_formal,
|
|
243
|
+
definition_raw: '',
|
|
244
|
+
term_raw: p30.term_raw,
|
|
245
|
+
term_forms: [],
|
|
246
|
+
typification_manual: '',
|
|
247
|
+
value_is_property: false
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const dependency = plan.items[0];
|
|
252
|
+
expect(plan.items).toHaveLength(2);
|
|
253
|
+
expect(dependency.alias).not.toBe('F42');
|
|
254
|
+
expect(dependency.definition_formal).toContain('S2');
|
|
255
|
+
expect(plan.items.at(-1)!.definition_formal).toContain(`${dependency.alias}[`);
|
|
256
|
+
expect(plan.items.at(-1)!.definition_formal).not.toContain('F42[');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('reuses template dependency when target has matching substituted definition', () => {
|
|
260
|
+
const userArgs: ArgumentValue[] = [{ alias: 'σ', typification: 'ℬ((R1×R1)×R1)', value: 'S2' }];
|
|
261
|
+
|
|
262
|
+
const baseline = planTemplateInstantiation({
|
|
263
|
+
targetSchema,
|
|
264
|
+
templateItems,
|
|
265
|
+
prototype: p30,
|
|
266
|
+
userArgs,
|
|
267
|
+
mainItem: {
|
|
268
|
+
alias: 'A2',
|
|
269
|
+
cst_type: CstType.PREDICATE,
|
|
270
|
+
crucial: false,
|
|
271
|
+
convention: '',
|
|
272
|
+
definition_formal: p30.definition_formal,
|
|
273
|
+
definition_raw: '',
|
|
274
|
+
term_raw: p30.term_raw,
|
|
275
|
+
term_forms: [],
|
|
276
|
+
typification_manual: '',
|
|
277
|
+
value_is_property: false
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const equivalentFunction = baseline.items[0];
|
|
223
282
|
const schemaWithF42 = mockSchema([
|
|
224
283
|
...targetSchema.items,
|
|
225
|
-
mockCst({
|
|
284
|
+
mockCst({
|
|
285
|
+
id: 12,
|
|
286
|
+
alias: 'F42',
|
|
287
|
+
cst_type: equivalentFunction.cst_type,
|
|
288
|
+
definition_formal: equivalentFunction.definition_formal
|
|
289
|
+
})
|
|
226
290
|
]);
|
|
227
291
|
|
|
228
292
|
const plan = planTemplateInstantiation({
|
|
229
293
|
targetSchema: schemaWithF42,
|
|
230
294
|
templateItems,
|
|
231
295
|
prototype: p30,
|
|
232
|
-
userArgs
|
|
296
|
+
userArgs,
|
|
233
297
|
mainItem: {
|
|
234
298
|
alias: 'A2',
|
|
235
299
|
cst_type: CstType.PREDICATE,
|
|
@@ -305,4 +369,58 @@ describe('planTemplateInstantiation', () => {
|
|
|
305
369
|
expect(plan.mainDuplicateAlias).toBe('A9');
|
|
306
370
|
expect(plan.items.some(item => item.term_raw === p35.term_raw)).toBe(false);
|
|
307
371
|
});
|
|
372
|
+
|
|
373
|
+
it('reports binding conflict when the same template parameter is bound to different aliases', () => {
|
|
374
|
+
const f10 = mockCst({
|
|
375
|
+
id: 5,
|
|
376
|
+
alias: 'F10',
|
|
377
|
+
cst_type: CstType.FUNCTION,
|
|
378
|
+
term_raw: 'g',
|
|
379
|
+
definition_formal: '[x∈R1] x'
|
|
380
|
+
});
|
|
381
|
+
const p11 = mockCst({
|
|
382
|
+
id: 6,
|
|
383
|
+
alias: 'P11',
|
|
384
|
+
cst_type: CstType.PREDICATE,
|
|
385
|
+
term_raw: 'h',
|
|
386
|
+
definition_formal: '[y∈R1] F10[y]'
|
|
387
|
+
});
|
|
388
|
+
const mainTemplate = mockCst({
|
|
389
|
+
id: 7,
|
|
390
|
+
alias: 'P12',
|
|
391
|
+
cst_type: CstType.PREDICATE,
|
|
392
|
+
term_raw: 'main',
|
|
393
|
+
definition_formal: '[a∈R1, b∈R1] P11[a] & P11[b]'
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
const plan = planTemplateInstantiation({
|
|
397
|
+
targetSchema,
|
|
398
|
+
templateItems: [f10, p11],
|
|
399
|
+
prototype: mainTemplate,
|
|
400
|
+
userArgs: [
|
|
401
|
+
{ alias: 'a', typification: 'R1', value: 'S1' },
|
|
402
|
+
{ alias: 'b', typification: 'R1', value: 'S2' }
|
|
403
|
+
],
|
|
404
|
+
mainItem: {
|
|
405
|
+
alias: 'A1',
|
|
406
|
+
cst_type: CstType.PREDICATE,
|
|
407
|
+
crucial: false,
|
|
408
|
+
convention: '',
|
|
409
|
+
definition_formal: mainTemplate.definition_formal,
|
|
410
|
+
definition_raw: '',
|
|
411
|
+
term_raw: mainTemplate.term_raw,
|
|
412
|
+
term_forms: [],
|
|
413
|
+
typification_manual: '',
|
|
414
|
+
value_is_property: false
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
expect(plan.bindingConflict).toEqual({
|
|
419
|
+
templateAlias: 'P11',
|
|
420
|
+
paramAlias: 'y',
|
|
421
|
+
existing: 'S1',
|
|
422
|
+
incoming: 'S2'
|
|
423
|
+
});
|
|
424
|
+
expect(plan.items).toHaveLength(0);
|
|
425
|
+
});
|
|
308
426
|
});
|
|
@@ -47,11 +47,19 @@ export interface TemplateInstantiationItem {
|
|
|
47
47
|
value_is_property: boolean;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/** Conflicting argument bindings inferred for the same template dependency. */
|
|
51
|
+
export interface TemplateBindingConflict {
|
|
52
|
+
templateAlias: string;
|
|
53
|
+
paramAlias: string;
|
|
54
|
+
existing: string;
|
|
55
|
+
incoming: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
50
58
|
/**
|
|
51
59
|
* Batch create plan: dependencies first, selected template constituent last.
|
|
52
60
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
61
|
+
* Reuses constituents whose substituted formal definition already exists in the target schema
|
|
62
|
+
* (normalized comparison), even when the bank alias differs from the matched target alias.
|
|
55
63
|
* {@link TemplateInstantiationPlan.mainDuplicateAlias} reports when the selected template is a duplicate.
|
|
56
64
|
* Does not set `insert_after`; the caller supplies insertion on the batch DTO.
|
|
57
65
|
*/
|
|
@@ -59,6 +67,8 @@ export interface TemplateInstantiationPlan {
|
|
|
59
67
|
items: TemplateInstantiationItem[];
|
|
60
68
|
/** Set when the selected template expression already exists in the target schema. */
|
|
61
69
|
mainDuplicateAlias: string | null;
|
|
70
|
+
/** Set when nested template calls bind the same parameter to different global aliases. */
|
|
71
|
+
bindingConflict: TemplateBindingConflict | null;
|
|
62
72
|
}
|
|
63
73
|
|
|
64
74
|
/**
|
|
@@ -115,6 +125,7 @@ export class TemplateInstantiationPlanner {
|
|
|
115
125
|
private readonly userMapping: AliasMapping;
|
|
116
126
|
private readonly depBindings = new Map<string, Map<string, string>>();
|
|
117
127
|
private readonly depRefs = new Map<string, Set<string>>();
|
|
128
|
+
private bindingConflict: TemplateBindingConflict | null = null;
|
|
118
129
|
|
|
119
130
|
/** @param input - See {@link TemplateInstantiationInput}. */
|
|
120
131
|
constructor(input: TemplateInstantiationInput) {
|
|
@@ -133,6 +144,10 @@ export class TemplateInstantiationPlanner {
|
|
|
133
144
|
const mainDefinition = substituteTemplateArgs(this.prototype.definition_formal, this.userMapping);
|
|
134
145
|
this.collectDependencies(mainDefinition, this.userMapping);
|
|
135
146
|
|
|
147
|
+
if (this.bindingConflict) {
|
|
148
|
+
return { items: [], mainDuplicateAlias: null, bindingConflict: this.bindingConflict };
|
|
149
|
+
}
|
|
150
|
+
|
|
136
151
|
const definitionIndex = createFormalDefinitionIndex(this.targetSchema);
|
|
137
152
|
const dependencyItems = this.buildDependencyItems(definitionIndex);
|
|
138
153
|
const { item: mainItem, duplicateAlias: mainDuplicateAlias } = this.buildMainItem(
|
|
@@ -144,7 +159,8 @@ export class TemplateInstantiationPlanner {
|
|
|
144
159
|
|
|
145
160
|
return {
|
|
146
161
|
items: mainItem ? [...dependencyItems.items, mainItem] : dependencyItems.items,
|
|
147
|
-
mainDuplicateAlias
|
|
162
|
+
mainDuplicateAlias,
|
|
163
|
+
bindingConflict: null
|
|
148
164
|
};
|
|
149
165
|
}
|
|
150
166
|
|
|
@@ -221,7 +237,10 @@ export class TemplateInstantiationPlanner {
|
|
|
221
237
|
}
|
|
222
238
|
|
|
223
239
|
private ensureDep(templateAlias: string, bindings: Map<string, string>, referrer?: string): void {
|
|
224
|
-
if (
|
|
240
|
+
if (this.bindingConflict) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (!this.templateByAlias.has(templateAlias)) {
|
|
225
244
|
return;
|
|
226
245
|
}
|
|
227
246
|
if (referrer && this.depBindings.has(referrer)) {
|
|
@@ -229,7 +248,12 @@ export class TemplateInstantiationPlanner {
|
|
|
229
248
|
refs.add(templateAlias);
|
|
230
249
|
this.depRefs.set(referrer, refs);
|
|
231
250
|
}
|
|
232
|
-
const
|
|
251
|
+
const mergeResult = mergeBindings(this.depBindings.get(templateAlias), bindings);
|
|
252
|
+
if (!mergeResult.ok) {
|
|
253
|
+
this.bindingConflict = { templateAlias, ...mergeResult.conflict };
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const merged = mergeResult.bindings;
|
|
233
257
|
const changed =
|
|
234
258
|
!this.depBindings.has(templateAlias) || merged.size !== (this.depBindings.get(templateAlias)?.size ?? 0);
|
|
235
259
|
this.depBindings.set(templateAlias, merged);
|
|
@@ -243,6 +267,9 @@ export class TemplateInstantiationPlanner {
|
|
|
243
267
|
}
|
|
244
268
|
|
|
245
269
|
private collectDependencies(expression: string, scope: AliasMapping, referrer?: string): void {
|
|
270
|
+
if (this.bindingConflict) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
246
273
|
const ast = parseRSLangExpression(expression);
|
|
247
274
|
if (!ast) {
|
|
248
275
|
return;
|
|
@@ -332,14 +359,21 @@ function resolveArgBinding(argExpr: string, scope: AliasMapping): string | undef
|
|
|
332
359
|
return undefined;
|
|
333
360
|
}
|
|
334
361
|
|
|
335
|
-
|
|
362
|
+
type MergeBindingsResult =
|
|
363
|
+
| { ok: true; bindings: Map<string, string> }
|
|
364
|
+
| { ok: false; conflict: { paramAlias: string; existing: string; incoming: string } };
|
|
365
|
+
|
|
366
|
+
function mergeBindings(existing: Map<string, string> | undefined, incoming: Map<string, string>): MergeBindingsResult {
|
|
336
367
|
const result = new Map(existing);
|
|
337
368
|
for (const [alias, value] of incoming) {
|
|
338
|
-
|
|
369
|
+
const existingValue = result.get(alias);
|
|
370
|
+
if (existingValue === undefined) {
|
|
339
371
|
result.set(alias, value);
|
|
372
|
+
} else if (existingValue !== value) {
|
|
373
|
+
return { ok: false, conflict: { paramAlias: alias, existing: existingValue, incoming: value } };
|
|
340
374
|
}
|
|
341
375
|
}
|
|
342
|
-
return result;
|
|
376
|
+
return { ok: true, bindings: result };
|
|
343
377
|
}
|
|
344
378
|
|
|
345
379
|
function bindingMapToArgs(params: TemplateParam[], bindings: Map<string, string>): ArgumentValue[] {
|
|
@@ -401,10 +435,6 @@ function extractFuncCalls(ast: AstNode, expression: string): FuncCallRef[] {
|
|
|
401
435
|
return result;
|
|
402
436
|
}
|
|
403
437
|
|
|
404
|
-
function targetHasAlias(schema: RSForm, alias: string): boolean {
|
|
405
|
-
return schema.items.some(cst => cst.alias === alias);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
438
|
function topologicalSort(aliases: string[], refs: Map<string, Set<string>>): string[] {
|
|
409
439
|
const remaining = new Set(aliases);
|
|
410
440
|
const sorted: string[] = [];
|
package/src/rslang/api.ts
CHANGED
|
@@ -15,13 +15,14 @@ export type AliasMapping = Record<string, string>;
|
|
|
15
15
|
const LOCALS_REGEXP = /[_a-zα-ω][a-zα-ω]*\d*/g;
|
|
16
16
|
const GLOBAL_ALIAS_SOURCE = '[XCSADFPTN]\\d+';
|
|
17
17
|
const GLOBALS_REGEXP = new RegExp(GLOBAL_ALIAS_SOURCE, 'g');
|
|
18
|
+
const GLOBAL_ALIAS_ANCHORED_REGEXP = new RegExp(`^${GLOBAL_ALIAS_SOURCE}$`);
|
|
18
19
|
const COMPLEX_SYMBOLS_REGEXP = /[∀∃×ℬ;|:]/g;
|
|
19
20
|
const TYPIFICATION_SET = /^ℬ+\([ℬ\(((X|C)\d+|Z)\)×]*\)$/g;
|
|
20
21
|
// cspell:enable
|
|
21
22
|
|
|
22
23
|
/** Whether {@link value} is a single schema global alias (e.g. `X1`, `F42`). */
|
|
23
24
|
export function isGlobalAlias(value: string): boolean {
|
|
24
|
-
return
|
|
25
|
+
return GLOBAL_ALIAS_ANCHORED_REGEXP.test(value);
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
/**
|
package/src/rslang/labels.ts
CHANGED
|
@@ -138,6 +138,15 @@ export function labelRSLangNode(node: AstNodeBase): string {
|
|
|
138
138
|
return 'UNKNOWN NODE: ' + String(node.typeID);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
/** Formats a base-set alias for display (mangled radicals `func<R#>` → `<func>R#`). */
|
|
142
|
+
export function labelBaseId(baseID: string): string {
|
|
143
|
+
const match = /^([^<]+)<(R[1-9]\d*)>$/.exec(baseID);
|
|
144
|
+
if (match) {
|
|
145
|
+
return `<${match[1]}>${match[2]}`;
|
|
146
|
+
}
|
|
147
|
+
return baseID;
|
|
148
|
+
}
|
|
149
|
+
|
|
141
150
|
/** Converts expression type to string. */
|
|
142
151
|
export function labelType(type: ExpressionType | null): string {
|
|
143
152
|
if (!type) {
|
|
@@ -149,7 +158,7 @@ export function labelType(type: ExpressionType | null): string {
|
|
|
149
158
|
case TypeID.integer:
|
|
150
159
|
return INTEGER_TYPE_NAME;
|
|
151
160
|
case TypeID.basic:
|
|
152
|
-
return type.baseID;
|
|
161
|
+
return labelBaseId(type.baseID);
|
|
153
162
|
case TypeID.tuple:
|
|
154
163
|
return type.factors
|
|
155
164
|
.map(factor => (factor.typeID === TypeID.tuple ? `(${labelType(factor)})` : labelType(factor)))
|
|
@@ -159,12 +159,12 @@ const errorData = [
|
|
|
159
159
|
// Functions
|
|
160
160
|
['F1[X1]', { code: RSErrorCode.invalidArgsArity, from: 0, to: 6, params: ['2', '1'] }],
|
|
161
161
|
['F1[X1, X1, X1]', { code: RSErrorCode.invalidArgsArity, from: 0, to: 14, params: ['2', '3'] }],
|
|
162
|
-
['F1[X1, S1]', { code: RSErrorCode.invalidArgumentType, from: 7, to: 9, params: ['b
|
|
163
|
-
['F1[S1, X1]', { code: RSErrorCode.invalidArgumentType, from: 3, to: 5, params: ['a
|
|
162
|
+
['F1[X1, S1]', { code: RSErrorCode.invalidArgumentType, from: 7, to: 9, params: ['F1<b>∈ℬ(X1)', 'ℬ(X1×X1)'] }],
|
|
163
|
+
['F1[S1, X1]', { code: RSErrorCode.invalidArgumentType, from: 3, to: 5, params: ['F1<a>∈ℬ(X1)', 'ℬ(X1×X1)'] }],
|
|
164
164
|
['P1[X1]', { code: RSErrorCode.invalidArgsArity, from: 0, to: 6, params: ['2', '1'] }],
|
|
165
165
|
['P1[X1, X1, X1]', { code: RSErrorCode.invalidArgsArity, from: 0, to: 14, params: ['2', '3'] }],
|
|
166
|
-
['P1[X1, S1]', { code: RSErrorCode.invalidArgumentType, from: 7, to: 9, params: ['b
|
|
167
|
-
['P1[S1, X1]', { code: RSErrorCode.invalidArgumentType, from: 3, to: 5, params: ['a
|
|
166
|
+
['P1[X1, S1]', { code: RSErrorCode.invalidArgumentType, from: 7, to: 9, params: ['P1<b>∈ℬ(X1)', 'ℬ(X1×X1)'] }],
|
|
167
|
+
['P1[S1, X1]', { code: RSErrorCode.invalidArgumentType, from: 3, to: 5, params: ['P1<a>∈ℬ(X1)', 'ℬ(X1×X1)'] }],
|
|
168
168
|
// Integral
|
|
169
169
|
['card(debool(X1))', { code: RSErrorCode.invalidCard, from: 5, to: 15, params: ['X1'] }],
|
|
170
170
|
['card(S3)', { code: RSErrorCode.invalidCard, from: 5, to: 7, params: ['C1'] }],
|
|
@@ -313,7 +313,26 @@ describe('TypeAuditor', () => {
|
|
|
313
313
|
code: RSErrorCode.invalidArgumentType,
|
|
314
314
|
from: 7,
|
|
315
315
|
to: 12,
|
|
316
|
-
params: ['b
|
|
316
|
+
params: ['F2<b>∈X1', 'ℬℬ(X1)']
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('Qualifies unresolved radicals and parameter aliases in invalidArgumentType message', () => {
|
|
321
|
+
typeContext.set('P44', {
|
|
322
|
+
typeID: TypeID.predicate,
|
|
323
|
+
result: LogicT,
|
|
324
|
+
args: [
|
|
325
|
+
{ alias: 'α', type: bool(tuple([tuple([basic('R1'), basic('R2')]), basic('C1')])) },
|
|
326
|
+
{ alias: 'β', type: bool(tuple([tuple([basic('R2'), basic('R3')]), basic('C1')])) }
|
|
327
|
+
]
|
|
328
|
+
});
|
|
329
|
+
const inner = tuple([basic('X1'), basic('X2')]);
|
|
330
|
+
typeContext.set('S9', bool(tuple([inner, basic('C1')])));
|
|
331
|
+
expectError('P44[S9, S9]', {
|
|
332
|
+
code: RSErrorCode.invalidArgumentType,
|
|
333
|
+
from: 8,
|
|
334
|
+
to: 10,
|
|
335
|
+
params: ['P44<β>∈ℬ((X2×<P44>R3)×C1)', 'ℬ((X1×X2)×C1)']
|
|
317
336
|
});
|
|
318
337
|
});
|
|
319
338
|
|
|
@@ -331,7 +350,7 @@ describe('TypeAuditor', () => {
|
|
|
331
350
|
code: RSErrorCode.invalidArgumentType,
|
|
332
351
|
from: 7,
|
|
333
352
|
to: 9,
|
|
334
|
-
params: ['
|
|
353
|
+
params: ['P2<ζ>∈ℬℬ(X1)', 'Z']
|
|
335
354
|
});
|
|
336
355
|
});
|
|
337
356
|
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
isTemplateRadical,
|
|
21
21
|
isTypification,
|
|
22
22
|
LogicT,
|
|
23
|
+
mangleParamAlias,
|
|
23
24
|
mangleRadicalId,
|
|
24
25
|
type Parametrized,
|
|
25
26
|
tuple,
|
|
@@ -1025,7 +1026,7 @@ export class TypeAuditor {
|
|
|
1025
1026
|
substituteBase(expectedType, substitutes);
|
|
1026
1027
|
}
|
|
1027
1028
|
this.onError(RSErrorCode.invalidArgumentType, node.children[child], [
|
|
1028
|
-
`${type.args[child - 1].alias}∈${labelType(expectedType)}`,
|
|
1029
|
+
`${mangleParamAlias(type.args[child - 1].alias, alias)}∈${labelType(expectedType)}`,
|
|
1029
1030
|
labelType(childType)
|
|
1030
1031
|
]);
|
|
1031
1032
|
return null;
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
3
|
import type { TypePath } from './typification';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
AnyTypificationT,
|
|
6
|
+
basic,
|
|
7
|
+
bool,
|
|
8
|
+
IntegerT,
|
|
9
|
+
mangleParamAlias,
|
|
10
|
+
mangleRadicalId,
|
|
11
|
+
tuple,
|
|
12
|
+
type Typification
|
|
13
|
+
} from './typification';
|
|
5
14
|
import { applyPath, cloneTypification, compareTemplated, substituteBase } from './typification-api';
|
|
6
15
|
|
|
7
16
|
describe('applyPath', () => {
|
|
@@ -56,15 +65,22 @@ describe('compareTemplated', () => {
|
|
|
56
65
|
|
|
57
66
|
describe('mangleRadicalId', () => {
|
|
58
67
|
it('qualifies template radical with function name', () => {
|
|
59
|
-
expect(mangleRadicalId('R1', 'P2')).toBe('R1
|
|
60
|
-
expect(mangleRadicalId('R2', 'F10')).toBe('R2
|
|
68
|
+
expect(mangleRadicalId('R1', 'P2')).toBe('P2<R1>');
|
|
69
|
+
expect(mangleRadicalId('R2', 'F10')).toBe('F10<R2>');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('mangleParamAlias', () => {
|
|
74
|
+
it('qualifies formal parameter alias with calling function name', () => {
|
|
75
|
+
expect(mangleParamAlias('β', 'P44')).toBe('P44<β>');
|
|
76
|
+
expect(mangleParamAlias('a', 'F1')).toBe('F1<a>');
|
|
61
77
|
});
|
|
62
78
|
});
|
|
63
79
|
|
|
64
80
|
describe('cloneTypification + substituteBase', () => {
|
|
65
81
|
it('resolves mangled radicals for error messages', () => {
|
|
66
|
-
const argType = bool(basic('R1
|
|
67
|
-
const substitutes = new Map<string, Typification>([['R1
|
|
82
|
+
const argType = bool(basic('F2<R1>'));
|
|
83
|
+
const substitutes = new Map<string, Typification>([['F2<R1>', basic('X1')]]);
|
|
68
84
|
const expectedType = cloneTypification(argType);
|
|
69
85
|
substituteBase(expectedType, substitutes);
|
|
70
86
|
expect(expectedType).toEqual(bool(basic('X1')));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
|
-
import { labelType } from '../labels';
|
|
3
|
+
import { labelBaseId, labelType } from '../labels';
|
|
4
4
|
|
|
5
5
|
import { TypeID } from './typification';
|
|
6
6
|
import { applyAsciiTypeSubstitutions, parseTypeText } from './typification-parser';
|
|
@@ -47,4 +47,9 @@ describe('parseTypeText', () => {
|
|
|
47
47
|
expect(applyAsciiTypeSubstitutions('X1*X2 -> X3')).toBe('X1×X2 → X3');
|
|
48
48
|
expect(applyAsciiTypeSubstitutions('B(X1)')).toBe('ℬ(X1)');
|
|
49
49
|
});
|
|
50
|
+
|
|
51
|
+
it('displays mangled radicals with function qualifier before radical', () => {
|
|
52
|
+
expect(labelBaseId('P44<R3>')).toBe('<P44>R3');
|
|
53
|
+
expect(labelType({ typeID: TypeID.basic, baseID: 'P2<R1>' })).toBe('<P2>R1');
|
|
54
|
+
});
|
|
50
55
|
});
|
|
@@ -174,17 +174,27 @@ export function isTypification(type: ExpressionType | null): boolean {
|
|
|
174
174
|
);
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
/** Checks if given alias is a template or mangled radical. */
|
|
178
|
-
export function isRadical(alias: string): boolean {
|
|
179
|
-
return /^R[1-9]\d*(?:<[^>]+>)?$/.test(alias);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
177
|
/** Checks if given alias is an unmangled template radical. */
|
|
183
178
|
export function isTemplateRadical(alias: string): boolean {
|
|
184
179
|
return /^R[1-9]\d*$/.test(alias);
|
|
185
180
|
}
|
|
186
181
|
|
|
182
|
+
/** Checks if given alias is a mangled template radical (`funcName<R#>`). */
|
|
183
|
+
export function isMangledRadical(alias: string): boolean {
|
|
184
|
+
return /^[^<]+<R[1-9]\d*>$/.test(alias);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Checks if given alias is a template or mangled radical. */
|
|
188
|
+
export function isRadical(alias: string): boolean {
|
|
189
|
+
return isTemplateRadical(alias) || isMangledRadical(alias);
|
|
190
|
+
}
|
|
191
|
+
|
|
187
192
|
/** Mangle template radical with calling function name. */
|
|
188
193
|
export function mangleRadicalId(radical: string, funcName: string): string {
|
|
189
|
-
return `${
|
|
194
|
+
return `${funcName}<${radical}>`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Mangle formal parameter alias with calling function name (for diagnostics). */
|
|
198
|
+
export function mangleParamAlias(alias: string, funcName: string): string {
|
|
199
|
+
return `${funcName}<${alias}>`;
|
|
190
200
|
}
|