Skip to content

Commit 0ee56cf

Browse files
committed
lexer: allow extglob wildcards as function names
the lexer assumes an extglob token if any of the wildcards expressions (such as `@`, and `+`) are succeeded by a left parenthesis but that proves to be an issue if the wildcard is used as a function name. example input: ``` $ cat in.sh @() { echo "$@"; } ``` `bash` and `gosh` comparison: ``` $ bash ./in.sh hello $ ./gosh in.sh in.sh:5:1: "}" can only be used to close a block ``` given `in.sh`, gosh reports about a syntax error - this is because a closing bracket is found while the lexer isn't assuming a function block fix the issue by assuming a function if one of the conditions below is true: * if the expression is found at the beginning of the statement or if its preceded by a "function" * if `(` is immediately succeeded by a `)` - although this is a valid bash syntax, we'll operate on the likelihood that it is a function fixes #739
1 parent f965c62 commit 0ee56cf

File tree

2 files changed

+32
-2
lines changed

2 files changed

+32
-2
lines changed

interp/interp_test.go

+10
Original file line numberDiff line numberDiff line change
@@ -3117,6 +3117,16 @@ hello, world
31173117
hello, world
31183118
`,
31193119
},
3120+
{
3121+
// globbing wildcard as function name
3122+
`@() { echo "$@"; }; @ lala; function +() { echo "$@"; }; + foo`,
3123+
"lala\nfoo\n",
3124+
},
3125+
{
3126+
// globbing wildcard as function name but with space after the name
3127+
`+ () { echo "$@"; }; + foo; @ () { echo "$@"; }; @ lala; ? () { echo "$@"; }; ? bar`,
3128+
"foo\nlala\nbar\n",
3129+
},
31203130
}
31213131

31223132
var runTestsWindows = []runTest{

syntax/lexer.go

+22-2
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ skipSpace:
290290
p.advanceLitNone(r)
291291
}
292292
case '?', '*', '+', '@', '!':
293-
if p.peekByte('(') {
293+
if p.isExprGlob() {
294294
switch r {
295295
case '?':
296296
p.tok = globQuest
@@ -346,6 +346,26 @@ skipSpace:
346346
}
347347
}
348348

349+
// isExprGlob determines whether the expression is an extglob wildcard literal
350+
func (p *Parser) isExprGlob() bool {
351+
leftParen := p.peekByte('(')
352+
if !leftParen {
353+
return false
354+
}
355+
// it must not be a globbing if its at the beginning of a statement or
356+
// if its preceded by "function"
357+
if p.pos.Col() == 1 || p.val == "function" {
358+
return false
359+
}
360+
// its must not be a globbing if left parens is succeeded by a right parens
361+
// NOTE: empty pattern list is a valid globbing syntax, eg @()
362+
// but we'll operate on the "likelihood" that it is a function
363+
if len(p.bs) > p.bsp+p.w {
364+
return p.bs[p.bsp+p.w] != ')'
365+
}
366+
return true
367+
}
368+
349369
func (p *Parser) peekByte(b byte) bool {
350370
if p.bsp == len(p.bs) {
351371
p.fill()
@@ -882,7 +902,7 @@ loop:
882902
tok = _Lit
883903
break loop
884904
case '?', '*', '+', '@', '!':
885-
if p.peekByte('(') {
905+
if p.isExprGlob() {
886906
tok = _Lit
887907
break loop
888908
}

0 commit comments

Comments
 (0)