hwhile-0.1.1.0: src/lib/DesugarSI.hs
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 ]