Apakah Roslyn SyntaxNodes digunakan kembali?

124

Saya telah melihat ke Roslyn CTP dan, sementara itu memecahkan masalah yang mirip dengan API pohon Ekspresi , keduanya tidak dapat diubah tetapi Roslyn melakukannya dengan cara yang sangat berbeda:

  • Expressionnode tidak memiliki referensi ke node induk, dimodifikasi menggunakan a ExpressionVisitordan itulah mengapa sebagian besar dapat digunakan kembali.

  • Roslyn SyntaxNode, di sisi lain, memiliki referensi ke induknya, jadi semua node secara efektif menjadi blok yang tidak mungkin digunakan kembali. Metode seperti Update,, ReplaceNodedll, disediakan untuk melakukan modifikasi.

Dimana ini berakhir? Document? Project? ISolution? API mempromosikan perubahan langkah-demi-langkah dari pohon (bukan tombol ke atas), tetapi apakah setiap langkah membuat salinan lengkap?

Mengapa mereka membuat pilihan seperti itu? Apakah ada trik menarik yang saya lewatkan?

Olmo
sumber

Jawaban:

181

PEMBARUAN: Pertanyaan ini adalah subjek blog saya pada tanggal 8 Juni 2012 . Terima kasih atas pertanyaan bagusnya!


Pertanyaan bagus. Kami memperdebatkan masalah yang Anda angkat untuk waktu yang sangat lama.

Kami ingin memiliki struktur data yang memiliki karakteristik sebagai berikut:

  • Kekal.
  • Bentuk pohon.
  • Akses murah ke node induk dari node anak.
  • Dimungkinkan untuk memetakan dari simpul di pohon ke offset karakter dalam teks.
  • Gigih .

Yang saya maksud dengan ketekunan adalah kemampuan untuk menggunakan kembali sebagian besar node yang ada di pohon saat pengeditan dilakukan pada buffer teks. Karena node tidak dapat diubah, tidak ada penghalang untuk menggunakannya kembali. Kami membutuhkan ini untuk kinerja; kami tidak dapat mengurai ulang gumpalan besar file setiap kali Anda menekan tombol. Kita perlu melakukan lex ulang dan mengurai ulang hanya bagian dari pohon yang terpengaruh oleh edit.

Sekarang ketika Anda mencoba memasukkan kelima hal itu ke dalam satu struktur data, Anda langsung mengalami masalah:

  • Bagaimana Anda membangun node di tempat pertama? Orang tua dan anak keduanya merujuk satu sama lain, dan tidak dapat diubah, jadi mana yang dibangun lebih dulu?
  • Seandainya Anda berhasil memecahkan masalah itu: bagaimana Anda membuatnya terus-menerus? Anda tidak dapat menggunakan kembali simpul anak di induk yang berbeda karena itu akan melibatkan memberi tahu anak bahwa ia memiliki induk baru. Tapi anak itu tidak bisa diubah.
  • Misalkan Anda berhasil memecahkan masalah itu: ketika Anda memasukkan karakter baru ke dalam buffer edit, posisi absolut setiap node yang dipetakan ke posisi setelah titik itu berubah. Ini membuat sangat sulit untuk membuat struktur data yang persisten, karena setiap pengeditan dapat mengubah rentang sebagian besar node!

Tetapi di tim Roslyn kami secara rutin melakukan hal-hal yang tidak mungkin. Kami sebenarnya melakukan hal yang mustahil dengan memelihara dua pohon parse. Pohon "hijau" tidak dapat diubah, tetap, tidak memiliki referensi induk, dibuat "dari bawah ke atas", dan setiap node melacak lebarnya tetapi bukan posisi absolutnya . Ketika pengeditan terjadi, kami hanya membangun kembali bagian dari pohon hijau yang terpengaruh oleh pengeditan, yang biasanya sekitar O (log n) dari total parse node di pohon.

