diff --git a/.build/antlr-cli.ts b/.build/antlr-cli.ts new file mode 100644 index 000000000..53187b8cf --- /dev/null +++ b/.build/antlr-cli.ts @@ -0,0 +1,92 @@ +/* eslint-disable no-console */ + +import { exec } from 'node:child_process'; +import { promisify } from 'node:util'; +import { resolve, dirname } from 'node:path'; +import { readFile, mkdir, access } from 'node:fs/promises'; +import { existsSync } from 'node:fs'; + +const execAsync = promisify(exec); + +interface AntlrGrammarConfig { + id: string; + grammar: string; + outputDir: string; + language: string; + generateVisitor?: boolean; + generateListener?: boolean; +} + +interface AntlrConfig { + projectName: string; + grammars: AntlrGrammarConfig[]; + mode: string; +} + +export async function generateFromConfig(configFile: string): Promise { + const configPath = resolve(configFile); + + if (!existsSync(configPath)) { + throw new Error(`ANTLR config file not found: ${configPath}`); + } + + const configContent = await readFile(configPath, 'utf-8'); + const config: AntlrConfig = JSON.parse(configContent); + + const configDir = dirname(configPath); + + for (const grammarConfig of config.grammars) { + await generateGrammar(grammarConfig, configDir); + } +} + +async function generateGrammar(grammarConfig: AntlrGrammarConfig, baseDir: string): Promise { + const grammarFile = resolve(baseDir, grammarConfig.grammar); + const outputDir = resolve(baseDir, grammarConfig.outputDir); + + // Check if grammar file exists + try { + await access(grammarFile); + } catch { + throw new Error(`Grammar file not found: ${grammarFile}`); + } + + // Ensure output directory exists + await mkdir(outputDir, { recursive: true }); + + // Build ANTLR command arguments + + // eslint-disable-next-line @cspell/spellchecker + const args = ['antlr-ng', `-Dlanguage=${grammarConfig.language}`]; + + if (grammarConfig.generateVisitor) { + args.push('--generate-visitor'); + } + + if (grammarConfig.generateListener) { + args.push('--generate-listener'); + } + + args.push('-o', `"${outputDir}"`, `"${grammarFile}"`); + + const command = `npx ${args.join(' ')}`; + + try { + await execAsync(command); + console.log(`Generated ANTLR files for ${grammarConfig.id}`); + } catch (error) { + console.error(`Failed to generate ANTLR files for ${grammarConfig.id}:`); + throw error; + } +} + +// CLI interface +if (import.meta.url === `file://${process.argv[1]}`) { + const configFile = process.argv[2] || './packages/parser/antlr-config.json'; + try { + await generateFromConfig(configFile); + console.log('ANTLR generation completed successfully!'); + } catch (error) { + console.error('ANTLR generation failed:', error.message); + } +} diff --git a/.build/generateAntlr.ts b/.build/generateAntlr.ts new file mode 100644 index 000000000..4516e09b0 --- /dev/null +++ b/.build/generateAntlr.ts @@ -0,0 +1,5 @@ +import { generateFromConfig } from './antlr-cli.js'; + +export async function generateAntlr() { + await generateFromConfig('./packages/parser/antlr-config.json'); +} diff --git a/.esbuild/build.ts b/.esbuild/build.ts index 72c0af869..05f370a1a 100644 --- a/.esbuild/build.ts +++ b/.esbuild/build.ts @@ -4,6 +4,7 @@ import { packageOptions } from '../.build/common.js'; import { generateLangium } from '../.build/generateLangium.js'; import type { MermaidBuildOptions } from './util.js'; import { defaultOptions, getBuildConfig } from './util.js'; +import { generateAntlr } from '../.build/generateAntlr.js'; const shouldVisualize = process.argv.includes('--visualize'); @@ -95,6 +96,7 @@ const buildTinyMermaid = async () => { const main = async () => { await generateLangium(); + await generateAntlr(); await mkdir('stats', { recursive: true }); const packageNames = Object.keys(packageOptions) as (keyof typeof packageOptions)[]; // it should build `parser` before `mermaid` because it's a dependency diff --git a/.vite/build.ts b/.vite/build.ts index 480dd6b30..96bec34c1 100644 --- a/.vite/build.ts +++ b/.vite/build.ts @@ -10,6 +10,7 @@ import type { TemplateType } from 'rollup-plugin-visualizer/dist/plugin/template import istanbul from 'vite-plugin-istanbul'; import { packageOptions } from '../.build/common.js'; import { generateLangium } from '../.build/generateLangium.js'; +import { generateAntlr } from '../.build/generateAntlr.js'; const visualize = process.argv.includes('--visualize'); const watch = process.argv.includes('--watch'); @@ -123,6 +124,7 @@ const main = async () => { }; await generateLangium(); +await generateAntlr(); if (watch) { await build(getBuildConfig({ minify: false, watch, core: false, entryName: 'parser' })); diff --git a/packages/parser/antlr-config.json b/packages/parser/antlr-config.json new file mode 100644 index 000000000..4e9b2ab20 --- /dev/null +++ b/packages/parser/antlr-config.json @@ -0,0 +1,14 @@ +{ + "projectName": "Mermaid", + "grammars": [ + { + "id": "usecase", + "grammar": "src/language/usecase/Usecase.g4", + "outputDir": "src/language/usecase/generated", + "language": "TypeScript", + "generateVisitor": true, + "generateListener": true + } + ], + "mode": "production" +} diff --git a/packages/parser/package.json b/packages/parser/package.json index 6aa48df7e..a1eee024f 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -20,7 +20,7 @@ "clean": "rimraf dist src/language/generated", "langium:generate": "langium generate", "langium:watch": "langium generate --watch", - "antlr:generate": "cd src/language/usecase && npx antlr-ng -Dlanguage=TypeScript --generate-visitor --generate-listener -o generated Usecase.g4" + "antlr:generate": "tsx ../../.build/antlr-cli.ts antlr-config.json" }, "repository": { "type": "git",