mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Merge branch 'main' of https://github.com/IRBorisov/ConceptPortal
This commit is contained in:
commit
f258ab4a97
|
@ -1,3 +1,8 @@
|
||||||
|
# Docs
|
||||||
|
README.md
|
||||||
|
LICENSE
|
||||||
|
TODO.txt
|
||||||
|
|
||||||
# Git
|
# Git
|
||||||
.git
|
.git
|
||||||
.gitignore
|
.gitignore
|
||||||
|
|
|
@ -35,6 +35,7 @@ This readme file is used mostly to document project dependencies
|
||||||
- react-tooltip
|
- react-tooltip
|
||||||
- @uiw/react-codemirror
|
- @uiw/react-codemirror
|
||||||
- @uiw/codemirror-themes
|
- @uiw/codemirror-themes
|
||||||
|
- @lezer/lr
|
||||||
</pre>
|
</pre>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
|
@ -45,6 +46,7 @@ This readme file is used mostly to document project dependencies
|
||||||
- jest
|
- jest
|
||||||
- ts-jest
|
- ts-jest
|
||||||
- @types/jest
|
- @types/jest
|
||||||
|
- @lezer/generator
|
||||||
</pre>
|
</pre>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
|
|
1
rsconcept/frontend/.eslintignore
Normal file
1
rsconcept/frontend/.eslintignore
Normal file
|
@ -0,0 +1 @@
|
||||||
|
**/parser.ts
|
15
rsconcept/frontend/package-lock.json
generated
15
rsconcept/frontend/package-lock.json
generated
|
@ -8,6 +8,7 @@
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@lezer/lr": "^1.3.9",
|
||||||
"@uiw/codemirror-themes": "^4.21.9",
|
"@uiw/codemirror-themes": "^4.21.9",
|
||||||
"@uiw/react-codemirror": "^4.21.9",
|
"@uiw/react-codemirror": "^4.21.9",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
|
@ -26,6 +27,7 @@
|
||||||
"reagraph": "^4.11.1"
|
"reagraph": "^4.11.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@lezer/generator": "^1.4.0",
|
||||||
"@types/jest": "^29.5.3",
|
"@types/jest": "^29.5.3",
|
||||||
"@types/node": "^20.4.5",
|
"@types/node": "^20.4.5",
|
||||||
"@types/react": "^18.2.15",
|
"@types/react": "^18.2.15",
|
||||||
|
@ -3681,6 +3683,19 @@
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.3.tgz",
|
||||||
"integrity": "sha512-JH4wAXCgUOcCGNekQPLhVeUtIqjH0yPBs7vvUdSjyQama9618IOKFJwkv2kcqdhF0my8hQEgCTEJU0GIgnahvA=="
|
"integrity": "sha512-JH4wAXCgUOcCGNekQPLhVeUtIqjH0yPBs7vvUdSjyQama9618IOKFJwkv2kcqdhF0my8hQEgCTEJU0GIgnahvA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@lezer/generator": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/generator/-/generator-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-X/gB7vr7rEhtPQtgGxK61pMFOt60iXUBvANMq7DNO06PxrY6ZAmEEZIfSX8kfaVO/EVd9xARAsvguYDoShcMWA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.2",
|
||||||
|
"@lezer/lr": "^1.3.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"lezer-generator": "dist/lezer-generator.cjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@lezer/highlight": {
|
"node_modules/@lezer/highlight": {
|
||||||
"version": "1.1.6",
|
"version": "1.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.6.tgz",
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"prepare": "lezer-generator src/components/RSInput/rslang/rslangFull.grammar -o src/components/RSInput/rslang/parser.ts",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
|
@ -11,6 +12,7 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@lezer/lr": "^1.3.9",
|
||||||
"@uiw/codemirror-themes": "^4.21.9",
|
"@uiw/codemirror-themes": "^4.21.9",
|
||||||
"@uiw/react-codemirror": "^4.21.9",
|
"@uiw/react-codemirror": "^4.21.9",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
|
@ -29,6 +31,7 @@
|
||||||
"reagraph": "^4.11.1"
|
"reagraph": "^4.11.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@lezer/generator": "^1.4.0",
|
||||||
"@types/jest": "^29.5.3",
|
"@types/jest": "^29.5.3",
|
||||||
"@types/node": "^20.4.5",
|
"@types/node": "^20.4.5",
|
||||||
"@types/react": "^18.2.15",
|
"@types/react": "^18.2.15",
|
||||||
|
|
Binary file not shown.
|
@ -38,12 +38,21 @@ createTheme('customDark', {
|
||||||
}
|
}
|
||||||
}, 'dark');
|
}, 'dark');
|
||||||
|
|
||||||
|
createTheme('customLight', {
|
||||||
|
divider: {
|
||||||
|
default: '#d1d5db'
|
||||||
|
},
|
||||||
|
striped: {
|
||||||
|
default: '#f0f2f7'
|
||||||
|
},
|
||||||
|
}, 'light');
|
||||||
|
|
||||||
function ConceptDataTable<T>({ theme, ...props }: TableProps<T>) {
|
function ConceptDataTable<T>({ theme, ...props }: TableProps<T>) {
|
||||||
const { darkMode } = useConceptTheme();
|
const { darkMode } = useConceptTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable<T>
|
<DataTable<T>
|
||||||
theme={ theme ?? (darkMode ? 'customDark' : '')}
|
theme={ theme ?? (darkMode ? 'customDark' : 'customLight')}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,7 +9,7 @@ extends Omit<PropsWithRef<SelectProps<T>>, 'noDataLabel'> {
|
||||||
function ConceptSelect<T extends object | string>({ className, ...props }: ConceptSelectProps<T>) {
|
function ConceptSelect<T extends object | string>({ className, ...props }: ConceptSelectProps<T>) {
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
className={`overflow-ellipsis whitespace-nowrap ${className}`}
|
className={`overflow-x-ellipsis whitespace-nowrap ${className}`}
|
||||||
{...props}
|
{...props}
|
||||||
noDataLabel='Список пуст'
|
noDataLabel='Список пуст'
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
import { ThreeDots } from 'react-loader-spinner';
|
import { ThreeDots } from 'react-loader-spinner';
|
||||||
|
|
||||||
export function Loader() {
|
interface LoaderProps {
|
||||||
|
size?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Loader({size=10}: LoaderProps) {
|
||||||
return (
|
return (
|
||||||
<div className='flex justify-center w-full h-full'>
|
<div className='flex justify-center w-full h-full'>
|
||||||
<ThreeDots color='rgb(96 165 250)' height='100' width='100' radius='10' />
|
<ThreeDots
|
||||||
|
color='rgb(96 165 250)'
|
||||||
|
height={size*10}
|
||||||
|
width={size*10}
|
||||||
|
radius={size}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
45
rsconcept/frontend/src/components/RSInput/bracketMatching.ts
Normal file
45
rsconcept/frontend/src/components/RSInput/bracketMatching.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { bracketMatching, MatchResult } from '@codemirror/language';
|
||||||
|
import { Decoration, EditorView } from '@codemirror/view';
|
||||||
|
|
||||||
|
const matchingMark = Decoration.mark({class: "cc-matchingBracket"});
|
||||||
|
const nonmatchingMark = Decoration.mark({class: "cc-nonmatchingBracket"});
|
||||||
|
|
||||||
|
function bracketRender(match: MatchResult) {
|
||||||
|
const decorations = [];
|
||||||
|
const mark = match.matched ? matchingMark : nonmatchingMark;
|
||||||
|
decorations.push(mark.range(match.start.from, match.start.to));
|
||||||
|
if (match.end) {
|
||||||
|
decorations.push(mark.range(match.end.from, match.end.to));
|
||||||
|
}
|
||||||
|
return decorations;
|
||||||
|
}
|
||||||
|
|
||||||
|
const darkTheme = EditorView.baseTheme({
|
||||||
|
'.cc-matchingBracket': {
|
||||||
|
fontWeight: 600,
|
||||||
|
},
|
||||||
|
'.cc-nonmatchingBracket': {
|
||||||
|
color: '#ef4444',
|
||||||
|
fontWeight: 700,
|
||||||
|
},
|
||||||
|
'&.cm-focused .cc-matchingBracket': {
|
||||||
|
backgroundColor: '#734f00',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const lightTheme = EditorView.baseTheme({
|
||||||
|
'.cc-matchingBracket': {
|
||||||
|
fontWeight: 600,
|
||||||
|
},
|
||||||
|
'.cc-nonmatchingBracket': {
|
||||||
|
color: '#ef4444',
|
||||||
|
fontWeight: 700,
|
||||||
|
},
|
||||||
|
'&.cm-focused .cc-matchingBracket': {
|
||||||
|
backgroundColor: '#dae6f2',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export function ccBracketMatching(darkMode: boolean) {
|
||||||
|
return [bracketMatching({ renderMatch: bracketRender }), darkMode ? darkTheme : lightTheme];
|
||||||
|
}
|
|
@ -1,17 +1,22 @@
|
||||||
import { bracketMatching } from '@codemirror/language';
|
|
||||||
import { Extension } from '@codemirror/state';
|
import { Extension } from '@codemirror/state';
|
||||||
|
import { tags as t } from '@lezer/highlight';
|
||||||
import { createTheme } from '@uiw/codemirror-themes';
|
import { createTheme } from '@uiw/codemirror-themes';
|
||||||
import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
import CodeMirror, { BasicSetupOptions, ReactCodeMirrorProps, ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
||||||
import { EditorView } from 'codemirror';
|
import { EditorView } from 'codemirror';
|
||||||
import { Ref, useMemo } from 'react';
|
import { Ref, useMemo } from 'react';
|
||||||
|
|
||||||
import { useConceptTheme } from '../../../context/ThemeContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
|
import { ccBracketMatching } from './bracketMatching';
|
||||||
|
import { RSLanguage } from './rslang';
|
||||||
|
import { rshoverTooltip } from './tooltip';
|
||||||
|
|
||||||
const editorSetup: BasicSetupOptions = {
|
const editorSetup: BasicSetupOptions = {
|
||||||
highlightSpecialChars: true,
|
highlightSpecialChars: false,
|
||||||
history: true,
|
history: true,
|
||||||
drawSelection: true,
|
drawSelection: false,
|
||||||
syntaxHighlighting: true,
|
syntaxHighlighting: false,
|
||||||
defaultKeymap: true,
|
defaultKeymap: true,
|
||||||
historyKeymap: true,
|
historyKeymap: true,
|
||||||
|
|
||||||
|
@ -35,11 +40,6 @@ const editorSetup: BasicSetupOptions = {
|
||||||
lintKeymap: false
|
lintKeymap: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const editorExtensions = [
|
|
||||||
EditorView.lineWrapping,
|
|
||||||
bracketMatching()
|
|
||||||
];
|
|
||||||
|
|
||||||
interface RSInputProps
|
interface RSInputProps
|
||||||
extends Omit<ReactCodeMirrorProps, 'onChange'> {
|
extends Omit<ReactCodeMirrorProps, 'onChange'> {
|
||||||
innerref?: Ref<ReactCodeMirrorRef> | undefined
|
innerref?: Ref<ReactCodeMirrorRef> | undefined
|
||||||
|
@ -49,10 +49,10 @@ extends Omit<ReactCodeMirrorProps, 'onChange'> {
|
||||||
|
|
||||||
function RSInput({
|
function RSInput({
|
||||||
innerref, onChange, editable,
|
innerref, onChange, editable,
|
||||||
height='10rem',
|
|
||||||
...props
|
...props
|
||||||
}: RSInputProps) {
|
}: RSInputProps) {
|
||||||
const { darkMode } = useConceptTheme();
|
const { darkMode } = useConceptTheme();
|
||||||
|
const { schema } = useRSForm();
|
||||||
|
|
||||||
const cursor = useMemo(() => editable ? 'cursor-text': 'cursor-default', [editable]);
|
const cursor = useMemo(() => editable ? 'cursor-text': 'cursor-default', [editable]);
|
||||||
const lightTheme: Extension = useMemo(
|
const lightTheme: Extension = useMemo(
|
||||||
|
@ -62,15 +62,17 @@ function RSInput({
|
||||||
fontFamily: 'inherit',
|
fontFamily: 'inherit',
|
||||||
background: editable ? '#ffffff' : '#f0f2f7',
|
background: editable ? '#ffffff' : '#f0f2f7',
|
||||||
foreground: '#000000',
|
foreground: '#000000',
|
||||||
selection: '#036dd626',
|
selection: '#aacef2',
|
||||||
selectionMatch: '#036dd626',
|
|
||||||
caret: '#5d00ff',
|
caret: '#5d00ff',
|
||||||
},
|
},
|
||||||
styles: [
|
styles: [
|
||||||
// { tag: t.comment, color: '#787b8099' },
|
{ tag: t.name, class: 'text-[#b266ff] cursor-default' }, // GlobalID
|
||||||
// { tag: t.variableName, color: '#0080ff' },
|
{ tag: t.variableName, class: 'text-[#24821a]' }, // LocalID
|
||||||
// { tag: [t.string, t.special(t.brace)], color: '#5c6166' },
|
{ tag: t.propertyName, class: '' }, // Radical
|
||||||
// { tag: t.definition(t.typeName), color: '#5c6166' },
|
{ tag: t.keyword, class: 'text-[#001aff]' }, // keywords
|
||||||
|
{ tag: t.literal, class: 'text-[#001aff]' }, // literals
|
||||||
|
{ tag: t.controlKeyword, class: 'font-semibold'}, // R | I | D
|
||||||
|
{ tag: t.unit, class: 'text-[0.75rem]' }, // indicies
|
||||||
]
|
]
|
||||||
}), [editable]);
|
}), [editable]);
|
||||||
|
|
||||||
|
@ -81,27 +83,36 @@ function RSInput({
|
||||||
fontFamily: 'inherit',
|
fontFamily: 'inherit',
|
||||||
background: editable ? '#070b12' : '#374151',
|
background: editable ? '#070b12' : '#374151',
|
||||||
foreground: '#e4e4e7',
|
foreground: '#e4e4e7',
|
||||||
selection: '#ffae00b0',
|
selection: '#8c6000',
|
||||||
selectionMatch: '#ffae00b0',
|
|
||||||
caret: '#ffaa00'
|
caret: '#ffaa00'
|
||||||
},
|
},
|
||||||
styles: [
|
styles: [
|
||||||
// { tag: t.comment, color: '#787b8099' },
|
{ tag: t.name, class: 'text-[#dfbfff] cursor-default' }, // GlobalID
|
||||||
// { tag: t.variableName, color: '#0080ff' },
|
{ tag: t.variableName, class: 'text-[#69bf60]' }, // LocalID
|
||||||
// { tag: [t.string, t.special(t.brace)], color: '#5c6166' },
|
{ tag: t.propertyName, class: '' }, // Radical
|
||||||
// { tag: t.definition(t.typeName), color: '#5c6166' },
|
{ tag: t.keyword, class: 'text-[#808dff]' }, // keywords
|
||||||
|
{ tag: t.literal, class: 'text-[#808dff]' }, // literals
|
||||||
|
{ tag: t.controlKeyword, class: 'font-semibold'}, // R | I | D
|
||||||
|
{ tag: t.unit, class: 'text-[0.75rem]' }, // indicies
|
||||||
]
|
]
|
||||||
}), [editable]);
|
}), [editable]);
|
||||||
|
|
||||||
|
const editorExtensions = useMemo(
|
||||||
|
() => [
|
||||||
|
EditorView.lineWrapping,
|
||||||
|
RSLanguage,
|
||||||
|
ccBracketMatching(darkMode),
|
||||||
|
rshoverTooltip(schema?.items || []),
|
||||||
|
], [darkMode, schema?.items]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`w-full h-[${height}] ${cursor}`}>
|
<div className={`w-full ${cursor} text-lg`}>
|
||||||
<CodeMirror
|
<CodeMirror
|
||||||
ref={innerref}
|
ref={innerref}
|
||||||
basicSetup={editorSetup}
|
basicSetup={editorSetup}
|
||||||
extensions={editorExtensions}
|
|
||||||
height={height}
|
|
||||||
indentWithTab={false}
|
|
||||||
theme={darkMode ? darkTheme : lightTheme}
|
theme={darkMode ? darkTheme : lightTheme}
|
||||||
|
extensions={editorExtensions}
|
||||||
|
indentWithTab={false}
|
||||||
onChange={value => onChange(value)}
|
onChange={value => onChange(value)}
|
||||||
editable={editable}
|
editable={editable}
|
||||||
{...props}
|
{...props}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import {styleTags, tags} from '@lezer/highlight';
|
||||||
|
|
||||||
|
export const highlighting = styleTags({
|
||||||
|
Index: tags.unit,
|
||||||
|
ComplexIndex: tags.unit,
|
||||||
|
Literal: tags.literal,
|
||||||
|
|
||||||
|
Radical: tags.propertyName,
|
||||||
|
Function: tags.name,
|
||||||
|
Predicate: tags.name,
|
||||||
|
Global: tags.name,
|
||||||
|
Local: tags.variableName,
|
||||||
|
|
||||||
|
TextFunction: tags.keyword,
|
||||||
|
Filter: tags.keyword,
|
||||||
|
PrefixR: tags.controlKeyword,
|
||||||
|
PrefixI: tags.controlKeyword,
|
||||||
|
PrefixD: tags.controlKeyword
|
||||||
|
});
|
|
@ -0,0 +1,8 @@
|
||||||
|
import {LRLanguage} from "@codemirror/language"
|
||||||
|
|
||||||
|
import { parser } from './parser';
|
||||||
|
|
||||||
|
export const RSLanguage = LRLanguage.define({
|
||||||
|
parser: parser,
|
||||||
|
languageData: {}
|
||||||
|
});
|
|
@ -0,0 +1,35 @@
|
||||||
|
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||||
|
export const
|
||||||
|
Expression = 1,
|
||||||
|
LogicPredicate = 2,
|
||||||
|
Literal = 3,
|
||||||
|
Local = 4,
|
||||||
|
Index = 5,
|
||||||
|
Global = 6,
|
||||||
|
Radical = 7,
|
||||||
|
BinaryOperation = 8,
|
||||||
|
Enumeration = 21,
|
||||||
|
Tuple = 22,
|
||||||
|
Boolean = 23,
|
||||||
|
Declarative = 25,
|
||||||
|
PrefixD = 27,
|
||||||
|
Variable = 28,
|
||||||
|
VariableComposite = 29,
|
||||||
|
PrefixI = 30,
|
||||||
|
Imperative = 31,
|
||||||
|
ImperativeBlocks = 32,
|
||||||
|
ImperativeIteration = 33,
|
||||||
|
ImperativeAssignment = 35,
|
||||||
|
ImperativeCondition = 37,
|
||||||
|
PrefixR = 38,
|
||||||
|
Recursion = 39,
|
||||||
|
FunctionCall = 41,
|
||||||
|
Function = 42,
|
||||||
|
TextFunctionExpression = 44,
|
||||||
|
TextFunction = 45,
|
||||||
|
ComplexIndex = 46,
|
||||||
|
Filter = 47,
|
||||||
|
Predicate = 58,
|
||||||
|
QuantorVariable = 62,
|
||||||
|
LogicBinary = 63,
|
||||||
|
FunctionDeclaration = 68
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { printTree } from '../../../utils/print-lezer-tree';
|
||||||
|
import { parser } from './parser';
|
||||||
|
|
||||||
|
const testData = [
|
||||||
|
['a1', '[Expression[Local[Index]]]'],
|
||||||
|
['A1', '[Expression[Global]]'],
|
||||||
|
['∅', '[Expression[Literal]]'],
|
||||||
|
['Z', '[Expression[Literal]]'],
|
||||||
|
['1', '[Expression[Literal]]'],
|
||||||
|
['12=41', '[Expression[LogicPredicate[Literal][=][Literal]]]'],
|
||||||
|
['0-5', '[Expression[BinaryOperation[Literal][-][Literal]]]'],
|
||||||
|
['12+41', '[Expression[BinaryOperation[Literal][+][Literal]]]'],
|
||||||
|
['a1∪Z', '[Expression[BinaryOperation[Local[Index]][∪][Literal]]]'],
|
||||||
|
['ℬℬ(X1)', '[Expression[Boolean[ℬ][Boolean[ℬ][(][Global][)]]]]'],
|
||||||
|
['P2[S1]', '[Expression[PredicateCall[Predicate][[][Global][]]]]'],
|
||||||
|
['[σ∈R1×R1] F6[σ]', '[Expression[FunctionDeclaration[[][Local][∈][BinaryOperation[Radical][×][Radical]][]][FunctionCall[Function][[][Local][]]]]]'],
|
||||||
|
['D{ξ∈red(S1) | ξ=ξ}', '[Expression[Declarative[PrefixD][{][Variable[Local]][∈][TextFunctionExpression[TextFunction][(][Global][)]][LogicPredicate[Local][=][Local]][}]]]'],
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('Testing RSParser', () => {
|
||||||
|
it.each(testData)('Parse %p',
|
||||||
|
(input: string, expectedTree: string) => {
|
||||||
|
const tree = parser.parse(input);
|
||||||
|
expect(printTree(tree)).toBe(expectedTree);
|
||||||
|
});
|
||||||
|
});
|
22
rsconcept/frontend/src/components/RSInput/rslang/parser.ts
Normal file
22
rsconcept/frontend/src/components/RSInput/rslang/parser.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||||
|
import {LRParser} from "@lezer/lr"
|
||||||
|
import {highlighting} from "./highlight.ts"
|
||||||
|
export const parser = LRParser.deserialize({
|
||||||
|
version: 14,
|
||||||
|
states: "7|O!pQPOOOOQO'#C_'#C_O!wQPO'#C`O&bQPO'#DvOVQPO'#CdO&iQPO'#CqO'yQPO'#CsO(RQPO'#CuO(WQPO'#C{O(]QPO'#DTOOQO'#D}'#D}O(bQPO'#DVO(gQQO'#DZOOQO'#DZ'#DZO(lQQO'#D]O(qQPO'#DYO(vQPO'#DYOOQO'#Dx'#DxO({QPO'#DgO&iQPO'#DiO)QQPO'#DkOOQO'#E^'#E^O)YQPO'#EcOOQO'#Ec'#EcO)kQPOOOOQO'#Dw'#DwO)TQPO'#DrQOQPOOOOQO'#Ca'#CaOOQO,58z,58zO&iQPO,59OO&iQPO,59OO&iQPO,59OO&iQPO,59OO&iQPO,59OO&iQPO,59OO&iQPO,59OO&iQPO,59OO&iQPO,58xO)yQPO'#EOO+[QPO,59OO+fQPO'#EPO+kQPO,59^O+sQPO,5:zO,pQPO'#EOO&iQPO'#CdO,}QPO,59]OOQO'#EO'#EOO-qQPO,59aO&iQPO,59_OOQO,59_,59_O)QQPO,59aO&iQPO,59gO)QQPO,59oO&iQPO,59qOOQO,59u,59uOOQO,59w,59wO&iQPO,59tO&iQPO,59tO&iQPO,5:RO%WQPO'#C^O.OQPO'#E_OOQO,5:T,5:TOOQO'#Cx'#CxO)QQPO'#CxO.ZQPO,5:VOOQO'#Dl'#DlOVQPO,5:XOVQPO,5:XOVQPO,5:XOVQPO,5:XO.cQPO'#EeOOQO'#Ed'#EdO.hQPO,5:^OOQO1G.j1G.jO0sQPO1G.jO0zQPO1G.jO3UQPO1G.jO5`QPO1G.jO5gQPO1G.jO5nQPO1G.jO6WQPO1G.jO8PQPO1G.dO&iQPO,5:kOOQO1G.x1G.xOOQO1G0f1G0fOOQO1G.w1G.wO&iQPO1G.{O8yQPO1G.yO9QQPO1G.{O9VQPO1G/RO9^QPO1G/ZO9cQPO1G/]O9kQPO1G/`O9rQPO1G/`O9zQPO1G/mO:SQPO,5:yOOQO'#ES'#ESO:XQPO,59dO:aQPO'#CyO)QQPO,5:|O:fQPO1G/qOOQO1G/s1G/sO<bQPO1G/sO<iQPO1G/sO<pQPO1G/sO&iQPO,5;PO)TQPO,5;OOVQPO1G/xO=_QPO1G0VO=oQPO7+$gOOQO7+$e7+$eO&iQPO7+$gOVQPO7+$mO&iQPO7+$uOOQO7+$w7+$wOOQO7+$z7+$zO=vQPO7+$zOOQO7+%X7+%XOVQPO'#E`OOQO1G0e1G0eOOQO1G/O1G/OO)QQPO,59eOOQO1G0h1G0hOOQO'#C`'#C`O={QPO7+%]O>iQPO1G0kOOQO1G0j1G0jOOQO7+%d7+%dOVQPO<<HRO>sQPO<<HRO>zQPO'#DxO?UQPO'#DROOQO'#ET'#ETOOQO'#C|'#C|O?jQPO<<HXO?rQPO<<HaO&iQPO<<HfOOQO1G/P1G/POOQO<<Hw<<HwO?yQPOAN=mOVQPOAN=mO&iQPO,59iO&iQPO,59kOVQPO,59hOOQOAN=sAN=sOVQPOAN={O@[QPOAN>QOOQOG23XG23XO@cQPOG23XO@tQPO1G/TOAOQPO1G/VOOQO1G/S1G/SOAYQPOG23gOAaQPOG23gOOQOG23lG23lOOQOLD(sLD(sOOQOLD)RLD)RO&iQPOLD)ROArQPO!$'LmOOQO!)9BX!)9BXO:fQPO,59OO:fQPO,59OO:fQPO,59OO:fQPO,59OO:fQPO,59OO:fQPO,59OO:fQPO,59OO:fQPO,59OOAyQPO1G.jOCfQPO1G.jOFxQPO1G.jOHvQPO1G.jOH}QPO1G.jOIUQPO1G.jOI]QPO1G.j",
|
||||||
|
stateData: "KZ~O!iOS~OUaOVaOaSOdTOhUOkVOnWOvXOzZO![bO!^cO!mPO!nPO!oPO!pQO!y[O!z[O!{]O!|]O!}]O#O]O#P^O#TdO~O{jO~PVO!mlOXSXYSXZSX[SX]SX^SX_SX`SXjSX!QSX!RSX!SSX!TSX!USX!VSX!WSX!XSX!YSX!gSX!tSXcSX!bSX!cSX!dSX!eSXbSX!uSXtSXxSX![SX!^SX#TSXrSX!xSX~OXnOYoOZpO[qO]rO^sO_tO`uOjvO!QvO!RvO!SvO!TvO!UvO!VvO!WvO!XvO!YvO~O!g!jX~P%WOUaOVaOa}OdTOhUOkVOnWOvXOzZO!mPO!nPO!oPO!pQO!y[O!z[O!{]O!|]O!}]O#O]O#P^O~Oa!ROhUO~Od!TO~Od!UO~Od!VO~O{!WO~O!O!XO~O!O!YO~Oa!ZO~O{![O~O{!]O~Oa!bO!pQO~O!b#VX!c#VX!d#VX!e#VX!g!jX~O!b!eO!c!fO!d!gO!e!hO~O!t!rX~P%WOX!lXY!lXZ!lX[!lX]!lX^!lX_!lX`!lXj!lX!Q!lX!R!lX!S!lX!T!lX!U!lX!V!lX!W!lX!X!lX!Y!lX~Ob!lO!t!lX~P*QO!t!uO~Ob!vO!t!rX~Ob!wO!b#VX!c#VX!d#VX!e#VX~OXnOYoOZpO[qO]rO^sO_tO`uO~Oc!rX!t!rXx!rX~P,UOc!xO!t!uO~OX!lXY!lXZ!lX[!lX]!lX^!lX_!lX`!lX~Oj!yOc!lX!t!lX~P-VO![bO!^cO#TdO~Oj#XO!t#WO~Oj#^O~Ox#`O!t#_O~OXnOZWi[Wi]Wi^Wi_Wi`WijWi!QWi!RWi!SWi!TWi!UWi!VWi!WWi!XWi!YWi!gWibWi!tWicWi!bWi!cWi!dWi!eWi!uWixWi![Wi!^Wi#TWi!xWi~OYWi~P.pOYoO~P.pOXnOYoOZpO`uO]Wi^Wi_WijWi!QWi!RWi!SWi!TWi!UWi!VWi!WWi!XWi!YWi!gWibWi!tWicWi!bWi!cWi!dWi!eWi!uWixWi![Wi!^Wi#TWi!xWi~O[Wi~P1ROXnOYoOZpO[qO_tO`uO^WijWi!QWi!RWi!SWi!TWi!UWi!VWi!WWi!XWi!YWi!gWibWi!tWicWi!bWi!cWi!dWi!eWi!uWixWi![Wi!^Wi#TWi!xWi~O]Wi~P3]O]rO~P3]O[qO~P1RO[Wi]Wi^Wi_Wi`Wi~OXnOYoOZpOjWi!QWi!RWi!SWi!TWi!UWi!VWi!WWi!XWi!YWi!gWibWi!tWicWi!bWi!cWi!dWi!eWi!uWixWi![Wi!^Wi#TWi!xWi~P5uO!bQi!cQi!dQi!eQi!gQibQi![Qi!^Qi#TQicQi!xQi!uQi~P,UOb#cO~P,UOj#dO~O!u#eO~P,UOt#fO~Ox#gO!t!uO~Ob#hO~P,UOx#iO!t!uO~Ox#jO!t!uO~Oa#kO~Ob#mO!t!vX~O!t#nO~OUaOVaOa}OdTOhUOkVOnWOvXOzZO!mPO!nPO!oPO!p#pO!y[O!z[O!{]O!|]O!}]O#O]O#P^O~O!b!eO!d!ai!e!ai!g!aib!aic!ai!x!ai!u!ai~O!c!ai~P;vO!c!fO~P;vO!b!eO!c!fO!d!gO!e!ai!g!aib!aic!ai!x!ai!u!ai~Ob!si!t!sic!six!si~P,UO!u#uO~P,UOa#}O~OX$gOY$hOZ$iO[$jO]$kO^$lO_$mO`$nO~P&iOx#Xi!t#Xi~P,UO!u$RO~P,UOr$SOt$TO~P*QOcuX!b#VX!c#VX!d#VX!e#VX!xuX~Oc$VO!x$UO~O!u$WO~P,UOc$YO!b#VX!c#VX!d#VX!e#VX~Ob$aO~P,UOc$bO!b#VX!c#VX!d#VX!e#VX~Ocqi!xqi~P,UOcsi!xsi~P,UOc$cO~P%WO!u$dO!b#VX!c#VX!d#VX!e#VX~Oc$fO~P,UOX$gOUWiVWiYWiZWiaWidWihWikWinWivWizWi!mWi!nWi!oWi!pWi!yWi!zWi!{Wi!|Wi!}Wi#OWi#PWi~P5uOX$gOY$hOUWiVWiZWiaWidWihWikWinWivWizWi!mWi!nWi!oWi!pWi!yWi!zWi!{Wi!|Wi!}Wi#OWi#PWi~P5uOX$gOY$hOZ$iO`$nOUWiVWi]Wi^Wi_WiaWidWihWikWinWivWizWi!mWi!nWi!oWi!pWi!yWi!zWi!{Wi!|Wi!}Wi#OWi#PWi~O[Wi~PEROX$gOY$hOZ$iO[$jO_$mO`$nOUWiVWi^WiaWidWihWikWinWivWizWi!mWi!nWi!oWi!pWi!yWi!zWi!{Wi!|Wi!}Wi#OWi#PWi~O]Wi~PGPO]$kO~PGPO[$jO~PEROX$gOY$hOZ$iOUWiVWiaWidWihWikWinWivWizWi!mWi!nWi!oWi!pWi!yWi!zWi!{Wi!|Wi!}Wi#OWi#PWi~P5uO#P!y![zUV!{!|!}#O!z!pvnkn~",
|
||||||
|
goto: "3Y#YPP#Z#n$t&[PP&_PPPPPPPPPPPP'g'g(mP'gPP)v*YP'g*]*`P*`P*`P'gP#nPP#n*dP+jPPPPPPPPP,pP,pP,p-Q-TPPPP-dPPP-g-m.YPPPP#n0t1UPP1`1cPPPPPPPP1i1{2RP2e2h3P3SjiOS!e!f!g!h#`#e#k#u$R$U$WT!_c#q#XaOSTcnopqrstuv}!R!U!W!Z![!]!e!f!g!h!u!y#X#^#`#d#e#f#k#q#u#}$R$S$T$U$W$d$g$h$i$j$k$l$m$n#QaOScnopqrstuv}!R!U!W!Z![!]!e!f!g!h!u!y#X#^#`#d#f#k#q#u#}$R$S$T$W$d$g$h$i$j$k$l$m$nQ!QT[!ad!T!V!b#W#nS!ij#_T#w#e$URmQ#SaOTcnopqrstuv!R!U!W!Z![!]!e!f!g!h!u!y#X#^#`#d#e#f#k#q#u#}$R$S$T$U$W$d$g$h$i$j$k$l$m$nTxS}#XYOSTcnopqrstuv}!R!U!W!Z![!]!e!f!g!h!u!y#X#^#`#d#e#f#k#q#u#}$R$S$T$U$W$d$g$h$i$j$k$l$m$n#WYOSTcnopqrstuv}!R!U!W!Z![!]!e!f!g!h!u!y#X#^#`#d#e#f#k#q#u#}$R$S$T$U$W$d$g$h$i$j$k$l$m$nR!SUQ!ddQ!{!TQ!}!VQ#T!bQ#o#WR$O#nR#U!bR#{#eT#y#e$U#X_OSTcnopqrstuv}!R!U!W!Z![!]!e!f!g!h!u!y#X#^#`#d#e#f#k#q#u#}$R$S$T$U$W$d$g$h$i$j$k$l$m$n#X`OSTcnopqrstuv}!R!U!W!Z![!]!e!f!g!h!u!y#X#^#`#d#e#f#k#q#u#}$R$S$T$U$W$d$g$h$i$j$k$l$m$nmeOS!_!e!f!g!h#`#e#k#u$R$U$WR!cdkiOS!e!f!g!h#`#e#k#u$R$U$WRkOQkOR#t#`SfO#`Wg!e!f!g!hS{S#kS#x#e$UQ$Q#uQ$Z$RR$`$WSRO#`QwSY|T}!W![!]f!^c!e!f!g!h#e#k#q#u$R$US!ln$gQ!moQ!npQ!oqQ!prQ!qsQ!rtQ!suQ!tvQ!z!RQ!|!UQ#P!ZQ#a!uQ#b!yQ#q#XQ#r#^Q#v#dQ#|#fQ$X#}Q$[$SQ$]$TQ$_$WQ$e$dQ$o$hQ$p$iQ$q$jQ$r$kQ$s$lQ$t$mR$u$nSyS}Q!OTQ#O!WQ#Q![R#R!]SzS}X!PT!W![!]R#V!bQ#z#eR$^$UjiOS!e!f!g!h#`#e#k#u$R$U$WR#S!_Q!`cR$P#qjgOS!e!f!g!h#`#e#k#u$R$U$WR#l#SR!ddbhOS#`#e#k#u$R$U$WQ#Y!eQ#Z!fQ#[!gR#]!hR!kjQ!jjR#s#_",
|
||||||
|
nodeNames: "⚠ Expression LogicPredicate Literal Local Index Global Radical BinaryOperation + - * ∪ \\ ∆ ∩ × ( ) } { Enumeration Tuple Boolean ℬ Declarative ∈ PrefixD Variable VariableComposite PrefixI Imperative ImperativeBlocks ImperativeIteration :∈ ImperativeAssignment := ImperativeCondition PrefixR Recursion ] FunctionCall Function [ TextFunctionExpression TextFunction ComplexIndex Filter ∉ ⊆ ⊄ ⊂ > ≥ ≤ ≠ = PredicateCall Predicate LogicNegation ¬ LogicQuantor QuantorVariable LogicBinary ⇔ ⇒ ∨ & FunctionDeclaration",
|
||||||
|
maxTerm: 101,
|
||||||
|
nodeProps: [
|
||||||
|
["closedBy", 17,")",20,"}"],
|
||||||
|
["openedBy", 18,"(",19,"{"]
|
||||||
|
],
|
||||||
|
propSources: [highlighting],
|
||||||
|
skippedNodes: [0],
|
||||||
|
repeatNodeCount: 0,
|
||||||
|
tokenData: "4X~R!iX^%ppq%pvw&exy&jyz&oz{&t{|&y|}'O}!O'T!Q!R'Y!R!['b![!]'}!]!^(b!_!`(g!`!a(l!c!d(q!e!f(q!f!g)P!h!i)X!k!l)o!r!s)t!t!u*[!u!v(q!v!w(q!z!{(q!|!}*l!}#O*q#O#P*v#P#Q*{#R#S+Q#T#U+Q#U#V+]#V#W,o#W#X.O#X#d+Q#d#e0U#e#f+Q#f#g0t#g#o+Q#o#p1u#p#q1z#q#r2P#y#z%p$f$g%p$r$s2U%o%p2Z5i6S+Q#BY#BZ%p$IS$I_%p$I|$JO%p$JT$JU%p$KV$KW%p% l% m2`%%Y%%Z2e%%[%%]2j%&Y%&Z2o%&]%&^2o%&_%&`2t%&`%&a2y%&b%&c3O%&c%&d3T%'S%'T3Y%'T%'U3_%'U%'V3d%(^%(_3i%(b%(c3n%(c%(d3s%)Q%)R3x%)S%)T3}%)U%)V4S&FU&FV%p~%uY!i~X^%ppq%p#y#z%p$f$g%p#BY#BZ%p$IS$I_%p$I|$JO%p$JT$JU%p$KV$KW%p&FU&FV%p~&jO!e~~&oOa~~&tOb~~&yOZ~~'OOX~~'TO!t~~'YOY~P'_P!mP!Q!['YR'iQ!OQ!mP|}'o!Q!['YQ'rP!R!['uQ'zP!OQ|}'o~(QQ!_!`(W%&b%&c(]~(]Ot~~(bOr~~(gO!x~~(lO!Y~~(qO!U~~(tP!Q![(w~(|PU~!Q![(w~)UPk~!Q![(w~)[Q!Q![)b#]#^)j~)gPz~!Q![)b~)oO#P~~)tOn~~)wQ!Q![)}#f#g*V~*SP![~!Q![)}~*[O!y~~*aPv~!Q![*d~*iPV~!Q![*d~*qO!o~~*vO{~~*{O]~~+QOx~~+VQ!p~#T#o+Q5i6S+Q~+bS!p~#T#c+Q#c#d+n#d#o+Q5i6S+Q~+sS!p~#T#c+Q#c#d,P#d#o+Q5i6S+Q~,US!p~#T#`+Q#`#a,b#a#o+Q5i6S+Q~,iQ!|~!p~#T#o+Q5i6S+Q~,tR!p~#T#U,}#U#o+Q5i6S+Q~-SS!p~#T#f+Q#f#g-`#g#o+Q5i6S+Q~-eS!p~#T#W+Q#W#X-q#X#o+Q5i6S+Q~-xQ!{~!p~#T#o+Q5i6S+Q~.TS!p~#T#X+Q#X#Y.a#Y#o+Q5i6S+Q~.fS!p~#T#U+Q#U#V.r#V#o+Q5i6S+Q~.wS!p~#T#c+Q#c#d/T#d#o+Q5i6S+Q~/YS!p~#T#c+Q#c#d/f#d#o+Q5i6S+Q~/kS!p~#T#`+Q#`#a/w#a#o+Q5i6S+Q~0OQ!}~!p~#T#o+Q5i6S+Q~0ZS!p~#T#f+Q#f#g0g#g#o+Q5i6S+Q~0nQ!z~!p~#T#o+Q5i6S+Q~0yS!p~#T#X+Q#X#Y1V#Y#o+Q5i6S+Q~1[S!p~#T#W+Q#W#X1h#X#o+Q5i6S+Q~1oQ#O~!p~#T#o+Q5i6S+Q~1zOd~~2PO!u~~2UOc~~2ZO!^~~2`O`~~2eOh~~2jO!c~~2oO!b~~2tO#T~~2yO!n~~3OO^~~3TOj~~3YO!Q~~3_O!d~~3dO_~~3iO[~~3nO!X~~3sO!W~~3xO!V~~3}O!T~~4SO!S~~4XO!R~",
|
||||||
|
tokenizers: [0, 1],
|
||||||
|
topRules: {"Expression":[0,1]},
|
||||||
|
tokenPrec: 1926
|
||||||
|
})
|
|
@ -0,0 +1,200 @@
|
||||||
|
@precedence {
|
||||||
|
plus @left minus @left,
|
||||||
|
times @left,
|
||||||
|
not @right,
|
||||||
|
log_equiv @left,
|
||||||
|
log_impl @left,
|
||||||
|
log_or @left,
|
||||||
|
log_and @left,
|
||||||
|
set_decart @left set_union @left set_intersect @left set_minus @left set_symminus @left,
|
||||||
|
set_bool @right,
|
||||||
|
p1, p2
|
||||||
|
}
|
||||||
|
|
||||||
|
@top Expression {
|
||||||
|
term_or_logic |
|
||||||
|
FunctionDeclaration
|
||||||
|
}
|
||||||
|
|
||||||
|
@skip { space }
|
||||||
|
|
||||||
|
@tokens {
|
||||||
|
space { @whitespace+ }
|
||||||
|
ComplexIndex { $[1-9](","$[1-9])* }
|
||||||
|
integer { $[0-9]+ }
|
||||||
|
emptySet { "∅" }
|
||||||
|
integerSet { "Z" }
|
||||||
|
|
||||||
|
bigPr { "Pr" }
|
||||||
|
smallPr { "pr" }
|
||||||
|
filter { "Fi" }
|
||||||
|
card { "card" }
|
||||||
|
bool { "bool" }
|
||||||
|
debool { "debool" }
|
||||||
|
red { "red" }
|
||||||
|
|
||||||
|
quantifier { $[∀∃] }
|
||||||
|
|
||||||
|
Global { $[XCSDAT]$[0-9]+ }
|
||||||
|
Function { "F"$[0-9]+ }
|
||||||
|
Predicate { "P"$[0-9]+ }
|
||||||
|
Radical { "R"$[0-9]+ }
|
||||||
|
local { $[_a-zα-ω]($[a-zα-ω])* }
|
||||||
|
PrefixR { "R" }
|
||||||
|
PrefixI { "I" }
|
||||||
|
PrefixD { "D" }
|
||||||
|
|
||||||
|
"¬"
|
||||||
|
"⇔" "⇒" "∨" "&"
|
||||||
|
"ℬ"
|
||||||
|
"+" "-" "*" "∪" "\\" "∆" "∩" "×"
|
||||||
|
"∈" "∉" "⊆" "⊄" "⊂" ">" "≥" "≤" "≠" "="
|
||||||
|
":∈" ":="
|
||||||
|
|
||||||
|
"[" "]"
|
||||||
|
"{" "}"
|
||||||
|
"(" ")"
|
||||||
|
|
||||||
|
@precedence { filter bigPr Predicate Function Global Radical PrefixR PrefixI PrefixD }
|
||||||
|
@precedence { card bool debool red smallPr local }
|
||||||
|
}
|
||||||
|
|
||||||
|
Index { integer }
|
||||||
|
Local {
|
||||||
|
!p1 local |
|
||||||
|
!p2 local Index
|
||||||
|
}
|
||||||
|
|
||||||
|
Literal { integer | emptySet | integerSet }
|
||||||
|
|
||||||
|
TextFunction {
|
||||||
|
bigPr ComplexIndex |
|
||||||
|
smallPr ComplexIndex |
|
||||||
|
card | bool | debool | red
|
||||||
|
}
|
||||||
|
|
||||||
|
Filter { filter ComplexIndex }
|
||||||
|
|
||||||
|
term_or_logic { logic | term }
|
||||||
|
|
||||||
|
FunctionDeclaration { "[" arg_declaration "]" term_or_logic }
|
||||||
|
arg_declaration {
|
||||||
|
declaration |
|
||||||
|
arg_declaration "," declaration
|
||||||
|
}
|
||||||
|
declaration { Local "∈" term }
|
||||||
|
|
||||||
|
logic {
|
||||||
|
LogicPredicate |
|
||||||
|
logicUnary |
|
||||||
|
LogicBinary
|
||||||
|
}
|
||||||
|
logic_all { logic | logic_par }
|
||||||
|
logic_par { "(" logic ")" }
|
||||||
|
logic_no_binary {
|
||||||
|
LogicPredicate
|
||||||
|
logicUnary
|
||||||
|
logic_par
|
||||||
|
}
|
||||||
|
logicUnary {
|
||||||
|
PredicateCall { Predicate "[" term_enum "]" } |
|
||||||
|
LogicNegation { !not "¬" logic_no_binary } |
|
||||||
|
LogicQuantor { quantifier QuantorVariable "∈" term logic_no_binary }
|
||||||
|
}
|
||||||
|
|
||||||
|
LogicPredicate {
|
||||||
|
term "∈" term |
|
||||||
|
term "∉" term |
|
||||||
|
term "⊆" term |
|
||||||
|
term "⊄" term |
|
||||||
|
term "⊂" term |
|
||||||
|
term ">" term |
|
||||||
|
term "≥" term |
|
||||||
|
term "≤" term |
|
||||||
|
term "≠" term |
|
||||||
|
term "=" term
|
||||||
|
}
|
||||||
|
|
||||||
|
LogicBinary {
|
||||||
|
logic_all !log_equiv "⇔" logic_all |
|
||||||
|
logic_all !log_impl "⇒" logic_all |
|
||||||
|
logic_all !log_or "∨" logic_all |
|
||||||
|
logic_all !log_and "&" logic_all
|
||||||
|
}
|
||||||
|
|
||||||
|
QuantorVariable { Variable | quant_var_enum }
|
||||||
|
quant_var_enum { QuantorVariable "," Variable }
|
||||||
|
Variable { Local | "(" VariableComposite ")" }
|
||||||
|
VariableComposite { var_all "," Variable }
|
||||||
|
var_all { Variable | VariableComposite }
|
||||||
|
|
||||||
|
term {
|
||||||
|
Literal | Local | Global | Radical |
|
||||||
|
BinaryOperation |
|
||||||
|
typed_constructors |
|
||||||
|
FunctionCall |
|
||||||
|
TextFunctionExpression
|
||||||
|
}
|
||||||
|
FunctionCall { Function "[" term_enum "]" }
|
||||||
|
|
||||||
|
TextFunctionExpression {
|
||||||
|
TextFunction "(" term ")" |
|
||||||
|
Filter "[" term_enum "]" "(" term ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
BinaryOperation {
|
||||||
|
term !plus "+" term |
|
||||||
|
term !minus "-" term |
|
||||||
|
term !times "*" term |
|
||||||
|
term !set_union "∪" term |
|
||||||
|
term !set_minus "\\" term |
|
||||||
|
term !set_symminus "∆" term |
|
||||||
|
term !set_intersect "∩" term |
|
||||||
|
term !set_decart "×" term |
|
||||||
|
"(" BinaryOperation ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
typed_constructors {
|
||||||
|
Enumeration |
|
||||||
|
Tuple |
|
||||||
|
Boolean |
|
||||||
|
Declarative |
|
||||||
|
Imperative |
|
||||||
|
Recursion
|
||||||
|
}
|
||||||
|
Enumeration { "{" term_enum "}"}
|
||||||
|
Tuple { "(" term_enum_min2 ")"}
|
||||||
|
Boolean {
|
||||||
|
!set_bool "ℬ" "(" term ")" |
|
||||||
|
!set_bool "ℬ" Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
term_enum { term | term_enum_min2 }
|
||||||
|
term_enum_min2 { term_enum "," term }
|
||||||
|
|
||||||
|
Declarative {
|
||||||
|
"{" Local "∈" term "|" logic "}" |
|
||||||
|
PrefixD "{" Variable "∈" term "|" logic "}"
|
||||||
|
}
|
||||||
|
Recursion {
|
||||||
|
PrefixR "{" Variable ":=" term ("|" logic)? "|" term "}"
|
||||||
|
}
|
||||||
|
Imperative {
|
||||||
|
PrefixI "{" term "|" ImperativeBlocks "}"
|
||||||
|
}
|
||||||
|
ImperativeBlocks {
|
||||||
|
imp_block |
|
||||||
|
ImperativeBlocks ";" imp_block
|
||||||
|
}
|
||||||
|
imp_block {
|
||||||
|
ImperativeIteration |
|
||||||
|
ImperativeAssignment |
|
||||||
|
ImperativeCondition
|
||||||
|
}
|
||||||
|
ImperativeIteration { Local ":∈" term }
|
||||||
|
ImperativeAssignment { Local ":=" term }
|
||||||
|
ImperativeCondition { logic }
|
||||||
|
|
||||||
|
@detectDelim
|
||||||
|
|
||||||
|
@external propSource highlighting from "./highlight.ts"
|
|
@ -0,0 +1,54 @@
|
||||||
|
@top Expression { token* }
|
||||||
|
|
||||||
|
@skip { space }
|
||||||
|
|
||||||
|
@tokens {
|
||||||
|
space { @whitespace+ }
|
||||||
|
Index { $[0-9]+ }
|
||||||
|
ComplexIndex { $[1-9](","$[1-9])* }
|
||||||
|
Integer { space$[0-9]+space? }
|
||||||
|
|
||||||
|
bigPr { "Pr" }
|
||||||
|
smallPr { "pr" }
|
||||||
|
filter { "Fi" }
|
||||||
|
card { "card" }
|
||||||
|
bool { "bool" }
|
||||||
|
debool { "debool" }
|
||||||
|
red { "red" }
|
||||||
|
|
||||||
|
ConstructPrefix { "D" | "R" | "I" }
|
||||||
|
|
||||||
|
Global { $[XCSDAPTF]$[0-9]+ }
|
||||||
|
Radical { "R"$[0-9]+ }
|
||||||
|
local { $[_a-zα-ω]$[a-zα-ω]* }
|
||||||
|
|
||||||
|
"(" ")"
|
||||||
|
"[" "]"
|
||||||
|
"{" "}"
|
||||||
|
|
||||||
|
@precedence { filter bigPr Global Radical ConstructPrefix }
|
||||||
|
@precedence { card bool debool red smallPr local }
|
||||||
|
@precedence { Integer space }
|
||||||
|
}
|
||||||
|
|
||||||
|
TextFunction {
|
||||||
|
bigPr ComplexIndex |
|
||||||
|
smallPr ComplexIndex |
|
||||||
|
filter ComplexIndex |
|
||||||
|
card | bool | debool | red
|
||||||
|
}
|
||||||
|
|
||||||
|
Local { local Index? }
|
||||||
|
|
||||||
|
token {
|
||||||
|
TextFunction |
|
||||||
|
ConstructPrefix |
|
||||||
|
Integer |
|
||||||
|
Global |
|
||||||
|
Radical |
|
||||||
|
Local
|
||||||
|
}
|
||||||
|
|
||||||
|
@detectDelim
|
||||||
|
|
||||||
|
@external propSource highlighting from "./highlight.ts"
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
import { ReactCodeMirrorRef } from '@uiw/react-codemirror';
|
||||||
|
|
||||||
import { TokenID } from '../../../utils/enums';
|
import { TokenID } from '../../utils/enums';
|
||||||
|
|
||||||
export function getSymbolSubstitute(input: string): string | undefined {
|
export function getSymbolSubstitute(input: string): string | undefined {
|
||||||
switch (input) {
|
switch (input) {
|
||||||
|
@ -68,6 +68,7 @@ export class TextWrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
insertToken(tokenID: TokenID): boolean {
|
insertToken(tokenID: TokenID): boolean {
|
||||||
|
const hasSelection = this.ref.view.state.selection.main.from !== this.ref.view.state.selection.main.to
|
||||||
switch (tokenID) {
|
switch (tokenID) {
|
||||||
case TokenID.NT_DECLARATIVE_EXPR: this.envelopeWith('D{ξ∈X1 | P1[ξ]', '}'); return true;
|
case TokenID.NT_DECLARATIVE_EXPR: this.envelopeWith('D{ξ∈X1 | P1[ξ]', '}'); return true;
|
||||||
case TokenID.NT_IMPERATIVE_EXPR: this.envelopeWith('I{(σ, γ) | σ:∈X1; γ:=F1[σ]; P1[σ, γ]', '}'); return true;
|
case TokenID.NT_IMPERATIVE_EXPR: this.envelopeWith('I{(σ, γ) | σ:∈X1; γ:=F1[σ]; P1[σ, γ]', '}'); return true;
|
||||||
|
@ -84,7 +85,7 @@ export class TextWrapper {
|
||||||
this.envelopeWith('(', ')');
|
this.envelopeWith('(', ')');
|
||||||
this.ref.view.dispatch({
|
this.ref.view.dispatch({
|
||||||
selection: {
|
selection: {
|
||||||
anchor: this.ref.view.state.selection.main.from + 1,
|
anchor: hasSelection ? this.ref.view.state.selection.main.to: this.ref.view.state.selection.main.from + 1,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
|
@ -93,15 +94,14 @@ export class TextWrapper {
|
||||||
this.envelopeWith('[', ']');
|
this.envelopeWith('[', ']');
|
||||||
this.ref.view.dispatch({
|
this.ref.view.dispatch({
|
||||||
selection: {
|
selection: {
|
||||||
anchor: this.ref.view.state.selection.main.from + 1,
|
anchor: hasSelection ? this.ref.view.state.selection.main.to: this.ref.view.state.selection.main.from + 1,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case TokenID.BOOLEAN: {
|
case TokenID.BOOLEAN: {
|
||||||
const selStart = this.ref.view.state.selection.main.from;
|
const selStart = this.ref.view.state.selection.main.from;
|
||||||
if (selStart !== this.ref.view.state.selection.main.to &&
|
if (hasSelection && this.ref.view.state.sliceDoc(selStart, selStart + 1) === 'ℬ') {
|
||||||
this.ref.view.state.sliceDoc(selStart, selStart + 1) === 'ℬ') {
|
|
||||||
this.envelopeWith('ℬ', '');
|
this.envelopeWith('ℬ', '');
|
||||||
} else {
|
} else {
|
||||||
this.envelopeWith('ℬ(', ')');
|
this.envelopeWith('ℬ(', ')');
|
64
rsconcept/frontend/src/components/RSInput/tooltip.ts
Normal file
64
rsconcept/frontend/src/components/RSInput/tooltip.ts
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import { Extension } from "@codemirror/state";
|
||||||
|
import { hoverTooltip } from "@codemirror/view";
|
||||||
|
|
||||||
|
import { IConstituenta } from '../../utils/models';
|
||||||
|
import { getCstTypificationLabel } from '../../utils/staticUI';
|
||||||
|
|
||||||
|
function createTooltipFor(cst: IConstituenta) {
|
||||||
|
const dom = document.createElement('div');
|
||||||
|
dom.className = 'overflow-y-auto border shadow-md max-h-[25rem] max-w-[25rem] min-w-[10rem] w-fit z-20 text-sm';
|
||||||
|
const alias = document.createElement('h1');
|
||||||
|
alias.className = 'text-sm text-left';
|
||||||
|
alias.textContent = `${cst.alias}: ${getCstTypificationLabel(cst)}`;
|
||||||
|
dom.appendChild(alias);
|
||||||
|
if (cst.term.resolved) {
|
||||||
|
const term = document.createElement('p');
|
||||||
|
term.innerHTML = `<b>Термин:</b> ${cst.term.resolved}`;
|
||||||
|
dom.appendChild(term);
|
||||||
|
}
|
||||||
|
if (cst.definition.formal) {
|
||||||
|
const expression = document.createElement('p');
|
||||||
|
expression.innerHTML = `<b>Выражение:</b> ${cst.definition.formal}`;
|
||||||
|
dom.appendChild(expression);
|
||||||
|
}
|
||||||
|
if (cst.definition.text.resolved) {
|
||||||
|
const definition = document.createElement('p');
|
||||||
|
definition.innerHTML = `<b>Определение:</b> ${cst.definition.text.resolved}`;
|
||||||
|
dom.appendChild(definition);
|
||||||
|
}
|
||||||
|
if (cst.convention) {
|
||||||
|
const convention = document.createElement('p');
|
||||||
|
convention.innerHTML = `<b>Конвенция:</b> ${cst.convention}`;
|
||||||
|
dom.appendChild(convention);
|
||||||
|
}
|
||||||
|
return { dom: dom }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getHoverTooltip = (items: IConstituenta[]) => {
|
||||||
|
return hoverTooltip((view, pos, side) => {
|
||||||
|
const {from, to, text} = view.state.doc.lineAt(pos);
|
||||||
|
let start = pos, end = pos;
|
||||||
|
while (start > from && /\w/.test(text[start - from - 1]))
|
||||||
|
start--;
|
||||||
|
while (end < to && /\w/.test(text[end - from]))
|
||||||
|
end++;
|
||||||
|
if (start == pos && side < 0 || end == pos && side > 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const alias = text.slice(start - from, end - from);
|
||||||
|
const cst = items.find(cst => cst.alias === alias);
|
||||||
|
if (!cst) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
pos: start,
|
||||||
|
end: end,
|
||||||
|
above: false,
|
||||||
|
create: () => createTooltipFor(cst)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rshoverTooltip(items: IConstituenta[]): Extension {
|
||||||
|
return [getHoverTooltip(items)];
|
||||||
|
}
|
|
@ -19,7 +19,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-editor {
|
.cm-editor {
|
||||||
@apply border shadow rounded clr-border px-2
|
@apply border shadow rounded clr-border px-1
|
||||||
}
|
}
|
||||||
.cm-editor.cm-focused {
|
.cm-editor.cm-focused {
|
||||||
@apply border shadow rounded outline-2 outline
|
@apply border shadow rounded outline-2 outline
|
||||||
|
|
|
@ -35,6 +35,7 @@ function DlgCreateCst({ hideWindow, defaultType, onCreate }: DlgCreateCstProps)
|
||||||
canSubmit={validated}
|
canSubmit={validated}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
|
<div className='fixed h-fit w-[15rem] px-2'>
|
||||||
<ConceptSelect
|
<ConceptSelect
|
||||||
className='my-4'
|
className='my-4'
|
||||||
options={CstTypeSelector}
|
options={CstTypeSelector}
|
||||||
|
@ -42,6 +43,8 @@ function DlgCreateCst({ hideWindow, defaultType, onCreate }: DlgCreateCstProps)
|
||||||
values={selectedType ? [{ value: selectedType, label: getCstTypeLabel(selectedType) }] : []}
|
values={selectedType ? [{ value: selectedType, label: getCstTypeLabel(selectedType) }] : []}
|
||||||
onChange={data => { setSelectedType(data.length > 0 ? data[0].value : undefined); }}
|
onChange={data => { setSelectedType(data.length > 0 ? data[0].value : undefined); }}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='h-[4rem]'></div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,9 @@ import { getCstTypeLabel, getCstTypificationLabel, mapStatusInfo } from '../../u
|
||||||
import EditorRSExpression from './EditorRSExpression';
|
import EditorRSExpression from './EditorRSExpression';
|
||||||
import ViewSideConstituents from './elements/ViewSideConstituents';
|
import ViewSideConstituents from './elements/ViewSideConstituents';
|
||||||
|
|
||||||
|
// Max height of content for left enditor pane
|
||||||
|
const UNFOLDED_HEIGHT = '59.1rem';
|
||||||
|
|
||||||
interface EditorConstituentaProps {
|
interface EditorConstituentaProps {
|
||||||
activeID?: number
|
activeID?: number
|
||||||
onOpenEdit: (cstID: number) => void
|
onOpenEdit: (cstID: number) => void
|
||||||
|
@ -109,8 +112,8 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onOpenEdit, onDe
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex items-stretch w-full gap-2 mb-2'>
|
<div className='flex items-stretch w-full gap-2 mb-2 justify-stretch'>
|
||||||
<form onSubmit={handleSubmit} className='flex-grow min-w-[50rem] max-w-min max-h-fit px-4 py-2 border'>
|
<form onSubmit={handleSubmit} className='min-w-[50rem] max-w-min px-4 py-2 border'>
|
||||||
<div className='flex items-start justify-between'>
|
<div className='flex items-start justify-between'>
|
||||||
<button type='submit'
|
<button type='submit'
|
||||||
title='Сохранить изменения'
|
title='Сохранить изменения'
|
||||||
|
@ -238,12 +241,15 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onOpenEdit, onDe
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div className='self-stretch border w-full pb-1'>
|
||||||
<ViewSideConstituents
|
<ViewSideConstituents
|
||||||
expression={expression}
|
expression={expression}
|
||||||
|
baseHeight={UNFOLDED_HEIGHT}
|
||||||
activeID={activeID}
|
activeID={activeID}
|
||||||
onOpenEdit={onOpenEdit}
|
onOpenEdit={onOpenEdit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,17 +4,17 @@ import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||||
import Button from '../../components/Common/Button';
|
import Button from '../../components/Common/Button';
|
||||||
import Label from '../../components/Common/Label';
|
import Label from '../../components/Common/Label';
|
||||||
import { Loader } from '../../components/Common/Loader';
|
import { Loader } from '../../components/Common/Loader';
|
||||||
|
import RSInput from '../../components/RSInput';
|
||||||
|
import { getSymbolSubstitute, TextWrapper } from '../../components/RSInput/textEditing';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import useCheckExpression from '../../hooks/useCheckExpression';
|
import useCheckExpression from '../../hooks/useCheckExpression';
|
||||||
import { TokenID } from '../../utils/enums';
|
import { TokenID } from '../../utils/enums';
|
||||||
import { IConstituenta, IRSErrorDescription, SyntaxTree } from '../../utils/models';
|
import { IConstituenta, IRSErrorDescription, SyntaxTree } from '../../utils/models';
|
||||||
import { getCstExpressionPrefix, getTypificationLabel } from '../../utils/staticUI';
|
import { getCstExpressionPrefix, getTypificationLabel } from '../../utils/staticUI';
|
||||||
import ParsingResult from './elements/ParsingResult';
|
import ParsingResult from './elements/ParsingResult';
|
||||||
import RSInput from './elements/RSInput';
|
|
||||||
import RSLocalButton from './elements/RSLocalButton';
|
import RSLocalButton from './elements/RSLocalButton';
|
||||||
import RSTokenButton from './elements/RSTokenButton';
|
import RSTokenButton from './elements/RSTokenButton';
|
||||||
import StatusBar from './elements/StatusBar';
|
import StatusBar from './elements/StatusBar';
|
||||||
import { getSymbolSubstitute, TextWrapper } from './elements/textEditing';
|
|
||||||
|
|
||||||
interface EditorRSExpressionProps {
|
interface EditorRSExpressionProps {
|
||||||
id: string
|
id: string
|
||||||
|
@ -227,6 +227,7 @@ function EditorRSExpression({
|
||||||
/>
|
/>
|
||||||
<RSInput innerref={rsInput}
|
<RSInput innerref={rsInput}
|
||||||
className='mt-2'
|
className='mt-2'
|
||||||
|
height='10.1rem'
|
||||||
value={value}
|
value={value}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
editable={!disabled}
|
editable={!disabled}
|
||||||
|
@ -246,9 +247,9 @@ function EditorRSExpression({
|
||||||
</div>
|
</div>
|
||||||
{isActive && !disabled && EditButtons}
|
{isActive && !disabled && EditButtons}
|
||||||
</div>
|
</div>
|
||||||
{ (loading || parseData) &&
|
{ (isActive || loading || parseData) &&
|
||||||
<div className='w-full overflow-y-auto border mt-2 max-h-[14rem] min-h-[7rem]'>
|
<div className='w-full overflow-y-auto border mt-2 max-h-[14rem] min-h-[4.2rem]'>
|
||||||
{ loading && <Loader />}
|
{ loading && <Loader size={6} />}
|
||||||
{ !loading && parseData &&
|
{ !loading && parseData &&
|
||||||
<ParsingResult
|
<ParsingResult
|
||||||
data={parseData}
|
data={parseData}
|
||||||
|
|
|
@ -107,6 +107,7 @@ function EditorTermGraph() {
|
||||||
onClick={handleCenter}
|
onClick={handleCenter}
|
||||||
/>
|
/>
|
||||||
<ConceptSelect
|
<ConceptSelect
|
||||||
|
className='min-w-[9.5rem]'
|
||||||
options={GraphLayoutSelector}
|
options={GraphLayoutSelector}
|
||||||
placeholder='Выберите тип'
|
placeholder='Выберите тип'
|
||||||
values={layout ? [{ value: layout, label: mapLayoutLabels.get(layout) }] : []}
|
values={layout ? [{ value: layout, label: mapLayoutLabels.get(layout) }] : []}
|
||||||
|
|
|
@ -33,9 +33,8 @@ function ParsingResult({ data, onShowAST, onShowError }: ParsingResultProps) {
|
||||||
title='отобразить дерево разбора'
|
title='отобразить дерево разбора'
|
||||||
onClick={handleShowAST}
|
onClick={handleShowAST}
|
||||||
>
|
>
|
||||||
Дерево разбора:
|
Дерево разбора
|
||||||
</button>
|
</button>
|
||||||
<span> {data.astText}</span>
|
|
||||||
</p>}
|
</p>}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -11,14 +11,18 @@ import ConstituentaTooltip from './ConstituentaTooltip';
|
||||||
import DependencyModePicker from './DependencyModePicker';
|
import DependencyModePicker from './DependencyModePicker';
|
||||||
import MatchModePicker from './MatchModePicker';
|
import MatchModePicker from './MatchModePicker';
|
||||||
|
|
||||||
|
// Height that should be left to accomodate navigation panel + bottom margin
|
||||||
|
const LOCAL_NAVIGATION_H = '2.6rem';
|
||||||
|
|
||||||
interface ViewSideConstituentsProps {
|
interface ViewSideConstituentsProps {
|
||||||
expression: string
|
expression: string
|
||||||
|
baseHeight: string
|
||||||
activeID?: number
|
activeID?: number
|
||||||
onOpenEdit: (cstID: number) => void
|
onOpenEdit: (cstID: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function ViewSideConstituents({ expression, activeID, onOpenEdit }: ViewSideConstituentsProps) {
|
function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: ViewSideConstituentsProps) {
|
||||||
const { darkMode } = useConceptTheme();
|
const { darkMode, noNavigation } = useConceptTheme();
|
||||||
const { schema } = useRSForm();
|
const { schema } = useRSForm();
|
||||||
|
|
||||||
const [filterMatch, setFilterMatch] = useLocalStorage('side-filter-match', CstMatchMode.ALL);
|
const [filterMatch, setFilterMatch] = useLocalStorage('side-filter-match', CstMatchMode.ALL);
|
||||||
|
@ -27,7 +31,8 @@ function ViewSideConstituents({ expression, activeID, onOpenEdit }: ViewSideCons
|
||||||
|
|
||||||
const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items ?? []);
|
const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items ?? []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(
|
||||||
|
() => {
|
||||||
if (!schema?.items) {
|
if (!schema?.items) {
|
||||||
setFilteredData([]);
|
setFilteredData([]);
|
||||||
return;
|
return;
|
||||||
|
@ -67,8 +72,8 @@ function ViewSideConstituents({ expression, activeID, onOpenEdit }: ViewSideCons
|
||||||
}
|
}
|
||||||
}, [onOpenEdit]);
|
}, [onOpenEdit]);
|
||||||
|
|
||||||
const conditionalRowStyles = useMemo(() =>
|
const conditionalRowStyles = useMemo(
|
||||||
[
|
() => [
|
||||||
{
|
{
|
||||||
when: (cst: IConstituenta) => cst.id === activeID,
|
when: (cst: IConstituenta) => cst.id === activeID,
|
||||||
style: {
|
style: {
|
||||||
|
@ -77,8 +82,8 @@ function ViewSideConstituents({ expression, activeID, onOpenEdit }: ViewSideCons
|
||||||
}
|
}
|
||||||
], [activeID, darkMode]);
|
], [activeID, darkMode]);
|
||||||
|
|
||||||
const columns = useMemo(() =>
|
const columns = useMemo(
|
||||||
[
|
() => [
|
||||||
{
|
{
|
||||||
id: 'id',
|
id: 'id',
|
||||||
selector: (cst: IConstituenta) => cst.id,
|
selector: (cst: IConstituenta) => cst.id,
|
||||||
|
@ -136,33 +141,42 @@ function ViewSideConstituents({ expression, activeID, onOpenEdit }: ViewSideCons
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
], []
|
], []);
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
const maxHeight = useMemo(
|
||||||
<div className='max-h-[calc(100vh-10.3rem)] min-h-[40rem] overflow-y-scroll border flex-grow w-full'>
|
() => {
|
||||||
|
const siblingHeight = `${baseHeight} - ${LOCAL_NAVIGATION_H}`
|
||||||
|
return (noNavigation ?
|
||||||
|
`calc(min(100vh - 5.2rem, ${siblingHeight}))`
|
||||||
|
: `calc(min(100vh - 8.7rem, ${siblingHeight}))`);
|
||||||
|
}, [noNavigation, baseHeight]);
|
||||||
|
|
||||||
|
return (<>
|
||||||
<div className='sticky top-0 left-0 right-0 z-10 flex items-center justify-between w-full gap-1 px-2 py-1 bg-white border-b rounded clr-bg-pop clr-border'>
|
<div className='sticky top-0 left-0 right-0 z-10 flex items-center justify-between w-full gap-1 px-2 py-1 bg-white border-b rounded clr-bg-pop clr-border'>
|
||||||
<div className='flex items-center justify-between w-full'>
|
<MatchModePicker
|
||||||
<MatchModePicker value={filterMatch} onChange={setFilterMatch}/>
|
value={filterMatch}
|
||||||
|
onChange={setFilterMatch}
|
||||||
|
/>
|
||||||
<input type='text'
|
<input type='text'
|
||||||
className='w-full px-2 bg-white outline-none hover:text-clip clr-bg-pop clr-border'
|
className='w-full px-2 bg-white outline-none hover:text-clip clr-bg-pop clr-border'
|
||||||
placeholder='наберите текст фильтра'
|
placeholder='наберите текст фильтра'
|
||||||
value={filterText}
|
value={filterText}
|
||||||
onChange={event => { setFilterText(event.target.value); }}
|
onChange={event => setFilterText(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<DependencyModePicker value={filterSource} onChange={setFilterSource}/>
|
<DependencyModePicker value={filterSource} onChange={setFilterSource}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className='overflow-y-auto' style={{maxHeight : `${maxHeight}`}}>
|
||||||
<ConceptDataTable
|
<ConceptDataTable
|
||||||
data={filteredData}
|
data={filteredData}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
conditionalRowStyles={conditionalRowStyles}
|
|
||||||
keyField='id'
|
keyField='id'
|
||||||
noContextMenu
|
conditionalRowStyles={conditionalRowStyles}
|
||||||
noDataComponent={<span className='flex flex-col justify-center p-2 text-center'>
|
noDataComponent={
|
||||||
|
<span className='flex flex-col justify-center p-2 text-center min-h-[5rem]'>
|
||||||
<p>Список конституент пуст</p>
|
<p>Список конституент пуст</p>
|
||||||
<p>Измените параметры фильтра</p>
|
<p>Измените параметры фильтра</p>
|
||||||
</span>}
|
</span>
|
||||||
|
}
|
||||||
|
|
||||||
striped
|
striped
|
||||||
highlightOnHover
|
highlightOnHover
|
||||||
|
@ -173,7 +187,7 @@ function ViewSideConstituents({ expression, activeID, onOpenEdit }: ViewSideCons
|
||||||
dense
|
dense
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
</>);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ViewSideConstituents;
|
export default ViewSideConstituents;
|
||||||
|
|
60
rsconcept/frontend/src/utils/print-lezer-tree.ts
Normal file
60
rsconcept/frontend/src/utils/print-lezer-tree.ts
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import { NodeType, Tree, TreeCursor } from "@lezer/common"
|
||||||
|
|
||||||
|
export type CursorNode = {
|
||||||
|
type: NodeType
|
||||||
|
from: number
|
||||||
|
to: number
|
||||||
|
isLeaf: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function cursorNode({ type, from, to }: TreeCursor, isLeaf = false): CursorNode {
|
||||||
|
return { type, from, to, isLeaf }
|
||||||
|
}
|
||||||
|
|
||||||
|
type TreeTraversalOptions = {
|
||||||
|
beforeEnter?: (cursor: TreeCursor) => void
|
||||||
|
onEnter: (node: CursorNode) => false | void
|
||||||
|
onLeave?: (node: CursorNode) => false | void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function traverseTree(tree: Tree, { beforeEnter, onEnter, onLeave, }: TreeTraversalOptions) {
|
||||||
|
const cursor = tree.cursor();
|
||||||
|
for (;;) {
|
||||||
|
let node = cursorNode(cursor)
|
||||||
|
let leave = false
|
||||||
|
const enter = !node.type.isAnonymous
|
||||||
|
if (enter && beforeEnter) beforeEnter(cursor)
|
||||||
|
node.isLeaf = !cursor.firstChild()
|
||||||
|
if (enter) {
|
||||||
|
leave = true
|
||||||
|
if (onEnter(node) === false) return
|
||||||
|
}
|
||||||
|
if (!node.isLeaf) continue
|
||||||
|
for (;;) {
|
||||||
|
node = cursorNode(cursor, node.isLeaf)
|
||||||
|
if (leave && onLeave) if (onLeave(node) === false) return;
|
||||||
|
leave = cursor.type.isAnonymous
|
||||||
|
node.isLeaf = false
|
||||||
|
if (cursor.nextSibling()) break;
|
||||||
|
if (!cursor.parent()) return;
|
||||||
|
leave = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function printTree(tree: Tree): string {
|
||||||
|
const state = {
|
||||||
|
output: "",
|
||||||
|
prefixes: [] as string[]
|
||||||
|
}
|
||||||
|
traverseTree(tree, {
|
||||||
|
onEnter: node => {
|
||||||
|
state.output += "[";
|
||||||
|
state.output += node.type.name;
|
||||||
|
},
|
||||||
|
onLeave: () => {
|
||||||
|
state.output += "]";
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return state.output;
|
||||||
|
}
|
|
@ -6,5 +6,5 @@
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"allowSyntheticDefaultImports": true
|
"allowSyntheticDefaultImports": true
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts"]
|
"include": ["vite.config.ts", "package.json"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,33 @@
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
import { dependencies } from './package.json'
|
||||||
|
|
||||||
|
const exclVendors = ['react', 'react-router-dom', 'react-dom']
|
||||||
|
function renderChunks(deps: Record<string, string>) {
|
||||||
|
const chunks = {}
|
||||||
|
Object.keys(deps).forEach((key) => {
|
||||||
|
if (exclVendors.includes(key)) return
|
||||||
|
chunks[key] = [key]
|
||||||
|
})
|
||||||
|
return chunks
|
||||||
|
}
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
server: {
|
server: {
|
||||||
port: 3000
|
port: 3000
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
chunkSizeWarningLimit: 4000, // KB
|
||||||
|
sourcemap: false,
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks: {
|
||||||
|
...renderChunks(dependencies),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
3
updateProd.sh
Normal file
3
updateProd.sh
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
git pull
|
||||||
|
docker compose -f "docker-compose-prod.yml" up --build -d
|
||||||
|
docker image prune -f
|
Loading…
Reference in New Issue
Block a user