Display text reference tooltips

This commit is contained in:
IRBorisov 2023-09-27 23:36:51 +03:00
parent 1f8f904626
commit 78c6a2306e
19 changed files with 490 additions and 292 deletions

View File

@ -13,7 +13,7 @@ import Label from '../Common/Label';
import { ccBracketMatching } from './bracketMatching'; import { ccBracketMatching } from './bracketMatching';
import { RSLanguage } from './rslang'; import { RSLanguage } from './rslang';
import { getSymbolSubstitute,TextWrapper } from './textEditing'; import { getSymbolSubstitute,TextWrapper } from './textEditing';
import { rshoverTooltip as rsHoverTooltip } from './tooltip'; import { rsHoverTooltip } from './tooltip';
const editorSetup: BasicSetupOptions = { const editorSetup: BasicSetupOptions = {
highlightSpecialChars: false, highlightSpecialChars: false,

View File

@ -1,6 +1,11 @@
import {LRLanguage} from '@codemirror/language' import {LRLanguage} from '@codemirror/language'
import { parser } from './parser'; import { parser } from './parser';
import { Function, Global, Predicate } from './parser.terms';
export const GlobalTokens: number[] = [
Global, Function, Predicate
]
export const RSLanguage = LRLanguage.define({ export const RSLanguage = LRLanguage.define({
parser: parser, parser: parser,

View File

@ -1,4 +1,4 @@
import { printTree } from '../../../utils/print-lezer-tree'; import { printTree } from '../../../utils/codemirror';
import { parser } from './parser'; import { parser } from './parser';
const testData = [ const testData = [

View File

@ -1,50 +1,33 @@
import { syntaxTree } from "@codemirror/language"
import { Extension } from '@codemirror/state'; import { Extension } from '@codemirror/state';
import { hoverTooltip } from '@codemirror/view'; import { hoverTooltip } from '@codemirror/view';
import { EditorState } from '@uiw/react-codemirror';
import { IConstituenta } from '../../models/rsform'; import { IConstituenta } from '../../models/rsform';
import { labelCstTypification } from '../../utils/labels'; import { findEnvelopingNodes } from '../../utils/codemirror';
import { domTooltipConstituenta } from '../../utils/codemirror';
import { GlobalTokens } from './rslang';
function createTooltipFor(cst: IConstituenta) { function findAliasAt(pos: number, state: EditorState) {
const dom = document.createElement('div'); const { from: lineStart, to: lineEnd, text } = state.doc.lineAt(pos);
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 nodes = findEnvelopingNodes(pos, pos, syntaxTree(state), GlobalTokens);
const alias = document.createElement('p'); let alias = '';
alias.innerHTML = `<b>${cst.alias}:</b> ${labelCstTypification(cst)}`; let start = 0;
dom.appendChild(alias); let end = 0;
if (cst.term_resolved) { nodes.forEach(
const term = document.createElement('p'); node => {
term.innerHTML = `<b>Термин:</b> ${cst.term_resolved}`; if (node.to <= lineEnd && node.from >= lineStart) {
dom.appendChild(term); alias = text.slice(node.from - lineStart, node.to - lineStart);
} start = node.from;
if (cst.definition_formal) { end = node.to;
const expression = document.createElement('p'); }
expression.innerHTML = `<b>Выражение:</b> ${cst.definition_formal}`; });
dom.appendChild(expression); return {alias, start, end};
}
if (cst.definition_resolved) {
const definition = document.createElement('p');
definition.innerHTML = `<b>Определение:</b> ${cst.definition_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[]) => { const globalsHoverTooltip = (items: IConstituenta[]) => {
return hoverTooltip((view, pos, side) => { return hoverTooltip((view, pos) => {
const {from, to, text} = view.state.doc.lineAt(pos); const { alias, start, end } = findAliasAt(pos, view.state);
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); const cst = items.find(cst => cst.alias === alias);
if (!cst) { if (!cst) {
return null; return null;
@ -53,11 +36,11 @@ export const getHoverTooltip = (items: IConstituenta[]) => {
pos: start, pos: start,
end: end, end: end,
above: false, above: false,
create: () => createTooltipFor(cst) create: () => domTooltipConstituenta(cst)
} }
}); });
} }
export function rshoverTooltip(items: IConstituenta[]): Extension { export function rsHoverTooltip(items: IConstituenta[]): Extension {
return [getHoverTooltip(items)]; return [globalsHoverTooltip(items)];
} }

View File

@ -13,7 +13,7 @@ import Label from '../Common/Label';
import Modal from '../Common/Modal'; import Modal from '../Common/Modal';
import PrettyJson from '../Common/PrettyJSON'; import PrettyJson from '../Common/PrettyJSON';
import { NaturalLanguage } from './parse'; import { NaturalLanguage } from './parse';
import { rshoverTooltip as rsHoverTooltip } from './tooltip'; import { refsHoverTooltip } from './tooltip';
const editorSetup: BasicSetupOptions = { const editorSetup: BasicSetupOptions = {
highlightSpecialChars: false, highlightSpecialChars: false,
@ -87,7 +87,7 @@ function RefsInput({
selection: colors.bgHover selection: colors.bgHover
}, },
styles: [ styles: [
{ tag: tags.name, color: colors.fgPurple }, // GlobalID { tag: tags.name, color: colors.fgPurple, cursor: 'pointer' }, // GlobalID
{ tag: tags.literal, color: colors.fgTeal }, // literals { tag: tags.literal, color: colors.fgTeal }, // literals
] ]
}), [editable, colors, darkMode]); }), [editable, colors, darkMode]);
@ -96,8 +96,8 @@ function RefsInput({
() => [ () => [
EditorView.lineWrapping, EditorView.lineWrapping,
NaturalLanguage, NaturalLanguage,
rsHoverTooltip(schema?.items || []), refsHoverTooltip(schema?.items || [], colors),
], [schema?.items]); ], [schema?.items, colors]);
function handleChange(newValue: string) { function handleChange(newValue: string) {
if (onChange) onChange(newValue); if (onChange) onChange(newValue);

View File

@ -3,8 +3,9 @@ import {styleTags, tags} from '@lezer/highlight';
export const highlighting = styleTags({ export const highlighting = styleTags({
RefEntity: tags.name, RefEntity: tags.name,
Global: tags.name, Global: tags.name,
Gram: tags.name, Grams: tags.name,
RefSyntactic: tags.literal, RefSyntactic: tags.literal,
Offset: tags.literal, Offset: tags.literal,
Nominal: tags.literal,
}); });

View File

@ -3,8 +3,8 @@ export const
Text = 1, Text = 1,
RefEntity = 2, RefEntity = 2,
Global = 3, Global = 3,
Gram = 4, Grams = 4,
RefSyntactic = 5, RefSyntactic = 5,
Offset = 6, Offset = 6,
Nominal = 7, Nominal = 7,
Word = 8 Filler = 8

View File

@ -1,18 +1,18 @@
import { printTree } from '../../../utils/print-lezer-tree'; import { printTree } from '../../../utils/codemirror';
import { parser } from './parser'; import { parser } from './parser';
const testData = [ const testData = [
['', '[Text]'], ['', '[Text]'],
['тест русский', '[Text[Word][Word]]'], ['тест русский', '[Text[Filler]]'],
['test english', '[Text[Word][Word]]'], ['test english', '[Text[Filler]]'],
['test greek σσσ', '[Text[Word][Word][Word]]'], ['test greek σσσ', '[Text[Filler]]'],
['X1 раз два X2', '[Text[Word][Word][Word][Word]]'], ['X1 раз два X2', '[Text[Filler]]'],
['@{1| черный }', '[Text[RefSyntactic[Offset][Nominal[Word]]]]'], ['@{1| черный }', '[Text[RefSyntactic[Offset][Nominal]]]'],
['@{-1| черный }', '[Text[RefSyntactic[Offset][Nominal[Word]]]]'], ['@{-1| черный }', '[Text[RefSyntactic[Offset][Nominal]]]'],
['@{-100| черный слон }', '[Text[RefSyntactic[Offset][Nominal[Word][Word]]]]'], ['@{-100| черный слон }', '[Text[RefSyntactic[Offset][Nominal]]]'],
['@{X1|VERB,past,sing}', '[Text[RefEntity[Global][Gram][Gram][Gram]]]'], ['@{X1|VERB,past,sing}', '[Text[RefEntity[Global][Grams]]]'],
['@{X12|VERB,past,sing}', '[Text[RefEntity[Global][Gram][Gram][Gram]]]'], ['@{X12|VERB,past,sing}', '[Text[RefEntity[Global][Grams]]]'],
]; ];
describe('Testing NaturalParser', () => { describe('Testing NaturalParser', () => {

View File

@ -3,16 +3,16 @@ import {LRParser} from "@lezer/lr"
import {highlighting} from "./highlight.ts" import {highlighting} from "./highlight.ts"
export const parser = LRParser.deserialize({ export const parser = LRParser.deserialize({
version: 14, version: 14,
states: "#rQVQPOOO_QQO'#C^OOQO'#Ck'#CkOOQO'#Cj'#CjOOQO'#Ce'#CeQVQPOOOgQPO,58xOlQPO,58{OOQO-E6c-E6cOqQSO1G.dOvQPO1G.gO{QQO'#CoO!TQPO7+$OOOQO'#Cf'#CfO!YQPO'#CcO!bQPO7+$ROqQSO,59ZOOQO<<Gj<<GjOOQO-E6d-E6dOOQO<<Gm<<GmOOQO1G.u1G.u", states: "$[QVQPOOO_QQO'#C^OOQO'#Ck'#CkOgQPO'#CsOOQO'#Cd'#CdOOQO'#Cj'#CjOOQO'#Ce'#CeQVQPOOOrQPO,58xOwQPO,58{OOQO,59_,59_OOQO-E6c-E6cO|QSO1G.dO!RQPO1G.gO!WQQO'#CoOOQO'#C`'#C`O!`QPO7+$OOOQO'#Cf'#CfO!eQPO'#CcO!mQPO7+$RO|QSO,59ZOOQO<<Gj<<GjOOQO-E6d-E6dOOQO<<Gm<<GmOOQO1G.u1G.u",
stateData: "!j~O]OS~OWROaPO~ORUOUVO~ObXO~ObYO~OSZO~OW]O~Od`O`cX~O`aO~OW]O`VX~O`cO~OW]~", stateData: "!u~O]OS~OaPOfRO~ORWOUXO~OfROZgXagX~Ob[O~Ob]O~Od^O~OfaO~OedO`cX~O`eO~OfaO`VX~O`gO~Of]~",
goto: "!WdPPePPePiPlrPPPx|PPP!QTQOTR_YQTORWTQ^YRb^TSOTTROTQ[XRd`", goto: "!fhPPiPmiPpsw}PPP!TsPPP!XPPP!_TQOVR`[Rc]TTOVQVORZVQb]RfbTUOVQ_[RhdSSOVRYR",
nodeNames: "⚠ Text RefEntity Global Gram RefSyntactic Offset Nominal Word", nodeNames: "⚠ Text RefEntity Global Grams RefSyntactic Offset Nominal Filler",
maxTerm: 20, maxTerm: 23,
propSources: [highlighting], propSources: [highlighting],
skippedNodes: [0], skippedNodes: [0],
repeatNodeCount: 2, repeatNodeCount: 2,
tokenData: "-}~R!TOX$bXZ%SZ^%w^p$bpq%Sq|$b|}'p}!O(^!O!Q$b!Q!R)a!R![*a![!b$b!b!c+c!c!d+n!d!e)a!e!f+n!f!g+n!g!h)a!h!i+n!i!r)a!r!s+n!s!t)a!t!u+n!u!v+n!v!w+n!w!z)a!z!{+n!{!})a!}#T$b#T#o)a#p#q-s#q#r-x#r#y$b#y#z%w#z$f$b$f$g%w$g#BY$b#BY#BZ%w#BZ$IS$b$IS$I_%w$I_$I|$b$I|$JO%w$JO$JT$b$JT$JU%w$JU$KV$b$KV$KW%w$KW&FU$b&FU&FV%w&FV;'S$b;'S;=`$|<%lO$bP$gVWPOX$bZp$bq!b$b!c#o$b#r;'S$b;'S;=`$|<%lO$bP%PP;=`<%l$b~%XY]~X^%Spq%S#y#z%S$f$g%S#BY#BZ%S$IS$I_%S$I|$JO%S$JT$JU%S$KV$KW%S&FU&FV%S~&OjWP]~OX$bXZ%SZ^%w^p$bpq%Sq!b$b!c#o$b#r#y$b#y#z%w#z$f$b$f$g%w$g#BY$b#BY#BZ%w#BZ$IS$b$IS$I_%w$I_$I|$b$I|$JO%w$JO$JT$b$JT$JU%w$JU$KV$b$KV$KW%w$KW&FU$b&FU&FV%w&FV;'S$b;'S;=`$|<%lO$bR'wVdQWPOX$bZp$bq!b$b!c#o$b#r;'S$b;'S;=`$|<%lO$bV(e^SSWPOX$bZp$bq}$b}!O)a!O!Q$b!Q!R)a!R![*a![!b$b!c!})a!}#T$b#T#o)a#r;'S$b;'S;=`$|<%lO$bT)h]SSWPOX$bZp$bq}$b}!O)a!O!Q$b!Q![)a![!b$b!c!})a!}#T$b#T#o)a#r;'S$b;'S;=`$|<%lO$bV*j]SSUQWPOX$bZp$bq}$b}!O)a!O!Q$b!Q![*a![!b$b!c!})a!}#T$b#T#o)a#r;'S$b;'S;=`$|<%lO$b~+fP#o#p+i~+nOa~V+u^SSWPOX$bZp$bq}$b}!O)a!O!Q$b!Q!R)a!R![,q![!b$b!c!})a!}#T$b#T#o)a#r;'S$b;'S;=`$|<%lO$bV,z]RQSSWPOX$bZp$bq}$b}!O)a!O!Q$b!Q![,q![!b$b!c!})a!}#T$b#T#o)a#r;'S$b;'S;=`$|<%lO$b~-xOb~~-}O`~", tokenData: "-}~R!TOX$bXZ%SZ^%w^p$bpq%Sq|$b|}'p}!O(^!O!Q$b!Q!R)a!R![*a![!b$b!b!c+c!c!d+n!d!e)a!e!f+n!f!g+n!g!h)a!h!i+n!i!r)a!r!s+n!s!t)a!t!u+n!u!v+n!v!w+n!w!z)a!z!{+n!{!})a!}#T$b#T#o)a#p#q-s#q#r-x#r#y$b#y#z%w#z$f$b$f$g%w$g#BY$b#BY#BZ%w#BZ$IS$b$IS$I_%w$I_$I|$b$I|$JO%w$JO$JT$b$JT$JU%w$JU$KV$b$KV$KW%w$KW&FU$b&FU&FV%w&FV;'S$b;'S;=`$|<%lO$bP$gVfPOX$bZp$bq!b$b!c#o$b#r;'S$b;'S;=`$|<%lO$bP%PP;=`<%l$b~%XY]~X^%Spq%S#y#z%S$f$g%S#BY#BZ%S$IS$I_%S$I|$JO%S$JT$JU%S$KV$KW%S&FU&FV%S~&OjfP]~OX$bXZ%SZ^%w^p$bpq%Sq!b$b!c#o$b#r#y$b#y#z%w#z$f$b$f$g%w$g#BY$b#BY#BZ%w#BZ$IS$b$IS$I_%w$I_$I|$b$I|$JO%w$JO$JT$b$JT$JU%w$JU$KV$b$KV$KW%w$KW&FU$b&FU&FV%w&FV;'S$b;'S;=`$|<%lO$bR'wVeQfPOX$bZp$bq!b$b!c#o$b#r;'S$b;'S;=`$|<%lO$bV(e^dSfPOX$bZp$bq}$b}!O)a!O!Q$b!Q!R)a!R![*a![!b$b!c!})a!}#T$b#T#o)a#r;'S$b;'S;=`$|<%lO$bT)h]dSfPOX$bZp$bq}$b}!O)a!O!Q$b!Q![)a![!b$b!c!})a!}#T$b#T#o)a#r;'S$b;'S;=`$|<%lO$bV*j]UQdSfPOX$bZp$bq}$b}!O)a!O!Q$b!Q![*a![!b$b!c!})a!}#T$b#T#o)a#r;'S$b;'S;=`$|<%lO$b~+fP#o#p+i~+nOa~V+u^dSfPOX$bZp$bq}$b}!O)a!O!Q$b!Q!R)a!R![,q![!b$b!c!})a!}#T$b#T#o)a#r;'S$b;'S;=`$|<%lO$bV,z]RQdSfPOX$bZp$bq}$b}!O)a!O!Q$b!Q![,q![!b$b!c!})a!}#T$b#T#o)a#r;'S$b;'S;=`$|<%lO$b~-xOb~~-}O`~",
tokenizers: [0, 1, 2], tokenizers: [0, 1, 2],
topRules: {"Text":[0,1]}, topRules: {"Text":[0,1]},
tokenPrec: 69 tokenPrec: 80
}) })

View File

@ -1,4 +1,5 @@
@precedence { @precedence {
text @right
p1 p1
p2 p2
} }
@ -13,16 +14,22 @@
Offset { $[-]?$[1-9]$[0-9]* } Offset { $[-]?$[1-9]$[0-9]* }
Global { $[XCSDATFPR]$[1-9]$[0-9]* } Global { $[XCSDATFPR]$[1-9]$[0-9]* }
Word { ![@{|} \t\n]+ } word { ![@{|} \t\n]+ }
Gram { $[-a-zA-Z0-9]+ } gram { $[-a-zA-Z0-9]+ }
@precedence { Word, space } @precedence { word, space }
} }
textItem { textItem {
!p1 ref | !p1 ref |
!p2 Word !p2 Filler
}
Filler { word_enum }
word_enum {
word |
word !text word_enum
} }
ref { ref {
@ -31,19 +38,18 @@ ref {
} }
RefEntity { RefEntity {
"@{" Global "|" grams "}" "@{" Global "|" Grams "}"
}
Grams { gram_enum }
gram_enum {
gram |
gram "," gram_enum
} }
RefSyntactic { RefSyntactic {
"@{" Offset "|" Nominal "}" "@{" Offset "|" Nominal "}"
} }
Nominal { word+ }
Nominal { Word+ }
grams {
Gram |
Gram "," grams
}
@detectDelim @detectDelim

View File

@ -1,63 +1,43 @@
import { syntaxTree } from "@codemirror/language"
import { Extension } from '@codemirror/state'; import { Extension } from '@codemirror/state';
import { hoverTooltip } from '@codemirror/view'; import { hoverTooltip } from '@codemirror/view';
import { parseEntityReference, parseSyntacticReference } from '../../models/language';
import { IConstituenta } from '../../models/rsform'; import { IConstituenta } from '../../models/rsform';
import { labelCstTypification } from '../../utils/labels'; import { domTooltipEntityReference, domTooltipSyntacticReference, findEnvelopingNodes } from '../../utils/codemirror';
import { IColorTheme } from '../../utils/color';
import { RefEntity, RefSyntactic } from './parse/parser.terms';
function createTooltipFor(cst: IConstituenta) { export const globalsHoverTooltip = (items: IConstituenta[], colors: IColorTheme) => {
const dom = document.createElement('div'); return hoverTooltip((view, pos) => {
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 nodes = findEnvelopingNodes(pos, pos, syntaxTree(view.state), [RefEntity, RefSyntactic]);
const alias = document.createElement('p'); if (nodes.length !== 1) {
alias.innerHTML = `<b>${cst.alias}:</b> ${labelCstTypification(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_resolved) {
const definition = document.createElement('p');
definition.innerHTML = `<b>Определение:</b> ${cst.definition_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; return null;
} }
const alias = text.slice(start - from, end - from); const start = nodes[0].from;
const cst = items.find(cst => cst.alias === alias); const end = nodes[0].to;
if (!cst) { const text = view.state.doc.sliceString(start, end);
return null; if (nodes[0].type.id === RefEntity) {
} const ref = parseEntityReference(text);
return { const cst = items.find(cst => cst.alias === ref.entity);
pos: start, return {
end: end, pos: start,
above: false, end: end,
create: () => createTooltipFor(cst) above: false,
create: () => domTooltipEntityReference(ref, cst, colors)
}
} else {
const ref = parseSyntacticReference(text);
return {
pos: start,
end: end,
above: false,
create: () => domTooltipSyntacticReference(ref)
}
} }
}); });
} }
export function rshoverTooltip(items: IConstituenta[]): Extension { export function refsHoverTooltip(items: IConstituenta[], colors: IColorTheme): Extension {
return [getHoverTooltip(items)]; return [globalsHoverTooltip(items, colors)];
} }

View File

@ -1,4 +1,4 @@
import { Grammeme, parseGrammemes } from './language'; import { Grammeme, parseEntityReference, parseGrammemes, parseSyntacticReference } from './language';
describe('Testing grammeme parsing', () => { describe('Testing grammeme parsing', () => {
@ -11,10 +11,28 @@ describe('Testing grammeme parsing', () => {
test('regular grammemes', test('regular grammemes',
() => { () => {
expect(parseGrammemes('NOUN')).toStrictEqual([{type: Grammeme.NOUN, data: 'NOUN'}]); expect(parseGrammemes('NOUN')).toStrictEqual([Grammeme.NOUN]);
expect(parseGrammemes('sing,nomn')).toStrictEqual([ expect(parseGrammemes('sing,nomn')).toStrictEqual([Grammeme.sing, Grammeme.nomn]);
{type: Grammeme.sing, data: 'sing'}, expect(parseGrammemes('nomn,sing')).toStrictEqual([Grammeme.sing, Grammeme.nomn]);
{type: Grammeme.nomn, data: 'nomn'} expect(parseGrammemes('nomn,invalid,sing')).toStrictEqual([Grammeme.sing, Grammeme.nomn, 'invalid']);
]); expect(parseGrammemes('invalid,test')).toStrictEqual(['invalid', 'test']);
});
});
describe('Testing reference parsing', () => {
test('entity reference',
() => {
expect(parseEntityReference('@{ X1 | NOUN,sing }')).toStrictEqual({entity: 'X1', form: 'NOUN,sing'});
expect(parseEntityReference('@{X1|NOUN,sing}')).toStrictEqual({entity: 'X1', form: 'NOUN,sing'});
expect(parseEntityReference('@{X111|NOUN,sing}')).toStrictEqual({entity: 'X111', form: 'NOUN,sing'});
});
test('syntactic reference',
() => {
expect(parseSyntacticReference('@{1|test test}')).toStrictEqual({offset: 1, nominal: 'test test'});
expect(parseSyntacticReference('@{101|test test}')).toStrictEqual({offset: 101, nominal: 'test test'});
expect(parseSyntacticReference('@{-1|test test}')).toStrictEqual({offset: -1, nominal: 'test test'});
expect(parseSyntacticReference('@{-99|test test}')).toStrictEqual({offset: -99, nominal: 'test test'});
}); });
}); });

View File

@ -1,4 +1,6 @@
// Module: Natural language model declarations. /**
* Module: Natural language model declarations.
*/
/** /**
* Represents API result for text output. * Represents API result for text output.
@ -11,9 +13,6 @@ export interface ITextResult {
* Represents single unit of language Morphology. * Represents single unit of language Morphology.
*/ */
export enum Grammeme { export enum Grammeme {
// Неизвестная граммема
UNKN = 'UNKN',
// Части речи // Части речи
NOUN = 'NOUN', ADJF = 'ADJF', ADJS = 'ADJS', COMP = 'COMP', NOUN = 'NOUN', ADJF = 'ADJF', ADJS = 'ADJS', COMP = 'COMP',
VERB = 'VERB', INFN = 'INFN', PRTF = 'PRTF', PRTS = 'PRTS', VERB = 'VERB', INFN = 'INFN', PRTF = 'PRTF', PRTS = 'PRTS',
@ -204,17 +203,14 @@ export const VerbGrams = [
/** /**
* Represents {@link Grammeme} parse data. * Represents {@link Grammeme} parse data.
*/ */
export interface IGramData { export type GramData = Grammeme | string;
type: Grammeme
data: string
}
/** /**
* Represents specific wordform attached to {@link Grammeme}s. * Represents specific wordform attached to {@link Grammeme}s.
*/ */
export interface IWordForm { export interface IWordForm {
text: string text: string
grams: IGramData[] grams: GramData[]
} }
/** /**
@ -232,16 +228,6 @@ export interface ILexemeData {
items: IWordFormPlain[] items: IWordFormPlain[]
} }
/**
* Equality comparator for {@link IGramData}. Compares text data for unknown grammemes
*/
export function matchGrammeme(left: IGramData, right: IGramData): boolean {
if (left.type !== right.type) {
return false;
}
return left.type !== Grammeme.UNKN || left.data === right.data;
}
/** /**
* Equality comparator for {@link IWordForm}. Compares a set of Grammemes attached to wordforms * Equality comparator for {@link IWordForm}. Compares a set of Grammemes attached to wordforms
*/ */
@ -250,41 +236,43 @@ export function matchWordForm(left: IWordForm, right: IWordForm): boolean {
return false; return false;
} }
for (let index = 0; index < left.grams.length; ++index) { for (let index = 0; index < left.grams.length; ++index) {
if (!matchGrammeme(left.grams[index], right.grams[index])) { if (left.grams[index] !== right.grams[index]) {
return false; return false;
} }
} }
return true; return true;
} }
function parseSingleGrammeme(text: string): IGramData { function parseSingleGrammeme(text: string): GramData {
if (Object.values(Grammeme).includes(text as Grammeme)) { if (Object.values(Grammeme).includes(text as Grammeme)) {
return { return text as Grammeme;
data: text,
type: text as Grammeme
}
} else { } else {
return { return text;
data: text,
type: Grammeme.UNKN
}
} }
} }
export function sortGrammemes<TData extends IGramData>(input: TData[]): TData[] { /**
const result: TData[] = []; * Compares {@link GramData} based on Grammeme enum and alpha order for strings.
Object.values(Grammeme).forEach( */
gram => { export function compareGrammemes(left: GramData, right: GramData): number {
const item = input.find(data => data.type === gram); const indexLeft = Object.values(Grammeme).findIndex(gram => gram === left as Grammeme);
if (item) { const indexRight = Object.values(Grammeme).findIndex(gram => gram === right as Grammeme);
result.push(item); if (indexLeft === -1 && indexRight === -1) {
} return left.localeCompare(right);
}); } else if (indexLeft === -1 && indexRight !== -1) {
return result; return 1;
} else if (indexLeft !== -1 && indexRight === -1) {
return -1;
} else {
return indexLeft - indexRight;
}
} }
export function parseGrammemes(termForm: string): IGramData[] { /**
const result: IGramData[] = []; * Transforms {@link Grammeme} enumeration to {@link GramData}.
*/
export function parseGrammemes(termForm: string): GramData[] {
const result: GramData[] = [];
const chunks = termForm.split(','); const chunks = termForm.split(',');
chunks.forEach(chunk => { chunks.forEach(chunk => {
chunk = chunk.trim(); chunk = chunk.trim();
@ -292,7 +280,7 @@ export function parseGrammemes(termForm: string): IGramData[] {
result.push(parseSingleGrammeme(chunk)); result.push(parseSingleGrammeme(chunk));
} }
}); });
return sortGrammemes(result); return result.sort(compareGrammemes);
} }
// ====== Reference resolution ===== // ====== Reference resolution =====
@ -353,3 +341,29 @@ export interface IResolutionData {
output: string output: string
refs: IResolvedReference[] refs: IResolvedReference[]
} }
/**
* Extracts {@link IEntityReference} from string representation.
*
* @param text - Reference text in a valid pattern. Must fit format '\@\{GLOBAL_ID|GRAMMEMES\}'
*/
export function parseEntityReference(text: string): IEntityReference {
const blocks = text.slice(2, text.length - 1).split('|');
return {
entity: blocks[0].trim(),
form: blocks[1].trim()
}
}
/**
* Extracts {@link ISyntacticReference} from string representation.
*
* @param text - Reference text in a valid pattern. Must fit format '\@\{OFFSET|NOMINAL_FORM\}'
*/
export function parseSyntacticReference(text: string): ISyntacticReference {
const blocks = text.slice(2, text.length - 1).split('|');
return {
offset: Number(blocks[0].trim()),
nominal: blocks[1].trim()
}
}

View File

@ -10,15 +10,13 @@ import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, ChevronDoubleDownIcon, CrossI
import { useConceptTheme } from '../../context/ThemeContext'; import { useConceptTheme } from '../../context/ThemeContext';
import useConceptText from '../../hooks/useConceptText'; import useConceptText from '../../hooks/useConceptText';
import { import {
Grammeme, GrammemeGroups, ITextRequest, IWordForm, GramData, Grammeme, GrammemeGroups, ITextRequest, IWordForm,
IWordFormPlain, IWordFormPlain, matchWordForm, NounGrams, parseGrammemes, VerbGrams
matchWordForm, NounGrams, parseGrammemes,
sortGrammemes, VerbGrams
} from '../../models/language'; } from '../../models/language';
import { IConstituenta, TermForm } from '../../models/rsform'; import { IConstituenta, TermForm } from '../../models/rsform';
import { colorfgGrammeme } from '../../utils/color'; import { colorfgGrammeme } from '../../utils/color';
import { labelGrammeme } from '../../utils/labels'; import { labelGrammeme } from '../../utils/labels';
import { IGrammemeOption, SelectorGrammemesList, SelectorGrammems } from '../../utils/selectors'; import { compareGrammemeOptions,IGrammemeOption, SelectorGrammemesList, SelectorGrammems } from '../../utils/selectors';
interface DlgEditTermProps { interface DlgEditTermProps {
hideWindow: () => void hideWindow: () => void
@ -44,7 +42,7 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
forms.forEach( forms.forEach(
({text, grams}) => result.push({ ({text, grams}) => result.push({
text: text, text: text,
tags: grams.map(gram => gram.data).join(',') tags: grams.join(',')
})); }));
return result; return result;
} }
@ -67,32 +65,32 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
// Filter grammemes when input changes // Filter grammemes when input changes
useEffect( useEffect(
() => { () => {
let newFilter: Grammeme[] = []; let newFilter: GramData[] = [];
inputGrams.forEach(({type: gram}) => { inputGrams.forEach(({value: gram}) => {
if (!newFilter.includes(gram)) { if (!newFilter.includes(gram)) {
if (NounGrams.includes(gram)) { if (NounGrams.includes(gram as Grammeme)) {
newFilter.push(...NounGrams); newFilter.push(...NounGrams);
} }
if (VerbGrams.includes(gram)) { if (VerbGrams.includes(gram as Grammeme)) {
newFilter.push(...VerbGrams); newFilter.push(...VerbGrams);
} }
} }
}); });
inputGrams.forEach(({type: gram}) => inputGrams.forEach(({value: gram}) =>
GrammemeGroups.forEach(group => { GrammemeGroups.forEach(group => {
if (group.includes(gram)) { if (group.includes(gram as Grammeme)) {
newFilter = newFilter.filter(item => !group.includes(item) || item === gram); newFilter = newFilter.filter(item => !group.includes(item as Grammeme) || item === gram);
} }
})); }));
newFilter.push(...inputGrams.map(({type: gram}) => gram)); newFilter.push(...inputGrams.map(({value}) => value));
if (newFilter.length === 0) { if (newFilter.length === 0) {
newFilter = [...VerbGrams, ...NounGrams]; newFilter = [...VerbGrams, ...NounGrams];
} }
newFilter = [... new Set(newFilter)]; newFilter = [... new Set(newFilter)];
setOptions(SelectorGrammems.filter(({type: gram}) => newFilter.includes(gram))); setOptions(SelectorGrammems.filter(({value}) => newFilter.includes(value)));
}, [inputGrams]); }, [inputGrams]);
const handleSubmit = () => onSave(getData()); const handleSubmit = () => onSave(getData());
@ -100,10 +98,7 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
function handleAddForm() { function handleAddForm() {
const newForm: IWordForm = { const newForm: IWordForm = {
text: inputText, text: inputText,
grams: inputGrams.map(item => ({ grams: inputGrams.map(item => item.value)
type: item.type,
data: item.data
}))
}; };
setForms(forms => [ setForms(forms => [
newForm, newForm,
@ -127,7 +122,7 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
function handleRowClicked(form: IWordForm) { function handleRowClicked(form: IWordForm) {
setInputText(form.text); setInputText(form.text);
setInputGrams(SelectorGrammems.filter(gram => form.grams.find(test => test.type === gram.type))); setInputGrams(SelectorGrammems.filter(gram => form.grams.find(test => test === gram.value)));
} }
function handleResetForm() { function handleResetForm() {
@ -138,7 +133,7 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
function handleInflect() { function handleInflect() {
const data: IWordFormPlain = { const data: IWordFormPlain = {
text: term, text: term,
grams: inputGrams.map(gram => gram.data).join(',') grams: inputGrams.map(gram => gram.value).join(',')
} }
textProcessor.inflect(data, response => setInputText(response.result)); textProcessor.inflect(data, response => setInputText(response.result));
} }
@ -149,7 +144,7 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
} }
textProcessor.parse(data, response => { textProcessor.parse(data, response => {
const grams = parseGrammemes(response.result); const grams = parseGrammemes(response.result);
setInputGrams(SelectorGrammems.filter(gram => grams.find(test => test.type === gram.type))); setInputGrams(SelectorGrammems.filter(gram => grams.find(test => test === gram.value)));
}); });
} }
@ -166,7 +161,7 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
const newForms: IWordForm[] = response.items.map( const newForms: IWordForm[] = response.items.map(
form => ({ form => ({
text: form.text, text: form.text,
grams: parseGrammemes(form.grams).filter(gram => SelectorGrammemesList.find(item => item === gram.type)) grams: parseGrammemes(form.grams).filter(gram => SelectorGrammemesList.find(item => item === gram as Grammeme))
})); }));
setForms(forms => [ setForms(forms => [
...newForms, ...newForms,
@ -196,13 +191,13 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
{ props.getValue().map( { props.getValue().map(
gram => gram =>
<div <div
key={`${props.cell.id}-${gram.type}`} key={`${props.cell.id}-${gram}`}
className='min-w-[3rem] px-1 text-sm text-center rounded-md whitespace-nowrap' className='min-w-[3rem] px-1 text-sm text-center rounded-md whitespace-nowrap'
title='' title=''
style={{ style={{
borderWidth: '1px', borderWidth: '1px',
borderColor: colorfgGrammeme(gram.type, colors), borderColor: colorfgGrammeme(gram, colors),
color: colorfgGrammeme(gram.type, colors), color: colorfgGrammeme(gram, colors),
fontWeight: 600, fontWeight: 600,
backgroundColor: colors.bgInput backgroundColor: colors.bgInput
}} }}
@ -260,7 +255,7 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
value={inputText} value={inputText}
onChange={event => setInputText(event.target.value)} onChange={event => setInputText(event.target.value)}
/> />
<div className='flex items-center justify-between'> <div className='flex items-center justify-between select-none'>
<div className='flex items-center justify-start'> <div className='flex items-center justify-start'>
<MiniButton <MiniButton
tooltip='Добавить словоформу' tooltip='Добавить словоформу'
@ -281,6 +276,9 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
onClick={handleGenerateLexeme} onClick={handleGenerateLexeme}
/> />
</div> </div>
<div className='text-sm'>
Словоформ: {forms.length}
</div>
<div className='flex items-center justify-start'> <div className='flex items-center justify-start'>
<MiniButton <MiniButton
tooltip='Генерировать словоформу' tooltip='Генерировать словоформу'
@ -304,7 +302,7 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
value={inputGrams} value={inputGrams}
isDisabled={textProcessor.loading} isDisabled={textProcessor.loading}
onChange={newValue => setInputGrams(sortGrammemes([...newValue]))} onChange={newValue => setInputGrams([...newValue].sort(compareGrammemeOptions))}
/> />
</div> </div>

View File

@ -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 = `<b>${cst.alias}:</b> ${labelCstTypification(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_resolved) {
const definition = document.createElement('p');
definition.innerHTML = `<b>Определение:</b> ${cst.definition_resolved}`;
dom.appendChild(definition);
}
if (cst.convention) {
const convention = document.createElement('p');
convention.innerHTML = `<b>Конвенция:</b> ${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 = `<b>${ref.entity}:</b> ${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 = '<b>Синтаксическая ссылка</b>';
dom.appendChild(title);
const offset = document.createElement('p');
offset.innerHTML = `<b>Смещение:</b> ${ref.offset}`;
dom.appendChild(offset);
const nominal = document.createElement('p');
nominal.innerHTML = `<b>Начальная форма:</b> ${ref.nominal}`;
dom.appendChild(nominal);
return { dom: dom };
}

View File

@ -1,6 +1,6 @@
// =========== Modules contains all dynamic color definitions ========== // =========== 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 { CstClass, ExpressionStatus } from '../models/rsform'
import { ISyntaxTreeNode, TokenID } from '../models/rslang' 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 { export function colorfgGrammeme(gram: GramData, colors: IColorTheme): string {
if (PartOfSpeech.includes(gram)) { if (PartOfSpeech.includes(gram as Grammeme)) {
return colors.fgBlue; return colors.fgBlue;
} }
if (NounGrams.includes(gram)) { if (NounGrams.includes(gram as Grammeme)) {
return colors.fgGreen; return colors.fgGreen;
} }
if (VerbGrams.includes(gram)) { if (VerbGrams.includes(gram as Grammeme)) {
return colors.fgTeal; return colors.fgTeal;
} }
if (gram === Grammeme.UNKN) { if (!Object.values(Grammeme).includes(gram as Grammeme)) {
return colors.fgRed; return colors.fgRed;
} else {
return colors.fgPurple;
} }
return colors.fgPurple;
} }
export function colorbgGrammeme(gram: Grammeme, colors: IColorTheme): string { export function colorbgGrammeme(gram: GramData, colors: IColorTheme): string {
if (PartOfSpeech.includes(gram)) { if (PartOfSpeech.includes(gram as Grammeme)) {
return colors.bgBlue; return colors.bgBlue;
} }
if (NounGrams.includes(gram)) { if (NounGrams.includes(gram as Grammeme)) {
return colors.bgGreen; return colors.bgGreen;
} }
if (VerbGrams.includes(gram)) { if (VerbGrams.includes(gram as Grammeme)) {
return colors.bgTeal; return colors.bgTeal;
} }
return colors.bgInput; return colors.bgInput;

View File

@ -1,6 +1,6 @@
// =========== Modules contains all text descriptors ========== // =========== 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 { CstMatchMode, DependencyMode, HelpTopic } from '../models/miscelanious';
import { CstClass, CstType, ExpressionStatus, IConstituenta } from '../models/rsform'; import { CstClass, CstType, ExpressionStatus, IConstituenta } from '../models/rsform';
import { IFunctionArg, IRSErrorDescription, ISyntaxTreeNode, ParsingStatus, RSErrorType, TokenID } from '../models/rslang'; 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) { export function labelConstituenta(cst: IConstituenta) {
return `${cst.alias}: ${describeConstituenta(cst)}`; return `${cst.alias}: ${describeConstituenta(cst)}`;
} }
@ -346,8 +357,10 @@ export function labelSyntaxTree(node: ISyntaxTreeNode): string {
return 'UNKNOWN ' + String(node.typeID); return 'UNKNOWN ' + String(node.typeID);
} }
export function labelGrammeme(gram: IGramData): string { export function labelGrammeme(gram: GramData): string {
switch (gram.type) { switch (gram) {
default: return `Неизв: ${gram}`;
case Grammeme.NOUN: return 'ЧР: сущ'; case Grammeme.NOUN: return 'ЧР: сущ';
case Grammeme.VERB: return 'ЧР: глагол'; case Grammeme.VERB: return 'ЧР: глагол';
case Grammeme.INFN: return 'ЧР: глагол инф'; case Grammeme.INFN: return 'ЧР: глагол инф';
@ -411,8 +424,6 @@ export function labelGrammeme(gram: IGramData): string {
case Grammeme.Slng: return 'Стиль: жаргон'; case Grammeme.Slng: return 'Стиль: жаргон';
case Grammeme.Arch: return 'Стиль: устаревший'; case Grammeme.Arch: return 'Стиль: устаревший';
case Grammeme.Litr: return 'Стиль: литературный'; case Grammeme.Litr: return 'Стиль: литературный';
case Grammeme.UNKN: return `Неизв: ${gram.data}`;
} }
} }

View File

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

View File

@ -1,13 +1,15 @@
// Module: Selector maps // Module: Selector maps
import { LayoutTypes } from 'reagraph'; import { LayoutTypes } from 'reagraph';
import { Grammeme, IGramData } from '../models/language'; import { compareGrammemes,type GramData, Grammeme } from '../models/language';
import { CstType } from '../models/rsform'; import { CstType } from '../models/rsform';
import { ColoringScheme } from '../pages/RSFormPage/EditorTermGraph'; import { ColoringScheme } from '../pages/RSFormPage/EditorTermGraph';
import { labelGrammeme } from './labels'; import { labelGrammeme } from './labels';
import { labelCstType } from './labels'; import { labelCstType } from './labels';
/**
* Represents options for GraphLayout selector.
*/
export const SelectorGraphLayout: { value: LayoutTypes, label: string }[] = [ export const SelectorGraphLayout: { value: LayoutTypes, label: string }[] = [
{ value: 'treeTd2d', label: 'Граф: ДеревоВ 2D' }, { value: 'treeTd2d', label: 'Граф: ДеревоВ 2D' },
{ value: 'treeTd3d', label: 'Граф: ДеревоВ 3D' }, { value: 'treeTd3d', label: 'Граф: ДеревоВ 3D' },
@ -24,12 +26,18 @@ export const SelectorGraphLayout: { value: LayoutTypes, label: string }[] = [
// { value: 'hierarchicalLr', label: 'hierarchicalLr'} // { value: 'hierarchicalLr', label: 'hierarchicalLr'}
]; ];
/**
* Represents options for {@link ColoringScheme} selector.
*/
export const SelectorGraphColoring: { value: ColoringScheme, label: string }[] = [ export const SelectorGraphColoring: { value: ColoringScheme, label: string }[] = [
{ value: 'none', label: 'Цвет: моно' }, { value: 'none', label: 'Цвет: моно' },
{ value: 'status', label: 'Цвет: статус' }, { value: 'status', label: 'Цвет: статус' },
{ value: 'type', label: 'Цвет: класс' }, { value: 'type', label: 'Цвет: класс' },
]; ];
/**
* Represents options for {@link CstType} selector.
*/
export const SelectorCstType = ( export const SelectorCstType = (
Object.values(CstType)).map( Object.values(CstType)).map(
typeStr => ({ 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 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 = [ export const SelectorGrammemesList = [
Grammeme.NOUN, Grammeme.VERB, Grammeme.NOUN, Grammeme.VERB,
@ -62,11 +83,12 @@ export const SelectorGrammemesList = [
Grammeme.pssv, Grammeme.actv, Grammeme.pssv, Grammeme.actv,
]; ];
/**
* Represents options for {@link Grammeme} selector.
*/
export const SelectorGrammems: IGrammemeOption[] = export const SelectorGrammems: IGrammemeOption[] =
SelectorGrammemesList.map( SelectorGrammemesList.map(
gram => ({ gram => ({
type: gram, value: gram,
data: gram as string, label: labelGrammeme(gram)
value: gram as string,
label: labelGrammeme({type: gram, data: ''} as IGramData)
})); }));