mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-08-14 04:40:36 +03:00
Implement RSExpr check UI
This commit is contained in:
parent
35ef677c60
commit
4f45b9978e
|
@ -2,8 +2,8 @@ import { ThreeDots } from 'react-loader-spinner';
|
|||
|
||||
export function Loader() {
|
||||
return (
|
||||
<div className='flex justify-center'>
|
||||
<ThreeDots color='rgb(96 165 250)' height='100' width='100' radius='10' />
|
||||
<div className='flex justify-center w-full h-full'>
|
||||
<ThreeDots color='rgb(96 165 250)' height='100' width='100' radius='10' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,23 +7,18 @@ import Button from './Button';
|
|||
interface ModalProps {
|
||||
title?: string
|
||||
submitText?: string
|
||||
show: boolean
|
||||
canSubmit: boolean
|
||||
canSubmit?: boolean
|
||||
hideWindow: () => void
|
||||
onSubmit: () => void
|
||||
onCancel?: () => void
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
function Modal({ title, show, hideWindow, onSubmit, onCancel, canSubmit, children, submitText = 'Продолжить' }: ModalProps) {
|
||||
function Modal({ title, hideWindow, onSubmit, onCancel, canSubmit, children, submitText = 'Продолжить' }: ModalProps) {
|
||||
const ref = useRef(null);
|
||||
useClickedOutside({ ref, callback: hideWindow });
|
||||
useEscapeKey(hideWindow);
|
||||
|
||||
if (!show) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
hideWindow();
|
||||
if (onCancel) onCancel();
|
||||
|
|
|
@ -2,18 +2,17 @@ import { useCallback, useState } from 'react'
|
|||
|
||||
import { type ErrorInfo } from '../components/BackendError';
|
||||
import { DataCallback, postCheckExpression } from '../utils/backendAPI';
|
||||
import { ExpressionParse, type IRSForm } from '../utils/models';
|
||||
import { IExpressionParse, type IRSForm } from '../utils/models';
|
||||
|
||||
function useCheckExpression({ schema }: { schema?: IRSForm }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||
const [parseData, setParseData] = useState<ExpressionParse | undefined>(undefined);
|
||||
const [parseData, setParseData] = useState<IExpressionParse | undefined>(undefined);
|
||||
|
||||
const resetParse = useCallback(() => { setParseData(undefined); }, []);
|
||||
|
||||
function checkExpression(expression: string, onSuccess?: DataCallback<ExpressionParse>) {
|
||||
function checkExpression(expression: string, onSuccess?: DataCallback<IExpressionParse>) {
|
||||
setError(undefined);
|
||||
setParseData(undefined);
|
||||
postCheckExpression(String(schema?.id), {
|
||||
data: { expression: expression },
|
||||
showError: true,
|
||||
|
|
|
@ -77,7 +77,7 @@
|
|||
}
|
||||
|
||||
.text-red {
|
||||
@apply text-red-400 dark:text-red-600
|
||||
@apply text-red-600 dark:text-red-400
|
||||
}
|
||||
|
||||
.text-green {
|
||||
|
|
|
@ -11,11 +11,10 @@ import { IRSFormCreateData } from '../../utils/models';
|
|||
import { getCloneTitle } from '../../utils/staticUI';
|
||||
|
||||
interface DlgCloneRSFormProps {
|
||||
show: boolean
|
||||
hideWindow: () => void
|
||||
}
|
||||
|
||||
function DlgCloneRSForm({ show, hideWindow }: DlgCloneRSFormProps) {
|
||||
function DlgCloneRSForm({ hideWindow }: DlgCloneRSFormProps) {
|
||||
const navigate = useNavigate();
|
||||
const [title, setTitle] = useState('');
|
||||
const [alias, setAlias] = useState('');
|
||||
|
@ -34,7 +33,6 @@ function DlgCloneRSForm({ show, hideWindow }: DlgCloneRSFormProps) {
|
|||
}, [schema, schema?.title, schema?.alias, schema?.comment, schema?.is_common]);
|
||||
|
||||
const handleSubmit = () => {
|
||||
hideWindow();
|
||||
const data: IRSFormCreateData = {
|
||||
title: title,
|
||||
alias: alias,
|
||||
|
@ -50,7 +48,6 @@ function DlgCloneRSForm({ show, hideWindow }: DlgCloneRSFormProps) {
|
|||
return (
|
||||
<Modal
|
||||
title='Создание копии концептуальной схемы'
|
||||
show={show}
|
||||
hideWindow={hideWindow}
|
||||
canSubmit={true}
|
||||
submitText='Создать'
|
||||
|
@ -76,7 +73,7 @@ function DlgCloneRSForm({ show, hideWindow }: DlgCloneRSFormProps) {
|
|||
onChange={event => { setCommon(event.target.checked); }}
|
||||
/>
|
||||
</Modal>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default DlgCloneRSForm;
|
||||
|
|
|
@ -6,13 +6,12 @@ import { type CstType } from '../../utils/models';
|
|||
import { CstTypeSelector, getCstTypeLabel } from '../../utils/staticUI';
|
||||
|
||||
interface DlgCreateCstProps {
|
||||
show: boolean
|
||||
hideWindow: () => void
|
||||
defaultType?: CstType
|
||||
onCreate: (type: CstType) => void
|
||||
}
|
||||
|
||||
function DlgCreateCst({ show, hideWindow, defaultType, onCreate }: DlgCreateCstProps) {
|
||||
function DlgCreateCst({ hideWindow, defaultType, onCreate }: DlgCreateCstProps) {
|
||||
const [validated, setValidated] = useState(false);
|
||||
const [selectedType, setSelectedType] = useState<CstType | undefined>(undefined);
|
||||
|
||||
|
@ -32,7 +31,6 @@ function DlgCreateCst({ show, hideWindow, defaultType, onCreate }: DlgCreateCstP
|
|||
return (
|
||||
<Modal
|
||||
title='Создание конституенты'
|
||||
show={show}
|
||||
hideWindow={hideWindow}
|
||||
canSubmit={validated}
|
||||
onSubmit={handleSubmit}
|
||||
|
@ -45,7 +43,7 @@ function DlgCreateCst({ show, hideWindow, defaultType, onCreate }: DlgCreateCstP
|
|||
onChange={(data) => { setSelectedType(data?.value); }}
|
||||
/>
|
||||
</Modal>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default DlgCreateCst;
|
||||
|
|
30
rsconcept/frontend/src/pages/RSFormPage/DlgShowAST.tsx
Normal file
30
rsconcept/frontend/src/pages/RSFormPage/DlgShowAST.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
import Modal from '../../components/Common/Modal';
|
||||
import PrettyJson from '../../components/Common/PrettyJSON';
|
||||
import { SyntaxTree } from '../../utils/models';
|
||||
|
||||
interface DlgShowASTProps {
|
||||
hideWindow: () => void
|
||||
syntaxTree: SyntaxTree
|
||||
}
|
||||
|
||||
function DlgShowAST({ hideWindow, syntaxTree }: DlgShowASTProps) {
|
||||
const handleSubmit = () => {
|
||||
// Do nothing
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title='Просмотр дерева разбора'
|
||||
hideWindow={hideWindow}
|
||||
onSubmit={handleSubmit}
|
||||
submitText='Закрыть'
|
||||
canSubmit={true}
|
||||
>
|
||||
<div className='max-w-[40rem] max-h-[30rem] overflow-auto'>
|
||||
<PrettyJson data={syntaxTree}/>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default DlgShowAST;
|
|
@ -8,17 +8,15 @@ import { useRSForm } from '../../context/RSFormContext';
|
|||
import { IRSFormUploadData } from '../../utils/models';
|
||||
|
||||
interface DlgUploadRSFormProps {
|
||||
show: boolean
|
||||
hideWindow: () => void
|
||||
}
|
||||
|
||||
function DlgUploadRSForm({ show, hideWindow }: DlgUploadRSFormProps) {
|
||||
function DlgUploadRSForm({ hideWindow }: DlgUploadRSFormProps) {
|
||||
const { upload } = useRSForm();
|
||||
const [loadMetadata, setLoadMetadata] = useState(false);
|
||||
const [file, setFile] = useState<File | undefined>()
|
||||
|
||||
const handleSubmit = () => {
|
||||
hideWindow();
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
@ -41,7 +39,6 @@ function DlgUploadRSForm({ show, hideWindow }: DlgUploadRSFormProps) {
|
|||
return (
|
||||
<Modal
|
||||
title='Загрузка схемы из Экстеор'
|
||||
show={show}
|
||||
hideWindow={hideWindow}
|
||||
canSubmit={!!file}
|
||||
onSubmit={handleSubmit}
|
||||
|
@ -57,7 +54,7 @@ function DlgUploadRSForm({ show, hideWindow }: DlgUploadRSFormProps) {
|
|||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default DlgUploadRSForm;
|
||||
|
|
|
@ -7,14 +7,18 @@ import SubmitButton from '../../components/Common/SubmitButton';
|
|||
import TextArea from '../../components/Common/TextArea';
|
||||
import { DumpBinIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import { type CstType, EditMode, type ICstCreateData, ICstUpdateData } from '../../utils/models';
|
||||
import { type CstType, EditMode, type ICstCreateData, ICstUpdateData, SyntaxTree } from '../../utils/models';
|
||||
import { createAliasFor, getCstTypeLabel } from '../../utils/staticUI';
|
||||
import DlgCreateCst from './DlgCreateCst';
|
||||
import EditorRSExpression from './EditorRSExpression';
|
||||
import ViewSideConstituents from './elements/ViewSideConstituents';
|
||||
import { RSTabsList } from './RSTabs';
|
||||
|
||||
function EditorConstituenta() {
|
||||
interface EditorConstituentaProps {
|
||||
onShowAST: (ast: SyntaxTree) => void
|
||||
}
|
||||
|
||||
function EditorConstituenta({onShowAST}: EditorConstituentaProps) {
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
activeCst, activeID, schema, setActiveID, processing, isEditable,
|
||||
|
@ -132,12 +136,11 @@ function EditorConstituenta() {
|
|||
|
||||
return (
|
||||
<div className='flex items-start w-full gap-2'>
|
||||
<DlgCreateCst
|
||||
show={showCstModal}
|
||||
{showCstModal && <DlgCreateCst
|
||||
hideWindow={() => { setShowCstModal(false); }}
|
||||
onCreate={handleAddNew}
|
||||
defaultType={activeCst?.cstType as CstType}
|
||||
/>
|
||||
/>}
|
||||
<form onSubmit={handleSubmit} className='flex-grow min-w-[50rem] max-w-min px-4 py-2 border'>
|
||||
<div className='flex items-start justify-between'>
|
||||
<button type='submit'
|
||||
|
@ -204,6 +207,7 @@ function EditorConstituenta() {
|
|||
disabled={!isEnabled}
|
||||
isActive={editMode === EditMode.RSLANG}
|
||||
toggleEditMode={() => { setEditMode(EditMode.RSLANG); }}
|
||||
onShowAST={onShowAST}
|
||||
onChange={event => { setExpression(event.target.value); }}
|
||||
setValue={setExpression}
|
||||
setTypification={setTypification}
|
||||
|
|
|
@ -284,11 +284,10 @@ function EditorItems({ onOpenEdit }: EditorItemsProps) {
|
|||
);
|
||||
|
||||
return (<>
|
||||
<DlgCreateCst
|
||||
show={showCstModal}
|
||||
{showCstModal && <DlgCreateCst
|
||||
hideWindow={() => { setShowCstModal(false); }}
|
||||
onCreate={handleAddNew}
|
||||
/>
|
||||
/>}
|
||||
<div className='w-full'>
|
||||
<div
|
||||
className={'flex justify-start w-full gap-1 px-2 py-1 border-y items-center h-[2.2rem] clr-app' +
|
||||
|
|
|
@ -7,7 +7,7 @@ import { Loader } from '../../components/Common/Loader';
|
|||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import useCheckExpression from '../../hooks/useCheckExpression';
|
||||
import { TokenID } from '../../utils/enums';
|
||||
import { CstType } from '../../utils/models';
|
||||
import { CstType, SyntaxTree } from '../../utils/models';
|
||||
import ParsingResult from './elements/ParsingResult';
|
||||
import RSLocalButton from './elements/RSLocalButton';
|
||||
import RSTokenButton from './elements/RSTokenButton';
|
||||
|
@ -22,13 +22,14 @@ interface EditorRSExpressionProps {
|
|||
placeholder?: string
|
||||
value: string
|
||||
onChange: (event: React.ChangeEvent<HTMLTextAreaElement>) => void
|
||||
onShowAST: (ast: SyntaxTree) => void
|
||||
toggleEditMode: () => void
|
||||
setTypification: (typificaiton: string) => void
|
||||
setValue: (expression: string) => void
|
||||
}
|
||||
|
||||
function EditorRSExpression({
|
||||
id, label, disabled, isActive, placeholder, value, setValue,
|
||||
id, label, disabled, isActive, placeholder, value, setValue, onShowAST,
|
||||
toggleEditMode, setTypification, onChange
|
||||
}: EditorRSExpressionProps) {
|
||||
const { schema, activeCst } = useRSForm();
|
||||
|
@ -48,11 +49,11 @@ function EditorRSExpression({
|
|||
const prefix = activeCst?.alias + (activeCst?.cstType === CstType.STRUCTURED ? '::=' : ':==');
|
||||
const expression = prefix + value;
|
||||
checkExpression(expression, parse => {
|
||||
// TODO: update cursor position
|
||||
if (!parse.parseResult && parse.errors.length > 0) {
|
||||
const errorPosition = parse.errors[0].position - prefix.length
|
||||
expressionCtrl.current!.selectionStart = errorPosition;
|
||||
expressionCtrl.current!.selectionEnd = errorPosition;
|
||||
expressionCtrl.current!.focus();
|
||||
}
|
||||
setIsModified(false);
|
||||
setTypification(parse.typification);
|
||||
|
@ -85,25 +86,26 @@ function EditorRSExpression({
|
|||
if (!expressionCtrl.current) {
|
||||
return;
|
||||
}
|
||||
if (event.altKey) {
|
||||
const text = new TextWrapper(expressionCtrl.current);
|
||||
if (text.processAltKey(event.key)) {
|
||||
event.preventDefault();
|
||||
text.finalize();
|
||||
setValue(text.value);
|
||||
setIsModified(true);
|
||||
const text = new TextWrapper(expressionCtrl.current);
|
||||
if (event.shiftKey && event.key === '*' && !event.altKey) {
|
||||
text.insertToken(TokenID.DECART);
|
||||
} else if (event.altKey) {
|
||||
if (!text.processAltKey(event.key)) {
|
||||
return;
|
||||
}
|
||||
} else if (!event.ctrlKey) {
|
||||
const newSymbol = getSymbolSubstitute(event.key);
|
||||
if (newSymbol) {
|
||||
event.preventDefault();
|
||||
const text = new TextWrapper(expressionCtrl.current);
|
||||
text.replaceWith(newSymbol);
|
||||
text.finalize();
|
||||
setValue(text.value);
|
||||
setIsModified(true);
|
||||
if (!newSymbol) {
|
||||
return;
|
||||
}
|
||||
text.replaceWith(newSymbol);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
text.finalize();
|
||||
setValue(text.value);
|
||||
setIsModified(true);
|
||||
}, [expressionCtrl, setValue]);
|
||||
|
||||
const handleFocusIn = useCallback(() => {
|
||||
|
@ -225,8 +227,11 @@ function EditorRSExpression({
|
|||
parseData={parseData}
|
||||
/>}
|
||||
</div>
|
||||
{ loading && <Loader />}
|
||||
{ parseData && <ParsingResult data={parseData} />}
|
||||
{ (loading || parseData) &&
|
||||
<div className='w-full overflow-y-auto border mt-2 max-h-[14rem] min-h-[7rem]'>
|
||||
{ loading && <Loader />}
|
||||
{ !loading && parseData && <ParsingResult data={parseData} onShowAST={onShowAST} />}
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
|
||||
import BackendError from '../../components/BackendError';
|
||||
|
@ -6,8 +6,9 @@ import ConceptTab from '../../components/Common/ConceptTab';
|
|||
import { Loader } from '../../components/Common/Loader';
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import useLocalStorage from '../../hooks/useLocalStorage';
|
||||
import { type IConstituenta } from '../../utils/models';
|
||||
import { type IConstituenta,SyntaxTree } from '../../utils/models';
|
||||
import DlgCloneRSForm from './DlgCloneRSForm';
|
||||
import DlgShowAST from './DlgShowAST';
|
||||
import DlgUploadRSForm from './DlgUploadRSForm';
|
||||
import EditorConstituenta from './EditorConstituenta';
|
||||
import EditorItems from './EditorItems';
|
||||
|
@ -26,8 +27,16 @@ function RSTabs() {
|
|||
const [tabIndex, setTabIndex] = useLocalStorage('rsform_edit_tab', RSTabsList.CARD);
|
||||
const [init, setInit] = useState(false);
|
||||
|
||||
const [showUploadDialog, setShowUploadDialog] = useState(false);
|
||||
const [showCloneDialog, setShowCloneDialog] = useState(false);
|
||||
const [showUpload, setShowUpload] = useState(false);
|
||||
const [showClone, setShowClone] = useState(false);
|
||||
const [syntaxTree, setSyntaxTree] = useState<SyntaxTree>([]);
|
||||
const [showAST, setShowAST] = useState(false);
|
||||
|
||||
const onShowAST = useCallback(
|
||||
(ast: SyntaxTree) => {
|
||||
setSyntaxTree(ast);
|
||||
setShowAST(true);
|
||||
}, [])
|
||||
|
||||
const onEditCst = (cst: IConstituenta) => {
|
||||
setActiveID(cst.id);
|
||||
|
@ -90,14 +99,9 @@ function RSTabs() {
|
|||
{ error && <BackendError error={error} />}
|
||||
{ schema && !loading &&
|
||||
<>
|
||||
<DlgUploadRSForm
|
||||
show={showUploadDialog}
|
||||
hideWindow={() => { setShowUploadDialog(false); }}
|
||||
/>
|
||||
<DlgCloneRSForm
|
||||
show={showCloneDialog}
|
||||
hideWindow={() => { setShowCloneDialog(false); }}
|
||||
/>
|
||||
{showUpload && <DlgUploadRSForm hideWindow={() => { setShowUpload(false); }}/>}
|
||||
{showClone && <DlgCloneRSForm hideWindow={() => { setShowClone(false); }}/>}
|
||||
{showAST && <DlgShowAST syntaxTree={syntaxTree} hideWindow={() => { setShowAST(false); }}/>}
|
||||
<Tabs
|
||||
selectedIndex={tabIndex}
|
||||
onSelect={onSelectTab}
|
||||
|
@ -106,8 +110,8 @@ function RSTabs() {
|
|||
>
|
||||
<TabList className='flex items-start w-fit clr-bg-pop'>
|
||||
<RSTabsMenu
|
||||
showCloneDialog={() => setShowCloneDialog(true)}
|
||||
showUploadDialog={() => setShowUploadDialog(true)}
|
||||
showCloneDialog={() => setShowClone(true)}
|
||||
showUploadDialog={() => setShowUpload(true)}
|
||||
/>
|
||||
<ConceptTab>Паспорт схемы</ConceptTab>
|
||||
<ConceptTab className='border-x-2 clr-border min-w-[10rem] flex justify-between gap-2'>
|
||||
|
@ -127,7 +131,7 @@ function RSTabs() {
|
|||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<EditorConstituenta />
|
||||
<EditorConstituenta onShowAST={onShowAST} />
|
||||
</TabPanel>
|
||||
</Tabs></>
|
||||
}
|
||||
|
|
|
@ -1,20 +1,41 @@
|
|||
import PrettyJson from '../../../components/Common/PrettyJSON';
|
||||
import { ExpressionParse } from '../../../utils/models';
|
||||
import { IExpressionParse, SyntaxTree } from '../../../utils/models';
|
||||
import { getRSErrorMessage, getRSErrorPrefix } from '../../../utils/staticUI';
|
||||
|
||||
interface ParsingResultProps {
|
||||
data?: ExpressionParse
|
||||
data: IExpressionParse
|
||||
onShowAST: (ast: SyntaxTree) => void
|
||||
}
|
||||
|
||||
function ParsingResult({ data }: ParsingResultProps) {
|
||||
function ParsingResult({ data, onShowAST }: ParsingResultProps) {
|
||||
const errorCount = data.errors.reduce((total, error) => (error.isCritical ? total + 1 : total), 0);
|
||||
const warningsCount = data.errors.length - errorCount;
|
||||
|
||||
function handleShowAST() {
|
||||
onShowAST(data.ast);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='w-full px-3 py-2 mt-2 border'>
|
||||
<PrettyJson data={data} />
|
||||
{/* <textarea
|
||||
className={'leading-tight border shadow dark:bg-gray-800 '}
|
||||
rows={3}
|
||||
placeholder='Результаты анализа выражения'
|
||||
value={data}
|
||||
/> */}
|
||||
<div className='px-3 py-2'>
|
||||
<p>Ошибок: <b>{errorCount}</b> | Предупреждений: <b>{warningsCount}</b></p>
|
||||
{data.errors.map(error => {
|
||||
return (
|
||||
<p className='text-red'>
|
||||
<span className='font-semibold'>{error.isCritical ? 'Ошибка' : 'Предупреждение'} {getRSErrorPrefix(error)}: </span>
|
||||
<span>{getRSErrorMessage(error)}</span>
|
||||
</p>
|
||||
);
|
||||
})}
|
||||
{data.astText &&
|
||||
<p>
|
||||
<button type='button'
|
||||
className='font-semibold underline text-primary'
|
||||
title='отобразить дерево разбора'
|
||||
onClick={handleShowAST}
|
||||
>
|
||||
Дерево разбора:
|
||||
</button>
|
||||
<span> {data.astText}</span>
|
||||
</p>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { useMemo } from 'react';
|
||||
|
||||
import { ExpressionParse,ExpressionStatus, type IConstituenta, inferStatus, ParsingStatus } from '../../../utils/models';
|
||||
import { ExpressionStatus, type IConstituenta, IExpressionParse,inferStatus, ParsingStatus } from '../../../utils/models';
|
||||
import { getStatusInfo } from '../../../utils/staticUI';
|
||||
|
||||
interface StatusBarProps {
|
||||
isModified?: boolean
|
||||
parseData?: ExpressionParse
|
||||
parseData?: IExpressionParse
|
||||
constituenta?: IConstituenta
|
||||
}
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ function ViewSideConstituents({ expression }: ViewSideConstituentsProps) {
|
|||
name: 'Описание',
|
||||
id: 'description',
|
||||
selector: (cst: IConstituenta) =>
|
||||
cst.term?.resolved ?? cst.definition?.text.resolved ?? cst.definition?.formal ?? cst.convention ?? '',
|
||||
cst.term.resolved || cst.definition.text.resolved || cst.definition.formal || cst.convention,
|
||||
minWidth: '350px',
|
||||
wrap: true,
|
||||
conditionalCellStyles: [
|
||||
|
|
|
@ -171,6 +171,7 @@ export class TextWrapper implements IManagedText {
|
|||
case 'q': return this.insertToken(TokenID.BIGPR);
|
||||
case 'w': return this.insertToken(TokenID.SMALLPR);
|
||||
case 'e': return this.insertToken(TokenID.BOOLEAN);
|
||||
case 'E': return this.insertToken(TokenID.DECART);
|
||||
case 'r': return this.insertToken(TokenID.REDUCE);
|
||||
case 't': return this.insertToken(TokenID.NT_RECURSIVE_FULL);
|
||||
case 'a': return this.insertToken(TokenID.INTERSECTION);
|
||||
|
|
|
@ -5,14 +5,14 @@ import { type ErrorInfo } from '../components/BackendError'
|
|||
import { FilterType, RSFormsFilter } from '../hooks/useRSForms'
|
||||
import { config } from './constants'
|
||||
import {
|
||||
ExpressionParse,
|
||||
IConstituentaList,
|
||||
IConstituentaMeta,
|
||||
ICstCreateData, ICstCreatedResponse, ICstMovetoData, ICstUpdateData,
|
||||
ICurrentUser, IRSFormCreateData, IRSFormData,
|
||||
ICurrentUser, IExpressionParse,
|
||||
IRSExpression,
|
||||
IRSFormCreateData, IRSFormData,
|
||||
IRSFormMeta, IRSFormUpdateData, IRSFormUploadData, IUserInfo,
|
||||
IUserLoginData, IUserProfile, IUserSignupData, RSExpression
|
||||
} from './models'
|
||||
IUserLoginData, IUserProfile, IUserSignupData} from './models'
|
||||
|
||||
// ================ Data transfer types ================
|
||||
export type DataCallback<ResponseData = undefined> = (data: ResponseData) => void;
|
||||
|
@ -201,7 +201,7 @@ export function patchMoveConstituenta(schema: string, request: FrontExchange<ICs
|
|||
});
|
||||
}
|
||||
|
||||
export function postCheckExpression(schema: string, request: FrontExchange<RSExpression, ExpressionParse>) {
|
||||
export function postCheckExpression(schema: string, request: FrontExchange<IRSExpression, IExpressionParse>) {
|
||||
AxiosPost({
|
||||
title: `Check expression for RSForm id=${schema}: ${request.data.expression }`,
|
||||
endpoint: `${config.url.BASE}rsforms/${schema}/check/`,
|
||||
|
|
|
@ -103,58 +103,72 @@ export enum TokenID {
|
|||
END,
|
||||
}
|
||||
|
||||
export enum RSError {
|
||||
|
||||
export enum RSErrorClass {
|
||||
LEXER,
|
||||
PARSER,
|
||||
SEMANTIC,
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
// '8201': 'Число превышает максимально допустимое значение 2147483647!',
|
||||
// '8203': 'Нераспознанный символ!',
|
||||
// '8400': 'Неопределенная синтаксическая ошибка!',
|
||||
// '8406': 'Пропущена скобка ‘)’!',
|
||||
// '8407': 'Пропущена скобка ‘}’!',
|
||||
// '8408': 'Некорректная кванторная декларация переменной!',
|
||||
// '8414': 'Некорректное объявление аргументов функции!',
|
||||
// '8415': 'Некорректное имя локальной переменной в декларации функции!',
|
||||
// '2801': 'Повторное объявление локальной переменной!',
|
||||
// '2802': 'Локальная переменная объявлена, но не использована!',
|
||||
// '8801': 'Использование необъявленной локальной переменной!',
|
||||
// '8802': 'Повторное объявление локальной переменной внутри области действия!',
|
||||
// '8803': 'Типизация операндов не совпадает!',
|
||||
// '8804': 'Использована конституента с неопределенной типизацией!',
|
||||
// '8805': 'Одна из проекций декартова произведения не является типизированным множеством имеющим характер множества!',
|
||||
// '8806': 'Аргумент булеана не является типизированным множеством имеющим характер множества!',
|
||||
// '8807': 'Операнд теоретико-множественного оператора не является типизированным множеством имеющим характер множества!',
|
||||
// '8808': 'Операнд оператора card не является типизированным множеством имеющим характер множества!',
|
||||
// '8809': 'Операнд оператора debool не является типизированным множеством имеющим характер множества!',
|
||||
// '880A': 'Неизвестное имя функции!',
|
||||
// '880B': 'Некорректное использование имени функции без аргументов!',
|
||||
// '8810': 'Операнд оператора red не является типизированным множеством имеющим характер двойного булеана!',
|
||||
// '8811': 'Некорректная типизация аргумента: проекция не определена!',
|
||||
// '8812': 'Некорректная типизация аргумента: T(Pri(a)) = B(Pi(D(T(a))))!',
|
||||
// '8813': 'Типизация элементов перечисления не совпадает!',
|
||||
// '8814': 'Некорректная декларация связанных локальных переменных: количестве переменных в кортеже не соответствует размерности декартова произведения типизации!',
|
||||
// '8815': 'Локальная переменная используется вне области действия!',
|
||||
// '8816': 'Несоответствие типизаций операндов для предиката!',
|
||||
// '8818': 'Некорректное количество аргументов терм-функции!',
|
||||
// '8819': 'Типизация аргумента терм-функции не совпадает с объявленной!',
|
||||
// '881A': 'Сравнение кортежа или элемента с пустым множеством!',
|
||||
// '881C': 'Выражение родовой структуры должно быть ступенью!',
|
||||
// '881F': 'Ожидалось выражение объявления функции!',
|
||||
// '8820': 'Некорректное использование пустого множества как типизированного выражения!',
|
||||
// '8821': 'Радикалы запрещены вне деклараций терм-функций!',
|
||||
// '8822': 'Типизация аргумента фильтра не корректна!',
|
||||
// '8823': 'Количество параметров фильтра не соответствует количеству индексов!',
|
||||
// '8824': 'Для выбранного типа не поддерживаются арифметические операции!',
|
||||
// '8825': 'Типизации не совместимы для выбранной операции/предиката!',
|
||||
// '8826': 'Для выбранного типа не поддерживаются предикаты порядка!',
|
||||
// '8840': 'Используется неинтерпретируемый глобальный идентификатор!',
|
||||
// '8841': 'Использование свойства в качестве значения!',
|
||||
// '8842': 'Не удалось получить дерево разбора для глобального идентификатора!',
|
||||
// '8843': 'Функция не интерпретируется для данных аргументов!',
|
||||
// '8A00': 'Неизвестная ошибка: вычисление прервано!',
|
||||
// '8A01': 'Превышен пределен количества элементов множества!',
|
||||
// '8A02': 'Превышен пределен количества элементов в основании булеана!',
|
||||
// '8A03': 'Использование конституенты с неопределенным значением!',
|
||||
// '8A04': 'Превышен предел количества итераций!',
|
||||
// '8A05': 'Попытка взять debool от многоэлементного множества!',
|
||||
// '8A06': 'Попытка перебрать бесконечное множество!'
|
||||
export enum RSErrorType {
|
||||
syntax = 0x8400, // Неизвестная синтаксическая ошибка
|
||||
missingParanthesis = 0x8406, // Пропущена скобка ')'
|
||||
missingCurlyBrace = 0x8407, // Пропущена скобка '}'
|
||||
invalidQuantifier = 0x8408, // Некорректная кванторная декларация
|
||||
expectedArgDeclaration = 0x8414, // Ожидалось объявление аргументов
|
||||
expectedLocal = 0x8415, // Ожидалось имя локальной переменной
|
||||
localDoubleDeclare = 0x2801, // Повторное использование одного и того же имени переменной
|
||||
localNotUsed = 0x2802, // Переменная объявлена но не использована
|
||||
|
||||
localUndeclared = 0x8801, // Использование необъявленной переменной
|
||||
localShadowing = 0x8802, // Повторное объявление переменной
|
||||
|
||||
typesNotEqual = 0x8803, // Некорректное использование операций
|
||||
globalNotTyped = 0x8804, // Не определена типизация глобальной конституенты
|
||||
invalidDecart = 0x8805, // Одна из проекций не является множеством
|
||||
invalidBoolean = 0x8806, // Попытка взять булеан от элемента, не имеющего характер множества
|
||||
invalidTypeOperation = 0x8807, // Применение ТМО к операндам, не имеющим характер множества
|
||||
invalidCard = 0x8808, // Мощность множества не определена для элемента
|
||||
invalidDebool = 0x8809, // Дебулеан берется от немножества
|
||||
globalFuncMissing = 0x880A, // Неизвестное имя функции
|
||||
globalFuncWithoutArgs = 0x880B, // Некорректное использование имени функции без аргументов
|
||||
invalidReduce = 0x8810, // Red можно брать только от двойного булеана
|
||||
invalidProjectionTuple = 0x8811, // Не определена проекция
|
||||
invalidProjectionSet = 0x8812, // Большая проекция определена только для множеств!
|
||||
invalidEnumeration = 0x8813, // Типизация аргументов перечисления не совпадает
|
||||
ivalidBinding = 0x8814, // Количество переменных в кортеже не соответствует размерности декартова произведения
|
||||
localOutOfScope = 0x8815, // Использование имени вне области видимости
|
||||
invalidElementPredicat = 0x8816, // Несоответствие типов для проверки принадлежности
|
||||
invalidArgsArtity = 0x8818, // Некорректное количество аргументов терм-функции
|
||||
invalidArgumentType = 0x8819, // Типизация аргумента не совпадает с объявленной
|
||||
invalidEqualsEmpty = 0x881A, // Сравнение с пустым множеством не множества
|
||||
globalStructure = 0x881C, // Родовая структура должна быть ступенью
|
||||
globalExpectedFunction = 0x881F, // Ожидалось выражение объявления функции
|
||||
emptySetUsage = 0x8820, // Некорректное использование пустого множества как типизированного выражения
|
||||
radicalUsage = 0x8821, // Радикалы запрещены вне деклараций терм-функций
|
||||
invalidFilterArgumentType = 0x8822, // Типизация аргумента фильтра не корректна
|
||||
invalidFilterArity = 0x8823, // Количество параметров фильра не соответствует количеству индексов
|
||||
arithmeticNotSupported = 0x8824, // Для данного типа не поддерживается арифметика
|
||||
typesNotCompatible = 0x8825, // Типы не совместимы в данном контексте
|
||||
orderingNotSupported = 0x8826, // Для данного типа не поддерживается порядок элементов
|
||||
|
||||
globalNoValue = 0x8840, // Используется неинтерпретируемый глобальный идентификатор
|
||||
invalidPropertyUsage = 0x8841, // Использование свойства в качестве значения
|
||||
globalMissingAST = 0x8842, // Не удалось получить дерево разбора для глобального идентификатора
|
||||
globalFuncNoInterpretation = 0x8843, // Функция не интерпретируется для данных аргументов
|
||||
}
|
||||
|
||||
const ERRCODE_LEXER_MASK = 0x0200;
|
||||
const ERRCODE_PARSER_MASK = 0x0400;
|
||||
const ERRCODE_TYPE_MASK = 0x0800;
|
||||
export function resolveErrorClass(error: RSErrorType): RSErrorClass {
|
||||
if ((error & ERRCODE_LEXER_MASK) != 0) {
|
||||
return RSErrorClass.LEXER;
|
||||
} else if ((error & ERRCODE_PARSER_MASK) != 0) {
|
||||
return RSErrorClass.PARSER;
|
||||
} else if ((error & ERRCODE_TYPE_MASK) != 0) {
|
||||
return RSErrorClass.SEMANTIC;
|
||||
} else {
|
||||
return RSErrorClass.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { RSErrorType, TokenID } from './enums'
|
||||
|
||||
// ========= Users ===========
|
||||
export interface IUser {
|
||||
id: number | null
|
||||
|
@ -21,45 +23,53 @@ export interface IUserSignupData extends Omit<IUser, 'is_staff' | 'id'> {
|
|||
export interface IUserProfile extends Omit<IUser, 'is_staff'> {}
|
||||
export interface IUserInfo extends Omit<IUserProfile, 'email'> {}
|
||||
|
||||
// ======== Parsing ============
|
||||
// ValueClass
|
||||
export enum ValueClass {
|
||||
INVALID = 'invalid',
|
||||
VALUE = 'value',
|
||||
PROPERTY = 'property'
|
||||
}
|
||||
|
||||
// Syntax
|
||||
// ======== RS Parsing ============
|
||||
export enum Syntax {
|
||||
UNDEF = 'undefined',
|
||||
ASCII = 'ascii',
|
||||
MATH = 'math'
|
||||
}
|
||||
|
||||
// ParsingStatus
|
||||
export enum ValueClass {
|
||||
INVALID = 'invalid',
|
||||
VALUE = 'value',
|
||||
PROPERTY = 'property'
|
||||
}
|
||||
|
||||
export enum ParsingStatus {
|
||||
UNDEF = 'undefined',
|
||||
VERIFIED = 'verified',
|
||||
INCORRECT = 'incorrect'
|
||||
}
|
||||
|
||||
export interface RSErrorDescription {
|
||||
errorType: number
|
||||
export interface IRSErrorDescription {
|
||||
errorType: RSErrorType
|
||||
position: number
|
||||
isCritical: boolean
|
||||
params: string[]
|
||||
}
|
||||
|
||||
export interface ExpressionParse {
|
||||
export interface ISyntaxTreeNode {
|
||||
uid: number
|
||||
parent: number
|
||||
typeID: TokenID
|
||||
start: number
|
||||
finish: number
|
||||
data: unknown
|
||||
}
|
||||
export type SyntaxTree = ISyntaxTreeNode[]
|
||||
|
||||
export interface IExpressionParse {
|
||||
parseResult: boolean
|
||||
syntax: Syntax
|
||||
typification: string
|
||||
valueClass: ValueClass
|
||||
errors: IRSErrorDescription[]
|
||||
astText: string
|
||||
errors: RSErrorDescription[]
|
||||
ast: SyntaxTree
|
||||
}
|
||||
|
||||
export interface RSExpression {
|
||||
export interface IRSExpression {
|
||||
expression: string
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { TokenID } from './enums';
|
||||
import { CstType, ExpressionStatus, type IConstituenta, type IRSForm, ParsingStatus, ValueClass } from './models';
|
||||
import { resolveErrorClass,RSErrorClass, RSErrorType, TokenID } from './enums';
|
||||
import { CstType, ExpressionStatus, type IConstituenta, IRSErrorDescription,type IRSForm, ParsingStatus, ValueClass } from './models';
|
||||
|
||||
export interface IRSButtonData {
|
||||
text: string
|
||||
|
@ -30,7 +30,7 @@ export function getRSButtonData(id: TokenID): IRSButtonData {
|
|||
};
|
||||
case TokenID.DECART: return {
|
||||
text: '×',
|
||||
tooltip: 'Декартово произведение [Shift + 8]'
|
||||
tooltip: 'Декартово произведение [Shift + 8 / Alt + Shift + E]'
|
||||
};
|
||||
case TokenID.PUNC_PL: return {
|
||||
text: '( )',
|
||||
|
@ -321,4 +321,104 @@ export function getCloneTitle(schema: IRSForm): string {
|
|||
} else {
|
||||
return (schema.title + '+');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getRSErrorPrefix(error: IRSErrorDescription): string {
|
||||
const id = error.errorType.toString(16)
|
||||
switch(resolveErrorClass(error.errorType)) {
|
||||
case RSErrorClass.LEXER: return 'L' + id;
|
||||
case RSErrorClass.PARSER: return 'P' + id;
|
||||
case RSErrorClass.SEMANTIC: return 'S' + id;
|
||||
case RSErrorClass.UNKNOWN: return 'U' + id;
|
||||
}
|
||||
}
|
||||
|
||||
export function getRSErrorMessage(error: IRSErrorDescription): string {
|
||||
switch (error.errorType) {
|
||||
case RSErrorType.syntax:
|
||||
return 'UNKNOWN SYNTAX ERROR';
|
||||
case RSErrorType.missingParanthesis:
|
||||
return 'Некорректная конструкция языка родов структур, проверьте структуру выражения';
|
||||
case RSErrorType.missingCurlyBrace:
|
||||
return "Пропущен символ '}'";
|
||||
case RSErrorType.invalidQuantifier:
|
||||
return 'Некорректная кванторная декларация';
|
||||
case RSErrorType.expectedArgDeclaration:
|
||||
return 'Ожидалось объявление аргументов терм-функции';
|
||||
case RSErrorType.expectedLocal:
|
||||
return 'Ожидалось имя локальной переменной';
|
||||
|
||||
case RSErrorType.localDoubleDeclare:
|
||||
return `Предупреждение! Повторное объявление локальной переменной ${error.params[0]}`;
|
||||
case RSErrorType.localNotUsed:
|
||||
return `Предупреждение! Переменная объявлена но не использована: ${error.params[0]}`;
|
||||
case RSErrorType.localShadowing:
|
||||
return `Повторное объявление переменной: ${error.params[0]}`;
|
||||
|
||||
case RSErrorType.typesNotEqual:
|
||||
return `Типизация операндов не совпадает! ${error.params[0]} != ${error.params[1]}`;
|
||||
case RSErrorType.globalNotTyped:
|
||||
return `Типизация конституенты не определена: ${error.params[0]}`;
|
||||
case RSErrorType.invalidDecart:
|
||||
return `τ(α×b) = ℬ(𝔇τ(α)×𝔇τ(b)). Некорректная типизация аргумента: ${error.params[0]}`;
|
||||
case RSErrorType.invalidBoolean:
|
||||
return `τ(ℬ(a)) = ℬℬ𝔇τ(a). Некорректная типизация аргумента: ${error.params[0]}`;
|
||||
case RSErrorType.invalidTypeOperation:
|
||||
return `Типизация операнда теоретико-множественной операции не корректна: ${error.params[0]}`;
|
||||
case RSErrorType.invalidCard:
|
||||
return `Некорректная типизация аргумента операции мощности: ${error.params[0]}`;
|
||||
case RSErrorType.invalidDebool:
|
||||
return `τ(debool(a)) = 𝔇τ(a). Некорректная типизация аргумента: ${error.params[0]}`;
|
||||
case RSErrorType.globalFuncMissing:
|
||||
return `Неизвестное имя функции: ${error.params[0]}`;
|
||||
case RSErrorType.globalFuncWithoutArgs:
|
||||
return `Некорректное использование имени функции без аргументов: ${error.params[0]}`;
|
||||
case RSErrorType.invalidReduce:
|
||||
return `τ(red(a)) = ℬ𝔇𝔇τ(a). Некорректная типизация аргумента: ${error.params[0]}`;
|
||||
case RSErrorType.invalidProjectionTuple:
|
||||
return `Проекция не определена: ${error.params[0]} -> ${error.params[1]}`;
|
||||
case RSErrorType.invalidProjectionSet:
|
||||
return `τ(Pri(a)) = ℬ𝒞i𝔇τ(a). Некорректная типизация аргумента: ${error.params[0]}`;
|
||||
case RSErrorType.invalidEnumeration:
|
||||
return `Типизация аргументов перечисления не совпадает: ${error.params[0]} != ${error.params[1]}`;
|
||||
case RSErrorType.ivalidBinding:
|
||||
return `Количество переменных в кортеже не соответствует размерности декартова произведения`;
|
||||
case RSErrorType.localOutOfScope:
|
||||
return `Использование имени переменной вне области действия: ${error.params[0]}`;
|
||||
case RSErrorType.invalidElementPredicat:
|
||||
return `Несоответствие типизаций операндов для оператора: ${error.params[0]}${error.params[1]}${error.params[2]}`;
|
||||
case RSErrorType.invalidArgsArtity:
|
||||
return `Неверное число аргументов терм-функции: ${error.params[0]} != ${error.params[1]}`;
|
||||
case RSErrorType.invalidArgumentType:
|
||||
return `Типизация аргумента терм-функции не соответствует объявленной: ${error.params[0]} != ${error.params[1]}`;
|
||||
case RSErrorType.invalidEqualsEmpty:
|
||||
return `Только множества можно сравнивать с пустым множеством: ${error.params[0]}`;
|
||||
case RSErrorType.globalStructure:
|
||||
return `Выражение родовой структуры должно быть ступенью`;
|
||||
case RSErrorType.globalExpectedFunction:
|
||||
return `Ожидалось выражение объявления функции`;
|
||||
case RSErrorType.emptySetUsage:
|
||||
return `Запрещено использование пустого множества как типизированного выражения`;
|
||||
case RSErrorType.radicalUsage:
|
||||
return `Радикалы запрещены вне деклараций терм-функци: ${error.params[0]}`;
|
||||
case RSErrorType.invalidFilterArgumentType:
|
||||
return `Типизация аргумента фильтра не корректна: ${error.params[0]}(${error.params[1]})`;
|
||||
case RSErrorType.invalidFilterArity:
|
||||
return `Количество параметров фильтра не соответствует количеству индексов`;
|
||||
case RSErrorType.arithmeticNotSupported:
|
||||
return `Тип не поддерживает арифметические операторы: ${error.params[0]}`;
|
||||
case RSErrorType.typesNotCompatible:
|
||||
return `Типы не совместимы для выбранной операции: ${error.params[0]} и ${error.params[1]}`;
|
||||
case RSErrorType.orderingNotSupported:
|
||||
return `Тип не поддерживает предикаты порядка: ${error.params[0]}`;
|
||||
case RSErrorType.globalNoValue:
|
||||
return `Используется неинтерпретируемый глобальный идентификатор: ${error.params[0]}`;
|
||||
case RSErrorType.invalidPropertyUsage:
|
||||
return `Использование неитерируемого множества в качестве значения`;
|
||||
case RSErrorType.globalMissingAST:
|
||||
return `Не удалось получить дерево разбора для глобального идентификатора: ${error.params[0]}`;
|
||||
case RSErrorType.globalFuncNoInterpretation:
|
||||
return `Функция не интерпретируется для данных аргументов`;
|
||||
}
|
||||
return 'UNKNOWN ERROR';
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user