// Copyright 2016 The OPA Authors. All rights reserved. // Use of this source code is governed by an Apache2 // license that can be found in the LICENSE file. package ast var ( termTypeVisitor = newTypeVisitor[*Term]() varTypeVisitor = newTypeVisitor[Var]() exprTypeVisitor = newTypeVisitor[*Expr]() ruleTypeVisitor = newTypeVisitor[*Rule]() refTypeVisitor = newTypeVisitor[Ref]() bodyTypeVisitor = newTypeVisitor[Body]() withTypeVisitor = newTypeVisitor[*With]() ) type ( // GenericVisitor provides a utility to walk over AST nodes using a // closure. If the closure returns true, the visitor will not walk // over AST nodes under x. GenericVisitor struct { f func(x any) bool } // BeforeAfterVisitor provides a utility to walk over AST nodes using // closures. If the before closure returns true, the visitor will not // walk over AST nodes under x. The after closure is invoked always // after visiting a node. BeforeAfterVisitor struct { before func(x any) bool after func(x any) } // VarVisitor walks AST nodes under a given node and collects all encountered // variables. The collected variables can be controlled by specifying // VarVisitorParams when creating the visitor. VarVisitor struct { params VarVisitorParams vars VarSet } // VarVisitorParams contains settings for a VarVisitor. VarVisitorParams struct { SkipRefHead bool SkipRefCallHead bool SkipObjectKeys bool SkipClosures bool SkipWithTarget bool SkipSets bool SkipTemplateStrings bool } // Visitor defines the interface for iterating AST elements. The Visit function // can return a Visitor w which will be used to visit the children of the AST // element v. If the Visit function returns nil, the children will not be // visited. // // Deprecated: use [GenericVisitor] or another visitor implementation Visitor interface { Visit(v any) (w Visitor) } // BeforeAndAfterVisitor wraps Visitor to provide hooks for being called before // and after the AST has been visited. // // Deprecated: use [GenericVisitor] or another visitor implementation BeforeAndAfterVisitor interface { Visitor Before(x any) After(x any) } // typeVisitor is a generic visitor for a specific type T (the "generic" name was // however taken). Contrary to the [GenericVisitor], the typeVisitor only invokes // the visit function for nodes of type T, saving both CPU cycles and type assertions. // typeVisitor implementations carry no state, and can be shared freely across // goroutines. Access is private for the time being, as there is already inflation // in visitor types exposed in the AST package. The various WalkXXX functions however // now leverage typeVisitor under the hood. // // While a typeVisitor is generally a more performant option over a GenericVisitor, // it is not as flexible: a type visitor can only visit nodes of a single type T, // whereas a GenericVisitor visits all nodes. Adding to that, a typeVisitor can only // be instantiated for **concrete types** — not interfaces (e.g., [*Expr], not [Node]), // as reflection would be required to determine the concrete type at runtime, thus // nullifying the performance benefits of the typeVisitor in the first place. typeVisitor[T any] struct { typ any } ) // Walk iterates the AST by calling the Visit function on the [Visitor] // v for x before recursing. // // Deprecated: use [GenericVisitor.Walk] func Walk(v Visitor, x any) { if bav, ok := v.(BeforeAndAfterVisitor); !ok { walk(v, x) } else { bav.Before(x) walk(bav, x) bav.After(x) } } // WalkBeforeAndAfter iterates the AST by calling the Visit function on the // Visitor v for x before recursing. // // Deprecated: use [GenericVisitor.Walk] func WalkBeforeAndAfter(v BeforeAndAfterVisitor, x any) { Walk(v, x) } func walk(v Visitor, x any) { w := v.Visit(x) if w == nil { return } switch x := x.(type) { case *Module: Walk(w, x.Package) for i := range x.Imports { Walk(w, x.Imports[i]) } for i := range x.Rules { Walk(w, x.Rules[i]) } for i := range x.Annotations { Walk(w, x.Annotations[i]) } for i := range x.Comments { Walk(w, x.Comments[i]) } case *Package: Walk(w, x.Path) case *Import: Walk(w, x.Path) Walk(w, x.Alias) case *Rule: Walk(w, x.Head) Walk(w, x.Body) if x.Else != nil { Walk(w, x.Else) } case *Head: Walk(w, x.Name) Walk(w, x.Args) if x.Key != nil { Walk(w, x.Key) } if x.Value != nil { Walk(w, x.Value) } case Body: for i := range x { Walk(w, x[i]) } case Args: for i := range x { Walk(w, x[i]) } case *Expr: switch ts := x.Terms.(type) { case *Term, *SomeDecl, *Every: Walk(w, ts) case []*Term: for i := range ts { Walk(w, ts[i]) } } for i := range x.With { Walk(w, x.With[i]) } case *With: Walk(w, x.Target) Walk(w, x.Value) case *Term: Walk(w, x.Value) case Ref: for i := range x { Walk(w, x[i]) } case *object: x.Foreach(func(k, vv *Term) { Walk(w, k) Walk(w, vv) }) case *Array: x.Foreach(func(t *Term) { Walk(w, t) }) case Set: x.Foreach(func(t *Term) { Walk(w, t) }) case *ArrayComprehension: Walk(w, x.Term) Walk(w, x.Body) case *ObjectComprehension: Walk(w, x.Key) Walk(w, x.Value) Walk(w, x.Body) case *SetComprehension: Walk(w, x.Term) Walk(w, x.Body) case Call: for i := range x { Walk(w, x[i]) } case *Every: if x.Key != nil { Walk(w, x.Key) } Walk(w, x.Value) Walk(w, x.Domain) Walk(w, x.Body) case *SomeDecl: for i := range x.Symbols { Walk(w, x.Symbols[i]) } case *TemplateString: for i := range x.Parts { Walk(w, x.Parts[i]) } } } // WalkVars calls the function f on all vars under x. If the function f // returns true, AST nodes under the last node will not be visited. func WalkVars(x any, f func(Var) bool) { varTypeVisitor.walk(x, f) } // WalkClosures calls the function f on all closures under x. If the function f // returns true, AST nodes under the last node will not be visited. func WalkClosures(x any, f func(any) bool) { vis := NewGenericVisitor(func(x any) bool { switch x := x.(type) { case *ArrayComprehension, *ObjectComprehension, *SetComprehension, *Every: return f(x) } return false }) vis.Walk(x) } // WalkRefs calls the function f on all references under x. If the function f // returns true, AST nodes under the last node will not be visited. func WalkRefs(x any, f func(Ref) bool) { refTypeVisitor.walk(x, f) } // WalkTerms calls the function f on all terms under x. If the function f // returns true, AST nodes under the last node will not be visited. func WalkTerms(x any, f func(*Term) bool) { termTypeVisitor.walk(x, f) } // WalkWiths calls the function f on all with modifiers under x. If the function f // returns true, AST nodes under the last node will not be visited. func WalkWiths(x any, f func(*With) bool) { withTypeVisitor.walk(x, f) } // WalkExprs calls the function f on all expressions under x. If the function f // returns true, AST nodes under the last node will not be visited. func WalkExprs(x any, f func(*Expr) bool) { exprTypeVisitor.walk(x, f) } // WalkBodies calls the function f on all bodies under x. If the function f // returns true, AST nodes under the last node will not be visited. func WalkBodies(x any, f func(Body) bool) { bodyTypeVisitor.walk(x, f) } // WalkRules calls the function f on all rules under x. If the function f // returns true, AST nodes under the last node will not be visited. func WalkRules(x any, f func(*Rule) bool) { switch x := x.(type) { case *Module: for i := range x.Rules { if !f(x.Rules[i]) && x.Rules[i].Else != nil { WalkRules(x.Rules[i].Else, f) } } case *Rule: if !f(x) && x.Else != nil { WalkRules(x.Else, f) } default: ruleTypeVisitor.walk(x, f) } } // WalkNodes calls the function f on all nodes under x. If the function f // returns true, AST nodes under the last node will not be visited. func WalkNodes(x any, f func(Node) bool) { vis := NewGenericVisitor(func(x any) bool { if n, ok := x.(Node); ok { return f(n) } return false }) vis.Walk(x) } func newTypeVisitor[T any]() *typeVisitor[T] { var t T return &typeVisitor[T]{typ: any(t)} } func (tv *typeVisitor[T]) walkArgs(args Args, visit func(x T) bool) { // If T is not Args, avoid allocation by inlining the walk. if _, ok := tv.typ.(Args); !ok { for i := range args { tv.walk(args[i], visit) } } else { tv.walk(args, visit) // allocates } } func (tv *typeVisitor[T]) walkBody(body Body, visit func(x T) bool) { if _, ok := tv.typ.(Body); !ok { for i := range body { tv.walk(body[i], visit) } } else { tv.walk(body, visit) // allocates } } func (tv *typeVisitor[T]) walkRef(ref Ref, visit func(x T) bool) { if _, ok := tv.typ.(Ref); !ok { for i := range ref { tv.walk(ref[i], visit) } } else { tv.walk(ref, visit) // allocates } } func (tv *typeVisitor[T]) walk(x any, visit func(x T) bool) { if v, ok := x.(T); ok && visit(v) { return } switch x := x.(type) { case *Module: tv.walk(x.Package, visit) for i := range x.Imports { tv.walk(x.Imports[i], visit) } for i := range x.Rules { tv.walk(x.Rules[i], visit) } for i := range x.Annotations { tv.walk(x.Annotations[i], visit) } for i := range x.Comments { tv.walk(x.Comments[i], visit) } case *Package: tv.walkRef(x.Path, visit) case *Import: tv.walk(x.Path, visit) if _, ok := tv.typ.(Var); ok { tv.walk(x.Alias, visit) } case *Rule: tv.walk(x.Head, visit) tv.walkBody(x.Body, visit) if x.Else != nil { tv.walk(x.Else, visit) } case *Head: if _, ok := tv.typ.(Var); ok { tv.walk(x.Name, visit) } tv.walkArgs(x.Args, visit) if x.Key != nil { tv.walk(x.Key, visit) } if x.Value != nil { tv.walk(x.Value, visit) } case Body: for i := range x { tv.walk(x[i], visit) } case Args: for i := range x { tv.walk(x[i], visit) } case *Expr: switch ts := x.Terms.(type) { case *Term, *SomeDecl, *Every: tv.walk(ts, visit) case []*Term: for i := range ts { tv.walk(ts[i], visit) } } for i := range x.With { tv.walk(x.With[i], visit) } case *With: tv.walk(x.Target, visit) tv.walk(x.Value, visit) case *Term: tv.walk(x.Value, visit) case Ref: for i := range x { tv.walk(x[i], visit) } case *object: x.Foreach(func(k, v *Term) { tv.walk(k, visit) tv.walk(v, visit) }) case Object: for _, k := range x.Keys() { tv.walk(k, visit) tv.walk(x.Get(k), visit) } case *Array: for i := range x.Len() { tv.walk(x.Elem(i), visit) } case Set: xSlice := x.Slice() for i := range xSlice { tv.walk(xSlice[i], visit) } case *ArrayComprehension: tv.walk(x.Term, visit) tv.walkBody(x.Body, visit) case *ObjectComprehension: tv.walk(x.Key, visit) tv.walk(x.Value, visit) tv.walkBody(x.Body, visit) case *SetComprehension: tv.walk(x.Term, visit) tv.walkBody(x.Body, visit) case Call: for i := range x { tv.walk(x[i], visit) } case *Every: if x.Key != nil { tv.walk(x.Key, visit) } tv.walk(x.Value, visit) tv.walk(x.Domain, visit) tv.walkBody(x.Body, visit) case *SomeDecl: for i := range x.Symbols { tv.walk(x.Symbols[i], visit) } case *TemplateString: for i := range x.Parts { tv.walk(x.Parts[i], visit) } } } // NewGenericVisitor returns a new GenericVisitor that will invoke the function // f on AST nodes. Note that while it returns a pointer, the creating a GenericVisitor // doesn't commonly allocate it on the heap, as long as it doesn't escape the function // in which it is created and used (as it's trivially inlined). func NewGenericVisitor(f func(x any) bool) *GenericVisitor { return &GenericVisitor{f} } // Walk iterates the AST by calling the function f on the // GenericVisitor before recursing. Contrary to the generic Walk, this // does not require allocating the visitor from heap. func (vis *GenericVisitor) Walk(x any) { if vis.f(x) { return } switch x := x.(type) { case *Module: vis.Walk(x.Package) for i := range x.Imports { vis.Walk(x.Imports[i]) } for i := range x.Rules { vis.Walk(x.Rules[i]) } for i := range x.Annotations { vis.Walk(x.Annotations[i]) } for i := range x.Comments { vis.Walk(x.Comments[i]) } case *Package: vis.Walk(x.Path) case *Import: vis.Walk(x.Path) if x.Alias != "" { vis.f(x.Alias) } case *Rule: vis.Walk(x.Head) vis.Walk(x.Body) if x.Else != nil { vis.Walk(x.Else) } case *Head: if x.Name != "" { vis.f(x.Name) } if x.Args != nil { vis.Walk(x.Args) } if x.Key != nil { vis.Walk(x.Key) } if x.Value != nil { vis.Walk(x.Value) } case Body: for i := range x { vis.Walk(x[i]) } case Args: for i := range x { vis.Walk(x[i]) } case *Expr: switch ts := x.Terms.(type) { case *Term, *SomeDecl, *Every: vis.Walk(ts) case []*Term: for i := range ts { vis.Walk(ts[i]) } } for i := range x.With { vis.Walk(x.With[i]) } case *With: vis.Walk(x.Target) vis.Walk(x.Value) case *Term: vis.Walk(x.Value) case Ref: for i := range x { vis.Walk(x[i]) } case *object: x.Foreach(func(k, _ *Term) { vis.Walk(k) vis.Walk(x.Get(k)) }) case Object: for _, k := range x.Keys() { vis.Walk(k) vis.Walk(x.Get(k)) } case *Array: for i := range x.Len() { vis.Walk(x.Elem(i)) } case Set: xSlice := x.Slice() for i := range xSlice { vis.Walk(xSlice[i]) } case *ArrayComprehension: vis.Walk(x.Term) vis.Walk(x.Body) case *ObjectComprehension: vis.Walk(x.Key) vis.Walk(x.Value) vis.Walk(x.Body) case *SetComprehension: vis.Walk(x.Term) vis.Walk(x.Body) case Call: for i := range x { vis.Walk(x[i]) } case *Every: if x.Key != nil { vis.Walk(x.Key) } vis.Walk(x.Value) vis.Walk(x.Domain) vis.Walk(x.Body) case *SomeDecl: for i := range x.Symbols { vis.Walk(x.Symbols[i]) } case *TemplateString: for i := range x.Parts { vis.Walk(x.Parts[i]) } } } // NewBeforeAfterVisitor returns a new BeforeAndAfterVisitor that // will invoke the functions before and after AST nodes. func NewBeforeAfterVisitor(before func(x any) bool, after func(x any)) *BeforeAfterVisitor { return &BeforeAfterVisitor{before, after} } // Walk iterates the AST by calling the functions on the // BeforeAndAfterVisitor before and after recursing. Contrary to the // generic Walk, this does not require allocating the visitor from // heap. func (vis *BeforeAfterVisitor) Walk(x any) { defer vis.after(x) if vis.before(x) { return } switch x := x.(type) { case *Module: vis.Walk(x.Package) for i := range x.Imports { vis.Walk(x.Imports[i]) } for i := range x.Rules { vis.Walk(x.Rules[i]) } for i := range x.Annotations { vis.Walk(x.Annotations[i]) } for i := range x.Comments { vis.Walk(x.Comments[i]) } case *Package: vis.Walk(x.Path) case *Import: vis.Walk(x.Path) vis.Walk(x.Alias) case *Rule: vis.Walk(x.Head) vis.Walk(x.Body) if x.Else != nil { vis.Walk(x.Else) } case *Head: if len(x.Reference) > 0 { vis.Walk(x.Reference) } else { vis.Walk(x.Name) if x.Key != nil { vis.Walk(x.Key) } } vis.Walk(x.Args) if x.Value != nil { vis.Walk(x.Value) } case Body: for i := range x { vis.Walk(x[i]) } case Args: for i := range x { vis.Walk(x[i]) } case *Expr: switch ts := x.Terms.(type) { case *Term, *SomeDecl, *Every: vis.Walk(ts) case []*Term: for i := range ts { vis.Walk(ts[i]) } } for i := range x.With { vis.Walk(x.With[i]) } case *With: vis.Walk(x.Target) vis.Walk(x.Value) case *Term: vis.Walk(x.Value) case Ref: for i := range x { vis.Walk(x[i]) } case *object: x.Foreach(func(k, _ *Term) { vis.Walk(k) vis.Walk(x.Get(k)) }) case Object: x.Foreach(func(k, _ *Term) { vis.Walk(k) vis.Walk(x.Get(k)) }) case *Array: x.Foreach(func(t *Term) { vis.Walk(t) }) case Set: xSlice := x.Slice() for i := range xSlice { vis.Walk(xSlice[i]) } case *ArrayComprehension: vis.Walk(x.Term) vis.Walk(x.Body) case *ObjectComprehension: vis.Walk(x.Key) vis.Walk(x.Value) vis.Walk(x.Body) case *SetComprehension: vis.Walk(x.Term) vis.Walk(x.Body) case Call: for i := range x { vis.Walk(x[i]) } case *Every: if x.Key != nil { vis.Walk(x.Key) } vis.Walk(x.Value) vis.Walk(x.Domain) vis.Walk(x.Body) case *SomeDecl: for i := range x.Symbols { vis.Walk(x.Symbols[i]) } } } // NewVarVisitor returns a new [VarVisitor] object. func NewVarVisitor() *VarVisitor { return &VarVisitor{ vars: NewVarSet(), } } // ClearOrNewVarVisitor clears a non-nil [VarVisitor] or returns a new one. func ClearOrNewVarVisitor(vis *VarVisitor) *VarVisitor { if vis == nil { return NewVarVisitor() } return vis.Clear() } // ClearOrNew resets the visitor to its initial state, or returns a new one if nil. // // Deprecated: use [ClearOrNewVarVisitor] instead. func (vis *VarVisitor) ClearOrNew() *VarVisitor { return ClearOrNewVarVisitor(vis) } // Clear resets the visitor to its initial state, and returns it for chaining. func (vis *VarVisitor) Clear() *VarVisitor { vis.params = VarVisitorParams{} clear(vis.vars) return vis } // WithParams sets the parameters in params on vis. func (vis *VarVisitor) WithParams(params VarVisitorParams) *VarVisitor { vis.params = params return vis } // Add adds a variable v to the visitor's set of variables. func (vis *VarVisitor) Add(v Var) { if vis.vars == nil { vis.vars = NewVarSet(v) } else { vis.vars.Add(v) } } // Vars returns a [VarSet] that contains collected vars. func (vis *VarVisitor) Vars() VarSet { return vis.vars } // visit determines if the VarVisitor will recurse into x: if it returns `true`, // the visitor will _skip_ that branch of the AST func (vis *VarVisitor) visit(v any) bool { if vis.params.SkipObjectKeys { if o, ok := v.(Object); ok { o.Foreach(func(_, v *Term) { vis.Walk(v) }) return true } } if vis.params.SkipRefHead { if r, ok := v.(Ref); ok { rSlice := r[1:] for i := range rSlice { vis.Walk(rSlice[i]) } return true } } if vis.params.SkipClosures { switch v := v.(type) { case *ArrayComprehension, *ObjectComprehension, *SetComprehension, *TemplateString: return true case *Expr: if ev, ok := v.Terms.(*Every); ok { vis.Walk(ev.Domain) // We're _not_ walking ev.Body -- that's the closure here return true } } } if vis.params.SkipWithTarget { if v, ok := v.(*With); ok { vis.Walk(v.Value) return true } } if vis.params.SkipSets { if _, ok := v.(Set); ok { return true } } if vis.params.SkipRefCallHead { switch v := v.(type) { case *Expr: if terms, ok := v.Terms.([]*Term); ok { termSlice := terms[0].Value.(Ref)[1:] for i := range termSlice { vis.Walk(termSlice[i]) } for i := 1; i < len(terms); i++ { vis.Walk(terms[i]) } for i := range v.With { vis.Walk(v.With[i]) } return true } case Call: operator := v[0].Value.(Ref) for i := 1; i < len(operator); i++ { vis.Walk(operator[i]) } for i := 1; i < len(v); i++ { vis.Walk(v[i]) } return true case *With: if ref, ok := v.Target.Value.(Ref); ok { refSlice := ref[1:] for i := range refSlice { vis.Walk(refSlice[i]) } } if ref, ok := v.Value.Value.(Ref); ok { refSlice := ref[1:] for i := range refSlice { vis.Walk(refSlice[i]) } } else { vis.Walk(v.Value) } return true } } if vis.params.SkipTemplateStrings { if _, ok := v.(*TemplateString); ok { return true } } if v, ok := v.(Var); ok { vis.Add(v) return true } return false } // Walk iterates the AST by calling the function f on the [VarVisitor] before recursing. // Contrary to the deprecated [Walk] function, this does not require allocating the visitor from heap. func (vis *VarVisitor) Walk(x any) { if vis.visit(x) { return } switch x := x.(type) { case *Module: for i := range x.Rules { vis.Walk(x.Rules[i]) } case *Package: vis.WalkRef(x.Path) case *Import: vis.Walk(x.Path) if x.Alias != "" { vis.Add(x.Alias) } case *Rule: vis.Walk(x.Head) vis.WalkBody(x.Body) if x.Else != nil { vis.Walk(x.Else) } case *Head: if len(x.Reference) > 0 { vis.WalkRef(x.Reference) } else { vis.Add(x.Name) if x.Key != nil { vis.Walk(x.Key) } } vis.WalkArgs(x.Args) if x.Value != nil { vis.Walk(x.Value) } case Body: vis.WalkBody(x) case Args: vis.WalkArgs(x) case *Expr: switch ts := x.Terms.(type) { case *Term, *SomeDecl, *Every: vis.Walk(ts) case []*Term: for i := range ts { vis.Walk(ts[i].Value) } } for i := range x.With { vis.Walk(x.With[i]) } case *With: vis.Walk(x.Target.Value) vis.Walk(x.Value.Value) case *Term: vis.Walk(x.Value) if vVar, ok := x.Value.(Var); ok { vis.vars.AddLocation(vVar, x.Location) } case Ref: for i := range x { vis.Walk(x[i].Value) } case *object: x.Foreach(func(k, v *Term) { vis.Walk(k) vis.Walk(v) }) case *Array: x.Foreach(func(t *Term) { vis.Walk(t) }) case Set: xSlice := x.Slice() for i := range xSlice { vis.Walk(xSlice[i]) } case *ArrayComprehension: vis.Walk(x.Term.Value) vis.WalkBody(x.Body) case *ObjectComprehension: vis.Walk(x.Key.Value) vis.Walk(x.Value.Value) vis.WalkBody(x.Body) case *SetComprehension: vis.Walk(x.Term.Value) vis.WalkBody(x.Body) case Call: for i := range x { vis.Walk(x[i].Value) } case *Every: if x.Key != nil { vis.Walk(x.Key.Value) } vis.Walk(x.Value) vis.Walk(x.Domain) vis.WalkBody(x.Body) case *SomeDecl: for i := range x.Symbols { vis.Walk(x.Symbols[i]) } case *TemplateString: for i := range x.Parts { vis.Walk(x.Parts[i]) } } } // WalkArgs exists only to avoid the allocation cost of boxing Args to `any` in the VarVisitor. // Use it when you know beforehand that the type to walk is Args. func (vis *VarVisitor) WalkArgs(x Args) { for i := range x { vis.Walk(x[i].Value) } } // WalkRef exists only to avoid the allocation cost of boxing Ref to `any` in the VarVisitor. // Use it when you know beforehand that the type to walk is a Ref. func (vis *VarVisitor) WalkRef(ref Ref) { if vis.params.SkipRefHead { ref = ref[1:] } for _, term := range ref { vis.Walk(term.Value) if vVar, ok := term.Value.(Var); ok { vis.vars.AddLocation(vVar, term.Location) } } } // WalkBody exists only to avoid the allocation cost of boxing Body to `any` in the VarVisitor. // Use it when you know beforehand that the type to walk is a Body. func (vis *VarVisitor) WalkBody(body Body) { for _, expr := range body { vis.Walk(expr) } }