diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..619cefd --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +release/ +test_addon/ + +build.log diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2c623a2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,26 @@ +{ + "Lua.runtime.version": "LuaJIT", + "Lua.workspace.library": [ + "c:/Users/avril/.vscode/extensions/nameouschangey.lifeboatapi-0.0.33/assets/lua/Common/", + "c:/Users/avril/.vscode/extensions/nameouschangey.lifeboatapi-0.0.33/assets/lua/Addon/", + "C:/Users/avril/AppData/Roaming/Code/User/globalStorage/sumneko.lua/addonManager/addons/luafilesystem/module/library", + "../SelenScript", + "../SelenScript/libs" + ], + "Lua.workspace.checkThirdParty": false, + "Lua.runtime.pathStrict": true, + "Lua.runtime.path": [ + "?.lua", + "?/init.lua", + "../SelenScript/libs/?.lua", + "../SelenScript/libs/?/init.lua", + "../SelenScript/?.lua", + "../SelenScript/?/init.lua" + ], + "Lua.workspace.ignoreDir": [ + ".vscode", + "release", + "test_addon", + "IMAI" + ] +} \ No newline at end of file diff --git a/build_release.bat b/build_release.bat new file mode 100644 index 0000000..f7ee45f --- /dev/null +++ b/build_release.bat @@ -0,0 +1,16 @@ +@echo off +@REM I really hate batch... + + +if exist "./release/" rmdir "./release/" /q /s +mkdir "./release/" + +mkdir "./release/SSSWTool" +copy "./main.lua" "./release/SSSWTool/main.lua" +copy "./ssswtool.bat" "./release/SSSWTool/ssswtool.bat" +copy "./transform_lua_to_swaddon.lua" "./release/SSSWTool/transform_lua_to_swaddon.lua" +copy "./transform_swaddon_tracing.lua" "./release/SSSWTool/transform_swaddon_tracing.lua" + +mkdir "./release/SelenScript" +xcopy "../SelenScript/libs" "./release/SelenScript/libs" /s /e /i +xcopy "../SelenScript/SelenScript" "./release/SelenScript/SelenScript" /s /e /i diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..167d1c7 --- /dev/null +++ b/main.lua @@ -0,0 +1,137 @@ +local args = assert(arg) + +local tool_path = assert(args[0]):gsub("[\\/][^\\/]*$", "") + +package.path = ("%s/?.lua;%s/?/init.lua;%s/../SelenScript/libs/?.lua;%s/../SelenScript/libs/?/init.lua;%s/../SelenScript/?.lua;%s/../SelenScript/?/init.lua;"):gsub("%%s", tool_path) +package.cpath = ("%s/?.dll;%s/../SelenScript/libs/?.dll;"):gsub("%%s", tool_path) + +local lfs = require "lfs" +local logging = require "logging".windows_enable_ansi().set_log_file("build.log", true) +---@diagnostic disable-next-line: duplicate-set-field +logging.get_source = function() return "" end + +local Parser = require "SelenScript.parser.parser" +local Transformer = require "SelenScript.transformer.transformer" +local Emitter = require "SelenScript.emitter.emitter" +local Utils = require "SelenScript.utils" +local ASTHelpers = require "SelenScript.transformer.ast_helpers" + +local SWAddonTransformerDefs = require "transform_lua_to_swaddon" +local SWAddonTracerTransformerDefs = require "transform_swaddon_tracing" + + +if table.remove(args, 1) ~= "build" then + print_error("Argument #1 expected 'build'") + os.exit(-1) +end + +local addon_dir = (table.remove(args, 1) or ""):gsub("\\", "/"):gsub("^./", ""):gsub("/$", "") +if #addon_dir <= 0 then + addon_dir = "." +end +local addon_dir_attributes = assert(lfs.attributes(addon_dir), "Invalid arg #1 addon path.") +assert(addon_dir_attributes.mode == "directory", "Invalid arg #1 addon path must be a directory.") + +local script_file = addon_dir .. "/script.lua" +assert(lfs.attributes(script_file), "Addon directory does not contain 'script.lua'.") + +local script_file_src = Utils.readFile(script_file) + +local enable_tracing = false +for i, arg in ipairs(args) do + if arg == "--trace" then + enable_tracing = true + elseif arg == "--no-trace" then + enable_tracing = false + else + print_warn(("Unexpected argument #%i '%s'"):format(i, arg)) + end +end + +local time_start = os.clock() + +local parser +do + local errors + print_info("Creating parser") + parser, errors = Parser.new() + if #errors > 0 then + print_error("-- Parser creation Errors: " .. #errors .. " --") + for _, v in ipairs(errors) do + print_error((v.id or "NO_ID") .. ": " .. v.msg) + end + os.exit(-1) + end + if parser == nil or #errors > 0 then + print_error("Failed to create parser.") + os.exit(-1) + end +end + +local ast, comments +do + local errors + print_info("Parsing 'script.lua'") + ast, errors, comments = parser:parse(script_file_src, script_file) + if #errors > 0 then + print_error("-- Parse Errors: " .. #errors .. " --") + for _, v in ipairs(errors) do + print_error(v.id .. ": " .. v.msg) + end + os.exit(-1) + end +end + +do + print_info("Transforming AST") + local transformer = Transformer.new(SWAddonTransformerDefs) + local errors = transformer:transform(ast, { + addon_dir=addon_dir, + parser=parser, + }) + if #errors > 0 then + print_error("-- Transformer Errors: " .. #errors .. " --") + for _, v in ipairs(errors) do + print_error(v.id .. ": " .. v.msg) + end + os.exit(-1) + end +end + + +if enable_tracing then + print_info("Transforming AST (DBG Tracer)") + local transformer = Transformer.new(SWAddonTracerTransformerDefs) + local errors = transformer:transform(ast, { + addon_dir=addon_dir, + parser=parser, + }) + if #errors > 0 then + print_error("-- Transformer Errors: " .. #errors .. " --") + for _, v in ipairs(errors) do + print_error(v.id .. ": " .. v.msg) + end + os.exit(-1) + end +end + +do + -- Add comment at beginning of file to disable all diagnostics of the file. + -- This isn't required but is nice to have. + table.insert(ast.block.block, 1, ASTHelpers.Nodes.LineComment(ast.block.block[1], "---", "@diagnostic disable")) +end + +do + print_info("Emitting Lua") + local emitter_lua = Emitter.new("lua", {}) + local script_out, script_out_source_map = emitter_lua:generate(ast, { + base_path = addon_dir, + luacats_source_prefix = "..", + }) + + lfs.mkdir(addon_dir .. "/_build") + Utils.writeFile(addon_dir .. "/_build/script.lua", script_out) + + local time_finish = os.clock() + print_info(("Finished in %ss."):format(time_finish-time_start)) +end diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..eeb84de --- /dev/null +++ b/readme.md @@ -0,0 +1,29 @@ +# SSSWTool +A tool utilizing [SelenScript](https://github.com/Avril112113/selenscript)'s parser to combine your StormWorks addon code into a single file. +The catch, it's intelligent and adds more features! + +This project is currently **VERY work in progress**, it's lacking a decent CLI and various features. +As it is right now, it's not considered a replacement for other tools until further testing and features has been finished. + +To clarify, the following is what this tool provides: +- Combining multiple files into one. +- Optional tracing with `--trace` which provides full stack traces at runtime with source file, line and column info +- TODO: Config file (for predefined options and output path, eg) +- TODO: Build output path. +- TODO: Custom build actions. +- TODO: vscode task template. + +## Usage +Run `ssswtool.bat` either directly or `ssswtool` if it's on your PATH. +The file `script.lua` is the entrypoint, any files `require()` from there will be directly included into the output. +Build with `ssswtool build ./`, or with tracing `ssswtool build ./ --trace` +The default output directory is `/_build/script.lua` (not configurable yet) + +## [Releases](https://github.com/Avril112113/SSSWTool/releases) +See the [releases](https://github.com/Avril112113/SSSWTool/releases) for download. +If you want to use `ssswtool` anywhere, consider adding the `./SSSWTool/` to your PATH (the directory containing `ssswtool.bat`). +See (usage)[#Usage] for how to use this tool. + +**Pre-Built Binaries:** +The releases come with `luajit` pre-built, you can delete the files and it'll use your system version instead. +SelenScript also comes with pre-built libraries for `lfs` and `lpeglabel` in `./SelenScript/libs`. diff --git a/ssswtool.bat b/ssswtool.bat new file mode 100644 index 0000000..0f02e66 --- /dev/null +++ b/ssswtool.bat @@ -0,0 +1,9 @@ +@echo off + +if exist "%~dp0luajit.exe" ( + echo - Using provided luajit. + "%~dp0\luajit.exe" "%~dp0main.lua" %* +) else ( + echo - Using system luajit. + luajit "%~dp0main.lua" %* +) diff --git a/tracing_prefix.lua b/tracing_prefix.lua new file mode 100644 index 0000000..c842493 --- /dev/null +++ b/tracing_prefix.lua @@ -0,0 +1,69 @@ +---@diagnostic disable: duplicate-set-field + + +---@alias SS_SW_DBG.INFO {name:string, line:integer, column:integer, file:string} + +SS_SW_DBG = {} +---@type integer[] +SS_SW_DBG._stack = {} + +function SS_SW_DBG._trace_enter(id) + table.insert(SS_SW_DBG._stack, #SS_SW_DBG._stack+1, id) +end + +function SS_SW_DBG._trace_exit(id) + local removed_id = table.remove(SS_SW_DBG._stack, #SS_SW_DBG._stack) + if removed_id ~= id then + local msg = ("Attempt to exit trace '%s' but found '%s' instead."):format(id, removed_id) + debug.log("[SW] [ERROR] " .. msg) + server.announce(server.getAddonData((server.getAddonIndex())).name, msg, -1) + end +end + +function SS_SW_DBG._trace_func(id, f, ...) + SS_SW_DBG._trace_enter(id) + local results = {f(...)} + SS_SW_DBG._trace_exit(id) + return table.unpack(results) +end + +---@param depth integer? +function SS_SW_DBG.stacktrace(depth) + depth = depth or #SS_SW_DBG._stack + local lines = {} + local prev_file + for i=depth,1,-1 do + local id = SS_SW_DBG._stack[i] + local trace = SS_SW_DBG._info[id] + if trace.file ~= prev_file then + prev_file = trace.file + table.insert(lines, (" '%s'"):format(trace.file)) + end + table.insert(lines, ("%s %s @ %s:%s"):format(i, trace.name, trace.line, trace.column)) + end + return lines +end + +---@param expected_depth integer +---@return boolean # true when stack was to be deeper than expected and was shortend with error been logged. +function SS_SW_DBG.check_stack(expected_depth) + if #SS_SW_DBG._stack > expected_depth then + local lines = SS_SW_DBG.stacktrace(#SS_SW_DBG._stack-expected_depth) + table.insert(lines, 1, "Detected unwound stacktrace:") + for i=#SS_SW_DBG._stack-expected_depth,1,-1 do + table.remove(SS_SW_DBG._stack, i) + end + for _, s in ipairs(lines) do debug.log("[SW] [ERROR] " .. s) end + server.announce(server.getAddonData((server.getAddonIndex())).name, table.concat(lines, "\n"), -1) + return true + end + return false +end + +---@return SS_SW_DBG.INFO +function SS_SW_DBG.get_current_info() + return SS_SW_DBG._info[SS_SW_DBG._stack[#SS_SW_DBG._stack]] +end + +---@type SS_SW_DBG.INFO[] +SS_SW_DBG._info = {} diff --git a/transform_lua_to_swaddon.lua b/transform_lua_to_swaddon.lua new file mode 100644 index 0000000..7d971d6 --- /dev/null +++ b/transform_lua_to_swaddon.lua @@ -0,0 +1,180 @@ +local Utils = require "SelenScript.utils" +local ASTHelpers = require "SelenScript.transformer.ast_helpers" +local ASTNodes = ASTHelpers.Nodes + + +---@class Transformer_Lua_to_SWAddon : Transformer +---@field parser Parser +---@field addon_dir string +local TransformerDefs = {} + + +---@param node ASTNode +function TransformerDefs:index(node) + local call_node = node.index + if node.type == "index" and node.expr.name == "require" and #call_node.args == 1 and call_node.args[1].type == "string" then + local modpath = call_node.args[1].value:match("^[\"'](.*)[\"']$") + local filepath, err = package.searchpath(modpath, ("%s/?.lua;%s/?/init.lua;"):format(self.addon_dir, self.addon_dir)) + self.required_files = self.required_files or {} + if err or not filepath then + print_error(("Failed to find '%s'%s"):format(modpath, err)) + return ASTNodes.LongComment(node, nil, ("Failed to find '%s'"):format(modpath)) + elseif not self.required_files[filepath] then + self.required_files[filepath] = true + print_info(("Parsing '%s'"):format(filepath:gsub("^"..Utils.escape_pattern(self.addon_dir).."/?", ""))) + local ast, errors, comments = self.parser:parse(Utils.readFile(filepath), filepath) + if #errors > 0 then + print_error("-- Parse Errors: " .. #errors .. " --") + for _, v in ipairs(errors) do + print_error(v.id .. ": " .. v.msg) + end + os.exit(-1) + end + self:visit(ast) + return ast + end + end + return node +end + + +-- local SPECIAL_NAME = "SS_SW_DBG" + +-- ---@param node ASTNode +-- ---@param id integer +-- ---@param is_recur boolean? +-- local function recur_fill_in_custom_funcs(node, id, is_recur) +-- if node.type == "index" and node.expr.name == SPECIAL_NAME and node.index and node.index.expr.name == "get_my_info" then +-- local call = node.index.index +-- if call.type == "index" then +-- call = (call.expr.type == "call" and call.expr) or (call.index.type == "call" and call.index) +-- end +-- if not call or call.type ~= "call" then +-- return +-- end +-- if #call.args <= 0 then +-- table.insert(call.args, #call.args+1, ASTNodes.numeral(node, tostring(id))) +-- end +-- elseif is_recur == nil or node.type ~= "funcbody" then +-- for i, v in pairs(node) do +-- if type(v) == "table" and v.type ~= nil then +-- recur_fill_in_custom_funcs(v, id, true) +-- end +-- end +-- end +-- end + +-- ---@param node ASTNode +-- ---@param exit_node ASTNode +-- ---@param is_recur boolean? +-- local function recur_add_exit_node(node, exit_node, is_recur) +-- if node.type == "return" then +-- table.insert(node.values, #node.values+1, exit_node) +-- return true +-- elseif is_recur == nil or node.type ~= "funcbody" then +-- local added_exit_node = false +-- for i, v in pairs(node) do +-- if type(v) == "table" and v.type ~= nil then +-- added_exit_node = recur_add_exit_node(v, exit_node, true) or added_exit_node +-- end +-- end +-- return added_exit_node +-- end +-- return false +-- end + +-- ---@param node ASTNode +-- function TransformerDefs:funcbody(node) +-- ---@type ASTNodeSource? +-- local source_node = self:find_parent_of_type(node, "source") +-- assert(source_node ~= nil, "source_node ~= nil") +-- local start_line, start_column = relabel.calcline(source_node.source, node.start) + +-- local name +-- local parent_node = self:get_parent(node) +-- if parent_node.type == "functiondef" then +-- name = emitter:generate(parent_node.name) +-- if name:sub(1, #SPECIAL_NAME) == SPECIAL_NAME then +-- return node +-- end +-- elseif parent_node.type == "function" then +-- local parent_expressionlist_node = self:get_parent(parent_node) +-- if parent_expressionlist_node and parent_expressionlist_node.type == "expressionlist" then +-- local parent_assign_node = self:get_parent(parent_expressionlist_node) +-- if parent_assign_node and parent_assign_node.type == "assign" then +-- local index = Utils.find_key(parent_expressionlist_node, parent_node) +-- local name_node = parent_assign_node.names[index] +-- if name_node then +-- name = emitter:generate(name_node) +-- end +-- end +-- end +-- end +-- self._swdbg_index = self._swdbg_index and self._swdbg_index + 1 or 1 +-- if name == nil then +-- name = "anonymous:"..self._swdbg_index +-- end +-- local source_block_index = 1 +-- for i, v in ipairs(source_node.block.block) do +-- if v.type == "assign" then +-- local name_index = v.names[1] +-- if +-- name_index.expr and name_index.expr.name == "SS_SW_DBG" and +-- name_index.index and name_index.index.expr and name_index.index.expr.name == "_info" +-- then +-- source_block_index = i + 1 +-- end +-- end +-- end +-- table.insert(source_node.block.block, source_block_index, ASTNodes.assign( +-- node, nil, +-- ASTNodes.namelist(node, ASTNodes.index( +-- node, nil, ASTNodes.name(node, SPECIAL_NAME), +-- ASTNodes.index( +-- node, ".", ASTNodes.name(node, "_info"), +-- ASTNodes.index(node, "[", ASTNodes.numeral(node, tostring(self._swdbg_index))) +-- ) +-- )), +-- ASTNodes.expressionlist(node, ASTNodes.table(node, ASTNodes.fieldlist(node, +-- ASTNodes.field(node, ASTNodes.string(node, "name", true), ASTNodes.string(node, name, true)), +-- ASTNodes.field(node, ASTNodes.string(node, "line", true), ASTNodes.numeral(node, tostring(start_line))), +-- ASTNodes.field(node, ASTNodes.string(node, "column", true), ASTNodes.numeral(node, tostring(start_column))) +-- ))) +-- )) +-- table.insert(node.block, 1, +-- ASTNodes.index( +-- node, nil, ASTNodes.name(node, SPECIAL_NAME), +-- ASTNodes.index( +-- node, ".", ASTNodes.name(node, "_trace_enter"), +-- ASTNodes.call( +-- node, ASTNodes.expressionlist( +-- node, +-- ASTNodes.numeral(node, tostring(self._swdbg_index)) +-- ) +-- ) +-- ) +-- ) +-- ) +-- local exit_node_call = ASTNodes.index( +-- node, nil, ASTNodes.name(node, SPECIAL_NAME), +-- ASTNodes.index( +-- node, ".", ASTNodes.name(node, "_trace_exit"), +-- ASTNodes.call( +-- node, ASTNodes.expressionlist( +-- node, +-- ASTNodes.numeral(node, tostring(self._swdbg_index)) +-- ) +-- ) +-- ) +-- ) +-- if not recur_add_exit_node(node.block, exit_node_call) then +-- table.insert(node.block, #node.block+1, exit_node_call) +-- end + +-- -- recur_fill_in_custom_funcs(node, self._swdbg_index) + +-- return node +-- end + + +return TransformerDefs diff --git a/transform_swaddon_tracing.lua b/transform_swaddon_tracing.lua new file mode 100644 index 0000000..8727118 --- /dev/null +++ b/transform_swaddon_tracing.lua @@ -0,0 +1,213 @@ +local modpath = ... +---@diagnostic disable-next-line: param-type-mismatch +local modfolderpath = package.searchpath(modpath, package.path):gsub("[\\/][^\\/]*$", "") +local TRACING_PREFIX_FILE = modfolderpath .. "/tracing_prefix.lua" + + +local Utils = require "SelenScript.utils" +local ASTHelpers = require "SelenScript.transformer.ast_helpers" +local relabel = require "relabel" +local ASTNodes = ASTHelpers.Nodes +local Emitter = require "SelenScript.emitter.emitter" +local AST = require "SelenScript.parser.ast" + + +--- Used for converting AST nodes into strings +local emitter = Emitter.new("lua", {}) + +local SPECIAL_NAME = "SS_SW_DBG" + +local EVENT_HOOKS = { + ["onTick"]=1, + -- ["onTick"]=1, ["onCreate"]=1, ["onDestroy"]=1, ["onCustomCommand"]=1, ["onChatMessage"]=1, ["httpReply"]=1, + -- ["onPlayerJoin"]=1, ["onPlayerLeave"]=1, ["onPlayerRespawn"]=1, ["onPlayerDie"]=1, ["onPlayerSit"]=1, ["onPlayerUnsit"]=1, + -- ["onToggleMap"]=1, + -- ["onCharacterSit"]=1, ["onCharacterUnsit"]=1, ["onCharacterPickup"]=1, + -- ["onCreatureSit"]=1, ["onCreatureUnsit"]=1, ["onCreaturePickup"]=1, + -- ["onEquipmentPickup"]=1, ["onEquipmentDrop"]=1, + -- ["onGroupSpawn"]=1, ["onVehicleSpawn"]=1, ["onVehicleDespawn"]=1, ["onVehicleLoad"]=1, ["onVehicleUnload"]=1, ["onVehicleTeleport"]=1, + -- ["onVehicleDamaged"]=1, + -- ["onButtonPress"]=1, + -- ["onObjectLoad"]=1, ["onObjectUnload"]=1, + -- ["onFireExtinguished"]=1, ["onForestFireSpawned"]=1, ["onForestFireExtinguised"]=1, + -- ["onTornado"]=1, ["onMeteor"]=1, ["onTsunami"]=1, ["onWhirlpool"]=1, ["onVolcano"]=1, ["onOilSpill"]=1, + -- ["onSpawnAddonComponent"]=1, +} + +---@class Transformer_SWAddon_Tracing : Transformer +---@field parser Parser +---@field addon_dir string +local TransformerDefs = {} + + +---@param node ASTNode +---@return ASTNodeSource? +function TransformerDefs:_get_root_source(node) + local source = node + while true do + local t = self:find_parent_of_type(source, "source") + if t then + source = t + else + break + end + end + return source.type == "source" and source or nil +end + + +---@param node ASTNodeSource +function TransformerDefs:source(node) + local source = self:_get_root_source(node) + ---@cast source -? + if not source._SWAddon_Tracing_HasPrefix then + source._SWAddon_Tracing_HasPrefix = true + local ast, errors, comments = self.parser:parse(Utils.readFile(TRACING_PREFIX_FILE), TRACING_PREFIX_FILE) + if #errors > 0 then + print_error("-- Parse Errors: " .. #errors .. " --") + for _, v in ipairs(errors) do + print_error(v.id .. ": " .. v.msg) + end + os.exit(-1) + end + table.insert(source.block.block, 1, ast) + end + return node +end + +-- ---@param node ASTNode +-- ---@param id integer +-- ---@param is_recur boolean? +-- local function recur_fill_in_custom_funcs(node, id, is_recur) +-- if node.type == "index" and node.expr.name == SPECIAL_NAME and node.index and node.index.expr.name == "get_my_info" then +-- local call = node.index.index +-- if call.type == "index" then +-- call = (call.expr.type == "call" and call.expr) or (call.index.type == "call" and call.index) +-- end +-- if not call or call.type ~= "call" then +-- return +-- end +-- if #call.args <= 0 then +-- table.insert(call.args, #call.args+1, ASTNodes.numeral(node, tostring(id))) +-- end +-- elseif is_recur == nil or node.type ~= "funcbody" then +-- for i, v in pairs(node) do +-- if type(v) == "table" and v.type ~= nil then +-- recur_fill_in_custom_funcs(v, id, true) +-- end +-- end +-- end +-- end + + +---@param node ASTNode +function TransformerDefs:_generate_check_call(node, name) + table.insert(node.block, 1, ASTNodes.index( + node, nil, ASTNodes.name(node, SPECIAL_NAME), + ASTNodes.index( + node, ".", ASTNodes.name(node, "check_stack"), + ASTNodes.call( + node, ASTNodes.expressionlist( + node, + ASTNodes.numeral(node, tostring(EVENT_HOOKS[name])) + ) + ) + ) + )) +end + +---@param node ASTNode +function TransformerDefs:funcbody(node) + ---@type ASTNodeSource? + local root_source_node = self:_get_root_source(node) + assert(root_source_node ~= nil, "root_source_node ~= nil") + local start_line, start_column = relabel.calcline(root_source_node.source, node.start) + + local name + local parent_node = self:get_parent(node) + if parent_node.type == "functiondef" then + name = emitter:generate(parent_node.name) + if name:sub(1, #SPECIAL_NAME) == SPECIAL_NAME then + return node + elseif EVENT_HOOKS[name] then + self:_generate_check_call(node, name) + end + elseif parent_node.type == "function" then + local parent_expressionlist_node = self:get_parent(parent_node) + if parent_expressionlist_node and parent_expressionlist_node.type == "expressionlist" then + local parent_assign_node = self:get_parent(parent_expressionlist_node) + if parent_assign_node and parent_assign_node.type == "assign" then + local index = Utils.find_key(parent_expressionlist_node, parent_node) + local name_node = parent_assign_node.names[index] + if name_node then + name = emitter:generate(name_node) + if EVENT_HOOKS[name] then + self:_generate_check_call(node, name) + end + end + end + end + end + self._swdbg_index = self._swdbg_index and self._swdbg_index + 1 or 1 + if name == nil then + name = "anonymous:"..self._swdbg_index + end + local source_block_index = 1 + for i, v in ipairs(root_source_node.block.block) do + if v.type == "assign" then + local name_index = v.names[1] + if + name_index.expr and name_index.expr.name == "SS_SW_DBG" and + name_index.index and name_index.index.expr and name_index.index.expr.name == "_info" + then + source_block_index = i + 1 + end + end + end + local local_source_node = self:find_parent_of_type(node, "source") + assert(local_source_node ~= nil, "local_source_node ~= nil") + local local_file_path = local_source_node.file:sub(#self.addon_dir+2) + table.insert(root_source_node.block.block, source_block_index, ASTNodes.assign( + node, nil, + ASTNodes.namelist(node, ASTNodes.index( + node, nil, ASTNodes.name(node, SPECIAL_NAME), + ASTNodes.index( + node, ".", ASTNodes.name(node, "_info"), + ASTNodes.index(node, "[", ASTNodes.numeral(node, tostring(self._swdbg_index))) + ) + )), + ASTNodes.expressionlist(node, ASTNodes.table(node, ASTNodes.fieldlist(node, + ASTNodes.field(node, ASTNodes.string(node, "name", true), ASTNodes.string(node, name, true)), + ASTNodes.field(node, ASTNodes.string(node, "line", true), ASTNodes.numeral(node, tostring(start_line))), + ASTNodes.field(node, ASTNodes.string(node, "column", true), ASTNodes.numeral(node, tostring(start_column))), + ASTNodes.field(node, ASTNodes.string(node, "file", true), ASTNodes.string(node, local_file_path:gsub("\\", "/"), true)) + ))) + )) + + return ASTNodes.funcbody( + node, + ASTNodes.varlist(node, ASTNodes.var_args(node)), + ASTNodes.block(node, + ASTNodes["return"](node, ASTNodes.expressionlist( + node, + ASTNodes.index( + node, nil, ASTNodes.name(node, SPECIAL_NAME), + ASTNodes.index( + node, ".", ASTNodes.name(node, "_trace_func"), + ASTNodes.call( + node, ASTNodes.expressionlist( + node, + ASTNodes.numeral(node, tostring(self._swdbg_index)), + ASTNodes["function"](node, node), + ASTNodes.var_args(node) + ) + ) + ) + ) + )) + ) + ) +end + + +return TransformerDefs