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

lunarmodules / Penlight / 589

29 Dec 2025 04:27PM UTC coverage: 89.278% (+0.4%) from 88.871%
589

Pull #503

appveyor

web-flow
chore(ci): Update Lua and LuaRocks versions for Lua 5.5
Pull Request #503: chore(ci): Update Lua and LuaRocks versions for Lua 5.5

5479 of 6137 relevant lines covered (89.28%)

165.5 hits per line

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

87.37
/lua/pl/func.lua
1
--- Functional helpers like composition, binding and placeholder expressions.
2
-- Placeholder expressions are useful for short anonymous functions, and were
3
-- inspired by the Boost Lambda library.
4
--
5
--    > utils.import 'pl.func'
6
--    > ls = List{10,20,30}
7
--    > = ls:map(_1+1)
8
--    {11,21,31}
9
--
10
-- They can also be used to _bind_ particular arguments of a function.
11
--
12
--    > p = bind(print,'start>',_0)
13
--    > p(10,20,30)
14
--    > start>   10   20  30
15
--
16
-- See @{07-functional.md.Creating_Functions_from_Functions|the Guide}
17
--
18
-- Dependencies: `pl.utils`, `pl.tablex`
19
-- @module pl.func
20
local type,setmetatable,getmetatable,rawset = type,setmetatable,getmetatable,rawset
12✔
21
local concat,append = table.concat,table.insert
12✔
22
local tostring = tostring
12✔
23
local utils = require 'pl.utils'
12✔
24
local pairs,rawget,unpack,pack = pairs,rawget,utils.unpack,utils.pack
12✔
25
local tablex = require 'pl.tablex'
12✔
26
local map = tablex.map
12✔
27
local _DEBUG = rawget(_G,'_DEBUG')
12✔
28
local assert_arg = utils.assert_arg
12✔
29

30
local func = {}
12✔
31

32
-- metatable for Placeholder Expressions (PE)
33
local _PEMT = {}
12✔
34

35
local function P (t)
36
    setmetatable(t,_PEMT)
580✔
37
    return t
580✔
38
end
39

40
func.PE = P
12✔
41

42
local function isPE (obj)
43
    return getmetatable(obj) == _PEMT
3,512✔
44
end
45

46
func.isPE = isPE
12✔
47

48
-- construct a placeholder variable (e.g _1 and _2)
49
local function PH (idx)
50
    return P {op='X',repr='_'..idx, index=idx}
60✔
51
end
52

53
-- construct a constant placeholder variable (e.g _C1 and _C2)
54
local function CPH (idx)
55
    return P {op='X',repr='_C'..idx, index=idx}
56✔
56
end
57

58
func._1,func._2,func._3,func._4,func._5 = PH(1),PH(2),PH(3),PH(4),PH(5)
12✔
59
func._0 = P{op='X',repr='...',index=0}
12✔
60

61
function func.Var (name)
12✔
62
    local ls = utils.split(name,'[%s,]+')
12✔
63
    local res = {}
12✔
64
    for i = 1, #ls do
24✔
65
        append(res,P{op='X',repr=ls[i],index=0})
12✔
66
    end
67
    return unpack(res)
12✔
68
end
69

70
function func._ (value)
12✔
71
    return P{op='X',repr=value,index='wrap'}
×
72
end
73

74
local repr
75

76
func.Nil = func.Var 'nil'
12✔
77

78
function _PEMT.__index(obj,key)
12✔
79
    return P{op='[]',obj,key}
16✔
80
end
81

82
function _PEMT.__call(fun,...)
12✔
83
    return P{op='()',fun,...}
208✔
84
end
85

86
function _PEMT.__tostring (e)
12✔
87
    return repr(e)
×
88
end
89

90
function _PEMT.__unm(arg)
12✔
91
    return P{op='unm',arg}
32✔
92
end
93

94
function func.Not (arg)
12✔
95
    return P{op='not',arg}
