Làm cách nào để tôi có thể xác định một cách đáng tin cậy loại biến được khai báo bằng var tại thời điểm thiết kế?


109

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 vartrong 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 vartừ 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:

  1. 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.

  2. 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.


4
Kiểu foo được trình biên dịch tìm ra và điền vào thông qua suy luận kiểu. Tôi nghi ngờ các cơ chế hoàn toàn khác nhau. Có lẽ động cơ suy luận kiểu có một cái móc? Ít nhất tôi sẽ sử dụng 'kiểu suy luận' làm thẻ.
George Mauer

3
Kỹ thuật tạo mô hình đối tượng "giả" có tất cả các kiểu nhưng không có ngữ nghĩa nào của các đối tượng thực là một kỹ thuật tốt. Đó là cách tôi đã làm IntelliSense cho JScript trong Visual InterDev ngày trước; chúng tôi tạo một phiên bản "giả" của mô hình đối tượng IE có tất cả các phương thức và kiểu nhưng không có tác dụng phụ nào, sau đó chạy một trình thông dịch nhỏ qua mã đã phân tích cú pháp tại thời điểm biên dịch và xem kiểu nào trở lại.
Eric Lippert

Câu trả lời:


202

Tôi có thể mô tả cho bạn cách chúng tôi thực hiện điều đó một cách hiệu quả trong C # IDE "thực".

Điều đầu tiên chúng tôi làm là chạy một đường chuyền chỉ phân tích nội dung "cấp cao nhất" trong mã nguồn. Chúng tôi bỏ qua tất cả các phần thân của phương thức. Điều đó cho phép chúng ta nhanh chóng xây dựng cơ sở dữ liệu thông tin về không gian tên, kiểu và phương thức (và các hàm tạo, v.v.) trong mã nguồn của chương trình. Việc phân tích từng dòng mã trong mỗi nội dung phương thức sẽ mất quá nhiều thời gian nếu bạn đang cố gắng thực hiện việc đó giữa các lần nhấn phím.

Khi IDE cần tìm ra loại biểu thức cụ thể bên trong thân phương thức - giả sử bạn đã nhập "foo". và chúng tôi cần tìm ra thành viên của foo là gì - chúng tôi cũng làm điều tương tự; chúng tôi bỏ qua nhiều công việc nhất có thể.

Chúng tôi bắt đầu với một đường chuyền chỉ phân tích các khai báo biến cục bộ trong phương thức đó. Khi chúng tôi chạy đường chuyền đó, chúng tôi thực hiện một ánh xạ từ một cặp "phạm vi" và "tên" thành "bộ xác định kiểu". "Công cụ xác định kiểu" là một đối tượng đại diện cho khái niệm "Tôi có thể tìm ra loại cục bộ này nếu tôi cần". Việc tìm ra loại địa phương có thể tốn kém, vì vậy chúng tôi muốn hoãn lại công việc đó nếu cần.

Bây giờ chúng tôi có một cơ sở dữ liệu được xây dựng một cách lười biếng có thể cho chúng tôi biết loại của mọi địa phương. Vì vậy, quay trở lại "foo" đó. - chúng tôi tìm ra cái nào câu lệnh biểu thức có liên quan nằm trong và sau đó chạy trình phân tích ngữ nghĩa dựa trên câu lệnh đó. Ví dụ: giả sử bạn có phần thân phương thức:

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

và bây giờ chúng ta cần tìm ra rằng foo thuộc loại char. Chúng tôi xây dựng cơ sở dữ liệu có tất cả siêu dữ liệu, phương thức mở rộng, loại mã nguồn, v.v. Chúng tôi xây dựng một cơ sở dữ liệu có các bộ xác định kiểu cho x, y và z. Chúng tôi phân tích câu lệnh chứa biểu thức thú vị. Chúng tôi bắt đầu bằng cách chuyển đổi cú pháp thành

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

