Чи може хтось надати мені простий приклад розбору LL порівняно з LR розбором?
Чи може хтось надати мені простий приклад розбору LL порівняно з LR розбором?
Відповіді:
На високому рівні різниця між синтаксичним розбором і розбором LR полягає в тому, що парсери LL починаються з символу старту і намагаються застосувати постановки для досягнення цільового рядка, тоді як LR-парсери починаються з цільового рядка і намагаються повернутися на початку символ.
Синтаксичний синтаксичний аналіз - це ліво-праворуч, крайнє ліве виведення. Тобто ми розглядаємо вхідні символи зліва направо і намагаємося побудувати виведення зліва. Це робиться, починаючи зі стартового символу і багаторазово розширюючи крайній лівий нетермінал, поки ми не дійдемо до цільового рядка. Синтаксичний синтаксичний аналіз - це ліворуч праворуч, найправіше виведення, що означає, що ми скануємо зліва направо і намагаємося побудувати виведення з правого краю. Парсер безперервно вибирає підрядку введення і намагається повернути його назад до нетерміналу.
Під час розбору LL аналізатор постійно вибирає між двома діями:
Як приклад, наведена ця граматика:
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 є дві дії:
Як приклад, аналізатор 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 синтаксичний розбір, я щойно закінчив викладання курсу компіляторів і отримав кілька роздаткових матеріалів та слайдів лекцій про розбір на веб-сайті курсу. Я буду радий детальніше розглянути будь-який з них, якщо ви вважаєте, що це буде корисно.
Джош Хаберман у своїй статті 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-аналізатор виводить обхід після замовлення.
Для поглибленого пояснення, прикладів та висновків дивіться статтю Габермана .
LL використовує зверху вниз, тоді як LR використовує підхід знизу вгору.
Якщо ви розбираєте мову, що розповсюджується:
Синтаксичний аналіз 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.
Приклад деривації зліва (ліва частина): Граматика 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)