xtype (halted)

Check-in [9efe067d01]
Login

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: 9efe067d017c18301af7f666418e81323178dad52494ab3ef404b97dd2cc728c
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
Unified Diff Ignore Whitespace Patch
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
10

11
12
13
14
15
16
17
#!/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"

}
-- 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









|
>







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