module Text.ProtocolBuffers.ProtoCompile.BreakRecursion
( makeResult,displayResult,Result(..),VertexKind(..),Part(..),pKey,pfKey,getKind ) where
import Prelude hiding (pi)
import Control.Monad(guard,mplus)
import qualified Data.Foldable as F
import Data.Function(on)
import Data.Graph
import Data.List
import qualified Data.Map as Map
import Data.Map(Map)
import Data.Maybe(mapMaybe)
import qualified Data.Set as Set
import Data.Set(Set)
import Text.ProtocolBuffers.Basic
import Text.ProtocolBuffers.Identifiers
import Text.ProtocolBuffers.Reflections
#if __GLASGOW_HASKELL__ < 710
import Data.Monoid
#endif
import Debug.Trace(trace)
ecart :: String -> a -> a
ecart _ a = a
fst3 :: (a,b,c) -> a
fst3 (x,_,_) = x
snd3 :: (a,b,c) -> b
snd3 (_,x,_) = x
imp :: String -> a
imp s = error $ "Inconceivable! Text.ProtocolBuffers.ProtoCompile.BreakRecursion."++s
iguard :: Monad m => Bool -> String -> m ()
iguard True _ = return ()
iguard False s = imp s
type MKey = FMName String
pKey :: ProtoName -> MKey
pKey (ProtoName {haskellPrefix=a,parentModule=b,baseName=c}) = foldr1 dotFM . map promoteFM $ a++b++[c]
pfKey :: ProtoFName -> MKey
pfKey (ProtoFName {haskellPrefix'=a,parentModule'=b}) = foldr1 dotFM . map promoteFM $ a++b
data VertexKind = TopProtoInfo
| Simple
| TypeBoot
| KeyTypeBoot
| SplitKeyTypeBoot
deriving (Show,Eq,Ord)
data Part = Normal | Source | KeyFile deriving (Show,Eq,Ord)
data V = V { vMKey :: !MKey
, vNeedsKeys :: !(Set MKey)
, vKeysNeedsTypes :: !(Set MKey)
, vTypeNeedsTypes :: !(Set MKey) }
deriving Show
data Label = L !Part !MKey deriving (Show,Eq,Ord)
type E = (V,Label,[Label])
type G = [E]
type SCCs = [G]
data Result = Result { rKind :: Map MKey VertexKind
, rIBoot :: Set (MKey,Part,MKey)
, rIKey :: Set (MKey,MKey) }
deriving (Eq,Show)
displayResult :: Result -> String
displayResult (Result {rKind = kv, rIBoot = ab, rIKey=ab'keys }) = unlines $
[ "--- displayResult ----"
, "Modules which are not Simple"
] ++ map (\(k,v) -> indent . shows (fmName k) . (" has kind "++) $ show v) (Map.assocs kv) ++
[ "Module imports marked with SOURCE for Types"
] ++ map (indent . untriple) (Set.toAscList ab) ++
[ "Module imports marked with SOURCE or 'Key for keys"
] ++ map (indent . unpair) (Set.toAscList ab'keys)
where indent = (' ':).(' ':)
untriple (a,p,b) = fmName a ++ " " ++ show p ++ " : import {-# SOURCE #-} " ++ fmName b
unpair (a,b) = fmName a ++ " : import {-# SOURCE or 'Key #-} " ++ fmName b
showSCCs :: SCCs -> String
showSCCs gs = concatMap (\ g -> "\n>< SCC Graph ><\n"++concatMap showE g) gs
showG :: G -> String
showG g = '\n':concatMap showE g
showE :: E -> String
showE (v,n,ls) = unlines $ [ "( "++show n, " , "++show v, " , "++show ls, ")" ]
instance Monoid Result where
mempty = Result mempty mempty mempty
mappend r1 r2 = Result { rKind = Map.unionWith max (rKind r1) (rKind r2)
, rIBoot = mappend (rIBoot r1) (rIBoot r2)
, rIKey = mappend (rIKey r1) (rIKey r2) }
getKind :: Result -> MKey -> VertexKind
getKind r = let m = rKind r in \n -> Map.findWithDefault Simple n m
getType :: VertexKind -> Part
getType TopProtoInfo = imp "getType: TopProtoInfo"
getType Simple = Normal
getType TypeBoot = Source
getType KeyTypeBoot = Source
getType SplitKeyTypeBoot = Source
getKey :: VertexKind -> Part
getKey TopProtoInfo = Normal
getKey Simple = Normal
getKey TypeBoot = Normal
getKey KeyTypeBoot = Source
getKey SplitKeyTypeBoot = KeyFile
makeResult :: ProtoInfo -> Result
makeResult protoInfo =
let pvs@(p,vs) = makeVertices protoInfo
initResult = breakKeys pvs
sccs = cycles (makeG (p:vs) initResult)
answer = cull (p:vs) $ breakGraph initResult sccs
finalGraph = makeG (p:vs) answer
remainingProblems = cycles finalGraph
msg = unlines [ "<!!!!!!!!!!!> KLAXON, RED SPINNING LIGHT, ETC."
, "! WARNING: hprotoc unexpectedly failed to disentangle all the mutually-recursive message definitions."
, "! PLEASE REPORT THIS FAILURE ALONG WITH THE PROTO FILE."
, "! The failed subset is:"
] ++ showSCCs remainingProblems ++ "\n</!!!!!!!!!!!>"
in if null remainingProblems then ecart (showG finalGraph) answer
else trace msg answer
makeG :: [V] -> Result -> G
makeG vs r = concatMap (makeEdgesForV r) vs
makeVertices :: ProtoInfo -> (V,[V])
makeVertices pi = answer where
answer = ( protoInfoV , map makeV (messages pi) ++ map makeVoneof (oneofs pi) )
protoInfoV = V { vMKey = pKey (protoMod pi)
, vNeedsKeys = mempty
, vKeysNeedsTypes = knt (extensionKeys pi)
, vTypeNeedsTypes = mempty }
makeV di = V { vMKey = pKey (descName di)
, vNeedsKeys = nk (knownKeys di)
, vKeysNeedsTypes = knt (keys di)
, vTypeNeedsTypes = Set.union (tnt (fields di)) (ont (descOneofs di)) }
makeVoneof oi = V { vMKey = pKey (oneofName oi)
, vNeedsKeys = mempty
, vKeysNeedsTypes = mempty
, vTypeNeedsTypes = (tnt . fmap snd . oneofFields) oi
}
allK = Set.fromList (pKey (protoMod pi) : map (pKey . descName) (messages pi))
allT = Set.fromList $ (map (pKey . descName) (messages pi)) ++ (map (pKey . oneofName) (oneofs pi))
tnt :: Seq FieldInfo -> Set MKey
tnt fs = Set.intersection allT $ Set.fromList $ map pKey . mapMaybe typeName . F.toList $ fs
ont :: Seq OneofInfo -> Set MKey
ont os = Set.intersection allT $ Set.fromList $ map (pKey . oneofName) . F.toList $ os
knt :: Seq KeyInfo -> Set MKey
knt ks =
let (pns, fsL) = unzip (F.toList ks)
fnt :: [FieldInfo] -> Set MKey
fnt fs = Set.fromList $ (map pKey . mapMaybe typeName $ fs) ++ (map (pfKey . fieldName) fs)
in Set.intersection allT $ Set.union (Set.fromList (map pKey pns)) (fnt fsL)
nk :: Seq FieldInfo -> Set MKey
nk fs = Set.intersection allK $ Set.fromList $ map (pfKey . fieldName) . F.toList $ fs
breakKeys :: (V,[V]) -> Result
breakKeys (pv,vsOther) =
let vs = pv : vsOther
es = map makeInitialEdges vs where
makeInitialEdges v = (v,L Normal self,[ L Normal b | b <- Set.toList (vNeedsKeys v), b/=self ])
where self = vMKey v
startingResult = Result { rKind = needTypeBoot, rIBoot = mempty, rIKey = mempty }
needTypeBoot = Map.singleton (vMKey pv) TopProtoInfo `Map.union`
( Map.fromList . map (\(_,L _ a,_) -> (a,TypeBoot))
. filter (\(_,_,bLs) -> not (null bLs)) $ es )
breakSCCs :: Result -> SCCs -> Result
breakSCCs r sccs = r `mappend` mconcat (map breakSCC sccs)
breakSCC :: G -> Result
breakSCC [] = imp $ "breakKeys.breakSCC: The SCC cannot be empty!"
breakSCC es' = let (toBust,next'sccs) = snd $ maximumBy (compare `on` fst) (map f (pullEach es'))
where f ((v,_,_),es'') = let (s,sccs) = score es'' in (s,(v,sccs))
bk = vMKey toBust
ik = Set.fromList [ (ek,bk) | ek <- map (vMKey . fst3) es', ek/=bk ]
newResult = Result { rKind = Map.singleton bk KeyTypeBoot
, rIBoot = mempty
, rIKey = ik }
in breakSCCs newResult next'sccs
initBoot r = r { rIBoot = Set.fromList . concatMap withParts $ es }
where withParts (_,L _ a,bLs) = [ withPart a b | L _ b <- bLs ]
withPart a b = let p = getKey (getKind r b) in (b,p,a)
in initBoot $ breakSCCs startingResult (cycles es)
score :: G -> ( (Int,Int), SCCs )
score es = ((value,parts),sccs) where
sccs = cycles es
value = (pred . length) es (sum (map (pred . length) sccs))
parts = length sccs
cycles :: G -> SCCs
cycles = filter atLeastTwo . map flattenSCC . stronglyConnCompR
where atLeastTwo :: [a] -> Bool
atLeastTwo (_:_:_) = True
atLeastTwo _ = False
pullEach :: [a] -> [(a,[a])]
pullEach = go id where go _ [] = []
go f (x:xs) = (x,f xs) : go (f . (x:)) xs
makeEdgesForV :: Result -> V -> [E]
makeEdgesForV r v =
let me = vMKey v; myKind = getK me
getK = getKind r; self p = L p me;
typeL p n = if Set.notMember (me,p,n) (rIBoot r) then L Normal n
else let checkSource = getType (getK n)
in if checkSource == Source then L Source n
else error "makeEdgesForV.typeL.getType.getK of n did not return Source!"
keyL n = if Set.notMember (me,n) (rIKey r) then L Normal n
else L (getKey (getK n)) n
sKNT (L p _) = Set.map (typeL p) (vKeysNeedsTypes v)
sTNT (L p _) = Set.map (typeL p) (vTypeNeedsTypes v)
sNK _ = Set.map keyL (vNeedsKeys v)
notMe set = [ e | e@(L _p o) <- Set.toList set, o/=me ]
standard = let s = self Normal in (v,s,notMe $ Set.unions [ sKNT s, sTNT s, sNK s])
source = let s = self Source in (v,s,[])
sourceKTB = let s = self Source in (v,s,notMe $ sKNT s)
standardSKTB = let s = self Normal in (v,s,notMe' $ Set.union (sNK s) (sTNT s))
where notMe' set = [ e | e@(L p o) <- Set.toList set, o/=me || p==KeyFile ]
keyfileSKTB = let s = self KeyFile in (v,s,Set.toList $ sKNT s)
in case myKind of
TopProtoInfo -> [standard]
Simple -> [standard]
TypeBoot -> [standard,source]
KeyTypeBoot -> [standard,sourceKTB]
SplitKeyTypeBoot -> [standardSKTB,keyfileSKTB,source]
breakGraph :: Result -> SCCs -> Result
breakGraph r [] = ecart ("\nbreakGraph leaf answer\n"++displayResult r) $ r
breakGraph r sccs = ecart ("\nbreakGraph\n"++displayResult r) $
r `mappend` mconcat (map (breakCycle r) (rejoinVertices sccs))
rejoinVertices :: SCCs -> SCCs
rejoinVertices [] = []
rejoinVertices g@([_]) = g
rejoinVertices gs =
let vgs :: [(Set MKey,G)]
vgs = map (\ g -> (Set.fromList . map (vMKey . fst3) $ g,g)) gs
process [] = []
process ((_,g):[]) = [g]
process ((v,g):rest) = walk id rest where
walk p [] = g : process (p [])
walk p (x@(v',g'):rest') | Set.null (Set.intersection v v') = walk (p . (x:)) rest'
| otherwise = process ((Set.union v v',g++g') : p [])
in process vgs
breakCycle :: Result -> G -> Result
breakCycle oldR sccIn =
let bits = map snd3 sccIn
toCompare = mapMaybe f (pullEach sccIn) where
allV = Set.fromList (map (vMKey . fst3) sccIn)
f :: (E,[E]) -> Maybe ((Int, Int), (Result, SCCs))
f (e@(v,L p me,_bLs), es) = ecart (">< picking:\n"++showE e++
"\nfrom:"++show bits++
"\nscore: "++show observe++"\n") $
answer where
answer = case (getKind oldR me,p) of
(TopProtoInfo,Normal) -> ans'R
(Simple,Normal) -> ans'R `mplus` ans'TB
(TypeBoot,Normal) -> ans' `mplus` ans'R
(KeyTypeBoot,Normal) -> ans' `mplus` ans'R
(KeyTypeBoot,Source) -> ans'RK `mplus` ans'SKTB
(SplitKeyTypeBoot,Normal) -> ans' `mplus` ans'R
(SplitKeyTypeBoot,KeyFile) -> ans'RK
(TypeBoot,Source) -> imp $
"breakCycle.toCompare.f cannot have (TypeBoot,Source) in SCC!" ++ eMsg
(SplitKeyTypeBoot,Source) -> imp $
"breakCycle.toCompare.f cannot have (SplitKeyTypeBoot,Source) in SCC!" ++ eMsg
_ -> imp $ "breakCycle.toCompare.f: impossible combination in SCC:"++ eMsg
observe = case answer of Nothing -> "Nothing"; Just (s,_) -> "Just "++show s
eMsg = '\n':unlines (map showE (e:es))
ans',ans'R,ans'TB,ans'SKTB :: Maybe ((Int, Int), (Result, SCCs))
ans' = if Set.null newIBoot then Nothing
else go $ oldR `mappend` Result { rKind = mempty
, rIBoot = newIBoot
, rIKey = mempty }
ans'R = if Set.null newIBootR then Nothing
else go $ oldR `mappend` Result { rKind = mempty
, rIBoot = newIBootR
, rIKey = mempty }
ans'RK = if Set.null newIBootRK then Nothing
else go $ oldR `mappend` Result { rKind = mempty
, rIBoot = newIBootRK
, rIKey = mempty }
ans'TB = go $ oldR `mappend` Result { rKind = Map.singleton me TypeBoot
, rIBoot = newIBoot
, rIKey = mempty }
ans'SKTB = go $ oldR `mappend` Result { rKind = Map.singleton me SplitKeyTypeBoot
, rIBoot = newIBootSKTB
, rIKey = Set.singleton (me,me) }
newIBoot,newIBootR,newIBootRK,newIBootSKTB :: Set (MKey,Part,MKey)
newIBoot = Set.fromList $ do
(va,L pa a,_) <- es
iguard (Set.member a allV) $
"breakCycle.toCompare.newIBoot sanity check 083425 failed:"++eMsg
guard (((pa == Normal) &&
(Set.member me (vTypeNeedsTypes va))) ||
((pa == getKey (getKind oldR a)) &&
(Set.member me (vKeysNeedsTypes va))))
let x=(a,pa,me)
guard (Set.notMember x (rIBoot oldR))
return x
newIBootR = Set.fromList $ do
b <- Set.toList (Set.union (vTypeNeedsTypes v) (vKeysNeedsTypes v))
guard (Set.member b allV)
guard (Source == getType (getKind oldR b))
guard (me /= b || p == KeyFile)
let x = (me,p,b)
guard (Set.notMember x (rIBoot oldR))
return x
newIBootRK = Set.fromList $ do
b <- Set.toList (vKeysNeedsTypes v)
guard (Set.member b allV)
guard (Source == getType (getKind oldR b))
guard (me /= b || p == KeyFile)
let x = (me,p,b)
guard (Set.notMember x (rIBoot oldR))
return x
newIBootSKTB = Set.union newIBoot . Set.fromList $
(if Set.member me (vKeysNeedsTypes v) then ((me,KeyFile,me):) else id) $ do
b <- Set.toList (vKeysNeedsTypes v)
guard (Set.member (me,Source,b) (rIBoot oldR))
let x = (me,KeyFile,b)
iguard (Set.notMember x (rIBoot oldR)) $
"breakCycle.toCompare.newIBoot2 KeyTypeBoot already had entries for KeyFile!:"++eMsg
return x
go :: Result -> Maybe ((Int, Int), (Result, SCCs))
go newR = let (s,sccs) = score (makeG (map fst3 (e:es)) newR)
in Just (s,(newR,sccs))
in ecart (">< breakCycle of "++show bits++"\n\n") $
if null toCompare
then imp $ "breakCycle: This SCC had no Simple or KeyTypeBoot nodes!\n"++ unlines (map show sccIn)
else let (newR,next'sccs) = snd $ maximumBy (compare `on` fst) toCompare
in breakGraph newR next'sccs
cull :: [V] -> Result -> Result
cull vs rIn =
let trial :: Result -> (MKey,Part,MKey) -> Result
trial old x = let new = old { rIBoot = Set.delete x (rIBoot old) }
in if null (cycles (makeG vs new)) then new else old
rOut = foldl' trial rIn (Set.toList (rIBoot rIn))
in if rOut == rIn then rOut else cull vs rOut