Browse Source

Use argparse-applicative. Add instant evaluation & delimiter args.

Josh Bicking 6 years ago
parent
commit
eface5f694
4 changed files with 103 additions and 44 deletions
  1. 5 2
      README.md
  2. 93 40
      app/Main.hs
  3. 4 1
      pi-digits.cabal
  4. 1 1
      stack.yaml

+ 5 - 2
README.md

@@ -16,6 +16,9 @@ Run `stack exec pi-digits-exe`, or execute the binary found in `./.stack-work/in
 
 - [X] Add a function for converting results to decimal (and binary).
 - [X] Implement threading.
-- [ ] Implement `optparse-applicative` for more formal/extensive arg parsing.
+- [X] Implement `optparse-applicative` for more formal/extensive arg parsing.
+- [X] ~~Replace the mess in `prompt` with a monad.~~ Issue corrected by using `optparse-applicative`.
 - [ ] Implement a faster mod operation, to allow for larger numbers (like 12345678901234567890). It will likely be implemented with the algorithm explained in the paper.
-- [ ] Replace the mess in `prompt` with a monad.
+- [ ] Customize print behavior and frequency. Flush output every N digits, or something similar.
+- [ ] Find a better way to transfer "globals", like `delim` and `printFun`.
+- [ ] Separate into different files. Possibly parsing and logic files.

+ 93 - 40
app/Main.hs

@@ -4,11 +4,12 @@ import Data.List (isInfixOf, genericIndex)
 import Data.List.Split(splitOn)
 import Text.Read (readMaybe)
 import System.IO (hFlush, stdout)
-import System.Environment (getArgs)
-import System.Exit (exitWith, ExitCode(ExitSuccess))
 import Control.Parallel
 
--- Get the hex representation of Pi at place n.
+import Options.Applicative
+import Data.Semigroup ((<>))
+
+-- Get the hex digit of Pi at place n.
 hexPi :: Integer -> Integer
 hexPi n =
   let
@@ -19,7 +20,7 @@ hexPi n =
       ssix = (sumPi n 6)
       in
       sone `par` sfour `par` sfive `par` ssix `par` sone - sfour - sfive - ssix
-    
+
     skimmedSum = summation - (fromIntegral (floor summation :: Integer)) -- Take only the decimal portion
   in
     floor (16 * skimmedSum) :: Integer
@@ -37,35 +38,47 @@ sumPi n x =
 
 
 -- Get a range of digits.
-rangePi :: (Integer -> String) -> Maybe Integer -> Maybe Integer -> String
-rangePi printFun (Just low) (Just high) =
+rangePi :: (Integer -> String) -> String -> Maybe Integer -> Maybe Integer -> String
+rangePi printFun delim (Just low) (Just high) =
   if low >= high then
     "Error: Please give a proper range."
   else
-    foldr (++) [] (map printFun (drop (fromIntegral low) . take (fromIntegral high) $ hexDigits))
+    init $ foldr (++) [] $ separate (map printFun (drop (fromIntegral low) . take (fromIntegral high) $ hexDigits)) delim
     -- foldl (.) id (map (showString . printFun) (drop (fromIntegral low) . take (fromIntegral high) $ hexDigits)) ""  -- Alternative implementation: not sure about speed
 
-rangePi _ _ _  = printErr
+rangePi _ _ _ _ = printErr
 
 
-prompt :: (Integer -> String) -> IO ()
-prompt printFun = do
-  putStr ":: "
-  hFlush stdout
-  response <- getLine
-  -- Check if response is range, or single digit.
+-- Separate a list with some delimiter.
+separate :: [a] -> a -> [a]
+separate (x:xs) delim = x : delim : separate xs delim
+separate [] _ = []
+
+
+-- Check if response is valid, then call the appropriate function.
+parseIndex :: (Integer -> String) -> String -> String -> String
+parseIndex printFun delim response =
   if (isInfixOf ".." response) then
     let
       range = splitOn ".." response
       low = readMaybe $ range !! 0 :: Maybe Integer
       high = readMaybe $ range !! 1 :: Maybe Integer
     in
-      putStrLn $ rangePi printFun low high
+      rangePi printFun delim low high
     else do
       case (readMaybe response :: Maybe Integer) of
-        Nothing -> putStrLn printErr
-        Just x -> putStrLn $ printFun $ hexDigits `genericIndex` x
-  prompt printFun
+        Nothing -> printErr
+        Just x -> printFun $ hexDigits `genericIndex` x
+
+
+-- Continuously prompt for input.
+prompt :: (Integer -> String) -> String -> IO ()
+prompt printFun delim = do
+  putStr ">> "
+  hFlush stdout
+  response <- getLine
+  putStrLn $ parseIndex printFun delim response
+  prompt printFun delim
 
 
 -- The list of answers.
@@ -79,28 +92,68 @@ printErr = "Error: Please give an Integer (ex: 3) or a range (ex: 3..5)."
 
 
 main :: IO ()
-main = do
-  -- Get formatter function (hex, binary, or decimal)
-  args <- getArgs
+main = argHandle =<< execParser opts
+  where
+    opts = info (arguments <**> helper)
+      ( fullDesc
+     <> progDesc "Generate hexadecimal Pi digits.")
+
+
+-- Arguments to parse.
+data Arguments = Arguments
+  { eval  :: Maybe String
+  , print :: Maybe PrintFunc
+  , delimiter :: String}
+
+arguments :: Parser Arguments
+arguments = Arguments
+      <$> optional ( argument str (
+                       help "Evaluate an index/range, and exit."
+                       <> (metavar "eval")))
+      <*> printFunc
+      <*> strOption ( long "delimiter"
+                        <> metavar "delim"
+                        <> value ""
+                        <> help "Delimiter to separate printed values.")
+
+
+
+-- Decide which printing function to use.
+data PrintFunc = DecPrint | BinPrint
+
+printFunc :: Parser (Maybe PrintFunc)
+printFunc = optional (decPrint <|> binPrint)
+
+decPrint :: Parser PrintFunc
+decPrint = flag' DecPrint ( long "decimal"
+                            <> short 'd'
+                            <> help "Output in decimal.")
+
+binPrint :: Parser PrintFunc
+binPrint = flag' BinPrint ( long "binary"
+                            <> short 'b'
+                            <> help "Output in binary.")
+
+
+-- Handle args, either prompt or eval & quit.
+argHandle :: Arguments -> IO ()
+argHandle (Arguments toEval outputType delim) = do
   let
     printFunIO =
-      case args of
-          ["-b"] -> do
-            putStrLn "Outputting in binary."
-            return (\n -> showIntAtBase 2 (\x -> show x !! 0) n "")
-          ["-d"] -> do
-            putStrLn "Outputting in decimal."
-            return (\n -> show n ++ " ")
-          ["-h"] -> do
-            putStrLn "Generate hexadecimal Pi digits. Output in hexidemical by default.\n\
-                     \\t-b\tOutput in binary.\n\
-                     \\t-d\tOutput in decimal.\n\
-                     \\t-h\tShow this help message."
-            exitWith ExitSuccess
-          _ -> do
-            putStrLn "Outputting in hex."
-            return (\n -> showHex n "")
+      case outputType of
+        Just DecPrint -> do
+          putStrLn "Outputting in decimal."
+          return (\n -> show n ++ "")
+        Just BinPrint -> do
+          putStrLn "Outputting in binary."
+          return (\n -> showIntAtBase 2 (\x -> show x !! 0) n "")
+        _ -> do
+          putStrLn "Outputting in hex."
+          return (\n -> showHex n "")
     in do
-      printFun <- printFunIO
-      putStrLn "Enter a digit or range (Ctrl-C to exit)."
-      prompt printFun
+    printFun <- printFunIO
+    case toEval of
+      (Just s) -> putStrLn $ parseIndex printFun delim s
+      _ -> do
+        putStrLn "Enter a digit or range (Ctrl-C to exit)."
+        prompt printFun delim

+ 4 - 1
pi-digits.cabal

@@ -24,9 +24,12 @@ executable pi-digits-exe
   main-is:             Main.hs
   ghc-options:         -threaded -rtsopts -with-rtsopts=-N
   build-depends:       base
+                     , optparse-applicative
+                     , parallel
                      , pi-digits
                      , split
-                     , parallel
+                     , transformers-compat
+                     , ansi-terminal
   default-language:    Haskell2010
 
 test-suite pi-digits-test

+ 1 - 1
stack.yaml

@@ -39,7 +39,7 @@ packages:
 - .
 # Dependency packages to be pulled from upstream that are not in the resolver
 # (e.g., acme-missiles-0.3)
-extra-deps: [split-0.2.3.2]
+extra-deps: [split-0.2.3.2, optparse-applicative-0.13.2.0, transformers-compat-0.5.1.4, ansi-terminal-0.6.3.1]
 
 # Override default flag values for local packages and extra-deps
 flags: {}