Tutorial; Lua Tag Methods - HWRM/KarosGraveyard GitHub Wiki
Tag Methods
Lua 4's tag sytem is fairly arcane, and the manual is (in my opinion), really not helpful at all.
Tag methods share a ton in common with Lua 5.x's concept of metatable
s, and are in fact a precursor to that system.
💡 The usage of tag methods is to define some 'custom' behavior for a table variable.
Tags
'Tags' just bind some callback to an action which may happen to a table. Such actions include regular stuff:
- any time someone tries to access an index on the table
- any time some indexes' value is updated or changed
But can allow you implement extended behaviors for a table, such as
- allowing it to be called like a function
- allowing it to be used with maths operators i.e
+
,%
- allowing it to be treated as a string
Tag Methods
A 'tag method' then, is just any callback function ('method') you like, being bound to some operation which may happen to a given table.
The basic flow is:
- A new tag is created with
newtag
- A method is associated with this tag and an event/operation type, using
settagmethod
- This tag can be applied to one or more tables using
settag
Each table may have at most one tag, and each tag may have only one method per event, i.e you can't have multiple hooks invoked for a "gettable"
event on the same table.
Examples
A basic example might be to print something to a log whenever someone accesses anything on a table:
-- some table...
local my_important_table = {};
-- the new tag
local logging_tag = newtag();
-- the callback we want to invoke on whatever action
local logImportantTableAccess = function (tbl, key)
print("Someone tried to access the important table!");
print("They tried accessing key: [" .. tostring(key) .. "]");
-- here we need to use `rawget`, which does not trigger tag methods - otherwise we would trigger THIS method on loop
local val = rawget(tbl, key) or "nil";
print("The value at [" .. tostring(key) .. "] is " .. tostring(val));
return val; -- important to pass the value through unless nobody can access values on the table
end
-- now we associate the above callback with the `"gettable"` hook on the tag, which fires whenever the table is indexed:
settagmethod(logging_tag, "gettable", logImportantTableAccess);
-- and finally, we associate this tag with the table:
settag(my_important_table, logging_tag);
An example, where we 'freeze' an input table, meaning it can no longer be modified:
--- Freezes a table, meaning it cannot be altered (except by `rawset`).
---
---@param tbl table
---@return table
function freeze(tbl)
local t = newtag();
local hook = function (tbl, key, value)
print("Cannot modify frozen table `" .. tostring(tbl) .. "`");
print("\tOccured while setting key " .. tostring(key) .. " to value " .. value);
print("\tExisting key/value: `" .. tostring(key) .. "`: " .. tostring(rawget(tbl, key)));
end
settagmethod(t, "settable", hook);
settag(tbl, t);
return tbl;
end
local t = {
x = 10
};
print(t.x); -- 10
t.x = 20; -- ok
print(t.x); -- 20
freeze(t);
t.x = 30; -- warning emitted, no alteration to 'x'
print(t.x); -- 20
Here we 'hijack' the "settable"
operation for the table. Instead of the default behavior, we define our own totally custom behavior (the hook
function).
Another example, where we may print a table value any time a script tries to access it:
--- Logs (prints) any access to the given table's values
---
---@param tbl table
---@return table
function logAccess(tbl)
local t = newtag();
local hook = function (tbl, key)
local value = rawget(tbl, key); -- need to use rawget to avoid triggering this very tag infinitely
print("Access on tbl " .. tostring(tbl));
print("\t[" .. tostring(key) .. "] = " .. tostring(value));
end
settagmethod(t, "gettable", hook);
settag(tbl, t);
return tbl;
end
local t = {
x = 10
};
local a = t.x; -- no logging performed on the access to 'x'
logAccess(t);
local b = t.x; -- prints the table's address & the key-val pair
A full list of available tag methods (i.e "getttable"
and "settable"
) is in the manual.