ฉันจะกำหนดประเภทของตัวแปรที่ประกาศโดยใช้ var ณ เวลาออกแบบได้อย่างน่าเชื่อถือได้อย่างไร


109

ฉันกำลังดำเนินการสิ่งอำนวยความสะดวก (Intellisense) สำหรับ C # ใน emacs

แนวคิดคือถ้าผู้ใช้พิมพ์แฟรกเมนต์แล้วขอให้ดำเนินการเสร็จสิ้นโดยใช้การกดแป้นพิมพ์ร่วมกันสิ่งอำนวยความสะดวกในการทำให้เสร็จสมบูรณ์จะใช้การสะท้อน. NET เพื่อพิจารณาความสำเร็จที่เป็นไปได้

การทำเช่นนี้ต้องการให้ทราบประเภทของสิ่งที่จะเสร็จสมบูรณ์ หากเป็นสตริงจะมีชุดวิธีการและคุณสมบัติที่เป็นไปได้ ถ้าเป็น Int32 จะมีชุดแยกต่างหากและอื่น ๆ

การใช้ความหมายแพคเกจรหัส lexer / parser ที่มีอยู่ใน emacs ฉันสามารถค้นหาการประกาศตัวแปรและประเภทของตัวแปรได้ ด้วยเหตุนี้จึงเป็นเรื่องง่ายที่จะใช้การสะท้อนเพื่อรับวิธีการและคุณสมบัติของประเภทจากนั้นจึงนำเสนอรายการตัวเลือกให้กับผู้ใช้ (โอเคไม่ตรงไปตรงมาที่จะทำภายใน emacs แต่การใช้ความสามารถในการเรียกใช้กระบวนการ powershell ภายใน emacsมันจะง่ายกว่ามากฉันเขียนแอสเซมบลี. NET ที่กำหนดเองเพื่อทำการสะท้อนโหลดลงใน powershell จากนั้น elisp ทำงานภายใน emacs สามารถส่งคำสั่งไปยัง powershell และอ่านการตอบกลับผ่าน comint ดังนั้น emacs จึงได้ผลลัพธ์ของการสะท้อนกลับอย่างรวดเร็ว)

ปัญหามาถึงเมื่อรหัสใช้varในการประกาศสิ่งที่เสร็จสมบูรณ์ นั่นหมายความว่าไม่มีการระบุประเภทอย่างชัดเจนและการกรอกข้อมูลจะไม่ทำงาน

ฉันจะกำหนดประเภทจริงที่ใช้อย่างน่าเชื่อถือได้อย่างไรเมื่อประกาศตัวแปรด้วยvarคีย์เวิร์ด เพื่อความชัดเจนฉันไม่จำเป็นต้องกำหนดตอนรันไทม์ ฉันต้องการตรวจสอบที่ "เวลาออกแบบ"

จนถึงตอนนี้ฉันมีแนวคิดเหล่านี้:

  1. รวบรวมและเรียกใช้:
    • แยกคำสั่งประกาศเช่น `var foo =" a string value ";"
    • เชื่อมต่อคำสั่ง `foo.GetType ();"
    • รวบรวมชิ้นส่วน C # ที่เป็นผลลัพธ์แบบไดนามิกลงในแอสเซมบลีใหม่
    • โหลดแอสเซมบลีลงใน AppDomain ใหม่เรียกใช้ framgment และรับประเภทการส่งคืน
    • ยกเลิกการโหลดและทิ้งชุดประกอบ

    ฉันรู้วิธีทำทั้งหมดนี้ แต่ฟังดูมีน้ำหนักมากสำหรับการร้องขอการกรอกข้อมูลแต่ละครั้งในโปรแกรมแก้ไข

    ฉันคิดว่าฉันไม่ต้องการ AppDomain ใหม่ทุกครั้ง ฉันสามารถใช้ AppDomain เดียวซ้ำสำหรับแอสเซมบลีชั่วคราวหลาย ๆ แอสเซมบลีและตัดทอนค่าใช้จ่ายในการตั้งค่าและฉีกมันลงในคำขอที่ทำให้เสร็จสมบูรณ์หลายรายการ นั่นเป็นการปรับเปลี่ยนแนวคิดพื้นฐานมากกว่า

  2. รวบรวมและตรวจสอบ IL

    เพียงรวบรวมคำประกาศลงในโมดูลจากนั้นตรวจสอบ IL เพื่อกำหนดประเภทจริงที่คอมไพเลอร์อนุมาน จะเป็นไปได้อย่างไร? ฉันจะใช้อะไรในการตรวจสอบ IL

มีความคิดที่ดีกว่านี้ไหม ความคิดเห็น? ข้อเสนอแนะ?


แก้ไข - การคิดถึงสิ่งนี้ต่อไปการคอมไพล์และเรียกใช้ไม่เป็นที่ยอมรับเนื่องจากการเรียกใช้อาจมีผลข้างเคียง ดังนั้นตัวเลือกแรกจะต้องถูกตัดออก

นอกจากนี้ฉันคิดว่าฉันไม่สามารถสันนิษฐานได้ว่ามี. NET 4.0


