Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions build-lualib.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
require("ts-node/register/transpile-only");
const fs = require("fs");
const path = require("path");
const ts = require("typescript");
const tstl = require("./src");
const { loadLuaLibFeatures } = require("./src/LuaLib");

const configFileName = path.resolve(__dirname, "src/lualib/tsconfig.json");
const { diagnostics } = tstl.transpileProject(configFileName);
diagnostics.forEach(tstl.createDiagnosticReporter(true));

const bundlePath = path.join(__dirname, "dist/lualib/lualib_bundle.lua");
if (fs.existsSync(bundlePath)) {
fs.unlinkSync(bundlePath);
}
const extraDiagnostics = require("./src/lualib-build/build-lualib").writeExtraLualibFiles(
path.resolve(__dirname, "dist/lualib")
);
extraDiagnostics.forEach(tstl.createDiagnosticReporter(true));

fs.writeFileSync(bundlePath, loadLuaLibFeatures(Object.values(tstl.LuaLibFeature), ts.sys));
if (diagnostics.length > 0 || extraDiagnostics.length > 0) {
process.exit(1);
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"dist/**/*.js",
"dist/**/*.lua",
"dist/**/*.ts",
"dist/lualib/*.json",
"language-extensions/**/*.ts"
],
"main": "dist/index.js",
Expand Down
134 changes: 70 additions & 64 deletions src/LuaLib.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as path from "path";
import { EmitHost } from "./transpilation";
import * as lua from "./LuaAST";

export enum LuaLibFeature {
ArrayConcat = "ArrayConcat",
Expand Down Expand Up @@ -99,81 +100,52 @@ export enum LuaLibFeature {
Unpack = "Unpack",
}

/* eslint-disable @typescript-eslint/naming-convention */
const luaLibDependencies: Partial<Record<LuaLibFeature, LuaLibFeature[]>> = {
ArrayConcat: [LuaLibFeature.ArrayIsArray],
ArrayFlat: [LuaLibFeature.ArrayConcat, LuaLibFeature.ArrayIsArray],
ArrayFlatMap: [LuaLibFeature.ArrayConcat, LuaLibFeature.ArrayIsArray],
Await: [LuaLibFeature.InstanceOf, LuaLibFeature.New, LuaLibFeature.Promise],
Decorate: [LuaLibFeature.ObjectGetOwnPropertyDescriptor, LuaLibFeature.SetDescriptor, LuaLibFeature.ObjectAssign],
DelegatedYield: [LuaLibFeature.StringAccess],
Delete: [LuaLibFeature.ObjectGetOwnPropertyDescriptors, LuaLibFeature.Error, LuaLibFeature.New],
Error: [LuaLibFeature.Class, LuaLibFeature.ClassExtends, LuaLibFeature.New],
FunctionBind: [LuaLibFeature.Unpack],
Generator: [LuaLibFeature.Symbol],
InstanceOf: [LuaLibFeature.Symbol],
Iterator: [LuaLibFeature.Symbol],
NumberToString: [LuaLibFeature.StringAccess],
ObjectDefineProperty: [LuaLibFeature.CloneDescriptor, LuaLibFeature.SetDescriptor],
ObjectFromEntries: [LuaLibFeature.Iterator, LuaLibFeature.Symbol],
Promise: [
LuaLibFeature.ArrayPush,
LuaLibFeature.Class,
LuaLibFeature.FunctionBind,
LuaLibFeature.InstanceOf,
LuaLibFeature.New,
],
PromiseAll: [LuaLibFeature.InstanceOf, LuaLibFeature.New, LuaLibFeature.Promise, LuaLibFeature.Iterator],
PromiseAllSettled: [LuaLibFeature.InstanceOf, LuaLibFeature.New, LuaLibFeature.Promise, LuaLibFeature.Iterator],
PromiseAny: [
LuaLibFeature.ArrayPush,
LuaLibFeature.InstanceOf,
LuaLibFeature.New,
LuaLibFeature.Promise,
LuaLibFeature.Iterator,
],
PromiseRace: [
LuaLibFeature.ArrayPush,
LuaLibFeature.InstanceOf,
LuaLibFeature.New,
LuaLibFeature.Promise,
LuaLibFeature.Iterator,
],
ParseFloat: [LuaLibFeature.StringAccess],
ParseInt: [LuaLibFeature.StringSubstr, LuaLibFeature.StringSubstring],
SetDescriptor: [LuaLibFeature.CloneDescriptor],
Spread: [LuaLibFeature.Iterator, LuaLibFeature.StringAccess, LuaLibFeature.Unpack],
StringSplit: [LuaLibFeature.StringSubstring, LuaLibFeature.StringAccess],
SymbolRegistry: [LuaLibFeature.Symbol],

Map: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol, LuaLibFeature.Class],
Set: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol, LuaLibFeature.Class],
WeakMap: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol, LuaLibFeature.Class],
WeakSet: [LuaLibFeature.InstanceOf, LuaLibFeature.Iterator, LuaLibFeature.Symbol, LuaLibFeature.Class],
};
/* eslint-enable @typescript-eslint/naming-convention */

