smh 0.2.0 → 0.2.1
raw patch · 7 files changed
+287/−264 lines, 7 files
Files
- CHANGELOG.md +11/−1
- smh.cabal +1/−1
- src/Common.hs +0/−27
- src/Focusers.hs +74/−73
- src/Mappings.hs +14/−19
- src/Parsers.hs +115/−79
- test/Main.hs +72/−64
CHANGELOG.md view
@@ -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
smh.cabal view
@@ -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
src/Common.hs view
@@ -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
src/Focusers.hs view
@@ -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)
src/Mappings.hs view
@@ -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
src/Parsers.hs view
@@ -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
test/Main.hs view
@@ -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" ]