xtype (halted)

Check-in [be91a3e5fe]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Add low-level API. E.g. to inspect/debug.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: be91a3e5fea5b80fe0d2a28711ef63a08d0d73cd70232de47c9fb9d81b059e16
User & Date: imagic 2021-11-08 20:25:51.000
Context
2021-11-10
00:55
Improve cdata support with __xtype field. ... (check-in: 1f1ab704d5 user: imagic tags: trunk)
2021-11-08
20:25
Add low-level API. ... (check-in: be91a3e5fe user: imagic tags: trunk)
19:30
Improve API checks. ... (check-in: 37988572a5 user: imagic tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to README.adoc.
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
--
-- The created type is a table with 3 fields: xtype_name, xtype_stack and xtype_set.
-- The table can be modified as long as the xtype fields are left untouched.
-- A default metatable is set; it can be replaced at the condition that the
-- type would still be recognized as a "xtype".
--
-- name: human-readable string (doesn't have to be unique)
-- ...: base types, be specific in descending order
-- return created type
xtype.create(name, ...)

-- Get/bind a type to a ctype (LuaJIT FFI).
--
-- This can't be changed afterwards.
-- The same type can be bound to different ctypes; it can be useful when
-- different ctype qualifiers should match the same type.
--
-- ctype: cdata ctype object
-- t: (optional) type
-- return bound type
xtype.ctype(ctype, t)








-- Get terminal type of a value.
xtype.get(v)

-- Check if a value is of type t.
xtype.is(v, t)

-- Check if a type is of type ot.
xtype.of(t, ot)

-- Create a multifunction.
xtype.multifunction()

-- Global multifunctions namespace for binary operators.
-- For interoperability between third-party types.
-- map of Lua binary op name => multifunction
-- (add, sub, mul, div, mod, pow, concat, eq, lt, le)
xtype.op





















-- Code generation tools.

-- Generate "a1, a2, a3, a4..." list string.
-- tpl: string where "$" will be replaced by the index
-- i: start index
-- j: end index
-- separator: (optional) default: ", "







|













>
>
>
>
>
>
>



















>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







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
--
-- The created type is a table with 3 fields: xtype_name, xtype_stack and xtype_set.
-- The table can be modified as long as the xtype fields are left untouched.
-- A default metatable is set; it can be replaced at the condition that the
-- type would still be recognized as a "xtype".
--
-- name: human-readable string (doesn't have to be unique)
-- ...: base types, ordered by descending proximity, to the least specific type
-- return created type
xtype.create(name, ...)

-- Get/bind a type to a ctype (LuaJIT FFI).
--
-- This can't be changed afterwards.
-- The same type can be bound to different ctypes; it can be useful when
-- different ctype qualifiers should match the same type.
--
-- ctype: cdata ctype object
-- t: (optional) type
-- return bound type
xtype.ctype(ctype, t)

-- Check if a value is a valid type.
xtype.check(t)

-- Get the name of a type.
-- return string or nothing if not a type
xtype.name(t)

-- Get terminal type of a value.
xtype.get(v)

-- Check if a value is of type t.
xtype.is(v, t)

-- Check if a type is of type ot.
xtype.of(t, ot)

-- Create a multifunction.
xtype.multifunction()

-- Global multifunctions namespace for binary operators.
-- For interoperability between third-party types.
-- map of Lua binary op name => multifunction
-- (add, sub, mul, div, mod, pow, concat, eq, lt, le)
xtype.op

-- Low-level API.

-- The type's xtype_stack field is a list of types ordered from the terminal
-- type to the least specific inherited types.

-- Stack distance to another type from a terminal type.
-- return distance or nil/nothing if not of type ot
xtype.typeDist(t, ot)

-- Check and return signature (list of types).
-- ...: types
xtype.checkSign(...)

-- Distance to another signature from a call signature.
-- return distance or nothing if not generalizable to osign
xtype.signDist(sign, osign)

-- Return formatted signature string.
xtype.formatSign(sign)

-- Code generation tools.

-- Generate "a1, a2, a3, a4..." list string.
-- tpl: string where "$" will be replaced by the index
-- i: start index
-- j: end index
-- separator: (optional) default: ", "
135
136
137
138
139
140
141









142
-- ...: call signature, list of types
-- return function or nil without a matching definition
multifunction:resolve(...)

-- Call the multifunction.
multifunction(...)
multifunction:call(...)









----







>
>
>
>
>
>
>
>
>

162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
-- ...: call signature, list of types
-- return function or nil without a matching definition
multifunction:resolve(...)

-- Call the multifunction.
multifunction(...)
multifunction:call(...)

-- Low-level API.

multifunction.definitions = {} -- map of sign hash => {.f, .sign}

-- Hash function signature.
-- sign: signature, list of types
-- return number
multifunction:hashSign(sign)
----
Changes to src/xtype.lua.
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
end

local type_mt = {
  xtype = "xtype",
  __tostring = xtype_tostring
}






local function check_type(v, index)
  if not (type(v) == "string" or xtype.is(v, "xtype")) then error_arg(index, "type") end
end










































-- Create a type.
--
-- The created type is a table with 3 fields: xtype_name, xtype_stack and xtype_set.
-- The table can be modified as long as the xtype fields are left untouched.
-- A default metatable is set; it can be replaced at the condition that the
-- type would still be recognized as a "xtype".
--
-- name: human-readable string (doesn't have to be unique)
-- ...: base types, be specific in descending order
-- return created type
function xtype.create(name, ...)
  if type(name) ~= "string" then error_arg(1, "string") end
  -- check base types
  local bases = table_pack(...)
  for i=1, bases.n do check_type(bases[i], i+1) end
  -- create







>
>
>
>
>

|

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>









|







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
end

local type_mt = {
  xtype = "xtype",
  __tostring = xtype_tostring
}

local xtype_is
-- Check if a value is a valid type.
local function xtype_check(t) return type(t) == "string" or xtype_is(t, "xtype") end
xtype.check = xtype_check
-- Check if an argument is a type.
local function check_type(v, index)
  if not xtype_check(v) then error_arg(index, "type") end
end

local ctype_types = {} -- map of ctype id => type

-- Get/bind a type to a ctype (LuaJIT FFI).
--
-- This can't be changed afterwards.
-- The same type can be bound to different ctypes; it can be useful when
-- different ctype qualifiers should match the same type.
--
-- ctype: cdata ctype object
-- t: (optional) type
-- return bound type
function xtype.ctype(ctype, t)
  local id = tonumber(ctype)
  if t and not ctype_types[id] then
    check_type(t, 2)
    ctype_types[id] = t
  end
  return ctype_types[id]
end

-- Get terminal type of a value.
local function xtype_get(v)
  local v_type = type(v)
  if v_type == "table" or v_type == "userdata" then
    local mt = getmetatable(v)
    return mt and mt.xtype or v_type
  elseif v_type == "cdata" then
    return ctype_types[tonumber(ffi.typeof(v))] or v_type
  else return v_type end
end
xtype.get = xtype_get

-- Check if a value is of type t.
xtype_is = function(v, t)
  check_type(t, 2)
  local vt = xtype_get(v)
  if type(vt) == "table" then return vt.xtype_set[t]
  else return vt == t end
end
xtype.is = xtype_is

-- Create a type.
--
-- The created type is a table with 3 fields: xtype_name, xtype_stack and xtype_set.
-- The table can be modified as long as the xtype fields are left untouched.
-- A default metatable is set; it can be replaced at the condition that the
-- type would still be recognized as a "xtype".
--
-- name: human-readable string (doesn't have to be unique)
-- ...: base types, ordered by descending proximity, to the least specific type
-- return created type
function xtype.create(name, ...)
  if type(name) ~= "string" then error_arg(1, "string") end
  -- check base types
  local bases = table_pack(...)
  for i=1, bases.n do check_type(bases[i], i+1) end
  -- create
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
176
177
178










































179
180
181
182
183
184
185
      end
    end
    step = step+1
  end
  return t
end

local ctype_types = {} -- map of ctype id => type

-- Get/bind a type to a ctype (LuaJIT FFI).
--
-- This can't be changed afterwards.
-- The same type can be bound to different ctypes; it can be useful when
-- different ctype qualifiers should match the same type.
--
-- ctype: cdata ctype object
-- t: (optional) type
-- return bound type
function xtype.ctype(ctype, t)
  local id = tonumber(ctype)
  if t and not ctype_types[id] then
    check_type(t, 2)
    ctype_types[id] = t
  end
  return ctype_types[id]
end

-- Get terminal type of a value.
local function xtype_get(v)
  local v_type = type(v)
  if v_type == "table" or v_type == "userdata" then
    local mt = getmetatable(v)
    return mt and mt.xtype or v_type
  elseif v_type == "cdata" then
    return ctype_types[tonumber(ffi.typeof(v))] or v_type
  else return v_type end
end
xtype.get = xtype_get

-- Check if a value is of type t.
function xtype.is(v, t)
  check_type(t, 2)
  local vt = xtype_get(v)
  if type(vt) == "table" then return vt.xtype_set[t]
  else return vt == t end
end

-- Check if a type is of type ot.
function xtype.of(t, ot)
  check_type(t, 1); check_type(ot, 2)
  if type(t) == "table" then return t.xtype_set[ot]
  else return t == ot end
end







-- Multifunction.

local function multifunction_tostring(t)
  local mt = getmetatable(t)
  mt.__tostring = nil
  local str = string.gsub(tostring(t), "table:", "multifunction:", 1)
  mt.__tostring = multifunction_tostring
  return str
end

local multifunction = {}











































-- Create hash sign tree.
local function new_hsign()
  local count = 0
  local hasher_mt
  hasher_mt = {
    __index = function(t, k)







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<






>
>
>
>
>
>












>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







160
161
162
163
164
165
166








































167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
      end
    end
    step = step+1
  end
  return t
end









































-- Check if a type is of type ot.
function xtype.of(t, ot)
  check_type(t, 1); check_type(ot, 2)
  if type(t) == "table" then return t.xtype_set[ot]
  else return t == ot end
end

-- Get the name of a type.
-- return string or nothing if not a type
function xtype.name(t)
  if xtype_check(t) then return type(t) == "string" and t or t.xtype_name end
end

-- Multifunction.

local function multifunction_tostring(t)
  local mt = getmetatable(t)
  mt.__tostring = nil
  local str = string.gsub(tostring(t), "table:", "multifunction:", 1)
  mt.__tostring = multifunction_tostring
  return str
end

local multifunction = {}

-- Check and return signature (list of types).
-- ...: types
local function check_sign(...)
  local sign = table_pack(...)
  for i=1, sign.n do check_type(sign[i], i) end
  return sign
end
xtype.checkSign = check_sign

-- Return formatted signature string.
local function format_sign(sign)
  local names = {}
  for _, t in ipairs(sign) do
    table.insert(names, type(t) == "string" and t or t.xtype_name)
  end
  return "("..table.concat(names, ", ")..")"
end
xtype.formatSign = format_sign

-- Stack distance to another type from a terminal type.
-- return distance or nil/nothing if not of type ot
local function type_dist(t, ot)
  if type(t) == "string" then return t == ot and 0 or nil end
  for i, st in ipairs(t.xtype_stack) do
    if st == ot then return i-1 end
  end
end
xtype.typeDist = type_dist

-- Distance to another signature from a call signature.
-- return distance or nothing if not generalizable to osign
local function sign_dist(sign, osign)
  local dist = 0
  for i=1, #sign do
    local tdist = type_dist(sign[i], osign[i])
    if not tdist then return end
    dist = dist+tdist
  end
  return dist
end
xtype.signDist = sign_dist

-- Create hash sign tree.
local function new_hsign()
  local count = 0
  local hasher_mt
  hasher_mt = {
    __index = function(t, k)
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
-- sign: signature, list of types
-- return number
local function mf_hash_sign(self, sign)
  local node = self.hsign
  for _, t in ipairs(sign) do node = node[t] end
  return node[1]
end

-- Check and return signature.
local function check_sign(...)
  local sign = table_pack(...)
  for i=1, sign.n do check_type(sign[i], i) end
  return sign
end

-- return formatted signature string
local function format_sign(sign)
  local names = {}
  for _, t in ipairs(sign) do
    table.insert(names, type(t) == "string" and t or t.xtype_name)
  end
  return "("..table.concat(names, ", ")..")"
end

-- Stack distance to another type from a terminal type.
-- return distance or nil if not of type ot
local function type_dist(t, ot)
  if type(t) == "string" then return t == ot and 0 or nil end
  for i, st in ipairs(t.xtype_stack) do
    if st == ot then return i-1 end
  end
end

-- Distance to another signature from a call signature.
-- return distance or nil if not generalizable to osign
local function sign_dist(sign, osign)
  local dist = 0
  for i=1, #sign do
    local tdist = type_dist(sign[i], osign[i])
    if not tdist then return end
    dist = dist+tdist
  end
  return dist
end

-- Find candidate.
-- return candidate or nil if none found
--- candidate: {def, dist}
local function mf_find_candidate(self, sign)
  -- find candidates
  local candidates = {}







|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







251
252
253
254
255
256
257
258




































259
260
261
262
263
264
265
-- sign: signature, list of types
-- return number
local function mf_hash_sign(self, sign)
  local node = self.hsign
  for _, t in ipairs(sign) do node = node[t] end
  return node[1]
end
multifunction.hashSign = mf_hash_sign





































-- Find candidate.
-- return candidate or nil if none found
--- candidate: {def, dist}
local function mf_find_candidate(self, sign)
  -- find candidates
  local candidates = {}
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
  local default_call = gen_opt_mfcall(0)
  local multifunction_mt = {
    xtype = "multifunction",
    __tostring = multifunction_tostring,
    __index = multifunction,
    __call = default_call
  }

  return setmetatable({
    hsign = new_hsign(),
    definitions = {}, -- map of sign hash => {.f, .sign}
    generators = {},
    candidates = {}, -- cached candidates, map of hash => {def, dist}
    max_calln = 0, -- maximum call parameters
    call = default_call
  }, multifunction_mt)
end

-- Code generation tools.







<



|







407
408
409
410
411
412
413

414
415
416
417
418
419
420
421
422
423
424
  local default_call = gen_opt_mfcall(0)
  local multifunction_mt = {
    xtype = "multifunction",
    __tostring = multifunction_tostring,
    __index = multifunction,
    __call = default_call
  }

  return setmetatable({
    hsign = new_hsign(),
    definitions = {}, -- map of sign hash => {.f, .sign}
    generators = {}, -- set of generator functions
    candidates = {}, -- cached candidates, map of hash => {def, dist}
    max_calln = 0, -- maximum call parameters
    call = default_call
  }, multifunction_mt)
end

-- Code generation tools.