Дан формальный контекстно-свободный грамматический фрагмент, генерирующий арифметические выражения с рекурсией влево; предложите метод преобразования грамматики для построения эффективного LL(1)-парсера, проанализируйте возможную неоднозначность и опишите, как построить AST, сохраняющий информацию о приоритете операций

3 Ноя в 19:21
4 +1
0
Ответы
1
Начнём с проблемы и затем предложу конкретное преобразование, анализ неоднозначности и способ построения AST.
1) Проблема (пример исходной грамматики)
G0:E→E+E∣E∗E∣(E)∣idG_0:\quad E \to E + E \mid E * E \mid (E) \mid idG0 :EE+EEE(E)id Эта грамматика леворекурсивна и неоднозначна: при выражении a+b∗ca+b*ca+bc нет правила приоритета, а при a−b−ca-b-cabc невозможно однозначно задать ассоциативность.
2) Преобразование для LL(1) — убрать левую рекурсию и ввести уровни приоритета
Обычно вводят нетерминалы для уровней приоритета (E — сумма, T — произведение, F — фактор). Без левой рекурсии:
E→T E′E′→+ T E′∣− T E′∣εT→F T′T′→∗ F T′∣/ F T′∣εF→(E)∣id∣number∣−F(унарный минус) \begin{aligned}
E &\to T\ E'\\
E' &\to +\ T\ E' \mid -\ T\ E' \mid \varepsilon\\
T &\to F\ T'\\
T' &\to *\ F\ T' \mid /\ F\ T' \mid \varepsilon\\
F &\to (E)\mid id\mid number\mid -F\quad(\text{унарный минус})
\end{aligned}
EETTF T E+ T E T EεF T F T/ F Tε(E)idnumberF(унарный минус)
Этот приём устраняет левую рекурсию и кодирует приоритет ∗/ * / / выше чем +− + - +. Для правой ассоциативных операторов (например, возведение в степень \(^\)) используют правую рекурсию:
P→B P∣B P \to B\ ^\ P \mid B
PB PB
(чтобы получить правую ассоциативность).
3) Анализ неоднозначности и LL(1)-корректность
- Неоднозначность исходной грамматики снимается: разделение на уровни приоритета однозначно определяет дерево (если вы однозначно задаёте ассоциативность: E' реализует левую ассоциативность через свёртку слева).
- Для LL(1) нужно, чтобы альтернативы имели раздельные FIRST (и при наличии пустой альтернативы — FIRST ∩ FOLLOW = ∅). В приведённой грамматике FIRST-альтернативы для E′E'E{+,−,ε}\{+,-,\varepsilon\}{+,,ε} и для T′T'T{∗,/,ε}\{*,/,\varepsilon\}{,/,ε}. Конфликтов нет. Унарный минус в FFF распознаётся на позициях начала фактора, а бинарный минус — в E′E'E после TTT, поэтому они не конфликтуют.
- При добавлении новых операторов нужно проверять FIRST/FOLLOW для конфликтов и при необходимости делать левое факторирование.
4) Построение AST, сохраняющего приоритет и ассоциативность
- Структура AST: листья — числа/идентификаторы; узлы — бинарные операторы с двумя поддеревьями, унарные — один ребёнок.
- Рекомендуемый приём реализации для левоассоциативных уровней — итеративная свёртка (точно соответствует LL(1)-реализации с правой рекурсией E′E'E), псевдокод для parseE:
parseE():
left = parseT()
while nextToken in {+, -}:
op = nextToken; consume(op)
right = parseT()
left = new BinOp(op, left, right)
return left
parseT() аналогично для {*, /}:
left = parseF()
while nextToken in {*, /}:
op = nextToken; consume(op)
right = parseF()
left = new BinOp(op, left, right)
return left
parseF():
if nextToken == '-':
consume('-')
child = parseF()
return new UnaryOp('-', child)
if nextToken == '(':
consume('(')
node = parseE()
consume(')')
return node
if nextToken is number or id:
return new Leaf(token)
- Этот алгоритм создаёт левую ассоциативность естественным образом: для a−b−ca-b-cabc получится дерево BinOp(′−′, BinOp(′−′, a,b),c)BinOp('-',\,BinOp('-',\,a, b), c)BinOp(,BinOp(,a,b),c).
- Для правой ассоциативных операторов (например, \(a^b^c\)) парсер-функция должна использовать рекурсивный разбор правой рекурсии:
parsePower():
left = parseBase()
if nextToken == '^':
consume('^')
right = parsePower()
return new BinOp('^', left, right)
return left
5) Дополнительные замечания
- Если нужны разные приоритеты/ассоциативности, добавьте соответствующие уровни нетерминалов; это автоматически сохранит приоритет в форме дерева (глубина отражает приоритет).
- Перед реализацией проверьте FIRST/FOLLOW для каждой альтернативы чтобы гарантировать LL(1). При конфликте примените левое факторирование или перестройте грамматику.
- Семантические действия (или построение узлов в AST) можно привязать к каждой продукции в генераторе парсера (например, ANTLR, hand-written recursive-descent).
Конец.
3 Ноя в 20:36
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир