Scala解析器失败处理,悬空逗号

时间:2013-08-15 09:53:47

标签: parsing scala error-handling

Scala解析器组合入门,在继续之前需要更好地掌握失败/错误处理(注意:仍然进入Scala)

想要将“a = b,c = d”等字符串解析为元组列表,但在找到悬空逗号时标记用户。

在匹配逗号分隔的属性赋值时考虑匹配失败(“a = b,”):

 def commaList[T](inner: Parser[T]): Parser[List[T]] =
   rep1sep(inner, ",") | rep1sep(inner, ",") ~> opt(",") ~> failure("Dangling comma")

 def propertyAssignment: Parser[(String, String)] = ident ~ "=" ~ ident ^^ {
   case id ~ "=" ~ prop => (id, prop)
 }

用以下方法调用解析器:

 p.parseAll(p.commaList(p.propertyAssignment), "name = John , ")

导致失败,没有意外但是:

 string matching regex `\p{javaJavaIdentifierStart}\p{javaJavaIdentifierPart}*' expected but end of source found

commList函数在第一个属性赋值上成功,并在给定逗号的情况下开始重复,但下一个“ident”因下一个字符是源数据的结尾而失败。以为我可以在commList中找到第二个替代选项:

 rep1sep(inner, ",") ~> opt(",") ~> failure("Dangling comma")

尼克斯。想法?

2 个答案:

答案 0 :(得分:4)

Scalaz救援: - )

当您处理警告时,退出解析器失败并不是一个好主意。您可以轻松地将解析器与Scalaz writer monad组合在一起。使用此monad,您可以在解析器运行期间向部分结果添加消息。这些消息可能是信息,警告或错误。解析器完成后,您可以验证结果,是否可以使用或者是否包含严重问题。通过这样一个单独的vaildator步骤,您可以获得更好的错误消息。例如,您可以在字符串末尾接受任意字符,但在找到它们时会发出错误(例如“在最后一个语句后找到垃圾”)。错误消息对于用户来说比下面示例中的隐藏默认消息更有帮助(“字符串匹配正则表达式\ \ z'期望[...]”)。

以下是基于您问题中代码的示例:

    scala> :paste
    // Entering paste mode (ctrl-D to finish)

    import util.parsing.combinator.RegexParsers
    import scalaz._, Scalaz._

    object DemoParser extends RegexParsers {
      type Warning = String
      case class Equation(left : String, right : String)
      type PWriter = Writer[Vector[Warning], List[Equation]]
      val emptyList : List[Equation] = Nil

      def rep1sep2[T](p : => Parser[T], q : => Parser[Any]): Parser[List[T]] =
        p ~ rep(q ~> p) ^^ {case x~y => x::y}


      def name : Parser[String] = """\w+""".r
      def equation : Parser[Equation] = name ~ "=" ~ name ^^ { case n ~ _ ~ v => Equation(n,v) }
      def commaList : Parser[PWriter] = rep1sep(equation, ",") ^^ (_.set(Vector()))
      def danglingComma  : Parser[PWriter] = opt(",") ^^ (
         _ map (_ => emptyList.set(Vector("Warning: Dangling comma")))
                 getOrElse(emptyList.set(Vector("getOrElse(emptyList.set(Vector(""))))
      def danglingList : Parser[PWriter] =  commaList ~ danglingComma ^^ {
        case l1 ~ l2 => (l1.over ++ l2.over).set(l1.written ++ l2.written) }

      def apply(input: String): PWriter = parseAll(danglingList, input) match {
        case Success(result, _) => result
        case failure : NoSuccess => emptyList.set(Vector(failure.msg))
      }
    }

    // Exiting paste mode, now interpreting.

    import util.parsing.combinator.RegexParsers
    import scalaz._
    import Scalaz._
    defined module DemoParser

    scala> DemoParser("a=1, b=2")
    res2: DemoParser.PWriter = (Vector(),List(Equation(a,1), Equation(b,2)))

    scala> DemoParser("a=1, b=2,")
    res3: DemoParser.PWriter = (Vector(Warning: Dangling comma),List(Equation(a,1), Equation(b,2)))

    scala> DemoParser("a=1, b=2, ")
    res4: DemoParser.PWriter = (Vector(Warning: Dangling comma),List(Equation(a,1), Equation(b,2)))

    scala> DemoParser("a=1, b=2, ;")
    res5: DemoParser.PWriter = (Vector(string matching regex `\z' expected but `;' found),List())

    scala>

正如您所看到的,它可以很好地处理错误情况。如果要扩展示例,请为不同类型的错误添加案例类,并在消息中包含当前的解析器位置。

顺便说一下。白色空格的问题由RegexParsers类处理。如果您想更改空白处理,只需覆盖字段whiteSpace

答案 1 :(得分:1)

您的解析器不期望"name = John , "末尾的尾随空格。

您可以使用正则表达式来选择性地解析","后跟任意数量的空格:

def commaList[T](inner: Parser[T]): Parser[List[T]] =
  rep1sep(inner, ",") <~ opt(",\\s*".r ~> failure("Dangling comma"))

请注意,您可以通过将失败作为可选解析器的一部分来避免在此处使用替代(|)。如果可选部分消耗了一些输入然后失败,则整个解析器失败。