Files
gf-core/src/GF/Infra/ReadFiles.hs
2004-03-31 12:30:34 +00:00

267 lines
8.6 KiB
Haskell

module ReadFiles
--- where
--
(
--
getAllFiles,fixNewlines,ModName,getOptionsFromFile,
--
gfcFile,gfFile,gfrFile,isGFC,resModName) where
import Arch (selectLater, modifiedFiles, ModTime, getModTime,laterModTime)
import Option
import Operations
import UseIO
import System
import Char
import Monad
import List
-- make analysis for GF grammar modules. AR 11/6/2003--24/2/2004
-- to find all files that have to be read, put them in dependency order, and
-- decide which files need recompilation. Name file.gf is returned for them,
-- and file.gfc or file.gfr otherwise.
type ModName = String
type ModEnv = [(ModName,ModTime)]
getAllFiles :: Options -> [InitPath] -> ModEnv -> FileName -> IOE [FullPath]
getAllFiles opts ps env file = do
-- read module headers from all files recursively
ds0 <- getImports ps file
let ds = [((snd m,map fst ms),p) | ((m,ms),p) <- ds0]
ioeIO $ putStrLn $ "all modules:" +++ show (map (fst . fst) ds)
-- get a topological sorting of files: returns file names --- deletes paths
ds1 <- ioeErr $ either
return
(\ms -> Bad $ "circular modules" +++
unwords (map show (head ms))) $ topoTest $ map fst ds
-- associate each file name with its path --- more optimal: save paths in ds1
let paths = [(f,p) | ((f,_),p) <- ds]
let pds1 = [(p,f) | f <- ds1, Just p <- [lookup f paths]]
if oElem fromSource opts
then return [gfFile (prefixPathName p f) | (p,f) <- pds1]
else do
ds2 <- ioeIO $ mapM (selectFormat env) pds1
let ds4 = needCompile opts (map fst ds0) ds2
return ds4
-- to decide whether to read gf or gfc, or if in env; returns full file path
data CompStatus =
CSComp -- compile: read gf
| CSRead -- read gfc
| CSEnv -- gfc is in env
| CSEnvR -- also gfr is in env
| CSDont -- don't read at all
| CSRes -- read gfr
deriving (Eq,Show)
selectFormat :: ModEnv -> (InitPath,ModName) -> IO (ModName,(InitPath,CompStatus))
selectFormat env (p,f) = do
let pf = prefixPathName p f
let mtenv = lookup f env -- Nothing if f is not in env
let rtenv = lookup (resModName f) env
mtgfc <- getModTime $ gfcFile pf
mtgf <- getModTime $ gfFile pf
let stat = case (rtenv,mtenv,mtgfc,mtgf) of
(Just tenv,_,_,Just tgf) | laterModTime tenv tgf -> CSEnvR
(_,Just tenv,_,Just tgf) | laterModTime tenv tgf -> CSEnv
(_,_,Just tgfc,Just tgf) | laterModTime tgfc tgf -> CSRead
_ -> CSComp
return $ (f, (p,stat))
needCompile :: Options ->
[ModuleHeader] -> [(ModName,(InitPath,CompStatus))] -> [FullPath]
needCompile opts headers sfiles0 = paths $ res $ mark $ iter changed where
deps = [(snd m,map fst ms) | (m,ms) <- headers]
typ m = maybe MTyOther id $ lookup m [(m,t) | ((t,m),_) <- headers]
uses m = [(n,u) | ((_,n),ms) <- headers, (k,u) <- ms, k==m]
stat0 m = maybe CSComp snd $ lookup m sfiles0
allDeps = [(m,iterFix add ms) | (m,ms) <- deps] where
add os = [m | o <- os, Just n <- [lookup o deps],m <- n]
-- only treat reused, interface, or instantiation if needed
sfiles = map relevant sfiles0
relevant fp@(f,(p,st)) =
let us = uses f in
if not (all noComp us) then
fp else
if (typ f == MTyIncomplete || (not (null us) && all isAux us)) then
(f,(p,CSDont)) else
fp
isAux = flip elem [MUReuse,MUInstance,MUComplete] . snd
noComp = flip elem [CSRead,CSEnv,CSEnvR] . stat0 . fst
-- start with the changed files themselves; returns [ModName]
changed = [f | (f,(_,CSComp)) <- sfiles]
-- add other files that depend on some changed file; returns [ModName]
iter np = let new = [f | (f,fs) <- deps,
not (elem f np), any (flip elem np) fs]
in if null new then np else (iter (new ++ np))
-- for each module in the full list, compile if depends on what needs compile
-- returns [FullPath]
mark cs = [(f,(path,st)) |
(f,(path,st0)) <- sfiles,
let st = if (elem f cs) then CSComp else st0]
-- if a compilable file depends on a resource, read gfr instead of gfc/env
-- but don't read gfr if already in env (by CSEnvR)
-- Also read res if the option "retain" is present
res cs = map mkRes cs where
mkRes x@(f,(path,st)) | elem st [CSRead,CSEnv] = case typ f of
MTyResource | not (null [m | (m,(_,CSComp)) <- cs,
Just ms <- [lookup m allDeps], elem f ms])
|| oElem retainOpers opts
-> (f,(path,CSRes))
_ -> x
mkRes x = x
-- construct list of paths to read
paths cs = [mkName f p st | (f,(p,st)) <- cs, elem st [CSComp, CSRead,CSRes]]
mkName f p st = mk $ prefixPathName p f where
mk = case st of
CSComp -> gfFile
CSRead -> gfcFile
CSRes -> gfrFile
isGFC = (== "gfc") . fileSuffix
gfcFile = suffixFile "gfc"
gfrFile = suffixFile "gfr"
gfFile = suffixFile "gf"
resModName = ('#':)
-- to get imports without parsing the whole files
getImports :: [InitPath] -> FileName -> IOE [(ModuleHeader,InitPath)]
getImports ps = get [] where
get ds file = do
let name = fileBody file
(p,s) <- readFileIfPath ps $ file
let ((typ,mname),imps) = importsOfFile s
ioeErr $ testErr (mname == name) $
"module name differs from file name in" +++ name
case imps of
_ | elem name (map (snd . fst . fst) ds) -> return ds --- file already read
[] -> return $ (((typ,name),[]),p):ds
_ -> do
let files = map (gfFile . fst) imps --- requires there's always .gf file
foldM get ((((typ,name),imps),p):ds) files
-- internal module dep information
data ModUse =
MUReuse
| MUInstance
| MUComplete
| MUOther
deriving (Eq,Show)
data ModTyp =
MTyResource
| MTyIncomplete
| MTyOther
deriving (Eq,Show)
type ModuleHeader = ((ModTyp,ModName),[(ModName,ModUse)])
importsOfFile :: String -> ModuleHeader
importsOfFile =
getModuleHeader . -- analyse into mod header
filter (not . spec) . -- ignore keywords and special symbols
unqual . -- take away qualifiers
unrestr . -- take away union restrictions
takeWhile (not . term) . -- read until curly or semic
lexs . -- analyse into lexical tokens
unComm -- ignore comments before the headed line
where
term = flip elem ["{",";"]
spec = flip elem ["of", "open","in",":", "->","=", "(", ")",",","**","union"]
unqual ws = case ws of
"(":q:ws' -> unqual ws'
w:ws' -> w:unqual ws'
_ -> ws
unrestr ws = case ws of
"[":ws' -> unrestr $ tail $ dropWhile (/="]") ws'
w:ws' -> w:unrestr ws'
_ -> ws
getModuleHeader :: [String] -> ModuleHeader -- with, reuse
getModuleHeader ws = case ws of
"incomplete":ws2 -> let ((_,name),us) = getModuleHeader ws2 in
((MTyIncomplete,name),us)
"interface":ws2 -> let ((_,name),us) = getModuleHeader ("resource":ws2) in
((MTyIncomplete,name),us)
"resource":name:ws2 -> case ws2 of
"reuse":m:_ -> ((MTyResource,name),[(m,MUReuse)])
m:"with":ms -> ((MTyResource,name),(m,MUOther):[(n,MUComplete) | n <- ms])
ms -> ((MTyResource,name),[(n,MUOther) | n <- ms])
"instance":name:m:ws2 -> case ws2 of
"reuse":n:_ -> ((MTyResource,name),(m,MUInstance):[(n,MUReuse)])
n:"with":ms ->
((MTyResource,name),(m,MUInstance):(n,MUComplete):[(n,MUOther) | n <- ms])
ms -> ((MTyResource,name),(m,MUInstance):[(n,MUOther) | n <- ms])
_:name:ws2 -> case ws2 of
"reuse":m:_ -> ((MTyOther,name),[(m,MUReuse)])
m:n:"with":ms ->
((MTyOther,name),(m,MUInstance):(n,MUOther):[(n,MUComplete) | n <- ms])
m:"with":ms -> ((MTyOther,name),(m,MUOther):[(n,MUComplete) | n <- ms])
ms -> ((MTyOther,name),[(n,MUOther) | n <- ms])
unComm s = case s of
'-':'-':cs -> unComm $ dropWhile (/='\n') cs
'{':'-':cs -> dpComm cs
c:cs -> c : unComm cs
_ -> s
dpComm s = case s of
'-':'}':cs -> unComm cs
c:cs -> dpComm cs
_ -> s
lexs s = x:xs where
(x,y) = head $ lex s
xs = if null y then [] else lexs y
-- options can be passed to the compiler by comments in --#, in the main file
getOptionsFromFile :: FilePath -> IO Options
getOptionsFromFile file = do
s <- readFileIf file
let ls = filter (isPrefixOf "--#") $ lines s
return $ fst $ getOptions "-" $ map (unwords . words . drop 3) ls
-- old GF tolerated newlines in quotes. No more supported!
fixNewlines s = case s of
'"':cs -> '"':mk cs
c :cs -> c:fixNewlines cs
_ -> s
where
mk s = case s of
'\\':'"':cs -> '\\':'"': mk cs
'"' :cs -> '"' :fixNewlines cs
'\n' :cs -> '\\':'n': mk cs
c :cs -> c : mk cs
_ -> s