crackNum-3.30: src/CrackNum/Utils.hs
---------------------------------------------------------------------------
-- |
-- Module : CrackNum.Utils
-- Copyright : (c) Levent Erkok
-- License : BSD3
-- Maintainer : erkokl@gmail.com
-- Stability : experimental
--
-- Small helpers: dying, parsing bit-patterns, and input fixups
-----------------------------------------------------------------------------
{-# OPTIONS_GHC -Wall -Werror #-}
module CrackNum.Utils(
copyRight, die, parseToBits, fixup, unrecognized
) where
import Data.Char (isDigit, isSpace, toLower)
import Data.List (unfoldr)
import Numeric (readHex)
import System.Exit (exitFailure)
import System.IO (hPutStr, stderr)
-- | Copyright info
copyRight :: String
copyRight = "(c) Levent Erkok. Released with a BSD3 license."
-- | Terminate early
die :: [String] -> IO a
die xs = do hPutStr stderr $ unlines $ "ERROR:" : map (" " ++) xs
exitFailure
parseToBits :: String -> IO [Bool]
parseToBits inp = do
let isSkippable c = c `elem` "_-" || isSpace c
cleanInput = map toLower (filter (not . isSkippable) inp)
(mbPadTo, isHex, stream) <- case cleanInput of
'0':'x':rest -> pure (Nothing, True, rest)
'0':'b':rest -> pure (Nothing, False, rest)
_ ->
case break (`elem` "'h") cleanInput of
(pre@(_:_), '\'' : 'h' : rest) | all isDigit pre -> pure (Just (read pre), True, rest)
_ -> die [ "Input string must start with 0b, 0x, or N'h for decoding."
, "Received prefix: " ++ show (take 2 cleanInput)
]
let cvtBin '1' = pure [True]
cvtBin '0' = pure [False]
cvtBin c = die ["Input has a non-binary digit: " ++ show c]
cvtHex c = case readHex [c] of
[(v, "")] -> pure $ pad
$ map (== (1::Int))
$ reverse
$ unfoldr (\x -> if x == 0 then Nothing else Just (x `rem` 2, x `div` 2)) v
_ -> die ["Input has a non-hexadecimal digit: " ++ show c]
where pad p = replicate (4 - length p) False ++ p
cvt i | isHex = concat <$> mapM cvtHex i
| True = concat <$> mapM cvtBin i
res <- cvt stream
let pad = case mbPadTo of
Nothing -> []
Just n -> replicate (n - length res) False
pure $ pad ++ res
-- | Convert certain strings to more understandable format by read
-- If first argument is True, then we're reading using reads, i.e., haskell syntax
-- If first argument is False, then we're using big-float library, which has a different notion for infinity and nans
fixup :: Bool -> String -> String
fixup True inp = case map toLower inp of
linp | linp `elem` ["inf", "infinity"] -> "Infinity"
linp | linp `elem` ["-inf", "-infinity"] -> "-Infinity"
linp | linp == "nan" -> "NaN"
_ -> inp
fixup False inp = case map toLower inp of
linp | linp `elem` ["inf", "infinity"] -> "inf"
linp | linp `elem` ["-inf", "-infinity"] -> "-inf"
linp | linp == "nan" -> "NaN"
_ -> inp
unrecognized :: String -> IO ()
unrecognized inp = die [ "Input does not represent floating point number we recognize."
, "Saw: " ++ inp
, ""
, "For decoding bit-strings, prefix them with 0x, N'h, 0b and"
, "provide a hexadecimal or binary representation of the input."
]