Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Курсовая работа.doc
Скачиваний:
39
Добавлен:
01.05.2014
Размер:
596.48 Кб
Скачать

Описание компилятора

Общая схема компиляции

В общем виде процесс перевода текста программы на языке ПАСКАЛЬ в текст программы на языке ассемблера можно представить в виде следующей схемы:

Можно выделить модули компилятора:

- лексический анализатор

Назначение: выделение в тексте программы лексем, исключение комментариев, заполнение таблиц лексем и формирование потока внутреннего представления лексем – токенов.

- менеджер таблиц лексем

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

- синтаксический анализатор

Назначение: реализация алгоритма «перенос-свёртка», и на основе собственных управляющих таблиц осуществление разбор текста программы в SLR(1)грамматике. В ходе выполнения алгоритма формируется внутреннее представление программы в виде потока тетрад. Синтаксический анализатор взаимодействует с менеджером таблиц лексем для определения семантической корректности программы и формирования обобщённой таблицы лексем.

- генератор выходного языка

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

Результат трансляции:код программы на Ассемблере.

Разработка лексического анализатора

Лексический анализ – это первый этап компиляции.Лексический анализ выполняется за один проход по тексту программы и решает следующие задачи:

- игнорирование в тексте программы комментариев;

- выделение в исходном тексте программы лексем и заполнение таблицы лексем;

- формирование токенов, соответствующих исходному тексту программы;

- обнаружение появления недопустимых символов и конструкций в тексте.

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

Типы выделяемых лексем

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

- ключевые слова и идентификаторы;

- целочисленные, вещественные и строковые константы;

- односимвольные и двусимвольные разделители;

- комментарии;

- незначащие символы (пробелы, табуляция, перевод каретки).

Описание ключевых слов и идентификаторов

Идентификатор - последовательность букв, цифр и символа подчёркивания, начинающаяся с буквы или символа подчёркивания. Этому соответствует следующая автоматная грамматика:

identifier  letter idrest | underline idrest

idrest  letter idrest | underline idrest | digit idrest | eps

Ряд идентификаторов имеет предопределённое значение, и пользователь не может использовать их для именования программы, констант и переменных. Такие идентификаторы называются ключевыми словами. Список ключевых слов входного языка:

program, begin, end, integer, real, boolean, char, string, true, false, stack, push, pop, top, for, to, do, or, and, not, if, then, else, read, write, writeln, const, var.

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

Примеры идентификаторов:

_2382, hello, sum7, Bond_007

Длина идентификатора потенциально не ограничена.

Описание целочисленных, вещественных и строковых констант

В тексте программы могут быть явно указаны целочисленные (intnum), вещественные(realnum) и строковые константы (strvalue) (общее название таких констант – “литералы”). Для каждого типа литералов представлена своя автоматная грамматика:

intnum  digit digits

digits  digit digits | eps

realnum digit fullpart

fullpart digit fullpart | period fracpart | e scipart

fracpart digit fracpart | eps

scipart  + power | - power

power  digit powerrest

powerrest digit powerrest | eps

strvalue quote strrest

strrest  symbol strrest | quote

Примеры целочисленных констант:

0, 1234, 777, 5

Примеры вещественных констант:

0.909, 12., 44e+05, 1E-12

Примеры строковых констант:

‘Milk’, ‘’, ‘I&*$@’, ‘happy’

Описание разделителей

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

- арифметические:+ - * /

- знаки отношения: = <> > >= <= <

- знаки присваивания: :=

- прочие разделители: ; , . : ( )

Описание комментариев и незначащих символов

Так как почти любая программа снабжена теми или иными комментариями, приведем для их описания автоматную грамматику, схожую с грамматикой строковых литералов:

comment  figurebracketopen commrest

commrest anysymbol commrest | figurebracketclose

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

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

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

Пример:

в записи “boolValue and true” пробелы необходимы, так как их отсутствие приведёт к считыванию лексемы-идентификатора “boolValueandtrue”.

в записи “boolValue := true” пробелы могут быть опущены.

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

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