Để tìm ra loại foo trước tiên chúng ta phải biết loại y. Vì vậy, tại thời điểm này, chúng tôi hỏi người xác định kiểu "kiểu của y" là gì? Sau đó, nó khởi động một trình đánh giá biểu thức phân tích cú pháp x.ToCharArray () và hỏi "kiểu của x" là gì? Chúng tôi có một công cụ xác định loại cho biết "Tôi cần tra cứu" Chuỗi "trong ngữ cảnh hiện tại". Không có kiểu Chuỗi trong kiểu hiện tại, vì vậy chúng tôi tìm kiếm trong không gian tên. Nó cũng không ở đó vì vậy chúng tôi xem xét các chỉ thị sử dụng và phát hiện ra rằng có một "Hệ thống đang sử dụng" và Hệ thống đó có một Chuỗi loại. OK, vậy đó là kiểu của x.

Sau đó, chúng tôi truy vấn siêu dữ liệu của System.String cho loại ToCharArray và nó nói rằng đó là System.Char []. Siêu. Vì vậy, chúng tôi có một loại cho y.

Bây giờ chúng ta hỏi "System.Char [] có phương thức ở đâu không?" Không. Vì vậy, chúng tôi xem xét các chỉ thị sử dụng; chúng tôi đã tính toán trước một cơ sở dữ liệu chứa tất cả siêu dữ liệu cho các phương thức mở rộng có thể được sử dụng.

Bây giờ chúng ta nói "OK, có mười tám chục phương thức mở rộng được đặt tên là Where trong scope, có bất kỳ phương thức nào trong số chúng có tham số hình thức đầu tiên có kiểu tương thích với System.Char [] không?" Vì vậy, chúng tôi bắt đầu một vòng kiểm tra khả năng chuyển đổi. Tuy nhiên, các phương thức mở rộng Where là chung chung , có nghĩa là chúng ta phải thực hiện suy luận kiểu.

Tôi đã viết một công cụ suy diễn kiểu đặc biệt có thể xử lý việc đưa ra các suy luận không đầy đủ từ đối số đầu tiên đến một phương thức mở rộng. Chúng tôi chạy trình suy luận kiểu và phát hiện ra rằng có một phương thức Where nhận một IEnumerable<T>và chúng tôi có thể đưa ra suy luận từ System.Char [] đến IEnumerable<System.Char>, vì vậy T là System.Char.

Chữ ký của phương thức này là Where<T>(this IEnumerable<T> items, Func<T, bool> predicate), và chúng ta biết rằng T là System.Char. Ngoài ra, chúng ta biết rằng đối số đầu tiên bên trong dấu ngoặc đơn của phương thức mở rộng là một lambda. Vì vậy, chúng tôi bắt đầu một trình suy luận kiểu biểu thức lambda cho biết "tham số chính thức foo được giả định là System.Char", sử dụng dữ kiện này khi phân tích phần còn lại của lambda.

Bây giờ chúng ta có tất cả thông tin cần thiết để phân tích phần thân của lambda, đó là "foo.". Chúng tôi tra cứu loại foo, chúng tôi phát hiện ra rằng theo chất kết dính lambda thì đó là System.Char, và chúng tôi đã hoàn tất; chúng tôi hiển thị thông tin loại cho System.Char.

Và chúng tôi làm mọi thứ ngoại trừ phân tích "cấp cao nhất" giữa các lần nhấn phím . Đó là một chút khó khăn thực sự. Thực ra viết tất cả các bài phân tích không khó; nó làm cho nó đủ nhanh để bạn có thể làm điều đó với tốc độ đánh máy, đó là một chút khó khăn thực sự.

Chúc may mắn!


8
Eric, cảm ơn vì đã trả lời đầy đủ. Bạn đã mở rộng tầm mắt của tôi một chút. Đối với emacs, tôi không có mong muốn tạo ra một công cụ động, giữa các phím bấm để cạnh tranh với Visual Studio về chất lượng trải nghiệm người dùng. Đối với một điều, vì độ trễ ~ 0,5 giây vốn có trong thiết kế của tôi , cơ sở dựa trên emacs đang và sẽ chỉ duy trì theo yêu cầu; không có đề xuất loại trước. Đối với vấn đề khác - tôi sẽ triển khai hỗ trợ cơ bản của var local, nhưng tôi sẽ rất vui khi mọi thứ trở nên rối rắm hoặc khi biểu đồ phụ thuộc vượt quá một giới hạn nhất định. Không chắc chắn giới hạn đó là gì. Cảm ơn một lần nữa.
Cheeso

