Синтаксический анализ (СА)
Синтаксический анализ по произвольной КС-грамматике
Применимость методов синтаксического анализа
Метод рекурсивного спуска (РС-метод)
Метод рекурсивного спуска 1
Метод рекурсивного спуска 2
Метод рекурсивного спуска 3
Реализация РС-метода для грамматики G_рс.
Достаточное условие применимости РС-метода
О применимости РС-метода
Критерий применимости РС-метода
Критерий применимости РС-метода. Примеры
Итерационные правила в КС-грамматиках
Итерационные правила в КС-грамматиках. Пример
Преобразования грамматик
Преобразования грамматик
Преобразования грамматик
Преобразования грамматик
Пример преобразования грамматики
СА для М-языка (1)
СА для М-языка (2)
Обработчик ошибок СА
Другие методы распознавания КС-языков
Другие методы распознавания КС-языков
Семантический анализ
Вставка действий в КС-грамматику
Пример 1.
Пример 2.
СемА для М-языка
Обработка описаний
307.00K
Category: programmingprogramming

Синтаксический анализ формальных языков

1.

Синтаксический анализ
формальных языков
1

2. Синтаксический анализ (СА)

Задачи синтаксического анализа
установить, имеет ли цепочка лексем структуру, заданную
синтаксисом языка, т.е. решить задачу разбора по заданной
грамматике,
зафиксировать эту структуру.
Для описания синтаксиса языков программирования
используются КС-грамматики, правила которых имеют вид
A → ,
где A N, (T N)*.
Для некоторых подклассов КС-грамматик существуют
достаточно эффективные алгоритмы разбора.
2

3. Синтаксический анализ по произвольной КС-грамматике

Некоторые общие алгоритмы анализа по КС-грамматикам:
- синтаксический анализ с возвратами (время работы
экспоненциально зависит от длины цепочки);
- алгоритм Кока-Янгера-Касами
(для разбора цепочек длины n требуется время cn3);
- алгоритм Эрли
(для разбора цепочек длины n требуется время cn3 ).
Эти (и подобные им по времени работы) алгоритмы
практически неприемлемы.
Алгоритмы анализа, расходующие на обработку входной
цепочки линейное время, применимы только к некоторым
подклассам КС-грамматик.
3

4. Применимость методов синтаксического анализа

Каждый метод синтаксического анализа основан на своей
технике построения дерева вывода и предполагает свой способ
построения по грамматике программы-анализатора, которая будет
осуществлять разбор цепочек.
Корректный анализатор завершает свою работу для любой
входной цепочки и выдает верный ответ о принадлежности
цепочки языку.
Анализатор некорректен, если:
не распознает хотя бы одну цепочку, принадлежащую языку;
распознает хотя бы одну цепочку, языку не принадлежащую;
зацикливается на какой-либо цепочке.
Метод анализа применим к данной грамматике, если
анализатор, построенный в соответствии с этим методом,
корректен и строит все возможные выводы цепочек в данной
грамматике.
4

5. Метод рекурсивного спуска (РС-метод)

Пусть дана грамматика
P: S → AB
A → a | cA
B → bA
G_рс = ({ a,b,c, }, { S,A,B }, P, S), где
L(G) = { cnabcma | n,m >=0 }
и надо определить, принадлежит ли цепочка caba языку L(G).
Построим вывод этой цепочки:
S → AB → cAB → caB → cabA → caba
Следовательно, цепочка принадлежит языку L(G).
Последовательность применений правил вывода эквивалентна
построению дерева разбора методом "сверху вниз”, а метод
рекурсивного спуска фактически реализует этот способ разбора.
5

6.

S
S
A
c
a
b
c
a
B
a
b
a
S
S
A
B
A
A
B
A
c
a
b
a
c
a
b
a
S
S
A
B
A
A
A
B
A
c
a
b
a
A
c
a
b
a
6

7. Метод рекурсивного спуска 1

Для каждого нетерминала N грамматики создается своя
процедура, носящая его имя:
void N( ) {…..}.
Задача этой процедуры - начиная с указанного места
исходной цепочки, найти подцепочку, которая выводится из
этого нетерминала.
Если такую подцепочку считать не удается, то процедура
завершает свою работу, сигнализируя об ошибке, что
означает, что цепочка не принадлежит языку, и
останавливая разбор.
Если подцепочку удалось найти, то работа процедуры
считается нормально завершенной и осуществляется
возврат в точку вызова.
7

8. Метод рекурсивного спуска 2

Тело каждой процедуры, соответствующей нетерминалу,
пишется непосредственно по правилам вывода из него.
Текущая анализируемая лексема входной цепочки должны
быть уже прочитана и доступна в процедуре, именно по ней
осуществляется выбор нужной альтернативы.
Для каждой альтернативы из правой части правила вывода
реализуется поиск подцепочки, выводимой из этой
альтернативы. При этом:
- терминалы проверяются в самой процедуре, и
в случае удачной проверки считывается очередная
лексема;
- нетерминалам соответствуют вызовы процедур,
носящих их имена.
8

9. Метод рекурсивного спуска 3

Работа системы процедур, построенных в соответствии с РСметодом, начинается с главной функции main ( ), которая:
считывает первый символ исходной цепочки
(заданной во входном потоке stdin),
затем вызывает процедуру S(), которая проверяет,
выводится ли входная цепочка из начального символа S
(в общем случае, это делается с участием других процедур,
которые, в свою очередь, рекурсивно могут вызывать и саму S()).
Считаем, что в конце любой анализируемой цепочки всегда
присутствует символ (признак конца цепочки). На практике этим
признаком может быть ситуация «конец файла» или маркер «конец
строки».
В задачу main ( ) входит также распознавание символа .
Можно считать, что main ( ) соответствует добавленному в
грамматику правилу M → S , где M — новый начальный
символ.
9

10. Реализация РС-метода для грамматики G_рс.

int c;
void A ( );
void B ( );
void gc ( ) {
c = cin.get();
}
void A ( ) {
if (c == 'a') gc ();
else if (c == 'c') { gc ( ); A ( ); }
else throw c;
void S ( ) {
А ( );
B ( );
}
void B ( ) {
if (c == 'b') { gc ( ); A ( ); }
else throw c;
}
M→
S → AB
A → a | cA
B → bA
}
int main ( ) {
try { gc ( );
S ( );
if (c != ' ') throw c;
cout << "SUCCESS !!!" << endl;
return 0;
}
catch ( int c ) {
cout << "ERROR on lexeme" << c << endl;
10

11. Достаточное условие применимости РС-метода

Метод рекурсивного спуска применим к КС-грамматике, если каждая ее группа
правил вывода из А имеет один из следующих видов:
1. либо A → ,
где (T N)*
и это единственное правило вывода
для этого нетерминала;
2. либо A → a1 1 | a2 2 | ... | an n ,
где ai T для всех i = 1, 2,..., n ; ai aj для i j; i (T N)*,
3. либо A → a1 1 | a2 2 | ... | an n | ,
где ai T для всех i = 1, 2,..., n ; ai aj для i j; i (T N)*,
и first (A) follow (A) = .
Множество first (A) - это множество терминальных символов, которыми
начинаются цепочки, выводимые из А в грамматике G = (T, N, P, S):
first (А) { a T | А a , где А N, ( T N )* }.
Множество follow (A) - это множество терминальных символов, которые
следуют за цепочками, выводимыми из А в грамматике G = (T, N, P, S),
follow (A) { a T | S A , a , A N, , , (T N)*}.
11
Если все правила вывода заданной КС-грамматики G удовлетворяют

12. О применимости РС-метода

Сформулированное выше условие не является необходимым.
Метод рекурсивного спуска представляет собой одну из возможных
реализаций нисходящего анализа с прогнозируемым выбором
альтернатив.
Прогнозируемый выбор означает, что по грамматике можно заранее
предсказать, какую альтернативу нужно будет выбрать на очередном
шаге вывода в соответствии с текущим символом (т.е. первым символом
из еще не прочитанной части входной цепочки).
РС-метод неприменим, если такой выбор неоднозначен.
Например, для грамматики G: S → A | B
A → aA | d
B → aB | b
РС-метод неприменим, поскольку, если первый прочитанный символ
есть ‘а’, то выбор альтернативы правила вывода из S неоднозначен.
12
РС-метод неприменим к неоднозначным грамматикам, так как на какомлибо шаге анализа выбор альтернативы вывода обязательно будет

13. Критерий применимости РС-метода

Пусть G — КС-грамматика.
РС-метод применим к G, тогда и только тогда, когда для любой пары
альтернатив
X → | выполняются следующие условия:
1.
2.
3.
first( ) first ( ) ;
справедливо не более чем одно из двух соотношений:
, ;
если , то first( ) follow( X ) .
Множество first ( ) — это множество терминальных символов, которыми
начинаются цепочки, выводимые из цепочки в грамматике
G ( T, N, P, S ), т. е.
first ( ) { a T | a , где ( T N )+, ( T N )* }.
Множество follow(A) — это множество терминальных символов,
которые могут появляться в сентенциальных формах грамматики
G (T, N, P, S) непосредственно справа от A (или от цепочек, выводимых
из A) , т.е.
13
follow(A) { a T | S A , a , A N, , , (T N)*}.

14. Критерий применимости РС-метода. Примеры

Определить, применим ли РС-метод к заданной грамматике.
Пример 1:
S → aAbc | A
1 п. – О.К.
A → bB | cBc
2 п. – О.К.
B → bcB | a | ɛ
3 п.: first (B) { b, a }, follow (B) = { b, c }, = {b} РС-метод - No!
Пример 2:
S → aSc | bA | ɛ
1 п. – О.К.
A → cSBbA | d
2 п. – О.К.
B → daB | ɛ
3 п.: first (S) { b, a }, follow (S) = { b, c, d }, = {b} РС-метод - No!
Пример 3:
S→A|B
A → aA | ɛ
B → cB | b | ɛ
1 п. – О.К.
2 п. – No. РС-метод - No!
14

15. Итерационные правила в КС-грамматиках

Наличие в грамматике итерационных правил вида скрывает правила с
-альтернативой.
А→ { } ,
где А N, ( T N )+, , (T N)*
Чтобы проверить грамматику с итерационными правилами на
применимость РС-метода, надо заменить итерационные правила
обычными правилами следующим образом: каждое правило вида
А→ { }
заменить на два правила
А→ W
W → W | ,
где W – новый нетерминал, ранее не принадлежавший множеству N,
а затем поверить критерий применимости РС-метода.
Процедура по РС-методу для итерационных правил пишется по
следующей схеме:
void A () {
< анализ цепочки >;
while (<текущий_символ_входной_цепочки == первый_символ_ >)
15
< анализ цепочки >;

16. Итерационные правила в КС-грамматиках. Пример

Определить, применим ли РС-метод к заданной грамматике.
Пример:
S → aAb | cC
A → a { bab }
B → cAc | aB | ɛ
C → Bb
Избавимся от итерационного правила вывода:
S → aAb | cC
A → aN
N → babN | ɛ
B → cAc | aB | ɛ
C → Bb
1 п. – О.К.
2 п. – О.К.
3 п.: first (N) { b } follow (N) = { b, c }
16
= {b} РС-метод - No!

17. Преобразования грамматик

Проблема возможности построения грамматики, к которой применим
метод
рекурсивного
спуска,
эквивалентной
грамматике,
не
удовлетворяющей
критерию применимости РС-метода, является
алгоритмически неразрешимой проблемой.
1) Если в грамматике есть нетерминалы, правила вывода которых
леворекурсивны, т.е. имеют вид
A → A 1 | ... | A n | 1 | ... | m,
//A → A |
где i (T N)+, j (T N)*, i = 1, 2, ..., n; j =1, 2 ,..., m,
то непосредственно применять РС-метод нельзя.
Левую рекурсию всегда можно заменить правой:
A → 1A’ | ... | mA’
A’ → 1A’ | ... | nA’ |
// A → A’
// A’ → A’ |
17
Будет получена грамматика, эквивалентная данной, т.к. из нетерминала
A по-прежнему выводятся цепочки вида

18. Преобразования грамматик

2) Если в грамматике есть нетерминал, у которого несколько правил
вывода, и среди них есть правила, начинающиеся нетерминальными
символами, т.е. имеют вид:
A → B1 1 | ... | Bn n | a1 1 | ... | am m
// A → B | С
B1 → 11 | ... | 1k
// B → 1 | 2
...
// С → 1 | 2
Bn → n1 | ... | np ,
где Bi N; aj T; i, j, ij (T N)*,
то можно заменить вхождения нетерминалов Bi их правилами вывода в
надежде, что правила вывода из нетерминала A станут удовлетворять
требованиям метода рекурсивного спуска:
A → 11 1 | ... | 1k 1 | ... | n1 n | ... | np n | a1 1 | ... | am m
// A → 1 | 12 | 1 | 2
18

19. Преобразования грамматик

3) Если в грамматике есть нетерминал, у которого
несколько правил вывода начинаются одинаковыми
терминальными символами, т.е. имеют вид
A → a 1 | a 2 | ... | a n | 1 | ... | m ,
// A → a 1 | a 2 |
где a T; i, j (T N)*,
то непосредственно применять РС-метод нельзя. Можно
преобразовать правила вывода данного нетерминала,
объединив правила с общими началами в одно правило:
A → aA’ | 1 | ... | m
A’ → 1 | 2 | ... | n
// A → aA’ |
// A’ → 1 | 2
Будет получена грамматика, эквивалентная данной.
19

20. Преобразования грамматик

4) Если в правилах вывода грамматики есть пустая альтернатива, т.е. есть
правила вида
A → a1 1 | ... | an n | ,
то метод рекурсивного спуска может оказаться неприменимым.
Например, для грамматики G = ( { a, b }, { S, A }, P, S), где
P: S → bAa
// S → bAс
A → aA |
РС-анализатор, реализованный по обычной схеме, будет таким:
void S ( ) {
void A( ) {
if (c == ‘b’) {
if (c == ‘a’) {
gc( ); A( );
gc(); A(); }
if (c != ‘a’) throw c; }
}
else throw c;
}
Тогда при анализе цепочки baa функция A( ) будет вызвана два раза; она
прочитает подцепочку аа, хотя второй символ а - это часть подцепочки, выводимой
из S. В результате окажется, что baa не принадлежит языку, порождаемому
грамматикой, но в действительности это не так.
20

21.

Итак, если в грамматике есть правила с пустой альтернативой
вида:
A → 1 A | ... | n A | 1 | ... | m |
B → A
и first(A) follow(A) (из-за вхождения А в правила вывода
для В), то можно преобразовать грамматику, заменив правило
вывода из В на следующие два правила:
B → A′
A′ → 1 A′ | ... | n A′ | 1 | ... | m |
Полученная грамматика будет эквивалентна исходной, т. к. из B
по-прежнему выводятся цепочки вида { i} j либо { i} .
Однако правило вывода для нетерминального символа A′ будет
иметь альтернативы, начинающиеся одинаковыми терминальными
символами (т. к. first(A) follow(A) ); следовательно,
потребуются дальнейшие преобразования, и успех не
21
гарантирован.

22. Пример преобразования грамматики

S fASd |
A Aa | Ab | dB | f
B bcB |
S fASd |
A dBA' | fA'
A' aA' | bA' |
B bcB |
FIRST(S) = { f }, FOLLOW(S) = { d }; =
FIRST(A') = { a, b }, FOLLOW(A') = { f, d }; =
FIRST(B) = { b }, FOLLOW(B) = { a, b, f, d }; = {b}
S fASd |
A dB' | fA'
B' bcB' | A' B' bcB' | aA' | bA' |
A' aA' | bA' |
B bcB | - недостижимые правила, их можно убрать.
S fASd |
A dB' | fA'
B' bС | aA' |
С cB' | A' С cB' | aA' | bA' |
A' aA' | bA' |
S - не менялось,
FIRST(B') = { a, b }, FOLLOW(B') = { f, d }; =
22

23. СА для М-языка (1)

