package pkglint type MkShWalker struct { Callback struct { List func(list *MkShList) AndOr func(andor *MkShAndOr) Pipeline func(pipeline *MkShPipeline) Command func(command *MkShCommand) SimpleCommand func(command *MkShSimpleCommand) CompoundCommand func(command *MkShCompoundCommand) Case func(caseClause *MkShCase) CaseItem func(caseItem *MkShCaseItem) FunctionDefinition func(funcdef *MkShFunctionDefinition) If func(ifClause *MkShIf) Loop func(loop *MkShLoop) Words func(words []*ShToken) Word func(word *ShToken) Redirects func(redirects []*MkShRedirection) Redirect func(redirect *MkShRedirection) For func(forClause *MkShFor) // For variable definition in a for loop. Varname func(varname string) } // Context[0] is the currently visited element, // Context[1] is its immediate parent element, and so on. // This is useful when the check for a CaseItem needs to look at the enclosing Case. Context []MkShWalkerPathElement } type MkShWalkerPathElement struct { // For fields that can be repeated, this is the index as seen from the parent element. // For fields that cannot be repeated, it is -1. // // For example, in the SimpleCommand "var=value cmd arg1 arg2", // there are multiple child elements of type Words. // // The first Words are the variable assignments, which have index 0. // // The command "cmd" has type Word, therefore it cannot be confused // with either of the Words lists and has index -1. // // The second Words are the arguments, which have index 1. // In this example, there are two arguments, so when visiting the // arguments individually, arg1 will have index 0 and arg2 will have index 1. // // TODO: It might be worth defining negative indexes to correspond // to the fields "Cond", "Action", "Else", etc. Index int Element interface{} } func NewMkShWalker() *MkShWalker { return &MkShWalker{} } // Walk calls the given callback for each node of the parsed shell program, // in visiting order from large to small. func (w *MkShWalker) Walk(list *MkShList) { w.walkList(-1, list) // The calls to w.push and w.pop must be balanced. assert(len(w.Context) == 0) } func (w *MkShWalker) walkList(index int, list *MkShList) { w.push(index, list) if callback := w.Callback.List; callback != nil { callback(list) } for i, andor := range list.AndOrs { w.walkAndOr(i, andor) } w.pop() } func (w *MkShWalker) walkAndOr(index int, andor *MkShAndOr) { w.push(index, andor) if callback := w.Callback.AndOr; callback != nil { callback(andor) } for i, pipeline := range andor.Pipes { w.walkPipeline(i, pipeline) } w.pop() } func (w *MkShWalker) walkPipeline(index int, pipeline *MkShPipeline) { w.push(index, pipeline) if callback := w.Callback.Pipeline; callback != nil { callback(pipeline) } for i, command := range pipeline.Cmds { w.walkCommand(i, command) } w.pop() } func (w *MkShWalker) walkCommand(index int, command *MkShCommand) { w.push(index, command) if callback := w.Callback.Command; callback != nil { callback(command) } switch { case command.Simple != nil: w.walkSimpleCommand(-1, command.Simple) case command.Compound != nil: w.walkCompoundCommand(-1, command.Compound) w.walkRedirects(command.Redirects) case command.FuncDef != nil: w.walkFunctionDefinition(-1, command.FuncDef) w.walkRedirects(command.Redirects) } w.pop() } func (w *MkShWalker) walkSimpleCommand(index int, command *MkShSimpleCommand) { w.push(index, command) if callback := w.Callback.SimpleCommand; callback != nil { callback(command) } w.walkWords(0, command.Assignments) if command.Name != nil { w.walkWord(-1, command.Name) } w.walkWords(1, command.Args) w.walkRedirects(command.Redirections) w.pop() } func (w *MkShWalker) walkCompoundCommand(index int, command *MkShCompoundCommand) { w.push(index, command) if callback := w.Callback.CompoundCommand; callback != nil { callback(command) } switch { case command.Brace != nil: w.walkList(-1, command.Brace) case command.Case != nil: w.walkCase(command.Case) case command.For != nil: w.walkFor(command.For) case command.If != nil: w.walkIf(command.If) case command.Loop != nil: w.walkLoop(command.Loop) case command.Subshell != nil: w.walkList(-1, command.Subshell) } w.pop() } func (w *MkShWalker) walkCase(caseClause *MkShCase) { w.push(-1, caseClause) if callback := w.Callback.Case; callback != nil { callback(caseClause) } w.walkWord(-1, caseClause.Word) for i, caseItem := range caseClause.Cases { w.push(i, caseItem) if callback := w.Callback.CaseItem; callback != nil { callback(caseItem) } w.walkWords(-1, caseItem.Patterns) if caseItem.Action != nil { w.walkList(-1, caseItem.Action) } w.pop() } w.pop() } func (w *MkShWalker) walkFunctionDefinition(index int, funcdef *MkShFunctionDefinition) { w.push(index, funcdef) if callback := w.Callback.FunctionDefinition; callback != nil { callback(funcdef) } w.walkCompoundCommand(-1, funcdef.Body) w.pop() } func (w *MkShWalker) walkIf(ifClause *MkShIf) { w.push(-1, ifClause) if callback := w.Callback.If; callback != nil { callback(ifClause) } // TODO: Replace these indices with proper field names; see MkShWalkerPathElement.Index. for i, cond := range ifClause.Conds { w.walkList(2*i, cond) w.walkList(2*i+1, ifClause.Actions[i]) } if ifClause.Else != nil { w.walkList(2*len(ifClause.Conds), ifClause.Else) } w.pop() } func (w *MkShWalker) walkLoop(loop *MkShLoop) { w.push(-1, loop) if callback := w.Callback.Loop; callback != nil { callback(loop) } w.walkList(0, loop.Cond) w.walkList(1, loop.Action) w.pop() } func (w *MkShWalker) walkWords(index int, words []*ShToken) { if len(words) == 0 { return } w.push(index, words) if callback := w.Callback.Words; callback != nil { callback(words) } for i, word := range words { w.walkWord(i, word) } w.pop() } func (w *MkShWalker) walkWord(index int, word *ShToken) { w.push(index, word) if callback := w.Callback.Word; callback != nil { callback(word) } w.pop() } func (w *MkShWalker) walkRedirects(redirects []*MkShRedirection) { if len(redirects) == 0 { return } w.push(-1, redirects) if callback := w.Callback.Redirects; callback != nil { callback(redirects) } for i, redirect := range redirects { w.push(i, redirect) if callback := w.Callback.Redirect; callback != nil { callback(redirect) } w.walkWord(i, redirect.Target) w.pop() } w.pop() } func (w *MkShWalker) walkFor(forClause *MkShFor) { w.push(-1, forClause) if callback := w.Callback.For; callback != nil { callback(forClause) } if callback := w.Callback.Varname; callback != nil { callback(forClause.Varname) } w.walkWords(-1, forClause.Values) w.walkList(-1, forClause.Body) w.pop() } // Current provides access to the element that the walker is currently // processing, especially its index as seen from its parent element. func (w *MkShWalker) Current() MkShWalkerPathElement { return w.Context[len(w.Context)-1] } // Parent returns an ancestor element from the currently visited path. // Parent(0) is the element that is currently visited, // Parent(1) is its direct parent, and so on. func (w *MkShWalker) Parent(steps int) interface{} { index := len(w.Context) - 1 - steps if index >= 0 { return w.Context[index].Element } return nil } func (w *MkShWalker) push(index int, element interface{}) { w.Context = append(w.Context, MkShWalkerPathElement{index, element}) } func (w *MkShWalker) pop() { w.Context = w.Context[:len(w.Context)-1] }