อัปเดต - คำตอบที่ถูกต้องซึ่งไม่ได้กล่าวถึงข้างต้น แต่ Eric Lippert ชี้ให้เห็นอย่างนุ่มนวลคือการใช้ระบบการอนุมานประเภทความเที่ยงตรงเต็มรูปแบบ เป็นวิธีเดียวที่จะกำหนดประเภทของตัวแปรในเวลาออกแบบได้อย่างน่าเชื่อถือ แต่ก็ไม่ใช่เรื่องง่ายที่จะทำ เนื่องจากฉันไม่ต้องทนทุกข์ทรมานจากภาพลวงตาที่ฉันต้องการพยายามสร้างสิ่งนี้ฉันจึงใช้ทางลัดของตัวเลือกที่ 2 - แยกรหัสการประกาศที่เกี่ยวข้องและรวบรวมจากนั้นตรวจสอบ IL ที่เป็นผลลัพธ์

สิ่งนี้ใช้งานได้จริงสำหรับส่วนย่อยที่เป็นธรรมของสถานการณ์ที่เสร็จสมบูรณ์

ตัวอย่างเช่นสมมติว่าในส่วนของรหัสต่อไปนี้? คือตำแหน่งที่ผู้ใช้ขอให้เสร็จสิ้น ใช้งานได้:

var x = "hello there"; 
x.?

การทำให้สมบูรณ์ตระหนักว่า x เป็นสตริงและมีตัวเลือกที่เหมาะสม ทำได้โดยการสร้างและรวบรวมซอร์สโค้ดต่อไปนี้:

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

... จากนั้นตรวจสอบ IL ด้วยการสะท้อนแสงอย่างง่าย

สิ่งนี้ยังใช้งานได้:

var x = new XmlDocument();
x.? 

เอ็นจิ้นจะเพิ่มส่วนคำสั่งโดยใช้ที่เหมาะสมให้กับซอร์สโค้ดที่สร้างขึ้นเพื่อให้คอมไพล์ถูกต้องจากนั้นการตรวจสอบ IL จะเหมือนกัน

ใช้งานได้เช่นกัน:

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

นั่นหมายความว่าการตรวจสอบ IL ต้องหาประเภทของตัวแปรท้องถิ่นตัวที่สามแทนที่จะเป็นตัวแรก

และนี่:

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

... ซึ่งเป็นเพียงระดับหนึ่งที่ลึกกว่าตัวอย่างก่อนหน้านี้

แต่สิ่งที่ใช้ไม่ได้คือการทำให้ตัวแปรโลคัลเสร็จสิ้นซึ่งการเริ่มต้นขึ้นอยู่กับจุดใด ๆ บนสมาชิกอินสแตนซ์หรืออาร์กิวเมนต์เมธอดเฉพาะที่ ชอบ:

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

หรือไวยากรณ์ LINQ

ฉันจะต้องคิดว่าสิ่งเหล่านั้นมีค่าเพียงใดก่อนที่ฉันจะพิจารณาจัดการกับสิ่งที่แน่นอนคือ "การออกแบบที่ จำกัด " (คำที่สุภาพสำหรับการแฮ็ก) เพื่อให้เสร็จสมบูรณ์

แนวทางในการแก้ไขปัญหาด้วยการพึ่งพาอาร์กิวเมนต์ของวิธีการหรือวิธีการอินสแตนซ์จะถูกแทนที่ในส่วนของโค้ดที่สร้างรวบรวมและวิเคราะห์จาก IL การอ้างอิงถึงสิ่งเหล่านั้นด้วยตัวแปรท้องถิ่น "สังเคราะห์" ที่เป็นประเภทเดียวกัน


การอัปเดตอื่น - การเสร็จสิ้นบน vars ที่ขึ้นอยู่กับสมาชิกอินสแตนซ์ตอนนี้ใช้งานได้

สิ่งที่ฉันทำคือซักถามประเภท (ผ่านทางความหมาย) จากนั้นสร้างสมาชิกสแตนด์อินสังเคราะห์สำหรับสมาชิกที่มีอยู่ทั้งหมด สำหรับบัฟเฟอร์ C # เช่นนี้:

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

... โค้ดที่สร้างขึ้นซึ่งได้รับการคอมไพล์เพื่อให้ฉันสามารถเรียนรู้จากเอาต์พุต IL ประเภทของ var nnn ในเครื่องมีลักษณะดังนี้:

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);
      }
  }
}

สมาชิกอินสแตนซ์และประเภทคงที่ทั้งหมดมีอยู่ในรหัสโครงกระดูก มันรวบรวมสำเร็จ ณ จุดนั้นการกำหนดประเภทของตัวแปรท้องถิ่นนั้นตรงไปตรงมาผ่านการสะท้อนกลับ

สิ่งที่ทำให้เป็นไปได้คือ:

  • ความสามารถในการเรียกใช้ powershell ใน emacs
  • คอมไพเลอร์ C # นั้นเร็วมาก ในเครื่องของฉันใช้เวลาประมาณ 0.5 วินาทีในการรวบรวมชุดประกอบในหน่วยความจำ ไม่เร็วพอสำหรับการวิเคราะห์ระหว่างการกดแป้นพิมพ์ แต่เร็วพอที่จะรองรับการสร้างรายการที่เสร็จสมบูรณ์ตามความต้องการ

