-- |
-- = 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 :: forall (m :: * -> *) s u. Monad m => ParsecT s u m Indentation
indentation = do
    SourcePos
pos <- ParsecT s u m SourcePos
forall (m :: * -> *) s u. Monad m => ParsecT s u m SourcePos
getPosition
    Indentation -> ParsecT s u m Indentation
forall a. a -> ParsecT s u m a
forall (m :: * -> *) a. Monad m => a -> m a
return (Indentation -> ParsecT s u m Indentation)
-> Indentation -> ParsecT s u m Indentation
forall a b. (a -> b) -> a -> b
$! Indentation {iLine :: Int
iLine = SourcePos -> Int
sourceLine SourcePos
pos, iColumn :: Int
iColumn = SourcePos -> Int
sourceColumn SourcePos
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 :: forall (m :: * -> *) s z u.
(Monad m, Stream s m z) =>
Indentation -> ParsecT s u m ()
indented Indentation
ref = do
    Indentation
pos <- ParsecT s u m Indentation
forall (m :: * -> *) s u. Monad m => ParsecT s u m Indentation
indentation
    Bool -> ParsecT s u m () -> ParsecT s u m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Indentation -> Int
iColumn Indentation
pos Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Indentation -> Int
iColumn Indentation
ref) (ParsecT s u m () -> ParsecT s u m ())
-> ParsecT s u m () -> ParsecT s u m ()
forall a b. (a -> b) -> a -> b
$ String -> ParsecT s u m ()
forall s (m :: * -> *) t u a.
Stream s m t =>
String -> ParsecT s u m a
unexpected (Indentation -> String
prettyIndentation Indentation
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 :: forall (m :: * -> *) s z u.
(Monad m, Stream s m z) =>
Indentation -> ParsecT s u m ()
sameOrIndented Indentation
ref = do
    -- This is equal to 'same <|> indented' but gives a cleaner error message.
    Indentation
pos <- ParsecT s u m Indentation
forall (m :: * -> *) s u. Monad m => ParsecT s u m Indentation
indentation
    Bool -> ParsecT s u m () -> ParsecT s u m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Indentation -> Int
iColumn Indentation
pos Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Indentation -> Int
iColumn Indentation
ref Bool -> Bool -> Bool
&& Indentation -> Int
iLine Indentation
pos Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
/= Indentation -> Int
iLine Indentation
ref) (ParsecT s u m () -> ParsecT s u m ())
-> ParsecT s u m () -> ParsecT s u m ()
forall a b. (a -> b) -> a -> b
$
        String -> ParsecT s u m ()
forall s (m :: * -> *) t u a.
Stream s m t =>
String -> ParsecT s u m a
unexpected (Indentation -> String
prettyIndentation Indentation
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 :: forall (m :: * -> *) s z u.
(Monad m, Stream s m z) =>
Indentation -> ParsecT s u m ()
same Indentation
ref = do
    Indentation
pos <- ParsecT s u m Indentation
forall (m :: * -> *) s u. Monad m => ParsecT s u m Indentation
indentation
    Bool -> ParsecT s u m () -> ParsecT s u m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Indentation -> Int
iLine Indentation
pos Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
/= Indentation -> Int
iLine Indentation
ref) (ParsecT s u m () -> ParsecT s u m ())
-> ParsecT s u m () -> ParsecT s u m ()
forall a b. (a -> b) -> a -> b
$ String -> ParsecT s u m ()
forall s (m :: * -> *) t u a.
Stream s m t =>
String -> ParsecT s u m a
unexpected String
"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 :: forall (m :: * -> *) s z u a.
(Monad m, Stream s m z) =>
ParsecT s u m a -> ParsecT s u m [a]
block ParsecT s u m a
p = do
    Indentation
ref <- ParsecT s u m Indentation
forall (m :: * -> *) s u. Monad m => ParsecT s u m Indentation
indentation
    ParsecT s u m a -> ParsecT s u m [a]
forall s (m :: * -> *) t u a.
Stream s m t =>
ParsecT s u m a -> ParsecT s u m [a]
many1 (Indentation -> ParsecT s u m ()
forall (m :: * -> *) s z u.
(Monad m, Stream s m z) =>
Indentation -> ParsecT s u m ()
checkIndent Indentation
ref ParsecT s u m () -> ParsecT s u m a -> ParsecT s u m a
forall a b. ParsecT s u m a -> ParsecT s u m b -> ParsecT s u m b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> ParsecT s u m a
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 :: forall (m :: * -> *) s z u.
(Monad m, Stream s m z) =>
Indentation -> ParsecT s u m ()
checkIndent Indentation
ref = do
    Indentation
pos <- ParsecT s u m Indentation
forall (m :: * -> *) s u. Monad m => ParsecT s u m Indentation
indentation
    Bool -> ParsecT s u m () -> ParsecT s u m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Indentation -> Int
iColumn Indentation
pos Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
/= Indentation -> Int
iColumn Indentation
ref) (ParsecT s u m () -> ParsecT s u m ())
-> ParsecT s u m () -> ParsecT s u m ()
forall a b. (a -> b) -> a -> b
$
        (ParsecT s u m () -> String -> ParsecT s u m ()
forall s u (m :: * -> *) a.
ParsecT s u m a -> String -> ParsecT s u m a
<?> Indentation -> String
prettyIndentation Indentation
ref String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" (started at line " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Indentation -> String
prettyLine Indentation
ref String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
")")
        (String -> ParsecT s u m ()
forall s (m :: * -> *) t u a.
Stream s m t =>
String -> ParsecT s u m a
unexpected (String -> ParsecT s u m ()) -> String -> ParsecT s u m ()
forall a b. (a -> b) -> a -> b
$ Indentation -> String
prettyIndentation Indentation
pos)

-- | Ensures that there is no indentation.
topLevel
    :: (Monad m, Stream s m z)
    => ParsecT s u m ()
topLevel :: forall (m :: * -> *) s z u.
(Monad m, Stream s m z) =>
ParsecT s u m ()
topLevel = do
    Indentation
pos <- ParsecT s u m Indentation
forall (m :: * -> *) s u. Monad m => ParsecT s u m Indentation
indentation
    Bool -> ParsecT s u m () -> ParsecT s u m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Indentation -> Int
iColumn Indentation
pos Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
1) (ParsecT s u m () -> ParsecT s u m ())
-> ParsecT s u m () -> ParsecT s u m ()
forall a b. (a -> b) -> a -> b
$ String -> ParsecT s u m ()
forall s (m :: * -> *) t u a.
Stream s m t =>
String -> ParsecT s u m a
unexpected String
"indentation"

-- | Ensures that there is at least some indentation.
notTopLevel
    :: (Monad m, Stream s m z)
    => ParsecT s u m ()
notTopLevel :: forall (m :: * -> *) s z u.
(Monad m, Stream s m z) =>
ParsecT s u m ()
notTopLevel = do
    Indentation
pos <- ParsecT s u m Indentation
forall (m :: * -> *) s u. Monad m => ParsecT s u m Indentation
indentation
    Bool -> ParsecT s u m () -> ParsecT s u m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Indentation -> Int
iColumn Indentation
pos Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
1) (ParsecT s u m () -> ParsecT s u m ())
-> ParsecT s u m () -> ParsecT s u m ()
forall a b. (a -> b) -> a -> b
$ String -> ParsecT s u m ()
forall s (m :: * -> *) t u a.
Stream s m t =>
String -> ParsecT s u m a
unexpected String
"top-level"