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

sile-typesetter / sile / 9302844829

30 May 2024 12:51PM UTC coverage: 74.707% (+1.7%) from 73.034%
9302844829

push

github

web-flow
Merge pull request #1931 from alerque/stylua

156 of 166 new or added lines in 4 files covered. (93.98%)

1 existing line in 1 file now uncovered.

11939 of 15981 relevant lines covered (74.71%)

7260.67 hits per line

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

95.83
/inputters/sil.lua
1
local base = require("inputters.base")
181✔
2

3
local epnf = require("epnf")
181✔
4

5
local inputter = pl.class(base)
181✔
6
inputter._name = "sil"
181✔
7

8
inputter.order = 50
181✔
9

10
inputter.appropriate = function (round, filename, doc)
11
  if round == 1 then
178✔
12
    return filename:match(".sil$")
175✔
13
  elseif round == 2 then
3✔
14
    local sniff = doc:sub(1, 100)
2✔
15
    local promising = sniff:match("\\begin") or sniff:match("\\document") or sniff:match("\\sile")
2✔
16
    return promising and inputter.appropriate(3, filename, doc) or false
3✔
17
  elseif round == 3 then
1✔
18
    local _parser = epnf.define(inputter._grammar)
1✔
19
    local status, _ = pcall(epnf.parsestring, _parser, doc)
1✔
20
    return status
1✔
21
  end
22
end
23

24
local bits = SILE.parserBits
181✔
25

26

27
inputter.passthroughCommands = {
181✔
28
  ftl = true,
29
  lua = true,
30
  math = true,
31
  raw = true,
32
  script = true,
33
  sil = true,
34
  use = true,
35
  xml = true
×
36
}
181✔
37

38
function inputter:_init ()
181✔
39
  -- Save time when parsing strings by only setting up the grammar once per
40
  -- instantiation then re-using it on every use.
41
  self._parser = self:rebuildParser()
348✔
42
  base._init(self)
174✔
43
end
44

45
-- luacheck: push ignore
46
-- stylua: ignore start
47
---@diagnostic disable: undefined-global, unused-local, lowercase-global
48
function inputter._grammar (_ENV)
181✔
49
   local isPassthrough = function (_, _, command)
50
      return inputter.passthroughCommands[command] or false
4,031✔
51
   end
52
   local isNotPassthrough = function (...)
53
      return not isPassthrough(...)
3,958✔
54
   end
55
   local isMatchingEndEnv = function (a, b, thisCommand, lastCommand)
56
      return thisCommand == lastCommand
292✔
57
   end
58
   local _ = WS^0
175✔
59
   local eol = S"\r\n"
175✔
60
   local specials = S"{}%\\"
175✔
61
   local escaped_specials = P"\\" * specials
175✔
62
   local unescapeSpecials = function (str)
63
      return str:gsub('\\([{}%%\\])', '%1')
1,798✔
64
   end
65
   local myID = C(bits.silidentifier) / 1
175✔
66
   local cmdID = myID - P"beign" - P"end"
175✔
67
   local wrapper = function (a) return type(a)=="table" and a or {} end
1,847✔
68
   local parameters = (P"[" * bits.parameters * P"]")^-1 / wrapper
175✔
69
   local comment = (
70
         P"%" *
175✔
71
         P(1-eol)^0 *
175✔
72
         eol^-1
175✔
73
      ) / ""
175✔
74

75
   START "document"
175✔
76
   document = V"texlike_stuff" * EOF"Unexpected character at end of input"
350✔
77
   texlike_stuff = Cg(
350✔
78
         V"environment" +
175✔
79
         comment +
175✔
80
         V"texlike_text" +
175✔
81
         V"texlike_braced_stuff" +
175✔
82
         V"texlike_command"
175✔
83
      )^0
175✔
84
   passthrough_stuff = C(Cg(
525✔
85
         V"passthrough_text" +
175✔
86
         V"passthrough_debraced_stuff"
175✔
87
      )^0)
350✔
88
   passthrough_env_stuff = Cg(
350✔
89
         V"passthrough_env_text"
175✔
90
      )^0
175✔
91
   texlike_text = C((1 - specials + escaped_specials)^1) / unescapeSpecials
175✔
92
   passthrough_text = C((1-S("{}"))^1)
175✔
93
   passthrough_env_text = C((1 - (P"\\end{" * Cmt(cmdID * Cb"command", isMatchingEndEnv) * P"}"))^1)
175✔
94
   texlike_braced_stuff = P"{" * V"texlike_stuff" * ( P"}" + E("} expected") )
350✔
95
   passthrough_braced_stuff = P"{" * V"passthrough_stuff" * ( P"}" + E("} expected") )
350✔
96
   passthrough_debraced_stuff = C(V"passthrough_braced_stuff")
175✔
NEW
97
   texlike_command = (
×
98
         P"\\" *
175✔
99
         Cg(cmdID, "command") *
175✔
100
         Cg(parameters, "options") *
175✔
NEW
101
         (
×
102
         (Cmt(Cb"command", isPassthrough) * V"passthrough_braced_stuff") +
175✔
103
         (Cmt(Cb"command", isNotPassthrough) * V"texlike_braced_stuff")
175✔
104
         )^0
175✔
105
      )
