Дана грамматика, порождающая язык арифметических выражений с приоритетами; объясните, как устранить левую рекурсию и почему это важно для построения LL‑парсеров
Приведу конкретно и коротко. Исходная (типичная) грамматика с леворекурсией: E→E+T∣E−T∣T
E \to E + T \mid E - T \mid T E→E+T∣E−T∣TT→T∗F∣T/F∣F
T \to T * F \mid T / F \mid F T→T∗F∣T/F∣FF→(E)∣num
F \to (E) \mid \text{num} F→(E)∣num Почему левую рекурсию надо убирать: - LL‑парсер (топ‑даун, рекурсивный спуск или таблица LL(1)) при попытке применить правило E→E+TE \to E + TE→E+T начнёт рекурсивно разбирать EEE снова и получит бесконечную рекурсию — разбор не завершится. - Левые рекурсивные правила приводят к конфликтам FIRST/FIRST и делают невозможным построение предиктивной (LL(1)) таблицы. Как устраняют непосредственную левую рекурсию (общий приём): для всех правил вида A→Aα1∣Aα2∣⋯∣β1∣β2∣…
A \to A\alpha_1 \mid A\alpha_2 \mid \dots \mid \beta_1 \mid \beta_2 \mid \dots A→Aα1∣Aα2∣⋯∣β1∣β2∣…
где каждая βi\beta_iβi не начинается с AAA, переписывают как A→β1A′∣β2A′∣…
A \to \beta_1 A' \mid \beta_2 A' \mid \dots A→β1A′∣β2A′∣…A′→α1A′∣α2A′∣⋯∣ε
A' \to \alpha_1 A' \mid \alpha_2 A' \mid \dots \mid \varepsilon A′→α1A′∣α2A′∣⋯∣ε Применим к нашей грамматике (сохранится приоритет операций): E→T E′
E \to T\ E' E→TE′E′→+ T E′∣− T E′∣ε
E' \to +\ T\ E' \mid -\ T\ E' \mid \varepsilon E′→+TE′∣−TE′∣εT→F T′
T \to F\ T' T→FT′T′→∗ F T′∣/ F T′∣ε
T' \to *\ F\ T' \mid /\ F\ T' \mid \varepsilon T′→∗FT′∣/FT′∣εF→(E)∣num
F \to (E) \mid \text{num} F→(E)∣num Это устраняет левую рекурсию и одновременно сохраняет приоритеты: TTT (умножение/деление) вложено в EEE (сложение/вычитание), поэтому умножение имеет более высокий приоритет. Как устраняют косвенную (непосредственную) левую рекурсию: - Упорядочить нетерминалы A1,…,AnA_1,\dots,A_nA1,…,An. - Для i от 1 до n: для каждого правила Ai→AjγA_i \to A_j \gammaAi→Ajγ с j<ij<ij<i подставить все правые части AjA_jAj; затем убрать непосредственную левую рекурсию для AiA_iAi стандартным приёмом. Коротко о значении для LL‑парсеров: - Устранение левой рекурсии обеспечивает конечность рекурсивного спуска (нет бесконечной левой рекурсии). - Тогда можно вычислить корректные множества FIRST/FOLLOW и построить единственную предиктивную продукцию — парсер становится детерминированным и эффективным (LL(1) или расширения LL(k)).
Исходная (типичная) грамматика с леворекурсией:
E→E+T∣E−T∣T E \to E + T \mid E - T \mid T
E→E+T∣E−T∣T T→T∗F∣T/F∣F T \to T * F \mid T / F \mid F
T→T∗F∣T/F∣F F→(E)∣num F \to (E) \mid \text{num}
F→(E)∣num
Почему левую рекурсию надо убирать:
- LL‑парсер (топ‑даун, рекурсивный спуск или таблица LL(1)) при попытке применить правило E→E+TE \to E + TE→E+T начнёт рекурсивно разбирать EEE снова и получит бесконечную рекурсию — разбор не завершится.
- Левые рекурсивные правила приводят к конфликтам FIRST/FIRST и делают невозможным построение предиктивной (LL(1)) таблицы.
Как устраняют непосредственную левую рекурсию (общий приём):
для всех правил вида
A→Aα1∣Aα2∣⋯∣β1∣β2∣… A \to A\alpha_1 \mid A\alpha_2 \mid \dots \mid \beta_1 \mid \beta_2 \mid \dots
A→Aα1 ∣Aα2 ∣⋯∣β1 ∣β2 ∣… где каждая βi\beta_iβi не начинается с AAA, переписывают как
A→β1A′∣β2A′∣… A \to \beta_1 A' \mid \beta_2 A' \mid \dots
A→β1 A′∣β2 A′∣… A′→α1A′∣α2A′∣⋯∣ε A' \to \alpha_1 A' \mid \alpha_2 A' \mid \dots \mid \varepsilon
A′→α1 A′∣α2 A′∣⋯∣ε
Применим к нашей грамматике (сохранится приоритет операций):
E→T E′ E \to T\ E'
E→T E′ E′→+ T E′∣− T E′∣ε E' \to +\ T\ E' \mid -\ T\ E' \mid \varepsilon
E′→+ T E′∣− T E′∣ε T→F T′ T \to F\ T'
T→F T′ T′→∗ F T′∣/ F T′∣ε T' \to *\ F\ T' \mid /\ F\ T' \mid \varepsilon
T′→∗ F T′∣/ F T′∣ε F→(E)∣num F \to (E) \mid \text{num}
F→(E)∣num
Это устраняет левую рекурсию и одновременно сохраняет приоритеты: TTT (умножение/деление) вложено в EEE (сложение/вычитание), поэтому умножение имеет более высокий приоритет.
Как устраняют косвенную (непосредственную) левую рекурсию:
- Упорядочить нетерминалы A1,…,AnA_1,\dots,A_nA1 ,…,An .
- Для i от 1 до n: для каждого правила Ai→AjγA_i \to A_j \gammaAi →Aj γ с j<ij<ij<i подставить все правые части AjA_jAj ; затем убрать непосредственную левую рекурсию для AiA_iAi стандартным приёмом.
Коротко о значении для LL‑парсеров:
- Устранение левой рекурсии обеспечивает конечность рекурсивного спуска (нет бесконечной левой рекурсии).
- Тогда можно вычислить корректные множества FIRST/FOLLOW и построить единственную предиктивную продукцию — парсер становится детерминированным и эффективным (LL(1) или расширения LL(k)).