weak_type.lua: a first whack at a small type library
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --[[ | |
| weak_type.lua | |
| A type library for the weak. | |
| Andrew Starks <andrew.starks@trms.com> | |
| Version .01, August, 10, 2013 | |
| This code is hereby placed in the public domain. | |
| --]] | |
| local _type, setmetatable, getmetatable, select, ipairs, print, tostring, error, format = | |
| type, setmetatable, getmetatable, select, ipairs, print, tostring, error, string.format | |
| _ENV = {} | |
| local type = setmetatable({}, { | |
| __call = function(t, ...) | |
| local arg_count = select("#", ...) | |
| if arg_count == 0 then | |
| return "nil", "invalid key" | |
| elseif arg_count == 1 then | |
| return _type(...) | |
| else | |
| return t.check((select(1,...)),select(2,...)) | |
| end | |
| end | |
| }) | |
| function type.tostring(o) | |
| return tostring(type.get(o)) | |
| end | |
| type.types = setmetatable({}, { | |
| __mode = "v", | |
| __type={types = 1, "types", table = true}} | |
| ) | |
| local type_mt = { | |
| __type = {type = 1, "type", table = true}, | |
| __tostring = function(self) | |
| return self[1] | |
| end, | |
| __eq = function(a, b) | |
| type.is_equal(a, b) | |
| end, | |
| __le = function(a, b) | |
| return type.is_contained(a,b) | |
| end | |
| } | |
| local types = type.types | |
| function type.new(t) | |
| --print(_type(t)) | |
| if not t or _type(t) ~= "string" and _type(t) ~= "table" then | |
| error("Expected string or table of type names.") | |
| end | |
| local new_type = {} | |
| for i,v in ipairs( _type(t) == "string" and {t} or t) do | |
| new_type[i] = v | |
| new_type[v] = i | |
| -- print("making",new_type[v],new_type[i]) | |
| end | |
| --make a table entry, but don't index it. | |
| --that way #length points to how many types | |
| --are assigned to it, but not the obvious ones. | |
| --also, tables that are passed in can be done this way too. | |
| new_type.table = true | |
| local t_name = new_type[1] | |
| local i = types[t_name] or #types + 1 | |
| types[t_name] = i | |
| types[i] = new_type | |
| return setmetatable(new_type, type_mt) | |
| end | |
| function type.assign (o, new_type) | |
| if _type(o) ~= "table" then | |
| error("Expected a table as first argument to type.assign. Received %s", _type(o) ) | |
| end | |
| local o_mt = getmetatable(o) or {} | |
| o_mt.__type = new_type ~= nil and | |
| (type.check(new_type, "type") and new_type or type.new(new_type)) or | |
| nil | |
| return setmetatable(o, o_mt) | |
| end | |
| function type.get(o) | |
| if _type(o) ~= "table" then | |
| return _type(o) | |
| else | |
| local o_mt = getmetatable(o) | |
| return o_mt and o_mt.__type or "table" | |
| end | |
| end | |
| function type.is_equal(a, b) | |
| return type.tostring(a) == type.tostring(b) | |
| end | |
| function type.is_contained(a, b) | |
| local a_t, b_t = type(a, "type") and a or type.get(a), type(b, "type") and b or type.get(b) | |
| if _type(a_t) == "string" then | |
| --warning, if somehow a_t is a string and b was passed in by __le, then this would not work. | |
| --because __le works on the <= operator and needs types. | |
| --type does not work with types, but with object that have them. | |
| return type(b, a_t) | |
| elseif _type(b_t) == "string" then | |
| if #a_t > 1 then | |
| return false | |
| else | |
| return a_t[1] == b_t | |
| end | |
| else | |
| local match = true | |
| for i, v in ipairs(a_t) do | |
| if not b_t[v] then | |
| return false | |
| end | |
| end | |
| return true | |
| end | |
| end | |
| function type.contains(a, b) | |
| return is_contained(b, a) | |
| end | |
| function type.check(o, ...) | |
| local search_count = select("#", ...) | |
| local arg_len = select("#", ...) | |
| if arg_len == 0 then | |
| return type.tostring(o) | |
| end | |
| local o_type = type.get(o) | |
| for i = 1, arg_len do | |
| local cur_search = (select(i, ...)) -- t:match( ) | |
| --we'll return the primary type and the matched type. | |
| if _type(o_type) == "table" and o_type[cur_search] or o_type == cur_search then | |
| return _type(o_type) == "table" and o_type[1] or o_type, cur_search | |
| end | |
| end | |
| --didn't find it. | |
| return false | |
| end | |
| --testing | |
| --[[ | |
| local pf = function(...) print(format(...)) end | |
| print(type((function() return end)())) | |
| pf([=[ | |
| Consider our friendly type function: | |
| type("Hello, World?"): %s | |
| ]=], | |
| type("Hello, World?")) | |
| --> string | |
| pf([=[ | |
| We normally check for type equality with this: | |
| type('hello') == 'string' (%s) | |
| ]=], | |
| type('hello') == 'string') | |
| --> string | |
| pf([=[ | |
| What if we could also do this: | |
| type('hello', 'string') (%s) | |
| ]=], | |
| type('hello', 'string') ) | |
| --> string | |
| local my_object = type.assign({}, {"My_Object_Type", "string"}) | |
| pf([=[ | |
| Not a big deal, but this could lead to some nice tricks: | |
| type(my_object, 'string') (%s, %s) | |
| ]=], | |
| type(my_object, 'string')) | |
| -->My_Object_Type, string | |
| print([=[ | |
| In the last example, My_Object_Type is the primary type of the object, | |
| so it is returned first. 'string' was the match, so it's second. | |
| So we see that objects can have multiple types. Let's look at that a bit further... | |
| ]=]) | |
| local obj = type.assign({},{"test", "foo", "bar"}) | |
| local obj1 = type.assign({}, {"test", "foo"}) | |
| local obj2 = type.assign({}, {"test", "foo", "baz"}) | |
| pf([=[ | |
| Containment: | |
| type.is_contained(obj1, obj2): %s | |
| type.is_contained(obj2, obj1): %s | |
| ]=], type.is_contained(obj1, obj2), type.is_contained(obj2, obj1)) | |
| --> true, false | |
| pf([=[ | |
| Containment with __le operator: | |
| type.get(obj2) <= type.get(obj1): %s | |
| type.get(obj1) <= type.get(obj2): %s | |
| ]=], | |
| type.get(obj2) <= type.get(obj1), type.get(obj1) <= type.get(obj2)) | |
| --> false, true | |
| local obj4 = type.assign({}, {"int64", "number"}) | |
| pf([=[ | |
| Use is_contained to see if an object can 'behave' like something, even with native types. | |
| type.is_contained(3, obj4): %s | |
| ]=], | |
| type.is_contained(3, obj4)) | |
| --> | |
| pf([=[ | |
| Note that <= doesn't work directly on the object. | |
| It works on the type (which I guess is an object, but you get the idea.) | |
| Oune could make these operators work, although I don't it's a good idea. | |
| Also, there is no '>=', but it still works: | |
| type.get(obj2) >= type.get(obj1): %s | |
| type.get(obj1) >= type.get(obj2)%s | |
| ]=], | |
| type.get(obj2) >= type.get(obj1), type.get(obj1) >= type.get(obj2) ) | |
| --> true, false | |
| local obj3 = type.assign({}, {"dizzle"}) | |
| pf([=[ | |
| Is equal is there. It doesn't check to see if all sub-types are there. | |
| Only if the tostring method for the type is equal. | |
| This matches the use cases that I can think of: | |
| type.is_equal(obj1, obj2): %s | |
| type.is_equal(obj1, obj3): %s | |
| ]=], | |
| type.is_equal(obj1, obj2), type.is_equal(obj1, obj3)) | |
| --> true false | |
| pf([=[ | |
| Works like normal, too: | |
| type("Hello, word!"): %s | |
| type(2): %s | |
| type(nil): %s | |
| ]=], type("Hello, word!"), type(2), type(nil)) | |
| print([=[ | |
| As demonstrated, assign puts a new type into a table. type.new returns a new type. | |
| Types are saved in a type.types table. Not sure why right now. | |
| Maybe an evil plan will emerge. | |
| type.get returns an object's type object. type. 'tostring' returns the string | |
| representation of the type, whereas type(o) return's o's native type (table). | |
| ]=]) | |
| pf([=[ | |
| Note that type(obj, 'table') returns a truthy value ('%s' in this case). | |
| This is because all objects are tables, so you get that type match for free. | |
| You're welcome. | |
| ]=], | |
| type(obj, 'table') ) | |
| print([=[ | |
| If it isn't abundantly clear, this is a first whack at something useful. | |
| Hopefully it's not too un-Lua-like. I tried to stick to real-world problems | |
| that I've actually run into. | |
| ]=]) | |
| --]] | |
| return type |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment