Files
Anton MedvedevandGitHub fee5690347 Refactor checker to use Nature (#665)
This PR introduces a new concept inside Expr's type checker - Nature.

It's an abstraction other reflect.Type which carries additional information about possible type, methods and builtin function.

I was able to find and fix a few bugs in type checker and optimizer. Implementation of nature concept has proven itself. As well nature will allow to bring more feature for type checker in future.

Why Nature? I was looking for a name to replace Type. Type name already carries much information, as well can be confused with reflect.Type. Nature name should be understood as "nature of the node", same as "type of the node".
2024-06-01 00:09:22 +02:00

82 lines
1.8 KiB
Go

package optimizer
import (
"fmt"
"reflect"
"strings"
. "github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/file"
)
var errorType = reflect.TypeOf((*error)(nil)).Elem()
type constExpr struct {
applied bool
err error
fns map[string]reflect.Value
}
func (c *constExpr) Visit(node *Node) {
defer func() {
if r := recover(); r != nil {
msg := fmt.Sprintf("%v", r)
// Make message more actual, it's a runtime error, but at compile step.
msg = strings.Replace(msg, "runtime error:", "compile error:", 1)
c.err = &file.Error{
Location: (*node).Location(),
Message: msg,
}
}
}()
if call, ok := (*node).(*CallNode); ok {
if name, ok := call.Callee.(*IdentifierNode); ok {
fn, ok := c.fns[name.Value]
if ok {
in := make([]reflect.Value, len(call.Arguments))
for i := 0; i < len(call.Arguments); i++ {
arg := call.Arguments[i]
var param any
switch a := arg.(type) {
case *NilNode:
param = nil
case *IntegerNode:
param = a.Value
case *FloatNode:
param = a.Value
case *BoolNode:
param = a.Value
case *StringNode:
param = a.Value
case *ConstantNode:
param = a.Value
default:
return // Const expr optimization not applicable.
}
if param == nil && reflect.TypeOf(param) == nil {
// In case of nil value and nil type use this hack,
// otherwise reflect.Call will panic on zero value.
in[i] = reflect.ValueOf(&param).Elem()
} else {
in[i] = reflect.ValueOf(param)
}
}
out := fn.Call(in)
value := out[0].Interface()
if len(out) == 2 && out[1].Type() == errorType && !out[1].IsNil() {
c.err = out[1].Interface().(error)
return
}
constNode := &ConstantNode{Value: value}
patchWithType(node, constNode)
c.applied = true
}
}
}
}