parcom-lib 0.5.0.0 → 0.6.0.0
raw patch · 6 files changed
+192/−11 lines, 6 files
Files
- Text/Parcom.hs +88/−0
- Text/Parcom/Combinators.hs +3/−1
- Text/Parcom/Core.hs +39/−5
- Text/Parcom/Prim.hs +20/−2
- Text/Parcom/Stream.hs +39/−1
- parcom-lib.cabal +3/−2
+ Text/Parcom.hs view
@@ -0,0 +1,88 @@+-- | A parser-combinator library.+--+-- The primary goal in writing Parcom was to facilitate parsing Unicode string+-- data from various source streams, including raw+-- 'Data.ByteString.ByteString's - while Attoparsec can parse+-- 'Data.ByteString.ByteString's, it sacrifices some convenience for+-- performance, and using it to parse textual data is not as comfortable as I+-- would like; Parsec can handle textual data much better, but it needs the+-- input to be converetd to Unicode for this to work nicely. Nonetheless,+-- Parcom\'s interface is quite obviously heavily inspired by both Parsec and+-- Attoparsec.+--+-- Parcom supports 'String', 'Data.ByteString.ByteString' (lazy and strict) and+-- 'Data.Text.Text' (lazy and strict) as its input format out-of-the-box. By+-- implementing one or more of the typeclasses in 'Text.Parcom.Stream', you can+-- extend Parcom to work on other input types as well.+module Text.Parcom+( module Text.Parcom.Prim+, module Text.Parcom.Combinators+, module Text.Parcom.Core+-- * Getting Started+-- | Parcom being a parser combinator library, the usual approach is to use+-- predefined atomic parsers (defined in "Text.Parcom.Prim" and re-exported+-- here for convenience) and combine them using predefined combinators (defined+-- in "Text.Parcom.Combinators"). Anyone with prior exposure to Parsec or+-- Attoparsec should be familiar with the concept. Here's an example that+-- parses a value, which can be a positive integer literal or NULL:+--+-- > myParser :: Parcom String Char (Maybe Int)+-- > myParser = intLiteral <|> nullLiteral <?> "value (integer or NULL)"+-- >+-- > intLiteral :: Parcom String Char (Maybe Int)+-- > intLiteral = do+-- > x <- oneOf ['1'..'9']+-- > xs <- many (oneOf ['0'..'9'])+-- > return $ Just $ read (x:xs)+-- > +-- > nullLiteral :: Parcom String Char (Maybe Int)+-- > nullLiteral = do+-- > tokens "NULL"+-- > notFollowedBy (satisfy (not . isSpace))+-- > return Nothing+--+-- Such a parser can then be run against some input using 'parse' or 'parseT',+-- the monadic equivalent.+-- +-- > main = do+-- > src <- getContents+-- > let parseResult = parse myParser "<STDIN>" src+-- > case parseResult of+-- > Left err -> do+-- > putStrLn "Sorry, there has been an error, namely:"+-- > print err+-- > Right (Just i) -> putStrLn $ "Found an integer value: " ++ show i+-- > Right Nothing -> putStrLn "Found NULL"++-- * Backtracking+-- | As you build more complex parsers, you may encounter situations where a+-- parser fails after having consumed some input already. Combining such a+-- parser with other alternatives will yield undesired results: the parser+-- fails, but it will not push the input it has already consumed back onto the+-- input stream. To fix this, use the 'try' primitive, which modifies a parser+-- such that when it fails, it undoes any input consumption it may have caused.++-- * Input types other than 'String'+-- | To support input from @Text@ or @ByteString@s, import one of the following+-- modules:+--+-- * "Text.Parcom.Text" or "Text.Parcom.Text.Strict" for strict @Text@+--+-- * "Text.Parcom.Text.Lazy" for lazy @Text@+--+-- * "Text.Parcom.ByteString" or "ByteString.Parcom.ByteString.Strict" for strict @ByteString@+--+-- * "Text.Parcom.ByteString.Lazy" for lazy @ByteString@++-- * Parsing textual data+-- | Parcom provides two primitives for textual data: 'Text.Parcom.Textual.char'+-- and 'Text.Parcom.Textual.string'. While textual data can be extracted from+-- input streams that are textual already (e.g. 'String' or 'Data.Text.Text')+-- using the normal token-based primities ('token', 'tokens', and 'prefix'),+-- doing so for bytestrings isn't trivial. The textual-data +)+where++import Text.Parcom.Prim+import Text.Parcom.Core+import Text.Parcom.Combinators
Text/Parcom/Combinators.hs view
@@ -1,3 +1,5 @@+-- | A collection of predefined combinators. Use these to combine other parsers+-- into more complex ones. module Text.Parcom.Combinators ( choice, namedChoice , before, between@@ -17,7 +19,7 @@ choice :: (Monad m, Stream s t) => [ParcomT s t m a] -> ParcomT s t m a choice xs = foldl (<|>) empty xs <?> "I tried to make a choice, but couldn't" --- | Like @choice@, but each choice tagged with a human-readable name for+-- | Like 'choice', but each choice tagged with a human-readable name for -- better error reporting. namedChoice :: (Monad m, Stream s t) => [(String, ParcomT s t m a)] -> ParcomT s t m a namedChoice xs = choice (map snd xs) <?> (formatOptionList . map fst) xs
Text/Parcom/Core.hs view
@@ -1,3 +1,7 @@+-- | The core functionality of a Parcom parser: defining and running parsers,+-- lifting, getting tokens and characters from a stream, and the most basic+-- primitive parsers and combinators that cannot easily be expressed in terms+-- of other parsers and combinators. {-#LANGUAGE MultiParamTypeClasses #-} module Text.Parcom.Core ( ParcomError (..)@@ -20,6 +24,8 @@ import Control.Monad.Identity import Control.Monad.Trans.Class +-- | Represents a position in a source file. Both lines and columns are+-- 1-based. data SourcePosition = SourcePosition { posFileName :: String@@ -28,22 +34,32 @@ } deriving (Show) +-- | A parser error. data ParcomError = ParcomError- { peErrorDescription :: String- , peSourcePosition :: SourcePosition+ { peErrorDescription :: String -- ^ Human-readable description of the error+ , peSourcePosition :: SourcePosition -- ^ Position in the source where the error was found. } deriving (Show) +-- | The parser's internal state. data ParcomState s = ParcomState- { psSourcePosition :: SourcePosition- , psStream :: s+ { psSourcePosition :: SourcePosition -- ^ Current source position, for error reporting+ , psStream :: s -- ^ The remaining source stream } +-- | Parcom as a pure parser type Parcom s t a = ParcomT s t Identity a++-- | Parcom as a monad transformer. You can access the underlying monad stack+-- using the usual lifting techniques. newtype ParcomT s t m a = ParcomT { runParcomT :: ParcomState s -> m (Either ParcomError a, ParcomState s) } +-- | Parcom is a monad. Obviously. Since the Parcom monad handles both failure+-- through 'Either' as well as carrying along its internal state, *and*+-- supporting the transformed parent monad, the implementation is a tiny bit+-- hairy. instance (Monad m) => Monad (ParcomT s t m) where return x = ParcomT (\s -> return (Right x, s)) fail err = ParcomT (\s -> return (Left $ ParcomError err (psSourcePosition s), s))@@ -54,6 +70,7 @@ Right ma -> do runParcomT (f ma) s' +-- | ParcomT enables lifting by implementing 'MonadTrans' instance MonadTrans (ParcomT s t) where lift a = ParcomT $ \s -> do v <- a@@ -70,6 +87,8 @@ (<|>) = alt empty = fail "empty" +-- | Wrapper that handles setting up a sensible initial state before running a+-- ParcomT runParserT :: (Stream s t, Token t) => ParcomT s t m a -> String -> s -> m (Either ParcomError a, ParcomState s) runParserT p fn str = runParcomT p state@@ -79,21 +98,27 @@ { psSourcePosition = SourcePosition fn 1 1 , psStream = str } +-- | Run a parcom transformer and return the result parseT :: (Stream s t, Token t, Monad m) => ParcomT s t m a -> String -> s -> m (Either ParcomError a) parseT p fn str = fst `liftM` runParserT p fn str +-- | Run a pure parcom parser and return the result parse :: (Stream s t, Token t) => Parcom s t a -> String -> s -> Either ParcomError a parse p fn str = runIdentity $ parseT p fn str +-- | Get the internal parser state getState :: Monad m => ParcomT s t m (ParcomState s) getState = ParcomT $ \s -> return (Right s, s) +-- | Get the internal parser state and apply a projection function useState :: Monad m => (ParcomState s -> a) -> ParcomT s t m a useState f = ParcomT $ \s -> return (Right $ f s, s) +-- | Overwrite the internal parser state entirely setState :: Monad m => ParcomState s -> ParcomT s t m () setState s = ParcomT $ \_ -> return (Right (), s) +-- | Apply a modification function to the internal parser state modifyState :: Monad m => (ParcomState s -> ParcomState s) -> ParcomT s t m () modifyState f = ParcomT $ \s -> return (Right (), f s) @@ -142,7 +167,7 @@ -- | Succeeds iff the given parser fails notFollowedBy :: (Monad m, Stream s t) => ParcomT s t m a -> ParcomT s t m () notFollowedBy p = handle' p (\_ s _ -> return (Right (), s)) (\x s _ -> runParcomT (fail "something followed that shouldn't") s)- + -- | Gets the next token from the stream without consuming it. -- Fails at end-of-input. peek :: (Monad m, Stream s t) => ParcomT s t m t@@ -179,6 +204,9 @@ else modifyState $ \s -> s { psSourcePosition = nextColumn (psSourcePosition s) } return t +-- | Get one character from the stream, but do not consume it. Fails when the+-- input stream contains a sequence that does not represent a valid character,+-- or when the end of the input stream has been reached. peekChar :: (Monad m, Stream s t, Token t, Textish s) => ParcomT s t m Char peekChar = do str <- psStream `liftM` getState@@ -187,6 +215,9 @@ Just c -> return c Nothing -> fail "Tokens do not form a valid character, or end of input reached" +-- | Get one character from the stream, and consume it. Fails when the input+-- stream contains a sequence that does not represent a valid character, or+-- when the end of the input stream has been reached. nextChar :: (Monad m, Stream s t, Token t, Textish s) => ParcomT s t m Char nextChar = do str <- psStream `liftM` getState@@ -197,5 +228,8 @@ return c Nothing -> fail "Tokens do not form a valid character, or end of input reached" +-- | Tags a parser with a human-readable description of the expected entity,+-- generating an "Expected {entity}" type error message on failure.+infixl 3 <?> (<?>) :: (Monad m, Stream s t) => ParcomT s t m a -> String -> ParcomT s t m a p <?> expected = p <|> fail ("Expected " ++ expected)
Text/Parcom/Prim.hs view
@@ -1,5 +1,13 @@+-- | Primitive parsers.+--+-- Regarding consuming input, unless states otherwise, all of these behave as+-- follows:+--+-- * If a parser succeeds, it consumes the input it matches+--+-- * If a parser fails, it does not consume any input at all module Text.Parcom.Prim-( anyToken, oneOf, eof+( anyToken, oneOf, noneOf, eof , satisfy , token, tokens, prefix )@@ -19,10 +27,15 @@ eof = notFollowedBy anyToken <?> "end of input" -- | Matches one token against a list of possible tokens; returns the--- matching token.+-- matching token or fails. oneOf :: (Monad m, Stream s t, Token t, Show t, Eq t) => [t] -> ParcomT s t m t oneOf xs = satisfy (`elem` xs) <?> (formatOptionList . map show) xs +-- | Matches one token against a list of prohibited tokens; returns the+-- non-matching token or fails.+noneOf :: (Monad m, Stream s t, Token t, Show t, Eq t) => [t] -> ParcomT s t m t+noneOf xs = satisfy (not . (`elem` xs)) <?> "anything but " ++ (formatOptionList . map show) xs+ -- | Succeeds if the given predicate is met for the next token. satisfy :: (Monad m, Stream s t, Token t) => (t -> Bool) -> ParcomT s t m t satisfy p = do@@ -43,5 +56,10 @@ cs <- tokens xs return (c:cs) +-- | Match a series of tokens exactly. Unlike 'tokens', this parser accepts+-- the target sequence by the stream's type instead of list-of-tokens.+-- Depending on the stream's 'Listish' implementation, this may be more+-- efficient than matching and consuming the tokens one-by-one, as 'tokens'+-- does. prefix :: (Monad m, Stream s t, Token t, Eq t, Show t, Listish s t) => s -> ParcomT s t m s prefix str = fromList `liftM` tokens (toList str)
Text/Parcom/Stream.hs view
@@ -1,7 +1,19 @@ {-#LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances #-}+-- | Typeclasses to describe various aspects of parsable streams and tokens.+-- The standard range of Haskell string-like types ('String', and both the lazy+-- and strict flavors of 'ByteString' and 'Text') is supported already, as well+-- as any 'List', so under normal circumstances, you should not need to touch+-- this module directly.+ module Text.Parcom.Stream where +-- | Typeclass for types that are suitable as source streams. Note that+-- implementing just 'Stream' gives you only a small subset of Parcom\'s+-- features; if you want to implement your own 'Stream' instances, you will+-- most likely also want to implement 'Token' for the corresponding token type,+-- 'Listish' to enable parsers that need to convert to or from lists of tokens,+-- and 'Textish' to enable parsers that work on Unicode text. class Stream s t | s -> t where peek :: s -> t atEnd :: s -> Bool@@ -10,27 +22,53 @@ consume = snd . pop pop s = (peek s, consume s) +-- | All lists are instances of 'Stream', and the corresponding token type is+-- the list element type. Obviously, this also includes 'String' ('[Char]') and+-- '[Word8]' instance Stream [a] a where peek = head atEnd = null consume = tail +-- | This typeclass is pretty much required to do anything useful with Parcom;+-- it is needed for Parcom to detect line endings so that parser errors will+-- report the correct source positions. If you need to parse streams that do+-- not support any meaningful concept of lines, consider implementing a dummy+-- instance, like so:+-- @+-- instance Token Foobar where+-- isLineDelimiter _ = False+-- @+-- This will treat the entire input as a single line. class Token t where isLineDelimiter :: t -> Bool +-- | Unicode characters are valid tokens. instance Token Char where isLineDelimiter '\n' = True isLineDelimiter _ = False +-- | List-like types. class Listish s t | s -> t where- toList :: s -> [t]+ toList :: s -> [t] fromList :: [t] -> s +-- | Any list is trivially list-like instance Listish [a] a where toList = id fromList = id +-- | Enables parsing on a per-character basis rather than per-token. For stream+-- types where the token type is 'Char' already, this is trivial, but for other+-- streams (e.g., bytestrings), some extra processing is required to perform a+-- conversion to Unicode. class Textish s where+ -- | Character-wise equivalent of 'peek'. Returns a pair, where the first+ -- element is 'Just' the parsed Unicode character, or Nothing on failure,+ -- and the second element is the number of tokens that the character has+ -- consumed. Generally, there are two reasons why parsing may fail:+ -- end-of-input, and a token sequence that does not represent a valid+ -- Unicode character according to the underlying stream's semantics. peekChar :: s -> (Maybe Char, Int) instance Textish [Char] where
parcom-lib.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/ name: parcom-lib-version: 0.5.0.0+version: 0.6.0.0 synopsis: A simple parser-combinator library, a bit like Parsec but without the frills description: Parcom provides parser combinator functionality in a string-type-agnostic way; it supports strict ByteStrings (with Word8 tokens) and any list type (with@@ -20,7 +20,8 @@ cabal-version: >=1.8 library- exposed-modules: Text.Parcom.Stream+ exposed-modules: Text.Parcom+ , Text.Parcom.Stream , Text.Parcom.Core , Text.Parcom.Word8 , Text.Parcom.Internal