From 67042f7e887e0e11dc56833743f3bdd6e38bb4a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philip=20Dub=C3=A9?= Date: Tue, 19 Mar 2024 05:29:13 +0000 Subject: [PATCH] bit32 for gopher-lua, passing bitwise.lua from Lua 5.2 test suite --- LICENSE | 14 +++++ bit32.go | 141 ++++++++++++++++++++++++++++++++++++++++++++++++++ bit32_test.go | 133 +++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 7 +++ go.sum | 2 + readme.md | 17 ++++++ 6 files changed, 314 insertions(+) create mode 100644 LICENSE create mode 100644 bit32.go create mode 100644 bit32_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 readme.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ec74691 --- /dev/null +++ b/LICENSE @@ -0,0 +1,14 @@ +BSD Zero Clause License + +Copyright (c) 2024 PeerDB + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/bit32.go b/bit32.go new file mode 100644 index 0000000..b4a7176 --- /dev/null +++ b/bit32.go @@ -0,0 +1,141 @@ +package gluabit32 + +import ( + "math/bits" + + "github.com/yuin/gopher-lua" +) + +func band(ls *lua.LState) uint32 { + x := ^uint32(0) + for i, top := 1, ls.GetTop(); i <= top; i += 1 { + x &= uint32(ls.CheckNumber(i)) + } + return x +} +func fieldWidthMaskArg(ls *lua.LState, arg int) (uint32, uint32) { + field := int32(ls.CheckNumber(arg)) + if field < 0 { + ls.RaiseError("field cannot be negative") + } + var width int32 + lwidth := ls.Get(arg + 1) + if w, ok := lwidth.(lua.LNumber); ok { + if w <= 0 { + ls.RaiseError("width must be positive") + } + width = int32(w) + } else if lwidth == lua.LNil { + width = 1 + } else { + ls.RaiseError("invalid width type") + return 0, 0 + } + if field+width > 32 { + ls.RaiseError("trying to access non-existent bits") + } + return uint32(field), ^(^uint32(0) << uint32(width)) +} + +func Loader(ls *lua.LState) int { + m := ls.SetFuncs(ls.NewTable(), map[string]lua.LGFunction{ + "arshift": Bit32arshift, + "band": Bit32band, + "bnot": Bit32bnot, + "bor": Bit32bor, + "btest": Bit32btest, + "bxor": Bit32bxor, + "extract": Bit32extract, + "replace": Bit32replace, + "lrotate": Bit32lrotate, + "lshift": Bit32lshift, + "rrotate": Bit32rrotate, + "rshift": Bit32rshift, + }) + ls.Push(m) + return 1 +} + +func Bit32arshift(ls *lua.LState) int { + x := int32(ls.CheckNumber(1)) + disp := int32(ls.CheckNumber(2)) + if disp >= 0 { + ls.Push(lua.LNumber(uint32(x >> disp))) + } else { + ls.Push(lua.LNumber(uint32(x) << -disp)) + } + return 1 +} +func Bit32band(ls *lua.LState) int { + ls.Push(lua.LNumber(band(ls))) + return 1 +} +func Bit32bnot(ls *lua.LState) int { + ls.Push(lua.LNumber(^uint32(ls.CheckNumber(1)))) + return 1 +} +func Bit32bor(ls *lua.LState) int { + x := uint32(0) + for i, top := 1, ls.GetTop(); i <= top; i += 1 { + x |= uint32(ls.CheckNumber(i)) + } + ls.Push(lua.LNumber(x)) + return 1 +} +func Bit32btest(ls *lua.LState) int { + ls.Push(lua.LBool(band(ls) != 0)) + return 1 +} +func Bit32bxor(ls *lua.LState) int { + x := uint32(0) + for i, top := 1, ls.GetTop(); i <= top; i += 1 { + x ^= uint32(ls.CheckNumber(i)) + } + ls.Push(lua.LNumber(x)) + return 1 +} +func Bit32extract(ls *lua.LState) int { + n := uint32(ls.CheckNumber(1)) + field, m := fieldWidthMaskArg(ls, 2) + ls.Push(lua.LNumber((n >> field) & m)) + return 1 +} +func Bit32lrotate(ls *lua.LState) int { + x := uint32(ls.CheckNumber(1)) + k := int(ls.CheckNumber(2)) + ls.Push(lua.LNumber(bits.RotateLeft32(x, k))) + return 1 +} +func Bit32lshift(ls *lua.LState) int { + x := uint32(ls.CheckNumber(1)) + disp := int32(ls.CheckNumber(2)) + if disp >= 0 { + ls.Push(lua.LNumber(x << disp)) + } else { + ls.Push(lua.LNumber(x >> -disp)) + } + return 1 +} +func Bit32replace(ls *lua.LState) int { + n := uint32(ls.CheckNumber(1)) + v := uint32(ls.CheckNumber(2)) + field, m := fieldWidthMaskArg(ls, 3) + ls.Push(lua.LNumber((n & ^(m << field)) | ((v & m) << field))) + return 1 +} +func Bit32rrotate(ls *lua.LState) int { + x := uint32(ls.CheckNumber(1)) + k := int(ls.CheckNumber(2)) + ls.Push(lua.LNumber(bits.RotateLeft32(x, -k))) + return 1 +} +func Bit32rshift(ls *lua.LState) int { + x := uint32(ls.CheckNumber(1)) + disp := int32(ls.CheckNumber(2)) + if disp >= 0 { + ls.Push(lua.LNumber(x >> disp)) + } else { + ls.Push(lua.LNumber(x << -disp)) + } + return 1 +} diff --git a/bit32_test.go b/bit32_test.go new file mode 100644 index 0000000..ed666bb --- /dev/null +++ b/bit32_test.go @@ -0,0 +1,133 @@ +package gluabit32 + +import ( + "testing" + + "github.com/yuin/gopher-lua" +) + +func Test_Lua(t *testing.T) { + ls := lua.NewState(lua.Options{}) + Loader(ls) + ls.Env.RawSetString("bit32", ls.Get(1)) + ls.SetTop(0) + + // From Lua 5.2 testsuite + if err := ls.DoString(`print("testing bitwise operations") + +assert(bit32.band() == bit32.bnot(0)) +assert(bit32.btest() == true) +assert(bit32.bor() == 0) +assert(bit32.bxor() == 0) + +assert(bit32.band() == bit32.band(0xffffffff)) +assert(bit32.band(1,2) == 0) + + +-- out-of-range numbers +assert(bit32.band(-1) == 0xffffffff) +assert(bit32.band(2^33 - 1) == 0xffffffff) +assert(bit32.band(-2^33 - 1) == 0xffffffff) +assert(bit32.band(2^33 + 1) == 1) +assert(bit32.band(-2^33 + 1) == 1) +assert(bit32.band(-2^40) == 0) +assert(bit32.band(2^40) == 0) +assert(bit32.band(-2^40 - 2) == 0xfffffffe) +assert(bit32.band(2^40 - 4) == 0xfffffffc) + +assert(bit32.lrotate(0, -1) == 0) +assert(bit32.lrotate(0, 7) == 0) +assert(bit32.lrotate(0x12345678, 4) == 0x23456781) +assert(bit32.rrotate(0x12345678, -4) == 0x23456781) +assert(bit32.lrotate(0x12345678, -8) == 0x78123456) +assert(bit32.rrotate(0x12345678, 8) == 0x78123456) +assert(bit32.lrotate(0xaaaaaaaa, 2) == 0xaaaaaaaa) +assert(bit32.lrotate(0xaaaaaaaa, -2) == 0xaaaaaaaa) +for i = -50, 50 do +assert(bit32.lrotate(0x89abcdef, i) == bit32.lrotate(0x89abcdef, i%32)) +end + +assert(bit32.lshift(0x12345678, 4) == 0x23456780) +assert(bit32.lshift(0x12345678, 8) == 0x34567800) +assert(bit32.lshift(0x12345678, -4) == 0x01234567) +assert(bit32.lshift(0x12345678, -8) == 0x00123456) +assert(bit32.lshift(0x12345678, 32) == 0) +assert(bit32.lshift(0x12345678, -32) == 0) +assert(bit32.rshift(0x12345678, 4) == 0x01234567) +assert(bit32.rshift(0x12345678, 8) == 0x00123456) +assert(bit32.rshift(0x12345678, 32) == 0) +assert(bit32.rshift(0x12345678, -32) == 0) +assert(bit32.arshift(0x12345678, 0) == 0x12345678) +assert(bit32.arshift(0x12345678, 1) == 0x12345678 / 2) +assert(bit32.arshift(0x12345678, -1) == 0x12345678 * 2) +assert(bit32.arshift(-1, 1) == 0xffffffff) +assert(bit32.arshift(-1, 24) == 0xffffffff) +assert(bit32.arshift(-1, 32) == 0xffffffff) +assert(bit32.arshift(-1, -1) == (-1 * 2) % 2^32) + +print("+") +-- some special cases +local c = {0, 1, 2, 3, 10, 0x80000000, 0xaaaaaaaa, 0x55555555, +0xffffffff, 0x7fffffff} + +for _, b in pairs(c) do +assert(bit32.band(b) == b) +assert(bit32.band(b, b) == b) +assert(bit32.btest(b, b) == (b ~= 0)) +assert(bit32.band(b, b, b) == b) +assert(bit32.btest(b, b, b) == (b ~= 0)) +assert(bit32.band(b, bit32.bnot(b)) == 0) +assert(bit32.bor(b, bit32.bnot(b)) == bit32.bnot(0)) +assert(bit32.bor(b) == b) +assert(bit32.bor(b, b) == b) +assert(bit32.bor(b, b, b) == b) +assert(bit32.bxor(b) == b) +assert(bit32.bxor(b, b) == 0) +assert(bit32.bxor(b, 0) == b) +assert(bit32.bnot(b) ~= b) +assert(bit32.bnot(bit32.bnot(b)) == b) +assert(bit32.bnot(b) == 2^32 - 1 - b) +assert(bit32.lrotate(b, 32) == b) +assert(bit32.rrotate(b, 32) == b) +assert(bit32.lshift(bit32.lshift(b, -4), 4) == bit32.band(b, bit32.bnot(0xf))) +assert(bit32.rshift(bit32.rshift(b, 4), -4) == bit32.band(b, bit32.bnot(0xf))) +for i = -40, 40 do +assert(bit32.lshift(b, i) == math.floor((b * 2^i) % 2^32)) +end +end + +assert(not pcall(bit32.band, {})) +assert(not pcall(bit32.bnot, "a")) +assert(not pcall(bit32.lshift, 45)) +assert(not pcall(bit32.lshift, 45, print)) +assert(not pcall(bit32.rshift, 45, print)) + +print("+") + + +-- testing extract/replace + +assert(bit32.extract(0x12345678, 0, 4) == 8) +assert(bit32.extract(0x12345678, 4, 4) == 7) +assert(bit32.extract(0xa0001111, 28, 4) == 0xa) +assert(bit32.extract(0xa0001111, 31, 1) == 1) +assert(bit32.extract(0x50000111, 31, 1) == 0) +assert(bit32.extract(0xf2345679, 0, 32) == 0xf2345679) + +assert(not pcall(bit32.extract, 0, -1)) +assert(not pcall(bit32.extract, 0, 32)) +assert(not pcall(bit32.extract, 0, 0, 33)) +assert(not pcall(bit32.extract, 0, 31, 2)) + +assert(bit32.replace(0x12345678, 5, 28, 4) == 0x52345678) +assert(bit32.replace(0x12345678, 0x87654321, 0, 32) == 0x87654321) +assert(bit32.replace(0, 1, 2) == 2^2) +assert(bit32.replace(0, -1, 4) == 2^4) +assert(bit32.replace(-1, 0, 31) == 2^31 - 1) +assert(bit32.replace(-1, 0, 1, 2) == 2^32 - 7) + +print("OK")`); err != nil { + t.Log(err.Error()) + t.FailNow() + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..12f11fd --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module github.com/PeerDB-io/gluabit32 + +go 1.17 + +require ( + github.com/yuin/gopher-lua v1.1.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e7daa0c --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..de6d780 --- /dev/null +++ b/readme.md @@ -0,0 +1,17 @@ +# bit32 for gopher-lua + +Implements Lua 5.2 [bit32](https://www.lua.org/manual/5.2/manual.html#6.7) for [gopher-lua](https://github.com/yuin/gopher-lua). To use, call +```go +import ( + "github.com/PeerDB-io/gluabit32" +) + +// add so that `local bit32 = require("bit32")` works +L.PreloadModule("bit32", gluabit32.Loader) + +// or add to global env +L.Push(ls.NewFunction(gluabit32.Loader)) +L.Call(0, 1) +L.Env.RawSetString("bit32", L.Get(-1)) +L.Pop(1) +```