// Copyright 2010 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.

// This file contains printing support for ASTs.

package ast

import (
	
	
	
	
	
)

// A FieldFilter may be provided to [Fprint] to control the output.
type FieldFilter func(name string, value reflect.Value) bool

// NotNilFilter is a [FieldFilter] that returns true for field values
// that are not nil; it returns false otherwise.
func ( string,  reflect.Value) bool {
	switch .Kind() {
	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
		return !.IsNil()
	}
	return true
}

// Fprint prints the (sub-)tree starting at AST node x to w.
// If fset != nil, position information is interpreted relative
// to that file set. Otherwise positions are printed as integer
// values (file set specific offsets).
//
// A non-nil [FieldFilter] f may be provided to control the output:
// struct fields for which f(fieldname, fieldvalue) is true are
// printed; all others are filtered from the output. Unexported
// struct fields are never printed.
func ( io.Writer,  *token.FileSet,  any,  FieldFilter) error {
	return fprint(, , , )
}

func fprint( io.Writer,  *token.FileSet,  any,  FieldFilter) ( error) {
	// setup printer
	 := printer{
		output: ,
		fset:   ,
		filter: ,
		ptrmap: make(map[any]int),
		last:   '\n', // force printing of line number on first line
	}

	// install error handler
	defer func() {
		if  := recover();  != nil {
			 = .(localError).err // re-panics if it's not a localError
		}
	}()

	// print x
	if  == nil {
		.printf("nil\n")
		return
	}
	.print(reflect.ValueOf())
	.printf("\n")

	return
}

// Print prints x to standard output, skipping nil fields.
// Print(fset, x) is the same as Fprint(os.Stdout, fset, x, NotNilFilter).
func ( *token.FileSet,  any) error {
	return Fprint(os.Stdout, , , NotNilFilter)
}

type printer struct {
	output io.Writer
	fset   *token.FileSet
	filter FieldFilter
	ptrmap map[any]int // *T -> line number
	indent int         // current indentation level
	last   byte        // the last byte processed by Write
	line   int         // current line number
}

var indent = []byte(".  ")

func ( *printer) ( []byte) ( int,  error) {
	var  int
	for ,  := range  {
		// invariant: data[0:n] has been written
		if  == '\n' {
			,  = .output.Write([ : +1])
			 += 
			if  != nil {
				return
			}
			.line++
		} else if .last == '\n' {
			_,  = fmt.Fprintf(.output, "%6d  ", .line)
			if  != nil {
				return
			}
			for  := .indent;  > 0; -- {
				_,  = .output.Write(indent)
				if  != nil {
					return
				}
			}
		}
		.last = 
	}
	if len() >  {
		,  = .output.Write([:])
		 += 
	}
	return
}

// localError wraps locally caught errors so we can distinguish
// them from genuine panics which we don't want to return as errors.
type localError struct {
	err error
}

// printf is a convenience wrapper that takes care of print errors.
func ( *printer) ( string,  ...any) {
	if ,  := fmt.Fprintf(, , ...);  != nil {
		panic(localError{})
	}
}

// Implementation note: Print is written for AST nodes but could be
// used to print arbitrary data structures; such a version should
// probably be in a different package.
//
// Note: This code detects (some) cycles created via pointers but
// not cycles that are created via slices or maps containing the
// same slice or map. Code for general data structures probably
// should catch those as well.

func ( *printer) ( reflect.Value) {
	if !NotNilFilter("", ) {
		.printf("nil")
		return
	}

	switch .Kind() {
	case reflect.Interface:
		.(.Elem())

	case reflect.Map:
		.printf("%s (len = %d) {", .Type(), .Len())
		if .Len() > 0 {
			.indent++
			.printf("\n")
			for ,  := range .MapKeys() {
				.()
				.printf(": ")
				.(.MapIndex())
				.printf("\n")
			}
			.indent--
		}
		.printf("}")

	case reflect.Pointer:
		.printf("*")
		// type-checked ASTs may contain cycles - use ptrmap
		// to keep track of objects that have been printed
		// already and print the respective line number instead
		 := .Interface()
		if ,  := .ptrmap[];  {
			.printf("(obj @ %d)", )
		} else {
			.ptrmap[] = .line
			.(.Elem())
		}

	case reflect.Array:
		.printf("%s {", .Type())
		if .Len() > 0 {
			.indent++
			.printf("\n")
			for ,  := 0, .Len();  < ; ++ {
				.printf("%d: ", )
				.(.Index())
				.printf("\n")
			}
			.indent--
		}
		.printf("}")

	case reflect.Slice:
		if ,  := .Interface().([]byte);  {
			.printf("%#q", )
			return
		}
		.printf("%s (len = %d) {", .Type(), .Len())
		if .Len() > 0 {
			.indent++
			.printf("\n")
			for ,  := 0, .Len();  < ; ++ {
				.printf("%d: ", )
				.(.Index())
				.printf("\n")
			}
			.indent--
		}
		.printf("}")

	case reflect.Struct:
		 := .Type()
		.printf("%s {", )
		.indent++
		 := true
		for ,  := 0, .NumField();  < ; ++ {
			// exclude non-exported fields because their
			// values cannot be accessed via reflection
			if  := .Field().Name; IsExported() {
				 := .Field()
				if .filter == nil || .filter(, ) {
					if  {
						.printf("\n")
						 = false
					}
					.printf("%s: ", )
					.()
					.printf("\n")
				}
			}
		}
		.indent--
		.printf("}")

	default:
		 := .Interface()
		switch v := .(type) {
		case string:
			// print strings in quotes
			.printf("%q", )
			return
		case token.Pos:
			// position values can be printed nicely if we have a file set
			if .fset != nil {
				.printf("%s", .fset.Position())
				return
			}
		}
		// default
		.printf("%v", )
	}
}