Що стосується розбору сканерів без "скасування інших проблем"?


13

Я не розумію цього речення зі статті Вікіпедії про проблему Данглінг Ельза :

[Проблема Dangling Else] - це проблема, яка часто виникає при побудові компілятора, особливо при скануванні без сканера.

Чи може хтось пояснити мені, як методи розбору сканерів можуть посилити цю проблему? Мені здається, що проблема полягає в граматиці - оскільки це неоднозначно - не у виборі техніки розбору. Що я пропускаю?


2
Єдине, про що я можу придумати, - це те, що для розбору сканерів потрібна більш складна граматика, що ускладнює надання евристики для вирішення неоднозначності.
Джорджіо

3
@ Роберт Харві: Справа в тому, що це припущення повинно бути відображено синтаксичним деревом. Якщо граматика дозволяє отримати два різних синтаксичних дерева для рядка if a then if b then s1 else s2, то граматика неоднозначна.
Джорджіо

1
@RobertHarvey поширеним способом визначення мов є використання без контексту граматики, а також купа правил, які розбивають граматику, якщо це необхідно.

2
Не всі сканери без сканера створені рівними. Для, скажімо, PEG або GLR, звисаюча інша поведінка завжди передбачувана.
SK-логіка

1
[Проблема Dangling Else] не має нічого спільного з розбором без сканерів. [Проблема Dangling Else] пов'язана з операціями зменшення зрушення LR (знизу вгору) парсерами. AFAIK
ddur

Відповіді:


6

Я найкраще здогадуюсь, що вирок у статті Вікіпедії є наслідком нерозуміння твору Е. Віссера.

Граматики для сканерів без сканерів (тобто граматики, що описують мову як набір послідовностей символів, а не як набір послідовностей лексем із лексемами, описаними окремо як рядки символів), як правило, мають багато неоднозначностей. E. Фільтри розбиття на папері Visser для генералізованих сканерів LR парсерів (*) пропонують кілька механізмів вирішення неоднозначностей, один з яких корисний для вирішення звисаючої іншої проблеми. Але в документі не зазначено, що точна неоднозначність під назвою "висяча проблема ще" пов'язана з аналізаторами без сканерів (навіть навіть, що механізм особливо корисний для парсерів без сканерів).

Той факт, що він пропонує механізм її вирішення, не є неявним твердженням, оскільки інший механізм вирішення неоднозначностей (пріоритет оператора і пріоритет) також видається абсолютно не пов'язаним з природою сканерів розглянутих парсерів (врахуйте, наприклад, що ці двозначності не можуть бути присутні в звичайних граматиках, оскільки вони є результатом гніздування, тоді як ті, що обробляються правилом найдовшого збігу, можуть).


(*) Який, мабуть, папір служить базою статті Вікіпедії про аналізатори без сканерів, навіть якщо вони посилаються на інший, також Е. Віссер, Scannerless Generalized-LR Parsing .


13

Тільки для того, щоб заявити про проблему, проблема "Dangling Else" - це неоднозначність у специфікації синтаксису коду, де вона може бути незрозумілою, у випадках, якщо вони є нерозбірливими ifs та elses, до яких ще належить, якщо якщо.

Найпростіший і класичний приклад:

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

Незрозуміло тим, хто не знає специфіки специфікації мови напам'ять, яка ifотримує else(і цей конкретний фрагмент коду дійсний на півдесятка мов, але може працювати по-різному).

Конструкція Dangling Else створює потенційну проблему для реалізацій парсерного сканування, оскільки стратегія полягає в перекручуванні файлового потоку по одному символу за один раз, поки аналізатор не побачить, що його достатньо для токенізації (дайджест на збірну або проміжну мову, яку він збирає) . Це дозволяє парсеру підтримувати мінімальний стан; як тільки він вважає, що має достатньо інформації для написання маркерів, які він розбирає у файл, він зробить це. Ось кінцева мета аналізатора без сканерів; швидка, проста, легка компіляція.

Якщо припустити, що нові рядки та пробіли до або після пунктуації є безглуздими (як це є у більшості мов стилю С), це твердження буде видаватися компілятору як:

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

Ідеально розбирається на комп’ютері, тому подивимось. Я отримую один символ за один раз, поки у мене є:

if(conditionA)

