{- |
Module : ./CommonLogic/ParseCLAsLibDefn.hs
Copyright : Eugen Kuksa 2011
License : GPLv2 or higher, see LICENSE.txt
Maintainer : eugenk@informatik.uni-bremen.de
Stability : provisional
Portability : non-portable (imports Logic.Logic)
Analyses CommonLogic files.
-}
module CommonLogic.ParseCLAsLibDefn (parseCL_CLIF) where
import Common.Id
import Common.IRI
import Common.LibName
import Common.AS_Annotation as Anno
import Common.AnnoState
import Common.Parsec
import Common.Result
import Common.ResultT
import Driver.Options
import Driver.ReadFn
import Text.ParserCombinators.Parsec
import Logic.Grothendieck
import CommonLogic.AS_CommonLogic as CL
import CommonLogic.Logic_CommonLogic
import qualified CommonLogic.Parse_CLIF as CLIF (basicSpec)
import Syntax.AS_Library
import Syntax.AS_Structured
import Control.Monad (foldM)
import Control.Monad.Trans
import qualified Data.Set as Set
import qualified Data.Map as Map
import Data.List
import System.FilePath
type SpecMap = Map.Map FilePath SpecInfo
type SpecInfo = (BASIC_SPEC, FilePath, Set.Set String, Set.Set FilePath)
-- (spec, topTexts, importedBy)
-- | call for CommonLogic CLIF-parser with recursive inclusion of importations
parseCL_CLIF :: FilePath -> HetcatsOpts -> ResultT IO [LIB_DEFN]
parseCL_CLIF filename opts = do
let dirFile@(dir, _) = splitFileName filename
specMap <- downloadSpec opts Map.empty Set.empty Set.empty False dirFile dir
return $ map convertToLibDefN $ topSortSpecs
$ Map.map (\ (b, f, _, _) ->
(b, f, Set.map (rmSuffix . tokStr) $ directImports b)) specMap
-- call for CommonLogic CLIF-parser for a single file
parseCL_CLIF_contents :: FilePath -> String -> Either ParseError [BASIC_SPEC]
parseCL_CLIF_contents = runParser (many (CLIF.basicSpec Map.empty) << eof)
(emptyAnnos ())
{- maps imports in basic spec to global definition links (extensions) in
development graph -}
convertToLibDefN :: (FilePath, BASIC_SPEC, FilePath) -> LIB_DEFN
convertToLibDefN (fp, spec, realFp) = let
i = filePathToLibId $ rmSuffix fp
in Lib_defn (setFilePath realFp $ iriLibName i)
(makeLogicItem CommonLogic
: map (addDownload False) (importsAsIris spec)
++ [convertToLibItem spec i])
nullRange []
convertToLibItem :: BASIC_SPEC -> IRI -> Anno.Annoted LIB_ITEM
convertToLibItem b i = makeSpecItem i $ createSpec b
importsAsIris :: BASIC_SPEC -> [IRI]
importsAsIris =
map (filePathToLibId . rmSuffix . tokStr) . Set.toList . directImports
createSpec :: BASIC_SPEC -> Anno.Annoted SPEC
createSpec b = addImports (importsAsIris b)
. makeSpec $ G_basic_spec CommonLogic b
specNameL :: [BASIC_SPEC] -> String -> [String]
specNameL bs def = case bs of
[_] -> [def]
_ -> map (specName def) [0 .. (length bs)]
-- returns a unique name for a node
specName :: String -> Int -> String
specName def i = def ++ "_" ++ show i
fileCombine :: FilePath -> FilePath -> FilePath
fileCombine d = (\ s -> case s of
'.' : p : r | isPathSeparator p -> r
_ -> s) . combine d
httpCombine :: FilePath -> FilePath -> FilePath
httpCombine d f = if checkUri f then f else fileCombine d f
collectDownloads :: HetcatsOpts -> String -> SpecMap -> (String, SpecInfo)
-> ResultT IO SpecMap
collectDownloads opts baseDir specMap (n, (b, _, topTexts, importedBy)) = do
let directImps = Set.elems $ Set.map tokStr $ directImports b
newTopTexts = Set.insert n topTexts
newImportedBy = Set.insert n importedBy
foldM (\ sm d -> do
let p@(ddir, _) = splitFileName d
baseDir' = httpCombine baseDir ddir
newDls <- downloadSpec opts sm newTopTexts newImportedBy True
p baseDir'
return (Map.unionWith unify newDls sm)
) specMap directImps -- imports get @n@ as new "importedBy"
justErr :: a -> String -> ResultT IO a
justErr a s = liftR $ plain_error a s nullRange
downloadSpec :: HetcatsOpts -> SpecMap -> Set.Set String -> Set.Set String
-> Bool -> (String, String) -> String -> ResultT IO SpecMap
downloadSpec opts specMap topTexts importedBy isImport dirFile baseDir = do
let fn = rmSuffix $ uncurry httpCombine dirFile
case Map.lookup fn specMap of
Just info@(b, f, t, _)
| isImport && Set.member fn importedBy ->
justErr specMap . intercalate "\n "
$ "unsupported cyclic imports:" : Set.toList importedBy
| t == topTexts
-> return specMap
| otherwise ->
let newInfo = unify info (b, f, topTexts, importedBy)
newSpecMap = Map.insert fn newInfo specMap
in collectDownloads opts baseDir newSpecMap (fn, newInfo)
Nothing -> do
mCont <- lift $ getCLIFContents opts dirFile baseDir
case mCont of
Left err -> justErr specMap $ show err
Right (file, contents) -> do
lift $ putIfVerbose opts 2 $ "Downloaded " ++ file
case parseCL_CLIF_contents fn contents of
Left err -> justErr specMap $ show err
Right bs -> do
let nbs = zip (specNameL bs fn) bs
nbs2 <- mapM (\ (n, b) -> case
map (rmSuffix . tokStr) $ clTexts b of
[txt] -> if txt == fn then return (n, b) else
liftR $ justWarn (if isImport then n else txt, b)
$ "filename " ++ show fn
++ " does not match cl-text " ++ show txt
[] -> liftR $ justWarn (n, b)
$ "missing cl-text in " ++ show fn
ts -> liftR $ justWarn (n, b)
$ "multiple cl-text entries in "
++ show fn ++ "\n" ++ unlines (map show ts)
) nbs
let nbtis = map (\ (n, b) ->
(n, (b, file, topTexts, importedBy))) nbs2
newSpecMap = foldr (\ (n, bti) sm ->
Map.insertWith unify n bti sm
) specMap nbtis
foldM (\ sm nbt -> do
newDls <- collectDownloads opts baseDir sm nbt
return (Map.unionWith unify newDls sm)
) newSpecMap nbtis
unify :: SpecInfo -> SpecInfo -> SpecInfo
unify (_, _, s, p) (a, b, t, q) = (a, b, s `Set.union` t, p `Set.union` q)
{- one could add support for uri fragments/query
(perhaps select a text from the file) -}
getCLIFContents :: HetcatsOpts -> (String, String) -> String
-> IO (Either String (FilePath, String))
getCLIFContents opts dirFile baseDir =
let fn = uncurry httpCombine dirFile
uStr = useCatalogURL opts
$ if checkUri baseDir then httpCombine baseDir fn else fn
in getExtContent opts { libdirs = baseDir : libdirs opts }
[".clif", ".clf"] uStr
-- retrieves all importations from the text
directImports :: BASIC_SPEC -> Set.Set NAME
directImports (CL.Basic_spec items) = Set.unions
$ map (getImports_textMetas . textsFromBasicItems . Anno.item) items
clTexts :: BASIC_SPEC -> [NAME]
clTexts (CL.Basic_spec items) =
concatMap (getClTexts . textsFromBasicItems . Anno.item) items
textsFromBasicItems :: BASIC_ITEMS -> [TEXT_META]
textsFromBasicItems (Axiom_items axs) = map Anno.item axs
getImports_textMetas :: [TEXT_META] -> Set.Set NAME
getImports_textMetas = Set.unions . map (getImports_text . getText)
getClTexts :: [TEXT_META] -> [NAME]
getClTexts = concatMap (getClTextTexts . getText)
getClTextTexts :: TEXT -> [NAME]
getClTextTexts txt = case txt of
Named_text n t _ -> n : getClTextTexts t
_ -> []
getImports_text :: TEXT -> Set.Set NAME
getImports_text txt = case txt of
Text p _ -> Set.fromList $ map impName $ filter isImportation p
Named_text _ t _ -> getImports_text t
isImportation :: PHRASE -> Bool
isImportation ph = case ph of
Importation _ -> True
_ -> False
impName :: PHRASE -> NAME
impName ph = case ph of
Importation (Imp_name n) -> n
_ -> error "CL.impName"
topSortSpecs :: Map.Map FilePath (BASIC_SPEC, FilePath, Set.Set FilePath)
-> [(FilePath, BASIC_SPEC, FilePath)]
topSortSpecs specMap =
let (fm, rm) = Map.partition (\ (_, _, is) -> Set.null is) specMap
in if Map.null fm then [] else
map (\ (n, (b, f, _)) -> (n, b, f)) (Map.toList fm) ++ topSortSpecs
(Map.map (\ (b, f, is) -> (b, f, is Set.\\ Map.keysSet fm)) rm)