【问题标题】:Parboiled2: How to process dependent fields?Parboiled2:如何处理依赖字段?
【发布时间】:2018-02-21 02:06:20
【问题描述】:

我正在尝试使用出色的 parboiled2 库解析文件格式,其中某些字段的存在取决于已处理的一个或多个字段的值。

例如,假设我有两个字段,其中第一个是指示第二个是否存在的标志。也就是说,如果第一个字段是true,那么第二个字段(在这个例子中是一个整数值)存在并且必须被处理 - 但如果它是false,那么第二个字段不存在于全部。请注意,第二个字段不是可选——它要么必须被处理(如果第一个字段是true)要么不能被处理已处理(如果第一个字段是 false)。

因此,如果第三个字段(我们假设始终存在)是带引号的字符串,则以下两行均有效:

true 52 "Some quoted string"
false "Some other quoted string"

但这将是无效的:

false 25 "Yet another quoted string"

忽略第三个字段,如何编写规则来解析前两个? (我无法从文档中看出,谷歌搜索到目前为止还没有帮助......)

更新:我应该澄清我不能使用如下规则,因为我正在解析的格式实际上比我的示例复杂得多:

import org.parboiled2._

class MyParser(override val input: ParserInput)
extends Parser {

  def ws = // whitepsace rule, puts nothing on the stack.

  def intField = // parse integer field, pushes Int onto stack...

  def dependentFields = rule {
    ("true" ~ ws ~ intField) | "false" ~> //etc.
  }
}

更新 2:我已修改以下内容以使我的意图更清晰:

我正在寻找的是与以下(不存在的)规则等效的有效规则,该规则仅在满足条件时才执行匹配:

import org.parboiled2._

class MyParser(input: ParserInput)
extends Parser {

  def ws = // whitepsace rule, puts nothing on the stack.

  def intField = // parse integer field, pushes Int onto stack...

  def boolField = // parse boolean field, pushes Boolean onto stack...

  def dependentFields = rule {
    boolField ~> {b =>

      // Match "ws ~ intField" only if b is true. If match succeeds, push Some(Int); if match
      // fails, the rule fails. If b is false, pushes None without attempting the match.
      conditional(b, ws ~ intField)
    }
  }
}

也就是说,ws ~ intField 仅在 boolField 产生 true 值时才匹配。这样的事情可能吗?

【问题讨论】:

    标签: scala parboiled2


    【解决方案1】:

    是的,你可以在test解析器动作的帮助下实现这样的功能:

    def conditional[U](bool: Boolean, parse: () => Rule1[U]): Rule1[Option[U]] = rule {
      test(bool) ~ parse() ~> (Some(_)) | push(None)
    }
    

    根据文档的Meta-Rules section,它只能通过传递一个函数来产生规则。您必须按如下方式定义dependentFields 规则:

    def dependentFields = rule {
      boolField ~> (conditional(_, () => rule { ws ~ intField }))
    }
    

    更新:

    虽然test(pred) ~ opt1 | opt2 是一种常用技术,它确实回溯并尝试应用opt2,如果test 成功test,但opt1 失败。这里有两种可能的解决方案来防止这种回溯。

    您可以使用~!~ 规则组合器,它具有“剪切”语义并禁止回溯自身:

    def conditional2[U](bool: Boolean, parse: () => Rule1[U]): Rule1[Option[U]] = rule {
      test(bool) ~!~ parse() ~> (Some(_)) | push(None)
    }
    

    或者您实际上在规则之外使用if 来检查布尔参数并返回两个可能的规则之一:

    def conditional3[U](bool: Boolean, parse: () => Rule1[U]): Rule1[Option[U]] =
      if (bool) rule { parse() ~> (Some(_: U)) } 
      else rule { push(None) }
    

    【讨论】:

    • 谢谢!这(几乎)有效!我知道元规则需要函数(文档对此很清楚)。我想我没有意识到我可以使用 |如果test 操作失败,则推送值的子句。那很好!不幸的是,如果解析操作本身失败,该子句也将执行 - 所以我无法区分错误的测试条件和解析 ws ~ intField 规则失败。有什么建议吗?
    • 然后,当我测试它时,它似乎确实完全符合要求。你能详细说明一下它是如何工作的吗?尤其是我有点不明白怎么了|如果解析规则(在本例中为ws ~ intField)不匹配,子句似乎不会执行?感谢您的帮助!
    • @MikeAllen 我可以在您的评论中重现该问题。可能它对您有用,因为您在 dependentFields 规则之后匹配了 EOI 或其他不兼容的东西,这导致解析器失败。我现在用两种可能的解决方案更新了这个问题的答案。
    • 感谢您的更新 - 太棒了!我将不得不检查我的测试代码中发生了什么,但我期待你描述的行为。使用 ~!~ 对我有用。我不确定在一个函数中包含多个 rules 是否合法(例如,if...else 表达式),但这也很高兴知道。再次感谢!
    • 嗯。我得到了正确的答案,但似乎是出于错误的原因。在test 条件之后使用~~!~ 没有区别。我有两个版本的repo on GitHubmaster 分支有剪切,nocut 分支没有)。如果有时间,如果您能检查结果并查看我可能做错了什么,我将不胜感激。谢谢!
    【解决方案2】:

    我会这样做:

    extends Parser {
    
      def dependentFields: Rule1[(Boolean, Option[Int], String)] = rule {
         ("true" ~ ws ~ trueBranch | "false" ~ ws ~ falseBranch)
      }
    
      def trueBranch = rule {
         intField ~ ws ~ stringField ~> { (i, s) => (true, Some(i), s) }
      }
    
      def falseBranch = rule {
         stringField ~> { s => (false, None, s) }
      }
    }
    

    【讨论】:

    • @MikeAllen 如果您的逻辑仍在布尔值上分支,我会说我的方法(和您的原始方法)是有效的。否则我没有足够的数据结构输入来对如何解析它做出有效的假设。
    • 也许我的例子太琐碎了(正如我在更新中指出的那样)。碰巧的是,我没有在布尔值上进行分支。在我的实际情况中,更准确地说,第一个字段实际上是 10 个标志的位字段,每个标志都确定其他数据是否 - 距位字段有一定距离(即不紧随其后) -存在。所以我想要传达的是,只有在某些表达式为真时才需要解析字段。这是否澄清了我的问题?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-19
    • 2016-05-02
    • 2018-07-16
    • 1970-01-01
    • 1970-01-01
    • 2014-08-02
    相关资源
    最近更新 更多