diff options
Diffstat (limited to 'src/constrain.lua')
| -rw-r--r-- | src/constrain.lua | 109 |
1 files changed, 109 insertions, 0 deletions
diff --git a/src/constrain.lua b/src/constrain.lua new file mode 100644 index 0000000..56f3b6a --- /dev/null +++ b/src/constrain.lua @@ -0,0 +1,109 @@ +--[[ +A function that allows adding constraints on tables. +This works by makeing a shallow copy of the table, +and setting __index and __newindex metamethods of the (now empty) table, +that access the appropriate values of the shallow copy after doing checks. + +constrain(tbl, ("get " | "set ") + "field name", function(tbl, value) + --this function is called with the table ("self") and the + --value that is being set or retrived +end) + +This module is just a function that can add constraints on a table. Use +it like this: + + local constrain = require("constrain") + local my_tbl = {} + constrain(my_tbl,"get my_field",function(self,value) + --Here, self is my_tbl, value will be + --"my_field" + assert(value > 0 and value < 20, "my_value must be between 0 and 20") + end) + --From now on, if my_tbl.my_field gets set to anything outside of (0:20), + --an error will be thrown. + +This function should be totally transparent to the outside. +]] + +local constrained_tables = {} + +return function(tbl,trigger,func) + local is_empty = true + for k,v in pairs(tbl) do + is_empty = false + break; + end + local meta = getmetatable(tbl) + + --This table has never had constrain() called on it before, + --make the shallow copy with hooks and stuff + if constrained_tables[tbl] == nil then + --Copy all the variables into a shallow copy + local shallow_copy = {} + for k,v in pairs(tbl) do + shallow_copy[k] = v + tbl[k] = nil + end + + --Set the shallow copy's metatable to the original table's + --metatable + setmetatable(shallow_copy,meta) + + --Set the original table's metatable to the hookable thing + local t_meta = {} + t_meta.get_hooks = {} + t_meta.set_hooks = {} + t_meta.__index = function(self,key) + local ret = shallow_copy[key] + for _,hook in pairs(t_meta.get_hooks[key] or {}) do + hook(self,ret) + end + return ret + end + t_meta.__newindex = function(self,key,value) + for _,hook in pairs(t_meta.set_hooks[key] or {}) do + hook(self,value) + end + shallow_copy[key] = value + end + t_meta.__pairs = function(self) + return pairs(shallow_copy) + end + t_meta.__len = function(self) + return #shallow_copy + end + t_meta.__call = function(self,...) + return shallow_copy(...) + end + t_meta.__mode = meta and meta.__mode or "" + t_meta.__gc = meta and meta.__gc + t_meta.__metatable = meta + + setmetatable(tbl,t_meta) + constrained_tables[tbl] = t_meta + end + + --By this point, tbl is a "constrainable" table. we can just + --add functios to it's get_hooks and set_hooks to do whatever checking we need + --functions added to get_hooks should be + -- function(self,value) ... end + --functions added to set_hooks should be + -- function(self,value) ... end + -- + local getset,field = string.match(trigger,"(get) (.+)") + if not getset then + getset,field = string.match(trigger,"(set) (.+)") + end + --local getset,field = string.match(trigger,"(get|set) (.+)") + assert(getset,"constrain() must be called with \"get\" or \"set\" as the first word in the pattern") + assert(field,"constrain() must specify a field to trigger on") + if getset == "get" then + local gh = constrained_tables[tbl].get_hooks + gh[field] = gh[field] or {} + table.insert(gh[field],func) + elseif getset == "set" then + local sh = constrained_tables[tbl].set_hooks + sh[field] = sh[field] or {} + table.insert(sh[field],func) + end +end |
