// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package syntax

import (
	
	
	
	
	
	
)

// ParserOption is a function which can be passed to NewParser
// to alter its behavior. To apply option to existing Parser
// call it directly, for example KeepComments(true)(parser).
type ParserOption func(*Parser)

// KeepComments makes the parser parse comments and attach them to
// nodes, as opposed to discarding them.
func ( bool) ParserOption {
	return func( *Parser) { .keepComments =  }
}

// LangVariant describes a shell language variant to use when tokenizing and
// parsing shell code. The zero value is LangBash.
type LangVariant int

const (
	// LangBash corresponds to the GNU Bash language, as described in its
	// manual at https://www.gnu.org/software/bash/manual/bash.html.
	//
	// We currently follow Bash version 5.1.
	//
	// Its string representation is "bash".
	LangBash LangVariant = iota

	// LangPOSIX corresponds to the POSIX Shell language, as described at
	// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html.
	//
	// Its string representation is "posix" or "sh".
	LangPOSIX

	// LangMirBSDKorn corresponds to the MirBSD Korn Shell, also known as
	// mksh, as described at http://www.mirbsd.org/htman/i386/man1/mksh.htm.
	// Note that it shares some features with Bash, due to the the shared
	// ancestry that is ksh.
	//
	// We currently follow mksh version 59.
	//
	// Its string representation is "mksh".
	LangMirBSDKorn

	// LangBats corresponds to the Bash Automated Testing System language,
	// as described at https://github.com/bats-core/bats-core. Note that
	// it's just a small extension of the Bash language.
	//
	// Its string representation is "bats".
	LangBats

	// LangAuto corresponds to automatic language detection,
	// commonly used by end-user applications like shfmt,
	// which can guess a file's language variant given its filename or shebang.
	//
	// At this time, the Parser does not support LangAuto.
	LangAuto
)

// Variant changes the shell language variant that the parser will
// accept.
//
// The passed language variant must be one of the constant values defined in
// this package.
func ( LangVariant) ParserOption {
	switch  {
	case LangBash, LangPOSIX, LangMirBSDKorn, LangBats:
	case LangAuto:
		panic("LangAuto is not supported by the parser at this time")
	default:
		panic(fmt.Sprintf("unknown shell language variant: %d", ))
	}
	return func( *Parser) { .lang =  }
}

func ( LangVariant) () string {
	switch  {
	case LangBash:
		return "bash"
	case LangPOSIX:
		return "posix"
	case LangMirBSDKorn:
		return "mksh"
	case LangBats:
		return "bats"
	case LangAuto:
		return "auto"
	}
	return "unknown shell language variant"
}

func ( *LangVariant) ( string) error {
	switch  {
	case "bash":
		* = LangBash
	case "posix", "sh":
		* = LangPOSIX
	case "mksh":
		* = LangMirBSDKorn
	case "bats":
		* = LangBats
	case "auto":
		* = LangAuto
	default:
		return fmt.Errorf("unknown shell language variant: %q", )
	}
	return nil
}

func ( LangVariant) () bool {
	return  == LangBash ||  == LangBats
}

// StopAt configures the lexer to stop at an arbitrary word, treating it
// as if it were the end of the input. It can contain any characters
// except whitespace, and cannot be over four bytes in size.
//
// This can be useful to embed shell code within another language, as
// one can use a special word to mark the delimiters between the two.
//
// As a word, it will only apply when following whitespace or a
// separating token. For example, StopAt("$$") will act on the inputs
// "foo $$" and "foo;$$", but not on "foo '$$'".
//
// The match is done by prefix, so the example above will also act on
// "foo $$bar".
func ( string) ParserOption {
	if len() > 4 {
		panic("stop word can't be over four bytes in size")
	}
	if strings.ContainsAny(, " \t\n\r") {
		panic("stop word can't contain whitespace characters")
	}
	return func( *Parser) { .stopAt = []byte() }
}

// NewParser allocates a new Parser and applies any number of options.
func ( ...ParserOption) *Parser {
	 := &Parser{}
	for ,  := range  {
		()
	}
	return 
}

// Parse reads and parses a shell program with an optional name. It
// returns the parsed program if no issues were encountered. Otherwise,
// an error is returned. Reads from r are buffered.
//
// Parse can be called more than once, but not concurrently. That is, a
// Parser can be reused once it is done working.
func ( *Parser) ( io.Reader,  string) (*File, error) {
	.reset()
	.f = &File{Name: }
	.src = 
	.rune()
	.next()
	.f.Stmts, .f.Last = .stmtList()
	if .err == nil {
		// EOF immediately after heredoc word so no newline to
		// trigger it
		.doHeredocs()
	}
	return .f, .err
}

// Stmts reads and parses statements one at a time, calling a function
// each time one is parsed. If the function returns false, parsing is
// stopped and the function is not called again.
func ( *Parser) ( io.Reader,  func(*Stmt) bool) error {
	.reset()
	.f = &File{}
	.src = 
	.rune()
	.next()
	.stmts()
	if .err == nil {
		// EOF immediately after heredoc word so no newline to
		// trigger it
		.doHeredocs()
	}
	return .err
}

type wrappedReader struct {
	*Parser
	io.Reader

	lastLine    int
	accumulated []*Stmt
	fn          func([]*Stmt) bool
}

func ( *wrappedReader) ( []byte) ( int,  error) {
	// If we lexed a newline for the first time, we just finished a line, so
	// we may need to give a callback for the edge cases below not covered
	// by Parser.Stmts.
	if (.r == '\n' || .r == escNewl) && .line > .lastLine {
		if .Incomplete() {
			// Incomplete statement; call back to print "> ".
			if !.fn(.accumulated) {
				return 0, io.EOF
			}
		} else if len(.accumulated) == 0 {
			// Nothing was parsed; call back to print another "$ ".
			if !.fn(nil) {
				return 0, io.EOF
			}
		}
		.lastLine = .line
	}
	return .Reader.Read()
}

// Interactive implements what is necessary to parse statements in an
// interactive shell. The parser will call the given function under two
// circumstances outlined below.
//
// If a line containing any number of statements is parsed, the function will be
// called with said statements.
//
// If a line ending in an incomplete statement is parsed, the function will be
// called with any fully parsed statements, and [Parser.Incomplete] will return true.
//
// One can imagine a simple interactive shell implementation as follows:
//
//	fmt.Fprintf(os.Stdout, "$ ")
//	parser.Interactive(os.Stdin, func(stmts []*syntax.Stmt) bool {
//		if parser.Incomplete() {
//			fmt.Fprintf(os.Stdout, "> ")
//			return true
//		}
//		run(stmts)
//		fmt.Fprintf(os.Stdout, "$ ")
//		return true
//	}
//
// If the callback function returns false, parsing is stopped and the function
// is not called again.
func ( *Parser) ( io.Reader,  func([]*Stmt) bool) error {
	 := wrappedReader{Parser: , Reader: , fn: }
	return .Stmts(&, func( *Stmt) bool {
		.accumulated = append(.accumulated, )
		// We finished parsing a statement and we're at a newline token,
		// so we finished fully parsing a number of statements. Call
		// back to run the statements and print "$ ".
		if .tok == _Newl {
			if !(.accumulated) {
				return false
			}
			.accumulated = .accumulated[:0]
			// The callback above would already print "$ ", so we
			// don't want the subsequent wrappedReader.Read to cause
			// another "$ " print thinking that nothing was parsed.
			.lastLine = .line + 1
		}
		return true
	})
}

// Words reads and parses words one at a time, calling a function each time one
// is parsed. If the function returns false, parsing is stopped and the function
// is not called again.
//
// Newlines are skipped, meaning that multi-line input will work fine. If the
// parser encounters a token that isn't a word, such as a semicolon, an error
// will be returned.
//
// Note that the lexer doesn't currently tokenize spaces, so it may need to read
// a non-space byte such as a newline or a letter before finishing the parsing
// of a word. This will be fixed in the future.
func ( *Parser) ( io.Reader,  func(*Word) bool) error {
	.reset()
	.f = &File{}
	.src = 
	.rune()
	.next()
	for {
		.got(_Newl)
		 := .getWord()
		if  == nil {
			if .tok != _EOF {
				.curErr("%s is not a valid word", .tok)
			}
			return .err
		}
		if !() {
			return nil
		}
	}
}

