├── .gitignore ├── LICENSE ├── README.md ├── Snakefile ├── dmc_lua ├── lua_class.lua ├── lua_events_mix.lua └── lua_objects.lua └── spec └── lua_objects_spec.lua /.gitignore: -------------------------------------------------------------------------------- 1 | .snakemake 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 David McCuskey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## lua-objects ## 2 | 3 | Advanced object oriented module for Lua (OOP) 4 | 5 | This single-file module started its life as [dmc-objects](https://github.com/dmccuskey/dmc-objects) and was used to create mobile apps built with the Corona SDK. It was later refactored into two files `lua_objects.lua` & `dmc_objects.lua` so that pure-Lua environments could benefit, too (eg, [lua-corovel](https://github.com/dmccuskey/lua-corovel)). 6 | 7 | This power-duo have been used to create relatively complex Lua mobile apps (~60k LOC), clients for websockets and the WAMP-protocol, and countless others. 8 | 9 | 10 | ### Features ### 11 | 12 | * **_new!_** customizable methods and names for constructor/destructor 13 | * **_new!_** multiple inheritance (all way to top level) 14 | * **_new!_** handles ambiguities of inherited attributes 15 | * **_new!_** advanced support for mixins 16 | * getters and setters 17 | * correctly handles missing methods on super classes 18 | * optimization (copy methods from super classes) 19 | * **_new!_** unit tested 20 | 21 | 22 | ### Examples ### 23 | 24 | #### A Simple Custom Class #### 25 | 26 | Here's a quick example showing how to create a custom class. 27 | 28 | ```lua 29 | --== Import module 30 | 31 | local Objects = require 'dmc_lua.lua_objects' 32 | 33 | 34 | --== Create a class 35 | 36 | local AccountClass = newClass() 37 | 38 | 39 | --== Class Properties 40 | 41 | AccountClass.DEFAULT_PATH = '/path/dir/' 42 | AccountClass.DEFAULT_AMOUNT = 100.45 43 | 44 | 45 | --== Class constructor/destructor 46 | 47 | -- called from obj:new() 48 | function AccountClass:__new__( params ) 49 | params = params or {} 50 | self._secure = params.secure or true 51 | self._amount = params.amount or self.DEFAULT_AMOUNT 52 | end 53 | 54 | -- called from obj:destroy() 55 | function AccountClass:__destroy__() 56 | self._secure = nil 57 | self._amount = nil 58 | end 59 | 60 | 61 | --== Class getters/setters 62 | 63 | function AccountClass.__setters:secure( value ) 64 | assert( type(value)=='boolean', "property 'secure' must be boolean" ) 65 | self._secure = value 66 | end 67 | function AccountClass.__getters:secure() 68 | return self._secure 69 | end 70 | 71 | 72 | --== Class methods 73 | 74 | function AccountClass:deposit( amount ) 75 | self._amount = self._amount + amount 76 | self:dispatchEvent( AccountClass.AMOUNT_CHANGED_EVENT, { amount=self._amount } ) 77 | end 78 | function AccountClass:withdraw( amount ) 79 | self._amount = self._amount - amount 80 | end 81 | 82 | ``` 83 | 84 | 85 | #### Create Class Instance #### 86 | 87 | And here's how to work with that class. 88 | 89 | ```lua 90 | 91 | -- Create instance 92 | 93 | local account = AccountClass:new{ secure=true, amount=94.32 } 94 | 95 | -- Call methods 96 | 97 | account:deposit( 32.12 ) 98 | account:withdraw( 50.00 ) 99 | 100 | 101 | -- optimize method lookup 102 | 103 | obj:optimize() 104 | obj:deoptimize() 105 | 106 | 107 | -- Check class/object types 108 | 109 | assert( AccountClass.is_class == true ), "AccountClass is a class" ) 110 | assert( AccountClass.is_instance == false ), "AccountClass is not an instance" ) 111 | 112 | assert( obj.is_class == false, "an object instance is not a class" ) 113 | assert( obj.is_instance == true, "an objects is an instance of a class" ) 114 | assert( obj:isa( AccountClass ) == true, "this obj is an instance of AccountClass" ) 115 | 116 | 117 | -- Destroy instance 118 | 119 | account:destroy() 120 | account = nil 121 | 122 | ``` 123 | 124 | #### More, Advanced Examples #### 125 | 126 | The project [dmc-objects](https://github.com/dmccuskey/dmc-objects) contains two `lua-objects` sub-classes made for mobile development (`ObjectBase` & `ComponentBase`). These sub-classes show how to get more out of `lua_objects`, such as: 127 | 128 | * custom initialization and teardown 129 | * custom constructor/destructor names 130 | * custom Event mixin (add/removeListener/dispatchEvent) [lua-events-mixin](https://github.com/dmccuskey/lua-events-mixin) 131 | 132 | 133 | 134 | ### Custom Constructor/Destructor ### 135 | 136 | You can even customize the names used for construction and destruction. 137 | 138 | ```lua 139 | -- use 'create' instead of 'new' 140 | -- eg, MyClass:create{ secure=true, amount=94.32 } 141 | -- 142 | registerCtorName( 'create' ) 143 | 144 | -- use 'removeSelf' instead of 'destroy' 145 | -- eg, obj:removeSelf() 146 | -- 147 | registerDtorName( 'removeSelf' ) 148 | 149 | ``` 150 | -------------------------------------------------------------------------------- /Snakefile: -------------------------------------------------------------------------------- 1 | # lua-objects 2 | 3 | try: 4 | if not gSTARTED: print( gSTARTED ) 5 | except: 6 | MODULE = "lua-objects" 7 | include: "../DMC-Lua-Library/snakemake/Snakefile" 8 | 9 | module_config = { 10 | "name": "lua-objects", 11 | "module": { 12 | "dir": "dmc_lua", 13 | "files": [ 14 | "lua_objects.lua" 15 | ], 16 | "requires": [ 17 | "lua-events-mixin", 18 | "lua-class" 19 | ] 20 | }, 21 | "tests": { 22 | "dir": "spec", 23 | "files": [], 24 | "requires": [] 25 | } 26 | } 27 | 28 | register( "lua-objects", module_config ) 29 | 30 | -------------------------------------------------------------------------------- /dmc_lua/lua_class.lua: -------------------------------------------------------------------------------- 1 | --====================================================================-- 2 | -- dmc_lua/lua_class.lua 3 | -- 4 | -- Documentation: http://docs.davidmccuskey.com/ 5 | --====================================================================-- 6 | 7 | --[[ 8 | 9 | The MIT License (MIT) 10 | 11 | Copyright (c) 2015 David McCuskey 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | 31 | --]] 32 | 33 | 34 | 35 | --====================================================================-- 36 | --== DMC Lua Library : Lua Objects 37 | --====================================================================-- 38 | 39 | 40 | -- Semantic Versioning Specification: http://semver.org/ 41 | 42 | local VERSION = "0.1.0" 43 | 44 | 45 | 46 | --====================================================================-- 47 | --== Imports 48 | 49 | 50 | -- none 51 | 52 | 53 | 54 | --====================================================================-- 55 | --== Setup, Constants 56 | 57 | 58 | -- cache globals 59 | local assert, type, rawget, rawset = assert, type, rawget, rawset 60 | local getmetatable, setmetatable = getmetatable, setmetatable 61 | 62 | local sformat = string.format 63 | local tinsert = table.insert 64 | local tremove = table.remove 65 | 66 | -- table for copies from lua_utils 67 | local Utils = {} 68 | 69 | -- forward declare 70 | local ClassBase 71 | 72 | 73 | 74 | --====================================================================-- 75 | --== Class Support Functions 76 | 77 | 78 | --== Start: copy from lua_utils ==-- 79 | 80 | -- extend() 81 | -- Copy key/values from one table to another 82 | -- Will deep copy any value from first table which is itself a table. 83 | -- 84 | -- @param fromTable the table (object) from which to take key/value pairs 85 | -- @param toTable the table (object) in which to copy key/value pairs 86 | -- @return table the table (object) that received the copied items 87 | -- 88 | function Utils.extend( fromTable, toTable ) 89 | 90 | if not fromTable or not toTable then 91 | error( "table can't be nil" ) 92 | end 93 | function _extend( fT, tT ) 94 | 95 | for k,v in pairs( fT ) do 96 | 97 | if type( fT[ k ] ) == "table" and 98 | type( tT[ k ] ) == "table" then 99 | 100 | tT[ k ] = _extend( fT[ k ], tT[ k ] ) 101 | 102 | elseif type( fT[ k ] ) == "table" then 103 | tT[ k ] = _extend( fT[ k ], {} ) 104 | 105 | else 106 | tT[ k ] = v 107 | end 108 | end 109 | 110 | return tT 111 | end 112 | 113 | return _extend( fromTable, toTable ) 114 | end 115 | 116 | --== End: copy from lua_utils ==-- 117 | 118 | 119 | 120 | 121 | -- registerCtorName 122 | -- add names for the constructor 123 | -- 124 | local function registerCtorName( name, class ) 125 | class = class or ClassBase 126 | --==-- 127 | assert( type( name ) == 'string', "ctor name should be string" ) 128 | assert( class.is_class, "Class is not is_class" ) 129 | 130 | class[ name ] = class.__ctor__ 131 | return class[ name ] 132 | end 133 | 134 | -- registerDtorName 135 | -- add names for the destructor 136 | -- 137 | local function registerDtorName( name, class ) 138 | class = class or ClassBase 139 | --==-- 140 | assert( type( name ) == 'string', "dtor name should be string" ) 141 | assert( class.is_class, "Class is not is_class" ) 142 | 143 | class[ name ] = class.__dtor__ 144 | return class[ name ] 145 | end 146 | 147 | 148 | 149 | --[[ 150 | obj:superCall( 'string', ... ) 151 | obj:superCall( Class, 'string', ... ) 152 | --]] 153 | 154 | -- superCall() 155 | -- function to intelligently find methods in object hierarchy 156 | -- 157 | local function superCall( self, ... ) 158 | local args = {...} 159 | local arg1 = args[1] 160 | assert( type(arg1)=='table' or type(arg1)=='string', "superCall arg not table or string" ) 161 | --==-- 162 | -- pick off arguments 163 | local parent_lock, method, params 164 | 165 | if type(arg1) == 'table' then 166 | parent_lock = tremove( args, 1 ) 167 | method = tremove( args, 1 ) 168 | else 169 | method = tremove( args, 1 ) 170 | end 171 | params = args 172 | 173 | local self_dmc_super = self.__dmc_super 174 | local super_flag = ( self_dmc_super ~= nil ) 175 | local result = nil 176 | 177 | -- finds method name in class hierarchy 178 | -- returns found class or nil 179 | -- @params classes list of Classes on which to look, table/list 180 | -- @params name name of method to look for, string 181 | -- @params lock Class object with which to constrain searching 182 | -- 183 | local function findMethod( classes, name, lock ) 184 | if not classes then return end -- when using mixins, etc 185 | local cls = nil 186 | for _, class in ipairs( classes ) do 187 | if not lock or class == lock then 188 | if rawget( class, name ) then 189 | cls = class 190 | break 191 | else 192 | -- check parents for method 193 | cls = findMethod( class.__parents, name ) 194 | if cls then break end 195 | end 196 | end 197 | end 198 | return cls 199 | end 200 | 201 | local c, s -- class, super 202 | 203 | -- structure in which to save our place 204 | -- in case superCall() is invoked again 205 | -- 206 | if self_dmc_super == nil then 207 | self.__dmc_super = {} -- a stack 208 | self_dmc_super = self.__dmc_super 209 | -- find out where we are in hierarchy 210 | s = findMethod( { self.__class }, method ) 211 | tinsert( self_dmc_super, s ) 212 | end 213 | 214 | -- pull Class from stack and search for method on Supers 215 | -- look for method on supers 216 | -- call method if found 217 | -- 218 | c = self_dmc_super[ # self_dmc_super ] 219 | -- TODO: when c==nil 220 | -- if c==nil or type(c)~='table' then return end 221 | 222 | s = findMethod( c.__parents, method, parent_lock ) 223 | if s then 224 | tinsert( self_dmc_super, s ) 225 | result = s[method]( self, unpack( args ) ) 226 | tremove( self_dmc_super, # self_dmc_super ) 227 | end 228 | 229 | -- this is the first iteration and last 230 | -- so clean up callstack, etc 231 | -- 232 | if super_flag == false then 233 | parent_lock = nil 234 | tremove( self_dmc_super, # self_dmc_super ) 235 | self.__dmc_super = nil 236 | end 237 | 238 | return result 239 | end 240 | 241 | 242 | 243 | -- initializeObject 244 | -- this is the beginning of object initialization 245 | -- either Class or Instance 246 | -- this is what calls the parent constructors, eg new() 247 | -- called from newClass(), __create__(), __call() 248 | -- 249 | -- @params obj the object context 250 | -- @params params table with : 251 | -- set_isClass = true/false 252 | -- data contains {...} 253 | -- 254 | local function initializeObject( obj, params ) 255 | params = params or {} 256 | --==-- 257 | assert( params.set_isClass ~= nil, "initializeObject requires paramter 'set_isClass'" ) 258 | 259 | local is_class = params.set_isClass 260 | local args = params.data or {} 261 | 262 | -- set Class/Instance flag 263 | obj.__is_class = params.set_isClass 264 | 265 | -- call Parent constructors, if any 266 | -- do in reverse 267 | -- 268 | local parents = obj.__parents 269 | for i = #parents, 1, -1 do 270 | local parent = parents[i] 271 | assert( parent, "Lua Objects: parent is nil, check parent list" ) 272 | 273 | rawset( obj, '__parent_lock', parent ) 274 | if parent.__new__ then 275 | parent.__new__( obj, unpack( args ) ) 276 | end 277 | 278 | end 279 | rawset( obj, '__parent_lock', nil ) 280 | 281 | return obj 282 | end 283 | 284 | 285 | 286 | -- newindexFunc() 287 | -- override the normal Lua lookup functionality to allow 288 | -- property setter functions 289 | -- 290 | -- @param t object table 291 | -- @param k key 292 | -- @param v value 293 | -- 294 | local function newindexFunc( t, k, v ) 295 | 296 | local o, f 297 | 298 | -- check for key in setters table 299 | o = rawget( t, '__setters' ) or {} 300 | f = o[k] 301 | if f then 302 | -- found setter, so call it 303 | f(t,v) 304 | else 305 | -- place key/value directly on object 306 | rawset( t, k, v ) 307 | end 308 | 309 | end 310 | 311 | 312 | 313 | -- multiindexFunc() 314 | -- override the normal Lua lookup functionality to allow 315 | -- property getter functions 316 | -- 317 | -- @param t object table 318 | -- @param k key 319 | -- 320 | local function multiindexFunc( t, k ) 321 | 322 | local o, val 323 | 324 | --== do key lookup in different places on object 325 | 326 | -- check for key in getters table 327 | o = rawget( t, '__getters' ) or {} 328 | if o[k] then return o[k](t) end 329 | 330 | -- check for key directly on object 331 | val = rawget( t, k ) 332 | if val ~= nil then return val end 333 | 334 | -- check OO hierarchy 335 | -- check Parent Lock else all of Parents 336 | -- 337 | o = rawget( t, '__parent_lock' ) 338 | if o then 339 | if o then val = o[k] end 340 | if val ~= nil then return val end 341 | else 342 | local par = rawget( t, '__parents' ) 343 | for _, o in ipairs( par ) do 344 | if o[k] ~= nil then 345 | val = o[k] 346 | break 347 | end 348 | end 349 | if val ~= nil then return val end 350 | end 351 | 352 | return nil 353 | end 354 | 355 | 356 | 357 | -- blessObject() 358 | -- create new object, setup with Lua OO aspects, dmc-style aspects 359 | -- @params inheritance table of supers/parents (dmc-style objects) 360 | -- @params params 361 | -- params.object 362 | -- params.set_isClass 363 | -- 364 | local function blessObject( inheritance, params ) 365 | params = params or {} 366 | params.object = params.object or {} 367 | params.set_isClass = params.set_isClass == true and true or false 368 | --==-- 369 | local o = params.object 370 | local o_id = tostring(o) 371 | local mt = { 372 | __index = multiindexFunc, 373 | __newindex = newindexFunc, 374 | __tostring = function(obj) 375 | return obj:__tostring__(o_id) 376 | end, 377 | __call = function( cls, ... ) 378 | return cls:__ctor__( ... ) 379 | end 380 | } 381 | setmetatable( o, mt ) 382 | 383 | -- add Class property, access via getters:supers() 384 | o.__parents = inheritance 385 | o.__is_dmc = true 386 | 387 | -- create lookup tables - setters, getters 388 | o.__setters = {} 389 | o.__getters = {} 390 | 391 | -- copy down all getters/setters of parents 392 | -- do in reverse order, to match order of property lookup 393 | for i = #inheritance, 1, -1 do 394 | local cls = inheritance[i] 395 | if cls.__getters then 396 | o.__getters = Utils.extend( cls.__getters, o.__getters ) 397 | end 398 | if cls.__setters then 399 | o.__setters = Utils.extend( cls.__setters, o.__setters ) 400 | end 401 | end 402 | 403 | return o 404 | end 405 | 406 | 407 | 408 | local function newClass( inheritance, params ) 409 | inheritance = inheritance or {} 410 | params = params or {} 411 | params.set_isClass = true 412 | params.name = params.name or "" 413 | --==-- 414 | assert( type( inheritance ) == 'table', "first parameter should be nil, a Class, or a list of Classes" ) 415 | 416 | -- wrap single-class into table list 417 | -- testing for DMC-Style objects 418 | -- TODO: see if we can test for other Class libs 419 | -- 420 | if inheritance.is_class == true then 421 | inheritance = { inheritance } 422 | elseif ClassBase and #inheritance == 0 then 423 | -- add default base Class 424 | tinsert( inheritance, ClassBase ) 425 | end 426 | 427 | local o = blessObject( inheritance, {} ) 428 | 429 | initializeObject( o, params ) 430 | 431 | -- add Class property, access via getters:class() 432 | o.__class = o 433 | 434 | -- add Class property, access via getters:NAME() 435 | o.__name = params.name 436 | 437 | return o 438 | 439 | end 440 | 441 | 442 | -- backward compatibility 443 | -- 444 | local function inheritsFrom( baseClass, options, constructor ) 445 | baseClass = baseClass == nil and baseClass or { baseClass } 446 | return newClass( baseClass, options ) 447 | end 448 | 449 | 450 | 451 | --====================================================================-- 452 | --== Base Class 453 | --====================================================================-- 454 | 455 | 456 | ClassBase = newClass( nil, { name="Class Class" } ) 457 | 458 | -- __ctor__ method 459 | -- called by 'new()' and other registrations 460 | -- 461 | function ClassBase:__ctor__( ... ) 462 | local params = { 463 | data = {...}, 464 | set_isClass = false 465 | } 466 | --==-- 467 | local o = blessObject( { self.__class }, params ) 468 | initializeObject( o, params ) 469 | 470 | return o 471 | end 472 | 473 | -- __dtor__ method 474 | -- called by 'destroy()' and other registrations 475 | -- 476 | function ClassBase:__dtor__() 477 | self:__destroy__() 478 | end 479 | 480 | 481 | function ClassBase:__new__( ... ) 482 | return self 483 | end 484 | 485 | 486 | function ClassBase:__tostring__( id ) 487 | return sformat( "%s (%s)", self.NAME, id ) 488 | end 489 | 490 | 491 | function ClassBase:__destroy__() 492 | end 493 | 494 | 495 | function ClassBase.__getters:NAME() 496 | return self.__name 497 | end 498 | 499 | 500 | function ClassBase.__getters:class() 501 | return self.__class 502 | end 503 | 504 | function ClassBase.__getters:supers() 505 | return self.__parents 506 | end 507 | 508 | 509 | function ClassBase.__getters:is_class() 510 | return self.__is_class 511 | end 512 | 513 | -- deprecated 514 | function ClassBase.__getters:is_intermediate() 515 | return self.__is_class 516 | end 517 | 518 | function ClassBase.__getters:is_instance() 519 | return not self.__is_class 520 | end 521 | 522 | function ClassBase.__getters:version() 523 | return self.__version 524 | end 525 | 526 | 527 | function ClassBase:isa( the_class ) 528 | local isa = false 529 | local cur_class = self.class 530 | 531 | -- test self 532 | if cur_class == the_class then 533 | isa = true 534 | 535 | -- test parents 536 | else 537 | local parents = self.__parents 538 | for i=1, #parents do 539 | local parent = parents[i] 540 | if parent.isa then 541 | isa = parent:isa( the_class ) 542 | end 543 | if isa == true then break end 544 | end 545 | end 546 | 547 | return isa 548 | end 549 | 550 | 551 | -- optimize() 552 | -- move super class methods to object 553 | -- 554 | function ClassBase:optimize() 555 | 556 | function _optimize( obj, inheritance ) 557 | 558 | if not inheritance or #inheritance == 0 then return end 559 | 560 | for i=#inheritance,1,-1 do 561 | local parent = inheritance[i] 562 | 563 | -- climb up the hierarchy 564 | _optimize( obj, parent.__parents ) 565 | 566 | -- make local references to all functions 567 | for k,v in pairs( parent ) do 568 | if type( v ) == 'function' then 569 | obj[ k ] = v 570 | end 571 | end 572 | end 573 | 574 | end 575 | 576 | _optimize( self, { self.__class } ) 577 | end 578 | 579 | -- deoptimize() 580 | -- remove super class (optimized) methods from object 581 | -- 582 | function ClassBase:deoptimize() 583 | for k,v in pairs( self ) do 584 | if type( v ) == 'function' then 585 | self[ k ] = nil 586 | end 587 | end 588 | end 589 | 590 | 591 | 592 | -- Setup Class Properties (function references) 593 | 594 | registerCtorName( 'new', ClassBase ) 595 | registerDtorName( 'destroy', ClassBase ) 596 | ClassBase.superCall = superCall 597 | 598 | 599 | 600 | 601 | --====================================================================-- 602 | --== Lua Objects Exports 603 | --====================================================================-- 604 | 605 | 606 | -- makeNewClassGlobal 607 | -- modifies the global namespace with newClass() 608 | -- add or remove 609 | -- 610 | local function makeNewClassGlobal( is_global ) 611 | is_global = is_global~=nil and is_global or true 612 | if _G.newClass ~= nil then 613 | print( "WARNING: newClass exists in global namespace" ) 614 | elseif is_global == true then 615 | _G.newClass = newClass 616 | else 617 | _G.newClass = nil 618 | end 619 | end 620 | 621 | makeNewClassGlobal() -- start it off 622 | 623 | 624 | return { 625 | __version=VERSION, 626 | __superCall=superCall, -- for testing 627 | setNewClassGlobal=makeNewClassGlobal, 628 | 629 | registerCtorName=registerCtorName, 630 | registerDtorName=registerDtorName, 631 | 632 | inheritsFrom=inheritsFrom, -- backwards compatibility 633 | newClass=newClass, 634 | 635 | Class=ClassBase 636 | } 637 | -------------------------------------------------------------------------------- /dmc_lua/lua_events_mix.lua: -------------------------------------------------------------------------------- 1 | --====================================================================-- 2 | -- dmc_lua/lua_events_mix.lua 3 | -- 4 | -- Documentation: http://docs.davidmccuskey.com/ 5 | --====================================================================-- 6 | 7 | --[[ 8 | 9 | The MIT License (MIT) 10 | 11 | Copyright (C) 2014-2015 David McCuskey. All Rights Reserved. 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | 31 | --]] 32 | 33 | 34 | 35 | --====================================================================-- 36 | --== DMC Lua Library : Lua Events Mixin 37 | --====================================================================-- 38 | 39 | 40 | -- Semantic Versioning Specification: http://semver.org/ 41 | 42 | local VERSION = "0.2.2" 43 | 44 | 45 | 46 | --====================================================================-- 47 | --== Setup, Constants 48 | 49 | 50 | local Events 51 | local Utils = {} -- make copying from Utils easier 52 | 53 | local assert = assert 54 | local sfmt = string.format 55 | local type = type 56 | 57 | 58 | 59 | --====================================================================-- 60 | --== Support Functions 61 | 62 | 63 | --== Start: copy from lua_utils ==-- 64 | 65 | function Utils.createObjectCallback( object, method ) 66 | assert( object ~= nil, "missing object in Utils.createObjectCallback" ) 67 | assert( method ~= nil, "missing method in Utils.createObjectCallback" ) 68 | --==-- 69 | return function( ... ) 70 | return method( object, ... ) 71 | end 72 | end 73 | 74 | --== End: copy from lua_utils ==-- 75 | 76 | 77 | -- callback is either function or object (table) 78 | -- creates listener lookup key given event name and handler 79 | -- 80 | local function _createEventListenerKey( e_name, handler ) 81 | return e_name .. "::" .. tostring( handler ) 82 | end 83 | 84 | 85 | 86 | -- return event unmodified 87 | -- 88 | local function _createCoronaEvent( obj, event ) 89 | return event 90 | end 91 | 92 | 93 | 94 | -- obj, 95 | -- event type, string 96 | -- data, anything 97 | -- params, table of params 98 | -- params.merge, boolean, if to merge data (table) in with event table 99 | -- 100 | local function _createDmcEvent( obj, e_type, data, params ) 101 | params = params or {} 102 | if params.merge==nil then params.merge=false end 103 | --==-- 104 | local e 105 | 106 | if params.merge and type( data )=='table' then 107 | e = data 108 | e.name = obj.EVENT 109 | e.type = e_type 110 | e.target = obj 111 | 112 | else 113 | e = { 114 | name=obj.EVENT, 115 | type=e_type, 116 | target=obj, 117 | data=data 118 | } 119 | 120 | end 121 | return e 122 | end 123 | 124 | 125 | 126 | local function _patch( obj ) 127 | 128 | obj = obj or {} 129 | 130 | -- add properties 131 | Events.__init__( obj ) 132 | 133 | if obj.EVENT==nil then 134 | obj.EVENT = Events.EVENT -- generic event name 135 | end 136 | 137 | -- add methods 138 | obj.dispatchEvent = Events.dispatchEvent 139 | obj.dispatchRawEvent = Events.dispatchRawEvent 140 | 141 | obj.addEventListener = Events.addEventListener 142 | obj.removeEventListener = Events.removeEventListener 143 | 144 | obj.setDebug = Events.setDebug 145 | obj.setEventFunc = Events.setEventFunc 146 | 147 | obj._dispatchEvent = Events._dispatchEvent 148 | 149 | return obj 150 | end 151 | 152 | 153 | 154 | --====================================================================-- 155 | --== Events Mixin 156 | --====================================================================-- 157 | 158 | 159 | Events = {} 160 | 161 | Events.EVENT = 'event_mix_event' 162 | 163 | 164 | --======================================================-- 165 | -- Start: Mixin Setup for Lua Objects 166 | 167 | function Events.__init__( self, params ) 168 | -- print( "Events.__init__" ) 169 | params = params or {} 170 | --==-- 171 | 172 | --[[ 173 | event listeners key'd by: 174 | * :: 175 | * :: 176 | { 177 | = { 178 | 'event::function' = func, 179 | 'event::object' = object (table) 180 | } 181 | } 182 | --]] 183 | self.__event_listeners = {} -- holds event listeners 184 | 185 | self.__debug_on = false 186 | self.__event_func = params.event_func or _createDmcEvent 187 | end 188 | 189 | function Events.__undoInit__( self ) 190 | -- print( "Events.__undoInit__" ) 191 | self.__event_listeners = nil 192 | self.__debug_on = nil 193 | self.__event_func = nil 194 | end 195 | 196 | -- END: Mixin Setup for Lua Objects 197 | --======================================================-- 198 | 199 | 200 | 201 | --====================================================================-- 202 | --== Public Methods 203 | 204 | 205 | function Events.createCallback( self, method ) 206 | return Utils.createObjectCallback( self, method ) 207 | end 208 | 209 | function Events.setDebug( self, value ) 210 | assert( type( value )=='boolean', "setDebug requires boolean" ) 211 | self.__debug_on = value 212 | end 213 | 214 | function Events.setEventFunc( self, func ) 215 | assert( func and type(func)=='function', 'setEventFunc requires function' ) 216 | self.__event_func = func 217 | end 218 | 219 | 220 | function Events.createEvent( self, ... ) 221 | return self.__event_func( self, ... ) 222 | end 223 | 224 | function Events.dispatchEvent( self, ... ) 225 | -- print( "Events.dispatchEvent" ) 226 | local f = self.__event_func 227 | self:_dispatchEvent( f( self, ... ) ) 228 | end 229 | 230 | function Events.dispatchRawEvent( self, event ) 231 | -- print( "Events.dispatchRawEvent", event ) 232 | assert( type( event )=='table', "wrong type for event" ) 233 | assert( event.name, "event must have property 'name'") 234 | --==-- 235 | self:_dispatchEvent( event ) 236 | end 237 | 238 | 239 | 240 | -- addEventListener() 241 | -- 242 | function Events.addEventListener( self, e_name, listener ) 243 | -- print( "Events.addEventListener", e_name, listener ) 244 | assert( type( e_name )=='string', sfmt( "Events.addEventListener event name should be a string, received '%s'", tostring(e_name)) ) 245 | assert( type(listener)=='function' or type(listener)=='table', sfmt( "Events.addEventListener callback should be function or object, received '%s'", tostring(listener) )) 246 | 247 | -- Sanity Check 248 | 249 | if not e_name or type( e_name )~='string' then 250 | error( "ERROR addEventListener: event name must be string", 2 ) 251 | end 252 | if not listener and not Utils.propertyIn( {'function','table'}, type(listener) ) then 253 | error( "ERROR addEventListener: listener must be a function or object", 2 ) 254 | end 255 | 256 | -- Processing 257 | 258 | local events, listeners, key 259 | 260 | events = self.__event_listeners 261 | if not events[ e_name ] then events[ e_name ] = {} end 262 | listeners = events[ e_name ] 263 | 264 | key = _createEventListenerKey( e_name, listener ) 265 | if listeners[ key ] then 266 | print("WARNING:: Events:addEventListener, already have listener") 267 | else 268 | listeners[ key ] = listener 269 | end 270 | 271 | end 272 | 273 | -- removeEventListener() 274 | -- 275 | function Events.removeEventListener( self, e_name, listener ) 276 | -- print( "Events.removeEventListener" ); 277 | 278 | local listeners, key 279 | 280 | listeners = self.__event_listeners[ e_name ] 281 | if not listeners or type(listeners)~= 'table' then 282 | print( "WARNING:: Events:removeEventListener, no listeners found" ) 283 | end 284 | 285 | key = _createEventListenerKey( e_name, listener ) 286 | 287 | if not listeners[ key ] then 288 | print( "WARNING:: Events:removeEventListener, listener not found" ) 289 | else 290 | listeners[ key ] = nil 291 | end 292 | 293 | end 294 | 295 | 296 | 297 | --====================================================================-- 298 | --== Private Methods 299 | 300 | 301 | function Events:_dispatchEvent( event ) 302 | -- print( "Events:_dispatchEvent", event.name ); 303 | local e_name, listeners 304 | 305 | e_name = event.name 306 | if not e_name or not self.__event_listeners[ e_name ] then return end 307 | 308 | listeners = self.__event_listeners[ e_name ] 309 | if type( listeners )~='table' then return end 310 | 311 | for k, callback in pairs( listeners ) do 312 | 313 | if type( callback )=='function' then 314 | -- have function 315 | callback( event ) 316 | 317 | elseif type( callback )=='table' and callback[e_name] then 318 | -- have object/table 319 | local method = callback[e_name] 320 | method( callback, event ) 321 | 322 | else 323 | print( "WARNING: Events dispatchEvent", e_name ) 324 | 325 | end 326 | end 327 | end 328 | 329 | 330 | 331 | 332 | --====================================================================-- 333 | --== Events Facade 334 | --====================================================================-- 335 | 336 | 337 | return { 338 | EventsMix=Events, 339 | 340 | dmcEventFunc=_createDmcEvent, 341 | coronaEventFunc=_createCoronaEvent, 342 | 343 | patch=_patch, 344 | } 345 | 346 | 347 | -------------------------------------------------------------------------------- /dmc_lua/lua_objects.lua: -------------------------------------------------------------------------------- 1 | --====================================================================-- 2 | -- dmc_lua/lua_objects.lua 3 | -- 4 | -- Documentation: http://docs.davidmccuskey.com/ 5 | --====================================================================-- 6 | 7 | --[[ 8 | 9 | The MIT License (MIT) 10 | 11 | Copyright (c) 2011-2015 David McCuskey 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | 31 | --]] 32 | 33 | 34 | 35 | --====================================================================-- 36 | --== DMC Lua Library : Lua Objects 37 | --====================================================================-- 38 | 39 | 40 | -- Semantic Versioning Specification: http://semver.org/ 41 | 42 | local VERSION = "1.3.0" 43 | 44 | 45 | 46 | 47 | --====================================================================-- 48 | --== Lua Objects 49 | --====================================================================-- 50 | 51 | 52 | --====================================================================-- 53 | --== Imports 54 | 55 | local Class = test 56 | local Objects = require 'lua_class' 57 | local EventsMixModule = require 'lua_events_mix' 58 | 59 | 60 | 61 | --====================================================================-- 62 | --== Setup, Constants 63 | 64 | 65 | local Class = Objects.Class 66 | local registerCtorName = Objects.registerCtorName 67 | local registerDtorName = Objects.registerDtorName 68 | 69 | local EventsMix = EventsMixModule.EventsMix 70 | 71 | 72 | -- Add new Dtor name (function references) 73 | registerDtorName( 'removeSelf', Class ) 74 | 75 | 76 | 77 | --====================================================================-- 78 | --== Object Base Class 79 | --====================================================================-- 80 | 81 | 82 | local ObjectBase = newClass( { Class, EventsMix }, { name="Object Base" } ) 83 | 84 | 85 | 86 | --======================================================-- 87 | --== Constructor / Destructor 88 | 89 | 90 | -- __new__() 91 | -- this method drives the construction flow for DMC-style objects 92 | -- typically, you won't override this 93 | -- 94 | function ObjectBase:__new__( ... ) 95 | 96 | --== Do setup sequence ==-- 97 | 98 | self:__init__( ... ) 99 | 100 | -- skip these if a Class object (ie, NOT an instance) 101 | if rawget( self, '__is_class' ) == false then 102 | self:__initComplete__() 103 | end 104 | 105 | return self 106 | end 107 | 108 | 109 | -- __destroy__() 110 | -- this method drives the destruction flow for DMC-style objects 111 | -- typically, you won't override this 112 | -- 113 | function ObjectBase:__destroy__() 114 | 115 | --== Do teardown sequence ==-- 116 | 117 | -- skip these if a Class object (ie, NOT an instance) 118 | if rawget( self, '__is_class' ) == false then 119 | self:__undoInitComplete__() 120 | end 121 | 122 | self:__undoInit__() 123 | end 124 | 125 | 126 | 127 | --======================================================-- 128 | -- Start: Setup Lua Objects 129 | 130 | -- __init__ 131 | -- initialize the object 132 | -- 133 | function ObjectBase:__init__( ... ) 134 | --[[ 135 | there is no __init__ on Class 136 | -- self:superCall( Class, '__init__', ... ) 137 | --]] 138 | self:superCall( EventsMix, '__init__', ... ) 139 | --==-- 140 | end 141 | 142 | -- __undoInit__ 143 | -- remove items added during __init__ 144 | -- 145 | function ObjectBase:__undoInit__() 146 | self:superCall( EventsMix, '__undoInit__' ) 147 | --[[ 148 | there is no __undoInit__ on Class 149 | -- self:superCall( Class, '__undoInit__' ) 150 | --]] 151 | end 152 | 153 | 154 | -- __initComplete__ 155 | -- any setup after object is done with __init__ 156 | -- 157 | function ObjectBase:__initComplete__() 158 | end 159 | 160 | -- __undoInitComplete__() 161 | -- remove any items added during __initComplete__ 162 | -- 163 | function ObjectBase:__undoInitComplete__() 164 | end 165 | 166 | -- END: Setup Lua Objects 167 | --======================================================-- 168 | 169 | 170 | 171 | --====================================================================-- 172 | --== Public Methods 173 | 174 | 175 | -- none 176 | 177 | 178 | 179 | --====================================================================-- 180 | --== Private Methods 181 | 182 | 183 | -- none 184 | 185 | 186 | 187 | --====================================================================-- 188 | --== Event Handlers 189 | 190 | 191 | -- none 192 | 193 | 194 | 195 | 196 | 197 | 198 | --====================================================================-- 199 | --== Lua Objects Exports 200 | --====================================================================-- 201 | 202 | 203 | -- simply add to current exports 204 | Objects.ObjectBase = ObjectBase 205 | 206 | 207 | 208 | return Objects 209 | 210 | -------------------------------------------------------------------------------- /spec/lua_objects_spec.lua: -------------------------------------------------------------------------------- 1 | --====================================================================-- 2 | -- spec/lua_objects_spec.lua 3 | -- 4 | -- Testing for lua-objects using Busted 5 | --====================================================================-- 6 | 7 | 8 | package.path = './dmc_lua/?.lua;' .. package.path 9 | 10 | 11 | --====================================================================-- 12 | --== Test: Lua Objects 13 | --====================================================================-- 14 | 15 | 16 | -- Semantic Versioning Specification: http://semver.org/ 17 | 18 | local VERSION = "0.2.1" 19 | 20 | 21 | 22 | --====================================================================-- 23 | --== Imports 24 | 25 | 26 | local Objects = require 'lua_objects' 27 | 28 | 29 | 30 | --====================================================================-- 31 | --== Setup, Constants 32 | 33 | 34 | -- setup some aliases to make code cleaner 35 | local Class = Objects.Class 36 | local Object = Objects.Object 37 | 38 | 39 | 40 | --====================================================================-- 41 | --== Testing Setup 42 | --====================================================================-- 43 | 44 | 45 | --[[ 46 | Test methods and such on items inside of Lua Objects 47 | --]] 48 | describe( "Module Test: test Lua Object availability", function() 49 | 50 | it( "has Class name", function() 51 | assert( Class.NAME == "Class Class" ) 52 | end) 53 | 54 | it( "has constructor function", function() 55 | assert( Class.new ~= nil ) 56 | assert( type( Class.new ) == 'function' ) 57 | end) 58 | 59 | it( "has method isa", function() 60 | assert( rawget( Class, 'isa' ) ~= nil ) 61 | end) 62 | 63 | it( "has newClass access", function() 64 | assert( Objects.newClass == _G.newClass, "mismatch of newClass() functions" ) 65 | end) 66 | 67 | end) 68 | 69 | 70 | 71 | 72 | --[[ 73 | Test the simplest class ever 74 | --]] 75 | describe( "Module Test: simplest class", function() 76 | 77 | local Class 78 | 79 | before_each( function() 80 | Class = newClass() 81 | end) 82 | 83 | after_each( function() 84 | Class = nil 85 | end) 86 | 87 | describe( "Test: simplest class elements", function() 88 | 89 | it( "returns a Class object", function() 90 | assert( type(Class) == 'table' ) 91 | assert( Class.is_class == true ) 92 | end) 93 | 94 | it( "is a subclass", function() 95 | assert.are.equal( Class:isa( Class ), true ) 96 | assert.are.equal( Class:isa( Class ), true ) 97 | 98 | assert.are.equal( Class:isa( nil ), false ) 99 | assert.are.equal( Class:isa( {} ), false ) 100 | end) 101 | 102 | it( "has a parent", function() 103 | assert.are.equal( #Class.supers, 1 ) 104 | end) 105 | 106 | it( "has property class", function() 107 | assert( Class.class == Class ) 108 | end) 109 | 110 | it( "has ctor/dtor methods", function() 111 | assert( Class.new ~= nil ) 112 | assert( type(Class.new) == 'function' ) 113 | 114 | assert( Class.destroy ~= nil ) 115 | assert( type(Class.destroy) == 'function' ) 116 | end) 117 | 118 | 119 | --== Private Testing ==-- 120 | 121 | it( "has dmc-style properties", function() 122 | assert( rawget( Class, '__setters' ) ~= nil ) 123 | assert( rawget( Class, '__getters' ) ~= nil ) 124 | assert( rawget( Class, '__parents' ) ~= nil ) 125 | end) 126 | 127 | end) 128 | 129 | end) 130 | 131 | 132 | 133 | 134 | --[[ 135 | Test the simplest inheritance 136 | --]] 137 | describe( "Module Test: single ineritance class", function() 138 | 139 | local ParentClass, Class 140 | 141 | before_each( function() 142 | ParentClass = newClass( {}, {name='Parent'} ) 143 | 144 | Class = newClass( ParentClass, {name='Class'} ) 145 | end) 146 | 147 | after_each( function() 148 | Class = nil 149 | end) 150 | 151 | describe( "Test: simplest class elements", function() 152 | 153 | it( "returns an object", function() 154 | assert( type(ParentClass) == 'table' ) 155 | assert( ParentClass.is_class == true ) 156 | 157 | assert( type(Class) == 'table' ) 158 | assert( Class.is_class == true ) 159 | end) 160 | 161 | it( "is not a table", function() 162 | assert( Class:isa( Class ) == true ) 163 | assert( Class:isa( ParentClass ) == true ) 164 | assert( Class:isa( Class ) == true ) 165 | 166 | assert( Class:isa( nil ) == false ) 167 | assert( Class:isa( {} ) == false ) 168 | end) 169 | 170 | it( "has property class", function() 171 | assert( Class.class == Class ) 172 | end) 173 | 174 | it( "has ctor/dtor methods", function() 175 | assert( type(ParentClass.new) == 'function' ) 176 | assert( type(ParentClass.destroy) == 'function' ) 177 | 178 | assert( type(Class.new) == 'function' ) 179 | assert( type(Class.destroy) == 'function' ) 180 | end) 181 | 182 | 183 | --== Private Testing ==-- 184 | 185 | it( "has dmc-style properties", function() 186 | assert( rawget( Class, '__setters' ) ~= nil ) 187 | assert( rawget( Class, '__getters' ) ~= nil ) 188 | assert( rawget( Class, '__parents' ) ~= nil ) 189 | end) 190 | 191 | end) 192 | 193 | end) 194 | 195 | 196 | 197 | 198 | --[[ 199 | Test simple-inheritance class methods 200 | --]] 201 | describe( "Module Test: class methods", function() 202 | 203 | local ClassA 204 | local obj, obj2 205 | local p 206 | 207 | setup( function() 208 | end) 209 | 210 | teardown( function() 211 | end) 212 | 213 | before_each( function() 214 | ClassA = newClass() 215 | 216 | function ClassA:__new__( params ) 217 | params = params or {} 218 | self:superCall( '__new__', params ) 219 | self._params = params 220 | end 221 | 222 | function ClassA:one( num ) 223 | return num 224 | end 225 | 226 | function ClassA:two( num ) 227 | return num * 2 228 | end 229 | 230 | p = {one=1} 231 | 232 | obj = ClassA:new( p ) 233 | obj2 = ClassA( p ) 234 | 235 | end) 236 | 237 | after_each( function() 238 | ClassA = nil 239 | obj = nil 240 | p = nil 241 | end) 242 | 243 | 244 | describe("Test: simplest class elements", function() 245 | 246 | it( "created object", function() 247 | assert( type( obj ) == 'table' ) 248 | assert( obj.is_class == false ) 249 | assert( obj.is_instance == true ) 250 | 251 | assert( type( obj2 ) == 'table' ) 252 | assert( obj2.is_class == false ) 253 | assert( obj2.is_instance == true ) 254 | end) 255 | 256 | it( "class has methods", function() 257 | assert( type( ClassA.one ) == 'function' ) 258 | assert( type( ClassA.two ) == 'function' ) 259 | end) 260 | 261 | it( "can access parent methods", function() 262 | assert.are.equal( obj:one( 4 ), 4 ) 263 | assert.are.equal( obj:two( 4 ), 8 ) 264 | 265 | assert.are.equal( obj2:one( 4 ), 4 ) 266 | assert.are.equal( obj2:two( 4 ), 8 ) 267 | end) 268 | 269 | it( "has properties", function() 270 | assert.are.equal( obj._params, p ) 271 | assert.are.equal( obj2._params, p ) 272 | end) 273 | 274 | end) 275 | 276 | end) 277 | 278 | 279 | 280 | 281 | --[[ 282 | Test multiple-inheritance class methods 283 | --]] 284 | describe( "Module Test: class methods", function() 285 | 286 | local ClassA, ClassB, obj, obj2 287 | 288 | setup( function() 289 | end) 290 | 291 | teardown( function() 292 | end) 293 | 294 | before_each( function() 295 | 296 | 297 | ClassA = newClass() 298 | 299 | function ClassA:one( num ) 300 | return num * 2 301 | end 302 | 303 | function ClassA:two( num ) 304 | return num * 4 305 | end 306 | 307 | function ClassA:three( num ) 308 | return num * 6 309 | end 310 | 311 | -- function ClassA:four( num ) end 312 | 313 | 314 | ClassB = newClass( ClassA ) 315 | 316 | function ClassB:one( num ) 317 | return num * 1 318 | end 319 | 320 | function ClassB:two( num ) 321 | return num * 2 322 | end 323 | 324 | -- function ClassB:three( num ) end 325 | 326 | function ClassB:four( num ) 327 | return num * 4 328 | end 329 | 330 | 331 | obj = ClassB:new() 332 | obj2 = ClassB:new() 333 | 334 | 335 | end) 336 | 337 | after_each( function() 338 | ClassA, ClassB = nil, nil 339 | obj, obj2 = nil, nil 340 | end) 341 | 342 | 343 | describe("Test: inherited class elements", function() 344 | 345 | it( "ClassA has methods", function() 346 | assert( rawget( ClassA, 'one' ) ~= nil ) 347 | assert( type( ClassA.one ) == 'function' ) 348 | 349 | assert( rawget( ClassA, 'two' ) ~= nil ) 350 | assert( type( ClassA.two ) == 'function' ) 351 | 352 | assert( rawget( ClassA, 'three' ) ~= nil ) 353 | assert( type( ClassA.three ) == 'function' ) 354 | 355 | assert( rawget( ClassA, 'four' ) == nil ) 356 | assert( type( ClassA.four ) == 'nil' ) 357 | end) 358 | 359 | it( "ClassB has methods", function() 360 | assert( rawget( ClassB, 'one' ) ~= nil ) 361 | assert( type( ClassB.one ) == 'function' ) 362 | 363 | assert( rawget( ClassB, 'two' ) ~= nil ) 364 | assert( type( ClassB.two ) == 'function' ) 365 | 366 | assert( rawget( ClassB, 'three' ) == nil ) 367 | assert( type( ClassB.three ) == 'function' ) 368 | 369 | assert( rawget( ClassB, 'four' ) ~= nil ) 370 | -- inherited 371 | assert( type( ClassB.four ) == 'function' ) 372 | end) 373 | 374 | it( "created object", function() 375 | assert( type(obj) == 'table' ) 376 | end) 377 | 378 | it( "Obj1 several parent classes", function() 379 | assert( obj:isa( ClassA ) == true ) 380 | assert( obj:isa( ClassB ) == true ) 381 | assert( obj:isa( Class ) == true ) 382 | 383 | assert( obj:isa( nil ) == false ) 384 | assert( obj:isa( {} ) == false ) 385 | end) 386 | 387 | it( "Obj2 several parent classes", function() 388 | assert( obj2:isa( ClassA ) == true ) 389 | assert( obj2:isa( ClassB ) == true ) 390 | assert( obj2:isa( Class ) == true ) 391 | 392 | assert( obj2:isa( nil ) == false ) 393 | assert( obj2:isa( {} ) == false ) 394 | end) 395 | 396 | it( "can access parent methods", function() 397 | assert.are.equal( obj:one( 4 ), 4 ) 398 | assert.are.equal( obj:two( 4 ), 8 ) 399 | assert.are.equal( obj:three( 4 ), 24 ) 400 | assert.are.equal( obj:four( 4 ), 16 ) 401 | end) 402 | 403 | end) 404 | 405 | end) 406 | 407 | 408 | 409 | 410 | --[[ 411 | Test complex multiple-inheritance class methods 412 | --]] 413 | describe( "Module Test: class methods", function() 414 | 415 | local ClassA, ClassB, ClassC, ClassD 416 | local obj, obj2 417 | 418 | before_each( function() 419 | 420 | ClassA = newClass() 421 | ClassA.NAME = "Class A" 422 | 423 | function ClassA:one( num ) 424 | return num * 4 425 | end 426 | 427 | function ClassA:two( num ) 428 | return num * 4 429 | end 430 | 431 | -- function ClassA:three( num ) end 432 | 433 | function ClassA:four( num ) 434 | return num * 4 435 | end 436 | 437 | 438 | ClassB = newClass( ClassA ) 439 | ClassB.NAME = "Class B" 440 | 441 | function ClassB:one( num ) 442 | local val = self:superCall( 'one', num ) 443 | return val * 3 444 | end 445 | 446 | -- function ClassB:two( num ) end 447 | 448 | function ClassB:three( num ) 449 | -- local val = self:superCall( 'three', num ) 450 | local val = num 451 | return val * 3 452 | end 453 | 454 | -- function ClassB:four( num ) end 455 | 456 | 457 | ClassC = newClass( ClassB ) 458 | ClassC.NAME = "Class C" 459 | 460 | function ClassC:one( num ) 461 | local val = self:superCall( 'one', num ) 462 | return val * 2 463 | end 464 | 465 | function ClassC:two( num ) 466 | local val = self:superCall( 'two', num ) 467 | return val * 2 468 | end 469 | 470 | -- function ClassC:three( num ) end 471 | 472 | -- function ClassC:four( num ) end 473 | 474 | 475 | ClassD = newClass( ClassC ) 476 | ClassD.NAME = "Class D" 477 | 478 | function ClassD:one( num ) 479 | local val = self:superCall( 'one', num ) 480 | return val * 1 481 | end 482 | 483 | -- function ClassD:two( num ) end 484 | 485 | function ClassD:three( num ) 486 | local val = self:superCall( 'three', num ) 487 | return val * 1 488 | end 489 | 490 | function ClassD:four( num ) 491 | local val = self:superCall( 'four', num ) 492 | return val * 1 493 | end 494 | 495 | 496 | obj = ClassD:new() 497 | 498 | end) 499 | 500 | after_each( function() 501 | ClassA, ClassB, ClassC, ClassD = nil, nil, nil, nil 502 | obj, obj2 = nil, nil 503 | end) 504 | 505 | 506 | describe("Test: complex multiple-inheritance method calls", function() 507 | 508 | it( "has good answers", function() 509 | assert.are.equal( obj:one( 4 ), 96 ) 510 | assert.are.equal( obj:two( 4 ), 32 ) 511 | assert.are.equal( obj:three( 4 ), 12 ) 512 | assert.are.equal( obj:four( 4 ), 16 ) 513 | end) 514 | 515 | end) 516 | 517 | end) 518 | --------------------------------------------------------------------------------