multiarg-0.6.0.0: System/Console/MultiArg/Prim.hs
-- | Parser primitives. These are the only functions that have access
-- to the internals of the parser. Use these functions if you want to
-- build your own parser from scratch. If your needs are simpler, you
-- will want to look at "System.Console.MultiArg.SimpleParser" or
-- "System.Console.MultiArg.Combinator", which do a lot of grunt work
-- for you.
module System.Console.MultiArg.Prim (
-- * Parser types
Parser,
-- * Running a parser
-- | Each parser runner is applied to a list of Strings, which are the
-- command line arguments to parse.
parse,
-- * Higher-level parser combinators
parserMap,
good,
apply,
choice,
combine,
lookAhead,
-- ** Running parsers multiple times
several,
manyTill,
-- ** Failure and errors
throw,
throwString,
genericThrow,
(<??>),
try,
-- * Parsers
-- ** Short options and arguments
pendingShortOpt,
nonPendingShortOpt,
pendingShortOptArg,
-- ** Long options and arguments
exactLongOpt,
approxLongOpt,
-- ** Stoppers
stopper,
resetStopper,
-- ** Positional (non-option) arguments
nextArg,
nonOptionPosArg,
-- ** Miscellaneous
end,
-- * Errors
Message(Expected, StrMsg, Replaced, UnknownError),
Error(Error),
Location
) where
import System.Console.MultiArg.Option
(ShortOpt,
unShortOpt,
LongOpt,
unLongOpt,
makeLongOpt )
import Control.Applicative ( Applicative, Alternative )
import qualified Control.Applicative
import Control.Monad.Exception.Synchronous
(Exceptional(Success, Exception))
import qualified Data.Set as Set
import Data.Set ( Set )
import Control.Monad ( when, MonadPlus(mzero, mplus), guard )
import Data.Monoid ( Monoid ( mempty, mappend ) )
import qualified Data.List as L
import Data.List (isPrefixOf, intercalate)
type Location = String
-- | An Error contains a list of Messages and a String indicating
-- where the error happened.
data Error = Error [Message] Location deriving Show
-- | Extract a Location from a ParseSt for use in error messages.
location :: ParseSt -> Location
location st = pending ++ next ++ stop where
pending
| null (pendingShort st) = ""
| otherwise = "short option or short option argument: "
++ pendingShort st ++ " "
next = case remaining st of
[] -> "no words remaining"
x:_ -> "next word: " ++ x
stop = if sawStopper st then " (stopper already seen)" else ""
-- | Error messages.
data Message =
Expected String
-- ^ The parser expected to see one thing, but it actually saw
-- something else. The string indicates what was expected.
| StrMsg String
-- ^ The 'fromString' function was applied.
| Replaced String
-- ^ A previous list of error messages was replaced with this error message.
| UnknownError
-- ^ Any other error; used by 'genericThrow'.
deriving Show
-- | Carries the internal state of the parser. The counter is a simple
-- way to determine whether the remaining list one ParseSt has been
-- modified from another. When parsers modify remaining, they
-- increment the counter.
data ParseSt = ParseSt { pendingShort :: String
, remaining :: [String]
, sawStopper :: Bool
, counter :: Int
, errors :: [Message]
} deriving Show
-- | Load up the ParseSt with an initial user state and a list of
-- commmand line arguments.
defaultState :: [String] -> ParseSt
defaultState ts = ParseSt { pendingShort = ""
, remaining = ts
, sawStopper = False
, counter = 0
, errors = [] }
-- | Carries the result of each parse.
data Result a = Bad | Good a
-- | Parsers. Internally the parser tracks what input remains to be
-- parsed, whether there are any pending short options, and whether a
-- stopper has been seen. A parser can return a value of any type.
--
-- The parser also includes the notion of failure. Any parser can
-- fail; a failed parser affects the behavior of combinators such as
-- combine.
data Parser a =
Parser { runParser :: ParseSt -> (Result a, ParseSt) }
instance Functor Parser where
fmap = parserMap
instance Applicative Parser where
pure = good
(<*>) = apply
instance Monoid (Parser a) where
mempty = genericThrow
mappend = choice
instance Alternative Parser where
empty = genericThrow
(<|>) = choice
many = several
instance Monad Parser where
(>>=) = combine
return = good
fail = throwString
instance MonadPlus Parser where
mzero = genericThrow
mplus = choice
-- | Runs a parser. This is the only way to change a value of type
-- @Parser a@ into a value of type @a@ (that is, it is the only way to
-- \"get out of the Parser monad\" or to \"escape the Parser monad\".)
parse ::
[String]
-- ^ Command line arguments to parse. Presumably you got these from
-- 'getArgs'. If there is any chance that you will be parsing
-- Unicode strings, see the documentation in
-- "System.Console.MultiArg.GetArgs" before you use
-- 'System.Environment.getArgs'.
-> Parser a
-- ^ Parser to run
-> Exceptional Error a
-- ^ Success or failure. Any parser might fail; for example, the
-- command line might not have any values left to parse. Use of the
-- 'choice' combinator can lead to a list of failures. If multiple
-- parsers are tried one after another using the 'choice' combinator,
-- and each fails without consuming any input, then multiple Error
-- will result, one for each failure.
parse ts p =
let (result, st') = runParser p (defaultState ts)
in case result of
Good g -> Success g
Bad ->
let e = Error (errors st') (location st')
in Exception e
-- | Combines two parsers into a single parser. The second parser can
-- optionally depend upon the result from the first parser.
--
-- This applies the first parser. If the first parser succeeds,
-- combine then takes the result from the first parser, applies the
-- function given to the result from the first parser, and then
-- applies the resulting parser.
--
-- If the first parser fails, combine will not apply the second
-- function but instead will bypass the second parser.
--
-- This provides the implementation for '>>=' in
-- 'Control.Monad.Monad'.
combine :: Parser a -> (a -> Parser b) -> Parser b
combine a k = Parser $ \s ->
let (r, s') = runParser a s
in case r of
Bad -> (Bad, s')
Good g -> runParser (k g) s'
-- | @lookAhead p@ runs parser p. If p succeeds, lookAhead p succeeds
-- without consuming any input. If p fails without consuming any
-- input, so does lookAhead. If p fails and consumes input, lookAhead
-- also fails and consumes input. If this is undesirable, combine with
-- "try".
lookAhead :: Parser a -> Parser a
lookAhead a = Parser $ \s ->
let (r, s') = runParser a s
in case r of
Good g -> (Good g, s)
Bad -> (Bad, s')
-- | @good a@ always succeeds without consuming any input and has
-- result a. This provides the implementation for
-- 'Control.Monad.Monad.return' and
-- 'Control.Applicative.Applicative.pure'.
good :: a -> Parser a
good a = Parser $ \s -> (Good a, s)
-- | @throwString s@ always fails without consuming any input. The
-- failure contains a record of the string passed in by s. This
-- provides the implementation for 'Control.Monad.Monad.fail'.
throwString :: String -> Parser a
throwString e = Parser $ \s ->
let err = StrMsg e
s' = s { errors = err : errors s }
in (Bad, s')
-- | @parserMap f p@ applies function f to the result of parser
-- p. First parser p is run. If it succeeds, function f is applied to
-- the result and another parser is returned with the result. If it
-- fails, f is not applied and a failed parser is returned. This
-- provides the implementation for 'Prelude.Functor.fmap'.
parserMap :: (a -> b) -> Parser a -> Parser b
parserMap f l = Parser $ \s ->
let (r, s') = runParser l s
in case r of
Good g -> (Good (f g), s')
Bad -> (Bad, s')
-- | apply l r applies the function found in parser l to the result of
-- parser r. First the l parser is run. If it succeeds, it has a
-- resulting function. Then the r parser is run. If it succeeds, the
-- function from the l parser is applied to the result of the r
-- parser, and a new parser is returned with the result. If either
-- parser l or parser r fails, then a failed parser is returned. This
-- provides the implementation for '<*>' in
-- 'Control.Applicative.Applicative'.
apply :: Parser (a -> b) -> Parser a -> Parser b
apply fa a = Parser $ \s ->
let (r, s') = runParser fa s
in case r of
Good g ->
let (ra, sa) = runParser a s'
in case ra of
Good ga -> (Good (g ga), sa)
Bad -> (Bad, sa)
Bad -> (Bad, s')
-- | Fail with an unhelpful error message. Usually throw is more
-- useful, but this is handy to implement some typeclass instances.
genericThrow :: Parser a
genericThrow = throw UnknownError
-- | throw e always fails without consuming any input and returns a
-- failed parser with error state e.
throw :: Message -> Parser a
throw e = Parser $ \s ->
(Bad, s { errors = e : errors s })
noConsumed :: ParseSt -> ParseSt -> Bool
noConsumed old new = counter old >= counter new
-- | Runs the first parser. If it fails without consuming any input,
-- then runs the second parser. If the first parser succeeds, then
-- returns the result of the first parser. If the first parser fails
-- and consumes input, then returns the result of the first
-- parser. This provides the implementation for
-- '<|>' in 'Control.Applicative.Alternative'.
choice :: Parser a -> Parser a -> Parser a
choice a b = Parser $ \sOld ->
let (ra, sa) = runParser a sOld
in case ra of
Good g ->
let sNew = sa { errors = [] }
in (Good g, sNew)
Bad ->
if noConsumed sOld sa
then let sNew = sOld { errors = errors sa }
(rb, sb) = runParser b sNew
in case rb of
Good g' -> let sb' = sb { errors = [] }
in (Good g', sb')
Bad -> (Bad, sb)
else (Bad, sa)
-- | Runs the parser given. If it fails /without consuming any input/,
-- then applies the given function to the list of messages and replaces
-- the list of messages with the list returned by the
-- function. Otherwise, returns the result of the parser.
(<??>) :: Parser a -> ([Message] -> [Message]) -> Parser a
(<??>) l f = Parser $ \s ->
let (r, s') = runParser l s
in case r of
Good g -> (Good g, s')
Bad ->
if noConsumed s s'
then let s'' = s' { errors = f $ errors s' }
in (Bad, s'')
else (Bad, s')
infix 0 <??>
increment :: ParseSt -> ParseSt
increment old = old { counter = succ . counter $ old }
-- | Parses only pending short options. Fails without consuming any
-- input if there has already been a stopper or if there are no
-- pending short options. Fails without consuming any input if there
-- is a pending short option, but it does not match the short option
-- given. Succeeds and consumes a pending short option if it matches
-- the short option given.
pendingShortOpt :: ShortOpt -> Parser ()
pendingShortOpt so = Parser $ \s ->
let err = Expected ("pending short option: " ++ [unShortOpt so])
es = (Bad, s { errors = err : errors s })
gd newSt = (Good (), newSt)
in maybe es gd $ do
when (sawStopper s) Nothing
(first, rest) <- case pendingShort s of
[] -> Nothing
x:xs -> return (x, xs)
when (unShortOpt so /= first) Nothing
return (increment s { pendingShort = rest })
-- | Parses only non-pending short options. Fails without consuming
-- any input if, in order:
--
-- * there are pending short options
--
-- * there has already been a stopper
--
-- * there are no arguments left to parse
--
-- * the next argument is an empty string
--
-- * the next argument does not begin with a dash
--
-- * the next argument is a single dash
--
-- * the next argument is a short option but it does not match
-- the one given
--
-- * the next argument is a stopper
--
-- Otherwise, consumes the next argument, puts any remaining letters
-- from the argument into a pending short, and removes the first word
-- from remaining arguments to be parsed.
nonPendingShortOpt :: ShortOpt -> Parser ()
nonPendingShortOpt so = Parser $ \s ->
let err = Expected (msg ++ [unShortOpt so])
msg = "non pending short option: "
errRet = (Bad, s { errors = err : errors s })
gd n = (Good (), n)
in maybe errRet gd $ do
guard (noPendingShorts s)
guard (noStopper s)
(a, s') <- nextWord s
(maybeDash, word) <- case a of
[] -> Nothing
x:xs -> return (x, xs)
guard (maybeDash == '-')
(letter, arg) <- case word of
[] -> Nothing
x:xs -> return (x, xs)
guard (letter == unShortOpt so)
let s'' = s' { pendingShort = arg }
return s''
-- | Parses an exact long option. That is, the text of the
-- command-line option must exactly match the text of the
-- option. Returns the option, and any argument that is attached to
-- the same word of the option with an equal sign (for example,
-- @--follow=\/dev\/random@ will return @Just \"\/dev\/random\"@ for the
-- argument.) If there is no equal sign, returns Nothing for the
-- argument. If there is an equal sign but there is nothing after it,
-- returns @Just \"\"@ for the argument.
--
-- If you do not want your long option to have equal signs and
-- GNU-style option arguments, wrap this parser in something that will
-- fail if there is an option argument.
--
-- Fails without consuming any input if:
--
-- * there are pending short options
--
-- * a stopper has been parsed
--
-- * there are no arguments left on the command line
--
-- * the next argument on the command line does not begin with
-- two dashes
--
-- * the next argument on the command line is @--@ (a stopper)
--
-- * the next argument on the command line does begin with two
-- dashes but its text does not match the argument we're looking for
exactLongOpt :: LongOpt -> Parser (Maybe String)
exactLongOpt lo = Parser $ \s ->
let ert = (Bad, err)
err = s { errors = Expected msg : errors s } where
msg = "long option: " ++ unLongOpt lo
gd (g, n) = (Good g, n)
in maybe ert gd $ do
guard (noPendingShorts s)
guard (noStopper s)
(x, s') <- nextWord s
(word, afterEq) <- getLongOption x
guard (word == unLongOpt lo)
return (afterEq, s')
-- | Takes a single String and returns a tuple, where the first element
-- is the first two letters, the second element is everything from the
-- third letter to the equal sign, and the third element is Nothing if
-- there is no equal sign, or Just String with everything after the
-- equal sign if there is one.
splitLongWord :: String -> (String, String, Maybe String)
splitLongWord t = (f, s, r) where
(f, rest) = L.splitAt 2 t
(s, withEq) = L.break (== '=') rest
r = case withEq of
[] -> Nothing
_:xs -> Just xs
noPendingShorts :: ParseSt -> Bool
noPendingShorts st = case pendingShort st of
[] -> True
_ -> False
noStopper :: ParseSt -> Bool
noStopper = not . sawStopper
getLongOption :: String -> Maybe (String, Maybe String)
getLongOption str = do
guard (str /= "--")
let (pre, word, afterEq) = splitLongWord str
guard (pre == "--")
return (word, afterEq)
nextWord :: ParseSt -> Maybe (String, ParseSt)
nextWord st = case remaining st of
[] -> Nothing
x:xs ->
let s' = increment st { remaining = xs }
in return (x, s')
approxLongOptError ::
Set LongOpt
-> ParseSt
-> ParseSt
approxLongOptError set st = st { errors = newE : errors st } where
newE = Expected ex
ex = "a long option: " ++ longs
longs = intercalate ", " opts
opts = fmap unLongOpt . Set.toList $ set
-- | Examines the next word. If it matches a Text in the set
-- unambiguously, returns a tuple of the word actually found and the
-- matching word in the set and the accompanying text after the equal
-- sign (if any). If the Set is empty, this parser will always fail.
approxLongOpt ::
Set LongOpt
-> Parser (String, LongOpt, Maybe String)
approxLongOpt ts = Parser $ \s ->
let err = (Bad, approxLongOptError ts s)
gd (g, newSt) = (Good g, newSt)
in maybe err gd $ do
guard (noPendingShorts s)
(x, s') <- nextWord s
(word, afterEq) <- getLongOption x
opt <- makeLongOpt word
if Set.member opt ts
then return ((word, opt, afterEq), s')
else do
let p t = word `isPrefixOf` unLongOpt t
matches = Set.filter p ts
case Set.toList matches of
[] -> Nothing
(m:[]) -> return ((word, m, afterEq), s')
_ -> Nothing
-- | Parses only pending short option arguments. For example, for the
-- @tail@ command, if you enter the option @-c25@, then after parsing
-- the @-c@ option the @25@ becomes a pending short option argument
-- because it was in the same command line argument as the @-c@.
--
-- Fails without consuming any input if:
--
-- * a stopper has already been parsed
--
-- * there are no pending short option arguments
--
-- On success, returns the String of the pending short option argument
-- (this String will never be empty).
pendingShortOptArg :: Parser String
pendingShortOptArg = Parser $ \s ->
let ert = (Bad, err)
err = s { errors = Expected msg : errors s } where
msg = "pending short option argument"
gd (g, newSt) = (Good g, newSt)
in maybe ert gd $ do
guard (noStopper s)
case pendingShort s of
[] -> Nothing
xs ->
let newSt = increment s { pendingShort = "" }
in return (xs, newSt)
-- | Parses a \"stopper\" - that is, a double dash. Changes the internal
-- state of the parser to reflect that a stopper has been seen.
stopper :: Parser ()
stopper = Parser $ \s ->
let err = s { errors = Expected msg : errors s } where
msg = "stopper"
ert = (Bad, err)
gd (g, newSt) = (Good g, newSt)
in maybe ert gd $ do
guard (noPendingShorts s)
guard (noStopper s)
(x, s') <- nextWord s
guard (x == "--")
let s'' = s' { sawStopper = True }
return ((), s'')
-- | If a stopper has already been seen, change the internal state
-- back to indicating that no stopper has been seen.
resetStopper :: Parser ()
resetStopper = Parser $ \s ->
let s' = s { sawStopper = False }
in (Good (), s')
-- | try p behaves just like p, but if p fails, try p will not consume
-- any input.
try :: Parser a -> Parser a
try a = Parser $ \s ->
let (r, s') = runParser a s
in case r of
Good g -> (Good g, s')
Bad -> (Bad, s'') where
s'' = s { errors = errors s' }
-- | Returns the next string on the command line as long as there are
-- no pendings. Be careful - this will return the next string even if
-- it looks like an option (that is, it starts with a dash.) Consider
-- whether you should be using nonOptionPosArg instead. However this
-- can be useful when parsing command line options after a stopper.
nextArg :: Parser String
nextArg = Parser $ \s ->
let ert = (Bad, err)
err = s { errors = Expected msg : errors s } where
msg = "next argument"
gd (g, newSt) = (Good g, newSt)
in maybe ert gd $ do
guard (noPendingShorts s)
nextWord s
-- | If there are pending short options, fails without consuming any input.
--
-- Otherwise, if a stopper has NOT already been parsed, then returns
-- the next word if it is either a single dash or any other word that
-- does not begin with a dash. If the next word does not meet these
-- criteria, fails without consuming any input.
--
-- Otherwise, if a stopper has already been parsed, then returns the
-- next word, regardless of whether it begins with a dash or not.
nonOptionPosArg :: Parser String
nonOptionPosArg = Parser $ \s ->
let ert = (Bad, err)
err = s { errors = Expected msg : errors s } where
msg = "non option positional argument"
gd (g, newSt) = (Good g, newSt)
in maybe ert gd $ do
guard (noPendingShorts s)
(x, s') <- nextWord s
result <-
if sawStopper s
then return x
else case x of
[] -> return x
'-':[] -> return "-"
f:_ -> if f == '-'
then Nothing
else return x
return (result, s')
-- | manyTill p e runs parser p repeatedly until parser e succeeds.
--
-- More precisely, first it runs parser e. If parser e succeeds, then
-- manyTill returns the result of all the preceding successful parses
-- of p. If parser e fails (it does not matter whether e consumed any
-- input or not), manyTill runs parser p again. What happens next
-- depends on whether p succeeded or failed. If p succeeded, then the
-- loop starts over by running parser e again. If p failed (it does
-- not matter whether it consumed any input or not), then manyTill
-- fails. The state of the parser is updated to reflect its state
-- after the failed run of p, and the parser is left in a failed
-- state.
--
-- Should parser e succeed (as it will on a successful application of
-- manyTill), then the parser state will reflect that parser e
-- succeeded--that is, if parser e consumes input, that input will be
-- consumed in the parser that is returned. Wrap e inside of
-- @lookAhead@ if that is undesirable.
--
-- Be particularly careful to get the order of the arguments
-- correct. Applying this function to reversed arguments will yield
-- bugs that are very difficult to diagnose.
manyTill :: Parser a -> Parser end -> Parser [a]
manyTill (Parser r) (Parser f) = Parser $ \s ->
let Till g lS lF = parseTill s r f
in if lF then (Bad, lS) else (Good g, lS)
data Till a =
Till { _goods :: [a]
, _lastSt :: ParseSt
, _lastRunFailed :: Bool }
parseTill ::
ParseSt
-> (ParseSt -> (Result a, ParseSt))
-> (ParseSt -> (Result b, ParseSt))
-> Till a
parseTill s fr ff =
case ff s of
(Good _, s') -> Till [] s' False
(Bad, _) ->
case fr s of
(Bad, s'') -> Till [] s'' True
(Good g, s'') ->
let Till gs lS lF = parseTill s'' fr ff
in if counter s'' == counter s
then parseTillErr
else Till (g:gs) lS lF
parseTillErr :: a
parseTillErr =
error "parseTill applied to parser that takes empty list"
-- | several p runs parser p zero or more times and returns all the
-- results. This proceeds like this: parser p is run and, if it
-- succeeds, the result is saved and parser p is run
-- again. Repeat. Eventually this will have to fail. If the last run
-- of parser p fails without consuming any input, then several p runs
-- successfully. The state of the parser is updated to reflect the
-- successful runs of p. If the last run of parser p fails but it
-- consumed input, then several p fails. The state of the parser is
-- updated to reflect the state up to and including the run that
-- partially consumed input. The parser is left in a failed state.
--
-- This semantic can come in handy. For example you might run a parser
-- multiple times that parses an option and arguments to the
-- option. If the arguments fail to parse, then several will fail.
--
-- This function provides the implementation for
-- 'Control.Applicative.Alternative.many'.
several :: Parser a -> Parser [a]
several (Parser l) = Parser $ \s ->
let (result, finalGoodSt, finalBadSt) = parseRepeat s l
in if noConsumed finalGoodSt finalBadSt
then (Good result, finalGoodSt)
else (Bad, finalBadSt)
parseRepeat ::
ParseSt
-> (ParseSt -> (Result a, ParseSt))
-> ([a], ParseSt, ParseSt)
parseRepeat st1 f =
case f st1 of
(Good a, st') ->
if noConsumed st1 st'
then error $ "several applied to parser that succeeds without"
++ " consuming any input"
else
let (ls, finalGoodSt, finalBadSt) = parseRepeat st' f
in (a : ls, finalGoodSt, finalBadSt)
(Bad, st') -> ([], st1, st')
-- | Succeeds if there is no more input left.
end :: Parser ()
end = Parser $ \s ->
let ert = (Bad, err)
err = s { errors = Expected msg : errors s } where
msg = "end of input"
gd (g, newSt) = (Good g, newSt)
in maybe ert gd $ do
guard (noPendingShorts s)
guard (null . remaining $ s)
return ((), s)