33 Commits
lab2 ... main

Author SHA1 Message Date
6a95dc6c9d Добавил пример с --x++ 2025-05-30 13:17:44 +03:00
78dddbc047 Добавил упоминание КС грамматики и рекурсивного спука в заключении 2025-05-30 13:17:17 +03:00
e0211cb6c5 Выделил цветом новый продукции 2025-05-30 13:06:01 +03:00
d61b1b7a2d заменил -- на textminus 2025-05-30 12:50:32 +03:00
c001ccb12a лаб 4 2025-05-28 14:19:58 +03:00
fdd30a8854 Инкремент и декремент в компиляторе 2025-05-28 12:26:04 +03:00
35cf9b5ed4 Добавил инкремент и декремент в лексер 2025-05-28 12:07:32 +03:00
e3918eb442 Исправил ошибки в исходной грамматике 2025-05-27 22:26:20 +03:00
207b428ad9 Лаб 4 - Матописание 2025-05-27 13:32:11 +03:00
09da4c5c62 Правки: добавил про модификацию грамматики 2025-05-25 15:31:50 +03:00
cc5fbb2163 Убрал следы другой лабы 2025-05-25 15:22:39 +03:00
037a4cd8a0 Правки: семантические действия через стрелочку 2025-05-25 15:18:12 +03:00
b64f7a3205 Правки: значение прочерка 2025-05-25 15:11:01 +03:00
b835502ed4 Дерево разбора 2025-05-25 15:10:41 +03:00
796ac33829 remove .o files 2025-05-22 15:39:26 +03:00
7520f5ec2f команды 2025-05-22 15:33:56 +03:00
6da094ebbf Компилятор и вм милана 2025-05-22 15:32:47 +03:00
9cad2ede63 Обновил картинку с результатом 2025-05-22 12:59:52 +03:00
6d4301a5d6 Заключение 2025-05-22 11:17:59 +03:00
0fb5794fb6 Отчёт по 5 без заключения 2025-05-21 21:10:57 +03:00
fb67acc55e Семантические действия 2025-05-21 20:40:34 +03:00
4448c675dd Скопировал прогу из lab3 2025-05-21 17:49:18 +03:00
97a78a4ccc Варианты лаб 4 2025-05-21 17:48:15 +03:00
5bd140cd39 Правки и программа в одном файле 2025-05-20 19:32:48 +03:00
a60d2a9db7 Отчёт по lab3 2025-05-14 21:39:37 +03:00
96c9e6db53 Предобработка 2025-05-14 11:58:04 +03:00
681b56b31b Точка в конце и удаление лишних пробелов 2025-05-14 11:29:34 +03:00
d7a0e400cd нормальный интерфейс в lab3 2025-05-14 11:15:28 +03:00
3c09cc0e9b Генерация случайной строки 2025-05-06 16:15:52 +03:00
529da8d98e Распознавание и ll(1) тема 2025-05-06 15:40:22 +03:00
c186941dce Название дисциплины 2025-04-29 19:21:39 +03:00
20782a2473 Правки по lab1 2025-04-29 13:13:11 +03:00
3963d304ec Правки по lab2 2025-04-28 22:57:53 +03:00
108 changed files with 12170 additions and 126 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -34,8 +34,15 @@ class LexemeType:
class Lexer:
def __init__(self, lexeme_types: Iterable[LexemeType]):
def __init__(
self,
lexeme_types: Iterable[LexemeType],
error_regex: str,
skip_types: Iterable[str] = [],
):
self.lexeme_types = lexeme_types
self.skip_types = skip_types
self.error_regex = re.compile(r"\s*(" + error_regex + ")")
def analyze(self, text: str) -> list[Lexem]:
lexems: list[Lexem] = []
@@ -43,16 +50,16 @@ class Lexer:
for lex_type in self.lexeme_types:
lexem, new_text = lex_type.consume(text)
if lexem:
if lexem.type_name not in self.skip_types:
lexems.append(lexem)
text = new_text
break
else:
error_lexeme, text = self._consume_error(text)
lexems.append(error_lexeme)
return lexems
def _consume_error(self, text: str) -> tuple[Lexem, str]:
match = re.match(r"\s*(\S+)", text)
match = self.error_regex.match(text)
err_text = match.group(1) if match else text.strip()
print(f"Недопустимая лексема: {err_text}")
rest = text[match.end() :] if match else ""

View File

@@ -2,9 +2,8 @@ import math
import os
from typing import Callable
from prettytable import PrettyTable
from lexer import LexemeType, Lexer
from prettytable import PrettyTable
class IdentifierMapper:
@@ -35,30 +34,34 @@ def exp_form_to_complex(exp_str: str) -> str:
LEXEME_TYPES: dict[str, LexemeType] = {
# 1. Идентификаторы
"IDENTIFIER": LexemeType(
"IDENTIFIER", r"[A-Za-z_][A-Za-z_0-9]{0,15}(?![A-Za-z_0-9])", IdentifierMapper()
"IDENTIFIER", r"[A-Za-z][A-Za-z_0-9]{0,15}(?![A-Za-z_0-9])", IdentifierMapper()
),
# 2. Комплексные числа в показательной форме (на самом деле — числа в экспоненциальной форме)
"COMPLEX": LexemeType(
# "COMPLEX", r"[+-]?\d+(?:\.\d+)?E[+-]?\d+(?!E)", exp_form_to_complex
"COMPLEX",
r"[+-]?\d+(?:\.\d+)?E[+-]?\d+(?:\.\d+)?(?!E)",
r"[+-]?\d+(?:\.\d+)?E[+-]?\d+(?:\.\d+)?",
exp_form_to_complex,
),
# 3. Оператор присваивания :=
"ASSIGN": LexemeType("ASSIGN", r"\:=(?![\:=])"),
"ASSIGN": LexemeType("ASSIGN", r"\:="),
# 4. Арифметические операторы
"ARITHMETIC_OP": LexemeType("ARITHMETIC_OP", r"[+\-*/^]"),
# 5. Скобки
"PAREN": LexemeType("PAREN", r"[()]"),
# 6. Разделитель выражений |
# 5. Левая скобка
"LPAREN": LexemeType("LPAREN", r"\("),
# 6. Правая скобка
"RPAREN": LexemeType("RPAREN", r"\)"),
# 7. Разделитель выражений |
"SEPARATOR": LexemeType("SEPARATOR", r"\|"),
# 7. Комментарии от # до конца строки
# 8. Комментарии от # до конца строки
"COMMENT": LexemeType("COMMENT", r"\#.*"),
}
ERROR_REGEX = r".[^|()+\-*/^:=\s]*"
def analyze_and_print_table(code: str):
lexer = Lexer(LEXEME_TYPES.values())
lexer = Lexer(LEXEME_TYPES.values(), ERROR_REGEX, skip_types=["COMMENT"])
lexemes = lexer.analyze(code)
table = PrettyTable(["Лексема", "Тип лексемы", "Значение"])

View File

