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

lunarmodules / Penlight / 23939896902

03 Apr 2026 08:30AM UTC coverage: 90.172%. Remained the same
23939896902

push

github

web-flow
fix(sequence): printall `fmt` param documented as format string (#519)

fixes #518

5542 of 6146 relevant lines covered (90.17%)

823.18 hits per line

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

93.24
/lua/pl/luabalanced.lua
1
--- Extract delimited Lua sequences from strings.
2
-- Inspired by Damian Conway's Text::Balanced in Perl. <br/>
3
-- <ul>
4
--   <li>[1] <a href="http://lua-users.org/wiki/LuaBalanced">Lua Wiki Page</a></li>
5
--   <li>[2] http://search.cpan.org/dist/Text-Balanced/lib/Text/Balanced.pm</li>
6
-- </ul> <br/>
7
-- <pre class=example>
8
-- local lb = require "pl.luabalanced"
9
-- --Extract Lua expression starting at position 4.
10
--  print(lb.match_expression("if x^2 + x > 5 then print(x) end", 4))
11
--  --> x^2 + x > 5     16
12
-- --Extract Lua string starting at (default) position 1.
13
-- print(lb.match_string([["test\"123" .. "more"]]))
14
-- --> "test\"123"     12
15
-- </pre>
16
-- (c) 2008, David Manura, Licensed under the same terms as Lua (MIT license).
17
-- @class module
18
-- @name pl.luabalanced
19

20
local M = {}
19✔
21

22
local assert = assert
19✔
23

24
-- map opening brace <-> closing brace.
25
local ends = { ['('] = ')', ['{'] = '}', ['['] = ']' }
19✔
26
local begins = {}; for k,v in pairs(ends) do begins[v] = k end
76✔
27

28

29
-- Match Lua string in string <s> starting at position <pos>.
30
-- Returns <string>, <posnew>, where <string> is the matched
31
-- string (or nil on no match) and <posnew> is the character
32
-- following the match (or <pos> on no match).
33
-- Supports all Lua string syntax: "...", '...', [[...]], [=[...]=], etc.
34
local function match_string(s, pos)
35
  pos = pos or 1
171✔
36
  local posa = pos
171✔
37
  local c = s:sub(pos,pos)
171✔
38
  if c == '"' or c == "'" then
171✔
39
    pos = pos + 1
95✔
40
    while 1 do
25✔
41
      pos = assert(s:find("[" .. c .. "\\]", pos), 'syntax error')
95✔
42
      if s:sub(pos,pos) == c then
120✔
43
        local part = s:sub(posa, pos)
95✔
44
        return part, pos + 1
95✔
45
      else
46
        pos = pos + 2
×
47
      end
48
    end
49
  else
50
    local sc = s:match("^%[(=*)%[", pos)
76✔
51
    if sc then
76✔
52
      local _; _, pos = s:find("%]" .. sc .. "%]", pos)
76✔
53
      assert(pos)
76✔
54
      local part = s:sub(posa, pos)
76✔
55
      return part, pos + 1
76✔
56
    else
57
      return nil, pos
×
58
    end
59
  end
60
end
61
M.match_string = match_string
19✔
62

63

64
-- Match bracketed Lua expression, e.g. "(...)", "{...}", "[...]", "[[...]]",
65
-- [=[...]=], etc.
66
-- Function interface is similar to match_string.
67
local function match_bracketed(s, pos)
68
  pos = pos or 1
494✔
69
  local posa = pos
494✔
70
  local ca = s:sub(pos,pos)
494✔
71
  if not ends[ca] then
494✔
72
    return nil, pos
×
73
  end
74
  local stack = {}
494✔
75
  while 1 do
315✔
76
    pos = s:find('[%(%{%[%)%}%]\"\']', pos)
1,197✔
77
    assert(pos, 'syntax error: unbalanced')
1,197✔
78
    local c = s:sub(pos,pos)
1,197✔
79
    if c == '"' or c == "'" then
1,197✔
80
      local part; part, pos = match_string(s, pos)
48✔
81
      assert(part)
38✔
82
    elseif ends[c] then -- open
1,159✔
83
      local mid, posb
84
      if c == '[' then mid, posb = s:match('^%[(=*)%[()', pos) end
589✔
85
      if mid then
589✔
86
        pos = s:match('%]' .. mid .. '%]()', posb)
19✔
87
        assert(pos, 'syntax error: long string not terminated')
19✔
88
        if #stack == 0 then
19✔
89
          local part = s:sub(posa, pos-1)
×
90
          return part, pos
×
91
        end
92
      else
93
        stack[#stack+1] = c
570✔
94
        pos = pos + 1
570✔
95
      end
96
    else -- close
97
      assert(stack[#stack] == assert(begins[c]), 'syntax error: unbalanced')
570✔
98
      stack[#stack] = nil
570✔
99
      if #stack == 0 then
570✔
100
        local part = s:sub(posa, pos)
494✔
101
        return part, pos+1
494✔
102
      end
103
      pos = pos + 1
76✔
104
    end
105
  end
106
end
107
M.match_bracketed = match_bracketed
19✔
108

109

110
-- Match Lua comment, e.g. "--...\n", "--[[...]]", "--[=[...]=]", etc.
111
-- Function interface is similar to match_string.
112
local function match_comment(s, pos)
113
  pos = pos or 1
76✔
114
  if s:sub(pos, pos+1) ~= '--' then
96✔
115
    return nil, pos
×
116
  end
117
  pos = pos + 2
76✔
118
  local partt, post = match_string(s, pos)
76✔
119
  if partt then
76✔
120
    return '--' .. partt, post
76✔
121
  end
122
  local part; part, pos = s:match('^([^\n]*\n?)()', pos)
×
123
  return '--' .. part, pos
×
124
end
125

126

127
-- Match Lua expression, e.g. "a + b * c[e]".
128
-- Function interface is similar to match_string.
129
local wordop = {['and']=true, ['or']=true, ['not']=true}
19✔
130
local is_compare = {['>']=true, ['<']=true, ['~']=true}
19✔
131
local function match_expression(s, pos)
132
  pos = pos or 1
1,083✔
133
  local _
134
  local posa = pos
1,083✔
135
  local lastident
136
  local poscs, posce
137
  while pos do
2,850✔
138
    local c = s:sub(pos,pos)
2,812✔
139
    if c == '"' or c == "'" or c == '[' and s:find('^[=%[]', pos+1) then
2,812✔
140
      local part; part, pos = match_string(s, pos)
24✔
141
      assert(part, 'syntax error')
19✔
142
    elseif c == '-' and s:sub(pos+1,pos+1) == '-' then
2,803✔
143
      -- note: handle adjacent comments in loop to properly support
144
      -- backtracing (poscs/posce).
145
      poscs = pos
38✔
146
      while s:sub(pos,pos+1) == '--' do
96✔
147
        local part; part, pos = match_comment(s, pos)
48✔
148
        assert(part)
38✔
149
        pos = s:match('^%s*()', pos)
38✔
150
        posce = pos
38✔
151
      end
152
    elseif c == '(' or c == '{' or c == '[' then
2,755✔
153
      _, pos = match_bracketed(s, pos)
144✔
154
    elseif c == '=' and s:sub(pos+1,pos+1) == '=' then
2,656✔
155
      pos = pos + 2  -- skip over two-char op containing '='
38✔
156
    elseif c == '=' and is_compare[s:sub(pos-1,pos-1)] then
2,608✔
157
      pos = pos + 1  -- skip over two-char op containing '='
19✔
158
    elseif c:match'^[%)%}%];,=]' then
2,584✔
159
      local part = s:sub(posa, pos-1)
399✔
160
      return part, pos
399✔
161
    elseif c:match'^[%w_]' then
2,185✔
162
      local newident,newpos = s:match('^([%w_]+)()', pos)
2,185✔
163
      if pos ~= posa and not wordop[newident] then -- non-first ident
2,185✔
164
        local pose = ((posce == pos) and poscs or pos) - 1
1,178✔
165
        while s:match('^%s', pose) do pose = pose - 1 end
1,938✔
166
        local ce = s:sub(pose,pose)
1,178✔
167
        if ce:match'[%)%}\'\"%]]' or
1,178✔
168
           ce:match'[%w_]' and not wordop[lastident]
1,121✔
169
        then
170
          local part = s:sub(posa, pos-1)
646✔
171
          return part, pos
646✔
172
        end
173
      end
174
      lastident, pos = newident, newpos
1,539✔
175
    else
176
      pos = pos + 1
×
177
    end
178
    pos = s:find('[%(%{%[%)%}%]\"\';,=%w_%-]', pos)
1,767✔
179
  end
180
  local part = s:sub(posa, #s)
38✔
181
  return part, #s+1
38✔
182
end
183
M.match_expression = match_expression
19✔
184

185

186
-- Match name list (zero or more names).  E.g. "a,b,c"
187
-- Function interface is similar to match_string,
188
-- but returns array as match.
189
local function match_namelist(s, pos)
190
  pos = pos or 1
703✔
191
  local list = {}
703✔
192
  while 1 do
385✔
193
    local c = #list == 0 and '^' or '^%s*,%s*'
1,463✔
194
    local item, post = s:match(c .. '([%a_][%w_]*)%s*()', pos)
1,463✔
195
    if item then pos = post else break end
1,463✔
196
    list[#list+1] = item
760✔
197
  end
198
  return list, pos
703✔
199
end
200
M.match_namelist = match_namelist
19✔
201

202

203
-- Match expression list (zero or more expressions).  E.g. "a+b,b*c".
204
-- Function interface is similar to match_string,
205
-- but returns array as match.
206
local function match_explist(s, pos)
207
  pos = pos or 1
722✔
208
  local list = {}
722✔
209
  while 1 do
440✔
210
    if #list ~= 0 then
1,672✔
211
      local post = s:match('^%s*,%s*()', pos)
950✔
212
      if post then pos = post else break end
950✔
213
    end
214
    local item; item, pos = match_expression(s, pos)
1,200✔
215
    assert(item, 'syntax error')
950✔
216
    list[#list+1] = item
950✔
217
  end
218
  return list, pos
722✔
219
end
220
M.match_explist = match_explist
19✔
221

222

223
-- Replace snippets of code in Lua code string <s>
224
-- using replacement function f(u,sin) --> sout.
225
-- <u> is the type of snippet ('c' = comment, 's' = string,
226
-- 'e' = any other code).
227
-- Snippet is replaced with <sout> (unless <sout> is nil or false, in
228
-- which case the original snippet is kept)
229
-- This is somewhat analogous to string.gsub .
230
local function gsub(s, f)
231
  local pos = 1
532✔
232
  local posa = 1
532✔
233
  local sret = ''
532✔
234
  while 1 do
160✔
235
    pos = s:find('[%-\'\"%[]', pos)
608✔
236
    if not pos then break end
608✔
237
    if s:match('^%-%-', pos) then
76✔
238
      local exp = s:sub(posa, pos-1)
38✔
239
      if #exp > 0 then sret = sret .. (f('e', exp) or exp) end
48✔
240
      local comment; comment, pos = match_comment(s, pos)
48✔
241
      sret = sret .. (f('c', assert(comment)) or comment)
48✔
242
      posa = pos
38✔
243
    else
244
      local posb = s:find('^[\'\"%[]', pos)
38✔
245
      local str
246
      if posb then str, pos = match_string(s, posb) end
48✔
247
      if str then
38✔
248
        local exp = s:sub(posa, posb-1)
38✔
249
        if #exp > 0 then sret = sret .. (f('e', exp) or exp) end
43✔
250
        sret = sret .. (f('s', str) or str)
48✔
251
        posa = pos
38✔
252
      else
253
        pos = pos + 1
×
254
      end
255
    end
256
  end
257
  local exp = s:sub(posa)
532✔
258
  if #exp > 0 then sret = sret .. (f('e', exp) or exp) end
672✔
259
  return sret
532✔
260
end
261
M.gsub = gsub
19✔
262

263

264
return M
19✔
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