class Parser {
Lex curr_lex;
type_of_lex c_type;
int c_val;
Scanner scan;
stack < int > st_int;
stack < type_of_lex> st_lex;
void P(); void D1(); void D (); void B (); void S ();
void E(); void E1(); void T(); void F();
void dec ( type_of_lex type); void check_id ();
void check_op (); void check_not (); void eq_type ();
void eq_bool (); void check_id_in_read ();
void gl ( ) {
curr_lex = scan.get_lex ();
c_type = curr_lex.get_type ();
c_val = curr_lex.get_value ();
}
public:
vector <Lex> poliz;
23

24. СА для М-языка (2)

void Parser::analyze () {
gl ();
P ();
if (c_type != LEX_FIN)
throw curr_lex;
for (Lex l : poliz) cout << l;
cout << endl << "Yes!!!" << endl;
}
void Parser::P () {
if (c_type == LEX_PROGRAM)
gl ();
else
throw curr_lex;
D1();
if (c_type == LEX_SEMICOLON)
gl ();
else
throw curr_lex;
24

25. Обработчик ошибок СА

СА М-языка работает до первой ошибки. Реальные компиляторы,
обнаружив ошибку, пытаются продолжить процесс анализа, используя
обработчик ошибок СА.
Цели обработчика ошибок СА
1. Ясно и точно сообщать о наличии ошибок.
2. Обеспечивать быстрое восстановление после ошибки, чтобы
продолжить поиск последующих ошибок
3. Не замедлять обработку корректной программы.
Неадекватное восстановление после ошибок может привести к лавине
ложных ошибок.
1.
2.
3.
4.
Стратегии восстановления после ошибок
Режим паники (после места ошибки пропускаются все лексемы до
какой-либо синхронизирующей, например, «end», «;», «}» … ).
Режим уровня фразы (при обнаружении ошибки выполняется
локальная коррекция: «, → ; », « → ; », « ; → », …).
Продукции ошибок (грамматика расширяется правилами вывода часто
встречающихся ошибочных конструкций).
Глобальная коррекция (представляет лишь теоретический интерес).
25

26. Другие методы распознавания КС-языков

Существуют другие методы анализа, анализаторы по которым
применимы к более широким подклассам КС-грамматик, чем РСанализатор, но обладающие теми же свойствами: входная
цепочка считывается один раз слева направо, процесс разбора
детерминирован, и в результате на обработку цепочки длины n
расходуется время cn.
Например:
Анализатор для LL(k)-грамматик осуществляет левосторонний
вывод (анализ сверху-вниз) , принимая во внимание k входных
символов, расположенных справа, от текущей позиции .
Анализатор для LR(k)-грамматик осуществляет
правосторонний вывод (анализ снизу-вверх), принимая во
внимание k входных символов, расположенных справа, от текущей
позиции.
Анализатор для грамматик предшествования осуществляет
правосторонний вывод (анализ снизу-вверх) , учитывая только
некоторые отношения между парами смежных символов
26
выводимой цепочки.

27. Другие методы распознавания КС-языков

Любая грамматика, анализируемая РС-методом, является LL(1)грамматикой - обратное неверно.
Любая LL-грамматика является LR-грамматикой - обратное
неверно.
Левосторонний (нисходящий) синтаксический анализ
предпочтителен с точки зрения процесса трансляции, поскольку на
его основе легче организовать процесс порождения цепочек
результирующего языка.
Восходящий синтаксический анализ привлекательнее тем, что
часто для языков программирования легче построить
правоанализируемую грамматику, а на ее основе - правосторонний
распознаватель.
Конкретный выбор анализатора зависит от конкретного
компилятора, от сложности грамматики входного языка
программирования и от того, как будут использованы
результаты работы анализатора.
27

28.

Контроль контекстных условий
(семантический анализ)

29. Семантический анализ

КC-грамматики, с помощью которых описывают синтаксис языков
программирования, не позволяют задавать контекстные условия (КУ),
имеющиеся в любом языке. Проверку КУ называют семантическим
анализом (СемА).
Наиболее часто встречающиеся контекстные условия:
каждый используемый в программе идентификатор должен быть
описан, но не более одного раза в одной зоне описания;
при вызове функции число и тип фактических параметров должны
соответствовать числу и типам формальных параметров;
обычно в языке накладываются ограничения на
- типы операндов любой операции, определенной в этом языке;
- типы левой и правой частей в операторе присваивания;
- тип параметра цикла;
- тип условия в операторах цикла и условном операторе и т.п.
Проверка КУ будет проводиться, как только синтаксический
анализатор распознает конструкцию, на компоненты которой наложены
некоторые ограничения, т.е. на этапе синтаксического анализа должны
29
выполняться некоторые дополнительные действия, осуществляющие

30. Вставка действий в КС-грамматику

Если для синтаксического анализа используется метод рекурсивного
спуска, то для контроля КУ в тела процедур РС-метода необходимо вставить
вызовы дополнительных "семантических" процедур (семантические действия).
Сначала семантические действия вставляются в синтаксические правила,
а потом по этим расширенным правилам строятся процедуры РС-метода.
Чтобы отличать вызовы семантических процедур от других символов
грамматики, они заключаются в угловые скобки.
Фактически при этом расширяется понятие КС-грамматики.
Пусть в грамматике есть правило (A → a B | b C ):
A → a < D1 > B < D1; D2 > | b C < D3 > (без действий - A → a B | b C ), где
A, B, C N; a,b T; < Di > - есть вызов процедуры Di, i = 1, 2, 3.
По такому правилу грамматики процедуру для РС-метода будет следующей:
void A ( ) {
if (c == 'a‘) {
gc( ); D1( ); B( ); D1( ); D2( );
}
else
if (c == 'b') { gc( ); C( ); D3( ); }
30

31. Пример 1.

Написать грамматику, которая порождает язык
L = { (0,1)+ | содержит равное количество 0 и 1}.
Решить эту задачу можно
- чисто синтаксическими средствами - описать цепочки,
обладающие нужным свойством;
- с помощью синтаксических правил описать
произвольные цепочки из 0 и 1, а потом вставить
действия для отбора цепочек с равным количеством 0 и 1.
S → < k0 = k1 = 0; > A
A → 0 < k0++; > B | 1 < k1++;> B
B → A | ε < if (k0 != k1) throw "ERROR !!!"; >
31

32. Пример 2.

Вставка действий в КС-грамматику расширяет мощность
грамматики.
Напишем регулярную грамматику с действиями, которая
порождает КЗ-язык
L = {an bn cn | n >= 1}.
S → a < k0 = 1; k1 = k2 = 0; > A
A → a < k0++; > A | b < k1++;> B
B → b < k1++; > B | c < k2++;> C
C → c < k2++;> C | ε < if (k0 != k1 || k0 != k2) throw "ERROR !!!"; >
32

33. СемА для М-языка

Контекстные условия, выполнение которых надо контролировать
в программах на М-языке, таковы:
Любое имя, используемое в программе, должно быть
описано и только один раз.
В операторе присваивания типы переменной и выражения
должны совпадать.
В условном операторе и в операторе цикла в качестве
условия возможно только логическое выражение.
Операнды операции отношения должны быть
целочисленными.
Тип выражения и совместимость типов операндов в
выражении определяются по обычным правилам
(как в Паскале).
Для проверки КУ М-языка в синтаксические правила грамматики
вставим вызовы процедур, осуществляющих необходимый
33
контроль, а затем перенесем их в процедуры рекурсивного спуска.

34. Обработка описаний

В синтаксические правила для описаний нужно вставить действия, с
помощью которых можно запомнить типы переменных и
контролировать единственность их описания.
i-ая строка таблицы TID соответствует идентификатору-лексеме
вида (LEX_ID, i).
Лексический анализатор заполнил поле name; значения полей
declare и type будем заполнять на этапе семантического анализа.
Раздел описаний имеет вид:
D I { ,I }: [ int | bool ],
т.е. имени типа (int или bool) предшествует список
идентификаторов.
Эти идентификаторы (номера соответствующих им строк таблицы
TID) надо запомнить, например, в стеке целых чисел
stack < int > st_int ,
а когда будет проанализировано имя типа, надо заполнить поля
declare и type в соответствующих строках.
Вспомогательная функция для извлечения элементов стека:
template <class T, class T_EL> void from_st (T& t, T_EL & x) { 34
x = t.top();

35.

Функция void Parser::dec (type_of_lex type) :
считывает из стека номера строк таблицы TID,
заносит в них информацию о типе соответствующих переменных и
о наличии их описаний и
контролирует повторное описание переменных.
void Parser::dec ( type_of_lex type ) {
int i;
while ( ! st_int.empty ( )) {
from_st (st_int, i);
if ( TID [ i ].get_declare () ) throw "twice";
else {
TID [ i ].put_declare ();
TID [ i ].put_type (type); }
}
}
С учетом имеющихся функций правило вывода с действиями для
обработки описаний будет таким:
D → I < st_int.push (c_val) >
{ , I < st_int.push (c_val) > }:
35
English     Русский Rules