From f03fd337baf1fc2ffb38b0f7fda638f18814d3de Mon Sep 17 00:00:00 2001 From: IRBorisov <8611739+IRBorisov@users.noreply.github.com> Date: Fri, 5 Apr 2024 15:53:05 +0300 Subject: [PATCH] Add concept derivation relation to backend --- rsconcept/frontend/src/components/Icons.tsx | 2 ++ .../src/components/info/InfoConstituenta.tsx | 12 +++++++ .../frontend/src/dialogs/DlgGraphParams.tsx | 6 ++++ rsconcept/frontend/src/models/Graph.ts | 4 +++ .../frontend/src/models/miscellaneous.ts | 1 + rsconcept/frontend/src/models/rsform.ts | 3 ++ rsconcept/frontend/src/models/rsformAPI.ts | 36 ++++++++++++++++++- .../frontend/src/models/rslangAPI.test.ts | 19 +++++++++- rsconcept/frontend/src/models/rslangAPI.ts | 14 ++++++-- .../EditorTermGraph/EditorTermGraph.tsx | 8 +++++ .../EditorTermGraph/GraphToolbar.tsx | 17 +++++++++ .../EditorTermGraph/useGraphFilter.ts | 7 ++++ rsconcept/frontend/src/utils/constants.ts | 2 +- 13 files changed, 126 insertions(+), 5 deletions(-) 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',