--  Compiler Toolkit: general purpose attribute management
--
--  Author : Manuel M. T. Chakravarty
--  Created: 14 February 95
--
--  Version $Revision: 1.27 $ from $Date: 1999/12/03 12:24:38 $
--
--  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 provides an abstract notion of attributes (in the sense of
--  compiler construction). The collection of attributes that is attached to a
--  single node of the structure tree is referenced via an attributes
--  identifier. This is basically a reference into so-called attribute tables,
--  which manage attributes of one type and may use different representations.
--  There is also a position attribute managed via the attribute identifier 
--  without needing a further table (it is already fixed on construction of
--  the structure tree).
--
--  The `Attributed' class is based on a suggestion from Roman Lechtchinsky.
--
--- DOCU ----------------------------------------------------------------------
--
--  language: Haskell 98
--
--  * Attribute identifiers are generated during parsing and whenever new
--    structure tree elements, possibly due to transformations, are generated.
--
--  * New attributes can be added by simply providing a new attribute table
--    indexed by the attribute identifiers. Thus, adding or discarding an
--    attribute does not involve any change in the structure tree.
--
--  * Consecutive sequences of names are used as attribute identifiers to
--    facilitate the use of arrays for attributes that are fixed; speeds up
--    read access. (See also TODO.)
--
--  * Each attribute table can simultaneously provide melted (updatable) and
--    frozen (non-updatable) attributes. It also allows to dynamically grow the
--    table, i.e., cover a wider range of attribute identifiers.
--
--  * There is a variant merely providing a position, which is used for
--    internal identifiers and such.
--
--  * `StdAttr' provides standard undefined and don't care variants for
--    attribute values.
--
--- TODO ----------------------------------------------------------------------
--
--  * When there are sparse attribute tables that we want to freeze (and they
--    will occur sooner or later), then introduce a third variant of tables
--    realized via hash table---depending on the type of attribute table, we
--    may even allow them to be soft.
--
--    NOTE: Currently, if assertions are switched on, on freezing a table, its 
--	    density is calculate and, if it is below 33%, an internal error is
--	    raised (only if there are more than 1000 entries in the table).
--
--  * check whether it would increase the performance significantly if we use
--    a mixed finite map/array representation for soft tables (all attributes
--    defined before the last `soften' could be held in the array, changing
--    an attribute just means to update it in the FM; i.e., the FM entries take
--    precedence over the array entries)
--

module Attributes (-- attribute management
		   --
		   Attrs, newAttrsOnlyPos, newAttrs,
		   Attributed(attrsOf), eqOfAttrsOf, posOfAttrsOf,
		   --
		   -- attributes and attribute tables
		   --
		   Attr(undef, isUndef, dontCare, isDontCare),
		   AttrTable, newAttrTable, getAttr, setAttr, updAttr,
		   copyAttr, freezeAttrTable, softenAttrTable,
		   StdAttr(..), getStdAttr, getStdAttrDft, isDontCareStdAttr,
		   isUndefStdAttr, setStdAttr, updStdAttr,
		   getGenAttr, setGenAttr, updGenAttr) 
where

import Array
import Common     (Position, Pos(posOf), nopos, isNopos, dontCarePos, 
		   isDontCarePos, assert)
import Errors     (interr)
import UNames	  (NameSupply, Name,
		   rootSupply, splitSupply, names)
import FiniteMaps (FiniteMap, listToFM, toListFM, addToFM, lookupDftFM, zeroFM)


-- attribute management data structures and operations
-- ---------------------------------------------------

-- abstract data structure used in the structure tree to represent the
-- attribute identifier and the position (EXPORTED)
--
data Attrs = OnlyPos Position		-- only pos (for internal stuff only)
	   | Attrs   Position Name	-- pos and unique name

-- get the position associated with an attribute identifier (EXPORTED)
--
instance Pos Attrs where
  posOf (OnlyPos pos  ) = pos
  posOf (Attrs   pos _) = pos

-- equality of attributes is used to define the equality of objects (EXPORTED)
--
instance Eq Attrs where
  (Attrs   _ id1) == (Attrs   _ id2) = id1 == id2
  _		  == _               = 
    interr "Attributes: Attempt to compare `OnlyPos' attributes!"

-- attribute ordering is also lifted to objects (EXPORTED)
--
instance Ord Attrs where
  (Attrs   _ id1) <= (Attrs   _ id2) = id1 <= id2
  _		  <= _               = 
    interr "Attributes: Attempt to compare `OnlyPos' attributes!"

-- a class for convenient access to the attributes of an attributed object
-- (EXPORTED)
--
class Attributed a where
  attrsOf :: a -> Attrs

-- equality induced by attribution (EXPORTED)
--
eqOfAttrsOf           :: Attributed a => a -> a -> Bool
eqOfAttrsOf obj1 obj2  = (attrsOf obj1) == (attrsOf obj2)

-- position induced by attribution (EXPORTED)
--
posOfAttrsOf :: Attributed a => a -> Position
posOfAttrsOf  = posOf . attrsOf


-- attribute identifier creation
-- -----------------------------

-- Given only a source position, create a new attribute identifier (EXPORTED)
--
newAttrsOnlyPos     :: Position -> Attrs
newAttrsOnlyPos pos  = OnlyPos pos

-- Given a source position and a unique name, create a new attribute
-- identifier (EXPORTED)
--
newAttrs          :: Position -> Name -> Attrs
newAttrs pos name  = Attrs pos name


-- attribute tables and operations on them
-- ---------------------------------------

-- the type class `Attr' determines which types may be used as attributes
-- (EXPORTED)
-- 
-- * such types have to provide values representing an undefined and a don't 
--   care state, together with two functions to test for these values
--
-- * an attribute in an attribute table is initially set to `undef' (before
--   some value is assigned to it)
--
-- * an attribute with value `dontCare' participated in an already detected
--   error, it's value may not be used for further computations in order to
--   avoid error avalanches
--
class Attr a where
  undef      :: a
  isUndef    :: a -> Bool
  dontCare   :: a
  isDontCare :: a -> Bool
  undef       = interr "Attributes: Undefined `undef' method in `Attr' class!"
  isUndef     = interr "Attributes: Undefined `isUndef' method in `Attr' \
		       \class!"
  dontCare    = interr "Attributes: Undefined `dontCare' method in `Attr' \
		       \class!"
  isDontCare  = interr "Attributes: Undefined `isDontCare' method in `Attr' \
		       \class!"

-- attribute tables map attribute identifiers to attribute values
-- (EXPORTED ABSTRACT)
--
-- * the attributes within a table can be soft or frozen, the former may by be
--   updated, but the latter can not be changed
--
-- * the attributes in a frozen table are stored in an array for fast
--   lookup; consequently, the attribute identifiers must be *dense*
--
-- * the table description string is used to emit better error messages (for
--   internal errors)
--
data Attr a => 
     AttrTable a = -- for all attribute identifiers not contained in the 
		   -- finite map the value is `undef'
		   --
		   SoftTable (FiniteMap Name a)   -- updated attr.s
			     String		  -- desc of the table

		   -- the array contains `undef' attributes for the undefined
		   -- attributes; for all attribute identifiers outside the
		   -- bounds, the value is also `undef'; 
		   --
		 | FrozenTable (Array Name a)     -- attribute values
			       String		  -- desc of the table

		   

-- create an attribute table, where all attributes are `undef' (EXPORTED) 
--
-- the description string is used to identify the table in error messages
-- (internal errors); a	table is initially soft
--
newAttrTable      :: Attr a => String -> AttrTable a
newAttrTable desc  = SoftTable zeroFM desc

-- get the value of an attribute from the given attribute table (EXPORTED)
--
getAttr                      :: Attr a => AttrTable a -> Attrs -> a
getAttr at (OnlyPos pos    )  = onlyPosErr "getAttr" at pos
getAttr at (Attrs   _   aid)  = 
  case at of
    (SoftTable   fm  _) -> lookupDftFM fm undef aid
    (FrozenTable arr _) -> let (lbd, ubd) = bounds arr
			   in
			   if (aid < lbd || aid > ubd) then undef else arr!aid

-- set the value of an, up to now, undefined attribute from the given
-- attribute table (EXPORTED)
--
setAttr :: Attr a => AttrTable a -> Attrs -> a -> AttrTable a
setAttr at (OnlyPos pos    ) av = onlyPosErr "setAttr" at pos
setAttr at (Attrs   pos aid) av = 
  case at of
    (SoftTable fm desc) -> assert (isUndef (lookupDftFM fm undef aid)) 
				  alreadySetErr $  
			     SoftTable (addToFM aid av fm) desc
    (FrozenTable arr _) -> interr frozenErr 
  where
    alreadySetErr = "Attributes.setAttr: Attempt to set *already* set \
		    \attribute in\n" ++ errLoc at pos
    frozenErr     = "Attributes.setAttr: Tried to write frozen attribute in\n"
		    ++ errLoc at pos

-- update the value of an attribute from the given attribute table (EXPORTED)
--
updAttr :: Attr a => AttrTable a -> Attrs -> a -> AttrTable a
updAttr at (OnlyPos pos    ) av = onlyPosErr "updAttr" at pos
updAttr at (Attrs   pos aid) av = 
  case at of
    (SoftTable   fm  desc) -> SoftTable (addToFM aid av fm) desc
    (FrozenTable arr _)    -> interr $ "Attributes.updAttr: Tried to\
				       \ update frozen attribute in\n"
				       ++ errLoc at pos

