packages feed

huff-0.1.0.0: src/Huff/QQ.hs

{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}

module Huff.QQ ( huff ) where

import           Huff.Compile as AST hiding (Name)
import qualified Huff.Input as Input
import           Huff.QQ.Lexer (SourcePos(..))
import           Huff.QQ.Parser (parseQQ)

import           Control.Monad (forM)
import           Data.Char (toLower)
import           Data.Function (on)
import qualified Data.Text as T
import qualified Data.Map.Strict as Map
import           Language.Haskell.TH
import           Language.Haskell.TH.Syntax
import           Language.Haskell.TH.Quote (QuasiQuoter(..))


huff :: QuasiQuoter
huff  = QuasiQuoter { quoteExp  = notSupported "expressions"
                    , quotePat  = notSupported "patterns"
                    , quoteType = notSupported "types"
                    , quoteDec  = huffDecs
                    }
  where
  notSupported n _ = fail ("huff: Quasi-quotation is not supported for " ++ n)

huffDecs :: String -> Q [Dec]
huffDecs str =
  do loc <- location
     runIO (print loc)
     let (line,col) = loc_start loc
     let start = SourcePos { sourceIndex  = 0
                           , sourceLine   = line
                           , sourceColumn = col }
     case parseQQ start str of
       Left err -> fail ("huff: " ++ show err)
       Right ds ->
         do dss <- mapM genDomain ds
            return (concat dss)

-- | Translate the domain into one data declaration per `type`, as well as one
-- big data declaration for the domain, with one constructor per operator.
-- Additionally, write out an altered version of the domain, that will produce
-- values of the new domain type.
genDomain :: Domain T.Text -> Q [Dec]
genDomain d@Domain { .. } =
  do let types = mkTypeMap domObjects
     objs     <- mapM genObjectDecl (Map.toList types)
     dom      <- genDomainDecl domName domOperators
     predss   <- mapM genPredicate domPreds
     domValue <- genDomainValue d
     return (dom : domValue ++ objs ++ concat predss)

mkTypeMap :: [Object] -> Map.Map T.Text [T.Text]
mkTypeMap  = foldl step Map.empty
  where
  step acc Typed { .. } = Map.alter (update tValue) tType acc
  update val Nothing    = Just [val]
  update val (Just vs)  = Just (val:vs)

-- | Generate a data declaration from an object definition.
--
-- INVARIANT: all objects are assumed to have the same type.
genObjectDecl :: (T.Text,[T.Text]) -> Q Dec
genObjectDecl (ty,objs) =
  do show <- [t| Show |]
     let cons = [ NormalC (mkName (T.unpack obj)) [] | obj <- objs ]
     return (DataD [] (mkName (T.unpack ty)) [] Nothing cons [show])

-- | Generate a specialized predicate that can produce either a literal, or a
-- term. Produces one type-class per predicate, with instances for literals and
-- terms.
genPredicate :: AST.Pred -> Q [Dec]
genPredicate (App f xs) =
  do let clsName = mkName ("Has_" ++ T.unpack f)
         var     = mkName "a"
         pred    = mkName (T.unpack f)
         arrow x = AppT (AppT ArrowT x)

         params  = take (length xs) [ mkName ("x" ++ show x) | x <- [ 1 .. ] ]

     instLiteral <- [t| $(conT clsName) Literal |]
     instTerm    <- [t| $(conT clsName) Term    |]

     let mkArg p = [| AName (T.pack (show $(varE p))) |]
     litBody <- [| LAtom (App $(text f) $(listE (map mkArg params))) |]

     termBody <- [| TLit $(foldl appE (varE pred) (map varE params)) |]

     return [ ClassD [] clsName [PlainTV var] []
              [ SigD pred (foldr arrow (VarT var) [ ConT (mkName (T.unpack x)) | x <- xs ])
              ]

            , InstanceD Nothing [] instLiteral
              [ FunD pred [ Clause (map VarP params) (NormalB litBody) [] ]
              ]

            , InstanceD Nothing [] instTerm
              [ FunD pred [ Clause (map VarP params) (NormalB termBody) [] ]
              ]
            ]

