github.com/hattya/go.sh@v0.0.0-20240328132134-f53276d95cc6/interp/expand_test.go (about)

     1  //
     2  // go.sh/interp :: expand_test.go
     3  //
     4  //   Copyright (c) 2021 Akinori Hattori <hattya@gmail.com>
     5  //
     6  //   SPDX-License-Identifier: MIT
     7  //
     8  
     9  package interp_test
    10  
    11  import (
    12  	"fmt"
    13  	"os"
    14  	"os/user"
    15  	"path/filepath"
    16  	"reflect"
    17  	"runtime"
    18  	"strconv"
    19  	"strings"
    20  	"testing"
    21  
    22  	"github.com/hattya/go.sh/ast"
    23  	"github.com/hattya/go.sh/interp"
    24  	"github.com/hattya/go.sh/printer"
    25  )
    26  
    27  const (
    28  	sep = string(os.PathListSeparator)
    29  	V   = "value"
    30  	E   = ""
    31  	P   = "foo/bar/baz"
    32  )
    33  
    34  var expandTests = []struct {
    35  	word   ast.Word
    36  	mode   interp.ExpMode
    37  	fields []string
    38  }{
    39  	{word(), 0, nil},
    40  	{word(), interp.Quote, []string{""}},
    41  	{word(quote(`'`, word())), 0, []string{""}},
    42  	{word(quote(`"`, word())), 0, []string{""}},
    43  
    44  	{word(lit("foo")), 0, []string{"foo"}},
    45  	{word(quote(`\`, word(lit("f"))), lit("oo")), 0, []string{"foo"}},
    46  	{word(quote(`'`, word(lit("foo")))), 0, []string{"foo"}},
    47  	{word(quote(`"`, word(lit("foo")))), 0, []string{"foo"}},
    48  	{word(lit("foo"), lit("bar")), 0, []string{"foobar"}},
    49  
    50  	{word(quote(`\`, word(lit("*")))), 0, []string{`*`}},
    51  	{word(quote(`'`, word(lit("*")))), 0, []string{`*`}},
    52  	{word(quote(`"`, word(lit("*")))), 0, []string{`*`}},
    53  
    54  	{word(quote(`\`, word(lit("*")))), interp.Literal, []string{`*`}},
    55  	{word(quote(`'`, word(lit("*")))), interp.Literal, []string{`*`}},
    56  	{word(quote(`"`, word(lit("*")))), interp.Literal, []string{`*`}},
    57  
    58  	{word(quote(`\`, word(lit("*")))), interp.Pattern, []string{`\*`}},
    59  	{word(quote(`'`, word(lit("*")))), interp.Pattern, []string{`\*`}},
    60  	{word(quote(`"`, word(lit("*")))), interp.Pattern, []string{`\*`}},
    61  
    62  	{word(paramExp(lit("E"), "", nil), lit(" * 1")), interp.Arith, []string{"E * 1"}},
    63  	{word(quote(`"`, word(paramExp(lit("E"), "", nil))), lit(" * 1")), interp.Arith, []string{"E * 1"}},
    64  }
    65  
    66  var tildeExpTests = []struct {
    67  	word   ast.Word
    68  	mode   interp.ExpMode
    69  	fields []string
    70  }{
    71  	{word(lit("~")), 0, []string{homeDir()}},
    72  	{word(lit("~/")), 0, []string{homeDir() + "/"}},
    73  	{word(lit("~"), lit("/")), 0, []string{homeDir() + "/"}},
    74  	{word(lit("~"), quote(`\`, word(lit(`/`)))), 0, []string{"~/"}},
    75  	{word(lit("~"), quote(`\`, word(lit(`\`)))), 0, []string{homeDir() + `\`}},
    76  
    77  	{word(lit("~" + username())), 0, []string{homeDir()}},
    78  	{word(lit("~" + username() + "/")), 0, []string{homeDir() + "/"}},
    79  	{word(lit("~"), lit(username()), lit("/")), 0, []string{homeDir() + "/"}},
    80  	{word(lit("~"+username()), quote(`\`, word(lit(`/`)))), 0, []string{"~" + username() + "/"}},
    81  	{word(lit("~"+username()), quote(`\`, word(lit(`\`)))), 0, []string{homeDir() + `\`}},
    82  
    83  	{word(lit("~_")), 0, []string{"~_"}},
    84  	{word(lit("~_/")), 0, []string{"~_/"}},
    85  	{word(lit("~"), lit("_"), lit("/")), 0, []string{"~_/"}},
    86  	{word(lit("~_"), quote(`\`, word(lit("/")))), 0, []string{"~_/"}},
    87  	{word(lit("~_"), quote(`\`, word(lit(`\`)))), 0, []string{`~_\`}},
    88  
    89  	{word(lit(sep)), interp.Assign, []string{sep}},
    90  	{word(litf("~/foo%v~/bar", sep)), interp.Assign, []string{fmt.Sprintf("%v/foo%v%[1]v/bar", homeDir(), sep)}},
    91  	{word(lit("~/foo"), lit(sep), lit("~/bar")), interp.Assign, []string{fmt.Sprintf("%v/foo%v%[1]v/bar", homeDir(), sep)}},
    92  	{word(lit("~"), lit("/foo"), lit(sep), lit("~"), lit("/bar")), interp.Assign, []string{fmt.Sprintf("%v/foo%v%[1]v/bar", homeDir(), sep)}},
    93  	{word(lit("~"), quote(`\`, word(lit(`/`))), litf("foo%v~", sep), quote(`\`, word(lit(`/`))), lit("bar")), interp.Assign, []string{fmt.Sprintf("~/foo%v~/bar", sep)}},
    94  	{word(lit("~"), quote(`\`, word(lit(`\`))), litf("foo%v~", sep), quote(`\`, word(lit(`/`))), lit("bar")), interp.Assign, []string{fmt.Sprintf(`%v\foo%v~/bar`, homeDir(), sep)}},
    95  	{word(lit("~"), quote(`\`, word(lit(`/`))), litf("foo%v~", sep), quote(`\`, word(lit(`\`))), lit("bar")), interp.Assign, []string{fmt.Sprintf(`~/foo%v%v\bar`, sep, homeDir())}},
    96  	{word(lit("~"), quote(`\`, word(lit(`\`))), litf("foo%v~", sep), quote(`\`, word(lit(`\`))), lit("bar")), interp.Assign, []string{fmt.Sprintf(`%v\foo%v%[1]v\bar`, homeDir(), sep)}},
    97  
    98  	{word(lit("~"), paramExp(lit("_"), "", nil), lit("/")), 0, []string{"~/"}},
    99  	{word(lit("~/")), interp.Quote, []string{"~/"}},
   100  	{word(quote(`"`, word(lit("~/")))), 0, []string{"~/"}},
   101  
   102  	{word(paramExp(lit("_"), ":-", word(litf("~/foo%v~/bar", sep)))), interp.Assign, []string{fmt.Sprintf("%v/foo%v%[1]v/bar", homeDir(), sep)}},
   103  	{word(paramExp(lit("E"), "+", word(litf("~/foo%v~/bar", sep)))), interp.Assign, []string{fmt.Sprintf("%v/foo%v%[1]v/bar", homeDir(), sep)}},
   104  }
   105  
   106  var paramExpTests = []struct {
   107  	word   ast.Word
   108  	fields []string
   109  	err    string
   110  	assign bool
   111  }{
   112  	// simplest form
   113  	{word(paramExp(lit("V"), "", nil)), []string{V}, "", false},
   114  	// use default values
   115  	{word(paramExp(lit("V"), ":-", word(lit("...")))), []string{V}, "", false},
   116  	{word(paramExp(lit("V"), "-", word(lit("...")))), []string{V}, "", false},
   117  	{word(paramExp(lit("E"), ":-", word(lit("...")))), []string{"..."}, "", false},
   118  	{word(paramExp(lit("E"), "-", word(lit("...")))), []string{""}, "", false},
   119  	{word(paramExp(lit("E"), ":-", word())), []string{""}, "", false},
   120  	{word(paramExp(lit("_"), ":-", word(quote(`'`, word(lit("...")))))), []string{"..."}, "", false},
   121  	{word(paramExp(lit("_"), "-", word(quote(`'`, word(lit("...")))))), []string{"..."}, "", false},
   122  	{word(paramExp(lit("_"), ":-", word())), []string{""}, "", false},
   123  	{word(paramExp(lit("_"), "-", word())), []string{""}, "", false},
   124  
   125  	{word(paramExp(lit("_"), ":-", word(quote(`"`, word(paramExp(lit("1"), ":=", word(lit("...")))))))), nil, "$1: cannot assign ", false},
   126  	{word(paramExp(lit("_"), "-", word(quote(`"`, word(paramExp(lit("1"), "=", word(lit("...")))))))), nil, "$1: cannot assign ", false},
   127  	// assign default values
   128  	{word(paramExp(lit("V"), ":=", word(lit("...")))), []string{V}, "", false},
   129  	{word(paramExp(lit("V"), "=", word(lit("...")))), []string{V}, "", false},
   130  	{word(paramExp(lit("E"), ":=", word(lit("...")))), []string{"..."}, "", true},
   131  	{word(paramExp(lit("E"), "=", word(lit("...")))), []string{""}, "", false},
   132  	{word(paramExp(lit("E"), ":=", word())), []string{""}, "", true},
   133  	{word(paramExp(lit("_"), ":=", word(lit("...")))), []string{"..."}, "", true},
   134  	{word(paramExp(lit("_"), "=", word(lit("...")))), []string{"..."}, "", true},
   135  	{word(paramExp(lit("_"), ":=", word())), []string{""}, "", true},
   136  	{word(paramExp(lit("_"), "=", word())), []string{""}, "", true},
   137  
   138  	{word(paramExp(lit("1"), ":=", word(lit("...")))), nil, "$1: cannot assign ", false},
   139  	{word(paramExp(lit("1"), "=", word(lit("...")))), nil, "$1: cannot assign ", false},
   140  	{word(paramExp(lit("@"), ":=", word(lit("...")))), nil, "$@: cannot assign ", false},
   141  	{word(paramExp(lit("_"), ":=", word(quote(`"`, word(paramExp(lit("1"), ":=", word(lit("...")))))))), nil, "$1: cannot assign ", false},
   142  	{word(paramExp(lit("_"), "=", word(quote(`"`, word(paramExp(lit("1"), "=", word(lit("...")))))))), nil, "$1: cannot assign ", false},
   143  	// indicate error if unset or null
   144  	{word(paramExp(lit("V"), ":?", word(lit("...")))), []string{V}, "", false},
   145  	{word(paramExp(lit("V"), "?", word(lit("...")))), []string{V}, "", false},
   146  	{word(paramExp(lit("E"), ":?", word(lit("...")))), nil, "$E: ...", false},
   147  	{word(paramExp(lit("E"), "?", word(lit("...")))), []string{""}, "", false},
   148  	{word(paramExp(lit("_"), ":?", word(quote(`'`, word(lit("...")))))), nil, "$_: ...", false},
   149  	{word(paramExp(lit("_"), "?", word(quote(`'`, word(lit("...")))))), nil, "$_: ...", false},
   150  	{word(paramExp(lit("_"), ":?", word())), nil, "$_: parameter is unset or null", false},
   151  	{word(paramExp(lit("_"), "?", word())), nil, "$_: parameter is unset or null", false},
   152  
   153  	{word(paramExp(lit("_"), ":?", word(quote(`"`, word(paramExp(lit("1"), ":=", word(lit("...")))))))), nil, "$1: cannot assign ", false},
   154  	{word(paramExp(lit("_"), "?", word(quote(`"`, word(paramExp(lit("1"), "=", word(lit("...")))))))), nil, "$1: cannot assign ", false},
   155  	// use alternative values
   156  	{word(paramExp(lit("V"), ":+", word(lit("...")))), []string{"..."}, "", false},
   157  	{word(paramExp(lit("V"), "+", word(lit("...")))), []string{"..."}, "", false},
   158  	{word(paramExp(lit("E"), ":+", word(lit("...")))), []string{""}, "", false},
   159  	{word(paramExp(lit("E"), "+", word(lit("...")))), []string{"..."}, "", false},
   160  	{word(paramExp(lit("E"), "+", word())), []string{""}, "", false},
   161  	{word(paramExp(lit("_"), ":+", word(lit("...")))), []string{""}, "", false},
   162  	{word(paramExp(lit("_"), "+", word(lit("...")))), []string{""}, "", false},
   163  
   164  	{word(paramExp(lit("V"), ":+", word(quote(`"`, word(paramExp(lit("1"), ":=", word(lit("...")))))))), nil, "$1: cannot assign ", false},
   165  	{word(paramExp(lit("V"), "+", word(quote(`"`, word(paramExp(lit("1"), "=", word(lit("...")))))))), nil, "$1: cannot assign ", false},
   166  	// string length
   167  	{word(paramExp(lit("V"), "#", nil)), []string{strconv.Itoa(len(V))}, "", false},
   168  	{word(paramExp(lit("E"), "#", nil)), []string{"0"}, "", false},
   169  	{word(paramExp(lit("_"), "#", nil)), nil, "$_: parameter is unset", false},
   170  	// remove suffix pattern
   171  	{word(paramExp(lit("P"), "%", word(lit("/*")))), []string{"foo/bar"}, "", false},
   172  	{word(paramExp(lit("P"), "%%", word(lit("/*")))), []string{"foo"}, "", false},
   173  	{word(paramExp(lit("P"), "%", word(quote(`'`, word(lit("/*")))))), []string{"foo/bar/baz"}, "", false},
   174  	{word(paramExp(lit("P"), "%%", word(quote(`'`, word(lit("/*")))))), []string{"foo/bar/baz"}, "", false},
   175  	{word(paramExp(lit("P"), "%", word())), []string{"foo/bar/baz"}, "", false},
   176  	{word(paramExp(lit("P"), "%%", word())), []string{"foo/bar/baz"}, "", false},
   177  	{word(paramExp(lit("_"), "%", word())), nil, "$_: parameter is unset", false},
   178  	{word(paramExp(lit("_"), "%%", word())), nil, "$_: parameter is unset", false},
   179  
   180  	{word(paramExp(lit("V"), "%", word(quote(`"`, word(paramExp(lit("1"), "=", word(lit("...")))))))), nil, "$1: cannot assign ", false},
   181  	{word(paramExp(lit("V"), "%%", word(quote(`"`, word(paramExp(lit("1"), ":=", word(lit("...")))))))), nil, "$1: cannot assign ", false},
   182  	{word(paramExp(lit("V"), "%", word(lit("\xff")))), nil, "regexp: invalid UTF-8", false},
   183  	{word(paramExp(lit("V"), "%%", word(lit("\xff")))), nil, "regexp: invalid UTF-8", false},
   184  	// remove prefix pattern
   185  	{word(paramExp(lit("P"), "#", word(lit("*/")))), []string{"bar/baz"}, "", false},
   186  	{word(paramExp(lit("P"), "##", word(lit("*/")))), []string{"baz"}, "", false},
   187  	{word(paramExp(lit("P"), "#", word(quote(`'`, word(lit("*/")))))), []string{"foo/bar/baz"}, "", false},
   188  	{word(paramExp(lit("P"), "##", word(quote(`'`, word(lit("*/")))))), []string{"foo/bar/baz"}, "", false},
   189  	{word(paramExp(lit("P"), "#", word())), []string{"foo/bar/baz"}, "", false},
   190  	{word(paramExp(lit("P"), "##", word())), []string{"foo/bar/baz"}, "", false},
   191  	{word(paramExp(lit("_"), "#", word())), nil, "$_: parameter is unset", false},
   192  	{word(paramExp(lit("_"), "##", word())), nil, "$_: parameter is unset", false},
   193  
   194  	{word(paramExp(lit("V"), "#", word(quote(`"`, word(paramExp(lit("1"), "=", word(lit("...")))))))), nil, "$1: cannot assign ", false},
   195  	{word(paramExp(lit("V"), "##", word(quote(`"`, word(paramExp(lit("1"), ":=", word(lit("...")))))))), nil, "$1: cannot assign ", false},
   196  	{word(paramExp(lit("V"), "#", word(lit("\xff")))), nil, "regexp: invalid UTF-8", false},
   197  	{word(paramExp(lit("V"), "##", word(lit("\xff")))), nil, "regexp: invalid UTF-8", false},
   198  }
   199  
   200  var spParamTests = []struct {
   201  	word   ast.Word
   202  	mode   interp.ExpMode
   203  	args   []string
   204  	ifs    interface{}
   205  	fields []string
   206  	err    string
   207  	assign string
   208  }{
   209  	// simplest form
   210  	{word(paramExp(lit("@"), "", nil)), 0, nil, nil, nil, "", ""},
   211  	{word(paramExp(lit("@"), "", nil)), 0, []string{""}, nil, nil, "", ""},
   212  	{word(quote(`"`, word(paramExp(lit("@"), "", nil)))), 0, []string{""}, nil, []string{""}, "", ""},
   213  	{word(paramExp(lit("@"), "", nil)), 0, []string{"1"}, nil, []string{"1"}, "", ""},
   214  	{word(paramExp(lit("@"), "", nil)), 0, []string{"1", "2"}, nil, []string{"1", "2"}, "", ""},
   215  	{word(paramExp(lit("@"), "", nil)), 0, []string{"", "2", ""}, nil, []string{"2"}, "", ""},
   216  	{word(quote(`"`, word(paramExp(lit("@"), "", nil)))), 0, []string{"", "2", ""}, nil, []string{"", "2", ""}, "", ""},
   217  
   218  	{word(paramExp(lit("*"), "", nil)), 0, nil, nil, nil, "", ""},
   219  	{word(paramExp(lit("*"), "", nil)), 0, []string{""}, nil, nil, "", ""},
   220  	{word(quote(`"`, word(paramExp(lit("*"), "", nil)))), 0, []string{""}, nil, []string{""}, "", ""},
   221  	{word(paramExp(lit("*"), "", nil)), 0, []string{"1"}, nil, []string{"1"}, "", ""},
   222  	{word(paramExp(lit("*"), "", nil)), 0, []string{"1", "2"}, nil, []string{"1", "2"}, "", ""},
   223  	{word(paramExp(lit("*"), "", nil)), 0, []string{"", "2", ""}, nil, []string{"2"}, "", ""},
   224  	{word(quote(`"`, word(paramExp(lit("*"), "", nil)))), 0, []string{"", "2", ""}, nil, []string{" 2 "}, "", ""},
   225  	{word(quote(`"`, word(paramExp(lit("*"), "", nil)))), 0, []string{"", "2", ""}, ", \t\n", []string{",2,"}, "", ""},
   226  	{word(quote(`"`, word(paramExp(lit("*"), "", nil)))), 0, []string{"", "2", ""}, "", []string{"2"}, "", ""},
   227  
   228  	{word(paramExp(lit("@"), "", nil)), interp.Literal, []string{"*", "?"}, nil, []string{"* ?"}, "", ""},
   229  	{word(paramExp(lit("@"), "", nil)), interp.Literal, []string{"*", "?"}, ", \t\n", []string{"*,?"}, "", ""},
   230  	{word(paramExp(lit("@"), "", nil)), interp.Literal, []string{"*", "?"}, "", []string{"*?"}, "", ""},
   231  	{word(quote(`"`, word(paramExp(lit("@"), "", nil)))), interp.Literal, []string{"*", "?"}, nil, []string{"* ?"}, "", ""},
   232  	{word(quote(`"`, word(paramExp(lit("@"), "", nil)))), interp.Literal, []string{"*", "?"}, ", \t\n", []string{"*,?"}, "", ""},
   233  	{word(quote(`"`, word(paramExp(lit("@"), "", nil)))), interp.Literal, []string{"*", "?"}, "", []string{"*?"}, "", ""},
   234  
   235  	{word(paramExp(lit("@"), "", nil)), interp.Pattern, []string{"*", "?"}, nil, []string{"* ?"}, "", ""},
   236  	{word(paramExp(lit("@"), "", nil)), interp.Pattern, []string{"*", "?"}, ", \t\n", []string{"*,?"}, "", ""},
   237  	{word(paramExp(lit("@"), "", nil)), interp.Pattern, []string{"*", "?"}, "", []string{"*?"}, "", ""},
   238  	{word(quote(`"`, word(paramExp(lit("@"), "", nil)))), interp.Pattern, []string{"*", "?"}, nil, []string{`\* \?`}, "", ""},
   239  	{word(quote(`"`, word(paramExp(lit("@"), "", nil)))), interp.Pattern, []string{"*", "?"}, ", \t\n", []string{`\*,\?`}, "", ""},
   240  	{word(quote(`"`, word(paramExp(lit("@"), "", nil)))), interp.Pattern, []string{"*", "?"}, "", []string{`\*\?`}, "", ""},
   241  	// use default values
   242  	{word(paramExp(lit("@"), ":-", word(lit("...")))), 0, []string{"", "2", ""}, nil, []string{"2"}, "", ""},
   243  	{word(paramExp(lit("_"), ":-", word(paramExp(lit("@"), "", nil)))), 0, []string{"", "2", ""}, nil, []string{"2"}, "", ""},
   244  	{word(paramExp(lit("_"), ":-", word(quote(`"`, word(paramExp(lit("@"), "", nil)))))), 0, []string{"", "2", ""}, nil, []string{"", "2", ""}, "", ""},
   245  	{word(quote(`"`, word(paramExp(lit("_"), ":-", word(paramExp(lit("@"), "", nil)))))), 0, []string{"", "2", ""}, nil, []string{"", "2", ""}, "", ""},
   246  	{word(quote(`"`, word(paramExp(lit("_"), ":-", word(quote(`"`, word(paramExp(lit("@"), "", nil)))))))), 0, []string{"", "2", ""}, nil, []string{"", "2", ""}, "", ""},
   247  	// assign default values
   248  	{word(paramExp(lit("@"), ":=", word(lit("...")))), 0, []string{"", "2", ""}, nil, []string{"2"}, "", ""},
   249  	{word(paramExp(lit("_"), ":=", word(paramExp(lit("@"), "", nil)))), 0, []string{"", "2", ""}, nil, []string{"2"}, "", " 2 "},
   250  	{word(paramExp(lit("_"), ":=", word(paramExp(lit("@"), "", nil)))), 0, []string{"", "2", ""}, ", \n\t", []string{"2"}, "", ",2,"},
   251  	{word(paramExp(lit("_"), ":=", word(paramExp(lit("@"), "", nil)))), 0, []string{"", "2", ""}, "", []string{"2"}, "", "2"},
   252  	{word(paramExp(lit("_"), ":=", word(quote(`"`, word(paramExp(lit("@"), "", nil)))))), 0, []string{"", "2", ""}, nil, []string{"", "2", ""}, "", " 2 "},
   253  	{word(quote(`"`, word(paramExp(lit("_"), ":=", word(paramExp(lit("@"), "", nil)))))), 0, []string{"", "2", ""}, nil, []string{"", "2", ""}, "", " 2 "},
   254  	{word(quote(`"`, word(paramExp(lit("_"), ":=", word(quote(`"`, word(paramExp(lit("@"), "", nil)))))))), 0, []string{"", "2", ""}, nil, []string{"", "2", ""}, "", " 2 "},
   255  	// indicate error if unset or null
   256  	{word(paramExp(lit("@"), ":?", word(lit("...")))), 0, []string{"", "2", ""}, nil, []string{"2"}, "", ""},
   257  	{word(paramExp(lit("_"), ":?", word(paramExp(lit("@"), "", nil)))), 0, []string{"", "2", ""}, nil, nil, "$_:  2 ", ""},
   258  	{word(paramExp(lit("_"), ":?", word(paramExp(lit("@"), "", nil)))), 0, []string{"", "2", ""}, ", \n\t", nil, "$_: ,2,", ""},
   259  	{word(paramExp(lit("_"), ":?", word(paramExp(lit("@"), "", nil)))), 0, []string{"", "2", ""}, "", nil, "$_: 2", ""},
   260  	{word(paramExp(lit("_"), ":?", word(quote(`"`, word(paramExp(lit("@"), "", nil)))))), 0, []string{"", "2", ""}, nil, nil, "$_:  2 ", ""},
   261  	{word(quote(`"`, word(paramExp(lit("_"), ":?", word(paramExp(lit("@"), "", nil)))))), 0, []string{"", "2", ""}, nil, nil, "$_:  2 ", ""},
   262  	{word(quote(`"`, word(paramExp(lit("_"), ":?", word(quote(`"`, word(paramExp(lit("@"), "", nil)))))))), 0, []string{"", "2", ""}, nil, nil, "$_:  2 ", ""},
   263  	// use alternative values
   264  	{word(paramExp(lit("@"), ":+", word(lit("...")))), 0, []string{"", "2", ""}, nil, []string{"..."}, "", ""},
   265  	{word(paramExp(lit("V"), ":+", word(paramExp(lit("@"), "", nil)))), 0, []string{"", "2", ""}, nil, []string{"2"}, "", ""},
   266  	{word(paramExp(lit("V"), ":+", word(quote(`"`, word(paramExp(lit("@"), "", nil)))))), 0, []string{"", "2", ""}, nil, []string{"", "2", ""}, "", ""},
   267  	{word(quote(`"`, word(paramExp(lit("V"), ":+", word(paramExp(lit("@"), "", nil)))))), 0, []string{"", "2", ""}, nil, []string{"", "2", ""}, "", ""},
   268  	{word(quote(`"`, word(paramExp(lit("V"), ":+", word(quote(`"`, word(paramExp(lit("@"), "", nil)))))))), 0, []string{"", "2", ""}, nil, []string{"", "2", ""}, "", ""},
   269  	// string length
   270  	{word(paramExp(lit("@"), "#", nil)), 0, nil, nil, []string{"0"}, "", ""},
   271  	{word(paramExp(lit("@"), "#", nil)), 0, []string{""}, nil, []string{"1"}, "", ""},
   272  	{word(paramExp(lit("@"), "#", nil)), 0, []string{"1"}, nil, []string{"1"}, "", ""},
   273  	{word(paramExp(lit("@"), "#", nil)), 0, []string{"1", "2"}, nil, []string{"2"}, "", ""},
   274  	// remove suffix pattern
   275  	{word(paramExp(lit("@"), "%", word(lit("/*")))), 0, []string{"foo/bar/baz", "qux/quux"}, nil, []string{"foo/bar", "qux"}, "", ""},
   276  	{word(paramExp(lit("@"), "%%", word(lit("/*")))), 0, []string{"foo/bar/baz", "qux/quux"}, nil, []string{"foo", "qux"}, "", ""},
   277  	{word(paramExp(lit("@"), "%", word())), 0, []string{"foo/bar/baz", "qux/quux"}, nil, []string{"foo/bar/baz", "qux/quux"}, "", ""},
   278  	{word(paramExp(lit("@"), "%%", word())), 0, []string{"foo/bar/baz", "qux/quux"}, nil, []string{"foo/bar/baz", "qux/quux"}, "", ""},
   279  
   280  	{word(paramExp(lit("P"), "%", word(paramExp(lit("@"), "", nil)))), 0, []string{"", "*"}, "/", []string{"foo", "bar"}, "", ""},
   281  	{word(paramExp(lit("P"), "%%", word(paramExp(lit("@"), "", nil)))), 0, []string{"", "*"}, "/", []string{"foo"}, "", ""},
   282  	{word(quote(`"`, word(paramExp(lit("P"), "%", word(paramExp(lit("@"), "", nil)))))), 0, []string{"", "*"}, "/", []string{"foo/bar"}, "", ""},
   283  	{word(quote(`"`, word(paramExp(lit("P"), "%%", word(paramExp(lit("@"), "", nil)))))), 0, []string{"", "*"}, "/", []string{"foo"}, "", ""},
   284  	// remove prefix pattern
   285  	{word(paramExp(lit("@"), "#", word(lit("*/")))), 0, []string{"foo/bar/baz", "qux/quux"}, nil, []string{"bar/baz", "quux"}, "", ""},
   286  	{word(paramExp(lit("@"), "##", word(lit("*/")))), 0, []string{"foo/bar/baz", "qux/quux"}, nil, []string{"baz", "quux"}, "", ""},
   287  	{word(paramExp(lit("@"), "#", word())), 0, []string{"foo/bar/baz", "qux/quux"}, nil, []string{"foo/bar/baz", "qux/quux"}, "", ""},
   288  	{word(paramExp(lit("@"), "##", word())), 0, []string{"foo/bar/baz", "qux/quux"}, nil, []string{"foo/bar/baz", "qux/quux"}, "", ""},
   289  
   290  	{word(paramExp(lit("P"), "#", word(paramExp(lit("@"), "", nil)))), 0, []string{"*", ""}, "/", []string{"bar", "baz"}, "", ""},
   291  	{word(paramExp(lit("P"), "##", word(paramExp(lit("@"), "", nil)))), 0, []string{"*", ""}, "/", []string{"baz"}, "", ""},
   292  	{word(quote(`"`, word(paramExp(lit("P"), "#", word(paramExp(lit("@"), "", nil)))))), 0, []string{"*", ""}, "/", []string{"bar/baz"}, "", ""},
   293  	{word(quote(`"`, word(paramExp(lit("P"), "##", word(paramExp(lit("@"), "", nil)))))), 0, []string{"*", ""}, "/", []string{"baz"}, "", ""},
   294  }
   295  
   296  var posParamTests = []struct {
   297  	word   ast.Word
   298  	args   []string
   299  	fields []string
   300  }{
   301  	{word(paramExp(lit("1"), "", nil)), []string{}, nil},
   302  	{word(paramExp(lit("1"), "", nil)), []string{""}, nil},
   303  	{word(quote(`"`, word(paramExp(lit("1"), "", nil)))), []string{""}, []string{""}},
   304  	{word(paramExp(lit("1"), "", nil)), []string{"1"}, []string{"1"}},
   305  	{word(paramExp(lit("1"), "", nil)), []string{"1", "2"}, []string{"1"}},
   306  
   307  	{word(paramExp(lit("01"), "", nil)), []string{}, nil},
   308  	{word(paramExp(lit("01"), "", nil)), []string{""}, nil},
   309  	{word(quote(`"`, word(paramExp(lit("01"), "", nil)))), []string{""}, []string{""}},
   310  	{word(paramExp(lit("01"), "", nil)), []string{"1"}, []string{"1"}},
   311  	{word(paramExp(lit("01"), "", nil)), []string{"1", "2"}, []string{"1"}},
   312  
   313  	{word(paramExp(lit("2"), "", nil)), []string{}, nil},
   314  	{word(paramExp(lit("2"), "", nil)), []string{"1"}, nil},
   315  	{word(paramExp(lit("2"), "", nil)), []string{"1", "2"}, []string{"2"}},
   316  	{word(paramExp(lit("2"), "", nil)), []string{"1", "2", "3"}, []string{"2"}},
   317  
   318  	{word(paramExp(lit("02"), "", nil)), []string{}, nil},
   319  	{word(paramExp(lit("02"), "", nil)), []string{"1"}, nil},
   320  	{word(paramExp(lit("02"), "", nil)), []string{"1", "2"}, []string{"2"}},
   321  	{word(paramExp(lit("02"), "", nil)), []string{"1", "2", "3"}, []string{"2"}},
   322  }
   323  
   324  var arithExpTests = []struct {
   325  	word   ast.Word
   326  	fields []string
   327  	err    string
   328  }{
   329  	{word(arithExp(word(lit("2 - 1")))), []string{"1"}, ""},
   330  	{word(quote(`"`, word(arithExp(word(lit("1 + 1")))))), []string{"2"}, ""},
   331  	{word(arithExp(word(quote(`"`, word(lit("6 / 2")))))), []string{"3"}, ""},
   332  	{word(arithExp(word(lit("1 << "), quote(`'`, word(lit("2")))))), []string{"4"}, ""},
   333  
   334  	{word(arithExp(word(lit("E")))), []string{"0"}, ""},
   335  	{word(arithExp(word(quote(`'`, word(lit("E")))))), []string{"0"}, ""},
   336  	{word(arithExp(word(lit("1 + E")))), []string{"1"}, ""},
   337  	{word(arithExp(word(lit("1 + "), quote(`'`, word(lit("E")))))), []string{"1"}, ""},
   338  	{word(arithExp(word(lit("++E + 1")))), []string{"2"}, ""},
   339  	{word(arithExp(word(lit("++"), quote(`'`, word(lit("E"))), lit(" + 1")))), []string{"2"}, ""},
   340  
   341  	{word(arithExp(word(paramExp(lit("E"), "", nil)))), []string{"0"}, ""},
   342  	{word(arithExp(word(quote(`"`, word(paramExp(lit("E"), "", nil)))))), []string{"0"}, ""},
   343  	{word(arithExp(word(lit("1 + "), paramExp(lit("E"), "", nil)))), []string{"1"}, ""},
   344  	{word(arithExp(word(lit("1 + "), quote(`"`, word(paramExp(lit("E"), "", nil)))))), []string{"1"}, ""},
   345  	{word(arithExp(word(lit("++"), paramExp(lit("E"), "", nil), lit(" + 1")))), []string{"2"}, ""},
   346  	{word(arithExp(word(lit("++"), quote(`"`, word(paramExp(lit("E"), "", nil))), lit(" + 1")))), []string{"2"}, ""},
   347  	{word(arithExp(word(paramExp(lit("E"), ":-", word(arithExp(word(lit("7 % 4")))))))), []string{"3"}, ""},
   348  
   349  	{word(arithExp(word(lit("1 * "), arithExp(word(lit("2 + 3")))))), []string{"5"}, ""},
   350  	{word(arithExp(word(lit("1 * "), quote(`"`, word(arithExp(word(lit("2 + 3")))))))), []string{"5"}, ""},
   351  
   352  	{word(arithExp(word())), nil, "arithmetic expression is missing"},
   353  	{word(arithExp(word(paramExp(lit("9"), ":=", word(lit("...")))))), nil, "$9: cannot assign "},
   354  	{word(arithExp(word(paramExp(lit("@"), "", nil)))), nil, "1 2 3: unexpected NUMBER"},
   355  	{word(arithExp(word(paramExp(lit("#"), "", nil), lit("++")))), nil, "3++: '++' requires lvalue"},
   356  	{word(arithExp(word(paramExp(lit("1"), "", nil), lit("--")))), nil, "1--: '--' requires lvalue"},
   357  }
   358  
   359  var fieldSplitTests = []struct {
   360  	word   ast.Word
   361  	ifs    interface{}
   362  	fields []string
   363  }{
   364  	{word(lit(" \t abc \t xyz \t ")), nil, []string{"abc", "xyz"}},
   365  	{word(lit(" \t abc \t, \t ,\t xyz \t ")), " \t\n,", []string{"abc", "xyz"}},
   366  	{word(lit(" \t abc \t, "), quote(`"`, word()), lit(" ,\t xyz \t ")), " \t\n,", []string{"abc", "", "xyz"}},
   367  	{word(lit(" \t,abc \t xyz \t,")), " \t\n,", []string{"abc", "xyz"}},
   368  	{word(quote(`"`, word()), lit("\t,abc \t xyz \t,")), " \t\n,", []string{"", "abc", "xyz"}},
   369  	{word(lit(" \t,abc \t xyz \t,"), quote(`"`, word())), " \t\n,", []string{"abc", "xyz", ""}},
   370  	{word(lit("abc \xff xyz")), nil, []string{"abc", "\xff", "xyz"}},
   371  	{word(lit("abc \t xyz")), "", []string{"abc \t xyz"}},
   372  	{word(quote(`'`, word(lit("abc \t xyz")))), "", []string{"abc \t xyz"}},
   373  	{word(quote(`"`, word(lit("abc \t xyz")))), "", []string{"abc \t xyz"}},
   374  }
   375  
   376  var pathExpTests = []struct {
   377  	word   ast.Word
   378  	opts   interp.Option
   379  	fields []string
   380  }{
   381  	{word(), 0, nil},
   382  	{word(lit("foo")), 0, []string{"foo"}},
   383  	{word(lit("qux")), 0, []string{"qux"}},
   384  
   385  	{word(lit("b*")), 0, []string{"bar", "baz"}},
   386  	{word(lit("b*")), interp.NoGlob, []string{"b*"}},
   387  	{word(lit("b"), quote(`\`, word(lit("*")))), 0, []string{"b*"}},
   388  	{word(quote(`'`, word(lit("b*")))), 0, []string{"b*"}},
   389  	{word(quote(`"`, word(lit("b*")))), 0, []string{"b*"}},
   390  
   391  	{word(lit("q*")), 0, []string{"q*"}},
   392  	{word(lit("q*")), interp.NoGlob, []string{"q*"}},
   393  	{word(lit("q"), quote(`\`, word(lit("*")))), 0, []string{"q*"}},
   394  	{word(quote(`'`, word(lit("q*")))), 0, []string{"q*"}},
   395  	{word(quote(`"`, word(lit("q*")))), 0, []string{"q*"}},
   396  
   397  	{word(lit("\xff*")), 0, []string{"\xff*"}},
   398  	{word(lit("\xff*")), interp.NoGlob, []string{"\xff*"}},
   399  	{word(lit("\xff"), quote(`\`, word(lit("*")))), 0, []string{"\xff*"}},
   400  	{word(quote(`'`, word(lit("\xff*")))), 0, []string{"\xff*"}},
   401  	{word(quote(`"`, word(lit("\xff*")))), 0, []string{"\xff*"}},
   402  }
   403  
   404  func TestExpand(t *testing.T) {
   405  	env := interp.NewExecEnv(name)
   406  	env.Set("E", E)
   407  	for _, tt := range expandTests {
   408  		g, _ := env.Expand(tt.word, tt.mode)
   409  		if e := tt.fields; !reflect.DeepEqual(g, e) {
   410  			t.Errorf("expected %#v, got %#v", e, g)
   411  		}
   412  	}
   413  	t.Run("TildeExp", func(t *testing.T) {
   414  		env := interp.NewExecEnv(name)
   415  		env.Set("E", E)
   416  		env.Unset("_")
   417  		for _, tt := range tildeExpTests {
   418  			if runtime.GOOS != "windows" && strings.ContainsRune(tt.fields[0], '\\') {
   419  				continue
   420  			}
   421  			g, _ := env.Expand(tt.word, tt.mode)
   422  			if e := tt.fields; !reflect.DeepEqual(g, e) {
   423  				t.Errorf("expected %#v, got %#v", e, g)
   424  			}
   425  		}
   426  	})
   427  	t.Run("ParamExp", func(t *testing.T) {
   428  		for _, tt := range paramExpTests {
   429  			env := interp.NewExecEnv(name)
   430  			env.Opts |= interp.NoUnset
   431  			env.Set("V", V)
   432  			env.Set("E", E)
   433  			env.Set("P", P)
   434  			env.Unset("_")
   435  			g, err := env.Expand(tt.word, interp.Quote)
   436  			switch {
   437  			case err == nil && tt.err != "":
   438  				t.Error("expected error")
   439  			case err != nil && (tt.err == "" || !strings.Contains(err.Error(), tt.err)):
   440  				t.Error("unexpected error:", err)
   441  			default:
   442  				if e := tt.fields; !reflect.DeepEqual(g, e) {
   443  					t.Errorf("expected %#v, got %#v", e, g)
   444  				}
   445  				if tt.assign {
   446  					pe := tt.word[0].(*ast.ParamExp)
   447  					if v, set := env.Get(pe.Name.Value); !set {
   448  						t.Errorf("%v is unset", pe.Name.Value)
   449  					} else {
   450  						var b strings.Builder
   451  						printer.Fprint(&b, pe.Word)
   452  						if g, e := v.Value, b.String(); g != e {
   453  							t.Errorf("expected %q, got %q", e, g)
   454  						}
   455  					}
   456  				}
   457  			}
   458  		}
   459  	})
   460  	t.Run("SpParam", func(t *testing.T) {
   461  		for _, tt := range spParamTests {
   462  			env := interp.NewExecEnv(name, tt.args...)
   463  			env.Set("V", V)
   464  			env.Set("P", P)
   465  			env.Unset("_")
   466  			if tt.ifs != nil {
   467  				env.Set("IFS", tt.ifs.(string))
   468  			} else {
   469  				env.Unset("IFS")
   470  			}
   471  			g, err := env.Expand(tt.word, tt.mode)
   472  			switch {
   473  			case err == nil && tt.err != "":
   474  				t.Error("expected error")
   475  			case err != nil && (tt.err == "" || !strings.Contains(err.Error(), tt.err)):
   476  				t.Error("unexpected error:", err)
   477  			default:
   478  				if e := tt.fields; !reflect.DeepEqual(g, e) {
   479  					t.Errorf("expected %#v, got %#v", e, g)
   480  				}
   481  				if tt.assign != "" {
   482  					pe, ok := tt.word[0].(*ast.ParamExp)
   483  					if !ok {
   484  						pe = tt.word[0].(*ast.Quote).Value[0].(*ast.ParamExp)
   485  					}
   486  					if v, set := env.Get(pe.Name.Value); !set {
   487  						t.Errorf("%v is unset", pe.Name.Value)
   488  					} else {
   489  						if g, e := v.Value, tt.assign; g != e {
   490  							t.Errorf("expected %q, got %q", e, g)
   491  						}
   492  					}
   493  				}
   494  			}
   495  		}
   496  	})
   497  	t.Run("PosParam", func(t *testing.T) {
   498  		for _, tt := range posParamTests {
   499  			env := interp.NewExecEnv(name, tt.args...)
   500  			g, _ := env.Expand(tt.word, 0)
   501  			if e := tt.fields; !reflect.DeepEqual(g, e) {
   502  				t.Errorf("expected %#v, got %#v", e, g)
   503  			}
   504  		}
   505  	})
   506  	t.Run("ArithExp", func(t *testing.T) {
   507  		for _, tt := range arithExpTests {
   508  			env := interp.NewExecEnv(name, "1", "2", "3")
   509  			env.Set("E", E)
   510  			g, err := env.Expand(tt.word, 0)
   511  			switch {
   512  			case err == nil && tt.err != "":
   513  				t.Error("expected error")
   514  			case err != nil && (tt.err == "" || !strings.Contains(err.Error(), tt.err)):
   515  				t.Error("unexpected error:", err)
   516  			default:
   517  				if e := tt.fields; !reflect.DeepEqual(g, e) {
   518  					t.Errorf("expected %#v, got %#v", e, g)
   519  				}
   520  			}
   521  		}
   522  	})
   523  	t.Run("FieldSplit", func(t *testing.T) {
   524  		for _, tt := range fieldSplitTests {
   525  			env := interp.NewExecEnv(name)
   526  			if tt.ifs != nil {
   527  				env.Set("IFS", tt.ifs.(string))
   528  			} else {
   529  				env.Unset("IFS")
   530  			}
   531  			g, _ := env.Expand(tt.word, 0)
   532  			if e := tt.fields; !reflect.DeepEqual(g, e) {
   533  				t.Errorf("expected %#v, got %#v", e, g)
   534  			}
   535  		}
   536  	})
   537  	t.Run("PathExp", func(t *testing.T) {
   538  		popd, err := pushd(t.TempDir())
   539  		if err != nil {
   540  			t.Fatal(err)
   541  		}
   542  		defer popd()
   543  
   544  		for _, name := range []string{"foo", "bar", "baz"} {
   545  			if err := touch(name); err != nil {
   546  				t.Fatal(err)
   547  			}
   548  		}
   549  
   550  		for _, tt := range pathExpTests {
   551  			env := interp.NewExecEnv(name)
   552  			env.Opts = tt.opts
   553  			g, _ := env.Expand(tt.word, 0)
   554  			if e := tt.fields; !reflect.DeepEqual(g, e) {
   555  				t.Errorf("expected %#v, got %#v", e, g)
   556  			}
   557  		}
   558  	})
   559  }
   560  
   561  func word(w ...ast.WordPart) ast.Word {
   562  	if len(w) == 0 {
   563  		return ast.Word{}
   564  	}
   565  	return ast.Word(w)
   566  }
   567  
   568  func lit(s string) *ast.Lit {
   569  	return &ast.Lit{Value: s}
   570  }
   571  
   572  func litf(format string, a ...interface{}) *ast.Lit {
   573  	return &ast.Lit{Value: fmt.Sprintf(format, a...)}
   574  }
   575  
   576  func quote(tok string, word ast.Word) *ast.Quote {
   577  	return &ast.Quote{
   578  		Tok:   tok,
   579  		Value: word,
   580  	}
   581  }
   582  
   583  func paramExp(name *ast.Lit, op string, word ast.Word) *ast.ParamExp {
   584  	return &ast.ParamExp{
   585  		Name: name,
   586  		Op:   op,
   587  		Word: word,
   588  	}
   589  }
   590  
   591  func arithExp(expr ast.Word) *ast.ArithExp {
   592  	return &ast.ArithExp{Expr: expr}
   593  }
   594  
   595  func username() string {
   596  	u, err := user.Current()
   597  	if err != nil {
   598  		panic(err)
   599  	}
   600  	return u.Username
   601  }
   602  
   603  func homeDir() string {
   604  	u, err := user.Current()
   605  	if err != nil {
   606  		panic(err)
   607  	}
   608  	return filepath.ToSlash(u.HomeDir)
   609  }