indents-0.5.0.0: src/Text/Parsec/Indent/Explicit.hs
-- |
-- = Introduction
--
-- The 'Text.Parsec.Indent' module provides an /implicit/ indentation parser.
-- For complex parsers split over many functions, this requires a good
-- understanding from the programmer which indentation is being referenced.
--
-- This module fixes that problem by explicitly passing around indentation as a
-- first-class value. This commonly makes complex parsers less concise but
-- easier to understand.
--
-- = The problem with reference indentation
--
-- Many functions from that module (@indented@, @checkIndent@,
-- @sameOrIndented@...) use a /reference indentation/. This
-- /reference indentation/ is stored in the @IndentParserT@ type. It can be set
-- by calling @withPos@, but also by other functions such as @block@.
--
-- Consider the following code snippet:
--
-- > import qualified Text.Parsec.Indent as I
-- >
-- > p = I.withPos $ do
-- > foo
-- > I.block $ I.checkIndent >> bar
--
-- @I.checkIndent@, in this case, will always succeed, since we are comparing
-- the current indentation to the implicit reference indentation set by
-- @I.block@, not by @I.withPos@, and there is no way to do the latter.
--
-- = Explicit indentation
--
-- This module makes indentation first-class rather than implicit, so we can
-- provide a good implementation without relying on @withPos@:
--
-- > import qualified Text.Parsec.Indent as I
-- > import qualified Text.Parsec.Indent.Explicit as EI
-- >
-- > p = do
-- > indentation <- EI.indentation
-- > foo
-- > I.block $ EI.checkIndent indentation >> bar
--
-- In order to preserve backwards-compatibility, the names in this module are
-- chosen to match their counterparts in "Text.Parsec.Indent".
module Text.Parsec.Indent.Explicit
( -- * Indentation type
Indentation
-- * Obtaining a reference implementation
, indentation
-- * Indentation-based parser combinators
, indented
, sameOrIndented
, same
, block
, checkIndent
, topLevel
, notTopLevel
) where
import Control.Monad (unless, when)
import Text.Parsec
import Text.Parsec.Indent.Internal
-- | Obtain the current indentation, to be used as a reference later.
indentation :: Monad m => ParsecT s u m Indentation
indentation = do
pos <- getPosition
return $! Indentation {iLine = sourceLine pos, iColumn = sourceColumn pos}
-- | Parses only when indented past the level of the reference
indented
:: (Monad m, Stream s m z)
=> Indentation -- ^ Reference indentation
-> ParsecT s u m ()
indented ref = do
pos <- indentation
when (iColumn pos <= iColumn ref) $ unexpected (prettyIndentation pos)
-- | Parses only when indented past the level of the reference or on the same line
sameOrIndented
:: (Monad m, Stream s m z)
=> Indentation -- ^ Reference indentation
-> ParsecT s u m ()
sameOrIndented ref = do
-- This is equal to 'same <|> indented' but gives a cleaner error message.
pos <- indentation
when (iColumn pos <= iColumn ref && iLine pos /= iLine ref) $
unexpected (prettyIndentation pos)
-- | Parses only on the same line as the reference
same
:: (Monad m, Stream s m z)
=> Indentation -- ^ Reference indentation
-> ParsecT s u m ()
same ref = do
pos <- indentation
when (iLine pos /= iLine ref) $ unexpected "line break"
-- | Parses a block of lines at the same indentation level starting at the
-- current position
block
:: (Monad m, Stream s m z)
=> ParsecT s u m a
-> ParsecT s u m [a]
block p = do
ref <- indentation
many1 (checkIndent ref >> p)
-- | Ensures the current indentation level matches that of the reference
checkIndent
:: (Monad m, Stream s m z)
=> Indentation -- ^ Reference indentation
-> ParsecT s u m ()
checkIndent ref = do
pos <- indentation
when (iColumn pos /= iColumn ref) $
(<?> prettyIndentation ref ++ " (started at line " ++ prettyLine ref ++ ")")
(unexpected $ prettyIndentation pos)
-- | Ensures that there is no indentation.
topLevel
:: (Monad m, Stream s m z)
=> ParsecT s u m ()
topLevel = do
pos <- indentation
unless (iColumn pos == 1) $ unexpected "indentation"
-- | Ensures that there is at least some indentation.
notTopLevel
:: (Monad m, Stream s m z)
=> ParsecT s u m ()
notTopLevel = do
pos <- indentation
when (iColumn pos == 1) $ unexpected "top-level"