{-# LANGUAGE DeriveDataTypeable #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Language.C.Syntax.Attributes
-- Copyright   :  (c) [1995..1999] Manuel M. T. Chakravarty
--                (c) 2008 Benedikt Huber (stripped radically)
-- License     :  BSD-style
-- Maintainer  :  benedikt.huber@gmail.com
-- Stability   :  experimental
-- Portability :  ghc
--
-- source position and unqiue name
-----------------------------------------------------------------------------
module Language.C.Data.Node (
   NodeInfo(..), undefNode, isUndefNode,
   mkNodeInfoOnlyPos,mkNodeInfoPosLen, mkNodeInfo,mkNodeInfo',
   internalNode, -- deprecated, use undefNode
   CNode(nodeInfo), fileOfNode,
   posOfNode, nameOfNode, getLastTokenPos, lengthOfNode,
   eqByName,
) where
import Language.C.Data.Position
import Language.C.Data.Name     (Name)
import Data.Generics

-- | Parsed entity attribute
data NodeInfo = OnlyPos  Position {-# UNPACK #-} !PosLength        -- only pos and last token (for internal stuff only)
              | NodeInfo Position {-# UNPACK #-} !PosLength !Name  -- pos, last token and unique name
           deriving (Data,Typeable)

instance Show NodeInfo where
    showsPrec d (OnlyPos p l) =
      (showString "(OnlyPos ") . (showsPrec d p) . (showString " ") . (showsPrec d l) . (showString ")")
    showsPrec d (NodeInfo p l n) =
      (showString "(NodeInfo ") . (showsPrec d p) . (showString " ") . (showsPrec d l) . (showString " ") . (showsPrec d n) . (showString ")")

-- name equality of attributes, used to define (name) equality of objects
instance Eq NodeInfo where
  (NodeInfo   _ _ id1) == (NodeInfo   _ _ id2) = id1 == id2
  _               == _               =
    error "Attributes: Attempt to compare `OnlyPos' attributes!"

-- attribute ordering
instance Ord NodeInfo where
  (NodeInfo   _ _ id1) <= (NodeInfo   _ _ id2) = id1 <= id2
  _               <= _               =
    error "Attributes: Attempt to compare `OnlyPos' attributes!"

instance Pos NodeInfo where
  posOf (OnlyPos pos _) = pos
  posOf (NodeInfo pos _ _) = pos

-- | get the number of characters an AST node spans
lengthOfNode :: NodeInfo -> Maybe Int
lengthOfNode ni = len
    where
    len = case ni of NodeInfo firstPos lastTok _ -> computeLength firstPos lastTok
                     OnlyPos firstPos lastTok -> computeLength firstPos lastTok
    computeLength pos (lastPos,len) | len < 0   = Nothing
                                    | otherwise = Just (posOffset lastPos + len - posOffset pos)

-- | get the position and length of the last token
getLastTokenPos :: NodeInfo -> PosLength
getLastTokenPos (NodeInfo _ lastTok _) = lastTok
getLastTokenPos (OnlyPos _ lastTok) = lastTok

-- | a class for convenient access to the attributes of an attributed object
class CNode a where
  nodeInfo :: a -> NodeInfo
instance CNode NodeInfo where
  nodeInfo = id
instance (CNode a, CNode b) => CNode (Either a b) where
  nodeInfo = either nodeInfo nodeInfo

nameOfNode :: NodeInfo -> Maybe Name
nameOfNode (OnlyPos _ _) = Nothing
nameOfNode (NodeInfo _ _ name) = Just name
posOfNode :: NodeInfo -> Position
posOfNode ni = case ni of (OnlyPos pos _) -> pos; (NodeInfo pos _ _) -> pos
fileOfNode :: (CNode a) => a -> Maybe FilePath
fileOfNode = fmap posFile . justIf isSourcePos . posOfNode . nodeInfo where
    justIf predicate x | predicate x = Just x
                       | otherwise   = Nothing

-- | equality by name
eqByName           :: CNode a => a -> a -> Bool
eqByName obj1 obj2  = (nodeInfo obj1) == (nodeInfo obj2)


-- attribute identifier creation
-- -----------------------------
{-# DEPRECATED internalNode "use undefNode instead" #-}
internalNode :: NodeInfo
internalNode = undefNode

-- | create a node with neither name nor positional information
undefNode :: NodeInfo
undefNode = OnlyPos nopos (nopos,-1)

-- | return True if the node carries neither name nor positional information
isUndefNode :: NodeInfo -> Bool
isUndefNode (OnlyPos p _) | isNoPos p = True
                          | otherwise = False
isUndefNode _ = False

-- |
-- | Given only a source position, create a new node attribute
mkNodeInfoOnlyPos :: Position -> NodeInfo
mkNodeInfoOnlyPos pos  = OnlyPos pos (nopos,-1)

-- | Given a source position and the position and length of the last token, create a new node attribute
mkNodeInfoPosLen :: Position -> PosLength -> NodeInfo
mkNodeInfoPosLen = OnlyPos

-- | Given a source position and a unique name, create a new attribute
-- identifier
mkNodeInfo :: Position -> Name -> NodeInfo
mkNodeInfo pos name  = NodeInfo pos (nopos,-1) name

-- | Given a source position, the position and length of the last token and a unique name, create a new attribute
-- identifier. Strict in
mkNodeInfo' :: Position -> PosLength -> Name -> NodeInfo
mkNodeInfo' pos lasttok name = NodeInfo pos lasttok name