Построение отдельных фаз компиляции

№ докум.

Изм.

  Лист

Лист

Подпись

Дата



Введение

Компилятор – программный модуль, задачей которого является перевод программы, написанной на одном из языков программирования (исходный язык) в программу на язык ассемблера или язык машинных команд.

Целью данной курсовой работы является изучение основных принципов построения и функционирования отдельных фаз компиляции, практическое освоение методов построения составных фаз компилятора для заданного входного языка.

Курсовая  работа  заключается  в  разработке  отдельных  фаз  компиляции для заданного входного языка. 

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

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

В третьей части работы требуется разработать модуль, который должен выполнить на основе таблицы лексем синтаксический разбор текста по заданной грамматике с построением дерева разбора. 

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

В качестве среды разработки для реализации программы был использован Borland Delphi 7. 

























































1. Организация таблицы идентификаторов

1.1. Исходные данные

На входе имеется набор идентификаторов. Необходимо написать модуль, который организует данный набор идентификаторов в таблицу идентификаторов методами цепочек и бинарного дерева с возможностью осуществления многократного поиска идентификатора в этой таблице. Список идентификаторов считается заданным в виде текстового файла. Идентификаторы также можно вводить вручную в процессе работы программы.

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



1.2. Назначение таблицы идентификаторов

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

Под идентификаторами подразумеваются константы, переменные, имена процедур и функций, формальные и фактические параметры. 







1.3. Метод бинарного дерева

Данный метод организует таблицу идентификаторов в форме бинарного дерева. Каждый узел этого дерева представляет собой элемент таблицы идентификаторов, причем корневым узлом становится первый элемент, встреченный компилятором во входном тексте. Дерево называется бинарным, так   как   каждая   вершина   в   нем   может   иметь   не   более   двух   вершин (для определенности используют понятия «правая» и «левая» ветви). Схема алгоритма добавления идентификатора приведена на рис. 1.1.



начало

КорневВершина := ДИд

конец

ДА

НЕТ

Есть элемент для добавления?



ЛевВерш[ТекУз]:=ДИд

Будет первым?

ТекУз:=КорнВершина



У ТекУзла есть ЛевВершина?

ТекУз:=ЛевВерш





1



ПрВерш[ТекУз]:=ДИд



У ТекУз есть ПрВерш?



ТекУз:=ПрВерш

1

Такой элемент уже есть

2

2

ДА

ДА

ДА

ДА

ДА

НЕТ

НЕТ

НЕТ

НЕТ

НЕТ

ДИд<Ид[ТекУз]?

ДИд=Ид[ТекУз]?

Рис. 1.1. Схема алгоритма добавления идентификатора

Схема алгоритма поиска идентификатора приведена на рис. 1.2.



начало

конец

ТекУзед:=КорнВершина

У ТекУзла есть ЛевВершина?



ТекУзед:=ЛевВершина



1

У ТекУзла есть ПрВершина?



ТекУзед:=ПрВершина



Элемент найден

1

Элемент не найден

ДА

ДА

ДА

ДА

НЕТ

НЕТ

НЕТ

НЕТ



ДИд<Ид[ТекУз]?



ДИд=Ид[ТекУз]?

Рис. 1.2. Схема алгоритма поиска идентификатора

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



1.4. Метод цепочек

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

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

Схема алгоритма добавления идентификатора представлена на рис. 1.3.



начало

Инициализация

i:=1

ni:=h(ДИд)

XT[ni] - пусто?

j:=1

mj из XT[ni]

поле ссылки ТИ[mj] – пусто?

XT[ni]:=Указ

ТИ[i]:=ДИд

Указ:=Адрес

Есть элемент для добавления?

поле ссылки ТИ[mj]:=Указ

mj из поля ссылки 

ТИ[mj-1]

j:=j+1

i:=i+1

конец

ДА

ДА

ДА

НЕТ

НЕТ

НЕТ

Рис. 1.3. Схема алгоритма добавления идентификатора

Схема алгоритма поиска идентификатора представлена на рис. 1.4. 



начало

ni:=h(ИИд)

XT[ni] - пусто?

j:=1

mj из XT[ni]

конец

поле ссылки ТИ[mj] – пусто?

mj из поля ссылки 

ТИ[mj-1]

j:=j+1

Элемент найден

Элемент не найден

Элемент не найден

ДА

ДА

ДА

НЕТ

НЕТ

НЕТ



ТИ[mj] = ИИд?



Рис. 1.4. Схема алгоритма поиска идентификатора

Достоинства данного метода: 

		нет необходимости заполнять пустыми значениями таблицу идентификаторов, 

		каждому идентификатору соответствует строго одна ячейка в таблице идентификаторов.

	Недостатком метода является организация работы с динамическими массивами.



1.5. Результаты сравнения методов

Поиск информации в таблице идентификаторов выполняется каждый раз, когда компилятору необходимы сведения о том или ином элементе программы. Кроме того, каждому добавлению элемента в таблицу в любом случае будет предшествовать   операция   поиска  –  чтобы   убедиться,   что   такого   элемента в таблице нет. Следовательно, поиск идентификатора происходит гораздо чаще, чем добавление, поэтому таблица идентификаторов должна быть организована так, время поиска идентификатора было минимально.

На рис. 1.5 представлена экранная форма организации таблицы идентификаторов методом цепочек и методом бинарного дерева.



Рис. 1.5. Экранная форма организации таблицы идентификаторов

В данном случае при наличии во входном файле восемнадцати идентификаторов среднее количество требуемых сравнений для заполнения таблицы  идентификаторов  методом  цепочек  оказалось  в   8,8  раз  меньше, чем методом бинарного дерева, следовательно, и среднее время для организации таблицы идентификаторов методом цепочек значительно меньше, чем методом бинарного дерева.

При добавлении нового идентификатора возможны два случая: 

		добавляемого идентификатора в таблице идентификаторов еще нет, добавление  происходит  успешно,  указывается  выполненное  число  сравнений по каждому методу; 

		добавляемый идентификатор в таблице идентификаторов уже существует, в поле ошибок выводится соответствующее сообщение, указывается выполненное   число   сравнений   для   обнаружения   повтора   идентификатора по каждому методу.

Экранная   форма   добавления   нового   идентификатора   представлена  на рис. 1.6.



Рис. 1.6. Экранная форма добавления нового идентификатора



Экранная форма добавления существующего идентификатора представлена на рис. 1.7.



Рис. 1.7. Экранная форма добавления существующего идентификатора

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













При поиске идентификатора также возможны два случая: 

		искомый идентификатор в таблице идентификаторов существует, поиск происходит успешно, указывается положение идентификатора в таблице идентификаторов и выполненное число сравнений для обнаружения идентификатора по каждому методу; 

		искомый идентификатор в таблице идентификаторов не существует, выводится сообщение о неуспешном поиске и указывается выполненное число сравнений для обнаружения отсутствия идентификатора в таблице идентификаторов по каждому методу.

Экранная  форма  поиска  существующего  идентификатора  представлена на рис. 1.8.



Рис. 1.8. Экранная форма поиска существующего идентификатора



Экранная форма поиска несуществующего идентификатора представлена на рис. 1.9.



Рис. 1.9. Экранная форма поиска несуществующего идентификатора

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











2. Проектирование лексического анализатора

2.1. Исходные данные

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

Лексемами данного языка являются:

		ключевые слова (prog, end., begin, end, while ,do, if, then, else);

		скобки ( «(», «)» );

		оператор присваивания ( := );

		операторы сравнения ( >,  <,  = );

		разделитель ( ; );

		арифметические операции ( +, –, ++ );

		шестнадцатеричные константы (тип данных word);

		идентификаторы;

		логические операции (or, not, and).

Кроме    перечисленных    лексем    распознаватель    должен    определять и исключать из входного текста комментарии (неограниченной длины). Комментарии заключаются в фигурные скобки со звездочкой {*…*}. 



2.2. Принцип работы лексического анализатора

Лексический анализатор (или сканер) –  это часть компилятора, которая читает литеры программы на исходном языке и строит из них слова (лексемы) исходного языка. На вход лексического анализатора поступает текст исходной программы, а выходная информация передается для дальнейшей обработки компилятором на этапе синтаксического анализа и разбора.

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

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

Язык описания констант и идентификаторов в большинстве случаев является регулярным, то есть может быть описан с помощью регулярных грамматик. Распознавателями для регулярных языков являются конечные автоматы (КА). 

Любой КА может быть задан с помощью пяти параметров:

М(Q, V,δ,q0,F),					     (2.1)

где:	Q –  конечное множество состояний автомата;

V – конечное множество допустимых входных символов (алфавит автомата);

δ – функция переходов, отображающая декартовое произведение множеств VQ во множество подмножеств Q: R(Q);

q0 Q – начальное состояние автомата;

FQ – непустое множество конечных состояний автомата.

Работа автомата выполняется по тактам. На каждом очередном такте i автомат, находясь в некотором состоянии qiQ, считывает очередной  символ vV из входной цепочки символов и изменяет свое состояние на qi+1=(qi,w), после чего указатель в цепочке входных символов передвигается на следующий символ и начинается такт i+1. Так продолжается до тех пор, пока цепочка входных символов не закончится. Конец цепочки символов часто помечают особым символом . Считается также, что перед первым тактом автомат находится в начальном состоянии q0.

Графически автомат отображается нагруженным однонаправленным графом, в котором вершины представляют состояния, дуги отображают переходы  из одного состояния в другое, а пометки дуг соответствуют функции перехода. Если функция перехода предусматривает переход из состояния qi в qi+1 по нескольким символам, то между ними строится одна дуга, которая помечается всеми символами, по которым происходит переход из qi в qi+1.



2.3. Схема распознавателя

Распознавателем лексем входного языка является конечный детерминированный автомат без внешней памяти (приложение Б). 

Начальное состояние автомата обозначено H, конечное S. В случае ошибочной входной цепочки автомат попадает в состояние ошибки ERR. При этом работа автомата останавливается. Остальные состояния автомата определяются допустимыми для компилятора исходного языка лексемами.

В графе также используются следующие обозначения:

		┴ – пробел, табуляция, перевод строки, +, –, =, <, >, (, ), ;, :, {

		┴c – перевод строки

		x0 – любой символ

		x1 – любой символ, кроме символа *

		x2 – любой символ, кроме символа }

		x3 – любой символ, кроме символа ┴

		x4 – любой символ, кроме символа h

		x5 – любой символ, кроме символов 0..9, A..F

		x6 – любой символ, кроме символа =

		x7 – любой символ, кроме символа +

		x8 – символы A..Z, c, f..h, j..m, q..s, u, v, x..z, _

		x9 –символы A..Z, a..d, f..z, _

		x10 – любой символ, кроме символов A..Z, a..z, _

		x11 – символы A..Z, a..f, h..z, _

		x12 – символы A..Z, a..h, j..z, _

		x13 – символы A..Z, a..g, i..z, _

		x14 – символы A..Z, a..z, _

		x15 – символы A..Z, a..k, m..z, _

		x16 – символы A..Z, a..m, o..z, _

		x17 – символы A..Z, a..e, g..z, _

		x18 –символы A..Z, a..q, s..z, _

		x19 – символы A..Z, a..n, p..z, _

		x20 – символы A..Z, a..s, u..z, _

		x21 – символы A..Z, a..c, e..z, _

		x22 – символы A..Z, a..k, m, o..z, _

		x23 – символы A..Z, a..r, t..z, _

		x24 – любой символ, кроме символов A..Z, a..z, _, ┴

		x25 – символы A..F, 0..9

		x26 – любой символ, кроме символов A..Z, a..z, _, ┴, '.'

Регулярная грамматика входного языка в форме Бэкуса-Наура имеет вид:

G ( {0..9, A..Z, a..z, +, – , <, >, =, (, ), ;, :, {, }, *, '.'}, {S, H, BEGIN1, BEGIN2, BEGIN3, BEGIN4, BEGIN5, WHILE1, WHILE2, WHILE3, WHILE4, WHILE5, THEN1, THEN2, THEN3, THEN4, IF1, IF2, PROG1, PROG2, PROG3, PROG4, VAR, NOT1, NOT2, NOT3, AND1, AND2, AND3, OR1, OR2, DO1, DO2, E1, END2, END3, END4, ELSE2, ELSE3, ELSE4}, P, S )

P:

PROG1  p					PROG2  PROG1 r

PROG3  PROG2 o				PROG4  PROG3 g

BEGIN1  b					BEGIN2  BEGIN1 e

BEGIN3  BEGIN2 g				BEGIN4  BEGIN3 i

