JustParse (empty) → 1.0
raw patch · 7 files changed
+797/−0 lines, 7 filesdep +basesetup-changed
Dependencies added: base
Files
- JustParse.cabal +20/−0
- LICENSE +24/−0
- Setup.hs +2/−0
- src/Data/JustParse.hs +141/−0
- src/Data/JustParse/Common.hs +284/−0
- src/Data/JustParse/Internal.hs +157/−0
- src/Data/JustParse/Language.hs +169/−0
+ JustParse.cabal view
@@ -0,0 +1,20 @@+name: JustParse+version: 1.0+synopsis: A simple and comprehensive Haskell parsing library+description: A simple and comprehensive Haskell parsing library+homepage: https://github.com/grantslatton/JustParse+license: PublicDomain+license-file: LICENSE+author: Grant Slatton+maintainer: grantslatton@gmail.com+-- copyright: +category: Text+build-type: Simple+cabal-version: >=1.8++library+ exposed-modules: Data.JustParse+ other-modules: Data.JustParse.Internal, Data.JustParse.Language, Data.JustParse.Common+ build-depends: base ==4.6.*+ hs-source-dirs: src+ extensions: MultiParamTypeClasses, Rank2Types, FlexibleInstances, FlexibleContexts, Safe
+ LICENSE view
@@ -0,0 +1,24 @@+This is free and unencumbered software released into the public domain.++Anyone is free to copy, modify, publish, use, compile, sell, or+distribute this software, either in source code form or as a compiled+binary, for any purpose, commercial or non-commercial, and by any+means.++In jurisdictions that recognize copyright laws, the author or authors+of this software dedicate any and all copyright interest in the+software to the public domain. We make this dedication for the benefit+of the public at large and to the detriment of our heirs and+successors. We intend this dedication to be an overt act of+relinquishment in perpetuity of all present and future rights to this+software under copyright law.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR+OTHER DEALINGS IN THE SOFTWARE.++For more information, please refer to <http://unlicense.org>
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ src/Data/JustParse.hs view
@@ -0,0 +1,141 @@+{-|+Module : Data.JustParse+Description : The one-stop import for the library+Copyright : Copyright Waived+License : PublicDomain+Maintainer : grantslatton@gmail.com+Stability : experimental+Portability : portable++A simple and comprehensive Haskell parsing library.+-} ++{-# LANGUAGE Safe #-}+module Data.JustParse (+ -- * Overview+ -- $overview++ -- * Quickstart Examples+ -- *** Simple char and string parsing+ -- $quickstart1++ -- *** Basic combinatorial parsing+ -- $quickstart2++ -- *** Recursive combinatorial parsing+ -- $quickstart3++ -- * General Parsing+ C.Stream(..),+ C.Result(..),+ C.Parser( parse ),+ C.justParse,+ C.runParser,+ C.finalize,+ C.extend,+ C.isDone,+ C.isFail,+ C.isPartial,+ C.rename,+ (C.<?>),++ -- * Generic Parsers+ C.test,+ C.greedy,+ C.option,+ C.satisfy,+ C.mN,+ C.many,+ C.many1,+ C.manyN,+ C.atLeast,+ C.exactly,+ C.eof,+ C.oneOf,+ C.noneOf,+ C.anyToken,+ C.lookAhead,++ -- * Char Parsers+ C.char,+ C.anyChar,+ C.ascii,+ C.latin1,+ C.control,+ C.space,+ C.lower,+ C.upper,+ C.alpha,+ C.alphaNum,+ C.print,+ C.digit,+ C.octDigit,+ C.hexDigit,+ C.eol,++ -- * String Parsers+ C.string,++ -- * Regex Parsers+ L.regex,+ L.regex',+ L.Match(..)++) where++import Data.JustParse.Language as L+import Data.JustParse.Common as C++-- $overview+-- +-- * Allows for parsing arbitrary 'Stream' types+-- +-- * Makes extensive use of combinators+-- +-- * Returns relatively verbose 'Fail'ure messages.+-- +-- * Allows one to 'rename' a 'Parser'. +-- +-- * Allows for a parser to return a 'Partial' 'Result'+-- +-- * Non-greedy parsing+-- +-- * Returns a list of all possible parses+-- +-- * Allows for conversion of a 'regex' to a parser++-- $quickstart1+--+-- This parser will only accept the string @\"hello world\"@+--+-- @+--p = do +-- h \<- char \'h\' \-\-Parses the character \'h\'+-- rest \<- string \"ello world\" \-\-Parses the string \"ello world\"+-- exp \<- char \'!\' \-\-Parses the character \'!\'+-- return ([h]++rest++[exp]) \-\-Returns all of the above concatenated together+-- @++-- $quickstart2+--+-- This parser will accept the string @\"hello woooooorld\"@ with any number of @o@\'s.+-- It returns the number of @o@\'s.+--+-- @+--p = do+-- first \<- string \"hello w\" \-\-Parses the string \"hello w\"+-- os \<- many1 (char \'o\') \-\-Applies the parser \"char \'o\'\" one or more times+-- second \<- string \"rld\" \-\-Parses the string \"rld\"+-- return (length os) \-\-Return the number of o\'s parsed+-- @++-- $quickstart3+-- +-- This parser will turn a string of comma separated values into a list of them+--+-- @+--csv = do +-- v \<- greedy (many (noneOf \",\")) \-\-Parses as many non-comma characters as possible+-- vs \<- option [] (char \',\' >> csv) \-\-Optionally parses a comma and a csv, returning the empty list upon failure+-- return (v:vs) \-\-Concatenates and returns the full list+-- @
+ src/Data/JustParse/Common.hs view
@@ -0,0 +1,284 @@+{-|+Module : Data.JustParse.Common+Description : Common Parser Combinators+Copyright : Copyright Waived+License : PublicDomain+Maintainer : grantslatton@gmail.com+Stability : experimental+Portability : portable++Many common parsing needs in one place.+-}++{-# LANGUAGE Safe #-}+module Data.JustParse.Common (+-- Parsing+ Stream(..),+ Result(..),+ Parser( parse ),+ finalize,+ extend,+ justParse,+ runParser,+ isDone,+ isFail,+ isPartial,+ rename,+ (<?>),++-- Primitive parsers+ satisfy,+ mN,++-- Derived Parsers++-- Generic Parsers+ test,+ greedy,+ option,+ many,+ many1,+ manyN,+ atLeast,+ exactly,+ eof,+ oneOf,+ noneOf,+ anyToken,+ lookAhead,++-- Char Parsers+ char,+ anyChar,+ ascii,+ latin1,+ control,+ space,+ lower,+ upper,+ alpha,+ alphaNum,+ print,+ digit,+ octDigit,+ hexDigit,+ eol,++-- String Parsers+ string,++) where++import Prelude hiding ( print, length )+import Data.JustParse.Internal ( Stream(..), Parser(..), Result(..), extend, finalize, isDone, isPartial, isFail, rename, (<?>) )+import Data.Monoid ( mempty, Monoid )+import Data.Maybe ( fromMaybe )+import Data.List ( minimumBy )+import Data.Char ( isControl, isSpace, isLower, isUpper, isAlpha, isAlphaNum, isPrint, + isDigit, isOctDigit, isHexDigit, isLetter, isMark, isNumber, isPunctuation, + isSymbol, isSeparator, isAscii, isLatin1, isAsciiUpper, isAsciiLower )+import Data.Ord ( comparing )+import Control.Monad ( void, (>=>), liftM )+import Control.Applicative ( (<|>), optional, (<*) )++-- | Supplies the input to the 'Parser'. Returns all 'Result' types, +-- including 'Partial' and 'Fail' types.+runParser :: Parser s a -> s -> [Result s a]+runParser p = parse p . Just++-- | This is a \"newbie\" command that one should probably only use out of frustration.+-- It runs the 'Parser' greedily over the input, 'finalize's all the results, and returns+-- the first successful result. If there are no successful results, it returns Nothing.+justParse :: Stream s t => Parser s a -> s -> Maybe a+justParse p s = + case finalize (parse (greedy p) (Just s)) of+ [] -> Nothing+ (Done v _:_) -> Just v+ (Fail m _:_) -> Nothing++-- | Parse a token that satisfies a predicate.+satisfy :: Stream s t => (t -> Bool) -> Parser s t+satisfy f = Parser $ \s -> + case s of+ Nothing -> [Fail ["satisfy"] s]+ Just s' -> case uncons s' of+ Nothing -> [Partial $ parse (satisfy f)]+ Just (x, xs) -> + if f x + then [Done x (Just xs)]+ else [Fail ["satisfy"] s]++-- | Parse from @m@ to @n@ occurences of a 'Parser'. Let @n@ be negative+-- if one wishes for no upper bound.+mN :: Int -> Int -> Parser s a -> Parser s [a]+mN m n p = mN' m n p <?> "mN"++mN' :: Int -> Int -> Parser s a -> Parser s [a]+mN' _ 0 _ = Parser $ \s -> [Done [] s] +mN' m n p = Parser $ \s -> + if m == 0 + then Done [] s : (parse p s >>= g)+ else parse p s >>= g+ where+ m' = if m == 0 then 0 else m-1+ g (Done a s) = parse (mN' m' (n-1) p) s >>= h a+ g (Partial p') = [Partial $ p' >=> g]+ g (Fail m l) = [Fail m l]+ h a (Done as s) = [Done (a:as) s]+ h a (Partial p') = [Partial $ p' >=> h a]+ h a (Fail m l) = [Fail m l]++-- | Return @True@ if the 'Parser' would succeed if one were to apply it,+-- otherwise, @False@.+test :: Parser s a -> Parser s Bool+test p = + do + a <- optional (lookAhead p)+ case a of+ Nothing -> return False+ _ -> return True++-- | Modifies a 'Parser' so that it will ony return the most consumptive+-- succesful results. If there are no successful results, it will only+-- return the most consumptive failures. One can use @greedy@ to emulate+-- parsers from @Parsec@ or @attoparsec@.+greedy :: Stream s t => Parser s a -> Parser s a+greedy (Parser p) = Parser $ \s -> g (p s) + where+ b (Done _ _) = True+ b (Fail _ _) = True+ b _ = False+ f Nothing = 0+ f (Just s) = length s+ g [] = []+ g xs + | all b xs = + let+ ds = filter isDone xs+ dm = minimum (map (f . leftover) ds)+ fs = filter isFail xs+ fm = minimum (map (f . leftover) fs)+ in+ if not (null ds)+ then filter ((dm==) . f . leftover) ds+ else filter ((fm==) . f . leftover) fs+ | otherwise = [Partial $ \s -> g $ extend s xs] ++-- | Attempts to apply a parser and returns a default value if it fails.+option :: a -> Parser s a -> Parser s a+option v p = + do+ r <- optional p+ case r of+ Nothing -> return v+ Just v' -> return v'++-- | Parse any number of occurences of the 'Parser'. Equivalent to @'mN' 0 (-1)@.+many :: Parser s a -> Parser s [a]+many p = rename "many" (mN 0 (-1) p)++-- | Parse one or more occurence of the 'Parser'. Equivalent to @'mN' 1 (-1)@.+many1 :: Parser s a -> Parser s [a]+many1 p = rename "many1" (mN 1 (-1) p)++-- | Parse at least @n@ occurences of the 'Parser'. Equivalent to @'mN' n (-1)@.+manyN :: Int -> Parser s a -> Parser s [a]+manyN n p = rename "manyN" (mN n (-1) p)++-- | Identical to 'manyN', just a more intuitive name.+atLeast :: Int -> Parser s a -> Parser s [a]+atLeast n p = rename "atLeast" (mN n (-1) p)++-- | Parse exactly @n@ occurences of the 'Parser'. Equivalent to @'mN' n n@.+exactly :: Int -> Parser s a -> Parser s [a]+exactly n p = rename "exactly" (mN n n p)++-- | Only succeeds when supplied with @Nothing@.+eof :: (Eq s, Monoid s) => Parser s ()+eof = Parser $ \s ->+ case s of+ Nothing -> [Done () s]+ Just s' -> + if s' == mempty+ then [Partial $ parse eof]+ else [Fail ["eof"] (Just s')]++oneOf :: (Eq t, Stream s t) => [t] -> Parser s t+oneOf ts = rename "oneOf" (satisfy (`elem` ts))++noneOf :: (Eq t, Stream s t) => [t] -> Parser s t+noneOf ts = rename "noneOf" (satisfy (not . (`elem` ts)))++-- | Parse a specific token.+token :: (Eq t, Stream s t) => t -> Parser s t+token t = rename "token" (satisfy (==t))++anyToken :: Stream s t => Parser s t+anyToken = rename "anyToken" (satisfy (const True))++-- | Applies the parser and returns its result, but resets+-- the leftovers as if it consumed nothing.+lookAhead :: Parser s a -> Parser s a+lookAhead (Parser p) = rename "lookAhead" $ Parser $ \s -> + let + g (Done a _) = [Done a s]+ g (Partial p') = [Partial $ p' >=> g]+ g (Fail m _) = [Fail m s]+ in+ p s >>= g++-- | Parse a specic char.+char :: Stream s Char => Char -> Parser s Char+char c = rename ("char "++[c]) (token c)++anyChar :: Stream s Char => Parser s Char+anyChar = rename "anyChar" anyToken++ascii :: Stream s Char => Parser s Char+ascii = rename "ascii" (satisfy isAscii)++latin1 :: Stream s Char => Parser s Char+latin1 = rename "latin1" (satisfy isLatin1)++control :: Stream s Char => Parser s Char+control = rename "control" (satisfy isControl)++space :: Stream s Char => Parser s Char+space = rename "space" (satisfy isSpace)++lower :: Stream s Char => Parser s Char+lower = rename "lower" (satisfy isLower)++upper :: Stream s Char => Parser s Char+upper = rename "upper" (satisfy isUpper)++alpha :: Stream s Char => Parser s Char+alpha = rename "alpha" (satisfy isAlpha)++alphaNum :: Stream s Char => Parser s Char+alphaNum = rename "alphaNum" (satisfy isAlphaNum)++print :: Stream s Char => Parser s Char+print = rename "print" (satisfy isPrint)++digit :: Stream s Char => Parser s Char+digit = rename "digit" (satisfy isDigit)++octDigit :: Stream s Char => Parser s Char+octDigit = rename "octDigit" (satisfy isOctDigit)++hexDigit :: Stream s Char => Parser s Char+hexDigit = rename "hexDigit" (satisfy isHexDigit)++-- | Parse a specific string.+string :: Stream s Char => String -> Parser s String+string s = rename ("string "++s) (mapM char s)++-- | Parses until a newline, carriage return + newline, or newline + carriage return.+eol :: Stream s Char => Parser s String+eol = rename "eol" (string "\r\n" <|> string "\n\r" <|> string "\n")++-- | Makes common types such as Strings into a Stream.+instance (Eq t) => Stream [t] t where+ uncons [] = Nothing+ uncons (x:xs) = Just (x, xs)
+ src/Data/JustParse/Internal.hs view
@@ -0,0 +1,157 @@+{-|+Module : Data.JustParse.Internal+Description : The engine behind the JustParse library+Copyright : Copyright Waived+License : PublicDomain+Maintainer : grantslatton@gmail.com+Stability : experimental+Portability : portable+-}++{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE Safe #-}++module Data.JustParse.Internal (+ finalize,+ extend,+ Stream (..),+ Parser (..),+ Result (..),+ isDone,+ isFail,+ isPartial,+ rename,+ (<?>)+) where++import Prelude hiding ( length )+import Control.Monad ( MonadPlus, mzero, mplus, (>=>), ap )+import Control.Applicative ( Alternative, Applicative, pure, (<*>), empty, (<|>) )+import Data.Monoid ( Monoid, mempty, mappend )+import Data.List ( intercalate )++-- | A @Stream@ instance has a stream of type @s@, made up of tokens of +-- type @t@, which must be determinable by the stream.+class (Eq s, Monoid s) => Stream s t | s -> t where+ -- | @uncons@ returns @Nothing@ if the @Stream@ is empty, otherwise it+ -- returns the first token of the stream, followed by the remainder+ -- of the stream, wrapped in a @Just@.+ uncons :: Stream s t => s -> Maybe (t, s)+ -- | The default @length@ implementation is O(n). If your stream provides+ -- a more efficient method for determining the length, it is wise to+ -- override this. The @length@ method is only used by the 'greedy' parser.+ length :: Stream s t => s -> Int+ length s = + case uncons s of+ Nothing -> 0+ Just (x, xs) -> 1 + length xs++newtype Parser s a = + Parser { + parse :: Maybe s -> [Result s a]+ }++instance Monoid (Parser s a) where+ mempty = mzero+ mappend = mplus++instance Functor (Parser s) where+ fmap f (Parser p) = Parser $ \s -> map (fmap f) (p s)++instance Applicative (Parser s) where+ pure = return + (<*>) = ap++instance Alternative (Parser s) where+ empty = mzero+ (<|>) = mplus++instance Monad (Parser s) where+ return v = Parser $ \s -> [Done v s] + (Parser p) >>= f = Parser $ p >=> g+ where+ g (Fail m l) = [Fail m l]+ g (Done a s) = parse (f a) s + g (Partial p) = [Partial $ p >=> g] ++instance MonadPlus (Parser s) where+ mzero = Parser $ const []+ mplus (Parser p1) (Parser p2) = Parser (\s -> p1 s ++ p2 s)++data Result s a + -- | A @Partial@ wraps the same function as a Parser. Supply it with a @Just@+ -- and it will continue parsing, or with a @Nothing@ and it will terminate.+ =+ Partial {+ continue :: Maybe s -> [Result s a]+ } |+ -- | A @Done@ contains the resultant @value@, and the @leftover@ stream, if any.+ Done {+ value :: a,+ leftover :: Maybe s+ } |+ -- | A @Fail@ contains a stack of error messages, and the @lftover@ stream, if any.+ Fail {+ messages :: [String],+ leftover :: Maybe s+ }++isDone :: Result s a -> Bool+isDone (Done _ _) = True+isDone _ = False++isPartial :: Result s a -> Bool+isPartial (Partial _) = True+isPartial _ = False++isFail :: Result s a -> Bool+isFail (Fail _ _) = True+isFail _ = False++instance Functor (Result s) where+ fmap f (Partial p) = Partial $ map (fmap f) . p+ fmap f (Done a s) = Done (f a) s+ fmap f (Fail m l) = Fail m l++instance Show a => Show (Result s a) where+ show (Partial _) = "Partial"+ show (Done a _) = show a+ show (Fail m l) = "Fail: \nIn: " ++ intercalate "\nIn: " m++-- | @finalize@ takes a list of results (presumably returned from a 'Parser' or 'Partial',+-- and supplies @Nothing@ to any remaining @Partial@ values, so that only 'Fail' and 'Done'+-- values remain.+finalize :: (Eq s, Monoid s) => [Result s a] -> [Result s a]+finalize = extend Nothing++-- | @extend@ takes a @Maybe s@ as input, and supplies the input to all values+-- in the 'Result' list. For 'Done' and 'Fail' values, it appends the @stream@ +-- to the 'leftover' portion, and for 'Partial' values, it runs the continuation,+-- adding in any new 'Result' values to the output.+extend :: (Eq s, Monoid s) => Maybe s -> [Result s a] -> [Result s a]+extend s rs = rs >>= g --`prnt` (show (map i rs, map i (rs >>= g), h s))+ where+ g (Fail m l) = [Fail m (f l s)]+ g (Partial p) = p s+ g (Done a s') = [Done a (f s' s)]+ f Nothing _ = Nothing+ f (Just s) Nothing = if s == mempty then Nothing else Just s+ f s s' = mappend s s'++-- | @rename@ pushes a new error message onto the stack in case of failure.+-- This is particularly useful when debugging a complex 'Parser'.+rename :: String -> Parser s a -> Parser s a+rename s p = Parser (map g . parse p)+ where+ g v@(Fail m l) = Fail (s:m) l+ g v = v+++infixl 0 <?>+-- | The infix version of 'rename'+(<?>) :: Parser s a -> String -> Parser s a+p <?> s = rename s p
+ src/Data/JustParse/Language.hs view
@@ -0,0 +1,169 @@+{-|+Module : Data.JustParse.Language+Description : Regular Expressions and Grammars in JustParse+Copyright : Copyright Waived+License : PublicDomain+Maintainer : grantslatton@gmail.com+Stability : experimental+Portability : portable++Takes ideas from the field of Formal Languages and imports them into the parsing library.+-}++{-# LANGUAGE Safe #-}+module Data.JustParse.Language (+ Match (..),+ regex,+ regex'+) where++import Data.JustParse.Common ( char, string, many1, digit, Stream, noneOf, oneOf, greedy, many, mN, anyChar, leftover, value, finalize, parse, Result(..), justParse, isFail )+import Data.JustParse.Internal( Parser (..) )+import Control.Applicative ( (<|>), optional )+import Control.Monad ( liftM, mzero )+import Data.Monoid ( Monoid, mconcat, mempty, mappend )+import Data.Maybe ( isJust )+import Data.List ( intercalate )++-- | @regex@ takes a regular expression in the form of a 'String' and,+-- if the regex is valid, returns a 'Parser' that parses that regex.+-- If the regex is invalid, it returns a Parser that will only return+-- 'Fail' with an \"Invalid Regex\" message.+regex :: Stream s Char => String -> Parser s Match+regex s + | null r = Parser $ \s -> [Fail ["Invalid Regex"] s]+ | isFail $ head r = Parser $ \s -> [Fail ["Invalid Regex"] s]+ | isJust $ leftover $ head r = Parser $ \s -> [Fail ["Invalid Regex"] s]+ | otherwise = value $ head r+ where+ r = finalize (parse (greedy regular) (Just s))++-- | The same as 'regex', but only returns the full matched text.+regex' :: Stream s Char => String -> Parser s String+regex' = liftM matched . regex ++-- | The result of a 'regex'+data Match = + Match {+ -- | The complete text matched within the regex+ matched :: String,+ -- | Any submatches created by using capture groups+ groups :: [Match]+ } ++instance Show Match where+ show = show' ""+ where+ show' i (Match m []) = i ++ m+ show' i (Match m gs) = i ++ m ++ "\n" ++ intercalate "\n" (map (show' ('\t':i)) gs)++-- mconcat makes things very nice for concatenating the results of subregexes+instance Monoid Match where+ mempty = Match "" []+ mappend (Match m g) (Match m' g') = + Match {+ matched = m ++ m',+ groups = g ++ g'+ }++regular :: (Stream s0 Char, Stream s1 Char) => Parser s0 (Parser s1 Match)+regular = liftM (liftM mconcat . sequence) (greedy $ many parser)++parser :: (Stream s0 Char, Stream s1 Char) => Parser s0 (Parser s1 Match)+parser = character <|> charClass <|> negCharClass <|> question <|> group <|> asterisk <|> plus <|> mn <|> period <|> pipe++parserNP :: (Stream s0 Char, Stream s1 Char) => Parser s0 (Parser s1 Match)+parserNP = character <|> charClass <|> negCharClass <|> question <|> group <|> asterisk <|> plus <|> mn <|> period ++restricted :: (Stream s0 Char, Stream s1 Char) => Parser s0 (Parser s1 Match)+restricted = character <|> charClass <|> negCharClass <|> group <|> period++unreserved :: Stream s Char => Parser s Char +unreserved = (char '\\' >> anyChar ) <|> noneOf "()[]\\*+{}^?:<>|."++character :: (Stream s0 Char, Stream s1 Char) => Parser s0 (Parser s1 Match)+character = + do+ c <- unreserved+ return $ do+ c' <- char c+ return $ Match [c] []++charClass :: (Stream s0 Char, Stream s1 Char) => Parser s0 (Parser s1 Match)+charClass = + do+ char '['+ c <- greedy (many1 unreserved)+ char ']'+ return $ do+ c' <- oneOf c+ return $ Match [c'] []++negCharClass :: (Stream s0 Char, Stream s1 Char) => Parser s0 (Parser s1 Match)+negCharClass = + do+ string "[^"+ c <- greedy (many1 unreserved)+ char ']'+ return $ do+ c' <- noneOf c+ return $ Match [c'] []++period :: (Stream s0 Char, Stream s1 Char) => Parser s0 (Parser s1 Match)+period = + do+ char '.'+ return $ do+ c <- noneOf "\n\r"+ return $ Match [c] []+++question :: (Stream s0 Char, Stream s1 Char) => Parser s0 (Parser s1 Match)+question = + do+ p <- restricted+ char '?'+ return $ liftM mconcat (mN 0 1 p)++group :: (Stream s0 Char, Stream s1 Char) => Parser s0 (Parser s1 Match)+group = + do+ string "("+ p <- regular+ char ')'+ return $ do+ r <- p+ return $ r { groups = [r] } ++asterisk :: (Stream s0 Char, Stream s1 Char) => Parser s0 (Parser s1 Match)+asterisk = + do+ p <- restricted+ char '*'+ return $ liftM mconcat (many p)++plus :: (Stream s0 Char, Stream s1 Char) => Parser s0 (Parser s1 Match)+plus = + do+ p <- restricted+ char '+'+ return $ liftM mconcat (many1 p)++mn :: (Stream s0 Char, Stream s1 Char) => Parser s0 (Parser s1 Match)+mn = + do+ p <- restricted+ char '{'+ l <- optional (many1 digit)+ char ','+ r <- optional (many1 digit)+ char '}'+ return $ liftM mconcat (mN (maybe 0 read l) (maybe (-1) read r) p)++pipe :: (Stream s0 Char, Stream s1 Char) => Parser s0 (Parser s1 Match)+pipe = + do+ p <- parserNP+ char '|'+ p' <- parser+ return $ p <|> p'