// Document parses a single here-document word. That is, it parses the input as
// if they were lines following a <<EOF redirection.
//
// In practice, this is the same as parsing the input as if it were within
// double quotes, but without having to escape all double quote characters.
// Similarly, the here-document word parsed here cannot be ended by any
// delimiter other than reaching the end of the input.
func ( *Parser) ( io.Reader) (*Word, error) {
	.reset()
	.f = &File{}
	.src = 
	.rune()
	.quote = hdocBody
	.hdocStops = [][]byte{[]byte("MVDAN_CC_SH_SYNTAX_EOF")}
	.parsingDoc = true
	.next()
	 := .getWord()
	return , .err
}

// Arithmetic parses a single arithmetic expression. That is, as if the input
// were within the $(( and )) tokens.
func ( *Parser) ( io.Reader) (ArithmExpr, error) {
	.reset()
	.f = &File{}
	.src = 
	.rune()
	.quote = arithmExpr
	.next()
	 := .arithmExpr(false)
	return , .err
}

// Parser holds the internal state of the parsing mechanism of a
// program.
type Parser struct {
	src io.Reader
	bs  []byte // current chunk of read bytes
	bsp int    // pos within chunk for the rune after r
	r   rune   // next rune
	w   int    // width of r

	f *File

	spaced bool // whether tok has whitespace on its left

	err     error // lexer/parser error
	readErr error // got a read error, but bytes left

	tok token  // current token
	val string // current value (valid if tok is _Lit*)

	// position of r, to be converted to Parser.pos later
	offs, line, col int

	pos Pos // position of tok

	// TODO: Guard against offset overflow too. Less likely as it's 32-bit,
	// whereas line and col are 16-bit.
	lineOverflow bool
	colOverflow  bool

	quote   quoteState // current lexer state
	eqlOffs int        // position of '=' in val (a literal)

	keepComments bool
	lang         LangVariant

	stopAt []byte

	forbidNested bool

	// list of pending heredoc bodies
	buriedHdocs int
	heredocs    []*Redirect

	hdocStops [][]byte // stack of end words for open heredocs

	parsingDoc bool // true if using Parser.Document

	// openStmts is how many entire statements we're currently parsing. A
	// non-zero number means that we require certain tokens or words before
	// reaching EOF.
	openStmts int
	// openBquotes is how many levels of backquotes are open at the moment.
	openBquotes int

	// lastBquoteEsc is how many times the last backquote token was escaped
	lastBquoteEsc int
	// buriedBquotes is like openBquotes, but saved for when the parser
	// comes out of single quotes
	buriedBquotes int

	rxOpenParens int
	rxFirstPart  bool

	accComs []Comment
	curComs *[]Comment

	litBatch  []Lit
	wordBatch []wordAlloc
	stmtBatch []Stmt
	callBatch []callAlloc

	readBuf [bufSize]byte
	litBuf  [bufSize]byte
	litBs   []byte
}

// Incomplete reports whether the parser is waiting to read more bytes because
// it needs to finish properly parsing a statement.
//
// It is only safe to call while the parser is blocked on a read. For an example
// use case, see [Parser.Interactive].
func ( *Parser) () bool {
	// If we're in a quote state other than noState, we're parsing a node
	// such as a double-quoted string.
	// If there are any open statements, we need to finish them.
	// If we're constructing a literal, we need to finish it.
	return .quote != noState || .openStmts > 0 || .litBs != nil
}

const bufSize = 1 << 10

func ( *Parser) () {
	.tok, .val = illegalTok, ""
	.eqlOffs = 0
	.bs, .bsp = nil, 0
	.offs, .line, .col = 0, 1, 1
	.r, .w = 0, 0
	.err, .readErr = nil, nil
	.quote, .forbidNested = noState, false
	.openStmts = 0
	.heredocs, .buriedHdocs = .heredocs[:0], 0
	.parsingDoc = false
	.openBquotes, .buriedBquotes = 0, 0
	.accComs, .curComs = nil, &.accComs
	.litBatch = nil
	.wordBatch = nil
	.stmtBatch = nil
	.callBatch = nil
}

func ( *Parser) () Pos {
	// TODO: detect offset overflow while lexing as well.
	var ,  uint
	if !.lineOverflow {
		 = uint(.line)
	}
	if !.colOverflow {
		 = uint(.col)
	}
	return NewPos(uint(.offs+.bsp-.w), , )
}

func ( *Parser) ( Pos,  string) *Lit {
	if len(.litBatch) == 0 {
		.litBatch = make([]Lit, 64)
	}
	 := &.litBatch[0]
	.litBatch = .litBatch[1:]
	.ValuePos = 
	.ValueEnd = .nextPos()
	.Value = 
	return 
}

type wordAlloc struct {
	word  Word
	parts [1]WordPart
}

func ( *Parser) () *Word {
	if len(.wordBatch) == 0 {
		.wordBatch = make([]wordAlloc, 32)
	}
	 := &.wordBatch[0]
	.wordBatch = .wordBatch[1:]
	 := &.word
	.Parts = .wordParts(.parts[:0])
	return 
}

func ( *Parser) ( WordPart) *Word {
	if len(.wordBatch) == 0 {
		.wordBatch = make([]wordAlloc, 32)
	}
	 := &.wordBatch[0]
	.wordBatch = .wordBatch[1:]
	 := &.word
	.Parts = .parts[:1]
	.Parts[0] = 
	return 
}

func ( *Parser) ( Pos) *Stmt {
	if len(.stmtBatch) == 0 {
		.stmtBatch = make([]Stmt, 32)
	}
	 := &.stmtBatch[0]
	.stmtBatch = .stmtBatch[1:]
	.Position = 
	return 
}

type callAlloc struct {
	ce CallExpr
	ws [4]*Word
}

func ( *Parser) ( *Word) *CallExpr {
	if len(.callBatch) == 0 {
		.callBatch = make([]callAlloc, 32)
	}
	 := &.callBatch[0]
	.callBatch = .callBatch[1:]
	 := &.ce
	.Args = .ws[:1]
	.Args[0] = 
	return 
}

//go:generate stringer -type=quoteState

type quoteState uint32

const (
	noState quoteState = 1 << iota
	subCmd
	subCmdBckquo
	dblQuotes
	hdocWord
	hdocBody
	hdocBodyTabs
	arithmExpr
	arithmExprLet
	arithmExprCmd
	arithmExprBrack
	testExpr
	testExprRegexp
	switchCase
	paramExpName
	paramExpSlice
	paramExpRepl
	paramExpExp
	arrayElems

	allKeepSpaces = paramExpRepl | dblQuotes | hdocBody |
		hdocBodyTabs | paramExpExp
	allRegTokens = noState | subCmd | subCmdBckquo | hdocWord |
		switchCase | arrayElems | testExpr
	allArithmExpr = arithmExpr | arithmExprLet | arithmExprCmd |
		arithmExprBrack | paramExpSlice
	allParamReg = paramExpName | paramExpSlice
	allParamExp = allParamReg | paramExpRepl | paramExpExp | arithmExprBrack
)

type saveState struct {
	quote       quoteState
	buriedHdocs int
}

func ( *Parser) ( quoteState) ( saveState) {
	.quote, .buriedHdocs = .quote, .buriedHdocs
	.buriedHdocs, .quote = len(.heredocs), 
	return
}

func ( *Parser) ( saveState) {
	.quote, .buriedHdocs = .quote, .buriedHdocs
}

func ( *Parser) ( *Word) ([]byte, bool) {
	 := make([]byte, 0, 4)
	 := false
	for ,  := range .Parts {
		,  = .unquotedWordPart(, , false)
	}
	return , 
}

func ( *Parser) ( []byte,  WordPart,  bool) ( []byte,  bool) {
	switch x := .(type) {
	case *Lit:
		for  := 0;  < len(.Value); ++ {
			if  := .Value[];  == '\\' && ! {
				if ++;  < len(.Value) {
					 = append(, .Value[])
				}
				 = true
			} else {
				 = append(, )
			}
		}
	case *SglQuoted:
		 = append(, []byte(.Value)...)
		 = true
	case *DblQuoted:
		for ,  := range .Parts {
			, _ = .(, , true)
		}
		 = true
	}
	return , 
}

