mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Refactoring: use cached maps to access constituents
This commit is contained in:
parent
5c52e2a6f8
commit
26fe49b352
|
@ -86,9 +86,9 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
|
|||
EditorView.lineWrapping,
|
||||
RSLanguage,
|
||||
ccBracketMatching(darkMode),
|
||||
...(noTooltip ? [] : [rsHoverTooltip(schema?.items || [])])
|
||||
...(noTooltip || !schema ? [] : [rsHoverTooltip(schema)])
|
||||
],
|
||||
[darkMode, schema?.items, noTooltip]
|
||||
[darkMode, schema, noTooltip]
|
||||
);
|
||||
|
||||
const handleInput = useCallback(
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Extension } from '@codemirror/state';
|
|||
import { hoverTooltip } from '@codemirror/view';
|
||||
import { EditorState } from '@uiw/react-codemirror';
|
||||
|
||||
import { IConstituenta } from '@/models/rsform';
|
||||
import { IRSForm } from '@/models/rsform';
|
||||
import { findEnvelopingNodes } from '@/utils/codemirror';
|
||||
import { domTooltipConstituenta } from '@/utils/codemirror';
|
||||
|
||||
|
@ -25,13 +25,13 @@ function findAliasAt(pos: number, state: EditorState) {
|
|||
return { alias, start, end };
|
||||
}
|
||||
|
||||
const globalsHoverTooltip = (items: IConstituenta[]) => {
|
||||
const globalsHoverTooltip = (schema: IRSForm) => {
|
||||
return hoverTooltip((view, pos) => {
|
||||
const { alias, start, end } = findAliasAt(pos, view.state);
|
||||
if (!alias) {
|
||||
return null;
|
||||
}
|
||||
const cst = items.find(cst => cst.alias === alias);
|
||||
const cst = schema.cstByAlias.get(alias);
|
||||
return {
|
||||
pos: start,
|
||||
end: end,
|
||||
|
@ -41,6 +41,6 @@ const globalsHoverTooltip = (items: IConstituenta[]) => {
|
|||
});
|
||||
};
|
||||
|
||||
export function rsHoverTooltip(items: IConstituenta[]): Extension {
|
||||
return [globalsHoverTooltip(items)];
|
||||
export function rsHoverTooltip(schema: IRSForm): Extension {
|
||||
return [globalsHoverTooltip(schema)];
|
||||
}
|
||||
|
|
|
@ -11,10 +11,9 @@ import { forwardRef, useCallback, useMemo, useRef, useState } from 'react';
|
|||
|
||||
import Label from '@/components/ui/Label';
|
||||
import { useConceptOptions } from '@/context/OptionsContext';
|
||||
import { useRSForm } from '@/context/RSFormContext';
|
||||
import DlgEditReference from '@/dialogs/DlgEditReference';
|
||||
import { ReferenceType } from '@/models/language';
|
||||
import { IConstituenta } from '@/models/rsform';
|
||||
import { IRSForm } from '@/models/rsform';
|
||||
import { CodeMirrorWrapper } from '@/utils/codemirror';
|
||||
|
||||
import { NaturalLanguage, ReferenceTokens } from './parse';
|
||||
|
@ -53,7 +52,7 @@ interface RefsInputInputProps
|
|||
extends Pick<ReactCodeMirrorProps, 'id' | 'height' | 'value' | 'className' | 'onFocus' | 'onBlur' | 'placeholder'> {
|
||||
label?: string;
|
||||
onChange?: (newValue: string) => void;
|
||||
items?: IConstituenta[];
|
||||
schema?: IRSForm;
|
||||
disabled?: boolean;
|
||||
|
||||
initialValue?: string;
|
||||
|
@ -62,9 +61,8 @@ interface RefsInputInputProps
|
|||
}
|
||||
|
||||
const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
|
||||
({ id, label, disabled, items, initialValue, value, resolved, onFocus, onBlur, onChange, ...restProps }, ref) => {
|
||||
({ id, label, disabled, schema, initialValue, value, resolved, onFocus, onBlur, onChange, ...restProps }, ref) => {
|
||||
const { darkMode, colors } = useConceptOptions();
|
||||
const { schema } = useRSForm();
|
||||
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
||||
|
@ -104,9 +102,9 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
|
|||
EditorView.lineWrapping,
|
||||
EditorView.contentAttributes.of({ spellcheck: 'true' }),
|
||||
NaturalLanguage,
|
||||
refsHoverTooltip(schema?.items || [], colors)
|
||||
...(schema ? [refsHoverTooltip(schema, colors)] : [])
|
||||
],
|
||||
[schema?.items, colors]
|
||||
[schema, colors]
|
||||
);
|
||||
|
||||
function handleChange(newValue: string) {
|
||||
|
@ -170,10 +168,10 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
|
|||
return (
|
||||
<div className={clsx('flex flex-col gap-2', cursor)}>
|
||||
<AnimatePresence>
|
||||
{showEditor ? (
|
||||
{showEditor && schema ? (
|
||||
<DlgEditReference
|
||||
hideWindow={() => setShowEditor(false)}
|
||||
items={items ?? []}
|
||||
schema={schema}
|
||||
initial={{
|
||||
type: currentType,
|
||||
refRaw: refText,
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Extension } from '@codemirror/state';
|
|||
import { hoverTooltip } from '@codemirror/view';
|
||||
|
||||
import { parseEntityReference, parseSyntacticReference } from '@/models/languageAPI';
|
||||
import { IConstituenta } from '@/models/rsform';
|
||||
import { IRSForm } from '@/models/rsform';
|
||||
import { IColorTheme } from '@/styling/color';
|
||||
import {
|
||||
domTooltipEntityReference,
|
||||
|
@ -15,7 +15,7 @@ import {
|
|||
import { ReferenceTokens } from './parse';
|
||||
import { RefEntity, RefSyntactic } from './parse/parser.terms';
|
||||
|
||||
export const globalsHoverTooltip = (items: IConstituenta[], colors: IColorTheme) => {
|
||||
export const globalsHoverTooltip = (schema: IRSForm, colors: IColorTheme) => {
|
||||
return hoverTooltip((view, pos) => {
|
||||
const nodes = findEnvelopingNodes(pos, pos, syntaxTree(view.state), ReferenceTokens);
|
||||
if (nodes.length !== 1) {
|
||||
|
@ -26,7 +26,7 @@ export const globalsHoverTooltip = (items: IConstituenta[], colors: IColorTheme)
|
|||
const text = view.state.doc.sliceString(start, end);
|
||||
if (nodes[0].type.id === RefEntity) {
|
||||
const ref = parseEntityReference(text);
|
||||
const cst = items.find(cst => cst.alias === ref.entity);
|
||||
const cst = schema.cstByAlias.get(ref.entity);
|
||||
return {
|
||||
pos: start,
|
||||
end: end,
|
||||
|
@ -61,6 +61,6 @@ export const globalsHoverTooltip = (items: IConstituenta[], colors: IColorTheme)
|
|||
});
|
||||
};
|
||||
|
||||
export function refsHoverTooltip(items: IConstituenta[], colors: IColorTheme): Extension {
|
||||
return [globalsHoverTooltip(items, colors)];
|
||||
export function refsHoverTooltip(schema: IRSForm, colors: IColorTheme): Extension {
|
||||
return [globalsHoverTooltip(schema, colors)];
|
||||
}
|
||||
|
|
|
@ -36,16 +36,16 @@ function InfoConstituenta({ data, className, ...restProps }: InfoConstituentaPro
|
|||
{data.definition_resolved}
|
||||
</p>
|
||||
) : null}
|
||||
{data.derived_from_alias ? (
|
||||
{data.parent_alias ? (
|
||||
<p>
|
||||
<b>Основание: </b>
|
||||
{data.derived_from_alias}
|
||||
{data.parent_alias}
|
||||
</p>
|
||||
) : null}
|
||||
{data.derived_children_alias.length > 0 ? (
|
||||
{data.children_alias.length > 0 ? (
|
||||
<p>
|
||||
<b>Порождает: </b>
|
||||
{data.derived_children_alias.join(', ')}
|
||||
{data.children_alias.join(', ')}
|
||||
</p>
|
||||
) : null}
|
||||
{data.convention ? (
|
||||
|
|
|
@ -7,7 +7,7 @@ import { ILibraryItem } from '@/models/library';
|
|||
import { matchLibraryItem } from '@/models/libraryAPI';
|
||||
import { ILibraryFilter } from '@/models/miscellaneous';
|
||||
import { IRSForm, IRSFormCloneData, IRSFormCreateData, IRSFormData } from '@/models/rsform';
|
||||
import { loadRSFormData } from '@/models/rsformAPI';
|
||||
import { RSFormLoader } from '@/models/RSFormLoader';
|
||||
import {
|
||||
DataCallback,
|
||||
deleteLibraryItem,
|
||||
|
@ -100,7 +100,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
|||
setLoading: setProcessing,
|
||||
onError: setError,
|
||||
onSuccess: data => {
|
||||
const schema = loadRSFormData(data);
|
||||
const schema = new RSFormLoader(data).produceRSForm();
|
||||
setCachedTemplates(prev => [...prev, schema]);
|
||||
callback(schema);
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ function TemplateTab({ state, partialUpdate }: TemplateTabProps) {
|
|||
}
|
||||
: null
|
||||
}
|
||||
onChange={data => partialUpdate({ filterCategory: category?.items.find(cst => cst.id === data?.value) })}
|
||||
onChange={data => partialUpdate({ filterCategory: data ? category?.cstByID.get(data?.value) : undefined })}
|
||||
isClearable
|
||||
/>
|
||||
<SelectSingle
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
import clsx from 'clsx';
|
||||
|
||||
import { IConstituenta } from '@/models/rsform';
|
||||
import { IRSForm } from '@/models/rsform';
|
||||
import { labelConstituenta } from '@/utils/labels';
|
||||
|
||||
interface ConstituentsListProps {
|
||||
list: number[];
|
||||
items: IConstituenta[];
|
||||
schema: IRSForm;
|
||||
prefix: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
function ConstituentsList({ list, items, title, prefix }: ConstituentsListProps) {
|
||||
function ConstituentsList({ list, schema, title, prefix }: ConstituentsListProps) {
|
||||
return (
|
||||
<div>
|
||||
{title ? (
|
||||
|
@ -20,7 +20,7 @@ function ConstituentsList({ list, items, title, prefix }: ConstituentsListProps)
|
|||
) : null}
|
||||
<div className={clsx('h-[9rem]', 'px-3', 'overflow-y-auto', 'border', 'whitespace-nowrap')}>
|
||||
{list.map(id => {
|
||||
const cst = items.find(cst => cst.id === id);
|
||||
const cst = schema.cstByID.get(id);
|
||||
return cst ? <p key={`${prefix}${cst.id}`}>{labelConstituenta(cst)}</p> : null;
|
||||
})}
|
||||
</div>
|
||||
|
|
|
@ -38,16 +38,11 @@ function DlgDeleteCst({ hideWindow, selected, schema, onDelete }: DlgDeleteCstPr
|
|||
onSubmit={handleSubmit}
|
||||
className={clsx('cc-column', 'max-w-[60vw] min-w-[30rem]', 'px-6')}
|
||||
>
|
||||
<ConstituentsList
|
||||
title='Выбраны к удалению'
|
||||
list={selected}
|
||||
items={schema.items}
|
||||
prefix={prefixes.cst_delete_list}
|
||||
/>
|
||||
<ConstituentsList title='Выбраны к удалению' list={selected} schema={schema} prefix={prefixes.cst_delete_list} />
|
||||
<ConstituentsList
|
||||
title='Зависимые конституенты'
|
||||
list={expansion}
|
||||
items={schema.items}
|
||||
schema={schema}
|
||||
prefix={prefixes.cst_dependant_list}
|
||||
/>
|
||||
<Checkbox label='Удалить зависимые конституенты' value={expandOut} setValue={value => setExpandOut(value)} />
|
||||
|
|
|
@ -10,7 +10,7 @@ import Overlay from '@/components/ui/Overlay';
|
|||
import TabLabel from '@/components/ui/TabLabel';
|
||||
import { ReferenceType } from '@/models/language';
|
||||
import { HelpTopic } from '@/models/miscellaneous';
|
||||
import { IConstituenta } from '@/models/rsform';
|
||||
import { IRSForm } from '@/models/rsform';
|
||||
import { labelReferenceType } from '@/utils/labels';
|
||||
|
||||
import EntityTab from './EntityTab';
|
||||
|
@ -26,7 +26,7 @@ export interface IReferenceInputState {
|
|||
|
||||
interface DlgEditReferenceProps {
|
||||
hideWindow: () => void;
|
||||
items: IConstituenta[];
|
||||
schema: IRSForm;
|
||||
initial: IReferenceInputState;
|
||||
onSave: (newRef: string) => void;
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ export enum TabID {
|
|||
SYNTACTIC = 1
|
||||
}
|
||||
|
||||
function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferenceProps) {
|
||||
function DlgEditReference({ hideWindow, schema, initial, onSave }: DlgEditReferenceProps) {
|
||||
const [activeTab, setActiveTab] = useState(initial.type === ReferenceType.ENTITY ? TabID.ENTITY : TabID.SYNTACTIC);
|
||||
|
||||
const [reference, setReference] = useState('');
|
||||
|
@ -77,7 +77,7 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
|
|||
</TabList>
|
||||
|
||||
<TabPanel>
|
||||
<EntityTab initial={initial} items={items} setReference={setReference} setIsValid={setIsValid} />
|
||||
<EntityTab initial={initial} schema={schema} setReference={setReference} setIsValid={setIsValid} />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
|
|
|
@ -10,7 +10,7 @@ import TextInput from '@/components/ui/TextInput';
|
|||
import { ReferenceType } from '@/models/language';
|
||||
import { parseEntityReference, parseGrammemes } from '@/models/languageAPI';
|
||||
import { CstMatchMode } from '@/models/miscellaneous';
|
||||
import { IConstituenta } from '@/models/rsform';
|
||||
import { IConstituenta, IRSForm } from '@/models/rsform';
|
||||
import { matchConstituenta } from '@/models/rsformAPI';
|
||||
import { prefixes } from '@/utils/constants';
|
||||
import { IGrammemeOption, SelectorGrammemes } from '@/utils/selectors';
|
||||
|
@ -20,12 +20,12 @@ import SelectWordForm from './SelectWordForm';
|
|||
|
||||
interface EntityTabProps {
|
||||
initial: IReferenceInputState;
|
||||
items: IConstituenta[];
|
||||
schema: IRSForm;
|
||||
setIsValid: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setReference: React.Dispatch<React.SetStateAction<string>>;
|
||||
}
|
||||
|
||||
function EntityTab({ initial, items, setIsValid, setReference }: EntityTabProps) {
|
||||
function EntityTab({ initial, schema, setIsValid, setReference }: EntityTabProps) {
|
||||
const [selectedCst, setSelectedCst] = useState<IConstituenta | undefined>(undefined);
|
||||
const [alias, setAlias] = useState('');
|
||||
const [term, setTerm] = useState('');
|
||||
|
@ -39,7 +39,7 @@ function EntityTab({ initial, items, setIsValid, setReference }: EntityTabProps)
|
|||
const grams = parseGrammemes(ref.form);
|
||||
setSelectedGrams(SelectorGrammemes.filter(data => grams.includes(data.value)));
|
||||
}
|
||||
}, [initial, items]);
|
||||
}, [initial, schema.items]);
|
||||
|
||||
// Produce result
|
||||
useEffect(() => {
|
||||
|
@ -49,9 +49,9 @@ function EntityTab({ initial, items, setIsValid, setReference }: EntityTabProps)
|
|||
|
||||
// Update term when alias changes
|
||||
useEffect(() => {
|
||||
const cst = items.find(item => item.alias === alias);
|
||||
const cst = schema.cstByAlias.get(alias);
|
||||
setTerm(cst?.term_resolved ?? '');
|
||||
}, [alias, term, items]);
|
||||
}, [alias, term, schema]);
|
||||
|
||||
function handleSelectConstituenta(cst: IConstituenta) {
|
||||
setAlias(cst.alias);
|
||||
|
@ -64,7 +64,7 @@ function EntityTab({ initial, items, setIsValid, setReference }: EntityTabProps)
|
|||
id='dlg_reference_entity_picker'
|
||||
initialFilter={initial.text}
|
||||
value={selectedCst}
|
||||
data={items}
|
||||
data={schema.items}
|
||||
onSelectValue={handleSelectConstituenta}
|
||||
prefixID={prefixes.cst_modal_list}
|
||||
describeFunc={cst => cst.term_resolved}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useCallback, useEffect, useState } from 'react';
|
|||
|
||||
import { type ErrorData } from '@/components/info/InfoError';
|
||||
import { IRSForm, IRSFormData } from '@/models/rsform';
|
||||
import { loadRSFormData } from '@/models/rsformAPI';
|
||||
import { RSFormLoader } from '@/models/RSFormLoader';
|
||||
import { getRSFormDetails } from '@/utils/backendAPI';
|
||||
|
||||
function useRSFormDetails({ target, version }: { target?: string; version?: string }) {
|
||||
|
@ -17,7 +17,7 @@ function useRSFormDetails({ target, version }: { target?: string; version?: stri
|
|||
setInnerSchema(undefined);
|
||||
return;
|
||||
}
|
||||
const schema = loadRSFormData(data);
|
||||
const schema = new RSFormLoader(data).produceRSForm();
|
||||
setInnerSchema(schema);
|
||||
}
|
||||
|
||||
|
|
178
rsconcept/frontend/src/models/RSFormLoader.ts
Normal file
178
rsconcept/frontend/src/models/RSFormLoader.ts
Normal file
|
@ -0,0 +1,178 @@
|
|||
/**
|
||||
* Module: RSForm data loading and processing.
|
||||
*/
|
||||
|
||||
import { Graph } from './Graph';
|
||||
import { ConstituentaID, CstType, IConstituenta, IRSForm, IRSFormData, IRSFormStats } from './rsform';
|
||||
import { inferClass, inferStatus, inferTemplate, isBaseSet, isFunctional } from './rsformAPI';
|
||||
import { ParsingStatus, ValueClass } from './rslang';
|
||||
import { extractGlobals, isSimpleExpression, splitTemplateDefinition } from './rslangAPI';
|
||||
|
||||
/**
|
||||
* Loads data into an {@link IRSForm} based on {@link IRSFormData}.
|
||||
*
|
||||
* @remarks
|
||||
* This function processes the provided input, initializes the IRSForm, and calculates statistics
|
||||
* based on the loaded data. It also establishes dependencies between concepts in the graph.
|
||||
*/
|
||||
export class RSFormLoader {
|
||||
private schema: IRSFormData;
|
||||
private graph: Graph = new Graph();
|
||||
private cstByAlias: Map<string, IConstituenta> = new Map();
|
||||
private cstByID: Map<ConstituentaID, IConstituenta> = new Map();
|
||||
|
||||
constructor(input: IRSFormData) {
|
||||
this.schema = input;
|
||||
}
|
||||
|
||||
produceRSForm(): IRSForm {
|
||||
this.prepareLookups();
|
||||
this.createGraph();
|
||||
this.inferCstAttributes();
|
||||
|
||||
const result = this.schema as IRSForm;
|
||||
result.stats = this.calculateStats();
|
||||
result.graph = this.graph;
|
||||
result.cstByAlias = this.cstByAlias;
|
||||
result.cstByID = this.cstByID;
|
||||
return result;
|
||||
}
|
||||
|
||||
private prepareLookups() {
|
||||
this.schema.items.forEach(cst => {
|
||||
this.cstByAlias.set(cst.alias, cst as IConstituenta);
|
||||
this.cstByID.set(cst.id, cst as IConstituenta);
|
||||
this.graph.addNode(cst.id);
|
||||
});
|
||||
}
|
||||
|
||||
private createGraph() {
|
||||
this.schema.items.forEach(cst => {
|
||||
const dependencies = extractGlobals(cst.definition_formal);
|
||||
dependencies.forEach(alias => {
|
||||
const source = this.cstByAlias.get(alias);
|
||||
if (source) {
|
||||
this.graph.addEdge(source.id, cst.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private inferCstAttributes() {
|
||||
this.graph.topologicalOrder().forEach(cstID => {
|
||||
const cst = this.cstByID.get(cstID)!;
|
||||
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);
|
||||
cst.children = [];
|
||||
cst.children_alias = [];
|
||||
cst.is_simple_expression = this.inferSimpleExpression(cst);
|
||||
if (!cst.is_simple_expression || cst.cst_type === CstType.STRUCTURED) {
|
||||
return;
|
||||
}
|
||||
cst.parent = this.inferParent(cst);
|
||||
if (cst.parent) {
|
||||
const parent = this.cstByID.get(cst.parent)!;
|
||||
cst.parent_alias = parent.alias;
|
||||
parent.children.push(cst.id);
|
||||
parent.children_alias.push(cst.alias);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private inferSimpleExpression(target: IConstituenta): boolean {
|
||||
if (target.cst_type === CstType.STRUCTURED || isBaseSet(target.cst_type)) {
|
||||
return false;
|
||||
}
|
||||
const dependencies = this.graph.at(target.id)!.inputs;
|
||||
const hasComplexDependency = dependencies.some(id => {
|
||||
const cst = this.cstByID.get(id)!;
|
||||
return cst.is_template && !cst.is_simple_expression;
|
||||
});
|
||||
if (hasComplexDependency) {
|
||||
return false;
|
||||
}
|
||||
const expression = isFunctional(target.cst_type)
|
||||
? splitTemplateDefinition(target.definition_formal).body
|
||||
: target.definition_formal;
|
||||
return isSimpleExpression(expression);
|
||||
}
|
||||
|
||||
private inferParent(target: IConstituenta): ConstituentaID | undefined {
|
||||
const sources = this.extractSources(target);
|
||||
if (sources.size !== 1 || sources.has(target.id)) {
|
||||
return undefined;
|
||||
}
|
||||
return sources.values().next().value as ConstituentaID;
|
||||
}
|
||||
|
||||
private extractSources(target: IConstituenta): Set<ConstituentaID> {
|
||||
const sources: Set<ConstituentaID> = new Set();
|
||||
if (!isFunctional(target.cst_type)) {
|
||||
const node = this.graph.at(target.id)!;
|
||||
node.inputs.forEach(id => {
|
||||
const parent = this.cstByID.get(id)!;
|
||||
if (!parent.is_template || !parent.is_simple_expression) {
|
||||
sources.add(parent.parent ?? id);
|
||||
}
|
||||
});
|
||||
return sources;
|
||||
}
|
||||
|
||||
const expression = splitTemplateDefinition(target.definition_formal);
|
||||
const bodyDependencies = extractGlobals(expression.body);
|
||||
bodyDependencies.forEach(alias => {
|
||||
const parent = this.cstByAlias.get(alias);
|
||||
if (parent && (!parent.is_template || !parent.is_simple_expression)) {
|
||||
sources.add(this.cstByID.get(parent.id)!.parent ?? parent.id);
|
||||
}
|
||||
});
|
||||
const needCheckHead = () => {
|
||||
if (sources.size === 0) {
|
||||
return true;
|
||||
} else if (sources.size !== 1) {
|
||||
return false;
|
||||
} else {
|
||||
const base = this.cstByID.get(sources.values().next().value as ConstituentaID)!;
|
||||
return !isFunctional(base.cst_type) || splitTemplateDefinition(base.definition_formal).head !== expression.head;
|
||||
}
|
||||
};
|
||||
if (needCheckHead()) {
|
||||
const headDependencies = extractGlobals(expression.head);
|
||||
headDependencies.forEach(alias => {
|
||||
const parent = this.cstByAlias.get(alias);
|
||||
if (parent && !isBaseSet(parent.cst_type) && (!parent.is_template || !parent.is_simple_expression)) {
|
||||
sources.add(parent.parent ?? parent.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
return sources;
|
||||
}
|
||||
|
||||
private calculateStats(): IRSFormStats {
|
||||
const items = this.schema.items;
|
||||
return {
|
||||
count_all: items.length,
|
||||
count_errors: items.reduce((sum, cst) => sum + (cst.parse.status === ParsingStatus.INCORRECT ? 1 : 0), 0),
|
||||
count_property: items.reduce((sum, cst) => sum + (cst.parse.valueClass === ValueClass.PROPERTY ? 1 : 0), 0),
|
||||
count_incalculable: items.reduce(
|
||||
(sum, cst) =>
|
||||
sum + (cst.parse.status === ParsingStatus.VERIFIED && cst.parse.valueClass === ValueClass.INVALID ? 1 : 0),
|
||||
0
|
||||
),
|
||||
|
||||
count_text_term: items.reduce((sum, cst) => sum + (cst.term_raw ? 1 : 0), 0),
|
||||
count_definition: items.reduce((sum, cst) => sum + (cst.definition_raw ? 1 : 0), 0),
|
||||
count_convention: items.reduce((sum, cst) => sum + (cst.convention ? 1 : 0), 0),
|
||||
|
||||
count_base: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.BASE ? 1 : 0), 0),
|
||||
count_constant: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.CONSTANT ? 1 : 0), 0),
|
||||
count_structured: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.STRUCTURED ? 1 : 0), 0),
|
||||
count_axiom: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.AXIOM ? 1 : 0), 0),
|
||||
count_term: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.TERM ? 1 : 0), 0),
|
||||
count_function: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.FUNCTION ? 1 : 0), 0),
|
||||
count_predicate: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.PREDICATE ? 1 : 0), 0),
|
||||
count_theorem: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.THEOREM ? 1 : 0), 0)
|
||||
};
|
||||
}
|
||||
}
|
|
@ -90,16 +90,9 @@ export interface ICstTarget {
|
|||
}
|
||||
|
||||
/**
|
||||
* Represents Constituenta.
|
||||
* Represents Constituenta data from server.
|
||||
*/
|
||||
export interface IConstituenta extends IConstituentaMeta {
|
||||
cst_class: CstClass;
|
||||
status: ExpressionStatus;
|
||||
is_template: boolean;
|
||||
derived_from: ConstituentaID;
|
||||
derived_from_alias?: string;
|
||||
derived_children: number[];
|
||||
derived_children_alias: string[];
|
||||
export interface IConstituentaData extends IConstituentaMeta {
|
||||
parse: {
|
||||
status: ParsingStatus;
|
||||
valueClass: ValueClass;
|
||||
|
@ -109,6 +102,20 @@ export interface IConstituenta extends IConstituentaMeta {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents Constituenta.
|
||||
*/
|
||||
export interface IConstituenta extends IConstituentaData {
|
||||
cst_class: CstClass;
|
||||
status: ExpressionStatus;
|
||||
is_template: boolean;
|
||||
is_simple_expression: boolean;
|
||||
parent?: ConstituentaID;
|
||||
parent_alias?: string;
|
||||
children: number[];
|
||||
children_alias: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents Constituenta list.
|
||||
*/
|
||||
|
@ -223,12 +230,16 @@ export interface IRSForm extends ILibraryItemEx {
|
|||
items: IConstituenta[];
|
||||
stats: IRSFormStats;
|
||||
graph: Graph;
|
||||
cstByAlias: Map<string, IConstituenta>;
|
||||
cstByID: Map<ConstituentaID, IConstituenta>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents data for {@link IRSForm} provided by backend.
|
||||
*/
|
||||
export interface IRSFormData extends Omit<IRSForm, 'stats' | 'graph'> {}
|
||||
export interface IRSFormData extends ILibraryItemEx {
|
||||
items: IConstituentaData[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents data, used for creating {@link IRSForm}.
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
* Module: API for formal representation for systems of concepts.
|
||||
*/
|
||||
|
||||
import { Graph } from '@/models/Graph';
|
||||
import { TextMatcher } from '@/utils/utils';
|
||||
|
||||
import { CstMatchMode } from './miscellaneous';
|
||||
|
@ -13,124 +12,9 @@ import {
|
|||
CstType,
|
||||
ExpressionStatus,
|
||||
IConstituenta,
|
||||
IRSForm,
|
||||
IRSFormData,
|
||||
IRSFormStats
|
||||
IRSForm
|
||||
} from './rsform';
|
||||
import { ParsingStatus, ValueClass } from './rslang';
|
||||
import { extractGlobals, isSimpleExpression, splitTemplateDefinition } from './rslangAPI';
|
||||
|
||||
/**
|
||||
* Loads data into an {@link IRSForm} based on {@link IRSFormData}.
|
||||
*
|
||||
* @remarks
|
||||
* This function processes the provided input, initializes the IRSForm, and calculates statistics
|
||||
* based on the loaded data. It also establishes dependencies between concepts in the graph.
|
||||
*/
|
||||
export function loadRSFormData(input: IRSFormData): IRSForm {
|
||||
const result = input as IRSForm;
|
||||
result.graph = new Graph();
|
||||
result.stats = calculateStats(result.items);
|
||||
|
||||
result.items.forEach(cst => {
|
||||
cst.derived_from = cst.id;
|
||||
cst.derived_children = [];
|
||||
cst.derived_children_alias = [];
|
||||
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);
|
||||
result.graph.addNode(cst.id);
|
||||
const dependencies = extractGlobals(cst.definition_formal);
|
||||
dependencies.forEach(value => {
|
||||
const source = input.items.find(cst => cst.alias === value);
|
||||
if (source) {
|
||||
result.graph.addEdge(source.id, cst.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
// Calculate derivation of constituents based on formal definition analysis
|
||||
const derivationLookup: Map<ConstituentaID, ConstituentaID> = new Map();
|
||||
result.graph.topologicalOrder().forEach(id => {
|
||||
derivationLookup.set(id, id);
|
||||
const cst = result.items.find(item => item.id === id)!;
|
||||
if (cst.cst_type === CstType.STRUCTURED) {
|
||||
return;
|
||||
}
|
||||
const resolvedInput: Set<ConstituentaID> = 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 bodyDependencies = extractGlobals(definition);
|
||||
bodyDependencies.forEach(alias => {
|
||||
const targetCst = result.items.find(item => item.alias === alias);
|
||||
if (targetCst) {
|
||||
resolvedInput.add(derivationLookup.get(targetCst.id)!);
|
||||
}
|
||||
});
|
||||
const needCheckHead = () => {
|
||||
if (resolvedInput.size === 0) {
|
||||
return true;
|
||||
} else if (resolvedInput.size !== 1) {
|
||||
return false;
|
||||
} else {
|
||||
const base = result.items.find(item => item.id === resolvedInput.values().next().value)!;
|
||||
return (
|
||||
!isFunctional(base.cst_type) || splitTemplateDefinition(base.definition_formal).head !== expression.head
|
||||
);
|
||||
}
|
||||
};
|
||||
if (needCheckHead()) {
|
||||
const headDependencies = extractGlobals(expression.head);
|
||||
headDependencies.forEach(alias => {
|
||||
const targetCst = result.items.find(item => item.alias === alias);
|
||||
if (targetCst && !isBaseSet(targetCst.cst_type)) {
|
||||
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_from_alias = parent.alias;
|
||||
parent.derived_children_alias.push(cst.alias);
|
||||
parent.derived_children.push(cst.id);
|
||||
derivationLookup.set(cst.id, parent.id);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function calculateStats(items: IConstituenta[]): IRSFormStats {
|
||||
return {
|
||||
count_all: items.length,
|
||||
count_errors: items.reduce((sum, cst) => sum + (cst.parse?.status === ParsingStatus.INCORRECT ? 1 : 0), 0),
|
||||
count_property: items.reduce((sum, cst) => sum + (cst.parse?.valueClass === ValueClass.PROPERTY ? 1 : 0), 0),
|
||||
count_incalculable: items.reduce(
|
||||
(sum, cst) =>
|
||||
sum + (cst.parse.status === ParsingStatus.VERIFIED && cst.parse.valueClass === ValueClass.INVALID ? 1 : 0),
|
||||
0
|
||||
),
|
||||
|
||||
count_text_term: items.reduce((sum, cst) => sum + (cst.term_raw ? 1 : 0), 0),
|
||||
count_definition: items.reduce((sum, cst) => sum + (cst.definition_raw ? 1 : 0), 0),
|
||||
count_convention: items.reduce((sum, cst) => sum + (cst.convention ? 1 : 0), 0),
|
||||
|
||||
count_base: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.BASE ? 1 : 0), 0),
|
||||
count_constant: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.CONSTANT ? 1 : 0), 0),
|
||||
count_structured: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.STRUCTURED ? 1 : 0), 0),
|
||||
count_axiom: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.AXIOM ? 1 : 0), 0),
|
||||
count_term: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.TERM ? 1 : 0), 0),
|
||||
count_function: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.FUNCTION ? 1 : 0), 0),
|
||||
count_predicate: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.PREDICATE ? 1 : 0), 0),
|
||||
count_theorem: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.THEOREM ? 1 : 0), 0)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given target {@link IConstituenta} matches the specified query using the provided matching mode.
|
||||
|
@ -232,9 +116,10 @@ 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: [],
|
||||
derived_children_alias: [],
|
||||
parent: id,
|
||||
children: [],
|
||||
children_alias: [],
|
||||
is_simple_expression: false,
|
||||
order: -1,
|
||||
schema: -1,
|
||||
alias: alias,
|
||||
|
@ -269,7 +154,7 @@ export function isMockCst(cst: IConstituenta) {
|
|||
/**
|
||||
* Apply filter based on start {@link IConstituenta} type.
|
||||
*/
|
||||
export function applyFilterCategory(start: IConstituenta, schema: IRSFormData): IConstituenta[] {
|
||||
export function applyFilterCategory(start: IConstituenta, schema: IRSForm): IConstituenta[] {
|
||||
const nextCategory = schema.items.find(cst => cst.order > start.order && cst.cst_type === CATEGORY_CST_TYPE);
|
||||
return schema.items.filter(cst => cst.order >= start.order && (!nextCategory || cst.order < nextCategory.order));
|
||||
}
|
||||
|
@ -367,7 +252,7 @@ export function isFunctional(type: CstType): boolean {
|
|||
* Validate new alias against {@link CstType} and {@link IRSForm}.
|
||||
*/
|
||||
export function validateNewAlias(alias: string, type: CstType, schema: IRSForm): boolean {
|
||||
return alias.length >= 2 && alias[0] == getCstTypePrefix(type) && !schema.items.find(cst => cst.alias === alias);
|
||||
return alias.length >= 2 && alias[0] == getCstTypePrefix(type) && !schema.cstByAlias.has(alias);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -146,7 +146,7 @@ function FormConstituenta({
|
|||
id='cst_term'
|
||||
label='Термин'
|
||||
placeholder='Обозначение, используемое в текстовых определениях'
|
||||
items={schema?.items}
|
||||
schema={schema}
|
||||
value={term}
|
||||
initialValue={state?.term_raw ?? ''}
|
||||
resolved={state?.term_resolved ?? ''}
|
||||
|
@ -198,7 +198,7 @@ function FormConstituenta({
|
|||
label='Текстовое определение'
|
||||
placeholder='Текстовая интерпретация формального выражения'
|
||||
height='3.8rem'
|
||||
items={schema?.items}
|
||||
schema={schema}
|
||||
value={textDefinition}
|
||||
initialValue={state?.definition_raw ?? ''}
|
||||
resolved={state?.definition_resolved ?? ''}
|
||||
|
|
|
@ -62,8 +62,8 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
|||
|
||||
const [hoverID, setHoverID] = useState<ConstituentaID | undefined>(undefined);
|
||||
const hoverCst = useMemo(() => {
|
||||
return controller.schema?.items.find(cst => cst.id === hoverID);
|
||||
}, [controller.schema?.items, hoverID]);
|
||||
return hoverID && controller.schema?.cstByID.get(hoverID);
|
||||
}, [controller.schema?.cstByID, hoverID]);
|
||||
|
||||
const [toggleResetView, setToggleResetView] = useState(false);
|
||||
|
||||
|
@ -87,14 +87,14 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
|||
return result;
|
||||
}
|
||||
filtered.nodes.forEach(node => {
|
||||
const cst = controller.schema!.items.find(cst => cst.id === node.id);
|
||||
const cst = controller.schema!.cstByID.get(node.id);
|
||||
if (cst) {
|
||||
result.push({
|
||||
id: String(node.id),
|
||||
fill: colorBgGraphNode(cst, coloringScheme, colors),
|
||||
label: cst.alias,
|
||||
subLabel: !filterParams.noText ? cst.term_resolved : undefined,
|
||||
size: cst.derived_from_alias ? 1 : 2
|
||||
size: cst.parent_alias ? 1 : 2
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -121,9 +121,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
|
|||
if (!controller.schema) {
|
||||
return;
|
||||
}
|
||||
const definition = controller.selected
|
||||
.map(id => controller.schema!.items.find(cst => cst.id === id)!.alias)
|
||||
.join(' ');
|
||||
const definition = controller.selected.map(id => controller.schema!.cstByID.get(id)!.alias).join(' ');
|
||||
controller.createCst(controller.nothingSelected ? CstType.BASE : CstType.TERM, false, definition);
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,8 @@ function TermGraph({
|
|||
ref: graphRef,
|
||||
nodes: nodes,
|
||||
edges: edges,
|
||||
type: 'multi'
|
||||
type: 'multi',
|
||||
focusOnSelect: false
|
||||
});
|
||||
|
||||
const handleHoverIn = useCallback(
|
||||
|
|
|
@ -33,7 +33,7 @@ function ViewHidden({ items, selected, toggleSelection, schema, coloringScheme,
|
|||
[selected]
|
||||
);
|
||||
|
||||
if (items.length <= 0) {
|
||||
if (!schema || items.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
|
@ -43,7 +43,7 @@ function ViewHidden({ items, selected, toggleSelection, schema, coloringScheme,
|
|||
</p>
|
||||
<div className='flex flex-wrap justify-center gap-2 py-2 overflow-y-auto' style={{ maxHeight: dismissedHeight }}>
|
||||
{items.map(cstID => {
|
||||
const cst = schema!.items.find(cst => cst.id === cstID)!;
|
||||
const cst = schema.cstByID.get(cstID)!;
|
||||
const adjustedColoring = coloringScheme === 'none' ? 'status' : coloringScheme;
|
||||
const id = `${prefixes.cst_hidden_list}${cst.alias}`;
|
||||
return (
|
||||
|
|
|
@ -48,7 +48,7 @@ function useGraphFilter(schema: IRSForm | undefined, params: GraphFilterParams)
|
|||
}
|
||||
if (params.foldDerived) {
|
||||
schema.items.forEach(cst => {
|
||||
if (cst.derived_from_alias) {
|
||||
if (cst.parent_alias) {
|
||||
graph.foldNode(cst.id);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -205,7 +205,7 @@ export const RSEditState = ({
|
|||
items: deleted
|
||||
};
|
||||
|
||||
const deletedNames = deleted.map(id => model.schema?.items.find(cst => cst.id === id)?.alias).join(', ');
|
||||
const deletedNames = deleted.map(id => model.schema!.cstByID.get(id)!.alias).join(', ');
|
||||
const isEmpty = deleted.length === model.schema.items.length;
|
||||
const nextActive = isEmpty ? undefined : getNextActiveOnDelete(activeCst?.id, model.schema.items, deleted);
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ function RSTabs() {
|
|||
if (!schema || selected.length === 0) {
|
||||
return undefined;
|
||||
} else {
|
||||
return schema.items.find(cst => cst.id === selected.at(-1));
|
||||
return schema.cstByID.get(selected.at(-1)!);
|
||||
}
|
||||
}, [schema, selected]);
|
||||
|
||||
|
@ -69,7 +69,7 @@ function RSTabs() {
|
|||
setIsModified(false);
|
||||
if (activeTab === RSTabID.CST_EDIT) {
|
||||
const cstID = Number(cstQuery);
|
||||
if (cstID && schema && schema.items.find(cst => cst.id === cstID)) {
|
||||
if (cstID && schema && schema.cstByID.has(cstID)) {
|
||||
setSelected([cstID]);
|
||||
} else if (schema && schema?.items.length > 0) {
|
||||
setSelected([schema.items[0].id]);
|
||||
|
|
|
@ -110,13 +110,13 @@ function ConstituentsTable({ items, activeID, onOpenEdit, maxHeight, denseThresh
|
|||
}
|
||||
},
|
||||
{
|
||||
when: (cst: IConstituenta) => cst.derived_from === activeID && cst.id !== activeID,
|
||||
when: (cst: IConstituenta) => cst.parent === activeID && cst.id !== activeID,
|
||||
style: {
|
||||
backgroundColor: colors.bgOrange50
|
||||
}
|
||||
},
|
||||
{
|
||||
when: (cst: IConstituenta) => activeID !== undefined && cst.derived_children.includes(activeID),
|
||||
when: (cst: IConstituenta) => activeID !== undefined && cst.children.includes(activeID),
|
||||
style: {
|
||||
backgroundColor: colors.bgGreen50
|
||||
}
|
||||
|
|
|
@ -170,15 +170,15 @@ export function domTooltipConstituenta(cst?: IConstituenta) {
|
|||
dom.appendChild(convention);
|
||||
}
|
||||
|
||||
if (cst.derived_from_alias) {
|
||||
if (cst.parent_alias) {
|
||||
const derived = document.createElement('p');
|
||||
derived.innerHTML = `<b>Основание:</b> ${cst.derived_from_alias}`;
|
||||
derived.innerHTML = `<b>Основание:</b> ${cst.parent_alias}`;
|
||||
dom.appendChild(derived);
|
||||
}
|
||||
|
||||
if (cst.derived_children_alias.length > 0) {
|
||||
if (cst.children_alias.length > 0) {
|
||||
const children = document.createElement('p');
|
||||
children.innerHTML = `<b>Порождает:</b> ${cst.derived_children_alias.join(', ')}`;
|
||||
children.innerHTML = `<b>Порождает:</b> ${cst.children_alias.join(', ')}`;
|
||||
dom.appendChild(children);
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -292,8 +292,8 @@ export const mapLabelLayout: Map<string, string> = new Map([
|
|||
['treeTd3d', 'Граф: ДеревоВер 3D'],
|
||||
['treeLr2d', 'Граф: ДеревоГор 2D'],
|
||||
['treeLr3d', 'Граф: ДеревоГор 3D'],
|
||||
['radialOut2d', 'Граф: Радиальная 2D'],
|
||||
['radialOut3d', 'Граф: Радиальная 3D'],
|
||||
['radialOut2d', 'Граф: Радиус 2D'],
|
||||
['radialOut3d', 'Граф: Радиус 3D'],
|
||||
['circular2d', 'Граф: Круговая'],
|
||||
['hierarchicalTd', 'Граф: ИерархияВер'],
|
||||
['hierarchicalLr', 'Граф: ИерархияГор'],
|
||||
|
|
|
@ -22,8 +22,8 @@ export const SelectorGraphLayout: { value: LayoutTypes; label: string }[] = [
|
|||
{ value: 'forceDirected3d', label: 'Граф: Силы 3D' },
|
||||
{ value: 'treeLr2d', label: 'Граф: ДеревоГ 2D' },
|
||||
{ value: 'treeLr3d', label: 'Граф: ДеревоГ 3D' },
|
||||
{ value: 'radialOut2d', label: 'Граф: Радиальная 2D' },
|
||||
{ value: 'radialOut3d', label: 'Граф: Радиальная 3D' }
|
||||
{ value: 'radialOut2d', label: 'Граф: Радиус 2D' },
|
||||
{ value: 'radialOut3d', label: 'Граф: Радиус 3D' }
|
||||
// { value: 'circular2d', label: 'circular2d'},
|
||||
// { value: 'nooverlap', label: 'nooverlap'},
|
||||
// { value: 'hierarchicalTd', label: 'hierarchicalTd'},
|
||||
|
|
Loading…
Reference in New Issue
Block a user