如何使用 Parsec 概括重复解析
How to generalize repetitive parsing with Parsec
我正在学习通过解析文本文件中的行来使用 Parsec。我有以下内容:
import Text.Parsec (ParseError, parse)
import Text.Parsec.String (Parser)
import Text.Parsec.Char (anyChar, digit, char, oneOf)
import Control.Monad (void)
import qualified Text.Parsec.Combinator as C
data Action =
ActionA Int Int
| ActionB Int Int Int Int Int
| ActionC Int Int Int
deriving (Show)
parseWithEof :: Parser a -> String -> Either ParseError a
parseWithEof p = parse (p <* C.eof) ""
parseActionA :: Parser Action
parseActionA = do
char 'A'
void $ oneOf " "
a <- C.many1 digit
void $ oneOf " "
b <- C.many1 digit
return $ ActionA (read a) (read b)
parseActionB :: Parser Action
parseActionB = do
char 'B'
void $ oneOf " "
a <- C.many1 digit
void $ oneOf " "
b <- C.many1 digit
void $ oneOf " "
c <- C.many1 digit
void $ oneOf " "
d <- C.many1 digit
void $ oneOf " "
e <- C.many1 digit
return $ ActionB (read a) (read b) (read c) (read d) (read e)
parseActionC :: Parser Action
parseActionC = do
char 'C'
void $ oneOf " "
a <- C.many1 digit
void $ oneOf " "
b <- C.many1 digit
void $ oneOf " "
c <- C.many1 digit
return $ ActionC (read a) (read b) (read c)
我希望能够概括这些解析函数,因为我觉得它们是重复的。我不知道这是否可能,或者如何可能。
我也想知道是否可以有这样的功能:
parseAction :: String -> Either ParseError Action
parseAction input =
parseWithEof parseActionA input
<some operator|combinator> parseWithEof parseActionB input
<some operator|combinator> parseWithEof parseActionC input
因此,当 parseAction
接收到一个字符串作为参数时,它将尝试使用不同的解析器对其进行解析。如果没有解析器可以解析输入,我希望它为 return(左 ParseError),如果解析器成功解析输入,则为(右动作)。
可能吗?
使用应用组合子你可以写:
num = do oneOf " "; fmap read (C.many1 digit)
parseActionA = ActionA <$> (char 'A' >> num) <*> num
parseActionB = ActionB <$> (char 'B' >> num) <*> num <*> num <*> num <*> num
对于你的第二个问题,只需使用 <|>
和 try
parseAction = try parseActionA <|> try parseActionB <|> try parseActionC
注意 - 最后一个解析器不需要 try
,但拥有它也无妨。此外,如果您对解析器的工作方式了解足够多,您也许可以去掉一些 try
s.
我正在学习通过解析文本文件中的行来使用 Parsec。我有以下内容:
import Text.Parsec (ParseError, parse)
import Text.Parsec.String (Parser)
import Text.Parsec.Char (anyChar, digit, char, oneOf)
import Control.Monad (void)
import qualified Text.Parsec.Combinator as C
data Action =
ActionA Int Int
| ActionB Int Int Int Int Int
| ActionC Int Int Int
deriving (Show)
parseWithEof :: Parser a -> String -> Either ParseError a
parseWithEof p = parse (p <* C.eof) ""
parseActionA :: Parser Action
parseActionA = do
char 'A'
void $ oneOf " "
a <- C.many1 digit
void $ oneOf " "
b <- C.many1 digit
return $ ActionA (read a) (read b)
parseActionB :: Parser Action
parseActionB = do
char 'B'
void $ oneOf " "
a <- C.many1 digit
void $ oneOf " "
b <- C.many1 digit
void $ oneOf " "
c <- C.many1 digit
void $ oneOf " "
d <- C.many1 digit
void $ oneOf " "
e <- C.many1 digit
return $ ActionB (read a) (read b) (read c) (read d) (read e)
parseActionC :: Parser Action
parseActionC = do
char 'C'
void $ oneOf " "
a <- C.many1 digit
void $ oneOf " "
b <- C.many1 digit
void $ oneOf " "
c <- C.many1 digit
return $ ActionC (read a) (read b) (read c)
我希望能够概括这些解析函数,因为我觉得它们是重复的。我不知道这是否可能,或者如何可能。
我也想知道是否可以有这样的功能:
parseAction :: String -> Either ParseError Action
parseAction input =
parseWithEof parseActionA input
<some operator|combinator> parseWithEof parseActionB input
<some operator|combinator> parseWithEof parseActionC input
因此,当 parseAction
接收到一个字符串作为参数时,它将尝试使用不同的解析器对其进行解析。如果没有解析器可以解析输入,我希望它为 return(左 ParseError),如果解析器成功解析输入,则为(右动作)。
可能吗?
使用应用组合子你可以写:
num = do oneOf " "; fmap read (C.many1 digit)
parseActionA = ActionA <$> (char 'A' >> num) <*> num
parseActionB = ActionB <$> (char 'B' >> num) <*> num <*> num <*> num <*> num
对于你的第二个问题,只需使用 <|>
和 try
parseAction = try parseActionA <|> try parseActionB <|> try parseActionC
注意 - 最后一个解析器不需要 try
,但拥有它也无妨。此外,如果您对解析器的工作方式了解足够多,您也许可以去掉一些 try
s.