local traceStack = pl.class({
afterFrame = nil,
})
traceStack.defaultFrame = pl.class({
location = function (self, relative)
local str = ""
if self.file and not relative then
str = str .. self.file .. ":"
end
if self.lno then
str = str .. self.lno .. ":"
if self.col then
str = str .. self.col .. ":"
end
end
str = str .. (str:len() > 0 and " " or "") .. "in "
str = str .. tostring(self)
return str
end,
__tostring = function (self)
self.file = nil
self.lno = nil
self.col = nil
return #self > 0 and tostring(self) or ""
end
})
traceStack.documentFrame = pl.class(traceStack.defaultFrame)
local function oneline (str)
return str:gsub("\n", ""):gsub("\r", "␍"):gsub("\f", "␊"):gsub("\a", "␇"):gsub("\b", "␈"):gsub("\t", "␉"):gsub("\v", "␋")
end
function traceStack.documentFrame:_init (file, snippet)
self.command = "document"
self.file = file
self.snippet = snippet
end
function traceStack.documentFrame:__tostring ()
return "<snippet>:\n\t\t[[" .. oneline(self.snippet) .. "]]"
end
traceStack.commandFrame = pl.class(traceStack.defaultFrame)
function traceStack.commandFrame:_init (command, content, options)
self.command = command
self.file = content.file or SILE.currentlyProcessingFile
self.lno = content.lno
self.col = content.col
self.options = options or {}
end
function traceStack.commandFrame:__tostring ()
local opts = pl.tablex.size(self.options) == 0 and "" or
pl.pretty.write(self.options, ""):gsub("^{", "["):gsub("}$", "]")
return "\\" .. tostring(self.command) .. opts
end
traceStack.contentFrame = pl.class(traceStack.commandFrame)
function traceStack.contentFrame:_init (command, content)
self:super(command, content, content.options)
end
traceStack.textFrame = pl.class(traceStack.defaultFrame)
function traceStack.textFrame:_init (text)
self.text = text
end
function traceStack.textFrame:__tostring ()
if self.text:len() > 20 then
self.text = luautf8.sub(self.text, 1, 18) .. "…"
end
self.text = oneline(self.text)
return '"' .. self.text .. '"'
end
local function formatTraceLine (string)
local prefix = "\t"
return prefix .. string .. "\n"
end
function traceStack:pushDocument(file, doc)
if type(doc) == "table" then doc = tostring(doc) end
local snippet = doc:sub(1, 100)
local frame = traceStack.documentFrame(file, snippet)
return self:pushFrame(frame)
end
function traceStack:pushCommand(command, content, options)
if not command then
SU.warn("Command should be specified for SILE.traceStack:pushCommand", true)
end
if type(content) == "function" then content = {} end
local frame = traceStack.commandFrame(command, content, options)
return self:pushFrame(frame)
end
function traceStack:pushContent(content, command)
if type(content) ~= "table" then
SU.warn("Content parameter of SILE.traceStack:pushContent must be a table", true)
end
command = command or content.command
if not command then
SU.warn("Command should be specified or inferable for SILE.traceStack:pushContent", true)
end
local frame = traceStack.contentFrame(command, content)
return self:pushFrame(frame)
end
function traceStack:pushText(text)
local frame = traceStack.textFrame(text)
return self:pushFrame(frame)
end
local lastPushId = 0
function traceStack:pushFrame(frame)
SU.debug("traceStack", function ()
return string.rep(".", #self) .. "PUSH(" .. frame:location() .. ")"
end)
self[#self + 1] = frame
self.afterFrame = nil
lastPushId = lastPushId + 1
frame._pushId = lastPushId
return lastPushId
end
function traceStack:pop(pushId)
if type(pushId) ~= "number" then
SU.error("SILE.traceStack:pop's argument must be the result value of the corresponding push", true)
end
local popped = self[#self]
if popped._pushId ~= pushId then
local message = "Unbalanced content push/pop"
if SILE.traceback or SU.debugging("traceStack") then
message = message .. ". Expected " .. popped.pushId .. " - (" .. popped:location() .. "), got " .. pushId
end
SU.warn(message, true)
else
self.afterFrame = popped
self[#self] = nil
SU.debug("traceStack", function ()
return string.rep(".", #self) .. "POP(" .. popped:location() .. ")"
end)
end
end
function traceStack:locationHead()
local afterFrame = self.afterFrame
local top = self[#self]
if not top then
return formatTraceLine(afterFrame and "after " .. afterFrame:location() or SILE.currentlyProcessingFile or "<nowhere>")
end
local trace = top:location()
local locationFrame = top
if not locationFrame.lno then
for i = #self - 1, 1, -1 do
if self[i].lno then
locationFrame = self[i]
trace = trace .. " near " .. locationFrame:location(locationFrame.file == top.file)
break
end
end
end
if afterFrame and (not locationFrame or afterFrame.file == locationFrame.file) then
trace = trace .. " after " .. afterFrame:location(true)
end
return trace
end
function traceStack:locationTrace(maxdepth)
local depth = maxdepth or #self
local trace = formatTraceLine(self:locationHead())
depth = depth - 1
if depth > 1 then
repeat
trace = trace .. formatTraceLine(self[depth]:location())
depth = depth - 1
until depth == 1 end
return trace
end
return traceStack