ฉันยังไม่ได้ดู LINQ
นั่นจะเป็นปัญหาที่ใหญ่กว่ามากเนื่องจาก emacs / parser ความหมายมีไว้สำหรับ C # ไม่ "ทำ" LINQ


4
ประเภทของ foo ถูกคำนวณและกรอกโดยคอมไพเลอร์ผ่านการอนุมานประเภท ฉันสงสัยว่ากลไกต่างกันอย่างสิ้นเชิง บางทีเอ็นจิ้นการอนุมานประเภทมีตะขอ? อย่างน้อยที่สุดฉันจะใช้ 'type-inference' เป็นแท็ก
George Mauer

3
เทคนิคของคุณในการสร้างแบบจำลองวัตถุ "ปลอม" ที่มีทุกประเภท แต่ไม่มีความหมายของวัตถุจริงใดที่ดี นั่นคือวิธีที่ฉันทำ IntelliSense สำหรับ JScript ใน Visual InterDev ในวันนั้น เราสร้างโมเดลอ็อบเจ็กต์ IE เวอร์ชัน "ปลอม" ที่มีวิธีการและประเภททั้งหมด แต่ไม่มีผลข้างเคียงใด ๆ จากนั้นเรียกใช้ล่ามเล็กน้อยบนโค้ดที่แยกวิเคราะห์ในเวลาคอมไพล์และดูว่าประเภทใดกลับมา
Eric Lippert

คำตอบ:


202

ฉันสามารถอธิบายให้คุณฟังว่าเราทำอย่างมีประสิทธิภาพได้อย่างไรใน C # IDE "ของจริง"

สิ่งแรกที่เราทำคือเรียกใช้ pass ซึ่งวิเคราะห์เฉพาะเนื้อหา "ระดับบนสุด" ในซอร์สโค้ด เราข้ามเนื้อหาวิธีการทั้งหมด ที่ช่วยให้เราสร้างฐานข้อมูลได้อย่างรวดเร็วเกี่ยวกับเนมสเปซประเภทและวิธีการ (และตัวสร้าง ฯลฯ ) ที่อยู่ในซอร์สโค้ดของโปรแกรม การวิเคราะห์โค้ดทุกบรรทัดในทุกส่วนของวิธีการอาจใช้เวลานานเกินไปหากคุณพยายามทำระหว่างการกดแป้นพิมพ์

เมื่อ IDE จำเป็นต้องระบุประเภทของนิพจน์เฉพาะภายในเมธอด body ให้บอกว่าคุณได้พิมพ์ "foo" และเราต้องหาว่าสมาชิกของ foo คืออะไร - เราทำสิ่งเดียวกัน เราข้ามงานไปให้มากที่สุดเท่าที่จะทำได้

เราเริ่มต้นด้วยการส่งผ่านซึ่งวิเคราะห์เฉพาะการประกาศตัวแปรท้องถิ่นภายในวิธีการนั้น เมื่อเราเรียกใช้พาสนั้นเราจะทำการแมปจากคู่ของ "ขอบเขต" และ "ชื่อ" ไปยัง "ตัวกำหนดประเภท" "ตัวกำหนดประเภท" เป็นวัตถุที่แสดงถึงแนวคิดของ "ฉันสามารถหาประเภทของท้องถิ่นนี้ได้ถ้าฉันต้องการ" การทำงานในประเภทท้องถิ่นอาจมีราคาแพงดังนั้นเราจึงต้องการเลื่อนการทำงานนั้นออกไปหากจำเป็น

ขณะนี้เรามีฐานข้อมูลที่สร้างขึ้นอย่างเฉื่อยชาซึ่งสามารถบอกประเภทของทุกท้องถิ่นได้ ดังนั้นกลับไปที่ "foo" นั้น - เราคิดว่าข้อความใดที่นิพจน์ที่เกี่ยวข้องอยู่ในนั้นจากนั้นเรียกใช้ตัววิเคราะห์ความหมายเทียบกับคำสั่งนั้น ตัวอย่างเช่นสมมติว่าคุณมีเนื้อหาวิธีการ:

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

และตอนนี้เราต้องหาว่า foo เป็นประเภท char เราสร้างฐานข้อมูลที่มีข้อมูลเมตาวิธีการส่วนขยายประเภทซอร์สโค้ดและอื่น ๆ ทั้งหมด เราสร้างฐานข้อมูลที่มีตัวกำหนดประเภทสำหรับ x, y และ z เราวิเคราะห์ข้อความที่มีนิพจน์ที่น่าสนใจ เราเริ่มต้นด้วยการเปลี่ยนรูปแบบทางวากยสัมพันธ์เป็น

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

