From 8b673862efc66c4ea159fac9eb11ec6575e2e5d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Sat, 14 Oct 2023 19:11:07 +0100 Subject: [PATCH] interp: support parentheses in classic test commands This mimics the logic in the bash test expression parser in the syntax package, which supported them for years. Fixes #1036. --- interp/interp_test.go | 4 ++++ interp/test_classic.go | 15 +++++++++++++-- syntax/tokens.go | 1 + 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/interp/interp_test.go b/interp/interp_test.go index e6fb20008..5c1f15253 100644 --- a/interp/interp_test.go +++ b/interp/interp_test.go @@ -1611,6 +1611,8 @@ var runTests = []runTest{ {"set -o pipefail; [[ -o pipefail ]]", ""}, // TODO: we don't implement precedence of && over ||. // {"[[ a == x && b == x || c == c ]]", ""}, + {"[[ (a == x && b == x) || c == c ]]", ""}, + {"[[ a == x && (b == x || c == c) ]]", "exit status 1"}, // classic test { @@ -1710,6 +1712,8 @@ var runTests = []runTest{ {"[ abc != ab* ]", ""}, // TODO: we don't implement precedence of -a over -o. // {"[ a = x -a b = x -o c = c ]", ""}, + {`[ \( a = x -a b = x \) -o c = c ]`, ""}, + {`[ a = x -a \( b = x -o c = c \) ]`, "exit status 1"}, // arithm { diff --git a/interp/test_classic.go b/interp/test_classic.go index 53cde38da..905f3a7d1 100644 --- a/interp/test_classic.go +++ b/interp/test_classic.go @@ -51,7 +51,7 @@ func (p *testParser) classicTest(fval string, pastAndOr bool) syntax.TestExpr { } else { left = p.classicTest(fval, true) } - if left == nil || p.eof { + if left == nil || p.eof || p.val == ")" { return left } opStr := p.val @@ -76,7 +76,7 @@ func (p *testParser) classicTest(fval string, pastAndOr bool) syntax.TestExpr { } func (p *testParser) testExprBase(fval string) syntax.TestExpr { - if p.eof { + if p.eof || p.val == ")" { return nil } op := testUnaryOp(p.val) @@ -86,6 +86,15 @@ func (p *testParser) testExprBase(fval string) syntax.TestExpr { p.next() u.X = p.classicTest(op.String(), false) return u + case syntax.TsParen: + pe := &syntax.ParenTest{} + p.next() + pe.X = p.classicTest(op.String(), false) + if p.val != ")" { + p.errf("reached %s without matching ( with )", p.val) + } + p.next() + return pe case illegalTok: return p.followWord(fval) default: @@ -108,6 +117,8 @@ func testUnaryOp(val string) syntax.UnTestOperator { switch val { case "!": return syntax.TsNot + case "(": + return syntax.TsParen case "-e", "-a": return syntax.TsExists case "-f": diff --git a/syntax/tokens.go b/syntax/tokens.go index 6a64b2137..97dec5433 100644 --- a/syntax/tokens.go +++ b/syntax/tokens.go @@ -312,6 +312,7 @@ const ( TsVarSet // -v TsRefVar // -R TsNot = UnTestOperator(exclMark) // ! + TsParen = UnTestOperator(leftParen) // ( ) type BinTestOperator token