class & interface
March 10, 2025 ยท View on GitHub
The classes are types that abstracted from a group of similar objects. The objects generated by the classes are tables with fixed meta-tables.
The class and interface only provide methods, meta-methods, construction and deconstruction to the objects. Things like property, event are registered as object features from outside.
Table of Contents
- object method
- object construction and the overload system
- object deconstruction
- meta-data
- static method
- Inheritance
- The multi-version class
- Appending methods
- Interface
- Event
- Property
- Inheritance and Priority
- Use other definition style
- Template class and interface
- System.Interface
- GetExtends
- GetFeature
- GetFeatures
- GetMethod
- GetMethods
- GetMetaMethod
- GetMetaMethods
- GetRequireClass
- GetSuperMethod
- GetSuperMetaMethod
- GetSuperFeature
- GetSuperRefer
- GetTemplate
- GetTemplateParameters
- HasAnonymousClass
- IsAbstract
- IsFinal
- IsImmutable
- IsSealed
- IsStaticMethod
- IsSubType
- ValidateValue
- Validate
- System.Class
- AttachObjectSource
- GetExtends
- GetFeature
- GetFeatures
- GetMethod
- GetMethods
- GetMetaMethod
- GetMetaMethods
- GetObjectClass
- GetObjectSource
- GetSuperClass
- GetSuperMethod
- GetSuperMetaMethod
- GetSuperFeature
- GetSuperObjectStyle
- GetSuperRefer
- GetTemplate
- GetTemplateParameters
- IsAbstract
- IsFinal
- IsImmutable
- IsObjectAttributeEnabled
- IsObjectFunctionAttributeEnabled
- IsObjectSourceDebug
- IsObjectType
- IsMethodAutoCache
- IsNilValueBlocked
- IsRawSetBlocked
- IsSealed
- IsSingleVersion
- IsStaticMethod
- IsSubType
- ValidateValue
- Validate
- System.Event
- System.Property
object method
We can declare object methods in the class's definition, so all its objects or child classes's objects can use them through their meta-tables.
require "PLoop" (function(_ENV)
class "Person" (function(_ENV)
function SetName(self, name)
self.name = name
end
function GetName(self, name)
return self.name
end
end)
Ann = Person()
Ann:SetName("Ann")
print("Hello " .. Ann:GetName()) -- Hello Ann
Ben = Person{ name = "Ben" }
print("Hello " .. Person.GetName(Ben)) -- Hello Ben
end)
Like the struct, the formal definition body of the class should be a function with _ENV as its first parameter.
In the definition, the global declared functions will be registered as the class's object method. The first parameter of those functions must be self to receive the objects.
When the definition is done, the meta-table of the class's objects will be generated, for a simple class like the Person, it has only object methods, so its object's meta-table will be:
{
__index = { SetName = Person.SetName, GetName = Person.GetName },
__metatable = Person,
}
We can create objects from the Person class by use it like a function, if we pass a table(also called the init table) as the parameter, the key-value pairs will be copied to the created object(not rawset, use obj[key] = value directly).
The Person class can access its object methods directly, but we need pass the object as the method's first argument.
object construction and the overload system
In the previous example, we use Person() or Person{ name = "Ben" } to create objects, that's the default behavior of the object creation.
__ctor
For classes, we could pass in several arguments to init the objects, that's done by declaring constructor:
require "PLoop" (function(_ENV)
class "Person" (function(_ENV)
function __ctor(self, name, age)
self.name = name
self.age = age
end
end)
Ann = Person("Ann", 12)
-- Ann 12
print(Ann.name, Ann.age)
Ben = Person{ name = "Ben" }
-- table: 00DA7F50
print(Ben.name)
end)
We can use the __ctor or the class name to declare the constructor, when an object is created by the class, the object and all arguments will be send to the constructor, so we can finish the init jobs to the created object.
When the constructor is defined, we can't use the init table or empty arguments to create the object, otherwise we could cause errors. To avoid the spreading of those errors, we need to validate the arguments and raise the error to the caller.
require "PLoop" (function(_ENV)
class "Person" (function(_ENV)
function __ctor(self, name, age)
if type(name) ~= "string" then
error("Usage: Person(name, age) - the name must be string")
end
if type(age) ~= "number" then
error("Usage: Person(name, age) - the age must be number")
end
self.name = name
self.age = age
end
end)
Ann = Person("Ann", 12)
-- Ann 12
print(Ann.name, Ann.age)
-- Error: Usage: Person(name, age) - the name must be string
Ben = Person(12)
-- Error: Usage: Person(name, age) - the age must be number
Ben = Person("Ben")
end)
There are some disadvantages to the argument validation:
-
We can't use
error(msg, stack)to raise the error message to the caller since the stack is unknown(with the inheritance system). -
When our project is fully test and released, those validation is useless, it's hard to remove all of them from the codes.
-
For complex arguments sets like
(name, age),(name),(age), it's hard to maintain the codes.
To simple the validation to functions, the PLoop provide the __Arguments__ attribute :
require "PLoop" (function(_ENV)
class "Person" (function(_ENV)
__Arguments__{ String, Number }
function __ctor(self, name, age)
self.name = name
self.age = age
end
end)
Ann = Person("Ann", 12)
-- Ann 12
print(Ann.name, Ann.age)
-- Error: Usage: Person(System.String, System.Number) - the 1st argument must be string, got table
Ben = Person{ name = "Ben" }
-- Error: Usage: Person(System.String, System.Number) - the 2nd argument can't be nil
Ben = Person("Ben")
end)
The String and Number are struct types for string and number data values. We could use any validatable data types like enum, struct, class and interface within the __Arguments__ attribute, so we don't need to check those arguments by ourselves.
Since the type validation is handled by the system, the error will be raised as an exception, and the system'll raise the exception at the caller. Also if we need raise an error to the caller, we can use the throw keyword:
require "PLoop" (function(_ENV)
class "A" (function(_ENV)
function __ctor(self)
throw("Something error")
end
end)
-- Error: something error
o = A()
end)
With the throw keyword, the system know it'd be raised to the caller.
When the project is released, we can turn off the type validation with the platform setting(declared before require the PLoop):
PLOOP_PLATFORM_SETTINGS = {
TYPE_VALIDATION_DISABLED = true,
}
require "PLoop" (function(_ENV)
class "Person" (function(_ENV)
__Arguments__{ String, Number }
function __ctor(self, name, age)
self.name = name
self.age = age
end
end)
Ann = Person("Ann", 12)
-- Ann 12
print(Ann.name, Ann.age)
-- no error
Ben = Person{ name = "Ben" }
-- no error
Ben = Person("Ben")
end)
We also can use the __Arguments__ to create multi constructors for different argument sets:
require "PLoop" (function(_ENV)
class "Person" (function(_ENV)
__Arguments__{ String, Number }
function __ctor(self, name, age)
self.name = name
self.age = age
end
__Arguments__{ String }
function __ctor(self, name)
this(self, name, 1)
end
__Arguments__{ Number }
function __ctor(self, age)
this(self, "anonymous", age)
end
end)
-- anonymous
print(Person(100).name)
-- 1
print(Person("Ann").age)
Ben = Person("Ben", 30)
-- Ben 30
print(Ben.name, Ben.age)
end)
This is normally called as the overload system, one method has several implementations.
Besides the constructor, we also could use the overload system for object method, static method and meta-method defined in the struct, class and interface(all features marked with System.AttributeTargets.Method)
For the constructor, we can use this(self, ...) to call other constructor implementations to simple the works, and only can use this for the constructors(also include the __new and __exist)
Since we only apply default values for the name and age in those constructors, we can combine them with the optional variable style:
require "PLoop" (function(_ENV)
class "Person" (function(_ENV)
__Arguments__{ String/"anonymous", Number/1 }
function __ctor(self, name, age)
self.name = name
self.age = age
end
end)
-- anonymous
print(Person(nil, 100).name)
-- 1
print(Person("Ann").age)
Ben = Person("Ben", 30)
-- Ben 30
print(Ben.name, Ben.age)
nobody = Person()
-- anonymous 1
print(nobody.name, nobody.age)
end)
The type/default style is used to declare optional variable, we also can use type/nil if there is no default value.
The optional variable must be used after the required variables, or an error would be raised.
require "PLoop" (function(_ENV)
class "Person" (function(_ENV)
-- Error : Usage: __Arguments__{ ... } - the non-optional variables must exist before the optional variables
__Arguments__{ String/"anonymous", Number }
function __ctor(self, name, age)
self.name = name
self.age = age
end
end)
end)
__field
In the previous examples, we only init fields of the object with arguments, we also can declare those fields directly:
require "PLoop" (function(_ENV)
class "Person" (function(_ENV)
-- the __field is a meta-data used to declare the default fields
__field = { name = "anonymous" }
-- we also can use "field" as keyword to declare the fields
-- it won't discard the previous field settings
field { age = 1 }
end)
-- anonymous
print(Person{ age = 10 }.name)
-- 1
print(Person{ name = "Ann" }.age)
Ben = Person{ name = "Ben", age = 30 }
-- Ben 30
print(Ben.name, Ben.age)
nobody = Person()
-- anonymous 1
print(nobody.name, nobody.age)
end)
So we can re-use the init-table to create the objects, the field settings are only used as the default values. It's very useful with the restrict coding mode of the PLoop, we'll see it in other chapters.
__new
The __ctor will receive the created objects, the objects are tables created by the system with the fixed meta-table. But for classes like List:
require "PLoop" (function(_ENV)
class "List" (function(_ENV)
function __ctor(self, ...)
for i = 1, select("#", ...) do
self[i] = select(i, ...)
end
end
end)
lst = List(1, 2, 3, ...)
end)
Fill all arguments into the list object will be a greater cost to the system, if we can create the object by our own, it'll be more simple, here is the __new meta-method.
require "PLoop" (function(_ENV)
class "List" (function(_ENV)
function __new(cls, ...)
return { ... }, true
end
end)
lst = List(1, 2, 3, ...)
end)
The __new method will receive the class and all arguments, it'd return a table(without the meta-table) as the new object, the system will set the meta-table to the returned object. The second return value means whether discard the arguments since they are handled by the __new method.
The object will be created and filled with data at once, there is no more iterator operations.
We also can use the __Arguments__ to control the arguments types for the List class:
require "PLoop" (function(_ENV)
class "List" (function(_ENV)
__Arguments__{ Number * 2 }
function __new(cls, ...)
return { ... }, true
end
end)
-- Error: Usage: List(... [*2] as System.Number) - the ... must contains at least 2 arguments
lst = List(1)
-- Error: Usage: List(... [*2] as System.Number) - the 4th argument must be number, got string
lst = List(1, 2, 3, "hi")
-- 5
print(#List(1, 2, 3, 4, 5))
end)
The type * N means the varargs variable, the N means the minimal count, so normally we use type * 0 which means there could be any number values of the types.
__exist
To reduce the cost of the object creation, we may try to re-use the objects:
require "PLoop" (function(_ENV)
class "Person" (function(_ENV)
_Persons = {}
function __exist(cls, name)
return _Persons[name]
end
function __ctor(self, name)
_Persons[name] = self
self.name = name
end
end)
Ann = Person("Ann")
-- true
print(Ann == Person("Ann"))
end)
The __exist will receive the class and all arguments, so it can check whether there is an object created with the same arguments and return the object instead of creating a new one.
the construction of an object
So we have __exist, __new, __field and __ctor, they are all used during the creation of the object, the fake code for the object's construction is :
-- Check whether existed
local object = __exist(cls, ...)
if object then return object end
-- Get a table as the new object
object = __new(cls, ...) or {}
-- Clone the field
copy(object, __field)
-- Wrap to the object
setmetatable(object, objMeta)
-- Call the constructor
__ctor(object, ...)
return object
the __new with init-table
Normally we can't use init-table if we declared the constructor of the class, but if we only decalred the __new without discarding the argument, we still can use the init-table like
require "PLoop" (function(_ENV)
class "Person" (function(_ENV)
function __new(_, ...) return {} end
end)
o = Person{ name = "Ann" }
print(o.name) -- Ann
end)
object deconstruction
Normally, the Lua GC will handle the unused objects very well, but we still need manually dispose the object to erase its reference so the GC can collect it.
We can use __dtor or Dispose to define the deconstructor method of the class objects, the deconstructor will only receive the object as its argument.
require "PLoop" (function(_ENV)
class "Person" (function(_ENV)
_Persons = {}
function __dtor(self)
_Persons[self.name] = nil
end
function __exist(self, name)
return _Persons[name]
end
function __ctor(self, name)
self.name = name
_Persons[name] = self
end
end)
Ann = Person("Ann")
-- true
print(Ann == Person("Ann"))
Ann:Dispose()
-- false
print(Ann == Person("Ann"))
end)
If the class has define the deconstructor, the object can use Dispose method to dispose itself(so don't use Dispose for other usages). We'll see more about it at later.
Nomrally when an object is disposed, its contents will be wiped out, and its Disposed field will be set to true as a mark. But in some scenarios, we may want recycle the object, we can clear its relationship by the dispose method, but we may want to keep its contents or handle them by the class itself. We can use an attribute System.__Recyclable__ to the class:
require "PLoop" (function(_ENV)
class "Person" { __dtor = function(self) print("Dispose " .. self.Name) end }
Ann = Person{ Name = "Ann" }
-- Dispose Ann
Ann:Dispose()
-- nil true
print(Ann.Name, Ann.Disposed)
__Recyclable__()
class "Person" { }
Ann = Person{ Name = "Ann" }
-- Dispose Ann
Ann:Dispose()
-- Ann nil
print(Ann.Name, Ann.Disposed)
end)
meta-data
The class system is used to build the meta-table for objects, so it also support all meta-method of the Lua:
require "PLoop" (function(_ENV)
class "Delegate" (function(_ENV)
__Arguments__{ Function }
function __add(self, func)
for i = 1, #self do
if self[i] == func then return end
end
self[#self + 1] = func
return self
end
__Arguments__{ Function }
function __sub(self, func)
for i = #self, 1 do
if self[i] == func then
table.remove(self, i)
break
end
end
return self
end
function __call(self, ...)
for i = 1, #self do
self[1](...)
end
end
end)
del = Delegate()
del = del + print
-- 1 2 3
del(1, 2, 3)
del = del - print
-- nothing happen
del(1, 2, 3)
end)
Besides those meta-method, the PLoop also use __new, __ctor, __dtor, __field and __exist as it's meta-data(not only method), here is the full list:
| Key | Description |
|---|---|
__add | the addition operation: a + b -- a is the object, also for the below operations |
__sub | the subtraction operation: a - b |
__mul | the multiplication operation: a * b |
__div | the division operation: a / b |
__mod | the modulo operation: a % b |
__pow | the exponentiation operation: a ^ b |
__unm | the negation operation: - a |
__idiv | the floor division operation: a // b |
__band | the bitwise AND operation: a & b |
__bor | the bitwise OR operation: a |
__bxor | the bitwise exclusive OR operation: a~b |
__bnot | the bitwise NOToperation: ~a |
__shl | the bitwise left shift operation: a<<b |
__shr | the bitwise right shift operation: a>>b |
__concat | the concatenation operation: a..b |
__len | the length operation: #a |
__eq | the equal operation: a == b |
__lt | the less than operation: a < b |
__le | the less equal operation: a <= b |
__index | the indexing access: return a[k] |
__newindex | the indexing assignment: a[k] = v |
__call | the call operation: a(...) |
__gc | the garbage-collection |
__tostring | the convert to string operation: tostring(a) |
__ipairs | the ipairs iterator: ipairs(a) |
__pairs | the pairs iterator: pairs(a) |
__exist | the object existence checker |
__field | the init object fields, must be a table |
__new | the function used to generate the table that'd be converted to an object |
__ctor | the object constructor |
__dtor | the object destructor |
Unlike the meta-data used by the PLoop. You must check the Lua version before using those meta-methods provided by the Lua.
static method
Besides the object method, the class can have its own static methods. The static methods can only be used by the class itself, also don't need use self as its first parameter.
require "PLoop" (function(_ENV)
class "Person" (function(_ENV)
local count = 0
__Static__()
function GetCount()
return count
end
function __ctor(self, name)
count = count + 1
self.name = name
end
end)
Ann = Person("Ann")
Ben = Person("Ben")
-- nil
print(Ann.GetCount)
-- 2
print(Person.GetCount())
end)
The static method can only be accessed by the Person class, it also don't use self as its first argument.
We also could use the __Arguments__ attribute for the static method, unlike the object method, the type validation will start at the first argument.
require "PLoop" (function(_ENV)
class "Person" (function(_ENV)
local count = 0
__Static__()
__Arguments__{ String/"anonymous" }
function New(name)
return Person{ name = name }
end
end)
Ann = Person.New("Ann")
-- Ann
print(Ann.name)
end)
Inheritance
The class can and only can have one super class, the class will inherit the super class's object methods, meta-data settings and other object features(like event, property).
If the class has override the super class's object method, meta-data or object feature, the class could use super keyword to access the super class's method, meta-data or object feature.
require "PLoop"
PLoop(function(_ENV)
class "A" (function(_ENV)
-- Object method
function Test(self)
print("Call A's method")
end
-- Constructor
function __ctor(self)
print("Call A's ctor")
end
-- Destructor
function __dtor(self)
print("Dispose A")
end
-- Meta-method
function __call(self)
print("Call A Object")
end
end)
class "B" (function(_ENV)
inherit "A" -- also can use inherit(A)
function Test(self)
print("Call super's method ==>")
super[self]:Test()
super.Test(self)
print("Call super's method ==<")
end
function __ctor(self)
super(self)
print("Call B's ctor")
end
function __dtor(self)
print("Dispose B")
end
function __call(self)
print("Call B Object")
super[self]:__call()
super.__call(self)
end
end)
-- Call A's ctor
-- Call B's ctor
o = B()
-- Call super's method ==>
-- Call A's method
-- Call A's method
-- Call super's method ==<
o:Test()
-- Call B Object
-- Call A Object
-- Call A Object
o()
-- Dispose B
-- Dispose A
o:Dispose()
end)
From the example, here are some details:
-
The
inherit "A"is a syntax sugar, is the same likeinherit(A), since the environment can access the A by its name. -
The destructor don't need call super's destructor, they are well controlled by the system, so the class only need to consider itself.
-
The constructor need call super's constructor manually, since only the class know what arguments should be used to call the super class's constructor.
-
For the object method and meta-method(include the
__newand__exist, we have two style to call its super:-
super.Test(self)is a simple version, it can only be used to call method or meta-method. -
super[self]:Test()is the formal version, since the self is passed to super before access the Test method, the super'd know the class version of the object and used the correct version methods. This is used for multi-version classes(by default, re-define a class would create two different version), also for features like properties and events(we'll see them later).
-
-
The static methods won't be inherited.
The multi-version class
If we don't use the __Sealed__ to seal the classes, we still can re-define it, unlike the struct, redefine a class will only add or override the previous definition, not wipe them.
Take an example:
require "PLoop" (function(_ENV)
class "A" (function(_ENV)
function test(self)
print("hi")
end
end)
o = A()
class "A" (function(_ENV)
function test(self)
print("hello")
end
end)
o:test() -- hi
A():test() -- hello
end)
The old object won't receive the updating, so we have two version objects of the same class. It's designed to make sure the new definition won't break the old object(use some new fields don't existed in the old object and etc).
If we need a class whose object will receive all updatings, we must use the System.__SingleVer__ to mark it, so it'll always keep only one versions:
require "PLoop"
PLoop(function(_ENV)
__SingleVer__()
class "A" (function(_ENV)
function test(self)
print("hi")
end
end)
o = A()
class "A" (function(_ENV)
function test(self)
print("hello")
end
end)
o:test() -- hello
A():test() -- hello
end)
So the old object will receive all updatings. If you need this to be default behaviors, you can modify the platform settings like :
PLOOP_PLATFORM_SETTINGS = { CLASS_NO_MULTI_VERSION_CLASS = true }
require "PLoop"
PLoop(function(_ENV)
class "A" (function(_ENV)
function test(self)
print("hi")
end
end)
o = A()
class "A" (function(_ENV)
function test(self)
print("hello")
end
end)
o:test() -- hello
A():test() -- hello
end)
Appending methods
There is another way to append methods without re-define the classes:
require "PLoop"
PLoop(function(_ENV)
__Sealed__()
class "A" (function(_ENV)
function test(self)
print("hi")
end
end)
o = A()
function A:test2()
print("hello")
end
o:test2() -- hello
end)
We can assign new object method or static method to the classes without a full re-definition. So, all object can receive the new method.
It also can be used on the sealed classes. Also we can use it on the interfaces.
We can't use this on the struct type, the method in a struct is copied to the data, if we add a method to a struct with no method, it'll change the struct from immutable to mutable, it's not allowed.
Interface
The interfaces are abstract types of functionality, it also provided the multi-inheritance mechanism to the class. Like the class, it support object method, static method and meta-datas.
The class and interface can extend many other interfaces, the super keyword'll choose the super object method, meta-method or object features based on the inheritance priority.
The interface use __init instead of the __ctor as the interface's initializer. The initializer only receive the object as it's parameter, and don't like the constructor, the initializer can't be accessed by super keyword. The method defined with the interface's name will also be used as the initializer.
If you only want defined methods and features that should be implemented by child interface or class, you can use __Abstract__ on the method or the feature, those abstract methods and featuers can't be accessed by super keyword.
Let's take an example :
require "PLoop"
PLoop(function(_ENV)
interface "IName" (function(_ENV)
__Abstract__()
function SetName(self) end
__Abstract__()
function GetName(self) end
-- initializer
function __init(self) print("IName Init") end
-- destructor
function __dtor(self) print("IName Dispose") end
end)
interface "IAge" (function(_ENV)
__Abstract__()
function SetAge(self) end
__Abstract__()
function GetAge(self) end
-- initializer
function __init(self) print("IAge Init") end
-- destructor
function __dtor(self) print("IAge Dispose") end
end)
class "Person" (function(_ENV)
extend "IName" "IAge" -- also can use `extend(IName)(IAge)`
-- Error: attempt to index global 'super' (a nil value)
-- Since there is no super method(the IName.SetName is abstract),
-- there is no super keyword can be use
function SetName(self, name) super[self]:SetName(name) end
function __ctor(self) print("Person Init") end
function __dtor(self) print("Person Dispose") end
end)
-- Person Init
-- IName Init
-- IAge Init
o = Person()
-- IAge Dispose
-- IName Dispose
-- Person Dispose
o:Dispose()
end)
Here are the details for the interface:
-
The initializers are called by the system after the object is created by the class, unlike the super class's constructor who is called by the class's constructor or used directly.
-
The initializers are called based on the extend orders, if an interface has extended another one, its initializer'll be called after its super interfaces.
-
The deconstruction order is the reversion of the construction. There is no need call super class or extended interfaces deconstructors manually.
-
The super keyword can't access abstract object method, abstract meta-method or abstract feature. The system will check if an interface or class could use the super keyword, remove the super could reduce cost of the object's accessing.
Anonymous class of interface
If we use System.__AnonymousClass__ attribute to an interface, the interface will create an anonymous class that extend itself, we can't use the anonymous class directly, but we can use the interface like we use a class:
require "PLoop"
PLoop(function(_ENV)
__AnonymousClass__()
interface "ITask" (function(_ENV)
__Abstract__() function Process()
end
end)
o = ITask{ Process = function() print("Hello") end }
o:Process()
end)
The interface can only accept a table as the init-table to generate the object.
But for the interface with only one abstract method, we can use a simple style
require "PLoop"
PLoop(function(_ENV)
__AnonymousClass__()
interface "ITask" (function(_ENV)
__Abstract__() function Process()
end
end)
o = ITask(function() print("Hello") end)
o:Process()
end)
We can pass a function as the implement of the abstract method to generate the object.
If you want all interface can be use as this, you can modify the platform settings(not recommend):
PLOOP_PLATFORM_SETTINGS = { INTERFACE_ALL_ANONYMOUS_CLASS = true }
require "PLoop"
PLoop(function(_ENV)
interface "ITask" (function(_ENV)
__Abstract__() function Process()
end
end)
o = ITask(function() print("Hello") end)
o:Process()
end)
the require class of the interface
We can use the require keyword to set a class to the interface, so all classes that extend the interface must be the sub-types of the class:
require "PLoop"
PLoop(function(_ENV)
class "A" {}
interface "IA" (function(_ENV)
require "A"
end)
class "B" (function(_ENV)
extend "IA" -- Error: interface.AddExtend(target, extendinterface[, stack]) - the class must be A's sub-class
end)
end)
Event
The events are type features used to notify the outside that the state of class object has changed. Let's take an example to start :
require "PLoop"
PLoop(function(_ENV)
class "Person" (function(_ENV)
-- declare an event for the class
event "OnNameChanged"
field { name = "anonymous" }
function SetName(self, name)
if name ~= self.name then
-- Notify the outside
OnNameChanged(self, name, self.name)
self.name = name
end
end
end)
o = Person()
-- Bind a function as handler to the event
function o:OnNameChanged(new, old)
print(("Renamed from %q to %q"):format(old, new))
end
-- Renamed from "anonymous" to "Ann"
o:SetName("Ann")
end)
The event is a feature type for the class and interface, there are two types of the event handler :
-
the final handler - the previous example has shown how to bind the final handler.
-
the stackable handler - The stackable handler are normally used in the class's constructor or interface's initializer:
require "PLoop"
PLoop(function(_ENV)
class "Person" (function(_ENV)
-- declare an event for the class
event "OnNameChanged"
field { name = "anonymous" }
function SetName(self, name)
if name ~= self.name then
-- Notify the outside
OnNameChanged(self, name, self.name)
self.name = name
end
end
end)
class "Student" (function(_ENV)
inherit "Person"
local function onNameChanged(self, name, old)
print(("Student %s renamed to %s"):format(old, name))
end
function Student(self, name)
self:SetName(name)
self.OnNameChanged = self.OnNameChanged + onNameChanged
end
end)
o = Student("Ann")
function o:OnNameChanged(name)
print("My new name is " .. name)
end
-- Student Ann renamed to Ammy
-- My new name is Ammy
o:SetName("Ammy")
end)
The self.OnNameChanged is an object generated by the System.Delegate class who has __add and __sub meta-methods so it can works with the style like
self.OnNameChanged = self.OnNameChanged + onNameChanged
or
self.OnNameChanged = self.OnNameChanged - onNameChanged
The stackable handlers are added with orders, so the super class's handler'd be called at first then the class's, then the interface's. The final handler will be called at the last, if any handler return true, the call process will be ended.
In some scenarios, we need to block the object's event, the Delegate can set an init function that'd be called before all other handlers, we can use
self.OnNameChanged:SetInitFunction(function() return true end)
To block the object's OnNameChanged event.
The attribute for event handlers
When binding any event handlers, we can use attributes for them, but since the attribute may wrap the handler as a new function, you can't remove it like self.OnNameChanged = self.OnNameChanged - xxx.
require "PLoop" (function(_ENV)
class "Person" (function(_ENV)
event "OnNameChanged"
function SetName(self, name)
self.name = name
OnNameChanged(self, name)
end
end)
ann = Person()
__Async__()
function ann:OnNameChanged(new)
print("Final:", coroutine.running(), new)
end
__Async__()
ann.OnNameChanged = ann.OnNameChanged + function(self, new)
print("Stack:", coroutine.running(), new)
end
-- Stack: thread: 00B90ECC Ann
-- Final: thread: 00B90ECC Ann
ann:SetName("Ann")
end)
The event of the event handler changes
When using PLoop to wrap objects generated from other system, we may need to bind the PLoop event to other system's event, there is two parts in it :
-
When the PLoop object event handlers are changed, we need know when and whether there is any handler for that event, so we can register or un-register in the other system.
-
When the event of the other system is triggered, we need invoke the PLoop's event.
Take the Frame widget from the World of Warcraft as an example, ignore the other details, let's focus on the event two-way binding :
require "PLoop"
PLoop(function(_ENV)
class "Frame" (function(_ENV)
__EventChangeHandler__(function(delegate, owner, eventname, init)
-- delegate is the object whose handlers are changed
-- owner is the frame object, also the owner of the delegate
-- eventname is the OnEnter for this case
-- init is only provided when the delegate created with true value
if delegate:IsEmpty() then
-- No event handler, so un-register the frame's script event
owner:SetScript(eventname, nil)
else
-- Has event handler, so we must regiser the frame's script event
if owner:GetScript(eventname) == nil then
owner:SetScript(eventname, function(self, ...)
-- Call the delegate directly
delegate(owner, ...)
end)
end
end
end)
event "OnEnter"
end)
end)
With the __EventChangeHandler__ attribute, we can bind a function to the target event, so all changes of the event handlers can be checked in the function. Since the event change handler has nothing special with the target event, we can use it on all script events in one system like :
require "PLoop"
PLoop(function(_ENV)
local function changehandler (delegate, owner, eventname)
if delegate:IsEmpty() then
owner:SetScript(eventname, nil)
else
if owner:GetScript(eventname) == nil then
owner:SetScript(eventname, function(self, ...)
-- Call the delegate directly
delegate(owner, ...)
end)
end
end
end
function __WidgetEvent__(self)
__EventChangeHandler__(changehandler)
end
class "Frame" (function(_ENV)
__WidgetEvent__()
event "OnEnter"
__WidgetEvent__()
event "OnLeave"
end)
end)
Static event
The event can also be marked as static, so it can be used and only be used by the class or interface :
require "PLoop"
PLoop(function(_ENV)
class "Person" (function(_ENV)
__Static__()
event "OnPersonCreated"
function Person(self, name)
OnPersonCreated(name)
end
end)
function Person.OnPersonCreated(name)
print("Person created " .. name)
end
-- Person created Ann
o = Person("Ann")
end)
super event
When the class or interface has overridden the event, and they need register handler to super event, we can use the super object access style :
require "PLoop"
PLoop(function(_ENV)
class "Person" (function(_ENV)
-- declare an event for the class
event "OnNameChanged"
field { name = "anonymous" }
function SetName(self, name)
if name ~= self.name then
-- Notify the outside
OnNameChanged(self, name, self.name)
self.name = name
end
end
end)
class "Student" (function(_ENV)
inherit "Person"
event "OnNameChanged"
local function raiseEvent(self, ...)
OnNameChanged(self, ...)
end
function Student(self)
super(self)
-- Use the super object access style
super[self].OnNameChanged = raiseEvent
end
end)
o = Student()
function o:OnNameChanged(name)
print("New name is " .. name)
end
-- New name is Test
o:SetName("Test")
end)
As we can see, the child class can listen the super's event and then raise its own event.
Property
The properties are object states, we can use the table fields to act as the object states, but they lack the value validation, and we also can't track the modification of those fields.
Like the event, the property is also a feature type for the interface and class. The property system provide many mechanisms like get/set, value type validation, value changed handler, value changed event, default value and default value factory. Let's start with a simple example :
require "PLoop"
PLoop(function(_ENV)
class "Person" (function(_ENV)
property "Name" { type = String }
property "Age" { type = Number }
end)
o = Person{ Name = "Ann", Age = 10 }
print(o.Name)-- Ann
o.Name = 123 -- Error : the Name must be string, got number
end)
The Person class has two properties: Name and Age, the table after property "Name" is the definition of the Name property, it contains a type field that contains the property value's type, so when we assign a number value to the Name, the operation is failed.
Like the member of the struct, we use table to give the property's definition, the key is case ignored, here is a full list:
| Field | Usage |
|---|---|
| auto | whether use the auto-binding mechanism for the property see blow example for details. |
| get | the function used to get the property value from the object like get(obj), also you can set false to it, so the property can't be read |
| set | the function used to set the property value of the object like set(obj, value), also you can set false to it, so the property can't be written |
| getmethod | the string name used to specified the object method to get the value like obj[getmethod](obj) |
| setmethod | the string name used to specified the object method to set the value like obj[setmethod](obj, value) |
| field | the table field to save the property value, no use if get/set specified, like the Name of the Person, since there is no get/set or field specified, the system will auto generate a field for it, it's recommended. |
| type | the value's type, if the value is immutable, the type validation can be turn off for release version, just turn on TYPE_VALIDATION_DISABLED in the PLOOP_PLATFORM_SETTINGS |
| default | the default value, must match the type setting |
| factory | the default value factory, used to generate the default value of the property |
| event | the event used to handle the property value changes, if it's value is string, an event will be created |
| handler | the function used to handle the property value changes, unlike the event, the handler is used to notify the class or interface itself, normally this is used combine with field (or auto-gen field), so the class or interface only need to act based on the value changes |
| static | true if the property is a static property |
| indexer | true if the property is an indexer property |
| throwable | true if the property's set method'll throw errors |
| require | true if can't use nil as value |
We'll see examples for each case:
get/set
require "PLoop"
PLoop(function(_ENV)
class "Person" (function(_ENV)
field { __name = "anonymous" }
property "Name" {
get = function(self) return self.__name end,
set = function(self, name) self.__name = name end,
}
end)
print(Person().Name)
end)
getmethod/setmethod
require "PLoop"
PLoop(function(_ENV)
class "Person" (function(_ENV)
field { __name = "anonymous" }
function SetName(self, name)
self.__name = name
end
function GetName(self)
return self.__name
end
property "Name" {
get = "GetName", -- or getmethod = "GetName"
set = "SetName", -- or setmethod = "SetName"
}
end)
print(Person().Name)
end)
property-throw
require "PLoop"
PLoop(function(_ENV)
class "Person" (function(_ENV)
field { __name = "anonymous" }
function SetName(self, name)
if type(name) ~= "string" then
throw("The name must be string")
end
self.__name = name
end
function GetName(self)
return self.__name
end
property "Name" {
get = "GetName", -- or getmethod = "GetName"
set = "SetName", -- or setmethod = "SetName"
throwable = true,
}
end)
Person().Name = 123 -- Error: The name must be string
end)
field & default
require "PLoop"
PLoop(function(_ENV)
class "Person" (function(_ENV)
property "Name" { field = "__name", default = "anonymous" }
property "Age" { type = Number } -- the field'll be auto generated
end)
obj = Person()
print(obj.Name, obj.__name) -- anonymous nil
obj.Name = "Ann"
obj.Age = 24
-- __name Ann
-- _Person_Age 24
for k, v in pairs(obj) do
print(k, v)
end
end)
default factory
If your property type is not function type, you can use default instead of the factory, it's the same.
require "PLoop"
PLoop(function(_ENV)
class "Person" (function(_ENV)
property "Age" { field = "__age", factory = function(self) return math.random(100) end }
end)
obj = Person()
print(obj.Age, obj.__age) -- 81 81
obj.Age = nil -- so the factory will works again
print(obj.Age, obj.__age) -- 88 88
end)
property-event
require "PLoop"
PLoop(function(_ENV)
class "Person" (function(_ENV)
property "Name" { type = String, event = "OnNameChanged" }
end)
o = Person { Name = "Ann" }
function o:OnNameChanged(new, old, prop)
print(("[%s] %s -> %s"):format(prop, old, new))
end
-- [Name] Ann -> Ammy
o.Name = "Ammy"
end)
property-handler
require "PLoop"
PLoop(function(_ENV)
class "Person" (function(_ENV)
property "Name" {
type = String, default = "anonymous",
handler = function(self, new, old, prop)
print(("[%s] %s -> %s"):format(prop, old, new))
end
}
end)
--[Name] anonymous -> Ann
o = Person { Name = "Ann" }
--[Name] Ann -> Ammy
o.Name = "Ammy"
end)
static property
require "PLoop"
PLoop(function(_ENV)
class "Person" (function(_ENV)
__Static__()
property "DefaultName" { type = String }
property "Name" {
type = String, default = function() return Person.DefaultName end,
}
end)
Person.DefaultName = "noname"
print(Person().Name) -- noname
end)
Auto-binding
If using the auto-binding mechanism and the definition don't provide get/set, getmethod/setmethod and field, the system will check the property owner's method(object method if non-static, static method if it is static), take an example if our property name is "name":
-
The setname, Setname, SetName, setName will be scanned, if it existed, the method will be used as the set setting
-
The getname, Getname, Isname, isname, getName, GetName, IsName, isname will be scanned, if it exsited, the method will be used as the get setting
require "PLoop"
PLoop(function(_ENV)
class "Person" (function(_ENV)
function SetName(self, name)
print("SetName", name)
end
property "Name" { type = String, auto = true }
end)
-- SetName Ann
o = Person { Name = "Ann"}
-- SetName Ammy
o.Name = "Ammy"
end)
super property
When the class or interface has overridden the property, they still can use the super object access style to use the super's property :
require "PLoop"
PLoop(function(_ENV)
class "Person" (function(_ENV)
property "Name" { type = String }
end)
class "Student" (function(_ENV)
inherit "Person"
property "Name" {
Set = function(self, name)
-- Use super property to save
super[self].Name = name
end,
Get = function(self)
-- Use super property to fetch
return super[self].Name
end,
}
end)
o = Student()
o.Name = "Test"
print(o.Name) -- Test
end)
indexer property
We also can build indexer properties like :
require "PLoop"
PLoop(function(_ENV)
class "A" (function( _ENV )
__Indexer__()
property "Items" {
set = function(self, idx, value)
self[idx] = value
end,
get = function(self, idx)
return self[idx]
end,
type = String,
}
end)
o = A()
o.Items[1] = "Hello"
print(o.Items[1]) -- Hello
end)
The indexer property can only accept set, get, getmethod, setmethod, type and static definitions.
We also can give a type for the indexer property's key:
require "PLoop"
PLoop(function(_ENV)
class "A" (function( _ENV )
__Indexer__(String)
property "Items" {
set = function(self, idx, value)
self[idx] = value
end,
get = function(self, idx)
return self[idx]
end,
type = String,
}
end)
o = A()
-- Error: the Items's key must be string, got number
o.Items[1] = "Hello"
end)
Get/Set Modifier
Beside those settings, we still can provide some behavior modifiers on the properties.
For property set, we have System.PropertySet to describe the value set behavior:
__Flags__() __Default__(0)
enum "System.PropertySet" {
Assign = 0, -- assign directly
Clone = 1, -- save the clone of the value
DeepClone = 2, -- save the deep clone of the value
Retain = 4, -- should dispose the old value
Weak = 8, -- save the value as weak mode
}
For property get, we have System.PropertyGet to describe the value get behavior:
__Flags__() __Default__(0)
enum "System.PropertyGet" {
Origin = 0, -- return the value directly
Clone = 1, -- return a clone of the value
DeepClone = 2, -- return a deep clone of the value
}
To apply them on the property, we need System.__Set__ and System.__Get__ attributes:
require "PLoop"
PLoop(function(_ENV)
class "Data" (function(_ENV)
extend "ICloneable" -- cloneable class must extend this interface
local _Cnt = 0
-- Implement the Clone method
function Clone(self)
return Data() -- for test, just return a new one
end
function Dispose(self)
print("Dispose Data " .. self.Index)
end
function __ctor(self)
_Cnt = _Cnt + 1
self.Index = _Cnt
end
end)
class "A" (function(_ENV)
__Set__(PropertySet.Clone + PropertySet.Retain)
__Get__(PropertySet.Clone)
property "Data" { type = Data }
end)
o = A()
dt = Data()
o.Data = dt
print(dt.Index, o.Data.Index) -- 1 3
o.Data = nil -- Dispose Data 2
end)
Init only
We can declare a property to be init-only, so it can only be set in the constructor:
PLOOP_PLATFORM_SETTINGS = { MULTI_OS_THREAD = false }
require "PLoop" (function(_ENV)
class "A" (function(_ENV)
property "Name" { init = true }
function __ctor(self, name)
self.Name = name
end
end)
o = A("Ann")
o.Name = 123 -- lua: xxxxx.lua:13: the Name is init-only
end)
Inheritance and Priority
A class can extend many interfaces and inherit one super class who'll have its own extended interfaces and super class.
If those extend interfaces and super classes have the same name feature(method, property, event, meta-method), the system will choose the nearest:
-
check the super class, then the super class's super class, repeat until no more super classes.
-
check the interfaces, the latest extended interface should be checked first.
Those are done by the system, so we don't need to control it, but we may affect it by give priority to those features with the System.__Abstract__ and System.__Final__ attributes:
-
If a feature(method, meta-method, property or event) marked with
__Abstract__, the feature's priority is the lowest. -
If a feature marked with
__Final__, the feature's priority is the highest.
Here is an example:
require "PLoop"
PLoop(function(_ENV)
interface "IA" (function(_ENV)
__Final__()
function Test(self)
print("Hello IA")
end
__Abstract__()
function Test2(self)
print("Hello2 IA")
end
end)
class "A" (function(_ENV)
extend "IA"
function Test(self)
print("Hello A")
end
function Test2(self)
print("Hello2 A")
end
end)
o = A()
o:Test() -- Hello IA
o:Test2() -- Hello2 A
end)
There is also a special usage of the __Final__ attribute, we can define the final method in the interface or abstract class, and call the object's own method with a trick:
require "PLoop"
PLoop(function(_ENV)
interface "IA" (function(_ENV)
__Final__() function Test(self)
print("Call Test of IA")
-- get the object's class, use the normal Test method of the class to do the real job
Class.GetNormalMethod(Class.GetObjectClass(self), "Test")(self)
end
end)
class "A" { IA, Test = function(self) print("Call Test of A") end }
o = A()
-- Call Test of IA
-- Call Test of A
o:Test()
end)
The object will use the final method, but the class'll keep its own version.
Use other definition style
Use string as definition body
The struct, interface and class can use string as the definition body, it's very simple, just have one example:
PLoop(function(_ENV)
class "A" [[
property "Name" { default = "anonymous" }
]]
print(A().Name)
end)
Just replace the function(_ENV) and end) to the start and end of the string.
Use table as definition body
We already see Table Style Definition for the struct, it's also possible to define interface or class with tables:
require "PLoop"
PLoop(function(_ENV)
class "Person" {
-- Declare a static event
-- it's not good to use the event in table
OnPersonCreated = true,
-- Declare an object event
OnNameChanged = false,
-- Define property, we can use type
-- or a table for the property
Name = String,
Age = { type = Number, default = 0 },
-- Define a object method
SetName = function(self, name)
self:OnNameChanged(name, self.Name)
self.Name = name
end,
-- Declare the constructor, we also can use
-- `__ctor` as the key
function (self, name)
Person.OnPersonCreated(name)
self.Name = name
end,
}
interface "IScore" {
Person, -- if the type is class, means require it
ICloneable, -- if the type is interface, means extend it
}
class "Student" {
Person, -- if the type is class, means inherit it
IScore, -- if the type is interface, means extend it
}
-- We can declare the method later
function Student:SetScore(score)
end
end)
Template class and interface
Like the template struct, we also can create template class or interface:
require "PLoop"
PLoop(function(_ENV)
__Arguments__ { AnyType }
class "Array" (function(_ENV, eletype)
__Arguments__{ eletype * 0 }
function __new(cls, ...)
return { ... }, true
end
end)
--Error: Usage: Anonymous([... as System.Integer]) - the 4th argument must be number, got string
o = Array[Integer](1, 2, 3, "hi", 5)
end)
Also we can inherit or extend the template class or interface:
require "PLoop"
PLoop(function(_ENV)
__Arguments__ { AnyType }
class "Array" (function(_ENV, eletype)
__Arguments__{ eletype * 0 }
function __new(cls, ...)
return { ... }, true
end
end)
class "NumberArray" { Array[Number] }
__Arguments__ { AnyType } (AnyType)
class "Queue" (function(_ENV, eletype)
inherit (Array[eletype])
end)
end)
System.Interface
The System.Interface is the proxy created from the interface prototype. It contains all features of the interface system. (The APIs used by System won't be introduced, but you can find them easily in the Prototype.lua.)
GetExtends
Get all the extended interfaces of the target interface
- Params:
- target - the target interface
- Return:
- iter - function, the iterator
- target - the target interface
GetFeature
Get a type feature of the target interface
- Params:
- target - the target interface
- name - string, the feature's name
- forobject - boolean, whether get from the object feature
- Return:
- type - the array element's type
GetFeatures
Get all the features of the target interface
- Params:
- target - the target interface
- forobject - boolean, whether get from the object feature
- Return:
- iter - function, the iterator
- target - the target interface
GetMethod
Get a method of the target interface
- Params:
- target - the target interface
- name - string, the method's name
- forobject - boolean, whether get from the object method
- Return:
- method - the method
- isstatic - boolean, true if this is a static method
GetMethods
Get all the methods of the interface
- Params:
- target - the target interface
- forobject - boolean, whether get from the object method
- Return:
- iter - function, the iterator
- target - the target interface
GetMetaMethod
Get a meta-method of the target interface
- Params:
- target - the target interface
- name - string, the meta-method's name
- forobject - boolean, whether get from the object meta-methods
- Return:
- method - the method
GetMetaMethods
Get all the meta-methods of the interface
- Params:
- target - the target interface
- forobject - boolean, whether get from the object meta-methods
- Return:
- iter - function, the iterator
- target - the target interface
GetRequireClass
Get the require class of the target interface
- Params:
- target - the target interface
- Return: class - the require class
GetSuperMethod
Get the super method of the target interface with the given name
- Params:
- target - the target interface
- name - string, the method's name
- Return:
- method - function, the super method
GetSuperMetaMethod
Get the super meta-method of the target interface with the given name
- Params:
- target - the target interface
- name - string, the meta-method's name
- Return:
- method - function, the super meta-method
GetSuperFeature
Get the super feature of the target interface with the given name
- Params:
- target - the target interface
- name - string, the feature's name
- Return:
- feature - the super feature
GetSuperRefer
Get the super refer of the target interface
- Params:
- target - the target interface
- Return:
- super - the super refer
GetTemplate
Get the template interface
- Params:
- target - the target interface
- Return:
- template - the template interface
GetTemplateParameters
Get the template parameters
- Params:
- target - the target interface
- Return:
- ... - the paramter list of the template
HasAnonymousClass
Whether the interface has anonymous class
- Params:
- target - the target interface
- Return:
- flag - true if the interface has anonymous class
IsAbstract
Whether the interface's method, meta-method or feature is abstract
- Params:
- target - the target interface
- name - the method, meta-method, feature name
- Return:
- flag - true if it abstract
IsFinal
Whether the interface or its method, meta-method, feature is final
- Params:
- target - the target interface
- name - the method, meta-method, feature name
- Return:
- flag - true if it final
IsImmutable
The objects are always immutable for type validation
- Params:
- target - the target interface
- Return:
- flag - always true
- isAlways - always true
IsSealed
Whether the interface is sealed, can't be re-defined
- Params:
- target - the target interface
- Return:
- flag - true if the interface is sealed
IsStaticMethod
Whether the interface's given name method is static
- Params:
- target - the target interface
- name - the method name
- Return:
- flag - true if the method is static
IsSubType
Whether the target interface is a sub-type of another interface
- Params:
- target - the target interface
- extendIF - the extened interface
- Return:
- flag - true if the target interface is a sub-type of another interface
ValidateValue
Validate the value with an interface
- Format: (target, value[, onlyvalid])
- Params:
- target - the target interface
- value - the value
- onlyvalid - true if only validate the value, no value modifiy
- Return:
- value - the validated value
- errormsg - the error message if not pass
Validate
Whether the value is an interface type
- Params:
- target - the target
- Return
- target - nil if not pass the validation
System.Class
The System.Class is the proxy created from the class prototype. It contains all features of the class system. (The APIs used by System won't be introduced, but you can find them easily in the Prototype.lua.)
AttachObjectSource
Attach source place to the object
- Format: (object[, stack])
- Params:
- object - the target object
- stack - the stack level
- Return:
- object - the target object
GetExtends
Get all the extended interfaces of the target class
- Params:
- target - the target class
- Return:
- iter - function, the iterator
- target - the target class
GetFeature
Get a type feature of the target class
- Params:
- target - the target class
- name - string, the feature's name
- forobject - boolean, whether get from the object feature
- Return:
- type - the array element's type
GetFeatures
Get all the features of the target class
- Params:
- target - the target class
- forobject - boolean, whether get from the object feature
- Return:
- iter - function, the iterator
- target - the target class
GetMethod
Get a method of the target class
- Params:
- target - the target class
- name - string, the method's name
- forobject - boolean, whether get from the object method
- Return:
- method - the method
- isstatic - boolean, true if this is a static method
GetMethods
Get all the methods of the class
- Params:
- target - the target class
- forobject - boolean, whether get from the object method
- Return:
- iter - function, the iterator
- target - the target class
GetMetaMethod
Get a meta-method of the target class
- Params:
- target - the target class
- name - string, the meta-method's name
- forobject - boolean, whether get from the object meta-methods
- Return:
- method - the method
GetMetaMethods
Get all the meta-methods of the class
- Params:
- target - the target class
- forobject - boolean, whether get from the object meta-methods
- Return:
- iter - function, the iterator
- target - the target class
GetNormalMethod
Get the normal method of the target with the given name
- Params:
- target - the target class
- name - the given name
- Return:
- function - the normal method
GetNormalMetaMethod
Get the normal method of the target with the given name
- Params:
- target - the target class
- name - the given name
- Return:
- function - the normal meta-method
GetNormalFeature
Get the normal method of the target with the given name
- Params:
- target - the target class
- name - the given name
- Return:
- feature - the normal feature
GetObjectClass
Get the object class of the object
- Params:
- target - the target object
- Return:
- class - the object class
GetObjectSource
Get the object's creation place
- Params:
- target - the target object
- Return:
- source - where the object is created
GetSuperClass
Get the super class of the target class
- Params:
- target - the target class
- Return:
- super - the super class
GetSuperMethod
Get the super method of the target class with the given name
- Params:
- target - the target class
- name - string, the method's name
- Return:
- method - function, the super method
GetSuperMetaMethod
Get the super meta-method of the target class with the given name
- Params:
- target - the target class
- name - string, the meta-method's name
- Return:
- method - function, the super meta-method
GetSuperFeature
Get the super feature of the target class with the given name
- Params:
- target - the target class
- name - string, the feature's name
- Return:
- feature - the super feature
GetSuperObjectStyle
Whether the class use super object access style like super[obj].Name = "Ann"
- Params:
- target - the target class
- Return:
- usesuper - true if the class don't use super object access style
GetSuperRefer
Get the super refer of the target class
- Params:
- target - the target class
- Return:
- super - the super refer
GetTemplate
Get the template class
- Params:
- target - the target class
- Return:
- template - the template class
GetTemplateParameters
Get the template parameters
- Params:
- target - the target class
- Return:
- ... - the paramter list of the template
IsAbstract
Whether the class or its method, meta-method or feature is abstract
- Format: (target[, name])
- Params:
- target - the target class
- name - the method, meta-method, feature name
- Return:
- flag - true if it abstract
IsFinal
Whether the class's method, meta-method, feature is final
- Params:
- target - the target class
- name - the method, meta-method, feature name
- Return:
- flag - true if it final
IsImmutable
The objects are always immutable for type validation
- Params:
- target - the target class
- Return:
- flag - always true
- isAlways - always true
IsObjectAttributeEnabled
Whether the attributes can be applied on the class's objects
- Params:
- target - the target class
- Return:
- flag - true if the attributes can be applied on the class's objects
IsObjectFunctionAttributeEnabled
Whether the class object has enabled the attribute for functions will be defined in it
- Params:
- target - the target class
- Return:
- flag - true if the class object enabled the function attribute
IsObjectSourceDebug
Whether the class object'll save its source when created
- Params:
- target - the target class
- Return:
- flag - true if the class object'll save its source when created
IsObjectType
Whether the object is generated from the target type
- Params:
- target - the target object
- type - the interface or class
- Return:
- flag - true if the object is generated from the target type
IsMethodAutoCache
Whether the class object will try to auto cache the object methods
- Params:
- target - the target class
- Return:
- flag - true if the class object will try to auto cache the object methods
IsNilValueBlocked
Whether the class object don't receive any value assignment excpet existed fields
- Params:
- target - the target class
- Return:
- flag - true if the class object don't receive any value assignment excpet existed fields
IsRawSetBlocked
Whether the class object don't receive any value assignment excpet existed fields
- Params:
- target - the target class
- Return:
- flag - true if the class object don't receive any value assignment excpet existed fields
IsSealed
Whether the class is sealed, can't be re-defined
- Params:
- target - the target class
- Return:
- flag - true if the class is sealed
IsSingleVersion
Whether the class is a single version class, so old object would receive re-defined class's features
- Params:
- target - the target class
- Return:
- flag - true if the class is a single version class
IsStaticMethod
Whether the class's given name method is static
- Params:
- target - the target class
- name - the method name
- Return:
- flag - true if the method is static
IsSubType
Whether the target class is a sub-type of another interface|class
- Format: (target, extendIF) (target, supercls)
- Params:
- target - the target class
- extendIF - the extened interface
- supercls - the super class
- Return:
- flag - true if the target class is a sub-type of another interface or class
ValidateValue
Validate the value with a class
- Format: (target, value[, onlyvalid])
- Params:
- target - the target class
- value - the value
- onlyvalid - true if only validate the value, no value modifiy
- Return:
- value - the validated value
- errormsg - the error message if not pass
Validate
Whether the value is a class type
- Params:
- target - the target
- Return
- target - nil if not pass the validation
System.Event
The System.Event is the proxy created from the event prototype. It contains all features of the event system. (The APIs used by System won't be introduced, but you can find them easily in the Prototype.lua.)
IsStatic
Whether the event is static
- Params:
- target - the event
- Return
- flag - true if it's a static event
Invoke
Invoke an event with parameters
- Format: (target[, object], ...)
- Params:
- target - the event
- object - the target object
- ... - the parameters
Validate
Whether the target is an event
- Params:
- target - the target
- Return:
- target - the target if it's an event
As an example:
require "PLoop"(function(_ENV)
class "A" (function(_ENV)
__Static__() event "OnObjectCreated"
event "OnNameChanged"
end)
for name, feature in Class.GetFeatures(A) do
if Event.Validate(feature) then
print(name, Event.IsStatic(feature))
end
end
end)
System.Property
The System.Property is the proxy created from the property prototype. It contains all features of the property system. (The APIs used by System won't be introduced, but you can find them easily in the Prototype.lua.)
GetField
Get the property field if existed
- Params:
- target - the property
- Return
- string - the property field
IsGetClone
Whether the property should return a clone copy of the value
- Params:
- target - the property
- Return
- flag - true if should return a clone copy of the value
IsGetDeepClone
Whether the property should return a deep clone copy of the value
- Params:
- target - the property
- Return
- flag - true if should return a deep clone copy of the value
IsIndexer
Whether the property is an indexer property, used like obj.prop[xxx] = xxx
- Params:
- target - the property
- Return
- flag - true if the property is an indexer
IsReadable
Whether the property is readable
- Params:
- target - the property
- Return
- flag - true if the property is readable
IsSetClone
Whether the property should save a clone copy to the value
- Params:
- target - the property
- Return
- flag - true if should save a clone copy to the value
IsSetDeepClone
Whether the property should save a deep clone copy to the value
- Params:
- target - the property
- Return
- flag - true if should save a deep clone copy to the value
IsRetainObject
Whether the property should dispose the old value
- Params:
- target - the property
- Return
- flag - true if should dispose the old value
IsStatic
Whether the property is static
- Params:
- target - the property
- Return
- flag - true if the property is static
IsThrowable
Whether the property is throwable
- Params:
- target - the property
- Return
- flag - true if the property is throwable
IsWeak
Whether the property value should kept in a weak table
- Params:
- target - the property
- Return
- flag - true if the property value should kept in a weak table
IsWritable
Whether the property is writable
- Params:
- target - the property
- Return
- flag - true if the property is writable
GetDefault
Get the property default value
- Params:
- target - the property
- Return
- default - the default value
GetType
Get the property type
- Params:
- target - the property
- Return
- type - the property value type
Validate
Wether the value is a property
- Params:
- target - the property
- Return
- target - return the taret is it's a property