+
+ Словоформ: {forms.length}
+
setInputGrams(sortGrammemes([...newValue]))}
+ onChange={newValue => setInputGrams([...newValue].sort(compareGrammemeOptions))}
/>
diff --git a/rsconcept/frontend/src/utils/codemirror.ts b/rsconcept/frontend/src/utils/codemirror.ts
new file mode 100644
index 00000000..a2636237
--- /dev/null
+++ b/rsconcept/frontend/src/utils/codemirror.ts
@@ -0,0 +1,219 @@
+import { NodeType, Tree, TreeCursor } from '@lezer/common'
+
+import { IEntityReference, ISyntacticReference, parseGrammemes } from '../models/language'
+import { IConstituenta } from '../models/rsform'
+import { colorfgGrammeme,IColorTheme } from './color'
+import { describeConstituentaTerm, labelCstTypification, labelGrammeme } from './labels'
+
+/**
+ * Represents syntax tree node data.
+*/
+export interface SyntaxNode {
+ type: NodeType
+ from: number
+ to: number
+}
+
+/**
+ * Represents syntax tree cursor data.
+*/
+export interface CursorNode
+extends SyntaxNode {
+ 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
+}
+
+/**
+ * Implements depth-first traversal.
+*/
+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
+ }
+ }
+}
+
+/**
+ * Prints tree to compact string.
+*/
+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;
+}
+
+/**
+ * Reteives a list of all nodes, containing given range and corresponding to a filter.
+*/
+export function findEnvelopingNodes(start: number, finish: number, tree: Tree, filter?: number[]): SyntaxNode[] {
+ const result: SyntaxNode[] = [];
+ tree.cursor().iterate(
+ node => {
+ if (
+ (!filter || filter.includes(node.type.id)) &&
+ node.to >= start && node.from <= finish
+ ) {
+ result.push({
+ type: node.type,
+ to: node.to,
+ from: node.from
+ });
+ }
+ });
+ return result;
+}
+
+/**
+ * Reteives a list of all nodes, contained in given range and corresponding to a filter.
+*/
+export function findContainedNodes(start: number, finish: number, tree: Tree, filter?: number[]): SyntaxNode[] {
+ const result: SyntaxNode[] = [];
+ tree.cursor().iterate(
+ node => {
+ if (
+ (!filter || filter.includes(node.type.id)) &&
+ node.to <= start && node.from >= finish
+ ) {
+ result.push({
+ type: node.type,
+ to: node.to,
+ from: node.from
+ });
+ }
+ });
+ return result;
+}
+
+/**
+ * Create DOM tooltip for {@link Constituenta}.
+*/
+export function domTooltipConstituenta(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-tooltip text-sm px-2 py-2';
+
+ const alias = document.createElement('p');
+ alias.innerHTML = `
${cst.alias}: ${labelCstTypification(cst)}`;
+ dom.appendChild(alias);
+
+ if (cst.term_resolved) {
+ const term = document.createElement('p');
+ term.innerHTML = `
Термин: ${cst.term_resolved}`;
+ dom.appendChild(term);
+ }
+
+ if (cst.definition_formal) {
+ const expression = document.createElement('p');
+ expression.innerHTML = `
Выражение: ${cst.definition_formal}`;
+ dom.appendChild(expression);
+ }
+
+ if (cst.definition_resolved) {
+ const definition = document.createElement('p');
+ definition.innerHTML = `
Определение: ${cst.definition_resolved}`;
+ dom.appendChild(definition);
+ }
+
+ if (cst.convention) {
+ const convention = document.createElement('p');
+ convention.innerHTML = `
Конвенция: ${cst.convention}`;
+ dom.appendChild(convention);
+ }
+ return { dom: dom };
+}
+
+/**
+ * Create DOM tooltip for {@link IEntityReference}.
+*/
+export function domTooltipEntityReference(ref: IEntityReference, cst: IConstituenta | undefined, colors: IColorTheme) {
+ const DIMENSIONS = 'max-h-[25rem] max-w-[25rem] min-w-[10rem] w-fit z-tooltip px-2 py-2';
+ const LAYOUT = 'flex flex-col gap-1 overflow-y-auto'
+
+ const dom = document.createElement('div');
+ dom.className = `${DIMENSIONS} ${LAYOUT} border shadow-md text-sm select-none cursor-auto`;
+
+ const term = document.createElement('p');
+ term.innerHTML = `
${ref.entity}: ${describeConstituentaTerm(cst)}`;
+ dom.appendChild(term);
+
+ const grams = document.createElement('div');
+ grams.className = 'flex flex-wrap gap-1';
+ parseGrammemes(ref.form).forEach(
+ gramStr => {
+ const gram = document.createElement('div');
+ gram.id =`tooltip-${gramStr}`;
+ gram.className='min-w-[3rem] px-1 text-sm text-center rounded-md whitespace-nowrap';
+ gram.style.borderWidth = '1px';
+ gram.style.borderColor = colorfgGrammeme(gramStr, colors);
+ gram.style.color = colorfgGrammeme(gramStr, colors);
+ gram.style.fontWeight = '600';
+ gram.style.backgroundColor = colors.bgInput;
+ gram.innerText = labelGrammeme(gramStr);
+ grams.appendChild(gram);
+ });
+ dom.appendChild(grams);
+
+ return { dom: dom };
+}
+
+/**
+ * Create DOM tooltip for {@link ISyntacticReference}.
+*/
+export function domTooltipSyntacticReference(ref: ISyntacticReference) {
+ const DIMENSIONS = 'max-h-[25rem] max-w-[25rem] min-w-[10rem] w-fit z-tooltip px-2 py-2';
+ const LAYOUT = 'flex flex-col gap-1 overflow-y-auto'
+
+ const dom = document.createElement('div');
+ dom.className = `${DIMENSIONS} ${LAYOUT} border shadow-md text-sm select-none cursor-auto`;
+
+ const title = document.createElement('p');
+ title.innerHTML = '
Синтаксическая ссылка';
+ dom.appendChild(title);
+
+ const offset = document.createElement('p');
+ offset.innerHTML = `
Смещение: ${ref.offset}`;
+ dom.appendChild(offset);
+
+ const nominal = document.createElement('p');
+ nominal.innerHTML = `
Начальная форма: ${ref.nominal}`;
+ dom.appendChild(nominal);
+
+ return { dom: dom };
+}
\ No newline at end of file
diff --git a/rsconcept/frontend/src/utils/color.ts b/rsconcept/frontend/src/utils/color.ts
index c645b051..4df491b3 100644
--- a/rsconcept/frontend/src/utils/color.ts
+++ b/rsconcept/frontend/src/utils/color.ts
@@ -1,6 +1,6 @@
// =========== Modules contains all dynamic color definitions ==========
-import { Grammeme, NounGrams, PartOfSpeech, VerbGrams } from '../models/language'
+import { GramData, Grammeme, NounGrams, PartOfSpeech, VerbGrams } from '../models/language'
import { CstClass, ExpressionStatus } from '../models/rsform'
import { ISyntaxTreeNode, TokenID } from '../models/rslang'
@@ -385,30 +385,31 @@ export function colorbgCstClass(cstClass: CstClass, colors: IColorTheme): string
}
}
-export function colorfgGrammeme(gram: Grammeme, colors: IColorTheme): string {
- if (PartOfSpeech.includes(gram)) {
+export function colorfgGrammeme(gram: GramData, colors: IColorTheme): string {
+ if (PartOfSpeech.includes(gram as Grammeme)) {
return colors.fgBlue;
}
- if (NounGrams.includes(gram)) {
+ if (NounGrams.includes(gram as Grammeme)) {
return colors.fgGreen;
}
- if (VerbGrams.includes(gram)) {
+ if (VerbGrams.includes(gram as Grammeme)) {
return colors.fgTeal;
}
- if (gram === Grammeme.UNKN) {
+ if (!Object.values(Grammeme).includes(gram as Grammeme)) {
return colors.fgRed;
+ } else {
+ return colors.fgPurple;
}
- return colors.fgPurple;
}
-export function colorbgGrammeme(gram: Grammeme, colors: IColorTheme): string {
- if (PartOfSpeech.includes(gram)) {
+export function colorbgGrammeme(gram: GramData, colors: IColorTheme): string {
+ if (PartOfSpeech.includes(gram as Grammeme)) {
return colors.bgBlue;
}
- if (NounGrams.includes(gram)) {
+ if (NounGrams.includes(gram as Grammeme)) {
return colors.bgGreen;
}
- if (VerbGrams.includes(gram)) {
+ if (VerbGrams.includes(gram as Grammeme)) {
return colors.bgTeal;
}
return colors.bgInput;
diff --git a/rsconcept/frontend/src/utils/labels.ts b/rsconcept/frontend/src/utils/labels.ts
index 902b660c..ba01d3f2 100644
--- a/rsconcept/frontend/src/utils/labels.ts
+++ b/rsconcept/frontend/src/utils/labels.ts
@@ -1,6 +1,6 @@
// =========== Modules contains all text descriptors ==========
-import { Grammeme,IGramData } from '../models/language';
+import { GramData,Grammeme } from '../models/language';
import { CstMatchMode, DependencyMode, HelpTopic } from '../models/miscelanious';
import { CstClass, CstType, ExpressionStatus, IConstituenta } from '../models/rsform';
import { IFunctionArg, IRSErrorDescription, ISyntaxTreeNode, ParsingStatus, RSErrorType, TokenID } from '../models/rslang';
@@ -23,6 +23,17 @@ export function describeConstituenta(cst: IConstituenta): string {
}
}
+export function describeConstituentaTerm(cst: IConstituenta | undefined): string {
+ if (!cst) {
+ return '!Конституента отсутствует!';
+ }
+ if (!cst.term_resolved) {
+ return '!Пустой термин!';
+ } else {
+ return cst.term_resolved;
+ }
+}
+
export function labelConstituenta(cst: IConstituenta) {
return `${cst.alias}: ${describeConstituenta(cst)}`;
}
@@ -346,8 +357,10 @@ export function labelSyntaxTree(node: ISyntaxTreeNode): string {
return 'UNKNOWN ' + String(node.typeID);
}
-export function labelGrammeme(gram: IGramData): string {
- switch (gram.type) {
+export function labelGrammeme(gram: GramData): string {
+ switch (gram) {
+ default: return `Неизв: ${gram}`;
+
case Grammeme.NOUN: return 'ЧР: сущ';
case Grammeme.VERB: return 'ЧР: глагол';
case Grammeme.INFN: return 'ЧР: глагол инф';
@@ -411,8 +424,6 @@ export function labelGrammeme(gram: IGramData): string {
case Grammeme.Slng: return 'Стиль: жаргон';
case Grammeme.Arch: return 'Стиль: устаревший';
case Grammeme.Litr: return 'Стиль: литературный';
-
- case Grammeme.UNKN: return `Неизв: ${gram.data}`;
}
}
diff --git a/rsconcept/frontend/src/utils/print-lezer-tree.ts b/rsconcept/frontend/src/utils/print-lezer-tree.ts
deleted file mode 100644
index 4020f825..00000000
--- a/rsconcept/frontend/src/utils/print-lezer-tree.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-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;
-}
diff --git a/rsconcept/frontend/src/utils/selectors.ts b/rsconcept/frontend/src/utils/selectors.ts
index f464f032..daedce1f 100644
--- a/rsconcept/frontend/src/utils/selectors.ts
+++ b/rsconcept/frontend/src/utils/selectors.ts
@@ -1,13 +1,15 @@
// Module: Selector maps
import { LayoutTypes } from 'reagraph';
-import { Grammeme, IGramData } from '../models/language';
+import { compareGrammemes,type GramData, Grammeme } from '../models/language';
import { CstType } from '../models/rsform';
import { ColoringScheme } from '../pages/RSFormPage/EditorTermGraph';
import { labelGrammeme } from './labels';
import { labelCstType } from './labels';
-
+/**
+ * Represents options for GraphLayout selector.
+*/
export const SelectorGraphLayout: { value: LayoutTypes, label: string }[] = [
{ value: 'treeTd2d', label: 'Граф: ДеревоВ 2D' },
{ value: 'treeTd3d', label: 'Граф: ДеревоВ 3D' },
@@ -24,12 +26,18 @@ export const SelectorGraphLayout: { value: LayoutTypes, label: string }[] = [
// { value: 'hierarchicalLr', label: 'hierarchicalLr'}
];
+/**
+ * Represents options for {@link ColoringScheme} selector.
+*/
export const SelectorGraphColoring: { value: ColoringScheme, label: string }[] = [
{ value: 'none', label: 'Цвет: моно' },
{ value: 'status', label: 'Цвет: статус' },
{ value: 'type', label: 'Цвет: класс' },
];
+/**
+ * Represents options for {@link CstType} selector.
+*/
export const SelectorCstType = (
Object.values(CstType)).map(
typeStr => ({
@@ -38,11 +46,24 @@ export const SelectorCstType = (
})
);
-export interface IGrammemeOption extends IGramData {
- value: string
+/**
+ * Represents single option for {@link Grammeme} selector.
+*/
+export interface IGrammemeOption {
+ value: GramData
label: string
}
+/**
+ * Compares {@link IGrammemeOption} based on Grammeme comparison.
+ */
+export function compareGrammemeOptions(left: IGrammemeOption, right: IGrammemeOption): number {
+ return compareGrammemes(left.value, right.value);
+}
+
+/**
+ * Represents list of {@link Grammeme}s available in reference construction.
+*/
export const SelectorGrammemesList = [
Grammeme.NOUN, Grammeme.VERB,
@@ -62,11 +83,12 @@ export const SelectorGrammemesList = [
Grammeme.pssv, Grammeme.actv,
];
+/**
+ * Represents options for {@link Grammeme} selector.
+*/
export const SelectorGrammems: IGrammemeOption[] =
SelectorGrammemesList.map(
gram => ({
- type: gram,
- data: gram as string,
- value: gram as string,
- label: labelGrammeme({type: gram, data: ''} as IGramData)
+ value: gram,
+ label: labelGrammeme(gram)
}));