• 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

94.67
/lua/pl/seq.lua
1
--- Manipulating iterators as sequences.
2
-- See @{07-functional.md.Sequences|The Guide}
3
--
4
-- Dependencies: `pl.utils`, `pl.types`, `debug`
5
-- @module pl.seq
6

7
local next,assert,pairs,tonumber,type,setmetatable = next,assert,pairs,tonumber,type,setmetatable
12✔
8
local strfind,format = string.find,string.format
12✔
9
local mrandom = math.random
12✔
10
local tsort,tappend = table.sort,table.insert
12✔
11
local io = io
12✔
12
local utils = require 'pl.utils'
12✔
13
local callable = require 'pl.types'.is_callable
12✔
14
local function_arg = utils.function_arg
12✔
15
local assert_arg = utils.assert_arg
12✔
16
local debug = require 'debug'
12✔
17

18
local seq = {}
12✔
19

20
-- given a number, return a function(y) which returns true if y > x
21
-- @param x a number
22
function seq.greater_than(x)
12✔
23
  return function(v)
24
    return tonumber(v) > x
100✔
25
  end
26
end
27

28
-- given a number, returns a function(y) which returns true if y < x
29
-- @param x a number
30
function seq.less_than(x)
12✔
31
  return function(v)
32
    return tonumber(v) < x
100✔
33
  end
34
end
35

36
-- given any value, return a function(y) which returns true if y == x
37
-- @param x a value
38
function seq.equal_to(x)
12✔
39
  if type(x) == "number" then
8✔
40
    return function(v)
41
      return tonumber(v) == x
20✔
42
    end
43
  else
44
    return function(v)
45
      return v == x
20✔
46
    end
47
  end
48
end
49

50
--- given a string, return a function(y) which matches y against the string.
51
-- @param s a string
52
function seq.matching(s)
12✔
53
  return function(v)
54
     return strfind(v,s)
12✔
55
  end
56
end
57

58
local nexti
59

60
--- sequence adaptor for a table.   Note that if any generic function is
61
-- passed a table, it will automatically use seq.list()
62
-- @param t a list-like table
63
-- @usage sum(list(t)) is the sum of all elements of t
64
-- @usage for x in list(t) do...end
65
function seq.list(t)
12✔
66
  assert_arg(1,t,'table')
156✔
67
  if not nexti then
156✔
68
    nexti = ipairs{}
4✔
69
  end
70
  local key,value = 0
156✔
71
  return function()
72
    key,value = nexti(t,key)
604✔
73
    return value
604✔
74
  end
75
end
76

77
--- return the keys of the table.
78
-- @param t an arbitrary table
79
-- @return iterator over keys
80
function seq.keys(t)
12✔
81
  assert_arg(1,t,'table')
4✔
82
  local key
83
  return function()
84
    key = next(t,key)
16✔
85
    return key
16✔
86
  end
87
end
88

89
local list = seq.list
12✔
90
local function default_iter(iter)
91
  if type(iter) == 'table' then return list(iter)
228✔
92
  else return iter end
188✔
93
end
94

95
seq.iter = default_iter
12✔
96

97
--- create an iterator over a numerical range. Like the standard Python function xrange.
98
-- @param start a number
99
-- @param finish a number greater than start
100
function seq.range(start,finish)
12✔
101
  local i = start - 1
4✔
102
  return function()
103
      i = i + 1
20✔
104
      if i > finish then return nil
20✔
105
      else return i end
16✔
106
  end
107
end
108

109
-- count the number of elements in the sequence which satisfy the predicate
110
-- @param iter a sequence
111
-- @param condn a predicate function (must return either true or false)
112
-- @param optional argument to be passed to predicate as second argument.
113
-- @return count
114
function seq.count(iter,condn,arg)
12✔
115
  local i = 0
4✔
116
  seq.foreach(iter,function(val)
8✔
117
        if condn(val,arg) then i = i + 1 end
16✔
118
  end)
