З точки зору мирян, що залишається рекурсією?


12

Відповідно до однієї сторінки на code.google.com, "ліва рекурсія" визначається так:

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

Вікіпедія пропонує два різні визначення:

  1. З точки зору безконтекстної граматики, нетермінальний r є ліворекурсивним, якщо найлівіший символ у будь-якій з виробництв r ('альтернативи') або негайно (прямий / безпосередній ліво-рекурсивний), або через якийсь інший нетермінальний визначення (непряме / приховане ліво-рекурсивне) переписується на r знову.

  2. "Граматика є рекурсивною ліворуч, якщо ми зможемо знайти якийсь нетермінальний А, який врешті-решт отримає сентенційну форму із самим собою як символ зліва".

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

Відповіді:


21

Правило Rє рекурсивним, якщо для того, щоб з’ясувати, чи Rвідповідають матчі, спочатку потрібно з’ясувати, чи Rвідповідають. Це відбувається, коли Rпрямо чи опосередковано з'являється як перший термін у деякому виробництві самого себе.

Уявіть іграшкову версію граматики для математичних виразів, лише додавання та множення, щоб уникнути відволікання:

Expression ::= Multiplication '+' Expression
            || Multiplication

Multiplication ::= Term '*' Term
                 || Term

Term ::= Number | Variable

Як було написано, тут немає лівої рекурсії - ми можемо передати цю граматику рекурсивно-розбірливому аналізатору.

Але припустимо, ви намагалися написати це так:

Expression ::= Expression '*' Expression
            || Expression '+' Expression
            || Term

Term ::= Number | Variable

Це граматика, і деякі парсери можуть впоратися з цим, але рекурсивні парсери парсерингу та LL-парсери не можуть - тому що правило для цього Expressionпочинається Expressionсаме з себе. Повинно бути очевидним, чому в аналізаторі рекурсивно-десантного зниження це призводить до безмежної рекурсії без фактичного споживання жодного вкладу.

Не має значення, чи правило посилається на себе прямо чи опосередковано; якщо Aє альтернатива , яка починається з B, і Bмає альтернативу , яка починається з A, то Aі Bобидва опосередковано залишили рекурсию, а в рекурсивном спуску синтаксичного аналізатора їх відповідність функцій призведе до нескінченної взаємної рекурсії.


Так що в другому прикладі, якщо ви змінили дуже перше , що після того, як ::=від Expressionдо Term, і якщо ви зробили те ж саме після того , як перший ||, вона більше не залишиться рекурсією? Але що якби ви це робили тільки після ::=, але ні ||, це все одно залишилося б рекурсивним?
Panzercrisis

Здається, ви говорите, що багато парсерів йде зліва направо, зупиняючись на кожному символі та рекурсивно оцінюючи його на місці. У цьому випадку, якби перше Expressionбуло б вимкнено Term, як після, так ::=і після першого ||, все було б добре; тому що рано чи пізно воно зіткнеться з чимось, що не є Numberані ані а Variable, таким чином, зможе визначити, що щось не є Expressionбез подальшого виконання ...
Panzercrisis

... Але якби хтось із тих, хто все ще починав Expression, потенційно знайде щось, що не є Term, і він би просто перевіряв, чи все Expressionзакінчується. Це це?
Panzercrisis

1
@Panzercrisis більш-менш. Вам дійсно потрібно шукати значення LL, LR та рекурсивно-парсерних аналізаторів.
варення

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

4

Я візьму на себе удар, коли його вкладають у мирянина.

Якщо ви думаєте з точки зору дерева розбору (не AST, а відвідування парсера та розширення вхідних даних), ліва рекурсія призводить до того, що дерево росте ліворуч і вниз. Права рекурсія - якраз навпаки.

Як приклад, звичайна граматика у компіляторі - це список елементів. Давайте візьмемо список рядків ("червоний", "зелений", "синій") та проаналізуємо його. Я міг написати граматику кількома способами. Наступні приклади прямо ліворуч або право рекурсивно відповідно:

arg_list:                           arg_list:
      STRING                              STRING
    | arg_list ',' STRING               | STRING ',' arg_list 

Дерева для цього розбору:

         (arg_list)                       (arg_list)
          /      \                         /      \
      (arg_list)  BLUE                  RED     (arg_list)
       /       \                                 /      \
   (arg_list) GREEN                          GREEN    (arg_list)
    /                                                  /
 RED                                                BLUE

Зверніть увагу, як він росте в напрямку рекурсії.

Це насправді не проблема, нормально хотіти написати ліву рекурсивну граматику ... якщо ваш інструмент аналізатора може це впоратися. Знизу вгору парсери справляються з цим просто чудово. Так можуть і більш сучасні парселери LL. Проблема з рекурсивними граматиками - це не рекурсія, це рекурсія без просування синтаксичного аналізатора або, повторне повторне використання без маркування. Якщо ми завжди споживаємо принаймні 1 маркер, коли ми повторюємо, ми зрештою доходимо до кінця синтаксичного аналізу. Ліва рекурсія визначається як повторювана без споживання, яка є нескінченною петлею.

Це обмеження - суто детальна реалізація реалізації граматики з наївним аналізатором LL згори вниз (рекурсивний аналізатор пониження). Якщо ви хочете дотримуватися лівих рекурсивних граматик, ви можете впоратися з цим, переписавши виробництво, щоб спожити щонайменше 1 маркер, перш ніж повторитись, так що це гарантує, що ми ніколи не застряжемо в невиробничому циклі. Будь-яке правило граматики, яке є рекурсивним ліворуч, ми можемо переписати його, додавши проміжне правило, яке розгладжує граматику лише на один рівень пошуку, використовуючи маркер між рекурсивними виробництвами. (ПРИМІТКА. Я не кажу, що це єдиний спосіб або кращий спосіб переписати граматику, просто вказавши на узагальнене правило. У цьому простому прикладі найкращим варіантом є використання право-рекурсивної форми). Оскільки такий підхід узагальнений, генератор парсера може реалізувати його, не залучаючи програміста (теоретично). На практиці я вважаю, що ANTLR 4 зараз робить саме це.

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

bool match_list()
{
    if(lookahead-predicts-something-besides-comma) {
       match_STRING();
    } else if(lookahead-is-comma) {
       match_list();   // left-recursion, infinite loop/stack overflow
       match(',');
       match_STRING();
    } else {
       throw new ParseException();
    }
}

Насправді, з чим ми справді маємо справу - це «наївна реалізація», тобто. ми спочатку передбачили дане речення, потім рекурсивно називали функцію для цього прогнозу, і ця функція наївно викликає те саме передбачення знову.

У парсерів знизу не виникає проблеми рекурсивних правил в будь-якому напрямку, оскільки вони не переосмислюють початок речення, вони працюють, складаючи речення разом.

Рекурсія в граматиці є лише проблемою, якщо ми виробляємо зверху вниз, тобто. наш парсер працює, "розширюючи" наші прогнози, коли ми споживаємо жетони. Якщо замість того, щоб розширюватись, ми згортаємось (виробництво "зменшується"), як у LALR (Yacc / Bison) аналізатор знизу вгору, то рекурсія будь-якої сторони не є проблемою.

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