func ( *Parser) () {
	 := .heredocs[.buriedHdocs:]
	if len() == 0 {
		// Nothing do do; don't even issue a read.
		return
	}
	.rune() // consume '\n', since we know p.tok == _Newl
	 := .quote
	.heredocs = .heredocs[:.buriedHdocs]
	for ,  := range  {
		if .err != nil {
			break
		}
		.quote = hdocBody
		if .Op == DashHdoc {
			.quote = hdocBodyTabs
		}
		,  := .unquotedWordBytes(.Word)
		.hdocStops = append(.hdocStops, )
		if  > 0 && .r == '\n' {
			.rune()
		}
		 := .line
		if  {
			.Hdoc = .quotedHdocWord()
		} else {
			.next()
			.Hdoc = .getWord()
		}
		if .Hdoc != nil {
			 = int(.Hdoc.End().Line())
		}
		if  < .line {
			// TODO: It seems like this triggers more often than it
			// should. Look into it.
			 := .lit(.nextPos(), "")
			if .Hdoc == nil {
				.Hdoc = .wordOne()
			} else {
				.Hdoc.Parts = append(.Hdoc.Parts, )
			}
		}
		if  := .hdocStops[len(.hdocStops)-1];  != nil {
			.posErr(.Pos(), "unclosed here-document '%s'", )
		}
		.hdocStops = .hdocStops[:len(.hdocStops)-1]
	}
	.quote = 
}

func ( *Parser) ( token) bool {
	if .tok ==  {
		.next()
		return true
	}
	return false
}

func ( *Parser) ( string) (Pos, bool) {
	 := .pos
	if .tok == _LitWord && .val ==  {
		.next()
		return , true
	}
	return , false
}

func readableStr( string) string {
	// don't quote tokens like & or }
	if  != "" && [0] >= 'a' && [0] <= 'z' {
		return strconv.Quote()
	}
	return 
}

func ( *Parser) ( Pos, ,  string) {
	 := readableStr()
	.posErr(, "%s must be followed by %s", , )
}

func ( *Parser) ( Pos,  string) {
	.followErr(, , "an expression")
}

func ( *Parser) ( Pos,  string,  token) {
	if !.got() {
		.followErr(, , .String())
	}
}

func ( *Parser) ( Pos, ,  string) Pos {
	,  := .gotRsrv()
	if ! {
		.followErr(, , fmt.Sprintf("%q", ))
	}
	return 
}

func ( *Parser) ( string,  Pos,  ...string) ([]*Stmt, []Comment) {
	if .got(semicolon) {
		return nil, nil
	}
	 := .got(_Newl)
	,  := .stmtList(...)
	if len() < 1 && ! {
		.followErr(, , "a statement list")
	}
	return , 
}

func ( *Parser) ( token,  Pos) *Word {
	 := .getWord()
	if  == nil {
		.followErr(, .String(), "a word")
	}
	return 
}

func ( *Parser) ( Node, ,  string) Pos {
	,  := .gotRsrv()
	if ! {
		.posErr(.Pos(), "%s statement must end with %q", , )
	}
	return 
}

func ( *Parser) ( Pos,  token) {
	.posErr(, "reached %s without closing quote %s",
		.tok.String(), )
}

func ( *Parser) ( Pos, ,  any) {
	.posErr(, "reached %s without matching %s with %s",
		.tok.String(), , )
}

func ( *Parser) ( Pos, ,  token) Pos {
	 := .pos
	if !.got() {
		.matchingErr(, , )
	}
	return 
}

func ( *Parser) ( error) {
	if .err == nil {
		.err = 
		.bsp = len(.bs) + 1
		.r = utf8.RuneSelf
		.w = 1
		.tok = _EOF
	}
}

// IsIncomplete reports whether a Parser error could have been avoided with
// extra input bytes. For example, if an [io.EOF] was encountered while there was
// an unclosed quote or parenthesis.
func ( error) bool {
	,  := .(ParseError)
	return  && .Incomplete
}

// IsKeyword returns true if the given word is part of the language keywords.
func ( string) bool {
	// This list has been copied from the bash 5.1 source code, file y.tab.c +4460
	switch  {
	case
		"!",
		"[[", // only if COND_COMMAND is defined
		"]]", // only if COND_COMMAND is defined
		"case",
		"coproc", // only if COPROCESS_SUPPORT is defined
		"do",
		"done",
		"else",
		"esac",
		"fi",
		"for",
		"function",
		"if",
		"in",
		"select", // only if SELECT_COMMAND is defined
		"then",
		"time", // only if COMMAND_TIMING is defined
		"until",
		"while",
		"{",
		"}":
		return true
	}
	return false
}

// ParseError represents an error found when parsing a source file, from which
// the parser cannot recover.
type ParseError struct {
	Filename string
	Pos      Pos
	Text     string

	Incomplete bool
}

func ( ParseError) () string {
	if .Filename == "" {
		return fmt.Sprintf("%s: %s", .Pos.String(), .Text)
	}
	return fmt.Sprintf("%s:%s: %s", .Filename, .Pos.String(), .Text)
}

// LangError is returned when the parser encounters code that is only valid in
// other shell language variants. The error includes what feature is not present
// in the current language variant, and what languages support it.
type LangError struct {
	Filename string
	Pos      Pos
	Feature  string
	Langs    []LangVariant
}

func ( LangError) () string {
	var  bytes.Buffer
	if .Filename != "" {
		.WriteString(.Filename + ":")
	}
	.WriteString(.Pos.String() + ": ")
	.WriteString(.Feature)
	if strings.HasSuffix(.Feature, "s") {
		.WriteString(" are a ")
	} else {
		.WriteString(" is a ")
	}
	for ,  := range .Langs {
		if  > 0 {
			.WriteString("/")
		}
		.WriteString(.String())
	}
	.WriteString(" feature")
	return .String()
}

func ( *Parser) ( Pos,  string,  ...any) {
	.errPass(ParseError{
		Filename:   .f.Name,
		Pos:        ,
		Text:       fmt.Sprintf(, ...),
		Incomplete: .tok == _EOF && .Incomplete(),
	})
}

func ( *Parser) ( string,  ...any) {
	.posErr(.pos, , ...)
}

func ( *Parser) ( Pos,  string,  ...LangVariant) {
	.errPass(LangError{
		Filename: .f.Name,
		Pos:      ,
		Feature:  ,
		Langs:    ,
	})
}

func ( *Parser) ( func(*Stmt) bool,  ...string) {
	 := true
:
	for .tok != _EOF {
		 := .got(_Newl)
		switch .tok {
		case _LitWord:
			for ,  := range  {
				if .val ==  {
					break 
				}
			}
		case rightParen:
			if .quote == subCmd {
				break 
			}
		case bckQuote:
			if .backquoteEnd() {
				break 
			}
		case dblSemicolon, semiAnd, dblSemiAnd, semiOr:
			if .quote == switchCase {
				break 
			}
			.curErr("%s can only be used in a case clause", .tok)
		}
		if ! && ! {
			.curErr("statements must be separated by &, ; or a newline")
		}
		if .tok == _EOF {
			break
		}
		.openStmts++
		 := .getStmt(true, false, false)
		.openStmts--
		if  == nil {
			.invalidStmtStart()
			break
		}
		 = .Semicolon.IsValid()
		if !() {
			break
		}
	}
}

func ( *Parser) ( ...string) ([]*Stmt, []Comment) {
	var  []*Stmt
	var  []Comment
	 := func( *Stmt) bool {
		 = append(, )
		return true
	}
	.stmts(, ...)
	 := len(.accComs)
	if .tok == _LitWord && (.val == "elif" || .val == "else" || .val == "fi") {
		// Split the comments, so that any aligned with an opening token
		// get attached to it. For example:
		//
		//     if foo; then
		//         # inside the body
		//     # document the else
		//     else
		//     fi
		// TODO(mvdan): look into deduplicating this with similar logic
		// in caseItems.
		for  := len(.accComs) - 1;  >= 0; -- {
			 := .accComs[]
			if .Pos().Col() != .pos.Col() {
				break
			}
			 = 
		}
	}
	if  > 0 { // keep last nil if empty
		 = .accComs[:]
	}
	.accComs = .accComs[:]
	return , 
}

func ( *Parser) () {
	switch .tok {
	case semicolon, and, or, andAnd, orOr:
		.curErr("%s can only immediately follow a statement", .tok)
	case rightParen:
		.curErr("%s can only be used to close a subshell", .tok)
	default:
		.curErr("%s is not a valid start for a statement", .tok)
	}
}

func ( *Parser) () *Word {
	if  := .wordAnyNumber(); len(.Parts) > 0 && .err == nil {
		return 
	}
	return nil
}

func ( *Parser) () *Lit {
	switch .tok {
	case _Lit, _LitWord, _LitRedir:
		 := .lit(.pos, .val)
		.next()
		return 
	}
	return nil
}

func ( *Parser) ( []WordPart) []WordPart {
	for {
		 := .wordPart()
		if  == nil {
			if len() == 0 {
				return nil // normalize empty lists into nil
			}
			return 
		}
		 = append(, )
		if .spaced {
			return 
		}
	}
}

