I'm trying to learn Haskell and I thought I would try and replicate the Linux 'xxd' utility. However I am stuck on the right-hand column (which displays ASCII or a blank for non-printing characters).
As a reminder, typical xxd output looks like this:
00000000: 4927 6d20 7472 7969 6e67 2074 6f20 6c65 I'm trying to le
00000010: 6172 6e20 4861 736b 656c 6c2e 2041 7320 arn Haskell. As
...
I also want to take advantage of the Unicode "Control Pictures" block to display little symbols for the control codes 0..31 rather than dots or placeholders. So I have a helper function that converts a Word8 to a Char and at the same time replaces the control characters with the equivalent from the Control Pictures block.
http://www.unicode-symbol.com/block/Control_Pictures.html
Constraints: Eventually the program will read a file from disk so I am expecting to read in a ByteString or lazy ByteString. Also I want to use Data.Text to hold the output rather than String.
Ideally: I would like to avoid converting the ByteString to something else wholesale e.g. [Word8] because - ultimately - I need to learn how to work with it, not around it.
My problem is that I can't get map
to work for me. Neither B.map nor T.map will work because they expect a function ([a] -> [a])
. Prelude.map
looks more promising as it expects ([a] -> [b])
but I can't get it to work with the imported types. So I had a go at defining my own map
(just to try and find something that would work which I could then replace with a built-in function when I had a better understanding) but that isn't working either.
The non-functioning code I have is as follows
import qualified Data.ByteString as B
import qualified Data.Text as T
import Data.Word
import Data.Char
{- Make some normally undisplayable bytes into displayable chars -}
displayableChar :: Word8 -> Char
displayableChar w
| i < 32 = chr (0x2400 + i) -- 0x2420 control codes
| i < 33 = chr 0x2423 -- 0x2423 trough for space
| i < 127 = chr i
| i < 128 = chr 0x2421 -- 0x2421 del
| otherwise = ' '
where i = fromIntegral w
mymap :: (Word8 -> Char) -> B.ByteString -> [Char]
mymap f bstr
| bstr == B.empty = []
| otherwise = f x : map f xs
where
x = B.head bstr
xs = B.tail bstr
test_data = B.pack [1..250]
Advice and suggestions welcome please on what is the 'right' way to apply displayableChar
to each byte in the ByeString and get Text out.