diff --git a/syntax/filetests_test.go b/syntax/filetests_test.go index 87d845797..0fa438df0 100644 --- a/syntax/filetests_test.go +++ b/syntax/filetests_test.go @@ -4008,6 +4008,19 @@ var fileTests = []testCase{ }}}, }}}, }, + { + Strs: []string{ + "a=([x]= [y]=)", + "a=(\n[x]=\n[y]=\n)", + }, + bash: &CallExpr{Assigns: []*Assign{{ + Name: lit("a"), + Array: &ArrayExpr{Elems: []*ArrayElem{ + {Index: litWord("x")}, + {Index: litWord("y")}, + }}, + }}}, + }, { Strs: []string{"a]b"}, common: litStmt("a]b"), @@ -4579,7 +4592,9 @@ func clearPosRecurse(tb testing.TB, src string, v interface{}) { if x.Index != nil { recurse(x.Index) } - recurse(x.Value) + if x.Value != nil { + recurse(x.Value) + } case *ExtGlob: setPos(&x.OpPos, x.Op.String()) checkSrc(posAddCol(x.End(), -1), ")") diff --git a/syntax/nodes.go b/syntax/nodes.go index 8527af972..e20d13b20 100644 --- a/syntax/nodes.go +++ b/syntax/nodes.go @@ -756,8 +756,12 @@ func (a *ArrayExpr) Pos() Pos { return a.Lparen } func (a *ArrayExpr) End() Pos { return posAddCol(a.Rparen, 1) } // ArrayElem represents a Bash array element. +// +// Index can be nil; for example, declare -a x=(value). +// Value can be nil; for example, declare -A x=([index]=). +// Finally, neither can be nil; for example, declare -A x=([index]=value) type ArrayElem struct { - Index ArithmExpr // [i]=, ["k"]= + Index ArithmExpr Value *Word Comments []Comment } @@ -768,7 +772,12 @@ func (a *ArrayElem) Pos() Pos { } return a.Value.Pos() } -func (a *ArrayElem) End() Pos { return a.Value.End() } +func (a *ArrayElem) End() Pos { + if a.Value != nil { + return a.Value.End() + } + return posAddCol(a.Index.Pos(), 1) +} // ExtGlob represents a Bash extended globbing expression. Note that these are // parsed independently of whether shopt has been called or not. diff --git a/syntax/parser.go b/syntax/parser.go index 298d93139..7fa46873e 100644 --- a/syntax/parser.go +++ b/syntax/parser.go @@ -1609,11 +1609,16 @@ func (p *Parser) getAssign(needEqual bool) *Assign { p.follow(left, `"[x]"`, assgn) } if ae.Value = p.getWord(); ae.Value == nil { - if p.tok == leftParen { + switch p.tok { + case leftParen: p.curErr("arrays cannot be nested") + return nil + case _Newl, rightParen, leftBrack: + // TODO: support [index]=[ + default: + p.curErr("array element values must be words") + break } - p.curErr("array element values must be words") - break } if len(p.accComs) > 0 { c := p.accComs[0] diff --git a/syntax/printer.go b/syntax/printer.go index f68e8656e..4419e40fd 100644 --- a/syntax/printer.go +++ b/syntax/printer.go @@ -788,7 +788,9 @@ func (p *Printer) elemJoin(elems []*ArrayElem, last []Comment) { if p.wroteIndex(el.Index) { p.WriteByte('=') } - p.word(el.Value) + if el.Value != nil { + p.word(el.Value) + } p.comments(left...) } if len(last) > 0 { diff --git a/syntax/walk.go b/syntax/walk.go index a67be8c63..d3f6332ea 100644 --- a/syntax/walk.go +++ b/syntax/walk.go @@ -201,7 +201,9 @@ func Walk(node Node, f func(Node) bool) { if x.Index != nil { Walk(x.Index, f) } - Walk(x.Value, f) + if x.Value != nil { + Walk(x.Value, f) + } case *ExtGlob: Walk(x.Pattern, f) case *ProcSubst: