require("\100\101\098\117\103")local W,h,K,Q=debug.getinfo,error,rawset,rawget local N={}local function y()local h=W(3,"\083")return h and h.what or"\067"end function N.module(W,N,X)local A,k,Y,j,F if X then F=X.__global end if type(N)=="\116\097\098\108\101"then A=getmetatable(N)if A and Q(A,"\095\095\100\101\099\108\097\114\101\100")then return end else N={}end if A==nil then A={}setmetatable(N,A)else k=A.__newindex Y=A.__index j=type(Y)end A.__declared=X or{}A.__newindex=function(W,N,X)if k then k(W,N,X)if Q(W,N)~=nil then return end end if not A.__declared[N]then if F then local W=y()if W~="\109\097\105\110"and W~="\067"then h("\097\115\115\105\103\110 \116\111 \117\110\100\101\099\108\097\114\101\100 \103\108\111\098\097\108 \'"..(N.."\'"),2)end end A.__declared[N]=true end K(W,N,X)end A.__index=function(K,N)if not A.__declared[N]and y()~="\067"then if Y then if j=="\116\097\098\108\101"then local W=Y[N]if W~=nil then return W end else local W=Y(K,N)if W~=nil then return W end end end local Q="\118\097\114\105\097\098\108\101 \'"..(N.."\' \105\115 \110\111\116 \100\101\099\108\097\114\101\100")if W then Q=Q..(" \105\110 \'"..(tostring(W).."\'"))end h(Q,2)end return Q(K,N)end return N end function N.make_all_strict(W)for h,K in pairs(W)do if type(K)=="\116\097\098\108\101"and K~=W then N.module(h,K)end end end function N.closed_module(W,h)local K={}W=W or{}local Q=getmetatable(W)if not Q then Q={}setmetatable(W,Q)end Q.__newindex=function(W,h,Q)K[h]=Q end return N.module(h,K)end if not Q(_G,"\080\069\078\076\073\071\072\084\095\078\079\095\071\076\079\066\065\076\095\083\084\082\073\067\084")then N.module(nil,_G,{_PROMPT=true;_PROMPT2=true;__global=true})end return N
--- Checks uses of undeclared global variables.
-- All global variables must be 'declared' through a regular assignment
-- (even assigning `nil` will do) in a main chunk before being used
-- anywhere or assigned to inside a function. Existing metatables `__newindex` and `__index`
-- metamethods are respected.
--
-- You can set any table to have strict behaviour using `strict.module`. Creating a new
-- module with `strict.closed_module` makes the module immune to monkey-patching, if
-- you don't wish to encourage monkey business.
--
-- If the global `PENLIGHT_NO_GLOBAL_STRICT` is defined, then this module won't make the
-- global environment strict - if you just want to explicitly set table strictness.
--
-- @module pl.strict

require 'debug' -- for Lua 5.2
local getinfo, error, rawset, rawget = debug.getinfo, error, rawset, rawget
local strict = {}

local function what ()
    local d = getinfo(3, "S")
    return d and d.what or "C"
end

--- make an existing table strict.
-- @string[opt] name name of table
-- @tab[opt] mod the table to protect - if `nil` then we'll return a new table
-- @tab[opt] predeclared - table of variables that are to be considered predeclared.
-- @return the given table, or a new table
-- @usage
-- local M = { hello = "world" }
-- strict.module ("Awesome_Module", M, {
--   Lua = true,  -- defines allowed keys
-- })
--
-- assert(M.hello == "world")
-- assert(M.Lua == nil)       -- access allowed, but has no value yet
-- M.Lua = "Rocks"
-- assert(M.Lua == "Rocks")
-- M.not_allowed = "bad boy"  -- throws an error
function strict.module (name,mod,predeclared)
    local mt, old_newindex, old_index, old_index_type, global
    if predeclared then
        global = predeclared.__global
    end
    if type(mod) == 'table' then
        mt = getmetatable(mod)
        if mt and rawget(mt,'__declared') then return end -- already patched...
    else
        mod = {}
    end
    if mt == nil then
        mt = {}
        setmetatable(mod, mt)
    else
        old_newindex = mt.__newindex
        old_index = mt.__index
        old_index_type = type(old_index)
    end
    mt.__declared = predeclared or {}
    mt.__newindex = function(t, n, v)
        if old_newindex then
            old_newindex(t, n, v)
            if rawget(t,n)~=nil then return end
        end
        if not mt.__declared[n] then
            if global then
                local w = what()
                if w ~= "main" and w ~= "C" then
                    error("assign to undeclared global '"..n.."'", 2)
                end
            end
            mt.__declared[n] = true
        end
        rawset(t, n, v)
    end
    mt.__index = function(t,n)
        if not mt.__declared[n] and what() ~= "C" then
            if old_index then
                if old_index_type == "table" then
                    local fallback = old_index[n]
                    if fallback ~= nil then
                        return fallback
                    end
                else
                    local res = old_index(t, n)
                    if res ~= nil then
                        return res
                    end
                end
            end
            local msg = "variable '"..n.."' is not declared"
            if name then
                msg = msg .. " in '"..tostring(name).."'"
            end
            error(msg, 2)
        end
        return rawget(t, n)
    end
    return mod
end

--- make all tables in a table strict.
-- So `strict.make_all_strict(_G)` prevents monkey-patching
-- of any global table
-- @tab T the table containing the tables to protect. Table `T` itself will NOT be protected.
function strict.make_all_strict (T)
    for k,v in pairs(T) do
        if type(v) == 'table' and v ~= T then
            strict.module(k,v)
        end
    end
end

--- make a new module table which is closed to further changes.
-- @tab mod module table
-- @string name module name
function strict.closed_module (mod,name)
    -- No clue to what this is useful for? see tests
    -- Deprecate this and remove???
    local M = {}
    mod = mod or {}
    local mt = getmetatable(mod)
    if not mt then
        mt = {}
        setmetatable(mod,mt)
    end
    mt.__newindex = function(t,k,v)
        M[k] = v
    end
    return strict.module(name,M)
end

if not rawget(_G,'PENLIGHT_NO_GLOBAL_STRICT') then
    strict.module(nil,_G,{_PROMPT=true,_PROMPT2=true,__global=true})
end

return strict
