Чому ліва рекурсія погана?


20

Чому під час проектування компілятора слід усунути ліву рекурсію у граматиках? Я читаю, що це тому, що це може спричинити нескінченну рекурсію, але чи не так це і для правильної рекурсивної граматики?


2
Зазвичай компілятори використовують розбір зверху вниз. Якщо у вас залишилася рекурсія, тоді аналізатор переходить у нескінченну рекурсію. Однак у правій рекурсії парсер може побачити префікс рядка, який він має до цього часу. Таким чином, він може перевірити, чи похідність пішла "занадто далеко". Звичайно, можна поміняти місцями ролі та інтерпретувати вирази праворуч, зробивши правильну рекурсію поганою, а ліво-рекурсійну штрафом.
Шоул

6
Ліва рекурсія погана, оскільки за старих часів, коли на комп’ютерах було 16 КБ оперативної пам’яті, найчастіше генератор парсера не впорався з цим.
Андрій Бауер

Відповіді:


15

Ліві рекурсивні граматики - це не обов'язково погана річ. Ці граматики легко розбираються, використовуючи стек, щоб відслідковувати вже проаналізовані фрази, як це відбувається у LR-аналізаторі .

Нагадаємо, що ліве рекурсивне правило граматики CF має вигляд:Г=(V,Σ,R,S)

ααβ

з - елемент V і β - елемент V Σ . (Дивіться повне формальне визначення кортежу ( V , Σ , R , S ) там ).αVβVΣ(V,Σ,R,S)

Зазвичай - це фактично послідовність терміналів і нетерміналів, і існує інше правило α, де α не відображається в правій частині.βαα

Щоразу, коли граматичний аналізатор граматики отримує новий термінал (від лексеру), цей термінал висувається на вершину стека: ця операція називається зсувом .

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

При правильних рекурсивних граматиках стек може зростати нескінченно до тих пір, поки не відбудеться зменшення, таким чином, досить різко обмеживши можливості аналізу. Однак ліві рекурсивні дозволить компілятору генерувати скорочення раніше (насправді якнайшвидше). Додаткову інформацію див. У статті wikipedia .


Це допоможе, якби ви визначили свої змінні.
Андрій S

12

Розглянемо це правило:

example : 'a' | example 'b' ;

Тепер розглянемо аналізатор LL, який намагається співставити невідповідний рядок, як 'b'це правило. Оскільки 'a'він не відповідає, він спробує співставити example 'b'. Але для цього він повинен відповідати example... що саме це намагалося зробити в першу чергу. Він може застрягнути, намагаючись назавжди побачити, чи може він відповідати, тому що він завжди намагається співставити той самий потік жетонів з тим же правилом.

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


3
Ви начебто сліпо припускаєте, що синтаксичний розбір обов'язково є наївним розбором зверху вниз.
reinierpost

Я підкреслюю недоліки досить поширеного методу розбору - проблеми, яку легко уникнути. Звичайно, можна обробити ліву рекурсію, але, зберігаючи її, створюється майже завжди непотрібне обмеження на тип аналізатора, який може її використовувати.
cHao

Так, це більш конструктивний і корисний спосіб його викладати.
reinierpost

4

(Я знаю, що це питання вже досить давнє, але якщо інші люди мають те саме питання ...)

Ви запитуєте в контексті рекурсивних аналізаторів спуску? Наприклад, для граматики expr:: = expr + term | term, чому щось подібне (зліва рекурсивна):

// expr:: = expr + term
expr() {
   expr();
   if (token == '+') {
      getNextToken();
   }
   term();
}

проблематично, але чи не це (правильно рекурсивно)?

// expr:: = term + expr
expr() {
   term();
   if (token == '+') {
      getNextToken();
      expr();
   }
}

Схоже, самі обидві версії expr()виклику. Але важлива відмінність - це контекст - тобто поточний маркер при здійсненні цього рекурсивного дзвінка.

У лівому рекурсивному випадку expr()постійно дзвонить собі з тим самим ознакою і жодного прогресу не досягається. У правому рекурсивному випадку він витрачає частину входу в дзвінок до term()та маркер PLUS, перш ніж перейти до виклику expr(). Тож у цей момент рекурсивний виклик може викликати термін, а потім припиняти, перш ніж знову доходити до тесту if.

Наприклад, розгляньте синтаксичний аналіз 2 + 3 + 4. Лівий рекурсивний синтаксичний синтаксичний виклик expr()нескінченно затримується на першому жетоні, тоді як правий рекурсивний аналізатор споживає "2 +" перед тим, як expr()знову зателефонувати . Другий дзвінок expr()відповідає "3 +" та дзвінки expr()лише з 4-х, що залишилися. 4 збігу на термін, і синтаксичний аналіз завершується без жодних викликів expr().


2

З посібника Bison:

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

http://www.gnu.org/software/bison/manual/html_node/Recursion.html

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

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