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  }