diff --git a/README.md b/README.md index 268f83d..cad4e5a 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ go get github.com/vadv/gopher-lua-libs * [xmlpath](/xmlpath) [gopkg.in/xmlpath.v2](https://gopkg.in/xmlpath.v2) port * [yaml](/yaml) [gopkg.in/yaml.v2](https://gopkg.in/yaml.v2) port * [zabbix](/zabbix) zabbix bot +* [bit](/bit) bitwise operations ## Usage diff --git a/bit/README.md b/bit/README.md new file mode 100644 index 0000000..fa63aba --- /dev/null +++ b/bit/README.md @@ -0,0 +1,16 @@ +# bit [![GoDoc](https://godoc.org/github.com/vadv/gopher-lua-libs/bit?bit.svg)](https://godoc.org/github.com/vadv/gopher-lua-libs/bit) + +## Usage + +```lua +local bit = require("bit") + +local result, _ = bit.band(1, 0) +print(result) +-- Output: 0 + +local result, _ = bit.lshift(10, 5) +print(result) +-- Output: 320 +``` + diff --git a/bit/api.go b/bit/api.go new file mode 100644 index 0000000..630d6ed --- /dev/null +++ b/bit/api.go @@ -0,0 +1,85 @@ +// Package bit implements Go bitwise operations functionality for Lua. +package bit + +import ( + "fmt" + "math" + + lua "github.com/yuin/gopher-lua" +) + +type op uint + +const ( + and op = iota + or + not + xor + ls + rs +) + +// Bitwise returns a Lua function used for bitwise operations. +func Bitwise(kind op) lua.LGFunction { + return func(l *lua.LState) int { + if kind > rs { + l.RaiseError("unsupported operation type") + return 0 + } + val1, val2, err := prepareParams(l) + if err != nil { + l.Push(lua.LNil) + l.Push(lua.LString(err.Error())) + return 2 + } + var ret uint32 + switch kind { + case and: + ret = val1 & val2 + case or: + ret = val1 | val2 + case xor: + ret = val1 ^ val2 + case ls: + ret = val1 << val2 + case rs: + ret = val1 >> val2 + } + l.Push(lua.LNumber(ret)) + return 1 + } +} + +// Not implements bitwise not. +func Not(l *lua.LState) int { + val, err := intToU32(l.CheckInt(1)) + if err != nil { + l.Push(lua.LNil) + l.Push(lua.LString(err.Error())) + return 2 + } + l.Push(lua.LNumber(^val)) + return 1 +} + +func prepareParams(l *lua.LState) (val1, val2 uint32, err error) { + val1, err = intToU32(l.CheckInt(1)) + if err != nil { + return 0, 0, err + } + val2, err = intToU32(l.CheckInt(2)) + if err != nil { + return 0, 0, err + } + return +} + +func intToU32(i int) (uint32, error) { + if i < 0 { + return 0, fmt.Errorf("cannot convert negative int %d to uint32", i) + } + if i > math.MaxUint32 { + return 0, fmt.Errorf("int %d overflows uint32", i) + } + return uint32(i), nil +} diff --git a/bit/api_test.go b/bit/api_test.go new file mode 100644 index 0000000..435638f --- /dev/null +++ b/bit/api_test.go @@ -0,0 +1,13 @@ +package bit + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/vadv/gopher-lua-libs/tests" +) + +func TestApi(t *testing.T) { + preload := tests.SeveralPreloadFuncs(Preload) + assert.NotZero(t, tests.RunLuaTestFile(t, preload, "./test/test_api.lua")) +} diff --git a/bit/loader.go b/bit/loader.go new file mode 100644 index 0000000..6b3a75a --- /dev/null +++ b/bit/loader.go @@ -0,0 +1,28 @@ +package bit + +import lua "github.com/yuin/gopher-lua" + +// Preload adds bit to the given Lua state's package.preload table. After it +// has been preloaded, it can be loaded using require: +// +// local bit = require("bit") +func Preload(l *lua.LState) { + l.PreloadModule("bit", Loader) +} + +// Loader is the module loader function. +func Loader(L *lua.LState) int { + t := L.NewTable() + L.SetFuncs(t, api) + L.Push(t) + return 1 +} + +var api = map[string]lua.LGFunction{ + "band": Bitwise(and), + "bor": Bitwise(or), + "bxor": Bitwise(xor), + "lshift": Bitwise(ls), + "rshift": Bitwise(rs), + "bnot": Not, +} diff --git a/bit/test/test_api.lua b/bit/test/test_api.lua new file mode 100644 index 0000000..a1e6e00 --- /dev/null +++ b/bit/test/test_api.lua @@ -0,0 +1,202 @@ +local bit = require 'bit' +local assert = require 'assert' + +function TestAnd(t) + local tests = { + { + input1 = -3, + input2 = 23, + expected = nil, + err = "cannot convert negative int -3 to uint32", + }, + { + input1 = 4294967296, + input2 = 23, + expected = nil, + err = "int 4294967296 overflows uint32", + }, + { + input1 = 1, + input2 = 0, + expected = 0, + }, + { + input1 = 111, + input2 = 222, + expected = 78, + } + } + for _, tt in ipairs(tests) do + t:Run(tostring(tt.input1) .. " and " .. tostring(tt.input2), function(t) + local got, err = bit.band(tt.input1, tt.input2) + assert:Equal(t, tt.expected, got) + assert:Equal(t, tt.err, err) + end) + end +end + +function TestOr(t) + local tests = { + { + input1 = 5, + input2 = -423, + expected = nil, + err = "cannot convert negative int -423 to uint32", + }, + { + input1 = 123, + input2 = 4294967296, + expected = nil, + err = "int 4294967296 overflows uint32", + }, + { + input1 = 1, + input2 = 0, + expected = 1, + }, + { + input1 = 111, + input2 = 222, + expected = 255, + } + } + for _, tt in ipairs(tests) do + t:Run(tostring(tt.input1) .. " or " .. tostring(tt.input2), function(t) + local got, err = bit.bor(tt.input1, tt.input2) + assert:Equal(t, tt.expected, got) + assert:Equal(t, tt.err, err) + end) + end +end + +function TestXor(t) + local tests = { + { + input1 = -1, + input2 = -46, + expected = nil, + err = "cannot convert negative int -1 to uint32", + }, + { + input1 = 4294967300, + input2 = 46, + expected = nil, + err = "int 4294967300 overflows uint32", + }, + { + input1 = 1, + input2 = 0, + expected = 1, + }, + { + input1 = 111, + input2 = 222, + expected = 177, + } + } + for _, tt in ipairs(tests) do + t:Run(tostring(tt.input1) .. " xor " .. tostring(tt.input2), function(t) + local got, err = bit.bxor(tt.input1, tt.input2) + assert:Equal(t, tt.expected, got) + assert:Equal(t, tt.err, err) + end) + end +end + +function TestLShift(t) + local tests = { + { + input1 = 0, + input2 = -10, + expected = nil, + err = "cannot convert negative int -10 to uint32", + }, + { + input1 = 4294967297, + input2 = 4294967298, + expected = nil, + err = "int 4294967297 overflows uint32", + }, + { + input1 = 123456, + input2 = 8, + expected = 31604736, + }, + { + input1 = 0XFF, + input2 = 8, + expected = 65280, + } + } + for _, tt in ipairs(tests) do + t:Run(tostring(tt.input1) .. " << " .. tostring(tt.input2), function(t) + local got, err = bit.lshift(tt.input1, tt.input2) + assert:Equal(t, tt.expected, got) + assert:Equal(t, tt.err, err) + end) + end +end + +function TestRShift(t) + local tests = { + { + input1 = -10, + input2 = 0, + expected = nil, + err = "cannot convert negative int -10 to uint32", + }, + { + input1 = 4294967296, + input2 = -3, + expected = nil, + err = "int 4294967296 overflows uint32", + }, + { + input1 = 123456, + input2 = 8, + expected = 482, + }, + { + input1 = 0XFF, + input2 = 1, + expected = 0x7F, + } + } + for _, tt in ipairs(tests) do + t:Run(tostring(tt.input1) .. " >> " .. tostring(tt.input2), function(t) + local got, err = bit.rshift(tt.input1, tt.input2) + assert:Equal(t, tt.expected, got) + assert:Equal(t, tt.err, err) + end) + end +end + +function TestNot(t) + local tests = { + { + input = -3, + expected = nil, + err = "cannot convert negative int -3 to uint32", + }, + { + input = 4294967297, + expected = nil, + err = "int 4294967297 overflows uint32", + }, + { + input = 65536, + expected = 4294901759, + }, + { + input = 4294901759, + expected = 65536, + } + } + for _, tt in ipairs(tests) do + t:Run("not " .. tostring(tt.input), function(t) + local got, err = bit.bnot(tt.input) + assert:Equal(t, tt.expected, got) + assert:Equal(t, tt.err, err) + end) + end +end diff --git a/plugin/preload.go b/plugin/preload.go index 45b8e8d..8b30441 100644 --- a/plugin/preload.go +++ b/plugin/preload.go @@ -4,6 +4,7 @@ import ( "github.com/vadv/gopher-lua-libs/argparse" "github.com/vadv/gopher-lua-libs/aws/cloudwatch" "github.com/vadv/gopher-lua-libs/base64" + "github.com/vadv/gopher-lua-libs/bit" "github.com/vadv/gopher-lua-libs/cert_util" "github.com/vadv/gopher-lua-libs/chef" "github.com/vadv/gopher-lua-libs/cmd" @@ -74,4 +75,5 @@ func PreloadAll(L *lua.LState) { xmlpath.Preload(L) yaml.Preload(L) zabbix.Preload(L) + bit.Preload(L) }