119
  return i
4✔
120
end
121

122
--- return the minimum and the maximum value of the sequence.
123
-- @param iter a sequence
124
-- @return minimum value
125
-- @return maximum value
126
function seq.minmax(iter)
12✔
127
  local vmin,vmax = 1e70,-1e70
4✔
128
  for val in default_iter(iter) do
44✔
129
    local v = tonumber(val)
40✔
130
    if v < vmin then vmin = v end
40✔
131
    if v > vmax then vmax = v end
40✔
132
  end
133
  return vmin,vmax
4✔
134
end
135

136
--- return the sum and element count of the sequence.
137
-- @param iter a sequence
138
-- @param fn an optional function to apply to the values
139
function seq.sum(iter,fn)
12✔
140
  local s = 0
4✔
141
  local i = 0
4✔
142
  for val in default_iter(iter) do
24✔
143
    local v
144
    if fn then
20✔
NEW
145
      v = fn(val)
×
146
    else
147
      v = val
20✔
148
    end
149
    s = s + v
20✔
150
    i = i + 1
20✔
151
  end
152
  return s,i
4✔
153
end
154

155
--- create a table from the sequence. (This will make the result a List.)
156
-- @param iter a sequence
157
-- @return a List
158
-- @usage copy(list(ls)) is equal to ls
159
-- @usage copy(list {1,2,3}) == List{1,2,3}
160
function seq.copy(iter)
12✔
161
    local res,k = {},1
112✔
162
    for v in default_iter(iter) do
432✔
163
        res[k] = v
320✔
164
        k = k + 1
320✔
165
    end
166
    setmetatable(res, require('pl.List'))
112✔
167
    return res
112✔
168
end
169

170
--- create a table of pairs from the double-valued sequence.
171
-- @param iter a double-valued sequence
172
-- @param i1 used to capture extra iterator values
173
-- @param i2 as with pairs & ipairs
174
-- @usage copy2(ipairs{10,20,30}) == {{1,10},{2,20},{3,30}}
175
-- @return a list-like table
176
function seq.copy2 (iter,i1,i2)
12✔
177
    local res,k = {},1
160✔
178
    for v1,v2 in iter,i1,i2 do
556✔
179
        res[k] = {v1,v2}
396✔
180
        k = k + 1
396✔
181
    end
182
    return res
160✔
183
end
184

185
--- create a table of 'tuples' from a multi-valued sequence.
186
-- A generalization of copy2 above
187
-- @param iter a multiple-valued sequence
188
-- @return a list-like table
189
function seq.copy_tuples (iter)
12✔
190
    iter = default_iter(iter)
4✔
191
    local res = {}
4✔
192
    local row = {iter()}
4✔
193
    while #row > 0 do
24✔
194
        tappend(res,row)
20✔
195
        row = {iter()}
20✔
196
    end
197
    return res
4✔
198
end
199

200
--- return an iterator of random numbers.
201
-- @param n the length of the sequence
202
-- @param l same as the first optional argument to math.random
203
-- @param u same as the second optional argument to math.random
204
-- @return a sequence
205
function seq.random(n,l,u)
12✔
206
  local rand
207
  assert(type(n) == 'number')
12✔
208
  if u then
12✔
209
     rand = function() return mrandom(l,u) end
32✔
210
  elseif l then
8✔
211
     rand = function() return mrandom(l) end
32✔
212
  else
213
     rand = mrandom
4✔
214
  end
215

216
  return function()
217
     if n == 0 then return nil
96✔
218
     else
219
       n = n - 1
84✔
220
       return rand()
84✔
221
     end
222
  end
223
end
224

225
--- return an iterator to the sorted elements of a sequence.
226
-- @param iter a sequence
227
-- @param comp an optional comparison function (comp(x,y) is true if x < y)
228
function seq.sort(iter,comp)
12✔
229
    local t = seq.copy(iter)
