-- | This module contains the definition of names of our intermediate language.
module FreeC.IR.Syntax.Name where

import           FreeC.IR.SrcSpan
import           FreeC.Pretty

-------------------------------------------------------------------------------
-- Unqualifiable Names                                                       --
-------------------------------------------------------------------------------
-- | An identifier or a symbolic name.
--
--   The constructors of this type do not contain source spans because
--   'Name's are intended to be comparable. They are used as keys to
--   identify nodes of the dependency graph for example.
data Name
  = Ident String  -- ^ An identifier, e.g. @Ident \"f\"@ for a function @f@.
  | Symbol String -- ^ A symbolic name, e.g. @Symbol \"+\"@ for @(+)@.
 deriving ( Eq, Ord, Show )

-- | Extracts an identifier from a name. Returns @Nothing@ if the
--   given name is a symbol and not an identifier.
identFromName :: Name -> Maybe String
identFromName (Ident ident) = Just ident
identFromName (Symbol _)    = Nothing

-- | Pretty instance for identifiers and symbols.
instance Pretty Name where
  pretty (Ident ident)   = prettyString ident
  pretty (Symbol symbol) = parens (prettyString symbol)

  prettyList             = prettySeparated (comma <> space) . map pretty

-------------------------------------------------------------------------------
-- Qualifiable Names                                                         --
-------------------------------------------------------------------------------
-- | A qualifiable 'Name'.
data QName
  = Qual ModName Name -- ^ A qualified 'Name'.
  | UnQual Name       -- ^ An unqualified 'Name'.
 deriving ( Eq, Ord, Show )

-- | Extracts the name of a qualifiable name.
nameFromQName :: QName -> Name
nameFromQName (UnQual name) = name
nameFromQName (Qual _ name) = name

-- | Extracts an identifier from a qualifiable name.
identFromQName :: QName -> Maybe String
identFromQName = identFromName . nameFromQName

-- | Converts a qualifiable name to a name that is qualified with
--   the given module name.
toQual :: ModName -> QName -> QName
toQual modName' = Qual modName' . nameFromQName

-- | Converts a qualifiable name to an unqualified name.
toUnQual :: QName -> QName
toUnQual = UnQual . nameFromQName

-- | Pretty instance for qualifiable identifiers and symbols.
instance Pretty QName where
  pretty (Qual modId name)
    | null modId = pretty name
    | otherwise = prettyString modId <> dot <> pretty name
  pretty (UnQual name)     = pretty name

  prettyList               = prettySeparated (comma <> space) . map pretty

-------------------------------------------------------------------------------
-- Name Spaces                                                               --
-------------------------------------------------------------------------------
-- | Data type for the different name spaces of the intermediate representation.
--
--   Similar to Haskell, type and function names live in separate name spaces.
--
--   Additionally, there is a name space for fresh identifiers without a
--   corresponding IR node. If a fresh identifier is introduced and used
--   as an IR variable or type variable name, the corresponding entry
--   lives in the value or type scope respectively. The 'FreshScope'
--   contains only IR identifiers that were generated such that their
--   renamed counterpart of the target language can be used as a fresh
--   identifier by the back end.
data Scope = TypeScope | ValueScope | FreshScope
 deriving ( Eq, Ord, Show )

-- | A 'QName' with additional information about its name space.
type ScopedName = (Scope, QName)

-------------------------------------------------------------------------------
-- Aliases for Name Types                                                    --
-------------------------------------------------------------------------------
-- | The name of a type variable.
type TypeVarIdent = String

-- | The name of a module.
type ModName = String

-- | The name of a function or built-in operator used in prefix notation, e.g.
--   @f x y@ or @(+) n m@
type VarName = QName

-- | The name of a constructor used in prefix notation, e.g. @(:) x xs@.
type ConName = QName

-- | The name of a type or type constructor, e.g. @Integer@ or @[] a@
type TypeConName = QName

-------------------------------------------------------------------------------
-- Names of Top-Level Declarations                                           --
-------------------------------------------------------------------------------
-- | The name of a top-level declaration including location information.
data DeclIdent
  = DeclIdent { declIdentSrcSpan :: SrcSpan, declIdentName :: QName }
 deriving ( Eq, Show )

-- | Pretty instance for names of declarations.
instance Pretty DeclIdent where
  pretty     = pretty . declIdentName

  prettyList = prettySeparated (comma <> space) . map pretty

-- | Type class for AST nodes with a declaration identifier.
class HasDeclIdent node where
  -- | Gets the name of the given AST node.
  declIdent :: node -> DeclIdent

-- | Gets the qualified name of the given AST node.
declQName :: HasDeclIdent node => node -> QName
declQName = declIdentName . declIdent

-- | Gets the unqualified name of the given AST node.
declName :: HasDeclIdent node => node -> Name
declName = nameFromQName . declQName

-------------------------------------------------------------------------------
-- Internal Identifiers                                                      --
-------------------------------------------------------------------------------
-- | The character that is used to mark internal identifiers.
--
--   This is used to generate fresh identifiers that don't conflict with user-
--   defined identifiers.
internalIdentChar :: Char
internalIdentChar = '@'

-- | Tests whether the given identifier was generated for internal use only
--   (i.e., contains 'internalIdentChar').
isInternalIdent :: String -> Bool
isInternalIdent = elem internalIdentChar

-- | Tests whether the given name was generated for internal use only (i.e.,
--   it is an identifier that matches 'isInternalIdent').
isInternalName :: Name -> Bool
isInternalName (Ident ident) = isInternalIdent ident
isInternalName (Symbol _)    = False

-- | Tests whether the given qualifiable name was generated for internal use
--   only (i.e., the qualified name is internal according to 'isInternalName').
isInternalQName :: QName -> Bool
isInternalQName (UnQual name) = isInternalName name
isInternalQName (Qual _ name) = isInternalName name