% % (c) The GRASP/AQUA Project, Glasgow University, 1998 % The @LexM@ hides the information maintained by the IDL lexer (and parser.) \begin{code}
module LexM 

       (
         LexM
	
       , runLexM         -- :: [FilePath] -> String -> LexM a -> IO (a, SymbolTable IDLToken)
       , invokeLexM      -- :: String -> String -> LexM a -> LexM a
       , ioToLexM        -- :: IO a   -> LexM a
       , incLineNo       -- :: LexM a -> LexM a
       , setSrcLoc       -- :: SrcLoc -> LexM a -> LexM a
       , getSrcLoc       -- :: LexM SrcLoc
       , getOrigSrcLoc   -- :: LexM SrcLoc
       , getPath         -- :: LexM [FilePath]
       , isEOF           -- :: LexM Bool
       , getNextChar     -- :: LexM Char
       , putBackChar     -- :: Char -> LexM ()
       , getStream       -- :: LexM String
       , setStream       -- :: String -> LexM ()
       , lookupSymbol    -- :: String -> LexM (Maybe IDLToken)
       , lookupType      -- :: String -> LexM (Maybe IDLToken)
       , addBuiltinType  -- :: String -> LexM ()
       , addTypedef      -- :: String -> LexM ()
       , setTok
       , getTok
       
       , inSystemContext
       , getSystemContextFlag

       , cacheFilePath
       , alreadySeenFile
       , importFile
       
       , handleImportLib
       , slurpImports

       , thenLexM
       , returnLexM
       ) where

import Data.IORef
import qualified SymbolTable
import SrcLoc
import IDLToken
import IDLSyn
import PreProc
import Utils   ( tryOpen, dropSuffix )
import Opts    ( optVerbose, optConvertImportLibs )
import IO      ( hPutStrLn, stderr )
import Monad   ( when )
import Char    ( toLower )

-- components threaded by the monad (apart from
-- the IO token.)
data LexState
 = LexState {
      sym_table  :: SymbolTable.SymbolTable IDLToken,
      cur_tok    :: Maybe IDLToken, {- current token (for error msgs.) -}
      inp_stream :: String     {- input stream -}
  }

data LexEnv = 
  LexEnv {
    env_src_loc     :: SrcLoc,
    env_origsrc_loc :: SrcLoc,
    env_in_system   :: Bool,
    env_file_path   :: [FilePath],      -- search path for imported .idl files.
    env_file_cache  :: IORef [FilePath] -- already imported .idl files.
  }

newtype LexM a = LexM (  LexEnv -> LexState -> IO (a, LexState))

runLexM :: [String]
        -> String
        -> String 
        -> LexM a 
	-> IO a
runLexM path fname str (LexM m) = do
  var <- newIORef []
  let sl = (mkSrcLoc fname 1)
  (v, _) <- m (LexEnv sl sl False path var) 
              (LexState (SymbolTable.mkSymbolTable idlKeywords) Nothing str)
  return v

-- nested invocations of LexM actions share
-- the keyword part of the symbol table + the cache
-- of already seen files.
invokeLexM :: String -> String -> LexM a -> LexM a
invokeLexM fname ls (LexM m) =
 LexM (\ (LexEnv _ _ flg path var) (LexState symt tok cs) -> do
    let -- symt1 = SymbolTable.newContext symt
        sl    = (mkSrcLoc fname 1)
    (v, LexState symt2 _ _) 
      <- m (LexEnv sl sl flg path var)
	   (LexState symt tok ls)
    return (v, LexState symt2{-(SymbolTable.combineSyms symt symt2)-} tok cs))

ioToLexM :: IO a -> LexM a
ioToLexM act =
 LexM (\ _ st -> do
         v <- act
	 return (v, st))

cacheFilePath :: FilePath -> LexM ()
cacheFilePath f =
 LexM (\ (LexEnv{env_file_cache=fp}) st -> do
     ls <- readIORef fp
     writeIORef fp (f:ls)
     return ((), st))

alreadySeenFile :: FilePath -> LexM Bool
alreadySeenFile f =
 LexM (\ (LexEnv{env_file_cache=fp}) st -> do
     ls <- readIORef fp
     return (f `elem` ls, st))

incLineNo :: LexM a -> LexM a
incLineNo (LexM m) = 
 LexM (\ env@(LexEnv{env_src_loc=l}) st -> m (env{env_src_loc=incSrcLineNo l}) st)

setSrcLoc :: SrcLoc -> LexM a -> LexM a
setSrcLoc new_loc (LexM m) = LexM (\ env st -> m (env{env_src_loc=new_loc}) st)

inSystemContext :: Bool -> LexM a -> LexM a
inSystemContext flg (LexM m) = LexM (\ env st -> m (env{env_in_system=flg}) st)

getSystemContextFlag :: LexM Bool
getSystemContextFlag = LexM (\ env st -> return (env_in_system env, st))

