// Copyright 2013 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 astutil// This file defines utilities for working with source positions.import ()// PathEnclosingInterval returns the node that encloses the source// interval [start, end), and all its ancestors up to the AST root.//// The definition of "enclosing" used by this function considers// additional whitespace abutting a node to be enclosed by it.// In this example://// z := x + y // add them// <-A->// <----B----->//// the ast.BinaryExpr(+) node is considered to enclose interval B// even though its [Pos()..End()) is actually only interval A.// This behaviour makes user interfaces more tolerant of imperfect// input.//// This function treats tokens as nodes, though they are not included// in the result. e.g. PathEnclosingInterval("+") returns the// enclosing ast.BinaryExpr("x + y").//// If start==end, the 1-char interval following start is used instead.//// The 'exact' result is true if the interval contains only path[0]// and perhaps some adjacent whitespace. It is false if the interval// overlaps multiple children of path[0], or if it contains only// interior whitespace of path[0].// In this example://// z := x + y // add them// <--C--> <---E-->// ^// D//// intervals C, D and E are inexact. C is contained by the// z-assignment statement, because it spans three of its children (:=,// x, +). So too is the 1-char interval D, because it contains only// interior whitespace of the assignment. E is considered interior// whitespace of the BlockStmt containing the assignment.//// The resulting path is never empty; it always contains at least the// 'root' *ast.File. Ideally PathEnclosingInterval would reject// intervals that lie wholly or partially outside the range of the// file, but unfortunately ast.File records only the token.Pos of// the 'package' keyword, but not of the start of the file itself.func ( *ast.File, , token.Pos) ( []ast.Node, bool) {// fmt.Printf("EnclosingInterval %d %d\n", start, end) // debugging// Precondition: node.[Pos..End) and adjoining whitespace contain [start, end).varfunc( ast.Node) bool = func( ast.Node) bool { = append(, ) := .Pos() := .End()// fmt.Printf("visit(%T, %d, %d)\n", node, nodePos, nodeEnd) // debugging// Intersect [start, end) with interval of node.if < { = }if > { = }// Find sole child that contains [start, end). := childrenOf() := len()for , := range {// [childPos, childEnd) is unaugmented interval of child. := .Pos() := .End()// [augPos, augEnd) is whitespace-augmented interval of child. := := if > 0 { = [-1].End() // start of preceding whitespace }if < -1 { := [+1].Pos()// Does [start, end) lie between child and next child?if >= && <= {returnfalse// inexact match } = // end of following whitespace }// fmt.Printf("\tchild %d: [%d..%d)\tcontains interval [%d..%d)?\n", // i, augPos, augEnd, start, end) // debugging// Does augmented child strictly contain [start, end)?if <= && <= {ifis[tokenNode]() {returntrue }// childrenOf elides the FuncType node beneath FuncDecl. // Add it back here for TypeParams, Params, Results, // all FieldLists). But we don't add it back for the "func" token // even though it is the tree at FuncDecl.Type.Func.if , := .(*ast.FuncDecl); {if , := .(*ast.FieldList); && != .Recv { = append(, .Type) } }return () }// Does [start, end) overlap multiple children? // i.e. left-augmented child contains start // but LR-augmented child does not contain end.if < && > {break } }// No single child contained [start, end), // so node is the result. Is it exact?// (It's tempting to put this condition before the // child loop, but it gives the wrong result in the // case where a node (e.g. ExprStmt) and its sole // child have equal intervals.)if == && == {returntrue// exact match }returnfalse// inexact: overlaps multiple children }// Ensure [start,end) is nondecreasing.if > { , = , }if < .End() && > .Pos() {if == { = + 1// empty interval => interval of size 1 } = ()// Reverse the path:for , := 0, len(); < /2; ++ { [], [-1-] = [-1-], [] } } else {// Selection lies within whitespace preceding the // first (or following the last) declaration in the file. // The result nonetheless always includes the ast.File. = append(, ) }return}// tokenNode is a dummy implementation of ast.Node for a single token.// They are used transiently by PathEnclosingInterval but never escape// this package.type tokenNode struct { pos token.Pos end token.Pos}func ( tokenNode) () token.Pos {return .pos}func ( tokenNode) () token.Pos {return .end}func tok( token.Pos, int) ast.Node {returntokenNode{, + token.Pos()}}// childrenOf returns the direct non-nil children of ast.Node n.// It may include fake ast.Node implementations for bare tokens.// it is not safe to call (e.g.) ast.Walk on such nodes.func childrenOf( ast.Node) []ast.Node {var []ast.Node// First add nodes for all true subtrees.ast.Inspect(, func( ast.Node) bool {if == { // push nreturntrue// recur }if != nil { // push child = append(, ) }returnfalse// no recursion })// TODO(adonovan): be more careful about missing (!Pos.Valid) // tokens in trees produced from invalid input.// Then add fake Nodes for bare tokens.switch n := .(type) {case *ast.ArrayType: = append(,tok(.Lbrack, len("[")),tok(.Elt.End(), len("]")))case *ast.AssignStmt: = append(,tok(.TokPos, len(.Tok.String())))case *ast.BasicLit: = append(,tok(.ValuePos, len(.Value)))case *ast.BinaryExpr: = append(, tok(.OpPos, len(.Op.String())))case *ast.BlockStmt:if .Lbrace.IsValid() { = append(, tok(.Lbrace, len("{"))) }if .Rbrace.IsValid() { = append(, tok(.Rbrace, len("}"))) }case *ast.BranchStmt: = append(,tok(.TokPos, len(.Tok.String())))case *ast.CallExpr: = append(,tok(.Lparen, len("(")),tok(.Rparen, len(")")))if .Ellipsis != 0 { = append(, tok(.Ellipsis, len("..."))) }case *ast.CaseClause:if .List == nil { = append(,tok(.Case, len("default"))) } else { = append(,tok(.Case, len("case"))) } = append(, tok(.Colon, len(":")))case *ast.ChanType:switch .Dir {caseast.RECV: = append(, tok(.Begin, len("<-chan")))caseast.SEND: = append(, tok(.Begin, len("chan<-")))caseast.RECV | ast.SEND: = append(, tok(.Begin, len("chan"))) }case *ast.CommClause:if .Comm == nil { = append(,tok(.Case, len("default"))) } else { = append(,tok(.Case, len("case"))) } = append(, tok(.Colon, len(":")))case *ast.Comment:// nopcase *ast.CommentGroup:// nopcase *ast.CompositeLit: = append(,tok(.Lbrace, len("{")),tok(.Rbrace, len("{")))case *ast.DeclStmt:// nopcase *ast.DeferStmt: = append(,tok(.Defer, len("defer")))case *ast.Ellipsis: = append(,tok(.Ellipsis, len("...")))case *ast.EmptyStmt:// nopcase *ast.ExprStmt:// nopcase *ast.Field:// TODO(adonovan): Field.{Doc,Comment,Tag}?case *ast.FieldList:if .Opening.IsValid() { = append(, tok(.Opening, len("("))) }if .Closing.IsValid() { = append(, tok(.Closing, len(")"))) }case *ast.File:// TODO test: Doc = append(,tok(.Package, len("package")))case *ast.ForStmt: = append(,tok(.For, len("for")))case *ast.FuncDecl:// TODO(adonovan): FuncDecl.Comment?// Uniquely, FuncDecl breaks the invariant that // preorder traversal yields tokens in lexical order: // in fact, FuncDecl.Recv precedes FuncDecl.Type.Func. // // As a workaround, we inline the case for FuncType // here and order things correctly. // We also need to insert the elided FuncType just // before the 'visit' recursion. // = nil// discard ast.Walk(FuncDecl) info subtrees = append(, tok(.Type.Func, len("func")))if .Recv != nil { = append(, .Recv) } = append(, .Name)if := .Type.TypeParams; != nil { = append(, ) }if .Type.Params != nil { = append(, .Type.Params) }if .Type.Results != nil { = append(, .Type.Results) }if .Body != nil { = append(, .Body) }case *ast.FuncLit:// nopcase *ast.FuncType:if .Func != 0 { = append(,tok(.Func, len("func"))) }case *ast.GenDecl: = append(,tok(.TokPos, len(.Tok.String())))if .Lparen != 0 { = append(,tok(.Lparen, len("(")),tok(.Rparen, len(")"))) }case *ast.GoStmt: = append(,tok(.Go, len("go")))case *ast.Ident: = append(,tok(.NamePos, len(.Name)))case *ast.IfStmt: = append(,tok(.If, len("if")))case *ast.ImportSpec:// TODO(adonovan): ImportSpec.{Doc,EndPos}?case *ast.IncDecStmt: = append(,tok(.TokPos, len(.Tok.String())))case *ast.IndexExpr: = append(,tok(.Lbrack, len("[")),tok(.Rbrack, len("]")))case *ast.IndexListExpr: = append(,tok(.Lbrack, len("[")),tok(.Rbrack, len("]")))case *ast.InterfaceType: = append(,tok(.Interface, len("interface")))case *ast.KeyValueExpr: = append(,tok(.Colon, len(":")))case *ast.LabeledStmt: = append(,tok(.Colon, len(":")))case *ast.MapType: = append(,tok(.Map, len("map")))case *ast.ParenExpr: = append(,tok(.Lparen, len("(")),tok(.Rparen, len(")")))case *ast.RangeStmt: = append(,tok(.For, len("for")),tok(.TokPos, len(.Tok.String())))case *ast.ReturnStmt: = append(,tok(.Return, len("return")))case *ast.SelectStmt: = append(,tok(.Select, len("select")))case *ast.SelectorExpr:// nopcase *ast.SendStmt: = append(,tok(.Arrow, len("<-")))case *ast.SliceExpr: = append(,tok(.Lbrack, len("[")),tok(.Rbrack, len("]")))case *ast.StarExpr: = append(, tok(.Star, len("*")))case *ast.StructType: = append(, tok(.Struct, len("struct")))case *ast.SwitchStmt: = append(, tok(.Switch, len("switch")))case *ast.TypeAssertExpr: = append(,tok(.Lparen-1, len(".")),tok(.Lparen, len("(")),tok(.Rparen, len(")")))case *ast.TypeSpec:// TODO(adonovan): TypeSpec.{Doc,Comment}?case *ast.TypeSwitchStmt: = append(, tok(.Switch, len("switch")))case *ast.UnaryExpr: = append(, tok(.OpPos, len(.Op.String())))case *ast.ValueSpec:// TODO(adonovan): ValueSpec.{Doc,Comment}?case *ast.BadDecl, *ast.BadExpr, *ast.BadStmt:// nop }// TODO(adonovan): opt: merge the logic of ast.Inspect() into // the switch above so we can make interleaved callbacks for // both Nodes and Tokens in the right order and avoid the need // to sort.sort.Sort(byPos())return}type byPos []ast.Nodefunc ( byPos) () int {returnlen()}func ( byPos) (, int) bool {return [].Pos() < [].Pos()}func ( byPos) (, int) { [], [] = [], []}// NodeDescription returns a description of the concrete type of n suitable// for a user interface.//// TODO(adonovan): in some cases (e.g. Field, FieldList, Ident,// StarExpr) we could be much more specific given the path to the AST// root. Perhaps we should do that.func ( ast.Node) string {switch n := .(type) {case *ast.ArrayType:return"array type"case *ast.AssignStmt:return"assignment"case *ast.BadDecl:return"bad declaration"case *ast.BadExpr:return"bad expression"case *ast.BadStmt:return"bad statement"case *ast.BasicLit:return"basic literal"case *ast.BinaryExpr:returnfmt.Sprintf("binary %s operation", .Op)case *ast.BlockStmt:return"block"case *ast.BranchStmt:switch .Tok {casetoken.BREAK:return"break statement"casetoken.CONTINUE:return"continue statement"casetoken.GOTO:return"goto statement"casetoken.FALLTHROUGH:return"fall-through statement" }case *ast.CallExpr:iflen(.Args) == 1 && !.Ellipsis.IsValid() {return"function call (or conversion)" }return"function call"case *ast.CaseClause:return"case clause"case *ast.ChanType:return"channel type"case *ast.CommClause:return"communication clause"case *ast.Comment:return"comment"case *ast.CommentGroup:return"comment group"case *ast.CompositeLit:return"composite literal"case *ast.DeclStmt:return (.Decl) + " statement"case *ast.DeferStmt:return"defer statement"case *ast.Ellipsis:return"ellipsis"case *ast.EmptyStmt:return"empty statement"case *ast.ExprStmt:return"expression statement"case *ast.Field:// Can be any of these: // struct {x, y int} -- struct field(s) // struct {T} -- anon struct field // interface {I} -- interface embedding // interface {f()} -- interface method // func (A) func(B) C -- receiver, param(s), result(s)return"field/method/parameter"case *ast.FieldList:return"field/method/parameter list"case *ast.File:return"source file"case *ast.ForStmt:return"for loop"case *ast.FuncDecl:return"function declaration"case *ast.FuncLit:return"function literal"case *ast.FuncType:return"function type"case *ast.GenDecl:switch .Tok {casetoken.IMPORT:return"import declaration"casetoken.CONST:return"constant declaration"casetoken.TYPE:return"type declaration"casetoken.VAR:return"variable declaration" }case *ast.GoStmt:return"go statement"case *ast.Ident:return"identifier"case *ast.IfStmt:return"if statement"case *ast.ImportSpec:return"import specification"case *ast.IncDecStmt:if .Tok == token.INC {return"increment statement" }return"decrement statement"case *ast.IndexExpr:return"index expression"case *ast.IndexListExpr:return"index list expression"case *ast.InterfaceType:return"interface type"case *ast.KeyValueExpr:return"key/value association"case *ast.LabeledStmt:return"statement label"case *ast.MapType:return"map type"case *ast.Package:return"package"case *ast.ParenExpr:return"parenthesized " + (.X)case *ast.RangeStmt:return"range loop"case *ast.ReturnStmt:return"return statement"case *ast.SelectStmt:return"select statement"case *ast.SelectorExpr:return"selector"case *ast.SendStmt:return"channel send"case *ast.SliceExpr:return"slice expression"case *ast.StarExpr:return"*-operation"// load/store expr or pointer typecase *ast.StructType:return"struct type"case *ast.SwitchStmt:return"switch statement"case *ast.TypeAssertExpr:return"type assertion"case *ast.TypeSpec:return"type specification"case *ast.TypeSwitchStmt:return"type switch"case *ast.UnaryExpr:returnfmt.Sprintf("unary %s operation", .Op)case *ast.ValueSpec:return"value specification" }panic(fmt.Sprintf("unexpected node type: %T", ))}func is[ any]( any) bool { , := .()return}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.