liszt-0.2: src/Database/Liszt/Internal.hs
{-# LANGUAGE LambdaCase, DeriveTraversable, DeriveGeneric #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE BangPatterns #-}
module Database.Liszt.Internal (
Key
, Tag
, LisztHandle(..)
, openLiszt
, closeLiszt
, withLiszt
-- * Writing
, clear
, insertRaw
, commit
, Transaction
, TransactionState
-- * Reading
, availableKeys
-- * Node
, Node(..)
, peekNode
, LisztDecodingException(..)
-- * Fetching
, Fetchable(..)
, RawPointer(..)
, fetchPayload
-- * Footer
, fetchRoot
, footerSize
, isFooter
, lookupSpine
-- * Spine
, Spine
, spineLength
, QueryResult
, wholeSpine
, takeSpine
, dropSpine
, takeSpineWhile
, dropSpineWhile
) where
import Control.Concurrent
import Control.DeepSeq
import Control.Monad
import Control.Monad.Catch
import Control.Monad.IO.Class
import Control.Monad.Trans.State.Strict
import Data.Bifunctor
import qualified Data.ByteString as B
import qualified Data.ByteString.Internal as B
import qualified Data.IntMap.Strict as IM
import Data.IORef
import Data.Monoid
import Data.Word
import Data.Winery.Internal.Builder
import Database.Liszt.Internal.Decoder
import Foreign.ForeignPtr
import Foreign.Ptr
import GHC.Generics (Generic)
import System.Directory
import System.IO
type Key = B.ByteString
-- | Tag is an extra value attached to a payload. This can be used to perform
-- a binary search.
type Tag = B.ByteString
type Spine a = [(Int, a)]
newtype KeyPointer = KeyPointer RawPointer deriving (Show, Eq, NFData)
data RawPointer = RP !Int !Int deriving (Show, Eq, Generic)
instance NFData RawPointer where
rnf r = r `seq` ()
data Node t a = Empty
| Leaf1 !KeyPointer !(Spine a)
| Leaf2 !KeyPointer !(Spine a) !KeyPointer !(Spine a)
| Node2 !a !KeyPointer !(Spine a) !a
| Node3 !a !KeyPointer !(Spine a) !a !KeyPointer !(Spine a) !a
| Tip !t !RawPointer
| Bin !t !RawPointer !a !a
deriving (Generic, Show, Eq, Functor, Foldable, Traversable)
encodeNode :: Node Encoding RawPointer -> Encoding
encodeNode Empty = word8 0x00
encodeNode (Leaf1 (KeyPointer p) s) = word8 0x01 <> encodeRP p <> encodeSpine s
encodeNode (Leaf2 (KeyPointer p) s (KeyPointer q) t) = word8 0x02
<> encodeRP p <> encodeSpine s <> encodeRP q <> encodeSpine t
encodeNode (Node2 l (KeyPointer p) s r) = word8 0x12
<> encodeRP l <> encodeRP p <> encodeSpine s <> encodeRP r
encodeNode (Node3 l (KeyPointer p) s m (KeyPointer q) t r) = word8 0x13
<> encodeRP l <> encodeRP p <> encodeSpine s
<> encodeRP m <> encodeRP q <> encodeSpine t <> encodeRP r
encodeNode (Tip t p) = word8 0x80 <> unsignedVarInt (getSize t) <> t <> encodeRP p
encodeNode (Bin t p l r) = word8 0x81 <> unsignedVarInt (getSize t) <> t
<> encodeRP p <> encodeRP l <> encodeRP r
encodeRP :: RawPointer -> Encoding
encodeRP (RP p l) = unsignedVarInt p <> unsignedVarInt l
encodeSpine :: Spine RawPointer -> Encoding
encodeSpine s = unsignedVarInt (length s) <> foldMap (\(r, p) -> unsignedVarInt r <> encodeRP p) s
data LisztDecodingException = LisztDecodingException deriving Show
instance Exception LisztDecodingException
peekNode :: Ptr Word8 -> IO (Node Tag RawPointer)
peekNode = runDecoder $ decodeWord8 >>= \case
0x00 -> return Empty
0x01 -> Leaf1 <$> kp <*> spine
0x02 -> Leaf2 <$> kp <*> spine <*> kp <*> spine
0x12 -> Node2 <$> rp <*> kp <*> spine <*> rp
0x13 -> Node3 <$> rp <*> kp <*> spine <*> rp <*> kp <*> spine <*> rp
0x80 -> Tip <$> bs <*> rp
0x81 -> Bin <$> bs <*> rp <*> rp <*> rp
_ -> throwM LisztDecodingException
where
kp = fmap KeyPointer rp
rp = RP <$> decodeVarInt <*> decodeVarInt
spine = do
len <- decodeVarInt
replicateM len $ (,) <$> decodeVarInt <*> rp
bs = do
len <- decodeVarInt
Decoder $ \src -> do
fp <- B.mallocByteString len
withForeignPtr fp $ \dst -> B.memcpy dst src len
return $ DecodeResult (src `plusPtr` len) (B.PS fp 0 len)
instance Bifunctor Node where
bimap f g = \case
Empty -> Empty
Leaf1 k s -> Leaf1 k (map (fmap g) s)
Leaf2 j s k t -> Leaf2 j (map (fmap g) s) k (map (fmap g) t)
Node2 l k s r -> Node2 (g l) k (map (fmap g) s) (g r)
Node3 l j s m k t r -> Node3 (g l) j (map (fmap g) s) (g m) k (map (fmap g) t) (g r)
Bin t p l r -> Bin (f t) p (g l) (g r)
Tip t p -> Tip (f t) p
data LisztHandle = LisztHandle
{ hPayload :: !Handle
, refBuffer :: MVar (Int, ForeignPtr Word8)
, keyCache :: IORef (IM.IntMap Key)
, refModified :: IORef Bool
, handleLock :: MVar ()
}
openLiszt :: MonadIO m => FilePath -> m LisztHandle
openLiszt path = liftIO $ do
exist <- doesFileExist path
hPayload <- openBinaryFile path ReadWriteMode
unless exist $ B.hPutStr hPayload emptyFooter
buf <- B.mallocByteString 4096
refBuffer <- newMVar (4096, buf)
keyCache <- newIORef IM.empty
handleLock <- newMVar ()
refModified <- newIORef False
return LisztHandle{..}
closeLiszt :: MonadIO m => LisztHandle -> m ()
closeLiszt lh = liftIO $ hClose $ hPayload lh
withLiszt :: (MonadIO m, MonadMask m) => FilePath -> (LisztHandle -> m a) -> m a
withLiszt path = bracket (openLiszt path) closeLiszt
--------------------------------------------------------------------------------
-- Transaction
data StagePointer = Commited !RawPointer | Uncommited !Int deriving Eq
data TransactionState = TS
{ dbHandle :: !LisztHandle
, freshId :: !Int
, pending :: !(IM.IntMap (Node Encoding StagePointer))
, currentRoot :: !(Node Encoding StagePointer)
, currentPos :: !Int
}
type Transaction = StateT TransactionState IO
-- | Replace the specified key with an empty list.
clear :: Key -> Transaction ()
clear key = do
root <- gets currentRoot
insertF key (const $ return []) root >>= \case
Pure t -> modify $ \ts -> ts { currentRoot = t }
Carry l k a r -> modify $ \ts -> ts { currentRoot = Node2 l k a r }
append :: LisztHandle -> (Handle -> IO a) -> IO a
append LisztHandle{..} cont = do
hSeek hPayload SeekFromEnd 0
writeIORef refModified True
cont hPayload
insertRaw :: Key -> Encoding -> Encoding -> Transaction ()
insertRaw key tag payload = do
lh <- gets dbHandle
ofs <- gets currentPos
liftIO $ append lh $ \h -> hPutEncoding h payload
root <- gets currentRoot
modify $ \ts -> ts { currentPos = ofs + getSize payload }
insertF key (insertSpine tag (ofs `RP` getSize payload)) root >>= \case
Pure t -> modify $ \ts -> ts { currentRoot = t }
Carry l k a r -> modify $ \ts -> ts { currentRoot = Node2 l k a r }
addNode :: Node Encoding StagePointer -> Transaction StagePointer
addNode f = state $ \ts -> (Uncommited (freshId ts), ts
{ freshId = freshId ts + 1
, pending = IM.insert (freshId ts) f (pending ts) })
allocKey :: Key -> Transaction KeyPointer
allocKey key = do
lh <- gets dbHandle
ofs <- gets currentPos
liftIO $ append lh $ \h -> do
B.hPutStr h key
modifyIORef' (keyCache lh) (IM.insert ofs key)
modify $ \ts -> ts { currentPos = ofs + B.length key }
return $ KeyPointer $ RP ofs (B.length key)
commit :: MonadIO m => LisztHandle -> Transaction a -> m a
commit h transaction = liftIO $ modifyMVar (handleLock h) $ const $ do
offset0 <- fromIntegral <$> hFileSize h'
root <- fetchRoot h
do
(a, TS _ _ pendings root' offset1) <- runStateT transaction
$ TS h 0 IM.empty (bimap bytes Commited root) offset0
let substP (Commited ofs) = return ofs
substP (Uncommited i) = case IM.lookup i pendings of
Just f -> substF f
Nothing -> error "panic!"
substF Empty = return (RP 0 0)
substF (Leaf1 pk pv) = do
pv' <- traverse (traverse substP) pv
write (Leaf1 pk pv')
substF (Leaf2 pk pv qk qv) = do
pv' <- traverse (traverse substP) pv
qv' <- traverse (traverse substP) qv
write (Leaf2 pk pv' qk qv')
substF (Node2 l pk pv r) = do
l' <- substP l
pv' <- traverse (traverse substP) pv
r' <- substP r
write (Node2 l' pk pv' r')
substF (Node3 l pk pv m qk qv r) = do
l' <- substP l
pv' <- traverse (traverse substP) pv
m' <- substP m
qv' <- traverse (traverse substP) qv
r' <- substP r
write (Node3 l' pk pv' m' qk qv' r')
substF (Bin t p l r) = do
l' <- substP l
r' <- substP r
write (Bin t p l' r')
substF (Tip t p) = write (Tip t p)
writeFooter offset1 $ substF root'
return ((), a)
`onException` writeFooter offset0 (write $ first bytes root)
where
writeFooter ofs m = do
modified <- readIORef (refModified h)
when modified $ do
hSeek h' SeekFromEnd 0
RP _ len <- evalStateT m ofs
B.hPutStr h' $ B.drop len emptyFooter
hFlush h'
writeIORef (refModified h) False
h' = hPayload h
write :: Node Encoding RawPointer -> StateT Int IO RawPointer
write f = do
ofs <- get
let e = encodeNode f
liftIO $ hPutEncoding h' e
put $! ofs + getSize e
return $ RP ofs (getSize e)
fetchKeyT :: KeyPointer -> Transaction Key
fetchKeyT p = gets dbHandle >>= \h -> liftIO (fetchKey h p)
fetchStage :: StagePointer -> Transaction (Node Encoding StagePointer)
fetchStage (Commited p) = do
h <- gets dbHandle
liftIO $ bimap bytes Commited <$> fetchNode h p
fetchStage (Uncommited i) = gets pending >>= return . maybe (error "fetch: not found") id . IM.lookup i
insertF :: Key
-> (Spine StagePointer -> Transaction (Spine StagePointer))
-> Node Encoding StagePointer
-> Transaction (Result StagePointer)
insertF k u Empty = Pure <$> (Leaf1 <$> allocKey k <*> u [])
insertF k u (Leaf1 pk pv) = do
vpk <- fetchKeyT pk
Pure <$> case compare k vpk of
LT -> do
kp <- allocKey k
(\v -> Leaf2 kp v pk pv) <$> u []
EQ -> Leaf1 pk <$> u pv
GT -> do
kp <- allocKey k
Leaf2 pk pv kp <$> u []
insertF k u (Leaf2 pk pv qk qv) = do
vpk <- fetchKeyT pk
case compare k vpk of
LT -> do
v <- u []
kp <- allocKey k
l <- addNode $ Leaf1 kp v
r <- addNode $ Leaf1 qk qv
return $ Carry l pk pv r
EQ -> do
v <- u pv
return $ Pure $ Leaf2 pk v qk qv
GT -> do
vqk <- fetchKeyT qk
case compare k vqk of
LT -> do
v <- u []
l <- addNode $ Leaf1 pk pv
r <- addNode $ Leaf1 qk qv
kp <- allocKey k
return $ Carry l kp v r
EQ -> do
v <- u qv
return $ Pure $ Leaf2 pk pv qk v
GT -> do
v <- u []
kp <- allocKey k
l <- addNode $ Leaf1 pk pv
r <- addNode $ Leaf1 kp v
return $ Carry l qk qv r
insertF k u (Node2 l pk0 pv0 r) = do
vpk0 <- fetchKeyT pk0
case compare k vpk0 of
LT -> do
fl <- fetchStage l
insertF k u fl >>= \case
Pure l' -> do
l'' <- addNode l'
return $ Pure $ Node2 l'' pk0 pv0 r
Carry l' ck cv r' -> return $ Pure $ Node3 l' ck cv r' pk0 pv0 r
EQ -> do
v <- u pv0
return $ Pure $ Node2 l pk0 v r
GT -> do
fr <- fetchStage r
insertF k u fr >>= \case
Pure r' -> do
r'' <- addNode r'
return $ Pure $ Node2 l pk0 pv0 r''
Carry l' ck cv r' -> return $ Pure $ Node3 l pk0 pv0 l' ck cv r'
insertF k u (Node3 l pk0 pv0 m qk0 qv0 r) = do
vpk0 <- fetchKeyT pk0
case compare k vpk0 of
LT -> do
fl <- fetchStage l
insertF k u fl >>= \case
Pure l' -> do
l'' <- addNode l'
return $ Pure $ Node3 l'' pk0 pv0 m qk0 qv0 r
Carry l' ck cv r' -> do
bl <- addNode (Node2 l' ck cv r')
br <- addNode (Node2 m qk0 qv0 r)
return $ Pure $ Node2 bl pk0 pv0 br
EQ -> do
v <- u pv0
return $ Pure $ Node3 l pk0 v m qk0 qv0 r
GT -> do
vqk0 <- fetchKeyT qk0
case compare k vqk0 of
LT -> do
fm <- fetchStage m
insertF k u fm >>= \case
Pure m' -> do
m'' <- addNode m'
return $ Pure $ Node3 l pk0 pv0 m'' qk0 qv0 r
Carry l' ck cv r' -> do
bl <- addNode $ Node2 l pk0 pv0 l'
br <- addNode $ Node2 r' qk0 qv0 r
return $ Pure $ Node2 bl ck cv br
EQ -> do
v <- u qv0
return $ Pure $ Node3 l pk0 pv0 m qk0 v r
GT -> do
fr <- fetchStage r
insertF k u fr >>= \case
Pure r' -> do
r'' <- addNode r'
return $ Pure $ Node3 l pk0 pv0 m qk0 qv0 r''
Carry l' ck cv r' -> do
bl <- addNode $ Node2 l pk0 pv0 m
br <- addNode $ Node2 l' ck cv r'
return $ Pure $ Node2 bl qk0 qv0 br
insertF _ _ (Bin _ _ _ _) = fail "Unexpected Tree"
insertF _ _ (Tip _ _) = fail "Unexpected Leaf"
data Result a = Pure (Node Encoding a)
| Carry !a !KeyPointer !(Spine a) !a
insertSpine :: Encoding -> RawPointer -> Spine StagePointer -> Transaction (Spine StagePointer)
insertSpine tag p ((m, x) : (n, y) : ss) | m == n = do
t <- addNode $ Bin tag p x y
return $ (2 * m + 1, t) : ss
insertSpine tag p ss = do
t <- addNode $ Tip tag p
return $ (1, t) : ss
--------------------------------------------------------------------------------
-- Fetching
class Fetchable a where
fetchNode :: LisztHandle -> a -> IO (Node Tag a)
instance Fetchable RawPointer where
fetchNode h (RP ofs len) = fetchNode'
(hSeek (hPayload h) AbsoluteSeek (fromIntegral ofs)) h len
fetchKey :: LisztHandle -> KeyPointer -> IO Key
fetchKey LisztHandle{..} (KeyPointer (RP ofs len)) = do
cache <- readIORef keyCache
case IM.lookup ofs cache of
Just key -> return key
Nothing -> do
hSeek hPayload AbsoluteSeek (fromIntegral ofs)
key <- B.hGet hPayload len
modifyIORef' keyCache (IM.insert ofs key)
return key
fetchNode' :: IO () -> LisztHandle -> Int -> IO (Node Tag RawPointer)
fetchNode' seek h len = modifyMVar (refBuffer h) $ \(blen, buf) -> do
seek
(blen', buf') <- if blen < len
then do
buf' <- B.mallocByteString len
return (len, buf')
else return (blen, buf)
f <- withForeignPtr buf' $ \ptr -> do
_ <- hGetBuf (hPayload h) ptr len
peekNode ptr
return ((blen', buf'), f)
{-# INLINE fetchNode' #-}
lookupSpine :: Fetchable a => LisztHandle -> Key -> Node tag a -> IO (Maybe (Spine a))
lookupSpine h k (Leaf1 p v) = do
vp <- fetchKey h p
if k == vp then return (Just v) else return Nothing
lookupSpine h k (Leaf2 p u q v) = do
vp <- fetchKey h p
if k == vp then return (Just u) else do
vq <- fetchKey h q
return $ if k == vq then Just v else Nothing
lookupSpine h k (Node2 l p v r) = do
vp <- fetchKey h p
case compare k vp of
LT -> fetchNode h l >>= lookupSpine h k
EQ -> return (Just v)
GT -> fetchNode h r >>= lookupSpine h k
lookupSpine h k (Node3 l p u m q v r) = do
vp <- fetchKey h p
case compare k vp of
LT -> fetchNode h l >>= lookupSpine h k
EQ -> return (Just u)
GT -> do
vq <- fetchKey h q
case compare k vq of
LT -> fetchNode h m >>= lookupSpine h k
EQ -> return (Just v)
GT -> fetchNode h r >>= lookupSpine h k
lookupSpine _ _ _ = return Nothing
availableKeys :: LisztHandle -> Node t RawPointer -> IO [Key]
availableKeys _ Empty = return []
availableKeys h (Leaf1 k _) = pure <$> fetchKey h k
availableKeys h (Leaf2 j _ k _) = sequence [fetchKey h j, fetchKey h k]
availableKeys h (Node2 l k _ r) = do
lks <- fetchNode h l >>= availableKeys h
vk <- fetchKey h k
rks <- fetchNode h r >>= availableKeys h
return $ lks ++ vk : rks
availableKeys h (Node3 l j _ m k _ r) = do
lks <- fetchNode h l >>= availableKeys h
vj <- fetchKey h j
mks <- fetchNode h m >>= availableKeys h
vk <- fetchKey h k
rks <- fetchNode h r >>= availableKeys h
return $ lks ++ vj : mks ++ vk : rks
availableKeys _ _ = fail "availableKeys: unexpected frame"
fetchPayload :: LisztHandle -> RawPointer -> IO B.ByteString
fetchPayload LisztHandle{..} (RP ofs len) = do
hSeek hPayload AbsoluteSeek (fromIntegral ofs)
B.hGet hPayload len
--------------------------------------------------------------------------------
-- Footer (root node)
footerSize :: Int
footerSize = 256
emptyFooter :: B.ByteString
emptyFooter = B.pack [0,14,171,160,140,150,185,18,22,70,203,145,129,232,42,76,81,176,163,195,96,209,8,74,97,123,57,136,107,174,241,142,100,164,181,138,253,170,25,77,12,191,212,224,142,167,215,73,48,0,2,170,226,114,8,29,141,85,243,179,81,11,59,246,62,189,202,254,56,140,227,195,189,118,152,26,106,81,4,121,152,72,247,119,111,128,75,242,29,96,157,190,170,1,57,77,61,132,72,8,233,94,254,18,197,152,128,15,174,9,91,78,125,21,72,250,179,176,176,47,230,45,255,228,214,223,28,61,123,159,104,233,131,39,88,245,13,242,228,48,17,119,159,173,71,172,238,69,137,141,153,133,79,24,81,242,19,21,209,44,120,69,180,103,100,185,189,191,50,132,52,229,248,12,207,134,45,241,2,217,112,21,239,65,39,30,33,16,147,152,52,204,221,56,87,191,235,235,173,181,106,165,37,52,245,221,13,80,91,207,224,95,157,222,3,210,54,28,99,1,7,50,189,163,141,244,200,101,250,61,48,10,243,248,153,98,224,73,227,121,72,156,228,205,43,82,166,48,85,132,0,76,73,83,90,84]
isFooter :: B.ByteString -> Bool
isFooter bs = B.drop (footerSize - 64) bs == B.drop (footerSize - 64) emptyFooter
fetchRoot :: LisztHandle -> IO (Node Tag RawPointer)
fetchRoot h = fetchNode'
(hSeek (hPayload h) SeekFromEnd (-fromIntegral footerSize)) h footerSize
--------------------------------------------------------------------------------
-- Spine operations
spineLength :: Spine a -> Int
spineLength = sum . map fst
type QueryResult = (Tag, RawPointer)
dropSpine :: Fetchable a => LisztHandle -> Int -> Spine a -> IO (Spine a)
dropSpine _ _ [] = return []
dropSpine _ 0 s = return s
dropSpine h n0 ((siz0, t0) : xs0)
| siz0 <= n0 = dropSpine h (n0 - siz0) xs0
| otherwise = dropBin n0 siz0 t0 xs0
where
dropBin 0 siz t xs = return $ (siz, t) : xs
dropBin n siz t xs = fetchNode h t >>= \case
Bin _ _ l r
| n == 1 -> return $ (siz', l) : (siz', r) : xs
| n <= siz' -> dropBin (n - 1) siz' l ((siz', r) : xs)
| otherwise -> dropBin (n - siz' - 1) siz' r xs
Tip _ _ -> return xs
_ -> error $ "dropTree: unexpected frame"
where
siz' = siz `div` 2
takeSpine :: Fetchable a => LisztHandle -> Int -> Spine a -> [QueryResult] -> IO [QueryResult]
takeSpine _ n _ ps | n <= 0 = return ps
takeSpine _ _ [] ps = return ps
takeSpine h n ((siz, t) : xs) ps
| n >= siz = takeAll h t ps >>= takeSpine h (n - siz) xs
| otherwise = takeBin h n siz t ps
takeBin :: Fetchable a => LisztHandle -> Int -> Int -> a -> [QueryResult] -> IO [QueryResult]
takeBin _ n _ _ ps | n <= 0 = return ps
takeBin h n siz t ps = fetchNode h t >>= \case
Bin tag p l r
| n == 1 -> return $ (tag, p) : ps
| n <= siz' -> takeBin h (n - 1) siz' l ((tag, p) : ps)
| otherwise -> do
ps' <- takeAll h l ((tag, p) : ps)
takeBin h (n - siz' - 1) siz' r ps'
Tip tag p -> return $ (tag, p) : ps
_ -> error $ "takeTree: unexpected frame"
where
siz' = siz `div` 2
wholeSpine :: Fetchable a => LisztHandle -> Spine a -> [QueryResult] -> IO [QueryResult]
wholeSpine h ((_, s) : ss) r = wholeSpine h ss r >>= takeAll h s
wholeSpine _ [] r = return r
takeAll :: Fetchable a => LisztHandle -> a -> [QueryResult] -> IO [QueryResult]
takeAll h t ps = fetchNode h t >>= \case
Bin tag p l r -> takeAll h l ((tag, p) : ps) >>= takeAll h r
Tip tag p -> return ((tag, p) : ps)
_ -> error $ "takeAll: unexpected frame"
takeSpineWhile :: Fetchable a
=> (Tag -> Bool)
-> LisztHandle
-> Spine a
-> [QueryResult] -> IO [QueryResult]
takeSpineWhile cond h = go where
go (t0 : ((siz, t) : xs)) ps = fetchNode h t >>= \case
Tip tag p
| cond tag -> takeAll h (snd t0) ps >>= go xs . ((tag, p):)
| otherwise -> inner t0 ps
Bin tag p l r
| cond tag -> takeAll h (snd t0) ps
>>= go ((siz', l) : (siz', r) : xs) . ((tag, p):)
| otherwise -> inner t0 ps
_ -> error "takeSpineWhile: unexpected frame"
where
siz' = siz `div` 2
go [t] ps = inner t ps
go [] ps = return ps
inner (siz, t) ps = fetchNode h t >>= \case
Tip tag p
| cond tag -> return $ (tag, p) : ps
| otherwise -> return ps
Bin tag p l r
| cond tag -> go [(siz', l), (siz', r)] ((tag, p) : ps)
| otherwise -> return ps
_ -> error "takeSpineWhile: unexpected frame"
where
siz' = siz `div` 2
dropSpineWhile :: Fetchable a
=> (Tag -> Bool)
-> LisztHandle
-> Spine a
-> IO (Maybe (Int, QueryResult, Spine a))
dropSpineWhile cond h = go 0 where
go !total (t0@(siz0, _) : ts@((siz, t) : xs)) = fetchNode h t >>= \case
Tip tag _
| cond tag -> go (total + siz0 + siz) xs
| otherwise -> dropBin total t0 ts
Bin tag _ l r
| cond tag -> go (total + siz0 + 1) $ (siz', l) : (siz', r) : xs
| otherwise -> dropBin total t0 ts
_ -> error "dropSpineWhile: unexpected frame"
where
siz' = siz `div` 2
go total (t : ts) = dropBin total t ts
go _ [] = return Nothing
dropBin !total (siz, t) ts = fetchNode h t >>= \case
Tip tag p
| cond tag -> go (total + 1) ts
| otherwise -> return $ Just (total, (tag, p), ts)
Bin tag p l r
| cond tag -> go (total + 1) $ (siz', l) : (siz', r) : ts
| otherwise -> return $ Just (total, (tag, p), (siz', l) : (siz', r) : ts)
_ -> error "dropSpineWhile: unexpected frame"
where
siz' = siz `div` 2