github.com/tobgu/qframe@v0.4.0/config/eval/context.go (about) 1 package eval 2 3 import ( 4 "fmt" 5 "math" 6 "reflect" 7 "strings" 8 9 "github.com/tobgu/qframe/function" 10 qfstrings "github.com/tobgu/qframe/internal/strings" 11 "github.com/tobgu/qframe/qerrors" 12 "github.com/tobgu/qframe/types" 13 ) 14 15 type functionsByArgCount struct { 16 singleArgs map[string]interface{} 17 doubleArgs map[string]interface{} 18 } 19 20 type functionsByArgType map[types.FunctionType]functionsByArgCount 21 22 // ArgCount is the number of arguments passed to a function to be evaluated. 23 type ArgCount byte 24 25 const ( 26 ArgCountOne ArgCount = iota 27 ArgCountTwo 28 ) 29 30 // String returns a string representation of the ArgCount 31 func (c ArgCount) String() string { 32 switch c { 33 case ArgCountOne: 34 return "Single argument" 35 case ArgCountTwo: 36 return "Double argument" 37 default: 38 return "Unknown argument count" 39 } 40 } 41 42 // Context describes the context in which an expression is executed. 43 // It maps function names to actual functions. 44 type Context struct { 45 functions functionsByArgType 46 } 47 48 // NewDefaultCtx creates a default context containing a base set of functions. 49 // It can be used as is or enhanced with other/more functions. See the source code 50 // for the current set of functions. 51 func NewDefaultCtx() *Context { 52 return &Context{ 53 functionsByArgType{ 54 types.FunctionTypeFloat: functionsByArgCount{ 55 singleArgs: map[string]interface{}{ 56 "abs": math.Abs, 57 "str": function.StrF, 58 "int": function.IntF, 59 }, 60 doubleArgs: map[string]interface{}{ 61 "+": function.PlusF, 62 "-": function.MinusF, 63 "*": function.MulF, 64 "/": function.DivF, 65 }, 66 }, 67 types.FunctionTypeInt: functionsByArgCount{ 68 singleArgs: map[string]interface{}{ 69 "abs": function.AbsI, 70 "str": function.StrI, 71 "bool": function.BoolI, 72 "float": function.FloatI, 73 }, 74 doubleArgs: map[string]interface{}{ 75 "+": function.PlusI, 76 "-": function.MinusI, 77 "*": function.MulI, 78 "/": function.DivI, 79 }, 80 }, 81 types.FunctionTypeBool: functionsByArgCount{ 82 singleArgs: map[string]interface{}{ 83 "!": function.NotB, 84 "str": function.StrB, 85 "int": function.IntB, 86 }, 87 doubleArgs: map[string]interface{}{ 88 "&": function.AndB, 89 "|": function.OrB, 90 "!=": function.XorB, 91 "nand": function.NandB, 92 }, 93 }, 94 types.FunctionTypeString: functionsByArgCount{ 95 singleArgs: map[string]interface{}{ 96 "upper": function.UpperS, 97 "lower": function.LowerS, 98 "str": function.StrS, 99 "len": function.LenS, 100 }, 101 doubleArgs: map[string]interface{}{ 102 "+": function.ConcatS, 103 }, 104 }, 105 }, 106 } 107 } 108 109 // GetFunc returns a reference to a function matching the given function type, argument count and name. 110 // If no matching function is found in the context the second return value is set to false. 111 func (ctx *Context) GetFunc(typ types.FunctionType, ac ArgCount, name string) (interface{}, bool) { 112 if typ == types.FunctionTypeUndefined { 113 // This is a special case for functions on columns with undefined type. These columns 114 // always of zero and the function will never be executed. 115 return nil, true 116 } 117 118 var fn interface{} 119 var ok bool 120 if ac == ArgCountOne { 121 fn, ok = ctx.functions[typ].singleArgs[name] 122 } else { 123 fn, ok = ctx.functions[typ].doubleArgs[name] 124 } 125 126 return fn, ok 127 } 128 129 func (ctx *Context) setFunc(typ types.FunctionType, ac ArgCount, name string, fn interface{}) { 130 if ac == ArgCountOne { 131 ctx.functions[typ].singleArgs[name] = fn 132 } else { 133 ctx.functions[typ].doubleArgs[name] = fn 134 } 135 } 136 137 // SetFunc inserts a function into the context under the given name. 138 func (ctx *Context) SetFunc(name string, fn interface{}) error { 139 if err := qfstrings.CheckName(name); err != nil { 140 return qerrors.Propagate("SetFunc", err) 141 } 142 143 // Since there's such a flexibility in the function types that can be 144 // used and there is no static typing to support it this function 145 // acts as the gate keeper for adding new functions. 146 var ac ArgCount 147 var typ types.FunctionType 148 switch fn.(type) { 149 // Int 150 case func(int, int) int: 151 ac, typ = ArgCountTwo, types.FunctionTypeInt 152 case func(int) int, func(int) bool, func(int) float64, func(int) *string: 153 ac, typ = ArgCountOne, types.FunctionTypeInt 154 155 // Float 156 case func(float64, float64) float64: 157 ac, typ = ArgCountTwo, types.FunctionTypeFloat 158 case func(float64) float64, func(float64) int, func(float64) bool, func(float64) *string: 159 ac, typ = ArgCountOne, types.FunctionTypeFloat 160 161 // Bool 162 case func(bool, bool) bool: 163 ac, typ = ArgCountTwo, types.FunctionTypeBool 164 case func(bool) bool, func(bool) int, func(bool) float64, func(bool) *string: 165 ac, typ = ArgCountOne, types.FunctionTypeBool 166 167 // String 168 case func(*string, *string) *string: 169 ac, typ = ArgCountTwo, types.FunctionTypeString 170 case func(*string) *string, func(*string) int, func(*string) float64, func(*string) bool: 171 ac, typ = ArgCountOne, types.FunctionTypeString 172 173 default: 174 return qerrors.New("SetFunc", "invalid function type for function \"%s\": %v", name, reflect.TypeOf(fn)) 175 } 176 177 ctx.setFunc(typ, ac, name, fn) 178 return nil 179 } 180 181 func (ctx *Context) String() string { 182 result := "" 183 for fnType, funcs := range ctx.functions { 184 result += fmt.Sprintf("\n%s\n%s", fnType, strings.Repeat("-", len(fnType.String()))) 185 result += "\n Single arg\n" 186 for funcName := range funcs.singleArgs { 187 result += " " + funcName + "\n" 188 } 189 190 result += "\n Double arg\n" 191 for funcName := range funcs.doubleArgs { 192 result += " " + funcName + "\n" 193 } 194 } 195 196 return result 197 }