0

我是 Haskell 的初学者,所以很明显我做错了什么......

在尝试解析时,我陷入了前瞻问题"1:1,2, 2:18, 3:100"[(1,1), (1,2), (2,18), (3,100)]

要知道一个数字是否是诗句编号,它应该向前看一个冒号,因为那样它是一个章节编号。

问题在于最后一个函数verseNr,如果后面没有冒号,它应该解析+消耗数字,否则失败而不消耗任何东西(将数字作为章节号解析refGroupByChapter)。

除了这个问题,它似乎工作得很好:)

import Text.ParserCombinators.Parsec

main = do
  case (parse refString "(unknown)" "1:1,2, 2:18, 3:100") of
    Left  e -> do putStr "parse error at "; print e
    Right x -> print x  -- expecting: [(1,1), (1,2), (2,18), (3,100)]

refString :: GenParser Char st [(Int, Int)]
refString = do
  refGroups <- many refGroupByChapter
  eof
  return $ concat $ map flatten refGroups
  where flatten (_, [])   = []
        flatten (c, v:vs) = (c, v):(flatten (c, vs))

refGroupByChapter :: GenParser Char st (Int, [Int])
refGroupByChapter = do
  chapterNum <- many digit
  char ':'
  verseNums <- verseNrs
  return ((read chapterNum :: Int), verseNums)

verseNrs :: GenParser Char st [Int]
verseNrs = do
  first <- verseNr
  remaining <- remainingVerseNrs
  return (first:remaining)
  where
    remainingVerseNrs = do  -- allow for spaces around the commas
      (spaces >> oneOf "," >> spaces >> verseNrs) <|> (return [])
    verseNr = try $ do
      n <- many1 digit
      notFollowedBy $ char ':'  -- if followed by a ':' it's a chapter number
      return (read n :: Int)
4

2 回答 2

1

您的特定问题的诀窍是使用sepBy函数族。您正在解析用逗号分隔的数字列表,这正是sepBy它的用途。经文列表具有以下属性:必须至少有一个经文编号,并且结尾有一个逗号。结合两者,我们意识到我们需要这个sepEndBy1功能。这些函数通常写在中缀位置,所以你的代码看起来像这样:

verseNrs = verseNr `sepEndBy1` (spaces >> char ',' >> spaces)

我认为您无需更改任何其他内容即可使代码正常工作。

其他一些次要样式说明:您有一些不必要的括号。这不重要,它只是让我个人感到恼火。例如,case ... of不需要围绕...位的括号。此外,使用时不需要类型签名read——编译器可以推断类型。也就是说,由于verseNrs返回[Int],编译器和我都完全清楚read n生成Int. 没有必要明确地说出来。

于 2013-01-07T04:03:38.353 回答
1

有两个问题。首先,该函数verseNr可能并不总是成功解析一个数字,因为该数字后面可能跟一个:. 虽然该verseNrs函数始终假定verseNr通过模式匹配来成功解析数字作为first. 其次,该函数verseNrs不处理字符串中最后一位数字的大小写,该数字后面没有,.

我相信Tikhon的建议是最好的。但是,如果您坚持手动实现它,我会这样做。

import Control.Monad (void)
import Control.Applicative ((<*))

verseNrs :: GenParser Char st [Int]
verseNrs = do
    first <- fmap Just (try (many1 digit
                          <* spaces
                          <* (eof <|> void (char ','))
                          <* spaces))
             <|> return Nothing
    case first of
      Just first -> fmap (read first:) verseNrs
      Nothing    -> return []

其余代码相同。

于 2013-01-07T15:13:31.090 回答