Skip to content

Commit cfed700

Browse files
committed
chore: modified the file structure to implement the new way of antlr parsing
on-behalf-of: @Mermaid-Chart <[email protected]>
1 parent b715d82 commit cfed700

24 files changed

+2467
-3745
lines changed

.esbuild/server.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import cors from 'cors';
44
import { context } from 'esbuild';
55
import type { Request, Response } from 'express';
66
import express from 'express';
7+
import { execSync } from 'child_process';
78
import { packageOptions } from '../.build/common.js';
89
import { generateLangium } from '../.build/generateLangium.js';
910
import { defaultOptions, getBuildConfig } from './util.js';
@@ -64,6 +65,28 @@ function eventsHandler(request: Request, response: Response) {
6465
}
6566

6667
let timeoutID: NodeJS.Timeout | undefined = undefined;
68+
let isGeneratingAntlr = false;
69+
70+
/**
71+
* Generate ANTLR parser files from grammar files
72+
*/
73+
function generateAntlr(): void {
74+
if (isGeneratingAntlr) {
75+
console.log('⏳ ANTLR generation already in progress, skipping...');
76+
return;
77+
}
78+
79+
try {
80+
isGeneratingAntlr = true;
81+
console.log('🎯 ANTLR: Generating parser files...');
82+
execSync('tsx scripts/antlr-generate.mts', { stdio: 'inherit' });
83+
console.log('✅ ANTLR: Parser files generated successfully\n');
84+
} catch (error) {
85+
console.error('❌ ANTLR: Failed to generate parser files:', error);
86+
} finally {
87+
isGeneratingAntlr = false;
88+
}
89+
}
6790

