การแยกวิเคราะห์ที่ไม่สแกนเนอร์เกี่ยวข้องกับ“ ปัญหาอื่น ๆ ที่น่าวิตก”


13

ฉันไม่เข้าใจประโยคนี้จากบทความ Wikipedia เกี่ยวกับปัญหา Dangling Else :

[ปัญหา Dangling Else] เป็นปัญหาที่เกิดขึ้นบ่อยครั้งในการสร้างคอมไพเลอร์โดยเฉพาะการแยกวิเคราะห์ที่ไม่มีสแกนเนอร์

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


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

3
@ Robert Harvey: ประเด็นคือข้อสันนิษฐานนี้จะต้องสะท้อนออกมาจากโครงสร้างไวยากรณ์ หากไวยากรณ์อนุญาตให้ได้รับแผนผังต้นไม้สองแบบที่แตกต่างกันสำหรับสตริงif a then if b then s1 else s2ไวยากรณ์นั้นจะคลุมเครือ
Giorgio

1
@RobertHarvey วิธีการทั่วไปในการกำหนดภาษากำลังใช้ไวยากรณ์ที่ไม่ต้องใช้บริบทรวมถึงกฎที่ทำให้เข้าใจไวยากรณ์ในกรณีที่จำเป็น

2
ไม่ใช่ตัวแยกวิเคราะห์สแกนเนอร์ที่ไม่เท่ากันทั้งหมดที่สร้างขึ้น สำหรับพูด PEG หรือ GLR พฤติกรรมที่เป็นอันตรายอื่นคาดการณ์ได้เสมอ
SK-logic

1
[ปัญหา Dangling Else] ไม่มีส่วนเกี่ยวข้องกับการแยกวิเคราะห์แบบสแกนเนอร์ [ปัญหา Dangling Else] เกี่ยวข้องกับการดำเนินการลดการเลื่อนของตัวแยกวิเคราะห์ LR (จากล่างขึ้นบน) AFAIK
ddur

คำตอบ:


6

การคาดเดาที่ดีที่สุดของฉันคือประโยคในบทความ Wikipedia เป็นผลมาจากความเข้าใจผิดในการทำงานของ E. Visser

ไวยากรณ์สำหรับตัวแยกวิเคราะห์แบบสแกนเนอร์ที่ไม่ใช้สแกนเนอร์ (เช่นไวยากรณ์ที่อธิบายภาษาเป็นชุดลำดับของตัวอักษรแทนเป็นชุดลำดับของโทเค็นที่มีโทเค็นที่อธิบายแยกกันเป็นสตริงอักขระ) มักจะมีความคลุมเครือจำนวนมาก E. Visser กระดาษDisambiguation ฟิลเตอร์สำหรับเครื่องสแกนเนอร์ LR Parsers (*) เสนอเครื่องสแกนเนอร์เสนอกลไกต่าง ๆ เพื่อแก้ปัญหาความกำกวมซึ่งหนึ่งในนั้นมีประโยชน์สำหรับการแก้ปัญหาห้อยต่องแต่งอื่น แต่กระดาษไม่ได้ระบุว่าความกำกวมแม่นยำที่เรียกว่า "ปัญหาห้อยต่องแต่งอย่างอื่น" นั้นเกี่ยวข้องกับการแยกวิเคราะห์แบบสแกนเนอร์ที่ไม่ใช้สแกนเนอร์

ความจริงที่ว่ามันเสนอกลไกสำหรับการแก้ปัญหามันไม่ใช่คำสั่งโดยนัยเนื่องจากกลไกการแก้ปัญหาความกำกวมอื่น (ลำดับความสำคัญของผู้ปฏิบัติงานและลำดับความสำคัญ) ดูเหมือนว่าไม่เกี่ยวข้องโดยสิ้นเชิงกับธรรมชาติสแกนเนอร์ของ parsers ที่พิจารณาแล้ว อยู่ในไวยากรณ์ปกติเนื่องจากเกิดจากการซ้อนขณะที่กฎการจับคู่ที่ยาวที่สุดสามารถทำได้)


(*) ซึ่งน่าจะเป็นกระดาษที่ทำหน้าที่เป็นฐานของบทความวิกิพีเดีย parsers scannerless แม้ว่าพวกเขาอ้างอิงอีกคนหนึ่งโดยอีไขควงScannerless ทั่วไป-LR แยก


13

เพียงเพื่อระบุปัญหาปัญหา Dangling Else นั้นเป็นความกำกวมในการกำหนดไวยากรณ์ของรหัสซึ่งอาจไม่ชัดเจนในกรณีของ ifs และ elses ถัดไปซึ่งเป็นของอื่น

