// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package ast

import "fmt"

// A Visitor's Visit method is invoked for each node encountered by Walk.
// If the result visitor w is not nil, Walk visits each of the children
// of node with the visitor w, followed by a call of w.Visit(nil).
type Visitor interface {
	Visit(node interface{}) (w Visitor)
}


func walkIdent(v Visitor, x *Ident) {
	if x != nil {
		Walk(v, x)
	}
}


func walkCommentGroup(v Visitor, g *CommentGroup) {
	if g != nil {
		Walk(v, g)
	}
}


func walkBlockStmt(v Visitor, b *BlockStmt) {
	if b != nil {
		Walk(v, b)
	}
}


// Walk traverses an AST in depth-first order: If node != nil, it
// invokes v.Visit(node). If the visitor w returned by v.Visit(node) is
// not nil, Walk visits each of the children of node with the visitor w,
// followed by a call of w.Visit(nil).
//
// Walk may be called with any of the named ast node types. It also
// accepts arguments of type []*Field, []*Ident, []Expr, []Stmt and []Decl;
// the respective children are the slice elements.
//
func Walk(v Visitor, node interface{}) {
	if node == nil {
		return
	}
	if v = v.Visit(node); v == nil {
		return
	}

	// walk children
	// (the order of the cases matches the order
	// of the corresponding declaration in ast.go)
	switch n := node.(type) {
	// Comments and fields
	case *Comment:
		// nothing to do

	case *CommentGroup:
		for _, c := range n.List {
			Walk(v, c)
		}

	case *Field:
		walkCommentGroup(v, n.Doc)
		Walk(v, n.Names)
		Walk(v, n.Type)
		Walk(v, n.Tag)
		walkCommentGroup(v, n.Comment)

	case *FieldList:
		for _, f := range n.List {
			Walk(v, f)
		}

	// Expressions
	case *BadExpr, *Ident, *Ellipsis, *BasicLit:
		// nothing to do

	case *FuncLit:
		if n != nil {
			Walk(v, n.Type)
		}
		walkBlockStmt(v, n.Body)

	case *CompositeLit:
		Walk(v, n.Type)
		Walk(v, n.Elts)

	case *ParenExpr:
		Walk(v, n.X)

	case *SelectorExpr:
		Walk(v, n.X)
		walkIdent(v, n.Sel)

	case *IndexExpr:
		Walk(v, n.X)
		Walk(v, n.Index)

	case *SliceExpr:
		Walk(v, n.X)
		Walk(v, n.Index)
		Walk(v, n.End)

	case *TypeAssertExpr:
		Walk(v, n.X)
		Walk(v, n.Type)

	case *CallExpr:
		Walk(v, n.Fun)
		Walk(v, n.Args)

	case *StarExpr:
		Walk(v, n.X)

	case *UnaryExpr:
		Walk(v, n.X)

	case *BinaryExpr:
		Walk(v, n.X)
		Walk(v, n.Y)

	case *KeyValueExpr:
		Walk(v, n.Key)
		Walk(v, n.Value)

	// Types
	case *ArrayType:
		Walk(v, n.Len)
		Walk(v, n.Elt)

	case *StructType:
		Walk(v, n.Fields)

	case *FuncType:
		Walk(v, n.Params)
		if n.Results != nil {
			Walk(v, n.Results)
		}

	case *InterfaceType:
		Walk(v, n.Methods)

	case *MapType:
		Walk(v, n.Key)
		Walk(v, n.Value)

	case *ChanType:
		Walk(v, n.Value)

	// Statements
	case *BadStmt:
		// nothing to do

	case *DeclStmt:
		Walk(v, n.Decl)

	case *EmptyStmt:
		// nothing to do

	case *LabeledStmt:
		walkIdent(v, n.Label)
		Walk(v, n.Stmt)

	case *ExprStmt:
		Walk(v, n.X)

	case *IncDecStmt:
		Walk(v, n.X)

	case *AssignStmt:
		Walk(v, n.Lhs)
		Walk(v, n.Rhs)

	case *GoStmt:
		if n.Call != nil {
			Walk(v, n.Call)
		}

	case *DeferStmt:
		if n.Call != nil {
			Walk(v, n.Call)
		}

	case *ReturnStmt:
		Walk(v, n.Results)

	case *BranchStmt:
		walkIdent(v, n.Label)

	case *BlockStmt:
		Walk(v, n.List)

	case *IfStmt:
		Walk(v, n.Init)
		Walk(v, n.Cond)
		walkBlockStmt(v, n.Body)
		Walk(v, n.Else)

	case *CaseClause:
		Walk(v, n.Values)
		Walk(v, n.Body)

	case *SwitchStmt:
		Walk(v, n.Init)
		Walk(v, n.Tag)
		walkBlockStmt(v, n.Body)

	case *TypeCaseClause:
		Walk(v, n.Types)
		Walk(v, n.Body)

	case *TypeSwitchStmt:
		Walk(v, n.Init)
		Walk(v, n.Assign)
		walkBlockStmt(v, n.Body)

	case *CommClause:
		Walk(v, n.Lhs)
		Walk(v, n.Rhs)
		Walk(v, n.Body)

	case *SelectStmt:
		walkBlockStmt(v, n.Body)

	case *ForStmt:
		Walk(v, n.Init)
		Walk(v, n.Cond)
		Walk(v, n.Post)
		walkBlockStmt(v, n.Body)

	case *RangeStmt:
		Walk(v, n.Key)
		Walk(v, n.Value)
		Walk(v, n.X)
		walkBlockStmt(v, n.Body)

	// Declarations
	case *ImportSpec:
		walkCommentGroup(v, n.Doc)
		walkIdent(v, n.Name)
		Walk(v, n.Path)
		walkCommentGroup(v, n.Comment)

	case *ValueSpec:
		walkCommentGroup(v, n.Doc)
		Walk(v, n.Names)
		Walk(v, n.Type)
		Walk(v, n.Values)
		walkCommentGroup(v, n.Comment)

	case *TypeSpec:
		walkCommentGroup(v, n.Doc)
		walkIdent(v, n.Name)
		Walk(v, n.Type)
		walkCommentGroup(v, n.Comment)

	case *BadDecl:
		// nothing to do

	case *GenDecl:
		walkCommentGroup(v, n.Doc)
		for _, s := range n.Specs {
			Walk(v, s)
		}

	case *FuncDecl:
		walkCommentGroup(v, n.Doc)
		if n.Recv != nil {
			Walk(v, n.Recv)
		}
		walkIdent(v, n.Name)
		if n.Type != nil {
			Walk(v, n.Type)
		}
		walkBlockStmt(v, n.Body)

	// Files and packages
	case *File:
		walkCommentGroup(v, n.Doc)
		walkIdent(v, n.Name)
		Walk(v, n.Decls)
		for _, g := range n.Comments {
			Walk(v, g)
		}

	case *Package:
		for _, f := range n.Files {
			Walk(v, f)
		}

	case []*Ident:
		for _, x := range n {
			Walk(v, x)
		}

	case []Expr:
		for _, x := range n {
			Walk(v, x)
		}

	case []Stmt:
		for _, x := range n {
			Walk(v, x)
		}

	case []Decl:
		for _, x := range n {
			Walk(v, x)
		}

	default:
		fmt.Printf("ast.Walk: unexpected type %T", n)
		panic("ast.Walk")
	}

	v.Visit(nil)
}
