【问题标题】:Trying to group terms by OR operator尝试按 OR 运算符对术语进行分组
【发布时间】:2020-09-24 02:20:29
【问题描述】:

我正在尝试解析一个字符串,以便我可以轻松识别由“OR”分隔的术语。

我目前有以下规则和解析器类设置:

class Parser < Parslet::Parser
  rule(:space)     { str(' ').repeat(1) }
  rule(:word)      { match['^\s"'].repeat(1) }
  rule(:or_op)     { space >> str('OR') >> space }
  rule(:term)      { word.as(:term) >> or_op.absent? }
  rule(:or_terms)  { (word.maybe >> or_op >> word).repeat(1).as(:or_terms) }
  rule(:clause)    { (or_terms | term).as(:clause) }
  rule(:query)     { (clause >> space.maybe).repeat.as(:query) }
  root(:query)

  def self.parse_tree_for(query)
    new.parse(query)
  end
end

这目前允许我这样做:

Parser.parse_tree_for('wow -bob')
=> {:query=>[{:clause=>{:term=>"wow"@0}}]}

Parser.parse_tree_for('wow OR lol')
=> {:query=>[{:clause=>{:or_terms=>"wow OR lol"@0}}]}

Parser.parse_tree_for('wow OR lol OR omg')
=> {:query=>[{:clause=>{:or_terms=>"wow OR lol OR omg"@0}}]}

