В чем разница между разбором LL и LR? - PullRequest
205 голосов
/ 12 мая 2011

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

Ответы [ 5 ]

438 голосов
/ 26 июля 2011

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

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

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

  1. Predict : На основе крайнего левого нетерминала и некоторого числа лексем предпросмотра выберите, какое произведение следует применить, чтобы приблизиться к входной строке.
  2. Сопоставление : сопоставление крайнего левого угаданного символа терминала с крайним левым неиспользованным символом ввода.

В качестве примера приведем следующую грамматику:

  • S & rarr; E
  • E & rarr; Т + Е
  • E & rarr; T
  • T & rarr; int

Затем, учитывая строку int + int + int, синтаксический анализатор LL (2) (который использует два токена предвидения) проанализирует строку следующим образом:

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, я только что закончил преподавать курс компиляторов и у меня есть некоторые раздаточные материалы и слайды с лекциями по синтаксическому анализу на сайте курса. Я был бы рад уточнить любой из них, если вы считаете, что это будет полезно.

51 голосов
/ 14 августа 2013

Джош Хаберман в своей статье Разбор LL и LR Демистифицирован утверждает, что разбор LL напрямую соответствует польской записи , тогда как LR соответствует обратной польской записи . Разница между 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 выводит обратный порядок обхода.

Подробное объяснение, примеры и выводы можно найти в статье Хабермана .

7 голосов
/ 28 мая 2018

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

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

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

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

0 голосов
/ 13 июля 2019

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

...