images/lua

The most recent version of this document can probably be found at http://mnoo.luaforge.net/index.html. It was compiled February, 19 2006.

1. Overview over mnoo

The purpose of mnoo is to provide an object oriented framework for the lua language.

It is closely related to the mnoofltk GUI library, which is built on top of mnoo.

Its object-oriented functionality can be used both with table and userdata type lua structures.


1.1 Targets

The mnoo framework aims to achieve the following targets:


1.2 compatibility / requirements

All code was developed for lua 5.0, according to the reference manual (implementation is pure lua code, no C level functions).


1.3 Status

mnoo is "beta" status. It hasn't been tested in large scale applications yet.


1.4 Examples

Since this manual is incomplete and will be probably never up-to-date, it is highly recommended to find some "real-world" example code.

The mnoo library itself contains test code for the most relevant features: mnoo.lua

Other files (probably not very polished) can be found from mnoofltk's code generation utility: build_mnoofltk.lua (toplevel), libBuilder_cl.lua, libBuilderType_cl.lua and libBuilderFun_cl.lua


2. Loading the library

 
-- ########################################################################################
-- # Load library
-- ########################################################################################      
local mnoo=require("mnoo_current/mnoo.lua")

This example loads the mnoo library into the local variable mnoo. The following examples will assume, that this has happened.


3. Classes


3.1 Creating new baseclass

An object represents data, and the class defines the functionality belonging to that data. Generally said, the purpose of a class is to collect everything that is common to a "class" of objects.

A new class is created "from scratch" using the newclass() function of the mnoo library:
 
-- ########################################################################################
-- # Create a new base class
-- ########################################################################################      
local creature_cl=mnoo.newclass()  

As a typing convention, we use variable names ending in "underscore cl" to denote, that the variable contains a class.


3.2 lua type

The lua type of a class is "table". The mnoo library function mnoo.isClass(x) returns true, if x is a valid class.


3.3 Declaring and defining class functions

A class function is declared, assigned and called using
 
my_cl:declareClassfunction('doSomething').
my_cl.doSomething=function(classarg, arg)
my_cl:doSomething(arg)

Redeclaring an already existing name (also from derived classes) is forbidden and causes an error.

Note, that the class appears as "classarg" argument. This is often needed, because the actual class, on which doSomething() is called may be derived from my_cl. One may not assume, that my_cl equals classarg (but classarg "is a" my_cl).


3.4 Deriving a class from another class (inheritance)

When giving one or more classes to the mnoo.newclass() function, the new class will be derived from the parent class and inherit its functionality.

3.5 Deriving a class from more than one parent class (multiple inheritance)

Multiple inheritance is possible by giving more than one argument to mnoo.newclass(), but currently there is no automatic way to invoke object constructors. Multiple inheritance is still useful to share common class / object functions between otherwise unrelated classes.


3.6 Relationship between objects / classes

The functions "objectIsA(obj, class)" and "classIsA(class1, class2)" can be used to verify, whether an object belongs to a class, or a class has been derived from another one. obj:objectIsA(class) can be called on any valid object, and class:classIsA(class) can be called on any valid class. They are also available through mnoo.objectIsA(obj, class) and mnoo.classIsA(class1, class2).

It is good practise to use "assert(obj:objectIsA(my_cl))" generously throughout the code, because it documents to the reader, what sort of object the variable contains.


3.7 Object methods

Declare: my_cl:declareObjectmethod('name1', 'name2', ...)

Assign: my_cl.name1=function(self, ...) assert(self:objectIsA(my_cl)) ... end


3.8 Creating objects

Every class has the default constructor function new(class).

The default constructor can be called with a single class argument:
 
local obj=my_cl:new()'')
It will return a new table, and attach the class functionality to it.

It is possible to use other names than "new" as constructor (simply declare it as classfunction).

Sometimes it is needed to forbid access to the default or inherited new{} constructor by assigning a function to it, that throws an error.

If called with two arguments, the object functionality gets attached to the given table argument:
 
local data={objdata1=10} local data2=my_cl:new(data) assert(data2==data)
It is recommended not to use this syntax, because there is no safety check for valid data - assigning values to non-objectdata fields may cause a lot of confusion later.


3.9 Overloading functions and constructors

 
-- ########################################################################################
-- # Overloading and calling of class functions
-- ########################################################################################

-- ########################################################################################
-- # Base class:
-- ########################################################################################
local c=lib.newclass() 
c:declareClassfunction("cfun")
c:declareObjectmethod("ofun")
c.cfun=
   function(actualclass, args) 
      assert(mnoo.classIsA(actualclass, c)) -- actualclass must be a child class of c
      return "onec"
   end