@@ -114,7 +114,7 @@
\large{Лабораторная работа №1}\\
\large{<<Создание лексического анализатора>>}\\
\large{по дисциплине}\\
\large{<<Математическая логика>>}\\
\large{<<Математическая логика и теория автоматов>>}\\
\large{Вариант 15}\\
% \hfill \break
@@ -224,7 +224,9 @@
\item \textbf{Арифметические операторы} --- знаки \texttt{+}, \texttt{-}, \texttt{*}, \texttt{/}, \texttt{\^} для выполнения соответствующих математических операций.
\item \textbf{Скобки} --- круглые скобки \texttt{(} и \texttt{)}, используемые для задания порядка вычислений в арифметических выражениях.
\item \textbf{Левая скобка} --- символ \texttt{(}, используемый для задания порядка вычислений в арифметических выражениях.
\item \textbf{Правая скобка} --- символ \texttt{)}, используемый для задания порядка вычислений в арифметических выражениях.
\item \textbf{Разделитель выражений} --- символ вертикальной черты \texttt{|}, отделяющий одно арифметическое выражение от другого.
@@ -284,21 +286,21 @@
\begin{enumerate}
\item \textbf{Идентификаторы.}
\begin{center}
$R_{\text{Identifier}}$ = [A-Za-z\_][A-Za-z\_0-9]\{0,15\}(?![A-Za-z\_0-9])
$R_{\text{Identifier}}$ = [A-Za-z][A-Za-z\_0-9]\{0,15\}(?![A-Za-z\_0-9])
\end{center}
Первый символ — латинская буква или подчёркивание. Далее допускается до 15 символов, включая буквы, цифры и подчёркивания. В конце используется \textit{negative lookahead}, чтобы за идентификатором не следовал символ, допустимый внутри него (иначе это была бы часть более длинного идентификатора).
Первый символ — латинская буква. Далее допускается до 15 символов, включая буквы, цифры и подчёркивания. В конце используется \textit{negative lookahead}, чтобы за идентификатором не следовал символ, допустимый внутри него (иначе это была бы часть более длинного идентификатора).
\item \textbf{Комплексные числа в показательной форме.}
\begin{center}
$R_{\text{Complex}}$ = [+-]?\textbackslash{}d+(?:\textbackslash{}.\textbackslash{}d+)?E[+-]?\textbackslash{}d+(?:\textbackslash{}.\textbackslash{}d+)?(?!E)
$R_{\text{Complex}}$ = [+-]?\textbackslash{}d+(?:\textbackslash{}.\textbackslash{}d+)?E[+-]?\textbackslash{}d+(?:\textbackslash{}.\textbackslash{}d+)?
\end{center}
Опциональный знак в начале числа, целое или десятичное число до \texttt{E}, затем обязательный символ \texttt{E}, снова опциональный знак, и целое или десятичное число. В конце используется \textit{negative lookahead}, чтобы за числом не мог сразу следовать символ \texttt{E}.
Опциональный знак в начале числа, целое или десятичное число до \texttt{E}, затем обязательный символ \texttt{E}, снова опциональный знак, и целое или десятичное число.
\item \textbf{Оператор присваивания.}
\begin{center}
$R_{\text{Assign}}$ = \textbackslash{}:=(?![\textbackslash{}:=])
$R_{\text{Assign}}$ = \textbackslash{}:=
\end{center}
\item \textbf{Арифметические операторы.}
@@ -306,9 +308,14 @@
$R_{\text{ArithmeticOp}}$ = [+\textbackslash{}-\textbackslash{}*/\textbackslash{}\^{}]
\end{center}
\item \textbf{Скобки.}
\item \textbf{Левая скобка.}
\begin{center}
$R_{\text{Paren}}$ = [()]
$R_{\text{LeftParen}}$ = \textbackslash{}(
\end{center}
\item \textbf{Правая скобка.}
\begin{center}
$R_{\text{RightParen}}$ = \textbackslash{})
\end{center}
\item \textbf{Разделитель выражений.}
@@ -324,9 +331,9 @@
В класс ошибок попадают все лексемы, неподошедшие ни под какой другой класс. Некорректная лексема считывается с помощью следующего регулярного выражения:
\begin{center}
$R_{\text{Error}}$ = \textbackslash{}S+
$R_{\text{Error}}$ = .[\textasciicircum|()+\textbackslash{}-*/\textasciicircum{}:=\textbackslash{}s]*
\end{center}
Это регулярное выражение считывает все непробельные символы до первого пробельного символа.
Это регулярное выражение считывает как минимум один символ, чтобы анализ текста не завис. Затем регулярное выражение считывает все символы до первого пробела, арифметического оператора, левой или правой скобки, или оператора присваивания.
\subsection{Алгоритм лексического анализа}
Алгоритм лексического анализа состоит из следующих шагов:
@@ -407,19 +414,30 @@ class LexemeType:
\end{lstlisting}
\subsection{Класс Lexer}
Класс Lexer представляет собой лексический анализатор. Код определения класса представлен в листинге~\ref{lst:Lexer}. В классе всего одно поле:
\texttt{lexeme\_types} -- список объектов класса \texttt{LexemeType}.
Класс Lexer представляет собой лексический анализатор. Код определения класса представлен в листинге~\ref{lst:Lexer}. В классе определены следующие поля:
\begin{itemize}
\item \texttt{lexeme\_types} -- список объектов класса \texttt{LexemeType}
\item \texttt{error\_regex} -- строка, содержащая регулярное выражение для захвата ошибочных лексем
\item \texttt{skip\_types} -- список строк с названиями типов лексем, которые следует пропускать при анализе (например, комментарии)
\end{itemize}
В классе определено два метода: \texttt{analyze} и вспомогательный \texttt{\_consume\_error}.
Метод \texttt{analyze} выполняет лексический разбор входного текста. Он принимает строку \texttt{text}, содержащую текст программы, и возвращает список объектов типа \texttt{Lexem}. Метод поочерёдно применяет каждый тип лексемы из \texttt{lexeme\_types}, пытаясь извлечь очередную лексему. Если хотя бы один тип лексемы успешно извлекает лексему, она добавляется в результат, а оставшийся текст анализируется далее. Если ни одна лексема не подошла, вызывается метод \texttt{\_consume\_error} для обработки ошибки.
Метод \texttt{analyze} выполняет лексический разбор входного текста. Он принимает строку \texttt{text}, содержащую текст программы, и возвращает список объектов типа \texttt{Lexem}. Метод поочерёдно применяет каждый тип лексемы из \texttt{lexeme\_types}, пытаясь извлечь очередную лексему. Если хотя бы один тип лексемы успешно извлекает лексему, она добавляется в результат (если её тип не входит в \texttt{skip\_types}), а оставшийся текст анализируется далее. Если ни одна лексема не подошла, вызывается метод \texttt{\_consume\_error} для обработки ошибки.
Метод \texttt{\_consume\_error} используется для обработки ситуаций, когда входной фрагмент не соответствует ни одному из допустимых шаблонов. Он находит первую непробельную последовательность символов, сообщает об ошибке в консоль и создаёт лексему с типом \texttt{"ERROR"}. Возвращает эту ошибочную лексему и оставшийся текст.
Метод \texttt{\_consume\_error} используется для обработки ситуаций, когда входной фрагмент не соответствует ни одному из допустимых шаблонов. Он использует \texttt{error\_regex} для поиска ошибочной последовательности символов, сообщает об ошибке в консоль и создаёт лексему с типом \texttt{"ERROR"}. Возвращает эту ошибочную лексему и оставшийся текст.
\begin{lstlisting}[caption={Определение класса Lexer.}, label={lst:Lexer}]
class Lexer:
def __init__(self, lexeme_types: Iterable[LexemeType]):
def __init__(
self,
lexeme_types: Iterable[LexemeType],
error_regex: str,
skip_types: Iterable[str] = [],
):
self.lexeme_types = lexeme_types
self.skip_types = skip_types
self.error_regex = re.compile(r"\s*(" + error_regex + ")")
def analyze(self, text: str) -> list[Lexem]:
lexems: list[Lexem] = []
@@ -427,16 +445,16 @@ class Lexer:
for lex_type in self.lexeme_types:
lexem, new_text = lex_type.consume(text)
if lexem:
if lexem.type_name not in self.skip_types:
lexems.append(lexem)
text = new_text
break
else:
error_lexeme, text = self._consume_error(text)
lexems.append(error_lexeme)
return lexems
def _consume_error(self, text: str) -> tuple[Lexem, str]:
match = re.match(r"\s*(\S+)", text)
match = self.error_regex.match(text)
err_text = match.group(1) if match else text.strip()
print(f"Недопустимая лексема: {err_text}")
rest = text[match.end() :] if match else ""
@@ -482,16 +500,17 @@ def exp_form_to_complex(exp_str: str) -> str:
\begin{lstlisting}[caption={Определение лексем в LEXEME\_TYPES.}, label={lst:LexemeTypes}]
LEXEME_TYPES: dict[str, LexemeType] = {
"IDENTIFIER": LexemeType(
"IDENTIFIER", r"[A-Za-z_][A-Za-z_0-9]{0,15}(?![A-Za-z_0-9])", IdentifierMapper()
"IDENTIFIER", r"[A-Za-z][A-Za-z_0-9]{0,15}(?![A-Za-z_0-9])", IdentifierMapper()
),
"COMPLEX": LexemeType(
"COMPLEX",
r"[+-]?\d+(?:\.\d+)?E[+-]?\d+(?:\.\d+)?(?!E)",
r"[+-]?\d+(?:\.\d+)?E[+-]?\d+(?:\.\d+)?",
exp_form_to_complex,
),
"ASSIGN": LexemeType("ASSIGN", r"\:=(?![\:=])"),
"ASSIGN": LexemeType("ASSIGN", r"\:="),
"ARITHMETIC_OP": LexemeType("ARITHMETIC_OP", r"[+\-*/^]"),
"PAREN": LexemeType("PAREN", r"[()]"),
"LPAREN": LexemeType("LPAREN", r"\("),
"RPAREN": LexemeType("RPAREN", r"\)"),
"SEPARATOR": LexemeType("SEPARATOR", r"\|"),
"COMMENT": LexemeType("COMMENT", r"\#.*"),
}
@@ -502,7 +521,7 @@ LEXEME_TYPES: dict[str, LexemeType] = {
\begin{lstlisting}[caption={Функция analyze\_and\_print\_table.}, label={lst:AnalyzePrint}]
def analyze_and_print_table(code: str):
lexer = Lexer(LEXEME_TYPES.values())
lexer = Lexer(LEXEME_TYPES.values(), ERROR_REGEX, skip_types=["COMMENT"])
lexemes = lexer.analyze(code)
table = PrettyTable(["Лексема", "Тип лексемы", "Значение"])
@@ -511,6 +530,8 @@ def analyze_and_print_table(code: str):
print(table)
print()
LEXEME_TYPES["IDENTIFIER"].value_func = IdentifierMapper()
\end{lstlisting}
\subsection{Функция \texttt{main}}
@@ -576,7 +597,7 @@ x := x + -2.5E+3 |
\begin{figure}[h!]
\centering
\includegraphics[width=0.8\linewidth]{img/result3.png}
\includegraphics[width=0.7\linewidth]{img/result3.png}
\caption{Результат работы лексического анализатора на программе 3 (листинг~\ref{lst:prog3}).}
\label{fig:result3}
\end{figure}
@@ -596,7 +617,11 @@ x := x + -2.5E+3 |
\newpage
\section*{Заключение}
\addcontentsline{toc}{section}{Заключение}
В ходе выполнения лабораторной работы была написана программа, которая выполняет лексический анализ входного текста в соответствии с вариантом лабораторной работы и выводит в консоль таблицу лексем с указанием их типов и значений. Также программа отдельно выводит сообщения об ошибочных лексемах.
В ходе выполнения лабораторной работы был разработан лексический анализатор для языка, поддерживающего идентификаторы до 16 символов, комплексные числа в экспоненциальной форме, оператор присваивания (\texttt{:=}), арифметические операторы (\texttt{+}, \texttt{-}, \texttt{*}, \texttt{/}, \texttt{\textasciicircum}), скобки (\texttt{(}, \texttt{)}), разделитель выражений (\texttt{|}), комментарии (\texttt{\#}). Для каждого типа лексем было разработано отдельное регулярное выражение. Предложенная программная реализация лексического анализатора выводит в консоль таблицу лексем с указанием их типов и значений. Также программа отдельно выводит сообщения об ошибочных лексемах.
Данный язык является автоматным, так как представим в виде синтаксической диаграммы. Автоматные грамматики — самые простые из формальных грамматик. Они являются подмножеством контекстно-свободных грамматик.
Из достоинств выполнения лабораторной работы можно выделить структурирование кода за счёт использования ООП. Вся логика работы лексического анализатора вынесена в отдельный класс \texttt{Lexer}. Логика работы с типами лексем в класс \texttt{LexemeType}. Также в качестве достоинства можно отметить удобочитаемый вывод таблиц в консоли с помощью библиотеки \texttt{PrettyTable}.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -27,25 +27,28 @@ class FiniteAutomaton:
for char in input_string:
if char not in self.alphabet:
return f"Символ '{char}' не из алфавита", transitions_path
return f"Символ '{char}' не из алфавита", transitions_path
next_state = self._get_next_state(current_state, char)
if next_state is None:
return "Строка не соответствует", transitions_path
return "Строка не соответствует", transitions_path
transitions_path.append(next_state)
current_state = next_state
return (
"Строка соответствует"
"Строка соответствует"
if current_state in self.final_states
else "Строка не соответствует"
else "Строка не соответствует"
), transitions_path
def generate_random_string(self, stop_probability: float = 0.3) -> str:
def generate_random_string(
self, stop_probability: float = 0.3
) -> tuple[str, list[str]]:
result = []
current_state = self.initial_state
path = [current_state]
while True:
if (
@@ -59,5 +62,6 @@ class FiniteAutomaton:
char = random.choice(transition)
result.append(char)
current_state = next_state
path.append(current_state)
return "".join(result)
return "".join(result), path

View File

@@ -2,20 +2,22 @@ from finite_automaton import FiniteAutomaton
def main():
alphabet = set("+-0123456789.,eE")
alphabet = set("+-0123456789.eE")
initial_state = "S0"
final_states = {"S2", "S4", "S7", "S8", "S9"}
final_states = {"S2", "S3", "S5", "S7", "S10"}
transitions = {
"S0": [("+-", "S1"), ("123456789", "S2"), ("0", "S8")],
"S1": [("123456789", "S2"), ("0", "S8")],
"S2": [("0123456789", "S2"), (".,", "S3"), ("eE", "S5")],
"S3": [("0123456789", "S4")],
"S4": [("0123456789", "S4"), ("eE", "S5")],
"S5": [("+-", "S6"), ("123456789", "S7"), ("0", "S9")],
"S6": [("123456789", "S7"), ("0", "S9")],
"S7": [("0123456789", "S7")],
"S8": [(".,", "S3")],
"S0": [("+-", "S1"), ("123456789", "S2"), ("0", "S3"), (".", "S6")],
"S1": [("123456789", "S2"), ("0", "S3"), (".", "S6")],
"S2": [("0123456789", "S2"), (".", "S5"), ("eE", "S8")],
"S3": [("0", "S3"), ("123456789", "S4"), (".", "S5"), ("eE", "S8")],
"S4": [("0123456789", "S4"), (".", "S5"), ("eE", "S8")],
"S5": [("0123456789", "S5"), ("eE", "S8")],
"S6": [("0123456789", "S7")],
"S7": [("0123456789", "S7"), ("eE", "S8")],
"S8": [("+-", "S9"), ("0123456789", "S10")],
"S9": [("0123456789", "S10")],
"S10": [("0123456789", "S10")],
}
automaton = FiniteAutomaton(
@@ -71,8 +73,9 @@ def main():
print(f"Ошибка: {e}")
continue
random_string = automaton.generate_random_string(stop_prob)
random_string, path = automaton.generate_random_string(stop_prob)
print(f"Сгенерированная строка: {random_string}")
print("Путь переходов:", " -> ".join(path))
else:
print(f"Неизвестная команда: {cmd}")

View File

@@ -114,7 +114,7 @@
\large{Лабораторная работа №2}\\
\large{<<Конечный автомат и регулярное выражение для распознавания форматов вещественных чисел>>}\\
\large{по дисциплине}\\
\large{<<Математическая логика>>}\\
\large{<<Математическая логика и теория автоматов>>}\\
\large{Вариант 15}\\
% \hfill \break
@@ -152,27 +152,27 @@
\textit{Вариант 15}. Соответствие вещественного числа разным форматам представления.
\newpage
\section {Математическое описание}
\subsection{Форматы представления вещественных чисел}
В данной лабораторной работе рассматриваются следующие форматы представления вещественных чисел.
\begin{itemize}
\item Целые числа, например: \texttt{''123''}, \texttt{''-456''}, \texttt{''0''}, в т. ч. \texttt{''+0''}, \texttt{''-0''}.
\item Десятичные числа с двумя возможными разделителями (точка или запятая), например: \texttt{''123.456''}, \texttt{''-456,789''}.
\item Экспоненциальная форма (буква E может быть как в верхнем, так и в нижнем регистре), например: \texttt{''1.23E4''}, \texttt{''-4,56e-7''}, \texttt{''7e8''}.
\item Десятичные числа с разделителем точка, например: \texttt{''123.456''}, \texttt{''-456.789''}, \texttt{''.5''}.
\item Экспоненциальная форма (буква E может быть как в верхнем, так и в нижнем регистре), например: \texttt{''1.23E4''}, \texttt{''-4.56e-7''}, \texttt{''7e8''}.
\end{itemize}
Формальное определение синтаксиса предложенного формата вещественных чисел в БНФ нотации:
\begin{verbatim}
real ::= decimal | exponential
decimal ::= integer [separator digit {digit}]
integer ::= [sign] (nonzerodigit {digit} | "0")
exponential ::= decimal ("e" | "E") integer
real ::= [sign] (integer | float | exponentnumber)
sign ::= "+" | "-"
integer ::= nonzerodigit {digit} | "0" {"0"}
nonzerodigit ::= "1" | "2" | ... | "9"
digit ::= "0" | nonzerodigit
separator ::= "." | ","
float ::= {digit} "." digitpart | digitpart "."
digitpart ::= digit {digit}
exponentnumber ::= (digitpart | float) exponent
exponent ::= ("e" | "E") [sign] digitpart
\end{verbatim}
Где:
\begin{itemize}
@@ -182,7 +182,8 @@
\end{itemize}
\newpage
\section {Математическое описание}
\subsection{Языки и грамматики}
Языком над конечным словарем $\Sigma$ называется произвольное множество конечных цепочек над этим словарем.
@@ -248,22 +249,30 @@
Для разных форматов представления вещественных чисел были построены следующие регулярные выражения в соответствии с определённой БНФ нотацией:
\begin{itemize}
\item Десятичные и целые числа: \\
\texttt{[+-]?([1-9][0-9]*|0)([.,][0-9]+)?}
\item Целые числа: \\
\texttt{[+-]?(0+|[1-9][0-9]*)}
\item Десятичные числа: \\
\texttt{[+-]?([0-9]*\textbackslash.[0-9]+|[0-9]+\textbackslash.)}
\item Экспоненциальная форма: \\
\texttt{[+-]?([1-9][0-9]*|0)([.,][0-9]+)?[eE][+-]?([1-9][0-9]*|0)}
\texttt{[+-]?([0-9]+|[0-9]*\textbackslash.[0-9]+|[0-9]+\textbackslash.)[eE][+-]?[0-9]+}
\end{itemize}
Объединяя все форматы в соответствии с нашей БНФ нотацией, получаем следующее регулярное выражение, которое распознает все форматы вещественных чисел:
\texttt{[+-]?([1-9][0-9]*|0)([.,][0-9]+)?([eE][+-]?([1-9][0-9]*|0))?}
Объединяя, получаем следующее регулярное выражение, которое распознает все форматы вещественных чисел в соответствии с БНФ нотацией:
\begin{verbatim}
[+-]?(
0+|
[1-9][0-9]*|
[0-9]*\.[0-9]+|
[0-9]+\.|
([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)[eE][+-]?[0-9]+
)
\end{verbatim}
Разберём структуру этого выражения:
\begin{itemize}
\item \texttt{[+-]?} -- необязательный знак числа (плюс или минус)
\item \texttt{([1-9][0-9]*|0)} -- целая часть числа, которая может быть либо нулём, либо цифрой от 1 до 9, за которой следует произвольное количество цифр
\item \texttt{([.,][0-9]+)?} -- необязательная десятичная часть, состоящая из разделителя (точка или запятая) и как минимум одной цифры
\item \texttt{([eE][+-]?([1-9][0-9]*|0))?} -- необязательная экспоненциальная часть, состоящая из буквы E (в любом регистре), необязательного знака и целого числа
\item \texttt{0+|[1-9][0-9]*} -- целое число, которое может быть либо последовательностью нулей, либо цифрой от 1 до 9, за которой следует произвольное количество цифр.
\item \texttt{[0-9]*\textbackslash.[0-9]+|[0-9]+\textbackslash.} -- десятичное число, которое может быть представлено произвольным количеством цифр до и как минимум одной цифрой после точки, либо произвольным количеством цифр и одной точкой в конце.
\item \texttt{([0-9]+|[0-9]*\textbackslash.[0-9]+|[0-9]+\textbackslash.)[eE][+-]?[0-9]+} -- экспоненциальная форма числа, состоящая из произвольного количества цифр, либо десятичного числа, за которым следует буква E (в любом регистре), необязательный знак и как минимум одна цифра.
\end{itemize}
Таким образом, полученное регулярное выражение распознаёт формат представления вещественных чисел, рассматриваемый в данной работе, и полностью соответствует формальному определению, представленному в БНФ нотации.
@@ -335,27 +344,29 @@
\footnotesize
\begin{tabularx}{\textwidth}{|c|X|X|X|X|X|}
\hline
\textbf{Состояние\textbackslash Вход} & \textbf{+-} & \textbf{0} & \textbf{1-9} & \textbf{.,} & \textbf{eE} \\
\textbf{Состояние\textbackslash Вход} & \textbf{+-} & \textbf{0} & \textbf{1-9} & \textbf{.} & \textbf{eE} \\
\hline
$S_0$ & $S_1$ & $S_8$ & $S_2$ & -- & -- \\
$S_0$ & $S_1$ & $S_3$ & $S_2$ & $S_6$ & -- \\
\hline
$S_1$ & -- & $S_8$ & $S_2$ & -- & -- \\
$S_1$ & -- & $S_3$ & $S_2$ & $S_6$ & -- \\
\hline
$S_2$ & -- & $S_2$ & $S_2$ & $S_3$ & $S_5$ \\
$S_2$ & -- & $S_2$ & $S_2$ & $S_5$ & $S_8$ \\
\hline
$S_3$ & -- & $S_4$ & $S_4$ & -- & -- \\
$S_3$ & -- & $S_3$ & $S_4$ & $S_5$ & $S_8$ \\
\hline
$S_4$ & -- & $S_4$ & $S_4$ & -- & $S_5$ \\
$S_4$ & -- & $S_4$ & $S_4$ & $S_5$ & $S_8$ \\
\hline
$S_5$ & $S_6$ & $S_9$ & $S_7$ & -- & -- \\
$S_5$ & -- & $S_5$ & $S_5$ & -- & $S_8$ \\
\hline
$S_6$ & -- & $S_9$ & $S_7$ & -- & -- \\
$S_6$ & -- & $S_7$ & $S_7$ & -- & -- \\
\hline
$S_7$ & -- & -- & $S_7$ & -- & -- \\
$S_7$ & -- & $S_7$ & $S_7$ & -- & $S_8$ \\
\hline
$S_8$ & -- & -- & -- & $S_3$ & -- \\
$S_8$ & $S_9$ & $S_{10}$ & $S_{10}$ & -- & -- \\
\hline
$S_9$ & -- & -- & -- & -- & -- \\
$S_9$ & -- & $S_{10}$ & $S_{10}$ & -- & -- \\
\hline
$S_{10}$ & -- & $S_{10}$ & $S_{10}$ & -- & -- \\
\hline
\end{tabularx}
\label{tab:nka}
@@ -374,7 +385,6 @@
\label{fig:ka}
\end{figure}
Матрица переходов для данного автомата представлена в Таблице~\ref{tab:ka}.
\begin{table}[h!]
\centering
@@ -382,27 +392,29 @@
\footnotesize
\begin{tabularx}{\textwidth}{|c|X|X|X|X|X|}
\hline
\textbf{Состояние\textbackslash Вход} & \textbf{+-} & \textbf{0} & \textbf{1-9} & \textbf{.,} & \textbf{eE} \\
\textbf{Состояние\textbackslash Вход} & \textbf{+-} & \textbf{0} & \textbf{1-9} & \textbf{.} & \textbf{eE} \\
\hline
$S_0$ & $S_1$ & $S_8$ & $S_2$ & $S_E$ & $S_E$ \\
$S_0$ & $S_1$ & $S_3$ & $S_2$ & $S_6$ & $S_E$ \\
\hline
$S_1$ & $S_E$ & $S_8$ & $S_2$ & $S_E$ & $S_E$ \\
$S_1$ & $S_E$ & $S_3$ & $S_2$ & $S_6$ & $S_E$ \\
\hline
$S_2$ & $S_E$ & $S_2$ & $S_2$ & $S_3$ & $S_5$ \\
$S_2$ & $S_E$ & $S_2$ & $S_2$ & $S_5$ & $S_8$ \\
\hline
$S_3$ & $S_E$ & $S_4$ & $S_4$ & $S_E$ & $S_E$ \\
$S_3$ & $S_E$ & $S_3$ & $S_4$ & $S_5$ & $S_8$ \\
\hline
$S_4$ & $S_E$ & $S_4$ & $S_4$ & $S_E$ & $S_5$ \\
$S_4$ & $S_E$ & $S_4$ & $S_4$ & $S_5$ & $S_8$ \\
\hline
$S_5$ & $S_6$ & $S_9$ & $S_7$ & $S_E$ & $S_E$ \\
$S_5$ & $S_E$ & $S_5$ & $S_5$ & $S_E$ & $S_8$ \\
\hline
$S_6$ & $S_E$ & $S_9$ & $S_7$ & $S_E$ & $S_E$ \\
$S_6$ & $S_E$ & $S_7$ & $S_7$ & $S_E$ & $S_E$ \\
\hline
$S_7$ & $S_E$ & $S_E$ & $S_7$ & $S_E$ & $S_E$ \\
$S_7$ & $S_E$ & $S_7$ & $S_7$ & $S_E$ & $S_8$ \\
\hline
$S_8$ & $S_E$ & $S_E$ & $S_E$ & $S_3$ & $S_E$ \\
$S_8$ & $S_9$ & $S_{10}$ & $S_{10}$ & $S_E$ & $S_E$ \\
\hline
$S_9$ & $S_E$ & $S_E$ & $S_E$ & $S_E$ & $S_E$ \\
$S_9$ & $S_E$ & $S_{10}$ & $S_{10}$ & $S_E$ & $S_E$ \\
\hline
$S_{10}$ & $S_E$ & $S_{10}$ & $S_{10}$ & $S_E$ & $S_E$ \\
\hline
\end{tabularx}
\label{tab:ka}
@@ -410,7 +422,9 @@
\newpage
\phantom{text}
Матрица переходов для данного автомата представлена в Таблице~\ref{tab:ka}.
\newpage
\section{Особенности реализации}
\subsection{Общая структура программы}
@@ -502,9 +516,12 @@ def process_input(self, input_string: str) -> tuple[str, list[str]]:
\end{enumerate}
\begin{lstlisting}[caption={Код метода \texttt{generate\_random\_string}.}, label={lst:generate_random_string}]
def generate_random_string(self, stop_probability: float = 0.3) -> str:
def generate_random_string(
self, stop_probability: float = 0.3
) -> tuple[str, list[str]]:
result = []
current_state = self.initial_state
path = [current_state]
while True:
if (
@@ -518,8 +535,9 @@ def generate_random_string(self, stop_probability: float = 0.3) -> str:
char = random.choice(transition)
result.append(char)
current_state = next_state
path.append(current_state)
return "".join(result)
return "".join(result), path
\end{lstlisting}
@@ -537,20 +555,22 @@ def generate_random_string(self, stop_probability: float = 0.3) -> str:
\begin{lstlisting}[caption={Функция main.}, label={lst:Main}]
def main():
alphabet = set("+-0123456789.,eE")
alphabet = set("+-0123456789.eE")
initial_state = "S0"
final_states = {"S2", "S4", "S7", "S8", "S9"}
final_states = {"S2", "S3", "S5", "S7", "S10"}
transitions = {
"S0": [("+-", "S1"), ("123456789", "S2"), ("0", "S8")],
"S1": [("123456789", "S2"), ("0", "S8")],
"S2": [("0123456789", "S2"), (".,", "S3"), ("eE", "S5")],
"S3": [("0123456789", "S4")],
"S4": [("0123456789", "S4"), ("eE", "S5")],
"S5": [("+-", "S6"), ("123456789", "S7"), ("0", "S9")],
"S6": [("123456789", "S7"), ("0", "S9")],
"S7": [("0123456789", "S7")],
"S8": [(".,", "S3")],
"S0": [("+-", "S1"), ("123456789", "S2"), ("0", "S3"), (".", "S6")],
"S1": [("123456789", "S2"), ("0", "S3"), (".", "S6")],
"S2": [("0123456789", "S2"), (".", "S5"), ("eE", "S8")],
"S3": [("0", "S3"), ("123456789", "S4"), (".", "S5"), ("eE", "S8")],
"S4": [("0123456789", "S4"), (".", "S5"), ("eE", "S8")],
"S5": [("0123456789", "S5"), ("eE", "S8")],
"S6": [("0123456789", "S7")],
"S7": [("0123456789", "S7"), ("eE", "S8")],
"S8": [("+-", "S9"), ("0123456789", "S10")],
"S9": [("0123456789", "S10")],
"S10": [("0123456789", "S10")],
}
automaton = FiniteAutomaton(
@@ -606,8 +626,9 @@ def main():
print(f"Ошибка: {e}")
continue
random_string = automaton.generate_random_string(stop_prob)
random_string, path = automaton.generate_random_string(stop_prob)
print(f"Сгенерированная строка: {random_string}")
print("Путь переходов:", " -> ".join(path))
else:
print(f"Неизвестная команда: {cmd}")
@@ -624,7 +645,7 @@ def main():
\label{fig:result1}
\end{figure}
На Рис.~\ref{fig:wrong} представлена реакция программы на некорректный пользовательский ввод.
\newpage
\begin{figure}[h!]
\centering
@@ -633,11 +654,14 @@ def main():
\label{fig:wrong}
\end{figure}
На Рис.~\ref{fig:wrong} представлена реакция программы на некорректный пользовательский ввод.
\newpage
\section*{Заключение}
\addcontentsline{toc}{section}{Заключение}
В ходе выполнения лабораторной работы было построено регулярное выражение для распознавания различных форматов вещественных чисел, созданы недетерминированный и детерминированный конечные автоматы-распознаватели. На основе разработанного автомата была реализована программа, которая проверяет соответствие входной строки заданному формату и генерирует случайные корректные строки.
В ходе выполнения лабораторной работы было построено регулярное выражение для распознавания различных форматов вещественных чисел. В соответствии с теоремой Клини по заданному регулярному выражению, задающему регулярный
язык, был построен недетерминированный конечный автомат-распознаватель. Затем полученный конечный автомат был детерминирован. На основе разработанного автомата была реализована программа, которая проверяет соответствие входной строки заданному формату и генерирует случайные корректные строки.
Из достоинств выполнения лабораторной работы можно выделить структурирование кода за счёт использования ООП. Вся логика работы с конечными автоматами вынесена в отдельный класс \texttt{FiniteAutomaton} с четко разделенными методами для проверки строк и генерации случайных строк. Создана удобная интерактивная консольная оболочка для взаимодействия с пользователем, позволяющая выполнять различные команды.

8
lab3/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
**/*
!.gitignore
!report.tex
!img
!img/**
!programm
!programm/*.py
!programm/*.txt

BIN
lab3/img/result1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
lab3/img/wrong.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

3
lab3/programm/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
grammar_*
analysis_*
generation_*

View File

@@ -0,0 +1,617 @@
import random
import re
from collections import OrderedDict
class Grammar:
EPSILON: str = "epsilon"
def __init__(self, text: str):
self.productions: OrderedDict[str, list[list[str]]] = OrderedDict()
self.start_symbol: str = ""
self._parse_productions(text)
self.terminals: set[str] = set()
self._find_terminals()
self.first_sets: dict[str, set[str]] = {}
self._calculate_first_sets()
self.follow_sets: dict[str, set[str]] = {}
self._calculate_follow_sets()
self.lookup_table: dict[str, dict[str, list[str]]] = {}
self._fill_lookup_table()
# Сопостовляем уникальный номер с каждым правилом
self.rule_numbers = {}
rule_idx = 1
for nt, rules in self.productions.items():
for rule in rules:
self.rule_numbers[(nt, tuple(rule))] = rule_idx
rule_idx += 1
def _parse_productions(self, text: str):
for line in text.splitlines():
line = line.strip()
if not line:
continue
non_terminal, rule = line.split("->")
if self.start_symbol == "":
self.start_symbol = non_terminal.strip()
non_terminal = non_terminal.strip()
rules = [
[
symbol.strip('"')
for symbol in re.findall(r"\".*?\"|\S+", rule.strip())
if symbol.strip('"') != self.EPSILON
]
for rule in rule.split("|")
]
if non_terminal not in self.productions:
self.productions[non_terminal] = []
self.productions[non_terminal].extend(rules)
def _find_terminals(self):
for rules in self.productions.values():
for rule in rules:
for symbol in rule:
if symbol not in self.productions:
self.terminals.add(symbol)
def _calculate_first_sets(self):
# Инициализация FIRST для всех символов
for non_terminal in self.productions:
self.first_sets[non_terminal] = set()
# Для терминалов FIRST содержит только сам терминал
for terminal in self.terminals:
self.first_sets[terminal] = {terminal}
# Вычисление FIRST для нетерминалов
changed = True
while changed:
changed = False
for non_terminal, rules in self.productions.items():
for rule in rules:
if not rule: # Пустое правило (эпсилон)
if "" not in self.first_sets[non_terminal]:
self.first_sets[non_terminal].add("")
changed = True
else:
all_can_derive_epsilon = True
for i, symbol in enumerate(rule):
# Добавляем все символы из FIRST(symbol) кроме эпсилон
first_without_epsilon = self.first_sets[symbol] - {""}
old_size = len(self.first_sets[non_terminal])
self.first_sets[non_terminal].update(first_without_epsilon)
if len(self.first_sets[non_terminal]) > old_size:
changed = True
# Если symbol не может порождать эпсилон, прерываем
if "" not in self.first_sets[symbol]:
all_can_derive_epsilon = False
break
# Если все символы в правиле могут порождать эпсилон,
# то и нетерминал может порождать эпсилон
if (
all_can_derive_epsilon
and "" not in self.first_sets[non_terminal]
):
self.first_sets[non_terminal].add("")
changed = True
def _calculate_follow_sets(self):
# инициализировать Fo(S) = { $ }, а все остальные Fo(Ai) пустыми множествами
for non_terminal in self.productions:
self.follow_sets[non_terminal] = set()
# Добавляем символ конца строки $ для начального символа
self.follow_sets[self.start_symbol].add("$")
# Повторяем, пока в наборах Follow происходят изменения
changed = True
while changed:
changed = False
# Для каждого нетерминала Aj в грамматике
for non_terminal_j, rules in self.productions.items():
# Для каждого правила Aj → w
for rule in rules:
# Для каждого символа в правиле
for i, symbol in enumerate(rule):
# Если символ - нетерминал Ai
if symbol in self.productions:
# w' - остаток правила после Ai
remainder = rule[i + 1 :] if i + 1 < len(rule) else []
# Если есть терминалы после Ai (w' не пусто)
if remainder:
# Вычисляем First(w')
first_of_remainder = self._first_of_sequence(remainder)
# Если терминал a находится в First(w'), то добавляем a к Follow(Ai)
for terminal in first_of_remainder - {""}:
if terminal not in self.follow_sets[symbol]:
self.follow_sets[symbol].add(terminal)
changed = True
# Если ε находится в First(w'), то добавляем Follow(Aj) к Follow(Ai)
if "" in first_of_remainder:
old_size = len(self.follow_sets[symbol])
self.follow_sets[symbol].update(
self.follow_sets[non_terminal_j]
)
if len(self.follow_sets[symbol]) > old_size:
changed = True
# Если w' пусто (Ai в конце правила), то добавляем Follow(Aj) к Follow(Ai)
else:
old_size = len(self.follow_sets[symbol])
self.follow_sets[symbol].update(
self.follow_sets[non_terminal_j]
)
if len(self.follow_sets[symbol]) > old_size:
changed = True
def _first_of_sequence(self, sequence):
"""Вычисляет множество FIRST для последовательности символов"""
if not sequence:
return {""}
result = set()
all_can_derive_epsilon = True
for symbol in sequence:
# Добавляем First(symbol) без эпсилон к результату
result.update(self.first_sets[symbol] - {""})
# Если symbol не может порождать эпсилон, останавливаемся
if "" not in self.first_sets[symbol]:
all_can_derive_epsilon = False
break
# Если все символы могут порождать эпсилон, добавляем эпсилон к результату
if all_can_derive_epsilon:
result.add("")
return result
def _fill_lookup_table(self):
# Для каждого нетерминала A
for non_terminal, rules in self.productions.items():
# Формируем таблицу синтаксического анализа
self.lookup_table[non_terminal] = {}
# Для каждого правила A → w
for rule in rules:
# Вычисляем First(w)
first_of_rule = self._first_of_sequence(rule)
# Для каждого терминала a в First(w)
for terminal in first_of_rule - {""}:
self._add_to_lookup_table(non_terminal, terminal, rule)
# Если эпсилон в First(w), то для каждого b в Follow(A)
if "" in first_of_rule:
for terminal in self.follow_sets[non_terminal]:
self._add_to_lookup_table(non_terminal, terminal, rule)
def _add_to_lookup_table(self, non_terminal, terminal, rule):
if terminal in self.lookup_table[non_terminal]:
raise ValueError(
"\nГрамматика не является LL(1)-грамматикой.\n"
f'Распознаваемый нетерминал: "{non_terminal}"\n'
f'Поступающий на вход терминал: "{terminal}"\n'
f"Неоднозначность между правилами:\n"
f"{non_terminal} -> {' '.join(rule)}\n"
f"{non_terminal} -> {' '.join(self.lookup_table[non_terminal][terminal])}"
)
self.lookup_table[non_terminal][terminal] = rule
def format_rules(self) -> str:
result = []
sorted_rules = sorted(self.rule_numbers.items(), key=lambda x: x[1])
for rule, number in sorted_rules:
non_terminal, symbols = rule
rule_text = f"{number}: {non_terminal} -> {' '.join(symbols)}"
result.append(rule_text)
return "\n".join(result)
def format_lookup_table(self) -> str:
"""Форматирует таблицу синтаксического анализа в текстовом виде."""
terminals = sorted(list(self.terminals))
all_terminals = terminals + ["$"]
# Определяем ширину каждого столбца
header_widths = {"": len("") + 2} # +2 для отступов
for term in all_terminals:
header_widths[term] = len(term) + 2
# Определяем ширину столбцов для содержимого таблицы
cell_widths = header_widths.copy()
for non_terminal in self.productions:
nt_width = len(non_terminal) + 2
cell_widths[""] = max(cell_widths[""], nt_width)
for terminal in all_terminals:
if terminal in self.lookup_table[non_terminal]:
rule = self.lookup_table[non_terminal][terminal]
rule_num = self.rule_numbers.get((non_terminal, tuple(rule)), "")
cell_content = f"{rule_num}: {' '.join(rule)}"
cell_widths[terminal] = max(
cell_widths[terminal], len(cell_content) + 2
)
# Создаем верхнюю границу таблицы
border = "+"
for term in [""] + all_terminals:
border += "-" * cell_widths[term] + "+"
# Создаем заголовок
header = "|" + " " * cell_widths[""] + "|"
for term in all_terminals:
header += f" {term.center(cell_widths[term]-2)} |"
# Создаем разделительную линию
separator = "+"
for term in [""] + all_terminals:
separator += "=" * cell_widths[term] + "+"
# Формируем строки таблицы
rows = []
for non_terminal in sorted(self.productions.keys()):
row = f"| {non_terminal.ljust(cell_widths['']-2)} |"
for terminal in all_terminals:
if terminal in self.lookup_table[non_terminal]:
rule = self.lookup_table[non_terminal][terminal]
rule_num = self.rule_numbers.get((non_terminal, tuple(rule)), "")
cell_content = f"{rule_num}: {' '.join(rule)}"
row += f" {cell_content.ljust(cell_widths[terminal]-2)} |"
else:
row += f" {' - '.ljust(cell_widths[terminal]-2)} |"
rows.append(row)
# Собираем таблицу
table = [border, header, separator]
for row in rows:
table.append(row)
table.append(border)
return "\n".join(table)
def format_first_sets(self) -> str:
"""Форматирует множества FIRST в читаемый вид."""
result = []
result.append("Множества FIRST:")
result.append("=" * 40)
# Сортируем для гарантии порядка вывода
for symbol in sorted(self.first_sets.keys()):
# Заменяем пустую строку на эпсилон для лучшей читаемости
first_set = {
self.EPSILON if item == "" else item for item in self.first_sets[symbol]
}
result.append(f"FIRST({symbol}) = {{{', '.join(sorted(first_set))}}}")
return "\n".join(result)
def format_follow_sets(self) -> str:
"""Форматирует множества FOLLOW в читаемый вид."""
result = []
result.append("Множества FOLLOW:")
result.append("=" * 40)
# Обрабатываем только нетерминалы
for non_terminal in sorted(self.productions.keys()):
follow_set = self.follow_sets.get(non_terminal, set())
result.append(
f"FOLLOW({non_terminal}) = {{{', '.join(sorted(follow_set))}}}"
)
return "\n".join(result)
def analyze(self, input_tokens: list[str]) -> list[int]:
input_tokens = input_tokens.copy()
input_tokens += ["$"]
input_pos = 0
# Инициализируем стек с терминальным символом и начальным символом
stack = ["$", self.start_symbol]
rules_applied = []
while stack:
top = stack[-1]
current_symbol = (
input_tokens[input_pos] if input_pos < len(input_tokens) else "$"
)
# Случай 1: Верхний символ стека - нетерминал
if top in self.productions:
# Ищем правило в таблице синтаксического анализа
if current_symbol in self.lookup_table[top]:
# Получаем правило
production = self.lookup_table[top][current_symbol]
# Удаляем нетерминал из стека
stack.pop()
# Добавляем правило в rules_applied
rule_number = self.rule_numbers[(top, tuple(production))]
rules_applied.append(rule_number)
# Добавляем правило в стек в обратном порядке
for symbol in reversed(production):
stack.append(symbol)
else:
expected_symbols = list(self.lookup_table[top].keys())
raise ValueError(
f"Syntax error: expected one of {expected_symbols}, got '{current_symbol}'"
)
# Случай 2: Верхний символ стека - терминал
elif top != "$":
if top == current_symbol:
# Удаляем терминал из стека
stack.pop()
# Переходим к следующему символу ввода
input_pos += 1
else:
raise ValueError(
f"Syntax error: expected '{top}', got '{current_symbol}'"
)
# Случай 3: Верхний символ стека - $
else: # top == "$"
if current_symbol == "$":
# Успешный синтаксический анализ
stack.pop() # Удаляем $ из стека
else:
raise ValueError(
f"Syntax error: unexpected symbols at end of input: '{current_symbol}'"
)
return rules_applied
def generate(self, symbol: str | None = None) -> tuple[list[str], list[int]]:
"""Генерирует предложение по заданной грамматике. Возвращает список терминалов
и список номеров применённых правил."""
if symbol is None:
return self.generate(self.start_symbol)
# Если символ - терминал, возвращаем его
if symbol not in self.productions:
return [symbol], []
# Выбираем случайное правило для нетерминала
rules = self.productions[symbol]
chosen_rule = random.choice(rules)
# Получаем номер выбранного правила
rule_number = self.rule_numbers[(symbol, tuple(chosen_rule))]
# Инициализируем результаты
terminals = []
rule_numbers = [rule_number]
# Разворачиваем каждый символ в правой части правила
for s in chosen_rule:
sub_terminals, sub_rules = self.generate(s)
terminals.extend(sub_terminals)
rule_numbers.extend(sub_rules)
return terminals, rule_numbers
def generate_derivation_steps(self, rule_numbers: list[int]) -> list[str]:
"""Преобразует список номеров правил в последовательность шагов вывода.
Возвращает список строк, представляющих каждый шаг вывода."""
# Получаем соответствие между номерами правил и самими правилами
rule_details = {num: rule for rule, num in self.rule_numbers.items()}
# Начинаем с начального символа
current = self.start_symbol
steps = [current]
# Применяем каждое правило по порядку
for rule_num in rule_numbers:
if rule_num in rule_details:
non_terminal, replacement = rule_details[rule_num]
# Находим первое вхождение нетерминала и заменяем его
words = current.split()
for i, word in enumerate(words):
if word == non_terminal:
words[i : i + 1] = replacement
break
current = " ".join(words)
steps.append(current)
return steps
def load_grammar(filename: str = "grammar.txt") -> Grammar | None:
try:
with open(filename, "r", encoding="utf-8") as file:
text = file.read()
grammar = Grammar(text)
# Сохраняем информацию о грамматике в файлы
with open("grammar_rules.txt", "w", encoding="utf-8") as output_file:
output_file.write(grammar.format_rules())
print("Правила грамматики с номерами сохранены в grammar_rules.txt")
with open("grammar_lookup_table.txt", "w", encoding="utf-8") as output_file:
output_file.write(grammar.format_lookup_table())
print(
"Таблица синтаксического анализа сохранена в grammar_lookup_table.txt"
)
with open("grammar_first.txt", "w", encoding="utf-8") as output_file:
output_file.write(grammar.format_first_sets())
print("Множества FIRST сохранены в grammar_first.txt")
with open("grammar_follow.txt", "w", encoding="utf-8") as output_file:
output_file.write(grammar.format_follow_sets())
print("Множества FOLLOW сохранены в grammar_follow.txt")
print(f"Грамматика успешно загружена из файла {filename}")
return grammar
except FileNotFoundError:
print(f"Ошибка: Файл {filename} не найден")
return None
except ValueError as e:
print(f"Ошибка при загрузке грамматики: {e}")
return None
except Exception as e:
print(f"Неизвестная ошибка: {e}")
return None
def tokenize_string(input_string: str) -> list[str]:
input_string = input_string.replace(",", " , ").replace(".", " . ")
return input_string.split()
def check_string(grammar: Grammar | None, input_string: str) -> None:
if not grammar:
print("Ошибка: Грамматика не загружена")
return
print(f"Проверка строки: '{input_string}'")
try:
input_tokens = tokenize_string(input_string)
if not input_tokens:
parse_result = grammar.analyze(input_tokens)
else:
try:
input_tokens[0] = input_tokens[0][0].lower() + input_tokens[0][1:]
parse_result = grammar.analyze(input_tokens)
except ValueError as e:
input_tokens[0] = input_tokens[0][0].upper() + input_tokens[0][1:]
parse_result = grammar.analyze(input_tokens)
print(f"Результат: Строка соответствует грамматике")
print(f"Применённые правила: {parse_result}")
# Сохраняем результат анализа в файл
with open("analysis_result.txt", "w", encoding="utf-8") as f:
f.write(f"Input: {input_string}\n")
f.write("Applied rules: ")
f.write(str(parse_result))
f.write("\n\n")
f.write("Derivation steps:\n")
derivation_steps = grammar.generate_derivation_steps(parse_result)
for step in derivation_steps:
f.write(f"{step}\n")
print("Подробный результат анализа сохранен в analysis_result.txt")
except ValueError as e:
print(f"Результат: Строка не соответствует грамматике")
print(f"Ошибка: {e}")
except Exception as e:
print(f"Произошла ошибка при анализе: {e}")
def post_process_string(string: str) -> str:
if string:
string = string[0].upper() + string[1:]
string = string.replace(" ,", ",")
string = string.replace(" .", ".")
string = string.replace(",.", ".")
return string
def generate_string(grammar: Grammar | None) -> None:
if not grammar:
print("Ошибка: Грамматика не загружена")
return
try:
terminals, rules = grammar.generate()
generated_string = " ".join(terminals)
generated_string = post_process_string(generated_string)
print(f"Сгенерированная строка: {generated_string}")
print(f"Применённые правила: {rules}")
# Сохраняем результат генерации в файл
with open("generation_result.txt", "w", encoding="utf-8") as f:
f.write(f"Generated string: {generated_string}\n")
f.write("Applied rules: ")
f.write(str(rules))
f.write("\n\n")
f.write("Derivation steps:\n")
derivation_steps = grammar.generate_derivation_steps(rules)
for step in derivation_steps:
f.write(f"{step}\n")
print("Подробный результат генерации сохранен в generation_result.txt")
except Exception as e:
print(f"Произошла ошибка при генерации: {e}")
def main():
print("Программа для работы с LL(1)-грамматиками")
print("=" * 60)
print("Варианты команд:")
print(" - load <файл> - загрузить грамматику из файла (по умолчанию grammar.txt)")
print(" - check <строка> - проверить, соответствует ли строка грамматике")
print(" - generate - сгенерировать случайную строку по грамматике")
print(" - exit - выход из программы")
print("=" * 60)
# Загружаем грамматику по умолчанию при старте
grammar = load_grammar()
while True:
command = input("\nВведите команду: ").strip()
if not command:
continue
parts = command.split(maxsplit=1)
cmd = parts[0].lower()
if cmd == "exit":
print("Выход из программы.")
break
elif cmd == "load":
filename = "grammar.txt"
if len(parts) > 1:
filename = parts[1].strip()
grammar = load_grammar(filename)
elif cmd == "check":
input_string = ""
if len(parts) > 1:
input_string = parts[1].strip()
check_string(grammar, input_string)
elif cmd == "generate":
generate_string(grammar)
else:
print(f"Неизвестная команда: {cmd}")
print("Доступные команды: load, check, generate, exit")
if __name__ == "__main__":
main()

401
lab3/programm/grammar.py Normal file
View File

@@ -0,0 +1,401 @@
import random
import re
from collections import OrderedDict
from prettytable import PrettyTable
class Grammar:
EPSILON: str = "epsilon"
def __init__(self, text: str):
self.productions: OrderedDict[str, list[list[str]]] = OrderedDict()
self.start_symbol: str = ""
self._parse_productions(text)
self.terminals: set[str] = set()
self._find_terminals()
self.first_sets: dict[str, set[str]] = {}
self._calculate_first_sets()
self.follow_sets: dict[str, set[str]] = {}
self._calculate_follow_sets()
self.lookup_table: dict[str, dict[str, list[str]]] = {}
self._fill_lookup_table()
# Сопостовляем уникальный номер с каждым правилом
self.rule_numbers = {}
rule_idx = 1
for nt, rules in self.productions.items():
for rule in rules:
self.rule_numbers[(nt, tuple(rule))] = rule_idx
rule_idx += 1
def _parse_productions(self, text: str):
for line in text.splitlines():
line = line.strip()
if not line:
continue
non_terminal, rule = line.split("->")
if self.start_symbol == "":
self.start_symbol = non_terminal.strip()
non_terminal = non_terminal.strip()
rules = [
[
symbol.strip('"')
for symbol in re.findall(r"\".*?\"|\S+", rule.strip())
if symbol.strip('"') != self.EPSILON
]
for rule in rule.split("|")
]
if non_terminal not in self.productions:
self.productions[non_terminal] = []
self.productions[non_terminal].extend(rules)
def _find_terminals(self):
for rules in self.productions.values():
for rule in rules:
for symbol in rule:
if symbol not in self.productions:
self.terminals.add(symbol)
def _calculate_first_sets(self):
# Инициализация FIRST для всех символов
for non_terminal in self.productions:
self.first_sets[non_terminal] = set()
# Для терминалов FIRST содержит только сам терминал
for terminal in self.terminals:
self.first_sets[terminal] = {terminal}
# Вычисление FIRST для нетерминалов
changed = True
while changed:
changed = False
for non_terminal, rules in self.productions.items():
for rule in rules:
if not rule: # Пустое правило (эпсилон)
if "" not in self.first_sets[non_terminal]:
self.first_sets[non_terminal].add("")
changed = True
else:
all_can_derive_epsilon = True
for i, symbol in enumerate(rule):
# Добавляем все символы из FIRST(symbol) кроме эпсилон
first_without_epsilon = self.first_sets[symbol] - {""}
old_size = len(self.first_sets[non_terminal])
self.first_sets[non_terminal].update(first_without_epsilon)
if len(self.first_sets[non_terminal]) > old_size:
changed = True
# Если symbol не может порождать эпсилон, прерываем
if "" not in self.first_sets[symbol]:
all_can_derive_epsilon = False
break
# Если все символы в правиле могут порождать эпсилон,
# то и нетерминал может порождать эпсилон
if (
all_can_derive_epsilon
and "" not in self.first_sets[non_terminal]
):
self.first_sets[non_terminal].add("")
changed = True
def _calculate_follow_sets(self):
# инициализировать Fo(S) = { $ }, а все остальные Fo(Ai) пустыми множествами
for non_terminal in self.productions:
self.follow_sets[non_terminal] = set()
# Добавляем символ конца строки $ для начального символа
self.follow_sets[self.start_symbol].add("$")
# Повторяем, пока в наборах Follow происходят изменения
changed = True
while changed:
changed = False
# Для каждого нетерминала Aj в грамматике
for non_terminal_j, rules in self.productions.items():
# Для каждого правила Aj → w
for rule in rules:
# Для каждого символа в правиле
for i, symbol in enumerate(rule):
# Если символ - нетерминал Ai
if symbol in self.productions:
# w' - остаток правила после Ai
remainder = rule[i + 1 :] if i + 1 < len(rule) else []
# Если есть терминалы после Ai (w' не пусто)
if remainder:
# Вычисляем First(w')
first_of_remainder = self._first_of_sequence(remainder)
# Если терминал a находится в First(w'), то добавляем a к Follow(Ai)
for terminal in first_of_remainder - {""}:
if terminal not in self.follow_sets[symbol]:
self.follow_sets[symbol].add(terminal)
changed = True
# Если ε находится в First(w'), то добавляем Follow(Aj) к Follow(Ai)
if "" in first_of_remainder:
old_size = len(self.follow_sets[symbol])
self.follow_sets[symbol].update(
self.follow_sets[non_terminal_j]
)
if len(self.follow_sets[symbol]) > old_size:
changed = True
# Если w' пусто (Ai в конце правила), то добавляем Follow(Aj) к Follow(Ai)
else:
old_size = len(self.follow_sets[symbol])
self.follow_sets[symbol].update(
self.follow_sets[non_terminal_j]
)
if len(self.follow_sets[symbol]) > old_size:
changed = True
def _first_of_sequence(self, sequence):
"""Вычисляет множество FIRST для последовательности символов"""
if not sequence:
return {""}
result = set()
all_can_derive_epsilon = True
for symbol in sequence:
# Добавляем First(symbol) без эпсилон к результату
result.update(self.first_sets[symbol] - {""})
# Если symbol не может порождать эпсилон, останавливаемся
if "" not in self.first_sets[symbol]:
all_can_derive_epsilon = False
break
# Если все символы могут порождать эпсилон, добавляем эпсилон к результату
if all_can_derive_epsilon:
result.add("")
return result
def _fill_lookup_table(self):
# Для каждого нетерминала A
for non_terminal, rules in self.productions.items():
# Формируем таблицу синтаксического анализа
self.lookup_table[non_terminal] = {}
# Для каждого правила A → w
for rule in rules:
# Вычисляем First(w)
first_of_rule = self._first_of_sequence(rule)
# Для каждого терминала a в First(w)
for terminal in first_of_rule - {""}:
self._add_to_lookup_table(non_terminal, terminal, rule)
# Если эпсилон в First(w), то для каждого b в Follow(A)
if "" in first_of_rule:
for terminal in self.follow_sets[non_terminal]:
self._add_to_lookup_table(non_terminal, terminal, rule)
def _add_to_lookup_table(self, non_terminal, terminal, rule):
if terminal in self.lookup_table[non_terminal]:
raise ValueError(
"\nГрамматика не является LL(1)-грамматикой.\n"
f'Распознаваемый нетерминал: "{non_terminal}"\n'
f'Поступающий на вход терминал: "{terminal}"\n'
f"Неоднозначность между правилами:\n"
f"{non_terminal} -> {' '.join(rule)}\n"
f"{non_terminal} -> {' '.join(self.lookup_table[non_terminal][terminal])}"
)
self.lookup_table[non_terminal][terminal] = rule
def format_rules(self) -> str:
result = []
sorted_rules = sorted(self.rule_numbers.items(), key=lambda x: x[1])
for rule, number in sorted_rules:
non_terminal, symbols = rule
rule_text = f"{number}: {non_terminal} -> {' '.join(symbols)}"
result.append(rule_text)
return "\n".join(result)
def format_lookup_table(self) -> str:
table = PrettyTable()
terminals = list(self.terminals)
table.field_names = [""] + terminals + ["$"]
for non_terminal in self.productions:
row = [non_terminal]
for terminal in terminals + ["$"]:
if terminal in self.lookup_table[non_terminal]:
rule = self.lookup_table[non_terminal][terminal]
rule_num = self.rule_numbers.get((non_terminal, tuple(rule)), "")
row.append(f"{rule_num}: {' '.join(rule)}")
else:
row.append(" - ")
table.add_row(row)
return str(table)
def format_first_sets(self) -> str:
"""Форматирует множества FIRST в читаемый вид."""
result = []
result.append("Множества FIRST:")
result.append("=" * 40)
# Сортируем для гарантии порядка вывода
for symbol in sorted(self.first_sets.keys()):
# Заменяем пустую строку на эпсилон для лучшей читаемости
first_set = {
self.EPSILON if item == "" else item for item in self.first_sets[symbol]
}
result.append(f"FIRST({symbol}) = {{{', '.join(sorted(first_set))}}}")
return "\n".join(result)
def format_follow_sets(self) -> str:
"""Форматирует множества FOLLOW в читаемый вид."""
result = []
result.append("Множества FOLLOW:")
result.append("=" * 40)
# Обрабатываем только нетерминалы
for non_terminal in sorted(self.productions.keys()):
follow_set = self.follow_sets.get(non_terminal, set())
result.append(
f"FOLLOW({non_terminal}) = {{{', '.join(sorted(follow_set))}}}"
)
return "\n".join(result)
def analyze(self, input_tokens: list[str]) -> list[int]:
input_tokens = input_tokens.copy()
input_tokens += ["$"]
input_pos = 0
# Инициализируем стек с терминальным символом и начальным символом
stack = ["$", self.start_symbol]
rules_applied = []
while stack:
top = stack[-1]
current_symbol = (
input_tokens[input_pos] if input_pos < len(input_tokens) else "$"
)
# Случай 1: Верхний символ стека - нетерминал
if top in self.productions:
# Ищем правило в таблице синтаксического анализа
if current_symbol in self.lookup_table[top]:
# Получаем правило
production = self.lookup_table[top][current_symbol]
# Удаляем нетерминал из стека
stack.pop()
# Добавляем правило в rules_applied
rule_number = self.rule_numbers[(top, tuple(production))]
rules_applied.append(rule_number)
# Добавляем правило в стек в обратном порядке
for symbol in reversed(production):
stack.append(symbol)
else:
expected_symbols = list(self.lookup_table[top].keys())
raise ValueError(
f"Syntax error: expected one of {expected_symbols}, got '{current_symbol}'"
)
# Случай 2: Верхний символ стека - терминал
elif top != "$":
if top == current_symbol:
# Удаляем терминал из стека
stack.pop()
# Переходим к следующему символу ввода
input_pos += 1
else:
raise ValueError(
f"Syntax error: expected '{top}', got '{current_symbol}'"
)
# Случай 3: Верхний символ стека - $
else: # top == "$"
if current_symbol == "$":
# Успешный синтаксический анализ
stack.pop() # Удаляем $ из стека
else:
raise ValueError(
f"Syntax error: unexpected symbols at end of input: '{current_symbol}'"
)
return rules_applied
def generate(self, symbol: str | None = None) -> tuple[list[str], list[int]]:
"""Генерирует предложение по заданной грамматике. Возвращает список терминалов
и список номеров применённых правил."""
if symbol is None:
return self.generate(self.start_symbol)
# Если символ - терминал, возвращаем его
if symbol not in self.productions:
return [symbol], []
# Выбираем случайное правило для нетерминала
rules = self.productions[symbol]
chosen_rule = random.choice(rules)
# Получаем номер выбранного правила
rule_number = self.rule_numbers[(symbol, tuple(chosen_rule))]
# Инициализируем результаты
terminals = []
rule_numbers = [rule_number]
# Разворачиваем каждый символ в правой части правила
for s in chosen_rule:
sub_terminals, sub_rules = self.generate(s)
terminals.extend(sub_terminals)
rule_numbers.extend(sub_rules)
return terminals, rule_numbers
def generate_derivation_steps(self, rule_numbers: list[int]) -> list[str]:
"""Преобразует список номеров правил в последовательность шагов вывода.
Возвращает список строк, представляющих каждый шаг вывода."""
# Получаем соответствие между номерами правил и самими правилами
rule_details = {num: rule for rule, num in self.rule_numbers.items()}
# Начинаем с начального символа
current = self.start_symbol
steps = [current]
# Применяем каждое правило по порядку
for rule_num in rule_numbers:
if rule_num in rule_details:
non_terminal, replacement = rule_details[rule_num]
# Находим первое вхождение нетерминала и заменяем его
words = current.split()
for i, word in enumerate(words):
if word == non_terminal:
words[i : i + 1] = replacement
break
current = " ".join(words)
steps.append(current)
return steps

26
lab3/programm/grammar.txt Normal file
View File

@@ -0,0 +1,26 @@
Предложение -> Повествовательное "."
Повествовательное -> ПрямойПорядок | Инверсия
ПрямойПорядок -> Подлежащее ДополнениеКПодлежащему Глагол ВторостепенныеЧлены Отрицание
Инверсия -> Обстоятельство Глагол Подлежащее ВторостепенныеЧлены Отрицание
Подлежащее -> ИменнаяГруппа ПридаточноеПредложение | Местоимение
ПридаточноеПредложение -> "," Союз Подлежащее Глагол Отрицание "," | epsilon
ВторостепенныеЧлены -> ВторостепенныйЧлен ВторостепенныеЧлены | epsilon
ВторостепенныйЧлен -> Обстоятельство | Дополнение
Обстоятельство -> ОбстоятельствоВремени | ОбстоятельствоМеста | ОбстоятельствоОбразаДействия
ИменнаяГруппа -> АртикльЛибоМестоимение Прилагательные Существительное
АртикльЛибоМестоимение -> Артикль | ПритяжательноеМестоимение | epsilon
Прилагательные -> Прилагательное Прилагательные | epsilon
ДополнениеКПодлежащему -> Предлог Существительное | epsilon
Дополнение -> АртикльЛибоМестоимение Прилагательные Существительное
ОбстоятельствоМеста -> Предлог АртикльЛибоМестоимение Существительное
Союз -> "welcher" | "welche" | "welches"
Отрицание -> "nicht" | epsilon
Местоимение -> "ich" | "du" | "er" | "sie" | "es" | "wir" | "ihr" | "Sie"
ПритяжательноеМестоимение -> "mein" | "dein" | "sein" | "unser" | "euer" | "ihre"
Артикль -> "der" | "die" | "das" | "ein" | "eine" | "einen" | "einem" | "einer"
Прилагательное -> "alt" | "jung" | "groß" | "klein" | "schön" | "freundlich" | "süß" | "ruhig"
Существительное -> "Mann" | "Frau" | "Kind" | "Buch" | "Brief" | "Freund" | "Abendessen" | "Suppe"
Предлог -> "in" | "auf" | "unter" | "aus" | "mit" | "für" | "zu" | "am"
ОбстоятельствоВремени -> "gestern" | "heute" | "morgen" | "damals" | "jetzt" | "früh" | "spät" | "immer"
ОбстоятельствоОбразаДействия -> "schnell" | "langsam" | "gut" | "schlecht" | "laut" | "leise" | "gern" | "fleißig"
Глагол -> "las" | "schrieb" | "kochte" | "aß" | "ging" | "kam" | "sagte" | "machte" | "liebte"

174
lab3/programm/main.py Normal file
View File

@@ -0,0 +1,174 @@
from grammar import Grammar
def load_grammar(filename: str = "grammar.txt") -> Grammar | None:
try:
with open(filename, "r", encoding="utf-8") as file:
text = file.read()
grammar = Grammar(text)
# Сохраняем информацию о грамматике в файлы
with open("grammar_rules.txt", "w", encoding="utf-8") as output_file:
output_file.write(grammar.format_rules())
print("Правила грамматики с номерами сохранены в grammar_rules.txt")
with open("grammar_lookup_table.txt", "w", encoding="utf-8") as output_file:
output_file.write(grammar.format_lookup_table())
print(
"Таблица синтаксического анализа сохранена в grammar_lookup_table.txt"
)
with open("grammar_first.txt", "w", encoding="utf-8") as output_file:
output_file.write(grammar.format_first_sets())
print("Множества FIRST сохранены в grammar_first.txt")
with open("grammar_follow.txt", "w", encoding="utf-8") as output_file:
output_file.write(grammar.format_follow_sets())
print("Множества FOLLOW сохранены в grammar_follow.txt")
print(f"Грамматика успешно загружена из файла {filename}")
return grammar
except FileNotFoundError:
print(f"Ошибка: Файл {filename} не найден")
return None
except ValueError as e:
print(f"Ошибка при загрузке грамматики: {e}")
return None
except Exception as e:
print(f"Неизвестная ошибка: {e}")
return None
def tokenize_string(input_string: str) -> list[str]:
input_string = input_string.replace(",", " , ").replace(".", " . ")
return input_string.split()
def check_string(grammar: Grammar | None, input_string: str) -> None:
if not grammar:
print("Ошибка: Грамматика не загружена")
return
print(f"Проверка строки: '{input_string}'")
try:
input_tokens = tokenize_string(input_string)
if not input_tokens:
parse_result = grammar.analyze(input_tokens)
else:
try:
input_tokens[0] = input_tokens[0][0].lower() + input_tokens[0][1:]
parse_result = grammar.analyze(input_tokens)
except ValueError as e:
input_tokens[0] = input_tokens[0][0].upper() + input_tokens[0][1:]
parse_result = grammar.analyze(input_tokens)
print(f"Результат: Строка соответствует грамматике")
print(f"Применённые правила: {parse_result}")
# Сохраняем результат анализа в файл
with open("analysis_result.txt", "w", encoding="utf-8") as f:
f.write(f"Input: {input_string}\n")
f.write("Applied rules: ")
f.write(str(parse_result))
f.write("\n\n")
f.write("Derivation steps:\n")
derivation_steps = grammar.generate_derivation_steps(parse_result)
for step in derivation_steps:
f.write(f"{step}\n")
print("Подробный результат анализа сохранен в analysis_result.txt")
except ValueError as e:
print(f"Результат: Строка не соответствует грамматике")
print(f"Ошибка: {e}")
except Exception as e:
print(f"Произошла ошибка при анализе: {e}")
def post_process_string(string: str) -> str:
if string:
string = string[0].upper() + string[1:]
string = string.replace(" ,", ",")
string = string.replace(" .", ".")
string = string.replace(",.", ".")
return string
def generate_string(grammar: Grammar | None) -> None:
if not grammar:
print("Ошибка: Грамматика не загружена")
return
try:
terminals, rules = grammar.generate()
generated_string = " ".join(terminals)
generated_string = post_process_string(generated_string)
print(f"Сгенерированная строка: {generated_string}")
print(f"Применённые правила: {rules}")
# Сохраняем результат генерации в файл
with open("generation_result.txt", "w", encoding="utf-8") as f:
f.write(f"Generated string: {generated_string}\n")
f.write("Applied rules: ")
f.write(str(rules))
f.write("\n\n")
f.write("Derivation steps:\n")
derivation_steps = grammar.generate_derivation_steps(rules)
for step in derivation_steps:
f.write(f"{step}\n")
print("Подробный результат генерации сохранен в generation_result.txt")
except Exception as e:
print(f"Произошла ошибка при генерации: {e}")
def main():
print("Программа для работы с LL(1)-грамматиками")
print("=" * 60)
print("Варианты команд:")
print(" - load <файл> - загрузить грамматику из файла (по умолчанию grammar.txt)")
print(" - check <строка> - проверить, соответствует ли строка грамматике")
print(" - generate - сгенерировать случайную строку по грамматике")
print(" - exit - выход из программы")
print("=" * 60)
# Загружаем грамматику по умолчанию при старте
grammar = load_grammar()
while True:
command = input("\nВведите команду: ").strip()
if not command:
continue
parts = command.split(maxsplit=1)
cmd = parts[0].lower()
if cmd == "exit":
print("Выход из программы.")
break
elif cmd == "load":
filename = "grammar.txt"
if len(parts) > 1:
filename = parts[1].strip()
grammar = load_grammar(filename)
elif cmd == "check":
input_string = ""
if len(parts) > 1:
input_string = parts[1].strip()
check_string(grammar, input_string)
elif cmd == "generate":
generate_string(grammar)
else:
print(f"Неизвестная команда: {cmd}")
print("Доступные команды: load, check, generate, exit")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,2 @@
prettytable==3.16.0
wcwidth==0.2.13

1097
lab3/report.tex Normal file

File diff suppressed because it is too large Load Diff

5
lab4/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
*.zip
*.cmil
*.o
cmilan/src/cmilan.exe
!cmilan/doc/cmilan.pdf

21
lab4/README.md Normal file
View File

@@ -0,0 +1,21 @@
## Компиляция компилятора (Windows)
```powershell
cd .\cmilan\src\
make
```
## Компиляция и запуск кода на Milan (Windows)
Компиляция в файл:
```powershell
.\cmilan\src\cmilan.exe .\cmilan\test\add.mil > compiled.cmil
```
Запуск:
```powershell
.\vm\bin\milanvm.exe compiled.cmil
```
> `.mil` ~ исходный код на milan.
> `.cmil` ~ скомпилированный код milan (*compiled milan*).

10
lab4/cmilan/doc/Makefile Normal file
View 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

Binary file not shown.

956
lab4/cmilan/doc/cmilan.tex Normal file
View 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
View 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)

View 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

View 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>

View 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>

View 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
View 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
View 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

32
lab4/cmilan/src/main.cpp Normal file
View 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;
}
}

293
lab4/cmilan/src/parser.cpp Normal file
View File

@@ -0,0 +1,293 @@
#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
| ++ identifier | -- identifier | identifier++ | identifier--
*/
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);
//Если встретили переменную, то выгружаем значение, лежащее по ее адресу, на вершину стека
// Постфиксный инкремент или декремент
if(see(T_INC) || see(T_DEC)) {
codegen_->emit(DUP);
codegen_->emit(PUSH, 1);
codegen_->emit(see(T_INC) ? ADD : SUB);
codegen_->emit(STORE, varAddress);
next();
}
}
// Префиксный инкремент или декремент
else if(see(T_INC) || see(T_DEC)) {
bool isIncrement = see(T_INC);
next();
mustBe(T_IDENTIFIER);
int varAddress = findOrAddVariable(scanner_->getStringValue());
codegen_->emit(LOAD, varAddress);
codegen_->emit(PUSH, 1);
codegen_->emit(isIncrement ? ADD : SUB);
codegen_->emit(DUP);
codegen_->emit(STORE, 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
View 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

251
lab4/cmilan/src/scanner.cpp Normal file
View File

@@ -0,0 +1,251 @@
#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 '+':
nextChar();
// Ищем оператор инкремента
if(ch_ == '+') {
token_ = T_INC;
nextChar();
}
else {
token_ = T_ADDOP;
arithmeticValue_ = A_PLUS;
}
break;
case '-':
nextChar();
// Ищем оператор декремента
if(ch_ == '-') {
token_ = T_DEC;
nextChar();
}
else {
token_ = T_ADDOP;
arithmeticValue_ = A_MINUS;
}
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];
}

166
lab4/cmilan/src/scanner.h Normal file
View File

@@ -0,0 +1,166 @@
#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, // ";"
T_INC, // Оператор инкремента
T_DEC // Оператор декремента
};
// Функция 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

7
lab4/cmilan/test/add.mil Normal file
View File

@@ -0,0 +1,7 @@
BEGIN
i := 1;
j := 2;
k := i + j;
WRITE(k)
END

View File

@@ -0,0 +1,14 @@
/* это комментарий */
/* это продолжение комментария */
/*****************************
* эта программа печатает 42 *
****************************/
begin /* комментарий в конце строки */
/* комментарий в начале строки */ write(42)
/* многострочный
комментарий */
end
/* комментарий в конце файла */

View 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
View 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
View 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
View 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

View File

@@ -0,0 +1,7 @@
BEGIN
x := 0;
write(x++);
write(x);
write(++x);
write(x)
END

View File

@@ -0,0 +1,6 @@
BEGIN
i := 1;
j := 2;
IF i < --j THEN WRITE(100) ELSE WRITE(-100) FI
END

View File

@@ -0,0 +1,13 @@
BEGIN
y := x++;
write(y);
write(x);
y := 10 - --x;
write(y);
write(x);
y := 10 -++x;
write(y);
write(x)
END

View File

@@ -0,0 +1,5 @@
BEGIN
x := 10;
y := 10 - --x; /* Корректно: минус и декремент разделены пробелом */
y := 10 ---x; /* Некорректно: три минуса подряд */
END

View File

@@ -0,0 +1,3 @@
BEGIN
x := --x++
END

View File

@@ -0,0 +1,5 @@
BEGIN
IF 1 2 THEN WRITE(n) ELSE WRITE(2) FI;
X := Y + 1;
WRITE (X +)
END

View 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
View 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

View 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

View 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
View 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

5
lab4/report/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
**/*
!.gitignore
!report.tex
!img
!img/**

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
lab4/report/img/result1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
lab4/report/img/result2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
lab4/report/img/result3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
lab4/report/img/result4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
lab4/report/img/result5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
lab4/report/img/scheme.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

979
lab4/report/report.tex Normal file
View File

@@ -0,0 +1,979 @@
\documentclass[a4paper, final]{article}
%\usepackage{literat} % Нормальные шрифты
\usepackage[14pt]{extsizes} % для того чтобы задать нестандартный 14-ый размер шрифта
\usepackage{tabularx}
\usepackage[T2A]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[russian]{babel}
\usepackage{amsmath}
\usepackage[left=25mm, top=20mm, right=20mm, bottom=20mm, footskip=10mm]{geometry}
\usepackage{ragged2e} %для растягивания по ширине
\usepackage{setspace} %для межстрочно го интервала
\usepackage{moreverb} %для работы с листингами
\usepackage{indentfirst} % для абзацного отступа
\usepackage{moreverb} %для печати в листинге исходного кода программ
\usepackage{pdfpages} %для вставки других pdf файлов
\usepackage{tikz}
\usepackage{graphicx}
\usepackage{afterpage}
\usepackage{longtable}
\usepackage{float}
\usepackage{fancyvrb}
% \usepackage[paper=A4,DIV=12]{typearea}
\usepackage{pdflscape}
% \usepackage{lscape}
\usepackage{array}
\usepackage{multirow}
\renewcommand\verbatimtabsize{4\relax}
\renewcommand\listingoffset{0.2em} %отступ от номеров строк в листинге
\renewcommand{\arraystretch}{1.4} % изменяю высоту строки в таблице
\usepackage[font=small, singlelinecheck=false, justification=centering, format=plain, labelsep=period]{caption} %для настройки заголовка таблицы
\usepackage{listings} %листинги
\usepackage{xcolor} % цвета
\usepackage{hyperref}% для гиперссылок
\usepackage{enumitem} %для перечислений
\newcommand{\specialcell}[2][l]{\begin{tabular}[#1]{@{}l@{}}#2\end{tabular}}
\setlist[enumerate,itemize]{leftmargin=1.2cm} %отступ в перечислениях
\hypersetup{colorlinks,
allcolors=[RGB]{010 090 200}} %красивые гиперссылки (не красные)
% подгружаемые языки — подробнее в документации listings (это всё для листингов)
\lstloadlanguages{ SQL}
% включаем кириллицу и добавляем кое−какие опции
\lstset{tabsize=2,
breaklines,
basicstyle=\footnotesize,
columns=fullflexible,
flexiblecolumns,
numbers=left,
numberstyle={\footnotesize},
keywordstyle=\color{blue},
inputencoding=cp1251,
extendedchars=true
}
\lstdefinelanguage{MyC}{
language=SQL,
% ndkeywordstyle=\color{darkgray}\bfseries,
% identifierstyle=\color{black},
% morecomment=[n]{/**}{*/},
% commentstyle=\color{blue}\ttfamily,
% stringstyle=\color{red}\ttfamily,
% morestring=[b]",
% showstringspaces=false,
% morecomment=[l][\color{gray}]{//},
keepspaces=true,
escapechar=\%,
texcl=true
}
\textheight=24cm % высота текста
\textwidth=16cm % ширина текста
\oddsidemargin=0pt % отступ от левого края
\topmargin=-1.5cm % отступ от верхнего края
\parindent=24pt % абзацный отступ
\parskip=5pt % интервал между абзацами
\tolerance=2000 % терпимость к "жидким" строкам
\flushbottom % выравнивание высоты страниц
% Настройка листингов
\lstset{
language=python,
extendedchars=\true,
inputencoding=utf8,
keepspaces=true,
% captionpos=b, % подписи листингов снизу
}
\begin{document} % начало документа
% НАЧАЛО ТИТУЛЬНОГО ЛИСТА
\begin{center}
\hfill \break
\hfill \break
\normalsize{МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ\\
федеральное государственное автономное образовательное учреждение высшего образования «Санкт-Петербургский политехнический университет Петра Великого»\\[10pt]}
\normalsize{Институт компьютерных наук и кибербезопасности}\\[10pt]
\normalsize{Высшая школа технологий искусственного интеллекта}\\[10pt]
\normalsize{Направление: 02.03.01 <<Математика и компьютерные науки>>}\\
\hfill \break
\hfill \break
\hfill \break
\hfill \break
\large{Лабораторная работа №4}\\
\large{<<Доработка компилятора языка MiLan>>}\\
\large{по дисциплине}\\
\large{<<Математическая логика и теория автоматов>>}\\
\large{Вариант 8}\\
% \hfill \break
\hfill \break
\end{center}
\small{
\begin{tabular}{lrrl}
\!\!\!Студент, & \hspace{2cm} & & \\
\!\!\!группы 5130201/20102 & \hspace{2cm} & \underline{\hspace{3cm}} &Тищенко А. А. \\\\
\!\!\!Преподаватель & \hspace{2cm} & \underline{\hspace{3cm}} & Востров А. В. \\\\
&&\hspace{4cm}
\end{tabular}
\begin{flushright}
<<\underline{\hspace{1cm}}>>\underline{\hspace{2.5cm}} 2025г.
\end{flushright}
}
\hfill \break
% \hfill \break
\begin{center} \small{Санкт-Петербург, 2025} \end{center}
\thispagestyle{empty} % выключаем отображение номера для этой страницы
% КОНЕЦ ТИТУЛЬНОГО ЛИСТА
\newpage
\tableofcontents
\newpage
\section*{Введение}
\addcontentsline{toc}{section}{Введение}
Лабораторная №4 заключается в доработке компилятора языка MiLan согласно варианту работы.
\textit{Вариант 8}: В компилятор необходимо добавить поддержку операций инкремента и декремента, как постфиксных, так и префиксных: \texttt{++i}, \texttt{\textminus{}\textminus{}i}, \texttt{i++}, \texttt{i\textminus{}\textminus{}}. С точки зрения действия на операнд i между префиксными и постфиксными операциями разницы нет. Если эти выражения являются частью более сложного выражения, то при префиксной форме сначала изменяется операнд i, и уже измененный результат используется в выражении. При постфиксной форме операторов инкремента или декремента сначала операнд i используется в выражении, а потом уже изменяется.
\newpage
\section {Математическое описание}
\subsection{Обзор языка MiLan}
Язык Милан — учебный язык программирования, описанный в учебнике~\cite{karpov}.
Программа на Милане представляет собой последовательность операторов, заключенных между ключевыми словами \texttt{begin} и \texttt{end}. Операторы отделяются друг от друга точкой с запятой. После последнего оператора в блоке точка с запятой не ставится. Компилятор \texttt{CMilan} не учитывает регистр символов в именах переменных и ключевых словах.
В базовую версию языка Милан входят следующие конструкции: константы, идентификаторы, арифметические операции над целыми числами, операторы чтения чисел со стандартного ввода и печати чисел на стандартный вывод, оператор присваивания, условный оператор, оператор цикла с предусловием.
Программа может содержать комментарии, которые могут быть многострочными. Комментарий начинается символами \texttt{/*} и заканчивается символами \texttt{*/}. Вложенные комментарии не допускаются.
В данной лабораторной рассматривается добавление поддержки операций инкремента и декремента, как постфиксных, так и префиксных: \texttt{++i}, \texttt{\textminus{}\textminus{}i}, \texttt{i++}, \texttt{i\textminus{}\textminus{}}. С точки зрения действия на операнд i между префиксными и постфиксными операциями разницы нет. Если эти выражения являются частью более сложного выражения, то при префиксной форме сначала изменяется операнд i, и уже измененный результат используется в выражении. При постфиксной форме операторов инкремента или декремента сначала операнд i используется в выражении, а потом уже изменяется.
\subsection{Лексический анализ}
В реальных трансляторах языков программирования (ЯП) первой фазой является так называемый лексический анализ входной программы — предварительная
обработка входного текста с выделением в нем структурно значимых единиц — лексем. На Рис.~\ref{fig:scheme} представлена схема транслятора.
\begin{figure}[h!]
\centering
\includegraphics[width=0.7\linewidth]{img/scheme.png}
\caption{Схема транслятора.}
\label{fig:scheme}
\end{figure}
Лексемы — минимальные единицы языка, которые имеют смысл.
Значение лексемы, определяющее подстроку символов входной цепочки, соответствующих распознанному классу лексемы. В зависимости от класса, значение
лексемы может быть преобразовано во внутреннее представление уже на этапе лексического анализа.
Класс лексемы, определяющий общее название для категории элементов, обладающих общими свойствами (идентификатор, целое число, строка символов...).
Лексический анализатор обрабатывает входную цепочку, а на его вход подаются
символы, сгруппированные по категориям. Поэтому перед лексическим анализом
осуществляется дополнительная обработка, сопоставляющая с каждым символом его
класс, что позволяет сканеру манипулировать единым понятием для целой группы
символов.
Лексический анализатор (лексер) — это конечный автомат, который преобразует входную строку символов в последовательность токенов. Формально его можно описать следующим образом:
Пусть заданы:
\begin{itemize}
\item $\Sigma$ — входной алфавит (множество допустимых символов)
\item $T$ — множество типов токенов
\item $D$ — множество допустимых значений токенов
\end{itemize}
Тогда лексический анализатор реализует отображение:
\[
F_{\text{lexer}} : \Sigma^* \rightarrow (T \times D)^*
\]
где:
\begin{itemize}
\item $\Sigma^*$ — множество всех возможных строк над алфавитом $\Sigma$
\item $(T \times D)^*$ — множество последовательностей пар (тип токена, значение)
\end{itemize}
Процесс лексического анализа можно представить как \textbf{детерминированный конечный автомат (ДКА)}:
\[
M = (Q, \Sigma, \delta, q_0, F),
\]
где:
\begin{itemize}
\item $Q$ — множество состояний автомата
\item $\delta : Q \times \Sigma \rightarrow Q$ — функция переходов
\item $q_0 \in Q$ — начальное состояние
\item $F \subseteq Q$ — множество конечных состояний
\end{itemize}
Для каждого распознанного токена $t_i$ выполняется:
\[
t_i = (\text{type}, \text{value}), \quad \text{где } \text{type} \in T, \text{value} \in D
\]
\subsection{Синтаксический анализ}
Синтаксический анализ — процесс сопоставления линейной последовательности лексем естественного или формального языка с его формальной грамматикой.
Результатом обычно является дерево разбора. Обычно применяется совместно с лексическим анализом.
Синтаксический анализатор выражений (парсер) — часть программы,
выполняющая чтение и анализ выражения.
Существует два типа алгоритмов синтаксического анализа: нисходящий и восходящий:
\begin{itemize}
\item Нисходящий парсер — продукции грамматики раскрываются, начиная со стартового символа, до получения требуемой последовательности токенов.
\item Восходящий парсер — продукции восстанавливаются из правых частей, начиная с токенов и кончая стартовым символом.
\end{itemize}
Грамматика языка MiLan использует нисходящий парсер. При восстановлении
синтаксического дерева при нисходящем разборе слева направо последовательно анализирует все поддеревья, принадлежащие самому левому нетерминалу. Когда самым
левым становится другой нетерминал, анализируется уже он.
Компилятор CMilan включает три компонента (Рис.~\ref{fig:compiler}):
\begin{enumerate}
\item лексический анализатор;
\item синтаксический анализатор;
\item генератор команд виртуальной машины Милана.
\end{enumerate}
\begin{figure}[h!]
\centering
\includegraphics[width=0.7\linewidth]{img/compiler.png}
\caption{Компоненты компилятора CMilan.}
\label{fig:compiler}
\end{figure}
\subsection{Грамматика языка MiLan}
Грамматика языка Милан является контекстно-свободной, так как удовлетворяет определению КС-грамматики, т.е. продукции грамматики имеют вид $A \rightarrow \beta$, где $A$ одиночный нетерминал, а $\beta$ произвольная цепочка из терминалов и нетерминалов. Более того, грамматика языка Милан является LL(1) грамматикой, так как необходимо просмотреть поток всего на один символ вперед при принятии решения о том, какое правило грамматики необходимо применить. В данной работе в грамматику были добавлены операции инкремента и декремента, однако это не повлияло на LL(1) свойство грамматики.
Грамматика языка Милан, расширенная операцией инкремента и декремента, в форме Бэкуса-Наура приведена ниже:
\begin{Verbatim}[commandchars=\\\{\}]
<program> ::= begin <statementList> end
<statementList> ::= <statement> ; <statementList>
| epsilon
<statement> ::= <ident> := <expression>
| if <relation> then
<statementList> [else <statementList>] fi
| while <relation> do <statementList> od
| write ( <expression> )
<expression> ::= <term> {<addop> <term>}
<term> ::= <factor> {<mulop> <factor>}
<factor> ::= <ident>
| <number>
| ( <expression> )
| read
| - <factor>
\textcolor{green!60!black}{| <ident> ++}
\textcolor{green!60!black}{| <ident> --}
\textcolor{green!60!black}{| ++ <ident>}
\textcolor{green!60!black}{| -- <ident>}
<relation> ::= <expression> <cmpi> <expression>
<addop> ::= +|-
<mulop> ::= *|/
<cmpi> ::= =|!=|<|<=|>|>=
<number> ::= <digit> {<digit>}
<ident> ::= <letter> {<letter> | <digit>}
<letter> ::= a|b|c | ...| z|A|B|C | ...| Z
<digit> ::= 0|1|2|3|4|5|6|7|8|9
\end{Verbatim}
Изменения коснулись только правила \texttt{<factor>}, в котором были добавлены 4 новые продукции, описывающие операции постфиксного и префиксного инкремента и декремента.
Синтаксические диаграммы для всех нетерминалов грамматики приведены на Рис.~\ref{fig:syntax_diagram_program} — Рис.~\ref{fig:syntax_diagram_digit}. Обновлённая синтаксическая диаграмма для нетерминала \texttt{<factor>} приведена на Рис.~\ref{fig:syntax_diagram_factor}.
\begin{figure}[h!]
\centering
\includegraphics[width=0.9\linewidth]{img/syntax_diagram_program.png}
\caption{Синтаксическая диаграмма для нетерминала \texttt{<program>}.}
\label{fig:syntax_diagram_program}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=0.7\linewidth]{img/syntax_diagram_statementList.png}
\caption{Синтаксическая диаграмма для нетерминала \texttt{<statementList>}.}
\label{fig:syntax_diagram_statementList}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=1\linewidth]{img/syntax_diagram_statement.png}
\caption{Синтаксическая диаграмма для нетерминала \texttt{<statement>}.}
\label{fig:syntax_diagram_statement}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=0.7\linewidth]{img/syntax_diagram_expression.png}
\caption{Синтаксическая диаграмма для нетерминала \texttt{<expression>}.}
\label{fig:syntax_diagram_expression}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=1\linewidth]{img/syntax_diagram_relation.png}
\caption{Синтаксическая диаграмма для нетерминала \texttt{<relation>}.}
\label{fig:syntax_diagram_relation}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=0.5\linewidth]{img/syntax_diagram_addop.png}
\caption{Синтаксическая диаграмма для нетерминала \texttt{<addop>}.}
\label{fig:syntax_diagram_addop}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=0.5\linewidth]{img/syntax_diagram_mulop.png}
\caption{Синтаксическая диаграмма для нетерминала \texttt{<mulop>}.}
\label{fig:syntax_diagram_mulop}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=0.7\linewidth]{img/syntax_diagram_factor.png}
\caption{Синтаксическая диаграмма для нетерминала \texttt{<factor>}, дополненная операциями инкремента и декремента (отмечены зеленым цветом).}
\label{fig:syntax_diagram_factor}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=0.6\linewidth]{img/syntax_diagram_number.png}
\caption{Синтаксическая диаграмма для нетерминала \texttt{<number>}.}
\label{fig:syntax_diagram_number}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=0.6\linewidth]{img/syntax_diagram_ident.png}
\caption{Синтаксическая диаграмма для нетерминала \texttt{<ident>}.}
\label{fig:syntax_diagram_ident}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=1\linewidth]{img/syntax_diagram_letter.png}
\caption{Синтаксическая диаграмма для нетерминала \texttt{<letter>}.}
\label{fig:syntax_diagram_letter}
\end{figure}
\begin{figure}[h!]
\centering
\includegraphics[width=1\linewidth]{img/syntax_diagram_digit.png}
\caption{Синтаксическая диаграмма для нетерминала \texttt{<digit>}.}
\label{fig:syntax_diagram_digit}
\end{figure}
\newpage
\phantom{text}
\newpage
\phantom{text}
\newpage
\phantom{text}
\section{Особенности реализации}
\subsection{Изменения в лексическом анализаторе}
\subsubsection{Файл \texttt{Scanner.h}}
В перечисление \texttt{Token} в файле \texttt{Scanner.h} были добавлены новые токены: \texttt{INC}, \texttt{DEC}. Код обновлённого перечисления представлен в листинге~\ref{lst:token}, добавлены строки 24-25.
\begin{lstlisting}[language=C++, caption=Обновлённое перечисление \texttt{Token}, label=lst:token]
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, // ";"
T_INC, // Оператор инкремента
T_DEC // Оператор декремента
};
\end{lstlisting}
\subsubsection{Файл \texttt{Scanner.cpp}}
В массив названий токенов \texttt{tokenNames\_} были добавлены новые строки, соответствующие инкременту и декременту: \texttt{"++"} и \texttt{"\textminus\textminus"}, соответственно. Код обновлённого массива представлен в листинге~\ref{lst:token_names}, добавлены строки 24-25.
\begin{lstlisting}[language=C++, caption=Обновлённый массив \texttt{tokenNames\_}, label=lst:token_names]
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",
"'('",
"')'",
"';'",
"'++'",
"'--'",
};
\end{lstlisting}
Также была обновлена функция \texttt{nextToken()}. В ней были добавлены новые условия для распознавания инкремента и декремента. Код обновлённой функции представлен в листинге~\ref{lst:next_token}, изменения коснулись строк 158-181.
\begin{lstlisting}[language=C++, caption=Обновлённая функция \texttt{nextToken()}, label=lst:next_token]
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 '+':
nextChar();
// Ищем оператор инкремента
if(ch_ == '+') {
token_ = T_INC;
nextChar();
}
else {
token_ = T_ADDOP;
arithmeticValue_ = A_PLUS;
}
break;
case '-':
nextChar();
// Ищем оператор декремента
if(ch_ == '-') {
token_ = T_DEC;
nextChar();
}
else {
token_ = T_ADDOP;
arithmeticValue_ = A_MINUS;
}
break;
case '*':
token_ = T_MULOP;
arithmeticValue_ = A_MULTIPLY;
nextChar();
break;
//Иначе лексема ошибки.
default:
token_ = T_ILLEGAL;
nextChar();
break;
}
}
}
\end{lstlisting}
\subsection{Изменения в компиляторе}
\subsubsection{Файл \texttt{Parser.cpp}}
В метод \texttt{factor()} объекта \texttt{Parser} была добавлена логика генерации команд для префиксного и постфиксного инкремента и декремента. Код обновлённого метода представлен в листинге~\ref{lst:parser_cpp}, изменения коснулись строк 20-27 (постфиксный инкремент и декремент) и строк 29-41 (префиксный инкремент и декремент).
Для постфиксного инкремента и декремента генерируется следующая последовательность команд виртуальной машины языка MiLan:
\begin{itemize}
\item \texttt{LOAD <адрес переменной>} - загрузка значения переменной на вершину стека.
\item \texttt{DUP} - дублирование значения на вершине стека до выполнения операции инкремента или декремента, так как постфиксная операция возвращает старое значение переменной.
\item \texttt{PUSH 1} - загрузка константы 1 на вершину стека
\item \texttt{ADD} или \texttt{SUB} - сложение или вычитание значения на вершине стека с константой 1.
\item \texttt{STORE <адрес переменной>} - сохранение результата на вершине стека по адресу переменной.
\end{itemize}
Для префиксного инкремента и декремента генерируется следующая последовательность команд виртуальной машины языка MiLan:
\begin{itemize}
\item \texttt{LOAD <адрес переменной>} - загрузка значения переменной на вершину стека.
\item \texttt{PUSH 1} - загрузка константы 1 на вершину стека
\item \texttt{ADD} или \texttt{SUB} - сложение или вычитание значения на вершине стека с константой 1.
\item \texttt{DUP} - дублирование значения на вершине стека после выполнения операции инкремента или декремента, так как префиксная операция возвращает новое значение переменной.
\item \texttt{STORE <адрес переменной>} - сохранение результата на вершине стека по адресу переменной.
\end{itemize}
\begin{lstlisting}[language=C++, caption=Обновлённый метод \texttt{factor()}, label=lst:parser_cpp]
void Parser::factor()
{
/*
Множитель описывается следующими правилами:
<factor> -> number | identifier | -<factor> | (<expression>) | READ
| ++ identifier | -- identifier | identifier++ | identifier--
*/
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);
//Если встретили переменную, то выгружаем значение, лежащее по ее адресу, на вершину стека
// Постфиксный инкремент или декремент
if(see(T_INC) || see(T_DEC)) {
codegen_->emit(DUP);
codegen_->emit(PUSH, 1);
codegen_->emit(see(T_INC) ? ADD : SUB);
codegen_->emit(STORE, varAddress);
next();
}
}
// Префиксный инкремент или декремент
else if(see(T_INC) || see(T_DEC)) {
bool isIncrement = see(T_INC);
next();
mustBe(T_IDENTIFIER);
int varAddress = findOrAddVariable(scanner_->getStringValue());
codegen_->emit(LOAD, varAddress);
codegen_->emit(PUSH, 1);
codegen_->emit(isIncrement ? ADD : SUB);
codegen_->emit(DUP);
codegen_->emit(STORE, 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.");
}
}
\end{lstlisting}
\newpage
\section{Результаты работы программы}
\subsection*{Программа №1}
Исходный код программы представлен в листинге~\ref{lst:program1}.
\begin{lstlisting}[caption=Исходный код программы №1, label=lst:program1]
BEGIN
x := 0;
write(x++);
write(x);
write(++x);
write(x)
END
\end{lstlisting}
Последовательность команд, сгенерированная компилятором для данной программы, представлена в листинге~\ref{lst:program1_commands}.
\begin{lstlisting}[caption={Последовательность команд, сгенерированная компилятором для программы №1.}, label={lst:program1_commands}, numbers=none]
0: PUSH 0
1: STORE 0
2: LOAD 0
3: DUP
4: PUSH 1
5: ADD
6: STORE 0
7: PRINT
8: LOAD 0
9: PRINT
10: LOAD 0
11: PUSH 1
12: ADD
13: DUP
14: STORE 0
15: PRINT
16: LOAD 0
17: PRINT
18: STOP
\end{lstlisting}
Результаты работы виртуальной машины для программы №1 представлены на Рис.~\ref{fig:result1}.
\begin{figure}[h!]
\centering
\includegraphics[width=0.4\linewidth]{img/result1.png}
\caption{Результаты работы виртуальной машины для программы №1.}
\label{fig:result1}
\end{figure}
\subsection*{Программа №2}
Исходный код программы представлен в листинге~\ref{lst:program2}.
\begin{lstlisting}[caption=Исходный код программы №2, label=lst:program2]
BEGIN
i := 1;
j := 2;
IF i < --j THEN WRITE(100) ELSE WRITE(-100) FI
END
\end{lstlisting}
Последовательность команд, сгенерированная компилятором для данной программы, представлена в листинге~\ref{lst:program2_commands}.
\begin{lstlisting}[caption={Последовательность команд, сгенерированная компилятором для программы №2.}, label={lst:program2_commands}, numbers=none]
0: PUSH 1
1: STORE 0
2: PUSH 2
3: STORE 1
4: LOAD 0
5: LOAD 1
6: PUSH 1
7: SUB
8: DUP
9: STORE 1
10: COMPARE 2
11: JUMP_NO 15
12: PUSH 100
13: PRINT
14: JUMP 18
15: PUSH 100
16: INVERT
17: PRINT
18: STOP
\end{lstlisting}
Результаты работы виртуальной машины для программы №2 представлены на Рис.~\ref{fig:result2}.
\begin{figure}[h!]
\centering
\includegraphics[width=0.4\linewidth]{img/result2.png}
\caption{Результаты работы виртуальной машины для программы №2.}
\label{fig:result2}
\end{figure}
\subsection*{Программа №3}
Исходный код программы представлен в листинге~\ref{lst:program3}.
\begin{lstlisting}[caption=Исходный код программы №3, label=lst:program3]
BEGIN
y := x++;
write(y);
write(x);
y := 10 - --x;
write(y);
write(x);
y := 10 -++x;
write(y);
write(x)
END
\end{lstlisting}
Последовательность команд, сгенерированная компилятором для данной программы, представлена в листинге~\ref{lst:program3_commands}.
\begin{lstlisting}[caption={Последовательность команд, сгенерированная компилятором для программы №3.}, label={lst:program3_commands}, numbers=none]
0: LOAD 1
1: DUP
2: PUSH 1
3: ADD
4: STORE 1
5: STORE 0
6: LOAD 0
7: PRINT
8: LOAD 1
9: PRINT
10: PUSH 10
11: LOAD 1
12: PUSH 1
13: SUB
14: DUP
15: STORE 1
16: SUB
17: STORE 0
18: LOAD 0
19: PRINT
20: LOAD 1
21: PRINT
22: PUSH 10
23: LOAD 1
24: PUSH 1
25: ADD
26: DUP
27: STORE 1
28: SUB
29: STORE 0
30: LOAD 0
31: PRINT
32: LOAD 1
33: PRINT
34: STOP
\end{lstlisting}
Результаты работы виртуальной машины для программы №3 представлены на Рис.~\ref{fig:result3}.
\begin{figure}[h!]
\centering
\includegraphics[width=0.4\linewidth]{img/result3.png}
\caption{Результаты работы виртуальной машины для программы №3.}
\label{fig:result3}
\end{figure}
\subsection*{Программа №4}
Исходный код программы представлен в листинге~\ref{lst:program4}.
\begin{lstlisting}[caption=Исходный код программы №4, label=lst:program4]
BEGIN
x := 10;
y := 10 - --x; /* Корректно: минус и декремент разделены пробелом */
y := 10 ---x; /* Некорректно: три минуса подряд */
END
\end{lstlisting}
Результат запуска компилятора для данной программы представлен на Рис.~\ref{fig:result4}.
\begin{figure}[h!]
\centering
\includegraphics[width=0.5\linewidth]{img/result4.png}
\caption{Результат запуска компилятора для программы №4.}
\label{fig:result4}
\end{figure}
\subsection*{Программа №5}
Исходный код программы представлен в листинге~\ref{lst:program5}.
\begin{lstlisting}[caption=Исходный код программы №5, label=lst:program5]
BEGIN
x := --x++
END
\end{lstlisting}
Результат запуска компилятора для данной программы представлен на Рис.~\ref{fig:result5}.
\begin{figure}[h!]
\centering
\includegraphics[width=0.5\linewidth]{img/result5.png}
\caption{Результат запуска компилятора для программы №5.}
\label{fig:result5}
\end{figure}
\newpage
\section*{Заключение}
\addcontentsline{toc}{section}{Заключение}
В ходе выполнения лабораторной работы была успешно реализована поддержка операций инкремента и декремента в компиляторе языка MiLan. Были добавлены как префиксные (\texttt{++i}, \texttt{\textminus{}\textminus{}i}), так и постфиксные (\texttt{i++}, \texttt{i\textminus{}\textminus{}}) операторы с корректной семантикой их выполнения. Модификации затронули как лексический анализатор (добавление новых токенов \texttt{T\_INC} и \texttt{T\_DEC}), так и синтаксический анализатор, использующий метод рекурсивного спуска (расширение метода \texttt{factor()} для обработки новых конструкций).
Расширенная грамматика языка MiLan осталась контекстно-свободной и сохранила свойство LL(1), что позволило избежать значительных изменений в существующей архитектуре компилятора. Было добавлено лишь несколько новых правил в определение нетерминала \texttt{<factor>}.
Из достоинств реализации можно отметить минимальность вносимых изменений в существующую архитектуру компилятора и сохранение всех ранее реализованных функций. При этом реализация выполняет поставленные задачи, корректно обрабатывая возможные случаи использования операторов инкремента и декремента.
К недостаткам текущей реализации можно отнести отсутствие каких-либо оптимизаций при генерации команд виртуальной машины, что может приводить к избыточному количеству инструкций. Также в коде наблюдается некоторое дублирование логики между обработкой инкремента и декремента.
В качестве направлений масштабирования можно предложить добавление составных операторов присваивания (\texttt{+=}, \texttt{-=}, \texttt{*=}, \texttt{/=}), для которых генерируется схожая последовательность низкоуровневых команд. Также возможна реализация оптимизаций генерируемого кода, таких как устранение избыточных команд в простых случаях.
На выполнение лабораторной работы ушло около 6 часов. Работа была выполнена в среде разработки Visual Studio Code.
\newpage
\section*{Список литературы}
\addcontentsline{toc}{section}{Список литературы}
\vspace{-1.5cm}
\begin{thebibliography}{0}
\bibitem{vostrov}
Востров, А.В. Курс лекций по дисциплине <<Математическая логика>>. URL \url{https://tema.spbstu.ru/compiler/} (дата обращения 01.05.2025 г.)
\bibitem{aho}
А. Ахо, М. Лам, Р. Сети, Дж. Ульман, Компиляторы: принципы, технологии и инструментарий, 2-е изд. М.: Вильямс, 2011.
\bibitem{karpov}
Ю.Г. Карпов, Теория и технология программирования. Основы построения трансляторов. СПб.: БХВ-Петербург, 2005.
\end{thebibliography}
\end{document}

BIN
lab4/vm/bin/milanvm.exe Normal file

Binary file not shown.

10
lab4/vm/doc/changelog.txt Normal file
View File

@@ -0,0 +1,10 @@
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>:
==================
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 1.2:
* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ymilan <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> NOP (<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>).
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 1.1:
* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ymilan 1.1.

224
lab4/vm/doc/vm.txt Normal file
View File

@@ -0,0 +1,224 @@
== <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ==
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD>.
<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
NOP
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>; <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
STOP
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>; <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
LOAD <<3C><><EFBFBD><EFBFBD><EFBFBD>>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <<3C><><EFBFBD><EFBFBD><EFBFBD>>.
STORE <<3C><><EFBFBD><EFBFBD><EFBFBD>>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <<3C><><EFBFBD><EFBFBD><EFBFBD>>.
<20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
BLOAD <<3C><><EFBFBD><EFBFBD><EFBFBD>>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>:
<<3C><><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>> = <<3C><><EFBFBD><EFBFBD><EFBFBD>> + <<3C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>>. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> BLOAD <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<20><><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
BSTORE <<3C><><EFBFBD><EFBFBD><EFBFBD>>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>:
<<3C><><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>> = <<3C><><EFBFBD><EFBFBD><EFBFBD>> + <<3C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<20><><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <<3C><><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>>.
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> BSTORE <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
<20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>: [10, 20, ...]. <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> BSTORE 5
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 15 <20><><EFBFBD><EFBFBD><EFBFBD> 20.
PUSH <<3C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <<3C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>> <20> <20><><EFBFBD><EFBFBD>.
POP
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD>.
DUP
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>.
ADD
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
MULT
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
SUB
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <a>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <b>
<20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <b> - <a>.
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
PUSH 10
PUSH 8
SUB
<20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> 2.
DIV
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <a>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <b>
<20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <b> / <a>. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>. <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <a> = 0, <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
INVERT
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
COMPARE <<3C><><EFBFBD>>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <a>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <b>,
<20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <b> <20> <a>.
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <<3C><><EFBFBD>>:
<<3C><><EFBFBD>> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
0 <b> = <a>
1 <b> != <a>
2 <b> < <a>
3 <b> > <a>
4 <b> <= <a>
5 <b> >= <a>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 1, <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20> 0 <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
PUSH 5
PUSH 7
COMPARE 2
<20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 1 (<28><><EFBFBD> <20><><EFBFBD> 5 < 7).
JUMP <<3C><><EFBFBD><EFBFBD><EFBFBD>>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <<3C><><EFBFBD><EFBFBD><EFBFBD>>, <20><> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <<3C><><EFBFBD><EFBFBD><EFBFBD>>. <20><><EFBFBD><EFBFBD>
<<3C><><EFBFBD><EFBFBD><EFBFBD>> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
JUMP_YES <<3C><><EFBFBD><EFBFBD><EFBFBD>>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <<3C><><EFBFBD><EFBFBD><EFBFBD>>, <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>; <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD>.
JUMP_NO <<3C><><EFBFBD><EFBFBD><EFBFBD>>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <<3C><><EFBFBD><EFBFBD><EFBFBD>>, <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 0; <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD>. <20><><EFBFBD><EFBFBD>
<<3C><><EFBFBD><EFBFBD><EFBFBD>> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
INPUT
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>
<20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20> <20><><EFBFBD><EFBFBD>. <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
PRINT
<20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<20><><EFBFBD><EFBFBD><EFBFBD>. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>.
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>. <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ';' <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD> <20> <20><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ':'. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> 0.
<EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>: STOP. <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>:
------------------------------------------------------------
0: INPUT
1: STORE 42 ; n := READ
2: LOAD 14
3: PUSH 4
4: LOAD 42
5: COMPARE 2
6: JUMP_NO 9
7: PUSH 10
8: STORE 42
9: LOAD 42
10: PRINT
11: STOP
-------------------------------------------------------------
<EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> SET:
SET <<3C><><EFBFBD><EFBFBD><EFBFBD>> <<3C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>>
<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> SET <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <<3C><><EFBFBD><EFBFBD><EFBFBD>> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <<3C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>>.
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> SET <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>. <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> SET <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> SET <<3C><><EFBFBD><EFBFBD><EFBFBD>> <<3C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
PUSH <<3C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>>
STORE <<3C><><EFBFBD><EFBFBD><EFBFBD>>
<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>.
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> SET <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>. <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> SET:
------------------------------------
SET 0 15
SET 1 40
0: LOAD 0
1: LOAD 1
2: ADD
3: PRINT
4: STOP
------------------------------------
<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> 55.

15
lab4/vm/vm/Makefile Normal file
View File

@@ -0,0 +1,15 @@
mvm: vm.c lex.yy.c vmparse.tab.h main.c
gcc -o mvm main.c vm.c lex.yy.c vmparse.tab.c
lex.yy.c: vmlex.l
flex vmlex.l
vmparse.tab.h: vmparse.y
bison -d vmparse.y
clean:
rm lex.yy.c vmparse.tab.h vmparse.tab.c
distclean:
rm mvm lex.yy.c vmparse.tab.h vmparse.tab.c

1690
lab4/vm/vm/lex.yy.c Normal file

File diff suppressed because it is too large Load Diff

46
lab4/vm/vm/main.c Normal file
View File

@@ -0,0 +1,46 @@
#include "vm.h"
#include "vmparse.tab.h"
#include <stdio.h>
#include <stdlib.h>
extern FILE *yyin;
int need_close = 0;
int yyparse();
void milan_error(char const * msg)
{
if(need_close)
fclose(yyin);
fprintf(stderr, msg);
exit(1);
}
int main(int argc, char **argv)
{
if(argc < 2) {
yyin = stdin;
printf("Reading input from stdin\n");
}
else {
yyin = fopen(argv[1], "rt");
if(!yyin) {
printf("Unable to read %s\n", argv[1]);
return 1;
}
need_close = 1;
printf("Reading input from %s\n", argv[1]);
}
if(0 == yyparse()) {
run();
}
if(need_close) {
fclose(yyin);
}
return 0;
}

6
lab4/vm/vm/test/bop1.ms Normal file
View File

@@ -0,0 +1,6 @@
4: STOP
1: PUSH 20
2: ADD
3: PRINT
0: PUSH 10
2: MULT

8
lab4/vm/vm/test/bop2.ms Normal file
View File

@@ -0,0 +1,8 @@
0: PUSH 10
1: PUSH -1
2: BSTORE 2
3: PUSH 0
4: BLOAD 1
5: PRINT
6: STOP

37
lab4/vm/vm/test/fib.ms Normal file
View File

@@ -0,0 +1,37 @@
; <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> N-<2D><> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
SET 0 0 ; <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 0
SET 1 1 ; <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 1
SET 1000 0 ; <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> n
SET 1001 1 ; <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> a
SET 1002 1 ; <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> b
; <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
0: INPUT
1: STORE 1000 ; n := READ
2: LOAD 1000
3: LOAD 1
4: COMPARE 3 ; IF n > 1
5: JUMP_NO 17
6: LOAD 1002 ; STACK <- b
7: LOAD 1001 ; STACK <- a
8: LOAD 1002
9: STORE 1001 ; a := b
10: ADD
11: STORE 1002 ; b := <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_a + b
12: LOAD 1000
13: LOAD 1
14: SUB
15: STORE 1000 ; n := n - 1
16: JUMP 2
17: LOAD 1001 ; WRITE(a)
18: PRINT
19: STOP

21
lab4/vm/vm/test/fib2.ms Normal file
View File

@@ -0,0 +1,21 @@
SET 0 1
SET 1 1
0: INPUT
1: DUP
2: PUSH 1
3: COMPARE 3
4: JUMP_NO 14
5: LOAD 1
6: LOAD 0
7: LOAD 1
8: STORE 0
9: ADD
10: STORE 1
11: PUSH 1
12: SUB
13: JUMP 1
14: LOAD 0
15: PRINT
16: STOP

371
lab4/vm/vm/vm.c Normal file
View File

@@ -0,0 +1,371 @@
#include <stdio.h>
#include <stdlib.h>
#include "vm.h"
void milan_error();
command vm_program[MAX_PROGRAM_SIZE];
int vm_memory[MAX_MEMORY_SIZE];
int vm_stack[MAX_STACK_SIZE];
unsigned int vm_stack_pointer = 0;
unsigned int vm_command_pointer = 0;
opcode_info opcodes_table[] = {
{"NOP", 0},
{"STOP", 0},
{"LOAD", 1},
{"STORE", 1},
{"BLOAD", 1},
{"BSTORE", 1},
{"PUSH", 1},
{"POP", 0},
{"DUP", 0},
{"INVERT", 0},
{"ADD", 0},
{"SUB", 0},
{"MULT", 0},
{"DIV", 0},
{"COMPARE", 1},
{"JUMP", 1},
{"JUMP_YES", 1},
{"JUMP_NO", 1},
{"INPUT", 0},
{"PRINT", 0}
};
int opcodes_table_size = sizeof(opcodes_table) / sizeof(opcode_info);
typedef enum {
BAD_DATA_ADDRESS,
BAD_CODE_ADDRESS,
BAD_RELATION,
STACK_OVERFLOW,
STACK_EMPTY,
DIVISION_BY_ZERO,
BAD_INPUT,
UNKNOWN_COMMAND
} runtime_error;
void vm_init()
{
vm_stack_pointer = 0;
vm_command_pointer = 0;
}
void vm_error(runtime_error error)
{
opcode_info* info;
switch(error) {
case BAD_DATA_ADDRESS:
fprintf(stderr, "Error: illegal data address\n");
break;
case BAD_CODE_ADDRESS:
fprintf(stderr, "Error: illegal address in JUMP* instruction\n");
break;
case BAD_RELATION:
fprintf(stderr, "Error: illegal comparison operator\n");
break;
case STACK_OVERFLOW:
fprintf(stderr, "Error: stack overflow\n");
break;
case STACK_EMPTY:
fprintf(stderr, "Error: stack is empty (no arguments are available)\n");
break;
case DIVISION_BY_ZERO:
fprintf(stderr, "Error: division by zero\n");
break;
case BAD_INPUT:
fprintf(stderr, "Error: illegal input\n");
break;
case UNKNOWN_COMMAND:
fprintf(stderr, "Error: unknown command, unable to execute\n");
break;
default:
fprintf(stderr, "Error: runtime error %d\n", error);
}
fprintf(stderr, "Code:\n\n");
info = operation_info(vm_program[vm_command_pointer].operation);
if(NULL == info) {
fprintf(stderr, "%d\t(%d)\t\t%d\n", vm_command_pointer,
vm_program[vm_command_pointer].operation,
vm_program[vm_command_pointer].arg);
}
else {
if(info->need_arg) {
fprintf(stderr, "\t%d\t%s\t\t%d\n", vm_command_pointer, info->name,
vm_program[vm_command_pointer].arg);
}
else {
fprintf(stderr, "\t%d\t%s\n", vm_command_pointer, info->name);
}
}
milan_error("VM error");
}
int vm_load(unsigned int address)
{
if(address < MAX_MEMORY_SIZE) {
return vm_memory[address];
}
else {
vm_error(BAD_DATA_ADDRESS);
return 0;
}
}
void vm_store(unsigned int address, int word)
{
if(address < MAX_MEMORY_SIZE) {
vm_memory[address] = word;
}
else {
vm_error(BAD_DATA_ADDRESS);
}
}
int vm_read()
{
int n;
fprintf(stderr, "> "); fflush(stdout);
if(scanf("%d", &n)) {
return n;
}
else {
vm_error(BAD_INPUT);
return 0;
}
}
void vm_write(int n)
{
fprintf(stderr, "%d\n", n);
}
int vm_pop()
{
if(vm_stack_pointer > 0) {
return vm_stack[--vm_stack_pointer];
}
else {
vm_error(STACK_EMPTY);
return 0;
}
}
void vm_push(int word)
{
if(vm_stack_pointer < MAX_STACK_SIZE) {
vm_stack[vm_stack_pointer++] = word;
}
else {
vm_error(STACK_OVERFLOW);
}
}
int vm_run_command()
{
unsigned int index = vm_command_pointer;
operation op = vm_program[index].operation;
unsigned int arg = vm_program[index].arg;
int data;
switch(op) {
case NOP:
/* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
break;
case STOP:
return 0;
break;
case LOAD:
vm_push(vm_load(arg));
break;
case STORE:
vm_store(arg, vm_pop());
break;
case BLOAD:
vm_push(vm_load(arg + vm_pop()));
break;
case BSTORE:
data = vm_pop();
vm_store(arg + data, vm_pop());
break;
case PUSH:
vm_push(arg);
break;
case POP:
vm_pop();
break;
case DUP:
data = vm_pop();
vm_push(data);
vm_push(data);
break;
case INVERT:
vm_push(-vm_pop());
break;
case ADD:
data = vm_pop();
vm_push(vm_pop() + data);
break;
case SUB:
data = vm_pop();
vm_push(vm_pop() - data);
break;
case MULT:
data = vm_pop();
vm_push(vm_pop() * data);
break;
case DIV:
data = vm_pop();
if(0 == data) {
vm_error(DIVISION_BY_ZERO);
}
else {
vm_push(vm_pop() / data);
}
break;
case COMPARE:
data = vm_pop();
switch(arg) {
case EQ:
vm_push((vm_pop() == data) ? 1 : 0);
break;
case NE:
vm_push((vm_pop() != data) ? 1 : 0);
break;
case LT:
vm_push((vm_pop() < data) ? 1 : 0);
break;
case GT:
vm_push((vm_pop() > data) ? 1 : 0);
break;
case LE:
vm_push((vm_pop() <= data) ? 1 : 0);
break;
case GE:
vm_push((vm_pop() >= data) ? 1 : 0);
break;
default:
vm_error(BAD_RELATION);
}
break;
case JUMP:
if(arg < MAX_PROGRAM_SIZE) {
vm_command_pointer = arg;
return 1;
}
else {
vm_error(BAD_CODE_ADDRESS);
}
break;
case JUMP_YES:
if(arg < MAX_PROGRAM_SIZE) {
data = vm_pop();
if(data) {
vm_command_pointer = arg;
return 1;
}
}
else {
vm_error(BAD_CODE_ADDRESS);
}
break;
case JUMP_NO:
if(arg < MAX_PROGRAM_SIZE) {
data = vm_pop();
if(!data) {
vm_command_pointer = arg;
return 1;
}
}
else {
vm_error(BAD_CODE_ADDRESS);
}
break;
case INPUT:
vm_push(vm_read());
break;
case PRINT:
vm_write(vm_pop());
break;
default:
vm_error(UNKNOWN_COMMAND);
}
++vm_command_pointer;
return 1;
}
void run()
{
vm_command_pointer = 0;
while(vm_command_pointer < MAX_PROGRAM_SIZE) {
if(!vm_run_command())
break;
}
}
opcode_info* operation_info(operation op)
{
return (op < opcodes_table_size) ? &opcodes_table[op] : NULL;
}
void put_command(unsigned int address, operation op, int arg)
{
if(address < MAX_PROGRAM_SIZE) {
vm_program[address].operation = op;
vm_program[address].arg = arg;
}
else {
milan_error("Illegal address in put_command()");
}
}
void set_mem(unsigned int address, int value)
{
vm_memory[address] = value;
}

88
lab4/vm/vm/vm.h Normal file
View File

@@ -0,0 +1,88 @@
#ifndef _MILAN_VM_H
#define _MILAN_VM_H
#include <stdio.h>
/* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
/* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
#define MAX_PROGRAM_SIZE 65536
/* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
#define MAX_MEMORY_SIZE 65536
/* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> */
#define MAX_STACK_SIZE 8192
/* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
typedef enum {
NOP = 0, /* <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
STOP, /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
LOAD, /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
STORE, /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
BLOAD, /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
BSTORE, /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
PUSH, /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD> */
POP, /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD> */
DUP, /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> */
INVERT, /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> */
ADD, /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
SUB, /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
MULT, /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
DIV, /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
COMPARE, /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
JUMP, /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
JUMP_YES, /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><> 0 */
JUMP_NO, /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> 0 */
INPUT, /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> */
PRINT /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
} operation;
/* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
typedef enum {
EQ, /* = */
NE, /* !- */
LT, /* < */
GT, /* > */
LE, /* <= */
GE /* >= */
} compare_type;
/* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
typedef struct {
operation operation; /* <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
int arg; /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
} command;
/* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
typedef struct opcode_info {
char *name; /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
int need_arg; /* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 1, <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> */
} opcode_info;
/* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD> op.
* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>, <20><> <20><><EFBFBD><EFBFBD><EFBFBD>.
*/
opcode_info* operation_info(operation op);
/* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> address. */
void put_command(unsigned int address, operation op, int arg);
/* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
*
* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 0 <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,
* <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> STOP <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.
*/
void run();
/* <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> value <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> address. */
void set_mem(unsigned int address, int value);
#endif

