{-# LANGUAGE TemplateHaskell #-} module Yesod.Routes.TH.ParseRoute ( -- ** ParseRoute mkParseRouteInstance ) where import Yesod.Routes.TH.Types import Language.Haskell.TH.Syntax import Data.Text (pack) import Web.PathPieces (PathPiece (..), PathMultiPiece (..)) import Yesod.Routes.Class import qualified Yesod.Routes.Dispatch as D import Data.List (foldl') import Control.Applicative ((<$>)) import Data.Maybe (catMaybes) import Control.Monad (forM) import Control.Monad (join) -- | Clauses for the 'parseRoute' method. mkParseRouteClauses :: [ResourceTree a] -> Q [Clause] mkParseRouteClauses ress' = do pieces <- newName "pieces0" dispatch <- newName "dispatch" query <- newName "_query" -- The 'D.Route's used in the dispatch function routes <- mapM (buildRoute query) ress -- The dispatch function itself toDispatch <- [|D.toDispatch|] let dispatchFun = FunD dispatch [Clause [] (NormalB $ toDispatch `AppE` ListE routes) [] ] join' <- [|join|] let body = join' `AppE` (VarE dispatch `AppE` VarE pieces) return $ return $ Clause [TupP [VarP pieces, VarP query]] (NormalB body) [dispatchFun] where ress = map noMethods $ flatten ress' noMethods (FlatResource a b c d) = FlatResource a b c $ noMethods' d noMethods' (Methods a _) = Methods a [] noMethods' (Subsite a b) = Subsite a b mkParseRouteInstance :: Type -> [ResourceTree a] -> Q Dec mkParseRouteInstance typ ress = do cls <- mkParseRouteClauses ress return $ InstanceD [] (ConT ''ParseRoute `AppT` typ) [ FunD 'parseRoute cls ] -- | Build a single 'D.Route' expression. buildRoute :: Name -> FlatResource a -> Q Exp buildRoute query (FlatResource parents name resPieces resDisp) = do -- First two arguments to D.Route routePieces <- ListE <$> mapM (convertPiece . snd) allPieces isMulti <- case resDisp of Methods Nothing _ -> [|False|] _ -> [|True|] [|D.Route $(return routePieces) $(return isMulti) $(routeArg3 query parents name (map snd allPieces) resDisp) |] where allPieces = concat $ map snd parents ++ [resPieces] routeArg3 :: Name -- ^ query string parameters -> [(String, [(CheckOverlap, Piece a)])] -> String -- ^ name of resource -> [Piece a] -> Dispatch a -> Q Exp routeArg3 query parents name resPieces resDisp = do pieces <- newName "pieces" -- Allocate input piece variables (xs) and variables that have been -- converted via fromPathPiece (ys) xs <- forM resPieces $ \piece -> case piece of Static _ -> return Nothing Dynamic _ -> Just <$> newName "x" -- Note: the zipping with Ints is just a workaround for (apparently) a bug -- in GHC where the identifiers are considered to be overlapping. Using -- newName should avoid the problem, but it doesn't. ys <- forM (zip (catMaybes xs) [1..]) $ \(x, i) -> do y <- newName $ "y" ++ show (i :: Int) return (x, y) -- In case we have multi pieces at the end xrest <- newName "xrest" yrest <- newName "yrest" -- Determine the pattern for matching the pieces pat <- case resDisp of Methods Nothing _ -> return $ ListP $ map (maybe WildP VarP) xs _ -> do let cons = mkName ":" return $ foldr (\a b -> ConP cons [maybe WildP VarP a, b]) (VarP xrest) xs -- Convert the xs fromPathPiece' <- [|fromPathPiece|] xstmts <- forM ys $ \(x, y) -> return $ BindS (VarP y) (fromPathPiece' `AppE` VarE x) -- Convert the xrest if appropriate (reststmts, yrest') <- case resDisp of Methods (Just _) _ -> do fromPathMultiPiece' <- [|fromPathMultiPiece|] return ([BindS (VarP yrest) (fromPathMultiPiece' `AppE` VarE xrest)], [yrest]) _ -> return ([], []) -- The final expression that actually uses the values we've computed caller <- buildCaller query xrest parents name resDisp $ map snd ys ++ yrest' -- Put together all the statements just <- [|Just|] let stmts = concat [ xstmts , reststmts , [NoBindS $ just `AppE` caller] ] errorMsg <- [|error "Invariant violated"|] let matches = [ Match pat (NormalB $ DoE stmts) [] , Match WildP (NormalB errorMsg) [] ] return $ LamE [VarP pieces] $ CaseE (VarE pieces) matches -- | The final expression in the individual Route definitions. buildCaller :: Name -- ^ query string parameters -> Name -- ^ xrest -> [(String, [(CheckOverlap, Piece a)])] -> String -- ^ name of resource -> Dispatch a -> [Name] -- ^ ys -> Q Exp buildCaller query xrest parents name resDisp ys = do -- Create the route let route = routeFromDynamics parents name ys case resDisp of Methods _ _ -> [|Just $(return route)|] Subsite _ _ -> [|fmap $(return route) $ parseRoute ($(return $ VarE xrest), $(return $ VarE query))|] -- | Convert a 'Piece' to a 'D.Piece' convertPiece :: Piece a -> Q Exp convertPiece (Static s) = [|D.Static (pack $(lift s))|] convertPiece (Dynamic _) = [|D.Dynamic|] routeFromDynamics :: [(String, [(CheckOverlap, Piece a)])] -- ^ parents -> String -- ^ constructor name -> [Name] -> Exp routeFromDynamics [] name ys = foldl' (\a b -> a `AppE` VarE b) (ConE $ mkName name) ys routeFromDynamics ((parent, pieces):rest) name ys = foldl' (\a b -> a `AppE` b) (ConE $ mkName parent) here where (here', ys') = splitAt (length $ filter (isDynamic . snd) pieces) ys isDynamic Dynamic{} = True isDynamic _ = False here = map VarE here' ++ [routeFromDynamics rest name ys']