func ( *Parser) () {
	if .forbidNested {
		.curErr("expansions not allowed in heredoc words")
	}
}

func ( *Parser) () WordPart {
	switch .tok {
	case _Lit, _LitWord, _LitRedir:
		 := .lit(.pos, .val)
		.next()
		return 
	case dollBrace:
		.ensureNoNested()
		switch .r {
		case '|':
			if .lang != LangMirBSDKorn {
				.curErr(`"${|stmts;}" is a mksh feature`)
			}
			fallthrough
		case ' ', '\t', '\n':
			if .lang != LangMirBSDKorn {
				.curErr(`"${ stmts;}" is a mksh feature`)
			}
			 := &CmdSubst{
				Left:     .pos,
				TempFile: .r != '|',
				ReplyVar: .r == '|',
			}
			 := .preNested(subCmd)
			.rune() // don't tokenize '|'
			.next()
			.Stmts, .Last = .stmtList("}")
			.postNested()
			,  := .gotRsrv("}")
			if ! {
				.matchingErr(.Left, "${", "}")
			}
			.Right = 
			return 
		default:
			return .paramExp()
		}
	case dollDblParen, dollBrack:
		.ensureNoNested()
		 := .tok
		 := &ArithmExp{Left: .pos, Bracket:  == dollBrack}
		var  saveState
		if .Bracket {
			 = .preNested(arithmExprBrack)
		} else {
			 = .preNested(arithmExpr)
		}
		.next()
		if .got(hash) {
			if .lang != LangMirBSDKorn {
				.langErr(.Pos(), "unsigned expressions", LangMirBSDKorn)
			}
			.Unsigned = true
		}
		.X = .followArithm(, .Left)
		if .Bracket {
			if .tok != rightBrack {
				.arithmMatchingErr(.Left, dollBrack, rightBrack)
			}
			.postNested()
			.Right = .pos
			.next()
		} else {
			.Right = .arithmEnd(dollDblParen, .Left, )
		}
		return 
	case dollParen:
		.ensureNoNested()
		 := &CmdSubst{Left: .pos}
		 := .preNested(subCmd)
		.next()
		.Stmts, .Last = .stmtList()
		.postNested()
		.Right = .matched(.Left, leftParen, rightParen)
		return 
	case dollar:
		 := .r
		switch {
		case singleRuneParam():
			.tok, .val = _LitWord, string()
			.rune()
		case 'a' <=  &&  <= 'z', 'A' <=  &&  <= 'Z',
			'0' <=  &&  <= '9',  == '_',  == '\\':
			.advanceNameCont()
		default:
			 := .lit(.pos, "$")
			.next()
			return 
		}
		.ensureNoNested()
		 := &ParamExp{Dollar: .pos, Short: true}
		.pos = posAddCol(.pos, 1)
		.Param = .getLit()
		if .Param != nil && .Param.Value == "" {
			 := .lit(.Dollar, "$")
			// e.g. "$\\\"" within double quotes, so we must
			// keep the rest of the literal characters.
			.ValueEnd = posAddCol(.ValuePos, 1)
			return 
		}
		return 
	case cmdIn, cmdOut:
		.ensureNoNested()
		 := &ProcSubst{Op: ProcOperator(.tok), OpPos: .pos}
		 := .preNested(subCmd)
		.next()
		.Stmts, .Last = .stmtList()
		.postNested()
		.Rparen = .matched(.OpPos, token(.Op), rightParen)
		return 
	case sglQuote, dollSglQuote:
		 := &SglQuoted{Left: .pos, Dollar: .tok == dollSglQuote}
		 := .r
		for .newLit(); ;  = .rune() {
			switch  {
			case '\\':
				if .Dollar {
					.rune()
				}
			case '\'':
				.Right = .nextPos()
				.Value = .endLit()

				// restore openBquotes
				.openBquotes = .buriedBquotes
				.buriedBquotes = 0

				.rune()
				.next()
				return 
			case escNewl:
				.litBs = append(.litBs, '\\', '\n')
			case utf8.RuneSelf:
				.tok = _EOF
				.quoteErr(.Pos(), sglQuote)
				return nil
			}
		}
	case dblQuote, dollDblQuote:
		if .quote == dblQuotes {
			// p.tok == dblQuote, as "foo$" puts $ in the lit
			return nil
		}
		return .dblQuoted()
	case bckQuote:
		if .backquoteEnd() {
			return nil
		}
		.ensureNoNested()
		 := &CmdSubst{Left: .pos, Backquotes: true}
		 := .preNested(subCmdBckquo)
		.openBquotes++

		// The lexer didn't call p.rune for us, so that it could have
		// the right p.openBquotes to properly handle backslashes.
		.rune()

		.next()
		.Stmts, .Last = .stmtList()
		if .tok == bckQuote && .lastBquoteEsc < .openBquotes-1 {
			// e.g. found ` before the nested backquote \` was closed.
			.tok = _EOF
			.quoteErr(.Pos(), bckQuote)
		}
		.postNested()
		.openBquotes--
		.Right = .pos

		// Like above, the lexer didn't call p.rune for us.
		.rune()
		if !.got(bckQuote) {
			.quoteErr(.Pos(), bckQuote)
		}
		return 
	case globQuest, globStar, globPlus, globAt, globExcl:
		if .lang == LangPOSIX {
			.langErr(.pos, "extended globs", LangBash, LangMirBSDKorn)
		}
		 := &ExtGlob{Op: GlobOperator(.tok), OpPos: .pos}
		 := 1
		 := .r
	:
		for .newLit(); ;  = .rune() {
			switch  {
			case utf8.RuneSelf:
				break 
			case '(':
				++
			case ')':
				if --;  == 0 {
					break 
				}
			}
		}
		.Pattern = .lit(posAddCol(.OpPos, 2), .endLit())
		.rune()
		.next()
		if  != 0 {
			.matchingErr(.OpPos, .Op, rightParen)
		}
		return 
	default:
		return nil
	}
}

func ( *Parser) () *DblQuoted {
	 := &struct {
		 DblQuoted
		  [1]WordPart
	}{
		: DblQuoted{Left: .pos, Dollar: .tok == dollDblQuote},
	}
	 := &.
	 := .quote
	.quote = dblQuotes
	.next()
	.Parts = .wordParts(.[:0])
	.quote = 
	.Right = .pos
	if !.got(dblQuote) {
		.quoteErr(.Pos(), dblQuote)
	}
	return 
}

func singleRuneParam( rune) bool {
	switch  {
	case '@', '*', '#', '$', '?', '!', '-',
		'0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
		return true
	}
	return false
}