BEGIN5  BEGIN4 n				WHILE1  w

WHILE2  WHILE1 h				WHILE3  WHILE2 i

WHILE4  WHILE3 l				WHILE5  WHILE4 e

DO1  d						DO2 DO1 o

IF1  i						IF2 IF1 f

THEN1  t						THEN2  THEN1 h

THEN3  THEN2 e				THEN4  THEN3 n

E1  e						ELSE2  E1 l

ELSE3  ELSE2 s				ELSE4  ELSE3 e

END2  E1 n					END3  END2 d

END4  END3 .					AND1  a

AND2  AND1 n					AND3  AND2 d

OR1  o						OR2  OR1 r

CONST1  0					CONST2  CONST1 x25

CONST3  CONST2 x25			CONST4  CONST3 x25

CONST5  CONST4 x25			CONST6  CONST5 h

ASSI1  :						ASSI2  ASSI1 =

SEMI  ;						SUB  –

ADD  +						INC  ADD +

OPEN  (						CLOSE  )

RAVN  =						MEN  <

BOL  >						

COMM1  {

COMM2  COMM1 * | COMM2 x1	

COMM3  COMM2 *

COMM4  COMM3 }

VAR  x8|BEGIN1 x9|BEGIN2 x11|BEGIN3 x12|BEGIN4 x16|BEGIN5 x14| VAR x14| WHILE1 x13|WHILE2 x12|WHILE3 x15|WHILE4 x9| IF1x17|     WHILE5 x14|E1 x22| THEN1 x13|THEN2 x9|THEN3 x16|THEN4 x14|  IF2 x14|PROG1 x18| PROG2 x19|PROG3 x11|PROG4 x14|OR1 x18|   OR2 x14| NOT1 x19|DO2 x14| NOT2 x20|NOT3 x14|AND1 x16|AND2 x21| AND3 x14|END2 x21| END3 x14|END4 x3|ELSE2 x23|ELSE3 x9|        DO1 x19|ELSE4 x14|

ERR  BEGIN1 x10|BEGIN2 x10|BEGIN3 x10|BEGIN4 x24|BEGIN5 x10|   DO1 x10| WHILE1 x10|WHILE2 x10|WHILE3 x10|WHILE4 x10|  WHILE5 x24| DO2 x24| THEN1 x10| THEN2 x0|THEN3 x10|THEN4 x24| IF2 x24| VAR x24| PROG1 x10|PROG2 x10|PROG3 x10|PROG4 x24|   IF1 x10|OR1 x10|OR2 x24|E1 x10| NOT1 x10| NOT2 x10|NOT3 x24| AND1 x10|AND2 x10|AND3 x24|END2 x10| END3 x26|END4 x3| x3| ELSE2 x10|ELSE3 x10|ELSE4 x24|CONST1 x5|INC x3| CONST2 x5| CONST3 x5|CONST4 x5|CONST5 x4|CONST6 x3|ASSI1 x6| ASSI2 x3| SEMI x3|SUB x3|ADD x7|OPEN x3|CLOSE x3|RAVN x3|MEN x3|BOL x3| COMM1 x1|COMM2 ┴c|COMM3 x2|COMM4 x3

S  	BEGIN5 ┴|WHILE5 ┴|DO2 ┴|THEN4 ┴|IF2 ┴|VAR ┴|PROG4 ┴|OR2 ┴| NOT3 ┴|AND3 ┴|END3 ┴|END4 ┴|ELSE4 ┴|CONST6 ┴|ASSI2 ┴|    ADD ┴| SEMI ┴| SUB┴| UMN ┴|OPEN ┴|CLOSE ┴|RAVN ┴|MEN ┴| BOL ┴|COMM2 ┴

Результатом работы лексического анализатора является перечень всех найденных в тексте исходной программы лексем с учетом их характеристик. Этот перечень лексем предоставляется в виде таблицы лексем. Информация об идентификаторах и константах помещается в таблицу идентификаторов. 

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











2.4. Результаты работы лексического анализатора

Программа  выполняет  лексический анализ входного текста в соответствие с заданной грамматикой. Результатом выполнения лексического анализа является таблица лексем с указанием их типов, а также таблица идентификаторов, сформированная методом цепочек. В таблице лексем имеется поле «Адрес», в котором указывается ссылка на место расположения идентификатора в таблице идентификаторов.

