diff --git a/osh/word_parse.py b/osh/word_parse.py index 333debf5e5..04665a2580 100644 --- a/osh/word_parse.py +++ b/osh/word_parse.py @@ -1479,14 +1479,15 @@ def _ReadArithSub(self): # $((echo * foo)) # looks like multiplication # $((echo / foo)) # looks like division - self._SetNext(lex_mode_e.Arith) - anode = self._ReadArithExpr(Id.Arith_RParen) + anode = arith_expr.EmptyZero + self._SetNextNonSpace() + + if self.token_type != Id.Arith_RParen: + anode = self._ReadArithExpr(Id.Arith_RParen) - # TODO: This could be DQ or Arith too self._SetNext(lex_mode_e.ShCommand) - # PROBLEM: $(echo $(( 1 + 2 )) ) - # Two right parens break the Id.Eof_RParen scheme + # Ensure we get closing ) self._GetToken() if self.token_type != Id.Right_DollarDParen: p_die('Expected second ) to end arith sub', self.cur_token) @@ -1501,24 +1502,25 @@ def ReadDParen(self): We're using the word parser because it's very similar to _ReadArithExpr above. - This also returns the terminating `Op_DRightParen` token for use as location - tracking. + This also returns the terminating Id.Op_DRightParen token for location + info. """ - # The second one needs to be disambiguated in stuff like stuff like: - # TODO: Be consistent with ReadForExpression below and use lex_mode_e.Arith? - # Then you can get rid of this. + anode = arith_expr.EmptyZero # (( )) + self.lexer.PushHint(Id.Op_RParen, Id.Op_DRightParen) - self._SetNext(lex_mode_e.Arith) - anode = self._ReadArithExpr(Id.Arith_RParen) + self._SetNextNonSpace() + self._GetToken() + if self.token_type != Id.Arith_RParen: + anode = self._ReadArithExpr(Id.Arith_RParen) self._SetNext(lex_mode_e.ShCommand) - # PROBLEM: $(echo $(( 1 + 2 )) ) + # Ensure we get the second ) self._GetToken() right = self.cur_token - if self.token_type != Id.Op_DRightParen: - p_die('Expected second ) to end arith statement', self.cur_token) + if right.id != Id.Op_DRightParen: + p_die('Expected second ) to end arith statement', right) self._SetNext(lex_mode_e.ShCommand) diff --git a/spec/arith-context.test.sh b/spec/arith-context.test.sh index 6c171bab6c..8b6e21142c 100644 --- a/spec/arith-context.test.sh +++ b/spec/arith-context.test.sh @@ -1,5 +1,5 @@ ## compare_shells: bash mksh zsh -## oils_failures_allowed: 2 +## oils_failures_allowed: 1 # Test arithmetic expressions in all their different contexts. diff --git a/test/parse-errors.sh b/test/parse-errors.sh index e04d92ff3f..5d13f26f37 100755 --- a/test/parse-errors.sh +++ b/test/parse-errors.sh @@ -117,6 +117,42 @@ test-word-parse() { _osh-parse-error '${x:' } +test-dparen() { + # (( )) + + _osh-should-parse '(())' + _osh-should-parse '(( ))' + _osh-parse-error '(( )' + _osh-parse-error '(( )x' + #_osh-should-parse '$(echo $(( 1 + 2 )) )' + + # Hard case + _osh-should-parse '$(echo $(( 1 + 2 )))' + _osh-should-parse '$( (()))' + + # More + _osh-parse-error '(( 1 + 2 /' + _osh-parse-error '(( 1 + 2 )/' + _osh-parse-error '(( 1' + _osh-parse-error '((' +} + +test-arith-sub() { + # $(( )) + + _osh-should-parse 'echo $(( ))' + _osh-should-parse 'echo $(())' + _osh-parse-error 'echo $(()x' + + _osh-parse-error 'echo $(()' + + _osh-parse-error 'echo $(( 1 + 2 ;' + _osh-parse-error 'echo $(( 1 + 2 );' + _osh-parse-error 'echo $(( ' + _osh-parse-error 'echo $(( 1' +} + + test-array-literal() { # Array literal with invalid TokenWord. _osh-parse-error 'a=(1 & 2)' @@ -126,12 +162,6 @@ test-array-literal() { } test-arith-context() { - # $(( )) - _osh-parse-error 'echo $(( 1 + 2 ;' - _osh-parse-error 'echo $(( 1 + 2 );' - _osh-parse-error 'echo $(( ' - _osh-parse-error 'echo $(( 1' - # Disable Oil stuff for osh_{parse,eval}.asan if false; then # Non-standard arith sub $[1 + 2] @@ -144,12 +174,6 @@ test-arith-context() { _osh-parse-error 'echo $[' fi - # (( )) - _osh-parse-error '(( 1 + 2 /' - _osh-parse-error '(( 1 + 2 )/' - _osh-parse-error '(( 1' - _osh-parse-error '((' - # Should be an error _osh-parse-error 'a[x+]=1' @@ -751,7 +775,6 @@ esac' echo bash=$? set -o errexit done - } all() {