-- | Adding code to initialise the runtime system.
module DDC.Core.Salt.Convert.Init
        (initRuntime)
where
import DDC.Core.Salt.Compounds
import DDC.Core.Salt.Runtime
import DDC.Core.Salt.Name
import DDC.Core.Module
import DDC.Core.Exp
import DDC.Core.Compounds
import Data.List


-- | If this it the Main module, then insert a main function for the posix
--   entry point that initialises the runtime system and calls the real main function.
--
--   Returns Nothing if this is the Main module, 
--      but there is no main function.
initRuntime
        :: Config
        -> Module a Name
        -> Maybe (Module a Name)

initRuntime config mm@ModuleCore{}
 | isMainModule mm
 = case initRuntimeTopX config (moduleBody mm) of
        Nothing -> Nothing
        Just x' -> Just 
                $ mm    { moduleExportValues    = patchMainExports (moduleExportValues mm)
                        , moduleBody            = x'}

 | otherwise     
 = Just mm


-- | Type of the POSIX main function.
posixMainType :: Type Name
posixMainType
        = tFunPE tInt (tFunPE (tPtr rTop tString) tInt)


-- | Patch the list of export definitions to export our wrapper instead
--   of the original main function.
patchMainExports 
        ::  [(Name, ExportSource Name)] 
        ->  [(Name, ExportSource Name)]

patchMainExports xx
 = case xx of
        []      -> []
        (x : xs)
         |  (NameVar "main", ExportSourceLocal n _) <- x
         -> (NameVar "main", ExportSourceLocal n posixMainType) : xs

         |  otherwise
         -> x : patchMainExports xs

 
-- | Takes the top-level let-bindings of amodule
--      and add code to initialise the runtime system.
initRuntimeTopX :: Config -> Exp a Name -> Maybe (Exp a Name)
initRuntimeTopX config xx
        | XLet a (LRec bxs) x2  <- xx
        , Just (bMainOrig, xMainOrig)   <- find   (isMainBind . fst) bxs
        , bxs_cut                       <- filter (not . isMainBind . fst) bxs
        , BName _ tMainOrig             <- bMainOrig
        =  let  
                -- Rename the old main function to '_main'
                bMainOrig'      = BName (NameVar "_main") $ tMainOrig

                -- The new entry point of the program is called 'main'.
                bMainEntry      = BName (NameVar "main")  $ posixMainType
                
                xMainEntry      = makeMainEntryX config a

           in   Just $ XLet a 
                        (LRec $ bxs_cut 
                                ++ [ (bMainOrig', xMainOrig)
                                   , (bMainEntry, xMainEntry)])
                        x2

        -- This was supposed to be the main Module,
        -- but there was no 'main' function for the program entry point.
        | otherwise
        = Nothing


-- | Check whether this is the bind for the 'main' function.
isMainBind :: Bind Name -> Bool
isMainBind bb
  = case bb of
        (BName (NameVar "main") _)      -> True
        _                               -> False


-- | Make the posix main function,
--   which is the entry point to the executable.
makeMainEntryX :: Config -> a -> Exp a Name
makeMainEntryX config a
 = XLam a  (BName (NameVar "argc")         tInt)
 $ XLam a  (BName (NameVar "argv")         (tPtr rTop tString))
 $ XLet a  (LLet  (BNone tVoid)            (xCreate a (configHeapSize config)))
 $ XLet a  (LLet  (BNone (tPtr rTop tObj)) 
                  (xApps a (XVar a (UName (NameVar "_main"))) 
                           [xAllocBoxed a rTop 0 (xNat a 0)]))
           (xInt a 0)