lib: revise comments for "final" parse errors
- also simplify their implementation a bit
This commit is contained in:
parent
2c9c4ebf79
commit
a8d642d5b5
@ -16,24 +16,24 @@ module Text.Megaparsec.Custom (
|
|||||||
customErrorBundlePretty,
|
customErrorBundlePretty,
|
||||||
|
|
||||||
|
|
||||||
-- * Final parse error types
|
-- * "Final" parse errors
|
||||||
FinalParseError,
|
FinalParseError,
|
||||||
FinalParseError',
|
FinalParseError',
|
||||||
FinalParseErrorBundle,
|
FinalParseErrorBundle,
|
||||||
FinalParseErrorBundle',
|
FinalParseErrorBundle',
|
||||||
|
|
||||||
-- * Constructing final parse errors
|
-- * Constructing "final" parse errors
|
||||||
finalError,
|
finalError,
|
||||||
finalFancyFailure,
|
finalFancyFailure,
|
||||||
finalFail,
|
finalFail,
|
||||||
finalCustomFailure,
|
finalCustomFailure,
|
||||||
|
|
||||||
-- * Handling errors from include files with final parse errors
|
-- * Pretty-printing "final" parse errors
|
||||||
parseIncludeFile,
|
finalErrorBundlePretty,
|
||||||
attachSource,
|
attachSource,
|
||||||
|
|
||||||
-- * Pretty-printing final parse errors
|
-- * Handling parse errors from include files with "final" parse errors
|
||||||
finalErrorBundlePretty,
|
parseIncludeFile,
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
@ -138,33 +138,50 @@ customErrorBundlePretty errBundle =
|
|||||||
finds f = asum . map f . toList
|
finds f = asum . map f . toList
|
||||||
|
|
||||||
|
|
||||||
--- * Final parse error types
|
--- * "Final" parse errors
|
||||||
|
|
||||||
-- | A parse error type intended for throwing parse errors without the
|
|
||||||
-- possiblity of backtracking. Intended for use as the error type in an
|
|
||||||
-- 'ExceptT' layer of the parser. The 'ExceptT' layer is responsible for
|
|
||||||
-- handling include files, so this type also records a stack of include
|
|
||||||
-- files in order to report the stack in parse errors.
|
|
||||||
--
|
--
|
||||||
-- In order to pretty-print our custom parse errors, we must bundle them
|
-- | A type representing "final" parse errors that cannot be backtracked
|
||||||
-- with their full source text and filepaths (the 'FinalBundleWithStack'
|
-- from and are guaranteed to halt parsing. The anti-backtracking
|
||||||
-- constructor). However, when an error is thrown from within a parser, we
|
-- behaviour is implemented by an 'ExceptT' layer in the parser's monad
|
||||||
-- do not have access to the full source, so we must hold the parse error
|
-- stack, using this type as the 'ExceptT' error type.
|
||||||
-- (the 'FinalError' constructor) until it can be joined with the source
|
--
|
||||||
-- text and its filepath by the parser's caller.
|
-- We have three goals for this type:
|
||||||
|
-- (1) it should be possible to convert any parse error into a "final"
|
||||||
|
-- parse error,
|
||||||
|
-- (2) it should be possible to take a parse error thrown from an include
|
||||||
|
-- file and re-throw it in the parent file, and
|
||||||
|
-- (3) the pretty-printing of "final" parse errors should be consistent
|
||||||
|
-- with that of ordinary parse errors, but should also report a stack of
|
||||||
|
-- files for errors thrown from include files.
|
||||||
|
--
|
||||||
|
-- In order to pretty-print a "final" parse error (goal 3), it must be
|
||||||
|
-- bundled with include filepaths and its full source text. When a "final"
|
||||||
|
-- parse error is thrown from within a parser, we do not have access to
|
||||||
|
-- the full source, so we must hold the parse error until it can be joined
|
||||||
|
-- with its source (and include filepaths, if it was thrown from an
|
||||||
|
-- include file) by the parser's caller.
|
||||||
|
--
|
||||||
|
-- A parse error with include filepaths and its full source text is
|
||||||
|
-- represented by the 'FinalParseErrorBundle' type, while a parse error in
|
||||||
|
-- need of either include filepaths, full source text, or both is
|
||||||
|
-- represented by the 'FinalParseError' type.
|
||||||
|
|
||||||
data FinalParseError' e
|
data FinalParseError' e
|
||||||
|
-- a parse error thrown as a "final" parse error
|
||||||
= FinalError (ParseError Text e)
|
= FinalError (ParseError Text e)
|
||||||
|
-- a parse error obtained from running a parser, e.g. using 'runParserT'
|
||||||
| FinalBundle (ParseErrorBundle Text e)
|
| FinalBundle (ParseErrorBundle Text e)
|
||||||
|
-- a parse error thrown from an include file
|
||||||
| FinalBundleWithStack (FinalParseErrorBundle' e)
|
| FinalBundleWithStack (FinalParseErrorBundle' e)
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
type FinalParseError = FinalParseError' CustomErr
|
type FinalParseError = FinalParseError' CustomErr
|
||||||
|
|
||||||
-- A 'Monoid' instance is necessary for 'ExceptT (FinalParseError' e)' to
|
-- We need a 'Monoid' instance for 'FinalParseError' so that 'ExceptT
|
||||||
-- be an instance of Alternative and MonadPlus, which are required for the
|
-- FinalParseError m' is an instance of Alternative and MonadPlus, which
|
||||||
-- use of e.g. the 'many' parser combinator. This monoid instance simply
|
-- is needed to use some parser combinators, e.g. 'many'.
|
||||||
-- takes the first (left-most) error.
|
--
|
||||||
|
-- This monoid instance simply takes the first (left-most) error.
|
||||||
|
|
||||||
instance Semigroup (FinalParseError' e) where
|
instance Semigroup (FinalParseError' e) where
|
||||||
e <> _ = e
|
e <> _ = e
|
||||||
@ -174,12 +191,16 @@ instance Monoid (FinalParseError' e) where
|
|||||||
S.singleton (ErrorFail "default parse error")
|
S.singleton (ErrorFail "default parse error")
|
||||||
mappend = (<>)
|
mappend = (<>)
|
||||||
|
|
||||||
-- | A type bundling a 'ParseError' with its full source file and a stack
|
-- | A type bundling a 'ParseError' with its full source text, filepath,
|
||||||
-- of include file paths (for pretty printing).
|
-- and stack of include files. Suitable for pretty-printing.
|
||||||
|
--
|
||||||
|
-- Megaparsec's 'ParseErrorBundle' type already bundles a parse error with
|
||||||
|
-- its full source text and filepath, so we just add a stack of include
|
||||||
|
-- files.
|
||||||
|
|
||||||
data FinalParseErrorBundle' e = FinalParseErrorBundle'
|
data FinalParseErrorBundle' e = FinalParseErrorBundle'
|
||||||
{ finalErrorBundle :: ParseErrorBundle Text e
|
{ finalErrorBundle :: ParseErrorBundle Text e
|
||||||
, sourceFileStack :: NE.NonEmpty FilePath
|
, includeFileStack :: [FilePath]
|
||||||
} deriving (Show)
|
} deriving (Show)
|
||||||
|
|
||||||
type FinalParseErrorBundle = FinalParseErrorBundle' CustomErr
|
type FinalParseErrorBundle = FinalParseErrorBundle' CustomErr
|
||||||
@ -192,7 +213,7 @@ type FinalParseErrorBundle = FinalParseErrorBundle' CustomErr
|
|||||||
finalError :: ParseError Text e -> FinalParseError' e
|
finalError :: ParseError Text e -> FinalParseError' e
|
||||||
finalError = FinalError
|
finalError = FinalError
|
||||||
|
|
||||||
-- | Like 'fancyFailure', but as a "final" parse error.
|
-- | Like megaparsec's 'fancyFailure', but as a "final" parse error.
|
||||||
|
|
||||||
finalFancyFailure
|
finalFancyFailure
|
||||||
:: (MonadParsec e s m, MonadError (FinalParseError' e) m)
|
:: (MonadParsec e s m, MonadError (FinalParseError' e) m)
|
||||||
@ -207,76 +228,88 @@ finalFail
|
|||||||
:: (MonadParsec e s m, MonadError (FinalParseError' e) m) => String -> m a
|
:: (MonadParsec e s m, MonadError (FinalParseError' e) m) => String -> m a
|
||||||
finalFail = finalFancyFailure . S.singleton . ErrorFail
|
finalFail = finalFancyFailure . S.singleton . ErrorFail
|
||||||
|
|
||||||
-- | Like 'customFailure', but as a "final" parse error.
|
-- | Like megaparsec's 'customFailure', but as a "final" parse error.
|
||||||
|
|
||||||
finalCustomFailure
|
finalCustomFailure
|
||||||
:: (MonadParsec e s m, MonadError (FinalParseError' e) m) => e -> m a
|
:: (MonadParsec e s m, MonadError (FinalParseError' e) m) => e -> m a
|
||||||
finalCustomFailure = finalFancyFailure . S.singleton . ErrorCustom
|
finalCustomFailure = finalFancyFailure . S.singleton . ErrorCustom
|
||||||
|
|
||||||
|
|
||||||
--- * Handling errors from include files with "final" parse errors
|
--- * Pretty-printing "final" parse errors
|
||||||
|
|
||||||
-- Some care must be taken for sources to be attached to the right parse
|
-- | Pretty-print a "final" parse error: print the stack of include files,
|
||||||
-- errors when dealing with include files, so we capture the necessary
|
-- then apply the pretty-printer for parse error bundles. Note that
|
||||||
-- procedure in this function.
|
-- 'attachSource' must be used on a "final" parse error before it can be
|
||||||
|
-- pretty-printed.
|
||||||
|
|
||||||
parseIncludeFile
|
finalErrorBundlePretty :: FinalParseErrorBundle' CustomErr -> String
|
||||||
:: forall st m a. Monad m
|
finalErrorBundlePretty bundle =
|
||||||
=> StateT st (ParsecT CustomErr Text (ExceptT FinalParseError m)) a
|
concatMap showIncludeFilepath (includeFileStack bundle)
|
||||||
-> st
|
<> customErrorBundlePretty (finalErrorBundle bundle)
|
||||||
-> FilePath
|
|
||||||
-> Text
|
|
||||||
-> StateT st (ParsecT CustomErr Text (ExceptT FinalParseError m)) a
|
|
||||||
parseIncludeFile parser initState filepath text =
|
|
||||||
catchError parser' handler
|
|
||||||
where
|
where
|
||||||
parser' = do
|
showIncludeFilepath path = "in file included from " <> path <> ",\n"
|
||||||
eResult <- lift $ lift $
|
|
||||||
runParserT (evalStateT parser initState) filepath text
|
|
||||||
case eResult of
|
|
||||||
Left parseErrorBundle -> throwError $ FinalBundle parseErrorBundle
|
|
||||||
Right result -> pure result
|
|
||||||
|
|
||||||
handler e = throwError $ FinalBundleWithStack $ attachSource filepath text e
|
|
||||||
|
|
||||||
|
-- | Supply a filepath and source text to a "final" parse error so that it
|
||||||
|
-- can be pretty-printed. You must ensure that you provide the appropriate
|
||||||
|
-- source text and filepath.
|
||||||
|
|
||||||
attachSource
|
attachSource
|
||||||
:: FilePath -> Text -> FinalParseError' e -> FinalParseErrorBundle' e
|
:: FilePath -> Text -> FinalParseError' e -> FinalParseErrorBundle' e
|
||||||
attachSource filePath sourceText finalParseError = case finalParseError of
|
attachSource filePath sourceText finalParseError = case finalParseError of
|
||||||
|
|
||||||
FinalError parseError ->
|
-- A parse error thrown directly with the 'FinalError' constructor
|
||||||
let bundle = ParseErrorBundle
|
-- requires both source and filepath.
|
||||||
{ bundleErrors = parseError NE.:| []
|
FinalError parseError ->
|
||||||
, bundlePosState = initialPosState filePath sourceText }
|
let bundle = ParseErrorBundle
|
||||||
in FinalParseErrorBundle'
|
{ bundleErrors = parseError NE.:| []
|
||||||
{ finalErrorBundle = bundle
|
, bundlePosState = initialPosState filePath sourceText }
|
||||||
, sourceFileStack = filePath NE.:| [] }
|
in FinalParseErrorBundle'
|
||||||
|
{ finalErrorBundle = bundle
|
||||||
|
, includeFileStack = [] }
|
||||||
|
|
||||||
FinalBundle peBundle -> FinalParseErrorBundle'
|
-- A 'ParseErrorBundle' already has the appropriate source and filepath
|
||||||
{ finalErrorBundle = peBundle
|
-- and so needs neither.
|
||||||
, sourceFileStack = filePath NE.:| [] }
|
FinalBundle peBundle -> FinalParseErrorBundle'
|
||||||
|
{ finalErrorBundle = peBundle
|
||||||
|
, includeFileStack = [] }
|
||||||
|
|
||||||
FinalBundleWithStack fpeBundle -> fpeBundle
|
-- A parse error from a 'FinalParseErrorBundle' was thrown from an
|
||||||
{ sourceFileStack = filePath NE.<| sourceFileStack fpeBundle }
|
-- include file, so we add the filepath to the stack.
|
||||||
|
FinalBundleWithStack fpeBundle -> fpeBundle
|
||||||
|
{ includeFileStack = filePath : includeFileStack fpeBundle }
|
||||||
|
|
||||||
|
|
||||||
--- * Pretty-printing final parse errors
|
--- * Handling parse errors from include files with "final" parse errors
|
||||||
|
|
||||||
-- | Pretty-print a "final" parse error: print the stack of include files,
|
-- | Parse a file with the given parser and initial state, discarding the
|
||||||
-- then apply the pretty-printer for custom parse errors.
|
-- final state and re-throwing any parse errors as "final" parse errors.
|
||||||
|
|
||||||
finalErrorBundlePretty :: FinalParseErrorBundle' CustomErr -> String
|
parseIncludeFile
|
||||||
finalErrorBundlePretty bundle =
|
:: Monad m
|
||||||
concatMap printIncludeFile (NE.init (sourceFileStack bundle))
|
=> StateT st (ParsecT CustomErr Text (ExceptT FinalParseError m)) a
|
||||||
<> customErrorBundlePretty (finalErrorBundle bundle)
|
-> st
|
||||||
|
-> FilePath
|
||||||
|
-> Text
|
||||||
|
-> StateT st (ParsecT CustomErr Text (ExceptT FinalParseError m)) a
|
||||||
|
parseIncludeFile parser initialState filepath text =
|
||||||
|
catchError parser' handler
|
||||||
where
|
where
|
||||||
printIncludeFile path = "in file included from " <> path <> ",\n"
|
parser' = do
|
||||||
|
eResult <- lift $ lift $
|
||||||
|
runParserT (evalStateT parser initialState) filepath text
|
||||||
|
case eResult of
|
||||||
|
Left parseErrorBundle -> throwError $ FinalBundle parseErrorBundle
|
||||||
|
Right result -> pure result
|
||||||
|
|
||||||
|
-- Attach source and filepath of the include file to its parse errors
|
||||||
|
handler e = throwError $ FinalBundleWithStack $ attachSource filepath text e
|
||||||
|
|
||||||
|
|
||||||
--- * Helpers
|
--- * Helpers
|
||||||
|
|
||||||
-- The "tab width" and "line prefix" are taken from the defaults defined
|
-- Like megaparsec's 'initialState', but instead for 'PosState'. Used when
|
||||||
-- in 'initialState'.
|
-- constructing 'ParseErrorBundle's. The values for "tab width" and "line
|
||||||
|
-- prefix" are taken from 'initialState'.
|
||||||
|
|
||||||
initialPosState :: FilePath -> Text -> PosState Text
|
initialPosState :: FilePath -> Text -> PosState Text
|
||||||
initialPosState filePath sourceText = PosState
|
initialPosState filePath sourceText = PosState
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user