• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sile-typesetter / sile / 11169993653

03 Oct 2024 09:26PM UTC coverage: 63.103% (+29.9%) from 33.23%
11169993653

push

github

web-flow
Merge pull request #2113 from Omikhleia/fix-ast-differences

46 of 57 new or added lines in 5 files covered. (80.7%)

88 existing lines in 10 files now uncovered.

11286 of 17885 relevant lines covered (63.1%)

3626.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

34.43
/core/utilities/ast.lua
1
--- AST utilities.
2
-- Functions for working with SILE's Abstract Syntax Trees.
3
-- @module SU.ast
4

5
-- @type SU.ast
6
local ast = {}
191✔
7

8
--- Output developer friendly debugging view of an AST.
9
-- @tparam table tree Abstract Syntax Tree.
10
-- @tparam integer level Starting level to review.
11
function ast.debug (tree, level)
191✔
12
   if not tree then
×
13
      SU.error("debugAST called with nil", true)
×
14
   end
15
   local out = string.rep("  ", 1 + level)
×
16
   if level == 0 then
×
17
      SU.debug("ast", function ()
×
NEW
18
         return "[" .. (SILE.currentlyProcessingFile or "<nowhere>")
×
19
      end)
20
   end
21
   if type(tree) == "function" then
×
22
      SU.debug("ast", function ()
×
23
         return out .. tostring(tree)
×
24
      end)
25
   elseif type(tree) == "table" then
×
26
      for _, content in ipairs(tree) do
×
27
         if type(content) == "string" then
×
28
            SU.debug("ast", function ()
×
29
               return out .. "[" .. content .. "]"
×
30
            end)
31
         elseif type(content) == "table" then
×
32
            if SILE.Commands[content.command] then
×
33
               SU.debug("ast", function ()
×
34
                  return out .. "\\" .. content.command .. " " .. pl.pretty.write(content.options, "")
×
35
               end)
36
               if #content >= 1 then
×
37
                  ast.debug(content, level + 1)
×
38
               end
NEW
39
            elseif not content.command and not content.id then
×
40
               ast.debug(content, level + 1)
×
41
            else
42
               SU.debug("ast", function ()
×
43
                  return out .. "?\\" .. (content.command or content.id)
×
44
               end)
45
            end
46
         end
47
      end
48
   end
49
   if level == 0 then
×
50
      SU.debug("ast", "]")
×
51
   end
52
end
53

