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

1

この場合、「コード行を生成する」必要はありません。@Uri がanswerruleで説明しようとしたように、そのメソッドの内容について特別なことは何もありません。単純な 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 に答える