Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | Import Luaoop as `xtype.class`. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
9efe067d017c18301af7f666418e8132 |
| User & Date: | imagic 2024-10-06 15:00:54.049 |
References
|
2024-10-06
| ||
| 15:10 | • Closed ticket [d464f3e456]: Merge `Luaoop` into `xtype`. plus 4 other changes ... (artifact: b3a994c55b user: imagic) | |
Context
|
2024-10-06
| ||
| 15:06 | Update dev rockspec. ... (check-in: 85f9d87c4c user: imagic tags: trunk) | |
| 15:00 | Import Luaoop as `xtype.class`. ... (check-in: 9efe067d01 user: imagic tags: trunk) | |
|
2024-10-05
| ||
| 22:19 | Remove `__meta` field from class inheritance. ... (Closed-Leaf check-in: 00e1805024 user: imagic tags: import-luaoop) | |
| 21:29 | Improve license embedding. ... (check-in: 4eed6cd5ea user: imagic tags: trunk) | |
Changes
Changes to README.md.
| ︙ | ︙ | |||
8 9 10 11 12 13 14 15 16 17 18 19 20 21 | Incentives: - Simplification: Prevent writing redundant type checking code. - Interoperability: Having a general low-level type system library can ease the interactions of third-party code. - Metaprogramming / Genericity: Exploit the dynamic nature of Lua to generate definitions on-the-fly. See `src/`, `rockspecs/` or [luarocks](https://luarocks.org/modules/imagicthecat-0a6b669a3a/xtype). ## Concepts ### Type | > > > | 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | Incentives: - Simplification: Prevent writing redundant type checking code. - Interoperability: Having a general low-level type system library can ease the interactions of third-party code. - Metaprogramming / Genericity: Exploit the dynamic nature of Lua to generate definitions on-the-fly. Moreover, the `xtype.class` submodule implements a class system based on **xtype**. See the dedicated [documentation](doc/class.md). See `src/`, `rockspecs/` or [luarocks](https://luarocks.org/modules/imagicthecat-0a6b669a3a/xtype). ## Concepts ### Type |
| ︙ | ︙ |
Added doc/class.md.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# xtype.class
## Concepts
### Class and Instances
A class is a **xtype**'s type with fields to define behavior for its instances.
It has the `xtype_*` and `__meta` (when built) special fields.
An instance is a table with behavior defined by a specific class.
### Type and Inheritance
Type checking and order of inheritance are already specified by **xtype**.
A class inherits from its base classes fields, except for the `xtype_*` and
`__meta` fields.
An instance inherits from its class fields (with inheritance), except for
`xtype_*` and those starting with `__`, which are special class fields.
Inheritance of fields is not dynamic (cached for performance) and is built by
`class.build`, which must be called for any later changes to the class
definition or base classes.
The **build** of a class is independent of other class builds and will affect
all its instances. The class is also partially built from the base classes when
created (i.e. the class inheritance, not the instance inheritance).
`class.build` is already called when using a class for instantiation for the
first time; in most cases calling this function is unnecessary because the class
is completely defined when used.
### Special Methods
Special methods are prefixed by `__`.
Misc:
method | description
----
construct | called at initialization
destruct | called at garbage collection
**Warning:** With Lua 5.1 or LuaJIT, neither the table `__gc` metamethod or
ephemeron tables are implemented, thus a `__proxy_gc` field will be added to
instances of a class with a destructor.
Unary operators:
method | description
----
call | like the metamethod
tostring | like the metamethod
unm | like the metamethod
len | like the metamethod
bnot | like the metamethod
### Binary Operators
**xtype** op multifunctions are assigned to the corresponding metamethods in the
instance metatable.
### Private / Protected
There is no private/protected mechanism implemented.
"Private" methods can be achieved with local functions in the class definition.
```lua
local function pmethod(self)
end
```
"Private" instance data can be achieved using a local table in the class
definition with weak keys for the instances.
```lua
local privates = setmetatable({}, {__mode = "k"})
function Object:__construct()
privates[self] = { a = 1, b = 2 }
end
function Object:method()
local p = privates[self]
p.a = p.a*p.b
end
```
## API
```lua
-- Create a new class.
-- Base types can be classes or other xtypes.
--
-- name: human-readable string (doesn't have to be unique)
-- ...: base types, ordered by descending proximity, to the least specific type
-- return created class (an xtype)
class.new(name, ...)
-- ALIAS class(name, ...)
-- Create instance.
-- Will build the class if not already built.
--
-- classdef: class
-- ...: constructor arguments
-- return created instance
class.instantiate(classdef, ...)
-- ALIAS Class(...)
-- Build/re-build the class (class and instance inheritance).
-- Will add the `__meta` field to the class.
--
-- classdef: class
class.build(classdef)
-- Get the class metatable applied to the instances.
-- Will build the class if not already built; useful to apply class behaviour
-- to a custom table.
--
-- classdef: class
-- return metatable
class.meta(classdef)
```
### Examples
#### Type Checking
```lua
A = class("A")
a = A()
xtype.is(A, class) -- true
xtype.is(a, A) -- true
```
#### Multiple Inheritance and Override
```lua
A = class("A")
function A:test() print("a") end
B = class("B")
function B:test() print("b") end
C = class("C", A, B) -- inheritance from A and B
function C:test() -- force the usage of B:test()
B.test(self)
end
```
#### Constructor Override
```lua
A = class("A")
function A:__construct() print("A") end
B = class("B", A)
function B:__construct()
A.__construct(self) -- call parent A constructor
print("B")
end
```
#### Binary Operator Definition
```lua
vec2 = class("vec2")
function vec2:__construct(x, y) self.x, self.y = x, y end
xtype.op.add:define(function(a, b)
return vec2(a.x+b.x, a.y+b.y)
end, vec2, vec2)
```
|
Added examples/test_class.lua.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
package.path = "src/?.lua;"..package.path
local xtype = require("xtype")
local class = require("xtype.class")
local function errcheck(perr, f, ...)
local ok, err = pcall(f, ...)
assert(not ok and not not err:find(perr))
end
local function countPairs(t)
local count = 0
for _ in pairs(t) do count = count+1 end
return count
end
do -- test class/instance basics
local A = class("A")
local a = A()
assert(xtype.is(A, class) and xtype.is(A, "xtype"))
assert(xtype.is(a, A))
assert((tostring(A):find("class<A>")))
assert((tostring(a):find("instance<A>")))
end
do -- test constructor/destructor/GC
local A = class("A")
local record = {}
function A:__construct(i) self.i = i; record[i] = true end
function A:__destruct() record[self.i] = nil end
local as = {}
for i=1,3 do as[i] = A(i) end
assert(countPairs(record) == 3)
-- GC
as = nil
for i=1,2 do collectgarbage("collect") end
assert(countPairs(record) == 0)
end
do -- test special methods: unary operator binding
local A = class("A")
function A:__construct(i) self.i = i end
function A:__call(a,b) return self.i+a+b end
function A:__tostring() return "A"..self.i end
local a = A(5)
assert(a(1,2) == 8)
assert(tostring(a) == "A5")
end
do -- test multifunction / binary operator
local vec2 = class("vec2")
function vec2:__construct(x, y) self.x, self.y = x, y end
xtype.op.add:define(function(a, b) return vec2(a.x+b.x, a.y+b.y) end, vec2, vec2)
xtype.op.eq:define(function(a, b) return a.x == b.x and a.y == b.y end, vec2, vec2)
assert(vec2(1,0) ~= vec2(0,1))
assert(vec2(1,0)+vec2(0,1) == vec2(1,1))
end
do -- test inheritance
-- define
local A = class("A"); function A:test() return "A" end
local B = class("B"); function B:test() return "B" end
local C = class("C", A, B);
local D = class("D", B, A);
local E = class("E", A, B); function E:test() return "E" end
function E:__construct() end
-- test
assert(C.test) -- check partial build: class inheritance
local e = E()
assert(xtype.of(E, A) and xtype.of(E, B))
assert(xtype.is(e, A) and xtype.is(e, B))
assert(E.xtype_name and E.__meta and not e.xtype_name and not e.__meta)
assert(E.__construct and not e.__construct)
assert(C():test() == "A"); assert(D():test() == "B")
assert(e:test() == "E")
end
do -- test class re-build
local A = class("A"); function A:A() return "A" end
local B = class("B"); function B:B() return "B" end
local C = class("C", A, B); function C:C() return self:A()..self:B() end
local c = C()
assert(c:C() == "AB")
function A:A() return "A'" end
function B:B() return "B'" end
class.build(A); class.build(B); class.build(C)
assert(c:C() == "A'B'")
end
do -- test class meta (rudimentary)
local A = class("A")
local a = setmetatable({}, class.meta(A))
assert(xtype.is(a, A))
end
do -- test errors
errcheck("class expected", class.build)
end
|
Added src/xtype/class.lua.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
-- MIT License (see LICENSE or src/xtype.lua)
-- Copyright (c) 2017 ImagicTheCat
local xtype = require "xtype"
local lua51 = string.find(_VERSION, "5.1")
local getmetatable, setmetatable, newproxy = getmetatable, setmetatable, newproxy
local function error_arg(index, expected)
error("bad argument #"..index.." ("..expected.." expected)")
end
-- class type
local class_mt = {xtype = "xtype"}
local class = setmetatable(xtype.create("class", "xtype"), class_mt)
local function check_class(v, index)
if not xtype.is(v, class) then error_arg(index, "class") end
end
local function instance_tostring(self)
local xt = xtype.get(self)
local mt = getmetatable(self)
mt.__tostring = nil
local str = string.gsub(tostring(self), "table:", "instance<"..xtype.name(xt)..">:", 1)
mt.__tostring = instance_tostring
return str
end
-- Build the class inheritance (not the instance inheritance).
local function class_build_inheritance(classdef)
local build = {}
for i=#classdef.xtype_stack,1,-1 do -- least specific, descending order
local base = classdef.xtype_stack[i]
if xtype.is(base, class) then
-- inherit base class fields
for k,v in pairs(base) do
if k ~= "__meta" and not k:find("^xtype_") then build[k] = v end
end
end
end
return build
end
-- Build/re-build the class (class and instance inheritance).
-- Will add the `__meta` field to the class.
--
-- classdef: class
local function class_build(classdef)
check_class(classdef, 1)
-- build
--- inheritance
---- class build
local build = class_build_inheritance(classdef)
---- instance build
local instance_build = {}
for k,v in pairs(build) do -- inherit class build, everything but special fields
if not k:find("^__") then instance_build[k] = v end
end
for k,v in pairs(classdef) do -- inherit class, everything but special fields
if not k:find("^xtype_") and not k:find("^__") then
instance_build[k] = v
end
end
--- setup class inheritance
getmetatable(classdef).__index = build
--- init instance metatable
if not classdef.__meta then
classdef.__meta = {
xtype = classdef,
-- binary operators
__add = xtype.op.add,
__sub = xtype.op.sub,
__mul = xtype.op.mul,
__div = xtype.op.div,
__mod = xtype.op.mod,
__pow = xtype.op.pow,
__concat = xtype.op.concat,
__eq = xtype.op.eq,
__lt = xtype.op.lt,
__le = xtype.op.le,
__idiv = xtype.op.idiv,
__band = xtype.op.band,
__bor = xtype.op.bor,
__bxor = xtype.op.bxor,
__shl = xtype.op.shl,
__shr = xtype.op.shr
}
end
--- update instance metatable
classdef.__meta.__index = instance_build
classdef.__meta.__call = classdef.__call
classdef.__meta.__gc = classdef.__destruct
classdef.__meta.__tostring = classdef.__tostring or instance_tostring
classdef.__meta.__unm = classdef.__unm
classdef.__meta.__len = classdef.__len
classdef.__meta.__bnot = classdef.__bnot
end
-- Build the class if not already built.
-- return instance metatable
local function class_prebuild(classdef)
if not classdef.__meta then class_build(classdef) end
return classdef.__meta
end
local function proxy_gc(self)
local mt = getmetatable(self)
mt.destructor(mt.instance)
end
-- Create instance.
-- Will build the class if not already built.
--
-- classdef: class
-- ...: constructor arguments
-- return created instance
local function class_instantiate(classdef, ...)
local meta = class_prebuild(classdef)
-- create instance
local instance = setmetatable({}, meta)
-- setup destructor (Lua 5.1)
if lua51 then
local destructor = classdef.__destruct
if destructor then
local proxy = newproxy(true)
local mt = getmetatable(proxy)
mt.__gc = proxy_gc
mt.destructor = destructor
mt.instance = instance
instance.__proxy_gc = proxy
end
end
-- construct
local constructor = classdef.__construct
if constructor then constructor(instance, ...) end
return instance
end
-- Create a new class.
-- Base types can be classes or other xtypes.
--
-- name: human-readable string (doesn't have to be unique)
-- ...: base types, ordered by descending proximity, to the least specific type
-- return created class (an xtype)
local function class_new(name, ...)
local xt = xtype.create(name, ...)
-- default print "class<type>: 0x..."
local tostring_const = string.gsub(tostring(xt), "xtype", "class", 1)
return setmetatable(xt, {
xtype = class,
__index = class_build_inheritance(xt),
__call = class_instantiate,
__tostring = function() return tostring_const end
})
end
-- Get the class metatable applied to the instances.
-- Will build the class if not already built; useful to apply class behaviour
-- to a custom table.
--
-- classdef: class
-- return metatable
local function class_meta(classdef)
return class_prebuild(classdef)
end
class.new = class_new
class.meta = class_meta
class.instantiate = class_instantiate
class.build = class_build
class_mt.__call = function(t, ...) return class_new(...) end
return class
|
Changes to test.lua.
1 2 3 4 5 6 7 8 9 |
#!/usr/bin/env luajit
-- or Lua 5.1
-- config
local envs = {"luajit", "lua5.1", "lua5.2", "lua5.3", "lua5.4"}
local tests = {
"examples/test_main.lua",
"examples/test_multifunction.lua",
"examples/test_cdata.lua",
| | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#!/usr/bin/env luajit
-- or Lua 5.1
-- config
local envs = {"luajit", "lua5.1", "lua5.2", "lua5.3", "lua5.4"}
local tests = {
"examples/test_main.lua",
"examples/test_multifunction.lua",
"examples/test_cdata.lua",
"examples/test_error.lua",
"examples/test_class.lua",
}
-- test
local errors = 0
for _, env in ipairs(envs) do
for _, test in ipairs(tests) do
local status = os.execute(env.." "..test)
if status ~= 0 then print(env, test, "FAILED"); errors = errors+1 end
|
| ︙ | ︙ |