51
lab4/vm/vm/vmlex.l Normal file
View File

@@ -0,0 +1,51 @@
%{
#include "vmparse.tab.h"
#include <stdlib.h>
#ifndef __GNUC__
#define YY_NO_UNISTD_H
#endif
%}
%option noyywrap
WHITESPACE [ \t]+
EOL \n
INT -?[0-9]+
COMMENT ;[^\n]*\n
%%
{EOL}
{COMMENT}
{WHITESPACE}
{INT} { yylval = atoi(yytext); return T_INT; }
: { return T_COLON; }
SET { return T_SET; }
STOP { return T_STOP; }
LOAD { return T_LOAD; }
STORE { return T_STORE; }
BLOAD { return T_BLOAD; }
BSTORE { return T_BSTORE; }
PUSH { return T_PUSH; }
POP { return T_POP; }
DUP { return T_DUP; }
INVERT { return T_INVERT; }
ADD { return T_ADD; }
SUB { return T_SUB; }
MULT { return T_MULT; }
DIV { return T_DIV; }
COMPARE { return T_COMPARE; }
JUMP { return T_JUMP; }
JUMP_YES { return T_JUMP_YES; }
JUMP_NO { return T_JUMP_NO; }
INPUT { return T_INPUT; }
PRINT { return T_PRINT; }
NOP { return T_NOP; }
<<EOF>> { yyterminate(); }
%%

