Bagaimana saya bisa dengan andal menentukan jenis variabel yang dideklarasikan menggunakan var pada waktu desain?

109

Saya sedang mengerjakan fasilitas penyelesaian (intellisense) untuk C # di emacs.

Idenya adalah, jika pengguna mengetikkan sebuah fragmen, kemudian meminta penyelesaian melalui kombinasi penekanan tombol tertentu, fasilitas penyelesaian akan menggunakan refleksi .NET untuk menentukan penyelesaian yang mungkin.

Melakukan ini mengharuskan jenis hal yang diselesaikan, diketahui. Jika itu sebuah string, ada satu set metode dan properti yang diketahui; jika itu Int32, itu memiliki set terpisah, dan seterusnya.

Dengan menggunakan semantik, paket kode lexer / parser yang tersedia di emacs, saya dapat menemukan deklarasi variabel, dan tipenya. Mengingat itu, sangatlah mudah untuk menggunakan refleksi untuk mendapatkan metode dan properti pada tipe, dan kemudian menyajikan daftar opsi kepada pengguna. (Ok, tidak cukup mudah untuk dilakukan dalam emacs, tetapi menggunakan kemampuan untuk menjalankan proses PowerShell di dalam emacs , itu menjadi jauh lebih mudah. ​​Saya menulis rakitan .NET khusus untuk melakukan refleksi, memuatnya ke PowerShell, dan kemudian menjalankan elisp di dalamnya emacs dapat mengirim perintah ke PowerShell dan membaca tanggapan, melalui comint. Hasilnya emacs bisa mendapatkan hasil refleksi dengan cepat.)

Masalahnya muncul ketika kode digunakan vardalam deklarasi hal yang sedang diselesaikan. Artinya, jenisnya tidak ditentukan secara eksplisit, dan penyelesaian tidak akan berfungsi.

Bagaimana saya bisa dengan andal menentukan tipe sebenarnya yang digunakan, ketika variabel dideklarasikan dengan varkata kunci? Untuk memperjelas, saya tidak perlu menentukannya saat runtime. Saya ingin menentukannya pada "Waktu desain".

Sejauh ini saya punya ide ini:

  1. kompilasi dan panggil:
    • ekstrak pernyataan deklarasi, misalnya `var foo =" a string value ";`
    • menggabungkan pernyataan `foo.GetType ();`
    • secara dinamis mengkompilasi fragmen C # yang dihasilkan ke dalam assembly baru
    • muat rakitan ke AppDomain baru, jalankan framgment dan dapatkan jenis pengembalian.
    • bongkar dan buang rakitan

    Saya tahu bagaimana melakukan semua ini. Tapi kedengarannya sangat berat, untuk setiap permintaan penyelesaian di editor.

    Saya kira saya tidak membutuhkan AppDomain baru setiap saat. Saya dapat menggunakan kembali AppDomain tunggal untuk beberapa rakitan sementara, dan mengamortisasi biaya pengaturan dan menghancurkannya, di beberapa permintaan penyelesaian. Itu lebih merupakan perubahan dari ide dasarnya.

  2. mengkompilasi dan memeriksa IL

    Cukup kompilasi deklarasi menjadi modul, lalu periksa IL, untuk menentukan tipe sebenarnya yang disimpulkan oleh kompilator. Bagaimana ini mungkin? Apa yang akan saya gunakan untuk memeriksa IL?

Ada ide yang lebih baik di luar sana? Komentar? saran?


EDIT - memikirkan hal ini lebih lanjut, kompilasi-dan-pemanggilan tidak dapat diterima, karena pemanggilan mungkin memiliki efek samping. Jadi, opsi pertama harus dikesampingkan.

Juga, saya rasa saya tidak bisa mengasumsikan adanya .NET 4.0.


PEMBARUAN - Jawaban yang benar, tidak disebutkan di atas, tetapi dengan lembut ditunjukkan oleh Eric Lippert, adalah dengan menerapkan sistem inferensi tipe fidelitas penuh. Ini adalah satu-satunya cara untuk menentukan jenis var pada waktu desain. Tapi, itu juga tidak mudah dilakukan. Karena saya tidak mengalami ilusi bahwa saya ingin mencoba membangun hal seperti itu, saya mengambil jalan pintas dari opsi 2 - mengekstrak kode deklarasi yang relevan, dan mengkompilasinya, kemudian memeriksa IL yang dihasilkan.

