我正在使用parslet库在Ruby中开发解析器。
我正在解析的语言有很多关键字可以合并到一个解析规则中,如下所示:
rule(:keyword) {
str('keyword1') |
str('keyword2') |
str('keyword2') ...
}
通过阅读包含所有关键字的文本文件,是否有一种动态生成这组代码行的好方法? 这将有助于我保持我的解析器清洁和小,使得更容易添加新关键字而无需修改代码。
我想在rule(:keyword)
中嵌入的伪代码将是这样的事情:
File.read("keywords.txt").each { |k| write_line " str(\'#{k}\') "}
到目前为止,我发现的解决方法是使用单独的ruby程序将解析器代码加载为:
keywords = ["keyword1", "keyword2","keyword3"]
subs = {:keyword_list => keywords .inject("") { |a,k| a << "str('#{k}') | \n"} }
eval( File.read("parser.rb") % subs)
其中解析器代码包含以下行:
rule(:keywords){
%{keyword_list}
}
有更优雅的方法来实现这一目标吗?
答案 0 :(得分:3)
您可以尝试这样的事情:
rule(:keyword) {
File.readlines("keywords.txt").map { |k| str(k.chomp) }.inject(&:|)
}
答案 1 :(得分:1)
在这种情况下,您实际上并不需要“生成代码行”。正如@Uri试图在his answer中解释的那样,rule
方法的内容没什么特别之处;它只是普通的Ruby代码。因此,您可以在Ruby中执行的任何操作也可以在该规则方法中执行,包括读取文件,动态调用方法以及调用对象上的方法。
让我分解您现有的代码,以便我可以更好地解释同一问题的动态解决方案如何工作:
rule(:keyword) {
# Stuff here
}
此处的代码调用rule
方法并将其传递给:keyword
和一段代码。在某些时候,parslet将调用该块并检查其返回值。 Parslet可能会选择使用instance_exec
调用块,这可以更改正在执行块的上下文,以使方法在块外可用的方法(如str
可能)在其中可用。
str('keyword1')
这里,在规则块的上下文中,您正在调用名为str
的方法,其字符串为“keyword1”,并获取结果。这里没什么特别的,这只是一个普通的方法调用。
str('keyword1') | str('keyword2')
这里,|
运算符实际上只是在str('keyword1')
返回的任何内容上调用的方法。此代码相当于str('keyword1').send(:'|', str('keyword2'))
。
str('keyword1') |
str('keyword2') |
str('keyword2')
与之前相同,但这次我们在返回的|
上调用str('keyword1').send(:'|', str('keyword2'))
。调用块时,此方法调用的结果将返回到rule
方法。
现在您已了解所有这些工作原理,您可以执行完全相同的操作(使用每个关键字调用str
,并使用|
方法动态地“添加”结果)基于文件的内容或许:
rule(:keyword) {
File.readlines("keywords.txt").map(&:chomp).map { |k| str(k) }.inject(:|)
}
故障:
rule(:keyword) { # Call the rule method with the `:keyword` argument, and pass
# it this block of code.
File.readlines("keywords.txt"). # Get an array of strings containing all the
# keywords
map(&:chomp). # Remove surrounding whitespace from each keyword in the array,
# by calling `chomp` on them. (The strings returned by
# `File.readlines` include the newline character at the end of
# each string.)
map { |k| str(k) }. # Convert each keyword in the array into whatever is
# returned by calling `str` with that keyword.
inject(:|) # Reduce the returned objects to a single one using the `|`
# method on each object. (Equivalent to obj1 | obj2 | obj3...)
}
就是这样!看到?无需生成任何代码行,只需执行实际代码所做的操作,而是动态执行!