Parsing 如何编写根据谓词验证其输入的解析器,否则将失败

Parsing 如何编写根据谓词验证其输入的解析器,否则将失败,parsing,scala,parser-combinators,Parsing,Scala,Parser Combinators,我想编写一个解析器,生成一些数据结构,并通过对其运行谓词来验证其一致性。如果谓词返回false,解析器应该返回自定义错误对象,而不是失败,因为这可以通过^?实现 我正在寻找一些解析器上的运算符来实现这一点。 例如,假设我想解析一个整数列表,并检查它们是否不同。我想要这样的东西: import util.parsing.combinator.RegexParsers object MyParser extends RegexParsers { val number: Parser[Int]

我想编写一个解析器,生成一些数据结构,并通过对其运行谓词来验证其一致性。如果谓词返回false,解析器应该返回自定义错误对象,而不是失败,因为这可以通过^?实现

我正在寻找一些解析器上的运算符来实现这一点。 例如,假设我想解析一个整数列表,并检查它们是否不同。我想要这样的东西:

import util.parsing.combinator.RegexParsers

object MyParser extends RegexParsers {
  val number: Parser[Int] = """\d+""".r ^^ {_.toInt }
  val list = repsep(number, ",") ^!(checkDistinct, "numbers have to be unique")

  def checkDistinct(numbers: List[Int]) = (numbers.length == numbers.distinct.length)
}
那^!在上面的代码是什么,我正在寻找。如果解析器输出未验证,如何验证它并返回有用的错误消息?

^?接受错误消息生成器,提交将故障转换为错误:

^??接受错误消息生成器,提交将故障转换为错误:


实现这一点的一种方法是使用模式添加^!运算符到解析器[List[T]]repsep的返回类型。定义隐式定义,然后在需要时将其导入范围:

class ParserWithMyExtras[T](val parser:Parser[List[T]]){
  def ^!(predicate:List[T]=>Boolean, errorMessage:String) = {...}
}

implicit def augmentParser[T](parser:Parser[List[T]]) = 
  new ParserWithMyExtras(parser)

实现这一点的一种方法是使用模式添加^!运算符到解析器[List[T]]repsep的返回类型。定义隐式定义,然后在需要时将其导入范围:

class ParserWithMyExtras[T](val parser:Parser[List[T]]){
  def ^!(predicate:List[T]=>Boolean, errorMessage:String) = {...}
}

implicit def augmentParser[T](parser:Parser[List[T]]) = 
  new ParserWithMyExtras(parser)
下面是一个完整的实现:

implicit def validatingParsers[T](parser: Parser[T]) = new {
  def ^!(predicate: T => Boolean, error: => String) = Parser { in =>
    parser(in) match {
      case s @Success(result, sin) => predicate(result) match {
        case true => s
        case false => Error(error, sin)   // <--
      }
      case e @NoSuccess(_, _) => e
    }
  }
}
新接线员^!将左侧的解析器转换为应用谓词的新解析器

需要注意的一件重要事情是标有的行上的sin,这是一个完整的实现:

implicit def validatingParsers[T](parser: Parser[T]) = new {
  def ^!(predicate: T => Boolean, error: => String) = Parser { in =>
    parser(in) match {
      case s @Success(result, sin) => predicate(result) match {
        case true => s
        case false => Error(error, sin)   // <--
      }
      case e @NoSuccess(_, _) => e
    }
  }
}
新接线员^!将左侧的解析器转换为应用谓词的新解析器

需要注意的一件重要事情是,行中标有Parsers.commit的sin将失败转换为错误。 所以第一步是

commit(p ^?(condition, message))
然而,如果p给出了一个失败,这将给出一个错误,我想这不是您想要的,只有当p成功,然后检查失败时,您才想要一个错误。 所以你宁愿这样做

p into {result => commit(success(result) ^? (condition,message))}
这听起来可能有些做作,您也可以直接实现,只是复制^的实现?用错误替换失败

最后,您可能应该按照Dylan的建议添加操作符。如果您想在语法分析器之外执行此操作,我认为您需要一个mixin:

trait PimpedParsers { self: Parsers => 
   implicit def ...
}
否则,您无法轻松地引用单个解析器。

Parsers.commit转换失败到错误。 所以第一步是

commit(p ^?(condition, message))
然而,如果p给出了一个失败,这将给出一个错误,我想这不是您想要的,只有当p成功,然后检查失败时,您才想要一个错误。 所以你宁愿这样做

p into {result => commit(success(result) ^? (condition,message))}
这听起来可能有些做作,您也可以直接实现,只是复制^的实现?用错误替换失败

最后,您可能应该按照Dylan的建议添加操作符。如果您想在语法分析器之外执行此操作,我认为您需要一个mixin:

trait PimpedParsers { self: Parsers => 
   implicit def ...
}

否则,您无法轻松引用单个解析器。

请提问;越专注越好。谢谢,我在最后加了一个问题。请问一个问题;越专注越好。谢谢,我在最后添加了一个问题。我之前已经评论过,这在某些情况下不起作用,但这似乎是我测试代码中的一个错误。还可以查看下面我自己的解决方案,它实际上是用^来拉皮条的解析器!运算符。如果您确实用rep1Sep替换了|关键字,则当输入为关键字时,它将无法解析。我想你只有在确定你有一个列表后才想提交。@didierd我发现你的答案更全面。没问题,但惊讶的是你的代码被OP测试得很好。当然它会出错,但在我看来,错误的关键是你有时可能会停止探索替代方案;如果所有的不成功都是错误,那么当你成功时,替代方案不会被探索,当你不成功时,它们也不会被探索,因此,在第一时间拥有替代方案是没有意义的。我觉得我可能遗漏了一些东西。我之前说过,这在某些情况下不起作用,但这似乎是我的测试代码中的一个错误。还可以查看下面我自己的解决方案,它实际上是用^来拉皮条的解析器!运算符。如果您确实用rep1Sep替换了|关键字,则当输入为关键字时,它将无法解析。我想你只有在确定你有一个列表后才想提交。@didierd我发现你的答案更全面。没问题,但惊讶的是你的代码被OP测试得很好。当然它会出错,但在我看来,错误的关键是你有时可能会停止探索替代方案;如果所有的不成功都是错误,那么当你成功时,替代方案不会被探索,当你不成功时,它们也不会被探索,因此,在第一时间拥有替代方案是没有意义的。我觉得我可能错过了一些东西。