Tôi đang làm việc trên một cơ sở hoàn thành (intellisense) cho C # trong emacs.
Ý tưởng là, nếu người dùng nhập một phân đoạn, sau đó yêu cầu hoàn thành thông qua một tổ hợp phím cụ thể, cơ sở hoàn thành sẽ sử dụng phản chiếu .NET để xác định các hoàn thành có thể xảy ra.
Thực hiện điều này đòi hỏi bạn phải biết rõ loại sự việc đang được hoàn thành. Nếu đó là một chuỗi, có một tập hợp các phương thức và thuộc tính khả thi đã biết; nếu đó là Int32, nó có một bộ riêng biệt, v.v.
Sử dụng ngữ nghĩa, một gói mã lexer / phân tích cú pháp có sẵn trong emacs, tôi có thể xác định vị trí các khai báo biến và kiểu của chúng. Do đó, thật đơn giản khi sử dụng phản chiếu để lấy các phương thức và thuộc tính trên kiểu, sau đó trình bày danh sách các tùy chọn cho người dùng. (Được rồi, không hoàn toàn đơn giản để thực hiện trong emacs, nhưng sử dụng khả năng chạy quy trình powershell bên trong emacs , nó sẽ trở nên dễ dàng hơn nhiều. Tôi viết một hợp ngữ .NET tùy chỉnh để thực hiện phản chiếu, tải nó vào powershell và sau đó chạy ngay trong emacs có thể gửi lệnh tới powershell và đọc phản hồi, thông qua comint. Do đó, emacs có thể nhận được kết quả phản ánh một cách nhanh chóng.)
Vấn đề xảy ra khi mã sử dụng var
trong khai báo điều được hoàn thành. Điều đó có nghĩa là loại không được chỉ định rõ ràng và việc hoàn thành sẽ không hoạt động.
Làm cách nào để xác định một cách đáng tin cậy kiểu thực được sử dụng, khi biến được khai báo với var
từ khóa? Chỉ cần nói rõ, tôi không cần xác định nó trong thời gian chạy. Tôi muốn xác định nó tại "Thời gian thiết kế".
Cho đến nay tôi có những ý tưởng sau:
- biên dịch và gọi:
- trích xuất câu lệnh khai báo, ví dụ: `var foo =" a string value ";`
- nối một câu lệnh `foo.GetType (); '
- biên dịch động phân mảnh C # kết quả nó thành một hợp ngữ mới
- tải assembly vào một AppDomain mới, chạy framgment và lấy kiểu trả về.
- dỡ bỏ và loại bỏ lắp ráp
Tôi biết làm thế nào để làm tất cả những điều này. Nhưng nó có vẻ nặng khủng khiếp, đối với mỗi yêu cầu hoàn thành trong trình chỉnh sửa.
Tôi cho rằng tôi không cần một AppDomain mới mỗi lần. Tôi có thể sử dụng lại một AppDomain duy nhất cho nhiều cụm tạm thời và khấu hao chi phí thiết lập và chia nhỏ nó, cho nhiều yêu cầu hoàn thành. Đó là một sự thay đổi ý tưởng cơ bản hơn.
- biên dịch và kiểm tra IL
Đơn giản chỉ cần biên dịch khai báo thành một mô-đun, và sau đó kiểm tra IL, để xác định kiểu thực tế được trình biên dịch suy ra. Làm thế nào điều này có thể được? Tôi sẽ sử dụng gì để kiểm tra IL?
Bất kỳ ý tưởng tốt hơn ra khỏi đó? Bình luận? gợi ý?
EDIT - suy nghĩ về điều này xa hơn, biên dịch và gọi là không được chấp nhận, vì lệnh gọi có thể có tác dụng phụ. Vì vậy, lựa chọn đầu tiên phải được loại trừ.
Ngoài ra, tôi nghĩ rằng tôi không thể giả định sự hiện diện của .NET 4.0.
CẬP NHẬT - Câu trả lời đúng, chưa được đề cập ở trên, nhưng được Eric Lippert chỉ ra một cách nhẹ nhàng, là triển khai một hệ thống suy luận kiểu trung thực đầy đủ. Đó là cách duy nhất để xác định một cách đáng tin cậy loại var tại thời điểm thiết kế. Nhưng, nó cũng không dễ thực hiện. Bởi vì tôi không có ảo tưởng rằng tôi muốn cố gắng tạo ra một thứ như vậy, tôi đã sử dụng phím tắt của tùy chọn 2 - trích xuất mã khai báo có liên quan và biên dịch nó, sau đó kiểm tra IL kết quả.
Điều này thực sự hoạt động, đối với một tập hợp con hợp lý của các tình huống hoàn thành.
Ví dụ, giả sử trong các đoạn mã sau, dấu? là vị trí mà người dùng yêu cầu hoàn thành. Những công việc này:
var x = "hello there";
x.?
Việc hoàn thành nhận ra rằng x là một Chuỗi và cung cấp các tùy chọn thích hợp. Nó thực hiện điều này bằng cách tạo và sau đó biên dịch mã nguồn sau:
namespace N1 {
static class dmriiann5he { // randomly-generated class name
static void M1 () {
var x = "hello there";
}
}
}
... và sau đó kiểm tra IL với phản xạ đơn giản.
Điều này cũng hoạt động:
var x = new XmlDocument();
x.?
Công cụ thêm các mệnh đề sử dụng thích hợp vào mã nguồn được tạo, để nó biên dịch đúng cách và sau đó việc kiểm tra IL cũng giống như vậy.
Điều này cũng hoạt động:
var x = "hello";
var y = x.ToCharArray();
var z = y.?
Nó chỉ có nghĩa là việc kiểm tra IL phải tìm loại biến cục bộ thứ ba, thay vì biến thứ nhất.
Và điều này:
var foo = "Tra la la";
var fred = new System.Collections.Generic.List<String>
{
foo,
foo.Length.ToString()
};
var z = fred.Count;
var x = z.?
... mà chỉ là một cấp độ sâu hơn ví dụ trước.
Nhưng, những gì không hoạt động là hoàn thành trên bất kỳ biến cục bộ nào mà việc khởi tạo phụ thuộc vào bất kỳ điểm nào vào một thành viên cá thể hoặc đối số phương thức cục bộ. Giống:
var foo = this.InstanceMethod();
foo.?
Cũng không phải cú pháp LINQ.
Tôi sẽ phải nghĩ xem những thứ đó có giá trị như thế nào trước khi xem xét giải quyết chúng thông qua thứ chắc chắn là "thiết kế hạn chế" (từ lịch sự để chỉ hack) để hoàn thành.
Một cách tiếp cận để giải quyết vấn đề với các phụ thuộc vào đối số phương thức hoặc phương thức phiên bản sẽ là thay thế, trong đoạn mã được tạo, biên dịch và sau đó IL phân tích, các tham chiếu đến những thứ đó bằng các vars cục bộ "tổng hợp" cùng loại.
Cập nhật khác - hoàn thành trên các vars phụ thuộc vào các thành viên cá thể, hiện đã hoạt động.
Những gì tôi đã làm là thẩm vấn loại (thông qua ngữ nghĩa), và sau đó tạo ra các thành viên độc lập tổng hợp cho tất cả các thành viên hiện có. Đối với bộ đệm C # như thế này:
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...
... mã đã tạo được biên dịch, để tôi có thể học từ IL đầu ra, kiểu của var nnn cục bộ, trông giống như sau:
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);
}
}
}
Tất cả các thành viên kiểu thể hiện và kiểu tĩnh đều có sẵn trong mã khung. Nó biên dịch thành công. Tại thời điểm đó, việc xác định loại var cục bộ rất đơn giản thông qua Reflection.
Điều làm cho điều này trở nên khả thi là:
- khả năng chạy powershell trong emacs
- trình biên dịch C # rất nhanh. Trên máy của tôi, mất khoảng 0,5 giây để biên dịch một cụm trong bộ nhớ. Không đủ nhanh để phân tích giữa các lần nhấn phím, nhưng đủ nhanh để hỗ trợ tạo danh sách hoàn thành theo yêu cầu.
Tôi vẫn chưa xem xét LINQ.
Đó sẽ là một vấn đề lớn hơn nhiều vì emacs lexer / parser ngữ nghĩa dành cho C #, không "làm" LINQ.