{-# LANGUAGE FlexibleInstances #-}
module Language.C.DSL.Decl where
import Language.C
import Data.String
import Language.C.DSL.StringLike

-- | A low level way to declare something.
decl :: CDeclSpec       -- ^ The declaration specifier, usually this is a type
        -> CDeclr      -- ^ Equivalent to the name of the object being declared. Often this will
                       -- make use of the overloaded string instance for 'CDeclr's
        -> Maybe CExpr -- ^ The optional init expression
        -> CDecl
decl ty name exp = CDecl [ty] [(Just name, flip CInitExpr undefNode `fmap` exp, Nothing)] undefNode

-- | The 'CTypeSpec' for @void@
voidSpec  :: CTypeSpec
voidSpec = CVoidType undefNode

-- | The 'CTypeSpec' for @char@
charSpec  :: CTypeSpec
charSpec = CCharType undefNode

-- | The 'CTypeSpec' for @short@
shortSpec :: CTypeSpec
shortSpec  = CShortType undefNode

-- | The 'CTypeSpec' for @int@
intSpec :: CTypeSpec
intSpec = CIntType undefNode

-- | The 'CTypeSpec' for @long@
longSpec :: CTypeSpec
longSpec = CLongType undefNode

-- | The 'CTypeSpec' for @float@
floatSpec :: CTypeSpec
floatSpec  = CFloatType undefNode

-- | The 'CTypeSpec' for @double@
doubleSpec :: CTypeSpec
doubleSpec = CDoubleType undefNode


-- | The 'CDeclSpec' for declarations of type @void@
voidTy  :: CDeclSpec
voidTy = CTypeSpec $ CVoidType undefNode

-- | The 'CDeclSpec' for declarations of type @char@
charTy  :: CDeclSpec
charTy = CTypeSpec $ CCharType undefNode

-- | The 'CDeclSpec' for declarations of type @short@
shortTy :: CDeclSpec
shortTy  = CTypeSpec $ CShortType undefNode

-- | The 'CDeclSpec' for declarations of type @int@
intTy :: CDeclSpec
intTy = CTypeSpec $ CIntType undefNode

-- | The 'CDeclSpec' for declarations of type @long@
longTy :: CDeclSpec
longTy = CTypeSpec $ CLongType undefNode

-- | The 'CDeclSpec' for declarations of type @float@
floatTy :: CDeclSpec
floatTy  = CTypeSpec $ CFloatType undefNode

-- | The 'CDeclSpec' for declarations of type @double@
doubleTy :: CDeclSpec
doubleTy = CTypeSpec $ CDoubleType undefNode


-- | Turns a string into the corresponding typedefed type.
-- 
-- For example
-- 
-- > struct "foo" [("bar, ty "quux")]
-- 
-- will generate the corresponding
-- 
-- > typedef foo {quux bar;} foo
ty :: Ident -> CTypeSpec
ty = flip CTypeDef undefNode

-- | Modifies a declarator to be a pointer. For example
-- @ptr someName@ would be @*x@ in C.
ptr :: CDeclr -> CDeclr
ptr (CDeclr nm mods cstr attrs node) = CDeclr nm (CPtrDeclr [] undefNode : mods) cstr attrs node

-- | A short cut for declaring a @char@.
-- 
-- >     char "x" .= 1
-- >     uninit $ char "y"
--
-- Would generate
--
-- > char x = 1;
-- > char y;
char :: CDeclr -> Maybe CExpr -> CDecl
char   = decl charTy

-- | A short cut for declaring a @short@
short :: CDeclr -> Maybe CExpr -> CDecl
short  = decl shortTy

-- | A short cut for declaring a @int@
int :: CDeclr -> Maybe CExpr -> CDecl
int    = decl intTy

-- | A short cut for declaring a @long@
long :: CDeclr -> Maybe CExpr -> CDecl
long   = decl longTy

-- | A short cut for declaring a @float@
float :: CDeclr -> Maybe CExpr -> CDecl
float  = decl floatTy

-- | A short cut for declaring a @double@
double :: CDeclr -> Maybe CExpr -> CDecl
double = decl doubleTy

-- | Equivalent to @'char'@ but wraps the @'CDeclr'@ in a pointer.
-- This means that @uninit $ charPtr someName@ is equivalent to @char *someName;@
charPtr  :: CDeclr -> Maybe CExpr -> CDecl
charPtr   = char . ptr

shortPtr :: CDeclr -> Maybe CExpr -> CDecl
shortPtr  = short . ptr

intPtr   :: CDeclr -> Maybe CExpr -> CDecl
intPtr    = int . ptr

longPtr  :: CDeclr -> Maybe CExpr -> CDecl
longPtr   = long . ptr

floatPtr :: CDeclr -> Maybe CExpr -> CDecl
floatPtr  = float . ptr

doublePtr:: CDeclr -> Maybe CExpr -> CDecl
doublePtr = double . ptr


-- | Supplies an initializer for an for a declaration. This
-- is meant to be used with the 'char' and friends short cuts
(.=) :: (Maybe CExpr -> CDecl) -> CExpr -> CDecl
f .= e = f (Just e)
infixl 7 .=

-- | Leave a declaration uninitialized. This is meant to be used
-- with the 'char' and friends declaration
uninit :: (Maybe CExpr -> CDecl) -> CDecl
uninit = ($ Nothing)

csu :: CStructTag -> String -> [(String, CTypeSpec)] -> CDecl
csu tag ident fields = CDecl
                       [CStorageSpec $ CTypedef undefNode, CTypeSpec $ CSUType structTy undefNode]
                       [(Just $ fromString ident, Nothing, Nothing)]
                       undefNode
  where structTy  = CStruct tag (Just $ fromString ident) (Just $ map structify fields) [] undefNode
        structify (name, ty) = CDecl [CTypeSpec ty] [(Just (fromString name), Nothing, Nothing)] undefNode

-- | Create a structure, for example @struct "foo" [("bar", intTy)]@ is
-- @typedef struct foo {int bar;} foo;@
struct ::  String -> [(String, CTypeSpec)] -> CDecl
struct = csu CStructTag

-- | Equivalent to 'struct' but generates a C union instead.
union :: String -> [(String, CTypeSpec)] -> CDecl
union  = csu CUnionTag

-- | Defines a C function. For example
-- 
-- >    test =
-- >       fun [intTy] "test"[int "a", int "b"] $ hblock [
-- >           creturn ("a" + "b")
-- >       ]
-- 
-- Would be the equivalent of
-- 
-- >   int test(int a, int b)
-- >   {
-- >      return a + b;
-- >   }
fun :: [CDeclSpec] -> String -> [Maybe CExpr -> CDecl] -> CStat -> CFunDef
fun specs name args body = annotatedFun specs name args [] body

-- | Identical to fun except this annotates the list of attributes given
-- as a list of strings.
annotatedFun :: [CDeclSpec] -> String -> [Maybe CExpr -> CDecl] -> [String] -> CStat -> CFunDef
annotatedFun specs name args annots body = CFunDef specs decl [] body undefNode
  where decl = CDeclr (Just $ fromString name)
               [CFunDeclr (Right (fmap ($Nothing) args, False)) [] undefNode]
               Nothing attrs undefNode
        attrs :: [CAttr]
        attrs = map (\ s -> CAttr (fromString s) [] undefNode) annots

class External a where
  export :: a -> CExtDecl
instance External CFunDef where
  export = CFDefExt
instance External CDecl where
  export = CDeclExt
instance External CStrLit where
  export = flip CAsmExt undefNode

-- | Exports a series of declarations to a translation unit.
transUnit :: [CExtDecl] -> CTranslUnit
transUnit = flip CTranslUnit undefNode