// Copyright (c) 2016, Daniel Martà <mvdan@mvdan.cc>// See LICENSE for licensing informationpackage syntaximport ()// Node represents a syntax tree node.typeNodeinterface {// Pos returns the position of the first character of the node. Comments // are ignored, except if the node is a *File.Pos() Pos// End returns the position of the character immediately after the node. // If the character is a newline, the line number won't cross into the // next line. Comments are ignored, except if the node is a *File.End() Pos}// File represents a shell source file.typeFilestruct { Name string Stmts []*Stmt Last []Comment}func ( *File) () Pos { returnstmtsPos(.Stmts, .Last) }func ( *File) () Pos { returnstmtsEnd(.Stmts, .Last) }func stmtsPos( []*Stmt, []Comment) Pos {iflen() > 0 { := [0] := .Pos()iflen(.Comments) > 0 {if := .Comments[0].Pos(); .After() {return } }return }iflen() > 0 {return [0].Pos() }returnPos{}}func stmtsEnd( []*Stmt, []Comment) Pos {iflen() > 0 {return [len()-1].End() }iflen() > 0 { := [len()-1] := .End()iflen(.Comments) > 0 {if := .Comments[0].End(); .After() {return } }return }returnPos{}}// Pos is a position within a shell source file.typePosstruct { offs, lineCol uint32}// We used to split line and column numbers evenly in 16 bits, but line numbers// are significantly more important in practice. Use more bits for them.const ( lineBitSize = 18 lineMax = (1 << lineBitSize) - 1 colBitSize = 32 - lineBitSize colMax = (1 << colBitSize) - 1 colBitMask = colMax)// TODO(v4): consider using uint32 for Offset/Line/Col to better represent bit sizes.// Or go with int64, which more closely resembles portable "sizes" elsewhere.// The latter is probably nicest, as then we can change the number of internal// bits later, and we can also do overflow checks for the user in NewPos.// NewPos creates a position with the given offset, line, and column.//// Note that Pos uses a limited number of bits to store these numbers.// If line or column overflow their allocated space, they are replaced with 0.func (, , uint) Pos {if > lineMax { = 0// protect against overflows; rendered as "?" }if > colMax { = 0// protect against overflows; rendered as "?" }returnPos{offs: uint32(),lineCol: (uint32() << colBitSize) | uint32(), }}// Offset returns the byte offset of the position in the original source file.// Byte offsets start at 0.//// Note that Offset is not protected against overflows;// if an input is larger than 4GiB, the offset will wrap around to 0.func ( Pos) () uint { returnuint(.offs) }// Line returns the line number of the position, starting at 1.//// Line is protected against overflows; if an input has too many lines, extra// lines will have a line number of 0, rendered as "?" by [Pos.String].func ( Pos) () uint { returnuint(.lineCol >> colBitSize) }// Col returns the column number of the position, starting at 1. It counts in// bytes.//// Col is protected against overflows; if an input line has too many columns,// extra columns will have a column number of 0, rendered as "?" by [Pos.String].func ( Pos) () uint { returnuint(.lineCol & colBitMask) }func ( Pos) () string {varstrings.Builderif := .Line(); > 0 { .WriteString(strconv.FormatUint(uint64(), 10)) } else { .WriteByte('?') } .WriteByte(':')if := .Col(); > 0 { .WriteString(strconv.FormatUint(uint64(), 10)) } else { .WriteByte('?') }return .String()}// IsValid reports whether the position contains useful position information.// Some positions returned via [Parse] may be invalid: for example, [Stmt.Semicolon]// will only be valid if a statement contained a closing token such as ';'.func ( Pos) () bool { return != Pos{} }// After reports whether the position p is after p2. It is a more expressive// version of p.Offset() > p2.Offset().func ( Pos) ( Pos) bool { return .offs > .offs }func posAddCol( Pos, int) Pos {// TODO: guard against overflows .lineCol += uint32() .offs += uint32()return}func posMax(, Pos) Pos {if .After() {return }return}// Comment represents a single comment on a single line.typeCommentstruct { Hash Pos Text string}func ( *Comment) () Pos { return .Hash }func ( *Comment) () Pos { returnposAddCol(.Hash, 1+len(.Text)) }// Stmt represents a statement, also known as a "complete command". It is// compromised of a command and other components that may come before or after// it.typeStmtstruct { Comments []Comment Cmd Command Position Pos Semicolon Pos// position of ';', '&', or '|&', if any Negated bool// ! stmt Background bool// stmt & Coprocess bool// mksh's |& Redirs []*Redirect// stmt >a <b}func ( *Stmt) () Pos { return .Position }func ( *Stmt) () Pos {if .Semicolon.IsValid() { := posAddCol(.Semicolon, 1) // ';' or '&'if .Coprocess { = posAddCol(, 1) // '|&' }return } := .Positionif .Negated { = posAddCol(, 1) }if .Cmd != nil { = .Cmd.End() }iflen(.Redirs) > 0 { = posMax(, .Redirs[len(.Redirs)-1].End()) }return}// Command represents all nodes that are simple or compound commands, including// function declarations.//// These are *CallExpr, *IfClause, *WhileClause, *ForClause, *CaseClause,// *Block, *Subshell, *BinaryCmd, *FuncDecl, *ArithmCmd, *TestClause,// *DeclClause, *LetClause, *TimeClause, and *CoprocClause.typeCommandinterface {Node commandNode()}func (*CallExpr) () {}func (*IfClause) () {}func (*WhileClause) () {}func (*ForClause) () {}func (*CaseClause) () {}func (*Block) () {}func (*Subshell) () {}func (*BinaryCmd) () {}func (*FuncDecl) () {}func (*ArithmCmd) () {}func (*TestClause) () {}func (*DeclClause) () {}func (*LetClause) () {}func (*TimeClause) () {}func (*CoprocClause) () {}func (*TestDecl) () {}// Assign represents an assignment to a variable.//// Here and elsewhere, Index can mean either an index expression into an indexed// array, or a string key into an associative array.//// If Index is non-nil, the value will be a word and not an array as nested// arrays are not allowed.//// If Naked is true and Name is nil, the assignment is part of a DeclClause and// the argument (in the Value field) will be evaluated at run-time. This// includes parameter expansions, which may expand to assignments or options.typeAssignstruct { Append bool// += Naked bool// without '=' Name *Lit// must be a valid name Index ArithmExpr// [i], ["k"] Value *Word// =val Array *ArrayExpr// =(arr)}func ( *Assign) () Pos {if .Name == nil {return .Value.Pos() }return .Name.Pos()}func ( *Assign) () Pos {if .Value != nil {return .Value.End() }if .Array != nil {return .Array.End() }if .Index != nil {returnposAddCol(.Index.End(), 2) }if .Naked {return .Name.End() }returnposAddCol(.Name.End(), 1)}// Redirect represents an input/output redirection.typeRedirectstruct { OpPos Pos Op RedirOperator N *Lit// fd>, or {varname}> in Bash Word *Word// >word Hdoc *Word// here-document body}func ( *Redirect) () Pos {if .N != nil {return .N.Pos() }return .OpPos}func ( *Redirect) () Pos {if .Hdoc != nil {return .Hdoc.End() }return .Word.End()}// CallExpr represents a command execution or function call, otherwise known as// a "simple command".//// If Args is empty, Assigns apply to the shell environment. Otherwise, they are// variables that cannot be arrays and which only apply to the call.typeCallExprstruct { Assigns []*Assign// a=x b=y args Args []*Word}func ( *CallExpr) () Pos {iflen(.Assigns) > 0 {return .Assigns[0].Pos() }return .Args[0].Pos()}func ( *CallExpr) () Pos {iflen(.Args) == 0 {return .Assigns[len(.Assigns)-1].End() }return .Args[len(.Args)-1].End()}// Subshell represents a series of commands that should be executed in a nested// shell environment.typeSubshellstruct { Lparen, Rparen Pos Stmts []*Stmt Last []Comment}func ( *Subshell) () Pos { return .Lparen }func ( *Subshell) () Pos { returnposAddCol(.Rparen, 1) }// Block represents a series of commands that should be executed in a nested// scope. It is essentially a list of statements within curly braces.typeBlockstruct { Lbrace, Rbrace Pos Stmts []*Stmt Last []Comment}func ( *Block) () Pos { return .Lbrace }func ( *Block) () Pos { returnposAddCol(.Rbrace, 1) }// IfClause represents an if statement.typeIfClausestruct { Position Pos// position of the starting "if", "elif", or "else" token ThenPos Pos// position of "then", empty if this is an "else" FiPos Pos// position of "fi", shared with .Else if non-nil Cond []*Stmt CondLast []Comment Then []*Stmt ThenLast []Comment Else *IfClause// if non-nil, an "elif" or an "else" Last []Comment// comments on the first "elif", "else", or "fi"}func ( *IfClause) () Pos { return .Position }func ( *IfClause) () Pos { returnposAddCol(.FiPos, 2) }// WhileClause represents a while or an until clause.typeWhileClausestruct { WhilePos, DoPos, DonePos Pos Until bool Cond []*Stmt CondLast []Comment Do []*Stmt DoLast []Comment}func ( *WhileClause) () Pos { return .WhilePos }func ( *WhileClause) () Pos { returnposAddCol(.DonePos, 4) }// ForClause represents a for or a select clause. The latter is only present in// Bash.typeForClausestruct { ForPos, DoPos, DonePos Pos Select bool Braces bool// deprecated form with { } instead of do/done Loop Loop Do []*Stmt DoLast []Comment}func ( *ForClause) () Pos { return .ForPos }func ( *ForClause) () Pos { returnposAddCol(.DonePos, 4) }// Loop holds either *WordIter or *CStyleLoop.typeLoopinterface {Node loopNode()}func (*WordIter) () {}func (*CStyleLoop) () {}// WordIter represents the iteration of a variable over a series of words in a// for clause. If InPos is an invalid position, the "in" token was missing, so// the iteration is over the shell's positional parameters.typeWordIterstruct { Name *Lit InPos Pos// position of "in" Items []*Word}func ( *WordIter) () Pos { return .Name.Pos() }func ( *WordIter) () Pos {iflen(.Items) > 0 {returnwordLastEnd(.Items) }returnposMax(.Name.End(), posAddCol(.InPos, 2))}// CStyleLoop represents the behavior of a for clause similar to the C// language.//// This node will only appear with LangBash.typeCStyleLoopstruct { Lparen, Rparen Pos// Init, Cond, Post can each be nil, if the for loop construct omits it. Init, Cond, Post ArithmExpr}func ( *CStyleLoop) () Pos { return .Lparen }func ( *CStyleLoop) () Pos { returnposAddCol(.Rparen, 2) }// BinaryCmd represents a binary expression between two statements.typeBinaryCmdstruct { OpPos Pos Op BinCmdOperator X, Y *Stmt}func ( *BinaryCmd) () Pos { return .X.Pos() }func ( *BinaryCmd) () Pos { return .Y.End() }// FuncDecl represents the declaration of a function.typeFuncDeclstruct { Position Pos RsrvWord bool// non-posix "function f" style Parens bool// with () parentheses, only meaningful with RsrvWord=true Name *Lit Body *Stmt}func ( *FuncDecl) () Pos { return .Position }func ( *FuncDecl) () Pos { return .Body.End() }// Word represents a shell word, containing one or more word parts contiguous to// each other. The word is delimited by word boundaries, such as spaces,// newlines, semicolons, or parentheses.typeWordstruct { Parts []WordPart}func ( *Word) () Pos { return .Parts[0].Pos() }func ( *Word) () Pos { return .Parts[len(.Parts)-1].End() }// Lit returns the word as a literal value, if the word consists of *Lit nodes// only. An empty string is returned otherwise. Words with multiple literals,// which can appear in some edge cases, are handled properly.//// For example, the word "foo" will return "foo", but the word "foo${bar}" will// return "".func ( *Word) () string {// In the usual case, we'll have either a single part that's a literal, // or one of the parts being a non-literal. Using strings.Join instead // of a strings.Builder avoids extra work in these cases, since a single // part is a shortcut, and many parts don't incur string copies. := make([]string, 0, 1)for , := range .Parts { , := .(*Lit)if ! {return"" } = append(, .Value) }returnstrings.Join(, "")}// WordPart represents all nodes that can form part of a word.//// These are *Lit, *SglQuoted, *DblQuoted, *ParamExp, *CmdSubst, *ArithmExp,// *ProcSubst, and *ExtGlob.typeWordPartinterface {Node wordPartNode()}func (*Lit) () {}func (*SglQuoted) () {}func (*DblQuoted) () {}func (*ParamExp) () {}func (*CmdSubst) () {}func (*ArithmExp) () {}func (*ProcSubst) () {}func (*ExtGlob) () {}func (*BraceExp) () {}// Lit represents a string literal.//// Note that a parsed string literal may not appear as-is in the original source// code, as it is possible to split literals by escaping newlines. The splitting// is lost, but the end position is not.typeLitstruct { ValuePos, ValueEnd Pos Value string}func ( *Lit) () Pos { return .ValuePos }func ( *Lit) () Pos { return .ValueEnd }// SglQuoted represents a string within single quotes.typeSglQuotedstruct { Left, Right Pos Dollar bool// $'' Value string}func ( *SglQuoted) () Pos { return .Left }func ( *SglQuoted) () Pos { returnposAddCol(.Right, 1) }// DblQuoted represents a list of nodes within double quotes.typeDblQuotedstruct { Left, Right Pos Dollar bool// $"" Parts []WordPart}func ( *DblQuoted) () Pos { return .Left }func ( *DblQuoted) () Pos { returnposAddCol(.Right, 1) }// CmdSubst represents a command substitution.typeCmdSubststruct { Left, Right Pos Stmts []*Stmt Last []Comment Backquotes bool// deprecated `foo` TempFile bool// mksh's ${ foo;} ReplyVar bool// mksh's ${|foo;}}func ( *CmdSubst) () Pos { return .Left }func ( *CmdSubst) () Pos { returnposAddCol(.Right, 1) }// ParamExp represents a parameter expansion.typeParamExpstruct { Dollar, Rbrace Pos Short bool// $a instead of ${a} Excl bool// ${!a} Length bool// ${#a} Width bool// ${%a} Param *Lit Index ArithmExpr// ${a[i]}, ${a["k"]} Slice *Slice// ${a:x:y} Repl *Replace// ${a/x/y} Names ParNamesOperator// ${!prefix*} or ${!prefix@} Exp *Expansion// ${a:-b}, ${a#b}, etc}func ( *ParamExp) () Pos { return .Dollar }func ( *ParamExp) () Pos {if !.Short {returnposAddCol(.Rbrace, 1) }if .Index != nil {returnposAddCol(.Index.End(), 1) }return .Param.End()}func ( *ParamExp) () bool {return .Short && .Index != nil}// Slice represents a character slicing expression inside a ParamExp.//// This node will only appear in LangBash and LangMirBSDKorn.typeSlicestruct { Offset, Length ArithmExpr}// Replace represents a search and replace expression inside a ParamExp.typeReplacestruct { All bool Orig, With *Word}// Expansion represents string manipulation in a ParamExp other than those// covered by Replace.typeExpansionstruct { Op ParExpOperator Word *Word}// ArithmExp represents an arithmetic expansion.typeArithmExpstruct { Left, Right Pos Bracket bool// deprecated $[expr] form Unsigned bool// mksh's $((# expr)) X ArithmExpr}func ( *ArithmExp) () Pos { return .Left }func ( *ArithmExp) () Pos {if .Bracket {returnposAddCol(.Right, 1) }returnposAddCol(.Right, 2)}// ArithmCmd represents an arithmetic command.//// This node will only appear in LangBash and LangMirBSDKorn.typeArithmCmdstruct { Left, Right Pos Unsigned bool// mksh's ((# expr)) X ArithmExpr}func ( *ArithmCmd) () Pos { return .Left }func ( *ArithmCmd) () Pos { returnposAddCol(.Right, 2) }// ArithmExpr represents all nodes that form arithmetic expressions.//// These are *BinaryArithm, *UnaryArithm, *ParenArithm, and *Word.typeArithmExprinterface {Node arithmExprNode()}func (*BinaryArithm) () {}func (*UnaryArithm) () {}func (*ParenArithm) () {}func (*Word) () {}// BinaryArithm represents a binary arithmetic expression.//// If Op is any assign operator, X will be a word with a single *Lit whose value// is a valid name.//// Ternary operators like "a ? b : c" are fit into this structure. Thus, if// Op==TernQuest, Y will be a *BinaryArithm with Op==TernColon. Op can only be// TernColon in that scenario.typeBinaryArithmstruct { OpPos Pos Op BinAritOperator X, Y ArithmExpr}func ( *BinaryArithm) () Pos { return .X.Pos() }func ( *BinaryArithm) () Pos { return .Y.End() }// UnaryArithm represents an unary arithmetic expression. The unary operator// may come before or after the sub-expression.//// If Op is Inc or Dec, X will be a word with a single *Lit whose value is a// valid name.typeUnaryArithmstruct { OpPos Pos Op UnAritOperator Post bool X ArithmExpr}func ( *UnaryArithm) () Pos {if .Post {return .X.Pos() }return .OpPos}func ( *UnaryArithm) () Pos {if .Post {returnposAddCol(.OpPos, 2) }return .X.End()}// ParenArithm represents an arithmetic expression within parentheses.typeParenArithmstruct { Lparen, Rparen Pos X ArithmExpr}func ( *ParenArithm) () Pos { return .Lparen }func ( *ParenArithm) () Pos { returnposAddCol(.Rparen, 1) }// CaseClause represents a case (switch) clause.typeCaseClausestruct { Case, In, Esac Pos Braces bool// deprecated mksh form with braces instead of in/esac Word *Word Items []*CaseItem Last []Comment}func ( *CaseClause) () Pos { return .Case }func ( *CaseClause) () Pos { returnposAddCol(.Esac, 4) }// CaseItem represents a pattern list (case) within a CaseClause.typeCaseItemstruct { Op CaseOperator OpPos Pos// unset if it was finished by "esac" Comments []Comment Patterns []*Word Stmts []*Stmt Last []Comment}func ( *CaseItem) () Pos { return .Patterns[0].Pos() }func ( *CaseItem) () Pos {if .OpPos.IsValid() {returnposAddCol(.OpPos, len(.Op.String())) }returnstmtsEnd(.Stmts, .Last)}// TestClause represents a Bash extended test clause.//// This node will only appear in LangBash and LangMirBSDKorn.typeTestClausestruct { Left, Right Pos X TestExpr}func ( *TestClause) () Pos { return .Left }func ( *TestClause) () Pos { returnposAddCol(.Right, 2) }// TestExpr represents all nodes that form test expressions.//// These are *BinaryTest, *UnaryTest, *ParenTest, and *Word.typeTestExprinterface {Node testExprNode()}func (*BinaryTest) () {}func (*UnaryTest) () {}func (*ParenTest) () {}func (*Word) () {}// BinaryTest represents a binary test expression.typeBinaryTeststruct { OpPos Pos Op BinTestOperator X, Y TestExpr}func ( *BinaryTest) () Pos { return .X.Pos() }func ( *BinaryTest) () Pos { return .Y.End() }// UnaryTest represents a unary test expression. The unary operator may come// before or after the sub-expression.typeUnaryTeststruct { OpPos Pos Op UnTestOperator X TestExpr}func ( *UnaryTest) () Pos { return .OpPos }func ( *UnaryTest) () Pos { return .X.End() }// ParenTest represents a test expression within parentheses.typeParenTeststruct { Lparen, Rparen Pos X TestExpr}func ( *ParenTest) () Pos { return .Lparen }func ( *ParenTest) () Pos { returnposAddCol(.Rparen, 1) }// DeclClause represents a Bash declare clause.//// Args can contain a mix of regular and naked assignments. The naked// assignments can represent either options or variable names.//// This node will only appear with LangBash.typeDeclClausestruct {// Variant is one of "declare", "local", "export", "readonly", // "typeset", or "nameref". Variant *Lit Args []*Assign}func ( *DeclClause) () Pos { return .Variant.Pos() }func ( *DeclClause) () Pos {iflen(.Args) > 0 {return .Args[len(.Args)-1].End() }return .Variant.End()}// ArrayExpr represents a Bash array expression.//// This node will only appear with LangBash.typeArrayExprstruct { Lparen, Rparen Pos Elems []*ArrayElem Last []Comment}func ( *ArrayExpr) () Pos { return .Lparen }func ( *ArrayExpr) () Pos { returnposAddCol(.Rparen, 1) }// ArrayElem represents a Bash array element.//// Index can be nil; for example, declare -a x=(value).// Value can be nil; for example, declare -A x=([index]=).// Finally, neither can be nil; for example, declare -A x=([index]=value)typeArrayElemstruct { Index ArithmExpr Value *Word Comments []Comment}func ( *ArrayElem) () Pos {if .Index != nil {return .Index.Pos() }return .Value.Pos()}func ( *ArrayElem) () Pos {if .Value != nil {return .Value.End() }returnposAddCol(.Index.Pos(), 1)}// ExtGlob represents a Bash extended globbing expression. Note that these are// parsed independently of whether shopt has been called or not.//// This node will only appear in LangBash and LangMirBSDKorn.typeExtGlobstruct { OpPos Pos Op GlobOperator Pattern *Lit}func ( *ExtGlob) () Pos { return .OpPos }func ( *ExtGlob) () Pos { returnposAddCol(.Pattern.End(), 1) }// ProcSubst represents a Bash process substitution.//// This node will only appear with LangBash.typeProcSubststruct { OpPos, Rparen Pos Op ProcOperator Stmts []*Stmt Last []Comment}func ( *ProcSubst) () Pos { return .OpPos }func ( *ProcSubst) () Pos { returnposAddCol(.Rparen, 1) }// TimeClause represents a Bash time clause. PosixFormat corresponds to the -p// flag.//// This node will only appear in LangBash and LangMirBSDKorn.typeTimeClausestruct { Time Pos PosixFormat bool Stmt *Stmt}func ( *TimeClause) () Pos { return .Time }func ( *TimeClause) () Pos {if .Stmt == nil {returnposAddCol(.Time, 4) }return .Stmt.End()}// CoprocClause represents a Bash coproc clause.//// This node will only appear with LangBash.typeCoprocClausestruct { Coproc Pos Name *Word Stmt *Stmt}func ( *CoprocClause) () Pos { return .Coproc }func ( *CoprocClause) () Pos { return .Stmt.End() }// LetClause represents a Bash let clause.//// This node will only appear in LangBash and LangMirBSDKorn.typeLetClausestruct { Let Pos Exprs []ArithmExpr}func ( *LetClause) () Pos { return .Let }func ( *LetClause) () Pos { return .Exprs[len(.Exprs)-1].End() }// BraceExp represents a Bash brace expression, such as "{a,f}" or "{1..10}".//// This node will only appear as a result of SplitBraces.typeBraceExpstruct { Sequence bool// {x..y[..incr]} instead of {x,y[,...]} Elems []*Word}func ( *BraceExp) () Pos {returnposAddCol(.Elems[0].Pos(), -1)}func ( *BraceExp) () Pos {returnposAddCol(wordLastEnd(.Elems), 1)}// TestDecl represents the declaration of a Bats test function.typeTestDeclstruct { Position Pos Description *Word Body *Stmt}func ( *TestDecl) () Pos { return .Position }func ( *TestDecl) () Pos { return .Body.End() }func wordLastEnd( []*Word) Pos {iflen() == 0 {returnPos{} }return [len()-1].End()}
The pages are generated with Goldsv0.8.2. (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.