trac-deb.lmt /size: 15 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. I will clean it up in lmtx.
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
22
23local implement    = interfaces.implement
24
25local ioflush      = io.flush
26local ioread       = io.read
27local ossleep      = os.sleep
28local osexit       = os.exit
29local writenl      = texio.writenl
30local write        = texio.write
31
32local runlocal     = tex.runlocal
33local terminaldata = false
34local context      = context
35
36implement {
37    name    = "fetchterminaldata",
38    actions = function()
39        context(terminaldata)
40    end,
41}
42
43function texio.terminal()
44    writenl("\n" .. "entering interactive mode, use \\quit to abort reading" .."\n\n")
45    while true do
46        write(">")
47        ioflush()
48        terminaldata = ioread()
49        if terminaldata == "\\quit" then
50            terminaldata = false
51            break
52        else
53            runlocal("t_syst_terminal_data",nil,nil,true) -- obeymode
54        end
55    end
56end
57
58implement {
59    name      = "readfromterminal",
60    public    = true,
61    protected = true,
62    actions   = texio.terminal,
63}
64
65local savedluaerror = nil
66local usescitelexer = nil
67local quitonerror   = true
68
69local function errorreporter(luaerror)
70    local category = luaerror and "lua error" or "tex error"
71    local report = logs.reporter(category)
72    logs.enable(category)
73    return report
74end
75
76function tracers.showlines(filename,linenumber,offset,luaerrorline)
77    local data = io.loaddata(filename)
78    if not data or data == "" then
79        local hash = url.hashed(filename)
80        if not hash.noscheme then
81            local ok, d, n = resolvers.loaders.byscheme(hash.scheme,filename)
82            if ok and n > 0 then
83                data = d
84            end
85        end
86    end
87    local scite = usescitelexer and require("util-sci")
88    if scite then
89        return utilities.scite.tohtml(data,"tex",linenumber or true,false)
90    else
91        local lines = data and string.splitlines(data)
92        if lines and #lines > 0 then
93            if luaerrorline and luaerrorline > 0 then
94                -- lua error: linenumber points to last line
95                local start = "\\startluacode"
96                local stop  = "\\stopluacode"
97                local n = linenumber
98                for i=n,1,-1 do
99                    local line = lines[i]
100                    if not line then
101                        break
102                    elseif find(line,start) then
103                        n = i + luaerrorline - 1
104                        if n <= linenumber then
105                            linenumber = n
106                        end
107                        break
108                    end
109                end
110            end
111            offset = tonumber(offset) or 10
112            linenumber = tonumber(linenumber) or 10
113            local start = math.max(linenumber - offset,1)
114            local stop = math.min(linenumber + offset,#lines)
115            if stop > #lines then
116                return "<linenumber past end of file>"
117            else
118                local result, fmt = { }, "%" .. #tostring(stop) .. "d %s  %s"
119                for n=start,stop do
120                    result[#result+1] = format(fmt,n,n == linenumber and ">>" or "  ",lines[n])
121                end
122                return concat(result,"\n")
123            end
124        else
125            return "<empty file>"
126        end
127    end
128end
129
130-- todo: last tex error has ! prepended
131-- todo: some nested errors have two line numbers
132-- todo: collect errorcontext in string (after code cleanup)
133-- todo: have a separate status.lualinenumber
134-- todo: \starttext bla \blank[foo] bla \stoptext
135
136local nop = function() end
137local resetmessages = status.resetmessages or nop
138
139local function processerror(offset,errortype)
140    local readstate      = status.getreadstate()
141    local filename       = readstate.filename
142    local linenumber     = readstate.linenumber
143    local skiplinenumber = readstate.skiplinenumber
144    local errorstate     = status.errorstate
145    local lastcontext    = errorstate.errorcontext
146    local lasttexerror   = errorstate.error or "?"
147    local lastluaerror   = errorstate.luaerror or "?" -- lasttexerror
148    local luaerrorline   = match(lastluaerror,[[lua%]?:.-(%d+)]]) or (lastluaerror and find(lastluaerror,"?:0:",1,true) and 0)
149    local lastmpserror   = match(lasttexerror,[[^.-mp%serror:%s*(.*)$]])
150    resetmessages()
151    lastluaerror = gsub(lastluaerror,"%[\\directlua%]","[ctxlua]")
152    tracers.printerror {
153        filename       = filename,
154        linenumber     = linenumber,
155        skiplinenumber = skiplinenumber,
156        offset         = tonumber(offset) or 10,
157        lasttexerror   = lasttexerror,
158        lastmpserror   = lastmpserror,
159        lastluaerror   = lastluaerror, -- can be the same as lasttexerror
160        luaerrorline   = luaerrorline,
161        lastcontext    = lastcontext,
162        lasttexhelp    = tex.gethelptext and tex.gethelptext() or nil,
163        errortype      = errortype,
164    }
165    if job and type(job.disablesave) == "function" then
166        job.disablesave()
167    end
168    lua.setexitcode(1)
169end
170
171directives.register("system.quitonerror",function(v)
172    quitonerror = toboolean(v)
173end)
174
175directives.register("system.usescitelexer",function(v)
176    usescitelexer = toboolean(v)
177end)
178
179local busy = false
180
181function tracers.printerror(specification)
182    if not busy then
183        busy = true
184        local errorvalues  = table.swapped(tex.geterrorvalues())
185        local filename     = specification.filename
186        local linenumber   = specification.linenumber
187        local lasttexerror = specification.lasttexerror
188        local lastmpserror = specification.lastmpserror
189        local lastluaerror = specification.lastluaerror
190        local lastcontext  = specification.lastcontext
191        local luaerrorline = specification.luaerrorline
192        local errortype    = specification.errortype
193        local offset       = specification.offset
194        local endoffile    = specification.endoffile
195        local report       = errorreporter(luaerrorline)
196        if errortype == errorvalues.warning then
197            report("warning error: %s",lasttexerror or "-")
198            busy = false;
199            return;
200        elseif errortype == errorvalues.eof then
201            report("runaway error: %s",lasttexerror or "-")
202            if not quitonerror and texio.terminal then
203                texio.terminal() -- not well tested
204            end
205        elseif errortype == errorvalues.condition then
206            linenumber = specification.skiplinenumber
207            if linenumber > 0 then
208                report("condition error on line %s in file %s: %s",linenumber,filename,lasttexerror)
209                report_nl()
210                report_str(tracers.showlines(filename,linenumber,offset,tonumber(luaerrorline)))
211                report_nl()
212            else
213                report("runaway condition error: %s",lasttexerror or "-")
214            end
215            quitonerror = true
216        elseif not filename then
217            report("fuzzy error:")
218            report("  tex: %s",lasttexerror or "-")
219            report("  lua: %s",lastluaerror or "-")
220            report("  mps: %s",lastmpserror or "-")
221        elseif type(filename) == "number" then
222            report("error on line %s of filehandle %s: %s ...",linenumber,lasttexerror)
223        else
224            report_nl()
225            if luaerrorline then
226                if linenumber == 0 or not filename or filename == "" then
227                    print("\nfatal lua error:\n\n",lastluaerror,"\n")
228                    luatex.abort()
229                    return
230                else
231                    report("lua error on line %s in file %s:\n\n%s",linenumber,filename,lastluaerror)
232                end
233            elseif lastmpserror then
234                report("mp error on line %s in file %s:\n\n%s",linenumber,filename,lastmpserror)
235            else
236                report("tex error on line %s in file %s: %s",linenumber,filename,lasttexerror)
237                if lastcontext then
238                    report_nl()
239                    report_str(lastcontext)
240                    report_nl()
241                else
242                    report_nl()
243                 -- tex.showcontext()
244                end
245                if lastluaerror and not match(lastluaerror,"^%s*[%?]*%s*$") then
246                    print("\nlua error:\n\n",lastluaerror,"\n")
247                    quitonerror = true
248                end
249            end
250            report_nl()
251            report_str(tracers.showlines(filename,linenumber,offset,tonumber(luaerrorline)))
252            report_nl()
253        end
254        if quitonerror then
255            local name = tex.jobname or ""
256            if name ~= "" then
257                table.save(name .. "-error.log",specification)
258            end
259            local help = specification.lasttexhelp
260            if help and #help > 0 then
261                report_nl()
262                report_str(help)
263                report_nl()
264                report_nl()
265            end
266            luatex.abort()
267        end
268        busy = false
269    end
270end
271
272luatex.wrapup(function() os.remove(tex.jobname .. "-error.log") end)
273
274local function processwarning(offset)
275    local warningstate = status.warningstate
276    local lastwarning  = warningstate.warning or "?"
277    local lastlocation = warningstate.warningtag or "?"
278    resetmessages()
279    tracers.printwarning {
280        lastwarning  = lastwarning,
281        lastlocation = lastlocation,
282    }
283end
284
285function tracers.printwarning(specification)
286    logs.report("luatex warning","%s: %s",specification.lastlocation,specification.lastwarning)
287end
288
289directives.register("system.errorcontext", function(v)
290    local register = callback.register
291    if v then
292        register("show_error_message",   nop)
293        register("show_warning_message", function()         processwarning(v)   end)
294        register("intercept_lua_error",  function()         processerror(v)     end)
295        register("intercept_tex_error",  function(mode,eof) processerror(v,eof) return mode end)
296    else
297        register("show_error_message",   nil)
298        register("show_warning_message", nil)
299        register("intercept_lua_error",  nil)
300        register("intercept_tex_error",  nil)
301    end
302end)
303
304-- this might move
305
306lmx = lmx or { }
307
308lmx.htmfile = function(name) return environment.jobname .. "-status.html" end
309lmx.lmxfile = function(name) return resolvers.findfile(name,'tex') end
310
311local function reportback(lmxname,default,variables)
312    if lmxname == false then
313        return variables
314    else
315        local name = lmx.show(type(lmxname) == "string" and lmxname or default,variables)
316        if name then
317            logs.report("context report","file: %s",name)
318        end
319    end
320end
321
322local function showerror(lmxname)
323    local readstate    = status.getreadstate()
324    local filename     = readstate.filename
325    local linenumber   = tonumber(readstate.linenumber) or 0
326    local errorcontext = ""
327    if not filename then
328        filename     = status.iocodes[readstate.iocode]
329        errorcontext = 'error in filename'
330    else
331        errorcontext = tracers.showlines(filename,linenumber,offset)
332    end
333    local variables = {
334        ['title']                = 'ConTeXt Error Information',
335        ['errormessage']         = status.geterrorstate().error or "?",
336        ['linenumber']           = linenumber,
337        ['color-background-one'] = lmx.get('color-background-yellow'),
338        ['color-background-two'] = lmx.get('color-background-purple'),
339        ['filename']             = filename,
340        ['errorcontext']         = errorcontext,
341    }
342    reportback(lmxname,"context-error.lmx",variables)
343    luatex.abort()
344end
345
346lmx.showerror = showerror
347
348function lmx.overloaderror(v)
349    if v == "scite" then
350        usescitelexer = true
351    end
352    callback.register('show_error_message',  function() showerror() end)
353--     callback.register('intercept_lua_error', function() showerror() end)
354--     callback.register('intercept_tex_error', function() showerror() end)
355end
356
357directives.register("system.showerror", lmx.overloaderror)
358
359-- local debugger = utilities.debugger
360--
361-- local function trace_calls(n)
362--     debugger.enable()
363--     luatex.registerstopactions(function()
364--         debugger.disable()
365--         debugger.savestats(tex.jobname .. "-luacalls.log",tonumber(n))
366--     end)
367--     trace_calls = function() end
368-- end
369--
370-- directives.register("system.tracecalls", function(n)
371--     trace_calls(n)
372-- end) -- indirect is needed for nilling
373
374-- Obsolete ... not that usefull as normally one runs from an editor and
375-- when run unattended it makes no sense either.
376
377-- local editor = [[scite "-open:%filename%" -goto:%linenumber%]]
378--
379-- directives.register("system.editor",function(v)
380--     editor = v
381-- end)
382--
383-- callback.register("call_edit",function(filename,linenumber)
384--     if editor then
385--         editor = gsub(editor,"%%s",filename)
386--         editor = gsub(editor,"%%d",linenumber)
387--         editor = gsub(editor,"%%filename%%",filename)
388--         editor = gsub(editor,"%%linenumber%%",linenumber)
389--         logs.report("system","starting editor: %s",editor)
390--         os.execute(editor)
391--     end
392-- end)
393
394implement { name = "showtrackers",       public = true, protected = true, actions = trackers.show }
395implement { name = "enabletrackers",     public = true, protected = true, actions = trackers.enable,     arguments = "optional" }
396implement { name = "disabletrackers",    public = true, protected = true, actions = trackers.disable,    arguments = "optional" }
397implement { name = "resettrackers",      public = true, protected = true, actions = trackers.reset }
398
399implement { name = "showdirectives",     public = true, protected = true, actions = directives.show }
400implement { name = "enabledirectives",   public = true, protected = true, actions = directives.enable,   arguments = "optional" }
401implement { name = "disabledirectives",  public = true, protected = true, actions = directives.disable,  arguments = "optional" }
402
403implement { name = "showexperiments",    public = true, protected = true, actions = experiments.show }
404implement { name = "enableexperiments",  public = true, protected = true, actions = experiments.enable,  arguments = "optional" }
405implement { name = "disableexperiments", public = true, protected = true, actions = experiments.disable, arguments = "optional" }
406
407implement { name = "overloaderror",      public = true, protected = true, actions = lmx.overloaderror }
408implement { name = "showlogcategories",  public = true, protected = true, actions = logs.show }
409
410local debugger = utilities.debugger
411
412directives.register("system.profile",function(n)
413    luatex.registerstopactions(function()
414        debugger.disable()
415        debugger.savestats("luatex-profile.log",tonumber(n) or 0)
416        report_nl()
417        logs.report("system","profiler stopped, log saved in %a","luatex-profile.log")
418        report_nl()
419    end)
420    logs.report("system","profiler started")
421    debugger.enable()
422end)
423
424local report = logs.reporter("[[diagnostic]]")
425
426implement {
427    name      = "diagnostic",
428    public    = true,
429 -- protected = false, -- expandable
430    arguments = { "optional", "string" },
431    actions   = function(t,s)
432        if t == "quit" then
433            report(s)
434            osexit()
435        else
436            t = tonumber(t)
437            if t then
438                report("%s (sleep: %.3N)",s,t)
439                ioflush()
440                ossleep(t)
441            else
442                report(s)
443                ioflush()
444            end
445        end
446    end
447}
448