Move mvdan.cc/sh to v3

This commit is contained in:
Daniele Rondina
2019-12-15 12:22:29 +01:00
parent d583fa8bf5
commit 47ba3c51cf
54 changed files with 3228 additions and 2146 deletions

3
go.mod
View File

@@ -35,5 +35,6 @@ require (
golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152 // indirect
golang.org/x/sys v0.0.0-20191110163157-d32e6e3b99c4 // indirect
gopkg.in/yaml.v2 v2.2.5
mvdan.cc/sh v2.6.4+incompatible
mvdan.cc/sh v2.6.4+incompatible // indirect
mvdan.cc/sh/v3 v3.0.0-beta1
)

9
go.sum
View File

@@ -204,6 +204,7 @@ github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzI
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/philopon/go-toposort v0.0.0-20170620085441-9be86dbd762f h1:WyCn68lTiytVSkk7W1K9nBiSGTSRlUOdyTnSjwrIlok=
github.com/philopon/go-toposort v0.0.0-20170620085441-9be86dbd762f/go.mod h1:/iRjX3DdSK956SzsUdV55J+wIsQ+2IBWmBrB4RvZfk4=
github.com/pkg/diff v0.0.0-20190930165518-531926345625/go.mod h1:kFj35MyHn14a6pIgWhm46KVjJr5CHys3eEYxkuKD1EI=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -221,6 +222,7 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.5.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rootless-containers/proto v0.1.0 h1:gS1JOMEtk1YDYHCzBAf/url+olMJbac7MTrgSeP6zh4=
github.com/rootless-containers/proto v0.1.0/go.mod h1:vgkUFZbQd0gcE/K/ZwtE4MYjZPu0UNHLXIQxhyqAFh8=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
@@ -302,6 +304,7 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152 h1:ZC1Xn5A1nlpSmQCIva4bZ3ob3lmhYIefc+GU+DLg1Ow=
golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -350,6 +353,8 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190913181337-0240832f5c3d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@@ -361,6 +366,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
@@ -377,5 +383,8 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.1.0+incompatible h1:5USw7CrJBYKqjg9R7QlA6jzqZKEAtvW82aNmsxxGPxw=
gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
mvdan.cc/editorconfig v0.1.1-0.20191109213504-890940e3f00e/go.mod h1:Ge4atmRUYqueGppvJ7JNrtqpqokoJEFxYbP0Z+WeKS8=
mvdan.cc/sh v2.6.4+incompatible h1:eD6tDeh0pw+/TOTI1BBEryZ02rD2nMcFsgcvde7jffM=
mvdan.cc/sh v2.6.4+incompatible/go.mod h1:IeeQbZq+x2SUGBensq/jge5lLQbS3XT2ktyp3wrt4x8=
mvdan.cc/sh/v3 v3.0.0-beta1 h1:UqiwBEXEPzelaGxuvixaOtzc7WzKtrElePJ8HqvW7K8=
mvdan.cc/sh/v3 v3.0.0-beta1/go.mod h1:rBIndNJFYPp8xSppiZcGIk6B5d1g3OEARxEaXjPxwVI=

27
vendor/golang.org/x/xerrors/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,27 @@
Copyright (c) 2019 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

22
vendor/golang.org/x/xerrors/PATENTS generated vendored Normal file
View File

@@ -0,0 +1,22 @@
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Go project.
Google hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation of Go, where such license applies only to those patent
claims, both currently owned or controlled by Google and acquired in
the future, licensable by Google that are necessarily infringed by this
implementation of Go. This grant does not include claims that would be
infringed only as a consequence of further modification of this
implementation. If you or your agent or exclusive licensee institute or
order or agree to the institution of patent litigation against any
entity (including a cross-claim or counterclaim in a lawsuit) alleging
that this implementation of Go or any code incorporated within this
implementation of Go constitutes direct or contributory patent
infringement, or inducement of patent infringement, then any patent
rights granted to you under this License for this implementation of Go
shall terminate as of the date such litigation is filed.

2
vendor/golang.org/x/xerrors/README generated vendored Normal file
View File

@@ -0,0 +1,2 @@
This repository holds the transition packages for the new Go 1.13 error values.
See golang.org/design/29934-error-values.

193
vendor/golang.org/x/xerrors/adaptor.go generated vendored Normal file
View File

@@ -0,0 +1,193 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xerrors
import (
"bytes"
"fmt"
"io"
"reflect"
"strconv"
)
// FormatError calls the FormatError method of f with an errors.Printer
// configured according to s and verb, and writes the result to s.
func FormatError(f Formatter, s fmt.State, verb rune) {
// Assuming this function is only called from the Format method, and given
// that FormatError takes precedence over Format, it cannot be called from
// any package that supports errors.Formatter. It is therefore safe to
// disregard that State may be a specific printer implementation and use one
// of our choice instead.
// limitations: does not support printing error as Go struct.
var (
sep = " " // separator before next error
p = &state{State: s}
direct = true
)
var err error = f
switch verb {
// Note that this switch must match the preference order
// for ordinary string printing (%#v before %+v, and so on).
case 'v':
if s.Flag('#') {
if stringer, ok := err.(fmt.GoStringer); ok {
io.WriteString(&p.buf, stringer.GoString())
goto exit
}
// proceed as if it were %v
} else if s.Flag('+') {
p.printDetail = true
sep = "\n - "
}
case 's':
case 'q', 'x', 'X':
// Use an intermediate buffer in the rare cases that precision,
// truncation, or one of the alternative verbs (q, x, and X) are
// specified.
direct = false
default:
p.buf.WriteString("%!")
p.buf.WriteRune(verb)
p.buf.WriteByte('(')
switch {
case err != nil:
p.buf.WriteString(reflect.TypeOf(f).String())
default:
p.buf.WriteString("<nil>")
}
p.buf.WriteByte(')')
io.Copy(s, &p.buf)
return
}
loop:
for {
switch v := err.(type) {
case Formatter:
err = v.FormatError((*printer)(p))
case fmt.Formatter:
v.Format(p, 'v')
break loop
default:
io.WriteString(&p.buf, v.Error())
break loop
}
if err == nil {
break
}
if p.needColon || !p.printDetail {
p.buf.WriteByte(':')
p.needColon = false
}
p.buf.WriteString(sep)
p.inDetail = false
p.needNewline = false
}
exit:
width, okW := s.Width()
prec, okP := s.Precision()
if !direct || (okW && width > 0) || okP {
// Construct format string from State s.
format := []byte{'%'}
if s.Flag('-') {
format = append(format, '-')
}
if s.Flag('+') {
format = append(format, '+')
}
if s.Flag(' ') {
format = append(format, ' ')
}
if okW {
format = strconv.AppendInt(format, int64(width), 10)
}
if okP {
format = append(format, '.')
format = strconv.AppendInt(format, int64(prec), 10)
}
format = append(format, string(verb)...)
fmt.Fprintf(s, string(format), p.buf.String())
} else {
io.Copy(s, &p.buf)
}
}
var detailSep = []byte("\n ")
// state tracks error printing state. It implements fmt.State.
type state struct {
fmt.State
buf bytes.Buffer
printDetail bool
inDetail bool
needColon bool
needNewline bool
}
func (s *state) Write(b []byte) (n int, err error) {
if s.printDetail {
if len(b) == 0 {
return 0, nil
}
if s.inDetail && s.needColon {
s.needNewline = true
if b[0] == '\n' {
b = b[1:]
}
}
k := 0
for i, c := range b {
if s.needNewline {
if s.inDetail && s.needColon {
s.buf.WriteByte(':')
s.needColon = false
}
s.buf.Write(detailSep)
s.needNewline = false
}
if c == '\n' {
s.buf.Write(b[k:i])
k = i + 1
s.needNewline = true
}
}
s.buf.Write(b[k:])
if !s.inDetail {
s.needColon = true
}
} else if !s.inDetail {
s.buf.Write(b)
}
return len(b), nil
}
// printer wraps a state to implement an xerrors.Printer.
type printer state
func (s *printer) Print(args ...interface{}) {
if !s.inDetail || s.printDetail {
fmt.Fprint((*state)(s), args...)
}
}
func (s *printer) Printf(format string, args ...interface{}) {
if !s.inDetail || s.printDetail {
fmt.Fprintf((*state)(s), format, args...)
}
}
func (s *printer) Detail() bool {
s.inDetail = true
return s.printDetail
}

1
vendor/golang.org/x/xerrors/codereview.cfg generated vendored Normal file
View File

@@ -0,0 +1 @@
issuerepo: golang/go

22
vendor/golang.org/x/xerrors/doc.go generated vendored Normal file
View File

@@ -0,0 +1,22 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package xerrors implements functions to manipulate errors.
//
// This package is based on the Go 2 proposal for error values:
// https://golang.org/design/29934-error-values
//
// These functions were incorporated into the standard library's errors package
// in Go 1.13:
// - Is
// - As
// - Unwrap
//
// Also, Errorf's %w verb was incorporated into fmt.Errorf.
//
// Use this package to get equivalent behavior in all supported Go versions.
//
// No other features of this package were included in Go 1.13, and at present
// there are no plans to include any of them.
package xerrors // import "golang.org/x/xerrors"

33
vendor/golang.org/x/xerrors/errors.go generated vendored Normal file
View File

@@ -0,0 +1,33 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xerrors
import "fmt"
// errorString is a trivial implementation of error.
type errorString struct {
s string
frame Frame
}
// New returns an error that formats as the given text.
//
// The returned error contains a Frame set to the caller's location and
// implements Formatter to show this information when printed with details.
func New(text string) error {
return &errorString{text, Caller(1)}
}
func (e *errorString) Error() string {
return e.s
}
func (e *errorString) Format(s fmt.State, v rune) { FormatError(e, s, v) }
func (e *errorString) FormatError(p Printer) (next error) {
p.Print(e.s)
e.frame.Format(p)
return nil
}

109
vendor/golang.org/x/xerrors/fmt.go generated vendored Normal file
View File

@@ -0,0 +1,109 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xerrors
import (
"fmt"
"strings"
"golang.org/x/xerrors/internal"
)
// Errorf formats according to a format specifier and returns the string as a
// value that satisfies error.
//
// The returned error includes the file and line number of the caller when
// formatted with additional detail enabled. If the last argument is an error
// the returned error's Format method will return it if the format string ends
// with ": %s", ": %v", or ": %w". If the last argument is an error and the
// format string ends with ": %w", the returned error implements Wrapper
// with an Unwrap method returning it.
func Errorf(format string, a ...interface{}) error {
err, wrap := lastError(format, a)
format = formatPlusW(format)
if err == nil {
return &noWrapError{fmt.Sprintf(format, a...), nil, Caller(1)}
}
// TODO: this is not entirely correct. The error value could be
// printed elsewhere in format if it mixes numbered with unnumbered
// substitutions. With relatively small changes to doPrintf we can
// have it optionally ignore extra arguments and pass the argument
// list in its entirety.
msg := fmt.Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...)
frame := Frame{}
if internal.EnableTrace {
frame = Caller(1)
}
if wrap {
return &wrapError{msg, err, frame}
}
return &noWrapError{msg, err, frame}
}
// formatPlusW is used to avoid the vet check that will barf at %w.
func formatPlusW(s string) string {
return s
}
func lastError(format string, a []interface{}) (err error, wrap bool) {
wrap = strings.HasSuffix(format, ": %w")
if !wrap &&
!strings.HasSuffix(format, ": %s") &&
!strings.HasSuffix(format, ": %v") {
return nil, false
}
if len(a) == 0 {
return nil, false
}
err, ok := a[len(a)-1].(error)
if !ok {
return nil, false
}
return err, wrap
}
type noWrapError struct {
msg string
err error
frame Frame
}
func (e *noWrapError) Error() string {
return fmt.Sprint(e)
}
func (e *noWrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) }
func (e *noWrapError) FormatError(p Printer) (next error) {
p.Print(e.msg)
e.frame.Format(p)
return e.err
}
type wrapError struct {
msg string
err error
frame Frame
}
func (e *wrapError) Error() string {
return fmt.Sprint(e)
}
func (e *wrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) }
func (e *wrapError) FormatError(p Printer) (next error) {
p.Print(e.msg)
e.frame.Format(p)
return e.err
}
func (e *wrapError) Unwrap() error {
return e.err
}

34
vendor/golang.org/x/xerrors/format.go generated vendored Normal file
View File

@@ -0,0 +1,34 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xerrors
// A Formatter formats error messages.
type Formatter interface {
error
// FormatError prints the receiver's first error and returns the next error in
// the error chain, if any.
FormatError(p Printer) (next error)
}
// A Printer formats error messages.
//
// The most common implementation of Printer is the one provided by package fmt
// during Printf (as of Go 1.13). Localization packages such as golang.org/x/text/message
// typically provide their own implementations.
type Printer interface {
// Print appends args to the message output.
Print(args ...interface{})
// Printf writes a formatted string.
Printf(format string, args ...interface{})
// Detail reports whether error detail is requested.
// After the first call to Detail, all text written to the Printer
// is formatted as additional detail, or ignored when
// detail has not been requested.
// If Detail returns false, the caller can avoid printing the detail at all.
Detail() bool
}

56
vendor/golang.org/x/xerrors/frame.go generated vendored Normal file
View File

@@ -0,0 +1,56 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xerrors
import (
"runtime"
)
// A Frame contains part of a call stack.
type Frame struct {
// Make room for three PCs: the one we were asked for, what it called,
// and possibly a PC for skipPleaseUseCallersFrames. See:
// https://go.googlesource.com/go/+/032678e0fb/src/runtime/extern.go#169
frames [3]uintptr
}
// Caller returns a Frame that describes a frame on the caller's stack.
// The argument skip is the number of frames to skip over.
// Caller(0) returns the frame for the caller of Caller.
func Caller(skip int) Frame {
var s Frame
runtime.Callers(skip+1, s.frames[:])
return s
}
// location reports the file, line, and function of a frame.
//
// The returned function may be "" even if file and line are not.
func (f Frame) location() (function, file string, line int) {
frames := runtime.CallersFrames(f.frames[:])
if _, ok := frames.Next(); !ok {
return "", "", 0
}
fr, ok := frames.Next()
if !ok {
return "", "", 0
}
return fr.Function, fr.File, fr.Line
}
// Format prints the stack as error detail.
// It should be called from an error's Format implementation
// after printing any other error detail.
func (f Frame) Format(p Printer) {
if p.Detail() {
function, file, line := f.location()
if function != "" {
p.Printf("%s\n ", function)
}
if file != "" {
p.Printf("%s:%d\n", file, line)
}
}
}

3
vendor/golang.org/x/xerrors/go.mod generated vendored Normal file
View File

@@ -0,0 +1,3 @@
module golang.org/x/xerrors
go 1.11

8
vendor/golang.org/x/xerrors/internal/internal.go generated vendored Normal file
View File

@@ -0,0 +1,8 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package internal
// EnableTrace indicates whether stack information should be recorded in errors.
var EnableTrace = true

106
vendor/golang.org/x/xerrors/wrap.go generated vendored Normal file
View File

@@ -0,0 +1,106 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xerrors
import (
"reflect"
)
// A Wrapper provides context around another error.
type Wrapper interface {
// Unwrap returns the next error in the error chain.
// If there is no next error, Unwrap returns nil.
Unwrap() error
}
// Opaque returns an error with the same error formatting as err
// but that does not match err and cannot be unwrapped.
func Opaque(err error) error {
return noWrapper{err}
}
type noWrapper struct {
error
}
func (e noWrapper) FormatError(p Printer) (next error) {
if f, ok := e.error.(Formatter); ok {
return f.FormatError(p)
}
p.Print(e.error)
return nil
}
// Unwrap returns the result of calling the Unwrap method on err, if err implements
// Unwrap. Otherwise, Unwrap returns nil.
func Unwrap(err error) error {
u, ok := err.(Wrapper)
if !ok {
return nil
}
return u.Unwrap()
}
// Is reports whether any error in err's chain matches target.
//
// An error is considered to match a target if it is equal to that target or if
// it implements a method Is(error) bool such that Is(target) returns true.
func Is(err, target error) bool {
if target == nil {
return err == target
}
isComparable := reflect.TypeOf(target).Comparable()
for {
if isComparable && err == target {
return true
}
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
return true
}
// TODO: consider supporing target.Is(err). This would allow
// user-definable predicates, but also may allow for coping with sloppy
// APIs, thereby making it easier to get away with them.
if err = Unwrap(err); err == nil {
return false
}
}
}
// As finds the first error in err's chain that matches the type to which target
// points, and if so, sets the target to its value and returns true. An error
// matches a type if it is assignable to the target type, or if it has a method
// As(interface{}) bool such that As(target) returns true. As will panic if target
// is not a non-nil pointer to a type which implements error or is of interface type.
//
// The As method should set the target to its value and return true if err
// matches the type to which target points.
func As(err error, target interface{}) bool {
if target == nil {
panic("errors: target cannot be nil")
}
val := reflect.ValueOf(target)
typ := val.Type()
if typ.Kind() != reflect.Ptr || val.IsNil() {
panic("errors: target must be a non-nil pointer")
}
if e := typ.Elem(); e.Kind() != reflect.Interface && !e.Implements(errorType) {
panic("errors: *target must be interface or implement error")
}
targetType := typ.Elem()
for err != nil {
if reflect.TypeOf(err).AssignableTo(targetType) {
val.Elem().Set(reflect.ValueOf(err))
return true
}
if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
return true
}
err = Unwrap(err)
}
return false
}
var errorType = reflect.TypeOf((*error)(nil)).Elem()

14
vendor/modules.txt vendored
View File

@@ -285,14 +285,18 @@ golang.org/x/text/runes
golang.org/x/text/internal/language
golang.org/x/text/internal/language/compact
golang.org/x/text/internal/tag
# golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898
golang.org/x/xerrors
golang.org/x/xerrors/internal
# gopkg.in/fsnotify.v1 v1.4.7
gopkg.in/fsnotify.v1
# gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
gopkg.in/tomb.v1
# gopkg.in/yaml.v2 v2.2.5
gopkg.in/yaml.v2
# mvdan.cc/sh v2.6.4+incompatible
mvdan.cc/sh/expand
mvdan.cc/sh/shell
mvdan.cc/sh/syntax
mvdan.cc/sh/interp
# mvdan.cc/sh/v3 v3.0.0-beta1
mvdan.cc/sh/v3/expand
mvdan.cc/sh/v3/shell
mvdan.cc/sh/v3/syntax
mvdan.cc/sh/v3/pattern
mvdan.cc/sh/v3/interp

View File

@@ -1,24 +0,0 @@
// Copyright (c) 2018, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package expand
import "mvdan.cc/sh/syntax"
// Braces performs Bash brace expansion on words. For example, passing it a
// literal word "foo{bar,baz}" will return two literal words, "foobar" and
// "foobaz".
//
// It does not return an error; malformed brace expansions are simply skipped.
// For example, "a{b{c,d}" results in the words "a{bc" and "a{bd".
//
// Note that the resulting words may have more word parts than necessary, such
// as contiguous *syntax.Lit nodes, and that these parts may be shared between
// words.
func Braces(words ...*syntax.Word) []*syntax.Word {
var res []*syntax.Word
for _, word := range words {
res = append(res, syntax.ExpandBraces(word)...)
}
return res
}

View File