ในการหาประเภทของ foo เราต้องรู้จักประเภทของ y ก่อน เมื่อถึงจุดนี้เราจึงถามตัวกำหนดประเภท "ประเภทของ y" คืออะไร? จากนั้นจะเริ่มตัวประเมินนิพจน์ซึ่งแยกวิเคราะห์ x ToCharArray () และถามว่า "x ประเภทใด" เรามีตัวกำหนดประเภทสำหรับสิ่งที่ระบุว่า "ฉันต้องการค้นหา" สตริง "ในบริบทปัจจุบัน" ไม่มีประเภท String ในประเภทปัจจุบันดังนั้นเราจึงดูในเนมสเปซ ไม่มีเช่นกันดังนั้นเราจึงดูคำสั่งการใช้และพบว่ามี "การใช้ระบบ" และระบบนั้นมีสตริงประเภทหนึ่ง ตกลงนั่นคือประเภทของ x

จากนั้นเราจะค้นหาข้อมูลเมตาของ System.String สำหรับประเภทของ ToCharArray และแจ้งว่าเป็น System.Char [] ซุปเปอร์. เราจึงมีประเภทสำหรับ y

ตอนนี้เราถามว่า System.Char [] มีวิธีการที่ไหน? ไม่ดังนั้นเราจึงดูคำสั่งการใช้งาน เราได้ทำการคำนวณฐานข้อมูลไว้ล่วงหน้าซึ่งมีข้อมูลเมตาทั้งหมดสำหรับวิธีการขยายที่สามารถใช้งานได้

ตอนนี้เราพูดว่า "ตกลงมีวิธีการขยายสิบแปดชื่อ Where in scope มีพารามิเตอร์ที่เป็นทางการแรกที่ประเภทเข้ากันได้กับ System.Char [] หรือไม่" ดังนั้นเราจึงเริ่มรอบการทดสอบการเปิดประทุน อย่างไรก็ตามวิธีการขยาย Where เป็นวิธีการทั่วไปซึ่งหมายความว่าเราต้องทำการอนุมานประเภท

ฉันได้เขียนเอ็นจินการอนุมานประเภทพิเศษที่สามารถจัดการการอนุมานที่ไม่สมบูรณ์ตั้งแต่อาร์กิวเมนต์แรกไปจนถึงวิธีการขยาย เราเรียกใช้การอนุมานประเภทและค้นพบว่ามีวิธี Where ที่ใช้IEnumerable<T>และเราสามารถอนุมานจาก System.Char [] ถึงIEnumerable<System.Char>ดังนั้น T คือ System.Char

ลายเซ็นของวิธีนี้คือWhere<T>(this IEnumerable<T> items, Func<T, bool> predicate)และเรารู้ว่า T คือ System.Char นอกจากนี้เรายังรู้ว่าอาร์กิวเมนต์แรกในวงเล็บของวิธีการขยายคือแลมบ์ดา ดังนั้นเราจึงเริ่มอนุมานประเภทนิพจน์แลมบ์ดาที่ระบุว่า "พารามิเตอร์ที่เป็นทางการ foo ถือว่าเป็น System.Char" ใช้ข้อเท็จจริงนี้เมื่อวิเคราะห์แลมด้าที่เหลือ

ตอนนี้เรามีข้อมูลทั้งหมดที่ต้องใช้ในการวิเคราะห์ร่างกายของแลมด้าซึ่งก็คือ "foo" เราค้นหาประเภทของ foo เราพบว่าตามตัวประสานแลมบ์ดามันคือ System.Char และเราทำเสร็จแล้ว เราแสดงข้อมูลประเภทสำหรับ System.Char

และเราจะทำทุกอย่างยกเว้น "ระดับบน" การวิเคราะห์ระหว่างการกดแป้นพิมพ์ นั่นเป็นเรื่องที่ยุ่งยากจริงๆ การเขียนบทวิเคราะห์ทั้งหมดไม่ใช่เรื่องยาก มันทำให้เร็วพอที่คุณจะทำได้ด้วยความเร็วในการพิมพ์ซึ่งเป็นบิตที่ยุ่งยากจริงๆ

โชคดี!


8
Eric ขอบคุณสำหรับการตอบกลับที่สมบูรณ์ คุณได้เปิดตาของฉันไม่น้อย สำหรับ emac ฉันไม่ได้ต้องการที่จะสร้างเอ็นจิ้นระหว่างการกดแป้นพิมพ์แบบไดนามิกที่จะแข่งขันกับ Visual Studio ในแง่ของคุณภาพของประสบการณ์ผู้ใช้ ประการหนึ่งเนื่องจากเวลาแฝง ~ 0.5s ที่มีอยู่ในการออกแบบของฉันสิ่งอำนวยความสะดวกที่ใช้ emacs จึงยังคงอยู่ตามความต้องการเท่านั้น ไม่มีคำแนะนำประเภทล่วงหน้า สำหรับอีกคนหนึ่ง - ฉันจะใช้การสนับสนุนขั้นพื้นฐานของคนในท้องถิ่นที่หลากหลาย แต่ฉันจะถ่ออย่างมีความสุขเมื่อสิ่งต่างๆมีขนดกหรือเมื่อกราฟการอ้างอิงเกินขีด จำกัด ที่กำหนด ยังไม่แน่ใจว่าขีด จำกัด นั้นคืออะไร ขอบคุณอีกครั้ง.
Cheeso

