1

我正在尝试解析一个字符串,以便我可以轻松识别由“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=>[{:clause=>{:term=>"wow",:or=>true}},{:clause=>{:term=>"lol",:or=>true},{:clause=>{:term=>"omg",:or=>true}}]}

这是应该用变压器做的事情吗?就像,只是在变压器中设置一个规则来做split(' OR ')还是有更好的方法来设置我的规则?

4

1 回答 1

0

你需要一个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}}}]}

我添加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 ... 的事实,并发现只有一个术语不设置ortrue. 我还使用 or_terms 匹配来使单个术语也像集合一样......所以所有子句都映射到一个列表。然后在匹配子树时,我可以展平列表以获取所有术语并将它们再次包装在“子句”哈希中......哎呀!;)

于 2020-06-05T02:19:04.030 回答