diff --git a/packages/parser/src/language/mindmap/mindmap.langium b/packages/parser/src/language/mindmap/mindmap.langium index 7606c0c8f..35a4c2c4f 100644 --- a/packages/parser/src/language/mindmap/mindmap.langium +++ b/packages/parser/src/language/mindmap/mindmap.langium @@ -5,13 +5,15 @@ grammar Mindmap entry MindmapDoc: - MINDMAP_KEYWORD (newline=NL)? + (PREDOC)? MINDMAP_KEYWORD (MindmapRows+=MindmapRow)*; -MindmapRow: - // indent=(INDENTATION | '0') item=Item (terminator=NL)?; - (indent=INDENTATION)? item=Item (terminator=NL)?; +PREDOC: + pre=(ML_COMMENT | WS)+; + +MindmapRow: + (indent=INDENTATION)|(indent=INDENTATION)? (item=Item); Item: Node | IconDecoration | ClassDecoration; @@ -38,16 +40,6 @@ CloudNode: HexagonNode: (id=ID)? desc=(HEXAGON_STR|HEXAGON_QSTR); -// Handle other complex node variants -OtherComplex: - id=ID - ( - ('[' '[' desc=(ID | STRING) ']' ']') | - ('{' '{' desc=(ID | STRING) '}' '}') | - ('(-' desc=(ID | STRING) '-)') | - ('(' desc=(ID | STRING) ')') - ); - // Simple node as fallback SimpleNode: id=ID; @@ -59,12 +51,10 @@ ClassDecoration: content=(CLASS); // This should be processed before whitespace is ignored -terminal INDENTATION: /[ \t]{2,}/; // Two or more spaces/tabs for indentation +terminal INDENTATION: /[ \t]{1,}/; // Two or more spaces/tabs for indentation // Keywords with fixed text patterns -terminal MINDMAP_KEYWORD: 'mindmap\n'; -// terminal ICON_KEYWORD: '::icon('; -// terminal CLASS_KEYWORD: ':::'; +terminal MINDMAP_KEYWORD: 'mindmap'; // Basic token types terminal CIRCLE_QSTR: "((\"" -> "\"))"; @@ -84,14 +74,14 @@ terminal ICON: "::icon(" -> ")"; terminal CLASS: /:::([^\n:])*/; terminal ID: /[a-zA-Z0-9_\-\.\/]+/; - +terminal STRING: /"[^"]*"|'[^']*'/; // Modified indentation rule to have higher priority than WS -terminal NL: /\r?\n/; +hidden terminal ML_COMMENT: /\s*\%\%[^\n]*/; +hidden terminal NL: /\r?\n/; // Hidden tokens terminal WS: /[ \t]/; // Single space or tab for hidden whitespace -hidden terminal ML_COMMENT: /\%\%[^\n]*/; // Type definition for node types type NodeType = 'DEFAULT' | 'CIRCLE' | 'CLOUD' | 'BANG' | 'HEXAGON' | 'ROUND'; diff --git a/packages/parser/src/language/mindmap/valueConverter.ts b/packages/parser/src/language/mindmap/valueConverter.ts index 5dafe2fa3..4e6fbfcf8 100644 --- a/packages/parser/src/language/mindmap/valueConverter.ts +++ b/packages/parser/src/language/mindmap/valueConverter.ts @@ -42,6 +42,7 @@ export class MindmapValueConverter extends AbstractMermaidValueConverter { } else if (rule.name === 'ICON') { return input.replace('::icon(', '').replace(')', '').trim(); } else if (rule.name === 'INDENTATION') { + console.debug('INDENTATION', input.length); return input.length; } return undefined; diff --git a/packages/parser/tests/mindmap-poc.test.ts b/packages/parser/tests/mindmap-poc.test.ts index 587b8af40..3ad5fd0a7 100644 --- a/packages/parser/tests/mindmap-poc.test.ts +++ b/packages/parser/tests/mindmap-poc.test.ts @@ -3,12 +3,9 @@ import { validatedMindmapParse as validatedParse, mindmapParse as parse } from ' import type { CircleNode, SimpleNode, OtherComplex } from '../src/language/generated/ast.js'; describe('Nodes (ported from mindmap.spec.ts)', () => { - it('MMP-21 should be possible to have comments in a mindmap', () => { - const result = parse( - 'mindmap\nroot(Root)\n Child(Child)\n a(a)\n\n %% This is a comment\n b[New Stuff]' - ); + it('MMP-20 should be possible to have meaningless empty rows in a mindmap', () => { + const result = parse('mindmap\nroot(Root)\n Child(Child)\n a(a)\n\n b[New Stuff]'); expect(result.lexerErrors).toHaveLength(0); - console.debug(result.parserErrors); expect(result.parserErrors).toHaveLength(0); const rootNode = result.value.MindmapRows[0].item as OtherComplex; const childNode = result.value.MindmapRows[1].item as OtherComplex; @@ -19,4 +16,65 @@ describe('Nodes (ported from mindmap.spec.ts)', () => { expect(aNode.desc).toBe('a'); expect(bNode.desc).toBe('New Stuff'); }); + it.only('MMP-24 Handle rows above the mindmap declarations', () => { + const result = parse('\n \nmindmap\nroot\n A\n \n\n B'); + if (result.lexerErrors.length > 0) { + console.debug('lexerErrors', result.lexerErrors); + } + expect(result.lexerErrors).toHaveLength(0); + if (result.parserErrors.length > 0) { + console.debug('Error', result.parserErrors); + } + expect(result.parserErrors).toHaveLength(0); + for (const row of result.value.MindmapRows) { + console.debug('Row', row); + } + const rootNode = result.value.MindmapRows[0].item as SimpleNode; + const aNode = result.value.MindmapRows[1].item as SimpleNode; + const bNode = result.value.MindmapRows[3].item as SimpleNode; + expect(rootNode.id).toBe('root'); + expect(aNode.id).toBe('A'); + expect(bNode.id).toBe('B'); + }); + + it('MMP-22 should be possible to have comments at the end of a line', () => { + const result = parse( + 'mindmap\nroot(Root)\n Child(Child)\n a(a) %% This is a comment\n b[New Stuff]' + ); + if (result.lexerErrors.length > 0) { + console.debug('lexerErrors', result.lexerErrors); + } + if (result.parserErrors.length > 0) { + console.debug('Error', result.parserErrors); + } + for (const row of result.value.MindmapRows) { + console.debug('Row', row); + } + expect(result.lexerErrors).toHaveLength(0); + expect(result.parserErrors).toHaveLength(0); + const rootNode = result.value.MindmapRows[0].item as OtherComplex; + const childNode = result.value.MindmapRows[1].item as OtherComplex; + const aNode = result.value.MindmapRows[2].item as OtherComplex; + const bNode = result.value.MindmapRows[3].item as OtherComplex; + expect(rootNode.desc).toBe('Root'); + expect(childNode.desc).toBe('Child'); + expect(aNode.desc).toBe('a'); + expect(bNode.desc).toBe('New Stuff'); + }); + // it('MMP-21 should be possible to have comments in a mindmap', () => { + // const result = parse( + // 'mindmap\nroot(Root)\n Child(Child)\n a(a)\n\n %% This is a comment\n b[New Stuff]' + // ); + // expect(result.lexerErrors).toHaveLength(0); + // console.debug(result.parserErrors); + // expect(result.parserErrors).toHaveLength(0); + // const rootNode = result.value.MindmapRows[0].item as OtherComplex; + // const childNode = result.value.MindmapRows[1].item as OtherComplex; + // const aNode = result.value.MindmapRows[2].item as OtherComplex; + // const bNode = result.value.MindmapRows[3].item as OtherComplex; + // expect(rootNode.desc).toBe('Root'); + // expect(childNode.desc).toBe('Child'); + // expect(aNode.desc).toBe('a'); + // expect(bNode.desc).toBe('New Stuff'); + // }); }); diff --git a/packages/parser/tests/mindmap.test.ts b/packages/parser/tests/mindmap.test.ts index 7d6f0f8d4..978815649 100644 --- a/packages/parser/tests/mindmap.test.ts +++ b/packages/parser/tests/mindmap.test.ts @@ -142,6 +142,7 @@ describe('Hierarchy (ported from mindmap.spec.ts)', () => { ); const str2 = 'mindmap\nroot\n notAFakeRoot'; const result2 = await validatedParse(str2, { validation: true }); + // console.debug('RESULT2:', result2.diagnostics); expect(result2.diagnostics?.length).toBe(0); }); @@ -353,17 +354,25 @@ describe('Miscellaneous (ported from mindmap.spec.ts)', () => { }); it('MMP-21 should be possible to have comments in a mindmap', () => { const result = parse( + // 'mindmap\nroot(Root)\n Child(Child)\n a(a)\n %% This is a comment\n b[New Stuff]' 'mindmap\nroot(Root)\n Child(Child)\n a(a)\n\n %% This is a comment\n b[New Stuff]' ); + if (result.lexerErrors.length > 0) { + console.debug('lexerErrors', result.lexerErrors); + } expect(result.lexerErrors).toHaveLength(0); + if (result.parserErrors.length > 0) { + console.debug('Error', result.parserErrors); + } + expect(result.parserErrors).toHaveLength(0); const rootNode = result.value.MindmapRows[0].item as OtherComplex; const childNode = result.value.MindmapRows[1].item as OtherComplex; const aNode = result.value.MindmapRows[2].item as OtherComplex; - const bNode = result.value.MindmapRows[3].item as OtherComplex; expect(rootNode.desc).toBe('Root'); expect(childNode.desc).toBe('Child'); expect(aNode.desc).toBe('a'); + const bNode = result.value.MindmapRows[4].item as OtherComplex; expect(bNode.desc).toBe('New Stuff'); }); it('MMP-22 should be possible to have comments at the end of a line', () => { @@ -375,19 +384,19 @@ describe('Miscellaneous (ported from mindmap.spec.ts)', () => { const rootNode = result.value.MindmapRows[0].item as OtherComplex; const childNode = result.value.MindmapRows[1].item as OtherComplex; const aNode = result.value.MindmapRows[2].item as OtherComplex; - const bNode = result.value.MindmapRows[3].item as OtherComplex; + const bNode = result.value.MindmapRows[4].item as OtherComplex; expect(rootNode.desc).toBe('Root'); expect(childNode.desc).toBe('Child'); expect(aNode.desc).toBe('a'); expect(bNode.desc).toBe('New Stuff'); }); it('MMP-23 Rows with only spaces should not interfere', () => { - const result = parse('mindmap\nroot\n A\n \n\n B'); + const result = parse('mindmap\nroot\n A\n \n\n B'); expect(result.lexerErrors).toHaveLength(0); expect(result.parserErrors).toHaveLength(0); const rootNode = result.value.MindmapRows[0].item as SimpleNode; const aNode = result.value.MindmapRows[1].item as SimpleNode; - const bNode = result.value.MindmapRows[2].item as SimpleNode; + const bNode = result.value.MindmapRows[3].item as SimpleNode; expect(rootNode.id).toBe('root'); expect(aNode.id).toBe('A'); expect(bNode.id).toBe('B'); @@ -398,7 +407,7 @@ describe('Miscellaneous (ported from mindmap.spec.ts)', () => { expect(result.parserErrors).toHaveLength(0); const rootNode = result.value.MindmapRows[0].item as SimpleNode; const aNode = result.value.MindmapRows[1].item as SimpleNode; - const bNode = result.value.MindmapRows[2].item as SimpleNode; + const bNode = result.value.MindmapRows[3].item as SimpleNode; expect(rootNode.id).toBe('root'); expect(aNode.id).toBe('A'); expect(bNode.id).toBe('B'); @@ -409,7 +418,7 @@ describe('Miscellaneous (ported from mindmap.spec.ts)', () => { expect(result.parserErrors).toHaveLength(0); const rootNode = result.value.MindmapRows[0].item as SimpleNode; const aNode = result.value.MindmapRows[1].item as SimpleNode; - const bNode = result.value.MindmapRows[2].item as SimpleNode; + const bNode = result.value.MindmapRows[3].item as SimpleNode; expect(rootNode.id).toBe('root'); expect(aNode.id).toBe('A'); expect(bNode.id).toBe('B');