indents-0.5.0.0: src/Text/Parsec/Indent.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Text.Parsec.Indent (
-- $doc
-- * Types
IndentT, IndentParserT, IndentParser, runIndent,
runIndentParserT, runIndentParser,
-- * Blocks
withBlock, withBlock', block,
-- * Indentation Checking
indented, same, sameOrIndented, checkIndent,
topLevel, notTopLevel,
withPos,
-- * Paired characters
indentBrackets, indentAngles, indentBraces, indentParens,
-- * Line Fold Chaining
-- | Any chain using these combinators must used with 'withPos'
(<+/>), (<-/>), (<*/>), (<?/>), Optional(..)
) where
import Control.Monad (ap, liftM2)
import Control.Monad.Identity (Identity, runIdentity)
import Control.Monad.Reader (ReaderT, ask, local, runReaderT)
import Text.Parsec
import qualified Text.Parsec.Indent.Explicit as Explicit
import Text.Parsec.Indent.Internal
import Text.Parsec.Token
-- $doc
-- A module to construct indentation aware parsers. Many programming
-- language have indentation based syntax rules e.g. python and Haskell.
-- This module exports combinators to create such parsers.
--
-- The input source can be thought of as a list of tokens. Abstractly
-- each token occurs at a line and a column and has a width. The column
-- number of a token measures is indentation. If t1 and t2 are two tokens
-- then we say that indentation of t1 is more than t2 if the column
-- number of occurrence of t1 is greater than that of t2.
--
-- Currently this module supports two kind of indentation based syntactic
-- structures which we now describe:
--
-- [Block] A block of indentation /c/ is a sequence of tokens with
-- indentation at least /c/. Examples for a block is a where clause of
-- Haskell with no explicit braces.
--
-- [Line fold] A line fold starting at line /l/ and indentation /c/ is a
-- sequence of tokens that start at line /l/ and possibly continue to
-- subsequent lines as long as the indentation is greater than /c/. Such
-- a sequence of lines need to be /folded/ to a single line. An example
-- is MIME headers. Line folding based binding separation is used in
-- Haskell as well.
referenceIndentation :: Monad m => IndentParserT s u m Indentation
referenceIndentation = ask
-- | Indentation transformer.
type IndentT m = ReaderT Indentation m
-- | Indentation sensitive parser type. Usually @m@ will be 'Identity' as with
-- any 'ParsecT'. In that case you can use the simpler 'IndentParser' type.
type IndentParserT s u m a = ParsecT s u (IndentT m) a
-- | A simplified 'IndentParserT'.
type IndentParser s u a = IndentParserT s u Identity a
-- | @ 'withBlock' f a p @ parses @ a @
-- followed by an indented block of @ p @
-- combining them with @ f @
withBlock
:: (Monad m, Stream s (IndentT m) z)
=> (a -> [b] -> c)
-> IndentParserT s u m a
-> IndentParserT s u m b
-> IndentParserT s u m c
withBlock f a p = withPos $ do
r1 <- a
r2 <- option [] (indented >> block p)
return (f r1 r2)
-- | Like 'withBlock', but throws away initial parse result
withBlock'
:: (Monad m, Stream s (IndentT m) z)
=> IndentParserT s u m a
-> IndentParserT s u m b
-> IndentParserT s u m [b]
withBlock' = withBlock (flip const)
-- | Parses only when indented past the level of the reference
indented
:: (Monad m, Stream s (IndentT m) z)
=> IndentParserT s u m ()
indented = referenceIndentation >>= Explicit.indented
-- | Parses only when indented past the level of the reference or on the same line
sameOrIndented
:: (Monad m, Stream s (IndentT m) z)
=> IndentParserT s u m ()
sameOrIndented = referenceIndentation >>= Explicit.sameOrIndented
-- | Parses only on the same line as the reference
same
:: (Monad m, Stream s (IndentT m) z)
=> IndentParserT s u m ()
same = referenceIndentation >>= Explicit.same
-- | Parses a block of lines at the same indentation level
block
:: (Monad m, Stream s (IndentT m) z)
=> IndentParserT s u m a
-> IndentParserT s u m [a]
block = Explicit.block
-- | Parses using the current location for indentation reference
withPos
:: (Monad m, Stream s (IndentT m) z)
=> IndentParserT s u m a
-> IndentParserT s u m a
withPos x = do
p <- Explicit.indentation
local (const p) x
-- | Ensures the current indentation level matches that of the reference
checkIndent
:: (Monad m, Stream s (IndentT m) z)
=> IndentParserT s u m ()
checkIndent = referenceIndentation >>= Explicit.checkIndent
-- | Ensures that there is no indentation.
topLevel
:: (Monad m, Stream s (IndentT m) z)
=> IndentParserT s u m ()
topLevel = Explicit.topLevel
-- | Ensures that there is at least some indentation.
notTopLevel
:: (Monad m, Stream s (IndentT m) z)
=> IndentParserT s u m ()
notTopLevel = Explicit.notTopLevel
-- | Run the result of an indentation sensitive parse
runIndentT :: Monad m => IndentT m a -> m a
runIndentT i = runReaderT i (Indentation 1 1)
-- | Simplified version of 'runIndentT'.
runIndent :: IndentT Identity a -> a
runIndent = runIdentity . runIndentT
-- | This is a convenience function which wraps 'runIndentT' and 'runParserT'.
runIndentParserT
:: (Monad m, Stream s (IndentT m) t)
=> IndentParserT s u m a -- ^ Parser to run
-> u -- ^ User state
-> SourceName -- ^ Source name
-> s -- ^ Input for the parser
-> m (Either ParseError a) -- ^ Result
runIndentParserT parser u source txt =
runIndentT (runParserT parser u source txt)
-- | This is another convenience function. Use this instead of
-- 'runIndentParserT' if 'm' is 'Identity'.
runIndentParser
:: Stream s (IndentT Identity) t
=> IndentParser s u a -- ^ Parser to run
-> u -- ^ User state
-> SourceName -- ^ Source name
-> s -- ^ Input for the parser
-> Either ParseError a -- ^ Result
runIndentParser parser u source txt =
runIdentity (runIndentParserT parser u source txt)
-- | '<+/>' is to indentation sensitive parsers what 'ap' is to monads
(<+/>)
:: (Monad m, Stream s (IndentT m) z)
=> IndentParserT s u m (a -> b)
-> IndentParserT s u m a
-> IndentParserT s u m b
a <+/> b = ap a (sameOrIndented >> b)
-- | '<-/>' is like '<+/>', but doesn't apply the function to the parsed value
(<-/>)
:: (Monad m, Stream s (IndentT m) z)
=> IndentParserT s u m a
-> IndentParserT s u m b
-> IndentParserT s u m a
a <-/> b = liftM2 const a (sameOrIndented >> b)
-- | Like '<+/>' but applies the second parser many times
(<*/>)
:: (Monad m, Stream s (IndentT m) z)
=> IndentParserT s u m ([a] -> b)
-> IndentParserT s u m a
-> IndentParserT s u m b
a <*/> b = ap a (many (sameOrIndented >> b))
-- | Datatype used to optional parsing
data Optional s u m a = Opt a (IndentParserT s u m a)
-- | Like '<+/>' but applies the second parser optionally using the 'Optional' datatype
(<?/>)
:: (Monad m, Stream s (IndentT m) z)
=> IndentParserT s u m (a -> b)
-> (Optional s u m a)
-> IndentParserT s u m b
(<?/>) a (Opt b c) = ap a (option b (sameOrIndented >> c))
-- | parses with surrounding brackets
indentBrackets
:: (Monad m, Stream s (IndentT m) z)
=> GenTokenParser s u (IndentT m)
-> IndentParserT s u m a
-> IndentParserT s u m a
indentBrackets lexer p = withPos $ return id <-/> symbol lexer "[" <+/> p <-/> symbol lexer "]"
-- | parses with surrounding angle brackets
indentAngles
:: (Monad m, Stream s (IndentT m) z)
=> GenTokenParser s u (IndentT m)
-> IndentParserT s u m a
-> IndentParserT s u m a
indentAngles lexer p = withPos $ return id <-/> symbol lexer "<" <+/> p <-/> symbol lexer ">"
-- | parses with surrounding braces
indentBraces
:: (Monad m, Stream s (IndentT m) z)
=> GenTokenParser s u (IndentT m)
-> IndentParserT s u m a
-> IndentParserT s u m a
indentBraces lexer p = withPos $ return id <-/> symbol lexer "{" <+/> p <-/> symbol lexer "}"
-- | parses with surrounding parentheses
indentParens
:: (Monad m, Stream s (IndentT m) z)
=> GenTokenParser s u (IndentT m)
-> IndentParserT s u m a
-> IndentParserT s u m a
indentParens lexer p = withPos $ return id <-/> symbol lexer "(" <+/> p <-/> symbol lexer ")"