blob: a23fce454de3568af445ad527fe9de8dba886b45 [file] [log] [blame]
// Copyright 2018 The CUE Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ast
import (
"fmt"
"cuelang.org/go/cue/token"
)
// Walk traverses an AST in depth-first order: It starts by calling f(node);
// node must not be nil. If before returns true, Walk invokes f recursively for
// each of the non-nil children of node, followed by a call of after. Both
// functions may be nil. If before is nil, it is assumed to always return true.
//
func Walk(node Node, before func(Node) bool, after func(Node)) {
walk(&inspector{before: before, after: after}, node)
}
// A visitor's before method is invoked for each node encountered by Walk.
// If the result visitor w is true, Walk visits each of the children
// of node with the visitor w, followed by a call of w.After.
type visitor interface {
Before(node Node) (w visitor)
After(node Node)
}
// Helper functions for common node lists. They may be empty.
func walkExprList(v visitor, list []Expr) {
for _, x := range list {
walk(v, x)
}
}
func walkDeclList(v visitor, list []Decl) {
for _, x := range list {
walk(v, x)
}
}
// walk traverses an AST in depth-first order: It starts by calling
// v.Visit(node); node must not be nil. If the visitor w returned by
// v.Visit(node) is not nil, walk is invoked recursively with visitor
// w for each of the non-nil children of node, followed by a call of
// w.Visit(nil).
//
func walk(v visitor, node Node) {
if v = v.Before(node); v == nil {
return
}
// TODO: record the comment groups and interleave with the values like for
// parsing and printing?
for _, c := range Comments(node) {
walk(v, c)
}
// walk children
// (the order of the cases matches the order
// of the corresponding node types in go)
switch n := node.(type) {
// Comments and fields
case *Comment:
// nothing to do
case *CommentGroup:
for _, c := range n.List {
walk(v, c)
}
case *Attribute:
// nothing to do
case *Field:
walk(v, n.Label)
if n.Value != nil {
walk(v, n.Value)
}
for _, a := range n.Attrs {
walk(v, a)
}
case *StructLit:
walkDeclList(v, n.Elts)
// Expressions
case *BottomLit, *BadExpr, *Ident, *BasicLit:
// nothing to do
case *Interpolation:
for _, e := range n.Elts {
walk(v, e)
}
case *ListLit:
walkExprList(v, n.Elts)
case *Ellipsis:
if n.Type != nil {
walk(v, n.Type)
}
case *ParenExpr:
walk(v, n.X)
case *SelectorExpr:
walk(v, n.X)
walk(v, n.Sel)
case *IndexExpr:
walk(v, n.X)
walk(v, n.Index)
case *SliceExpr:
walk(v, n.X)
if n.Low != nil {
walk(v, n.Low)
}
if n.High != nil {
walk(v, n.High)
}
case *CallExpr:
walk(v, n.Fun)
walkExprList(v, n.Args)
case *UnaryExpr:
walk(v, n.X)
case *BinaryExpr:
walk(v, n.X)
walk(v, n.Y)
// Declarations
case *ImportSpec:
if n.Name != nil {
walk(v, n.Name)
}
walk(v, n.Path)
case *BadDecl:
// nothing to do
case *ImportDecl:
for _, s := range n.Specs {
walk(v, s)
}
case *EmbedDecl:
walk(v, n.Expr)
case *LetClause:
walk(v, n.Ident)
walk(v, n.Expr)
case *Alias:
walk(v, n.Ident)
walk(v, n.Expr)
case *Comprehension:
for _, c := range n.Clauses {
walk(v, c)
}
walk(v, n.Value)
// Files and packages
case *File:
walkDeclList(v, n.Decls)
case *Package:
walk(v, n.Name)
case *ForClause:
if n.Key != nil {
walk(v, n.Key)
}
walk(v, n.Value)
walk(v, n.Source)
case *IfClause:
walk(v, n.Condition)
default:
panic(fmt.Sprintf("Walk: unexpected node type %T", n))
}
v.After(node)
}
type inspector struct {
before func(Node) bool
after func(Node)
commentStack []commentFrame
current commentFrame
}
type commentFrame struct {
cg []*CommentGroup
pos int8
}
func (f *inspector) Before(node Node) visitor {
if f.before == nil || f.before(node) {
f.commentStack = append(f.commentStack, f.current)
f.current = commentFrame{cg: Comments(node)}
f.visitComments(f.current.pos)
return f
}
return nil
}
func (f *inspector) After(node Node) {
f.visitComments(127)
p := len(f.commentStack) - 1
f.current = f.commentStack[p]
f.commentStack = f.commentStack[:p]
f.current.pos++
if f.after != nil {
f.after(node)
}
}
func (f *inspector) Token(t token.Token) {
f.current.pos++
}
func (f *inspector) setPos(i int8) {
f.current.pos = i
}
func (f *inspector) visitComments(pos int8) {
c := &f.current
for ; len(c.cg) > 0; c.cg = c.cg[1:] {
cg := c.cg[0]
if cg.Position == pos {
continue
}
if f.before == nil || f.before(cg) {
for _, c := range cg.List {
if f.before == nil || f.before(c) {
if f.after != nil {
f.after(c)
}
}
}
if f.after != nil {
f.after(cg)
}
}
}
}