Repository: compiler-explorer/compiler-explorer
Pull request #6852 (2024-09-11 22:06:48)

Pascal parse mess
Supersedes #6850

Fixes a bunch of stuff with filename recognition in both source, link to binary and tree modes.

It's a giant mess because of how fpc works, but it works


Patch:
diff --git a/lib/compilers/pascal-utils.ts b/lib/compilers/pascal-utils.ts
index 564ce9d78..35f131095 100644
--- a/lib/compilers/pascal-utils.ts
+++ b/lib/compilers/pascal-utils.ts
@@ -42,4 +42,14 @@ export class PascalUtils {
 
         return 'example';
     }
+
+    getProgName(source: string) {
+        const re = /\s?program\s+([\w.-]*);/i;
+        const match = source.match(re);
+        if (match) {
+            return match[1];
+        }
+
+        return 'prog';
+    }
 }
diff --git a/lib/compilers/pascal.ts b/lib/compilers/pascal.ts
index ea48cead7..2664f1863 100644
--- a/lib/compilers/pascal.ts
+++ b/lib/compilers/pascal.ts
@@ -69,6 +69,17 @@ export class FPCCompiler extends BaseCompiler {
         return this.asm.process(result.asm, filters);
     }
 
+    override async postProcess(result, outputFilename: string, filters: ParseFiltersAndOutputOptions) {
+        const userSourceFilename = result.inputFilename;
+        const pasFilepath = path.join(result.dirPath, userSourceFilename);
+        const asmDumpFilepath = pasFilepath.substring(0, pasFilepath.length - 3) + 's';
+        return super.postProcess(result, asmDumpFilepath, filters);
+    }
+
+    isTheSameFileProbably(originalName: string, compareTo: string): boolean {
+        return originalName === compareTo || `/${originalName}` === compareTo;
+    }
+
     override postProcessAsm(result, filters?: ParseFiltersAndOutputOptions) {
         if (!result.okToCache) return result;
 
@@ -78,8 +89,17 @@ export class FPCCompiler extends BaseCompiler {
             }
         }
 
-        for (let j = 0; j < result.asm.length; ++j)
+        for (let j = 0; j < result.asm.length; ++j) {
             result.asm[j].text = this.demangler.demangleIfNeeded(result.asm[j].text);
+            if (
+                result.asm[j].source &&
+                result.asm[j].source.file &&
+                !result.asm[j].source.mainsource &&
+                this.isTheSameFileProbably(result.inputFilename, result.asm[j].source.file)
+            ) {
+                result.asm[j].source.mainsource = true;
+            }
+        }
 
         return result;
     }
@@ -100,8 +120,11 @@ export class FPCCompiler extends BaseCompiler {
         return options;
     }
 