8✔
230
    tsort(t,comp)
8✔
231
    return list(t)
8✔
232
end
233

234
--- return an iterator which returns elements of two sequences.
235
-- @param iter1 a sequence
236
-- @param iter2 a sequence
237
-- @usage for x,y in seq.zip(ls1,ls2) do....end
238
function seq.zip(iter1,iter2)
12✔
239
    iter1 = default_iter(iter1)
4✔
240
    iter2 = default_iter(iter2)
4✔
241
    return function()
242
        return iter1(),iter2()
16✔
243
    end
244
end
245

246
--- Makes a table where the key/values are the values and value counts of the sequence.
247
-- This version works with 'hashable' values like strings and numbers.
248
-- `pl.tablex.count_map` is more general.
249
-- @param iter a sequence
250
-- @return a map-like table
251
-- @return a table
252
-- @see pl.tablex.count_map
253
function seq.count_map(iter)
12✔
254
    local t = {}
4✔
255
    local v
256
    for s in default_iter(iter) do
24✔
257
        v = t[s]
20✔
258
        if v then t[s] = v + 1
20✔
259
        else t[s] = 1 end
12✔
260
    end
261
    return setmetatable(t, require('pl.Map'))
4✔
262
end
263

264
-- given a sequence, return all the unique values in that sequence.
265
-- @param iter a sequence
266
-- @param returns_table true if we return a table, not a sequence
267
-- @return a sequence or a table; defaults to a sequence.
268
function seq.unique(iter,returns_table)
12✔
269
    local t = seq.count_map(iter)
4✔
270
    local res,k = {},1
4✔
271
    for key in pairs(t) do res[k] = key; k = k + 1 end
16✔
272
    table.sort(res)
4✔
273
    if returns_table then
4✔
274
        return res
×
275
    else
276
        return list(res)
4✔
277
    end
278
end
279

280
--- print out a sequence iter with a separator.
281
-- @param iter a sequence
282
-- @param sep the separator (default space)
283
-- @param nfields maximum number of values per line (default 7)
284
-- @param fmt optional format function for each value
285
function seq.printall(iter,sep,nfields,fmt)
12✔
286
  local write = io.write
4✔
287
  if not sep then sep = ' ' end
4✔
288
  if not nfields then
4✔
289
      if sep == '\n' then nfields = 1e30
4✔
290
      else nfields = 7 end
4✔
291
  end
292
  if fmt then
4✔
293
    local fstr = fmt
4✔
294
    fmt = function(v) return format(fstr,v) end
44✔
295
  end
296
  local k = 1
4✔
297
  for v in default_iter(iter) do
44✔
298
    local value = v
40✔
299
    if fmt then value = fmt(v) end
40✔
300
    if k < nfields then
40✔
301
      write(value,sep)
36✔
302
      k = k + 1
36✔
303
    else
304
      write(value,'\n')
4✔
305
      k = 1
4✔
306
    end
307
  end
308
  write '\n'
4✔
309
end
310

311
-- return an iterator running over every element of two sequences (concatenation).
312
-- @param iter1 a sequence
313
-- @param iter2 a sequence
314
function seq.splice(iter1,iter2)
12✔
315
  iter1 = default_iter(iter1)
4✔
316
  iter2 = default_iter(iter2)
4✔
317
  local iter = iter1
4✔
318
  return function()
319
    local ret = iter()
20✔
320
    if ret == nil then
20✔
321
      if iter == iter1 then
8✔
322
        iter = iter2
4✔
323
        return iter()
4✔
324
      else return nil end
4✔
325
   else
326
       return  ret
12✔
327
   end
328
 end
329
end
330