genDomainDecl :: T.Text -> [Operator T.Text] -> Q Dec
genDomainDecl dom ops =
  do show <- [t| Show |]
     let tyName = mkName (T.unpack dom)
     cons <- mapM mkOpCon ops
     return (DataD [] tyName [] Nothing cons [show])


mkOpCon :: Operator T.Text -> Q Con
mkOpCon op =
  do let conName = mkName (T.unpack (opName op))
     fields <- forM (opParams op) $ \ Typed { .. } ->
       do let tyName = mkName (T.unpack tType)
          bangType (bang noSourceUnpackedness noSourceStrictness) (conT tyName)

     return (NormalC conName fields)


genDomainValue :: Domain T.Text -> Q [Dec]
genDomainValue Domain { .. } =
  do let n          = mkDomVar (T.unpack domName)
         typesC     = typeMap domObjects

         mkCon args name =
           let con x = ConE (mkName (T.unpack x))
            in foldl AppE (con name) (map con args)

         opsC = compileOperators mkCon typesC domOperators

     body <-
       [d| $(varP n) =
             let types = $(liftTypes typesC)
                 ops   = $(listE (map liftOperator opsC))
              in \ start goal ->
                   if null goal
                      then error "invalid goal specification"
                      else compileProblem types ops (Problem start (foldr1 (/\) goal))
         |]

     let domType = mkName (T.unpack domName)
     sigType <- [t| [AST.Literal] -> [AST.Term] -> Input.Spec $(conT domType) |]
     let sig = SigD n sigType

     return (sig:body)

liftTypes :: TypeMap T.Text -> Q Exp
liftTypes (TypeMap ts) = [| TypeMap (Map.fromList $(listE (map mkPair (Map.toList ts)))) |]
  where
  mkPair (k,xs) = [| ($(text k), $(listE (map text xs))) |]

text :: T.Text -> Q Exp
text str = [| T.pack $(lift (T.unpack str)) |]

-- | Lift a grounded operator.
liftOperator :: Operator Exp -> Q Exp
liftOperator Operator { .. } =
  [| Operator { opName    = $(text opName)
              , opDerived = $(lift opDerived)
              , opParams  = []
              , opVal     = $(liftVal opVal)
              , opPrecond = $(liftTerm opPrecond)
              , opEffects = $(liftEffect opEffects)
              } |]
  where
  liftVal :: Maybe Exp -> Q Exp
  liftVal Nothing  = [| Nothing          |]
  liftVal (Just e) = [| Just $(return e) |]

-- | Lift a precondition from a grounded operator.
liftTerm :: Term -> Q Exp
liftTerm (TAnd ts)  = [| TAnd $(listE (map liftTerm ts))  |]
liftTerm (TOr  ts)  = [| TOr  $(listE (map liftTerm ts))  |]
liftTerm (TLit lit) = [| TLit $(liftLit lit)              |]
liftTerm t          = fail ("Compilation pass missed something: " ++ show t)

liftLit :: Literal -> Q Exp
liftLit (LAtom a) = [| LAtom $(liftAtom a) |]
liftLit (LNot  a) = [| LNot  $(liftAtom a) |]

liftAtom :: Atom -> Q Exp
liftAtom (App f xs) = [| App $(text f) $(listE (map liftArg xs)) |]

liftArg :: Arg -> Q Exp
liftArg (AName n) = [| AName $(text n) |]
liftArg (AVar  n) = [| AVar  $(text n) |]

-- | Lift effects from a grounded operator.
liftEffect :: Effect -> Q Exp
liftEffect (EWhen p qs) = [| EWhen $(liftTerm p) $(listE (map liftLit qs)) |]
liftEffect (EAnd es)    = [| EAnd  $(listE (map liftEffect es))            |]
liftEffect (ELit lit)   = [| ELit  $(liftLit lit)                          |]
liftEffect e            = fail ("Compilation pass missed something: " ++ show e)

mkDomVar :: String -> Name
mkDomVar (h:tl) = mkName (toLower h : tl)
mkDomVar []     = error "Invalid domain name: no name given"