В чем разница между анализом LL и LR?

Может ли кто-нибудь дать мне простой пример синтаксического анализа LL и LR?

Ответ 1

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

Разбор LL - левый-правый, самый левый вывод. То есть мы рассмотрим входные символы слева направо и попытаемся построить левый вывод. Это делается, начиная с символа начала и многократно расширяя крайний левый нетерминал, пока мы не достигнем целевой строки. Параметр LR - это левый справа, самый правый вывод, что означает, что мы сканируем слева направо и пытаемся построить самый правый вывод. Анализатор непрерывно выбирает подстроку входа и пытается отменить его обратно к нетерминальному.

Во время анализа LL парсер непрерывно выбирает между двумя действиями:

  • Предсказать. Основываясь на самом левом нетерминале и некотором количестве токенов, выберите, какое производство следует применять, чтобы приблизиться к входной строке.
  • Соответствие. Сопоставьте крайний левый конец символа терминала с самым левым неиспользованным символом ввода.

В качестве примера, учитывая эту грамматику:

  • S → E
  • E → T + E
  • E → Т
  • 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 есть два действия:

  • Shift: добавьте следующий токен ввода в буфер для рассмотрения.
  • Уменьшить. Уменьшите сбор терминалов и нетерминалов в этом буфере обратно к некоторому нетерминалу, изменив процесс производства.

В качестве примера, парсер 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 parsers. Анализаторы LR представлены во многих вариантах (LR (0), SLR (1), LALR (1), LR (1), IELR (1), GLR (0) и т.д.) И являются гораздо более мощными. Они также имеют гораздо более сложный характер и почти всегда генерируются такими инструментами, как yacc или bison. Анализаторы LL также представлены во многих вариантах (включая LL (*), который используется инструментом ANTLR), хотя на практике LL (1) широко используется.

Как бесстыдный плагин, если вы хотите больше узнать о разборе LL и LR, я только что закончил обучение курсу компиляторов и некоторые раздаточные материалы и слайды для лекций по разбору на веб-сайте курса. Я был бы рад разработать любой из них, если вы считаете, что это было бы полезно.

Ответ 2

Джош Хаберман в своей статье LL и LR Parsing Demystified утверждает, что анализ LL напрямую соответствует Польская нотация, тогда как LR соответствует Reverse Polish Notation. Разница между PN и RPN - это порядок прохождения двоичного дерева уравнения:

binary tree of an equation

+ 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 выводит послепорядок.

Для подробного объяснения, примеры и выводы см. статью Haberman .

Ответ 3

LL использует нисходящий, а LR использует восходящий.

Если вы анализируете язык программирования:

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

Ответ 4

Разбор 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 

FunctionDef выглядит в точности как 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.

Ответ 5

Самый левый пример деривации: грамматика 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)