13
มันทำให้ฉันเข้าใจจริงๆว่าทั้งหมดนี้สามารถทำงานได้อย่างรวดเร็วและน่าเชื่อถือโดยเฉพาะอย่างยิ่งกับนิพจน์แลมบ์ดาและการอนุมานประเภททั่วไป ฉันรู้สึกประหลาดใจมากในครั้งแรกที่ฉันเขียนนิพจน์แลมบ์ดาและ Intellisense รู้ประเภทของพารามิเตอร์ของฉันเมื่อฉันกดแม้ว่าข้อความจะยังไม่สมบูรณ์และฉันไม่เคยระบุพารามิเตอร์ทั่วไปของวิธีการขยายอย่างชัดเจน ขอบคุณสำหรับความมหัศจรรย์เล็กน้อยนี้
Dan Bryant

21
@ แดน: ฉันเคยเห็น (หรือเขียน) ซอร์สโค้ดและมันทำให้ฉันคิดว่ามันใช้งานได้ดี :-) มีของมีขนอยู่ในนั้น
Eric Lippert

11
พวก Eclipse น่าจะทำได้ดีกว่าเพราะมันเจ๋งกว่าคอมไพเลอร์ C # และทีม IDE
Eric Lippert

23
ฉันจำไม่ได้ว่าแสดงความคิดเห็นโง่ ๆ นี้เลย มันไม่สมเหตุสมผลเลยด้วยซ้ำ ฉันคงเมา ขออภัย.
Tomas Andrle

15

ฉันสามารถบอกคุณได้คร่าวๆว่า Delphi IDE ทำงานร่วมกับคอมไพเลอร์ Delphi เพื่อทำ intellisense ได้อย่างไร (ความเข้าใจรหัสคือสิ่งที่ Delphi เรียกมัน) ไม่สามารถใช้ได้กับ C # 100% แต่เป็นแนวทางที่น่าสนใจซึ่งสมควรได้รับการพิจารณา

การวิเคราะห์ความหมายส่วนใหญ่ใน Delphi จะทำในตัวแยกวิเคราะห์เอง นิพจน์จะถูกพิมพ์เมื่อมีการแยกวิเคราะห์ยกเว้นสถานการณ์ที่ไม่ง่ายซึ่งในกรณีนี้การแยกวิเคราะห์แบบมองไปข้างหน้าจะใช้เพื่อหาสิ่งที่ตั้งใจไว้จากนั้นจึงใช้การตัดสินใจนั้นในการแยกวิเคราะห์

การแยกวิเคราะห์ส่วนใหญ่เป็นการสืบเชื้อสายแบบเรียกซ้ำ LL (2) ยกเว้นนิพจน์ซึ่งแยกวิเคราะห์โดยใช้ตัวดำเนินการลำดับความสำคัญ สิ่งที่แตกต่างอย่างหนึ่งเกี่ยวกับ Delphi คือเป็นภาษา single-pass ดังนั้นจึงต้องมีการประกาศโครงสร้างก่อนที่จะใช้ดังนั้นจึงไม่จำเป็นต้องใช้ pass ระดับบนสุดเพื่อนำข้อมูลนั้นออกมา

คุณลักษณะที่รวมกันนี้หมายความว่าโปรแกรมแยกวิเคราะห์มีข้อมูลทั้งหมดที่จำเป็นสำหรับการเข้าใจโค้ดในทุกจุดที่จำเป็น วิธีการทำงานคือ IDE แจ้งตำแหน่งของเคอร์เซอร์ของคอมไพเลอร์ให้ทราบ (จุดที่ต้องการข้อมูลเชิงลึกของโค้ด) และตัวเล็กเซอร์จะเปลี่ยนเป็นโทเค็นพิเศษ (เรียกว่าโทเค็น kibitz) เมื่อใดก็ตามที่ตัวแยกวิเคราะห์ตรงกับโทเค็นนี้ (ซึ่งอาจอยู่ที่ใดก็ได้) มันจะรู้ว่านี่คือสัญญาณที่จะส่งข้อมูลทั้งหมดที่มีกลับไปยังตัวแก้ไข ใช้ longjmp เพราะเขียนด้วย C; สิ่งที่ทำคือการแจ้งให้ผู้เรียกทราบถึงชนิดของโครงสร้างวากยสัมพันธ์ (เช่นบริบททางไวยากรณ์) ที่พบจุด kibitz ตลอดจนตารางสัญลักษณ์ทั้งหมดที่จำเป็นสำหรับจุดนั้น ตัวอย่างเช่น หากบริบทอยู่ในนิพจน์ซึ่งเป็นอาร์กิวเมนต์ของเมธอดเราสามารถตรวจสอบเมธอดโอเวอร์โหลดดูประเภทอาร์กิวเมนต์และกรองสัญลักษณ์ที่ถูกต้องให้เหลือเฉพาะที่สามารถแก้ไขประเภทอาร์กิวเมนต์นั้นได้ (ซึ่งจะตัดลงใน a ส่วนที่ไม่เกี่ยวข้องจำนวนมากในเมนูแบบเลื่อนลง) หากอยู่ในบริบทขอบเขตที่ซ้อนกัน (เช่นหลัง ".") ตัวแยกวิเคราะห์จะส่งการอ้างอิงถึงขอบเขตกลับไปและ IDE สามารถระบุสัญลักษณ์ทั้งหมดที่พบในขอบเขตนั้น

