module Text.ProtocolBuffers.Identifiers
( unull,toString,fromString
, IName(..),DIName(..),FIName(..)
, MName(..),FMName(..),PMName(..)
, FName(..),FFName(..),PFName(..)
, Dotted(..),Mangle(..)
, joinPM,joinPF,difi,splitDI,splitFI,splitFM
, checkDIString,checkDIUtf8
, promoteDI,promoteFI,promoteFM,promoteFF,dotFM,dotFF,fqAppend
) where
import qualified Data.ByteString.Lazy.Char8 as LC
import qualified Data.ByteString.Lazy.UTF8 as U
import Data.Char
import Data.List
import Data.Monoid
import Data.Generics(Data)
import Data.Typeable(Typeable)
import Data.Set(Set)
import qualified Data.Set as S
import Text.ProtocolBuffers.Basic
unull :: Utf8 -> Bool
unull = LC.null . utf8
toString :: Utf8 -> String
toString = U.toString . utf8
fromString :: String -> Utf8
fromString = Utf8 . U.fromString
newtype IName a = IName {iName::a} deriving (Data,Typeable,Eq,Ord)
newtype MName a = MName {mName::a} deriving (Data,Typeable,Eq,Ord)
newtype FName a = FName {fName::a} deriving (Data,Typeable,Eq,Ord)
newtype DIName a = DIName {diName :: a} deriving (Data,Typeable,Eq,Ord)
newtype FIName a = FIName {fiName::a} deriving (Data,Typeable,Eq,Ord)
newtype FMName a = FMName {fmName::a} deriving (Data,Typeable,Eq,Ord)
newtype FFName a = FFName {ffName::a} deriving (Data,Typeable,Eq,Ord)
data PMName a = PMName [MName a] (MName a) deriving (Show,Data,Typeable,Read,Eq,Ord)
data PFName a = PFName [MName a] (FName a) deriving (Show,Data,Typeable,Read,Eq,Ord)
app_prec,max_prec :: Int
app_prec = 10
max_prec = 11
readIt :: (Read a) => (a -> a1) -> String -> Int -> String -> [(a1, String)]
readIt con name d = readParen (d > app_prec) (\r -> [(con m,t) | (name',s) <- lex r, name==name', (m,t) <- readsPrec max_prec s])
showIt :: (Show a) => Int -> [Char] -> a -> String -> String
showIt d name a = showParen (d > app_prec) $ (name++) . (' ':) . showsPrec max_prec a
instance Read a => Read (IName a) where readsPrec = readIt IName "IName"
instance Read a => Read (MName a) where readsPrec = readIt MName "MName"
instance Read a => Read (FName a) where readsPrec = readIt FName "FName"
instance Read a => Read (DIName a) where readsPrec = readIt DIName "DIName"
instance Read a => Read (FIName a) where readsPrec = readIt FIName "FIName"
instance Read a => Read (FFName a) where readsPrec = readIt FFName "FFName"
instance Read a => Read (FMName a) where readsPrec = readIt FMName "FMName"
instance Show a => Show (IName a) where showsPrec d (IName a) = showIt d "IName" a
instance Show a => Show (MName a) where showsPrec d (MName a) = showIt d "MName" a
instance Show a => Show (FName a) where showsPrec d (FName a) = showIt d "FName" a
instance Show a => Show (DIName a) where showsPrec d (DIName a) = showIt d "DIName" a
instance Show a => Show (FIName a) where showsPrec d (FIName a) = showIt d "FIName" a
instance Show a => Show (FMName a) where showsPrec d (FMName a) = showIt d "FMName" a
instance Show a => Show (FFName a) where showsPrec d (FFName a) = showIt d "FFName" a
class (Monoid a) => Dotted a where
uncons :: a -> Maybe (Char,a)
cons :: Char -> a -> a
dot :: a -> a -> a
validI :: a -> Maybe (IName a)
validDI :: a -> Maybe (DIName a)
split :: a -> [a]
joinPM :: Dotted a => PMName a -> FMName a
joinPM (PMName xs (MName x)) = FMName (foldr dot x . map mName $ xs)
joinPF :: Dotted a => PFName a -> FFName a
joinPF (PFName xs (FName x)) = FFName (foldr dot x . map mName $ xs)
difi :: Dotted a => DIName a -> FIName a
difi (DIName a) = case uncons a of
Just ('.',_) -> FIName a
_ -> FIName (cons '.' a)
splitDI :: Dotted a => DIName a -> [IName a]
splitDI = map IName . split . diName
splitFI :: Dotted a => FIName a -> [IName a]
splitFI = map IName . split . fiName
splitFM :: Dotted a => FMName a -> [MName a]
splitFM = map MName . split . fmName
promoteDI :: Dotted a => IName a -> DIName a
promoteDI = DIName . iName
promoteFI :: Dotted a => IName a -> FIName a
promoteFI = FIName . cons '.' . iName
promoteFM :: Dotted a => MName a -> FMName a
promoteFM = FMName . mName
promoteFF :: Dotted a => FName a -> FFName a
promoteFF = FFName . fName
dotFM :: Dotted a => FMName a -> FMName a -> FMName a
dotFM (FMName a) (FMName b) = FMName (a `dot` b)
dotFF :: Dotted a => FMName a -> FFName a -> FFName a
dotFF (FMName a) (FFName b) = FFName (a `dot` b)
fqAppend :: Dotted a => FIName a -> [IName a] -> FIName a
fqAppend (FIName base) xs = FIName (foldl' dot base . map iName $ xs)
checkDIString :: String -> Either String (Bool,[IName String])
checkDIString "" = Left $ "Invalid empty identifier: "++show ""
checkDIString "." = Left $ "Invalid identifier of just a period: "++show "."
checkDIString xs | ('.':ys) <- xs = fmap ((,) True) $ parts id (span ('.'/=) ys)
| otherwise = fmap ((,) False) $ parts id (span ('.'/=) xs)
where parts _f ("","") = Left $ "Invalid identifier because it ends with a period: "++show xs
parts _f ("",_) = Left $ "Invalid identifier because is contains two periods in a row: "++show xs
parts f (a,"") = Right (f [IName a])
parts f (a,b) = parts (f . (IName a:)) (span ('.'/=) (tail b))
checkDIUtf8 :: Utf8 -> Either String (Bool,[IName Utf8])
checkDIUtf8 s@(Utf8 xs) =
case U.uncons xs of
Nothing -> Left $ "Invalid empty identifier: "++show ""
Just ('.',ys) | LC.null ys -> Left $ "Invalid identifier of just a period: "++show "."
| otherwise -> fmap ((,) True) $ parts id (U.span ('.'/=) ys)
Just _ -> fmap ((,) False) $ parts id (U.span ('.'/=) xs)
where parts f (a,b) = case (LC.null a,LC.null b) of
(True,True) -> Left $ "Invalid identifier because it ends with a period: "++show (toString s)
(True,_) -> Left $ "Invalid identifier because is contains two periods in a row: "++show (toString s)
(_,True) -> Right (f [IName (Utf8 a)])
_ -> parts (f . (IName (Utf8 a):)) (U.span ('.'/=) (U.drop 1 b))
class Mangle a b where mangle :: a -> b
instance Mangle (IName String) (MName String) where
mangle (IName s) = MName (fixUp s)
instance Mangle (IName Utf8) (MName String) where
mangle (IName s) = MName (fixUp . toString $ s)
instance Mangle (FName String) (MName String) where
mangle (FName s) = MName (fixUp s)
instance Mangle (IName String) (FName String) where
mangle (IName s) = FName (fixLow s)
instance Mangle (IName Utf8) (FName String) where
mangle (IName s) = FName (fixLow . toString $ s)
instance Mangle (MName String) (FName String) where
mangle (MName s) = FName (fixLow s)
instance Mangle (DIName Utf8) (PMName String) where
mangle s = let ms = splitDI s in PMName (map mangle $ init ms) (mangle $ last ms)
instance Mangle (FIName Utf8) (PMName String) where
mangle s = let ms = splitFI s in PMName (map mangle $ init ms) (mangle $ last ms)
instance Mangle (DIName Utf8) (PFName String) where
mangle s = let ms = splitDI s in PFName (map mangle $ init ms) (mangle $ last ms)
instance Mangle (FIName Utf8) (PFName String) where
mangle s = let ms = splitFI s in PFName (map mangle $ init ms) (mangle $ last ms)
dotUtf8 :: Utf8 -> Utf8 -> Utf8
dotUtf8 (Utf8 a) (Utf8 b) = Utf8 (LC.append a (LC.cons '.' b))
dotString :: String -> String -> String
dotString a b = a ++ ('.':b)
splitUtf8 :: Utf8 -> [Utf8]
splitUtf8 = unfoldr s . utf8 where
s :: ByteString -> Maybe (Utf8,ByteString)
s y = case LC.uncons y of
Nothing -> Nothing
Just ('.',xs) -> s xs
_ -> Just (let (a,b) = U.span ('.'/=) y in (Utf8 a,b))
splitString :: String -> [String]
splitString = unfoldr s where
s [] = Nothing
s ('.':xs) = s xs
s xs = Just (span ('.'/=) xs)
validIUtf8 :: Utf8 -> Maybe (IName Utf8)
validIUtf8 xs | unull xs = Nothing
validIUtf8 xs@(Utf8 bs) = if LC.all (`S.member` validISet) bs
then Just (IName xs)
else Nothing
validIString :: String -> Maybe (IName String)
validIString [] = Nothing
validIString xs = if all (`S.member` validISet) xs
then Just (IName xs)
else Nothing
validDIUtf8 :: Utf8 -> Maybe (DIName Utf8)
validDIUtf8 xs | unull xs = Nothing
validDIUtf8 xs@(Utf8 bs) =
if LC.all (`S.member` validDISet) bs && LC.any ('.'/=) bs && LC.last bs /= '.'
&& (all (\(x,y) -> '.'/=x || '.'/=y) . (\x -> zip (init x) (tail x)) . toString $ xs)
then Just (DIName xs)
else Nothing
validDIString :: String -> Maybe (DIName String)
validDIString [] = Nothing
validDIString xs =
if all (`S.member` validDISet) xs && any ('.'/=) xs && last xs /= '.'
&& all (\(x,y) -> '.'/=x || '.'/=y) (zip (init xs) (tail xs))
then Just (DIName xs)
else Nothing
validISet :: Set Char
validISet = S.fromDistinctAscList "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"
validDISet :: Set Char
validDISet = S.fromDistinctAscList ".0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"
instance Dotted Utf8 where
uncons x = case U.uncons (utf8 x) of
Nothing -> Nothing
Just (c,b) -> Just (c,Utf8 b)
cons b (Utf8 bs) | fromEnum b < 128 = Utf8 (LC.cons b bs)
| otherwise = Utf8 ((U.fromString [b]) `mappend` bs)
dot = dotUtf8
split = splitUtf8
validI = validIUtf8
validDI = validDIUtf8
instance Dotted String where
uncons [] = Nothing
uncons (x:xs) = Just (x,xs)
cons = (:)
dot = dotString
split = splitString
validI = validIString
validDI = validDIString
err :: String -> a
err s = error ("Text.ProtocolBuffers.ProtoCompile.Identifiers: "++s)
fixUp :: String -> String
fixUp xs | last xs == '\'' = fixUp (init xs)
fixUp ('_':xs) = "U'"++xs
fixUp i@(x:xs) | isLower x =
let x' = toUpper x
in if isLower x' then err ("fixUp: stubborn lower case"++show i)
else x': xs
fixUp xs = xs
fixLow :: String -> String
fixLow [] = []
fixLow ('U':'\'':xs@('_':_))= fixLow xs
fixLow i@(x:xs) | i `S.member` reserved = i ++ "'"
| isUpper x = let x' = toLower x
in if isUpper x' then err ("fixLow: stubborn upper case: "++show i)
else let i' = (x':xs)
in if i' `S.member` reserved then i' ++ "'" else i'
| otherwise = i
reserved :: Set String
reserved = S.fromDistinctAscList
["_"
,"case","class","data","default","deriving","do","else"
,"forall"
,"if","import","in","infix","infixl","infixr","instance"
,"let","module","newtype","of","then","type","where"
]