Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a95dc6c9d | |||
| 78dddbc047 | |||
| e0211cb6c5 | |||
| d61b1b7a2d | |||
| c001ccb12a | |||
| fdd30a8854 | |||
| 35cf9b5ed4 | |||
| e3918eb442 | |||
| 207b428ad9 | |||
| 09da4c5c62 | |||
| cc5fbb2163 | |||
| 037a4cd8a0 | |||
| b64f7a3205 | |||
| b835502ed4 | |||
| 796ac33829 | |||
| 7520f5ec2f | |||
| 6da094ebbf | |||
| 9cad2ede63 | |||
| 6d4301a5d6 | |||
| 0fb5794fb6 | |||
| fb67acc55e | |||
| 4448c675dd | |||
| 97a78a4ccc | |||
| 5bd140cd39 | |||
| a60d2a9db7 | |||
| 96c9e6db53 | |||
| 681b56b31b | |||
| d7a0e400cd | |||
| 3c09cc0e9b | |||
| 529da8d98e | |||
| c186941dce | |||
| 20782a2473 | |||
| 3963d304ec | |||
| 7030059bbb |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 20 KiB |
@@ -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 ""
|
||||
|
||||
@@ -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(["Лексема", "Тип лексемы", "Значение"])
|
||||
|
||||
@@ -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}.
|
||||
|
||||
|
||||
8
lab2/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
**/*
|
||||
!.gitignore
|
||||
!report.tex
|
||||
!img
|
||||
!img/**
|
||||
!programm
|
||||
!programm/*.py
|
||||
!programm/*.txt
|
||||
BIN
lab2/img/ka.png
Normal file
|
After Width: | Height: | Size: 172 KiB |
BIN
lab2/img/nka.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
lab2/img/result1.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
lab2/img/wrong.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
67
lab2/programm/finite_automaton.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import random
|
||||
|
||||
|
||||
class FiniteAutomaton:
|
||||
def __init__(
|
||||
self,
|
||||
transitions: dict[str, list[tuple[str, str]]],
|
||||
initial_state: str,
|
||||
final_states: set[str],
|
||||
alphabet: set[str],
|
||||
):
|
||||
self.transitions = transitions
|
||||
self.initial_state = initial_state
|
||||
self.final_states = final_states
|
||||
self.alphabet = alphabet
|
||||
|
||||
def _get_next_state(self, current_state: str, char: str) -> str | None:
|
||||
if current_state in self.transitions:
|
||||
for transition, next_state in self.transitions[current_state]:
|
||||
if char in transition:
|
||||
return next_state
|
||||
return None
|
||||
|
||||
def process_input(self, input_string: str) -> tuple[str, list[str]]:
|
||||
current_state = self.initial_state
|
||||
transitions_path = [current_state]
|
||||
|
||||
for char in input_string:
|
||||
if char not in self.alphabet:
|
||||
return f"Символ '{char}' не из алфавита ✗", transitions_path
|
||||
|
||||
next_state = self._get_next_state(current_state, char)
|
||||
|
||||
if next_state is None:
|
||||
return "Строка не соответствует ✗", transitions_path
|
||||
|
||||
transitions_path.append(next_state)
|
||||
current_state = next_state
|
||||
|
||||
return (
|
||||
"Строка соответствует ✓"
|
||||
if current_state in self.final_states
|
||||
else "Строка не соответствует ✗"
|
||||
), transitions_path
|
||||
|
||||
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 (
|
||||
current_state in self.final_states
|
||||
and random.random() < stop_probability
|
||||
):
|
||||
break
|
||||
|
||||
transition, next_state = random.choice(self.transitions[current_state])
|
||||
|
||||
char = random.choice(transition)
|
||||
result.append(char)
|
||||
current_state = next_state
|
||||
path.append(current_state)
|
||||
|
||||
return "".join(result), path
|
||||
85
lab2/programm/main.py
Normal file
@@ -0,0 +1,85 @@
|
||||
from finite_automaton import FiniteAutomaton
|
||||
|
||||
|
||||
def main():
|
||||
alphabet = set("+-0123456789.eE")
|
||||
initial_state = "S0"
|
||||
final_states = {"S2", "S3", "S5", "S7", "S10"}
|
||||
|
||||
transitions = {
|
||||
"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(
|
||||
transitions=transitions,
|
||||
initial_state=initial_state,
|
||||
final_states=final_states,
|
||||
alphabet=alphabet,
|
||||
)
|
||||
|
||||
print("Конечный автомат для распознавания форматов вещественных чисел")
|
||||
print("=" * 60)
|
||||
print("Варианты команд:")
|
||||
print(" - check <строка> - проверить, соответствует ли строка автомату")
|
||||
print(
|
||||
" - gen [<вероятность_остановки>] - сгенерировать случайную строку (по умолчанию 0.3)"
|
||||
)
|
||||
print(" - q - выход из программы")
|
||||
print("=" * 60)
|
||||
|
||||
while True:
|
||||
command = input("\nВведите команду: ").strip()
|
||||
|
||||
if not command:
|
||||
continue
|
||||
|
||||
parts = command.split()
|
||||
cmd = parts[0].lower()
|
||||
|
||||
if cmd == "q":
|
||||
print("Выход из программы.")
|
||||
break
|
||||
|
||||
elif cmd == "check":
|
||||
input_string = ""
|
||||
if len(parts) > 1:
|
||||
input_string = " ".join(parts[1:]).strip()
|
||||
|
||||
message, transitions = automaton.process_input(input_string)
|
||||
|
||||
print(f"Результат: {message}")
|
||||
print("Путь переходов:", " -> ".join(transitions))
|
||||
|
||||
elif cmd == "gen":
|
||||
stop_prob = 0.3
|
||||
if len(parts) > 1:
|
||||
try:
|
||||
stop_prob = float(parts[1])
|
||||
if not (0 < stop_prob <= 1):
|
||||
raise ValueError(
|
||||
"Вероятность должна быть больше 0 и меньше либо равна 1"
|
||||
)
|
||||
except ValueError as e:
|
||||
print(f"Ошибка: {e}")
|
||||
continue
|
||||
|
||||
random_string, path = automaton.generate_random_string(stop_prob)
|
||||
print(f"Сгенерированная строка: {random_string}")
|
||||
print("Путь переходов:", " -> ".join(path))
|
||||
|
||||
else:
|
||||
print(f"Неизвестная команда: {cmd}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
688
lab2/report.tex
Normal file
@@ -0,0 +1,688 @@
|
||||
\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[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{Лабораторная работа №2}\\
|
||||
\large{<<Конечный автомат и регулярное выражение для распознавания форматов вещественных чисел>>}\\
|
||||
\large{по дисциплине}\\
|
||||
\large{<<Математическая логика и теория автоматов>>}\\
|
||||
\large{Вариант 15}\\
|
||||
|
||||
% \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}{Введение}
|
||||
Лабораторная №2 по дисциплине <<Математическая логика>> заключается в следующем. Необходимо построить регулярное выражение для заданного варианта, затем создать недетерминированный конечный автомат и детерминировать его. Реализовать программу, которая проверяет введённый текст через реализацию конечного автомата, с вариантами вывода: строка соответствует, не соответствует, символы не из алфавита. Также необходимо реализовать функцию случайной генерации верной строки по полученному конечному автомату.
|
||||
|
||||
\textit{Вариант 15}. Соответствие вещественного числа разным форматам представления.
|
||||
|
||||
В данной лабораторной работе рассматриваются следующие форматы представления вещественных чисел.
|
||||
\begin{itemize}
|
||||
\item Целые числа, например: \texttt{''123''}, \texttt{''-456''}, \texttt{''0''}, в т. ч. \texttt{''+0''}, \texttt{''-0''}.
|
||||
\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 ::= [sign] (integer | float | exponentnumber)
|
||||
sign ::= "+" | "-"
|
||||
|
||||
integer ::= nonzerodigit {digit} | "0" {"0"}
|
||||
nonzerodigit ::= "1" | "2" | ... | "9"
|
||||
digit ::= "0" | nonzerodigit
|
||||
|
||||
float ::= {digit} "." digitpart | digitpart "."
|
||||
digitpart ::= digit {digit}
|
||||
|
||||
exponentnumber ::= (digitpart | float) exponent
|
||||
exponent ::= ("e" | "E") [sign] digitpart
|
||||
\end{verbatim}
|
||||
Где:
|
||||
\begin{itemize}
|
||||
\item \lbrack R\rbrack -- необязательный элемент (0 или 1 раз)
|
||||
\item \{R\} -- повторение элемента (0 или более раз)
|
||||
\item P|Q -- альтернатива (либо P, либо Q)
|
||||
\end{itemize}
|
||||
|
||||
|
||||
\newpage
|
||||
\section {Математическое описание}
|
||||
\subsection{Языки и грамматики}
|
||||
Языком над конечным словарем $\Sigma$ называется произвольное множество конечных цепочек над этим словарем.
|
||||
|
||||
\begin{itemize}
|
||||
\item Цепочки языка называются словами (предложениями).
|
||||
\item Над конечным непустым словарем можно определить бесконечное
|
||||
количество слов конечной длины (счетное множество).
|
||||
\item Над конечным
|
||||
непустым словарем можно определить бесконечное количество языков, т.е.
|
||||
подмножество множества всех возможных слов
|
||||
(континуум, как число подмножеств счетного множества).
|
||||
\end{itemize}
|
||||
|
||||
Языки могут быть конечными и бесконечными (содержать бесконечное число цепочек). Словарь всегда конечен.
|
||||
|
||||
Формальная грамматика – способ описания того, какие предложения возможны в языке. Существует два вида грамматик:
|
||||
\begin{itemize}
|
||||
\item порождающие грамматики – правила, позволяющие
|
||||
строить любое предложение языка,
|
||||
\item распознающие грамматики (алгоритмы) - позволяют
|
||||
определить, принадлежит ли данное предложение языку.
|
||||
\end{itemize}
|
||||
|
||||
Распознающая грамматика – это конечный набор
|
||||
правил, алгоритм, который по введенной цепочке
|
||||
определяет, принадлежит цепочка языку, или нет.
|
||||
|
||||
\subsection{Регулярные множества}
|
||||
Регулярные множества, как множества цепочек, построенные над конечным словарем (по определенным правилам) – это языки. Их называют регулярными языками.
|
||||
|
||||
Правила построения регулярных множеств:
|
||||
\begin{itemize}
|
||||
\item Объединение двух регулярных множеств $L_1$ и $L_2$ обозначается $L_1 \cup L_2$ и состоит из цепочек, которые принадлежат хотя бы одному из множеств $L_1$ или $L_2$.
|
||||
$$L_1 \cup L_2 = \{ \alpha \mid \alpha \in L_1 \text{ или } \alpha \in L_2 \}$$
|
||||
|
||||
\item Конкатенация (произведение) двух регулярных множеств $L_1$ и $L_2$ обозначается $L_1 \cdot L_2$ и состоит из цепочек, которые можно разбить на две части, одна из которых принадлежит $L_1$, а другая $L_2$.
|
||||
$$L_1 \cdot L_2 = \{ \alpha \beta \mid \alpha \in L_1 \text{ и } \beta \in L_2 \}$$
|
||||
|
||||
Обозначим $L^0 = \{ \varepsilon \}$, $L^1 = L$, $L^2 = L \cdot L$, $L^{k + 1} = L^k \cdot L$, и так далее. Обозначение $\varepsilon$ обозначает пустую цепочку.
|
||||
|
||||
\item Итерация регулярного множества $L$ обозначается $L^*$ и состоит из цепочек, которые можно разбить на произвольное количество повторений множества $L$.
|
||||
$$L^* = \{ \varepsilon \} \cup L \cup L^2 \cup L^3 \cup \ldots$$
|
||||
\end{itemize}
|
||||
|
||||
\subsection{Регулярные выражения}
|
||||
Регулярные выражения -- формальный язык шаблонов для поиска и выполнения манипуляций с подстроками в тексте. Регулярное выражение -- это формула (pattern, шаблон), задающая правило поиска подстрок в потоке символов.
|
||||
|
||||
Регулярное выражение показывает, как можно построить регулярное множество цепочек из одноэлементных множеств с использованием трех операций: конкатенации, объединения и итерации.
|
||||
|
||||
Примеры регулярных выражений:
|
||||
\begin{itemize}
|
||||
\item $ab + ba^*$ -- представляет регулярное множество:
|
||||
$$\{ a\} \{b \} \cup \{ b \} \{ a \}^*$$
|
||||
\item $(ac)^*b+c^*$ -- представляет регулярное множество:
|
||||
$$\{b, acb, acacb, acacacb, \ldots, \varepsilon, c, cc, ccc, \ldots \}$$
|
||||
\end{itemize}
|
||||
|
||||
В реальных программах и языках программирования используется расширенный синтаксис регулярных выражений, который добавляет множество удобных конструкций. Среди дополнительных возможностей: классы символов (например, \verb|[a-z0-9]|), квантификаторы (\verb|?|, \verb|+|, \verb|{n,m}|), группировка с помощью скобок, обратные ссылки, опережающие и ретроспективные проверки. Эти расширения делают регулярные выражения более компактными и удобными для использования, но не меняют их выразительную мощность с теоретической точки зрения.
|
||||
|
||||
Если регулярные множества это языки, то регулярные выражения -- это распознающие грамматики этих языков.
|
||||
|
||||
\subsection{Регулярные выражения для заданного варианта}
|
||||
|
||||
Для разных форматов представления вещественных чисел были построены следующие регулярные выражения в соответствии с определённой БНФ нотацией:
|
||||
\begin{itemize}
|
||||
\item Целые числа: \\
|
||||
\texttt{[+-]?(0+|[1-9][0-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]+}
|
||||
\end{itemize}
|
||||
|
||||
Объединяя, получаем следующее регулярное выражение, которое распознает все форматы вещественных чисел в соответствии с БНФ нотацией:
|
||||
\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{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}
|
||||
|
||||
Таким образом, полученное регулярное выражение распознаёт формат представления вещественных чисел, рассматриваемый в данной работе, и полностью соответствует формальному определению, представленному в БНФ нотации.
|
||||
|
||||
\subsection{Конечный автомат-распознаватель}
|
||||
Конечный автомат-распознаватель – это математическая модель, которая используется для распознавания цепочек символов в соответствии с заданным формальным языком.
|
||||
|
||||
Конечный автомат-распознаватель $A = (S, \Sigma, s_0, \delta, F)$, где:
|
||||
\begin{itemize}
|
||||
\item $S$ – конечное множество состояний
|
||||
\item $\Sigma$ – конечное множество входных символов
|
||||
\item $s_0 \in S$ – начальное состояние
|
||||
\item $\delta: S \times \Sigma \rightarrow S$ – функция переходов
|
||||
\item $F \subseteq S$ – множество финальных (допускающих) состояний
|
||||
\end{itemize}
|
||||
|
||||
Автомат $A$ допускает (распознает) цепочку, если эта цепочка переводит $A$ из начального в одно из финальных состояний. Автомат $A$ допускает язык $L$, если он допускает все цепочки этого языка – и только их.
|
||||
|
||||
Конечный автомат-распознаватель является распознающей грамматикой.
|
||||
|
||||
\subsection{Недетерминированный КА-распознаватель}
|
||||
Недетерминизм - очень удобное свойство формальной
|
||||
модели, его можно определенным образом трактовать,
|
||||
даже и не реализовывая, ограничиваясь только
|
||||
формальными аналитическими преобразованиями.
|
||||
|
||||
В недетерминированном конечном автомате-распознавателе могут быть следующие неоднозначности:
|
||||
\begin{itemize}
|
||||
\item несколько начальных состояний,
|
||||
\item несколько переходов, помеченных одним и тем
|
||||
же символом,
|
||||
\item переходы, помеченные пустым символом $\varepsilon$.
|
||||
\end{itemize}
|
||||
|
||||
Цепочка допускается конечным автоматом, если существует путь, по которому эта цепочка переводит автомат из какого-нибудь начального состояния в какое-нибудь финальное состояние.
|
||||
|
||||
Недетерминированный конечный автомат-распознаватель $A = (S, \Sigma, S_0, \delta, F)$, где:
|
||||
\begin{itemize}
|
||||
\item $S$ – конечное множество состояний
|
||||
\item $\Sigma$ – конечное множество входов
|
||||
\item $S_0 \subseteq S$ – множество начальных состояний
|
||||
\item $\delta: S \times \Sigma \rightarrow 2^S$ – функция переходов
|
||||
\item $F \subseteq S$ – множество финальных состояний
|
||||
\end{itemize}
|
||||
|
||||
\subsection{Теорема Клини}
|
||||
\textbf{Теорема Клини}. Классы регулярных множеств и автоматных языков совпадают. Это значит, что:
|
||||
\begin{itemize}
|
||||
\item Любой язык, распознаваемый конечным автоматом, может быть задан регулярным выражением.
|
||||
\item Для любого регулярного выражения существует конечный автомат, распознающий соответствующий язык.
|
||||
\end{itemize}
|
||||
|
||||
\subsection{Недетерминированный КА-распознаватель для заданного варианта}
|
||||
|
||||
На Рис.~\ref{fig:nka} представлен недетерминированный КА-распознаватель, соответствующий регулярному выражению для заданного варианта.
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=1\linewidth]{img/nka.png}
|
||||
\caption{Недетерминированный КА-распознаватель для заданного варианта.}
|
||||
\label{fig:nka}
|
||||
\end{figure}
|
||||
|
||||
Матрица переходов для данного автомата представлена в Таблице~\ref{tab:nka}.
|
||||
|
||||
\begin{table}[h!]
|
||||
\centering
|
||||
\caption{Таблица переходов для недетерминированного КА-распознавателя.}
|
||||
\footnotesize
|
||||
\begin{tabularx}{\textwidth}{|c|X|X|X|X|X|}
|
||||
\hline
|
||||
\textbf{Состояние\textbackslash Вход} & \textbf{+-} & \textbf{0} & \textbf{1-9} & \textbf{.} & \textbf{eE} \\
|
||||
\hline
|
||||
$S_0$ & $S_1$ & $S_3$ & $S_2$ & $S_6$ & -- \\
|
||||
\hline
|
||||
$S_1$ & -- & $S_3$ & $S_2$ & $S_6$ & -- \\
|
||||
\hline
|
||||
$S_2$ & -- & $S_2$ & $S_2$ & $S_5$ & $S_8$ \\
|
||||
\hline
|
||||
$S_3$ & -- & $S_3$ & $S_4$ & $S_5$ & $S_8$ \\
|
||||
\hline
|
||||
$S_4$ & -- & $S_4$ & $S_4$ & $S_5$ & $S_8$ \\
|
||||
\hline
|
||||
$S_5$ & -- & $S_5$ & $S_5$ & -- & $S_8$ \\
|
||||
\hline
|
||||
$S_6$ & -- & $S_7$ & $S_7$ & -- & -- \\
|
||||
\hline
|
||||
$S_7$ & -- & $S_7$ & $S_7$ & -- & $S_8$ \\
|
||||
\hline
|
||||
$S_8$ & $S_9$ & $S_{10}$ & $S_{10}$ & -- & -- \\
|
||||
\hline
|
||||
$S_9$ & -- & $S_{10}$ & $S_{10}$ & -- & -- \\
|
||||
\hline
|
||||
$S_{10}$ & -- & $S_{10}$ & $S_{10}$ & -- & -- \\
|
||||
\hline
|
||||
\end{tabularx}
|
||||
\label{tab:nka}
|
||||
\end{table}
|
||||
|
||||
\subsection{Детерминированный КА-распознаватель для заданного варианта}
|
||||
|
||||
Для того, чтобы преобразовать недетерминированный КА-распознаватель (Рис.~\ref{fig:nka}) в детерминированный, достаточно добавить ещё одно состояние $S_E$, соответствующее недопустимой цепочке символов, и переходы в него из всех остальных состояний.
|
||||
|
||||
На Рис.~\ref{fig:ka} представлен детерминированный КА-распознаватель. Символом \textit{C} (от англ. Complement -- дополнение) обозначены переходы, соответствующие любым символам, кроме тех, по которым уже есть переходы в другие состояния. Символом \textit{A} (от англ. Any -- любой) обозначен переход, соответствующий любому символу.
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=1\linewidth]{img/ka.png}
|
||||
\caption{Детерминированный КА-распознаватель для заданного варианта.}
|
||||
\label{fig:ka}
|
||||
\end{figure}
|
||||
|
||||
|
||||
\begin{table}[h!]
|
||||
\centering
|
||||
\caption{Таблица переходов для детерминированного КА-распознавателя.}
|
||||
\footnotesize
|
||||
\begin{tabularx}{\textwidth}{|c|X|X|X|X|X|}
|
||||
\hline
|
||||
\textbf{Состояние\textbackslash Вход} & \textbf{+-} & \textbf{0} & \textbf{1-9} & \textbf{.} & \textbf{eE} \\
|
||||
\hline
|
||||
$S_0$ & $S_1$ & $S_3$ & $S_2$ & $S_6$ & $S_E$ \\
|
||||
\hline
|
||||
$S_1$ & $S_E$ & $S_3$ & $S_2$ & $S_6$ & $S_E$ \\
|
||||
\hline
|
||||
$S_2$ & $S_E$ & $S_2$ & $S_2$ & $S_5$ & $S_8$ \\
|
||||
\hline
|
||||
$S_3$ & $S_E$ & $S_3$ & $S_4$ & $S_5$ & $S_8$ \\
|
||||
\hline
|
||||
$S_4$ & $S_E$ & $S_4$ & $S_4$ & $S_5$ & $S_8$ \\
|
||||
\hline
|
||||
$S_5$ & $S_E$ & $S_5$ & $S_5$ & $S_E$ & $S_8$ \\
|
||||
\hline
|
||||
$S_6$ & $S_E$ & $S_7$ & $S_7$ & $S_E$ & $S_E$ \\
|
||||
\hline
|
||||
$S_7$ & $S_E$ & $S_7$ & $S_7$ & $S_E$ & $S_8$ \\
|
||||
\hline
|
||||
$S_8$ & $S_9$ & $S_{10}$ & $S_{10}$ & $S_E$ & $S_E$ \\
|
||||
\hline
|
||||
$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}
|
||||
\end{table}
|
||||
|
||||
|
||||
\newpage
|
||||
Матрица переходов для данного автомата представлена в Таблице~\ref{tab:ka}.
|
||||
|
||||
|
||||
\newpage
|
||||
\section{Особенности реализации}
|
||||
\subsection{Общая структура программы}
|
||||
Программа состоит из двух файлов:
|
||||
\begin{itemize}
|
||||
\item \texttt{finite\_automaton.py} -- содержит класс \texttt{FiniteAutomaton} для создания конечных автоматов.
|
||||
\item \texttt{main.py} -- содержит определение конечного автомата для распознавания вещественных чисел, а также функцию \texttt{main}, которая реализует интерактивный интерфейс для проверки и генерации строк.
|
||||
\end{itemize}
|
||||
|
||||
\subsection{Класс \texttt{FiniteAutomaton}}
|
||||
|
||||
Класс \texttt{FiniteAutomaton} это класс для представления конечных автоматов. Код конструктора класса представлен в листинге~\ref{lst:FiniteAutomaton}.
|
||||
|
||||
В классе определены четыре поля.
|
||||
\begin{itemize}
|
||||
\item \texttt{transitions} -- \texttt{dict[str, list[tuple[str, str]]]} -- словарь, определяющий переходы между состояниями. Ключами словаря являются состояния, значениями -- списки кортежей вида \texttt{(transition, next\_state)}, определяющие переходы из данного состояния. \texttt{transition} это строка, содержащая символы, по которым можно перейти из данного состояния в следующее состояние -- \texttt{next\_state}.
|
||||
\item \texttt{initial\_state} -- \texttt{str} -- начальное состояние.
|
||||
\item \texttt{final\_states} -- \texttt{set[str]} -- множество финальных состояний.
|
||||
\item \texttt{alphabet} -- \texttt{set[str]} -- множество символов, которые может содержать входная строка.
|
||||
\end{itemize}
|
||||
|
||||
\begin{lstlisting}[caption={Код конструктора класса FiniteAutomaton.}, label={lst:FiniteAutomaton}]
|
||||
class FiniteAutomaton:
|
||||
def __init__(
|
||||
self,
|
||||
transitions: dict[str, list[tuple[str, str]]],
|
||||
initial_state: str,
|
||||
final_states: set[str],
|
||||
alphabet: set[str],
|
||||
):
|
||||
self.transitions = transitions
|
||||
self.initial_state = initial_state
|
||||
self.final_states = final_states
|
||||
self.alphabet = alphabet
|
||||
\end{lstlisting}
|
||||
|
||||
Класс \texttt{FiniteAutomaton} содержит три метода: \texttt{\_get\_next\_state}, \texttt{process\_input} и \texttt{generate\_random\_string}.
|
||||
|
||||
\subsection{Метод \texttt{\_get\_next\_state}}
|
||||
Приватный метод \texttt{\_get\_next\_state} принимает три параметра: \texttt{self} -- ссылку на объект класса, \texttt{current\_state} (\texttt{str}) -- текущее состояние, и \texttt{char} (\texttt{str}) -- символ, по которому нужно перейти из текущего состояния. Возвращает следующее состояние (\texttt{str}), либо \texttt{None}, если переход невозможен.
|
||||
|
||||
\begin{lstlisting}[caption={Код метода \texttt{\_get\_next\_state}.}, label={lst:get_next_state}]
|
||||
def _get_next_state(self, current_state: str, char: str) -> str | None:
|
||||
if current_state in self.transitions:
|
||||
for transition, next_state in self.transitions[current_state]:
|
||||
if char in transition:
|
||||
return next_state
|
||||
return None
|
||||
\end{lstlisting}
|
||||
|
||||
|
||||
\subsection{Метод \texttt{process\_input}}
|
||||
Метод \texttt{process\_input} принимает два параметра: \texttt{self} -- ссылку на объект класса, и входную строку \texttt{input\_string}. Возвращает кортеж из двух элементов: строку с сообщением о результате проверки, и список пройденных состояний.
|
||||
|
||||
\begin{lstlisting}[caption={Код метода \texttt{process\_input}.}, label={lst:process_input}]
|
||||
def process_input(self, input_string: str) -> tuple[str, list[str]]:
|
||||
current_state = self.initial_state
|
||||
transitions_path = [current_state]
|
||||
|
||||
for char in input_string:
|
||||
if char not in self.alphabet:
|
||||
return f"Символ '{char}' не из алфавита", transitions_path
|
||||
|
||||
next_state = self._get_next_state(current_state, char)
|
||||
|
||||
if next_state is None:
|
||||
return "Строка не соответствует", transitions_path
|
||||
|
||||
transitions_path.append(next_state)
|
||||
current_state = next_state
|
||||
|
||||
return (
|
||||
"Строка соответствует"
|
||||
if current_state in self.final_states
|
||||
else "Строка не соответствует"
|
||||
), transitions_path
|
||||
\end{lstlisting}
|
||||
|
||||
|
||||
\subsection{Метод \texttt{generate\_random\_string}}
|
||||
Метод \texttt{generate\_random\_string} принимает два параметра: \texttt{self} -- ссылку на объект класса, и вероятность остановки в финальном состоянии -- \texttt{stop\_probability} (\texttt{float}). Возвращает сгенерированную строку.
|
||||
|
||||
Шаг алгоритма заключается в следующем:
|
||||
\begin{enumerate}
|
||||
\item Проверяем условие остановки: если текущее состояние является финальным и случайное число оказывается меньше заданной вероятности остановки, то генерация завершается.
|
||||
\item Случайным образом выбираем переход из текущего состояния в следующее.
|
||||
\item Случайным образом выбираем символ из множества символов выбранного перехода.
|
||||
\item Добавляем выбранный символ в результат и переходим в следующее состояние.
|
||||
\end{enumerate}
|
||||
|
||||
\begin{lstlisting}[caption={Код метода \texttt{generate\_random\_string}.}, label={lst:generate_random_string}]
|
||||
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 (
|
||||
current_state in self.final_states
|
||||
and random.random() < stop_probability
|
||||
):
|
||||
break
|
||||
|
||||
transition, next_state = random.choice(self.transitions[current_state])
|
||||
|
||||
char = random.choice(transition)
|
||||
result.append(char)
|
||||
current_state = next_state
|
||||
path.append(current_state)
|
||||
|
||||
return "".join(result), path
|
||||
\end{lstlisting}
|
||||
|
||||
|
||||
|
||||
\subsection{Функция \texttt{main}}
|
||||
В функции \texttt{main} (листинг~\ref{lst:Main}) заданы параметры конечного автомата и реализован интерактивный интерфейс пользователя. Функция не принимает параметров и ничего не возвращает.
|
||||
|
||||
Пользователю доступны следующие команды:
|
||||
|
||||
\begin{itemize}
|
||||
\item \texttt{check <строка>} -- проверяет, соответствует ли строка автомату.
|
||||
\item \texttt{gen [<вероятность\_остановки>]} -- сгенерировать случайную строку.
|
||||
\item \texttt{q} -- выход из программы.
|
||||
\end{itemize}
|
||||
|
||||
\begin{lstlisting}[caption={Функция main.}, label={lst:Main}]
|
||||
def main():
|
||||
alphabet = set("+-0123456789.eE")
|
||||
initial_state = "S0"
|
||||
final_states = {"S2", "S3", "S5", "S7", "S10"}
|
||||
|
||||
transitions = {
|
||||
"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(
|
||||
transitions=transitions,
|
||||
initial_state=initial_state,
|
||||
final_states=final_states,
|
||||
alphabet=alphabet,
|
||||
)
|
||||
|
||||
print("Конечный автомат для распознавания форматов вещественных чисел")
|
||||
print("=" * 60)
|
||||
print("Варианты команд:")
|
||||
print(" - check <строка> - проверить, соответствует ли строка автомату")
|
||||
print(
|
||||
" - gen [<вероятность_остановки>] - сгенерировать случайную строку (по умолчанию 0.3)"
|
||||
)
|
||||
print(" - q - выход из программы")
|
||||
print("=" * 60)
|
||||
|
||||
while True:
|
||||
command = input("\nВведите команду: ").strip()
|
||||
|
||||
if not command:
|
||||
continue
|
||||
|
||||
parts = command.split()
|
||||
cmd = parts[0].lower()
|
||||
|
||||
if cmd == "q":
|
||||
print("Выход из программы.")
|
||||
break
|
||||
|
||||
elif cmd == "check":
|
||||
input_string = ""
|
||||
if len(parts) > 1:
|
||||
input_string = " ".join(parts[1:]).strip()
|
||||
|
||||
message, transitions = automaton.process_input(input_string)
|
||||
|
||||
print(f"Результат: {message}")
|
||||
print("Путь переходов:", " -> ".join(transitions))
|
||||
|
||||
elif cmd == "gen":
|
||||
stop_prob = 0.3
|
||||
if len(parts) > 1:
|
||||
try:
|
||||
stop_prob = float(parts[1])
|
||||
if not (0 < stop_prob <= 1):
|
||||
raise ValueError(
|
||||
"Вероятность должна быть больше 0 и меньше либо равна 1"
|
||||
)
|
||||
except ValueError as e:
|
||||
print(f"Ошибка: {e}")
|
||||
continue
|
||||
|
||||
random_string, path = automaton.generate_random_string(stop_prob)
|
||||
print(f"Сгенерированная строка: {random_string}")
|
||||
print("Путь переходов:", " -> ".join(path))
|
||||
|
||||
else:
|
||||
print(f"Неизвестная команда: {cmd}")
|
||||
\end{lstlisting}
|
||||
|
||||
\newpage
|
||||
\section{Результаты работы программы}
|
||||
Результаты работы программы представлены на Рис.~\ref{fig:result1}.
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=1\linewidth]{img/result1.png}
|
||||
\caption{Результаты работы программы.}
|
||||
\label{fig:result1}
|
||||
\end{figure}
|
||||
|
||||
\newpage
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.8\linewidth]{img/wrong.png}
|
||||
\caption{Реакция программы на некорректный пользовательский ввод.}
|
||||
\label{fig:wrong}
|
||||
\end{figure}
|
||||
|
||||
На Рис.~\ref{fig:wrong} представлена реакция программы на некорректный пользовательский ввод.
|
||||
|
||||
|
||||
\newpage
|
||||
\section*{Заключение}
|
||||
\addcontentsline{toc}{section}{Заключение}
|
||||
В ходе выполнения лабораторной работы было построено регулярное выражение для распознавания различных форматов вещественных чисел. В соответствии с теоремой Клини по заданному регулярному выражению, задающему регулярный
|
||||
язык, был построен недетерминированный конечный автомат-распознаватель. Затем полученный конечный автомат был детерминирован. На основе разработанного автомата была реализована программа, которая проверяет соответствие входной строки заданному формату и генерирует случайные корректные строки.
|
||||
|
||||
Из достоинств выполнения лабораторной работы можно выделить структурирование кода за счёт использования ООП. Вся логика работы с конечными автоматами вынесена в отдельный класс \texttt{FiniteAutomaton} с четко разделенными методами для проверки строк и генерации случайных строк. Создана удобная интерактивная консольная оболочка для взаимодействия с пользователем, позволяющая выполнять различные команды.
|
||||
|
||||
К недостаткам текущей реализации можно отнести следующие аспекты. Во-первых, переходы в автомате представлены в виде строк, содержащих допустимые символы, такой способ представления переходов не является самым оптимальным с точки зрения производительности. Во-вторых, в реализации генерации случайных строк вероятность остановки одинакова для всех финальных состояний, что может приводить к неравномерному распределению различных форматов чисел в генерируемых строках.
|
||||
|
||||
Функционал программы несложно масштабировать. Класс \texttt{FiniteAutomaton} может быть использован для работы с различными конечными автоматами-распознавателями. Для изменения распознаваемого языка достаточно задать новые параметры для автомата, не меняя базовую логику программы. Однако, текущая реализация работает только с символьными переходами, поэтому задать строчные переходы в виде, например, регулярных выражений не представляется возможным. Однако подобный функционал также несложно реализовать, взяв за основу существующий код.
|
||||
|
||||
На выполнение лабораторной работы ушло около 10 часов. Работа была выполнена в среде разработки Visual Studio Code. Программа написана на Python версии 3.10.
|
||||
|
||||
\newpage
|
||||
\section*{Список литературы}
|
||||
\addcontentsline{toc}{section}{Список литературы}
|
||||
|
||||
\vspace{-1.5cm}
|
||||
\begin{thebibliography}{0}
|
||||
\bibitem{vostrov}
|
||||
Востров, А.В. Курс лекций по дисциплине <<Математическая логика>>. URL \url{https://tema.spbstu.ru/compiler/} (дата обращения 01.04.2025 г.)
|
||||
\bibitem{lutz}
|
||||
Лутц, М. Изучаем Python. 5-е изд. / М. Лутц. — СПб.: Питер, 2019. — 1216 с.
|
||||
\bibitem{friedl}
|
||||
Фридл, Дж. Регулярные выражения = Mastering Regular Expressions / Дж. Фридл. — СПб.: Питер, 2001. — 352 с. — (Библиотека программиста).
|
||||
\end{thebibliography}
|
||||
|
||||
\end{document}
|
||||
8
lab3/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
**/*
|
||||
!.gitignore
|
||||
!report.tex
|
||||
!img
|
||||
!img/**
|
||||
!programm
|
||||
!programm/*.py
|
||||
!programm/*.txt
|
||||
BIN
lab3/img/result1.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
lab3/img/wrong.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
3
lab3/programm/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
grammar_*
|
||||
analysis_*
|
||||
generation_*
|
||||
617
lab3/programm/analyze-ll-one.py
Normal 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
@@ -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
@@ -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
@@ -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()
|
||||
2
lab3/programm/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
prettytable==3.16.0
|
||||
wcwidth==0.2.13
|
||||
1097
lab3/report.tex
Normal file
5
lab4/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
*.zip
|
||||
*.cmil
|
||||
*.o
|
||||
cmilan/src/cmilan.exe
|
||||
!cmilan/doc/cmilan.pdf
|
||||
21
lab4/README.md
Normal 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
@@ -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
956
lab4/cmilan/doc/cmilan.tex
Normal file
@@ -0,0 +1,956 @@
|
||||
\documentclass[a4paper,12pt]{article}
|
||||
\usepackage{ucs}
|
||||
\usepackage{cmap}
|
||||
\usepackage[utf8x]{inputenc}
|
||||
\usepackage[T2A]{fontenc}
|
||||
\usepackage[english,russian]{babel}
|
||||
\usepackage[hmargin=2cm,vmargin=2cm]{geometry}
|
||||
\usepackage{indentfirst}
|
||||
\usepackage{listings}
|
||||
\usepackage{syntax}
|
||||
\usepackage[section]{placeins}
|
||||
|
||||
\setlength{\grammarparsep}{10pt plus 1pt minus 1pt}
|
||||
\setlength{\grammarindent}{9em}
|
||||
|
||||
\lstset{
|
||||
frame=TB,
|
||||
morekeywords={begin,end,if,then,else,fi,while,do,od,write,read}
|
||||
}
|
||||
|
||||
\title{Компилятор \textsc{CMilan}}
|
||||
\author{Э. Ф. Аллахвердиев, Д. А. Тимофеев}
|
||||
|
||||
\begin{document}
|
||||
\maketitle
|
||||
\tableofcontents
|
||||
|
||||
\section{Обзор языка Милан}
|
||||
|
||||
Язык Милан --- учебный язык программирования, описанный в
|
||||
учебнике~\cite{karpov05}.
|
||||
|
||||
Программа на Милане представляет собой последовательность операторов,
|
||||
заключенных между ключевыми словами \texttt{begin} и \texttt{end}. Операторы
|
||||
отделяются друг от друга точкой с запятой. После последнего оператора в блоке
|
||||
точка с запятой не ставится. Компилятор \textsc{CMilan} не учитывает регистр
|
||||
символов в именах переменных и ключевых словах.
|
||||
|
||||
В базовую версию языка Милан входят следующие конструкции: константы,
|
||||
идентификаторы, арифметические операции над целыми числами, операторы чтения
|
||||
чисел со стандартного ввода и печати чисел на стандартный вывод, оператор
|
||||
присваивания, условный оператор, оператор цикла с предусловием.
|
||||
|
||||
Программа может содержать комментарии, которые могут быть многострочными.
|
||||
Комментарий начинается символами `\texttt{/*}' и заканчивается символами
|
||||
`\texttt{*/}'. Вложенные комментарии не допускаются.
|
||||
|
||||
Грамматика языка Милан в расширенной форме Бэкуса-Наура приведена на
|
||||
рисунке~\ref{milan-grammar}.
|
||||
|
||||
\begin{figure}
|
||||
\begin{grammar}
|
||||
|
||||
<program> ::= `begin' <statementList> `end'
|
||||
|
||||
<statementList> ::= <statement> `;' <statementList>
|
||||
\alt $\epsilon$
|
||||
|
||||
<statement> ::= <ident> `:=' <expression>
|
||||
\alt `if' <relation> `then' <statementList> [`else' <statementList>] `fi'
|
||||
\alt `while' <relation> `do' <statementList> `od'
|
||||
\alt `write' `(' <expression> `)'
|
||||
|
||||
<expression> ::= <term> \{<addop> <term>\}
|
||||
|
||||
<term> ::= <factor> \{<mulop> <factor>\}
|
||||
|
||||
<factor> ::= <ident> | <number> | `(' <expression> `)'
|
||||
|
||||
<relation> ::= <expression> <cmp> <expression>
|
||||
|
||||
<addop> ::= `+' | `-'
|
||||
|
||||
<multop> ::= `*' | `/'
|
||||
|
||||
<cmp> ::= `=' | `!=' | `<' | `<=' | `>' | `>='
|
||||
|
||||
<ident> ::= <letter> \{<letter> | <digit>\}
|
||||
|
||||
<letter> ::= `a' | `b' | `c' | \ldots | `z' | `A' | `B' | `C' | \ldots | `Z'
|
||||
|
||||
<digit> ::= `0' | `1' | `2' | `3' | `4' | `5' | `6' | `7' | `8' | `9'
|
||||
|
||||
\end{grammar}
|
||||
\label{milan-grammar}
|
||||
\caption{Грамматика языка Милан}
|
||||
\end{figure}
|
||||
|
||||
\subsection{Константы}
|
||||
|
||||
В языке реализована поддержка знаковых целочисленных констант. Синтаксически
|
||||
константы представляют собой последовательность цифр, перед которой может
|
||||
находиться знак `-'. Примеры констант: \texttt{0}, \texttt{9}, \texttt{1234},
|
||||
\texttt{-19}, \texttt{-0}.
|
||||
|
||||
\subsection{Идентификаторы}
|
||||
|
||||
Идентификаторы представляют собой последовательность букв латинского алфавита и
|
||||
цифр. Первым символом идентификатора должна быть буква. Максимальная длина
|
||||
идентификатора ограничена 63 символами. Регистр символов не учитывается.
|
||||
Идентификаторы не должны совпадать с ключевыми словами \texttt{begin},
|
||||
\texttt{end}, \texttt{if}, \texttt{then}, \texttt{else}, \texttt{fi},
|
||||
\texttt{do}, \texttt{od}, \texttt{while}, \texttt{read}, \texttt{write}.
|
||||
|
||||
Примеры идентификаторов: \texttt{a}, \texttt{X}, \texttt{GCD}, \texttt{Milan23}.
|
||||
Цепочки символов, недопустимые в качестве идентификаторов: \texttt{12a} (первый
|
||||
символ не является буквой), \texttt{Begin} (цепочка символов совпадает с
|
||||
ключевым словом), \texttt{a\_1} (цепочка содержит символ подчеркивания).
|
||||
|
||||
\subsection{Арифметические выражения}
|
||||
|
||||
\begin{grammar}
|
||||
<expression> ::= <term> \{<addop> <term>\}
|
||||
|
||||
<term> ::= <factor> \{<mulop> <factor>\}
|
||||
|
||||
<factor> ::= <ident> | <number> | `(' <expression> `)'
|
||||
\end{grammar}
|
||||
|
||||
Арифметические выражения строятся по традиционным для языков программирования
|
||||
правилам. Элементами арифметических выражений могут быть константы,
|
||||
идентификаторы, знаки арифметических операций `\texttt{+}' (сложение),
|
||||
`\texttt{-}' (вычитание), `\texttt{*}' (умножение), `\texttt{/}'
|
||||
(деление), скобки и ключевое слово \texttt{read}, которое обозначает операцию
|
||||
чтения числа со стандартного ввода.
|
||||
|
||||
Примеры правильных выражений: \texttt{12 + 4}, \texttt{i}, \texttt{(x+1)*y},
|
||||
\texttt{2*x+1}, \texttt{read}, \texttt{read * (x - read)}.
|
||||
|
||||
Операции `\texttt{*}' и `\texttt{/}' имеют более высокий приоритет, чем
|
||||
операции `\texttt{+}' и `\texttt{-}'. Все эти операции левоассоциативны.
|
||||
|
||||
Значения арифметических выражений вычисляются слева направо с учетом приоритета
|
||||
операций. Порядок вычисления важен из-за присутствия операции \texttt{read},
|
||||
благодаря которой вычисление значения выражения имеет побочный эффект.
|
||||
|
||||
\subsection{Печать чисел на стандартный вывод}
|
||||
|
||||
\begin{grammar}
|
||||
<statement> ::= `write' `(' <expression> `)'
|
||||
\end{grammar}
|
||||
|
||||
Печать чисел осуществляется с помощью оператора \texttt{write}, аргументом
|
||||
которого является произвольное арифметическое выражение. Примеры использования
|
||||
оператора \texttt{write}: \texttt{write(5)},
|
||||
\texttt{write(n+7)}, \texttt{write((2+read)*3)}, \texttt{write(read)}.
|
||||
|
||||
\subsection{Оператор присваивания}
|
||||
|
||||
\begin{grammar}
|
||||
<statement> ::= <ident> `:=' <expression>
|
||||
\end{grammar}
|
||||
|
||||
При выполнении оператора присваивания сначала вычисляется значение выражения,
|
||||
записанного в его правой части. Затем результат записывается в ячейку памяти,
|
||||
соответствующую переменной в левой части оператора присваивания.
|
||||
|
||||
Последовательность символов `\texttt{:=}' является неделимой и не может
|
||||
содержать пробелов между символами `\texttt{:}' и `\texttt{=}'.
|
||||
|
||||
Примеры корректных операторов присваивания: \texttt{a := b + 1}, \texttt{c :=
|
||||
read}.
|
||||
|
||||
\subsection{Условный оператор}
|
||||
|
||||
\begin{grammar}
|
||||
<statement> ::= `if' <relation> `then' <statementList> [`else' <statementList>] `fi'
|
||||
|
||||
<relation> ::= <expression> <cmp> <expression>
|
||||
|
||||
<cmp> ::= `=' | `!=' | `<' | `<=' | `>' | `>='
|
||||
\end{grammar}
|
||||
|
||||
Условный оператор включает:
|
||||
\begin{enumerate}
|
||||
\item условие, которое представляет собой проверку на равенство или неравенство
|
||||
двух арифметических выражений,
|
||||
\item последовательность операторов, которая должна быть выполнена, если условие
|
||||
истинно (блок \texttt{then}),
|
||||
\item необязательную последовательность операторов, которая должна быть
|
||||
выполнена, если условие ложно (блок \texttt{else}).
|
||||
\end{enumerate}
|
||||
|
||||
Проверка условия $a ? b$, где $a$ и $b$ --- арифметические выражения, а <<$?$>>
|
||||
--- один из операторов сравнения, производится следующим образом:
|
||||
\begin{enumerate}
|
||||
\item вычисляется значение выражения $a$;
|
||||
\item вычисляется значение выражения $b$;
|
||||
\item проверяется выполнение условия.
|
||||
\end{enumerate}
|
||||
|
||||
Поддерживаются следующие отношения: `\texttt{=}' (<<равно>>), `\texttt{!=}'
|
||||
(<<не равно>>), `\texttt{<}' (<<меньше>>), `\texttt{<=}' (<<меньше или равно>>),
|
||||
`\texttt{>}' (<<больше>>), `\texttt{>=}' (<<больше или равно>>).
|
||||
|
||||
Условный оператор завершается ключевым словом \texttt{fi}. Оно используется,
|
||||
чтобы избежать известной проблемы <<висячего else>>, которая встречается, в
|
||||
частности, в языках C, C++ и Java. Наличие <<закрывающей скобки>> \texttt{fi}
|
||||
позволяет однозначно интерпретировать вложенные условные операторы.
|
||||
|
||||
Примеры использования условного оператора приведены на рисунках~\ref{ifthen} и
|
||||
\ref{ifthenelse}. Обе эти программы считывают со стандартного ввода два числа.
|
||||
Программа на рисунке~\ref{ifthen} печатает наибольшее из двух чисел. Программа
|
||||
на рисунке~\ref{ifthenelse} печатает числа в порядке возрастания.
|
||||
|
||||
\begin{figure}
|
||||
\begin{lstlisting}
|
||||
begin
|
||||
x := read;
|
||||
y := read;
|
||||
max := x;
|
||||
if y > max then
|
||||
max := y
|
||||
fi;
|
||||
write (max)
|
||||
end
|
||||
\end{lstlisting}
|
||||
\label{ifthen}
|
||||
\caption{Пример условного оператора \texttt{if..then..fi}}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}
|
||||
\begin{lstlisting}
|
||||
begin
|
||||
x := read;
|
||||
y := read;
|
||||
if x <= y then
|
||||
write (x);
|
||||
write (y)
|
||||
else
|
||||
write (y);
|
||||
write (x)
|
||||
fi
|
||||
end
|
||||
\end{lstlisting}
|
||||
\label{ifthenelse}
|
||||
\caption{Пример условного оператора \texttt{if..then..else..fi}}
|
||||
\end{figure}
|
||||
|
||||
\subsection{Цикл с предусловием}
|
||||
|
||||
\begin{grammar}
|
||||
<statement> ::= `while' <relation> `do' <statementList> `od'
|
||||
|
||||
<relation> ::= <expression> <cmp> <expression>
|
||||
|
||||
<cmp> ::= `=' | `!=' | `<' | `<=' | `>' | `>='
|
||||
\end{grammar}
|
||||
|
||||
При выполнении цикла с предусловием сначала проверяется, истинно ли условие.
|
||||
Если оно истинно, последовательно выполняются операторы, составляющие тело
|
||||
цикла. После того, как последний оператор будет выполнен, управление снова
|
||||
возвращается к проверке условия. Если условие оказывается ложным, выполнение
|
||||
цикла завершается. Вычисление выражений, необходимых для проверки условия,
|
||||
производится так же, как и при выполнении условного оператора.
|
||||
|
||||
Пример программы, использующей цикл, приведен на рисунке~\ref{while}. Эта
|
||||
программа считывает со стандартного ввода число и печатает его факториал.
|
||||
|
||||
\begin{figure}
|
||||
\begin{lstlisting}
|
||||
begin
|
||||
n := read;
|
||||
factorial := 1;
|
||||
x := 1;
|
||||
while x <= n do
|
||||
factorial := factorial * x;
|
||||
x := x + 1
|
||||
od;
|
||||
write (factorial)
|
||||
end
|
||||
\end{lstlisting}
|
||||
\label{while}
|
||||
\caption{Пример цикла с предусловием}
|
||||
\end{figure}
|
||||
|
||||
\section{Использование компилятора}
|
||||
|
||||
Компилятор \textsc{CMilan} преобразует программы на языке Милан в
|
||||
последовательность команд виртуальной машины Милана. Общее описание виртуальной
|
||||
машины можно найти в учебнике~\cite{karpov05}, а более детальное
|
||||
описание --- или в документации, прилагаемой к исходным текстам виртуальной
|
||||
машины.
|
||||
|
||||
Исполняемый файл компилятора называется \texttt{cmilan} (в операционных системах
|
||||
семейства Unix) или \texttt{cmilan.exe} (в Windows). Компилятор имеет интерфейс
|
||||
командной строки. Чтобы скомпилировать программу, записанную в файле
|
||||
<<program.mil>>, нужно выполнить команду <<\texttt{cmilan program.mil}>>.
|
||||
Результатом работы компилятора является либо последовательность команд для
|
||||
виртуальной машины Милана, которая печатается на стандарный вывод, либо набор
|
||||
сообщений о синтаксических ошибках, которые выводятся в стандартный поток
|
||||
ошибок.
|
||||
|
||||
При вызове программы \texttt{cmilan} без параметров компилятор печатает краткую
|
||||
информацию о порядке его вызова.
|
||||
|
||||
Чтобы сохранить генерируемую компилятором программу для виртуальной машины в
|
||||
файл, достаточно перенаправить в этот файл стандартный поток вывода.
|
||||
|
||||
На рисунке~\ref{cmilan-usage} приведен пример сеанса работы с компилятором.
|
||||
|
||||
\begin{figure}
|
||||
\begin{verbatim}
|
||||
bash$ cat factorial.mil
|
||||
BEGIN
|
||||
n := READ;
|
||||
factorial := 1;
|
||||
i := 1;
|
||||
WHILE i <= n DO
|
||||
factorial := factorial * i;
|
||||
i := i + 1
|
||||
OD;
|
||||
WRITE(factorial)
|
||||
END
|
||||
bash$ cmilan factorial.mil > factorial.out
|
||||
bash$ milanvm factorial.out
|
||||
Reading input from factorial.out
|
||||
> 5
|
||||
120
|
||||
\end{verbatim}
|
||||
\label{cmilan-usage}
|
||||
\caption{Пример использования компилятора \textsc{CMilan}}
|
||||
\end{figure}
|
||||
|
||||
\subsection{Сборка компилятора в Unix}
|
||||
|
||||
Для сборки компилятора в операционных системах семейства Unix (в частности,
|
||||
GNU/Linux и FreeBSD) необходим компилятор C++, в качестве которого рекомендуется
|
||||
использовать GCC, и GNU Make. Для сборки компилятора достаточно перейти в
|
||||
каталог \texttt{src}, в котором расположены исходные тексты компилятора
|
||||
\textsc{CMilan}, и выполнить команду \texttt{make}.
|
||||
|
||||
\subsection{Сборка компилятора в Windows}
|
||||
|
||||
Для сборки компилятора \textsc{CMilan} в Windows можно использовать компилятор
|
||||
GCC или Microsoft Visual Studio. При использовании GCC (Cygwin, MinGW) сборка
|
||||
производится так же, как и в Unix. Для сборки компилятора с помощью Visual
|
||||
Studio 2010 в дистрибутив компилятора включены файлы проекта (каталог
|
||||
\texttt{src\\cmilan\_vs2011}). Если необходимо использовать другие версии Visual
|
||||
Studio, достаточно создать пустой проект консольного приложения Win32 и добавить
|
||||
в проект существующие исходные тексты из каталога \texttt{src}.
|
||||
|
||||
\section{Устройство компилятора}
|
||||
|
||||
\subsection{Архитектура компилятора}
|
||||
Компилятор \textsc{CMilan} включает три компонента:
|
||||
\begin{enumerate}
|
||||
\item лексический анализатор;
|
||||
\item синтаксический анализатор;
|
||||
\item генератор команд виртуальной машины Милана.
|
||||
\end{enumerate}
|
||||
|
||||
Лексический анализатор посимвольно читает из входного потока текст программы и преобразует
|
||||
группы символов в лексемы (терминальные символы грамматики языка Милан). При
|
||||
этом он отслеживает номер текущей строки, который используется синтаксическим
|
||||
анализатором при формировании сообщений об ошибках. Также лексический анализатор
|
||||
удаляет пробельные символы и комментарии. При формировании лексем анализатор
|
||||
идентифицирует ключевые слова и последовательности символов (например, оператор
|
||||
присваивания), а также определяет значения числовых констант и имена переменных.
|
||||
Эти значения становятся значениями атрибутов, связанных с лексемами.
|
||||
|
||||
Синтаксический анализатор читает сформированную лексическим анализатором
|
||||
последовательность лексем и проверяет ее соответствие грамматике языка Милан.
|
||||
Для этого используется метод рекурсивного спуска. Если в процессе
|
||||
синтаксического анализа обнаруживается ошибка, анализатор формирует сообщение об
|
||||
ошибке, включающее сведения об ошибке и номер строки, в которой она возникла.
|
||||
В процессе анализа программы синтаксический анализатор генерирует набор машинных
|
||||
команд, соответствующие каждой конструкции языка.
|
||||
|
||||
Генератор кода представляет собой служебный компонент, ответственный за
|
||||
формирование внешнего представления генерируемого кода. Генератор поддерживает
|
||||
буфер команд и предоставляет синтаксическому анализатору набор функций,
|
||||
позволяющий записать указанную команду по определенному адресу. После того, как
|
||||
синтаксический анализатор заканчивает формирование программы, генератор кода
|
||||
используется для печати на стандартный вывод отсортированной по возрастанию
|
||||
адресов последовательности инструкций. Использование генератора кода несколько
|
||||
упрощает устройство компилятора, поскольку синтаксический анализатор может
|
||||
генерировать команды не в порядке их следования в программе, а по мере получения
|
||||
всей необходимой информации для их формирования. Это особенно важно при
|
||||
трансляции таких инструкций, как условные операторы или циклы.
|
||||
|
||||
Все три компонента объединяются <<драйвером>> --- управляющей программой
|
||||
компилятора. Драйвер анализирует аргументы командной строки, инициализирует
|
||||
синтаксический анализатор и вызывает его метод \texttt{parse()}, запуская
|
||||
процесс трансляции.
|
||||
|
||||
\textsc{CMilan} является примером простейшего однопроходного компилятора,
|
||||
в котором синтаксический анализ и генерация кода выполняются совместно. При этом
|
||||
компилятор никак не оптимизирует код. Реальные компиляторы
|
||||
обычно выполняют трансляцию и оптимизацию кода в несколько проходов, используя
|
||||
несколько видов внутреннего представления программы. Сведения об архитектуре
|
||||
таких компиляторов можно найти, в частности, в классической <<книге
|
||||
дракона>>~\cite{dragonbook11}. В случае \textsc{CMilan}, тем не менее,
|
||||
предпочтение было отдано не качеству генерируемого кода, а простоте реализации и
|
||||
легкости расширения.
|
||||
|
||||
\subsection{Генератор кода}
|
||||
|
||||
Основной задачей генератора кода является хранение и заполнение буфера
|
||||
инструкций последовательностью команд для виртуальной машины Милана. Генератор
|
||||
кода не отвечает за правильность этой последовательности и не выполняет никаких
|
||||
семантических преобразований. Тем не менее, генератор кода вполне мог бы быть
|
||||
расширен для того, чтобы выполнять простые оптимизации на уровне машинных
|
||||
команд.
|
||||
|
||||
Генератор кода описан в файлах \texttt{codegen.h} и \texttt{codegen.cpp}. Его
|
||||
реализация состоит из двух классов. Класс \texttt{Command} описывает машинные
|
||||
инструкций и отвечает за их вывод. В классе Codegen реализованы функции добавления
|
||||
инструкций в буфер, получения текущего адреса в буфере, резервирования ячейки
|
||||
для инструкции, которая должна быть добавлена в код позднее, и печати
|
||||
последовательности инструкций из буфера на стандартный вывод.
|
||||
|
||||
Приведем краткое описание методов генератора кода.
|
||||
\begin{itemize}
|
||||
\item \texttt{void Command::print(int address, ostream\& os)}
|
||||
|
||||
Печать инструкции с указанием адреса \texttt{address} в поток вывода \texttt{os}.
|
||||
|
||||
\item \texttt{void CodeGen::emit(Instruction instruction)}
|
||||
|
||||
Добавление инструкции \texttt{instruction} без аргументов в конец буфера.
|
||||
|
||||
\item \texttt{void CodeGen::emit(Instruction instruction, int arg)}
|
||||
|
||||
Добавление инструкции \texttt{instruction} с аргументом \texttt{arg} в конец буфера.
|
||||
|
||||
\item \texttt{void CodeGen::emitAt(int address, Instruction instruction)}
|
||||
|
||||
Запись инструкции \texttt{instruction} без аргументов в буфер со смещением
|
||||
\texttt{address}.
|
||||
|
||||
\item \texttt{void CodeGen::emitAt(int address, Instruction instruction, int
|
||||
arg)}
|
||||
|
||||
Запись инструкции \texttt{instruction} с аргументом \texttt{arg} в буфер
|
||||
со смещением \texttt{address}
|
||||
|
||||
\item \texttt{int CodeGen::getCurrentAddress()}
|
||||
|
||||
Возврат адреса, по которому будет записана очередная инструкция.
|
||||
|
||||
\item \texttt{int CodeGen::reserve()}
|
||||
|
||||
Резервирование ячейки памяти для инструкции. Метод добавляет в конец
|
||||
буфера инструкцию \texttt{NOP} и возвращает ее адрес.
|
||||
|
||||
\item \texttt{void CodeGen::flush()}
|
||||
|
||||
Вывод последовательности инструкций в выходной поток.
|
||||
\end{itemize}
|
||||
|
||||
\subsection{Лексический анализатор}
|
||||
|
||||
Лексический анализатор преобразует считываемые из входного потока символы в
|
||||
лексемы языка Милан. Например, последовательность символов `\texttt{B}',
|
||||
`\texttt{E}', `\texttt{G}', `\texttt{I}', `\texttt{N}' соответствует ключевому
|
||||
слову `\texttt{begin}'. Каждой лексеме соответствует отдельный элемент
|
||||
перечислимого типа \texttt{Token}. В частности, ключевому слову `\texttt{begin}'
|
||||
соответствует константа \texttt{T\_BEGIN}. С некоторыми лексемами связана
|
||||
дополнительная информация --- значение атрибута. Так, с лексемой
|
||||
\texttt{T\_NUMBER} (целочисленная константа) связано число, равное значению этой
|
||||
константы, а с лексемой \texttt{T\_IDENTIFIER} (идентификатор) --- строка,
|
||||
содержащая имя переменной.
|
||||
|
||||
В таблице~\ref{lexemes} приведен полный список лексем и значений связанных с ними
|
||||
атрибутов.
|
||||
|
||||
\begin{table}
|
||||
\begin{center}
|
||||
\begin{tabular}{|l|l|p{6cm}|}
|
||||
\hline
|
||||
\hline
|
||||
Имя лексемы & Значение & Атрибут \\
|
||||
\hline
|
||||
\hline
|
||||
\texttt{T\_EOF} & Конец текстового потока & \\
|
||||
\hline
|
||||
\texttt{T\_ILLEGAL} & Недопустимый символ & \\
|
||||
\hline
|
||||
\texttt{T\_IDENTIFIER}& Идентификатор & Имя переменной \\
|
||||
\hline
|
||||
\texttt{T\_NUMBER} & Константа & Значение константы \\
|
||||
\hline
|
||||
\texttt{T\_BEGIN} & Ключевое слово `\texttt{begin}' & \\
|
||||
\hline
|
||||
\texttt{T\_END} & Ключевое слово `\texttt{end}' & \\
|
||||
\hline
|
||||
\texttt{T\_IF} & Ключевое слово `\texttt{if}' & \\
|
||||
\hline
|
||||
\texttt{T\_THEN} & Ключевое слово `\texttt{then}' & \\
|
||||
\hline
|
||||
\texttt{T\_ELSE} & Ключевое слово `\texttt{else}' & \\
|
||||
\hline
|
||||
\texttt{T\_FI} & Ключевое слово `\texttt{fi}' & \\
|
||||
\hline
|
||||
\texttt{T\_WHILE} & Ключевое слово `\texttt{while}' & \\
|
||||
\hline
|
||||
\texttt{T\_DO} & Ключевое слово `\texttt{do}' & \\
|
||||
\hline
|
||||
\texttt{T\_OD} & Ключевое слово `\texttt{od}' & \\
|
||||
\hline
|
||||
\texttt{T\_WRITE} & Ключевое слово `\texttt{write}' & \\
|
||||
\hline
|
||||
\texttt{T\_READ} & Ключевое слово `\texttt{read}' & \\
|
||||
\hline
|
||||
\texttt{T\_ASSIGN} & Оператор `\texttt{:=}' & \\
|
||||
\hline
|
||||
\texttt{T\_ADDOP} & Операция типа сложения &
|
||||
\texttt{A\_PLUS}~(`\texttt{+}'), \texttt{A\_MINUS}~(`\texttt{-}') \\
|
||||
\hline
|
||||
\texttt{T\_MULOP} & Операция типа умножения &
|
||||
\texttt{A\_MULTIPLY}~(`\texttt{*}'), \texttt{A\_DIVIDE}~(`\texttt{/}') \\
|
||||
\hline
|
||||
\texttt{T\_CMP} & Оператор отношения &
|
||||
\texttt{C\_EQ}~(`\texttt{=}'), \texttt{C\_NE}~(`\texttt{!=}'),
|
||||
\texttt{C\_LT}~(`\texttt{<}'), \texttt{C\_LE}~(`\texttt{<=}'),
|
||||
\texttt{C\_GT}~(`\texttt{>}'), \texttt{C\_GE}~(`\texttt{=}') \\
|
||||
\hline
|
||||
\texttt{T\_LPAREN} & Открывающая скобка & \\
|
||||
\hline
|
||||
\texttt{T\_RPAREN} & Закрывающая скобка & \\
|
||||
\hline
|
||||
\texttt{T\_SEMICOLON} & `\texttt{;}' & \\
|
||||
\hline
|
||||
\hline
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
\caption{Лексемы языка Милан}
|
||||
\label{lexemes}
|
||||
\end{table}
|
||||
|
||||
Все ключевые слова перечислены в ассоциативном массиве \texttt{keywords\_}. При
|
||||
этом ключом является строка, соответствующая ключевому слову в нижнем регистре,
|
||||
а значением --- соответствующая лексема. С помощью массива \texttt{tokenNames\_}
|
||||
каждой лексеме сопоставлено ее строковое внешнее представление, которое
|
||||
используется при формировании сообщений об ошибках. Последовательность
|
||||
символов, не соответствующая ни одной из лексем, считается ошибочной. В этом
|
||||
случае лексический анализатор возвращает значение \texttt{T\_ILLEGAL}.
|
||||
|
||||
Исходный текст лексического анализатора находится в файлах \texttt{scanner.h} и
|
||||
\texttt{scanner.cpp}. Алгоритм лексического анализа реализован в классе
|
||||
\texttt{Scanner}. Он содержит следующие открытые методы:
|
||||
|
||||
\begin{itemize}
|
||||
\item \texttt{Token token()}
|
||||
|
||||
Получение текущей лексемы. Одна и та же лексема может быть прочитана
|
||||
многократно.
|
||||
|
||||
\item \texttt{void nextToken()}
|
||||
|
||||
Переход к следующей лексеме.
|
||||
|
||||
\item \texttt{int getIntValue()}
|
||||
|
||||
Получение целочисленного атрибута текущей лексемы. В базовой версии Милана
|
||||
этот метод используется только для получения значений целочисленных
|
||||
констант.
|
||||
|
||||
\item \texttt{string getStringValue()}
|
||||
|
||||
Получение строкового атрибута текущей лексемы. В базовой версии Милана
|
||||
этот метод используется для получения имен идентификаторов.
|
||||
|
||||
\item \texttt{Cmp getCmpValue()}
|
||||
|
||||
Получение кода операции сравнения текущей лексемы (используется только
|
||||
вместе с лексемой \texttt{T\_CMP}).
|
||||
|
||||
\item \texttt{Arithmetic getArithmeticValue()}
|
||||
|
||||
Получение кода арифметической операции (используется вместе с лексемами
|
||||
\texttt{T\_ADDOP} и \texttt{T\_MULOP}).
|
||||
|
||||
\item \texttt{const int getLineNumber()}
|
||||
|
||||
Получение номера текущей строки в исходном файле (используется при
|
||||
формировании сообщений об ошибках).
|
||||
\end{itemize}
|
||||
|
||||
Поля класса описывают состояние лексического анализатора:
|
||||
\begin{itemize}
|
||||
\item \texttt{const string fileName\_}
|
||||
|
||||
Имя входного файла.
|
||||
|
||||
\item \texttt{int lineNumber\_}
|
||||
|
||||
Номер текущей строки в анализируемой программе.
|
||||
|
||||
\item \texttt{Token token\_}
|
||||
|
||||
Текущая лексема.
|
||||
|
||||
\item \texttt{int intValue\_}
|
||||
|
||||
Целочисленный атрибут лексемы.
|
||||
|
||||
\item \texttt{string stringValue\_}
|
||||
|
||||
Строковый атрибут лексемы.
|
||||
|
||||
\item \texttt{Cmp cmpValue\_}
|
||||
|
||||
Код оператора сравнения.
|
||||
|
||||
\item \texttt{Arithmetic arithmeticValue\_}
|
||||
|
||||
Код арифметической операции.
|
||||
|
||||
\item \texttt{map<string, Token> keywords\_}
|
||||
|
||||
Ассоциативный массив, описывающий ключевые слова языка Милан. Используется
|
||||
при обнаружении цепочки символов, которая может быть как идентификатором,
|
||||
так и ключевым словом.
|
||||
|
||||
\item \texttt{ifstream input\_}
|
||||
|
||||
Входной поток для чтения из файла.
|
||||
|
||||
\item \texttt{char ch\_}
|
||||
|
||||
Очередной символ программы.
|
||||
\end{itemize}
|
||||
|
||||
Закрытые методы класса (служебные функции):
|
||||
\begin{itemize}
|
||||
\item \texttt{bool isIdentifierStart(char c)}
|
||||
|
||||
Метод возвращает значение \texttt{true}, если символ \texttt{c} может быть
|
||||
первым символом идентификатора (в базовой версии языка Милан это означает,
|
||||
что символ является буквой латинского алфавита).
|
||||
|
||||
\item \texttt{bool isIdentifierBody(char c)}
|
||||
|
||||
Метод возвращает значение \texttt{true}, если символ \texttt{c} может
|
||||
быть частью идентификатора. В базовой версии языка Милан эт означает, что
|
||||
символ является буквой латинского алфавита или цифрой.
|
||||
|
||||
\item \texttt{void skipSpace()}
|
||||
|
||||
Пропуск всех пробельных символов (символов пробела, табуляции, перевода
|
||||
строки). При чтении символа перевода строки увеличивается номер текущей
|
||||
строки \texttt{lineNumber\_}.
|
||||
|
||||
\item \texttt{void nextChar()}
|
||||
|
||||
Переход к следующему символу программы.
|
||||
\end{itemize}
|
||||
|
||||
Основная часть алгоритма лексического анализа реализована в методе
|
||||
\texttt{nextToken}. Для того, чтобы перейти к следующей лексеме, выполняется
|
||||
следующая последовательность действий,
|
||||
|
||||
Прежде всего необходимо удалить пробелы и комментарии. Признаком начала
|
||||
комментария является последовательность символов `\texttt{/*}'. При обнаружении
|
||||
символа `\texttt{/}' необходимо проверить следующий за ним символ. Если он не
|
||||
совпадает с `\texttt{*}', значит, был найден оператор деления. В этом случае
|
||||
переменной \texttt{token\_} присваивается значение \texttt{T\_MULOP}, а атрибуту
|
||||
\texttt{arithmeticValue\_} --- значение \texttt{A\_DIVIDE}. Если за символом
|
||||
`\texttt{/}' непосредственно следует `\texttt{*}', лексический анализатор
|
||||
пропускает все символы, пока не встретит закрывающую комментарий
|
||||
последовательность `\texttt{*/}' или конец файла. После того, как был найден
|
||||
признак конца комментария, еще раз вызывается функция \texttt{skipSpace}.
|
||||
Операция удаления комментариев повторяется многократно, пока очередной
|
||||
непробельный символ не окажется отличным от символа `\texttt{/}'. Если в
|
||||
процессе удаления пробелов или комментариев был встречен конец файла, очередной
|
||||
лексемой считается \texttt{T\_EOF}.
|
||||
|
||||
После удаления пробелов и комментариев происходит анализ очередного символа. Он
|
||||
выполняется по следующим правилам.
|
||||
\begin{enumerate}
|
||||
\item Если символ является цифрой, то очередная лексема --- целочисленная константа
|
||||
(\texttt{T\_NUMBER}). Лексический анализатор считывает из входного потока эту
|
||||
и все следующие за ней цифры, преобразуя полученную последовательность в целое
|
||||
число. Это число становится значением атрибута \texttt{intValue\_}.
|
||||
|
||||
\item Если символ может быть началом идентификатора, из потока считываются все
|
||||
последующие символы, которые могут быть частью идентификатора. Полученная
|
||||
последовательность проверяется на совпадение с ключевым словом. В случае
|
||||
совпадения очередной лексемой считается лексема, соответствующая этому ключевому
|
||||
слову. Если цепочка символов не совпала ни с одним ключевым словом, очередная
|
||||
лексема считается идентификатором (\texttt{T\_IDENTIFIER}), а сама цепочка
|
||||
становится значением строкового атрибута \texttt{stringValue\_}.
|
||||
|
||||
\item Если символ равен `\texttt{:}', лексический анализатор считывает следующий
|
||||
символ и проверяет, что он равен `\texttt{=}'. В этом случае возвращается
|
||||
лексема \texttt{T\_ASSIGN}. Если следом за `\texttt{:}' идет любой другой
|
||||
символ, лексема считается ошибочной (\texttt{T\_ILLEGAL}).
|
||||
|
||||
\item Если символ равен `\texttt{!}', производится аналогичная проверка на
|
||||
равенство следующего символа `\texttt{=}'. В случае успеха текущей лексемой
|
||||
становится лексема \texttt{T\_CMP}, а атрибут \texttt{cmpValue\_} принимает
|
||||
значение \texttt{T\_NE}.
|
||||
|
||||
\item Если символ равен `\texttt{<}', `\texttt{>}' или `\texttt{=}', текущей
|
||||
лексемой становится \texttt{T\_CMP}. Чтобы определить значение атрибута
|
||||
\texttt{cmpValue\_}, лексический анализатор может прочитать еще один символ для
|
||||
того, чтобы отличить оператор `\texttt{<}' от оператора `\texttt{>}', а оператор
|
||||
`\texttt{>}' от оператора `\texttt{>=}'.
|
||||
|
||||
\item Если символ равен `\texttt{+}' или `\texttt{-}', переменная
|
||||
\texttt{token\_} принимает значение \texttt{T\_ADDOP}, при этом соответствующим
|
||||
образом устанавливается значение атрибута \texttt{arithmeticValue\_}. Аналогично
|
||||
обрабатываются символ `\texttt{*}' (лексема \texttt{T\_MULOP}). Оператор деления
|
||||
обрабатывать таким же образом не нужно, поскольку он обнаруживается в ходе
|
||||
удаления комментариев.
|
||||
|
||||
\item Если символ совпадает с `\texttt{;}', `\texttt{(}', `\texttt{)}',
|
||||
очередная лексема устанавливается в соответствующее символу значение.
|
||||
\end{enumerate}
|
||||
|
||||
\subsection{Синтаксический анализатор}
|
||||
|
||||
Задачами синтаксического анализатора является проверка соответствия программы
|
||||
грамматике языка Милан (рисунок~\ref{milan-grammar}) и формирование кода для
|
||||
виртуальной машины Милана в соответствии со структурой программы. Синтаксический
|
||||
анализ выполняется методом рекурсивного спуска. Каждому нетерминальному символу
|
||||
грамматики сопоставлен метод, выполняющий проверку соответствия
|
||||
последовательности лексем одному из тех правил грамматики, в левой части которых
|
||||
стоит данный нетерминальный символ. Семантические действия (генерация кода)
|
||||
встроены в код метода.
|
||||
|
||||
Исходный текст синтаксического анализатора находится в файлах \texttt{parser.h}
|
||||
и \texttt{parser.cpp}. Алгоритм синтаксического анализа реализован в классе
|
||||
\texttt{Parser}. Конструктор класса в качестве аргумента принимает имя файла, в
|
||||
котором находится анализируемый текст, и создает экземпляры лексического
|
||||
анализатора и генератора кода.
|
||||
|
||||
Синтаксический анализатор предоставляет один открытый метод \texttt{void parse()},
|
||||
который используется для того, чтобы начать процесс анализа.
|
||||
|
||||
Состояние анализатора описывается следующими полями:
|
||||
\begin{itemize}
|
||||
\item \texttt{Scanner* scanner\_}
|
||||
|
||||
Экземпляр лексического анализатора.
|
||||
|
||||
\item \texttt{CodeGen* codegen\_}
|
||||
|
||||
Экземпляр генератора кода.
|
||||
|
||||
\item \texttt{std::ostream\& output\_}
|
||||
|
||||
Выходной поток, в который должны быть выведены инструкции программы.
|
||||
Базовая версия компилятора Милан использует в качестве выходного потока
|
||||
стандартный вывод (\texttt{std::cout}).
|
||||
|
||||
\item \texttt{bool error\_}
|
||||
|
||||
Признак ошибки в тексте программы. Если \texttt{error\_} принимает
|
||||
значение <<истина>>, генерируемый машинный код не выводится.
|
||||
|
||||
\item \texttt{map<string, int> variables\_}
|
||||
|
||||
Таблица имен, найденных в программе. Она сопоставляет каждой переменной ее
|
||||
адрес в памяти виртуальной машины. Поскольку базовая версия языка Милан не
|
||||
содержит вложенных блоков, процедур или функций, таблица имен представляет
|
||||
собой простой ассоциативный массив.
|
||||
|
||||
\item \texttt{int lastVar\_}
|
||||
|
||||
Адрес последней найденной переменной.
|
||||
\end{itemize}
|
||||
|
||||
Синтаксический анализатор включает ряд вспомогательных функций (закрытые методы
|
||||
класса).
|
||||
|
||||
\begin{itemize}
|
||||
\item \texttt{bool see(Token t)}
|
||||
|
||||
Сравнение текущей лексемы с образцом. Текущая позиция в потоке лексем не
|
||||
изменяется.
|
||||
|
||||
\item \texttt{bool match(Token t)}
|
||||
|
||||
Проверка совпадения текущей лексемы с образцом. Если лексема и образец
|
||||
совпадают, лексема изымается из потока.
|
||||
|
||||
\item \texttt{void mustBe(Token t)}
|
||||
|
||||
Проверка совпадения текущей лексемы с образцом. Если лексема и образец
|
||||
совпадают, лексема изымается из потока. В противном случае формируется
|
||||
сообщение об ошибке, а программа считается некорректной.
|
||||
|
||||
\item \texttt{void next()}
|
||||
|
||||
Переход к следующей лексеме.
|
||||
|
||||
\item \texttt{void reportError(const string\& message)}
|
||||
|
||||
Формирование сообщения об ошибке. Каждое сообщение включает текст
|
||||
\texttt{message} и номер строки, в которой обнаружена ошибка.
|
||||
|
||||
\item \texttt{void recover(Token t)}
|
||||
|
||||
Восстановление после ошибки. Используется примитивный алгоритм: если
|
||||
очередная лексема не совпадает с ожидаемой, анализатор пропускает все
|
||||
последующие лексемы, пока не встретит ожидаемую лексему или
|
||||
конец файла. Хотя такой метод восстановления не отличается точностью, он,
|
||||
тем не менее, позволяет продолжить анализ и, возможно, найти ошибки в
|
||||
оставшейся части программы.
|
||||
|
||||
\item \texttt{int findOrAddVariable(const string\&)}
|
||||
|
||||
Поиск имени переменной в таблице имен. Если имя найдено, метод возвращает
|
||||
адрес переменной, в противном случае имя добавляется в таблицу имен, и для
|
||||
соответствующей переменной резервируется новый адрес.
|
||||
\end{itemize}
|
||||
|
||||
Кроме служебных методов, класс \texttt{Parser} содержит закрытые методы
|
||||
\texttt{void program()}, \texttt{void statementList()}, \texttt{void
|
||||
statement()}, \texttt{void expression()}, \texttt{void term()}, \texttt{void
|
||||
factor()}, \texttt{void relation()}, которые соответствуют нетерминальным
|
||||
символам грамматики. Эти методы не возвращают значений и не принимают
|
||||
аргументов, поскольку с нетерминальными символами не связаны явные атрибуты: все
|
||||
семантические действия, которые производятся во время анализа программы,
|
||||
модифицируют буфер общего для всех методов генератора кода.
|
||||
|
||||
\subsubsection{Распознавание операторов}
|
||||
|
||||
Продемонстрируем алгоритм работы синтаксического анализатора на примере анализа
|
||||
операторов (метод \texttt{statement}).
|
||||
|
||||
В соответствии с грамматикой языка Милан, в качестве оператора может выступать
|
||||
оператор присваивания, условный оператор \texttt{if}, оператор цикла
|
||||
\texttt{while} или оператор печати \texttt{write}. Для выбора нужной
|
||||
альтернативы достаточно прочитать первую лексему оператора.
|
||||
|
||||
Если очередная лексема равна \texttt{T\_IDENTIFIER}, синтаксический анализатор
|
||||
имеет дело с оператором присваивания. Он считывает имя переменной, стоящей в
|
||||
левой части присваивания, и определяет ее адрес. Затем анализатор проверяет, что
|
||||
следующая лексема совпадает с \texttt{T\_ASSIGN}. После знака `\texttt{:=}' в
|
||||
программе должно следовать арифметическое выражение, поэтому анализатор вызывает
|
||||
метод \texttt{expression()}. Этот метод проверяет правильность арифметического
|
||||
выражения и генерирует последовательность команд для его вычисления. В
|
||||
результате выполнены этой последовательности на вершине стека будет находиться
|
||||
значение выражения. Чтобы выполнить присваивание, достаточно записать значение с
|
||||
вершины стека в память по адресу переменной в левой части оператора
|
||||
присваивания. Для этого после вызова \texttt{expression()} нужно добавить в
|
||||
буфер команд инструкцию \texttt{STORE}, аргументом которой будет запомненный
|
||||
ранее адрес переменной.
|
||||
|
||||
Если очередная лексема равна \texttt{T\_IF}, анализатор должен начать разбор
|
||||
условного оператора. Сначала он вызывает метод \texttt{relation()}, проверяя,
|
||||
что после лексемы \texttt{T\_IF} следует правильное сравнение двух выражений.
|
||||
Метод \texttt{relation()} генерирует код, вычисляющий и сравнивающий два
|
||||
выражения. При выполнении этого кода на вершине стека останется значение $1$,
|
||||
если условие выполнено, и $0$ в противном случае. Если условие не выполнено,
|
||||
должен произойти переход к списку операторов блока \texttt{else} или, если этого
|
||||
блока нет, по адресу следующей за условным оператором инструкции. Однако этот
|
||||
адрес еще не известен синтаксическому анализатору. Чтобы решить эту проблему,
|
||||
применяется так называемый <<метод обратных поправок>>~\cite{dragonbook11}.
|
||||
Анализатор резервирует место для условного перехода \texttt{JUMP\_NO} и
|
||||
запоминает адрес этой инструкции в переменной \texttt{jumpNoAddress}, после чего
|
||||
переходит к дальнейшему анализу условного оператора.
|
||||
|
||||
Следом за условием в тексте программы должна находиться лексема
|
||||
\texttt{T\_THEN}. Если она обнаружена, анализатор переходит к следующей лексеме
|
||||
и вызывает метод \texttt{statementList()}, который анализирует список операторов
|
||||
и генерирует для них исполняемый код. Дальнейшие действия зависят от того,
|
||||
присутствует ли в условном операторе ветвь \texttt{else}. Если следующая лексема
|
||||
равна \texttt{T\_ELSE}, синтаксический анализатор должен сгенерировать
|
||||
инструкцию \texttt{JUMP} для выхода из условного оператора, после чего
|
||||
приступить к анализу блока \texttt{else}. Если условный оператор не содержит
|
||||
блока \texttt{else}, инструкция безусловного перехода в конце блока
|
||||
\texttt{then} не нужна.
|
||||
|
||||
Генерация безусловного перехода также требует знания адреса, следующего за
|
||||
последней инструкцией условного оператора, поэтому анализатор снова должен
|
||||
зарезервировать ячейку памяти для команды перехода. Следующий за этой ячейкой
|
||||
адрес содержит первую команду блока \texttt{else}. Это именно тот адрес, по
|
||||
которому необходимо было выполнить переход в случае, если условие не было
|
||||
выполнено. Поскольку теперь этот адрес известен, синтаксический анализатор
|
||||
генерирует инструкцию безусловного перехода и помещает ее в буфер команд по
|
||||
адресу, который записан в переменной \texttt{jumpNoAddress}. В случае отсутствия
|
||||
блока \texttt{else} поправка происходит таким же образом, но в качестве адреса
|
||||
перехода просто используется следующий за последней инструкцией блока
|
||||
\texttt{then} адрес. После вызова функции \texttt{statementList}, которая
|
||||
разбирает список операторов блока \texttt{else}, аналогично исправляется
|
||||
зарезервированная инструкция безусловного перехода.
|
||||
|
||||
Структура кода, соответствующая условному оператору, приведена на
|
||||
рисунке~\ref{ifthenelse-code}.
|
||||
|
||||
\begin{figure}
|
||||
\begin{verbatim}
|
||||
Вычисление выражений
|
||||
...
|
||||
COMPARE operator
|
||||
JUMP_NO elseLabel
|
||||
Операторы блока THEN
|
||||
...
|
||||
JUMP endLabel
|
||||
elseLabel: Операторы блока ELSE
|
||||
...
|
||||
endLabel:
|
||||
\end{verbatim}
|
||||
\caption{Структура исполняемого кода условного оператора}
|
||||
\label{ifthenelse-code}
|
||||
\end{figure}
|
||||
|
||||
Если очередная лексема равна \texttt{T\_WHILE}, синтаксическому анализатору
|
||||
предстоит разобрать оператор цикла. Анализатор должен запомнить текущий адрес
|
||||
команды, поскольку после каждой итерации цикла по этому адресу нужно будет
|
||||
возвращаться. Затем анализатор вызывает метод \texttt{relation()}, чтобы
|
||||
сгенерировать код проверки условия. Если условие окажется ложным, нужно будет
|
||||
выполнить переход на следующий за циклом оператор. Для этого синтаксический
|
||||
анализатор резервирует ячейку памяти для команды \texttt{JUMP\_NO}. После этого
|
||||
он проверяет наличие лексемы \texttt{T\_DO} и вызывает метод
|
||||
\texttt{statementList()} для обработки тела цикла. Затем анализатор генерирует
|
||||
инструкцию безусловного перехода назад, к началу проверки условия, и записывает
|
||||
в зарезервированную ячейку инструкцию условного перехода по известному теперь
|
||||
адресу конца цикла. Для завершения анализа инструкции достаточно убедиться, что
|
||||
очередная лексема равна ожидаемому значению \texttt{T\_OD}. Структура
|
||||
исполняемого кода для цикла приведена на рисунке~\ref{while-code}.
|
||||
|
||||
\begin{figure}
|
||||
\begin{verbatim}
|
||||
whileLabel: Вычисление выражений
|
||||
...
|
||||
COMPARE operator
|
||||
JUMP_NO endLabel
|
||||
...
|
||||
Операторы тела цикла
|
||||
...
|
||||
JUMP whileLabel
|
||||
endLabel:
|
||||
\end{verbatim}
|
||||
\caption{Структура исполняемого кода цикла с предусловием}
|
||||
\label{while-code}
|
||||
\end{figure}
|
||||
|
||||
Если очередная лексема равна \texttt{T\_WRITE}, необходимо проверить, что
|
||||
следующая лексема равна \texttt{T\_LPAREN}, затем вызвать метод
|
||||
\texttt{expression()} и проверить, что за выражением в потоке следует лексема
|
||||
\texttt{T\_RPAREN}. Поскольку код, вычисляющий значение выражения, оставляет его
|
||||
значение на вершине стека, для выполнения печати достаточно после вызова
|
||||
\texttt{expression()} сгенерировать инструкцию \texttt{PRINT}.
|
||||
|
||||
\begin{thebibliography}{9}
|
||||
\addcontentsline{toc}{section}{Список литературы}
|
||||
|
||||
\bibitem{dragonbook11}
|
||||
А. Ахо, М. Лам, Р. Сети, Дж. Ульман,
|
||||
\emph{Компиляторы: принципы, технологии и инструментарий}, 2-е изд.
|
||||
М.: Вильямс, 2011.
|
||||
|
||||
\bibitem{karpov05}
|
||||
Ю.Г. Карпов,
|
||||
\emph{Теория и технология программирования. Основы построения трансляторов}.
|
||||
СПб.: БХВ-Петербург, 2005.
|
||||
\end{thebibliography}
|
||||
|
||||
\end{document}
|
||||
|
||||
23
lab4/cmilan/src/Makefile
Normal file
@@ -0,0 +1,23 @@
|
||||
CFLAGS = -Wall -W -Werror -O2
|
||||
LDFLAGS =
|
||||
|
||||
HEADERS = scanner.h \
|
||||
parser.h \
|
||||
codegen.h
|
||||
|
||||
OBJS = main.o \
|
||||
codegen.o \
|
||||
scanner.o \
|
||||
parser.o \
|
||||
|
||||
EXE = cmilan
|
||||
|
||||
$(EXE): $(OBJS) $(HEADERS)
|
||||
$(CXX) $(LDFLAGS) -o $@ $(OBJS)
|
||||
|
||||
.cpp.o:
|
||||
$(CXX) $(CFLAGS) -c $< -o $@
|
||||
|
||||
clean:
|
||||
-@rm -f $(EXE) $(OBJS)
|
||||
|
||||
20
lab4/cmilan/src/cmilan_vs2011/cmilan_vs2011.sln
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 11.00
|
||||
# Visual Studio 2010
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cmilan_vs2011", "cmilan_vs2011.vcxproj", "{1D542465-2019-4442-864E-DC785FA067A8}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Win32 = Debug|Win32
|
||||
Release|Win32 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{1D542465-2019-4442-864E-DC785FA067A8}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{1D542465-2019-4442-864E-DC785FA067A8}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{1D542465-2019-4442-864E-DC785FA067A8}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{1D542465-2019-4442-864E-DC785FA067A8}.Release|Win32.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
92
lab4/cmilan/src/cmilan_vs2011/cmilan_vs2011.vcxproj
Normal file
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{1D542465-2019-4442-864E-DC785FA067A8}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>cmilan_vs2011</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<TargetName>cmilan</TargetName>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<TargetName>cmilan</TargetName>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\codegen.h" />
|
||||
<ClInclude Include="..\parser.h" />
|
||||
<ClInclude Include="..\scanner.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\codegen.cpp" />
|
||||
<ClCompile Include="..\main.cpp" />
|
||||
<ClCompile Include="..\parser.cpp" />
|
||||
<ClCompile Include="..\scanner.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
42
lab4/cmilan/src/cmilan_vs2011/cmilan_vs2011.vcxproj.filters
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\codegen.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\parser.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\scanner.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\codegen.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\main.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\parser.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\scanner.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
3
lab4/cmilan/src/cmilan_vs2011/cmilan_vs2011.vcxproj.user
Normal file
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,7 @@
|
||||
BEGIN
|
||||
i := 1;
|
||||
j := 2;
|
||||
k := i + j;
|
||||
WRITE(k)
|
||||
END
|
||||
|
||||
14
lab4/cmilan/test/comment.mil
Normal file
@@ -0,0 +1,14 @@
|
||||
/* это комментарий */
|
||||
/* это продолжение комментария */
|
||||
|
||||
/*****************************
|
||||
* эта программа печатает 42 *
|
||||
****************************/
|
||||
|
||||
begin /* комментарий в конце строки */
|
||||
/* комментарий в начале строки */ write(42)
|
||||
/* многострочный
|
||||
комментарий */
|
||||
end
|
||||
|
||||
/* комментарий в конце файла */
|
||||
10
lab4/cmilan/test/factorial.mil
Normal file
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
7
lab4/cmilan/test/incdec/inc.mil
Normal file
@@ -0,0 +1,7 @@
|
||||
BEGIN
|
||||
x := 0;
|
||||
write(x++);
|
||||
write(x);
|
||||
write(++x);
|
||||
write(x)
|
||||
END
|
||||
6
lab4/cmilan/test/incdec/incif.mil
Normal file
@@ -0,0 +1,6 @@
|
||||
BEGIN
|
||||
i := 1;
|
||||
j := 2;
|
||||
|
||||
IF i < --j THEN WRITE(100) ELSE WRITE(-100) FI
|
||||
END
|
||||
13
lab4/cmilan/test/incdec/incxy.mil
Normal 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
|
||||
5
lab4/cmilan/test/incdec/invalid.mil
Normal file
@@ -0,0 +1,5 @@
|
||||
BEGIN
|
||||
x := 10;
|
||||
y := 10 - --x; /* Корректно: минус и декремент разделены пробелом */
|
||||
y := 10 ---x; /* Некорректно: три минуса подряд */
|
||||
END
|
||||
3
lab4/cmilan/test/incdec/invalid2.mil
Normal file
@@ -0,0 +1,3 @@
|
||||
BEGIN
|
||||
x := --x++
|
||||
END
|
||||
5
lab4/cmilan/test/invalid.mil
Normal file
@@ -0,0 +1,5 @@
|
||||
BEGIN
|
||||
IF 1 2 THEN WRITE(n) ELSE WRITE(2) FI;
|
||||
X := Y + 1;
|
||||
WRITE (X +)
|
||||
END
|
||||
14
lab4/cmilan/test/invert.mil
Normal file
@@ -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
@@ -0,0 +1,17 @@
|
||||
BEGIN
|
||||
/* Read a number */
|
||||
|
||||
i := READ;
|
||||
|
||||
/* Count from 0 to this number */
|
||||
|
||||
IF i > 0 THEN step := 1 ELSE step := -1 FI;
|
||||
k := 0;
|
||||
WHILE k != i DO
|
||||
WRITE(k);
|
||||
k := k + step
|
||||
OD;
|
||||
|
||||
WRITE(i)
|
||||
END
|
||||
|
||||
14
lab4/cmilan/test/power.mil
Normal file
@@ -0,0 +1,14 @@
|
||||
BEGIN
|
||||
N := READ;
|
||||
P := READ;
|
||||
|
||||
S := 1;
|
||||
|
||||
WHILE P > 0 DO
|
||||
S := S * N;
|
||||
P := P - 1
|
||||
OD;
|
||||
|
||||
WRITE(S)
|
||||
END
|
||||
|
||||
8
lab4/cmilan/test/read2.mil
Normal file
@@ -0,0 +1,8 @@
|
||||
BEGIN
|
||||
IF READ < READ
|
||||
THEN
|
||||
WRITE(1)
|
||||
ELSE
|
||||
WRITE(2)
|
||||
FI
|
||||
END
|
||||
9
lab4/cmilan/test/sum.mil
Normal file
@@ -0,0 +1,9 @@
|
||||
BEGIN
|
||||
i := 8;
|
||||
j := 2;
|
||||
|
||||
WHILE i != j DO
|
||||
IF i < j THEN j := j - i ELSE i := i - j FI
|
||||
OD;
|
||||
WRITE(i)
|
||||
END
|
||||
5
lab4/report/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
**/*
|
||||
!.gitignore
|
||||
!report.tex
|
||||
!img
|
||||
!img/**
|
||||
BIN
lab4/report/img/compiler.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
lab4/report/img/result1.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
lab4/report/img/result2.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
lab4/report/img/result3.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
lab4/report/img/result4.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
lab4/report/img/result5.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
lab4/report/img/scheme.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
lab4/report/img/syntax_diagram_addop.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
lab4/report/img/syntax_diagram_cmpi.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
lab4/report/img/syntax_diagram_digit.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
lab4/report/img/syntax_diagram_expression.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
lab4/report/img/syntax_diagram_factor.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
lab4/report/img/syntax_diagram_ident.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
lab4/report/img/syntax_diagram_letter.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
lab4/report/img/syntax_diagram_mulop.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
lab4/report/img/syntax_diagram_number.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
lab4/report/img/syntax_diagram_program.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
lab4/report/img/syntax_diagram_relation.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
lab4/report/img/syntax_diagram_statement.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
lab4/report/img/syntax_diagram_statementList.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
979
lab4/report/report.tex
Normal 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
10
lab4/vm/doc/changelog.txt
Normal 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
@@ -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
@@ -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
46
lab4/vm/vm/main.c
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
79
lab4/vm/vm/vmparse.tab.h
Normal 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
@@ -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
|
After Width: | Height: | Size: 161 KiB |
8
lab5/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
**/*
|
||||
!.gitignore
|
||||
!report.tex
|
||||
!img
|
||||
!img/**
|
||||
!programm
|
||||
!programm/*.py
|
||||
!programm/*.txt
|
||||
BIN
lab5/img/homsky.png
Normal file
|
After Width: | Height: | Size: 136 KiB |