summaryrefslogtreecommitdiff
path: root/indra/newview/scripts/lua/mapargs.lua
blob: 45f5a9c556db2149aac0d2416e7481eb5e6c3968 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
-- Allow a calling function to be passed a mix of positional arguments with
-- keyword arguments. Reference them as fields of a table.
-- Don't use this for a function that can accept a single table argument.
-- mapargs() assumes that a single table argument means its caller was called
-- with f{table constructor} syntax, and maps that table to the specified names.
-- Usage:
-- function f(...)
--     local a = mapargs({'a1', 'a2', 'a3'}, ...)
--     ... a.a1 ... etc.
-- end
-- f(10, 20, 30)     -- a.a1 == 10,  a.a2 == 20,  a.a3 == 30
-- f{10, 20, 30}     -- a.a1 == 10,  a.a2 == 20,  a.a3 == 30
-- f{a3=300, a1=100} -- a.a1 == 100, a.a2 == nil, a.a3 == 300
-- f{1, a3=3}        -- a.a1 == 1,   a.a2 == nil, a.a3 == 3
-- f{a3=3, 1}        -- a.a1 == 1,   a.a2 == nil, a.a3 == 3
local function mapargs(names, ...)
    local args = table.pack(...)
    local posargs = {}
    local keyargs = {}
    -- For a mixed table, no Lua operation will reliably tell you how many
    -- array items it contains, if there are any holes. Track that by hand.
    -- We must be able to handle f(1, nil, 3) calls.
    local maxpos = 0

    -- For convenience, allow passing 'names' as a string 'n0,n1,...'
    if type(names) == 'string' then
        names = string.split(names, ',')
    end

    if not (args.n == 1 and type(args[1]) == 'table') then
        -- If caller passes more than one argument, or if the first argument
        -- is not a table, then it's classic positional function-call syntax:
        -- f(first, second, etc.). In that case we need not bother teasing
        -- apart positional from keyword arguments.
        posargs = args
        maxpos = args.n
    else
        -- Single table argument implies f{mixed} syntax.
        -- Tease apart positional arguments from keyword arguments.
        for k, v in pairs(args[1]) do
            if type(k) == 'number' then
                posargs[k] = v
                maxpos = math.max(maxpos, k)
            else
                if table.find(names, k) == nil then
                    error('unknown keyword argument ' .. tostring(k))
                end
                keyargs[k] = v
            end
        end
    end

    -- keyargs already has keyword arguments in place, just fill in positionals
    args = keyargs
    -- Don't exceed the number of parameter names. Loop explicitly over every
    -- index value instead of using ipairs() so we can support holes (nils) in
    -- posargs.
    for i = 1, math.min(#names, maxpos) do
        if posargs[i] ~= nil then
            -- As in Python, make it illegal to pass an argument both positionally
            -- and by keyword. This implementation permits func(17, first=nil), a
            -- corner case about which I don't particularly care.
            if args[names[i]] ~= nil then
                error(string.format('parameter %s passed both positionally and by keyword',
                                    tostring(names[i])))
            end
            args[names[i]] = posargs[i]
        end
    end
    return args
end

return mapargs