Предложите и обоснуйте формальную грамматику (BNF/EBNF) для простого языка выражений с приоритетами операций и скобками; объясните, как лексический анализ и парсинг взаимодействуют и какие ошибки могут возникнуть при неоднозначностях грамматики

24 Ноя в 09:34
2 +2
0
Ответы
1
Предложу компактную EBNF-версию грамматики для выражений с приоритетами и скобками, обоснование, и кратко — как лексер и парсер взаимодействуют и какие ошибки возможны при неоднозначностях.
Грамматика (EBNF, числа и формулы в KaTeX):
Expr::=AddExprExpr ::= AddExprExpr::=AddExpr
AddExpr::=MulExpr{(′+′∣′−′) MulExpr}AddExpr ::= MulExpr \{ ('+' \mid '-') \; MulExpr \}AddExpr::=MulExpr{(+)MulExpr} — (низкий приоритет, левосторонняя ассоциативность)
MulExpr::=PowExpr{(′∗′∣′/′) PowExpr}MulExpr ::= PowExpr \{ ('*' \mid '/') \; PowExpr \}MulExpr::=PowExpr{(/)PowExpr} — (средний приоритет, левосторонняя ассоциативность)
\(PowExpr ::= UnaryExpr [ '^' \; PowExpr ]\) — (высокий приоритет, правосторонняя ассоциативность)
UnaryExpr::=(′+′∣′−′) UnaryExpr∣PrimaryUnaryExpr ::= ('+' \mid '-') \; UnaryExpr \mid PrimaryUnaryExpr::=(+)UnaryExprPrimary — (унарные операции выше умножения/сложения)
Primary::=NUMBER∣IDENTIFIER∣′(′ Expr ′)′Primary ::= NUMBER \mid IDENTIFIER \mid '(' \; Expr \; ')'Primary::=NUMBERIDENTIFIER(Expr)
Терминалы: NUMBERNUMBERNUMBER, IDENTIFIERIDENTIFIERIDENTIFIER, '+', '-', '*', '/', '^', '(', ')'.
(В варианте для LR-парсеров можно записать явную левую рекурсию: AddExpr::=AddExpr(′+′∣′−′)MulExpr∣MulExprAddExpr ::= AddExpr ('+'|'-') MulExpr \mid MulExprAddExpr::=AddExpr(+)MulExprMulExpr, и аналогично для MulExprMulExprMulExpr.)
Обоснование:
- Уровни нетерминалов (AddExpr, MulExpr, PowExpr) задают приоритеты: операции в нижних уровнях не «поглощают» более приоритетные, т.е. ∗ * и / // сильнее +++ и −-.
- Правосторонняя рекурсия для возведения в степень (PowExprPowExprPowExpr) обеспечивает правильную правую ассоциативность: \(a^b^c\) парсится как a(bc)a^{(b^c)}a(bc).
- Унарные операции обработаны отдельно, чтобы `" - 2 * 3"` понималось как (−(2))∗3(-(2)) * 3((2))3 или −(2∗3)-(2*3)(23) в зависимости от желаемой семантики; предложенная грамматика делает унарный минус выше умножения (т.е. −x2-x^2x2 парсится как −(x2)-(x^2)(x2)) — при необходимости можно изменить порядок.
Взаимодействие лексера и парсера:
- Лексер разбивает вход на токены: NUMBERNUMBERNUMBER (например, последовательность цифр и точка), IDENTIFIERIDENTIFIERIDENTIFIER (буквы/цифры/подчеркивания), символы-операторы и скобки; пропускает пробелы и комментарии; сообщает парсеру поток токенов с позициями (номер строки/столбца) и значениями (лексемы).
- Парсер читает токены и строит дерево разбора (AST) согласно грамматике. Лексер должен давать «чистые» токены: например, знак минус обычно возвращается как отдельный токен '-', а не включается в NUMBERNUMBERNUMBER (чтобы у парсера был выбор распознать унарный минус или бинарный).
- Для корректной диагностики ошибок лексер передаёт позиции токенов; парсер может запросить следующий токен (lookahead) или несколько для принятия решений (LL(k)).
Типичные ошибки и неоднозначности:
- Лексические ошибки: неизвестный символ, некорректный литерал числа (например, несколько точек), неопознанная последовательность — генерация ошибки лексера.
- Синтаксические ошибки: неожиданный токен, неполное выражение, незакрытая скобка — парсер сообщает позицию и ожидаемые токены.
- Неоднозначная грамматика приводит к конфликтам у генераторов парсеров:
- Shift/reduce — когда парсер не знает, сделать ли сдвиг (прочитать ещё токен) или редьюс (свернуть правило). Часто возникает при сочетаниях префиксных/постфиксных операторов или при конструкции "dangling else".
- Reduce/reduce — когда два разных сокращения возможны в одной точке; это «худшая» неоднозначность.
- Примеры неоднозначностей:
- Если не указать, что '^' правосторонний, строка \(a^b^c\) может иметь два дерева: (ab)c(a^b)^c(ab)c или a(bc)a^{(b^c)}a(bc).
- Общая неоднозначная грамматика: S::=S S∣′a′S ::= S\;S \mid 'a'S::=SSa даёт несколько деревьев для строки ′aaa′'aaa'aaa.
- Решения:
- Переписать грамматику по уровням (как выше) для явного задание приоритетов и ассоциативности.
- Для генераторов Yacc/Bison — использовать директивы precedence/associativity (например, %left, %right).
- Для LL-парсеров — устранить левую рекурсию и использовать EBNF-формы или парсер Pratt/precedence-climbing.
- Для ошибок — реализовать восстановление после ошибки (panic mode, синтаксическое предсказание) и выдачу информативных сообщений с позициями.
Краткая рекомендация: для простого выраженческого языка — использовать уровневую грамматику (как приведено), лексер отдаёт токены без знака в числах, а парсер строит AST; для генераторов парсеров использовать либо EBNF для LL-парсеров, либо левую рекурсию для LR, и явно задавать ассоциативность/приоритет для операторов, чтобы избежать конфликтов.
24 Ноя в 10:27
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир