soyuz-0.0.0: DCPU16/Hex.hs
-- | A weird utility module, with stuff I don't know where to put.
--
-- I didn't call it Util because that's just a slippery slope.
module DCPU16.Hex (
-- * Hexdumps
dump, dumpBytes
-- * Homeless utility functions
, warn, getWord16s
) where
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import Data.Vector (Vector)
import qualified Data.Vector as V
-- printing warnings to stderr:
import System.IO (hPutStrLn, stderr)
import System.IO.Unsafe (unsafePerformIO)
import Text.Printf
-- bytestring->V word16
import Data.Serialize
import Data.Word (Word16)
-- | Prints a message to stderr.
--
-- Inserts newlines automatically. Pretends it doesn't perform IO.
warn :: String -> a -> a
warn s a = unsafePerformIO (hPutStrLn stderr s >> return a)
-- | Prints a nice dump of 16-bit words.
--
-- >>> putStrLn . dump . fromList $ [1..32]
-- 0000: 0001 0002 0003 0004 0005 0006 0007 0008
-- 0008: 0009 000a 000b 000c 000d 000e 000f 0010
-- 0010: 0011 0012 0013 0014 0015 0016 0017 0018
-- 0018: 0019 001a 001b 001c 001d 001e 001f 0020
--
-- Complexity is probably terrible, but when people write programs big enough
-- for it to matter, I'll fix it.
dump :: Vector Word16 -> String
dump = concat . V.toList . V.imap pAddr
where
pAddr :: Int -> Word16 -> String
pAddr 0 = ("0000:"++) . pWord
pAddr n | mod n 8 == 0 = (printf "\n%04x:" n++) . pWord
| otherwise = pWord
pWord :: Word16 -> String
pWord = printf " %04x" . toInteger
-- | ByteString version of 'dump'.
--
-- If faced with odd number of bytes, truncates the last one and prints a
-- warning to stderr.
dumpBytes :: ByteString -> String
dumpBytes s = dump . getWord16s $ s where
len = B.length s
s' = case len `mod` 2 of
1 -> warn msg $ B.init s
0 -> s
msg = "Warning: truncating last byte of "++show len++" byte hex string before\n"
++ " dumping. Dump works on 16-bit words only."
-- | Utility for parsing bytestrings as word lists.
--
-- >>> getWord16s . pack $ "asdfasdf"
-- fromList [24947,25702,24947,25702]
--
-- If length in bytes is odd, discards last byte silently.
getWord16s :: ByteString -> Vector Word16
getWord16s s = V.fromList . reverse . either err id $ runGet (f len []) s
where
len = B.length s `div` 2
f 0 acc = return acc
f n acc = do
w <- getWord16be
f (n-1) (w:acc)
err e = error $ "getWord16s impossible error: "++e