--
-- Parser AST.
--
-- Copyright (C) 2014, Galois, Inc.
-- All rights reserved.
--

module Ivory.Language.Syntax.Concrete.ParseAST where

import           Data.List                               (foldl')
import           Prelude                                 ()
import           Prelude.Compat                          hiding (init)

import           Ivory.Language.Syntax.Concrete.Location

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

type FnSym     = String
type Var       = String
type RefVar    = String
type IxVar     = String
type TypeVar   = String
type FieldNm   = String
type MacroVar  = String

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

-- Top level symbols.
data GlobalSym = GlobalProc       ProcDef
               | GlobalInclProc   IncludeProc
               | GlobalStruct     StructDef
               | GlobalBitData    BitDataDef
               | GlobalTypeDef    TypeDef
               | GlobalConstDef   ConstDef
               | GlobalInclude    IncludeDef
               | GlobalExtern     Extern
               | GlobalArea       AreaDef
               | GlobalAreaImport AreaImportDef
  deriving (Show, Read, Eq, Ord)

--------------------------------------------------------------------------------
-- Mem Areas

data AreaDef = AreaDef
  { areaConst  :: Bool
  , areaType   :: Type
  , areaInit   :: AllocRef
  , memAreaLoc :: SrcLoc
  } deriving (Show, Read, Eq, Ord)

data AreaImportDef = AreaImportDef
  { aiSym   :: String
  , aiConst :: Bool
  , aiType  :: Type
  , aiFile  :: String
  , aiLoc   :: SrcLoc
  } deriving (Show, Read, Eq, Ord)

--------------------------------------------------------------------------------
-- Includes

data IncludeDef = IncludeDef
  { inclModule :: String
  , inclDefLoc :: SrcLoc
  } deriving (Show, Read, Eq, Ord)

--------------------------------------------------------------------------------
-- Externs

data Extern = Extern
  { externSym  :: String
  , externFile :: String
  , externType :: Type
  , externLoc  :: SrcLoc
  } deriving (Show, Read, Eq, Ord)

--------------------------------------------------------------------------------
-- Constant definition

data ConstDef = ConstDef
  { constSym    :: String
  , constExp    :: Exp
  , constType   :: Maybe Type
  , constDefLoc :: SrcLoc
  } deriving (Show, Read, Eq, Ord)

--------------------------------------------------------------------------------
-- Type definition

data TypeDef = TypeDef
  { tySym    :: String
  , tyDef    :: Type
  , tyDefLoc :: SrcLoc
  } deriving (Show, Read, Eq, Ord)

--------------------------------------------------------------------------------
-- Procs

data ProcDef = ProcDef
  { procTy      :: Type         -- ^ Return type
  , procSym     :: FnSym        -- ^ Function name
  , procArgs    :: [(Type,Var)] -- ^ Argument types
  , procStmt    :: [Stmt]       -- ^ Body
  , procPrePost :: [PrePost]
  , procLoc     :: SrcLoc
  } deriving (Show, Read, Eq, Ord)

-- | We distinguish the name used from the name imported so the same symbol can
-- be used twice at different types. (E.g., @printf@).
data IncludeProc = IncludeProc
  { procInclTy   :: Type         -- ^ Return type
  , procInclSym  :: FnSym        -- ^ Function name used
  , procInclArgs :: [(Type,Var)] -- ^ Argument types
-- XXX add later
--  , procInclPrePost :: [PrePost]
  , procIncl     :: (String, FnSym) -- ^ Header to import from and function
                                       -- name imported
  , procInclLoc  :: SrcLoc
  } deriving (Show, Read, Eq, Ord)

-- Pre and post conditions
data PrePost = PreCond  Exp
             | PostCond Exp
  deriving (Show, Read, Eq, Ord)

--------------------------------------------------------------------------------
-- Types

type SzType = Either String Integer

data Type
  = TyVoid                               -- ^ Unit type
  | TyInt IntSize                        -- ^ Signed ints
  | TyWord WordSize                      -- ^ Unsigned ints
  | TyBool                               -- ^ Booleans
  | TyChar                               -- ^ Characters
  | TyFloat                              -- ^ Floats
  | TyDouble                             -- ^ Doubles
  -- XXX
  -- | TyPtr Type                        -- ^ Pointers
  | TyIx Integer                         -- ^ Index type
  | TyString                             -- ^ Static strings
  | TyStored Type                        -- ^ References
  | TyStruct String                      -- ^ Structures
  | TyArray Type SzType                  -- ^ Arrays of fixed length (can be a macro or integer)
  | TyRef      Scope Type                -- ^ References
  | TyConstRef Scope Type                -- ^ Constant References
  | TySynonym String                     -- ^ Type synonym
  | LocTy (Located Type)
  deriving (Show, Read, Eq, Ord)

-- Helper to put array type indexes in the right order.
tyArray :: Type -> SzType -> [SzType] -> Type
tyArray ty idx idxs =
  let idxs' = idxs ++ [idx] in
  let i = head idxs' in
  foldl' TyArray (TyArray ty i) (tail idxs')