这行得通,但理想情况下,我想要一些可以单独给我这些术语但带有 or 标志的东西:{:query=&gt;[{:clause=&gt;{:term=&gt;"wow",:or=&gt;true}},{:clause=&gt;{:term=&gt;"lol",:or=&gt;true},{:clause=&gt;{:term=&gt;"omg",:or=&gt;true}}]}

这是应该用变压器做的吗?比如,只需在转换器中设置一个规则来执行split(' OR ') 还是有更好的方法来设置我的规则?

【问题讨论】:

    标签: regex ruby peg parslet


    【解决方案1】:

    你需要一个as 在你想明确捕获的每一件事上。

    您的or_term 逻辑有点古怪。总是把必需的东西放在第一位,然后是可选的东西。

    试试这个...

    require 'parslet'
    
    class Parser < Parslet::Parser
      rule(:space)     { str(' ').repeat(1) }
      rule(:word)      { match['^\s"'].repeat(1).as(:word) }
      rule(:or_op)     { space >> str('OR') >> space }
      rule(:term)      { word.as(:term) >> or_op.absent? }
      rule(:or_terms)  { (word >> (or_op >> word).repeat(0)).as(:or_terms) }
      rule(:clause)    { (term | or_terms).as(:clause) }
      rule(:query)     { (clause >> space.maybe).repeat.as(:query) }
      root(:query)
    
      def self.parse_tree_for(query)
        new.parse(query)
      end
    end
    
    puts Parser.parse_tree_for('wow OR lol OR omg')
    # {:query=>[{:clause=>{:or_terms=>[{:word=>"wow"@0}, {:word=>"lol"@7}, {:word=>"omg"@14}]}}]}
    
    puts Parser.parse_tree_for('wow')
    # {:query=>[{:clause=>{:term=>{:word=>"wow"@0}}}]}
    

    我在 word 中添加了as,因此它们总是被明确地捕获。

    最好先捕获比你想要的更多的东西,然后用变压器将其压平。

    假设您要将其扩展到涵盖 AND ......您会发现使 AND 和 OR 表达式成为必需的将使运算符优先级更容易处理。

    require 'parslet'
    
    class Parser < Parslet::Parser
      rule(:space)     { str(' ').repeat(1) }
      rule(:word)      { match['^\s"'].repeat(1) }
      rule(:or_op)     { space >> str('OR') >> space }
      rule(:and_op)    { space >> str('AND') >> space }
      rule(:term)      { word.as(:term) }
      rule(:or_terms)  { (and_terms >> (or_op >> and_terms).repeat(0)).as(:or_terms) }
      rule(:and_terms) { (term >> (and_op >> term).repeat()).as(:and_terms) }
      rule(:clause)    { (or_terms).as(:clause) }
      rule(:query)     { (clause >> space.maybe).repeat.as(:query) }
      root(:query)
    
      def self.parse_tree_for(query)
        new.parse(query)
      end
    end
    
    pp Parser.parse_tree_for('wow OR lol OR omg')
    # {:query=>
    #   [{:clause=>
    #      {:or_terms=>
    #        [{:and_terms=>{:term=>"wow"@0}},
    #         {:and_terms=>{:term=>"lol"@7}},
    #         {:and_terms=>{:term=>"omg"@14}}]}}]}
    
    pp Parser.parse_tree_for('wow')
    # {:query=>[{:clause=>{:or_terms=>{:and_terms=>{:term=>"wow"@0}}}}]}
    
    pp Parser.parse_tree_for('wow OR lol AND omg OR bob')
    # {:query=>
    #   [{:clause=>
    #      {:or_terms=>
    #        [{:and_terms=>{:term=>"wow"@0}},
    #         {:and_terms=>[{:term=>"lol"@7}, {:term=>"omg"@15}]},
    #         {:and_terms=>{:term=>"bob"@22}}]}}]}
    

    回答您的完整问题...在变压器中,您必须一次匹配整个哈希。为了解决这个问题,您可以匹配“子树”,但这通常是一种黑客行为。

    require 'parslet'
    
    class Parser < Parslet::Parser
      rule(:space)     { str(' ').repeat(1) }
      rule(:word)      { match['^\s"'].repeat(1) }
      rule(:or_op)     { space >> str('OR') >> space }
      rule(:and_op)    { space >> str('AND') >> space }
      rule(:term)      { word.as(:term) }
      rule(:or_terms)  { (term >> (or_op >> term).repeat(0)).as(:or_terms) }
      rule(:clause)    { (or_terms).as(:clause) }
      rule(:query)     { (clause >> space.maybe).repeat.as(:query) }
      root(:query)
    
      def self.parse_tree_for(query)
        new.parse(query)
      end
    end
    
    class MyTransform < Parslet::Transform
      rule(:term => simple(:t)) {t}
      rule(:or_terms => sequence(:terms)){ 
        terms.map{|t| {term:{word:t, or:true}}}
      }
      rule(:or_terms => simple(:cs)){ [{term:{word:cs}}] } # so a single hash looks like a list.
      rule(:query => subtree(:cs)){ {:query => cs.map{|c| c[:clause]}.flatten.map{|c| {clause:c}}}}
    end
    
    pp MyTransform.new.apply(Parser.parse_tree_for('foo bar OR baz'))
    

    此示例输出:

    {:query=>
      [{:clause=>{:term=>{:word=>"foo"@0}}},
       {:clause=>{:term=>{:word=>"bar"@4, :or=>true}}},
       {:clause=>{:term=>{:word=>"baz"@11, :or=>true}}}]}
    

    我使用了所有表达式都是 or_terms ... 的事实,并发现了只有一个术语不能将 or 设置为 true 的情况。 我还使用 or_terms 匹配来使单个术语也像集合一样......所以所有子句都映射到一个列表。然后在匹配子树时,我可以展平列表以获取所有术语并将它们再次包装在“子句”哈希中......哎呀! ;)

    【讨论】:

    • 问题在于,Parser.parse_tree_for('wow') 被错误地检测为 or_terms 而不是 term
    • 交换子句 therms (term | or_terms)
    • 我想你会发现,如果所有表达式都是 OR 术语,那么任何使用该数据结构的代码都会更容易编写......那么你不需要将它们作为异常处理。跨度>
    • 或者将 (or_op &gt;&gt; word).repeat(0) 更改为 repeat(1) 所以 orterm 必须至少有一个 OR
    • repeat(1) 效果很好。谢谢!最后一个问题,给定一个解析树,如: {:query=>[{:clause=>{:term=>{:word=>"foo"@0}}}, {:clause=>{:or_terms=> [{:word=>"bar"@4}, {:word=>"baz"@11}]}}]} .. 是否可以使用 Parslet 的转换类将其转换为 {:query=>[{ :clause=>{:term=>{:word=>"foo"@0}}}, {:clause=>{:term=>{:word=>"bar"@4, :or => true} , {:clause=>{:term=>{:word=>"baz"@11, :or => true}}}]} ?或者那是我需要自己手动完成的事情?
    猜你喜欢
    • 1970-01-01
    • 2014-02-17
    • 2014-02-06
    • 2017-04-06
    • 2020-04-17
    • 1970-01-01
    • 2018-03-13
    • 1970-01-01
    • 2018-03-24
    相关资源
    最近更新 更多