├── test.lua └── typedefine.lua /test.lua: -------------------------------------------------------------------------------- 1 | local td = require "typedefine" 2 | 3 | td.e = td.enum { "XXX", "YYY" } 4 | 5 | td.foo = { 6 | x = td.number, 7 | y = 1, 8 | z = td.enum { "a", "b", "c" }, 9 | obj = td.object, -- a ref to lua object (table/userdata) 10 | a = td.array(td.e), 11 | m = td.map(td.string, td.number), 12 | s = td.struct { -- anonymous struct 13 | alpha = false, 14 | beta = true, 15 | }, 16 | } 17 | 18 | local foo = td.foo { 19 | a = { "XXX", "XXX" }, 20 | m = { 21 | x = 1, 22 | y = 2, 23 | }, 24 | z = "c", 25 | obj = td.foo, 26 | s = { alpha = true }, 27 | } 28 | 29 | assert(foo.x == 0) -- default number is 0 30 | assert(foo.y == 1) -- default 1 31 | assert(foo.z == "c") 32 | assert(foo.a[1] == "XXX") 33 | assert(foo.m.x == 1) 34 | assert(foo.m.y == 2) 35 | assert(foo.obj == td.foo) -- a type 36 | assert(foo.s.alpha == true) 37 | assert(foo.s.beta == true) 38 | 39 | foo.z = "d" -- invalid enum 40 | print(td.foo:verify(foo)) 41 | foo.z = nil 42 | print(td.foo:verify(foo)) 43 | foo.z = "a" 44 | print(td.foo:verify(foo)) 45 | foo.a[1] = nil 46 | print(td.foo:verify(foo)) 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /typedefine.lua: -------------------------------------------------------------------------------- 1 | local supported_type = { 2 | type_map = true, 3 | type_enum = true, 4 | type_array = true, 5 | type_undefined = true, 6 | type_struct = true, 7 | type_internal = true, 8 | type_object = true, 9 | } 10 | 11 | local internal_type = { number = true, string = true, boolean = true } 12 | 13 | local function type_tostring(self) 14 | return self._typename 15 | end 16 | 17 | -------- type internal (number/string/boolean) 18 | 19 | local internal_mt = { __metatable = "type_internal", __tostring = type_tostring }; internal_mt.__index = internal_mt 20 | 21 | local function gen_type(v) 22 | return setmetatable({ _default = v , _typename = type(v) }, internal_mt) 23 | end 24 | 25 | function internal_mt:__call(v) 26 | if v ~= nil then 27 | if type(v) ~= self._typename then 28 | error("type mismatch " .. tostring(v) .. " is not " .. self._typename) 29 | end 30 | return v 31 | else 32 | return self._default 33 | end 34 | end 35 | 36 | function internal_mt:verify(v) 37 | if type(v) == self._typename then 38 | return true 39 | else 40 | return false, "type mismatch " .. tostring(v) .. " is not " .. self._typename 41 | end 42 | end 43 | 44 | -------- type enum 45 | 46 | local enum_mt = { __metatable = "type_enum" , __tostring = type_tostring }; enum_mt.__index = enum_mt 47 | 48 | function enum_mt:__call(v) 49 | if v == nil then 50 | return self._default 51 | else 52 | assert(self[v] == true, "Invalid enum value") 53 | return v 54 | end 55 | end 56 | 57 | function enum_mt:verify(v) 58 | if self[v] == true then 59 | return true 60 | else 61 | return false, "Invalid enum value " .. tostring(v) 62 | end 63 | end 64 | 65 | local function new_enum(enums) 66 | assert(type(enums) == "table", "Invalid enum") 67 | local enum_obj = { _default = enums[1] , _typename = "enum" } 68 | for idx,v in ipairs(enums) do 69 | assert(type(v) == "string" and enum_obj[v] == nil, "Invalid enum value") 70 | enum_obj[v] = true 71 | end 72 | return setmetatable(enum_obj, enum_mt) 73 | end 74 | 75 | ----------- type object 76 | 77 | local object_mt = { __metatable = "type_object" , __tostring = type_tostring }; object_mt.__index = object_mt 78 | 79 | function object_mt:__call(obj) 80 | assert(self:verify(obj)) 81 | return obj 82 | end 83 | 84 | function object_mt:verify(v) 85 | if v == nil then 86 | return true 87 | end 88 | local t = type(v) 89 | if t == "table" or t == "userdata" then 90 | return true 91 | end 92 | 93 | return false, "Not an object " .. tostring(v) 94 | end 95 | 96 | ---------- type array 97 | 98 | local array_mt = { __metatable = "type_array", __tostring = type_tostring }; array_mt.__index = array_mt 99 | 100 | function array_mt:__call(init) 101 | if init == nil then 102 | return {} 103 | else 104 | local array = {} 105 | local t = self._array 106 | for idx, v in ipairs(init) do 107 | assert(t:verify(v)) 108 | array[idx] = t(v) 109 | end 110 | return array 111 | end 112 | end 113 | 114 | function array_mt:verify(obj) 115 | if type(obj) ~= "table" then 116 | return false, "Not an table" 117 | else 118 | local t = self._array 119 | local max = 0 120 | for idx, v in ipairs(obj) do 121 | local ok, err = t:verify(v) 122 | if not ok then 123 | return ok, err 124 | end 125 | max = idx 126 | end 127 | for k in pairs(obj) do 128 | if type(k) ~= "number" then 129 | return false, "Invalid key " .. tostring(k) 130 | end 131 | local nk = math.tointeger(k) 132 | if nk == nil or nk <=0 or nk > max then 133 | return false, "Invalid key " .. tostring(nk) 134 | end 135 | end 136 | return true 137 | end 138 | end 139 | 140 | local function new_array(t) 141 | assert(supported_type[getmetatable(t)], "Need a type for array") 142 | return setmetatable({ _typename = "array of " .. tostring(t) , _array = t }, array_mt) 143 | end 144 | 145 | -------------- type map 146 | local map_mt = { __metatable = "type_map", __tostring = type_tostring }; map_mt.__index = map_mt 147 | 148 | function map_mt:__call(init) 149 | if init == nil then 150 | return {} 151 | else 152 | local map = {} 153 | local keyt = self._key 154 | local valuet = self._value 155 | for k, v in pairs(init) do 156 | assert(keyt:verify(k)) 157 | assert(valuet:verify(v)) 158 | map[keyt(k)] = valuet(v) 159 | end 160 | return map 161 | end 162 | end 163 | 164 | function map_mt:verify(obj) 165 | if type(obj) ~= "table" then 166 | return false, "Not an table" 167 | else 168 | local keyt = self._key 169 | local valuet = self._value 170 | for k,v in pairs(obj) do 171 | local ok, err = keyt:verify(k) 172 | if not ok then 173 | return false, string.format("Invalid key %s : %s", k, err) 174 | end 175 | local ok, err = valuet:verify(v) 176 | if not ok then 177 | return false, string.format("Invalid value %s : %s", k, err) 178 | end 179 | end 180 | return true 181 | end 182 | end 183 | 184 | local function new_map(key, value) 185 | assert(supported_type[getmetatable(key)], "Need a type for key") 186 | assert(supported_type[getmetatable(value)], "Need a type for value") 187 | return setmetatable({ _typename = string.format("map of %s:%s", key, value) , _key = key, _value = value }, map_mt) 188 | end 189 | 190 | --------------------- 191 | 192 | local types = { 193 | enum = new_enum, 194 | array = new_array, 195 | map = new_map, 196 | object = setmetatable({ _typename = "object" }, object_mt), 197 | } 198 | 199 | for _,v in ipairs{0, false, ""} do 200 | types[type(v)] = gen_type(v) 201 | end 202 | 203 | local struct_mt = { __metatable = "type_struct" , __tostring = type_tostring }; struct_mt.__index = struct_mt 204 | 205 | function struct_mt:__call(init) 206 | local obj = {} 207 | local meta = self._types 208 | local default = self._defaults 209 | if init then 210 | for k,type_obj in pairs(meta) do 211 | local v = init[k] 212 | if v == nil then 213 | v = default[k] 214 | end 215 | obj[k] = type_obj(v) 216 | end 217 | for k,v in pairs(init) do 218 | if not meta[k] then 219 | error(tostring(k) .. " is not a valid key") 220 | end 221 | end 222 | else 223 | for k,type_obj in pairs(meta) do 224 | local v = default[k] 225 | obj[k] = type_obj(v) 226 | end 227 | end 228 | return obj 229 | end 230 | 231 | function struct_mt:verify(obj) 232 | local t = self._types 233 | if type(obj) ~= "table" then 234 | return false, "Is not a table" 235 | end 236 | for k,v in pairs(obj) do 237 | local meta = t[k] 238 | if not meta then 239 | return false, "Invalid key : " .. tostring(k) 240 | end 241 | end 242 | for k,meta in pairs(t) do 243 | local v = obj[k] 244 | local ok, err = meta:verify(v) 245 | if not ok then 246 | return false, string.format("Type mismatch : %s should be %s (%s)", k, meta, err) 247 | end 248 | end 249 | return true 250 | end 251 | 252 | local function create_type(proto, t) 253 | t._defaults = {} 254 | t._types = {} 255 | for k,v in pairs(proto) do 256 | t._defaults[k] = v 257 | local vt = type(v) 258 | if internal_type[vt] then 259 | t._types[k] = types[vt] 260 | elseif vt == "table" and supported_type[getmetatable(v)] then 261 | t._types[k] = v 262 | t._defaults[k] = nil 263 | else 264 | error("Unsupport type " .. tostring(k) .. ":" .. tostring(v)) 265 | end 266 | end 267 | return setmetatable(t, struct_mt) 268 | end 269 | 270 | function types.struct(proto) 271 | assert(type(proto) == "table", "Invalid type proto") 272 | return create_type(proto, { _typename = "anonymous struct"}) 273 | end 274 | 275 | local function define_type(_, typename, proto) 276 | local t = rawget(types,typename) 277 | if t == nil then 278 | t = {} 279 | elseif getmetatable(t) == "type_undefined" then 280 | debug.setmetatable(t, nil) 281 | else 282 | error("Redefined type " .. tostring(typename)) 283 | end 284 | assert(type(proto) == "table", "Invalid type proto") 285 | local pt = getmetatable(proto) 286 | if pt == nil then 287 | proto = create_type(proto, t) 288 | elseif not supported_type[pt] then 289 | error("Invalid proto meta " .. pt) 290 | end 291 | proto._typename = typename 292 | types[typename] = proto 293 | end 294 | 295 | local function undefined_error(self) 296 | error(self._typename .. " is undefined") 297 | end 298 | 299 | local undefined_type_mt = { 300 | __call = undefined_error, 301 | verify = undefined_error, 302 | __metatable = "type_undefined", 303 | __tostring = function(self) 304 | return "undefined " .. self._typename 305 | end, 306 | } 307 | 308 | undefined_type_mt.__index = undefined_type_mt 309 | 310 | local function create_undefined_type(_, typename) 311 | local type_obj = setmetatable({ _typename = typename } , undefined_type_mt) 312 | types[typename] = type_obj 313 | return type_obj 314 | end 315 | 316 | setmetatable(types, { __index = create_undefined_type }) 317 | 318 | return setmetatable({}, { 319 | __index = types, 320 | __newindex = define_type, 321 | }) 322 | --------------------------------------------------------------------------------