package serder import ( "reflect" "github.com/zyedidia/generic/stack" ) type WalkEvent interface{} type StructBeginEvent struct { Value reflect.Value } type StructArriveFieldEvent struct { Info reflect.StructField Value reflect.Value } type StructLeaveFieldEvent struct { Info reflect.StructField Value reflect.Value } type StructEndEvent struct { Value reflect.Value } type ArrayBeginEvent struct { Value reflect.Value } type ArrayArriveElementEvent struct { Index int Value reflect.Value } type ArrayLeaveElementEvent struct { Index int Value reflect.Value } type ArrayEndEvent struct { Value reflect.Value } type MapBeginEvent struct { Value reflect.Value } type MapArriveEntryEvent struct { Key reflect.Value Value reflect.Value } type MapLeaveEntryEvent struct { Key reflect.Value Value reflect.Value } type MapEndEvent struct { Value reflect.Value } type WalkingOp int const ( Next WalkingOp = iota Skip Stop ) type Walker func(ctx *WalkContext, event WalkEvent) WalkingOp type WalkContext struct { stack *stack.Stack[any] } func (c *WalkContext) StackPush(val any) { c.stack.Push(val) } func (c *WalkContext) StackPop() any { return c.stack.Pop() } func (c *WalkContext) StackPeek() any { return c.stack.Peek() } type WalkOption struct { StackValues []any } func WalkValue(value any, walker Walker, opts ...WalkOption) *WalkContext { var opt WalkOption if len(opts) > 0 { opt = opts[0] } ctx := &WalkContext{ stack: stack.New[any](), } for _, v := range opt.StackValues { ctx.StackPush(v) } doWalking(ctx, reflect.ValueOf(value), walker) return ctx } func doWalking(ctx *WalkContext, val reflect.Value, walker Walker) WalkingOp { if !WillWalkInto(val) { return Next } switch val.Kind() { case reflect.Array: fallthrough case reflect.Slice: if walker(ctx, ArrayBeginEvent{Value: val}) == Stop { return Stop } for i := 0; i < val.Len(); i++ { eleVal := val.Index(i) op := walker(ctx, ArrayArriveElementEvent{ Index: i, Value: eleVal, }) if op == Skip { if walker(ctx, ArrayLeaveElementEvent{ Index: i, Value: eleVal, }) == Stop { return Stop } continue } if op == Stop { return Stop } if doWalking(ctx, eleVal, walker) == Stop { return Stop } if walker(ctx, ArrayLeaveElementEvent{ Index: i, Value: eleVal, }) == Stop { return Stop } } if walker(ctx, ArrayEndEvent{Value: val}) == Stop { return Stop } case reflect.Map: if walker(ctx, MapBeginEvent{Value: val}) == Stop { return Stop } keys := val.MapKeys() for _, key := range keys { val := val.MapIndex(key) op := walker(ctx, MapArriveEntryEvent{ Key: key, Value: val, }) if op == Skip { if walker(ctx, MapLeaveEntryEvent{ Key: key, Value: val, }) == Stop { return Stop } continue } if op == Stop { return Stop } if doWalking(ctx, val, walker) == Stop { return Stop } if walker(ctx, MapLeaveEntryEvent{ Key: key, Value: val, }) == Stop { return Stop } } if walker(ctx, MapEndEvent{Value: val}) == Stop { return Stop } case reflect.Struct: if walker(ctx, StructBeginEvent{Value: val}) == Stop { return Stop } for i := 0; i < val.NumField(); i++ { field := val.Field(i) op := walker(ctx, StructArriveFieldEvent{ Info: val.Type().Field(i), Value: field, }) if op == Skip { if walker(ctx, StructLeaveFieldEvent{ Info: val.Type().Field(i), Value: field, }) == Stop { return Stop } continue } if op == Stop { return Stop } if doWalking(ctx, field, walker) == Stop { return Stop } if walker(ctx, StructLeaveFieldEvent{ Info: val.Type().Field(i), Value: field, }) == Stop { return Stop } } if walker(ctx, StructEndEvent{Value: val}) == Stop { return Stop } case reflect.Interface: fallthrough case reflect.Pointer: eleVal := val.Elem() return doWalking(ctx, eleVal, walker) } return Next } const ( WillWalkIntoTypeKinds = (1 << reflect.Array) | (1 << reflect.Map) | (1 << reflect.Slice) | (1 << reflect.Struct) ) func WillWalkInto(val reflect.Value) bool { if val.IsZero() { return false } typ := val.Type() typeKind := typ.Kind() if typeKind == reflect.Interface || typeKind == reflect.Pointer { return WillWalkInto(val.Elem()) } return ((1 << typeKind) & WillWalkIntoTypeKinds) != 0 }