54
--- Find a command node in a SILE AST tree,
55
-- looking only at the first level.
56
-- (We're not reimplementing XPath here.)
57
-- @tparam table tree AST tree
58
-- @tparam string command command name
59
-- @treturn table|nil AST command node
60
function ast.findInTree (tree, command)
191✔
61
   for i = 1, #tree do
×
62
      if type(tree[i]) == "table" and tree[i].command == command then
×
63
         return tree[i]
×
64
      end
65
   end
66
end
67

68
--- Find and extract (remove) a command node in a SILE AST tree,
69
-- looking only at the first level.
70
-- @tparam table tree AST tree
71
-- @tparam string command command name
72
-- @treturn table|nil AST command node
73
function ast.removeFromTree (tree, command)
191✔
74
   for i = 1, #tree do
×
75
      if type(tree[i]) == "table" and tree[i].command == command then
×
76
         return table.remove(tree, i)
×
77
      end
78
   end
79
end
80

81
--- Create a command from a simple content tree.
82
-- It encapsulates the content in a command node.
83
-- @tparam string command command name
84
-- @tparam table options command options
85
-- @tparam table content child AST tree
86
-- @tparam table position position in source (or parent AST command node)
87
-- @treturn table       AST command node
88
function ast.createCommand (command, options, content, position)
191✔
89
   local result = { content }
732✔
90
   result.options = options or {}
732✔
91
   result.command = command
732✔
92
   result.id = "command"
732✔
93
   if position then
732✔
94
      result.col = position.col or 0
732✔
95
      result.lno = position.lno or 0
732✔
96
      result.pos = position.pos or 0
732✔
97
   else
98
      result.col = 0
×
99
      result.lno = 0
×
100
      result.pos = 0
×
101
   end
102
   return result
732✔
103
end
104

105
--- Create a command from a structured content tree.
106
-- The content is normally a table of an already prepared content list.
107
-- @tparam string command command name
108
-- @tparam table options command options
109
-- @tparam table content child AST tree
110
-- @tparam table position position in source (or parent AST command node)
111
-- @treturn table AST command node
112
function ast.createStructuredCommand (command, options, content, position)
191✔
113
   local result = type(content) == "table" and content or { content }
×
114
   result.options = options or {}
×
115
   result.command = command
×
116
   result.id = "command"
×
117
   if position then
×
118
      result.col = position.col or 0
×
119
      result.lno = position.lno or 0
×
120
      result.pos = position.pos or 0
×
121
   else
122
      result.col = 0
×
123
      result.lno = 0
×
124
      result.pos = 0
×
125
   end
126
   return result
×
127
end
128

129
--- Extract the sub-content tree from a (command) node,
130
-- that is the child nodes of the (command) node.
131
-- @tparam table content AST tree
132
-- @treturn table AST tree
133
function ast.subContent (content)
191✔
134
   local out = {}
6✔
135
   for _, val in ipairs(content) do
12✔
136
      out[#out + 1] = val
6✔
137
   end
138
   return out
6✔
139
end
140

141
-- String trimming
142
local function trimLeft (str)
143
   return str:gsub("^%s*", "")
×
144
end
145
local function trimRight (str)
146
   return str:gsub("%s*$", "")
×
147
end
148

149
--- Content tree trimming: remove leading and trailing spaces, but from
150
--- a content tree i.e. possibly containing several elements.
151
-- @tparam table content AST tree
152
-- @treturn table AST tree
153
function ast.trimSubContent (content)
191✔
154
   if #content == 0 then
×
155
      return
×
156
   end
157
   if type(content[1]) == "string" then
×
158
      content[1] = trimLeft(content[1])
×
159
      if content[1] == "" then
×
160
         table.remove(content, 1)
×
161
      end
162
   end
163
   if type(content[#content]) == "string" then
×
164
      content[#content] = trimRight(content[#content])
×
165
      if content[#content] == "" then
×
166
         table.remove(content, #content)
×
167
      end
168
   end
169
   return content
×
170
end
171

172
--- Process the AST walking through content nodes as a "structure":
173
-- Text nodes are ignored (e.g. usually just spaces due to indentation)
174
-- Command options are enriched with their "true" node position, so we can later
175
-- refer to it (as with an XPath pos()).
176
-- @tparam table content AST tree
177
function ast.processAsStructure (content)
191✔
178
   local iElem = 0
×
179
   local nElem = 0
×
180
   for i = 1, #content do
×
181
      if type(content[i]) == "table" then
×
182
         nElem = nElem + 1
×
183
      end
184
   end
185
   for i = 1, #content do
×
186
      if type(content[i]) == "table" then
×
187
         iElem = iElem + 1
×
188
         content[i].options._pos_ = iElem
×
189
         content[i].options._last_ = iElem == nElem
×
190
         SILE.process({ content[i] })
×
191
      end
192
      -- All text nodes in ignored in structure tags.
193
   end
194
end
195

196
--- Call `action` on each content AST node, recursively, including `content` itself.
197
-- Not called on leaves, i.e. strings.
198
-- @tparam table content AST tree
199
-- @tparam function action A function to call on each node
200
function ast.walkContent (content, action)
191✔
201
   if type(content) ~= "table" then
×
202
      return
×
203
   end
204
   action(content)
×
205
   for i = 1, #content do
×
206
      ast.walkContent(content[i], action)
×
207
   end
208
end
209

210
--- Strip position, line and column recursively from a content tree.
211
-- This can be used to remove position details where we do not want them,
212
-- e.g. in table of contents entries (referring to the original content,
213
-- regardless where it was exactly, for the purpose of checking whether
214
-- the table of contents changed.)
215
-- @param table content AST tree
216
-- @treturn table AST tree
217
function ast.stripContentPos (content)
191✔
218
   if type(content) ~= "table" then
6✔
219
      return content
×
220
   end
221
   local stripped = {}
6✔
222
   for k, v in pairs(content) do
12✔
223
      if type(v) == "table" then
6✔
224
         v = ast.stripContentPos(v)
×
225
      end
226
      stripped[k] = v
6✔
227
   end
228
   if content.id or content.command then
6✔
229
      stripped.pos, stripped.col, stripped.lno = nil, nil, nil
×
230
   end
231
   return stripped
6✔
232
end
233

234
--- Flatten content trees into just the string components (allows passing
235
-- objects with complex structures to functions that need plain strings)
236
-- @tparam table content AST tree
237
-- @treturn string A string representation of content
238
function ast.contentToString (content)
191✔
239
   local string = ""
26✔
240
   for i = 1, #content do
52✔
241
      if type(content[i]) == "table" and type(content[i][1]) == "string" then
26✔
242
         string = string .. content[i][1]
×
243
      elseif type(content[i]) == "string" then
26✔
244
         -- Work around PEG parser returning env tags as content
245
         -- TODO: refactor capture groups in PEG parser
246
         if content.command == content[i] and content[i] == content[i + 1] then
26✔
247
            break
248
         end
249
         string = string .. content[i]
26✔
250
      end
251
   end
252
   return string
26✔
253
end
254

255
--- Check whether a content AST tree is empty.
256
-- @tparam table content AST tree
257
-- @treturn boolean true if content is not empty
258
function ast.hasContent (content)
191✔
259
   return type(content) == "function" or type(content) == "table" and #content > 0
1,149✔
260
end
261

262
return ast
191✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc