Skip to content

Commit 4854992

Browse files
Omikhleiaalerque
authored andcommitted
fix(inputters): SIL commands and environments must generate the same syntax tree
1 parent f6a3920 commit 4854992

File tree

4 files changed

+81
-35
lines changed

4 files changed

+81
-35
lines changed

core/sile.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ function SILE.process (ast)
411411
content()
412412
elseif SILE.Commands[content.command] then
413413
SILE.call(content.command, content.options, content)
414-
elseif content.id == "content" or (not content.command and not content.id) then
414+
elseif not content.command and not content.id then
415415
local pId = SILE.traceStack:pushContent(content, "content")
416416
SILE.process(content)
417417
SILE.traceStack:pop(pId)

core/utilities/ast.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ function ast.debug (tree, level)
3636
if #content >= 1 then
3737
ast.debug(content, level + 1)
3838
end
39-
elseif content.id == "content" or (not content.command and not content.id) then
39+
elseif not content.command and not content.id then
4040
ast.debug(content, level + 1)
4141
else
4242
SU.debug("ast", function ()

inputters/sil.lua

Lines changed: 77 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -80,49 +80,96 @@ local function getline (str, pos)
8080
return lno, col
8181
end
8282

83-
local function massage_ast (tree, doc)
83+
local function ast_from_parse_tree (tree, doc, depth)
8484
if type(tree) == "string" then
8585
return tree
8686
end
87+
8788
if tree.pos then
8889
tree.lno, tree.col = getline(doc, tree.pos)
8990
tree.pos = nil
9091
end
91-
SU.debug("inputter", "Processing ID:", tree.id)
92-
if false or tree.id == "comment" then
93-
SU.debug("inputter", "Discarding comment:", pl.stringx.strip(tree[1]))
94-
return {}
92+
93+
local sep -- luacheck: ignore 211
94+
if SU.debugging("inputter") then
95+
depth = depth + 1
96+
sep = (" "):rep(depth)
97+
end
98+
SU.debug("inputter", sep and (sep .. "Processing ID:"), tree.id)
99+
100+
local res
101+
if tree.id == "comment" then
102+
-- Drop comments
103+
SU.debug("inputter", sep and (sep .. "Discarding comment"))
104+
res = {}
95105
elseif
96106
false
97107
or tree.id == "document"
98108
or tree.id == "braced_content"
99109
or tree.id == "passthrough_content"
100110
or tree.id == "braced_passthrough_content"
101111
or tree.id == "env_passthrough_content"
102-
then
103-
SU.debug("inputter", "Re-massage subtree", tree.id)
104-
return massage_ast(tree[1], doc)
105-
elseif
106-
false
107112
or tree.id == "text"
108113
or tree.id == "passthrough_text"
109114
or tree.id == "braced_passthrough_text"
110115
or tree.id == "env_passthrough_text"
111116
then
112-
SU.debug("inputter", " - Collapse subtree")
113-
return tree[1]
114-
elseif false or tree.id == "content" or tree.id == "environment" or tree.id == "command" then
115-
SU.debug("inputter", " - Massage in place", tree.id)
116-
for key, val in ipairs(tree) do
117-
SU.debug("inputter", " -", val.id)
118-
if val.id == "content" then
119-
SU.splice(tree, key, key, massage_ast(val, doc))
120-
elseif val.id then -- requiring an id discards nodes with no content such as comments
121-
tree[key] = massage_ast(val, doc)
117+
-- These nodes have only one child, which needs recursion.
118+
SU.debug("inputter", sep and (sep .. "Massaging a node"))
119+
res = ast_from_parse_tree(tree[1], doc, depth)
120+
--res = #res > 1 and not res.id and res or res[1]
121+
elseif false or tree.id == "environment" or tree.id == "command" then
122+
-- These nodes have multiple children, which need recursion.
123+
SU.debug("inputter", sep and (sep .. "Processing command"), tree.command, #tree, "subtrees")
124+
local newtree = { -- I don't think we can avoid a shallow copy here
125+
command = tree.command,
126+
options = tree.options,
127+
id = tree.id,
128+
lno = tree.lno,
129+
col = tree.col,
130+
}
131+
for _, node in ipairs(tree) do
132+
if type(node) == "table" then
133+
SU.debug("inputter", sep and (sep .. " -"), node.id or "table")
134+
local ast_node = ast_from_parse_tree(node, doc, depth)
135+
if type(ast_node) == "table" and not ast_node.id then
136+
SU.debug("inputter", sep and (sep .. " -"), "Collapsing subtree")
137+
-- Comments can an empty table, skip them
138+
if #ast_node > 0 then
139+
-- Simplify the tree if it's just a plain list
140+
for _, child in ipairs(ast_node) do
141+
if type(child) ~= "table" or child.id or #child > 0 then
142+
table.insert(newtree, child)
143+
end
144+
end
145+
end
146+
else
147+
table.insert(newtree, ast_node)
148+
end
122149
end
150+
-- Non table nodes are skipped (e.g. extraneous text from 'raw' commands)
123151
end
124-
return tree
152+
res = newtree
153+
elseif tree.id == "content" then
154+
-- This node has multiple children, which need recursion
155+
-- And the node itself needs to be replaced with its children
156+
SU.debug("inputter", sep and (sep .. "Massage content node"), #tree, "subtrees")
157+
local newtree = {} -- I don't think we can avoid a shallow copy here
158+
for i, node in ipairs(tree) do
159+
SU.debug("inputter", sep and (sep .. " -"), node.id)
160+
newtree[i] = ast_from_parse_tree(node, doc, depth)
161+
end
162+
-- Simplify the tree if it has only one child
163+
res = #newtree == 1 and not newtree.id and newtree[1] or newtree
164+
elseif tree.id then
165+
-- Shouldn't happen, or we missed something
166+
SU.error("Unknown node type: " .. tree.id)
167+
else
168+
SU.debug("inputter", sep and (sep .. "Table node"), #tree, "subtrees")
169+
res = #tree == 1 and tree[1] or tree
125170
end
171+
SU.debug("inputter", sep and (sep .. "Returning a"), type(res) == "table" and res.id or "string")
172+
return res
126173
end
127174

128175
function inputter:parse (doc)
@@ -133,24 +180,27 @@ function inputter:parse (doc)
133180
%s thrown from document beginning]]):format(pl.stringx.indent(result, 6)))
134181
end
135182
resetCache()
136-
local top = massage_ast(result[1], doc)
183+
local top = ast_from_parse_tree(result[1], doc, 0)
137184
local tree
138185
-- Content not part of a tagged command could either be part of a document
139186
-- fragment or junk (e.g. comments, whitespace) outside of a document tag. We
140187
-- need to either capture the document tag only or decide this is a fragment
141188
-- and wrap it in a document tag.
142-
for _, leaf in ipairs(top) do
143-
if leaf.command and (leaf.command == "document" or leaf.command == "sile") then
144-
tree = leaf
145-
break
189+
if top.command == "document" or top.command == "sile" then
190+
tree = top
191+
elseif type(top) == "table" then
192+
for _, leaf in ipairs(top) do
193+
if leaf.command and (leaf.command == "document" or leaf.command == "sile") then
194+
tree = leaf
195+
break
196+
end
146197
end
147198
end
148199
-- In the event we didn't isolate a top level document tag above, assume this
149200
-- is a fragment and wrap it in one.
150201
if not tree then
151202
tree = { top, command = "document" }
152203
end
153-
-- SU.dump(tree)
154204
return { tree }
155205
end
156206

packages/autodoc/init.lua

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,8 @@ local function typesetAST (options, content)
101101
else
102102
seenCommandWithoutArg = true
103103
end
104-
elseif ast.id == "content" or (not ast.command and not ast.id) then
105-
-- Due to the way it is implemented, the SILE-inputter may generate such
106-
-- nodes in the AST. It's poorly documented, so it's not clear why they
107-
-- are even kept there (esp. the "content" nodes), but anyhow, as
108-
-- far as autodoc is concerned for presentation purposes, just
109-
-- recurse into them.
104+
elseif not ast.command and not ast.id then
105+
-- Mere list of nodes
110106
typesetAST(options, ast)
111107
else
112108
SU.error("Unrecognized AST element, type " .. type(ast))

0 commit comments

Comments
 (0)