forked from teal-language/tl
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlua_compat.lua
More file actions
307 lines (246 loc) · 10.2 KB
/
Copy pathlua_compat.lua
File metadata and controls
307 lines (246 loc) · 10.2 KB
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
local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local ipairs = _tl_compat and _tl_compat.ipairs or ipairs; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table; local tldebug = require("teal.debug")
local TL_DEBUG = tldebug.TL_DEBUG
local environment = require("teal.environment")
local parser = require("teal.parser")
local node_at = parser.node_at
local metamethods = require("teal.metamethods")
local unop_to_metamethod = metamethods.unop_to_metamethod
local binop_to_metamethod = metamethods.binop_to_metamethod
local traversal = require("teal.traversal")
local traverse_nodes = traversal.traverse_nodes
local util = require("teal.util")
local sorted_keys = util.sorted_keys
local lua_compat = {}
local compat_code_cache = {}
local function add_compat_entries(program, used_set, gen_compat)
if not next(used_set) or gen_compat == "off" then
return
end
local tl_debug = TL_DEBUG
TL_DEBUG = nil
local used_list = sorted_keys(used_set)
local compat_loaded = false
local n = 1
local function load_code(name, text)
local code = compat_code_cache[name]
if not code then
code = parser.parse(text, "@<internal>.lua")
compat_code_cache[name] = code
end
for _, c in ipairs(code) do
table.insert(program, n, c)
n = n + 1
end
end
local function req(m)
return (gen_compat == "optional") and
"pcall(require, '" .. m .. "')" or
"true, require('" .. m .. "')"
end
for _, name in ipairs(used_list) do
if name == "table.unpack" then
load_code(name, "local _tl_table_unpack = unpack or table.unpack")
elseif name == "table.pack" then
load_code(name, [[local _tl_table_pack = table.pack or function(...) return { n = select("#", ...), ... } end]])
elseif name == "bit32" then
load_code(name, "local bit32 = bit32; if not bit32 then local p, m = " .. req("bit32") .. "; if p then bit32 = m end")
elseif name == "mt" then
load_code(name, "local _tl_mt = function(m, s, a, b) return (getmetatable(s == 1 and a or b)[m](a, b) end")
elseif name == "math.maxinteger" then
load_code(name, "local _tl_math_maxinteger = math.maxinteger or math.pow(2,53)")
elseif name == "math.mininteger" then
load_code(name, "local _tl_math_mininteger = math.mininteger or -math.pow(2,53)")
elseif name == "type" then
load_code(name, "local type = type")
else
if not compat_loaded then
load_code("compat", "local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = " .. req("compat53.module") .. "; if p then _tl_compat = m end")
compat_loaded = true
end
load_code(name, (("local $NAME = _tl_compat and _tl_compat.$NAME or $NAME"):gsub("$NAME", name)))
end
end
program.y = 1
TL_DEBUG = tl_debug
end
local function convert_node_to_compat_call(node, mod_name, fn_name, e1, e2)
node.op.op = "@funcall"
node.op.arity = 2
node.op.prec = 100
node.e1 = node_at(node, { kind = "op", op = parser.operator(node, 2, ".") })
node.e1.e1 = node_at(node, { kind = "identifier", tk = mod_name })
node.e1.e2 = node_at(node, { kind = "identifier", tk = fn_name })
node.e2 = node_at(node, { kind = "expression_list" })
node.e2[1] = e1
node.e2[2] = e2
end
local function convert_node_to_compat_mt_call(node, mt_name, which_self, e1, e2)
node.op.op = "@funcall"
node.op.arity = 2
node.op.prec = 100
node.e1 = node_at(node, { kind = "identifier", tk = "_tl_mt" })
node.e2 = node_at(node, { kind = "expression_list" })
node.e2[1] = node_at(node, { kind = "string", tk = "\"" .. mt_name .. "\"" })
node.e2[2] = node_at(node, { kind = "integer", tk = tostring(which_self) })
node.e2[3] = e1
node.e2[4] = e2
end
local bit_operators = {
["&"] = "band",
["|"] = "bor",
["~"] = "bxor",
[">>"] = "rshift",
["<<"] = "lshift",
}
local function shadow_control_var(node, control_var)
if #node.body == 0 then
return
end
local stmt = node_at(node, {
kind = "local_declaration",
vars = node_at(node, {
kind = "variable_list",
node_at(node, { kind = "variable", is_lvalue = true, tk = control_var }),
}),
exps = node_at(node, {
kind = "expression_list",
node_at(node, { kind = "variable", tk = control_var }),
}),
})
table.insert(node.body, 1, stmt)
end
local function adjust_code(ast, needs_compat, gen_compat, gen_target)
local visit = false
local visit_node = {
cbs = {},
}
if gen_target < "5.5" then
visit = true
local functionvisit = {
after = function(_self, node, _children)
local last_arg = node.args[#node.args]
if last_arg and last_arg.tk == "..." and last_arg.name then
local use_compat_pack = (gen_target == "5.1" or gen_target == "5.3") and gen_compat ~= "off"
local fnname
if use_compat_pack then
needs_compat["table.pack"] = true
fnname = node_at(last_arg, {
kind = "variable",
tk = "_tl_table_pack",
})
else
fnname = node_at(last_arg, {
kind = "op",
op = parser.operator(last_arg, 2, "."),
e1 = node_at(last_arg, { kind = "variable", tk = "table" }),
e2 = node_at(last_arg, { kind = "identifier", tk = "pack" }),
})
end
local stmt = node_at(last_arg, {
kind = "local_declaration",
vars = node_at(last_arg, {
kind = "variable_list",
node_at(last_arg, { kind = "variable", is_lvalue = true, tk = last_arg.name.tk }),
}),
exps = node_at(last_arg, {
kind = "expression_list",
node_at(last_arg, {
kind = "op",
op = parser.operator(last_arg, 2, "@funcall"),
e1 = fnname,
e2 = node_at(last_arg, { kind = "expression_list", node_at(last_arg, { kind = "...", tk = "..." }) }),
}),
}),
})
table.insert(node.body, 1, stmt)
end
end,
}
visit_node.cbs["function"] = functionvisit
visit_node.cbs["global_function"] = functionvisit
visit_node.cbs["record_function"] = functionvisit
visit_node.cbs["local_function"] = functionvisit
end
if (gen_target == "5.1" or gen_target == "5.3") and gen_compat ~= "off" then
visit = true
visit_node.cbs["op"] = {
after = function(_, node, _children)
if node.op.op == "is" then
if node.e2.casttype.typename == "integer" then
needs_compat["math"] = true
elseif node.e2.casttype.typename ~= "nil" then
needs_compat["type"] = true
end
elseif node.op.op == "." then
if node.op.needs_compat then
if node.e1.kind == "variable" and node.e2.kind == "identifier" then
local key = node.e1.tk .. "." .. node.e2.tk
node.kind = "variable"
node.tk = "_tl_" .. node.e1.tk .. "_" .. node.e2.tk
needs_compat[key] = true
end
end
elseif node.op.op == "~" and gen_target == "5.1" then
if node.op.meta_on_operand then
needs_compat["mt"] = true
convert_node_to_compat_mt_call(node, unop_to_metamethod[node.op.op], 1, node.e1)
else
needs_compat["bit32"] = true
convert_node_to_compat_call(node, "bit32", "bnot", node.e1)
end
elseif node.op.op == "//" and gen_target == "5.1" then
if node.op.meta_on_operand then
needs_compat["mt"] = true
convert_node_to_compat_mt_call(node, "__idiv", node.op.meta_on_operand, node.e1, node.e2)
else
local div = node_at(node, { kind = "op", op = parser.operator(node, 2, "/"), e1 = node.e1, e2 = node.e2 })
convert_node_to_compat_call(node, "math", "floor", div)
end
elseif bit_operators[node.op.op] and gen_target == "5.1" then
if node.op.meta_on_operand then
needs_compat["mt"] = true
convert_node_to_compat_mt_call(node, binop_to_metamethod[node.op.op], node.op.meta_on_operand, node.e1, node.e2)
else
needs_compat["bit32"] = true
convert_node_to_compat_call(node, "bit32", bit_operators[node.op.op], node.e1, node.e2)
end
end
end,
}
end
if gen_target == "5.4" or gen_target == "5.5" then
visit = true
visit_node.cbs["forin"] = {
after = function(_, node, _children)
if node.forin_modifies_control_var then
shadow_control_var(node, node.vars[1].tk)
end
end,
}
visit_node.cbs["fornum"] = {
after = function(_, node, _children)
if node.fornum_modifies_control_var then
shadow_control_var(node, node.var.tk)
end
end,
}
end
if not visit then
return
end
traverse_nodes(nil, ast, visit_node, {})
add_compat_entries(ast, needs_compat, gen_compat)
end
function lua_compat.apply(result)
if not (result and result.ast) then
return
end
if result.compat_applied then
return
end
result.compat_applied = true
local gen_compat = result.env.opts.gen_compat or environment.DEFAULT_GEN_COMPAT
local gen_target = result.env.opts.gen_target or environment.DEFAULT_GEN_TARGET
adjust_code(result.ast, result.needs_compat, gen_compat, gen_target)
end
return lua_compat