diff --git a/src/diagram-api/diagramAPI.ts b/src/diagram-api/diagramAPI.ts index 428ea9fea..80343a8f3 100644 --- a/src/diagram-api/diagramAPI.ts +++ b/src/diagram-api/diagramAPI.ts @@ -30,8 +30,7 @@ import infoDb from '../diagrams/info/infoDb'; import infoRenderer from '../diagrams/info/infoRenderer'; import infoParser from '../diagrams/info/infoParser'; import infoStyles from '../diagrams/info/styles'; -// @ts-ignore -import pieParser from '../diagrams/pie/parser/pie'; +import pieParser from '../diagrams/pie/pieParser'; import pieDb from '../diagrams/pie/pieDb'; import pieRenderer from '../diagrams/pie/pieRenderer'; import pieStyles from '../diagrams/pie/styles'; diff --git a/src/diagrams/pie/parser/pie.jison b/src/diagrams/pie/parser/pie.jison deleted file mode 100644 index e98638aa8..000000000 --- a/src/diagrams/pie/parser/pie.jison +++ /dev/null @@ -1,106 +0,0 @@ -/** mermaid - * https://knsv.github.io/mermaid - * (c) 2015 Knut Sveidqvist - * MIT license. - */ -%lex -%options case-insensitive - -%x string -%x title -%x open_directive -%x type_directive -%x arg_directive -%x close_directive -%x acc_title -%x acc_descr -%x acc_descr_multiline -%% -\%\%\{ { this.begin('open_directive'); return 'open_directive'; } -((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } -":" { this.popState(); this.begin('arg_directive'); return ':'; } -\}\%\% { this.popState(); this.popState(); return 'close_directive'; } -((?:(?!\}\%\%).|\n)*) return 'arg_directive'; -\%\%(?!\{)[^\n]* /* skip comments */ -[^\}]\%\%[^\n]* /* skip comments */{ /*console.log('');*/ } -[\n\r]+ return 'NEWLINE'; -\%\%[^\n]* /* do nothing */ -[\s]+ /* ignore */ -title { this.begin("title");return 'title'; } -(?!\n|;|#)*[^\n]* { this.popState(); return "title_value"; } - -accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } -<acc_title>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; } -accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; } -<acc_descr>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; } -accDescr\s*"{"\s* { this.begin("acc_descr_multiline");} -<acc_descr_multiline>[\}] { this.popState(); } -<acc_descr_multiline>[^\}]* return "acc_descr_multiline_value"; -["] { this.begin("string"); } -<string>["] { this.popState(); } -<string>[^"]* { return "txt"; } -"pie" return 'PIE'; -"showData" return 'showData'; -":"[\s]*[\d]+(?:\.[\d]+)? return "value"; -<<EOF>> return 'EOF'; - -/lex - -%start start - -%% /* language grammar */ - -start - : eol start - | directive start - | PIE document - | PIE showData document {yy.setShowData(true);} - ; - -document - : /* empty */ - | document line - ; - -line - : statement eol { $$ = $1 } - ; - -statement - : - | txt value { yy.addSection($1,yy.cleanupValue($2)); } - | title title_value { $$=$2.trim();yy.setDiagramTitle($$); } - | acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); } - | acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); } - | acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } | section {yy.addSection($1.substr(8));$$=$1.substr(8);} - | directive - ; - -directive - : openDirective typeDirective closeDirective - | openDirective typeDirective ':' argDirective closeDirective - ; - -eol - : NEWLINE - | ';' - | EOF - ; - -openDirective - : open_directive { yy.parseDirective('%%{', 'open_directive'); } - ; - -typeDirective - : type_directive { yy.parseDirective($1, 'type_directive'); } - ; - -argDirective - : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } - ; - -closeDirective - : close_directive { yy.parseDirective('}%%', 'close_directive', 'pie'); } - ; - -%% diff --git a/src/diagrams/pie/parser/pie.spec.js b/src/diagrams/pie/pie.spec.js similarity index 50% rename from src/diagrams/pie/parser/pie.spec.js rename to src/diagrams/pie/pie.spec.js index 21523dde4..3e52bb512 100644 --- a/src/diagrams/pie/parser/pie.spec.js +++ b/src/diagrams/pie/pie.spec.js @@ -1,6 +1,6 @@ -import pieDb from '../pieDb'; -import pie from './pie'; -import { setConfig } from '../../../config'; +import pieDb from './pieDb'; +import pie from './pieParser'; +import { setConfig } from '../../config'; setConfig({ securityLevel: 'strict', @@ -8,19 +8,18 @@ setConfig({ describe('when parsing pie', function () { beforeEach(function () { - pie.parser.yy = pieDb; - pie.parser.yy.clear(); + pieDb.clear(); }); - it('should handle very simple pie', function () { - const res = pie.parser.parse(`pie + it('should handle simple pie', function () { + pie.parser.parse(`pie "ash" : 100 `); const sections = pieDb.getSections(); const section1 = sections['ash']; expect(section1).toBe(100); }); - it('should handle simple pie', function () { - const res = pie.parser.parse(`pie + it('should handle pie', function () { + pie.parser.parse(`pie "ash" : 60 "bat" : 40 `); @@ -28,33 +27,33 @@ describe('when parsing pie', function () { const section1 = sections['ash']; expect(section1).toBe(60); }); - it('should handle simple pie with comments', function () { - const res = pie.parser.parse(`pie - %% comments -"ash" : 60 -"bat" : 40 -`); + it('should handle pie with comments', function () { + pie.parser.parse(`pie + %% comments + "ash" : 60 + "bat" : 40 + `); const sections = pieDb.getSections(); const section1 = sections['ash']; expect(section1).toBe(60); }); - it('should handle simple pie with a directive', function () { - const res = pie.parser.parse(`%%{init: {'logLevel':0}}%% -pie -"ash" : 60 -"bat" : 40 -`); + it('should handle pie with a directive', function () { + pie.parser.parse(`%%{init: {'logLevel':0}}%% + pie + "ash" : 60 + "bat" : 40 + `); const sections = pieDb.getSections(); const section1 = sections['ash']; expect(section1).toBe(60); }); - it('should handle simple pie with a title', function () { - const res = pie.parser.parse(`pie title a 60/40 pie -"ash" : 60 -"bat" : 40 -`); + it('should handle pie with a title', function () { + pie.parser.parse(`pie title a 60/40 pie + "ash" : 60 + "bat" : 40 + `); const sections = pieDb.getSections(); const title = pieDb.getDiagramTitle(); const section1 = sections['ash']; @@ -62,27 +61,25 @@ pie expect(title).toBe('a 60/40 pie'); }); - it('should handle simple pie without an acc description (accDescr)', function () { - const res = pie.parser.parse(`pie title a neat chart -"ash" : 60 -"bat" : 40 -`); + it('should handle pie with an acc title (accTitle)', function () { + pie.parser.parse(`pie title a neat chart + accTitle: Hello World + "ash" : 60 + "bat" : 40 + `); - const sections = pieDb.getSections(); - const title = pieDb.getDiagramTitle(); const description = pieDb.getAccDescription(); - const section1 = sections['ash']; - expect(section1).toBe(60); - expect(title).toBe('a neat chart'); + const title = pieDb.getAccTitle(); + expect(title).toBe('Hello World'); expect(description).toBe(''); }); - it('should handle simple pie with an acc description (accDescr)', function () { - const res = pie.parser.parse(`pie title a neat chart - accDescr: a neat description -"ash" : 60 -"bat" : 40 -`); + it('should handle pie with an acc description (accDescr)', function () { + pie.parser.parse(`pie title a neat chart + accDescr: a neat description + "ash" : 60 + "bat" : 40 + `); const sections = pieDb.getSections(); const title = pieDb.getDiagramTitle(); @@ -92,15 +89,18 @@ pie expect(title).toBe('a neat chart'); expect(description).toBe('a neat description'); }); - it('should handle simple pie with a multiline acc description (accDescr)', function () { - const res = pie.parser.parse(`pie title a neat chart - accDescr { - a neat description - on multiple lines - } -"ash" : 60 -"bat" : 40 -`); + + it('should handle pie with a multiline acc description (accDescr)', function () { + pie.parser.parse(`pie title a neat chart + accDescr { + a neat description + on multiple lines + } + "ash" : 60 + + "bat" : 40 + + `); const sections = pieDb.getSections(); const title = pieDb.getDiagramTitle(); @@ -111,22 +111,31 @@ pie expect(description).toBe('a neat description\non multiple lines'); }); - it('should handle simple pie with positive decimal', function () { - const res = pie.parser.parse(`pie -"ash" : 60.67 -"bat" : 40 -`); + it('should handle pie with positive decimal', function () { + pie.parser.parse(`pie + "ash" : 60.67 + "bat" : 40 + `); const sections = pieDb.getSections(); const section1 = sections['ash']; expect(section1).toBe(60.67); }); - it('should handle simple pie with negative decimal', function () { + it('should handle pie with invalid decimal', function () { expect(() => { pie.parser.parse(`pie -"ash" : 60.67 -"bat" : 40..12 -`); + "ash" : 60.67 + "bat" : 40..12 + `); + }).toThrowError(); + }); + + it('should handle pie with negative decimal', function () { + expect(() => { + pie.parser.parse(`pie + "ash" : 60.67 + "bat" : -40.12 + `); }).toThrowError(); }); }); diff --git a/src/diagrams/pie/pieDb.js b/src/diagrams/pie/pieDb.ts similarity index 93% rename from src/diagrams/pie/pieDb.js rename to src/diagrams/pie/pieDb.ts index def0242a3..d0db9b20b 100644 --- a/src/diagrams/pie/pieDb.js +++ b/src/diagrams/pie/pieDb.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { log } from '../../logger'; import mermaidAPI from '../../mermaidAPI'; import * as configApi from '../../config'; @@ -13,8 +14,6 @@ import { } from '../../commonDb'; let sections = {}; -let title = ''; -let description = ''; let showData = false; export const parseDirective = function (statement, context, type) { @@ -25,7 +24,7 @@ const addSection = function (id, value) { id = common.sanitizeText(id, configApi.getConfig()); if (typeof sections[id] === 'undefined') { sections[id] = value; - log.debug('Added new section :', id); + log.debug('Added new section : ', id, ' with value:', value); } }; const getSections = () => sections; @@ -49,7 +48,6 @@ const cleanupValue = function (value) { const clear = function () { sections = {}; - title = ''; showData = false; commonClear(); }; diff --git a/src/diagrams/pie/pieParser.ts b/src/diagrams/pie/pieParser.ts new file mode 100644 index 000000000..3163ab9b9 --- /dev/null +++ b/src/diagrams/pie/pieParser.ts @@ -0,0 +1,168 @@ +import { createToken, EmbeddedActionsParser, Lexer } from 'chevrotain'; +import { log } from '../../logger'; +import pieDb from './pieDb'; + +const NewLine = createToken({ + name: 'NewLine', + pattern: /\r?\n/, +}); +const WhiteSpace = createToken({ + name: 'WhiteSpace', + pattern: /\s+/, + group: Lexer.SKIPPED, +}); + +const Colon = createToken({ name: 'Colon', pattern: /:/ }); +const Text = createToken({ name: 'Text', pattern: /[^\n\r"]+/ }); +const StringLiteral = createToken({ + name: 'StringLiteral', + pattern: /"(:?[^\\"]|\\(:?[bfnrtv"\\/]|u[0-9a-fA-F]{4}))*"/, +}); +const NumberLiteral = createToken({ + name: 'NumberLiteral', + pattern: /(0|[1-9]\d*)(\.\d+)?([eE][+-]?\d+)?/, +}); +// TODO: Fix +const Comment = createToken({ + name: 'Comment', + pattern: /%%.*\n/, + group: Lexer.SKIPPED, +}); + +const Pie = createToken({ name: 'Pie', pattern: /pie/i }); +const ShowData = createToken({ name: 'ShowData', pattern: /showData/i }); +const Title = createToken({ name: 'Title', pattern: /title/i }); +const AccDescription = createToken({ name: 'AccDescription', pattern: /accDescr/i }); +const AccTitle = createToken({ name: 'AccTitle', pattern: /accTitle/i }); +const LeftCurly = createToken({ name: 'LeftCurly', pattern: /{/ }); +const RightCurly = createToken({ name: 'RightCurly', pattern: /}/ }); +// TODO: Figure out ordering of tokens +const allTokens = [ + NewLine, + WhiteSpace, + Colon, + LeftCurly, + RightCurly, + Comment, + // Keywords + Pie, + ShowData, + Title, + AccTitle, + AccDescription, + // Literals + NumberLiteral, + StringLiteral, + Text, +]; +const PieLexer = new Lexer(allTokens); + +class PieParser extends EmbeddedActionsParser { + constructor() { + super(allTokens); + this.performSelfAnalysis(); + } + + public reset(): void { + super.reset(); + pieDb.clear(); + } + + public diagram = this.RULE('diagram', () => { + this.SUBRULE(this.header); + this.OPTION(() => { + this.SUBRULE(this.accTitle); + this.CONSUME(NewLine); + }); + this.OPTION2(() => { + this.OR([ + { ALT: () => this.SUBRULE(this.accDescriptionSingleLine) }, + { ALT: () => this.SUBRULE(this.accDescriptionMultiLine) }, + ]); + this.CONSUME2(NewLine); + }); + this.AT_LEAST_ONE(() => { + this.SUBRULE2(this.row); + }); + }); + + public header = this.RULE('header', () => { + this.CONSUME(Pie); + this.OPTION(() => { + this.CONSUME(ShowData); + this.ACTION(() => pieDb.setShowData(true)); + }); + this.OPTION2(() => { + this.SUBRULE(this.title); + }); + this.CONSUME(NewLine); + }); + + public title = this.RULE('title', () => { + this.CONSUME(Title); + const titleText = this.CONSUME(Text).image; + this.ACTION(() => pieDb.setDiagramTitle(titleText)); + }); + + public accTitle = this.RULE('accTitle', () => { + this.CONSUME(AccTitle); + this.CONSUME(Colon); + const accTitleText = this.CONSUME(Text).image; + this.ACTION(() => pieDb.setAccTitle(accTitleText)); + }); + + public accDescriptionSingleLine = this.RULE('accDescriptionSingleLine', () => { + this.CONSUME(AccDescription); + this.CONSUME(Colon); + const accDescrText = this.CONSUME(Text).image; + this.ACTION(() => pieDb.setAccDescription(accDescrText)); + }); + + public accDescriptionMultiLine = this.RULE('accDescriptionMultiLine', () => { + this.CONSUME(AccDescription); + this.CONSUME(LeftCurly); + this.MANY(() => this.CONSUME(NewLine)); + + const text: string[] = []; + this.AT_LEAST_ONE(() => { + const line = this.CONSUME(Text); + text.push(line.image); + this.MANY1(() => this.CONSUME2(NewLine)); + }); + this.CONSUME(RightCurly); + this.ACTION(() => pieDb.setAccDescription(text.join('\n'))); + }); + + public row = this.RULE('row', () => { + this.SUBRULE(this.section); + this.MANY(() => { + this.CONSUME(NewLine); + }); + }); + + public section = this.RULE('section', () => { + const quotedKey = this.CONSUME(StringLiteral).image; + const key = quotedKey.slice(1, quotedKey.length - 1); + this.CONSUME(Colon); + const value = parseFloat(this.CONSUME(NumberLiteral).image); + this.ACTION(() => pieDb.addSection(key, value)); + }); +} + +const parser = new PieParser(); + +const parse = (text: string): void => { + const lexResult = PieLexer.tokenize(text); + parser.input = lexResult.tokens; + parser.diagram(); + + if (parser.errors.length > 0 || lexResult.errors.length > 0) { + log.error( + { parserErrors: parser.errors, lexerErrors: lexResult.errors }, + 'Error parsing info diagram' + ); + throw new Error(`Parser errors: ${parser.errors} Lex errors: ${lexResult.errors}`); + } +}; + +export default { parser: { parse }, parse };