From cb302a08b8227abea8b4527f5645e15103813265 Mon Sep 17 00:00:00 2001 From: NicolasNewman Date: Mon, 15 Apr 2024 15:42:05 -0500 Subject: [PATCH] feat(arch): converted parser from jison to langium --- demos/architecture.html | 2 +- .../diagrams/architecture/architectureDb.ts | 35 ++++----- .../architecture/architectureDiagram.ts | 3 +- .../architecture/architectureParser.ts | 23 ++++++ .../architecture/architectureStyles.ts | 5 +- .../architecture/architectureTypes.ts | 10 +-- .../architecture/parser/architecture.jison | 1 + packages/parser/langium-config.json | 5 ++ .../architecture/architecture.langium | 40 ++++++++++ .../parser/src/language/architecture/index.ts | 1 + .../src/language/architecture/module.ts | 74 +++++++++++++++++++ .../src/language/architecture/tokenBuilder.ts | 7 ++ .../language/architecture/valueConverter.ts | 19 +++++ packages/parser/src/language/index.ts | 5 ++ packages/parser/src/parse.ts | 10 ++- 15 files changed, 204 insertions(+), 36 deletions(-) create mode 100644 packages/mermaid/src/diagrams/architecture/architectureParser.ts create mode 100644 packages/parser/src/language/architecture/architecture.langium create mode 100644 packages/parser/src/language/architecture/index.ts create mode 100644 packages/parser/src/language/architecture/module.ts create mode 100644 packages/parser/src/language/architecture/tokenBuilder.ts create mode 100644 packages/parser/src/language/architecture/valueConverter.ts 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