packages feed

cspretty-1.0: Language/CSPM/Pretty.hs

{- |
   Module      :  Language.CSP.Pretty
   Description :  Pretty printing for the CSP abstract syntax
   Copyright   :  Draper Laboratories
   
   The two functions in the module are used for pretty-printing the abstract CSP
   syntax.  The output is intended to be readable by FDR3.
-}

module Language.CSPM.Pretty (emitMod, emitMods) where

import Language.CSPM.Syntax
import Text.PrettyPrint
import Data.List
import Control.Monad
import qualified Data.Map as M
import qualified Data.Set as S

-- We need to keep track of which identifiers are in scope, so that we can use
-- simple names where possible.  We use a state monad (note: not a reader
-- monad, since there are globals, though we do provide reader like
-- functionality).  The state consists of three parts: 
--
-- - A map of identifiers to Doc, which says how to print anything currently
--   in scope.
--
-- - A Set, which just contains the range of the map.  When we see a new
--   identifier, we need to check that the way we want to print it doesn't
--   conflict with things already in scope.  Keeping a around is much more
--   efficient than getting the range of the map as a list and searching that.
--
-- - A list of "warnings".  If we encounter an identifier that's not in scope,
--   we have to guess about how to print it.  This may not be safe, so we note
--   the problem in the warnings.
--
-- Additionally, because CSP declarations may be mutually recursive, we do a
-- first pass to add all the global variables to the monad.  This first pass
-- also shows up where a block of local definitions may be mutually recursive,
-- as in let-within clauses.

data PPState = PPState {ppIdMap    :: !(M.Map Id String),
                        ppUsedDocs :: !(S.Set String),
                        ppWarnings :: ![String]}

emptyPPState :: PPState
emptyPPState = PPState {ppIdMap    = M.empty,
                        ppUsedDocs = S.empty,
                        ppWarnings = []}

newtype PP a = PP (PPState -> (PPState,a))

unPP :: PP a -> PPState -> (PPState,a)
unPP (PP f) = f

instance Functor PP where
  fmap f (PP g) = PP (\st -> fmap f (g st))

