From 826887ba818abf5310c36bbed6ba183b0be23722 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Mon, 7 May 2018 21:50:28 +0200 Subject: [PATCH 1/2] Get and set accessors implementation --- src/TSHelper.ts | 26 +++++++++++ src/Transpiler.ts | 107 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) diff --git a/src/TSHelper.ts b/src/TSHelper.ts index 6bfa7e015..b0b49e3d2 100644 --- a/src/TSHelper.ts +++ b/src/TSHelper.ts @@ -108,4 +108,30 @@ export class TSHelper { } return null; } + + public static hasGetAccessor(node: ts.Node, checker: ts.TypeChecker): boolean { + if (ts.isPropertyAccessExpression(node)) { + const name = node.name.escapedText; + const type = checker.getTypeAtLocation(node.expression); + + if (type && type.symbol && type.symbol.members) { + const field = type.symbol.members.get(name); + return field && (field.flags & ts.SymbolFlags.GetAccessor) !== 0; + } + } + return false; + } + + public static hasSetAccessor(node: ts.Node, checker: ts.TypeChecker): boolean { + if (ts.isPropertyAccessExpression(node)) { + const name = node.name.escapedText; + const type = checker.getTypeAtLocation(node.expression); + + if (type && type.symbol && type.symbol.members) { + const field = type.symbol.members.get(name); + return field && (field.flags & ts.SymbolFlags.SetAccessor) !== 0; + } + } + return false; + } } diff --git a/src/Transpiler.ts b/src/Transpiler.ts index 1b6862a18..1865ddeb1 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -633,30 +633,50 @@ export class LuaTranspiler { result = `bit.band(${lhs},${rhs})`; break; case ts.SyntaxKind.AmpersandEqualsToken: + if (tsEx.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 (tsEx.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 (tsEx.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 (tsEx.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 (tsEx.hasSetAccessor(node.left, this.checker)) { + return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, + `bit.rshift(${lhs},${rhs})`); + } result = `${lhs}=bit.rshift(${lhs},${rhs})`; break; } @@ -666,30 +686,45 @@ export class LuaTranspiler { result = `${lhs}&${rhs}`; break; case ts.SyntaxKind.AmpersandEqualsToken: + if (tsEx.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 (tsEx.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 (tsEx.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 (tsEx.hasSetAccessor(node.left, this.checker)) { + return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, `${lhs}>>${rhs}`); + } result = `${lhs}=${lhs}>>${rhs}`; break; case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken: result = `${lhs}>>>${rhs}`; break; case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: + if (tsEx.hasSetAccessor(node.left, this.checker)) { + return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, `${lhs}>>>${rhs}`); + } result = `${lhs}=${lhs}>>>${rhs}`; break; } @@ -699,15 +734,27 @@ export class LuaTranspiler { if (result === "") { switch (node.operatorToken.kind) { case ts.SyntaxKind.PlusEqualsToken: + if (tsEx.hasSetAccessor(node.left, this.checker)) { + return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, `${lhs}+${rhs}`); + } result = `${lhs}=${lhs}+${rhs}`; break; case ts.SyntaxKind.MinusEqualsToken: + if (tsEx.hasSetAccessor(node.left, this.checker)) { + return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, `${lhs}-${rhs}`); + } result = `${lhs}=${lhs}-${rhs}`; break; case ts.SyntaxKind.AsteriskEqualsToken: + if (tsEx.hasSetAccessor(node.left, this.checker)) { + return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, `${lhs}*${rhs}`); + } result = `${lhs}=${lhs}*${rhs}`; break; case ts.SyntaxKind.SlashEqualsToken: + if (tsEx.hasSetAccessor(node.left, this.checker)) { + return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, `${lhs}/${rhs}`); + } result = `${lhs}=${lhs}/${rhs}`; break; case ts.SyntaxKind.AmpersandAmpersandToken: @@ -751,6 +798,9 @@ export class LuaTranspiler { result = `${lhs}<=${rhs}`; break; case ts.SyntaxKind.EqualsToken: + if (tsEx.hasSetAccessor(node.left, this.checker)) { + return this.transpileSetAccessor(node.left as ts.PropertyAccessExpression, rhs); + } result = `${lhs}=${rhs}`; break; case ts.SyntaxKind.EqualsEqualsToken: @@ -1007,6 +1057,8 @@ export class LuaTranspiler { case ts.TypeFlags.Object: if (tsEx.isArrayType(type, this.checker)) { return this.transpileArrayProperty(node); + } else if (tsEx.hasGetAccessor(node, this.checker)) { + return this.transpileGetAccessor(node); } } @@ -1024,6 +1076,18 @@ export class LuaTranspiler { return `${callPath}.${property}`; } + public transpileGetAccessor(node: ts.PropertyAccessExpression): string { + const name = node.name.escapedText; + const expression = this.transpileExpression(node.expression); + return `${expression}.get__${name}()`; + } + + public transpileSetAccessor(node: ts.PropertyAccessExpression, value: string): string { + const name = node.name.escapedText; + const expression = this.transpileExpression(node.expression); + return `${expression}.set__${name}(${value})`; + } + // Transpile a Math._ property public transpileMathExpression(identifier: ts.Identifier): string { const translation = { @@ -1322,6 +1386,16 @@ export class LuaTranspiler { ); } + // Transpile get accessors + node.members.filter(ts.isGetAccessor).forEach((getAccessor) => { + result += this.transpileGetAccessorDeclaration(getAccessor, className); + }); + + // Transpile set accessors + node.members.filter(ts.isSetAccessor).forEach((setAccessor) => { + result += this.transpileSetAccessorDeclaration(setAccessor, className); + }); + // Transpile methods node.members.filter(ts.isMethodDeclaration).forEach((method) => { result += this.transpileMethodDeclaration(method, `${className}.`); @@ -1330,6 +1404,39 @@ export class LuaTranspiler { return result; } + public transpileGetAccessorDeclaration(getAccessor: ts.GetAccessorDeclaration, className: string): string { + const name = (getAccessor.name as ts.Identifier).escapedText; + + let result = this.indent + `function ${className}.get__${name}()\n`; + + this.pushIndent(); + result += this.transpileBlock(getAccessor.body); + this.popIndent(); + + result += this.indent + `end\n`; + + return result; + } + + public transpileSetAccessorDeclaration(setAccessor: ts.SetAccessorDeclaration, className: string): string { + const name = (setAccessor.name as ts.Identifier).escapedText; + + const paramNames: string[] = []; + setAccessor.parameters.forEach((param) => { + paramNames.push((param.name as ts.Identifier).escapedText as string); + }); + + let result = this.indent + `function ${className}.set__${name}(${paramNames.join(",")})\n`; + + this.pushIndent(); + result += this.transpileBlock(setAccessor.body); + this.popIndent(); + + result += this.indent + `end\n`; + + return result; + } + public transpileConstructor(node: ts.ConstructorDeclaration, className: string, instanceFields: ts.PropertyDeclaration[]): string { From 1034fe71db9b48bafe55f1ea2b3efffc63b71506 Mon Sep 17 00:00:00 2001 From: Perryvw Date: Wed, 9 May 2018 22:16:54 +0200 Subject: [PATCH 2/2] Added instance to accessors, added unit tests --- src/Transpiler.ts | 16 +++--- test/src/util.ts | 4 +- test/translation/lua/getSetAccessors.lua | 21 ++++++++ test/translation/ts/getSetAccessors.ts | 14 ++++++ test/unit/expressions.spec.ts | 62 +++++++++++++++++++++++- 5 files changed, 106 insertions(+), 11 deletions(-) create mode 100644 test/translation/lua/getSetAccessors.lua create mode 100644 test/translation/ts/getSetAccessors.ts diff --git a/src/Transpiler.ts b/src/Transpiler.ts index 1865ddeb1..9f6213164 100644 --- a/src/Transpiler.ts +++ b/src/Transpiler.ts @@ -17,13 +17,13 @@ export class TranspileError extends Error { } } -export enum Target { +export enum LuaTarget { Lua53 = "5.3", LuaJIT = "JIT", } export class LuaTranspiler { - public static AvailableLuaTargets = [Target.LuaJIT, Target.Lua53]; + public static AvailableLuaTargets = [LuaTarget.LuaJIT, LuaTarget.Lua53]; // Transpile a source file public static transpileSourceFile(node: ts.SourceFile, @@ -627,7 +627,7 @@ export class LuaTranspiler { let result = ""; // Transpile Bitops - if (this.options.luaTarget === Target.LuaJIT) { + if (this.options.luaTarget === LuaTarget.LuaJIT) { switch (node.operatorToken.kind) { case ts.SyntaxKind.AmpersandToken: result = `bit.band(${lhs},${rhs})`; @@ -981,7 +981,7 @@ export class LuaTranspiler { fromCodePoint: "utf8.char", }; - if (identifier.escapedText as string === "fromCodePoint" && this.options.luaTarget !== Target.Lua53) { + 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 @@ -1079,13 +1079,13 @@ export class LuaTranspiler { public transpileGetAccessor(node: ts.PropertyAccessExpression): string { const name = node.name.escapedText; const expression = this.transpileExpression(node.expression); - return `${expression}.get__${name}()`; + return `${expression}:get__${name}()`; } public transpileSetAccessor(node: ts.PropertyAccessExpression, value: string): string { const name = node.name.escapedText; const expression = this.transpileExpression(node.expression); - return `${expression}.set__${name}(${value})`; + return `${expression}:set__${name}(${value})`; } // Transpile a Math._ property @@ -1407,7 +1407,7 @@ export class LuaTranspiler { public transpileGetAccessorDeclaration(getAccessor: ts.GetAccessorDeclaration, className: string): string { const name = (getAccessor.name as ts.Identifier).escapedText; - let result = this.indent + `function ${className}.get__${name}()\n`; + let result = this.indent + `function ${className}.get__${name}(self)\n`; this.pushIndent(); result += this.transpileBlock(getAccessor.body); @@ -1421,7 +1421,7 @@ export class LuaTranspiler { public transpileSetAccessorDeclaration(setAccessor: ts.SetAccessorDeclaration, className: string): string { const name = (setAccessor.name as ts.Identifier).escapedText; - const paramNames: string[] = []; + const paramNames: string[] = ["self"]; setAccessor.parameters.forEach((param) => { paramNames.push((param.name as ts.Identifier).escapedText as string); }); diff --git a/test/src/util.ts b/test/src/util.ts index 71ddd40b1..30216fdf0 100644 --- a/test/src/util.ts +++ b/test/src/util.ts @@ -3,7 +3,7 @@ import * as path from "path"; import { Expect } from "alsatian"; -import { LuaTranspiler, TranspileError } from "../../src/Transpiler"; +import { LuaTranspiler, TranspileError, LuaTarget } from "../../src/Transpiler"; import { CompilerOptions } from "../../src/CommandLineParser"; const LuaVM = require("lua.vm.js"); @@ -11,7 +11,7 @@ const fs = require("fs"); const libSource = fs.readFileSync(path.join(path.dirname(require.resolve('typescript')), 'lib.d.ts')).toString(); -export function transpileString(str: string, options: CompilerOptions = { dontRequireLuaLib: true }): string { +export function transpileString(str: string, options: CompilerOptions = { dontRequireLuaLib: true, luaTarget: LuaTarget.LuaJIT }): string { let compilerHost = { getSourceFile: (filename, languageVersion) => { if (filename === "file.ts") { diff --git a/test/translation/lua/getSetAccessors.lua b/test/translation/lua/getSetAccessors.lua new file mode 100644 index 000000000..382a65beb --- /dev/null +++ b/test/translation/lua/getSetAccessors.lua @@ -0,0 +1,21 @@ +MyClass = MyClass or {} +MyClass.__index = MyClass +function MyClass.new(construct, ...) + local instance = setmetatable({}, MyClass) + if construct and MyClass.constructor then MyClass.constructor(instance, ...) end + return instance +end +function MyClass.constructor(self) +end +function MyClass.get__field(self) + return self._field+4 +end +function MyClass.set__field(self,v) + self._field=(v*2) +end +local instance = MyClass.new(true) + +instance:set__field(4) +local b = instance:get__field() + +local c = (4+instance:get__field())*3 diff --git a/test/translation/ts/getSetAccessors.ts b/test/translation/ts/getSetAccessors.ts new file mode 100644 index 000000000..714be88a8 --- /dev/null +++ b/test/translation/ts/getSetAccessors.ts @@ -0,0 +1,14 @@ +class MyClass { + private _field: number; + public get field(): number { + return this._field + 4; + } + public set field(v: number) { + this._field = v*2; + } +} + +var instance = new MyClass(); +instance.field = 4; +const b = instance.field; +const c = (4 + instance.field)*3; \ No newline at end of file diff --git a/test/unit/expressions.spec.ts b/test/unit/expressions.spec.ts index 47a164160..9664fd485 100644 --- a/test/unit/expressions.spec.ts +++ b/test/unit/expressions.spec.ts @@ -1,4 +1,5 @@ -import { Expect, Test, TestCase } from "alsatian"; +import { Expect, Test, TestCase, FocusTest } from "alsatian"; +import { LuaTarget } from "../../src/Transpiler"; import * as ts from "typescript"; import * as util from "../src/util"; @@ -196,4 +197,63 @@ export class ExpressionTests { // Assert Expect(result).toBe(v1 + v2); } + + @TestCase("inst.field", 8) + @TestCase("inst.field + 3", 8 + 3) + @TestCase("inst.field * 3", 8 * 3) + @TestCase("inst.field / 3", 8 / 3) + @TestCase("inst.field && 3", 8 && 3) + @TestCase("inst.field || 3", 8 || 3) + // @TestCase("inst.field & 3", 8 & 3) + // @TestCase("inst.field | 3", 8 | 3) + // @TestCase("inst.field << 3", 8 << 3) + // @TestCase("inst.field >> 1", 8 >> 1) + @TestCase(`"abc" + inst.field`, "abc8") + public getAccessorBinary(expression: string, expected: any) { + const source = `class MyClass {` + + ` public _field: number;` + + ` public get field(): number { return this._field + 4; }` + + ` public set field(v: number) { this._field = v; }` + + `}` + + `var inst = new MyClass();` + + `inst._field = 4;` + + `return ${expression};`; + + // Transpile + const lua = util.transpileString(source); + + // Execute + const result = util.executeLua(lua); + + // Assert + Expect(result).toBe(expected); + } + + @TestCase("= 4", 4 + 4) + @TestCase("+= 3", 4 + 3 + 4) + @TestCase("*= 3", 4 * 3 + 4) + @TestCase("/= 3", 4 / 3 + 4) + // @TestCase("&= 3", 4 & 3 + 4) + // @TestCase("|= 3", 4 | 3 + 4) + // @TestCase("<<= 3", 4 << 3 + 4) + // @TestCase(">>= 3", 4 >> 3 + 4) + public setAccessorBinary(expression: string, expected: any) { + const source = `class MyClass {` + + ` public _field: number = 4;` + + ` public get field(): number { return this._field; }` + + ` public set field(v: number) { this._field = v + 4; }` + + `}` + + `var inst = new MyClass();` + + `inst.field ${expression};` + + `return inst._field;`; + + // Transpile + const lua = util.transpileString(source); + + // Execute + const result = util.executeLua(lua); + + // Assert + Expect(result).toBe(expected); + } }