{- | Module : System.Console.HsOptions.ParserCore Description : Core features of the Parser module Copyright : (c) Jose Raymundo Cruz (jose.r.cruz01@gmail.com) License : Apache-2.0 Maintainer : Jose Raymundo Cruz (jose.r.cruz01@gmail.com) Stability : stable Portability : portable Core functions of the 'System.Console.HsOptions.Parser.Parser'. -} module System.Console.HsOptions.ParserCore where import Control.Monad(void) import Data.Char import Data.Maybe import Text.ParserCombinators.Parsec import qualified Data.Map as Map import System.Console.HsOptions.Types -- | Map of operation keywords to the corresponding operation token. operationsKeyMap :: [(String, OperationToken)] operationsKeyMap = [ ("+=!", OperationTokenAppend') , ("+=", OperationTokenAppend) , ("=+!", OperationTokenPrepend') , ("=+", OperationTokenPrepend) , ("=", OperationTokenAssign) ] -- | Returns a list of all operation keywords. operationKeywords :: [String] operationKeywords = [k | (k,_) <- operationsKeyMap] -- | Returns the corresponding operation token for the input keyword. operationTokenFor :: String -> OperationToken operationTokenFor s = head [v | (k, v) <- operationsKeyMap, k == s] -- | Parses a flag. -- -- A flag consist of a 'name', followed by an 'flagOperation' and -- a 'value'. -- -- Returns: -- -- * A 'FlagToken'. flag :: DefaultOp -> GenParser Char st Token flag defaultOp = do name <- flagName op <- flagOperation name defaultOp value <- flagValue return (FlagToken name op value) -- | Parses the name of a flag. -- -- The name must follow the pattern of: \"'flagPrefix' 'letter' -- 'validFlagChars'\". -- -- Returns: -- -- * A string with the flag name (without the prefix part). flagName :: GenParser Char st String flagName = do spaces flagPrefix l1 <- letter ls <- validFlagChars return (l1:ls) -- | Parses the flag prefix. -- -- A flag prefix is a double dash (--) or a single dash (-). flagPrefix :: GenParser Char st () flagPrefix = void $ try (string "--") <|> string "-" -- | Parses a flag operation. -- -- Flag operations will be parsed from the keywords defined in the -- 'operationsKeyMap'. flagOperation :: String -> DefaultOp -> GenParser Char st OperationToken flagOperation name defaultOp = try operation <|> do spaceOrEof return defaultOp' where defaultOp' = fromMaybe OperationTokenAssign (Map.lookup name defaultOp) -- | Parses a space or the end of file character. spaceOrEof :: GenParser Char st () spaceOrEof = void space <|> eof -- | Parses a word that is not a flag. -- -- \"Not a flag\" parses anything that is not parsed by 'flag' parser. notFlag :: GenParser Char st String notFlag = do spaces choice [ try (quotedString '"') , try twoDash , try singleDash , try nf1 , try nf2 , nf3 ] where nf1 = do c1 <- string "--" c2 <- satisfy (not . isLetter) rest <- allButSpace return (c1 ++ [c2] ++ rest) nf2 = do c1 <- string "-" c2 <- satisfy (\s -> (not . isLetter) s && s /= '-') rest <- allButSpace return (c1 ++ [c2] ++ rest) nf3 = do c1 <- noneOf "-" rest <- allButSpace return (c1:rest) twoDash = do c1 <- string "--" spaceOrEof return c1 singleDash = do c1 <- string "-" spaceOrEof return c1 -- | Parses a quoted string using the @character@ for quotes. -- -- Arguments: -- -- *@character@: the character used as quotes. quotedString :: Char -> GenParser Char st String quotedString c = do _ <- char c middle <- many (noneOf [c]) void (char c) <|> eof return middle -- | Parses a flag value. -- -- A flag value is parsed with 'notFlag'. If this parser fails then -- 'FlagValueTokenEmpty' is returned. flagValue :: GenParser Char st FlagValueToken flagValue = try getValue <|> return FlagValueTokenEmpty where getValue = do value <- notFlag return (FlagValueToken value) -- | Parses all characters until a space is found. allButSpace :: GenParser Char st String allButSpace = many (satisfy (not . isSpace)) -- | Parses a command line argument. -- -- A command argument is an argument that is not a flag ('notFlag'). cmdLineArg :: GenParser Char st Token cmdLineArg = do arg <- notFlag return (ArgToken arg) -- | Parses a flag operation. -- -- Returns: a token representing for that operation. operation :: GenParser Char st OperationToken operation = do op <- choice $ map aux operationKeywords return $ operationTokenFor op where aux op = try (spaces >> string op) -- | Parses and returns the characters that are valid for a flag. validFlagChars :: GenParser Char st String validFlagChars = many (oneOf "-_" <|> alphaNum) -- | Parses many flags and/or many positional arguments. -- -- Returns: -- -- * A list of tokens. manyToken :: DefaultOp -> GenParser Char st [Token] manyToken defaultOp = many (try (flag defaultOp) <|> try cmdLineArg) -- | Runs parser with the 'manyToken' parser. parseInput' :: DefaultOp -> String -> Either ParseError [Token] parseInput' defaultOp = parse (manyToken defaultOp ) "Top level parse error" -- | Parses the flags from the input stream of characters to a stream of -- tokens. -- -- Based on the syntax of the @flags input@ this parser should not fail. -- If there is any kind of errors while parsing an exception is thrown. -- -- Arguments: -- -- *@default_operations@: a map from flag name to default operation. -- -- *@input@: the input stream of characters. -- -- Returns: -- -- * A stream of tokens. -- -- Throws: -- -- * An exception if some error with the parser occurs. parseInput :: DefaultOp -> String -> [Token] parseInput defaultOp input = case parseInput' defaultOp input of Left err -> error (show err) Right result -> result