Skip to content

Commit

Permalink
generate bit32 with Lua 5.2, and bit with Lua 5.1 & LuaJIT
Browse files Browse the repository at this point in the history
  • Loading branch information
fperrad committed Aug 1, 2023
1 parent 710c081 commit 09855e2
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 9 deletions.
6 changes: 5 additions & 1 deletion docs/compiler_options.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ return {
| `-l --require` | | `{string}` | `run` | Require a module prior to executing the script. This is similar in behavior to the `-l` flag in the Lua interpreter. |
| `-I --include-dir` | `include_dir` | `{string}` | `build` `check` `gen` `run` | Prepend this directory to the module search path.
| `--gen-compat` | `gen_compat` | `string` | `build` `gen` `run` | Generate compatibility code for targeting different Lua VM versions. See [below](#generated-code) for details.
| `--gen-target` | `gen_target` | `string` | `build` `gen` `run` | Minimum targeted Lua version for generated code. Options are `5.1`, `5.3` and `5.4`. See [below](#generated-code) for details.
| `--gen-target` | `gen_target` | `string` | `build` `gen` `run` | Minimum targeted Lua version for generated code. Options are `5.1`, `5.2`, `5.3` and `5.4`. See [below](#generated-code) for details.
|| `include` | `{string}` | `build` | The set of files to compile/check. See below for details on patterns.
|| `exclude` | `{string}` | `build` | The set of files to exclude. See below for details on patterns.
| `-s --source-dir` | `source_dir` | `string` | `build` | Set the directory to be searched for files. `build` will compile every .tl file in every subdirectory by default.
Expand Down Expand Up @@ -55,6 +55,10 @@ choose what is the minimum Lua version you want to target. Valid options are
`5.1` (for Lua 5.1 and above, including LuaJIT) and `5.3` for Lua 5.3 and above.

Using `5.1`, Teal will generate compatibility code for the integer division operator,
a compatibility forward declaration for `table.unpack` and will use the `bit`
library (from LuaJIT or the luabitop compat module) for bitwise operators.

Using `5.2`, Teal will generate compatibility code for the integer division operator,
a compatibility forward declaration for `table.unpack` and will use the `bit32`
library for bitwise operators.

Expand Down
3 changes: 2 additions & 1 deletion docs/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -1045,7 +1045,8 @@ your own.
The Teal compiler also supports Lua-5.3-style bitwise operators (`&`, `|`, `~`,
`<<`, `>>`) and the integer division `//` operator on all supported Lua
versions. For Lua versions that do not support it natively, it generates code
using the bit32 library, which is also included in compat-5.3 for Lua 5.1.
using the bit32 library for Lua 5.2, and using the bit library for Lua 5.1
(included in LuaJIT or from the luabitop compat module).

You can explicitly disable the use of compat-5.3 with the `--skip-compat53`
flag and equivalent option in `tlconfig.lua`. However, if you do so, the Lua
Expand Down
44 changes: 43 additions & 1 deletion spec/cli/gen_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,48 @@ describe("tl gen", function()
util.assert_popen_close(0, pd:close())
local lua_name = tl_to_lua(name)
assert.match("Wrote: " .. lua_name, output, 1, true)
util.assert_line_by_line([[
local bit = bit; if not bit then local p, m = pcall(require, 'bit'); if p then bit = m end end
local x = math.floor(2 / 3)
local y = bit.lshift(2, 3)
]], util.read_file(lua_name))
end)

it("generates bit operations even for invalid variables (regression test for #673)", function()
local name = util.write_tmp_file(finally, [[
local foo = require("nonexisting")
local y = 2 | (foo.wat << 9)
local x = ~y
local z = aa // bb
]])
local pd = io.popen(util.tl_cmd("gen", "--gen-target=5.1", name), "r")
local output = pd:read("*a")
util.assert_popen_close(0, pd:close())
local lua_name = tl_to_lua(name)
assert.match("Wrote: " .. lua_name, output, 1, true)
util.assert_line_by_line([[
local bit = bit; if not bit then local p, m = pcall(require, 'bit'); if p then bit = m end end
local foo = require("nonexisting")
local y = bit.bor(2, (bit.lshift(foo.wat, 9)))
local x = bit.bnot(y)
local z = math.floor(aa / bb)
]], util.read_file(lua_name))
end)
end)

describe("with --gen-target=5.2", function()
it("targets generated code to Lua 5.2+", function()
local name = util.write_tmp_file(finally, [[
local x = 2 // 3
local y = 2 << 3
]])
local pd = io.popen(util.tl_cmd("gen", "--gen-target=5.2", name), "r")
local output = pd:read("*a")
util.assert_popen_close(0, pd:close())
local lua_name = tl_to_lua(name)
assert.match("Wrote: " .. lua_name, output, 1, true)
util.assert_line_by_line([[
local bit32 = bit32; if not bit32 then local p, m = pcall(require, 'bit32'); if p then bit32 = m end end
local x = math.floor(2 / 3)
Expand All @@ -212,7 +254,7 @@ describe("tl gen", function()
local x = ~y
local z = aa // bb
]])
local pd = io.popen(util.tl_cmd("gen", "--gen-target=5.1", name), "r")
local pd = io.popen(util.tl_cmd("gen", "--gen-target=5.2", name), "r")
local output = pd:read("*a")
util.assert_popen_close(0, pd:close())
local lua_name = tl_to_lua(name)
Expand Down
4 changes: 2 additions & 2 deletions spec/compat/lua_versions_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ describe("Lua version compatibility", function()
local c = 0xcafebabe
local x = 2 & (c >> ~4 | 0xff)
]], [[
local bit32 = bit32; if not bit32 then local p, m = pcall(require, 'bit32'); if p then bit32 = m end end
local bit = bit; if not bit then local p, m = pcall(require, 'bit'); if p then bit = m end end
local c = 0xcafebabe
local x = bit32.band(2, (bit32.bor(bit32.rshift(c, bit32.bnot(4)), 0xff)))
local x = bit.band(2, (bit.bor(bit.rshift(c, bit.bnot(4)), 0xff)))
]], "5.1"))

it("generates compat code for bitwise unary operator metamethods", util.gen([[
Expand Down
4 changes: 2 additions & 2 deletions tl
Original file line number Diff line number Diff line change
Expand Up @@ -766,7 +766,7 @@ local function validate_config(config)
quiet = "boolean",
skip_compat53 = "boolean",
gen_compat = { ["off"] = true, ["optional"] = true, ["required"] = true },
gen_target = { ["5.1"] = true, ["5.3"] = true, ["5.4"] = true },
gen_target = { ["5.1"] = true, ["5.2"] = true, ["5.3"] = true, ["5.4"] = true },
disable_warnings = "{string}",
warning_error = "{string}",

Expand Down Expand Up @@ -842,7 +842,7 @@ local function get_args_parser()
:defmode("a")

parser:option("--gen-target", "Minimum targeted Lua version for generated code.")
:choices({ "5.1", "5.3", "5.4" })
:choices({ "5.1", "5.2", "5.3", "5.4" })

parser:flag("--skip-compat53", "Skip compat53 insertions.")
:hidden(true)
Expand Down
23 changes: 22 additions & 1 deletion tl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ local tl = {TypeCheckOptions = {}, Env = {}, Symbol = {}, Result = {}, Error = {






tl.version = function()
Expand Down Expand Up @@ -4951,6 +4952,8 @@ local function add_compat_entries(program, used_set, gen_compat)
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 == "bit" then
load_code(name, "local bit = bit; if not bit then local p, m = " .. req("bit") .. "; if p then bit = m 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
Expand Down Expand Up @@ -10010,6 +10013,16 @@ tl.type_check = function(ast, opts)
end

if node.op.op == "~" and env.gen_target == "5.1" then
if meta_on_operator then
all_needs_compat["mt"] = true
convert_node_to_compat_mt_call(node, unop_to_metamethod[node.op.op], 1, node.e1)
else
all_needs_compat["bit"] = true
convert_node_to_compat_call(node, "bit", "bnot", node.e1)
end
end

if node.op.op == "~" and env.gen_target == "5.2" then
if meta_on_operator then
all_needs_compat["mt"] = true
convert_node_to_compat_mt_call(node, unop_to_metamethod[node.op.op], 1, node.e1)
Expand Down Expand Up @@ -10048,7 +10061,7 @@ tl.type_check = function(ast, opts)
node.known = FACT_TRUTHY
end

if node.op.op == "//" and env.gen_target == "5.1" then
if node.op.op == "//" and (env.gen_target == "5.1" or env.gen_target == "5.2") then
if meta_on_operator then
all_needs_compat["mt"] = true
convert_node_to_compat_mt_call(node, "__idiv", meta_on_operator, node.e1, node.e2)
Expand All @@ -10057,6 +10070,14 @@ tl.type_check = function(ast, opts)
convert_node_to_compat_call(node, "math", "floor", div)
end
elseif bit_operators[node.op.op] and env.gen_target == "5.1" then
if meta_on_operator then
all_needs_compat["mt"] = true
convert_node_to_compat_mt_call(node, binop_to_metamethod[node.op.op], meta_on_operator, node.e1, node.e2)
else
all_needs_compat["bit"] = true
convert_node_to_compat_call(node, "bit", bit_operators[node.op.op], node.e1, node.e2)
end
elseif bit_operators[node.op.op] and env.gen_target == "5.2" then
if meta_on_operator then
all_needs_compat["mt"] = true
convert_node_to_compat_mt_call(node, binop_to_metamethod[node.op.op], meta_on_operator, node.e1, node.e2)
Expand Down
23 changes: 22 additions & 1 deletion tl.tl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ local record tl

enum TargetMode
"5.1"
"5.2"
"5.3"
"5.4"
end
Expand Down Expand Up @@ -4951,6 +4952,8 @@ local function add_compat_entries(program: Node, used_set: {string: boolean}, ge
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 == "bit" then
load_code(name, "local bit = bit; if not bit then local p, m = " .. req("bit") .. "; if p then bit = m 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
Expand Down Expand Up @@ -10010,6 +10013,16 @@ tl.type_check = function(ast: Node, opts: TypeCheckOptions): Result, string
end

if node.op.op == "~" and env.gen_target == "5.1" then
if meta_on_operator then
all_needs_compat["mt"] = true
convert_node_to_compat_mt_call(node, unop_to_metamethod[node.op.op], 1, node.e1)
else
all_needs_compat["bit"] = true
convert_node_to_compat_call(node, "bit", "bnot", node.e1)
end
end

if node.op.op == "~" and env.gen_target == "5.2" then
if meta_on_operator then
all_needs_compat["mt"] = true
convert_node_to_compat_mt_call(node, unop_to_metamethod[node.op.op], 1, node.e1)
Expand Down Expand Up @@ -10048,7 +10061,7 @@ tl.type_check = function(ast: Node, opts: TypeCheckOptions): Result, string
node.known = FACT_TRUTHY
end

if node.op.op == "//" and env.gen_target == "5.1" then
if node.op.op == "//" and (env.gen_target == "5.1" or env.gen_target == "5.2") then
if meta_on_operator then
all_needs_compat["mt"] = true
convert_node_to_compat_mt_call(node, "__idiv", meta_on_operator, node.e1, node.e2)
Expand All @@ -10057,6 +10070,14 @@ tl.type_check = function(ast: Node, opts: TypeCheckOptions): Result, string
convert_node_to_compat_call(node, "math", "floor", div)
end
elseif bit_operators[node.op.op] and env.gen_target == "5.1" then
if meta_on_operator then
all_needs_compat["mt"] = true
convert_node_to_compat_mt_call(node, binop_to_metamethod[node.op.op], meta_on_operator, node.e1, node.e2)
else
all_needs_compat["bit"] = true
convert_node_to_compat_call(node, "bit", bit_operators[node.op.op], node.e1, node.e2)
end
elseif bit_operators[node.op.op] and env.gen_target == "5.2" then
if meta_on_operator then
all_needs_compat["mt"] = true
convert_node_to_compat_mt_call(node, binop_to_metamethod[node.op.op], meta_on_operator, node.e1, node.e2)
Expand Down

0 comments on commit 09855e2

Please sign in to comment.