From 3b53461527b7a7558cc55e05ffd560f483478bd5 Mon Sep 17 00:00:00 2001 From: Matthias Heinzel Date: Tue, 9 Aug 2016 23:46:49 +0200 Subject: [PATCH 01/11] Add test for multiple bitwise operators --- test/operators/bitwise.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/operators/bitwise.py b/test/operators/bitwise.py index b57bd5c..7d4ff82 100644 --- a/test/operators/bitwise.py +++ b/test/operators/bitwise.py @@ -4,3 +4,11 @@ print(22 ^ 5) print(2 << 4) print(16 >> 2) + +# multiple +print(~~19) +print(197 & 92 & 345) +print(197 | 92 | 345) +print(197 ^ 92 ^ 345) +print(1 << 2 << 3) +print(256 >> 4 >> 3) From fe93c5e411aa6ab22ddc8e7857d838f740a927ed Mon Sep 17 00:00:00 2001 From: Matthias Heinzel Date: Wed, 10 Aug 2016 00:35:06 +0200 Subject: [PATCH 02/11] Parser: Implement repeated bitwise operations --- src/Language/Python/Parser.y | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/Language/Python/Parser.y b/src/Language/Python/Parser.y index a89ed0a..d81c778 100644 --- a/src/Language/Python/Parser.y +++ b/src/Language/Python/Parser.y @@ -499,29 +499,25 @@ star_expr : '*' expr { undefined } -- expr: xor_expr ('|' xor_expr)* --- TODO: implement 0-n handling expr : xor_expr { $1 } - | xor_expr '|' xor_expr { BinOp (BitOp BitOr) $1 $3 } + | expr '|' xor_expr { BinOp (BitOp BitOr) $1 $3 } -- xor_expr: and_expr ('^' and_expr)* --- TODO: implement 0-n handling xor_expr : and_expr { $1 } - | and_expr '^' and_expr { BinOp (BitOp BitXor) $1 $3 } + | xor_expr '^' and_expr { BinOp (BitOp BitXor) $1 $3 } -- and_expr: shift_expr ('&' shift_expr)* --- TODO: implement 0-n handling and_expr : shift_expr { $1 } - | shift_expr '&' shift_expr { BinOp (BitOp BitAnd) $1 $3 } + | and_expr '&' shift_expr { BinOp (BitOp BitAnd) $1 $3 } -- shift_expr: arith_expr (('<<'|'>>') arith_expr)* --- TODO: implement 0-n handling shift_expr : arith_expr { $1 } - | arith_expr '<<' arith_expr { BinOp (BitOp LShift) $1 $3 } - | arith_expr '>>' arith_expr { BinOp (BitOp RShift) $1 $3 } + | shift_expr '<<' arith_expr { BinOp (BitOp LShift) $1 $3 } + | shift_expr '>>' arith_expr { BinOp (BitOp RShift) $1 $3 } -- arith_expr: term (('+'|'-') term)* arith_expr From 59a6ea53f0b162681dedd1b6118cd376b6f8a87e Mon Sep 17 00:00:00 2001 From: Matthias Heinzel Date: Fri, 12 Aug 2016 01:43:26 +0200 Subject: [PATCH 03/11] Add test for logical operators --- test/operators/logical.py | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 test/operators/logical.py diff --git a/test/operators/logical.py b/test/operators/logical.py new file mode 100644 index 0000000..c1de513 --- /dev/null +++ b/test/operators/logical.py @@ -0,0 +1,46 @@ +print(not True) +print(not False) +print(not not True) +print(not not False) + +for a in [False, True]: + for b in [False, True]: + print(a or b) + print(a and b) + print(a or not b) + print(a and not b) + print(not a or b) + print(not a and b) + print(not a or not b) + print(not a and not b) + + +# Truthiness +print([] or {}) +print("" or 0) +print(1 or "test") +print({False: 1} or [2,3]) + +print([0] and "") +print("abc" and [1]) +print(0 and {2: True}) +print({} and 1231) + + +# Test short-circuiting behavior +def t(n): + print("t", n) + return True + +def f(n): + print("f", n) + return False + +for a in [t, f]: + for b in [t, f]: + for c in [t, f]: + print(a(1) and b(2) and c(3)) + print(a(1) and b(2) or c(3)) + print(a(1) or b(2) and c(3)) + print(a(1) or b(2) or c(3)) + From 2fe295a9cf937602c35c097bc1c37f032f848757 Mon Sep 17 00:00:00 2001 From: Matthias Heinzel Date: Fri, 12 Aug 2016 02:21:49 +0200 Subject: [PATCH 04/11] Make boolean operators short-circuit --- src/Hython/Expression.hs | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/Hython/Expression.hs b/src/Hython/Expression.hs index bd8e5bb..c489e83 100644 --- a/src/Hython/Expression.hs +++ b/src/Hython/Expression.hs @@ -82,21 +82,13 @@ evalExpr (BinOp (BitOp op) leftExpr rightExpr) = do return None evalExpr (BinOp (BoolOp op) leftExpr rightExpr) = do - [lhs, rhs] <- mapM evalExpr [leftExpr, rightExpr] - case (op, lhs, rhs) of - (And, Bool l, Bool r) -> newBool (l && r) - (And, l, r) -> do - left <- isTruthy l - right <- isTruthy r - return $ if left && right - then r - else l - (Or, Bool l, Bool r) -> newBool (l || r) - (Or, l, r) -> do - left <- isTruthy l - return $ if left - then l - else r + lhs <- evalExpr leftExpr + lTruthy <- isTruthy lhs + case (op, lTruthy) of + (And, False) -> return lhs + (And, True) -> evalExpr rightExpr + (Or, False) -> evalExpr rightExpr + (Or, True) -> return lhs evalExpr (BinOp (CompOp op) leftExpr rightExpr) = do [lhs, rhs] <- mapM evalExpr [leftExpr, rightExpr] From 76189e837e731c2c93e7d6aaf2811585a0df6314 Mon Sep 17 00:00:00 2001 From: Matthias Heinzel Date: Fri, 12 Aug 2016 12:02:16 +0200 Subject: [PATCH 05/11] This seems like it is already done --- src/Language/Python/Parser.y | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Language/Python/Parser.y b/src/Language/Python/Parser.y index d81c778..be7445b 100644 --- a/src/Language/Python/Parser.y +++ b/src/Language/Python/Parser.y @@ -457,19 +457,16 @@ lambdef_nocond : LAMBDA varargslist ':' test_nocond { LambdaExpr $2 $4 } -- or_test: and_test ('or' and_test)* --- TODO: implement 0-n clauses or_test : and_test { $1 } - | or_test OR and_test { BinOp (BoolOp Or) $1 $3 } + | or_test OR and_test { BinOp (BoolOp Or) $1 $3 } -- and_test: not_test ('and' not_test)* --- TODO: implement 0-n clauses and_test : not_test { $1 } | and_test AND not_test { BinOp (BoolOp And) $1 $3 } -- not_test: 'not' not_test | comparison --- TODO: implement 0-n clauses not_test : NOT not_test { UnaryOp Not $2 } | comparison { $1 } From 52307916fafcd2190cca20fa49bff0f723d33d7e Mon Sep 17 00:00:00 2001 From: Matthias Heinzel Date: Fri, 12 Aug 2016 12:28:54 +0200 Subject: [PATCH 06/11] Split operators test up, add some tests --- test/operators/arithmetic.py | 48 +++++++++++++++++++++++++ test/operators/comparison.py | 31 ++++++++++++++++ test/operators/operators.py | 70 ------------------------------------ test/operators/precedence.py | 1 - test/operators/string.py | 9 +++++ 5 files changed, 88 insertions(+), 71 deletions(-) create mode 100644 test/operators/arithmetic.py create mode 100644 test/operators/comparison.py delete mode 100644 test/operators/operators.py delete mode 100644 test/operators/precedence.py create mode 100644 test/operators/string.py diff --git a/test/operators/arithmetic.py b/test/operators/arithmetic.py new file mode 100644 index 0000000..d227e42 --- /dev/null +++ b/test/operators/arithmetic.py @@ -0,0 +1,48 @@ +# Integral arithmetic +print(+5) +print(-5) + +print(1+1) +print(3-5) +print(2*4) +print(3/5) +print(5%2) +print(5//2) +print(5**3) +print(0**0) + +# Floating point arithmetic +print(+5.0) +print(-5.0) +print(1.0 + 2.0) +print(2.0 - 0.5) +print(1.0 * 1.6) +print(1.0 / 4.8) +print(5.0 % 2.0) +print(5.0 // 2.0) +print(5.0 ** 3.4) +print(0.0 ** 0.0) + +# Mixed integral / floating point +print(1 + 2.0) +print(1.7 - 2) +print(17.24 * 4) +print(2 / 0.3) +print(5.4 % 2) +print(7 // 1.7) +print(9.1 ** 3) +print(2 ** 3.14159) + +# Precedence +print(2**-1) + +print(3 - 2 + 1) +print(1 + 2.2 * 3) +print((1 + 2.2) * 3) +print(3 + 5 % 2) +print(10 / 3 // 2) +print(4 + 2 ** 7) + + +# TODO: imaginary number arithmetic +#print(3.14j + 1.0) diff --git a/test/operators/comparison.py b/test/operators/comparison.py new file mode 100644 index 0000000..d5a7eaa --- /dev/null +++ b/test/operators/comparison.py @@ -0,0 +1,31 @@ +# Boolean operations +print(42 == 42) +print(42 != 18) + +# Integer boolean operations +print(5 > 1) +print(5 >= 5) +print(4 < 1) +print(4 <= 4) + +# Float boolean operators +print(5.0 > 1.0) +print(4.0 < 1.0) +print(5.0 >= 5.0) +print(4.0 <= 4.0) + +# Mixed int/float with operators +print(5 == 1.0) +print(4 != 1.0) +print(5 > 1.0) +print(4 < 1.0) +print(5 >= 5.0) +print(4 <= 4.0) + +print(5.0 == 1) +print(4.0 != 1) +print(5.0 > 1) +print(4.0 < 1) +print(5.0 >= 5) +print(4.0 <= 4) + diff --git a/test/operators/operators.py b/test/operators/operators.py deleted file mode 100644 index fea132e..0000000 --- a/test/operators/operators.py +++ /dev/null @@ -1,70 +0,0 @@ -# Integral arithmetic -print(+5) -print(-5) - -print(1+1) -print(3-5) -print(2*4) -print(3/5) -print(5%2) -print(5//2) -print(5**3) -print(0**0) - -# Floating point arithmetic -print(+5.0) -print(-5.0) -print(1.0 + 2.0) -print(2.0 - 0.5) -print(1.0 * 1.6) -print(1.0 / 4.8) -print(5.0 % 2.0) -print(5.0 // 2.0) -print(5.0 ** 3.4) -print(0.0 ** 0.0) - -# TODO: imaginary number arithmetic - -# Parentheses affect order of operations -print((1+1)*4) - -# String concatenation -print("Hello " + "there") - -# String/Int multiplication -print(4 * "hi") -print("hi" * 4) - -# Boolean operations -print(42 == 42) -print(42 != 18) - -# Integer boolean operations -print(5 > 1) -print(5 >= 5) -print(4 < 1) -print(4 <= 4) - -# Float boolean operators -print(5.0 > 1.0) -print(4.0 < 1.0) -print(5.0 >= 5.0) -print(4.0 <= 4.0) - -# Mixed int/float with operators -print(5 == 1.0) -print(4 != 1.0) -print(5 > 1.0) -print(4 < 1.0) -print(5 >= 5.0) -print(4 <= 4.0) - -print(5.0 == 1) -print(4.0 != 1) -print(5.0 > 1) -print(4.0 < 1) -print(5.0 >= 5) -print(4.0 <= 4) - -# Imaginary -#print(3.14j + 1.0) diff --git a/test/operators/precedence.py b/test/operators/precedence.py deleted file mode 100644 index 2316938..0000000 --- a/test/operators/precedence.py +++ /dev/null @@ -1 +0,0 @@ -print(2**-1) diff --git a/test/operators/string.py b/test/operators/string.py new file mode 100644 index 0000000..7d43894 --- /dev/null +++ b/test/operators/string.py @@ -0,0 +1,9 @@ +# String concatenation +print("Hello " + "there" + " :)") + +# String/Int multiplication +print(4 * "hi") +print("hi" * 4) + +print("hi" * 4 + ", funny!") +print("hi" + "au" * 4) From 6dcfe936bd6549e4c24adc4c5397bac02b1b490a Mon Sep 17 00:00:00 2001 From: Matthias Heinzel Date: Fri, 12 Aug 2016 22:48:23 +0200 Subject: [PATCH 07/11] Add test for comparison/equality of String, Bytes, Bool etc. --- test/operators/comparison.py | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/operators/comparison.py b/test/operators/comparison.py index d5a7eaa..e48843f 100644 --- a/test/operators/comparison.py +++ b/test/operators/comparison.py @@ -29,3 +29,44 @@ print(5.0 >= 5) print(4.0 <= 4) + +# On strings +print("test" == "test") +print("" == "test") +print("a" < "b") +print("b" < "a") +print("abc" < "abcde") + + +# On bytes +print(b'test' == b'test') +print(b'' == b'test') +print(b'a' < b'b') +print(b'b' < b'a') +print(b'abc' < b'abcde') + + +# Mixed +print("mixed!") +elements = [-3, -0.00001, 0, 0.0, 1, 1.0, 32, "", "foo", b'fo', b'foo', False, True, None] +for a in elements: + for b in elements: + try: + print("comparison") + print(a == b) + print(a != b) + print(a < b) + print(a > b) + print(a <= b) + print(a >= b) + except TypeError: + print("TypeError") + + +# TODO: is +# TODO: Chained comparison operators +# TODO: Comparison of lists, tuples +# TODO: Equality of sets, dicts +# TODO: Imaginary +# TODO: objects with __eq__() etc. + From a189ef2c07beb26aa3d1cd53dfce784c19bdb190 Mon Sep 17 00:00:00 2001 From: Matthias Heinzel Date: Fri, 12 Aug 2016 20:54:53 +0200 Subject: [PATCH 08/11] Implement comparison for Bool, String, Bytes --- src/Hython/Expression.hs | 65 ++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/src/Hython/Expression.hs b/src/Hython/Expression.hs index c489e83..6585c19 100644 --- a/src/Hython/Expression.hs +++ b/src/Hython/Expression.hs @@ -92,32 +92,7 @@ evalExpr (BinOp (BoolOp op) leftExpr rightExpr) = do evalExpr (BinOp (CompOp op) leftExpr rightExpr) = do [lhs, rhs] <- mapM evalExpr [leftExpr, rightExpr] - case (op, lhs, rhs) of - (Eq, l, r) -> newBool =<< equal l r - (NotEq, l, r) -> do - b <- equal l r - newBool $ not b - (LessThan, Float l, Float r) -> newBool (l < r) - (LessThan, Int l, Int r) -> newBool (l < r) - (LessThanEq, Float l, Float r) -> newBool (l <= r) - (LessThanEq, Int l, Int r) -> newBool (l <= r) - (GreaterThan, Float l, Float r) -> newBool (l > r) - (GreaterThan, Int l, Int r) -> newBool (l > r) - (GreaterThanEq, Int l, Int r) -> newBool (l >= r) - (GreaterThanEq, Float l, Float r) -> newBool (l >= r) - (Is, l, r) -> newBool $ l `is` r - (IsNot, l, r) -> newBool . not $ l `is` r - (_, Float _, Int r) -> evalExpr (BinOp (CompOp op) leftExpr (constantF r)) - (_, Int l, Float _) -> evalExpr (BinOp (CompOp op) (constantF l) rightExpr) - (In, l, r@(Object {})) -> invoke r "__contains__" [l] - (NotIn, _, _) -> do - (Bool b) <- evalExpr (BinOp (CompOp In) leftExpr rightExpr) - newBool (not b) - _ -> do - raise "SystemError" ("unsupported operand type " ++ show op) - return None - where - constantF i = Constant $ ConstantFloat $ fromIntegral i + compareObjs op lhs rhs evalExpr (Call expr argExprs) = do target <- evalExpr expr @@ -258,6 +233,44 @@ evalParam (DefaultParam param expr) = do evalParam (SplatParam param) = return $ SParam param evalParam (DoubleSplatParam param) = return $ DSParam param + +compareObjs :: MonadInterpreter m => ComparisonOperator -> Object -> Object -> m Object +compareObjs op lhs rhs = case (op, lhs, rhs) of + -- equality + (Eq, l, r) -> newBool =<< equal l r + (NotEq, l, r) -> newBool . not =<< equal l r + -- "is", "is not", "in", "not in" + (Is, l, r) -> newBool $ l `is` r + (IsNot, l, r) -> newBool . not $ l `is` r + (In, l, r) -> invoke r "__contains__" [l] + (NotIn, l, r) -> do + Bool b <- invoke r "__contains__" [l] + newBool (not b) + -- conversion + (_, Bool l, _) -> compareObjs op (Float (boolToFloat l)) rhs + (_, Int l, _) -> compareObjs op (Float (fromIntegral l)) rhs + (_, _, Bool r) -> compareObjs op lhs (Float (boolToFloat r)) + (_, _, Int r) -> compareObjs op lhs (Float (fromIntegral r)) + -- comparison + (_, Bytes l, Bytes r) -> applyOp op l r + (_, Float l, Float r) -> applyOp op l r + (_, String l, String r) -> applyOp op l r + -- error cases + _ -> do + raise "TypeError" "unorderable types" + return None + where + boolToFloat True = 1 + boolToFloat False = 0 + applyOp :: (Ord a, MonadInterpreter m) => ComparisonOperator -> a -> a -> m Object + applyOp c a b = newBool $ compOp c a b + compOp LessThan = (<) + compOp LessThanEq = (<=) + compOp GreaterThan = (>) + compOp GreaterThanEq = (>=) + compOp _ = error "compOp: Eq, NotEq, Is, IsNot, In, NotIn should be handled" + + is :: Object -> Object -> Bool is None None = True is (Bool l) (Bool r) = l == r From 271cf56176a67a5937da414d82d03d3f8e85b6f2 Mon Sep 17 00:00:00 2001 From: Matthias Heinzel Date: Fri, 12 Aug 2016 22:44:05 +0200 Subject: [PATCH 09/11] Implement equality for Bool/Int/Float combination --- src/Hython/Types.hs | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/Hython/Types.hs b/src/Hython/Types.hs index 4b321e9..d37e6b3 100644 --- a/src/Hython/Types.hs +++ b/src/Hython/Types.hs @@ -223,27 +223,34 @@ isNone (None) = True isNone _ = False equal :: MonadInterpreter m => Object -> Object -> m Bool -equal (Bool l) (Bool r) = return $ l == r -equal (Bytes l) (Bytes r) = return $ l == r -equal (Float l) (Float r) = return $ l == r -equal (Imaginary l) (Imaginary r) = return $ l == r -equal (Int l) (Int r) = return $ l == r -equal (String l) (String r) = return $ l == r -equal (BuiltinFn l) (BuiltinFn r) = return $ l == r -equal (Dict l) (Dict r) = do - left <- readRef l - right <- readRef r - if IntMap.size left /= IntMap.size right - then return False - else do - results <- zipWithM pairEqual (IntMap.elems left) (IntMap.elems right) - return $ all (== True) results +equal lhs rhs = case (lhs, rhs) of + -- conversion + (Bool l, _) -> equal (Float (boolToFloat l)) rhs + (Int l, _) -> equal (Float (fromIntegral l)) rhs + (_, Bool r) -> equal lhs (Float (boolToFloat r)) + (_, Int r) -> equal lhs (Float (fromIntegral r)) + (None, None ) -> return True + (Bytes l, Bytes r) -> return $ l == r + (Float l, Float r) -> return $ l == r + (Imaginary l, Imaginary r) -> return $ l == r + (String l, String r) -> return $ l == r + (BuiltinFn l, BuiltinFn r) -> return $ l == r + (Dict l, Dict r) -> do + left <- readRef l + right <- readRef r + if IntMap.size left /= IntMap.size right + then return False + else do + results <- zipWithM pairEqual (IntMap.elems left) (IntMap.elems right) + return $ all (== True) results + (_, _) -> return False where + boolToFloat False = 0.0 + boolToFloat True = 1.0 pairEqual (lk, lv) (rk, rv) = do k <- equal lk rk v <- equal lv rv return $ k && v -equal _ _ = return False isTruthy :: MonadInterpreter m => Object -> m Bool isTruthy (None) = return False From 184c80c05f88d22418613daa57fb68082cc0071c Mon Sep 17 00:00:00 2001 From: Matthias Heinzel Date: Fri, 12 Aug 2016 22:50:53 +0200 Subject: [PATCH 10/11] Add test for chained comparison operators --- test/operators/comparison.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/operators/comparison.py b/test/operators/comparison.py index e48843f..0310c00 100644 --- a/test/operators/comparison.py +++ b/test/operators/comparison.py @@ -63,6 +63,32 @@ print("TypeError") +# Chaining comparison operators +print("chained") +print(3 == 3 != 3) +print(4 == 4 > 2) +print(1 < 2 < 3 < 4) +print(2 > 3 > -1) +print(3 <= 10 > 17) +print(-3 < 10 >= 10) + +# Mixed with other operators +print(3 < 2 * 2 < 5) +print(4 // 2 == 2 < 3) +print(7 > 6 > 7 - 1 and (True and False) < True) + +def p(n): + print(n) + return n + +# Only evaluate each element once +print(p(1) < p(2) < p(3) < p(4)) + +# Stop after first wrong comparison +print(p(7) < p(6) < p(8) < p(9)) + + + # TODO: is # TODO: Chained comparison operators # TODO: Comparison of lists, tuples From a67cf34ac5320a1d16863a622a297e48c93182b9 Mon Sep 17 00:00:00 2001 From: Matthias Heinzel Date: Fri, 12 Aug 2016 21:01:44 +0200 Subject: [PATCH 11/11] Implement chained comparison e.g. 1 < 2 < 3 --- src/Hython/Expression.hs | 19 ++++++++++++++++--- src/Language/Python/Parser.y | 6 +++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Hython/Expression.hs b/src/Hython/Expression.hs index 6585c19..db6ef1c 100644 --- a/src/Hython/Expression.hs +++ b/src/Hython/Expression.hs @@ -90,9 +90,22 @@ evalExpr (BinOp (BoolOp op) leftExpr rightExpr) = do (Or, False) -> evalExpr rightExpr (Or, True) -> return lhs -evalExpr (BinOp (CompOp op) leftExpr rightExpr) = do - [lhs, rhs] <- mapM evalExpr [leftExpr, rightExpr] - compareObjs op lhs rhs +evalExpr (BinOp (CompOp operator) leftExpression rightExpression) = do + lhs <- evalExpr leftExpression + go operator lhs rightExpression + where + go :: (MonadCont m, MonadEnv Object m, MonadIO m, MonadInterpreter m) + => ComparisonOperator -> Object -> Expression -> m Object + -- for chained comparison, e.g. 1 < 2 < 3 + go op lhs (BinOp (CompOp rop) rl rr) = do + rlhs <- evalExpr rl + comp <- compareObjs op lhs rlhs + case comp of + Bool True -> go rop rlhs rr + _ -> newBool False + go op lhs rightExpr = do + rhs <- evalExpr rightExpr + compareObjs op lhs rhs evalExpr (Call expr argExprs) = do target <- evalExpr expr diff --git a/src/Language/Python/Parser.y b/src/Language/Python/Parser.y index be7445b..e87a15f 100644 --- a/src/Language/Python/Parser.y +++ b/src/Language/Python/Parser.y @@ -472,10 +472,10 @@ not_test | comparison { $1 } -- comparison: expr (comp_op expr)* --- TODO: implement 0-n clauses +-- NOTE: right-recursive, because of 1 < 2 < 3 etc. comparison - : expr { $1 } - | expr comp_op expr { BinOp (CompOp $2) $1 $3 } + : expr { $1 } + | expr comp_op comparison { BinOp (CompOp $2) $1 $3 } -- comp_op: '<'|'>'|'=='|'>='|'<='|'<>'|'!='|'in'|'not' 'in'|'is'|'is' 'not' comp_op