如何在解析时跳过指定的符号

How do I skip specified symbols while parsing

我正在尝试编写一个 trifecta 解析器,它可以解析下面所有三个 phone 数字。当我尝试通过调用 parseString parsePhone mempty phoneNum2 来使用 parsePhone 时,解析器在第一个破折号处失败并表示它期望 '('.

当我在 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

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

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

让我们看看skipSymbol如何对输入)进行操作。

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

如前所述 ,修复方法是将 skipSymbol 的定义更改为

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

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