├── lib ├── regex.txt └── func.json ├── gen ├── TranslateOption.js └── TsCollector.js ├── lua ├── trycatch.lua ├── stringutil.lua ├── tableutil.lua ├── class.lua ├── date.lua └── dmc_lua │ └── lua_class.lua ├── src ├── gen │ ├── TranslateOption.ts │ ├── TsCollector.ts │ └── LuaMaker.ts └── index.ts ├── LICENSE ├── package.json ├── index.d.ts ├── .gitignore ├── .npmignore ├── tsconfig.json ├── index.js └── README.md /lib/regex.txt: -------------------------------------------------------------------------------- 1 | \\, \\ 2 | \n, \n 3 | \r\n, \r\n 4 | \s, %s 5 | \s+, %s+ 6 | [\n|\r]+, [\n|\r]+, -------------------------------------------------------------------------------- /gen/TranslateOption.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | -------------------------------------------------------------------------------- /lib/func.json: -------------------------------------------------------------------------------- 1 | { 2 | "push": "table.insert", 3 | "concat": "table.merge", 4 | "GetType": "typeof", 5 | "replace": "gsub", 6 | "charAt": "sub", 7 | "indexOf": "find" 8 | } -------------------------------------------------------------------------------- /lua/trycatch.lua: -------------------------------------------------------------------------------- 1 | -- 异常捕获 2 | function try_catch(block) 3 | local main = block.main 4 | local catch = block.catch 5 | local finally = block.finally 6 | 7 | assert(main) 8 | 9 | -- try to call it 10 | local ok, errors = xpcall(main, debug.traceback) 11 | if not ok then 12 | -- run the catch function 13 | if catch then 14 | catch(errors) 15 | end 16 | end 17 | 18 | -- run the finally function 19 | if finally then 20 | finally(ok, errors) 21 | end 22 | 23 | -- ok? 24 | if ok then 25 | return errors 26 | end 27 | end -------------------------------------------------------------------------------- /lua/stringutil.lua: -------------------------------------------------------------------------------- 1 | string.trim = function(s) 2 | return (s:gsub("^%s*(.-)%s*$", "%1")) 3 | end 4 | 5 | string.split = function(str, pat) 6 | local t = {} -- NOTE: use {n = 0} in Lua-5.0 7 | local fpat = "(.-)" .. pat 8 | local last_end = 1 9 | local s, e, cap = str:find(fpat, 1) 10 | while s do 11 | if s ~= 1 or cap ~= "" then 12 | table.insert(t,cap) 13 | end 14 | last_end = e+1 15 | s, e, cap = str:find(fpat, last_end) 16 | end 17 | if last_end <= #str then 18 | cap = str:sub(last_end) 19 | table.insert(t, cap) 20 | end 21 | return t 22 | end -------------------------------------------------------------------------------- /lua/tableutil.lua: -------------------------------------------------------------------------------- 1 | table.merge = function(a, b) 2 | local c = {} 3 | local flag 4 | 5 | for k,v in pairs(a) do 6 | flag = false 7 | for key,value in pairs(c) do 8 | if value == v then 9 | flag = true 10 | end 11 | end 12 | if flag == false then 13 | table.insert(c,v) 14 | end 15 | end 16 | 17 | if b then 18 | for k,v in pairs(b) do 19 | flag = false 20 | for key,value in pairs(c) do 21 | if value == v then 22 | flag = true 23 | end 24 | end 25 | if flag == false then 26 | table.insert(c,v) 27 | end 28 | end 29 | end 30 | 31 | return c 32 | end -------------------------------------------------------------------------------- /src/gen/TranslateOption.ts: -------------------------------------------------------------------------------- 1 | export interface TranslateOption { 2 | /**生成lua代码文件后缀名,默认为'.lua' */ 3 | ext?: string, 4 | /**lua代码风格,默认适配xlua */ 5 | style?: 'xlua' | null, 6 | /**是否在生成的lua代码中,增加ts2lua认为有必要人工处理的提示,默认为true */ 7 | addTip?: boolean, 8 | /**函数名替换配置json文件路径,默认为lib\\func.json */ 9 | funcReplConfJson?: string, 10 | /**正则表达式替换配置txt文件路径,默认为lib\\regex.txt */ 11 | regexReplConfTxt?: string, 12 | /**对于没有替换配置的正则表达式,是否尝试简单翻译成lua,默认false。如果为true,则将正则表达式翻译为字符串,将转义符翻译成%。 */ 13 | translateRegex?: boolean, 14 | /**输出未识别的正则表达式的txt文件路径,默认不输出 */ 15 | traceUnknowRegex?: string, 16 | /**是否忽略代码块中单纯的成员表达式,默认为true */ 17 | ignoreNoUsedExp?: boolean, 18 | /**字符串处理函数 */ 19 | strLiteralProcessor?: (str: string) => string | null 20 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Halliwood 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts2lua", 3 | "version": "1.0.69", 4 | "description": "Change typescript to lua.", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "scripts": { 8 | "test": "tsc && node index.js", 9 | "build": "tsc", 10 | "release": "npm config set registry=https://registry.npmjs.org && npm version patch && npm publish --access=public" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/Halliwood/ts2lua.git" 15 | }, 16 | "keywords": [ 17 | "typescript", 18 | "lua" 19 | ], 20 | "author": "Halliwood", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/Halliwood/ts2lua/issues" 24 | }, 25 | "homepage": "https://github.com/Halliwood/ts2lua#readme", 26 | "declaration": true, 27 | "dependencies": { 28 | "@types/string-format": "^2.0.0", 29 | "@typescript-eslint/typescript-estree": "^1.13.0", 30 | "string-format": "^2.0.0", 31 | "typescript": "^3.5.3" 32 | }, 33 | "devDependencies": { 34 | "@types/node": "^12.6.8", 35 | "@types/typescript": "^2.0.0", 36 | "dtsmake": "0.0.10" 37 | }, 38 | "files": [ 39 | "index.js", 40 | "index.d.ts", 41 | "gen/LuaMaker.js", 42 | "gen/TsCollector.js", 43 | "lib/func.json", 44 | "lib/regex.txt", 45 | "lua/class.lua", 46 | "lua/date.lua", 47 | "lua/trycatch.lua", 48 | "lua/stringutil.lua", 49 | "lua/tableutil.lua", 50 | "lua/dmc_lua/lua_class.lua" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /lua/class.lua: -------------------------------------------------------------------------------- 1 | Class = {}; 2 | Class.__index = Class 3 | 4 | Class.name = "Object"; 5 | 6 | local Class_Constructor = {}; 7 | Class_Constructor.__call = function (type, ...) 8 | local instance = {}; 9 | instance.class = type; 10 | setmetatable(instance, type.prototype); 11 | instance:ctor(...) 12 | return instance; 13 | end 14 | setmetatable(Class, Class_Constructor); 15 | Class.__call = Class_Constructor.__call; 16 | 17 | function Class:subclass(typeName) 18 | -- 以传入类型名称作为全局变量名称创建table 19 | _G[typeName] = {}; 20 | 21 | -- 设置元方法__index,并绑定父级类型作为元表 22 | local subtype = _G[typeName]; 23 | 24 | subtype.name = typeName; 25 | subtype.super = self; 26 | subtype.__call = Class_Constructor.__call; 27 | subtype.__index = subtype; 28 | setmetatable(subtype, self); 29 | 30 | -- 创建prototype并绑定父类prototype作为元表 31 | subtype.prototype = {}; 32 | subtype.prototype.__index = subtype.prototype; 33 | subtype.prototype.__gc = self.prototype.__gc; 34 | subtype.prototype.ctor = self.prototype.ctor; 35 | subtype.prototype.__tostring = self.prototype.__tostring; 36 | subtype.prototype.instanceof = self.prototype.instanceof; 37 | setmetatable(subtype.prototype, self.prototype); 38 | 39 | return subtype; 40 | end 41 | 42 | Class.prototype = {}; 43 | Class.prototype.__index = Class.prototype; 44 | -- Class.prototype.__gc = function (instance) 45 | -- print(instance, "destroy"); 46 | -- end 47 | Class.prototype.ctor = function(instance) 48 | end 49 | 50 | Class.prototype.__tostring = function (instance) 51 | return "[" .. instance.class.name .." object]"; 52 | end 53 | 54 | Class.prototype.instanceof = function(instance, typeClass) 55 | if typeClass == nil then 56 | return false 57 | end 58 | 59 | if instance.class == typeClass then 60 | return true 61 | end 62 | 63 | local theSuper = instance.class.super 64 | while(theSuper ~= nil) do 65 | if theSuper == typeClass then 66 | return true 67 | end 68 | theSuper = theSuper.super 69 | end 70 | return false 71 | end -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for index.js 2 | // Project: https://github.com/Halliwood/ts2lua 3 | // Definitions by: Halliwood 4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped 5 | 6 | export interface TranslateOption { 7 | /**生成lua代码文件后缀名,默认为'.lua' */ 8 | ext?: string, 9 | /**lua代码风格,默认适配xlua */ 10 | style?: 'xlua' | null, 11 | /**是否在生成的lua代码中,增加ts2lua认为有必要人工处理的提示,默认为true */ 12 | addTip?: boolean, 13 | /**函数名替换配置json文件路径,默认为lib\\func.json */ 14 | funcReplConfJson?: string, 15 | /**正则表达式替换配置json文件路径,默认为lib\\regex.json */ 16 | regexReplConfJson?: string, 17 | /**对于没有替换配置的正则表达式,是否尝试简单翻译成lua,默认false。如果为true,则将正则表达式翻译为字符串,将转义符翻译成%。 */ 18 | translateRegex?: boolean, 19 | /**输出未识别的正则表达式的文件路径,默认不输出 */ 20 | traceUnknowRegex?: string, 21 | /**是否忽略代码块中单纯的成员表达式,默认为true */ 22 | ignoreNoUsedExp?: boolean, 23 | /**字符串处理函数 */ 24 | strLiteralProcessor?: (str: string) => string | null 25 | } 26 | 27 | /** 28 | * 29 | */ 30 | declare var fs : /*no type*/{}; 31 | 32 | /** 33 | * 34 | */ 35 | declare var lm : /*no type*/{}; 36 | 37 | /** 38 | * 39 | */ 40 | declare var luaFilesToCopy : Array; 41 | 42 | /** 43 | * 44 | */ 45 | declare var inputFolder : string; 46 | 47 | /** 48 | * 49 | */ 50 | declare var outputFolder : string; 51 | 52 | /** 53 | * Translate the input code string. 54 | * @param tsCode input code string. 55 | */ 56 | export declare function translate(tsCode : string, option ?: TranslateOption): string; 57 | 58 | /** 59 | * 60 | */ 61 | declare var devMode : boolean; 62 | 63 | /** 64 | * 65 | */ 66 | declare var fileCnt : number; 67 | 68 | /** 69 | * 70 | */ 71 | declare var luaExt : string; 72 | 73 | /** 74 | * Translate typescript files from the given input path and write lua files into the given output path. 75 | * @param inputPath input path which contains typescript files to translate. 76 | * @param outputPath output path where to write lua files into. 77 | * @param option translate option 78 | */ 79 | export declare function translateFiles(inputPath : string, outputPath : string, option ?: TranslateOption): void; 80 | 81 | /** 82 | * 83 | * @param dirPath 84 | */ 85 | declare function readDir(dirPath : string): void; 86 | 87 | /** 88 | * 89 | * @param filePath 90 | */ 91 | declare function doTranslateFile(filePath : string): void; 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Specified 2 | **/test/ 3 | smartCode.js 4 | **/robot/ 5 | **/lua/*.lua 6 | !**/lua/date.lua 7 | !**/lua/trycatch.lua 8 | !**/lua/stringutil.lua 9 | !**/lua/tableutil.lua 10 | **/lua/dmc_lua/*.lua 11 | **/lua/dmc_lua/lib/bit/*.lua 12 | **/lua/dmc_lua/lua_bytearray/*.lua 13 | !**/lua/dmc_lua/lua_class.lua 14 | 15 | # Logs 16 | logs 17 | *.log 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | lerna-debug.log* 22 | 23 | # Diagnostic reports (https://nodejs.org/api/report.html) 24 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 25 | 26 | # Runtime data 27 | pids 28 | *.pid 29 | *.seed 30 | *.pid.lock 31 | 32 | # Directory for instrumented libs generated by jscoverage/JSCover 33 | lib-cov 34 | 35 | # Coverage directory used by tools like istanbul 36 | coverage 37 | *.lcov 38 | 39 | # nyc test coverage 40 | .nyc_output 41 | 42 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | bower_components 47 | 48 | # node-waf configuration 49 | .lock-wscript 50 | 51 | # Compiled binary addons (https://nodejs.org/api/addons.html) 52 | build/Release 53 | 54 | # Dependency directories 55 | node_modules/ 56 | jspm_packages/ 57 | 58 | # Snowpack dependency directory (https://snowpack.dev/) 59 | web_modules/ 60 | 61 | # TypeScript cache 62 | *.tsbuildinfo 63 | 64 | # Optional npm cache directory 65 | .npm 66 | 67 | # Optional eslint cache 68 | .eslintcache 69 | 70 | # Microbundle cache 71 | .rpt2_cache/ 72 | .rts2_cache_cjs/ 73 | .rts2_cache_es/ 74 | .rts2_cache_umd/ 75 | 76 | # Optional REPL history 77 | .node_repl_history 78 | 79 | # Output of 'npm pack' 80 | *.tgz 81 | 82 | # Yarn Integrity file 83 | .yarn-integrity 84 | 85 | # dotenv environment variables file 86 | .env 87 | .env.test 88 | 89 | # parcel-bundler cache (https://parceljs.org/) 90 | .cache 91 | .parcel-cache 92 | 93 | # Next.js build output 94 | .next 95 | out 96 | 97 | # Nuxt.js build / generate output 98 | .nuxt 99 | dist 100 | 101 | # Gatsby files 102 | .cache/ 103 | # Comment in the public line in if your project uses Gatsby and not Next.js 104 | # https://nextjs.org/blog/next-9-1#public-directory-support 105 | # public 106 | 107 | # vuepress build output 108 | .vuepress/dist 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Source code 2 | src 3 | 4 | # Specified 5 | **/test/ 6 | smartCode.js 7 | **/robot/ 8 | **/lua/*.lua 9 | !**/lua/date.lua 10 | !**/lua/trycatch.lua 11 | !**/lua/stringutil.lua 12 | !**/lua/tableutil.lua 13 | **/lua/dmc_lua/*.lua 14 | **/lua/dmc_lua/lib/bit/*.lua 15 | **/lua/dmc_lua/lua_bytearray/*.lua 16 | !**/lua/dmc_lua/lua_class.lua 17 | 18 | # Logs 19 | logs 20 | *.log 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | lerna-debug.log* 25 | 26 | # Diagnostic reports (https://nodejs.org/api/report.html) 27 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 28 | 29 | # Runtime data 30 | pids 31 | *.pid 32 | *.seed 33 | *.pid.lock 34 | 35 | # Directory for instrumented libs generated by jscoverage/JSCover 36 | lib-cov 37 | 38 | # Coverage directory used by tools like istanbul 39 | coverage 40 | *.lcov 41 | 42 | # nyc test coverage 43 | .nyc_output 44 | 45 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 46 | .grunt 47 | 48 | # Bower dependency directory (https://bower.io/) 49 | bower_components 50 | 51 | # node-waf configuration 52 | .lock-wscript 53 | 54 | # Compiled binary addons (https://nodejs.org/api/addons.html) 55 | build/Release 56 | 57 | # Dependency directories 58 | node_modules/ 59 | jspm_packages/ 60 | 61 | # Snowpack dependency directory (https://snowpack.dev/) 62 | web_modules/ 63 | 64 | # TypeScript cache 65 | *.tsbuildinfo 66 | 67 | # Optional npm cache directory 68 | .npm 69 | 70 | # Optional eslint cache 71 | .eslintcache 72 | 73 | # Microbundle cache 74 | .rpt2_cache/ 75 | .rts2_cache_cjs/ 76 | .rts2_cache_es/ 77 | .rts2_cache_umd/ 78 | 79 | # Optional REPL history 80 | .node_repl_history 81 | 82 | # Output of 'npm pack' 83 | *.tgz 84 | 85 | # Yarn Integrity file 86 | .yarn-integrity 87 | 88 | # dotenv environment variables file 89 | .env 90 | .env.test 91 | 92 | # parcel-bundler cache (https://parceljs.org/) 93 | .cache 94 | .parcel-cache 95 | 96 | # Next.js build output 97 | .next 98 | out 99 | 100 | # Nuxt.js build / generate output 101 | .nuxt 102 | dist 103 | 104 | # Gatsby files 105 | .cache/ 106 | # Comment in the public line in if your project uses Gatsby and not Next.js 107 | # https://nextjs.org/blog/next-9-1#public-directory-support 108 | # public 109 | 110 | # vuepress build output 111 | .vuepress/dist 112 | 113 | # Serverless directories 114 | .serverless/ 115 | 116 | # FuseBox cache 117 | .fusebox/ 118 | 119 | # DynamoDB Local files 120 | .dynamodb/ 121 | 122 | # TernJS port file 123 | .tern-port 124 | 125 | # Stores VSCode versions used for testing VSCode extensions 126 | .vscode-test 127 | 128 | # yarn v2 129 | .yarn/cache 130 | .yarn/unplugged 131 | .yarn/build-state.yml 132 | .yarn/install-state.gz 133 | .pnp.* 134 | -------------------------------------------------------------------------------- /gen/TsCollector.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var ast_node_types_1 = require("@typescript-eslint/typescript-estree/dist/ts-estree/ast-node-types"); 4 | var TsCollector = /** @class */ (function () { 5 | function TsCollector() { 6 | this.classMap = {}; 7 | this.moduleMap = {}; 8 | this.enumMap = {}; 9 | this.moduleName = ''; 10 | } 11 | TsCollector.prototype.collect = function (ast) { 12 | this.moduleName = ''; 13 | for (var i = 0, len = ast.body.length; i < len; i++) { 14 | var theBody = ast.body[i]; 15 | this.processAST(theBody); 16 | } 17 | }; 18 | TsCollector.prototype.processAST = function (ast) { 19 | switch (ast.type) { 20 | case ast_node_types_1.AST_NODE_TYPES.ClassDeclaration: 21 | this.processClassDeclaration(ast); 22 | break; 23 | case ast_node_types_1.AST_NODE_TYPES.TSEnumDeclaration: 24 | this.processTSEnumDeclaration(ast); 25 | break; 26 | case ast_node_types_1.AST_NODE_TYPES.ExportNamedDeclaration: 27 | this.processExportNamedDeclaration(ast); 28 | break; 29 | case ast_node_types_1.AST_NODE_TYPES.TSModuleBlock: 30 | this.processTSModuleBlock(ast); 31 | break; 32 | case ast_node_types_1.AST_NODE_TYPES.TSModuleDeclaration: 33 | this.processTSModuleDeclaration(ast); 34 | break; 35 | default: 36 | break; 37 | } 38 | }; 39 | TsCollector.prototype.processTSModuleBlock = function (ast) { 40 | for (var _i = 0, _a = ast.body; _i < _a.length; _i++) { 41 | var stt = _a[_i]; 42 | this.processAST(stt); 43 | if (stt.type == ast_node_types_1.AST_NODE_TYPES.ExportNamedDeclaration && stt.declaration.type == ast_node_types_1.AST_NODE_TYPES.FunctionDeclaration) { 44 | var funcName = stt.declaration.id.name; 45 | this.moduleMap[this.moduleName].funcs[funcName] = 1; 46 | } 47 | } 48 | }; 49 | TsCollector.prototype.processTSModuleDeclaration = function (ast) { 50 | this.moduleName = this.getId(ast.id); 51 | var info = { type: ast_node_types_1.AST_NODE_TYPES.TSModuleDeclaration, name: this.moduleName, properties: {}, funcs: {}, classes: {} }; 52 | this.moduleMap[this.moduleName] = info; 53 | this.processAST(ast.body); 54 | this.moduleName = ''; 55 | }; 56 | TsCollector.prototype.processClassDeclaration = function (ast) { 57 | var info = { type: ast_node_types_1.AST_NODE_TYPES.ClassDeclaration, name: ast.id.name, properties: {}, funcs: {} }; 58 | for (var _i = 0, _a = ast.body.body; _i < _a.length; _i++) { 59 | var cbb = _a[_i]; 60 | if (cbb.type == ast_node_types_1.AST_NODE_TYPES.ClassProperty) { 61 | var cp = cbb; 62 | var cpInfo = { type: ast_node_types_1.AST_NODE_TYPES.ClassProperty, name: cp.key.name, isStatic: cp.static, varType: cp.typeAnnotation }; 63 | info.properties[cpInfo.name] = cpInfo; 64 | } 65 | else if (cbb.type == ast_node_types_1.AST_NODE_TYPES.MethodDefinition) { 66 | var md = cbb; 67 | var mdInfo = { type: ast_node_types_1.AST_NODE_TYPES.MethodDefinition, name: md.key.name, isStatic: md.static, returnType: md.value.returnType }; 68 | info.funcs[mdInfo.name] = mdInfo; 69 | } 70 | } 71 | this.classMap[ast.id.name] = info; 72 | if (this.moduleName) { 73 | this.classMap[this.moduleName + '.' + ast.id.name] = info; 74 | this.moduleMap[this.moduleName].classes[ast.id.name] = info; 75 | } 76 | }; 77 | TsCollector.prototype.processTSEnumDeclaration = function (ast) { 78 | var info = { type: ast_node_types_1.AST_NODE_TYPES.TSEnumDeclaration, name: ast.id.name, members: {} }; 79 | for (var _i = 0, _a = ast.members; _i < _a.length; _i++) { 80 | var em = _a[_i]; 81 | var emInfo = { type: ast_node_types_1.AST_NODE_TYPES.TSEnumMember, name: this.getId(em.id) }; 82 | info.members[emInfo.name] = emInfo; 83 | } 84 | this.enumMap[info.name] = info; 85 | }; 86 | TsCollector.prototype.processExportNamedDeclaration = function (ast) { 87 | this.processAST(ast.declaration); 88 | }; 89 | TsCollector.prototype.getId = function (ast) { 90 | if (ast.type == ast_node_types_1.AST_NODE_TYPES.Identifier) { 91 | return ast.name; 92 | } 93 | return ast.value; 94 | }; 95 | return TsCollector; 96 | }()); 97 | exports.TsCollector = TsCollector; 98 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | // "lib": [], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "./", /* Redirect output structure to the directory. */ 15 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "removeComments": true, /* Do not emit comments to output. */ 18 | // "noEmit": true, /* Do not emit outputs. */ 19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 21 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 22 | 23 | /* Strict Type-Checking Options */ 24 | // "strict": true, /* Enable all strict type-checking options. */ 25 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 26 | // "strictNullChecks": true, /* Enable strict null checks. */ 27 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 28 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 29 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 30 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 31 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 32 | 33 | /* Additional Checks */ 34 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 35 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 36 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 37 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 38 | 39 | /* Module Resolution Options */ 40 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 41 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 42 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 43 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 44 | // "typeRoots": [], /* List of folders to include type definitions from. */ 45 | // "types": [], /* Type declaration files to be included in compilation. */ 46 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 47 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 48 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 49 | 50 | /* Source Map Options */ 51 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 52 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 53 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 54 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 55 | 56 | /* Experimental Options */ 57 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 58 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 59 | }, 60 | "include": [ 61 | "src/*" 62 | ], 63 | "exclude": [ 64 | "node_modules" 65 | ] 66 | } -------------------------------------------------------------------------------- /src/gen/TsCollector.ts: -------------------------------------------------------------------------------- 1 | import { ArrayExpression, ArrayPattern, ArrowFunctionExpression, AssignmentExpression, AssignmentPattern, AwaitExpression, BigIntLiteral, BinaryExpression, BlockStatement, BreakStatement, CallExpression, CatchClause, ClassBody, ClassDeclaration, ClassExpression, ClassProperty, ConditionalExpression, ContinueStatement, DebuggerStatement, Decorator, DoWhileStatement, EmptyStatement, ExportAllDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExpressionStatement, ForInStatement, ForOfStatement, ForStatement, FunctionDeclaration, FunctionExpression, Identifier, IfStatement, Import, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, LabeledStatement, Literal, LogicalExpression, MemberExpression, MetaProperty, MethodDefinition, NewExpression, ObjectExpression, ObjectPattern, Program, Property, RestElement, ReturnStatement, SequenceExpression, SpreadElement, Super, SwitchCase, SwitchStatement, TaggedTemplateExpression, TemplateElement, TemplateLiteral, ThisExpression, ThrowStatement, TryStatement, UnaryExpression, UpdateExpression, VariableDeclaration, VariableDeclarator, WhileStatement, WithStatement, YieldExpression, TSEnumDeclaration, BindingName, TSAsExpression, TSInterfaceDeclaration, TSTypeAssertion, TSModuleDeclaration, TSModuleBlock, TSDeclareFunction, TSAbstractMethodDefinition, BaseNode, TSEnumMember, TSTypeAnnotation } from '@typescript-eslint/typescript-estree/dist/ts-estree/ts-estree'; 2 | import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree/dist/ts-estree/ast-node-types'; 3 | 4 | export interface TsInfoBase { 5 | type: AST_NODE_TYPES; 6 | } 7 | 8 | export interface TsPropInfo extends TsInfoBase { 9 | type: AST_NODE_TYPES.ClassProperty; 10 | name: string; 11 | isStatic: boolean; 12 | varType: TSTypeAnnotation; 13 | } 14 | 15 | export interface TsFuncInfo extends TsInfoBase { 16 | type: AST_NODE_TYPES.MethodDefinition; 17 | name: string; 18 | isStatic: boolean; 19 | returnType: TSTypeAnnotation; 20 | } 21 | 22 | export interface TsModuleInfo extends TsInfoBase { 23 | type: AST_NODE_TYPES.TSModuleDeclaration; 24 | name: string; 25 | properties: { [name: string]: TsPropInfo }; 26 | funcs: { [name: string]: 1 }; 27 | classes: { [name: string]: TsClassInfo }; 28 | } 29 | 30 | export interface TsClassInfo extends TsInfoBase { 31 | type: AST_NODE_TYPES.ClassDeclaration; 32 | name: string; 33 | properties: { [name: string]: TsPropInfo }; 34 | funcs: { [name: string]: TsFuncInfo }; 35 | } 36 | 37 | export interface TsEnumMemberInfo extends TsInfoBase { 38 | type: AST_NODE_TYPES.TSEnumMember; 39 | name: string; 40 | } 41 | 42 | export interface TsEnumInfo extends TsInfoBase { 43 | type: AST_NODE_TYPES.TSEnumDeclaration; 44 | name: string; 45 | members: { [name: string]: TsEnumMemberInfo }; 46 | } 47 | 48 | export class TsCollector { 49 | public classMap: { [name: string]: TsClassInfo } = {}; 50 | public moduleMap: { [name: string]: TsModuleInfo } = {}; 51 | public enumMap: { [name: string]: TsEnumInfo } = {}; 52 | private moduleName: string = ''; 53 | 54 | public collect(ast: Program): void { 55 | this.moduleName = ''; 56 | for(let i = 0, len = ast.body.length; i < len; i++) { 57 | let theBody = ast.body[i]; 58 | this.processAST(theBody); 59 | } 60 | } 61 | 62 | private processAST(ast: any) { 63 | switch(ast.type) { 64 | case AST_NODE_TYPES.ClassDeclaration: 65 | this.processClassDeclaration(ast as ClassDeclaration); 66 | break; 67 | case AST_NODE_TYPES.TSEnumDeclaration: 68 | this.processTSEnumDeclaration(ast as TSEnumDeclaration); 69 | break; 70 | case AST_NODE_TYPES.ExportNamedDeclaration: 71 | this.processExportNamedDeclaration(ast as ExportNamedDeclaration); 72 | break; 73 | case AST_NODE_TYPES.TSModuleBlock: 74 | this.processTSModuleBlock(ast as TSModuleBlock); 75 | break; 76 | case AST_NODE_TYPES.TSModuleDeclaration: 77 | this.processTSModuleDeclaration(ast as TSModuleDeclaration); 78 | break; 79 | default: 80 | break; 81 | } 82 | } 83 | 84 | private processTSModuleBlock(ast: TSModuleBlock) { 85 | for(let stt of ast.body) { 86 | this.processAST(stt); 87 | if(stt.type == AST_NODE_TYPES.ExportNamedDeclaration && (stt as ExportNamedDeclaration).declaration.type == AST_NODE_TYPES.FunctionDeclaration) { 88 | let funcName = ((stt as ExportNamedDeclaration).declaration as FunctionDeclaration).id.name 89 | this.moduleMap[this.moduleName].funcs[funcName] = 1; 90 | } 91 | } 92 | } 93 | 94 | 95 | private processTSModuleDeclaration(ast: TSModuleDeclaration) { 96 | this.moduleName = this.getId(ast.id); 97 | let info: TsModuleInfo = { type: AST_NODE_TYPES.TSModuleDeclaration, name: this.moduleName, properties: {}, funcs: {}, classes: {} }; 98 | this.moduleMap[this.moduleName] = info; 99 | this.processAST(ast.body); 100 | this.moduleName = ''; 101 | } 102 | 103 | private processClassDeclaration(ast: ClassDeclaration) { 104 | let info: TsClassInfo = { type: AST_NODE_TYPES.ClassDeclaration, name: ast.id.name, properties: {}, funcs: {} }; 105 | for(let cbb of ast.body.body) { 106 | if(cbb.type == AST_NODE_TYPES.ClassProperty) { 107 | let cp = cbb as ClassProperty; 108 | let cpInfo: TsPropInfo = { type: AST_NODE_TYPES.ClassProperty, name: (cp.key as Identifier).name, isStatic: cp.static, varType: cp.typeAnnotation }; 109 | info.properties[cpInfo.name] = cpInfo; 110 | } else if(cbb.type == AST_NODE_TYPES.MethodDefinition) { 111 | let md = cbb as MethodDefinition; 112 | let mdInfo: TsFuncInfo = { type: AST_NODE_TYPES.MethodDefinition, name: (md.key as Identifier).name, isStatic: md.static, returnType: md.value.returnType }; 113 | info.funcs[mdInfo.name] = mdInfo; 114 | } 115 | } 116 | this.classMap[ast.id.name] = info; 117 | if(this.moduleName) { 118 | this.classMap[this.moduleName + '.' + ast.id.name] = info; 119 | this.moduleMap[this.moduleName].classes[ast.id.name] = info; 120 | } 121 | } 122 | 123 | private processTSEnumDeclaration(ast: TSEnumDeclaration) { 124 | let info: TsEnumInfo = { type: AST_NODE_TYPES.TSEnumDeclaration, name: ast.id.name, members: {} }; 125 | for(let em of ast.members) { 126 | let emInfo: TsEnumMemberInfo = { type: AST_NODE_TYPES.TSEnumMember, name: this.getId(em.id) }; 127 | info.members[emInfo.name] = emInfo; 128 | } 129 | this.enumMap[info.name] = info; 130 | } 131 | 132 | private processExportNamedDeclaration(ast: ExportNamedDeclaration) { 133 | this.processAST(ast.declaration); 134 | } 135 | 136 | private getId(ast: Identifier | Literal): string { 137 | if(ast.type == AST_NODE_TYPES.Identifier) { 138 | return (ast as Identifier).name; 139 | } 140 | return (ast as Literal).value as string; 141 | } 142 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import path = require('path'); 3 | import util = require('util') 4 | import parser = require('@typescript-eslint/typescript-estree'); 5 | import { TranslateOption } from './gen/TranslateOption'; 6 | import { LuaMaker } from './gen/LuaMaker'; 7 | import { TsCollector } from './gen/TsCollector'; 8 | import { Program } from '@typescript-eslint/typescript-estree/dist/ts-estree/ts-estree'; 9 | 10 | const luaFilesToCopy: string[] = ['dmc_lua\\lua_class', 'trycatch', 'date', 'stringutil', 'tableutil']; 11 | 12 | const devMode: boolean = false; 13 | let fileCnt = 0; 14 | let requireContent = ''; 15 | let funcReplConf: {[func: string]: string} = {}; 16 | let regexReplConf: {[regex: string]: string} = {}; 17 | 18 | let opt: TranslateOption = { 19 | ext: '.lua', 20 | style: 'xlua', 21 | addTip: true, 22 | funcReplConfJson: path.join(__dirname, 'lib\\func.json'), 23 | regexReplConfTxt: path.join(__dirname, 'lib\\regex.txt'), 24 | translateRegex: false, 25 | traceUnknowRegex: undefined, 26 | ignoreNoUsedExp: true 27 | }; 28 | let tc = new TsCollector(); 29 | let lm = new LuaMaker(); 30 | 31 | let astMap: { [path: string]: Program } = {}; 32 | 33 | let inputFolder: string; 34 | let outputFolder: string; 35 | // translateFiles('G:\\ly\\trunk\\TsScripts', 36 | // // 'G:\\ly\\trunk\\Assets\\StreamingAssets\\luaScript', 37 | // 'test\\out', 38 | // { 39 | // ext: '.lua.txt', 40 | // translateRegex: true, 41 | // funcReplConfJson: 'lib\\func.json', 42 | // regexReplConfTxt: 'lib\\regex.txt' 43 | // }); 44 | /** 45 | * Translate the input code string. 46 | * @param tsCode input code string. 47 | */ 48 | export function translate(tsCode: string, option?: TranslateOption): string { 49 | processOption(option); 50 | 51 | const parsed = parser.parse(tsCode); 52 | tc.collect(parsed); 53 | lm.setClassMap(tc.classMap, tc.moduleMap, tc.enumMap); 54 | let luaCode = lm.toLuaBySource(parsed); 55 | collectUnknowRegex(); 56 | return luaCode; 57 | } 58 | 59 | /** 60 | * Translate typescript files from the given input path and write lua files into the given output path. 61 | * @param inputPath input path which contains typescript files to translate. 62 | * @param outputPath output path where to write lua files into. 63 | */ 64 | export function translateFiles(inputPath: string, outputPath: string, option?: TranslateOption) { 65 | processOption(option); 66 | 67 | // copy class.lua & trycatch.lua 68 | if(!fs.existsSync(outputPath)) fs.mkdirSync(outputPath, { recursive: true }); 69 | for(let luaFile of luaFilesToCopy) { 70 | let dstPath = path.join(outputPath, luaFile) + opt.ext; 71 | let dstPathDir = path.parse(dstPath).dir; 72 | if(!fs.existsSync(dstPathDir)) fs.mkdirSync(dstPathDir, { recursive: true }); 73 | fs.copyFileSync(path.join(__dirname, 'lua', luaFile) + '.lua', dstPath); 74 | } 75 | 76 | inputFolder = inputPath; 77 | outputFolder = outputPath; 78 | fileCnt = 0; 79 | let inputStat = fs.statSync(inputPath); 80 | if(inputStat.isFile()) { 81 | collectClass(inputPath); 82 | lm.setClassMap(tc.classMap, tc.moduleMap, tc.enumMap); 83 | doTranslateFile(inputPath); 84 | } else { 85 | console.log('Processing... Please wait.'); 86 | readDir(inputPath, true); 87 | console.log('Making lua... Please wait.'); 88 | lm.setClassMap(tc.classMap, tc.moduleMap, tc.enumMap); 89 | readDir(inputPath, false); 90 | } 91 | 92 | console.log("\x1B[36m%d\x1B[0m .ts files translated.", fileCnt); 93 | collectUnknowRegex(); 94 | } 95 | 96 | function readDir(dirPath: string, collectOrTranslate: boolean) { 97 | let files = fs.readdirSync(dirPath); 98 | for(let i = 0, len = files.length; i < len; i++) { 99 | let filename = files[i]; 100 | let filePath = path.join(dirPath, filename); 101 | let fileStat = fs.statSync(filePath); 102 | if(fileStat.isFile()) { 103 | let fileExt = path.extname(filename).toLowerCase(); 104 | if('.ts' == fileExt) { 105 | if(collectOrTranslate) { 106 | collectClass(filePath); 107 | } else { 108 | doTranslateFile(filePath); 109 | } 110 | } 111 | } else { 112 | readDir(filePath, collectOrTranslate); 113 | } 114 | } 115 | } 116 | 117 | function collectClass(filePath: string) { 118 | const fileContent = fs.readFileSync(filePath, 'utf-8'); 119 | const parsed = parser.parse(fileContent); 120 | astMap[filePath] = parsed; 121 | tc.collect(parsed); 122 | } 123 | 124 | function doTranslateFile(filePath: string) { 125 | if(filePath.match(/\.d\.ts$/i)) return; 126 | // console.log('parsing: ', filePath); 127 | const parsed = astMap[filePath]; 128 | 129 | let outFilePath = filePath.replace(inputFolder, outputFolder); 130 | let outFilePP = path.parse(outFilePath); 131 | 132 | if(devMode) { 133 | let str = util.inspect(parsed, true, 100); 134 | fs.writeFileSync(outFilePath.replace(/\.ts$/, '.txt'), str); 135 | } 136 | 137 | let luaContent = lm.toLuaByFile(parsed, filePath, inputFolder); 138 | if(luaContent) { 139 | let luaFilePath = outFilePath.replace(/\.ts$/, opt.ext); 140 | if(!fs.existsSync(outFilePP.dir)) fs.mkdirSync(outFilePP.dir, { recursive: true }); 141 | fs.writeFileSync(luaFilePath, luaContent); 142 | } 143 | 144 | let dotIndex = outFilePP.name.indexOf('.'); 145 | let diffDir = outFilePP.dir + '\\' + (dotIndex >= 0 ? outFilePP.name.substr(0, dotIndex) : outFilePP.name); 146 | for(let className in lm.classContentMap) { 147 | let classContent = lm.classContentMap[className]; 148 | let classFilePath = diffDir + '\\' + className + opt.ext; 149 | let fileFolder = classFilePath.substr(0, classFilePath.lastIndexOf('\\')); 150 | if(!fs.existsSync(fileFolder)) fs.mkdirSync(fileFolder, { recursive: true }); 151 | fs.writeFileSync(classFilePath, classContent); 152 | } 153 | fileCnt++; 154 | } 155 | 156 | function processOption(option?: TranslateOption) { 157 | for(let key in option) { 158 | opt[key] = option[key]; 159 | } 160 | if(opt.funcReplConfJson) { 161 | let frj = fs.readFileSync(opt.funcReplConfJson, 'utf-8'); 162 | funcReplConf = JSON.parse(frj); 163 | console.log("Using \x1B[36m%s\x1B[0m ...", opt.funcReplConfJson); 164 | // console.log(frj); 165 | } 166 | if(opt.regexReplConfTxt) { 167 | let rrt = fs.readFileSync(opt.regexReplConfTxt, 'utf-8'); 168 | let rrLines = rrt.split(/[\r\n]+/); 169 | for(let rrline of rrLines) { 170 | if(rrline) { 171 | let rrPair = rrline.split(/,\s*/); 172 | if(rrPair.length > 1) { 173 | regexReplConf[rrPair[0]] = rrPair[1]; 174 | } 175 | } 176 | } 177 | console.log("Using \x1B[36m%s\x1B[0m ...", opt.regexReplConfTxt); 178 | // console.log(rrt); 179 | } 180 | lm.setEnv(devMode, opt, funcReplConf, regexReplConf); 181 | } 182 | 183 | function collectUnknowRegex() { 184 | if(opt.traceUnknowRegex && lm.unknowRegexs.length > 0) { 185 | lm.unknowRegexs.sort(); 186 | let unknowRegexContent = ''; 187 | for(let ur of lm.unknowRegexs) { 188 | unknowRegexContent += ur + ',\n'; 189 | } 190 | fs.writeFileSync(opt.traceUnknowRegex, unknowRegexContent, 'utf-8'); 191 | 192 | console.log("\x1B[36m%d\x1B[0m unknown regular expression.", lm.unknowRegexs.length); 193 | } 194 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importStar = (this && this.__importStar) || function (mod) { 3 | if (mod && mod.__esModule) return mod; 4 | var result = {}; 5 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 6 | result["default"] = mod; 7 | return result; 8 | }; 9 | Object.defineProperty(exports, "__esModule", { value: true }); 10 | var fs = __importStar(require("fs")); 11 | var path = require("path"); 12 | var util = require("util"); 13 | var parser = require("@typescript-eslint/typescript-estree"); 14 | var LuaMaker_1 = require("./gen/LuaMaker"); 15 | var TsCollector_1 = require("./gen/TsCollector"); 16 | var luaFilesToCopy = ['dmc_lua\\lua_class', 'trycatch', 'date', 'stringutil', 'tableutil']; 17 | var devMode = false; 18 | var fileCnt = 0; 19 | var requireContent = ''; 20 | var funcReplConf = {}; 21 | var regexReplConf = {}; 22 | var opt = { 23 | ext: '.lua', 24 | style: 'xlua', 25 | addTip: true, 26 | funcReplConfJson: path.join(__dirname, 'lib\\func.json'), 27 | regexReplConfTxt: path.join(__dirname, 'lib\\regex.txt'), 28 | translateRegex: false, 29 | traceUnknowRegex: undefined, 30 | ignoreNoUsedExp: true 31 | }; 32 | var tc = new TsCollector_1.TsCollector(); 33 | var lm = new LuaMaker_1.LuaMaker(); 34 | var astMap = {}; 35 | var inputFolder; 36 | var outputFolder; 37 | // translateFiles('G:\\ly\\trunk\\TsScripts', 38 | // // 'G:\\ly\\trunk\\Assets\\StreamingAssets\\luaScript', 39 | // 'test\\out', 40 | // { 41 | // ext: '.lua.txt', 42 | // translateRegex: true, 43 | // funcReplConfJson: 'lib\\func.json', 44 | // regexReplConfTxt: 'lib\\regex.txt' 45 | // }); 46 | /** 47 | * Translate the input code string. 48 | * @param tsCode input code string. 49 | */ 50 | function translate(tsCode, option) { 51 | processOption(option); 52 | var parsed = parser.parse(tsCode); 53 | tc.collect(parsed); 54 | lm.setClassMap(tc.classMap, tc.moduleMap, tc.enumMap); 55 | var luaCode = lm.toLuaBySource(parsed); 56 | collectUnknowRegex(); 57 | return luaCode; 58 | } 59 | exports.translate = translate; 60 | /** 61 | * Translate typescript files from the given input path and write lua files into the given output path. 62 | * @param inputPath input path which contains typescript files to translate. 63 | * @param outputPath output path where to write lua files into. 64 | */ 65 | function translateFiles(inputPath, outputPath, option) { 66 | processOption(option); 67 | // copy class.lua & trycatch.lua 68 | if (!fs.existsSync(outputPath)) 69 | fs.mkdirSync(outputPath, { recursive: true }); 70 | for (var _i = 0, luaFilesToCopy_1 = luaFilesToCopy; _i < luaFilesToCopy_1.length; _i++) { 71 | var luaFile = luaFilesToCopy_1[_i]; 72 | var dstPath = path.join(outputPath, luaFile) + opt.ext; 73 | var dstPathDir = path.parse(dstPath).dir; 74 | if (!fs.existsSync(dstPathDir)) 75 | fs.mkdirSync(dstPathDir, { recursive: true }); 76 | fs.copyFileSync(path.join(__dirname, 'lua', luaFile) + '.lua', dstPath); 77 | } 78 | inputFolder = inputPath; 79 | outputFolder = outputPath; 80 | fileCnt = 0; 81 | var inputStat = fs.statSync(inputPath); 82 | if (inputStat.isFile()) { 83 | collectClass(inputPath); 84 | lm.setClassMap(tc.classMap, tc.moduleMap, tc.enumMap); 85 | doTranslateFile(inputPath); 86 | } 87 | else { 88 | console.log('Processing... Please wait.'); 89 | readDir(inputPath, true); 90 | console.log('Making lua... Please wait.'); 91 | lm.setClassMap(tc.classMap, tc.moduleMap, tc.enumMap); 92 | readDir(inputPath, false); 93 | } 94 | console.log("\x1B[36m%d\x1B[0m .ts files translated.", fileCnt); 95 | collectUnknowRegex(); 96 | } 97 | exports.translateFiles = translateFiles; 98 | function readDir(dirPath, collectOrTranslate) { 99 | var files = fs.readdirSync(dirPath); 100 | for (var i = 0, len = files.length; i < len; i++) { 101 | var filename = files[i]; 102 | var filePath = path.join(dirPath, filename); 103 | var fileStat = fs.statSync(filePath); 104 | if (fileStat.isFile()) { 105 | var fileExt = path.extname(filename).toLowerCase(); 106 | if ('.ts' == fileExt) { 107 | if (collectOrTranslate) { 108 | collectClass(filePath); 109 | } 110 | else { 111 | doTranslateFile(filePath); 112 | } 113 | } 114 | } 115 | else { 116 | readDir(filePath, collectOrTranslate); 117 | } 118 | } 119 | } 120 | function collectClass(filePath) { 121 | var fileContent = fs.readFileSync(filePath, 'utf-8'); 122 | var parsed = parser.parse(fileContent); 123 | astMap[filePath] = parsed; 124 | tc.collect(parsed); 125 | } 126 | function doTranslateFile(filePath) { 127 | if (filePath.match(/\.d\.ts$/i)) 128 | return; 129 | // console.log('parsing: ', filePath); 130 | var parsed = astMap[filePath]; 131 | var outFilePath = filePath.replace(inputFolder, outputFolder); 132 | var outFilePP = path.parse(outFilePath); 133 | if (devMode) { 134 | var str = util.inspect(parsed, true, 100); 135 | fs.writeFileSync(outFilePath.replace(/\.ts$/, '.txt'), str); 136 | } 137 | var luaContent = lm.toLuaByFile(parsed, filePath, inputFolder); 138 | if (luaContent) { 139 | var luaFilePath = outFilePath.replace(/\.ts$/, opt.ext); 140 | if (!fs.existsSync(outFilePP.dir)) 141 | fs.mkdirSync(outFilePP.dir, { recursive: true }); 142 | fs.writeFileSync(luaFilePath, luaContent); 143 | } 144 | var dotIndex = outFilePP.name.indexOf('.'); 145 | var diffDir = outFilePP.dir + '\\' + (dotIndex >= 0 ? outFilePP.name.substr(0, dotIndex) : outFilePP.name); 146 | for (var className in lm.classContentMap) { 147 | var classContent = lm.classContentMap[className]; 148 | var classFilePath = diffDir + '\\' + className + opt.ext; 149 | var fileFolder = classFilePath.substr(0, classFilePath.lastIndexOf('\\')); 150 | if (!fs.existsSync(fileFolder)) 151 | fs.mkdirSync(fileFolder, { recursive: true }); 152 | fs.writeFileSync(classFilePath, classContent); 153 | } 154 | fileCnt++; 155 | } 156 | function processOption(option) { 157 | for (var key in option) { 158 | opt[key] = option[key]; 159 | } 160 | if (opt.funcReplConfJson) { 161 | var frj = fs.readFileSync(opt.funcReplConfJson, 'utf-8'); 162 | funcReplConf = JSON.parse(frj); 163 | console.log("Using \x1B[36m%s\x1B[0m ...", opt.funcReplConfJson); 164 | // console.log(frj); 165 | } 166 | if (opt.regexReplConfTxt) { 167 | var rrt = fs.readFileSync(opt.regexReplConfTxt, 'utf-8'); 168 | var rrLines = rrt.split(/[\r\n]+/); 169 | for (var _i = 0, rrLines_1 = rrLines; _i < rrLines_1.length; _i++) { 170 | var rrline = rrLines_1[_i]; 171 | if (rrline) { 172 | var rrPair = rrline.split(/,\s*/); 173 | if (rrPair.length > 1) { 174 | regexReplConf[rrPair[0]] = rrPair[1]; 175 | } 176 | } 177 | } 178 | console.log("Using \x1B[36m%s\x1B[0m ...", opt.regexReplConfTxt); 179 | // console.log(rrt); 180 | } 181 | lm.setEnv(devMode, opt, funcReplConf, regexReplConf); 182 | } 183 | function collectUnknowRegex() { 184 | if (opt.traceUnknowRegex && lm.unknowRegexs.length > 0) { 185 | lm.unknowRegexs.sort(); 186 | var unknowRegexContent = ''; 187 | for (var _i = 0, _a = lm.unknowRegexs; _i < _a.length; _i++) { 188 | var ur = _a[_i]; 189 | unknowRegexContent += ur + ',\n'; 190 | } 191 | fs.writeFileSync(opt.traceUnknowRegex, unknowRegexContent, 'utf-8'); 192 | console.log("\x1B[36m%d\x1B[0m unknown regular expression.", lm.unknowRegexs.length); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ts2lua 2 | ts2lua是一个将TypeScript代码转换为lua代码的工具。在使用之前,建议您先阅读[lua-objects](https://github.com/dmccuskey/lua-objects "lua-objects的Github"),它是一个很不错的lua面向对象解决方案,支持多重继承、getter/setter等,ts2lua按照lua-objects的规范来处理TypeScript类。 3 | 4 | ## 安装 5 | ``` 6 | $ npm i ts2lua 7 | ``` 8 | 9 | ## 用法 10 | 转换TypeScript语句 11 | 12 | ```JavaScript 13 | const ts2lua = require('ts2lua'); 14 | const tsCode = 'let a = "Hello World!";'; 15 | const luaCode = ts2lua.translate(tsCode); 16 | console.log(luaCode); 17 | ``` 18 | 19 | TypeScript 20 | ```TypeScript 21 | import ts2lua = require('ts2lua'); 22 | 23 | let testCode = ` 24 | let num = 123; 25 | if(num > 100) { 26 | console.log(num + ' is bigger than 100!'); 27 | } else { 28 | console.log(num + ' is smaller than 100!'); 29 | } 30 | ` 31 | console.log(ts2lua.translate(testCode)); 32 | ``` 33 | 34 | 批量转换TypeScript文件 35 | 36 | ```JavaScript 37 | const ts2lua = require('ts2lua'); 38 | const inputPath = 'ts_file_root'; 39 | conse outputPath = 'lua_file_root'; 40 | ts2lua.translateFiles(inputPath, outputPath); 41 | // 指定生成lua文件后缀名 42 | ts2lua.translateFiles(inputPath, outputPath, { ext: '.lua.txt' }); 43 | ``` 44 | 45 | ## 批量转换接口的说明 46 | 使用`translateFiles`可以批量转换TypeScript代码。先来看看index.d.ts的声明: 47 | 48 | ```TypeScript 49 | /** 50 | * Translate typescript files from the given input path and write lua files into the given output path. 51 | * @param inputPath input path which contains typescript files to translate. 52 | * @param outputPath output path where to write lua files into. 53 | * @param option translate option 54 | */ 55 | export declare function translateFiles(inputPath : string, outputPath : string, option ?: TranslateOption): void; 56 | ``` 57 | 58 | 其中,必选参数`inputPath`和`outputPath`分别表示TypeScript文件目录和生成的lua文件目录。可选参数`option`表示转换选项,当前支持如下选项: 59 | 60 | ```TypeScript 61 | export interface TranslateOption { 62 | /**生成lua代码文件后缀名,默认为'.lua' */ 63 | ext?: string, 64 | /**lua代码风格,默认适配xlua */ 65 | style?: 'xlua' | null, 66 | /**是否在生成的lua代码中,增加ts2lua认为有必要人工处理的提示,默认为true */ 67 | addTip?: boolean, 68 | /**函数名替换配置json文件路径,默认为lib\\func.json */ 69 | funcReplConfJson?: string, 70 | /**正则表达式替换配置txt文件路径,默认为lib\\regex.txt */ 71 | regexReplConfTxt?: string, 72 | /**对于没有替换配置的正则表达式,是否尝试简单翻译成lua,默认false。如果为true,则将正则表达式翻译为字符串,将转义符翻译成%。 */ 73 | translateRegex?: boolean, 74 | /**输出未识别的正则表达式的文件路径,默认不输出 */ 75 | traceUnknowRegex?: string 76 | } 77 | ``` 78 | 79 | *可选字段`ext`表示生成lua文件后缀,比如可以指定为`.lua.txt`。 80 | *可选字段`style`表示生成lua代码的风格,默认是xlua风格,ts2lua会按照xlua的一些规范生成对应的lua代码,详细见下方说明。如果不使用xlua风格,请设置`style`为`null`,如下所示: 81 | 82 | ```JavaScript 83 | ts2lua.translateFiles('in', 'out', { style: null }); 84 | ``` 85 | 86 | * 可选字段`addTip`默认为true,当ts2lua遇到无法确定转换结果100%效果一致时,将在代码中插入必要的提示。比如数组下标访问、正则表达式处理等。 87 | * 可选字段`funcReplConfJson`表示用于配置函数名转换规则的json文件的存放路径。ts2lua将根据该文件的映射关系对指定的函数名进行翻译,你可以直接修改默认配置`lib\\func.json`。比如,`replace`函数将默认翻译为`gsub`。 88 | * 可选字段`regexReplConfTxt`表示用于配置正则表达式转换规则的txt文件的存放路径。ts2lua将根据该文件的映射关系对指定的正则表达式进行翻译,你可以直接修改默认配置`lib\\regex.txt`。 89 | * 可选字段`translateRegex`若为`true`,则对于正则表达式转换规则json文件中没有配置的正则表达式,ts2lua将简单的进行处理:将正则表达式翻译为字符串,将转义符翻译成%。比如`/\w+/g`将翻译成`'%w+'`。该字段默认为`false`,即原样输出(对lua来说,通常会有语法错误)。 90 | * 可选字段`traceUnknowRegex`表示未识别正则表达式的输出路径。若指定了该值,则对于正则表达式转换规则json文件中没有配置的正则表达式,ts2lua将记录到一个文件中,方便集中处理后加入到转换配置中。 91 | * 可选字段`ignoreNoUsedExp`若为`true`,则ts2lua会忽略代码块(`BlockStatement`)中没有实际用途的表达式(包括多余的成员表达式`MemberExpression`和标识`Identifier`访问)。该字段默认为`true`。如下述代码中多余的语句将被忽略: 92 | 93 | TypeScript 94 | ```TypeScript 95 | doStr() { 96 | let idx = 2; 97 | this.myArr; // 这句是多余的MemberExpression 98 | idx; // 这句是多余的Identifier 99 | console.log('idx == ' + idx); 100 | } 101 | ``` 102 | 103 | lua 104 | ```lua 105 | function doStr() 106 | local idx = 2 107 | print('idx == ' .. idx) 108 | end 109 | ``` 110 | 111 | 112 | ## 关于变量名、函数名不符合lua规范的处理 113 | 如果变量名、函数名为lua关键字,则自动添加`tsvar_`的前缀。如果包含`$`等lua不支持的字符,则自动将`$`替换为`tsvar_`。 114 | 115 | ## 关于单个ts文件中存在多个类定义的处理 116 | TypeScript允许在单个ts文件中定义多个类,lua其实也可以这么写。但是为了避免循环引用的问题,最好的做法是将每个“类”定义在单独的文件里。ts2lua采用了这一策略。比如,在`module/something/Thing.ts`中定义了类`ThingB`,ts2lua会将`ThingB`生成到`module/something/Thing/ThingB.lua`中。 117 | 118 | ## 关于数组下标访问的处理 119 | 由于lua的下标从1开始,所以对于类似`arr[i]`这种会转化为`arr[i+1]`,而对于`arr[idx]`这种则不会进行+1处理,ts2lua会自动添加注释提醒您人工确认转换结果是否正确。 120 | 比如,下述代码将转换为 121 | 122 | TypeScript 123 | ```TypeScript 124 | doStr() { 125 | if(this.subValue > 10) { 126 | console.log('subValue is bigger than 10: ' + this.subValue + ', yes!'); 127 | } else { 128 | console.log('subValue is smaller than 10: ' + this.subValue + ', no!'); 129 | } 130 | for(let i = 0, len = this.myArr.length; i < len; i++) { 131 | console.log(this.myArr[i]); 132 | } 133 | let idx = 2; 134 | console.log('this.myArr[2] == ' + this.myArr[idx]); 135 | } 136 | ``` 137 | 138 | lua 139 | ```lua 140 | function doStr() 141 | if self.subValue>10 then 142 | print('subValue is bigger than 10: '..self.subValue..', yes!') 143 | 144 | else 145 | print('subValue is smaller than 10: '..self.subValue..', no!') 146 | 147 | end 148 | 149 | local i=0 150 | local len=#self.myArr 151 | repeat 152 | print(self.myArr[i+1]) 153 | i=i+1 154 | until not(i" 423 | --==-- 424 | assert( type( inheritance ) == 'table', "first parameter should be nil, a Class, or a list of Classes" ) 425 | 426 | -- wrap single-class into table list 427 | -- testing for DMC-Style objects 428 | -- TODO: see if we can test for other Class libs 429 | -- 430 | if inheritance.is_class == true then 431 | inheritance = { inheritance } 432 | elseif ClassBase and #inheritance == 0 then 433 | -- add default base Class 434 | tinsert( inheritance, ClassBase ) 435 | end 436 | 437 | local o = blessObject( inheritance, {} ) 438 | 439 | initializeObject( o, params ) 440 | 441 | -- add Class property, access via getters:class() 442 | o.__class = o 443 | 444 | -- add Class property, access via getters:NAME() 445 | o.__name = params.name 446 | 447 | return o 448 | 449 | end 450 | 451 | 452 | -- backward compatibility 453 | -- 454 | local function inheritsFrom( baseClass, options, constructor ) 455 | baseClass = baseClass == nil and baseClass or { baseClass } 456 | return newClass( baseClass, options ) 457 | end 458 | 459 | 460 | 461 | --====================================================================-- 462 | --== Base Class 463 | --====================================================================-- 464 | 465 | 466 | ClassBase = newClass( nil, { name="Class Class" } ) 467 | 468 | -- __ctor__ method 469 | -- called by 'new()' and other registrations 470 | -- 471 | function ClassBase:__ctor__( ... ) 472 | local params = { 473 | data = {...}, 474 | set_isClass = false 475 | } 476 | --==-- 477 | local o = blessObject( { self.__class }, params ) 478 | initializeObject( o, params ) 479 | 480 | return o 481 | end 482 | 483 | -- __dtor__ method 484 | -- called by 'destroy()' and other registrations 485 | -- 486 | function ClassBase:__dtor__() 487 | self:__destroy__() 488 | -- unblessObject( self ) 489 | end 490 | 491 | 492 | function ClassBase:__new__( ... ) 493 | return self 494 | end 495 | 496 | 497 | function ClassBase:__tostring__( id ) 498 | return sformat( "%s (%s)", self.NAME, id ) 499 | end 500 | 501 | 502 | function ClassBase:__destroy__() 503 | end 504 | 505 | 506 | function ClassBase.__getters:NAME() 507 | return self.__name 508 | end 509 | 510 | 511 | function ClassBase.__getters:class() 512 | return self.__class 513 | end 514 | 515 | function ClassBase.__getters:supers() 516 | return self.__parents 517 | end 518 | 519 | 520 | function ClassBase.__getters:is_class() 521 | return self.__is_class 522 | end 523 | 524 | -- deprecated 525 | function ClassBase.__getters:is_intermediate() 526 | return self.__is_class 527 | end 528 | 529 | function ClassBase.__getters:is_instance() 530 | return not self.__is_class 531 | end 532 | 533 | function ClassBase.__getters:version() 534 | return self.__version 535 | end 536 | 537 | 538 | function ClassBase:isa( the_class ) 539 | local isa = false 540 | local cur_class = self.class 541 | 542 | -- test self 543 | if cur_class == the_class then 544 | isa = true 545 | 546 | -- test parents 547 | else 548 | local parents = self.__parents 549 | for i=1, #parents do 550 | local parent = parents[i] 551 | if parent.isa then 552 | isa = parent:isa( the_class ) 553 | end 554 | if isa == true then break end 555 | end 556 | end 557 | 558 | return isa 559 | end 560 | 561 | 562 | -- optimize() 563 | -- move super class methods to object 564 | -- 565 | function ClassBase:optimize() 566 | 567 | function _optimize( obj, inheritance ) 568 | 569 | if not inheritance or #inheritance == 0 then return end 570 | 571 | for i=#inheritance,1,-1 do 572 | local parent = inheritance[i] 573 | 574 | -- climb up the hierarchy 575 | _optimize( obj, parent.__parents ) 576 | 577 | -- make local references to all functions 578 | for k,v in pairs( parent ) do 579 | if type( v ) == 'function' then 580 | obj[ k ] = v 581 | end 582 | end 583 | end 584 | 585 | end 586 | 587 | _optimize( self, { self.__class } ) 588 | end 589 | 590 | -- deoptimize() 591 | -- remove super class (optimized) methods from object 592 | -- 593 | function ClassBase:deoptimize() 594 | for k,v in pairs( self ) do 595 | if type( v ) == 'function' then 596 | self[ k ] = nil 597 | end 598 | end 599 | end 600 | 601 | 602 | 603 | -- Setup Class Properties (function references) 604 | 605 | registerCtorName( 'new', ClassBase ) 606 | registerDtorName( 'destroy', ClassBase ) 607 | ClassBase.superCall = superCall 608 | 609 | 610 | 611 | 612 | --====================================================================-- 613 | --== Lua Objects Exports 614 | --====================================================================-- 615 | 616 | 617 | -- makeNewClassGlobal 618 | -- modifies the global namespace with newClass() 619 | -- add or remove 620 | -- 621 | local function makeNewClassGlobal( is_global ) 622 | is_global = is_global~=nil and is_global or true 623 | if _G.newClass ~= nil then 624 | print( "WARNING: newClass exists in global namespace" ) 625 | elseif is_global == true then 626 | _G.newClass = newClass 627 | else 628 | _G.newClass = nil 629 | end 630 | end 631 | 632 | makeNewClassGlobal() -- start it off 633 | 634 | 635 | return { 636 | __version=VERSION, 637 | __superCall=superCall, -- for testing 638 | setNewClassGlobal=makeNewClassGlobal, 639 | 640 | registerCtorName=registerCtorName, 641 | registerDtorName=registerDtorName, 642 | 643 | inheritsFrom=inheritsFrom, -- backwards compatibility 644 | newClass=newClass, 645 | 646 | Class=ClassBase 647 | } 648 | -------------------------------------------------------------------------------- /src/gen/LuaMaker.ts: -------------------------------------------------------------------------------- 1 | import { ArrayExpression, ArrayPattern, ArrowFunctionExpression, AssignmentExpression, AssignmentPattern, AwaitExpression, BigIntLiteral, BinaryExpression, BlockStatement, BreakStatement, CallExpression, CatchClause, ClassBody, ClassDeclaration, ClassExpression, ClassProperty, ConditionalExpression, ContinueStatement, DebuggerStatement, Decorator, DoWhileStatement, EmptyStatement, ExportAllDeclaration, ExportDefaultDeclaration, ExportNamedDeclaration, ExportSpecifier, ExpressionStatement, ForInStatement, ForOfStatement, ForStatement, FunctionDeclaration, FunctionExpression, Identifier, IfStatement, Import, ImportDeclaration, ImportDefaultSpecifier, ImportNamespaceSpecifier, ImportSpecifier, LabeledStatement, Literal, LogicalExpression, MemberExpression, MetaProperty, MethodDefinition, NewExpression, ObjectExpression, ObjectPattern, Program, Property, RestElement, ReturnStatement, SequenceExpression, SpreadElement, Super, SwitchCase, SwitchStatement, TaggedTemplateExpression, TemplateElement, TemplateLiteral, ThisExpression, ThrowStatement, TryStatement, UnaryExpression, UpdateExpression, VariableDeclaration, VariableDeclarator, WhileStatement, WithStatement, YieldExpression, TSEnumDeclaration, BindingName, TSAsExpression, TSInterfaceDeclaration, TSTypeAssertion, TSModuleDeclaration, TSModuleBlock, TSDeclareFunction, TSAbstractMethodDefinition, BaseNode } from '@typescript-eslint/typescript-estree/dist/ts-estree/ts-estree'; 2 | import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; 3 | import util = require('util'); 4 | import path = require('path'); 5 | import { TsClassInfo, TsEnumInfo, TsModuleInfo } from './TsCollector'; 6 | import { TranslateOption } from './TranslateOption'; 7 | import { stringify } from 'querystring'; 8 | 9 | export class LuaMaker { 10 | private readonly noBraceTypes = [AST_NODE_TYPES.MemberExpression, AST_NODE_TYPES.ThisExpression, AST_NODE_TYPES.Identifier, AST_NODE_TYPES.CallExpression, AST_NODE_TYPES.TSAsExpression, AST_NODE_TYPES.TSTypeAssertion, AST_NODE_TYPES.Super]; 11 | 12 | // TODO: Typeof's return value may be different between ts and lua 13 | private readonly tsType2luaType = { 14 | 'undefined': 'nil', 15 | 'object': 'table' 16 | }; 17 | 18 | private readonly ignoreExpressionType = [AST_NODE_TYPES.MemberExpression, AST_NODE_TYPES.Identifier]; 19 | 20 | private readonly luaKeyWords = ['and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', 'return', 'then', 'true', 'until', 'while']; 21 | 22 | private pv = 0; 23 | private readonly operatorPriorityMap: { [opt: string]: number } = {}; 24 | 25 | constructor() { 26 | this.setPriority(['( … )'], this.pv++); 27 | this.setPriority(['… . …', '… [ … ]', 'new … ( … )', '… ( … )'], this.pv++); 28 | this.setPriority(['new …'], this.pv++); 29 | this.setPriority(['… ++', '… --'], this.pv++); 30 | this.setPriority(['! …', '~ …', '+ …', '- …', '++ …', '-- …', 'typeof …', 'void …', 'delete …', 'await …'], this.pv++); 31 | this.setPriority(['… ** …'], this.pv++); 32 | this.setPriority(['… * …', '… / …', '… % …'], this.pv++); 33 | this.setPriority(['… + …', '… - …'], this.pv++); 34 | this.setPriority(['… << …', '… >> …', '… >>> …'], this.pv++); 35 | this.setPriority(['… < …', '… <= …', '… > …', '… >= …', '… in …', '… instanceof …'], this.pv++); 36 | this.setPriority(['… == …', '… != …', '… === …', '… !== …'], this.pv++); 37 | this.setPriority(['… & …'], this.pv++); 38 | this.setPriority(['… ^ …'], this.pv++); 39 | this.setPriority(['… | …'], this.pv++); 40 | this.setPriority(['… && …'], this.pv++); 41 | this.setPriority(['… || …'], this.pv++); 42 | this.setPriority(['… ? … : …'], this.pv++); 43 | this.setPriority(['… = …', '… += …', '… -= …', '… *= …', '… /= …', '… %= …', '… <<= …', '… >>= …', '… >>>= …', '… &= …', '… ^= …', '… |= …'], this.pv++); 44 | this.setPriority(['yield …', 'yield* …'], this.pv++); 45 | this.setPriority(['...'], this.pv++); 46 | this.setPriority(['… , …'], this.pv++); 47 | } 48 | 49 | private setPriority(keys: string[], value: number) { 50 | for (let i = 0, len = keys.length; i < len; i++) { 51 | this.operatorPriorityMap[keys[i]] = value; 52 | } 53 | } 54 | 55 | private getPriority(raw: string) { 56 | let idx = this.operatorPriorityMap[raw]; 57 | if (idx < 0) { 58 | idx = 999; 59 | console.error('no prioritys: ' + raw); 60 | } 61 | return idx; 62 | } 63 | 64 | private calPriority(ast: any) { 65 | if ('__calPriority' in ast) { 66 | return ast.__calPriority; 67 | } 68 | switch (ast.type) { 69 | case AST_NODE_TYPES.UnaryExpression: 70 | { 71 | let ue = ast as UnaryExpression; 72 | ast.__calPriority = this.getPriority(ue.prefix ? ue.operator + ' …' : '… ' + ue.operator); 73 | } 74 | break; 75 | 76 | case AST_NODE_TYPES.UpdateExpression: 77 | { 78 | let ue = ast as UpdateExpression; 79 | ast.__calPriority = this.getPriority(ue.prefix ? ue.operator + ' …' : '… ' + ue.operator); 80 | } 81 | break; 82 | 83 | case AST_NODE_TYPES.BinaryExpression: 84 | { 85 | let be = ast as BinaryExpression; 86 | ast.__calPriority = this.getPriority('… ' + be.operator + ' …'); 87 | } 88 | break; 89 | 90 | case AST_NODE_TYPES.AssignmentExpression: 91 | { 92 | let ae = ast as AssignmentExpression; 93 | ast.__calPriority = this.getPriority('… ' + ae.operator + ' …'); 94 | } 95 | break; 96 | 97 | case AST_NODE_TYPES.LogicalExpression: 98 | { 99 | let le = ast as LogicalExpression; 100 | ast.__calPriority = this.getPriority('… ' + le.operator + ' …'); 101 | } 102 | break; 103 | 104 | case AST_NODE_TYPES.MemberExpression: 105 | { 106 | let me = ast as MemberExpression; 107 | ast.__calPriority = this.getPriority(me.computed ? '… [ … ]' : '… . …'); 108 | } 109 | break; 110 | 111 | case AST_NODE_TYPES.ConditionalExpression: 112 | { 113 | ast.__calPriority = this.getPriority('… ? … : …'); 114 | } 115 | break; 116 | 117 | case AST_NODE_TYPES.CallExpression: 118 | { 119 | ast.__calPriority = this.getPriority('… ( … )'); 120 | } 121 | break; 122 | 123 | case AST_NODE_TYPES.NewExpression: 124 | { 125 | let ne = ast as NewExpression; 126 | if (ne.arguments.length > 0) { 127 | ast.__calPriority = this.getPriority('new … ( … )'); 128 | } else { 129 | ast.__calPriority = this.getPriority('new …'); 130 | } 131 | } 132 | break; 133 | 134 | case AST_NODE_TYPES.SequenceExpression: 135 | { 136 | ast.__calPriority = this.getPriority('… , …'); 137 | } 138 | break; 139 | } 140 | return ast.__calPriority; 141 | } 142 | 143 | private isDevMode = false; 144 | private option: TranslateOption; 145 | private classMap: { [name: string]: TsClassInfo }; 146 | private moduleMap: { [name: string]: TsModuleInfo }; 147 | private enumMap: { [name: string]: TsEnumInfo }; 148 | private funcReplConf: {[func: string]: string} = {}; 149 | private regexReplConf: {[regex: string]: string} = {}; 150 | 151 | private filePath: string; 152 | private fileName: string; 153 | private rootPath: string; 154 | private usedIdMapByClass: { [className: string]: { [id: string]: number } } = {}; 155 | private importAsts: ImportDeclaration[] = []; 156 | private imports: string[] = []; 157 | private importMapByClass: { [className: string]: string[] } = {}; 158 | private className: string; 159 | private isDiffClass: boolean; 160 | private hasClass: boolean; 161 | public classContentMap: { [className: string]: string } = {}; 162 | private diffClassNames: string[] = []; 163 | private diffEnumNames: string[] = []; 164 | private nativeEnumNames: string[] = []; 165 | private moduleName: string; 166 | private hasContinue = false; 167 | private inSwitchCase = false; 168 | private inStatic = false; 169 | private classPropDefStr = ''; 170 | 171 | public unknowRegexs: string[] = []; 172 | 173 | public setEnv(devMode: boolean, option: TranslateOption, funcReplConf: {[func: string]: string}, regexReplConf: {[regex: string]: string}): void { 174 | this.isDevMode = devMode; 175 | this.option = option; 176 | this.funcReplConf = funcReplConf; 177 | this.regexReplConf = regexReplConf; 178 | } 179 | 180 | public setClassMap(classMap: { [name: string]: TsClassInfo }, moduleMap: { [name: string]: TsModuleInfo }, enumMap: { [name: string]: TsEnumInfo }) { 181 | this.classMap = classMap; 182 | this.moduleMap = moduleMap; 183 | this.enumMap = enumMap; 184 | } 185 | 186 | public toLuaBySource(ast: any): string { 187 | this.fileName = '__Source__'; 188 | return this.toLuaInternal(ast, '', ''); 189 | } 190 | 191 | public toLuaByFile(ast: any, filePath: string, rootPath: string): string { 192 | let fp = path.parse(filePath); 193 | this.fileName = fp.name; 194 | return this.toLuaInternal(ast, filePath, rootPath); 195 | } 196 | 197 | private toLuaInternal(ast: any, filePath: string, rootPath: string): string { 198 | this.filePath = filePath; 199 | this.rootPath = rootPath; 200 | 201 | this.usedIdMapByClass = {}; 202 | this.usedIdMapByClass[this.fileName] = {}; 203 | this.imports = []; 204 | this.importMapByClass = {}; 205 | this.importMapByClass[this.fileName] = []; 206 | this.diffClassNames.length = 0; 207 | this.diffEnumNames.length = 0; 208 | this.nativeEnumNames.length = 0; 209 | this.importAsts.length = 0; 210 | this.className = null; 211 | this.hasClass = false; 212 | this.classContentMap = {}; 213 | 214 | let content = this.codeFromAST(ast); 215 | let outStr = this.afterTreatment(content, this.fileName, this.hasClass); 216 | 217 | for(let className of this.diffClassNames) { 218 | this.classContentMap[className] = this.afterTreatment(this.classContentMap[className], className, true); 219 | } 220 | for(let enumName of this.nativeEnumNames) { 221 | delete this.classContentMap[enumName]; 222 | } 223 | return outStr; 224 | } 225 | 226 | private afterTreatment(content: string, className: string, hasClass: boolean) { 227 | let outStr = ''; 228 | 229 | content = content.replace(/console[\.|:]log/g, 'print'); 230 | content = this.formatTip(content); 231 | content = this.formatPop(content); 232 | if('xlua' == this.option.style) { 233 | content = content.replace(/UnityEngine\./g, 'CS.UnityEngine.'); 234 | // let regex = new RegExp('(?<=[^\w])Game(?=\.)', 'g'); 235 | } 236 | 237 | let imports: string[] = this.importMapByClass[className]; 238 | let locals: string[] = []; 239 | // if(hasClass) { 240 | // imports.push('Objects = dmc_lua/lua_class'); 241 | // locals.push('local Class = Objects.Class'); 242 | // } 243 | let uim = this.usedIdMapByClass[className]; 244 | for(let ia of this.importAsts) { 245 | let importSource = (ia.source as Literal).value as string; 246 | let importPP = path.parse(importSource); 247 | for(let s of ia.specifiers) { 248 | let importedName: string; 249 | if(s.type == AST_NODE_TYPES.ImportDefaultSpecifier || s.type == AST_NODE_TYPES.ImportNamespaceSpecifier) { 250 | importedName = s.local.name; 251 | } else { 252 | importedName = (s as ImportSpecifier).imported.name; 253 | } 254 | if(uim[s.local.name] > 0) { 255 | let importPath: string; 256 | if(importedName != importPP.name && (this.classMap[importedName] || this.enumMap[importedName])) { 257 | importPath = importPP.dir + '/' + importPP.name + '/' + importedName; 258 | } else { 259 | // importPath = importPP.dir + '/' + importedName; 260 | importPath = importSource; 261 | } 262 | if(imports.indexOf(importPath) < 0) { 263 | imports.push(importPath); 264 | if(s.local.name != importedName) { 265 | locals.push('local ' + s.local.name + ' = ' + importedName); 266 | } 267 | } 268 | } 269 | } 270 | } 271 | for(let diffClassName of this.diffClassNames) { 272 | if(diffClassName != className && uim[diffClassName] > 0) { 273 | let importPath: string = './' + this.fileName + '/' + diffClassName; 274 | if(imports.indexOf(importPath) < 0) { 275 | imports.push(importPath); 276 | } 277 | } 278 | } 279 | for(let diffEnumName of this.diffEnumNames) { 280 | if(diffEnumName != className && uim[diffEnumName] > 0) { 281 | let importPath: string = './' + this.fileName + '/' + diffEnumName; 282 | if(imports.indexOf(importPath) < 0) { 283 | imports.push(importPath); 284 | } 285 | } 286 | } 287 | if(className != this.fileName && uim[this.fileName] > 0) { 288 | let importPath: string = './' + this.fileName; 289 | if(imports.indexOf(importPath) < 0) { 290 | imports.push(importPath); 291 | } 292 | } 293 | // imports.sort(); 294 | for(let p of imports) { 295 | let exportVar: string; 296 | let importMtc = p.match(/(\w+) = (.+)/); 297 | if(importMtc) { 298 | exportVar = importMtc[1]; 299 | p = importMtc[2]; 300 | } 301 | if(p.indexOf('./') == 0 || p.indexOf('../') == 0) { 302 | p = path.relative(this.rootPath, path.join(path.dirname(this.filePath), p)).replace(/\\+/g, '/'); 303 | } 304 | if(exportVar) { 305 | outStr += 'local ' + exportVar + ' = '; 306 | } 307 | outStr += 'require("' + p + '")\n'; 308 | } 309 | for(let lcl of locals) { 310 | outStr += lcl + '\n'; 311 | } 312 | 313 | if(this.fileName == className) { 314 | for(let enumName of this.nativeEnumNames) { 315 | outStr += this.classContentMap[enumName] + '\n'; 316 | } 317 | } 318 | 319 | outStr += content; 320 | return outStr; 321 | } 322 | 323 | private codeFromAST(ast: any): string { 324 | let str = ''; 325 | switch (ast.type) { 326 | 327 | case AST_NODE_TYPES.ArrayExpression: 328 | str += this.codeFromArrayExpression(ast); 329 | break; 330 | 331 | case AST_NODE_TYPES.ArrayPattern: 332 | str += this.codeFromArrayPattern(ast); 333 | break; 334 | 335 | case AST_NODE_TYPES.ArrowFunctionExpression: 336 | str += this.codeFromArrowFunctionExpression(ast); 337 | break; 338 | 339 | case AST_NODE_TYPES.AssignmentExpression: 340 | str += this.codeFromAssignmentExpression(ast); 341 | break; 342 | 343 | case AST_NODE_TYPES.AssignmentPattern: 344 | str += this.codeFromAssignmentPattern(ast); 345 | break; 346 | 347 | case AST_NODE_TYPES.AwaitExpression: 348 | str += this.codeFromAwaitExpression(ast); 349 | break; 350 | 351 | case AST_NODE_TYPES.BigIntLiteral: 352 | str += this.codeFromBigIntLiteral(ast); 353 | break; 354 | 355 | case AST_NODE_TYPES.BinaryExpression: 356 | str += this.codeFromBinaryExpression(ast); 357 | break; 358 | 359 | case AST_NODE_TYPES.BlockStatement: 360 | str += this.codeFromBlockStatement(ast); 361 | break; 362 | 363 | case AST_NODE_TYPES.BreakStatement: 364 | str += this.codeFromBreakStatement(ast); 365 | break; 366 | 367 | case AST_NODE_TYPES.CallExpression: 368 | str += this.codeFromCallExpression(ast); 369 | break; 370 | 371 | case AST_NODE_TYPES.CatchClause: 372 | str += this.codeFromCatchClause(ast); 373 | break; 374 | 375 | case AST_NODE_TYPES.ClassBody: 376 | str += this.codeFromClassBody(ast); 377 | break; 378 | 379 | case AST_NODE_TYPES.ClassDeclaration: 380 | str += this.codeFromClassDeclaration(ast); 381 | break; 382 | 383 | case AST_NODE_TYPES.ClassExpression: 384 | str += this.codeFromClassExpression(ast); 385 | break; 386 | 387 | case AST_NODE_TYPES.ClassProperty: 388 | str += this.codeFromClassProperty(ast); 389 | break; 390 | 391 | case AST_NODE_TYPES.ConditionalExpression: 392 | str += this.codeFromConditionalExpression(ast); 393 | break; 394 | 395 | case AST_NODE_TYPES.ContinueStatement: 396 | str += this.codeFromContinueStatement(ast); 397 | break; 398 | 399 | case AST_NODE_TYPES.DebuggerStatement: 400 | str += this.codeFromDebuggerStatement(ast); 401 | break; 402 | 403 | case AST_NODE_TYPES.Decorator: 404 | str += this.codeFromDecorator(ast); 405 | break; 406 | 407 | case AST_NODE_TYPES.DoWhileStatement: 408 | str += this.codeFromDoWhileStatement(ast); 409 | break; 410 | 411 | case AST_NODE_TYPES.EmptyStatement: 412 | str += this.codeFromEmptyStatement(ast); 413 | break; 414 | 415 | case AST_NODE_TYPES.ExportAllDeclaration: 416 | str += this.codeFromExportAllDeclaration(ast); 417 | break; 418 | 419 | case AST_NODE_TYPES.ExportDefaultDeclaration: 420 | str += this.codeFromExportDefaultDeclaration(ast); 421 | break; 422 | 423 | case AST_NODE_TYPES.ExportNamedDeclaration: 424 | str += this.codeFromExportNamedDeclaration(ast); 425 | break; 426 | 427 | case AST_NODE_TYPES.ExportSpecifier: 428 | str += this.codeFromExportSpecifier(ast); 429 | break; 430 | 431 | case AST_NODE_TYPES.ExpressionStatement: 432 | str += this.codeFromExpressionStatement(ast); 433 | break; 434 | 435 | case AST_NODE_TYPES.ForInStatement: 436 | str += this.codeFromForInStatement(ast); 437 | break; 438 | 439 | case AST_NODE_TYPES.ForOfStatement: 440 | str += this.codeFromForOfStatement(ast); 441 | break; 442 | 443 | case AST_NODE_TYPES.ForStatement: 444 | str += this.codeFromForStatement(ast); 445 | break; 446 | 447 | case AST_NODE_TYPES.FunctionDeclaration: 448 | str += this.codeFromFunctionDeclaration(ast); 449 | break; 450 | 451 | case AST_NODE_TYPES.FunctionExpression: 452 | str += this.codeFromFunctionExpression(ast); 453 | break; 454 | 455 | case AST_NODE_TYPES.Identifier: 456 | str += this.codeFromIdentifier(ast); 457 | break; 458 | 459 | case AST_NODE_TYPES.IfStatement: 460 | str += this.codeFromIfStatement(ast); 461 | break; 462 | 463 | case AST_NODE_TYPES.Import: 464 | str += this.codeFromImport(ast); 465 | break; 466 | 467 | case AST_NODE_TYPES.ImportDeclaration: 468 | str += this.codeFromImportDeclaration(ast); 469 | break; 470 | 471 | case AST_NODE_TYPES.ImportDefaultSpecifier: 472 | str += this.codeFromImportDefaultSpecifier(ast); 473 | break; 474 | 475 | case AST_NODE_TYPES.ImportNamespaceSpecifier: 476 | str += this.codeFromImportNamespaceSpecifier(ast); 477 | break; 478 | 479 | case AST_NODE_TYPES.ImportSpecifier: 480 | str += this.codeFromImportSpecifier(ast); 481 | break; 482 | 483 | case AST_NODE_TYPES.LabeledStatement: 484 | str += this.codeFromLabeledStatement(ast); 485 | break; 486 | 487 | case AST_NODE_TYPES.Literal: 488 | str += this.codeFromLiteral(ast); 489 | break; 490 | 491 | case AST_NODE_TYPES.LogicalExpression: 492 | str += this.codeFromLogicalExpression(ast); 493 | break; 494 | 495 | case AST_NODE_TYPES.MemberExpression: 496 | str += this.codeFromMemberExpression(ast); 497 | break; 498 | 499 | case AST_NODE_TYPES.MetaProperty: 500 | str += this.codeFromMetaProperty(ast); 501 | break; 502 | 503 | case AST_NODE_TYPES.MethodDefinition: 504 | str += this.codeFromMethodDefinition(ast); 505 | break; 506 | 507 | case AST_NODE_TYPES.NewExpression: 508 | str += this.codeFromNewExpression(ast); 509 | break; 510 | 511 | case AST_NODE_TYPES.ObjectExpression: 512 | str += this.codeFromObjectExpression(ast); 513 | break; 514 | 515 | case AST_NODE_TYPES.ObjectPattern: 516 | str += this.codeFromObjectPattern(ast); 517 | break; 518 | 519 | case AST_NODE_TYPES.Program: 520 | str += this.codeFromProgram(ast); 521 | break; 522 | 523 | case AST_NODE_TYPES.Property: 524 | str += this.codeFromProperty(ast); 525 | break; 526 | 527 | case AST_NODE_TYPES.RestElement: 528 | str += this.codeFromRestElement(ast); 529 | break; 530 | 531 | case AST_NODE_TYPES.ReturnStatement: 532 | str += this.codeFromReturnStatement(ast); 533 | break; 534 | 535 | case AST_NODE_TYPES.SequenceExpression: 536 | str += this.codeFromSequenceExpression(ast); 537 | break; 538 | 539 | case AST_NODE_TYPES.SpreadElement: 540 | str += this.codeFromSpreadElement(ast); 541 | break; 542 | 543 | case AST_NODE_TYPES.Super: 544 | str += this.codeFromSuper(ast); 545 | break; 546 | 547 | case AST_NODE_TYPES.SwitchCase: 548 | str += this.codeFromSwitchCase(ast); 549 | break; 550 | 551 | case AST_NODE_TYPES.SwitchStatement: 552 | str += this.codeFromSwitchStatement(ast); 553 | break; 554 | 555 | case AST_NODE_TYPES.TaggedTemplateExpression: 556 | str += this.codeFromTaggedTemplateExpression(ast); 557 | break; 558 | 559 | case AST_NODE_TYPES.TemplateElement: 560 | str += this.codeFromTemplateElement(ast); 561 | break; 562 | 563 | case AST_NODE_TYPES.TemplateLiteral: 564 | str += this.codeFromTemplateLiteral(ast); 565 | break; 566 | 567 | case AST_NODE_TYPES.ThisExpression: 568 | str += this.codeFromThisExpression(ast); 569 | break; 570 | 571 | case AST_NODE_TYPES.ThrowStatement: 572 | str += this.codeFromThrowStatement(ast); 573 | break; 574 | 575 | case AST_NODE_TYPES.TryStatement: 576 | str += this.codeFromTryStatement(ast); 577 | break; 578 | 579 | case AST_NODE_TYPES.UnaryExpression: 580 | str += this.codeFromUnaryExpression(ast); 581 | break; 582 | 583 | case AST_NODE_TYPES.UpdateExpression: 584 | str += this.codeFromUpdateExpression(ast); 585 | break; 586 | 587 | case AST_NODE_TYPES.VariableDeclaration: 588 | str += this.codeFromVariableDeclaration(ast); 589 | break; 590 | 591 | case AST_NODE_TYPES.VariableDeclarator: 592 | str += this.codeFromVariableDeclarator(ast); 593 | break; 594 | 595 | case AST_NODE_TYPES.WhileStatement: 596 | str += this.codeFromWhileStatement(ast); 597 | break; 598 | 599 | case AST_NODE_TYPES.WithStatement: 600 | str += this.codeFromWithStatement(ast); 601 | break; 602 | 603 | case AST_NODE_TYPES.YieldExpression: 604 | str += this.codeFromYieldExpression(ast); 605 | break; 606 | 607 | case AST_NODE_TYPES.TSAbstractMethodDefinition: 608 | str += this.codeFromTSAbstractMethodDefinition(ast); 609 | break; 610 | 611 | case AST_NODE_TYPES.TSAsExpression: 612 | str += this.codeFromTSAsExpression(ast); 613 | break; 614 | 615 | case AST_NODE_TYPES.TSDeclareFunction: 616 | str += this.codeFromTSDeclareFunction(ast); 617 | break; 618 | 619 | case AST_NODE_TYPES.TSEnumDeclaration: 620 | str += this.codeFromTSEnumDeclaration(ast); 621 | break; 622 | 623 | case AST_NODE_TYPES.TSModuleBlock: 624 | str += this.codeFromTSModuleBlock(ast); 625 | break; 626 | 627 | case AST_NODE_TYPES.TSModuleDeclaration: 628 | str += this.codeFromTSModuleDeclaration(ast); 629 | break; 630 | 631 | case AST_NODE_TYPES.TSInterfaceDeclaration: 632 | str += this.codeFromTSInterfaceDeclaration(ast); 633 | break; 634 | 635 | case AST_NODE_TYPES.TSTypeAssertion: 636 | str += this.codeFromTSTypeAssertion(ast); 637 | break; 638 | 639 | default: 640 | console.log(util.inspect(ast, true, 3)); 641 | throw new Error('unrecornized type: ' + ast.type); 642 | break; 643 | } 644 | return str; 645 | } 646 | 647 | 648 | private codeFromArrayExpression(ast: ArrayExpression): string { 649 | let str = ''; 650 | for (let i = 0, len = ast.elements.length; i < len; i++) { 651 | if (str) { 652 | str += ', '; 653 | } 654 | str += this.codeFromAST(ast.elements[i]); 655 | } 656 | return '{' + str + '}'; 657 | } 658 | 659 | private codeFromArrayPattern(ast: ArrayPattern): string { 660 | this.assert(false, ast, 'Not support ArrayPattern yet!'); 661 | return ''; 662 | } 663 | 664 | private codeFromArrowFunctionExpression(ast: ArrowFunctionExpression): string { 665 | let str = 'function('; 666 | let defaultParamsStr = ''; 667 | if (ast.params) { 668 | for (let i = 0, len = ast.params.length; i < len; i++) { 669 | if (i > 0) { 670 | str += ', '; 671 | } 672 | let oneParam = ast.params[i]; 673 | (oneParam as any).__parent = ast; 674 | str += this.codeFromAST(oneParam); 675 | 676 | if(oneParam.type == AST_NODE_TYPES.AssignmentPattern) { 677 | let paramIdStr = this.codeFromAST(oneParam.left); 678 | defaultParamsStr += 'if ' + paramIdStr + ' == nil then\n'; 679 | defaultParamsStr += this.indent(paramIdStr + '=' + this.codeFromAST(oneParam.right)) + '\n'; 680 | defaultParamsStr += 'end\n'; 681 | } 682 | } 683 | } 684 | str += ')\n'; 685 | if (ast.body) { 686 | let bodyStr = this.codeFromAST(ast.body); 687 | if(defaultParamsStr) { 688 | bodyStr = defaultParamsStr + bodyStr; 689 | } 690 | str += this.indent(bodyStr) + '\n'; 691 | } 692 | this.assert(!ast.generator, ast, 'Not support generator yet!'); 693 | this.assert(!ast.async, ast, 'Not support async yet!'); 694 | this.assert(!ast.expression, ast, 'Not support expression yet!'); 695 | str += 'end\n'; 696 | return str; 697 | } 698 | 699 | private codeFromAssignmentExpression(ast: AssignmentExpression): string { 700 | return this.codeFromBinaryExpression(ast as any); 701 | } 702 | 703 | private codeFromAssignmentPattern(ast: AssignmentPattern): string { 704 | let str = this.codeFromAST(ast.left); 705 | let parent = (ast as any).__parent; 706 | if(!parent || (parent.type != AST_NODE_TYPES.FunctionExpression && parent.type != AST_NODE_TYPES.FunctionDeclaration)) { 707 | str += ' = ' + this.codeFromAST(ast.right); 708 | } 709 | return str; 710 | } 711 | 712 | private codeFromAwaitExpression(ast: AwaitExpression): string { 713 | this.assert(false, ast, 'Not support AwaitExpression yet!'); 714 | return ''; 715 | } 716 | 717 | private codeFromBigIntLiteral(ast: BigIntLiteral): string { 718 | return this.codeFromLiteral(ast as any); 719 | } 720 | 721 | private codeFromBinaryExpression(ast: BinaryExpression): string { 722 | let optStr = ast.operator; 723 | this.assert('>>>=' != optStr, ast, 'Not support >>>= yet!'); 724 | (ast.left as any).__parent = ast; 725 | (ast.right as any).__parent = ast; 726 | let left = this.codeFromAST(ast.left); 727 | let right = this.codeFromAST(ast.right); 728 | 729 | if(optStr == 'in') { 730 | return right + '[' + left + ']'; 731 | } 732 | 733 | let selffOpts: string[] = ['+=', '-=', '*=', '/=', '%=', '<<=', '>>=', '&=', '^=', '|=']; 734 | let isSelfOperator = false; 735 | if(selffOpts.indexOf(optStr) >= 0) { 736 | // self operator 737 | isSelfOperator = true; 738 | optStr = optStr.substr(0, optStr.length - 1); 739 | } 740 | if(optStr == '+') { 741 | if(('string' == (ast.left as any).__type || 'string' == (ast.right as any).__type)) { 742 | // TODO: Take care of string combination 743 | optStr = '..'; 744 | (ast as any).__type = 'string'; 745 | } 746 | } else if(optStr == '!=') { 747 | optStr = '~='; 748 | } else if(optStr == '===') { 749 | optStr = '=='; 750 | } else if(optStr == '!==') { 751 | optStr = '~='; 752 | } 753 | 754 | if(optStr == 'instanceof') { 755 | return left + ':instanceof(' + right + ')'; 756 | } 757 | 758 | let str = ''; 759 | let astType = (ast as any).type; 760 | if(astType == AST_NODE_TYPES.AssignmentExpression) { 761 | if(ast.right.type == AST_NODE_TYPES.AssignmentExpression) { 762 | // 处理 a = b = c 763 | str = right + '\n'; 764 | right = this.codeFromAST((ast.right as AssignmentExpression).left); 765 | } else if(ast.right.type == AST_NODE_TYPES.UpdateExpression && (ast.right as UpdateExpression).prefix) { 766 | // 处理 a = ++b 767 | str = right + '\n'; 768 | right = this.codeFromAST((ast.right as UpdateExpression).argument); 769 | } 770 | } 771 | 772 | if(isSelfOperator) { 773 | return str + left + ' = ' + left + ' ' + optStr + ' ' + right; 774 | } 775 | 776 | return str + left + ' ' + optStr + ' ' + right; 777 | } 778 | 779 | private codeFromBlockStatement(ast: BlockStatement): string { 780 | let str = ''; 781 | for (let i = 0, len = ast.body.length; i < len; i++) { 782 | let bodyEle = ast.body[i]; 783 | if(this.option.ignoreNoUsedExp && 784 | bodyEle.type == AST_NODE_TYPES.ExpressionStatement && 785 | this.ignoreExpressionType.indexOf((bodyEle as ExpressionStatement).expression.type) >= 0) { 786 | console.log('ignore statement at \x1B[36m%s\x1B[0m:\x1B[33m%d:%d\x1B[0m', this.filePath, bodyEle.loc.start.line, bodyEle.loc.start.column); 787 | continue; 788 | } 789 | let bstr = this.codeFromAST(bodyEle); 790 | if(bstr) { 791 | if (i > 0) { 792 | str += '\n'; 793 | } 794 | str += bstr; 795 | } 796 | } 797 | return str; 798 | } 799 | 800 | private codeFromBreakStatement(ast: BreakStatement): string { 801 | this.assert(!ast.label, ast, 'Not support break label yet!'); 802 | if(this.inSwitchCase) { 803 | return 'return'; 804 | } 805 | return 'break'; 806 | } 807 | 808 | private codeFromCallExpression(ast: CallExpression): string { 809 | (ast.callee as any).__parent = ast; 810 | let calleeStr = this.codeFromAST(ast.callee); 811 | if(calleeStr == 'super') { 812 | calleeStr += ':__new__'; 813 | } 814 | let str = ''; 815 | let allAgmStr = ''; 816 | for (let i = 0, len = ast.arguments.length; i < len; i++) { 817 | let arg = ast.arguments[i]; 818 | let argStr = this.codeFromAST(arg); 819 | if(arg.type == AST_NODE_TYPES.AssignmentExpression) { 820 | str += argStr + '\n'; 821 | argStr = this.codeFromAST((arg as AssignmentExpression).left); 822 | } else if(arg.type == AST_NODE_TYPES.UpdateExpression) { 823 | str += argStr + '\n'; 824 | argStr = this.codeFromAST((arg as UpdateExpression).argument); 825 | } 826 | if(allAgmStr) { 827 | allAgmStr += ', '; 828 | } 829 | allAgmStr += argStr; 830 | } 831 | let funcName = ''; 832 | let funcNameRegexResult = calleeStr.match(/[\.:](\w+)$/); 833 | if(funcNameRegexResult) { 834 | funcName = funcNameRegexResult[1]; 835 | } 836 | let funcRepl = this.funcReplConf[funcName]; 837 | if(funcRepl == 'table.insert' || funcRepl == 'table.merge') { 838 | // Array push change into table.insert 839 | // Array concat change into table.merge 840 | str += funcRepl + '(' + calleeStr.substr(0, calleeStr.length - funcName.length - 1); 841 | if(allAgmStr) { 842 | str += ', ' + allAgmStr; 843 | } 844 | str += ')'; 845 | if(funcRepl == 'table.merge') { 846 | this.addImport('tableutil'); 847 | } 848 | } else if('xlua' == this.option.style && !allAgmStr && funcRepl == 'typeof') { 849 | str = 'typeof(' + calleeStr.substr(0, calleeStr.length - 8) + ')'; 850 | } else { 851 | if(typeof(funcRepl) === 'string') { 852 | calleeStr = calleeStr.replace(/(?<=[\.:])\w+$/, funcRepl); 853 | } else if(!funcRepl && (funcName == 'trim' || funcName == 'split')) { 854 | this.addImport('stringutil'); 855 | } 856 | str = calleeStr + '('; 857 | str += allAgmStr; 858 | str += ')'; 859 | } 860 | return str; 861 | } 862 | 863 | private codeFromCatchClause(ast: CatchClause): string { 864 | let str = 'function($param$)\n'.replace('$param$', this.codeFromAST(ast.param)); 865 | str += this.codeFromBlockStatement(ast.body); 866 | return str; 867 | } 868 | 869 | private codeFromClassBody(ast: ClassBody): string { 870 | let str = ''; 871 | this.classPropDefStr = ''; 872 | for (let i = 0, len = ast.body.length; i < len; i++) { 873 | let cbodyStr = this.codeFromAST(ast.body[i]); 874 | if(cbodyStr) { 875 | if (i > 0) { 876 | str += '\n'; 877 | } 878 | str += cbodyStr; 879 | } 880 | } 881 | if(this.classPropDefStr) { 882 | let propDefPos = -1; 883 | let superStrMtc = str.match(/super:__new__\(.*\n/); 884 | if(superStrMtc) { 885 | propDefPos = superStrMtc.index + superStrMtc[0].length; 886 | } else { 887 | let ctorStrMtc = str.match(/:__new__\(.*\)\n/); 888 | if(ctorStrMtc) { 889 | propDefPos = ctorStrMtc.index + ctorStrMtc[0].length; 890 | } 891 | } 892 | 893 | if(propDefPos >= 0) { 894 | let ctorBody = this.classPropDefStr; 895 | str = str.substr(0, propDefPos) + this.indent(ctorBody) + '\n' + str.substr(propDefPos); 896 | } else { 897 | str = 'function ' + this.className + ':__new__(...)\n' + 898 | this.indent('self:superCall(\'__new__\', unpack({...}))\n' + this.classPropDefStr) + 899 | '\nend\n' + 900 | str; 901 | } 902 | } 903 | return str; 904 | } 905 | 906 | private codeFromClassDeclaration(ast: ClassDeclaration): string { 907 | if (ast.typeParameters) { 908 | // typeParameters?: TSTypeParameterDeclaration; 909 | } 910 | if (ast.superTypeParameters) { 911 | // TSTypeParameterInstantiation; 912 | } 913 | if (!ast.id) { 914 | this.assert(false, ast, 'Class name is necessary!'); 915 | } 916 | let className = this.codeFromAST(ast.id); 917 | this.usedIdMapByClass[this.fileName][className]--; 918 | this.className = className; 919 | this.isDiffClass = this.filePath && (ast as any).__exported && this.fileName != className; 920 | if(!this.usedIdMapByClass[className]) { 921 | this.usedIdMapByClass[className] = {}; 922 | } 923 | if(!this.importMapByClass[className]) { 924 | this.importMapByClass[className] = []; 925 | } 926 | 927 | let str = ''; 928 | if(!(ast as any).__exported) { 929 | str += 'local '; 930 | } 931 | str += className + ' = newClass({$BaseClass$}, {name = \'' + className + '\'})\n'; 932 | 933 | str += this.codeFromClassBody(ast.body); 934 | if (ast.superClass) { 935 | str = str.replace('$BaseClass$', this.codeFromAST(ast.superClass)); 936 | } else { 937 | str = str.replace('$BaseClass$', 'Class'); 938 | } 939 | // if(ast.implements) { 940 | // ExpressionWithTypeArguments[]; 941 | // } 942 | // if(ast.abstract) { 943 | // // boolean; 944 | // } 945 | if (ast.declare) { 946 | // boolean 947 | this.assert(false, ast); 948 | } 949 | if (ast.decorators) { 950 | // Decorator[]; 951 | this.assert(false, ast); 952 | } 953 | 954 | str = str.replace(/super:([^\(]+)\(\)/g, "self:superCall('$1')"); 955 | str = str.replace(/super:([^\(]+)\(/g, "self:superCall('$1',"); 956 | 957 | if(this.isDiffClass) { 958 | // save as another file 959 | this.diffClassNames.push(className); 960 | this.classContentMap[className] = str; 961 | str = ''; 962 | } else { 963 | this.hasClass = true; 964 | } 965 | this.className = null; 966 | return str; 967 | } 968 | 969 | private codeFromClassExpression(ast: ClassExpression): string { 970 | // this.pintHit(ast); 971 | return this.codeFromClassDeclaration(ast as any); 972 | } 973 | 974 | private codeFromClassProperty(ast: ClassProperty): string { 975 | let str = ''; 976 | if (ast.value) { 977 | if (ast.static) { 978 | str = this.className + '.' + this.codeFromAST(ast.key) + ' = ' + this.codeFromAST(ast.value) + ';'; 979 | } else { 980 | if(this.classPropDefStr) { 981 | this.classPropDefStr += '\n'; 982 | } 983 | this.classPropDefStr += 'self.' + this.codeFromAST(ast.key) + ' = ' + this.codeFromAST(ast.value) + ';'; 984 | } 985 | // readonly?: boolean; 986 | // decorators?: Decorator[]; 987 | // accessibility?: Accessibility; 988 | // optional?: boolean; 989 | // definite?: boolean; 990 | // typeAnnotation?: TSTypeAnnotation; 991 | } 992 | 993 | return str; 994 | } 995 | 996 | private codeFromConditionalExpression(ast: ConditionalExpression): string { 997 | // TODO: 0 or '' are considered true in lua while false in TypeScript 998 | let testStr = this.codeFromAST(ast.test); 999 | let str = '(' + testStr + ' and {' + this.codeFromAST(ast.consequent) + '} or {' + this.codeFromAST(ast.alternate) + '})[1]'; 1000 | str += this.wrapTip('lua中0和空字符串也是true,此处' + testStr + '需要确认'); 1001 | return str; 1002 | } 1003 | 1004 | private codeFromContinueStatement(ast: ContinueStatement): string { 1005 | this.hasContinue = true; 1006 | return 'break'; 1007 | } 1008 | 1009 | private codeFromDebuggerStatement(ast: DebuggerStatement): string { 1010 | this.assert(false, ast, 'Not support DebuggerStatement yet!'); 1011 | return ''; 1012 | } 1013 | 1014 | private codeFromDecorator(ast: Decorator): string { 1015 | this.assert(false, ast, 'Not support Decorator yet!'); 1016 | return ''; 1017 | } 1018 | 1019 | private codeFromDoWhileStatement(ast: DoWhileStatement): string { 1020 | this.assert(false, ast, 'Not support DoWhileStatement yet!'); 1021 | return ''; 1022 | } 1023 | 1024 | private codeFromEmptyStatement(ast: EmptyStatement): string { 1025 | return ''; 1026 | } 1027 | 1028 | private codeFromExportAllDeclaration(ast: ExportAllDeclaration): string { 1029 | this.assert(false, ast, 'Not support ExportAllDeclaration yet!'); 1030 | return ''; 1031 | } 1032 | 1033 | private codeFromExportDefaultDeclaration(ast: ExportDefaultDeclaration): string { 1034 | return ''; 1035 | } 1036 | 1037 | private codeFromExportNamedDeclaration(ast: ExportNamedDeclaration): string { 1038 | (ast.declaration as any).__exported = true; 1039 | if((ast as any).__module) { 1040 | (ast.declaration as any).__module = (ast as any).__module; 1041 | } 1042 | return this.codeFromAST(ast.declaration); 1043 | } 1044 | 1045 | private codeFromExportSpecifier(ast: ExportSpecifier): string { 1046 | this.assert(false, ast, 'Not support ExportSpecifier yet!'); 1047 | return ''; 1048 | } 1049 | 1050 | private codeFromExpressionStatement(ast: ExpressionStatement): string { 1051 | return this.codeFromAST(ast.expression); 1052 | } 1053 | 1054 | private codeFromForInStatement(ast: ForInStatement): string { 1055 | (ast.left as any).__parent = ast; 1056 | let str = 'for ' + this.codeFromAST(ast.left) + ' in pairs(' + this.codeFromAST(ast.right) + ') do\n'; 1057 | str += this.indent(this.codeFromAST(ast.body)) + '\n'; 1058 | str += 'end'; 1059 | return str; 1060 | } 1061 | 1062 | private codeFromForOfStatement(ast: ForOfStatement): string { 1063 | (ast.left as any).__parent = ast; 1064 | let str = 'for _tmpi, ' + this.codeFromAST(ast.left) + ' in pairs(' + this.codeFromAST(ast.right) + ') do\n'; 1065 | str += this.indent(this.codeFromAST(ast.body)) + '\n'; 1066 | str += 'end'; 1067 | return str; 1068 | } 1069 | 1070 | private codeFromForStatement(ast: ForStatement): string { 1071 | this.hasContinue = false; 1072 | 1073 | let str = ''; 1074 | if (ast.init && ast.init.type != AST_NODE_TYPES.Identifier) { 1075 | str += this.codeFromAST(ast.init) + '\n'; 1076 | } 1077 | str += 'repeat\n'; 1078 | let repeatBodyStr = this.codeFromAST(ast.body); 1079 | if(this.hasContinue) { 1080 | repeatBodyStr = 'repeat\n' + this.indent(repeatBodyStr) + '\nuntil true' 1081 | } 1082 | if (ast.update) { 1083 | repeatBodyStr += '\n'; 1084 | repeatBodyStr += this.codeFromAST(ast.update); 1085 | } 1086 | str += this.indent(repeatBodyStr) + '\n'; 1087 | str += 'until '; 1088 | if (ast.test) { 1089 | str += 'not(' + this.codeFromAST(ast.test) + ')'; 1090 | } else { 1091 | str += 'false'; 1092 | } 1093 | return str; 1094 | } 1095 | 1096 | private codeFromFunctionDeclaration(ast: FunctionDeclaration): string { 1097 | return this.codeFromFunctionExpression(ast as any); 1098 | } 1099 | 1100 | private codeFromFunctionExpression(ast: FunctionExpression): string { 1101 | return this.codeFromFunctionExpressionInternal(null, false, null, ast); 1102 | } 1103 | 1104 | private codeFromFunctionExpressionInternal(funcName: string, isStatic: boolean, kind: string, ast: FunctionExpression): string { 1105 | this.inStatic = isStatic; 1106 | let str = ''; 1107 | if(!funcName && ast.id) { 1108 | funcName = this.codeFromAST(ast.id); 1109 | } 1110 | if (funcName) { 1111 | if('constructor' == funcName) { 1112 | funcName = '__new__'; 1113 | } else if((ast as any).__module) { 1114 | if(!(ast as any).__exported) { 1115 | str = 'local '; 1116 | } else { 1117 | funcName = (ast as any).__module + '.' + funcName; 1118 | } 1119 | } 1120 | if((ast as any).type == AST_NODE_TYPES.FunctionDeclaration) { 1121 | // 比如匿名函数 1122 | str += 'function ' + funcName + '('; 1123 | } else { 1124 | if (this.className) { 1125 | // 成员函数 1126 | let dotOrColon = ':'; 1127 | if(isStatic) { 1128 | dotOrColon = '.'; 1129 | } 1130 | if(kind == 'get') { 1131 | dotOrColon = '.__getters' + dotOrColon; 1132 | } else if(kind == 'set') { 1133 | dotOrColon = '.__setters' + dotOrColon; 1134 | } 1135 | str += 'function ' + this.className + dotOrColon + funcName + '('; 1136 | } else { 1137 | // 普通函数 1138 | str += 'function ' + funcName + '('; 1139 | } 1140 | } 1141 | } else { 1142 | str = 'function('; 1143 | } 1144 | let defaultParamsStr = ''; 1145 | if (ast.params) { 1146 | for (let i = 0, len = ast.params.length; i < len; i++) { 1147 | if (i > 0) { 1148 | str += ', '; 1149 | } 1150 | let oneParam = ast.params[i]; 1151 | (oneParam as any).__parent = ast; 1152 | str += this.codeFromAST(oneParam); 1153 | 1154 | if(oneParam.type == AST_NODE_TYPES.AssignmentPattern) { 1155 | let paramIdStr = this.codeFromAST(oneParam.left); 1156 | defaultParamsStr += 'if ' + paramIdStr + ' == nil then\n'; 1157 | defaultParamsStr += this.indent(paramIdStr + '=' + this.codeFromAST(oneParam.right)) + '\n'; 1158 | defaultParamsStr += 'end\n'; 1159 | } 1160 | } 1161 | } 1162 | str += ')'; 1163 | let bodyStr = ''; 1164 | if (ast.body) { 1165 | bodyStr = this.codeFromAST(ast.body); 1166 | if(defaultParamsStr) { 1167 | bodyStr = defaultParamsStr + bodyStr; 1168 | } 1169 | } 1170 | 1171 | if(bodyStr) { 1172 | str += '\n' + this.indent(bodyStr) + '\nend\n'; 1173 | } else { 1174 | str += ' end'; 1175 | } 1176 | this.assert(!ast.generator, ast, 'Not support generator yet!'); 1177 | this.assert(!ast.async, ast, 'Not support async yet!'); 1178 | this.assert(!ast.expression, ast, 'Not support expression yet!'); 1179 | this.assert(!ast.declare, ast, 'Not support declare yet!'); 1180 | this.inStatic = false; 1181 | return str; 1182 | } 1183 | 1184 | private codeFromIdentifier(ast: Identifier): string { 1185 | let str = ast.name; 1186 | this.addUsedId(str); 1187 | if(this.luaKeyWords.indexOf(str) >= 0) { 1188 | str = 'tsvar_' + str; 1189 | } else if(str.substr(0, 1) == '$') { 1190 | str = 'tsvar_' + str.substr(1); 1191 | } 1192 | return str; 1193 | } 1194 | 1195 | private codeFromIfStatement(ast: IfStatement): string { 1196 | let testStr = this.codeFromAST(ast.test); 1197 | let str = 'if ' + testStr + ' then\n'; 1198 | str += this.indent(this.codeFromAST(ast.consequent)); 1199 | if (ast.alternate && (ast.alternate.type != AST_NODE_TYPES.BlockStatement || (ast.alternate as BlockStatement).body.length > 0)) { 1200 | str += '\nelse'; 1201 | let altStr = this.codeFromAST(ast.alternate); 1202 | if(ast.alternate.type != AST_NODE_TYPES.IfStatement) { 1203 | str += '\n'; 1204 | str += this.indent(altStr); 1205 | str += '\nend'; 1206 | } else { 1207 | str += altStr; 1208 | } 1209 | } else { 1210 | str += '\nend'; 1211 | } 1212 | return str; 1213 | } 1214 | 1215 | private codeFromImport(ast: Import): string { 1216 | this.assert(false, ast, 'Not support Import yet!'); 1217 | return ''; 1218 | } 1219 | 1220 | private codeFromImportDeclaration(ast: ImportDeclaration): string { 1221 | this.importAsts.push(ast); 1222 | return ''; 1223 | } 1224 | 1225 | private codeFromImportDefaultSpecifier(ast: ImportDefaultSpecifier): string { 1226 | this.assert(false, ast, 'Not support ImportDefaultSpecifier yet!'); 1227 | return ''; 1228 | } 1229 | 1230 | private codeFromImportNamespaceSpecifier(ast: ImportNamespaceSpecifier): string { 1231 | this.assert(false, ast, 'Not support ImportNamespaceSpecifier yet!'); 1232 | return ''; 1233 | } 1234 | 1235 | private codeFromImportSpecifier(ast: ImportSpecifier): string { 1236 | this.assert(false, ast, 'Not support ImportSpecifier yet!'); 1237 | return ''; 1238 | } 1239 | 1240 | private codeFromLabeledStatement(ast: LabeledStatement): string { 1241 | this.assert(false, ast, 'Not support LabeledStatement yet!'); 1242 | return ''; 1243 | } 1244 | 1245 | private codeFromLiteral(ast: Literal): string { 1246 | if (ast.regex) { 1247 | let regexRepl = this.getRegexReplacor(ast.regex.pattern); 1248 | if(regexRepl) { 1249 | return '\'' + regexRepl + '\''; 1250 | } 1251 | if(this.unknowRegexs.indexOf(ast.regex.pattern) < 0) { 1252 | this.unknowRegexs.push(ast.regex.pattern); 1253 | } 1254 | if(this.option.translateRegex) { 1255 | let luaRegex = ast.regex.pattern.replace(/(?= this.calPriority(ast)) { 1282 | left = '(' + left + ')'; 1283 | } 1284 | let right = this.codeFromAST(ast.right); 1285 | if (this.calPriority(ast.right) >= this.calPriority(ast)) { 1286 | right = '(' + right + ')'; 1287 | } 1288 | let optStr = ast.operator; 1289 | if(optStr == '&&') { 1290 | optStr = 'and'; 1291 | } else if(optStr == '||') { 1292 | optStr = 'or'; 1293 | } 1294 | let str = left + ' ' + optStr + ' ' + right; 1295 | return str; 1296 | } 1297 | 1298 | private codeFromMemberExpression(ast: MemberExpression): string { 1299 | (ast.property as any).__parent = ast; 1300 | let objStr = this.codeFromAST(ast.object); 1301 | let str = objStr; 1302 | if (this.noBraceTypes.indexOf(ast.object.type) < 0) { 1303 | str = '(' + str + ')'; 1304 | } 1305 | if (ast.computed) { 1306 | let propertyStr = this.codeFromAST(ast.property); 1307 | if(propertyStr.length == 1) { 1308 | // Auto modify xx[i] to xx[i + 1] 1309 | propertyStr += '+1'; 1310 | } else { 1311 | // Add some tips 1312 | propertyStr += this.wrapTip(str + '下标访问可能不正确'); 1313 | } 1314 | str += '[' + propertyStr + ']'; 1315 | } else { 1316 | if(ast.property.type == AST_NODE_TYPES.Identifier && ast.property.name == 'length') { 1317 | if((!(ast as any).__parent || (ast as any).__parent.type != AST_NODE_TYPES.AssignmentExpression)) { 1318 | str = '#' + str; 1319 | } else { 1320 | str += '.length' + this.wrapTip('修改数组长度需要手动处理。'); 1321 | } 1322 | } else { 1323 | // TODO: do something with static members 1324 | let pstr = this.codeFromAST(ast.property); 1325 | let parent = (ast as any).__parent; 1326 | if(parent && parent.type == AST_NODE_TYPES.CallExpression && 1327 | (!this.inStatic || ast.object.type != AST_NODE_TYPES.ThisExpression) && 1328 | (!this.classMap[objStr] || !this.classMap[objStr].funcs[pstr] || !this.classMap[objStr].funcs[pstr].isStatic) && 1329 | (!this.moduleMap[objStr] || !this.moduleMap[objStr].funcs[pstr])) { 1330 | str += ':'; 1331 | } else { 1332 | str += '.'; 1333 | } 1334 | str += pstr; 1335 | } 1336 | } 1337 | return str; 1338 | } 1339 | 1340 | private codeFromMetaProperty(ast: MetaProperty): string { 1341 | this.assert(false, ast, 'Not support MetaProperty yet!'); 1342 | return ''; 1343 | } 1344 | 1345 | private codeFromMethodDefinition(ast: MethodDefinition): string { 1346 | let funcName = null; 1347 | if (ast.key) { 1348 | funcName = this.codeFromAST(ast.key); 1349 | } 1350 | if(ast.value.type == "TSEmptyBodyFunctionExpression") { 1351 | this.assert(false, ast, 'Not support TSEmptyBodyFunctionExpression yet!'); 1352 | } 1353 | return this.codeFromFunctionExpressionInternal(funcName, ast.static, ast.kind, ast.value as FunctionExpression); 1354 | } 1355 | 1356 | private codeFromNewExpression(ast: NewExpression): string { 1357 | let callee = this.codeFromAST(ast.callee); 1358 | if('Date' == callee) { 1359 | this.addImport('date'); 1360 | } 1361 | if (this.calPriority(ast.callee) > this.calPriority(ast)) { 1362 | callee = '(' + callee + ')'; 1363 | } 1364 | if('Array' == callee/* && ast.arguments.length == 0*/) { 1365 | return '{}'; 1366 | } 1367 | let argStr = ''; 1368 | for (let i = 0, len = ast.arguments.length; i < len; i++) { 1369 | if (i > 0) { 1370 | argStr += ', '; 1371 | } 1372 | argStr += this.codeFromAST(ast.arguments[i]); 1373 | } 1374 | if('RegExp' == callee) { 1375 | return argStr; 1376 | } 1377 | let str = callee + '(' + argStr + ')'; 1378 | return str; 1379 | } 1380 | 1381 | private codeFromObjectExpression(ast: ObjectExpression): string { 1382 | var str = '{'; 1383 | for (let i = 0, len = ast.properties.length; i < len; i++) { 1384 | if (i > 0) { 1385 | str += ', '; 1386 | } 1387 | str += this.codeFromAST(ast.properties[i]); 1388 | } 1389 | return str + '}'; 1390 | } 1391 | 1392 | private codeFromObjectPattern(ast: ObjectPattern): string { 1393 | this.assert(false, ast, 'Not support ObjectPattern yet!'); 1394 | return ''; 1395 | } 1396 | 1397 | private codeFromProgram(ast: Program): string { 1398 | let str = ''; 1399 | for (let i = 0, len = ast.body.length; i < len; i++) { 1400 | let stm = ast.body[i]; 1401 | let bodyStr = this.codeFromAST(stm); 1402 | if(bodyStr) { 1403 | if(i > 0) { 1404 | str += '\n'; 1405 | } 1406 | str += bodyStr; 1407 | } 1408 | } 1409 | return str; 1410 | } 1411 | 1412 | private codeFromProperty(ast: Property): string { 1413 | (ast.key as any).__parent = ast; 1414 | return this.codeFromAST(ast.key) + '=' + this.codeFromAST(ast.value); 1415 | } 1416 | 1417 | private codeFromRestElement(ast: RestElement): string { 1418 | return '...'; 1419 | } 1420 | 1421 | private codeFromReturnStatement(ast: ReturnStatement): string { 1422 | if(!ast.argument) { 1423 | return 'return'; 1424 | } 1425 | let argStr = this.codeFromAST(ast.argument); 1426 | if(ast.argument.type == AST_NODE_TYPES.UpdateExpression) { 1427 | let uaStr = this.codeFromAST((ast.argument as UpdateExpression).argument); 1428 | if((ast.argument as UpdateExpression).prefix) { 1429 | return argStr + '\nreturn ' + uaStr; 1430 | } else { 1431 | let newVarName = this.getVarNameBefore(uaStr); 1432 | if(newVarName) { 1433 | return 'local ' + newVarName + ' = ' + uaStr + '\n' + argStr + '\nreturn ' + uaStr; 1434 | } 1435 | } 1436 | } 1437 | return 'return ' + argStr; 1438 | } 1439 | 1440 | private codeFromSequenceExpression(ast: SequenceExpression): string { 1441 | let str = ''; 1442 | for (var i = 0, len = ast.expressions.length; i < len; i++) { 1443 | if (i > 0) { 1444 | str += '; '; 1445 | } 1446 | str += this.codeFromAST(ast.expressions[i]); 1447 | } 1448 | return str; 1449 | } 1450 | 1451 | private codeFromSpreadElement(ast: SpreadElement): string { 1452 | return '...'; 1453 | } 1454 | 1455 | private codeFromSuper(ast: Super): string { 1456 | return 'super'; 1457 | } 1458 | 1459 | private codeFromSwitchCase(ast: SwitchCase): string { 1460 | let str = ''; 1461 | if (ast.test) { 1462 | str += '[' + this.codeFromAST(ast.test) + '] = function()\n'; 1463 | } else { 1464 | str += '["default"] = function()\n'; 1465 | } 1466 | let csqStr = ''; 1467 | for (let i = 0, len = ast.consequent.length; i < len; i++) { 1468 | if(ast.consequent[i].type != AST_NODE_TYPES.BreakStatement) { 1469 | if(i > 0) { 1470 | csqStr += '\n'; 1471 | } 1472 | this.inSwitchCase = true; 1473 | csqStr += this.codeFromAST(ast.consequent[i]); 1474 | this.inSwitchCase = false; 1475 | } 1476 | } 1477 | if(csqStr) { 1478 | str += this.indent(csqStr); 1479 | str += '\nend'; 1480 | } else { 1481 | str += ' end'; 1482 | } 1483 | return str; 1484 | } 1485 | 1486 | private codeFromSwitchStatement(ast: SwitchStatement): string { 1487 | let str = 'local switch = {\n'; 1488 | let caseStr = ''; 1489 | for (let i = 0, len = ast.cases.length; i < len; i++) { 1490 | if (i > 0) { 1491 | caseStr += ',\n'; 1492 | } 1493 | caseStr += this.codeFromSwitchCase(ast.cases[i]); 1494 | } 1495 | str += this.indent(caseStr); 1496 | str += '\n}\n'; 1497 | str += 'local casef = switch[' + this.codeFromAST(ast.discriminant) + ']\n'; 1498 | str += 'if not casef then casef = switch["default"] end\n'; 1499 | str += 'if casef then casef() end'; 1500 | return str; 1501 | } 1502 | 1503 | private codeFromTaggedTemplateExpression(ast: TaggedTemplateExpression): string { 1504 | this.assert(false, ast, 'Not support TaggedTemplateExpression yet!'); 1505 | return ''; 1506 | } 1507 | 1508 | private codeFromTemplateElement(ast: TemplateElement): string { 1509 | this.assert(false, ast, 'Not support TemplateElement yet!'); 1510 | return ''; 1511 | } 1512 | 1513 | private codeFromTemplateLiteral(ast: TemplateLiteral): string { 1514 | this.assert(false, ast, 'Not support TemplateLiteral yet!'); 1515 | return ''; 1516 | } 1517 | 1518 | private codeFromThisExpression(ast: ThisExpression): string { 1519 | if(this.inStatic) { 1520 | return this.className; 1521 | } 1522 | return 'self'; 1523 | } 1524 | 1525 | private codeFromThrowStatement(ast: ThrowStatement): string { 1526 | return 'error(' + this.codeFromAST(ast.argument) + ')'; 1527 | } 1528 | 1529 | private codeFromTryStatement(ast: TryStatement): string { 1530 | this.addImport('trycatch'); 1531 | 1532 | let str = 'try_catch{\n'; 1533 | let tcStr = 'main = function()\n'; 1534 | tcStr += this.indent(this.codeFromAST(ast.block)); 1535 | tcStr += '\nend'; 1536 | if(ast.handler) { 1537 | tcStr += ',\ncatch = '; 1538 | tcStr += this.indent(this.codeFromAST(ast.handler), 1); 1539 | tcStr += '\nend' 1540 | } 1541 | if(ast.finalizer) { 1542 | tcStr += ',\nfinally = function()\n'; 1543 | tcStr += this.indent(this.codeFromAST(ast.finalizer)); 1544 | tcStr += '\nend' 1545 | } 1546 | str += this.indent(tcStr); 1547 | str += '\n}'; 1548 | return str; 1549 | } 1550 | 1551 | private codeFromUnaryExpression(ast: UnaryExpression): string { 1552 | let str; 1553 | let agm = this.codeFromAST(ast.argument); 1554 | if (this.calPriority(ast.argument) >= this.calPriority(ast)) { 1555 | agm = '(' + agm + ')'; 1556 | } 1557 | if (ast.prefix) { 1558 | if ('typeof' == ast.operator) { 1559 | str = 'type(' + agm + ')'; 1560 | } else if ('delete' == ast.operator) { 1561 | str = agm + ' = nil'; 1562 | } else if ('!' == ast.operator) { 1563 | str = 'not ' + agm; 1564 | } else if ('void' == ast.operator) { 1565 | this.assert(false, ast, 'Not support void yet!'); 1566 | } else { 1567 | this.assert('-' == ast.operator, ast, 'Not support UnaryOperator: ' + ast.operator); 1568 | str = ast.operator + agm; 1569 | } 1570 | } else { 1571 | str = agm + ast.operator; 1572 | } 1573 | return str; 1574 | } 1575 | 1576 | private codeFromUpdateExpression(ast: UpdateExpression): string { 1577 | let astr = this.codeFromAST(ast.argument); 1578 | if (this.calPriority(ast.argument) >= this.calPriority(ast)) { 1579 | astr = '(' + astr + ')'; 1580 | } 1581 | // if (ast.prefix) { 1582 | // // TODO: Consider if the value is right when used as Assignment/Property 1583 | // str = ast.operator + str; 1584 | // } else { 1585 | // str = str + ast.operator; 1586 | // } 1587 | let str = astr + '=' + astr + ast.operator.substring(0, 1) + '1'; 1588 | let parent = (ast as any).__parent; 1589 | if(parent) { 1590 | if(parent.type == AST_NODE_TYPES.BinaryExpression || parent.type == AST_NODE_TYPES.MemberExpression) { 1591 | if(ast.prefix) { 1592 | str = this.wrapPop(str, true); 1593 | str += astr; 1594 | } else { 1595 | let newVarName = this.getVarNameBefore(astr); 1596 | if(newVarName) { 1597 | str = this.wrapPop('local ' + newVarName + ' = ' + astr, true) + this.wrapPop(str, true); 1598 | str += newVarName; 1599 | } 1600 | } 1601 | } 1602 | } 1603 | return str; 1604 | } 1605 | 1606 | private codeFromVariableDeclaration(ast: VariableDeclaration): string { 1607 | // not support const 1608 | let forInOfTypes: string[] = [AST_NODE_TYPES.ForInStatement, AST_NODE_TYPES.ForOfStatement]; 1609 | let isForInOf = (ast as any).__parent && forInOfTypes.indexOf((ast as any).__parent.type) >= 0; 1610 | let str = ''; 1611 | for (let i = 0, len = ast.declarations.length; i < len; i++) { 1612 | let d = ast.declarations[i]; 1613 | if(isForInOf) { 1614 | (d as any).__isForInOf = true; 1615 | if(i > 0) { 1616 | str += ', '; 1617 | } 1618 | } else { 1619 | if(i > 0) { 1620 | str += '\n'; 1621 | } 1622 | } 1623 | str += this.codeFromVariableDeclarator(d); 1624 | } 1625 | return str; 1626 | } 1627 | 1628 | private codeFromVariableDeclarator(ast: VariableDeclarator): string { 1629 | let str = ''; 1630 | let idStr = this.codeFromAST(ast.id); 1631 | let initStr = ''; 1632 | if(ast.init) { 1633 | initStr = this.codeFromAST(ast.init); 1634 | if(ast.init.type == AST_NODE_TYPES.AssignmentExpression) { 1635 | str = initStr + '\n'; 1636 | initStr = this.codeFromAST((ast.init as AssignmentExpression).left); 1637 | } else if(ast.init.type == AST_NODE_TYPES.UpdateExpression) { 1638 | let uaStr = this.codeFromAST((ast.init as UpdateExpression).argument); 1639 | if((ast.init as UpdateExpression).prefix) { 1640 | str = initStr + '\n'; 1641 | initStr = uaStr; 1642 | } else { 1643 | let newVarName = this.getVarNameBefore(uaStr); 1644 | if(newVarName) { 1645 | str = 'local ' + newVarName + ' = ' + uaStr + '\n' + initStr + '\n'; 1646 | initStr = uaStr; 1647 | } 1648 | } 1649 | } 1650 | } 1651 | if(!(ast as any).__isForInOf) { 1652 | str += 'local '; 1653 | } 1654 | str += idStr; 1655 | if(initStr) { 1656 | str += ' = ' + initStr; 1657 | } else if(!(ast as any).__isForInOf) { 1658 | str += ' = nil'; 1659 | } 1660 | return str; 1661 | } 1662 | 1663 | private codeFromWhileStatement(ast: WhileStatement): string { 1664 | let str = 'while(' + this.codeFromAST(ast.test) + ')\n'; 1665 | str += 'do\n'; 1666 | let bodyCode = this.codeFromAST(ast.body); 1667 | str += bodyCode + '\n'; 1668 | str += 'end'; 1669 | return str; 1670 | } 1671 | 1672 | private codeFromWithStatement(ast: WithStatement): string { 1673 | this.assert(false, ast, 'Not support WithStatement yet'); 1674 | return ''; 1675 | } 1676 | 1677 | private codeFromYieldExpression(ast: YieldExpression): string { 1678 | let str = 'coroutine.yield('; 1679 | str += this.codeFromAST(ast.argument); 1680 | str += ')'; 1681 | return str; 1682 | } 1683 | 1684 | private codeFromTSAbstractMethodDefinition(ast: TSAbstractMethodDefinition): string { 1685 | return this.codeFromMethodDefinition(ast as any); 1686 | } 1687 | 1688 | private codeFromTSAsExpression(ast: TSAsExpression): string { 1689 | return this.codeFromAST(ast.expression); 1690 | } 1691 | 1692 | private codeFromTSDeclareFunction(ast: TSDeclareFunction): string { 1693 | return this.wrapTip('请手动处理DeclareFunction'); 1694 | } 1695 | 1696 | private codeFromTSEnumDeclaration(ast: TSEnumDeclaration): string { 1697 | let str = ''; 1698 | if(!(ast as any).__exported) { 1699 | str += 'local '; 1700 | } 1701 | let enumName = this.codeFromAST(ast.id); 1702 | str += enumName + ' = {\n'; 1703 | let membersStr = ''; 1704 | let nextValue = 0; 1705 | for(let i = 0, len = ast.members.length; i < len; i++) { 1706 | if(i > 0) { 1707 | membersStr += ',\n'; 1708 | } 1709 | let m = ast.members[i]; 1710 | membersStr += this.codeFromAST(m.id) + ' = '; 1711 | if(m.initializer) { 1712 | membersStr += this.codeFromAST(m.initializer) 1713 | nextValue = ((m.initializer as Literal).value as number) + 1; 1714 | } else { 1715 | membersStr += nextValue; 1716 | nextValue++; 1717 | } 1718 | } 1719 | str += this.indent(membersStr) + '\n'; 1720 | str += '}'; 1721 | this.assert(!ast.const, ast); 1722 | this.assert(!ast.declare, ast); 1723 | this.assert(!ast.modifiers, ast); 1724 | this.assert(!ast.decorators, ast); 1725 | 1726 | if(this.filePath && (ast as any).__exported && this.fileName != enumName) { 1727 | this.diffEnumNames.push(enumName); 1728 | } else { 1729 | this.nativeEnumNames.push(enumName); 1730 | } 1731 | this.classContentMap[enumName] = str; 1732 | return ''; 1733 | } 1734 | 1735 | private codeFromTSModuleBlock(ast: TSModuleBlock): string { 1736 | let str = ''; 1737 | for(let i = 0, len = ast.body.length; i < len; i++) { 1738 | (ast.body[i] as any).__module = this.moduleName; 1739 | let bstr = this.codeFromAST(ast.body[i]); 1740 | if(bstr) { 1741 | if(i > 0) { 1742 | str += '\n'; 1743 | } 1744 | str += bstr; 1745 | } 1746 | } 1747 | return str; 1748 | } 1749 | 1750 | private codeFromTSModuleDeclaration(ast: TSModuleDeclaration): string { 1751 | this.moduleName = this.codeFromAST(ast.id); 1752 | let str = this.moduleName + ' = {}\n'; 1753 | if(ast.body) { 1754 | str += this.codeFromAST(ast.body); 1755 | } 1756 | this.moduleName = null; 1757 | return str; 1758 | } 1759 | 1760 | private codeFromTSInterfaceDeclaration(ast: TSInterfaceDeclaration): string { 1761 | return ''; 1762 | } 1763 | 1764 | private codeFromTSTypeAssertion(ast: TSTypeAssertion): string { 1765 | return this.codeFromAST(ast.expression); 1766 | } 1767 | 1768 | private indent(str: string, fromLine: number = 0): string { 1769 | let indentStr = ' '; 1770 | // for(let i = 0; i < blockDeep; i++) { 1771 | // indentStr += ' '; 1772 | // } 1773 | let endWithNewLine = str.substr(str.length - 1) == '\n'; 1774 | let lines = str.split(/\n/); 1775 | let newStr = ''; 1776 | for(let i = 0, len = lines.length; i < len; i++) { 1777 | if(i > 0) { 1778 | newStr += '\n'; 1779 | } 1780 | if(i >= fromLine) { 1781 | newStr += indentStr; 1782 | } 1783 | newStr += lines[i]; 1784 | } 1785 | if(endWithNewLine) { 1786 | newStr += '\n'; 1787 | } 1788 | return newStr; 1789 | } 1790 | 1791 | private getRegexReplacor(pattern: string): string { 1792 | let regexRepl = this.regexReplConf[pattern]; 1793 | if(regexRepl) { 1794 | return regexRepl; 1795 | } 1796 | let marginBegin = false, marginEnd = false; 1797 | if(pattern.charAt(0) == '^') { 1798 | marginBegin = true; 1799 | pattern = pattern.substr(1); 1800 | } 1801 | if(pattern.charAt(pattern.length - 1) == '$') { 1802 | marginEnd = true; 1803 | pattern = pattern.substr(0, pattern.length - 1); 1804 | } 1805 | regexRepl = this.regexReplConf[pattern]; 1806 | if(!regexRepl) { 1807 | regexRepl = this.regexReplConf['^' + pattern]; 1808 | if(regexRepl) { 1809 | regexRepl = regexRepl.substr(1); 1810 | } else { 1811 | regexRepl = this.regexReplConf[pattern + '$']; 1812 | if(regexRepl) { 1813 | regexRepl = regexRepl.substr(0, regexRepl.length - 1); 1814 | } else { 1815 | regexRepl = this.regexReplConf['^' + pattern + '$']; 1816 | if(regexRepl) { 1817 | regexRepl = regexRepl.substr(1, regexRepl.length - 2); 1818 | } 1819 | } 1820 | } 1821 | } 1822 | if(regexRepl) { 1823 | if(marginBegin) { 1824 | regexRepl = '^' + regexRepl; 1825 | } 1826 | if(marginEnd) { 1827 | regexRepl = regexRepl + '$'; 1828 | } 1829 | } 1830 | return regexRepl; 1831 | } 1832 | 1833 | private addUsedId(id: string) { 1834 | let map: { [id: string]: number }; 1835 | if(this.className && this.isDiffClass) { 1836 | map = this.usedIdMapByClass[this.className]; 1837 | } else { 1838 | map = this.usedIdMapByClass[this.fileName]; 1839 | } 1840 | if(map[id]) { 1841 | map[id]++; 1842 | } else { 1843 | map[id] = 1; 1844 | } 1845 | } 1846 | 1847 | private addImport(importName: string) { 1848 | if(this.imports.indexOf(importName) < 0) { 1849 | this.imports.push(importName); 1850 | } 1851 | let imArr: string[]; 1852 | if(this.className && this.isDiffClass) { 1853 | imArr = this.importMapByClass[this.className]; 1854 | } else { 1855 | imArr = this.importMapByClass[this.fileName]; 1856 | } 1857 | if(imArr.indexOf(importName) < 0) { 1858 | imArr.push(importName); 1859 | } 1860 | } 1861 | 1862 | private pintHit(ast: any): void { 1863 | console.warn('hit %s!', ast.type); 1864 | console.log(util.inspect(ast, true, 4)); 1865 | } 1866 | 1867 | private wrapTip(rawTip: string): string { 1868 | return this.option.addTip ? '[ts2lua]' + rawTip.replace(/.*?<\/TT>/g, '') + '' : ''; 1869 | } 1870 | 1871 | private wrapPop(popStr: string, upOrDown: boolean): string { 1872 | if(popStr) { 1873 | return '<~ts2lua' + popStr.length + 'u>' + popStr; 1874 | } else { 1875 | return '<~ts2lua' + popStr.length + 'd>' + popStr; 1876 | } 1877 | } 1878 | 1879 | private formatTip(content: string): string { 1880 | let re = /.*?<\/TT>/; 1881 | let rema = content.match(re); 1882 | while(rema) { 1883 | let rawComment = rema[0]; 1884 | let rawCommentLen = rawComment.length; 1885 | let preContent = content.substr(0, rema.index); 1886 | let postContent = content.substr(rema.index + rawCommentLen); 1887 | let luaComment = '-- ' + rawComment.substr(4, rawCommentLen - 9); 1888 | let lastNewLineIdx = preContent.lastIndexOf('\n'); 1889 | if(lastNewLineIdx) { 1890 | let tmpStr = preContent.substr(lastNewLineIdx + 1); 1891 | let blanksRema = tmpStr.match(/^ */); 1892 | if(blanksRema) { 1893 | luaComment = blanksRema[0] + luaComment; 1894 | } 1895 | content = preContent.substr(0, lastNewLineIdx) + '\n' + luaComment + '\n' + tmpStr + postContent; 1896 | } else { 1897 | content = luaComment + '\n' + preContent + postContent; 1898 | } 1899 | rema = content.match(re); 1900 | } 1901 | return content; 1902 | } 1903 | 1904 | private formatPop(content: string): string { 1905 | let re = /<~ts2lua(\d+)u>/; 1906 | let rema = content.match(re); 1907 | while(rema) { 1908 | let rawComment = rema[0]; 1909 | let codeLen = Number(rema[1]); 1910 | let rawCommentLen = rawComment.length; 1911 | let preContent = content.substr(0, rema.index); 1912 | let postContent = content.substr(rema.index + rawCommentLen + codeLen); 1913 | let code2Pop = content.substr(rema.index + rawCommentLen, codeLen); 1914 | let lastNewLineIdx = preContent.lastIndexOf('\n'); 1915 | if(lastNewLineIdx) { 1916 | let tmpStr = preContent.substr(lastNewLineIdx + 1); 1917 | let blanksRema = tmpStr.match(/^ */); 1918 | if(blanksRema) { 1919 | code2Pop = blanksRema[0] + code2Pop; 1920 | } 1921 | content = preContent.substr(0, lastNewLineIdx) + '\n' + code2Pop + '\n' + tmpStr + postContent; 1922 | } else { 1923 | content = code2Pop + '\n' + preContent + postContent; 1924 | } 1925 | rema = content.match(re); 1926 | } 1927 | return content; 1928 | } 1929 | 1930 | private getVarNameBefore(varName: string): string { 1931 | let theVarName = varName.match(/[^\.]+$/); 1932 | if(theVarName) { 1933 | let newVarName = theVarName[0].replace(/\[/g, '_').replace(/\]/g, '_') + 'Before'; 1934 | return newVarName; 1935 | } 1936 | return null; 1937 | } 1938 | 1939 | private assert(cond: boolean, ast: BaseNode, message: string = null) { 1940 | if(!cond) { 1941 | if(this.isDevMode) { 1942 | console.log(util.inspect(ast, true, 6)); 1943 | } 1944 | console.log('\x1B[36m%s\x1B[0m:\x1B[33m%d:%d\x1B[0m - \x1B[31merror\x1B[0m: %s', this.filePath, ast.loc.start.line, ast.loc.start.column, message ? message : 'Error'); 1945 | } 1946 | } 1947 | } --------------------------------------------------------------------------------