175✔
106
   local notpass_end =
107
         P"\\end{" *
175✔
108
         ( Cmt(cmdID * Cb"command", isMatchingEndEnv) + E"Environment mismatch") *
350✔
109
         ( P"}" * _ ) + E"Environment begun but never ended"
350✔
110
   local pass_end =
111
         P"\\end{" *
175✔
112
         ( cmdID * Cb"command" ) *
175✔
113
         ( P"}" * _ ) + E"Environment begun but never ended"
350✔
NEW
114
   environment =
×
115
      P"\\begin" *
175✔
116
      Cg(parameters, "options") *
175✔
117
      P"{" *
175✔
118
      Cg(cmdID, "command") *
175✔
119
      P"}" *
175✔
UNCOV
120
      (
×
121
         (Cmt(Cb"command", isPassthrough) * V"passthrough_env_stuff" * pass_end) +
175✔
122
         (Cmt(Cb"command", isNotPassthrough) * V"texlike_stuff" * notpass_end)
175✔
123
      )
175✔
124
end
125
-- luacheck: pop
126
-- stylua: ignore end
127
---@diagnostic enable: undefined-global, unused-local, lowercase-global
128

129
local linecache = {}
181✔
130
local lno, col, lastpos
131
local function resetCache ()
132
  lno = 1
174✔
133
  col = 1
174✔
134
  lastpos = 0
174✔
135
  linecache = { { lno = 1, pos = 1} }
174✔
136
end
137

138
local function getline (str, pos)
139
  local start = 1
6,840✔
140
  lno = 1
6,840✔
141
  if pos > lastpos then
6,840✔
142
    lno = linecache[#linecache].lno
4,508✔
143
    start = linecache[#linecache].pos + 1
4,508✔
144
    col = 1
4,508✔
145
  else
146
    for j = 1, #linecache-1 do
30,885✔
147
      if linecache[j+1].pos >= pos then
30,885✔
148
        lno = linecache[j].lno
2,332✔
149
        col = pos - linecache[j].pos
2,332✔
150
        return lno, col
2,332✔
151
      end
152
    end
153
  end
154
  for i = start, pos do
128,380✔
155
    if string.sub( str, i, i ) == "\n" then
247,744✔
156
      lno = lno + 1
2,440✔
157
      col = 1
2,440✔
158
      linecache[#linecache+1] = { pos = i, lno = lno }
2,440✔
159
      lastpos = i
2,440✔
160
    end
161
    col = col + 1
123,872✔
162
  end
163
  return lno, col
4,508✔
164
end
165

166
local function massage_ast (tree, doc)
167
  -- Sort out pos
168
  if type(tree) == "string" then return tree end
9,420✔
169
  if tree.pos then
6,840✔
170
    tree.lno, tree.col = getline(doc, tree.pos)
13,680✔
171
  end
172
  if tree.id == "document"
6,840✔
173
      or tree.id == "texlike_braced_stuff"
6,840✔
174
      or tree.id == "passthrough_stuff"
6,491✔
175
      or tree.id == "passthrough_braced_stuff"
6,452✔
176
      or tree.id == "passthrough_env_stuff"
6,413✔
177
    then
178
      return massage_ast(tree[1], doc)
461✔
179
  end
180
  if tree.id == "texlike_text"
6,379✔
181
    or tree.id == "passthrough_text"
4,582✔
182
    or tree.id == "passthrough_env_text"
4,582✔
183
    then
184
      return tree[1]
1,831✔
185
  end
186
  for key, val in ipairs(tree) do
13,333✔
187
    if val.id == "texlike_stuff" then
8,785✔
188
      SU.splice(tree, key, key, massage_ast(val, doc))
1,698✔
189
    else
190
      tree[key] = massage_ast(val, doc)
16,438✔
191
    end
192
  end
193
  return tree
4,548✔
194
end
195

196
function inputter:rebuildParser ()
181✔
197
  return epnf.define(self._grammar)
174✔
198
end
199

200
function inputter:parse (doc)
181✔
201
  local parsed = epnf.parsestring(self._parser, doc)[1]
348✔
202
  if not parsed then
174✔
203
    return SU.error("Unable to parse input document to an AST tree")
×
204
  end
205
  resetCache()
174✔
206
  local top = massage_ast(parsed, doc)
174✔
207
  local tree
208
  -- Content not part of a tagged command could either be part of a document
209
  -- fragment or junk (e.g. comments, whitespace) outside of a document tag. We
210
  -- need to either capture the document tag only or decide this is a fragment
211
  -- and wrap it in a document tag.
212
  for _, leaf in ipairs(top) do
181✔
213
    if leaf.command and (leaf.command == "document" or leaf.command == "sile") then
180✔
214
        tree = leaf
173✔
215
        break
173✔
216
    end
217
  end
218
  -- In the event we didn't isolate a top level document tag above, assume this
219
  -- is a fragment and wrap it in one.
220
  if not tree then
174✔
221
    tree = { top, command = "document" }
1✔
222
  end
223
  return { tree }
174✔
224
end
225

226
return inputter
181✔
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