-
Notifications
You must be signed in to change notification settings - Fork 8
/
haxify.lua
335 lines (278 loc) · 8.22 KB
/
haxify.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
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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
-- Be warned, here be dragons
api = require "love-api.love_api"
do
-- Map types to their modules, so we can properly do imports
local lovetypes = {}
for _, type in ipairs(api.types) do
lovetypes[type.name] = "love"
end
for _, module in ipairs(api.modules) do
local modulename = "love." .. module.name
if module.types then
for _, type in ipairs(module.types) do
lovetypes[type.name] = modulename
end
end
if module.enums then
for _, type in ipairs(module.enums) do
lovetypes[type.name] = modulename
end
end
end
-- types: { name -> true }
function resolveImports(types, package)
local imports = {}
for i, v in pairs(types) do
local module = lovetypes[i]
if module and module ~= package then
table.insert(imports, ("import %s.%s;"):format(module, i))
end
end
table.sort(imports)
return table.concat(imports, "\n")
end
end
do
-- The keys are type names, the values are their "priority",
-- the most generic base class (Object) has the lowest priority.
-- Used to find the most specific supertype later on.
local priority = {}
priority["Object"] = 0
-- Now we first need a complete registry of types and their supertypes
local supertypes = {}
for _, type in ipairs(api.types) do
supertypes[type.name] = type.supertypes or {}
end
for _, module in ipairs(api.modules) do
if module.types then
for _, type in ipairs(module.types) do
supertypes[type.name] = type.supertypes or {}
end
end
if module.enums then
for _, type in ipairs(module.enums) do
supertypes[type.name] = type.supertypes or {}
end
end
end
-- To assign the priority of a type, take the maximum priority of its
-- supertypes and add 1.
local function assignPriority(name)
if priority[name] then
-- Priority is known, skip
return priority[name]
end
local max = -math.huge
for i, v in ipairs(supertypes[name]) do
max = math.max(max, assignPriority(v))
end
priority[name] = max+1
return max+1
end
-- Now assign all priorities, and dump the type list
for i, v in pairs(supertypes) do
assignPriority(i)
end
supertypes = nil
-- Now we can just return the supertype with the highest priority
function mostSpecificSupertype(t)
local maxVal, maxPriority = "UserData", -math.huge
for i, v in ipairs(t) do
local priority = priority[v]
if priority > maxPriority then
maxVal, maxPriority = v, priority
end
end
return maxVal
end
end
do
local map =
{
number = "Float",
string = "String",
boolean = "Bool",
table = "Table<Dynamic,Dynamic>",
["light userdata"] = "UserData",
userdata = "UserData",
["function"] = "Dynamic", -- FIXME
mixed = "Dynamic",
value = "Dynamic",
any = "Dynamic",
Variant = "Dynamic",
-- FIXME
["ShaderVariableType"] = "String",
["KeyConstant"] = "String",
["Scancode"] = "String",
}
function typeMap(t)
return map[t] or t
end
end
function capitalize(s)
return s:sub(1, 1):upper() .. s:sub(2)
end
function mergeTables(target, src, prefix)
prefix = prefix or ""
for i, v in pairs(src) do
target[prefix .. i] = v
end
return target
end
function dirname(path)
return path:match("^(.-)/?[^/]+$")
end
function emitMultiReturnType(name, returns, types)
local parts = {}
parts[1] = ("\n@:multiReturn\nextern class %s\n{\n"):format(name)
for i, v in ipairs(returns) do
-- TODO: Maybe never? Vararg return can't really be modeled.
if v.name ~= "..." then
local type = typeMap(v.type)
types[type] = true
table.insert(parts, ("\tvar %s : %s;\n"):format(v.name, type))
end
end
table.insert(parts, "}")
return table.concat(parts)
end
function emitOverload(typeName, name, o, types, multirets)
local args = {}
for i, v in ipairs(o.arguments or {}) do
v.type = typeMap(v.type)
types[v.type] = true
v.name = v.name:match("^\"(.*)\"$") or v.name -- FIXME: workaround for love.event.quit
if v.name == "..." then
table.insert(args, ("args:Rest<%s>"):format(v.type))
else
local arg = (v.default and "?" or "") .. v.name .. ":" .. v.type
table.insert(args, arg)
end
end
local retType = "Void"
if o.returns and #o.returns > 1 then
-- In case of multiple returns we need to generate a new return type
retType = typeName .. capitalize(name) .. "Result"
multirets[name] = emitMultiReturnType(retType, o.returns, types)
elseif o.returns then
retType = typeMap(o.returns[1].type)
types[retType] = true
end
return ("(%s) : %s"):format(table.concat(args, ", "), retType)
end
function emitCallback(c, types)
local type = {}
for i, v in ipairs(c.variants[1].arguments or {}) do -- TODO: Multiple variants? Does that even exist?
table.insert(type, typeMap(v.type))
types[type[#type]] = true
end
if c.variants[1].returns then -- TODO: Multiple returns?
table.insert(type, typeMap(c.variants[1].returns[1].type))
types[type[#type]] = true
else
table.insert(type, "Void")
end
-- If there are no arguments, prepend Void
if #type == 1 then
table.insert(type, 1, "Void")
end
type = table.concat(type, "->")
return ("\tpublic static var %s : %s;"):format(c.name, type)
end
function rawEmitFunction(typeName, f, types, static, multirets)
local out = {""}
local sigs = {}
for i, v in ipairs(f.variants) do
table.insert(sigs, emitOverload(typeName, f.name, v, types, multirets))
end
local main = table.remove(sigs, 1)
for i, v in ipairs(sigs) do
table.insert(out, ("\t@:overload(function %s {})"):format(v))
end
table.insert(out, ("\tpublic%s function %s%s;"):format(static and " static" or "", f.name, main))
return table.concat(out, "\n")
end
function emitFunction(typeName, f, types, multirets)
return rawEmitFunction(typeName, f, types, true, multirets)
end
function emitMethod(typeName, m, types, multirets)
return rawEmitFunction(typeName, m, types, false, multirets)
end
function emitEnum(e, packageName)
local out = {}
table.insert(out, ("package %s;"):format(packageName))
table.insert(out, "@:enum")
table.insert(out, ("abstract %s (String)\n{"):format(e.name))
for i, v in ipairs(e.constants) do
table.insert(out, ("\tvar %s = \"%s\";"):format(capitalize(v.name), v.name))
end
table.insert(out, "}")
return {[e.name .. ".hx"] = table.concat(out, "\n")}
end
function emitHeader(out, packageName)
table.insert(out, ("package %s;"):format(packageName))
table.insert(out, "import haxe.extern.Rest;")
table.insert(out, "import lua.Table;")
table.insert(out, "import lua.UserData;")
table.insert(out, "")
end
function emitType(t, packageName)
local out = {}
local types = {}
local multirets = {}
emitHeader(out, packageName)
local superType = t.supertypes and mostSpecificSupertype(t.supertypes) or "UserData"
table.insert(out, ("extern class %s extends %s\n{"):format(t.name, superType))
for i, v in ipairs(t.functions or {}) do
table.insert(out, emitMethod(t.name, v, types, multirets))
end
table.insert(out, "}")
table.insert(out, 2, resolveImports(types, packageName))
for i, v in pairs(multirets) do
table.insert(out, v)
end
return {[t.name .. ".hx"] = table.concat(out, "\n")}
end
function emitModule(m, luaName)
local out = {}
local files = {}
local types = {}
local multirets = {}
local moduleName = luaName or "love." .. m.name
local prefix = moduleName:gsub("%.", "/") .. "/"
emitHeader(out, moduleName)
table.insert(out, ("@:native(\"%s\")"):format(moduleName))
local className = capitalize(luaName or (m.name .. "Module"))
table.insert(out, ("extern class %s"):format(className))
table.insert(out, "{")
for i, v in ipairs(m.functions) do
table.insert(out, emitFunction(className, v, types, multirets))
end
for i, v in ipairs(m.callbacks or {}) do
table.insert(out, emitCallback(v, types))
end
table.insert(out, "}")
for i, v in ipairs(m.enums or {}) do
mergeTables(files, emitEnum(v, moduleName), prefix)
end
for i, v in ipairs(m.types or {}) do
mergeTables(files, emitType(v, moduleName), prefix)
end
table.insert(out, 2, resolveImports(types, moduleName))
for i, v in pairs(multirets) do
table.insert(out, v)
end
files[prefix .. className .. ".hx"] = table.concat(out, "\n")
return files
end
local files = {}
for i, v in ipairs(api.modules) do
mergeTables(files, emitModule(v))
end
mergeTables(files, emitModule(api, "love"))
for i, v in pairs(files) do
os.execute("mkdir -p " .. dirname(i))
local f = io.open(i, "w")
f:write(v)
f:close()
end