github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/easy/yamlx/function.go (about) 1 package yamlx 2 3 import ( 4 "crypto/rand" 5 "encoding/hex" 6 "errors" 7 "fmt" 8 goast "go/ast" 9 goparser "go/parser" 10 gotoken "go/token" 11 "io" 12 "reflect" 13 "strconv" 14 "time" 15 16 "github.com/jxskiss/gopkg/v2/internal/unsafeheader" 17 "github.com/jxskiss/gopkg/v2/perf/fastrand" 18 "github.com/jxskiss/gopkg/v2/utils/strutil" 19 ) 20 21 func (p *parser) addFuncs(funcMap map[string]any) { 22 p.funcValMap = make(map[string]reflect.Value, len(builtinFuncs)+len(funcMap)) 23 for name, fn := range builtinFuncs { 24 p.funcValMap[name] = reflect.ValueOf(fn) 25 } 26 for name, fn := range funcMap { 27 p.funcValMap[name] = reflect.ValueOf(fn) 28 } 29 } 30 31 func (p *parser) callFunction(str string) (ret any, err error) { 32 var funcName string 33 var fn reflect.Value 34 var callArgs []reflect.Value 35 36 if fn = p.funcValMap[str]; fn.IsValid() { 37 funcName = str 38 } else { 39 var expr *expression 40 expr, err = parseExpression(str) 41 if err != nil { 42 return nil, fmt.Errorf("cannot parse function expression: %w", err) 43 } 44 fn = p.funcValMap[expr.Func] 45 if !fn.IsValid() { 46 return nil, fmt.Errorf("function %s is unknown", expr.Func) 47 } 48 fnTyp := fn.Type() 49 if len(expr.Args) != fnTyp.NumIn() { 50 return nil, fmt.Errorf("function %s arguments count not match", expr.Func) 51 } 52 funcName = expr.Func 53 for i := 0; i < len(expr.Args); i++ { 54 fnArgTyp := fnTyp.In(i) 55 if !expr.Args[i].Type().ConvertibleTo(fnArgTyp) { 56 return nil, fmt.Errorf("function %s argument type not match: %v", expr.Func, expr.Args[i].Interface()) 57 } 58 callArgs = append(callArgs, expr.Args[i].Convert(fnArgTyp)) 59 } 60 } 61 62 out := fn.Call(callArgs) 63 if len(out) > 1 && !out[1].IsNil() { 64 return nil, fmt.Errorf("error calling function %s: %w", funcName, out[1].Interface().(error)) 65 } 66 67 result := out[0] 68 return result.Interface(), nil 69 } 70 71 var ( 72 errNotCallExpression = errors.New("not a call expression") 73 errArgumentTypeNotSupported = errors.New("argument type not supported") 74 ) 75 76 type expression struct { 77 Func string 78 Args []reflect.Value 79 } 80 81 func parseExpression(str string) (*expression, error) { 82 expr, err := goparser.ParseExpr(str) 83 if err != nil { 84 return nil, err 85 } 86 call, ok := expr.(*goast.CallExpr) 87 if !ok { 88 return nil, errNotCallExpression 89 } 90 fnName := call.Fun.(*goast.Ident).String() 91 args := make([]reflect.Value, 0, len(call.Args)) 92 for _, a := range call.Args { 93 lit, ok := a.(*goast.BasicLit) 94 if !ok { 95 return nil, errArgumentTypeNotSupported 96 } 97 var aVal any 98 switch lit.Kind { 99 case gotoken.INT: 100 aVal, _ = strconv.ParseInt(lit.Value, 10, 64) 101 case gotoken.FLOAT: 102 aVal, _ = strconv.ParseFloat(lit.Value, 64) 103 case gotoken.STRING: 104 aVal = lit.Value[1 : len(lit.Value)-1] 105 default: 106 return nil, errArgumentTypeNotSupported 107 } 108 args = append(args, reflect.ValueOf(aVal)) 109 } 110 return &expression{ 111 Func: fnName, 112 Args: args, 113 }, nil 114 } 115 116 // -------- builtins -------- // 117 118 var builtinFuncs = map[string]any{ 119 "nowUnix": builtinNowUnix, 120 "nowMilli": builtinNowMilli, 121 "nowNano": builtinNowNano, 122 "nowRFC3339": builtinNowRFC3339, 123 "nowFormat": builtinNowFormat, 124 "uuid": builtinUUID, 125 "rand": builtinRand, 126 "randN": builtinRandN, 127 "randStr": builtinRandStr, 128 } 129 130 func builtinNowUnix() int64 { 131 return time.Now().Unix() 132 } 133 134 func builtinNowMilli() int64 { 135 return time.Now().UnixNano() / 1e6 136 } 137 138 func builtinNowNano() int64 { 139 return time.Now().UnixNano() 140 } 141 142 func builtinNowRFC3339() string { 143 return time.Now().Format(time.RFC3339) 144 } 145 146 func builtinNowFormat(layout string) string { 147 return time.Now().Format(layout) 148 } 149 150 func builtinUUID() string { 151 uuid := make([]byte, 16) 152 _, err := io.ReadFull(rand.Reader, uuid) 153 if err != nil { 154 panic(err) 155 } 156 uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4 157 uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 158 159 buf := make([]byte, 36) 160 hex.Encode(buf[:8], uuid[:4]) 161 buf[8] = '-' 162 hex.Encode(buf[9:13], uuid[4:6]) 163 buf[13] = '-' 164 hex.Encode(buf[14:18], uuid[6:8]) 165 buf[18] = '-' 166 hex.Encode(buf[19:23], uuid[8:10]) 167 buf[23] = '-' 168 hex.Encode(buf[24:], uuid[10:]) 169 return unsafeheader.BytesToString(buf) 170 } 171 172 func builtinRand() (x int64) { 173 return fastrand.Int64() 174 } 175 176 func builtinRandN(n int64) (x int64) { 177 return fastrand.N(n) 178 } 179 180 func builtinRandStr(n int) string { 181 return strutil.Random(strutil.AlphaDigits, n) 182 }