1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
-- This file contains the logic for making nicer error messages
debug_getinfo = debug.getinfo
export SOURCE_MAP
ok, to_lua = pcall -> require('moonscript.base').to_lua
if not ok then to_lua = nil
moonscript_line_tables = setmetatable {}, {
__index: (filename)=>
return nil unless to_lua
_, line_table = to_lua(FILE_CACHE[filename])
self[filename] = line_table
return line_table
}
-- Make a better version of debug.getinfo that provides info about the original source
-- where the error came from, even if that's in another language.
debug.getinfo = (thread,f,what)->
if what == nil
f,what,thread = thread,f,nil
if type(f) == 'number' then f += 1 -- Account for this wrapper function
info = if thread == nil
debug_getinfo(f,what)
else debug_getinfo(thread,f,what)
if not info or not info.func then return info
if info.short_src or info.source or info.linedefine or info.currentline
-- TODO: get name properly
if map = SOURCE_MAP[info.source]
if info.currentline
info.currentline = assert(map[info.currentline])
if info.linedefined
info.linedefined = assert(map[info.linedefined])
if info.lastlinedefined
info.lastlinedefined = assert(map[info.lastlinedefined])
info.short_src = info.source\match('@([^[]*)') or info.short_src
return info
print_err_msg = (error_message, stack_offset=3)->
io.stderr\write("#{colored.red "ERROR:"} #{colored.bright colored.red (error_message or "")}\n")
io.stderr\write("stack traceback:\n")
-- TODO: properly print out the calling site of nomsu code, not just the *called* code
ok, to_lua = pcall -> require('moonscript.base').to_lua
if not ok then to_lua = -> nil
LINE_TABLES = setmetatable {},
__index: (file)=>
_, line_table = to_lua(file)
self[file] = line_table or false
return line_table or false
get_line = (file, line_no)->
start = LINE_STARTS[file][line_no] or 1
stop = (LINE_STARTS[file][line_no+1] or 0) - 1
return file\sub(start, stop)
level = stack_offset
while true
-- TODO: reduce duplicate code
calling_fn = debug_getinfo(level)
if not calling_fn then break
if calling_fn.func == run then break
level += 1
name = calling_fn.name and "function '#{calling_fn.name}'" or nil
if calling_fn.linedefined == 0 then name = "main chunk"
if name == "run_lua_fn" then continue
line = nil
if map = SOURCE_MAP[calling_fn.source]
if calling_fn.currentline
calling_fn.currentline = assert(map[calling_fn.currentline])
if calling_fn.linedefined
calling_fn.linedefined = assert(map[calling_fn.linedefined])
if calling_fn.lastlinedefined
calling_fn.lastlinedefined = assert(map[calling_fn.lastlinedefined])
--calling_fn.short_src = calling_fn.source\match('"([^[]*)')
filename,start,stop = calling_fn.source\match('@([^[]*)%[([0-9]+):([0-9]+)]')
assert(filename)
file = FILE_CACHE[filename]\sub(tonumber(start),tonumber(stop))
err_line = get_line(file, calling_fn.currentline)\sub(1,-2)
offending_statement = colored.bright(colored.red(err_line\match("^[ ]*(.*)")))
-- TODO: get name properly
name = "action '#{calling_fn.name}'"
line = colored.yellow("#{filename}:#{calling_fn.currentline} in #{name}\n #{offending_statement}")
else
ok, file = pcall ->FILE_CACHE[calling_fn.short_src]
if not ok then file = nil
local line_num
if name == nil
search_level = level
_info = debug.getinfo(search_level)
while _info and (_info.func == pcall or _info.func == xpcall)
search_level += 1
_info = debug.getinfo(search_level)
if _info
for i=1,999
varname, val = debug.getlocal(search_level, i)
if not varname then break
if val == calling_fn.func
name = "local '#{varname}'"
if not varname\match("%(")
break
unless name
for i=1,_info.nups
varname, val = debug.getupvalue(_info.func, i)
if not varname then break
if val == calling_fn.func
name = "upvalue '#{varname}'"
if not varname\match("%(")
break
if file and calling_fn.short_src\match(".moon$") and LINE_TABLES[file]
char = LINE_TABLES[file][calling_fn.currentline]
line_num = 1
for _ in file\sub(1,char)\gmatch("\n") do line_num += 1
line = colored.cyan("#{calling_fn.short_src}:#{line_num} in #{name or '?'}")
else
line_num = calling_fn.currentline
if calling_fn.short_src == '[C]'
line = colored.green("#{calling_fn.short_src} in #{name or '?'}")
else
line = colored.blue("#{calling_fn.short_src}:#{calling_fn.currentline} in #{name or '?'}")
if file
err_line = get_line(file, line_num)\sub(1,-2)
offending_statement = colored.bright(colored.red(err_line\match("^[ ]*(.*)$")))
line ..= "\n "..offending_statement
io.stderr\write(" #{line}\n")
if calling_fn.istailcall
io.stderr\write(" #{colored.dim colored.white " (...tail calls...)"}\n")
io.stderr\flush!
err_hand = (error_message)->
print_err_msg error_message
os.exit(false, true)
-- Note: xpcall has a slightly different API in Lua <=5.1 vs. >=5.2, but this works
-- for both APIs
has_ldt, ldt = pcall(require,'ldt')
safe_run = if has_ldt
ldt.guard
else
(fn)->
xpcall(fn, err_hand)
return safe_run
|