{-# LANGUAGE GADTs #-} module Main where import Common (readMaybeRational, showRational) import Data.Char (isAlpha, isAlphaNum, isDigit, isLower, isSpace, isUpper, toLower, toUpper) import Data.Function (on) import Data.List (groupBy, isInfixOf, isPrefixOf, isSuffixOf, transpose) import Data.List.Extra (dropEnd, groupBy, sort, takeEnd, transpose) import Data.Maybe (fromJust, fromMaybe, mapMaybe) import qualified Data.Text as T import Focusers (interleave, myWords) import System.IO.Unsafe (unsafePerformIO) import System.Process (readProcess) import Test.Tasty (TestTree, defaultMain, testGroup) import Test.Tasty.HUnit (testCase, (@?=)) import Text.Read (readMaybe) main :: IO () main = defaultMain $ testGroup "All" [focuserTests, mappingTests] focuserTests :: TestTree focuserTests = testGroup "Focuser Tests" [ testGroup "id" [ "id|get-tree" $= show [input] ] , testGroup "each" [ "each|get" $= concatMap (: "\n") input , "%words.each|get" $=$ "words|get" ] , testGroup "words" [ "words|get-tree" $= show (words input) , "words|over id" $= input ] , testGroup "lines" [ "lines|get-tree" $= show (lines input) , "lines|over id" $= input ] , testGroup "ws" [ "ws|get-tree" $= show (getWS input) , "ws|over id" $= input ] , testGroup "cols" [ "cols|get-tree" $= show (getCols input) , "cols|over id" $= input ] , testGroup "slice" [ "{0}|get-tree" $= show [take 1 input] , "{2}|get-tree" $= show [take 1 $ drop 2 input] , "{1:3}|get-tree" $= show [take 2 $ drop 1 input] , "{:5}|get-tree" $= show [take 5 input] , "{7:}|get-tree" $= show [drop 7 input] , "{-2}|get-tree" $= show [take 1 $ takeEnd 2 input] , "{-3:}|get-tree" $= show [takeEnd 3 input] , "{:-5}|get-tree" $= show [dropEnd 5 input] , "{-10:-4}|get-tree" $= show [take 6 $ takeEnd 10 input] , "{-1000:5}|get-tree" $=$ "{:5}|get-tree" , "{10:1000}|get-tree" $=$ "{10:}|get-tree" , "{1:5,7:10}|get-tree" $= show [take 4 (drop 1 input) ++ take 3 (drop 7 input)] , "{}|get-tree" $= "[\"\"]" , "{:}|get-tree" $= show [input] ] , testGroup "sortLexBy" [ "%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" ] , testGroup "sortedBy" [ "%(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" ] , testGroup "sorted" [ "%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" ] , testGroup "index" [ "[3]|get-tree" $= show [take 1 $ drop 3 input] , "[-3]|get-tree" $= show [take 1 $ takeEnd 3 input] , "[-1000]|get-tree" $= "[]" , "[1000]|get-tree" $= "[]" ] , testGroup "to" [ "to upper|get-tree" $= show [map toUpper input] , "to lower|get-tree" $= show [map toLower input] , "to reverse|get-tree" $= show [reverse input] ] , testGroup "len" [ "len|get-tree" $=$ "to len|get-tree" , "len|get-tree" $= show [show $ length input] ] , testGroup "()" [ "(words).len|get-tree" $=$ "words.len|get-tree" , "words.(len)|get-tree" $=$ "words.len|get-tree" , "(words.len)|get-tree" $=$ "words.len|get-tree" ] , testGroup "sum" [ "%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 (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 (map (showRational . average . mapMaybe (readMaybeRational . T.pack . (:[]))) $ words input) ] , testGroup "add" [ "%(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)] ] , testGroup "mult" [ "%(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)] ] , testGroup "pow" [ "%(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)] ] , testGroup "sign" [ "%(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 =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 && < 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) && > len 1 < len 3|get-tree" $= show (filter (\w -> isUpper (head w) || length w > 1 && length w < 3) $ words input) , "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 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) ] , testGroup "isLower" [ "words.if isLower|get-tree" $= show (filter (all isLower) $ words input) ] , testGroup "isAlpha" [ "words.if isAlpha|get-tree" $= show (filter (all isAlpha) $ words input) ] , testGroup "isAlphaNum" [ "words.if isAlphaNum|get-tree" $= show (filter (all isAlphaNum) $ words input) ] , testGroup "isSpace" [ "words.if isSpace|get-tree" $= show (filter (all isSpace) $ words input) ] , testGroup "isDigit" [ "words.if isDigit|get-tree" $= show (filter (all isDigit) $ words input) ] , testGroup "collect" [ "%words|get-tree" $= show [words input] ] , testGroup "filter" [ "%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" ] , testGroup "json" [ testGroup "kv" [ "kv.[0]|get" $$= "\"quiz\"\n" , "kv.[1].kv.[0]|get" $$= "\"sport\"\n\"maths\"\n" ] , testGroup "key" [ "key|get" $$=$ "kv.key|get" , "kv.key|get" $$=$ "kv.[0].{1:-1}|get" ] , testGroup "val" [ "val|get" $$=$ "kv.val|get" , "kv.val|get" $$=$ "kv.[1]|get" ] , testGroup "atKey" [ "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" ] ] , 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 mappingTests = testGroup "Mapping Tests" [ testGroup "reverse" [ "id|over reverse" $= reverse input , "id|over reverse:reverse" $= input ] , testGroup "length" [ "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 upper" $=$ "words|over upper" , "id|over id" $= input ] , testGroup "append/prepend" [ "id|over append \"\"" $= input , "id|over prepend \"\"" $= input , "id|over append 1" $= (input ++ "1") , "id|over prepend 1" $= ("1" ++ input) , "id|over append \"hello\"" $= (input ++ "hello") , "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 ] , testGroup "upper/lower" [ "id|over upper" $= map toUpper input , "id|over lower" $= map toLower input ] , testGroup "add/sub/div/pow/abs/sign" [ "id|over add 1" $= mapNums (+ 1) input , "id|over sub 1" $= mapNums (subtract 1) input , "id|over div 2" $= mapNums (/ 2) input , "id|over pow 2" $= mapNums (^^ 2) input , "id|over abs" $= mapNums abs input , "id|over sign" $= mapNums signum input ] , testGroup "slice" [ "id|over {0}" $= take 1 input , "id|over {2}" $= take 1 (drop 2 input) , "id|over {1:3}" $= take 2 ( drop 1 input) , "id|over {:5}" $= take 5 input , "id|over {7:}" $= drop 7 input , "id|over {-2}" $= take 1 ( takeEnd 2 input) , "id|over {-3:}" $= takeEnd 3 input , "id|over {:-5}" $= dropEnd 5 input , "id|over {-10:-4}" $= take 6 ( takeEnd 10 input) , "id|over {-1000:5}" $=$ "id|over {:5}" , "id|over {10:1000}" $=$ "id|over {10:}" , "id|over {1:5,7:10}" $= take 4 (drop 1 input) ++ take 3 (drop 7 input) , "id|over {}" $= "" , "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 ] , testGroup "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 ] , testGroup "sort" [ "%words|over sortBy id" $=$ "%words|over sort" ] , testGroup "id" [ "%words|over id" $= input ] , testGroup "to" [ "words|over to len" $=$ "words|over len" ] ] allEqual :: (Eq a) => [a] -> Bool allEqual [] = True allEqual (x:xs) = all (== x) xs anyEqual :: (Eq a) => [a] -> Bool anyEqual [] = False anyEqual (x:xs) = x `elem` xs || anyEqual xs mapNums :: (Rational -> Rational) -> String -> String mapNums f str = let (ws, words) = myWords $ T.pack str new_words = map (mapNum f . T.unpack) words in T.unpack $ T.concat $ interleave ws words where mapNum :: (Rational -> Rational) -> String -> String mapNum f str = case readMaybe str of Nothing -> str Just n -> T.unpack $ showRational $ f n getWS :: String -> [String] getWS str = let groups = groupBy (\c1 c2 -> isSpace c1 == isSpace c2) str in filter (isSpace . head) groups getCols :: String -> [[String]] getCols = transpose . map words . lines inputNums :: String -> [Rational] inputNums str = mapMaybe (readMaybeRational . T.pack) $ words str average :: [Rational] -> Rational average [] = 0 average ns = sum ns / fromIntegral (length ns) {-# NOINLINE input #-} input :: String input = unsafePerformIO $ readFile "test/input.txt" {-# NOINLINE json #-} json :: String json = unsafePerformIO $ readFile "test/input.json" infixl 1 $= ($=) :: String -> String -> TestTree ($=) command desiredOutput = testCase command $ do output <- readProcess "./smh" [command] input output @?= desiredOutput infixl 1 $=$ ($=$) :: String -> String -> TestTree ($=$) cmd1 cmd2 = testCase (cmd1 ++ " == " ++ cmd2) $ do out1 <- readProcess "./smh" [cmd1] input out2 <- readProcess "./smh" [cmd2] input out1 @?= out2 -- This is stupid, but whatever infixl 1 $$= ($$=) :: String -> String -> TestTree ($$=) command desiredOutput = testCase command $ do output <- readProcess "./smh" [command] json output @?= desiredOutput infixl 1 $$=$ ($$=$) :: String -> String -> TestTree ($$=$) cmd1 cmd2 = testCase (cmd1 ++ " == " ++ cmd2) $ do out1 <- readProcess "./smh" [cmd1] json out2 <- readProcess "./smh" [cmd2] json out1 @?= out2