packages feed

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

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

A parser to read .scg files.

A .scg file follows the following rules :
    1. The first line is an integer specifying the max number of loops morphisms can do.
    2. The rest of the file is a cg file. (See IO.Parsers.CompositionGraph)
-}

module IO.Parsers.SafeCompositionGraph 
(
    SCG(..),
    parseSCGString,
    readSCGFile,
    writeSCGFile
)
where
    import FiniteCategory.FiniteCategory
    import CompositionGraph.CompositionGraph
    import CompositionGraph.SafeCompositionGraph
    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)
    
    -- | The type of SafeCompositionGraph created by reading a scg file.
    type SCG = SafeCompositionGraph Text Text
    
    addObject :: [Token] -> SCG -> SCG
    addObject [Name str] cg@SafeCompositionGraph{graphS=(n,a),lawS=l,maxCycles=mc} = if elem str (ob cg) then cg else SafeCompositionGraph{graphS=((str:(ob cg)),a),lawS=l,maxCycles=mc}
    addObject otherTokens _ = error $ "addObject on invalid tokens : "++show otherTokens
    
    addMorphism :: [Token] -> SCG -> SCG
    addMorphism [Name src, BeginArrow, Name arr, EndArrow, Name tgt] cg = if elem (Just arr) (getLabelS <$> (ar newSCG2 src tgt)) then newSCG2 else SafeCompositionGraph{graphS=(n,((src,tgt,arr):a)),lawS=l,maxCycles=mc}
        where
            newSCG1 = addObject [Name src] cg
            newSCG2@SafeCompositionGraph{graphS=(n,a),lawS=l,maxCycles=mc} = addObject [Name tgt] newSCG1
    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] -> SCG -> SCG
    addCompositionLawEntry tokens cg@SafeCompositionGraph{graphS=(n,a),lawS=l,maxCycles=mc} = SafeCompositionGraph{graphS=(n++newObj,a++newMorph),lawS=(pathLeft,pathRight):l,maxCycles=mc}
        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 -> SCG -> SCG
    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)
    
    -- | Parse a string extracted from a scg file. Returns a safe composition graph.
    parseSCGString :: String -> SCG
    parseSCGString str = if test then newSCG else error $ "First line of scg file is not a number : "++show ls
        where
            test = null $ filter (\x -> not $ elem x ['0'..'9']) $ head ls
            ls = filter (not.null.parserLex) $ lines str
            maxCyc = (read $ head ls) :: Int
            cg = mkEmptySafeCompositionGraph maxCyc
            newSCG = foldr readLine cg (tail ls)
        
    -- | Reads a scg file and returns a safe composition graph.
    readSCGFile :: String -> IO SCG
    readSCGFile path = do
        file <- readFile path
        return $ parseSCGString 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    
    
    unparseSCG :: (PrettyPrintable a, PrettyPrintable b, Eq a, Eq b) => SafeCompositionGraph a b -> String
    unparseSCG cg = finalString
        where
            obString = intercalate "\n" $ pprint <$> ob cg
            arNotIdentity = filter (isNotIdentity cg) (arrows cg)
            reversedRawPaths = (reverse.snd3.pathS) <$> 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))) <$> (lawS cg)
            finalString = "#Max number of cycles :\n"++show (maxCycles cg)++"\n\n#Objects :\n"++obString++"\n\n# Arrows :\n"++arString++"\n\n# Composition law :\n"++lawString
    
    -- | Saves a safe composition graph into a file located at a given path.
    writeSCGFile :: (PrettyPrintable a, PrettyPrintable b, Eq a, Eq b) => SafeCompositionGraph a b -> String -> IO ()
    writeSCGFile cg filepath = do
        createDirectoryIfMissing True $ takeDirectory filepath
        writeFile filepath $ unparseSCG cg