อย่างอื่นก็ทำ; ตัวอย่างเช่นเนื้อความของวิธีการจะถูกข้ามไปหากโทเค็น kibitz ไม่อยู่ในช่วงของพวกเขาซึ่งจะทำได้ในแง่ดีและจะย้อนกลับหากข้ามไปที่โทเค็น วิธีการส่วนขยายที่เทียบเท่า - ตัวช่วยคลาสใน Delphi - มีแคชเวอร์ชันหนึ่งดังนั้นการค้นหาจึงรวดเร็วพอสมควร แต่การอนุมานประเภททั่วไปของ Delphi นั้นอ่อนแอกว่า C # มาก

ตอนนี้สำหรับคำถามเฉพาะ: การอนุมานประเภทของตัวแปรที่ประกาศด้วยvarจะเทียบเท่ากับวิธีที่ Pascal อนุมานประเภทของค่าคงที่ มันมาจากประเภทของนิพจน์การเริ่มต้น ประเภทเหล่านี้สร้างขึ้นจากล่างขึ้นบน ถ้าxเป็นประเภทIntegerและyเป็นประเภทDoubleก็x + yจะเป็นประเภทDoubleเนื่องจากเป็นกฎของภาษา ฯลฯ คุณทำตามกฎเหล่านี้จนกว่าคุณจะมีประเภทสำหรับนิพจน์แบบเต็มทางด้านขวามือและนั่นคือประเภทที่คุณใช้สำหรับสัญลักษณ์ทางด้านซ้าย


7

หากคุณไม่ต้องการเขียนตัวแยกวิเคราะห์ของคุณเองเพื่อสร้างโครงสร้างไวยากรณ์แบบนามธรรมคุณสามารถดูการใช้ตัวแยกวิเคราะห์จากSharpDevelopหรือMonoDevelopซึ่งทั้งสองอย่างนี้เป็นโอเพ่นซอร์ส


4

โดยทั่วไประบบ Intellisense จะแสดงโค้ดโดยใช้ Abstract Syntax Tree ซึ่งช่วยให้สามารถแก้ไขประเภทการส่งคืนของฟังก์ชันที่กำหนดให้กับตัวแปร 'var' ได้มากหรือน้อยในลักษณะเดียวกับที่คอมไพเลอร์ต้องการ หากคุณใช้ VS Intellisense คุณอาจสังเกตเห็นว่ามันจะไม่ให้ประเภทของ var จนกว่าคุณจะป้อนนิพจน์การกำหนดที่ถูกต้อง (แก้ไขได้) เสร็จสิ้น หากนิพจน์ยังคงคลุมเครือ (ตัวอย่างเช่นไม่สามารถสรุปอาร์กิวเมนต์ทั่วไปสำหรับนิพจน์ได้ทั้งหมด) ประเภท var จะไม่สามารถแก้ไขได้ นี่อาจเป็นกระบวนการที่ค่อนข้างซับซ้อนเนื่องจากคุณอาจต้องเดินลึกเข้าไปในต้นไม้เพื่อที่จะแก้ไขประเภท ตัวอย่างเช่น:

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

ประเภทการส่งคืนคือIEnumerable<Bar>แต่การแก้ไขสิ่งนี้จำเป็นต้องรู้:

  1. myList IEnumerableเป็นประเภทที่ดำเนินการ
  2. มีวิธีการขยายOfType<T>ที่ใช้กับ IEnumerable
  3. ค่าผลลัพธ์คือIEnumerable<Foo>และมีวิธีการขยายSelectที่ใช้กับสิ่งนี้
  4. นิพจน์แลมบ์ดาfoo => foo.Barมีพารามิเตอร์ foo เป็นประเภท Foo สิ่งนี้อนุมานได้จากการใช้ Select ซึ่งใช้เวลา a Func<TIn,TOut>และเนื่องจาก TIn เป็นที่รู้จัก (Foo) จึงสามารถอนุมานประเภทของ foo ได้
  5. ประเภท Foo มีคุณสมบัติ Bar ซึ่งเป็นประเภท Bar เรารู้ว่าการเลือกผลตอบแทนIEnumerable<TOut>และ Tout IEnumerable<Bar>สามารถอนุมานจากผลของการแสดงออกแลมบ์ดาเพื่อให้ชนิดที่เกิดจากรายการที่จะต้อง

