diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,4 +26,14 @@
 
 * fixed "regex" (it was very broken, lol)
 
-* added escaping support to string literals (only \")
+* added escaping support to string literals (only \\")
+
+## 0.2.1 -- 2024-03-15
+
+* rewrote the "if" and "filter" focusers
+
+* added "=", "!=", "<", ">", "<=", ">=", "&&", "||", "all", "any", "not" focusers
+
+* changed the collecting syntax from "<focuser>" to "%focuser"
+
+* added the "literal" focuser
diff --git a/smh.cabal b/smh.cabal
--- a/smh.cabal
+++ b/smh.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               smh
-version:            0.2.0
+version:            0.2.1
 synopsis:           String manipulation tool written in haskell
 description:        String manipulation CLI tool based on optics
 category:           CLI Tool
diff --git a/src/Common.hs b/src/Common.hs
--- a/src/Common.hs
+++ b/src/Common.hs
@@ -95,33 +95,6 @@
         | i < 0 = max (i + len) 0
         | otherwise = min i len
 
-
-data Evaluatable
-    = EText !Text
-    | ENumber !Rational
-    | EFocuser { evalFocuserUnsafe :: !Focuser }
-
-data IfExpr
-    = IfAnd ![IfExpr]
-    | IfOr ![IfExpr]
-    | IfSingle !Comparison
-
-data Comparison = Comparison
-    { cmpLHS :: !(Quantor, Evaluatable)
-    , cmpOp  :: !Oper
-    , cmpRHS :: !(Quantor, Evaluatable)
-    }
-
-data Quantor = QAll | QAny
-
-data Oper
-    = OpEq
-    | OpNe
-    | OpLt
-    | OpLe
-    | OpGt
-    | OpGe
-
 ws :: Parser ()
 ws = L.space space1 empty empty
 
diff --git a/src/Focusers.hs b/src/Focusers.hs
--- a/src/Focusers.hs
+++ b/src/Focusers.hs
@@ -6,12 +6,11 @@
 
 module Focusers where
 
-import           Common               (Comparison (..), Evaluatable (..),
-                                       Focus (..), Focuser (..), IfExpr (..),
-                                       Mapping, Oper (..), Parser, Quantor (..),
-                                       Range (RangeSingle), _toListUnsafe,
-                                       composeFocusers, fromIndexes, getIndexes,
-                                       lexeme, makeFilteredText, mapText,
+import           Common               (Focus (..), Focuser (..), Mapping,
+                                       Parser, Range (RangeSingle),
+                                       _toListUnsafe, composeFocusers,
+                                       fromIndexes, getIndexes, lexeme,
+                                       makeFilteredText, mapText,
                                        readMaybeRational, showRational, symbol,
                                        toListUnsafe, toTextUnsafe, unsort, ws)
 import           Control.Applicative  ((<|>))
@@ -309,70 +308,10 @@
 
 
 
-focusIf :: IfExpr -> Focuser
-focusIf ifexpr = FTrav $ \f focus -> if focus `passesIf` ifexpr
-    then f focus
-    else pure focus
-  where
-    passesIf :: Focus -> IfExpr -> Bool
-    passesIf focus (IfAnd ifexprs) = all (passesIf focus) ifexprs
-    passesIf focus (IfOr ifexprs) = any (passesIf focus) ifexprs
-    passesIf focus (IfSingle comp) =
-        let op = cmpOp comp
-            q1 = fst $ cmpLHS comp
-            q2 = fst $ cmpRHS comp
-            f1s = evaluateEval focus $ snd $ cmpLHS comp
-            f2s = evaluateEval focus $ snd $ cmpRHS comp
-            results = [[applyOp op f1 f2 | f2 <- f2s] | f1 <- f1s]
-        in case (q1, q2) of
-            (QAll, QAll) -> all and results
-            (QAll, QAny) -> all or results
-            (QAny, QAll) -> any and results
-            (QAny, QAny) -> any or results
-
-    evaluateEval :: Focus -> Evaluatable -> [Either Rational Focus]
-    evaluateEval focus eval = case eval of
-        EText s               -> [Right $ FText s]
-        ENumber n             -> [Left n]
-        EFocuser (FTrav trav) -> Right <$> focus ^.. trav
-
-    applyOp :: Oper -> Either Rational Focus -> Either Rational Focus -> Bool
-    applyOp OpEq (Left n1) (Left n2) = n1 == n2
-    applyOp OpEq (Right f1) (Right f2) = f1 == f2
-    applyOp OpEq (Left n1) (Right (FText s2)) = Just n1 == readMaybeRational s2
-    applyOp OpEq (Right (FText s1)) (Left n2) = readMaybeRational s1 == Just n2
-    applyOp OpNe (Left n1) (Left n2) = n1 /= n2
-    applyOp OpNe (Left n1) (Right (FText s2)) = case readMaybeRational s2 of
-        Just n2 -> n1 /= n2
-        Nothing -> False
-    applyOp OpNe (Right (FText s1)) (Left n2) = case readMaybeRational s1 of
-        Just n1 -> n1 /= n2
-        Nothing -> False
-    applyOp OpNe (Right f1) (Right f2) = f1 /= f2
-    applyOp op e1 e2 = case op of
-        OpLt -> applyOpOrd (<) e1 e2
-        OpGt -> applyOpOrd (>) e1 e2
-        OpLe -> applyOpOrd (<=) e1 e2
-        OpGe -> applyOpOrd (>=) e1 e2
-        _    -> False
-      where
-        applyOpOrd
-            :: (forall a. Ord a => a -> a -> Bool)
-            -> Either Rational Focus
-            -> Either Rational Focus
-            -> Bool
-        applyOpOrd f (Left n1) (Left n2) = f n1 n2
-        applyOpOrd f (Left n1) (Right (FText s2)) = case readMaybeRational s2 of
-            Just n2 -> f n1 n2
-            Nothing -> False
-        applyOpOrd f (Right (FText s1)) (Left n2) = case readMaybeRational s1 of
-            Just n1 -> f n1 n2
-            Nothing -> False
-        applyOpOrd f (Right (FText s1)) (Right (FText s2)) =
-            case (readMaybeRational s1, readMaybeRational s2) of
-                (Just n1, Just n2) -> f n1 n2
-                _                  -> f s1 s2
-        applyOpOrd _ _ _ = False
+focusIf :: Focuser -> Focuser
+focusIf (FTrav trav) = FTrav $ \f focus -> case focus ^.. trav of
+    [FText "1"] -> f focus
+    _           -> pure focus
 
 logicFocuser :: (Focus -> Bool) -> Focuser
 logicFocuser pred = FTrav $ lens
@@ -411,6 +350,13 @@
     FText s -> T.all isSpace s
     _         -> False)
 
+focusIsNumber :: Focuser
+focusIsNumber = logicFocuser (\case
+    FText s -> case readMaybeRational s of
+        Just _  -> True
+        Nothing -> False
+    _         -> False)
+
 focusRegex :: Text -> Focuser
 focusRegex regex = FTrav $ \f focus -> case focus of
     FText s ->
@@ -420,8 +366,8 @@
         in  FText . T.concat . interleave nonMatches <$> newMatches
     _ -> pure focus
 
-focusFilter :: IfExpr -> Focuser
-focusFilter pred = focusCollect $ focusEach `composeFocusers` focusIf pred
+focusFilter :: Focuser -> Focuser
+focusFilter ftrav = focusCollect $ focusEach `composeFocusers` focusIf ftrav
 
 focusContains :: Text -> Focuser
 focusContains text = FTrav $ lens contains const
@@ -608,8 +554,63 @@
 
 focusAtKey :: Text -> Focuser
 focusAtKey key = focusKV
-    `composeFocusers` focusIf (IfSingle $ Comparison (QAll, EFocuser focusKey) OpEq (QAll, EText key))
+    `composeFocusers` focusIf (focusCompEq (==) focusKey (focusConst key))
     `composeFocusers` focusVal
 
 focusAtIdx :: Int -> Focuser
 focusAtIdx i = focusCollect focusEl `composeFocusers` focusIndex i
+
+textToBool :: Text -> Bool
+textToBool = \case
+    "1" -> True
+    _   -> False
+
+boolToText :: Bool -> Text
+boolToText = \case
+    True  -> "1"
+    False -> "0"
+
+focusLogic2 :: (Bool -> Bool -> Bool) -> Focuser -> Focuser -> Focuser
+focusLogic2 op (FTrav t1) (FTrav t2) = FTrav $ \f focus ->
+    case (focus ^.. t1, focus ^.. t2) of
+    ([FText s1], [FText s2]) ->
+        let b1 = textToBool s1
+            b2 = textToBool s2
+        in  focus <$ (f . FText . boolToText $ op b1 b2)
+
+focusToMaybeBool :: Focus -> Maybe Bool
+focusToMaybeBool = \case
+    FText s -> Just $ textToBool s
+    FList _ -> Nothing
+
+focusLogicMany :: ([Bool] -> Bool) -> Focuser -> Focuser
+focusLogicMany op (FTrav t) = FTrav $ \f focus ->
+    case traverse focusToMaybeBool (focus ^.. t) of
+        Just bs -> focus <$ (f . FText . boolToText $ op bs)
+        Nothing -> pure focus
+
+focusNot :: Focuser
+focusNot = FTrav $ \f focus ->
+    case focusToMaybeBool focus of
+        Just b  -> focus <$ (f . FText . boolToText $ not b)
+        Nothing -> pure focus
+
+focusConst :: Text -> Focuser
+focusConst s = FTrav $ lens (const $ FText s) const
+
+focusCompOrd :: (forall a . (Ord a, Eq a) => a -> a -> Bool) -> Focuser -> Focuser -> Focuser
+focusCompOrd op (FTrav t1) (FTrav t2) = FTrav $ \f focus ->
+    case (focus ^.. t1, focus ^.. t2) of
+    ([FText s1], [FText s2]) -> case (readMaybeRational s1, readMaybeRational s2) of
+        (Just r1, Just r2) -> focus <$ (f . FText . boolToText $ op r1 r2)
+        _                  -> focus <$ (f . FText . boolToText $ op s1 s2)
+    _ -> pure focus
+
+focusCompEq :: (forall a . Eq a => a -> a -> Bool) -> Focuser -> Focuser -> Focuser
+focusCompEq op (FTrav t1) (FTrav t2) = FTrav $ \f focus ->
+    case (focus ^.. t1, focus ^.. t2) of
+    ([FText s1], [FText s2]) -> case (readMaybeRational s1, readMaybeRational s2) of
+        (Just r1, Just r2) -> focus <$ (f . FText . boolToText $ op r1 r2)
+        _                  -> focus <$ (f . FText . boolToText $ op s1 s2)
+    ([FList lst1], [FList lst2]) ->
+        focus <$ (f . FText . boolToText $ all (uncurry op) $ zip lst1 lst2)
diff --git a/src/Mappings.hs b/src/Mappings.hs
--- a/src/Mappings.hs
+++ b/src/Mappings.hs
@@ -3,17 +3,16 @@
 
 module Mappings where
 
-import           Common          (Evaluatable (..), Focus (FList, FText),
-                                  Focuser (..), Mapping, Range, getIndexes,
-                                  makeFilteredText, mapText,
-                                  showRational, toTextUnsafe, readMaybeRational)
-import           Control.Lens    ((^..))
-import           Data.Char       (toLower, toUpper)
-import           Data.Function   (on)
-import           Data.List       (sortBy)
-import           Data.Text       (Text)
-import qualified Data.Text       as T
-import           Text.Read       (readMaybe)
+import           Common        (Focus (FList, FText), Focuser (..), Mapping,
+                                Range, getIndexes, makeFilteredText, mapText,
+                                readMaybeRational, showRational, toTextUnsafe)
+import           Control.Lens  ((^..))
+import           Data.Char     (toLower, toUpper)
+import           Data.Function (on)
+import           Data.List     (sortBy)
+import           Data.Text     (Text)
+import qualified Data.Text     as T
+import           Text.Read     (readMaybe)
 
 mappingReverse :: Mapping
 mappingReverse (FList lst) = FList (reverse lst)
@@ -28,18 +27,14 @@
 mappingMap mapping (FText str) = FText $ T.concat $ mapText
     (toTextUnsafe . mapping . FText . T.singleton) str
 
-mappingAppend :: Evaluatable -> Mapping
-mappingAppend (EText str') (FText str) = FText $ T.append str str'
-mappingAppend (ENumber n) (FText str) = FText $ T.append str (showRational n)
-mappingAppend (EFocuser (FTrav trav)) fstr@(FText str) = case fstr ^.. trav of
+mappingAppend :: Focuser -> Mapping
+mappingAppend (FTrav trav) fstr@(FText str) = case fstr ^.. trav of
     [FText s] -> FText $ T.append str s
     _         -> fstr
 mappingAppend _ flist            = flist
 
-mappingPrepend :: Evaluatable -> Mapping
-mappingPrepend (EText str') (FText str) = FText $ T.append str' str
-mappingPrepend (ENumber n) (FText str) = FText $ T.append (showRational n) str
-mappingPrepend (EFocuser (FTrav trav)) fstr@(FText str) = case fstr ^.. trav of
+mappingPrepend :: Focuser -> Mapping
+mappingPrepend (FTrav trav) fstr@(FText str) = case fstr ^.. trav of
     [FText s] -> FText $ T.append s str
     _         -> fstr
 mappingPrepend _ flist            = flist
diff --git a/src/Parsers.hs b/src/Parsers.hs
--- a/src/Parsers.hs
+++ b/src/Parsers.hs
@@ -2,12 +2,11 @@
 {-# LANGUAGE OverloadedStrings #-}
 
 module Parsers where
-import           Common               (Comparison (..), Evaluatable (..),
-                                       Focuser (..), IfExpr (..), Mapping,
-                                       Oper (..), Parser, Quantor (..),
+import           Common               (Focuser (..), Mapping, Parser,
                                        Range (..), composeFocusers, focusTo,
                                        foldFocusers, foldMappings, integer,
-                                       lexeme, mappingTo, rational, symbol)
+                                       lexeme, mappingTo, rational,
+                                       showRational, symbol)
 import           Data.Char            (isAlphaNum)
 import           Data.Functor         (($>))
 import           Data.Maybe           (fromMaybe)
@@ -15,15 +14,18 @@
 import qualified Data.Text            as T
 import           Focusers             (escaping, focusAtIdx, focusAtKey,
                                        focusAverage, focusCollect, focusCols,
+                                       focusCompEq, focusCompOrd, focusConst,
                                        focusContains, focusEach, focusEl,
                                        focusEndsWith, focusFilter, focusId,
                                        focusIf, focusIndex, focusIsAlpha,
                                        focusIsAlphaNum, focusIsDigit,
-                                       focusIsLower, focusIsSpace, focusIsUpper,
-                                       focusKV, focusKey, focusLength,
-                                       focusLines, focusMaxBy, focusMaxLexBy,
-                                       focusMinBy, focusMinLexBy, focusProduct,
-                                       focusRegex, focusSlice, focusSortedBy,
+                                       focusIsLower, focusIsNumber,
+                                       focusIsSpace, focusIsUpper, focusKV,
+                                       focusKey, focusLength, focusLines,
+                                       focusLogic2, focusLogicMany, focusMaxBy,
+                                       focusMaxLexBy, focusMinBy, focusMinLexBy,
+                                       focusNot, focusProduct, focusRegex,
+                                       focusSlice, focusSortedBy,
                                        focusSortedLexBy, focusSpace,
                                        focusStartsWith, focusSum, focusVal,
                                        focusWords)
@@ -48,7 +50,6 @@
 parseFocuser = label "valid focuser" $ choice
     [ symbol "id" $> focusId
     , symbol "each" $> focusEach
-    , parseFocusCollect
     , symbol "words" $> focusWords
     , symbol "lines" $> focusLines
     , symbol "ws" $> focusSpace
@@ -87,6 +88,7 @@
     , symbol "isAlphaNum" $> focusIsAlphaNum
     , symbol "isAlpha" $> focusIsAlpha
     , symbol "isSpace" $> focusIsSpace
+    , symbol "isNumber" $> focusIsNumber
     , parseFocusRegex
     , parseFocusFilter
     , parseFocusContains
@@ -98,6 +100,18 @@
     , symbol "val" $> focusVal
     , parseFocusAtKey
     , parseFocusAtIdx
+    , parseFocusAll
+    , parseFocusAny
+    , symbol "not" $> focusNot
+    , parseFocusOr
+    , parseFocusAnd
+    , parseFocusEq
+    , parseFocusNeq
+    , parseFocusLeq
+    , parseFocusGeq
+    , try parseFocusLt <|> parseFocusCollect
+    , parseFocusGt
+    , parseFocusLit
     ]
 
 parseFocusers :: Parser [Focuser]
@@ -105,11 +119,8 @@
 
 parseFocusCollect :: Parser Focuser
 parseFocusCollect = do
-    symbol "<"
-    focusers <- parseFocusers
-    symbol ">"
-    let focuser = foldFocusers focusers
-    return $ focusCollect focuser
+    symbol "%"
+    focusCollect <$> parseFocuser
 
 parseFocusSlice :: Parser Focuser
 parseFocusSlice = do
@@ -200,54 +211,7 @@
 parseFocusIf :: Parser Focuser
 parseFocusIf = do
     lexeme $ string "if" >> notFollowedBy (satisfy isAlphaNum)
-    focusIf <$> parseIfExpr
-
-parseIfExpr :: Parser IfExpr
-parseIfExpr = label "one or more blocks separated by '||'" $ do
-    andBlocks <- parseAndBlock `sepBy1` symbol "||"
-    case andBlocks of
-        []      -> empty
-        [block] -> return block
-        _       -> return $ IfOr andBlocks
-
-parseAndBlock :: Parser IfExpr
-parseAndBlock = label "one or more blocks separated by '&&'" $ do
-    atoms <- parseAtom `sepBy1` symbol "&&"
-    case atoms of
-        []     -> empty
-        [atom] -> return atom
-        _      -> return $ IfAnd atoms
-
-parseAtom :: Parser IfExpr
-parseAtom = between (symbol "(") (symbol ")") parseIfExpr <|> try parseComp <|> parseIfExprShort
-
-parseComp :: Parser IfExpr
-parseComp = do
-    q1 <- fromMaybe QAll <$> optional parseQuantor
-    lhs <- fromMaybe (EFocuser focusId) <$> optional parseEvaluatableLong
-    comp <- parseCompOp
-    q2 <- fromMaybe QAll <$> optional parseQuantor
-    rhs <- parseEvaluatableLong
-    return $ IfSingle $ Comparison (q1, lhs) comp (q2, rhs)
-
-parseQuantor :: Parser Quantor
-parseQuantor = symbol "all " $> QAll <|> symbol "any " $> QAny
-
-parseCompOp :: Parser Oper
-parseCompOp = choice
-    [ symbol "=" $> OpEq
-    , symbol "!=" $> OpNe
-    , symbol "<=" $> OpLe
-    , symbol "<"  $> OpLt
-    , symbol ">=" $> OpGe
-    , symbol ">"  $> OpGt
-    ]
-
-parseIfExprShort :: Parser IfExpr
-parseIfExprShort = do
-    q <- fromMaybe QAll <$> optional parseQuantor
-    e <- EFocuser <$> parseFocuser
-    return $ IfSingle $ Comparison (q, e) OpEq (QAny, EText "1")
+    focusIf <$> parseFocuser
 
 parseFocusRegex :: Parser Focuser
 parseFocusRegex = do
@@ -257,7 +221,7 @@
 parseFocusFilter :: Parser Focuser
 parseFocusFilter = do
     lexeme $ string "filter" >> notFollowedBy (satisfy isAlphaNum)
-    focusFilter <$> parseIfExpr
+    focusFilter <$> parseFocuser
 
 parseFocusContains :: Parser Focuser
 parseFocusContains = do
@@ -290,6 +254,90 @@
     symbol "atIdx "
     focusAtIdx <$> integer
 
+parseFocusAll :: Parser Focuser
+parseFocusAll = do
+    lexeme $ string "all" >> notFollowedBy (satisfy isAlphaNum)
+    focusLogicMany and <$> parseFocuser
+
+parseFocusAny :: Parser Focuser
+parseFocusAny = do
+    lexeme $ string "any" >> notFollowedBy (satisfy isAlphaNum)
+    focusLogicMany or <$> parseFocuser
+
+parseFocusAnd :: Parser Focuser
+parseFocusAnd = do
+    symbol "&&"
+    p1 <- parseFocuser
+    p2 <- parseFocuser
+    return $ focusLogic2 (&&) p1 p2
+
+parseFocusOr :: Parser Focuser
+parseFocusOr = do
+    symbol "||"
+    p1 <- parseFocuser
+    p2 <- parseFocuser
+    return $ focusLogic2 (||) p1 p2
+
+parseFocusEq :: Parser Focuser
+parseFocusEq = do
+    symbol "="
+    p1 <- parseFocuser
+    mp2 <- optional parseFocuser
+    return $ case mp2 of
+        Just p2 -> focusCompEq (==) p1 p2
+        Nothing -> focusCompEq (==) focusId p1
+
+parseFocusNeq :: Parser Focuser
+parseFocusNeq = do
+    symbol "!="
+    p1 <- parseFocuser
+    mp2 <- optional parseFocuser
+    return $ case mp2 of
+        Just p2 -> focusCompEq (/=) p1 p2
+        Nothing -> focusCompEq (/=) focusId p1
+
+parseFocusLt :: Parser Focuser
+parseFocusLt = do
+    symbol "<"
+    p1 <- parseFocuser
+    mp2 <- optional parseFocuser
+    return $ case mp2 of
+        Just p2 -> focusCompOrd (<) p1 p2
+        Nothing -> focusCompOrd (<) focusId p1
+
+parseFocusGt :: Parser Focuser
+parseFocusGt = do
+    symbol ">"
+    p1 <- parseFocuser
+    mp2 <- optional parseFocuser
+    return $ case mp2 of
+        Just p2 -> focusCompOrd (>) p1 p2
+        Nothing -> focusCompOrd (>) focusId p1
+
+parseFocusLeq :: Parser Focuser
+parseFocusLeq = do
+    symbol "<="
+    p1 <- parseFocuser
+    mp2 <- optional parseFocuser
+    return $ case mp2 of
+        Just p2 -> focusCompOrd (<=) p1 p2
+        Nothing -> focusCompOrd (<=) focusId p1
+
+parseFocusGeq :: Parser Focuser
+parseFocusGeq = do
+    symbol ">="
+    p1 <- parseFocuser
+    mp2 <- optional parseFocuser
+    return $ case mp2 of
+        Just p2 -> focusCompOrd (>=) p1 p2
+        Nothing -> focusCompOrd (>=) focusId p1
+
+parseFocusLit :: Parser Focuser
+parseFocusLit = parseFocusString <|> parseFocusNumber
+  where
+    parseFocusString = focusConst <$> stringLiteral
+    parseFocusNumber = focusConst . showRational <$> rational
+
 -- mapping parsers
 
 parseMapping :: Parser Mapping
@@ -326,21 +374,9 @@
     lexeme $ string "map" >> notFollowedBy (satisfy isAlphaNum)
     mappingMap <$> parseMapping
 
-parseEvaluatable :: Parser Evaluatable
-parseEvaluatable =
-    EText <$> stringLiteral <|>
-    ENumber <$> rational <|>
-    EFocuser <$> parseFocuser
-
-parseEvaluatableLong :: Parser Evaluatable
-parseEvaluatableLong =
-    EText <$> stringLiteral <|>
-    ENumber <$> rational <|>
-    EFocuser . foldFocusers <$> parseFocusers
-
 stringLiteral :: Parser Text
 stringLiteral = T.pack <$> label "string literal" (do
-    between (char '"') (char '"') $ many $ choice
+    between (char '"') (symbol "\"") $ many $ choice
         [ char '\\' >> anySingle
         , anySingleBut '"'
         ])
@@ -348,12 +384,12 @@
 parseMappingAppend :: Parser Mapping
 parseMappingAppend = do
     lexeme $ string "append" >> notFollowedBy (satisfy isAlphaNum)
-    mappingAppend <$> parseEvaluatableLong
+    mappingAppend . foldFocusers <$> parseFocusers
 
 parseMappingPrepend :: Parser Mapping
 parseMappingPrepend = do
     lexeme $ string "prepend" >> notFollowedBy (satisfy isAlphaNum)
-    mappingPrepend <$> parseEvaluatableLong
+    mappingPrepend . foldFocusers <$> parseFocusers
 
 parseMappingAdd :: Parser Mapping
 parseMappingAdd = do
diff --git a/test/Main.hs b/test/Main.hs
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -5,9 +5,10 @@
 import           Data.Char        (isAlpha, isAlphaNum, isDigit, isLower,
                                    isSpace, isUpper, toLower, toUpper)
 import           Data.Function    (on)
-import           Data.List        (groupBy, transpose)
+import           Data.List        (groupBy, isInfixOf, isPrefixOf, isSuffixOf,
+                                   transpose)
 import           Data.List.Extra  (dropEnd, groupBy, sort, takeEnd, transpose)
-import           Data.Maybe       (fromMaybe, mapMaybe, fromJust)
+import           Data.Maybe       (fromJust, fromMaybe, mapMaybe)
 import qualified Data.Text        as T
 import           Focusers         (interleave, myWords)
 import           System.IO.Unsafe (unsafePerformIO)
@@ -26,7 +27,7 @@
         ]
     , testGroup "each"
         [ "each|get" $= concatMap (: "\n") input
-        , "<words>.each|get" $=$ "words|get"
+        , "%words.each|get" $=$ "words|get"
         ]
     , testGroup "words"
         [ "words|get-tree" $= show (words input)
@@ -61,28 +62,28 @@
         , "{:}|get-tree" $= show [input]
         ]
     , testGroup "sortLexBy"
-        [ "<words>.sortedLexBy id.each|get-tree" $= show (sort $ words input)
-        , "<words>.sortedLexBy id.each|over id" $= input
+        [ "%words.sortedLexBy id.each|get-tree" $= show (sort $ words input)
+        , "%words.sortedLexBy id.each|over id" $= input
         ]
     , testGroup "minLexBy/maxLexBy/minLex/maxLex"
-        [ "<words>.minLexBy id|get-tree" $=$ "<words>.sortedLexBy id.[0]|get-tree"
-        , "<words>.maxLexBy id|get-tree" $=$ "<words>.sortedLexBy id.[-1]|get-tree"
-        , "<words>.minLex|get-tree" $=$ "<words>.minLexBy id|get-tree"
-        , "<words>.maxLex|get-tree" $=$ "<words>.maxLexBy id|get-tree"
+        [ "%words.minLexBy id|get-tree" $=$ "%words.sortedLexBy id.[0]|get-tree"
+        , "%words.maxLexBy id|get-tree" $=$ "%words.sortedLexBy id.[-1]|get-tree"
+        , "%words.minLex|get-tree" $=$ "%words.minLexBy id|get-tree"
+        , "%words.maxLex|get-tree" $=$ "%words.maxLexBy id|get-tree"
         ]
     , testGroup "sortedBy"
-        [ "<words.if isDigit>.sortedBy id.each|get-tree" $=
+        [ "%(words.if isDigit).sortedBy id.each|get-tree" $=
             show (map showRational $ sort $ map (fromJust . readMaybeRational . T.pack) $ filter (all isDigit) $ words input)
-        , "<words.if isAlpha>.sortedBy id.each|over id" $=$ "id|over id"
+        , "%(words.if isAlpha).sortedBy id.each|over id" $=$ "id|over id"
         ]
     , testGroup "sorted"
-        [ "<words>.sorted|get-tree" $=$ "<words>.sortedBy id|get-tree"
+        [ "%words.sorted|get-tree" $=$ "%words.sortedBy id|get-tree"
         ]
     , testGroup "minBy/maxBy/min/max"
-        [ "<words>.minBy id|get-tree" $=$ "<words>.sortedBy id.[0]|get-tree"
-        , "<words>.maxBy id|get-tree" $=$ "<words>.sortedBy id.[-1]|get-tree"
-        , "<words>.min|get-tree" $=$ "<words>.minBy id|get-tree"
-        , "<words>.max|get-tree" $=$ "<words>.maxBy id|get-tree"
+        [ "%words.minBy id|get-tree" $=$ "%words.sortedBy id.[0]|get-tree"
+        , "%words.maxBy id|get-tree" $=$ "%words.sortedBy id.[-1]|get-tree"
+        , "%words.min|get-tree" $=$ "%words.minBy id|get-tree"
+        , "%words.max|get-tree" $=$ "%words.maxBy id|get-tree"
         ]
     , testGroup "index"
         [ "[3]|get-tree" $= show [take 1 $ drop 3 input]
@@ -105,73 +106,71 @@
         , "(words.len)|get-tree" $=$ "words.len|get-tree"
         ]
     , testGroup "sum"
-        [ "<words>.sum|get-tree" $= show [showRational (sum $ inputNums input)]
+        [ "%words.sum|get-tree" $= show [showRational (sum $ inputNums input)]
         , "words.sum|get-tree" $=
             show (map (showRational . sum . mapMaybe (readMaybeRational . T.pack . (:[]))) $ words input)
         ]
     , testGroup "product"
-        [ "<words>.product|get-tree" $= show [showRational (product $ inputNums input)]
+        [ "%words.product|get-tree" $= show [showRational (product $ inputNums input)]
         , "words.product|get-tree" $=
             show (map (showRational . product . mapMaybe (readMaybeRational . T.pack . (:[]))) $ words input)
         ]
     , testGroup "average"
-        [ "<words>.average|get-tree" $= show [showRational (average $ inputNums input)]
+        [ "%words.average|get-tree" $= show [showRational (average $ inputNums input)]
         , "words.average|get-tree" $=
             show (map (showRational . average . mapMaybe (readMaybeRational . T.pack . (:[]))) $ words input)
         ]
     , testGroup "add"
-        [ "<words.add 1>.sum|get-tree" $= show [showRational (sum $ map (+1) $ inputNums input)]
+        [ "%(words.add 1).sum|get-tree" $= show [showRational (sum $ map (+1) $ inputNums input)]
         ]
     , testGroup "sub"
-        [ "<words.sub 1>.sum|get-tree" $= show [showRational (sum $ map (subtract 1) $ inputNums input)]
+        [ "%(words.sub 1).sum|get-tree" $= show [showRational (sum $ map (subtract 1) $ inputNums input)]
         ]
     , testGroup "mult"
-        [ "<words.mult 2>.sum|get-tree" $= show [showRational (sum $ map (*2) $ inputNums input)]
+        [ "%(words.mult 2).sum|get-tree" $= show [showRational (sum $ map (*2) $ inputNums input)]
         ]
     , testGroup "div"
-        [ "<words.div 2>.sum|get-tree" $= show [showRational (sum $ map (/ 2) $ inputNums input)]
+        [ "%(words.div 2).sum|get-tree" $= show [showRational (sum $ map (/ 2) $ inputNums input)]
         ]
     , testGroup "pow"
-        [ "<words.pow 2>.sum|get-tree" $= show [showRational (sum $ map (^^ 2) $ inputNums input)]
+        [ "%(words.pow 2).sum|get-tree" $= show [showRational (sum $ map (^^ 2) $ inputNums input)]
         ]
     , testGroup "abs"
-        [ "<words.abs>.sum|get-tree" $= show [showRational (sum $ map abs $ inputNums input)]
+        [ "%(words.abs).sum|get-tree" $= show [showRational (sum $ map abs $ inputNums input)]
         ]
     , testGroup "sign"
-        [ "<words.sign>.sum|get-tree" $= show [showRational (sum $ map signum $ inputNums input)]
+        [ "%(words.sign).sum|get-tree" $= show [showRational (sum $ map signum $ inputNums input)]
         ]
     , testGroup "if"
-        [ "words.if 1=1|get-tree" $= show (words input)
-        , "words.if 1=2|get-tree" $= "[]"
-        , "words.if \"1\"=\"1\"|get-tree" $= show (words input)
-        , "words.if \"1\"=\"2\"|get-tree" $= "[]"
-        , "words.if 1=\"1\"|get-tree" $= show (words input)
-        , "words.if 1=\"2\"|get-tree" $= "[]"
-        , "words.if \"1\"=1|get-tree" $= show (words input)
-        , "words.if \"1\"=2|get-tree" $= "[]"
-        , "words.if id<=id|get-tree" $= show (words input)
-        , "words.if id=len|get-tree" $= show (filter (\w -> w == show (length w)) $ words input)
-        , "words.if len=2|get-tree" $= show (filter (\w -> length w == 2) $ words input)
-        , "words.if len=\"3\"|get-tree" $= show (filter (\w -> length w == 3) $ words input)
-        , "<words>.if id<=id|get-tree" $= "[]"
-        , "<words>.if id<id|get-tree" $= "[]"
-        , "<words>.if id>id|get-tree" $= "[]"
-        , "<words>.if id>=id|get-tree" $= "[]"
-        , "words.if len<3 && len>1|get-tree" $= show (filter (\w -> length w < 3 && length w > 1) $ words input)
-        , "words.if len<2 || len>2|get-tree" $= show (filter (\w -> length w < 2 || length w > 2) $ words input)
-        , "words.if len>1 && len<3 || [0].isUpper=1|get-tree" $=
+        [ "words.if =1 1|get-tree" $= show (words input)
+        , "words.if =2 1|get-tree" $= "[]"
+        , "words.if =\"1\" \"1\"|get-tree" $= show (words input)
+        , "words.if =\"2\" \"1\"|get-tree" $= "[]"
+        , "words.if =\"1\" 1|get-tree" $= show (words input)
+        , "words.if =\"2\" 1|get-tree" $= "[]"
+        , "words.if =1 \"1\"|get-tree" $= show (words input)
+        , "words.if =2 \"1\"|get-tree" $= "[]"
+        , "words.if <=id id|get-tree" $= show (words input)
+        , "words.if =len id|get-tree" $= show (filter (\w -> w == show (length w)) $ words input)
+        , "words.if =len 2|get-tree" $= show (filter (\w -> length w == 2) $ words input)
+        , "words.if =len \"3\"|get-tree" $= show (filter (\w -> length w == 3) $ words input)
+        , "%words.if <=id id|get-tree" $= "[]"
+        , "%words.if <id id|get-tree" $= "[]"
+        , "%words.if >id id|get-tree" $= "[]"
+        , "%words.if >=id id|get-tree" $= "[]"
+        , "words.if && < len 3 > len 1|get-tree" $= show (filter (\w -> length w < 3 && length w > 1) $ words input)
+        , "words.if || < len 2 > len 2|get-tree" $= show (filter (\w -> length w < 2 || length w > 2) $ words input)
+        , "words.if || && > len 1 < len 3 ([0].isUpper)|get-tree" $=
             show (filter (\w -> length w > 1 && length w < 3 || isUpper (head w)) $ words input)
-        , "words.if [0].isUpper=1 || len>1 && len<3|get-tree" $=
+        , "words.if || ([0].isUpper) && > len 1 < len 3|get-tree" $=
             show (filter (\w -> isUpper (head w) || length w > 1 && length w < 3) $ words input)
-        , "words.if ([0].isUpper=1 || len>1) && len<3|get-tree" $=
+        , "words.if && || ([0].isUpper) > len 1 < len 3|get-tree" $=
             show (filter (\w -> (isUpper (head w) || length w > 1) && length w < 3) $ words input)
         , "words.if len|get-tree" $= show (filter (\w -> length w == 1) $ words input)
         , "words.if all (each.isUpper)|get-tree" $= show (filter (all isUpper) $ words input)
         , "words.if any (each.isUpper)|get-tree" $= show (filter (any isUpper) $ words input)
-        , "words.if 1=all each.isUpper|get-tree" $= show (filter (all isUpper) $ words input)
-        , "words.if 1=any each.isUpper|get-tree" $= show (filter (any isUpper) $ words input)
-        , "words.if each=each|get-tree" $= show (filter allEqual $ words input)
-        , "words.if =\"ee\"|get-tree" $=$ "words.if id=\"ee\"|get-tree"
+        , "words.if all (each.isUpper)|get-tree" $= show (filter (all isUpper) $ words input)
+        , "words.if any (each.isUpper)|get-tree" $= show (filter (any isUpper) $ words input)
         ]
     , testGroup "isUpper"
         [ "words.if isUpper|get-tree" $= show (filter (all isUpper) $ words input)
@@ -192,10 +191,10 @@
         [ "words.if isDigit|get-tree" $= show (filter (all isDigit) $ words input)
         ]
     , testGroup "collect"
-        [ "<words>|get-tree" $= show [words input]
+        [ "%words|get-tree" $= show [words input]
         ]
     , testGroup "filter"
-        [ "<words>.filter len<3.each|get-tree" $=$ "words.if len<3|get-tree" ]
+        [ "%words.filter < len 3.each|get-tree" $=$ "words.if < len 3|get-tree" ]
     , testGroup "regex"
         [ "regex \"([a-z]|[A-Z]|[0-9]|_)+\"|get" $=$ "words|get"
         , "regex \"([a-z]|[A-Z]|[0-9]|_)+\"|over id" $=$ "id|over id"
@@ -214,14 +213,23 @@
             , "kv.val|get" $$=$ "kv.[1]|get"
             ]
         , testGroup "atKey"
-            [ "atKey \"quiz\"|get" $$=$ "kv.if key=\"quiz\".val|get"
+            [ "atKey \"quiz\"|get" $$=$ "kv.if = key \"quiz\".val|get"
             ]
         , testGroup "atIdx"
             [ "atKey \"quiz\".atKey \"sport\".val.atKey \"options\".atIdx 3|get" $$= "\"Huston Rocket\"\n"
             ]
         , testGroup "el"
-            [ "atKey \"quiz\".atKey \"sport\".val.atKey \"options\".<el>.[3]|get" $$= "\"Huston Rocket\"\n" ]
+            [ "atKey \"quiz\".atKey \"sport\".val.atKey \"options\".%el.[3]|get" $$= "\"Huston Rocket\"\n" ]
         ]
+    , testGroup "startsWith"
+        [ "words.if startsWith \"a\"|get-tree" $= show (filter (isPrefixOf "a") $ words input)
+        ]
+    , testGroup "endsWith"
+        [ "words.if endsWith \"a\"|get-tree" $= show (filter (isSuffixOf "a") $ words input)
+        ]
+    , testGroup "contains"
+        [ "words.if contains \"a\"|get-tree" $= show (filter (isInfixOf "a") $ words input)
+        ]
     ]
 
 mappingTests :: TestTree
@@ -234,7 +242,7 @@
         [ "id|over len" $= show (length input)
         ]
     , testGroup "map"
-        [ "<words>|over map len" $= "1 2 3 3 1 1 1 1\n1 2 3 3 1 1 1 1  1\n1 2 3 3 1 1 1 1  1\n\n"
+        [ "%words|over map len" $= "1 2 3 3 1 1 1 1\n1 2 3 3 1 1 1 1  1\n1 2 3 3 1 1 1 1  1\n\n"
         , "words|over map upper" $=$ "words|over upper"
         , "id|over id" $= input
         ]
@@ -247,7 +255,7 @@
         , "id|over prepend \"hello\"" $= ("hello" ++ input)
         , "id|over append len" $= (input ++ show (length input))
         , "id|over prepend len" $= (show (length input) ++ input)
-        , "id|over prepend <words>" $= input
+        , "id|over prepend %words" $= input
         ]
     , testGroup "upper/lower"
         [ "id|over upper" $= map toUpper input
@@ -278,21 +286,21 @@
         , "id|over {:}" $= input
         ]
     , testGroup "sortLexBy"
-        [ "<words>|over sortLexBy id" $= "1 1 2 2 3 3 Ccc Hhh\nMmm _ _ _ _ _ a bb  dd1\ne f gg ii2 j k ll nn3  o\n\n"
-        , "<words.<each>>|over sortLexBy id" $= input
+        [ "%words|over sortLexBy id" $= "1 1 2 2 3 3 Ccc Hhh\nMmm _ _ _ _ _ a bb  dd1\ne f gg ii2 j k ll nn3  o\n\n"
+        , "%(words.%each)|over sortLexBy id" $= input
         ]
     , testGroup "sortLex"
-        [ "<words>|over sortLexBy id" $=$ "<words>|over sortLex"
+        [ "%words|over sortLexBy id" $=$ "%words|over sortLex"
         ]
     , testGroup "sortBy"
-        [ "<words>|over sortBy id" $= "a bb Ccc dd1 e 1 1 3\n_ f gg Hhh ii2 j 2 2  _\n_ k ll Mmm nn3 o 3 _  _\n\n"
-        , "<words.<each>>|over sortBy id" $= input
+        [ "%words|over sortBy id" $= "a bb Ccc dd1 e 1 1 3\n_ f gg Hhh ii2 j 2 2  _\n_ k ll Mmm nn3 o 3 _  _\n\n"
+        , "%(words.%each)|over sortBy id" $= input
         ]
     , testGroup "sort"
-        [ "<words>|over sortBy id" $=$ "<words>|over sort"
+        [ "%words|over sortBy id" $=$ "%words|over sort"
         ]
     , testGroup "id"
-        [ "<words>|over id" $= input
+        [ "%words|over id" $= input
         ]
     , testGroup "to"
         [ "words|over to len" $=$ "words|over len" ]
