Компилятор и вм милана
This commit is contained in:
10
lab4/cmilan/doc/Makefile
Normal file
10
lab4/cmilan/doc/Makefile
Normal file
@@ -0,0 +1,10 @@
|
||||
cmilan.pdf: cmilan.tex
|
||||
pdflatex cmilan.tex
|
||||
pdflatex cmilan.tex
|
||||
pdflatex cmilan.tex
|
||||
|
||||
clean:
|
||||
rm -f cmilan.aux
|
||||
rm -f cmilan.toc
|
||||
rm -f cmilan.log
|
||||
|
||||
BIN
lab4/cmilan/doc/cmilan.pdf
Normal file
BIN
lab4/cmilan/doc/cmilan.pdf
Normal file
Binary file not shown.
956
lab4/cmilan/doc/cmilan.tex
Normal file
956
lab4/cmilan/doc/cmilan.tex
Normal file
@@ -0,0 +1,956 @@
|
||||
\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}
|
||||
|
||||
23
lab4/cmilan/src/Makefile
Normal file
23
lab4/cmilan/src/Makefile
Normal file
@@ -0,0 +1,23 @@
|
||||
CFLAGS = -Wall -W -Werror -O2
|
||||
LDFLAGS =
|
||||
|
||||
HEADERS = scanner.h \
|
||||
parser.h \
|
||||
codegen.h
|
||||
|
||||
OBJS = main.o \
|
||||
codegen.o \
|
||||
scanner.o \
|
||||
parser.o \
|
||||
|
||||
EXE = cmilan
|
||||
|
||||
$(EXE): $(OBJS) $(HEADERS)
|
||||
$(CXX) $(LDFLAGS) -o $@ $(OBJS)
|
||||
|
||||
.cpp.o:
|
||||
$(CXX) $(CFLAGS) -c $< -o $@
|
||||
|
||||
clean:
|
||||
-@rm -f $(EXE) $(OBJS)
|
||||
|
||||
20
lab4/cmilan/src/cmilan_vs2011/cmilan_vs2011.sln
Normal file
20
lab4/cmilan/src/cmilan_vs2011/cmilan_vs2011.sln
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 11.00
|
||||
# Visual Studio 2010
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cmilan_vs2011", "cmilan_vs2011.vcxproj", "{1D542465-2019-4442-864E-DC785FA067A8}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Win32 = Debug|Win32
|
||||
Release|Win32 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{1D542465-2019-4442-864E-DC785FA067A8}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{1D542465-2019-4442-864E-DC785FA067A8}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{1D542465-2019-4442-864E-DC785FA067A8}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{1D542465-2019-4442-864E-DC785FA067A8}.Release|Win32.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
92
lab4/cmilan/src/cmilan_vs2011/cmilan_vs2011.vcxproj
Normal file
92
lab4/cmilan/src/cmilan_vs2011/cmilan_vs2011.vcxproj
Normal file
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{1D542465-2019-4442-864E-DC785FA067A8}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>cmilan_vs2011</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<TargetName>cmilan</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<TargetName>cmilan</TargetName>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\codegen.h" />
|
||||
<ClInclude Include="..\parser.h" />
|
||||
<ClInclude Include="..\scanner.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\codegen.cpp" />
|
||||
<ClCompile Include="..\main.cpp" />
|
||||
<ClCompile Include="..\parser.cpp" />
|
||||
<ClCompile Include="..\scanner.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
42
lab4/cmilan/src/cmilan_vs2011/cmilan_vs2011.vcxproj.filters
Normal file
42
lab4/cmilan/src/cmilan_vs2011/cmilan_vs2011.vcxproj.filters
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\codegen.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\parser.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\scanner.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\codegen.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\main.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\parser.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\scanner.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
3
lab4/cmilan/src/cmilan_vs2011/cmilan_vs2011.vcxproj.user
Normal file
3
lab4/cmilan/src/cmilan_vs2011/cmilan_vs2011.vcxproj.user
Normal file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
</Project>
|
||||
129
lab4/cmilan/src/codegen.cpp
Normal file
129
lab4/cmilan/src/codegen.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#include "codegen.h"
|
||||
|
||||
void Command::print(int address, ostream& os)
|
||||
{
|
||||
os << address << ":\t";
|
||||
switch(instruction_) {
|
||||
case NOP:
|
||||
os << "NOP";
|
||||
break;
|
||||
|
||||
case STOP:
|
||||
os << "STOP";
|
||||
break;
|
||||
|
||||
case LOAD:
|
||||
os << "LOAD\t" << arg_;
|
||||
break;
|
||||
|
||||
case STORE:
|
||||
os << "STORE\t" << arg_;
|
||||
break;
|
||||
|
||||
case BLOAD:
|
||||
os << "BLOAD\t" << arg_;
|
||||
break;
|
||||
|
||||
case BSTORE:
|
||||
os << "BSTORE\t" << arg_;
|
||||
break;
|
||||
|
||||
case PUSH:
|
||||
os << "PUSH\t" << arg_;
|
||||
break;
|
||||
|
||||
case POP:
|
||||
os << "POP";
|
||||
break;
|
||||
|
||||
case DUP:
|
||||
os << "DUP";
|
||||
break;
|
||||
|
||||
case ADD:
|
||||
os << "ADD";
|
||||
break;
|
||||
|
||||
case SUB:
|
||||
os << "SUB";
|
||||
break;
|
||||
|
||||
case MULT:
|
||||
os << "MULT";
|
||||
break;
|
||||
|
||||
case DIV:
|
||||
os << "DIV";
|
||||
break;
|
||||
|
||||
case INVERT:
|
||||
os << "INVERT";
|
||||
break;
|
||||
|
||||
case COMPARE:
|
||||
os << "COMPARE\t" << arg_;
|
||||
break;
|
||||
|
||||
case JUMP:
|
||||
os << "JUMP\t" << arg_;
|
||||
break;
|
||||
|
||||
case JUMP_YES:
|
||||
os << "JUMP_YES\t" << arg_;
|
||||
break;
|
||||
|
||||
case JUMP_NO:
|
||||
os << "JUMP_NO\t" << arg_;
|
||||
break;
|
||||
|
||||
case INPUT:
|
||||
os << "INPUT";
|
||||
break;
|
||||
|
||||
case PRINT:
|
||||
os << "PRINT";
|
||||
break;
|
||||
}
|
||||
|
||||
os << endl;
|
||||
}
|
||||
|
||||
void CodeGen::emit(Instruction instruction)
|
||||
{
|
||||
commandBuffer_.push_back(Command(instruction));
|
||||
}
|
||||
|
||||
void CodeGen::emit(Instruction instruction, int arg)
|
||||
{
|
||||
commandBuffer_.push_back(Command(instruction, arg));
|
||||
}
|
||||
|
||||
void CodeGen::emitAt(int address, Instruction instruction)
|
||||
{
|
||||
commandBuffer_[address] = Command(instruction);
|
||||
}
|
||||
|
||||
void CodeGen::emitAt(int address, Instruction instruction, int arg)
|
||||
{
|
||||
commandBuffer_[address] = Command(instruction, arg);
|
||||
}
|
||||
|
||||
int CodeGen::getCurrentAddress()
|
||||
{
|
||||
return commandBuffer_.size();
|
||||
}
|
||||
|
||||
int CodeGen::reserve()
|
||||
{
|
||||
emit(NOP);
|
||||
return commandBuffer_.size() - 1;
|
||||
}
|
||||
|
||||
void CodeGen::flush()
|
||||
{
|
||||
int count = commandBuffer_.size();
|
||||
for(int address = 0; address < count; ++address) {
|
||||
commandBuffer_[address].print(address, output_);
|
||||
}
|
||||
output_.flush();
|
||||
}
|
||||
100
lab4/cmilan/src/codegen.h
Normal file
100
lab4/cmilan/src/codegen.h
Normal file
@@ -0,0 +1,100 @@
|
||||
#ifndef CMILAN_CODEGEN_H
|
||||
#define CMILAN_CODEGEN_H
|
||||
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
// Инструкции виртуальной машины Милана
|
||||
|
||||
enum Instruction
|
||||
{
|
||||
NOP, // отсутствие операции
|
||||
STOP, // остановка машины, завершение работы программы
|
||||
LOAD, // LOAD addr - загрузка слова данных в стек из памяти по адресу addr
|
||||
STORE, // STORE addr - запись слова данных с вершины стека в память по адресу addr
|
||||
BLOAD, // BLOAD addr - загрузка слова данных в стек из памяти по адресу addr + значение на вершине стека
|
||||
BSTORE, // BSTORE addr - запись слова данных по адресу addr + значение на вершине стека
|
||||
PUSH, // PUSH n - загрузка в стек константы n
|
||||
POP, // удаление слова с вершины стека
|
||||
DUP, // копирование слова на вершине стека
|
||||
ADD, // сложение двух слов на вершине стека и запись результата вместо них
|
||||
SUB, // вычитание двух слов на вершине стека и запись результата вместо них
|
||||
MULT, // умножение двух слов на вершине стека и запись результата вместо них
|
||||
DIV, // деление двух слов на вершине стека и запись результата вместо них
|
||||
INVERT, // изменение знака слова на вершине стека
|
||||
COMPARE, // COMPARE cmp - сравнение двух слов на вершине стека с помощью операции сравнения с кодом cmp
|
||||
JUMP, // JUMP addr - безусловный переход по адресу addr
|
||||
JUMP_YES, // JUMP_YES addr - переход по адресу addr, если на вершине стека значение 1
|
||||
JUMP_NO, // JUMP_NO addr - переход по адресу addr, если на вершине стека значение 0
|
||||
INPUT, // чтение целого числа со стандартного ввода и загрузка его в стек
|
||||
PRINT // печать на стандартный вывод числа с вершины стека
|
||||
};
|
||||
|
||||
// Класс Command представляет машинные инструкции.
|
||||
|
||||
class Command
|
||||
{
|
||||
public:
|
||||
// Конструктор для инструкций без аргументов
|
||||
Command(Instruction instruction)
|
||||
: instruction_(instruction), arg_(0)
|
||||
{}
|
||||
|
||||
// Конструктор для инструкций с одним аргументом
|
||||
Command(Instruction instruction, int arg)
|
||||
: instruction_(instruction), arg_(arg)
|
||||
{}
|
||||
|
||||
// Печать инструкции
|
||||
// int address - адрес инструкции
|
||||
// ostream& os - поток вывода, куда будет напечатана инструкция
|
||||
void print(int address, ostream& os);
|
||||
|
||||
private:
|
||||
Instruction instruction_; // Код инструкции
|
||||
int arg_; // Аргумент инструкции
|
||||
};
|
||||
|
||||
// Кодогенератор.
|
||||
// Назначение кодогенератора:
|
||||
// - Формировать программу для виртуальной машины Милана
|
||||
// - Отслеживать адрес последней инструкции
|
||||
// - Буферизовать программу и печатать ее в указанный поток вывода
|
||||
|
||||
class CodeGen
|
||||
{
|
||||
public:
|
||||
explicit CodeGen(ostream& output)
|
||||
: output_(output)
|
||||
{
|
||||
}
|
||||
|
||||
// Добавление инструкции без аргументов в конец программы
|
||||
void emit(Instruction instruction);
|
||||
|
||||
// Добавление инструкции с одним аргументом в конец программы
|
||||
void emit(Instruction instruction, int arg);
|
||||
|
||||
// Запись инструкции без аргументов по указанному адресу
|
||||
void emitAt(int address, Instruction instruction);
|
||||
|
||||
// Запись инструкции с одним аргументом по указанному адресу
|
||||
void emitAt(int address, Instruction instruction, int arg);
|
||||
|
||||
// Получение адреса, непосредственно следующего за последней инструкцией в программе
|
||||
int getCurrentAddress();
|
||||
|
||||
// Формирование "пустой" инструкции (NOP) и возврат ее адреса
|
||||
int reserve();
|
||||
|
||||
// Запись последовательности инструкций в выходной поток
|
||||
void flush();
|
||||
|
||||
private:
|
||||
ostream& output_; // Выходной поток
|
||||
vector<Command> commandBuffer_; // Буфер инструкций
|
||||
};
|
||||
|
||||
#endif
|
||||
BIN
lab4/cmilan/src/codegen.o
Normal file
BIN
lab4/cmilan/src/codegen.o
Normal file
Binary file not shown.
32
lab4/cmilan/src/main.cpp
Normal file
32
lab4/cmilan/src/main.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include "parser.h"
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
|
||||
using namespace std;
|
||||
|
||||
void printHelp()
|
||||
{
|
||||
cout << "Usage: cmilan input_file" << endl;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if(argc < 2) {
|
||||
printHelp();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
ifstream input;
|
||||
input.open(argv[1]);
|
||||
|
||||
if(input) {
|
||||
Parser p(argv[1], input);
|
||||
p.parse();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
else {
|
||||
cerr << "File '" << argv[1] << "' not found" << endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
BIN
lab4/cmilan/src/main.o
Normal file
BIN
lab4/cmilan/src/main.o
Normal file
Binary file not shown.
270
lab4/cmilan/src/parser.cpp
Normal file
270
lab4/cmilan/src/parser.cpp
Normal file
@@ -0,0 +1,270 @@
|
||||
#include "parser.h"
|
||||
#include <sstream>
|
||||
|
||||
//Выполняем синтаксический разбор блока program. Если во время разбора не обнаруживаем
|
||||
//никаких ошибок, то выводим последовательность команд стек-машины
|
||||
void Parser::parse()
|
||||
{
|
||||
program();
|
||||
if(!error_) {
|
||||
codegen_->flush();
|
||||
}
|
||||
}
|
||||
|
||||
void Parser::program()
|
||||
{
|
||||
mustBe(T_BEGIN);
|
||||
statementList();
|
||||
mustBe(T_END);
|
||||
codegen_->emit(STOP);
|
||||
}
|
||||
|
||||
void Parser::statementList()
|
||||
{
|
||||
// Если список операторов пуст, очередной лексемой будет одна из возможных "закрывающих скобок": END, OD, ELSE, FI.
|
||||
// В этом случае результатом разбора будет пустой блок (его список операторов равен null).
|
||||
// Если очередная лексема не входит в этот список, то ее мы считаем началом оператора и вызываем метод statement.
|
||||
// Признаком последнего оператора является отсутствие после оператора точки с запятой.
|
||||
if(see(T_END) || see(T_OD) || see(T_ELSE) || see(T_FI)) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
bool more = true;
|
||||
while(more) {
|
||||
statement();
|
||||
more = match(T_SEMICOLON);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Parser::statement()
|
||||
{
|
||||
// Если встречаем переменную, то запоминаем ее адрес или добавляем новую если не встретили.
|
||||
// Следующей лексемой должно быть присваивание. Затем идет блок expression, который возвращает значение на вершину стека.
|
||||
// Записываем это значение по адресу нашей переменной
|
||||
if(see(T_IDENTIFIER)) {
|
||||
int varAddress = findOrAddVariable(scanner_->getStringValue());
|
||||
next();
|
||||
mustBe(T_ASSIGN);
|
||||
expression();
|
||||
codegen_->emit(STORE, varAddress);
|
||||
}
|
||||
// Если встретили IF, то затем должно следовать условие. На вершине стека лежит 1 или 0 в зависимости от выполнения условия.
|
||||
// Затем зарезервируем место для условного перехода JUMP_NO к блоку ELSE (переход в случае ложного условия). Адрес перехода
|
||||
// станет известным только после того, как будет сгенерирован код для блока THEN.
|
||||
else if(match(T_IF)) {
|
||||
relation();
|
||||
|
||||
int jumpNoAddress = codegen_->reserve();
|
||||
|
||||
mustBe(T_THEN);
|
||||
statementList();
|
||||
if(match(T_ELSE)) {
|
||||
//Если есть блок ELSE, то чтобы не выполнять его в случае выполнения THEN,
|
||||
//зарезервируем место для команды JUMP в конец этого блока
|
||||
int jumpAddress = codegen_->reserve();
|
||||
//Заполним зарезервированное место после проверки условия инструкцией перехода в начало блока ELSE.
|
||||
codegen_->emitAt(jumpNoAddress, JUMP_NO, codegen_->getCurrentAddress());
|
||||
statementList();
|
||||
//Заполним второй адрес инструкцией перехода в конец условного блока ELSE.
|
||||
codegen_->emitAt(jumpAddress, JUMP, codegen_->getCurrentAddress());
|
||||
}
|
||||
else {
|
||||
//Если блок ELSE отсутствует, то в зарезервированный адрес после проверки условия будет записана
|
||||
//инструкция условного перехода в конец оператора IF...THEN
|
||||
codegen_->emitAt(jumpNoAddress, JUMP_NO, codegen_->getCurrentAddress());
|
||||
}
|
||||
|
||||
mustBe(T_FI);
|
||||
}
|
||||
|
||||
else if(match(T_WHILE)) {
|
||||
//запоминаем адрес начала проверки условия.
|
||||
int conditionAddress = codegen_->getCurrentAddress();
|
||||
relation();
|
||||
//резервируем место под инструкцию условного перехода для выхода из цикла.
|
||||
int jumpNoAddress = codegen_->reserve();
|
||||
mustBe(T_DO);
|
||||
statementList();
|
||||
mustBe(T_OD);
|
||||
//переходим по адресу проверки условия
|
||||
codegen_->emit(JUMP, conditionAddress);
|
||||
//заполняем зарезервированный адрес инструкцией условного перехода на следующий за циклом оператор.
|
||||
codegen_->emitAt(jumpNoAddress, JUMP_NO, codegen_->getCurrentAddress());
|
||||
}
|
||||
else if(match(T_WRITE)) {
|
||||
mustBe(T_LPAREN);
|
||||
expression();
|
||||
mustBe(T_RPAREN);
|
||||
codegen_->emit(PRINT);
|
||||
}
|
||||
else {
|
||||
reportError("statement expected.");
|
||||
}
|
||||
}
|
||||
|
||||
void Parser::expression()
|
||||
{
|
||||
|
||||
/*
|
||||
Арифметическое выражение описывается следующими правилами: <expression> -> <term> | <term> + <term> | <term> - <term>
|
||||
При разборе сначала смотрим первый терм, затем анализируем очередной символ. Если это '+' или '-',
|
||||
удаляем его из потока и разбираем очередное слагаемое (вычитаемое). Повторяем проверку и разбор очередного
|
||||
терма, пока не встретим за термом символ, отличный от '+' и '-'
|
||||
*/
|
||||
|
||||
term();
|
||||
while(see(T_ADDOP)) {
|
||||
Arithmetic op = scanner_->getArithmeticValue();
|
||||
next();
|
||||
term();
|
||||
|
||||
if(op == A_PLUS) {
|
||||
codegen_->emit(ADD);
|
||||
}
|
||||
else {
|
||||
codegen_->emit(SUB);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Parser::term()
|
||||
{
|
||||
/*
|
||||
Терм описывается следующими правилами: <expression> -> <factor> | <factor> + <factor> | <factor> - <factor>
|
||||
При разборе сначала смотрим первый множитель, затем анализируем очередной символ. Если это '*' или '/',
|
||||
удаляем его из потока и разбираем очередное слагаемое (вычитаемое). Повторяем проверку и разбор очередного
|
||||
множителя, пока не встретим за ним символ, отличный от '*' и '/'
|
||||
*/
|
||||
factor();
|
||||
while(see(T_MULOP)) {
|
||||
Arithmetic op = scanner_->getArithmeticValue();
|
||||
next();
|
||||
factor();
|
||||
|
||||
if(op == A_MULTIPLY) {
|
||||
codegen_->emit(MULT);
|
||||
}
|
||||
else {
|
||||
codegen_->emit(DIV);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Parser::factor()
|
||||
{
|
||||
/*
|
||||
Множитель описывается следующими правилами:
|
||||
<factor> -> number | identifier | -<factor> | (<expression>) | READ
|
||||
*/
|
||||
if(see(T_NUMBER)) {
|
||||
int value = scanner_->getIntValue();
|
||||
next();
|
||||
codegen_->emit(PUSH, value);
|
||||
//Если встретили число, то преобразуем его в целое и записываем на вершину стека
|
||||
}
|
||||
else if(see(T_IDENTIFIER)) {
|
||||
int varAddress = findOrAddVariable(scanner_->getStringValue());
|
||||
next();
|
||||
codegen_->emit(LOAD, varAddress);
|
||||
//Если встретили переменную, то выгружаем значение, лежащее по ее адресу, на вершину стека
|
||||
}
|
||||
else if(see(T_ADDOP) && scanner_->getArithmeticValue() == A_MINUS) {
|
||||
next();
|
||||
factor();
|
||||
codegen_->emit(INVERT);
|
||||
//Если встретили знак "-", и за ним <factor> то инвертируем значение, лежащее на вершине стека
|
||||
}
|
||||
else if(match(T_LPAREN)) {
|
||||
expression();
|
||||
mustBe(T_RPAREN);
|
||||
//Если встретили открывающую скобку, тогда следом может идти любое арифметическое выражение и обязательно
|
||||
//закрывающая скобка.
|
||||
}
|
||||
else if(match(T_READ)) {
|
||||
codegen_->emit(INPUT);
|
||||
//Если встретили зарезервированное слово READ, то записываем на вершину стека идет запись со стандартного ввода
|
||||
}
|
||||
else {
|
||||
reportError("expression expected.");
|
||||
}
|
||||
}
|
||||
|
||||
void Parser::relation()
|
||||
{
|
||||
//Условие сравнивает два выражения по какому-либо из знаков. Каждый знак имеет свой номер. В зависимости от
|
||||
//результата сравнения на вершине стека окажется 0 или 1.
|
||||
expression();
|
||||
if(see(T_CMP)) {
|
||||
Cmp cmp = scanner_->getCmpValue();
|
||||
next();
|
||||
expression();
|
||||
switch(cmp) {
|
||||
//для знака "=" - номер 0
|
||||
case C_EQ:
|
||||
codegen_->emit(COMPARE, 0);
|
||||
break;
|
||||
//для знака "!=" - номер 1
|
||||
case C_NE:
|
||||
codegen_->emit(COMPARE, 1);
|
||||
break;
|
||||
//для знака "<" - номер 2
|
||||
case C_LT:
|
||||
codegen_->emit(COMPARE, 2);
|
||||
break;
|
||||
//для знака ">" - номер 3
|
||||
case C_GT:
|
||||
codegen_->emit(COMPARE, 3);
|
||||
break;
|
||||
//для знака "<=" - номер 4
|
||||
case C_LE:
|
||||
codegen_->emit(COMPARE, 4);
|
||||
break;
|
||||
//для знака ">=" - номер 5
|
||||
case C_GE:
|
||||
codegen_->emit(COMPARE, 5);
|
||||
break;
|
||||
};
|
||||
}
|
||||
else {
|
||||
reportError("comparison operator expected.");
|
||||
}
|
||||
}
|
||||
|
||||
int Parser::findOrAddVariable(const string& var)
|
||||
{
|
||||
VarTable::iterator it = variables_.find(var);
|
||||
if(it == variables_.end()) {
|
||||
variables_[var] = lastVar_;
|
||||
return lastVar_++;
|
||||
}
|
||||
else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
void Parser::mustBe(Token t)
|
||||
{
|
||||
if(!match(t)) {
|
||||
error_ = true;
|
||||
|
||||
// Подготовим сообщение об ошибке
|
||||
std::ostringstream msg;
|
||||
msg << tokenToString(scanner_->token()) << " found while " << tokenToString(t) << " expected.";
|
||||
reportError(msg.str());
|
||||
|
||||
// Попытка восстановления после ошибки.
|
||||
recover(t);
|
||||
}
|
||||
}
|
||||
|
||||
void Parser::recover(Token t)
|
||||
{
|
||||
while(!see(t) && !see(T_EOF)) {
|
||||
next();
|
||||
}
|
||||
|
||||
if(see(t)) {
|
||||
next();
|
||||
}
|
||||
}
|
||||
119
lab4/cmilan/src/parser.h
Normal file
119
lab4/cmilan/src/parser.h
Normal file
@@ -0,0 +1,119 @@
|
||||
#ifndef CMILAN_PARSER_H
|
||||
#define CMILAN_PARSER_H
|
||||
|
||||
#include "scanner.h"
|
||||
#include "codegen.h"
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
using namespace std;
|
||||
|
||||
/* Синтаксический анализатор.
|
||||
*
|
||||
* Задачи:
|
||||
* - проверка корректности программы,
|
||||
* - генерация кода для виртуальной машины в процессе анализа,
|
||||
* - простейшее восстановление после ошибок.
|
||||
*
|
||||
* Синтаксический анализатор языка Милан.
|
||||
*
|
||||
* Парсер с помощью переданного ему при инициализации лексического анализатора
|
||||
* читает по одной лексеме и на основе грамматики Милана генерирует код для
|
||||
* стековой виртуальной машины. Синтаксический анализ выполняется методом
|
||||
* рекурсивного спуска.
|
||||
*
|
||||
* При обнаружении ошибки парсер печатает сообщение и продолжает анализ со
|
||||
* следующего оператора, чтобы в процессе разбора найти как можно больше ошибок.
|
||||
* Поскольку стратегия восстановления после ошибки очень проста, возможна печать
|
||||
* сообщений о несуществующих ("наведенных") ошибках или пропуск некоторых
|
||||
* ошибок без печати сообщений. Если в процессе разбора была найдена хотя бы
|
||||
* одна ошибка, код для виртуальной машины не печатается.*/
|
||||
|
||||
class Parser
|
||||
{
|
||||
public:
|
||||
// Конструктор
|
||||
// const string& fileName - имя файла с программой для анализа
|
||||
//
|
||||
// Конструктор создает экземпляры лексического анализатора и генератора.
|
||||
|
||||
Parser(const string& fileName, istream& input)
|
||||
: output_(cout), error_(false), recovered_(true), lastVar_(0)
|
||||
{
|
||||
scanner_ = new Scanner(fileName, input);
|
||||
codegen_ = new CodeGen(output_);
|
||||
next();
|
||||
}
|
||||
|
||||
~Parser()
|
||||
{
|
||||
delete codegen_;
|
||||
delete scanner_;
|
||||
}
|
||||
|
||||
void parse(); //проводим синтаксический разбор
|
||||
|
||||
private:
|
||||
typedef map<string, int> VarTable;
|
||||
//описание блоков.
|
||||
void program(); //Разбор программы. BEGIN statementList END
|
||||
void statementList(); // Разбор списка операторов.
|
||||
void statement(); //разбор оператора.
|
||||
void expression(); //разбор арифметического выражения.
|
||||
void term(); //разбор слагаемого.
|
||||
void factor(); //разбор множителя.
|
||||
void relation(); //разбор условия.
|
||||
|
||||
// Сравнение текущей лексемы с образцом. Текущая позиция в потоке лексем не изменяется.
|
||||
bool see(Token t)
|
||||
{
|
||||
return scanner_->token() == t;
|
||||
}
|
||||
|
||||
// Проверка совпадения текущей лексемы с образцом. Если лексема и образец совпадают,
|
||||
// лексема изымается из потока.
|
||||
|
||||
bool match(Token t)
|
||||
{
|
||||
if(scanner_->token() == t) {
|
||||
scanner_->nextToken();
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Переход к следующей лексеме.
|
||||
|
||||
void next()
|
||||
{
|
||||
scanner_->nextToken();
|
||||
}
|
||||
|
||||
// Обработчик ошибок.
|
||||
void reportError(const string& message)
|
||||
{
|
||||
cerr << "Line " << scanner_->getLineNumber() << ": " << message << endl;
|
||||
error_ = true;
|
||||
}
|
||||
|
||||
void mustBe(Token t); //проверяем, совпадает ли данная лексема с образцом. Если да, то лексема изымается из потока.
|
||||
//Иначе создаем сообщение об ошибке и пробуем восстановиться
|
||||
void recover(Token t); //восстановление после ошибки: идем по коду до тех пор,
|
||||
//пока не встретим эту лексему или лексему конца файла.
|
||||
int findOrAddVariable(const string&); //функция пробегает по variables_.
|
||||
//Если находит нужную переменную - возвращает ее номер, иначе добавляет ее в массив, увеличивает lastVar и возвращает его.
|
||||
|
||||
Scanner* scanner_; //лексический анализатор для конструктора
|
||||
CodeGen* codegen_; //указатель на виртуальную машину
|
||||
ostream& output_; //выходной поток (в данном случае используем cout)
|
||||
bool error_; //флаг ошибки. Используется чтобы определить, выводим ли список команд после разбора или нет
|
||||
bool recovered_; //не используется
|
||||
VarTable variables_; //массив переменных, найденных в программе
|
||||
int lastVar_; //номер последней записанной переменной
|
||||
};
|
||||
|
||||
#endif
|
||||
BIN
lab4/cmilan/src/parser.o
Normal file
BIN
lab4/cmilan/src/parser.o
Normal file
Binary file not shown.
233
lab4/cmilan/src/scanner.cpp
Normal file
233
lab4/cmilan/src/scanner.cpp
Normal file
@@ -0,0 +1,233 @@
|
||||
#include "scanner.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <cctype>
|
||||
|
||||
using namespace std;
|
||||
|
||||
static const char * tokenNames_[] = {
|
||||
"end of file",
|
||||
"illegal token",
|
||||
"identifier",
|
||||
"number",
|
||||
"'BEGIN'",
|
||||
"'END'",
|
||||
"'IF'",
|
||||
"'THEN'",
|
||||
"'ELSE'",
|
||||
"'FI'",
|
||||
"'WHILE'",
|
||||
"'DO'",
|
||||
"'OD'",
|
||||
"'WRITE'",
|
||||
"'READ'",
|
||||
"':='",
|
||||
"'+' or '-'",
|
||||
"'*' or '/'",
|
||||
"comparison operator",
|
||||
"'('",
|
||||
"')'",
|
||||
"';'",
|
||||
};
|
||||
|
||||
void Scanner::nextToken()
|
||||
{
|
||||
skipSpace();
|
||||
|
||||
// Пропускаем комментарии
|
||||
// Если встречаем "/", то за ним должна идти "*". Если "*" не встречена, считаем, что встретили операцию деления
|
||||
// и лексему - операция типа умножения. Дальше смотрим все символы, пока не находим звездочку или символ конца файла.
|
||||
// Если нашли * - проверяем на наличие "/" после нее. Если "/" не найден - ищем следующую "*".
|
||||
while(ch_ == '/') {
|
||||
nextChar();
|
||||
if(ch_ == '*') {
|
||||
nextChar();
|
||||
bool inside = true;
|
||||
while(inside) {
|
||||
while(ch_ != '*' && !input_.eof()) {
|
||||
nextChar();
|
||||
}
|
||||
|
||||
if(input_.eof()) {
|
||||
token_ = T_EOF;
|
||||
return;
|
||||
}
|
||||
|
||||
nextChar();
|
||||
if(ch_ == '/') {
|
||||
inside = false;
|
||||
nextChar();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
token_ = T_MULOP;
|
||||
arithmeticValue_ = A_DIVIDE;
|
||||
return;
|
||||
}
|
||||
|
||||
skipSpace();
|
||||
}
|
||||
|
||||
//Если встречен конец файла, считаем за лексему конца файла.
|
||||
if(input_.eof()) {
|
||||
token_ = T_EOF;
|
||||
return;
|
||||
}
|
||||
//Если встретили цифру, то до тех пока дальше идут цифры - считаем как продолжение числа.
|
||||
//Запоминаем полученное целое, а за лексему считаем целочисленный литерал
|
||||
|
||||
if(isdigit(ch_)) {
|
||||
int value = 0;
|
||||
while(isdigit(ch_)) {
|
||||
value = value * 10 + (ch_ - '0'); //поразрядное считывание, преобразуем символьное значение к числу.
|
||||
nextChar();
|
||||
}
|
||||
token_ = T_NUMBER;
|
||||
intValue_ = value;
|
||||
}
|
||||
//Если же следующий символ - буква ЛА - тогда считываем до тех пор, пока дальше буквы ЛА или цифры.
|
||||
//Как только считали имя переменной, сравниваем ее со списком зарезервированных слов. Если не совпадает ни с одним из них,
|
||||
//считаем, что получили переменную, имя которой запоминаем, а за текущую лексему считаем лексему идентификатора.
|
||||
//Если совпадает с каким-либо словом из списка - считаем что получили лексему, соответствующую этому слову.
|
||||
else if(isIdentifierStart(ch_)) {
|
||||
string buffer;
|
||||
while(isIdentifierBody(ch_)) {
|
||||
buffer += ch_;
|
||||
nextChar();
|
||||
}
|
||||
|
||||
transform(buffer.begin(), buffer.end(), buffer.begin(), ::tolower);
|
||||
|
||||
map<string, Token>::iterator kwd = keywords_.find(buffer);
|
||||
if(kwd == keywords_.end()) {
|
||||
token_ = T_IDENTIFIER;
|
||||
stringValue_ = buffer;
|
||||
}
|
||||
else {
|
||||
token_ = kwd->second;
|
||||
}
|
||||
}
|
||||
//Символ не является буквой, цифрой, "/" или признаком конца файла
|
||||
else {
|
||||
switch(ch_) {
|
||||
//Признак лексемы открывающей скобки - встретили "("
|
||||
case '(':
|
||||
token_ = T_LPAREN;
|
||||
nextChar();
|
||||
break;
|
||||
//Признак лексемы закрывающей скобки - встретили ")"
|
||||
case ')':
|
||||
token_ = T_RPAREN;
|
||||
nextChar();
|
||||
break;
|
||||
//Признак лексемы ";" - встретили ";"
|
||||
case ';':
|
||||
token_ = T_SEMICOLON;
|
||||
nextChar();
|
||||
break;
|
||||
//Если встречаем ":", то дальше смотрим наличие символа "=". Если находим, то считаем что нашли лексему присваивания
|
||||
//Иначе - лексема ошибки.
|
||||
case ':':
|
||||
nextChar();
|
||||
if(ch_ == '=') {
|
||||
token_ = T_ASSIGN;
|
||||
nextChar();
|
||||
|
||||
}
|
||||
else {
|
||||
token_ = T_ILLEGAL;
|
||||
}
|
||||
break;
|
||||
//Если встретили символ "<", то либо следующий символ "=", тогда лексема нестрогого сравнения. Иначе - строгого.
|
||||
case '<':
|
||||
token_ = T_CMP;
|
||||
nextChar();
|
||||
if(ch_ == '=') {
|
||||
cmpValue_ = C_LE;
|
||||
nextChar();
|
||||
}
|
||||
else {
|
||||
cmpValue_ = C_LT;
|
||||
}
|
||||
break;
|
||||
//Аналогично предыдущему случаю
|
||||
case '>':
|
||||
token_ = T_CMP;
|
||||
nextChar();
|
||||
if(ch_ == '=') {
|
||||
cmpValue_ = C_GE;
|
||||
nextChar();
|
||||
}
|
||||
else {
|
||||
cmpValue_ = C_GT;
|
||||
}
|
||||
break;
|
||||
//Если встретим "!", то дальше должно быть "=", тогда считаем, что получили лексему сравнения
|
||||
//и знак "!=" иначе считаем, что у нас лексема ошибки
|
||||
case '!':
|
||||
nextChar();
|
||||
if(ch_ == '=') {
|
||||
nextChar();
|
||||
token_ = T_CMP;
|
||||
cmpValue_ = C_NE;
|
||||
}
|
||||
else {
|
||||
token_ = T_ILLEGAL;
|
||||
}
|
||||
break;
|
||||
//Если встретим "=" - лексема сравнения и знак "="
|
||||
case '=':
|
||||
token_ = T_CMP;
|
||||
cmpValue_ = C_EQ;
|
||||
nextChar();
|
||||
break;
|
||||
//Знаки операций. Для "+"/"-" получим лексему операции типа сложнения, и соответствующую операцию.
|
||||
//для "*" - лексему операции типа умножения
|
||||
case '+':
|
||||
token_ = T_ADDOP;
|
||||
arithmeticValue_ = A_PLUS;
|
||||
nextChar();
|
||||
break;
|
||||
|
||||
case '-':
|
||||
token_ = T_ADDOP;
|
||||
arithmeticValue_ = A_MINUS;
|
||||
nextChar();
|
||||
break;
|
||||
|
||||
case '*':
|
||||
token_ = T_MULOP;
|
||||
arithmeticValue_ = A_MULTIPLY;
|
||||
nextChar();
|
||||
break;
|
||||
//Иначе лексема ошибки.
|
||||
default:
|
||||
token_ = T_ILLEGAL;
|
||||
nextChar();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Scanner::skipSpace()
|
||||
{
|
||||
while(isspace(ch_)) {
|
||||
if(ch_ == '\n') {
|
||||
++lineNumber_;
|
||||
}
|
||||
|
||||
nextChar();
|
||||
}
|
||||
}
|
||||
|
||||
void Scanner::nextChar()
|
||||
{
|
||||
ch_ = input_.get();
|
||||
}
|
||||
|
||||
const char * tokenToString(Token t)
|
||||
{
|
||||
return tokenNames_[t];
|
||||
}
|
||||
|
||||
164
lab4/cmilan/src/scanner.h
Normal file
164
lab4/cmilan/src/scanner.h
Normal file
@@ -0,0 +1,164 @@
|
||||
#ifndef CMILAN_SCANNER_H
|
||||
#define CMILAN_SCANNER_H
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
using namespace std;
|
||||
|
||||
enum Token {
|
||||
T_EOF, // Конец текстового потока
|
||||
T_ILLEGAL, // Признак недопустимого символа
|
||||
T_IDENTIFIER, // Идентификатор
|
||||
T_NUMBER, // Целочисленный литерал
|
||||
T_BEGIN, // Ключевое слово "begin"
|
||||
T_END, // Ключевое слово "end"
|
||||
T_IF, // Ключевое слово "if"
|
||||
T_THEN, // Ключевое слово "then"
|
||||
T_ELSE, // Ключевое слово "else"
|
||||
T_FI, // Ключевое слово "fi"
|
||||
T_WHILE, // Ключевое слово "while"
|
||||
T_DO, // Ключевое слово "do"
|
||||
T_OD, // Ключевое слово "od"
|
||||
T_WRITE, // Ключевое слово "write"
|
||||
T_READ, // Ключевое слово "read"
|
||||
T_ASSIGN, // Оператор ":="
|
||||
T_ADDOP, // Сводная лексема для "+" и "-" (операция типа сложения)
|
||||
T_MULOP, // Сводная лексема для "*" и "/" (операция типа умножения)
|
||||
T_CMP, // Сводная лексема для операторов отношения
|
||||
T_LPAREN, // Открывающая скобка
|
||||
T_RPAREN, // Закрывающая скобка
|
||||
T_SEMICOLON // ";"
|
||||
};
|
||||
|
||||
// Функция tokenToString возвращает описание лексемы.
|
||||
// Используется при печати сообщения об ошибке.
|
||||
const char * tokenToString(Token t);
|
||||
|
||||
// Виды операций сравнения
|
||||
enum Cmp {
|
||||
C_EQ, // Операция сравнения "="
|
||||
C_NE, // Операция сравнения "!="
|
||||
C_LT, // Операция сравнения "<"
|
||||
C_LE, // Операция сравнения "<="
|
||||
C_GT, // Операция сравнения ">"
|
||||
C_GE // Операция сравнения ">="
|
||||
};
|
||||
|
||||
// Виды арифметических операций
|
||||
enum Arithmetic {
|
||||
A_PLUS, //операция "+"
|
||||
A_MINUS, //операция "-"
|
||||
A_MULTIPLY, //операция "*"
|
||||
A_DIVIDE //операция "/"
|
||||
};
|
||||
|
||||
// Лексический анализатор
|
||||
|
||||
class Scanner
|
||||
{
|
||||
public:
|
||||
// Конструктор. В качестве аргумента принимает имя файла и поток,
|
||||
// из которого будут читаться символы транслируемой программы.
|
||||
|
||||
explicit Scanner(const string& fileName, istream& input)
|
||||
: fileName_(fileName), lineNumber_(1), input_(input)
|
||||
{
|
||||
keywords_["begin"] = T_BEGIN;
|
||||
keywords_["end"] = T_END;
|
||||
keywords_["if"] = T_IF;
|
||||
keywords_["then"] = T_THEN;
|
||||
keywords_["else"] = T_ELSE;
|
||||
keywords_["fi"] = T_FI;
|
||||
keywords_["while"] = T_WHILE;
|
||||
keywords_["do"] = T_DO;
|
||||
keywords_["od"] = T_OD;
|
||||
keywords_["write"] = T_WRITE;
|
||||
keywords_["read"] = T_READ;
|
||||
|
||||
nextChar();
|
||||
}
|
||||
|
||||
// Деструктор
|
||||
virtual ~Scanner()
|
||||
{}
|
||||
|
||||
//getters всех private переменных
|
||||
const string& getFileName() const //не используется
|
||||
{
|
||||
return fileName_;
|
||||
}
|
||||
|
||||
int getLineNumber() const
|
||||
{
|
||||
return lineNumber_;
|
||||
}
|
||||
|
||||
Token token() const
|
||||
{
|
||||
return token_;
|
||||
}
|
||||
|
||||
int getIntValue() const
|
||||
{
|
||||
return intValue_;
|
||||
}
|
||||
|
||||
string getStringValue() const
|
||||
{
|
||||
return stringValue_;
|
||||
}
|
||||
|
||||
Cmp getCmpValue() const
|
||||
{
|
||||
return cmpValue_;
|
||||
}
|
||||
|
||||
Arithmetic getArithmeticValue() const
|
||||
{
|
||||
return arithmeticValue_;
|
||||
}
|
||||
|
||||
// Переход к следующей лексеме.
|
||||
// Текущая лексема записывается в token_ и изымается из потока.
|
||||
void nextToken();
|
||||
private:
|
||||
|
||||
// Пропуск всех пробельные символы.
|
||||
// Если встречается символ перевода строки, номер текущей строки
|
||||
// (lineNumber) увеличивается на единицу.
|
||||
void skipSpace();
|
||||
|
||||
|
||||
void nextChar(); //переходит к следующему символу
|
||||
//проверка переменной на первый символ (должен быть буквой латинского алфавита)
|
||||
bool isIdentifierStart(char c)
|
||||
{
|
||||
return ((c >= 'a' && c <= 'z') ||
|
||||
(c >= 'A' && c <= 'Z'));
|
||||
}
|
||||
//проверка на остальные символы переменной (буква или цифра)
|
||||
bool isIdentifierBody(char c)
|
||||
{
|
||||
return isIdentifierStart(c) || isdigit(c);
|
||||
}
|
||||
|
||||
|
||||
const string fileName_; //входной файл
|
||||
int lineNumber_; //номер текущей строки кода
|
||||
|
||||
Token token_; //текущая лексема
|
||||
int intValue_; //значение текущего целого
|
||||
string stringValue_; //имя переменной
|
||||
Cmp cmpValue_; //значение оператора сравнения (>, <, =, !=, >=, <=)
|
||||
Arithmetic arithmeticValue_; //значение знака (+,-,*,/)
|
||||
|
||||
map<string, Token> keywords_; //ассоциативный массив с лексемами и
|
||||
//соответствующими им зарезервированными словами в качестве индексов
|
||||
|
||||
istream& input_; //входной поток для чтения из файла.
|
||||
char ch_; //текущий символ
|
||||
};
|
||||
|
||||
#endif
|
||||
BIN
lab4/cmilan/src/scanner.o
Normal file
BIN
lab4/cmilan/src/scanner.o
Normal file
Binary file not shown.
7
lab4/cmilan/test/add.mil
Normal file
7
lab4/cmilan/test/add.mil
Normal file
@@ -0,0 +1,7 @@
|
||||
BEGIN
|
||||
i := 1;
|
||||
j := 2;
|
||||
k := i + j;
|
||||
WRITE(k)
|
||||
END
|
||||
|
||||
14
lab4/cmilan/test/comment.mil
Normal file
14
lab4/cmilan/test/comment.mil
Normal file
@@ -0,0 +1,14 @@
|
||||
/* это комментарий */
|
||||
/* это продолжение комментария */
|
||||
|
||||
/*****************************
|
||||
* эта программа печатает 42 *
|
||||
****************************/
|
||||
|
||||
begin /* комментарий в конце строки */
|
||||
/* комментарий в начале строки */ write(42)
|
||||
/* многострочный
|
||||
комментарий */
|
||||
end
|
||||
|
||||
/* комментарий в конце файла */
|
||||
10
lab4/cmilan/test/factorial.mil
Normal file
10
lab4/cmilan/test/factorial.mil
Normal file
@@ -0,0 +1,10 @@
|
||||
BEGIN
|
||||
n := READ;
|
||||
factorial := 1;
|
||||
i := 1;
|
||||
WHILE i <= n DO
|
||||
factorial := factorial * i;
|
||||
i := i + 1
|
||||
OD;
|
||||
WRITE(factorial)
|
||||
END
|
||||
17
lab4/cmilan/test/fib.mil
Normal file
17
lab4/cmilan/test/fib.mil
Normal file
@@ -0,0 +1,17 @@
|
||||
/* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> N-<2D><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
|
||||
|
||||
BEGIN
|
||||
a := 1;
|
||||
b := 1;
|
||||
n := READ;
|
||||
|
||||
WHILE n > 1 DO
|
||||
t := a;
|
||||
a := b;
|
||||
b := b + t;
|
||||
n := n - 1
|
||||
OD;
|
||||
|
||||
WRITE(a)
|
||||
END
|
||||
|
||||
17
lab4/cmilan/test/gcd.mil
Normal file
17
lab4/cmilan/test/gcd.mil
Normal file
@@ -0,0 +1,17 @@
|
||||
/* Greatest common divisor */
|
||||
|
||||
BEGIN
|
||||
a := READ;
|
||||
b := READ;
|
||||
|
||||
WHILE a != b DO
|
||||
IF a < b THEN
|
||||
b := b - a
|
||||
ELSE
|
||||
a := a - b
|
||||
FI
|
||||
OD;
|
||||
|
||||
WRITE(a)
|
||||
END
|
||||
|
||||
8
lab4/cmilan/test/if.mil
Normal file
8
lab4/cmilan/test/if.mil
Normal file
@@ -0,0 +1,8 @@
|
||||
BEGIN
|
||||
i := 1;
|
||||
j := 2;
|
||||
|
||||
IF i < j THEN WRITE(i) FI;
|
||||
IF i < j THEN WRITE(i) ELSE WRITE(j) FI
|
||||
END
|
||||
|
||||
5
lab4/cmilan/test/invalid.mil
Normal file
5
lab4/cmilan/test/invalid.mil
Normal file
@@ -0,0 +1,5 @@
|
||||
BEGIN
|
||||
IF 1 2 THEN WRITE(n) ELSE WRITE(2) FI;
|
||||
X := Y + 1;
|
||||
WRITE (X +)
|
||||
END
|
||||
14
lab4/cmilan/test/invert.mil
Normal file
14
lab4/cmilan/test/invert.mil
Normal file
@@ -0,0 +1,14 @@
|
||||
BEGIN
|
||||
x := READ;
|
||||
x := -x;
|
||||
|
||||
WRITE(-x);
|
||||
|
||||
IF -x > 0 THEN x := 1 FI;
|
||||
|
||||
x := x + -1;
|
||||
x := x - -x;
|
||||
|
||||
x := x -1
|
||||
END
|
||||
|
||||
17
lab4/cmilan/test/loop.mil
Normal file
17
lab4/cmilan/test/loop.mil
Normal file
@@ -0,0 +1,17 @@
|
||||
BEGIN
|
||||
/* Read a number */
|
||||
|
||||
i := READ;
|
||||
|
||||
/* Count from 0 to this number */
|
||||
|
||||
IF i > 0 THEN step := 1 ELSE step := -1 FI;
|
||||
k := 0;
|
||||
WHILE k != i DO
|
||||
WRITE(k);
|
||||
k := k + step
|
||||
OD;
|
||||
|
||||
WRITE(i)
|
||||
END
|
||||
|
||||
14
lab4/cmilan/test/power.mil
Normal file
14
lab4/cmilan/test/power.mil
Normal file
@@ -0,0 +1,14 @@
|
||||
BEGIN
|
||||
N := READ;
|
||||
P := READ;
|
||||
|
||||
S := 1;
|
||||
|
||||
WHILE P > 0 DO
|
||||
S := S * N;
|
||||
P := P - 1
|
||||
OD;
|
||||
|
||||
WRITE(S)
|
||||
END
|
||||
|
||||
8
lab4/cmilan/test/read2.mil
Normal file
8
lab4/cmilan/test/read2.mil
Normal file
@@ -0,0 +1,8 @@
|
||||
BEGIN
|
||||
IF READ < READ
|
||||
THEN
|
||||
WRITE(1)
|
||||
ELSE
|
||||
WRITE(2)
|
||||
FI
|
||||
END
|
||||
9
lab4/cmilan/test/sum.mil
Normal file
9
lab4/cmilan/test/sum.mil
Normal file
@@ -0,0 +1,9 @@
|
||||
BEGIN
|
||||
i := 8;
|
||||
j := 2;
|
||||
|
||||
WHILE i != j DO
|
||||
IF i < j THEN j := j - i ELSE i := i - j FI
|
||||
OD;
|
||||
WRITE(i)
|
||||
END
|
||||
Reference in New Issue
Block a user