Лексический анализатор может выявить в тексте программы единственный тип ошибок: в тексте обнаружен символ, не являющийся началом ни одной из возможных лексем. Некоторые символы (например, #, % или ~) не могут принадлежать ни одной из лексем, в этом случае лексический анализатор распознаёт ошибку “неизвестный символ”.

Диаграмма лексического анализатора

Как упоминалось выше, лексический анализатор описывается автоматными грамматиками и может быть представлен конечным автоматом. Для иллюстрации его работы можно воспользоваться диаграммой переходов конечного автомата, приведённой на схеме. Символом “S” обозначается начальное состояние, символом “F” - конечное состояние, в прямоугольниках расположены семантические процедуры выделения лексем и символы текста программы, вызывающие выполнение той или иной процедуры.

Спецификация лексического анализатора

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

Лексический анализатор оформлен в виде отдельного класса, заголовок которого приведён ниже:

class CLexAn

{

unsigned m_LineNumber;

static char Delims[];

//----------------------------------------------------------------

std::string m_Buff;

std::string m_Src;

std::string m_ErrBuf;

std::string::iterator m_Iter;

//----------------------------------------------------------------

bool IsDelimiter( char Ch );

//----------------------------------------------------------------

void ToBuffer();

void FlushBuffer( std::string &Target, ENUM_TOKEN_TYPES tot);

//----------------------------------------------------------------

void ExtractComment( std::string &Rcv );

void ExtractString( std::string &Rcv );

void ExtractIdentifier( std::string &Rcv );

void ExtractNumber( std::string &Rcv );

void ExtractDelimiter( std::string &Rcv );

void ExtractColon( std::string &Rcv );

void ExtractPeriod( std::string &Rcv );

void ExtractGreater( std::string &Rcv );

void ExtractLess( std::string &Rcv );

void LookForErrors();

//----------------------------------------------------------------

public:

//----------------------------------------------------------------

void ProcessText( std::string inText , std::string &outText );

//----------------------------------------------------------------

};

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

Множество токенов лексического анализатора

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

Таблица 4

Токен

Описание

TKN_AND

ключевое слово “and”

TKN_ASSIGN

присваивание

TKN_BEGIN

ключевое слово “begin”

TKN_BOOLEAN

ключевое слово “boolean”

TKN_BRACKET_CLOSE

закрывающая круглая скобка

TKN_BRACKET_OPEN

открывающая круглая скобка

TKN_CHAR

ключевое слово “char”

TKN_COLON

двоеточие

TKN_ZPT

запятая

TKN_CONST

ключевое слово “const”

TKN_DO

ключевое слово “do”

TKN_ELSE

ключевое слово “else”

TKN_END

ключевое слово “end”

TKN_FALSE

ключевое слово “false”

TKN_FOR

ключевое слово “for”

TKN_ID

произвольный идентификатор

TKN_IF

ключевое слово “if”

TKN_INTEGER

ключевое слово “integer”

TKN_LITER_CHAR

символьный литерал

TKN_LITER_INT

целочисленный литерал

TKN_LITER_REAL

вещественный литерал

TKN_LITER_STRING

строковый литерал

TKN_MULT

знак операции приоритета произведения

TKN_NOT

ключевое слово “not”

TKN_OR

ключевое слово “or”

TKN_TOCHKA

точка

TKN_PLUS

знак операции приоритета сложения

TKN_POP

ключевое слово “pop”

TKN_PROGRAM

ключевое слово ‘program”

TKN_PUSH

ключевое слово “push”

TKN_READ

ключевое слово “read”

TKN_REAL

ключевое слово “real”

TKN_REL_EQUAL

символ равенства

TKN_REL_NOT_EQUAL

символы неравенства

TKN_ TOCHKA - ZPT

точка с запятой

TKN_STACK

ключевое слово “stack”

TKN_STRING

ключевое слово “string”

TKN_THEN

ключевое слово “then”

TKN_TO

ключевое слово “to”

TKN_TOP

ключевое слово “top”

TKN_TRUE

ключевое слово “true”

TKN_VAR

ключевое слово “var”

TKN_WHILE

ключевое слово “while”

TKN_WRITE

ключевое слово “write”

TKN_WRITELN

ключевое слово “writeln”

Формат используемых таблиц лексем

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

- таблица ключевых слов;

- таблица “специальных символов”, к которым относятся знаки операций и символы-разделители;

- таблицы литералов: символьных, строковых, целочисленных и вещественных;

- таблица идентификаторов.

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

Каждая запись в таблица ключевых слов имеет следующий формат:

Лексема

Токен

Формат записей в таблице идентификаторов:

Лексема

Тип

Флаг константности

Внутреннее имя

Флаг константности устанавливается на этапе синтаксического анализа всем идентификаторам, обозначающим константу.

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

Формат записей в таблице специальных символов:

Лексема

Токен

Операция

Необходимость поля “Операция” обусловлена тем, что одному токену в общем случае может соответствовать несколько операций одного приоритета.

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

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

Представление потока токенов на выходе лексического анализатора

Для представления отдельных токенов и потока токенов в удобном для последующего анализа виде, разработаны классы CToken CTokenStream:

class CToken

{

ENUM_TOKENS m_tok;

unsigned m_index;

unsigned m_line;

public:

CToken(ENUM_TOKENS tok, unsigned index, unsigned line)

: m_tok(tok), m_index(index), m_line(line) { }

operator ENUM_TOKENS() { return m_tok; }

unsigned Index() { return m_index; }

unsigned Line() { return m_line; }

};

Как видно из кода, объект класса CToken хранит в себе название токена (m_tok), положение соответствующей токену записи в одной из таблиц (m_index) и номер строки, в которой был обнаружен данный токен (используется для вывода информации об ошибках).

class CTokenStream

{

public:

typedef std::vector<CToken> container;

typedef container::iterator iterator;

private:

container m_Container;

public:

CTokenStream() {};

iterator begin() { return m_Container.begin(); }

iterator end() { return m_Container.end(); }

bool empty() const { return m_Container.empty(); }

void clear() { m_Container.clear(); }

void Add(CToken &Tok) { m_Container.push_back(Tok); }

};

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