如何使用 Parsec 仅在某个范围内解析一串整数?

How to parse a string of integers only in some range with Parsec?

我正在尝试通过解析 "YYYYMMDD" 格式的日期字符串来学习秒差距,例如“20161030”。我的解决方案是:

date :: Parser (String, String, String)
date = do
  year <- replicateM 4 digit
  month <- replicateM 2 digit
  day <- replicateM 2 digit
  return (year, month, day)

但问题是“20161356”也是我代码的有效日期。

如何验证 "MM" 在 1 到 12 之间; "DD" 介于 1 和 31 之间?

您可以按照 Thomas M. DuBuisson 的建议添加 guard

date :: Parser (String, String, String)
date = do
  year <- replicateM 4 digit
  month <- replicateM 2 digit
  day <- replicateM 2 digit
  guard $ read month > 0 && read month <= 12 && read day > 0 && read day <= 31
  return (year, month, day)

但是,这会导致错误消息:

λ> parse date "" "20161356"
Left (line 1, column 9):unknown parse error

我们可以通过结合 guard<?> 来解决这个问题,以提供更好的错误消息:

date :: Parser (String, String, String)
date = do
  year <- replicateM 4 digit
  month <- replicateM 2 digit
  guard (read month > 0 && read month <= 12) <?> "valid month (1–12)"
  day <- replicateM 2 digit
  guard (read day > 0 && read day <= 31) <?> "valid day (1–31)"
  return (year, month, day)

通过这种方法,您会得到更有用的错误消息:

λ> parse date "" "20161356"
Left (line 1, column 7):
expecting valid month (1–12)

作为旁注,我认为 在解析器中验证(或至少完整性检查)日期很有价值——它确保日期验证与其余部分相结合您的解析器和错误处理代码。您不能忘记稍后在您的代码中检查日期,并且错误已正确定位,如果您正在解析包含大量日期的文档,这将非常有用。