Skip to content

Commit 90060b3

Browse files
committed
lua OOP with extension
1 parent eecd0a6 commit 90060b3

File tree

6 files changed

+104
-59
lines changed

6 files changed

+104
-59
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
*.js
22
node_modules/
3+
*.lua

src/Compiler.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ function compile(fileNames: string[], options: ts.CompilerOptions): void {
5151
// Graciously handle transpilation errors
5252
console.error("Encountered error parsing file: " + exception.message);
5353
console.error(sourceFile.fileName + " line: " + (1 + pos.line) + " column: " + pos.character);
54+
console.error(exception.stack);
5455
} else {
5556
throw exception;
5657
}

src/Transpiler.ts

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,8 @@ export class LuaTranspiler {
346346
return this.transpileFunctionExpression(<ts.FunctionExpression>node);
347347
case ts.SyntaxKind.NewExpression:
348348
return this.transpileNewExpression(<ts.NewExpression>node);
349+
case ts.SyntaxKind.SuperKeyword:
350+
return "self.__base.constructor";
349351
case ts.SyntaxKind.TypeAssertionExpression:
350352
// Simply ignore the type assertion
351353
return this.transpileExpression((<ts.TypeAssertion>node).expression);
@@ -445,7 +447,7 @@ export class LuaTranspiler {
445447
const name = this.transpileExpression(node.expression);
446448
const params = this.transpileArguments(node.arguments);
447449

448-
return `${name}(${params})`;
450+
return `${name}.new(${params})`;
449451
}
450452

451453
transpileCallExpression(node: ts.CallExpression): string {
@@ -467,6 +469,13 @@ export class LuaTranspiler {
467469
return `${callPath}(${params})`;
468470
}
469471

472+
// Handle super calls properly
473+
if (node.expression.kind == ts.SyntaxKind.SuperKeyword) {
474+
let callPath = this.transpileExpression(node.expression);
475+
const params = this.transpileArguments(node.arguments, <ts.Expression>ts.createNode(ts.SyntaxKind.ThisKeyword));
476+
return `${callPath}(${params})`;
477+
}
478+
470479
let callPath = this.transpileExpression(node.expression);
471480
const params = this.transpileArguments(node.arguments);
472481
return `${callPath}(${params})`;
@@ -642,51 +651,42 @@ export class LuaTranspiler {
642651

643652
// Transpile a class declaration
644653
transpileClass(node: ts.ClassDeclaration): string {
645-
// Write class declaration
646-
const className = <string>node.name.escapedText;
647-
let result = this.indent + `${className} = ${className} or class({})\n`;
648-
649-
// Get all properties
650-
const properties = tsEx.getChildrenOfType<ts.PropertyDeclaration>(node, ts.isPropertyDeclaration);
651-
652-
let staticFields: ts.PropertyDeclaration[] = [];
653-
let instanceFields: ts.PropertyDeclaration[] = [];
654+
// Figure out inheritance
655+
const isExtension = node.heritageClauses && node.heritageClauses.length > 0;
656+
let baseName = "";
654657

655-
// Divide properties in static and instance fields
656-
for (const p of properties) {
657-
// Check if property is static
658-
const isStatic = tsEx.getChildrenOfType(p, child => child.kind == ts.SyntaxKind.StaticKeyword).length > 0;
658+
if (isExtension)
659+
baseName = <string>(<ts.Identifier>node.heritageClauses[0].types[0].expression).escapedText;
659660

660-
// Find value assignment
661-
const assignments = tsEx.getChildrenOfType(p, tsEx.isValueType);
662-
663-
// Ignore fields with no assigned value
664-
if (!p.initializer)
665-
continue;
666-
667-
if (isStatic) {
668-
staticFields.push(p);
669-
} else {
670-
instanceFields.push(p);
671-
}
672-
}
661+
// Write class declaration
662+
const className = <string>node.name.escapedText;
663+
let result = this.indent + `${className} = ${className} or {}\n`;
664+
result += this.indent + `${className}.__index = ${className}\n`;
665+
if (isExtension) result += this.indent + `${className}.__base = ${baseName}\n`
666+
result += this.indent + `function ${className}.new(...)\n`;
667+
result += this.indent + ` local instance = setmetatable({}, ${className})\n`;
668+
result += this.indent + ` if ${className}.constructor then ${className}.constructor(instance, ...) end\n`;
669+
result += this.indent + ` return instance\n`;
670+
result += this.indent + `end\n`;
671+
672+
// Get all properties with value
673+
const properties = node.members.filter(ts.isPropertyDeclaration)
674+
.filter(_ => _.initializer);
675+
676+
// Divide properties into static and non-static
677+
const isStatic = _ => _.modifiers && _.modifiers.some(_ => _.kind == ts.SyntaxKind.StaticKeyword);
678+
const staticFields = properties.filter(isStatic);
679+
const instanceFields = properties.filter(_ => !isStatic(_));
673680

674681
// Add static declarations
675-
for (const f of staticFields) {
676-
// Get identifier
677-
const fieldIdentifier = tsEx.getFirstChildOfType<ts.Identifier>(f, ts.isIdentifier);
678-
const fieldName = fieldIdentifier.escapedText;
679-
680-
// Get value at index 1 (index 0 is field name)
681-
const valueNode = tsEx.getChildrenOfType<ts.Node>(f, tsEx.isValueType)[1];
682-
let value = this.transpileExpression(valueNode);
683-
684-
// Build lua assignment string
682+
for (const field of staticFields) {
683+
const fieldName = (<ts.Identifier>field.name).escapedText;
684+
let value = this.transpileExpression(field.initializer);
685685
result += this.indent + `${className}.${fieldName} = ${value}\n`;
686686
}
687687

688688
// Try to find constructor
689-
const constructor = tsEx.getFirstChildOfType<ts.ConstructorDeclaration>(node, ts.isConstructorDeclaration);
689+
const constructor = node.members.filter(ts.isConstructorDeclaration)[0];
690690
if (constructor) {
691691
// Add constructor plus initialisation of instance fields
692692
result += this.transpileConstructor(constructor, className, instanceFields);
@@ -698,17 +698,19 @@ export class LuaTranspiler {
698698
}
699699
}
700700

701-
// Find all methods
702-
const methods = tsEx.getChildrenOfType<ts.MethodDeclaration>(node, ts.isMethodDeclaration);
703-
methods.forEach(method => {
701+
// Transpile methods
702+
node.members.filter(ts.isMethodDeclaration).forEach(method => {
704703
result += this.transpileMethodDeclaration(method, `${className}.`);
705704
});
706705

707706
return result;
708707
}
709708

710709
transpileConstructor(node: ts.ConstructorDeclaration, className: string, instanceFields: ts.PropertyDeclaration[]): string {
711-
let result = this.indent + `function ${className}:constructor()\n`;
710+
let parameters = ["self"];
711+
node.parameters.forEach(param => parameters.push(<string>(<ts.Identifier>param.name).escapedText));
712+
713+
let result = this.indent + `function ${className}.constructor(${parameters.join(",")})\n` ;
712714

713715
// Add in instance field declarations
714716
for (const f of instanceFields) {
@@ -764,7 +766,7 @@ export class LuaTranspiler {
764766

765767
let result = `function(${paramNames.join(",")})\n`;
766768
this.pushIndent();
767-
result += this.transpileNode(node.body);
769+
result += this.transpileBlock(node.body);
768770
this.popIndent();
769771
return result + this.indent + "end ";
770772
}

test/src/class.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
class testA {
2+
fieldA: number = 5;
3+
constructor(a: number) {
4+
this.fieldA = a;
5+
}
6+
7+
testMethod(): number {
8+
return this.fieldA;
9+
}
10+
}
11+
12+
class testB extends testA {
13+
fieldB: number;
14+
constructor(a: number, b: number) {
15+
super(a);
16+
this.fieldB = b;
17+
}
18+
19+
mul(): number {
20+
return this.fieldA * this.fieldB;
21+
}
22+
}
23+
24+
declare function print(...args:any[]): void;
25+
26+
let instance = new testB(4, 6);
27+
print(instance.fieldA);
28+
print(instance.fieldB);
29+
print(instance.mul());

test/src/test.lua

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,32 @@
1-
require("./test2"){$imports.name.escapedText} = require("./test2")local a = TestClass(0)
1+
require("./test2"){$imports.name.escapedText} = require("./test2")local a = TestClass.new(0)
22
GameState = GameState or {}
3-
function GameState:Init()
3+
GameState.__index = GameState
4+
function GameState.new(...)
5+
local instance = setmetatable({}, GameState)
6+
if GameState.constructor then GameState.constructor(instance, ...) end
7+
return instance
8+
end
9+
function GameState.Init(self)
410
self.state=DOTA_GAMERULES_STATE_CUSTOM_GAME_SETUP
511
self.callbacks={}
6-
CustomGameEventManager:RegisterListener("game_state_request",self.OnStateRequest)
12+
CustomGameEventManager.RegisterListener(CustomGameEventManager,"game_state_request",self.OnStateRequest)
713
end
8-
function GameState:SetState(state)
14+
function GameState.SetState(self,state)
915
self.state=state
10-
CustomGameEventManager:Send_ServerToAllClients("game_state_update",{["state"]=self.state})
16+
CustomGameEventManager.Send_ServerToAllClients(CustomGameEventManager,"game_state_update",{["state"]=self.state})
1117
for _, callback in ipairs(self.callbacks) do
1218
callback(self.state)
1319
end
1420
end
15-
function GameState:RegisterListener(callback)
21+
function GameState.RegisterListener(self,callback)
1622
table.insert(self.callbacks, callback)
1723
end
18-
function GameState:RegisterListenerWithContext(callback,context)
24+
function GameState.RegisterListenerWithContext(self,callback,context)
1925
table.insert(self.callbacks, function(state)
2026
callback(context,state)
2127
end )
2228
end
23-
function GameState:OnStateRequest(userid,event)
24-
local player = PlayerResource:GetPlayer(event.PlayerID)
25-
CustomGameEventManager:Send_ServerToPlayer(player,"game_state_response",{["state"]=self.state})
29+
function GameState.OnStateRequest(self,userid,event)
30+
local player = PlayerResource.GetPlayer(PlayerResource,event.PlayerID)
31+
CustomGameEventManager.Send_ServerToPlayer(CustomGameEventManager,player,"game_state_response",{["state"]=self.state})
2632
end

test/src/test2.lua

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,25 @@ local globalString = "glob"
22
local input = {1,2}
33
local objTest = {["a"]=3,["B"]=true}
44
TestClass = TestClass or {}
5-
function TestClass:constructor()
5+
TestClass.__index = TestClass
6+
function TestClass.new(...)
7+
local instance = setmetatable({}, TestClass)
8+
if TestClass.constructor then TestClass.constructor(instance, ...) end
9+
return instance
10+
end
11+
function TestClass.constructor(self,tf)
612
self.field3 = globalString
713
local localString = "abc"
814
globalString="abc"
915
self.field=""
1016
self.unit=GetUnit()
1117
end
12-
function TestClass:Test()
18+
function TestClass.Test(self)
1319
print("sup")
14-
self:Test3(3,"")
15-
self.unit:GetParent():GetParent():GetAbsOrigin()
20+
self.Test3(self,3,"")
21+
self.unit.GetParent(self.unit).GetParent(self.unit.GetParent(self.unit)).GetAbsOrigin(self.unit.GetParent(self.unit).GetParent(self.unit.GetParent(self.unit)))
1622
end
17-
function TestClass:Test3(a,b)
23+
function TestClass.Test3(self,a,b)
1824
return ""
1925
end
2026
function Activate()
@@ -98,4 +104,4 @@ function Activate()
98104
::switchDone4::
99105
--------Switch statement end--------
100106
end
101-
local a = TestClass(3)
107+
local a = TestClass.new(3)

0 commit comments

Comments
 (0)