instance Monad PP where
  return x  = PP $ \pps -> (pps,x)
  m >>= k   = PP $ \pps -> let (pps',res) = unPP m pps in unPP (k res) pps'

instance Applicative PP where
  pure = return
  (<*>) = ap


runPP :: PPState -> PP a -> (a,[String])
runPP st (PP f) = let (st',res) = f st in (res, ppWarnings st')

getIdMap :: PP (M.Map Id String)
getIdMap = PP $ \pps -> (pps,ppIdMap pps)

warn :: String -> PP ()
warn warning = PP $ \pps -> (pps {ppWarnings = warning : ppWarnings pps},())

addGlobal :: Id -> PP Doc
addGlobal var = PP $ \pps -> 
  let ppvar = if S.member niceVersion (ppUsedDocs pps)
                then uglyVersion
                else niceVersion
  in
    (pps {ppIdMap = M.insert var ppvar (ppIdMap pps),
          ppUsedDocs = S.insert ppvar (ppUsedDocs pps)},
     text ppvar)
  where
    niceVersion :: String
    niceVersion = namePart var

    uglyVersion :: String
    uglyVersion =
      case var of
        Id (s,i) -> s ++ show i
        Fixed s  -> s

addLocal :: Id -> (Doc -> PP a) -> PP a
addLocal var action = PP $ \pps ->
  case M.lookup var (ppIdMap pps) of
    Just d -> unPP (action $ text d) pps
    Nothing -> 
      let ppvar = if S.member niceVersion (ppUsedDocs pps)
                    then uglyVersion
                    else niceVersion 
          
          (pps',result) = 
             unPP (action $ text ppvar) $
               pps {ppIdMap = M.insert var ppvar (ppIdMap pps),
                    ppUsedDocs = S.insert ppvar (ppUsedDocs pps)}
      in 
        (pps' {ppIdMap = M.delete var (ppIdMap pps'),
               ppUsedDocs = S.delete ppvar (ppUsedDocs pps')},
         result)
      where
        niceVersion :: String
        niceVersion = namePart var
    
        uglyVersion :: String
        uglyVersion =
          case var of
            Id (s,i) -> s ++ show i
            Fixed s  -> s

addLocals :: [Id] -> ([Doc] -> PP a) -> PP a
addLocals vars action =  addLocalsRec vars []
  where
    addLocalsRec []     ds = action $ reverse ds
    addLocalsRec (v:vs) ds = 
      addLocal v $ \d -> addLocalsRec vs (d:ds)

---

ppId :: Id -> PP Doc
ppId var = do 
  idmap <- getIdMap
  case M.lookup var idmap of
    Just d -> return $ text d
    Nothing -> do
      pp <- 
        case var of
          Fixed s  -> return s
          Id (s,i) -> do 
            warn $ "Identifier " ++ show var ++ " not found."
            return $ s ++ show i
      return $ text pp

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

commas :: [Doc] -> Doc
commas es = hsep $ punctuate comma es

dots :: [Doc] -> Doc
dots es = hcat $ punctuate (char '.') es

ppFDRMod :: FDRMod -> PP Doc
ppFDRMod tops = do
  addTLGlobals tops
  ttops <- mapM ppDefinition $ topLevels tops
  return $ vcat $ punctuate (text "\n") ttops

-- This takes a pattern and does two things: tells you how it should be
-- displayed, and extends the local scope with the relevant identifiers for
-- the duration of the pretty printing.
ppPattern :: Pat -> (Doc -> PP a) -> PP a
ppPattern (PId var)         f = addLocal var f
ppPattern (PConcat p1 p2)   f = ppBinaryPattern p1 p2 (char '^') f
ppPattern (PDot p1 ps)      f = ppPatList (p1:ps) (char '.') f
ppPattern (PDouble p1 p2)   f = ppBinaryPattern p1 p2 (text "@@") f
ppPattern (PList pats)      f = 
  ppPatList pats comma $ \ppats -> f $ char '<' <> ppats <> char '>'
ppPattern (PConst c)        f = f $ ppConst c
ppPattern PEmptySet         f = f $ text "{}"
ppPattern (PSingletonSet p) f = ppPattern p $ \pp -> f $ braces pp
ppPattern (PTuple pats)     f =
  ppPatList pats comma $ \ppats -> f $ parens ppats
ppPattern PWildCard         f = f $ char '_'

-- abstract out the common case of printing a binary pattern
ppBinaryPattern :: Pat -> Pat -> Doc -> (Doc -> PP a) -> PP a
ppBinaryPattern p1 p2 pnct f = do
  ppPatParens p1 $ \pp1 -> 
    ppPatParens p2 $ \pp2 ->
      f $ pp1 <> pnct <> pp2

-- abstract out the common case of printing a list of patterns
ppPatList :: [Pat] -> Doc -> (Doc -> PP a) -> PP a
ppPatList pats pnct f = patList pats []
  where
--    patList :: [Exp] -> [Doc] -> PP a
    patList []     ds = f $ hcat $ punctuate pnct $ reverse ds
    patList (p:ps) ds = ppPatParens p $ \d -> patList ps (d:ds)


-- prints the pattern with an extra set of parens if it's not a var
ppPatParens :: Pat -> (Doc -> PP a) -> PP a
ppPatParens p@(PId _) f = ppPattern p f
ppPatParens p f = ppPattern p (\d -> f $ parens d)

-- print a datatype constructor declaration
ppConstructor :: (Id,[Exp]) -> PP Doc
ppConstructor (c,tys) = do
  tc  <- ppId c
  ttys <- mapM ppExp tys
  return $ dots (tc : map parens ttys)

ppDefinition :: Definition -> PP Doc
ppDefinition (DVar p e) = do
  -- Note that this may "double add" the variables in the pattern to the map,
  -- since they were probably added when we entered the scope of this definition
  -- as part of handling mutually recursive definitions.  This is fine, because
  -- ppPattern calls addLocal, which handles existing variables declarations
  -- gracefully.
  ppPattern p $ \pp -> do
    pe <- ppExp e
    return $ pp <+> char '=' <+> pe
ppDefinition (DFun funName funClauses) = do
  tname <- ppId funName
  vcat <$> mapM (ppFunClause tname) funClauses
  where
    ppFunClause :: Doc -> ([Pat],Exp) -> PP Doc
    ppFunClause fnm (pats,body) = ppPatList pats (char ',') $ \ppats -> do
      pbody <- ppExp body
      return $ fnm <+> parens ppats <+> char '=' $+$ (nest 2 $ pbody)
ppDefinition (DAnnot x t) = do
  px <- ppId x
  pt <- ppType t
  return $ px <+> text "::" <+> pt
ppDefinition (DDataType nm cnstrs) = do
  tnm <- ppId nm
  tcnstrs <- mapM ppConstructor cnstrs
  return $ 
    text "datatype" <+> tnm <+> char '=' 
       <+> (hcat $ punctuate (text " | ") tcnstrs)
ppDefinition (DSubType nm cnstrs) = do
  tnm <- ppId nm
  tcnstrs <- mapM ppConstructor cnstrs
  return $ 
    text "subtype" <+> tnm <+> char '=' 
       <+> (hcat $ punctuate (text " | ") tcnstrs)
ppDefinition (DChan xs []) = do 
  pxs <- commas <$> mapM ppId xs
  return $ text "channel" <+> pxs
ppDefinition (DChan xs tys) = do
  pxs <- commas <$> mapM ppId xs
  ptys <- mapM ppType tys
  return $ text "channel" <+> pxs <+> colon <+> dots ptys
ppDefinition (DInclude str) = 
  return $ text "include" <+> text (show str) --- XXXX hack for escape chars
ppDefinition (DExternal x) = do
  px <- ppId x
  return $ text "external" <+> px
ppDefinition (DTransparent x) = do
  px <- ppId x
  return $ text "transparent" <+> px
ppDefinition (DAssert a m) = do
  pa <- ppAssertion a m
  return $ text "assert" <+> pa

--just the letter part, not the brackets or [T=
modelLetter :: Model -> String
modelLetter MT  = "T"
modelLetter MF  = "F"
modelLetter MFD = "FD"

--This is [T] or [FD], etc.
ppModel :: Model -> Doc
ppModel = brackets . text . modelLetter

ppAssertion :: Assertion -> Model -> PP Doc
ppAssertion (ARefines p q) m = do
  pp <- ppExp p
  pq <- ppExp q
  return $ pp <+> text ('[' : pm ++ "=") <+> pq
  where
    pm = modelLetter m
ppAssertion (ADeadlockFree p) m = do
  pp <- ppExp p
  let pm = ppModel m
  return $ pp <+> text ":[deadlock free" <+> pm <> char ']'
ppAssertion (ADivergenceFree p) m = do
  pp <- ppExp p
  let pm = ppModel m
  return $ pp <+> text ":[divergence free" <+> pm <> char ']'
ppAssertion (ADeterministic p) m = do
  pp <- ppExp p
  let pm = ppModel m
  return $ pp <+> text ":[deterministic" <+> pm <> char ']'
ppAssertion (AHasTrace p t) m = do
  pp <- ppExp p
  pe <- ppExp t
  let pm = ppModel m
  return $ pp <+> text ":[has trace" <+> pm <> text "]:" <+> pe


ppType :: Type -> PP Doc
ppType TEvent        = return $ text "Event"
ppType TChar         = return $ text "Char"
ppType TBool         = return $ text "Bool"
ppType TInt          = return $ text "Int"
ppType TUnit         = return $ text "Unit"
ppType (TList t)     = do
  pt <- ppType t
  return $ char '<' <+> pt <+> char '>'
ppType (TSet t)      = do
  pt <- ppType t
  return $ braces pt
ppType (TMap t1 t2)  = do
  pt1 <- ppType t1
  pt2 <- ppType t2
  return $ text "(|" <+> pt1 <+> text "=>" <+> pt2 <+> text "|)"
ppType (TData x)     = ppId x
ppType (TRange a b)  = do 
  pa <- ppExpParens a
  pb <- ppExpParens b
  return $ braces $ (parens pa) <> text ".." <> (parens pb)
ppType TProc         = return $ text "Proc"

-- Printing comprehension/set statements.  Three notes:
--
-- 1) We take in a list of comprehension statements rather than each
--    individually for reasons of scope.  In particular, you can write
--    comprehensions like:
--    
--       < x+1 | x <- xs, isOdd(x) >
-- 
--    Here, the later comprehension statement "isOdd(x)" needs "x" to be in
--    scope.
-- 
-- 2) For the same reason, ppCompStmts takes in an argument indicating the
--    monadic scope of the local variables declared in the patterns (much like
--    addLocal and ppPattern).
--
-- 3) FDR3 appears to support both the "x <- a" syntax that is natural in
--    comprehensions, as in:
--
--      < x+1 | x <- xs >
-- 
--    and the "x : a" syntax that is natural in replicated operations, as in:
--
--     [] x : {0..5} @ P(x)
--
--    These mean the same thing, and it appears you can use either syntax in
--    either place.  For uniformity, we use only the "<-" syntax, since it is
--    the one mentioned on the relevant fdr page:
--
--      https://www.cs.ox.ac.uk/projects/fdr/manual/cspm/syntax.html#csp-statements
ppCompStmt :: [CompStmt] -> (Doc -> PP a) -> PP a
ppCompStmt cs f = ppCS cs []
  where
    -- ppCS :: [CompStmt] -> [Doc] -> PP a
    ppCS []                ds = 
      f $ commas $ reverse ds
    ppCS (CSGen p e : css) ds = do
      pe <- ppExp e
      ppPattern p $ \pp -> ppCS css (pp <+> text "<-" <+> pe : ds)
    ppCS (CSPred e : css)  ds = do
      pe <- ppExp e
      ppCS css (pe : ds)

