Skip to content

Commit 7497dbf

Browse files
authored
Lua 5.0 support (#1263)
* feat: add flag for lua 5.0 target * feat(5.0): implement spread arguments * fix(5.0): in general, cases that apply to 5.1 should also apply to 5.0 * style: run prettier * test(5.0): share the expected results of the obvious cases with 5.1 * feat(5.0): work around the missing '#' and '%' operators * fix: log the correct lua version for unsupported features * style: condense spread table if branch * test: add a special case for modulo for 5.0 * style: fix indentation for test function snippet * feat(5.0): implement spread element correctly * fix: report correct lua version in bitops diagnostic message * test: refactor spread tests to accomodate 5.0 * Revert "test: add a special case for modulo for 5.0" This reverts commit fac16d2. * test: test basic numeric expressions against all versions * style: run prettier * refactor: use the convenience function to make an unpack call * style(lualib): use @noselfinfile for the modulo polyfill * fix: make bundling compatible with lua 5.0 * fix(5.0): optimized single element array push for 5.0 * fix(5.0): fix constructor w/ instance fields for 5.0 * fix(lualib): count varargs correctly with lua 5.0 * fix(lualib): tracebacks don't support threads with lua 5.0 * fix(lualib): work around missing math.modf() in lua 5.0 * fix(lualib): work around missing string.match() in lua 5.0 * fix(lualib): detect lua 5.0 minor versions correctly * refactor(lualib): lua 5.0 check can be a constant instead of a function * fix(5.0): get json test library to load * style: rename 5.0 json file * style: use a better definition of infinity * feat(5.0): don't transform infinity to math.huge, which doesn't exist in 5.0 * chore: bump lua-types version * feat: build another copy of lualib for 5.0 * style: run prettier * fix: small issues discovered in testing * chore: bump lua-wasm-bindings version * fix(lualib): select() call was not using vararg optimization, breaking some spead cases * test: use inline lualib imports to get around problems with preloading the entire bundle * test: remove old snapshots * fix: json files can't contain comments * Revert "fix: json files can't contain comments" This reverts commit 581fb52. Turns out they CAN contain comments, when they're named tsconfig.json! * test: exclude lualib-lua50 from coverage * style: move 5.0 tsconfig.json to existing lualib dir * style: refactor lualib resolution into common function * style: factor out table length transpilation into common function * style: format 5.0 and non-5.0 require shims * test: remove obsolete lua 5.0 arg test * refactor(lualib): use builtins for the 5.0 branches instead of a version check * refactor(lualib): move 5.0-specific to its own directory * chore: add tsconfig to eslint * refactor(lualib): reorganize dist/lualib directories to better support future targets * fix(lualib): cache should account for multiple possible versions * Revert "test: use inline lualib imports to get around problems with preloading the entire bundle" This reverts commit 8d9ce98. * chore: adjust jest and npm configs for new lualib structure * refactor: cache lualib by file path so the universal target isn't cached several times * chore: bump lua-types * refactor: deduplicate require boilerplate * refactor: simplify universal definition of TS_Match * style: rename TS_Modulo -> TS_Modulo50 * refactor(lualib): use unpack helper to avoid code duplication in Generator.ts * refactor(lualib): eliminate duplication in SourceMapTraceBack.ts * chore(lualib): fix comment in CountVarargs.ts * refactor(lualib): eliminate duplication in Error.ts * fix(lualib): modules info should be cached per-target * refactor(lualib): eliminate duplication in NumbertToString.ts * refactor(lualib): modules info can also be cached by file path to avoid duplication * style: for locating lualib features, it makes more sense to default to the universal target * style: delete unnecessary noselfinfile * chore: add 5.0 target to tsconfig schema
1 parent 0d7e54b commit 7497dbf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+943
-457
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module.exports = {
55
project: [
66
"test/tsconfig.json",
77
"src/lualib/tsconfig.json",
8+
"src/lualib/tsconfig.lua50.json",
89
"benchmark/tsconfig.json",
910
"language-extensions/tsconfig.json",
1011
],

package-lock.json

Lines changed: 36 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818
"dist/**/*.js",
1919
"dist/**/*.lua",
2020
"dist/**/*.ts",
21-
"dist/lualib/*.json",
21+
"dist/lualib/**/*.json",
2222
"language-extensions/**/*.ts"
2323
],
2424
"main": "dist/index.js",
2525
"types": "dist/index.d.ts",
2626
"scripts": {
2727
"build": "tsc && npm run build-lualib",
28-
"build-lualib": "node dist/tstl.js -p src/lualib/tsconfig.json",
28+
"build-lualib": "node dist/tstl.js -p src/lualib/tsconfig.json && node dist/tstl.js -p src/lualib/tsconfig.lua50.json",
2929
"pretest": "npm run lint && npm run check:language-extensions && npm run build-lualib",
3030
"test": "jest",
3131
"lint": "npm run lint:eslint && npm run lint:prettier",
@@ -65,8 +65,8 @@
6565
"javascript-stringify": "^2.0.1",
6666
"jest": "^28.1.3",
6767
"jest-circus": "^29.0.1",
68-
"lua-types": "^2.12.1",
69-
"lua-wasm-bindings": "^0.2.2",
68+
"lua-types": "^2.12.2",
69+
"lua-wasm-bindings": "^0.3.1",
7070
"prettier": "^2.3.2",
7171
"ts-jest": "^28.0.8",
7272
"ts-node": "^10.9.1",

src/CompilerOptions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export enum LuaLibImportKind {
5353

5454
export enum LuaTarget {
5555
Universal = "universal",
56+
Lua50 = "5.0",
5657
Lua51 = "5.1",
5758
Lua52 = "5.2",
5859
Lua53 = "5.3",

src/LuaAST.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export enum SyntaxKind {
3232
NumericLiteral,
3333
NilKeyword,
3434
DotsKeyword,
35+
ArgKeyword,
3536
TrueKeyword,
3637
FalseKeyword,
3738
FunctionExpression,
@@ -544,6 +545,18 @@ export function createDotsLiteral(tsOriginal?: ts.Node): DotsLiteral {
544545
return createNode(SyntaxKind.DotsKeyword, tsOriginal) as DotsLiteral;
545546
}
546547

548+
export interface ArgLiteral extends Expression {
549+
kind: SyntaxKind.ArgKeyword;
550+
}
551+
552+
export function isArgLiteral(node: Node): node is ArgLiteral {
553+
return node.kind === SyntaxKind.ArgKeyword;
554+
}
555+
556+
export function createArgLiteral(tsOriginal?: ts.Node): ArgLiteral {
557+
return createNode(SyntaxKind.ArgKeyword, tsOriginal) as ArgLiteral;
558+
}
559+
547560
// StringLiteral / NumberLiteral
548561
// TODO TS uses the export interface "LiteralLikeNode" with a "text: string" member
549562
// but since we don't parse from text I think we can simplify by just having a value member
@@ -581,10 +594,11 @@ export function createStringLiteral(value: string, tsOriginal?: ts.Node): String
581594

582595
export function isLiteral(
583596
node: Node
584-
): node is NilLiteral | DotsLiteral | BooleanLiteral | NumericLiteral | StringLiteral {
597+
): node is NilLiteral | DotsLiteral | ArgLiteral | BooleanLiteral | NumericLiteral | StringLiteral {
585598
return (
586599
isNilLiteral(node) ||
587600
isDotsLiteral(node) ||
601+
isArgLiteral(node) ||
588602
isBooleanLiteral(node) ||
589603
isNumericLiteral(node) ||
590604
isStringLiteral(node)

src/LuaLib.ts

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as path from "path";
22
import { EmitHost } from "./transpilation";
33
import * as lua from "./LuaAST";
4+
import { LuaTarget } from "./CompilerOptions";
45

56
export enum LuaLibFeature {
67
ArrayConcat = "ArrayConcat",
@@ -34,6 +35,7 @@ export enum LuaLibFeature {
3435
Class = "Class",
3536
ClassExtends = "ClassExtends",
3637
CloneDescriptor = "CloneDescriptor",
38+
CountVarargs = "CountVarargs",
3739
Decorate = "Decorate",
3840
DecorateParam = "DecorateParam",
3941
Delete = "Delete",
@@ -46,8 +48,11 @@ export enum LuaLibFeature {
4648
Iterator = "Iterator",
4749
LuaIteratorSpread = "LuaIteratorSpread",
4850
Map = "Map",
51+
Match = "Match",
4952
MathAtan2 = "MathAtan2",
53+
MathModf = "MathModf",
5054
MathSign = "MathSign",
55+
Modulo50 = "Modulo50",
5156
New = "New",
5257
Number = "Number",
5358
NumberIsFinite = "NumberIsFinite",
@@ -107,23 +112,28 @@ export interface LuaLibFeatureInfo {
107112
}
108113
export type LuaLibModulesInfo = Record<LuaLibFeature, LuaLibFeatureInfo>;
109114

115+
export function resolveLuaLibDir(luaTarget: LuaTarget) {
116+
const luaLibDir = luaTarget === LuaTarget.Lua50 ? "5.0" : "universal";
117+
return path.resolve(__dirname, path.join("..", "dist", "lualib", luaLibDir));
118+
}
119+
110120
export const luaLibModulesInfoFileName = "lualib_module_info.json";
111-
let luaLibModulesInfo: LuaLibModulesInfo | undefined;
112-
export function getLuaLibModulesInfo(emitHost: EmitHost): LuaLibModulesInfo {
113-
if (luaLibModulesInfo === undefined) {
114-
const lualibPath = path.resolve(__dirname, `../dist/lualib/${luaLibModulesInfoFileName}`);
121+
const luaLibModulesInfo = new Map<string, LuaLibModulesInfo>();
122+
export function getLuaLibModulesInfo(luaTarget: LuaTarget, emitHost: EmitHost): LuaLibModulesInfo {
123+
const lualibPath = path.join(resolveLuaLibDir(luaTarget), luaLibModulesInfoFileName);
124+
if (!luaLibModulesInfo.has(lualibPath)) {
115125
const result = emitHost.readFile(lualibPath);
116126
if (result !== undefined) {
117-
luaLibModulesInfo = JSON.parse(result) as LuaLibModulesInfo;
127+
luaLibModulesInfo.set(lualibPath, JSON.parse(result) as LuaLibModulesInfo);
118128
} else {
119129
throw new Error(`Could not load lualib dependencies from '${lualibPath}'`);
120130
}
121131
}
122-
return luaLibModulesInfo;
132+
return luaLibModulesInfo.get(lualibPath) as LuaLibModulesInfo;
123133
}
124134

125-
export function readLuaLibFeature(feature: LuaLibFeature, emitHost: EmitHost): string {
126-
const featurePath = path.resolve(__dirname, `../dist/lualib/${feature}.lua`);
135+
export function readLuaLibFeature(feature: LuaLibFeature, luaTarget: LuaTarget, emitHost: EmitHost): string {
136+
const featurePath = path.join(resolveLuaLibDir(luaTarget), `${feature}.lua`);
127137
const luaLibFeature = emitHost.readFile(featurePath);
128138
if (luaLibFeature === undefined) {
129139
throw new Error(`Could not load lualib feature from '${featurePath}'`);
@@ -133,8 +143,9 @@ export function readLuaLibFeature(feature: LuaLibFeature, emitHost: EmitHost): s
133143

134144
export function resolveRecursiveLualibFeatures(
135145
features: Iterable<LuaLibFeature>,
146+
luaTarget: LuaTarget,
136147
emitHost: EmitHost,
137-
luaLibModulesInfo: LuaLibModulesInfo = getLuaLibModulesInfo(emitHost)
148+
luaLibModulesInfo: LuaLibModulesInfo = getLuaLibModulesInfo(luaTarget, emitHost)
138149
): LuaLibFeature[] {
139150
const loadedFeatures = new Set<LuaLibFeature>();
140151
const result: LuaLibFeature[] = [];
@@ -158,19 +169,27 @@ export function resolveRecursiveLualibFeatures(
158169
return result;
159170
}
160171

161-
export function loadInlineLualibFeatures(features: Iterable<LuaLibFeature>, emitHost: EmitHost): string {
172+
export function loadInlineLualibFeatures(
173+
features: Iterable<LuaLibFeature>,
174+
luaTarget: LuaTarget,
175+
emitHost: EmitHost
176+
): string {
162177
let result = "";
163178

164-
for (const feature of resolveRecursiveLualibFeatures(features, emitHost)) {
165-
const luaLibFeature = readLuaLibFeature(feature, emitHost);
179+
for (const feature of resolveRecursiveLualibFeatures(features, luaTarget, emitHost)) {
180+
const luaLibFeature = readLuaLibFeature(feature, luaTarget, emitHost);
166181
result += luaLibFeature + "\n";
167182
}
168183

169184
return result;
170185
}
171186

172-
export function loadImportedLualibFeatures(features: Iterable<LuaLibFeature>, emitHost: EmitHost): lua.Statement[] {
173-
const luaLibModuleInfo = getLuaLibModulesInfo(emitHost);
187+
export function loadImportedLualibFeatures(
188+
features: Iterable<LuaLibFeature>,
189+
luaTarget: LuaTarget,
190+
emitHost: EmitHost
191+
): lua.Statement[] {
192+
const luaLibModuleInfo = getLuaLibModulesInfo(luaTarget, emitHost);
174193

175194
const imports = Array.from(features).flatMap(feature => luaLibModuleInfo[feature].exports);
176195

@@ -196,17 +215,17 @@ export function loadImportedLualibFeatures(features: Iterable<LuaLibFeature>, em
196215
return statements;
197216
}
198217

199-
let luaLibBundleContent: string;
200-
export function getLuaLibBundle(emitHost: EmitHost): string {
201-
if (luaLibBundleContent === undefined) {
202-
const lualibPath = path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua");
218+
const luaLibBundleContent = new Map<string, string>();
219+
export function getLuaLibBundle(luaTarget: LuaTarget, emitHost: EmitHost): string {
220+
const lualibPath = path.join(resolveLuaLibDir(luaTarget), "lualib_bundle.lua");
221+
if (!luaLibBundleContent.has(lualibPath)) {
203222
const result = emitHost.readFile(lualibPath);
204223
if (result !== undefined) {
205-
luaLibBundleContent = result;
224+
luaLibBundleContent.set(lualibPath, result);
206225
} else {
207226
throw new Error(`Could not load lualib bundle from '${lualibPath}'`);
208227
}
209228
}
210229

211-
return luaLibBundleContent;
230+
return luaLibBundleContent.get(lualibPath) as string;
212231
}

src/LuaPrinter.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as path from "path";
22
import { Mapping, SourceMapGenerator, SourceNode } from "source-map";
33
import * as ts from "typescript";
4-
import { CompilerOptions, isBundleEnabled, LuaLibImportKind } from "./CompilerOptions";
4+
import { CompilerOptions, isBundleEnabled, LuaLibImportKind, LuaTarget } from "./CompilerOptions";
55
import * as lua from "./LuaAST";
66
import { loadInlineLualibFeatures, LuaLibFeature, loadImportedLualibFeatures } from "./LuaLib";
77
import { isValidLuaIdentifier, shouldAllowUnicode } from "./transformation/utils/safe-names";
@@ -233,14 +233,17 @@ export class LuaPrinter {
233233
sourceChunks.push(tstlHeader);
234234
}
235235

236+
const luaTarget = this.options.luaTarget ?? LuaTarget.Universal;
236237
const luaLibImport = this.options.luaLibImport ?? LuaLibImportKind.Require;
237238
if (luaLibImport === LuaLibImportKind.Require && file.luaLibFeatures.size > 0) {
238239
// Import lualib features
239-
sourceChunks = this.printStatementArray(loadImportedLualibFeatures(file.luaLibFeatures, this.emitHost));
240+
sourceChunks = this.printStatementArray(
241+
loadImportedLualibFeatures(file.luaLibFeatures, luaTarget, this.emitHost)
242+
);
240243
} else if (luaLibImport === LuaLibImportKind.Inline && file.luaLibFeatures.size > 0) {
241244
// Inline lualib features
242245
sourceChunks.push("-- Lua Library inline imports\n");
243-
sourceChunks.push(loadInlineLualibFeatures(file.luaLibFeatures, this.emitHost));
246+
sourceChunks.push(loadInlineLualibFeatures(file.luaLibFeatures, luaTarget, this.emitHost));
244247
sourceChunks.push("-- End of Lua Library inline imports\n");
245248
}
246249

@@ -583,6 +586,8 @@ export class LuaPrinter {
583586
return this.printNilLiteral(expression as lua.NilLiteral);
584587
case lua.SyntaxKind.DotsKeyword:
585588
return this.printDotsLiteral(expression as lua.DotsLiteral);
589+
case lua.SyntaxKind.ArgKeyword:
590+
return this.printArgLiteral(expression as lua.ArgLiteral);
586591
case lua.SyntaxKind.TrueKeyword:
587592
case lua.SyntaxKind.FalseKeyword:
588593
return this.printBooleanLiteral(expression as lua.BooleanLiteral);
@@ -625,6 +630,10 @@ export class LuaPrinter {
625630
return this.createSourceNode(expression, "...");
626631
}
627632

633+
public printArgLiteral(expression: lua.ArgLiteral): SourceNode {
634+
return this.createSourceNode(expression, "arg");
635+
}
636+
628637
public printBooleanLiteral(expression: lua.BooleanLiteral): SourceNode {
629638
return this.createSourceNode(expression, expression.kind === lua.SyntaxKind.TrueKeyword ? "true" : "false");
630639
}

src/lualib-build/plugin.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,23 @@ class LuaLibPlugin implements tstl.Plugin {
4242
emitBOM
4343
);
4444

45+
// Flatten the output folder structure; we do not want to keep the target-specific directories
46+
for (const file of result) {
47+
let outPath = file.fileName;
48+
while (outPath.includes("lualib") && path.basename(path.dirname(outPath)) !== "lualib") {
49+
const upOne = path.join(path.dirname(outPath), "..", path.basename(outPath));
50+
outPath = path.normalize(upOne);
51+
}
52+
file.fileName = outPath;
53+
}
54+
4555
// Create map of result files keyed by their 'lualib name'
4656
const exportedLualibFeatures = new Map(result.map(f => [path.basename(f.fileName).split(".")[0], f.code]));
4757

4858
// Figure out the order required in the bundle by recursively resolving all dependency features
4959
const allFeatures = Object.values(LuaLibFeature) as LuaLibFeature[];
50-
const orderedFeatures = resolveRecursiveLualibFeatures(allFeatures, emitHost, luaLibModuleInfo);
60+
const luaTarget = options.luaTarget ?? tstl.LuaTarget.Universal;
61+
const orderedFeatures = resolveRecursiveLualibFeatures(allFeatures, luaTarget, emitHost, luaLibModuleInfo);
5162

5263
// Concatenate lualib files into bundle with exports table and add lualib_bundle.lua to results
5364
let lualibBundle = orderedFeatures.map(f => exportedLualibFeatures.get(LuaLibFeature[f])).join("\n");

src/lualib/5.0/CountVarargs.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/** @noSelfInFile */
2+
3+
export function __TS__CountVarargs<T>(...args: T[]): number {
4+
// select() is not available in Lua 5.0. In this version, the arg table
5+
// includes trailing nils.
6+
return args.length;
7+
}

0 commit comments

Comments
 (0)