Удаление левой рекурсии в ANTLR

Как объяснен в Удалении левой рекурсии, существует два способа удалить левую рекурсию.

  • Измените исходную грамматику для удаления левой рекурсии с помощью некоторой процедуры
  • Запишите грамматику первоначально, чтобы не иметь левую рекурсию

Что люди обычно используют для удаления (не имеющий) левая рекурсия с ANTLR? Я использовал гибкий провод/бизона для синтаксического анализатора, но я должен использовать ANTLR. Единственной вещью, которую я обеспокоен использованием ANTLR (или LL-анализатор в genearal) является удаление левой рекурсии.

  • В практическом смысле, как серьезный из удаления левой рекурсии в ANTLR? Действительно ли это - showstopper в использовании ANTLR? Или, никто не заботится об этом в сообществе ANTLR?
  • Мне нравится идея поколения AST ANTLR. С точки зрения получения быстрого и простого способа AST, какой метод (из 2 методов левой рекурсии удаления) предпочтителен?

Добавленный

Я сделал некоторый эксперимент со следующей грамматикой.

E -> E + T|T
T -> T * F|F
F -> INT | ( E )

После удаления левой рекурсии я получаю следующее

E -> TE'
E' -> null | + TE'
T -> FT'
T' -> null | * FT'

Я мог придумать следующее представление ANTLR. Даже при том, что, Это относительно симпатично простой и простой, это кажется грамматикой, которая не имеет левой рекурсии, должен быть лучший способ пойти.

grammar T;

options {
    language=Python;
}

start returns [value]
   : e {$value = $e.value};
e returns [value]
   : t ep  
     {
       $value = $t.value
       if $ep.value != None:
         $value += $ep.value
     }
   ;
ep returns [value]
   : {$value = None}
   | '+' t r = ep 
     {
       $value = $t.value
       if $r.value != None:
            $value += $r.value
     }
   ;
t returns [value]
  : f tp 
    {
      $value = $f.value
      if $tp.value != None:
        $value *= $tp.value
    }
  ;
tp returns [value]
  : {$value = None}
  | '*' f r = tp 
    {
      $value = $f.value;
      if $r.value != None:
        $value *= $r.value
    }
  ;
f returns [int value]
  : INT {$value = int($INT.text)}
  | '(' e ')' {$value = $e.value}
  ;

INT :   '0'..'9'+ ;
WS: (' '|'\n'|'\r')+ {$channel=HIDDEN;} ;

8
задан Community 23 May 2017 в 12:18
поделиться

4 ответа

Если вы пишете грамматику, то, конечно, вы стараетесь написать ее так, чтобы избежать подводных камней вашего конкретного генератора парсера.

Обычно, по моему опыту, я получаю какое-нибудь справочное руководство по интересующему меня языку (legacy), и оно уже содержит грамматику или диаграммы железных дорог, и это то, что есть.

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

Во многих случаях удаление левых рекурсий - это вопрос пота, а его обычно не так много. Поэтому обычный подход - достать свой грамматический нож и заняться этим.

Я не думаю, что то, как вы удаляете левую рекурсию, меняет то, как ANTLR получает деревья. Вы должны сначала убрать левую рекурсию, иначе ANTLR (какой бы генератор синтаксического анализатора LL вы ни использовали) просто не примет вашу грамматику.

Есть те из нас, кто не хочет, чтобы генератор синтаксического анализатора накладывал какие-либо серьезные ограничения на то, что мы можем написать для контекстно-свободной грамматики. В этом случае вы хотите использовать что-то вроде генератора синтаксического анализатора GLR, который легко справляется с левой или правой рекурсией. Неразумные люди могут даже настаивать на автоматической генерации AST без каких-либо усилий со стороны автора грамматики. Инструмент, который может делать и то, и другое, см. в DMS Software Reengineering Toolkit.

1
ответ дан 5 December 2019 в 11:22
поделиться

Рассмотрим что-то вроде типичного списка параметров:

parameter_list: parameter
              | parameter_list ',' parameter
              ;

Поскольку вас не волнует ничего вроде приоритета или ассоциативности параметров, это довольно легко преобразовать в правую рекурсию за счет добавления дополнительной продукции:

parameter_list: parameter more_params
              ;

more_params:
           | ',' parameter more_params
           ;

В наиболее серьезных случаях вы можете потратить некоторое время на Книгу Дракона. Если сделать быструю проверку, это в основном рассматривается в главе 4.

Что касается серьезности, я почти уверен, что ANTLR просто не примет грамматику, содержащую левую рекурсию, которая поставила бы ее в «абсолютную необходимость». категория.

8
ответ дан 5 December 2019 в 11:22
поделиться

Я не могу говорить об ANTLR, но в целом шаги по устранению левой рекурсии формы:

A -> A B
  -> B

заключаются в изменении ее на:

A -> B+

(обратите внимание, что B должен появиться хотя бы один раз)

или, если ANTLR не поддерживает закрытие Клини, вы можете сделать:

A -> B B'

B' -> B B'
   -> 

Если вы предоставите пример ваших правил, у которых есть конфликты, я могу предоставить лучший, более конкретный ответ.

2
ответ дан 5 December 2019 в 11:22
поделиться

В практическом смысле, насколько серьезно удаление левой рекурсии в ANTLR? Является это препятствие в использовании ANTLR?

Я думаю, что вы неправильно понимаете левую рекурсию. Это свойство грамматики, а не генератора синтаксического анализатора или взаимодействия между генератором синтаксического анализатора и спецификацией. Это происходит, когда первый символ в правой части правила равен нетерминалу, соответствующему самому правилу.

Чтобы понять присущую здесь проблему, вам нужно кое-что знать о том, как работает синтаксический анализатор с рекурсивным спуском (LL). В анализаторе LL правило для каждого нетерминального символа реализуется функцией, соответствующей этому правилу. Итак, предположим, что у меня есть такая грамматика:

S -> A B
A -> a
B -> b

Тогда синтаксический анализатор будет выглядеть (примерно) следующим образом:

boolean eat(char x) {
  // if the next character is x, advance the stream and return true
  // otherwise, return false
}

boolean S() {
  if (!A()) return false;
  if (!B()) return false;
  return true;
}

boolean A(char symbol) {
  return eat('a');
}

boolean B(char symbol) {
  return eat('b');
}

Однако что произойдет, если я изменю грамматику на следующую?

S -> A B
A -> A c | null
B -> b

Предположительно, я хочу это грамматика для представления языка вроде c * b . Соответствующая функция в анализаторе LL будет выглядеть так:

boolean A() {
  if (!A()) return false;  // stack overflow!  We continually call A()
                           // without consuming any input.
  eat('c');
  return true;
}

Итак, у нас не может быть левой рекурсии. Перепишите грамматику как:

S -> A B
A -> c A | null
B -> b

, и синтаксический анализатор изменится как таковой:

boolean A() {
  if (!eat('c')) return true;
  A();
  return true;
}

(Отказ от ответственности: это мое примитивное приближение анализатора LL, предназначенное только для демонстрационных целей по этому вопросу. В нем есть очевидные ошибки.)

4
ответ дан 5 December 2019 в 11:22
поделиться
Другие вопросы по тегам:

Похожие вопросы: