Дана контекстно-свободная грамматика: S -> S + S | S * S | a | (S) ; покажите, что грамматика неоднозначна, приведите пример строки с двумя различными деревьями разбора, постройте неамбигуализированную грамматику, явно задающую приоритеты и ассоциативности (+ ниже * и левосторонняя ассоциативность), и опишите алгоритм синтаксического анализатора (LR/LL) для неё
Грамматика исходнаяисходнаяисходная
S -> S + S | S * S | a | SSS
Строка-пример: a + a * a
Два различных дерева разбора.
Вариант A плюснаверхнемуровне:a+(a∗a)плюс на верхнем уровне: a + (a * a)плюснаверхнемуровне:a+(a∗a):
S /|\ S + S | /|\ a S * S | | a a
Соответствующий вывод: S ⇒ S + S ⇒ a + S ⇒ a + S<em>SS <em> SS<em>S ⇒ a + a</em>aa </em> aa</em>a
Вариант B звёздочканаверхнемуровне:(a+a)∗aзвёздочка на верхнем уровне: (a + a) * aзвёздочканаверхнемуровне:(a+a)∗a:
S /|\ S * S /|\ | S + S a | | | a a a
Вывод: S ⇒ S S ⇒ S+SS + SS+S S ⇒ a+aa + aa+a * a
Поскольку получено два разных дерева разбора для одной и той же строки, грамматика неоднозначна.
2) Неамбигуализированная грамматика с приоритетами и левой ассоциативностью
Требование: * имеет более высокий приоритет, + — ниже; обе операции левосторонние.
Классическое неамбигуизированное правило:
E -> E + T | T T -> T * F | F F -> a | E E E
Здесь:
T соответствует произведениям болеевысокийприоритетболее высокий приоритетболеевысокийприоритет,E — суммам нижнийприоритетнижний приоритетнижнийприоритет,левая рекурсия E -> E + T и T -> T * F задаёт левую ассоциативность.
Эквивалентная форма, удобная для LL111убираемлевуюрекурсиюубираем левую рекурсиюубираемлевуюрекурсию:
E -> T E' E' -> + T E' | ε T -> F T' T' -> * F T' | ε F -> a | E E E
3) Почему это однозначно и задаёт заданные приоритеты/ассоциативности
В этой грамматике операции образуются в поддеревьях T, которые находятся ниже по иерархии, чем E, поэтому связывается сильнее, чем +. Для строки a + a a единственный допустимый разбор по этой грамматике — E -> T E' с T->F и правой частью T' дающей aaaa — то есть a + a∗aa * aa∗a.Левая рекурсия E -> E + T гарантирует, что последовательность a + a + a разобьётся как a+aa + aa+a + a леваяассоциативностьлевая ассоциативностьлеваяассоциативность, а не как a + a+aa + aa+a.
4) Описание алгоритма синтаксического анализатора
Вариант A — LL111, рекурсивный нисходящий предиктивный парсер
Используем LL111-форму E,E′,T,T′,FE, E', T, T', FE,E′,T,T′,F.Построить таблицу предсказания по FIRST/FOLLOW (коротко: FIRST(+ T E') = '+', FIRST(T)= {'a','('}, FIRST(E') = {'+','ε'}, и т.д.). Эта грамматика является LL(1).Реализовать функции (псевдокод):
function parseE(): parseT() parseEprime()
function parseEprime(): if current_token == '+': consume('+') parseT() parseEprime() else // ε: вернуть
function parseT(): parseF() parseTprime()
function parseTprime(): if current_token == '': consume('') parseF() parseTprime() else // ε: вернуть
function parseF(): if current_token == 'a': consume('a') else if current_token == '(': consume('(') parseE() expect(')') // consume ')' или ошибка else error()
Этот парсер читается слева направо, использует прогноз по одному символу (LL(1)). Постфикс вызовов гарантирует, что парсер отражает приоритеты: внутри E мы сначала распознаём T (вся цепочка * и F), затем уже разрешаем +.
Вариант B — LR (shift-reduce, LALR(1)/LR(1))
Можно строить стандартный LR(1)/LALR(1)-парсер для неамбигуализированной грамматики (E->E+T|T, T->T*F|F, F->a|(E)).Алгоритм (общая схема): Построить множество состояний автомата LR(1) (канонические множества LR(1)-шпилек/пунктов) и таблицы action/goto.Парсер хранит стек состояний; на вход подаётся поток токенов.На каждом шаге берётся текущее состояние s (вершина стека) и текущий токен a:если action[s,a] = "shift t": поместить токен и состояние t в стек, прочитать следующий токен;если action[s,a] = "reduce A -> β": снять 2*|β| элементов со стека (символы и состояния), найти новое состояние s' = top(stack), поместить A и goto[s',A] в стек;если action[s,a] = "accept": успешно завершить;иначе — синтаксическая ошибка.Для нашей грамматики таблицы action/goto будут однозначными (без конфликта shift/reduce) — она принадлежит к классу LR(1) (и обычно к LALR(1)), поэтому парсер корректно обрабатывает приоритеты, заданные структурой грамматики.
Пример шагов (LR shift/reduce) для входа a + a * a (схематично):
shift 'a' ; reduce F->a ; reduce T->F ; (в зависимости от состояний можно сразу редьюсить) ; далее shift '+' ; shift 'a' ; shift '' ; shift 'a' ; reduce F->a ; reduce T->T F ; reduce E->E + T ; accept. Точные состояния и действия зависят от построенной таблицы.
Резюме:
Исходная грамматика неоднозначна (пример a+a*a даёт два дерева).Неамбигуизированная версия с требуемыми приоритетами и левой ассоциативностью — стандартная E/T/F-иерархия: E -> E + T | T ; T -> T F | F ; F -> a | ( E ) или LL(1)-эквивалент: E->T E' ; E'-> + T E' | ε ; T->F T' ; T'-> F T' | ε ; F-> a | (E)Для неё можно реализовать либо предиктивный LL(1)-парсер (рекурсивный нисходящий), либо стандартный LR(1)/LALR(1) shift-reduce парсер; оба разбора однозначны и соблюдают требуемые приоритеты и ассоциативность.
1) Демонстрация неоднозначности
Грамматика исходнаяисходнаяисходная S -> S + S | S * S | a | SSS
Строка-пример: a + a * a
Два различных дерева разбора.
Вариант A плюснаверхнемуровне:a+(a∗a)плюс на верхнем уровне: a + (a * a)плюснаверхнемуровне:a+(a∗a):
S/|\
S + S
| /|\
a S * S
| |
a a
Соответствующий вывод:
S ⇒ S + S ⇒ a + S ⇒ a + S<em>SS <em> SS<em>S ⇒ a + a</em>aa </em> aa</em>a
Вариант B звёздочканаверхнемуровне:(a+a)∗aзвёздочка на верхнем уровне: (a + a) * aзвёздочканаверхнемуровне:(a+a)∗a:
S/|\
S * S
/|\ |
S + S a
| | |
a a a
Вывод:
S ⇒ S S ⇒ S+SS + SS+S S ⇒ a+aa + aa+a * a
Поскольку получено два разных дерева разбора для одной и той же строки, грамматика неоднозначна.
2) Неамбигуализированная грамматика с приоритетами и левой ассоциативностью
Требование: * имеет более высокий приоритет, + — ниже; обе операции левосторонние.
Классическое неамбигуизированное правило:
E -> E + T | T
T -> T * F | F
F -> a | E E E
Здесь:
T соответствует произведениям болеевысокийприоритетболее высокий приоритетболеевысокийприоритет,E — суммам нижнийприоритетнижний приоритетнижнийприоритет,левая рекурсия E -> E + T и T -> T * F задаёт левую ассоциативность.Эквивалентная форма, удобная для LL111 убираемлевуюрекурсиюубираем левую рекурсиюубираемлевуюрекурсию:
E -> T E'
E' -> + T E' | ε
T -> F T'
T' -> * F T' | ε
F -> a | E E E
3) Почему это однозначно и задаёт заданные приоритеты/ассоциативности
В этой грамматике операции образуются в поддеревьях T, которые находятся ниже по иерархии, чем E, поэтому связывается сильнее, чем +. Для строки a + a a единственный допустимый разбор по этой грамматике — E -> T E' с T->F и правой частью T' дающей aaaa — то есть a + a∗aa * aa∗a.Левая рекурсия E -> E + T гарантирует, что последовательность a + a + a разобьётся как a+aa + aa+a + a леваяассоциативностьлевая ассоциативностьлеваяассоциативность, а не как a + a+aa + aa+a.4) Описание алгоритма синтаксического анализатора
Вариант A — LL111, рекурсивный нисходящий предиктивный парсер
Используем LL111-форму E,E′,T,T′,FE, E', T, T', FE,E′,T,T′,F.Построить таблицу предсказания по FIRST/FOLLOW (коротко: FIRST(+ T E') = '+', FIRST(T)= {'a','('}, FIRST(E') = {'+','ε'}, и т.д.). Эта грамматика является LL(1).Реализовать функции (псевдокод):function parseE():
parseT()
parseEprime()
function parseEprime():
if current_token == '+':
consume('+')
parseT()
parseEprime()
else
// ε: вернуть
function parseT():
parseF()
parseTprime()
function parseTprime():
if current_token == '':
consume('')
parseF()
parseTprime()
else
// ε: вернуть
function parseF():
Этот парсер читается слева направо, использует прогноз по одному символу (LL(1)). Постфикс вызовов гарантирует, что парсер отражает приоритеты: внутри E мы сначала распознаём T (вся цепочка * и F), затем уже разрешаем +.if current_token == 'a':
consume('a')
else if current_token == '(':
consume('(')
parseE()
expect(')') // consume ')' или ошибка
else
error()
Вариант B — LR (shift-reduce, LALR(1)/LR(1))
Можно строить стандартный LR(1)/LALR(1)-парсер для неамбигуализированной грамматики (E->E+T|T, T->T*F|F, F->a|(E)).Алгоритм (общая схема):Построить множество состояний автомата LR(1) (канонические множества LR(1)-шпилек/пунктов) и таблицы action/goto.Парсер хранит стек состояний; на вход подаётся поток токенов.На каждом шаге берётся текущее состояние s (вершина стека) и текущий токен a:если action[s,a] = "shift t": поместить токен и состояние t в стек, прочитать следующий токен;если action[s,a] = "reduce A -> β": снять 2*|β| элементов со стека (символы и состояния), найти новое состояние s' = top(stack), поместить A и goto[s',A] в стек;если action[s,a] = "accept": успешно завершить;иначе — синтаксическая ошибка.Для нашей грамматики таблицы action/goto будут однозначными (без конфликта shift/reduce) — она принадлежит к классу LR(1) (и обычно к LALR(1)), поэтому парсер корректно обрабатывает приоритеты, заданные структурой грамматики.
Пример шагов (LR shift/reduce) для входа a + a * a (схематично):
shift 'a' ; reduce F->a ; reduce T->F ; (в зависимости от состояний можно сразу редьюсить) ; далее shift '+' ; shift 'a' ; shift '' ; shift 'a' ; reduce F->a ; reduce T->T F ; reduce E->E + T ; accept.Точные состояния и действия зависят от построенной таблицы.
Резюме:
Исходная грамматика неоднозначна (пример a+a*a даёт два дерева).Неамбигуизированная версия с требуемыми приоритетами и левой ассоциативностью — стандартная E/T/F-иерархия:E -> E + T | T ; T -> T F | F ; F -> a | ( E )
или LL(1)-эквивалент:
E->T E' ; E'-> + T E' | ε ; T->F T' ; T'-> F T' | ε ; F-> a | (E)Для неё можно реализовать либо предиктивный LL(1)-парсер (рекурсивный нисходящий), либо стандартный LR(1)/LALR(1) shift-reduce парсер; оба разбора однозначны и соблюдают требуемые приоритеты и ассоциативность.