packages feed

parsley-core-2.1.0.0: src/ghc/Parsley/Internal/Common/RewindQueue/Impl.hs

{-# LANGUAGE DerivingStrategies, RecordWildCards #-}
{-|
Module      : Parsley.Internal.Common.Queue.Impl
Description : Implementation of a queue which can be rewound.
License     : BSD-3-Clause
Maintainer  : Jamie Willis
Stability   : experimental

Implementation of a FIFO queue structure, with amortized operations that also supports a rewinding
operation backed by a LIFO stack.

@since 1.5.0.0
-}
module Parsley.Internal.Common.RewindQueue.Impl (module Parsley.Internal.Common.RewindQueue.Impl) where

import Prelude hiding (null, foldr)
import Data.List (foldl')
import Parsley.Internal.Common.Queue.Impl as Queue (Queue(..), toList)

import qualified Parsley.Internal.Common.Queue.Impl as Queue (
    empty, enqueue, enqueueAll, dequeue, null, size, foldr, poke
  )

{-|
Concrete FIFO Queue, with amortized constant operations.

Also keeps history of dequeued values, which can be undone
in a LIFO manner.

@since 1.5.0.0
-}
data RewindQueue a = RewindQueue {
    queue :: Queue a,
    undo :: [a],
    undosz :: Int
  } deriving stock (Eq, Show)

{-|
Construct an empty queue.

@since 1.5.0.0
-}
empty :: RewindQueue a
empty = RewindQueue Queue.empty [] 0

{-|
Adds an element onto the end of the queue.

@since 1.5.0.0
-}
enqueue :: a -> RewindQueue a -> RewindQueue a
enqueue x q = q { queue = Queue.enqueue x (queue q) }

{-|
Adds each of the elements onto the queue, from left-to-right.

@since 1.5.0.0
-}
enqueueAll :: [a] -> RewindQueue a -> RewindQueue a
enqueueAll xs q = q { queue = Queue.enqueueAll xs (queue q) }

{-|
Removes an element from the front of the queue.

@since 1.5.0.0
-}
dequeue :: RewindQueue a -> (a, RewindQueue a)
dequeue RewindQueue{..} =
  let (x, queue') = Queue.dequeue queue
  in (x, RewindQueue { queue = queue', undo = x : undo, undosz = undosz + 1 })

{-|
modifies the head of the queue, without removal. Returns the old head

@since 2.1.0.0
-}
poke :: (a -> a) -> RewindQueue a -> (a, RewindQueue a)
poke f q = let (x, queue') = Queue.poke f (queue q) in (x, q { queue = queue' })

{-|
Undoes the last \(n\) `dequeue` operations but /only/ if there are that many
available undos. Otherwise, it will throw an error.

@since 1.5.0.0
-}
rewind :: Int -> RewindQueue a -> RewindQueue a
rewind n RewindQueue{..}
  | n <= undosz = let (rs, undo') = splitAt n undo
                  in RewindQueue { queue = queue { outsz = outsz queue + length rs,
                                                   outs = foldl' (flip (:)) (outs queue) rs },
                                   undo = undo',
                                   undosz = undosz - n }
  | otherwise   = error $ "Cannot rewind more than " ++ show undosz ++ " elements, but tried " ++ show n

{-|
Is the queue empty?

@since 1.5.0.0
-}
null :: RewindQueue a -> Bool
null = Queue.null . queue

{-|
Returns how many elements are in the queue.

@since 1.5.0.0
-}
size :: RewindQueue a -> Int
size = Queue.size . queue

{-|
Folds the values in the queue. Undo history is not included.

@since 1.5.0.0
-}
foldr :: (a -> b -> b) -> b -> RewindQueue a -> b
foldr f k = Queue.foldr f k . queue

{-|
Converts this queue into a list. Undo history is discarded.

@since 1.5.0.0
-}
toList :: RewindQueue a -> [a]
toList = Queue.toList . queue