Ini benar-benar berfungsi, untuk subset yang adil dari skenario penyelesaian.

Misalnya, dalam fragmen kode berikut,? adalah posisi di mana pengguna meminta penyelesaian. Ini bekerja:

var x = "hello there"; 
x.?

Penyelesaian menyadari bahwa x adalah String, dan menyediakan opsi yang sesuai. Ini dilakukan dengan membuat dan kemudian menyusun kode sumber berikut:

namespace N1 {
  static class dmriiann5he { // randomly-generated class name
    static void M1 () {
       var x = "hello there"; 
    }
  }
}

... dan kemudian memeriksa IL dengan refleksi sederhana.

Ini juga berfungsi:

var x = new XmlDocument();
x.? 

Mesin menambahkan klausa penggunaan yang sesuai ke kode sumber yang dihasilkan, sehingga dapat dikompilasi dengan benar, lalu inspeksi IL-nya sama.

Ini juga berfungsi:

var x = "hello"; 
var y = x.ToCharArray();    
var z = y.?

Ini hanya berarti inspeksi IL harus menemukan jenis variabel lokal ketiga, bukan yang pertama.

Dan ini:

var foo = "Tra la la";
var fred = new System.Collections.Generic.List<String>
    {
        foo,
        foo.Length.ToString()
    };
var z = fred.Count;
var x = z.?

... yang hanya satu tingkat lebih dalam dari contoh sebelumnya.

Tapi, yang tidak berhasil adalah penyelesaian pada variabel lokal yang inisialisasinya bergantung pada titik mana pun pada anggota instance, atau argumen metode lokal. Suka:

var foo = this.InstanceMethod();
foo.?

Juga sintaks LINQ.

Saya harus memikirkan betapa berharganya hal-hal itu sebelum saya mempertimbangkan untuk mengatasinya melalui apa yang pasti merupakan "desain terbatas" (kata sopan untuk retasan) untuk penyelesaian.

Pendekatan untuk mengatasi masalah dengan dependensi pada argumen metode atau metode instance adalah dengan mengganti, dalam fragmen kode yang dihasilkan, dikompilasi, dan kemudian dianalisis IL, referensi ke hal-hal tersebut dengan variabel lokal "sintetik" dari jenis yang sama.


Pembaruan Lain - penyelesaian pada vars yang bergantung pada anggota instance, sekarang berfungsi.

Apa yang saya lakukan adalah menginterogasi jenisnya (melalui semantik), dan kemudian menghasilkan anggota siaga sintetis untuk semua anggota yang ada. Untuk buffer C # seperti ini:

public class CsharpCompletion
{
    private static int PrivateStaticField1 = 17;

    string InstanceMethod1(int index)
    {
        ...lots of code here...
        return result;
    }