ใช่มันสามารถลึกได้สวย ฉันพอใจกับการแก้ไขการอ้างอิงทั้งหมด แค่คิดถึงสิ่งนี้ตัวเลือกแรกที่ฉันอธิบาย - รวบรวมและเรียกใช้ - ไม่สามารถยอมรับได้อย่างแน่นอนเนื่องจากการเรียกใช้โค้ดอาจมีผลข้างเคียงเช่นการอัปเดตฐานข้อมูลและนั่นไม่ใช่สิ่งที่บรรณาธิการควรทำ การคอมไพล์ก็โอเคไม่ต้องวิงวอน เท่าที่สร้าง AST ฉันไม่คิดว่าจะทำอย่างนั้น จริงๆฉันต้องการเลื่อนงานนั้นไปยังคอมไพเลอร์ซึ่งรู้วิธีการทำงานอยู่แล้ว อยากได้ก็ขอให้คอมไพเลอร์บอกสิ่งที่อยากรู้ ฉันแค่อยากได้คำตอบง่ายๆ
Cheeso

ความท้าทายในการตรวจสอบจากการคอมไพเลอร์คือการอ้างอิงอาจมีความลึกตามอำเภอใจซึ่งหมายความว่าคุณอาจต้องสร้างทุกอย่างเพื่อให้คอมไพเลอร์สร้างโค้ด หากคุณทำเช่นนั้นฉันคิดว่าคุณสามารถใช้สัญลักษณ์ดีบักเกอร์กับ IL ที่สร้างขึ้นและจับคู่ประเภทของแต่ละท้องถิ่นด้วยสัญลักษณ์นั้น
Dan Bryant

1
@Cheeso: คอมไพเลอร์ไม่ได้เสนอการวิเคราะห์ประเภทนั้นเป็นบริการ ฉันหวังว่าในอนาคตจะมี แต่สัญญา
Eric Lippert

ใช่ฉันคิดว่านั่นอาจเป็นหนทางไป - แก้ไขการอ้างอิงทั้งหมดจากนั้นรวบรวมและตรวจสอบ IL @ เอริกน่ารู้. สำหรับตอนนี้หากฉันไม่ต้องการทำการวิเคราะห์ AST อย่างสมบูรณ์ดังนั้นฉันต้องใช้แฮ็คสกปรกเพื่อสร้างบริการนี้โดยใช้เครื่องมือที่มีอยู่ เช่นรวบรวมส่วนย่อยของโค้ดที่สร้างขึ้นอย่างชาญฉลาดจากนั้นใช้ ILDASM (หรือที่คล้ายกัน) โดยใช้โปรแกรมเพื่อรับคำตอบที่ฉันต้องการ
Cheeso

4

เนื่องจากคุณกำหนดเป้าหมาย Emacs จึงควรเริ่มต้นด้วยชุด CEDET รายละเอียดทั้งหมดที่ Eric Lippert กล่าวถึงในตัววิเคราะห์โค้ดในเครื่องมือ CEDET / Semantic สำหรับ C ++ แล้ว นอกจากนี้ยังมีตัวแยกวิเคราะห์ C # (ซึ่งอาจต้องใช้ TLC เล็กน้อย) ดังนั้นส่วนเดียวที่ขาดหายไปจึงเกี่ยวข้องกับการปรับแต่งส่วนที่จำเป็นสำหรับ C #

พฤติกรรมพื้นฐานถูกกำหนดไว้ในอัลกอริทึมหลักซึ่งขึ้นอยู่กับฟังก์ชันที่โอเวอร์โหลดได้ซึ่งกำหนดไว้ตามแต่ละภาษา ความสำเร็จของเครื่องยนต์ที่สมบูรณ์ขึ้นอยู่กับว่าได้ทำการปรับแต่งมากน้อยเพียงใด ด้วย c ++ เป็นแนวทางการรับการสนับสนุนที่คล้ายกับ C ++ ไม่ควรเลวร้ายเกินไป

คำตอบของ Daniel แนะนำให้ใช้ MonoDevelop เพื่อแยกวิเคราะห์และวิเคราะห์ นี่อาจเป็นกลไกอื่นแทนตัวแยกวิเคราะห์ C # ที่มีอยู่หรืออาจใช้เพื่อเพิ่มตัวแยกวิเคราะห์ที่มีอยู่


ใช่ฉันรู้เกี่ยวกับ CEDET และฉันใช้การสนับสนุน C # ในไดเร็กทอรี Contrib สำหรับความหมาย ความหมายแสดงรายการตัวแปรท้องถิ่นและประเภทของตัวแปร เอ็นจิ้นที่สมบูรณ์สามารถสแกนรายการนั้นและเสนอทางเลือกที่เหมาะสมให้กับผู้ใช้ varปัญหาคือเมื่อตัวแปรคือ ความหมายระบุได้อย่างถูกต้องว่าเป็น var แต่ไม่ได้ให้การอนุมานประเภท คำถามของฉันเกี่ยวกับวิธีจัดการกับสิ่งนั้นโดยเฉพาะ ฉันยังตรวจสอบการเชื่อมต่อกับการเสร็จสิ้น CEDET ที่มีอยู่ แต่ฉันคิดไม่ออกว่าจะทำอย่างไร เอกสารสำหรับ CEDET คือ ... อา ... ไม่สมบูรณ์
Cheeso