331
--- return a sequence where every element of a sequence has been transformed
332
-- by a function. If you don't supply an argument, then the function will
333
-- receive both values of a double-valued sequence, otherwise behaves rather like
334
-- tablex.map.
335
-- @param fn a function to apply to elements; may take two arguments
336
-- @param iter a sequence of one or two values
337
-- @param arg optional argument to pass to function.
338
function seq.map(fn,iter,arg)
12✔
339
    fn = function_arg(1,fn)
8✔
340
    iter = default_iter(iter)
8✔
341
    return function()
342
        local v1,v2 = iter()
28✔
343
        if v1 == nil then return nil end
28✔
344
        return fn(v1,arg or v2) or false
20✔
345
    end
346
end
347

348
--- filter a sequence using a predicate function.
349
-- @param iter a sequence of one or two values
350
-- @param pred a boolean function; may take two arguments
351
-- @param arg optional argument to pass to function.
352
function seq.filter (iter,pred,arg)
12✔
353
    pred = function_arg(2,pred)
44✔
354
    return function ()
355
        local v1,v2
356
        while true do
357
            v1,v2 = iter()
296✔
358
            if v1 == nil then return nil end
296✔
359
            if pred(v1,arg or v2) then return v1,v2 end
252✔
360
        end
361
    end
362
end
363

364
--- 'reduce' a sequence using a binary function.
365
-- @func fn a function of two arguments
366
-- @param iter a sequence
367
-- @param initval optional initial value
368
-- @usage seq.reduce(operator.add,seq.list{1,2,3,4}) == 10
369
-- @usage seq.reduce('-',{1,2,3,4,5}) == -13
370
function seq.reduce (fn,iter,initval)
12✔
371
   fn = function_arg(1,fn)
28✔
372
   iter = default_iter(iter)
28✔
373
   local val = initval or iter()
28✔
374
   if val == nil then return nil end
28✔
375
   for v in iter do
80✔
376
       val = fn(val,v)
56✔
377
   end
378
   return val
24✔
379
end
380

381
--- take the first n values from the sequence.
382
-- @param iter a sequence of one or two values
383
-- @param n number of items to take
384
-- @return a sequence of at most n items
385
function seq.take (iter,n)
12✔
386
    iter = default_iter(iter)
20✔
387
    return function()
388
        if n < 1 then return end
68✔
389
        local val1,val2 = iter()
52✔
390
        if not val1 then return end
52✔
391
        n = n - 1
48✔
392
        return val1,val2
48✔
393
    end
394
end
395

396
--- skip the first n values of a sequence
397
-- @param iter a sequence of one or more values
398
-- @param n number of items to skip
399
function seq.skip (iter,n)
12✔
400
    n = n or 1
8✔
401
    for i = 1,n do
24✔
402
        if iter() == nil then return list{} end
20✔
403
    end
404
    return iter
4✔
405
end
406

407
--- a sequence with a sequence count and the original value.
408
-- enum(copy(ls)) is a roundabout way of saying ipairs(ls).
409
-- @param iter a single or double valued sequence
410
-- @return sequence of (i,v), i = 1..n and v is from iter.
411
function seq.enum (iter)
12✔
412
    local i = 0
4✔
413
    iter = default_iter(iter)
4✔
414
    return function  ()
415
        local val1,val2 = iter()
24✔
416
        if not val1 then return end
24✔
417
        i = i + 1
20✔
418
        return i,val1,val2
20✔
419
    end
420
end
421

422
--- map using a named method over a sequence.
423
-- @param iter a sequence
424
-- @param name the method name
425
-- @param arg1 optional first extra argument
426
-- @param arg2 optional second extra argument
427
function seq.mapmethod (iter,name,arg1,arg2)
12✔
428
    iter = default_iter(iter)
4✔
429
    return function()
430
        local val = iter()
12✔
431
        if not val then return end
12✔
432
        local fn = val[name]
8✔
433
        if not fn then error(type(val).." does not have method "..name) end
8✔
434
        return fn(val,arg1,arg2)
8✔
435
    end
436
end
437

