Documentation ¶
Overview ¶
Package demo is used for demonstration and testing of walkabout.
Example (Abstract) ¶
This example demonstrates how an enhanced visitable type can be used as though it were a tree of nodes.
package main import ( "fmt" "reflect" "github.com/cockroachdb/walkabout/demo" ) func main() { data, _ := demo.NewContainer(true) for i := 0; i < data.TargetCount(); i++ { child := data.TargetAt(i) if child == nil { fmt.Printf("%d: nil\n", i) } else { fmt.Printf("%d: %s %s\n", i, child.TargetTypeID(), reflect.TypeOf(child)) } } }
Output: 0: ByRefType *demo.ByRefType 1: ByRefType *demo.ByRefType 2: []ByRefType *demo.targetAbstract 3: []*ByRefType *demo.targetAbstract 4: ByValType *demo.ByValType 5: ByValType *demo.ByValType 6: []ByValType *demo.targetAbstract 7: []*ByValType *demo.targetAbstract 8: nil 9: ByValType *demo.ByValType 10: ByValType *demo.ByValType 11: ByValType *demo.ByValType 12: ByValType *demo.ByValType 13: []Target *demo.targetAbstract 14: []*Target *demo.targetAbstract 15: []Target *demo.targetAbstract
Example (Actions) ¶
This example shows a toy calculator AST and how custom actions can be introduced into the visitation flow. We've decided to use a visitor to stringify the Calculation. It's not how one would generally approach this problem, but it's useful to demonstrate more advanced flow-control.
package main import ( "fmt" "strconv" "strings" ) // This generation flow will find all types in this package that // are reachable from the Calculation struct and create a // Calc interface to unify them. //go:generate -command walkabout go run .. //go:generate walkabout --union Calc --reachable Calculation // This example shows a toy calculator AST and how custom actions can be // introduced into the visitation flow. We've decided to use a visitor // to stringify the Calculation. It's not how one would generally // approach this problem, but it's useful to demonstrate more advanced // flow-control. func main() { c := &Calculation{ Expr: &Func{"Avg", []Expr{ &BinaryOp{"+", &Scalar{1}, &Scalar{3}}, &Func{"Sum", []Expr{ &Scalar{10}, &Func{"Random", []Expr{&Scalar{1}, &Scalar{10}}}, &Scalar{99}}}, &BinaryOp{"*", &Scalar{5}, &Scalar{3}}, }}, } var w strings.Builder _, _, err := WalkCalc(c, func(ctx CalcContext, x Calc) CalcDecision { switch t := x.(type) { case *BinaryOp: // With a BinaryOp, we want to visit left, print the operator, // then right. We'll generate a sequence of actions to be // executed. return ctx.Actions( ctx.ActionVisit(t.Left), ctx.ActionCall(func() error { w.WriteString(t.Operator); return nil }), ctx.ActionVisit(t.Right), ) case *Func: // With a function, we want to print commas between the arguments. // Rather than creating an arbitrarily number of actions to // perform, an Intercept() function can be registered to // traverse a struct normally and receive a pre-visit for // the immediate children. We combine Intercept() and Post() // to print the closing parenthesis. w.WriteString(t.Fn) w.WriteString("(") comma := "" return ctx.Continue().Intercept(func(CalcContext, Calc) (ret CalcDecision) { w.WriteString(comma) comma = ", " return }).Post(func(CalcContext, Calc) (ret CalcDecision) { w.WriteString(")") return }) case *Scalar: w.WriteString(strconv.Itoa(t.val)) } return ctx.Continue() }) if err != nil { panic(err) } fmt.Println(w.String()) } type Calculation struct{ Expr Expr } type Expr interface { Calc isExpr() } type BinaryOp struct { Operator string Left Expr Right Expr } func (*BinaryOp) isExpr() {} type Scalar struct{ val int } func (*Scalar) isExpr() {} type Func struct { Fn string Args []Expr } func (*Func) isExpr() {}
Output: Avg(1+3, Sum(10, Random(1, 10), 99), 5*3)
Example (Error) ¶
This example shows how an error can be returned from a visitor function.
package main import ( "errors" "fmt" "github.com/cockroachdb/walkabout/demo" ) func main() { data, _ := demo.NewContainer(true) ret, changed, err := data.WalkTarget( func(ctx demo.TargetContext, x demo.Target) demo.TargetDecision { return ctx.Error(errors.New("an error")) }) fmt.Println(ret, changed, err) }
Output: <nil> false an error
Example (Post) ¶
This example demonstrates how pre- and post-visitation works. It also shows how visitation can be short-circuited.
package main import ( "fmt" "github.com/cockroachdb/walkabout/demo" ) func main() { data, _ := demo.NewContainer(true) _, _, err := data.WalkTarget(func(ctx demo.TargetContext, x demo.Target) demo.TargetDecision { switch x.(type) { case *demo.ContainerType: fmt.Println("pre container") return ctx.Continue().Post(func(demo.TargetContext, demo.Target) (d demo.TargetDecision) { fmt.Println("post container") return }) default: fmt.Println("halting") return ctx.Halt() } }) if err != nil { panic(err) } }
Output: pre container halting post container
Example (Replace) ¶
This example demonstrates how copy-on-replace can be implemented for "immutable" datastructures.
package main import ( "fmt" "github.com/cockroachdb/walkabout/demo" ) func main() { data, _ := demo.NewContainer(true) count := 0 data2, changed, err := data.WalkTarget( func(ctx demo.TargetContext, x demo.Target) demo.TargetDecision { switch t := x.(type) { case *demo.ByRefType: count++ cp := *t cp.Val = fmt.Sprintf("ByRef %d", count) return ctx.Skip().Replace(&cp) case *demo.ByValType: count++ cp := *t cp.Val = fmt.Sprintf("ByVal %d", count) return ctx.Skip().Replace(&cp) default: return ctx.Continue() } }) if err != nil { panic(err) } fmt.Printf("Changed: %v\n", changed) fmt.Printf("data != data2: %v", data != data2) }
Output: Changed: true data != data2: true
Example (Walk) ¶
This example demonstrates how enhanced visitable types can be visited with a function.
package main import ( "fmt" "reflect" "github.com/cockroachdb/walkabout/demo" ) func main() { data, _ := demo.NewContainer(true) var container, byVal, byRef int _, _, err := data.WalkTarget(func(ctx demo.TargetContext, x demo.Target) (d demo.TargetDecision) { switch x.(type) { case *demo.ContainerType: container++ case *demo.ByValType: byVal++ case *demo.ByRefType: byRef++ default: return ctx.Error(fmt.Errorf("unknown type %s", reflect.TypeOf(x))) } return }) if err != nil { panic(err) } fmt.Printf("Saw %d Container, %d ByValType, and %d ByRefType", container, byVal, byRef) }
Output: Saw 1 Container, 17 ByValType, and 6 ByRefType
Index ¶
- type ByRefType
- type ByValType
- type ContainerType
- type EmbedsTarget
- type NeverType
- type ReachableType
- type Target
- type TargetAbstract
- type TargetAction
- type TargetContext
- func (c *TargetContext) ActionCall(fn func() error) TargetAction
- func (c *TargetContext) ActionVisit(x Target) TargetAction
- func (c *TargetContext) Actions(actions ...TargetAction) TargetDecision
- func (c *TargetContext) Continue() TargetDecision
- func (c *TargetContext) Error(err error) TargetDecision
- func (c *TargetContext) Halt() TargetDecision
- func (c *TargetContext) Skip() TargetDecision
- type TargetDecision
- type TargetTypeID
- type TargetWalkerFn
- type Targets
- type Unionable
- type UnionableType
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ByRefType ¶
type ByRefType struct {
Val string
}
ByRefType implements Target with a pointer receiver.
func (*ByRefType) TargetAt ¶ added in v0.1.4
func (x *ByRefType) TargetAt(index int) TargetAbstract
TargetAt implements TargetAbstract.
func (*ByRefType) TargetCount ¶ added in v0.1.4
TargetCount returns 0.
func (*ByRefType) TargetTypeID ¶ added in v0.1.4
func (*ByRefType) TargetTypeID() TargetTypeID
TargetTypeID returns TargetTypeByRefType.
func (*ByRefType) WalkTarget ¶
func (x *ByRefType) WalkTarget(fn TargetWalkerFn) (_ *ByRefType, changed bool, err error)
WalkTarget visits the receiver with the provided callback.
type ByValType ¶
type ByValType struct {
Val string
}
ByValType implements the Target interface with a value receiver.
func (*ByValType) TargetAt ¶ added in v0.1.4
func (x *ByValType) TargetAt(index int) TargetAbstract
TargetAt implements TargetAbstract.
func (*ByValType) TargetCount ¶ added in v0.1.4
TargetCount returns 0.
func (*ByValType) TargetTypeID ¶ added in v0.1.4
func (*ByValType) TargetTypeID() TargetTypeID
TargetTypeID returns TargetTypeByValType.
func (*ByValType) WalkTarget ¶
func (x *ByValType) WalkTarget(fn TargetWalkerFn) (_ *ByValType, changed bool, err error)
WalkTarget visits the receiver with the provided callback.
type ContainerType ¶
type ContainerType struct { ByRef ByRefType ByRefPtr *ByRefType ByRefSlice []ByRefType ByRefPtrSlice []*ByRefType ByVal ByValType ByValPtr *ByValType ByValSlice []ByValType ByValPtrSlice []*ByValType // We can break cycles, too. Container *ContainerType AnotherTarget Target AnotherTargetPtr *Target // Interfaces which extend the visitable interface are supported. EmbedsTarget EmbedsTarget EmbedsTargetPtr *EmbedsTarget // Slices of interfaces are supported. TargetSlice []Target // We can support slices of interface pointers. InterfacePtrSlice []*Target // Demonstrate use of named visitable type. NamedTargets Targets // Unexported types aren't generated. Ignored *ignoredType // This field will be generated only when in --union mode. UnionableType *UnionableType /// This field will only be visited when in --union --reachable mode. ReachableType ReachableType // This type is declared in another package. It shouldn't be present // in any configuration, unless we allow the code generator to // start writing to multiple directories, in which case we can make // --union --reachable work. OtherReachable other.Reachable // This field is in --reachable mode, since it does implement // our Target interface. OtherImplementor other.Implementor // contains filtered or unexported fields }
ContainerType is just a regular struct that contains fields whose types implement or contain Target.
func NewContainer ¶
func NewContainer(useValuePtrs bool) (*ContainerType, int)
NewContainer generates test data.
func (*ContainerType) TargetAt ¶ added in v0.1.4
func (x *ContainerType) TargetAt(index int) TargetAbstract
TargetAt implements TargetAbstract.
func (*ContainerType) TargetCount ¶ added in v0.1.4
func (x *ContainerType) TargetCount() int
TargetCount returns 16.
func (*ContainerType) TargetTypeID ¶ added in v0.1.4
func (*ContainerType) TargetTypeID() TargetTypeID
TargetTypeID returns TargetTypeContainerType.
func (*ContainerType) Value ¶
func (*ContainerType) Value() string
Value implements the Target interface.
func (*ContainerType) WalkTarget ¶
func (x *ContainerType) WalkTarget(fn TargetWalkerFn) (_ *ContainerType, changed bool, err error)
WalkTarget visits the receiver with the provided callback.
type EmbedsTarget ¶
type EmbedsTarget interface { Target // contains filtered or unexported methods }
EmbedsTarget demonstrates an interface hierarchy.
type NeverType ¶
type NeverType struct{}
NeverType isn't reachable from any type that implements Target, so it will never be generated.
type ReachableType ¶
type ReachableType struct{}
ReachableType will be included in --union --reachable mode. It doesn't implement the Target interface, but it is a field in ContainerType.
type Target ¶
type Target interface {
Value() string
}
Target is a base interface that we run the code-generator against. There's nothing special about this interface.
func WalkTarget ¶
func WalkTarget(x Target, fn TargetWalkerFn) (_ Target, changed bool, err error)
WalkTarget visits the receiver with the provided callback.
type TargetAbstract ¶
type TargetAbstract interface { // TargetAt returns the nth field of a struct or nth element of a // slice. If the child is a type which directly implements // TargetAbstract, it will be returned. If the child is of a pointer or // interface type, the value will be automatically dereferenced if it // is non-nil. If the child is a slice type, a TargetAbstract wrapper // around the slice will be returned. TargetAt(index int) TargetAbstract // TargetCount returns the number of visitable fields in a struct, // or the length of a slice. TargetCount() int // TargetTypeID returns a type token. TargetTypeID() TargetTypeID }
TargetAbstract allows users to treat a Target as an abstract tree of nodes. All visitable struct types will have generated methods which implement this interface.
type TargetAction ¶ added in v0.1.4
TargetAction is used by TargetContext.Actions() and allows users to have fine-grained control over traversal.
type TargetContext ¶
type TargetContext struct {
// contains filtered or unexported fields
}
TargetContext is provided to TargetWalkerFn and acts as a factory for constructing TargetDecision instances.
func (*TargetContext) ActionCall ¶ added in v0.1.4
func (c *TargetContext) ActionCall(fn func() error) TargetAction
ActionCall constructs a TargetAction that will invoke the given callback.
func (*TargetContext) ActionVisit ¶ added in v0.1.4
func (c *TargetContext) ActionVisit(x Target) TargetAction
ActionVisit constructs a TargetAction that will visit the given value.
func (*TargetContext) Actions ¶ added in v0.1.4
func (c *TargetContext) Actions(actions ...TargetAction) TargetDecision
Actions will perform the given actions in place of visiting values that would normally be visited. This allows callers to control specific field visitation order or to insert additional callbacks between visiting certain values.
func (*TargetContext) Continue ¶
func (c *TargetContext) Continue() TargetDecision
Continue returns the zero-value of TargetDecision. It exists only for cases where it improves the readability of code.
func (*TargetContext) Error ¶
func (c *TargetContext) Error(err error) TargetDecision
Error returns a TargetDecision which will cause the given error to be returned from the Walk() function. Post-visit functions will not be called.
func (*TargetContext) Halt ¶
func (c *TargetContext) Halt() TargetDecision
Halt will end a visitation early and return from the Walk() function. Any registered post-visit functions will be called.
func (*TargetContext) Skip ¶
func (c *TargetContext) Skip() TargetDecision
Skip will not traverse the fields of the current object.
type TargetDecision ¶
TargetDecision is used by TargetWalkerFn to control visitation. The TargetContext provided to a TargetWalkerFn acts as a factory for TargetDecision instances. In general, the factory methods choose a traversal strategy and additional methods on the TargetDecision can achieve a variety of side-effects.
func (TargetDecision) Intercept ¶ added in v0.1.4
func (d TargetDecision) Intercept(fn TargetWalkerFn) TargetDecision
Intercept registers a function to be called immediately before visiting each field or element of the current value.
func (TargetDecision) Post ¶
func (d TargetDecision) Post(fn TargetWalkerFn) TargetDecision
Post registers a post-visit function, which will be called after the fields of the current object. The function can make another decision about the current value.
func (TargetDecision) Replace ¶
func (d TargetDecision) Replace(x Target) TargetDecision
Replace allows the currently-visited value to be replaced. All parent nodes will be cloned.
type TargetTypeID ¶ added in v0.1.4
TargetTypeID is a lightweight type token.
const ( TargetTypeByRefType TargetTypeID TargetTypeByRefTypePtr TargetTypeByRefTypePtrSlice TargetTypeByRefTypeSlice TargetTypeByValType TargetTypeByValTypePtr TargetTypeByValTypePtrSlice TargetTypeByValTypeSlice TargetTypeContainerType TargetTypeContainerTypePtr TargetTypeEmbedsTarget TargetTypeEmbedsTargetPtr TargetTypeTarget TargetTypeTargetPtr TargetTypeTargetPtrSlice TargetTypeTargetSlice )
These are lightweight type tokens.
func (TargetTypeID) String ¶ added in v0.1.4
func (t TargetTypeID) String() string
String is for debugging use only.
type TargetWalkerFn ¶
type TargetWalkerFn func(ctx TargetContext, x Target) TargetDecision
TargetWalkerFn is used to implement a visitor pattern over types which implement Target.
Implementations of this function return a TargetDecision, which allows the function to control traversal. The zero value of TargetDecision means "continue". Other values can be obtained from the provided TargetContext to stop or to return an error.
A TargetDecision can also specify a post-visit function to execute or can be used to replace the value being visited.
type Unionable ¶
type Unionable interface {
// contains filtered or unexported methods
}
Unionable demonstrates how multiple type hierarchies can be unioned using another generated interface.
type UnionableType ¶
type UnionableType struct{}
UnionableType will be included only when in --union mode.