data Scope =
    Stack (Maybe TypeVar)
    -- ^ Stack allocated.  If no type variable is provided, a fresh one is
    -- constructed.
  | Global
  -- ^ Globally allocated
  | PolyMem (Maybe TypeVar)
  -- ^ Either allocation.  If no type variable is provided, a fresh one is
  -- constructed.
  deriving (Show, Read, Eq, Ord)

data IntSize
  = Int8
  | Int16
  | Int32
  | Int64
  deriving (Show, Read, Eq, Ord)

data WordSize
  = Word8
  | Word16
  | Word32
  | Word64
  deriving (Show, Read, Eq, Ord)

--------------------------------------------------------------------------------
-- Expressions

data Literal
  = LitInteger Integer
  | LitFloat Double -- represents floats, too
  | LitString String
  deriving (Show, Read, Eq, Ord)

data Exp
  = ExpLit Literal
  | ExpVar Var
  | ExpRet -- Used only in post-conditions
  | ExpOp ExpOp [Exp]
  | IvoryMacroExp (String,[Exp])
  | ExpDeref  Exp
  | ExpArray  Exp Exp
  | ExpStruct Exp Exp
  | ExpCall FnSym [Exp]
  | ExpAddrOf Var
  | LocExp (Located Exp)
  deriving (Show, Read, Eq, Ord)

data ExpOp
  = EqOp
  | NeqOp
  | CondOp

  | GtOp Bool
  -- ^ True is >=, False is >
  | LtOp Bool
  -- ^ True is <=, False is <

  | NotOp
  | AndOp
  | OrOp

  | MulOp
  | AddOp
  | SubOp
  | NegateOp
  | AbsOp
  | SignumOp

  | DivOp
  | EucDivOp
  | ModOp

  | FExpOp
  | FSqrtOp
  | FLogOp
  | FPowOp
  | FSinOp
  | FTanOp
  | FCosOp
  | FAsinOp
  | FAtanOp
  | FAtan2Op
  | FAcosOp
  | FSinhOp
  | FTanhOp
  | FCoshOp
  | FAsinhOp
  | FAtanhOp
  | FAcoshOp

  | IsNanOp
  | IsInfOp
  | RoundFOp
  | CeilFOp
  | FloorFOp

  | BitAndOp
  | BitOrOp
  | BitXorOp
  | BitComplementOp
  | BitShiftLOp
  | BitShiftROp

  | ConstRefOp

  | SafeCast
  | BitCast
  | CastWith
  | TwosCompCast
  | TwosCompRep

  | ToIx
  | FromIx
  | IxSize
  | ArrayLen
  | SizeOf
  | NullPtr
  | RefToPtr
  | ToCArray

  deriving (Show, Read, Eq, Ord)

data StructInit
  = Empty
  | MacroInit (String, [Exp])
  | FieldInits [(FieldNm, Exp)]
  deriving (Show, Read, Eq, Ord)

data AllocRef
  = AllocBase    RefVar (Maybe Exp)
  | AllocArr     RefVar [Exp]
  | AllocStruct  RefVar StructInit
  deriving (Show, Read, Eq, Ord)

allocRefVar :: AllocRef -> RefVar
allocRefVar a =
  case a of
    AllocBase   v _ -> v
    AllocArr    v _ -> v
    AllocStruct v _ -> v

-- | AST for parsing C-like statements.
data Stmt
  = IfTE Exp [Stmt] [Stmt]
  | Assert Exp
  | Assume Exp
  | Return Exp
  | ReturnVoid
  -- Deref dereferencing is an expression in our language here.
  | Store Exp Exp
  | Assign Var Exp (Maybe Type)
  | NoBindCall Var [Exp]
  | RefCopy Exp Exp
-- Local is AllocRef
  | AllocRef AllocRef
  | MapArr IxVar [Stmt]
  | UpTo Exp IxVar [Stmt]
  | UpFromTo Exp Exp IxVar [Stmt]
  | DownFrom Exp IxVar [Stmt]
  | DownFromTo Exp Exp IxVar [Stmt]
  | Forever [Stmt]
  | IvoryMacroStmt (Maybe Var) (String, [Exp])
  | Break
  | LocStmt (Located Stmt)
  deriving (Show, Read, Eq, Ord)

--------------------------------------------------------------------------------
-- Structs

data StructDef
  = StructDef   String [Field]  SrcLoc
  | AbstractDef String FilePath SrcLoc
  | StringDef   String Integer  SrcLoc
    deriving (Show, Read, Eq, Ord)

