1

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

有没有更优雅的方法来实现这一点?

4

2 回答 2

3

你可以尝试这样的事情:

rule(:keyword) {  
  File.readlines("keywords.txt").map { |k| str(k.chomp) }.inject(&:|)
}
于 2014-12-12T11:25:41.190 回答
1

在这种情况下,您实际上并不需要“生成代码行”。正如@Uri 试图在他的回答中解释的那样,该方法的内容没有什么特别之处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...)
}

就是这样!看?无需生成任何代码行,只需执行真实代码正在执行的操作,而是动态执行!

于 2014-12-12T14:45:24.983 回答