nodes.go

  1// Copyright (c) 2016, Daniel MartΓ­ <mvdan@mvdan.cc>
  2// See LICENSE for licensing information
  3
  4package syntax
  5
  6import (
  7	"math"
  8	"strconv"
  9	"strings"
 10)
 11
 12// Node represents a syntax tree node.
 13type Node interface {
 14	// Pos returns the position of the first character of the node. Comments
 15	// are ignored, except if the node is a [*File].
 16	Pos() Pos
 17	// End returns the position of the character immediately after the node.
 18	// If the character is a newline, the line number won't cross into the
 19	// next line. Comments are ignored, except if the node is a [*File].
 20	End() Pos
 21}
 22
 23// File represents a shell source file.
 24type File struct {
 25	Name string
 26
 27	Stmts []*Stmt
 28	Last  []Comment
 29}
 30
 31func (f *File) Pos() Pos { return stmtsPos(f.Stmts, f.Last) }
 32func (f *File) End() Pos { return stmtsEnd(f.Stmts, f.Last) }
 33
 34func stmtsPos(stmts []*Stmt, last []Comment) Pos {
 35	if len(stmts) > 0 {
 36		s := stmts[0]
 37		sPos := s.Pos()
 38		if len(s.Comments) > 0 {
 39			if cPos := s.Comments[0].Pos(); sPos.After(cPos) {
 40				return cPos
 41			}
 42		}
 43		return sPos
 44	}
 45	if len(last) > 0 {
 46		return last[0].Pos()
 47	}
 48	return Pos{}
 49}
 50
 51func stmtsEnd(stmts []*Stmt, last []Comment) Pos {
 52	if len(last) > 0 {
 53		return last[len(last)-1].End()
 54	}
 55	if len(stmts) > 0 {
 56		s := stmts[len(stmts)-1]
 57		sEnd := s.End()
 58		if len(s.Comments) > 0 {
 59			if cEnd := s.Comments[0].End(); cEnd.After(sEnd) {
 60				return cEnd
 61			}
 62		}
 63		return sEnd
 64	}
 65	return Pos{}
 66}
 67
 68// Pos is a position within a shell source file.
 69type Pos struct {
 70	offs, lineCol uint32
 71}
 72
 73const (
 74	// Offsets use 32 bits for a reasonable amount of precision.
 75	// We reserve a few of the highest values to represent types of invalid positions.
 76	// We leave some space before the real uint32 maximum so that we can easily detect
 77	// when arithmetic on invalid positions is done by mistake.
 78	offsetRecovered = math.MaxUint32 - 10
 79	offsetMax       = math.MaxUint32 - 11
 80
 81	// We used to split line and column numbers evenly in 16 bits, but line numbers
 82	// are significantly more important in practice. Use more bits for them.
 83
 84	lineBitSize = 18
 85	lineMax     = (1 << lineBitSize) - 1
 86
 87	colBitSize = 32 - lineBitSize
 88	colMax     = (1 << colBitSize) - 1
 89	colBitMask = colMax
 90)
 91
 92// TODO(v4): consider using uint32 for Offset/Line/Col to better represent bit sizes.
 93// Or go with int64, which more closely resembles portable "sizes" elsewhere.
 94// The latter is probably nicest, as then we can change the number of internal
 95// bits later, and we can also do overflow checks for the user in NewPos.
 96
 97// NewPos creates a position with the given offset, line, and column.
 98//
 99// Note that [Pos] uses a limited number of bits to store these numbers.
