typerbole-0.0.0.1: src/Control/Typecheckable.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-|
Module containing typechecking functions and data structures.
-}
module Control.Typecheckable (
-- * Typeclasses for Typechecking and Inference
Typecheckable(..)
, Inferable(..)
-- * Typing context and Type error structures
-- ** General use
, Typecheck
, runTypecheck
, expression
, environment
, (<><>)
-- ** ErrorContext
, ErrorContext(..)
, ErrorContext'
, throwErrorContext
, throwErrorContexts
, errorOfContext
, appendExprToEContexts
) where
import Data.Bifunctor
import Control.Monad.State
import Control.Monad.Except
import Data.Semigroup
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Data.List.NonEmpty as NE
import Control.Lens
import Data.Tree as Tree
{-|
Data type for an error context, holding the expression where
the error occurred, the environment when it did, and the error
itself.
-}
data ErrorContext env expr err = ErrorContext {
_expression :: [expr]
-- ^ A stack of expressions leading the the expression that
-- caused the error as the final element.
, _environment :: env
-- ^ The environment's state at the time of error.
, _errorOfContext :: err
-- ^ The error that occurred.
} deriving (Eq, Ord, Show)
makeLenses ''ErrorContext
{-|
ErrorContext but the environment is assumed to be the TypingContext of @t@.
-}
type ErrorContext' term t err = ErrorContext (TypingContext term t) (term t) err
instance Functor (ErrorContext env expr) where
fmap f = errorOfContext %~ f
{-|
Throw an error in an 'ErrorContext'.
-}
throwErrorContext exprStack err = throwErrorContexts [(exprStack, err)]
{-|
Throw a list of errors in `ErrorContext`'s.
-}
throwErrorContexts exprsAndErrs = do
-- Get the current environment at the time of the errors.
env <- get
-- For every error, create an ErrorContext with the environment 'env' and the given error
-- error and expression stack.
throwError ((\(exprStack, err) -> ErrorContext exprStack env err) <$> exprsAndErrs)
{-|
If there have been any errors, add the given expression to the head of
all the error contexts' expression stacks.
-}
appendExprToEContexts expr = flip catchError (throwError . fmap (expression %~ (expr :)))
{-|
A validation semigroup on Eithers, combining Left values when two appear.
-}
(<><>) :: (Semigroup s1, Semigroup s2) => Either s1 s2 -> Either s1 s2 -> Either s1 s2
(<><>) = curry $ \case
(Left a, Left b) -> Left (a <> b)
(a, b) -> (<>) <$> a <*> b
{-|
Typechecking type, uses the TypingContext as state and TypeError as an exception type.
-}
type Typecheck term t = ExceptT [TypeError term t] (State (TypingContext term t))
-- | runs a `Typecheck` action
runTypecheck :: st -> ExceptT e (State st) t -> Either e (st, t)
runTypecheck env = (\(e, st) -> (,) st <$> e ) . flip runState env . runExceptT
{-|
A typeclass for typechecking terms (@term@) with a typesystem (@t@).
-}
class Typecheckable (term :: * -> *) t where
{-|
The typing context, in type theory this is usually shown as 𝚪.
For a basic typechecker of the simply-typed lambda calculus this might contain
a mapping of variable and constant names to their types. This is left to be defined
for the implementer though as other typesystems might need extra information
in their contexts.
-}
type TypingContext term t :: *
{-|
The type error representation.
-}
type TypeError term t :: *
{-|
Given a typing context, typecheck an expression and either return a typeerror or the
type of the expression that was passed.
-}
typecheck :: TypingContext term t -- ^ The given context
-> term t -- ^ The expression to typecheck
-> Either [TypeError term t]
(TypingContext term t, t) -- ^ The result
class (Typecheckable term t) => Inferable term t where
{-|
The inference context, has a similar function to `TypingContext`
-}
type InferenceContext term t :: *
{-|
The inference error representation.
-}
type InferError term t :: *
infer :: InferenceContext term t -- ^ The given context
-> term (Maybe t) -- ^ The expression to infer from
-> Either [InferError term t]
(InferenceContext term t, term t) -- ^ The result
{-|
Get a typing context from an inference context.
-}
typingContext :: InferenceContext term t -> TypingContext term t