8

JavaScript ソース ファイルを解析し、いくつかの事実を抽出し、コードの一部を挿入/置換する必要があるプログラムを作成しています。このコードを考えると、私がする必要があることの種類の簡単な説明は次のとおりです。

foo(['a', 'b', 'c']);

'a''b'、およびを抽出し、'c'コードを次のように書き換えます。

foo('bar', [0, 1, 2]);

解析のニーズに ANTLR を使用して、C# 3 コードを生成しています。他の誰かがすでに JavaScript 文法を提供していました。ソースコードの解析が機能しています。

私が直面している問題は、ソース ファイルを実際に適切に分析して変更する方法を見つけ出すことです。問題を実際に解決するために取ろうとする各アプローチは、私を行き詰まりに導きます。ツールを意図したとおりに使用していないか、AST を扱うのが初心者すぎると思わずにはいられません。

私の最初のアプローチは、 を使用して解析し、関心のあるルールTokenRewriteStreamの部分メソッドを実装するEnterRule_*ことでした。これにより、トークン ストリームの変更が非常に簡単になるように見えますが、分析のための十分なコンテキスト情報がありません。私がアクセスできるのはトークンのフラットなストリームだけのようで、コードの構造全体について十分に教えてくれません。たとえば、foo関数が呼び出されているかどうかを検出するために、最初のトークンを調べるだけではうまくいきません。これも誤って一致するからです。

a.b.foo();

より洗練されたコード分析を行えるようにするための 2 番目のアプローチは、より多くのツリーを生成するように書き換え規則を使用して文法を変更することでした。さて、最初のサンプル コード ブロックはこれを生成します。

プログラム
    CallExpression
        識別子('foo')
        引数リスト
            配列リテラル
                StringLiteral('a')
                StringLiteral('b')
                StringLiteral('c')

これは、コードの分析に最適です。しかし、今ではコードを簡単に書き直すことはできません。確かに、ツリー構造を変更して必要なコードを表すことはできますが、これを使用してソース コードを出力することはできません。各ノードに関連付けられたトークンが、少なくとも元のテキストのどこを変更する必要があるかを知るのに十分な情報を提供してくれることを望んでいましたが、得られるのはトークン インデックスまたは行/列番号だけです。行番号と列番号を使用するには、ソース コードを介して厄介な 2 番目のパスを作成する必要があります。

ANTLRを適切に使用して必要なことを行う方法を理解する上で、何かが欠けていると思います。この問題を解決するためのより適切な方法はありますか?

4

3 に答える 3

9

あなたがやろうとしていることは、プログラムの変換、つまり、あるプログラムから別のプログラムへの自動生成と呼ばれます。あなたが「間違っている」ことは、パーサーだけが必要であると仮定し、そうではなく、ギャップを埋めなければならないことを発見することです。

これをうまく行うツールには、パーサー (AST をビルドするため)、AST を変更する手段 (手続き型およびパターン指向の両方)、および(変更された) AST を正規のソース コードに変換するprettyprinterがあります。ANTLR には prettyprinters が付属していないという事実に苦労しているようです。それはその哲学の一部ではありません。ANTLR は (細かい) パーサー ジェネレーターです。他の回答では、ANTLR の「文字列テンプレート」を使用することが提案されています。これは、それ自体はプリティプリンターではありませんが、実装するという代償を払って実装するために使用できます。これは見た目よりも難しいことです。AST をソース コードにコンパイルする方法については、SO の回答を参照してください。

ここでの本当の問題は、「パーサーがあれば、複雑なプログラム分析および変換ツールを構築する道を順調に進んでいる」という、広く行われているが誤った仮定です。これについての長い議論については、Life After Parsingに関する私のエッセイを参照してください。基本的に、タスクを続行する代わりにインフラストラクチャのかなりの部分を自分で再構築したくない場合を除き、これを行うには「単なる」パーサーよりも多くのツールが必要です。実用的なプログラム変換システムのその他の便利な機能には、通常、ソースからソースへの変換が含まれます。これにより、ツリー内の複雑なパターンを見つけて置き換える問題が大幅に簡素化されます。

