Опубликован: 06.08.2007 | Доступ: свободный | Студентов: 1901 / 1054 | Оценка: 4.45 / 4.29 | Длительность: 18:50:00
Специальности: Программист
Лекция 5:

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

Рекурсивный спуск

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

В процедуре A для случая, когда имеется правило A -> ui, такое, что u_i \Rightarrow^* e (напомним, что не может быть больше одного правила, из которого выводится e ), приведены два варианта - 1.1 и 1.2. В варианте 1.1 делается проверка, принадлежит ли следующий входной символ FOLLOW(A): Если нет - выдается ошибка. В варианте 1.2 этого не делается, так что анализ ошибки откладывается на процедуру, вызвавшую A.

\begin{align*}
&\text{$\textbf{void} \; A()\{ \; // \; A \rightarrow u_1\mid u_2 \mid \ldots  \mid u_k$}\\
&\quad \; \text{$\textbf{if} \; (InSym \in FIRST(u_i))$ // только одному!}\\
& \quad \quad \; \text{$\textbf{if} \; (\textrm{parse}(u_i))$}\\
& \quad \quad \quad   \text{$\textrm{result}("A \rightarrow u_i");$}\\
& \quad \quad \; \text{$\textbf{else} \; \textrm{error}();$}\\
&\quad \; \text{$\textbf{else}$}\\
&\quad \; \text{//Вариант 1:}\\
&\quad \; \text{$\textbf{if}$ \; (имеется правило \; $A \rightarrow u_i$ \; такое, что \; $u_i\Rightarrow^*e$)}\\
& \quad \quad \;\text{//Вариант 1.1}\\
& \quad \quad \;\text{$\textbf{if} \; (\textit{InSym} \in \textit{FOLLOW}(\textit{A}))$}\\
& \quad \quad \quad   \text{$\textrm{result}("A \rightarrow u_i");$}\\
& \quad \quad \; \text{$\textbf{else} \; \textrm{error}();$}\\
& \quad \quad \;\text{//Конец варианта 1.1}\\
& \quad \quad \;\text{//Вариант 1.2:}\\
& \quad \quad \;\text{$\textrm{result}("A \rightarrow u_i");$}\\
& \quad \quad \;\text{//Конец варианта 1.2}\\
&\quad \; \text{//Конец варианта 1}\\
&\quad \; \text{//Вариант 2:}\\
&\quad \; \text{$\textbf{if}$ \;(нет правила \; $A \rightarrow u_i$ \; такого, что \; $u_i \Rightarrow^* e)
$}\\
& \quad \quad \;\text{error();}\\
&\quad \; \text{//Конец варианта 2}\\
&\text{\}}\\
&\text{\textbf{boolean} parse($u$)\{ // из $u$ не выводится $e$}\\
&\quad \; \text{$v = u;$}\\
&\quad \; \text{while ($v \neq e)\{\; //\; v = Xz$}\\
& \quad \quad \;\text{\textbf{if} ($X$ - терминал $a$)}\\
& \quad \quad \quad   \text{\textbf{if} ($InSym \neq a$)}\\
& \quad \quad \quad \quad   \text{\textbf{return}(false);}\\
& \quad \quad \quad   \text{\textbf{else} \textit{InSym} = getInsym();}\\
& \quad \quad \;\text{\textbf{else} // $X$ - нетерминал $B$}\\
& \quad \quad \quad   \text{$B$();}\\
& \quad \quad \quad   \text{$v=z;$}\\
&\quad \; \text{\}}\\
&\quad \; \text{\textbf{return}(true);}\\
& \text{\}}
\end{align*}

Восстановление процесса анализа после синтаксических ошибок

В приведенных программах рекурсивного спуска была использована процедура реакции на синтаксические ошибки error(). В простейшем случае эта процедура выдает диагностику и завершает работу анализатора. Но можно попытаться некоторым разумным образом продолжить работу. Для разбора сверху вниз можно предложить следующий простой алгоритм.

Если в момент обнаружения ошибки на верхушке магазина оказался нетерминальный символ A и для него нет правила, соответствующего входному символу, то сканируем вход до тех пор, пока не встретим символ либо из FIRST(A), либо из FOLLOW(A). В первом случае разворачиваем A по соответствующему правилу, во втором - удаляем A из магазина.

Если на верхушке магазина терминальный символ, то можно удалить все терминальные символы с верхушки магазина вплоть до первого (сверху) нетерминального символа и продолжать так, как это было описано выше.

Разбор снизу-вверх типа сдвиг- свертка

Основа

В процессе разбора снизу-вверх типа сдвиг-свертка строится дерево разбора входной цепочки, начиная с листьев (снизу) к корню (вверх). Этот процесс можно рассматривать как "свертку" цепочки w к начальному символу грамматики. На каждом шаге свертки подцепочка, которую можно сопоставить правой части некоторого правила вывода, заменяется символом левой части этого правила вывода, и если на каждом шаге выбирается правильная подцепочка, то в обратном порядке прослеживается правосторонний вывод ( рис. 4.5) Здесь ко входной цепочке, так же как и при анализе LL(1)-грамматик, приписан концевой маркер $.


Рис. 4.5.

Основой цепочки называется подцепочка сентенциальной формы, которая может быть сопоставлена правой части некоторого правила вывода, свертка по которому к левой части правила соответствует одному шагу в обращении правостороннего вывода. Самая левая подцепочка, которая сопоставляется правой части некоторого правила вывода A \to  \gamma, не обязательно является основой, поскольку свертка по правилу A \to  \gamma может дать цепочку, которая не может быть сведена к аксиоме.

Формально, основа правой сентенциальной формы z - это правило вывода A \to  \gamma и позиция в z, в которой может быть найдена цепочка \gamma такие, что в результате замены \gamma на A получается предыдущая сентенциальная форма в правостороннем выводе z. Так, если S \Rightarrow^*_r \; \alpha A \beta  \Rightarrow^*_r \alpha \gamma \beta, то A \to  \gamma в позиции, следующей за \alpha, это основа цепочки \alpha \gamma \beta. Подцепочка \beta справа от основы содержит только терминальные символы.

Вообще говоря, грамматика может быть неоднозначной, поэтому не единственным может быть правосторонний вывод \alpha \gamma \beta и не единственной может быть основа. Если грамматика однозначна, то каждая правая сентенциальная форма грамматики имеет в точности одну основу. Замена основы в сентенциальной форме на нетерминал левой части называется отсечением основы. Обращение правостороннего вывода может быть получено с помощью повторного применения отсечения основы, начиная с исходной цепочки w. Если w - слово в рассматриваемой грамматике, то w = \alpha _{n}, где \alpha _{n} - n -я правая сентенциальная форма еще неизвестного правого вывода S = \alpha_0 \Rightarrow_r \alpha_1 \Rightarrow_r \ldots \Rightarrow_r \alpha_{n-1} \Rightarrow_r \alpha_n = w.

Чтобы восстановить этот вывод в обратном порядке, выделяем основу \gamma _{n} в \alpha _{n} и заменяем \gamma _{n} на левую часть некоторого правила вывода A_{n} \to  \gamma _{n}, получая ( n - 1 )-ю правую сентенциальную форму \alpha _{n-1}. Затем повторяем этот процесс, то есть выделяем основу \gamma _{n-1}в \alpha _{n-1} и сворачиваем эту основу, получая правую сентенциальную форму \alpha _{n-2}. Если, повторяя этот процесс, мы получаем правую сентенциальную форму, состоящую только из начального символа S, то останавливаемся и сообщаем об успешном завершении разбора. Обращение последовательности правил, использованных в свертках, есть правый вывод входной строки.

Таким образом, главная задача анализатора типа сдвиг- свертка - это выделение и отсечение основы.

LR(1)-анализаторы

В названии LR(1) символ L указывает на то, что входная цепочка читается слева-направо, R - на то, что строится правый вывод, наконец, 1 указывает на то, что анализатор видит один символ непрочитанной части входной цепочки.

LR(1)-анализ привлекателен по нескольким причинам:

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

Схематически структура LR(1)-анализатора изображена на рис. 4.4. Анализатор состоит из входной ленты, выходной ленты, магазина, управляющей программы и таблицы анализа (LR(1)-таблицы), которая имеет две части - функцию действий ( Action ) и функцию переходов ( Goto ). Управляющая программа одна и та же для всех LR(1)-анализаторов, разные анализаторы отличаются только таблицами анализа.

Анализатор читает символы на входной ленте по одному за шаг. В процессе анализа используется магазин, в котором хранятся строки вида S0X1S1X2S2 ... XmSm ( Sm - верхушка магазина). Каждый Xi - символ грамматики (терминальный или нетерминальный), а Si - символ состояния.

Заметим, что символы грамматики (либо символы состояний) не обязательно должны размещаться в магазине. Однако, их использование облегчает понимание поведения LR-анализатора.

Элемент функции действий Action[Sm; ai] для символа состояния Sm и входа a_i \in T \cup \{$\}, может иметь одно из четырех значений:

  1. shift S (сдвиг), где S - символ состояния,
  2. reduce \ A \to  \gamma (свертка по правилу грамматики A \to  \gamma ),
  3. accept (допуск),
  4. error (ошибка).

Элемент функции переходов Goto[Sm; A] для символа состояния Sm и входа A \in N, может иметь одно из двух значений:


Рис. 4.6.
  1. S, где S - символ состояния,
  2. error (ошибка).

Конфигурацией LR(1)-анализатора называется пара, первая компонента которой - содержимое магазина, а вторая - непросмотренный вход:

(S0X1S1X2S2 ... XmSm, aiai+1 ... an$)

Эта конфигурация соответствует правой сентенциальной форме

X1X2 ... Xmaiai+1 ... an

Префиксы правых сентенциальных форм, которые могут появиться в магазине анализатора, называются активными префиксами. Основа сентенциальной формы всегда располагается на верхушке магазина. Таким образом, активный префикс - это такой префикс правой сентенциальной формы, который не переходит правую границу основы этой формы.

Когда анализатор начинает работу, в магазине находится только символ начального состояния S0, на входной ленте - анализируемая цепочка с маркером конца.

Каждый очередной шаг анализатора определяется текущим входным символом ai и символом состояния на верхушке магазина Sm следующим ниже образом.

Пусть LR(1)-анализатор находится в конфигурации

(S0X1S1X2S2 ... XmSm, aiai+1 ... an$)

Анализатор может проделать один из следующих шагов:

  1. Если Action[Sm, ai] = shift S, то анализатор выполняет сдвиг, переходя в конфигурацию
    (S_0X_1S_1X_2S_2 \ldots X_mS_ma_iS, a_{i+1} \ldots a_n\$)
    То есть, в магазин помещаются входной символ ai и символ состояния S, определяемый Action[Sm, ai]. Текущим входным символом становится ai+1.
  2. Если Action[S_{m}, a_{i}] = reduce \ A \to  \gamma, то анализатор выполняет свертку, переходя в конфигурацию
    (S_0X_1S_1X_2S_2 \ldots X_{m-r}S_{m-r}AS, a_ia_{i+1} \ldots a_n\$)
    где S = Goto[Sm-r; A] и r - длина \gamma, правой части правила вывода. Анализатор сначала удаляет из магазина 2r символов ( r символов состояния и r символов грамматики), так что на верхушке оказывается состояние Sm-r. Затем анализатор помещает в магазин A - левую часть правила вывода, и S - символ состояния, определяемый Goto[Sm-r, A]. На шаге свертки текущий входной символ не меняется. Для LR(1)- анализаторов последовательность символов грамматики Xm-r+1 ... Xm, удаляемых из магазина , всегда соответствует \gamma - правой части правила вывода, по которому делается свертка. После осуществления шага свертки генерируется выход LR(1)-анализатора, то есть исполняются семантические действия, связанные с правилом, по которому делается свертка, например, печатаются номера правил, по которым делается свертка. Заметим, что функция Goto таблицы анализа, построенная по грамматике G, фактически представляет собой функцию переходов детерминированного конечного автомата, распознающего активные префиксы G.
  3. Если Action[Sm, ai] = accept, то разбор успешно завершен.
  4. Если Action[Sm, ai] = error, то анализатор обнаружил ошибку, и выполняются действия по диагностике и восстановлению.

Пример 4.10. Рассмотрим грамматику арифметических выражений G = ({E, T, F}, {id, +, *}, P, E) с правилами:

  1. E -> E + T
  2. E -> T
  3. T -> T * F
  4. T -> F
  5. F -> id

На рис. 4.7 изображены функции Action и Goto, образующие LR(1)-таблицу для этой грамматики. Элемент Si функции Action означает сдвиг и помещение в магазин состояния с номером i, Rj - свертку по правилу номер j, acc - допуск, пустая клетка - ошибку. Для функции Goto символ i означает помещение в магазин состояния с номером i, пустая клетка - ошибку.

На входе id + id * id последовательность состояний магазина и входной ленты показаны на рис. 4.8. Например, в первой строке LR-анализатор находится в нулевом состоянии и "видит" первый входной символ id. Действие S6 в нулевой строке и столбце id в поле Action ( рис. 4.7) означает сдвиг и помещение символа состояния 6 на верхушку магазина. Это и изображено во второй строке: первый символ id и символ состояния 6 помещаются в магазин, а id удаляется со входной ленты.

Текущим входным символом становится +, и действием в состоянии 6 на вход + является свертка по F -> id. Из магазина удаляются два символа (один символ состояния и один символ грамматики). Затем анализируется нулевое состояние. Поскольку Goto в нулевом состоянии по символу F - это 3, F и 3 помещаются в магазин. Теперь имеем конфигурацию, соответствующую третьей строке. Остальные шаги определяются аналогично.