mirror of
https://github.com/danog/liquid.git
synced 2024-11-30 07:08:58 +01:00
89 lines
2.4 KiB
Go
89 lines
2.4 KiB
Go
package evaluator
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
)
|
|
|
|
// Call applies a function to arguments, converting them as necessary.
|
|
//
|
|
// The conversion follows Liquid (Ruby?) semantics, which are more aggressive than
|
|
// Go conversion.
|
|
//
|
|
// The function should return one or two values; the second value,
|
|
// if present, should be an error.
|
|
func Call(fn reflect.Value, args []interface{}) (interface{}, error) {
|
|
in, err := convertCallArguments(fn, args)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
results := fn.Call(in)
|
|
return convertCallResults(results)
|
|
}
|
|
|
|
// A CallParityError is a mismatch between the argument and parameter counts.
|
|
type CallParityError struct{ NumArgs, NumParams int }
|
|
|
|
func (e *CallParityError) Error() string {
|
|
return fmt.Sprintf("wrong number of arguments (given %d, expected %d)", e.NumArgs, e.NumParams)
|
|
}
|
|
|
|
func convertCallResults(results []reflect.Value) (interface{}, error) {
|
|
if len(results) > 1 && results[1].Interface() != nil {
|
|
switch e := results[1].Interface().(type) {
|
|
case error:
|
|
return nil, e
|
|
default:
|
|
panic(e)
|
|
}
|
|
}
|
|
return results[0].Interface(), nil
|
|
}
|
|
|
|
// Convert args to match the input types of function fn.
|
|
func convertCallArguments(fn reflect.Value, args []interface{}) (results []reflect.Value, err error) {
|
|
rt := fn.Type()
|
|
if len(args) > rt.NumIn() {
|
|
return nil, &CallParityError{NumArgs: len(args), NumParams: rt.NumIn()}
|
|
}
|
|
results = make([]reflect.Value, rt.NumIn())
|
|
for i, arg := range args {
|
|
typ := rt.In(i)
|
|
switch {
|
|
case isDefaultFunctionType(typ):
|
|
results[i] = makeConstantFunction(typ, arg)
|
|
case arg == nil:
|
|
results[i] = reflect.Zero(typ)
|
|
default:
|
|
results[i] = reflect.ValueOf(MustConvert(arg, typ))
|
|
}
|
|
}
|
|
// create zeros and default functions for parameters without arguments
|
|
for i := len(args); i < rt.NumIn(); i++ {
|
|
typ := rt.In(i)
|
|
switch {
|
|
case isDefaultFunctionType(typ):
|
|
results[i] = makeIdentityFunction(typ)
|
|
default:
|
|
results[i] = reflect.Zero(typ)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func isDefaultFunctionType(typ reflect.Type) bool {
|
|
return typ.Kind() == reflect.Func && typ.NumIn() == 1 && typ.NumOut() == 1
|
|
}
|
|
|
|
func makeConstantFunction(typ reflect.Type, arg interface{}) reflect.Value {
|
|
return reflect.MakeFunc(typ, func(args []reflect.Value) []reflect.Value {
|
|
return []reflect.Value{reflect.ValueOf(MustConvert(arg, typ.Out(0)))}
|
|
})
|
|
}
|
|
|
|
func makeIdentityFunction(typ reflect.Type) reflect.Value {
|
|
return reflect.MakeFunc(typ, func(args []reflect.Value) []reflect.Value {
|
|
return args
|
|
})
|
|
}
|