diff --git a/demos/architecture.html b/demos/architecture.html index bcfb243d3..ad588042b 100644 --- a/demos/architecture.html +++ b/demos/architecture.html @@ -69,7 +69,7 @@
architecture service db(database)[Database] - service s3(storage)[Storage] + service s3(disk)[Storage] service serv1(server)[Server 1] service serv2(server)[Server 2] service disk(disk)[Disk] diff --git a/packages/mermaid/src/diagrams/architecture/architectureDb.ts b/packages/mermaid/src/diagrams/architecture/architectureDb.ts index 654a220d8..bd8d7f0a7 100644 --- a/packages/mermaid/src/diagrams/architecture/architectureDb.ts +++ b/packages/mermaid/src/diagrams/architecture/architectureDb.ts @@ -3,7 +3,6 @@ import type { ArchitectureDB, ArchitectureService, ArchitectureGroup, - ArchitectureDirection, ArchitectureEdge, ArchitectureDirectionPairMap, ArchitectureDirectionPair, @@ -47,21 +46,20 @@ const clear = (): void => { commonClear(); }; -const addService = function (id: string, opts: Omit= {}) { - const { icon, in: inside, title } = opts; +const addService = function ({id, icon, in: parent, title}: Omit ) { if (state.records.registeredIds[id] !== undefined) { throw new Error(`The service id [${id}] is already in use by another ${state.records.registeredIds[id]}`); } - if (inside !== undefined) { - if (id === inside) { + if (parent !== undefined) { + if (id === parent) { throw new Error(`The service [${id}] cannot be placed within itself`); } - if (state.records.registeredIds[inside] === undefined) { + if (state.records.registeredIds[parent] === undefined) { throw new Error( `The service [${id}]'s parent does not exist. Please make sure the parent is created before this service` ); } - if (state.records.registeredIds[inside] === 'service') { + if (state.records.registeredIds[parent] === 'service') { throw new Error(`The service [${id}]'s parent is not a group`); } } @@ -73,27 +71,27 @@ const addService = function (id: string, opts: Omit Object.values(state.records.services); -const addGroup = function (id: string, opts: Omit = {}) { - const { icon, in: inside, title } = opts; +const addGroup = function ({id, icon, in: parent, title}: ArchitectureGroup) { + // const { icon, in: inside, title } = opts; if (state.records.registeredIds[id] !== undefined) { throw new Error(`The group id [${id}] is already in use by another ${state.records.registeredIds[id]}`); } - if (inside !== undefined) { - if (id === inside) { + if (parent !== undefined) { + if (id === parent) { throw new Error(`The group [${id}] cannot be placed within itself`); } - if (state.records.registeredIds[inside] === undefined) { + if (state.records.registeredIds[parent] === undefined) { throw new Error( `The group [${id}]'s parent does not exist. Please make sure the parent is created before this group` ); } - if (state.records.registeredIds[inside] === 'service') { + if (state.records.registeredIds[parent] === 'service') { throw new Error(`The group [${id}]'s parent is not a group`); } } @@ -104,7 +102,7 @@ const addGroup = function (id: string, opts: Omit = {}) id, icon, title, - in: inside, + in: parent, }); }; const getGroups = (): ArchitectureGroup[] => { @@ -112,13 +110,8 @@ const getGroups = (): ArchitectureGroup[] => { }; const addEdge = function ( - lhsId: string, - lhsDir: ArchitectureDirection, - rhsId: string, - rhsDir: ArchitectureDirection, - opts: Omit = {} + {lhsId, rhsId, lhsDir, rhsDir, lhsInto, rhsInto, title}: ArchitectureEdge ) { - const { title, lhsInto: lhsInto, rhsInto: rhsInto } = opts; if (!isArchitectureDirection(lhsDir)) { throw new Error( `Invalid direction given for left hand side of edge ${lhsId}--${rhsId}. Expected (L,R,T,B) got ${lhsDir}` diff --git a/packages/mermaid/src/diagrams/architecture/architectureDiagram.ts b/packages/mermaid/src/diagrams/architecture/architectureDiagram.ts index 614e2a7f3..82dacd3e1 100644 --- a/packages/mermaid/src/diagrams/architecture/architectureDiagram.ts +++ b/packages/mermaid/src/diagrams/architecture/architectureDiagram.ts @@ -1,6 +1,5 @@ import type { DiagramDefinition } from '../../diagram-api/types.js'; -// @ts-ignore: JISON doesn't support types -import parser from './parser/architecture.jison'; +import { parser } from './architectureParser.js'; import { db } from './architectureDb.js'; import styles from './architectureStyles.js'; import { renderer } from './architectureRenderer.js'; diff --git a/packages/mermaid/src/diagrams/architecture/architectureParser.ts b/packages/mermaid/src/diagrams/architecture/architectureParser.ts new file mode 100644 index 000000000..464b1034f --- /dev/null +++ b/packages/mermaid/src/diagrams/architecture/architectureParser.ts @@ -0,0 +1,23 @@ +import type { Architecture } from '@mermaid-js/parser'; +import { parse } from '@mermaid-js/parser'; +import { log } from '../../logger.js'; +import type { ParserDefinition } from '../../diagram-api/types.js'; +import { populateCommonDb } from '../common/populateCommonDb.js'; +import type { ArchitectureDB } from './architectureTypes.js'; +import { db } from './architectureDb.js'; + +const populateDb = (ast: Architecture, db: ArchitectureDB) => { + populateCommonDb(ast, db); + ast.groups.map(db.addGroup); + ast.services.map(db.addService); + // @ts-ignore TODO our parser guarantees the type is L/R/T/B and not string. How to change to union type? + ast.edges.map(db.addEdge); +}; + +export const parser: ParserDefinition = { + parse: async (input: string): Promise => { + const ast: Architecture = await parse('architecture', input); + log.debug(ast); + populateDb(ast, db); + }, +}; diff --git a/packages/mermaid/src/diagrams/architecture/architectureStyles.ts b/packages/mermaid/src/diagrams/architecture/architectureStyles.ts index 3a02a1fa6..be5951032 100644 --- a/packages/mermaid/src/diagrams/architecture/architectureStyles.ts +++ b/packages/mermaid/src/diagrams/architecture/architectureStyles.ts @@ -18,9 +18,8 @@ const genSections: DiagramStylesProvider = (options) => { for (let i = 0; i < options.THEME_COLOR_LIMIT; i++) { const sw = '' + (17 - 3 * i); sections += ` - .section-${i - 1} rect, .section-${i - 1} path, .section-${i - 1} circle, .section-${ - i - 1 - } polygon, .section-${i - 1} path { + .section-${i - 1} rect, .section-${i - 1} path, .section-${i - 1} circle, .section-${i - 1 + } polygon, .section-${i - 1} path { fill: ${options['cScale' + i]}; } .section-${i - 1} text { diff --git a/packages/mermaid/src/diagrams/architecture/architectureTypes.ts b/packages/mermaid/src/diagrams/architecture/architectureTypes.ts index 1519a686b..5368da46a 100644 --- a/packages/mermaid/src/diagrams/architecture/architectureTypes.ts +++ b/packages/mermaid/src/diagrams/architecture/architectureTypes.ts @@ -168,16 +168,12 @@ export interface ArchitectureEdge { export interface ArchitectureDB extends DiagramDB { clear: () => void; - addService: (id: string, opts: Omit ) => void; + addService: (service: Omit ) => void; getServices: () => ArchitectureService[]; - addGroup: (id: string, opts: Omit ) => void; + addGroup: (group: ArchitectureGroup) => void; getGroups: () => ArchitectureGroup[]; addEdge: ( - lhsId: string, - lhsDir: ArchitectureDirection, - rhsId: string, - rhsDir: ArchitectureDirection, - opts: Omit + edge: ArchitectureEdge ) => void; getEdges: () => ArchitectureEdge[]; setElementForId: (id: string, element: D3Element) => void; diff --git a/packages/mermaid/src/diagrams/architecture/parser/architecture.jison b/packages/mermaid/src/diagrams/architecture/parser/architecture.jison index 2a0ffc116..fc8aacd5a 100644 --- a/packages/mermaid/src/diagrams/architecture/parser/architecture.jison +++ b/packages/mermaid/src/diagrams/architecture/parser/architecture.jison @@ -1,3 +1,4 @@ +// TODO: delete file %lex %options case-insensitive diff --git a/packages/parser/langium-config.json b/packages/parser/langium-config.json index c750f049d..ab93c6501 100644 --- a/packages/parser/langium-config.json +++ b/packages/parser/langium-config.json @@ -15,6 +15,11 @@ "id": "pie", "grammar": "src/language/pie/pie.langium", "fileExtensions": [".mmd", ".mermaid"] + }, + { + "id": "architecture", + "grammar": "src/language/architecture/architecture.langium", + "fileExtensions": [".mmd", ".mermaid"] } ], "mode": "production", diff --git a/packages/parser/src/language/architecture/architecture.langium b/packages/parser/src/language/architecture/architecture.langium new file mode 100644 index 000000000..7d182958f --- /dev/null +++ b/packages/parser/src/language/architecture/architecture.langium @@ -0,0 +1,40 @@ +grammar Architecture +import "../common/common"; + +entry Architecture: + NEWLINE* + "architecture" + ( + NEWLINE* TitleAndAccessibilities + | NEWLINE* Statement* + | NEWLINE* + ) +; + +fragment Statement: + groups+=Group + | services+=Service + | edges+=Edge +; + +fragment Arrow: + lhsInto?=ARROW_INTO? lhsDir=ARROW_DIRECTION '--' rhsDir=ARROW_DIRECTION rhsInto?=ARROW_INTO? +; + +Group: + 'group' id=ARCH_ID icon=ARCH_ICON? title=ARCH_TITLE? ('in' in=ARCH_ID)? EOL +; + +Service: + 'service' id=ARCH_ID icon=ARCH_ICON? title=ARCH_TITLE? ('in' in=ARCH_ID)? EOL +; + +Edge: + lhsId=ARCH_ID Arrow rhsId=ARCH_ID EOL +; + +terminal ARROW_DIRECTION: 'L' | 'R' | 'T' | 'B'; +terminal ARCH_ID: /[\w]+/; +terminal ARCH_ICON: /\([\w]+\)/; +terminal ARCH_TITLE: /\[[\w ]+\]/; +terminal ARROW_INTO: /\(|\)/; \ No newline at end of file diff --git a/packages/parser/src/language/architecture/index.ts b/packages/parser/src/language/architecture/index.ts new file mode 100644 index 000000000..fd3c604b0 --- /dev/null +++ b/packages/parser/src/language/architecture/index.ts @@ -0,0 +1 @@ +export * from './module.js'; diff --git a/packages/parser/src/language/architecture/module.ts b/packages/parser/src/language/architecture/module.ts new file mode 100644 index 000000000..65ce7ea80 --- /dev/null +++ b/packages/parser/src/language/architecture/module.ts @@ -0,0 +1,74 @@ +import type { + DefaultSharedCoreModuleContext, + LangiumCoreServices, + LangiumSharedCoreServices, + Module, + PartialLangiumCoreServices, +} from 'langium'; +import { + EmptyFileSystem, + createDefaultCoreModule, + createDefaultSharedCoreModule, + inject, +} from 'langium'; + +import { MermaidGeneratedSharedModule, ArchitectureGeneratedModule } from '../generated/module.js'; +import { ArchitectureTokenBuilder } from './tokenBuilder.js'; +import { ArchitectureValueConverter } from './valueConverter.js'; + +/** + * Declaration of `Architecture` services. + */ +type ArchitectureAddedServices = { + parser: { + TokenBuilder: ArchitectureTokenBuilder; + ValueConverter: ArchitectureValueConverter; + }; +}; + +/** + * Union of Langium default services and `Architecture` services. + */ +export type ArchitectureServices = LangiumCoreServices & ArchitectureAddedServices; + +/** + * Dependency injection module that overrides Langium default services and + * contributes the declared `Architecture` services. + */ +export const ArchitectureModule: Module = { + parser: { + TokenBuilder: () => new ArchitectureTokenBuilder(), + ValueConverter: () => new ArchitectureValueConverter(), + }, +}; + +/** + * Create the full set of services required by Langium. + * + * First inject the shared services by merging two modules: + * - Langium default shared services + * - Services generated by langium-cli + * + * Then inject the language-specific services by merging three modules: + * - Langium default language-specific services + * - Services generated by langium-cli + * - Services specified in this file + * @param context - Optional module context with the LSP connection + * @returns An object wrapping the shared services and the language-specific services + */ +export function createArchitectureServices(context: DefaultSharedCoreModuleContext = EmptyFileSystem): { + shared: LangiumSharedCoreServices; + Architecture: ArchitectureServices; +} { + const shared: LangiumSharedCoreServices = inject( + createDefaultSharedCoreModule(context), + MermaidGeneratedSharedModule + ); + const Architecture: ArchitectureServices = inject( + createDefaultCoreModule({ shared }), + ArchitectureGeneratedModule, + ArchitectureModule + ); + shared.ServiceRegistry.register(Architecture); + return { shared, Architecture }; +} diff --git a/packages/parser/src/language/architecture/tokenBuilder.ts b/packages/parser/src/language/architecture/tokenBuilder.ts new file mode 100644 index 000000000..6a7c6a37a --- /dev/null +++ b/packages/parser/src/language/architecture/tokenBuilder.ts @@ -0,0 +1,7 @@ +import { AbstractMermaidTokenBuilder } from '../common/index.js'; + +export class ArchitectureTokenBuilder extends AbstractMermaidTokenBuilder { + public constructor() { + super(['architecture']); + } +} diff --git a/packages/parser/src/language/architecture/valueConverter.ts b/packages/parser/src/language/architecture/valueConverter.ts new file mode 100644 index 000000000..e6a2049a0 --- /dev/null +++ b/packages/parser/src/language/architecture/valueConverter.ts @@ -0,0 +1,19 @@ +import type { CstNode, GrammarAST, ValueType } from 'langium'; + +import { AbstractMermaidValueConverter } from '../common/index.js'; + +export class ArchitectureValueConverter extends AbstractMermaidValueConverter { + protected runCustomConverter( + rule: GrammarAST.AbstractRule, + input: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _cstNode: CstNode + ): ValueType | undefined { + if (rule.name === 'ARCH_ICON') { + return input.replace(/[()]/g, '').trim(); + } else if (rule.name === 'ARCH_TITLE') { + return input.replace(/[[\]]/g, '').trim(); + } + return undefined + } +} diff --git a/packages/parser/src/language/index.ts b/packages/parser/src/language/index.ts index 9f1d92ba8..6204f0967 100644 --- a/packages/parser/src/language/index.ts +++ b/packages/parser/src/language/index.ts @@ -5,21 +5,26 @@ export { PacketBlock, Pie, PieSection, + Architecture, isCommon, isInfo, isPacket, isPacketBlock, isPie, isPieSection, + isArchitecture } from './generated/ast.js'; + export { InfoGeneratedModule, MermaidGeneratedSharedModule, PacketGeneratedModule, PieGeneratedModule, + ArchitectureGeneratedModule } from './generated/module.js'; export * from './common/index.js'; export * from './info/index.js'; export * from './packet/index.js'; export * from './pie/index.js'; +export * from './architecture/index.js'; diff --git a/packages/parser/src/parse.ts b/packages/parser/src/parse.ts index 577a1cea6..a48cd9cbc 100644 --- a/packages/parser/src/parse.ts +++ b/packages/parser/src/parse.ts @@ -1,8 +1,8 @@ import type { LangiumParser, ParseResult } from 'langium'; -import type { Info, Packet, Pie } from './index.js'; +import type { Info, Packet, Pie, Architecture } from './index.js'; -export type DiagramAST = Info | Packet | Pie; +export type DiagramAST = Info | Packet | Pie | Architecture; const parsers: Record = {}; const initializers = { @@ -21,11 +21,17 @@ const initializers = { const parser = createPieServices().Pie.parser.LangiumParser; parsers['pie'] = parser; }, + architecture: async () => { + const { createArchitectureServices } = await import('./language/architecture/index.js'); + const parser = createArchitectureServices().Architecture.parser.LangiumParser; + parsers['architecture'] = parser; + }, } as const; export async function parse(diagramType: 'info', text: string): Promise ; export async function parse(diagramType: 'packet', text: string): Promise ; export async function parse(diagramType: 'pie', text: string): Promise ; +export async function parse(diagramType: 'architecture', text: string): Promise ; export async function parse ( diagramType: keyof typeof initializers, text: string