module DesugarSI (loadProg, desugarProg, desugarBlock, desugarComm) where import System.FilePath (pathSeparator) import SourceParser (parseProg) import Lexer (scan) import PureSyntax import InterSyntax import SugarSyntax import qualified Data.Set as S import Control.Monad.Except import Control.Exception (try) import Control.Exception.Base (SomeException) import Control.Arrow (left) -- Given its directory, base name, a macro call stack and macro seed, load a -- program from disk, returning it with the macro seed after unparsing etc. -- Also check for macro recursion or non-matching file+prog names, failing if -- either is found. loadProg :: FilePath -> -- The directory we look for files to open FilePath -> -- The file we want to load [FilePath] -> -- The 'macro stack'. Prevents recursion thusly: every time we -- recurse into loading a new macro, we push the name of the -- previous macro on the stack, and then check that the -- new macro is not already somewhere on the stack. If it is, -- we've found recursion so we quit. Otherwise, we pop the -- current macro off the stack when we finish it. ExceptT String IO InProgram -- The loaded program loadProg dir fileBaseName macroStack = if fileBaseName `elem` macroStack then throwError "Recursive macros detected." else do fileStr <- safeReadFile $ dir ++ pathSeparator : fileBaseName ++ ".while" let fileTokens = scan fileStr fileBaseName suProg <- parseProg fileTokens case (suProg, macroStack) of ( SuProgram n _ _ _ , _ ) | nameName n /= fileBaseName -> throwError $ "Program name (" ++ nameName n ++ ") must match file base name." ( _ , [] ) -> desugarProg dir ( fileBaseName : macroStack ) suProg ( SuProgram n r b w , _ ) -> let namesToInit = S.delete r $ S.insert w $ namesSuBlock b initCode = map ( \n -> SuAssign ( Info ( "+IMPL+", 0 ) ) n ( Lit ENil ) ) (S.toList namesToInit) in desugarProg dir ( fileBaseName : macroStack ) ( SuProgram n r ( initCode ++ b ) w ) -- Safely read a file in the ExceptT String IO monad. safeReadFile :: FilePath -> ExceptT String IO String safeReadFile file = do tryFile <- lift $ try $ readFile file case tryFile of Left exc -> throwError $ show (exc :: SomeException) Right fileContents -> return fileContents -- Desugar a program, that is, convert it to pure while syntax desugarProg :: FilePath -> [FilePath] -> SuProgram -> ExceptT String IO InProgram desugarProg dir macroStack ( SuProgram n r blk w ) = do desugaredBlk <- desugarBlock dir macroStack blk return $ InProgram n r desugaredBlk w -- Desugar a block desugarBlock :: FilePath -> [FilePath] -> SuBlock -> ExceptT String IO InBlock desugarBlock dir _ [] = return [] desugarBlock dir macroStack ( c : cs ) = do desugaredC <- desugarComm dir macroStack c desugaredCs <- desugarBlock dir macroStack cs return $ desugaredC ++ desugaredCs -- Desugar a command desugarComm :: FilePath -> -- Path to search for macro files [FilePath] -> -- Macro call stack SuCommand -> -- The command to desugar ExceptT String IO InBlock desugarComm dir macroStack suComm = case suComm of SuAssign i x exp -> return [ InAssign i x exp ] SuWhile i gd blk -> do desugaredBlk <- desugarBlock dir macroStack blk return [ InWhile i gd desugaredBlk ] SuIfElse i gd bt bf -> do desugaredBT <- desugarBlock dir macroStack bt desugaredBF <- desugarBlock dir macroStack bf return [ InIfElse i gd desugaredBT desugaredBF ] Macro i x f e -> do prog <- loadProg dir f macroStack return $ [ InAssign i ( inReadVar prog ) e ] ++ inBlock prog ++ [ InAssign i x ( Var ( inWriteVar prog ) ) ] Switch i e cases def -> do desugaredDef <- desugarBlock dir macroStack def desugaredCases <- sequence $ map ( \( matchE , blk ) -> do desugaredBlk <- desugarBlock dir macroStack blk return ( matchE , desugaredBlk ) ) cases return $ [ InSwitch i e desugaredCases desugaredDef ]