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

lunarmodules / Penlight / 583

11 Aug 2025 02:27PM UTC coverage: 89.269% (+0.4%) from 88.871%
583

Pull #498

appveyor

web-flow
fix(*): some more Lua 5.5 fixes for constant loop variables (#499)
Pull Request #498: fix(*): some Lua 5.5 fixes for constant loop variables

23 of 24 new or added lines in 4 files covered. (95.83%)

79 existing lines in 14 files now uncovered.

5482 of 6141 relevant lines covered (89.27%)

165.45 hits per line

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

89.86
/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 = {}
4✔
21

22
local assert = assert
4✔
23

24
-- map opening brace <-> closing brace.
25
local ends = { ['('] = ')', ['{'] = '}', ['['] = ']' }
4✔
26
local begins = {}; for k,v in pairs(ends) do begins[v] = k end
16✔
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
36✔
36
  local posa = pos
36✔
37
  local c = s:sub(pos,pos)
36✔
38
  if c == '"' or c == "'" then
36✔
39
    pos = pos + 1
20✔
UNCOV
40
    while 1 do
×
41
      pos = assert(s:find("[" .. c .. "\\]", pos), 'syntax error')
20✔
42
      if s:sub(pos,pos) == c then
20✔
43
        local part = s:sub(posa, pos)
20✔
44
        return part, pos + 1
20✔
45
      else
46
        pos = pos + 2
×
47
      end
48
    end
49
  else
50
    local sc = s:match("^%[(=*)%[", pos)
16✔
51
    if sc then
16✔
52
      local _; _, pos = s:find("%]" .. sc .. "%]", pos)
16✔
53
      assert(pos)
16✔
54
      local part = s:sub(posa, pos)
16✔
55
      return part, pos + 1
16✔
56
    else
57
      return nil, pos
×
58
    end
59
  end
60
end
61
M.match_string = match_string
4✔
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
104✔
69
  local posa = pos
104✔
70
  local ca = s:sub(pos,pos)
104✔
71
  if not ends[ca] then
104✔
72
    return nil, pos
×
73
  end
74
  local stack = {}
104✔
UNCOV
75
  while 1 do
×
76
    pos = s:find('[%(%{%[%)%}%]\"\']', pos)
252✔
77
    assert(pos, 'syntax error: unbalanced')
252✔
78
    local c = s:sub(pos,pos)
252✔
79
    if c == '"' or c == "'" then
252✔
80
      local part; part, pos = match_string(s, pos)
8✔
81
      assert(part)
8✔
82
    elseif ends[c] then -- open
244✔
83
      local mid, posb
84
      if c == '[' then mid, posb = s:match('^%[(=*)%[()', pos) end
124✔
85
      if mid then
124✔
86
        pos = s:match('%]' .. mid .. '%]()', posb)
4✔
87
        assert(pos, 'syntax error: long string not terminated')
4✔
88
        if #stack == 0 then
4✔
89
          local part = s:sub(posa, pos-1)
×
90
          return part, pos
×
91
        end
92
      else
93
        stack[#stack+1] = c
120✔
94
        pos = pos + 1
120✔
95
      end
96
    else -- close
97
      assert(stack[#stack] == assert(begins[c]), 'syntax error: unbalanced')
120✔
98
      stack[#stack] = nil
120✔
99
      if #stack == 0 then
120✔
100
        local part = s:sub(posa, pos)
104✔
101
        return part, pos+1
104✔
102
      end
103
      pos = pos + 1
16✔
104
    end
105
  end
106
end
107
M.match_bracketed = match_bracketed
4✔
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
16✔
114
  if s:sub(pos, pos+1) ~= '--' then
16✔
115
    return nil, pos
×
116
  end
117
  pos = pos + 2
16✔
118
  local partt, post = match_string(s, pos)
16✔
119
  if partt then
16✔
120
    return '--' .. partt, post
16✔
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}
4✔
130
local is_compare = {['>']=true, ['<']=true, ['~']=true}
4✔
131
local function match_expression(s, pos)
132
  pos = pos or 1
228✔
133
  local _
134
  local posa = pos
228✔
135
  local lastident
136
  local poscs, posce
137
  while pos do
600✔
138
    local c = s:sub(pos,pos)
592✔
139
    if c == '"' or c == "'" or c == '[' and s:find('^[=%[]', pos+1) then
592✔
140
      local part; part, pos = match_string(s, pos)
4✔
141
      assert(part, 'syntax error')
4✔
142
    elseif c == '-' and s:sub(pos+1,pos+1) == '-' then
588✔
143
      -- note: handle adjacent comments in loop to properly support
144
      -- backtracing (poscs/posce).
145
      poscs = pos
8✔
146
      while s:sub(pos,pos+1) == '--' do
16✔
147
        local part; part, pos = match_comment(s, pos)
8✔
148
        assert(part)
8✔
149
        pos = s:match('^%s*()', pos)
8✔
150
        posce = pos
8✔
151
      end
152
    elseif c == '(' or c == '{' or c == '[' then
580✔
153
      _, pos = match_bracketed(s, pos)
24✔
154
    elseif c == '=' and s:sub(pos+1,pos+1) == '=' then
556✔
155
      pos = pos + 2  -- skip over two-char op containing '='
8✔
156
    elseif c == '=' and is_compare[s:sub(pos-1,pos-1)] then
548✔
157
      pos = pos + 1  -- skip over two-char op containing '='
4✔
158
    elseif c:match'^[%)%}%];,=]' then
544✔
159
      local part = s:sub(posa, pos-1)
84✔
160
      return part, pos
84✔
161
    elseif c:match'^[%w_]' then
460✔
162
      local newident,newpos = s:match('^([%w_]+)()', pos)
460✔
163
      if pos ~= posa and not wordop[newident] then -- non-first ident
460✔
164
        local pose = ((posce == pos) and poscs or pos) - 1
248✔
165
        while s:match('^%s', pose) do pose = pose - 1 end
408✔
166
        local ce = s:sub(pose,pose)
248✔
167
        if ce:match'[%)%}\'\"%]]' or
248✔
168
           ce:match'[%w_]' and not wordop[lastident]
236✔
169
        then
170
          local part = s:sub(posa, pos-1)
136✔
171
          return part, pos
136✔
172
        end
173
      end
174
      lastident, pos = newident, newpos
324✔
175
    else
176
      pos = pos + 1
×
177
    end
178
    pos = s:find('[%(%{%[%)%}%]\"\';,=%w_%-]', pos)
372✔
179
  end
180
  local part = s:sub(posa, #s)
8✔
181
  return part, #s+1
8✔
182
end
183
M.match_expression = match_expression
4✔
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
148✔
191
  local list = {}
148✔
UNCOV
192
  while 1 do
×
193
    local c = #list == 0 and '^' or '^%s*,%s*'
308✔
194
    local item, post = s:match(c .. '([%a_][%w_]*)%s*()', pos)
308✔
195
    if item then pos = post else break end
308✔
196
    list[#list+1] = item
160✔
197
  end
198
  return list, pos
148✔
199
end
200
M.match_namelist = match_namelist
4✔
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
152✔
208
  local list = {}
152✔
UNCOV
209
  while 1 do
×
210
    if #list ~= 0 then
352✔
211
      local post = s:match('^%s*,%s*()', pos)
200✔
212
      if post then pos = post else break end
200✔
213
    end
214
    local item; item, pos = match_expression(s, pos)
200✔
215
    assert(item, 'syntax error')
200✔
216
    list[#list+1] = item
200✔
217
  end
218
  return list, pos
152✔
219
end
220
M.match_explist = match_explist
4✔
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
112✔
232
  local posa = 1
112✔
233
  local sret = ''
112✔
UNCOV
234
  while 1 do
×
235
    pos = s:find('[%-\'\"%[]', pos)
128✔
236
    if not pos then break end
128✔
237
    if s:match('^%-%-', pos) then
16✔
238
      local exp = s:sub(posa, pos-1)
8✔
239
      if #exp > 0 then sret = sret .. (f('e', exp) or exp) end
8✔
240
      local comment; comment, pos = match_comment(s, pos)
8✔
241
      sret = sret .. (f('c', assert(comment)) or comment)
8✔
242
      posa = pos
8✔
243
    else
244
      local posb = s:find('^[\'\"%[]', pos)
8✔
245
      local str
246
      if posb then str, pos = match_string(s, posb) end
8✔
247
      if str then
8✔
248
        local exp = s:sub(posa, posb-1)
8✔
249
        if #exp > 0 then sret = sret .. (f('e', exp) or exp) end
8✔
250
        sret = sret .. (f('s', str) or str)
8✔
251
        posa = pos
8✔
252
      else
253
        pos = pos + 1
×
254
      end
255
    end
256
  end
257
  local exp = s:sub(posa)
112✔
258
  if #exp > 0 then sret = sret .. (f('e', exp) or exp) end
112✔
259
  return sret
112✔
260
end
261
M.gsub = gsub
4✔
262

263

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

© 2025 Coveralls, Inc