{-# LANGUAGE GADTs #-}

module Compiler.Hoopl.Zipper
  ( ZBlock(..), ZGraph, ZBody
  , frontBiasBlock, backBiasBlock
  )
where

import Compiler.Hoopl.Graph

data ZBlock n e x where
  -- nodes
  ZFirst  :: n C O                 -> ZBlock n C O
  ZMiddle :: n O O                 -> ZBlock n O O
  ZLast   :: n O C                 -> ZBlock n O C

  -- concatenation operations
  ZCat    :: ZBlock n O O -> ZBlock n O O -> ZBlock n O O -- non-list-like
  ZHead   :: ZBlock n C O -> n O O        -> ZBlock n C O
  ZTail   :: n O O        -> ZBlock n O C -> ZBlock n O C  

  ZClosed :: ZBlock n C O -> ZBlock n O C -> ZBlock n C C -- the zipper

type ZGraph = Graph' ZBlock
type ZBody  = Body'  ZBlock

instance Edges n => Edges (ZBlock n) where
  entryLabel (ZFirst n)    = entryLabel n
  entryLabel (ZHead h _)   = entryLabel h
  entryLabel (ZClosed h _) = entryLabel h
  successors (ZLast n)     = successors n
  successors (ZTail _ t)   = successors t
  successors (ZClosed _ t) = successors t


----------------------------------------------------------------

-- | A block is "front biased" if the left child of every
-- concatenation operation is a node, not a general block; a
-- front-biased block is analogous to an ordinary list.  If a block is
-- front-biased, then its nodes can be traversed from front to back
-- without general recusion; tail recursion suffices.  Not all shapes
-- can be front-biased; a closed/open block is inherently back-biased.

frontBiasBlock :: ZBlock n e x -> ZBlock n e x
frontBiasBlock b@(ZFirst  {}) = b
frontBiasBlock b@(ZMiddle {}) = b
frontBiasBlock b@(ZLast   {}) = b
frontBiasBlock b@(ZCat {}) = rotate b
  where -- rotate and append ensure every left child of ZCat is ZMiddle
        -- provided 2nd argument to append already has this property
    rotate :: ZBlock n O O -> ZBlock n O O
    append :: ZBlock n O O -> ZBlock n O O -> ZBlock n O O
    rotate (ZCat h t)     = append h (rotate t)
    rotate b@(ZMiddle {}) = b
    append b@(ZMiddle {}) t = b `ZCat` t
    append (ZCat b1 b2) b3 = b1 `append` (b2 `append` b3)
frontBiasBlock b@(ZHead {}) = b -- back-biased by nature; cannot fix
frontBiasBlock b@(ZTail {}) = b -- statically front-biased
frontBiasBlock (ZClosed h t) = shiftRight h t
    where shiftRight :: ZBlock n C O -> ZBlock n O C -> ZBlock n C C
          shiftRight (ZHead b1 b2) b3 = shiftRight b1 (ZTail b2 b3)
          shiftRight b1@(ZFirst {}) b2 = ZClosed b1 b2

-- | A block is "back biased" if the right child of every
-- concatenation operation is a node, not a general block; a
-- back-biased block is analogous to a snoc-list.  If a block is
-- back-biased, then its nodes can be traversed from back to back
-- without general recusion; tail recursion suffices.  Not all shapes
-- can be back-biased; an open/closed block is inherently front-biased.

backBiasBlock :: ZBlock n e x -> ZBlock n e x
backBiasBlock b@(ZFirst  {}) = b
backBiasBlock b@(ZMiddle {}) = b
backBiasBlock b@(ZLast   {}) = b
backBiasBlock b@(ZCat {}) = rotate b
  where -- rotate and append ensure every right child of ZCat is ZMiddle
        -- provided 1st argument to append already has this property
    rotate :: ZBlock n O O -> ZBlock n O O
    append :: ZBlock n O O -> ZBlock n O O -> ZBlock n O O
    rotate (ZCat h t)     = append (rotate h) t
    rotate b@(ZMiddle {}) = b
    append h b@(ZMiddle {}) = h `ZCat` b
    append b1 (ZCat b2 b3) = (b1 `append` b2) `append` b3
backBiasBlock b@(ZHead {}) = b -- statically back-biased
backBiasBlock b@(ZTail {}) = b -- front-biased by nature; cannot fix
backBiasBlock (ZClosed h t) = shiftLeft h t
    where shiftLeft :: ZBlock n C O -> ZBlock n O C -> ZBlock n C C
          shiftLeft b1 (ZTail b2 b3) = shiftLeft (ZHead b1 b2) b3
          shiftLeft b1 b2@(ZLast {}) = ZClosed b1 b2