Difference between revisions of "Module:Arguments"
Jump to navigation
Jump to search
memoize nils using a separate nilArgs table; this fixes a bug where nil values were being iterated over with pairs()
>Mr. Stradivarius (fix undefined next() behaviour bug by checking for metatable.donePairs in the __index metamethod; also, format the module so it fits into 80 characters) |
>Mr. Stradivarius (memoize nils using a separate nilArgs table; this fixes a bug where nil values were being iterated over with pairs()) |
||
Line 7: | Line 7: | ||
local arguments = {} | local arguments = {} | ||
-- Generate four different tidyVal functions, so that we don't have to check the | -- Generate four different tidyVal functions, so that we don't have to check the | ||
Line 76: | Line 74: | ||
luaArgs = frame | luaArgs = frame | ||
end | end | ||
-- Set | -- Set the order of precedence of the argument tables. If the variables are | ||
-- | -- nil, nothing will be added to the table, which is how we avoid clashes | ||
-- | -- between the frame/parent args and the Lua args. | ||
local | local argTables = {fargs} | ||
argTables[#argTables + 1] = pargs | |||
argTables[#argTables + 1] = luaArgs | |||
--[[ | --[[ | ||
Line 113: | Line 112: | ||
end | end | ||
end | end | ||
--[[ | |||
-- Set up the args, metaArgs and nilArgs tables. args will be the one | |||
-- accessed from functions, and metaArgs will hold the actual arguments. Nil | |||
-- arguments are memoized in nilArgs, and the metatable connects all of them | |||
-- together. | |||
--]] | |||
local args, metaArgs, nilArgs, metatable = {}, {}, {}, {} | |||
setmetatable(args, metatable) | |||
local function mergeArgs(iterator, tables) | local function mergeArgs(iterator, tables) | ||
Line 124: | Line 132: | ||
for _, t in ipairs(tables) do | for _, t in ipairs(tables) do | ||
for key, val in iterator(t) do | for key, val in iterator(t) do | ||
if metaArgs[key] == nil then | |||
local tidiedVal = tidyVal(key, val) | local tidiedVal = tidyVal(key, val) | ||
if tidiedVal == nil then | if tidiedVal == nil then | ||
nilArgs[key] = true | |||
else | else | ||
metaArgs[key] = tidiedVal | metaArgs[key] = tidiedVal | ||
Line 136: | Line 143: | ||
end | end | ||
end | end | ||
--[[ | --[[ | ||
-- Define metatable behaviour. Arguments are memoized in the metaArgs table, | -- Define metatable behaviour. Arguments are memoized in the metaArgs table, | ||
-- and are only fetched from the argument tables once. | -- and are only fetched from the argument tables once. Fetching arguments | ||
-- | -- from the argument tables is the most resource-intensive step in this | ||
-- Also, we keep a record in the metatable of when pairs and ipairs have | -- module, so we try and avoid it where possible. For this reason, nil | ||
-- arguments are also memoized, in the nilArgs table. Also, we keep a record | |||
-- in the metatable of when pairs and ipairs have been called, so we do not | |||
-- run pairs and ipairs on the argument tables more than once. We also do | |||
-- not run ipairs on fargs and pargs if pairs has already been run, as all | |||
-- the arguments will already have been copied over. | |||
--]] | --]] | ||
metatable.__index = function (t, key) | metatable.__index = function (t, key) | ||
--[[ | |||
-- Fetches an argument when the args table is indexed. First we check | |||
-- to see if the value is memoized, and if not we try and fetch it from | |||
-- the argument tables. When we check memoization, we need to check | |||
-- metaArgs before nilArgs, as both can be non-nil at the same time. | |||
-- If the argument is not present in metaArgs, we also check whether | |||
-- pairs has been run yet. If pairs has already been run, we return nil. | |||
-- This is because all the arguments will have already been copied into | |||
-- metaArgs by the mergeArgs function, meaning that any other arguments | |||
-- must be nil. | |||
--]] | |||
local val = metaArgs[key] | local val = metaArgs[key] | ||
if | if val ~= nil then | ||
return val | |||
elseif metatable.donePairs or nilArgs[key] then | |||
return nil | |||
end | end | ||
for _, argTable in ipairs(argTables) do | for _, argTable in ipairs(argTables) do | ||
local argTableVal = tidyVal(key, argTable[key]) | local argTableVal = tidyVal(key, argTable[key]) | ||
if argTableVal == nil then | if argTableVal == nil then | ||
nilArgs[key] = true | |||
else | else | ||
metaArgs[key] = argTableVal | metaArgs[key] = argTableVal | ||
Line 186: | Line 187: | ||
metatable.__newindex = function (t, key, val) | metatable.__newindex = function (t, key, val) | ||
-- This function is called when a module tries to add a new value to the | |||
-- args table, or tries to change an existing value. | |||
if options.readOnly then | if options.readOnly then | ||
error( | error( | ||
Line 201: | Line 204: | ||
) | ) | ||
elseif val == nil then | elseif val == nil then | ||
metaArgs[key] = | --[[ | ||
-- If the argument is to be overwritten with nil, we need to erase | |||
-- the value in metaArgs, so that __index, __pairs and __ipairs do | |||
-- not use a previous existing value, if present; and we also need | |||
-- to memoize the nil in nilArgs, so that the value isn't looked | |||
-- up in the argument tables if it is accessed again. | |||
--]] | |||
metaArgs[key] = nil | |||
nilArgs[key] = true -- Memoize nils. | |||
else | else | ||
metaArgs[key] = val | metaArgs[key] = val | ||
Line 208: | Line 219: | ||
metatable.__pairs = function () | metatable.__pairs = function () | ||
-- Called when pairs is run on the args table. | |||
if not metatable.donePairs then | if not metatable.donePairs then | ||
mergeArgs(pairs, argTables) | mergeArgs(pairs, argTables) | ||
Line 213: | Line 225: | ||
metatable.doneIpairs = true | metatable.doneIpairs = true | ||
end | end | ||
return | return pairs(metaArgs) | ||
end | end | ||
metatable.__ipairs = function () | metatable.__ipairs = function () | ||
-- Called when ipairs is run on the args table. | |||
if not metatable.doneIpairs then | if not metatable.doneIpairs then | ||
mergeArgs(ipairs, argTables) | mergeArgs(ipairs, argTables) | ||
metatable.doneIpairs = true | metatable.doneIpairs = true | ||
end | end | ||
return | return ipairs(metaArgs) | ||
end | end | ||