packages feed

formura-1.0: src/Formura/Compiler.hs

{-# LANGUAGE FlexibleContexts, FlexibleInstances, GeneralizedNewtypeDeriving, MultiParamTypeClasses, StandaloneDeriving, TemplateHaskell #-}

module Formura.Compiler where

import           Control.Applicative
import           Control.Lens
import           Control.Monad.Trans.Either
import           Control.Monad.Morph
import           Control.Monad.RWS
import qualified Data.Set as S
import qualified Text.Trifecta as P
import qualified Text.PrettyPrint.ANSI.Leijen as Ppr

import Formura.Language.Combinator

type CompilerError = Ppr.Doc

-- | The state of the compiler.
data CompilerSyntacticState =
  CompilerSyntacticState
  { _compilerFocus :: Maybe Metadata
  , _compilerStage :: String }

makeClassy ''CompilerSyntacticState

defaultCompilerSyntacticState :: CompilerSyntacticState
defaultCompilerSyntacticState = CompilerSyntacticState Nothing ""

-- | The formura compiler monad.
newtype CompilerMonad r w s a = CompilerMonad
  { runCompilerMonad :: EitherT CompilerError (RWST r w s IO) a}
              deriving (Functor, Applicative, Monad, MonadIO,
                        MonadReader r, MonadState s, MonadWriter w)

compileErrMsg :: (HasCompilerSyntacticState s, MonadState s m) => P.Err -> m Ppr.Doc
compileErrMsg errMsg = do
    stg <- use compilerStage
    foc <- use compilerFocus
    let errMsg2
          | stg == "" = errMsg
          | otherwise = errMsg & P.footnotes %~ (++ [Ppr.text ("when " ++ stg)])
    case foc of
      Nothing -> return $ P.explain P.emptyRendering $ errMsg2
      Just (Metadata r b e) -> return $
        P.explain (P.addSpan b e $ r) $ errMsg2


-- | Throw an error, possibly with user-friendly diagnostics of the current compiler state.
instance (HasCompilerSyntacticState s, Monoid w) => P.Errable (CompilerMonad r w s) where
  raiseErr errMsg = do
    msg2 <- compileErrMsg errMsg
    CompilerMonad $ left $ msg2

-- | Run the compiler and get the result.
evalCompiler :: CompilerMonad r w s a -> r -> s -> IO (Either CompilerError a)
evalCompiler m r s = fmap fst $ evalRWST (runEitherT $ runCompilerMonad m) r s

-- | Run the compiler and get the state and written results.
--   Note that you get some partial results even when the compilation aborts.
runCompiler :: CompilerMonad r w s a -> r -> s -> IO (Either CompilerError a,s,w)
runCompiler m r s = runRWST (runEitherT $ runCompilerMonad m) r s

-- | Run compiler, changing the reader and the state.
withCompiler :: Monoid w => (r' -> s -> (r,s)) -> CompilerMonad r w s a -> CompilerMonad r' w s a
withCompiler f = CompilerMonad . (hoist $ withRWST f) . runCompilerMonad

-- | Raise doc as an error
raiseDoc :: P.Errable m => Ppr.Doc ->  m a
raiseDoc doc = P.raiseErr $ P.Err (Just doc) [] S.empty

-- | The monadic algebra, specialized to the compiler monad.
type CompilerAlgebra r w s f a = f a -> CompilerMonad r w s a

-- | The compiler-monad-specific fold, that takes track of the syntax tree traversed.
compilerMFold :: (Monoid w, Traversable f, HasCompilerSyntacticState s) =>
           CompilerAlgebra r w s f (Lang g) -> Fix f -> CompilerMonad r w s (Lang g)
compilerMFold k (In meta x) = do
  r1 <- traverse (compilerMFold k) x
  compilerFocus %= (meta <|>)
  r2 <- k r1
  return $ propagateMetadata meta r2

-- | The compiler-monad-specific fold, that takes track of the syntax tree traversed and produces non-language results.
compilerMFoldout :: (Monoid w, Traversable f, HasCompilerSyntacticState s) =>
           CompilerAlgebra r w s f g -> Fix f -> CompilerMonad r w s g
compilerMFoldout k (In meta x) = do
  r1 <- traverse (compilerMFoldout k) x
  compilerFocus %= (meta <|>)
  r2 <- k r1
  return $ r2

-- | The compiler-monad-specific pure foldout, that takes track of the syntax tree traversed.
compilerFoldout :: (Monoid w, Traversable f, HasCompilerSyntacticState s) =>
           Algebra f (CompilerMonad r w s a) -> Fix f -> CompilerMonad r w s a
compilerFoldout k (In meta x) = do
  -- TODO: in order for this compilerFocus to work properly, the compiler state
  -- needs to be a reader monad rather than state monad.
  compilerFocus %= (meta <|>)
  k $ fmap (compilerFoldout k) x