Ruby:如何在程序中生成代码行?

时间:2014-12-12 11:19:32

标签: ruby metaprogramming parslet

我正在使用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} 
 }

有更优雅的方法来实现这一目标吗?

2 个答案:

答案 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...)
}

就是这样!看到?无需生成任何代码行,只需执行实际代码所做的操作,而是动态执行!