structSym :: StructDef -> String
structSym s = case s of
  StructDef   sym _ _ -> sym
  AbstractDef sym _ _ -> sym
  StringDef   sym _ _ -> ivoryStringStructName sym

ivoryStringStructName :: String -> String
ivoryStringStructName = ("ivory_string_" ++)

data Field = Field
  { fieldName :: FieldNm
  , fieldType :: Type
  , fieldLoc  :: SrcLoc
  } deriving (Show, Read, Eq, Ord)

--------------------------------------------------------------------------------
-- Bit-data

-- | A "bitdata" definition.
data BitDataDef = BitDataDef
  { defName    :: String
  , defType    :: BitTy
  , defConstrs :: [Constr]
  , bdLoc      :: SrcLoc
  } deriving (Show, Read, Eq, Ord)

-- | Basic type representation allowed in bit definitions.
data BitTy = Bit
           | Bits Integer
           | BitArray Integer BitTy
           | BitTySynonym String
           | LocBitTy (Located BitTy)
  deriving (Show, Read, Eq, Ord)

-- | A constructor definition within a "bitdata".
data Constr = Constr
  { constrName   :: String
  , constrFields :: [BitField]
  , constrLayout :: [LayoutItem]
  , constrLoc    :: SrcLoc
  } deriving (Show, Read, Eq, Ord)

-- | One element of a bit data constructor layout.
data LayoutItem = LayoutConst BitLiteral
                | LayoutField String
  deriving (Show, Read, Eq, Ord)

-- | A bit integer literal with a known or unknown size.
data BitLiteral =
    BitLitKnown   { bitLitLen :: Integer , bitLitVal :: Integer }
  | BitLitUnknown { bitLitVal :: Integer }
 deriving (Show, Read, Eq, Ord)

-- | A record-like field defined within a "bitdata" constructor.  If the name is
-- an underscore, we name it with 'Nothing'.
data BitField = BitField
  { bitFieldName :: Maybe String
  , bitFieldType :: BitTy
  , bitFieldLoc  :: SrcLoc
  } deriving (Show, Read, Eq, Ord)

--------------------------------------------------------------------------------
-- Instances

instance HasLocation GlobalSym where
  getLoc = mempty
  stripLoc g = case g of
    GlobalProc p       -> GlobalProc       (stripLoc p)
    GlobalInclProc p   -> GlobalInclProc   (stripLoc p)
    GlobalStruct s     -> GlobalStruct     (stripLoc s)
    GlobalBitData b    -> GlobalBitData    (stripLoc b)
    GlobalTypeDef t    -> GlobalTypeDef    (stripLoc t)
    GlobalConstDef c   -> GlobalConstDef   (stripLoc c)
    GlobalInclude i    -> GlobalInclude    (stripLoc i)
    GlobalExtern e     -> GlobalExtern     (stripLoc e)
    GlobalArea   a     -> GlobalArea       (stripLoc a)
    GlobalAreaImport a -> GlobalAreaImport (stripLoc a)

instance HasLocation IncludeDef where
  getLoc = inclDefLoc
  stripLoc incl = incl { inclDefLoc = mempty }

instance HasLocation IncludeProc where
  getLoc = procInclLoc
  stripLoc incl = incl { procInclLoc = mempty }

instance HasLocation Extern where
  getLoc = externLoc
  stripLoc e = e { externLoc = mempty }

instance HasLocation ConstDef where
  getLoc = constDefLoc
  stripLoc c = c { constDefLoc = mempty }

instance HasLocation TypeDef where
  getLoc = tyDefLoc
  stripLoc td = td { tyDefLoc = mempty }

instance HasLocation ProcDef where
  getLoc = procLoc
  stripLoc p = p { procLoc = mempty }

instance HasLocation PrePost where
  getLoc _ = mempty
  stripLoc pp = case pp of
    PreCond e  -> PreCond (stripLoc e)
    PostCond e -> PostCond (stripLoc e)

instance HasLocation AreaDef where
  getLoc _ = mempty
  stripLoc p = p { memAreaLoc = mempty }

instance HasLocation AreaImportDef where
    getLoc _ = mempty
    stripLoc p = p { aiLoc = mempty }

instance HasLocation Type where
  getLoc ty = case ty of
                LocTy t -> getLoc t
                _       -> mempty
  stripLoc ty = case ty of
    TyVoid{}         -> ty
    TyInt{}          -> ty
    TyWord{}         -> ty
    TyBool{}         -> ty
    TyChar{}         -> ty
    TyFloat{}        -> ty
    TyDouble{}       -> ty
    TyString{}       -> ty
    TyIx{}           -> ty
    TyStored ty0     -> TyStored (stripLoc ty0)
    TyStruct{}       -> ty
    TyArray ty0 i    -> TyArray (stripLoc ty0) i
    TyRef s ty0      -> TyRef s (stripLoc ty0)
    TyConstRef s ty0 -> TyConstRef s (stripLoc ty0)
    TySynonym{}      -> ty
    LocTy ty0        -> unLoc ty0

