mirror of
https://github.com/hanzoai/expr.git
synced 2026-09-24 16:58:58 +00:00
feat: Implement Bitwise and Shift Operator Support (#496)
Co-authored-by: Ganesan Karuppasamy <[email protected]>
This commit is contained in:
committed by
GitHub
co-authored by
Ganesan Karuppasamy
parent
07479cbe09
commit
a0db9a6c19
@@ -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)),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -3372,4 +3372,4 @@ func Modulo(a, b interface{}) int {
|
||||
}
|
||||
}
|
||||
panic(fmt.Sprintf("invalid operation: %T %% %T", a, b))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user