package macro import ( "fmt" "reflect" "regexp" "strconv" "strings" "unicode" ) // EvaluatorFunc is the signature for both param types and param funcs. // It should accepts the param's value as string // and return true if validated otherwise false. type EvaluatorFunc func(paramValue string) bool // NewEvaluatorFromRegexp accepts a regexp "expr" expression // and returns an EvaluatorFunc based on that regexp. // the regexp is compiled before return. // // Returns a not-nil error on regexp compile failure. func NewEvaluatorFromRegexp(expr string) (EvaluatorFunc, error) { if expr == "" { return nil, fmt.Errorf("empty regex expression") } // add the last $ if missing (and not wildcard(?)) if i := expr[len(expr)-1]; i != '$' && i != '*' { expr += "$" } r, err := regexp.Compile(expr) if err != nil { return nil, err } return r.MatchString, nil } // MustNewEvaluatorFromRegexp same as NewEvaluatorFromRegexp // but it panics on the "expr" parse failure. func MustNewEvaluatorFromRegexp(expr string) EvaluatorFunc { r, err := NewEvaluatorFromRegexp(expr) if err != nil { panic(err) } return r } var ( goodParamFuncReturnType = reflect.TypeOf(func(string) bool { return false }) goodParamFuncReturnType2 = reflect.TypeOf(EvaluatorFunc(func(string) bool { return false })) ) func goodParamFunc(typ reflect.Type) bool { // should be a func // which returns a func(string) bool if typ.Kind() == reflect.Func { if typ.NumOut() == 1 { typOut := typ.Out(0) if typOut == goodParamFuncReturnType || typOut == goodParamFuncReturnType2 { return true } } } return false } // goodParamFuncName reports whether the function name is a valid identifier. func goodParamFuncName(name string) bool { if name == "" { return false } // valid names are only letters and _ for _, r := range name { switch { case r == '_': case !unicode.IsLetter(r): return false } } return true } // the convertBuilderFunc return value is generating at boot time. // convertFunc converts an interface to a valid full param function. func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder { typFn := reflect.TypeOf(fn) if !goodParamFunc(typFn) { return nil } numFields := typFn.NumIn() return func(args []string) EvaluatorFunc { if len(args) != numFields { // no variadics support, for now. panic("args should be the same len as numFields") } var argValues []reflect.Value for i := 0; i < numFields; i++ { field := typFn.In(i) arg := args[i] // try to convert the string literal as we get it from the parser. var ( val interface{} panicIfErr = func(err error) { if err != nil { panic(fmt.Sprintf("on field index: %d: %v", i, err)) } } ) // try to get the value based on the expected type. switch field.Kind() { case reflect.Int: v, err := strconv.Atoi(arg) panicIfErr(err) val = v case reflect.Int8: v, err := strconv.ParseInt(arg, 10, 8) panicIfErr(err) val = int8(v) case reflect.Int16: v, err := strconv.ParseInt(arg, 10, 16) panicIfErr(err) val = int16(v) case reflect.Int32: v, err := strconv.ParseInt(arg, 10, 32) panicIfErr(err) val = int32(v) case reflect.Int64: v, err := strconv.ParseInt(arg, 10, 64) panicIfErr(err) val = v case reflect.Uint8: v, err := strconv.ParseUint(arg, 10, 8) panicIfErr(err) val = uint8(v) case reflect.Uint16: v, err := strconv.ParseUint(arg, 10, 16) panicIfErr(err) val = uint16(v) case reflect.Uint32: v, err := strconv.ParseUint(arg, 10, 32) panicIfErr(err) val = uint32(v) case reflect.Uint64: v, err := strconv.ParseUint(arg, 10, 64) panicIfErr(err) val = v case reflect.Float32: v, err := strconv.ParseFloat(arg, 32) panicIfErr(err) val = float32(v) case reflect.Float64: v, err := strconv.ParseFloat(arg, 64) panicIfErr(err) val = v case reflect.Bool: v, err := strconv.ParseBool(arg) panicIfErr(err) val = v case reflect.Slice: if len(arg) > 1 { if arg[0] == '[' && arg[len(arg)-1] == ']' { // it is a single argument but as slice. val = strings.Split(arg[1:len(arg)-1], ",") // only string slices. } } default: val = arg } argValue := reflect.ValueOf(val) if expected, got := field.Kind(), argValue.Kind(); expected != got { panic(fmt.Sprintf("fields should have the same type: [%d] expected %s but got %s", i, expected, got)) } argValues = append(argValues, argValue) } evalFn := reflect.ValueOf(fn).Call(argValues)[0].Interface() var evaluator EvaluatorFunc // check for typed and not typed if _v, ok := evalFn.(EvaluatorFunc); ok { evaluator = _v } else if _v, ok = evalFn.(func(string) bool); ok { evaluator = _v } return func(paramValue string) bool { return evaluator(paramValue) } } } type ( // Macro represents the parsed macro, // which holds // the evaluator (param type's evaluator + param functions evaluators) // and its param functions. // // Any type contains its own macro // instance, so an String type // contains its type evaluator // which is the "Evaluator" field // and it can register param functions // to that macro which maps to a parameter type. Macro struct { indent string alias string master bool trailing bool Evaluator EvaluatorFunc funcs []ParamFunc } // ParamEvaluatorBuilder is a func // which accepts a param function's arguments (values) // and returns an EvaluatorFunc, its job // is to make the macros to be registered // by user at the most generic possible way. ParamEvaluatorBuilder func([]string) EvaluatorFunc // ParamFunc represents the parsed // parameter function, it holds // the parameter's name // and the function which will build // the evaluator func. ParamFunc struct { Name string Func ParamEvaluatorBuilder } ) // NewMacro creates and returns a Macro that can be used as a registry for // a new customized parameter type and its functions. func NewMacro(indent, alias string, master, trailing bool, evaluator EvaluatorFunc) *Macro { return &Macro{ indent: indent, alias: alias, master: master, trailing: trailing, Evaluator: evaluator, } } func (m *Macro) Indent() string { return m.indent } func (m *Macro) Alias() string { return m.alias } func (m *Macro) Master() bool { return m.master } func (m *Macro) Trailing() bool { return m.trailing } // func (m *Macro) SetParamResolver(fn func(memstore.Entry) interface{}) *Macro { // m.ParamResolver = fn // return m // } // RegisterFunc registers a parameter function // to that macro. // Accepts the func name ("range") // and the function body, which should return an EvaluatorFunc // a bool (it will be converted to EvaluatorFunc later on), // i.e RegisterFunc("min", func(minValue int) func(paramValue string) bool){}) func (m *Macro) RegisterFunc(funcName string, fn interface{}) *Macro { fullFn := convertBuilderFunc(fn) m.registerFunc(funcName, fullFn) return m } func (m *Macro) registerFunc(funcName string, fullFn ParamEvaluatorBuilder) { if !goodParamFuncName(funcName) { return } for _, fn := range m.funcs { if fn.Name == funcName { fn.Func = fullFn return } } m.funcs = append(m.funcs, ParamFunc{ Name: funcName, Func: fullFn, }) } func (m *Macro) getFunc(funcName string) ParamEvaluatorBuilder { for _, fn := range m.funcs { if fn.Name == funcName { if fn.Func == nil { continue } return fn.Func } } return nil }