angle-0.8.1.0: src/Angle/Exec/Error.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-|
Module : Angle.Exec.Error
Description : Defines main error system used in Angle.
Copyright : Copyright (C) 2015 Ben Moon
License : GNU GPL, version 3
Maintainer : GuiltyDolphin@gmail.com
Stability : alpha
Defines functions for throwing, catching and handling errors that
occur in Angle.
-}
module Angle.Exec.Error
(
-- ** Type errors
typeAnnWrongErr
, typeConstrWrongReturnErr
, typeExpectConstrErr
, typeMismatchOpErr
, typeNotValidErr
, typeUnexpectedErr
, typeCastErr
-- ** Name errors
, assignToBuiltinErr
, nameNotDefinedErr
, nameNotDefinedFunErr
, nameNotDefinedLitErr
-- ** Call errors
, callBuiltinErr
, malformedSignatureErr
, wrongNumberOfArgumentsErr
-- ** Keyword errors
, returnFromGlobalErr
-- ** Value errors
, badRangeErr
, indexOutOfBoundsErr
, infiniteRangeErr
, nonEnumErr
, divideByZeroErr
-- ** Control-flow
, catchBreak
, catchContinue
, catchReturn
, throwBreak
, throwContinue
, throwReturn
-- ** User errors
, userErr
, errToKeyword
, genErrKeyword
-- ** IO errors
, alreadyExistsErr
, doesNotExistErr
, alreadyInUseErr
, deviceFullErr
, eofErr
, illegalOperationErr
, permissionErr
-- ** Syntax errors
, syntaxErr
, readErr
-- ** Include errors
, noSuchFileErr
, badSyntaxErr
-- ** Classes, base types and basic functions
, AngleError
, CanError(..)
, CanErrorWithPos(..)
, ExecError
, throwError
, throwImplementationErr
, throwExecError
) where
import Control.Monad.Except
import Data.Function (on)
import Data.Maybe (catMaybes)
import qualified System.IO.Error as IO
import Angle.Scanner
import Angle.Types.Lang
-- | Instances are types that can throw Angle errors.
class (Monad m) => CanError (m :: * -> *) where
throwAE :: AngleError -> m a
catchAE :: m a -> (AngleError -> m a) -> m a
instance CanError (Either AngleError) where
throwAE = Left
catchAE e f = case e of
r@(Right _) -> r
Left l -> f l
-- | Instances are types that can throw Angle errors and provide
-- information about the position at which the error occurred.
class (CanError m) => CanErrorWithPos m where
getErrorPos :: m SourceRef
getErrorSource :: m String
getErrorFile :: m (Maybe FilePath)
-- FIXME: This should maybe just have the line numbers?
-- Then could perhaps not need the separate position.
getErrorCallStack :: m [(LangIdent, Stmt)]
-- getErrorStmt :: m Stmt
getErrorCall :: m (LangIdent, Stmt)
-- | Errors that can be caught by the user via the try...catch
-- construct.
class KWError e where
errToKeyword :: e -> LangIdent
genErrKeyword :: e -> LangIdent
-- ^ General keyword that can be used to handle multiple exceptions
-- of this type.
-- | General error structure.
data AngleError = ExecError
{ execErrSourceRef :: SourceRef
, execErrErr :: ExecError
, execErrSourceText :: String
, execErrFile :: Maybe FilePath
, execErrStack :: [(LangIdent, Stmt)]
, execErrCall :: (LangIdent, Stmt)
}
| ImplementationError String
deriving (Eq)
implementationErr :: String -> AngleError
implementationErr = ImplementationError
instance Show AngleError where
show (ImplementationError x) = "Implementation error: " ++ x
show (ExecError { execErrErr=ee
, execErrSourceRef=SourceRef (start,_)
, execErrSourceText=es
, execErrFile=ef
, execErrStack=eStack
, execErrCall=eCall
})
= cEf ++ cEStack ++ cECall ++ cEk ++ cEe
where
cEf = case ef of
Nothing -> ""
Just f -> "In file: " ++ f ++ "\n"
cECall = showCall eCall
cEStack = concatMap showCall (reverse eStack)
cEk = concat ["\n(:", showSyn $ errToKeyword ee, ")\n"]
cEp = show start ++ "\n"
cEe = show ee
showCall (c,s) = r
where
r = " line " ++ ln ++ ", in " ++ f ++ "\n" ++ t
t = " " ++ st ++ "\n"
ln = show (stmtLine s + 1)
f = showSyn c
st = let (SingleStmt s' _) = s; tos = showSyn s' in if last tos == '\n' then init tos else tos
stmtLine (SingleStmt _ (SourceRef (l,_))) = lineNo l
instance KWError AngleError where
errToKeyword (ImplementationError x) = error $ "(Attempt to handle) Implementation error: " ++ show x
errToKeyword (ExecError { execErrErr = e }) = errToKeyword e
genErrKeyword (ImplementationError x) = error $ "(Attempt to handle) Implementation error: " ++ show x
genErrKeyword (ExecError { execErrErr = e }) = genErrKeyword e
-- | Base for errors that occur during execution of code.
data ExecError = TypeError TypeError
| NameError NameError
| CallError CallError
| ValueError ValueError
| KeywordError KeywordError
| EIOError EIOError
| UserError LangIdent
| SynError SynError
| IncludeError IncludeError
| ControlException ControlException
deriving (Eq)
-- | Expression produced an invalid type.
typeErr :: TypeError -> ExecError
typeErr = TypeError
-- | Issue with identifier.
nameErr :: NameError -> ExecError
nameErr = NameError
-- | Bad function call.
callErr :: CallError -> ExecError
callErr = CallError
-- | Invalid literal.
valueErr :: ValueError -> ExecError
valueErr = ValueError
-- | Misused keyword.
keywordErr :: KeywordError -> ExecError
keywordErr = KeywordError
-- | IO Error.
eioErr :: EIOError -> ExecError
eioErr = EIOError
-- | Custom exceptions.
userErr :: LangIdent -> ExecError
userErr = UserError
-- | Syntax errors.
synErr :: SynError -> ExecError
synErr = SynError
-- | Control flow
controlException :: ControlException -> ExecError
controlException = ControlException
-- | Including files
includeErr :: IncludeError -> ExecError
includeErr = IncludeError
instance Show ExecError where
show (TypeError e) = "wrong type in expression: " ++ show e
show (NameError v) = "name error: " ++ show v
show (CallError x) = "call error: " ++ show x
show (ValueError x) = "value error: " ++ show x
show (EIOError e) = "io error: " ++ show e
show (UserError (LangIdent x)) = "user error: " ++ x
show (KeywordError x) = "keyword error: " ++ show x
show (SynError e) = "syntax error:\n" ++ show e
show (ControlException e) = "control: " ++ show e
show (IncludeError e) = "include: " ++ show e
instance KWError ExecError where
errToKeyword (TypeError e) = errToKeyword e
errToKeyword (NameError e) = errToKeyword e
errToKeyword (CallError e) = errToKeyword e
errToKeyword (ValueError e) = errToKeyword e
errToKeyword (EIOError e) = errToKeyword e
errToKeyword (UserError e) = e
errToKeyword (KeywordError e) = errToKeyword e
errToKeyword (SynError e) = errToKeyword e
errToKeyword (ControlException e) = errToKeyword e
errToKeyword (IncludeError e) = errToKeyword e
genErrKeyword (TypeError e) = genErrKeyword e
genErrKeyword (NameError e) = genErrKeyword e
genErrKeyword (CallError e) = genErrKeyword e
genErrKeyword (ValueError e) = genErrKeyword e
genErrKeyword (EIOError e) = genErrKeyword e
genErrKeyword (UserError{}) = LangIdent "user"
genErrKeyword (KeywordError e) = genErrKeyword e
genErrKeyword (SynError e) = genErrKeyword e
genErrKeyword (ControlException e) = genErrKeyword e
genErrKeyword (IncludeError e) = genErrKeyword e
-- | Errors involving types.
data TypeError = TypeMismatch LangType LangType
| TypeUnexpected LangType LangType
| TypeNotValid LangType
| TypeCast LangType LangType
| TypeMismatchOp LangType LangType
| TypeExpectConstr LangLit LangIdent
| TypeConstrWrongReturn LangIdent LangType
| TypeAnnWrong AnnType AnnType
deriving (Eq)
-- | Wrong type has been passed and required type is known.
typeUnexpectedErr :: LangType -> LangType -> ExecError
typeUnexpectedErr t1 = typeErr . TypeUnexpected t1
-- | Wrong type has been passed and required type is not known.
typeNotValidErr :: LangLit -> ExecError
typeNotValidErr = typeErr . TypeNotValid . typeOf
-- | Expecting types to be the same.
typeMismatchOpErr :: LangLit -> LangLit -> ExecError
typeMismatchOpErr x y = typeErr $ (TypeMismatchOp `on` typeOf) x y
-- | Expecting value to satisfy given parameter constraint.
typeExpectConstrErr :: LangLit -> LangIdent -> ExecError
typeExpectConstrErr cls = typeErr . TypeExpectConstr cls
-- | Function used as parameter constraint did not return boolean
-- value.
typeConstrWrongReturnErr :: LangIdent -> LangType -> ExecError
typeConstrWrongReturnErr cls = typeErr . TypeConstrWrongReturn cls
-- | Value did not satisfy given annotation constraint.
typeAnnWrongErr :: AnnType -> AnnType -> ExecError
typeAnnWrongErr e = typeErr . TypeAnnWrong e
-- | Cannot coerce one type to another
typeCastErr :: LangType -> LangType -> ExecError
typeCastErr t1 = typeErr . TypeCast t1
instance Show TypeError where
show (TypeMismatch l r) = "type mismatch: got (" ++ show l ++ ", " ++ show r ++ ") but both types should be the same"
show (TypeUnexpected l r) = "unexpected type: " ++ show l ++ ", expecting: " ++ show r
show (TypeNotValid l) = "type not valid : " ++ show l
show (TypeCast l r) = "cannot convert " ++ show l ++ " to " ++ show r
show (TypeMismatchOp l r) = "cannot perform operation on types " ++ show l ++ " and " ++ show r
show (TypeExpectConstr v c) = "expecting value that satisfies function '" ++ showSyn c ++ "' but got: " ++ showSyn v
show (TypeConstrWrongReturn c t) = "bad class: " ++ showSyn c ++ ", expecting return value of type bool, but got " ++ show t
show (TypeAnnWrong t1 t2) = "bad type in function call, expecting " ++ show t1 ++ " but got " ++ show t2
instance KWError TypeError where
errToKeyword (TypeMismatch{}) = LangIdent "typeMismatch"
errToKeyword (TypeUnexpected{}) = LangIdent "typeUnexpected"
errToKeyword (TypeNotValid{}) = LangIdent "typeNotValid"
errToKeyword (TypeCast{}) = LangIdent "typeCast"
errToKeyword (TypeMismatchOp{}) = LangIdent "typeMismatchOp"
errToKeyword (TypeExpectConstr{}) = LangIdent "typeExpectConstr"
errToKeyword (TypeConstrWrongReturn{}) = LangIdent "typeConstrWrongReturn"
errToKeyword (TypeAnnWrong{}) = LangIdent "typeAnnWrong"
genErrKeyword _ = LangIdent "typeError"
-- | Errors involving identifiers and names.
data NameError = NameNotDefined LangIdent
| NameNotDefinedFun LangIdent
| NameNotDefinedLit LangIdent
| NameNotOp LangIdent
| AssignToBuiltin LangIdent (Maybe String)
deriving (Eq)
-- | Given identifier has no definition.
nameNotDefinedErr :: LangIdent -> ExecError
nameNotDefinedErr = nameErr . NameNotDefined
-- | Given identifier has no lambda assigned.
nameNotDefinedFunErr :: LangIdent -> ExecError
nameNotDefinedFunErr = nameErr . NameNotDefinedFun
-- | Given identifier has no value assigned.
nameNotDefinedLitErr :: LangIdent -> ExecError
nameNotDefinedLitErr = nameErr . NameNotDefinedLit
-- | Attempt to re-assign a builtin variable.
assignToBuiltinErr :: LangIdent -> Maybe String -> ExecError
assignToBuiltinErr name = nameErr . AssignToBuiltin name
instance Show NameError where
show (NameNotDefined (LangIdent name)) = "not in scope: " ++ name
show (NameNotDefinedFun (LangIdent name)) = "not a valid function: " ++ name
show (NameNotDefinedLit (LangIdent name)) = "no value assigned: " ++ name
show (NameNotOp (LangIdent name)) = "not a valid operator: " ++ name
show (AssignToBuiltin (LangIdent name) reason) = "cannot assign to builtin: " ++ name
++ maybe "" ("\n" ++) reason
instance KWError NameError where
errToKeyword (NameNotDefined{}) = LangIdent "nameNotDefined"
errToKeyword (NameNotDefinedFun{}) = LangIdent "nameNotDefinedFun"
errToKeyword (NameNotDefinedLit{}) = LangIdent "nameNotDefinedLit"
errToKeyword (NameNotOp{}) = LangIdent "nameNotOp"
errToKeyword (AssignToBuiltin{}) = LangIdent "assignToBuiltin"
genErrKeyword _ = LangIdent "nameError"
-- | Errors involving operator and function calls.
data CallError = WrongNumberOfArguments Int Int
| BuiltIn String
| MalformedSignature String
deriving (Eq)
-- | Attempted to pass an invalid number of arguments to a function.
wrongNumberOfArgumentsErr :: Int -> Int -> ExecError
wrongNumberOfArgumentsErr expect = callErr . WrongNumberOfArguments expect
-- | Error when calling builtin.
callBuiltinErr :: String -> ExecError
callBuiltinErr = callErr . BuiltIn
-- | Order of arguments not valid in scenario.
malformedSignatureErr :: String -> ExecError
malformedSignatureErr = callErr . MalformedSignature
instance Show CallError where
show (WrongNumberOfArguments x y) = "wrong number of arguments: expected " ++ show x ++ " but got " ++ show y
show (BuiltIn x) = "builtin: " ++ x
show (MalformedSignature x) = "malformed signature: " ++ x
instance KWError CallError where
errToKeyword (WrongNumberOfArguments{}) = LangIdent "wrongNumberOfArguments"
errToKeyword (BuiltIn{}) = LangIdent "builtin"
errToKeyword (MalformedSignature{}) = LangIdent "malformedSignature"
genErrKeyword _ = LangIdent "callError"
-- | Errors involving keywords.
data KeywordError = ReturnFromGlobal
deriving (Eq)
-- | Return keyword used in wrong place.
returnFromGlobalErr :: ExecError
returnFromGlobalErr = keywordErr ReturnFromGlobal
instance Show KeywordError where
show ReturnFromGlobal = "return from outermost scope"
instance KWError KeywordError where
errToKeyword ReturnFromGlobal = LangIdent "returnFromGlobal"
genErrKeyword _ = LangIdent "keywordError"
-- | Raise a 'ExecError' into an 'AngleError'.
throwExecError :: (CanErrorWithPos m, Monad m) => ExecError -> m a
throwExecError e = do
errPosRef <- getErrorPos
errSource <- getErrorSource
errPath <- getErrorFile
errStack <- getErrorCallStack
errCall <- getErrorCall
throwAE ExecError { execErrSourceRef = errPosRef
, execErrSourceText = errSource
, execErrErr = e
, execErrFile = errPath
, execErrStack = errStack
, execErrCall = errCall
}
-- | Throw a fatal error caused by the internals of the implementation.
throwImplementationErr :: (CanError m) => String -> m a
throwImplementationErr = throwAE . implementationErr
-- | Errors involving literals.
data ValueError = IndexOutOfBoundsError Int
| BadRange LangType (Maybe LangType) (Maybe LangType)
| InfiniteRange
| NonEnum LangType
| DivideByZero
deriving (Eq)
-- | Attempt to access a non-existant index of a list.
indexOutOfBoundsErr :: Int -> ExecError
indexOutOfBoundsErr = valueErr . IndexOutOfBoundsError
-- | Types are not uniform in range.
badRangeErr :: LangType -> Maybe LangType -> Maybe LangType -> ExecError
badRangeErr t1 t2 = valueErr . BadRange t1 t2
-- | Range is infinite.
infiniteRangeErr :: ExecError
infiniteRangeErr = valueErr InfiniteRange
-- | Non-enumerable type used where enumerable expected.
nonEnumErr :: LangType -> ExecError
nonEnumErr = valueErr . NonEnum
-- | Attempted division by zero.
divideByZeroErr :: ExecError
divideByZeroErr = valueErr DivideByZero
instance Show ValueError where
show (IndexOutOfBoundsError x) = "index out of bounds: " ++ show x
show (BadRange t1 t2 t3) = "bad range: all types should be same, but got: " ++ show t1 ++ concatMap ((", "++) . show) (catMaybes [t2,t3])
show InfiniteRange = "infinite range"
show (NonEnum t) = "expected enumerable type but got: " ++ show t
show DivideByZero = "attempted division by zero"
instance KWError ValueError where
errToKeyword (IndexOutOfBoundsError{}) = LangIdent "indexOutOfBounds"
errToKeyword (BadRange{}) = LangIdent "badRange"
errToKeyword InfiniteRange = LangIdent "infiniteRange"
errToKeyword (NonEnum{}) = LangIdent "nonEnum"
errToKeyword DivideByZero = LangIdent "divideByZero"
genErrKeyword _ = LangIdent "valueError"
-- | Errors that occur during IO operations.
data EIOError = AlreadyExists IOError
| DoesNotExist IOError
| AlreadyInUse IOError
| DeviceFull IOError
| EOF IOError
| IllegalOperation IOError
| Permission IOError
deriving (Eq)
alreadyExistsErr :: IOError -> ExecError
alreadyExistsErr = eioErr . AlreadyExists
doesNotExistErr :: IOError -> ExecError
doesNotExistErr = eioErr . DoesNotExist
alreadyInUseErr :: IOError -> ExecError
alreadyInUseErr = eioErr . AlreadyInUse
deviceFullErr :: IOError -> ExecError
deviceFullErr = eioErr . DeviceFull
eofErr :: IOError -> ExecError
eofErr = eioErr . EOF
illegalOperationErr :: IOError -> ExecError
illegalOperationErr = eioErr . IllegalOperation
permissionErr :: IOError -> ExecError
permissionErr = eioErr . Permission
eioErrorShow :: IOError -> String
eioErrorShow e = l ++ fn ++ s
where
fn = case IO.ioeGetFileName e of
Nothing -> ""
Just n -> show n ++ ": "
l = case IO.ioeGetLocation e of
"openFile" -> "open file: "
"readFile" -> "read file: "
"readProcess: runInteractiveProcess: exec" -> "shell: "
x -> x ++ ": "
s = IO.ioeGetErrorString e
instance Show EIOError where
show (AlreadyExists e) = eioErrorShow e
show (DoesNotExist e) = eioErrorShow e
show (AlreadyInUse e) = eioErrorShow e
show (DeviceFull e) = eioErrorShow e
show (EOF e) = eioErrorShow e
show (IllegalOperation e) = eioErrorShow e
show (Permission e) = eioErrorShow e
instance KWError EIOError where
errToKeyword (AlreadyExists{}) = LangIdent "alreadyExists"
errToKeyword (DoesNotExist{}) = LangIdent "doesNotExist"
errToKeyword (AlreadyInUse{}) = LangIdent "alreadyInUse"
errToKeyword (DeviceFull{}) = LangIdent "deviceFull"
errToKeyword (EOF{}) = LangIdent "eof"
errToKeyword (IllegalOperation{}) = LangIdent "illegalOperation"
errToKeyword (Permission{}) = LangIdent "permission"
genErrKeyword _ = LangIdent "ioError"
-- | Represents errors that occur during run-time parsing.
data SynError = SyntaxError ParseError
| ReadError String
deriving (Eq)
-- | Error when parsing text intended to be code.
syntaxErr :: ParseError -> ExecError
syntaxErr = synErr . SyntaxError
-- | Error when parsing non-code.
readErr :: String -> ExecError
readErr = synErr . ReadError
instance Show SynError where
show (SyntaxError e) = show e
show (ReadError e) = "could not read: " ++ show e
instance KWError SynError where
errToKeyword (SyntaxError{}) = LangIdent "syntax"
errToKeyword (ReadError{}) = LangIdent "read"
genErrKeyword _ = LangIdent "syntaxError"
data IncludeError = NoSuchFile FilePath
| BadSyntax ParseError
deriving (Eq)
noSuchFileErr :: FilePath -> ExecError
noSuchFileErr = includeErr . NoSuchFile
badSyntaxErr :: ParseError -> ExecError
badSyntaxErr = includeErr . BadSyntax
instance Show IncludeError where
show (NoSuchFile fp) = "no such file: " ++ fp
show (BadSyntax e) = show e
instance KWError IncludeError where
errToKeyword (NoSuchFile{}) = LangIdent "doesNotExist"
errToKeyword (BadSyntax{}) = LangIdent "syntax"
genErrKeyword _ = LangIdent "include"
-- | Used for control-flow within the language.
--
-- These should never make it to the user, but instead be caught in
-- internal code.
data ControlException = ControlReturn LangLit
| ControlBreak (Maybe LangLit)
| ControlContinue
deriving (Eq)
controlReturn :: LangLit -> ExecError
controlReturn = controlException . ControlReturn
controlBreak :: Maybe LangLit -> ExecError
controlBreak = controlException . ControlBreak
controlContinue :: ExecError
controlContinue = controlException ControlContinue
instance Show ControlException where
show (ControlReturn{}) = "return made it to top level"
show (ControlBreak{}) = "break outside of loop"
show ControlContinue = "continue outside of loop"
instance KWError ControlException where
errToKeyword (ControlReturn{}) = LangIdent "return"
errToKeyword (ControlBreak{}) = LangIdent "break"
errToKeyword ControlContinue = LangIdent "continue"
genErrKeyword _ = LangIdent "controlException"
-- | Used for the 'return' keyword.
throwReturn :: (CanErrorWithPos m) => LangLit -> m a
throwReturn = throwExecError . controlReturn -- throwAE . controlReturn
-- | Used for the 'break' keyword.
throwBreak :: (CanErrorWithPos m) => Maybe LangLit -> m a
throwBreak = throwExecError . controlBreak -- throwAE . controlBreak
-- | Used for the 'continue' keyword.
throwContinue :: (CanErrorWithPos m) => m a
throwContinue = throwExecError controlContinue -- throwAE controlContinue
-- | Catch 'ControlReturn', but allow other errors to propagate.
catchReturn :: (CanError m) => m a -> (LangLit -> m a) -> m a
catchReturn ex h = ex `catchAE`
(\e -> case e of
ExecError { execErrErr = ControlException (ControlReturn v) } -> h v
err -> throwAE err)
-- | Catch 'ControlBreak', but allow other errors to propagate.
catchBreak :: (CanError m) => m a -> (Maybe LangLit -> m a) -> m a
catchBreak ex h
= ex `catchAE`
(\e -> case e of
ExecError
{ execErrErr = ControlException (ControlBreak v)
} -> h v
err -> throwAE err)
-- | Catch 'ControlContinue', but allow other errors to propagate.
catchContinue :: (CanError m) => m a -> m a -> m a
catchContinue ex v
= ex `catchAE`
(\e -> case e of
ExecError
{ execErrErr = ControlException ControlContinue } -> v
err -> throwAE err)