Skip to content

Commit

Permalink
Fixing issues with variable parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
kenshaw committed Oct 26, 2024
1 parent 29e25b0 commit 14d9625
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 140 deletions.
15 changes: 6 additions & 9 deletions stmt/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,15 @@ loop:
quote = c
case c == ':' && next != ':':
if v := readVar(p.R, i, p.Len, next); v != nil {
switch ok, z, err := unquote(v.Name, true); {
ok, z, err := unquote(v.Name, true)
switch {
case err != nil:
return false, "", err
case v.Quote == '?':
z = trueFalse(ok)
p.R, p.Len = substitute(p.R, v.I, p.Len, len(v.String()), z)
i = v.I + len(z) - 1
case ok:
p.R, p.Len = substitute(p.R, v.I, p.Len, len(v.String()), z)
i = v.I + len(z) - 1
case ok || v.Quote == '?':
p.R, p.Len = v.Substitute(p.R, z, ok)
i += v.Len - 1
default:
i += len(v.String()) - 1
i = v.End - 1
}
}
case unicode.IsSpace(c):
Expand Down
7 changes: 4 additions & 3 deletions stmt/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestDecodeParamsGetRaw(t *testing.T) {
if err != nil {
t.Fatalf("expected no error, got: %v", err)
}
unquote := testUnquote(t, u, 0, exp)
unquote := testUnquote(t, u)
switch ok, s, err := p.Get(unquote); {
case err != nil:
t.Fatalf("expected no error, got: %v", err)
Expand Down Expand Up @@ -53,6 +53,7 @@ func TestDecodeParamsGetAll(t *testing.T) {
{` :foo`, []string{`bar`}, nil},
{` :'foo`, nil, text.ErrUnterminatedQuotedString},
{`:'foo'`, []string{`'bar'`}, nil},
{` :'foo' `, []string{`'bar'`}, nil},
{`:'foo':foo`, []string{`'bar'bar`}, nil},
{`:'foo':foo:"foo"`, []string{`'bar'bar"bar"`}, nil},
{`:'foo':foo:foo`, []string{`'bar'barbar`}, nil},
Expand Down Expand Up @@ -88,7 +89,7 @@ func TestDecodeParamsGetAll(t *testing.T) {
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
vals, err := DecodeParams(test.s).GetAll(testUnquote(t, u, i, test.s))
vals, err := DecodeParams(test.s).GetAll(testUnquote(t, u))
if err != test.err {
t.Fatalf("expected error %v, got: %v", test.err, err)
}
Expand All @@ -99,7 +100,7 @@ func TestDecodeParamsGetAll(t *testing.T) {
}
}

func testUnquote(t *testing.T, u *user.User, i int, teststr string) func(string, bool) (bool, string, error) {
func testUnquote(t *testing.T, u *user.User) func(string, bool) (bool, string, error) {
t.Helper()
f := env.Unquote(u, false, env.Vars{
"foo": "bar",
Expand Down
172 changes: 44 additions & 128 deletions stmt/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,26 +344,26 @@ func TestReadVar(t *testing.T) {
{`:'ab ' `, 0, nil},
{`:"ab " `, 0, nil},
{`:{?ab } `, 0, nil},
{`:a`, 0, v(0, 2, `a`)},
{`:ab`, 0, v(0, 3, `ab`)},
{`:a `, 0, v(0, 2, `a`)},
{`:a_ `, 0, v(0, 3, `a_`)},
{":a_\t ", 0, v(0, 3, `a_`)},
{":a_\n ", 0, v(0, 3, `a_`)},
{`:a9`, 0, v(0, 3, `a9`)},
{`:ab9`, 0, v(0, 4, `ab9`)},
{`:a 9`, 0, v(0, 2, `a`)},
{`:a_9 `, 0, v(0, 4, `a_9`)},
{":a_9\t ", 0, v(0, 4, `a_9`)},
{":a_9\n ", 0, v(0, 4, `a_9`)},
{`:a_;`, 0, v(0, 3, `a_`)},
{`:a_\`, 0, v(0, 3, `a_`)},
{`:a_$`, 0, v(0, 3, `a_`)},
{`:a_'`, 0, v(0, 3, `a_`)},
{`:a_"`, 0, v(0, 3, `a_`)},
{`:ab `, 0, v(0, 3, `ab`)},
{`:ab123 `, 0, v(0, 6, `ab123`)},
{`:ab123`, 0, v(0, 6, `ab123`)},
{`:a`, 0, v(0, `a`)},
{`:ab`, 0, v(0, `ab`)},
{`:a `, 0, v(0, `a`)},
{`:a_ `, 0, v(0, `a_`)},
{":a_\t ", 0, v(0, `a_`)},
{":a_\n ", 0, v(0, `a_`)},
{`:a9`, 0, v(0, `a9`)},
{`:ab9`, 0, v(0, `ab9`)},
{`:a 9`, 0, v(0, `a`)},
{`:a_9 `, 0, v(0, `a_9`)},
{":a_9\t ", 0, v(0, `a_9`)},
{":a_9\n ", 0, v(0, `a_9`)},
{`:a_;`, 0, v(0, `a_`)},
{`:a_\`, 0, v(0, `a_`)},
{`:a_$`, 0, v(0, `a_`)},
{`:a_'`, 0, v(0, `a_`)},
{`:a_"`, 0, v(0, `a_`)},
{`:ab `, 0, v(0, `ab`)},
{`:ab123 `, 0, v(0, `ab123`)},
{`:ab123`, 0, v(0, `ab123`)},
{`:'`, 0, nil},
{`:' `, 0, nil},
{`:' a`, 0, nil},
Expand All @@ -382,30 +382,30 @@ func TestReadVar(t *testing.T) {
{`:' `, 0, nil},
{`:" `, 0, nil},
{`:" `, 0, nil},
{`:'a'`, 0, v(0, 4, `a`, `'`)},
{`:'a' `, 0, v(0, 4, `a`, `'`)},
{`:'ab'`, 0, v(0, 5, `ab`, `'`)},
{`:'ab' `, 0, v(0, 5, `ab`, `'`)},
{`:"a"`, 0, v(0, 4, `a`, `"`)},
{`:"a" `, 0, v(0, 4, `a`, `"`)},
{`:"ab"`, 0, v(0, 5, `ab`, `"`)},
{`:"ab" `, 0, v(0, 5, `ab`, `"`)},
{`:型`, 0, v(0, 2, "型")},
{`:'型'`, 0, v(0, 4, "型", `'`)},
{`:"型"`, 0, v(0, 4, "型", `"`)},
{` :型 `, 1, v(1, 3, "型")},
{` :'型' `, 1, v(1, 5, "型", `'`)},
{` :"型" `, 1, v(1, 5, "型", `"`)},
{`:型示師`, 0, v(0, 4, "型示師")},
{`:'型示師'`, 0, v(0, 6, "型示師", `'`)},
{`:"型示師"`, 0, v(0, 6, "型示師", `"`)},
{` :型示師 `, 1, v(1, 5, "型示師")},
{` :'型示師' `, 1, v(1, 7, "型示師", `'`)},
{` :"型示師" `, 1, v(1, 7, "型示師", `"`)},
{`:{?a}`, 0, v(0, 5, "a", `?`)},
{` :{?a} `, 1, v(1, 6, "a", `?`)},
{`:{?a_b} `, 0, v(0, 7, "a_b", `?`)},
{` :{?a_b} `, 1, v(1, 8, "a_b", `?`)},
{`:'a'`, 0, v(0, `a`, `'`)},
{`:'a' `, 0, v(0, `a`, `'`)},
{`:'ab'`, 0, v(0, `ab`, `'`)},
{`:'ab' `, 0, v(0, `ab`, `'`)},
{`:"a"`, 0, v(0, `a`, `"`)},
{`:"a" `, 0, v(0, `a`, `"`)},
{`:"ab"`, 0, v(0, `ab`, `"`)},
{`:"ab" `, 0, v(0, `ab`, `"`)},
{`:型`, 0, v(0, "型")},
{`:'型'`, 0, v(0, "型", `'`)},
{`:"型"`, 0, v(0, "型", `"`)},
{` :型 `, 1, v(1, "型")},
{` :'型' `, 1, v(1, "型", `'`)},
{` :"型" `, 1, v(1, "型", `"`)},
{`:型示師`, 0, v(0, "型示師")},
{`:'型示師'`, 0, v(0, "型示師", `'`)},
{`:"型示師"`, 0, v(0, "型示師", `"`)},
{` :型示師 `, 1, v(1, "型示師")},
{` :'型示師' `, 1, v(1, "型示師", `'`)},
{` :"型示師" `, 1, v(1, "型示師", `"`)},
{`:{?a}`, 0, v(0, "a", `?`)},
{` :{?a} `, 1, v(1, "a", `?`)},
{`:{?a_b} `, 0, v(0, "a_b", `?`)},
{` :{?a_b} `, 1, v(1, "a_b", `?`)},
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
Expand Down Expand Up @@ -478,87 +478,3 @@ func TestSubstitute(t *testing.T) {
})
}
}

func TestSubstituteVar(t *testing.T) {
a512 := sl(512, 'a')
tests := []struct {
s string
v *Var
sub string
exp string
}{
{`:a`, v(0, 2, `a`), `x`, `x`},
{` :a`, v(1, 3, `a`), `x`, ` x`},
{`:a `, v(0, 2, `a`), `x`, `x `},
{` :a `, v(1, 3, `a`), `x`, ` x `},
{` :'a' `, v(1, 5, `a`, `'`), `'x'`, ` 'x' `},
{` :"a" `, v(1, 5, "a", `"`), `"x"`, ` "x" `},
{`:a`, v(0, 2, `a`), ``, ``},
{` :a`, v(1, 3, `a`), ``, ` `},
{`:a `, v(0, 2, `a`), ``, ` `},
{` :a `, v(1, 3, `a`), ``, ` `},
{` :'a' `, v(1, 5, `a`, `'`), ``, ` `},
{` :"a" `, v(1, 5, "a", `"`), "", ` `},
{` :aaa `, v(1, 5, "aaa"), "", " "},
{` :aaa `, v(1, 5, "aaa"), a512, " " + a512 + " "},
{` :` + a512 + ` `, v(1, len(a512)+2, a512), "", " "},
{`:foo`, v(0, 4, "foo"), "这是一个", `这是一个`},
{`:foo `, v(0, 4, "foo"), "这是一个", `这是一个 `},
{` :foo`, v(1, 5, "foo"), "这是一个", ` 这是一个`},
{` :foo `, v(1, 5, "foo"), "这是一个", ` 这是一个 `},
{`:'foo'`, v(0, 6, `foo`, `'`), `'这是一个'`, `'这是一个'`},
{`:'foo' `, v(0, 6, `foo`, `'`), `'这是一个'`, `'这是一个' `},
{` :'foo'`, v(1, 7, `foo`, `'`), `'这是一个'`, ` '这是一个'`},
{` :'foo' `, v(1, 7, `foo`, `'`), `'这是一个'`, ` '这是一个' `},
{`:"foo"`, v(0, 6, `foo`, `"`), `"这是一个"`, `"这是一个"`},
{`:"foo" `, v(0, 6, `foo`, `"`), `"这是一个"`, `"这是一个" `},
{` :"foo"`, v(1, 7, `foo`, `"`), `"这是一个"`, ` "这是一个"`},
{` :"foo" `, v(1, 7, `foo`, `"`), `"这是一个"`, ` "这是一个" `},
{`:型`, v(0, 2, `型`), `x`, `x`},
{` :型`, v(1, 3, `型`), `x`, ` x`},
{`:型 `, v(0, 2, `型`), `x`, `x `},
{` :型 `, v(1, 3, `型`), `x`, ` x `},
{` :'型' `, v(1, 5, `型`, `'`), `'x'`, ` 'x' `},
{` :"型" `, v(1, 5, "型", `"`), `"x"`, ` "x" `},
{`:型`, v(0, 2, `型`), ``, ``},
{` :型`, v(1, 3, `型`), ``, ` `},
{`:型 `, v(0, 2, `型`), ``, ` `},
{` :型 `, v(1, 3, `型`), ``, ` `},
{` :'型' `, v(1, 5, `型`, `'`), ``, ` `},
{` :"型" `, v(1, 5, "型", `"`), "", ` `},
{`:型示師`, v(0, 4, `型示師`), `本門台初埼本門台初埼`, `本門台初埼本門台初埼`},
{` :型示師`, v(1, 5, `型示師`), `本門台初埼本門台初埼`, ` 本門台初埼本門台初埼`},
{`:型示師 `, v(0, 4, `型示師`), `本門台初埼本門台初埼`, `本門台初埼本門台初埼 `},
{` :型示師 `, v(1, 5, `型示師`), `本門台初埼本門台初埼`, ` 本門台初埼本門台初埼 `},
{` :型示師 `, v(1, 5, `型示師`), `本門台初埼本門台初埼`, ` 本門台初埼本門台初埼 `},
{` :'型示師' `, v(1, 7, `型示師`), `'本門台初埼本門台初埼'`, ` '本門台初埼本門台初埼' `},
{` :"型示師" `, v(1, 7, `型示師`), `"本門台初埼本門台初埼"`, ` "本門台初埼本門台初埼" `},
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
z := []rune(test.s)
y, l := test.v.Substitute(z, test.sub, true)
if sl := len([]rune(test.sub)); test.v.Len != sl {
t.Errorf("expected v.Len to be %d, got: %d", sl, test.v.Len)
}
if el := len([]rune(test.exp)); l != el {
t.Errorf("expected l==%d, got: %d", el, l)
}
if s := string(y); s != test.exp {
t.Errorf("expected %q, got: %q", test.exp, s)
}
})
}
}

func v(i, end int, n string, x ...string) *Var {
z := &Var{
I: i,
End: end,
Name: n,
}
if len(x) != 0 {
z.Quote = []rune(x[0])[0]
}
return z
}
3 changes: 3 additions & 0 deletions stmt/stmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,10 @@ func (v *Var) Substitute(r []rune, s string, ok bool) ([]rune, int) {
switch v.Quote {
case '?':
s = trueFalse(ok)
case '\'', '"':
s = string(v.Quote) + s + string(v.Quote)
}
// fmt.Fprintf(os.Stderr, "orig: %q repl: %q\n", string(r), s)
sr, rcap := []rune(s), cap(r)
v.Len = len(sr)
// grow ...
Expand Down
96 changes: 96 additions & 0 deletions stmt/stmt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,102 @@ func TestEmptyVariablesRawString(t *testing.T) {
}
}

func TestVarSubstitute(t *testing.T) {
a512 := sl(512, 'a')
tests := []struct {
s string
v *Var
sub string
exp string
}{
{`:a`, v(0, `a`), `x`, `x`},
{` :a`, v(1, `a`), `x`, ` x`},
{`:a `, v(0, `a`), `x`, `x `},
{` :a `, v(1, `a`), `x`, ` x `},
{` :'a' `, v(1, `a`, `'`), `x`, ` 'x' `},
{` :"a" `, v(1, "a", `"`), `x`, ` "x" `},
{`:a`, v(0, `a`), ``, ``},
{` :a`, v(1, `a`), ``, ` `},
{`:a `, v(0, `a`), ``, ` `},
{` :a `, v(1, `a`), ``, ` `},
{` :'a' `, v(1, `a`, `'`), ``, ` '' `},
{` :"a" `, v(1, "a", `"`), "", ` "" `},
{` :aaa `, v(1, "aaa"), "", " "},
{` :aaa `, v(1, "aaa"), a512, " " + a512 + " "},
{` :` + a512 + ` `, v(1, a512), "", " "},
{`:foo`, v(0, "foo"), "这是一个", `这是一个`},
{`:foo `, v(0, "foo"), "这是一个", `这是一个 `},
{` :foo`, v(1, "foo"), "这是一个", ` 这是一个`},
{` :foo `, v(1, "foo"), "这是一个", ` 这是一个 `},
{`:'foo'`, v(0, `foo`, `'`), `这是一个`, `'这是一个'`},
{`:'foo' `, v(0, `foo`, `'`), `这是一个`, `'这是一个' `},
{` :'foo'`, v(1, `foo`, `'`), `这是一个`, ` '这是一个'`},
{` :'foo' `, v(1, `foo`, `'`), `这是一个`, ` '这是一个' `},
{`:"foo"`, v(0, `foo`, `"`), `这是一个`, `"这是一个"`},
{`:"foo" `, v(0, `foo`, `"`), `这是一个`, `"这是一个" `},
{` :"foo"`, v(1, `foo`, `"`), `这是一个`, ` "这是一个"`},
{` :"foo" `, v(1, `foo`, `"`), `这是一个`, ` "这是一个" `},
{`:型`, v(0, `型`), `x`, `x`},
{` :型`, v(1, `型`), `x`, ` x`},
{`:型 `, v(0, `型`), `x`, `x `},
{` :型 `, v(1, `型`), `x`, ` x `},
{` :'型' `, v(1, `型`, `'`), `x`, ` 'x' `},
{` :"型" `, v(1, "型", `"`), `x`, ` "x" `},
{`:型`, v(0, `型`), ``, ``},
{` :型`, v(1, `型`), ``, ` `},
{`:型 `, v(0, `型`), ``, ` `},
{` :型 `, v(1, `型`), ``, ` `},
{` :'型' `, v(1, `型`, `'`), ``, ` '' `},
{` :"型" `, v(1, "型", `"`), "", ` "" `},
{`:型示師`, v(0, `型示師`), `本門台初埼本門台初埼`, `本門台初埼本門台初埼`},
{` :型示師`, v(1, `型示師`), `本門台初埼本門台初埼`, ` 本門台初埼本門台初埼`},
{`:型示師 `, v(0, `型示師`), `本門台初埼本門台初埼`, `本門台初埼本門台初埼 `},
{` :型示師 `, v(1, `型示師`), `本門台初埼本門台初埼`, ` 本門台初埼本門台初埼 `},
{` :型示師 `, v(1, `型示師`), `本門台初埼本門台初埼`, ` 本門台初埼本門台初埼 `},
{` :'型示師' `, v(1, `型示師`, `'`), `a`, ` 'a' `},
{` :"型示師" `, v(1, `型示師`, `"`), `b`, ` "b" `},
{` :'型示師' `, v(1, `型示師`, `'`), `本門台初埼本門台初埼`, ` '本門台初埼本門台初埼' `},
{` :"型示師" `, v(1, `型示師`, `"`), `本門台初埼本門台初埼`, ` "本門台初埼本門台初埼" `},
{`:{?foo}`, v(0, `foo`, `?`), ``, `TRUE`},
{`:{?foo_}`, v(0, `foo_`, `?`), ``, `FALSE`},
{` :{?foo} `, v(1, `foo`, `?`), ``, ` TRUE `},
{` :{?foo_} `, v(1, `foo_`, `?`), ``, ` FALSE `},
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
t.Logf("`%s` `%s`: `%s` --> `%s`", test.v.Name, test.sub, test.s, test.exp)
t.Logf("[]byte: %d", len([]byte(test.s)))
t.Logf("[]rune: %d", len([]rune(test.s)))
t.Logf("v.I:%d v.End:%d", test.v.I, test.v.End)
r, n := test.v.Substitute([]rune(test.s), test.sub, test.v.Name == "foo")
if exp := len([]rune(test.exp)); n != exp {
t.Errorf("expected n to be %d, got: %d", exp, n)
}
if s := string(r); s != test.exp {
t.Errorf("expected %q, got: %q", test.exp, s)
}
})
}
}

func v(i int, name string, x ...string) *Var {
z := &Var{
I: i,
Name: name,
End: i + len([]rune(name)) + 1, // :name
}
if len(x) != 0 {
z.Quote = []rune(x[0])[0]
switch z.Quote {
case '\'', '"':
z.End += 2 // '', ""
case '?':
z.End += 3 // {?}
}
}
return z
}

// cc combines commands with params.
func cc(cmds []string, params []string) []string {
if len(cmds) == 0 {
Expand Down

0 comments on commit 14d9625

Please sign in to comment.