package ast

import "fmt"

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

// Walk traverses an AST in depth-first order: It starts by calling
// v.Visit(expr); Expression must not be nil. If the visitor w returned by
// v.Visit(expr) is not nil, Walk is invoked recursively with visitor
// w for each of the non-nil children of Expression, followed by a call of
// w.Visit(nil).
func Walk(v Visitor, expr Expression) {
	if v = v.Visit(expr); v == nil {
		return
	}

	switch expr := expr.(type) {
	case *ActionExpr:
		Walk(v, expr.Expr)
	case *AndCodeExpr:
		// Nothing to do
	case *AndExpr:
		Walk(v, expr.Expr)
	case *AnyMatcher:
		// Nothing to do
	case *CharClassMatcher:
		// Nothing to do
	case *ChoiceExpr:
		for _, e := range expr.Alternatives {
			Walk(v, e)
		}
	case *Grammar:
		for _, e := range expr.Rules {
			Walk(v, e)
		}
	case *LabeledExpr:
		Walk(v, expr.Expr)
	case *LitMatcher:
		// Nothing to do
	case *NotCodeExpr:
		// Nothing to do
	case *NotExpr:
		Walk(v, expr.Expr)
	case *OneOrMoreExpr:
		Walk(v, expr.Expr)
	case *Rule:
		Walk(v, expr.Expr)
	case *RuleRefExpr:
		// Nothing to do
	case *SeqExpr:
		for _, e := range expr.Exprs {
			Walk(v, e)
		}
	case *StateCodeExpr:
		// Nothing to do
	case *ZeroOrMoreExpr:
		Walk(v, expr.Expr)
	case *ZeroOrOneExpr:
		Walk(v, expr.Expr)
	default:
		panic(fmt.Sprintf("unknown expression type %T", expr))
	}
}

type inspector func(Expression) bool

func (f inspector) Visit(expr Expression) Visitor {
	if f(expr) {
		return f
	}
	return nil
}

// Inspect traverses an AST in depth-first order: It starts by calling
// f(expr); expr must not be nil. If f returns true, Inspect invokes f
// recursively for each of the non-nil children of expr, followed by a
// call of f(nil).
func Inspect(expr Expression, f func(Expression) bool) {
	Walk(inspector(f), expr)
}