О, я знаю, що це означає (у C #), це означає " pushconditionA на eval стек, а потім заклик brfalseперейти до заяви після наступної крапки з комою, якщо це неправда". Зараз я не бачу крапки з комою, тому поки що після цієї інструкції я встановлю зміщення переходу до наступного пробілу, і збільшу це зміщення, коли я вставляю більше інструкцій, поки не побачу крапку з комою. Продовжуючи розбирати ...

if(conditionB)

Гаразд, це аналізує аналогічну пару операцій ІЛ, і це відбувається відразу після інструкції, яку я щойно проаналізував. Я не бачу крапки з комою, тому я збільшить зсув мого попереднього твердження на довжину моїх двох команд (одна для натискання та одна для перерви) і продовжую шукати.

doFoo();

Гаразд, це просто. Це " calldoFoo". І це крапка з комою я бачу? Ну, це чудово, ось і кінець рядка. Я збільшить зсув обох своїх блоків по довжині цих двох команд і забуду, що я коли-небудь хвилювався. Добре, рухаємось далі ...

else

... Ой-ой. Це не так просто, як виглядало. Гаразд, я забув, що я тільки робив, але elseозначає, що десь я вже бачив умовний перерву, який я вже бачив, тому дозвольте оглянутись назад… так, так це brfalse, одразу після того, як я натискаю на деякий «умоваB» на стек, що б там не було. Гаразд, зараз мені потрібен безумовний, breakяк наступне твердження. Заява, яка надійде після цього, тепер, безумовно, є цільовою умовою перерви, тому я переконуюсь, що я маю це правильно, і збільшу безумовну перерву, яку я вклав. Переміщуючись далі ...

doBar();

Це легко. " calldoBar". І є крапка з комою, і я ніколи не бачив дужок. Отже, безумовне breakповинно перейти до наступного твердження, яке б воно не було, і я можу забути, що я коли-небудь хвилювався.


Отже, що ми маємо ... (зауважте: це 10:00 вечора, і мені не здається перетворювати зсуви бітів у шістнадцятковий чи заповнювати повну оболонку 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) полягає в тому, що elseйдеться з найближчими if. Відступ для наступного введення в виконання, воно буде виконуватись так, якщо, якщо умоваA є помилковою, весь решта фрагмента пропускається:

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

... але це робиться через serendipity, тому що розрив, пов'язаний із зовнішнім ifтвердженням, переходить до breakоператора в кінці внутрішнього if , який виводить покажчик виконання за межі всього оператора. Це зайвий непотрібний стрибок, і якби цей приклад був ще складнішим, він би більше не функціонував, якщо проаналізувати та токенізувати таким чином.

Крім того, що робити, якщо мовна специфікація сказала, що звисання elseналежить першому if, а якщо умоваA - помилковою, то виконується doBar, тоді як якщо умоваA - це правда, але не умоваB, тоді нічого не відбувається, як це?

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

Парсер забув перший ifіснував і тому цей простий алгоритм аналізатора не дасть правильного коду, не кажучи вже про ефективний код.

Тепер, аналізатор міг би бути досить розумним, щоб запам'ятати ifs і elses, які він має довший час, але якщо мовна специфікація говорить сингл elseпісля двох ifs збігів з першою if, це спричиняє проблеми з двома ifs зі збігом elses:

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

Парсер побачить перше else, збігться з першим if, потім побачить другий і впаде в паніку "що, чорт забираю, я робив знову". У цей момент аналізатор отримав досить багато коду в зміненому стані, що він, скоріше, вже витіснив би вихідний потік файлів.

У всіх цих проблемах є рішення та що - якщо. Але або код, необхідний для розумного, збільшує складність алгоритму аналізатора, або мовна специфікація, що дозволяє аналізатору бути таким німим, збільшує багатослівність мовного вихідного коду, наприклад, вимагаючи закінчення операторів типу end if, або дужки із зазначенням вкладених блокує, якщо у ifвисловлюванні є else(обидва вони зазвичай бачать в інших мовних стилях).

Це лише один, простий приклад пари ifтверджень, і подивіться на всі рішення, які повинен був прийняти компілятор, і де він міг би дуже легко зіпсуватися. Це деталь, що стоїть за цією нешкідливою заявою з Вікіпедії у вашому запитанні.


1
Цікаво, але я далеко не впевнений, що це було призначено статтею у Вікіпедії. Він посилається (через запис без сканера) звіт Eelco Visser, вміст якого на перший погляд не сумісний з вашими поясненнями.
AProgrammer

3
Дякую за відповідь, але це дійсно не стосується ОП. Я не погоджуюся з припущеннями у публікації про те, яка мета сканера без сканера та як вона реалізована. Існує багато способів реалізації парсерів без сканерів, і ця публікація, здається, має справу лише з обмеженим набором.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.