6891
/**
6992
* Debounce file change events to avoid rebuilding multiple times.
@@ -89,7 +112,7 @@ async function createServer() {
89112
handleFileChange();
90113
const app = express();
91114
chokidar
92-
.watch('**/src/**/*.{js,ts,langium,yaml,json}', {
115+
.watch('**/src/**/*.{js,ts,g4,langium,yaml,json}', {
93116
ignoreInitial: true,
94117
ignored: [/node_modules/, /dist/, /docs/, /coverage/],
95118
})
@@ -103,6 +126,9 @@ async function createServer() {
103126
if (path.endsWith('.langium')) {
104127
await generateLangium();
105128
}
129+
if (path.endsWith('.g4')) {
130+
generateAntlr();
131+
}
106132
handleFileChange();
107133
});
108134

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
"git graph"
1616
],
1717
"scripts": {
18-
"build": "pnpm build:esbuild && pnpm build:types",
18+
"build": "pnpm antlr:generate && pnpm build:esbuild && pnpm build:types",
1919
"build:esbuild": "pnpm run -r clean && tsx .esbuild/build.ts",
20+
"antlr:generate": "tsx scripts/antlr-generate.mts",
21+
"antlr:watch": "tsx scripts/antlr-watch.mts",
2022
"build:mermaid": "pnpm build:esbuild --mermaid",
2123
"build:viz": "pnpm build:esbuild --visualize",
2224
"build:types": "pnpm --filter mermaid types:build-config && tsx .build/types.ts",

packages/mermaid/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
"scripts": {
3535
"clean": "rimraf dist",
3636
"dev": "pnpm -w dev",
37+
"antlr:generate": "tsx ../../scripts/antlr-generate.mts",
38+
"antlr:watch": "tsx ../../scripts/antlr-watch.mts",
3739
"docs:code": "typedoc src/defaultConfig.ts src/config.ts src/mermaid.ts && prettier --write ./src/docs/config/setup",
3840
"docs:build": "rimraf ../../docs && pnpm docs:code && pnpm docs:spellcheck && tsx scripts/docs.cli.mts",
3941
"docs:verify": "pnpm docs:code && pnpm docs:spellcheck && tsx scripts/docs.cli.mts --verify",
@@ -71,6 +73,7 @@
7173
"@iconify/utils": "^3.0.2",
7274
"@mermaid-js/parser": "workspace:^",
7375
"@types/d3": "^7.4.3",
76+
"antlr4ng": "^3.0.7",
7477
"cytoscape": "^3.33.1",
7578
"cytoscape-cose-bilkent": "^4.1.0",
7679
"cytoscape-fcose": "^2.2.0",
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { BaseErrorListener } from 'antlr4ng';
2+
import type { RecognitionException, Recognizer } from 'antlr4ng';
3+
4+
/**
5+
* Custom error listener for ANTLR usecase parser
6+
* Captures syntax errors and provides detailed error messages
7+
*/
8+
export class UsecaseErrorListener extends BaseErrorListener {
9+
private errors: { line: number; column: number; message: string; offendingSymbol?: any }[] = [];
10+
11+
syntaxError(
12+
_recognizer: Recognizer<any>,
13+
offendingSymbol: any,
14+
line: number,
15+
charPositionInLine: number,
16+
message: string,
17+
_e: RecognitionException | null
18+
): void {
19+
this.errors.push({
20+
line,
21+
column: charPositionInLine,
22+
message,
23+
offendingSymbol,
24+
});
25+
}
26+
27+
reportAmbiguity(): void {
28+
// Optional: handle ambiguity reports
29+
}
30+
31+
reportAttemptingFullContext(): void {
32+
// Optional: handle full context attempts
33+
}
34+
35+
reportContextSensitivity(): void {
36+
// Optional: handle context sensitivity reports
37+
}
38+
39+
getErrors(): { line: number; column: number; message: string; offendingSymbol?: any }[] {
40+
return this.errors;
41+
}
42+
43+
hasErrors(): boolean {
44+
return this.errors.length > 0;
45+
}
46+
47+
clear(): void {
48+
this.errors = [];
49+
}
50+
51+
/**
52+
* Create a detailed error with JISON-compatible hash property
53+
*/
54+
createDetailedError(): Error {
55+
if (this.errors.length === 0) {
56+
return new Error('Unknown parsing error');
57+
}
58+
59+
const firstError = this.errors[0];
60+
const message = `Parse error on line ${firstError.line}: ${firstError.message}`;
61+
const error = new Error(message);
62+
63+
// Add hash property for JISON compatibility
64+
Object.assign(error, {
65+
hash: {
66+
line: firstError.line,
67+
loc: {
68+
first_line: firstError.line,
69+
last_line: firstError.line,
70+
first_column: firstError.column,
71+
last_column: firstError.column,
72+
},
73+
text: firstError.offendingSymbol?.text ?? '',
74+
token: firstError.offendingSymbol?.text ?? '',
75+
expected: [],
76+
},
77+
});
78+
79+
return error;
80+
}
81+
82+
/**
83+
* Get all error messages as a single string
84+
*/
85+
getErrorMessages(): string {
86+
return this.errors
87+
.map((error) => `Line ${error.line}:${error.column} - ${error.message}`)
88+
.join('\n');
89+
}
90+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
lexer grammar UsecaseLexer;
2+
3+
// Keywords
4+
ACTOR: 'actor';
5+
SYSTEM_BOUNDARY: 'systemBoundary';
6+
END: 'end';
7+
DIRECTION: 'direction';
8+
CLASS_DEF: 'classDef';
9+
CLASS: 'class';
10+
STYLE: 'style';
11+
USECASE: 'usecase';
12+
13+
// Direction keywords
14+
TB: 'TB';
15+
TD: 'TD';
16+
BT: 'BT';
17+
RL: 'RL';
18+
LR: 'LR';
19+
20+
// System boundary types
21+
PACKAGE: 'package';
22+
RECT: 'rect';
23+
TYPE: 'type';
24+
25+
// Arrow types (order matters - longer patterns first)
26+
SOLID_ARROW: '-->';
27+
BACK_ARROW: '<--';
28+
CIRCLE_ARROW: '--o';
29+
CIRCLE_ARROW_REVERSED: 'o--';
30+
CROSS_ARROW: '--x';
31+
CROSS_ARROW_REVERSED: 'x--';
32+
LINE_SOLID: '--';
33+
34+
// Symbols
35+
COMMA: ',';
36+
AT: '@';
37+
LBRACE: '{';
38+
RBRACE: '}';
39+
COLON: ':';
40+
LPAREN: '(';
41+
RPAREN: ')';
42+
CLASS_SEPARATOR: ':::';
43+
44+
// Hash color (must come before HASH to avoid conflicts)
45+
HASH_COLOR: '#' [a-fA-F0-9]+;
46+
47+
// Number with optional unit
48+
NUMBER: [0-9]+ ('.' [0-9]+)? ([a-zA-Z]+)?;
49+
50+
// Identifier
51+
IDENTIFIER: [a-zA-Z_][a-zA-Z0-9_]*;
52+
53+
// String literals
54+
STRING: '"' (~["\r\n])* '"' | '\'' (~['\r\n])* '\'';
55+
56+
// These tokens are defined last so they have lowest priority
57+
// This ensures arrow tokens like '-->' are matched before DASH
58+
DASH: '-';
59+
DOT: '.';
60+
PERCENT: '%';
61+
62+
// Whitespace and newlines
63+
NEWLINE: [\r\n]+;
64+
WS: [ \t]+ -> skip;
65+

0 commit comments

Comments
 (0)