scala-parser-combinators:opt()和“|”组合,奇怪的结果

时间:2015-06-23 22:50:27

标签: scala parsing

我正在尝试实现ISO 8601日期/时间格式解析器,并在可选的时间部分遇到一些麻烦。我构建了一个简单的问题示例:

class ISO8601 extends RegexParsers {
  val hour = s"[0-9]{2}".r ^^ {_.toInt}
  val minute = s"[0-9]{2}".r ^^ {_.toInt}
  val timeSep = ":"
  val test = (hour ~ opt(timeSep ~> minute) |
    hour ~ opt(minute)) ^^ {
    case hh ~ mmOpt =>
      val mm = mmOpt.getOrElse(0)
      (hh, mm, 0, 0)
  }
}

我想要做的是允许以下时间格式:

  • HH
  • HHMM
  • HH:MM

我的解析器成功解析“23”和“23:30”,但拒绝解析“2330”:

isoRes: iso.ParseResult[(Int, Int, Int, Int)] = [1.3] failure: string matching regex `\z' expected but `3' found

2330

不应解析该失败的解析器并尝试匹配第二个选项(在“|”之后)?

3 个答案:

答案 0 :(得分:2)

问题是opt()解析器。首先,我假设你这样称呼它:

parseAll(ISO8601.test, new CharSequenceReader("2330"))

那会发生什么? parseAll将尝试解析阅读器中的所有输入,即直到它不再返回字符为止。

因此使用test解析器,它尝试第一个替代方案并解析" 23"。然后没有分隔符,因此opt()解析器将返回None并且第一个替代成功。所以没有必要检查第二种选择。然后读者中仍然存在字符30,但解析器应该在输入的末尾!这就是你失败的原因。

现在尝试:

println(ISO8601.parseAll(ISO8601.rep(ISO8601.test), new CharSequenceReader("2330")))

输出:

[1.5] parsed: List((23,0,0,0), (30,0,0,0))

所以你看到第一个替代品被使用过2次。

那你怎么解决呢?另一种方法是使分钟可选,分钟中的分隔符也可选。

def test = hour ~ opt(opt(timeSep) ~> minute) map {
    case h ~ None => (h, 0, 0, 0)
    case h ~ Some(mm) => (h, mm, 0, 0)
}

使用" 23"," 2330"," 23:30"连续运行,你得到:

[1.3] parsed: (23,0,0,0)
[1.6] parsed: (23,30,0,0)
[1.5] parsed: (23,30,0,0)

顺便说一下,您应该在hourminute解析器中添加一些检查,否则"9999"是有效输入。

答案 1 :(得分:0)

我这样做:

val time = "^([0-9]{2}):?([0-9]{0,2})$".r
def parse(str: String) = str match {
  case time(h, m) => (h, if (m == "") 0 else m, 0, 0)
}

parse("12")
parse("13:21")
parse("1456")

答案 2 :(得分:0)

嗯,我想我已经弄明白了。

原因是第一个替代匹配仅消费" 23", 所以测试术语是" 23"其余的输入是" 30"。 然后解析器期望输入结束但看到剩余的" 30"。

class ISO8601 extends RegexParsers {
  val hour = s"[0-9]{2}".r ^^ {_.toInt}
  val minute = s"[0-9]{2}".r ^^ {_.toInt}
  val timeSep = ":"
  val test = (hour ~ (timeSep ~> minute) |
    hour ~ success(0) |
    hour ~ minute) ^^ { case hh ~ mm => (hh, mm, 0, 0) }
}

然而,如果我加上秒和毫秒,这个词似乎非常不优雅:

val time = (hour ~ (timeSep ~> minute) ~ (timeSep ~> second) ~ (msSep ~> ms) |
  hour ~ (timeSep ~> minute) ~ (timeSep ~> second) ~ success(0) |
  hour ~ (timeSep ~> minute) ~ success(0) ~ success(0) |
  hour ~ success(0) ~ success(0) ~ success(0) |
  hour ~ minute ~ second ~ (msSep ~> ms) |
  hour ~ minute ~ second ~ (msSep ~> success(0)) |
  hour ~ minute ~ second ~ success(0) |
  hour ~ minute ~ success(0) ~ success(0)
  ) ^^ {
  case hh ~ mm ~ ss ~ sss =>
    (hh, mm, ss, sss)
}

我没有办法解决这个问题。