c.ofun=
   function(self, args) 
      assert(self:objectIsA(c)) -- class of self must be derived from c
      return "oneo"
   end

-- ########################################################################################
-- # Derived class:
-- ########################################################################################
local c2=lib.newclass(c)

local superclass_cfun=c2.cfun
c2.cfun=
   function() 
      return superclass_cfun().."twoc" 
   end

local superclass_ofun=c2.ofun
c2.ofun=
   function() 
      return superclass_ofun().."twoo" 
   end

assert(c.fun()=="onec")
assert(c2.fun()=="onectwoc")          
-- same for objects
-- program is untested.


3.10 Serialising and restoring objects

"mnoo" provides standard functions to generate code, that rebuilds a hierarchy of objects. This is typically used to save and restore the program state. It is a very powerful feature, because writing and debugging code to save/restore data can require a lot of work.

To enable serialisation for a class, it must be given a unique ID, that is used to look up the class when restoring the data.

 
lib.enable_serialize(my_cl, "test") 

In most practical cases, it is not desirable to store the complete content of an object, but only selected fields. Therefore, only fields that are declared with my_cl:declarePermanentObjectdata('name1', ...) are saved and restored.

It is generated using the library function
 
local ASCII=mnoo.serialise(data)

and the objects are rebuilt with the library function
 
local data=mnoo.deserialise(ASCII)

When deserialise() is called, all classes must be loaded.

Below an example for the generated, serialized code. It contains a single object with four data fields:
 
return function(constructors) return constructors.test{["perm2"]="b",["perm1"]="a",["multi"]="d",["perm3"]="c"} end


3.11 Private object functions (experimental)

Note, this isn't ready "for the masses" yet.

The section below deals with both how to use it and how it works internally at the same time, TODO.

When the code of an application grows in size, there is typically the following problem:

lua itself provides no mechanism for access control, as implemented in C++ through the the protected / private keywords.

mnoo provides an experimental mechanism that prevents calling object functions from outside a predefined private environment:

The file that implements the actual object functions generates a new private environment using

 
local env=mnoo.createPrivateEnvironment()
(copy needed globals to env)
setfenv(env)
createPrivateEnvironment() creates a new environment table from the current global table, and stores it in a "friends list" of my_cl. This changes the global environment _G from that point on (but using globals has never been a good idea, IMO -mn). The global environment gets attached to all functions created after the setfenv statement in the current file (or until you "setfenv()" it back).

Private object functions are now declared as usual, and then set to private using
 
my_cl:setPrivate("objfun1", "objfun2", ...)

From now on, any read access on an object will check, if the environment of the calling function matches one of the known "friendly" environments, and possibly produce an error.

Privacy is inherited, meaning that derived classes have access to the parent's class private functions.

As a technical note, implementing privacy for object functions is relatively easy, because they are never defined in the object and always trigger metamethods. Privacy for object data cannot be implemented the same way (or would be extremely inefficient).


4. args_cl

The "arguments" class is shipped with mnoo.lua. It provides a standard way to handle named function arguments. It is highly recommended to write readable and maintainable code (at least for functions unlike sin(x), where the meaning of arguments is obvious).

Using args_cl allows to write code that is "human-readable", without having to look up the implementations of functions called.

Example:
 
 -- ########################################################################################
 -- # Example for args_cl
 -- ########################################################################################

-- load library
local args_cl=require("mnoo_current/args_cl.lua")

local myfun=
   function(t)
      local t=args_cl:new(t) -- +++++++++++++ arg handling starts ++++++++++++++++++
      local stringarg=t:get{name="stringarg", type="string"}
      local numarg=t:get{name="numarg", type="number"}
      local boolarg=t:get{name="boolarg", type="boolean"}
      assert(t:all_done())   -- +++++++++++++ arg handling ends ++++++++++++++++++++ 

      print(stringarg, numarg, boolarg)
      return self
   end

myfun{stringarg="one", numarg="1", boolarg=true} -- OK ("1" gets converted to 1)

myfun{stringarg="one", numarg="1", boolarg=true, extra=1} -- causes error: extra argument

myfun{stringarg="one", numarg="one", boolarg=true, extra=1} -- causes error: "one" is nonnumeric

args_cl implementation

Obviously, it causes some overhead.

If performance is absolutely critical, don't use args_cl.

If performance is an issue, omit the all_done{} check.


5. License

The source files included in the distribution and this document are in the public domain and may be used freely. No warranty is given or implied.


6. Contact

images/email

[Top] [Contents] [Index] [ ? ]

Table of Contents



This document was generated by markus nentwig on February, 19 2006 using texi2html