func ( *Parser) () *ParamExp {
	 := &ParamExp{Dollar: .pos}
	 := .quote
	.quote = paramExpName
	if .r == '#' {
		.tok = hash
		.pos = .nextPos()
		.rune()
	} else {
		.next()
	}
	switch .tok {
	case hash:
		if paramNameOp(.r) {
			.Length = true
			.next()
		}
	case perc:
		if .lang != LangMirBSDKorn {
			.posErr(.Pos(), `"${%%foo}" is a mksh feature`)
		}
		if paramNameOp(.r) {
			.Width = true
			.next()
		}
	case exclMark:
		if paramNameOp(.r) {
			.Excl = true
			.next()
		}
	}
	 := .tok
	switch .tok {
	case _Lit, _LitWord:
		if !numberLiteral(.val) && !ValidName(.val) {
			.curErr("invalid parameter name")
		}
		.Param = .lit(.pos, .val)
		.next()
	case quest, minus:
		if .Length && .r != '}' {
			// actually ${#-default}, not ${#-}; fix the ambiguity
			.Length = false
			.Param = .lit(posAddCol(.pos, -1), "#")
			.Param.ValueEnd = .pos
			break
		}
		fallthrough
	case at, star, hash, exclMark, dollar:
		.Param = .lit(.pos, .tok.String())
		.next()
	default:
		.curErr("parameter expansion requires a literal")
	}
	switch .tok {
	case _Lit, _LitWord:
		.curErr("%s cannot be followed by a word", )
	case rightBrace:
		if .Excl && .lang == LangPOSIX {
			.posErr(.Pos(), `"${!foo}" is a bash/mksh feature`)
		}
		.Rbrace = .pos
		.quote = 
		.next()
		return 
	case leftBrack:
		if .lang == LangPOSIX {
			.langErr(.pos, "arrays", LangBash, LangMirBSDKorn)
		}
		if !ValidName(.Param.Value) {
			.curErr("cannot index a special parameter name")
		}
		.Index = .eitherIndex()
	}
	if .tok == rightBrace {
		.Rbrace = .pos
		.quote = 
		.next()
		return 
	}
	if .tok != _EOF && (.Length || .Width) {
		.curErr("cannot combine multiple parameter expansion operators")
	}
	switch .tok {
	case slash, dblSlash:
		// pattern search and replace
		if .lang == LangPOSIX {
			.langErr(.pos, "search and replace", LangBash, LangMirBSDKorn)
		}
		.Repl = &Replace{All: .tok == dblSlash}
		.quote = paramExpRepl
		.next()
		.Repl.Orig = .getWord()
		.quote = paramExpExp
		if .got(slash) {
			.Repl.With = .getWord()
		}
	case colon:
		// slicing
		if .lang == LangPOSIX {
			.langErr(.pos, "slicing", LangBash, LangMirBSDKorn)
		}
		.Slice = &Slice{}
		 := .pos
		.quote = paramExpSlice
		if .next(); .tok != colon {
			.Slice.Offset = .followArithm(colon, )
		}
		 = .pos
		if .got(colon) {
			.Slice.Length = .followArithm(colon, )
		}
		// Need to use a different matched style so arithm errors
		// get reported correctly
		.quote = 
		.Rbrace = .pos
		.matchedArithm(.Dollar, dollBrace, rightBrace)
		return 
	case caret, dblCaret, comma, dblComma:
		// upper/lower case
		if !.lang.isBash() {
			.langErr(.pos, "this expansion operator", LangBash)
		}
		.Exp = .paramExpExp()
	case at, star:
		switch {
		case .tok == at && .lang == LangPOSIX:
			.langErr(.pos, "this expansion operator", LangBash, LangMirBSDKorn)
		case .tok == star && !.Excl:
			.curErr("not a valid parameter expansion operator: %v", .tok)
		case .Excl && .r == '}':
			if !.lang.isBash() {
				.posErr(.Pos(), `"${!foo`+.tok.String()+`}" is a bash feature`)
			}
			.Names = ParNamesOperator(.tok)
			.next()
		default:
			.Exp = .paramExpExp()
		}
	case plus, colPlus, minus, colMinus, quest, colQuest, assgn, colAssgn,
		perc, dblPerc, hash, dblHash:
		.Exp = .paramExpExp()
	case _EOF:
	default:
		.curErr("not a valid parameter expansion operator: %v", .tok)
	}
	.quote = 
	.Rbrace = .pos
	.matched(.Dollar, dollBrace, rightBrace)
	return 
}

func ( *Parser) () *Expansion {
	 := ParExpOperator(.tok)
	.quote = paramExpExp
	.next()
	if  == OtherParamOps {
		switch .tok {
		case _Lit, _LitWord:
		default:
			.curErr("@ expansion operator requires a literal")
		}
		switch .val {
		case "a", "u", "A", "E", "K", "L", "P", "U":
			if !.lang.isBash() {
				.langErr(.pos, "this expansion operator", LangBash)
			}
		case "#":
			if .lang != LangMirBSDKorn {
				.langErr(.pos, "this expansion operator", LangMirBSDKorn)
			}
		case "Q":
		default:
			.curErr("invalid @ expansion operator")
		}
	}
	return &Expansion{Op: , Word: .getWord()}
}

func ( *Parser) () ArithmExpr {
	 := .quote
	 := .pos
	.quote = arithmExprBrack
	.next()
	if .tok == star || .tok == at {
		.tok, .val = _LitWord, .tok.String()
	}
	 := .followArithm(leftBrack, )
	.quote = 
	.matchedArithm(, leftBrack, rightBrack)
	return 
}

func ( *Parser) () bool {
	switch .tok {
	case _EOF, _Newl, semicolon, and, or, andAnd, orOr, orAnd, dblSemicolon,
		semiAnd, dblSemiAnd, semiOr, rightParen:
		return true
	case bckQuote:
		return .backquoteEnd()
	}
	return false
}

func ( *Parser) () bool {
	return .lastBquoteEsc < .openBquotes
}

// ValidName returns whether val is a valid name as per the POSIX spec.
func ( string) bool {
	if  == "" {
		return false
	}
	for ,  := range  {
		switch {
		case 'a' <=  &&  <= 'z':
		case 'A' <=  &&  <= 'Z':
		case  == '_':
		case  > 0 && '0' <=  &&  <= '9':
		default:
			return false
		}
	}
	return true
}

func numberLiteral( string) bool {
	for ,  := range  {
		if '0' >  ||  > '9' {
			return false
		}
	}
	return true
}

func ( *Parser) () bool {
	if .tok != _Lit && .tok != _LitWord {
		return false
	}
	if  := .eqlOffs;  > 0 {
		if .val[-1] == '+' && .lang != LangPOSIX {
			-- // a+=x
		}
		if ValidName(.val[:]) {
			return true
		}
	} else if !ValidName(.val) {
		return false // *[i]=x
	}
	return .r == '[' // a[i]=x
}

func ( *Parser) ( bool) *Assign {
	 := &Assign{}
	if .eqlOffs > 0 { // foo=bar
		 := .eqlOffs
		if .lang != LangPOSIX && .val[.eqlOffs-1] == '+' {
			// a+=b
			.Append = true
			--
		}
		.Name = .lit(.pos, .val[:])
		// since we're not using the entire p.val
		.Name.ValueEnd = posAddCol(.Name.ValuePos, )
		 := .lit(posAddCol(.pos, 1), .val[.eqlOffs+1:])
		if .Value != "" {
			.ValuePos = posAddCol(.ValuePos, .eqlOffs)
			.Value = .wordOne()
		}
		.next()
	} else { // foo[x]=bar
		.Name = .lit(.pos, .val)
		// hasValidIdent already checks p.r is '['
		.rune()
		.pos = posAddCol(.pos, 1)
		.Index = .eitherIndex()
		if .spaced || .stopToken() {
			if  {
				.followErr(.Pos(), "a[b]", "=")
			} else {
				.Naked = true
				return 
			}
		}
		if len(.val) > 0 && .val[0] == '+' {
			.Append = true
			.val = .val[1:]
			.pos = posAddCol(.pos, 1)
		}
		if len(.val) < 1 || .val[0] != '=' {
			if .Append {
				.followErr(.Pos(), "a[b]+", "=")
			} else {
				.followErr(.Pos(), "a[b]", "=")
			}
			return nil
		}
		.pos = posAddCol(.pos, 1)
		.val = .val[1:]
		if .val == "" {
			.next()
		}
	}
	if .spaced || .stopToken() {
		return 
	}
	if .Value == nil && .tok == leftParen {
		if .lang == LangPOSIX {
			.langErr(.pos, "arrays", LangBash, LangMirBSDKorn)
		}
		if .Index != nil {
			.curErr("arrays cannot be nested")
		}
		.Array = &ArrayExpr{Lparen: .pos}
		 := .quote
		if .lang.isBash() {
			 = arrayElems
		}
		 := .preNested()
		.next()
		.got(_Newl)
		for .tok != _EOF && .tok != rightParen {
			 := &ArrayElem{}
			.Comments, .accComs = .accComs, nil
			if .tok == leftBrack {
				 := .pos
				.Index = .eitherIndex()
				.follow(, `"[x]"`, assgn)
			}
			if .Value = .getWord(); .Value == nil {
				switch .tok {
				case leftParen:
					.curErr("arrays cannot be nested")
					return nil
				case _Newl, rightParen, leftBrack:
					// TODO: support [index]=[
				default:
					.curErr("array element values must be words")
					return nil
				}
			}
			if len(.accComs) > 0 {
				 := .accComs[0]
				if .Pos().Line() == .End().Line() {
					.Comments = append(.Comments, )
					.accComs = .accComs[1:]
				}
			}
			.Array.Elems = append(.Array.Elems, )
			.got(_Newl)
		}
		.Array.Last, .accComs = .accComs, nil
		.postNested()
		.Array.Rparen = .matched(.Array.Lparen, leftParen, rightParen)
	} else if  := .getWord();  != nil {
		if .Value == nil {
			.Value = 
		} else {
			.Value.Parts = append(.Value.Parts, .Parts...)
		}
	}
	return 
}

func ( *Parser) () bool {
	switch .tok {
	case rdrOut, appOut, rdrIn, dplIn, dplOut, clbOut, rdrInOut,
		hdoc, dashHdoc, wordHdoc, rdrAll, appAll, _LitRedir:
		return true
	}
	return false
}