1736
lab4/vm/vm/vmparse.tab.c Normal file

File diff suppressed because it is too large Load Diff

79
lab4/vm/vm/vmparse.tab.h Normal file
View File

@@ -0,0 +1,79 @@
/* A Bison parser, made by GNU Bison 2.4.1. */
/* Skeleton interface for Bison's Yacc-like parsers in C
Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006
Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* As a special exception, you may create a larger work that contains
part or all of the Bison parser skeleton and distribute that work
under terms of your choice, so long as that work isn't itself a
parser generator using the skeleton or a modified version thereof
as a parser skeleton. Alternatively, if you modify or redistribute
the parser skeleton itself, you may (at your option) remove this
special exception, which will cause the skeleton and the resulting
Bison output files to be licensed under the GNU General Public
License without this special exception.
This special exception was added by the Free Software Foundation in
version 2.2 of Bison. */
/* Tokens. */
#ifndef YYTOKENTYPE
# define YYTOKENTYPE
/* Put the tokens into the symbol table, so that GDB and other debuggers
know about them. */
enum yytokentype {
T_INT = 258,
T_SET = 259,
T_NOP = 260,
T_STOP = 261,
T_LOAD = 262,
T_STORE = 263,
T_BLOAD = 264,
T_BSTORE = 265,
T_PUSH = 266,
T_POP = 267,
T_DUP = 268,
T_INVERT = 269,
T_ADD = 270,
T_SUB = 271,
T_MULT = 272,
T_DIV = 273,
T_COMPARE = 274,
T_JUMP = 275,
T_JUMP_YES = 276,
T_JUMP_NO = 277,
T_INPUT = 278,
T_PRINT = 279,
T_COLON = 280
};
#endif
#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
typedef int YYSTYPE;
# define YYSTYPE_IS_TRIVIAL 1
# define yystype YYSTYPE /* obsolescent; will be withdrawn */
# define YYSTYPE_IS_DECLARED 1
#endif
extern YYSTYPE yylval;