8✔
96
end
97

98
function func.Len (arg)
12✔
99
    return P{op='#',arg}
4✔
100
end
101

102

103
local function binreg(context,t)
104
    for name,op in pairs(t) do
216✔
105
        rawset(context,name,function(x,y)
360✔
106
            return P{op=op,x,y}
172✔
107
        end)
108
    end
109
end
110

111
local function import_name (name,fun,context)
112
    rawset(context,name,function(...)
×
113
        return P{op='()',fun,...}
×
114
    end)
115
end
116

117
local imported_functions = {}
12✔
118

119
local function is_global_table (n)
120
    return type(_G[n]) == 'table'
×
121
end
122

123
--- wrap a table of functions. This makes them available for use in
124
-- placeholder expressions.
125
-- @string tname a table name
126
-- @tab context context to put results, defaults to environment of caller
127
function func.import(tname,context)
12✔
128
    assert_arg(1,tname,'string',is_global_table,'arg# 1: not a name of a global table')
×
129
    local t = _G[tname]
×
130
    context = context or _G
×
131
    for name,fun in pairs(t) do
×
132
        import_name(name,fun,context)
×
133
        imported_functions[fun] = name
×
134
    end
135
end
136

137
--- register a function for use in placeholder expressions.
138
-- @func fun a function
139
-- @string[opt] name an optional name
140
-- @return a placeholder functiond
141
function func.register (fun,name)
12✔
142
    assert_arg(1,fun,'function')
×
143
    if name then
×
144
        assert_arg(2,name,'string')
×
145
        imported_functions[fun] = name
×
146
    end
147
    return function(...)
148
        return P{op='()',fun,...}
×
149
    end
150
end
151

152
function func.lookup_imported_name (fun)
12✔
153
    return imported_functions[fun]
×
154
end
155

156
local function _arg(...) return ... end
12✔
157

158
function func.Args (...)
12✔
159
    return P{op='()',_arg,...}
×
160
end
161

162
-- binary operators with their precedences (see Lua manual)
163
-- precedences might be incremented by one before use depending on
164
-- left- or right-associativity, space them out
165
local binary_operators = {
12✔
166
    ['or'] = 0,
12✔
167
    ['and'] = 2,
12✔
168
    ['=='] = 4, ['~='] = 4, ['<'] = 4, ['>'] = 4,  ['<='] = 4,   ['>='] = 4,
12✔
169
    ['..'] = 6,
12✔
170
    ['+'] = 8, ['-'] = 8,
12✔
171
    ['*'] = 10, ['/'] = 10, ['%'] = 10,
12✔
172
    ['^'] = 14
12✔
173
}
174

175
-- unary operators with their precedences
176
local unary_operators = {
12✔
177
    ['not'] = 12, ['#'] = 12, ['unm'] = 12
12✔
178
}
179

180
-- comparisons (as prefix functions)
181
binreg (func,{And='and',Or='or',Eq='==',Lt='<',Gt='>',Le='<=',Ge='>='})
12✔
182

183
-- standard binary operators (as metamethods)
184
binreg (_PEMT,{__add='+',__sub='-',__mul='*',__div='/',__mod='%',__pow='^',__concat='..'})
12✔
185

186
binreg (_PEMT,{__eq='=='})
12✔
187

188
--- all elements of a table except the first.
189
-- @tab ls a list-like table.
190
function func.tail (ls)
12✔
191
    assert_arg(1,ls,'table')
208✔
192
    local res = {}
208✔
193
    for i = 2,#ls do
240✔
194
        append(res,ls[i])
32✔
195
    end
196
    return res
208✔
197
end
198

199
--- create a string representation of a placeholder expression.
200
-- @param e a placeholder expression
201
-- @param lastpred not used
202
function repr (e,lastpred)
9✔
203
    local tail = func.tail
2,104✔
204
    if isPE(e) then
2,104✔
205
        local pred = binary_operators[e.op] or unary_operators[e.op]
