diff --git a/rsconcept/frontend/src/components/Icons.tsx b/rsconcept/frontend/src/components/Icons.tsx
index 2db5ad7e..f475f25a 100644
--- a/rsconcept/frontend/src/components/Icons.tsx
+++ b/rsconcept/frontend/src/components/Icons.tsx
@@ -44,6 +44,8 @@ export { BiDownvote as IconMoveDown } from 'react-icons/bi';
export { LuRotate3D as IconRotate3D } from 'react-icons/lu';
export { MdOutlineFitScreen as IconFitImage } from 'react-icons/md';
+export { LuSparkles as IconClustering } from 'react-icons/lu';
+export { LuSparkle as IconClusteringOff } from 'react-icons/lu';
interface IconSVGProps {
viewBox: string;
diff --git a/rsconcept/frontend/src/components/info/InfoConstituenta.tsx b/rsconcept/frontend/src/components/info/InfoConstituenta.tsx
index 51615be9..f43c4fcd 100644
--- a/rsconcept/frontend/src/components/info/InfoConstituenta.tsx
+++ b/rsconcept/frontend/src/components/info/InfoConstituenta.tsx
@@ -13,6 +13,18 @@ function InfoConstituenta({ data, className, ...restProps }: InfoConstituentaPro
return (
Конституента {data.alias}
+ {data.derived_alias ? (
+
+ Основана на:
+ {data.derived_alias}
+
+ ) : null}
+ {data.derived_children.length > 0 ? (
+
+ Порождает:
+ {data.derived_children.join(', ')}
+
+ ) : null}
Типизация:
{labelCstTypification(data)}
diff --git a/rsconcept/frontend/src/dialogs/DlgGraphParams.tsx b/rsconcept/frontend/src/dialogs/DlgGraphParams.tsx
index 441a93d1..dde5357f 100644
--- a/rsconcept/frontend/src/dialogs/DlgGraphParams.tsx
+++ b/rsconcept/frontend/src/dialogs/DlgGraphParams.tsx
@@ -55,6 +55,12 @@ function DlgGraphParams({ hideWindow, initial, onConfirm }: DlgGraphParamsProps)
value={params.noTransitive}
setValue={value => updateParams({ noTransitive: value })}
/>
+ updateParams({ foldDerived: value })}
+ />
Типы конституент
diff --git a/rsconcept/frontend/src/models/Graph.ts b/rsconcept/frontend/src/models/Graph.ts
index 277a016e..f65930c2 100644
--- a/rsconcept/frontend/src/models/Graph.ts
+++ b/rsconcept/frontend/src/models/Graph.ts
@@ -69,6 +69,10 @@ export class Graph {
return result;
}
+ at(target: number): GraphNode | undefined {
+ return this.nodes.get(target);
+ }
+
addNode(target: number): GraphNode {
let node = this.nodes.get(target);
if (!node) {
diff --git a/rsconcept/frontend/src/models/miscellaneous.ts b/rsconcept/frontend/src/models/miscellaneous.ts
index 20896b7b..d29c00c8 100644
--- a/rsconcept/frontend/src/models/miscellaneous.ts
+++ b/rsconcept/frontend/src/models/miscellaneous.ts
@@ -96,6 +96,7 @@ export interface GraphFilterParams {
noTransitive: boolean;
noTemplates: boolean;
noText: boolean;
+ foldDerived: boolean;
allowBase: boolean;
allowStruct: boolean;
diff --git a/rsconcept/frontend/src/models/rsform.ts b/rsconcept/frontend/src/models/rsform.ts
index 0b03e95b..08924ea7 100644
--- a/rsconcept/frontend/src/models/rsform.ts
+++ b/rsconcept/frontend/src/models/rsform.ts
@@ -96,6 +96,9 @@ export interface IConstituenta extends IConstituentaMeta {
cst_class: CstClass;
status: ExpressionStatus;
is_template: boolean;
+ derived_from: ConstituentaID;
+ derived_alias?: string;
+ derived_children: string[];
parse: {
status: ParsingStatus;
valueClass: ValueClass;
diff --git a/rsconcept/frontend/src/models/rsformAPI.ts b/rsconcept/frontend/src/models/rsformAPI.ts
index b94ac972..807352b4 100644
--- a/rsconcept/frontend/src/models/rsformAPI.ts
+++ b/rsconcept/frontend/src/models/rsformAPI.ts
@@ -18,7 +18,7 @@ import {
IRSFormStats
} from './rsform';
import { ParsingStatus, ValueClass } from './rslang';
-import { extractGlobals } from './rslangAPI';
+import { extractGlobals, isSimpleExpression, splitTemplateDefinition } from './rslangAPI';
/**
* Loads data into an {@link IRSForm} based on {@link IRSFormData}.
@@ -32,7 +32,11 @@ export function loadRSFormData(input: IRSFormData): IRSForm {
result.graph = new Graph();
result.stats = calculateStats(result.items);
+ const derivationLookup: Map = new Map();
result.items.forEach(cst => {
+ derivationLookup.set(cst.id, cst.id);
+ cst.derived_from = cst.id;
+ cst.derived_children = [];
cst.status = inferStatus(cst.parse.status, cst.parse.valueClass);
cst.is_template = inferTemplate(cst.definition_formal);
cst.cst_class = inferClass(cst.cst_type, cst.is_template);
@@ -45,6 +49,34 @@ export function loadRSFormData(input: IRSFormData): IRSForm {
}
});
});
+ // Calculate derivation of constituents based on formal definition analysis
+ result.graph.topologicalOrder().forEach(id => {
+ const cst = result.items.find(item => item.id === id)!;
+ const resolvedInput: Set = new Set();
+ let definition = '';
+ if (!isFunctional(cst.cst_type)) {
+ const node = result.graph.at(id)!;
+ node.inputs.forEach(id => resolvedInput.add(derivationLookup.get(id)!));
+ definition = cst.definition_formal;
+ } else {
+ const expression = splitTemplateDefinition(cst.definition_formal);
+ definition = expression.body;
+ const dependencies = extractGlobals(definition);
+ dependencies.forEach(alias => {
+ const targetCst = result.items.find(item => item.alias === alias);
+ if (targetCst) {
+ resolvedInput.add(derivationLookup.get(targetCst.id)!);
+ }
+ });
+ }
+ if (resolvedInput.size === 1 && isSimpleExpression(definition)) {
+ const parent = result.items.find(item => item.id === resolvedInput.values().next().value)!;
+ cst.derived_from = parent.id;
+ cst.derived_alias = parent.alias;
+ parent.derived_children.push(cst.alias);
+ derivationLookup.set(cst.id, parent.id);
+ }
+ });
return result;
}
@@ -174,6 +206,8 @@ export function inferClass(type: CstType, isTemplate: boolean): CstClass {
export function createMockConstituenta(id: ConstituentaID, alias: string, comment: string): IConstituenta {
return {
id: id,
+ derived_from: id,
+ derived_children: [],
order: -1,
schema: -1,
alias: alias,
diff --git a/rsconcept/frontend/src/models/rslangAPI.test.ts b/rsconcept/frontend/src/models/rslangAPI.test.ts
index a7bbe0c3..32d61f1b 100644
--- a/rsconcept/frontend/src/models/rslangAPI.test.ts
+++ b/rsconcept/frontend/src/models/rslangAPI.test.ts
@@ -1,4 +1,4 @@
-import { extractGlobals, splitTemplateDefinition } from './rslangAPI';
+import { extractGlobals, isSimpleExpression, splitTemplateDefinition } from './rslangAPI';
const globalsData = [
['', ''],
@@ -13,6 +13,23 @@ describe('Testing extract globals', () => {
});
});
+const simpleExpressionData = [
+ ['', 'true'],
+ ['Pr1(S1)', 'true'],
+ ['pr1(S1)', 'true'],
+ ['red(S1)', 'true'],
+ ['red(Pr1(F1[α,σ]))', 'true'],
+ ['D{(α,β)∈D6×D6 | α≠β & α∩β≠∅}', 'false'],
+ ['I{(β,α) | α:∈D2; σ:=F5[α]; β:∈σ}', 'false'],
+ ['∀σ∈S1 (F1[σ]×F1[σ])∩D11=∅', 'false']
+];
+describe('Testing simple expression', () => {
+ it.each(simpleExpressionData)('isSimpleExpression %p', (input: string, expected: string) => {
+ const result = isSimpleExpression(input);
+ expect(String(result)).toBe(expected);
+ });
+});
+
const splitData = [
['', '||'],
['[α∈ℬ(R1)] α⊆red(σ)', 'α∈ℬ(R1)||α⊆red(σ)'],
diff --git a/rsconcept/frontend/src/models/rslangAPI.ts b/rsconcept/frontend/src/models/rslangAPI.ts
index 972adaf1..2cadb795 100644
--- a/rsconcept/frontend/src/models/rslangAPI.ts
+++ b/rsconcept/frontend/src/models/rslangAPI.ts
@@ -7,14 +7,24 @@ import { applyPattern } from '@/utils/utils';
import { CstType } from './rsform';
import { IArgumentValue, IRSErrorDescription, RSErrorClass, RSErrorType } from './rslang';
+// cspell:disable
const LOCALS_REGEXP = /[_a-zα-ω][a-zα-ω]*\d*/g;
+const GLOBALS_REGEXP = /[XCSADFPT]\d+/g;
+const COMPLEX_SYMBOLS_REGEXP = /[∀∃×ℬ;|:]/g;
+// cspell:enable
/**
* Extracts global variable names from a given expression.
*/
export function extractGlobals(expression: string): Set {
- // cspell:disable-next-line
- return new Set(expression.match(/[XCSADFPT]\d+/g) ?? []);
+ return new Set(expression.match(GLOBALS_REGEXP) ?? []);
+}
+
+/**
+ * Check if expression is simple derivation.
+ */
+export function isSimpleExpression(text: string): boolean {
+ return !COMPLEX_SYMBOLS_REGEXP.test(text);
}
/**
diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/EditorTermGraph.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/EditorTermGraph.tsx
index f6e745b9..74227e11 100644
--- a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/EditorTermGraph.tsx
+++ b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/EditorTermGraph.tsx
@@ -36,6 +36,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
noTemplates: false,
noTransitive: true,
noText: false,
+ foldDerived: false,
allowBase: true,
allowStruct: true,
@@ -217,11 +218,18 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
is3D={is3D}
orbit={orbit}
noText={filterParams.noText}
+ foldDerived={filterParams.foldDerived}
showParamsDialog={() => setShowParamsDialog(true)}
onCreate={handleCreateCst}
onDelete={handleDeleteCst}
onResetViewpoint={() => setToggleResetView(prev => !prev)}
toggleOrbit={() => setOrbit(prev => !prev)}
+ toggleFoldDerived={() =>
+ setFilterParams(prev => ({
+ ...prev,
+ foldDerived: !prev.foldDerived
+ }))
+ }
toggleNoText={() =>
setFilterParams(prev => ({
...prev,
diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/GraphToolbar.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/GraphToolbar.tsx
index f285a938..886e5829 100644
--- a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/GraphToolbar.tsx
+++ b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/GraphToolbar.tsx
@@ -1,6 +1,8 @@
'use client';
import {
+ IconClustering,
+ IconClusteringOff,
IconDestroy,
IconFilter,
IconFitImage,
@@ -22,12 +24,14 @@ interface GraphToolbarProps {
orbit: boolean;
noText: boolean;
+ foldDerived: boolean;
showParamsDialog: () => void;
onCreate: () => void;
onDelete: () => void;
onResetViewpoint: () => void;
+ toggleFoldDerived: () => void;
toggleNoText: () => void;
toggleOrbit: () => void;
}
@@ -35,7 +39,9 @@ interface GraphToolbarProps {
function GraphToolbar({
is3D,
noText,
+ foldDerived,
toggleNoText,
+ toggleFoldDerived,
orbit,
toggleOrbit,
showParamsDialog,
@@ -67,6 +73,17 @@ function GraphToolbar({
}
onClick={toggleNoText}
/>
+
+ ) : (
+
+ )
+ }
+ onClick={toggleFoldDerived}
+ />
}
title='Граф целиком'
diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/useGraphFilter.ts b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/useGraphFilter.ts
index 797d4aef..1be288fe 100644
--- a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/useGraphFilter.ts
+++ b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph/useGraphFilter.ts
@@ -46,6 +46,13 @@ function useGraphFilter(schema: IRSForm | undefined, params: GraphFilterParams)
}
});
}
+ if (params.foldDerived) {
+ schema.items.forEach(cst => {
+ if (cst.derived_alias) {
+ graph.foldNode(cst.id);
+ }
+ });
+ }
setFiltered(graph);
}, [schema, params, allowedTypes]);
diff --git a/rsconcept/frontend/src/utils/constants.ts b/rsconcept/frontend/src/utils/constants.ts
index 38c91603..82eab432 100644
--- a/rsconcept/frontend/src/utils/constants.ts
+++ b/rsconcept/frontend/src/utils/constants.ts
@@ -92,7 +92,7 @@ export const storage = {
librarySearchStrategy: 'library.search.strategy',
libraryPagination: 'library.pagination',
- rsgraphFilter: 'rsgraph.filter',
+ rsgraphFilter: 'rsgraph.filter_options',
rsgraphLayout: 'rsgraph.layout',
rsgraphColoringScheme: 'rsgraph.coloring_scheme',