--  Compiler Toolkit: name space management
--
--  Author : Manuel M. T. Chakravarty
--  Created: 12 November 95
--
--  Version $Revision: 1.2 $ from $Date: 2004/11/13 17:26:50 $
--
--  Copyright (c) [1995..1999] Manuel M. T. Chakravarty
--
--  This file is free software; you can redistribute it and/or modify
--  it under the terms of the GNU General Public License as published by
--  the Free Software Foundation; either version 2 of the License, or
--  (at your option) any later version.
--
--  This file is distributed in the hope that it will be useful,
--  but WITHOUT ANY WARRANTY; without even the implied warranty of
--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--  GNU General Public License for more details.
--
--- DESCRIPTION ---------------------------------------------------------------
--
--  This module manages name spaces.
--
--- DOCU ----------------------------------------------------------------------
--
--  language: Haskell 98
--
--  * A name space associates identifiers with their definition.
--
--  * Each name space is organized in a hierarchical way using the notion of
--    ranges. A name space, at any moment, always has a global range and may
--    have several local ranges. Definitions in inner ranges hide definitions
--    of the same identifiert in outer ranges.
--
--- TODO ----------------------------------------------------------------------
--
--  * evaluate the performance gain that a hashtable would bring
--

module NameSpaces (NameSpace, nameSpace, defGlobal, enterNewRange, leaveRange,
                   defLocal, find, nameSpaceToList)
where

import Map        (Map)
import qualified  Map as Map (empty, insert, lookup, toList)
import Idents     (Ident)
import Errors     (interr)
import Binary     (Binary(..))


-- name space (EXPORTED ABSTRACT)
--
--  * the definitions in the global ranges are stored in a finite map, because
--   they tend to be a lot and are normally not updated after the global range
--   is constructed
--
--  * the definitions of the local ranges are stored in a single list, usually
--   they are not very many and the definitions entered last are the most
--   frequently accessed ones; the list structure naturally hides older
--   definitions, i.e., definitions from outer ranges; adding new definitions
--   is done in time proportional to the current size of the range; removing a
--   range is done in constant time (and the definitions of a range can be
--   returned as a result of leaving the range); lookup is proportional to the
--   number of definitions in the local ranges and the logarithm of the number
--   of definitions in the global range---i.e., efficiency relies on a
--   relatively low number of local definitions together with frequent lookup
--   of the most recently defined local identifiers
--
data NameSpace a = NameSpace (Map Ident a)  -- defs in global range
                             [[(Ident, a)]]       -- stack of local ranges

-- create a name space (EXPORTED)
--
nameSpace :: NameSpace a
nameSpace  = NameSpace Map.empty []

-- add global definition (EXPORTED)
--
--  * returns the modified name space
--
--  * if the identifier is already declared, the resulting name space contains
--   the new binding and the second component of the result contains the
--   definition declared previously (which is henceforth not contained in the
--   name space anymore)
--
defGlobal :: NameSpace a -> Ident -> a -> (NameSpace a, Maybe a)
defGlobal (NameSpace gs lss) id def  = (NameSpace (Map.insert id def gs) lss,
                                        Map.lookup id gs)

-- add new range (EXPORTED)
--
enterNewRange                    :: NameSpace a -> NameSpace a
enterNewRange (NameSpace gs lss)  = NameSpace gs ([]:lss)

-- pop topmost range and return its definitions (EXPORTED)
--
leaveRange :: NameSpace a -> (NameSpace a, [(Ident, a)])
leaveRange (NameSpace gs [])        = interr "NameSpaces.leaveRange: \
                                             \No local range!"
leaveRange (NameSpace gs (ls:lss))  = (NameSpace gs lss, ls)

-- add local definition (EXPORTED)
--
--  * returns the modified name space
--
--  * if there is no local range, the definition is entered globally
--
--  * if the identifier is already declared, the resulting name space contains
--   the new binding and the second component of the result contains the
--   definition declared previously (which is henceforth not contained in the
--   name space anymore)
--
defLocal :: NameSpace a -> Ident -> a -> (NameSpace a, Maybe a)
defLocal ns@(NameSpace gs []      ) id def = defGlobal ns id def
defLocal (NameSpace    gs (ls:lss)) id def =
  (NameSpace gs (((id, def):ls):lss),
   lookup ls)
  where
    lookup []                          = Nothing
    lookup ((id', def):ls) | id == id' = Just def
                           | otherwise = lookup ls

-- search for a definition (EXPORTED)
--
--  * the definition from the innermost range is returned, if any
--
find                       :: NameSpace a -> Ident -> Maybe a
find (NameSpace gs lss) id  = case (lookup lss) of
                                Nothing  -> Map.lookup id gs
                                Just def -> Just def
                              where
                                lookup []       = Nothing
                                lookup (ls:lss) = case (lookup' ls) of
                                                    Nothing  -> lookup lss
                                                    Just def -> Just def

                                lookup' []              = Nothing
                                lookup' ((id', def):ls)
                                        | id' == id     = Just def
                                        | otherwise     = lookup' ls

-- dump a name space into a list (EXPORTED)
--
--  * local ranges are concatenated
--
nameSpaceToList                    :: NameSpace a -> [(Ident, a)]
nameSpaceToList (NameSpace gs lss)  = Map.toList gs ++ concat lss


{-! for NameSpace derive : GhcBinary !-}
{-* Generated by DrIFT : Look, but Don't Touch. *-}
instance (Binary a) => Binary (NameSpace a) where
    put_ bh (NameSpace aa ab) = do
            put_ bh aa
            put_ bh ab
    get bh = do
    aa <- get bh
    ab <- get bh
    return (NameSpace aa ab)
