local ast = {}
function ast.debug (tree, level)
if not tree then
SU.error("debugAST called with nil", true)
end
local out = string.rep(" ", 1 + level)
if level == 0 then
SU.debug("ast", function ()
return "[" .. SILE.currentlyProcessingFile
end)
end
if type(tree) == "function" then
SU.debug("ast", function ()
return out .. tostring(tree)
end)
elseif type(tree) == "table" then
for _, content in ipairs(tree) do
if type(content) == "string" then
SU.debug("ast", function ()
return out .. "[" .. content .. "]"
end)
elseif type(content) == "table" then
if SILE.Commands[content.command] then
SU.debug("ast", function ()
return out .. "\\" .. content.command .. " " .. pl.pretty.write(content.options, "")
end)
if #content >= 1 then
ast.debug(content, level + 1)
end
elseif content.id == "content" or (not content.command and not content.id) then
ast.debug(content, level + 1)
else
SU.debug("ast", function ()
return out .. "?\\" .. (content.command or content.id)
end)
end
end
end
end
if level == 0 then
SU.debug("ast", "]")
end
end
function ast.findInTree (tree, command)
for i = 1, #tree do
if type(tree[i]) == "table" and tree[i].command == command then
return tree[i]
end
end
end
function ast.removeFromTree (tree, command)
for i = 1, #tree do
if type(tree[i]) == "table" and tree[i].command == command then
return table.remove(tree, i)
end
end
end
function ast.createCommand (command, options, content, position)
local result = { content }
result.options = options or {}
result.command = command
result.id = "command"
if position then
result.col = position.col or 0
result.lno = position.lno or 0
result.pos = position.pos or 0
else
result.col = 0
result.lno = 0
result.pos = 0
end
return result
end
function ast.createStructuredCommand (command, options, content, position)
local result = type(content) == "table" and content or { content }
result.options = options or {}
result.command = command
result.id = "command"
if position then
result.col = position.col or 0
result.lno = position.lno or 0
result.pos = position.pos or 0
else
result.col = 0
result.lno = 0
result.pos = 0
end
return result
end
function ast.subContent (content)
local out = {}
for _, val in ipairs(content) do
out[#out + 1] = val
end
return out
end
local function trimLeft (str)
return str:gsub("^%s*", "")
end
local function trimRight (str)
return str:gsub("%s*$", "")
end
function ast.trimSubContent (content)
if #content == 0 then
return
end
if type(content[1]) == "string" then
content[1] = trimLeft(content[1])
if content[1] == "" then
table.remove(content, 1)
end
end
if type(content[#content]) == "string" then
content[#content] = trimRight(content[#content])
if content[#content] == "" then
table.remove(content, #content)
end
end
return content
end
function ast.processAsStructure (content)
local iElem = 0
local nElem = 0
for i = 1, #content do
if type(content[i]) == "table" then
nElem = nElem + 1
end
end
for i = 1, #content do
if type(content[i]) == "table" then
iElem = iElem + 1
content[i].options._pos_ = iElem
content[i].options._last_ = iElem == nElem
SILE.process({ content[i] })
end
end
end
function ast.walkContent (content, action)
if type(content) ~= "table" then
return
end
action(content)
for i = 1, #content do
ast.walkContent(content[i], action)
end
end
function ast.stripContentPos (content)
if type(content) ~= "table" then
return content
end
local stripped = {}
for k, v in pairs(content) do
if type(v) == "table" then
v = ast.stripContentPos(v)
end
stripped[k] = v
end
if content.id or content.command then
stripped.pos, stripped.col, stripped.lno = nil, nil, nil
end
return stripped
end
function ast.contentToString (content)
local string = ""
for i = 1, #content do
if type(content[i]) == "table" and type(content[i][1]) == "string" then
string = string .. content[i][1]
elseif type(content[i]) == "string" then
if content.command == content[i] and content[i] == content[i + 1] then
break
end
string = string .. content[i]
end
end
return string
end
function ast.hasContent (content)
return type(content) == "function" or type(content) == "table" and #content > 0
end
return ast