多くのエディターとIDEにはコード補完があります。それらのいくつかは非常に「インテリジェント」であり、他は実際にはそうではありません。よりインテリジェントなタイプに興味があります。たとえば、a)現在のスコープで使用できるb)戻り値が有効である場合にのみ関数を提供するIDEを見てきました。 (たとえば、「5 + foo [tab]」の後は、正しいタイプの整数または変数名に追加できるものを返す関数のみを提供します。)また、より頻繁に使用されるオプションまたは最も長いオプションを前に置いていることも確認しました。リストの。
コードを解析する必要があることを理解しています。しかし、通常、現在のコードの編集が無効である間、その中に構文エラーがあります。不完全でエラーが含まれている場合、どのように解析しますか?
時間的な制約もあります。リストを思いつくのに数秒かかるなら、完成は役に立たない。時々、補完アルゴリズムは何千ものクラスを扱います。
これに適したアルゴリズムとデータ構造は何ですか?
UnrealScript言語サービス製品のIntelliSenseエンジンは複雑ですが、ここではできる限り概要を説明します。 VS2008 SP1のC#言語サービスが私のパフォーマンス目標です(正当な理由による)。それはまだありませんが、1つの文字が入力された後、Ctrl +スペースまたはユーザーが_.
_(ドット)を入力するのを待たずに安全に提案できるほど高速/正確です。人々[言語サービスに取り組んでいる人]がこの主題についてより多くの情報を得るほど、私が彼らの製品を使用した場合に得られるエンドユーザーエクスペリエンスがより良くなります。不幸な作業をした経験のある製品のなかには、細部にそれほど注意を払わなかったものがあり、その結果、IDEコーディング。
私の言語サービスでは、次のようにレイアウトされています。
aa.bb.cc
_の形式ですが、aa.bb(3+2).cc
のようにメソッド呼び出しを含めることもできます。IDeclarationProvider
を実装するとします。ここで、GetDeclarations()
を呼び出して、スコープに表示されているすべてのアイテムの_IEnumerable<IDeclaration>
_を取得できます。私の場合、このリストには、ローカル/パラメーター(メソッド内の場合)、メンバー(フィールドとメソッド、インスタンスメソッドでない限り静的であり、基本型のプライベートメンバーはありません)、グローバル(言語Iの型と定数)が含まれています取り組んでいます)、そしてキーワード。このリストには、aa
という名前のアイテムがあります。 #1の式を評価する最初のステップとして、aa
という名前のコンテキスト列挙からアイテムを選択し、次のステップにIDeclaration
を与えます。IDeclaration
を表すaa
に演算子を適用して、aa
の「メンバー」(ある意味で)を含む別の_IEnumerable<IDeclaration>
_を取得します。 _.
_演算子は_->
_演算子とは異なるため、declaration.GetMembers(".")
を呼び出し、IDeclaration
オブジェクトがリストされた演算子を正しく適用することを期待しています。cc
を押すまで続きます。宣言リストには、cc
という名前のオブジェクトが含まれる場合と含まれない場合があります。お気づきのことと思いますが、cc
で始まる複数のアイテムも表示されます。これを解決するには、最後の列挙を取得して 私の文書化されたアルゴリズム に渡し、ユーザーに最も役立つ情報を提供します。IntelliSenseバックエンドに関する追加の注意事項は次のとおりです:
GetMembers
の実装では、LINQの遅延評価メカニズムを広範囲に使用しています。私のキャッシュ内の各オブジェクトは、そのメンバーを評価するファンクターを提供できるため、ツリーで複雑なアクションを実行するのは簡単です。List<IDeclaration>
_を保持する代わりに、_List<Name>
_を保持します。ここで、Name
は、メンバーを説明する特別な形式の文字列のハッシュを含む構造体です。名前をオブジェクトにマップする巨大なキャッシュがあります。このようにして、ファイルを再解析するときに、ファイルで宣言されているすべてのアイテムをキャッシュから削除し、更新されたメンバーを再入力できます。ファンクタの設定方法により、すべての式はすぐに新しいアイテムに評価されます。IntelliSense "フロントエンド"
ユーザーが入力すると、ファイルは構文的に不正確であることが、正しいよりも頻繁になります。そのため、ユーザーが入力したときにキャッシュのセクションを無計画に削除したくありません。増分更新をできるだけ迅速に処理するために、多数の特別なケースのルールを用意しています。インクリメンタルキャッシュは開いているファイルのローカルにのみ保持され、ユーザーが入力することでバックエンドキャッシュがファイル内の各メソッドなどの誤った行/列情報を保持していることに気付かないようにするのに役立ちます。
前のセクションのコードスニペット:
_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機能のリストを追加すると思いました。 それぞれの写真はここにあります。
特定の実装で使用されているアルゴリズムを正確に述べることはできませんが、知識に基づいた推測はできます。 トライ は、この問題に非常に役立つデータ構造です。IDEは、いくつかの追加のメタデータを使用して、プロジェクト内のすべてのシンボルのメモリに大きなトライを維持できます。各ノードで。
文字を入力すると、トライの道を歩きます。特定のトライノードのすべての子孫は、可能な補完です。 IDE次に、現在のコンテキストで意味のあるものでそれらを除外する必要があるだけですが、タブ補完ポップアップウィンドウに表示できる数だけ計算する必要があります。
より高度なタブ補完には、より複雑なトライが必要です。たとえば、 Visual Assist X には、CamelCase記号の大文字を入力するだけで済む機能があります。たとえば、SFNと入力すると、記号SomeFunctionName
が表示されます。タブ補完ウィンドウ。
トライ(または他のデータ構造)を計算するには、プロジェクト内のすべてのシンボルのリストを取得するために、すべてのコードを解析する必要があります。 Visual StudioはこれをIntelliSenseデータベース、.ncb
ファイルはプロジェクトと一緒に保存されるため、プロジェクトを閉じて再度開くたびにすべてを再解析する必要はありません。大きなプロジェクト(たとえば、フォームのソース管理を同期したばかりのプロジェクト)を初めて開いたとき、VSは時間をかけてすべてを解析し、データベースを生成します。
増分変更をどのように処理するのかわかりません。あなたが言ったように、あなたがコードを書いているとき、それは時間の90%が無効な構文であり、あなたがアイドル状態にあるときはいつでもすべてを再解析することは、特に以下によってインクルードされたヘッダーファイルを変更している場合、CPUに大きな負担をかけ、ほとんどメリットがありません多数のソースファイル。
(a)実際にプロジェクトをビルドするとき(またはプロジェクトを閉じたり開いたりしたとき)にのみ再解析するか、または(b)ある種のローカル解析を実行して、コードを解析するだけのように思われる関連するシンボルの名前を取得するためだけに、限られた方法で編集されます。 C++には非常に複雑な文法があるため、重いテンプレートメタプログラミングなどを使用している場合、暗いコーナーでは奇妙な動作をする可能性があります。
次のリンクはさらに役立ちます。
構文の強調表示: 構文の強調表示用の高速色のテキストボックス