diff --git a/packages/parser/src/language/common/common.langium b/packages/parser/src/language/common/common.langium index 6a2af29e4..f1a695e49 100644 --- a/packages/parser/src/language/common/common.langium +++ b/packages/parser/src/language/common/common.langium @@ -1,14 +1,19 @@ +interface Common { + accDescr?: string; + accTitle?: string; + title?: string; +} + fragment TitleAndAccessibilities: ((accDescr=ACC_DESCR | accTitle=ACC_TITLE | title=TITLE) NEWLINE+)+ ; terminal NEWLINE: /\r?\n/; -terminal ACC_DESCR: /accDescr(?:[\t ]*:[\t ]*[^\n\r]*?(?=%%)|\s*{[^}]*})|accDescr(?:[\t ]*:[\t ]*[^\n\r]*|\s*{[^}]*})/; -terminal ACC_TITLE: /accTitle[\t ]*:[\t ]*[^\n\r]*?(?=%%)|accTitle[\t ]*:[\t ]*[^\n\r]*/; -terminal TITLE: /title(?:[\t ]+[^\n\r]*?|)(?=%%)|title(?:[\t ]+[^\n\r]*|)/; +terminal ACC_DESCR: /accDescr([\t ]*:[^\n\r]*(?=%%)|\s*{[^}]*})|accDescr([\t ]*:[^\n\r]*|\s*{[^}]*})/; +terminal ACC_TITLE: /accTitle[\t ]*:[^\n\r]*(?=%%)|accTitle[\t ]*:[^\n\r]*/; +terminal TITLE: /title([\t ][^\n\r]*|)(?=%%)|title([\t ][^\n\r]*|)/; hidden terminal WHITESPACE: /[\t ]+/; -// TODO: add YAML_COMMENT hidden rule without interfere actual grammar hidden terminal YAML: /---[\t ]*\r?\n[\S\s]*?---[\t ]*(?!.)/; hidden terminal DIRECTIVE: /[\t ]*%%{[\S\s]*?}%%\s*/; hidden terminal SINGLE_LINE_COMMENT: /[\t ]*%%[^\n\r]*/; diff --git a/packages/parser/src/language/common/commonValueConverters.ts b/packages/parser/src/language/common/commonValueConverters.ts deleted file mode 100644 index 228746c8a..000000000 --- a/packages/parser/src/language/common/commonValueConverters.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { CstNode, GrammarAST, ValueType } from 'langium'; -import { DefaultValueConverter } from 'langium'; - -import { accessibilityDescrRegex, accessibilityTitleRegex, titleRegex } from './commonMatcher.js'; - -export class CommonValueConverter extends DefaultValueConverter { - protected override runConverter( - rule: GrammarAST.AbstractRule, - input: string, - cstNode: CstNode - ): ValueType { - const value: ValueType | undefined = CommonValueConverter.customRunConverter( - rule, - input, - cstNode - ); - if (value === undefined) { - return super.runConverter(rule, input, cstNode); - } else { - return value; - } - } - - /** - * A method contains convert logic to be used by class itself or `MermaidValueConverter`. - * - * @param rule - Parsed rule. - * @param input - Matched string. - * @param _cstNode - Node in the Concrete Syntax Tree (CST). - * @returns converted the value if it's common rule or `undefined` if it's not. - */ - public static customRunConverter( - rule: GrammarAST.AbstractRule, - input: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _cstNode: CstNode - ): ValueType | undefined { - let regex: RegExp | undefined; - switch (rule.name) { - case 'ACC_DESCR': { - regex = new RegExp(accessibilityDescrRegex.source); - break; - } - case 'ACC_TITLE': { - regex = new RegExp(accessibilityTitleRegex.source); - break; - } - case 'TITLE': { - regex = new RegExp(titleRegex.source); - break; - } - } - if (regex === undefined) { - return undefined; - } - const match = regex.exec(input); - if (match === null) { - return undefined; - } - // single line title, accTitle, accDescr - if (match[1] !== undefined) { - return match[1].trim().replaceAll(/[\t ]{2,}/gm, ' '); - } - // multi line accDescr - if (match[2] !== undefined) { - return match[2] - .replaceAll(/^\s*/gm, '') - .replaceAll(/\s+$/gm, '') - .replaceAll(/[\t ]{2,}/gm, ' ') - .replaceAll(/[\n\r]{2,}/gm, '\n'); - } - return undefined; - } -} diff --git a/packages/parser/src/language/common/index.ts b/packages/parser/src/language/common/index.ts index 554e7902c..2ab5ebcd4 100644 --- a/packages/parser/src/language/common/index.ts +++ b/packages/parser/src/language/common/index.ts @@ -1,2 +1,3 @@ -export * from './commonLexer.js'; -export * from './commonValueConverters.js'; +export * from './lexer.js'; +export * from './tokenBuilder.js'; +export { MermaidValueConverter } from './valueConverter.js'; diff --git a/packages/parser/src/language/common/commonLexer.ts b/packages/parser/src/language/common/lexer.ts similarity index 100% rename from packages/parser/src/language/common/commonLexer.ts rename to packages/parser/src/language/common/lexer.ts diff --git a/packages/parser/src/language/common/commonMatcher.ts b/packages/parser/src/language/common/matcher.ts similarity index 100% rename from packages/parser/src/language/common/commonMatcher.ts rename to packages/parser/src/language/common/matcher.ts diff --git a/packages/parser/src/language/info/infoTokenBuilder.ts b/packages/parser/src/language/common/tokenBuilder.ts similarity index 70% rename from packages/parser/src/language/info/infoTokenBuilder.ts rename to packages/parser/src/language/common/tokenBuilder.ts index b71fa30ad..5a94d5d59 100644 --- a/packages/parser/src/language/info/infoTokenBuilder.ts +++ b/packages/parser/src/language/common/tokenBuilder.ts @@ -1,9 +1,16 @@ import type { GrammarAST, Stream, TokenBuilderOptions } from 'langium'; -import { DefaultTokenBuilder } from 'langium'; - import type { TokenType } from '../chevrotainWrapper.js'; -export class InfoTokenBuilder extends DefaultTokenBuilder { +import { DefaultTokenBuilder } from 'langium'; + +export abstract class MermaidTokenBuilder extends DefaultTokenBuilder { + private keywords: Set; + + public constructor(keywords: string[]) { + super(); + this.keywords = new Set(keywords); + } + protected override buildKeywordTokens( rules: Stream, terminalTokens: TokenType[], @@ -12,10 +19,7 @@ export class InfoTokenBuilder extends DefaultTokenBuilder { const tokenTypes: TokenType[] = super.buildKeywordTokens(rules, terminalTokens, options); // to restrict users, they mustn't have any non-whitespace characters after the keyword. tokenTypes.forEach((tokenType: TokenType): void => { - if ( - (tokenType.name === 'info' || tokenType.name === 'showInfo') && - tokenType.PATTERN !== undefined - ) { + if (this.keywords.has(tokenType.name) && tokenType.PATTERN !== undefined) { tokenType.PATTERN = new RegExp(tokenType.PATTERN.toString() + '(?!\\S)'); } }); diff --git a/packages/parser/src/language/common/valueConverter.ts b/packages/parser/src/language/common/valueConverter.ts new file mode 100644 index 000000000..bc31ba714 --- /dev/null +++ b/packages/parser/src/language/common/valueConverter.ts @@ -0,0 +1,82 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import type { CstNode, GrammarAST, ValueType } from 'langium'; +import { DefaultValueConverter } from 'langium'; + +import { accessibilityDescrRegex, accessibilityTitleRegex, titleRegex } from './matcher.js'; + +const rulesRegexes: Record = { + ACC_DESCR: accessibilityDescrRegex, + ACC_TITLE: accessibilityTitleRegex, + TITLE: titleRegex, +}; + +export abstract class MermaidValueConverter extends DefaultValueConverter { + /** + * A method contains convert logic to be used by class. + * + * @param rule - Parsed rule. + * @param input - Matched string. + * @param cstNode - Node in the Concrete Syntax Tree (CST). + * @returns converted the value if it's available or `undefined` if it's not. + */ + protected abstract runCustomConverter( + rule: GrammarAST.AbstractRule, + input: string, + cstNode: CstNode + ): ValueType | undefined; + + protected override runConverter( + rule: GrammarAST.AbstractRule, + input: string, + cstNode: CstNode + ): ValueType { + let value: ValueType | undefined = this.runCommonConverter(rule, input, cstNode); + + if (value === undefined) { + value = this.runCustomConverter(rule, input, cstNode); + } + if (value === undefined) { + return super.runConverter(rule, input, cstNode); + } + + return value; + } + + private runCommonConverter( + rule: GrammarAST.AbstractRule, + input: string, + _cstNode: CstNode + ): ValueType | undefined { + const regex: RegExp | undefined = rulesRegexes[rule.name]; + if (regex === undefined) { + return undefined; + } + const match = regex.exec(input); + if (match === null) { + return undefined; + } + // single line title, accTitle, accDescr + if (match[1] !== undefined) { + return match[1].trim().replace(/[\t ]{2,}/gm, ' '); + } + // multi line accDescr + if (match[2] !== undefined) { + return match[2] + .replace(/^\s*/gm, '') + .replace(/\s+$/gm, '') + .replace(/[\t ]{2,}/gm, ' ') + .replace(/[\n\r]{2,}/gm, '\n'); + } + return undefined; + } +} + +export class CommonValueConverter extends MermaidValueConverter { + protected runCustomConverter( + _rule: GrammarAST.AbstractRule, + _input: string, + _cstNode: CstNode + ): ValueType | undefined { + return undefined; + } +} diff --git a/packages/parser/src/language/info/index.ts b/packages/parser/src/language/info/index.ts index 197952c4d..fd3c604b0 100644 --- a/packages/parser/src/language/info/index.ts +++ b/packages/parser/src/language/info/index.ts @@ -1 +1 @@ -export * from './infoModule.js'; +export * from './module.js'; diff --git a/packages/parser/src/language/info/infoModule.ts b/packages/parser/src/language/info/module.ts similarity index 86% rename from packages/parser/src/language/info/infoModule.ts rename to packages/parser/src/language/info/module.ts index 96dfd5a84..5d6b74e10 100644 --- a/packages/parser/src/language/info/infoModule.ts +++ b/packages/parser/src/language/info/module.ts @@ -7,10 +7,10 @@ import type { } from 'langium'; import { EmptyFileSystem, createDefaultModule, createDefaultSharedModule, inject } from 'langium'; -import { MermaidGeneratedSharedModule, InfoGeneratedModule } from '../generated/module.js'; -import { CommonLexer } from '../common/commonLexer.js'; -import { CommonValueConverter } from '../common/commonValueConverters.js'; -import { InfoTokenBuilder } from './infoTokenBuilder.js'; +import { CommonLexer } from '../common/lexer.js'; +import { CommonValueConverter } from '../common/valueConverter.js'; +import { InfoGeneratedModule, MermaidGeneratedSharedModule } from '../generated/module.js'; +import { InfoTokenBuilder } from './tokenBuilder.js'; /** * Declaration of `Info` services. @@ -34,7 +34,7 @@ export type InfoServices = LangiumServices & InfoAddedServices; */ export const InfoModule: Module = { parser: { - Lexer: (services) => new CommonLexer(services), + Lexer: (services: InfoServices) => new CommonLexer(services), TokenBuilder: () => new InfoTokenBuilder(), ValueConverter: () => new CommonValueConverter(), }, diff --git a/packages/parser/src/language/info/tokenBuilder.ts b/packages/parser/src/language/info/tokenBuilder.ts new file mode 100644 index 000000000..311d372e4 --- /dev/null +++ b/packages/parser/src/language/info/tokenBuilder.ts @@ -0,0 +1,7 @@ +import { MermaidTokenBuilder } from '../common/index.js'; + +export class InfoTokenBuilder extends MermaidTokenBuilder { + public constructor() { + super(['info', 'showInfo']); + } +}