parsec1-1.0.0.8: Text/ParserCombinators/Parsec/Error.hs
{- |
Module : Text.ParserCombinators.Parsec.Error
Copyright : (c) Daan Leijen 1999-2001
License : BSD-style (see the file LICENSE)
Maintainer : Christian Maeder <chr.maeder@web.de>
Stability : provisional
Portability : portable
Parse errors
-}
module Text.ParserCombinators.Parsec.Error
( Message (SysUnExpect, UnExpect, Expect, Message)
, ParseError
, addErrorMessage
, errorIsUnknown
, errorMessages
, errorPos
, mergeError
, messageCompare
, messageEq
, messageString
, newErrorMessage
, newErrorUnknown
, setErrorMessage
, setErrorPos
, showErrorMessages
) where
import Data.List (nub, sortBy)
import Text.ParserCombinators.Parsec.Pos
{- | This abstract data type represents parse error messages. There are
four kinds of messages:
> data Message = SysUnExpect String
> | UnExpect String
> | Expect String
> | Message String
The fine distinction between different kinds of parse errors allows
the system to generate quite good error messages for the user. It
also allows error messages that are formatted in different
languages. Each kind of message is generated by different combinators:
A 'SysUnExpect' message is automatically generated by the
'Text.Parsec.Combinator.satisfy' combinator. The argument is the
unexpected input.
A 'UnExpect' message is generated by the 'Text.Parsec.Prim.unexpected'
combinator. The argument describes the
unexpected item.
A 'Expect' message is generated by the 'Text.Parsec.Prim.<?>'
combinator. The argument describes the expected item.
A 'Message' message is generated by the 'fail'
combinator. The argument is some general parser message. -}
data Message
= SysUnExpect !String -- library generated unexpect
| UnExpect !String -- unexpected something
| Expect !String -- expecting something
| Message !String -- raw message
deriving Eq
messageToEnum :: Message -> Int
messageToEnum msg = case msg of
SysUnExpect _ -> 0
UnExpect _ -> 1
Expect _ -> 2
Message _ -> 3
messageCompare :: Message -> Message -> Ordering
messageCompare msg1 msg2
= compare (messageToEnum msg1) (messageToEnum msg2)
-- | Extract the message string from an error message
messageString :: Message -> String
messageString msg = case msg of
SysUnExpect s -> s
UnExpect s -> s
Expect s -> s
Message s -> s
messageEq :: Message -> Message -> Bool
messageEq msg1 msg2
= messageCompare msg1 msg2 == EQ
{- | The abstract data type @ParseError@ represents parse errors. It
provides the source position ('SourcePos') of the error
and a list of error messages ('Message'). A @ParseError@
can be returned by the function 'Text.Parsec.Prim.parse'. @ParseError@ is an
instance of the 'Show' class. -}
data ParseError = ParseError !SourcePos [Message] deriving Eq
-- | Extracts the source position from the parse error
errorPos :: ParseError -> SourcePos
errorPos (ParseError pos _msgs) = pos
-- | Extracts the list of error messages from the parse error
errorMessages :: ParseError -> [Message]
errorMessages (ParseError _pos msgs) = sortBy messageCompare msgs
errorIsUnknown :: ParseError -> Bool
errorIsUnknown (ParseError _pos msgs) = null msgs
{- ---------------------------------------------------------
Create parse errors
--------------------------------------------------------- -}
newErrorUnknown :: SourcePos -> ParseError
newErrorUnknown pos = ParseError pos []
newErrorMessage :: Message -> SourcePos -> ParseError
newErrorMessage msg pos = ParseError pos [msg]
addErrorMessage :: Message -> ParseError -> ParseError
addErrorMessage msg (ParseError pos msgs) = ParseError pos (msg : msgs)
setErrorPos :: SourcePos -> ParseError -> ParseError
setErrorPos pos (ParseError _ msgs) = ParseError pos msgs
setErrorMessage :: Message -> ParseError -> ParseError
setErrorMessage msg (ParseError pos msgs)
= ParseError pos (msg : filter (not . messageEq msg) msgs)
mergeError :: ParseError -> ParseError -> ParseError
mergeError e1@(ParseError pos1 msgs1) e2@(ParseError pos2 msgs2)
-- prefer meaningful errors
| null msgs2 && not (null msgs1) = e1
| null msgs1 && not (null msgs2) = e2
| otherwise
= case pos1 `compare` pos2 of
-- select the longest match
EQ -> ParseError pos1 (msgs1 ++ msgs2)
GT -> e1
LT -> e2
{- ---------------------------------------------------------
Show Parse Errors
--------------------------------------------------------- -}
instance Show ParseError where
show err = show (errorPos err) ++ ":" ++
showErrorMessages "or" "unknown parse error"
"expecting" "unexpected" "end of input"
(errorMessages err)
-- | Language independent show function
showErrorMessages ::
String -> String -> String -> String -> String -> [Message] -> String
showErrorMessages msgOr msgUnknown msgExpecting msgUnExpected msgEndOfInput msgs
| null msgs = msgUnknown
| otherwise = concatMap ("\n" ++) $ clean
[showSysUnExpect, showUnExpect, showExpect, showMessages]
where
(sysUnExpect, msgs1) = span (messageEq (SysUnExpect "")) msgs
(unExpect, msgs2) = span (messageEq (UnExpect "")) msgs1
(expect, messages) = span (messageEq (Expect "")) msgs2
showExpect = showMany msgExpecting expect
showUnExpect = showMany msgUnExpected unExpect
showSysUnExpect
| not (null unExpect) || null sysUnExpect = ""
| null firstMsg = msgUnExpected ++ " " ++ msgEndOfInput
| otherwise = msgUnExpected ++ " " ++ firstMsg
where
firstMsg = messageString (head sysUnExpect)
showMessages = showMany "" messages
-- helpers
showMany pre es = case clean (map messageString es) of
[] -> ""
ms | null pre -> commasOr ms
| otherwise -> pre ++ " " ++ commasOr ms
commasOr ms = case ms of
[] -> ""
[m] -> m
_ -> commaSep (init ms) ++ " " ++ msgOr ++ " " ++ last ms
commaSep = seperate ", " . clean
seperate sep ms = case ms of
[] -> ""
[m] -> m
m : rs -> m ++ sep ++ seperate sep rs
clean = nub . filter (not . null)