Module:Arguments/Fallback

From Omniversalis

Documentation for this module may be created at Module:Arguments/Fallback/doc

-- A simple index fallback implementation for tables.
-- Useful for template argument aliasing.
-- Hmm, what about __newindex?

local function makeFallback(args, arg_aliases)
	local oldArgsMeta = getmetatable(args) or {}
	local newArgsMeta = {}
	
	-- Forget about thread-safety.
	-- States kept to avoid strange loops.
	local referencedKeys = {} -- dataType:Set/hashtable-impl
	local attemptDepth = 0    -- useful for "last nil" case
	
	-- Start the new metatable as a copy of the old metatable.
	for k, v in ipairs(oldArgsMeta) do
		newArgsMeta[k] = v
	end

	-- Change the __index metamethod to our implementation.
	-- See https://www.lua.org/pil/13.4.1.html.
	newArgsMeta.__index = function (t, k)
		-- My friend, why are you here again?
		if referencedKeys[k] then
			-- You have to be drunk. Go home.
			return nil
		end
		referencedKeys[k] = true
		
		-- Try the old metamethod first.
		-- Thanks to closures, this whole oldArgsMeta object will stay.
		if oldArgsMeta.__index ~= nil then
			local val = oldArgsMeta.__index(t, k)
			if val ~= nil then
				referencedKeys = {}
				return val
			end
		end
		
		attemptDepth = attemptDepth + 1
		-- Now try use the aliases given.
		for _, v in ipairs(arg_aliases[k] or {}) do
			-- If a working value is found, use it.
			-- Note: mw-argument-specific empty str chk.
			if t[v] ~= nil and t[v] ~= '' then
				referencedKeys = {}
				return t[v]
			end
		end
		attemptDepth = attemptDepth - 1
		
		if attemptDepth == 0 then
			referencedKeys = {}
		end
		return nil
	end
	setmetatable(args, newArgsMeta)
	return args  -- just in case someone wants a return value.
end

return makeFallback

--[[
P.S. Don't blame me for writing these obj-states. Sure, we can use an
accumulator and do some "index with acc" ourselves, but that sounds like too
much work done just to avoid states.

Here is that custom index function in case anyone is curious about it:

	function index_w_acc(t, k, acc)
	    local v = rawget(t, k)
	    if v ~= nil
	        return v
	    else
	        return getmetatable(t).__index(k, acc)
	   end
	end

My guess is that it will certainly be slower than the native t[k] one. Perhaps
not getting the metatable every time will help a bit.
]]--