Pārlūkot izejas kodu

Pascal compiler service: Blaise backend alongside fpc

Eric Streit 3 dienas atpakaļ
vecāks
revīzija
7166301653

+ 10 - 2
extensions/pascal-compiler/src/common/compiler.ts

@@ -51,7 +51,11 @@ export function formatBytes(bytes: number): string {
   return `${(kb / 1024).toFixed(1)} MB`;
 }
 
+export type CompilerType = 'fpc' | 'blaise';
+
 export interface CompilerOptions {
+  /** Compiler backend: "fpc" (default) or the Blaise compiler. */
+  type?: CompilerType;
   /** Compiler executable, e.g. "fpc" or an absolute path. */
   path: string;
   options: string[];
@@ -100,6 +104,7 @@ export function parseProjectFile(root: string, jsonText: string): PascalProject
     throw new Error('pascal.json: missing "main".');
   }
   const compiler = raw.compiler ?? {};
+  const type = compiler.type === 'blaise' ? 'blaise' as const : 'fpc' as const;
   const options = Array.isArray(compiler.options)
     ? compiler.options.filter((o): o is string => typeof o === 'string')
     : [];
@@ -110,7 +115,8 @@ export function parseProjectFile(root: string, jsonText: string): PascalProject
     main: raw.main,
     output: typeof raw.output === 'string' ? raw.output : undefined,
     compiler: {
-      path: explicitPath ? (compiler.path as string) : 'fpc',
+      type,
+      path: explicitPath ? (compiler.path as string) : (type === 'blaise' ? 'blaise' : 'fpc'),
       options,
     },
     compilerPathExplicit: explicitPath,
@@ -133,7 +139,9 @@ export interface ProvidedTask {
 /** Pure task list for a project (tested headlessly; the Theia provider wraps these). */
 export function buildProvidedTasks(project: PascalProject): ProvidedTask[] {
   const out = defaultOutput(project);
-  const buildArgs = ['-g', ...project.compiler.options, '-o' + out, project.main];
+  const buildArgs = project.compiler.type === 'blaise'
+    ? ['--source', project.main, '--output', out, ...project.compiler.options]
+    : ['-g', ...project.compiler.options, '-o' + out, project.main];
   return [
     { label: 'Pascal: Build', group: { kind: 'build', isDefault: true }, command: project.compiler.path, args: buildArgs },
     { label: 'Pascal: Clean', group: 'clean', command: 'rm', args: ['-f', out] },

+ 50 - 6
extensions/pascal-compiler/src/node/compiler-service.ts

@@ -72,6 +72,41 @@ export function runProcess(command: string, args: string[], cwd: string): Promis
 
 const FPC_LINE = /^(.+?)\((\d+),(\d+)\)\s+(Error|Warning|Fatal|Note|Hint):\s+(.*)$/;
 
+/** Parse Blaise diagnostics (`Parse error: msg at line L col C in F`). */
+export function parseBlaiseDiagnostics(projectRoot: string, main: string, output: string): BuildDiagnostic[] {
+  const diagnostics: BuildDiagnostic[] = [];
+  const re = /^(Parse|Semantic) error:\s*(.*?)\s+at line (\d+) col (\d+) in (\S+)\s*$/gm;
+  let m: RegExpExecArray | null;
+  while ((m = re.exec(output)) !== null) {
+    diagnostics.push({
+      file: resolveBlaiseFile(projectRoot, main, m[5]),
+      line: Math.max(0, Number(m[3]) - 1),
+      character: Math.max(0, Number(m[4]) - 1),
+      message: `${m[1]} error: ${m[2].trim()}`,
+      severity: 'error',
+    });
+  }
+  return diagnostics;
+}
+
+/** Blaise reports a bare module name as often as a file name. */
+function resolveBlaiseFile(projectRoot: string, main: string, reported: string): string {
+  const direct = path.join(projectRoot, reported);
+  try {
+    if (fs.existsSync(direct) && fs.statSync(direct).isFile()) return reported;
+  } catch {
+    // Fall through to the guesses below.
+  }
+  for (const candidate of [`${reported}.pas`, `${reported}.pp`]) {
+    try {
+      if (fs.existsSync(path.join(projectRoot, candidate))) return candidate;
+    } catch {
+      // Keep guessing.
+    }
+  }
+  return main;
+}
+
 /** Parse FPC-style diagnostics; context/caret lines are ignored. */
 export function parseFpcDiagnostics(output: string): BuildDiagnostic[] {
   const diagnostics: BuildDiagnostic[] = [];
@@ -94,10 +129,15 @@ export function parseFpcDiagnostics(output: string): BuildDiagnostic[] {
 export class FreePascalCompilerService implements PascalCompiler {
   async detect(compilerPath = 'fpc'): Promise<CompilerInfo | undefined> {
     try {
-      const result = await runProcess(compilerPath, ['-iV'], process.cwd());
-      if (result.exitCode !== 0) return undefined;
-      const version = (result.output.split('\n')[0] ?? '').trim();
-      return { path: compilerPath, version };
+      const probe = await runProcess(compilerPath, ['-iV'], process.cwd());
+      const first = (probe.output.split('\n')[0] ?? '').trim();
+      if (probe.exitCode === 0 && /^\d+\.\d+/.test(first)) return { path: compilerPath, version: first };
+      const mark = probe.output.split('\n').find(l => l.includes('Blaise Compiler'));
+      if (mark) return { path: compilerPath, version: mark.trim() };
+      const help = await runProcess(compilerPath, ['--help'], process.cwd());
+      const banner = help.output.split('\n').find(l => l.includes('Blaise Compiler'));
+      if (banner) return { path: compilerPath, version: banner.trim() };
+      return undefined;
     } catch {
       return undefined;
     }
@@ -106,7 +146,9 @@ export class FreePascalCompilerService implements PascalCompiler {
   async build(project: PascalProject): Promise<BuildResult> {
     const out = defaultOutput(project);
     await fs.promises.mkdir(path.dirname(path.join(project.root, out)), { recursive: true });
-    const args = ['-g', ...project.compiler.options, '-o' + out, project.main];
+    const args = project.compiler.type === 'blaise'
+      ? ['--source', project.main, '--output', out, ...project.compiler.options]
+      : ['-g', ...project.compiler.options, '-o' + out, project.main];
     const command = [project.compiler.path, ...args].join(' ');
     let result: ProcessResult;
     try {
@@ -125,7 +167,9 @@ export class FreePascalCompilerService implements PascalCompiler {
       exitCode: result.exitCode,
       command,
       output: result.output,
-      diagnostics: parseFpcDiagnostics(result.output),
+      diagnostics: project.compiler.type === 'blaise'
+        ? parseBlaiseDiagnostics(project.root, project.main, result.output)
+        : parseFpcDiagnostics(result.output),
       stats: success ? await collectBuildStats(project, out) : undefined,
     };
   }