13
Nó thực sự làm tôi bối rối rằng tất cả điều này có thể hoạt động rất nhanh chóng và đáng tin cậy, đặc biệt là với các biểu thức lambda và suy luận kiểu chung. Tôi thực sự khá ngạc nhiên khi lần đầu tiên viết biểu thức lambda và Intellisense biết loại tham số của tôi khi tôi nhấn., Mặc dù câu lệnh chưa hoàn chỉnh và tôi chưa bao giờ chỉ định rõ ràng các tham số chung của các phương thức mở rộng. Cảm ơn vì cái nhìn nhỏ này vào điều kỳ diệu.
Dan Bryant

21
@Dan: Tôi đã xem (hoặc viết) mã nguồn và nó làm tôi bối rối rằng nó cũng hoạt động. :-) Có một số thứ lông trong đó.
Eric Lippert

11
Những người chơi Eclipse có lẽ làm điều đó tốt hơn vì họ tuyệt vời hơn so với trình biên dịch C # và nhóm IDE.
Eric Lippert

23
Tôi không nhớ đã đưa ra nhận xét ngu ngốc này chút nào. Nó thậm chí không có ý nghĩa. Chắc tôi say rồi. Lấy làm tiếc.
Tomas Andrle

15

Tôi có thể cho bạn biết đại khái cách Delphi IDE hoạt động với trình biên dịch Delphi để thực hiện intellisense (thông tin chi tiết về mã là những gì Delphi gọi nó). Nó không áp dụng được 100% cho C #, nhưng đó là một cách tiếp cận thú vị đáng được xem xét.

Hầu hết các phân tích ngữ nghĩa trong Delphi được thực hiện trong chính trình phân tích cú pháp. Các biểu thức được nhập khi chúng được phân tích cú pháp, ngoại trừ những trường hợp không dễ dàng - trong trường hợp đó, phân tích cú pháp nhìn trước được sử dụng để tìm ra những gì dự định và sau đó quyết định đó được sử dụng trong quá trình phân tích cú pháp.

Phân tích cú pháp phần lớn là xuống gốc đệ quy LL (2), ngoại trừ các biểu thức, được phân tích cú pháp sử dụng ưu tiên toán tử. Một trong những điều khác biệt về Delphi là nó là một ngôn ngữ truyền một lần, vì vậy các cấu trúc cần phải được khai báo trước khi được sử dụng, vì vậy không cần vượt qua cấp cao nhất để đưa thông tin đó ra ngoài.

Sự kết hợp của các tính năng này có nghĩa là trình phân tích cú pháp có gần như tất cả thông tin cần thiết cho thông tin chi tiết về mã cho bất kỳ điểm nào cần thiết. Cách thức hoạt động là như sau: IDE thông báo cho trình biên dịch biết vị trí của con trỏ (điểm mà mã thông tin chi tiết được mong muốn) và lexer biến nó thành một mã thông báo đặc biệt (nó được gọi là mã thông báo kibitz). Bất cứ khi nào trình phân tích cú pháp gặp mã thông báo này (có thể ở bất kỳ đâu), nó biết rằng đây là tín hiệu để gửi lại tất cả thông tin mà nó có trở lại trình chỉnh sửa. Nó thực hiện điều này bằng cách sử dụng longjmp vì nó được viết bằng C; những gì nó làm là nó thông báo cho người gọi cuối cùng về loại cấu trúc cú pháp (tức là ngữ cảnh) mà điểm kibitz được tìm thấy, cũng như tất cả các bảng biểu tượng cần thiết cho điểm đó. Ví dụ, nếu ngữ cảnh nằm trong một biểu thức là đối số của một phương thức, thì chúng ta có thể kiểm tra quá tải phương thức, xem xét các loại đối số và lọc các ký hiệu hợp lệ để chỉ những ký hiệu có thể giải quyết cho loại đối số đó (điều này cắt giảm trong nhiều mấu chốt không liên quan trong trình đơn thả xuống). Nếu nó nằm trong ngữ cảnh phạm vi lồng nhau (ví dụ: sau dấu "."), Trình phân tích cú pháp sẽ đưa lại một tham chiếu đến phạm vi và IDE có thể liệt kê tất cả các ký hiệu được tìm thấy trong phạm vi đó.

