local ENUM_RUN_TESTS = true
local function _pair(k, v, is_auto)
return { __enum_key = k, __enum_val = v, __enum_auto = is_auto == true }
end
local function _symbol(name)
return function(v)
-- if user wrote NAME (x) then v is provided
if v ~= nil then
return _pair(name, v, false)
end
-- if user wrote NAME with no parentheses, we need a bare value in the list
-- but Lua cannot place a function call-less identifier into a list without evaluating it
-- so we return an auto marker by making the symbol itself evaluate to this marker:
return _pair(name, nil, true)
end
end
local function _build(list)
local enum = {}
local next_auto = 1
for i = 1, #list do
local item = list[i]
if type(item) == "table" and item.__enum_key ~= nil then
local key = item.__enum_key
if item.__enum_auto then
enum[key] = next_auto
next_auto = next_auto + 1
else
local v = item.__enum_val
enum[key] = v
-- C++ style: after explicit value, next implicit continues from v+1
if type(v) == "number" then
next_auto = v + 1
end
end
end
end
return enum
end
local function _stack_contains_func(target_func, max_depth)
max_depth = max_depth or 12
for level = 2, max_depth do
local info = debug.getinfo(level, "f")
if not info then break end
if info.func == target_func then
return true
end
end
return false
end
ENUM = setmetatable({}, {
__call = function(_, enumName)
local prev_mt = getmetatable(_G)
local prev_index = prev_mt and prev_mt.__index or nil
-- function that invoked: ENUM "NAME"
local caller = debug.getinfo(2, "f").func
local caller_co = coroutine.running()
local sym_cache = {}
local function guarded_index(t, k)
-- preserve any prior __index behavior
if type(prev_index) == "function" then
local v = prev_index(t, k)
if v ~= nil then return v end
elseif type(prev_index) == "table" then
local v = prev_index[k]
if v ~= nil then return v end
end
-- real globals
local v = rawget(t, k)
if v ~= nil then return v end
-- only during the same call chain + same coroutine
if coroutine.running() ~= caller_co then
return nil
end
if not _stack_contains_func(caller) then
return nil
end
v = sym_cache[k]
if not v then
-- IMPORTANT: return a value that can appear in the list without parentheses
-- We make the global lookup itself return the AUTO marker table,
-- but we also want parentheses support. So we return a callable table:
local auto_marker = _pair(k, nil, true)
v = setmetatable(auto_marker, {
__call = function(_, arg)
return _pair(k, arg, false)
end
})
sym_cache[k] = v
end
return v
end
local tmp_mt = {}
if prev_mt then
for k, v in pairs(prev_mt) do
tmp_mt[k] = v
end
end
tmp_mt.__index = guarded_index
setmetatable(_G, tmp_mt)
return function(list)
setmetatable(_G, prev_mt)
local enum = _build(list)
rawset(_G, enumName, enum)
return enum
end
end
})
if ENUM_RUN_TESTS then
-- note: use ENUM
local x = ENUM "HOUSING_e" ({
APARTMENT, -- 1
HOUSE, -- 2
MANSION, -- 3
SHOP(10), -- 10
OFFICE, -- 11
WAREHOUSE(20), -- 20
GARAGE, -- 21
TRAILER, -- 22
HIGHENDAPARTMENT, -- 23
})
print(HOUSING_e.APARTMENT, HOUSING_e.HIGHENDAPARTMENT, HOUSING_e.NONEXISTENT)
end
return ENUM