module Language.MicroLisp.Compiler
( compile
, CompileError(..)
) where
import Control.Monad.Except
import Data.Bifunctor
import Language.MicroLisp.SExprParser
import SECD.Types
data SF a = FF | SS a
deriving (Show, Eq)
data CompileError = CompileError
| ParseError
deriving Show
data LTerm = LInt Int
| LVar String
| LAdd LTerm LTerm
| LSub LTerm LTerm
| LApp LTerm LTerm
| LAbs String LTerm
deriving (Show, Eq)
data DBTerm = DBInt Int
| DBAdd DBTerm DBTerm
| DBSub DBTerm DBTerm
| DBApp DBTerm DBTerm
| DBAbs DBTerm
| DBIndex Int
deriving (Show, Eq)
parseTerm :: (MonadError CompileError m) => SExpr -> m LTerm
parseTerm (SAtom v) = return $ LVar v
parseTerm (SInt n) = return $ LInt n
parseTerm (SList [SAtom "+", t1, t2]) = do
t1' <- parseTerm t1
t2' <- parseTerm t2
return $ LAdd t1' t2'
parseTerm (SList [SAtom "-", t1, t2]) = do
t1' <- parseTerm t1
t2' <- parseTerm t2
return $ LSub t1' t2'
parseTerm (SList [SAtom "lambda", SList [SAtom var], t]) = do
t' <- parseTerm t
return $ LAbs var t'
parseTerm (SList [t1, t2]) = do
t1' <- parseTerm t1
t2' <- parseTerm t2
return $ LApp t1' t2'
parseTerm _ = throwError CompileError
toDeBrujin :: (MonadError CompileError m) => LTerm -> m DBTerm
toDeBrujin = toDeBrujin' [] where
toDeBrujin' env (LInt n) = return $ DBInt n
toDeBrujin' env (LAdd t1 t2) = do
t1' <- toDeBrujin' env t1
t2' <- toDeBrujin' env t2
return $ DBAdd t1' t2'
toDeBrujin' env (LSub t1 t2) = do
t1' <- toDeBrujin' env t1
t2' <- toDeBrujin' env t2
return $ DBSub t1' t2'
toDeBrujin' env (LAbs v t) = do
t' <- toDeBrujin' (v:env) t
return $ DBAbs t'
toDeBrujin' env (LApp t1 t2) = do
t1' <- toDeBrujin' env t1
t2' <- toDeBrujin' env t2
return $ DBApp t1' t2'
toDeBrujin' env (LVar v) =
case find v env of
SS n -> return $ DBIndex n
FF -> throwError CompileError
where
find v [] = FF
find v (v':rest) | v == v' = SS 0
| otherwise = case find v rest of
FF -> FF
SS n -> SS (n + 1)
compileToSECD :: (MonadError CompileError m) => DBTerm -> m [Command]
compileToSECD (DBInt n) = return [IInt n]
compileToSECD (DBAbs t) = do
c <- compileToSECD t
return [IClosure (c ++ [IReturn])]
compileToSECD (DBApp t1 t2) = do
c1 <- compileToSECD t1
c2 <- compileToSECD t2
return $ c1 ++ c2 ++ [IApply]
compileToSECD (DBAdd t1 t2) = do
c1 <- compileToSECD t1
c2 <- compileToSECD t2
return $ c2 ++ c1 ++ [IAdd]
compileToSECD (DBSub t1 t2) = do
c1 <- compileToSECD t1
c2 <- compileToSECD t2
return $ c2 ++ c1 ++ [ISub]
compileToSECD (DBIndex i) = return [IAccess i]
compile :: String -> Either CompileError [Command]
compile = readSExpr' >=> parseTerm >=> toDeBrujin >=> compileToSECD
where
readSExpr' :: String -> Either CompileError SExpr
readSExpr' = first (const ParseError) . readSExpr