Những việc khác cũng được thực hiện; ví dụ: các phần thân của phương thức bị bỏ qua nếu mã thông báo kibitz không nằm trong phạm vi của chúng - điều này được thực hiện một cách lạc quan và được khôi phục nếu nó bỏ qua mã thông báo. Tương đương với các phương thức mở rộng - trình trợ giúp lớp trong Delphi - có một loại bộ nhớ cache được phiên bản hóa, vì vậy việc tra cứu của chúng khá nhanh. Nhưng kiểu suy luận chung của Delphi yếu hơn nhiều so với của C #.

Bây giờ, đối với câu hỏi cụ thể: suy ra loại biến được khai báo vartương đương với cách Pascal suy ra loại hằng số. Nó xuất phát từ kiểu của biểu thức khởi tạo. Các loại này được xây dựng từ dưới lên. Nếu xthuộc loại Integer, và ythuộc loại Double, thì x + ysẽ thuộc loại Double, bởi vì đó là các quy tắc của ngôn ngữ; v.v ... Bạn tuân theo các quy tắc này cho đến khi bạn có một kiểu cho biểu thức đầy đủ ở phía bên tay phải và đó là kiểu bạn sử dụng cho biểu tượng ở bên trái.


7

Nếu bạn không muốn phải viết trình phân tích cú pháp của riêng mình để xây dựng cây cú pháp trừu tượng, bạn có thể sử dụng trình phân tích cú pháp từ SharpDevelop hoặc MonoDevelop , cả hai đều là mã nguồn mở.


4

Các hệ thống Intellisense thường biểu diễn mã bằng cách sử dụng Cây cú pháp trừu tượng, cho phép chúng giải quyết kiểu trả về của hàm được gán cho biến 'var' theo cách giống như cách trình biên dịch làm. Nếu bạn sử dụng VS Intellisense, bạn có thể nhận thấy rằng nó sẽ không cung cấp cho bạn loại var cho đến khi bạn nhập xong biểu thức gán hợp lệ (có thể giải quyết). Nếu biểu thức vẫn còn mơ hồ (ví dụ: nó không thể suy ra đầy đủ các đối số chung cho biểu thức), kiểu var sẽ không giải quyết được. Đây có thể là một quá trình khá phức tạp, vì bạn có thể cần phải đi khá sâu vào một cái cây để giải quyết loại. Ví dụ:

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

Kiểu trả về là IEnumerable<Bar>, nhưng giải quyết điều này bắt buộc phải biết:

  1. myList thuộc loại triển khai IEnumerable.
  2. Có một phương pháp mở rộng OfType<T>áp dụng cho IEnumerable.
  3. Giá trị kết quả là IEnumerable<Foo>và có một phương pháp mở rộng Selectáp dụng cho điều này.
  4. Biểu thức lambda foo => foo.Barcó tham số foo kiểu Foo. Điều này được suy ra bởi việc sử dụng Chọn, lấy một Func<TIn,TOut>và vì TIn được biết đến (Foo), loại foo có thể được suy ra.
  5. Loại Foo có Thanh thuộc tính, thuộc loại Thanh. Chúng ta biết rằng các trả về Chọn IEnumerable<TOut>và TOut có thể được suy ra từ kết quả của biểu thức lambda, do đó, kiểu kết quả của các mục phải là IEnumerable<Bar>.

Đúng vậy, nó có thể khá sâu. Tôi thấy thoải mái với việc giải quyết tất cả các phụ thuộc. Chỉ nghĩ về điều này, tùy chọn đầu tiên mà tôi đã mô tả - biên dịch và gọi - hoàn toàn không được chấp nhận, bởi vì việc gọi mã có thể có tác dụng phụ, như cập nhật cơ sở dữ liệu và đó không phải là điều mà một trình soạn thảo phải làm. Biên dịch là ok, viện dẫn thì không. Về việc xây dựng AST, tôi không nghĩ mình muốn làm điều đó. Thực sự tôi muốn trì hoãn công việc đó cho trình biên dịch, trình biên dịch đã biết cách thực hiện. Tôi muốn có thể yêu cầu trình biên dịch cho tôi biết những gì tôi muốn biết. Tôi chỉ muốn một câu trả lời đơn giản.
Cheeso

Thách thức với việc kiểm tra nó từ quá trình biên dịch là các phần phụ thuộc có thể sâu tùy ý, có nghĩa là bạn có thể cần phải xây dựng mọi thứ để trình biên dịch tạo mã. Nếu bạn làm điều đó, tôi nghĩ bạn có thể sử dụng các ký hiệu trình gỡ lỗi với IL được tạo và khớp loại của từng cục bộ với ký hiệu của nó.
Dan Bryant

1
@Cheeso: trình biên dịch không cung cấp loại phân tích kiểu đó như một dịch vụ. Tôi hy vọng rằng trong tương lai nó sẽ nhưng không có hứa hẹn.
Eric Lippert

vâng, tôi nghĩ đó có thể là cách để đi - giải quyết tất cả các phụ thuộc và sau đó biên dịch và kiểm tra IL. @Eric, thật tốt khi biết. Hiện tại, nếu tôi không muốn thực hiện phân tích AST hoàn chỉnh, vì vậy tôi phải dùng đến một bản hack bẩn để sản xuất dịch vụ này bằng các công cụ hiện có. Ví dụ: biên dịch một đoạn mã được xây dựng thông minh và sau đó sử dụng ILDASM (hoặc tương tự) theo chương trình để nhận được câu trả lời mà tôi tìm kiếm.
Cheeso

4

Vì bạn đang nhắm mục tiêu Emacs, tốt nhất nên bắt đầu với bộ CEDET. Tất cả các chi tiết mà Eric Lippert đã đề cập trong trình phân tích mã trong công cụ CEDET / Semantic cho C ++. Ngoài ra còn có một trình phân tích cú pháp C # (có lẽ cần một chút TLC) vì vậy các phần duy nhất bị thiếu liên quan đến việc điều chỉnh các phần cần thiết cho C #.

Các hành vi cơ bản được định nghĩa trong các thuật toán cốt lõi phụ thuộc vào các hàm có thể nạp chồng được định nghĩa trên cơ sở mỗi ngôn ngữ. Sự thành công của động cơ hoàn thành phụ thuộc vào mức độ điều chỉnh đã được thực hiện. Với c ++ như một hướng dẫn, việc nhận được hỗ trợ tương tự như C ++ sẽ không quá tệ.

Câu trả lời của Daniel đề xuất sử dụng MonoDevelop để phân tích và phân tích cú pháp. Đây có thể là một cơ chế thay thế thay cho trình phân tích cú pháp C # hiện có hoặc nó có thể được sử dụng để tăng cường trình phân tích cú pháp hiện có.


Đúng vậy, tôi biết về CEDET, và tôi đang sử dụng hỗ trợ C # trong thư mục đóng góp cho ngữ nghĩa. Ngữ nghĩa cung cấp danh sách các biến cục bộ và kiểu của chúng. Một công cụ hoàn thành có thể quét danh sách đó và đưa ra các lựa chọn phù hợp cho người dùng. Vấn đề là biến khi nào var. Ngữ nghĩa xác định chính xác nó là var, nhưng không cung cấp suy luận kiểu. Câu hỏi của tôi đặc biệt xoay quanh cách giải quyết vấn đề đó . Tôi cũng đã xem xét việc hoàn thành CEDET hiện có, nhưng tôi không thể tìm ra cách. Tài liệu cho CEDET là ... à ... chưa đầy đủ.
Cheeso

