{-# LANGUAGE TemplateHaskell #-}

-- |
-- Module      : Data.Binding.Hobbits.QQ
-- Copyright   : (c) 2011 Edwin Westbrook, Nicolas Frisby, and Paul Brauner
--
-- License     : BSD3
--
-- Maintainer  : emw4@rice.edu
-- Stability   : experimental
-- Portability : GHC
--
-- Defines a quasi-quoter for writing patterns that match the bodies of 'Mb'
-- multi-bindings. Uses the haskell-src-exts parser. @[nuP| P ]@ defines a
-- pattern that will match a multi-binding whose body matches @P@. Any
-- variables matched by @P@ will remain inside the binding; thus, for example,
-- in the pattern @[nuP| x |]@, @x@ matches the entire multi-binding.
--
-- Examples:
--
-- > case (nu Left) of [nuP| Left x |] -> x  ==  nu id
--
-- [clP| P |] does the same for the Cl type, and [clNuP| P |] works for
-- both simultaneously: Cl (Mb ctx a).

module Data.Binding.Hobbits.QQ (nuP, clP, clNuP) where

import qualified Data.Binding.Hobbits.InternalUtilities as IU
import Data.Binding.Hobbits.Internal (Mb(..), Cl(..))
import Data.Binding.Hobbits.PatternParser (parsePattern)

import Language.Haskell.TH.Syntax as TH
import Language.Haskell.TH.Ppr as TH
import Language.Haskell.TH.Quote

import qualified Data.Generics as SYB
import Control.Monad.Writer (runWriterT, tell)
import Data.Monoid (Any(..))





compose :: Exp -> Exp -> Exp
compose f g = VarE '(.) `AppE` f `AppE` g

patQQ n pat = QuasiQuoter (err "Exp") pat (err "Type") (err "Decs")
  where err s = error $ "QQ `" ++ n ++ "' is for patterns, not " ++ s ++ "."





data WrapKit =
  -- _add adds structure just before binding the name (i.e. on VarP)
  -- _rm  removes structure that was added for the @ patterns
  -- _top removes structure at the top of the whole pattern
  WrapKit {_add :: Exp, _rm :: Pat -> Pat, _top :: Bool -> Pat -> Pat}

outsideKit (WrapKit {_add = addO, _rm = rmO, _top = topO})
           (WrapKit {_add = addI, _rm = rmI, _top = topI}) =
  WrapKit {_add = addO `compose` addI,
           _rm = rmO . rmI,
           _top = \b -> topO b . topI b}

-- wrapVars changes the types of names bound in a pattern
wrapVars :: Monad m => WrapKit -> Pat -> m Pat
wrapVars (WrapKit {_add = add, _rm = rm, _top = top}) pat = do
  (pat', Any usedAdd) <- runWriterT m
  return $ top usedAdd pat'
  where
    m = IU.everywhereButM (SYB.mkQ False isExp) (SYB.mkM w) pat
      where isExp :: Exp -> Bool
            -- don't recur into the expression part of view patterns
            isExp _ = True

    -- this should be called if the 'add' function is ever used
    hit x = tell (Any True) >> return x

    -- wraps up bound names
    w p@VarP{} = hit $ ViewP add p
    -- wraps for the bound name, then immediately unwraps
    -- for the rest of the pattern
    w (AsP v p) = hit $ ViewP add $ AsP v $ rm p
    -- requires the expression to be closed
    w (ViewP (VarE n) p) = return $ ViewP (VarE 'unCl `AppE` VarE n) p
    w (ViewP e _) = fail $ "view function must be a single name: `" ++ show (TH.ppr e) ++ "'"
    w p = return p





parseHere :: String -> Q Pat
parseHere s = do
  fn <- loc_filename `fmap` location
  case parsePattern fn s of
    Left e -> error $ "Parse error: `" ++ e ++
      "'\n\n\t when parsing pattern: `" ++ s ++ "'."
    Right p -> return p





same_ctx :: Mb ctx a -> Mb ctx b -> Mb ctx b
same_ctx _ x = x

nuKit mb ln = WrapKit {_add = add, _rm = rm, _top = top} where
  add = (VarE 'same_ctx `AppE` VarE mb) `compose`
        (ConE 'MkMb     `AppE` VarE ln)

  rm p = ConP 'MkMb [WildP, p]

  top b p = if b then AsP mb $ ConP 'MkMb [VarP ln, p] else rm p





nuP = patQQ "nuP" $ \s -> do
  mb <- newName "mb"
  ln <- newName "bs"

  parseHere s >>= wrapVars (nuKit mb ln)





clKit = WrapKit {_add = ConE 'Cl, _rm = rm, _top = const rm}
  where rm p = ConP 'Cl [p]

clP = patQQ "clP" $ (>>= wrapVars clKit) . parseHere





clNuP = patQQ "clNuP" $ \s -> do
  mb <- newName "mb"
  ln <- newName "bs"

  parseHere s >>= wrapVars (clKit `outsideKit` nuKit mb ln)