From 532b6c2b576daecb5302fb0a387785906e745f30 Mon Sep 17 00:00:00 2001 From: leftibot Date: Wed, 3 Jun 2026 21:45:23 -0600 Subject: [PATCH] Fix #8695: [LANGUAGE REQUEST]: Add lua as a supported language (#8696) Automated fix by @leftibot. ### What changed > Fix #8695: add Lua as a supported language > Add support for Lua via the reference PUC-Rio interpreter. Disassembly is > produced by `luac -l -l -p`, which writes a verbose bytecode listing to > stdout that the LuaCompiler captures and writes to the output file. The > class exposes overridable hooks (`resolveLuacExe`, `getDisassemblyArgs`) > so alternative implementations such as LuaJIT can plug in a different > bytecode dumper without rewriting the compiler. Production config ships > five Lua releases (5.1.5, 5.2.4, 5.3.6, 5.4.7, 5.5.0) covering the > actively used minor versions. ### Files ``` etc/config/lua.amazon.properties | 27 ++++++ etc/config/lua.defaults.properties | 5 + examples/lua/default.lua | 5 + lib/compilers/_all.ts | 1 + lib/compilers/lua.ts | 182 +++++++++++++++++++++++++++++++++++++ lib/languages.ts | 11 +++ test/lua-tests.ts | 138 ++++++++++++++++++++++++++++ types/languages.interfaces.ts | 1 + 8 files changed, 370 insertions(+) ``` Closes #8695 _Triggered by @lefticus._ --------- Co-authored-by: leftibot Co-authored-by: Matt Godbolt Co-authored-by: Claude Opus 4.8 (1M context) --- etc/config/lua.amazon.properties | 32 ++++++ etc/config/lua.defaults.properties | 6 + examples/lua/default.lua | 5 + lib/compilers/_all.ts | 1 + lib/compilers/lua.ts | 170 +++++++++++++++++++++++++++++ lib/languages.ts | 11 ++ public/logos/lua.png | Bin 0 -> 22918 bytes test/lua-tests.ts | 128 ++++++++++++++++++++++ types/languages.interfaces.ts | 1 + 9 files changed, 354 insertions(+) create mode 100644 etc/config/lua.amazon.properties create mode 100644 etc/config/lua.defaults.properties create mode 100644 examples/lua/default.lua create mode 100644 lib/compilers/lua.ts create mode 100644 public/logos/lua.png create mode 100644 test/lua-tests.ts diff --git a/etc/config/lua.amazon.properties b/etc/config/lua.amazon.properties new file mode 100644 index 000000000..e40ba3945 --- /dev/null +++ b/etc/config/lua.amazon.properties @@ -0,0 +1,32 @@ +compilers=&lua +defaultCompiler=lua550 + +group.lua.compilers=lua515:lua524:lua536:lua547:lua550 +group.lua.isSemVer=true +group.lua.baseName=Lua +group.lua.groupName=Lua (PUC-Rio) +group.lua.compilerType=lua +group.lua.licenseLink=https://www.lua.org/license.html +group.lua.licenseName=MIT License +group.lua.licensePreamble=Copyright (C) 1994-2024 Lua.org, PUC-Rio. + +compiler.lua515.semver=5.1.5 +compiler.lua515.exe=/opt/compiler-explorer/lua-5.1.5/bin/luac +compiler.lua515.interpreter=/opt/compiler-explorer/lua-5.1.5/bin/lua +compiler.lua524.semver=5.2.4 +compiler.lua524.exe=/opt/compiler-explorer/lua-5.2.4/bin/luac +compiler.lua524.interpreter=/opt/compiler-explorer/lua-5.2.4/bin/lua +compiler.lua536.semver=5.3.6 +compiler.lua536.exe=/opt/compiler-explorer/lua-5.3.6/bin/luac +compiler.lua536.interpreter=/opt/compiler-explorer/lua-5.3.6/bin/lua +compiler.lua547.semver=5.4.7 +compiler.lua547.exe=/opt/compiler-explorer/lua-5.4.7/bin/luac +compiler.lua547.interpreter=/opt/compiler-explorer/lua-5.4.7/bin/lua +compiler.lua550.semver=5.5.0 +compiler.lua550.exe=/opt/compiler-explorer/lua-5.5.0/bin/luac +compiler.lua550.interpreter=/opt/compiler-explorer/lua-5.5.0/bin/lua + +supportsBinary=false +supportsExecute=true +interpreted=true +compilerType=lua diff --git a/etc/config/lua.defaults.properties b/etc/config/lua.defaults.properties new file mode 100644 index 000000000..3c0633201 --- /dev/null +++ b/etc/config/lua.defaults.properties @@ -0,0 +1,6 @@ +compilers=/usr/bin/luac +supportsBinary=false +supportsExecute=true +interpreted=true +interpreter=/usr/bin/lua +compilerType=lua diff --git a/examples/lua/default.lua b/examples/lua/default.lua new file mode 100644 index 000000000..09ef7da18 --- /dev/null +++ b/examples/lua/default.lua @@ -0,0 +1,5 @@ +local function square(num) + return num * num +end + +print(square(5)) diff --git a/lib/compilers/_all.ts b/lib/compilers/_all.ts index 1dbbce419..0a15973a6 100644 --- a/lib/compilers/_all.ts +++ b/lib/compilers/_all.ts @@ -109,6 +109,7 @@ export {LFortranCompiler} from './lfortran.js'; export {LLCCompiler} from './llc.js'; export {LLVMmcaTool} from './llvm-mca.js'; export {LLVMMOSCompiler} from './llvm-mos.js'; +export {LuaCompiler} from './lua.js'; export {M68kCompiler} from './m68k.js'; export {MadPascalCompiler} from './madpascal.js'; export {MicroPythonCompiler} from './micropython.js'; diff --git a/lib/compilers/lua.ts b/lib/compilers/lua.ts new file mode 100644 index 000000000..f49425255 --- /dev/null +++ b/lib/compilers/lua.ts @@ -0,0 +1,170 @@ +// Copyright (c) 2026, 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 fs from 'node:fs/promises'; +import path from 'node:path'; + +import type {AsmResultSource, ParsedAsmResultLine} from '../../types/asmresult/asmresult.interfaces.js'; +import type { + CacheKey, + CompilationResult, + ExecutionOptionsWithEnv, +} from '../../types/compilation/compilation.interfaces.js'; +import type {PreliminaryCompilerInfo} from '../../types/compiler.interfaces.js'; +import type {ExecutableExecutionOptions} from '../../types/execution/execution.interfaces.js'; +import type {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js'; +import {BaseCompiler} from '../base-compiler.js'; +import {CompilationEnvironment} from '../compilation-env.js'; +import {BaseParser} from './argument-parsers.js'; + +// Compiler for the reference (PUC-Rio) Lua interpreter. +// +// The configured `compiler.exe` is expected to be `luac`; it is invoked with +// `-l -l -p` to produce a verbose bytecode listing on stdout. Execution uses +// the `lua` interpreter configured via the `interpreter` property. +// +// Alternative Lua implementations (e.g. LuaJIT) can extend this class and +// override `getDisassemblyArgs` to plug in a different bytecode dumper. +export class LuaCompiler extends BaseCompiler { + private readonly interpreterExe: string; + + static get key() { + return 'lua'; + } + + constructor(compilerInfo: PreliminaryCompilerInfo, env: CompilationEnvironment) { + super(compilerInfo, env); + this.compiler.demangler = ''; + this.demanglerClass = null; + // Per-compiler properties aren't inherited through the normal cascade + // (TODO(#7150)), so resolve the interpreter explicitly: compiler-specific, + // then group, then the language-level default. Reading plain 'interpreter' + // would only ever see the top-level value, so every version would execute + // with the same Lua. + this.interpreterExe = + this.compilerProps(`compiler.${this.compiler.id}.interpreter`, '') || + this.compilerProps(`group.${this.compiler.group}.interpreter`, '') || + this.compilerProps('interpreter', ''); + } + + override getArgumentParserClass() { + return BaseParser; + } + + override optionsForFilter(_filters: ParseFiltersAndOutputOptions, _outputFilename: string): string[] { + // luac's listing always goes to stdout; runCompiler handles capturing it. + return []; + } + + override async runCompiler( + compiler: string, + options: string[], + inputFilename: string, + execOptions: ExecutionOptionsWithEnv, + filters?: ParseFiltersAndOutputOptions, + ): Promise { + if (!execOptions) { + execOptions = this.getDefaultExecOptions(); + } + if (!execOptions.customCwd) { + execOptions.customCwd = path.dirname(inputFilename); + } + + const luacArgs = this.getDisassemblyArgs(options, inputFilename); + const result = await this.exec(compiler, luacArgs, execOptions); + + const outputFilename = this.getOutputFilename(path.dirname(inputFilename), this.outputFilebase); + await fs.writeFile(outputFilename, result.stdout); + result.stdout = ''; + + return { + ...this.transformToCompilationResult(result, inputFilename), + languageId: this.getCompilerResultLanguageId(filters), + instructionSet: this.getInstructionSetFromCompilerArgs(luacArgs), + }; + } + + override async handleInterpreting( + key: CacheKey, + executeParameters: ExecutableExecutionOptions, + ): Promise { + if (!this.interpreterExe) { + return super.handleInterpreting(key, executeParameters); + } + // `compiler.exe` points at luac for compilation; swap it to the lua + // interpreter for the duration of the script run. + const originalExe = this.compiler.exe; + this.compiler.exe = this.interpreterExe; + try { + return await super.handleInterpreting(key, executeParameters); + } finally { + this.compiler.exe = originalExe; + } + } + + override async processAsm(result: {asm: string}) { + // luac listing format (Lua 5.x): + // "main (N instructions at 0x...)" + // " 1 [3] VARARGPREP 0" + // The bracketed value is the source line for that bytecode instruction. + const instructionLineRe = /^\s*\d+\s+\[(\d+)\]/; + const functionHeaderRe = /<[^:>]*:(\d+),\d+>/; + + const bytecodeLines = result.asm.split('\n'); + const bytecodeResult: ParsedAsmResultLine[] = []; + let lastLineNo: number | null = null; + let sourceLoc: AsmResultSource | null = null; + + for (const line of bytecodeLines) { + const insnMatch = line.match(instructionLineRe); + const fnMatch = line.match(functionHeaderRe); + + if (insnMatch) { + const lineno = Number.parseInt(insnMatch[1], 10); + lastLineNo = lineno; + sourceLoc = {line: lineno, file: null}; + } else if (fnMatch) { + const lineno = Number.parseInt(fnMatch[1], 10); + lastLineNo = lineno > 0 ? lineno : null; + sourceLoc = {line: lastLineNo, file: null}; + } else if (line) { + sourceLoc = {line: lastLineNo, file: null}; + } else { + lastLineNo = null; + sourceLoc = {line: null, file: null}; + } + + bytecodeResult.push({text: line, source: sourceLoc}); + } + + return {asm: bytecodeResult}; + } + + /** Arguments passed to the bytecode dumper. */ + protected getDisassemblyArgs(_options: string[], inputFilename: string): string[] { + // -l -l : verbose listing (constants, locals, upvalues) + // -p : parse only, do not write a bytecode file + return ['-l', '-l', '-p', inputFilename]; + } +} diff --git a/lib/languages.ts b/lib/languages.ts index 459724cc5..5ab940854 100644 --- a/lib/languages.ts +++ b/lib/languages.ts @@ -629,6 +629,17 @@ const definitions: Record = { previewFilter: null, monacoDisassembly: null, }, + lua: { + name: 'Lua', + monaco: 'lua', + extensions: ['.lua'], + alias: [], + logoFilename: 'lua.png', + logoFilenameDark: null, + formatter: null, + previewFilter: null, + monacoDisassembly: null, + }, modula2: { name: 'Modula-2', monaco: 'modula2', diff --git a/public/logos/lua.png b/public/logos/lua.png new file mode 100644 index 0000000000000000000000000000000000000000..dd98c93d87a39bcbacc3053d27bfcba6092adaf1 GIT binary patch literal 22918 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 literal 0 HcmV?d00001 diff --git a/test/lua-tests.ts b/test/lua-tests.ts new file mode 100644 index 000000000..909ac08b6 --- /dev/null +++ b/test/lua-tests.ts @@ -0,0 +1,128 @@ +// Copyright (c) 2026, 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 {beforeAll, describe, expect, it} from 'vitest'; + +import {CompilationEnvironment} from '../lib/compilation-env.js'; +import {LuaCompiler} from '../lib/compilers/lua.js'; +import {languages} from '../lib/languages.js'; +import {LanguageKey} from '../types/languages.interfaces.js'; +import {makeCompilationEnvironment, makeFakeCompilerInfo} from './utils.js'; + +// Subclass that exposes the protected disassembly hook so tests can validate +// the extension point alternative Lua implementations would use. +class TestLuaCompiler extends LuaCompiler { + public callGetDisassemblyArgs(options: string[], inputFilename: string): string[] { + return this.getDisassemblyArgs(options, inputFilename); + } +} + +const testLanguages = { + lua: {id: 'lua' as LanguageKey}, +}; + +// Obviously-fake paths so nobody mistakes these for real install locations. +const fakeLuacExe = '/fake/test/lua/bin/luac'; + +describe('Lua language definition', () => { + it('is registered with the expected metadata', () => { + expect(languages.lua).toBeDefined(); + expect(languages.lua.id).toBe('lua'); + expect(languages.lua.name).toBe('Lua'); + expect(languages.lua.monaco).toBe('lua'); + expect(languages.lua.extensions[0]).toBe('.lua'); + }); + + it('has a default example', () => { + expect(languages.lua.example).not.toMatch(/something went wrong/i); + expect(languages.lua.example.length).toBeGreaterThan(0); + }); +}); + +describe('LuaCompiler', () => { + let ce: CompilationEnvironment; + const info = { + exe: fakeLuacExe, + remote: { + target: 'foo', + path: 'bar', + cmakePath: 'cmake', + basePath: '/', + }, + lang: testLanguages.lua.id, + }; + + beforeAll(() => { + ce = makeCompilationEnvironment({languages: testLanguages}); + }); + + it('uses BaseParser for argument parsing', () => { + const compiler = new TestLuaCompiler(makeFakeCompilerInfo(info), ce); + const parser = compiler.getArgumentParserClass(); + expect(parser.name).toBe('BaseParser'); + }); + + it('emits no framework options for the lua filter', () => { + const compiler = new TestLuaCompiler(makeFakeCompilerInfo(info), ce); + expect(compiler.optionsForFilter({} as any, '/fake/test/output')).toEqual([]); + }); + + it('builds a luac listing command line for disassembly', () => { + const compiler = new TestLuaCompiler(makeFakeCompilerInfo(info), ce); + expect(compiler.callGetDisassemblyArgs([], '/fake/test/input.lua')).toEqual([ + '-l', + '-l', + '-p', + '/fake/test/input.lua', + ]); + }); + + it('processAsm parses source line numbers from luac listing format', async () => { + const compiler = new TestLuaCompiler(makeFakeCompilerInfo(info), ce); + const sample = [ + 'main (5 instructions at 0x1000)', + '0+ params, 2 slots, 1 upvalue, 0 locals, 1 constant, 1 function', + '\t1\t[3]\tVARARGPREP\t0', + '\t2\t[1]\tCLOSURE \t0 0\t; 0x1100', + '\t3\t[3]\tSETTABUP \t0 0 0\t; _ENV "square"', + '\t4\t[3]\tRETURN \t0 1 1\t; 0 out', + '', + 'function (3 instructions at 0x1100)', + '\t1\t[2]\tMUL \t1 0 0', + '\t2\t[2]\tRETURN1 \t1', + ].join('\n'); + + const {asm} = await compiler.processAsm({asm: sample}); + + const sourceLines = asm.map(line => (line.source ? line.source.line : null)); + // The instruction lines must report the source line in their `[N]` token. + expect(sourceLines).toContain(3); + expect(sourceLines).toContain(1); + expect(sourceLines).toContain(2); + + // Each input line is preserved as text in order. + expect(asm.length).toBe(sample.split('\n').length); + expect(asm[0].text).toBe(sample.split('\n')[0]); + }); +}); diff --git a/types/languages.interfaces.ts b/types/languages.interfaces.ts index 97b8f7255..04a8be8a5 100644 --- a/types/languages.interfaces.ts +++ b/types/languages.interfaces.ts @@ -76,6 +76,7 @@ export type LanguageKey = | 'lean' | 'llvm' | 'llvm_mir' + | 'lua' | 'mlir' | 'modula2' | 'mojo'