🏴

Lua(JIT)のbit演算

2022/12/18に公開

この記事はLua advent calendar 2022の19日目です。

Luaとbit演算

現在使われているLuaの多くは、5.x系だと思います。
5.3以降でbit演算子が追加されました。

Lua supports the following bitwise operators:

  • &: bitwise AND
  • |: bitwise OR
  • ~: bitwise exclusive OR
  • >>: right shift
  • <<: left shift
  • ~: unary bitwise NOT

出典 : https://www.lua.org/manual/5.3/manual.html#3.4.2

ところで、Luaのruntimeには公式以外に有名なものが一つありますよね。
そう、LuaJIT (a Just-In-Time Compiler for Lua)です。
これは非常に高速ですが、Lua5.1互換です。
ですのでbit演算子はありませんし、他にも幾つかのlibraryが使えなかったりします。

LuaJITでもbit演算はできる

さて、早速矛盾するようですが、LuaJITでもbit演算はできます。
言い直すなら、「bit演算子はない」ですが「bit演算」はできます。
新しい演算子を追加してしまうと互換性を失ってしまいますからね。
つまり、この機能は関数として提供されます。

http://bitop.luajit.org

LuaJITはLua5.1互換ですが、全く同じなわけではありません。
本家にはない機能を持っていたりもします。

使用例

LuaJITはNeovimというテキストエディタに組み込まれており、設定の記述やプラグインの作成に使えます。
vim scriptよりもはるかに高速なことから人気で、多くのプラグインが作成されています。

しかし、日本人である私にとって困る点が一つありました。
それは、LuaJITでは標準でマルチバイト文字を扱う方法がないということです。

Lua5.3以降では標準でutf8 moduleが存在するのですが、LuaJITにはありません。
そこで、同様の機能を使うためにbit演算を用いて作ったのがこちらのプラグインです。

https://github.com/uga-rosa/utf8.nvim

こんな感じでbit演算を使っています。

local utf8 = {}

local bit = require("bit") -- luajit

local band = bit.band
local bor = bit.bor
local rshift = bit.rshift
local lshift = bit.lshift

--------------------------------------------------------------
-- 中略
--------------------------------------------------------------

---Receives zero or more integers, converts each one to its corresponding UTF-8 byte sequence
---and returns a string with the concatenation of all these sequences.
---@vararg integer
---@return string
function utf8.char(...)
  local buffer = {}
  for i, v in ipairs({ ... }) do
    if v < 0 or v > 0x10FFFF then
      error(create_errmsg(i, "char", "value"), 2)
    elseif v < 0x80 then
      -- single-byte
      buffer[i] = string.char(v)
    elseif v < 0x800 then
      -- two-byte
      local b1 = bor(0xC0, band(rshift(v, 6), 0x1F)) -- 110x-xxxx
      local b2 = bor(0x80, band(v, 0x3F)) -- 10xx-xxxx
      buffer[i] = string.char(b1, b2)
    elseif v < 0x10000 then
      -- three-byte
      local b1 = bor(0xE0, band(rshift(v, 12), 0x0F)) -- 1110-xxxx
      local b2 = bor(0x80, band(rshift(v, 6), 0x3F)) -- 10xx-xxxx
      local b3 = bor(0x80, band(v, 0x3F)) -- 10xx-xxxx
      buffer[i] = string.char(b1, b2, b3)
    else
      -- four-byte
      local b1 = bor(0xF0, band(rshift(v, 18), 0x07)) -- 1111-0xxx
      local b2 = bor(0x80, band(rshift(v, 12), 0x3F)) -- 10xx-xxxx
      local b3 = bor(0x80, band(rshift(v, 6), 0x3F)) -- 10xx-xxxx
      local b4 = bor(0x80, band(v, 0x3F)) -- 10xx-xxxx
      buffer[i] = string.char(b1, b2, b3, b4)
    end
  end
  return table.concat(buffer, "")
end

ネストが増えやすく演算子ほど書きやすくはないですが、十分使えます。
中々使う機会はないかもしれませんが、覚えておくとどこかで助かるかもしれませんね。

GitHubで編集を提案

Discussion