2

我正在尝试编写一个可以解析下面所有三个电话号码的trifecta解析器。当我尝试parsePhone通过调用使用parseString parsePhone mempty phoneNum2时,解析器在第一个破折号处失败并说它是预期'('的。

当我在 phoneNum1 上调用解析器时,它失败了')',说它是预期'('的。

为什么我的 skipSymbol 解析器失败了?我认为由于我使用了<|>,解析器不会检测到'('并继续前进。我尝试的技术skipSymbol一定会失败吗?

phoneNum1 = "(123) 456 7890"
phoneNum2 = "123-456-7890"
phoneNum3 = "1234567890"

type NumberingPlanArea = Integer
type Exchange = Integer
type LineNumber = Integer

data PhoneNumber =
  PhoneNumber NumberingPlanArea
              Exchange LineNumber
  deriving (Eq, Show)

parse3digits :: Parser Integer
parse3digits = read <$> replicateM 3 digit

skipSymbol :: Parser ()
skipSymbol =
      skipMany (char '(')
  <|> skipMany (char ')')
  <|> skipMany (char '-')
  <|> skipMany (char ' ')

parsePhone :: Parser PhoneNumber
parsePhone =
  skipSymbol >>
  parse3digits >>=
    \area -> skipSymbol >>
    parse3digits >>=
      \exch -> skipSymbol >>
      integer >>=
        \line ->
          pure $ PhoneNumber area exch line
4

1 回答 1

1

skipMany p应用解析器p零次或多次。在操作上,它是这样的:

  1. 尝试申请p
  2. 如果p成功,请重复步骤 1。
  3. 如果p在不消耗输入的情况下失败,则成功并返回()。(这就是“零或更多”中的“零”的含义。)
  4. 如果p在消耗一些输入后失败,则报告失败。

让我们看看如何skipSymbol对 input 进行操作)

  1. Parsec 尝试左手选择skipSymbol,即skipMany (char '(').
  2. skipMany (char '(')尝试应用char '(',因为输入字符是).
  3. 因为不消耗输入就char '('失败了,不消耗输入就skipMany (char '(') 成功了。这意味着skipSymbol不会尝试其他选择。
  4. 当前输入字符仍然是)(这是导致parse3Digits后来失败的原因)。

评论中所述,解决方法是将定义更改skipSymbol

skipSymbol :: Parser ()
skipSymbol = skipMany $ choice [char c | c <- "()- "]

此版本循环选择,而不是在循环之间进行选择。

于 2020-06-15T15:41:08.727 回答