Экранная    форма    работы    лексического    анализатора    представлена на рис.2.1.



Рис. 2.1. Экранная форма работы лексического анализатора

Таблица  лексем  служит  базой  для  проведения  синтаксического  анализа и построения дерева вывода в третьей части курсовой работы.

Лексическая ошибка возникает при обнаружении незакрытого комментария,  неверного  символа  в  лексеме  или  если  лексема  незавершенна (не обнаружен признак окончания лексемы).

Экранная  форма  при  обнаружении  лексической  ошибки  представлена на рис. 2.2.



Рис. 2.2. Экранная форма при обнаружении лексической ошибки

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













3. Проектирование синтаксического анализатора

3.1. Исходные данные

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

Исходная КС-грамматика имеет вид:

G ({ prog, end., begin, end, while, do, if, then, else, and, or, not, =, <, >, (, ), -, +, ++, a, c, ;, :=}, {S, L, O, B, C, D, E, F}, P, S)

P:

S → prog L end.

L → O | L;O | L; 

O → if (B) then O else O| if O then O| begin L end | while (B) do 0 | a:=E | a++

B → B or C | C

C → C and D | D

D → E<E | E>E | E=E | (B) | not(B)

E → E–F | E+F | F

F → (E) | a | c



3.2. Построение синтаксического анализатора

Перед синтаксическим анализатором стоят две основные задачи: проверить правильность конструкций входной цепочки, которая представляется в виде уже выделенных слов входного языка, и преобразовать ее в дерево вывода – вид, удобный для дальнейшей семантической обработки и генерации кода. 

Основой для построения синтаксического анализатора являются автоматы с магазинной памятью, или МП-автоматы. В общем виде МП-автомат можно определить следующим образом: 

 (Q, V, Z, δ, q0, z0, F), 				        (3.1)

где:	Q – множество состояний автомата;

V – алфавит входных символов автомата;

Z – специальный конечный алфавит магазинных символов автомата;

δ – функция переходов автомата;

F – множество конечных состояний;

q0 – начальное состояние автомата;

z0 – начальный символ магазина (стека).

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

При выполнении перехода МП-автомата из одной конфигурации в другую из   стека   удаляются   верхние  символы,   соответствующие  условию  перехода, и добавляется цепочка, соответствующая правилу перехода. Первый символ цепочки становится верхушкой стека. Если при окончании цепочки автомат находится в одном из заданных конечных состояний, а стек пуст, цепочка считается принятой, иначе цепочка символов не принимается.

Для построения распознавателя для языка, заданного КС-грамматикой, воспользуемся алгоритмом «сдвиг-свертка»:

		если  на  верхушке стека МП-автомата находится цепочка символов , то   ее   можно   заменить   на   нетерминальный   символ   A   при   условии,   что в грамматике языка существует правило вида A → , не сдвигая при этом считывающую головку автомата («свертка»);

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

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

	Для более легкого построения распознавателя, преобразуем исходную КС-грамматику в грамматику операторного предшествования (это такая приведенная КС-грамматика, в которой правые части всех правил не содержат смежных нетерминальных символов и для которой отношения предшествования можно задать на множестве терминальных символов). При этом для каждой упорядоченной пары терминальных символов определено не более чем одно из трех следующих отношений (=., <., .>).

На основе множества правил грамматики P определим множества крайних левых и крайних правых символов L(U), R(U) относительно всех нетерминальных символов грамматики (табл. 3.1).

Таблица 3.1.

Множества крайних левых и крайних правых нетерминальных 

символов L(U), R(U) относительно всех нетерминальных символов грамматики

Символ (U)

Шаг 1

Шаг 2



L(U)

R(U)

L(U)

R(U)

F

(, a, c

), a, c

(, a, c

), a, c

E

E, F

F

E, F, (, a, c

F, ), a, c

D

E, (, not

E, )

E, (,a, c, not

E, F, ),a, c

C

C, D

D

C, D, E, F, (, a, c, not

D, E, F, ), a, c

B

B, C

C

B, C, D, E, F, (, a, c, not

C, D, E, F, ), a, c

O

if, begin, while, a

O, E, end, ++

if, begin, while, a

O, E, F, ), a, c, end, ++

L

O, L

O, ;

O, L, if, begin, while, a

