Files
mathlogic/lab4/cmilan/doc/cmilan.tex

957 lines
59 KiB
TeX
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

\documentclass[a4paper,12pt]{article}
\usepackage{ucs}
\usepackage{cmap}
\usepackage[utf8x]{inputenc}
\usepackage[T2A]{fontenc}
\usepackage[english,russian]{babel}
\usepackage[hmargin=2cm,vmargin=2cm]{geometry}
\usepackage{indentfirst}
\usepackage{listings}
\usepackage{syntax}
\usepackage[section]{placeins}
\setlength{\grammarparsep}{10pt plus 1pt minus 1pt}
\setlength{\grammarindent}{9em}
\lstset{
frame=TB,
morekeywords={begin,end,if,then,else,fi,while,do,od,write,read}
}
\title{Компилятор \textsc{CMilan}}
\author{Э. Ф. Аллахвердиев, Д. А. Тимофеев}
\begin{document}
\maketitle
\tableofcontents
\section{Обзор языка Милан}
Язык Милан --- учебный язык программирования, описанный в
учебнике~\cite{karpov05}.
Программа на Милане представляет собой последовательность операторов,
заключенных между ключевыми словами \texttt{begin} и \texttt{end}. Операторы
отделяются друг от друга точкой с запятой. После последнего оператора в блоке
точка с запятой не ставится. Компилятор \textsc{CMilan} не учитывает регистр
символов в именах переменных и ключевых словах.
В базовую версию языка Милан входят следующие конструкции: константы,
идентификаторы, арифметические операции над целыми числами, операторы чтения
чисел со стандартного ввода и печати чисел на стандартный вывод, оператор
присваивания, условный оператор, оператор цикла с предусловием.
Программа может содержать комментарии, которые могут быть многострочными.
Комментарий начинается символами `\texttt{/*}' и заканчивается символами
`\texttt{*/}'. Вложенные комментарии не допускаются.
Грамматика языка Милан в расширенной форме Бэкуса-Наура приведена на
рисунке~\ref{milan-grammar}.
\begin{figure}
\begin{grammar}
<program> ::= `begin' <statementList> `end'
<statementList> ::= <statement> `;' <statementList>
\alt $\epsilon$
<statement> ::= <ident> `:=' <expression>
\alt `if' <relation> `then' <statementList> [`else' <statementList>] `fi'
\alt `while' <relation> `do' <statementList> `od'
\alt `write' `(' <expression> `)'
<expression> ::= <term> \{<addop> <term>\}
<term> ::= <factor> \{<mulop> <factor>\}
<factor> ::= <ident> | <number> | `(' <expression> `)'
<relation> ::= <expression> <cmp> <expression>
<addop> ::= `+' | `-'
<multop> ::= `*' | `/'
<cmp> ::= `=' | `!=' | `<' | `<=' | `>' | `>='
<ident> ::= <letter> \{<letter> | <digit>\}
<letter> ::= `a' | `b' | `c' | \ldots | `z' | `A' | `B' | `C' | \ldots | `Z'
<digit> ::= `0' | `1' | `2' | `3' | `4' | `5' | `6' | `7' | `8' | `9'
\end{grammar}
\label{milan-grammar}
\caption{Грамматика языка Милан}
\end{figure}
\subsection{Константы}
В языке реализована поддержка знаковых целочисленных констант. Синтаксически
константы представляют собой последовательность цифр, перед которой может
находиться знак `-'. Примеры констант: \texttt{0}, \texttt{9}, \texttt{1234},
\texttt{-19}, \texttt{-0}.
\subsection{Идентификаторы}
Идентификаторы представляют собой последовательность букв латинского алфавита и
цифр. Первым символом идентификатора должна быть буква. Максимальная длина
идентификатора ограничена 63 символами. Регистр символов не учитывается.
Идентификаторы не должны совпадать с ключевыми словами \texttt{begin},
\texttt{end}, \texttt{if}, \texttt{then}, \texttt{else}, \texttt{fi},
\texttt{do}, \texttt{od}, \texttt{while}, \texttt{read}, \texttt{write}.
Примеры идентификаторов: \texttt{a}, \texttt{X}, \texttt{GCD}, \texttt{Milan23}.
Цепочки символов, недопустимые в качестве идентификаторов: \texttt{12a} (первый
символ не является буквой), \texttt{Begin} (цепочка символов совпадает с
ключевым словом), \texttt{a\_1} (цепочка содержит символ подчеркивания).
\subsection{Арифметические выражения}
\begin{grammar}
<expression> ::= <term> \{<addop> <term>\}
<term> ::= <factor> \{<mulop> <factor>\}
<factor> ::= <ident> | <number> | `(' <expression> `)'
\end{grammar}
Арифметические выражения строятся по традиционным для языков программирования
правилам. Элементами арифметических выражений могут быть константы,
идентификаторы, знаки арифметических операций `\texttt{+}' (сложение),
`\texttt{-}' (вычитание), `\texttt{*}' (умножение), `\texttt{/}'
(деление), скобки и ключевое слово \texttt{read}, которое обозначает операцию
чтения числа со стандартного ввода.
Примеры правильных выражений: \texttt{12 + 4}, \texttt{i}, \texttt{(x+1)*y},
\texttt{2*x+1}, \texttt{read}, \texttt{read * (x - read)}.
Операции `\texttt{*}' и `\texttt{/}' имеют более высокий приоритет, чем
операции `\texttt{+}' и `\texttt{-}'. Все эти операции левоассоциативны.
Значения арифметических выражений вычисляются слева направо с учетом приоритета
операций. Порядок вычисления важен из-за присутствия операции \texttt{read},
благодаря которой вычисление значения выражения имеет побочный эффект.
\subsection{Печать чисел на стандартный вывод}
\begin{grammar}
<statement> ::= `write' `(' <expression> `)'
\end{grammar}
Печать чисел осуществляется с помощью оператора \texttt{write}, аргументом
которого является произвольное арифметическое выражение. Примеры использования
оператора \texttt{write}: \texttt{write(5)},
\texttt{write(n+7)}, \texttt{write((2+read)*3)}, \texttt{write(read)}.
\subsection{Оператор присваивания}
\begin{grammar}
<statement> ::= <ident> `:=' <expression>
\end{grammar}
При выполнении оператора присваивания сначала вычисляется значение выражения,
записанного в его правой части. Затем результат записывается в ячейку памяти,
соответствующую переменной в левой части оператора присваивания.
Последовательность символов `\texttt{:=}' является неделимой и не может
содержать пробелов между символами `\texttt{:}' и `\texttt{=}'.
Примеры корректных операторов присваивания: \texttt{a := b + 1}, \texttt{c :=
read}.
\subsection{Условный оператор}
\begin{grammar}
<statement> ::= `if' <relation> `then' <statementList> [`else' <statementList>] `fi'
<relation> ::= <expression> <cmp> <expression>
<cmp> ::= `=' | `!=' | `<' | `<=' | `>' | `>='
\end{grammar}
Условный оператор включает:
\begin{enumerate}
\item условие, которое представляет собой проверку на равенство или неравенство
двух арифметических выражений,
\item последовательность операторов, которая должна быть выполнена, если условие
истинно (блок \texttt{then}),
\item необязательную последовательность операторов, которая должна быть
выполнена, если условие ложно (блок \texttt{else}).
\end{enumerate}
Проверка условия $a ? b$, где $a$ и $b$ --- арифметические выражения, а <<$?$>>
--- один из операторов сравнения, производится следующим образом:
\begin{enumerate}
\item вычисляется значение выражения $a$;
\item вычисляется значение выражения $b$;
\item проверяется выполнение условия.
\end{enumerate}
Поддерживаются следующие отношения: `\texttt{=}' (<<равно>>), `\texttt{!=}'
(<<не равно>>), `\texttt{<}' (<<меньше>>), `\texttt{<=}' (<<меньше или равно>>),
`\texttt{>}' (<<больше>>), `\texttt{>=}' (<<больше или равно>>).
Условный оператор завершается ключевым словом \texttt{fi}. Оно используется,
чтобы избежать известной проблемы <<висячего else>>, которая встречается, в
частности, в языках C, C++ и Java. Наличие <<закрывающей скобки>> \texttt{fi}
позволяет однозначно интерпретировать вложенные условные операторы.
Примеры использования условного оператора приведены на рисунках~\ref{ifthen} и
\ref{ifthenelse}. Обе эти программы считывают со стандартного ввода два числа.
Программа на рисунке~\ref{ifthen} печатает наибольшее из двух чисел. Программа
на рисунке~\ref{ifthenelse} печатает числа в порядке возрастания.
\begin{figure}
\begin{lstlisting}
begin
x := read;
y := read;
max := x;
if y > max then
max := y
fi;
write (max)
end
\end{lstlisting}
\label{ifthen}
\caption{Пример условного оператора \texttt{if..then..fi}}
\end{figure}
\begin{figure}
\begin{lstlisting}
begin
x := read;
y := read;
if x <= y then
write (x);
write (y)
else
write (y);
write (x)
fi
end
\end{lstlisting}
\label{ifthenelse}
\caption{Пример условного оператора \texttt{if..then..else..fi}}
\end{figure}
\subsection{Цикл с предусловием}
\begin{grammar}
<statement> ::= `while' <relation> `do' <statementList> `od'
<relation> ::= <expression> <cmp> <expression>
<cmp> ::= `=' | `!=' | `<' | `<=' | `>' | `>='
\end{grammar}
При выполнении цикла с предусловием сначала проверяется, истинно ли условие.
Если оно истинно, последовательно выполняются операторы, составляющие тело
цикла. После того, как последний оператор будет выполнен, управление снова
возвращается к проверке условия. Если условие оказывается ложным, выполнение
цикла завершается. Вычисление выражений, необходимых для проверки условия,
производится так же, как и при выполнении условного оператора.
Пример программы, использующей цикл, приведен на рисунке~\ref{while}. Эта
программа считывает со стандартного ввода число и печатает его факториал.
\begin{figure}
\begin{lstlisting}
begin
n := read;
factorial := 1;
x := 1;
while x <= n do
factorial := factorial * x;
x := x + 1
od;
write (factorial)
end
\end{lstlisting}
\label{while}
\caption{Пример цикла с предусловием}
\end{figure}
\section{Использование компилятора}
Компилятор \textsc{CMilan} преобразует программы на языке Милан в
последовательность команд виртуальной машины Милана. Общее описание виртуальной
машины можно найти в учебнике~\cite{karpov05}, а более детальное
описание --- или в документации, прилагаемой к исходным текстам виртуальной
машины.
Исполняемый файл компилятора называется \texttt{cmilan} (в операционных системах
семейства Unix) или \texttt{cmilan.exe} (в Windows). Компилятор имеет интерфейс
командной строки. Чтобы скомпилировать программу, записанную в файле
<<program.mil>>, нужно выполнить команду <<\texttt{cmilan program.mil}>>.
Результатом работы компилятора является либо последовательность команд для
виртуальной машины Милана, которая печатается на стандарный вывод, либо набор
сообщений о синтаксических ошибках, которые выводятся в стандартный поток
ошибок.
При вызове программы \texttt{cmilan} без параметров компилятор печатает краткую
информацию о порядке его вызова.
Чтобы сохранить генерируемую компилятором программу для виртуальной машины в
файл, достаточно перенаправить в этот файл стандартный поток вывода.
На рисунке~\ref{cmilan-usage} приведен пример сеанса работы с компилятором.
\begin{figure}
\begin{verbatim}
bash$ cat factorial.mil
BEGIN
n := READ;
factorial := 1;
i := 1;
WHILE i <= n DO
factorial := factorial * i;
i := i + 1
OD;
WRITE(factorial)
END
bash$ cmilan factorial.mil > factorial.out
bash$ milanvm factorial.out
Reading input from factorial.out
> 5
120
\end{verbatim}
\label{cmilan-usage}
\caption{Пример использования компилятора \textsc{CMilan}}
\end{figure}
\subsection{Сборка компилятора в Unix}
Для сборки компилятора в операционных системах семейства Unix (в частности,
GNU/Linux и FreeBSD) необходим компилятор C++, в качестве которого рекомендуется
использовать GCC, и GNU Make. Для сборки компилятора достаточно перейти в
каталог \texttt{src}, в котором расположены исходные тексты компилятора
\textsc{CMilan}, и выполнить команду \texttt{make}.
\subsection{Сборка компилятора в Windows}
Для сборки компилятора \textsc{CMilan} в Windows можно использовать компилятор
GCC или Microsoft Visual Studio. При использовании GCC (Cygwin, MinGW) сборка
производится так же, как и в Unix. Для сборки компилятора с помощью Visual
Studio 2010 в дистрибутив компилятора включены файлы проекта (каталог
\texttt{src\\cmilan\_vs2011}). Если необходимо использовать другие версии Visual
Studio, достаточно создать пустой проект консольного приложения Win32 и добавить
в проект существующие исходные тексты из каталога \texttt{src}.
\section{Устройство компилятора}
\subsection{Архитектура компилятора}
Компилятор \textsc{CMilan} включает три компонента:
\begin{enumerate}
\item лексический анализатор;
\item синтаксический анализатор;
\item генератор команд виртуальной машины Милана.
\end{enumerate}
Лексический анализатор посимвольно читает из входного потока текст программы и преобразует
группы символов в лексемы (терминальные символы грамматики языка Милан). При
этом он отслеживает номер текущей строки, который используется синтаксическим
анализатором при формировании сообщений об ошибках. Также лексический анализатор
удаляет пробельные символы и комментарии. При формировании лексем анализатор
идентифицирует ключевые слова и последовательности символов (например, оператор
присваивания), а также определяет значения числовых констант и имена переменных.
Эти значения становятся значениями атрибутов, связанных с лексемами.
Синтаксический анализатор читает сформированную лексическим анализатором
последовательность лексем и проверяет ее соответствие грамматике языка Милан.
Для этого используется метод рекурсивного спуска. Если в процессе
синтаксического анализа обнаруживается ошибка, анализатор формирует сообщение об
ошибке, включающее сведения об ошибке и номер строки, в которой она возникла.
В процессе анализа программы синтаксический анализатор генерирует набор машинных
команд, соответствующие каждой конструкции языка.
Генератор кода представляет собой служебный компонент, ответственный за
формирование внешнего представления генерируемого кода. Генератор поддерживает
буфер команд и предоставляет синтаксическому анализатору набор функций,
позволяющий записать указанную команду по определенному адресу. После того, как
синтаксический анализатор заканчивает формирование программы, генератор кода
используется для печати на стандартный вывод отсортированной по возрастанию
адресов последовательности инструкций. Использование генератора кода несколько
упрощает устройство компилятора, поскольку синтаксический анализатор может
генерировать команды не в порядке их следования в программе, а по мере получения
всей необходимой информации для их формирования. Это особенно важно при
трансляции таких инструкций, как условные операторы или циклы.
Все три компонента объединяются <<драйвером>> --- управляющей программой
компилятора. Драйвер анализирует аргументы командной строки, инициализирует
синтаксический анализатор и вызывает его метод \texttt{parse()}, запуская
процесс трансляции.
\textsc{CMilan} является примером простейшего однопроходного компилятора,
в котором синтаксический анализ и генерация кода выполняются совместно. При этом
компилятор никак не оптимизирует код. Реальные компиляторы
обычно выполняют трансляцию и оптимизацию кода в несколько проходов, используя
несколько видов внутреннего представления программы. Сведения об архитектуре
таких компиляторов можно найти, в частности, в классической <<книге
дракона>>~\cite{dragonbook11}. В случае \textsc{CMilan}, тем не менее,
предпочтение было отдано не качеству генерируемого кода, а простоте реализации и
легкости расширения.
\subsection{Генератор кода}
Основной задачей генератора кода является хранение и заполнение буфера
инструкций последовательностью команд для виртуальной машины Милана. Генератор
кода не отвечает за правильность этой последовательности и не выполняет никаких
семантических преобразований. Тем не менее, генератор кода вполне мог бы быть
расширен для того, чтобы выполнять простые оптимизации на уровне машинных
команд.
Генератор кода описан в файлах \texttt{codegen.h} и \texttt{codegen.cpp}. Его
реализация состоит из двух классов. Класс \texttt{Command} описывает машинные
инструкций и отвечает за их вывод. В классе Codegen реализованы функции добавления
инструкций в буфер, получения текущего адреса в буфере, резервирования ячейки
для инструкции, которая должна быть добавлена в код позднее, и печати
последовательности инструкций из буфера на стандартный вывод.
Приведем краткое описание методов генератора кода.
\begin{itemize}
\item \texttt{void Command::print(int address, ostream\& os)}
Печать инструкции с указанием адреса \texttt{address} в поток вывода \texttt{os}.
\item \texttt{void CodeGen::emit(Instruction instruction)}
Добавление инструкции \texttt{instruction} без аргументов в конец буфера.
\item \texttt{void CodeGen::emit(Instruction instruction, int arg)}
Добавление инструкции \texttt{instruction} с аргументом \texttt{arg} в конец буфера.
\item \texttt{void CodeGen::emitAt(int address, Instruction instruction)}
Запись инструкции \texttt{instruction} без аргументов в буфер со смещением
\texttt{address}.
\item \texttt{void CodeGen::emitAt(int address, Instruction instruction, int
arg)}
Запись инструкции \texttt{instruction} с аргументом \texttt{arg} в буфер
со смещением \texttt{address}
\item \texttt{int CodeGen::getCurrentAddress()}
Возврат адреса, по которому будет записана очередная инструкция.
\item \texttt{int CodeGen::reserve()}
Резервирование ячейки памяти для инструкции. Метод добавляет в конец
буфера инструкцию \texttt{NOP} и возвращает ее адрес.
\item \texttt{void CodeGen::flush()}
Вывод последовательности инструкций в выходной поток.
\end{itemize}
\subsection{Лексический анализатор}
Лексический анализатор преобразует считываемые из входного потока символы в
лексемы языка Милан. Например, последовательность символов `\texttt{B}',
`\texttt{E}', `\texttt{G}', `\texttt{I}', `\texttt{N}' соответствует ключевому
слову `\texttt{begin}'. Каждой лексеме соответствует отдельный элемент
перечислимого типа \texttt{Token}. В частности, ключевому слову `\texttt{begin}'
соответствует константа \texttt{T\_BEGIN}. С некоторыми лексемами связана
дополнительная информация --- значение атрибута. Так, с лексемой
\texttt{T\_NUMBER} (целочисленная константа) связано число, равное значению этой
константы, а с лексемой \texttt{T\_IDENTIFIER} (идентификатор) --- строка,
содержащая имя переменной.
В таблице~\ref{lexemes} приведен полный список лексем и значений связанных с ними
атрибутов.
\begin{table}
\begin{center}
\begin{tabular}{|l|l|p{6cm}|}
\hline
\hline
Имя лексемы & Значение & Атрибут \\
\hline
\hline
\texttt{T\_EOF} & Конец текстового потока & \\
\hline
\texttt{T\_ILLEGAL} & Недопустимый символ & \\
\hline
\texttt{T\_IDENTIFIER}& Идентификатор & Имя переменной \\
\hline
\texttt{T\_NUMBER} & Константа & Значение константы \\
\hline
\texttt{T\_BEGIN} & Ключевое слово `\texttt{begin}' & \\
\hline
\texttt{T\_END} & Ключевое слово `\texttt{end}' & \\
\hline
\texttt{T\_IF} & Ключевое слово `\texttt{if}' & \\
\hline
\texttt{T\_THEN} & Ключевое слово `\texttt{then}' & \\
\hline
\texttt{T\_ELSE} & Ключевое слово `\texttt{else}' & \\
\hline
\texttt{T\_FI} & Ключевое слово `\texttt{fi}' & \\
\hline
\texttt{T\_WHILE} & Ключевое слово `\texttt{while}' & \\
\hline
\texttt{T\_DO} & Ключевое слово `\texttt{do}' & \\
\hline
\texttt{T\_OD} & Ключевое слово `\texttt{od}' & \\
\hline
\texttt{T\_WRITE} & Ключевое слово `\texttt{write}' & \\
\hline
\texttt{T\_READ} & Ключевое слово `\texttt{read}' & \\
\hline
\texttt{T\_ASSIGN} & Оператор `\texttt{:=}' & \\
\hline
\texttt{T\_ADDOP} & Операция типа сложения &
\texttt{A\_PLUS}~(`\texttt{+}'), \texttt{A\_MINUS}~(`\texttt{-}') \\
\hline
\texttt{T\_MULOP} & Операция типа умножения &
\texttt{A\_MULTIPLY}~(`\texttt{*}'), \texttt{A\_DIVIDE}~(`\texttt{/}') \\
\hline
\texttt{T\_CMP} & Оператор отношения &
\texttt{C\_EQ}~(`\texttt{=}'), \texttt{C\_NE}~(`\texttt{!=}'),
\texttt{C\_LT}~(`\texttt{<}'), \texttt{C\_LE}~(`\texttt{<=}'),
\texttt{C\_GT}~(`\texttt{>}'), \texttt{C\_GE}~(`\texttt{=}') \\
\hline
\texttt{T\_LPAREN} & Открывающая скобка & \\
\hline
\texttt{T\_RPAREN} & Закрывающая скобка & \\
\hline
\texttt{T\_SEMICOLON} & `\texttt{;}' & \\
\hline
\hline
\end{tabular}
\end{center}
\caption{Лексемы языка Милан}
\label{lexemes}
\end{table}
Все ключевые слова перечислены в ассоциативном массиве \texttt{keywords\_}. При
этом ключом является строка, соответствующая ключевому слову в нижнем регистре,
а значением --- соответствующая лексема. С помощью массива \texttt{tokenNames\_}
каждой лексеме сопоставлено ее строковое внешнее представление, которое
используется при формировании сообщений об ошибках. Последовательность
символов, не соответствующая ни одной из лексем, считается ошибочной. В этом
случае лексический анализатор возвращает значение \texttt{T\_ILLEGAL}.
Исходный текст лексического анализатора находится в файлах \texttt{scanner.h} и
\texttt{scanner.cpp}. Алгоритм лексического анализа реализован в классе
\texttt{Scanner}. Он содержит следующие открытые методы:
\begin{itemize}
\item \texttt{Token token()}
Получение текущей лексемы. Одна и та же лексема может быть прочитана
многократно.
\item \texttt{void nextToken()}
Переход к следующей лексеме.
\item \texttt{int getIntValue()}
Получение целочисленного атрибута текущей лексемы. В базовой версии Милана
этот метод используется только для получения значений целочисленных
констант.
\item \texttt{string getStringValue()}
Получение строкового атрибута текущей лексемы. В базовой версии Милана
этот метод используется для получения имен идентификаторов.
\item \texttt{Cmp getCmpValue()}
Получение кода операции сравнения текущей лексемы (используется только
вместе с лексемой \texttt{T\_CMP}).
\item \texttt{Arithmetic getArithmeticValue()}
Получение кода арифметической операции (используется вместе с лексемами
\texttt{T\_ADDOP} и \texttt{T\_MULOP}).
\item \texttt{const int getLineNumber()}
Получение номера текущей строки в исходном файле (используется при
формировании сообщений об ошибках).
\end{itemize}
Поля класса описывают состояние лексического анализатора:
\begin{itemize}
\item \texttt{const string fileName\_}
Имя входного файла.
\item \texttt{int lineNumber\_}
Номер текущей строки в анализируемой программе.
\item \texttt{Token token\_}
Текущая лексема.
\item \texttt{int intValue\_}
Целочисленный атрибут лексемы.
\item \texttt{string stringValue\_}
Строковый атрибут лексемы.
\item \texttt{Cmp cmpValue\_}
Код оператора сравнения.
\item \texttt{Arithmetic arithmeticValue\_}
Код арифметической операции.
\item \texttt{map<string, Token> keywords\_}
Ассоциативный массив, описывающий ключевые слова языка Милан. Используется
при обнаружении цепочки символов, которая может быть как идентификатором,
так и ключевым словом.
\item \texttt{ifstream input\_}
Входной поток для чтения из файла.
\item \texttt{char ch\_}
Очередной символ программы.
\end{itemize}
Закрытые методы класса (служебные функции):
\begin{itemize}
\item \texttt{bool isIdentifierStart(char c)}
Метод возвращает значение \texttt{true}, если символ \texttt{c} может быть
первым символом идентификатора (в базовой версии языка Милан это означает,
что символ является буквой латинского алфавита).
\item \texttt{bool isIdentifierBody(char c)}
Метод возвращает значение \texttt{true}, если символ \texttt{c} может
быть частью идентификатора. В базовой версии языка Милан эт означает, что
символ является буквой латинского алфавита или цифрой.
\item \texttt{void skipSpace()}
Пропуск всех пробельных символов (символов пробела, табуляции, перевода
строки). При чтении символа перевода строки увеличивается номер текущей
строки \texttt{lineNumber\_}.
\item \texttt{void nextChar()}
Переход к следующему символу программы.
\end{itemize}
Основная часть алгоритма лексического анализа реализована в методе
\texttt{nextToken}. Для того, чтобы перейти к следующей лексеме, выполняется
следующая последовательность действий,
Прежде всего необходимо удалить пробелы и комментарии. Признаком начала
комментария является последовательность символов `\texttt{/*}'. При обнаружении
символа `\texttt{/}' необходимо проверить следующий за ним символ. Если он не
совпадает с `\texttt{*}', значит, был найден оператор деления. В этом случае
переменной \texttt{token\_} присваивается значение \texttt{T\_MULOP}, а атрибуту
\texttt{arithmeticValue\_} --- значение \texttt{A\_DIVIDE}. Если за символом
`\texttt{/}' непосредственно следует `\texttt{*}', лексический анализатор
пропускает все символы, пока не встретит закрывающую комментарий
последовательность `\texttt{*/}' или конец файла. После того, как был найден
признак конца комментария, еще раз вызывается функция \texttt{skipSpace}.
Операция удаления комментариев повторяется многократно, пока очередной
непробельный символ не окажется отличным от символа `\texttt{/}'. Если в
процессе удаления пробелов или комментариев был встречен конец файла, очередной
лексемой считается \texttt{T\_EOF}.
После удаления пробелов и комментариев происходит анализ очередного символа. Он
выполняется по следующим правилам.
\begin{enumerate}
\item Если символ является цифрой, то очередная лексема --- целочисленная константа
(\texttt{T\_NUMBER}). Лексический анализатор считывает из входного потока эту
и все следующие за ней цифры, преобразуя полученную последовательность в целое
число. Это число становится значением атрибута \texttt{intValue\_}.
\item Если символ может быть началом идентификатора, из потока считываются все
последующие символы, которые могут быть частью идентификатора. Полученная
последовательность проверяется на совпадение с ключевым словом. В случае
совпадения очередной лексемой считается лексема, соответствующая этому ключевому
слову. Если цепочка символов не совпала ни с одним ключевым словом, очередная
лексема считается идентификатором (\texttt{T\_IDENTIFIER}), а сама цепочка
становится значением строкового атрибута \texttt{stringValue\_}.
\item Если символ равен `\texttt{:}', лексический анализатор считывает следующий
символ и проверяет, что он равен `\texttt{=}'. В этом случае возвращается
лексема \texttt{T\_ASSIGN}. Если следом за `\texttt{:}' идет любой другой
символ, лексема считается ошибочной (\texttt{T\_ILLEGAL}).
\item Если символ равен `\texttt{!}', производится аналогичная проверка на
равенство следующего символа `\texttt{=}'. В случае успеха текущей лексемой
становится лексема \texttt{T\_CMP}, а атрибут \texttt{cmpValue\_} принимает
значение \texttt{T\_NE}.
\item Если символ равен `\texttt{<}', `\texttt{>}' или `\texttt{=}', текущей
лексемой становится \texttt{T\_CMP}. Чтобы определить значение атрибута
\texttt{cmpValue\_}, лексический анализатор может прочитать еще один символ для
того, чтобы отличить оператор `\texttt{<}' от оператора `\texttt{>}', а оператор
`\texttt{>}' от оператора `\texttt{>=}'.
\item Если символ равен `\texttt{+}' или `\texttt{-}', переменная
\texttt{token\_} принимает значение \texttt{T\_ADDOP}, при этом соответствующим
образом устанавливается значение атрибута \texttt{arithmeticValue\_}. Аналогично
обрабатываются символ `\texttt{*}' (лексема \texttt{T\_MULOP}). Оператор деления
обрабатывать таким же образом не нужно, поскольку он обнаруживается в ходе
удаления комментариев.
\item Если символ совпадает с `\texttt{;}', `\texttt{(}', `\texttt{)}',
очередная лексема устанавливается в соответствующее символу значение.
\end{enumerate}
\subsection{Синтаксический анализатор}
Задачами синтаксического анализатора является проверка соответствия программы
грамматике языка Милан (рисунок~\ref{milan-grammar}) и формирование кода для
виртуальной машины Милана в соответствии со структурой программы. Синтаксический
анализ выполняется методом рекурсивного спуска. Каждому нетерминальному символу
грамматики сопоставлен метод, выполняющий проверку соответствия
последовательности лексем одному из тех правил грамматики, в левой части которых
стоит данный нетерминальный символ. Семантические действия (генерация кода)
встроены в код метода.
Исходный текст синтаксического анализатора находится в файлах \texttt{parser.h}
и \texttt{parser.cpp}. Алгоритм синтаксического анализа реализован в классе
\texttt{Parser}. Конструктор класса в качестве аргумента принимает имя файла, в
котором находится анализируемый текст, и создает экземпляры лексического
анализатора и генератора кода.
Синтаксический анализатор предоставляет один открытый метод \texttt{void parse()},
который используется для того, чтобы начать процесс анализа.
Состояние анализатора описывается следующими полями:
\begin{itemize}
\item \texttt{Scanner* scanner\_}
Экземпляр лексического анализатора.
\item \texttt{CodeGen* codegen\_}
Экземпляр генератора кода.
\item \texttt{std::ostream\& output\_}
Выходной поток, в который должны быть выведены инструкции программы.
Базовая версия компилятора Милан использует в качестве выходного потока
стандартный вывод (\texttt{std::cout}).
\item \texttt{bool error\_}
Признак ошибки в тексте программы. Если \texttt{error\_} принимает
значение <<истина>>, генерируемый машинный код не выводится.
\item \texttt{map<string, int> variables\_}
Таблица имен, найденных в программе. Она сопоставляет каждой переменной ее
адрес в памяти виртуальной машины. Поскольку базовая версия языка Милан не
содержит вложенных блоков, процедур или функций, таблица имен представляет
собой простой ассоциативный массив.
\item \texttt{int lastVar\_}
Адрес последней найденной переменной.
\end{itemize}
Синтаксический анализатор включает ряд вспомогательных функций (закрытые методы
класса).
\begin{itemize}
\item \texttt{bool see(Token t)}
Сравнение текущей лексемы с образцом. Текущая позиция в потоке лексем не
изменяется.
\item \texttt{bool match(Token t)}
Проверка совпадения текущей лексемы с образцом. Если лексема и образец
совпадают, лексема изымается из потока.
\item \texttt{void mustBe(Token t)}
Проверка совпадения текущей лексемы с образцом. Если лексема и образец
совпадают, лексема изымается из потока. В противном случае формируется
сообщение об ошибке, а программа считается некорректной.
\item \texttt{void next()}
Переход к следующей лексеме.
\item \texttt{void reportError(const string\& message)}
Формирование сообщения об ошибке. Каждое сообщение включает текст
\texttt{message} и номер строки, в которой обнаружена ошибка.
\item \texttt{void recover(Token t)}
Восстановление после ошибки. Используется примитивный алгоритм: если
очередная лексема не совпадает с ожидаемой, анализатор пропускает все
последующие лексемы, пока не встретит ожидаемую лексему или
конец файла. Хотя такой метод восстановления не отличается точностью, он,
тем не менее, позволяет продолжить анализ и, возможно, найти ошибки в
оставшейся части программы.
\item \texttt{int findOrAddVariable(const string\&)}
Поиск имени переменной в таблице имен. Если имя найдено, метод возвращает
адрес переменной, в противном случае имя добавляется в таблицу имен, и для
соответствующей переменной резервируется новый адрес.
\end{itemize}
Кроме служебных методов, класс \texttt{Parser} содержит закрытые методы
\texttt{void program()}, \texttt{void statementList()}, \texttt{void
statement()}, \texttt{void expression()}, \texttt{void term()}, \texttt{void
factor()}, \texttt{void relation()}, которые соответствуют нетерминальным
символам грамматики. Эти методы не возвращают значений и не принимают
аргументов, поскольку с нетерминальными символами не связаны явные атрибуты: все
семантические действия, которые производятся во время анализа программы,
модифицируют буфер общего для всех методов генератора кода.
\subsubsection{Распознавание операторов}
Продемонстрируем алгоритм работы синтаксического анализатора на примере анализа
операторов (метод \texttt{statement}).
В соответствии с грамматикой языка Милан, в качестве оператора может выступать
оператор присваивания, условный оператор \texttt{if}, оператор цикла
\texttt{while} или оператор печати \texttt{write}. Для выбора нужной
альтернативы достаточно прочитать первую лексему оператора.
Если очередная лексема равна \texttt{T\_IDENTIFIER}, синтаксический анализатор
имеет дело с оператором присваивания. Он считывает имя переменной, стоящей в
левой части присваивания, и определяет ее адрес. Затем анализатор проверяет, что
следующая лексема совпадает с \texttt{T\_ASSIGN}. После знака `\texttt{:=}' в
программе должно следовать арифметическое выражение, поэтому анализатор вызывает
метод \texttt{expression()}. Этот метод проверяет правильность арифметического
выражения и генерирует последовательность команд для его вычисления. В
результате выполнены этой последовательности на вершине стека будет находиться
значение выражения. Чтобы выполнить присваивание, достаточно записать значение с
вершины стека в память по адресу переменной в левой части оператора
присваивания. Для этого после вызова \texttt{expression()} нужно добавить в
буфер команд инструкцию \texttt{STORE}, аргументом которой будет запомненный
ранее адрес переменной.
Если очередная лексема равна \texttt{T\_IF}, анализатор должен начать разбор
условного оператора. Сначала он вызывает метод \texttt{relation()}, проверяя,
что после лексемы \texttt{T\_IF} следует правильное сравнение двух выражений.
Метод \texttt{relation()} генерирует код, вычисляющий и сравнивающий два
выражения. При выполнении этого кода на вершине стека останется значение $1$,
если условие выполнено, и $0$ в противном случае. Если условие не выполнено,
должен произойти переход к списку операторов блока \texttt{else} или, если этого
блока нет, по адресу следующей за условным оператором инструкции. Однако этот
адрес еще не известен синтаксическому анализатору. Чтобы решить эту проблему,
применяется так называемый <<метод обратных поправок>>~\cite{dragonbook11}.
Анализатор резервирует место для условного перехода \texttt{JUMP\_NO} и
запоминает адрес этой инструкции в переменной \texttt{jumpNoAddress}, после чего
переходит к дальнейшему анализу условного оператора.
Следом за условием в тексте программы должна находиться лексема
\texttt{T\_THEN}. Если она обнаружена, анализатор переходит к следующей лексеме
и вызывает метод \texttt{statementList()}, который анализирует список операторов
и генерирует для них исполняемый код. Дальнейшие действия зависят от того,
присутствует ли в условном операторе ветвь \texttt{else}. Если следующая лексема
равна \texttt{T\_ELSE}, синтаксический анализатор должен сгенерировать
инструкцию \texttt{JUMP} для выхода из условного оператора, после чего
приступить к анализу блока \texttt{else}. Если условный оператор не содержит
блока \texttt{else}, инструкция безусловного перехода в конце блока
\texttt{then} не нужна.
Генерация безусловного перехода также требует знания адреса, следующего за
последней инструкцией условного оператора, поэтому анализатор снова должен
зарезервировать ячейку памяти для команды перехода. Следующий за этой ячейкой
адрес содержит первую команду блока \texttt{else}. Это именно тот адрес, по
которому необходимо было выполнить переход в случае, если условие не было
выполнено. Поскольку теперь этот адрес известен, синтаксический анализатор
генерирует инструкцию безусловного перехода и помещает ее в буфер команд по
адресу, который записан в переменной \texttt{jumpNoAddress}. В случае отсутствия
блока \texttt{else} поправка происходит таким же образом, но в качестве адреса
перехода просто используется следующий за последней инструкцией блока
\texttt{then} адрес. После вызова функции \texttt{statementList}, которая
разбирает список операторов блока \texttt{else}, аналогично исправляется
зарезервированная инструкция безусловного перехода.
Структура кода, соответствующая условному оператору, приведена на
рисунке~\ref{ifthenelse-code}.
\begin{figure}
\begin{verbatim}
Вычисление выражений
...
COMPARE operator
JUMP_NO elseLabel
Операторы блока THEN
...
JUMP endLabel
elseLabel: Операторы блока ELSE
...
endLabel:
\end{verbatim}
\caption{Структура исполняемого кода условного оператора}
\label{ifthenelse-code}
\end{figure}
Если очередная лексема равна \texttt{T\_WHILE}, синтаксическому анализатору
предстоит разобрать оператор цикла. Анализатор должен запомнить текущий адрес
команды, поскольку после каждой итерации цикла по этому адресу нужно будет
возвращаться. Затем анализатор вызывает метод \texttt{relation()}, чтобы
сгенерировать код проверки условия. Если условие окажется ложным, нужно будет
выполнить переход на следующий за циклом оператор. Для этого синтаксический
анализатор резервирует ячейку памяти для команды \texttt{JUMP\_NO}. После этого
он проверяет наличие лексемы \texttt{T\_DO} и вызывает метод
\texttt{statementList()} для обработки тела цикла. Затем анализатор генерирует
инструкцию безусловного перехода назад, к началу проверки условия, и записывает
в зарезервированную ячейку инструкцию условного перехода по известному теперь
адресу конца цикла. Для завершения анализа инструкции достаточно убедиться, что
очередная лексема равна ожидаемому значению \texttt{T\_OD}. Структура
исполняемого кода для цикла приведена на рисунке~\ref{while-code}.
\begin{figure}
\begin{verbatim}
whileLabel: Вычисление выражений
...
COMPARE operator
JUMP_NO endLabel
...
Операторы тела цикла
...
JUMP whileLabel
endLabel:
\end{verbatim}
\caption{Структура исполняемого кода цикла с предусловием}
\label{while-code}
\end{figure}
Если очередная лексема равна \texttt{T\_WRITE}, необходимо проверить, что
следующая лексема равна \texttt{T\_LPAREN}, затем вызвать метод
\texttt{expression()} и проверить, что за выражением в потоке следует лексема
\texttt{T\_RPAREN}. Поскольку код, вычисляющий значение выражения, оставляет его
значение на вершине стека, для выполнения печати достаточно после вызова
\texttt{expression()} сгенерировать инструкцию \texttt{PRINT}.
\begin{thebibliography}{9}
\addcontentsline{toc}{section}{Список литературы}
\bibitem{dragonbook11}
А. Ахо, М. Лам, Р. Сети, Дж. Ульман,
\emph{Компиляторы: принципы, технологии и инструментарий}, 2-е изд.
М.: Вильямс, 2011.
\bibitem{karpov05}
Ю.Г. Карпов,
\emph{Теория и технология программирования. Основы построения трансляторов}.
СПб.: БХВ-Петербург, 2005.
\end{thebibliography}
\end{document}