web-dev-qa-db-ja.com

コード補完はどのように機能しますか?

多くのエディターとIDEにはコード補完があります。それらのいくつかは非常に「インテリジェント」であり、他は実際にはそうではありません。よりインテリジェントなタイプに興味があります。たとえば、a)現在のスコープで使用できるb)戻り値が有効である場合にのみ関数を提供するIDEを見てきました。 (たとえば、「5 + foo [tab]」の後は、正しいタイプの整数または変数名に追加できるものを返す関数のみを提供します。)また、より頻繁に使用されるオプションまたは最も長いオプションを前に置いていることも確認しました。リストの。

コードを解析する必要があることを理解しています。しかし、通常、現在のコードの編集が無効である間、その中に構文エラーがあります。不完全でエラーが含まれている場合、どのように解析しますか?

時間的な制約もあります。リストを思いつくのに数秒かかるなら、完成は役に立たない。時々、補完アルゴリズムは何千ものクラスを扱います。

これに適したアルゴリズムとデータ構造は何ですか?

83
stribika

UnrealScript言語サービス製品のIntelliSenseエンジンは複雑ですが、ここではできる限り概要を説明します。 VS2008 SP1のC#言語サービスが私のパフォーマンス目標です(正当な理由による)。それはまだありませんが、1つの文字が入力された後、Ctrl +スペースまたはユーザーが_._(ドット)を入力するのを待たずに安全に提案できるほど高速/正確です。人々[言語サービスに取り組んでいる人]がこの主題についてより多くの情報を得るほど、私が彼らの製品を使用した場合に得られるエンドユーザーエクスペリエンスがより良くなります。不幸な作業をした経験のある製品のなかには、細部にそれほど注意を払わなかったものがあり、その結果、IDEコーディング。

私の言語サービスでは、次のようにレイアウトされています。

  1. カーソル位置の式を取得します。これは、メンバーアクセス式の先頭から、カーソルが置かれている識別子の末尾まで進みます。メンバーアクセス式は、通常_aa.bb.cc_の形式ですが、aa.bb(3+2).ccのようにメソッド呼び出しを含めることもできます。
  2. カーソルを囲むcontextを取得します。これは、コンパイラとは常に同じ規則(長いストーリー)に従うとは限らないため、非常にトリッキーですが、ここではそれを前提としています。通常、これは、カーソルが含まれるメソッド/クラスに関するキャッシュされた情報を取得することを意味します。
  3. コンテキストオブジェクトがIDeclarationProviderを実装するとします。ここで、GetDeclarations()を呼び出して、スコープに表示されているすべてのアイテムの_IEnumerable<IDeclaration>_を取得できます。私の場合、このリストには、ローカル/パラメーター(メソッド内の場合)、メンバー(フィールドとメソッド、インスタンスメソッドでない限り静的であり、基本型のプライベートメンバーはありません)、グローバル(言語Iの型と定数)が含まれています取り組んでいます)、そしてキーワード。このリストには、aaという名前のアイテムがあります。 #1の式を評価する最初のステップとして、aaという名前のコンテキスト列挙からアイテムを選択し、次のステップにIDeclarationを与えます。
  4. 次に、IDeclarationを表すaaに演算子を適用して、aaの「メンバー」(ある意味で)を含む別の_IEnumerable<IDeclaration>_を取得します。 _._演算子は_->_演算子とは異なるため、declaration.GetMembers(".")を呼び出し、IDeclarationオブジェクトがリストされた演算子を正しく適用することを期待しています。
  5. これは、ccを押すまで続きます。宣言リストには、ccという名前のオブジェクトが含まれる場合と含まれない場合があります。お気づきのことと思いますが、ccで始まる複数のアイテムも表示されます。これを解決するには、最後の列挙を取得して 私の文書化されたアルゴリズム に渡し、ユーザーに最も役立つ情報を提供します。

IntelliSenseバックエンドに関する追加の注意事項は次のとおりです:

  • GetMembersの実装では、LINQの遅延評価メカニズムを広範囲に使用しています。私のキャッシュ内の各オブジェクトは、そのメンバーを評価するファンクターを提供できるため、ツリーで複雑なアクションを実行するのは簡単です。
  • 各オブジェクトがそのメンバーの_List<IDeclaration>_を保持する代わりに、_List<Name>_を保持します。ここで、Nameは、メンバーを説明する特別な形式の文字列のハッシュを含む構造体です。名前をオブジェクトにマップする巨大なキャッシュがあります。このようにして、ファイルを再解析するときに、ファイルで宣言されているすべてのアイテムをキャッシュから削除し、更新されたメンバーを再入力できます。ファンクタの設定方法により、すべての式はすぐに新しいアイテムに評価されます。

IntelliSense "フロントエンド"

ユーザーが入力すると、ファイルは構文的に不正確であることが、正しいよりも頻繁になります。そのため、ユーザーが入力したときにキャッシュのセクションを無計画に削除したくありません。増分更新をできるだけ迅速に処理するために、多数の特別なケースのルールを用意しています。インクリメンタルキャッシュは開いているファイルのローカルにのみ保持され、ユーザーが入力することでバックエンドキャッシュがファイル内の各メソッドなどの誤った行/列情報を保持していることに気付かないようにするのに役立ちます。

  • 償還要因の1つは、パーサーがfastであることです。優先度の低いバックグラウンドスレッドで自己完結型で動作しながら、20000行のソースファイルの完全なキャッシュ更新を150ミリ秒で処理できます。このパーサーが開いているファイルへのパスを正常に(構文的に)完了するたびに、ファイルの現在の状態がグローバルキャッシュに移動されます。
  • ファイルが構文的に正しくない場合、私は ANTLRフィルターパーサー(リンクについて申し訳ありません-ほとんどの情報はメーリングリストにあるか、ソースの読み取りから収集されます) を使用してファイルを再解析し、[[を探します。 ____。]
    • 変数/フィールド宣言。
    • クラス/構造体定義の署名。
    • メソッド定義の署名。
  • ローカルキャッシュでは、クラス/構造体/メソッドの定義はシグネチャで始まり、ブレースのネストレベルが偶数に戻ると終了します。メソッドは、別のメソッド宣言に到達した場合(ネストメソッドなし)にも終了します。
  • ローカルキャッシュでは、変数/フィールドは直前のunclosed要素にリンクされます。これが重要である理由の例については、以下の簡単なコードスニペットを参照してください。
  • また、ユーザーが入力するときに、追加/削除された文字範囲をマークするリマップテーブルを保持します。これは次の目的で使用されます:
    • メソッドは完全な解析間でファイル内を移動できるため、カーソルの正しいコンテキストを識別できることを確認します。
    • 宣言/定義/参照に移動すると、開いているファイル内のアイテムが正しく配置されます。

前のセクションのコードスニペット:

_class A
{
    int x; // linked to A

    void foo() // linked to A
    {
        int local; // linked to foo()

    // foo() ends here because bar() is starting
    void bar() // linked to A
    {
        int local2; // linked to bar()
    }

    int y; // linked again to A
_

このレイアウトで実装したIntelliSense機能のリストを追加すると思いました。 それぞれの写真はここにあります。

  • オートコンプリート
  • ツールチップ
  • メソッドのヒント
  • クラスビュー
  • コード定義ウィンドウ
  • ブラウザーの呼び出し(VS 2010はこれを最終的にC#に追加します)
  • 意味的に正しいすべての参照を検索
64
Sam Harwell

特定の実装で使用されているアルゴリズムを正確に述べることはできませんが、知識に基づいた推測はできます。 トライ は、この問題に非常に役立つデータ構造です。IDEは、いくつかの追加のメタデータを使用して、プロジェクト内のすべてのシンボルのメモリに大きなトライを維持できます。各ノードで。

文字を入力すると、トライの道を歩きます。特定のトライノードのすべての子孫は、可能な補完です。 IDE次に、現在のコンテキストで意味のあるものでそれらを除外する必要があるだけですが、タブ補完ポップアップウィンドウに表示できる数だけ計算する必要があります。

より高度なタブ補完には、より複雑なトライが必要です。たとえば、 Visual Assist X には、CamelCase記号の大文字を入力するだけで済む機能があります。たとえば、SFNと入力すると、記号SomeFunctionNameが表示されます。タブ補完ウィンドウ。

トライ(または他のデータ構造)を計算するには、プロジェクト内のすべてのシンボルのリストを取得するために、すべてのコードを解析する必要があります。 Visual StudioはこれをIntelliSenseデータベース、.ncbファイルはプロジェクトと一緒に保存されるため、プロジェクトを閉じて再度開くたびにすべてを再解析する必要はありません。大きなプロジェクト(たとえば、フォームのソース管理を同期したばかりのプロジェクト)を初めて開いたとき、VSは時間をかけてすべてを解析し、データベースを生成します。

増分変更をどのように処理するのかわかりません。あなたが言ったように、あなたがコードを書いているとき、それは時間の90%が無効な構文であり、あなたがアイドル状態にあるときはいつでもすべてを再解析することは、特に以下によってインクルードされたヘッダーファイルを変更している場合、CPUに大きな負担をかけ、ほとんどメリットがありません多数のソースファイル。

(a)実際にプロジェクトをビルドするとき(またはプロジェクトを閉じたり開いたりしたとき)にのみ再解析するか、または(b)ある種のローカル解析を実行して、コードを解析するだけのように思われる関連するシンボルの名前を取得するためだけに、限られた方法で編集されます。 C++には非常に複雑な文法があるため、重いテンプレートメタプログラミングなどを使用している場合、暗いコーナーでは奇妙な動作をする可能性があります。

15
Adam Rosenfield

次のリンクはさらに役立ちます。

構文の強調表示: 構文の強調表示用の高速色のテキストボックス

2
matrix