func ( *Parser) ( *Stmt) {
	var  *Redirect
	if .Redirs == nil {
		var  struct {
			 [4]*Redirect
			  Redirect
		}
		.Redirs = .[:0]
		 = &.
		.Redirs = append(.Redirs, )
	} else {
		 = &Redirect{}
		.Redirs = append(.Redirs, )
	}
	.N = .getLit()
	if !.lang.isBash() && .N != nil && .N.Value[0] == '{' {
		.langErr(.N.Pos(), "{varname} redirects", LangBash)
	}
	if .lang == LangPOSIX && (.tok == rdrAll || .tok == appAll) {
		.langErr(.pos, "&> redirects", LangBash, LangMirBSDKorn)
	}
	.Op, .OpPos = RedirOperator(.tok), .pos
	.next()
	switch .Op {
	case Hdoc, DashHdoc:
		 := .quote
		.quote, .forbidNested = hdocWord, true
		.heredocs = append(.heredocs, )
		.Word = .followWordTok(token(.Op), .OpPos)
		.quote, .forbidNested = , false
		if .tok == _Newl {
			if len(.accComs) > 0 {
				 := .accComs[0]
				if .Pos().Line() == .End().Line() {
					.Comments = append(.Comments, )
					.accComs = .accComs[1:]
				}
			}
			.doHeredocs()
		}
	case WordHdoc:
		if .lang == LangPOSIX {
			.langErr(.OpPos, "herestrings", LangBash, LangMirBSDKorn)
		}
		fallthrough
	default:
		.Word = .followWordTok(token(.Op), .OpPos)
	}
}

func ( *Parser) (, ,  bool) *Stmt {
	,  := .gotRsrv("!")
	 := .stmt()
	if  {
		.Negated = true
		if .stopToken() {
			.posErr(.Pos(), `"!" cannot form a statement alone`)
		}
		if ,  := .gotRsrv("!");  {
			.posErr(.Pos(), `cannot negate a command multiple times`)
		}
	}
	if  = .gotStmtPipe(, false);  == nil || .err != nil {
		return nil
	}
	// instead of using recursion, iterate manually
	for .tok == andAnd || .tok == orOr {
		if  {
			// left associativity: in a list of BinaryCmds, the
			// right recursion should only read a single element
			return 
		}
		 := &BinaryCmd{
			OpPos: .pos,
			Op:    BinCmdOperator(.tok),
			X:     ,
		}
		.next()
		.got(_Newl)
		.Y = .(false, true, false)
		if .Y == nil || .err != nil {
			.followErr(.OpPos, .Op.String(), "a statement")
			return nil
		}
		 = .stmt(.Position)
		.Cmd = 
		.Comments, .X.Comments = .X.Comments, nil
	}
	if  {
		switch .tok {
		case semicolon:
			.Semicolon = .pos
			.next()
		case and:
			.Semicolon = .pos
			.next()
			.Background = true
		case orAnd:
			.Semicolon = .pos
			.next()
			.Coprocess = true
		}
	}
	if len(.accComs) > 0 && ! && ! {
		 := .accComs[0]
		if .Pos().Line() == .End().Line() {
			.Comments = append(.Comments, )
			.accComs = .accComs[1:]
		}
	}
	return 
}

func ( *Parser) ( *Stmt,  bool) *Stmt {
	.Comments, .accComs = .accComs, nil
	switch .tok {
	case _LitWord:
		switch .val {
		case "{":
			.block()
		case "if":
			.ifClause()
		case "while", "until":
			.whileClause(, .val == "until")
		case "for":
			.forClause()
		case "case":
			.caseClause()
		case "}":
			.curErr(`%q can only be used to close a block`, .val)
		case "then":
			.curErr(`%q can only be used in an if`, .val)
		case "elif":
			.curErr(`%q can only be used in an if`, .val)
		case "fi":
			.curErr(`%q can only be used to end an if`, .val)
		case "do":
			.curErr(`%q can only be used in a loop`, .val)
		case "done":
			.curErr(`%q can only be used to end a loop`, .val)
		case "esac":
			.curErr(`%q can only be used to end a case`, .val)
		case "!":
			if !.Negated {
				.curErr(`"!" can only be used in full statements`)
				break
			}
		case "[[":
			if .lang != LangPOSIX {
				.testClause()
			}
		case "]]":
			if .lang != LangPOSIX {
				.curErr(`%q can only be used to close a test`, .val)
			}
		case "let":
			if .lang != LangPOSIX {
				.letClause()
			}
		case "function":
			if .lang != LangPOSIX {
				.bashFuncDecl()
			}
		case "declare":
			if .lang.isBash() { // Note that mksh lacks this one.
				.declClause()
			}
		case "local", "export", "readonly", "typeset", "nameref":
			if .lang != LangPOSIX {
				.declClause()
			}
		case "time":
			if .lang != LangPOSIX {
				.timeClause()
			}
		case "coproc":
			if .lang.isBash() { // Note that mksh lacks this one.
				.coprocClause()
			}
		case "select":
			if .lang != LangPOSIX {
				.selectClause()
			}
		case "@test":
			if .lang == LangBats {
				.testDecl()
			}
		}
		if .Cmd != nil {
			break
		}
		if .hasValidIdent() {
			.callExpr(, nil, true)
			break
		}
		 := .lit(.pos, .val)
		if .next(); .got(leftParen) {
			.follow(.ValuePos, "foo(", rightParen)
			if .lang == LangPOSIX && !ValidName(.Value) {
				.posErr(.Pos(), "invalid func name")
			}
			.funcDecl(, , .ValuePos, true)
		} else {
			.callExpr(, .wordOne(), false)
		}
	case rdrOut, appOut, rdrIn, dplIn, dplOut, clbOut, rdrInOut,
		hdoc, dashHdoc, wordHdoc, rdrAll, appAll, _LitRedir:
		.doRedirect()
		.callExpr(, nil, false)
	case bckQuote:
		if .backquoteEnd() {
			return nil
		}
		fallthrough
	case _Lit, dollBrace, dollDblParen, dollParen, dollar, cmdIn, cmdOut,
		sglQuote, dollSglQuote, dblQuote, dollDblQuote, dollBrack,
		globQuest, globStar, globPlus, globAt, globExcl:
		if .hasValidIdent() {
			.callExpr(, nil, true)
			break
		}
		 := .wordAnyNumber()
		if .got(leftParen) {
			.posErr(.Pos(), "invalid func name")
		}
		.callExpr(, , false)
	case leftParen:
		.subshell()
	case dblLeftParen:
		.arithmExpCmd()
	default:
		if len(.Redirs) == 0 {
			return nil
		}
	}
	for .peekRedir() {
		.doRedirect()
	}
	// instead of using recursion, iterate manually
	for .tok == or || .tok == orAnd {
		if  {
			// left associativity: in a list of BinaryCmds, the
			// right recursion should only read a single element
			return 
		}
		if .tok == orAnd && .lang == LangMirBSDKorn {
			// No need to check for LangPOSIX, as on that language
			// we parse |& as two tokens.
			break
		}
		 := &BinaryCmd{OpPos: .pos, Op: BinCmdOperator(.tok), X: }
		.next()
		.got(_Newl)
		if .Y = .(.stmt(.pos), true); .Y == nil || .err != nil {
			.followErr(.OpPos, .Op.String(), "a statement")
			break
		}
		 = .stmt(.Position)
		.Cmd = 
		.Comments, .X.Comments = .X.Comments, nil
		// in "! x | y", the bang applies to the entire pipeline
		.Negated = .X.Negated
		.X.Negated = false
	}
	return 
}

func ( *Parser) ( *Stmt) {
	 := &Subshell{Lparen: .pos}
	 := .preNested(subCmd)
	.next()
	.Stmts, .Last = .stmtList()
	.postNested()
	.Rparen = .matched(.Lparen, leftParen, rightParen)
	.Cmd = 
}

func ( *Parser) ( *Stmt) {
	 := &ArithmCmd{Left: .pos}
	 := .preNested(arithmExprCmd)
	.next()
	if .got(hash) {
		if .lang != LangMirBSDKorn {
			.langErr(.Pos(), "unsigned expressions", LangMirBSDKorn)
		}
		.Unsigned = true
	}
	.X = .followArithm(dblLeftParen, .Left)
	.Right = .arithmEnd(dblLeftParen, .Left, )
	.Cmd = 
}

func ( *Parser) ( *Stmt) {
	 := &Block{Lbrace: .pos}
	.next()
	.Stmts, .Last = .stmtList("}")
	,  := .gotRsrv("}")
	.Rbrace = 
	if ! {
		.matchingErr(.Lbrace, "{", "}")
	}
	.Cmd = 
}

