feat: Implement Bitwise and Shift Operator Support (#496)

Co-authored-by: Ganesan Karuppasamy <[email protected]>
This commit is contained in:
Ganesan Karuppasamy
2023-12-25 18:25:26 +01:00
committed by GitHub
co-authored by Ganesan Karuppasamy
parent 07479cbe09
commit a0db9a6c19
9 changed files with 235 additions and 5 deletions
+44
View File
@@ -972,4 +972,48 @@ var Builtins = []*ast.Function{
return arrayType, nil
},
},
bitFunc("bitand", func(x, y int) (any, error) {
return x & y, nil
}),
bitFunc("bitor", func(x, y int) (any, error) {
return x | y, nil
}),
bitFunc("bitxor", func(x, y int) (any, error) {
return x ^ y, nil
}),
bitFunc("bitnand", func(x, y int) (any, error) {
return x &^ y, nil
}),
bitFunc("bitshl", func(x, y int) (any, error) {
if y < 0 {
return nil, fmt.Errorf("invalid operation: negative shift count %d (type int)", y)
}
return x << y, nil
}),
bitFunc("bitshr", func(x, y int) (any, error) {
if y < 0 {
return nil, fmt.Errorf("invalid operation: negative shift count %d (type int)", y)
}
return x >> y, nil
}),
bitFunc("bitushr", func(x, y int) (any, error) {
if y < 0 {
return nil, fmt.Errorf("invalid operation: negative shift count %d (type int)", y)
}
return int(uint(x) >> y), nil
}),
{
Name: "bitnot",
Func: func(args ...any) (any, error) {
if len(args) != 1 {
return nil, fmt.Errorf("invalid number of arguments for bitnot (expected 1, got %d)", len(args))
}
x, err := toInt(args[0])
if err != nil {
return nil, fmt.Errorf("%v to call bitnot", err)
}
return ^x, nil
},
Types: types(new(func(int) int)),
},
}
+35
View File
@@ -199,6 +199,13 @@ func TestBuiltin_errors(t *testing.T) {
{`date("error")`, `invalid date`},
{`get()`, `invalid number of arguments (expected 2, got 0)`},
{`get(1, 2)`, `type int does not support indexing`},
{`bitnot("1")`, "cannot use string as argument (type int) to call bitnot (1:8)"},
{`bitand("1", 1)`, "cannot use string as argument (type int) to call bitand (1:8)"},
{`"10" | bitor(1)`, "cannot use string as argument (type int) to call bitor (1:1)"},
{`bitshr("5", 1)`, "cannot use string as argument (type int) to call bitshr (1:8)"},
{`bitshr(-5, -2)`, "invalid operation: negative shift count -2 (type int) (1:1)"},
{`bitshl(1, -1)`, "invalid operation: negative shift count -1 (type int) (1:1)"},
{`bitushr(-5, -2)`, "invalid operation: negative shift count -2 (type int) (1:1)"},
}
for _, test := range errorTests {
t.Run(test.input, func(t *testing.T) {
@@ -437,3 +444,31 @@ func TestBuiltin_sort(t *testing.T) {
})
}
}
func TestBuiltin_bitOpsFunc(t *testing.T) {
tests := []struct {
input string
want int
}{
{`bitnot(156)`, -157},
{`bitand(bitnot(156), 255)`, 99},
{`bitor(987, -123)`, -33},
{`bitxor(15, 32)`, 47},
{`bitshl(39, 3)`, 312},
{`bitshr(5, 1)`, 2},
{`bitushr(-5, 2)`, 4611686018427387902},
{`bitnand(35, 9)`, 34},
}
for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
program, err := expr.Compile(test.input, expr.Env(nil))
require.NoError(t, err)
out, err := expr.Run(program, nil)
fmt.Printf("%v : %v", test.input, out)
require.NoError(t, err)
assert.Equal(t, test.want, out)
})
}
}
+22
View File
@@ -6,6 +6,7 @@ import (
"reflect"
"strconv"
"github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/vm/runtime"
)
@@ -272,3 +273,24 @@ func Min(args ...any) (any, error) {
}
return min, nil
}
func bitFunc(name string, fn func(x, y int) (any, error)) *ast.Function {
return &ast.Function{
Name: name,
Func: func(args ...any) (any, error) {
if len(args) != 2 {
return nil, fmt.Errorf("invalid number of arguments for %s (expected 2, got %d)", name, len(args))
}
x, err := toInt(args[0])
if err != nil {
return nil, fmt.Errorf("%v to call %s", err, name)
}
y, err := toInt(args[1])
if err != nil {
return nil, fmt.Errorf("%v to call %s", err, name)
}
return fn(x, y)
},
Types: types(new(func(int, int) int)),
}
}
+27
View File
@@ -63,3 +63,30 @@ loop:
panic(fmt.Sprintf("cannot deref %s", v))
}
func toInt(val any) (int, error) {
switch v := val.(type) {
case int:
return v, nil
case int8:
return int(v), nil
case int16:
return int(v), nil
case int32:
return int(v), nil
case int64:
return int(v), nil
case uint:
return int(v), nil
case uint8:
return int(v), nil
case uint16:
return int(v), nil
case uint32:
return int(v), nil
case uint64:
return int(v), nil
default:
return 0, fmt.Errorf("cannot use %T as argument (type int)", val)
}
}
+3 -2
View File
@@ -4,12 +4,13 @@ import (
"math"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/expr-lang/expr"
"github.com/expr-lang/expr/test/playground"
"github.com/expr-lang/expr/vm"
"github.com/expr-lang/expr/vm/runtime"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type B struct {
+68
View File
@@ -696,6 +696,74 @@ Converts an array of key-value pairs to a map.
fromPairs([["name", "John"], ["age", 30]]) == {"name": "John", "age": 30}
```
## Bitwise Functions
### bitand(int, int)
Returns the values resulting from the bitwise AND operation.
```expr
bitand(10, 12) == 8
```
### bitor(int, int)
Returns the values resulting from the bitwise OR operation.
```expr
bitor(10, 12) == 14
```
### bitxor(int, int)
Returns the values resulting from the bitwise XOR operation.
```expr
bitxor(10, 12) == 6
```
### bitnand(int, int)
Returns the values resulting from the bitwise AND NOT operation.
```expr
bitnand(10, 12) == 2
```
### bitnot(int)
Returns the values resulting from the bitwise NOT operation.
```expr
bitnot(10) == -11
```
## Shift Functions
### bitshl(int, int)
Returns the values resulting from the Left Shift operation.
```expr
bitshl(45, 2) == 180
```
### bitshr(int, int)
Returns the values resulting from the Right Shift operation.
```expr
bitshr(45, 2) == 11
```
### bitushr(int, int)
Returns the values resulting from the unsigned Right Shift operation.
```expr
bitushr(-5, 2) == 4611686018427387902
```
## Miscellaneous Functions
### len(v)
+32
View File
@@ -1136,6 +1136,38 @@ func TestExpr(t *testing.T) {
`.5 in ArrayOfInt`,
false,
},
{
`bitnot(10)`,
-11,
},
{
`bitxor(15, 32)`,
47,
},
{
`bitand(90, 34)`,
2,
},
{
`bitnand(35, 9)`,
34,
},
{
`bitor(10, 5)`,
15,
},
{
`bitshr(7, 2)`,
1,
},
{
`bitshl(7, 2)`,
28,
},
{
`bitushr(-100, 5)`,
576460752303423484,
},
}
for _, tt := range tests {
+3 -2
View File
@@ -5,10 +5,11 @@ import (
"strings"
"testing"
. "github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/parser"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
. "github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/parser"
)
func TestParse(t *testing.T) {
+1 -1
View File
@@ -3372,4 +3372,4 @@ func Modulo(a, b interface{}) int {
}
}
panic(fmt.Sprintf("invalid operation: %T %% %T", a, b))
}
}