Refactoring: use cached maps to access constituents

This commit is contained in:
IRBorisov 2024-04-07 15:38:24 +03:00
parent 5c52e2a6f8
commit 26fe49b352
26 changed files with 276 additions and 210 deletions

View File

@ -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(

View File

@ -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)];
}

View File

@ -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,

View File

@ -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)];
}

View File

@ -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 ? (

View File

@ -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);
}

View File

@ -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

View File

@ -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>

View File

@ -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)} />

View File

@ -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>

View File

@ -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}

View File

@ -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);
}

View 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)
};
}
}

View File

@ -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}.

View File

@ -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);
}
/**

View File

@ -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 ?? ''}

View File

@ -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);
}

View File

@ -45,7 +45,8 @@ function TermGraph({
ref: graphRef,
nodes: nodes,
edges: edges,
type: 'multi'
type: 'multi',
focusOnSelect: false
});
const handleHoverIn = useCallback(

View File

@ -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 (

View File

@ -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);
}
});

View File

@ -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);

View File

@ -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]);

View File

@ -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
}

View File

@ -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 {

View File

@ -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', 'Граф: ИерархияГор'],

View File

@ -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'},