func ( *Parser) ( *Stmt) {
	 := &IfClause{Position: .pos}
	.next()
	.Cond, .CondLast = .followStmts("if", .Position, "then")
	.ThenPos = .followRsrv(.Position, "if <cond>", "then")
	.Then, .ThenLast = .followStmts("then", .ThenPos, "fi", "elif", "else")
	 := 
	for .tok == _LitWord && .val == "elif" {
		 := &IfClause{Position: .pos}
		.Last = .accComs
		.accComs = nil
		.next()
		.Cond, .CondLast = .followStmts("elif", .Position, "then")
		.ThenPos = .followRsrv(.Position, "elif <cond>", "then")
		.Then, .ThenLast = .followStmts("then", .ThenPos, "fi", "elif", "else")
		.Else = 
		 = 
	}
	if ,  := .gotRsrv("else");  {
		.Last = .accComs
		.accComs = nil
		 := &IfClause{Position: }
		.Then, .ThenLast = .followStmts("else", .Position, "fi")
		.Else = 
		 = 
	}
	.Last = .accComs
	.accComs = nil
	.FiPos = .stmtEnd(, "if", "fi")
	for  := .Else;  != nil;  = .Else {
		// All the nested IfClauses share the same FiPos.
		.FiPos = .FiPos
	}
	.Cmd = 
}

func ( *Parser) ( *Stmt,  bool) {
	 := &WhileClause{WhilePos: .pos, Until: }
	 := "while"
	 := "while <cond>"
	if .Until {
		 = "until"
		 = "until <cond>"
	}
	.next()
	.Cond, .CondLast = .followStmts(, .WhilePos, "do")
	.DoPos = .followRsrv(.WhilePos, , "do")
	.Do, .DoLast = .followStmts("do", .DoPos, "done")
	.DonePos = .stmtEnd(, , "done")
	.Cmd = 
}

func ( *Parser) ( *Stmt) {
	 := &ForClause{ForPos: .pos}
	.next()
	.Loop = .loop(.ForPos)

	,  := "do", "done"
	if ,  := .gotRsrv("{");  {
		if .lang == LangPOSIX {
			.langErr(, "for loops with braces", LangBash, LangMirBSDKorn)
		}
		.DoPos = 
		.Braces = true
		,  = "{", "}"
	} else {
		.DoPos = .followRsrv(.ForPos, "for foo [in words]", )
	}

	.Comments = append(.Comments, .accComs...)
	.accComs = nil
	.Do, .DoLast = .followStmts(, .DoPos, )
	.DonePos = .stmtEnd(, "for", )
	.Cmd = 
}

func ( *Parser) ( Pos) Loop {
	if !.lang.isBash() {
		switch .tok {
		case leftParen, dblLeftParen:
			.langErr(.pos, "c-style fors", LangBash)
		}
	}
	if .tok == dblLeftParen {
		 := &CStyleLoop{Lparen: .pos}
		 := .preNested(arithmExprCmd)
		.next()
		.Init = .arithmExpr(false)
		if !.got(dblSemicolon) {
			.follow(.pos, "expr", semicolon)
			.Cond = .arithmExpr(false)
			.follow(.pos, "expr", semicolon)
		}
		.Post = .arithmExpr(false)
		.Rparen = .arithmEnd(dblLeftParen, .Lparen, )
		.got(semicolon)
		.got(_Newl)
		return 
	}
	return .wordIter("for", )
}

func ( *Parser) ( string,  Pos) *WordIter {
	 := &WordIter{}
	if .Name = .getLit(); .Name == nil {
		.followErr(, , "a literal")
	}
	if .got(semicolon) {
		.got(_Newl)
		return 
	}
	.got(_Newl)
	if ,  := .gotRsrv("in");  {
		.InPos = 
		for !.stopToken() {
			if  := .getWord();  == nil {
				.curErr("word list can only contain words")
			} else {
				.Items = append(.Items, )
			}
		}
		.got(semicolon)
		.got(_Newl)
	} else if .tok == _LitWord && .val == "do" {
	} else {
		.followErr(, +" foo", `"in", "do", ;, or a newline`)
	}
	return 
}

func ( *Parser) ( *Stmt) {
	 := &ForClause{ForPos: .pos, Select: true}
	.next()
	.Loop = .wordIter("select", .ForPos)
	.DoPos = .followRsrv(.ForPos, "select foo [in words]", "do")
	.Do, .DoLast = .followStmts("do", .DoPos, "done")
	.DonePos = .stmtEnd(, "select", "done")
	.Cmd = 
}

func ( *Parser) ( *Stmt) {
	 := &CaseClause{Case: .pos}
	.next()
	.Word = .getWord()
	if .Word == nil {
		.followErr(.Case, "case", "a word")
	}
	 := "esac"
	.got(_Newl)
	if ,  := .gotRsrv("{");  {
		.In = 
		.Braces = true
		if .lang != LangMirBSDKorn {
			.posErr(.Pos(), `"case i {" is a mksh feature`)
		}
		 = "}"
	} else {
		.In = .followRsrv(.Case, "case x", "in")
	}
	.Items = .caseItems()
	.Last, .accComs = .accComs, nil
	.Esac = .stmtEnd(, "case", )
	.Cmd = 
}

func ( *Parser) ( string) ( []*CaseItem) {
	.got(_Newl)
	for .tok != _EOF && (.tok != _LitWord || .val != ) {
		 := &CaseItem{}
		.Comments, .accComs = .accComs, nil
		.got(leftParen)
		for .tok != _EOF {
			if  := .getWord();  == nil {
				.curErr("case patterns must consist of words")
			} else {
				.Patterns = append(.Patterns, )
			}
			if .tok == rightParen {
				break
			}
			if !.got(or) {
				.curErr("case patterns must be separated with |")
			}
		}
		 := .preNested(switchCase)
		.next()
		.Stmts, .Last = .stmtList()
		.postNested()
		switch .tok {
		case dblSemicolon, semiAnd, dblSemiAnd, semiOr:
		default:
			.Op = Break
			 = append(, )
			return
		}
		.Last = append(.Last, .accComs...)
		.accComs = nil
		.OpPos = .pos
		.Op = CaseOperator(.tok)
		.next()
		.got(_Newl)

		// Split the comments:
		//
		// case x in
		// a)
		//   foo
		//   ;;
		//   # comment for a
		// # comment for b
		// b)
		//   [...]
		 := len(.accComs)
		for  := len(.accComs) - 1;  >= 0; -- {
			 := .accComs[]
			if .Pos().Col() != .pos.Col() {
				break
			}
			 = 
		}
		.Comments = append(.Comments, .accComs[:]...)
		.accComs = .accComs[:]

		 = append(, )
	}
	return
}

func ( *Parser) ( *Stmt) {
	 := &TestClause{Left: .pos}
	 := .preNested(testExpr)
	.next()
	if ,  := .gotRsrv("]]");  || .tok == _EOF {
		.posErr(.Left, "test clause requires at least one expression")
	}
	.X = .testExpr(dblLeftBrack, .Left, false)
	if .X == nil {
		.followErrExp(.Left, "[[")
	}
	.Right = .pos
	if ,  := .gotRsrv("]]"); ! {
		.matchingErr(.Left, "[[", "]]")
	}
	.postNested()
	.Cmd = 
}

func ( *Parser) ( token,  Pos,  bool) TestExpr {
	.got(_Newl)
	var  TestExpr
	if  {
		 = .testExprBase()
	} else {
		 = .(, , true)
	}
	if  == nil {
		return 
	}
	.got(_Newl)
	switch .tok {
	case andAnd, orOr:
	case _LitWord:
		if .val == "]]" {
			return 
		}
		if .tok = token(testBinaryOp(.val)); .tok == illegalTok {
			.curErr("not a valid test operator: %s", .val)
		}
	case rdrIn, rdrOut:
	case _EOF, rightParen:
		return 
	case _Lit:
		.curErr("test operator words must consist of a single literal")
	default:
		.curErr("not a valid test operator: %v", .tok)
	}
	 := &BinaryTest{
		OpPos: .pos,
		Op:    BinTestOperator(.tok),
		X:     ,
	}
	// Save the previous quoteState, since we change it in TsReMatch.
	 := .quote

	switch .Op {
	case AndTest, OrTest:
		.next()
		if .Y = .(token(.Op), .OpPos, false); .Y == nil {
			.followErrExp(.OpPos, .Op.String())
		}
	case TsReMatch:
		if !.lang.isBash() {
			.langErr(.pos, "regex tests", LangBash)
		}
		.rxOpenParens = 0
		.rxFirstPart = true
		// TODO(mvdan): Using nested states within a regex will break in
		// all sorts of ways. The better fix is likely to use a stop
		// token, like we do with heredocs.
		.quote = testExprRegexp
		fallthrough
	default:
		if ,  := .X.(*Word); ! {
			.posErr(.OpPos, "expected %s, %s or %s after complex expr",
				AndTest, OrTest, "]]")
		}
		.next()
		.Y = .followWordTok(token(.Op), .OpPos)
	}
	.quote = 
	return 
}