ความคิดเห็นด้านข้าง - CEDET มีความทะเยอทะยานอย่างน่าชื่นชม แต่ฉันพบว่ามันยากที่จะใช้และต่อยอด ขณะนี้โปรแกรมแยกวิเคราะห์ถือว่า "เนมสเปซ" เป็นตัวบ่งชี้คลาสใน C # ฉันคิดไม่ออกว่าจะเพิ่ม "เนมสเปซ" เป็นองค์ประกอบวากยสัมพันธ์ที่แตกต่างกันอย่างไร การทำเช่นนี้ป้องกันการวิเคราะห์วากยสัมพันธ์อื่น ๆ ทั้งหมดและฉันไม่สามารถหาสาเหตุได้ ก่อนหน้านี้ฉันได้อธิบายถึงความยากลำบากที่มีกับกรอบการทำให้เสร็จสมบูรณ์ นอกเหนือจากปัญหาเหล่านี้ยังมีตะเข็บและการทับซ้อนกันระหว่างชิ้นส่วน ดังตัวอย่างหนึ่งการนำทางเป็นส่วนหนึ่งของทั้งความหมายและวุฒิสมาชิก CEDET ดูน่าหลงใหล แต่ในที่สุด ... มันก็เทอะทะเกินไปที่จะยอมรับ
Cheeso

Cheeso หากคุณต้องการใช้ประโยชน์สูงสุดจากส่วนที่มีเอกสารน้อยของ CEDET ทางออกที่ดีที่สุดของคุณคือลองใช้รายชื่อผู้รับจดหมาย เป็นเรื่องง่ายสำหรับคำถามที่จะเจาะลึกลงไปในส่วนที่ยังไม่ได้รับการพัฒนาที่ดีดังนั้นจึงต้องใช้การทำซ้ำสองสามครั้งเพื่อหาวิธีแก้ปัญหาที่ดีหรืออธิบายสิ่งที่มีอยู่ สำหรับ C # โดยเฉพาะเนื่องจากฉันไม่รู้อะไรเลยจะไม่มีคำตอบง่ายๆ
Eric

2

มันเป็นปัญหายากที่จะทำดี โดยทั่วไปคุณจะต้องสร้างแบบจำลองข้อมูลจำเพาะ / คอมไพเลอร์ภาษาผ่านการตรวจสอบคำศัพท์ / การแยกวิเคราะห์ / การพิมพ์ส่วนใหญ่และสร้างแบบจำลองภายในของซอร์สโค้ดซึ่งคุณสามารถสืบค้นได้ Eric อธิบายรายละเอียดเกี่ยวกับ C # คุณสามารถดาวน์โหลดซอร์สโค้ดคอมไพเลอร์ F # ได้ตลอดเวลา (ส่วนหนึ่งของ F # CTP) และดูที่service.fsiอินเทอร์เฟซที่เปิดเผยจากคอมไพเลอร์ F # ที่บริการภาษา F # ใช้สำหรับการจัดหา Intellisense คำแนะนำเครื่องมือสำหรับประเภทที่อนุมานเป็นต้น ความรู้สึกของ 'อินเทอร์เฟซ' ที่เป็นไปได้หากคุณมีคอมไพเลอร์ที่พร้อมใช้งานเป็น API เพื่อเรียกใช้

อีกเส้นทางหนึ่งคือการใช้คอมไพเลอร์ซ้ำตามที่คุณกำลังอธิบายจากนั้นใช้การสะท้อนกลับหรือดูโค้ดที่สร้างขึ้น นี่เป็นปัญหาจากมุมมองที่คุณต้องการ 'โปรแกรมเต็มรูปแบบ' เพื่อรับเอาต์พุตการรวบรวมจากคอมไพเลอร์ในขณะที่เมื่อแก้ไขซอร์สโค้ดในตัวแก้ไขคุณมักจะมีเพียง 'โปรแกรมบางส่วน' ที่ยังไม่ได้แยกวิเคราะห์ มีการนำวิธีการทั้งหมดไปใช้ยัง ฯลฯ

ในระยะสั้นฉันคิดว่าเวอร์ชัน 'งบประมาณต่ำ' นั้นทำได้ยากมากและเวอร์ชัน 'ของจริง' นั้นยากมากที่จะทำได้ดี (โดยที่ 'ยาก' ในที่นี้จะวัดทั้ง 'ความพยายาม' และ 'ความยากทางเทคนิค')


ใช่รุ่น "งบน้อย" มีข้อ จำกัด ที่ชัดเจน ฉันกำลังพยายามตัดสินใจว่าอะไร "ดีพอ" และฉันจะได้พบกับบาร์นั้นหรือไม่ จากประสบการณ์ของฉันเองการลองใช้สิ่งที่ฉันมีจนถึงตอนนี้มันทำให้การเขียน C # ภายใน emac นั้นดีกว่ามาก
Cheeso


0

สำหรับโซลูชัน "1" คุณมีสิ่งอำนวยความสะดวกใหม่ใน. NET 4 เพื่อดำเนินการนี้ได้อย่างรวดเร็วและง่ายดาย ดังนั้นหากคุณสามารถแปลงโปรแกรมของคุณเป็น. NET 4 ได้ก็จะเป็นทางเลือกที่ดีที่สุดของคุณ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.