ฉันได้ยินมาพักหนึ่งแล้วว่าเคยเป็นคอมไพเลอร์ที่พยายามแก้ไขข้อผิดพลาดทางไวยากรณ์โดยการวิเคราะห์บริบทและสรุปสิ่งที่ตั้งใจไว้
คอมไพเลอร์ดังกล่าวมีอยู่จริงหรือไม่? เห็นได้ชัดว่ามันมีคุณค่าในทางปฏิบัติน้อย แต่จะน่าสนใจมากที่จะเล่นกับและเรียนรู้จาก
ฉันได้ยินมาพักหนึ่งแล้วว่าเคยเป็นคอมไพเลอร์ที่พยายามแก้ไขข้อผิดพลาดทางไวยากรณ์โดยการวิเคราะห์บริบทและสรุปสิ่งที่ตั้งใจไว้
คอมไพเลอร์ดังกล่าวมีอยู่จริงหรือไม่? เห็นได้ชัดว่ามันมีคุณค่าในทางปฏิบัติน้อย แต่จะน่าสนใจมากที่จะเล่นกับและเรียนรู้จาก
คำตอบ:
ในบางแง่มุมการคอมไพล์กำลังอนุมานสิ่งที่ไวยากรณ์บางอย่างตั้งใจจะทำและด้วยเหตุนี้ข้อผิดพลาดทางไวยากรณ์คือเมื่อคอมไพเลอร์ไม่สามารถคิดออก คุณสามารถเพิ่ม "การคาดเดา" เพิ่มเติมเพื่อให้คอมไพเลอร์อนุมานสิ่งต่าง ๆ เพิ่มเติมและมีความยืดหยุ่นกับไวยากรณ์มากขึ้น แต่จะต้องทำเช่นนี้โดยการตั้งกฎเฉพาะ และกฎเหล่านั้นก็กลายเป็นส่วนหนึ่งของภาษาและไม่ใช่ข้อผิดพลาดอีกต่อไป
ดังนั้นไม่ไม่มีคอมไพเลอร์ดังกล่าวจริงๆเพราะคำถามไม่สมเหตุสมผล การคาดเดาว่าข้อผิดพลาดทางไวยากรณ์หมายถึงอะไรที่จะทำตามกฏบางชุดก็กลายเป็นส่วนหนึ่งของไวยากรณ์
ในแง่นั้นมีตัวอย่างที่ดีของคอมไพเลอร์ที่ทำสิ่งนี้: คอมไพเลอร์ C ใด ๆ พวกเขามักจะพิมพ์คำเตือนของสิ่งที่ไม่ควรจะเป็นและจากนั้นสมมติว่าคุณหมายถึง X และดำเนินการต่อไป นี่คือความจริง "การคาดเดา" ของรหัสที่ไม่ชัดเจน (แม้ว่าส่วนใหญ่จะไม่ใช่ไวยากรณ์ต่อ se) สิ่งที่อาจจะหยุดการคอมไพล์ด้วยข้อผิดพลาดและดังนั้นจึงถือว่าเป็นข้อผิดพลาด
ฟังดูอันตรายจริงๆ หากคอมไพเลอร์พยายามที่จะอนุมานความตั้งใจของคุณ infers ผิดแก้ไขรหัสแล้วจะไม่บอกคุณ (หรือบอกคุณในการเตือนว่าคุณเช่นทุกคนไม่สนใจ) แล้วคุณกำลังจะเรียกใช้รหัสที่อาจ ทำเสียหายอย่างจริงจัง
คอมไพเลอร์เช่นนี้อาจเป็นสิ่งที่ไม่ได้สร้างขึ้นโดยเจตนา
IDE สำหรับภาษาการเขียนโปรแกรมโดยปกติแล้วทุกวันนี้มีคอมไพเลอร์ที่ทำงานในพื้นหลังเพื่อให้สามารถให้บริการวิเคราะห์เช่นการระบายสีไวยากรณ์ IntelliSense ข้อผิดพลาดและอื่น ๆ เห็นได้ชัดว่าคอมไพเลอร์ดังกล่าวจะต้องสามารถเข้าใจโค้ดที่เสียอย่างลึกซึ้ง ส่วนใหญ่เวลาแก้ไขรหัสไม่ถูกต้อง แต่เราก็ยังต้องทำความเข้าใจกับมัน
อย่างไรก็ตามโดยปกติคุณสมบัติการกู้คืนข้อผิดพลาดจะถูกใช้ระหว่างการแก้ไขเท่านั้น มันไม่มีเหตุผลที่จะยอมให้มีการคอมไพล์จริงในสถานการณ์ "การฉีด"
ที่น่าสนใจคือเราได้สร้างคุณลักษณะนั้นลงในคอมไพเลอร์ JScript.NET โดยพื้นฐานแล้วมันเป็นไปได้ที่จะนำคอมไพเลอร์เข้าสู่โหมดที่เราอนุญาตให้คอมไพเลอร์ดำเนินการต่อแม้ว่าจะพบข้อผิดพลาดถ้า IDE จะกู้คืนจากมัน คุณสามารถพิมพ์รหัสVisual Basicในเรียกใช้คอมไพเลอร์ JScript.NET กับมันและมีโอกาสที่เหมาะสมของโปรแกรมที่ทำงานออกมาอีกปลาย!
นี่เป็นตัวอย่างที่น่าขบขัน แต่กลับกลายเป็นว่าไม่ใช่คุณลักษณะที่ดีมากสำหรับสถานการณ์ "การฉีด" ด้วยเหตุผลหลายประการ คำอธิบายแบบเต็มจะค่อนข้างยาว คำอธิบายสั้น ๆ คือมันทำให้โปรแกรมที่ทำงานไม่ถูกต้องและโดยบังเอิญและทำให้ยากที่จะเรียกใช้รหัสเดียวกันผ่านคอมไพเลอร์หลายตัวหรือคอมไพเลอร์รุ่นเดียวกันหลายเวอร์ชัน ค่าใช้จ่ายจำนวนมากที่คุณสมบัติเพิ่มไม่ได้เป็นผลมาจากผลประโยชน์เพียงเล็กน้อย
ปีเตอร์ Torr ที่ PM'd กลับคุณลักษณะในวันที่กล่าวถึงมันสั้นในบล็อกโพสต์นี้จาก 2003
แม้ว่าเราจะเปิดเผยคุณสมบัตินี้ผ่านสคริปต์การโฮสต์ API ของเครื่องมือ JScript .NET แต่ฉันไม่ทราบว่ามีลูกค้าจริง ๆ ที่เคยใช้งาน
สิ่งแรกที่อยู่ในใจของฉันคือ Javascript ของอัตโนมัติแทรกลำไส้ใหญ่กึ่ง คุณลักษณะที่น่ากลัวและน่ากลัวซึ่งไม่ควรนำมาใช้กับภาษา
ไม่ได้หมายความว่ามันจะทำงานได้ดีขึ้น หากมองไปข้างหน้าบรรทัดต่อไปนี้ก็อาจจะสามารถคาดเดาได้ดีขึ้นตามความตั้งใจของโปรแกรมเมอร์ แต่ในตอนท้ายของวันหากมีหลายวิธีที่ถูกต้องไวยากรณ์จะได้หายไปแล้วก็ไม่มีตัวแทนจริงๆ สำหรับโปรแกรมเมอร์ที่ชัดเจน
มันฟังฉันว่าถ้าคอมไพเลอร์สามารถแก้ไขไวยากรณ์ที่ไม่ถูกต้องแล้วไวยากรณ์นั้นควรบันทึกไว้ในภาษา
สาเหตุของข้อผิดพลาดทางไวยากรณ์คือเนื่องจาก parser ไม่สามารถสร้างแผนผังไวยากรณ์นามธรรมจากโปรแกรม สิ่งนี้เกิดขึ้นเมื่อโทเค็นไม่อยู่ในสถานที่ เพื่อที่จะเดาว่าโทเค็นนั้นควรอยู่ที่ใดถ้าควรลบโทเค็นหรือหากมีการเพิ่มโทเค็นอื่นเพื่อแก้ไขข้อผิดพลาดคุณจะต้องใช้คอมพิวเตอร์บางประเภทที่สามารถเดาเจตนาของโปรแกรมเมอร์ได้ เครื่องจะเดาได้อย่างไรว่า:
int x = 5 6;
ควรจะเป็น:
int x = 5 + 6;
56
มันอาจจะได้อย่างง่ายดายเพียงใดต่อไปนี้: 5 - 6
, 5 & 6
, ไม่มีวิธีใดที่คอมไพเลอร์จะรู้
เทคโนโลยีนั้นยังไม่มี
แม้ว่าจะไม่ใช่สิ่งเดียวกัน แต่นี่เป็นเหตุผลว่าทำไม HTML จึงกลายเป็นความหายนะ เบราว์เซอร์ยอมรับมาร์กอัปที่ไม่ดีและสิ่งต่อไปที่คุณรู้เบราว์เซอร์ A ไม่สามารถแสดงแบบเดียวกับที่เบราว์เซอร์ B ทำ (ใช่มีเหตุผลอื่น แต่นี่เป็นหนึ่งในไม่กี่อันดับแรกโดยประมาณ 10 ปีที่ผ่านมา )
ในฐานะที่เป็น Eric Lippert infers หลายสิ่งเหล่านี้ได้รับการจัดการที่ดีที่สุดโดย IDE ไม่ใช่คอมไพเลอร์ ให้คุณดูว่าบิตอัตโนมัติกำลังพยายามทำให้คุณล้มเหลว
กลยุทธ์ที่ฉันคิดว่าเด่นตอนนี้คือการปรับแต่งภาษาอย่างต่อเนื่องแทนการคลายตัวแปล: ถ้ามันเป็นสิ่งที่ผู้แปลสามารถคิดออกโดยอัตโนมัติแล้วแนะนำภาษาสร้างที่กำหนดไว้รอบ ๆ
ตัวอย่างทันทีที่นึกถึงคือคุณสมบัติอัตโนมัติใน C # (ไม่ใช่ภาษาเดียวที่มีบางสิ่งที่คล้ายกัน): เนื่องจากส่วนใหญ่ของ getters / setters ในแอปใด ๆ เป็นเพียงแค่ล้อมรอบฟิลด์เพียงอนุญาตให้นักพัฒนาระบุ ความตั้งใจและให้คอมไพเลอร์ฉีดส่วนที่เหลือ
ซึ่งทำให้ฉันคิด: ภาษาสไตล์ C ส่วนใหญ่ทำสิ่งนี้ไปบ้างแล้ว สำหรับสิ่งที่สามารถคำนวณได้โดยอัตโนมัติเพียงปรับแต่งไวยากรณ์:
if (true == x)
{
dothis();
}
else
{
dothat();
}
สามารถลดเป็น:
if (true == x)
dothis();
else
dothat();
ในท้ายที่สุดฉันคิดว่ามันมาจากสิ่งนี้: แนวโน้มคือคุณไม่ได้ทำให้คอมไพเลอร์ "ฉลาดขึ้น" หรือ "คลาย" มันเป็นภาษาที่ทำให้ฉลาดขึ้น
นอกจากนี้ "ความช่วยเหลือ" มากเกินไปอาจเป็นอันตรายเช่นข้อผิดพลาด "คลาสสิค" ถ้า:
if (true == x)
if (true == y)
dothis();
else
dothat();
if (x && y) dothis(); else dothat();
จะดูดีขึ้นเล็กน้อย
true
false
เมื่อฉันเขียนโปรแกรม FORTRAN และ PL / ฉันกลับมาในช่วงปลายยุค 80 และต้นยุค 90 ในระบบ DEC และ IBM minicomputer และเมนเฟรมของ IBM ฉันดูเหมือนจะจำได้ว่าคอมไพเลอร์จะออกจากระบบข้อความเช่น "blah blah error; . " ย้อนกลับไปนี่เป็นมรดกของการประมวลผลแบบกลุ่ม (ก่อนหน้านี้ก่อนหน้าของฉัน) วันที่มีการรอคอยอย่างมากระหว่างการส่งรหัสของคุณเพื่อเรียกใช้และรับผลลัพธ์กลับมา ดังนั้นจึงเป็นเรื่องที่สมเหตุสมผลสำหรับคอมไพเลอร์ที่จะพยายามคาดเดาโปรแกรมเมอร์และดำเนินการต่อไปมากกว่าจะยกเลิกในข้อผิดพลาดแรกที่พบ ในใจคุณฉันจำไม่ได้ว่า "การแก้ไข" มีความซับซ้อนเป็นพิเศษ ในที่สุดเมื่อฉันย้ายไปยังเวิร์กสเตชัน Unix แบบโต้ตอบ (Sun, SGI ฯลฯ )
เป้าหมายของคอมไพเลอร์คือการสร้างไฟล์โปรแกรมที่ทำงานได้ตามที่ต้องการ หากโปรแกรมเมอร์เขียนสิ่งที่ไม่ถูกต้องแม้ว่าคอมไพเลอร์สามารถคาดเดาได้ 90% ว่ามีจุดประสงค์อะไรโดยทั่วไปแล้วดีกว่าถ้าต้องการให้โปรแกรมเมอร์แก้ไขโปรแกรมเพื่อทำให้ความตั้งใจชัดเจนกว่าให้คอมไพเลอร์เดินหน้าและสร้างไฟล์ปฏิบัติการ ซึ่งจะมีโอกาสที่สำคัญในการปกปิดบั๊ก
โดยทั่วไปภาษาควรได้รับการออกแบบเพื่อให้รหัสที่แสดงความตั้งใจอย่างชัดเจนนั้นถูกต้องตามกฎหมายและห้ามมิให้ใช้รหัสที่ไม่ได้แสดงเจตนาอย่างชัดเจน แต่ไม่ได้หมายความว่าเป็นภาษานั้น พิจารณารหัสต่อไปนี้ [Java หรือ C #]
const double oneTenth = 0.1;
const float oneTenthF = 0.1f;
...
float f1 = oneTenth;
double d1 = oneTenthF;
การมีคอมไพเลอร์เพิ่ม typecast โดยปริยายสำหรับการมอบหมายให้f1
เป็นประโยชน์เนื่องจากมีเพียงสิ่งเดียวเท่านั้นที่โปรแกรมเมอร์อาจต้องการf1
เก็บไว้ ( float
ค่าใกล้เคียงกับ 1/10) แทนที่จะส่งเสริมให้คอมไพเลอร์ยอมรับโปรแกรมที่ไม่เหมาะสม แต่จะเป็นการดีกว่าสำหรับข้อมูลจำเพาะที่จะอนุญาตให้มีการแปลงแบบสองต่อหนึ่งโดยปริยายในบริบทบางอย่าง ในทางกลับกันการมอบหมายให้ทำd1
อาจจะใช่หรือไม่ใช่สิ่งที่โปรแกรมเมอร์ตั้งใจจริง แต่ไม่มีกฎภาษาที่ห้ามใช้
ประเภทของกฎทางภาษาที่เลวร้ายที่สุดคือภาษาที่คอมไพเลอร์จะทำการอ้างถึงในกรณีที่บางสิ่งบางอย่างไม่สามารถรวบรวมได้อย่างถูกกฎหมาย แต่โปรแกรมอาจ "ตั้งใจ" ถูกต้องในกรณีที่มีการอนุมาน หลายสถานการณ์ที่เกี่ยวข้องกับการสิ้นสุดของคำสั่งโดยนัยอยู่ในหมวดหมู่นี้ หากโปรแกรมเมอร์ที่ตั้งใจจะเขียนสองคำสั่งแยกกันละเว้นคำสั่ง terminator คอมไพเลอร์อาจจัดการเพื่ออนุมานขอบเขตคำสั่ง แต่บางครั้งอาจถือว่าเป็นคำสั่งหนึ่งสิ่งที่ควรจะประมวลผลเป็นสอง
ข้อผิดพลาดทางไวยากรณ์โดยเฉพาะอย่างยิ่งยากที่จะแก้ไข พิจารณากรณีที่ขาดหายไปทางขวา)
: เรารู้ว่าเราสามารถซ่อมแซมรหัสได้ด้วยการแทรกรหัส แต่มีหลาย ๆ ที่ที่เราสามารถแทรกได้และรับโปรแกรมที่ถูกต้องทางซินแทคติค
จุดที่ง่ายกว่าคือตัวระบุที่สะกดผิด (แต่โปรดทราบว่านี่ไม่ใช่ข้อผิดพลาดทางไวยากรณ์) เราสามารถคำนวณระยะแก้ไขระหว่างตัวระบุ unresolvable และตัวระบุทั้งหมดที่อยู่ในขอบเขตและโดยการแทนที่คำ unresolvable ด้วยคำที่ผู้ใช้มีความหมายมากที่สุดหนึ่งจะเกิดขึ้นกับโปรแกรมที่ถูกต้องในหลายกรณี อย่างไรก็ตามปรากฎว่ายังดีกว่าการตั้งค่าสถานะข้อผิดพลาดและให้ IDE แนะนำการเปลี่ยนที่ถูกต้อง
ผู้เรียบเรียงดังกล่าวจะเป็นการใช้งานที่ไม่ได้มาตรฐานและไม่ได้มาตรฐานสำหรับภาษาใดก็ตามที่กำลังรวบรวม
มีการทดลองหลายครั้ง แต่บ่อยครั้งที่มันไม่ได้ผลตามที่ต้องการ: คิดว่า HAL 9000 หรือ GlaDOS
ใน C คุณไม่สามารถผ่านอาร์เรย์ตามค่าได้ แต่คอมไพเลอร์อนุญาตให้คุณเขียน:
void foo(int array[10]);
ซึ่งจะถูกเขียนใหม่อย่างเงียบ ๆ เมื่อ:
void foo(int* array);
มันช่างโง่เหลือเกิน ฉันต้องการข้อผิดพลาดอย่างหนักที่นี่แทนที่จะเขียนใหม่แบบเงียบ ๆ เนื่องจากกฎพิเศษนี้ทำให้โปรแกรมเมอร์หลายคนเชื่อว่าอาร์เรย์และตัวชี้เป็นสิ่งเดียวกัน พวกเขาจะไม่.