たとえば、ソースからソースへの変換機能 (当社のツールであるDMS Software Reengineering Toolkitの機能) があれば、これらの DMS 変換を使用してサンプル コードの変更の一部を記述することができます。

       domain ECMAScript.

       tag replace;  -- says this is a special kind of temporary tree


       rule barize(function_name:IDENTIFIER,list:expression_list,b:body):
           expression->expression
        =   " \function_name ( '[' \list ']' ) "
        -> "\function_name( \firstarg\(\function_name\), \replace\(\list\))";


       rule replace_unit_list(s:character_literal):
           expression_list -> expression_list
           replace(s) -> compute_index_for(s);

       rule replace_long_list(s:character_list, list:expression_list):
           expression_list -> expression_list
           "\replace\(\s\,\list)->  "compute_index_for\(\s\),\list";

ルール外部の「メタ」プロシージャ「first_arg」(識別子「foo」を指定して「バー」を計算する方法を知っている[あなたはこれをやりたいと思います)、および文字列リテラルを指定した「compute_index_for」を使用して、何を知っていますか置き換える整数。

個々の書き換えルールには、サブツリーを表すスロットの名前が付けられたパラメーター リスト "(....)" があり、左側は一致するパターンとして機能し、右側は置換として機能します。どちらも通常はメタクォート "で引用されます。書き換えルール言語のテキストをターゲット言語 (JavaScript など) のテキストから分離します. 特別な書き換えルール言語の項目を示すメタクォートの中に多くのメタエスケープがあります **パラメーターが表す名前ツリー、または外部メタ プロシージャ コール (first_arg など。その引数リスト ( , ) がメタ引用符で囲まれていることに注意してください!)、または最後に、「replace」などの「タグ」を表します。これは、より多くの変換を行うという将来の意図を表す独特の種類の木です。

この特定のルールのセットは、リストを変換する追加の意図「置換」を使用して、候補関数呼び出しをバー化されたバージョンで置き換えることによって機能します。他の 2 つの変換は、リストの要素を一度に 1 つずつ処理することによって「置換」を変換し、最終的に末尾から外れて置換が完了するまで置換をリストのさらに下に押し込むことによって、意図を実現します。(これは、ループに相当する変形です)。

詳細については正確ではなかったため、特定の例は多少異なる場合があります。

解析されたツリーを変更するためにこれらのルールを適用すると、DMS は簡単に結果をプリティプリントできます (一部の構成でのデフォルトの動作は、「AST に解析し、使い果たされるまでルールを適用し、AST をプリティプリントする」というものです。これは便利なためです)。

DMS ドメインとして (高校) Algebraで、「言語の定義」、「書き換え規則の定義」、「規則の適用と prettyprint」の完全なプロセスを見ることができます。

その他のプログラム変換システムには、TXLStrategoなどがあります。DMS は、これらの産業用強度バージョンであり、多くの標準言語パーサーや prettyprintersを含むすべてのインフラストラクチャが構築されていると考えています。

于 2012-07-23T13:14:25.220 に答える
4

したがって、実際に書き換えツリー文法を使用し、. を使用してトークンを挿入/置換できることが判明していTokenRewriteStreamます。さらに、実際に行うのは非常に簡単です。私のコードは次のようになります。

var charStream = new ANTLRInputStream(stream);
var lexer = new JavaScriptLexer(charStream);
var tokenStream = new TokenRewriteStream(lexer);
var parser = new JavaScriptParser(tokenStream);
var program = parser.program().Tree as Program;

var dependencies = new List<IModule>();

var functionCall = (
    from callExpression in program.Children.OfType<CallExpression>()
    where callExpression.Children[0].Text == "foo"
    select callExpression
).Single();
var argList = functionCall.Children[1] as ArgumentList;
var array = argList.Children[0] as ArrayLiteral;

tokenStream.InsertAfter(argList.Token.TokenIndex, "'bar', ");
for (var i = 0; i < array.Children.Count(); i++)
{
    tokenStream.Replace(
        (array.Children[i] as StringLiteral).Token.TokenIndex, 
        i.ToString());
}

var rewrittenCode = tokenStream.ToString();
于 2012-07-24T04:35:15.783 に答える
2

文字列テンプレートライブラリを見たことがありますか。それはANTLRを書いたのと同じ人によるものであり、彼らは一緒に働くことを意図しています。それはあなたが探しているものを実行するのに適しているように聞こえます。一致した文法規則をフォーマットされたテキストとして出力します。

これがANTLRによる翻訳に関する記事です

于 2012-07-23T08:45:35.473 に答える