2017-04-24 14:47:10 +02:00
|
|
|
// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
|
|
|
|
// See LICENSE for licensing information
|
|
|
|
|
|
|
|
package syntax
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"strconv"
|
|
|
|
"unicode/utf8"
|
|
|
|
)
|
|
|
|
|
2017-07-31 00:11:34 +02:00
|
|
|
// KeepComments makes the parser parse comments and attach them to
|
|
|
|
// nodes, as opposed to discarding them.
|
2017-05-17 19:49:27 +02:00
|
|
|
func KeepComments(p *Parser) { p.keepComments = true }
|
|
|
|
|
|
|
|
type LangVariant int
|
2017-04-24 14:47:10 +02:00
|
|
|
|
|
|
|
const (
|
2017-05-17 19:49:27 +02:00
|
|
|
LangBash LangVariant = iota
|
|
|
|
LangPOSIX
|
2017-05-27 16:17:49 +02:00
|
|
|
LangMirBSDKorn
|
2017-04-24 14:47:10 +02:00
|
|
|
)
|
|
|
|
|
2017-07-31 00:11:34 +02:00
|
|
|
// Variant changes the shell language variant that the parser will
|
|
|
|
// accept.
|
2017-05-17 19:49:27 +02:00
|
|
|
func Variant(l LangVariant) func(*Parser) {
|
|
|
|
return func(p *Parser) { p.lang = l }
|
|
|
|
}
|
|
|
|
|
2017-07-31 00:11:34 +02:00
|
|
|
// NewParser allocates a new Parser and applies any number of options.
|
2017-05-17 19:49:27 +02:00
|
|
|
func NewParser(options ...func(*Parser)) *Parser {
|
|
|
|
p := &Parser{helperBuf: new(bytes.Buffer)}
|
|
|
|
for _, opt := range options {
|
|
|
|
opt(p)
|
|
|
|
}
|
|
|
|
return p
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Parse reads and parses a shell program with an optional name. It
|
|
|
|
// returns the parsed program if no issues were encountered. Otherwise,
|
2017-07-31 00:11:34 +02:00
|
|
|
// 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 (p *Parser) Parse(r io.Reader, name string) (*File, error) {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.reset()
|
2017-07-06 01:46:05 +02:00
|
|
|
p.f = &File{Name: name}
|
2017-07-31 00:11:34 +02:00
|
|
|
p.src = r
|
2017-04-24 14:47:10 +02:00
|
|
|
p.rune()
|
|
|
|
p.next()
|
2017-07-06 01:46:05 +02:00
|
|
|
p.f.StmtList = p.stmts()
|
2017-04-24 14:47:10 +02:00
|
|
|
if p.err == nil {
|
|
|
|
// EOF immediately after heredoc word so no newline to
|
|
|
|
// trigger it
|
|
|
|
p.doHeredocs()
|
|
|
|
}
|
2017-05-17 19:49:27 +02:00
|
|
|
return p.f, p.err
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
|
2017-07-31 00:11:34 +02:00
|
|
|
// Parser holds the internal state of the parsing mechanism of a
|
|
|
|
// program.
|
2017-05-17 19:49:27 +02:00
|
|
|
type Parser struct {
|
2017-04-24 14:47:10 +02:00
|
|
|
src io.Reader
|
|
|
|
bs []byte // current chunk of read bytes
|
2017-07-31 00:11:34 +02:00
|
|
|
bsp int // pos within chunk for the rune after r
|
|
|
|
r rune // next rune
|
|
|
|
w uint16 // width of r
|
2017-04-24 14:47:10 +02:00
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
f *File
|
2017-04-24 14:47:10 +02:00
|
|
|
|
|
|
|
spaced bool // whether tok has whitespace on its left
|
|
|
|
newLine bool // whether tok is on a new line
|
|
|
|
|
|
|
|
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*)
|
|
|
|
|
2017-07-06 01:46:05 +02:00
|
|
|
offs int
|
2017-04-24 14:47:10 +02:00
|
|
|
pos Pos // position of tok
|
2017-07-31 00:11:34 +02:00
|
|
|
npos Pos // next position (of r)
|
2017-04-24 14:47:10 +02:00
|
|
|
|
2017-07-31 00:11:34 +02:00
|
|
|
quote quoteState // current lexer state
|
|
|
|
eqlOffs int // position of '=' in val (a literal)
|
2017-04-24 14:47:10 +02:00
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
keepComments bool
|
|
|
|
lang LangVariant
|
|
|
|
|
2017-04-24 14:47:10 +02:00
|
|
|
forbidNested bool
|
|
|
|
|
|
|
|
// list of pending heredoc bodies
|
|
|
|
buriedHdocs int
|
|
|
|
heredocs []*Redirect
|
|
|
|
hdocStop []byte
|
|
|
|
|
2017-07-06 01:46:05 +02:00
|
|
|
accComs []Comment
|
|
|
|
curComs *[]Comment
|
|
|
|
|
2017-04-24 14:47:10 +02:00
|
|
|
helperBuf *bytes.Buffer
|
|
|
|
|
|
|
|
litBatch []Lit
|
|
|
|
wordBatch []Word
|
|
|
|
wpsBatch []WordPart
|
|
|
|
stmtBatch []Stmt
|
|
|
|
stListBatch []*Stmt
|
|
|
|
callBatch []callAlloc
|
|
|
|
|
|
|
|
readBuf [bufSize]byte
|
|
|
|
litBuf [bufSize]byte
|
|
|
|
litBs []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
const bufSize = 1 << 10
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) reset() {
|
2017-07-31 00:11:34 +02:00
|
|
|
p.tok, p.val = illegalTok, ""
|
|
|
|
p.eqlOffs = 0
|
2017-07-06 01:46:05 +02:00
|
|
|
p.bs, p.bsp = nil, 0
|
|
|
|
p.offs = 0
|
2017-07-31 00:11:34 +02:00
|
|
|
p.npos = Pos{line: 1, col: 1}
|
|
|
|
p.r, p.w = 0, 0
|
|
|
|
p.err, p.readErr = nil, nil
|
2017-04-24 14:47:10 +02:00
|
|
|
p.quote, p.forbidNested = noState, false
|
|
|
|
p.heredocs, p.buriedHdocs = p.heredocs[:0], 0
|
2017-07-06 01:46:05 +02:00
|
|
|
p.accComs, p.curComs = nil, &p.accComs
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
|
2017-07-06 01:46:05 +02:00
|
|
|
func (p *Parser) getPos() Pos {
|
2017-07-31 00:11:34 +02:00
|
|
|
p.npos.offs = uint32(p.offs + p.bsp - int(p.w))
|
2017-07-06 01:46:05 +02:00
|
|
|
return p.npos
|
|
|
|
}
|
2017-04-24 14:47:10 +02:00
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) lit(pos Pos, val string) *Lit {
|
2017-04-24 14:47:10 +02:00
|
|
|
if len(p.litBatch) == 0 {
|
2017-06-04 21:06:04 +02:00
|
|
|
p.litBatch = make([]Lit, 128)
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
l := &p.litBatch[0]
|
|
|
|
p.litBatch = p.litBatch[1:]
|
|
|
|
l.ValuePos = pos
|
|
|
|
l.ValueEnd = p.getPos()
|
|
|
|
l.Value = val
|
|
|
|
return l
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) word(parts []WordPart) *Word {
|
2017-04-24 14:47:10 +02:00
|
|
|
if len(p.wordBatch) == 0 {
|
2017-06-04 21:06:04 +02:00
|
|
|
p.wordBatch = make([]Word, 64)
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
w := &p.wordBatch[0]
|
|
|
|
p.wordBatch = p.wordBatch[1:]
|
|
|
|
w.Parts = parts
|
|
|
|
return w
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) wps(wp WordPart) []WordPart {
|
2017-04-24 14:47:10 +02:00
|
|
|
if len(p.wpsBatch) == 0 {
|
|
|
|
p.wpsBatch = make([]WordPart, 64)
|
|
|
|
}
|
|
|
|
wps := p.wpsBatch[:1:1]
|
|
|
|
p.wpsBatch = p.wpsBatch[1:]
|
|
|
|
wps[0] = wp
|
|
|
|
return wps
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) stmt(pos Pos) *Stmt {
|
2017-04-24 14:47:10 +02:00
|
|
|
if len(p.stmtBatch) == 0 {
|
2017-06-04 21:06:04 +02:00
|
|
|
p.stmtBatch = make([]Stmt, 64)
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
s := &p.stmtBatch[0]
|
|
|
|
p.stmtBatch = p.stmtBatch[1:]
|
|
|
|
s.Position = pos
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) stList() []*Stmt {
|
2017-04-24 14:47:10 +02:00
|
|
|
if len(p.stListBatch) == 0 {
|
2017-06-04 21:06:04 +02:00
|
|
|
p.stListBatch = make([]*Stmt, 256)
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
stmts := p.stListBatch[:0:4]
|
|
|
|
p.stListBatch = p.stListBatch[4:]
|
|
|
|
return stmts
|
|
|
|
}
|
|
|
|
|
|
|
|
type callAlloc struct {
|
|
|
|
ce CallExpr
|
|
|
|
ws [4]*Word
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) call(w *Word) *CallExpr {
|
2017-04-24 14:47:10 +02:00
|
|
|
if len(p.callBatch) == 0 {
|
|
|
|
p.callBatch = make([]callAlloc, 32)
|
|
|
|
}
|
|
|
|
alloc := &p.callBatch[0]
|
|
|
|
p.callBatch = p.callBatch[1:]
|
|
|
|
ce := &alloc.ce
|
|
|
|
ce.Args = alloc.ws[:1]
|
|
|
|
ce.Args[0] = w
|
|
|
|
return ce
|
|
|
|
}
|
|
|
|
|
2017-06-04 21:06:04 +02:00
|
|
|
type quoteState uint32
|
2017-04-24 14:47:10 +02:00
|
|
|
|
|
|
|
const (
|
|
|
|
noState quoteState = 1 << iota
|
|
|
|
subCmd
|
|
|
|
subCmdBckquo
|
|
|
|
sglQuotes
|
|
|
|
dblQuotes
|
|
|
|
hdocWord
|
|
|
|
hdocBody
|
|
|
|
hdocBodyTabs
|
|
|
|
arithmExpr
|
|
|
|
arithmExprLet
|
|
|
|
arithmExprCmd
|
|
|
|
arithmExprBrack
|
|
|
|
testRegexp
|
|
|
|
switchCase
|
2017-05-01 00:50:22 +02:00
|
|
|
paramName
|
2017-04-24 14:47:10 +02:00
|
|
|
paramExpName
|
|
|
|
paramExpInd
|
|
|
|
paramExpOff
|
|
|
|
paramExpLen
|
|
|
|
paramExpRepl
|
|
|
|
paramExpExp
|
2017-05-27 16:17:49 +02:00
|
|
|
arrayElems
|
2017-04-24 14:47:10 +02:00
|
|
|
|
|
|
|
allKeepSpaces = paramExpRepl | dblQuotes | hdocBody |
|
|
|
|
hdocBodyTabs | paramExpExp | sglQuotes
|
2017-05-27 16:17:49 +02:00
|
|
|
allRegTokens = noState | subCmd | subCmdBckquo | hdocWord |
|
|
|
|
switchCase | arrayElems
|
2017-04-24 14:47:10 +02:00
|
|
|
allArithmExpr = arithmExpr | arithmExprLet | arithmExprCmd |
|
|
|
|
arithmExprBrack | allParamArith
|
2017-05-17 19:49:27 +02:00
|
|
|
allRbrack = arithmExprBrack | paramExpInd | paramName
|
2017-04-24 14:47:10 +02:00
|
|
|
allParamArith = paramExpInd | paramExpOff | paramExpLen
|
2017-05-01 00:50:22 +02:00
|
|
|
allParamReg = paramName | paramExpName | allParamArith
|
2017-04-24 14:47:10 +02:00
|
|
|
allParamExp = allParamReg | paramExpRepl | paramExpExp
|
|
|
|
)
|
|
|
|
|
|
|
|
type saveState struct {
|
|
|
|
quote quoteState
|
|
|
|
buriedHdocs int
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) preNested(quote quoteState) (s saveState) {
|
2017-04-24 14:47:10 +02:00
|
|
|
s.quote, s.buriedHdocs = p.quote, p.buriedHdocs
|
|
|
|
p.buriedHdocs, p.quote = len(p.heredocs), quote
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) postNested(s saveState) {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.quote, p.buriedHdocs = s.quote, s.buriedHdocs
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) unquotedWordBytes(w *Word) ([]byte, bool) {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.helperBuf.Reset()
|
|
|
|
didUnquote := false
|
|
|
|
for _, wp := range w.Parts {
|
|
|
|
if p.unquotedWordPart(p.helperBuf, wp, false) {
|
|
|
|
didUnquote = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return p.helperBuf.Bytes(), didUnquote
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) unquotedWordPart(buf *bytes.Buffer, wp WordPart, quotes bool) (quoted bool) {
|
2017-04-24 14:47:10 +02:00
|
|
|
switch x := wp.(type) {
|
|
|
|
case *Lit:
|
|
|
|
for i := 0; i < len(x.Value); i++ {
|
|
|
|
if b := x.Value[i]; b == '\\' && !quotes {
|
|
|
|
if i++; i < len(x.Value) {
|
|
|
|
buf.WriteByte(x.Value[i])
|
|
|
|
}
|
|
|
|
quoted = true
|
|
|
|
} else {
|
|
|
|
buf.WriteByte(b)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case *SglQuoted:
|
|
|
|
buf.WriteString(x.Value)
|
|
|
|
quoted = true
|
|
|
|
case *DblQuoted:
|
|
|
|
for _, wp2 := range x.Parts {
|
|
|
|
p.unquotedWordPart(buf, wp2, true)
|
|
|
|
}
|
|
|
|
quoted = true
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) doHeredocs() {
|
2017-04-24 14:47:10 +02:00
|
|
|
old := p.quote
|
|
|
|
hdocs := p.heredocs[p.buriedHdocs:]
|
|
|
|
p.heredocs = p.heredocs[:p.buriedHdocs]
|
|
|
|
for i, r := range hdocs {
|
|
|
|
if p.err != nil {
|
|
|
|
break
|
|
|
|
}
|
2017-05-27 16:17:49 +02:00
|
|
|
p.quote = hdocBody
|
2017-04-24 14:47:10 +02:00
|
|
|
if r.Op == DashHdoc {
|
|
|
|
p.quote = hdocBodyTabs
|
|
|
|
}
|
|
|
|
var quoted bool
|
|
|
|
p.hdocStop, quoted = p.unquotedWordBytes(r.Word)
|
|
|
|
if i > 0 && p.r == '\n' {
|
|
|
|
p.rune()
|
|
|
|
}
|
|
|
|
if quoted {
|
|
|
|
r.Hdoc = p.hdocLitWord()
|
|
|
|
} else {
|
|
|
|
p.next()
|
2017-05-27 16:17:49 +02:00
|
|
|
r.Hdoc = p.getWord()
|
|
|
|
}
|
|
|
|
if p.hdocStop != nil {
|
|
|
|
p.posErr(r.Pos(), "unclosed here-document '%s'",
|
|
|
|
string(p.hdocStop))
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
p.quote = old
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) got(tok token) bool {
|
2017-04-24 14:47:10 +02:00
|
|
|
if p.tok == tok {
|
|
|
|
p.next()
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) gotRsrv(val string) bool {
|
2017-04-24 14:47:10 +02:00
|
|
|
if p.tok == _LitWord && p.val == val {
|
|
|
|
p.next()
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) gotSameLine(tok token) bool {
|
2017-04-24 14:47:10 +02:00
|
|
|
if !p.newLine && p.tok == tok {
|
|
|
|
p.next()
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func readableStr(s string) string {
|
|
|
|
// don't quote tokens like & or }
|
|
|
|
if s != "" && s[0] >= 'a' && s[0] <= 'z' {
|
|
|
|
return strconv.Quote(s)
|
|
|
|
}
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) followErr(pos Pos, left, right string) {
|
2017-04-24 14:47:10 +02:00
|
|
|
leftStr := readableStr(left)
|
|
|
|
p.posErr(pos, "%s must be followed by %s", leftStr, right)
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) followErrExp(pos Pos, left string) {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.followErr(pos, left, "an expression")
|
|
|
|
}
|
|
|
|
|
2017-09-02 16:19:00 +02:00
|
|
|
func (p *Parser) follow(lpos Pos, left string, tok token) {
|
2017-04-24 14:47:10 +02:00
|
|
|
if !p.got(tok) {
|
|
|
|
p.followErr(lpos, left, tok.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) followRsrv(lpos Pos, left, val string) Pos {
|
2017-04-24 14:47:10 +02:00
|
|
|
pos := p.pos
|
|
|
|
if !p.gotRsrv(val) {
|
|
|
|
p.followErr(lpos, left, fmt.Sprintf("%q", val))
|
|
|
|
}
|
|
|
|
return pos
|
|
|
|
}
|
|
|
|
|
2017-07-06 01:46:05 +02:00
|
|
|
func (p *Parser) followStmts(left string, lpos Pos, stops ...string) StmtList {
|
2017-04-24 14:47:10 +02:00
|
|
|
if p.gotSameLine(semicolon) {
|
2017-07-06 01:46:05 +02:00
|
|
|
return StmtList{}
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
2017-07-06 01:46:05 +02:00
|
|
|
sl := p.stmts(stops...)
|
|
|
|
if len(sl.Stmts) < 1 && !p.newLine {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.followErr(lpos, left, "a statement list")
|
|
|
|
}
|
2017-07-06 01:46:05 +02:00
|
|
|
return sl
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) followWordTok(tok token, pos Pos) *Word {
|
2017-04-24 14:47:10 +02:00
|
|
|
w := p.getWord()
|
|
|
|
if w == nil {
|
|
|
|
p.followErr(pos, tok.String(), "a word")
|
|
|
|
}
|
|
|
|
return w
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) followWord(s string, pos Pos) *Word {
|
2017-04-24 14:47:10 +02:00
|
|
|
w := p.getWord()
|
|
|
|
if w == nil {
|
|
|
|
p.followErr(pos, s, "a word")
|
|
|
|
}
|
|
|
|
return w
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) stmtEnd(n Node, start, end string) Pos {
|
2017-04-24 14:47:10 +02:00
|
|
|
pos := p.pos
|
|
|
|
if !p.gotRsrv(end) {
|
|
|
|
p.posErr(n.Pos(), "%s statement must end with %q", start, end)
|
|
|
|
}
|
|
|
|
return pos
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) quoteErr(lpos Pos, quote token) {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.posErr(lpos, "reached %s without closing quote %s",
|
|
|
|
p.tok.String(), quote)
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) matchingErr(lpos Pos, left, right interface{}) {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.posErr(lpos, "reached %s without matching %s with %s",
|
|
|
|
p.tok.String(), left, right)
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) matched(lpos Pos, left, right token) Pos {
|
2017-04-24 14:47:10 +02:00
|
|
|
pos := p.pos
|
|
|
|
if !p.got(right) {
|
|
|
|
p.matchingErr(lpos, left, right)
|
|
|
|
}
|
|
|
|
return pos
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) errPass(err error) {
|
2017-04-24 14:47:10 +02:00
|
|
|
if p.err == nil {
|
|
|
|
p.err = err
|
2017-07-06 01:46:05 +02:00
|
|
|
p.bsp = len(p.bs) + 1
|
2017-04-24 14:47:10 +02:00
|
|
|
p.r = utf8.RuneSelf
|
|
|
|
p.tok = _EOF
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParseError represents an error found when parsing a source file.
|
|
|
|
type ParseError struct {
|
2017-07-06 01:46:05 +02:00
|
|
|
Filename string
|
|
|
|
Pos
|
2017-04-24 14:47:10 +02:00
|
|
|
Text string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *ParseError) Error() string {
|
2017-07-06 01:46:05 +02:00
|
|
|
if e.Filename == "" {
|
|
|
|
return fmt.Sprintf("%s: %s", e.Pos.String(), e.Text)
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("%s:%s: %s", e.Filename, e.Pos.String(), e.Text)
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) posErr(pos Pos, format string, a ...interface{}) {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.errPass(&ParseError{
|
2017-07-06 01:46:05 +02:00
|
|
|
Filename: p.f.Name,
|
|
|
|
Pos: pos,
|
2017-04-24 14:47:10 +02:00
|
|
|
Text: fmt.Sprintf(format, a...),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) curErr(format string, a ...interface{}) {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.posErr(p.pos, format, a...)
|
|
|
|
}
|
|
|
|
|
2017-07-06 01:46:05 +02:00
|
|
|
func (p *Parser) stmts(stops ...string) (sl StmtList) {
|
2017-04-24 14:47:10 +02:00
|
|
|
gotEnd := true
|
2017-07-06 01:46:05 +02:00
|
|
|
loop:
|
2017-04-24 14:47:10 +02:00
|
|
|
for p.tok != _EOF {
|
|
|
|
switch p.tok {
|
|
|
|
case _LitWord:
|
|
|
|
for _, stop := range stops {
|
|
|
|
if p.val == stop {
|
2017-07-06 01:46:05 +02:00
|
|
|
break loop
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
case rightParen:
|
|
|
|
if p.quote == subCmd {
|
2017-07-06 01:46:05 +02:00
|
|
|
break loop
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
case bckQuote:
|
|
|
|
if p.quote == subCmdBckquo {
|
2017-07-06 01:46:05 +02:00
|
|
|
break loop
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
2017-05-27 16:17:49 +02:00
|
|
|
case dblSemicolon, semiAnd, dblSemiAnd, semiOr:
|
2017-04-24 14:47:10 +02:00
|
|
|
if p.quote == switchCase {
|
2017-07-06 01:46:05 +02:00
|
|
|
break loop
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
p.curErr("%s can only be used in a case clause", p.tok)
|
|
|
|
}
|
|
|
|
if !p.newLine && !gotEnd {
|
|
|
|
p.curErr("statements must be separated by &, ; or a newline")
|
|
|
|
}
|
|
|
|
if p.tok == _EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if s, end := p.getStmt(true, false); s == nil {
|
|
|
|
p.invalidStmtStart()
|
|
|
|
} else {
|
2017-07-06 01:46:05 +02:00
|
|
|
if sl.Stmts == nil {
|
|
|
|
sl.Stmts = p.stList()
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
2017-07-06 01:46:05 +02:00
|
|
|
sl.Stmts = append(sl.Stmts, s)
|
2017-04-24 14:47:10 +02:00
|
|
|
gotEnd = end
|
|
|
|
}
|
|
|
|
}
|
2017-07-06 01:46:05 +02:00
|
|
|
sl.Last, p.accComs = p.accComs, nil
|
2017-04-24 14:47:10 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) invalidStmtStart() {
|
2017-04-24 14:47:10 +02:00
|
|
|
switch p.tok {
|
|
|
|
case semicolon, and, or, andAnd, orOr:
|
|
|
|
p.curErr("%s can only immediately follow a statement", p.tok)
|
|
|
|
case rightParen:
|
|
|
|
p.curErr("%s can only be used to close a subshell", p.tok)
|
|
|
|
default:
|
|
|
|
p.curErr("%s is not a valid start for a statement", p.tok)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) getWord() *Word {
|
2017-04-24 14:47:10 +02:00
|
|
|
if parts := p.wordParts(); len(parts) > 0 {
|
|
|
|
return p.word(parts)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) getLit() *Lit {
|
2017-04-24 14:47:10 +02:00
|
|
|
switch p.tok {
|
|
|
|
case _Lit, _LitWord, _LitRedir:
|
|
|
|
l := p.lit(p.pos, p.val)
|
|
|
|
p.next()
|
|
|
|
return l
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) wordParts() (wps []WordPart) {
|
2017-04-24 14:47:10 +02:00
|
|
|
for {
|
|
|
|
n := p.wordPart()
|
|
|
|
if n == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if wps == nil {
|
2017-05-01 00:50:22 +02:00
|
|
|
wps = p.wps(n)
|
|
|
|
} else {
|
|
|
|
wps = append(wps, n)
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
if p.spaced {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) ensureNoNested() {
|
2017-04-24 14:47:10 +02:00
|
|
|
if p.forbidNested {
|
|
|
|
p.curErr("expansions not allowed in heredoc words")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) wordPart() WordPart {
|
2017-04-24 14:47:10 +02:00
|
|
|
switch p.tok {
|
|
|
|
case _Lit, _LitWord:
|
|
|
|
l := p.lit(p.pos, p.val)
|
|
|
|
p.next()
|
|
|
|
return l
|
|
|
|
case dollBrace:
|
|
|
|
p.ensureNoNested()
|
2017-05-27 16:17:49 +02:00
|
|
|
switch p.r {
|
|
|
|
case '|':
|
|
|
|
if p.lang != LangMirBSDKorn {
|
|
|
|
p.curErr(`"${|stmts;}" is a mksh feature`)
|
|
|
|
}
|
|
|
|
fallthrough
|
|
|
|
case ' ', '\t', '\n':
|
|
|
|
if p.lang != LangMirBSDKorn {
|
|
|
|
p.curErr(`"${ stmts;}" is a mksh feature`)
|
|
|
|
}
|
|
|
|
cs := &CmdSubst{
|
2017-06-04 21:06:04 +02:00
|
|
|
Left: p.pos,
|
|
|
|
TempFile: p.r != '|',
|
|
|
|
ReplyVar: p.r == '|',
|
2017-05-27 16:17:49 +02:00
|
|
|
}
|
|
|
|
old := p.preNested(subCmd)
|
|
|
|
p.rune() // don't tokenize '|'
|
|
|
|
p.next()
|
2017-07-06 01:46:05 +02:00
|
|
|
cs.StmtList = p.stmts("}")
|
2017-05-27 16:17:49 +02:00
|
|
|
p.postNested(old)
|
|
|
|
cs.Right = p.pos
|
|
|
|
if !p.gotRsrv("}") {
|
|
|
|
p.matchingErr(cs.Left, "${", "}")
|
|
|
|
}
|
|
|
|
return cs
|
|
|
|
default:
|
|
|
|
return p.paramExp()
|
|
|
|
}
|
2017-04-24 14:47:10 +02:00
|
|
|
case dollDblParen, dollBrack:
|
|
|
|
p.ensureNoNested()
|
|
|
|
left := p.tok
|
|
|
|
ar := &ArithmExp{Left: p.pos, Bracket: left == dollBrack}
|
|
|
|
var old saveState
|
|
|
|
if ar.Bracket {
|
|
|
|
old = p.preNested(arithmExprBrack)
|
|
|
|
} else {
|
|
|
|
old = p.preNested(arithmExpr)
|
|
|
|
}
|
|
|
|
p.next()
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.got(hash) {
|
|
|
|
if p.lang != LangMirBSDKorn {
|
|
|
|
p.posErr(ar.Pos(), "unsigned expressions are a mksh feature")
|
|
|
|
}
|
|
|
|
ar.Unsigned = true
|
|
|
|
}
|
|
|
|
ar.X = p.followArithm(left, ar.Left)
|
2017-04-24 14:47:10 +02:00
|
|
|
if ar.Bracket {
|
|
|
|
if p.tok != rightBrack {
|
|
|
|
p.matchingErr(ar.Left, dollBrack, rightBrack)
|
|
|
|
}
|
|
|
|
p.postNested(old)
|
|
|
|
ar.Right = p.pos
|
|
|
|
p.next()
|
|
|
|
} else {
|
|
|
|
ar.Right = p.arithmEnd(dollDblParen, ar.Left, old)
|
|
|
|
}
|
|
|
|
return ar
|
|
|
|
case dollParen:
|
|
|
|
p.ensureNoNested()
|
|
|
|
cs := &CmdSubst{Left: p.pos}
|
|
|
|
old := p.preNested(subCmd)
|
|
|
|
p.next()
|
2017-07-06 01:46:05 +02:00
|
|
|
cs.StmtList = p.stmts()
|
2017-04-24 14:47:10 +02:00
|
|
|
p.postNested(old)
|
|
|
|
cs.Right = p.matched(cs.Left, leftParen, rightParen)
|
|
|
|
return cs
|
|
|
|
case dollar:
|
2017-07-15 18:34:42 +02:00
|
|
|
r := p.r
|
|
|
|
if r == utf8.RuneSelf || wordBreak(r) || r == '"' || r == '\'' || r == '`' || r == '[' {
|
|
|
|
l := p.lit(p.pos, "$")
|
|
|
|
p.next()
|
|
|
|
return l
|
|
|
|
}
|
2017-04-24 14:47:10 +02:00
|
|
|
p.ensureNoNested()
|
2017-05-17 19:49:27 +02:00
|
|
|
return p.shortParamExp()
|
2017-04-24 14:47:10 +02:00
|
|
|
case cmdIn, cmdOut:
|
|
|
|
p.ensureNoNested()
|
|
|
|
ps := &ProcSubst{Op: ProcOperator(p.tok), OpPos: p.pos}
|
|
|
|
old := p.preNested(subCmd)
|
|
|
|
p.next()
|
2017-07-06 01:46:05 +02:00
|
|
|
ps.StmtList = p.stmts()
|
2017-04-24 14:47:10 +02:00
|
|
|
p.postNested(old)
|
|
|
|
ps.Rparen = p.matched(ps.OpPos, token(ps.Op), rightParen)
|
|
|
|
return ps
|
|
|
|
case sglQuote:
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.quote&allArithmExpr != 0 {
|
|
|
|
p.curErr("quotes should not be used in arithmetic expressions")
|
|
|
|
}
|
2017-07-06 01:46:05 +02:00
|
|
|
sq := &SglQuoted{Left: p.pos}
|
2017-04-24 14:47:10 +02:00
|
|
|
r := p.r
|
|
|
|
loop:
|
|
|
|
for p.newLit(r); ; r = p.rune() {
|
|
|
|
switch r {
|
|
|
|
case utf8.RuneSelf, '\'':
|
2017-07-06 01:46:05 +02:00
|
|
|
sq.Right = p.getPos()
|
2017-04-24 14:47:10 +02:00
|
|
|
sq.Value = p.endLit()
|
|
|
|
p.rune()
|
|
|
|
break loop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if r != '\'' {
|
|
|
|
p.posErr(sq.Pos(), "reached EOF without closing quote %s", sglQuote)
|
|
|
|
}
|
|
|
|
p.next()
|
|
|
|
return sq
|
|
|
|
case dollSglQuote:
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.quote&allArithmExpr != 0 {
|
|
|
|
p.curErr("quotes should not be used in arithmetic expressions")
|
|
|
|
}
|
2017-07-06 01:46:05 +02:00
|
|
|
sq := &SglQuoted{Left: p.pos, Dollar: true}
|
2017-04-24 14:47:10 +02:00
|
|
|
old := p.quote
|
|
|
|
p.quote = sglQuotes
|
|
|
|
p.next()
|
|
|
|
p.quote = old
|
|
|
|
if p.tok != sglQuote {
|
|
|
|
sq.Value = p.val
|
|
|
|
p.next()
|
|
|
|
}
|
2017-07-06 01:46:05 +02:00
|
|
|
sq.Right = p.pos
|
2017-04-24 14:47:10 +02:00
|
|
|
if !p.got(sglQuote) {
|
|
|
|
p.quoteErr(sq.Pos(), sglQuote)
|
|
|
|
}
|
|
|
|
return sq
|
|
|
|
case dblQuote:
|
|
|
|
if p.quote == dblQuotes {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
fallthrough
|
|
|
|
case dollDblQuote:
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.quote&allArithmExpr != 0 {
|
|
|
|
p.curErr("quotes should not be used in arithmetic expressions")
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
2017-05-27 16:17:49 +02:00
|
|
|
return p.dblQuoted()
|
2017-04-24 14:47:10 +02:00
|
|
|
case bckQuote:
|
|
|
|
if p.quote == subCmdBckquo {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
p.ensureNoNested()
|
|
|
|
cs := &CmdSubst{Left: p.pos}
|
|
|
|
old := p.preNested(subCmdBckquo)
|
|
|
|
p.next()
|
2017-07-06 01:46:05 +02:00
|
|
|
cs.StmtList = p.stmts()
|
2017-04-24 14:47:10 +02:00
|
|
|
p.postNested(old)
|
|
|
|
cs.Right = p.pos
|
|
|
|
if !p.got(bckQuote) {
|
|
|
|
p.quoteErr(cs.Pos(), bckQuote)
|
|
|
|
}
|
|
|
|
return cs
|
|
|
|
case globQuest, globStar, globPlus, globAt, globExcl:
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.lang == LangPOSIX {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.curErr("extended globs are a bash feature")
|
|
|
|
}
|
|
|
|
eg := &ExtGlob{Op: GlobOperator(p.tok), OpPos: p.pos}
|
|
|
|
lparens := 0
|
|
|
|
r := p.r
|
|
|
|
globLoop:
|
|
|
|
for p.newLit(r); ; r = p.rune() {
|
|
|
|
switch r {
|
|
|
|
case utf8.RuneSelf:
|
|
|
|
break globLoop
|
|
|
|
case '(':
|
|
|
|
lparens++
|
|
|
|
case ')':
|
|
|
|
if lparens--; lparens < 0 {
|
|
|
|
break globLoop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-07-06 01:46:05 +02:00
|
|
|
eg.Pattern = p.lit(posAddCol(eg.OpPos, 2), p.endLit())
|
2017-04-24 14:47:10 +02:00
|
|
|
p.rune()
|
|
|
|
p.next()
|
|
|
|
if lparens != -1 {
|
|
|
|
p.matchingErr(eg.OpPos, eg.Op, rightParen)
|
|
|
|
}
|
|
|
|
return eg
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-27 16:17:49 +02:00
|
|
|
func (p *Parser) dblQuoted() *DblQuoted {
|
|
|
|
q := &DblQuoted{Position: p.pos, Dollar: p.tok == dollDblQuote}
|
|
|
|
old := p.quote
|
|
|
|
p.quote = dblQuotes
|
|
|
|
p.next()
|
|
|
|
q.Parts = p.wordParts()
|
|
|
|
p.quote = old
|
|
|
|
if !p.got(dblQuote) {
|
|
|
|
p.quoteErr(q.Pos(), dblQuote)
|
|
|
|
}
|
|
|
|
return q
|
|
|
|
}
|
|
|
|
|
2017-04-24 14:47:10 +02:00
|
|
|
func arithmOpLevel(op BinAritOperator) int {
|
|
|
|
switch op {
|
|
|
|
case Comma:
|
|
|
|
return 0
|
|
|
|
case AddAssgn, SubAssgn, MulAssgn, QuoAssgn, RemAssgn, AndAssgn,
|
|
|
|
OrAssgn, XorAssgn, ShlAssgn, ShrAssgn:
|
|
|
|
return 1
|
|
|
|
case Assgn:
|
|
|
|
return 2
|
|
|
|
case Quest, Colon:
|
|
|
|
return 3
|
|
|
|
case AndArit, OrArit:
|
|
|
|
return 4
|
|
|
|
case And, Or, Xor:
|
|
|
|
return 5
|
|
|
|
case Eql, Neq:
|
|
|
|
return 6
|
|
|
|
case Lss, Gtr, Leq, Geq:
|
|
|
|
return 7
|
|
|
|
case Shl, Shr:
|
|
|
|
return 8
|
|
|
|
case Add, Sub:
|
|
|
|
return 9
|
|
|
|
case Mul, Quo, Rem:
|
|
|
|
return 10
|
|
|
|
case Pow:
|
|
|
|
return 11
|
|
|
|
}
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
2017-05-27 16:17:49 +02:00
|
|
|
func (p *Parser) followArithm(ftok token, fpos Pos) ArithmExpr {
|
2017-08-05 19:13:35 +02:00
|
|
|
x := p.arithmExpr(0, false, false)
|
2017-05-27 16:17:49 +02:00
|
|
|
if x == nil {
|
|
|
|
p.followErrExp(fpos, ftok.String())
|
|
|
|
}
|
|
|
|
return x
|
|
|
|
}
|
|
|
|
|
2017-08-05 19:13:35 +02:00
|
|
|
func (p *Parser) arithmExpr(level int, compact, tern bool) ArithmExpr {
|
2017-04-24 14:47:10 +02:00
|
|
|
if p.tok == _EOF || p.peekArithmEnd() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
var left ArithmExpr
|
|
|
|
if level > 11 {
|
|
|
|
left = p.arithmExprBase(compact)
|
|
|
|
} else {
|
2017-08-05 19:13:35 +02:00
|
|
|
left = p.arithmExpr(level+1, compact, false)
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
if compact && p.spaced {
|
|
|
|
return left
|
|
|
|
}
|
|
|
|
newLevel := arithmOpLevel(BinAritOperator(p.tok))
|
|
|
|
if !tern && p.tok == colon && p.quote&allParamArith != 0 {
|
|
|
|
newLevel = -1
|
|
|
|
}
|
|
|
|
if newLevel < 0 {
|
|
|
|
switch p.tok {
|
|
|
|
case _Lit, _LitWord:
|
|
|
|
p.curErr("not a valid arithmetic operator: %s", p.val)
|
|
|
|
return nil
|
2017-06-04 21:06:04 +02:00
|
|
|
case leftBrack:
|
|
|
|
p.curErr("[ must follow a name")
|
|
|
|
return nil
|
2017-04-24 14:47:10 +02:00
|
|
|
case rightParen, _EOF:
|
|
|
|
default:
|
|
|
|
if p.quote == arithmExpr {
|
|
|
|
p.curErr("not a valid arithmetic operator: %v", p.tok)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if newLevel < level {
|
|
|
|
return left
|
|
|
|
}
|
|
|
|
if left == nil {
|
|
|
|
p.curErr("%s must follow an expression", p.tok.String())
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
b := &BinaryArithm{
|
|
|
|
OpPos: p.pos,
|
|
|
|
Op: BinAritOperator(p.tok),
|
|
|
|
X: left,
|
|
|
|
}
|
|
|
|
switch b.Op {
|
|
|
|
case Colon:
|
|
|
|
if !tern {
|
|
|
|
p.posErr(b.Pos(), "ternary operator missing ? before :")
|
|
|
|
}
|
|
|
|
case AddAssgn, SubAssgn, MulAssgn, QuoAssgn, RemAssgn, AndAssgn,
|
|
|
|
OrAssgn, XorAssgn, ShlAssgn, ShrAssgn, Assgn:
|
2017-05-27 16:17:49 +02:00
|
|
|
if !isArithName(b.X) {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.posErr(b.OpPos, "%s must follow a name", b.Op.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if p.next(); compact && p.spaced {
|
|
|
|
p.followErrExp(b.OpPos, b.Op.String())
|
|
|
|
}
|
2017-08-05 19:13:35 +02:00
|
|
|
b.Y = p.arithmExpr(newLevel, compact, b.Op == Quest)
|
2017-04-24 14:47:10 +02:00
|
|
|
if b.Y == nil {
|
|
|
|
p.followErrExp(b.OpPos, b.Op.String())
|
|
|
|
}
|
|
|
|
if b.Op == Quest {
|
|
|
|
if b2, ok := b.Y.(*BinaryArithm); !ok || b2.Op != Colon {
|
|
|
|
p.posErr(b.Pos(), "ternary operator missing : after ?")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
2017-05-27 16:17:49 +02:00
|
|
|
func isArithName(left ArithmExpr) bool {
|
|
|
|
w, ok := left.(*Word)
|
|
|
|
if !ok || len(w.Parts) != 1 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
switch x := w.Parts[0].(type) {
|
|
|
|
case *Lit:
|
2017-06-04 21:06:04 +02:00
|
|
|
return ValidName(x.Value)
|
2017-05-27 16:17:49 +02:00
|
|
|
case *ParamExp:
|
|
|
|
return x.nakedIndex()
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) arithmExprBase(compact bool) ArithmExpr {
|
2017-04-24 14:47:10 +02:00
|
|
|
var x ArithmExpr
|
|
|
|
switch p.tok {
|
|
|
|
case exclMark:
|
|
|
|
ue := &UnaryArithm{OpPos: p.pos, Op: UnAritOperator(p.tok)}
|
|
|
|
p.next()
|
|
|
|
if ue.X = p.arithmExprBase(compact); ue.X == nil {
|
|
|
|
p.followErrExp(ue.OpPos, ue.Op.String())
|
|
|
|
}
|
|
|
|
return ue
|
|
|
|
case addAdd, subSub:
|
|
|
|
ue := &UnaryArithm{OpPos: p.pos, Op: UnAritOperator(p.tok)}
|
|
|
|
p.next()
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.tok != _LitWord {
|
2017-05-01 00:50:22 +02:00
|
|
|
p.followErr(ue.OpPos, token(ue.Op).String(), "a literal")
|
|
|
|
}
|
2017-05-27 16:17:49 +02:00
|
|
|
ue.X = p.arithmExprBase(compact)
|
2017-04-24 14:47:10 +02:00
|
|
|
return ue
|
|
|
|
case leftParen:
|
|
|
|
pe := &ParenArithm{Lparen: p.pos}
|
|
|
|
p.next()
|
2017-05-27 16:17:49 +02:00
|
|
|
pe.X = p.followArithm(leftParen, pe.Lparen)
|
2017-04-24 14:47:10 +02:00
|
|
|
pe.Rparen = p.matched(pe.Lparen, leftParen, rightParen)
|
|
|
|
x = pe
|
|
|
|
case plus, minus:
|
|
|
|
ue := &UnaryArithm{OpPos: p.pos, Op: UnAritOperator(p.tok)}
|
|
|
|
if p.next(); compact && p.spaced {
|
|
|
|
p.followErrExp(ue.OpPos, ue.Op.String())
|
|
|
|
}
|
2017-05-17 19:49:27 +02:00
|
|
|
ue.X = p.arithmExprBase(compact)
|
2017-04-24 14:47:10 +02:00
|
|
|
if ue.X == nil {
|
|
|
|
p.followErrExp(ue.OpPos, ue.Op.String())
|
|
|
|
}
|
|
|
|
x = ue
|
2017-05-01 00:50:22 +02:00
|
|
|
case _LitWord:
|
2017-05-17 19:49:27 +02:00
|
|
|
l := p.getLit()
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.tok != leftBrack {
|
|
|
|
x = p.word(p.wps(l))
|
2017-05-17 19:49:27 +02:00
|
|
|
break
|
|
|
|
}
|
2017-05-27 16:17:49 +02:00
|
|
|
left := p.pos
|
2017-05-17 19:49:27 +02:00
|
|
|
pe := &ParamExp{Dollar: l.ValuePos, Short: true, Param: l}
|
|
|
|
old := p.preNested(arithmExprBrack)
|
|
|
|
p.next()
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.tok == dblQuote {
|
2017-06-14 19:00:36 +02:00
|
|
|
pe.Index = p.word(p.wps(p.dblQuoted()))
|
2017-05-27 16:17:49 +02:00
|
|
|
} else {
|
|
|
|
pe.Index = p.followArithm(leftBrack, left)
|
2017-05-17 19:49:27 +02:00
|
|
|
}
|
|
|
|
p.postNested(old)
|
|
|
|
p.matched(left, leftBrack, rightBrack)
|
2017-05-27 16:17:49 +02:00
|
|
|
x = p.word(p.wps(pe))
|
2017-04-24 14:47:10 +02:00
|
|
|
case bckQuote:
|
|
|
|
if p.quote == arithmExprLet {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
fallthrough
|
|
|
|
default:
|
2017-05-27 16:17:49 +02:00
|
|
|
if w := p.getWord(); w != nil {
|
|
|
|
// we want real nil, not (*Word)(nil) as that
|
|
|
|
// sets the type to non-nil and then x != nil
|
|
|
|
x = w
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if compact && p.spaced {
|
|
|
|
return x
|
|
|
|
}
|
|
|
|
if p.tok == addAdd || p.tok == subSub {
|
2017-05-27 16:17:49 +02:00
|
|
|
if !isArithName(x) {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.curErr("%s must follow a name", p.tok.String())
|
|
|
|
}
|
|
|
|
u := &UnaryArithm{
|
|
|
|
Post: true,
|
|
|
|
OpPos: p.pos,
|
|
|
|
Op: UnAritOperator(p.tok),
|
|
|
|
X: x,
|
|
|
|
}
|
|
|
|
p.next()
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
return x
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) shortParamExp() *ParamExp {
|
|
|
|
pe := &ParamExp{Dollar: p.pos, Short: true}
|
2017-07-06 01:46:05 +02:00
|
|
|
p.pos = posAddCol(p.pos, 1)
|
2017-05-17 19:49:27 +02:00
|
|
|
switch p.r {
|
|
|
|
case '@', '*', '#', '$', '?', '!', '0', '-':
|
|
|
|
p.tok, p.val = _LitWord, string(p.r)
|
|
|
|
p.rune()
|
|
|
|
default:
|
|
|
|
old := p.quote
|
|
|
|
p.quote = paramName
|
|
|
|
p.advanceLitOther(p.r)
|
|
|
|
p.quote = old
|
|
|
|
}
|
|
|
|
pe.Param = p.getLit()
|
|
|
|
return pe
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Parser) paramExp() *ParamExp {
|
2017-04-24 14:47:10 +02:00
|
|
|
pe := &ParamExp{Dollar: p.pos}
|
|
|
|
old := p.quote
|
|
|
|
p.quote = paramExpName
|
2017-06-04 21:06:04 +02:00
|
|
|
if p.r == '#' {
|
|
|
|
p.tok = hash
|
|
|
|
p.pos = p.getPos()
|
|
|
|
p.rune()
|
|
|
|
} else {
|
|
|
|
p.next()
|
|
|
|
}
|
2017-04-24 14:47:10 +02:00
|
|
|
switch p.tok {
|
|
|
|
case at:
|
|
|
|
p.tok, p.val = _LitWord, "@"
|
|
|
|
case hash:
|
2017-06-04 21:06:04 +02:00
|
|
|
if paramNameOp(p.r) {
|
2017-04-24 14:47:10 +02:00
|
|
|
pe.Length = true
|
|
|
|
p.next()
|
|
|
|
}
|
2017-05-27 16:17:49 +02:00
|
|
|
case perc:
|
|
|
|
if p.lang != LangMirBSDKorn {
|
|
|
|
p.posErr(pe.Pos(), `"${%%foo}" is a mksh feature`)
|
|
|
|
}
|
2017-06-04 21:06:04 +02:00
|
|
|
if paramNameOp(p.r) {
|
2017-05-27 16:17:49 +02:00
|
|
|
pe.Width = true
|
|
|
|
p.next()
|
|
|
|
}
|
2017-04-24 14:47:10 +02:00
|
|
|
case exclMark:
|
2017-06-04 21:06:04 +02:00
|
|
|
if paramNameOp(p.r) {
|
2017-08-05 19:13:35 +02:00
|
|
|
pe.Excl = true
|
2017-04-24 14:47:10 +02:00
|
|
|
p.next()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
switch p.tok {
|
|
|
|
case _Lit, _LitWord:
|
|
|
|
pe.Param = p.lit(p.pos, p.val)
|
|
|
|
p.next()
|
|
|
|
case hash, exclMark:
|
|
|
|
pe.Param = p.lit(p.pos, p.tok.String())
|
|
|
|
p.next()
|
|
|
|
case dollar, quest, minus:
|
|
|
|
op := p.tok
|
|
|
|
pe.Param = p.lit(p.pos, p.tok.String())
|
|
|
|
p.next()
|
|
|
|
switch p.tok {
|
|
|
|
case _Lit, _LitWord:
|
|
|
|
p.curErr("%s cannot be followed by a word", op)
|
|
|
|
}
|
|
|
|
default:
|
2017-06-04 21:06:04 +02:00
|
|
|
p.curErr("parameter expansion requires a literal")
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
if p.tok == rightBrace {
|
|
|
|
pe.Rbrace = p.pos
|
|
|
|
p.quote = old
|
|
|
|
p.next()
|
|
|
|
return pe
|
|
|
|
}
|
|
|
|
if p.tok == leftBrack {
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.lang == LangPOSIX {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.curErr("arrays are a bash feature")
|
|
|
|
}
|
|
|
|
lpos := p.pos
|
|
|
|
p.quote = paramExpInd
|
|
|
|
p.next()
|
|
|
|
switch p.tok {
|
2017-05-17 19:49:27 +02:00
|
|
|
case star, at:
|
|
|
|
p.tok, p.val = _LitWord, p.tok.String()
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.tok == dblQuote {
|
2017-06-14 19:00:36 +02:00
|
|
|
pe.Index = p.word(p.wps(p.dblQuoted()))
|
2017-05-27 16:17:49 +02:00
|
|
|
} else {
|
|
|
|
pe.Index = p.followArithm(leftBrack, lpos)
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
p.quote = paramExpName
|
|
|
|
p.matched(lpos, leftBrack, rightBrack)
|
|
|
|
}
|
|
|
|
switch p.tok {
|
|
|
|
case rightBrace:
|
|
|
|
pe.Rbrace = p.pos
|
|
|
|
p.quote = old
|
|
|
|
p.next()
|
|
|
|
return pe
|
|
|
|
case slash, dblSlash:
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.lang == LangPOSIX {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.curErr("search and replace is a bash feature")
|
|
|
|
}
|
|
|
|
pe.Repl = &Replace{All: p.tok == dblSlash}
|
|
|
|
p.quote = paramExpRepl
|
|
|
|
p.next()
|
2017-05-27 16:17:49 +02:00
|
|
|
pe.Repl.Orig = p.getWord()
|
|
|
|
p.quote = paramExpExp
|
2017-06-04 21:06:04 +02:00
|
|
|
if p.got(slash) {
|
|
|
|
pe.Repl.With = p.getWord()
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
case colon:
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.lang == LangPOSIX {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.curErr("slicing is a bash feature")
|
|
|
|
}
|
|
|
|
pe.Slice = &Slice{}
|
|
|
|
colonPos := p.pos
|
|
|
|
p.quote = paramExpOff
|
|
|
|
if p.next(); p.tok != colon {
|
2017-05-27 16:17:49 +02:00
|
|
|
pe.Slice.Offset = p.followArithm(colon, colonPos)
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
colonPos = p.pos
|
|
|
|
p.quote = paramExpLen
|
|
|
|
if p.got(colon) {
|
2017-05-27 16:17:49 +02:00
|
|
|
pe.Slice.Length = p.followArithm(colon, colonPos)
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
2017-05-27 16:17:49 +02:00
|
|
|
case caret, dblCaret, comma, dblComma:
|
|
|
|
if p.lang != LangBash {
|
|
|
|
p.curErr("this expansion operator is a bash feature")
|
|
|
|
}
|
|
|
|
fallthrough
|
|
|
|
case at:
|
|
|
|
if p.lang == LangPOSIX {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.curErr("this expansion operator is a bash feature")
|
|
|
|
}
|
|
|
|
fallthrough
|
|
|
|
default:
|
|
|
|
pe.Exp = &Expansion{Op: ParExpOperator(p.tok)}
|
|
|
|
p.quote = paramExpExp
|
|
|
|
p.next()
|
2017-05-27 16:17:49 +02:00
|
|
|
pe.Exp.Word = p.getWord()
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
p.quote = old
|
|
|
|
pe.Rbrace = p.pos
|
|
|
|
p.matched(pe.Dollar, dollBrace, rightBrace)
|
|
|
|
return pe
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) peekArithmEnd() bool {
|
2017-04-24 14:47:10 +02:00
|
|
|
return p.tok == rightParen && p.r == ')'
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) arithmEnd(ltok token, lpos Pos, old saveState) Pos {
|
2017-05-27 16:17:49 +02:00
|
|
|
if !p.peekArithmEnd() {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.matchingErr(lpos, ltok, dblRightParen)
|
|
|
|
}
|
2017-05-27 16:17:49 +02:00
|
|
|
p.rune()
|
2017-04-24 14:47:10 +02:00
|
|
|
p.postNested(old)
|
|
|
|
pos := p.pos
|
|
|
|
p.next()
|
|
|
|
return pos
|
|
|
|
}
|
|
|
|
|
|
|
|
func stopToken(tok token) bool {
|
|
|
|
switch tok {
|
2017-05-27 16:17:49 +02:00
|
|
|
case _EOF, semicolon, and, or, andAnd, orOr, orAnd, dblSemicolon,
|
|
|
|
semiAnd, dblSemiAnd, semiOr, rightParen:
|
2017-04-24 14:47:10 +02:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2017-06-04 21:06:04 +02:00
|
|
|
// ValidName returns whether val is a valid name as per the POSIX spec.
|
|
|
|
func ValidName(val string) bool {
|
2017-04-24 14:47:10 +02:00
|
|
|
for i, c := range val {
|
|
|
|
switch {
|
|
|
|
case 'a' <= c && c <= 'z':
|
|
|
|
case 'A' <= c && c <= 'Z':
|
|
|
|
case c == '_':
|
|
|
|
case i > 0 && '0' <= c && c <= '9':
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) hasValidIdent() bool {
|
2017-07-31 00:11:34 +02:00
|
|
|
if end := p.eqlOffs; end > 0 {
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.val[end-1] == '+' && p.lang != LangPOSIX {
|
|
|
|
end--
|
|
|
|
}
|
2017-06-04 21:06:04 +02:00
|
|
|
if ValidName(p.val[:end]) {
|
2017-05-27 16:17:49 +02:00
|
|
|
return true
|
|
|
|
}
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
2017-05-17 19:49:27 +02:00
|
|
|
return p.tok == _Lit && p.r == '['
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
|
2017-05-27 16:17:49 +02:00
|
|
|
func (p *Parser) getAssign(needEqual bool) *Assign {
|
2017-04-24 14:47:10 +02:00
|
|
|
as := &Assign{}
|
2017-07-31 00:11:34 +02:00
|
|
|
if p.eqlOffs > 0 { // foo=bar
|
|
|
|
nameEnd := p.eqlOffs
|
|
|
|
if p.lang != LangPOSIX && p.val[p.eqlOffs-1] == '+' {
|
2017-05-17 19:49:27 +02:00
|
|
|
// a+=b
|
|
|
|
as.Append = true
|
|
|
|
nameEnd--
|
|
|
|
}
|
|
|
|
as.Name = p.lit(p.pos, p.val[:nameEnd])
|
|
|
|
// since we're not using the entire p.val
|
2017-07-06 01:46:05 +02:00
|
|
|
as.Name.ValueEnd = posAddCol(as.Name.ValuePos, nameEnd)
|
2017-07-31 00:11:34 +02:00
|
|
|
left := p.lit(posAddCol(p.pos, 1), p.val[p.eqlOffs+1:])
|
2017-05-17 19:49:27 +02:00
|
|
|
if left.Value != "" {
|
2017-07-31 00:11:34 +02:00
|
|
|
left.ValuePos = posAddCol(left.ValuePos, p.eqlOffs)
|
2017-05-17 19:49:27 +02:00
|
|
|
as.Value = p.word(p.wps(left))
|
|
|
|
}
|
2017-05-27 16:17:49 +02:00
|
|
|
p.next()
|
|
|
|
} else { // foo[x]=bar
|
2017-05-17 19:49:27 +02:00
|
|
|
as.Name = p.lit(p.pos, p.val)
|
|
|
|
// hasValidIdent already checks p.r is '['
|
|
|
|
p.rune()
|
2017-07-06 01:46:05 +02:00
|
|
|
left := posAddCol(p.pos, 1)
|
2017-05-17 19:49:27 +02:00
|
|
|
old := p.preNested(arithmExprBrack)
|
|
|
|
p.next()
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.tok == star {
|
|
|
|
p.tok, p.val = _LitWord, p.tok.String()
|
|
|
|
}
|
|
|
|
if p.tok == dblQuote {
|
2017-06-14 19:00:36 +02:00
|
|
|
as.Index = p.word(p.wps(p.dblQuoted()))
|
2017-05-27 16:17:49 +02:00
|
|
|
} else {
|
|
|
|
as.Index = p.followArithm(leftBrack, left)
|
2017-05-17 19:49:27 +02:00
|
|
|
}
|
|
|
|
p.postNested(old)
|
|
|
|
p.matched(left, leftBrack, rightBrack)
|
2017-05-27 16:17:49 +02:00
|
|
|
if !needEqual && (p.spaced || stopToken(p.tok)) {
|
|
|
|
return as
|
|
|
|
}
|
|
|
|
if len(p.val) > 0 && p.val[0] == '+' {
|
|
|
|
as.Append = true
|
|
|
|
p.val = p.val[1:]
|
2017-07-06 01:46:05 +02:00
|
|
|
p.pos = posAddCol(p.pos, 1)
|
2017-05-27 16:17:49 +02:00
|
|
|
}
|
|
|
|
if len(p.val) < 1 || p.val[0] != '=' {
|
|
|
|
if as.Append {
|
|
|
|
p.followErr(as.Pos(), "a[b]+", "=")
|
|
|
|
} else {
|
|
|
|
p.followErr(as.Pos(), "a[b]", "=")
|
|
|
|
}
|
2017-05-17 19:49:27 +02:00
|
|
|
return nil
|
|
|
|
}
|
2017-07-06 01:46:05 +02:00
|
|
|
p.pos = posAddCol(p.pos, 1)
|
2017-05-17 19:49:27 +02:00
|
|
|
p.val = p.val[1:]
|
|
|
|
if p.val == "" {
|
|
|
|
p.next()
|
|
|
|
}
|
|
|
|
}
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.spaced || stopToken(p.tok) {
|
|
|
|
return as
|
|
|
|
}
|
2017-05-17 19:49:27 +02:00
|
|
|
if as.Value == nil && p.tok == leftParen {
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.lang == LangPOSIX {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.curErr("arrays are a bash feature")
|
|
|
|
}
|
2017-05-17 19:49:27 +02:00
|
|
|
as.Array = &ArrayExpr{Lparen: p.pos}
|
2017-05-27 16:17:49 +02:00
|
|
|
newQuote := p.quote
|
|
|
|
if p.lang == LangBash {
|
|
|
|
newQuote = arrayElems
|
|
|
|
}
|
|
|
|
old := p.preNested(newQuote)
|
2017-04-24 14:47:10 +02:00
|
|
|
p.next()
|
|
|
|
for p.tok != _EOF && p.tok != rightParen {
|
2017-05-27 16:17:49 +02:00
|
|
|
ae := &ArrayElem{}
|
2017-07-06 01:46:05 +02:00
|
|
|
ae.Comments, p.accComs = p.accComs, nil
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.tok == leftBrack {
|
|
|
|
left := p.pos
|
|
|
|
p.quote = arithmExprBrack
|
|
|
|
p.next()
|
|
|
|
if p.tok == dblQuote {
|
2017-06-14 19:00:36 +02:00
|
|
|
ae.Index = p.word(p.wps(p.dblQuoted()))
|
2017-05-27 16:17:49 +02:00
|
|
|
} else {
|
|
|
|
ae.Index = p.followArithm(leftBrack, left)
|
|
|
|
}
|
|
|
|
if p.tok != rightBrack {
|
|
|
|
p.matchingErr(left, leftBrack, rightBrack)
|
|
|
|
}
|
|
|
|
p.quote = arrayElems
|
|
|
|
if p.r != '=' {
|
|
|
|
p.followErr(left, `"[x]"`, "=")
|
|
|
|
}
|
|
|
|
p.rune()
|
|
|
|
p.next()
|
|
|
|
}
|
|
|
|
if ae.Value = p.getWord(); ae.Value == nil {
|
|
|
|
p.curErr("array element values must be words")
|
2017-07-06 01:46:05 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
if len(p.accComs) > 0 {
|
|
|
|
c := p.accComs[0]
|
|
|
|
if c.Pos().Line() == ae.End().Line() {
|
|
|
|
ae.Comments = append(ae.Comments, c)
|
|
|
|
p.accComs = p.accComs[1:]
|
|
|
|
}
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
2017-05-27 16:17:49 +02:00
|
|
|
as.Array.Elems = append(as.Array.Elems, ae)
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
2017-07-06 01:46:05 +02:00
|
|
|
as.Array.Last, p.accComs = p.accComs, nil
|
2017-05-27 16:17:49 +02:00
|
|
|
p.postNested(old)
|
2017-05-17 19:49:27 +02:00
|
|
|
as.Array.Rparen = p.matched(as.Array.Lparen, leftParen, rightParen)
|
2017-05-27 16:17:49 +02:00
|
|
|
} else if w := p.getWord(); w != nil {
|
|
|
|
if as.Value == nil {
|
|
|
|
as.Value = w
|
|
|
|
} else {
|
|
|
|
as.Value.Parts = append(as.Value.Parts, w.Parts...)
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return as
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) peekRedir() bool {
|
2017-04-24 14:47:10 +02:00
|
|
|
switch p.tok {
|
|
|
|
case rdrOut, appOut, rdrIn, dplIn, dplOut, clbOut, rdrInOut,
|
|
|
|
hdoc, dashHdoc, wordHdoc, rdrAll, appAll, _LitRedir:
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) doRedirect(s *Stmt) {
|
2017-04-24 14:47:10 +02:00
|
|
|
r := &Redirect{}
|
|
|
|
r.N = p.getLit()
|
|
|
|
r.Op, r.OpPos = RedirOperator(p.tok), p.pos
|
|
|
|
p.next()
|
|
|
|
if p.newLine {
|
|
|
|
p.curErr("redirect word must be on the same line")
|
|
|
|
}
|
|
|
|
switch r.Op {
|
|
|
|
case Hdoc, DashHdoc:
|
|
|
|
old := p.quote
|
|
|
|
p.quote, p.forbidNested = hdocWord, true
|
|
|
|
p.heredocs = append(p.heredocs, r)
|
|
|
|
r.Word = p.followWordTok(token(r.Op), r.OpPos)
|
|
|
|
p.quote, p.forbidNested = old, false
|
|
|
|
if p.tok == illegalTok {
|
|
|
|
p.next()
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
r.Word = p.followWordTok(token(r.Op), r.OpPos)
|
|
|
|
}
|
|
|
|
s.Redirs = append(s.Redirs, r)
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) getStmt(readEnd, binCmd bool) (s *Stmt, gotEnd bool) {
|
2017-04-24 14:47:10 +02:00
|
|
|
s = p.stmt(p.pos)
|
|
|
|
if p.gotRsrv("!") {
|
|
|
|
s.Negated = true
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.newLine || stopToken(p.tok) {
|
2017-09-02 16:19:00 +02:00
|
|
|
p.posErr(s.Pos(), `"!" cannot form a statement alone`)
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
}
|
2017-07-06 01:46:05 +02:00
|
|
|
if s = p.gotStmtPipe(s); s == nil || p.err != nil {
|
2017-04-24 14:47:10 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
switch p.tok {
|
|
|
|
case andAnd, orOr:
|
|
|
|
// left associativity: in a list of BinaryCmds, the
|
|
|
|
// right recursion should only read a single element.
|
|
|
|
if binCmd {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// and instead of using recursion, iterate manually
|
|
|
|
for p.tok == andAnd || p.tok == orOr {
|
|
|
|
b := &BinaryCmd{
|
|
|
|
OpPos: p.pos,
|
|
|
|
Op: BinCmdOperator(p.tok),
|
|
|
|
X: s,
|
|
|
|
}
|
|
|
|
p.next()
|
2017-07-06 01:46:05 +02:00
|
|
|
if b.Y, _ = p.getStmt(false, true); b.Y == nil || p.err != nil {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.followErr(b.OpPos, b.Op.String(), "a statement")
|
2017-07-06 01:46:05 +02:00
|
|
|
return
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
s = p.stmt(s.Position)
|
|
|
|
s.Cmd = b
|
2017-07-06 01:46:05 +02:00
|
|
|
s.Comments, b.X.Comments = b.X.Comments, nil
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.tok != semicolon {
|
|
|
|
break
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
2017-05-27 16:17:49 +02:00
|
|
|
fallthrough
|
2017-04-24 14:47:10 +02:00
|
|
|
case semicolon:
|
|
|
|
if !p.newLine && readEnd {
|
|
|
|
s.Semicolon = p.pos
|
|
|
|
p.next()
|
|
|
|
}
|
2017-05-27 16:17:49 +02:00
|
|
|
case and:
|
|
|
|
p.next()
|
|
|
|
s.Background = true
|
|
|
|
case orAnd:
|
|
|
|
p.next()
|
|
|
|
s.Coprocess = true
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
2017-05-27 16:17:49 +02:00
|
|
|
gotEnd = s.Semicolon.IsValid() || s.Background || s.Coprocess
|
2017-07-06 01:46:05 +02:00
|
|
|
if len(p.accComs) > 0 && !binCmd {
|
|
|
|
c := p.accComs[0]
|
|
|
|
if c.Pos().Line() == s.End().Line() {
|
|
|
|
s.Comments = append(s.Comments, c)
|
|
|
|
p.accComs = p.accComs[1:]
|
|
|
|
}
|
|
|
|
}
|
2017-04-24 14:47:10 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) gotStmtPipe(s *Stmt) *Stmt {
|
2017-09-02 16:19:00 +02:00
|
|
|
s.Comments, p.accComs = p.accComs, nil
|
2017-04-24 14:47:10 +02:00
|
|
|
switch p.tok {
|
|
|
|
case _LitWord:
|
2017-05-01 00:50:22 +02:00
|
|
|
switch p.val {
|
|
|
|
case "{":
|
2017-04-24 14:47:10 +02:00
|
|
|
s.Cmd = p.block()
|
2017-05-01 00:50:22 +02:00
|
|
|
case "if":
|
2017-04-24 14:47:10 +02:00
|
|
|
s.Cmd = p.ifClause()
|
2017-05-17 19:49:27 +02:00
|
|
|
case "while", "until":
|
|
|
|
s.Cmd = p.whileClause(p.val == "until")
|
2017-05-01 00:50:22 +02:00
|
|
|
case "for":
|
2017-04-24 14:47:10 +02:00
|
|
|
s.Cmd = p.forClause()
|
2017-05-01 00:50:22 +02:00
|
|
|
case "case":
|
2017-04-24 14:47:10 +02:00
|
|
|
s.Cmd = p.caseClause()
|
2017-05-01 00:50:22 +02:00
|
|
|
case "}":
|
2017-09-02 16:19:00 +02:00
|
|
|
p.curErr(`%q can only be used to close a block`, p.val)
|
2017-05-01 00:50:22 +02:00
|
|
|
case "then":
|
|
|
|
p.curErr(`%q can only be used in an if`, p.val)
|
|
|
|
case "elif":
|
|
|
|
p.curErr(`%q can only be used in an if`, p.val)
|
|
|
|
case "fi":
|
|
|
|
p.curErr(`%q can only be used to end an if`, p.val)
|
|
|
|
case "do":
|
|
|
|
p.curErr(`%q can only be used in a loop`, p.val)
|
|
|
|
case "done":
|
|
|
|
p.curErr(`%q can only be used to end a loop`, p.val)
|
|
|
|
case "esac":
|
|
|
|
p.curErr(`%q can only be used to end a case`, p.val)
|
2017-08-05 19:13:35 +02:00
|
|
|
case "!":
|
|
|
|
if !s.Negated {
|
|
|
|
p.curErr(`"!" can only be used in full statements`)
|
|
|
|
break
|
|
|
|
}
|
2017-05-27 16:17:49 +02:00
|
|
|
case "[[":
|
2017-06-04 21:06:04 +02:00
|
|
|
if p.lang != LangPOSIX {
|
|
|
|
s.Cmd = p.testClause()
|
2017-05-01 00:50:22 +02:00
|
|
|
}
|
2017-05-27 16:17:49 +02:00
|
|
|
case "]]":
|
2017-06-04 21:06:04 +02:00
|
|
|
if p.lang != LangPOSIX {
|
2017-09-02 16:19:00 +02:00
|
|
|
p.curErr(`%q can only be used to close a test`,
|
2017-06-04 21:06:04 +02:00
|
|
|
p.val)
|
2017-05-27 16:17:49 +02:00
|
|
|
}
|
|
|
|
case "let":
|
2017-06-04 21:06:04 +02:00
|
|
|
if p.lang != LangPOSIX {
|
|
|
|
s.Cmd = p.letClause()
|
2017-05-27 16:17:49 +02:00
|
|
|
}
|
|
|
|
case "function":
|
2017-06-04 21:06:04 +02:00
|
|
|
if p.lang != LangPOSIX {
|
|
|
|
s.Cmd = p.bashFuncDecl()
|
2017-05-27 16:17:49 +02:00
|
|
|
}
|
|
|
|
case "declare":
|
2017-06-04 21:06:04 +02:00
|
|
|
if p.lang == LangBash {
|
|
|
|
s.Cmd = p.declClause()
|
2017-05-27 16:17:49 +02:00
|
|
|
}
|
|
|
|
case "local", "export", "readonly", "typeset", "nameref":
|
2017-06-04 21:06:04 +02:00
|
|
|
if p.lang != LangPOSIX {
|
|
|
|
s.Cmd = p.declClause()
|
2017-05-01 00:50:22 +02:00
|
|
|
}
|
2017-05-27 16:17:49 +02:00
|
|
|
case "time":
|
2017-06-04 21:06:04 +02:00
|
|
|
if p.lang != LangPOSIX {
|
|
|
|
s.Cmd = p.timeClause()
|
2017-05-27 16:17:49 +02:00
|
|
|
}
|
|
|
|
case "coproc":
|
2017-06-04 21:06:04 +02:00
|
|
|
if p.lang == LangBash {
|
|
|
|
s.Cmd = p.coprocClause()
|
2017-05-27 16:17:49 +02:00
|
|
|
}
|
2017-07-06 01:46:05 +02:00
|
|
|
case "select":
|
|
|
|
if p.lang != LangPOSIX {
|
|
|
|
s.Cmd = p.selectClause()
|
|
|
|
}
|
2017-06-04 21:06:04 +02:00
|
|
|
}
|
|
|
|
if s.Cmd != nil {
|
|
|
|
break
|
|
|
|
}
|
2017-07-31 00:11:34 +02:00
|
|
|
if p.hasValidIdent() {
|
|
|
|
s.Cmd = p.callExpr(s, nil, true)
|
|
|
|
break
|
|
|
|
}
|
2017-06-04 21:06:04 +02:00
|
|
|
name := p.lit(p.pos, p.val)
|
|
|
|
if p.next(); p.gotSameLine(leftParen) {
|
|
|
|
p.follow(name.ValuePos, "foo(", rightParen)
|
|
|
|
if p.lang == LangPOSIX && !ValidName(name.Value) {
|
|
|
|
p.posErr(name.Pos(), "invalid func name")
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
2017-06-04 21:06:04 +02:00
|
|
|
s.Cmd = p.funcDecl(name, name.ValuePos)
|
|
|
|
} else {
|
2017-07-31 00:11:34 +02:00
|
|
|
s.Cmd = p.callExpr(s, p.word(p.wps(name)), false)
|
|
|
|
}
|
|
|
|
case rdrOut, appOut, rdrIn, dplIn, dplOut, clbOut, rdrInOut,
|
|
|
|
hdoc, dashHdoc, wordHdoc, rdrAll, appAll, _LitRedir:
|
|
|
|
p.doRedirect(s)
|
|
|
|
switch {
|
|
|
|
case p.newLine, p.tok == _EOF, p.tok == semicolon:
|
|
|
|
return s
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
2017-07-31 00:11:34 +02:00
|
|
|
s.Cmd = p.callExpr(s, nil, false)
|
2017-04-24 14:47:10 +02:00
|
|
|
case bckQuote:
|
|
|
|
if p.quote == subCmdBckquo {
|
2017-08-05 19:13:35 +02:00
|
|
|
return nil
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
fallthrough
|
|
|
|
case _Lit, dollBrace, dollDblParen, dollParen, dollar, cmdIn, cmdOut,
|
|
|
|
sglQuote, dollSglQuote, dblQuote, dollDblQuote, dollBrack,
|
|
|
|
globQuest, globStar, globPlus, globAt, globExcl:
|
2017-07-31 00:11:34 +02:00
|
|
|
if p.tok == _Lit && p.hasValidIdent() {
|
|
|
|
s.Cmd = p.callExpr(s, nil, true)
|
|
|
|
break
|
|
|
|
}
|
2017-04-24 14:47:10 +02:00
|
|
|
w := p.word(p.wordParts())
|
|
|
|
if p.gotSameLine(leftParen) && p.err == nil {
|
|
|
|
p.posErr(w.Pos(), "invalid func name")
|
|
|
|
}
|
2017-07-31 00:11:34 +02:00
|
|
|
s.Cmd = p.callExpr(s, w, false)
|
2017-05-01 00:50:22 +02:00
|
|
|
case leftParen:
|
|
|
|
s.Cmd = p.subshell()
|
|
|
|
case dblLeftParen:
|
|
|
|
s.Cmd = p.arithmExpCmd()
|
2017-05-27 16:17:49 +02:00
|
|
|
default:
|
2017-07-31 00:11:34 +02:00
|
|
|
if len(s.Redirs) == 0 {
|
2017-05-27 16:17:49 +02:00
|
|
|
return nil
|
|
|
|
}
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
for !p.newLine && p.peekRedir() {
|
|
|
|
p.doRedirect(s)
|
|
|
|
}
|
2017-05-27 16:17:49 +02:00
|
|
|
switch p.tok {
|
|
|
|
case orAnd:
|
|
|
|
if p.lang == LangMirBSDKorn {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
fallthrough
|
|
|
|
case or:
|
2017-04-24 14:47:10 +02:00
|
|
|
b := &BinaryCmd{OpPos: p.pos, Op: BinCmdOperator(p.tok), X: s}
|
|
|
|
p.next()
|
2017-07-06 01:46:05 +02:00
|
|
|
if b.Y = p.gotStmtPipe(p.stmt(p.pos)); b.Y == nil || p.err != nil {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.followErr(b.OpPos, b.Op.String(), "a statement")
|
2017-07-06 01:46:05 +02:00
|
|
|
break
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
s = p.stmt(s.Position)
|
|
|
|
s.Cmd = b
|
2017-07-06 01:46:05 +02:00
|
|
|
s.Comments, b.X.Comments = b.X.Comments, nil
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) subshell() *Subshell {
|
2017-04-24 14:47:10 +02:00
|
|
|
s := &Subshell{Lparen: p.pos}
|
|
|
|
old := p.preNested(subCmd)
|
|
|
|
p.next()
|
2017-07-06 01:46:05 +02:00
|
|
|
s.StmtList = p.stmts()
|
2017-04-24 14:47:10 +02:00
|
|
|
p.postNested(old)
|
|
|
|
s.Rparen = p.matched(s.Lparen, leftParen, rightParen)
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) arithmExpCmd() Command {
|
2017-04-24 14:47:10 +02:00
|
|
|
ar := &ArithmCmd{Left: p.pos}
|
|
|
|
old := p.preNested(arithmExprCmd)
|
|
|
|
p.next()
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.got(hash) {
|
|
|
|
if p.lang != LangMirBSDKorn {
|
|
|
|
p.posErr(ar.Pos(), "unsigned expressions are a mksh feature")
|
|
|
|
}
|
|
|
|
ar.Unsigned = true
|
|
|
|
}
|
|
|
|
ar.X = p.followArithm(dblLeftParen, ar.Left)
|
2017-04-24 14:47:10 +02:00
|
|
|
ar.Right = p.arithmEnd(dblLeftParen, ar.Left, old)
|
|
|
|
return ar
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) block() *Block {
|
2017-04-24 14:47:10 +02:00
|
|
|
b := &Block{Lbrace: p.pos}
|
|
|
|
p.next()
|
2017-07-06 01:46:05 +02:00
|
|
|
b.StmtList = p.stmts("}")
|
2017-04-24 14:47:10 +02:00
|
|
|
b.Rbrace = p.pos
|
|
|
|
if !p.gotRsrv("}") {
|
|
|
|
p.matchingErr(b.Lbrace, "{", "}")
|
|
|
|
}
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) ifClause() *IfClause {
|
2017-07-06 01:46:05 +02:00
|
|
|
rif := &IfClause{IfPos: p.pos}
|
2017-04-24 14:47:10 +02:00
|
|
|
p.next()
|
2017-07-06 01:46:05 +02:00
|
|
|
rif.Cond = p.followStmts("if", rif.IfPos, "then")
|
|
|
|
rif.ThenPos = p.followRsrv(rif.IfPos, "if <cond>", "then")
|
|
|
|
rif.Then = p.followStmts("then", rif.ThenPos, "fi", "elif", "else")
|
|
|
|
curIf := rif
|
2017-04-24 14:47:10 +02:00
|
|
|
for p.tok == _LitWord && p.val == "elif" {
|
2017-07-06 01:46:05 +02:00
|
|
|
elf := &IfClause{IfPos: p.pos, Elif: true}
|
2017-04-24 14:47:10 +02:00
|
|
|
p.next()
|
2017-07-06 01:46:05 +02:00
|
|
|
elf.Cond = p.followStmts("elif", elf.IfPos, "then")
|
|
|
|
elf.ThenPos = p.followRsrv(elf.IfPos, "elif <cond>", "then")
|
|
|
|
elf.Then = p.followStmts("then", elf.ThenPos, "fi", "elif", "else")
|
|
|
|
s := p.stmt(elf.IfPos)
|
|
|
|
s.Cmd = elf
|
|
|
|
curIf.ElsePos = elf.IfPos
|
|
|
|
curIf.Else.Stmts = []*Stmt{s}
|
|
|
|
curIf = elf
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
if elsePos := p.pos; p.gotRsrv("else") {
|
2017-07-06 01:46:05 +02:00
|
|
|
curIf.ElsePos = elsePos
|
|
|
|
curIf.Else = p.followStmts("else", curIf.ElsePos, "fi")
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
2017-07-06 01:46:05 +02:00
|
|
|
rif.FiPos = p.stmtEnd(rif, "if", "fi")
|
|
|
|
curIf.FiPos = rif.FiPos
|
|
|
|
return rif
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) whileClause(until bool) *WhileClause {
|
2017-07-06 01:46:05 +02:00
|
|
|
wc := &WhileClause{WhilePos: p.pos, Until: until}
|
2017-05-17 19:49:27 +02:00
|
|
|
rsrv := "while"
|
|
|
|
rsrvCond := "while <cond>"
|
|
|
|
if wc.Until {
|
|
|
|
rsrv = "until"
|
|
|
|
rsrvCond = "until <cond>"
|
|
|
|
}
|
2017-04-24 14:47:10 +02:00
|
|
|
p.next()
|
2017-07-06 01:46:05 +02:00
|
|
|
wc.Cond = p.followStmts(rsrv, wc.WhilePos, "do")
|
|
|
|
wc.DoPos = p.followRsrv(wc.WhilePos, rsrvCond, "do")
|
|
|
|
wc.Do = p.followStmts("do", wc.DoPos, "done")
|
|
|
|
wc.DonePos = p.stmtEnd(wc, rsrv, "done")
|
2017-04-24 14:47:10 +02:00
|
|
|
return wc
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) forClause() *ForClause {
|
2017-07-06 01:46:05 +02:00
|
|
|
fc := &ForClause{ForPos: p.pos}
|
2017-04-24 14:47:10 +02:00
|
|
|
p.next()
|
2017-07-06 01:46:05 +02:00
|
|
|
fc.Loop = p.loop(fc.ForPos)
|
|
|
|
fc.DoPos = p.followRsrv(fc.ForPos, "for foo [in words]", "do")
|
|
|
|
fc.Do = p.followStmts("do", fc.DoPos, "done")
|
|
|
|
fc.DonePos = p.stmtEnd(fc, "for", "done")
|
2017-04-24 14:47:10 +02:00
|
|
|
return fc
|
|
|
|
}
|
|
|
|
|
2017-07-06 01:46:05 +02:00
|
|
|
func (p *Parser) loop(fpos Pos) Loop {
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.lang != LangBash {
|
|
|
|
switch p.tok {
|
|
|
|
case leftParen, dblLeftParen:
|
|
|
|
p.curErr("c-style fors are a bash feature")
|
|
|
|
}
|
|
|
|
}
|
2017-04-24 14:47:10 +02:00
|
|
|
if p.tok == dblLeftParen {
|
|
|
|
cl := &CStyleLoop{Lparen: p.pos}
|
|
|
|
old := p.preNested(arithmExprCmd)
|
2017-06-04 21:06:04 +02:00
|
|
|
p.next()
|
2017-08-05 19:13:35 +02:00
|
|
|
cl.Init = p.arithmExpr(0, false, false)
|
2017-06-04 21:06:04 +02:00
|
|
|
if !p.got(dblSemicolon) {
|
|
|
|
p.follow(p.pos, "expr", semicolon)
|
2017-08-05 19:13:35 +02:00
|
|
|
cl.Cond = p.arithmExpr(0, false, false)
|
2017-06-04 21:06:04 +02:00
|
|
|
p.follow(p.pos, "expr", semicolon)
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
2017-08-05 19:13:35 +02:00
|
|
|
cl.Post = p.arithmExpr(0, false, false)
|
2017-04-24 14:47:10 +02:00
|
|
|
cl.Rparen = p.arithmEnd(dblLeftParen, cl.Lparen, old)
|
|
|
|
p.gotSameLine(semicolon)
|
|
|
|
return cl
|
|
|
|
}
|
2017-09-02 16:19:00 +02:00
|
|
|
return p.wordIter("for", fpos)
|
2017-07-06 01:46:05 +02:00
|
|
|
}
|
|
|
|
|
2017-09-02 16:19:00 +02:00
|
|
|
func (p *Parser) wordIter(ftok string, fpos Pos) *WordIter {
|
|
|
|
wi := &WordIter{}
|
2017-04-24 14:47:10 +02:00
|
|
|
if wi.Name = p.getLit(); wi.Name == nil {
|
2017-07-06 01:46:05 +02:00
|
|
|
p.followErr(fpos, ftok, "a literal")
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
if p.gotRsrv("in") {
|
|
|
|
for !p.newLine && p.tok != _EOF && p.tok != semicolon {
|
|
|
|
if w := p.getWord(); w == nil {
|
|
|
|
p.curErr("word list can only contain words")
|
|
|
|
} else {
|
2017-05-27 16:17:49 +02:00
|
|
|
wi.Items = append(wi.Items, w)
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
p.gotSameLine(semicolon)
|
|
|
|
} else if !p.newLine && !p.got(semicolon) {
|
2017-07-06 01:46:05 +02:00
|
|
|
p.followErr(fpos, ftok+" foo", `"in", ; or a newline`)
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
return wi
|
|
|
|
}
|
|
|
|
|
2017-09-02 16:19:00 +02:00
|
|
|
func (p *Parser) selectClause() *ForClause {
|
|
|
|
fc := &ForClause{ForPos: p.pos, Select: true}
|
2017-07-06 01:46:05 +02:00
|
|
|
p.next()
|
2017-09-02 16:19:00 +02:00
|
|
|
fc.Loop = p.wordIter("select", fc.ForPos)
|
|
|
|
fc.DoPos = p.followRsrv(fc.ForPos, "select foo [in words]", "do")
|
2017-07-06 01:46:05 +02:00
|
|
|
fc.Do = p.followStmts("do", fc.DoPos, "done")
|
|
|
|
fc.DonePos = p.stmtEnd(fc, "select", "done")
|
|
|
|
return fc
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) caseClause() *CaseClause {
|
2017-04-24 14:47:10 +02:00
|
|
|
cc := &CaseClause{Case: p.pos}
|
|
|
|
p.next()
|
|
|
|
cc.Word = p.followWord("case", cc.Case)
|
2017-07-06 01:46:05 +02:00
|
|
|
end := "esac"
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.gotRsrv("{") {
|
|
|
|
if p.lang != LangMirBSDKorn {
|
|
|
|
p.posErr(cc.Pos(), `"case i {" is a mksh feature`)
|
|
|
|
}
|
2017-07-06 01:46:05 +02:00
|
|
|
end = "}"
|
2017-05-27 16:17:49 +02:00
|
|
|
} else {
|
|
|
|
p.followRsrv(cc.Case, "case x", "in")
|
|
|
|
}
|
2017-07-06 01:46:05 +02:00
|
|
|
cc.Items = p.caseItems(end)
|
|
|
|
cc.Last, p.accComs = p.accComs, nil
|
|
|
|
cc.Esac = p.stmtEnd(cc, "case", end)
|
2017-04-24 14:47:10 +02:00
|
|
|
return cc
|
|
|
|
}
|
|
|
|
|
2017-05-27 16:17:49 +02:00
|
|
|
func (p *Parser) caseItems(stop string) (items []*CaseItem) {
|
|
|
|
for p.tok != _EOF && !(p.tok == _LitWord && p.val == stop) {
|
|
|
|
ci := &CaseItem{}
|
2017-07-06 01:46:05 +02:00
|
|
|
ci.Comments, p.accComs = p.accComs, nil
|
2017-04-24 14:47:10 +02:00
|
|
|
p.got(leftParen)
|
|
|
|
for p.tok != _EOF {
|
|
|
|
if w := p.getWord(); w == nil {
|
|
|
|
p.curErr("case patterns must consist of words")
|
|
|
|
} else {
|
2017-05-27 16:17:49 +02:00
|
|
|
ci.Patterns = append(ci.Patterns, w)
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
if p.tok == rightParen {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if !p.got(or) {
|
|
|
|
p.curErr("case patterns must be separated with |")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
old := p.preNested(switchCase)
|
|
|
|
p.next()
|
2017-07-06 01:46:05 +02:00
|
|
|
ci.StmtList = p.stmts(stop)
|
2017-04-24 14:47:10 +02:00
|
|
|
p.postNested(old)
|
2017-05-27 16:17:49 +02:00
|
|
|
ci.OpPos = p.pos
|
|
|
|
switch p.tok {
|
|
|
|
case dblSemicolon, semiAnd, dblSemiAnd, semiOr:
|
|
|
|
default:
|
|
|
|
ci.Op = Break
|
|
|
|
items = append(items, ci)
|
|
|
|
return
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
2017-07-06 01:46:05 +02:00
|
|
|
ci.Last = append(ci.Last, p.accComs...)
|
|
|
|
p.accComs = nil
|
2017-05-27 16:17:49 +02:00
|
|
|
ci.Op = CaseOperator(p.tok)
|
2017-04-24 14:47:10 +02:00
|
|
|
p.next()
|
2017-07-06 01:46:05 +02:00
|
|
|
if len(p.accComs) > 0 {
|
|
|
|
c := p.accComs[0]
|
|
|
|
if c.Pos().Line() == ci.OpPos.Line() {
|
|
|
|
ci.Comments = append(ci.Comments, c)
|
|
|
|
p.accComs = p.accComs[1:]
|
|
|
|
}
|
|
|
|
}
|
2017-05-27 16:17:49 +02:00
|
|
|
items = append(items, ci)
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) testClause() *TestClause {
|
2017-04-24 14:47:10 +02:00
|
|
|
tc := &TestClause{Left: p.pos}
|
|
|
|
if p.next(); p.tok == _EOF || p.gotRsrv("]]") {
|
|
|
|
p.posErr(tc.Left, "test clause requires at least one expression")
|
|
|
|
}
|
2017-06-04 21:06:04 +02:00
|
|
|
tc.X = p.testExpr(illegalTok, tc.Left, false)
|
2017-04-24 14:47:10 +02:00
|
|
|
tc.Right = p.pos
|
|
|
|
if !p.gotRsrv("]]") {
|
|
|
|
p.matchingErr(tc.Left, "[[", "]]")
|
|
|
|
}
|
|
|
|
return tc
|
|
|
|
}
|
|
|
|
|
2017-06-04 21:06:04 +02:00
|
|
|
func (p *Parser) testExpr(ftok token, fpos Pos, pastAndOr bool) TestExpr {
|
2017-04-24 14:47:10 +02:00
|
|
|
var left TestExpr
|
2017-06-04 21:06:04 +02:00
|
|
|
if pastAndOr {
|
2017-04-24 14:47:10 +02:00
|
|
|
left = p.testExprBase(ftok, fpos)
|
|
|
|
} else {
|
2017-06-04 21:06:04 +02:00
|
|
|
left = p.testExpr(ftok, fpos, true)
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
if left == nil {
|
|
|
|
return left
|
|
|
|
}
|
|
|
|
switch p.tok {
|
|
|
|
case andAnd, orOr:
|
|
|
|
case _LitWord:
|
|
|
|
if p.val == "]]" {
|
|
|
|
return left
|
|
|
|
}
|
|
|
|
case rdrIn, rdrOut:
|
|
|
|
case _EOF, rightParen:
|
|
|
|
return left
|
|
|
|
case _Lit:
|
2017-05-17 19:49:27 +02:00
|
|
|
p.curErr("test operator words must consist of a single literal")
|
2017-04-24 14:47:10 +02:00
|
|
|
default:
|
|
|
|
p.curErr("not a valid test operator: %v", p.tok)
|
|
|
|
}
|
|
|
|
if p.tok == _LitWord {
|
2017-06-04 21:06:04 +02:00
|
|
|
if p.tok = token(testBinaryOp(p.val)); p.tok == illegalTok {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.curErr("not a valid test operator: %s", p.val)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
b := &BinaryTest{
|
|
|
|
OpPos: p.pos,
|
|
|
|
Op: BinTestOperator(p.tok),
|
|
|
|
X: left,
|
|
|
|
}
|
|
|
|
switch b.Op {
|
|
|
|
case AndTest, OrTest:
|
|
|
|
p.next()
|
2017-06-04 21:06:04 +02:00
|
|
|
if b.Y = p.testExpr(token(b.Op), b.OpPos, false); b.Y == nil {
|
2017-04-24 14:47:10 +02:00
|
|
|
p.followErrExp(b.OpPos, b.Op.String())
|
|
|
|
}
|
|
|
|
case TsReMatch:
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.lang != LangBash {
|
|
|
|
p.curErr("regex tests are a bash feature")
|
|
|
|
}
|
2017-04-24 14:47:10 +02:00
|
|
|
old := p.preNested(testRegexp)
|
|
|
|
defer p.postNested(old)
|
|
|
|
fallthrough
|
|
|
|
default:
|
|
|
|
if _, ok := b.X.(*Word); !ok {
|
|
|
|
p.posErr(b.OpPos, "expected %s, %s or %s after complex expr",
|
|
|
|
AndTest, OrTest, "]]")
|
|
|
|
}
|
|
|
|
p.next()
|
|
|
|
b.Y = p.followWordTok(token(b.Op), b.OpPos)
|
|
|
|
}
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) testExprBase(ftok token, fpos Pos) TestExpr {
|
2017-04-24 14:47:10 +02:00
|
|
|
switch p.tok {
|
2017-07-06 01:46:05 +02:00
|
|
|
case _EOF, rightParen:
|
2017-04-24 14:47:10 +02:00
|
|
|
return nil
|
|
|
|
case _LitWord:
|
2017-06-04 21:06:04 +02:00
|
|
|
op := token(testUnaryOp(p.val))
|
2017-05-27 16:17:49 +02:00
|
|
|
switch op {
|
|
|
|
case illegalTok:
|
2017-06-04 21:06:04 +02:00
|
|
|
case tsRefVar, tsModif: // not available in mksh
|
2017-05-27 16:17:49 +02:00
|
|
|
if p.lang == LangBash {
|
|
|
|
p.tok = op
|
|
|
|
}
|
|
|
|
default:
|
2017-04-24 14:47:10 +02:00
|
|
|
p.tok = op
|
|
|
|
}
|
|
|
|
}
|
|
|
|
switch p.tok {
|
|
|
|
case exclMark:
|
|
|
|
u := &UnaryTest{OpPos: p.pos, Op: TsNot}
|
|
|
|
p.next()
|
2017-07-31 00:11:34 +02:00
|
|
|
if u.X = p.testExpr(token(u.Op), u.OpPos, false); u.X == nil {
|
|
|
|
p.followErrExp(u.OpPos, u.Op.String())
|
|
|
|
}
|
2017-04-24 14:47:10 +02:00
|
|
|
return u
|
|
|
|
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:
|
|
|
|
u := &UnaryTest{OpPos: p.pos, Op: UnTestOperator(p.tok)}
|
|
|
|
p.next()
|
2017-07-31 00:11:34 +02:00
|
|
|
u.X = p.followWordTok(token(u.Op), u.OpPos)
|
2017-04-24 14:47:10 +02:00
|
|
|
return u
|
|
|
|
case leftParen:
|
|
|
|
pe := &ParenTest{Lparen: p.pos}
|
|
|
|
p.next()
|
2017-06-04 21:06:04 +02:00
|
|
|
if pe.X = p.testExpr(leftParen, pe.Lparen, false); pe.X == nil {
|
2017-05-27 16:17:49 +02:00
|
|
|
p.followErrExp(pe.Lparen, "(")
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
pe.Rparen = p.matched(pe.Lparen, leftParen, rightParen)
|
|
|
|
return pe
|
|
|
|
default:
|
|
|
|
// since we don't have [[ as a token
|
|
|
|
fstr := "[["
|
|
|
|
if ftok != illegalTok {
|
|
|
|
fstr = ftok.String()
|
|
|
|
}
|
|
|
|
return p.followWord(fstr, fpos)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) declClause() *DeclClause {
|
2017-07-06 01:46:05 +02:00
|
|
|
ds := &DeclClause{Variant: p.lit(p.pos, p.val)}
|
2017-04-24 14:47:10 +02:00
|
|
|
p.next()
|
2017-05-27 16:17:49 +02:00
|
|
|
for (p.tok == _LitWord || p.tok == _Lit) && p.val[0] == '-' {
|
2017-04-24 14:47:10 +02:00
|
|
|
ds.Opts = append(ds.Opts, p.getWord())
|
|
|
|
}
|
|
|
|
for !p.newLine && !stopToken(p.tok) && !p.peekRedir() {
|
|
|
|
if (p.tok == _Lit || p.tok == _LitWord) && p.hasValidIdent() {
|
2017-05-27 16:17:49 +02:00
|
|
|
ds.Assigns = append(ds.Assigns, p.getAssign(false))
|
2017-07-31 00:11:34 +02:00
|
|
|
} else if p.eqlOffs > 0 {
|
|
|
|
p.curErr("invalid var name")
|
2017-05-27 16:17:49 +02:00
|
|
|
} else if p.tok == _LitWord {
|
|
|
|
ds.Assigns = append(ds.Assigns, &Assign{
|
|
|
|
Naked: true,
|
|
|
|
Name: p.getLit(),
|
|
|
|
})
|
2017-07-06 01:46:05 +02:00
|
|
|
} else if w := p.getWord(); w != nil {
|
|
|
|
ds.Assigns = append(ds.Assigns, &Assign{
|
|
|
|
Naked: true,
|
|
|
|
Value: w,
|
|
|
|
})
|
2017-04-24 14:47:10 +02:00
|
|
|
} else {
|
2017-07-06 01:46:05 +02:00
|
|
|
p.followErr(p.pos, ds.Variant.Value, "names or assignments")
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return ds
|
|
|
|
}
|
|
|
|
|
|
|
|
func isBashCompoundCommand(tok token, val string) bool {
|
|
|
|
switch tok {
|
|
|
|
case leftParen, dblLeftParen:
|
|
|
|
return true
|
|
|
|
case _LitWord:
|
|
|
|
switch val {
|
2017-05-01 00:50:22 +02:00
|
|
|
case "{", "if", "while", "until", "for", "case", "[[",
|
|
|
|
"coproc", "let", "function", "declare", "local",
|
|
|
|
"export", "readonly", "typeset", "nameref":
|
2017-04-24 14:47:10 +02:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2017-05-27 16:17:49 +02:00
|
|
|
func (p *Parser) timeClause() *TimeClause {
|
|
|
|
tc := &TimeClause{Time: p.pos}
|
|
|
|
p.next()
|
|
|
|
if !p.newLine {
|
|
|
|
tc.Stmt = p.gotStmtPipe(p.stmt(p.pos))
|
|
|
|
}
|
|
|
|
return tc
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) coprocClause() *CoprocClause {
|
2017-04-24 14:47:10 +02:00
|
|
|
cc := &CoprocClause{Coproc: p.pos}
|
|
|
|
if p.next(); isBashCompoundCommand(p.tok, p.val) {
|
|
|
|
// has no name
|
2017-05-27 16:17:49 +02:00
|
|
|
cc.Stmt = p.gotStmtPipe(p.stmt(p.pos))
|
2017-04-24 14:47:10 +02:00
|
|
|
return cc
|
|
|
|
}
|
|
|
|
if p.newLine {
|
|
|
|
p.posErr(cc.Coproc, "coproc clause requires a command")
|
|
|
|
}
|
|
|
|
cc.Name = p.getLit()
|
2017-05-27 16:17:49 +02:00
|
|
|
cc.Stmt = p.gotStmtPipe(p.stmt(p.pos))
|
2017-04-24 14:47:10 +02:00
|
|
|
if cc.Stmt == nil {
|
|
|
|
if cc.Name == nil {
|
|
|
|
p.posErr(cc.Coproc, "coproc clause requires a command")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// name was in fact the stmt
|
|
|
|
cc.Stmt = p.stmt(cc.Name.ValuePos)
|
2017-05-01 00:50:22 +02:00
|
|
|
cc.Stmt.Cmd = p.call(p.word(p.wps(cc.Name)))
|
2017-04-24 14:47:10 +02:00
|
|
|
cc.Name = nil
|
|
|
|
} else if cc.Name != nil {
|
|
|
|
if call, ok := cc.Stmt.Cmd.(*CallExpr); ok {
|
|
|
|
// name was in fact the start of a call
|
2017-05-01 00:50:22 +02:00
|
|
|
call.Args = append([]*Word{p.word(p.wps(cc.Name))},
|
2017-04-24 14:47:10 +02:00
|
|
|
call.Args...)
|
|
|
|
cc.Name = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return cc
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) letClause() *LetClause {
|
2017-04-24 14:47:10 +02:00
|
|
|
lc := &LetClause{Let: p.pos}
|
|
|
|
old := p.preNested(arithmExprLet)
|
|
|
|
p.next()
|
|
|
|
for !p.newLine && !stopToken(p.tok) && !p.peekRedir() {
|
2017-08-05 19:13:35 +02:00
|
|
|
x := p.arithmExpr(0, true, false)
|
2017-04-24 14:47:10 +02:00
|
|
|
if x == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
lc.Exprs = append(lc.Exprs, x)
|
|
|
|
}
|
|
|
|
if len(lc.Exprs) == 0 {
|
2017-05-27 16:17:49 +02:00
|
|
|
p.followErrExp(lc.Let, "let")
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
p.postNested(old)
|
|
|
|
if p.tok == illegalTok {
|
|
|
|
p.next()
|
|
|
|
}
|
|
|
|
return lc
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) bashFuncDecl() *FuncDecl {
|
2017-04-24 14:47:10 +02:00
|
|
|
fpos := p.pos
|
|
|
|
p.next()
|
|
|
|
if p.tok != _LitWord {
|
|
|
|
if w := p.followWord("function", fpos); p.err == nil {
|
|
|
|
p.posErr(w.Pos(), "invalid func name")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
name := p.lit(p.pos, p.val)
|
|
|
|
if p.next(); p.gotSameLine(leftParen) {
|
|
|
|
p.follow(name.ValuePos, "foo(", rightParen)
|
|
|
|
}
|
|
|
|
return p.funcDecl(name, fpos)
|
|
|
|
}
|
|
|
|
|
2017-07-31 00:11:34 +02:00
|
|
|
func (p *Parser) callExpr(s *Stmt, w *Word, assign bool) Command {
|
2017-04-24 14:47:10 +02:00
|
|
|
ce := p.call(w)
|
2017-07-31 00:11:34 +02:00
|
|
|
if w == nil {
|
|
|
|
ce.Args = ce.Args[:0]
|
|
|
|
}
|
|
|
|
if assign {
|
|
|
|
ce.Assigns = append(ce.Assigns, p.getAssign(true))
|
|
|
|
}
|
|
|
|
loop:
|
2017-04-24 14:47:10 +02:00
|
|
|
for !p.newLine {
|
|
|
|
switch p.tok {
|
2017-05-27 16:17:49 +02:00
|
|
|
case _EOF, semicolon, and, or, andAnd, orOr, orAnd,
|
|
|
|
dblSemicolon, semiAnd, dblSemiAnd, semiOr:
|
2017-07-31 00:11:34 +02:00
|
|
|
break loop
|
2017-04-24 14:47:10 +02:00
|
|
|
case _LitWord:
|
2017-07-31 00:11:34 +02:00
|
|
|
if len(ce.Args) == 0 && p.hasValidIdent() {
|
|
|
|
ce.Assigns = append(ce.Assigns, p.getAssign(true))
|
|
|
|
break
|
|
|
|
}
|
2017-04-24 14:47:10 +02:00
|
|
|
ce.Args = append(ce.Args, p.word(
|
2017-05-01 00:50:22 +02:00
|
|
|
p.wps(p.lit(p.pos, p.val)),
|
2017-04-24 14:47:10 +02:00
|
|
|
))
|
|
|
|
p.next()
|
2017-07-31 00:11:34 +02:00
|
|
|
case _Lit:
|
|
|
|
if len(ce.Args) == 0 && p.hasValidIdent() {
|
|
|
|
ce.Assigns = append(ce.Assigns, p.getAssign(true))
|
|
|
|
break
|
|
|
|
}
|
|
|
|
ce.Args = append(ce.Args, p.word(p.wordParts()))
|
2017-04-24 14:47:10 +02:00
|
|
|
case bckQuote:
|
|
|
|
if p.quote == subCmdBckquo {
|
2017-07-31 00:11:34 +02:00
|
|
|
break loop
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
fallthrough
|
2017-07-31 00:11:34 +02:00
|
|
|
case dollBrace, dollDblParen, dollParen, dollar, cmdIn, cmdOut,
|
2017-04-24 14:47:10 +02:00
|
|
|
sglQuote, dollSglQuote, dblQuote, dollDblQuote, dollBrack,
|
|
|
|
globQuest, globStar, globPlus, globAt, globExcl:
|
|
|
|
ce.Args = append(ce.Args, p.word(p.wordParts()))
|
|
|
|
case rdrOut, appOut, rdrIn, dplIn, dplOut, clbOut, rdrInOut,
|
|
|
|
hdoc, dashHdoc, wordHdoc, rdrAll, appAll, _LitRedir:
|
|
|
|
p.doRedirect(s)
|
|
|
|
case dblLeftParen:
|
|
|
|
p.curErr("%s can only be used to open an arithmetic cmd", p.tok)
|
|
|
|
case rightParen:
|
|
|
|
if p.quote == subCmd {
|
2017-07-31 00:11:34 +02:00
|
|
|
break loop
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
fallthrough
|
|
|
|
default:
|
|
|
|
p.curErr("a command can only contain words and redirects")
|
|
|
|
}
|
|
|
|
}
|
2017-07-31 00:11:34 +02:00
|
|
|
if len(ce.Assigns) == 0 && len(ce.Args) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if len(ce.Args) == 0 {
|
|
|
|
ce.Args = nil
|
|
|
|
}
|
2017-04-24 14:47:10 +02:00
|
|
|
return ce
|
|
|
|
}
|
|
|
|
|
2017-05-17 19:49:27 +02:00
|
|
|
func (p *Parser) funcDecl(name *Lit, pos Pos) *FuncDecl {
|
2017-04-24 14:47:10 +02:00
|
|
|
fd := &FuncDecl{
|
2017-06-04 21:06:04 +02:00
|
|
|
Position: pos,
|
|
|
|
RsrvWord: pos != name.ValuePos,
|
|
|
|
Name: name,
|
2017-04-24 14:47:10 +02:00
|
|
|
}
|
|
|
|
if fd.Body, _ = p.getStmt(false, false); fd.Body == nil {
|
|
|
|
p.followErr(fd.Pos(), "foo()", "a statement")
|
|
|
|
}
|
|
|
|
return fd
|
|
|
|
}
|