{- |
We add implicit imports are for public nested modules.  This allows
using definitions from nested modules without having to explicitly import
them, for example:

module A where

  submodule B where
    x = 0x20

  y = x     // This works because of the implicit import of `B`

Restriction:
============

We only add impicit imports of modules that are syntactically visiable
in the source code.  Consider the following example:

module A where
  submodule M = F {X}   -- F,X are external modules (e.g., top-level)

We will add an implicit import for `M`, but *NO* implicit imports for
any modules imported vial `M` as those are not sytnactically visible
in the source (i.e., we have to know what `F` refers to).

This restriction allows us to add implicit imports before doing the
`Imports` pass.
-}

module Cryptol.ModuleSystem.Renamer.ImplicitImports
  ( addImplicitNestedImports
  ) where

import Data.List(partition)

import Cryptol.Parser.Position(Range)
import Cryptol.Utils.Ident(packModName)
import Cryptol.Parser.AST

{- | Add additional imports for modules nested withing this one -}
addImplicitNestedImports :: [TopDecl PName] -> [TopDecl PName]
addImplicitNestedImports = snd . addImplicitNestedImports'

{- | Returns:

  * declarations with additional imports and
  * the public module names of this module and its children.
-}
addImplicitNestedImports' ::
  [TopDecl PName] -> ([[Ident]], [TopDecl PName])
addImplicitNestedImports' decls =
  (concat exportedMods, concat newDecls ++ other)
  where
  (mods,other)            = partition isNestedMod decls
  (newDecls,exportedMods) = unzip (map processModule mods)


processModule :: TopDecl PName -> ([TopDecl PName], [[Ident]])
processModule ~dcl@(DModule m) =
  let NestedModule m1 = tlValue m
  in
  case mDef m1 of
    NormalModule ds ->
      let (childExs, ds1) = addImplicitNestedImports' ds
          mname           = getIdent (thing (mName m1))
          imps            = map (mname :) ([] : childExs) -- this & nested
          loc             = srcRange (mName m1)
      in ( DModule m { tlValue = NestedModule m1 { mDef = NormalModule ds1 } }
         : map (mkImp loc) imps
         , case tlExport m of
             Public  -> imps
             Private -> []
         )

    FunctorInstance {} -> ([dcl], [])
    InterfaceModule {} -> ([dcl], [])




isNestedMod :: TopDecl name -> Bool
isNestedMod d =
  case d of
    DModule tl -> case tlValue tl of
                    NestedModule m -> not (mIsFunctor m)
    _          -> False

-- | Make a name qualifier out of a list of identifiers.
isToQual :: [Ident] -> ModName
isToQual is = packModName (map identText is)

-- | Make a module name out of a list of identifier.
-- This is the name of the module we are implicitly importing.
isToName :: [Ident] -> PName
isToName is = case is of
                [i] -> mkUnqual i
                _   -> mkQual (isToQual (init is)) (last is)

-- | Make an implicit import declaration.
mkImp :: Range -> [Ident] -> TopDecl PName
mkImp loc xs =
  DImport
    Located
      { srcRange = loc
      , thing    = Import
                     { iModule = ImpNested (isToName xs)
                     , iAs     = Just (isToQual xs)
                     , iSpec   = Nothing
                     , iInst   = Nothing
                     }
      }



