packages feed

matterhorn-40800.0.0: src/Zipper.hs

module Zipper
  ( Zipper
  , fromList
  , toList
  , focus
  , focusL
  , left
  , leftL
  , right
  , rightL
  , findLeft
  , findRight
  , updateList
  , filterZipper
  )
where

import Prelude ()
import Prelude.Compat

import Lens.Micro.Platform (Lens, lens, ix, (&), (.~))

data Zipper a = Zipper
  { zFocus :: Int
  , zElems :: [a]
  }

-- Move the focus one element to the left
left :: Zipper a -> Zipper a
left z = z { zFocus = (zFocus z - 1) `mod` length (zElems z) }

-- A lens on the zipper moved to the left
leftL :: Lens (Zipper a) (Zipper a) (Zipper a) (Zipper a)
leftL = lens left (\ _ b -> right b)

-- Move the focus one element to the right
right :: Zipper a -> Zipper a
right z = z { zFocus = (zFocus z + 1) `mod` length (zElems z) }

-- A lens on the zipper moved to the right
rightL :: Lens (Zipper a) (Zipper a) (Zipper a) (Zipper a)
rightL = lens right (\ _ b -> left b)

-- Return the focus element
focus :: Zipper a -> a
focus z = zElems z !! zFocus z

-- A lens to return the focus element
focusL :: Lens (Zipper a) (Zipper a) a a
focusL = lens focus upd
  where upd (Zipper n elems) x = Zipper n (elems & ix(n) .~ x)

-- Turn a list into a wraparound zipper, focusing on the head
fromList :: [a] -> Zipper a
fromList xs = Zipper { zFocus = 0, zElems = xs }

toList :: Zipper a -> [a]
toList = zElems

-- Shift the focus until a given element is found, or return the
-- same zipper if none applies
findRight :: (a -> Bool) -> Zipper a -> Zipper a
findRight f z
  | f (focus z) = z
  | otherwise   = go (right z) (zFocus z)
  where go zC n
          | n == zFocus zC = zC
          | f (focus zC)   = zC
          | otherwise      = go (right zC) n

-- Shift the focus until a given element is found, or return the
-- same zipper if none applies
findLeft :: (a -> Bool) -> Zipper a -> Zipper a
findLeft f z
  | f (focus z) = z
  | otherwise   = go (left z) (zFocus z)
  where go zC n
          | n == zFocus zC = zC
          | f (focus zC)   = zC
          | otherwise      = go (left zC) n

updateList :: (Eq a) => [a] -> Zipper a -> Zipper a
updateList newList oldZip = findLeft (== oldFocus) newZip
  where oldFocus = focus oldZip
        newZip   = oldZip { zElems = newList }

filterZipper :: (Eq a) => (a -> Bool) -> Zipper a -> Zipper a
filterZipper f oldZip = findLeft (== oldFocus) newZip
  where oldFocus = focus oldZip
        newZip   = oldZip { zElems = filter f $ zElems oldZip }