-- Printing expressions
ppExp :: Exp -> PP Doc
ppExp (EId x) = ppId x
ppExp (ELambda pats e) = 
  ppPatList pats comma $ \ppats -> do
    te <- ppExp e
    return $ char '\\' <+> ppats <+> char '@' <+> te
ppExp (EApp fun es) = do
  tfun <- ppExpParens fun
  tes <- mapM ppExp es
  return $ tfun <> (parens $ commas tes)
ppExp (ELet defs e) = do
  addTLLocals (FDRMod defs) $ do
    pdefs <- mapM ppDefinition defs
    pe <- ppExp e
    return $ text "let" $$ nest 2 (vcat $ punctuate (text "\n") pdefs)
                        $$ text "within" $$ (nest 2 pe)
ppExp (EIfThenElse c e1 e2) = do
  tc <- ppExp c
  te1 <- ppExp e1
  te2 <- ppExp e2
  return $ 
     text "if" <+> tc <+> text "then" $+$ (nest 2 te1)
                       $$ text "else" $+$ (nest 2 te2)
ppExp (EUOp op e) = do
  te <- ppExpParens e
  return $ ppUOp op <+> te
ppExp (EBinOp e1 op e2) = ppBOpExp e1 (ppBinOp op) e2
ppExp (EConst c) = return $ ppConst c
ppExp (EError s) = return $ text "error" <> (parens $ text $ show s)
ppExp (EDot e es) = do 
  te <- ppExpParens e
  tes <- mapM ppExpParens es
  return $ dots (te:tes)