    public void Run(int count)
    {
        var foo = "this is a string";
        var fred = new System.Collections.Generic.List<String>
        {
            foo,
            foo.Length.ToString()
        };
        var z = fred.Count;
        var mmm = count + z + CsharpCompletion.PrivateStaticField1;
        var nnn = this.InstanceMethod1(mmm);
        var fff = nnn.?

        ...more code here...

... kode yang dihasilkan yang dikompilasi, sehingga saya dapat belajar dari output IL jenis var nnn lokal, terlihat seperti ini:

namespace Nsbwhi0rdami {
  class CsharpCompletion {
    private static int PrivateStaticField1 = default(int);
    string InstanceMethod1(int index) { return default(string); }

    void M0zpstti30f4 (int count) {
       var foo = "this is a string";
       var fred = new System.Collections.Generic.List<String> { foo, foo.Length.ToString() };
       var z = fred.Count;
       var mmm = count + z + CsharpCompletion.PrivateStaticField1;
       var nnn = this.InstanceMethod1(mmm);
      }
  }
}

Semua anggota instance dan tipe statis tersedia dalam kode kerangka. Ini berhasil dikompilasi. Pada titik itu, menentukan jenis var lokal secara langsung melalui Refleksi.

Yang memungkinkan hal ini adalah:

  • kemampuan untuk menjalankan PowerShell di emacs
  • kompiler C # sangat cepat. Di komputer saya, dibutuhkan sekitar 0,5 detik untuk mengkompilasi rakitan dalam memori. Tidak cukup cepat untuk analisis antar-penekanan tombol, tetapi cukup cepat untuk mendukung pembuatan daftar penyelesaian sesuai permintaan.

Saya belum melihat ke LINQ.
Itu akan menjadi masalah yang jauh lebih besar karena lexer / parser semantik emacs memiliki C #, tidak "melakukan" LINQ.

Cheeso
sumber
4
Jenis foo ditentukan dan diisi oleh kompilator melalui inferensi jenis. Saya menduga mekanismenya sama sekali berbeda. Mungkin mesin tipe-inferensi memiliki pengait? Setidaknya saya akan menggunakan 'type-inference' sebagai tag.
George Mauer
3
Teknik Anda membuat model objek "palsu" yang memiliki semua tipe tetapi tidak ada semantik dari objek sebenarnya yang bagus. Begitulah cara saya melakukan IntelliSense untuk JScript dalam Visual InterDev saat itu; kami membuat versi "palsu" dari model objek IE yang memiliki semua metode dan tipe tetapi tidak ada efek sampingnya, dan kemudian menjalankan penerjemah kecil di atas kode yang diurai pada waktu kompilasi dan melihat tipe apa yang muncul kembali.
Eric Lippert

Jawaban:

202

Saya dapat menjelaskan untuk Anda bagaimana kami melakukannya secara efisien dalam C # IDE "nyata".

Hal pertama yang kami lakukan adalah menjalankan pass yang hanya menganalisis hal-hal "tingkat atas" dalam kode sumber. Kami melewatkan semua badan metode. Itu memungkinkan kita untuk dengan cepat membangun database informasi tentang namespace, jenis dan metode (dan konstruktor, dll) yang ada di kode sumber program. Menganalisis setiap baris kode di setiap badan metode akan memakan waktu terlalu lama jika Anda mencoba melakukannya di antara penekanan tombol.

Ketika IDE perlu menentukan jenis ekspresi tertentu di dalam tubuh metode - katakanlah Anda telah mengetik "foo." dan kita perlu mencari tahu apa saja anggota foo - kita melakukan hal yang sama; kita melewatkan pekerjaan sebanyak mungkin.

Kita mulai dengan pass yang hanya menganalisis deklarasi variabel lokal dalam metode itu. Ketika kita menjalankan pass itu kita membuat pemetaan dari sepasang "scope" dan "name" ke "type determiner". "Penentu jenis" adalah objek yang mewakili gagasan "Saya bisa menentukan jenis lokal ini jika saya perlu". Mengerjakan jenis pekerjaan lokal bisa jadi mahal, jadi kami ingin menunda pekerjaan itu jika perlu.

Kami sekarang memiliki database yang dibuat dengan malas yang dapat memberi tahu kami jenis setiap lokal. Jadi, kembali ke "foo" itu. - kita mencari tahu di pernyataan mana ekspresi yang relevan berada dan kemudian menjalankan penganalisis semantik hanya terhadap pernyataan itu. Misalnya, Anda memiliki metode body:

String x = "hello";
var y = x.ToCharArray();
var z = from foo in y where foo.

dan sekarang kita perlu mencari tahu bahwa foo adalah tipe char. Kami membangun database yang memiliki semua metadata, metode ekstensi, jenis kode sumber, dan sebagainya. Kami membangun database yang memiliki penentu tipe untuk x, y dan z. Kami menganalisis pernyataan yang berisi ungkapan yang menarik. Kami mulai dengan mengubahnya secara sintaksis menjadi

var z = y.Where(foo=>foo.

Untuk mengetahui jenis foo kita harus terlebih dahulu mengetahui jenis y. Jadi pada poin ini kita bertanya kepada penentu tipe "apa tipe y"? Ini kemudian memulai evaluator ekspresi yang mem-parsing x.ToCharArray () dan menanyakan "apa tipe x"? Kami memiliki penentu jenis untuk itu yang mengatakan "Saya perlu mencari" String "dalam konteks saat ini". Tidak ada tipe String di tipe saat ini, jadi kami mencari di namespace. Itu tidak ada di sana juga jadi kita melihat di direktif menggunakan dan menemukan bahwa ada "Sistem menggunakan" dan Sistem itu memiliki tipe String. Oke, jadi itu tipe x.

Kami kemudian menanyakan metadata System.String untuk jenis ToCharArray dan dikatakan bahwa itu adalah System.Char []. Super. Jadi kami memiliki tipe untuk y.

Sekarang kita bertanya "apakah System.Char [] memiliki metode Dimana?" Tidak. Jadi kita melihat menggunakan arahan; kami telah menghitung database yang berisi semua metadata untuk metode ekstensi yang mungkin dapat digunakan.

Sekarang kita berkata "Oke, ada delapan belas lusin metode ekstensi bernama Where in scope, apakah salah satu dari mereka memiliki parameter formal pertama yang tipenya kompatibel dengan System.Char []?" Jadi kami memulai putaran pengujian konvertibilitas. Namun, metode ekstensi Where adalah generik , yang berarti kita harus melakukan inferensi tipe.

Saya telah menulis mesin infererencing tipe khusus yang dapat menangani pembuatan kesimpulan yang tidak lengkap dari argumen pertama ke metode ekstensi. Kami menjalankan tipe inferrer dan menemukan bahwa ada metode Di mana yang mengambil IEnumerable<T>, dan kami dapat membuat kesimpulan dari System.Char [] ke IEnumerable<System.Char>, jadi T adalah System.Char.

Tanda tangan dari metode ini adalah Where<T>(this IEnumerable<T> items, Func<T, bool> predicate), dan kita tahu bahwa T adalah System.Char. Kita juga tahu bahwa argumen pertama di dalam tanda kurung untuk metode ekstensi adalah lambda. Jadi kita memulai inferrer jenis ekspresi lambda yang mengatakan "parameter formal foo diasumsikan System.Char", gunakan fakta ini saat menganalisis lambda lainnya.

Kami sekarang memiliki semua informasi yang kami butuhkan untuk menganalisis tubuh lambda, yaitu "foo.". Kami mencari jenis foo, kami menemukan bahwa menurut pengikat lambda itu adalah System.Char, dan kami selesai; kami menampilkan informasi jenis untuk System.Char.

Dan kami melakukan segalanya kecuali analisis "tingkat atas" di antara penekanan tombol . Itu sedikit rumit. Sebenarnya menulis semua analisis tidaklah sulit; itu membuatnya cukup cepat sehingga Anda dapat melakukannya dengan kecepatan mengetik yang benar-benar rumit.

Semoga berhasil!

Eric Lippert
sumber
8
Eric, terima kasih atas balasan lengkapnya. Anda telah membuka mata saya sedikit. Untuk emacs, saya tidak bercita-cita untuk menghasilkan mesin dinamis antara ketukan tombol yang akan bersaing dengan Visual Studio dalam hal kualitas pengalaman pengguna. Untuk satu hal, karena ~ 0,5s latensi yang melekat dalam desain saya , fasilitas berbasis emacs dan akan tetap hanya sesuai permintaan; tidak ada saran untuk mengetik. Untuk yang lain - saya akan menerapkan dukungan dasar dari var lokal, tetapi saya akan dengan senang hati menyepak bola ketika ada yang tidak beres, atau ketika grafik ketergantungan melebihi batas tertentu. Belum yakin apa batasannya. Terima kasih lagi.
Cheeso
13
Sejujurnya mengejutkan saya bahwa semua ini dapat bekerja dengan sangat cepat dan andal, terutama dengan ekspresi lambda dan inferensi tipe umum. Saya sebenarnya cukup terkejut saat pertama kali saya menulis ekspresi lambda dan Intellisense mengetahui jenis parameter saya ketika saya menekan., Meskipun pernyataan itu belum lengkap dan saya tidak pernah secara eksplisit menentukan parameter umum dari metode ekstensi. Terima kasih untuk sedikit mengintip keajaiban ini.
Dan Bryant
21
@ Dan: Saya telah melihat (atau menulis) kode sumber dan itu mengejutkan saya bahwa itu juga berfungsi. :-) Ada beberapa benda berbulu di sana.
Eric Lippert
11
Orang-orang Eclipse mungkin melakukannya dengan lebih baik karena mereka lebih hebat daripada kompiler C # dan tim IDE.
Eric Lippert
23
Saya sama sekali tidak ingat membuat komentar bodoh ini. Ini bahkan tidak masuk akal. Saya pasti mabuk. Maaf.
Tomas Andrle
15

Saya dapat memberitahu Anda secara kasar bagaimana Delphi IDE bekerja dengan kompiler Delphi untuk melakukan intellisense (kode wawasan adalah apa yang Delphi sebut itu). Ini tidak 100% berlaku untuk C #, tapi ini adalah pendekatan menarik yang patut dipertimbangkan.

Sebagian besar analisis semantik di Delphi dilakukan di parser itu sendiri. Ekspresi diketik saat diurai, kecuali untuk situasi di mana hal ini tidak mudah - dalam hal ini penguraian tampilan ke depan digunakan untuk mengetahui apa yang diinginkan, dan kemudian keputusan tersebut digunakan dalam penguraian.

Parse sebagian besar adalah LL (2) turunan rekursif, kecuali untuk ekspresi, yang diurai menggunakan prioritas operator. Salah satu hal yang berbeda tentang Delphi adalah bahwa ini adalah bahasa single-pass, jadi konstruksi perlu dideklarasikan sebelum digunakan, jadi tidak diperlukan pass level atas untuk mengeluarkan informasi itu.

Kombinasi fitur ini berarti bahwa pengurai memiliki kira-kira semua informasi yang diperlukan untuk wawasan kode untuk setiap titik yang membutuhkannya. Cara kerjanya adalah sebagai berikut: IDE menginformasikan lexer kompiler tentang posisi kursor (titik di mana wawasan kode diinginkan) dan lexer mengubahnya menjadi token khusus (disebut token kibitz). Setiap kali parser bertemu dengan token ini (yang bisa berada di mana saja) ia tahu bahwa ini adalah sinyal untuk mengirim kembali semua informasi yang dimilikinya kembali ke editor. Ia melakukan ini menggunakan longjmp karena ditulis dalam C; apa yang dilakukannya adalah memberi tahu pemanggil terakhir tentang jenis konstruksi sintaksis (yaitu konteks tata bahasa) tempat titik kibitz ditemukan, serta semua tabel simbolik yang diperlukan untuk titik itu. Misalnya, jika konteksnya berada dalam ekspresi yang merupakan argumen untuk suatu metode, kita dapat memeriksa kelebihan metode, melihat tipe argumen, dan memfilter simbol yang valid hanya untuk yang dapat menyelesaikan tipe argumen itu (ini memotong dalam banyak cruft yang tidak relevan di drop-down). Jika berada dalam konteks lingkup bersarang (misalnya setelah "."), Parser akan mengembalikan referensi ke lingkup, dan IDE dapat menyebutkan semua simbol yang ditemukan dalam lingkup itu.

Hal-hal lain juga dilakukan; misalnya, badan metode dilewati jika token kibitz tidak berada dalam jangkauannya - ini dilakukan secara optimis, dan dibatalkan jika token dilewati. Setara dengan metode ekstensi - pembantu kelas di Delphi - memiliki semacam cache berversi, sehingga pencariannya cukup cepat. Tapi tipe inferensi umum Delphi jauh lebih lemah daripada C #.

Sekarang, ke pertanyaan khusus: menyimpulkan jenis variabel yang dideklarasikan dengan varsetara dengan cara Pascal menyimpulkan jenis konstanta. Itu berasal dari jenis ekspresi inisialisasi. Jenis ini dibangun dari bawah ke atas. If xis of type Integer, and yis of type Double, maka x + yakan menjadi type Double, karena itulah aturan bahasanya; dll. Anda mengikuti aturan ini sampai Anda memiliki tipe untuk ekspresi penuh di sisi kanan, dan itu adalah tipe yang Anda gunakan untuk simbol di sebelah kiri.

Barry Kelly
sumber
7

Jika Anda tidak ingin menulis parser Anda sendiri untuk membangun pohon sintaksis abstrak, Anda dapat menggunakan parser dari SharpDevelop atau MonoDevelop , keduanya open source.

Daniel Plaisted
sumber
4

Sistem Intellisense biasanya merepresentasikan kode menggunakan Pohon Sintaks Abstrak, yang memungkinkan mereka untuk menyelesaikan jenis kembalian dari fungsi yang ditugaskan ke variabel 'var' dengan cara yang kurang lebih sama seperti yang akan dilakukan kompilator. Jika Anda menggunakan VS Intellisense, Anda mungkin memperhatikan bahwa itu tidak akan memberi Anda tipe var sampai Anda selesai memasukkan ekspresi penugasan yang valid (dapat diatasi). Jika ekspresi masih ambigu (misalnya, tidak dapat sepenuhnya menyimpulkan argumen umum untuk ekspresi tersebut), jenis var tidak akan menyelesaikan. Ini bisa menjadi proses yang cukup rumit, karena Anda mungkin perlu berjalan cukup jauh ke dalam pohon untuk menyelesaikan jenisnya. Misalnya:

var items = myList.OfType<Foo>().Select(foo => foo.Bar);

Jenis kembaliannya adalah IEnumerable<Bar>, tetapi menyelesaikan ini membutuhkan pengetahuan:

  1. myList adalah tipe yang mengimplementasikan IEnumerable.
  2. Ada metode ekstensi OfType<T>yang berlaku untuk IEnumerable.
  3. Nilai yang dihasilkan adalah IEnumerable<Foo>dan ada metode ekstensi Selectyang berlaku untuk ini.
  4. Ekspresi lambda foo => foo.Barmemiliki parameter foo berjenis Foo. Hal ini disimpulkan dengan penggunaan Select, yang membutuhkan Func<TIn,TOut>dan karena TIn diketahui (Foo), jenis foo dapat disimpulkan.
  5. Tipe Foo memiliki properti Bar, yaitu tipe Bar. Kita tahu bahwa Select return IEnumerable<TOut>dan TOut dapat disimpulkan dari hasil ekspresi lambda, jadi jenis item yang dihasilkan haruslah IEnumerable<Bar>.
Dan Bryant
sumber
Benar, itu bisa sangat dalam. Saya merasa nyaman dengan menyelesaikan semua dependensi. Hanya memikirkan hal ini, opsi pertama yang saya jelaskan - kompilasi dan panggil - sama sekali tidak dapat diterima, karena memanggil kode dapat memiliki efek samping, seperti memperbarui database, dan itu bukan sesuatu yang harus dilakukan editor. Mengompilasi tidak apa-apa, tidak memohon. Sejauh membangun AST, saya rasa saya tidak ingin melakukan itu. Sungguh saya ingin menangguhkan pekerjaan itu ke kompiler, yang sudah tahu bagaimana melakukannya. Saya ingin dapat meminta kompiler untuk memberi tahu saya apa yang ingin saya ketahui. Saya hanya ingin jawaban sederhana.
Cheeso
Tantangan dalam menginspeksinya dari kompilasi adalah bahwa dependensi bisa sangat dalam, yang berarti Anda mungkin perlu membangun semuanya agar compiler dapat menghasilkan kode. Jika Anda melakukan itu, saya pikir Anda dapat menggunakan simbol debugger dengan IL yang dihasilkan dan mencocokkan tipe setiap lokal dengan simbol itu.
Dan Bryant
1
@Cheeso: kompilator tidak menawarkan jenis analisis seperti itu sebagai layanan. Saya berharap di masa depan itu akan tetapi tidak ada janji.
Eric Lippert
ya, saya pikir itu mungkin cara untuk pergi - menyelesaikan semua dependensi dan kemudian mengkompilasi dan memeriksa IL. @ Eric, senang mengetahuinya. Untuk saat ini jika saya tidak bercita-cita untuk melakukan analisis AST lengkap, jadi saya harus menggunakan peretasan kotor untuk menghasilkan layanan ini menggunakan alat yang ada. Misalnya, kompilasi fragmen kode yang dibangun dengan cerdas dan kemudian gunakan ILDASM (atau serupa) secara terprogram untuk mendapatkan jawaban yang saya cari.
Cheeso
4

Karena Anda menargetkan Emacs, mungkin yang terbaik adalah memulai dengan rangkaian CEDET. Semua detail yang Eric Lippert tercakup dalam penganalisis kode di alat CEDET / Semantic untuk C ++ sudah. Ada juga parser C # (yang mungkin membutuhkan sedikit TLC) sehingga satu-satunya bagian yang hilang terkait dengan penyetelan bagian yang diperlukan untuk C #.

Perilaku dasar ditentukan dalam algoritme inti yang bergantung pada fungsi yang dapat diisi berlebih yang ditentukan per bahasa. Keberhasilan mesin penyelesaian tergantung pada seberapa banyak penyetelan yang telah dilakukan. Dengan c ++ sebagai panduan, mendapatkan dukungan yang mirip dengan C ++ seharusnya tidak terlalu buruk.

Jawaban Daniel menyarankan penggunaan MonoDevelop untuk melakukan parsing dan analisis. Ini bisa menjadi mekanisme alternatif daripada pengurai C # yang sudah ada, atau bisa digunakan untuk menambah pengurai yang ada.

Eric
sumber
Benar, saya tahu tentang CEDET, dan saya menggunakan dukungan C # di direktori contrib untuk semantik. Semantic menyediakan daftar variabel lokal dan tipenya. Mesin penyelesaian dapat memindai daftar itu dan menawarkan pilihan yang tepat kepada pengguna. Masalahnya adalah saat variabelnya var. Semantic dengan benar mengidentifikasinya sebagai var, tetapi tidak memberikan inferensi tipe. Pertanyaan saya adalah khusus sekitar bagaimana untuk mengatasi itu . Saya juga mencari cara untuk memasukkan penyelesaian CEDET yang ada, tetapi saya tidak tahu caranya. Dokumentasi untuk CEDET adalah ... ah ... tidak lengkap.
Cheeso
Komentar samping - CEDET sangat ambisius, tetapi saya merasa sulit untuk menggunakan dan memperluasnya. Saat ini parser memperlakukan "namespace" sebagai indikator kelas di C #. Saya bahkan tidak tahu cara menambahkan "namespace" sebagai elemen sintaksis yang berbeda. Melakukan hal itu mencegah semua analisis sintaksis lainnya, dan saya tidak tahu mengapa. Saya sebelumnya menjelaskan kesulitan yang saya alami dengan kerangka penyelesaian. Di luar masalah ini, ada jahitan dan tumpang tindih antar bagian. Sebagai salah satu contoh, navigasi adalah bagian dari semantik dan senator. CEDET tampaknya menarik, tetapi pada akhirnya ... terlalu berat untuk berkomitmen.
Cheeso
Cheeso, jika Anda ingin mendapatkan hasil maksimal dari bagian-bagian CEDET yang kurang terdokumentasi, taruhan terbaik Anda adalah mencoba milis. Sangat mudah bagi pertanyaan untuk menyelidiki area yang belum berkembang dengan baik, sehingga dibutuhkan beberapa iterasi untuk mendapatkan solusi yang baik, atau untuk menjelaskan solusi yang sudah ada. Untuk C # khususnya, karena saya tidak tahu apa-apa tentang itu, tidak akan ada jawaban yang sederhana.
Eric
2

Sulit untuk melakukannya dengan baik. Pada dasarnya Anda perlu memodelkan spek / kompiler bahasa melalui sebagian besar pemeriksaan lexing / parsing / typecheck dan membangun model internal kode sumber yang kemudian dapat Anda kueri. Eric menjelaskannya secara rinci untuk C #. Anda selalu dapat mengunduh kode sumber kompilator F # (bagian dari F # CTP) dan service.fsimelihat antarmuka yang diekspos keluar dari kompilator F # yang digunakan layanan bahasa F # untuk menyediakan intellisense, keterangan alat untuk tipe yang disimpulkan, dll. rasa 'antarmuka' yang mungkin jika Anda sudah memiliki kompiler yang tersedia sebagai API untuk dipanggil.

Rute lainnya adalah dengan menggunakan kembali kompiler apa adanya seperti yang Anda gambarkan, dan kemudian gunakan refleksi atau lihat kode yang dihasilkan. Ini bermasalah dari sudut pandang bahwa Anda memerlukan 'program lengkap' untuk mendapatkan output kompilasi dari kompiler, sedangkan saat mengedit kode sumber di editor, Anda seringkali hanya memiliki 'program parsial' yang belum diurai, jangan sudah menerapkan semua metode, dll.

Singkatnya, menurut saya versi 'anggaran rendah' ​​sangat sulit dilakukan dengan baik, dan versi 'nyata' sangat, sangat sulit dilakukan dengan baik. (Di mana 'keras' di sini mengukur 'upaya' dan 'kesulitan teknis'.)

Brian
sumber
Ya, versi "anggaran rendah" memiliki beberapa batasan yang jelas. Saya mencoba untuk memutuskan apa yang "cukup baik", dan apakah saya dapat memenuhi standar itu. Dalam pengalaman saya sendiri, melakukan dogfood terhadap apa yang saya dapatkan sejauh ini, itu membuat menulis C # dalam emacs jauh lebih bagus.
Cheeso
0

Untuk solusi "1" Anda memiliki fasilitas baru di .NET 4 untuk melakukan ini dengan cepat dan mudah. Jadi, jika Anda dapat mengubah program Anda menjadi .NET 4, itu akan menjadi pilihan terbaik Anda.

Softlion
sumber