------------------------------------------------------------------------
--  envext.lua - Lua 5.2 compatibility setfenv/getfenv
--
--  Based on compat_env by David Manura (MIT License)
--
--  Created: Wed Oct 01 19:00:57 2014
--  Copyright  2014  Tim Niemueller [www.niemueller.de]
------------------------------------------------------------------------
--  This program is free software; you can redistribute it and/or modify
--  it under the terms of the GNU General Public License as published by
--  the Free Software Foundation; either version 2 of the License, or
--  (at your option) any later version.
--
--  This program is distributed in the hope that it will be useful,
--  but WITHOUT ANY WARRANTY; without even the implied warranty of
--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--  GNU Library General Public License for more details.
--
--  Read the full text in the LICENSE.GPL file in the doc directory.
local _M = {_TYPE = 'module', _NAME = 'envext'}

if _G.setfenv then -- Lua 5.1
    _M.setfenv = _G.setfenv
    _M.getfenv = _G.getfenv
else -- >= Lua 5.2
    -- helper function for `getfenv`/`setfenv`
    local function envlookup(f)
        local name, val
        local up = 0
        local unknown
        repeat
            up = up + 1;
            name, val = debug.getupvalue(f, up)
            if name == '' then unknown = true end
        until name == '_ENV' or name == nil
        if name ~= '_ENV' then
            up = nil
            if unknown then
                error(
                    "upvalues not readable in Lua 5.2 when debug info missing",
                    3)
            end
        end
        return (name == '_ENV') and up, val, unknown
    end

    -- helper function for `getfenv`/`setfenv`
    local function envhelper(f, name)
        if type(f) == 'number' then
            if f < 0 then
                error(
                    ("bad argument #1 to '%s' (level must be non-negative)"):format(
                        name), 3)
            elseif f < 1 then
                error("thread environments unsupported in Lua 5.2", 3) -- [*]
            end
            f = debug.getinfo(f + 2, 'f').func
        elseif type(f) ~= 'function' then
            error(("bad argument #1 to '%s' (number expected, got %s)"):format(
                      type(name, f)), 2)
        end
        return f
    end
    -- [*] might simulate with table keyed by coroutine.running()

    -- 5.1 style `setfenv` implemented in 5.2
    function _M.setfenv(f, t)
        local f = envhelper(f, 'setfenv')
        local up, val, unknown = envlookup(f)
        if up then
            debug.upvaluejoin(f, up, function() return up end, 1) -- unique upval[*]
            debug.setupvalue(f, up, t)
        else
            local what = debug.getinfo(f, 'S').what
            if what ~= 'Lua' and what ~= 'main' then -- not Lua func
                error("'setfenv' cannot change environment of given object", 2)
            end -- else ignore no _ENV upvalue (warning: incompatible with 5.1)
        end
        return f -- invariant: original f ~= 0
    end
    -- [*] http://lua-users.org/lists/lua-l/2010-06/msg00313.html

    -- 5.1 style `getfenv` implemented in 5.2
    function _M.getfenv(f)
        if f == 0 or f == nil then return _G end -- simulated behavior
        local f = envhelper(f, 'setfenv')
        local up, val = envlookup(f)
        if not up then return _G end -- simulated behavior [**]
        return val
    end
    -- [**] possible reasons: no _ENV upvalue, C function
end

return _M
