trac-deb.lua /size: 13 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['trac-deb'] = {
2    version   = 1.001,
3    comment   = "companion to trac-deb.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- This is an old mechanism, a result of some experiments in the early days of
10-- luatex and mkiv, but still nice anyway.
11
12local status = status
13
14local tonumber, tostring, type = tonumber, tostring, type
15local format, concat, match, find, gsub = string.format, table.concat, string.match, string.find, string.gsub
16
17local report_nl     = logs.newline
18local report_str    = logs.writer
19
20tracers             = tracers or { }
21local tracers       = tracers
22local implement     = interfaces.implement
23
24local savedluaerror = nil
25local usescitelexer = nil
26local quitonerror   = true
27
28local function errorreporter(luaerror)
29    local category = luaerror and "lua error" or "tex error"
30    local report = logs.reporter(category)
31    logs.enable(category)
32    return report
33end
34
35function tracers.showlines(filename,linenumber,offset,luaerrorline)
36    local data = io.loaddata(filename)
37    if not data or data == "" then
38        local hash = url.hashed(filename)
39        if not hash.noscheme then
40            local ok, d, n = resolvers.loaders.byscheme(hash.scheme,filename)
41            if ok and n > 0 then
42                data = d
43            end
44        end
45    end
46    local scite = usescitelexer and require("util-sci")
47    if scite then
48        return utilities.scite.tohtml(data,"tex",linenumber or true,false)
49    else
50        local lines = data and string.splitlines(data)
51        if lines and #lines > 0 then
52            if luaerrorline and luaerrorline > 0 then
53                -- lua error: linenumber points to last line
54                local start = "\\startluacode"
55                local stop  = "\\stopluacode"
56                local n = linenumber
57                for i=n,1,-1 do
58                    local line = lines[i]
59                    if not line then
60                        break
61                    elseif find(line,start) then
62                        n = i + luaerrorline - 1
63                        if n <= linenumber then
64                            linenumber = n
65                        end
66                        break
67                    end
68                end
69            end
70            offset = tonumber(offset) or 10
71            linenumber = tonumber(linenumber) or 10
72            local start = math.max(linenumber - offset,1)
73            local stop = math.min(linenumber + offset,#lines)
74            if stop > #lines then
75                return "<linenumber past end of file>"
76            else
77                local result, fmt = { }, "%" .. #tostring(stop) .. "d %s  %s"
78                for n=start,stop do
79                    result[#result+1] = format(fmt,n,n == linenumber and ">>" or "  ",lines[n])
80                end
81                return concat(result,"\n")
82            end
83        else
84            return "<empty file>"
85        end
86    end
87end
88
89-- this will work ok in >=0.79
90
91-- todo: last tex error has ! prepended
92-- todo: some nested errors have two line numbers
93-- todo: collect errorcontext in string (after code cleanup)
94-- todo: have a separate status.lualinenumber
95-- todo: \starttext bla \blank[foo] bla \stoptext
96
97local nop = function() end
98local resetmessages = status.resetmessages or nop
99
100local function processerror(offset,eof)
101    local filename     = status.filename
102    local linenumber   = tonumber(status.linenumber) or 0
103    local lastcontext  = status.lasterrorcontext
104    local lasttexerror = status.lasterrorstring or "?"
105    local lastluaerror = status.lastluaerrorstring or "?" -- lasttexerror
106    local luaerrorline = match(lastluaerror,[[lua%]?:.-(%d+)]]) or (lastluaerror and find(lastluaerror,"?:0:",1,true) and 0)
107    local lastmpserror = match(lasttexerror,[[^.-mp%serror:%s*(.*)$]])
108    resetmessages()
109    lastluaerror = gsub(lastluaerror,"%[\\directlua%]","[ctxlua]")
110    tracers.printerror {
111        filename     = filename,
112        linenumber   = linenumber,
113        offset       = tonumber(offset) or 10,
114        lasttexerror = lasttexerror,
115        lastmpserror = lastmpserror,
116        lastluaerror = lastluaerror, -- can be the same as lasttexerror
117        luaerrorline = luaerrorline,
118        lastcontext  = lastcontext,
119        lasttexhelp  = tex.gethelptext and tex.gethelptext() or nil,
120        endoffile    = eof,
121    }
122    if job and type(job.disablesave) == "function" then
123        job.disablesave()
124    end
125end
126
127directives.register("system.quitonerror",function(v)
128    quitonerror = toboolean(v)
129end)
130
131directives.register("system.usescitelexer",function(v)
132    usescitelexer = toboolean(v)
133end)
134
135local busy = false
136
137function tracers.printerror(specification)
138    if not busy then
139        busy = true
140        local filename     = specification.filename
141        local linenumber   = specification.linenumber
142        local lasttexerror = specification.lasttexerror
143        local lastmpserror = specification.lastmpserror
144        local lastluaerror = specification.lastluaerror
145        local lastcontext  = specification.lasterrorcontext
146        local luaerrorline = specification.luaerrorline
147        local errortype    = specification.errortype
148        local offset       = specification.offset
149        local endoffile    = specification.endoffile
150        local report       = errorreporter(luaerrorline)
151        if endoffile then
152            report("runaway error: %s",lasttexerror or "-")
153            if not quitonerror and texio.terminal then
154                texio.terminal() -- not well tested
155            end
156        elseif not filename then
157            report("fuzzy error:")
158            report("  tex: %s",lasttexerror or "-")
159            report("  lua: %s",lastluaerror or "-")
160            report("  mps: %s",lastmpserror or "-")
161        elseif type(filename) == "number" then
162            report("error on line %s of filehandle %s: %s ...",linenumber,lasttexerror)
163        else
164            report_nl()
165            if luaerrorline then
166                if linenumber == 0 or not filename or filename == "" then
167                    print("\nfatal lua error:\n\n",lastluaerror,"\n")
168                    luatex.abort()
169                    return
170                else
171                    report("lua error on line %s in file %s:\n\n%s",linenumber,filename,lastluaerror)
172                end
173            elseif lastmpserror then
174                report("mp error on line %s in file %s:\n\n%s",linenumber,filename,lastmpserror)
175            else
176                report("tex error on line %s in file %s: %s",linenumber,filename,lasttexerror)
177                if lastcontext then
178                    report_nl()
179                    report_str(lastcontext)
180                    report_nl()
181                elseif tex.show_context then
182                    report_nl()
183                    tex.show_context()
184                end
185                if lastluaerror and not match(lastluaerror,"^%s*[%?]*%s*$") then
186                    print("\nlua error:\n\n",lastluaerror,"\n")
187                    quitonerror = true
188                end
189            end
190            report_nl()
191            report_str(tracers.showlines(filename,linenumber,offset,tonumber(luaerrorline)))
192            report_nl()
193        end
194        if quitonerror then
195            local name = tex.jobname or ""
196            if name ~= "" then
197                table.save(name .. "-error.log",specification)
198            end
199            local help = specification.lasttexhelp
200            if help and #help > 0 then
201                report_nl()
202                report_str(help)
203                report_nl()
204                report_nl()
205            end
206            luatex.abort()
207        end
208        busy = false
209    end
210end
211
212luatex.wrapup(function() os.remove(file.addsuffix(tex.jobname .. "-error","log")) end)
213
214local function processwarning(offset)
215    local lastwarning  = status.lastwarningstring or "?"
216    local lastlocation = status.lastwarningtag or "?"
217    resetmessages()
218    tracers.printwarning {
219        lastwarning  = lastwarning ,
220        lastlocation = lastlocation,
221    }
222end
223
224function tracers.printwarning(specification)
225    logs.report("luatex warning","%s: %s",specification.lastlocation,specification.lastwarning)
226end
227
228directives.register("system.errorcontext", function(v)
229    local register = callback.register
230    if v then
231        register('show_error_message',  nop)
232        register('show_warning_message',function()         processwarning(v)   end)
233        register('show_error_hook',     function(eof)      processerror(v,eof) end)
234        register('show_lua_error_hook', function()         processerror(v)     end)
235    else
236        register('show_error_message',  nil)
237        register('show_error_hook',     nil)
238        register('show_warning_message',nil)
239        register('show_lua_error_hook', nil)
240    end
241end)
242
243-- this might move
244
245lmx = lmx or { }
246
247lmx.htmfile = function(name) return environment.jobname .. "-status.html" end
248lmx.lmxfile = function(name) return resolvers.findfile(name,'tex') end
249
250local function reportback(lmxname,default,variables)
251    if lmxname == false then
252        return variables
253    else
254        local name = lmx.show(type(lmxname) == "string" and lmxname or default,variables)
255        if name then
256            logs.report("context report","file: %s",name)
257        end
258    end
259end
260
261local function showerror(lmxname)
262    local filename, linenumber, errorcontext = status.filename, tonumber(status.linenumber) or 0, ""
263    if not filename then
264        filename, errorcontext = 'unknown', 'error in filename'
265    elseif type(filename) == "number" then
266        filename, errorcontext = format("<read %s>",filename), 'unknown error'
267    else
268        errorcontext = tracers.showlines(filename,linenumber,offset)
269    end
270    local variables = {
271        ['title']                = 'ConTeXt Error Information',
272        ['errormessage']         = status.lasterrorstring,
273        ['linenumber']           = linenumber,
274        ['color-background-one'] = lmx.get('color-background-yellow'),
275        ['color-background-two'] = lmx.get('color-background-purple'),
276        ['filename']             = filename,
277        ['errorcontext']         = errorcontext,
278    }
279    reportback(lmxname,"context-error.lmx",variables)
280    luatex.abort()
281end
282
283lmx.showerror = showerror
284
285function lmx.overloaderror(v)
286    if v == "scite" then
287        usescitelexer = true
288    end
289    callback.register('show_error_hook',     function() showerror() end) -- prevents arguments being passed
290    callback.register('show_lua_error_hook', function() showerror() end) -- prevents arguments being passed
291    callback.register('show_tex_error_hook', function() showerror() end) -- prevents arguments being passed
292end
293
294directives.register("system.showerror", lmx.overloaderror)
295
296-- local debugger = utilities.debugger
297--
298-- local function trace_calls(n)
299--     debugger.enable()
300--     luatex.registerstopactions(function()
301--         debugger.disable()
302--         debugger.savestats(tex.jobname .. "-luacalls.log",tonumber(n))
303--     end)
304--     trace_calls = function() end
305-- end
306--
307-- directives.register("system.tracecalls", function(n)
308--     trace_calls(n)
309-- end) -- indirect is needed for nilling
310
311-- Obsolete ... not that usefull as normally one runs from an editor and
312-- when run unattended it makes no sense either.
313
314-- local editor = [[scite "-open:%filename%" -goto:%linenumber%]]
315--
316-- directives.register("system.editor",function(v)
317--     editor = v
318-- end)
319--
320-- callback.register("call_edit",function(filename,linenumber)
321--     if editor then
322--         editor = gsub(editor,"%%s",filename)
323--         editor = gsub(editor,"%%d",linenumber)
324--         editor = gsub(editor,"%%filename%%",filename)
325--         editor = gsub(editor,"%%linenumber%%",linenumber)
326--         logs.report("system","starting editor: %s",editor)
327--         os.execute(editor)
328--     end
329-- end)
330
331implement { name = "showtrackers",       actions = trackers.show }
332implement { name = "enabletrackers",     actions = trackers.enable,     arguments = "string" }
333implement { name = "disabletrackers",    actions = trackers.disable,    arguments = "string" }
334implement { name = "resettrackers",      actions = trackers.reset }
335
336implement { name = "showdirectives",     actions = directives.show }
337implement { name = "enabledirectives",   actions = directives.enable,   arguments = "string" }
338implement { name = "disabledirectives",  actions = directives.disable,  arguments = "string" }
339
340implement { name = "showexperiments",    actions = experiments.show }
341implement { name = "enableexperiments",  actions = experiments.enable,  arguments = "string" }
342implement { name = "disableexperiments", actions = experiments.disable, arguments = "string" }
343
344implement { name = "overloaderror",      actions = lmx.overloaderror }
345implement { name = "showlogcategories",  actions = logs.show }
346
347local debugger = utilities.debugger
348
349directives.register("system.profile",function(n)
350    luatex.registerstopactions(function()
351        debugger.disable()
352        debugger.savestats("luatex-profile.log",tonumber(n) or 0)
353        report_nl()
354        logs.report("system","profiler stopped, log saved in %a","luatex-profile.log")
355        report_nl()
356    end)
357    logs.report("system","profiler started")
358    debugger.enable()
359end)
360