70
lab4/vm/vm/vmparse.y Normal file
View File

@@ -0,0 +1,70 @@
%{
#include "vm.h"
#include <stdio.h>
#include <stdlib.h>
#define YYSTYPE int
int yylex();
void yyerror(char const *);
%}
%token T_INT
%token T_SET
%token T_NOP
%token T_STOP
%token T_LOAD
%token T_STORE
%token T_BLOAD
%token T_BSTORE
%token T_PUSH
%token T_POP
%token T_DUP
%token T_INVERT
%token T_ADD
%token T_SUB
%token T_MULT
%token T_DIV
%token T_COMPARE
%token T_JUMP
%token T_JUMP_YES
%token T_JUMP_NO
%token T_INPUT
%token T_PRINT
%token T_COLON
%%
program : line program
| line
;
line : T_INT T_COLON T_NOP { put_command($1, NOP, 0); }
| T_INT T_COLON T_STOP { put_command($1, STOP, 0); }
| T_INT T_COLON T_LOAD T_INT { put_command($1, LOAD, $4); }
| T_INT T_COLON T_STORE T_INT { put_command($1, STORE, $4); }
| T_INT T_COLON T_BLOAD T_INT { put_command($1, BLOAD, $4); }
| T_INT T_COLON T_BSTORE T_INT { put_command($1, BSTORE, $4); }
| T_INT T_COLON T_PUSH T_INT { put_command($1, PUSH, $4); }
| T_INT T_COLON T_POP { put_command($1, POP, 0); }
| T_INT T_COLON T_DUP { put_command($1, DUP, 0); }
| T_INT T_COLON T_INVERT { put_command($1, INVERT, 0); }
| T_INT T_COLON T_ADD { put_command($1, ADD, 0); }
| T_INT T_COLON T_SUB { put_command($1, SUB, 0); }
| T_INT T_COLON T_MULT { put_command($1, MULT, 0); }
| T_INT T_COLON T_DIV { put_command($1, DIV, 0); }
| T_INT T_COLON T_COMPARE T_INT { put_command($1, COMPARE, $4); }
| T_INT T_COLON T_JUMP T_INT { put_command($1, JUMP, $4); }
| T_INT T_COLON T_JUMP_YES T_INT { put_command($1, JUMP_YES, $4); }
| T_INT T_COLON T_JUMP_NO T_INT { put_command($1, JUMP_NO, $4); }
| T_INT T_COLON T_INPUT { put_command($1, INPUT, 0); }
| T_INT T_COLON T_PRINT { put_command($1, PRINT, 0); }
| T_SET T_INT T_INT { set_mem($2, $3); }
;
%%
void yyerror(char const *str)
{
printf("Error: %s\n", str);
}

BIN
lab4/Варианты.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

8
lab5/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
**/*
!.gitignore
!report.tex
!img
!img/**
!programm
!programm/*.py
!programm/*.txt

BIN
lab5/img/homsky.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

BIN
lab5/img/result1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Some files were not shown because too many files have changed in this diff Show More