getSrcLoc :: LexM SrcLoc
getSrcLoc = LexM (\ env st -> return (env_src_loc env, st))

getOrigSrcLoc :: LexM SrcLoc
getOrigSrcLoc = LexM (\ env st -> return (env_origsrc_loc env, st))

getPath :: LexM [FilePath]
getPath = LexM (\ env st -> return (env_file_path env, st))

isEOF :: LexM Bool
isEOF = LexM (\ _ st -> return (null (inp_stream st), st))

getNextChar :: LexM Char
getNextChar = 
  LexM (\ _ st ->
     case inp_stream st of
       (c:cs) -> return (c, st{inp_stream=cs})
       _      -> return (error "getNextChar: stream is empty", st))

putBackChar :: Char -> LexM ()
putBackChar c = LexM ( \ _ st -> return ((), st{inp_stream=c:inp_stream st}))

getStream :: LexM String
getStream = LexM (\ _ st -> return (inp_stream st, st))

setStream :: String -> LexM ()
setStream cs = LexM (\ _ st -> return ((), st{inp_stream=cs}))

lookupSymbol :: String -> LexM (Maybe IDLToken)
lookupSymbol str = 
  LexM (\ _ st -> return (SymbolTable.lookupSymbol (sym_table st) str, st))

lookupType :: String -> LexM (Maybe IDLToken)
lookupType str = 
  LexM (\ _ st -> return (SymbolTable.lookupType (sym_table st) str, st))

-- back door entry for adding new types after we've installed
-- the default set.
addBuiltinType :: String -> LexM ()
addBuiltinType str =
 LexM (\ _ st ->
     return ( ()
            , st{sym_table=SymbolTable.addKeyword (sym_table st) str (T_type str)}))

addTypedef :: String -> LexM ()
addTypedef str =
 LexM (\ _ st ->
       return (()
              , st{sym_table=SymbolTable.addType (sym_table st) str (T_type str)}))

setTok :: IDLToken -> LexM ()
setTok t = LexM (\ _ st -> return ((),st{cur_tok=Just t}))

getTok :: LexM (Maybe IDLToken)
getTok = LexM (\ _ st -> return (cur_tok st, st))

-----

thenLexM :: LexM a -> (a -> LexM b) -> LexM b
thenLexM (LexM m) n =
 LexM ( \ env st -> do
          (a, st1) <- m env st
	  let (LexM act) = n a
	  act env st1 )

returnLexM :: a -> LexM a
returnLexM v = LexM (\ _ st -> return (v, st) )

{- UNUSED
mapLexM :: (a -> b) -> LexM a -> LexM b
mapLexM f (LexM m) =
 LexM (\ env st -> do
   (x,st1) <- m env st
   return (f x, st1))
-}

instance Monad LexM where
  (>>=)  = thenLexM
  return = returnLexM

\end{code} \begin{code}
importFile :: String -> LexM (Maybe String)
importFile fname = do
  path  <- getPath 
  res   <- ioToLexM (tryOpen optVerbose path exts fname)
  case res of
    Nothing   -> do
        l   <- getSrcLoc
        ioToLexM (ioError
		     (userError  (show l ++": Unable to import "++ fname)))
        
    Just fn   -> do
        flg <- alreadySeenFile fn
        if flg
	 then do
           ioToLexM (when optVerbose (hPutStrLn stderr (show fn ++ " already loaded.")))
	   return Nothing -- already slurped this one.
	 else do
           cacheFilePath fn
	   res1 <- ioToLexM (preProcessFile fn)
	   ls   <- ioToLexM (readFile res1)
	   return (Just ls)
 where
  exts = [""]

\end{code} \begin{code}
handleImportLib :: LexM [Defn] -> String -> LexM Defn
handleImportLib parse str
 | not optConvertImportLibs = do
{- BEGIN_NOT_TLB_SUPPORT
     warningMsg ("ignoring importlib("++show str ++"): Type library imports not supported")
   END_NOT_TLB_SUPPORT -}
     return (ImportLib str) 
 | otherwise  = slurpImports parse [str']
  where
    str' = dropSuffix (map toLower str) ++ ".idl"
\end{code} \begin{code}

slurpImports :: LexM [Defn] -> [String] -> LexM Defn
slurpImports parse ls = do
  defss <- mapM (slurpImport parse) ls
  return (Import (zip ls defss))

--
-- Importing an IDL file means bringing the types and interfaces
-- into scope, no code is generated for the interfaces.
--
slurpImport :: LexM [a] -> String -> LexM [a]
slurpImport parse fname = do
  flg           <- alreadySeenFile fname
  if flg
   then return []
   else do
     mb_ls  <- importFile fname
     case mb_ls of
       Nothing -> return []
       Just ls -> invokeLexM fname ls (parse)
\end{code}