Pohon "merah" adalah fasad kekal yang dibangun di sekitar pohon hijau; itu dibangun "top-down" sesuai permintaan dan dibuang pada setiap edit. Ini menghitung referensi induk dengan membuatnya sesuai permintaan saat Anda turun melalui pohon dari atas . Ini menghasilkan posisi absolut dengan menghitungnya dari lebar, sekali lagi, saat Anda turun.

Anda, sebagai pengguna, hanya pernah melihat pohon merah; pohon hijau adalah detail implementasi. Jika Anda mengintip ke dalam status internal node parse, Anda sebenarnya akan melihat bahwa ada referensi ke node parse lain di sana dengan tipe yang berbeda; itulah simpul pohon hijau.

Kebetulan, ini disebut "pohon merah / hijau" karena itulah warna penanda papan tulis yang kami gunakan untuk menggambar struktur data dalam pertemuan desain. Tidak ada arti lain dari warna.

Manfaat dari strategi ini adalah kita mendapatkan semua hal hebat itu: kekekalan, ketekunan, referensi orang tua, dan sebagainya. Biayanya adalah sistem ini rumit dan dapat menghabiskan banyak memori jika fasad "merah" menjadi besar. Saat ini kami sedang melakukan eksperimen untuk melihat apakah kami dapat mengurangi sebagian biaya tanpa kehilangan manfaatnya.

Eric Lippert
sumber
3
Dan untuk menjawab pertanyaan Anda tentang IProjects dan IDocuments: kami menggunakan model serupa di lapisan layanan. Secara internal ada jenis "DocumentState" dan "ProjectState" yang secara moral setara dengan simpul hijau pohon sintaks. Objek IProject / IDocument yang Anda dapatkan adalah fasad node merah untuk ini. Jika Anda melihat implementasi Roslyn.Services.Project dalam sebuah decompiler, Anda akan melihat bahwa hampir semua panggilan diteruskan ke objek keadaan internal.
Jason Malinowski
@Eric minta maaf atas komentarnya, tetapi Anda bertentangan dengan diri sendiri. The expense and difficulty of building a complex persistent data structure doesn't pay for itself.ref: stackoverflow.com/questions/6742923/… Jika Anda memiliki sasaran kinerja tinggi, mengapa Anda membuatnya tidak berubah sejak awal? Apakah hanya ada alasan lain selain yang sudah jelas? misalnya lebih mudah untuk membuat threadsafe, bernalar tentang dll.
Lukasz Madon
2
@lukas Anda mengambil kutipan itu di luar konteks. Kalimat sebelumnya adalah "Karena ketika Anda melihat operasi yang biasanya dilakukan pada string dalam program .NET, itu dalam segala hal yang relevan hampir tidak lebih buruk sama sekali hanya membuat string yang sama sekali baru." OTOH, saat Anda melihat operasi yang biasanya dilakukan pada pohon ekspresi - misalnya mengetik beberapa karakter ke dalam file sumber - akan jauh lebih buruk untuk membangun pohon ekspresi yang sama sekali baru. Jadi mereka hanya membangun setengahnya.
Timbo
1
@lukas Dugaan saya: Mengingat bahwa Roslyn seharusnya beroperasi pada utas latar belakang, kekekalan memungkinkan beberapa utas untuk menganalisis kode sumber yang sama pada saat yang sama tanpa khawatir itu akan diubah ketika pengguna menekan tombol. Menanggapi masukan pengguna, pohon yang tidak dapat diubah dapat diperbarui tanpa menghentikan tugas analisis yang sedang berjalan. Jadi saya membayangkan bahwa tujuan utama kekekalan adalah membuat Roslyn lebih mudah untuk menulis (dan mungkin lebih mudah digunakan oleh klien).
Qwertie
3
@lukas Struktur data persisten lebih efisien daripada menyalin, ketika struktur data biasanya jauh lebih besar daripada perubahan padanya. Maksud Anda, jika Anda memilikinya, hilang pada saya.
Qwertie