@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.
Files changed (62) hide show
  1. package/dist/api-Bf0f_uiI.d.ts +57 -0
  2. package/dist/diagnostics-CljEzVBV.js +1245 -0
  3. package/dist/diagnostics-CljEzVBV.js.map +1 -0
  4. package/dist/{diagnostics-EaIt3zIw.d.ts → diagnostics-Dac5sJhO.d.ts} +4 -4
  5. package/dist/index.d.ts +2 -2
  6. package/dist/index.js +3 -3
  7. package/dist/library/diagnostics.d.ts +1 -1
  8. package/dist/library/diagnostics.js +1 -1
  9. package/dist/library/index.d.ts +2 -2
  10. package/dist/library/index.js +1 -1
  11. package/dist/library/oss-api.d.ts +1 -1
  12. package/dist/library/oss-api.js +4 -4
  13. package/dist/library/oss-api.js.map +1 -1
  14. package/dist/library/rsengine.d.ts +1 -1
  15. package/dist/library/rsengine.js +2 -2
  16. package/dist/library/rsform-api/index.d.ts +299 -0
  17. package/dist/library/rsform-api/index.js +2 -0
  18. package/dist/library/rsform.d.ts +2 -2
  19. package/dist/library/rsform.js.map +1 -1
  20. package/dist/library/rsmodel-api.d.ts +1 -1
  21. package/dist/library/rsmodel-api.js +4 -2
  22. package/dist/library/rsmodel-api.js.map +1 -1
  23. package/dist/library/rsmodel.d.ts +1 -1
  24. package/dist/library/structure-planner.d.ts +1 -1
  25. package/dist/library/structure-planner.js +2 -2
  26. package/dist/rslang/api.d.ts +2 -58
  27. package/dist/rslang/api.js +1 -1
  28. package/dist/rslang/index.js +1 -1
  29. package/dist/rslang/parser/syntax-errors/index.js +1 -1
  30. package/dist/rslang/semantic/analyzer.js +2 -2
  31. package/dist/{syntax-errors-DwPXhpVe.js → syntax-errors-BN_bqTaX.js} +2 -2
  32. package/dist/{syntax-errors-DwPXhpVe.js.map → syntax-errors-BN_bqTaX.js.map} +1 -1
  33. package/package.json +3 -3
  34. package/src/index.ts +1 -0
  35. package/src/library/index.ts +1 -0
  36. package/src/library/oss-api.ts +5 -2
  37. package/src/library/rsform-api/alias.ts +160 -0
  38. package/src/library/rsform-api/analysis.ts +157 -0
  39. package/src/library/rsform-api/attribution.ts +87 -0
  40. package/src/library/rsform-api/cst-type.ts +216 -0
  41. package/src/library/rsform-api/expression.ts +73 -0
  42. package/src/library/rsform-api/formal-graph.test.ts +72 -0
  43. package/src/library/rsform-api/formal-graph.ts +195 -0
  44. package/src/library/rsform-api/index.ts +53 -0
  45. package/src/library/rsform-api/semantic-relations.ts +117 -0
  46. package/src/library/rsform-api/stats.ts +170 -0
  47. package/src/library/rsform-api/structure.ts +93 -0
  48. package/src/library/rsform-api/substitution.ts +82 -0
  49. package/src/library/rsform-api/synthesis.test.ts +126 -0
  50. package/src/library/rsform-api/synthesis.ts +166 -0
  51. package/src/library/rsform-api/text-resolution.test.ts +86 -0
  52. package/src/library/rsform-api/text-resolution.ts +152 -0
  53. package/src/library/rsform-api/transforms.test.ts +161 -0
  54. package/src/library/rsform-api/transforms.ts +42 -0
  55. package/src/library/rsform-api/types.ts +38 -0
  56. package/src/library/rsform.ts +3 -3
  57. package/src/library/rsmodel-api.ts +7 -1
  58. package/dist/diagnostics-CY9sKggB.js +0 -658
  59. package/dist/diagnostics-CY9sKggB.js.map +0 -1
  60. package/dist/library/rsform-api.d.ts +0 -103
  61. package/dist/library/rsform-api.js +0 -2
  62. package/src/library/rsform-api.ts +0 -755
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Formal dependency graph and declaration-order restore for RSForm constituents.
3
+ */
4
+
5
+ import { Graph } from '../../graph';
6
+ import { extractGlobals } from '../../rslang/api';
7
+ import { CstType } from '../rsform';
8
+
9
+ import { computeSemanticRelations } from './semantic-relations';
10
+ import { type OrderableConstituenta, type SemanticRelations } from './types';
11
+
12
+ /**
13
+ * Build the formal dependency graph: supplier → dependent.
14
+ * Matches backend {@code RSForm.graph_formal}.
15
+ */
16
+ export function buildFormalDependencyGraph(items: readonly OrderableConstituenta[]): Graph<number> {
17
+ const graph = new Graph<number>();
18
+ const byAlias = new Map(items.map(cst => [cst.alias, cst]));
19
+ for (const cst of items) {
20
+ graph.addNode(cst.id);
21
+ }
22
+ for (const cst of items) {
23
+ for (const alias of extractGlobals(cst.definition_formal)) {
24
+ const supplier = byAlias.get(alias);
25
+ if (supplier) {
26
+ graph.addEdge(supplier.id, cst.id);
27
+ }
28
+ }
29
+ }
30
+ return graph;
31
+ }
32
+
33
+ /**
34
+ * Reorder constituents: formal topology hard, semantic children sticky when ready,
35
+ * otherwise stable against type/kernel preferred ranks.
36
+ *
37
+ * One Kahn pass over the formal dependency graph.
38
+ */
39
+ export function restoreConstituentOrder<T extends OrderableConstituenta>(
40
+ items: readonly T[],
41
+ graph: Graph<number> = buildFormalDependencyGraph(items),
42
+ relations: SemanticRelations = computeSemanticRelations(items, graph)
43
+ ): T[] {
44
+ if (items.length <= 1) {
45
+ return [...items];
46
+ }
47
+
48
+ const byId = new Map(items.map(cst => [cst.id, cst]));
49
+ const preferred = kernelPriority(items, graph, relations.parentOf);
50
+ const rank = new Map(preferred.map((cst, index) => [cst.id, index]));
51
+
52
+ const remainingInputs = new Map<number, number>();
53
+ for (const cst of preferred) {
54
+ const inputs = graph.at(cst.id)?.inputs ?? [];
55
+ remainingInputs.set(
56
+ cst.id,
57
+ inputs.reduce((count, src) => count + (rank.has(src) ? 1 : 0), 0)
58
+ );
59
+ }
60
+
61
+ const ready = new Set<number>();
62
+ for (const cst of preferred) {
63
+ if ((remainingInputs.get(cst.id) ?? 0) === 0) {
64
+ ready.add(cst.id);
65
+ }
66
+ }
67
+
68
+ const children = new Map<number, number[]>();
69
+ for (const cst of preferred) {
70
+ children.set(
71
+ cst.id,
72
+ (relations.childrenOf.get(cst.id) ?? []).filter(childId => rank.has(childId))
73
+ );
74
+ }
75
+
76
+ const result: T[] = [];
77
+ const pendingChildren: number[] = [];
78
+
79
+ function enqueueSticky(candidateIds: readonly number[]): void {
80
+ const sticky = candidateIds.filter(
81
+ childId => ready.has(childId) && !pendingChildren.includes(childId)
82
+ );
83
+ sticky.sort((left, right) => (rank.get(left) ?? 0) - (rank.get(right) ?? 0));
84
+ pendingChildren.push(...sticky);
85
+ }
86
+
87
+ function pickReady(): number {
88
+ while (pendingChildren.length > 0) {
89
+ const childId = pendingChildren.shift()!;
90
+ if (ready.has(childId)) {
91
+ return childId;
92
+ }
93
+ }
94
+ let best = -1;
95
+ let bestRank = Number.POSITIVE_INFINITY;
96
+ for (const nodeId of ready) {
97
+ const nodeRank = rank.get(nodeId) ?? Number.POSITIVE_INFINITY;
98
+ if (nodeRank < bestRank) {
99
+ best = nodeId;
100
+ bestRank = nodeRank;
101
+ }
102
+ }
103
+ return best;
104
+ }
105
+
106
+ while (ready.size > 0 || pendingChildren.length > 0) {
107
+ if (ready.size === 0) {
108
+ pendingChildren.length = 0;
109
+ continue;
110
+ }
111
+ const nodeId = pickReady();
112
+ ready.delete(nodeId);
113
+ result.push(byId.get(nodeId)!);
114
+
115
+ for (const dependentId of graph.at(nodeId)?.outputs ?? []) {
116
+ if (!remainingInputs.has(dependentId)) {
117
+ continue;
118
+ }
119
+ const next = (remainingInputs.get(dependentId) ?? 0) - 1;
120
+ remainingInputs.set(dependentId, next);
121
+ if (next === 0) {
122
+ ready.add(dependentId);
123
+ }
124
+ }
125
+
126
+ enqueueSticky(children.get(nodeId) ?? []);
127
+ }
128
+
129
+ if (result.length < preferred.length) {
130
+ const placed = new Set(result.map(cst => cst.id));
131
+ for (const cst of preferred) {
132
+ if (!placed.has(cst.id)) {
133
+ result.push(cst);
134
+ }
135
+ }
136
+ }
137
+
138
+ return result;
139
+ }
140
+
141
+ function kernelPriority<T extends OrderableConstituenta>(
142
+ items: readonly T[],
143
+ graph: Graph<number>,
144
+ parentOf: Map<number, number>
145
+ ): T[] {
146
+ const byId = new Map(items.map(cst => [cst.id, cst]));
147
+ const seen = new Set<number>();
148
+ const result: T[] = [];
149
+
150
+ function push(cst: T): void {
151
+ if (seen.has(cst.id)) {
152
+ return;
153
+ }
154
+ seen.add(cst.id);
155
+ result.push(cst);
156
+ }
157
+
158
+ for (const cst of items) {
159
+ if (cst.cst_type === CstType.BASE) {
160
+ push(cst);
161
+ }
162
+ }
163
+ for (const cst of items) {
164
+ if (cst.cst_type === CstType.CONSTANT) {
165
+ push(cst);
166
+ }
167
+ }
168
+ for (const cst of items) {
169
+ if ((graph.at(cst.id)?.inputs.length ?? 0) === 0) {
170
+ push(cst);
171
+ }
172
+ }
173
+
174
+ const kernelSeeds = items
175
+ .filter(cst => {
176
+ const parentId = parentOf.get(cst.id) ?? cst.id;
177
+ const parent = byId.get(parentId);
178
+ return (
179
+ cst.cst_type === CstType.STRUCTURED ||
180
+ cst.cst_type === CstType.AXIOM ||
181
+ parent?.cst_type === CstType.STRUCTURED
182
+ );
183
+ })
184
+ .map(cst => cst.id);
185
+ const kernel = new Set<number>([...kernelSeeds, ...graph.expandAllInputs(kernelSeeds)]);
186
+ for (const cst of items) {
187
+ if (kernel.has(cst.id)) {
188
+ push(cst);
189
+ }
190
+ }
191
+ for (const cst of items) {
192
+ push(cst);
193
+ }
194
+ return result;
195
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Module: API for formal representation for systems of concepts.
3
+ */
4
+
5
+ export {
6
+ addAliasReference,
7
+ allocateImportAliases,
8
+ applyMappingToConstituents,
9
+ argumentValuesToMapping,
10
+ buildSequentialAliasMapping,
11
+ type ConstituentaMappableFields,
12
+ generateAlias,
13
+ maxAliasIndex,
14
+ removeAliasReference,
15
+ validateAliasFormat,
16
+ validateNewAlias
17
+ } from './alias';
18
+ export { getAnalysisFor, refineAnalysisForDependencyCycles } from './analysis';
19
+ export { filterAttributions, importAttributions, remapAttributionsUnderSubstitution } from './attribution';
20
+ export {
21
+ canHaveManualTypification,
22
+ canProduceStructure,
23
+ getCstTypePrefix,
24
+ guessCstType,
25
+ inferClass,
26
+ isBaseSet,
27
+ isBasicConcept,
28
+ isFunctional,
29
+ isLogical,
30
+ isTemplateCst,
31
+ typeClassForCstType
32
+ } from './cst-type';
33
+ export { applyFilterCategory, inferStatus, inferTemplate, inferTemplatedType, normalizeExpression } from './expression';
34
+ export { buildFormalDependencyGraph, restoreConstituentOrder } from './formal-graph';
35
+ export { calculateSchemaStats, isSchemaIssue } from './stats';
36
+ export { findCstByStructure, getStructureName, inferNewSpawnPosition } from './structure';
37
+ export { applyConstituentSubstitutions, remapSubstitutionsAfterImport, type SubstitutionResult } from './substitution';
38
+ export {
39
+ inlineSynthesis,
40
+ type InlineSynthesisInput,
41
+ type InlineSynthesisResult,
42
+ sortItemsForInlineSynthesis,
43
+ type SynthesizableConstituenta
44
+ } from './synthesis';
45
+ export {
46
+ buildDefinitionReferenceGraph,
47
+ buildTermReferenceGraph,
48
+ resolveAllConstituentTexts,
49
+ resolveConstituentTextChange,
50
+ type TextChangeOptions
51
+ } from './text-resolution';
52
+ export { insertItemAfter, moveIdsInOrder, reorderItemsByIds } from './transforms';
53
+ export type { AliasTypedConstituenta, OrderableConstituenta, ResolvableConstituenta, SemanticRelations } from './types';
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Semantic parent/children used when restoring constituent order.
3
+ */
4
+
5
+ import { type Graph } from '../../graph';
6
+ import { extractGlobals, isSimpleExpression, splitTemplateDefinition } from '../../rslang/api';
7
+ import { CstType } from '../rsform';
8
+
9
+ import { isBaseSet, isFunctional } from './cst-type';
10
+ import { inferTemplate } from './expression';
11
+ import { type OrderableConstituenta, type SemanticRelations } from './types';
12
+
13
+ /**
14
+ * Infer semantic parent/children used for clustering during restore order.
15
+ * Port of backend {@code SemanticInfo}.
16
+ */
17
+ export function computeSemanticRelations(
18
+ items: readonly OrderableConstituenta[],
19
+ graph: Graph<number>
20
+ ): SemanticRelations {
21
+ const byId = new Map(items.map(cst => [cst.id, cst]));
22
+ const byAlias = new Map(items.map(cst => [cst.alias, cst]));
23
+ const isTemplate = new Map<number, boolean>();
24
+ const isSimple = new Map<number, boolean>();
25
+ const parentOf = new Map<number, number>();
26
+ const childrenOf = new Map<number, number[]>();
27
+
28
+ for (const cst of items) {
29
+ parentOf.set(cst.id, cst.id);
30
+ childrenOf.set(cst.id, []);
31
+ }
32
+
33
+ function needCheckHead(sources: Set<number>, head: string): boolean {
34
+ if (sources.size === 0) {
35
+ return true;
36
+ }
37
+ if (sources.size !== 1) {
38
+ return false;
39
+ }
40
+ const base = byId.get([...sources][0])!;
41
+ return !isFunctional(base.cst_type) || splitTemplateDefinition(base.definition_formal).head !== head;
42
+ }
43
+
44
+ function extractSources(target: OrderableConstituenta): Set<number> {
45
+ const sources = new Set<number>();
46
+ if (!isFunctional(target.cst_type)) {
47
+ for (const parentId of graph.at(target.id)?.inputs ?? []) {
48
+ if (!(isTemplate.get(parentId) && isSimple.get(parentId))) {
49
+ sources.add(parentOf.get(parentId) ?? parentId);
50
+ }
51
+ }
52
+ return sources;
53
+ }
54
+
55
+ const expression = splitTemplateDefinition(target.definition_formal);
56
+ for (const alias of extractGlobals(expression.body)) {
57
+ const parent = byAlias.get(alias);
58
+ if (!parent) {
59
+ continue;
60
+ }
61
+ if (!(isTemplate.get(parent.id) && isSimple.get(parent.id))) {
62
+ sources.add(parentOf.get(parent.id) ?? parent.id);
63
+ }
64
+ }
65
+ if (needCheckHead(sources, expression.head)) {
66
+ for (const alias of extractGlobals(expression.head)) {
67
+ const parent = byAlias.get(alias);
68
+ if (!parent) {
69
+ continue;
70
+ }
71
+ if (!isBaseSet(parent.cst_type) && !(isTemplate.get(parent.id) && isSimple.get(parent.id))) {
72
+ sources.add(parentOf.get(parent.id) ?? parent.id);
73
+ }
74
+ }
75
+ }
76
+ return sources;
77
+ }
78
+
79
+ function inferSimple(target: OrderableConstituenta): boolean {
80
+ if (target.cst_type === CstType.STRUCTURED || isBaseSet(target.cst_type)) {
81
+ return false;
82
+ }
83
+ const dependencies = graph.at(target.id)?.inputs ?? [];
84
+ if (dependencies.some(id => isTemplate.get(id) && !isSimple.get(id))) {
85
+ return false;
86
+ }
87
+ const expression = isFunctional(target.cst_type)
88
+ ? splitTemplateDefinition(target.definition_formal).body
89
+ : target.definition_formal;
90
+ return isSimpleExpression(expression);
91
+ }
92
+
93
+ for (const cstId of graph.topologicalOrder()) {
94
+ const cst = byId.get(cstId);
95
+ if (!cst) {
96
+ continue;
97
+ }
98
+ isTemplate.set(cstId, inferTemplate(cst.definition_formal));
99
+ isSimple.set(cstId, inferSimple(cst));
100
+ if (!isSimple.get(cstId) || cst.cst_type === CstType.STRUCTURED) {
101
+ continue;
102
+ }
103
+ const sources = extractSources(cst);
104
+ if (sources.size !== 1) {
105
+ continue;
106
+ }
107
+ const parentId = [...sources][0];
108
+ const parent = byId.get(parentId);
109
+ if (!parent || isBaseSet(parent.cst_type)) {
110
+ continue;
111
+ }
112
+ parentOf.set(cstId, parentId);
113
+ childrenOf.get(parentId)!.push(cstId);
114
+ }
115
+
116
+ return { parentOf, childrenOf };
117
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Schema-level statistics and issue flags.
3
+ */
4
+
5
+ import { TypeID, type Typification, ValueClass } from '../../rslang';
6
+ import { hasCstDiagnostic, RSDiagnosticCode } from '../diagnostics';
7
+ import { type Constituenta, CstStatus, CstType, type RSForm, type RSFormStats } from '../rsform';
8
+
9
+ import { isBasicConcept, isLogical } from './cst-type';
10
+
11
+ /** Checks if {@link Constituenta} is a schema issue. */
12
+ export function isSchemaIssue(cst: Constituenta): boolean {
13
+ return (
14
+ (cst.diagnostics?.length ?? 0) > 0 || cst.status === CstStatus.INCORRECT || cst.status === CstStatus.INCALCULABLE
15
+ );
16
+ }
17
+
18
+ /** Calculate statistics for {@link RSForm}. */
19
+ export function calculateSchemaStats(target: RSForm): RSFormStats {
20
+ const stats: RSFormStats = {
21
+ count_all: target.items.length,
22
+ count_crucial: 0,
23
+ step_complexity: 0,
24
+ count_problematic: 0,
25
+ count_homonyms: 0,
26
+ count_formal_duplicates: 0,
27
+ count_missing_basic_text: 0,
28
+ count_type_mismatch: 0,
29
+ count_incorrect: 0,
30
+ count_property: 0,
31
+ count_incalculable: 0,
32
+ count_inherited: 0,
33
+ count_text_term: 0,
34
+ count_definition: 0,
35
+ count_convention: 0,
36
+ count_comment: 0,
37
+ count_base: 0,
38
+ count_constant: 0,
39
+ count_structured: 0,
40
+ count_axiom: 0,
41
+ count_term: 0,
42
+ count_function: 0,
43
+ count_predicate: 0,
44
+ count_statement: 0,
45
+ count_nominal: 0
46
+ };
47
+
48
+ for (const cst of target.items) {
49
+ if (cst.crucial) {
50
+ stats.count_crucial += 1;
51
+ }
52
+ stats.step_complexity += calculateStepComplexity(cst);
53
+
54
+ if (isSchemaIssue(cst)) {
55
+ stats.count_problematic += 1;
56
+ }
57
+ if (hasCstDiagnostic(cst, RSDiagnosticCode.schemaHomonym)) {
58
+ stats.count_homonyms += 1;
59
+ }
60
+ if (hasCstDiagnostic(cst, RSDiagnosticCode.schemaFormalDuplicate)) {
61
+ stats.count_formal_duplicates += 1;
62
+ }
63
+ if (isMissingBasicText(cst)) {
64
+ stats.count_missing_basic_text += 1;
65
+ }
66
+ if (cst.is_type_mismatch) {
67
+ stats.count_type_mismatch += 1;
68
+ }
69
+ if (cst.status === CstStatus.INCORRECT) {
70
+ stats.count_incorrect += 1;
71
+ }
72
+ if (cst.analysis?.valueClass === ValueClass.PROPERTY) {
73
+ stats.count_property += 1;
74
+ }
75
+ if (cst.analysis?.success && cst.analysis.valueClass === null) {
76
+ stats.count_incalculable += 1;
77
+ }
78
+ if (cst.is_inherited) {
79
+ stats.count_inherited += 1;
80
+ }
81
+ if (cst.term_raw) {
82
+ stats.count_text_term += 1;
83
+ }
84
+ if (cst.definition_raw) {
85
+ stats.count_definition += 1;
86
+ }
87
+ if (isBasicConcept(cst.cst_type) && cst.convention) {
88
+ stats.count_convention += 1;
89
+ }
90
+ if (!isBasicConcept(cst.cst_type) && !cst.convention) {
91
+ stats.count_comment += 1;
92
+ }
93
+
94
+ switch (cst.cst_type) {
95
+ case CstType.BASE:
96
+ stats.count_base += 1;
97
+ break;
98
+ case CstType.CONSTANT:
99
+ stats.count_constant += 1;
100
+ break;
101
+ case CstType.STRUCTURED:
102
+ stats.count_structured += 1;
103
+ break;
104
+ case CstType.AXIOM:
105
+ stats.count_axiom += 1;
106
+ break;
107
+ case CstType.TERM:
108
+ stats.count_term += 1;
109
+ break;
110
+ case CstType.FUNCTION:
111
+ stats.count_function += 1;
112
+ break;
113
+ case CstType.PREDICATE:
114
+ stats.count_predicate += 1;
115
+ break;
116
+ case CstType.STATEMENT:
117
+ stats.count_statement += 1;
118
+ break;
119
+ case CstType.NOMINAL:
120
+ stats.count_nominal += 1;
121
+ break;
122
+ }
123
+ }
124
+
125
+ return stats;
126
+ }
127
+
128
+ function calculateStepComplexity(cst: Constituenta): number {
129
+ if (cst.cst_type === CstType.AXIOM || cst.cst_type === CstType.NOMINAL || !isBasicConcept(cst.cst_type)) {
130
+ return 0;
131
+ }
132
+ if (cst.cst_type === CstType.BASE || cst.cst_type === CstType.CONSTANT || !cst.effectiveType) {
133
+ return 1;
134
+ }
135
+
136
+ const type = cst.effectiveType as Typification;
137
+ return calculateTypificationComplexity(type) + 1;
138
+ }
139
+
140
+ function calculateTypificationComplexity(type: Typification): number {
141
+ switch (type.typeID) {
142
+ case TypeID.basic:
143
+ case TypeID.integer:
144
+ case TypeID.anyTypification:
145
+ return 0;
146
+ case TypeID.tuple:
147
+ return (
148
+ type.factors.length + type.factors.reduce((sum, factor) => sum + calculateTypificationComplexity(factor), 0)
149
+ );
150
+ case TypeID.collection:
151
+ if (type.base.typeID === TypeID.tuple) {
152
+ let sum = 0;
153
+ type.base.factors.forEach(factor => {
154
+ sum += calculateTypificationComplexity(factor);
155
+ });
156
+ return sum + type.base.factors.length;
157
+ } else if (type.base.typeID === TypeID.collection) {
158
+ return calculateTypificationComplexity(type.base) + 1;
159
+ }
160
+ return 0;
161
+ }
162
+ }
163
+
164
+ /** Non-logical basic concept missing convention and/or resolved term (trimmed). */
165
+ function isMissingBasicText(cst: Constituenta): boolean {
166
+ if (!isBasicConcept(cst.cst_type) || isLogical(cst.cst_type)) {
167
+ return false;
168
+ }
169
+ return !cst.convention.trim() || !cst.term_resolved.trim();
170
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Structure-path helpers for spawned constituents.
3
+ */
4
+
5
+ import { TypeID, type TypePath, type Typification } from '../../rslang';
6
+ import { isTypification } from '../../rslang/semantic/typification';
7
+ import { applyPath } from '../../rslang/semantic/typification-api';
8
+ import { type Constituenta, type RSForm } from '../rsform';
9
+
10
+ /**
11
+ * Id to pass as `insert_after` when creating a new {@link Constituenta} spawned by `targetId`:
12
+ * after the last existing child of that spawner, or after the spawner itself if none.
13
+ */
14
+ export function inferNewSpawnPosition(schema: RSForm, targetId: number): number {
15
+ let last: number | null = null;
16
+ for (const cst of schema.items) {
17
+ if (cst.spawner === targetId) {
18
+ last = cst.id;
19
+ }
20
+ }
21
+ return last ?? targetId;
22
+ }
23
+
24
+ /** Finds {@link Constituenta} by structure path. */
25
+ export function findCstByStructure(schema: RSForm, target: Constituenta, path: TypePath): Constituenta | null {
26
+ const byId = schema.cstByID ?? new Map(schema.items.map(item => [item.id, item]));
27
+ for (const cst of schema.items) {
28
+ if (!cst.spawner_path) {
29
+ continue;
30
+ }
31
+ const absolutePath = resolveAbsoluteSpawnerPath(cst, target.id, byId);
32
+ if (absolutePath?.length === path.length && absolutePath.every((v, i) => v === path[i])) {
33
+ return cst;
34
+ }
35
+ }
36
+ return null;
37
+ }
38
+
39
+ /** Retrieves name for piece of target {@link Constituenta} structure. */
40
+ export function getStructureName(schema: RSForm, target: Constituenta, path: TypePath): string {
41
+ const representation = findCstByStructure(schema, target, path);
42
+ if (representation) {
43
+ return `${representation.alias}: ${representation.term_resolved}`;
44
+ }
45
+ if (!isTypification(target.effectiveType)) {
46
+ return '';
47
+ }
48
+ const type = applyPath(target.effectiveType as Typification, path);
49
+ if (type?.typeID === TypeID.basic) {
50
+ const cst = schema.cstByAlias.get(type.baseID);
51
+ if (cst?.term_resolved) {
52
+ return `${cst.alias}: ${cst.term_resolved}`;
53
+ }
54
+ }
55
+ return '';
56
+ }
57
+
58
+ function resolveAbsoluteSpawnerPath(
59
+ cst: Constituenta,
60
+ targetId: number,
61
+ byId: Map<number, Constituenta>
62
+ ): TypePath | null {
63
+ if (!cst.spawner_path || cst.spawner == null) {
64
+ return null;
65
+ }
66
+ if (cst.spawner === targetId) {
67
+ return cst.spawner_path;
68
+ }
69
+
70
+ const visited = new Set<number>([cst.id]);
71
+ let currentPath: number[] = [...cst.spawner_path];
72
+ let currentSpawnerId: number | null = cst.spawner;
73
+
74
+ while (currentSpawnerId != null) {
75
+ if (currentSpawnerId === targetId) {
76
+ return currentPath as TypePath;
77
+ }
78
+ if (visited.has(currentSpawnerId)) {
79
+ return null;
80
+ }
81
+ visited.add(currentSpawnerId);
82
+
83
+ const parent = byId.get(currentSpawnerId);
84
+ if (!parent?.spawner_path || parent.spawner == null) {
85
+ return null;
86
+ }
87
+
88
+ currentPath = [...parent.spawner_path, ...currentPath];
89
+ currentSpawnerId = parent.spawner;
90
+ }
91
+
92
+ return null;
93
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Constituenta substitution and post-import substitution remapping.
3
+ */
4
+
5
+ import { type AliasMapping } from '../../rslang/api';
6
+ import { type Attribution, type Substitution } from '../rsform';
7
+
8
+ import { applyMappingToConstituents, type ConstituentaMappableFields } from './alias';
9
+ import { remapAttributionsUnderSubstitution } from './attribution';
10
+
11
+ /** Result of applying substitutions to a schema fragment. */
12
+ export interface SubstitutionResult<T> {
13
+ items: T[];
14
+ attributions: Attribution[];
15
+ deletedIds: Set<number>;
16
+ /** Replacement constituent ids (survivors referenced by substitutions). */
17
+ replacementIds: number[];
18
+ }
19
+
20
+ /**
21
+ * Apply constituenta substitutions: remap attributions, drop originals, rewrite aliases in survivors.
22
+ * Does not resolve texts — call {@link resolveAllConstituentTexts} afterwards.
23
+ */
24
+ export function applyConstituentSubstitutions<T extends ConstituentaMappableFields & { id: number; alias: string }>(
25
+ items: readonly T[],
26
+ attributions: readonly Attribution[],
27
+ substitutions: readonly Substitution[]
28
+ ): SubstitutionResult<T> {
29
+ if (substitutions.length === 0) {
30
+ return {
31
+ items: [...items],
32
+ attributions: attributions.map(attr => ({ ...attr })),
33
+ deletedIds: new Set(),
34
+ replacementIds: []
35
+ };
36
+ }
37
+
38
+ const byId = new Map(items.map(cst => [cst.id, cst]));
39
+ const mapping: AliasMapping = {};
40
+ const deletedIds = new Set<number>();
41
+ const origToSub = new Map<number, number>();
42
+ const replacementIds: number[] = [];
43
+
44
+ for (const { original, substitution } of substitutions) {
45
+ const originalCst = byId.get(original);
46
+ const substitutionCst = byId.get(substitution);
47
+ if (!originalCst || !substitutionCst) {
48
+ throw new Error(`applyConstituentSubstitutions: unknown constituenta ${original} -> ${substitution}`);
49
+ }
50
+ mapping[originalCst.alias] = substitutionCst.alias;
51
+ deletedIds.add(original);
52
+ origToSub.set(original, substitution);
53
+ replacementIds.push(substitution);
54
+ }
55
+
56
+ const nextItems = items.filter(cst => !deletedIds.has(cst.id));
57
+ applyMappingToConstituents(nextItems, mapping, false);
58
+
59
+ return {
60
+ items: nextItems,
61
+ attributions: remapAttributionsUnderSubstitution(attributions, origToSub),
62
+ deletedIds,
63
+ replacementIds
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Remap substitution endpoints after source constituents were inserted under new ids.
69
+ * If {@link original} was imported, map it; otherwise map {@link substitution}.
70
+ */
71
+ export function remapSubstitutionsAfterImport(
72
+ substitutions: readonly Substitution[],
73
+ sourceIdSet: ReadonlySet<number>,
74
+ idMap: Readonly<Record<number, number>>
75
+ ): Substitution[] {
76
+ return substitutions.map(sub => {
77
+ if (sourceIdSet.has(sub.original)) {
78
+ return { original: idMap[sub.original], substitution: sub.substitution };
79
+ }
80
+ return { original: sub.original, substitution: idMap[sub.substitution] };
81
+ });
82
+ }