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  }