Parsing 如何编写根据谓词验证其输入的解析器,否则将失败
我想编写一个解析器,生成一些数据结构,并通过对其运行谓词来验证其一致性。如果谓词返回false,解析器应该返回自定义错误对象,而不是失败,因为这可以通过^?实现 我正在寻找一些解析器上的运算符来实现这一点。 例如,假设我想解析一个整数列表,并检查它们是否不同。我想要这样的东西:Parsing 如何编写根据谓词验证其输入的解析器,否则将失败,parsing,scala,parser-combinators,Parsing,Scala,Parser Combinators,我想编写一个解析器,生成一些数据结构,并通过对其运行谓词来验证其一致性。如果谓词返回false,解析器应该返回自定义错误对象,而不是失败,因为这可以通过^?实现 我正在寻找一些解析器上的运算符来实现这一点。 例如,假设我想解析一个整数列表,并检查它们是否不同。我想要这样的东西: import util.parsing.combinator.RegexParsers object MyParser extends RegexParsers { val number: Parser[Int]
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测试得很好。当然它会出错,但在我看来,错误的关键是你有时可能会停止探索替代方案;如果所有的不成功都是错误,那么当你成功时,替代方案不会被探索,当你不成功时,它们也不会被探索,因此,在第一时间拥有替代方案是没有意义的。我觉得我可能错过了一些东西。