@rsconcept/domain 2.1.2 → 2.3.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.
- package/dist/api-Bf0f_uiI.d.ts +57 -0
- package/dist/diagnostics-CljEzVBV.js +1245 -0
- package/dist/diagnostics-CljEzVBV.js.map +1 -0
- package/dist/{diagnostics-EaIt3zIw.d.ts → diagnostics-Dac5sJhO.d.ts} +4 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/library/diagnostics.d.ts +1 -1
- package/dist/library/diagnostics.js +1 -1
- package/dist/library/index.d.ts +2 -2
- package/dist/library/index.js +1 -1
- package/dist/library/oss-api.d.ts +1 -1
- package/dist/library/oss-api.js +4 -4
- package/dist/library/oss-api.js.map +1 -1
- package/dist/library/rsengine.d.ts +1 -1
- package/dist/library/rsengine.js +2 -2
- package/dist/library/rsform-api/index.d.ts +299 -0
- package/dist/library/rsform-api/index.js +2 -0
- package/dist/library/rsform.d.ts +2 -2
- package/dist/library/rsform.js.map +1 -1
- package/dist/library/rsmodel-api.d.ts +1 -1
- package/dist/library/rsmodel-api.js +4 -2
- package/dist/library/rsmodel-api.js.map +1 -1
- package/dist/library/rsmodel.d.ts +1 -1
- package/dist/library/structure-planner.d.ts +1 -1
- package/dist/library/structure-planner.js +2 -2
- package/dist/rslang/api.d.ts +2 -58
- package/dist/rslang/api.js +1 -1
- package/dist/rslang/index.js +1 -1
- package/dist/rslang/parser/syntax-errors/index.js +1 -1
- package/dist/rslang/semantic/analyzer.js +2 -2
- package/dist/{syntax-errors-DwPXhpVe.js → syntax-errors-BN_bqTaX.js} +2 -2
- package/dist/{syntax-errors-DwPXhpVe.js.map → syntax-errors-BN_bqTaX.js.map} +1 -1
- package/package.json +3 -3
- package/src/index.ts +1 -0
- package/src/library/index.ts +1 -0
- package/src/library/oss-api.ts +5 -2
- package/src/library/rsform-api/alias.ts +160 -0
- package/src/library/rsform-api/analysis.ts +157 -0
- package/src/library/rsform-api/attribution.ts +87 -0
- package/src/library/rsform-api/cst-type.ts +216 -0
- package/src/library/rsform-api/expression.ts +73 -0
- package/src/library/rsform-api/formal-graph.test.ts +72 -0
- package/src/library/rsform-api/formal-graph.ts +195 -0
- package/src/library/rsform-api/index.ts +53 -0
- package/src/library/rsform-api/semantic-relations.ts +117 -0
- package/src/library/rsform-api/stats.ts +170 -0
- package/src/library/rsform-api/structure.ts +93 -0
- package/src/library/rsform-api/substitution.ts +82 -0
- package/src/library/rsform-api/synthesis.test.ts +126 -0
- package/src/library/rsform-api/synthesis.ts +166 -0
- package/src/library/rsform-api/text-resolution.test.ts +86 -0
- package/src/library/rsform-api/text-resolution.ts +152 -0
- package/src/library/rsform-api/transforms.test.ts +161 -0
- package/src/library/rsform-api/transforms.ts +42 -0
- package/src/library/rsform-api/types.ts +38 -0
- package/src/library/rsform.ts +3 -3
- package/src/library/rsmodel-api.ts +7 -1
- package/dist/diagnostics-CY9sKggB.js +0 -658
- package/dist/diagnostics-CY9sKggB.js.map +0 -1
- package/dist/library/rsform-api.d.ts +0 -103
- package/dist/library/rsform-api.js +0 -2
- package/src/library/rsform-api.ts +0 -755
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formal expression analysis against an RSForm schema.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type Graph } from '../../graph';
|
|
6
|
+
import { type AnalysisFull, RSErrorCode, ValueClass } from '../../rslang';
|
|
7
|
+
import { extractGlobals } from '../../rslang/api';
|
|
8
|
+
import { basic, bool, constant } from '../../rslang/semantic/typification';
|
|
9
|
+
import { formatDependencyCycle, RSDiagnosticCode } from '../diagnostics';
|
|
10
|
+
import { CstType, type RSForm } from '../rsform';
|
|
11
|
+
|
|
12
|
+
import { canHaveFormalDefinition, typeClassForCstType } from './cst-type';
|
|
13
|
+
|
|
14
|
+
export function getAnalysisFor(expression: string, cstType: CstType, schema: RSForm, alias?: string): AnalysisFull {
|
|
15
|
+
if (!canHaveFormalDefinition(cstType)) {
|
|
16
|
+
if (expression.trim().length === 0) {
|
|
17
|
+
const fallbackAlias = alias && alias.length > 0 ? alias : 'X0';
|
|
18
|
+
const type = cstType === CstType.BASE ? bool(basic(fallbackAlias)) : bool(constant(fallbackAlias));
|
|
19
|
+
return {
|
|
20
|
+
success: true,
|
|
21
|
+
type,
|
|
22
|
+
valueClass: ValueClass.VALUE,
|
|
23
|
+
errors: [],
|
|
24
|
+
ast: null
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
success: false,
|
|
29
|
+
type: null,
|
|
30
|
+
valueClass: null,
|
|
31
|
+
errors: [
|
|
32
|
+
{
|
|
33
|
+
code: RSErrorCode.definitionNotAllowed,
|
|
34
|
+
from: 0,
|
|
35
|
+
to: Math.max(expression.length, 1),
|
|
36
|
+
params: [cstType, alias ?? '']
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
ast: null
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const analysis = schema.analyzer.checkFull(expression, {
|
|
43
|
+
expected: typeClassForCstType(cstType),
|
|
44
|
+
isDomain: cstType === CstType.STRUCTURED
|
|
45
|
+
});
|
|
46
|
+
return refineAnalysisForDependencyCycles(analysis, expression, schema, alias);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* When an untyped / bare F·P reference names a constituent on a definition cycle with the
|
|
51
|
+
* current expression, replace that error with {@link RSDiagnosticCode.schemaDependencyCycle}.
|
|
52
|
+
*
|
|
53
|
+
* Covers both semantic `globalNotTyped` (e.g. `F3[σ]`) and parser `globalFuncWithoutArgs`
|
|
54
|
+
* (bare `P1` / `F1` treated as a call without arguments).
|
|
55
|
+
*
|
|
56
|
+
* Uses a draft dependency graph: incoming edges of the edited constituent are taken from
|
|
57
|
+
* `expression`, so Check works before Save.
|
|
58
|
+
*/
|
|
59
|
+
export function refineAnalysisForDependencyCycles(
|
|
60
|
+
analysis: AnalysisFull,
|
|
61
|
+
expression: string,
|
|
62
|
+
schema: RSForm,
|
|
63
|
+
alias?: string
|
|
64
|
+
): AnalysisFull {
|
|
65
|
+
if (analysis.errors.length === 0 || !schema.graph) {
|
|
66
|
+
return analysis;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const currentId = alias ? schema.cstByAlias.get(alias)?.id : undefined;
|
|
70
|
+
const expressionGlobals = extractGlobals(expression);
|
|
71
|
+
const draftGraph =
|
|
72
|
+
currentId !== undefined ? buildDraftDependencyGraph(schema, currentId, expressionGlobals) : schema.graph;
|
|
73
|
+
|
|
74
|
+
let changed = false;
|
|
75
|
+
const errors = analysis.errors.map(error => {
|
|
76
|
+
if (error.code !== RSErrorCode.globalNotTyped && error.code !== RSErrorCode.globalFuncWithoutArgs) {
|
|
77
|
+
return error;
|
|
78
|
+
}
|
|
79
|
+
const missingAlias = error.params?.[0];
|
|
80
|
+
if (!missingAlias) {
|
|
81
|
+
return error;
|
|
82
|
+
}
|
|
83
|
+
const missing = schema.cstByAlias.get(missingAlias);
|
|
84
|
+
if (!missing) {
|
|
85
|
+
return error;
|
|
86
|
+
}
|
|
87
|
+
const cycleText = resolveDependencyCycleText(schema, draftGraph, currentId, missing.id);
|
|
88
|
+
if (!cycleText) {
|
|
89
|
+
return error;
|
|
90
|
+
}
|
|
91
|
+
changed = true;
|
|
92
|
+
return {
|
|
93
|
+
...error,
|
|
94
|
+
code: RSErrorCode.schemaDependencyCycle,
|
|
95
|
+
params: [cycleText]
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
return changed ? { ...analysis, errors } : analysis;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Clone the schema graph and replace dependencies of `currentId` with globals from the
|
|
103
|
+
* expression under check (unsaved draft).
|
|
104
|
+
*/
|
|
105
|
+
function buildDraftDependencyGraph(schema: RSForm, currentId: number, expressionGlobals: ReadonlySet<string>): Graph {
|
|
106
|
+
const graph = schema.graph.clone();
|
|
107
|
+
const node = graph.at(currentId);
|
|
108
|
+
if (node) {
|
|
109
|
+
for (const parentId of [...node.inputs]) {
|
|
110
|
+
graph.removeEdge(parentId, currentId);
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
graph.addNode(currentId);
|
|
114
|
+
}
|
|
115
|
+
for (const globalAlias of expressionGlobals) {
|
|
116
|
+
const source = schema.cstByAlias.get(globalAlias);
|
|
117
|
+
if (source) {
|
|
118
|
+
graph.addEdge(source.id, currentId);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return graph;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Resolve formatted cycle text for an untyped global reference in analysis.
|
|
126
|
+
*
|
|
127
|
+
* Uses `graph` (often a draft graph built from the expression under check). When
|
|
128
|
+
* `currentId` is omitted, falls back to schema diagnostics on `missingId`.
|
|
129
|
+
*
|
|
130
|
+
* @returns Closed alias path such as `F3 → P1 → F3`, or `null` when no shared cycle exists.
|
|
131
|
+
*/
|
|
132
|
+
function resolveDependencyCycleText(
|
|
133
|
+
schema: RSForm,
|
|
134
|
+
graph: Graph,
|
|
135
|
+
currentId: number | undefined,
|
|
136
|
+
missingId: number
|
|
137
|
+
): string | null {
|
|
138
|
+
for (const cycle of graph.findCycles()) {
|
|
139
|
+
const members = new Set(cycle);
|
|
140
|
+
if (!members.has(missingId)) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (currentId !== undefined && !members.has(currentId)) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
return formatDependencyCycle(cycle.map(id => schema.cstByID.get(id)?.alias ?? String(id)));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (currentId === undefined) {
|
|
150
|
+
const cycleDiag = schema.cstByID
|
|
151
|
+
.get(missingId)
|
|
152
|
+
?.diagnostics.find(item => item.code === RSDiagnosticCode.schemaDependencyCycle);
|
|
153
|
+
return cycleDiag?.params?.[0] ?? null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attribution remapping helpers for substitution and schema embedding.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type Attribution } from '../rsform';
|
|
6
|
+
|
|
7
|
+
function attributionKey(attr: Attribution): string {
|
|
8
|
+
return `${attr.container}:${attr.attribute}`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Remap attributions when constituents are substituted (original → replacement).
|
|
13
|
+
* Drops self-loops and duplicates. Matches sandbox / backend substitute attribution rewrite.
|
|
14
|
+
*/
|
|
15
|
+
export function remapAttributionsUnderSubstitution(
|
|
16
|
+
attributions: readonly Attribution[],
|
|
17
|
+
origToSub: ReadonlyMap<number, number>
|
|
18
|
+
): Attribution[] {
|
|
19
|
+
const result: Attribution[] = [];
|
|
20
|
+
const seen = new Set<string>();
|
|
21
|
+
|
|
22
|
+
for (const attr of attributions) {
|
|
23
|
+
if (!origToSub.has(attr.container) && !origToSub.has(attr.attribute)) {
|
|
24
|
+
const key = attributionKey(attr);
|
|
25
|
+
if (!seen.has(key)) {
|
|
26
|
+
seen.add(key);
|
|
27
|
+
result.push({ ...attr });
|
|
28
|
+
}
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const container = origToSub.get(attr.container) ?? attr.container;
|
|
33
|
+
const attribute = origToSub.get(attr.attribute) ?? attr.attribute;
|
|
34
|
+
if (container === attribute) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const key = `${container}:${attribute}`;
|
|
38
|
+
if (seen.has(key)) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
seen.add(key);
|
|
42
|
+
result.push({ container, attribute });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Copy attributions whose both endpoints are in {@link sourceIdSet}, remapping ids via {@link idMap}.
|
|
50
|
+
* Appends onto a clone of {@link existing} with dedupe. Used by schema embedding / insert.
|
|
51
|
+
*/
|
|
52
|
+
export function importAttributions(
|
|
53
|
+
sourceAttributions: readonly Attribution[],
|
|
54
|
+
sourceIdSet: ReadonlySet<number>,
|
|
55
|
+
idMap: Readonly<Record<number, number>>,
|
|
56
|
+
existing: readonly Attribution[] = []
|
|
57
|
+
): Attribution[] {
|
|
58
|
+
const result = existing.map(attr => ({ ...attr }));
|
|
59
|
+
const seen = new Set(result.map(attributionKey));
|
|
60
|
+
|
|
61
|
+
for (const attr of sourceAttributions) {
|
|
62
|
+
if (!sourceIdSet.has(attr.container) || !sourceIdSet.has(attr.attribute)) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const container = idMap[attr.container];
|
|
66
|
+
const attribute = idMap[attr.attribute];
|
|
67
|
+
if (container === undefined || attribute === undefined || container === attribute) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const key = `${container}:${attribute}`;
|
|
71
|
+
if (seen.has(key)) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
seen.add(key);
|
|
75
|
+
result.push({ container, attribute });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Drop attributions that reference any id in {@link deletedIds}. */
|
|
82
|
+
export function filterAttributions(
|
|
83
|
+
attributions: readonly Attribution[],
|
|
84
|
+
deletedIds: ReadonlySet<number>
|
|
85
|
+
): Attribution[] {
|
|
86
|
+
return attributions.filter(attr => !deletedIds.has(attr.container) && !deletedIds.has(attr.attribute));
|
|
87
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CstType classification, prefixes, and capability predicates.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { TypeClass, TypeID, type Typification } from '../../rslang';
|
|
6
|
+
import { type EchelonFunctional, isTypification } from '../../rslang/semantic/typification';
|
|
7
|
+
import { type Constituenta, CstClass, CstType } from '../rsform';
|
|
8
|
+
|
|
9
|
+
/** Record of {@link CstType} prefixes. */
|
|
10
|
+
const CST_TYPE_PREFIX: Record<CstType, string> = {
|
|
11
|
+
[CstType.NOMINAL]: 'N',
|
|
12
|
+
[CstType.BASE]: 'X',
|
|
13
|
+
[CstType.CONSTANT]: 'C',
|
|
14
|
+
[CstType.STRUCTURED]: 'S',
|
|
15
|
+
[CstType.AXIOM]: 'A',
|
|
16
|
+
[CstType.TERM]: 'D',
|
|
17
|
+
[CstType.FUNCTION]: 'F',
|
|
18
|
+
[CstType.PREDICATE]: 'P',
|
|
19
|
+
[CstType.STATEMENT]: 'T'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** Record of {@link CstType} to {@link CstClass} mapping. */
|
|
23
|
+
const CST_TYPE_TO_CLASS: Record<CstType, CstClass> = {
|
|
24
|
+
[CstType.NOMINAL]: CstClass.NOMINAL,
|
|
25
|
+
[CstType.BASE]: CstClass.BASIC,
|
|
26
|
+
[CstType.CONSTANT]: CstClass.BASIC,
|
|
27
|
+
[CstType.STRUCTURED]: CstClass.BASIC,
|
|
28
|
+
[CstType.TERM]: CstClass.DERIVED,
|
|
29
|
+
[CstType.FUNCTION]: CstClass.DERIVED,
|
|
30
|
+
[CstType.AXIOM]: CstClass.STATEMENT,
|
|
31
|
+
[CstType.PREDICATE]: CstClass.DERIVED,
|
|
32
|
+
[CstType.STATEMENT]: CstClass.STATEMENT
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Prefix for alias indicating {@link CstType}. */
|
|
36
|
+
export function getCstTypePrefix(type: CstType): string {
|
|
37
|
+
return CST_TYPE_PREFIX[type];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Guess {@link CstType} from user input hint. */
|
|
41
|
+
export function guessCstType(hint: string): CstType | null {
|
|
42
|
+
if (hint.length !== 1) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
for (const [type, prefix] of Object.entries(CST_TYPE_PREFIX)) {
|
|
46
|
+
if (hint === prefix) {
|
|
47
|
+
return type as CstType;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Infers the {@link CstClass} based on the provided {@link CstType} and template status. */
|
|
54
|
+
export function inferClass(type: CstType, isTemplate: boolean = false): CstClass {
|
|
55
|
+
if (isTemplate) {
|
|
56
|
+
return CstClass.TEMPLATE;
|
|
57
|
+
}
|
|
58
|
+
return CST_TYPE_TO_CLASS[type];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Check if {@link Constituenta} is a template or a category. */
|
|
62
|
+
export function isTemplateCst(cst: Constituenta): boolean {
|
|
63
|
+
return cst.cst_type === CstType.FUNCTION || cst.cst_type === CstType.PREDICATE || cst.cst_type === CstType.STATEMENT;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Evaluate if {@link CstType} is basic concept. */
|
|
67
|
+
export function isBasicConcept(type: CstType): boolean {
|
|
68
|
+
switch (type) {
|
|
69
|
+
case CstType.NOMINAL:
|
|
70
|
+
case CstType.BASE:
|
|
71
|
+
case CstType.CONSTANT:
|
|
72
|
+
case CstType.STRUCTURED:
|
|
73
|
+
case CstType.AXIOM:
|
|
74
|
+
return true;
|
|
75
|
+
|
|
76
|
+
case CstType.TERM:
|
|
77
|
+
case CstType.FUNCTION:
|
|
78
|
+
case CstType.PREDICATE:
|
|
79
|
+
case CstType.STATEMENT:
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Evaluate if {@link CstType} is base set or constant set. */
|
|
85
|
+
export function isBaseSet(type: CstType): boolean {
|
|
86
|
+
switch (type) {
|
|
87
|
+
case CstType.BASE:
|
|
88
|
+
case CstType.CONSTANT:
|
|
89
|
+
return true;
|
|
90
|
+
|
|
91
|
+
case CstType.NOMINAL:
|
|
92
|
+
case CstType.STRUCTURED:
|
|
93
|
+
case CstType.AXIOM:
|
|
94
|
+
case CstType.TERM:
|
|
95
|
+
case CstType.FUNCTION:
|
|
96
|
+
case CstType.PREDICATE:
|
|
97
|
+
case CstType.STATEMENT:
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Evaluate if {@link CstType} is a function. */
|
|
103
|
+
export function isFunctional(type: CstType): boolean {
|
|
104
|
+
switch (type) {
|
|
105
|
+
case CstType.FUNCTION:
|
|
106
|
+
case CstType.PREDICATE:
|
|
107
|
+
return true;
|
|
108
|
+
|
|
109
|
+
case CstType.NOMINAL:
|
|
110
|
+
case CstType.BASE:
|
|
111
|
+
case CstType.CONSTANT:
|
|
112
|
+
case CstType.STRUCTURED:
|
|
113
|
+
case CstType.AXIOM:
|
|
114
|
+
case CstType.TERM:
|
|
115
|
+
case CstType.STATEMENT:
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Evaluate if {@link CstType} is logical. */
|
|
121
|
+
export function isLogical(type: CstType): boolean {
|
|
122
|
+
switch (type) {
|
|
123
|
+
case CstType.AXIOM:
|
|
124
|
+
case CstType.STATEMENT:
|
|
125
|
+
return true;
|
|
126
|
+
|
|
127
|
+
case CstType.NOMINAL:
|
|
128
|
+
case CstType.BASE:
|
|
129
|
+
case CstType.CONSTANT:
|
|
130
|
+
case CstType.STRUCTURED:
|
|
131
|
+
case CstType.TERM:
|
|
132
|
+
case CstType.FUNCTION:
|
|
133
|
+
case CstType.PREDICATE:
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Evaluate if {@link CstType} can have manual typification. */
|
|
139
|
+
export function canHaveManualTypification(type: CstType): boolean {
|
|
140
|
+
switch (type) {
|
|
141
|
+
case CstType.STRUCTURED:
|
|
142
|
+
case CstType.TERM:
|
|
143
|
+
case CstType.FUNCTION:
|
|
144
|
+
case CstType.PREDICATE:
|
|
145
|
+
return true;
|
|
146
|
+
|
|
147
|
+
case CstType.NOMINAL:
|
|
148
|
+
case CstType.BASE:
|
|
149
|
+
case CstType.CONSTANT:
|
|
150
|
+
case CstType.AXIOM:
|
|
151
|
+
case CstType.STATEMENT:
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Checks whether a constituenta type supports formal definitions. */
|
|
157
|
+
export function canHaveFormalDefinition(cstType: CstType): boolean {
|
|
158
|
+
return cstType !== CstType.BASE && cstType !== CstType.CONSTANT;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Returns expected {@link TypeClass} of formal definition for {@link CstType}. */
|
|
162
|
+
export function typeClassForCstType(cstType: CstType): TypeClass {
|
|
163
|
+
switch (cstType) {
|
|
164
|
+
case CstType.NOMINAL:
|
|
165
|
+
case CstType.BASE:
|
|
166
|
+
case CstType.CONSTANT:
|
|
167
|
+
case CstType.STRUCTURED:
|
|
168
|
+
case CstType.TERM:
|
|
169
|
+
return TypeClass.typification;
|
|
170
|
+
case CstType.FUNCTION:
|
|
171
|
+
return TypeClass.function;
|
|
172
|
+
case CstType.PREDICATE:
|
|
173
|
+
return TypeClass.predicate;
|
|
174
|
+
case CstType.AXIOM:
|
|
175
|
+
case CstType.STATEMENT:
|
|
176
|
+
return TypeClass.logic;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Evaluate if {@link Constituenta} can be used produce structure. */
|
|
181
|
+
export function canProduceStructure(cst: Constituenta): boolean {
|
|
182
|
+
switch (cst.cst_type) {
|
|
183
|
+
case CstType.NOMINAL:
|
|
184
|
+
case CstType.BASE:
|
|
185
|
+
case CstType.CONSTANT:
|
|
186
|
+
case CstType.AXIOM:
|
|
187
|
+
case CstType.STATEMENT:
|
|
188
|
+
case CstType.PREDICATE:
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
if (!cst.effectiveType) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
if (cst.cst_type === CstType.FUNCTION) {
|
|
195
|
+
const result = (cst.effectiveType as EchelonFunctional).result;
|
|
196
|
+
return typeCanProduceStructure(result);
|
|
197
|
+
}
|
|
198
|
+
if (!isTypification(cst.effectiveType)) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
return typeCanProduceStructure(cst.effectiveType as Typification);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function typeCanProduceStructure(type: Typification): boolean {
|
|
205
|
+
if (type.typeID === TypeID.basic || type.typeID === TypeID.integer || type.typeID === TypeID.anyTypification) {
|
|
206
|
+
return false;
|
|
207
|
+
} else if (type.typeID === TypeID.tuple) {
|
|
208
|
+
return true;
|
|
209
|
+
} else {
|
|
210
|
+
return (
|
|
211
|
+
type.base.typeID !== TypeID.basic &&
|
|
212
|
+
type.base.typeID !== TypeID.integer &&
|
|
213
|
+
type.base.typeID !== TypeID.anyTypification
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formal expression helpers: normalization, templates, status.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type AstNode } from '../../parsing';
|
|
6
|
+
import { ValueClass } from '../../rslang';
|
|
7
|
+
import { generateExpressionFromAst, parseRSLangExpression } from '../../rslang/api';
|
|
8
|
+
import { type ArgumentValue, type Constituenta, CstStatus, CstType } from '../rsform';
|
|
9
|
+
|
|
10
|
+
import { isBasicConcept } from './cst-type';
|
|
11
|
+
|
|
12
|
+
/** Infers the status of an expression based on parsing and value information. */
|
|
13
|
+
export function inferStatus(parse: boolean, value?: ValueClass | null): CstStatus {
|
|
14
|
+
if (!parse) {
|
|
15
|
+
return CstStatus.INCORRECT;
|
|
16
|
+
}
|
|
17
|
+
if (value === null) {
|
|
18
|
+
return CstStatus.INCALCULABLE;
|
|
19
|
+
}
|
|
20
|
+
if (value === ValueClass.PROPERTY) {
|
|
21
|
+
return CstStatus.PROPERTY;
|
|
22
|
+
}
|
|
23
|
+
return CstStatus.VERIFIED;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Infers type of constituent for a given template and arguments. */
|
|
27
|
+
export function inferTemplatedType(templateType: CstType, args: ArgumentValue[]): CstType {
|
|
28
|
+
if (args.length === 0 || args.some(arg => !arg.value)) {
|
|
29
|
+
return templateType;
|
|
30
|
+
} else if (templateType === CstType.PREDICATE) {
|
|
31
|
+
return CstType.AXIOM;
|
|
32
|
+
} else {
|
|
33
|
+
return CstType.TERM;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Normalize a RSLang expression for duplicate comparison.
|
|
39
|
+
*
|
|
40
|
+
* Returns `null` for basic concepts without formal definitions. Pass a pre-parsed `ast`
|
|
41
|
+
* when the expression was already analyzed to avoid re-parsing.
|
|
42
|
+
*/
|
|
43
|
+
export function normalizeExpression(expression: string, cst_type: CstType, ast?: AstNode | null): string | null {
|
|
44
|
+
if (isBasicConcept(cst_type) && cst_type !== CstType.AXIOM) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const parsed = ast ?? parseRSLangExpression(expression);
|
|
48
|
+
let normalized = '';
|
|
49
|
+
if (parsed && !parsed.hasError) {
|
|
50
|
+
normalized = generateExpressionFromAst(parsed, { normalize: true });
|
|
51
|
+
} else {
|
|
52
|
+
normalized = expression;
|
|
53
|
+
}
|
|
54
|
+
normalized = normalized.replace(/\s+/g, '');
|
|
55
|
+
return normalized === '' ? null : normalized;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Checks if given expression is a template. */
|
|
59
|
+
export function inferTemplate(expression: string): boolean {
|
|
60
|
+
const match = expression.match(/R\d+/g);
|
|
61
|
+
return (match && match?.length > 0) ?? false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Apply filter based on start {@link Constituenta} type. */
|
|
65
|
+
export function applyFilterCategory(start: Constituenta, items: Constituenta[]): Constituenta[] {
|
|
66
|
+
const startIndex = items.indexOf(start);
|
|
67
|
+
if (startIndex === -1) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
const nextCategoryIndex = items.findIndex((cst, index) => index > startIndex && cst.cst_type === CstType.STATEMENT);
|
|
71
|
+
|
|
72
|
+
return items.filter((_, index) => index >= startIndex && (nextCategoryIndex === -1 || index < nextCategoryIndex));
|
|
73
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { CstType } from '../rsform';
|
|
4
|
+
|
|
5
|
+
import { buildFormalDependencyGraph, restoreConstituentOrder } from './formal-graph';
|
|
6
|
+
import { computeSemanticRelations } from './semantic-relations';
|
|
7
|
+
import { type OrderableConstituenta } from './types';
|
|
8
|
+
|
|
9
|
+
function cst(id: number, alias: string, cst_type: CstType, definition_formal = ''): OrderableConstituenta {
|
|
10
|
+
return { id, alias, cst_type, definition_formal };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe('restoreConstituentOrder', () => {
|
|
14
|
+
it('keeps short lists unchanged', () => {
|
|
15
|
+
expect(restoreConstituentOrder([])).toEqual([]);
|
|
16
|
+
const only = [cst(1, 'X1', CstType.BASE)];
|
|
17
|
+
expect(restoreConstituentOrder(only)).toEqual(only);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('matches classic kernel order with sticky semantic child', () => {
|
|
21
|
+
const items = [
|
|
22
|
+
cst(1, 'D2', CstType.TERM, 'D{ξ∈S1 | 1=1}'),
|
|
23
|
+
cst(2, 'D1', CstType.TERM, 'Pr1(S1)\\X1'),
|
|
24
|
+
cst(3, 'X1', CstType.BASE),
|
|
25
|
+
cst(4, 'X2', CstType.BASE),
|
|
26
|
+
cst(5, 'S1', CstType.STRUCTURED, 'ℬ(X1×X1)'),
|
|
27
|
+
cst(6, 'C1', CstType.CONSTANT),
|
|
28
|
+
cst(7, 'S2', CstType.STRUCTURED, 'ℬ(X2×D1)'),
|
|
29
|
+
cst(8, 'A1', CstType.AXIOM, 'D3=∅'),
|
|
30
|
+
cst(9, 'A2', CstType.AXIOM, 'P1[S1]'),
|
|
31
|
+
cst(10, 'D3', CstType.TERM, 'Pr2(S2)'),
|
|
32
|
+
cst(11, 'F1', CstType.FUNCTION, '[α∈ℬ(X1)] D{σ∈S1 | α⊆pr1(σ)}'),
|
|
33
|
+
cst(12, 'D4', CstType.TERM, 'Pr2(D3)'),
|
|
34
|
+
cst(13, 'F2', CstType.FUNCTION, '[α∈ℬ(X1)] X1\\α'),
|
|
35
|
+
cst(14, 'P1', CstType.PREDICATE, '[α∈ℬ(R1)] card(α)=0')
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const ordered = restoreConstituentOrder(items).map(item => item.alias);
|
|
39
|
+
expect(ordered).toEqual(['X1', 'X2', 'C1', 'P1', 'S1', 'A2', 'D1', 'S2', 'D3', 'A1', 'D4', 'D2', 'F1', 'F2']);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('keeps formal supplier before semantic child (schema 875 pattern)', () => {
|
|
43
|
+
const items = [
|
|
44
|
+
cst(1, 'X1', CstType.BASE),
|
|
45
|
+
cst(2, 'C1', CstType.CONSTANT),
|
|
46
|
+
cst(3, 'P2', CstType.PREDICATE, '[σ∈ℬ(C1)] card(σ)=0'),
|
|
47
|
+
cst(4, 'P6', CstType.PREDICATE, '[σ∈ℬℬ(C1)] ∀ξ∈σ P2[ξ]'),
|
|
48
|
+
cst(5, 'F7', CstType.FUNCTION, '[σ∈ℬ(R1×R2)] Pr1(σ)'),
|
|
49
|
+
cst(6, 'F9', CstType.FUNCTION, '[β∈ℬ(R1×R2), α∈F7[β]] pr2(debool(Fi1[{α}](β)))'),
|
|
50
|
+
cst(7, 'F3', CstType.FUNCTION, '[σ∈D{σ0∈ℬ(ℬ(R1)×ℬ(C1)) | P6[pr2(σ0)]}] F7[σ]'),
|
|
51
|
+
cst(8, 'F4', CstType.FUNCTION, '[β∈D{β0∈ℬ(ℬ(R1)×ℬ(C1)) | P6[pr2(β0)]}, α∈F3[β]] F9[β,α]'),
|
|
52
|
+
cst(9, 'S1', CstType.STRUCTURED, 'ℬ(ℬ(X1)×ℬ(C1))'),
|
|
53
|
+
cst(10, 'A1', CstType.AXIOM, 'P6[ℬ(C1)]')
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const ordered = restoreConstituentOrder(items);
|
|
57
|
+
const aliases = ordered.map(item => item.alias);
|
|
58
|
+
expect(aliases.indexOf('P6')).toBeLessThan(aliases.indexOf('F3'));
|
|
59
|
+
expect(aliases.indexOf('F9')).toBeLessThan(aliases.indexOf('F4'));
|
|
60
|
+
|
|
61
|
+
const relations = computeSemanticRelations(items, buildFormalDependencyGraph(items));
|
|
62
|
+
expect(relations.parentOf.get(8)).toBe(4); // F4 → P6
|
|
63
|
+
expect(relations.childrenOf.get(4)).toEqual(expect.arrayContaining([7, 8]));
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('builds supplier → dependent formal edges', () => {
|
|
67
|
+
const items = [cst(1, 'X1', CstType.BASE), cst(2, 'D1', CstType.TERM, 'ℬ(X1)')];
|
|
68
|
+
const graph = buildFormalDependencyGraph(items);
|
|
69
|
+
expect(graph.hasEdge(1, 2)).toBe(true);
|
|
70
|
+
expect(graph.hasEdge(2, 1)).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
});
|