-- | Implements Base 64-encoded VLQ for 32-bit -- integers. Implementation copied from -- https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java -- {-# OPTIONS -Wall #-} module VLQ (encode ,decode) where import Data.Bits hiding (shift) import Data.ByteString.Lazy (ByteString) import qualified Data.ByteString.Lazy as B import Data.Int import Data.List import Data.Word import Prelude hiding ((>>)) -- | A Base64 VLQ digit can represent 5 bits, so it is base-32. baseShift :: Int baseShift = 5 -- | Base point. base :: Int32 base = 1 << baseShift -- | A mask of bits for a VLQ digit (11111), 31 decimal. baseMask :: Int32 baseMask = base - 1 -- | The continuation bit is the 6th bit. continuationBit :: Int32 continuationBit = base -- | Converts from a two-complement value to a value where the sign -- bit is is placed in the least significant bit. For example, as -- decimals: -- 1 becomes 2 (10 binary), -1 becomes 3 (11 binary) -- 2 becomes 4 (100 binary), -2 becomes 5 (101 binary) toVlqSigned :: Int32 -> Int32 toVlqSigned value = if value < 0 then ((-value) << 1) + 1 else (value << 1) + 0 -- | Converts to a two-complement value from a value where the sign -- bit is is placed in the least significant bit. For example, as -- decimals: -- 2 (10 binary) becomes 1, 3 (11 binary) becomes -1 -- 4 (100 binary) becomes 2, 5 (101 binary) becomes -2 fromVlgSigned :: Int32 -> Int32 fromVlgSigned value = let value' = value >> 1 in if (value & 1) == 1 then -value' else value' -- | Produces a ByteString containing a VLQ-encoded value of the given 32-bit integer. encode :: Int32 -> ByteString encode = B.map encodeBase64 . start where start 0 = B.singleton (fst (continue 0)) start n = B.unfoldr go . toVlqSigned $ n go value | value <= 0 = Nothing | otherwise = Just (continue value) continue value = let digit = value & baseMask value' = value >> baseShift digit' = if value' > 0 then digit .|. continuationBit else digit in (fromIntegral digit',value') -- | Decodes the given VLQ-encoded value into a 32-bit integer. decode :: ByteString -> Int32 decode = fromVlgSigned . go (0,0) . B.map decodeBase64 where go (result,shift) bytes = case B.uncons bytes of Nothing -> result Just (c,next) -> let digit = fromIntegral c continuation = (digit & continuationBit) /= 0 digit' = digit & baseMask result' = result + (digit' << shift) shift' = shift + baseShift in if continuation then go (result',shift') next else result' -- | Base 64 characters. base64Chars :: [Word8] base64Chars = map (fromIntegral.fromEnum) "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -- | Encode the given number to a base 64 character. encodeBase64 :: Word8 -> Word8 encodeBase64 i = maybe (error "Base 64 char must be between 0 and 63.") id (lookup i (zip [0..] base64Chars)) -- | Encode the given base 64 character to a number. decodeBase64 :: Word8 -> Word8 decodeBase64 i = maybe (error "Not a valid base 65 digit.") id (lookup i (zip base64Chars [0..])) -- | Makes the code more familiar to read. Shift-left. (<<) :: Int32 -> Int -> Int32 (<<) = shiftL -- | Makes the code more familiar to read. Shift-right. (>>) :: Int32 -> Int -> Int32 (>>) = shiftR -- | Makes the code more familiar to read. And. (&) :: Int32 -> Int32 -> Int32 (&) = (.&.)