@@ -1,164 +0,0 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package interp
import (
"context"
"fmt"
"io"
"os"
"os/exec"
"runtime"
"strings"
"syscall"
"time"
"mvdan.cc/sh/expand"
)
// FromModuleContext returns the ModuleCtx value stored in ctx, if any.
func FromModuleContext(ctx context.Context) (ModuleCtx, bool) {
mc, ok := ctx.Value(moduleCtxKey{}).(ModuleCtx)
return mc, ok
}
type moduleCtxKey struct{}
// ModuleCtx is the data passed to all the module functions via a context value.
// It contains some of the current state of the Runner, as well as some fields
// necessary to implement some of the modules.
type ModuleCtx struct {
Env expand.Environ
Dir string
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
KillTimeout time.Duration
}
// UnixPath fixes absolute unix paths on Windows, for example converting
// "C:\\CurDir\\dev\\null" to "/dev/null".
func (mc ModuleCtx) UnixPath(path string) string {
if runtime.GOOS != "windows" {
return path
}
path = strings.TrimPrefix(path, mc.Dir)
return strings.Replace(path, `\`, `/`, -1)
}
// ModuleExec is the module responsible for executing a program. It is
// executed for all CallExpr nodes where the first argument is neither a
// declared function nor a builtin.
//
// Note that the name is included as the first argument. If path is an
// empty string, it means that the executable did not exist or was not
// found in $PATH.
//
// Use a return error of type ExitStatus to set the exit status. A nil error has
// the same effect as ExitStatus(0). If the error is of any other type, the
// interpreter will come to a stop.
type ModuleExec func(ctx context.Context, path string, args []string) error
func (ModuleExec) isModule() {}
var DefaultExec = ModuleExec(func(ctx context.Context, path string, args []string) error {
mc, _ := FromModuleContext(ctx)
if path == "" {
fmt.Fprintf(mc.Stderr, "%q: executable file not found in $PATH\n", args[0])
return ExitStatus(127)
}
cmd := exec.Cmd{
Path: path,
Args: args,
Env: execEnv(mc.Env),
Dir: mc.Dir,
Stdin: mc.Stdin,
Stdout: mc.Stdout,
Stderr: mc.Stderr,
}
err := cmd.Start()
if err == nil {
if done := ctx.Done(); done != nil {
go func() {
<-done
if mc.KillTimeout <= 0 || runtime.GOOS == "windows" {
_ = cmd.Process.Signal(os.Kill)
return
}
// TODO: don't temporarily leak this goroutine
// if the program stops itself with the
// interrupt.
go func() {
time.Sleep(mc.KillTimeout)
_ = cmd.Process.Signal(os.Kill)
}()
_ = cmd.Process.Signal(os.Interrupt)
}()
}
err = cmd.Wait()
}
switch x := err.(type) {
case *exec.ExitError:
// started, but errored - default to 1 if OS
// doesn't have exit statuses
if status, ok := x.Sys().(syscall.WaitStatus); ok {
if status.Signaled() && ctx.Err() != nil {
return ctx.Err()
}
return ExitStatus(status.ExitStatus())
}
return ExitStatus(1)
case *exec.Error:
// did not start
fmt.Fprintf(mc.Stderr, "%v\n", err)
return ExitStatus(127)
default:
return err
}
})
// ModuleOpen is the module responsible for opening a file. It is
// executed for all files that are opened directly by the shell, such as
// in redirects. Files opened by executed programs are not included.
//
// The path parameter is absolute and has been cleaned.
//
// Use a return error of type *os.PathError to have the error printed to
// stderr and the exit status set to 1. If the error is of any other type, the
// interpreter will come to a stop.
//
// TODO: What about stat calls? They are used heavily in the builtin
// test expressions, and also when doing a cd. Should they have a
// separate module?
type ModuleOpen func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error)
func (ModuleOpen) isModule() {}
var DefaultOpen = ModuleOpen(func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
return os.OpenFile(path, flag, perm)
})
func OpenDevImpls(next ModuleOpen) ModuleOpen {
return func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
mc, _ := FromModuleContext(ctx)
switch mc.UnixPath(path) {
case "/dev/null":
return devNull{}, nil
}
return next(ctx, path, flag, perm)
}
}
var _ io.ReadWriteCloser = devNull{}
type devNull struct{}
func (devNull) Read(p []byte) (int, error) { return 0, io.EOF }
func (devNull) Write(p []byte) (int, error) { return len(p), nil }
func (devNull) Close() error { return nil }

View File

@@ -1,282 +0,0 @@
// Copyright (c) 2018, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
import "strconv"
// TODO(v3): Consider making these special syntax nodes.
// Among other things, we can make use of Word.Lit.
type brace struct {
seq bool // {x..y[..incr]} instead of {x,y[,...]}
chars bool // sequence is of chars, not numbers
elems []*braceWord
}
// braceWord is like Word, but with braceWordPart.
type braceWord struct {
parts []braceWordPart
}
// braceWordPart contains any WordPart or a brace.
type braceWordPart interface{}
var (
litLeftBrace = &Lit{Value: "{"}
litComma = &Lit{Value: ","}
litDots = &Lit{Value: ".."}
litRightBrace = &Lit{Value: "}"}
)
func splitBraces(word *Word) (*braceWord, bool) {
any := false
top := &braceWord{}
acc := top
var cur *brace
open := []*brace{}
pop := func() *brace {
old := cur
open = open[:len(open)-1]
if len(open) == 0 {
cur = nil
acc = top
} else {
cur = open[len(open)-1]
acc = cur.elems[len(cur.elems)-1]
}
return old
}
addLit := func(lit *Lit) {
acc.parts = append(acc.parts, lit)
}
addParts := func(parts ...braceWordPart) {
acc.parts = append(acc.parts, parts...)
}
for _, wp := range word.Parts {
lit, ok := wp.(*Lit)
if !ok {
addParts(wp)
continue
}
last := 0
for j := 0; j < len(lit.Value); j++ {
addlitidx := func() {
if last == j {
return // empty lit
}
l2 := *lit
l2.Value = l2.Value[last:j]
addLit(&l2)
}
switch lit.Value[j] {
case '{':
addlitidx()
acc = &braceWord{}
cur = &brace{elems: []*braceWord{acc}}
open = append(open, cur)
case ',':
if cur == nil {
continue
}
addlitidx()
acc = &braceWord{}
cur.elems = append(cur.elems, acc)
case '.':
if cur == nil {
continue
}
if j+1 >= len(lit.Value) || lit.Value[j+1] != '.' {
continue
}
addlitidx()
cur.seq = true
acc = &braceWord{}
cur.elems = append(cur.elems, acc)
j++
case '}':
if cur == nil {
continue
}
any = true
addlitidx()
br := pop()
if len(br.elems) == 1 {
// return {x} to a non-brace
addLit(litLeftBrace)
addParts(br.elems[0].parts...)
addLit(litRightBrace)
break
}
if !br.seq {
addParts(br)
break
}
var chars [2]bool
broken := false
for i, elem := range br.elems[:2] {
val := braceWordLit(elem)
if _, err := strconv.Atoi(val); err == nil {
} else if len(val) == 1 &&
'a' <= val[0] && val[0] <= 'z' {
chars[i] = true
} else {
broken = true
}
}
if len(br.elems) == 3 {
// increment must be a number
val := braceWordLit(br.elems[2])
if _, err := strconv.Atoi(val); err != nil {
broken = true
}
}
// are start and end both chars or
// non-chars?
if chars[0] != chars[1] {
broken = true
}
if !broken {
br.chars = chars[0]
addParts(br)
break
}
// return broken {x..y[..incr]} to a non-brace
addLit(litLeftBrace)
for i, elem := range br.elems {
if i > 0 {
addLit(litDots)
}
addParts(elem.parts...)
}
addLit(litRightBrace)
default:
continue
}
last = j + 1
}
if last == 0 {
addLit(lit)
} else {
left := *lit
left.Value = left.Value[last:]
addLit(&left)
}
}
// open braces that were never closed fall back to non-braces
for acc != top {
br := pop()
addLit(litLeftBrace)
for i, elem := range br.elems {
if i > 0 {
if br.seq {
addLit(litDots)
} else {
addLit(litComma)
}
}
addParts(elem.parts...)
}
}
return top, any
}
func braceWordLit(v interface{}) string {
word, _ := v.(*braceWord)
if word == nil || len(word.parts) != 1 {
return ""
}
lit, ok := word.parts[0].(*Lit)
if !ok {
return ""
}
return lit.Value
}
func expandRec(bw *braceWord) []*Word {
var all []*Word
var left []WordPart
for i, wp := range bw.parts {
br, ok := wp.(*brace)
if !ok {
left = append(left, wp.(WordPart))
continue
}
if br.seq {
var from, to int
if br.chars {
from = int(braceWordLit(br.elems[0])[0])
to = int(braceWordLit(br.elems[1])[0])
} else {
from, _ = strconv.Atoi(braceWordLit(br.elems[0]))
to, _ = strconv.Atoi(braceWordLit(br.elems[1]))
}
upward := from <= to
incr := 1
if !upward {
incr = -1
}
if len(br.elems) > 2 {
val := braceWordLit(br.elems[2])
n, _ := strconv.Atoi(val)
if n != 0 && n > 0 == upward {
incr = n
}
}
n := from
for {
if upward && n > to {
break
}
if !upward && n < to {
break
}
next := *bw
next.parts = next.parts[i+1:]
lit := &Lit{}
if br.chars {
lit.Value = string(n)
} else {
lit.Value = strconv.Itoa(n)
}
next.parts = append([]braceWordPart{lit}, next.parts...)
exp := expandRec(&next)
for _, w := range exp {
w.Parts = append(left, w.Parts...)
}
all = append(all, exp...)
n += incr
}
return all
}
for _, elem := range br.elems {
next := *bw
next.parts = next.parts[i+1:]
next.parts = append(elem.parts, next.parts...)
exp := expandRec(&next)
for _, w := range exp {
w.Parts = append(left, w.Parts...)
}
all = append(all, exp...)
}
return all
}
return []*Word{{Parts: left}}
}
// TODO(v3): remove
// ExpandBraces performs Bash brace expansion on a word. For example,
// passing it a single-literal word "foo{bar,baz}" will return two
// single-literal words, "foobar" and "foobaz".
//
// Deprecated: use mvdan.cc/sh/expand.Braces instead.
func ExpandBraces(word *Word) []*Word {
topBrace, any := splitBraces(word)
if !any {
return []*Word{word}
}
return expandRec(topBrace)
}

View File

@@ -1,173 +0,0 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
import (
"bytes"
"fmt"
"regexp"
"strings"
)
func charClass(s string) (string, error) {
if strings.HasPrefix(s, "[[.") || strings.HasPrefix(s, "[[=") {
return "", fmt.Errorf("collating features not available")
}
if !strings.HasPrefix(s, "[[:") {
return "", nil
}
name := s[3:]
end := strings.Index(name, ":]]")
if end < 0 {
return "", fmt.Errorf("[[: was not matched with a closing :]]")
}
name = name[:end]
switch name {
case "alnum", "alpha", "ascii", "blank", "cntrl", "digit", "graph",
"lower", "print", "punct", "space", "upper", "word", "xdigit":
default:
return "", fmt.Errorf("invalid character class: %q", name)
}
return s[:len(name)+6], nil
}
// TranslatePattern turns a shell wildcard pattern into a regular expression
// that can be used with regexp.Compile. It will return an error if the input
// pattern was incorrect. Otherwise, the returned expression can be passed to
// regexp.MustCompile.
//
// For example, TranslatePattern(`foo*bar?`, true) returns `foo.*bar.`.
//
// Note that this function (and QuotePattern) should not be directly used with
// file paths if Windows is supported, as the path separator on that platform is
// the same character as the escaping character for shell patterns.
func TranslatePattern(pattern string, greedy bool) (string, error) {
any := false
loop:
for _, r := range pattern {
switch r {
// including those that need escaping since they are
// special chars in regexes
case '*', '?', '[', '\\', '.', '+', '(', ')', '|',
']', '{', '}', '^', '$':
any = true
break loop
}
}
if !any { // short-cut without a string copy
return pattern, nil
}
var buf bytes.Buffer
for i := 0; i < len(pattern); i++ {
switch c := pattern[i]; c {
case '*':
buf.WriteString(".*")
if !greedy {
buf.WriteByte('?')
}
case '?':
buf.WriteString(".")
case '\\':
if i++; i >= len(pattern) {
return "", fmt.Errorf(`\ at end of pattern`)
}
buf.WriteString(regexp.QuoteMeta(string(pattern[i])))
case '[':
name, err := charClass(pattern[i:])
if err != nil {
return "", err
}
if name != "" {
buf.WriteString(name)
i += len(name) - 1
break
}
buf.WriteByte(c)
if i++; i >= len(pattern) {
return "", fmt.Errorf("[ was not matched with a closing ]")
}
switch c = pattern[i]; c {
case '!', '^':
buf.WriteByte('^')
i++
c = pattern[i]
}
buf.WriteByte(c)
last := c
rangeStart := byte(0)
for {
if i++; i >= len(pattern) {
return "", fmt.Errorf("[ was not matched with a closing ]")
}
last, c = c, pattern[i]
buf.WriteByte(c)
if c == ']' {
break
}
if rangeStart != 0 && rangeStart > c {
return "", fmt.Errorf("invalid range: %c-%c", rangeStart, c)
}
if c == '-' {
rangeStart = last
} else {
rangeStart = 0
}
}
default:
buf.WriteString(regexp.QuoteMeta(string(c)))
}
}
return buf.String(), nil
}
// HasPattern returns whether a string contains any unescaped wildcard
// characters: '*', '?', or '['. When the function returns false, the given
// pattern can only match at most one string.
//
// For example, HasPattern(`foo\*bar`) returns false, but HasPattern(`foo*bar`)
// returns true.
//
// This can be useful to avoid extra work, like TranslatePattern. Note that this
// function cannot be used to avoid QuotePattern, as backslashes are quoted by
// that function but ignored here.
func HasPattern(pattern string) bool {
for i := 0; i < len(pattern); i++ {
switch pattern[i] {
case '\\':
i++
case '*', '?', '[':
return true
}
}
return false
}
// QuotePattern returns a string that quotes all special characters in the given
// wildcard pattern. The returned string is a pattern that matches the literal
// string.
//
// For example, QuotePattern(`foo*bar?`) returns `foo\*bar\?`.
func QuotePattern(pattern string) string {
any := false
loop:
for _, r := range pattern {
switch r {
case '*', '?', '[', '\\':
any = true
break loop
}
}
if !any { // short-cut without a string copy
return pattern
}
var buf bytes.Buffer
for _, r := range pattern {
switch r {
case '*', '?', '[', '\\':
buf.WriteByte('\\')
}
buf.WriteRune(r)
}
return buf.String()
}

View File

@@ -1,16 +0,0 @@
// Code generated by "stringer -type token -linecomment -trimprefix _"; DO NOT EDIT.
package syntax
import "strconv"
const _token_name = "illegalTokEOFNewlLitLitWordLitRedir'\"`&&&||||&$$'$\"${$[$($(([[[(((}])));;;;&;;&;|!++--***==!=<=>=+=-=*=/=%=&=|=^=<<=>>=>>><<><&>&>|<<<<-<<<&>&>><(>(+:+-:-?:?=:=%%%###^^^,,,@///:-e-f-d-c-b-p-S-L-k-g-u-G-O-N-r-w-x-s-t-z-n-o-v-R=~-nt-ot-ef-eq-ne-le-ge-lt-gt?(*(+(@(!("
var _token_index = [...]uint16{0, 10, 13, 17, 20, 27, 35, 36, 37, 38, 39, 41, 43, 44, 46, 47, 49, 51, 53, 55, 57, 60, 61, 63, 64, 66, 67, 68, 69, 71, 72, 74, 76, 79, 81, 82, 84, 86, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, 116, 119, 120, 122, 123, 125, 127, 129, 131, 133, 136, 139, 141, 144, 146, 148, 149, 151, 152, 154, 155, 157, 158, 160, 161, 163, 164, 166, 167, 169, 170, 172, 173, 174, 176, 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 227, 230, 233, 236, 239, 242, 245, 248, 251, 254, 256, 258, 260, 262, 264}
func (i token) String() string {
if i >= token(len(_token_index)-1) {
return "token(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _token_name[_token_index[i]:_token_index[i+1]]
}

View File

@@ -1,346 +0,0 @@
// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
//go:generate stringer -type token -linecomment -trimprefix _
type token uint32
// The list of all possible tokens.
const (
illegalTok token = iota
_EOF
_Newl
_Lit
_LitWord
_LitRedir
sglQuote // '
dblQuote // "
bckQuote // `
and // &
andAnd // &&
orOr // ||
or // |
orAnd // |&
dollar // $
dollSglQuote // $'
dollDblQuote // $"
dollBrace // ${
dollBrack // $[
dollParen // $(
dollDblParen // $((
leftBrack // [
dblLeftBrack // [[
leftParen // (
dblLeftParen // ((
rightBrace // }
rightBrack // ]
rightParen // )
dblRightParen // ))
semicolon // ;
dblSemicolon // ;;
semiAnd // ;&
dblSemiAnd // ;;&
semiOr // ;|
exclMark // !
addAdd // ++
subSub // --
star // *
power // **
equal // ==
nequal // !=
lequal // <=
gequal // >=
addAssgn // +=
subAssgn // -=
mulAssgn // *=
quoAssgn // /=
remAssgn // %=
andAssgn // &=
orAssgn // |=
xorAssgn // ^=
shlAssgn // <<=
shrAssgn // >>=
rdrOut // >
appOut // >>
rdrIn // <
rdrInOut // <>
dplIn // <&
dplOut // >&
clbOut // >|
hdoc // <<
dashHdoc // <<-
wordHdoc // <<<
rdrAll // &>
appAll // &>>
cmdIn // <(
cmdOut // >(
plus // +
colPlus // :+
minus // -
colMinus // :-
quest // ?
colQuest // :?
assgn // =
colAssgn // :=
perc // %
dblPerc // %%
hash // #
dblHash // ##
caret // ^
dblCaret // ^^
comma // ,
dblComma // ,,
at // @
slash // /
dblSlash // //
colon // :
tsExists // -e
tsRegFile // -f
tsDirect // -d
tsCharSp // -c
tsBlckSp // -b
tsNmPipe // -p
tsSocket // -S
tsSmbLink // -L
tsSticky // -k
tsGIDSet // -g
tsUIDSet // -u
tsGrpOwn // -G
tsUsrOwn // -O
tsModif // -N
tsRead // -r
tsWrite // -w
tsExec // -x
tsNoEmpty // -s
tsFdTerm // -t
tsEmpStr // -z
tsNempStr // -n
tsOptSet // -o
tsVarSet // -v
tsRefVar // -R
tsReMatch // =~
tsNewer // -nt
tsOlder // -ot
tsDevIno // -ef
tsEql // -eq
tsNeq // -ne
tsLeq // -le
tsGeq // -ge
tsLss // -lt
tsGtr // -gt
globQuest // ?(
globStar // *(
globPlus // +(
globAt // @(
globExcl // !(
)
type RedirOperator token
const (
RdrOut = RedirOperator(rdrOut) + iota
AppOut
RdrIn
RdrInOut
DplIn
DplOut
ClbOut
Hdoc
DashHdoc
WordHdoc
RdrAll
AppAll
)
type ProcOperator token
const (
CmdIn = ProcOperator(cmdIn) + iota
CmdOut
)
type GlobOperator token
const (
GlobQuest = GlobOperator(globQuest) + iota
GlobStar
GlobPlus
GlobAt
GlobExcl
)
type BinCmdOperator token
const (
AndStmt = BinCmdOperator(andAnd) + iota
OrStmt
Pipe
PipeAll
)
type CaseOperator token
const (
Break = CaseOperator(dblSemicolon) + iota
Fallthrough
Resume
ResumeKorn
)
type ParNamesOperator token
const (
NamesPrefix = ParNamesOperator(star)
NamesPrefixWords = ParNamesOperator(at)
)
type ParExpOperator token
const (
SubstPlus = ParExpOperator(plus) + iota
SubstColPlus
SubstMinus
SubstColMinus
SubstQuest
SubstColQuest
SubstAssgn
SubstColAssgn
RemSmallSuffix
RemLargeSuffix
RemSmallPrefix
RemLargePrefix
UpperFirst
UpperAll
LowerFirst
LowerAll
OtherParamOps
)
type UnAritOperator token
const (
Not = UnAritOperator(exclMark) + iota
Inc
Dec
Plus = UnAritOperator(plus)
Minus = UnAritOperator(minus)
)
type BinAritOperator token
const (
Add = BinAritOperator(plus)
Sub = BinAritOperator(minus)
Mul = BinAritOperator(star)
Quo = BinAritOperator(slash)
Rem = BinAritOperator(perc)
Pow = BinAritOperator(power)
Eql = BinAritOperator(equal)
Gtr = BinAritOperator(rdrOut)
Lss = BinAritOperator(rdrIn)
Neq = BinAritOperator(nequal)
Leq = BinAritOperator(lequal)
Geq = BinAritOperator(gequal)
And = BinAritOperator(and)
Or = BinAritOperator(or)
Xor = BinAritOperator(caret)
Shr = BinAritOperator(appOut)
Shl = BinAritOperator(hdoc)
AndArit = BinAritOperator(andAnd)
OrArit = BinAritOperator(orOr)
Comma = BinAritOperator(comma)
Quest = BinAritOperator(quest)
Colon = BinAritOperator(colon)
Assgn = BinAritOperator(assgn)
AddAssgn = BinAritOperator(addAssgn)
SubAssgn = BinAritOperator(subAssgn)
MulAssgn = BinAritOperator(mulAssgn)
QuoAssgn = BinAritOperator(quoAssgn)
RemAssgn = BinAritOperator(remAssgn)
AndAssgn = BinAritOperator(andAssgn)
OrAssgn = BinAritOperator(orAssgn)
XorAssgn = BinAritOperator(xorAssgn)
ShlAssgn = BinAritOperator(shlAssgn)
ShrAssgn = BinAritOperator(shrAssgn)
)
type UnTestOperator token
const (
TsExists = UnTestOperator(tsExists) + iota
TsRegFile
TsDirect
TsCharSp
TsBlckSp
TsNmPipe
TsSocket
TsSmbLink
TsSticky
TsGIDSet
TsUIDSet
TsGrpOwn
TsUsrOwn
TsModif
TsRead
TsWrite
TsExec
TsNoEmpty
TsFdTerm
TsEmpStr
TsNempStr
TsOptSet
TsVarSet
TsRefVar
TsNot = UnTestOperator(exclMark)
)
type BinTestOperator token
const (
TsReMatch = BinTestOperator(tsReMatch) + iota
TsNewer
TsOlder
TsDevIno
TsEql
TsNeq
TsLeq
TsGeq
TsLss
TsGtr
AndTest = BinTestOperator(andAnd)
OrTest = BinTestOperator(orOr)
TsMatch = BinTestOperator(equal)
TsNoMatch = BinTestOperator(nequal)
TsBefore = BinTestOperator(rdrIn)
TsAfter = BinTestOperator(rdrOut)
)
func (o RedirOperator) String() string { return token(o).String() }
func (o ProcOperator) String() string { return token(o).String() }
func (o GlobOperator) String() string { return token(o).String() }
func (o BinCmdOperator) String() string { return token(o).String() }
func (o CaseOperator) String() string { return token(o).String() }
func (o ParNamesOperator) String() string { return token(o).String() }
func (o ParExpOperator) String() string { return token(o).String() }
func (o UnAritOperator) String() string { return token(o).String() }
func (o BinAritOperator) String() string { return token(o).String() }
func (o UnTestOperator) String() string { return token(o).String() }
func (o BinTestOperator) String() string { return token(o).String() }

View File

@@ -7,7 +7,7 @@ import (
"fmt"
"strconv"
"mvdan.cc/sh/syntax"
"mvdan.cc/sh/v3/syntax"
)
func Arithm(cfg *Config, expr syntax.ArithmExpr) (int, error) {
@@ -19,7 +19,7 @@ func Arithm(cfg *Config, expr syntax.ArithmExpr) (int, error) {
}
// recursively fetch vars
i := 0
for str != "" && syntax.ValidName(str) {
for syntax.ValidName(str) {
val := cfg.envGet(str)
if val == "" {
break
@@ -44,7 +44,9 @@ func Arithm(cfg *Config, expr syntax.ArithmExpr) (int, error) {
} else {
val--
}
cfg.envSet(name, strconv.Itoa(val))
if err := cfg.envSet(name, strconv.Itoa(val)); err != nil {
return 0, err
}
if x.Post {
return old, nil
}
@@ -57,6 +59,8 @@ func Arithm(cfg *Config, expr syntax.ArithmExpr) (int, error) {
switch x.Op {
case syntax.Not:
return oneIf(val == 0), nil
case syntax.BitNegation:
return ^val, nil
case syntax.Plus:
return val, nil
default: // syntax.Minus
@@ -69,12 +73,12 @@ func Arithm(cfg *Config, expr syntax.ArithmExpr) (int, error) {
syntax.AndAssgn, syntax.OrAssgn, syntax.XorAssgn,
syntax.ShlAssgn, syntax.ShrAssgn:
return cfg.assgnArit(x)
case syntax.Quest: // Colon can't happen here
case syntax.TernQuest: // TernColon can't happen here
cond, err := Arithm(cfg, x.X)
if err != nil {
return 0, err
}
b2 := x.Y.(*syntax.BinaryArithm) // must have Op==Colon
b2 := x.Y.(*syntax.BinaryArithm) // must have Op==TernColon
if cond == 1 {
return Arithm(cfg, b2.X)
}
@@ -139,7 +143,9 @@ func (cfg *Config) assgnArit(b *syntax.BinaryArithm) (int, error) {
case syntax.ShrAssgn:
val >>= uint(arg)
}
cfg.envSet(name, strconv.Itoa(val))
if err := cfg.envSet(name, strconv.Itoa(val)); err != nil {
return 0, err
}
return val, nil
}

85
vendor/mvdan.cc/sh/v3/expand/braces.go vendored Normal file
View File

@@ -0,0 +1,85 @@
// Copyright (c) 2018, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package expand
import (
"strconv"
"mvdan.cc/sh/v3/syntax"
)
// Braces performs brace expansion on a word, given that it contains any
// syntax.BraceExp parts. For example, the word with a brace expansion
// "foo{bar,baz}" will return two literal words, "foobar" and "foobaz".
//
// Note that the resulting words may share word parts.
func Braces(word *syntax.Word) []*syntax.Word {
var all []*syntax.Word
var left []syntax.WordPart
for i, wp := range word.Parts {
br, ok := wp.(*syntax.BraceExp)
if !ok {
left = append(left, wp.(syntax.WordPart))
continue
}
if br.Sequence {
var from, to int
if br.Chars {
from = int(br.Elems[0].Lit()[0])
to = int(br.Elems[1].Lit()[0])
} else {
from, _ = strconv.Atoi(br.Elems[0].Lit())
to, _ = strconv.Atoi(br.Elems[1].Lit())
}
upward := from <= to
incr := 1
if !upward {
incr = -1
}
if len(br.Elems) > 2 {
n, _ := strconv.Atoi(br.Elems[2].Lit())
if n != 0 && n > 0 == upward {
incr = n
}
}
n := from
for {
if upward && n > to {
break
}
if !upward && n < to {
break
}
next := *word
next.Parts = next.Parts[i+1:]
lit := &syntax.Lit{}
if br.Chars {
lit.Value = string(n)
} else {
lit.Value = strconv.Itoa(n)
}
next.Parts = append([]syntax.WordPart{lit}, next.Parts...)
exp := Braces(&next)
for _, w := range exp {
w.Parts = append(left, w.Parts...)
}
all = append(all, exp...)
n += incr
}
return all
}
for _, elem := range br.Elems {
next := *word
next.Parts = next.Parts[i+1:]
next.Parts = append(elem.Parts, next.Parts...)
exp := Braces(&next)
for _, w := range exp {
w.Parts = append(left, w.Parts...)
}
all = append(all, exp...)
}
return all
}
return []*syntax.Word{{Parts: left}}
}

View File

@@ -40,15 +40,27 @@ type WriteEnviron interface {
// attributes correctly. For example, changing an exported variable's
// value does not unexport it, and overwriting a name reference variable
// should modify its target.
Set(name string, vr Variable)
//
// An error may be returned if the operation is invalid, such as if the
// name is empty or if we're trying to overwrite a read-only variable.
Set(name string, vr Variable) error
}
type ValueKind uint8
const (
Unset ValueKind = iota
String
NameRef
Indexed
Associative
)
// Variable describes a shell variable, which can have a number of attributes
// and a value.
//
// A Variable is unset if its Value field is untyped nil, which can be checked
// via Variable.IsSet. The zero value of a Variable is thus a valid unset
// variable.
// A Variable is unset if its Kind field is Unset, which can be checked via
// Variable.IsSet. The zero value of a Variable is thus a valid unset variable.
//
// If a variable is set, its Value field will be a []string if it is an indexed
// array, a map[string]string if it's an associative array, or a string
@@ -57,27 +69,31 @@ type Variable struct {
Local bool
Exported bool
ReadOnly bool
NameRef bool // if true, Value must be string
Value interface{} // string, []string, or map[string]string
Kind ValueKind
Str string // Used when Kind is String or NameRef.
List []string // Used when Kind is Indexed.
Map map[string]string // Used when Kind is Associative.
}
// IsSet returns whether the variable is set. An empty variable is set, but an
// undeclared variable is not.
func (v Variable) IsSet() bool {
return v.Value != nil
return v.Kind != Unset
}
// String returns the variable's value as a string. In general, this only makes
// sense if the variable has a string value or no value at all.
func (v Variable) String() string {
switch x := v.Value.(type) {
case string:
return x
case []string:
if len(x) > 0 {
return x[0]
switch v.Kind {
case String:
return v.Str
case Indexed:
if len(v.List) > 0 {
return v.List[0]
}
case map[string]string:
case Associative:
// nothing to do
}
return ""
@@ -93,10 +109,10 @@ const maxNameRefDepth = 100
func (v Variable) Resolve(env Environ) (string, Variable) {
name := ""
for i := 0; i < maxNameRefDepth; i++ {
if !v.NameRef {
if v.Kind != NameRef {
return name, v
}
name = v.Value.(string)
name = v.Str // keep name for the next iteration
v = env.Get(name)
}
return name, Variable{}
@@ -118,7 +134,7 @@ func (f funcEnviron) Get(name string) Variable {
if value == "" {
return Variable{}
}
return Variable{Exported: true, Value: value}
return Variable{Exported: true, Kind: String, Str: value}
}
func (f funcEnviron) Each(func(name string, vr Variable) bool) {}
@@ -167,15 +183,14 @@ func listEnvironWithUpper(upper bool, pairs ...string) Environ {
return listEnviron(list)
}
// listEnviron is a sorted list of "name=value" strings.
type listEnviron []string
func (l listEnviron) Get(name string) Variable {
// TODO: binary search
prefix := name + "="
for _, pair := range l {
if val := strings.TrimPrefix(pair, prefix); val != pair {
return Variable{Exported: true, Value: val}
}
i := sort.SearchStrings(l, prefix)
if i < len(l) && strings.HasPrefix(l[i], prefix) {
return Variable{Exported: true, Kind: String, Str: strings.TrimPrefix(l[i], prefix)}
}
return Variable{}
}
@@ -184,11 +199,11 @@ func (l listEnviron) Each(fn func(name string, vr Variable) bool) {
for _, pair := range l {
i := strings.IndexByte(pair, '=')
if i < 0 {
// can't happen; see above
// should never happen; see listEnvironWithUpper
panic("expand.listEnviron: did not expect malformed name-value pair: " + pair)
}
name, value := pair[:i], pair[i+1:]
if !fn(name, Variable{Exported: true, Value: value}) {
if !fn(name, Variable{Exported: true, Kind: String, Str: value}) {
return
}
}

View File

@@ -15,7 +15,8 @@ import (
"strconv"
"strings"
"mvdan.cc/sh/syntax"
"mvdan.cc/sh/v3/pattern"
"mvdan.cc/sh/v3/syntax"
)
// A Config specifies details about how shell expansion should be performed. The
@@ -35,14 +36,6 @@ type Config struct {
// variables.
Env Environ
// TODO(mvdan): consider replacing NoGlob==true with ReadDir==nil.
// NoGlob corresponds to the shell option that disables globbing.
NoGlob bool
// GlobStar corresponds to the shell option that allows globbing with
// "**".
GlobStar bool
// CmdSubst expands a command substitution node, writing its standard
// output to the provided io.Writer.
//
@@ -54,6 +47,10 @@ type Config struct {
// Use ioutil.ReadDir to use the filesystem directly.
ReadDir func(string) ([]os.FileInfo, error)
// GlobStar corresponds to the shell option that allows globbing with
// "**".
GlobStar bool
bufferAlloc bytes.Buffer
fieldAlloc [4]fieldPart
fieldsAlloc [4][]fieldPart
@@ -118,13 +115,12 @@ func (cfg *Config) envGet(name string) string {
return cfg.Env.Get(name).String()
}
func (cfg *Config) envSet(name, value string) {
func (cfg *Config) envSet(name, value string) error {
wenv, ok := cfg.Env.(WriteEnviron)
if !ok {
// TODO: we should probably error here
return
return fmt.Errorf("environment is read-only")
}
wenv.Set(name, Variable{Value: value})
return wenv.Set(name, Variable{Kind: String, Str: value})
}
// Literal expands a single shell word. It is similar to Fields, but the result
@@ -178,7 +174,7 @@ func Pattern(cfg *Config, word *syntax.Word) (string, error) {
buf := cfg.strBuilder()
for _, part := range field {
if part.quote > quoteNone {
buf.WriteString(syntax.QuotePattern(part.val))
buf.WriteString(pattern.QuoteMeta(part.val))
} else {
buf.WriteString(part.val)
}
@@ -196,28 +192,81 @@ func Pattern(cfg *Config, word *syntax.Word) (string, error) {
func Format(cfg *Config, format string, args []string) (string, int, error) {
cfg = prepareConfig(cfg)
buf := cfg.strBuilder()
esc := false
var fmts []rune
var fmts []byte
initialArgs := len(args)
for _, c := range format {
switch {
case esc:
esc = false
switch c {
case 'n':
buf.WriteRune('\n')
case 'r':
buf.WriteRune('\r')
case 't':
buf.WriteRune('\t')
case '\\':
buf.WriteRune('\\')
default:
buf.WriteRune('\\')
buf.WriteRune(c)
for i := 0; i < len(format); i++ {
// readDigits reads from 0 to max digits, either octal or
// hexadecimal.
readDigits := func(max int, hex bool) string {
j := 0
for ; j < max; j++ {
c := format[i+j]
if (c >= '0' && c <= '9') ||
(hex && c >= 'a' && c <= 'f') ||
(hex && c >= 'A' && c <= 'F') {
// valid octal or hex char
} else {
break
}
}
digits := format[i : i+j]
i += j - 1 // -1 since the outer loop does i++
return digits
}
c := format[i]
switch {
case c == '\\': // escaped
i++
switch c = format[i]; c {
case 'a': // bell
buf.WriteByte('\a')
case 'b': // backspace
buf.WriteByte('\b')
case 'e', 'E': // escape
buf.WriteByte('\x1b')
case 'f': // form feed
buf.WriteByte('\f')
case 'n': // new line
buf.WriteByte('\n')
case 'r': // carriage return
buf.WriteByte('\r')
case 't': // horizontal tab
buf.WriteByte('\t')
case 'v': // vertical tab
buf.WriteByte('\v')
case '\\', '\'', '"', '?': // just the character
buf.WriteByte(c)
case '0', '1', '2', '3', '4', '5', '6', '7':
digits := readDigits(3, false)
// if digits don't fit in 8 bits, 0xff via strconv
n, _ := strconv.ParseUint(digits, 8, 8)
buf.WriteByte(byte(n))
case 'x', 'u', 'U':
i++
max := 2
if c == 'u' {
max = 4
} else if c == 'U' {
max = 8
}
digits := readDigits(max, true)
if len(digits) > 0 {
// can't error
n, _ := strconv.ParseUint(digits, 16, 32)
if c == 'x' {
// always as a single byte
buf.WriteByte(byte(n))
} else {
buf.WriteRune(rune(n))
}
break
}
fallthrough
default: // no escape sequence
buf.WriteByte('\\')
buf.WriteByte(c)
}
case len(fmts) > 0:
switch c {
case '%':
@@ -264,14 +313,12 @@ func Format(cfg *Config, format string, args []string) (string, int, error) {
default:
return "", 0, fmt.Errorf("invalid format char: %c", c)
}
case c == '\\':
esc = true
case args != nil && c == '%':
// if args == nil, we are not doing format
// arguments
fmts = []rune{c}
fmts = []byte{c}
default:
buf.WriteRune(c)
buf.WriteByte(c)
}
}
if len(fmts) > 0 {
@@ -298,11 +345,11 @@ func (cfg *Config) escapedGlobField(parts []fieldPart) (escaped string, glob boo
buf := cfg.strBuilder()
for _, part := range parts {
if part.quote > quoteNone {
buf.WriteString(syntax.QuotePattern(part.val))
buf.WriteString(pattern.QuoteMeta(part.val))
continue
}
buf.WriteString(part.val)
if syntax.HasPattern(part.val) {
if pattern.HasMeta(part.val) {
glob = true
}
}
@@ -319,34 +366,30 @@ func Fields(cfg *Config, words ...*syntax.Word) ([]string, error) {
cfg = prepareConfig(cfg)
fields := make([]string, 0, len(words))
dir := cfg.envGet("PWD")
for _, expWord := range Braces(words...) {
wfields, err := cfg.wordFields(expWord.Parts)
for _, word := range words {
afterBraces := []*syntax.Word{word}
if syntax.SplitBraces(word) {
afterBraces = Braces(word)
}
for _, word2 := range afterBraces {
wfields, err := cfg.wordFields(word2.Parts)
if err != nil {
return nil, err
}
for _, field := range wfields {
path, doGlob := cfg.escapedGlobField(field)
var matches []string
abs := filepath.IsAbs(path)
if doGlob && !cfg.NoGlob {
base := ""
if !abs {
base = dir
}
matches, err = cfg.glob(base, path)
if doGlob && cfg.ReadDir != nil {
matches, err = cfg.glob(dir, path)
if err != nil {
return nil, err
}
}
if len(matches) == 0 {
fields = append(fields, cfg.fieldJoin(field))
if len(matches) > 0 {
fields = append(fields, matches...)
continue
}
for _, match := range matches {
if !abs {
match = strings.TrimPrefix(match, dir)
}
fields = append(fields, match)
fields = append(fields, cfg.fieldJoin(field))
}
}
}
@@ -385,9 +428,6 @@ func (cfg *Config) wordField(wps []syntax.WordPart, ql quoteLevel) ([]fieldPart,
b := s[i]
if b == '\\' && i+1 < len(s) {
switch s[i+1] {
case '\n': // remove \\\n
i++
continue
case '"', '\\', '$', '`': // special chars
continue
}
@@ -559,14 +599,14 @@ func (cfg *Config) quotedElems(pe *syntax.ParamExp) []string {
return nil
}
if pe.Param.Value == "@" {
return cfg.Env.Get("@").Value.([]string)
return cfg.Env.Get("@").List
}
if nodeLit(pe.Index) != "@" {
return nil
}
val := cfg.Env.Get(pe.Param.Value).Value
if x, ok := val.([]string); ok {
return x
vr := cfg.Env.Get(pe.Param.Value)
if vr.Kind == Indexed {
return vr.List
}
return nil
}
@@ -581,8 +621,26 @@ func (cfg *Config) expandUser(field string) (prefix, rest string) {
name = name[:i]
}
if name == "" {
return cfg.Env.Get("HOME").String(), rest
// Current user; try via "HOME", otherwise fall back to the
// system's appropriate home dir env var. Don't use os/user, as
// that's overkill. We can't use os.UserHomeDir, because we want
// to use cfg.Env, and we always want to check "HOME" first.
if vr := cfg.Env.Get("HOME"); vr.IsSet() {
return vr.String(), rest
}
if runtime.GOOS == "windows" {
if vr := cfg.Env.Get("USERPROFILE"); vr.IsSet() {
return vr.String(), rest
}
}
return "", field
}
// Not the current user; try via "HOME <name>", otherwise fall back to
// os/user. There isn't a way to lookup user home dirs without cgo.
if vr := cfg.Env.Get("HOME " + name); vr.IsSet() {
return vr.String(), rest
}
@@ -594,8 +652,8 @@ func (cfg *Config) expandUser(field string) (prefix, rest string) {
return u.HomeDir, rest
}
func findAllIndex(pattern, name string, n int) [][]int {
expr, err := syntax.TranslatePattern(pattern, true)
func findAllIndex(pat, name string, n int) [][]int {
expr, err := pattern.Regexp(pat, 0)
if err != nil {
return nil
}
@@ -603,16 +661,6 @@ func findAllIndex(pattern, name string, n int) [][]int {
return rx.FindAllStringIndex(name, n)
}
// TODO: use this again to optimize globbing; see
// https://github.com/mvdan/sh/issues/213
func hasGlob(path string) bool {
magicChars := `*?[`
if runtime.GOOS != "windows" {
magicChars = `*?[\`
}
return strings.ContainsAny(path, magicChars)
}
var rxGlobStar = regexp.MustCompile(".*")
// pathJoin2 is a simpler version of filepath.Join without cleaning the result,
@@ -635,10 +683,10 @@ func pathSplit(path string) []string {
return strings.Split(path, string(filepath.Separator))
}
func (cfg *Config) glob(base, pattern string) ([]string, error) {
parts := pathSplit(pattern)
func (cfg *Config) glob(base, pat string) ([]string, error) {
parts := pathSplit(pat)
matches := []string{""}
if filepath.IsAbs(pattern) {
if filepath.IsAbs(pat) {
if parts[0] == "" {
// unix-like
matches[0] = string(filepath.Separator)
@@ -649,20 +697,13 @@ func (cfg *Config) glob(base, pattern string) ([]string, error) {
}
parts = parts[1:]
}
for _, part := range parts {
for i, part := range parts {
wantDir := i < len(parts)-1
switch {
case part == "", part == ".", part == "..":
var newMatches []string
for _, dir := range matches {
// TODO(mvdan): reuse the previous ReadDir call
if cfg.ReadDir == nil {
continue // no globbing
} else if _, err := cfg.ReadDir(filepath.Join(base, dir)); err != nil {
continue // not actually a dir
for i, dir := range matches {
matches[i] = pathJoin2(dir, part)
}
newMatches = append(newMatches, pathJoin2(dir, part))
}
matches = newMatches
continue
case part == "**" && cfg.GlobStar:
for i, match := range matches {
@@ -677,7 +718,7 @@ func (cfg *Config) glob(base, pattern string) ([]string, error) {
var newMatches []string
for _, dir := range latest {
var err error
newMatches, err = cfg.globDir(base, dir, rxGlobStar, newMatches)
newMatches, err = cfg.globDir(base, dir, rxGlobStar, wantDir, newMatches)
if err != nil {
return nil, err
}
@@ -692,7 +733,7 @@ func (cfg *Config) glob(base, pattern string) ([]string, error) {
}
continue
}
expr, err := syntax.TranslatePattern(part, true)
expr, err := pattern.Regexp(part, pattern.Filenames)
if err != nil {
// If any glob part is not a valid pattern, don't glob.
return nil, nil
@@ -700,7 +741,7 @@ func (cfg *Config) glob(base, pattern string) ([]string, error) {
rx := regexp.MustCompile("^" + expr + "$")
var newMatches []string
for _, dir := range matches {
newMatches, err = cfg.globDir(base, dir, rx, newMatches)
newMatches, err = cfg.globDir(base, dir, rx, wantDir, newMatches)
if err != nil {
return nil, err
}
@@ -710,24 +751,30 @@ func (cfg *Config) glob(base, pattern string) ([]string, error) {
return matches, nil
}
func (cfg *Config) globDir(base, dir string, rx *regexp.Regexp, matches []string) ([]string, error) {
if cfg.ReadDir == nil {
// TODO(mvdan): check this at the beginning of a glob?
return nil, nil
func (cfg *Config) globDir(base, dir string, rx *regexp.Regexp, wantDir bool, matches []string) ([]string, error) {
fullDir := dir
if !filepath.IsAbs(dir) {
fullDir = filepath.Join(base, dir)
}
infos, err := cfg.ReadDir(filepath.Join(base, dir))
infos, err := cfg.ReadDir(fullDir)
if err != nil {
// Ignore the error, as this might be a file instead of a
// directory. v3 refactored globbing to only use one ReadDir
// call per directory instead of two, so it knows to skip this
// kind of path at the ReadDir call of its parent.
// Instead of backporting that complex rewrite into v2, just
// work around the edge case here. We might ignore other kinds
// of errors, but at least we don't fail on a correct glob.
return matches, nil
return nil, err
}
for _, info := range infos {
name := info.Name()
if !wantDir {
// no filtering
} else if mode := info.Mode(); mode&os.ModeSymlink != 0 {
// TODO: is there a way to do this without the
// extra syscall?
if _, err := cfg.ReadDir(filepath.Join(fullDir, name)); err != nil {
// symlink pointing to non-directory
continue
}
} else if !mode.IsDir() {
// definitely not a directory
continue
}
if !strings.HasPrefix(rx.String(), `^\.`) && name[0] == '.' {
continue
}
@@ -738,6 +785,7 @@ func (cfg *Config) globDir(base, dir string, rx *regexp.Regexp, matches []string
return matches, nil
}
// ReadFields TODO write doc.
//
// The config specifies shell expansion options; nil behaves the same as an
// empty config.
@@ -791,7 +839,7 @@ func ReadFields(cfg *Config, s string, n int, raw bool) []string {
fpos = fpos[:n]
}
var fields = make([]string, len(fpos))
fields := make([]string, len(fpos))
for i, p := range fpos {
fields[i] = string(runes[p.start:p.end])
}

View File

@@ -12,7 +12,8 @@ import (
"unicode"
"unicode/utf8"
"mvdan.cc/sh/syntax"
"mvdan.cc/sh/v3/pattern"
"mvdan.cc/sh/v3/syntax"
)
func nodeLit(node syntax.Node) string {
@@ -50,7 +51,7 @@ func (cfg *Config) paramExp(pe *syntax.ParamExp) (string, error) {
// This is the only parameter expansion that the environment
// interface cannot satisfy.
line := uint64(cfg.curParam.Pos().Line())
vr.Value = strconv.FormatUint(line, 10)
vr = Variable{Kind: String, Str: strconv.FormatUint(line, 10)}
default:
vr = cfg.Env.Get(name)
}
@@ -74,11 +75,11 @@ func (cfg *Config) paramExp(pe *syntax.ParamExp) (string, error) {
elems := []string{str}
switch nodeLit(index) {
case "@", "*":
switch x := vr.Value.(type) {
case nil:
switch vr.Kind {
case Unset:
elems = nil
case []string:
elems = x
case Indexed:
elems = vr.List
}
}
switch {
@@ -92,21 +93,24 @@ func (cfg *Config) paramExp(pe *syntax.ParamExp) (string, error) {
str = strconv.Itoa(n)
case pe.Excl:
var strs []string
if pe.Names != 0 {
switch {
case pe.Names != 0:
strs = cfg.namesByPrefix(pe.Param.Value)
} else if orig.NameRef {
strs = append(strs, orig.Value.(string))
} else if x, ok := vr.Value.([]string); ok {
for i, e := range x {
case orig.Kind == NameRef:
strs = append(strs, orig.Str)
case vr.Kind == Indexed:
for i, e := range vr.List {
if e != "" {
strs = append(strs, strconv.Itoa(i))
}
}
} else if x, ok := vr.Value.(map[string]string); ok {
for k := range x {
case vr.Kind == Associative:
for k := range vr.Map {
strs = append(strs, k)
}
} else if str != "" {
case !syntax.ValidName(str):
return "", fmt.Errorf("invalid indirect expansion")
default:
vr = cfg.Env.Get(str)
strs = append(strs, vr.String())
}
@@ -156,54 +160,54 @@ func (cfg *Config) paramExp(pe *syntax.ParamExp) (string, error) {
return "", err
}
switch op := pe.Exp.Op; op {
case syntax.SubstColPlus:
case syntax.AlternateUnsetOrNull:
if str == "" {
break
}
fallthrough
case syntax.SubstPlus:
case syntax.AlternateUnset:
if vr.IsSet() {
str = arg
}
case syntax.SubstMinus:
case syntax.DefaultUnset:
if vr.IsSet() {
break
}
fallthrough
case syntax.SubstColMinus:
case syntax.DefaultUnsetOrNull:
if str == "" {
str = arg
}
case syntax.SubstQuest:
case syntax.ErrorUnset:
if vr.IsSet() {
break
}
fallthrough
case syntax.SubstColQuest:
case syntax.ErrorUnsetOrNull:
if str == "" {
return "", UnsetParameterError{
Node: pe,
Message: arg,
}
}
case syntax.SubstAssgn:
case syntax.AssignUnset:
if vr.IsSet() {
break
}
fallthrough
case syntax.SubstColAssgn:
case syntax.AssignUnsetOrNull:
if str == "" {
cfg.envSet(name, arg)
if err := cfg.envSet(name, arg); err != nil {
return "", err
}
str = arg
}
case syntax.RemSmallPrefix, syntax.RemLargePrefix,
syntax.RemSmallSuffix, syntax.RemLargeSuffix:
suffix := op == syntax.RemSmallSuffix ||
op == syntax.RemLargeSuffix
large := op == syntax.RemLargePrefix ||
op == syntax.RemLargeSuffix
suffix := op == syntax.RemSmallSuffix || op == syntax.RemLargeSuffix
small := op == syntax.RemSmallPrefix || op == syntax.RemSmallSuffix
for i, elem := range elems {
elems[i] = removePattern(elem, arg, suffix, large)
elems[i] = removePattern(elem, arg, suffix, small)
}
str = strings.Join(elems, " ")
case syntax.UpperFirst, syntax.UpperAll,
@@ -216,7 +220,7 @@ func (cfg *Config) paramExp(pe *syntax.ParamExp) (string, error) {
all := op == syntax.UpperAll || op == syntax.LowerAll
// empty string means '?'; nothing to do there
expr, err := syntax.TranslatePattern(arg, false)
expr, err := pattern.Regexp(arg, 0)
if err != nil {
return str, nil
}
@@ -258,14 +262,18 @@ func (cfg *Config) paramExp(pe *syntax.ParamExp) (string, error) {
return str, nil
}
func removePattern(str, pattern string, fromEnd, greedy bool) string {
expr, err := syntax.TranslatePattern(pattern, greedy)
func removePattern(str, pat string, fromEnd, shortest bool) string {
var mode pattern.Mode
if shortest {
mode |= pattern.Shortest
}
expr, err := pattern.Regexp(pat, mode)
if err != nil {
return str
}
switch {
case fromEnd && !greedy:
// use .* to get the right-most (shortest) match
case fromEnd && shortest:
// use .* to get the right-most shortest match
expr = ".*(" + expr + ")$"
case fromEnd:
// simple suffix
@@ -274,7 +282,7 @@ func removePattern(str, pattern string, fromEnd, greedy bool) string {
// simple prefix
expr = "^(" + expr + ")"
}
// no need to check error as TranslatePattern returns one
// no need to check error as Translate returns one
rx := regexp.MustCompile(expr)
if loc := rx.FindStringSubmatchIndex(str); loc != nil {
// remove the original pattern (the submatch)
@@ -287,41 +295,37 @@ func (cfg *Config) varInd(vr Variable, idx syntax.ArithmExpr) (string, error) {
if idx == nil {
return vr.String(), nil
}
switch x := vr.Value.(type) {
case string:
switch vr.Kind {
case String:
n, err := Arithm(cfg, idx)
if err != nil {
return "", err
}
if n == 0 {
return x, nil
return vr.Str, nil
}
case []string:
case Indexed:
switch nodeLit(idx) {
case "@":
return strings.Join(x, " "), nil
return strings.Join(vr.List, " "), nil
case "*":
return cfg.ifsJoin(x), nil
return cfg.ifsJoin(vr.List), nil
}
i, err := Arithm(cfg, idx)
if err != nil {
return "", err
}
if len(x) > 0 {
return x[i], nil
if len(vr.List) > 0 {
return vr.List[i], nil
}
case map[string]string:
case Associative:
switch lit := nodeLit(idx); lit {
case "@", "*":
var strs []string
keys := make([]string, 0, len(x))
for k := range x {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
strs = append(strs, x[k])
strs := make([]string, 0, len(vr.Map))
for _, val := range vr.Map {
strs = append(strs, val)
}
sort.Strings(strs)
if lit == "*" {
return cfg.ifsJoin(strs), nil
}
@@ -331,7 +335,7 @@ func (cfg *Config) varInd(vr Variable, idx syntax.ArithmExpr) (string, error) {
if err != nil {
return "", err
}
return x[val], nil
return vr.Map[val], nil
}
return "", nil
}

View File

@@ -13,8 +13,8 @@ import (
"strconv"
"strings"
"mvdan.cc/sh/expand"
"mvdan.cc/sh/syntax"
"mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/syntax"
)
func isBuiltin(name string) bool {
@@ -50,21 +50,21 @@ func (r *Runner) builtinCode(ctx context.Context, pos syntax.Pos, name string, a
case "false":
return 1
case "exit":
r.exitShell = true
switch len(args) {
case 0:
return r.exit
case 1:
if n, err := strconv.Atoi(args[0]); err != nil {
n, err := strconv.Atoi(args[0])
if err != nil {
r.errf("invalid exit status code: %q\n", args[0])
r.exit = 2
} else {
r.exit = n
return 2
}
return n
default:
r.errf("exit cannot take multiple arguments\n")
r.exit = 1
return 1
}
r.setErr(ShellExitStatus(r.exit))
return 0 // the command's exit status does not matter
case "set":
if err := Params(args...)(r); err != nil {
r.errf("set: %v\n", err)
@@ -200,11 +200,8 @@ func (r *Runner) builtinCode(ctx context.Context, pos syntax.Pos, name string, a
if len(args) > 0 {
panic("wait with args not handled yet")
}
switch err := r.bgShells.Wait().(type) {
case nil:
case ExitStatus:
case ShellExitStatus:
default:
err := r.bgShells.Wait()
if _, ok := IsExitStatus(err); err != nil && !ok {
r.setErr(err)
}
case "builtin":
@@ -244,14 +241,14 @@ func (r *Runner) builtinCode(ctx context.Context, pos syntax.Pos, name string, a
r.errf("eval: %v\n", err)
return 1
}
r.stmts(ctx, file.StmtList)
r.stmts(ctx, file.Stmts)
return r.exit
case "source", ".":
if len(args) < 1 {
r.errf("%v: source: need filename\n", pos)
return 2
}
f, err := r.open(ctx, r.relPath(args[0]), os.O_RDONLY, 0, false)
f, err := r.open(ctx, args[0], os.O_RDONLY, 0, false)
if err != nil {
r.errf("source: %v\n", err)
return 1
@@ -267,13 +264,13 @@ func (r *Runner) builtinCode(ctx context.Context, pos syntax.Pos, name string, a
r.Params = args[1:]
oldInSource := r.inSource
r.inSource = true
r.stmts(ctx, file.StmtList)
r.stmts(ctx, file.Stmts)
r.Params = oldParams
r.inSource = oldInSource
if code, ok := r.err.(returnStatus); ok {
r.err = nil
r.exit = int(code)
return int(code)
}
return r.exit
case "[":
@@ -308,8 +305,8 @@ func (r *Runner) builtinCode(ctx context.Context, pos syntax.Pos, name string, a
break
}
r.exec(ctx, args)
r.setErr(ShellExitStatus(r.exit))
return 0
r.exitShell = true
return r.exit
case "command":
show := false
for len(args) > 0 && strings.HasPrefix(args[0], "-") {
@@ -470,7 +467,7 @@ func (r *Runner) builtinCode(ctx context.Context, pos syntax.Pos, name string, a
if i < len(values) {
val = values[i]
}
r.setVar(name, nil, expand.Variable{Value: val})
r.setVar(name, nil, expand.Variable{Kind: expand.String, Str: val})
}
return 0
@@ -584,7 +581,7 @@ func (r *Runner) readLine(raw bool) ([]byte, error) {
for {
var buf [1]byte
n, err := r.Stdin.Read(buf[:])
n, err := r.stdin.Read(buf[:])
if n > 0 {
b := buf[0]
switch {
@@ -612,7 +609,7 @@ func (r *Runner) readLine(raw bool) ([]byte, error) {
}
func (r *Runner) changeDir(path string) int {
path = r.relPath(path)
path = r.absPath(path)
info, err := r.stat(path)
if err != nil || !info.IsDir() {
return 1
@@ -622,11 +619,11 @@ func (r *Runner) changeDir(path string) int {
}
r.Dir = path
r.Vars["OLDPWD"] = r.Vars["PWD"]
r.Vars["PWD"] = expand.Variable{Value: path}
r.Vars["PWD"] = expand.Variable{Kind: expand.String, Str: path}
return 0
}
func (r *Runner) relPath(path string) string {
func (r *Runner) absPath(path string) string {
if !filepath.IsAbs(path) {
path = filepath.Join(r.Dir, path)
}

288
vendor/mvdan.cc/sh/v3/interp/handler.go vendored Normal file
View File

@@ -0,0 +1,288 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package interp
import (
"context"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"syscall"
"time"
"mvdan.cc/sh/v3/expand"
)
// HandlerCtx returns HandlerContext value stored in ctx.
// It panics if ctx has no HandlerContext stored.
func HandlerCtx(ctx context.Context) HandlerContext {
hc, ok := ctx.Value(handlerCtxKey{}).(HandlerContext)
if !ok {
panic("interp.HandlerCtx: no HandlerContext in ctx")
}
return hc
}
type handlerCtxKey struct{}
// HandlerContext is the data passed to all the handler functions via a context value.
// It contains some of the current state of the Runner.
type HandlerContext struct {
// Env is a read-only version of the interpreter's environment,
// including environment variables, global variables, and local function
// variables.
Env expand.Environ
// Dir is the interpreter's current directory.
Dir string
// Stdin is the interpreter's current standard input reader.
Stdin io.Reader
// Stdout is the interpreter's current standard output writer.
Stdout io.Writer
// Stderr is the interpreter's current standard error writer.
Stderr io.Writer
}
// ExecHandlerFunc is a handler which executes simple command. It is
// called for all CallExpr nodes where the first argument is neither a
// declared function nor a builtin.
//
// Returning nil error sets commands exit status to 0. Other exit statuses
// can be set with NewExitStatus. Any other error will halt an interpreter.
type ExecHandlerFunc func(ctx context.Context, args []string) error
// DefaultExecHandler returns an ExecHandlerFunc used by default.
// It finds binaries in PATH and executes them.
// When context is cancelled, interrupt signal is sent to running processes.
// KillTimeout is a duration to wait before sending kill signal.
// A negative value means that a kill signal will be sent immediately.
// On Windows, the kill signal is always sent immediately,
// because Go doesn't currently support sending Interrupt on Windows.
// Runner.New sets killTimeout to 2 seconds by default.
func DefaultExecHandler(killTimeout time.Duration) ExecHandlerFunc {
return func(ctx context.Context, args []string) error {
hc := HandlerCtx(ctx)
path, err := LookPath(hc.Env, args[0])
if err != nil {
fmt.Fprintln(hc.Stderr, err)
return NewExitStatus(127)
}
cmd := exec.Cmd{
Path: path,
Args: args,
Env: execEnv(hc.Env),
Dir: hc.Dir,
Stdin: hc.Stdin,
Stdout: hc.Stdout,
Stderr: hc.Stderr,
}
err = cmd.Start()
if err == nil {
if done := ctx.Done(); done != nil {
go func() {
<-done
if killTimeout <= 0 || runtime.GOOS == "windows" {
_ = cmd.Process.Signal(os.Kill)
return
}
// TODO: don't temporarily leak this goroutine
// if the program stops itself with the
// interrupt.
go func() {
time.Sleep(killTimeout)
_ = cmd.Process.Signal(os.Kill)
}()
_ = cmd.Process.Signal(os.Interrupt)
}()
}
err = cmd.Wait()
}
switch x := err.(type) {
case *exec.ExitError:
// started, but errored - default to 1 if OS
// doesn't have exit statuses
if status, ok := x.Sys().(syscall.WaitStatus); ok {
if status.Signaled() && ctx.Err() != nil {
return ctx.Err()
}
return NewExitStatus(uint8(status.ExitStatus()))
}
return NewExitStatus(1)
case *exec.Error:
// did not start
fmt.Fprintf(hc.Stderr, "%v\n", err)
return NewExitStatus(127)
default:
return err
}
}
}
func checkStat(dir, file string) (string, error) {
if !filepath.IsAbs(file) {
file = filepath.Join(dir, file)
}
info, err := os.Stat(file)
if err != nil {
return "", err
}
m := info.Mode()
if m.IsDir() {
return "", fmt.Errorf("is a directory")
}
if runtime.GOOS != "windows" && m&0111 == 0 {
return "", fmt.Errorf("permission denied")
}
return file, nil
}
func winHasExt(file string) bool {
i := strings.LastIndex(file, ".")
if i < 0 {
return false
}
return strings.LastIndexAny(file, `:\/`) < i
}
func findExecutable(dir, file string, exts []string) (string, error) {
if len(exts) == 0 {
// non-windows
return checkStat(dir, file)
}
if winHasExt(file) {
if file, err := checkStat(dir, file); err == nil {
return file, nil
}
}
for _, e := range exts {
f := file + e
if f, err := checkStat(dir, f); err == nil {
return f, nil
}
}
return "", fmt.Errorf("not found")
}
func driveLetter(c byte) bool {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
}
// splitList is like filepath.SplitList, but always using the unix path
// list separator ':'. On Windows, it also makes sure not to split
// [A-Z]:[/\].
func splitList(path string) []string {
if path == "" {
return []string{""}
}
list := strings.Split(path, ":")
if runtime.GOOS != "windows" {
return list
}
// join "C", "/foo" into "C:/foo"
var fixed []string
for i := 0; i < len(list); i++ {
s := list[i]
switch {
case len(s) != 1, !driveLetter(s[0]):
case i+1 >= len(list):
// last element
case strings.IndexAny(list[i+1], `/\`) != 0:
// next element doesn't start with / or \
default:
fixed = append(fixed, s+":"+list[i+1])
i++
continue
}
fixed = append(fixed, s)
}
return fixed
}
// LookPath is similar to os/exec.LookPath, with the difference that it uses the
// provided environment. env is used to fetch relevant environment variables
// such as PWD and PATH.
//
// If no error is returned, the returned path must be valid.
func LookPath(env expand.Environ, file string) (string, error) {
pathList := splitList(env.Get("PATH").String())
chars := `/`
if runtime.GOOS == "windows" {
chars = `:\/`
// so that "foo" always tries "./foo"
pathList = append([]string{"."}, pathList...)
}
exts := pathExts(env)
dir := env.Get("PWD").String()
if strings.ContainsAny(file, chars) {
return findExecutable(dir, file, exts)
}
for _, elem := range pathList {
var path string
switch elem {
case "", ".":
// otherwise "foo" won't be "./foo"
path = "." + string(filepath.Separator) + file
default:
path = filepath.Join(elem, file)
}
if f, err := findExecutable(dir, path, exts); err == nil {
return f, nil
}
}
return "", fmt.Errorf("%q: executable file not found in $PATH", file)
}
func pathExts(env expand.Environ) []string {
if runtime.GOOS != "windows" {
return nil
}
pathext := env.Get("PATHEXT").String()
if pathext == "" {
return []string{".com", ".exe", ".bat", ".cmd"}
}
var exts []string
for _, e := range strings.Split(strings.ToLower(pathext), `;`) {
if e == "" {
continue
}
if e[0] != '.' {
e = "." + e
}
exts = append(exts, e)
}
return exts
}
// OpenHandlerFunc is a handler which opens files. It is
// called for all files that are opened directly by the shell, such as
// in redirects. Files opened by executed programs are not included.
//
// The path parameter may be relative to the current directory, which can be
// fetched via HandlerCtx.
//
// Use a return error of type *os.PathError to have the error printed to
// stderr and the exit status set to 1. If the error is of any other type, the
// interpreter will come to a stop.
type OpenHandlerFunc func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error)
// DefaultOpenHandler returns an OpenHandlerFunc used by default. It uses os.OpenFile to open files.
func DefaultOpenHandler() OpenHandlerFunc {
return func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
mc := HandlerCtx(ctx)
if !filepath.IsAbs(path) {
path = filepath.Join(mc.Dir, path)
}
return os.OpenFile(path, flag, perm)
}
}

View File

@@ -12,7 +12,8 @@ import (
"golang.org/x/crypto/ssh/terminal"
"mvdan.cc/sh/syntax"
"mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/syntax"
)
// non-empty string is true, empty string is false
@@ -24,17 +25,17 @@ func (r *Runner) bashTest(ctx context.Context, expr syntax.TestExpr, classic boo
return r.bashTest(ctx, x.X, classic)
case *syntax.BinaryTest:
switch x.Op {
case syntax.TsMatch, syntax.TsNoMatch:
case syntax.TsMatchShort, syntax.TsMatch, syntax.TsNoMatch:
str := r.literal(x.X.(*syntax.Word))
yw := x.Y.(*syntax.Word)
if classic { // test, [
lit := r.literal(yw)
if (str == lit) == (x.Op == syntax.TsMatch) {
if (str == lit) == (x.Op != syntax.TsNoMatch) {
return "1"
}
} else { // [[
pattern := r.pattern(yw)
if match(pattern, str) == (x.Op == syntax.TsMatch) {
if match(pattern, str) == (x.Op != syntax.TsNoMatch) {
return "1"
}
}
@@ -132,7 +133,7 @@ func (r *Runner) unTest(ctx context.Context, op syntax.UnTestOperator, x string)
case syntax.TsSocket:
return r.statMode(x, os.ModeSocket)
case syntax.TsSmbLink:
info, err := os.Lstat(r.relPath(x))
info, err := os.Lstat(r.absPath(x))
return err == nil && info.Mode()&os.ModeSymlink != 0
case syntax.TsSticky:
return r.statMode(x, os.ModeSticky)
@@ -140,23 +141,23 @@ func (r *Runner) unTest(ctx context.Context, op syntax.UnTestOperator, x string)
return r.statMode(x, os.ModeSetuid)
case syntax.TsGIDSet:
return r.statMode(x, os.ModeSetgid)
//case syntax.TsGrpOwn:
//case syntax.TsUsrOwn:
//case syntax.TsModif:
// case syntax.TsGrpOwn:
// case syntax.TsUsrOwn:
// case syntax.TsModif:
case syntax.TsRead:
f, err := r.open(ctx, r.relPath(x), os.O_RDONLY, 0, false)
f, err := r.open(ctx, x, os.O_RDONLY, 0, false)
if err == nil {
f.Close()
}
return err == nil
case syntax.TsWrite:
f, err := r.open(ctx, r.relPath(x), os.O_WRONLY, 0, false)
f, err := r.open(ctx, x, os.O_WRONLY, 0, false)
if err == nil {
f.Close()
}
return err == nil
case syntax.TsExec:
_, err := exec.LookPath(r.relPath(x))
_, err := exec.LookPath(r.absPath(x))
return err == nil
case syntax.TsNoEmpty:
info, err := r.stat(x)
@@ -175,7 +176,7 @@ func (r *Runner) unTest(ctx context.Context, op syntax.UnTestOperator, x string)
case syntax.TsVarSet:
return r.lookupVar(x).IsSet()
case syntax.TsRefVar:
return r.lookupVar(x).NameRef
return r.lookupVar(x).Kind == expand.NameRef
case syntax.TsNot:
return x == ""
default:

View File

@@ -6,7 +6,7 @@ package interp
import (
"fmt"
"mvdan.cc/sh/syntax"
"mvdan.cc/sh/v3/syntax"
)
const illegalTok = 0

View File

@@ -9,8 +9,8 @@ import (
"strconv"
"strings"
"mvdan.cc/sh/expand"
"mvdan.cc/sh/syntax"
"mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/syntax"
)
type overlayEnviron struct {
@@ -39,7 +39,7 @@ func (o overlayEnviron) Each(f func(name string, vr expand.Variable) bool) {
}
func execEnv(env expand.Environ) []string {
list := make([]string, 0, 32)
list := make([]string, 0, 64)
env.Each(func(name string, vr expand.Variable) bool {
if vr.Exported {
list = append(list, name+"="+vr.String())
@@ -53,39 +53,41 @@ func (r *Runner) lookupVar(name string) expand.Variable {
if name == "" {
panic("variable name must not be empty")
}
var value interface{}
var vr expand.Variable
switch name {
case "#":
value = strconv.Itoa(len(r.Params))
vr.Kind, vr.Str = expand.String, strconv.Itoa(len(r.Params))
case "@", "*":
value = r.Params
vr.Kind, vr.List = expand.Indexed, r.Params
case "?":
value = strconv.Itoa(r.exit)
vr.Kind, vr.Str = expand.String, strconv.Itoa(r.exit)
case "$":
value = strconv.Itoa(os.Getpid())
vr.Kind, vr.Str = expand.String, strconv.Itoa(os.Getpid())
case "PPID":
value = strconv.Itoa(os.Getppid())
vr.Kind, vr.Str = expand.String, strconv.Itoa(os.Getppid())
case "DIRSTACK":
value = r.dirStack
vr.Kind, vr.List = expand.Indexed, r.dirStack
case "0":
vr.Kind = expand.String
if r.filename != "" {
value = r.filename
vr.Str = r.filename
} else {
value = "gosh"
vr.Str = "gosh"
}
case "1", "2", "3", "4", "5", "6", "7", "8", "9":
vr.Kind = expand.String
i := int(name[0] - '1')
if i < len(r.Params) {
value = r.Params[i]
vr.Str = r.Params[i]
} else {
value = ""
vr.Str = ""
}
}
if value != nil {
return expand.Variable{Value: value}
if vr.IsSet() {
return vr
}
if value, e := r.cmdVars[name]; e {
return expand.Variable{Value: value}
return expand.Variable{Kind: expand.String, Str: value}
}
if vr, e := r.funcVars[name]; e {
vr.Local = true
@@ -105,7 +107,8 @@ func (r *Runner) lookupVar(name string) expand.Variable {
}
if r.opts[optNoUnset] {
r.errf("%s: unbound variable\n", name)
r.setErr(ShellExitStatus(1))
r.exit = 1
r.exitShell = true
}
return expand.Variable{}
}
@@ -130,11 +133,11 @@ func (r *Runner) delVar(name string) {
}
func (r *Runner) setVarString(name, value string) {
r.setVar(name, nil, expand.Variable{Value: value})
r.setVar(name, nil, expand.Variable{Kind: expand.String, Str: value})
}
func (r *Runner) setVarInternal(name string, vr expand.Variable) {
if _, ok := vr.Value.(string); ok {
if vr.Kind == expand.String {
if r.opts[optAllExport] {
vr.Exported = true
}
@@ -161,20 +164,17 @@ func (r *Runner) setVar(name string, index syntax.ArithmExpr, vr expand.Variable
if name2, var2 := cur.Resolve(r.Env); name2 != "" {
name = name2
cur = var2
vr.NameRef = false
cur.NameRef = false
}
_, isIndexArray := cur.Value.([]string)
_, isAssocArray := cur.Value.(map[string]string)
if _, ok := vr.Value.(string); ok && index == nil {
if vr.Kind == expand.String && index == nil {
// When assigning a string to an array, fall back to the
// zero value for the index.
if isIndexArray {
switch cur.Kind {
case expand.Indexed:
index = &syntax.Word{Parts: []syntax.WordPart{
&syntax.Lit{Value: "0"},
}}
} else if isAssocArray {
case expand.Associative:
index = &syntax.Word{Parts: []syntax.WordPart{
&syntax.DblQuoted{},
}}
@@ -187,36 +187,33 @@ func (r *Runner) setVar(name string, index syntax.ArithmExpr, vr expand.Variable
// from the syntax package, we know that value must be a string if index
// is non-nil; nested arrays are forbidden.
valStr := vr.Value.(string)
valStr := vr.Str
// if the existing variable is already an AssocArray, try our best
// to convert the key to a string
if isAssocArray {
amap := cur.Value.(map[string]string)
var list []string
switch cur.Kind {
case expand.String:
list = append(list, cur.Str)
case expand.Indexed:
list = cur.List
case expand.Associative:
// if the existing variable is already an AssocArray, try our
// best to convert the key to a string
w, ok := index.(*syntax.Word)
if !ok {
return
}
k := r.literal(w)
amap[k] = valStr
cur.Value = amap
cur.Map[k] = valStr
r.setVarInternal(name, cur)
return
}
var list []string
switch x := cur.Value.(type) {
case string:
list = append(list, x)
case []string:
list = x
case map[string]string: // done above
}
k := r.arithm(index)
for len(list) < k+1 {
list = append(list, "")
}
list[k] = valStr
cur.Value = list
cur.Kind = expand.Indexed
cur.List = list
r.setVarInternal(name, cur)
}
@@ -239,56 +236,64 @@ func stringIndex(index syntax.ArithmExpr) bool {
return false
}
func (r *Runner) assignVal(as *syntax.Assign, valType string) interface{} {
func (r *Runner) assignVal(as *syntax.Assign, valType string) expand.Variable {
prev := r.lookupVar(as.Name.Value)
if as.Naked {
return prev.Value
return prev
}
if as.Value != nil {
s := r.literal(as.Value)
if !as.Append || !prev.IsSet() {
return s
prev.Kind = expand.String
if valType == "-n" {
prev.Kind = expand.NameRef
}
switch x := prev.Value.(type) {
case string:
return x + s
case []string:
if len(x) == 0 {
x = append(x, "")
prev.Str = s
return prev
}
x[0] += s
return x
case map[string]string:
switch prev.Kind {
case expand.String:
prev.Str += s
case expand.Indexed:
if len(prev.List) == 0 {
prev.List = append(prev.List, "")
}
prev.List[0] += s
case expand.Associative:
// TODO
}
return s
return prev
}
if as.Array == nil {
// don't return nil, as that's an unset variable
return ""
// don't return the zero value, as that's an unset variable
prev.Kind = expand.String
if valType == "-n" {
prev.Kind = expand.NameRef
}
prev.Str = ""
return prev
}
elems := as.Array.Elems
if valType == "" {
if len(elems) == 0 || !stringIndex(elems[0].Index) {
valType = "-a" // indexed
} else {
if len(elems) > 0 && stringIndex(elems[0].Index) {
valType = "-A" // associative
}
}
if valType == "-A" {
// associative array
amap := make(map[string]string, len(elems))
for _, elem := range elems {
k := r.literal(elem.Index.(*syntax.Word))
amap[k] = r.literal(elem.Value)
}
if !as.Append || !prev.IsSet() {
return amap
if !as.Append {
prev.Kind = expand.Associative
prev.Map = amap
return prev
}
// TODO
return amap
return prev
}
// indexed array
maxIndex := len(elems) - 1
indexes := make([]int, len(elems))
for i, elem := range elems {
@@ -306,16 +311,19 @@ func (r *Runner) assignVal(as *syntax.Assign, valType string) interface{} {
for i, elem := range elems {
strs[indexes[i]] = r.literal(elem.Value)
}
if !as.Append || !prev.IsSet() {
return strs
if !as.Append {
prev.Kind = expand.Indexed
prev.List = strs
return prev
}
switch x := prev.Value.(type) {
case string:
return append([]string{x}, strs...)
case []string:
return append(x, strs...)
case map[string]string:
switch prev.Kind {
case expand.String:
prev.Kind = expand.Indexed
prev.List = append([]string{prev.Str}, strs...)
case expand.Indexed:
prev.List = append(prev.List, strs...)
case expand.Associative:
// TODO
}
return strs
return prev
}

295
vendor/mvdan.cc/sh/v3/pattern/pattern.go vendored Normal file
View File

@@ -0,0 +1,295 @@
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
// Package pattern allows working with shell pattern matching notation, also
// known as wildcards or globbing.
//
// For reference, see
// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13.
package pattern
import (
"bytes"
"fmt"
"regexp"
"strconv"
"strings"
)
// TODO: support Mode in the other APIs too
type Mode uint
const (
Shortest Mode = 1 << iota // prefer the shortest match.
Filenames // "*" and "?" don't match slashes; only "**" does
Braces // support "{a,b}" and "{1..4}"
)
var numRange = regexp.MustCompile(`^([+-]?\d+)\.\.([+-]?\d+)}`)
// Regexp turns a shell pattern into a regular expression that can be used with
// regexp.Compile. It will return an error if the input pattern was incorrect.
// Otherwise, the returned expression can be passed to regexp.MustCompile.
//
// For example, Regexp(`foo*bar?`, true) returns `foo.*bar.`.
//
// Note that this function (and QuoteMeta) should not be directly used with file
// paths if Windows is supported, as the path separator on that platform is the
// same character as the escaping character for shell patterns.
func Regexp(pat string, mode Mode) (string, error) {
any := false
noopLoop:
for _, r := range pat {
switch r {
// including those that need escaping since they are
// regular expression metacharacters
case '*', '?', '[', '\\', '.', '+', '(', ')', '|',
']', '{', '}', '^', '$':
any = true
break noopLoop
}
}
if !any { // short-cut without a string copy
return pat, nil
}
closingBraces := []int{}
var buf bytes.Buffer
writeLoop:
for i := 0; i < len(pat); i++ {
switch c := pat[i]; c {
case '*':
if mode&Filenames != 0 {
if i++; i < len(pat) && pat[i] == '*' {
if i++; i < len(pat) && pat[i] == '/' {
buf.WriteString("(.*/|)")
} else {
buf.WriteString(".*")
i--
}
} else {
buf.WriteString("[^/]*")
i--
}
} else {
buf.WriteString(".*")
}
if mode&Shortest != 0 {
buf.WriteByte('?')
}
case '?':
if mode&Filenames != 0 {
buf.WriteString("[^/]")
} else {
buf.WriteByte('.')
}
case '\\':
if i++; i >= len(pat) {
return "", fmt.Errorf(`\ at end of pattern`)
}
buf.WriteString(regexp.QuoteMeta(string(pat[i])))
case '[':
name, err := charClass(pat[i:])
if err != nil {
return "", err
}
if name != "" {
buf.WriteString(name)
i += len(name) - 1
break
}
if mode&Filenames != 0 {
for _, c := range pat[i:] {
if c == ']' {
break
} else if c == '/' {
buf.WriteString("\\[")
continue writeLoop
}
}
}
buf.WriteByte(c)
if i++; i >= len(pat) {
return "", fmt.Errorf("[ was not matched with a closing ]")
}
switch c = pat[i]; c {
case '!', '^':
buf.WriteByte('^')
if i++; i >= len(pat) {
return "", fmt.Errorf("[ was not matched with a closing ]")
}
}
if c = pat[i]; c == ']' {
buf.WriteByte(']')
if i++; i >= len(pat) {
return "", fmt.Errorf("[ was not matched with a closing ]")
}
}
rangeStart := byte(0)
loopBracket:
for ; i < len(pat); i++ {
c = pat[i]
buf.WriteByte(c)
switch c {
case '\\':
if i++; i < len(pat) {
buf.WriteByte(pat[i])
}
continue
case ']':
break loopBracket
}
if rangeStart != 0 && rangeStart > c {
return "", fmt.Errorf("invalid range: %c-%c", rangeStart, c)
}
if c == '-' {
rangeStart = pat[i-1]
} else {
rangeStart = 0
}
}
if i >= len(pat) {
return "", fmt.Errorf("[ was not matched with a closing ]")
}
case '{':
if mode&Braces == 0 {
buf.WriteString(regexp.QuoteMeta(string(c)))
break
}
innerLevel := 1
commas := false
peekBrace:
for j := i + 1; j < len(pat); j++ {
switch c := pat[j]; c {
case '{':
innerLevel++
case ',':
commas = true
case '\\':
j++
case '}':
if innerLevel--; innerLevel > 0 {
continue
}
if !commas {
break peekBrace
}
closingBraces = append(closingBraces, j)
buf.WriteString("(?:")
continue writeLoop
}
}
if match := numRange.FindStringSubmatch(pat[i+1:]); len(match) == 3 {
start, err1 := strconv.Atoi(match[1])
end, err2 := strconv.Atoi(match[2])
if err1 != nil || err2 != nil || start > end {
return "", fmt.Errorf("invalid range: %q", match[0])
}
// TODO: can we do better here?
buf.WriteString("(?:")
for n := start; n <= end; n++ {
if n > start {
buf.WriteByte('|')
}
fmt.Fprintf(&buf, "%d", n)
}
buf.WriteByte(')')
i += len(match[0])
break
}
buf.WriteString(regexp.QuoteMeta(string(c)))
case ',':
if len(closingBraces) == 0 {
buf.WriteString(regexp.QuoteMeta(string(c)))
} else {
buf.WriteByte('|')
}
case '}':
if len(closingBraces) > 0 && closingBraces[len(closingBraces)-1] == i {
buf.WriteByte(')')
closingBraces = closingBraces[:len(closingBraces)-1]
} else {
buf.WriteString(regexp.QuoteMeta(string(c)))
}
default:
if c > 128 {
buf.WriteByte(c)
} else {
buf.WriteString(regexp.QuoteMeta(string(c)))
}
}
}
return buf.String(), nil
}
func charClass(s string) (string, error) {
if strings.HasPrefix(s, "[[.") || strings.HasPrefix(s, "[[=") {
return "", fmt.Errorf("collating features not available")
}
if !strings.HasPrefix(s, "[[:") {
return "", nil
}
name := s[3:]
end := strings.Index(name, ":]]")
if end < 0 {
return "", fmt.Errorf("[[: was not matched with a closing :]]")
}
name = name[:end]
switch name {
case "alnum", "alpha", "ascii", "blank", "cntrl", "digit", "graph",
"lower", "print", "punct", "space", "upper", "word", "xdigit":
default:
return "", fmt.Errorf("invalid character class: %q", name)
}
return s[:len(name)+6], nil
}
// HasMeta returns whether a string contains any unescaped pattern
// metacharacters: '*', '?', or '['. When the function returns false, the given
// pattern can only match at most one string.
//
// For example, HasMeta(`foo\*bar`) returns false, but HasMeta(`foo*bar`)
// returns true.
//
// This can be useful to avoid extra work, like TranslatePattern. Note that this
// function cannot be used to avoid QuotePattern, as backslashes are quoted by
// that function but ignored here.
func HasMeta(pat string) bool {
for i := 0; i < len(pat); i++ {
switch pat[i] {
case '\\':
i++
case '*', '?', '[':
return true
}
}
return false
}
// QuoteMeta returns a string that quotes all pattern metacharacters in the
// given text. The returned string is a pattern that matches the literal text.
//
// For example, QuoteMeta(`foo*bar?`) returns `foo\*bar\?`.
func QuoteMeta(pat string) string {
any := false
loop:
for _, r := range pat {
switch r {
case '*', '?', '[', '\\':
any = true
break loop
}
}
if !any { // short-cut without a string copy
return pat
}
var buf bytes.Buffer
for _, r := range pat {
switch r {
case '*', '?', '[', '\\':
buf.WriteByte('\\')
}
buf.WriteRune(r)
}
return buf.String()
}

View File

@@ -7,8 +7,8 @@ import (
"os"
"strings"
"mvdan.cc/sh/expand"
"mvdan.cc/sh/syntax"
"mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/syntax"
)
// Expand performs shell expansion on s as if it were within double quotes,

View File

@@ -8,9 +8,9 @@ import (
"fmt"
"os"
"mvdan.cc/sh/expand"
"mvdan.cc/sh/interp"
"mvdan.cc/sh/syntax"
"mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax"
)
// SourceFile sources a shell file from disk and returns the variables
@@ -48,6 +48,7 @@ func SourceNode(ctx context.Context, node syntax.Node) (map[string]expand.Variab
// delete the internal shell vars that the user is not
// interested in
delete(r.Vars, "PWD")
delete(r.Vars, "UID")
delete(r.Vars, "HOME")
delete(r.Vars, "PATH")
delete(r.Vars, "IFS")

178
vendor/mvdan.cc/sh/v3/syntax/braces.go vendored Normal file
View File

@@ -0,0 +1,178 @@
// Copyright (c) 2018, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
import "strconv"
var (
litLeftBrace = &Lit{Value: "{"}
litComma = &Lit{Value: ","}
litDots = &Lit{Value: ".."}
litRightBrace = &Lit{Value: "}"}
)
// SplitBraces parses brace expansions within a word's literal parts. If any
// valid brace expansions are found, they are replaced with BraceExp nodes, and
// the function returns true. Otherwise, the word is left untouched and the
// function returns false.
//
// For example, a literal word "foo{bar,baz}" will result in a word containing
// the literal "foo", and a brace expansion with the elements "bar" and "baz".
//
// It does not return an error; malformed brace expansions are simply skipped.
// For example, the literal word "a{b" is left unchanged.
func SplitBraces(word *Word) bool {
any := false
top := &Word{}
acc := top
var cur *BraceExp
open := []*BraceExp{}
pop := func() *BraceExp {
old := cur
open = open[:len(open)-1]
if len(open) == 0 {
cur = nil
acc = top
} else {
cur = open[len(open)-1]
acc = cur.Elems[len(cur.Elems)-1]
}
return old
}
addLit := func(lit *Lit) {
acc.Parts = append(acc.Parts, lit)
}
for _, wp := range word.Parts {
lit, ok := wp.(*Lit)
if !ok {
acc.Parts = append(acc.Parts, wp)
continue
}
last := 0
for j := 0; j < len(lit.Value); j++ {
addlitidx := func() {
if last == j {
return // empty lit
}
l2 := *lit
l2.Value = l2.Value[last:j]
addLit(&l2)
}
switch lit.Value[j] {
case '{':
addlitidx()
acc = &Word{}
cur = &BraceExp{Elems: []*Word{acc}}
open = append(open, cur)
case ',':
if cur == nil {
continue
}
addlitidx()
acc = &Word{}
cur.Elems = append(cur.Elems, acc)
case '.':
if cur == nil {
continue
}
if j+1 >= len(lit.Value) || lit.Value[j+1] != '.' {
continue
}
addlitidx()
cur.Sequence = true
acc = &Word{}
cur.Elems = append(cur.Elems, acc)
j++
case '}':
if cur == nil {
continue
}
any = true
addlitidx()
br := pop()
if len(br.Elems) == 1 {
// return {x} to a non-brace
addLit(litLeftBrace)
acc.Parts = append(acc.Parts, br.Elems[0].Parts...)
addLit(litRightBrace)
break
}
if !br.Sequence {
acc.Parts = append(acc.Parts, br)
break
}
var chars [2]bool
broken := false
for i, elem := range br.Elems[:2] {
val := elem.Lit()
if _, err := strconv.Atoi(val); err == nil {
} else if len(val) == 1 &&
'a' <= val[0] && val[0] <= 'z' {
chars[i] = true
} else {
broken = true
}
}
if len(br.Elems) == 3 {
// increment must be a number
val := br.Elems[2].Lit()
if _, err := strconv.Atoi(val); err != nil {
broken = true
}
}
// are start and end both chars or
// non-chars?
if chars[0] != chars[1] {
broken = true
}
if !broken {
br.Chars = chars[0]
acc.Parts = append(acc.Parts, br)
break
}
// return broken {x..y[..incr]} to a non-brace
addLit(litLeftBrace)
for i, elem := range br.Elems {
if i > 0 {
addLit(litDots)
}
acc.Parts = append(acc.Parts, elem.Parts...)
}
addLit(litRightBrace)
default:
continue
}
last = j + 1
}
if last == 0 {
addLit(lit)
} else {
left := *lit
left.Value = left.Value[last:]
addLit(&left)
}
}
if !any {
return false
}
// open braces that were never closed fall back to non-braces
for acc != top {
br := pop()
addLit(litLeftBrace)
for i, elem := range br.Elems {
if i > 0 {
if br.Sequence {
addLit(litDots)
} else {
addLit(litComma)
}
}
acc.Parts = append(acc.Parts, elem.Parts...)
}
}
*word = *top
return true
}

View File

@@ -2,5 +2,5 @@
// See LICENSE for licensing information
// Package syntax implements parsing and formatting of shell programs.
// It supports both POSIX Shell and Bash.
// It supports POSIX Shell, Bash, and mksh.
package syntax

View File

@@ -40,7 +40,7 @@ func paramNameOp(r rune) bool {
// tokenize these inside arithmetic expansions
func arithmOps(r rune) bool {
switch r {
case '+', '-', '!', '*', '/', '%', '(', ')', '^', '<', '>', ':', '=',
case '+', '-', '!', '~', '*', '/', '%', '(', ')', '^', '<', '>', ':', '=',
',', '?', '|', '&', '[', ']', '#':
return true
}
@@ -55,8 +55,10 @@ func bquoteEscaped(b byte) bool {
return false
}
const escNewl rune = utf8.RuneSelf + 1
func (p *Parser) rune() rune {
if p.r == '\n' {
if p.r == '\n' || p.r == escNewl {
// p.r instead of b so that newline
// character positions don't have col 0.
p.npos.line++
@@ -68,11 +70,14 @@ retry:
if p.bsp < len(p.bs) {
if b := p.bs[p.bsp]; b < utf8.RuneSelf {
p.bsp++
if b == '\\' && p.openBquotes > 0 {
// don't do it for newlines, as we want
// the newlines to be eaten in p.next
if bquotes < p.openBquotes && p.bsp < len(p.bs) &&
bquoteEscaped(p.bs[p.bsp]) {
if b == '\\' {
if p.r != '\\' && p.peekByte('\n') {
p.bsp++
p.w, p.r = 1, escNewl
return escNewl
}
if p.openBquotes > 0 && bquotes < p.openBquotes &&
p.bsp < len(p.bs) && bquoteEscaped(p.bs[p.bsp]) {
bquotes++
goto retry
}
@@ -194,6 +199,9 @@ func (p *Parser) next() {
p.tok = _EOF
return
}
for p.r == escNewl {
p.rune()
}
p.spaced = false
if p.quote&allKeepSpaces != 0 {
p.nextKeepSpaces()
@@ -206,6 +214,8 @@ skipSpace:
case utf8.RuneSelf:
p.tok = _EOF
return
case escNewl:
r = p.rune()
case ' ', '\t', '\r':
p.spaced = true
r = p.rune()
@@ -221,12 +231,6 @@ skipSpace:
p.doHeredocs()
}
return
case '\\':
if !p.peekByte('\n') {
break skipSpace
}
p.rune()
r = p.rune()
default:
break skipSpace
}
@@ -240,7 +244,6 @@ skipSpace:
return
}
}
changedState:
p.pos = p.getPos()
switch {
case p.quote&allRegTokens != 0:
@@ -250,7 +253,10 @@ changedState:
case '#':
r = p.rune()
p.newLit(r)
for r != utf8.RuneSelf && r != '\n' {
for r != '\n' && r != utf8.RuneSelf {
if r == escNewl {
p.litBs = append(p.litBs, '\\', '\n')
}
r = p.rune()
}
if p.keepComments {
@@ -297,7 +303,7 @@ changedState:
case p.quote == testRegexp:
if !p.rxFirstPart && p.spaced {
p.quote = noState
goto changedState
goto skipSpace
}
p.rxFirstPart = false
switch r {
@@ -613,6 +619,9 @@ func (p *Parser) arithmToken(r rune) token {
return equal
}
return assgn
case '~':
p.rune()
return tilde
case '(':
p.rune()
return leftParen
@@ -739,7 +748,7 @@ func (p *Parser) newLit(r rune) {
case r < utf8.RuneSelf:
p.litBs = p.litBuf[:1]
p.litBs[0] = byte(r)
case r > utf8.RuneSelf:
case r > escNewl:
w := utf8.RuneLen(r)
p.litBs = append(p.litBuf[:0], p.bs[p.bsp-w:p.bsp]...)
default:
@@ -749,10 +758,8 @@ func (p *Parser) newLit(r rune) {
}
}
func (p *Parser) discardLit(n int) { p.litBs = p.litBs[:len(p.litBs)-n] }
func (p *Parser) endLit() (s string) {
if p.r == utf8.RuneSelf {
if p.r == utf8.RuneSelf || p.r == escNewl {
s = string(p.litBs)
} else {
s = string(p.litBs[:len(p.litBs)-int(p.w)])
@@ -781,17 +788,11 @@ func (p *Parser) advanceNameCont(r rune) {
loop:
for p.newLit(r); r != utf8.RuneSelf; r = p.rune() {
switch {
case r == '\\':
if p.peekByte('\n') {
p.rune()
p.discardLit(2)
} else {
break loop
}
case 'a' <= r && r <= 'z':
case 'A' <= r && r <= 'Z':
case r == '_':
case '0' <= r && r <= '9':
case r == escNewl:
default:
break loop
}
@@ -805,9 +806,7 @@ loop:
for p.newLit(r); r != utf8.RuneSelf; r = p.rune() {
switch r {
case '\\': // escaped byte follows
if r = p.rune(); r == '\n' {
p.discardLit(2)
}
p.rune()
case '"', '`', '$':
tok = _Lit
break loop
@@ -819,7 +818,7 @@ loop:
if p.quote != paramExpExp {
break loop
}
case ':', '=', '%', '^', ',', '?', '!', '*':
case ':', '=', '%', '^', ',', '?', '!', '~', '*':
if p.quote&allArithmExpr != 0 || p.quote == paramExpName {
break loop
}
@@ -842,7 +841,7 @@ loop:
}
func (p *Parser) advanceLitNone(r rune) {
p.eqlOffs = 0
p.eqlOffs = -1
tok := _LitWord
loop:
for p.newLit(r); r != utf8.RuneSelf; r = p.rune() {
@@ -850,13 +849,11 @@ loop:
case ' ', '\t', '\n', '\r', '&', '|', ';', '(', ')':
break loop
case '\\': // escaped byte follows
if r = p.rune(); r == '\n' {
p.discardLit(2)
}
p.rune()
case '>', '<':
if p.peekByte('(') || !p.isLitRedir() {
if p.peekByte('(') {
tok = _Lit
} else {
} else if p.isLitRedir() {
tok = _LitRedir
}
break loop
@@ -874,7 +871,7 @@ loop:
break loop
}
case '=':
if p.eqlOffs == 0 {
if p.eqlOffs < 0 {
p.eqlOffs = len(p.litBs) - 1
}
case '[':
@@ -896,7 +893,7 @@ loop:
break loop
case '\\': // escaped byte follows
p.rune()
case '`', '$':
case escNewl, '`', '$':
tok = _Lit
break loop
}
@@ -913,12 +910,9 @@ func (p *Parser) advanceLitHdoc(r rune) {
}
}
lStart := len(p.litBs) - 1
if lStart < 0 {
return
}
for ; ; r = p.rune() {
switch r {
case '`', '$':
case escNewl, '`', '$':
p.val = p.endLit()
return
case '\\': // escaped byte follows
@@ -929,7 +923,7 @@ func (p *Parser) advanceLitHdoc(r rune) {
p.val = p.endLit()
return
}
} else if bytes.HasPrefix(p.litBs[lStart:], p.hdocStop) {
} else if lStart >= 0 && bytes.HasPrefix(p.litBs[lStart:], p.hdocStop) {
p.val = p.endLit()[:lStart]
if p.val == "" {
p.tok = _Newl
@@ -964,13 +958,13 @@ func (p *Parser) quotedHdocWord() *Word {
}
}
lStart := len(p.litBs) - 1
if lStart < 0 {
return nil
}
for r != utf8.RuneSelf && r != '\n' {
if r == escNewl {
p.litBs = append(p.litBs, '\\', '\n')
}
r = p.rune()
}
if bytes.HasPrefix(p.litBs[lStart:], p.hdocStop) {
if lStart >= 0 && bytes.HasPrefix(p.litBs[lStart:], p.hdocStop) {
p.hdocStop = nil
val := p.endLit()[:lStart]
if val == "" {
@@ -1070,7 +1064,9 @@ func testUnaryOp(val string) UnTestOperator {
func testBinaryOp(val string) BinTestOperator {
switch val {
case "==", "=":
case "=":
return TsMatchShort
case "==":
return TsMatch
case "!=":
return TsNoMatch

View File

@@ -23,19 +23,16 @@ type Node interface {
type File struct {
Name string
StmtList
}
// StmtList is a list of statements with any number of trailing comments. Both
// lists can be empty.
type StmtList struct {
Stmts []*Stmt
Last []Comment
}
func (s StmtList) pos() Pos {
if len(s.Stmts) > 0 {
s := s.Stmts[0]
func (f *File) Pos() Pos { return stmtsPos(f.Stmts, f.Last) }
func (f *File) End() Pos { return stmtsEnd(f.Stmts, f.Last) }
func stmtsPos(stmts []*Stmt, last []Comment) Pos {
if len(stmts) > 0 {
s := stmts[0]
sPos := s.Pos()
if len(s.Comments) > 0 {
if cPos := s.Comments[0].Pos(); sPos.After(cPos) {
@@ -44,18 +41,18 @@ func (s StmtList) pos() Pos {
}
return sPos
}
if len(s.Last) > 0 {
return s.Last[0].Pos()
if len(last) > 0 {
return last[0].Pos()
}
return Pos{}
}
func (s StmtList) end() Pos {
if len(s.Last) > 0 {
return s.Last[len(s.Last)-1].End()
func stmtsEnd(stmts []*Stmt, last []Comment) Pos {
if len(last) > 0 {
return last[len(last)-1].End()
}
if len(s.Stmts) > 0 {
s := s.Stmts[len(s.Stmts)-1]
if len(stmts) > 0 {
s := stmts[len(stmts)-1]
sEnd := s.End()
if len(s.Comments) > 0 {
if cEnd := s.Comments[0].End(); cEnd.After(sEnd) {
@@ -67,10 +64,6 @@ func (s StmtList) end() Pos {
return Pos{}
}
func (s StmtList) empty() bool {
return len(s.Stmts) == 0 && len(s.Last) == 0
}
// Pos is a position within a shell source file.
type Pos struct {
offs uint32
@@ -100,9 +93,6 @@ func (p Pos) IsValid() bool { return p.line > 0 }
// version of p.Offset() > p2.Offset().
func (p Pos) After(p2 Pos) bool { return p.offs > p2.offs }
func (f *File) Pos() Pos { return f.StmtList.pos() }
func (f *File) End() Pos { return f.StmtList.end() }
func posAddCol(p Pos, n int) Pos {
p.col += uint16(n)
p.offs += uint32(n)
@@ -198,11 +188,12 @@ func (*CoprocClause) commandNode() {}
// arrays are not allowed.
//
// If Naked is true and Name is nil, the assignment is part of a DeclClause and
// the assignment expression (in the Value field) will be evaluated at run-time.
// the argument (in the Value field) will be evaluated at run-time. This
// includes parameter expansions, which may expand to assignments or options.
type Assign struct {
Append bool // +=
Naked bool // without '='
Name *Lit
Name *Lit // must be a valid name
Index ArithmExpr // [i], ["k"]
Value *Word // =val
Array *ArrayExpr // =(arr)
@@ -246,6 +237,7 @@ func (r *Redirect) Pos() Pos {
}
return r.OpPos
}
func (r *Redirect) End() Pos {
if r.Hdoc != nil {
return r.Hdoc.End()
@@ -281,74 +273,54 @@ func (c *CallExpr) End() Pos {
// shell environment.
type Subshell struct {
Lparen, Rparen Pos
StmtList
Stmts []*Stmt
Last []Comment
}
func (s *Subshell) Pos() Pos { return s.Lparen }
func (s *Subshell) End() Pos { return posAddCol(s.Rparen, 1) }
// Block represents a series of commands that should be executed in a nested
// scope.
// scope. It is essentially a list of statements within curly braces.
type Block struct {
Lbrace, Rbrace Pos
StmtList
Stmts []*Stmt
Last []Comment
}
func (b *Block) Pos() Pos { return b.Lbrace }
func (b *Block) End() Pos { return posAddCol(b.Rbrace, 1) }
// TODO(v3): Refactor and simplify elif/else. For example, we could likely make
// Else an *IfClause, remove ElsePos, make IfPos also do opening "else"
// positions, and join the comment slices as Last []Comment.
// IfClause represents an if statement.
type IfClause struct {
Elif bool // whether this IfClause begins with "elif"
IfPos Pos // position of the starting "if" or "elif" token
ThenPos Pos
ElsePos Pos // position of a following "else" or "elif", if any
FiPos Pos // position of "fi", empty if Elif == true
Position Pos // position of the starting "if", "elif", or "else" token
ThenPos Pos // position of "then", empty if this is an "else"
FiPos Pos // position of "fi", shared with .Else if non-nil
Cond StmtList
Then StmtList
Else StmtList
Cond []*Stmt
CondLast []Comment
Then []*Stmt
ThenLast []Comment
ElseComments []Comment // comments on the "else"
FiComments []Comment // comments on the "fi"
Else *IfClause // if non-nil, an "elif" or an "else"
Last []Comment // comments on the first "elif", "else", or "fi"
}
func (c *IfClause) Pos() Pos { return c.IfPos }
func (c *IfClause) End() Pos {
if !c.FiPos.IsValid() {
return posAddCol(c.ElsePos, 4)
}
return posAddCol(c.FiPos, 2)
}
// FollowedByElif reports whether this IfClause is followed by an "elif"
// IfClause in its Else branch. This is true if Else.Stmts has exactly one
// statement with an IfClause whose Elif field is true.
func (c *IfClause) FollowedByElif() bool {
if len(c.Else.Stmts) != 1 {
return false
}
ic, _ := c.Else.Stmts[0].Cmd.(*IfClause)
return ic != nil && ic.Elif
}
func (c *IfClause) bodyEndPos() Pos {
if c.ElsePos.IsValid() {
return c.ElsePos
}
return c.FiPos
}
func (c *IfClause) Pos() Pos { return c.Position }
func (c *IfClause) End() Pos { return posAddCol(c.FiPos, 2) }
// WhileClause represents a while or an until clause.
type WhileClause struct {
WhilePos, DoPos, DonePos Pos
Until bool
Cond StmtList
Do StmtList
Cond []*Stmt
CondLast []Comment
Do []*Stmt
DoLast []Comment
}
func (w *WhileClause) Pos() Pos { return w.WhilePos }
@@ -360,7 +332,9 @@ type ForClause struct {
ForPos, DoPos, DonePos Pos
Select bool
Loop Loop
Do StmtList
Do []*Stmt
DoLast []Comment
}
func (f *ForClause) Pos() Pos { return f.ForPos }
@@ -474,6 +448,7 @@ func (*CmdSubst) wordPartNode() {}
func (*ArithmExp) wordPartNode() {}
func (*ProcSubst) wordPartNode() {}
func (*ExtGlob) wordPartNode() {}
func (*BraceExp) wordPartNode() {}
// Lit represents a string literal.
//
@@ -500,27 +475,22 @@ func (q *SglQuoted) End() Pos { return posAddCol(q.Right, 1) }
// DblQuoted represents a list of nodes within double quotes.
type DblQuoted struct {
Position Pos
Left, Right Pos
Dollar bool // $""
Parts []WordPart
}
func (q *DblQuoted) Pos() Pos { return q.Position }
func (q *DblQuoted) End() Pos {
if len(q.Parts) == 0 {
if q.Dollar {
return posAddCol(q.Position, 3)
}
return posAddCol(q.Position, 2)
}
return posAddCol(q.Parts[len(q.Parts)-1].End(), 1)
}
func (q *DblQuoted) Pos() Pos { return q.Left }
func (q *DblQuoted) End() Pos { return posAddCol(q.Right, 1) }
// CmdSubst represents a command substitution.
type CmdSubst struct {
Left, Right Pos
StmtList
Stmts []*Stmt
Last []Comment
Backquotes bool // deprecated `foo`
TempFile bool // mksh's ${ foo;}
ReplyVar bool // mksh's ${|foo;}
}
@@ -531,6 +501,7 @@ func (c *CmdSubst) End() Pos { return posAddCol(c.Right, 1) }
// ParamExp represents a parameter expansion.
type ParamExp struct {
Dollar, Rbrace Pos
Short bool // $a instead of ${a}
Excl bool // ${!a}
Length bool // ${#a}
@@ -583,6 +554,7 @@ type ArithmExp struct {
Left, Right Pos
Bracket bool // deprecated $[expr] form
Unsigned bool // mksh's $((# expr))
X ArithmExpr
}
@@ -600,6 +572,7 @@ func (a *ArithmExp) End() Pos {
type ArithmCmd struct {
Left, Right Pos
Unsigned bool // mksh's ((# expr))
X ArithmExpr
}
@@ -625,8 +598,8 @@ func (*Word) arithmExprNode() {}
// is a valid name.
//
// Ternary operators like "a ? b : c" are fit into this structure. Thus, if
// Op==Quest, Y will be a *BinaryArithm with Op==Colon. Op can only be Colon in
// that scenario.
// Op==TernQuest, Y will be a *BinaryArithm with Op==TernColon. Op can only be
// TernColon in that scenario.
type BinaryArithm struct {
OpPos Pos
Op BinAritOperator
@@ -665,6 +638,7 @@ func (u *UnaryArithm) End() Pos {
// ParenArithm represents an arithmetic expression within parentheses.
type ParenArithm struct {
Lparen, Rparen Pos
X ArithmExpr
}
@@ -674,6 +648,7 @@ func (p *ParenArithm) End() Pos { return posAddCol(p.Rparen, 1) }
// CaseClause represents a case (switch) clause.
type CaseClause struct {
Case, Esac Pos
Word *Word
Items []*CaseItem
Last []Comment
@@ -688,7 +663,9 @@ type CaseItem struct {
OpPos Pos // unset if it was finished by "esac"
Comments []Comment
Patterns []*Word
StmtList
Stmts []*Stmt
Last []Comment
}
func (c *CaseItem) Pos() Pos { return c.Patterns[0].Pos() }
@@ -696,7 +673,7 @@ func (c *CaseItem) End() Pos {
if c.OpPos.IsValid() {
return posAddCol(c.OpPos, len(c.Op.String()))
}
return c.StmtList.end()
return stmtsEnd(c.Stmts, c.Last)
}
// TestClause represents a Bash extended test clause.
@@ -704,6 +681,7 @@ func (c *CaseItem) End() Pos {
// This node will only appear in LangBash and LangMirBSDKorn.
type TestClause struct {
Left, Right Pos
X TestExpr
}
@@ -747,6 +725,7 @@ func (u *UnaryTest) End() Pos { return u.X.End() }
// ParenTest represents a test expression within parentheses.
type ParenTest struct {
Lparen, Rparen Pos
X TestExpr
}
@@ -755,22 +734,21 @@ func (p *ParenTest) End() Pos { return posAddCol(p.Rparen, 1) }
// DeclClause represents a Bash declare clause.
//
// Args can contain a mix of regular and naked assignments. The naked
// assignments can represent either options or variable names.
//
// This node will only appear with LangBash.
type DeclClause struct {
// Variant is one of "declare", "local", "export", "readonly",
// "typeset", or "nameref".
Variant *Lit
Opts []*Word
Assigns []*Assign
Args []*Assign
}
func (d *DeclClause) Pos() Pos { return d.Variant.Pos() }
func (d *DeclClause) End() Pos {
if len(d.Assigns) > 0 {
return d.Assigns[len(d.Assigns)-1].End()
}
if len(d.Opts) > 0 {
return wordLastEnd(d.Opts)
if len(d.Args) > 0 {
return d.Args[len(d.Args)-1].End()
}
return d.Variant.End()
}
@@ -780,6 +758,7 @@ func (d *DeclClause) End() Pos {
// This node will only appear with LangBash.
type ArrayExpr struct {
Lparen, Rparen Pos
Elems []*ArrayElem
Last []Comment
}
@@ -804,6 +783,7 @@ func (a *ArrayElem) Pos() Pos {
}
return a.Value.Pos()
}
func (a *ArrayElem) End() Pos {
if a.Value != nil {
return a.Value.End()
@@ -830,7 +810,9 @@ func (e *ExtGlob) End() Pos { return posAddCol(e.Pattern.End(), 1) }
type ProcSubst struct {
OpPos, Rparen Pos
Op ProcOperator
StmtList
Stmts []*Stmt
Last []Comment
}
func (s *ProcSubst) Pos() Pos { return s.OpPos }
@@ -859,7 +841,7 @@ func (c *TimeClause) End() Pos {
// This node will only appear with LangBash.
type CoprocClause struct {
Coproc Pos
Name *Lit
Name *Word
Stmt *Stmt
}
@@ -877,6 +859,23 @@ type LetClause struct {
func (l *LetClause) Pos() Pos { return l.Let }
func (l *LetClause) End() Pos { return l.Exprs[len(l.Exprs)-1].End() }
// BraceExp represents a Bash brace expression, such as "{x,y}" or "{1..10}".
//
// This node will only appear as a result of SplitBraces.
type BraceExp struct {
Sequence bool // {x..y[..incr]} instead of {x,y[,...]}
Chars bool // sequence is of chars, not numbers (TODO: remove)
Elems []*Word
}
func (b *BraceExp) Pos() Pos {
return posAddCol(b.Elems[0].Pos(), -1)
}
func (b *BraceExp) End() Pos {
return posAddCol(wordLastEnd(b.Elems), 1)
}
func wordLastEnd(ws []*Word) Pos {
if len(ws) == 0 {
return Pos{}

View File

@@ -12,9 +12,16 @@ import (
"unicode/utf8"
)
// ParserOption is a function which can be passed to NewParser
// to alter its behaviour. To apply option to existing Parser
// call it directly, for example syntax.KeepComments(true)(parser).
type ParserOption func(*Parser)
// KeepComments makes the parser parse comments and attach them to
// nodes, as opposed to discarding them.
func KeepComments(p *Parser) { p.keepComments = true }
func KeepComments(enabled bool) ParserOption {
return func(p *Parser) { p.keepComments = enabled }
}
type LangVariant int
@@ -26,7 +33,7 @@ const (
// Variant changes the shell language variant that the parser will
// accept.
func Variant(l LangVariant) func(*Parser) {
func Variant(l LangVariant) ParserOption {
return func(p *Parser) { p.lang = l }
}
@@ -55,7 +62,7 @@ func (l LangVariant) String() string {
//
// The match is done by prefix, so the example above will also act on
// "foo $$bar".
func StopAt(word string) func(*Parser) {
func StopAt(word string) ParserOption {
if len(word) > 4 {
panic("stop word can't be over four bytes in size")
}
@@ -66,7 +73,7 @@ func StopAt(word string) func(*Parser) {
}
// NewParser allocates a new Parser and applies any number of options.
func NewParser(options ...func(*Parser)) *Parser {
func NewParser(options ...ParserOption) *Parser {
p := &Parser{helperBuf: new(bytes.Buffer)}
for _, opt := range options {
opt(p)
@@ -86,7 +93,7 @@ func (p *Parser) Parse(r io.Reader, name string) (*File, error) {
p.src = r
p.rune()
p.next()
p.f.StmtList = p.stmtList()
p.f.Stmts, p.f.Last = p.stmtList()
if p.err == nil {
// EOF immediately after heredoc word so no newline to
// trigger it
@@ -126,7 +133,7 @@ func (w *wrappedReader) Read(p []byte) (n int, err 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 w.r == '\n' && w.npos.line > w.lastLine {
if (w.r == '\n' || w.r == escNewl) && w.npos.line > w.lastLine {
if w.Incomplete() {
// Incomplete statement; call back to print "> ".
if !w.fn(w.accumulated) {
@@ -242,6 +249,19 @@ func (p *Parser) Document(r io.Reader) (*Word, error) {
return w, p.err
}
// Arithmetic parses a single arithmetic expression. That is, as if the input
// were within the $(( and )) tokens.
func (p *Parser) Arithmetic(r io.Reader) (ArithmExpr, error) {
p.reset()
p.f = &File{}
p.src = r
p.rune()
p.quote = arithmExpr
p.next()
expr := p.arithmExpr(0, false, false)
return expr, p.err
}
// Parser holds the internal state of the parsing mechanism of a
// program.
type Parser struct {
@@ -314,6 +334,11 @@ type Parser struct {
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 the documentation for Parser.Interactive.
func (p *Parser) Incomplete() bool {
// If we're in a quote state other than noState, we're parsing a node
// such as a double-quoted string.
@@ -578,16 +603,16 @@ func (p *Parser) followRsrv(lpos Pos, left, val string) Pos {
return pos
}
func (p *Parser) followStmts(left string, lpos Pos, stops ...string) StmtList {
func (p *Parser) followStmts(left string, lpos Pos, stops ...string) ([]*Stmt, []Comment) {
if p.got(semicolon) {
return StmtList{}
return nil, nil
}
newLine := p.got(_Newl)
sl := p.stmtList(stops...)
if len(sl.Stmts) < 1 && !newLine {
stmts, last := p.stmtList(stops...)
if len(stmts) < 1 && !newLine {
p.followErr(lpos, left, "a statement list")
}
return sl
return stmts, last
}
func (p *Parser) followWordTok(tok token, pos Pos) *Word {
@@ -642,12 +667,22 @@ func (p *Parser) errPass(err error) {
}
}
// 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 IsIncomplete(err error) bool {
perr, ok := err.(ParseError)
return ok && perr.Incomplete
}
// ParseError represents an error found when parsing a source file, from which
// the parser cannot recover.
type ParseError struct {
Filename string
Pos
Text string
Incomplete bool
}
func (e ParseError) Error() string {
@@ -694,6 +729,7 @@ func (p *Parser) posErr(pos Pos, format string, a ...interface{}) {
Filename: p.f.Name,
Pos: pos,
Text: fmt.Sprintf(format, a...),
Incomplete: p.tok == _EOF && p.Incomplete(),
})
}
@@ -756,12 +792,14 @@ loop:
}
}
func (p *Parser) stmtList(stops ...string) (sl StmtList) {
func (p *Parser) stmtList(stops ...string) ([]*Stmt, []Comment) {
var stmts []*Stmt
var last []Comment
fn := func(s *Stmt) bool {
if sl.Stmts == nil {
sl.Stmts = p.stList()
if stmts == nil {
stmts = p.stList()
}
sl.Stmts = append(sl.Stmts, s)
stmts = append(stmts, s)
return true
}
p.stmts(fn, stops...)
@@ -785,9 +823,9 @@ func (p *Parser) stmtList(stops ...string) (sl StmtList) {
split = i
}
}
sl.Last = p.accComs[:split]
last = p.accComs[:split]
p.accComs = p.accComs[split:]
return
return stmts, last
}
func (p *Parser) invalidStmtStart() {
@@ -867,7 +905,7 @@ func (p *Parser) wordPart() WordPart {
old := p.preNested(subCmd)
p.rune() // don't tokenize '|'
p.next()
cs.StmtList = p.stmtList("}")
cs.Stmts, cs.Last = p.stmtList("}")
p.postNested(old)
pos, ok := p.gotRsrv("}")
if !ok {
@@ -912,7 +950,7 @@ func (p *Parser) wordPart() WordPart {
cs := &CmdSubst{Left: p.pos}
old := p.preNested(subCmd)
p.next()
cs.StmtList = p.stmtList()
cs.Stmts, cs.Last = p.stmtList()
p.postNested(old)
cs.Right = p.matched(cs.Left, leftParen, rightParen)
return cs
@@ -936,15 +974,9 @@ func (p *Parser) wordPart() WordPart {
pe.Param = p.getLit()
if pe.Param != nil && pe.Param.Value == "" {
l := p.lit(pe.Dollar, "$")
if p.val == "" {
// e.g. "$\\\n" followed by a closing double
// quote, so we need the next token.
p.next()
} else {
// e.g. "$\\\"" within double quotes, so we must
// keep the rest of the literal characters.
l.ValueEnd = posAddCol(l.ValuePos, 1)
}
return l
}
return pe
@@ -953,7 +985,7 @@ func (p *Parser) wordPart() WordPart {
ps := &ProcSubst{Op: ProcOperator(p.tok), OpPos: p.pos}
old := p.preNested(subCmd)
p.next()
ps.StmtList = p.stmtList()
ps.Stmts, ps.Last = p.stmtList()
p.postNested(old)
ps.Rparen = p.matched(ps.OpPos, token(ps.Op), rightParen)
return ps
@@ -977,8 +1009,11 @@ func (p *Parser) wordPart() WordPart {
p.rune()
p.next()
return sq
case escNewl:
p.litBs = append(p.litBs, '\\', '\n')
case utf8.RuneSelf:
p.posErr(sq.Pos(), "reached EOF without closing quote %s", sglQuote)
p.tok = _EOF
p.quoteErr(sq.Pos(), sglQuote)
return nil
}
}
@@ -993,7 +1028,7 @@ func (p *Parser) wordPart() WordPart {
return nil
}
p.ensureNoNested()
cs := &CmdSubst{Left: p.pos}
cs := &CmdSubst{Left: p.pos, Backquotes: true}
old := p.preNested(subCmdBckquo)
p.openBquotes++
@@ -1002,7 +1037,7 @@ func (p *Parser) wordPart() WordPart {
p.rune()
p.next()
cs.StmtList = p.stmtList()
cs.Stmts, cs.Last = p.stmtList()
if p.tok == bckQuote && p.lastBquoteEsc < p.openBquotes-1 {
// e.g. found ` before the nested backquote \` was closed.
p.tok = _EOF
@@ -1051,12 +1086,13 @@ func (p *Parser) wordPart() WordPart {
}
func (p *Parser) dblQuoted() *DblQuoted {
q := &DblQuoted{Position: p.pos, Dollar: p.tok == dollDblQuote}
q := &DblQuoted{Left: p.pos, Dollar: p.tok == dollDblQuote}
old := p.quote
p.quote = dblQuotes
p.next()
q.Parts = p.wordParts()
p.quote = old
q.Right = p.pos
if !p.got(dblQuote) {
p.quoteErr(q.Pos(), dblQuote)
}
@@ -1072,7 +1108,7 @@ func arithmOpLevel(op BinAritOperator) int {
return 1
case Assgn:
return 2
case Quest, Colon:
case TernQuest, TernColon:
return 3
case AndArit, OrArit:
return 4
@@ -1149,7 +1185,7 @@ func (p *Parser) arithmExpr(level int, compact, tern bool) ArithmExpr {
X: left,
}
switch b.Op {
case Colon:
case TernColon:
if !tern {
p.posErr(b.Pos(), "ternary operator missing ? before :")
}
@@ -1162,12 +1198,12 @@ func (p *Parser) arithmExpr(level int, compact, tern bool) ArithmExpr {
if p.next(); compact && p.spaced {
p.followErrExp(b.OpPos, b.Op.String())
}
b.Y = p.arithmExpr(newLevel, compact, b.Op == Quest)
b.Y = p.arithmExpr(newLevel, compact, b.Op == TernQuest)
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 {
if b.Op == TernQuest {
if b2, ok := b.Y.(*BinaryArithm); !ok || b2.Op != TernColon {
p.posErr(b.Pos(), "ternary operator missing : after ?")
}
}
@@ -1193,7 +1229,7 @@ func (p *Parser) arithmExprBase(compact bool) ArithmExpr {
p.got(_Newl)
var x ArithmExpr
switch p.tok {
case exclMark:
case exclMark, tilde:
ue := &UnaryArithm{OpPos: p.pos, Op: UnAritOperator(p.tok)}
p.next()
if ue.X = p.arithmExprBase(compact); ue.X == nil {
@@ -1317,9 +1353,10 @@ func (p *Parser) paramExp() *ParamExp {
p.next()
case quest, minus:
if pe.Length && p.r != '}' {
// actually ${#-default}, not ${#-}; error below
// actually ${#-default}, not ${#-}; fix the ambiguity
pe.Length = false
pe.Param = p.lit(p.pos, "#")
pe.Param = p.lit(posAddCol(p.pos, -1), "#")
pe.Param.ValueEnd = p.pos
break
}
fallthrough
@@ -1542,10 +1579,14 @@ func (p *Parser) getAssign(needEqual bool) *Assign {
p.rune()
p.pos = posAddCol(p.pos, 1)
as.Index = p.eitherIndex()
if !needEqual && (p.spaced || stopToken(p.tok)) {
if p.spaced || stopToken(p.tok) {
if needEqual {
p.followErr(as.Pos(), "a[b]", "=")
} else {
as.Naked = true
return as
}
}
if len(p.val) > 0 && p.val[0] == '+' {
as.Append = true
p.val = p.val[1:]
@@ -1600,7 +1641,7 @@ func (p *Parser) getAssign(needEqual bool) *Assign {
// TODO: support [index]=[
default:
p.curErr("array element values must be words")
break
return nil
}
}
if len(p.accComs) > 0 {
@@ -1689,14 +1730,14 @@ func (p *Parser) getStmt(readEnd, binCmd, fnBody bool) *Stmt {
p.posErr(s.Pos(), `cannot negate a command multiple times`)
}
}
if s = p.gotStmtPipe(s); s == nil || p.err != nil {
if s = p.gotStmtPipe(s, false); s == nil || p.err != nil {
return nil
}
// instead of using recursion, iterate manually
for p.tok == andAnd || p.tok == orOr {
if binCmd {
// left associativity: in a list of BinaryCmds, the
// right recursion should only read a single element
if binCmd {
return s
}
b := &BinaryCmd{
@@ -1740,7 +1781,7 @@ func (p *Parser) getStmt(readEnd, binCmd, fnBody bool) *Stmt {
return s
}
func (p *Parser) gotStmtPipe(s *Stmt) *Stmt {
func (p *Parser) gotStmtPipe(s *Stmt, binCmd bool) *Stmt {
s.Comments, p.accComs = p.accComs, nil
switch p.tok {
case _LitWord:
@@ -1862,17 +1903,22 @@ func (p *Parser) gotStmtPipe(s *Stmt) *Stmt {
for p.peekRedir() {
p.doRedirect(s)
}
switch p.tok {
case orAnd:
if p.lang == LangMirBSDKorn {
// instead of using recursion, iterate manually
for p.tok == or || p.tok == orAnd {
if binCmd {
// left associativity: in a list of BinaryCmds, the
// right recursion should only read a single element
return s
}
if p.tok == orAnd && p.lang == LangMirBSDKorn {
// No need to check for LangPOSIX, as on that language
// we parse |& as two tokens.
break
}
fallthrough
case or:
b := &BinaryCmd{OpPos: p.pos, Op: BinCmdOperator(p.tok), X: s}
p.next()
p.got(_Newl)
if b.Y = p.gotStmtPipe(p.stmt(p.pos)); b.Y == nil || p.err != nil {
if b.Y = p.gotStmtPipe(p.stmt(p.pos), true); b.Y == nil || p.err != nil {
p.followErr(b.OpPos, b.Op.String(), "a statement")
break
}
@@ -1890,7 +1936,7 @@ func (p *Parser) subshell(s *Stmt) {
sub := &Subshell{Lparen: p.pos}
old := p.preNested(subCmd)
p.next()
sub.StmtList = p.stmtList()
sub.Stmts, sub.Last = p.stmtList()
p.postNested(old)
sub.Rparen = p.matched(sub.Lparen, leftParen, rightParen)
s.Cmd = sub
@@ -1914,7 +1960,7 @@ func (p *Parser) arithmExpCmd(s *Stmt) {
func (p *Parser) block(s *Stmt) {
b := &Block{Lbrace: p.pos}
p.next()
b.StmtList = p.stmtList("}")
b.Stmts, b.Last = p.stmtList("}")
pos, ok := p.gotRsrv("}")
b.Rbrace = pos
if !ok {
@@ -1924,37 +1970,39 @@ func (p *Parser) block(s *Stmt) {
}
func (p *Parser) ifClause(s *Stmt) {
rif := &IfClause{IfPos: p.pos}
rootIf := &IfClause{Position: p.pos}
p.next()
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
rootIf.Cond, rootIf.CondLast = p.followStmts("if", rootIf.Position, "then")
rootIf.ThenPos = p.followRsrv(rootIf.Position, "if <cond>", "then")
rootIf.Then, rootIf.ThenLast = p.followStmts("then", rootIf.ThenPos, "fi", "elif", "else")
curIf := rootIf
for p.tok == _LitWord && p.val == "elif" {
elf := &IfClause{IfPos: p.pos, Elif: true}
s := p.stmt(elf.IfPos)
s.Cmd = elf
s.Comments = p.accComs
elf := &IfClause{Position: p.pos}
curIf.Last = p.accComs
p.accComs = nil
p.next()
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")
curIf.ElsePos = elf.IfPos
curIf.Else.Stmts = []*Stmt{s}
elf.Cond, elf.CondLast = p.followStmts("elif", elf.Position, "then")
elf.ThenPos = p.followRsrv(elf.Position, "elif <cond>", "then")
elf.Then, elf.ThenLast = p.followStmts("then", elf.ThenPos, "fi", "elif", "else")
curIf.Else = elf
curIf = elf
}
if elsePos, ok := p.gotRsrv("else"); ok {
curIf.ElseComments = p.accComs
curIf.Last = p.accComs
p.accComs = nil
curIf.ElsePos = elsePos
curIf.Else = p.followStmts("else", curIf.ElsePos, "fi")
els := &IfClause{Position: elsePos}
els.Then, els.ThenLast = p.followStmts("else", els.Position, "fi")
curIf.Else = els
curIf = els
}
curIf.FiComments = p.accComs
curIf.Last = p.accComs
p.accComs = nil
rif.FiPos = p.stmtEnd(rif, "if", "fi")
curIf.FiPos = rif.FiPos
s.Cmd = rif
rootIf.FiPos = p.stmtEnd(rootIf, "if", "fi")
for els := rootIf.Else; els != nil; els = els.Else {
// All the nested IfClauses share the same FiPos.
els.FiPos = rootIf.FiPos
}
s.Cmd = rootIf
}
func (p *Parser) whileClause(s *Stmt, until bool) {
@@ -1966,9 +2014,9 @@ func (p *Parser) whileClause(s *Stmt, until bool) {
rsrvCond = "until <cond>"
}
p.next()
wc.Cond = p.followStmts(rsrv, wc.WhilePos, "do")
wc.Cond, wc.CondLast = p.followStmts(rsrv, wc.WhilePos, "do")
wc.DoPos = p.followRsrv(wc.WhilePos, rsrvCond, "do")
wc.Do = p.followStmts("do", wc.DoPos, "done")
wc.Do, wc.DoLast = p.followStmts("do", wc.DoPos, "done")
wc.DonePos = p.stmtEnd(wc, rsrv, "done")
s.Cmd = wc
}
@@ -1981,7 +2029,7 @@ func (p *Parser) forClause(s *Stmt) {
s.Comments = append(s.Comments, p.accComs...)
p.accComs = nil
fc.Do = p.followStmts("do", fc.DoPos, "done")
fc.Do, fc.DoLast = p.followStmts("do", fc.DoPos, "done")
fc.DonePos = p.stmtEnd(fc, "for", "done")
s.Cmd = fc
}
@@ -2045,7 +2093,7 @@ func (p *Parser) selectClause(s *Stmt) {
p.next()
fc.Loop = p.wordIter("select", fc.ForPos)
fc.DoPos = p.followRsrv(fc.ForPos, "select foo [in words]", "do")
fc.Do = p.followStmts("do", fc.DoPos, "done")
fc.Do, fc.DoLast = p.followStmts("do", fc.DoPos, "done")
fc.DonePos = p.stmtEnd(fc, "select", "done")
s.Cmd = fc
}
@@ -2091,7 +2139,7 @@ func (p *Parser) caseItems(stop string) (items []*CaseItem) {
}
old := p.preNested(switchCase)
p.next()
ci.StmtList = p.stmtList(stop)
ci.Stmts, ci.Last = p.stmtList(stop)
p.postNested(old)
switch p.tok {
case dblSemicolon, semiAnd, dblSemiAnd, semiOr:
@@ -2253,22 +2301,18 @@ func (p *Parser) testExprBase(ftok token, fpos Pos) TestExpr {
func (p *Parser) declClause(s *Stmt) {
ds := &DeclClause{Variant: p.lit(p.pos, p.val)}
p.next()
for (p.tok == _LitWord || p.tok == _Lit) &&
(p.val[0] == '-' || p.val[0] == '+') {
ds.Opts = append(ds.Opts, p.getWord())
}
for !stopToken(p.tok) && !p.peekRedir() {
if p.hasValidIdent() {
ds.Assigns = append(ds.Assigns, p.getAssign(false))
ds.Args = append(ds.Args, p.getAssign(false))
} else if p.eqlOffs > 0 {
p.curErr("invalid var name")
} else if p.tok == _LitWord {
ds.Assigns = append(ds.Assigns, &Assign{
} else if p.tok == _LitWord && ValidName(p.val) {
ds.Args = append(ds.Args, &Assign{
Naked: true,
Name: p.getLit(),
})
} else if w := p.getWord(); w != nil {
ds.Assigns = append(ds.Assigns, &Assign{
ds.Args = append(ds.Args, &Assign{
Naked: true,
Value: w,
})
@@ -2300,7 +2344,7 @@ func (p *Parser) timeClause(s *Stmt) {
if _, ok := p.gotRsrv("-p"); ok {
tc.PosixFormat = true
}
tc.Stmt = p.gotStmtPipe(p.stmt(p.pos))
tc.Stmt = p.gotStmtPipe(p.stmt(p.pos), false)
s.Cmd = tc
}
@@ -2308,26 +2352,25 @@ func (p *Parser) coprocClause(s *Stmt) {
cc := &CoprocClause{Coproc: p.pos}
if p.next(); isBashCompoundCommand(p.tok, p.val) {
// has no name
cc.Stmt = p.gotStmtPipe(p.stmt(p.pos))
cc.Stmt = p.gotStmtPipe(p.stmt(p.pos), false)
s.Cmd = cc
return
}
cc.Name = p.getLit()
cc.Stmt = p.gotStmtPipe(p.stmt(p.pos))
cc.Name = p.getWord()
cc.Stmt = p.gotStmtPipe(p.stmt(p.pos), false)
if cc.Stmt == nil {
if cc.Name == nil {
p.posErr(cc.Coproc, "coproc clause requires a command")
return
}
// name was in fact the stmt
cc.Stmt = p.stmt(cc.Name.ValuePos)
cc.Stmt.Cmd = p.call(p.word(p.wps(cc.Name)))
cc.Stmt = p.stmt(cc.Name.Pos())
cc.Stmt.Cmd = p.call(cc.Name)
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
call.Args = append([]*Word{p.word(p.wps(cc.Name))},
call.Args...)
call.Args = append([]*Word{cc.Name}, call.Args...)
cc.Name = nil
}
}

View File

@@ -9,27 +9,39 @@ import (
"fmt"
"io"
"strings"
"text/tabwriter"
"unicode"
)
// PrinterOption is a function which can be passed to NewPrinter
// to alter its behaviour. To apply option to existing Printer
// call it directly, for example syntax.KeepPadding(true)(printer).
type PrinterOption func(*Printer)
// Indent sets the number of spaces used for indentation. If set to 0,
// tabs will be used instead.
func Indent(spaces uint) func(*Printer) {
func Indent(spaces uint) PrinterOption {
return func(p *Printer) { p.indentSpaces = spaces }
}
// BinaryNextLine will make binary operators appear on the next line
// when a binary command, such as a pipe, spans multiple lines. A
// backslash will be used.
func BinaryNextLine(p *Printer) { p.binNextLine = true }
func BinaryNextLine(enabled bool) PrinterOption {
return func(p *Printer) { p.binNextLine = enabled }
}
// SwitchCaseIndent will make switch cases be indented. As such, switch
// case bodies will be two levels deeper than the switch itself.
func SwitchCaseIndent(p *Printer) { p.swtCaseIndent = true }
func SwitchCaseIndent(enabled bool) PrinterOption {
return func(p *Printer) { p.swtCaseIndent = enabled }
}
// SpaceRedirects will put a space after most redirection operators. The
// exceptions are '>&', '<&', '>(', and '<('.
func SpaceRedirects(p *Printer) { p.spaceRedirects = true }
func SpaceRedirects(enabled bool) PrinterOption {
return func(p *Printer) { p.spaceRedirects = enabled }
}
// KeepPadding will keep most nodes and tokens in the same column that
// they were in the original source. This allows the user to decide how
@@ -38,25 +50,31 @@ func SpaceRedirects(p *Printer) { p.spaceRedirects = true }
// Note that this feature is best-effort and will only keep the
// alignment stable, so it may need some human help the first time it is
// run.
func KeepPadding(p *Printer) {
func KeepPadding(enabled bool) PrinterOption {
return func(p *Printer) {
// TODO: support setting this option to false.
if enabled {
p.keepPadding = true
p.cols.Writer = p.bufWriter.(*bufio.Writer)
p.bufWriter = &p.cols
}
}
}
// Minify will print programs in a way to save the most bytes possible.
// For example, indentation and comments are skipped, and extra
// whitespace is avoided when possible.
func Minify(p *Printer) { p.minify = true }
func Minify(enabled bool) PrinterOption {
return func(p *Printer) { p.minify = enabled }
}
// NewPrinter allocates a new Printer and applies any number of options.
func NewPrinter(options ...func(*Printer)) *Printer {
func NewPrinter(opts ...PrinterOption) *Printer {
p := &Printer{
bufWriter: bufio.NewWriter(nil),
lenPrinter: new(Printer),
tabsPrinter: new(Printer),
tabWriter: new(tabwriter.Writer),
}
for _, opt := range options {
for _, opt := range opts {
opt(p)
}
return p
@@ -70,26 +88,53 @@ func NewPrinter(options ...func(*Printer)) *Printer {
// *File is used.
func (p *Printer) Print(w io.Writer, node Node) error {
p.reset()
// TODO: consider adding a raw mode to skip the tab writer, much like in
// go/printer.
twmode := tabwriter.DiscardEmptyColumns | tabwriter.StripEscape
tabwidth := 8
if p.indentSpaces == 0 {
// indenting with tabs
twmode |= tabwriter.TabIndent
} else {
// indenting with spaces
tabwidth = int(p.indentSpaces)
}
p.tabWriter.Init(w, 0, tabwidth, 1, ' ', twmode)
w = p.tabWriter
p.bufWriter.Reset(w)
switch x := node.(type) {
case *File:
p.stmtList(x.StmtList)
p.stmtList(x.Stmts, x.Last)
p.newline(x.End())
case *Stmt:
p.stmtList(StmtList{Stmts: []*Stmt{x}})
p.stmtList([]*Stmt{x}, nil)
case Command:
p.line = x.Pos().Line()
p.command(x, nil)
case *Word:
p.line = x.Pos().Line()
p.word(x)
case WordPart:
p.line = x.Pos().Line()
p.wordPart(x, nil)
default:
return fmt.Errorf("unsupported node type: %T", x)
}
p.flushHeredocs()
p.flushComments()
return p.bufWriter.Flush()
// flush the writers
if err := p.bufWriter.Flush(); err != nil {
return err
}
if tw, _ := w.(*tabwriter.Writer); tw != nil {
if err := tw.Flush(); err != nil {
return err
}
}
return nil
}
type bufWriter interface {
@@ -140,6 +185,7 @@ func (c *colCounter) Reset(w io.Writer) {
// program.
type Printer struct {
bufWriter
tabWriter *tabwriter.Writer
cols colCounter
indentSpaces uint
@@ -153,8 +199,6 @@ type Printer struct {
wantNewline bool
wroteSemi bool
commentPadding uint
// pendingComments are any comments in the current line or statement
// that we have yet to print. This is useful because that way, we can
// ensure that all comments are written immediately before a newline.
@@ -180,17 +224,12 @@ type Printer struct {
// pendingHdocs is the list of pending heredocs to write.
pendingHdocs []*Redirect
// used in stmtCols to align comments
lenPrinter *Printer
lenCounter byteCounter
// used when printing <<- heredocs with tab indentation
tabsPrinter *Printer
}
func (p *Printer) reset() {
p.wantSpace, p.wantNewline = false, false
p.commentPadding = 0
p.pendingComments = p.pendingComments[:0]
// minification uses its own newline logic
@@ -272,6 +311,23 @@ func (p *Printer) semiOrNewl(s string, pos Pos) {
p.wantSpace = true
}
func (p *Printer) writeLit(s string) {
if !strings.Contains(s, "\t") {
p.WriteString(s)
return
}
p.WriteByte('\xff')
for i := 0; i < len(s); i++ {
b := s[i]
if b != '\t' {
p.WriteByte(b)
continue
}
p.WriteByte(b)
}
p.WriteByte('\xff')
}
func (p *Printer) incLevel() {
inc := false
if p.level <= p.lastLevel || len(p.levelIncs) == 0 {
@@ -299,9 +355,11 @@ func (p *Printer) indent() {
switch {
case p.level == 0:
case p.indentSpaces == 0:
p.WriteByte('\xff')
for i := uint(0); i < p.level; i++ {
p.WriteByte('\t')
}
p.WriteByte('\xff')
default:
p.spaces(p.indentSpaces * p.level)
}
@@ -345,29 +403,31 @@ func (p *Printer) flushHeredocs() {
p.line++
p.WriteByte('\n')
p.wantNewline, p.wantSpace = false, false
if r.Op == DashHdoc && p.indentSpaces == 0 &&
!p.minify && p.tabsPrinter != nil {
if r.Op == DashHdoc && p.indentSpaces == 0 && !p.minify {
if r.Hdoc != nil {
extra := extraIndenter{
bufWriter: p.bufWriter,
baseIndent: int(p.level + 1),
firstIndent: -1,
}
*p.tabsPrinter = Printer{
p.tabsPrinter = &Printer{
bufWriter: &extra,
line: r.Hdoc.Pos().Line(),
}
p.tabsPrinter.line = r.Hdoc.Pos().Line()
p.tabsPrinter.word(r.Hdoc)
p.indent()
p.line = r.Hdoc.End().Line()
} else {
p.indent()
}
} else if r.Hdoc != nil {
p.word(r.Hdoc)
p.line = r.Hdoc.End().Line()
}
p.unquotedWord(r.Word)
if r.Hdoc != nil {
// Overwrite p.line, since printing r.Word again can set
// p.line to the beginning of the heredoc again.
p.line = r.Hdoc.End().Line()
}
p.wantSpace = false
}
p.level = newLevel
@@ -433,7 +493,7 @@ func (p *Printer) flushComments() {
if p.keepPadding {
p.spacePad(c.Pos())
} else {
p.spaces(p.commentPadding + 1)
p.WriteByte('\t')
}
}
// don't go back one line, which may happen in some edge cases
@@ -441,7 +501,7 @@ func (p *Printer) flushComments() {
p.line = cline
}
p.WriteByte('#')
p.WriteString(strings.TrimRightFunc(c.Text, unicode.IsSpace))
p.writeLit(strings.TrimRightFunc(c.Text, unicode.IsSpace))
p.wantNewline = true
}
p.pendingComments = nil
@@ -454,26 +514,36 @@ func (p *Printer) comments(comments ...Comment) {
p.pendingComments = append(p.pendingComments, comments...)
}
func (p *Printer) wordParts(wps []WordPart) {
for i, n := range wps {
func (p *Printer) wordParts(wps []WordPart, quoted bool) {
for i, wp := range wps {
var next WordPart
if i+1 < len(wps) {
next = wps[i+1]
}
p.wordPart(n, next)
for wp.Pos().Line() > p.line {
if quoted {
// No extra spacing or indentation if quoted.
p.WriteString("\\\n")
p.line++
} else {
p.bslashNewl()
}
}
p.wordPart(wp, next)
p.line = wp.End().Line()
}
}
func (p *Printer) wordPart(wp, next WordPart) {
switch x := wp.(type) {
case *Lit:
p.WriteString(x.Value)
p.writeLit(x.Value)
case *SglQuoted:
if x.Dollar {
p.WriteByte('$')
}
p.WriteByte('\'')
p.WriteString(x.Value)
p.writeLit(x.Value)
p.WriteByte('\'')
p.line = x.End().Line()
case *DblQuoted:
@@ -484,18 +554,18 @@ func (p *Printer) wordPart(wp, next WordPart) {
case x.TempFile:
p.WriteString("${")
p.wantSpace = true
p.nestedStmts(x.StmtList, x.Right)
p.nestedStmts(x.Stmts, x.Last, x.Right)
p.wantSpace = false
p.semiRsrv("}", x.Right)
case x.ReplyVar:
p.WriteString("${|")
p.nestedStmts(x.StmtList, x.Right)
p.nestedStmts(x.Stmts, x.Last, x.Right)
p.wantSpace = false
p.semiRsrv("}", x.Right)
default:
p.WriteString("$(")
p.wantSpace = len(x.Stmts) > 0 && startsWithLparen(x.Stmts[0])
p.nestedStmts(x.StmtList, x.Right)
p.nestedStmts(x.Stmts, x.Last, x.Right)
p.rightParen(x.Right)
}
case *ParamExp:
@@ -527,7 +597,7 @@ func (p *Printer) wordPart(wp, next WordPart) {
p.WriteString("))")
case *ExtGlob:
p.WriteString(x.Op.String())
p.WriteString(x.Pattern.Value)
p.writeLit(x.Pattern.Value)
p.WriteByte(')')
case *ProcSubst:
// avoid conflict with << and others
@@ -535,7 +605,7 @@ func (p *Printer) wordPart(wp, next WordPart) {
p.space()
}
p.WriteString(x.Op.String())
p.nestedStmts(x.StmtList, x.Rparen)
p.nestedStmts(x.Stmts, x.Last, x.Rparen)
p.rightParen(x.Rparen)
}
}
@@ -546,8 +616,12 @@ func (p *Printer) dblQuoted(dq *DblQuoted) {
}
p.WriteByte('"')
if len(dq.Parts) > 0 {
p.wordParts(dq.Parts)
p.line = dq.Parts[len(dq.Parts)-1].End().Line()
p.wordParts(dq.Parts, true)
}
// Add any trailing escaped newlines.
for p.line < dq.Right.Line() {
p.WriteString("\\\n")
p.line++
}
p.WriteByte('"')
}
@@ -564,13 +638,13 @@ func (p *Printer) wroteIndex(index ArithmExpr) bool {
func (p *Printer) paramExp(pe *ParamExp) {
if pe.nakedIndex() { // arr[x]
p.WriteString(pe.Param.Value)
p.writeLit(pe.Param.Value)
p.wroteIndex(pe.Index)
return
}
if pe.Short { // $var
p.WriteByte('$')
p.WriteString(pe.Param.Value)
p.writeLit(pe.Param.Value)
return
}
// ${var...}
@@ -583,7 +657,7 @@ func (p *Printer) paramExp(pe *ParamExp) {
case pe.Excl:
p.WriteByte('!')
}
p.WriteString(pe.Param.Value)
p.writeLit(pe.Param.Value)
p.wroteIndex(pe.Index)
switch {
case pe.Slice != nil:
@@ -606,7 +680,7 @@ func (p *Printer) paramExp(pe *ParamExp) {
p.word(pe.Repl.With)
}
case pe.Names != 0:
p.WriteString(pe.Names.String())
p.writeLit(pe.Names.String())
case pe.Exp != nil:
p.WriteString(pe.Exp.Op.String())
if pe.Exp.Word != nil {
@@ -619,7 +693,7 @@ func (p *Printer) paramExp(pe *ParamExp) {
func (p *Printer) loop(loop Loop) {
switch x := loop.(type) {
case *WordIter:
p.WriteString(x.Name.Value)
p.writeLit(x.Name.Value)
if x.InPos.IsValid() {
p.spacedString(" in", Pos{})
p.wordJoin(x.Items)
@@ -702,7 +776,7 @@ func (p *Printer) testExpr(expr TestExpr) {
}
func (p *Printer) word(w *Word) {
p.wordParts(w.Parts)
p.wordParts(w.Parts, false)
p.wantSpace = true
}
@@ -710,9 +784,9 @@ func (p *Printer) unquotedWord(w *Word) {
for _, wp := range w.Parts {
switch x := wp.(type) {
case *SglQuoted:
p.WriteString(x.Value)
p.writeLit(x.Value)
case *DblQuoted:
p.wordParts(x.Parts)
p.wordParts(x.Parts, true)
case *Lit:
for i := 0; i < len(x.Value); i++ {
if b := x.Value[i]; b == '\\' {
@@ -818,7 +892,7 @@ func (p *Printer) stmt(s *Stmt) {
p.spacePad(r.Pos())
}
if r.N != nil {
p.WriteString(r.N.Value)
p.writeLit(r.N.Value)
}
p.WriteString(r.Op.String())
if p.spaceRedirects && (r.Op != DplIn && r.Op != DplOut) {
@@ -868,7 +942,7 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) {
p.spacePad(r.Pos())
}
if r.N != nil {
p.WriteString(r.N.Value)
p.writeLit(r.N.Value)
}
p.WriteString(r.Op.String())
if p.spaceRedirects && (r.Op != DplIn && r.Op != DplOut) {
@@ -883,15 +957,15 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) {
case *Block:
p.WriteByte('{')
p.wantSpace = true
p.nestedStmts(x.StmtList, x.Rbrace)
p.nestedStmts(x.Stmts, x.Last, x.Rbrace)
p.semiRsrv("}", x.Rbrace)
case *IfClause:
p.ifClause(x, false)
case *Subshell:
p.WriteByte('(')
p.wantSpace = len(x.Stmts) > 0 && startsWithLparen(x.Stmts[0])
p.spacePad(x.StmtList.pos())
p.nestedStmts(x.StmtList, x.Rparen)
p.spacePad(stmtsPos(x.Stmts, x.Last))
p.nestedStmts(x.Stmts, x.Last, x.Rparen)
p.wantSpace = false
p.spacePad(x.Rparen)
p.rightParen(x.Rparen)
@@ -901,9 +975,9 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) {
} else {
p.spacedString("while", x.Pos())
}
p.nestedStmts(x.Cond, Pos{})
p.nestedStmts(x.Cond, x.CondLast, Pos{})
p.semiOrNewl("do", x.DoPos)
p.nestedStmts(x.Do, x.DonePos)
p.nestedStmts(x.Do, x.DoLast, x.DonePos)
p.semiRsrv("done", x.DonePos)
case *ForClause:
if x.Select {
@@ -913,7 +987,7 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) {
}
p.loop(x.Loop)
p.semiOrNewl("do", x.DoPos)
p.nestedStmts(x.Do, x.DonePos)
p.nestedStmts(x.Do, x.DoLast, x.DonePos)
p.semiRsrv("done", x.DonePos)
case *BinaryCmd:
p.stmt(x.X)
@@ -959,7 +1033,7 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) {
if x.RsrvWord {
p.WriteString("function ")
}
p.WriteString(x.Name.Value)
p.writeLit(x.Name.Value)
p.WriteString("()")
if !p.minify {
p.space()
@@ -987,9 +1061,12 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) {
p.casePatternJoin(ci.Patterns)
p.WriteByte(')')
p.wantSpace = !p.minify
sep := len(ci.Stmts) > 1 || ci.StmtList.pos().Line() > p.line ||
(!ci.StmtList.empty() && ci.OpPos.Line() > ci.StmtList.end().Line())
p.nestedStmts(ci.StmtList, ci.OpPos)
bodyPos := stmtsPos(ci.Stmts, ci.Last)
bodyEnd := stmtsEnd(ci.Stmts, ci.Last)
sep := len(ci.Stmts) > 1 || bodyPos.Line() > p.line ||
(bodyEnd.IsValid() && ci.OpPos.Line() > bodyEnd.Line())
p.nestedStmts(ci.Stmts, ci.Last, ci.OpPos)
p.level++
if !p.minify || i != len(x.Items)-1 {
if sep {
@@ -1023,11 +1100,7 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) {
p.spacedString("]]", x.Right)
case *DeclClause:
p.spacedString(x.Variant.Value, x.Pos())
for _, w := range x.Opts {
p.space()
p.word(w)
}
p.assigns(x.Assigns)
p.assigns(x.Args)
case *TimeClause:
p.spacedString("time", x.Pos())
if x.PosixFormat {
@@ -1040,7 +1113,7 @@ func (p *Printer) command(cmd Command, redirs []*Redirect) (startRedirs int) {
p.spacedString("coproc", x.Pos())
if x.Name != nil {
p.space()
p.WriteString(x.Name.Value)
p.word(x.Name)
}
p.space()
p.stmt(x.Stmt)
@@ -1058,33 +1131,37 @@ func (p *Printer) ifClause(ic *IfClause, elif bool) {
if !elif {
p.spacedString("if", ic.Pos())
}
p.nestedStmts(ic.Cond, Pos{})
p.nestedStmts(ic.Cond, ic.CondLast, Pos{})
p.semiOrNewl("then", ic.ThenPos)
p.nestedStmts(ic.Then, ic.bodyEndPos())
thenEnd := ic.FiPos
el := ic.Else
if el != nil {
thenEnd = el.Position
}
p.nestedStmts(ic.Then, ic.ThenLast, thenEnd)
if el != nil && el.ThenPos.IsValid() {
p.comments(ic.Last...)
p.semiRsrv("elif", el.Position)
p.ifClause(el, true)
return
}
if el == nil {
p.comments(ic.Last...)
} else {
var left []Comment
for _, c := range ic.ElseComments {
if c.Pos().After(ic.ElsePos) {
for _, c := range ic.Last {
if c.Pos().After(el.Position) {
left = append(left, c)
break
}
p.comments(c)
}
if ic.FollowedByElif() {
s := ic.Else.Stmts[0]
p.comments(s.Comments...)
p.semiRsrv("elif", ic.ElsePos)
p.ifClause(s.Cmd.(*IfClause), true)
return
}
if !ic.Else.empty() {
p.semiRsrv("else", ic.ElsePos)
p.semiRsrv("else", el.Position)
p.comments(left...)
p.nestedStmts(ic.Else, ic.FiPos)
} else if ic.ElsePos.IsValid() {
p.line = ic.ElsePos.Line()
p.nestedStmts(el.Then, el.ThenLast, ic.FiPos)
p.comments(el.Last...)
}
p.comments(ic.FiComments...)
p.semiRsrv("fi", ic.FiPos)
}
@@ -1098,21 +1175,10 @@ func startsWithLparen(s *Stmt) bool {
return false
}
func (p *Printer) hasInline(s *Stmt) bool {
for _, c := range s.Comments {
if c.Pos().Line() == s.End().Line() {
return true
}
}
return false
}
func (p *Printer) stmtList(sl StmtList) {
func (p *Printer) stmtList(stmts []*Stmt, last []Comment) {
sep := p.wantNewline ||
(len(sl.Stmts) > 0 && sl.Stmts[0].Pos().Line() > p.line)
inlineIndent := 0
lastIndentedLine := uint(0)
for i, s := range sl.Stmts {
(len(stmts) > 0 && stmts[0].Pos().Line() > p.line)
for _, s := range stmts {
pos := s.Pos()
var midComs, endComs []Comment
for _, c := range s.Comments {
@@ -1120,7 +1186,7 @@ func (p *Printer) stmtList(sl StmtList) {
endComs = append(endComs, c)
break
}
if c.Pos().After(s.Pos()) {
if c.Pos().After(pos) {
midComs = append(midComs, c)
continue
}
@@ -1130,72 +1196,17 @@ func (p *Printer) stmtList(sl StmtList) {
p.newlines(pos)
}
p.line = pos.Line()
if !p.hasInline(s) {
inlineIndent = 0
p.commentPadding = 0
p.comments(midComs...)
p.stmt(s)
p.wantNewline = true
continue
}
p.comments(midComs...)
p.stmt(s)
if s.Pos().Line() > lastIndentedLine+1 {
inlineIndent = 0
}
if inlineIndent == 0 {
for _, s2 := range sl.Stmts[i:] {
if !p.hasInline(s2) {
break
}
if l := p.stmtCols(s2); l > inlineIndent {
inlineIndent = l
}
}
}
if inlineIndent > 0 {
if l := p.stmtCols(s); l > 0 {
p.commentPadding = uint(inlineIndent - l)
}
lastIndentedLine = p.line
}
p.comments(endComs...)
p.wantNewline = true
}
if len(sl.Stmts) == 1 && !sep {
if len(stmts) == 1 && !sep {
p.wantNewline = false
}
p.comments(sl.Last...)
p.comments(last...)
}
type byteCounter int
func (c *byteCounter) WriteByte(b byte) error {
switch {
case *c < 0:
case b == '\n':
*c = -1
default:
*c++
}
return nil
}
func (c *byteCounter) Write(p []byte) (int, error) {
return c.WriteString(string(p))
}
func (c *byteCounter) WriteString(s string) (int, error) {
switch {
case *c < 0:
case strings.Contains(s, "\n"):
*c = -1
default:
*c += byteCounter(len(s))
}
return 0, nil
}
func (c *byteCounter) Reset(io.Writer) { *c = 0 }
func (c *byteCounter) Flush() error { return nil }
// extraIndenter ensures that all lines in a '<<-' heredoc body have at least
// baseIndent leading tabs. Those that had more tab indentation than the first
// heredoc line will keep that relative indentation.
@@ -1213,8 +1224,20 @@ func (e *extraIndenter) WriteByte(b byte) error {
if b != '\n' {
return nil
}
trimmed := bytes.TrimLeft(e.curLine, "\t")
lineIndent := len(e.curLine) - len(trimmed)
line := e.curLine
if bytes.HasPrefix(e.curLine, []byte("\xff")) {
// beginning a multiline sequence, with the leading escape
line = line[1:]
}
trimmed := bytes.TrimLeft(line, "\t")
if len(trimmed) == 1 {
// no tabs if this is an empty line, i.e. "\n"
e.bufWriter.Write(trimmed)
e.curLine = e.curLine[:0]
return nil
}
lineIndent := len(line) - len(trimmed)
if e.firstIndent < 0 {
e.firstIndent = lineIndent
e.firstChange = e.baseIndent - lineIndent
@@ -1241,41 +1264,26 @@ func (e *extraIndenter) WriteString(s string) (int, error) {
return len(s), nil
}
// stmtCols reports the length that s will take when formatted in a
// single line. If it will span multiple lines, stmtCols will return -1.
func (p *Printer) stmtCols(s *Stmt) int {
if p.lenPrinter == nil {
return -1 // stmtCols call within stmtCols, bail
}
*p.lenPrinter = Printer{
bufWriter: &p.lenCounter,
line: s.Pos().Line(),
}
p.lenPrinter.bufWriter.Reset(nil)
p.lenPrinter.stmt(s)
return int(p.lenCounter)
}
func (p *Printer) nestedStmts(sl StmtList, closing Pos) {
func (p *Printer) nestedStmts(stmts []*Stmt, last []Comment, closing Pos) {
p.incLevel()
switch {
case len(sl.Stmts) > 1:
case len(stmts) > 1:
// Force a newline if we find:
// { stmt; stmt; }
p.wantNewline = true
case closing.Line() > p.line && len(sl.Stmts) > 0 &&
sl.end().Line() < closing.Line():
case closing.Line() > p.line && len(stmts) > 0 &&
stmtsEnd(stmts, last).Line() < closing.Line():
// Force a newline if we find:
// { stmt
// }
p.wantNewline = true
case len(p.pendingComments) > 0 && len(sl.Stmts) > 0:
case len(p.pendingComments) > 0 && len(stmts) > 0:
// Force a newline if we find:
// for i in a b # stmt
// do foo; done
p.wantNewline = true
}
p.stmtList(sl)
p.stmtList(stmts, last)
if closing.IsValid() {
p.flushComments()
}
@@ -1291,7 +1299,7 @@ func (p *Printer) assigns(assigns []*Assign) {
p.spacePad(a.Pos())
}
if a.Name != nil {
p.WriteString(a.Name.Value)
p.writeLit(a.Name.Value)
p.wroteIndex(a.Index)
if a.Append {
p.WriteByte('+')

View File

@@ -4,6 +4,30 @@ package syntax
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[noState-1]
_ = x[subCmd-2]
_ = x[subCmdBckquo-4]
_ = x[dblQuotes-8]
_ = x[hdocWord-16]
_ = x[hdocBody-32]
_ = x[hdocBodyTabs-64]
_ = x[arithmExpr-128]
_ = x[arithmExprLet-256]
_ = x[arithmExprCmd-512]
_ = x[arithmExprBrack-1024]
_ = x[testRegexp-2048]
_ = x[switchCase-4096]
_ = x[paramExpName-8192]
_ = x[paramExpSlice-16384]
_ = x[paramExpRepl-32768]
_ = x[paramExpExp-65536]
_ = x[arrayElems-131072]
}
const _quoteState_name = "noStatesubCmdsubCmdBckquodblQuoteshdocWordhdocBodyhdocBodyTabsarithmExprarithmExprLetarithmExprCmdarithmExprBracktestRegexpswitchCaseparamExpNameparamExpSliceparamExpReplparamExpExparrayElems"
var _quoteState_map = map[quoteState]string{

View File

@@ -5,8 +5,8 @@ package syntax
import "bytes"
// Simplify simplifies a given program and returns whether any changes
// were made.
// Simplify modifies a node to remove redundant pieces of syntax, and returns
// whether any changes were made.
//
// The changes currently applied are:
//
@@ -71,6 +71,10 @@ func (s *simplifier) visit(node Node) bool {
case *BinaryTest:
x.X = s.unquoteParams(x.X)
x.X = s.removeNegateTest(x.X)
if x.Op == TsMatchShort {
s.modified = true
x.Op = TsMatch
}
switch x.Op {
case TsMatch, TsNoMatch:
// unquoting enables globbing

View File

@@ -0,0 +1,149 @@
// Code generated by "stringer -type token -linecomment -trimprefix _"; DO NOT EDIT.
package syntax
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[illegalTok-0]
_ = x[_EOF-1]
_ = x[_Newl-2]
_ = x[_Lit-3]
_ = x[_LitWord-4]
_ = x[_LitRedir-5]
_ = x[sglQuote-6]
_ = x[dblQuote-7]
_ = x[bckQuote-8]
_ = x[and-9]
_ = x[andAnd-10]
_ = x[orOr-11]
_ = x[or-12]
_ = x[orAnd-13]
_ = x[dollar-14]
_ = x[dollSglQuote-15]
_ = x[dollDblQuote-16]
_ = x[dollBrace-17]
_ = x[dollBrack-18]
_ = x[dollParen-19]
_ = x[dollDblParen-20]
_ = x[leftBrack-21]
_ = x[dblLeftBrack-22]
_ = x[leftParen-23]
_ = x[dblLeftParen-24]
_ = x[rightBrace-25]
_ = x[rightBrack-26]
_ = x[rightParen-27]
_ = x[dblRightParen-28]
_ = x[semicolon-29]
_ = x[dblSemicolon-30]
_ = x[semiAnd-31]
_ = x[dblSemiAnd-32]
_ = x[semiOr-33]
_ = x[exclMark-34]
_ = x[tilde-35]
_ = x[addAdd-36]
_ = x[subSub-37]
_ = x[star-38]
_ = x[power-39]
_ = x[equal-40]
_ = x[nequal-41]
_ = x[lequal-42]
_ = x[gequal-43]
_ = x[addAssgn-44]
_ = x[subAssgn-45]
_ = x[mulAssgn-46]
_ = x[quoAssgn-47]
_ = x[remAssgn-48]
_ = x[andAssgn-49]
_ = x[orAssgn-50]
_ = x[xorAssgn-51]
_ = x[shlAssgn-52]
_ = x[shrAssgn-53]
_ = x[rdrOut-54]
_ = x[appOut-55]
_ = x[rdrIn-56]
_ = x[rdrInOut-57]
_ = x[dplIn-58]
_ = x[dplOut-59]
_ = x[clbOut-60]
_ = x[hdoc-61]
_ = x[dashHdoc-62]
_ = x[wordHdoc-63]
_ = x[rdrAll-64]
_ = x[appAll-65]
_ = x[cmdIn-66]
_ = x[cmdOut-67]
_ = x[plus-68]
_ = x[colPlus-69]
_ = x[minus-70]
_ = x[colMinus-71]
_ = x[quest-72]
_ = x[colQuest-73]
_ = x[assgn-74]
_ = x[colAssgn-75]
_ = x[perc-76]
_ = x[dblPerc-77]
_ = x[hash-78]
_ = x[dblHash-79]
_ = x[caret-80]
_ = x[dblCaret-81]
_ = x[comma-82]
_ = x[dblComma-83]
_ = x[at-84]
_ = x[slash-85]
_ = x[dblSlash-86]
_ = x[colon-87]
_ = x[tsExists-88]
_ = x[tsRegFile-89]
_ = x[tsDirect-90]
_ = x[tsCharSp-91]
_ = x[tsBlckSp-92]
_ = x[tsNmPipe-93]
_ = x[tsSocket-94]
_ = x[tsSmbLink-95]
_ = x[tsSticky-96]
_ = x[tsGIDSet-97]
_ = x[tsUIDSet-98]
_ = x[tsGrpOwn-99]
_ = x[tsUsrOwn-100]
_ = x[tsModif-101]
_ = x[tsRead-102]
_ = x[tsWrite-103]
_ = x[tsExec-104]
_ = x[tsNoEmpty-105]
_ = x[tsFdTerm-106]
_ = x[tsEmpStr-107]
_ = x[tsNempStr-108]
_ = x[tsOptSet-109]
_ = x[tsVarSet-110]
_ = x[tsRefVar-111]
_ = x[tsReMatch-112]
_ = x[tsNewer-113]
_ = x[tsOlder-114]
_ = x[tsDevIno-115]
_ = x[tsEql-116]
_ = x[tsNeq-117]
_ = x[tsLeq-118]
_ = x[tsGeq-119]
_ = x[tsLss-120]
_ = x[tsGtr-121]
_ = x[globQuest-122]
_ = x[globStar-123]
_ = x[globPlus-124]
_ = x[globAt-125]
_ = x[globExcl-126]
}
const _token_name = "illegalTokEOFNewlLitLitWordLitRedir'\"`&&&||||&$$'$\"${$[$($(([[[(((}])));;;;&;;&;|!~++--***==!=<=>=+=-=*=/=%=&=|=^=<<=>>=>>><<><&>&>|<<<<-<<<&>&>><(>(+:+-:-?:?=:=%%%###^^^,,,@///:-e-f-d-c-b-p-S-L-k-g-u-G-O-N-r-w-x-s-t-z-n-o-v-R=~-nt-ot-ef-eq-ne-le-ge-lt-gt?(*(+(@(!("
var _token_index = [...]uint16{0, 10, 13, 17, 20, 27, 35, 36, 37, 38, 39, 41, 43, 44, 46, 47, 49, 51, 53, 55, 57, 60, 61, 63, 64, 66, 67, 68, 69, 71, 72, 74, 76, 79, 81, 82, 83, 85, 87, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 117, 120, 121, 123, 124, 126, 128, 130, 132, 134, 137, 140, 142, 145, 147, 149, 150, 152, 153, 155, 156, 158, 159, 161, 162, 164, 165, 167, 168, 170, 171, 173, 174, 175, 177, 178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 228, 231, 234, 237, 240, 243, 246, 249, 252, 255, 257, 259, 261, 263, 265}
func (i token) String() string {
if i >= token(len(_token_index)-1) {
return "token(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _token_name[_token_index[i]:_token_index[i+1]]
}

349
vendor/mvdan.cc/sh/v3/syntax/tokens.go vendored Normal file
View File

@@ -0,0 +1,349 @@
// Copyright (c) 2016, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
//go:generate stringer -type token -linecomment -trimprefix _
type token uint32
// The list of all possible tokens.
const (
illegalTok token = iota
_EOF
_Newl
_Lit
_LitWord
_LitRedir
sglQuote // '
dblQuote // "
bckQuote // `
and // &
andAnd // &&
orOr // ||
or // |
orAnd // |&
dollar // $
dollSglQuote // $'
dollDblQuote // $"
dollBrace // ${
dollBrack // $[
dollParen // $(
dollDblParen // $((
leftBrack // [
dblLeftBrack // [[
leftParen // (
dblLeftParen // ((
rightBrace // }
rightBrack // ]
rightParen // )
dblRightParen // ))
semicolon // ;
dblSemicolon // ;;
semiAnd // ;&
dblSemiAnd // ;;&
semiOr // ;|
exclMark // !
tilde // ~
addAdd // ++
subSub // --
star // *
power // **
equal // ==
nequal // !=
lequal // <=
gequal // >=
addAssgn // +=
subAssgn // -=
mulAssgn // *=
quoAssgn // /=
remAssgn // %=
andAssgn // &=
orAssgn // |=
xorAssgn // ^=
shlAssgn // <<=
shrAssgn // >>=
rdrOut // >
appOut // >>
rdrIn // <
rdrInOut // <>
dplIn // <&
dplOut // >&
clbOut // >|
hdoc // <<
dashHdoc // <<-
wordHdoc // <<<
rdrAll // &>
appAll // &>>
cmdIn // <(
cmdOut // >(
plus // +
colPlus // :+
minus // -
colMinus // :-
quest // ?
colQuest // :?
assgn // =
colAssgn // :=
perc // %
dblPerc // %%
hash // #
dblHash // ##
caret // ^
dblCaret // ^^
comma // ,
dblComma // ,,
at // @
slash // /
dblSlash // //
colon // :
tsExists // -e
tsRegFile // -f
tsDirect // -d
tsCharSp // -c
tsBlckSp // -b
tsNmPipe // -p
tsSocket // -S
tsSmbLink // -L
tsSticky // -k
tsGIDSet // -g
tsUIDSet // -u
tsGrpOwn // -G
tsUsrOwn // -O
tsModif // -N
tsRead // -r
tsWrite // -w
tsExec // -x
tsNoEmpty // -s
tsFdTerm // -t
tsEmpStr // -z
tsNempStr // -n
tsOptSet // -o
tsVarSet // -v
tsRefVar // -R
tsReMatch // =~
tsNewer // -nt
tsOlder // -ot
tsDevIno // -ef
tsEql // -eq
tsNeq // -ne
tsLeq // -le
tsGeq // -ge
tsLss // -lt
tsGtr // -gt
globQuest // ?(
globStar // *(
globPlus // +(
globAt // @(
globExcl // !(
)
type RedirOperator token
const (
RdrOut = RedirOperator(rdrOut) + iota // >
AppOut // >>
RdrIn // <
RdrInOut // <>
DplIn // <&
DplOut // >&
ClbOut // >|
Hdoc // <<
DashHdoc // <<-
WordHdoc // <<<
RdrAll // &>
AppAll // &>>
)
type ProcOperator token
const (
CmdIn = ProcOperator(cmdIn) + iota // <(
CmdOut // >(
)
type GlobOperator token
const (
GlobZeroOrOne = GlobOperator(globQuest) + iota // ?(
GlobZeroOrMore // *(
GlobOneOrMore // +(
GlobOne // @(
GlobExcept // !(
)
type BinCmdOperator token
const (
AndStmt = BinCmdOperator(andAnd) + iota // &&
OrStmt // ||
Pipe // |
PipeAll // |&
)
type CaseOperator token
const (
Break = CaseOperator(dblSemicolon) + iota // ;;
Fallthrough // ;&
Resume // ;;&
ResumeKorn // ;|
)
type ParNamesOperator token
const (
NamesPrefix = ParNamesOperator(star) // *
NamesPrefixWords = ParNamesOperator(at) // @
)
type ParExpOperator token
const (
AlternateUnset = ParExpOperator(plus) + iota // +
AlternateUnsetOrNull // :+
DefaultUnset // -
DefaultUnsetOrNull // :-
ErrorUnset // ?
ErrorUnsetOrNull // :?
AssignUnset // =
AssignUnsetOrNull // :=
RemSmallSuffix // %
RemLargeSuffix // %%
RemSmallPrefix // #
RemLargePrefix // ##
UpperFirst // ^
UpperAll // ^^
LowerFirst // ,
LowerAll // ,,
OtherParamOps // @
)
type UnAritOperator token
const (
Not = UnAritOperator(exclMark) + iota // !
BitNegation // ~
Inc // ++
Dec // --
Plus = UnAritOperator(plus) // +
Minus = UnAritOperator(minus) // -
)
type BinAritOperator token
const (
Add = BinAritOperator(plus) // +
Sub = BinAritOperator(minus) // -
Mul = BinAritOperator(star) // *
Quo = BinAritOperator(slash) // /
Rem = BinAritOperator(perc) // %
Pow = BinAritOperator(power) // **
Eql = BinAritOperator(equal) // ==
Gtr = BinAritOperator(rdrOut) // >
Lss = BinAritOperator(rdrIn) // <
Neq = BinAritOperator(nequal) // !=
Leq = BinAritOperator(lequal) // <=
Geq = BinAritOperator(gequal) // >=
And = BinAritOperator(and) // &
Or = BinAritOperator(or) // |
Xor = BinAritOperator(caret) // ^
Shr = BinAritOperator(appOut) // >>
Shl = BinAritOperator(hdoc) // <<
AndArit = BinAritOperator(andAnd) // &&
OrArit = BinAritOperator(orOr) // ||
Comma = BinAritOperator(comma) // ,
TernQuest = BinAritOperator(quest) // ?
TernColon = BinAritOperator(colon) // :
Assgn = BinAritOperator(assgn) // =
AddAssgn = BinAritOperator(addAssgn) // +=
SubAssgn = BinAritOperator(subAssgn) // -=
MulAssgn = BinAritOperator(mulAssgn) // *=
QuoAssgn = BinAritOperator(quoAssgn) // /=
RemAssgn = BinAritOperator(remAssgn) // %=
AndAssgn = BinAritOperator(andAssgn) // &=
OrAssgn = BinAritOperator(orAssgn) // |=
XorAssgn = BinAritOperator(xorAssgn) // ^=
ShlAssgn = BinAritOperator(shlAssgn) // <<=
ShrAssgn = BinAritOperator(shrAssgn) // >>=
)
type UnTestOperator token
const (
TsExists = UnTestOperator(tsExists) + iota // -e
TsRegFile // -f
TsDirect // -d
TsCharSp // -c
TsBlckSp // -b
TsNmPipe // -p
TsSocket // -S
TsSmbLink // -L
TsSticky // -k
TsGIDSet // -g
TsUIDSet // -u
TsGrpOwn // -G
TsUsrOwn // -O
TsModif // -N
TsRead // -r
TsWrite // -w
TsExec // -x
TsNoEmpty // -s
TsFdTerm // -t
TsEmpStr // -z
TsNempStr // -n
TsOptSet // -o
TsVarSet // -v
TsRefVar // -R
TsNot = UnTestOperator(exclMark) // !
)
type BinTestOperator token
const (
TsReMatch = BinTestOperator(tsReMatch) + iota // =~
TsNewer // -nt
TsOlder // -ot
TsDevIno // -ef
TsEql // -eq
TsNeq // -ne
TsLeq // -le
TsGeq // -ge
TsLss // -lt
TsGtr // -gt
AndTest = BinTestOperator(andAnd) // &&
OrTest = BinTestOperator(orOr) // ||
TsMatchShort = BinTestOperator(assgn) // =
TsMatch = BinTestOperator(equal) // ==
TsNoMatch = BinTestOperator(nequal) // !=
TsBefore = BinTestOperator(rdrIn) // <
TsAfter = BinTestOperator(rdrOut) // >
)
func (o RedirOperator) String() string { return token(o).String() }
func (o ProcOperator) String() string { return token(o).String() }
func (o GlobOperator) String() string { return token(o).String() }
func (o BinCmdOperator) String() string { return token(o).String() }
func (o CaseOperator) String() string { return token(o).String() }
func (o ParNamesOperator) String() string { return token(o).String() }
func (o ParExpOperator) String() string { return token(o).String() }
func (o UnAritOperator) String() string { return token(o).String() }
func (o BinAritOperator) String() string { return token(o).String() }
func (o UnTestOperator) String() string { return token(o).String() }
func (o BinTestOperator) String() string { return token(o).String() }

View File

@@ -9,11 +9,11 @@ import (
"reflect"
)
func walkStmts(sl StmtList, f func(Node) bool) {
for _, s := range sl.Stmts {
func walkStmts(stmts []*Stmt, last []Comment, f func(Node) bool) {
for _, s := range stmts {
Walk(s, f)
}
for _, c := range sl.Last {
for _, c := range last {
Walk(&c, f)
}
}
@@ -35,7 +35,7 @@ func Walk(node Node, f func(Node) bool) {
switch x := node.(type) {
case *File:
walkStmts(x.StmtList, f)
walkStmts(x.Stmts, x.Last, f)
case *Comment:
case *Stmt:
for _, c := range x.Comments {
@@ -78,19 +78,21 @@ func Walk(node Node, f func(Node) bool) {
}
walkWords(x.Args, f)
case *Subshell:
walkStmts(x.StmtList, f)
walkStmts(x.Stmts, x.Last, f)
case *Block:
walkStmts(x.StmtList, f)
walkStmts(x.Stmts, x.Last, f)
case *IfClause:
walkStmts(x.Cond, f)
walkStmts(x.Then, f)
walkStmts(x.Else, f)
walkStmts(x.Cond, x.CondLast, f)
walkStmts(x.Then, x.ThenLast, f)
if x.Else != nil {
Walk(x.Else, f)
}
case *WhileClause:
walkStmts(x.Cond, f)
walkStmts(x.Do, f)
walkStmts(x.Cond, x.CondLast, f)
walkStmts(x.Do, x.DoLast, f)
case *ForClause:
Walk(x.Loop, f)
walkStmts(x.Do, f)
walkStmts(x.Do, x.DoLast, f)
case *WordIter:
Walk(x.Name, f)
walkWords(x.Items, f)
@@ -121,7 +123,7 @@ func Walk(node Node, f func(Node) bool) {
Walk(wp, f)
}
case *CmdSubst:
walkStmts(x.StmtList, f)
walkStmts(x.Stmts, x.Last, f)
case *ParamExp:
Walk(x.Param, f)
if x.Index != nil {
@@ -173,12 +175,11 @@ func Walk(node Node, f func(Node) bool) {
Walk(&c, f)
}
walkWords(x.Patterns, f)
walkStmts(x.StmtList, f)
walkStmts(x.Stmts, x.Last, f)
case *TestClause:
Walk(x.X, f)
case *DeclClause:
walkWords(x.Opts, f)
for _, a := range x.Assigns {
for _, a := range x.Args {
Walk(a, f)
}
case *ArrayExpr:
@@ -205,7 +206,7 @@ func Walk(node Node, f func(Node) bool) {
case *ExtGlob:
Walk(x.Pattern, f)
case *ProcSubst:
walkStmts(x.StmtList, f)
walkStmts(x.Stmts, x.Last, f)
case *TimeClause:
if x.Stmt != nil {
Walk(x.Stmt, f)