800✔
206
        if pred then
800✔
207
            -- binary or unary operator
208
            local s
209
            if binary_operators[e.op] then
216✔
210
                local left_pred = pred
172✔
211
                local right_pred = pred
172✔
212
                if e.op == '..' or e.op == '^' then
172✔
213
                    left_pred = left_pred + 1
64✔
214
                else
215
                    right_pred = right_pred + 1
108✔
216
                end
217
                local left_arg = repr(e[1], left_pred)
172✔
218
                local right_arg = repr(e[2], right_pred)
172✔
219
                s = left_arg..' '..e.op..' '..right_arg
172✔
220
            else
221
                local op = e.op == 'unm' and '-' or e.op
44✔
222
                s = op..' '..repr(e[1], pred)
44✔
223
            end
224
            if lastpred and lastpred > pred then
216✔
225
                s = '('..s..')'
24✔
226
            end
227
            return s
216✔
228
        else -- either postfix, or a placeholder
229
            local ls = map(repr,e)
584✔
230
            if e.op == '[]' then
584✔
231
                return ls[1]..'['..ls[2]..']'
16✔
232
            elseif e.op == '()' then
568✔
233
                local fn
234
                if ls[1] ~= nil then -- was _args, undeclared!
208✔
235
                    fn = ls[1]
208✔
236
                else
237
                    fn = ''
×
238
                end
239
                return fn..'('..concat(tail(ls),',')..')'
208✔
240
            else
241
                return e.repr
360✔
242
            end
243
        end
244
    elseif type(e) == 'string' then
1,304✔
245
        return '"'..e..'"'
944✔
246
    elseif type(e) == 'function' then
360✔
247
        local name = func.lookup_imported_name(e)
×
248
        if name then return name else return tostring(e) end
×
249
    else
250
        return tostring(e) --should not really get here!
360✔
251
    end
252
end
253
func.repr = repr
12✔
254

255
-- collect all the non-PE values in this PE into vlist, and replace each occurrence
256
-- with a constant PH (_C1, etc). Return the maximum placeholder index found.
257
local collect_values
258
function collect_values (e,vlist)
9✔
259
    if isPE(e) then
732✔
260
        if e.op ~= 'X' then
732✔
261
            local m = 0
436✔
262
            for i = 1,#e do
1,088✔
263
                local subx = e[i]
652✔
264
                local pe = isPE(subx)
652✔
265
                if pe then
652✔
266
                    if subx.op == 'X' and subx.index == 'wrap' then
596✔
267
                        subx = subx.repr
×
268
                        pe = false
×
269
                    else
270
                        m = math.max(m,collect_values(subx,vlist))
596✔
271
                    end
272
                end
273
                if not pe then
652✔
274
                    append(vlist,subx)
