github.com/expr-lang/expr@v1.16.9/optimizer/const_expr.go (about)

     1  package optimizer
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strings"
     7  
     8  	. "github.com/expr-lang/expr/ast"
     9  	"github.com/expr-lang/expr/file"
    10  )
    11  
    12  var errorType = reflect.TypeOf((*error)(nil)).Elem()
    13  
    14  type constExpr struct {
    15  	applied bool
    16  	err     error
    17  	fns     map[string]reflect.Value
    18  }
    19  
    20  func (c *constExpr) Visit(node *Node) {
    21  	defer func() {
    22  		if r := recover(); r != nil {
    23  			msg := fmt.Sprintf("%v", r)
    24  			// Make message more actual, it's a runtime error, but at compile step.
    25  			msg = strings.Replace(msg, "runtime error:", "compile error:", 1)
    26  			c.err = &file.Error{
    27  				Location: (*node).Location(),
    28  				Message:  msg,
    29  			}
    30  		}
    31  	}()
    32  
    33  	patch := func(newNode Node) {
    34  		c.applied = true
    35  		Patch(node, newNode)
    36  	}
    37  
    38  	if call, ok := (*node).(*CallNode); ok {
    39  		if name, ok := call.Callee.(*IdentifierNode); ok {
    40  			fn, ok := c.fns[name.Value]
    41  			if ok {
    42  				in := make([]reflect.Value, len(call.Arguments))
    43  				for i := 0; i < len(call.Arguments); i++ {
    44  					arg := call.Arguments[i]
    45  					var param any
    46  
    47  					switch a := arg.(type) {
    48  					case *NilNode:
    49  						param = nil
    50  					case *IntegerNode:
    51  						param = a.Value
    52  					case *FloatNode:
    53  						param = a.Value
    54  					case *BoolNode:
    55  						param = a.Value
    56  					case *StringNode:
    57  						param = a.Value
    58  					case *ConstantNode:
    59  						param = a.Value
    60  
    61  					default:
    62  						return // Const expr optimization not applicable.
    63  					}
    64  
    65  					if param == nil && reflect.TypeOf(param) == nil {
    66  						// In case of nil value and nil type use this hack,
    67  						// otherwise reflect.Call will panic on zero value.
    68  						in[i] = reflect.ValueOf(&param).Elem()
    69  					} else {
    70  						in[i] = reflect.ValueOf(param)
    71  					}
    72  				}
    73  
    74  				out := fn.Call(in)
    75  				value := out[0].Interface()
    76  				if len(out) == 2 && out[1].Type() == errorType && !out[1].IsNil() {
    77  					c.err = out[1].Interface().(error)
    78  					return
    79  				}
    80  				constNode := &ConstantNode{Value: value}
    81  				patch(constNode)
    82  			}
    83  		}
    84  	}
    85  }