diff options
-rw-r--r-- | docs/TurboC.md | 28 | ||||
-rw-r--r-- | docs/turboc.properties | 15 | ||||
-rw-r--r-- | lib/base-compiler.d.ts | 22 | ||||
-rw-r--r-- | lib/compilers/_all.js | 1 | ||||
-rw-r--r-- | lib/compilers/argument-parsers.js | 7 | ||||
-rw-r--r-- | lib/compilers/dosbox-compiler.ts | 168 | ||||
-rw-r--r-- | lib/compilers/dotnet.ts | 3 | ||||
-rw-r--r-- | lib/compilers/turboc.ts | 75 | ||||
-rw-r--r-- | lib/parsers/asm-parser-turboc.js | 119 |
9 files changed, 433 insertions, 5 deletions
diff --git a/docs/TurboC.md b/docs/TurboC.md new file mode 100644 index 000000000..a8f84577f --- /dev/null +++ b/docs/TurboC.md @@ -0,0 +1,28 @@ +# Running Turbo C + +Instructions on how to run Turbo C using Dosbox on Linux. + +## Prerequisites + +To run the Turbo C compiler you will need: + +- Dosbox - the easiest way to install is to use your OS's package manager, e.g. `sudo apt install dosbox` +- Turbo C installation - if you have the 3 installation disks, first install those to a single directory with dosbox + - You will need to setup a directory to function as the `C` drive with a `TC` directory inside. + - Note that it's assumed all files are in uppercase + +## Configuration + +In the `turboc.properties` file you can see an example on how to setup the compiler to work with Compiler Explorer. + +Make sure the `.dosbox` path is correct, as well as the `.root` and `.exe`. The `.root` indicates the root of the `C` +drive, and the `.exe` points to the actual `TCC.EXE` + +## More notes + +Note that Turbo C is C only, so it belongs in your `c.local.properties`. + +Also note that you will immediately get an error with the default example source code, because the compiler doesn't +support `//` comments. + +Also note that in this old C version, you must declare all variables in the first few lines of your functions. diff --git a/docs/turboc.properties b/docs/turboc.properties new file mode 100644 index 000000000..c049f0350 --- /dev/null +++ b/docs/turboc.properties @@ -0,0 +1,15 @@ +compilers=turboc + +group.turboc.groupName=Turbo C +group.turboc.baseName=Turbo C +group.turboc.compilers=turboc201 +group.turboc.compilerType=turboc +group.turboc.isSemVer=true +group.turboc.demangler= +group.turboc.supportsBinary=false +group.turboc.supportsExecute=false + +compiler.turboc201.semver=2.01 +compiler.turboc201.exe=/home/user/tc201/TC/TCC.EXE +compiler.turboc201.root=/home/user/tc201 +compiler.turboc201.dosbox=/usr/bin/dosbox diff --git a/lib/base-compiler.d.ts b/lib/base-compiler.d.ts index 0afec698a..fb45df1d6 100644 --- a/lib/base-compiler.d.ts +++ b/lib/base-compiler.d.ts @@ -27,10 +27,26 @@ export declare class BaseCompiler { public compiler; public lang; public outputFilebase: string; + protected mtime; + protected env; + protected compileFilename; + protected asm; + protected getSharedLibraryPathsAsArguments(libraries: object[], libDownloadPath: string); + protected getSharedLibraryPathsAsLdLibraryPaths(libraries: object[]); + protected getCompilerCacheKey(compiler: string, args: string[], options: object); + protected async execCompilerCached(compiler, args, options); + protected async newTempDir(); + protected filename(fn: string): string; + protected optionsForFilter(filters: object, outputFilename: string): string[]; + protected getExtraFilepath(dirPath: string, filename: string): string; + protected async writeAllFiles(dirPath: string, source: string, files: any[], filters: object); + protected async writeMultipleFiles(files: any[], dirPath: string): Promise<any[]>; public compilerProps: (key: string) => string; - public getOutputFilename(path: string, filename: string): string; - public exec(filepath: string, args: string[], execOptions); + public getOutputFilename(dirPath: string, outputFilebase: string, key?: object): string; + public async exec(filepath: string, args: string[], execOptions); public parseCompilationOutput(result, filename: string); public getDefaultExecOptions(); - public runCompiler(compiler: string, args: string[], filename: string, execOptions); + public async runCompiler(compiler: string, args: string[], filename: string, execOptions); + public async getVersion(); + protected getArgumentParser(): class; } diff --git a/lib/compilers/_all.js b/lib/compilers/_all.js index 4ddf9d498..44fd91c0c 100644 --- a/lib/compilers/_all.js +++ b/lib/compilers/_all.js @@ -87,3 +87,4 @@ export {WslVcCompiler} from './wsl-vc'; export {ZigCompiler} from './zig'; export {ZigCC} from './zigcc'; export {ZigCXX} from './zigcxx'; +export {TurboCCompiler} from './turboc'; diff --git a/lib/compilers/argument-parsers.js b/lib/compilers/argument-parsers.js index 1047677ce..10255df3e 100644 --- a/lib/compilers/argument-parsers.js +++ b/lib/compilers/argument-parsers.js @@ -351,3 +351,10 @@ export class TypeScriptNativeParser extends BaseParser { return compiler; } } + +export class TurboCParser extends BaseParser { + static async parse(compiler) { + await TurboCParser.getOptions(compiler, ''); + return compiler; + } +} diff --git a/lib/compilers/dosbox-compiler.ts b/lib/compilers/dosbox-compiler.ts new file mode 100644 index 000000000..3bb784e15 --- /dev/null +++ b/lib/compilers/dosbox-compiler.ts @@ -0,0 +1,168 @@ +// Copyright (c) 2022, Compiler Explorer Authors +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +import path from 'path'; + +import fs from 'fs-extra'; + +import {BaseCompiler} from '../base-compiler'; +import * as exec from '../exec'; +import {logger} from '../logger'; +import {TurboCAsmParser} from '../parsers/asm-parser-turboc'; + +export class DosboxCompiler extends BaseCompiler { + private readonly dosbox: string; + private readonly root: string; + + constructor(compilerInfo, env) { + super(compilerInfo, env); + + this.dosbox = this.compilerProps(`compiler.${this.compiler.id}.dosbox`); + this.root = this.compilerProps(`compiler.${this.compiler.id}.root`); + this.asm = new TurboCAsmParser(this.compilerProps); + } + + protected override async writeMultipleFiles(files: any[], dirPath: string): Promise<any[]> { + const filesToWrite: any[] = []; + + for (const file of files) { + if (!file.filename) throw new Error('One of more files do not have a filename'); + + const fullpath = this.getExtraFilepath(dirPath, file.filename); + const contents = file.contents.replaceAll(/\n/g, '\r\n'); + filesToWrite.push(fs.outputFile(fullpath, contents)); + } + + return Promise.all(filesToWrite); + } + + protected override async writeAllFiles(dirPath: string, source: string, files: any[], filters: object) { + if (!source) throw new Error(`File ${this.compileFilename} has no content or file is missing`); + + const inputFilename = path.join(dirPath, this.compileFilename); + await fs.writeFile(inputFilename, source.replaceAll(/\n/g, '\r\n')); + + if (files && files.length > 0) { + (filters as any).dontMaskFilenames = true; + + await this.writeMultipleFiles(files, dirPath); + } + + return { + inputFilename, + }; + } + + private getDosboxArgs(tempDir: string, compileArgs: string[]) { + const binPath = path.relative(this.root, path.dirname(this.compiler.exe)); + const exeName = path.basename(this.compiler.exe).replace(/\.exe$/i, ''); + return [ + '-c', + `mount c ${this.root}`, + '-c', + `mount d ${tempDir}`, + '-c', + `PATH=%PATH%;C:\\${binPath}`, + '-c', + 'd:', + '-c', + `${exeName} ${compileArgs.join(' ')} > STDOUT.TXT`, + '-c', + 'exit', + ]; + } + + private getDosboxEnv() { + return { + SDL_VIDEODRIVER: 'dummy', + }; + } + + protected override async execCompilerCached(compiler, args, options) { + if (this.mtime === null) { + throw new Error('Attempt to access cached compiler before initialise() called'); + } + if (!options) { + options = this.getDefaultExecOptions(); + options.timeoutMs = 0; + options.ldPath = this.getSharedLibraryPathsAsLdLibraryPaths([]); + } + + const key = this.getCompilerCacheKey(compiler, args, options); + let result = await this.env.compilerCacheGet(key); + if (!result) { + result = await this.env.enqueue(async () => this.exec(compiler, args, options)); + if (result.okToCache) { + this.env + .compilerCachePut(key, result) + .then(() => { + // Do nothing, but we don't await here. + }) + .catch(e => { + logger.info('Uncaught exception caching compilation results', e); + }); + } + } + + return result; + } + + public override async exec(filepath: string, args: string[], execOptions: any) { + if (!execOptions) { + execOptions = this.getDefaultExecOptions(); + } + + execOptions.env = this.getDosboxEnv(); + + if (!execOptions.customCwd) { + execOptions.customCwd = await this.newTempDir(); + } + + const tempDir = execOptions.customCwd; + const fullArgs = this.getDosboxArgs(tempDir, args); + + const result = await exec.executeDirect(this.dosbox, fullArgs, execOptions); + + const stdoutFilename = path.join(tempDir, 'STDOUT.TXT'); + const stdout = await fs.readFile(stdoutFilename); + (result as any).stdout = stdout.toString('utf8'); + + return result; + } + + public override async runCompiler(compiler, options, inputFilename, execOptions) { + return super.runCompiler( + compiler, + options.map(option => { + if (option === inputFilename) { + return path.basename(option); + } else { + return option; + } + }), + inputFilename, + execOptions, + ); + } +} diff --git a/lib/compilers/dotnet.ts b/lib/compilers/dotnet.ts index 8bbcdc4a5..12e607bfe 100644 --- a/lib/compilers/dotnet.ts +++ b/lib/compilers/dotnet.ts @@ -38,7 +38,6 @@ class DotNetCompiler extends BaseCompiler { private clrBuildDir: string; private additionalSources: string; private langVersion: string; - protected asm: DotNetAsmParser; constructor(compilerInfo, env) { super(compilerInfo, env); @@ -174,7 +173,7 @@ class DotNetCompiler extends BaseCompiler { return compilerResult; } - optionsForFilter() { + override optionsForFilter() { return this.compilerOptions; } diff --git a/lib/compilers/turboc.ts b/lib/compilers/turboc.ts new file mode 100644 index 000000000..757526da1 --- /dev/null +++ b/lib/compilers/turboc.ts @@ -0,0 +1,75 @@ +// Copyright (c) 2022, Compiler Explorer Authors +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +import path from 'path'; + +import {logger} from '../logger'; + +import {TurboCParser} from './argument-parsers'; +import {DosboxCompiler} from './dosbox-compiler'; + +export class TurboCCompiler extends DosboxCompiler { + static get key() { + return 'turboc'; + } + + override optionsForFilter() { + return ['-B']; + } + + override getSharedLibraryPathsAsArguments(libraries: object[], libDownloadPath: string) { + return []; + } + + override getSharedLibraryPathsAsLdLibraryPaths(libraries: object[]) { + return []; + } + + override async getVersion() { + logger.info(`Gathering ${this.compiler.id} version information on ${this.compiler.exe}...`); + if (this.compiler.explicitVersion) { + logger.debug(`${this.compiler.id} has forced version output: ${this.compiler.explicitVersion}`); + return {stdout: [this.compiler.explicitVersion], stderr: [], code: 0}; + } + const execOptions = this.getDefaultExecOptions(); + const versionFlag = ''; + execOptions.timeoutMs = 0; + execOptions.ldPath = this.getSharedLibraryPathsAsLdLibraryPaths([]); + + try { + return this.execCompilerCached(this.compiler.exe, [versionFlag], execOptions); + } catch (err) { + logger.error(`Unable to get version for compiler '${this.compiler.exe}' - ${err}`); + return null; + } + } + + override getOutputFilename(dirPath: string, outputFilebase: string, key?: object) { + return path.join(dirPath, 'EXAMPLE.ASM'); + } + + override getArgumentParser() { + return TurboCParser; + } +} diff --git a/lib/parsers/asm-parser-turboc.js b/lib/parsers/asm-parser-turboc.js new file mode 100644 index 000000000..511a5499a --- /dev/null +++ b/lib/parsers/asm-parser-turboc.js @@ -0,0 +1,119 @@ +// Copyright (c) 2022, Compiler Explorer Authors +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +import {filter} from 'underscore'; + +import * as utils from '../utils'; + +import {AsmParser} from './asm-parser'; + +export class TurboCAsmParser extends AsmParser { + constructor(compilerProps) { + super(compilerProps); + this.asmBinaryParser = new AsmParser(compilerProps); + + this.filestart = /^\s+\?debug\s+S\s"(.+)"/; + this.linestart = /^;\s+\?debug\s+L\s(\d+)/; + + this.procbegin = /^(\w+)\sproc\s*near/; + this.procend = /^(\w+)\sendp/; + } + + processAsm(asm, filters) { + if (filter.binary) return this.asmBinaryParser.processBinaryAsm(asm, filters); + + let currentfile = ''; + let currentline = 0; + let currentproc = ''; + + const asmLines = []; + let isDirective = true; + asm = asm.replace(/\u001A$/, ''); + + utils.eachLine(asm, line => { + const procmatch = line.match(this.procbegin); + if (procmatch) { + currentproc = procmatch[1]; + isDirective = false; + } + + const endprocmatch = line.match(this.procend); + if (endprocmatch) { + currentproc = ''; + currentline = 0; + isDirective = false; + } + + const filematch = line.match(this.filestart); + if (filematch) { + currentfile = filematch[1]; + isDirective = true; + } + + const linematch = line.match(this.linestart); + if (linematch) { + currentline = linematch[1]; + isDirective = true; + } + + let source = null; + if (currentfile && currentline) { + if (filters.dontMaskFilenames) { + source = { + file: currentfile, + line: parseInt(currentline), + }; + } else { + source = { + file: null, + line: parseInt(currentline), + }; + } + } + + if (currentproc) { + if (filters.directives && isDirective) { + isDirective = false; + return; + } + + asmLines.push({ + text: line, + source, + }); + } else if (!filters.directives || !isDirective) { + isDirective = true; + + asmLines.push({ + text: line, + source, + }); + } + }); + + return { + asm: asmLines, + }; + } +} |