{-# OPTIONS -Wall #-} -------------------------------------------------------------------------------- -- | -- Module : Text.PrettyPrint.JoinPrint.HexDump -- Copyright : (c) Stephen Tetley 2009-2010 -- License : BSD3 -- -- Maintainer : Stephen Tetley -- Stability : highly unstable -- Portability : to be determined. -- -- Hex dumps -- -------------------------------------------------------------------------------- module Text.PrettyPrint.JoinPrint.HexDump ( hex , hex2 , hex4 , hex8 , oxhex , oxhex2 , oxhex4 , oxhex8 , hexdump , hexdumpA ) where import Text.PrettyPrint.JoinPrint.Core import Data.Array.IO import Data.Char import Data.List ( unfoldr ) import Data.Word import Numeric import Prelude hiding ( length ) import qualified Prelude as Pre asterix :: String -> Doc asterix = text . map (const '*') -- | 'hex' : @i -> Doc@ -- -- Print @i@ as hexadecimal, no zero padding. -- -- Negative numbers are printed as a string of asterisks. -- hex :: Integral a => a -> Doc hex i | i >= 0 = text $ showHex i [] | otherwise = asterix $ showHex (abs i) [] -- | Print a Word8 as a 2-digit hex number. -- hex2 :: Word8 -> Doc hex2 = padl 2 '0' . text . ($ []) . showHex -- | Print a Word16 as a 4-digit hex number. -- hex4 :: Word16 -> Doc hex4 = padl 4 '0' . text . ($ []) . showHex -- | Print a Word32 as a 8-digit hex number. -- hex8 :: Word32 -> Doc hex8 = padl 8 '0' . text . ($ []) . showHex -- | 'oxhex' : @pad-length * i -> Doc@ -- -- Print @i@ in hexadecimal, padding with \'0\' to the supplied -- @pad-length@ and prefixing with \"0x\". -- -- Negative numbers are printed as a string of asterisks. -- oxhex :: Integral a => Int -> a -> Doc oxhex plen i | i >= 0 = text "0x" <> padl plen '0' (text $ showHex i []) | otherwise = text "0x" <> padl plen '*' (asterix $ showHex (abs i) []) -- | Print a Word8 as a 2-digit hex number prefixed with \"0x\". -- oxhex2 :: Word8 -> Doc oxhex2 = (text "0x" <>) . hex2 -- | Print a Word16 as a 4-digit hex number prefixed with \"0x\". -- oxhex4 :: Word16 -> Doc oxhex4 = (text "0x" <>) . hex4 -- | Print a Word32 as a 8-digit hex number prefixed with \"0x\". -- oxhex8 :: Word32 -> Doc oxhex8 = (text "0x" <>) . hex8 -------------------------------------------------------------------------------- hexdumpA :: Int -> Int -> IOUArray Int Word8 -> IO VDoc hexdumpA start end arr = getBounds arr >>= next where next (s,e) = lineSpans c1_width (segment16Ixs (max s start) (min e end)) arr c1_width = 2 + sizeHexStr end lineSpans :: Int -> [(Int,Int)] -> IOUArray Int Word8 -> IO VDoc lineSpans _ [] _ = return $ vcat [] lineSpans c1_width (x:xs) arr = do w8s <- spanArr x arr let vdoc = vcat [firstLine (lineStart $ fst x) w8s] docTail xs vdoc where docTail [] vdoc = return vdoc docTail (ix@(s,_):ixs) vdoc = do { w8s <- spanArr ix arr ; let d = tailLine (lineStart s) w8s ; docTail ixs (vdoc `vsnoc` d) } firstLine = hexLine True c1_width tailLine = hexLine False c1_width -- This would be better if it didn't need a list in the first -- place (i.e. it could use an array directly)... hexdump :: Int -> Int -> [Word8] -> VDoc hexdump start end bs = hexdump2 start end segs where segs = segment16 (16 - (start `mod` 16)) bs hexdump2 :: Int -> Int -> [[Word8]] -> VDoc hexdump2 start end segs = vcat $ aZipWith (hexLine True c1_width, hexLine False c1_width) index_nums segs where c1_width = 2 + sizeHexStr end index_nums = lineNumbers start end sizeHexStr :: Int -> Int sizeHexStr n = Pre.length $ showHex (abs n) [] -- zipWith an \anacrusis\ on the first element. -- aZipWith :: (a -> b -> c, a -> b -> c) -> [a] -> [b] -> [c] aZipWith (f,g) (x:xs) (y:ys) = f x y : zipWith g xs ys aZipWith _ _ _ = [] -------------------------------------------------------------------------------- type Width = Int type LineNum = Int hexLine :: Bool -> Width -> LineNum -> [Word8] -> Doc hexLine is_initial c1_max n xs = c1 <+> empty <+> c2 <+> c3 where c1 = padl c1_max ' ' (hex n) c2 = columnPad is_initial (16*3) $ hsep $ map hex2 xs c3 = columnPad is_initial 16 $ text $ map printable xs -- Default is to pad to the right... -- columnPad :: Bool -> Width -> Doc -> Doc columnPad pad_left w d = step $ length d where step l | l < w = if pad_left then padl w ' ' d else padr w ' ' d step _ = d lineNumbers :: Int -> Int -> [Int] lineNumbers s e = unfoldr phi $ lineStart s where phi n | n > e = Nothing | otherwise = Just (n,n+16) lineEnd :: Int -> Int lineEnd l = (l `div` 16) * 16 + 15 lineStart :: Int -> Int lineStart l = (l `div` 16) * 16 segment16Ixs :: Int -> Int -> [(Int,Int)] segment16Ixs start end = unfoldr phi start where phi i | i > end = Nothing | otherwise = let e1 = lineEnd i in Just ((i, min end e1), e1+1) -- Show 16 bytes per line... segment16 :: Int -> [a] -> [[a]] segment16 initial ls = let (top,rest) = splitAt initial ls in top : unfoldr phi rest where phi [] = Nothing phi cs = let (xs,rest) = splitAt 16 cs in Just (xs,rest) spanArr :: (Int,Int) -> IOUArray Int Word8 -> IO [Word8] spanArr (l,u) arr = rstep u [] where rstep i acc | i < l = return acc rstep i acc = do { e <- readArray arr i ; rstep (i-1) (e:acc ) } printable :: Word8 -> Char printable = fn . chr . fromIntegral where fn c | ord c >= 160 = '.' -- GHC 6.12.1 Windows bug? | isPrint c = c | otherwise = '.'