From df3c3d2fdc80c7449b09f5ee54e923ac47add22f Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Sat, 19 Apr 2025 04:54:25 -0400 Subject: [PATCH] WIP - fixing grammar separating SimpleNode from ComplexNode --- .../src/diagrams/mindmap/parser/mindmap.jison | 189 ++++++------- packages/parser/langium-config.json | 38 ++- packages/parser/src/language/index.ts | 2 + .../src/language/mindmap/mindmap.langium | 83 +++--- .../src/language/mindmap/minmap.langium | 56 ---- .../parser/src/language/mindmap/module.ts | 258 +++--------------- .../src/language/mindmap/tokenBuilder.ts | 2 +- packages/parser/tests/mindmap-debug.js | 7 + packages/parser/tests/mindmap.test.ts | 69 +++-- packages/parser/tests/test-print-esm.js | 24 ++ packages/parser/tests/test-print.js | 27 ++ packages/parser/tests/test-util.ts | 18 +- 12 files changed, 320 insertions(+), 453 deletions(-) delete mode 100644 packages/parser/src/language/mindmap/minmap.langium create mode 100644 packages/parser/tests/mindmap-debug.js create mode 100644 packages/parser/tests/test-print-esm.js create mode 100644 packages/parser/tests/test-print.js diff --git a/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison b/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison index afd5e2300..86dacdb06 100644 --- a/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison +++ b/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison @@ -1,127 +1,94 @@ -/** mermaid - * https://knsv.github.io/mermaid - * (c) 2015 Knut Sveidqvist - * MIT license. +/** + * Mindmap grammar for Langium + * Converted from mermaid's jison grammar */ -%lex +grammar Mindmap -%options case-insensitive +// Entry rule - equivalent to the 'start' rule in jison +entry MindmapDocument: + // The document starts with the 'mindmap' keyword + (spaceLines+=SPACELINE)* + 'mindmap' (NL)? + (documentContent=DocumentContent)?; -%{ - // Pre-lexer code can go here -%} -%x NODE -%x NSTR -%x NSTR2 -%x ICON -%x CLASS +// Document contains multiple statements separated by newlines +DocumentContent: + statements+=Statement (stop+=Stop statements+=Statement)* (stop+=Stop)?; -%% +// A stop is a newline, EOF, or a spaceline - used to separate statements +Stop: + NL | EOF | SPACELINE; -\s*\%\%.* {yy.getLogger().trace('Found comment',yytext); return 'SPACELINE';} -// \%\%[^\n]*\n /* skip comments */ -"mindmap" return 'MINDMAP'; -":::" { this.begin('CLASS'); } -.+ { this.popState();return 'CLASS'; } -\n { this.popState();} -// [\s]*"::icon(" { this.begin('ICON'); } -"::icon(" { yy.getLogger().trace('Begin icon');this.begin('ICON'); } -[\s]+[\n] {yy.getLogger().trace('SPACELINE');return 'SPACELINE' /* skip all whitespace */ ;} -[\n]+ return 'NL'; -[^\)]+ { return 'ICON'; } -\) {yy.getLogger().trace('end icon');this.popState();} -"-)" { yy.getLogger().trace('Exploding node'); this.begin('NODE');return 'NODE_DSTART'; } -"(-" { yy.getLogger().trace('Cloud'); this.begin('NODE');return 'NODE_DSTART'; } -"))" { yy.getLogger().trace('Explosion Bang'); this.begin('NODE');return 'NODE_DSTART'; } -")" { yy.getLogger().trace('Cloud Bang'); this.begin('NODE');return 'NODE_DSTART'; } -"((" { this.begin('NODE');return 'NODE_DSTART'; } -"{{" { this.begin('NODE');return 'NODE_DSTART'; } -"(" { this.begin('NODE');return 'NODE_DSTART'; } -"[" { this.begin('NODE');return 'NODE_DSTART'; } -[\s]+ return 'SPACELIST' /* skip all whitespace */ ; -// !(-\() return 'NODE_ID'; -[^\(\[\n\)\{\}]+ return 'NODE_ID'; -<> return 'EOF'; -["][`] { this.begin("NSTR2");} -[^`"]+ { return "NODE_DESCR";} -[`]["] { this.popState();} -["] { yy.getLogger().trace('Starting NSTR');this.begin("NSTR");} -[^"]+ { yy.getLogger().trace('description:', yytext); return "NODE_DESCR";} -["] {this.popState();} -[\)]\) {this.popState();yy.getLogger().trace('node end ))');return "NODE_DEND";} -[\)] {this.popState();yy.getLogger().trace('node end )');return "NODE_DEND";} -[\]] {this.popState();yy.getLogger().trace('node end ...',yytext);return "NODE_DEND";} -"}}" {this.popState();yy.getLogger().trace('node end ((');return "NODE_DEND";} -"(-" {this.popState();yy.getLogger().trace('node end (-');return "NODE_DEND";} -"-)" {this.popState();yy.getLogger().trace('node end (-');return "NODE_DEND";} -"((" {this.popState();yy.getLogger().trace('node end ((');return "NODE_DEND";} -"(" {this.popState();yy.getLogger().trace('node end ((');return "NODE_DEND";} -[^\)\]\(\}]+ { yy.getLogger().trace('Long description:', yytext); return 'NODE_DESCR';} -.+(?!\(\() { yy.getLogger().trace('Long description:', yytext); return 'NODE_DESCR';} -// [\[] return 'NODE_START'; -// .+ return 'TXT' ; +// Statements can be nodes, icons, classes, or empty lines +Statement: + // The whitespace prefix determines nesting level in the mindmap + (indent=INDENT)? ( + node=Node | // A node in the mindmap + icon=IconDecoration | // Icon decoration for a node + cssClass=ClassDecoration // CSS class for a node + ) | + SPACELINE; // Empty or comment lines -/lex +// A node can be either simple (just ID) or complex (with description) +Node: + SimpleNode | ComplexNode; -%start start +// Simple node is just an identifier +SimpleNode: + id=NODE_ID; -%% /* language grammar */ +// Complex node has a description enclosed in brackets, parentheses, etc. +ComplexNode: + // Optional ID followed by a description with delimiters + (id=NODE_ID)? start=NODE_DSTART description=NODE_DESCR end=NODE_DEND; -start -// %{ : info document 'EOF' { return yy; } } - : mindMap - | spaceLines mindMap - ; +// Icon decoration for nodes +IconDecoration: + '::icon(' name=ICON ')'; -spaceLines - : SPACELINE - | spaceLines SPACELINE - | spaceLines NL - ; +// CSS class decoration for nodes +ClassDecoration: + ':::' name=CLASS; -mindMap - : MINDMAP document { return yy; } - | MINDMAP NL document { return yy; } - ; +// Hidden terminal rules (comments, whitespace that should be ignored during parsing) +hidden terminal WS: /[ \t]+/; -stop - : NL {yy.getLogger().trace('Stop NL ');} - | EOF {yy.getLogger().trace('Stop EOF ');} - | SPACELINE - | stop NL {yy.getLogger().trace('Stop NL2 ');} - | stop EOF {yy.getLogger().trace('Stop EOF2 ');} - ; -document - : document statement stop - | statement stop - ; +// Terminal rules (lexer rules) +terminal INDENT: /[ \t]+/; +terminal SPACELINE: /\s*\%\%.*|[ \t]+\n/; +terminal NL: /\n+/; +terminal EOF: /$/; -statement - : SPACELIST node { yy.getLogger().info('Node: ',$2.id);yy.addNode($1.length, $2.id, $2.descr, $2.type); } - | SPACELIST ICON { yy.getLogger().trace('Icon: ',$2);yy.decorateNode({icon: $2}); } - | SPACELIST CLASS { yy.decorateNode({class: $2}); } - | SPACELINE { yy.getLogger().trace('SPACELIST');} - | node { yy.getLogger().trace('Node: ',$1.id);yy.addNode(0, $1.id, $1.descr, $1.type); } - | ICON { yy.decorateNode({icon: $1}); } - | CLASS { yy.decorateNode({class: $1}); } - | SPACELIST - ; +// Node related terminals with refined regex patterns to match the jison lexer +terminal NODE_ID: /[^\(\[\n\)\{\}]+/; +terminal NODE_DSTART: /\(\(|\{\{|\(|\[|\-\)|\(\-|\)\)|\)/; +terminal NODE_DEND: /\)\)|\}\}|\)|\]|\(\-|\-\)|\(\(/; +terminal NODE_DESCR: /[^"\)`\]]+/; +terminal ICON: /[^\)]+/; +terminal CLASS: /[^\n]+/; +// We also need to implement these semantic actions from the jison grammar: +// - addNode(level, id, description, type) +// - decorateNode({icon: iconName}) +// - decorateNode({class: className}) +// - getType(startDelimiter, endDelimiter) +/** + * Interface for a MindmapNode. + * This represents the AST node for a mindmap node. + */ +interface MindmapNode { + id: string; + description?: string; + type: NodeType; + level: number; // Indentation level (derived from the INDENT token) + icon?: string; + cssClass?: string; + children?: MindmapNode[]; +} -node - :nodeWithId - |nodeWithoutId - ; - -nodeWithoutId - : NODE_DSTART NODE_DESCR NODE_DEND - { yy.getLogger().trace("node found ..", $1); $$ = { id: $2, descr: $2, type: yy.getType($1, $3) }; } - ; - -nodeWithId - : NODE_ID { $$ = { id: $1, descr: $1, type: yy.nodeType.DEFAULT }; } - | NODE_ID NODE_DSTART NODE_DESCR NODE_DEND - { yy.getLogger().trace("node found ..", $1); $$ = { id: $1, descr: $3, type: yy.getType($2, $4) }; } - ; -%% +/** + * The different node types in mindmap based on delimiters. + * This corresponds to the yy.getType() function in the jison grammar. + */ +type NodeType = 'DEFAULT' | 'CIRCLE' | 'CLOUD' | 'BANG' | 'HEXAGON' | 'ROUND'; diff --git a/packages/parser/langium-config.json b/packages/parser/langium-config.json index ad80350c2..35c3a2169 100644 --- a/packages/parser/langium-config.json +++ b/packages/parser/langium-config.json @@ -4,32 +4,58 @@ { "id": "info", "grammar": "src/language/info/info.langium", - "fileExtensions": [".mmd", ".mermaid"] + "fileExtensions": [ + ".mmd", + ".mermaid" + ] }, { "id": "packet", "grammar": "src/language/packet/packet.langium", - "fileExtensions": [".mmd", ".mermaid"] + "fileExtensions": [ + ".mmd", + ".mermaid" + ] }, { "id": "pie", "grammar": "src/language/pie/pie.langium", - "fileExtensions": [".mmd", ".mermaid"] + "fileExtensions": [ + ".mmd", + ".mermaid" + ] }, { "id": "architecture", "grammar": "src/language/architecture/architecture.langium", - "fileExtensions": [".mmd", ".mermaid"] + "fileExtensions": [ + ".mmd", + ".mermaid" + ] }, { "id": "gitGraph", "grammar": "src/language/gitGraph/gitGraph.langium", - "fileExtensions": [".mmd", ".mermaid"] + "fileExtensions": [ + ".mmd", + ".mermaid" + ] }, { "id": "radar", "grammar": "src/language/radar/radar.langium", - "fileExtensions": [".mmd", ".mermaid"] + "fileExtensions": [ + ".mmd", + ".mermaid" + ] + }, + { + "id": "mindmap", + "grammar": "src/language/mindmap/mindmap.langium", + "fileExtensions": [ + ".mmd", + ".mermaid" + ] } ], "mode": "production", diff --git a/packages/parser/src/language/index.ts b/packages/parser/src/language/index.ts index 42bce13e8..23aeeb0da 100644 --- a/packages/parser/src/language/index.ts +++ b/packages/parser/src/language/index.ts @@ -11,6 +11,7 @@ export { Branch, Commit, Merge, + MindmapDoc as Mindmap, Statement, isInfo, isPacket, @@ -32,6 +33,7 @@ export { ArchitectureGeneratedModule, GitGraphGeneratedModule, RadarGeneratedModule, + MindmapGeneratedModule, } from './generated/module.js'; export * from './gitGraph/index.js'; diff --git a/packages/parser/src/language/mindmap/mindmap.langium b/packages/parser/src/language/mindmap/mindmap.langium index 0f0312d0b..c9684ccd5 100644 --- a/packages/parser/src/language/mindmap/mindmap.langium +++ b/packages/parser/src/language/mindmap/mindmap.langium @@ -1,49 +1,64 @@ -grammar MindMap +/** + * Mindmap grammar for Langium + * Converted from mermaid's jison grammar + */ +grammar Mindmap -import 'Terminals' - -entry Diagram: - keyword='mindmap' - statements+=Statement*; +entry MindmapDoc: + MINDMAP_KEYWORD (newline=NL)? + (statements+=Statement)*; Statement: - RootNode | Node; + (indent=INDENTATION)? element=Element (terminator=NL)?; -RootNode: - root=Text (child=Node)?; +Element: + Node | IconDecoration | ClassDecoration; Node: - depth=DEPTH text=Text (child=Node)?; + ComplexNode | SimpleNode; -terminal DEPTH: - /\t+/ | / {2,}/; +SimpleNode: + id=ID + // Ensure it does not match the structure of a ComplexNode + (NL | INDENTATION)?; -Text: - content=TEXT_CONTENT shorthand=SHORTHAND?; +ComplexNode: + (id=ID)? + start=(LPAREN|LBRACKET|LCURLY|START_CLOUD|DOUBLE_PAREN) + desc=(ID|STRING) + end=(RPAREN|RBRACKET|RCURLY|END_CLOUD|DOUBLE_PAREN); -terminal TEXT_CONTENT: - /[^\n\r\[\]]+/; +IconDecoration: + ICON_KEYWORD content=(ID|STRING) RPAREN; -terminal SHORTHAND: - /\[[^\]]*\]/; +ClassDecoration: + CLASS_KEYWORD content=(ID|STRING); -terminal ICON: - /::icon\([^\)]+\)/; +// Keywords with fixed text patterns +terminal MINDMAP_KEYWORD: 'mindmap'; +terminal ICON_KEYWORD: '::icon('; +terminal CLASS_KEYWORD: ':::'; -terminal CLASSNAME: - /:::[^\n]*/; +// Delimiters - using unique string literals +terminal LPAREN: '('; +terminal RPAREN: ')'; +terminal DOUBLE_PAREN: '((' | '))'; // Combined to avoid regex conflicts +terminal LBRACKET: '['; +terminal RBRACKET: ']'; +terminal LCURLY: '{{'; +terminal RCURLY: '}}'; +terminal START_CLOUD: '(-'; +terminal END_CLOUD: '-)'; -hidden terminal WS: - /\s+/; +// Basic token types +terminal ID: /[a-zA-Z0-9_\-\.\/]+/; +terminal STRING: /"[^"]*"|'[^']*'/; +terminal INDENTATION: /[ \t]{2,}/; // Two or more spaces/tabs for indentation +terminal NL: /\r?\n/; -hidden terminal NEWLINE: - /\r?\n/; +// Hidden tokens +hidden terminal WS: /[ \t]/; // Single space or tab for hidden whitespace +hidden terminal ML_COMMENT: /\%\%[^\n]*/; -hidden terminal ML_COMMENT: - /\/\*[\s\S]*?\*\//; - -hidden terminal SL_COMMENT: - /\/\/[^\n\r]*/; - -hidden terminal DIRECTIVE: - /%%[^\n\r]*/; +// Type definition for node types +type NodeType = 'DEFAULT' | 'CIRCLE' | 'CLOUD' | 'BANG' | 'HEXAGON' | 'ROUND'; diff --git a/packages/parser/src/language/mindmap/minmap.langium b/packages/parser/src/language/mindmap/minmap.langium deleted file mode 100644 index 5a4b0596c..000000000 --- a/packages/parser/src/language/mindmap/minmap.langium +++ /dev/null @@ -1,56 +0,0 @@ -grammar GitGraph -import "../common/common"; -import "reference"; - -entry GitGraph: - NEWLINE* - ('gitGraph' | 'gitGraph' ':' | 'gitGraph:' | ('gitGraph' Direction ':')) - ( - NEWLINE - | TitleAndAccessibilities - | statements+=Statement - )* -; - -Statement -: Commit -| Branch -| Merge -| Checkout -| CherryPicking -; - -Direction: - dir=('LR' | 'TB' | 'BT'); - -Commit: - 'commit' - ( - 'id:' id=STRING - |'msg:'? message=STRING - |'tag:' tags+=STRING - |'type:' type=('NORMAL' | 'REVERSE' | 'HIGHLIGHT') - )* EOL; -Branch: - 'branch' name=(REFERENCE|STRING) - ('order:' order=INT)? - EOL; - -Merge: - 'merge' branch=(REFERENCE|STRING) - ( - 'id:' id=STRING - |'tag:' tags+=STRING - |'type:' type=('NORMAL' | 'REVERSE' | 'HIGHLIGHT') - )* EOL; - -Checkout: - ('checkout'|'switch') branch=(REFERENCE|STRING) EOL; - -CherryPicking: - 'cherry-pick' - ( - 'id:' id=STRING - |'tag:' tags+=STRING - |'parent:' parent=STRING - )* EOL; diff --git a/packages/parser/src/language/mindmap/module.ts b/packages/parser/src/language/mindmap/module.ts index 925b3e54c..8c56d31f4 100644 --- a/packages/parser/src/language/mindmap/module.ts +++ b/packages/parser/src/language/mindmap/module.ts @@ -4,242 +4,74 @@ import type { LangiumSharedCoreServices, Module, PartialLangiumCoreServices, - LanguageMetaData, - Grammar, } from 'langium'; import { - inject, + EmptyFileSystem, createDefaultCoreModule, createDefaultSharedCoreModule, - EmptyFileSystem, - loadGrammarFromJson, + inject, } from 'langium'; + +import { MermaidGeneratedSharedModule, MindmapGeneratedModule } from '../generated/module.js'; +import { MindmapTokenBuilder } from './tokenBuilder.js'; import { CommonValueConverter } from '../common/valueConverter.js'; -import { MermaidGeneratedSharedModule } from '../generated/module.js'; -import { MindMapTokenBuilder } from './tokenBuilder.js'; -export const MindMapLanguageMetaData: LanguageMetaData = { - languageId: 'mindmap', - fileExtensions: ['.mmd', '.mermaid'], - caseInsensitive: false, - mode: 'production', -}; - -// Define a minimal grammar directly in JSON format -let loadedMindMapGrammar: Grammar | undefined; -export const MindMapGrammar = (): Grammar => - loadedMindMapGrammar ?? - (loadedMindMapGrammar = loadGrammarFromJson(`{ - "$type": "Grammar", - "isDeclared": true, - "name": "MindMap", - "imports": [], - "rules": [ - { - "$type": "ParserRule", - "entry": true, - "name": "Diagram", - "definition": { - "$type": "Group", - "elements": [ - { - "$type": "Assignment", - "feature": "keyword", - "operator": "=", - "terminal": { - "$type": "Keyword", - "value": "mindmap" - } - }, - { - "$type": "Assignment", - "feature": "statements", - "operator": "+=", - "terminal": { - "$type": "Alternatives", - "elements": [ - { - "$type": "RuleCall", - "rule": {"$ref": "#/rules@1"}, - "arguments": [] - }, - { - "$type": "RuleCall", - "rule": {"$ref": "#/rules@2"}, - "arguments": [] - } - ] - }, - "cardinality": "*" - } - ] - }, - "definesHiddenTokens": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, - { - "$type": "ParserRule", - "name": "RootNode", - "definition": { - "$type": "Group", - "elements": [ - { - "$type": "Assignment", - "feature": "content", - "operator": "=", - "terminal": { - "$type": "RuleCall", - "rule": {"$ref": "#/rules@3"}, - "arguments": [] - } - } - ] - }, - "definesHiddenTokens": false, - "entry": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, - { - "$type": "ParserRule", - "name": "ChildNode", - "definition": { - "$type": "Group", - "elements": [ - { - "$type": "Assignment", - "feature": "depth", - "operator": "=", - "terminal": { - "$type": "RuleCall", - "rule": {"$ref": "#/rules@4"}, - "arguments": [] - } - }, - { - "$type": "Assignment", - "feature": "content", - "operator": "=", - "terminal": { - "$type": "RuleCall", - "rule": {"$ref": "#/rules@3"}, - "arguments": [] - } - } - ] - }, - "definesHiddenTokens": false, - "entry": false, - "fragment": false, - "hiddenTokens": [], - "parameters": [], - "wildcard": false - }, - { - "$type": "TerminalRule", - "name": "WORD", - "type": {"$type": "ReturnType", "name": "string"}, - "definition": { - "$type": "RegexToken", - "regex": "/[a-zA-Z0-9_-]+/" - }, - "fragment": false, - "hidden": false - }, - { - "$type": "TerminalRule", - "name": "INDENT", - "type": {"$type": "ReturnType", "name": "string"}, - "definition": { - "$type": "RegexToken", - "regex": "/(?:\\\\t+| {2,})/" - }, - "fragment": false, - "hidden": false - }, - { - "$type": "TerminalRule", - "name": "WS", - "definition": { - "$type": "RegexToken", - "regex": "/\\\\s+/" - }, - "fragment": false, - "hidden": true - }, - { - "$type": "TerminalRule", - "name": "NL", - "definition": { - "$type": "RegexToken", - "regex": "/\\\\r?\\\\n/" - }, - "fragment": false, - "hidden": false - }, - { - "$type": "TerminalRule", - "name": "ML_COMMENT", - "definition": { - "$type": "RegexToken", - "regex": "/\\\\/\\\\*[\\\\s\\\\S]*?\\\\*\\\\//" - }, - "fragment": false, - "hidden": true - }, - { - "$type": "TerminalRule", - "name": "SL_COMMENT", - "definition": { - "$type": "RegexToken", - "regex": "/(?:%+|\\\\/{2,})[^\\\\n\\\\r]*/" - }, - "fragment": false, - "hidden": true - } - ], - "definesHiddenTokens": false, - "hiddenTokens": [], - "interfaces": [], - "types": [], - "usedGrammars": [] - }`)); - -interface MindMapAddedServices { +/** + * Declaration of `Mindmap` services. + */ +interface MindmapAddedServices { parser: { - TokenBuilder: MindMapTokenBuilder; + TokenBuilder: MindmapTokenBuilder; ValueConverter: CommonValueConverter; }; } -export type MindMapServices = LangiumCoreServices & MindMapAddedServices; +/** + * Union of Langium default services and `Mindmap` services. + */ +export type MindmapServices = LangiumCoreServices & MindmapAddedServices; -export const MindMapModule: Module< - MindMapServices, - PartialLangiumCoreServices & MindMapAddedServices +/** + * Dependency injection module that overrides Langium default services and + * contributes the declared `Mindmap` services. + */ +export const MindmapModule: Module< + MindmapServices, + PartialLangiumCoreServices & MindmapAddedServices > = { parser: { - TokenBuilder: () => new MindMapTokenBuilder(), + TokenBuilder: () => new MindmapTokenBuilder(), ValueConverter: () => new CommonValueConverter(), }, - Grammar: MindMapGrammar, - LanguageMetaData: () => MindMapLanguageMetaData, }; -export function createMindMapServices(context: DefaultSharedCoreModuleContext = EmptyFileSystem): { +/** + * 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 createMindmapServices(context: DefaultSharedCoreModuleContext = EmptyFileSystem): { shared: LangiumSharedCoreServices; - MindMap: MindMapServices; + Mindmap: MindmapServices; } { const shared: LangiumSharedCoreServices = inject( createDefaultSharedCoreModule(context), MermaidGeneratedSharedModule ); - const MindMap: MindMapServices = inject(createDefaultCoreModule({ shared }), MindMapModule); - shared.ServiceRegistry.register(MindMap); - return { shared, MindMap }; + const Mindmap: MindmapServices = inject( + createDefaultCoreModule({ shared }), + MindmapGeneratedModule, + MindmapModule + ); + shared.ServiceRegistry.register(Mindmap); + return { shared, Mindmap }; } diff --git a/packages/parser/src/language/mindmap/tokenBuilder.ts b/packages/parser/src/language/mindmap/tokenBuilder.ts index 596a0f0f0..da859e04b 100644 --- a/packages/parser/src/language/mindmap/tokenBuilder.ts +++ b/packages/parser/src/language/mindmap/tokenBuilder.ts @@ -1,6 +1,6 @@ import { AbstractMermaidTokenBuilder } from '../common/index.js'; -export class MindMapTokenBuilder extends AbstractMermaidTokenBuilder { +export class MindmapTokenBuilder extends AbstractMermaidTokenBuilder { public constructor() { super(['mindmap']); } diff --git a/packages/parser/tests/mindmap-debug.js b/packages/parser/tests/mindmap-debug.js new file mode 100644 index 000000000..b9cbb0b75 --- /dev/null +++ b/packages/parser/tests/mindmap-debug.js @@ -0,0 +1,7 @@ +// Debug file to print the structure of a parsed mindmap +import { mindMapParse } from './test-util.js'; + +const result = mindMapParse('mindmap\nroot\n child1\n child2'); + +console.log('Parser result:', result.value); +console.log('Statement structure:', JSON.stringify(result.value?.statements[0], null, 2)); diff --git a/packages/parser/tests/mindmap.test.ts b/packages/parser/tests/mindmap.test.ts index 250a90b4d..f83b5520c 100644 --- a/packages/parser/tests/mindmap.test.ts +++ b/packages/parser/tests/mindmap.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { mindMapParse as parse } from './test-util.js'; +import { mindmapParse as parse } from './test-util.js'; +import { keys } from '../../mermaid-flowchart-elk/dist/packages/mermaid/src/diagrams/state/id-cache'; // Tests for mindmap parser with simple root and child nodes describe('MindMap Parser Tests', () => { @@ -30,8 +31,9 @@ describe('MindMap Parser Tests', () => { expect(rootNode.content).toBe('root'); }); - it('should parse a mindmap with child nodes', () => { - const _result = parse( + it.only('should parse a mindmap with child nodes', () => { + console.log('BEFORE RESULT:'); + const result = parse( 'mindmap\nroot((Root))\n child1((Child 1))\n child2((Child 2))\n grandchild((Grand Child))' ); @@ -40,28 +42,49 @@ describe('MindMap Parser Tests', () => { // Statements length: result.value?.statements?.length // If statements exist, they would have properties like id, type, text, depth + const statements = result.value.statements; + const s0 = statements[0]; + const s1 = statements[1]; + + console.debug('Statements:', s0); + + expect(result.value.statements[0].$type).toBe('Statement'); + // expect(result.value.statements[0].element.$type).toBe('ComplexNode'); + expect(s0.element.$type).toBe('ComplexNode'); + expect(Object.keys(s0)).toBe('Root'); + expect(s0.element.ID).toBe('Root'); + + expect(result.value.statements[1].$type).toBe('Statement'); + expect(result.value.statements[1].element.$type).toBe('ComplexNode'); + expect(result.value.statements[1].element.ID).toBe('Root'); + expect(result.value.statements[1].element.desc).toBe('Root'); + expect(Object.keys(result.value.statements[1].element)).toBe('root'); + expect(result.value.statements[1].indent).toBe('indent'); + expect(Object.keys(result.value.statements[1].element)).toBe(true); + expect(result.value.statements[1].element.id).toBe('SimpleNode'); + // Temporarily commenting out failing assertions // expect(result.successful).toBe(true); - // // Check that there are 4 statements: mindmap, root, child1, child2, grandchild - // expect(result.value.statements.length).toBe(5); - // // Check that the first statement is the mindmap - // expect(result.value.statements[0].type).toBe('mindmap'); - // // Check that the second statement is the root - // expect(result.value.statements[1].type.type).toBe('circle'); - // expect(result.value.statements[1].text).toBe('Root'); - // expect(result.value.statements[1].depth).toBe(0); - // // Check that the third statement is the first child - // expect(result.value.statements[2].type.type).toBe('circle'); - // expect(result.value.statements[2].text).toBe('Child 1'); - // expect(result.value.statements[2].depth).toBe(1); - // // Check that the fourth statement is the second child - // expect(result.value.statements[3].type.type).toBe('circle'); - // expect(result.value.statements[3].text).toBe('Child 2'); - // expect(result.value.statements[3].depth).toBe(1); - // // Check that the fifth statement is the grandchild - // expect(result.value.statements[4].type.type).toBe('circle'); - // expect(result.value.statements[4].text).toBe('Grand Child'); - // expect(result.value.statements[4].depth).toBe(2); + // Check that there are 4 statements: mindmap, root, child1, child2, grandchild + expect(result.value.statements.length).toBe(5); + // Check that the first statement is the mindmap + expect(result.value.statements[0].type).toBe('mindmap'); + // Check that the second statement is the root + expect(result.value.statements[1].type.type).toBe('circle'); + expect(result.value.statements[1].text).toBe('Root'); + expect(result.value.statements[1].depth).toBe(0); + // Check that the third statement is the first child + expect(result.value.statements[2].type.type).toBe('circle'); + expect(result.value.statements[2].text).toBe('Child 1'); + expect(result.value.statements[2].depth).toBe(1); + // Check that the fourth statement is the second child + expect(result.value.statements[3].type.type).toBe('circle'); + expect(result.value.statements[3].text).toBe('Child 2'); + expect(result.value.statements[3].depth).toBe(1); + // Check that the fifth statement is the grandchild + expect(result.value.statements[4].type.type).toBe('circle'); + expect(result.value.statements[4].text).toBe('Grand Child'); + expect(result.value.statements[4].depth).toBe(2); }); }); diff --git a/packages/parser/tests/test-print-esm.js b/packages/parser/tests/test-print-esm.js new file mode 100644 index 000000000..48a8ba99d --- /dev/null +++ b/packages/parser/tests/test-print-esm.js @@ -0,0 +1,24 @@ +// Test file to print the parsed structure of a mindmap +import { createMindMapServices } from '../src/language/index.js'; + +// Create services +const mindMapServices = createMindMapServices().MindMap; +const mindMapParser = mindMapServices.parser.LangiumParser; + +// Function to parse mindmap +function parse(input) { + return mindMapParser.parse(input); +} + +// Test with a simple mindmap +const result = parse('mindmap\nroot\n child1\n child2'); + +// Print the result structure +console.log('Parse result:'); +console.log(JSON.stringify(result.value, null, 2)); + +// Print the first statement +if (result.value?.statements?.[0]) { + console.log('\nFirst statement:'); + console.log(JSON.stringify(result.value.statements[0], null, 2)); +} diff --git a/packages/parser/tests/test-print.js b/packages/parser/tests/test-print.js new file mode 100644 index 000000000..c5c20f038 --- /dev/null +++ b/packages/parser/tests/test-print.js @@ -0,0 +1,27 @@ +// Test parsing +import { createMindMapServices } from '../lib/language/mindmap/module.js'; +import { parseDocument } from 'langium'; + +// Create services for handling the language +const services = createMindMapServices(); +// Get the service for parsing documents +const documentBuilder = services.MindMap.shared.workspace.DocumentBuilder; + +// Sample mindmap text to parse +const text = 'mindmap\nroot\n child1\n child2'; + +// Parse the document +const doc = documentBuilder.buildDocuments([ + { + uri: 'file:///test.mindmap', + content: text, + version: 1, + }, +]); + +// Get the parsed document +const result = Array.isArray(doc) ? doc[0] : undefined; +if (result) { + console.log('AST:', JSON.stringify(result.parseResult.value, null, 2)); + console.log('First node:', JSON.stringify(result.parseResult.value.statements?.[0], null, 2)); +} diff --git a/packages/parser/tests/test-util.ts b/packages/parser/tests/test-util.ts index af68a2176..cd1842ff9 100644 --- a/packages/parser/tests/test-util.ts +++ b/packages/parser/tests/test-util.ts @@ -13,8 +13,8 @@ import type { PacketServices, GitGraph, GitGraphServices, - MindMap, - MindMapServices, + Mindmap, + MindmapServices, } from '../src/language/index.js'; import { createArchitectureServices, @@ -23,7 +23,7 @@ import { createRadarServices, createPacketServices, createGitGraphServices, - createMindMapServices, + createMindmapServices, } from '../src/language/index.js'; const consoleMock = vi.spyOn(console, 'log').mockImplementation(() => undefined); @@ -108,13 +108,13 @@ export function createGitGraphTestServices() { } export const gitGraphParse = createGitGraphTestServices().parse; -const mindMapServices: MindMapServices = createMindMapServices().MindMap; -const mindMapParser: LangiumParser = mindMapServices.parser.LangiumParser; -export function createMindMapTestServices() { +const mindmapServices: MindmapServices = createMindmapServices().Mindmap; +const mindmapParser: LangiumParser = mindmapServices.parser.LangiumParser; +export function createMindmapTestServices() { const parse = (input: string) => { - return mindMapParser.parse(input); + return mindmapParser.parse(input); }; - return { services: mindMapServices, parse }; + return { services: mindmapServices, parse }; } -export const mindMapParse = createMindMapTestServices().parse; +export const mindmapParse = createMindmapTestServices().parse;