56✔
275
                    e[i] = CPH(#vlist)
56✔
276
                end
277
            end
278
            return m
436✔
279
        else -- was a placeholder, it has an index...
280
            return e.index
296✔
281
        end
282
    else -- plain value has no placeholder dependence
283
        return 0
×
284
    end
285
end
286
func.collect_values = collect_values
12✔
287

288
--- instantiate a PE into an actual function. First we find the largest placeholder used,
289
-- e.g. _2; from this a list of the formal parameters can be build. Then we collect and replace
290
-- any non-PE values from the PE, and build up a constant binding list.
291
-- Finally, the expression can be compiled, and e.__PE_function is set.
292
-- @param e a placeholder expression
293
-- @return a function
294
function func.instantiate (e)
12✔
295
    local consts,values,parms = {},{},{}
52✔
296
    local rep, err, fun
297
    local n = func.collect_values(e,values)
52✔
298
    for i = 1,#values do
100✔
299
        append(consts,'_C'..i)
48✔
300
        if _DEBUG then print(i,values[i]) end
48✔
301
    end
302
    for i =1,n do
128✔
303
        append(parms,'_'..i)
76✔
304
    end
305
    consts = concat(consts,',')
52✔
306
    parms = concat(parms,',')
52✔
307
    rep = repr(e)
52✔
308
    local fstr = ('return function(%s) return function(%s) return %s end end'):format(consts,parms,rep)
52✔
309
    if _DEBUG then print(fstr) end
52✔
310
    fun,err = utils.load(fstr,'fun')
52✔
311
    if not fun then return nil,err end
52✔
312
    fun = fun()  -- get wrapper
52✔
313
    fun = fun(unpack(values)) -- call wrapper (values could be empty)
52✔
314
    e.__PE_function = fun
52✔
315
    return fun
52✔
316
end
317

318
--- instantiate a PE unless it has already been done.
319
-- @param e a placeholder expression
320
-- @return the function
321
function func.I(e)
12✔
322
    if rawget(e,'__PE_function')  then
36✔
323
        return e.__PE_function
×
324
    else return func.instantiate(e)
36✔
325
    end
326
end
327

328
utils.add_function_factory(_PEMT,func.I)
12✔
329

330
--- bind the first parameter of the function to a value.
331
-- @function func.bind1
332
-- @func fn a function of one or more arguments
333
-- @param p a value
334
-- @return a function of one less argument
335
-- @usage (bind1(math.max,10))(20) == math.max(10,20)
336
func.bind1 = utils.bind1
12✔
337
func.curry = func.bind1
12✔
338

339
--- create a function which chains multiple functions.
340
-- @func f a function of at least one argument
341
-- @func g a function of at least one argument
342
-- @param ... additional functions to compose
343
-- @return a function
344
-- @usage printf = compose(io.write, string.format)
345
-- @usage printf = compose(io.write, string.lower, string.format)
346
function func.compose (...)
12✔
347
    local args = pack(...)
24✔
348
    return tablex.reduce(function(f, g)
48✔
349
      return function(...)
350
        return f(g(...))
24✔
351
      end
352
    end, args)
24✔
353
end
354

355
--- bind the arguments of a function to given values.
356
-- `bind(fn,v,_2)` is equivalent to `bind1(fn,v)`.
357
-- @func fn a function of at least one argument
358
-- @param ... values or placeholder variables
359
-- @return a function
360
-- @usage (bind(f,_1,a))(b) == f(a,b)
361
-- @usage (bind(f,_2,_1))(a,b) == f(b,a)
362
function func.bind(fn,...)
12✔
363
    local args = pack(...)
8✔
364
    local holders,parms,bvalues,values = {},{},{'fn'},{}
8✔
365
    local nv,maxplace,varargs = 1,0,false
8✔
366
    for i = 1,args.n do
32✔
367
        local a = args[i]
24✔
368
        if isPE(a) and a.op == 'X' then
24✔
369
            append(holders,a.repr)
16✔
370
            maxplace = math.max(maxplace,a.index)
16✔
371
            if a.index == 0 then varargs = true end
16✔
372
        else
373
            local v = '_v'..nv
8✔
374
            append(bvalues,v)
8✔
375
            append(holders,v)
8✔
376
            append(values,a)
8✔
377
            nv = nv + 1
8✔
378
        end
379
    end
380
    for np = 1,maxplace do
24✔
381
        append(parms,'_'..np)
16✔
382
    end
383
    if varargs then append(parms,'...') end
8✔
384
    bvalues = concat(bvalues,',')
8✔
385
    parms = concat(parms,',')
8✔
386
    holders = concat(holders,',')
8✔
387
    local fstr = ([[
6✔
388
return function (%s)
389
    return function(%s) return fn(%s) end
390
end
391
]]):format(bvalues,parms,holders)
8✔
392
    if _DEBUG then print(fstr) end
8✔
393
    local res = utils.load(fstr)
8✔
394
    res = res()
8✔
395
    return res(fn,unpack(values))
8✔
396
end
397

398
return func
12✔
399

400

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