Permalink
Browse files

Fix a bug where [ foo -o bar ] was not parsed correctly.

We handle the overloading between [ -o x ] and [ foo -o bar ] in the
parser now rather than the lexer.

Likewise, we can now implement the [ -a x ] alias for [ -e x ] while
also implementing [ foo -a bar ].

Addresses issue #49.
  • Loading branch information...
Andy Chu
Andy Chu committed Nov 15, 2017
1 parent 1edc3f7 commit e96d835b66dde94552116991c144a1651b19aff7
Showing with 37 additions and 25 deletions.
  1. +1 −1 core/expr_eval.py
  2. +7 −15 core/id_kind.py
  3. +11 −1 core/test_builtin.py
  4. +2 −0 core/word_eval.py
  5. +4 −2 osh/bool_parse.py
  6. +7 −6 spec/builtin-test.test.sh
  7. +5 −0 spec/quote.test.sh
View
@@ -485,7 +485,7 @@ def Eval(self, node):
#self._AddErrorContext("Error from stat(%r): %s" % (s, e))
return False
if op_id == Id.BoolUnary_e:
if op_id in (Id.BoolUnary_e, Id.BoolUnary_a): # -a is alias for -e
return True
if op_id == Id.BoolUnary_f:
View
@@ -341,8 +341,6 @@ def _AddKinds(spec):
'Command', 'Assign', 'AndOr', 'Block', 'Subshell', 'Fork',
'FuncDef', 'ForEach', 'ForExpr', 'NoOp',
# TODO: Unify ExprNode and BNode under these Unary, Binary, Ternary nodes.
# They hold one, two, or three words.
'UnaryExpr', 'BinaryExpr', 'TernaryExpr', 'FuncCall',
'ConstInt', # for arithmetic. There is no ConstBool.
# Could be Lit_Digits? But oil will need
@@ -385,12 +383,8 @@ def _AddKinds(spec):
# Shared between [[ and test/[.
_UNARY_STR_CHARS = 'zn' # -z -n
_UNARY_OTHER_CHARS = 'ovR'
# NOTE: In bash and mksh, -a is an alias for -e (exists). For example, see
# test.c in bash. That causes an ambiguity with -a as the binary "and"
# operator, so I just omit it. BoolParser can be changed to deal with the
# ambiguity later if necessary.
_UNARY_PATH_CHARS = 'bcdefghLprsStuwxOGN'
_UNARY_OTHER_CHARS = 'ovR' # -o is overloaded
_UNARY_PATH_CHARS = 'abcdefghLprsStuwxOGN' # -a is overloaded
_BINARY_PATH = ['ef', 'nt', 'ot']
_BINARY_INT = ['eq', 'ne', 'gt', 'ge', 'lt', 'le']
@@ -403,9 +397,6 @@ def _Dash(strs):
return [(s, '-' + s) for s in strs]
# TODO: Need to check for binary -o first, before unary -o.
# Then you can fix unary -a too.
def _AddBoolKinds(spec):
spec.AddBoolKind('BoolUnary', {
OperandType.Str: _Dash(list(_UNARY_STR_CHARS)),
@@ -449,16 +440,17 @@ def _SetupTestBuiltin(id_spec, unary_lookup, binary_lookup, other_lookup):
# Like the [[ definition above, but without globbing and without =~ .
for token_name, token_str in [('Equal', '='), ('DEqual', '=='), ('NEqual', '!=')]:
for token_name, token_str in [
('Equal', '='), ('DEqual', '=='), ('NEqual', '!=')]:
id_val = id_spec.AddBoolBinaryForBuiltin(token_name)
binary_lookup[token_str] = id_val
# Some of these names don't quite match, but it keeps the BoolParser simple.
binary_lookup['<'] = Id.Redir_Less
binary_lookup['>'] = Id.Redir_Great
other_lookup['-a'] = Id.Op_DAmp # like [[ &&
other_lookup['-o'] = Id.Op_DPipe # like [[ ||
# NOTE: -a and -o overloaded as unary prefix operators BoolUnary_a and
# BoolUnary_o. The parser rather than the tokenizer handles this.
other_lookup['!'] = Id.KW_Bang # like [[ !
other_lookup['('] = Id.Op_LParen
other_lookup[')'] = Id.Op_RParen
@@ -515,7 +507,7 @@ def _SetupTestBuiltin(id_spec, unary_lookup, binary_lookup, other_lookup):
Id.Redir_Great: RedirType.Path,
Id.Redir_DGreat: RedirType.Path,
Id.Redir_Clobber: RedirType.Path,
Id.Redir_LessGreat: RedirType.Path, # TODO: What does echo <>foo do?
Id.Redir_LessGreat: RedirType.Path,
# descriptor
Id.Redir_GreatAnd: RedirType.Desc,
View
@@ -35,7 +35,7 @@ def __init__(self, argv):
self.i = 0
self.n = len(argv)
def ReadWord(self, lex_mode):
def ReadWord(self, unused_lex_mode):
if self.i == self.n:
# NOTE: Could define something special
return ast.StringWord(Id.Eof_Real, '')
@@ -187,3 +187,13 @@ def Test(argv, need_right_bracket):
b = bool_ev.Eval(bool_node)
status = 0 if b else 1
return status
if __name__ == '__main__':
# Test
e = _StringWordEmitter('-z X -o -z Y -a -z X'.split())
while True:
w = e.ReadWord(None)
print w
if w.id == Id.Eof_Real:
break
View
@@ -852,6 +852,8 @@ def _EvalWordPart(self, part, quoted=False):
assert len(val) == 2, val # e.g. \*
assert val[0] == '\\'
c = val[1]
# TODO: This can be done at compile time instead! Change _BACKSLASH
# definition in DQ state.
if quoted:
# https://www.gnu.org/software/bash/manual/bash.html#Double-Quotes
if c in ('$', '`', '"', '\\'):
View
@@ -158,7 +158,8 @@ def ParseExpr(self):
Expr : Term (OR Expr)?
"""
left = self.ParseTerm()
if self.op_id == Id.Op_DPipe:
# [[ uses || while [ uses -o
if self.op_id in (Id.Op_DPipe, Id.BoolUnary_o):
if not self._Next(): return None
right = self.ParseExpr()
return ast.LogicalOr(left, right)
@@ -175,7 +176,8 @@ def ParseTerm(self):
left = self.ParseNegatedFactor()
if not left:
return None # TODO: An exception should handle this case.
if self.op_id == Id.Op_DAmp:
# [[ uses && while [ uses -a
if self.op_id in (Id.Op_DAmp, Id.BoolUnary_a):
if not self._Next(): return None
right = self.ParseTerm()
return ast.LogicalAnd(left, right)
View
@@ -1,8 +1,4 @@
#!/usr/bin/env bash
#
# NOTES:
# - osh is using the external binary.
# - because ! -a -o ( ) are the same, we can share logic with [[.
### zero args: [ ]
[ ] || echo false
@@ -31,7 +27,7 @@ echo status=$?
[ -a /nonexistent ]
echo status=$?
# stdout-json: "status=0\nstatus=1\n"
# N-I dash/osh stdout-json: "status=2\nstatus=2\n"
# N-I dash stdout-json: "status=2\nstatus=2\n"
### two args: -z with = ! ( ]
[ -z = ]
@@ -85,11 +81,16 @@ test -n 'a' && echo true
test -n '' || echo false
# stdout-json: "true\nfalse\n"
### ! -a -o
### ! -a
[ -z '' -a ! -z x ]
echo status=$?
# stdout: status=0
### -o
[ -z x -o ! -z x ]
echo status=$?
# stdout: status=0
### ( )
[ -z '' -a '(' ! -z x ')' ]
echo status=$?
View
@@ -81,6 +81,11 @@ echo foo\
$
# stdout: foo$
### Line continuation inside double quotes
echo "foo\
$"
# stdout: foo$
### $? split over multiple lines
# Same with $$, etc. OSH won't do this because $? is a single token.
echo $\

0 comments on commit e96d835

Please sign in to comment.