Устранение левой рекурсии — различия между версиями
м (rollbackEdits.php mass rollback) |
|||
(не показана 101 промежуточная версия 19 участников) | |||
Строка 1: | Строка 1: | ||
+ | __TOC__ | ||
{{Определение | {{Определение | ||
− | |definition=Говорят, что [[Контекстно-свободные грамматики, вывод, лево- и правосторонний вывод, дерево разбора|контекстно-свободная( | + | |definition=Говорят, что [[Контекстно-свободные грамматики, вывод, лево- и правосторонний вывод, дерево разбора|контекстно-свободная (КС) грамматика]] <tex>\Gamma</tex> содержит '''непосредственную левую рекурсию''' (англ. ''direct left recursion''), если она содержит правило вида <tex>A \to A\alpha</tex>. |
}} | }} | ||
{{Определение | {{Определение | ||
− | |definition=Говорят, что | + | |definition=Говорят, что КС-грамматика <tex>\Gamma</tex> содержит '''левую рекурсию''' (англ. ''left recursion''), если в ней существует вывод вида <tex>A \Rightarrow^* A\alpha</tex>. |
}} | }} | ||
− | + | [[Методы трансляции#Нисходящий разбор|Методы нисходящего разбора]] не в состоянии работать с леворекурсивными грамматиками. Проблема в том, что продукция вида <tex>A \Rightarrow^* A\alpha</tex> может применяться бесконечно долго, так и не выработав некий терминальный символ, который можно было бы сравнить со строкой. Поэтому требуется преобразование грамматики, которое бы устранило левую рекурсию. | |
− | |||
− | + | ==Устранение непосредственной левой рекурсии== | |
− | + | Опишем процедуру, устраняющую все правила вида <tex>A \to A\alpha</tex>, для фиксированного нетерминала <tex>A</tex>. | |
− | |||
− | |||
− | + | <ol> | |
− | + | <li>Запишем все правила вывода из <tex>A</tex> в виде: | |
+ | <tex>A \to A\alpha_1 \mid \ldots \mid A\alpha_n \mid \beta_1 \mid \ldots \mid \beta_m </tex>, где | ||
+ | <ul> | ||
+ | <li> <tex>\alpha</tex> {{---}} непустая последовательность терминалов и нетерминалов (<tex>\alpha \nrightarrow \varepsilon </tex>);</li> | ||
+ | <li> <tex>\beta</tex> {{---}} непустая последовательность терминалов и нетерминалов, не начинающаяся с <tex>A</tex>.</li> | ||
+ | </ul> | ||
+ | <li>Заменим правила вывода из <tex>A</tex> на <tex>A \to\beta_1A^\prime \mid \ldots\ \mid \beta_mA^\prime \mid \beta_1 \mid \ldots \mid \beta_m</tex>.</li> | ||
− | + | <li>Создадим новый нетерминал <tex>{A^\prime} \to \alpha_1{A^\prime} \mid \ldots \mid \alpha_n{A^\prime} \mid \alpha_1 \mid \ldots \mid \alpha_n</tex>. </li> | |
+ | </li> | ||
+ | </ol> | ||
− | <tex>A \ | + | Изначально нетерминал <tex>A</tex> порождает строки вида <tex>\beta\alpha_{i0}\alpha_{i1} \ldots \alpha_{ik}</tex>. В новой грамматике нетерминал <tex>A</tex> порождает <tex>\beta{A^\prime}</tex>, а <tex>A^\prime</tex> порождает строки вида <tex>\alpha_{i0}\alpha_{i1} \ldots \alpha_{ik}</tex>. Из этого очевидно, что изначальная грамматика эквивалентна новой. |
− | + | ===Пример=== | |
− | + | <tex>A \to S\alpha \mid A\alpha</tex> | |
− | |||
− | + | <tex>S \to A\beta</tex> | |
− | <tex>A \ | + | Есть непосредственная левая рекурсия <tex>A \to A\alpha</tex>. Добавим нетерминал <tex>A^\prime</tex> и добавим правила <tex>A \to S\alpha{A^{\prime}}</tex>, <tex> A^{\prime} \to \alpha{A^{\prime}} </tex>. |
− | + | Новая грамматика: | |
− | <tex>A | + | <tex>A \to S\alpha{A^{\prime}} \mid S\alpha</tex> |
− | + | <tex>A^{\prime} \to \alpha{A^{\prime} \mid \alpha}</tex> | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | <tex>S \to A\beta</tex> | |
− | |||
− | |||
− | |||
− | + | В новой грамматике нет непосредственной левой рекурсии, но нетерминал <tex>A</tex> леворекурсивен, так как есть <tex> A \Rightarrow S\alpha{A^{\prime}} \Rightarrow A\beta\alpha{A^{\prime}} </tex> | |
− | |||
− | |||
− | == | + | ==Алгоритм устранения произвольной левой рекурсии== |
+ | Воспользуемся [[Удаление_eps-правил_из_грамматики | алгоритмом удаления <tex> \varepsilon </tex>-правил]]. Получим грамматику без <tex> \varepsilon </tex>-правил для языка <tex>L(\Gamma) \setminus \lbrace \varepsilon \rbrace</tex>. | ||
+ | |||
+ | Упорядочим нетерминалы, например по возрастанию индексов, и будем добиваться того, чтобы не было правил вида <tex>A_i \to A_j\alpha</tex>, где <tex>j \leqslant i</tex>. | ||
+ | Если данное условие выполняется для всех <tex>A_i</tex>, то в грамматике нет <tex>A_i \Rightarrow^* A_i</tex>, а значит не будет левой рекурсии. | ||
+ | |||
+ | Пусть <tex>N = \lbrace A_1, A_2, \ldots , A_n \rbrace</tex> {{---}} упорядоченное множество всех нетерминалов. | ||
+ | |||
+ | '''for''' <tex>A_i \in N</tex> | ||
+ | '''for''' <tex>A_j \in \{ N \mid 1 \leqslant j < i \}</tex> | ||
+ | '''for''' <tex>p \in \{ P \mid A_i \to A_j\gamma \}</tex> | ||
+ | удалить продукцию <tex>p</tex> | ||
+ | '''for''' <tex>Q \to x_i \in \{A_j \to \delta_1 \mid \ldots \mid \delta_k\}</tex> | ||
+ | добавить правило <tex>A_i \to x_i\gamma</tex> | ||
+ | устранить непосредственную левую рекурсию для <tex>A_i</tex> | ||
+ | |||
+ | Если <tex>\varepsilon</tex> присутствовал в языке исходной грамматики, добавим новый начальный символ <tex>S'</tex> и правила <tex>S' \to S \, \mid \, \varepsilon </tex>. | ||
+ | |||
+ | После <tex>i</tex> итерации внешнего цикла в любой продукции внешнего цикла в любой продукции вида <tex>A_k \to A_l\alpha, k < i</tex>, должно быть <tex>l > k</tex>. В результате при следующей итерации внутреннего цикла растет нижний предел <tex>m</tex> всех продукций вида <tex>A_i \to A_m\alpha</tex> до тех пор, пока не будет достигнуто <tex>i \leqslant m </tex>. | ||
+ | |||
+ | После <tex>i</tex> итерации внешнего цикла в грамматике будут только правила вида <tex>A_i \to A_j\alpha</tex>, где <tex>j > i</tex>. | ||
+ | Можно заметить, что неравенство становится строгим только после применения алгоритма устранения непосредственной левой рекурсии. При этом добавляются новые нетерминалы. Пусть <tex>{A^\prime}_i </tex> новый нетерминал. Можно заметить, что нет правила вида <tex>\ldots \to {A^\prime}_i</tex>, где <tex>{A^\prime}_i</tex> самый левый нетерминал, а значит новые нетерминалы можно не рассматривать во внешнем цикле. Для строгого поддержания инвариантов цикла можно считать, что созданный на <tex>i</tex> итерации в процессе устранения непосредственной левой рекурсии нетерминал имеет номер <tex>A_{-i}</tex> (т.е. имеет номер, меньший всех имеющихся на данный момент нетерминалов). | ||
+ | |||
+ | На <tex>i</tex> итерации внешнего цикла все правила вида <tex>A_i \to A_j \gamma</tex> где <tex> j < i </tex> заменяются на <tex>A_i \to \delta_1\gamma \mid \ldots \mid \delta_k\gamma</tex> где <tex>A_j \to \delta_1 \mid \ldots \mid \delta_k</tex>. Очевидно, что одна итерация алгоритма не меняет язык, а значит язык получившийся в итоге грамматики совпадает с исходным. | ||
+ | |||
+ | ===Оценка времени работы=== | ||
+ | Пусть <tex>a_i</tex> количество правил для нетерминала <tex>A_i</tex>. | ||
+ | Тогда <tex>i</tex> итерация внешнего цикла будет выполняться за <tex>O\left(\sum\limits_{A_i \to A_j, j < i} a_j\right) + O(a_i)</tex>, что меньше чем <tex>O\left(\sum\limits_{i=1}^n a_j\right)</tex>, значит асимптотика алгоритма <tex>O\left(n\sum\limits_{i=1}^n a_j\right)</tex>. | ||
+ | |||
+ | ===Худший случай=== | ||
+ | Проблема этого алгоритма в том, что в зависимости от порядка нетерминалов в множестве размер грамматки может получиться экспоненциальным. | ||
+ | |||
+ | Пример грамматики для которой имеет значение порядок нетерминалов | ||
+ | |||
+ | <tex>A_1 \to 0 \mid 1</tex> | ||
+ | |||
+ | <tex>A_{i+1} \to {A_i}0 \mid {A_i}1 </tex> для <tex>1 \leqslant i < n</tex> | ||
+ | |||
+ | Упорядочим множество нетерминалов по возрастанию индексов. Легко заметить, что правила для <tex>A_i</tex> будут представлять из себя все двоичные вектора длины <tex>i</tex>, а значит размер грамматики будет экспоненциальным. | ||
+ | ===Порядок выбора нетерминалов=== | ||
+ | {{Определение | ||
+ | |definition=Говорят, что нетерминал <tex>X</tex> {{---}} '''прямой левый вывод''' (англ. ''direct left corner'') из <tex>A</tex>, если существует правило вида <tex>A \to X\alpha</tex>. | ||
+ | }} | ||
+ | |||
+ | {{Определение | ||
+ | |definition='''Левый вывод''' (англ. ''left corner'') {{---}} [[Транзитивное_отношение|транзитивное]], [[Рефлексивное_отношение|рефлексивное]] замыкание отношения «быть прямым левым выводом». | ||
+ | }} | ||
+ | |||
+ | Во внутреннем цикле алгоритма для всех нетерминалов <tex>A_i</tex> и <tex>A_j</tex>, таких что <tex>j < i</tex> и <tex>A_j</tex> {{---}} прямой левый вывод из <tex>A_i</tex> заменяем все прямые левые выводы <tex>A_j</tex> из <tex>A_i</tex> на все выводы из <tex>A_j</tex>. | ||
+ | |||
+ | Это действие удаляет левую рекурсию только если <tex>A_i</tex> {{---}} леворекурсивный нетерминал и <tex>A_j</tex> содержится в выводе <tex>A_i</tex> (то есть <tex>A_i</tex> {{---}} левый вывод из <tex>A_j</tex>,в то время как <tex>A_j</tex> {{---}} левый вывод из <tex>A_i</tex>). | ||
+ | |||
+ | Перестанем добавлять бесполезные выводы, которые не участвуют в удалении левой рекурсии, упорядочив нетерминалы так: если <tex>j < i</tex> и <tex>A_j</tex> {{---}} прямой левый вывод из <tex>A_i</tex>, то <tex>A_i</tex> {{---}} левый вывод из <tex>A_j</tex>. | ||
+ | Упорядочим их по уменьшению количества различных прямых левых выводов из них. | ||
+ | |||
+ | Так как отношение «быть левым выводом» транзитивно,то если <tex>C</tex> {{---}} прямой левый вывод из <tex>B</tex>, то каждый левый вывод из С также будет левым выводом из <tex>B</tex>. А так как отношение «быть левым выводом» рефлексивно, <tex>B</tex> явлеяется своим левым выводом, а значит если <tex>C</tex> {{---}} прямой левый вывод из <tex>B</tex> {{---}} он должен следовать за <tex>B</tex> в упорядоченном множестве, если только <tex>B</tex> не является левым выводом из <tex>C</tex>. | ||
+ | |||
+ | ==Пример== | ||
+ | Дана грамматика: | ||
+ | |||
+ | <tex>A \to S\alpha </tex> | ||
+ | |||
+ | <tex>S \to S\beta \mid A\gamma \mid \beta</tex> | ||
+ | |||
+ | Среди правил <tex>A</tex> непосредственной рекурсии нет, поэтому во время первой итерации внешнего цикла ничего не происходит. | ||
+ | Во время второй итерации внешнего цикла правило <tex> S \to A\gamma </tex> переходит в <tex> S \to S\alpha\gamma </tex>. | ||
+ | |||
+ | Грамматика имеет вид | ||
+ | |||
+ | <tex>A \to S\alpha </tex> | ||
+ | |||
+ | <tex>S \to{S}{\beta} \mid {S}{\alpha}{\gamma} </tex> | ||
+ | |||
+ | Устраняем левую рекурсию для <tex>S </tex> | ||
+ | |||
+ | <tex> S \to\beta{S_1} \mid \beta</tex> | ||
+ | |||
+ | <tex> {S_1} \to\beta{S_1} \mid \alpha\gamma{S_1} \mid {\beta} \mid {\alpha}{\gamma} </tex> | ||
+ | |||
+ | == См. также == | ||
+ | * [[Контекстно-свободные_грамматики,_вывод,_лево-_и_правосторонний_вывод,_дерево_разбора|Контекстно-свободные грамматики]] | ||
+ | * [[Нормальная_форма_Хомского|Нормальная форма Хомского]] | ||
+ | * [[Удаление_eps-правил_из_грамматики | Удаления <tex> \varepsilon </tex>-правил из грамматики]] | ||
+ | |||
+ | == Источники информации == | ||
* ''Хопкрофт Д., Мотвани Р., Ульман Д.'' — '''Введение в теорию автоматов, языков и вычислений''', 2-е изд. : Пер. с англ. — Москва, Издательский дом «Вильямс», 2002. — 528 с. : ISBN 5-8459-0261-4 (рус.) | * ''Хопкрофт Д., Мотвани Р., Ульман Д.'' — '''Введение в теорию автоматов, языков и вычислений''', 2-е изд. : Пер. с англ. — Москва, Издательский дом «Вильямс», 2002. — 528 с. : ISBN 5-8459-0261-4 (рус.) | ||
+ | * ''Robert C. Moore'' — [https://aclanthology.org/A00-2033.pdf Removing Left Recursion from Context-Free Grammars ] | ||
+ | |||
+ | [[Категория: Теория формальных языков]] | ||
+ | [[Категория: Контекстно-свободные грамматики]] | ||
+ | [[Категория: Нормальные формы КС-грамматик]] |
Текущая версия на 19:25, 4 сентября 2022
Содержание
Определение: |
Говорят, что контекстно-свободная (КС) грамматика содержит непосредственную левую рекурсию (англ. direct left recursion), если она содержит правило вида . |
Определение: |
Говорят, что КС-грамматика | содержит левую рекурсию (англ. left recursion), если в ней существует вывод вида .
Методы нисходящего разбора не в состоянии работать с леворекурсивными грамматиками. Проблема в том, что продукция вида может применяться бесконечно долго, так и не выработав некий терминальный символ, который можно было бы сравнить со строкой. Поэтому требуется преобразование грамматики, которое бы устранило левую рекурсию.
Устранение непосредственной левой рекурсии
Опишем процедуру, устраняющую все правила вида
, для фиксированного нетерминала .- Запишем все правила вывода из
- — непустая последовательность терминалов и нетерминалов ( );
- — непустая последовательность терминалов и нетерминалов, не начинающаяся с .
в виде:
, где
- Заменим правила вывода из на .
- Создадим новый нетерминал .
Изначально нетерминал
порождает строки вида . В новой грамматике нетерминал порождает , а порождает строки вида . Из этого очевидно, что изначальная грамматика эквивалентна новой.Пример
Есть непосредственная левая рекурсия
. Добавим нетерминал и добавим правила , .Новая грамматика:
В новой грамматике нет непосредственной левой рекурсии, но нетерминал
леворекурсивен, так как естьАлгоритм устранения произвольной левой рекурсии
Воспользуемся алгоритмом удаления . Получим грамматику без -правил -правил для языка .
Упорядочим нетерминалы, например по возрастанию индексов, и будем добиваться того, чтобы не было правил вида
, где . Если данное условие выполняется для всех , то в грамматике нет , а значит не будет левой рекурсии.Пусть
— упорядоченное множество всех нетерминалов.forfor for удалить продукцию for добавить правило устранить непосредственную левую рекурсию для
Если
присутствовал в языке исходной грамматики, добавим новый начальный символ и правила .После
итерации внешнего цикла в любой продукции внешнего цикла в любой продукции вида , должно быть . В результате при следующей итерации внутреннего цикла растет нижний предел всех продукций вида до тех пор, пока не будет достигнуто .После
итерации внешнего цикла в грамматике будут только правила вида , где . Можно заметить, что неравенство становится строгим только после применения алгоритма устранения непосредственной левой рекурсии. При этом добавляются новые нетерминалы. Пусть новый нетерминал. Можно заметить, что нет правила вида , где самый левый нетерминал, а значит новые нетерминалы можно не рассматривать во внешнем цикле. Для строгого поддержания инвариантов цикла можно считать, что созданный на итерации в процессе устранения непосредственной левой рекурсии нетерминал имеет номер (т.е. имеет номер, меньший всех имеющихся на данный момент нетерминалов).На
итерации внешнего цикла все правила вида где заменяются на где . Очевидно, что одна итерация алгоритма не меняет язык, а значит язык получившийся в итоге грамматики совпадает с исходным.Оценка времени работы
Пусть
количество правил для нетерминала . Тогда итерация внешнего цикла будет выполняться за , что меньше чем , значит асимптотика алгоритма .Худший случай
Проблема этого алгоритма в том, что в зависимости от порядка нетерминалов в множестве размер грамматки может получиться экспоненциальным.
Пример грамматики для которой имеет значение порядок нетерминалов
для
Упорядочим множество нетерминалов по возрастанию индексов. Легко заметить, что правила для
будут представлять из себя все двоичные вектора длины , а значит размер грамматики будет экспоненциальным.Порядок выбора нетерминалов
Определение: |
Говорят, что нетерминал | — прямой левый вывод (англ. direct left corner) из , если существует правило вида .
Определение: |
Левый вывод (англ. left corner) — транзитивное, рефлексивное замыкание отношения «быть прямым левым выводом». |
Во внутреннем цикле алгоритма для всех нетерминалов и , таких что и — прямой левый вывод из заменяем все прямые левые выводы из на все выводы из .
Это действие удаляет левую рекурсию только если
— леворекурсивный нетерминал и содержится в выводе (то есть — левый вывод из ,в то время как — левый вывод из ).Перестанем добавлять бесполезные выводы, которые не участвуют в удалении левой рекурсии, упорядочив нетерминалы так: если
и — прямой левый вывод из , то — левый вывод из . Упорядочим их по уменьшению количества различных прямых левых выводов из них.Так как отношение «быть левым выводом» транзитивно,то если
— прямой левый вывод из , то каждый левый вывод из С также будет левым выводом из . А так как отношение «быть левым выводом» рефлексивно, явлеяется своим левым выводом, а значит если — прямой левый вывод из — он должен следовать за в упорядоченном множестве, если только не является левым выводом из .Пример
Дана грамматика:
Среди правил
непосредственной рекурсии нет, поэтому во время первой итерации внешнего цикла ничего не происходит. Во время второй итерации внешнего цикла правило переходит в .Грамматика имеет вид
Устраняем левую рекурсию для
См. также
- Контекстно-свободные грамматики
- Нормальная форма Хомского
- Удаления -правил из грамматики
Источники информации
- Хопкрофт Д., Мотвани Р., Ульман Д. — Введение в теорию автоматов, языков и вычислений, 2-е изд. : Пер. с англ. — Москва, Издательский дом «Вильямс», 2002. — 528 с. : ISBN 5-8459-0261-4 (рус.)
- Robert C. Moore — Removing Left Recursion from Context-Free Grammars