package builtin import ( "fmt" "math" "reflect" "strconv" "unicode/utf8" "github.com/expr-lang/expr/internal/deref" "github.com/expr-lang/expr/vm/runtime" ) func Len(x any) any { v := reflect.ValueOf(x) switch v.Kind() { case reflect.Array, reflect.Slice, reflect.Map: return v.Len() case reflect.String: return utf8.RuneCountInString(v.String()) default: panic(fmt.Sprintf("invalid argument for len (type %T)", x)) } } func Type(arg any) any { if arg == nil { return "nil" } v := reflect.ValueOf(arg) if v.Type().Name() != "" && v.Type().PkgPath() != "" { return fmt.Sprintf("%s.%s", v.Type().PkgPath(), v.Type().Name()) } switch v.Type().Kind() { case reflect.Invalid: return "invalid" case reflect.Bool: return "bool" case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return "int" case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return "uint" case reflect.Float32, reflect.Float64: return "float" case reflect.String: return "string" case reflect.Array, reflect.Slice: return "array" case reflect.Map: return "map" case reflect.Func: return "func" case reflect.Struct: return "struct" default: return "unknown" } } func Abs(x any) any { switch x := x.(type) { case float32: if x < 0 { return -x } else { return x } case float64: if x < 0 { return -x } else { return x } case int: if x < 0 { return -x } else { return x } case int8: if x < 0 { return -x } else { return x } case int16: if x < 0 { return -x } else { return x } case int32: if x < 0 { return -x } else { return x } case int64: if x < 0 { return -x } else { return x } case uint: if x < 0 { return -x } else { return x } case uint8: if x < 0 { return -x } else { return x } case uint16: if x < 0 { return -x } else { return x } case uint32: if x < 0 { return -x } else { return x } case uint64: if x < 0 { return -x } else { return x } } panic(fmt.Sprintf("invalid argument for abs (type %T)", x)) } func Ceil(x any) any { switch x := x.(type) { case float32: return math.Ceil(float64(x)) case float64: return math.Ceil(x) case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: return Float(x) } panic(fmt.Sprintf("invalid argument for ceil (type %T)", x)) } func Floor(x any) any { switch x := x.(type) { case float32: return math.Floor(float64(x)) case float64: return math.Floor(x) case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: return Float(x) } panic(fmt.Sprintf("invalid argument for floor (type %T)", x)) } func Round(x any) any { switch x := x.(type) { case float32: return math.Round(float64(x)) case float64: return math.Round(x) case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: return Float(x) } panic(fmt.Sprintf("invalid argument for round (type %T)", x)) } func Int(x any) any { switch x := x.(type) { case float32: return int(x) case float64: return int(x) case int: return x case int8: return int(x) case int16: return int(x) case int32: return int(x) case int64: return int(x) case uint: return int(x) case uint8: return int(x) case uint16: return int(x) case uint32: return int(x) case uint64: return int(x) case string: i, err := strconv.Atoi(x) if err != nil { panic(fmt.Sprintf("invalid operation: int(%s)", x)) } return i default: val := reflect.ValueOf(x) if val.CanConvert(integerType) { return val.Convert(integerType).Interface() } panic(fmt.Sprintf("invalid operation: int(%T)", x)) } } func Float(x any) any { switch x := x.(type) { case float32: return float64(x) case float64: return x case int: return float64(x) case int8: return float64(x) case int16: return float64(x) case int32: return float64(x) case int64: return float64(x) case uint: return float64(x) case uint8: return float64(x) case uint16: return float64(x) case uint32: return float64(x) case uint64: return float64(x) case string: f, err := strconv.ParseFloat(x, 64) if err != nil { panic(fmt.Sprintf("invalid operation: float(%s)", x)) } return f default: panic(fmt.Sprintf("invalid operation: float(%T)", x)) } } func String(arg any) any { return fmt.Sprintf("%v", arg) } func minMax(name string, fn func(any, any) bool, args ...any) (any, error) { var val any for _, arg := range args { rv := reflect.ValueOf(arg) switch rv.Kind() { case reflect.Array, reflect.Slice: size := rv.Len() for i := 0; i < size; i++ { elemVal, err := minMax(name, fn, rv.Index(i).Interface()) if err != nil { return nil, err } switch elemVal.(type) { case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64: if elemVal != nil && (val == nil || fn(val, elemVal)) { val = elemVal } default: return nil, fmt.Errorf("invalid argument for %s (type %T)", name, elemVal) } } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: elemVal := rv.Interface() if val == nil || fn(val, elemVal) { val = elemVal } default: if len(args) == 1 { return args[0], nil } return nil, fmt.Errorf("invalid argument for %s (type %T)", name, arg) } } return val, nil } func mean(args ...any) (int, float64, error) { var total float64 var count int for _, arg := range args { rv := reflect.ValueOf(arg) switch rv.Kind() { case reflect.Array, reflect.Slice: size := rv.Len() for i := 0; i < size; i++ { elemCount, elemSum, err := mean(rv.Index(i).Interface()) if err != nil { return 0, 0, err } total += elemSum count += elemCount } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: total += float64(rv.Int()) count++ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: total += float64(rv.Uint()) count++ case reflect.Float32, reflect.Float64: total += rv.Float() count++ default: return 0, 0, fmt.Errorf("invalid argument for mean (type %T)", arg) } } return count, total, nil } func median(args ...any) ([]float64, error) { var values []float64 for _, arg := range args { rv := reflect.ValueOf(arg) switch rv.Kind() { case reflect.Array, reflect.Slice: size := rv.Len() for i := 0; i < size; i++ { elems, err := median(rv.Index(i).Interface()) if err != nil { return nil, err } values = append(values, elems...) } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: values = append(values, float64(rv.Int())) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: values = append(values, float64(rv.Uint())) case reflect.Float32, reflect.Float64: values = append(values, rv.Float()) default: return nil, fmt.Errorf("invalid argument for median (type %T)", arg) } } return values, nil } func flatten(arg reflect.Value) []any { ret := []any{} for i := 0; i < arg.Len(); i++ { v := deref.Value(arg.Index(i)) if v.Kind() == reflect.Array || v.Kind() == reflect.Slice { x := flatten(v) ret = append(ret, x...) } else { ret = append(ret, v.Interface()) } } return ret } func get(params ...any) (out any, err error) { from := params[0] i := params[1] v := reflect.ValueOf(from) if v.Kind() == reflect.Invalid { panic(fmt.Sprintf("cannot fetch %v from %T", i, from)) } // Methods can be defined on any type. if v.NumMethod() > 0 { if methodName, ok := i.(string); ok { method := v.MethodByName(methodName) if method.IsValid() { return method.Interface(), nil } } } switch v.Kind() { case reflect.Array, reflect.Slice, reflect.String: index := runtime.ToInt(i) l := v.Len() if index < 0 { index = l + index } if 0 <= index && index < l { value := v.Index(index) if value.IsValid() { return value.Interface(), nil } } case reflect.Map: var value reflect.Value if i == nil { value = v.MapIndex(reflect.Zero(v.Type().Key())) } else { value = v.MapIndex(reflect.ValueOf(i)) } if value.IsValid() { return value.Interface(), nil } case reflect.Struct: fieldName := i.(string) value := v.FieldByNameFunc(func(name string) bool { field, _ := v.Type().FieldByName(name) if field.Tag.Get("expr") == fieldName { return true } return name == fieldName }) if value.IsValid() { return value.Interface(), nil } } // Main difference from runtime.Fetch // is that we return `nil` instead of panic. return nil, nil }