-- copy the value of an attribute to another one (EXPORTED)
--
-- * undefined attributes are not copied, to avoid filling the table
--
copyAttr :: Attr a => AttrTable a -> Attrs -> Attrs -> AttrTable a
copyAttr at ats ats' 
  | isUndef av = assert (isUndef (getAttr at ats')) alreadySetErr $
		   at
  | otherwise  = updAttr at ats' av
  where
    av = getAttr at ats
    --
    alreadySetErr = "Attributes.copyAttr: Attempt to set *already* set \
		    \attribute in\n" ++ errLoc at (posOf ats')

-- auxiliary functions for error messages
--
onlyPosErr		  :: Attr a => String -> AttrTable a -> Position -> b
onlyPosErr fctName at pos  = 
  interr $ "Attributes." ++ fctName ++ ": No attribute identifier in\n"
	   ++ errLoc at pos
--
errLoc        :: Attr a => AttrTable a -> Position -> String
errLoc at pos  = "  table `" ++ tableDesc at ++ "' for construct at\n\
		 \  position " ++ show pos ++ "!"
  where
    tableDesc (SoftTable   _ desc) = desc
    tableDesc (FrozenTable _ desc) = desc

-- freeze a soft table; afterwards no more changes are possible until the
-- table is softened again (EXPORTED)
--
freezeAttrTable			       :: Attr a => AttrTable a -> AttrTable a
freezeAttrTable (SoftTable   fm  desc)  = 
  let contents = toListFM fm
      keys     = map fst contents
      lbd      = minimum keys
      ubd      = maximum keys
  in
  assert (length keys < 1000 || (length . range) (lbd, ubd) > 3 * length keys)
	 ("Attributes.freezeAttrTable: Attempt to freeze a table with less\n\
	  \than 33% density (`" ++ desc ++ "'!")
  (FrozenTable (array (lbd, ubd) contents) desc)
freezeAttrTable (FrozenTable arr desc)  = 
  interr ("Attributes.freezeAttrTable: Attempt to freeze the already frozen\n\
	  \  table `" ++ desc ++ "'!")

-- soften a frozen table; afterwards changes are possible until the
-- table is frozen again (EXPORTED)
--
softenAttrTable			       :: Attr a => AttrTable a -> AttrTable a
softenAttrTable (SoftTable   fm  desc)  = 
  interr ("Attributes.softenAttrTable: Attempt to soften the already \
	  \softened\n  table `" ++ desc ++ "'!")
softenAttrTable (FrozenTable arr desc)  = 
  SoftTable (listToFM . assocs $ arr) desc


-- standard attributes
-- -------------------

-- standard attribute variants (EXPORTED)
--
data StdAttr a = UndefStdAttr
	       | DontCareStdAttr
	       | JustStdAttr a

instance Attr (StdAttr a) where
  undef = UndefStdAttr

  isUndef UndefStdAttr = True
  isUndef _	       = False

  dontCare = DontCareStdAttr

  isDontCare DontCareStdAttr = True
  isDontCare _		     = False

-- get an attribute value from a standard attribute table (EXPORTED)
--
-- * if the attribute can be "don't care", this should be checked before
--   calling this function (using `isDontCareStdAttr')
--
getStdAttr         :: AttrTable (StdAttr a) -> Attrs -> a
getStdAttr atab at  = getStdAttrDft atab at err
  where
    err = interr $ "Attributes.getStdAttr: Don't care in\n" 
		   ++ errLoc atab (posOf at)

-- get an attribute value from a standard attribute table, where a default is
-- substituted if the table is don't care (EXPORTED)
--
getStdAttrDft             :: AttrTable (StdAttr a) -> Attrs -> a -> a
getStdAttrDft atab at dft  = 
  case getAttr atab at of
    DontCareStdAttr -> dft
    JustStdAttr av  -> av
    UndefStdAttr    -> interr $ "Attributes.getStdAttrDft: Undefined in\n" 
				++ errLoc atab (posOf at)

-- check if the attribue value is marked as "don't care" (EXPORTED)
--
isDontCareStdAttr         :: AttrTable (StdAttr a) -> Attrs -> Bool
isDontCareStdAttr atab at  = isDontCare (getAttr atab at)

-- check if the attribue value is still undefined (EXPORTED)
--
-- * we also regard "don't care" attributes as undefined
--
isUndefStdAttr         :: AttrTable (StdAttr a) -> Attrs -> Bool
isUndefStdAttr atab at  = isUndef (getAttr atab at)

-- set an attribute value in a standard attribute table (EXPORTED)
--
setStdAttr :: AttrTable (StdAttr a) -> Attrs -> a -> AttrTable (StdAttr a)
setStdAttr atab at av = setAttr atab at (JustStdAttr av)

-- update an attribute value in a standard attribute table (EXPORTED)
--
updStdAttr :: AttrTable (StdAttr a) -> Attrs -> a -> AttrTable (StdAttr a)
updStdAttr atab at av = updAttr atab at (JustStdAttr av)


-- generic attribute table access (EXPORTED)
-- ------------------------------

getGenAttr         :: (Attr a, Attributed obj) => AttrTable a -> obj -> a
getGenAttr atab at  = getAttr atab (attrsOf at)

setGenAttr            :: (Attr a, Attributed obj) 
	              => AttrTable a -> obj -> a -> AttrTable a
setGenAttr atab at av  = setAttr atab (attrsOf at) av

updGenAttr            :: (Attr a, Attributed obj) 
		      => AttrTable a -> obj -> a -> AttrTable a
updGenAttr atab at av  = updAttr atab (attrsOf at) av
