aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLi Jin <dragon-fly@qq.com>2020-03-11 09:12:17 +0800
committerLi Jin <dragon-fly@qq.com>2020-03-11 09:12:17 +0800
commit61896f4e39c8f70a4cf7b804e1887f4c324a5ab7 (patch)
tree3243f68647271af395a869433f61c4f8dc3ba56f
parent9705b3bfd45f536a3efc93a8782b96607547254a (diff)
downloadyuescript-61896f4e39c8f70a4cf7b804e1887f4c324a5ab7.tar.gz
yuescript-61896f4e39c8f70a4cf7b804e1887f4c324a5ab7.tar.bz2
yuescript-61896f4e39c8f70a4cf7b804e1887f4c324a5ab7.zip
fix commit messed with gitignore.
-rw-r--r--.gitignore1
-rw-r--r--src/MoonP/moonplus.cpp129
-rw-r--r--src/MoonP/moonplus.h250
-rw-r--r--src/MoonP/stacktraceplus.h548
4 files changed, 928 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index b81e000..77a4b57 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
1moonp 1moonp
2!src/MoonP
2build 3build
3build.luarocks 4build.luarocks
4spec/outputs 5spec/outputs
diff --git a/src/MoonP/moonplus.cpp b/src/MoonP/moonplus.cpp
new file mode 100644
index 0000000..d6352f5
--- /dev/null
+++ b/src/MoonP/moonplus.cpp
@@ -0,0 +1,129 @@
1/* Copyright (c) 2020 Jin Li, http://www.luvfight.me
2
3Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
5The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
7THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
8#include "MoonP/moon_compiler.h"
9
10extern "C" {
11
12#include "lua.h"
13#include "lauxlib.h"
14
15static const char moonplusCodes[] =
16#include "MoonP/moonplus.h"
17
18static void init_moonplus(lua_State* L) {
19 if (luaL_loadbuffer(L, moonplusCodes, sizeof(moonplusCodes) / sizeof(moonplusCodes[0]) - 1, "=(moonplus)") != 0) {
20 std::string err = std::string("fail to load moonplus module.\n") + lua_tostring(L, -1);
21 luaL_error(L, err.c_str());
22 } else if (lua_pcall(L, 0, 0, 0) != 0) {
23 std::string err = std::string("fail to init moonplus module.\n") + lua_tostring(L, -1);
24 luaL_error(L, err.c_str());
25 }
26}
27
28static const char stpCodes[] =
29#include "MoonP/stacktraceplus.h"
30
31static int init_stacktraceplus(lua_State* L) {
32 if (luaL_loadbuffer(L, stpCodes, sizeof(stpCodes) / sizeof(stpCodes[0]) - 1, "=(stacktraceplus)") != 0) {
33 std::string err = std::string("fail to load stacktraceplus module.\n") + lua_tostring(L, -1);
34 luaL_error(L, err.c_str());
35 } else if (lua_pcall(L, 0, 1, 0) != 0) {
36 std::string err = std::string("fail to init stacktraceplus module.\n") + lua_tostring(L, -1);
37 luaL_error(L, err.c_str());
38 }
39 return 1;
40}
41
42static int moontolua(lua_State* L) {
43 size_t size = 0;
44 const char* input = luaL_checklstring(L, 1, &size);
45 MoonP::MoonConfig config;
46 bool sameModule = false;
47 if (lua_gettop(L) == 2) {
48 luaL_checktype(L, 2, LUA_TTABLE);
49 lua_pushliteral(L, "lint_global");
50 lua_gettable(L, -2);
51 if (lua_isboolean(L, -1) != 0) {
52 config.lintGlobalVariable = lua_toboolean(L, -1) != 0;
53 }
54 lua_pop(L, 1);
55 lua_pushliteral(L, "implicit_return_root");
56 lua_gettable(L, -2);
57 if (lua_isboolean(L, -1) != 0) {
58 config.implicitReturnRoot = lua_toboolean(L, -1) != 0;
59 }
60 lua_pop(L, 1);
61 lua_pushliteral(L, "reserve_line_number");
62 lua_gettable(L, -2);
63 if (lua_isboolean(L, -1) != 0) {
64 config.reserveLineNumber = lua_toboolean(L, -1) != 0;
65 }
66 lua_pop(L, 1);
67 lua_pushliteral(L, "same_module");
68 lua_gettable(L, -2);
69 if (lua_isboolean(L, -1) != 0) {
70 sameModule = lua_toboolean(L, -1) != 0;
71 }
72 lua_pop(L, 1);
73 lua_pushliteral(L, "line_offset");
74 lua_gettable(L, -2);
75 if (lua_isnumber(L, -1) != 0) {
76 config.lineOffset = static_cast<int>(lua_tonumber(L, -1));
77 }
78 lua_pop(L, 1);
79 }
80 std::string s(input, size);
81 std::string codes, err;
82 MoonP::GlobalVars globals;
83 std::tie(codes, err, globals) = MoonP::MoonCompiler(L, nullptr, sameModule).compile(s, config);
84 if (codes.empty() && !err.empty()) {
85 lua_pushnil(L);
86 } else {
87 lua_pushlstring(L, codes.c_str(), codes.size());
88 }
89 if (err.empty()) {
90 lua_pushnil(L);
91 } else {
92 lua_pushlstring(L, err.c_str(), err.size());
93 }
94 if (globals) {
95 lua_createtable(L, static_cast<int>(globals->size()), 0);
96 int i = 1;
97 for (const auto& var : *globals) {
98 lua_createtable(L, 3, 0);
99 lua_pushlstring(L, var.name.c_str(), var.name.size());
100 lua_rawseti(L, -2, 1);
101 lua_pushinteger(L, var.line);
102 lua_rawseti(L, -2, 2);
103 lua_pushinteger(L, var.col);
104 lua_rawseti(L, -2, 3);
105 lua_rawseti(L, -2, i);
106 i++;
107 }
108 } else {
109 lua_pushnil(L);
110 }
111 return 3;
112}
113
114int luaopen_moonp(lua_State* L) {
115 lua_getglobal(L, "package"); // package
116 lua_getfield(L, -1, "loaded"); // package loaded
117 lua_createtable(L, 0, 0); // package loaded moonp
118 lua_pushcfunction(L, moontolua); // package loaded moonp func
119 lua_setfield(L, -2, "to_lua"); // moonp["to_lua"] = func, package loaded tb
120 lua_pushcfunction(L, init_stacktraceplus); // package loaded moonp func1
121 lua_setfield(L, -2, "load_stacktraceplus"); // moonp["load_stacktraceplus"] = func1, package loaded moonp
122 lua_setfield(L, -2, "moonp"); // loaded["moonp"] = moonp, package loaded
123 lua_pop(L, 2); // empty
124 init_moonplus(L);
125 return 0;
126}
127
128} // extern "C"
129
diff --git a/src/MoonP/moonplus.h b/src/MoonP/moonplus.h
new file mode 100644
index 0000000..7059286
--- /dev/null
+++ b/src/MoonP/moonplus.h
@@ -0,0 +1,250 @@
1R"moonscript_codes(
2--[[
3Copyright (C) 2020 by Leaf Corcoran, modified by Li Jin
4
5Permission is hereby granted, free of charge, to any person obtaining a copy
6of this software and associated documentation files (the "Software"), to deal
7in the Software without restriction, including without limitation the rights
8to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9copies of the Software, and to permit persons to whom the Software is
10furnished to do so, subject to the following conditions:
11
12The above copyright notice and this permission notice shall be included in
13all copies or substantial portions of the Software.
14
15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21THE SOFTWARE.]]
22
23local moonp = require("moonp")
24local concat, insert, remove = table.concat, table.insert, table.remove
25local unpack = unpack or table.unpack
26local lua = {
27 loadstring = loadstring,
28 load = load
29}
30local dirsep, split, get_options, create_moonpath, moon_loader, load_text, moon_call, loadstring, loadfile, dofile, insert_loader, remove_loader, moon_require, find_modulepath
31dirsep = "/"
32moonp.moon_compiled = { }
33moonp.file_exist = function(fname)
34 local file = io.open(fname)
35 if file then
36 file:close()
37 return true
38 else
39 return false
40 end
41end
42moonp.read_file = function(fname)
43 local file, err = io.open(fname)
44 if not file then
45 return nil, err
46 end
47 local text = assert(file:read("*a"))
48 file:close()
49 return text
50end
51split = function(str, delim)
52 if str == "" then
53 return { }
54 end
55 str = str .. delim
56 local _accum_0 = { }
57 local _len_0 = 1
58 for m in str:gmatch("(.-)" .. delim) do
59 _accum_0[_len_0] = m
60 _len_0 = _len_0 + 1
61 end
62 return _accum_0
63end
64get_options = function(...)
65 local count = select("#", ...)
66 local opts = select(count, ...)
67 if type(opts) == "table" then
68 return opts, unpack({
69 ...
70 }, nil, count - 1)
71 else
72 return { }, ...
73 end
74end
75create_moonpath = function(package_path)
76 local moonpaths
77 do
78 local _accum_0 = { }
79 local _len_0 = 1
80 local _list_0 = split(package_path, ";")
81 for _index_0 = 1, #_list_0 do
82 local path = _list_0[_index_0]
83 local _continue_0 = false
84 repeat
85 local prefix = path:match("^(.-)%.lua$")
86 if not prefix then
87 _continue_0 = true
88 break
89 end
90 _accum_0[_len_0] = prefix .. ".moon"
91 _len_0 = _len_0 + 1
92 _continue_0 = true
93 until true
94 if not _continue_0 then
95 break
96 end
97 end
98 moonpaths = _accum_0
99 end
100 return concat(moonpaths, ";")
101end
102find_modulepath = function(name)
103 if not package.moonpath then
104 package.moonpath = create_moonpath(package.path)
105 end
106 local name_path = name:gsub("%.", dirsep)
107 local file_exist, file_path
108 for path in package.moonpath:gmatch("[^;]+") do
109 file_path = path:gsub("?", name_path)
110 file_exist = moonp.file_exist(file_path)
111 if file_exist then
112 break
113 end
114 end
115 if file_exist then
116 return file_path
117 else
118 return nil
119 end
120end
121load_text = function(name)
122 local file_path = find_modulepath(name)
123 if file_path then
124 return moonp.read_file(file_path), file_path
125 end
126 return nil, nil
127end
128moon_loader = function(name)
129 local text, file_path = load_text(name)
130 if text then
131 local res, err = loadstring(text, file_path)
132 if not res then
133 error(file_path .. ": " .. err)
134 end
135 return res
136 end
137 return nil, "Could not find moon file"
138end
139moon_call = function(f, ...)
140 local args = {
141 ...
142 }
143 return xpcall((function()
144 return f(unpack(args))
145 end), function(err)
146 return moonp.stp.stacktrace(err, 1)
147 end)
148end
149loadstring = function(...)
150 local options, str, chunk_name, mode, env = get_options(...)
151 chunk_name = chunk_name or "=(moonscript.loadstring)"
152 local code, err = moonp.to_lua(str, options)
153 if not code then
154 return nil, err
155 end
156 if chunk_name then
157 moonp.moon_compiled["@" .. chunk_name] = code
158 end
159 return (lua.loadstring or lua.load)(code, chunk_name, unpack({
160 mode,
161 env
162 }))
163end
164loadfile = function(fname, ...)
165 local text = moonp.read_file(fname)
166 return loadstring(text, tostring(fname), ...)
167end
168dofile = function(...)
169 local f = assert(loadfile(...))
170 return f()
171end
172insert_loader = function(pos)
173 if pos == nil then
174 pos = 2
175 end
176 if not package.moonpath then
177 package.moonpath = create_moonpath(package.path)
178 end
179 local loaders = package.loaders or package.searchers
180 for _index_0 = 1, #loaders do
181 local loader = loaders[_index_0]
182 if loader == moon_loader then
183 return false
184 end
185 end
186 insert(loaders, pos, moon_loader)
187 return true
188end
189remove_loader = function()
190 local loaders = package.loaders or package.searchers
191 for i, loader in ipairs(loaders) do
192 if loader == moon_loader then
193 remove(loaders, i)
194 return true
195 end
196 end
197 return false
198end
199moon_require = function(name)
200 insert_loader()
201 local success, res = xpcall((function()
202 return require(name)
203 end), function(err)
204 local msg = moonp.stp.stacktrace(err, 1)
205 print(msg)
206 return msg
207 end)
208 if success then
209 return res
210 else
211 return nil
212 end
213end
214setmetatable(moonp, {
215 __index = function(self, key)
216 if not (key == "stp") then
217 return nil
218 end
219 local stp = rawget(moonp, "stp")
220 if not stp then
221 do
222 local _with_0 = moonp.load_stacktraceplus()
223 _with_0.dump_locals = false
224 _with_0.simplified = true
225 stp = _with_0
226 end
227 rawset(moonp, "stp", stp)
228 rawset(moonp, "load_stacktraceplus", nil)
229 end
230 return stp
231 end,
232 __call = function(self, name)
233 return self.require(name)
234 end
235})
236for k, v in pairs({
237 insert_loader = insert_loader,
238 remove_loader = remove_loader,
239 loader = moon_loader,
240 dofile = dofile,
241 loadfile = loadfile,
242 loadstring = loadstring,
243 create_moonpath = create_moonpath,
244 find_modulepath = find_modulepath,
245 pcall = moon_call,
246 require = moon_require
247}) do
248 moonp[k] = v
249end
250)moonscript_codes";
diff --git a/src/MoonP/stacktraceplus.h b/src/MoonP/stacktraceplus.h
new file mode 100644
index 0000000..a309578
--- /dev/null
+++ b/src/MoonP/stacktraceplus.h
@@ -0,0 +1,548 @@
1R"lua_codes(
2--[[
3Copyright (c) 2010 Ignacio Burgueño, modified by Li Jin
4
5Permission is hereby granted, free of charge, to any person obtaining a copy
6of this software and associated documentation files (the "Software"), to deal
7in the Software without restriction, including without limitation the rights
8to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9copies of the Software, and to permit persons to whom the Software is
10furnished to do so, subject to the following conditions:
11
12The above copyright notice and this permission notice shall be included in
13all copies or substantial portions of the Software.
14
15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21THE SOFTWARE.]]
22
23-- tables
24local _G = _G
25local string, io, debug, coroutine = string, io, debug, coroutine
26
27-- functions
28local tostring, require = tostring, require
29local next, assert = next, assert
30local pcall, type, pairs, ipairs = pcall, type, pairs, ipairs
31local error = error
32
33assert(debug, "debug table must be available at this point")
34
35local string_gmatch = string.gmatch
36local string_sub = string.sub
37local table_concat = table.concat
38
39local moonp = require("moonp")
40
41local _M = {
42 max_tb_output_len = 70, -- controls the maximum length of the 'stringified' table before cutting with ' (more...)'
43 dump_locals = true,
44 simplified = false
45}
46
47-- this tables should be weak so the elements in them won't become uncollectable
48local m_known_tables = { [_G] = "_G (global table)" }
49local function add_known_module(name, desc)
50 local ok, mod = pcall(require, name)
51 if ok then
52 m_known_tables[mod] = desc
53 end
54end
55
56add_known_module("string", "string module")
57add_known_module("io", "io module")
58add_known_module("os", "os module")
59add_known_module("table", "table module")
60add_known_module("math", "math module")
61add_known_module("package", "package module")
62add_known_module("debug", "debug module")
63add_known_module("coroutine", "coroutine module")
64
65-- lua5.2
66add_known_module("bit32", "bit32 module")
67-- luajit
68add_known_module("bit", "bit module")
69add_known_module("jit", "jit module")
70-- lua5.3
71if _VERSION >= "Lua 5.3" then
72 add_known_module("utf8", "utf8 module")
73end
74
75
76local m_user_known_tables = {}
77
78local m_known_functions = {}
79for _, name in ipairs{
80 -- Lua 5.2, 5.1
81 "assert",
82 "collectgarbage",
83 "dofile",
84 "error",
85 "getmetatable",
86 "ipairs",
87 "load",
88 "loadfile",
89 "next",
90 "pairs",
91 "pcall",
92 "print",
93 "rawequal",
94 "rawget",
95 "rawlen",
96 "rawset",
97 "require",
98 "select",
99 "setmetatable",
100 "tonumber",
101 "tostring",
102 "type",
103 "xpcall",
104
105 -- Lua 5.1
106 "gcinfo",
107 "getfenv",
108 "loadstring",
109 "module",
110 "newproxy",
111 "setfenv",
112 "unpack",
113 -- TODO: add table.* etc functions
114} do
115 if _G[name] then
116 m_known_functions[_G[name]] = name
117 end
118end
119
120local m_user_known_functions = {}
121
122local function safe_tostring (value)
123 local ok, err = pcall(tostring, value)
124 if ok then return err else return ("<failed to get printable value>: '%s'"):format(err) end
125end
126
127-- Private:
128-- Parses a line, looking for possible function definitions (in a very naive way)
129-- Returns '(anonymous)' if no function name was found in the line
130local function ParseLine(line)
131 assert(type(line) == "string")
132 local match = line:match("^%s*function%s+(%w+)")
133 if match then
134 --print("+++++++++++++function", match)
135 return match
136 end
137 match = line:match("^%s*local%s+function%s+(%w+)")
138 if match then
139 --print("++++++++++++local", match)
140 return match
141 end
142 match = line:match("^%s*local%s+(%w+)%s+=%s+function")
143 if match then
144 --print("++++++++++++local func", match)
145 return match
146 end
147 match = line:match("%s*function%s*%(") -- this is an anonymous function
148 if match then
149 --print("+++++++++++++function2", match)
150 return "(anonymous)"
151 end
152 return "(anonymous)"
153end
154
155-- Private:
156-- Tries to guess a function's name when the debug info structure does not have it.
157-- It parses either the file or the string where the function is defined.
158-- Returns '?' if the line where the function is defined is not found
159local function GuessFunctionName(info)
160 -- print("guessing function name")
161 if type(info.source) == "string" and info.source:sub(1,1) == "@" then
162 local fname = info.source:sub(2)
163 local text
164 if moonp.file_exist(fname) then
165 text = moonp.read_file(fname)
166 end
167 if not text then
168 -- print("file not found: "..tostring(err)) -- whoops!
169 return "?"
170 end
171 local line
172 local count = 0
173 for lineText in (text.."\n"):gmatch("(.-)\n") do
174 line = lineText
175 count = count + 1
176 if count == info.linedefined then
177 break
178 end
179 end
180 if not line then
181 --print("line not found") -- whoops!
182 return "?"
183 end
184 return ParseLine(line)
185 else
186 local line
187 local lineNumber = 0
188 for l in string_gmatch(info.source, "([^\n]+)\n-") do
189 lineNumber = lineNumber + 1
190 if lineNumber == info.linedefined then
191 line = l
192 break
193 end
194 end
195 if not line then
196 -- print("line not found") -- whoops!
197 return "?"
198 end
199 return ParseLine(line)
200 end
201end
202
203---
204-- Dumper instances are used to analyze stacks and collect its information.
205--
206local Dumper = {}
207
208Dumper.new = function(thread)
209 local t = { lines = {} }
210 for k,v in pairs(Dumper) do t[k] = v end
211
212 t.dumping_same_thread = (thread == coroutine.running())
213
214 -- if a thread was supplied, bind it to debug.info and debug.get
215 -- we also need to skip this additional level we are introducing in the callstack (only if we are running
216 -- in the same thread we're inspecting)
217 if type(thread) == "thread" then
218 t.getinfo = function(level, what)
219 if t.dumping_same_thread and type(level) == "number" then
220 level = level + 1
221 end
222 return debug.getinfo(thread, level, what)
223 end
224 t.getlocal = function(level, loc)
225 if t.dumping_same_thread then
226 level = level + 1
227 end
228 return debug.getlocal(thread, level, loc)
229 end
230 else
231 t.getinfo = debug.getinfo
232 t.getlocal = debug.getlocal
233 end
234
235 return t
236end
237
238-- helpers for collecting strings to be used when assembling the final trace
239function Dumper:add (text)
240 self.lines[#self.lines + 1] = text
241end
242function Dumper:add_f (fmt, ...)
243 self:add(fmt:format(...))
244end
245function Dumper:concat_lines ()
246 return table_concat(self.lines)
247end
248
249---
250-- Private:
251-- Iterates over the local variables of a given function.
252--
253-- @param level The stack level where the function is.
254--
255function Dumper:DumpLocals (level)
256 if not _M.dump_locals then return end
257
258 local prefix = "\t "
259 local i = 1
260
261 if self.dumping_same_thread then
262 level = level + 1
263 end
264
265 local name, value = self.getlocal(level, i)
266 if not name then
267 return
268 end
269 self:add("\tLocal variables:\r\n")
270 while name do
271 if type(value) == "number" then
272 self:add_f("%s%s = number: %g\r\n", prefix, name, value)
273 elseif type(value) == "boolean" then
274 self:add_f("%s%s = boolean: %s\r\n", prefix, name, tostring(value))
275 elseif type(value) == "string" then
276 self:add_f("%s%s = string: %q\r\n", prefix, name, value)
277 elseif type(value) == "userdata" then
278 self:add_f("%s%s = %s\r\n", prefix, name, safe_tostring(value))
279 elseif type(value) == "nil" then
280 self:add_f("%s%s = nil\r\n", prefix, name)
281 elseif type(value) == "table" then
282 if m_known_tables[value] then
283 self:add_f("%s%s = %s\r\n", prefix, name, m_known_tables[value])
284 elseif m_user_known_tables[value] then
285 self:add_f("%s%s = %s\r\n", prefix, name, m_user_known_tables[value])
286 else
287 local txt = "{"
288 for k,v in pairs(value) do
289 txt = txt..safe_tostring(k)..":"..safe_tostring(v)
290 if #txt > _M.max_tb_output_len then
291 txt = txt.." (more...)"
292 break
293 end
294 if next(value, k) then txt = txt..", " end
295 end
296 self:add_f("%s%s = %s %s\r\n", prefix, name, safe_tostring(value), txt.."}")
297 end
298 elseif type(value) == "function" then
299 local info = self.getinfo(value, "nS")
300 local fun_name = info.name or m_known_functions[value] or m_user_known_functions[value]
301 if info.what == "C" then
302 self:add_f("%s%s = C %s\r\n", prefix, name, (fun_name and ("function: " .. fun_name) or tostring(value)))
303 else
304 local source = info.short_src
305 if source:sub(2,7) == "string" then
306 source = source:sub(9)
307 end
308 --for k,v in pairs(info) do print(k,v) end
309 fun_name = fun_name or GuessFunctionName(info)
310 self:add_f("%s%s = Lua function '%s' (defined at line %d of chunk %s)\r\n", prefix, name, fun_name, info.linedefined, source)
311 end
312 elseif type(value) == "thread" then
313 self:add_f("%sthread %q = %s\r\n", prefix, name, tostring(value))
314 end
315 i = i + 1
316 name, value = self.getlocal(level, i)
317 end
318end
319
320local function getMoonLineNumber(fname, line)
321 local moonCompiled = require("moonp").moon_compiled
322 local source = moonCompiled["@"..fname]
323 if not source then
324 source = moonCompiled["@="..fname]
325 end
326 if not source then
327 if moonp.file_exist(fname) then
328 local codes = moonp.read_file(fname)
329 local moonFile = codes:match("^%s*--%s*%[moon%]:%s*([^\n]*)")
330 if moonFile then
331 fname = moonFile:gsub("^%s*(.-)%s*$", "%1")
332 source = codes
333 end
334 end
335 end
336 if source then
337 local i, target = 1, tonumber(line)
338 for lineCode in source:gmatch("([^\n]*)\n") do
339 if i == target then
340 local num = lineCode:match("--%s*(%d*)%s*$")
341 if num then
342 return fname, num
343 end
344 break
345 end
346 i = i + 1
347 end
348 end
349 return fname, line
350end
351
352---
353-- Public:
354-- Collects a detailed stack trace, dumping locals, resolving function names when they're not available, etc.
355-- This function is suitable to be used as an error handler with pcall or xpcall
356--
357-- @param thread An optional thread whose stack is to be inspected (defaul is the current thread)
358-- @param message An optional error string or object.
359-- @param level An optional number telling at which level to start the traceback (default is 1)
360--
361-- Returns a string with the stack trace and a string with the original error.
362--
363function _M.stacktrace(thread, message, level)
364 if type(thread) ~= "thread" then
365 -- shift parameters left
366 thread, message, level = nil, thread, message
367 end
368
369 thread = thread or coroutine.running()
370
371 level = level or 1
372
373 local dumper = Dumper.new(thread)
374
375 if type(message) == "table" then
376 dumper:add("an error object {\r\n")
377 local first = true
378 for k,v in pairs(message) do
379 if first then
380 dumper:add(" ")
381 first = false
382 else
383 dumper:add(",\r\n ")
384 end
385 dumper:add(safe_tostring(k))
386 dumper:add(": ")
387 dumper:add(safe_tostring(v))
388 end
389 dumper:add("\r\n}")
390 elseif type(message) == "string" then
391 local fname, line, msg = message:match('(.+):(%d+): (.*)$')
392 local nfname, nline, nmsg = fname:match('(.+):(%d+): (.*)$')
393 if nfname then
394 fname = nmsg
395 end
396 if fname then
397 fname = fname:gsub("%[string \"", "")
398 fname = fname:gsub("\"%]", "")
399 fname = fname:gsub("^%s*(.-)%s*$", "%1")
400 local extension = fname:match("%.([^%.\\/]*)$")
401 if not extension then
402 local fext = fname .. ".lua"
403 if moonp.file_exist(fext) then
404 fname = fext
405 else
406 fext = fname .. ".moon"
407 if moonp.file_exist(fext) then
408 fname = fext
409 end
410 end
411 end
412 fname, line = getMoonLineNumber(fname, line)
413 if _M.simplified then
414 message = table.concat({
415 "", fname, ":",
416 line, ": ", msg})
417 message = message:gsub("^%(moonplus%):%s*%d+:%s*", "")
418 message = message:gsub("%s(%d+):", "%1:")
419 else
420 message = table.concat({
421 "[string \"", fname, "\"]:",
422 line, ": ", msg})
423 end
424 end
425 dumper:add(message)
426 end
427
428 local moonp = require("moonp")
429 if moonp._hide_stacktrace_ then
430 local msg = dumper:concat_lines()
431 moonp._hide_stacktrace_ = nil
432 return message
433 end
434
435 dumper:add("\r\n")
436 dumper:add[[
437Stack Traceback
438===============
439]]
440
441 local level_to_show = 1
442 if dumper.dumping_same_thread then level = level + 1 end
443
444 local info = dumper.getinfo(level, "nSlf")
445 while info do
446 if info.source and info.source:sub(1,1) == "@" then
447 info.source = info.source:sub(2)
448 elseif info.what == "main" or info.what == "Lua" then
449 info.source = info.source
450 end
451 local fname = info.source
452 local extension = fname:match("%.([^%.\\/]*)$")
453 if not extension then
454 local fext = fname .. ".lua"
455 if moonp.file_exist(fext) then
456 fname = fext
457 else
458 fext = fname .. ".moon"
459 if moonp.file_exist(fext) then
460 fname = fext
461 end
462 end
463 end
464 info.source, info.currentline = getMoonLineNumber(fname, info.currentline)
465 if info.what == "main" then
466 if _M.simplified then
467 dumper:add_f("(%d) '%s':%d\r\n", level_to_show, info.source, info.currentline)
468 else
469 dumper:add_f("(%d) main chunk of file '%s' at line %d\r\n", level_to_show, info.source, info.currentline)
470 end
471 elseif info.what == "C" then
472 --print(info.namewhat, info.name)
473 --for k,v in pairs(info) do print(k,v, type(v)) end
474 local function_name = m_user_known_functions[info.func] or m_known_functions[info.func] or info.name or tostring(info.func)
475 dumper:add_f("(%d) %s C function '%s'\r\n", level_to_show, info.namewhat, function_name)
476 --dumper:add_f("%s%s = C %s\r\n", prefix, name, (m_known_functions[value] and ("function: " .. m_known_functions[value]) or tostring(value)))
477 elseif info.what == "tail" then
478 --print("tail")
479 --for k,v in pairs(info) do print(k,v, type(v)) end--print(info.namewhat, info.name)
480 dumper:add_f("(%d) tail call\r\n", level_to_show)
481 dumper:DumpLocals(level)
482 elseif info.what == "Lua" then
483 local source = info.source
484 local function_name = m_user_known_functions[info.func] or m_known_functions[info.func] or info.name
485 if source:sub(2, 7) == "string" then
486 source = source:sub(10,-3)
487 end
488 local was_guessed = false
489 if not function_name or function_name == "?" then
490 --for k,v in pairs(info) do print(k,v, type(v)) end
491 function_name = GuessFunctionName(info)
492 was_guessed = true
493 end
494 -- test if we have a file name
495 local function_type = (info.namewhat == "") and "function" or info.namewhat
496 if info.source and info.source:sub(1, 1) == "@" then
497 if _M.simplified then
498 dumper:add_f("(%d) '%s':%d%s\r\n", level_to_show, info.source:sub(2), info.currentline, was_guessed and " (guess)" or "")
499 else
500 dumper:add_f("(%d) Lua %s '%s' at file '%s':%d%s\r\n", level_to_show, function_type, function_name, info.source:sub(2), info.currentline, was_guessed and " (best guess)" or "")
501 end
502 elseif info.source and info.source:sub(1,1) == '#' then
503 if _M.simplified then
504 dumper:add_f("(%d) '%s':%d%s\r\n", level_to_show, info.source:sub(2), info.currentline, was_guessed and " (guess)" or "")
505 else
506 dumper:add_f("(%d) Lua %s '%s' at template '%s':%d%s\r\n", level_to_show, function_type, function_name, info.source:sub(2), info.currentline, was_guessed and " (best guess)" or "")
507 end
508 else
509 if _M.simplified then
510 dumper:add_f("(%d) '%s':%d\r\n", level_to_show, source, info.currentline)
511 else
512 dumper:add_f("(%d) Lua %s '%s' at chunk '%s':%d\r\n", level_to_show, function_type, function_name, source, info.currentline)
513 end
514 end
515 dumper:DumpLocals(level)
516 else
517 dumper:add_f("(%d) unknown frame %s\r\n", level_to_show, info.what)
518 end
519
520 level = level + 1
521 level_to_show = level_to_show + 1
522 info = dumper.getinfo(level, "nSlf")
523 end
524
525 return dumper:concat_lines()
526end
527
528--
529-- Adds a table to the list of known tables
530function _M.add_known_table(tab, description)
531 if m_known_tables[tab] then
532 error("Cannot override an already known table")
533 end
534 m_user_known_tables[tab] = description
535end
536
537--
538-- Adds a function to the list of known functions
539function _M.add_known_function(fun, description)
540 if m_known_functions[fun] then
541 error("Cannot override an already known function")
542 end
543 m_user_known_functions[fun] = description
544end
545
546return _M
547
548)lua_codes";