438
--- a sequence of (last,current) values from another sequence.
439
--  This will return S(i-1),S(i) if given S(i)
440
-- @param iter a sequence
441
function seq.last (iter)
12✔
442
    iter = default_iter(iter)
12✔
443
    local val, l = iter(), nil
12✔
444
    if val == nil then return list{} end
12✔
445
    return function ()
446
        val,l = iter(),val
16✔
447
        if val == nil then return nil end
16✔
448
        return val,l
8✔
449
    end
450
end
451

452
--- call the function on each element of the sequence.
453
-- @param iter a sequence with up to 3 values
454
-- @param fn a function
455
function seq.foreach(iter,fn)
12✔
456
    fn = function_arg(2,fn)
4✔
457
    for i1,i2,i3 in default_iter(iter) do fn(i1,i2,i3) end
20✔
458
end
459

460
---------------------- Sequence Adapters ---------------------
461

462
local SMT
463

464
local function SW (iter,...)
465
    if callable(iter) then
176✔
466
        return setmetatable({iter=iter},SMT)
76✔
467
    else
468
        return iter,...
100✔
469
    end
470
end
471

472

473
-- can't directly look these up in seq because of the wrong argument order...
474
local map,reduce,mapmethod = seq.map, seq.reduce, seq.mapmethod
12✔
475
local overrides = {
12✔
476
    map = function(self,fun,arg)
477
        return map(fun,self,arg)
4✔
478
    end,
479
    reduce = function(self,fun,initval)
480
        return reduce(fun,self,initval)
24✔
481
    end
482
}
483

484
SMT = {
12✔
485
    __index = function (tbl,key)
486
        local fn = overrides[key] or seq[key]
172✔
487
        if fn then
172✔
488
            return function(sw,...) return SW(fn(sw.iter,...)) end
336✔
489
        else
490
            return function(sw,...) return SW(mapmethod(sw.iter,key,...)) end
8✔
491
        end
492
    end,
493
    __call = function (sw)
494
        return sw.iter()
×
495
    end,
496
}
12✔
497

498
setmetatable(seq,{
24✔
499
    __call = function(tbl,iter,extra)
500
        if not callable(iter) then
96✔
501
            if type(iter) == 'table' then iter = seq.list(iter)
76✔
502
            else return iter
×
503
            end
504
        end
505
        if extra then
96✔
506
            return setmetatable({iter=function()
8✔
507
                return iter(extra)
16✔
508
            end},SMT)
7✔
509
        else
510
            return setmetatable({iter=iter},SMT)
92✔
511
        end
512
    end
513
})
514

515
--- create a wrapped iterator over all lines in the file.
516
-- @param f either a filename, file-like object, or 'STDIN' (for standard input)
517
-- @param ... for Lua 5.2 only, optional format specifiers, as in `io.read`.
518
-- @return a sequence wrapper
519
function seq.lines (f,...)
12✔
520
    local iter,obj
521
    if f == 'STDIN' then
4✔
522
        f = io.stdin
×
523
    elseif type(f) == 'string' then
4✔
524
        iter,obj = io.lines(f,...)
×
525
    elseif not f.read then
4✔
526
        error("Pass either a string or a file-like object",2)
×
527
    end
528
    if not iter then
4✔
529
        iter,obj = f:lines(...)
4✔
530
    end
531
    if obj then -- LuaJIT version returns a function operating on a file
4✔
532
        local lines,file = iter,obj
×
533
        iter = function() return lines(file) end
×
534
    end
535
    return SW(iter)
4✔
536
end
537

538
function seq.import ()
12✔
539
    debug.setmetatable(function() end,{
×
540
        __index = function(tbl,key)
541
            local s = overrides[key] or seq[key]
×
542
            if s then return s
×
543
            else
544
                return function(s,...) return seq.mapmethod(s,key,...) end
×
545
            end
546
        end
547
    })
548
end
549

550
return seq
12✔
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