indents 0.4.0.1 → 0.5.0.0
raw patch · 5 files changed
+183/−59 lines, 5 filesdep ~tasty
Dependency ranges changed: tasty
Files
- CHANGELOG.md +3/−0
- indents.cabal +5/−3
- src/Text/Parsec/Indent.hs +17/−56
- src/Text/Parsec/Indent/Explicit.hs +137/−0
- src/Text/Parsec/Indent/Internal.hs +21/−0
CHANGELOG.md view
@@ -1,6 +1,9 @@ Changelog ========= +- 0.5.0.0 (2018-06-22)+ * Add the `Text.Parsec.Indent.Explicit` module+ - 0.4.0.1 (2017-12-26) * Bump `tasty` and `tasty-hunit` dependencies
indents.cabal view
@@ -1,12 +1,12 @@ Name: indents-Version: 0.4.0.1+Version: 0.5.0.0 Synopsis: indentation sensitive parser-combinators for parsec Description: This library provides functions for use in parsing indentation sensitive contexts. It parses blocks of lines all indented to the same level as well as lines continued at an indented level below. Homepage: http://github.com/jaspervdj/indents License: BSD3 License-file: LICENSE Author: Sam Anklesaria, Jasper Van der Jeugt-Maintainer: jaspervdj@gmail.com+Maintainer: Jasper Van der Jeugt <m@jaspervdj.be> Category: Text, Parsing Build-type: Simple Cabal-version: >=1.8@@ -28,6 +28,8 @@ Exposed-modules: Text.Parsec.Indent+ Text.Parsec.Indent.Explicit+ Text.Parsec.Indent.Internal Build-depends: base >= 4 && < 5,@@ -45,7 +47,7 @@ Build-depends: indents,- tasty >= 0.11 && < 0.13,+ tasty >= 0.11 && < 1.1, tasty-hunit >= 0.9 && < 0.11, -- Copy-pasted from 'Library' dependencies. base >= 4 && < 5,
src/Text/Parsec/Indent.hs view
@@ -19,10 +19,12 @@ (<+/>), (<-/>), (<*/>), (<?/>), Optional(..) ) where -import Control.Monad (ap, liftM2, unless, when)-import Control.Monad.Identity (Identity, runIdentity)-import Control.Monad.Reader (ReaderT, ask, local, runReaderT)+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@@ -50,30 +52,11 @@ -- is MIME headers. Line folding based binding separation is used in -- Haskell as well. --- | We use our own 'Position' type that doesn't require a 'SourceName'.-data Pos = Pos- { pLine :: !Int- , pColumn :: !Int- } deriving (Show)--showIndent :: Pos -> String-showIndent pos = case pColumn pos of- 1 -> "top-level indentation"- c -> show (c - 1) ++ "-column indentation"--showLine :: Pos -> String-showLine = show . pLine--getCurrentPos :: Monad m => IndentParserT s u m Pos-getCurrentPos = do- pos <- getPosition- return $! Pos {pLine = sourceLine pos, pColumn = sourceColumn pos}--getReferencePos :: Monad m => IndentParserT s u m Pos-getReferencePos = ask+referenceIndentation :: Monad m => IndentParserT s u m Indentation+referenceIndentation = ask -- | Indentation transformer.-type IndentT m = ReaderT Pos m+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.@@ -108,39 +91,26 @@ indented :: (Monad m, Stream s (IndentT m) z) => IndentParserT s u m ()-indented = do- pos <- getCurrentPos- ref <- getReferencePos- when (pColumn pos <= pColumn ref) $ unexpected (showIndent pos)+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 = do- -- This is equal to 'same <|> indented' but gives a cleaner error message.- pos <- getCurrentPos- ref <- getReferencePos- when (pColumn pos <= pColumn ref && pLine pos /= pLine ref) $- unexpected (showIndent pos)+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 = do- pos <- getCurrentPos- ref <- getReferencePos- when (pLine pos /= pLine ref) $ unexpected "line break"+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 p = withPos $ do- r <- many1 (checkIndent >> p)- return r+block = Explicit.block -- | Parses using the current location for indentation reference withPos@@ -148,39 +118,30 @@ => IndentParserT s u m a -> IndentParserT s u m a withPos x = do- p <- getCurrentPos+ 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 = do- ref <- getReferencePos- pos <- getCurrentPos- when (pColumn pos /= pColumn ref) $- (<?> showIndent ref ++ " (started at line " ++ showLine ref ++ ")")- (unexpected $ showIndent pos)+checkIndent = referenceIndentation >>= Explicit.checkIndent -- | Ensures that there is no indentation. topLevel :: (Monad m, Stream s (IndentT m) z) => IndentParserT s u m ()-topLevel = do- pos <- getCurrentPos- unless (pColumn pos == 1) $ unexpected "indentation"+topLevel = Explicit.topLevel -- | Ensures that there is at least some indentation. notTopLevel :: (Monad m, Stream s (IndentT m) z) => IndentParserT s u m ()-notTopLevel = do- pos <- getCurrentPos- when (pColumn pos == 1) $ unexpected "top-level"+notTopLevel = Explicit.notTopLevel -- | Run the result of an indentation sensitive parse runIndentT :: Monad m => IndentT m a -> m a-runIndentT i = runReaderT i (Pos 1 1)+runIndentT i = runReaderT i (Indentation 1 1) -- | Simplified version of 'runIndentT'. runIndent :: IndentT Identity a -> a
+ src/Text/Parsec/Indent/Explicit.hs view
@@ -0,0 +1,137 @@+-- |+-- = 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"
+ src/Text/Parsec/Indent/Internal.hs view
@@ -0,0 +1,21 @@+-- | Some internals. They are exposed for your convenience but can change in+-- between patch releases.+module Text.Parsec.Indent.Internal+ ( Indentation (..)+ , prettyIndentation+ , prettyLine+ ) where++-- | We use our own position type that doesn't require a 'SourceName'.+data Indentation = Indentation+ { iLine :: !Int+ , iColumn :: !Int+ } deriving (Show)++prettyIndentation :: Indentation -> String+prettyIndentation i = case iColumn i of+ 1 -> "top-level indentation"+ c -> show (c - 1) ++ "-column indentation"++prettyLine :: Indentation -> String+prettyLine = show . iLine