Bình luận bên lề - CEDET có tham vọng đáng ngưỡng mộ, nhưng tôi thấy khó sử dụng và mở rộng. Hiện tại, trình phân tích cú pháp xử lý "không gian tên" như một chỉ báo lớp trong C #. Tôi thậm chí không thể tìm ra cách thêm "không gian tên" như một phần tử cú pháp riêng biệt. Làm như vậy đã ngăn chặn tất cả các phân tích cú pháp khác và tôi không thể tìm ra lý do tại sao. Trước đây tôi đã giải thích khó khăn mà tôi gặp phải với khung hoàn thành. Ngoài những vấn đề này, có các đường nối và chồng chéo giữa các mảnh. Như một ví dụ, điều hướng là một phần của cả ngữ nghĩa và thượng nghị sĩ. CEDET có vẻ hấp dẫn, nhưng cuối cùng ... nó quá khó sử dụng để cam kết.
Cheeso

Cheeso, nếu bạn muốn tận dụng tối đa các phần ít tài liệu hơn của CEDET, tốt nhất bạn nên thử danh sách gửi thư. Các câu hỏi rất dễ đi sâu vào các lĩnh vực chưa được phát triển tốt, vì vậy cần một vài lần lặp lại để tìm ra các giải pháp tốt hoặc để giải thích các giải pháp hiện có. Đối với C # nói riêng, vì tôi không biết gì về nó, nên sẽ không có câu trả lời đơn giản nào.
Eric

2

Đó là một vấn đề khó để làm tốt. Về cơ bản, bạn cần phải mô hình hóa đặc tả / trình biên dịch ngôn ngữ thông qua hầu hết các thao tác lexing / parsing / typechecking và xây dựng một mô hình nội bộ của mã nguồn mà sau đó bạn có thể truy vấn. Eric mô tả nó chi tiết cho C #. Bạn luôn có thể tải xuống mã nguồn trình biên dịch F # (một phần của F # CTP) và xem quaservice.fsi để xem giao diện được hiển thị ra khỏi trình biên dịch F # mà dịch vụ ngôn ngữ F # sử dụng để cung cấp intellisense, chú giải công cụ cho các loại suy luận, v.v. cảm giác về một 'giao diện' khả thi nếu bạn đã có sẵn trình biên dịch dưới dạng API để gọi vào.

Cách khác là sử dụng lại các trình biên dịch như bạn đang mô tả, sau đó sử dụng phản chiếu hoặc xem mã đã tạo. Điều này có vấn đề theo quan điểm là bạn cần 'chương trình đầy đủ' để nhận đầu ra biên dịch từ trình biên dịch, trong khi khi chỉnh sửa mã nguồn trong trình chỉnh sửa, bạn thường chỉ có 'chương trình một phần' chưa phân tích cú pháp, không đã triển khai tất cả các phương pháp chưa, v.v.

Tóm lại, tôi nghĩ phiên bản 'kinh phí thấp' rất khó làm tốt, còn phiên bản 'thực' thì rất rất khó làm tốt. (Trong đó 'khó' ở đây đo lường cả 'nỗ lực' và 'khó khăn kỹ thuật'.)


Ya, phiên bản "ngân sách thấp" có một số hạn chế rõ ràng. Tôi đang cố gắng quyết định xem thế nào là "đủ tốt" và liệu tôi có thể đáp ứng được thanh đó hay không. Theo kinh nghiệm của riêng tôi về dogfood những gì tôi đã có cho đến nay, nó làm cho việc viết C # trong emacs đẹp hơn nhiều.
Cheeso


0

Đối với giải pháp "1", bạn có một cơ sở mới trong .NET 4 để thực hiện việc này một cách nhanh chóng và dễ dàng. Vì vậy, nếu bạn có thể chuyển đổi chương trình của mình sang .NET 4 thì đó sẽ là lựa chọn tốt nhất của bạn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.