为什么 Parsec 的 sepBy 停止并且不解析所有元素?
Why Parsec's sepBy stops and does not parse all elements?
我正在尝试解析一些逗号分隔的字符串,它可能包含也可能不包含具有图像尺寸的字符串。例如 "hello world, 300x300, good bye world"
.
我写了下面的小程序:
import Text.Parsec
import qualified Text.Parsec.Text as PS
parseTestString :: Text -> [Maybe (Int, Int)]
parseTestString s = case parse dimensStringParser "" s of
Left _ -> [Nothing]
Right dimens -> dimens
dimensStringParser :: PS.Parser [Maybe (Int, Int)]
dimensStringParser = (optionMaybe dimensParser) `sepBy` (char ',')
dimensParser :: PS.Parser (Int, Int)
dimensParser = do
w <- many1 digit
char 'x'
h <- many1 digit
return (read w, read h)
main :: IO ()
main = do
print $ parseTestString "300x300,40x40,5x5"
print $ parseTestString "300x300,hello,5x5,6x6"
根据 optionMaybe
文档,它 returns Nothing
如果它无法解析,所以我希望得到这个输出:
[Just (300,300),Just (40,40),Just (5,5)]
[Just (300,300),Nothing, Just (5,5), Just (6,6)]
但我得到:
[Just (300,300),Just (40,40),Just (5,5)]
[Just (300,300),Nothing]
即第一次失败后解析停止。所以我有两个问题:
- 为什么会这样?
- 如何为这种情况编写正确的解析器?
我猜 optionMaybe dimensParser
在输入 "hello,..."
时会尝试 dimensParser
。那失败了,所以 optionMaybe
returns 成功 Nothing
,并且不消耗输入的任何部分。
最后一段是关键:返回Nothing
后,待解析的输入字符串仍然是"hello,..."
.
此时 sepBy
尝试解析 char ','
,但失败了。因此,它推断列表结束,并终止输出列表,而不消耗任何更多输入。
如果您想跳过其他实体,您需要一个 "consuming" 解析器 returns Nothing
而不是 optionMaybe
。但是,该解析器需要知道要消耗多少:在您的情况下,直到逗号。
也许你需要一些类似的(未经测试)
( try (Just <$> dimensParser)
<|> (noneOf "," >> return Nothing))
`sepBy` char ','
为了回答这个问题,拿一张纸,写下输入,充当哑巴解析器是很方便的。
我们从“300x300,hello,5x5,6x6”开始,我们当前的解析器是optionMaybe ...
。我们的 dimensParser
是否正确解析维度?让我们检查一下:
w <- many1 digit -- yes, "300"
char 'x' -- yes, "x"
h <- many1 digit -- yes, "300"
return (read w, read h) -- never fails
我们已经成功解析了第一个维度。下一个标记是 ,
,因此 sepBy
也成功解析了它。接下来,我们尝试解析 "hello" 并失败:
w <- many1 digit -- no. 'h' is not a digit. Stop
接下来,sepBy
尝试解析 ,
,但这是不可能的,因为下一个标记是 'h'
,而不是 ,
。因此,sepBy
停止。
我们还没有解析 所有 输入,但这实际上不是必需的。如果您使用
,您会收到正确的错误消息
parse (dimensStringParser <* eof)
无论哪种方式,如果您想丢弃列表中不是维度的任何内容,您可以使用
dimensStringParser1 :: Parser (Maybe (Int, Int))
dimensStringParser1 = (Just <$> dimensParser) <|> (skipMany (noneOf ",") >> Nothing)
dimensStringParser = dimensStringParser1 `sepBy` char ','
我正在尝试解析一些逗号分隔的字符串,它可能包含也可能不包含具有图像尺寸的字符串。例如 "hello world, 300x300, good bye world"
.
我写了下面的小程序:
import Text.Parsec
import qualified Text.Parsec.Text as PS
parseTestString :: Text -> [Maybe (Int, Int)]
parseTestString s = case parse dimensStringParser "" s of
Left _ -> [Nothing]
Right dimens -> dimens
dimensStringParser :: PS.Parser [Maybe (Int, Int)]
dimensStringParser = (optionMaybe dimensParser) `sepBy` (char ',')
dimensParser :: PS.Parser (Int, Int)
dimensParser = do
w <- many1 digit
char 'x'
h <- many1 digit
return (read w, read h)
main :: IO ()
main = do
print $ parseTestString "300x300,40x40,5x5"
print $ parseTestString "300x300,hello,5x5,6x6"
根据 optionMaybe
文档,它 returns Nothing
如果它无法解析,所以我希望得到这个输出:
[Just (300,300),Just (40,40),Just (5,5)]
[Just (300,300),Nothing, Just (5,5), Just (6,6)]
但我得到:
[Just (300,300),Just (40,40),Just (5,5)]
[Just (300,300),Nothing]
即第一次失败后解析停止。所以我有两个问题:
- 为什么会这样?
- 如何为这种情况编写正确的解析器?
我猜 optionMaybe dimensParser
在输入 "hello,..."
时会尝试 dimensParser
。那失败了,所以 optionMaybe
returns 成功 Nothing
,并且不消耗输入的任何部分。
最后一段是关键:返回Nothing
后,待解析的输入字符串仍然是"hello,..."
.
此时 sepBy
尝试解析 char ','
,但失败了。因此,它推断列表结束,并终止输出列表,而不消耗任何更多输入。
如果您想跳过其他实体,您需要一个 "consuming" 解析器 returns Nothing
而不是 optionMaybe
。但是,该解析器需要知道要消耗多少:在您的情况下,直到逗号。
也许你需要一些类似的(未经测试)
( try (Just <$> dimensParser)
<|> (noneOf "," >> return Nothing))
`sepBy` char ','
为了回答这个问题,拿一张纸,写下输入,充当哑巴解析器是很方便的。
我们从“300x300,hello,5x5,6x6”开始,我们当前的解析器是optionMaybe ...
。我们的 dimensParser
是否正确解析维度?让我们检查一下:
w <- many1 digit -- yes, "300"
char 'x' -- yes, "x"
h <- many1 digit -- yes, "300"
return (read w, read h) -- never fails
我们已经成功解析了第一个维度。下一个标记是 ,
,因此 sepBy
也成功解析了它。接下来,我们尝试解析 "hello" 并失败:
w <- many1 digit -- no. 'h' is not a digit. Stop
接下来,sepBy
尝试解析 ,
,但这是不可能的,因为下一个标记是 'h'
,而不是 ,
。因此,sepBy
停止。
我们还没有解析 所有 输入,但这实际上不是必需的。如果您使用
,您会收到正确的错误消息parse (dimensStringParser <* eof)
无论哪种方式,如果您想丢弃列表中不是维度的任何内容,您可以使用
dimensStringParser1 :: Parser (Maybe (Int, Int))
dimensStringParser1 = (Just <$> dimensParser) <|> (skipMany (noneOf ",") >> Nothing)
dimensStringParser = dimensStringParser1 `sepBy` char ','