-    override getOutputFilename(dirPath: string) {
-        return path.join(dirPath, `${path.basename(this.compileFilename, this.lang.extensions[0])}.s`);
+    override getOutputFilename(dirPath: string, outputFilebase: string, key?: any): string {
+        const inputFilename = this.getMainSourceFilename(key.source);
+        const baseFilename = inputFilename.substring(0, inputFilename.length - 4);
+
+        return path.join(dirPath, `${baseFilename}.s`);
     }
 
     override getExecutableFilename(dirPath: string) {
@@ -119,7 +142,14 @@ export class FPCCompiler extends BaseCompiler {
         filters: ParseFiltersAndOutputOptions,
     ) {
         const dirPath = path.dirname(outputFilename);
-        const execBinary = this.getExecutableFilename(dirPath);
+
+        let execBinary = this.getExecutableFilename(dirPath);
+
+        const inputExt = path.extname(result.inputFilename);
+        if (inputExt.toLowerCase() === '.dpr') {
+            execBinary = path.join(dirPath, path.basename(result.inputFilename, inputExt));
+        }
+
         if (await utils.fileExists(execBinary)) {
             return super.objdump(execBinary, result, maxSize, intelAsm, demangle, staticReloc, dynamicReloc, filters);
         }
@@ -128,20 +158,36 @@ export class FPCCompiler extends BaseCompiler {
     }
 
     static preProcessBinaryAsm(input: string) {
-        const systemInitOffset = input.indexOf('<SYSTEM_$$_init$>');
-        const relevantAsmStartsAt = input.indexOf('...', systemInitOffset);
-        if (relevantAsmStartsAt !== -1) {
-            const lastLinefeedBeforeStart = input.lastIndexOf('\n', relevantAsmStartsAt);
-            if (lastLinefeedBeforeStart === -1) {
-                input = input.substring(0, input.indexOf('00000000004')) + '\n' + input.substring(relevantAsmStartsAt);
+        // todo: write some pascal logic into external asm-parser to only include user code,
+        //  this is currently a giant mess
+
+        fs.writeFileSync('/tmp/fpctest/dump.txt', input);
+
+        const preamble = 'Disassembly of section .text:';
+        const disasmStart = input.indexOf(preamble);
+
+        let newSource = input.substring(0, disasmStart + preamble.length + 2);
+
+        let foundSourceAt = input.indexOf('/app/', disasmStart);
+        while (foundSourceAt !== -1) {
+            const endOfProc = input.indexOf('...', foundSourceAt);
+
+            const lookback = input.lastIndexOf('>:', foundSourceAt);
+            if (lookback === -1) break;
+            const furtherLookback = input.lastIndexOf('\n', lookback);
+            if (furtherLookback === -1) break;
+
+            if (endOfProc === -1) {
+                newSource = newSource + input.substring(furtherLookback) + '\n';
+                break;
             } else {
-                input =
-                    input.substring(0, input.indexOf('00000000004')) +
-                    '\n' +
-                    input.substring(lastLinefeedBeforeStart + 1);
+                newSource = newSource + input.substring(furtherLookback, endOfProc + 3) + '\n';
             }
+
+            foundSourceAt = input.indexOf('/app/', endOfProc + 3);
         }
-        return input;
+
+        return newSource.replaceAll('/app//', '/app/');
     }
 
     override postProcessObjdumpOutput(output) {
@@ -159,19 +205,25 @@ export class FPCCompiler extends BaseCompiler {
         );
     }
 
-    override async writeAllFiles(dirPath: string, source: string, files: any[], filters: ParseFiltersAndOutputOptions) {
+    getMainSourceFilename(source: string) {
         let inputFilename;
         if (this.pasUtils.isProgram(source)) {
-            inputFilename = path.join(dirPath, this.dprFilename);
+            inputFilename = this.pasUtils.getProgName(source) + '.dpr';
         } else {
             const unitName = this.pasUtils.getUnitname(source);
             if (unitName) {
-                inputFilename = path.join(dirPath, unitName + '.pas');
+                inputFilename = unitName + '.pas';
             } else {
-                inputFilename = path.join(dirPath, this.compileFilename);
+                inputFilename = this.compileFilename;
             }
         }
 
+        return inputFilename;
+    }
+
+    override async writeAllFiles(dirPath: string, source: string, files: any[], filters: ParseFiltersAndOutputOptions) {
+        const inputFilename = path.join(dirPath, this.getMainSourceFilename(source));
+
         if (source !== '' || !files) {
             await fs.writeFile(inputFilename, source);
         }
@@ -195,10 +247,14 @@ export class FPCCompiler extends BaseCompiler {
             execOptions = this.getDefaultExecOptions();
         }
 
-        const alreadyHasDPR = path.basename(inputFilename) === this.dprFilename;
+        const alreadyHasDPR = path.extname(inputFilename).toLowerCase() === '.dpr';
         const dirPath = path.dirname(inputFilename);
 
-        const projectFile = path.join(dirPath, this.dprFilename);
+        let projectFile = path.join(dirPath, this.dprFilename);
+        if (alreadyHasDPR) {
+            projectFile = inputFilename;
+        }
+
         execOptions.customCwd = dirPath;
         if (this.nasmPath) {
             execOptions.env = _.clone(process.env) as Record<string, string>;


Test patch:
diff --git a/test/pascal-tests.ts b/test/pascal-tests.ts
index 3c6f14ef9..2caf1c798 100644
--- a/test/pascal-tests.ts
+++ b/test/pascal-tests.ts
@@ -54,9 +54,9 @@ describe('Pascal', () => {
 
     it('Basic compiler setup', () => {
         if (process.platform === 'win32') {
-            expect(compiler.getOutputFilename('/tmp/', 'output.pas')).toEqual('\\tmp\\output.s');
+            expect(compiler.getOutputFilename('/tmp/', 'prog', {source: 'unit prog;\n//etc'})).toEqual('\\tmp\\prog.s');
         } else {
-            expect(compiler.getOutputFilename('/tmp/', 'output.pas')).toEqual('/tmp/output.s');
+            expect(compiler.getOutputFilename('/tmp/', 'prog', {source: 'unit prog;\n//etc'})).toEqual('/tmp/prog.s');
         }
     });