100// If line or column overflow their allocated space, they are replaced with 0.
101func NewPos(offset, line, column uint) Pos {
102	// Basic protection against offset overflow;
103	// note that an offset of 0 is valid, so we leave the maximum.
104	offset = min(offset, offsetMax)
105	if line > lineMax {
106		line = 0 // protect against overflows; rendered as "?"
107	}
108	if column > colMax {
109		column = 0 // protect against overflows; rendered as "?"
110	}
111	return Pos{
112		offs:    uint32(offset),
113		lineCol: (uint32(line) << colBitSize) | uint32(column),
114	}
115}
116
117// Offset returns the byte offset of the position in the original source file.
118// Byte offsets start at 0. Invalid positions always report the offset 0.
119//
120// Offset has basic protection against overflows; if an input is too large,
121// offset numbers will stop increasing past a very large number.
122func (p Pos) Offset() uint {
123	if p.offs > offsetMax {
124		return 0 // invalid
125	}
126	return uint(p.offs)
127}
128
129// Line returns the line number of the position, starting at 1.
130// Invalid positions always report the line number 0.
131//
132// Line is protected against overflows; if an input has too many lines, extra
133// lines will have a line number of 0, rendered as "?" by [Pos.String].
134func (p Pos) Line() uint { return uint(p.lineCol >> colBitSize) }
135
136// Col returns the column number of the position, starting at 1. It counts in
137// bytes. Invalid positions always report the column number 0.
138//
139// Col is protected against overflows; if an input line has too many columns,
140// extra columns will have a column number of 0, rendered as "?" by [Pos.String].
141func (p Pos) Col() uint { return uint(p.lineCol & colBitMask) }
142
143func (p Pos) String() string {
144	var b strings.Builder
145	if line := p.Line(); line > 0 {
146		b.WriteString(strconv.FormatUint(uint64(line), 10))
147	} else {
148		b.WriteByte('?')
149	}
150	b.WriteByte(':')
151	if col := p.Col(); col > 0 {
152		b.WriteString(strconv.FormatUint(uint64(col), 10))
153	} else {
154		b.WriteByte('?')
155	}
156	return b.String()
157}
158
159// IsValid reports whether the position contains useful position information.
160// Some positions returned via [Parse] may be invalid: for example, [Stmt.Semicolon]
161// will only be valid if a statement contained a closing token such as ';'.
162//
163// Recovered positions, as reported by [Pos.IsRecovered], are not considered valid
164// given that they don't contain position information.
165func (p Pos) IsValid() bool {
166	return p.offs <= offsetMax && p.lineCol != 0
167}
168
169var recoveredPos = Pos{offs: offsetRecovered}
170
171// IsRecovered reports whether the position that the token or node belongs to
172// was missing in the original input and recovered via [RecoverErrors].
173func (p Pos) IsRecovered() bool { return p == recoveredPos }
174
175// After reports whether the position p is after p2. It is a more expressive
176// version of p.Offset() > p2.Offset().
177// It always returns false if p is an invalid position.
178func (p Pos) After(p2 Pos) bool {
179	if !p.IsValid() {
180		return false
181	}
182	return p.offs > p2.offs
183}
184
185func posAddCol(p Pos, n int) Pos {
186	if !p.IsValid() {
187		return p
188	}
189	// TODO: guard against overflows
190	p.lineCol += uint32(n)
191	p.offs += uint32(n)
192	return p
193}
194
195func posMax(p1, p2 Pos) Pos {
196	if p2.After(p1) {
197		return p2
198	}
199	return p1
200}
201
202// Comment represents a single comment on a single line.
203type Comment struct {
204	Hash Pos
205	Text string
206}
207
208func (c *Comment) Pos() Pos { return c.Hash }
209func (c *Comment) End() Pos { return posAddCol(c.Hash, 1+len(c.Text)) }
210
211// Stmt represents a statement, also known as a "complete command". It is
212// compromised of a command and other components that may come before or after
213// it.
214type Stmt struct {
215	Comments   []Comment
216	Cmd        Command
217	Position   Pos
218	Semicolon  Pos  // position of ';', '&', or '|&', if any
219	Negated    bool // ! stmt
220	Background bool // stmt &
221	Coprocess  bool // mksh's |&
222
223	Redirs []*Redirect // stmt >a <b
224}
225
226func (s *Stmt) Pos() Pos { return s.Position }
227func (s *Stmt) End() Pos {
228	if s.Semicolon.IsValid() {
229		end := posAddCol(s.Semicolon, 1) // ';' or '&'
230		if s.Coprocess {
231			end = posAddCol(end, 1) // '|&'
232		}
233		return end
234	}
235	end := s.Position
236	if s.Negated {
237		end = posAddCol(end, 1)
238	}
239	if s.Cmd != nil {
240		end = s.Cmd.End()
241	}
242	if len(s.Redirs) > 0 {
243		end = posMax(end, s.Redirs[len(s.Redirs)-1].End())
244	}
245	return end
246}
247
248// Command represents all nodes that are simple or compound commands, including
249// function declarations.
250//
251// These are [*CallExpr], [*IfClause], [*WhileClause], [*ForClause], [*CaseClause],
252// [*Block], [*Subshell], [*BinaryCmd], [*FuncDecl], [*ArithmCmd], [*TestClause],
253// [*DeclClause], [*LetClause], [*TimeClause], and [*CoprocClause].
254type Command interface {
255	Node
256	commandNode()
257}
258
259func (*CallExpr) commandNode()     {}
260func (*IfClause) commandNode()     {}
261func (*WhileClause) commandNode()  {}
262func (*ForClause) commandNode()    {}
263func (*CaseClause) commandNode()   {}
264func (*Block) commandNode()        {}
265func (*Subshell) commandNode()     {}
266func (*BinaryCmd) commandNode()    {}
267func (*FuncDecl) commandNode()     {}
268func (*ArithmCmd) commandNode()    {}
269func (*TestClause) commandNode()   {}
270func (*DeclClause) commandNode()   {}
271func (*LetClause) commandNode()    {}
272func (*TimeClause) commandNode()   {}
273func (*CoprocClause) commandNode() {}
274func (*TestDecl) commandNode()     {}
275
276// Assign represents an assignment to a variable.
277//
278// Here and elsewhere, Index can mean either an index expression into an indexed
279// array, or a string key into an associative array.
280//
281// If Index is non-nil, the value will be a word and not an array as nested
282// arrays are not allowed.
283//
284// If Naked is true and Name is nil, the assignment is part of a [DeclClause] and
285// the argument (in the Value field) will be evaluated at run-time. This
286// includes parameter expansions, which may expand to assignments or options.
287type Assign struct {
288	Append bool       // +=
289	Naked  bool       // without '='
290	Name   *Lit       // must be a valid name
291	Index  ArithmExpr // [i], ["k"]
292	Value  *Word      // =val
293	Array  *ArrayExpr // =(arr)
294}
295
296func (a *Assign) Pos() Pos {
297	if a.Name == nil {
298		return a.Value.Pos()
299	}
300	return a.Name.Pos()
301}
302
303func (a *Assign) End() Pos {
304	if a.Value != nil {
305		return a.Value.End()
306	}
307	if a.Array != nil {
308		return a.Array.End()
309	}
310	if a.Index != nil {
311		return posAddCol(a.Index.End(), 2)
312	}
313	if a.Naked {
314		return a.Name.End()
315	}
316	return posAddCol(a.Name.End(), 1)
317}
318
319// Redirect represents an input/output redirection.
320type Redirect struct {
321	OpPos Pos
322	Op    RedirOperator
323	N     *Lit  // fd>, or {varname}> in Bash
324	Word  *Word // >word
325	Hdoc  *Word // here-document body
326}
327
328func (r *Redirect) Pos() Pos {
329	if r.N != nil {
330		return r.N.Pos()
331	}
332	return r.OpPos
333}
334
335func (r *Redirect) End() Pos {
336	if r.Hdoc != nil {
337		return r.Hdoc.End()
338	}
339	return r.Word.End()
340}
341
342// CallExpr represents a command execution or function call, otherwise known as
343// a "simple command".
344//
345// If Args is empty, Assigns apply to the shell environment. Otherwise, they are
346// variables that cannot be arrays and which only apply to the call.
347type CallExpr struct {
348	Assigns []*Assign // a=x b=y args
349	Args    []*Word
350}
351
352func (c *CallExpr) Pos() Pos {
353	if len(c.Assigns) > 0 {
354		return c.Assigns[0].Pos()
355	}
356	return c.Args[0].Pos()
357}
358
359func (c *CallExpr) End() Pos {
360	if len(c.Args) == 0 {
361		return c.Assigns[len(c.Assigns)-1].End()
362	}
363	return c.Args[len(c.Args)-1].End()
364}
365
366// Subshell represents a series of commands that should be executed in a nested
367// shell environment.
368type Subshell struct {
369	Lparen, Rparen Pos
370
371	Stmts []*Stmt
372	Last  []Comment
373}
374
375func (s *Subshell) Pos() Pos { return s.Lparen }
376func (s *Subshell) End() Pos { return posAddCol(s.Rparen, 1) }
377
378// Block represents a series of commands that should be executed in a nested
379// scope. It is essentially a list of statements within curly braces.
380type Block struct {
381	Lbrace, Rbrace Pos
382
383	Stmts []*Stmt
384	Last  []Comment
385}
386
387func (b *Block) Pos() Pos { return b.Lbrace }
388func (b *Block) End() Pos { return posAddCol(b.Rbrace, 1) }
389
390// IfClause represents an if statement.
391type IfClause struct {
392	Position Pos // position of the starting "if", "elif", or "else" token
393	ThenPos  Pos // position of "then", empty if this is an "else"
394	FiPos    Pos // position of "fi", shared with .Else if non-nil
395
396	Cond     []*Stmt
397	CondLast []Comment
398	Then     []*Stmt
399	ThenLast []Comment
400
401	Else *IfClause // if non-nil, an "elif" or an "else"
402
403	Last []Comment // comments on the first "elif", "else", or "fi"
404}
405
406func (c *IfClause) Pos() Pos { return c.Position }
407func (c *IfClause) End() Pos { return posAddCol(c.FiPos, 2) }
408
409// WhileClause represents a while or an until clause.
410type WhileClause struct {
411	WhilePos, DoPos, DonePos Pos
412	Until                    bool
413
414	Cond     []*Stmt
415	CondLast []Comment
416	Do       []*Stmt
417	DoLast   []Comment
418}
419
420func (w *WhileClause) Pos() Pos { return w.WhilePos }
421func (w *WhileClause) End() Pos { return posAddCol(w.DonePos, 4) }
422
423// ForClause represents a for or a select clause. The latter is only present in
424// Bash.
425type ForClause struct {
426	ForPos, DoPos, DonePos Pos
427	Select                 bool
428	Braces                 bool // deprecated form with { } instead of do/done
429	Loop                   Loop
430
431	Do     []*Stmt
432	DoLast []Comment
433}
434
435func (f *ForClause) Pos() Pos { return f.ForPos }
436func (f *ForClause) End() Pos { return posAddCol(f.DonePos, 4) }
437
438// Loop holds either [*WordIter] or [*CStyleLoop].
439type Loop interface {
440	Node
441	loopNode()
442}
443
444func (*WordIter) loopNode()   {}
445func (*CStyleLoop) loopNode() {}
446
447// WordIter represents the iteration of a variable over a series of words in a
448// for clause. If InPos is an invalid position, the "in" token was missing, so
449// the iteration is over the shell's positional parameters.
450type WordIter struct {
451	Name  *Lit
452	InPos Pos // position of "in"
453	Items []*Word
454}
455
456func (w *WordIter) Pos() Pos { return w.Name.Pos() }
457func (w *WordIter) End() Pos {
458	if len(w.Items) > 0 {
459		return wordLastEnd(w.Items)
460	}
461	return posMax(w.Name.End(), posAddCol(w.InPos, 2))
462}
463
464// CStyleLoop represents the behavior of a for clause similar to the C
465// language.
466//
467// This node will only appear with [LangBash].
468type CStyleLoop struct {
469	Lparen, Rparen Pos
470	// Init, Cond, Post can each be nil, if the for loop construct omits it.
471	Init, Cond, Post ArithmExpr
472}
473
474func (c *CStyleLoop) Pos() Pos { return c.Lparen }
475func (c *CStyleLoop) End() Pos { return posAddCol(c.Rparen, 2) }
476
477// BinaryCmd represents a binary expression between two statements.
478type BinaryCmd struct {
479	OpPos Pos
480	Op    BinCmdOperator
481	X, Y  *Stmt
482}
483
484func (b *BinaryCmd) Pos() Pos { return b.X.Pos() }
485func (b *BinaryCmd) End() Pos { return b.Y.End() }
486
487// FuncDecl represents the declaration of a function.
488type FuncDecl struct {
489	Position Pos
490	RsrvWord bool // non-posix "function f" style
491	Parens   bool // with () parentheses, only meaningful with RsrvWord=true
492	Name     *Lit
493	Body     *Stmt
494}
495
496func (f *FuncDecl) Pos() Pos { return f.Position }
497func (f *FuncDecl) End() Pos { return f.Body.End() }
498
499// Word represents a shell word, containing one or more word parts contiguous to
500// each other. The word is delimited by word boundaries, such as spaces,
501// newlines, semicolons, or parentheses.
502type Word struct {
503	Parts []WordPart
504}
505
506func (w *Word) Pos() Pos { return w.Parts[0].Pos() }
507func (w *Word) End() Pos { return w.Parts[len(w.Parts)-1].End() }
508
509// Lit returns the word as a literal value, if the word consists of [*Lit] nodes
510// only. An empty string is returned otherwise. Words with multiple literals,
511// which can appear in some edge cases, are handled properly.
512//
513// For example, the word "foo" will return "foo", but the word "foo${bar}" will
514// return "".
515func (w *Word) Lit() string {
516	// In the usual case, we'll have either a single part that's a literal,
517	// or one of the parts being a non-literal. Using strings.Join instead
518	// of a strings.Builder avoids extra work in these cases, since a single
519	// part is a shortcut, and many parts don't incur string copies.
520	lits := make([]string, 0, 1)
521	for _, part := range w.Parts {
522		lit, ok := part.(*Lit)
523		if !ok {
524			return ""
525		}
526		lits = append(lits, lit.Value)
527	}
528	return strings.Join(lits, "")
529}
530
531// WordPart represents all nodes that can form part of a word.
532//
533// These are [*Lit], [*SglQuoted], [*DblQuoted], [*ParamExp], [*CmdSubst], [*ArithmExp],
534// [*ProcSubst], and [*ExtGlob].
535type WordPart interface {
536	Node
537	wordPartNode()
538}
539
540func (*Lit) wordPartNode()       {}
541func (*SglQuoted) wordPartNode() {}
542func (*DblQuoted) wordPartNode() {}
543func (*ParamExp) wordPartNode()  {}
544func (*CmdSubst) wordPartNode()  {}
545func (*ArithmExp) wordPartNode() {}
546func (*ProcSubst) wordPartNode() {}
547func (*ExtGlob) wordPartNode()   {}
548func (*BraceExp) wordPartNode()  {}
549
550// Lit represents a string literal.
551//
552// Note that a parsed string literal may not appear as-is in the original source
553// code, as it is possible to split literals by escaping newlines. The splitting
554// is lost, but the end position is not.
555type Lit struct {
556	ValuePos, ValueEnd Pos
557	Value              string
558}
559
560func (l *Lit) Pos() Pos { return l.ValuePos }
561func (l *Lit) End() Pos { return l.ValueEnd }
562
563// SglQuoted represents a string within single quotes.
564type SglQuoted struct {
565	Left, Right Pos
566	Dollar      bool // $''
567	Value       string
568}
569
570func (q *SglQuoted) Pos() Pos { return q.Left }
571func (q *SglQuoted) End() Pos { return posAddCol(q.Right, 1) }
572
573// DblQuoted represents a list of nodes within double quotes.
574type DblQuoted struct {
575	Left, Right Pos
576	Dollar      bool // $""
577	Parts       []WordPart
578}
579
580func (q *DblQuoted) Pos() Pos { return q.Left }
581func (q *DblQuoted) End() Pos { return posAddCol(q.Right, 1) }
582
583// CmdSubst represents a command substitution.
584type CmdSubst struct {
585	Left, Right Pos
586
587	Stmts []*Stmt
588	Last  []Comment
589
590	Backquotes bool // deprecated `foo`
591	TempFile   bool // mksh's ${ foo;}
592	ReplyVar   bool // mksh's ${|foo;}
593}
594
595func (c *CmdSubst) Pos() Pos { return c.Left }
596func (c *CmdSubst) End() Pos { return posAddCol(c.Right, 1) }
597
598// ParamExp represents a parameter expansion.
599type ParamExp struct {
600	Dollar, Rbrace Pos
601
602	Short  bool // $a instead of ${a}
603	Excl   bool // ${!a}
604	Length bool // ${#a}
605	Width  bool // ${%a}
606	Param  *Lit
607	Index  ArithmExpr       // ${a[i]}, ${a["k"]}
608	Slice  *Slice           // ${a:x:y}
609	Repl   *Replace         // ${a/x/y}
610	Names  ParNamesOperator // ${!prefix*} or ${!prefix@}
611	Exp    *Expansion       // ${a:-b}, ${a#b}, etc
612}
613
614func (p *ParamExp) Pos() Pos { return p.Dollar }
615func (p *ParamExp) End() Pos {
616	if !p.Short {
617		return posAddCol(p.Rbrace, 1)
618	}
619	if p.Index != nil {
620		return posAddCol(p.Index.End(), 1)
621	}
622	return p.Param.End()
623}
624
625func (p *ParamExp) nakedIndex() bool {
626	return p.Short && p.Index != nil
627}
628
629// Slice represents a character slicing expression inside a [ParamExp].
630//
631// This node will only appear with [LangBash] and [LangMirBSDKorn].
632type Slice struct {
633	Offset, Length ArithmExpr
634}
635
636// Replace represents a search and replace expression inside a [ParamExp].
637type Replace struct {
638	All        bool
639	Orig, With *Word
640}
641
642// Expansion represents string manipulation in a [ParamExp] other than those
643// covered by [Replace].
644type Expansion struct {
645	Op   ParExpOperator
646	Word *Word
647}
648
649// ArithmExp represents an arithmetic expansion.
650type ArithmExp struct {
651	Left, Right Pos
652	Bracket     bool // deprecated $[expr] form
653	Unsigned    bool // mksh's $((# expr))
654
655	X ArithmExpr
656}
657
658func (a *ArithmExp) Pos() Pos { return a.Left }
659func (a *ArithmExp) End() Pos {
660	if a.Bracket {
661		return posAddCol(a.Right, 1)
662	}
663	return posAddCol(a.Right, 2)
664}
665
666// ArithmCmd represents an arithmetic command.
667//
668// This node will only appear with [LangBash] and [LangMirBSDKorn].
669type ArithmCmd struct {
670	Left, Right Pos
671	Unsigned    bool // mksh's ((# expr))
672
673	X ArithmExpr
674}
675
676func (a *ArithmCmd) Pos() Pos { return a.Left }
677func (a *ArithmCmd) End() Pos { return posAddCol(a.Right, 2) }
678
679// ArithmExpr represents all nodes that form arithmetic expressions.
680//
681// These are [*BinaryArithm], [*UnaryArithm], [*ParenArithm], and [*Word].
682type ArithmExpr interface {
683	Node
684	arithmExprNode()
685}
686
687func (*BinaryArithm) arithmExprNode() {}
688func (*UnaryArithm) arithmExprNode()  {}
689func (*ParenArithm) arithmExprNode()  {}
690func (*Word) arithmExprNode()         {}
691
692// BinaryArithm represents a binary arithmetic expression.
693//
694// If Op is any assign operator, X will be a word with a single [*Lit] whose value
695// is a valid name.
696//
697// Ternary operators like "a ? b : c" are fit into this structure. Thus, if
698// Op==[TernQuest], Y will be a [*BinaryArithm] with Op==[TernColon].
699// [TernColon] does not appear in any other scenario.
700type BinaryArithm struct {
701	OpPos Pos
702	Op    BinAritOperator
703	X, Y  ArithmExpr
704}
705
706func (b *BinaryArithm) Pos() Pos { return b.X.Pos() }
707func (b *BinaryArithm) End() Pos { return b.Y.End() }
708
709// UnaryArithm represents an unary arithmetic expression. The unary operator
710// may come before or after the sub-expression.
711//
712// If Op is [Inc] or [Dec], X will be a word with a single [*Lit] whose value is a
713// valid name.
714type UnaryArithm struct {
715	OpPos Pos
716	Op    UnAritOperator
717	Post  bool
718	X     ArithmExpr
719}
720
721func (u *UnaryArithm) Pos() Pos {
722	if u.Post {
723		return u.X.Pos()
724	}
725	return u.OpPos
726}
727
728func (u *UnaryArithm) End() Pos {
729	if u.Post {
730		return posAddCol(u.OpPos, 2)
731	}
732	return u.X.End()
733}
734
735// ParenArithm represents an arithmetic expression within parentheses.
736type ParenArithm struct {
737	Lparen, Rparen Pos
738
739	X ArithmExpr
740}
741
742func (p *ParenArithm) Pos() Pos { return p.Lparen }
743func (p *ParenArithm) End() Pos { return posAddCol(p.Rparen, 1) }
744
745// CaseClause represents a case (switch) clause.
746type CaseClause struct {
747	Case, In, Esac Pos
748	Braces         bool // deprecated mksh form with braces instead of in/esac
749
750	Word  *Word
751	Items []*CaseItem
752	Last  []Comment
753}
754
755func (c *CaseClause) Pos() Pos { return c.Case }
756func (c *CaseClause) End() Pos { return posAddCol(c.Esac, 4) }
757
758// CaseItem represents a pattern list (case) within a [CaseClause].
759type CaseItem struct {
760	Op       CaseOperator
761	OpPos    Pos // unset if it was finished by "esac"
762	Comments []Comment
763	Patterns []*Word
764
765	Stmts []*Stmt
766	Last  []Comment
767}
768
769func (c *CaseItem) Pos() Pos { return c.Patterns[0].Pos() }
770func (c *CaseItem) End() Pos {
771	if c.OpPos.IsValid() {
772		return posAddCol(c.OpPos, len(c.Op.String()))
773	}
774	return stmtsEnd(c.Stmts, c.Last)
775}
776
777// TestClause represents a Bash extended test clause.
778//
779// This node will only appear with [LangBash] and [LangMirBSDKorn].
780type TestClause struct {
781	Left, Right Pos
782
783	X TestExpr
784}
785
786func (t *TestClause) Pos() Pos { return t.Left }
787func (t *TestClause) End() Pos { return posAddCol(t.Right, 2) }
788
789// TestExpr represents all nodes that form test expressions.
790//
791// These are [*BinaryTest], [*UnaryTest], [*ParenTest], and [*Word].
792type TestExpr interface {
793	Node
794	testExprNode()
795}
796
797func (*BinaryTest) testExprNode() {}
798func (*UnaryTest) testExprNode()  {}
799func (*ParenTest) testExprNode()  {}
800func (*Word) testExprNode()       {}
801
802// BinaryTest represents a binary test expression.
803type BinaryTest struct {
804	OpPos Pos
805	Op    BinTestOperator
806	X, Y  TestExpr
807}
808
809func (b *BinaryTest) Pos() Pos { return b.X.Pos() }
810func (b *BinaryTest) End() Pos { return b.Y.End() }
811
812// UnaryTest represents a unary test expression. The unary operator may come
813// before or after the sub-expression.
814type UnaryTest struct {
815	OpPos Pos
816	Op    UnTestOperator
817	X     TestExpr
818}
819
820func (u *UnaryTest) Pos() Pos { return u.OpPos }
821func (u *UnaryTest) End() Pos { return u.X.End() }
822
823// ParenTest represents a test expression within parentheses.
824type ParenTest struct {
825	Lparen, Rparen Pos
826
827	X TestExpr
828}
829
830func (p *ParenTest) Pos() Pos { return p.Lparen }
831func (p *ParenTest) End() Pos { return posAddCol(p.Rparen, 1) }
832
833// DeclClause represents a Bash declare clause.
834//
835// Args can contain a mix of regular and naked assignments. The naked
836// assignments can represent either options or variable names.
837//
838// This node will only appear with [LangBash].
839type DeclClause struct {
840	// Variant is one of "declare", "local", "export", "readonly",
841	// "typeset", or "nameref".
842	Variant *Lit
843	Args    []*Assign
844}
845
846func (d *DeclClause) Pos() Pos { return d.Variant.Pos() }
847func (d *DeclClause) End() Pos {
848	if len(d.Args) > 0 {
849		return d.Args[len(d.Args)-1].End()
850	}
851	return d.Variant.End()
852}
853
854// ArrayExpr represents a Bash array expression.
855//
856// This node will only appear with [LangBash].
857type ArrayExpr struct {
858	Lparen, Rparen Pos
859
860	Elems []*ArrayElem
861	Last  []Comment
862}
863
864func (a *ArrayExpr) Pos() Pos { return a.Lparen }
865func (a *ArrayExpr) End() Pos { return posAddCol(a.Rparen, 1) }
866
867// ArrayElem represents a Bash array element.
868//
869// Index can be nil; for example, declare -a x=(value).
870// Value can be nil; for example, declare -A x=([index]=).
871// Finally, neither can be nil; for example, declare -A x=([index]=value)
872type ArrayElem struct {
873	Index    ArithmExpr
874	Value    *Word
875	Comments []Comment
876}
877
878func (a *ArrayElem) Pos() Pos {
879	if a.Index != nil {
880		return a.Index.Pos()
881	}
882	return a.Value.Pos()
883}
884
885func (a *ArrayElem) End() Pos {
886	if a.Value != nil {
887		return a.Value.End()
888	}
889	return posAddCol(a.Index.Pos(), 1)
890}
891
892// ExtGlob represents a Bash extended globbing expression. Note that these are
893// parsed independently of whether shopt has been called or not.
894//
895// This node will only appear with [LangBash] and [LangMirBSDKorn].
896type ExtGlob struct {
897	OpPos   Pos
898	Op      GlobOperator
899	Pattern *Lit
900}
901
902func (e *ExtGlob) Pos() Pos { return e.OpPos }
903func (e *ExtGlob) End() Pos { return posAddCol(e.Pattern.End(), 1) }
904
905// ProcSubst represents a Bash process substitution.
906//
907// This node will only appear with [LangBash].
908type ProcSubst struct {
909	OpPos, Rparen Pos
910	Op            ProcOperator
911
912	Stmts []*Stmt
913	Last  []Comment
914}
915
916func (s *ProcSubst) Pos() Pos { return s.OpPos }
917func (s *ProcSubst) End() Pos { return posAddCol(s.Rparen, 1) }
918
919// TimeClause represents a Bash time clause. PosixFormat corresponds to the -p
920// flag.
921//
922// This node will only appear with [LangBash] and [LangMirBSDKorn].
923type TimeClause struct {
924	Time        Pos
925	PosixFormat bool
926	Stmt        *Stmt
927}
928
929func (c *TimeClause) Pos() Pos { return c.Time }
930func (c *TimeClause) End() Pos {
931	if c.Stmt == nil {
932		return posAddCol(c.Time, 4)
933	}
934	return c.Stmt.End()
935}
936
937// CoprocClause represents a Bash coproc clause.
938//
939// This node will only appear with [LangBash].
940type CoprocClause struct {
941	Coproc Pos
942	Name   *Word
943	Stmt   *Stmt
944}
945
946func (c *CoprocClause) Pos() Pos { return c.Coproc }
947func (c *CoprocClause) End() Pos { return c.Stmt.End() }
948
949// LetClause represents a Bash let clause.
950//
951// This node will only appear with [LangBash] and [LangMirBSDKorn].
952type LetClause struct {
953	Let   Pos
954	Exprs []ArithmExpr
955}
956
957func (l *LetClause) Pos() Pos { return l.Let }
958func (l *LetClause) End() Pos { return l.Exprs[len(l.Exprs)-1].End() }
959
960// BraceExp represents a Bash brace expression, such as "{a,f}" or "{1..10}".
961//
962// This node will only appear as a result of [SplitBraces].
963type BraceExp struct {
964	Sequence bool // {x..y[..incr]} instead of {x,y[,...]}
965	Elems    []*Word
966}
967
968func (b *BraceExp) Pos() Pos {
969	return posAddCol(b.Elems[0].Pos(), -1)
970}
971
972func (b *BraceExp) End() Pos {
973	return posAddCol(wordLastEnd(b.Elems), 1)
974}
975
976// TestDecl represents the declaration of a Bats test function.
977type TestDecl struct {
978	Position    Pos
979	Description *Word
980	Body        *Stmt
981}
982
983func (f *TestDecl) Pos() Pos { return f.Position }
984func (f *TestDecl) End() Pos { return f.Body.End() }
985
986func wordLastEnd(ws []*Word) Pos {
987	if len(ws) == 0 {
988		return Pos{}
989	}
990	return ws[len(ws)-1].End()
991}