diff --git a/src/Compiler.ts b/src/Compiler.ts index d6b419025..510ba8a4c 100644 --- a/src/Compiler.ts +++ b/src/Compiler.ts @@ -5,10 +5,18 @@ import * as path from "path"; import * as ts from "typescript"; import { CompilerOptions, parseCommandLine } from "./CommandLineParser"; -import { LuaTranspiler, TranspileError } from "./Transpiler"; +import { LuaTranspiler51 } from "./targets/Transpiler.51"; +import { LuaTranspiler52 } from "./targets/Transpiler.52"; +import { LuaTranspiler53 } from "./targets/Transpiler.53"; +import { LuaTranspilerJIT } from "./targets/Transpiler.JIT"; +import { LuaTarget, LuaTranspiler, TranspileError } from "./Transpiler"; import { TSHelper as tsEx } from "./TSHelper"; export function compile(fileNames: string[], options: CompilerOptions): void { + if (!options.luaTarget) { + options.luaTarget = LuaTarget.LuaJIT; + } + const program = ts.createProgram(fileNames, options); const checker = program.getTypeChecker(); @@ -41,7 +49,7 @@ export function compile(fileNames: string[], options: CompilerOptions): void { const rootDir = options.rootDir; // Transpile AST - const lua = LuaTranspiler.transpileSourceFile(sourceFile, checker, options); + const lua = createTranspiler(checker, options, sourceFile).transpileSourceFile(); let outPath = sourceFile.fileName; if (options.outDir !== options.rootDir) { @@ -90,6 +98,31 @@ export function compile(fileNames: string[], options: CompilerOptions): void { ); } +export function createTranspiler(checker: ts.TypeChecker, + options: ts.CompilerOptions, + sourceFile: ts.SourceFile): LuaTranspiler { + let luaTargetTranspiler: LuaTranspiler; + switch (options.luaTarget) { + case LuaTarget.LuaJIT: + luaTargetTranspiler = new LuaTranspilerJIT(checker, options, sourceFile); + break; + case LuaTarget.Lua51: + luaTargetTranspiler = new LuaTranspiler51(checker, options, sourceFile); + break; + case LuaTarget.Lua52: + luaTargetTranspiler = new LuaTranspiler52(checker, options, sourceFile); + break; + case LuaTarget.Lua53: + luaTargetTranspiler = new LuaTranspiler53(checker, options, sourceFile); + break; + default: + // should not happen + throw Error("No luaTarget Specified please ensure a target is set!"); + } + + return luaTargetTranspiler; +} + export function execCommandLine(argv?: string[]) { argv = argv ? argv : process.argv.slice(2); const commandLine = parseCommandLine(argv); diff --git a/src/Transpiler.ts b/src/Transpiler.ts index 0d28ebba2..5857eb43a 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -18,40 +18,15 @@ export class TranspileError extends Error { } export enum LuaTarget { + Lua51 = "5.1", + Lua52 = "5.2", Lua53 = "5.3", LuaJIT = "JIT", } -export class LuaTranspiler { +export abstract class LuaTranspiler { public static AvailableLuaTargets = [LuaTarget.LuaJIT, LuaTarget.Lua53]; - // Transpile a source file - public static transpileSourceFile(node: ts.SourceFile, - checker: ts.TypeChecker, - options: CompilerOptions): string { - const transpiler = new LuaTranspiler(checker, options, node); - - let header = ""; - if (options.addHeader) { - header = "-- Generated by TypescriptToLua v" + packageJSON.version + "\n" + - "-- https://github.com/Perryvw/TypescriptToLua\n"; - } - let result = header; - if (!options.dontRequireLuaLib) { - // require helper functions - result += `require("typescript_lualib")\n`; - } - if (transpiler.isModule) { - // Shadow exports if it already exists - result += "local exports = exports or {}\n"; - } - result += transpiler.transpileBlock(node); - if (transpiler.isModule) { - result += "return exports\n"; - } - return result; - } - public indent: string; public checker: ts.TypeChecker; public options: ts.CompilerOptions; @@ -130,6 +105,29 @@ export class LuaTranspiler { return `"${relativePath.replace(new RegExp("\\\\|\/", "g"), ".")}"`; } + // Transpile a source file + public transpileSourceFile(): string { + let header = ""; + if (this.options.addHeader) { + header = "-- Generated by TypescriptToLua v" + packageJSON.version + "\n" + + "-- https://github.com/Perryvw/TypescriptToLua\n"; + } + let result = header; + if (!this.options.dontRequireLuaLib) { + // require helper functions + result += `require("typescript_lualib")\n`; + } + if (this.isModule) { + // Shadow exports if it already exists + result += "local exports = exports or {}\n"; + } + result += this.transpileBlock(this.sourceFile); + if (this.isModule) { + result += "return exports\n"; + } + return result; + } + // Transpile a block public transpileBlock(node: ts.Node): string { let result = ""; @@ -655,102 +653,18 @@ export class LuaTranspiler { let result = ""; // Transpile Bitops - if (this.options.luaTarget === LuaTarget.LuaJIT) { - switch (node.operatorToken.kind) { - case ts.SyntaxKind.AmpersandToken: - result = `bit.band(${lhs},${rhs})`; - break; - case ts.SyntaxKind.AmpersandEqualsToken: - if (tsHelper.hasSetAccessor(node.left, this.checker)) { - return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, - `bit.band(${lhs},${rhs})`); - } - result = `${lhs}=bit.band(${lhs},${rhs})`; - break; - case ts.SyntaxKind.BarToken: - result = `bit.bor(${lhs},${rhs})`; - break; - case ts.SyntaxKind.BarEqualsToken: - if (tsHelper.hasSetAccessor(node.left, this.checker)) { - return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, - `bit.bor(${lhs},${rhs})`); - } - result = `${lhs}=bit.bor(${lhs},${rhs})`; - break; - case ts.SyntaxKind.LessThanLessThanToken: - result = `bit.lshift(${lhs},${rhs})`; - break; - case ts.SyntaxKind.LessThanLessThanEqualsToken: - if (tsHelper.hasSetAccessor(node.left, this.checker)) { - return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, - `bit.lshift(${lhs},${rhs})`); - } - result = `${lhs}=bit.lshift(${lhs},${rhs})`; - break; - case ts.SyntaxKind.GreaterThanGreaterThanToken: - result = `bit.arshift(${lhs},${rhs})`; - break; - case ts.SyntaxKind.GreaterThanGreaterThanEqualsToken: - if (tsHelper.hasSetAccessor(node.left, this.checker)) { - return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, - `bit.arshift(${lhs},${rhs})`); - } - result = `${lhs}=bit.arshift(${lhs},${rhs})`; - break; - case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken: - result = `bit.rshift(${lhs},${rhs})`; - break; - case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: - if (tsHelper.hasSetAccessor(node.left, this.checker)) { - return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, - `bit.rshift(${lhs},${rhs})`); - } - result = `${lhs}=bit.rshift(${lhs},${rhs})`; - break; - } - } else { - switch (node.operatorToken.kind) { - case ts.SyntaxKind.AmpersandToken: - result = `${lhs}&${rhs}`; - break; - case ts.SyntaxKind.AmpersandEqualsToken: - if (tsHelper.hasSetAccessor(node.left, this.checker)) { - return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, `${lhs}&${rhs}`); - } - result = `${lhs}=${lhs}&${rhs}`; - break; - case ts.SyntaxKind.BarToken: - result = `${lhs}|${rhs}`; - break; - case ts.SyntaxKind.BarEqualsToken: - if (tsHelper.hasSetAccessor(node.left, this.checker)) { - return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, `${lhs}|${rhs}`); - } - result = `${lhs}=${lhs}|${rhs}`; - break; - case ts.SyntaxKind.LessThanLessThanToken: - result = `${lhs}<<${rhs}`; - break; - case ts.SyntaxKind.LessThanLessThanEqualsToken: - if (tsHelper.hasSetAccessor(node.left, this.checker)) { - return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, `${lhs}<<${rhs}`); - } - result = `${lhs}=${lhs}<<${rhs}`; - break; - case ts.SyntaxKind.GreaterThanGreaterThanToken: - result = `${lhs}>>${rhs}`; - break; - case ts.SyntaxKind.GreaterThanGreaterThanEqualsToken: - if (tsHelper.hasSetAccessor(node.left, this.checker)) { - return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, `${lhs}>>${rhs}`); - } - result = `${lhs}=${lhs}>>${rhs}`; - break; - case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken: - throw new TranspileError("Bitwise operator >>> not supported", node); - case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: - throw new TranspileError("Bitwise operator >>> not supported", node); - } + switch (node.operatorToken.kind) { + case ts.SyntaxKind.AmpersandToken: + case ts.SyntaxKind.AmpersandEqualsToken: + case ts.SyntaxKind.BarToken: + case ts.SyntaxKind.BarEqualsToken: + case ts.SyntaxKind.LessThanLessThanToken: + case ts.SyntaxKind.LessThanLessThanEqualsToken: + case ts.SyntaxKind.GreaterThanGreaterThanToken: + case ts.SyntaxKind.GreaterThanGreaterThanEqualsToken: + case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken: + case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: + result = this.transpileBitOperation(node, lhs, rhs); } // Transpile operators @@ -862,6 +776,10 @@ export class LuaTranspiler { } } + public transpileBitOperation(node: ts.BinaryExpression, lhs: string, rhs: string): string { + throw new TranspileError(`Bit Oeprations are not supported in Lua ${this.options.target}`, node); + } + public transpileTemplateExpression(node: ts.TemplateExpression) { const parts = [`"${node.head.text}"`]; node.templateSpans.forEach(span => { @@ -1015,24 +933,22 @@ export class LuaTranspiler { } } - // Transpile a String._ property - public transpileStringExpression(identifier: ts.Identifier): string { - const translation = { + public getValidStringProperties(): {[js: string]: string} { + return { fromCharCode: "string.char", - fromCodePoint: "utf8.char", }; + } - if (identifier.escapedText as string === "fromCodePoint" && this.options.luaTarget !== LuaTarget.Lua53) { - throw new TranspileError( - `Unsupported string property ${identifier.escapedText} is only supported for lua 5.3.`, - identifier - ); - } + // Transpile a String._ property + public transpileStringExpression(identifier: ts.Identifier): string { + const translation = this.getValidStringProperties(); if (translation[identifier.escapedText as string]) { return `${translation[identifier.escapedText as string]}`; } else { - throw new TranspileError(`Unsupported string property ${identifier.escapedText}.`, identifier); + throw new TranspileError(`Unsupported string property ${identifier.escapedText}, ` + + `is not supported in Lua ${this.options.luaTarget}.`, + identifier); } } diff --git a/src/targets/Transpiler.51.ts b/src/targets/Transpiler.51.ts new file mode 100644 index 000000000..05162471b --- /dev/null +++ b/src/targets/Transpiler.51.ts @@ -0,0 +1,7 @@ +import { LuaTranspiler, TranspileError } from "../Transpiler"; +import { TSHelper as tsHelper } from "../TSHelper"; + +import * as ts from "typescript"; + +export class LuaTranspiler51 extends LuaTranspiler { +} diff --git a/src/targets/Transpiler.52.ts b/src/targets/Transpiler.52.ts new file mode 100644 index 000000000..3aff85cef --- /dev/null +++ b/src/targets/Transpiler.52.ts @@ -0,0 +1,8 @@ +import { TSHelper as tsHelper } from "../TSHelper"; +import { LuaTranspiler51 } from "./Transpiler.51"; + +import * as ts from "typescript"; + +export class LuaTranspiler52 extends LuaTranspiler51 { + +} diff --git a/src/targets/Transpiler.53.ts b/src/targets/Transpiler.53.ts new file mode 100644 index 000000000..99885f883 --- /dev/null +++ b/src/targets/Transpiler.53.ts @@ -0,0 +1,51 @@ +import { TranspileError } from "../Transpiler"; +import { TSHelper as tsHelper } from "../TSHelper"; +import { LuaTranspiler52 } from "./Transpiler.52"; + +import * as ts from "typescript"; + +export class LuaTranspiler53 extends LuaTranspiler52 { + public transpileBitOperation(node: ts.BinaryExpression, lhs: string, rhs: string): string { + switch (node.operatorToken.kind) { + case ts.SyntaxKind.AmpersandToken: + return `${lhs}&${rhs}`; + case ts.SyntaxKind.AmpersandEqualsToken: + if (tsHelper.hasSetAccessor(node.left, this.checker)) { + return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, `${lhs}&${rhs}`); + } + return `${lhs}=${lhs}&${rhs}`; + case ts.SyntaxKind.BarToken: + return `${lhs}|${rhs}`; + case ts.SyntaxKind.BarEqualsToken: + if (tsHelper.hasSetAccessor(node.left, this.checker)) { + return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, `${lhs}|${rhs}`); + } + return `${lhs}=${lhs}|${rhs}`; + case ts.SyntaxKind.LessThanLessThanToken: + return `${lhs}<<${rhs}`; + case ts.SyntaxKind.LessThanLessThanEqualsToken: + if (tsHelper.hasSetAccessor(node.left, this.checker)) { + return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, `${lhs}<<${rhs}`); + } + return `${lhs}=${lhs}<<${rhs}`; + case ts.SyntaxKind.GreaterThanGreaterThanToken: + return `${lhs}>>${rhs}`; + case ts.SyntaxKind.GreaterThanGreaterThanEqualsToken: + if (tsHelper.hasSetAccessor(node.left, this.checker)) { + return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, `${lhs}>>${rhs}`); + } + return `${lhs}=${lhs}>>${rhs}`; + case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken: + throw new TranspileError("Bitwise operator >>> not supported in Lua 5.3", node); + case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: + throw new TranspileError("Bitwise operator >>> not supported in Lua 5.3", node); + } + } + public getValidStringProperties(): {[js: string]: string} { + return { + fromCharCode: "string.char", + fromCodePoint: "utf8.char", + }; + } + +} diff --git a/src/targets/Transpiler.JIT.ts b/src/targets/Transpiler.JIT.ts new file mode 100644 index 000000000..d80aa4234 --- /dev/null +++ b/src/targets/Transpiler.JIT.ts @@ -0,0 +1,51 @@ +import { TSHelper as tsHelper } from "../TSHelper"; +import { LuaTranspiler52 } from "./Transpiler.52"; + +import * as ts from "typescript"; + +export class LuaTranspilerJIT extends LuaTranspiler52 { + public transpileBitOperation(node: ts.BinaryExpression, lhs: string, rhs: string): string { + switch (node.operatorToken.kind) { + case ts.SyntaxKind.AmpersandToken: + return `bit.band(${lhs},${rhs})`; + case ts.SyntaxKind.AmpersandEqualsToken: + if (tsHelper.hasSetAccessor(node.left, this.checker)) { + return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, + `bit.band(${lhs},${rhs})`); + } + return `${lhs}=bit.band(${lhs},${rhs})`; + case ts.SyntaxKind.BarToken: + return `bit.bor(${lhs},${rhs})`; + case ts.SyntaxKind.BarEqualsToken: + if (tsHelper.hasSetAccessor(node.left, this.checker)) { + return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, + `bit.bor(${lhs},${rhs})`); + } + return `${lhs}=bit.bor(${lhs},${rhs})`; + case ts.SyntaxKind.LessThanLessThanToken: + return `bit.lshift(${lhs},${rhs})`; + case ts.SyntaxKind.LessThanLessThanEqualsToken: + if (tsHelper.hasSetAccessor(node.left, this.checker)) { + return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, + `bit.lshift(${lhs},${rhs})`); + } + return `${lhs}=bit.lshift(${lhs},${rhs})`; + case ts.SyntaxKind.GreaterThanGreaterThanToken: + return `bit.arshift(${lhs},${rhs})`; + case ts.SyntaxKind.GreaterThanGreaterThanEqualsToken: + if (tsHelper.hasSetAccessor(node.left, this.checker)) { + return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, + `bit.arshift(${lhs},${rhs})`); + } + return `${lhs}=bit.arshift(${lhs},${rhs})`; + case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken: + return `bit.rshift(${lhs},${rhs})`; + case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: + if (tsHelper.hasSetAccessor(node.left, this.checker)) { + return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, + `bit.rshift(${lhs},${rhs})`); + } + return `${lhs}=bit.rshift(${lhs},${rhs})`; + } + } +} diff --git a/test/src/util.ts b/test/src/util.ts index f12ac47b4..ea7f9ebc8 100644 --- a/test/src/util.ts +++ b/test/src/util.ts @@ -5,6 +5,7 @@ import { Expect } from "alsatian"; import { LuaTarget, LuaTranspiler, TranspileError } from "../../src/Transpiler"; import { CompilerOptions } from "../../src/CommandLineParser"; +import { createTranspiler } from "../../src/Compiler"; import {lauxlib, lua, lualib, to_jsstring, to_luastring } from "fengari"; @@ -40,9 +41,9 @@ export function transpileString(str: string, options: CompilerOptions = { dontRe }; const program = ts.createProgram(["file.ts"], options, compilerHost); - const result = LuaTranspiler.transpileSourceFile(program.getSourceFile("file.ts"), - program.getTypeChecker(), - options); + const result = createTranspiler(program.getTypeChecker(), + options, + program.getSourceFile("file.ts")).transpileSourceFile(); return result.trim(); } @@ -55,7 +56,7 @@ export function transpileFile(filePath: string): string { diagnostics.forEach(diagnostic => console.log(`${ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")}`)); const options: ts.CompilerOptions = { dontRequireLuaLib: true }; - const result = LuaTranspiler.transpileSourceFile(program.getSourceFile(filePath), checker, options); + const result = createTranspiler(checker, options, program.getSourceFile(filePath)).transpileSourceFile(); return result.trim(); } @@ -104,9 +105,9 @@ export function expectCodeEqual(code1: string, code2: string) { // Get a mock transpiler to use for testing export function makeTestTranspiler(target: LuaTarget = LuaTarget.Lua53) { - return new LuaTranspiler({} as ts.TypeChecker, - { dontRequireLuaLib: true, luaTarget: target } as any, - { statements: [] } as any as ts.SourceFile); + return createTranspiler({} as ts.TypeChecker, + { dontRequireLuaLib: true, luaTarget: target } as any, + { statements: [] } as any as ts.SourceFile); } const tslualib = fs.readFileSync("dist/lualib/typescript.lua") + "\n"; diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index 95c4ff9cd..e9792dbaf 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -126,7 +126,7 @@ export class ExpressionTests { @Test("Unsupported bitop 5.3") public bitOperatorOverride53Unsupported(input: string) { Expect(() => util.transpileString(input, { luaTarget: "5.3", dontRequireLuaLib: true })) - .toThrowError(Error, "Bitwise operator >>> not supported"); + .toThrowError(Error, "Bitwise operator >>> not supported in Lua 5.3"); } @TestCase("1+1", "1+1") @@ -422,7 +422,7 @@ export class ExpressionTests { const identifier = ts.createIdentifier("fromCodePoint"); Expect(() => transpiler.transpileStringExpression(identifier)) - .toThrowError(Error, "Unsupported string property fromCodePoint is only supported for lua 5.3."); + .toThrowError(Error, "Unsupported string property fromCodePoint, is not supported in Lua JIT."); } @Test("Unknown string expression error") @@ -431,7 +431,7 @@ export class ExpressionTests { const identifier = ts.createIdentifier("abcd"); Expect(() => transpiler.transpileStringExpression(identifier)) - .toThrowError(Error, "Unsupported string property abcd."); + .toThrowError(Error, "Unsupported string property abcd, is not supported in Lua JIT."); } @Test("Unsupported array function error")