instance HasLocation Exp where
  getLoc e = case e of
               LocExp le -> getLoc le
               _         -> mempty

  stripLoc e = case e of
    ExpLit{}               -> e
    ExpVar{}               -> e
    ExpRet{}               -> e
    ExpOp op args          -> ExpOp op (stripLoc args)
    IvoryMacroExp (s,args) -> IvoryMacroExp (s, stripLoc args)
    ExpDeref e0            -> ExpDeref (stripLoc e0)
    ExpArray e0 e1         -> ExpArray (stripLoc e0) (stripLoc e1)
    ExpStruct e0 e1        -> ExpStruct (stripLoc e0) (stripLoc e1)
    ExpCall fn args        -> ExpCall fn (stripLoc args)
    ExpAddrOf{}            -> e
    LocExp le              -> unLoc le

instance HasLocation AllocRef where
  getLoc _ = mempty
  stripLoc a = case a of
    AllocBase v e      -> AllocBase   v (stripLoc e)
    AllocArr v es      -> AllocArr    v (stripLoc es)
    AllocStruct v init -> AllocStruct v (stripLoc init)

instance HasLocation StructInit where
  getLoc _ = mempty
  stripLoc init = case init of
    Empty             -> Empty
    MacroInit (fn,es) -> MacroInit (fn, map stripLoc es)
    FieldInits fs     -> FieldInits (map (\(n,e) -> (n, stripLoc e)) fs)

instance HasLocation Stmt where
  getLoc s = case s of
               LocStmt s0 -> getLoc s0
               _          -> mempty
  stripLoc s = case s of
    IfTE e s0 s1             -> IfTE (stripLoc e) (stripLoc s0) (stripLoc s1)
    Assert e                 -> Assert (stripLoc e)
    Assume e                 -> Assume (stripLoc e)
    Return e                 -> Return (stripLoc e)
    ReturnVoid               -> ReturnVoid
    Break                    -> Break
    Store e0 e1              -> Store (stripLoc e0) (stripLoc e1)
    Assign v e t             -> Assign v (stripLoc e) (stripLoc t)
    NoBindCall v es          -> NoBindCall v (stripLoc es)
    RefCopy e0 e1            -> RefCopy (stripLoc e0) (stripLoc e1)
    AllocRef ar              -> AllocRef (stripLoc ar)
    MapArr v ss              -> MapArr v (stripLoc ss)
    UpTo e v ss              -> UpTo (stripLoc e) v (stripLoc ss)
    UpFromTo e0 e1 v ss      -> UpFromTo (stripLoc e0) (stripLoc e1) v (stripLoc ss)
    DownFrom e v ss          -> DownFrom (stripLoc e) v (stripLoc ss)
    DownFromTo e0 e1 v ss    -> DownFromTo (stripLoc e0) (stripLoc e1) v (stripLoc ss)
    Forever ss               -> Forever (stripLoc ss)
    IvoryMacroStmt v (s0,es) -> IvoryMacroStmt v (s0, stripLoc es)
    LocStmt s0               -> unLoc s0

instance HasLocation StructDef where
  getLoc s = case s of
               StructDef _ _   srcloc -> srcloc
               AbstractDef _ _ srcloc -> srcloc
               StringDef _ _   srcloc -> srcloc
  stripLoc s = case s of
    StructDef s0 fs _   -> StructDef s0 (stripLoc fs) mempty
    AbstractDef s0 fp _ -> AbstractDef s0 fp mempty
    StringDef s0 i _    -> StringDef s0 i mempty

instance HasLocation Field where
  getLoc = fieldLoc
  stripLoc (Field n t _) = Field n (stripLoc t) mempty

instance HasLocation BitDataDef where
  getLoc = bdLoc
  stripLoc (BitDataDef s t cs _) = BitDataDef s (stripLoc t) (stripLoc cs) mempty

instance HasLocation BitTy where
  getLoc bt = case bt of
                LocBitTy bt' -> getLoc bt'
                _            -> mempty
  stripLoc bt = case bt of
    Bit            -> bt
    Bits{}         -> bt
    BitArray i bt0 -> BitArray i (stripLoc bt0)
    BitTySynonym{} -> bt
    LocBitTy bt0   -> unLoc bt0

instance HasLocation Constr where
  getLoc = constrLoc
  stripLoc (Constr n fs l _) = Constr n (stripLoc fs) l mempty

instance HasLocation BitField where
  getLoc = bitFieldLoc
  stripLoc (BitField n t _) = BitField n (stripLoc t) mempty