O, E, F ,), a, c, end, ++, ;

S

prog

end.

prog

end.

На основе множества правил грамматики P и полученных множеств крайних левых и крайних правых символов L(U), R(U) построим множества крайних левых и крайних правых терминальных символов Lt(U), Rt(U) относительно всех нетерминальных символов грамматики (табл. 3.2).



Таблица 3.2.

Множества крайних левых и крайних правых 

терминальных символов Lt(U), Rt(U)

Символ (U)

Шаг 1

Шаг 2



Lt(U)

Rt(U)

Lt(U)

Rt(U)

F

(, a, c

), a, c

(, a, c

), a, c

E

-, +

-, +

(, a, c, -, +

), a, c, -, +

D

<, >, =, (, not

<, >, =, )

(, a, c, -, +, <, >, =, not

), a, c, -, +, <, >, =

C

and

and

(, a, c, -, +, <, >, =, not, and

), a, c, -, +, <, >, =, and

B

or

or

(, a, c, -, +, <, >, =, not, and, or

), a, c, -, +, <, >, =, and, or

O

if, begin, while, a

else, then, end, do, :=, ++

if, begin, while, a

), a, c, -, +, else, then, end, do, :=, ++

L

;

;

if, begin, while, a, ;

), a, c, -, +, else, then, end, do, :=, ++, ;

S

prog

end.

prog

end.

На основе полученных множеств и правил грамматики G построим матрицу операторного предшествования (приложение В).

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

G ({ prog, end., begin, end, while, do, if, then, else, and, or, not, =, <, >, (, ), -, +, ++, a, c, ;, :=}, {B, E}, P, E)

P:

E → prog E end.

E → E | E;E | E; 

E → if (B) then E else E| if E then E| begin E end | while (B) do E | a:=E | a++

B → B or B | B

B → B and B | B

B → E<E | E>E | E=E | (B) | not(B)

E → E–E | E+E | E

E → (E) | a | c



3.3. Результат работы синтаксического анализатора

Программа проводит синтаксический анализ исходного теста, поступающего от лексического анализатора на основе правил остовной грамматики. Результатом его работы является дерево вывода. 

Экранная   форма   работы   синтаксического   анализатора   представлена на рис. 3.1.



Рис. 3.1. Экранная форма работы синтаксического анализатора

По последовательности сработавших правил «свертки» строится цепочка вывода и дерево вывода. 

Синтаксическая ошибка возникает, если между двумя обозреваемыми символами нет ни одного отношения предшествования, либо не удалось выполнить операцию «свертка», т.е. не найдено правило, совпадающее с основой.

Экранная форма при обнаружении синтаксической ошибки представлена на рис. 3.2.



Рис. 3.2. Экранная форма при обнаружении синтаксической ошибки

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




Заключение

В результате выполнения курсовой работы для заданного входного языка были построены отдельные фазы компиляции.

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

Было установлено, что заданных методов более эффективным является метод цепочек, так как для него время поиска идентификатора в таблице идентификаторов гораздо меньше, чем для метода бинарного дерева, а именно операция «поиск» является основным критерием сравнения эффективности методов.

Во второй части работы разработан модуль, который выполняет лексический анализ входного текста и порождает таблицу лексем с указанием их типов и значений, а также методом цепочек формирует таблицу идентификаторов, при этом в таблице лексем в поле «Адрес» указывается место расположения  идентификатора  в  таблице  идентификаторов.  При обнаружении в исходном тексте лексической ошибки выдается сообщение об ошибке, а работа лексического анализатора прекращается.

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

Отдельные фазы компиляции, разработанные в данной курсовой работе, дают представление о технике и методах, лежащих в основе построения компиляторов.




Список литературы

		Гордеев А.В. Молчанов Л.Ю. Системное программное обеспечение. – СПб.: Питер, 2005. – 734с.: ил.

		Кампапиец Р. II. Манькоп Е. В., Филатов Н. Е. Системное программирование.   Основы   построения   трансляторов:   Учебное   пособие для высших и средних учебных заведений. – СПб.: КОРОНА Принт, 2004. – 256с.: ил.

		Молчанов Л.Ю. Системное программное обеспечение. Лабораторный практикум. – СПб.: Питер, 2005. – 284с.: ил.







4

		            	2601.408219.000 ПЗ