lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


2010/8/23 Cuero Bugot <cbugot@sierrawireless.com>:
> I came across a case where I found no short way to write it in Lua. This case could be solved with a "Safe navigation operator" (at least it is the way it is called in Groovy).
>
> The problem happens (for example) when you use multiple level tables to store configurations. For example, you store something like:
>        config.network.server.url = "xxx"
> If the parameter is optional, or if you do not want bad surprise, when you want to use it you need to write:
>        if confg.network and config.network.server and config.network.server.url then
>                dosomething(config.network.server.url)
>        End
>
> This is quite verbose, and it could be nice if it returned nil if one of the level of "config.network.server.url" is nil...
> (See Safe navigation operator in groovy)
>
> So the next step was "Well Lua is wonderful, it'll be easy to implement that 'specific' behavior with metamethods!". But after a little though and tries out, I realized that this use case was not coverable with __index / __newindex, because those metamethods lacks a 'context'.
>
> Does somebody already encountered/solved that use case ?

If you don't mind adding a pair of parenthesis (or quotes) to the end
of the expression, you can get something pretty nice with __call :


local wrapper_mt = {}

local function wrapper(root, path)
    return setmetatable({
        root = root,
        path = path,
    }, wrapper_mt)
end

local function follow(root, path, create)
    for _,elem in ipairs(path) do
        local child = root[elem]
        if not child then
            if create then
                root[elem] = {}
            else
                return
            end
        end
        root = child
    end
    return root
end

function wrapper_mt:__index(k)
    local path = {}
    for _,elem in ipairs(self.path) do
        table.insert(path, elem)
    end
    table.insert(path, k)
    return wrapper(self.root, path)
end

function wrapper_mt:__newindex(k, v)
    local parent = follow(self.root, self.path, true)
    parent[k] = v
end

function wrapper_mt:__call(...)
    local path = {}
    for _,elem in ipairs(self.path) do
        table.insert(path, elem)
    end
    for _,elem in ipairs{...} do
        table.insert(path, elem)
    end
    return follow(self.root, path, false)
end

function wrap(t)
    return wrapper(t, {})
end


if ...=='test' then
    local a = { b = { c = 42 } }

    a = wrap(a)

    assert(pcall(function() return a.c() end))
    assert(pcall(function() return a.b.b() end))
    assert(pcall(function() return a.c.c() end))
    assert(pcall(function() return a.c.c.D.e.f() end))
    assert(pcall(function() return a.c'c' end))
    assert(pcall(function() return a.c.c.D.e'f' end))
    assert(pcall(function() return a('c', 'c') end))
    assert(pcall(function() return a('c', 'c', 'D', 'e', 'f') end))

    assert( nil == a.c() )
    assert( nil == a.b.b() )
    assert( nil == a.c.c() )
    assert( nil == a.c.c() )
    assert( nil == a.c.c.D.e.f() )
    assert( nil == a.c'c' )
    assert( nil == a.c.c.D.e'f' )
    assert( nil == a('c', 'c') )
    assert( nil == a('c', 'c', 'D', 'e', 'f') )

    assert(pcall(function() return a.b.c() end))
    assert(pcall(function() return a.b'c' end))
    assert(pcall(function() return a('b', 'c') end))

    assert( 42 == a.b.c() )
    assert( 42 == a.b'c' )
    assert( 42 == a('b', 'c') )

    print("all tests passed successfully")
end