-- |
-- Module      :  $Header$
-- Copyright   :  (c) 2013-2016 Galois, Inc.
-- License     :  BSD3
-- Maintainer  :  cryptol@galois.com
-- Stability   :  provisional
-- Portability :  portable

{-# LANGUAGE Safe #-}

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE PatternGuards #-}
{-# LANGUAGE RecordWildCards #-}
module Cryptol.Parser.AST
  ( -- * Names
    Ident, mkIdent, mkInfix, isInfixIdent, nullIdent, identText
  , ModName, modRange
  , PName(..), getModName, getIdent, mkUnqual, mkQual
  , Named(..)
  , Pass(..)
  , Assoc(..)

    -- * Types
  , Schema(..)
  , TParam(..)
  , Kind(..)
  , Type(..), tconNames
  , Prop(..)

    -- * Declarations
  , Module(..)
  , Program(..)
  , TopDecl(..)
  , Decl(..)
  , Fixity(..), defaultFixity
  , FixityCmp(..), compareFixity
  , TySyn(..)
  , Bind(..)
  , BindDef(..), LBindDef
  , Pragma(..)
  , ExportType(..)
  , ExportSpec(..), exportBind, exportType
  , isExportedBind, isExportedType
  , TopLevel(..)
  , Import(..), ImportSpec(..)
  , Newtype(..)

    -- * Interactive
  , ReplInput(..)

    -- * Expressions
  , Expr(..)
  , Literal(..), NumInfo(..)
  , Match(..)
  , Pattern(..)
  , Selector(..)
  , TypeInst(..)

    -- * Positions
  , Located(..)
  , LPName, LString, LIdent
  , NoPos(..)

    -- * Pretty-printing
  , cppKind, ppSelector
  ) where

import Cryptol.Parser.Name
import Cryptol.Parser.Position
import Cryptol.Prims.Syntax (TFun(..))
import Cryptol.Utils.Ident
import Cryptol.Utils.PP
import Cryptol.Utils.Panic (panic)

import qualified Data.Set as Set
import           Data.List(intersperse)
import           Data.Bits(shiftR)
import           Data.Maybe (catMaybes)
import qualified Data.Map as Map
import           Numeric(showIntAtBase)

import GHC.Generics (Generic)
import Control.DeepSeq

import Prelude ()
import Prelude.Compat

-- AST -------------------------------------------------------------------------

-- | A name with location information.
type LPName    = Located PName

-- | An identifier with location information.
type LIdent    = Located Ident

-- | A string with location information.
type LString  = Located String


newtype Program name = Program [TopDecl name]
                       deriving (Show)

data Module name = Module { mName    :: Located ModName
                          , mImports :: [Located Import]
                          , mDecls   :: [TopDecl name]
                          } deriving (Show, Generic, NFData)


modRange :: Module name -> Range
modRange m = rCombs $ catMaybes
    [ getLoc (mName m)
    , getLoc (mImports m)
    , getLoc (mDecls m)
    , Just (Range { from = start, to = start, source = "" })
    ]


data TopDecl name = Decl (TopLevel (Decl name))
                  | TDNewtype (TopLevel (Newtype name))
                  | Include (Located FilePath)
                    deriving (Show, Generic, NFData)

data Decl name = DSignature [Located name] (Schema name)
               | DFixity !Fixity [Located name]
               | DPragma [Located name] Pragma
               | DBind (Bind name)
               | DPatBind (Pattern name) (Expr name)
               | DType (TySyn name)
               | DLocated (Decl name) Range
                 deriving (Eq, Show, Generic, NFData)

-- | An import declaration.
data Import = Import { iModule    :: !ModName
                     , iAs        :: Maybe ModName
                     , iSpec      :: Maybe ImportSpec
                     } deriving (Eq, Show, Generic, NFData)

-- | The list of names following an import.
--
-- INVARIANT: All of the 'Name' entries in the list are expected to be
-- unqualified names; the 'QName' or 'NewName' constructors should not be
-- present.
data ImportSpec = Hiding [Ident]
                | Only   [Ident]
                  deriving (Eq, Show, Generic, NFData)

data TySyn n  = TySyn (Located n) [TParam n] (Type n)
                deriving (Eq, Show, Generic, NFData)

{- | Bindings.  Notes:

    * The parser does not associate type signatures and pragmas with
      their bindings: this is done in a separate pass, after de-sugaring
      pattern bindings.  In this way we can associate pragmas and type
      signatures with the variables defined by pattern bindings as well.

    * Currently, there is no surface syntax for defining monomorphic
      bindings (i.e., bindings that will not be automatically generalized
      by the type checker.  However, they are useful when de-sugaring
      patterns.
-}
data Bind name = Bind { bName      :: Located name -- ^ Defined thing
                      , bParams    :: [Pattern name]-- ^ Parameters
                      , bDef       :: Located (BindDef name) -- ^ Definition
                      , bSignature :: Maybe (Schema name) -- ^ Optional type sig
                      , bInfix     :: Bool         -- ^ Infix operator?
                      , bFixity    :: Maybe Fixity -- ^ Optional fixity info
                      , bPragmas   :: [Pragma]     -- ^ Optional pragmas
                      , bMono      :: Bool         -- ^ Is this a monomorphic binding
                      , bDoc       :: Maybe String -- ^ Optional doc string
                      } deriving (Eq, Show, Generic, NFData)

type LBindDef = Located (BindDef PName)

data BindDef name = DPrim
                  | DExpr (Expr name)
                    deriving (Eq, Show, Generic, NFData)

data Fixity   = Fixity { fAssoc :: !Assoc
                       , fLevel :: !Int
                       } deriving (Eq, Show, Generic, NFData)

data FixityCmp = FCError
               | FCLeft
               | FCRight
                 deriving (Show,Eq)

compareFixity :: Fixity -> Fixity -> FixityCmp
compareFixity (Fixity a1 p1) (Fixity a2 p2) =
  case compare p1 p2 of
    GT -> FCLeft
    LT -> FCRight
    EQ -> case (a1,a2) of
            (LeftAssoc,LeftAssoc)   -> FCLeft
            (RightAssoc,RightAssoc) -> FCRight
            _                       -> FCError

-- | The fixity used when none is provided.
defaultFixity :: Fixity
defaultFixity  = Fixity LeftAssoc 100

data Pragma   = PragmaNote String
              | PragmaProperty
                deriving (Eq, Show, Generic, NFData)

data Newtype name = Newtype { nName   :: Located name        -- ^ Type name
                            , nParams :: [TParam name]       -- ^ Type params
                            , nBody   :: [Named (Type name)] -- ^ Constructor
                            } deriving (Eq, Show, Generic, NFData)

-- | Input at the REPL, which can either be an expression or a @let@
-- statement.
data ReplInput name = ExprInput (Expr name)
                    | LetInput (Decl name)
                      deriving (Eq, Show)

-- | Export information for a declaration.
data ExportType = Public
                | Private
                  deriving (Eq, Show, Ord, Generic, NFData)

data TopLevel a = TopLevel { tlExport :: ExportType
                           , tlDoc    :: Maybe (Located String)
                           , tlValue  :: a
                           }
  deriving (Show, Generic, NFData, Functor, Foldable, Traversable)

data ExportSpec name = ExportSpec { eTypes  :: Set.Set name
                                  , eBinds  :: Set.Set name
                                  } deriving (Show, Generic, NFData)

instance Ord name => Monoid (ExportSpec name) where
  mempty      = ExportSpec { eTypes = mempty, eBinds = mempty }
  mappend l r = ExportSpec { eTypes = mappend (eTypes l) (eTypes r)
                           , eBinds  = mappend (eBinds  l) (eBinds  r)
                           }

-- | Add a binding name to the export list, if it should be exported.
exportBind :: Ord name => TopLevel name -> ExportSpec name
exportBind n
  | tlExport n == Public = mempty { eBinds = Set.singleton (tlValue n) }
  | otherwise            = mempty

-- | Check to see if a binding is exported.
isExportedBind :: Ord name => name -> ExportSpec name -> Bool
isExportedBind n = Set.member n . eBinds

-- | Add a type synonym name to the export list, if it should be exported.
exportType :: Ord name => TopLevel name -> ExportSpec name
exportType n
  | tlExport n == Public = mempty { eTypes = Set.singleton (tlValue n) }
  | otherwise            = mempty

-- | Check to see if a type synonym is exported.
isExportedType :: Ord name => name -> ExportSpec name -> Bool
isExportedType n = Set.member n . eTypes

-- | Infromation about the representation of a numeric constant.
data NumInfo  = BinLit Int                      -- ^ n-digit binary literal
              | OctLit Int                      -- ^ n-digit octal  literal
              | DecLit                          -- ^ overloaded decimal literal
              | HexLit Int                      -- ^ n-digit hex literal
              | CharLit                         -- ^ character literal
              | PolyLit Int                     -- ^ polynomial literal
                deriving (Eq, Show, Generic, NFData)

-- | Literals.
data Literal  = ECNum Integer NumInfo           -- ^ @0x10@  (HexLit 2)
              | ECString String                 -- ^ @\"hello\"@
                deriving (Eq, Show, Generic, NFData)

data Expr n   = EVar n                          -- ^ @ x @
              | ELit Literal                    -- ^ @ 0x10 @
              | ETuple [Expr n]                 -- ^ @ (1,2,3) @
              | ERecord [Named (Expr n)]        -- ^ @ { x = 1, y = 2 } @
              | ESel (Expr n) Selector          -- ^ @ e.l @
              | EList [Expr n]                  -- ^ @ [1,2,3] @
              | EFromTo (Type n) (Maybe (Type n)) (Maybe (Type n)) -- ^ @[1, 5 ..  117 ] @
              | EInfFrom (Expr n) (Maybe (Expr n))-- ^ @ [1, 3 ...] @
              | EComp (Expr n) [[Match n]]      -- ^ @ [ 1 | x <- xs ] @
              | EApp (Expr n) (Expr n)          -- ^ @ f x @
              | EAppT (Expr n) [(TypeInst n)]   -- ^ @ f `{x = 8}, f`{8} @
              | EIf (Expr n) (Expr n) (Expr n)  -- ^ @ if ok then e1 else e2 @
              | EWhere (Expr n) [Decl n]        -- ^ @ 1 + x where { x = 2 } @
              | ETyped (Expr n) (Type n)        -- ^ @ 1 : [8] @
              | ETypeVal (Type n)               -- ^ @ `(x + 1)@, @x@ is a type
              | EFun [Pattern n] (Expr n)       -- ^ @ \\x y -> x @
              | ELocated (Expr n) Range         -- ^ position annotation

              | EParens (Expr n)                -- ^ @ (e)   @ (Removed by Fixity)
              | EInfix (Expr n) (Located n) Fixity (Expr n)-- ^ @ a + b @ (Removed by Fixity)
                deriving (Eq, Show, Generic, NFData)

data TypeInst name = NamedInst (Named (Type name))
                   | PosInst (Type name)
                     deriving (Eq, Show, Generic, NFData)

{- | Selectors are used for projecting from various components.
Each selector has an option spec to specify the shape of the thing
that is being selected.  Currently, there is no surface syntax for
list selectors, but they are used during the desugaring of patterns.
-}

data Selector = TupleSel Int   (Maybe Int)
                -- ^ Zero-based tuple selection.
                -- Optionally specifies the shape of the tuple (one-based).

              | RecordSel Ident (Maybe [Ident])
                -- ^ Record selection.
                -- Optionally specifies the shape of the record.

              | ListSel Int    (Maybe Int)
                -- ^ List selection.
                -- Optionally specifies the length of the list.
                deriving (Eq, Show, Ord, Generic, NFData)

data Match name = Match (Pattern name) (Expr name)              -- ^ p <- e
                | MatchLet (Bind name)
                  deriving (Eq, Show, Generic, NFData)

data Pattern n = PVar (Located n)              -- ^ @ x @
               | PWild                         -- ^ @ _ @
               | PTuple [Pattern n]            -- ^ @ (x,y,z) @
               | PRecord [ Named (Pattern n) ] -- ^ @ { x = (a,b,c), y = z } @
               | PList [ Pattern n ]           -- ^ @ [ x, y, z ] @
               | PTyped (Pattern n) (Type n)   -- ^ @ x : [8] @
               | PSplit (Pattern n) (Pattern n)-- ^ @ (x # y) @
               | PLocated (Pattern n) Range    -- ^ Location information
                 deriving (Eq, Show, Generic, NFData)

data Named a = Named { name :: Located Ident, value :: a }
  deriving (Eq, Show, Foldable, Traversable, Generic, NFData, Functor)

data Schema n = Forall [TParam n] [Prop n] (Type n) (Maybe Range)
  deriving (Eq, Show, Generic, NFData)

data Kind = KNum | KType
  deriving (Eq, Show, Generic, NFData)

data TParam n = TParam { tpName  :: n
                       , tpKind  :: Maybe Kind
                       , tpRange :: Maybe Range
                       }
  deriving (Eq, Show, Generic, NFData)

data Type n = TFun (Type n) (Type n)  -- ^ @[8] -> [8]@
            | TSeq (Type n) (Type n)  -- ^ @[8] a@
            | TBit                    -- ^ @Bit@
            | TNum Integer            -- ^ @10@
            | TChar Char              -- ^ @'a'@
            | TInf                    -- ^ @inf@
            | TUser n [Type n]        -- ^ A type variable or synonym
            | TApp TFun [Type n]      -- ^ @2 + x@
            | TRecord [Named (Type n)]-- ^ @{ x : [8], y : [32] }@
            | TTuple [Type n]         -- ^ @([8], [32])@
            | TWild                   -- ^ @_@, just some type.
            | TLocated (Type n) Range -- ^ Location information
            | TParens (Type n)        -- ^ @ (ty) @
            | TInfix (Type n) (Located n) Fixity (Type n) -- ^ @ ty + ty @
              deriving (Eq, Show, Generic, NFData)

tconNames :: Map.Map PName (Type PName)
tconNames  = Map.fromList
  [ (mkUnqual (packIdent "Bit"), TBit)
  , (mkUnqual (packIdent "inf"), TInf)
  ]

data Prop n   = CFin (Type n)             -- ^ @ fin x   @
              | CEqual (Type n) (Type n)  -- ^ @ x == 10 @
              | CGeq (Type n) (Type n)    -- ^ @ x >= 10 @
              | CArith (Type n)           -- ^ @ Arith a @
              | CCmp (Type n)             -- ^ @ Cmp a @
              | CLocated (Prop n) Range   -- ^ Location information
              | CType (Type n)            -- ^ After parsing
                deriving (Eq, Show, Generic, NFData)

--------------------------------------------------------------------------------
-- Note: When an explicit location is missing, we could use the sub-components
-- to try to estimate a location...


instance AddLoc (Expr n) where
  addLoc = ELocated

  dropLoc (ELocated e _) = dropLoc e
  dropLoc e              = e

instance HasLoc (Expr name) where
  getLoc (ELocated _ r) = Just r
  getLoc _              = Nothing

instance HasLoc (TParam name) where
  getLoc (TParam _ _ r) = r

instance AddLoc (TParam name) where
  addLoc (TParam a b _) l = TParam a b (Just l)
  dropLoc (TParam a b _)  = TParam a b Nothing

instance HasLoc (Type name) where
  getLoc (TLocated _ r) = Just r
  getLoc _              = Nothing

instance AddLoc (Type name) where
  addLoc = TLocated

  dropLoc (TLocated e _) = dropLoc e
  dropLoc e              = e

instance HasLoc (Prop name) where
  getLoc (CLocated _ r) = Just r
  getLoc _              = Nothing

instance AddLoc (Prop name) where
  addLoc = CLocated

  dropLoc (CLocated e _) = dropLoc e
  dropLoc e              = e

instance AddLoc (Pattern name) where
  addLoc = PLocated

  dropLoc (PLocated e _) = dropLoc e
  dropLoc e              = e

instance HasLoc (Pattern name) where
  getLoc (PLocated _ r) = Just r
  getLoc (PTyped r _)   = getLoc r
  getLoc (PVar x)       = getLoc x
  getLoc _              = Nothing

instance HasLoc (Bind name) where
  getLoc b = getLoc (bName b, bDef b)

instance HasLoc (Match name) where
  getLoc (Match p e)    = getLoc (p,e)
  getLoc (MatchLet b)   = getLoc b

instance HasLoc a => HasLoc (Named a) where
  getLoc l = getLoc (name l, value l)

instance HasLoc (Schema name) where
  getLoc (Forall _ _ _ r) = r

instance AddLoc (Schema name) where
  addLoc  (Forall xs ps t _) r = Forall xs ps t (Just r)
  dropLoc (Forall xs ps t _)   = Forall xs ps t Nothing

instance HasLoc (Decl name) where
  getLoc (DLocated _ r) = Just r
  getLoc _              = Nothing

instance AddLoc (Decl name) where
  addLoc d r             = DLocated d r

  dropLoc (DLocated d _) = dropLoc d
  dropLoc d              = d

instance HasLoc a => HasLoc (TopLevel a) where
  getLoc = getLoc . tlValue

instance HasLoc (TopDecl name) where
  getLoc td = case td of
    Decl tld    -> getLoc tld
    TDNewtype n -> getLoc n
    Include lfp -> getLoc lfp

instance HasLoc (Module name) where
  getLoc m
    | null locs = Nothing
    | otherwise = Just (rCombs locs)
    where
    locs = catMaybes [ getLoc (mName m)
                     , getLoc (mImports m)
                     , getLoc (mDecls m)
                     ]

instance HasLoc (Newtype name) where
  getLoc n
    | null locs = Nothing
    | otherwise = Just (rCombs locs)
    where
    locs = catMaybes [ getLoc (nName n), getLoc (nBody n) ]


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





--------------------------------------------------------------------------------
-- Pretty printing


ppL :: PP a => Located a -> Doc
ppL = pp . thing

ppNamed :: PP a => String -> Named a -> Doc
ppNamed s x = ppL (name x) <+> text s <+> pp (value x)


instance (Show name, PPName name) => PP (Module name) where
  ppPrec _ m = text "module" <+> ppL (mName m) <+> text "where"
            $$ vcat (map ppL (mImports m))
            $$ vcat (map pp (mDecls m))

instance (Show name, PPName name) => PP (Program name) where
  ppPrec _ (Program ds) = vcat (map pp ds)

instance (Show name, PPName name) => PP (TopDecl name) where
  ppPrec _ top_decl =
    case top_decl of
      Decl    d   -> pp d
      TDNewtype n -> pp n
      Include l   -> text "include" <+> text (show (thing l))

instance (Show name, PPName name) => PP (Decl name) where
  ppPrec n decl =
    case decl of
      DSignature xs s -> commaSep (map ppL xs) <+> text ":" <+> pp s
      DPatBind p e    -> pp p <+> text "=" <+> pp e
      DBind b         -> ppPrec n b
      DFixity f ns    -> ppFixity f ns
      DPragma xs p    -> ppPragma xs p
      DType ts        -> ppPrec n ts
      DLocated d _    -> ppPrec n d

ppFixity :: PPName name => Fixity -> [Located name] -> Doc
ppFixity (Fixity LeftAssoc  i) ns = text "infixl" <+> int i <+> commaSep (map pp ns)
ppFixity (Fixity RightAssoc i) ns = text "infixr" <+> int i <+> commaSep (map pp ns)
ppFixity (Fixity NonAssoc   i) ns = text "infix"  <+> int i <+> commaSep (map pp ns)

instance PPName name => PP (Newtype name) where
  ppPrec _ nt = hsep
    [ text "newtype", ppL (nName nt), hsep (map pp (nParams nt)), char '='
    , braces (commaSep (map (ppNamed ":") (nBody nt))) ]

instance PP Import where
  ppPrec _ d = text "import" <+> sep [ pp (iModule d), mbAs, mbSpec ]
    where
    mbAs = maybe empty (\ name -> text "as" <+> pp name ) (iAs d)

    mbSpec = maybe empty pp (iSpec d)

instance PP ImportSpec where
  ppPrec _ s = case s of
    Hiding names -> text "hiding" <+> parens (commaSep (map pp names))
    Only names   ->                   parens (commaSep (map pp names))

-- TODO: come up with a good way of showing the export specification here
instance PP a => PP (TopLevel a) where
  ppPrec _ tl = pp (tlValue tl)


instance PP Pragma where
  ppPrec _ (PragmaNote x) = text x
  ppPrec _ PragmaProperty = text "property"

ppPragma :: PPName name => [Located name] -> Pragma -> Doc
ppPragma xs p =
  text "/*" <+> text "pragma" <+> commaSep (map ppL xs) <+> text ":" <+> pp p
  <+> text "*/"

instance (Show name, PPName name) => PP (Bind name) where
  ppPrec _ b = sig $$ vcat [ ppPragma [f] p | p <- bPragmas b ] $$
               hang (def <+> eq) 4 (pp (thing (bDef b)))
    where def | bInfix b  = lhsOp
              | otherwise = lhs
          f = bName b
          sig = case bSignature b of
                  Nothing -> empty
                  Just s  -> pp (DSignature [f] s)
          eq  = if bMono b then text ":=" else text "="
          lhs = ppL f <+> fsep (map (ppPrec 3) (bParams b))

          lhsOp = case bParams b of
                    [x,y] -> pp x <+> ppL f <+> pp y
                    _     -> panic "AST" [ "Malformed infix operator", show b ]


instance (Show name, PPName name) => PP (BindDef name) where
  ppPrec _ DPrim     = text "<primitive>"
  ppPrec p (DExpr e) = ppPrec p e


instance PPName name => PP (TySyn name) where
  ppPrec _ (TySyn x xs t) = text "type" <+> ppL x <+> fsep (map (ppPrec 1) xs)
                                        <+> text "=" <+> pp t
instance PP Literal where
  ppPrec _ lit =
    case lit of
      ECNum n i     -> ppNumLit n i
      ECString s    -> text (show s)


ppNumLit :: Integer -> NumInfo -> Doc
ppNumLit n info =
  case info of
    DecLit    -> integer n
    CharLit   -> text (show (toEnum (fromInteger n) :: Char))
    BinLit w  -> pad 2  "0b" w
    OctLit w  -> pad 8  "0o" w
    HexLit w  -> pad 16 "0x" w
    PolyLit w -> text "<|" <+> poly w <+> text "|>"
  where
  pad base pref w =
    let txt = showIntAtBase base ("0123456789abcdef" !!) n ""
    in text pref <> text (replicate (w - length txt) '0') <> text txt

  poly w = let (res,deg) = bits Nothing [] 0 n
               z | w == 0 = []
                 | Just d <- deg, d + 1 == w = []
                 | otherwise = [polyTerm0 (w-1)]
           in fsep $ intersperse (text "+") $ z ++ map polyTerm res

  polyTerm 0 = text "1"
  polyTerm 1 = text "x"
  polyTerm p = text "x" <> text "^^" <> int p

  polyTerm0 0 = text "0"
  polyTerm0 p = text "0" <> text "*" <> polyTerm p

  bits d res p num
    | num == 0  = (res,d)
    | even num  = bits d             res  (p + 1) (num `shiftR` 1)
    | otherwise = bits (Just p) (p : res) (p + 1) (num `shiftR` 1)

wrap :: Int -> Int -> Doc -> Doc
wrap contextPrec myPrec doc = if myPrec < contextPrec then parens doc else doc

isEApp :: Expr n -> Maybe (Expr n, Expr n)
isEApp (ELocated e _)     = isEApp e
isEApp (EApp e1 e2)       = Just (e1,e2)
isEApp _                  = Nothing

asEApps :: Expr n -> (Expr n, [Expr n])
asEApps expr = go expr []
    where go e es = case isEApp e of
                      Nothing       -> (e, es)
                      Just (e1, e2) -> go e1 (e2 : es)

instance PPName name => PP (TypeInst name) where
  ppPrec _ (PosInst t)   = pp t
  ppPrec _ (NamedInst x) = ppNamed "=" x

{- Precedences:
0: lambda, if, where, type annotation
2: infix expression   (separate precedence table)
3: application, prefix expressions
-}
instance (Show name, PPName name) => PP (Expr name) where
  -- Wrap if top level operator in expression is less than `n`
  ppPrec n expr =
    case expr of

      -- atoms
      EVar x        -> ppPrefixName x
      ELit x        -> pp x
      ETuple es     -> parens (commaSep (map pp es))
      ERecord fs    -> braces (commaSep (map (ppNamed "=") fs))
      EList es      -> brackets (commaSep (map pp es))
      EFromTo e1 e2 e3 -> brackets (pp e1 <> step <+> text ".." <+> end)
        where step = maybe empty (\e -> comma <+> pp e) e2
              end  = maybe empty pp e3
      EInfFrom e1 e2 -> brackets (pp e1 <> step <+> text "...")
        where step = maybe empty (\e -> comma <+> pp e) e2
      EComp e mss   -> brackets (pp e <+> vcat (map arm mss))
        where arm ms = text "|" <+> commaSep (map pp ms)
      ETypeVal t    -> text "`" <> ppPrec 5 t     -- XXX
      EAppT e ts    -> ppPrec 4 e <> text "`" <> braces (commaSep (map pp ts))
      ESel    e l   -> ppPrec 4 e <> text "." <> pp l

      -- low prec
      EFun xs e     -> wrap n 0 ((text "\\" <> hsep (map (ppPrec 3) xs)) <+>
                                 text "->" <+> pp e)

      EIf e1 e2 e3  -> wrap n 0 $ sep [ text "if"   <+> pp e1
                                      , text "then" <+> pp e2
                                      , text "else" <+> pp e3 ]

      ETyped e t    -> wrap n 0 (ppPrec 2 e <+> text ":" <+> pp t)

      EWhere  e ds  -> wrap n 0 (pp e
                                $$ text "where"
                                $$ nest 2 (vcat (map pp ds))
                                $$ text "")

      -- infix applications
      _ | Just ifix <- isInfix expr ->
              optParens (n > 2)
              $ ppInfix 2 isInfix ifix

      EApp _ _      -> let (e, es) = asEApps expr in
                       wrap n 3 (ppPrec 3 e <+> fsep (map (ppPrec 4) es))

      ELocated e _  -> ppPrec n e

      EParens e -> parens (pp e)

      EInfix e1 op _ e2 -> wrap n 0 (pp e1 <+> ppInfixName (thing op) <+> pp e2)
   where
   isInfix (EApp (EApp (EVar ieOp) ieLeft) ieRight) = do
     (ieAssoc,iePrec) <- ppNameFixity ieOp
     return Infix { .. }
   isInfix _ = Nothing

instance PP Selector where
  ppPrec _ sel =
    case sel of
      TupleSel x sig    -> int x <+> ppSig tupleSig sig
      RecordSel x sig  -> pp x  <+> ppSig recordSig sig
      ListSel x sig    -> int x <+> ppSig listSig sig

    where
    tupleSig n   = int n
    recordSig xs = braces $ fsep $ punctuate comma $ map pp xs
    listSig n    = int n

    ppSig f = maybe empty (\x -> text "/* of" <+> f x <+> text "*/")


-- | Display the thing selected by the selector, nicely.
ppSelector :: Selector -> Doc
ppSelector sel =
  case sel of
    TupleSel x _  -> ordinal x <+> text "field"
    RecordSel x _ -> text "field" <+> pp x
    ListSel x _   -> ordinal x <+> text "element"



instance PPName name => PP (Pattern name) where
  ppPrec n pat =
    case pat of
      PVar x        -> pp (thing x)
      PWild         -> char '_'
      PTuple ps     -> parens   (commaSep (map pp ps))
      PRecord fs    -> braces   (commaSep (map (ppNamed "=") fs))
      PList ps      -> brackets (commaSep (map pp ps))
      PTyped p t    -> wrap n 0 (ppPrec 1 p  <+> text ":" <+> pp t)
      PSplit p1 p2  -> wrap n 1 (ppPrec 1 p1 <+> text "#" <+> ppPrec 1 p2)
      PLocated p _  -> ppPrec n p

instance (Show name, PPName name) => PP (Match name) where
  ppPrec _ (Match p e)  = pp p <+> text "<-" <+> pp e
  ppPrec _ (MatchLet b) = pp b


instance PPName name => PP (Schema name) where
  ppPrec _ (Forall xs ps t _) = sep [vars <+> preds, pp t]
    where vars = case xs of
                   [] -> empty
                   _  -> braces (commaSep (map pp xs))
          preds = case ps of
                    [] -> empty
                    _  -> parens (commaSep (map pp ps)) <+> text "=>"

instance PP Kind where
  ppPrec _ KType  = text "*"
  ppPrec _ KNum   = text "#"

-- | "Conversational" printing of kinds (e.g., to use in error messages)
cppKind :: Kind -> Doc
cppKind KType = text "a value type"
cppKind KNum  = text "a numeric type"

instance PPName name => PP (TParam name) where
  ppPrec n (TParam p Nothing _)   = ppPrec n p
  ppPrec n (TParam p (Just k) _)  = wrap n 1 (pp p <+> text ":" <+> pp k)

-- 4: wrap [_] t
-- 3: wrap application
-- 2: wrap function
-- 1:
instance PPName name => PP (Type name) where
  ppPrec n ty =
    case ty of
      TWild          -> text "_"
      TTuple ts      -> parens $ commaSep $ map pp ts
      TRecord fs     -> braces $ commaSep $ map (ppNamed ":") fs
      TBit           -> text "Bit"
      TInf           -> text "inf"
      TNum x         -> integer x
      TChar x        -> text (show x)
      TSeq t1 TBit   -> brackets (pp t1)
      TSeq t1 t2     -> optParens (n > 3)
                      $ brackets (pp t1) <> ppPrec 3 t2

      _ | Just tinf <- isInfix ty ->
              optParens (n > 2)
              $ ppInfix 2 isInfix tinf

      TApp f ts      -> optParens (n > 2)
                      $ pp f <+> fsep (map (ppPrec 4) ts)

      TUser f []     -> ppPrefixName f

      TUser f ts     -> optParens (n > 2)
                      $ ppPrefixName f <+> fsep (map (ppPrec 4) ts)

      TFun t1 t2     -> optParens (n > 1)
                      $ sep [ppPrec 2 t1 <+> text "->", ppPrec 1 t2]

      TLocated t _   -> ppPrec n t

      TParens t      -> parens (pp t)

      TInfix t1 o _ t2 -> optParens (n > 0)
                        $ sep [ppPrec 2 t1 <+> ppInfixName o, ppPrec 1 t2]

   where
   isInfix (TApp ieOp [ieLeft, ieRight]) = do
     (ieAssoc,iePrec) <- ppNameFixity ieOp
     return Infix { .. }
   isInfix _ = Nothing

instance PPName name => PP (Prop name) where
  ppPrec n prop =
    case prop of
      CFin t       -> text "fin"   <+> ppPrec 4 t
      CArith t     -> text "Arith" <+> ppPrec 4 t
      CCmp t       -> text "Cmp"   <+> ppPrec 4 t
      CEqual t1 t2 -> ppPrec 2 t1 <+> text "==" <+> ppPrec 2 t2
      CGeq t1 t2   -> ppPrec 2 t1 <+> text ">=" <+> ppPrec 2 t2
      CLocated c _ -> ppPrec n c

      CType t      -> ppPrec n t


--------------------------------------------------------------------------------
-- Drop all position information, so equality reflects program structure

class NoPos t where
  noPos :: t -> t

-- WARNING: This does not call `noPos` on the `thing` inside
instance NoPos (Located t) where
  noPos x = x { srcRange = rng }
    where rng = Range { from = Position 0 0, to = Position 0 0, source = "" }

instance NoPos t => NoPos (Named t) where
  noPos t = Named { name = noPos (name t), value = noPos (value t) }

instance NoPos t => NoPos [t]       where noPos = fmap noPos
instance NoPos t => NoPos (Maybe t) where noPos = fmap noPos

instance NoPos (Program name) where
  noPos (Program x) = Program (noPos x)

instance NoPos (Module name) where
  noPos m = Module { mName    = mName m
                   , mImports = noPos (mImports m)
                   , mDecls   = noPos (mDecls m)
                   }

instance NoPos (TopDecl name) where
  noPos decl =
    case decl of
      Decl    x   -> Decl     (noPos x)
      TDNewtype n -> TDNewtype(noPos n)
      Include x   -> Include  (noPos x)

instance NoPos a => NoPos (TopLevel a) where
  noPos tl = tl { tlValue = noPos (tlValue tl) }

instance NoPos (Decl name) where
  noPos decl =
    case decl of
      DSignature x y   -> DSignature (noPos x) (noPos y)
      DPragma    x y   -> DPragma    (noPos x) (noPos y)
      DPatBind   x y   -> DPatBind   (noPos x) (noPos y)
      DFixity f ns     -> DFixity f (noPos ns)
      DBind      x     -> DBind      (noPos x)
      DType      x     -> DType      (noPos x)
      DLocated   x _   -> noPos x

instance NoPos (Newtype name) where
  noPos n = Newtype { nName   = noPos (nName n)
                    , nParams = nParams n
                    , nBody   = noPos (nBody n)
                    }

instance NoPos (Bind name) where
  noPos x = Bind { bName      = noPos (bName      x)
                 , bParams    = noPos (bParams    x)
                 , bDef       = noPos (bDef       x)
                 , bSignature = noPos (bSignature x)
                 , bInfix     = bInfix x
                 , bFixity    = bFixity x
                 , bPragmas   = noPos (bPragmas   x)
                 , bMono      = bMono x
                 , bDoc       = bDoc x
                 }

instance NoPos Pragma where
  noPos p@(PragmaNote {})   = p
  noPos p@(PragmaProperty)  = p




instance NoPos (TySyn name) where
  noPos (TySyn x y z) = TySyn (noPos x) (noPos y) (noPos z)

instance NoPos (Expr name) where
  noPos expr =
    case expr of
      EVar x        -> EVar     x
      ELit x        -> ELit     x
      ETuple x      -> ETuple   (noPos x)
      ERecord x     -> ERecord  (noPos x)
      ESel x y      -> ESel     (noPos x) y
      EList x       -> EList    (noPos x)
      EFromTo x y z -> EFromTo  (noPos x) (noPos y) (noPos z)
      EInfFrom x y  -> EInfFrom (noPos x) (noPos y)
      EComp x y     -> EComp    (noPos x) (noPos y)
      EApp  x y     -> EApp     (noPos x) (noPos y)
      EAppT x y     -> EAppT    (noPos x) (noPos y)
      EIf   x y z   -> EIf      (noPos x) (noPos y) (noPos z)
      EWhere x y    -> EWhere   (noPos x) (noPos y)
      ETyped x y    -> ETyped   (noPos x) (noPos y)
      ETypeVal x    -> ETypeVal (noPos x)
      EFun x y      -> EFun     (noPos x) (noPos y)
      ELocated x _  -> noPos x

      EParens e     -> EParens (noPos e)
      EInfix x y f z-> EInfix (noPos x) y f (noPos z)

instance NoPos (TypeInst name) where
  noPos (PosInst ts)   = PosInst (noPos ts)
  noPos (NamedInst fs) = NamedInst (noPos fs)

instance NoPos (Match name) where
  noPos (Match x y)  = Match (noPos x) (noPos y)
  noPos (MatchLet b) = MatchLet (noPos b)

instance NoPos (Pattern name) where
  noPos pat =
    case pat of
      PVar x       -> PVar    (noPos x)
      PWild        -> PWild
      PTuple x     -> PTuple  (noPos x)
      PRecord x    -> PRecord (noPos x)
      PList x      -> PList   (noPos x)
      PTyped x y   -> PTyped  (noPos x) (noPos y)
      PSplit x y   -> PSplit  (noPos x) (noPos y)
      PLocated x _ -> noPos x

instance NoPos (Schema name) where
  noPos (Forall x y z _) = Forall (noPos x) (noPos y) (noPos z) Nothing

instance NoPos (TParam name) where
  noPos (TParam x y _)  = TParam x y Nothing

instance NoPos (Type name) where
  noPos ty =
    case ty of
      TWild         -> TWild
      TApp x y      -> TApp     x         (noPos y)
      TUser x y     -> TUser    x         (noPos y)
      TRecord x     -> TRecord  (noPos x)
      TTuple x      -> TTuple   (noPos x)
      TFun x y      -> TFun     (noPos x) (noPos y)
      TSeq x y      -> TSeq     (noPos x) (noPos y)
      TBit          -> TBit
      TInf          -> TInf
      TNum n        -> TNum n
      TChar n       -> TChar n
      TLocated x _  -> noPos x
      TParens x     -> TParens (noPos x)
      TInfix x y f z-> TInfix (noPos x) y f (noPos z)

instance NoPos (Prop name) where
  noPos prop =
    case prop of
      CEqual  x y   -> CEqual  (noPos x) (noPos y)
      CGeq x y      -> CGeq (noPos x) (noPos y)
      CFin x        -> CFin (noPos x)
      CArith x      -> CArith (noPos x)
      CCmp x        -> CCmp   (noPos x)
      CLocated c _  -> noPos c
      CType t       -> CType (noPos t)