merged branch 2.0

This commit is contained in:
Enrique García Cota 2011-09-19 00:21:14 +02:00
commit 3f9ae1d6e8
14 changed files with 955 additions and 162 deletions

View File

@ -22,5 +22,4 @@ BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -5,7 +5,7 @@ Lua OOP classes usually end being:
* multi-file libraries, too difficult to understand
* very small libraries, not very powerful
Middleclass attemps to be a mid-sized library (~120 lines of code, on a single file), with clean, easy to understand code, and yet powerful enough to be used in most cases.
Middleclass attemps to be a mid-sized library (~130 lines of code, on a single file), with clean, easy to understand code, and yet powerful enough to be used in most cases.
h1. Documentation
@ -13,12 +13,13 @@ See the "github wiki page":https://github.com/kikito/middleclass/wiki for exampl
h1. Features
* ~120 lines of code
* ~130 lines of code
* top-level Object class
* all methods are virtual
* instance.class returns the instance's class
* @Class.name@ returns the class name (a string)
* @Class.superclass@ returns its super class
* @Class.super@ returns its super class
* Class methods can be defined with Class.static.methodname
* Subclassing:
** @class(name)@ creates a subclass of @Object@
** @class(name, Superclass)@ creates a subclass of the class @SuperClass@
@ -43,47 +44,19 @@ Features left out:
* classes are not Objects (instances are)
* simulating a 'super' keyword (for performance concerns)
h1. Installation using git (recommended)
h1. Installation
The easiest way is creating a clone using git:
<pre>git clone git://github.com/kikito/middleclass.git</pre>
This will create a folder called @middleclass@. You can require it using:
<pre>require 'middleclass.init'</pre>
If you have @?/init.lua@ included in your @package.path@ variable, you can remove the @.init@ part.
If you ever want to update middleclass, you can use the following commands:
<pre>cd your/path/to/middleclass
git pull origin master</pre>
Git will automatically update middleclass for you.
h1. Manual download
Middleclass can be directly downloaded using "the github downloader":https://github.com/kikito/middleclass/archives/master
You will not be able to get automatic updates with that method, but your folder size will be smaller.
As an alternative, you can directly download "middleclass.lua":https://github.com/kikito/middleclass/raw/master/middleclass.lua and "BSD-LICENSE.txt":https://github.com/kikito/middleclass/raw/master/BSD-LICENSE.txt and put them in your project.
In that case, you will have to require it doing something like this (will vary if you put middleclass.lua inside a folder):
Just copy the middleclass.lua file wherever you want it (for example on a lib/ folder). Then write this in any Lua file where you want to use it:
<pre>require 'middleclass'</pre>
(This assumes you have @?.lua@ in your @package.path@, which is the default)
You will have to put BSD-LICENSE.txt somewhere in your folder, or copy-paste it inside of your own license.txt file.
h1. middleclass-extras
This library has a companion lib that adds a lot of interesting functionality to your objects. Give it a look at "middleclass-extras":https://github.com/kikito/middleclass-extras
If you are looking for @MindState@ (now called @Stateful@), it's over there, too.
Make sure that you read the license, too. In order to comply with it you will have to include its full text inside your program's documentation, or else make it visible in the app's credits.
h1. Specs
You may find the specs for this library in "middleclass-specs":https://github.com/kikito/middleclass-specs
This project uses "telescope":https://github.com/norman/telescope for its specs. If you want to run the specs, you will have to install telescope first. Then just execute the following from the root inspect folder:
<pre>
tsc -f spec/*
</pre>

View File

@ -1,120 +1,138 @@
-----------------------------------------------------------------------------------------------------------------------
-- middleclass.lua - v1.4 (2011-03)
-- Enrique García Cota - enrique.garcia.cota [AT] gmail [DOT] com
-- Based on YaciCode, from Julien Patte and LuaObject, from Sébastien Rocca-Serra
-----------------------------------------------------------------------------------------------------------------------
local _nilf = function() end -- empty function
local _classes = setmetatable({}, {__mode = "k"}) -- keeps track of all the tables that are classes
Object = { name = "Object", __mixins = {} }
Object.__classDict = {
initialize = _nilf, destroy = _nilf, subclassed = _nilf,
__tostring = function(instance) return ("instance of ".. instance.class.name) end, -- root of __tostring method,
__metamethods = { '__add', '__call', '__concat', '__div', '__le', '__lt',
'__mod', '__mul', '__pow', '__sub', '__tostring', '__unm'
}
}
Object.__classDict.__index = Object.__classDict -- instances of Object need this
setmetatable(Object, {
__index = Object.__classDict, -- look up methods in the classDict
__newindex = Object.__classDict, -- any new Object methods will be defined in classDict
__call = Object.new, -- allows instantiation via Object()
__tostring = function() return "class Object" end -- allows tostring(obj)
})
_classes[Object] = true -- register Object on the list of classes.
-- creates the instance based of the class, but doesn't initialize it
function Object.allocate(theClass)
assert(_classes[theClass], "Use Class:allocate instead of Class.allocate")
return setmetatable({ class = theClass }, theClass.__classDict)
end
-- both creates and initializes an instance
function Object.new(theClass, ...)
local instance = theClass:allocate()
instance:initialize(...)
return instance
end
-- creates a subclass
function Object.subclass(theClass, name)
assert(_classes[theClass], "Use Class:subclass instead of Class.subclass")
assert( type(name)=="string", "You must provide a name(string) for your class")
local theSubClass = { name = name, superclass = theClass, __classDict = {}, __mixins={} }
local dict = theSubClass.__classDict -- classDict contains all the [meta]methods of the class
dict.__index = dict -- It "points to itself" so instances can use it as a metatable.
local superDict = theClass.__classDict -- The superclass' classDict
setmetatable(dict, superDict) -- when a method isn't found on classDict, 'escalate upwards'.
setmetatable(theSubClass, {
__index = dict, -- look for stuff on the dict
__newindex = function(_, methodName, method) -- ensure that __index isn't modified by mistake
assert(methodName ~= '__index', "Can't modify __index. Include middleclass-extras.Indexable and use 'index' instead")
rawset(dict, methodName , method)
end,
__tostring = function() return ("class ".. name) end, -- allows tostring(MyClass)
__call = function(_, ...) return theSubClass:new(...) end -- allows MyClass(...) instead of MyClass:new(...)
})
for _,mmName in ipairs(theClass.__metamethods) do -- Creates the initial metamethods
dict[mmName]= function(...) -- by default, they just 'look up' for an implememtation
local method = superDict[mmName] -- and if none found, they throw an error
assert( type(method)=='function', tostring(theSubClass) .. " doesn't implement metamethod '" .. mmName .. "'" )
return method(...)
end
end
theSubClass.initialize = function(instance,...) theClass.initialize(instance, ...) end
_classes[theSubClass]= true -- registers the new class on the list of _classes
theClass:subclassed(theSubClass) -- hook method. By default it does nothing
return theSubClass
end
-- Mixin extension function - simulates very basically ruby's include. Receives a table table, probably with functions.
-- Its contents are copied to theClass, with one exception: the included() method will be called instead of copied
function Object.include(theClass, mixin, ... )
assert(_classes[theClass], "Use class:include instead of class.include")
assert(type(mixin)=='table', "mixin must be a table")
for methodName,method in pairs(mixin) do
if methodName ~="included" then theClass[methodName] = method end
end
if type(mixin.included)=="function" then mixin:included(theClass, ... ) end
theClass.__mixins[mixin] = mixin
return theClass
end
-- Returns true if aClass is a subclass of other, false otherwise
function subclassOf(other, aClass)
if not _classes[aClass] or not _classes[other] then return false end
if aClass.superclass==nil then return false end -- aClass is Object, or a non-class
return aClass.superclass == other or subclassOf(other, aClass.superclass)
end
-- Returns true if obj is an instance of aClass (or one of its subclasses) false otherwise
function instanceOf(aClass, obj)
if not _classes[aClass] or type(obj)~='table' or not _classes[obj.class] then return false end
if obj.class==aClass then return true end
return subclassOf(aClass, obj.class)
end
-- Returns true if the mixin has already been included on a class (or a superclass)
function includes(mixin, aClass)
if not _classes[aClass] then return false end
if aClass.__mixins[mixin]==mixin then return true end
return includes(mixin, aClass.superclass)
end
-- Creates a new class named 'name'. Uses Object if no baseClass is specified.
function class(name, baseClass, ...)
baseClass = baseClass or Object
return baseClass:subclass(name, ...)
end
-----------------------------------------------------------------------------------------------------------------------
-- middleclass.lua - v2.0 (2011-08)
-- Enrique Garcia Cota - enrique.garcia.cota [AT] gmail [DOT] com
-- Based on YaciCode, from Julien Patte and LuaObject, from Sebastien Rocca-Serra
-----------------------------------------------------------------------------------------------------------------------
local _classes = setmetatable({}, {__mode = "k"})
local function _setClassDictionariesMetatables(klass)
local dict = klass.__instanceDict
dict.__index = dict
local super = klass.super
if super then
local superStatic = super.static
setmetatable(dict, super.__instanceDict)
setmetatable(klass.static, { __index = function(_,k) return dict[k] or superStatic[k] end })
else
setmetatable(klass.static, { __index = function(_,k) return dict[k] end })
end
end
local function _setClassMetatable(klass)
setmetatable(klass, {
__tostring = function() return "class " .. klass.name end,
__index = klass.static,
__newindex = klass.__instanceDict,
__call = function(self, ...) return self:new(...) end
})
end
local function _createClass(name, super)
local klass = { name = name, super = super, static = {}, __mixins = {}, __instanceDict={} }
klass.subclasses = setmetatable({}, {__mode = "k"})
_setClassDictionariesMetatables(klass)
_setClassMetatable(klass)
_classes[klass] = true
return klass
end
local function _createLookupMetamethod(klass, name)
return function(...)
local method = klass.super[name]
assert( type(method)=='function', tostring(klass) .. " doesn't implement metamethod '" .. name .. "'" )
return method(...)
end
end
local function _setClassMetamethods(klass)
for _,m in ipairs(klass.__metamethods) do
klass[m]= _createLookupMetamethod(klass, m)
end
end
local function _setDefaultInitializeMethod(klass, super)
klass.initialize = function(instance, ...)
return super.initialize(instance, ...)
end
end
local function _includeMixin(klass, mixin)
assert(type(mixin)=='table', "mixin must be a table")
for name,method in pairs(mixin) do
if name ~= "included" and name ~= "static" then klass[name] = method end
end
if mixin.static then
for name,method in pairs(mixin.static) do
klass.static[name] = method
end
end
if type(mixin.included)=="function" then mixin:included(klass) end
klass.__mixins[mixin] = true
end
Object = _createClass("Object", nil)
Object.static.__metamethods = { '__add', '__call', '__concat', '__div', '__le', '__lt',
'__mod', '__mul', '__pow', '__sub', '__tostring', '__unm' }
function Object.static:allocate()
assert(_classes[self], "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
return setmetatable({ class = self }, self.__instanceDict)
end
function Object.static:new(...)
local instance = self:allocate()
instance:initialize(...)
return instance
end
function Object.static:subclass(name)
assert(_classes[self], "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'")
assert(type(name) == "string", "You must provide a name(string) for your class")
local subclass = _createClass(name, self)
_setClassMetamethods(subclass)
_setDefaultInitializeMethod(subclass, self)
self.subclasses[subclass] = true
self:subclassed(subclass)
return subclass
end
function Object.static:subclassed(other) end
function Object.static:include( ... )
assert(_classes[self], "Make sure you that you are using 'Class:include' instead of 'Class.include'")
for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end
return self
end
function Object:initialize() end
function Object:__tostring() return "instance of " .. tostring(self.class) end
function class(name, super, ...)
super = super or Object
return super:subclass(name, ...)
end
function instanceOf(aClass, obj)
if not _classes[aClass] or type(obj) ~= 'table' or not _classes[obj.class] then return false end
if obj.class == aClass then return true end
return subclassOf(aClass, obj.class)
end
function subclassOf(other, aClass)
if not _classes[aClass] or not _classes[other] or aClass.super == nil then return false end
return aClass.super == other or subclassOf(other, aClass.super)
end
function includes(mixin, aClass)
if not _classes[aClass] then return false end
if aClass.__mixins[mixin] then return true end
return includes(mixin, aClass.super)
end

43
performance/run.lua Normal file
View File

@ -0,0 +1,43 @@
require 'middleclass'
time = require 'performance/time'
time('class creation', function()
local A = class('A')
end)
local A = class('A')
time('instance creation', function()
local a = A:new()
end)
function A:foo()
return 1
end
local a = A:new()
time('instance method invocation', function()
a:foo()
end)
local B = class('B', A)
local b = B:new()
time('inherited method invocation', function()
b:foo()
end)
function A.static:bar()
return 2
end
time('class method invocation', function()
A:bar()
end)
time('inherited class method invocation', function()
B:bar()
end)

13
performance/time.lua Normal file
View File

@ -0,0 +1,13 @@
return function(title, f)
collectgarbage()
local startTime = os.clock()
for i=0,10000 do f() end
local endTime = os.clock()
print( title, endTime - startTime )
end

125
spec/Object_spec.lua Normal file
View File

@ -0,0 +1,125 @@
require 'middleclass'
context('Object', function()
context('name', function()
test('is correctly set', function()
assert_equal(Object.name, 'Object')
end)
end)
context('tostring', function()
test('returns "class Object"', function()
assert_equal(tostring(Object), 'class Object')
end)
end)
context('()', function()
test('returns an object, like Object:new()', function()
local obj = Object()
assert_true(instanceOf(Object, obj))
end)
end)
context('subclass', function()
test('throws an error when used without the :', function()
assert_error(function() Object.subclass() end)
end)
test('throws an error when no name is given', function()
assert_error( function() Object:subclass() end)
end)
context('when given a class name', function()
local SubClass
before(function()
SubClass = Object:subclass('SubClass')
end)
test('it returns a class with the correct name', function()
assert_equal(SubClass.name, 'SubClass')
end)
test('it returns a class with the correct superclass', function()
assert_equal(SubClass.super, Object)
end)
test('it includes the subclass in the list of subclasses', function()
assert_true(Object.subclasses[SubClass])
end)
end)
end)
context('instance creation', function()
local SubClass
before(function()
SubClass = class('SubClass')
function SubClass:initialize() self.mark=true end
end)
context('allocate', function()
test('allocates instances properly', function()
local instance = SubClass:allocate()
assert_equal(instance.class, SubClass)
assert_equal(tostring(instance), "instance of " .. tostring(SubClass))
end)
test('throws an error when used without the :', function()
assert_error(Object.allocate)
end)
test('does not call the initializer', function()
local allocated = SubClass:allocate()
assert_nil(allocated.mark)
end)
test('can be overriden', function()
local previousAllocate = SubClass.static.allocate
function SubClass.static:allocate()
local instance = previousAllocate(SubClass)
instance.mark = true
return instance
end
local allocated = SubClass:allocate()
assert_true(allocated.mark)
end)
end)
context('new', function()
test('initializes instances properly', function()
local instance = SubClass:new()
assert_equal(instance.class, SubClass)
end)
test('throws an error when used without the :', function()
assert_error(SubClass.new)
end)
test('calls the initializer', function()
local initialized = SubClass:new()
assert_true(initialized.mark)
end)
end)
end)
end)

40
spec/class_spec.lua Normal file
View File

@ -0,0 +1,40 @@
require 'middleclass'
context('class()', function()
context('when given no params', function()
test('it throws an error', function()
assert_error(class)
end)
end)
context('when given a name', function()
local TheClass
before(function()
TheClass = class('TheClass')
end)
test('the resulting class has the correct name', function()
assert_equal(TheClass.name, 'TheClass')
end)
test('the resulting class has Object as its superclass', function()
assert_equal(TheClass.super, Object)
end)
end)
context('when given a name and a superclass', function()
local TheSuperClass = class('TheSuperClass')
local TheSubClass = class('TheSubClass', TheSuperClass)
test('the resulting class has the correct name', function()
assert_equal(TheSubClass.name, 'TheSubClass')
end)
test('the resulting class has the correct superclass', function()
assert_equal(TheSubClass.super, TheSuperClass)
end)
end)
end)

138
spec/classes_spec.lua Normal file
View File

@ -0,0 +1,138 @@
require 'middleclass'
context('A Class', function()
context('Default stuff', function()
local AClass
before(function()
AClass = class('AClass')
end)
context('name', function()
test('is correctly set', function()
assert_equal(AClass.name, 'AClass')
end)
end)
context('tostring', function()
test('returns "class *name*"', function()
assert_equal(tostring(AClass), 'class AClass')
end)
end)
context('()', function()
test('returns an object, like Class:new()', function()
local obj = AClass()
assert_equal(obj.class, AClass)
end)
end)
context('include', function()
test('throws an error when used without the :', function()
assert_error(function() AClass.include() end)
end)
test('throws an error when passed a non-table:', function()
assert_error(function() AClass:include(1) end)
end)
end)
context('subclass', function()
test('throws an error when used without the :', function()
assert_error(function() AClass.subclass() end)
end)
test('throws an error when no name is given', function()
assert_error( function() AClass:subclass() end)
end)
context('when given a subclass name', function()
local SubClass
before(function()
function AClass.static:subclassed(other) self.static.child = other end
SubClass = AClass:subclass('SubClass')
end)
test('it returns a class with the correct name', function()
assert_equal(SubClass.name, 'SubClass')
end)
test('it returns a class with the correct superclass', function()
assert_equal(SubClass.super, AClass)
end)
test('it invokes the subclassed hook method', function()
assert_equal(SubClass, AClass.child)
end)
test('it includes the subclass in the list of subclasses', function()
assert_true(AClass.subclasses[SubClass])
end)
end)
end)
end)
context('attributes', function()
local A, B
before(function()
A = class('A')
A.static.foo = 'foo'
B = class('B', A)
end)
test('are available after being initialized', function()
assert_equal(A.foo, 'foo')
end)
test('are available for subclasses', function()
assert_equal(B.foo, 'foo')
end)
test('are overridable by subclasses, without affecting the superclasses', function()
B.static.foo = 'chunky bacon'
assert_equal(B.foo, 'chunky bacon')
assert_equal(A.foo, 'foo')
end)
end)
context('methods', function()
local A, B
before(function()
A = class('A')
function A.static:foo() return 'foo' end
B = class('B', A)
end)
test('are available after being initialized', function()
assert_equal(A:foo(), 'foo')
end)
test('are available for subclasses', function()
assert_equal(B:foo(), 'foo')
end)
test('are overridable by subclasses, without affecting the superclasses', function()
function B.static:foo() return 'chunky bacon' end
assert_equal(B:foo(), 'chunky bacon')
assert_equal(A:foo(), 'foo')
end)
end)
end)

66
spec/includes_spec.lua Normal file
View File

@ -0,0 +1,66 @@
require 'middleclass'
context('includes', function()
context('nils, numbers, etc', function()
local o = Object:new()
local primitives = {nil, 1, 'hello', {}, function() end}
for _,primitive in pairs(primitives) do
local theType = type(primitive)
context('A ' .. theType, function()
local f1 = function() return includes(Object, primitive) end
local f2 = function() return includes(primitive, o) end
local f3 = function() return includes(primitive, primitive) end
context('don\'t throw errors', function()
test('includes(Object, '.. theType ..')', function()
assert_not_error(f1)
end)
test('includes(' .. theType .. ', Object:new())', function()
assert_not_error(f2)
end)
test('includes(' .. theType .. ',' .. theType ..')', function()
assert_not_error(f3)
end)
end)
test('make includes return false', function()
assert_false(f1())
assert_false(f2())
assert_false(f3())
end)
end)
end -- for
end)
context('A class', function()
local Class1 = class('Class1')
local Class2 = class('Class2', Class1)
local Class3 = class('Class3', Class2)
local UnrelatedClass = class('Unrelated')
local hasFoo = { foo=function() return 'foo' end }
Class1:include(hasFoo)
test('returns true if it includes a mixin', function()
assert_true(includes(hasFoo, Class1))
end)
test('returns true if its superclass includes a mixin', function()
assert_true(includes(hasFoo, Class2))
assert_true(includes(hasFoo, Class3))
end)
test('returns false otherwise', function()
assert_false(includes(hasFoo, UnrelatedClass))
end)
end)
end)

80
spec/instanceOf_spec.lua Normal file
View File

@ -0,0 +1,80 @@
require 'middleclass'
context('instanceOf', function()
context('nils, integers, strings, tables, and functions', function()
local o = Object:new()
local primitives = {nil, 1, 'hello', {}, function() end}
for _,primitive in pairs(primitives) do
local theType = type(primitive)
context('A ' .. theType, function()
local f1 = function() return instanceOf(Object, primitive) end
local f2 = function() return instanceOf(primitive, o) end
local f3 = function() return instanceOf(primitive, primitive) end
context('does not throw errors', function()
test('instanceOf(Object, '.. theType ..')', function()
assert_not_error(f1)
end)
test('instanceOf(' .. theType .. ', Object:new())', function()
assert_not_error(f2)
end)
test('instanceOf(' .. theType .. ',' .. theType ..')', function()
assert_not_error(f3)
end)
end)
test('makes instanceOf return false', function()
assert_false(f1())
assert_false(f2())
assert_false(f3())
end)
end)
end
end)
context('An instance', function()
local Class1 = class('Class1')
local Class2 = class('Class2', Class1)
local Class3 = class('Class3', Class2)
local UnrelatedClass = class('Unrelated')
local o1, o2, o3 = Class1:new(), Class2:new(), Class3:new()
test('is instanceOf(Object)', function()
assert_true(instanceOf(Object, o1))
assert_true(instanceOf(Object, o2))
assert_true(instanceOf(Object, o3))
end)
test('is instanceOf its class', function()
assert_true(instanceOf(Class1, o1))
assert_true(instanceOf(Class2, o2))
assert_true(instanceOf(Class3, o3))
end)
test('is instanceOf its class\' superclasses', function()
assert_true(instanceOf(Class1, o2))
assert_true(instanceOf(Class1, o3))
assert_true(instanceOf(Class2, o3))
end)
test('is not instanceOf its class\' subclasses', function()
assert_false(instanceOf(Class2, o1))
assert_false(instanceOf(Class3, o1))
assert_false(instanceOf(Class3, o2))
end)
test('is not instanceOf an unrelated class', function()
assert_false(instanceOf(UnrelatedClass, o1))
assert_false(instanceOf(UnrelatedClass, o2))
assert_false(instanceOf(UnrelatedClass, o3))
end)
end)
end)

65
spec/instances_spec.lua Normal file
View File

@ -0,0 +1,65 @@
require 'middleclass'
context('An instance', function()
context('attributes', function()
local Person
before(function()
Person = class('Person')
function Person:initialize(name)
self.name = name
end
end)
test('are available in the instance after being initialized', function()
local bob = Person:new('bob')
assert_equal(bob.name, 'bob')
end)
test('are available in the instance after being initialized by a superclass', function()
local AgedPerson = class('AgedPerson', Person)
function AgedPerson:initialize(name, age)
Person.initialize(self, name)
self.age = age
end
local pete = AgedPerson:new('pete', 31)
assert_equal(pete.name, 'pete')
assert_equal(pete.age, 31)
end)
end)
context('methods', function()
local A, B, a, b
before(function()
A = class('A')
function A:overridden() return 'foo' end
function A:regular() return 'regular' end
B = class('B', A)
function B:overridden() return 'bar' end
a = A:new()
b = B:new()
end)
test('are available for any instance', function()
assert_equal(a:overridden(), 'foo')
end)
test('are inheritable', function()
assert_equal(b:regular(), 'regular')
end)
test('are overridable', function()
assert_equal(b:overridden(), 'bar')
end)
end)
end)

106
spec/metamethods_spec.lua Normal file
View File

@ -0,0 +1,106 @@
require 'middleclass'
context('Metamethods', function()
context('Custom Metamethods', function()
-- Tests all metamethods. Note that __len is missing (lua makes table length unoverridable)
-- I'll use a() to note the length of vector "a" (I would have preferred to use #a, but it's not possible)
-- I'll be using 'a' instead of 'self' on this example since it is shorter
local Vector= class('Vector')
function Vector.initialize(a,x,y,z) a.x, a.y, a.z = x,y,z end
function Vector.__tostring(a) return a.class.name .. '[' .. a.x .. ',' .. a.y .. ',' .. a.z .. ']' end
function Vector.__eq(a,b) return a.x==b.x and a.y==b.y and a.z==b.z end
function Vector.__lt(a,b) return a() < b() end
function Vector.__le(a,b) return a() <= b() end
function Vector.__add(a,b) return Vector:new(a.x+b.x, a.y+b.y ,a.z+b.z) end
function Vector.__sub(a,b) return Vector:new(a.x-b.x, a.y-b.y, a.z-b.z) end
function Vector.__div(a,s) return Vector:new(a.x/s, a.y/s, a.z/s) end
function Vector.__unm(a) return Vector:new(-a.x, -a.y, -a.z) end
function Vector.__concat(a,b) return a.x*b.x+a.y*b.y+a.z*b.z end
function Vector.__call(a) return math.sqrt(a.x*a.x+a.y*a.y+a.z*a.z) end
function Vector.__pow(a,b)
return Vector:new(a.y*b.z-a.z*b.y,a.z*b.x-a.x*b.z,a.x*b.y-a.y*b.x)
end
function Vector.__mul(a,b)
if type(b)=="number" then return Vector:new(a.x*b, a.y*b, a.z*b) end
if type(a)=="number" then return Vector:new(a*b.x, a*b.y, a*b.z) end
end
local a = Vector:new(1,2,3)
local b = Vector:new(2,4,6)
for metamethod,values in pairs({
__tostring = { tostring(a), "Vector[1,2,3]" },
__eq = { a, a},
__lt = { a<b, true },
__le = { a<=b, true },
__add = { a+b, Vector(3,6,9) },
__sub = { b-a, Vector(1,2,3) },
__div = { b/2, Vector(1,2,3) },
__unm = { -a, Vector(-1,-2,-3) },
__concat = { a..b, 28 },
__call = { a(), math.sqrt(14) },
__pow = { a^b, Vector(0,0,0) },
__mul = { 4*a, Vector(4,8,12) }--,
--__index = { b[1], 3 }
}) do
test(metamethod .. ' works as expected', function()
assert_equal(values[1], values[2])
end)
end
context('Inherited Metamethods', function()
local Vector2= class('Vector2', Vector)
function Vector2:initialize(x,y,z) Vector.initialize(self,x,y,z) end
local c = Vector2:new(1,2,3)
local d = Vector2:new(2,4,6)
for metamethod,values in pairs({
__tostring = { tostring(c), "Vector2[1,2,3]" },
__eq = { c, c },
__lt = { c<d, true },
__le = { c<=d, true },
__add = { c+d, Vector(3,6,9) },
__sub = { d-c, Vector(1,2,3) },
__div = { d/2, Vector(1,2,3) },
__unm = { -c, Vector(-1,-2,-3) },
__concat = { c..d, 28 },
__call = { c(), math.sqrt(14) },
__pow = { c^d, Vector(0,0,0) },
__mul = { 4*c, Vector(4,8,12) }
}) do
test(metamethod .. ' works as expected', function()
assert_equal(values[1], values[2])
end)
end
end)
end)
context('Default Metamethods', function()
local Peter, peter
before(function()
Peter = class('Peter')
peter = Peter()
end)
context('A Class', function()
test('has a call metamethod properly set', function()
assert_true(instanceOf(Peter, peter))
end)
test('has a tostring metamethod properly set', function()
assert_equal(tostring(Peter), 'class Peter')
end)
end)
context('An instance', function()
test('has a tostring metamethod, returning a different result from Object.__tostring', function()
assert_not_equal(Peter.__tostring, Object.__tostring)
assert_equal(tostring(peter), 'instance of class Peter')
end)
end)
end)
end)

53
spec/mixins_spec.lua Normal file
View File

@ -0,0 +1,53 @@
require 'middleclass'
context('A Mixin', function()
local Mixin1, Mixin2, Class1, Class2
before(function()
Mixin1, Mixin2 = {},{}
function Mixin1:included(theClass) theClass.includesMixin1 = true end
function Mixin1:foo() return 'foo' end
function Mixin1:bar() return 'bar' end
Mixin1.static = {}
Mixin1.static.bazzz = function() return 'bazzz' end
function Mixin2:baz() return 'baz' end
Class1 = class('Class1'):include(Mixin1, Mixin2)
function Class1:foo() return 'foo1' end
Class2 = class('Class2', Class1)
function Class2:bar2() return 'bar2' end
end)
test('invokes the "included" method when included', function()
assert_true(Class1.includesMixin1)
end)
test('has all its functions (except "included") copied to its target class', function()
assert_equal(Class1:bar(), 'bar')
assert_nil(Class1.included)
end)
test('makes its functions available to subclasses', function()
assert_equal(Class2:baz(), 'baz')
end)
test('allows overriding of methods in the same class', function()
assert_equal(Class2:foo(), 'foo1')
end)
test('allows overriding of methods on subclasses', function()
assert_equal(Class2:bar2(), 'bar2')
end)
test('makes new static methods available in classes', function()
assert_equal(Class1:bazzz(), 'bazzz')
assert_equal(Class2:bazzz(), 'bazzz')
end)
end)

74
spec/subclassOf_spec.lua Normal file
View File

@ -0,0 +1,74 @@
require 'middleclass'
context('subclassOf', function()
context('nils, integers, etc', function()
local primitives = {nil, 1, 'hello', {}, function() end}
for _,primitive in pairs(primitives) do
local theType = type(primitive)
context('A ' .. theType, function()
local f1 = function() return subclassOf(Object, primitive) end
local f2 = function() return subclassOf(primitive, o) end
local f3 = function() return subclassOf(primitive, primitive) end
context('does not throw errors', function()
test('subclassOf(Object, '.. theType ..')', function()
assert_not_error(f1)
end)
test('subclassOf(' .. theType .. ', Object:new())', function()
assert_not_error(f2)
end)
test('subclassOf(' .. theType .. ',' .. theType ..')', function()
assert_not_error(f3)
end)
end)
test('makes subclassOf return false', function()
assert_false(f1())
assert_false(f2())
assert_false(f3())
end)
end)
end
end)
context('Any class (except Object)', function()
local Class1 = class('Class1')
local Class2 = class('Class2', Class1)
local Class3 = class('Class3', Class2)
local UnrelatedClass = class('Unrelated')
test('is subclassOf(Object)', function()
assert_true(subclassOf(Object, Class1))
assert_true(subclassOf(Object, Class2))
assert_true(subclassOf(Object, Class3))
end)
test('is subclassOf its direct superclass', function()
assert_true(subclassOf(Class1, Class2))
assert_true(subclassOf(Class2, Class3))
end)
test('is subclassOf its ancestors', function()
assert_true(subclassOf(Class1, Class3))
end)
test('is a subclassOf its class\' subclasses', function()
assert_false(subclassOf(Class2, Class1))
assert_false(subclassOf(Class3, Class1))
assert_false(subclassOf(Class3, Class2))
end)
test('is not a subclassOf an unrelated class', function()
assert_false(subclassOf(UnrelatedClass, Class1))
assert_false(subclassOf(UnrelatedClass, Class2))
assert_false(subclassOf(UnrelatedClass, Class3))
end)
end)
end)