尝试按 OR 运算符对术语进行分组

Trying to group terms by OR operator

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

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

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=>[{:clause=>{:term=>"wow",:or=>true}},{:clause=>{:term=>"lol",:or=>true},{:clause=>{:term=>"omg",:or=>true}}]}

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

您需要在每个要明确捕获的事物上添加一个 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}}]}}]}

回答您的完整问题...在变形金刚中,您必须一次匹配整个哈希。要解决这个问题,您可以匹配 'subtree' 但这通常是一种 hack。

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 匹配来使单个术语也像一个集合一样……因此所有子句都映射到一个列表。然后在匹配子树时,我可以展平列表以获取所有术语并将它们再次包装在 'clause' 哈希中...... Yuk! ;)