Яка різниця між LL та LR розбором?


Відповіді:


483

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

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

Під час розбору LL аналізатор постійно вибирає між двома діями:

  1. Прогнозуйте : Виходячи з крайнього лівого нетермінального каналу та деякої кількості символів пошуку, виберіть, яке виробництво потрібно застосувати, щоб наблизитись до вхідного рядка.
  2. Матч : Зіставте найменш лівий відгаданий термінальний символ із самим крайнім лівим символом введення.

Як приклад, наведена ця граматика:

  • S → E
  • E → T + E
  • E → T
  • T → int

Тоді, давши рядок int + int + int, аналізатор LL (2) (який використовує два лексеми lookahead) буде аналізувати рядок наступним чином:

Production       Input              Action
---------------------------------------------------------
S                int + int + int    Predict S -> E
E                int + int + int    Predict E -> T + E
T + E            int + int + int    Predict T -> int
int + E          int + int + int    Match int
+ E              + int + int        Match +
E                int + int          Predict E -> T + E
T + E            int + int          Predict T -> int
int + E          int + int          Match int
+ E              + int              Match +
E                int                Predict E -> T
T                int                Predict T -> int
int              int                Match int
                                    Accept

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

У аналізаторі LR є дві дії:

  1. Shift : Додайте наступний маркер введення до буфера для розгляду.
  2. Зменшити : зменшити набір терміналів і нетерміналів у цьому буфері назад до деяких нетермінальних, повернувши виробництво.

Як приклад, аналізатор LR (1) (з одним маркером lookahead) може проаналізувати ту саму рядок, як описано нижче:

Workspace        Input              Action
---------------------------------------------------------
                 int + int + int    Shift
int              + int + int        Reduce T -> int
T                + int + int        Shift
T +              int + int          Shift
T + int          + int              Reduce T -> int
T + T            + int              Shift
T + T +          int                Shift
T + T + int                         Reduce T -> int
T + T + T                           Reduce E -> T
T + T + E                           Reduce E -> T + E
T + E                               Reduce E -> T + E
E                                   Reduce S -> E
S                                   Accept

Як відомо, два алгоритми синтаксичного розбору (LL та LR) мають різні характеристики. LL-парсери, як правило, простіше писати вручну, але вони менш потужні, ніж LR-парсери, і приймають набагато менший набір граматик, ніж LR-парсери. LR-парсери мають багато ароматів (LR (0), SLR (1), LALR (1), LR (1), IELR (1), GLR (0) тощо) і є набагато більш потужними. Вони також мають набагато складніший характер і майже завжди породжуються такими інструментами, як yaccабо bison. LL-аналізатори також мають багато ароматів (включаючи LL (*), який використовується ANTLRінструментом), хоча на практиці LL (1) є найбільш широко використовуваним.

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


40
Ваші слайди лекції є феноменальними, легко найсмішнішими поясненнями, які я бачив :) Це та річ, яка насправді викликає інтереси.
kizzx2

1
Я також повинен коментувати слайди! Переглядаю їх усі зараз. Дуже допомагає! Дякую!
kornfridge

Дійсно насолоджуючись слайдами теж. Я не думаю, що ви можете розмістити не-Windows версію файлів проекту (і файл scanner.l, для pp2)? :)
Ерік П.

1
Єдине, що я можу внести у відмінні відповіді Метта, це те, що будь-яка граматика, яку можна проаналізувати за допомогою LL (k) синтаксичного розбору (тобто, "k" терміналів, щоб вирішити наступну дію розбору), може бути проаналізована LR ( 1) парсер. Це дає нам натяк на неймовірну силу LR розбору над LL розбором. Джерело: курс компілятора в UCSC, який викладав доктор Ф. Деремер, творець парсерів LALR ().
JoGusto

1
Відмінний ресурс! Дякуємо за надання слайдів, роздаткових матеріалів, проектів.
П. Хінкер

58

