mirror of
https://github.com/hanzoai/expr.git
synced 2026-09-24 16:58:58 +00:00
Add OpCallBuiltin opcode to avoid an extra allocation
This commit is contained in:
+18
-8
@@ -8,14 +8,24 @@ import (
|
||||
type Function struct {
|
||||
Name string
|
||||
Func func(args ...interface{}) (interface{}, error)
|
||||
Builtin1 func(arg interface{}) interface{}
|
||||
Types []reflect.Type
|
||||
Validate func(args []reflect.Type) (reflect.Type, error)
|
||||
}
|
||||
|
||||
var Index map[string]int
|
||||
|
||||
func init() {
|
||||
Index = make(map[string]int)
|
||||
for i, fn := range Builtins {
|
||||
Index[fn.Name] = i
|
||||
}
|
||||
}
|
||||
|
||||
var Builtins = []*Function{
|
||||
{
|
||||
Name: "len",
|
||||
Func: Len,
|
||||
Name: "len",
|
||||
Builtin1: Len,
|
||||
Validate: func(args []reflect.Type) (reflect.Type, error) {
|
||||
if len(args) != 1 {
|
||||
return anyType, fmt.Errorf("invalid number of arguments for len (expected 1, got %d)", len(args))
|
||||
@@ -28,8 +38,8 @@ var Builtins = []*Function{
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "abs",
|
||||
Func: Abs,
|
||||
Name: "abs",
|
||||
Builtin1: Abs,
|
||||
Validate: func(args []reflect.Type) (reflect.Type, error) {
|
||||
if len(args) != 1 {
|
||||
return anyType, fmt.Errorf("invalid number of arguments for abs (expected 1, got %d)", len(args))
|
||||
@@ -42,8 +52,8 @@ var Builtins = []*Function{
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "int",
|
||||
Func: Int,
|
||||
Name: "int",
|
||||
Builtin1: Int,
|
||||
Validate: func(args []reflect.Type) (reflect.Type, error) {
|
||||
if len(args) != 1 {
|
||||
return anyType, fmt.Errorf("invalid number of arguments for int (expected 1, got %d)", len(args))
|
||||
@@ -60,8 +70,8 @@ var Builtins = []*Function{
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "float",
|
||||
Func: Float,
|
||||
Name: "float",
|
||||
Builtin1: Float,
|
||||
Validate: func(args []reflect.Type) (reflect.Type, error) {
|
||||
if len(args) != 1 {
|
||||
return anyType, fmt.Errorf("invalid number of arguments for float (expected 1, got %d)", len(args))
|
||||
|
||||
+61
-65
@@ -6,168 +6,164 @@ import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func Len(xs ...interface{}) (interface{}, error) {
|
||||
x := xs[0]
|
||||
func Len(x interface{}) interface{} {
|
||||
v := reflect.ValueOf(x)
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
|
||||
return v.Len(), nil
|
||||
return v.Len()
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid argument for len (type %T)", x)
|
||||
panic(fmt.Sprintf("invalid argument for len (type %T)", x))
|
||||
}
|
||||
}
|
||||
|
||||
func Abs(xs ...interface{}) (interface{}, error) {
|
||||
x := xs[0]
|
||||
func Abs(x interface{}) interface{} {
|
||||
switch x.(type) {
|
||||
case float32:
|
||||
if x.(float32) < 0 {
|
||||
return -x.(float32), nil
|
||||
return -x.(float32)
|
||||
} else {
|
||||
return x, nil
|
||||
return x
|
||||
}
|
||||
case float64:
|
||||
if x.(float64) < 0 {
|
||||
return -x.(float64), nil
|
||||
return -x.(float64)
|
||||
} else {
|
||||
return x, nil
|
||||
return x
|
||||
}
|
||||
case int:
|
||||
if x.(int) < 0 {
|
||||
return -x.(int), nil
|
||||
return -x.(int)
|
||||
} else {
|
||||
return x, nil
|
||||
return x
|
||||
}
|
||||
case int8:
|
||||
if x.(int8) < 0 {
|
||||
return -x.(int8), nil
|
||||
return -x.(int8)
|
||||
} else {
|
||||
return x, nil
|
||||
return x
|
||||
}
|
||||
case int16:
|
||||
if x.(int16) < 0 {
|
||||
return -x.(int16), nil
|
||||
return -x.(int16)
|
||||
} else {
|
||||
return x, nil
|
||||
return x
|
||||
}
|
||||
case int32:
|
||||
if x.(int32) < 0 {
|
||||
return -x.(int32), nil
|
||||
return -x.(int32)
|
||||
} else {
|
||||
return x, nil
|
||||
return x
|
||||
}
|
||||
case int64:
|
||||
if x.(int64) < 0 {
|
||||
return -x.(int64), nil
|
||||
return -x.(int64)
|
||||
} else {
|
||||
return x, nil
|
||||
return x
|
||||
}
|
||||
case uint:
|
||||
if x.(uint) < 0 {
|
||||
return -x.(uint), nil
|
||||
return -x.(uint)
|
||||
} else {
|
||||
return x, nil
|
||||
return x
|
||||
}
|
||||
case uint8:
|
||||
if x.(uint8) < 0 {
|
||||
return -x.(uint8), nil
|
||||
return -x.(uint8)
|
||||
} else {
|
||||
return x, nil
|
||||
return x
|
||||
}
|
||||
case uint16:
|
||||
if x.(uint16) < 0 {
|
||||
return -x.(uint16), nil
|
||||
return -x.(uint16)
|
||||
} else {
|
||||
return x, nil
|
||||
return x
|
||||
}
|
||||
case uint32:
|
||||
if x.(uint32) < 0 {
|
||||
return -x.(uint32), nil
|
||||
return -x.(uint32)
|
||||
} else {
|
||||
return x, nil
|
||||
return x
|
||||
}
|
||||
case uint64:
|
||||
if x.(uint64) < 0 {
|
||||
return -x.(uint64), nil
|
||||
return -x.(uint64)
|
||||
} else {
|
||||
return x, nil
|
||||
return x
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("invalid argument for abs (type %T)", x)
|
||||
panic(fmt.Sprintf("invalid argument for abs (type %T)", x))
|
||||
}
|
||||
|
||||
func Int(xs ...interface{}) (interface{}, error) {
|
||||
x := xs[0]
|
||||
func Int(x interface{}) interface{} {
|
||||
switch x := x.(type) {
|
||||
case float32:
|
||||
return int(x), nil
|
||||
return int(x)
|
||||
case float64:
|
||||
return int(x), nil
|
||||
return int(x)
|
||||
case int:
|
||||
return x, nil
|
||||
return x
|
||||
case int8:
|
||||
return int(x), nil
|
||||
return int(x)
|
||||
case int16:
|
||||
return int(x), nil
|
||||
return int(x)
|
||||
case int32:
|
||||
return int(x), nil
|
||||
return int(x)
|
||||
case int64:
|
||||
return int(x), nil
|
||||
return int(x)
|
||||
case uint:
|
||||
return int(x), nil
|
||||
return int(x)
|
||||
case uint8:
|
||||
return int(x), nil
|
||||
return int(x)
|
||||
case uint16:
|
||||
return int(x), nil
|
||||
return int(x)
|
||||
case uint32:
|
||||
return int(x), nil
|
||||
return int(x)
|
||||
case uint64:
|
||||
return int(x), nil
|
||||
return int(x)
|
||||
case string:
|
||||
i, err := strconv.Atoi(x)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid operation: int(%s)", x)
|
||||
panic(fmt.Sprintf("invalid operation: int(%s)", x))
|
||||
}
|
||||
return i, nil
|
||||
return i
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid operation: int(%T)", x)
|
||||
panic(fmt.Sprintf("invalid operation: int(%T)", x))
|
||||
}
|
||||
}
|
||||
|
||||
func Float(xs ...interface{}) (interface{}, error) {
|
||||
x := xs[0]
|
||||
func Float(x interface{}) interface{} {
|
||||
switch x := x.(type) {
|
||||
case float32:
|
||||
return float64(x), nil
|
||||
return float64(x)
|
||||
case float64:
|
||||
return x, nil
|
||||
return x
|
||||
case int:
|
||||
return float64(x), nil
|
||||
return float64(x)
|
||||
case int8:
|
||||
return float64(x), nil
|
||||
return float64(x)
|
||||
case int16:
|
||||
return float64(x), nil
|
||||
return float64(x)
|
||||
case int32:
|
||||
return float64(x), nil
|
||||
return float64(x)
|
||||
case int64:
|
||||
return float64(x), nil
|
||||
return float64(x)
|
||||
case uint:
|
||||
return float64(x), nil
|
||||
return float64(x)
|
||||
case uint8:
|
||||
return float64(x), nil
|
||||
return float64(x)
|
||||
case uint16:
|
||||
return float64(x), nil
|
||||
return float64(x)
|
||||
case uint32:
|
||||
return float64(x), nil
|
||||
return float64(x)
|
||||
case uint64:
|
||||
return float64(x), nil
|
||||
return float64(x)
|
||||
case string:
|
||||
f, err := strconv.ParseFloat(x, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid operation: float(%s)", x)
|
||||
panic(fmt.Sprintf("invalid operation: float(%s)", x))
|
||||
}
|
||||
return f, nil
|
||||
return f
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid operation: float(%T)", x)
|
||||
panic(fmt.Sprintf("invalid operation: float(%T)", x))
|
||||
}
|
||||
}
|
||||
|
||||
+135
-127
@@ -493,45 +493,8 @@ func (v *visitor) CallNode(node *ast.CallNode) (reflect.Type, info) {
|
||||
fn, fnInfo := v.visit(node.Callee)
|
||||
|
||||
if fnInfo.fn != nil {
|
||||
f := fnInfo.fn
|
||||
node.Func = f
|
||||
if f.Validate != nil {
|
||||
args := make([]reflect.Type, len(node.Arguments))
|
||||
for i, arg := range node.Arguments {
|
||||
args[i], _ = v.visit(arg)
|
||||
}
|
||||
t, err := f.Validate(args)
|
||||
if err != nil {
|
||||
return v.error(node, "%v", err)
|
||||
}
|
||||
return t, info{}
|
||||
}
|
||||
if len(f.Types) == 0 {
|
||||
t, err := v.checkFunc(f.Name, functionType, false, node)
|
||||
if err != nil {
|
||||
if v.err == nil {
|
||||
v.err = err
|
||||
}
|
||||
return anyType, info{}
|
||||
}
|
||||
// No type was specified, so we assume the function returns any.
|
||||
return t, info{}
|
||||
}
|
||||
var lastErr *file.Error
|
||||
for _, t := range f.Types {
|
||||
outType, err := v.checkFunc(f.Name, t, false, node)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
return outType, info{}
|
||||
}
|
||||
if lastErr != nil {
|
||||
if v.err == nil {
|
||||
v.err = lastErr
|
||||
}
|
||||
return anyType, info{}
|
||||
}
|
||||
node.Func = fnInfo.fn
|
||||
return v.checkFunction(fnInfo.fn, node.Arguments, node)
|
||||
}
|
||||
|
||||
fnName := "function"
|
||||
@@ -564,7 +527,7 @@ func (v *visitor) CallNode(node *ast.CallNode) (reflect.Type, info) {
|
||||
}
|
||||
}
|
||||
|
||||
outType, err := v.checkFunc(fnName, fn, fnInfo.method, node)
|
||||
outType, err := v.checkArguments(fnName, fn, fnInfo.method, node.Arguments, node)
|
||||
if err != nil {
|
||||
if v.err == nil {
|
||||
v.err = err
|
||||
@@ -579,90 +542,6 @@ func (v *visitor) CallNode(node *ast.CallNode) (reflect.Type, info) {
|
||||
return v.error(node, "%v is not callable", fn)
|
||||
}
|
||||
|
||||
func (v *visitor) checkFunc(name string, fn reflect.Type, method bool, node *ast.CallNode) (reflect.Type, *file.Error) {
|
||||
if isAny(fn) {
|
||||
return anyType, nil
|
||||
}
|
||||
|
||||
if fn.NumOut() == 0 {
|
||||
return anyType, &file.Error{
|
||||
Location: node.Location(),
|
||||
Message: fmt.Sprintf("func %v doesn't return value", name),
|
||||
}
|
||||
}
|
||||
if numOut := fn.NumOut(); numOut > 2 {
|
||||
return anyType, &file.Error{
|
||||
Location: node.Location(),
|
||||
Message: fmt.Sprintf("func %v returns more then two values", name),
|
||||
}
|
||||
}
|
||||
|
||||
// If func is method on an env, first argument should be a receiver,
|
||||
// and actual arguments less than fnNumIn by one.
|
||||
fnNumIn := fn.NumIn()
|
||||
if method {
|
||||
fnNumIn--
|
||||
}
|
||||
// Skip first argument in case of the receiver.
|
||||
fnInOffset := 0
|
||||
if method {
|
||||
fnInOffset = 1
|
||||
}
|
||||
|
||||
if fn.IsVariadic() {
|
||||
if len(node.Arguments) < fnNumIn-1 {
|
||||
return anyType, &file.Error{
|
||||
Location: node.Location(),
|
||||
Message: fmt.Sprintf("not enough arguments to call %v", name),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if len(node.Arguments) > fnNumIn {
|
||||
return anyType, &file.Error{
|
||||
Location: node.Location(),
|
||||
Message: fmt.Sprintf("too many arguments to call %v", name),
|
||||
}
|
||||
}
|
||||
if len(node.Arguments) < fnNumIn {
|
||||
return anyType, &file.Error{
|
||||
Location: node.Location(),
|
||||
Message: fmt.Sprintf("not enough arguments to call %v", name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, arg := range node.Arguments {
|
||||
t, _ := v.visit(arg)
|
||||
|
||||
var in reflect.Type
|
||||
if fn.IsVariadic() && i >= fnNumIn-1 {
|
||||
// For variadic arguments fn(xs ...int), go replaces type of xs (int) with ([]int).
|
||||
// As we compare arguments one by one, we need underling type.
|
||||
in = fn.In(fn.NumIn() - 1).Elem()
|
||||
} else {
|
||||
in = fn.In(i + fnInOffset)
|
||||
}
|
||||
|
||||
if isIntegerOrArithmeticOperation(arg) && (isInteger(in) || isFloat(in)) {
|
||||
t = in
|
||||
setTypeForIntegers(arg, t)
|
||||
}
|
||||
|
||||
if t == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if !t.AssignableTo(in) && t.Kind() != reflect.Interface {
|
||||
return anyType, &file.Error{
|
||||
Location: arg.Location(),
|
||||
Message: fmt.Sprintf("cannot use %v as argument (type %v) to call %v ", t, in, name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fn.Out(0), nil
|
||||
}
|
||||
|
||||
func (v *visitor) BuiltinNode(node *ast.BuiltinNode) (reflect.Type, info) {
|
||||
switch node.Name {
|
||||
case "all", "none", "any", "one":
|
||||
@@ -748,10 +627,139 @@ func (v *visitor) BuiltinNode(node *ast.BuiltinNode) (reflect.Type, info) {
|
||||
return integerType, info{}
|
||||
}
|
||||
return v.error(node.Arguments[1], "closure should has one input and one output param")
|
||||
|
||||
default:
|
||||
return v.error(node, "unknown builtin %v", node.Name)
|
||||
}
|
||||
|
||||
if id, ok := builtin.Index[node.Name]; ok {
|
||||
return v.checkFunction(builtin.Builtins[id], node.Arguments, node)
|
||||
}
|
||||
|
||||
return v.error(node, "unknown builtin %v", node.Name)
|
||||
}
|
||||
|
||||
func (v *visitor) checkFunction(f *builtin.Function, arguments []ast.Node, node ast.Node) (reflect.Type, info) {
|
||||
if f.Validate != nil {
|
||||
args := make([]reflect.Type, len(arguments))
|
||||
for i, arg := range arguments {
|
||||
args[i], _ = v.visit(arg)
|
||||
}
|
||||
t, err := f.Validate(args)
|
||||
if err != nil {
|
||||
return v.error(node, "%v", err)
|
||||
}
|
||||
return t, info{}
|
||||
}
|
||||
if len(f.Types) == 0 {
|
||||
t, err := v.checkArguments(f.Name, functionType, false, arguments, node)
|
||||
if err != nil {
|
||||
if v.err == nil {
|
||||
v.err = err
|
||||
}
|
||||
return anyType, info{}
|
||||
}
|
||||
// No type was specified, so we assume the function returns any.
|
||||
return t, info{}
|
||||
}
|
||||
var lastErr *file.Error
|
||||
for _, t := range f.Types {
|
||||
outType, err := v.checkArguments(f.Name, t, false, arguments, node)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
return outType, info{}
|
||||
}
|
||||
if lastErr != nil {
|
||||
if v.err == nil {
|
||||
v.err = lastErr
|
||||
}
|
||||
return anyType, info{}
|
||||
}
|
||||
|
||||
return v.error(node, "no matching overload for %v", f.Name)
|
||||
}
|
||||
|
||||
func (v *visitor) checkArguments(name string, fn reflect.Type, method bool, arguments []ast.Node, node ast.Node) (reflect.Type, *file.Error) {
|
||||
if isAny(fn) {
|
||||
return anyType, nil
|
||||
}
|
||||
|
||||
if fn.NumOut() == 0 {
|
||||
return anyType, &file.Error{
|
||||
Location: node.Location(),
|
||||
Message: fmt.Sprintf("func %v doesn't return value", name),
|
||||
}
|
||||
}
|
||||
if numOut := fn.NumOut(); numOut > 2 {
|
||||
return anyType, &file.Error{
|
||||
Location: node.Location(),
|
||||
Message: fmt.Sprintf("func %v returns more then two values", name),
|
||||
}
|
||||
}
|
||||
|
||||
// If func is method on an env, first argument should be a receiver,
|
||||
// and actual arguments less than fnNumIn by one.
|
||||
fnNumIn := fn.NumIn()
|
||||
if method {
|
||||
fnNumIn--
|
||||
}
|
||||
// Skip first argument in case of the receiver.
|
||||
fnInOffset := 0
|
||||
if method {
|
||||
fnInOffset = 1
|
||||
}
|
||||
|
||||
if fn.IsVariadic() {
|
||||
if len(arguments) < fnNumIn-1 {
|
||||
return anyType, &file.Error{
|
||||
Location: node.Location(),
|
||||
Message: fmt.Sprintf("not enough arguments to call %v", name),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if len(arguments) > fnNumIn {
|
||||
return anyType, &file.Error{
|
||||
Location: node.Location(),
|
||||
Message: fmt.Sprintf("too many arguments to call %v", name),
|
||||
}
|
||||
}
|
||||
if len(arguments) < fnNumIn {
|
||||
return anyType, &file.Error{
|
||||
Location: node.Location(),
|
||||
Message: fmt.Sprintf("not enough arguments to call %v", name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, arg := range arguments {
|
||||
t, _ := v.visit(arg)
|
||||
|
||||
var in reflect.Type
|
||||
if fn.IsVariadic() && i >= fnNumIn-1 {
|
||||
// For variadic arguments fn(xs ...int), go replaces type of xs (int) with ([]int).
|
||||
// As we compare arguments one by one, we need underling type.
|
||||
in = fn.In(fn.NumIn() - 1).Elem()
|
||||
} else {
|
||||
in = fn.In(i + fnInOffset)
|
||||
}
|
||||
|
||||
if isIntegerOrArithmeticOperation(arg) && (isInteger(in) || isFloat(in)) {
|
||||
t = in
|
||||
setTypeForIntegers(arg, t)
|
||||
}
|
||||
|
||||
if t == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if !t.AssignableTo(in) && t.Kind() != reflect.Interface {
|
||||
return anyType, &file.Error{
|
||||
Location: arg.Location(),
|
||||
Message: fmt.Sprintf("cannot use %v as argument (type %v) to call %v ", t, in, name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fn.Out(0), nil
|
||||
}
|
||||
|
||||
func (v *visitor) ClosureNode(node *ast.ClosureNode) (reflect.Type, info) {
|
||||
|
||||
+18
-3
@@ -5,6 +5,7 @@ import (
|
||||
"reflect"
|
||||
|
||||
"github.com/antonmedv/expr/ast"
|
||||
"github.com/antonmedv/expr/builtin"
|
||||
"github.com/antonmedv/expr/conf"
|
||||
"github.com/antonmedv/expr/file"
|
||||
"github.com/antonmedv/expr/parser"
|
||||
@@ -603,6 +604,7 @@ func (c *compiler) BuiltinNode(node *ast.BuiltinNode) {
|
||||
c.emit(OpTrue)
|
||||
c.patchJump(loopBreak)
|
||||
c.emit(OpEnd)
|
||||
return
|
||||
|
||||
case "none":
|
||||
c.compile(node.Arguments[0])
|
||||
@@ -617,6 +619,7 @@ func (c *compiler) BuiltinNode(node *ast.BuiltinNode) {
|
||||
c.emit(OpTrue)
|
||||
c.patchJump(loopBreak)
|
||||
c.emit(OpEnd)
|
||||
return
|
||||
|
||||
case "any":
|
||||
c.compile(node.Arguments[0])
|
||||
@@ -630,6 +633,7 @@ func (c *compiler) BuiltinNode(node *ast.BuiltinNode) {
|
||||
c.emit(OpFalse)
|
||||
c.patchJump(loopBreak)
|
||||
c.emit(OpEnd)
|
||||
return
|
||||
|
||||
case "one":
|
||||
c.compile(node.Arguments[0])
|
||||
@@ -644,6 +648,7 @@ func (c *compiler) BuiltinNode(node *ast.BuiltinNode) {
|
||||
c.emitPush(1)
|
||||
c.emit(OpEqual)
|
||||
c.emit(OpEnd)
|
||||
return
|
||||
|
||||
case "filter":
|
||||
c.compile(node.Arguments[0])
|
||||
@@ -658,6 +663,7 @@ func (c *compiler) BuiltinNode(node *ast.BuiltinNode) {
|
||||
c.emit(OpGetCount)
|
||||
c.emit(OpEnd)
|
||||
c.emit(OpArray)
|
||||
return
|
||||
|
||||
case "map":
|
||||
c.compile(node.Arguments[0])
|
||||
@@ -668,6 +674,7 @@ func (c *compiler) BuiltinNode(node *ast.BuiltinNode) {
|
||||
c.emit(OpGetLen)
|
||||
c.emit(OpEnd)
|
||||
c.emit(OpArray)
|
||||
return
|
||||
|
||||
case "count":
|
||||
c.compile(node.Arguments[0])
|
||||
@@ -680,10 +687,18 @@ func (c *compiler) BuiltinNode(node *ast.BuiltinNode) {
|
||||
})
|
||||
c.emit(OpGetCount)
|
||||
c.emit(OpEnd)
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown builtin %v", node.Name))
|
||||
return
|
||||
}
|
||||
|
||||
if id, ok := builtin.Index[node.Name]; ok {
|
||||
for _, arg := range node.Arguments {
|
||||
c.compile(arg)
|
||||
}
|
||||
c.emit(OpCallBuiltin, id)
|
||||
return
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("unknown builtin %v", node.Name))
|
||||
}
|
||||
|
||||
func (c *compiler) emitCond(body func()) {
|
||||
|
||||
@@ -1308,34 +1308,6 @@ func TestPatch(t *testing.T) {
|
||||
require.Equal(t, true, output)
|
||||
}
|
||||
|
||||
type lengthPatcher struct{}
|
||||
|
||||
func (p *lengthPatcher) Visit(node *ast.Node) {
|
||||
switch n := (*node).(type) {
|
||||
case *ast.MemberNode:
|
||||
if prop, ok := n.Property.(*ast.StringNode); ok && prop.Value == "length" {
|
||||
ast.Patch(node, &ast.CallNode{
|
||||
Callee: &ast.IdentifierNode{Value: "len"},
|
||||
Arguments: []ast.Node{n.Node},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPatch_length(t *testing.T) {
|
||||
program, err := expr.Compile(
|
||||
`String.length == 5`,
|
||||
expr.Env(mockEnv{}),
|
||||
expr.Patch(&lengthPatcher{}),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
env := mockEnv{String: "hello"}
|
||||
output, err := expr.Run(program, env)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, output)
|
||||
}
|
||||
|
||||
func TestCompile_exposed_error(t *testing.T) {
|
||||
_, err := expr.Compile(`1 == true`)
|
||||
require.Error(t, err)
|
||||
@@ -1864,48 +1836,6 @@ func TestEnv_keyword(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
type Bar interface {
|
||||
Bar() int
|
||||
}
|
||||
type Foo interface {
|
||||
Foo() Bar
|
||||
}
|
||||
|
||||
type FooImpl struct{}
|
||||
|
||||
func (f FooImpl) Foo() Bar {
|
||||
return BarImpl{}
|
||||
}
|
||||
|
||||
type BarImpl struct{}
|
||||
|
||||
// Aba is a special method that is not part of the Bar interface,
|
||||
// but is used to test that the correct method is called. "Aba" name
|
||||
// is chosen to be before "Bar" in the alphabet.
|
||||
func (b BarImpl) Aba() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (b BarImpl) Bar() int {
|
||||
return 42
|
||||
}
|
||||
|
||||
func TestEval_interface_method(t *testing.T) {
|
||||
require.True(t, BarImpl{}.Aba())
|
||||
require.True(t, BarImpl{}.Bar() == 42)
|
||||
|
||||
env := map[string]interface{}{
|
||||
"var": FooImpl{},
|
||||
}
|
||||
p, err := expr.Compile(`var.Foo().Bar()`, expr.Env(env))
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
out, err := expr.Run(p, env)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 42, out)
|
||||
}
|
||||
|
||||
// Mock types
|
||||
|
||||
type mockEnv struct {
|
||||
|
||||
+19
-6
@@ -7,6 +7,7 @@ import (
|
||||
"unicode/utf8"
|
||||
|
||||
. "github.com/antonmedv/expr/ast"
|
||||
"github.com/antonmedv/expr/builtin"
|
||||
"github.com/antonmedv/expr/file"
|
||||
. "github.com/antonmedv/expr/parser/lexer"
|
||||
)
|
||||
@@ -57,11 +58,9 @@ var binaryOperators = map[string]operator{
|
||||
"??": {500, left},
|
||||
}
|
||||
|
||||
type builtin struct {
|
||||
var predicates = map[string]struct {
|
||||
arity int
|
||||
}
|
||||
|
||||
var builtins = map[string]builtin{
|
||||
}{
|
||||
"all": {2},
|
||||
"none": {2},
|
||||
"any": {2},
|
||||
@@ -355,7 +354,7 @@ func (p *parser) parseCall(token Token) Node {
|
||||
if p.current.Is(Bracket, "(") {
|
||||
var arguments []Node
|
||||
|
||||
if b, ok := builtins[token.Value]; ok {
|
||||
if b, ok := predicates[token.Value]; ok {
|
||||
p.expect(Bracket, "(")
|
||||
if b.arity == 1 {
|
||||
arguments = make([]Node, 1)
|
||||
@@ -373,6 +372,12 @@ func (p *parser) parseCall(token Token) Node {
|
||||
Arguments: arguments,
|
||||
}
|
||||
node.SetLocation(token.Location)
|
||||
} else if _, ok := builtin.Index[token.Value]; ok {
|
||||
node = &BuiltinNode{
|
||||
Name: token.Value,
|
||||
Arguments: p.parseArguments(),
|
||||
}
|
||||
node.SetLocation(token.Location)
|
||||
} else {
|
||||
callee := &IdentifierNode{Value: token.Value}
|
||||
callee.SetLocation(token.Location)
|
||||
@@ -588,13 +593,21 @@ func (p *parser) parsePipe(node Node) Node {
|
||||
|
||||
arguments := []Node{node}
|
||||
|
||||
if b, ok := builtins[identifier.Value]; ok {
|
||||
if b, ok := predicates[identifier.Value]; ok {
|
||||
p.expect(Bracket, "(")
|
||||
if b.arity == 2 {
|
||||
arguments = append(arguments, p.parseClosure())
|
||||
}
|
||||
p.expect(Bracket, ")")
|
||||
|
||||
node = &BuiltinNode{
|
||||
Name: identifier.Value,
|
||||
Arguments: arguments,
|
||||
}
|
||||
node.SetLocation(identifier.Location)
|
||||
} else if _, ok := builtin.Index[identifier.Value]; ok {
|
||||
arguments = append(arguments, p.parseArguments()...)
|
||||
|
||||
node = &BuiltinNode{
|
||||
Name: identifier.Value,
|
||||
Arguments: arguments,
|
||||
|
||||
@@ -265,8 +265,8 @@ func TestParse(t *testing.T) {
|
||||
},
|
||||
{
|
||||
"len(foo)",
|
||||
&CallNode{
|
||||
Callee: &IdentifierNode{Value: "len"},
|
||||
&BuiltinNode{
|
||||
Name: "len",
|
||||
Arguments: []Node{
|
||||
&IdentifierNode{Value: "foo"},
|
||||
},
|
||||
@@ -644,8 +644,8 @@ func TestParse_pipe_operator(t *testing.T) {
|
||||
expect := &CallNode{
|
||||
Callee: &IdentifierNode{Value: "Foo"},
|
||||
Arguments: []Node{
|
||||
&CallNode{
|
||||
Callee: &IdentifierNode{Value: "len"},
|
||||
&BuiltinNode{
|
||||
Name: "len",
|
||||
Arguments: []Node{
|
||||
&BuiltinNode{
|
||||
Name: "map",
|
||||
|
||||
+42
-12
@@ -2,37 +2,67 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/antonmedv/expr"
|
||||
"github.com/antonmedv/expr/vm"
|
||||
"github.com/chzyer/readline"
|
||||
)
|
||||
|
||||
func main() {
|
||||
rl, err := readline.New("> ")
|
||||
rl, err := readline.NewEx(&readline.Config{
|
||||
Prompt: "> ",
|
||||
AutoComplete: readline.NewPrefixCompleter(
|
||||
readline.PcItem("exit"),
|
||||
readline.PcItem("opcodes"),
|
||||
readline.PcItem("len"),
|
||||
readline.PcItem("abs"),
|
||||
readline.PcItem("int"),
|
||||
readline.PcItem("float"),
|
||||
readline.PcItem("map"),
|
||||
readline.PcItem("filter"),
|
||||
readline.PcItem("all"),
|
||||
readline.PcItem("any"),
|
||||
readline.PcItem("none"),
|
||||
readline.PcItem("one"),
|
||||
readline.PcItem("count"),
|
||||
readline.PcItem("sum"),
|
||||
),
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer rl.Close()
|
||||
|
||||
var program *vm.Program
|
||||
for {
|
||||
line, err := rl.Readline()
|
||||
if err != nil { // io.EOF when Ctrl-D is pressed
|
||||
break
|
||||
}
|
||||
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "exit" {
|
||||
break
|
||||
}
|
||||
if line == "opcodes" {
|
||||
if program == nil {
|
||||
fmt.Println("no program")
|
||||
continue
|
||||
}
|
||||
fmt.Println(program.Disassemble())
|
||||
continue
|
||||
}
|
||||
|
||||
result := evaluate(line)
|
||||
fmt.Println(result)
|
||||
program, err = expr.Compile(line, expr.Env(nil))
|
||||
if err != nil {
|
||||
fmt.Printf("compile error: %s\n", err)
|
||||
continue
|
||||
}
|
||||
output, err := expr.Run(program, nil)
|
||||
if err != nil {
|
||||
fmt.Printf("runtime error: %s\n", err)
|
||||
continue
|
||||
}
|
||||
fmt.Println(output)
|
||||
}
|
||||
}
|
||||
|
||||
func evaluate(input string) string {
|
||||
result, err := expr.Eval(input, nil)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return fmt.Sprintf("%v", result)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package interface_method_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/antonmedv/expr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type Bar interface {
|
||||
Bar() int
|
||||
}
|
||||
|
||||
type FooImpl struct{}
|
||||
|
||||
func (f FooImpl) Foo() Bar {
|
||||
return BarImpl{}
|
||||
}
|
||||
|
||||
type BarImpl struct{}
|
||||
|
||||
// Aba is a special method that is not part of the Bar interface,
|
||||
// but is used to test that the correct method is called. "Aba" name
|
||||
// is chosen to be before "Bar" in the alphabet.
|
||||
func (b BarImpl) Aba() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (b BarImpl) Bar() int {
|
||||
return 42
|
||||
}
|
||||
|
||||
func TestInterfaceMethod(t *testing.T) {
|
||||
require.True(t, BarImpl{}.Aba())
|
||||
require.True(t, BarImpl{}.Bar() == 42)
|
||||
|
||||
env := map[string]interface{}{
|
||||
"var": FooImpl{},
|
||||
}
|
||||
p, err := expr.Compile(`var.Foo().Bar()`, expr.Env(env))
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
out, err := expr.Run(p, env)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 42, out)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package patch_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/antonmedv/expr"
|
||||
"github.com/antonmedv/expr/ast"
|
||||
"github.com/antonmedv/expr/test/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type lengthPatcher struct{}
|
||||
|
||||
func (p *lengthPatcher) Visit(node *ast.Node) {
|
||||
switch n := (*node).(type) {
|
||||
case *ast.MemberNode:
|
||||
if prop, ok := n.Property.(*ast.StringNode); ok && prop.Value == "length" {
|
||||
ast.Patch(node, &ast.BuiltinNode{
|
||||
Name: "len",
|
||||
Arguments: []ast.Node{n.Node},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPatch_length(t *testing.T) {
|
||||
program, err := expr.Compile(
|
||||
`String.length == 5`,
|
||||
expr.Env(mock.Env{}),
|
||||
expr.Patch(&lengthPatcher{}),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
env := mock.Env{String: "hello"}
|
||||
output, err := expr.Run(program, env)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, output)
|
||||
}
|
||||
@@ -57,6 +57,7 @@ const (
|
||||
OpCallN
|
||||
OpCallFast
|
||||
OpCallTyped
|
||||
OpCallBuiltin
|
||||
OpArray
|
||||
OpMap
|
||||
OpLen
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/antonmedv/expr/ast"
|
||||
"github.com/antonmedv/expr/builtin"
|
||||
"github.com/antonmedv/expr/file"
|
||||
"github.com/antonmedv/expr/vm/runtime"
|
||||
)
|
||||
@@ -63,6 +64,9 @@ func (program *Program) Disassemble() string {
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t%v\n", pp, label, arg, c)
|
||||
}
|
||||
builtin := func(label string) {
|
||||
_, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t%v\n", pp, label, arg, builtin.Builtins[arg].Name)
|
||||
}
|
||||
|
||||
switch op {
|
||||
case OpInvalid:
|
||||
@@ -228,6 +232,9 @@ func (program *Program) Disassemble() string {
|
||||
signature := reflect.TypeOf(FuncTypes[arg]).Elem().String()
|
||||
_, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t%v\n", pp, "OpCallTyped", arg, signature)
|
||||
|
||||
case OpCallBuiltin:
|
||||
builtin("OpCallBuiltin")
|
||||
|
||||
case OpArray:
|
||||
code("OpArray")
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/antonmedv/expr/builtin"
|
||||
"github.com/antonmedv/expr/file"
|
||||
"github.com/antonmedv/expr/vm/runtime"
|
||||
)
|
||||
@@ -380,6 +381,9 @@ func (vm *VM) Run(program *Program, env interface{}) (_ interface{}, err error)
|
||||
out := vm.call(fn, arg)
|
||||
vm.push(out)
|
||||
|
||||
case OpCallBuiltin:
|
||||
vm.push(builtin.Builtins[arg].Builtin1(vm.pop()))
|
||||
|
||||
case OpArray:
|
||||
size := vm.pop().(int)
|
||||
array := make([]interface{}, size)
|
||||
|
||||
Reference in New Issue
Block a user