urbit/pkg/hs-urbit/lib/Data/Noun/Jam/Get.hs

221 lines
5.6 KiB
Haskell
Raw Normal View History

module Data.Noun.Jam.Get where
import ClassyPrelude
import Data.Noun (Noun)
import Data.Bits (shiftR, (.|.), (.&.))
import Foreign.Ptr (Ptr, plusPtr, ptrToWordPtr)
import Foreign.Storable (peek)
import Data.Map (Map)
import Control.Monad (guard)
import qualified Data.HashTable.IO as H
-- Types -----------------------------------------------------------------------
{-|
The decoder state.
- An array of words (internal structure of our atoms).
- A pointer to the word *after* the last word in the array.
- A pointer into the current word of that array.
- A bit-offset into that word.
-}
data S = S
{ currPtr :: {-# UNPACK #-} !(Ptr Word)
, usedBits :: {-# UNPACK #-} !Word
, pos :: {-# UNPACK #-} !Word
} deriving (Show,Eq,Ord)
type Env = (Ptr Word, S)
data DecodeExn
= NotEnoughSpace Env
| TooMuchSpace Env
| BadEncoding Env String
deriving (Show, Eq, Ord)
data GetResult a = GetResult {-# UNPACK #-} !S !a
deriving Functor
newtype Get a = Get
{ runGet :: Ptr Word
-> H.LinearHashTable Word Noun
-> S
-> IO (GetResult a)
}
type Bits = Vector Bool
--------------------------------------------------------------------------------
instance Exception DecodeExn
instance Functor Get where
fmap f g = Get $ \end tbl s -> do
GetResult s' a <- runGet g end tbl s
return $ GetResult s' (f a)
{-# INLINE fmap #-}
instance Applicative Get where
pure x = Get (\_ _ s -> return $ GetResult s x)
{-# INLINE pure #-}
Get f <*> Get g = Get $ \end tbl s1 -> do
GetResult s2 f' <- f end tbl s1
GetResult s3 g' <- g end tbl s2
return $ GetResult s3 (f' g')
{-# INLINE (<*>) #-}
Get f *> Get g = Get $ \end tbl s1 -> do
GetResult s2 _ <- f end tbl s1
g end tbl s2
{-# INLINE (*>) #-}
instance Monad Get where
return = pure
{-# INLINE return #-}
(>>) = (*>)
{-# INLINE (>>) #-}
Get x >>= f = Get $ \end tbl s -> do
GetResult s' x' <- x end tbl s
runGet (f x') end tbl s'
{-# INLINE (>>=) #-}
fail msg = Get $ \end tbl s ->
badEncoding end s msg
{-# INLINE fail #-}
--------------------------------------------------------------------------------
badEncoding :: Ptr Word -> S -> String -> IO a
badEncoding endPtr s msg = throwIO $ BadEncoding (endPtr,s) msg
--------------------------------------------------------------------------------
getPos :: Get Word
getPos = Get $ \_ _ s ->
pure (GetResult s (pos s))
insRef :: Word -> Noun -> Get ()
insRef pos now = Get \_ tbl s -> do
H.insert tbl pos now
pure $ GetResult s ()
getRef :: Word -> Get Noun
getRef ref = Get \_ tbl s -> do
H.lookup tbl ref >>= \case
Nothing -> fail "Invalid Reference"
Just no -> pure (GetResult s no)
advance :: Word -> Get ()
advance n = Get \_ _ s -> do
let newUsed = n + usedBits s
newS = s { pos = pos s + n
, usedBits = newUsed `mod` 64
, currPtr = plusPtr (currPtr s)
(fromIntegral $ newUsed `div` 64)
}
pure (GetResult newS ())
--------------------------------------------------------------------------------
-- TODO Should this be (>= end) or (> end)?
peekCurWord :: Get Word
peekCurWord = Get \end _ s ->
if ptrToWordPtr (currPtr s) >= ptrToWordPtr end
then pure (GetResult s 0)
else GetResult s <$> peek (currPtr s)
-- TODO Same question as above.
peekNextWord :: Get Word
peekNextWord = Get \end _ s ->
if ptrToWordPtr (currPtr s) > ptrToWordPtr end
then pure (GetResult s 0)
else GetResult s <$> peek (currPtr s `plusPtr` 1)
peekUsedBits :: Get Word
peekUsedBits = Get \_ _ s -> pure (GetResult s (usedBits s))
{-|
Get a bit.
- Peek the current word.
- Right-shift by the bit-offset.
- Mask the high bits.
-}
dBit :: Get Bool
dBit = do
wor <- peekCurWord
use <- fromIntegral <$> peekUsedBits
advance 1
pure (0 /= shiftR wor use .&. 1)
{-|
Get n bits, where n > 64:
- Get (n/64) words.
- Advance by n bits.
- Calculate an offset (equal to the current bit-offset)
- Calculate the length (equal to n)
- Construct a bit-vector using the buffer*length*offset.
-}
dBits :: Word -> Get Bits
dBits = undefined
{-|
In order to peek at the next Word64:
- If we are past the end of the buffer:
- Return zero.
- If the bit-offset is zero:
- Just peek.
- If we are pointing to the last word:
- Peek and right-shift by the bit offset.
- Otherwise,
- Peek the current word *and* the next word.
- Right-shift the current word by the bit-offset.
- Left-shift the next word by the bit-offset.
- Binary or the resulting two words.
-}
peekWord :: Get Word
peekWord = do
off <- peekUsedBits
cur <- peekCurWord
if off == 0 then pure cur else
do
nex <- peekNextWord
advance 64
pure (dropLowBits off cur .|. dropHighBits off nex)
dropLowBits :: Word -> Word -> Word
dropLowBits bits wor = shiftR wor (fromIntegral bits :: Int)
takeLowBits :: Word -> Word -> Word
takeLowBits 64 wor = wor
takeLowBits wid wor = (2^wid - 1) .&. wor
takeHighBits :: Word -> Word -> Word
takeHighBits off wor = dropLowBits (64-off) wor
dropHighBits :: Word -> Word -> Word
dropHighBits off wor = takeLowBits (64-off) wor
{-|
Make a word from the next n bits (where n <= 64).
- Peek at the next word.
- Mask the n lowest bits from the word.
- Advance by that number of bits.
- Return the word.
-}
dWordBits :: Word -> Get Word
dWordBits n = do
w <- peekWord
advance n
pure (takeLowBits n w)