func ( *Parser) () TestExpr {
	switch .tok {
	case _EOF, rightParen:
		return nil
	case _LitWord:
		 := token(testUnaryOp(.val))
		switch  {
		case illegalTok:
		case tsRefVar, tsModif: // not available in mksh
			if .lang.isBash() {
				.tok = 
			}
		default:
			.tok = 
		}
	}
	switch .tok {
	case exclMark:
		 := &UnaryTest{OpPos: .pos, Op: TsNot}
		.next()
		if .X = .testExpr(token(.Op), .OpPos, false); .X == nil {
			.followErrExp(.OpPos, .Op.String())
		}
		return 
	case tsExists, tsRegFile, tsDirect, tsCharSp, tsBlckSp, tsNmPipe,
		tsSocket, tsSmbLink, tsSticky, tsGIDSet, tsUIDSet, tsGrpOwn,
		tsUsrOwn, tsModif, tsRead, tsWrite, tsExec, tsNoEmpty,
		tsFdTerm, tsEmpStr, tsNempStr, tsOptSet, tsVarSet, tsRefVar:
		 := &UnaryTest{OpPos: .pos, Op: UnTestOperator(.tok)}
		.next()
		.X = .followWordTok(token(.Op), .OpPos)
		return 
	case leftParen:
		 := &ParenTest{Lparen: .pos}
		.next()
		if .X = .testExpr(leftParen, .Lparen, false); .X == nil {
			.followErrExp(.Lparen, "(")
		}
		.Rparen = .matched(.Lparen, leftParen, rightParen)
		return 
	case _LitWord:
		if .val == "]]" {
			return nil
		}
		fallthrough
	default:
		if  := .getWord();  != nil {
			return 
		}
		// otherwise we'd return a typed nil above
		return nil
	}
}

func ( *Parser) ( *Stmt) {
	 := &DeclClause{Variant: .lit(.pos, .val)}
	.next()
	for !.stopToken() && !.peekRedir() {
		if .hasValidIdent() {
			.Args = append(.Args, .getAssign(false))
		} else if .eqlOffs > 0 {
			.curErr("invalid var name")
		} else if .tok == _LitWord && ValidName(.val) {
			.Args = append(.Args, &Assign{
				Naked: true,
				Name:  .getLit(),
			})
		} else if  := .getWord();  != nil {
			.Args = append(.Args, &Assign{
				Naked: true,
				Value: ,
			})
		} else {
			.followErr(.pos, .Variant.Value, "names or assignments")
		}
	}
	.Cmd = 
}

func isBashCompoundCommand( token,  string) bool {
	switch  {
	case leftParen, dblLeftParen:
		return true
	case _LitWord:
		switch  {
		case "{", "if", "while", "until", "for", "case", "[[",
			"coproc", "let", "function", "declare", "local",
			"export", "readonly", "typeset", "nameref":
			return true
		}
	}
	return false
}

func ( *Parser) ( *Stmt) {
	 := &TimeClause{Time: .pos}
	.next()
	if ,  := .gotRsrv("-p");  {
		.PosixFormat = true
	}
	.Stmt = .gotStmtPipe(.stmt(.pos), false)
	.Cmd = 
}

func ( *Parser) ( *Stmt) {
	 := &CoprocClause{Coproc: .pos}
	if .next(); isBashCompoundCommand(.tok, .val) {
		// has no name
		.Stmt = .gotStmtPipe(.stmt(.pos), false)
		.Cmd = 
		return
	}
	.Name = .getWord()
	.Stmt = .gotStmtPipe(.stmt(.pos), false)
	if .Stmt == nil {
		if .Name == nil {
			.posErr(.Coproc, "coproc clause requires a command")
			return
		}
		// name was in fact the stmt
		.Stmt = .stmt(.Name.Pos())
		.Stmt.Cmd = .call(.Name)
		.Name = nil
	} else if .Name != nil {
		if ,  := .Stmt.Cmd.(*CallExpr);  {
			// name was in fact the start of a call
			.Args = append([]*Word{.Name}, .Args...)
			.Name = nil
		}
	}
	.Cmd = 
}

func ( *Parser) ( *Stmt) {
	 := &LetClause{Let: .pos}
	 := .preNested(arithmExprLet)
	.next()
	for !.stopToken() && !.peekRedir() {
		 := .arithmExpr(true)
		if  == nil {
			break
		}
		.Exprs = append(.Exprs, )
	}
	if len(.Exprs) == 0 {
		.followErrExp(.Let, "let")
	}
	.postNested()
	.Cmd = 
}

func ( *Parser) ( *Stmt) {
	 := .pos
	if .next(); .tok != _LitWord {
		.followErr(, "function", "a name")
	}
	 := .lit(.pos, .val)
	 := false
	if .next(); .got(leftParen) {
		 = true
		.follow(.ValuePos, "foo(", rightParen)
	}
	.funcDecl(, , , )
}

func ( *Parser) ( *Stmt) {
	 := &TestDecl{Position: .pos}
	.next()
	if .Description = .getWord(); .Description == nil {
		.followErr(.Position, "@test", "a description word")
	}
	if .Body = .getStmt(false, false, true); .Body == nil {
		.followErr(.Position, `@test "desc"`, "a statement")
	}
	.Cmd = 
}

func ( *Parser) ( *Stmt,  *Word,  bool) {
	 := .call()
	if  == nil {
		.Args = .Args[:0]
	}
	if  {
		.Assigns = append(.Assigns, .getAssign(true))
	}
:
	for {
		switch .tok {
		case _EOF, _Newl, semicolon, and, or, andAnd, orOr, orAnd,
			dblSemicolon, semiAnd, dblSemiAnd, semiOr:
			break 
		case _LitWord:
			if len(.Args) == 0 && .hasValidIdent() {
				.Assigns = append(.Assigns, .getAssign(true))
				break
			}
			.Args = append(.Args, .wordOne(.lit(.pos, .val)))
			.next()
		case _Lit:
			if len(.Args) == 0 && .hasValidIdent() {
				.Assigns = append(.Assigns, .getAssign(true))
				break
			}
			.Args = append(.Args, .wordAnyNumber())
		case bckQuote:
			if .backquoteEnd() {
				break 
			}
			fallthrough
		case dollBrace, dollDblParen, dollParen, dollar, cmdIn, cmdOut,
			sglQuote, dollSglQuote, dblQuote, dollDblQuote, dollBrack,
			globQuest, globStar, globPlus, globAt, globExcl:
			.Args = append(.Args, .wordAnyNumber())
		case rdrOut, appOut, rdrIn, dplIn, dplOut, clbOut, rdrInOut,
			hdoc, dashHdoc, wordHdoc, rdrAll, appAll, _LitRedir:
			.doRedirect()
		case dblLeftParen:
			.curErr("%s can only be used to open an arithmetic cmd", .tok)
		case rightParen:
			if .quote == subCmd {
				break 
			}
			fallthrough
		default:
			// Note that we'll only keep the first error that happens.
			if len(.Args) > 0 {
				if  := .Args[0].Lit(); .lang == LangPOSIX && isBashCompoundCommand(_LitWord, ) {
					.curErr("the %q builtin exists in bash; tried parsing as posix", )
				}
			}
			.curErr("a command can only contain words and redirects; encountered %s", .tok)
		}
	}
	if len(.Assigns) == 0 && len(.Args) == 0 {
		return
	}
	if len(.Args) == 0 {
		.Args = nil
	} else {
		for ,  := range .Assigns {
			if .Index != nil || .Array != nil {
				.posErr(.Pos(), "inline variables cannot be arrays")
			}
		}
	}
	.Cmd = 
}

func ( *Parser) ( *Stmt,  *Lit,  Pos,  bool) {
	 := &FuncDecl{
		Position: ,
		RsrvWord:  != .ValuePos,
		Parens:   ,
		Name:     ,
	}
	.got(_Newl)
	if .Body = .getStmt(false, false, true); .Body == nil {
		.followErr(.Pos(), "foo()", "a statement")
	}
	.Cmd = 
}