ตัวอย่างที่ง่ายและคลาสสิค:

if(conditionA)
if(conditionB)
   doFoo();
else
   doBar();

มันไม่ชัดเจนสำหรับผู้ที่ไม่ทราบรายละเอียดของข้อกำหนดภาษาด้วยใจซึ่งifได้รับelse(และข้อมูลโค้ดเฉพาะนี้ใช้ได้ในครึ่งโหลภาษา แต่อาจทำงานแตกต่างกันในแต่ละภาษา)

โครงสร้าง Dangling Else ก่อให้เกิดปัญหาที่อาจเกิดขึ้นสำหรับการใช้งานตัวแยกวิเคราะห์แบบไม่ใช้สแกนเนอร์เนื่องจากกลยุทธ์คือการสตรีมไฟล์ทีละตัวอักษรทีละตัวจนกว่าตัวแยกวิเคราะห์จะเห็นว่ามันเพียงพอที่จะทำเครื่องหมาย (แยกย่อยเป็นภาษาแอสเซมบลี . สิ่งนี้อนุญาตให้ parser รักษาสถานะน้อยที่สุด ทันทีที่มันคิดว่ามีข้อมูลเพียงพอที่จะเขียนโทเค็นที่มีการแยกวิเคราะห์ไฟล์มันจะทำเช่นนั้น นั่นคือเป้าหมายสุดท้ายของเครื่องมือแยกวิเคราะห์แบบสแกนเนอร์ การรวบรวมที่รวดเร็วง่ายและมีน้ำหนักเบา

สมมติว่าบรรทัดใหม่และช่องว่างก่อนหรือหลังเครื่องหมายวรรคตอนไม่มีความหมาย (เนื่องจากเป็นภาษา C-style ส่วนใหญ่) ข้อความนี้จะปรากฏต่อคอมไพเลอร์เป็น:

if(conditionA)if(conditionB)doFoo();else doBar;

แยกวิเคราะห์คอมพิวเตอร์ได้อย่างสมบูรณ์แบบดังนั้นมาดูกัน ฉันจะได้รับตัวละครทีละตัวจนกว่าฉันจะได้:

if(conditionA)

โอ้ฉันรู้ว่าสิ่งที่หมายถึง (ใน C #) มันหมายถึง " pushconditionA บน eval stack และจากนั้นโทรbrfalseเพื่อข้ามไปยังคำสั่งหลังจากเซมิโคลอนถัดไปถ้ามันไม่จริง" ตอนนี้ฉันไม่เห็นเครื่องหมายอัฒภาคดังนั้นตอนนี้ฉันจะตั้งค่าการข้ามของฉันไปที่ช่องว่างถัดไปหลังจากคำแนะนำนี้และฉันจะเพิ่มออฟเซ็ตที่ฉันใส่คำแนะนำเพิ่มเติมจนกว่าฉันจะเห็นเครื่องหมายอัฒภาค กำลังดำเนินการแยกวิเคราะห์ ...

if(conditionB)

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

doFoo();

ตกลงนั่นเป็นเรื่องง่าย นั่นคือ " calldoFoo" และนั่นคืออัฒภาคที่ฉันเห็นหรือไม่ นั่นเยี่ยมมากนั่นคือจุดสิ้นสุดของเส้น ฉันจะเพิ่มการกระโดดข้ามบล็อกของฉันทั้งสองตามความยาวของคำสั่งทั้งสองนี้และลืมว่าฉันเคยใส่ใจ ตกลงย้ายที่ ...

else

... เอ่อโอ้. มันไม่ง่ายอย่างที่คิด ตกลงฉันลืมสิ่งที่ฉันเพิ่งทำ แต่elseวิธีการที่มีคำสั่งหยุดพักตามเงื่อนไขบางแห่งที่ฉันได้เห็นแล้วดังนั้นให้ฉันดูกลับ ... อ๋อมีนั่นคือbrfalseทันทีหลังจากที่ฉันกด "เงื่อนไข B" บน กองอะไรก็ตามที่เป็น ตกลงตอนนี้ฉันต้องการเงื่อนไขที่ไม่มีเงื่อนไขbreakเป็นคำสั่งต่อไป คำแถลงที่จะตามมาในตอนนี้คือเป้าหมายการหยุดพักตามเงื่อนไขของฉันอย่างแน่นอนดังนั้นฉันจะตรวจสอบให้แน่ใจว่าฉันมีสิทธิ์แล้วและฉันจะเพิ่มการหยุดพักแบบไม่มีเงื่อนไขที่ฉันวางไว้

doBar();

ง่ายมาก " calldoBar" และมีเครื่องหมายอัฒภาคและฉันไม่เคยเห็นวงเล็บปีกกาใด ๆ ดังนั้นเงื่อนไขที่ไม่มีเงื่อนไขbreakควรข้ามไปยังข้อความถัดไปไม่ว่าจะเป็นอะไรและฉันสามารถลืมได้ว่าฉันเคยใส่ใจ


ดังนั้นเรามีอะไร ... (หมายเหตุ: มันคือ 10:00 PM และฉันไม่รู้สึกอยากแปลง bit offsets เป็นเลขฐานสิบหกหรือเติมเปลือก IL แบบเต็มของฟังก์ชั่นด้วยคำสั่งเหล่านี้ดังนั้นนี่คือหลอกหลอก-IL ใช้หมายเลขบรรทัดโดยปกติจะมีออฟเซ็ตไบต์):

ldarg.1 //conditionA
brfalse <line 6> //jumps to "break"
ldarg.2 //conditionB
brfalse <line 7> //jumps to "call doBar"
call doFoo
break <line 8> //jumps beyond statement in scope
call doBar
<line 8 is here>

ดีที่ดำเนินการอย่างถูกต้องจริงถ้ากฎ (ในขณะที่ส่วนใหญ่ภาษา C-style) คือการที่จะไปกับที่อยู่ใกล้ที่สุดelse ifเยื้องเพื่อติดตามการซ้อนการดำเนินการมันจะดำเนินการเช่นนี้โดยที่ถ้าเงื่อนไข A เป็นเท็จส่วนที่เหลือทั้งหมดของตัวอย่างจะถูกข้าม:

if(conditionA)
    if(conditionB)
       doFoo();
    else
       doBar();

... แต่มันทำโดยบังเอิญเพราะตัวแบ่งที่เกี่ยวข้องกับifคำสั่งด้านนอกกระโดดไปที่breakคำสั่งในตอนท้ายของด้านใน ifซึ่งจะใช้ตัวชี้การดำเนินการเกินคำสั่งทั้งหมด มันเป็นการกระโดดที่ไม่จำเป็นเป็นพิเศษและถ้าตัวอย่างนี้มีความซับซ้อนมากขึ้นมันอาจไม่ทำงานอีกต่อไปถ้าแยกวิเคราะห์และโทเค็นด้วยวิธีนี้

นอกจากนี้ถ้าข้อมูลจำเพาะเกี่ยวกับภาษาบอกว่าห้อยelseเป็นของแรกifและถ้า conditionA เป็นเท็จ doBar จะถูกดำเนินการในขณะที่ถ้าเงื่อนไข A เป็นจริง แต่ไม่ใช่เงื่อนไข B ก็ไม่มีอะไรเกิดขึ้นเช่นนั้น?

if(conditionA)
    if(conditionB)
       doFoo();
else
   doBar();

ตัวแยกวิเคราะห์ได้ลืมสิ่งแรกที่ifเคยมีอยู่ดังนั้นอัลกอริธึมตัวแยกวิเคราะห์แบบง่ายนี้จะไม่สร้างรหัสที่ถูกต้อง

ตอนนี้ parser อาจฉลาดพอที่จะจดจำifs และelses ได้เป็นเวลานาน แต่ถ้า spec ภาษาบอกว่าelseหลังจากifจับคู่สองครั้งเดียวกับครั้งแรกifที่ทำให้เกิดปัญหากับสองifs กับการจับคู่elses:

if(conditionA)
    if(conditionB)
       doFoo();
    else
       doBar();
else
    doBaz();

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

มีวิธีแก้ไขปัญหาเหล่านี้และสิ่งที่ควรทำ แต่รหัสที่จำเป็นต้องเป็นสมาร์ทจะเพิ่มความซับซ้อนของอัลกอริธึม parser หรือสเป็คภาษาที่อนุญาตให้ parser เป็น dumb นี้เพิ่มความฟุ่มเฟื่อยของซอร์สโค้ดภาษาเช่นโดยต้องการคำสั่งสิ้นสุดเช่นend ifหรือวงเล็บแสดง nested บล็อกถ้าifข้อความมีelse(ทั้งสองอย่างซึ่งมักจะเห็นในรูปแบบภาษาอื่น ๆ )

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


1
ที่น่าสนใจ แต่ฉันก็ยังห่างไกลจากความแน่ใจว่าเป็นสิ่งที่ตั้งใจโดยบทความ Wikipedia มันอ้างอิง (ผ่านรายการสแกนเนอร์) รายงานโดย Eelco Visser ซึ่งเนื้อหาตั้งแต่แรกเห็นไม่เข้ากันกับคำอธิบายของคุณ
AProgrammer

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