Skip to content

Commit

Permalink
syntax,interp: fix "for i; do ..." loops
Browse files Browse the repository at this point in the history
They aren't no-op loops like "for i in; do ..."; they range over the
shell's positional paramters. That is, it's a short version of:

	for i in "$@"; do ...

Fixes mvdan#341.
  • Loading branch information
mvdan committed Jan 5, 2019
1 parent b4690b0 commit e3c5ce0
Show file tree
Hide file tree
Showing 7 changed files with 30 additions and 5 deletions.
6 changes: 5 additions & 1 deletion interp/interp.go
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,11 @@ func (r *Runner) cmd(ctx context.Context, cm syntax.Command) {
switch y := x.Loop.(type) {
case *syntax.WordIter:
name := y.Name.Value
for _, field := range r.fields(y.Items...) {
items := r.Params // for i; do ...
if y.InPos.IsValid() {
items = r.fields(y.Items...) // for i in ...; do ...
}
for _, field := range items {
r.setVarString(name, field)
if r.loopStmtsBroken(ctx, x.Do) {
break
Expand Down
8 changes: 8 additions & 0 deletions interp/interp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,14 @@ var fileCases = []struct {
"for ((i=0; i<3; i++)); do :; done; echo $i",
"3\n",
},
{
"set -- a 'b c'; for i in; do echo $i; done",
"",
},
{
"set -- a 'b c'; for i; do echo $i; done",
"a\nb c\n",
},

// block
{
Expand Down
3 changes: 3 additions & 0 deletions syntax/filetests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4384,6 +4384,9 @@ func clearPosRecurse(tb testing.TB, src string, v interface{}) {
recurse(x.Do)
case *WordIter:
recurse(x.Name)
if x.InPos.IsValid() {
setPos(&x.InPos, "in")
}
recurse(x.Items)
case *CStyleLoop:
setPos(&x.Lparen, "((")
Expand Down
11 changes: 9 additions & 2 deletions syntax/nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,14 +373,21 @@ func (*WordIter) loopNode() {}
func (*CStyleLoop) loopNode() {}

// WordIter represents the iteration of a variable over a series of words in a
// for clause.
// for clause. If InPos is an invalid position, the "in" token was missing, so
// the iteration is over the shell's positional parameters.
type WordIter struct {
Name *Lit
InPos Pos // position of "in"
Items []*Word
}

func (w *WordIter) Pos() Pos { return w.Name.Pos() }
func (w *WordIter) End() Pos { return posMax(w.Name.End(), wordLastEnd(w.Items)) }
func (w *WordIter) End() Pos {
if len(w.Items) > 0 {
return wordLastEnd(w.Items)
}
return posMax(w.Name.End(), posAddCol(w.InPos, 2))
}

// CStyleLoop represents the behaviour of a for clause similar to the C
// language.
Expand Down
3 changes: 2 additions & 1 deletion syntax/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -2017,7 +2017,8 @@ func (p *Parser) wordIter(ftok string, fpos Pos) *WordIter {
return wi
}
p.got(_Newl)
if _, ok := p.gotRsrv("in"); ok {
if pos, ok := p.gotRsrv("in"); ok {
wi.InPos = pos
for !stopToken(p.tok) {
if w := p.getWord(); w == nil {
p.curErr("word list can only contain words")
Expand Down
2 changes: 1 addition & 1 deletion syntax/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ func (p *Printer) loop(loop Loop) {
switch x := loop.(type) {
case *WordIter:
p.WriteString(x.Name.Value)
if len(x.Items) > 0 {
if x.InPos.IsValid() {
p.spacedString(" in", Pos{})
p.wordJoin(x.Items)
}
Expand Down
2 changes: 2 additions & 0 deletions syntax/printer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,8 @@ var printTests = []printCase{
"if foo # inline\nthen\n\tbar\nfi",
"if foo; then # inline\n\tbar\nfi",
},
samePrint("for i; do echo $i; done"),
samePrint("for i in; do echo $i; done"),
{
"for foo in a b # inline\ndo\n\tbar\ndone",
"for foo in a b; do # inline\n\tbar\ndone",
Expand Down

0 comments on commit e3c5ce0

Please sign in to comment.