ppExp (EExtChoice e1 e2) = ppBOpExp e1 (text "[]") e2
ppExp (EGuarded e1 e2)   = ppBOpExp e1 (char '&') e2
ppExp (EHide e1 e2)      = ppBOpExp e1 (char '\\') e2
ppExp (EIntChoice e1 e2) = ppBOpExp e1 (text "|~|") e2
ppExp (EPrefix e1 ces e2) = do
  te1 <- ppExpParens e1
  tces_te2 <- ppCommsPrefix ces []
  return $ te1 <> tces_te2
  where
    ppCommsPrefix :: [(CommField)] -> [Doc] -> PP Doc
    ppCommsPrefix [] ds = do 
      te2 <- ppExpParens e2
      return $ hcat $ reverse $ te2 : (text " -> ") : ds
    ppCommsPrefix (comm:comms) ds =
      case ppComm comm of
        (d,Left e) -> do 
          pe <- ppExpParens e
          ppCommsPrefix comms ((d <> pe) : ds)
        (d,Right p) ->
          ppPattern p $ \pp -> 
            ppCommsPrefix comms ((d <> pp) : ds)
      where
        -- How to display the comm, and whether the thing that comes after it
        -- is a binder (as in the case of "c?x -> ...") or not (as in the case
        -- of "c!x -> ..."
        ppComm :: CommField -> (Doc,Either Exp Pat)
        ppComm (CFPlain e)   = (char '.', Left e)
        ppComm (CFInput p)   = (char '?', Right p)
        ppComm (CFOutput e)  = (char '!', Left e)
        ppComm (CFNDInput p) = (char '$', Right p)
ppExp (EProject e1 e2) = ppBOpExp e1 (text "|\\") e2
ppExp (ERename e1 nms) = do
  pe1 <- ppExpParens e1
  pnms <- mapM (\(n1,n2) -> (,) <$> ppExpParens n1 <*> ppExpParens n2) nms
  let prnms = map (\(pn1,pn2) -> pn1 <+> text "<-" <+> pn2) pnms
  return $ pe1 <+> text "[[" <+> commas prnms <+> text "]]"
ppExp (EAlphaParallel p a b q) = do
  pa <- ppExpParens a
  pb <- ppExpParens b
  ppBOpExp p (brackets $ pa <+> text "||" <+> pb) q
ppExp (EGenParallel p a q) = do
  pa <- ppExp a
  ppBOpExp p (text "[|" <+> pa <+> text "|]") q
ppExp (EInterleave e1 e2) = ppBOpExp e1 (text "|||") e2
ppExp (EException p a q)  = do
  pa <- ppExp a
  ppBOpExp p (text "[|" <+> pa <+> text "|>") q
ppExp (EInterrupt e1 e2)  = ppBOpExp e1 (text "/\\") e2
ppExp (ESeq e1 e2)        = ppBOpExp e1 (char ';') e2
ppExp (ELinkedParallel p nms q) = do
  pnms <- mapM (\(c,d) -> (,) <$> ppExpParens c <*> ppExpParens d) nms
  let ppnms = commas $ map (\(pc,pd) -> pc <+> text "<->" <+> pd) pnms
  ppBOpExp p ppnms q
ppExp (ETimeout e1 e2)    = ppBOpExp e1 (text "[>") e2
ppExp (ESyncExtChoice p a q) = do
  pa <- ppExp a
  ppBOpExp p (text "[+" <+> pa <+> text "+]") q
ppExp (ESyncInterrupt p a q) = do
  pa <- ppExp a
  ppBOpExp p (text "/+" <+> pa <+> text "+\\") q
ppExp (ERepAlphaParallel cs a p) =
  ppCompStmt cs $ \pstmts -> do
    pa <- ppExp a
    pp <- ppExpParens p
    return $ text "||" <+> pstmts <+> char '@' <+> brackets pa <+> pp
ppExp (ERepExtChoice cs p)     = ppRepExp (text "[]") cs p
ppExp (ERepGenParallel a cs p) = do
  pa <- ppExp a
  ppRepExp (text "[|" <+> pa <+> text "|]") cs p
ppExp (ERepInterleave cs p)    = ppRepExp (text "|||") cs p
ppExp (ERepIntChoice cs p)     = ppRepExp (text "|~|") cs p
ppExp (ERepLinkedParallel links cs p) = do
  plinks <- mapM (\(l,r) -> (,) <$> ppExpParens l <*> ppExpParens r) links
  let linkedLinks = map (\(pl,pr) -> pl <+> text "<->" <+> pr) plinks
  ppRepExp (brackets $ commas linkedLinks) cs p
ppExp (ERepSeq cs p)           = ppRepExp (char ';') cs p
ppExp (ERepSyncExtChoice a cs p) = do
  pa <- ppExp a
  ppRepExp (text "[+" <+> pa <+> text "+]") cs p
ppExp (EList es)          = do
  tes <- mapM ppExpParens es
  return $ char '<' <+> commas tes <+> char '>'
ppExp (EListComp es cs)   =
  ppCompStmt cs $ \pcs -> do
    pes <- mapM ppExpParens es
    return $ char '<' <+> commas pes <+> char '|' <+> pcs <+> char '>'
ppExp (EListFrom e)       = do
  pe <- ppExpParens e
  return $ char '<' <+> pe <+> text "..>"
ppExp (EListFromComp e cs) =
  ppCompStmt cs $ \pcs -> do
    pe <- ppExpParens e
    return $ char '<' <+> pe <+> text ".. |" <+> pcs <+> char '>'
ppExp (EListFromTo e1 e2) = do
  pe1 <- ppExpParens e1
  pe2 <- ppExpParens e2
  return $ char '<' <+> pe1 <+> text ".." <+> pe2 <+> char '>'
ppExp (EListFromToComp e1 e2 cs) =
  ppCompStmt cs $ \pcs -> do
    pe1 <- ppExpParens e1
    pe2 <- ppExpParens e2
    return $ char '<' <+> pe1 <+> text ".." <+> pe2
                      <+> char '|' <+> pcs <+> char '>'
ppExp (EListConcat e1 e2) = ppBOpExp e1 (char '^') e2
ppExp (EListLength e)     = do
  pe <- ppExp e
  return $ char '#' <+> pe
ppExp (ETuple es)         = parens . commas <$> mapM ppExp es
ppExp (ESet es)           = braces . commas <$> mapM ppExp es
ppExp (ESetComp es cs)    = 
  ppCompStmt cs $ \pcs -> do
    pes <- mapM ppExp es
    return $ braces $ commas pes <+> char '|' <+> pcs
ppExp (ESetFrom e)        = do
  pe <- ppExp e
  return $ braces $ pe <+> text ".."
ppExp (ESetFromComp e cs) =
  ppCompStmt cs $ \pcs -> do
    pe <- ppExp e
    return $ braces $ pe <+> text ".. |" <+> pcs
ppExp (ESetFromTo e1 e2)  = do
  pe1 <- ppExp e1
  pe2 <- ppExp e2
  return $ braces $ pe1 <+> text ".." <+> pe2
ppExp (ESetFromToComp e1 e2 cs) =
  ppCompStmt cs $ \pcs -> do
    pe1 <- ppExp e1
    pe2 <- ppExp e2
    return $ braces $ pe1 <+> text ".." <+> pe2 <+> char '|' <+> pcs
ppExp ESetInt  = return $ text "Int"
ppExp ESetBool = return $ text "Bool"
ppExp (EEnumSet es)       = do
  pes <- commas <$> mapM ppExp es
  return $ text "{|" <+> pes <+> text "|}"
ppExp (EEnumSetComp es cs) =
  ppCompStmt cs $ \pcs -> do
    pes <- commas <$> mapM ppExp es
    return $ text "{|" <+> pes <+> char '|' <+> pcs <+> text "|}"
ppExp (EMap es) = do
  tes <- mapM ppMapPair es
  return $ text "(|" <+> commas tes <+> text "|)"
  where 
    ppMapPair (e1, e2) = do te1 <- ppExpParens e1 
                            te2 <- ppExpParens e2
                            return $ te1 <+> text "=>" <+> te2


-- parens around compound things only
ppExpParens :: Exp -> PP Doc
ppExpParens e@(EId _)                 = ppExp e
ppExpParens e@(EConst _)              = ppExp e
ppExpParens e@(EList _)               = ppExp e
ppExpParens e@(EListComp _ _)         = ppExp e
ppExpParens e@(EListFrom _)           = ppExp e
ppExpParens e@(EListFromComp _ _)     = ppExp e
ppExpParens e@(EListFromTo _ _)       = ppExp e
ppExpParens e@(EListFromToComp _ _ _) = ppExp e
ppExpParens e@(ETuple _)              = ppExp e
ppExpParens e@(ESet _)                = ppExp e
ppExpParens e@(ESetComp _ _)          = ppExp e
ppExpParens e@(ESetFrom _)            = ppExp e
ppExpParens e@(ESetFromComp _ _)      = ppExp e
ppExpParens e@(ESetFromTo _ _)        = ppExp e
ppExpParens e@(ESetFromToComp _ _ _)  = ppExp e
ppExpParens e@(ESetInt)               = ppExp e
ppExpParens e@(ESetBool)              = ppExp e
ppExpParens e@(EEnumSet _)            = ppExp e
ppExpParens e@(EEnumSetComp _ _)      = ppExp e
ppExpParens e@(EMap _)                = ppExp e
ppExpParens e = parens <$> ppExp e

-- Abstract the common case of "binary" operators, which actually comes up much
-- more than just the arithmetic ones.
ppBOpExp :: Exp -> Doc -> Exp -> PP Doc
ppBOpExp e1 pop e2 = do
  pe1 <- ppExpParens e1
  pe2 <- ppExpParens e2
  return $ pe1 <+> pop <+> pe2

-- Abstract the common case of "replicated" operators
ppRepExp :: Doc -> [CompStmt] -> Exp -> PP Doc
ppRepExp op stmts e = do
  ppCompStmt stmts $ \pstmts -> do
    pe <- ppExp e
    return $ op <+> pstmts <+> char '@' <+> pe

ppConst :: Const -> Doc
ppConst CStop = text "STOP"
ppConst CSkip = text "SKIP"
ppConst (CInt n) = integer n
ppConst (CChar c) = char '\'' <> char c <> char '\''
ppConst (CBool b) = if b then text "True" else text "False"
ppConst (CString s) = text $ show s -- XXX hack.  I think this escapes things that matter?
ppConst CUnit = text "UnitVal"

ppUOp :: UOp -> Doc
ppUOp UNeg      = char '-'
ppUOp UNot      = text "not"
--ppUOp (Hide _) = error "Internal error: ppUOpApp called on hide"

ppBinOp :: BinOp -> Doc
ppBinOp BEq         = text "=="
ppBinOp BLeq        = text "<="
ppBinOp BLt         = char '<'
ppBinOp BGeq        = text ">="
ppBinOp BGt         = char '>'
ppBinOp BAnd        = text "and"
ppBinOp BOr         = text "or"
ppBinOp BNeq        = text "!="
ppBinOp BPlus       = char '+'
ppBinOp BMinus      = char '-'
ppBinOp BTimes      = char '*'
ppBinOp BDiv        = char '\\'
ppBinOp BMod        = char '%'

------------------
------------------ Top-level identifiers
------------------

topLevelIDs :: FDRMod -> [Id]
topLevelIDs m = concatMap tlIDs $ topLevels m
  where
   tlIDs :: Definition -> [Id]
   tlIDs (DFun nm _)      = [nm]
   tlIDs (DVar pat _)     = patIDs pat
   tlIDs (DAnnot _ _)     = []
   tlIDs (DDataType nm cnstrs) = nm : map fst cnstrs
   tlIDs (DSubType nm _)  = [nm]
   tlIDs (DChan x _)      = x
   tlIDs (DInclude _)     = []
   tlIDs (DExternal f)    = [f]
   tlIDs (DTransparent f) = [f]
   tlIDs (DAssert _ _)    = []

-- The IDs inside a pattern
patIDs :: Pat -> [Id]
patIDs (PId x)           = [x]
patIDs (PConcat p1 p2)   = patIDs p1 ++ patIDs p2
patIDs (PDot p1 p2)      = concatMap patIDs (p1:p2)
patIDs (PDouble p1 p2)   = patIDs p1 ++ patIDs p2
patIDs (PList ps)        = concatMap patIDs ps
patIDs (PConst _)        = []
patIDs PEmptySet         = []
patIDs (PSingletonSet p) = patIDs p
patIDs (PTuple ps)       = concatMap patIDs ps
patIDs PWildCard         = []

---- This scans a module and adds any top-level names to the monad, since they
---- may be used mutually recursively.
addTLGlobals :: FDRMod -> PP ()
addTLGlobals m = mapM_ addGlobal $ topLevelIDs m

-- The `PP a` arg here is the scope of these locals
addTLLocals :: FDRMod -> PP a -> PP a
addTLLocals m a = addLocals (topLevelIDs m) $ \_ -> a
--------------
--------------
--------------

genStyle :: Style
genStyle = Style {mode = LeftMode, lineLength = 0, ribbonsPerLine = 0}

preludeNames :: [Id]
preludeNames = []

prelude :: PPState
prelude = foldl' addName emptyPPState preludeNames
  where
    addName :: PPState -> Id -> PPState
    addName pps nm@(Fixed s) =
      pps {ppIdMap    = M.insert nm s (ppIdMap pps),
           ppUsedDocs = S.insert s (ppUsedDocs pps)}
    addName pps nm@(Id (s,_)) =
      pps {ppIdMap    = M.insert nm s (ppIdMap pps),
           ppUsedDocs = S.insert s (ppUsedDocs pps),
           ppWarnings = w : (ppWarnings pps)}
      where
        w = "Non-fixed id " ++ show nm ++ " in prelude,"

-- | Pretty-prints an FDR3 module.
-- Returns the pretty-printed module as a string, and a list of warnings.  The
-- module string should be valid FDR3 input.  The warnings will mention any
-- non-fixed identifiers that are used out of their scope.
emitMod :: FDRMod -> (String,[String])
emitMod m = let (d,ws) = runPP prelude $ ppFDRMod m in
               (renderStyle genStyle d, ws)

-- | Pretty-prints an FDR3 module.
-- Returns two lists of strings.  The strings in the first list are the
-- pretty-printed input modules, and should be valid input for FDR3.  The
-- strings in the second list are warnings, and will mention any non-fixed
-- identifiers used out of scope.  Globals with non-fixed identifiers in each
-- input module are considered in scope for subsequent modules in the list.
emitMods :: [FDRMod] -> ([String],[String])
emitMods mds =
  let (ds,ws) = runPP prelude $ mapM ppFDRMod mds in
    (map (renderStyle genStyle) ds, ws)