我有这个字符串:
%{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)
将正则表达式与字符串尽可能多次匹配,输出“匹配”数组。如果正则表达式包含捕获括号,则“匹配”是捕获的项目数组 - 因此 $1
变为 match[0]
,$2
变为 match[1]
,等等。任何未与部分匹配的捕获括号字符串映射到结果“匹配”中的 nil
条目。
#map
然后获取这些匹配项,使用一些块魔法将每个捕获的术语分解为不同的变量(我们可以这样做do |match| ; word,phrase,boost = *match
),然后创建您想要的哈希值。 word
或 phrase
中的一个将是 nil
,因为两者都无法与输入匹配,因此 (word || phrase)
将返回非 nil
1,而 #downcase
将其转换为所有小写。 boost.to_i
会将字符串转换为整数,而 (boost.nil? ? nil : boost.to_i)
将确保 nil
提升保持 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
LR 解析器:
- http://raa.ruby-lang.org/project/racc/(ruby 中的运行时扩展 1.8)
- http://raa.ruby-lang.org/project/rockit/(在 1.8 上不起作用?)
RACC 作为 Ruby 1.8 的标准模块提供,所以我建议你集中精力,即使它的手册不太容易理解并且需要熟悉 yacc。