如何在Ruby中对此字符串进行标记?

我有这个字符串:

%{Children^10 Health "sanitation management"^5} 

我想将其转换为将其标记为哈希数组:

 [{:keywords=>"children", :boost=>10}, {:keywords=>"health", :boost=>nil}, {:keywords=>"sanitation management", :boost=>5}] 

我知道StringScanner和Syntax gem,但我找不到足够的代码示例。

有什么指针吗?

对于一个真正的语言,一个词法分析者是要走的路 – 就像Guss说的那样 。 但是如果完整语言只是像你的例子一样复杂,你可以使用这个快速的黑客:

 irb> text = %{Children^10 Health "sanitation management"^5} irb> text.scan(/(?:(\w+)|"((?:\\.|[^\\"])*)")(?:\^(\d+))?/).map do |word,phrase,boost| { :keywords => (word || phrase).downcase, :boost => (boost.nil? ? nil : boost.to_i) } end #=> [{:boost=>10, :keywords=>"children"}, {:boost=>nil, :keywords=>"health"}, {:boost=>5, :keywords=>"sanitation management"}] 

如果您正在尝试解析常规语言,那么这种方法就足够了 – 尽管使语言非常规并不会带来更多复杂性。

正则表达式的快速细分:

  • \w+匹配任何单项关键字
  • (?:\\.|[^\\"]])*使用非捕获括号( (?:...) )来匹配转义双引号字符串的内容 – 转义符号( \n\"\\ ,等等)或任何不是转义符号或结束引号的单个字符。
  • "((?:\\.|[^\\"]])*)"仅捕获引用的关键字词组的内容。
  • (?:(\w+)|"((?:\\.|[^\\"])*)")匹配任何关键字 – 单个术语或短语,将单个术语捕获到$1 ,将短语内容捕获到$2
  • \d+匹配一个数字。
  • \^(\d+)捕获插入符后面的数字( ^ )。 由于这是第三组捕捉括号,它将被限制在$3
  • (?:\^(\d+))? 如果它在那里,则在一个插入符号后面捕获一个数字,否则匹配空字符串。

String#scan(regex)将正则表达式与字符串尽可能多地匹配,输出“匹配”数组。 如果正则表达式包含捕获的parens,则“匹配”是捕获的项目数组 – 因此$1变为match[0]$2变为match[1] ,等等。任何捕获的括号都不会与字符串映射的一部分匹配在得到的“匹配”中输入nil

#map然后接受这些匹配,使用一些块魔法将每个捕获的术语分成不同的变量(我们可以do |match| ; word,phrase,boost = *match ),然后创建所需的哈希值。 wordphrase将是nil ,因为两者都不能与输入匹配,因此(word || phrase)将返回非零值,而#downcase将其转换为全部小写。 boost.to_i会将字符串转换为整数,而(boost.nil? ? nil : boost.to_i)将确保nil (boost.nil? ? nil : boost.to_i)保持nil

这是一个使用StringScanner的非强大示例。 这是我刚刚从Ruby Quiz改编的代码:Parsing JSON ,它有一个很好的解释。

 require 'strscan' def test_parse text = %{Children^10 Health "sanitation management"^5} expected = [{:keywords=>"children", :boost=>10}, {:keywords=>"health", :boost=>nil}, {:keywords=>"sanitation management", :boost=>5}] assert_equal(expected, parse(text)) end def parse(text) @input = StringScanner.new(text) output = [] while keyword = parse_string || parse_quoted_string output << { :keywords => keyword, :boost => parse_boost } trim_space end output end def parse_string if @input.scan(/\w+/) @input.matched.downcase else nil end end def parse_quoted_string if @input.scan(/"/) str = parse_quoted_contents @input.scan(/"/) or raise "unclosed string" str else nil end end def parse_quoted_contents @input.scan(/[^\\"]+/) and @input.matched end def parse_boost if @input.scan(/\^/) boost = @input.scan(/\d+/) raise 'missing boost value' if boost.nil? boost.to_i else nil end end def trim_space @input.scan(/\s+/) end 

你在这里有一个任意的语法,并解析它你真正想要的是一个词法分析器 – 你可以编写一个描述你的语法的语法文件,然后使用词法分析器从你的语法生成一个递归的解析器。

编写词法分析器(甚至是递归解析器)并不是很简单 – 虽然它在编程中很有用 – 但是你可以在这个电子邮件中找到Ruby词法分析器/解析器的列表: http : //newsgroups.derkeiler.com /Archive/Comp/comp.lang.ruby/2005-11/msg02233.html

RACC作为Ruby 1.8的标准模块提供,所以我建议你专注于它,即使它的手册不是很容易理解,它需要熟悉yacc。