packages feed

FiniteCategories-0.1.0.0: src/IO/Parsers/CompositionGraph.hs

{-| Module  : FiniteCategories
Description : A parser to read .cg files.
Copyright   : Guillaume Sabbagh 2021
License     : GPL-3
Maintainer  : guillaumesabbagh@protonmail.com
Stability   : experimental
Portability : portable

A parser to read .cg files.

A .cg file follows the following rules :
    1. Each line defines either an object, a morphism or a composition law entry.
    2. The following strings are reserved : ' -','-> ',' = '
    3. To define an object, write a line containing its name.
    4. To define an arrow, the syntax "source_object -name_of_morphism-> target_object" is used, where "source_object", "target_object" and "name_of_morphism" should be replaced.
    4.1. If an object mentionned does not exist, it is created.
    4.2. The spaces are important. 
    5. To define a composition law entry, the syntax "source_object1 -name_of_first_morphism-> middle_object -name_of_second_morphism-> target_object1 = source_object2 -name_of_composite_morphism-> target_object2" is used, where "source_object1", "name_of_first_morphism", "middle_object", "name_of_second_morphism", "target_object1", "source_object2", "name_of_composite_morphism", "target_object2" should be replaced.
    5.1 If an object mentionned does not exist, it is created.
    5.2 If a morphism mentionned does not exist, it is created.
    5.3 You can use the tag <ID/> in order to map a morphism to an identity.
-}

module IO.Parsers.CompositionGraph
(
    readCGFile,
    writeCGFile
)
where
    import FiniteCategory.FiniteCategory
    import CompositionGraph.CompositionGraph
    import IO.Parsers.Lexer
    import Data.IORef
    import Data.Text (Text, pack, unpack)
    import Data.List (elemIndex, nub, intercalate)
    import Utils.Tuple
    import IO.PrettyPrint
    
    import System.Directory (createDirectoryIfMissing)
    import System.FilePath.Posix (takeDirectory)
    
    type CG = CompositionGraph Text Text
    
    addObject :: [Token] -> CG -> CG
    addObject [Name str] cg@CompositionGraph{graph=(n,a),law=l} = if elem str (ob cg) then cg else CompositionGraph{graph=((str:(ob cg)),a),law=l}
    addObject otherTokens _ = error $ "addObject on invalid tokens : "++show otherTokens
    
    addMorphism :: [Token] -> CG -> CG
    addMorphism [Name src, BeginArrow, Name arr, EndArrow, Name tgt] cg = if elem (Just arr) (getLabel <$> (ar newCG2 src tgt)) then newCG2 else CompositionGraph{graph=(n,((src,tgt,arr):a)),law=l}
        where
            newCG1 = addObject [Name src] cg
            newCG2@CompositionGraph{graph=(n,a),law=l} = addObject [Name tgt] newCG1
    addMorphism otherTokens _ = error $ "addMorphism on invalid tokens : "++show otherTokens
    
    extractPath :: [Token] -> RawPath Text Text
    extractPath [] = []
    extractPath [Identity] = []
    extractPath [(Name _)] = []
    extractPath ((Name src) : (BeginArrow : ((Name arr) : (EndArrow : ((Name tgt) : ts))))) = (extractPath ((Name tgt) : ts)) ++ [(src,tgt,arr)]
    extractPath otherTokens = error $ "extractPath on invalid tokens : "++show otherTokens
    
    addCompositionLawEntry :: [Token] -> CG -> CG
    addCompositionLawEntry tokens cg@CompositionGraph{graph=(n,a),law=l} = CompositionGraph{graph=(n++newObj,a++newMorph),law=(pathLeft,pathRight):l}
        where
            Just indexEquals = elemIndex Equals tokens
            (tokensLeft,(_:tokensRight)) = splitAt indexEquals tokens
            pathLeft = extractPath tokensLeft
            pathRight = extractPath tokensRight
            newObj = nub $ [s | (s,_,_) <- pathLeft++pathRight, not (elem s n)]++[t | (_,t,_) <- pathLeft++pathRight, not (elem t n)]
            newMorph = nub [e | e <- pathLeft++pathRight, not (elem e a)]
    
    readLine :: String -> CG -> CG
    readLine line cg
        | null lexedLine = cg
        | elem Equals lexedLine = addCompositionLawEntry lexedLine cg
        | elem BeginArrow lexedLine = addMorphism lexedLine cg
        | otherwise = addObject lexedLine cg
        where
            lexedLine = (parserLex line)
    
    parseCGString :: String -> CG
    parseCGString str = newCG
        where
            ls = lines str
            cg = mkEmptyCompositionGraph
            newCG = foldr readLine cg ls
    
    -- | Reads a cg file and returns a composition graph.
    readCGFile :: String -> IO CG
    readCGFile path = do
        file <- readFile path
        return $ parseCGString file
        
    reversedRawPathToString :: (PrettyPrintable a, PrettyPrintable b) => RawPath a b -> String
    reversedRawPathToString [] = "<ID>"
    reversedRawPathToString [(s,t,l)] = pprint s ++ " -" ++ pprint l ++ "-> " ++ pprint t
    reversedRawPathToString ((s,t,l):xs) = pprint s ++ " -" ++ pprint l ++ "-> " ++ reversedRawPathToString xs
    
    unparseCG :: (PrettyPrintable a, PrettyPrintable b, Eq a, Eq b) => CompositionGraph a b -> String
    unparseCG cg = finalString
        where
            obString = intercalate "\n" $ pprint <$> ob cg
            arNotIdentity = filter (isNotIdentity cg) (genArrows cg)
            reversedRawPaths = (reverse.snd3.path) <$> arNotIdentity
            arStringBeforeComment = reversedRawPathToString <$> reversedRawPaths
            commentOutComposite = [if isComposite cg m then ('#':s) else s | (s,m) <- zip arStringBeforeComment arNotIdentity]
            arString = intercalate "\n" $ commentOutComposite
            lawString = intercalate "\n" $ (\(rp1,rp2) -> (reversedRawPathToString (reverse rp1)) ++ " = " ++ (reversedRawPathToString (reverse rp2))) <$> (law cg)
            finalString = "#Objects :\n"++obString++"\n\n# Arrows :\n"++arString++"\n\n# Composition law :\n"++lawString
    
    -- | Saves a composition graph into a file located at a given path.
    writeCGFile :: (PrettyPrintable a, PrettyPrintable b, Eq a, Eq b) => CompositionGraph a b -> String -> IO ()
    writeCGFile cg filepath = do
        createDirectoryIfMissing True $ takeDirectory filepath
        writeFile filepath $ unparseCG cg