{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TemplateHaskell #-} -- -- Binding expressions that are Ivory statements. -- -- Copyright (C) 2014, Galois, Inc. -- All rights reserved. -- module Ivory.Language.Syntax.Concrete.QQ.BindExp ( fromExp , fromArea , fromExpStmt , fromAreaStmt ) where import Prelude hiding (exp) import Language.Haskell.TH hiding (Exp, Stmt, Type) import qualified Language.Haskell.TH as T import qualified Ivory.Language.Proc as I import qualified Ivory.Language.Ref as I import Ivory.Language.Syntax.Concrete.ParseAST import Ivory.Language.Syntax.Concrete.QQ.Common import Ivory.Language.Syntax.Concrete.QQ.ExprQQ -------------------------------------------------------------------------------- -- Insert statements -- Collect up expressions that turn into Ivory statements. This call comes from -- a use of an expression in a statement. -- -- This isn't heavily optimized (for the sake of simplicity): we might duplicate --dereference statements for e_1 and e_2 in the same statement. fromExp :: Insert a -> Exp -> QStM a T.Exp fromExp f exp = do env <- (mkBinds f) exp return (toExp env exp) mkBinds :: Insert a -> Exp -> QStM a VarEnv mkBinds f exp = do -- collectBindExps gets all subexpressions that contain Ivory statements (area -- dereferences, function calls), except the array index in array areas (which -- are processed in fromArea) and the function args. envs <- mapM (insertBind f) (collectBindExps exp) return (concat envs) -- For each binding, (1) insert a statement and (2) return a map, mapping to the -- fresh variable associated with the key so the expression can lookup the -- binding variable from the monadic statement. insertBind :: Insert a -> Key -> QStM a VarEnv insertBind f key = do b <- fromBind f key i@(_, nm) <- fresh f key nm b return [i] where fresh = do nm <- liftQ (freshVar key) return (key, nm) fromBind :: Insert a -> Key -> QStM a T.Exp fromBind f key | isArea key = fromArea f (keyToArea key) | isCall key = fromCall f (keyToCall key) | otherwise = error "impossible in fromBind" fromCall :: Insert a -> Call -> QStM a T.Exp fromCall f (Call sym args) = do es <- mapM (fromExp f) args let call = AppE (VarE 'I.call) (mkVar sym) return $ callit call es -- Base names for call bindings callToVar :: Call -> String callToVar (Call sym _) = sym -- Base names for dereference variables areaToVar :: Area -> String areaToVar area = case area of AreaVar v -> map (\c -> if c == '.' then '_' else c) v AddrOf v -> areaToVar v -- Ignore the expression. Ok, since these are bases to fresh vars. ArrayArea area' _ -> areaToVar area' StructArea area0 area1 -> areaToVar area0 ++ ('_': areaToVar area1) -- Create a TH expression for an area. fromArea :: Insert a -> Area -> QStM a T.Exp fromArea f area = case area of AreaVar v -- ref -> return (mkVar v) AddrOf area' -- ref -> do a <- (fromArea f) area' return $ toAddrOf a ArrayArea area' ixExp -- (arr @ ix) -> do ix <- (fromExp f) ixExp a <- (fromArea f) area' return $ toArray a ix StructArea area0 area1 -- (area . area) -> do a0 <- (fromArea f) area0 a1 <- (fromArea f) area1 return $ toStruct a0 a1 -- We want to generate a fresh name that won't capture other user-defined -- names, since we're inserting these variables. We'll make a name base that -- helps us track it's usage. freshVar :: Key -> Q Name freshVar key | isArea key = newName $ "deref_" ++ areaToVar (keyToArea key) | isCall key = newName $ "call_" ++ callToVar (keyToCall key) | otherwise = error "Impossible in freshVar" insertStmt :: Insert T.Stmt insertStmt key nm exp | isArea key = insert $ BindS (VarP nm) (AppE (VarE 'I.deref) exp) | isCall key = insert $ BindS (VarP nm) exp | otherwise = error "Impossible in insertStmt" fromExpStmt :: Exp -> QStM T.Stmt T.Exp fromExpStmt = fromExp insertStmt fromAreaStmt :: Area -> QStM T.Stmt T.Exp fromAreaStmt = fromArea insertStmt