packages feed

nirum-0.1.0: src/Nirum/Constructs/DeclarationSet.hs

{-# LANGUAGE OverloadedLists, TypeFamilies #-}
module Nirum.Constructs.DeclarationSet ( DeclarationSet()
                                       , NameDuplication( BehindNameDuplication
                                                        , FacialNameDuplication
                                                        )
                                       , empty
                                       , fromList
                                       , lookup
                                       , lookup'
                                       , null
                                       , null'
                                       , size
                                       , toList
                                       , union
                                       , (!)
                                       ) where

import Data.Maybe (fromJust)
import qualified GHC.Exts as L
import Prelude hiding (lookup, null)

import qualified Data.Map.Strict as M
import qualified Data.Set as S

import Nirum.Constructs.Declaration (Declaration(name))
import Nirum.Constructs.Identifier (Identifier)
import Nirum.Constructs.Name (Name(Name, behindName, facialName))

data Declaration a => DeclarationSet a
    -- | The set of 'Declaration' values.
    -- Every 'name' has to be unique in the set.
    -- The order of declarations is maintained.
    = DeclarationSet { declarations :: M.Map Identifier a
                     , index :: ![Identifier]
                     }
    deriving (Eq, Ord, Show)

data NameDuplication = FacialNameDuplication !Name
                     | BehindNameDuplication !Name
                     deriving (Eq, Show)

empty :: Declaration a => DeclarationSet a
empty = DeclarationSet { declarations = M.empty
                       , index = []
                       }

fromList :: Declaration a => [a] -> Either NameDuplication (DeclarationSet a)
fromList declarations' =
    case findDup names facialName S.empty of
        Just dup -> Left $ FacialNameDuplication dup
        _ -> case findDup names behindName S.empty of
            Just dup -> Left $ BehindNameDuplication dup
            _ -> Right DeclarationSet { declarations = M.fromList mapList
                                      , index = index'
                                      }
  where
    names :: [Name]
    names = map name declarations'
    index' :: [Identifier]
    index' = map facialName names
    mapList = [(facialName (name d), d) | d <- declarations']
    findDup :: [Name] -> (Name -> Identifier) -> S.Set Identifier -> Maybe Name
    findDup names' f dups =
        case names' of
            x:xs -> let name' = f x
                    in if name' `S.member` dups
                       then Just x
                       else findDup xs f $ S.insert name' dups
            _ -> Nothing

toList :: Declaration a => DeclarationSet a -> [a]
toList (DeclarationSet declarations' index') =
    map ((M.!) declarations') index'

size :: Declaration a => DeclarationSet a -> Int
size (DeclarationSet declarations' _) = M.size declarations'

null :: Declaration a => DeclarationSet a -> Bool
null = null'

null' :: Declaration a => DeclarationSet a -> Bool
null' (DeclarationSet declarations' _) = M.null declarations'

lookup :: Declaration a => Identifier -> DeclarationSet a -> Maybe a
lookup = lookup'

lookup' :: Declaration a => Identifier -> DeclarationSet a -> Maybe a
lookup' facialName' (DeclarationSet declarations' _) =
    M.lookup facialName' declarations'

(!) :: Declaration a => DeclarationSet a -> Identifier -> a
declarationSet ! facialName' = fromJust $ lookup' facialName' declarationSet

union :: Declaration a
      => DeclarationSet a
      -> DeclarationSet a
      -> Either NameDuplication (DeclarationSet a)
union a b = fromList $ toList a ++ toList b

instance (Declaration a) => L.IsList (DeclarationSet a) where
    type Item (DeclarationSet a) = a
    fromList declarations' =
        case fromList declarations' of
            Right set -> set
            Left (FacialNameDuplication (Name fname _)) ->
                error ("identifiers must be unique; " ++ show fname ++
                       " is defined more than once")
            Left (BehindNameDuplication (Name _ bname)) ->
                error ("behind names must be unique; " ++ show bname ++
                        " is defined more than once")
    toList = toList