Приведённая контекстно‑свободная грамматика для арифметических выражений S -> S + S | S * S | (S) | id неоднозначна и леворекурсивна; преобразуйте её в эквивалентную грамматику, пригодную для построения LL(1)‑парсера и для LALR‑parserа, поясните, как вы устраняете неоднозначность при соблюдении приоритетов операторов и ассоциативности?
Коротко: нужно убрать левую рекурсию и явно заявить уровни приоритетов и ассоциативность. Привожу устойчивую LL(1)‑версию и вариант для LALR (через либо переработанную грамматику, либо декларации приоритетов). 1) LL(1)‑грамматика (без левой рекурсии, с приоритетом ′∗′>′+′'*' > '+'′∗′>′+′ и левой ассоциативностью): E→T E′
E \to T\ E' E→TE′E′→+ T E′ ∣ ε
E' \to +\ T\ E'\ \mid\ \varepsilon E′→+TE′∣εT→F T′
T \to F\ T' T→FT′T′→∗ F T′ ∣ ε
T' \to *\ F\ T'\ \mid\ \varepsilon T′→∗FT′∣εF→( E ) ∣ id
F \to (\ E\ )\ \mid\ id F→(E)∣id Пояснения: - Уровни соответствуют нетерминалам: EEE — выражение с суммой, TTT — терм (умножение), FFF — фактор. Это гарантирует, что операторы ∗*∗ связываются сильнее, чем +++, потому что в EEE сначала полностью распознаётся TTT (включая все ∗*∗), и только затем возможны «+». - Ассоциативность «влево» реализуется хвостовой рекурсией E′→+ T E′E' \to +\ T\ E'E′→+TE′ и T′→∗ F T′T' \to *\ F\ T'T′→∗FT′: при построении AST принято сворачивать последовательность справа налево в левую цепочку, что даёт семантику (a+b)+c(a+b)+c(a+b)+c и (a∗b)∗c(a*b)*c(a∗b)∗c. - Грамматика не содержит левой рекурсии и удовлетворяет условиям LL(1): для каждого нетерминала множества FIRST альтернатив и FOLLOW при ε\varepsilonε являются разделимыми. 2) Вариант для LALR (две возможности) a) Использовать ту же грамматику, что и для LL(1). Она подходит и для LALR(1) парсеров (нет неоднозначности). b) Если хотите оставить исходную компактную форму (удобно в Yacc/Bison), разрешить неоднозначности с помощью приоритетов и ассоциативности: Пример в стиле Yacc/Bison (псевдосинтаксис; порядок деклараций определяет приоритет — более поздние выше): %left '+' %left '*' %% S : S '+' S | S '*' S | '(' S ')' | id ; Пояснения: - Директивы `%left` устанавливают левую ассоциативность для указанных токенов. - Порядок деклараций делает `*` более приоритетным, чем `+` (в Bison/Yacc позднее объявленные токены имеют больший приоритет). - При возникновении конфликтов shift/reduce парсер использует эти декларации: при конфликте между `+` и `*` выберется соответствующее разрешение по приоритету; при конфликте с одинаковым приоритетом и `%left` — произойдёт reduce (что даёт левую ассоциативность). Итого: - Для LL(1) и детерминированного парсинга безопасно применять приведённую «разложенную» грамматику E,T,FE,T,FE,T,F. - Для генераторов LALR можно либо использовать ту же неамбициозную грамматику, либо оставить компактную и разрешать конфликты через объявления приоритета/ассоциативности.
1) LL(1)‑грамматика (без левой рекурсии, с приоритетом ′∗′>′+′'*' > '+'′∗′>′+′ и левой ассоциативностью):
E→T E′ E \to T\ E'
E→T E′ E′→+ T E′ ∣ ε E' \to +\ T\ E'\ \mid\ \varepsilon
E′→+ T E′ ∣ ε T→F T′ T \to F\ T'
T→F T′ T′→∗ F T′ ∣ ε T' \to *\ F\ T'\ \mid\ \varepsilon
T′→∗ F T′ ∣ ε F→( E ) ∣ id F \to (\ E\ )\ \mid\ id
F→( E ) ∣ id
Пояснения:
- Уровни соответствуют нетерминалам: EEE — выражение с суммой, TTT — терм (умножение), FFF — фактор. Это гарантирует, что операторы ∗*∗ связываются сильнее, чем +++, потому что в EEE сначала полностью распознаётся TTT (включая все ∗*∗), и только затем возможны «+».
- Ассоциативность «влево» реализуется хвостовой рекурсией E′→+ T E′E' \to +\ T\ E'E′→+ T E′ и T′→∗ F T′T' \to *\ F\ T'T′→∗ F T′: при построении AST принято сворачивать последовательность справа налево в левую цепочку, что даёт семантику (a+b)+c(a+b)+c(a+b)+c и (a∗b)∗c(a*b)*c(a∗b)∗c.
- Грамматика не содержит левой рекурсии и удовлетворяет условиям LL(1): для каждого нетерминала множества FIRST альтернатив и FOLLOW при ε\varepsilonε являются разделимыми.
2) Вариант для LALR (две возможности)
a) Использовать ту же грамматику, что и для LL(1). Она подходит и для LALR(1) парсеров (нет неоднозначности).
b) Если хотите оставить исходную компактную форму (удобно в Yacc/Bison), разрешить неоднозначности с помощью приоритетов и ассоциативности:
Пример в стиле Yacc/Bison (псевдосинтаксис; порядок деклараций определяет приоритет — более поздние выше):
%left '+'
%left '*'
%%
S : S '+' S
| S '*' S
| '(' S ')'
| id
;
Пояснения:
- Директивы `%left` устанавливают левую ассоциативность для указанных токенов.
- Порядок деклараций делает `*` более приоритетным, чем `+` (в Bison/Yacc позднее объявленные токены имеют больший приоритет).
- При возникновении конфликтов shift/reduce парсер использует эти декларации: при конфликте между `+` и `*` выберется соответствующее разрешение по приоритету; при конфликте с одинаковым приоритетом и `%left` — произойдёт reduce (что даёт левую ассоциативность).
Итого:
- Для LL(1) и детерминированного парсинга безопасно применять приведённую «разложенную» грамматику E,T,FE,T,FE,T,F.
- Для генераторов LALR можно либо использовать ту же неамбициозную грамматику, либо оставить компактную и разрешать конфликты через объявления приоритета/ассоциативности.