-
Notifications
You must be signed in to change notification settings - Fork 48
Add bitwise operations support #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
26a82cc
78f9a17
5105462
49eb520
86bc86f
2902ac6
dfef897
b50b064
4a804b9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # bit [](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 | ||
| ``` | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: Why why not 64 bit?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mainly because numbers in Lua 5.1 are 64-bit floating-point, and Go uint64 overflows them so Go 64-bit uint cannot be presented properly in Lua 5.1 floating-point.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. makes sense; it's a little disappointing that it can't use the full size… I wonder if we should suffix methods with 32 to indicate that, but I won't press that and we can consider this resolved
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can add a suffix if you insist. I was considering that, but I didn't do it because it's impossible to have |
||
| 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 | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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")) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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, | ||
| } | ||
| } | ||
|
Comment on lines
+5
to
+28
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. praise: I like this style - I was thinking of doing it - Github copilot did the positional thing for me so I left it, but associative arrays are more descriptive!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! I completely missed your PR on my branch, so I did it this way, similarly to the tests you linked in the previous comments (there's a bit of repetition, but I hope that's acceptable). |
||
| 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 | ||
Uh oh!
There was an error while loading. Please reload this page.