export function loadLuaLibFeatures(features: Iterable<LuaLibFeature>, emitHost: EmitHost): string {
export interface LuaLibFeatureInfo {
dependencies?: LuaLibFeature[];
exports: string[];
}
export type LuaLibModulesInfo = Record<LuaLibFeature, LuaLibFeatureInfo>;

export const luaLibModulesInfoFileName = "lualib_module_info.json";
let luaLibModulesInfo: LuaLibModulesInfo | undefined;
export function getLuaLibModulesInfo(emitHost: EmitHost): LuaLibModulesInfo {
if (luaLibModulesInfo === undefined) {
const lualibPath = path.resolve(__dirname, `../dist/lualib/${luaLibModulesInfoFileName}`);
const result = emitHost.readFile(lualibPath);
if (result !== undefined) {
luaLibModulesInfo = JSON.parse(result) as LuaLibModulesInfo;
} else {
throw new Error(`Could not load lualib dependencies from '${lualibPath}'`);
}
}
return luaLibModulesInfo;
}

export function readLuaLibFeature(feature: LuaLibFeature, emitHost: EmitHost): string {
const featurePath = path.resolve(__dirname, `../dist/lualib/${feature}.lua`);
const luaLibFeature = emitHost.readFile(featurePath);
if (luaLibFeature === undefined) {
throw new Error(`Could not load lualib feature from '${featurePath}'`);
}
return luaLibFeature;
}

export function loadInlineLualibFeatures(features: Iterable<LuaLibFeature>, emitHost: EmitHost): string {
let result = "";

const luaLibModulesInfo = getLuaLibModulesInfo(emitHost);
const loadedFeatures = new Set<LuaLibFeature>();

function load(feature: LuaLibFeature): void {
if (loadedFeatures.has(feature)) return;
loadedFeatures.add(feature);

const dependencies = luaLibDependencies[feature];
const dependencies = luaLibModulesInfo[feature]?.dependencies;
if (dependencies) {
dependencies.forEach(load);
}

const featurePath = path.resolve(__dirname, `../dist/lualib/${feature}.lua`);
const luaLibFeature = emitHost.readFile(featurePath);
if (luaLibFeature !== undefined) {
result += luaLibFeature + "\n";
} else {
throw new Error(`Could not load lualib feature from '${featurePath}'`);
}
const luaLibFeature = readLuaLibFeature(feature, emitHost);
result += luaLibFeature + "\n";
}

for (const feature of features) {
Expand All @@ -183,6 +155,40 @@ export function loadLuaLibFeatures(features: Iterable<LuaLibFeature>, emitHost:
return result;
}

export function loadImportedLualibFeatures(
features: Iterable<LuaLibFeature>,
emitHost: EmitHost,
alwaysRequire = false
): lua.Statement[] {
const luaLibModuleInfo = getLuaLibModulesInfo(emitHost);

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

const requireCall = lua.createCallExpression(lua.createIdentifier("require"), [
lua.createStringLiteral("lualib_bundle"),
]);
if (imports.length === 0) {
if (alwaysRequire) {
return [lua.createExpressionStatement(requireCall)];
}
return [];
}

const luaLibId = lua.createIdentifier("____lualib");
const importStatement = lua.createVariableDeclarationStatement(luaLibId, requireCall);
const statements: lua.Statement[] = [importStatement];
// local <export> = ____luaLib.<export>
for (const item of imports) {
statements.push(
lua.createVariableDeclarationStatement(
lua.createIdentifier(item),
lua.createTableIndexExpression(luaLibId, lua.createStringLiteral(item))
)
);
}
return statements;
}

let luaLibBundleContent: string;
export function getLuaLibBundle(emitHost: EmitHost): string {
if (luaLibBundleContent === undefined) {
Expand Down
20 changes: 13 additions & 7 deletions src/LuaPrinter.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import * as path from "path";
import { Mapping, SourceMapGenerator, SourceNode } from "source-map";
import { getEmitPath } from ".";
import * as ts from "typescript";
import { CompilerOptions, isBundleEnabled, LuaLibImportKind } from "./CompilerOptions";
import * as lua from "./LuaAST";
import { loadLuaLibFeatures, LuaLibFeature } from "./LuaLib";
import { loadInlineLualibFeatures, LuaLibFeature, loadImportedLualibFeatures } from "./LuaLib";
import { isValidLuaIdentifier, shouldAllowUnicode } from "./transformation/utils/safe-names";
import { EmitHost } from "./transpilation";
import { EmitHost, getEmitPath } from "./transpilation";
import { intersperse, normalizeSlashes } from "./utils";

// https://www.lua.org/pil/2.4.html
Expand Down Expand Up @@ -233,18 +232,25 @@ export class LuaPrinter {
if (!this.options.noHeader) {
header += tstlHeader;
}
let statements = file.statements;

const luaLibImport = this.options.luaLibImport ?? LuaLibImportKind.Require;
if (
luaLibImport === LuaLibImportKind.Always ||
(luaLibImport === LuaLibImportKind.Require && file.luaLibFeatures.size > 0)
) {
// Require lualib bundle
header += 'require("lualib_bundle");\n';
// Import lualib features
const importStatements = loadImportedLualibFeatures(
file.luaLibFeatures,
this.emitHost,
luaLibImport === LuaLibImportKind.Always
);

statements = importStatements.concat(statements);
} else if (luaLibImport === LuaLibImportKind.Inline && file.luaLibFeatures.size > 0) {
// Inline lualib features
header += "-- Lua Library inline imports\n";
header += loadLuaLibFeatures(file.luaLibFeatures, this.emitHost);
header += loadInlineLualibFeatures(file.luaLibFeatures, this.emitHost);
}

if (this.options.sourceMapTraceback && !isBundleEnabled(this.options)) {
Expand All @@ -253,7 +259,7 @@ export class LuaPrinter {
header += `${LuaPrinter.sourceMapTracebackPlaceholder}\n`;
}

return this.concatNodes(header, ...this.printStatementArray(file.statements));
return this.concatNodes(header, ...this.printStatementArray(statements));
}

protected pushIndent(): void {
Expand Down
20 changes: 20 additions & 0 deletions src/lualib-build/build-lualib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import pluginInstance from "./plugin";
import { luaLibModulesInfoFileName, loadInlineLualibFeatures, LuaLibFeature } from "../LuaLib";
import * as path from "path";
import * as ts from "typescript";

// should be called after lualib modules are built
export function writeExtraLualibFiles(destPath: string): ts.Diagnostic[] {
const { result: luaLibModuleInfo, diagnostics } = pluginInstance.createLuaLibModulesInfo();
const emitHost = ts.sys;
emitHost.writeFile(path.join(destPath, luaLibModulesInfoFileName), JSON.stringify(luaLibModuleInfo, null, 2));

const allFeatures = Object.values(LuaLibFeature) as LuaLibFeature[];

let lualibBundle = loadInlineLualibFeatures(allFeatures, emitHost);
const exports = allFeatures.flatMap(feature => luaLibModuleInfo[feature].exports);
lualibBundle += `\nreturn {\n${exports.map(exportName => ` ${exportName} = ${exportName}`).join(",\n")}\n}\n`;
emitHost.writeFile(path.join(destPath, "lualib_bundle.lua"), lualibBundle);

return diagnostics;
}
Loading