aboutsummaryrefslogtreecommitdiff
path: root/error_handling.lua
blob: f5bd10b0e1c9a76697aeafc900464ac41c9c2047 (plain)
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
local debug_getinfo = debug.getinfo
local ok, to_lua = pcall(function()
  return require('moonscript.base').to_lua
end)
if not ok then
  to_lua = nil
end
local moonscript_line_tables = setmetatable({ }, {
  __index = function(self, filename)
    if not (to_lua) then
      return nil
    end
    local _, line_table = to_lua(FILE_CACHE[filename])
    self[filename] = line_table
    return line_table
  end
})
debug.getinfo = function(thread, f, what)
  if what == nil then
    f, what, thread = thread, f, nil
  end
  if type(f) == 'number' then
    f = f + 1
  end
  local info
  if thread == nil then
    info = debug_getinfo(f, what)
  else
    info = debug_getinfo(thread, f, what)
  end
  if not info or not info.func then
    return info
  end
  if info.short_src or info.source or info.linedefine or info.currentline then
    do
      local map = SOURCE_MAP[info.source]
      if map then
        if info.currentline then
          info.currentline = assert(map[info.currentline])
        end
        if info.linedefined then
          info.linedefined = assert(map[info.linedefined])
        end
        if info.lastlinedefined then
          info.lastlinedefined = assert(map[info.lastlinedefined])
        end
        info.short_src = info.source:match('@([^[]*)') or info.short_src
      end
    end
  end
  return info
end
local print_err_msg
print_err_msg = function(error_message, stack_offset)
  if stack_offset == nil then
    stack_offset = 3
  end
  io.stderr:write(tostring(colored.red("ERROR:")) .. " " .. tostring(colored.bright(colored.red((error_message or "")))) .. "\n")
  io.stderr:write("stack traceback:\n")
  ok, to_lua = pcall(function()
    return require('moonscript.base').to_lua
  end)
  if not ok then
    to_lua = function()
      return nil
    end
  end
  local LINE_TABLES = setmetatable({ }, {
    __index = function(self, file)
      local _, line_table = to_lua(file)
      self[file] = line_table or false
      return line_table or false
    end
  })
  local get_line
  get_line = function(file, line_no)
    local start = LINE_STARTS[file][line_no] or 1
    local stop = (LINE_STARTS[file][line_no + 1] or 0) - 1
    return file:sub(start, stop)
  end
  local level = stack_offset
  while true do
    local _continue_0 = false
    repeat
      local calling_fn = debug_getinfo(level)
      if not calling_fn then
        break
      end
      if calling_fn.func == run then
        break
      end
      level = level + 1
      local name = calling_fn.name and "function '" .. tostring(calling_fn.name) .. "'" or nil
      if calling_fn.linedefined == 0 then
        name = "main chunk"
      end
      if name == "run_lua_fn" then
        _continue_0 = true
        break
      end
      local line = nil
      do
        local map = SOURCE_MAP[calling_fn.source]
        if map then
          if calling_fn.currentline then
            calling_fn.currentline = assert(map[calling_fn.currentline])
          end
          if calling_fn.linedefined then
            calling_fn.linedefined = assert(map[calling_fn.linedefined])
          end
          if calling_fn.lastlinedefined then
            calling_fn.lastlinedefined = assert(map[calling_fn.lastlinedefined])
          end
          local filename, start, stop = calling_fn.source:match('@([^[]*)%[([0-9]+):([0-9]+)]')
          assert(filename)
          local file = FILE_CACHE[filename]:sub(tonumber(start), tonumber(stop))
          local err_line = get_line(file, calling_fn.currentline):sub(1, -2)
          local offending_statement = colored.bright(colored.red(err_line:match("^[ ]*(.*)")))
          name = "action '" .. tostring(calling_fn.name) .. "'"
          line = colored.yellow(tostring(filename) .. ":" .. tostring(calling_fn.currentline) .. " in " .. tostring(name) .. "\n        " .. tostring(offending_statement))
        else
          local file
          ok, file = pcall(function()
            return FILE_CACHE[calling_fn.short_src]
          end)
          if not ok then
            file = nil
          end
          local line_num
          if name == nil then
            local search_level = level
            local _info = debug.getinfo(search_level)
            while _info and (_info.func == pcall or _info.func == xpcall) do
              search_level = search_level + 1
              _info = debug.getinfo(search_level)
            end
            if _info then
              for i = 1, 999 do
                local varname, val = debug.getlocal(search_level, i)
                if not varname then
                  break
                end
                if val == calling_fn.func then
                  name = "local '" .. tostring(varname) .. "'"
                  if not varname:match("%(") then
                    break
                  end
                end
              end
              if not (name) then
                for i = 1, _info.nups do
                  local varname, val = debug.getupvalue(_info.func, i)
                  if not varname then
                    break
                  end
                  if val == calling_fn.func then
                    name = "upvalue '" .. tostring(varname) .. "'"
                    if not varname:match("%(") then
                      break
                    end
                  end
                end
              end
            end
          end
          if file and calling_fn.short_src:match(".moon$") and LINE_TABLES[file] then
            local char = LINE_TABLES[file][calling_fn.currentline]
            line_num = 1
            for _ in file:sub(1, char):gmatch("\n") do
              line_num = line_num + 1
            end
            line = colored.cyan(tostring(calling_fn.short_src) .. ":" .. tostring(line_num) .. " in " .. tostring(name or '?'))
          else
            line_num = calling_fn.currentline
            if calling_fn.short_src == '[C]' then
              line = colored.green(tostring(calling_fn.short_src) .. " in " .. tostring(name or '?'))
            else
              line = colored.blue(tostring(calling_fn.short_src) .. ":" .. tostring(calling_fn.currentline) .. " in " .. tostring(name or '?'))
            end
          end
          if file then
            local err_line = get_line(file, line_num):sub(1, -2)
            local offending_statement = colored.bright(colored.red(err_line:match("^[ ]*(.*)$")))
            line = line .. ("\n        " .. offending_statement)
          end
        end
      end
      io.stderr:write("    " .. tostring(line) .. "\n")
      if calling_fn.istailcall then
        io.stderr:write("    " .. tostring(colored.dim(colored.white("  (...tail calls...)"))) .. "\n")
      end
      _continue_0 = true
    until true
    if not _continue_0 then
      break
    end
  end
  return io.stderr:flush()
end
local err_hand
err_hand = function(error_message)
  print_err_msg(error_message)
  return os.exit(false, true)
end
local has_ldt, ldt = pcall(require, 'ldt')
local safe_run
if has_ldt then
  safe_run = ldt.guard
else
  safe_run = function(fn)
    return xpcall(fn, err_hand)
  end
end
return safe_run