Permalink
Browse files

[ a = b ] [ a == b] and [ a != b ] now don't do globbing/fnmatch.

The [[ counterparts do globbing, because they can distinguish between
quoted and unquoted strings.

We now have new IDs like Id.BoolBinary_GlobEqual and
Id.BoolBinary_Equal.  The latter is not integrated with osh/lex.py.

Also: Fix unit tests.

Addresses issue #19.
  • Loading branch information...
Andy Chu
Andy Chu committed Aug 28, 2017
1 parent 0f0fb35 commit 2b5791ed3730ae238db852a0c0ecf48a2bb6f843
Showing with 48 additions and 23 deletions.
  1. +9 −3 core/expr_eval.py
  2. +23 −6 core/id_kind.py
  3. +2 −2 core/id_kind_test.py
  4. +3 −5 core/test_builtin.py
  5. +4 −0 osh/bool_parse.py
  6. +7 −7 osh/bool_parse_test.py
View
@@ -503,7 +503,7 @@ def Eval(self, node):
s1 = self._EvalCompoundWord(node.left)
# Whehter to glob escape
do_fnmatch = op_id in (
Id.BoolBinary_Equal, Id.BoolBinary_DEqual, Id.BoolBinary_NEqual)
Id.BoolBinary_GlobEqual, Id.BoolBinary_GlobDEqual, Id.BoolBinary_GlobNEqual)
s2 = self._EvalCompoundWord(node.right, do_fnmatch=do_fnmatch)
# Now dispatch on arg type
@@ -533,13 +533,19 @@ def Eval(self, node):
# TODO:
# - Compare arrays. (Although bash coerces them to string first)
if op_id in (Id.BoolBinary_Equal, Id.BoolBinary_DEqual):
if op_id in (Id.BoolBinary_GlobEqual, Id.BoolBinary_GlobDEqual):
#log('Comparing %s and %s', s2, s1)
return libc.fnmatch(s2, s1)
if op_id == Id.BoolBinary_NEqual:
if op_id == Id.BoolBinary_GlobNEqual:
return not libc.fnmatch(s2, s1)
if op_id in (Id.BoolBinary_Equal, Id.BoolBinary_DEqual):
return s1 == s2
if op_id == Id.BoolBinary_NEqual:
return s1 != s2
if op_id == Id.BoolBinary_EqualTilde:
# NOTE: regex matching can't fail if compilation succeeds.
match = libc.regex_match(s2, s1)
View
@@ -138,6 +138,16 @@ def AddBoolKind(self, kind_name, arg_type_pairs):
self._AddKind(kind_name)
self.kind_sizes.append(num_tokens) # debug info
def AddBoolBinaryForBuiltin(self, token_name):
"""For [ = ] [ == ] and [ != ].
These operators are NOT added to the lexer. The are "lexed" as StringWord.
"""
token_name = 'BoolBinary_%s' % token_name
id_val = self._AddId(token_name)
self.AddBoolOp(id_val, OperandType.Str)
return id_val
def AddBoolOp(self, id_, arg_type):
self.bool_ops[id_] = arg_type
@@ -366,6 +376,10 @@ def _AddKinds(spec):
# Id -> OperandType
BOOL_OPS = {} # type: dict
TEST_UNARY_LOOKUP = {}
TEST_BINARY_LOOKUP = {}
TEST_OTHER_LOOKUP = {}
# Shared between [[ and test/[.
_UNARY_STR_CHARS = 'zn' # -z -n
_UNARY_OTHER_CHARS = 'ovR'
@@ -395,7 +409,7 @@ def _AddBoolKinds(spec):
spec.AddBoolKind('BoolBinary', {
OperandType.Str: [
('Equal', '='), ('DEqual', '=='), ('NEqual', '!='),
('GlobEqual', '='), ('GlobDEqual', '=='), ('GlobNEqual', '!='),
('EqualTilde', '=~'),
],
OperandType.Path: _Dash(_BINARY_PATH),
@@ -411,7 +425,7 @@ def _AddBoolKinds(spec):
spec.AddBoolOp(Id.Redir_Great, OperandType.Str)
def SetupTestBuiltin(unary_lookup, binary_lookup, other_lookup):
def _SetupTestBuiltin(id_spec, unary_lookup, binary_lookup, other_lookup):
"""Setup tokens for test/[.
Similar to _AddBoolKinds above. Differences:
@@ -427,10 +441,11 @@ def SetupTestBuiltin(unary_lookup, binary_lookup, other_lookup):
token_name = 'BoolBinary_%s' % s
binary_lookup['-' + s] = getattr(Id, token_name)
# Like the above, but without =~.
binary_lookup['='] = Id.BoolBinary_Equal
binary_lookup['=='] = Id.BoolBinary_DEqual
binary_lookup['!='] = Id.BoolBinary_NEqual
# Like the [[ definition above, but without globbing and without =~ .
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
@@ -454,6 +469,8 @@ def SetupTestBuiltin(unary_lookup, binary_lookup, other_lookup):
_AddKinds(ID_SPEC)
_AddBoolKinds(ID_SPEC) # must come second
_SetupTestBuiltin(ID_SPEC, TEST_UNARY_LOOKUP, TEST_BINARY_LOOKUP, TEST_OTHER_LOOKUP)
# Debug
_kind_sizes = ID_SPEC.kind_sizes
View
@@ -50,7 +50,7 @@ def testTokens(self):
t = ast.token(Id.Arith_RBrace, '}')
self.assertEqual(Kind.Arith, LookupKind(t.id))
t = ast.token(Id.BoolBinary_DEqual, '==')
t = ast.token(Id.BoolBinary_GlobDEqual, '==')
self.assertEqual(Kind.BoolBinary, LookupKind(t.id))
def testLexerPairs(self):
@@ -59,7 +59,7 @@ def MakeLookup(p):
lookup = MakeLookup(id_kind.ID_SPEC.LexerPairs(Kind.BoolUnary))
print(lookup)
self.assertEqual(Id.BoolUnary_a, lookup['-a'])
self.assertEqual(Id.BoolUnary_e, lookup['-e'])
self.assertEqual(Id.BoolUnary_z, lookup['-z'])
lookup2 = MakeLookup(id_kind.ID_SPEC.LexerPairs(Kind.BoolBinary))
View
@@ -18,11 +18,9 @@
log = util.log
_UNARY_LOOKUP = {}
_BINARY_LOOKUP = {}
_OTHER_LOOKUP = {}
id_kind.SetupTestBuiltin(_UNARY_LOOKUP, _BINARY_LOOKUP, _OTHER_LOOKUP)
_UNARY_LOOKUP = id_kind.TEST_UNARY_LOOKUP
_BINARY_LOOKUP = id_kind.TEST_BINARY_LOOKUP
_OTHER_LOOKUP = id_kind.TEST_OTHER_LOOKUP
class _StringWordEmitter:
View
@@ -131,6 +131,10 @@ def Parse(self):
return None
return node
def _TestAtEnd(self):
"""For unit tests only."""
return self.op_id == Id.Lit_DRightBracket
def ParseForBuiltin(self):
"""For test builtin."""
if not self._Next(): return None
View
@@ -49,36 +49,36 @@ class BoolParserTest(unittest.TestCase):
def testParseFactor(self):
p = _MakeParser('foo')
print(p.ParseFactor())
self.assertTrue(p.AtEnd())
self.assertTrue(p._TestAtEnd())
p = _MakeParser('$foo"bar"')
print(p.ParseFactor())
self.assertTrue(p.AtEnd())
self.assertTrue(p._TestAtEnd())
p = _MakeParser('-z foo')
print('-------------')
node = p.ParseFactor()
print(node)
self.assertTrue(p.AtEnd())
self.assertTrue(p._TestAtEnd())
self.assertEqual(bool_expr_e.BoolUnary, node.tag)
p = _MakeParser('foo == bar')
node = p.ParseFactor()
print(node)
self.assertTrue(p.AtEnd())
self.assertTrue(p._TestAtEnd())
self.assertEqual(bool_expr_e.BoolBinary, node.tag)
def testParseNegatedFactor(self):
p = _MakeParser('foo')
node = p.ParseNegatedFactor()
print(node)
self.assertTrue(p.AtEnd())
self.assertTrue(p._TestAtEnd())
self.assertEqual(bool_expr_e.WordTest, node.tag)
p = _MakeParser('! foo')
node = p.ParseNegatedFactor()
print(node)
self.assertTrue(p.AtEnd())
self.assertTrue(p._TestAtEnd())
self.assertEqual(bool_expr_e.LogicalNot, node.tag)
def testParseTerm(self):
@@ -111,7 +111,7 @@ def testParseFactorInParens(self):
p = _MakeParser('( foo == bar )')
node = p.ParseFactor()
print(node)
self.assertTrue(p.AtEnd())
self.assertTrue(p._TestAtEnd())
self.assertEqual(bool_expr_e.BoolBinary, node.tag)
def testParseParenthesized(self):

0 comments on commit 2b5791e

Please sign in to comment.