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

sile-typesetter / sile / 9400953783

06 Jun 2024 12:32PM UTC coverage: 62.819% (-11.3%) from 74.124%
9400953783

push

github

alerque
Merge branch 'develop'

1752 of 2644 new or added lines in 109 files covered. (66.26%)

2019 existing lines in 84 files now uncovered.

10830 of 17240 relevant lines covered (62.82%)

3306.33 hits per line

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

34.96
/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 = {}
198✔
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)
198✔
NEW
12
   if not tree then
×
NEW
13
      SU.error("debugAST called with nil", true)
×
14
   end
NEW
15
   local out = string.rep("  ", 1 + level)
×
NEW
16
   if level == 0 then
×
NEW
17
      SU.debug("ast", function ()
×
NEW
18
         return "[" .. SILE.currentlyProcessingFile
×
19
      end)
20
   end
NEW
21
   if type(tree) == "function" then
×
NEW
22
      SU.debug("ast", function ()
×
NEW
23
         return out .. tostring(tree)
×
24
      end)
NEW
25
   elseif type(tree) == "table" then
×
NEW
26
      for _, content in ipairs(tree) do
×
NEW
27
         if type(content) == "string" then
×
NEW
28
            SU.debug("ast", function ()
×
NEW
29
               return out .. "[" .. content .. "]"
×
30
            end)
NEW
31
         elseif type(content) == "table" then
×
NEW
32
            if SILE.Commands[content.command] then
×
NEW
33
               SU.debug("ast", function ()
×
NEW
34
                  return out .. "\\" .. content.command .. " " .. pl.pretty.write(content.options, "")
×
35
               end)
NEW
36
               if #content >= 1 then
×
NEW
37
                  ast.debug(content, level + 1)
×
38
               end
NEW
39
            elseif content.id == "content" or (not content.command and not content.id) then
×
NEW
40
               ast.debug(content, level + 1)
×
41
            else
NEW
42
               SU.debug("ast", function ()
×
NEW
43
                  return out .. "?\\" .. (content.command or content.id)
×
44
               end)
45
            end
46
         end
47
      end
48
   end
NEW
49
   if level == 0 then
×
NEW
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)
198✔
NEW
61
   for i = 1, #tree do
×
NEW
62
      if type(tree[i]) == "table" and tree[i].command == command then
×
NEW
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)
198✔
NEW
74
   for i = 1, #tree do
×
NEW
75
      if type(tree[i]) == "table" and tree[i].command == command then
×
NEW
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)
198✔
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
NEW
98
      result.col = 0
×
NEW
99
      result.lno = 0
×
NEW
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)
198✔
NEW
113
   local result = type(content) == "table" and content or { content }
×
NEW
114
   result.options = options or {}
×
NEW
115
   result.command = command
×
NEW
116
   result.id = "command"
×
NEW
117
   if position then
×
NEW
118
      result.col = position.col or 0
×
NEW
119
      result.lno = position.lno or 0
×
NEW
120
      result.pos = position.pos or 0
×
121
   else
NEW
122
      result.col = 0
×
NEW
123
      result.lno = 0
×
NEW
124
      result.pos = 0
×
125
   end
NEW
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)
198✔
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)
NEW
143
   return str:gsub("^%s*", "")
×
144
end
145
local function trimRight (str)
NEW
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)
198✔
NEW
154
   if #content == 0 then
×
NEW
155
      return
×
156
   end
NEW
157
   if type(content[1]) == "string" then
×
NEW
158
      content[1] = trimLeft(content[1])
×
NEW
159
      if content[1] == "" then
×
NEW
160
         table.remove(content, 1)
×
161
      end
162
   end
NEW
163
   if type(content[#content]) == "string" then
×
NEW
164
      content[#content] = trimRight(content[#content])
×
NEW
165
      if content[#content] == "" then
×
NEW
166
         table.remove(content, #content)
×
167
      end
168
   end
NEW
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)
198✔
NEW
178
   local iElem = 0
×
NEW
179
   local nElem = 0
×
NEW
180
   for i = 1, #content do
×
NEW
181
      if type(content[i]) == "table" then
×
NEW
182
         nElem = nElem + 1
×
183
      end
184
   end
NEW
185
   for i = 1, #content do
×
NEW
186
      if type(content[i]) == "table" then
×
NEW
187
         iElem = iElem + 1
×
NEW
188
         content[i].options._pos_ = iElem
×
NEW
189
         content[i].options._last_ = iElem == nElem
×
NEW
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)
198✔
NEW
201
   if type(content) ~= "table" then
×
NEW
202
      return
×
203
   end
NEW
204
   action(content)
×
NEW
205
   for i = 1, #content do
×
NEW
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)
198✔
218
   if type(content) ~= "table" then
6✔
NEW
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✔
NEW
224
         v = ast.stripContentPos(v)
×
225
      end
226
      stripped[k] = v
6✔
227
   end
228
   if content.id or content.command then
6✔
NEW
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)
198✔
239
   local string = ""
4✔
240
   for i = 1, #content do
8✔
241
      if type(content[i]) == "table" and type(content[i][1]) == "string" then
8✔
NEW
242
         string = string .. content[i][1]
×
243
      elseif type(content[i]) == "string" then
8✔
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
8✔
247
            break
4✔
248
         end
249
         string = string .. content[i]
4✔
250
      end
251
   end
252
   return string
4✔
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)
198✔
259
   return type(content) == "function" or type(content) == "table" and #content > 0
1,159✔
260
end
261

262
return ast
198✔
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