1+ import * as ts from "typescript" ;
2+
3+ import { TSHelper as tsEx } from "./TSHelper" ;
4+
5+ class TranspileError extends Error {
6+ node : ts . Node ;
7+ constructor ( message : string , node : ts . Node ) {
8+ super ( message ) ;
9+ this . node = node ;
10+ }
11+ }
12+
13+ export class LuaTranspiler {
14+ // Transpile a source file
15+ static transpileSourceFile ( node : ts . SourceFile ) : string {
16+ return this . transpileBlock ( node , "" ) ;
17+ }
18+
19+ // Transpile a block
20+ static transpileBlock ( node : ts . Node , indent : string ) : string {
21+ let result = "" ;
22+
23+ node . forEachChild ( child => {
24+ result += this . transpileNode ( child , indent ) ;
25+ } )
26+
27+ return result ;
28+ }
29+
30+ // Transpile a node of unknown kind.
31+ static transpileNode ( node : ts . Node , indent : string ) : string {
32+ //Ignore declarations
33+ if ( tsEx . getChildrenOfType ( node , child => child . kind == ts . SyntaxKind . DeclareKeyword ) . length > 0 ) return "" ;
34+
35+ switch ( node . kind ) {
36+ case ts . SyntaxKind . ClassDeclaration :
37+ return this . transpileClass ( < ts . ClassDeclaration > node , indent ) ;
38+ case ts . SyntaxKind . FunctionDeclaration :
39+ return this . transpileFunctionDeclaration ( < ts . FunctionDeclaration > node , "" , indent ) ;
40+ case ts . SyntaxKind . VariableStatement :
41+ return indent + this . transpileVariableStatement ( < ts . VariableStatement > node ) + "\n" ;
42+ case ts . SyntaxKind . ExpressionStatement :
43+ return indent + this . transpileExpression ( < ts . Expression > tsEx . getChildren ( node ) [ 0 ] ) + "\n" ;
44+ case ts . SyntaxKind . ReturnStatement :
45+ return indent + this . transpileReturn ( < ts . ReturnStatement > node ) + "\n" ;
46+ case ts . SyntaxKind . EndOfFileToken :
47+ return "" ;
48+ default :
49+ throw new TranspileError ( "Unsupported node kind: " + tsEx . enumName ( node . kind , ts . SyntaxKind ) , node ) ;
50+ }
51+ }
52+
53+ static transpileReturn ( node : ts . ReturnStatement ) : string {
54+ return "return " + this . transpileExpression ( tsEx . getChildren ( node ) [ 0 ] ) ;
55+ }
56+
57+ static transpileExpression ( node : ts . Node ) : string {
58+ switch ( node . kind ) {
59+ case ts . SyntaxKind . BinaryExpression :
60+ return this . transpileBinaryExpression ( < ts . BinaryExpression > node ) ;
61+ case ts . SyntaxKind . CallExpression :
62+ return this . transpileCallExpression ( < ts . CallExpression > node ) ;
63+ case ts . SyntaxKind . PropertyAccessExpression :
64+ return this . transpilePropertyAccessExpression ( < ts . PropertyAccessExpression > node ) ;
65+ case ts . SyntaxKind . Identifier :
66+ // For identifiers simply return their name
67+ return ( < ts . Identifier > node ) . text ;
68+ case ts . SyntaxKind . StringLiteral :
69+ const text = ( < ts . StringLiteral > node ) . text ;
70+ return `"${ text } "` ;
71+ case ts . SyntaxKind . NumericLiteral :
72+ return ( < ts . NumericLiteral > node ) . text ;
73+ case ts . SyntaxKind . TrueKeyword :
74+ return "true" ;
75+ case ts . SyntaxKind . FalseKeyword :
76+ return "false" ;
77+ case ts . SyntaxKind . ArrayLiteralExpression :
78+ return this . transpileArrayLiteral ( node ) ;
79+ case ts . SyntaxKind . ObjectLiteralExpression :
80+ return this . transpileObjectLiteral ( node )
81+ default :
82+ throw new TranspileError ( "Unsupported expression kind: " + tsEx . enumName ( node . kind , ts . SyntaxKind ) , node ) ;
83+ }
84+ }
85+
86+ static transpileBinaryExpression ( node : ts . BinaryExpression ) : string {
87+ let [ lhs , operator , rhs ] = tsEx . getChildren ( node ) ;
88+ return this . transpileExpression ( lhs ) + ts . tokenToString ( operator . kind ) + this . transpileExpression ( rhs ) ;
89+ }
90+
91+ static transpileCallExpression ( node : ts . CallExpression ) : string {
92+ const children = tsEx . getChildren ( node ) ;
93+
94+ let callPath = this . transpileExpression ( children [ 0 ] ) ;
95+ // Remove last . with :
96+ if ( callPath . indexOf ( "." ) > - 1 ) {
97+ callPath = callPath . substr ( 0 , callPath . lastIndexOf ( "." ) ) + ":" + callPath . substr ( callPath . lastIndexOf ( "." ) + 1 ) ;
98+ }
99+
100+ const parameters = [ ] ;
101+ children . slice ( 1 ) . forEach ( param => {
102+ parameters . push ( this . transpileExpression ( param ) ) ;
103+ } )
104+
105+ return `${ callPath } (${ parameters . join ( "," ) } )` ;
106+ }
107+
108+ static transpilePropertyAccessExpression ( node : ts . PropertyAccessExpression ) : string {
109+ let parts = [ ] ;
110+ node . forEachChild ( child => {
111+ switch ( child . kind ) {
112+ case ts . SyntaxKind . ThisKeyword :
113+ parts . push ( "self" ) ;
114+ break ;
115+ case ts . SyntaxKind . Identifier :
116+ parts . push ( ( < ts . Identifier > child ) . escapedText ) ;
117+ break ;
118+ case ts . SyntaxKind . CallExpression :
119+ parts . push ( this . transpileCallExpression ( < ts . CallExpression > child ) ) ;
120+ break ;
121+ case ts . SyntaxKind . PropertyAccessExpression :
122+ parts . push ( this . transpilePropertyAccessExpression ( < ts . PropertyAccessExpression > child ) ) ;
123+ break ;
124+ default :
125+ throw new TranspileError ( "Unsupported access kind: " + tsEx . enumName ( child . kind , ts . SyntaxKind ) , node ) ;
126+ }
127+ } ) ;
128+ return parts . join ( "." ) ;
129+ }
130+
131+ // Transpile a variable statement
132+ static transpileVariableStatement ( node : ts . VariableStatement ) : string {
133+ let result = "" ;
134+
135+ let list = tsEx . getFirstChildOfType < ts . VariableDeclarationList > ( node , ts . isVariableDeclarationList ) ;
136+ list . forEachChild ( declaration => {
137+ result += this . transpileVariableDeclaration ( < ts . VariableDeclaration > declaration ) ;
138+ } ) ;
139+
140+ return result ;
141+ }
142+
143+ static transpileVariableDeclaration ( node : ts . VariableDeclaration ) : string {
144+ // Find variable identifier
145+ const identifier = tsEx . getFirstChildOfType < ts . Identifier > ( node , ts . isIdentifier ) ;
146+ const valueLiteral = tsEx . getChildren ( node ) [ 1 ] ;
147+
148+ const value = this . transpileExpression ( valueLiteral ) ;
149+
150+ return `local ${ identifier . escapedText } = ${ value } ` ;
151+ }
152+
153+ static transpileFunctionDeclaration ( node : ts . Declaration , path : string , indent : string ) : string {
154+ let result = "" ;
155+ const identifier = tsEx . getFirstChildOfType < ts . Identifier > ( node , ts . isIdentifier ) ;
156+ const methodName = identifier . escapedText ;
157+ const parameters = tsEx . getChildrenOfType < ts . ParameterDeclaration > ( node , ts . isParameter ) ;
158+ const block = tsEx . getFirstChildOfType < ts . Block > ( node , ts . isBlock ) ;
159+
160+ // Build parameter string
161+ let paramNames = [ ] ;
162+ parameters . forEach ( param => {
163+ paramNames . push ( tsEx . getFirstChildOfType < ts . Identifier > ( param , ts . isIdentifier ) . escapedText ) ;
164+ } ) ;
165+
166+ // Build function header
167+ result += indent + `function ${ path } ${ methodName } (${ paramNames . join ( "," ) } )\n` ;
168+
169+ result += this . transpileBlock ( block , indent + " " ) ;
170+
171+ // Close function block
172+ result += indent + "end\n" ;
173+
174+ return result ;
175+ }
176+
177+ // Transpile a class declaration
178+ static transpileClass ( node : ts . ClassDeclaration , indent : string ) : string {
179+
180+ // Find first identifier
181+ const identifierNode = tsEx . getFirstChildOfType < ts . Identifier > ( node , child => ts . isIdentifier ( child ) ) ;
182+
183+ // Write class declaration
184+ const className = < string > identifierNode . escapedText ;
185+ let result = indent + `${ className } = ${ className } or {}\n` ;
186+
187+ // Get all properties
188+ const properties = tsEx . getChildrenOfType < ts . PropertyDeclaration > ( node , ts . isPropertyDeclaration ) ;
189+
190+ let staticFields : ts . PropertyDeclaration [ ] = [ ] ;
191+ let instanceFields : ts . PropertyDeclaration [ ] = [ ] ;
192+
193+ // Divide properties in static and instance fields
194+ for ( const p of properties ) {
195+ // Check if property is static
196+ const isStatic = tsEx . getChildrenOfType ( p , child => child . kind == ts . SyntaxKind . StaticKeyword ) . length > 0 ;
197+
198+ // Find value assignment
199+ const assignments = tsEx . getChildrenOfType ( p , tsEx . isValueType ) ;
200+
201+ // [0] is name literal, [1] is value, ignore fields with no assigned value
202+ if ( assignments . length < 2 )
203+ continue ;
204+
205+ if ( isStatic ) {
206+ staticFields . push ( p ) ;
207+ } else {
208+ instanceFields . push ( p ) ;
209+ }
210+ }
211+
212+ // Add static declarations
213+ for ( const f of staticFields ) {
214+ // Get identifier
215+ const fieldIdentifier = tsEx . getFirstChildOfType < ts . Identifier > ( f , ts . isIdentifier ) ;
216+ const fieldName = fieldIdentifier . escapedText ;
217+
218+ // Get value at index 1 (index 0 is field name)
219+ const valueNode = tsEx . getChildrenOfType < ts . Node > ( f , tsEx . isValueType ) [ 1 ] ;
220+ let value = this . transpileExpression ( valueNode ) ;
221+
222+ // Build lua assignment string
223+ result += indent + `${ className } .${ fieldName } = ${ value } \n` ;
224+ }
225+
226+ // Try to find constructor
227+ const constructor = tsEx . getFirstChildOfType < ts . ConstructorDeclaration > ( node , ts . isConstructorDeclaration ) ;
228+ if ( constructor ) {
229+ // Add constructor plus initialisation of instance fields
230+ result += indent + this . transpileConstructor ( constructor , className , instanceFields , indent ) ;
231+ } else {
232+ // No constructor, make one to set all instance fields if there are any
233+ if ( instanceFields . length > 0 ) {
234+ // Create empty constructor and add instance fields
235+ result += indent + this . transpileConstructor ( ts . createConstructor ( [ ] , [ ] , [ ] , ts . createBlock ( [ ] , true ) ) , className , instanceFields , indent ) ;
236+ }
237+ }
238+
239+ // Find all methods
240+ const methods = tsEx . getChildrenOfType < ts . MethodDeclaration > ( node , ts . isMethodDeclaration ) ;
241+ methods . forEach ( method => {
242+ result += this . transpileFunctionDeclaration ( method , `${ className } :` , indent ) ;
243+ } ) ;
244+
245+ return result ;
246+ }
247+
248+ static transpileConstructor ( node : ts . ConstructorDeclaration , className : string , instanceFields : ts . PropertyDeclaration [ ] , indent : string ) : string {
249+ let result = indent + `function ${ className } :constructor()\n` ;
250+
251+ // Add in instance field declarations
252+ for ( const f of instanceFields ) {
253+ // Get identifier
254+ const fieldIdentifier = tsEx . getFirstChildOfType < ts . Identifier > ( f , ts . isIdentifier ) ;
255+ const fieldName = fieldIdentifier . escapedText ;
256+
257+ // Get value at index 1 (index 0 is field name)
258+ const valueNode = tsEx . getChildrenOfType < ts . Node > ( f , tsEx . isValueType ) [ 1 ] ;
259+ let value = this . transpileExpression ( valueNode ) ;
260+
261+ result += indent + ` self.${ fieldName } = ${ value } \n` ;
262+ }
263+
264+ // Transpile constructor body
265+ tsEx . getChildrenOfType < ts . Block > ( node , ts . isBlock ) . forEach ( child => {
266+ result += this . transpileBlock ( child , indent + " " ) ;
267+ } ) ;
268+
269+ return result + `${ indent } end\n` ;
270+ }
271+
272+ static transpileArrayLiteral ( node : ts . Node ) : string {
273+ let values = [ ] ;
274+
275+ node . forEachChild ( child => {
276+ values . push ( this . transpileExpression ( child ) ) ;
277+ } ) ;
278+
279+ return "{" + values . join ( "," ) + "}" ;
280+ }
281+
282+ static transpileObjectLiteral ( node : ts . Node ) : string {
283+ let properties = [ ] ;
284+ // Add all property assignments
285+ tsEx . getChildrenOfType < ts . PropertyAssignment > ( node , ts . isPropertyAssignment ) . forEach ( assignment => {
286+ const [ key , value ] = tsEx . getChildren ( assignment ) ;
287+ if ( ts . isIdentifier ( key ) ) {
288+ properties . push ( `["${ key . escapedText } "]=` + this . transpileExpression ( value ) ) ;
289+ } else {
290+ const index = this . transpileExpression ( < ts . Expression > key ) ;
291+ properties . push ( `[${ index } ]=` + this . transpileExpression ( value ) ) ;
292+ }
293+ } ) ;
294+
295+ return "{" + properties . join ( "," ) + "}" ;
296+ }
297+ }
0 commit comments