diff --git a/builtin/builtin.go b/builtin/builtin.go index 0107d276..5ddcf0c3 100644 --- a/builtin/builtin.go +++ b/builtin/builtin.go @@ -1010,4 +1010,14 @@ var Builtins = []*Function{ }, Types: types(new(func(int) int)), }, + { + Name: "view", + Fast: View, + Validate: func(args []reflect.Type) (reflect.Type, error) { + if len(args) != 1 { + return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args)) + } + return anyType, nil + }, + }, } diff --git a/builtin/lib.go b/builtin/lib.go index 13c7d207..43ad166c 100644 --- a/builtin/lib.go +++ b/builtin/lib.go @@ -368,7 +368,11 @@ func flatten(arg reflect.Value) []any { x := flatten(v) ret = append(ret, x...) } else { - ret = append(ret, v.Interface()) + if v.CanInterface() { + ret = append(ret, v.Interface()) + } else { + ret = append(ret, v) + } } } return ret diff --git a/builtin/view.go b/builtin/view.go new file mode 100644 index 00000000..c9badb04 --- /dev/null +++ b/builtin/view.go @@ -0,0 +1,103 @@ +package builtin + +import ( + "encoding/json" + "fmt" + "reflect" + + "github.com/expr-lang/expr/internal/deref" +) + +type DataView struct { + Type string `json:"type,omitempty"` + Value string `json:"value,omitempty"` + Fields map[string]DataView `json:"fields,omitempty"` +} + +var ZeroValue = DataView{ + Type: "nil", + Value: "nil", +} + +func View(arg any) (result any) { + defer func() { + resultBytes, _ := json.Marshal(result) + result = string(resultBytes) + }() + if arg == nil { + return ZeroValue + } + + argValue := deref.Value(reflect.ValueOf(arg)) + + if !isComplicateKind(argValue.Kind()) { + return getSimpleView(argValue) + } + + if argValue.Kind() != reflect.Struct { + return DataView{ + Type: argValue.Type().String(), + Value: fmt.Sprintf("%v", innerInterface(argValue)), + } + } + + argType := deref.Type(reflect.TypeOf(arg)) + fields := make(map[string]DataView) + for i := 0; i < argValue.NumField(); i++ { + fieldType := argType.Field(i) + fieldValue := deref.Value(argValue.Field(i)) + if !isComplicateKind(fieldValue.Kind()) { + fields[fieldType.Name] = getSimpleView(fieldValue) + } else { + fields[fieldType.Name] = DataView{ + Type: fieldType.Type.String(), + } + } + } + + return DataView{ + Type: argValue.Type().String(), + Fields: fields, + } +} + +func isComplicateKind(kind reflect.Kind) bool { + switch kind { + case reflect.Struct, reflect.Chan, reflect.Func, reflect.Interface: + return true + } + return false +} + +func getSimpleView(arg reflect.Value) DataView { + switch arg.Kind() { + case reflect.Array, reflect.Slice: + return DataView{ + Type: arg.Type().String(), + Value: fmt.Sprintf("%v", flatten(arg)), + } + case reflect.Map: + keys := arg.MapKeys() + out := make([][2]any, len(keys)) + // TODO how to show the detail when the value is a struct + for i, key := range keys { + out[i] = [2]any{innerInterface(key), innerInterface(arg.MapIndex(key))} + } + return DataView{ + Type: arg.Type().String(), + Value: fmt.Sprintf("%v", out), + } + } + + return DataView{ + Type: arg.Type().String(), + Value: fmt.Sprintf("%v", innerInterface(arg)), + } +} + +func innerInterface(v reflect.Value) any { + if v.CanInterface() { + return v.Interface() + } + return v +} diff --git a/checker/checker.go b/checker/checker.go index 46911a3e..ad3cb012 100644 --- a/checker/checker.go +++ b/checker/checker.go @@ -22,13 +22,14 @@ func ParseCheck(input string, config *conf.Config) (*parser.Tree, error) { } if len(config.Visitors) > 0 { + // We need to perform types check, because some visitors may rely on + // types information available in the tree. + _, _ = Check(tree, config) + + // TODO fubang 1000 count? for i := 0; i < 1000; i++ { more := false for _, v := range config.Visitors { - // We need to perform types check, because some visitors may rely on - // types information available in the tree. - _, _ = Check(tree, config) - ast.Walk(&tree.Node, v) if v, ok := v.(interface { diff --git a/vm/runtime/runtime.go b/vm/runtime/runtime.go index 3cba9fea..23f04026 100644 --- a/vm/runtime/runtime.go +++ b/vm/runtime/runtime.go @@ -72,7 +72,7 @@ func Fetch(from, i any) any { return name == fieldName }) if value.IsValid() { - return value.Interface() + return GetFieldValue(value) } } panic(fmt.Sprintf("cannot fetch %v from %T", i, from)) @@ -86,7 +86,7 @@ type Field struct { func FetchField(from any, field *Field) any { v := reflect.ValueOf(from) if v.Kind() != reflect.Invalid { - v = reflect.Indirect(v) + v = deref.Value(v) // We can use v.FieldByIndex here, but it will panic if the field // is not exists. And we need to recover() to generate a more @@ -117,9 +117,11 @@ func fieldByIndex(v reflect.Value, field *Field) reflect.Value { if len(field.Index) == 1 { return v.Field(field.Index[0]) } + fmt.Println("inner print", field.Index) for i, x := range field.Index { + println("inner print for:", i, x) if i > 0 { - if v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface { if v.IsNil() { panic(fmt.Sprintf("cannot get %v from %v", field.Path[i], field.Path[i-1])) }