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