Джош Хаберман у своїй статті LL та LR Parsing Demystified стверджує, що синтаксичний аналіз безпосередньо відповідає польській нотації , тоді як LR відповідає зворотній польській нотації . Різниця між PN та RPN - це порядок переходу двійкового дерева рівняння:

двійкове дерево рівняння

+ 1 * 2 3  // Polish (prefix) expression; pre-order traversal.
1 2 3 * +  // Reverse Polish (postfix) expression; post-order traversal.

За словами Габермана, це ілюструє основну відмінність між LL та LR парсерами:

Основна різниця між тим, як працюють парсери LL та LR, полягає в тому, що парсер LL видає обхід дерева розбору перед замовленням, а LR-аналізатор виводить обхід після замовлення.

Для поглибленого пояснення, прикладів та висновків дивіться статтю Габермана .


9

LL використовує зверху вниз, тоді як LR використовує підхід знизу вгору.

Якщо ви розбираєте мову, що розповсюджується:

  • LL бачить вихідний код, який містить функції, які містять вираз.
  • LR бачить вираз, який належить до функцій, що призводить до повного джерела.

6

Синтаксичний аналіз LL є несприятливим у порівнянні з LR. Ось граматика, яка є кошмаром для генератора аналізаторів LL:

Goal           -> (FunctionDef | FunctionDecl)* <eof>                  

FunctionDef    -> TypeSpec FuncName '(' [Arg/','+] ')' '{' '}'       

FunctionDecl   -> TypeSpec FuncName '(' [Arg/','+] ')' ';'            

TypeSpec       -> int        
               -> char '*' '*'                
               -> long                 
               -> short                   

FuncName       -> IDENTIFIER                

Arg            -> TypeSpec ArgName         

ArgName        -> IDENTIFIER 

ФункціяDef точно схожа на FunctionDecl, поки не з'явиться значення ';' або "{".

Аналізатор LL не може обробляти два правила одночасно, тому він повинен вибрати або FunctionDef, або FunctionDecl. Але щоб знати, що правильно, воно має шукати ';' або '{'. Під час аналізу граматики час пошуку (k) здається нескінченним. На час розбору це скінченно, але може бути великим.

Аналізатор LR не повинен шукати голову, оскільки він може обробляти два правила одночасно. Тож генератори парного аналізу LALR (1) можуть легко впоратися з цією граматикою.

Враховуючи вхідний код:

int main (int na, char** arg); 

int main (int na, char** arg) 
{

}

LR-аналізатор може проаналізувати

int main (int na, char** arg)

не піклуючись про те, яке правило визнається, поки воно не зіткнеться з ';' або "{".

Аналізатор LL зависає на "int", оскільки він повинен знати, яке правило визнається. Тому він повинен шукати ';' або '{'.

Інший кошмар для парсерів LL - це рекурсія в граматиці. Ліва рекурсія - нормальна річ у граматиках, для генератора парного аналізатора LR не виникає проблем, але LL не справляється з цим.

Тож вам доведеться писати свої граматики неприродно з LL.


0

Приклад деривації зліва (ліва частина): Граматика G, яка не є контекстною, має результати

z → xXY (Правило: 1) X → Ybx (Правило: 2) Y → bY (Правило: 3) Y → c (Правило: 4)

Обчисліть рядок w = 'xcbxbc' з найбільшою лівою деривацією.

z ⇒ xXY (Правило: 1) ⇒ xYbxY (Правило: 2) ⇒ xcbxY (Правило: 4) ⇒ xcbxbY (Правило: 3) ⇒ xcbxbc (Правило: 4)


Приклад найвищого праворуч: K → aKK (Правило: 1) A → b (Правило: 2)

Обчисліть рядок w = 'aababbb' з найбільш правою деривацією.

K ⇒ aKK (Правило: 1) ⇒ aKb (Правило: 2) ⇒ aaKKb (Правило: 1) ⇒ aaKaKKb (Правило: 1) ⇒ aaKaKbb (Правило: 2) ⇒ aaKabbb (Правило: 2) ⇒ aababbb (Правило: 2)

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