lab1
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
**/*.pdf
|
||||||
8
lab1/.gitignore
vendored
Normal file
8
lab1/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
**/*
|
||||||
|
!.gitignore
|
||||||
|
!report.tex
|
||||||
|
!img
|
||||||
|
!img/**
|
||||||
|
!programm
|
||||||
|
!programm/*.py
|
||||||
|
!programm/*.txt
|
||||||
BIN
lab1/img/diag.png
Normal file
BIN
lab1/img/diag.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
BIN
lab1/img/result1.png
Normal file
BIN
lab1/img/result1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
BIN
lab1/img/result2.png
Normal file
BIN
lab1/img/result2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
lab1/img/result3.png
Normal file
BIN
lab1/img/result3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
lab1/img/trs.png
Normal file
BIN
lab1/img/trs.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
BIN
lab1/img/wrong.png
Normal file
BIN
lab1/img/wrong.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
35
lab1/programm/example.txt
Normal file
35
lab1/programm/example.txt
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Описание языка
|
||||||
|
|
||||||
|
# Комментарии начинаются с #
|
||||||
|
|
||||||
|
# Идентификаторы
|
||||||
|
abcdefg SomeThing1 _yet_another__id
|
||||||
|
|
||||||
|
# Но не больше 16 символов
|
||||||
|
ThisIdentifierIsTooLong
|
||||||
|
|
||||||
|
# И нельзя наичнать с цифры
|
||||||
|
123NotAllowed
|
||||||
|
|
||||||
|
# Комплексные числа в показательной форме
|
||||||
|
1.5E3 -2E-4 3.0E+2 +2E3.14
|
||||||
|
2E2E22E2
|
||||||
|
|
||||||
|
# Оператор присваивания
|
||||||
|
:=
|
||||||
|
|
||||||
|
# Нельзя дублировать операторы присваивания
|
||||||
|
:=:= :==
|
||||||
|
|
||||||
|
# Арифметические операторы
|
||||||
|
+ - * / ^
|
||||||
|
|
||||||
|
# Скобки
|
||||||
|
()
|
||||||
|
|
||||||
|
# Разделитель выражений
|
||||||
|
|
|
||||||
|
|
||||||
|
# Пример программы
|
||||||
|
alpha:=2E3.1415|beta:=4E0|theta:=alpha+beta+-3E-1.57
|
||||||
|
| dzeta := alpha + (2.1E2.1 - 22E2)
|
||||||
73
lab1/programm/lexeme_types_test.py
Normal file
73
lab1/programm/lexeme_types_test.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from main import LEXEME_TYPES
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"text,expected",
|
||||||
|
[
|
||||||
|
("X", "X"),
|
||||||
|
("alpha1", "alpha1"),
|
||||||
|
(" result123", "result123"),
|
||||||
|
(" __private", "__private"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_identifier_valid(text, expected):
|
||||||
|
lexeme_type = LEXEME_TYPES["IDENTIFIER"]
|
||||||
|
lexem, rest = lexeme_type.consume(text)
|
||||||
|
assert lexem is not None
|
||||||
|
assert lexem.type_name == "IDENTIFIER"
|
||||||
|
assert lexem.text == expected
|
||||||
|
assert rest == ""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"text",
|
||||||
|
[
|
||||||
|
"1alpha",
|
||||||
|
" 1alpha",
|
||||||
|
"alphatoolongidentifier123",
|
||||||
|
":=",
|
||||||
|
"234E+234",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_identifier_invalid(text):
|
||||||
|
lexeme_type = LEXEME_TYPES["IDENTIFIER"]
|
||||||
|
lexem, _ = lexeme_type.consume(text)
|
||||||
|
assert lexem is None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"text,expected",
|
||||||
|
[
|
||||||
|
("1.5E3", "1.5E3"),
|
||||||
|
("1.5E3.14", "1.5E3.14"),
|
||||||
|
("-2E-4", "-2E-4"),
|
||||||
|
(" 3.0E+2", "3.0E+2"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_exp_number_valid(text, expected):
|
||||||
|
lexeme_type = LEXEME_TYPES["EXP_NUMBER"]
|
||||||
|
lexem, rest = lexeme_type.consume(text)
|
||||||
|
assert lexem is not None
|
||||||
|
assert lexem.type_name == "EXP_NUMBER"
|
||||||
|
assert lexem.text == expected
|
||||||
|
assert rest == ""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"text",
|
||||||
|
[
|
||||||
|
"1.5e3",
|
||||||
|
"1alpha",
|
||||||
|
":=",
|
||||||
|
"234E++234",
|
||||||
|
"--234E234",
|
||||||
|
"234EE234",
|
||||||
|
"234E2E34",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_exp_number_invalid(text):
|
||||||
|
lexeme_type = LEXEME_TYPES["EXP_NUMBER"]
|
||||||
|
lexem, _ = lexeme_type.consume(text)
|
||||||
|
assert lexem is None
|
||||||
59
lab1/programm/lexer.py
Normal file
59
lab1/programm/lexer.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Callable, Iterable
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Lexem:
|
||||||
|
text: str
|
||||||
|
type_name: str
|
||||||
|
value: str
|
||||||
|
|
||||||
|
|
||||||
|
class LexemeType:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
pattern: str,
|
||||||
|
value_func: Callable[[str], str] = lambda _: "",
|
||||||
|
):
|
||||||
|
self.name = name
|
||||||
|
self.regex = re.compile(r"\s*(" + pattern + ")")
|
||||||
|
self.value_func = value_func
|
||||||
|
|
||||||
|
def consume(self, text: str) -> tuple[Lexem | None, str]:
|
||||||
|
match = self.regex.match(text)
|
||||||
|
if match:
|
||||||
|
lexeme_text = match.group(1)
|
||||||
|
value = self.value_func(lexeme_text)
|
||||||
|
rest = text[match.end() :]
|
||||||
|
return Lexem(lexeme_text, self.name, value), rest
|
||||||
|
return None, text
|
||||||
|
|
||||||
|
|
||||||
|
class Lexer:
|
||||||
|
def __init__(self, lexeme_types: Iterable[LexemeType]):
|
||||||
|
self.lexeme_types = lexeme_types
|
||||||
|
|
||||||
|
def analyze(self, text: str) -> list[Lexem]:
|
||||||
|
lexems: list[Lexem] = []
|
||||||
|
while text.strip():
|
||||||
|
for lex_type in self.lexeme_types:
|
||||||
|
lexem, new_text = lex_type.consume(text)
|
||||||
|
if lexem:
|
||||||
|
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)
|
||||||
|
err_text = match.group(1) if match else text.strip()
|
||||||
|
print(f"Недопустимая лексема: {err_text}")
|
||||||
|
rest = text[match.end() :] if match else ""
|
||||||
|
return Lexem(err_text, "ERROR", ""), rest
|
||||||
98
lab1/programm/main.py
Normal file
98
lab1/programm/main.py
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import math
|
||||||
|
import os
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
from prettytable import PrettyTable
|
||||||
|
|
||||||
|
from lexer import LexemeType, Lexer
|
||||||
|
|
||||||
|
|
||||||
|
class IdentifierMapper:
|
||||||
|
def __init__(self):
|
||||||
|
self.id_table = {}
|
||||||
|
self.counter = 0
|
||||||
|
|
||||||
|
def __call__(self, lex_text: str) -> str:
|
||||||
|
if lex_text not in self.id_table:
|
||||||
|
self.id_table[lex_text] = f"{lex_text} : {self.counter}"
|
||||||
|
self.counter += 1
|
||||||
|
return self.id_table[lex_text]
|
||||||
|
|
||||||
|
|
||||||
|
def exp_form_to_complex(exp_str: str) -> str:
|
||||||
|
# Разделяем строку по 'E'
|
||||||
|
base, exponent = exp_str.split("E")
|
||||||
|
r = float(base)
|
||||||
|
phi = float(exponent)
|
||||||
|
|
||||||
|
# Преобразуем в алгебраическую форму
|
||||||
|
a = r * math.cos(phi)
|
||||||
|
b = r * math.sin(phi)
|
||||||
|
|
||||||
|
return f"{a:.2f} + i * {b:.2f}"
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
),
|
||||||
|
# 2. Комплексные числа в показательной форме (на самом деле — числа в экспоненциальной форме)
|
||||||
|
"COMPLEX": LexemeType(
|
||||||
|
# "COMPLEX", r"[+-]?\d+(?:\.\d+)?E[+-]?\d+(?!E)", exp_form_to_complex
|
||||||
|
"COMPLEX",
|
||||||
|
r"[+-]?\d+(?:\.\d+)?E[+-]?\d+(?:\.\d+)?(?!E)",
|
||||||
|
exp_form_to_complex,
|
||||||
|
),
|
||||||
|
# 3. Оператор присваивания :=
|
||||||
|
"ASSIGN": LexemeType("ASSIGN", r"\:=(?![\:=])"),
|
||||||
|
# 4. Арифметические операторы
|
||||||
|
"ARITHMETIC_OP": LexemeType("ARITHMETIC_OP", r"[+\-*/^]"),
|
||||||
|
# 5. Скобки
|
||||||
|
"PAREN": LexemeType("PAREN", r"[()]"),
|
||||||
|
# 6. Разделитель выражений |
|
||||||
|
"SEPARATOR": LexemeType("SEPARATOR", r"\|"),
|
||||||
|
# 7. Комментарии от # до конца строки
|
||||||
|
"COMMENT": LexemeType("COMMENT", r"\#.*"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def analyze_and_print_table(code: str):
|
||||||
|
lexer = Lexer(LEXEME_TYPES.values())
|
||||||
|
lexemes = lexer.analyze(code)
|
||||||
|
|
||||||
|
table = PrettyTable(["Лексема", "Тип лексемы", "Значение"])
|
||||||
|
for l in lexemes:
|
||||||
|
table.add_row([l.text, l.type_name, l.value])
|
||||||
|
|
||||||
|
print(table)
|
||||||
|
print()
|
||||||
|
|
||||||
|
LEXEME_TYPES["IDENTIFIER"].value_func = IdentifierMapper()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
while True:
|
||||||
|
# Запрашиваем название файла
|
||||||
|
file_name = input(
|
||||||
|
"Введите название файла для анализа (или 'exit' для выхода): "
|
||||||
|
)
|
||||||
|
|
||||||
|
if file_name.lower() == "exit":
|
||||||
|
print("Завершаю программу.")
|
||||||
|
break
|
||||||
|
|
||||||
|
if not os.path.isfile(file_name):
|
||||||
|
print(f"Файл '{file_name}' не найден. Попробуйте снова.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Читаем содержимое файла
|
||||||
|
with open(file_name, "r", encoding="utf-8") as file:
|
||||||
|
code = file.read()
|
||||||
|
|
||||||
|
# Анализируем и выводим таблицу
|
||||||
|
analyze_and_print_table(code)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
BIN
lab1/programm/requirements.txt
Normal file
BIN
lab1/programm/requirements.txt
Normal file
Binary file not shown.
2
lab1/programm/test1.txt
Normal file
2
lab1/programm/test1.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
alpha:=2E3.1415|beta:=4E0|theta:=alpha+beta+-3E-1.57
|
||||||
|
| dzeta := alpha + (2.1E2.1 - 22E2)
|
||||||
3
lab1/programm/test2.txt
Normal file
3
lab1/programm/test2.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
abc + +100E-3.1415
|
||||||
|
# some_id_ :=:= 100
|
||||||
|
ThisIdentifierIsTooLong :=:=
|
||||||
4
lab1/programm/test3.txt
Normal file
4
lab1/programm/test3.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
x := 2.5E+3 + y1 |
|
||||||
|
z := 3.1E+ | # число с ошибкой
|
||||||
|
x := x + -2.5E+3 |
|
||||||
|
1_first # идентификатор с цифры
|
||||||
625
lab1/report.tex
Normal file
625
lab1/report.tex
Normal file
@@ -0,0 +1,625 @@
|
|||||||
|
\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{Лабораторная работа №1}\\
|
||||||
|
\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}{Введение}
|
||||||
|
Лабораторная №1 по дисциплине <<Математическая логика>> заключается в следующем. Необходимо написать программу, которая выполняет лексический анализ входного текста в
|
||||||
|
соответствии с вариантом задания и порождает таблицу лексем с указанием их типов и значений.
|
||||||
|
Также необходимо подготовить несколько вариантов программы в виде текста на входном языке.
|
||||||
|
Программа должна выдавать сообщения о наличие во входном тексте ошибок, которые
|
||||||
|
могут быть обнаружены на этапе лексического анализа.
|
||||||
|
Длина идентификатора и строковых констант ограничена 16 символами, только
|
||||||
|
латиница. Программа должна допускать наличие комментариев неограниченной длины
|
||||||
|
во входном файле.
|
||||||
|
|
||||||
|
\textit{Вариант 15}. Входной язык содержит арифметические выражения, разделенные символом |
|
||||||
|
(вертикальная полоса). Арифметические выражения состоят из идентификаторов,
|
||||||
|
комплексных чисел (в показательной форме), знака присваивания (:=), знаков операций +,
|
||||||
|
–, *, /, \textasciicircum и круглых скобок.
|
||||||
|
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\section {Математическое описание}
|
||||||
|
|
||||||
|
\subsection{Структура транслятора}
|
||||||
|
|
||||||
|
Транслятор выполняет преобразование исходного текста $L$ на каком-либо языке в какую-либо структуру данных с сохранением смысла входного текста.
|
||||||
|
|
||||||
|
Транслятор можно разделить на три основные части (см. Рис.~\ref{fig:trs}):
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\item Лексический анализатор -- представляет исходную программу как последовательность лексем. Лексемы - минимальные единицы языка, имеющие смысл.
|
||||||
|
\item Распознаватель -- строит структуру исходного текста (например, в виде дерева) по полученной от лексического анализатора цепочке лексем.
|
||||||
|
\item Генератор -- использует построенную структуру для создания выходных данных, отражающих семантику входной цепочки.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\begin{figure}[h!]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=0.8\linewidth]{img/trs.png}
|
||||||
|
\caption{Структура транслятора.}
|
||||||
|
\label{fig:trs}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
В данной лабораторной работе необходимо написать программу лексический анализатор для заданного языка.
|
||||||
|
|
||||||
|
\subsection{Формальное определение лексического анализа}
|
||||||
|
|
||||||
|
Лексический анализ --- первая фаза трансляции программ на языках программирования, цель которой состоит в преобразовании входного текста программы в последовательность структурно значимых элементов --- \textit{лексем}.
|
||||||
|
|
||||||
|
Лексема --- это минимальная единица языка программирования, обладающая самостоятельным смыслом. В отличие от естественных языков, где лексемами выступают слова или словоформы, в языках программирования лексемами являются имена, ключевые (служебные) слова, числовые и строковые константы, а также операторы (в том числе составные, например, \verb|:=|).
|
||||||
|
|
||||||
|
Цели лексического анализа включают:
|
||||||
|
\begin{itemize}
|
||||||
|
\item определение класса каждой лексемы (например, идентификатор, число, строка, оператор и т.п.);
|
||||||
|
\item определение значения лексемы;
|
||||||
|
\item преобразование значений некоторых лексем (например, чисел) во внутреннее представление;
|
||||||
|
\item фильтрация и классификация символов для облегчения анализа.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
В зависимости от класса, значение лексемы может быть преобразовано во внутреннее представление уже на этапе лексического анализа. Например, числа преобразуют в двоичное машинное представление, что обеспечивает более компактное хранение и проверку правильности диапазона на ранней стадии трансляции.
|
||||||
|
|
||||||
|
Перед лексическим анализом производится этап транслитерации, на котором каждому символу сопоставляется его класс. Типичные классы символов:
|
||||||
|
\begin{itemize}
|
||||||
|
\item \textit{буква} --- символы алфавита;
|
||||||
|
\item \textit{цифра} --- символы от \verb|0| до \verb|9|;
|
||||||
|
\item \textit{разделитель} --- пробел, перевод строки, табуляция и др.;
|
||||||
|
\item \textit{игнорируемый} --- символы, не несущие смысловой нагрузки (например, сигнальные коды);
|
||||||
|
\item \textit{запрещённый} --- символы, не входящие в алфавит языка;
|
||||||
|
\item \textit{прочие} --- остальные символы, не попавшие в предыдущие категории.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
Для данного варианта лабораторной работы были выбраны следующие классы лексем:
|
||||||
|
|
||||||
|
\begin{enumerate}
|
||||||
|
\item \textbf{Идентификаторы} --- последовательности латинских букв, цифр и символов нижнего подчёркивания. Не могут начинаться с цифры. Максимальная длина идентификатора --- 16 символов. Примеры: \texttt{X}, \texttt{alpha1}, \texttt{\_private}.
|
||||||
|
|
||||||
|
\item \textbf{Комплексные числа в показательной форме} --- числа вида \texttt{aEb}, где \texttt{a} и \texttt{b} --- действительные числа (целые или с плавающей точкой), знак 'E' указывает на показатель степени. Примеры: \texttt{1.5E3}, \texttt{-2E-4}, \texttt{3.0E+2}.
|
||||||
|
|
||||||
|
\item \textbf{Оператор присваивания} --- составной символ \texttt{:=}, обозначающий операцию присваивания значения.
|
||||||
|
|
||||||
|
\item \textbf{Арифметические операторы} --- знаки \texttt{+}, \texttt{-}, \texttt{*}, \texttt{/}, \texttt{\^} для выполнения соответствующих математических операций.
|
||||||
|
|
||||||
|
\item \textbf{Скобки} --- круглые скобки \texttt{(} и \texttt{)}, используемые для задания порядка вычислений в арифметических выражениях.
|
||||||
|
|
||||||
|
\item \textbf{Разделитель выражений} --- символ вертикальной черты \texttt{|}, отделяющий одно арифметическое выражение от другого.
|
||||||
|
|
||||||
|
\item \textbf{Комментарии} --- начинаются с символа \texttt{\#} и продолжаются до конца строки. Комментарии могут быть произвольной длины и содержать любые символы кроме переноса строки. Пример: \texttt{\# это комментарий}.
|
||||||
|
\item \textbf{Ошибки} — это случаи, когда входной текст нарушает правила формирования лексем. К таким ошибкам относятся идентификаторы, длина которых превышает 16 символов, идентификаторы, начинающиеся с цифры, и прочие неопознанные лексемы.
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
|
|
||||||
|
\subsection{Формальная модель лексического анализатора}
|
||||||
|
Лексический анализатор (лексер) — это конечный автомат, который преобразует входную строку символов в последовательность токенов. Формально его можно описать следующим образом:
|
||||||
|
|
||||||
|
Пусть заданы:
|
||||||
|
\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{Регулярные выражения}
|
||||||
|
Регулярные выражения -- формальный язык шаблонов для поиска и выполнения манипуляций с подстроками в тексте. Регулярное выражение - это формула (pattern, шаблон), задающая правило поиска подстрок в потоке символов.
|
||||||
|
|
||||||
|
Все лексемы языка можно описать регулярными выражениями и для каждой задать
|
||||||
|
семантику (какую функцию вызывать, если распознана эта лексема – например,
|
||||||
|
обратиться к таблице служебных слов и искать там, если нет, то ... ).
|
||||||
|
|
||||||
|
Классы лексем для этого варианта лабораторной работы определяются через регулярные выражения следующим образом.
|
||||||
|
|
||||||
|
\begin{enumerate}
|
||||||
|
\item \textbf{Идентификаторы.}
|
||||||
|
\begin{center}
|
||||||
|
$R_{\text{Identifier}}$ = [A-Za-z\_][A-Za-z\_0-9]\{0,15\}(?![A-Za-z\_0-9])
|
||||||
|
\end{center}
|
||||||
|
|
||||||
|
Первый символ — латинская буква или подчёркивание. Далее допускается до 15 символов, включая буквы, цифры и подчёркивания. В конце используется \textit{negative lookahead}, чтобы за идентификатором не следовал символ, допустимый внутри него (иначе это была бы часть более длинного идентификатора).
|
||||||
|
|
||||||
|
\item \textbf{Комплексные числа в показательной форме.}
|
||||||
|
\begin{center}
|
||||||
|
$R_{\text{Complex}}$ = [+-]?\textbackslash{}d+(?:\textbackslash{}.\textbackslash{}d+)?E[+-]?\textbackslash{}d+(?:\textbackslash{}.\textbackslash{}d+)?(?!E)
|
||||||
|
\end{center}
|
||||||
|
|
||||||
|
Опциональный знак в начале числа, целое или десятичное число до \texttt{E}, затем обязательный символ \texttt{E}, снова опциональный знак, и целое или десятичное число. В конце используется \textit{negative lookahead}, чтобы за числом не мог сразу следовать символ \texttt{E}.
|
||||||
|
|
||||||
|
\item \textbf{Оператор присваивания.}
|
||||||
|
\begin{center}
|
||||||
|
$R_{\text{Assign}}$ = \textbackslash{}:=(?![\textbackslash{}:=])
|
||||||
|
\end{center}
|
||||||
|
|
||||||
|
\item \textbf{Арифметические операторы.}
|
||||||
|
\begin{center}
|
||||||
|
$R_{\text{ArithmeticOp}}$ = [+\textbackslash{}-\textbackslash{}*/\textbackslash{}\^{}]
|
||||||
|
\end{center}
|
||||||
|
|
||||||
|
\item \textbf{Скобки.}
|
||||||
|
\begin{center}
|
||||||
|
$R_{\text{Paren}}$ = [()]
|
||||||
|
\end{center}
|
||||||
|
|
||||||
|
\item \textbf{Разделитель выражений.}
|
||||||
|
\begin{center}
|
||||||
|
$R_{\text{Separator}}$ = \textbackslash{}\textbar
|
||||||
|
\end{center}
|
||||||
|
|
||||||
|
\item \textbf{Комментарии.}
|
||||||
|
\begin{center}
|
||||||
|
$R_{\text{Comment}}$ = \textbackslash{}\#.*
|
||||||
|
\end{center}
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
|
В класс ошибок попадают все лексемы, неподошедшие ни под какой другой класс. Некорректная лексема считывается с помощью следующего регулярного выражения:
|
||||||
|
\begin{center}
|
||||||
|
$R_{\text{Error}}$ = \textbackslash{}S+
|
||||||
|
\end{center}
|
||||||
|
Это регулярное выражение считывает все непробельные символы до первого пробельного символа.
|
||||||
|
|
||||||
|
\subsection{Алгоритм лексического анализа}
|
||||||
|
Алгоритм лексического анализа состоит из следующих шагов:
|
||||||
|
\begin{enumerate}
|
||||||
|
\item \textbf{Инициализация:} указатель устанавливается на начало строки.
|
||||||
|
\item \textbf{Определение типа лексемы:} регулярные выражения, соответствующие различным типам лексем, перебираются в цикле до первого совпадения. Если регулярное выражение не найдено, то применяется регулярное выражение $R_{\text{Error}}$.
|
||||||
|
\item \textbf{Извлечение лексемы:} с помощью регулярного выражения лексема извлекается из строки и сохраняется в списке лексем с указанием типа лексемы.
|
||||||
|
\item \textbf{Вычисление значения лексемы:} значение каждой лексемы определяется посредством вызова функции обработчика, соответствующей типу лексемы. Функция обработчик получает на вход текст лексемы и возвращает некоторый объект, представляющий её значение.
|
||||||
|
\item \textbf{Сохранение лексемы:} текст лексемы, её тип и значение сохраняются в итоговый список лексем.
|
||||||
|
\item \textbf{Сдвиг указателя:} указатель сдвигается на следующий непросмотренный символ.
|
||||||
|
\item \textbf{Завершение анализа:} алгоритм завершается, когда указатель сдвигается за пределы строки. То есть, когда вся строка разобрана на лексемы.
|
||||||
|
\end{enumerate}
|
||||||
|
|
||||||
|
Сложность такого алгортма без учёта сложности функций вычисления значений лексем -- $O(n)$, где $n$ -- количество символов в исходном тексте программы.
|
||||||
|
|
||||||
|
\subsection{Синтаксическая диаграмма}
|
||||||
|
|
||||||
|
Синтаксическая диаграмма реализованной программы представлена на Рис.~\ref{fig:diag}.
|
||||||
|
|
||||||
|
\begin{figure}[h!]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=0.9\linewidth]{img/diag.png}
|
||||||
|
\caption{Синтаксическая диаграмма реализованной программы.}
|
||||||
|
\label{fig:diag}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\phantom{text}
|
||||||
|
\newpage
|
||||||
|
\section{Особенности реализации}
|
||||||
|
\subsection{Общая структура программы}
|
||||||
|
Программа состоит из двух файлов:
|
||||||
|
\begin{itemize}
|
||||||
|
\item \texttt{lexer.py} -- содержит небольшую библиотеку для создания лексических анализаторов для произвольных языков. Файл содержит классы \texttt{Lexem}, \texttt{LexemeType} и \texttt{Lexer}.
|
||||||
|
\item \texttt{main.py} -- файл содержит список типов лексем и их регулярных выражений -- (\texttt{LEXEME\_TYPES}), а также два обработчика для вычисления значений лексем -- \texttt{exp\_form\_to\_complex} и \texttt{IdentifierMapper}. Также в файле определены функции: \texttt{analyze\_and\_print\_table} -- запускает лексический анализ и выводит результаты в консоль в виде таблицы, \texttt{main} -- обрабатывает пользовательский ввод.
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\subsection{Класс Lexem}
|
||||||
|
|
||||||
|
Класс Lexem это простой датакласс для представления лексем. Код определения класса представлен в листинге~\ref{lst:Lexem}. В классе есть всего три поля строкового типа:
|
||||||
|
\texttt{text} -- текст лексемы, \texttt{type\_name} -- название типа лексемы, \texttt{value} -- значение лексемы.
|
||||||
|
|
||||||
|
\begin{lstlisting}[caption={Определение класса Lexem.}, label={lst:Lexem}]
|
||||||
|
@dataclass
|
||||||
|
class Lexem:
|
||||||
|
text: str
|
||||||
|
type_name: str
|
||||||
|
value: str
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
|
||||||
|
\subsection{Класс LexemeType}
|
||||||
|
Класс LexemeType представляет собой тип лексемы. Код определения класса представлен в листинге~\ref{lst:LexemeType}. В классе всего три поля:
|
||||||
|
\texttt{name} -- строка с названием типа лексемы, \texttt{regex} -- регулярное выражение, соответствующее типу лексемы, \texttt{value\_func} -- функция для вычисления значения лексемы.
|
||||||
|
|
||||||
|
В классе определён единственный метод \texttt{consume}. Он принимает два параметра: \texttt{self} -- ссылку на объект класса, и строку \texttt{text} с текстом программы, из которого нужно попытаться извлечь лексему. Возвращает кортеж из двух элементов: объект класса Lexem, если лексему удалось извлечь, иначе None, и строку с текстом программы, оставшимся после извлечения лексемы.
|
||||||
|
|
||||||
|
\begin{lstlisting}[caption={Определение класса LexemeType.}, label={lst:LexemeType}]
|
||||||
|
class LexemeType:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
pattern: str,
|
||||||
|
value_func: Callable[[str], str] = lambda _: "",
|
||||||
|
):
|
||||||
|
self.name = name
|
||||||
|
self.regex = re.compile(r"\s*(" + pattern + ")")
|
||||||
|
self.value_func = value_func
|
||||||
|
|
||||||
|
def consume(self, text: str) -> tuple[Lexem | None, str]:
|
||||||
|
match = self.regex.match(text)
|
||||||
|
if match:
|
||||||
|
lexeme_text = match.group(1)
|
||||||
|
value = self.value_func(lexeme_text)
|
||||||
|
rest = text[match.end() :]
|
||||||
|
return Lexem(lexeme_text, self.name, value), rest
|
||||||
|
return None, text
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\subsection{Класс Lexer}
|
||||||
|
Класс Lexer представляет собой лексический анализатор. Код определения класса представлен в листинге~\ref{lst:Lexer}. В классе всего одно поле:
|
||||||
|
\texttt{lexeme\_types} -- список объектов класса \texttt{LexemeType}.
|
||||||
|
|
||||||
|
В классе определено два метода: \texttt{analyze} и вспомогательный \texttt{\_consume\_error}.
|
||||||
|
|
||||||
|
Метод \texttt{analyze} выполняет лексический разбор входного текста. Он принимает строку \texttt{text}, содержащую текст программы, и возвращает список объектов типа \texttt{Lexem}. Метод поочерёдно применяет каждый тип лексемы из \texttt{lexeme\_types}, пытаясь извлечь очередную лексему. Если хотя бы один тип лексемы успешно извлекает лексему, она добавляется в результат, а оставшийся текст анализируется далее. Если ни одна лексема не подошла, вызывается метод \texttt{\_consume\_error} для обработки ошибки.
|
||||||
|
|
||||||
|
Метод \texttt{\_consume\_error} используется для обработки ситуаций, когда входной фрагмент не соответствует ни одному из допустимых шаблонов. Он находит первую непробельную последовательность символов, сообщает об ошибке в консоль и создаёт лексему с типом \texttt{"ERROR"}. Возвращает эту ошибочную лексему и оставшийся текст.
|
||||||
|
|
||||||
|
\begin{lstlisting}[caption={Определение класса Lexer.}, label={lst:Lexer}]
|
||||||
|
class Lexer:
|
||||||
|
def __init__(self, lexeme_types: Iterable[LexemeType]):
|
||||||
|
self.lexeme_types = lexeme_types
|
||||||
|
|
||||||
|
def analyze(self, text: str) -> list[Lexem]:
|
||||||
|
lexems: list[Lexem] = []
|
||||||
|
while text.strip():
|
||||||
|
for lex_type in self.lexeme_types:
|
||||||
|
lexem, new_text = lex_type.consume(text)
|
||||||
|
if lexem:
|
||||||
|
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)
|
||||||
|
err_text = match.group(1) if match else text.strip()
|
||||||
|
print(f"Недопустимая лексема: {err_text}")
|
||||||
|
rest = text[match.end() :] if match else ""
|
||||||
|
return Lexem(err_text, "ERROR", ""), rest
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\subsection{Класс \texttt{IdentifierMapper}}
|
||||||
|
Класс \texttt{IdentifierMapper} используется для сопоставления идентификаторов с уникальными значениями. Код класса приведён в листинге~\ref{lst:IdentifierMapper}. В классе определены два поля: \texttt{id\_table: dict[str, str]}~--- таблица, содержащая отображения идентификаторов на строки с их порядковыми номерами, и \texttt{counter: int}~--- счётчик, увеличиваемый при каждом новом идентификаторе.
|
||||||
|
|
||||||
|
Метод \texttt{\_\_call\_\_(self, lex\_text: str)~$\rightarrow$~str} проверяет, был ли ранее встречен переданный идентификатор. Если нет~--- добавляет его в таблицу, присваивая уникальный номер, и возвращает соответствующее строковое представление.
|
||||||
|
|
||||||
|
\begin{lstlisting}[caption={Класс IdentifierMapper.}, label={lst:IdentifierMapper}]
|
||||||
|
class IdentifierMapper:
|
||||||
|
def __init__(self):
|
||||||
|
self.id_table = {}
|
||||||
|
self.counter = 0
|
||||||
|
|
||||||
|
def __call__(self, lex_text: str) -> str:
|
||||||
|
if lex_text not in self.id_table:
|
||||||
|
self.id_table[lex_text] = f"{lex_text} : {self.counter}"
|
||||||
|
self.counter += 1
|
||||||
|
return self.id_table[lex_text]
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\subsection{Функция \texttt{exp\_form\_to\_complex}}
|
||||||
|
Функция \texttt{exp\_form\_to\_complex}, показанная в листинге~\ref{lst:expToComplex}, принимает один параметр \texttt{exp\_str: str}~--- строку с числом в экспоненциальной форме. Возвращает строку \texttt{str}, представляющую это число в виде комплексного числа в алгебраической форме \( a + i \cdot b \), где \( a = r \cdot \cos(\varphi) \), \( b = r \cdot \sin(\varphi) \).
|
||||||
|
|
||||||
|
\begin{lstlisting}[caption={Функция exp\_form\_to\_complex.}, label={lst:expToComplex}]
|
||||||
|
def exp_form_to_complex(exp_str: str) -> str:
|
||||||
|
base, exponent = exp_str.split("E")
|
||||||
|
r = float(base)
|
||||||
|
phi = float(exponent)
|
||||||
|
|
||||||
|
a = r * math.cos(phi)
|
||||||
|
b = r * math.sin(phi)
|
||||||
|
|
||||||
|
return f"{a:.2f} + i * {b:.2f}"
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\subsection{Список \texttt{LEXEME\_TYPES}}
|
||||||
|
Словарь \texttt{LEXEME\_TYPES} (листинг~\ref{lst:LexemeTypes}) содержит определения всех типов лексем, используемых в лексическом анализе. Ключами являются строковые названия типов, значениями~--- экземпляры класса \texttt{LexemeType}. Каждая лексема описывается с помощью регулярного выражения и, при необходимости, функцией обработки значения (например, \texttt{IdentifierMapper} или \texttt{exp\_form\_to\_complex}).
|
||||||
|
|
||||||
|
\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()
|
||||||
|
),
|
||||||
|
"COMPLEX": LexemeType(
|
||||||
|
"COMPLEX",
|
||||||
|
r"[+-]?\d+(?:\.\d+)?E[+-]?\d+(?:\.\d+)?(?!E)",
|
||||||
|
exp_form_to_complex,
|
||||||
|
),
|
||||||
|
"ASSIGN": LexemeType("ASSIGN", r"\:=(?![\:=])"),
|
||||||
|
"ARITHMETIC_OP": LexemeType("ARITHMETIC_OP", r"[+\-*/^]"),
|
||||||
|
"PAREN": LexemeType("PAREN", r"[()]"),
|
||||||
|
"SEPARATOR": LexemeType("SEPARATOR", r"\|"),
|
||||||
|
"COMMENT": LexemeType("COMMENT", r"\#.*"),
|
||||||
|
}
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\subsection{Функция \texttt{analyze\_and\_print\_table}}
|
||||||
|
Функция \texttt{analyze\_and\_print\_table}, представленная в листинге~\ref{lst:AnalyzePrint}, принимает один параметр \texttt{code: str}~--- строку с текстом программы. Она создаёт объект \texttt{Lexer}, выполняет анализ текста на лексемы и выводит результат в виде таблицы, используя библиотеку \texttt{PrettyTable}. Каждая строка таблицы содержит текст лексемы, её тип и вычисленное значение.
|
||||||
|
|
||||||
|
\begin{lstlisting}[caption={Функция analyze\_and\_print\_table.}, label={lst:AnalyzePrint}]
|
||||||
|
def analyze_and_print_table(code: str):
|
||||||
|
lexer = Lexer(LEXEME_TYPES.values())
|
||||||
|
lexemes = lexer.analyze(code)
|
||||||
|
|
||||||
|
table = PrettyTable(["Лексема", "Тип лексемы", "Значение"])
|
||||||
|
for l in lexemes:
|
||||||
|
table.add_row([l.text, l.type_name, l.value])
|
||||||
|
|
||||||
|
print(table)
|
||||||
|
print()
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\subsection{Функция \texttt{main}}
|
||||||
|
Функция \texttt{main} (листинг~\ref{lst:Main}) организует интерактивный интерфейс пользователя. Не принимает параметров. Пользователь вводит строку \texttt{file\_name}, содержащую путь к файлу. Если файл существует, его содержимое читается и передаётся в функцию \texttt{analyze\_and\_print\_table}. При вводе строки \texttt{"exit"} программа завершает выполнение. Если файл не найден, пользователю выводится сообщение об ошибке.
|
||||||
|
|
||||||
|
\begin{lstlisting}[caption={Функция main.}, label={lst:Main}]
|
||||||
|
def main():
|
||||||
|
while True:
|
||||||
|
file_name = input(
|
||||||
|
"Введите название файла для анализа (или 'exit' для выхода): "
|
||||||
|
)
|
||||||
|
|
||||||
|
if file_name.lower() == "exit":
|
||||||
|
print("Завершаю программу.")
|
||||||
|
break
|
||||||
|
|
||||||
|
if not os.path.isfile(file_name):
|
||||||
|
print(f"Файл '{file_name}' не найден. Попробуйте снова.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
with open(file_name, "r", encoding="utf-8") as file:
|
||||||
|
code = file.read()
|
||||||
|
|
||||||
|
analyze_and_print_table(code)
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\section{Результаты работы программы}
|
||||||
|
На Рис.~\ref{fig:result1}-\ref{fig:result3} представлены результаты работы лексического анализатора, запущенного на файлах с тремя различными программами (см. листинги~\ref{lst:prog1}-\ref{lst:prog3}).
|
||||||
|
|
||||||
|
\begin{lstlisting}[caption={Программа 1.}, label={lst:prog1}]
|
||||||
|
alpha:=2E3.1415|beta:=4E0|theta:=alpha+beta+-3E-1.57
|
||||||
|
| dzeta := alpha + (2.1E2.1 - 22E2)
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\begin{figure}[h!]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=0.6\linewidth]{img/result1.png}
|
||||||
|
\caption{Результат работы лексического анализатора на программе 1 (листинг~\ref{lst:prog1}).}
|
||||||
|
\label{fig:result1}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\begin{lstlisting}[caption={Программа 2.}, label={lst:prog2}]
|
||||||
|
abc + +100E-3.1415
|
||||||
|
# some_id_ :=:= 100
|
||||||
|
ThisIdentifierIsTooLong :=:=
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\begin{figure}[h!]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=0.8\linewidth]{img/result2.png}
|
||||||
|
\caption{Результат работы лексического анализатора на программе 2 (листинг~\ref{lst:prog2}).}
|
||||||
|
\label{fig:result2}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\begin{lstlisting}[caption={Программа 3.}, label={lst:prog3}]
|
||||||
|
x := 2.5E+3 + y1 |
|
||||||
|
z := 3.1E+ | # число с ошибкой
|
||||||
|
x := x + -2.5E+3 |
|
||||||
|
1_first # идентификатор с цифры
|
||||||
|
\end{lstlisting}
|
||||||
|
|
||||||
|
\begin{figure}[h!]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=0.8\linewidth]{img/result3.png}
|
||||||
|
\caption{Результат работы лексического анализатора на программе 3 (листинг~\ref{lst:prog3}).}
|
||||||
|
\label{fig:result3}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
|
||||||
|
\begin{figure}[h!]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=0.9\linewidth]{img/wrong.png}
|
||||||
|
\caption{Реакция программы на некорректный пользовательский ввод.}
|
||||||
|
\label{fig:wrong}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
На Рис.~\ref{fig:wrong} представлена реакция программы на некорректный пользовательский ввод.
|
||||||
|
|
||||||
|
|
||||||
|
\newpage
|
||||||
|
\section*{Заключение}
|
||||||
|
\addcontentsline{toc}{section}{Заключение}
|
||||||
|
В ходе выполнения лабораторной работы была написана программа, которая выполняет лексический анализ входного текста в соответствии с вариантом лабораторной работы и выводит в консоль таблицу лексем с указанием их типов и значений. Также программа отдельно выводит сообщения об ошибочных лексемах.
|
||||||
|
|
||||||
|
Из достоинств выполнения лабораторной работы можно выделить структурирование кода за счёт использования ООП. Вся логика работы лексического анализатора вынесена в отдельный класс \texttt{Lexer}. Логика работы с типами лексем в класс \texttt{LexemeType}. Также в качестве достоинства можно отметить удобочитаемый вывод таблиц в консоли с помощью библиотеки \texttt{PrettyTable}.
|
||||||
|
|
||||||
|
Можно выделить два недостатка текущей реализации. Во-первых, классы \texttt{Lexer} и \texttt{LexemeType} не поддерживают лексемы, в состав которых входят пробельные символы. Это ограничение также касается и лексем из класса <<Ошибка>>, поэтому предложенный лексический анализатор не может обнаружить, например, следующую ошибку: <<\texttt{:= :=}>> -- между повторяющимися операторами присваивания находится пробельный символ. Во-вторых, алгоритм, лежащий в основе реализации лексического анализатора, не предусматривает анализ структуры входного текста, поэтому способен обнаружить ошибки только на уровне отдельных лексем.
|
||||||
|
|
||||||
|
Функционал программы несложно масштабировать. Классы \texttt{Lexer}, \texttt{LexemeType} и \texttt{Lexem} получились достаточно универсальными. Для того, чтобы добавить обработку дополнительных типов лексем, достаточно расширить словарь \texttt{LEXEME\_TYPES}. Кроме того, класс \texttt{LexemeType} позволяет использовать любые \texttt{Callable} объекты в качестве обработчиков для получения значений лексем. Таким образом код, написанный для решения конкретного варианта лабораторной работы, легко можно адаптировать для решения других вариантов.
|
||||||
|
|
||||||
|
На выполнение лабораторной работы ушло около 12 часов. Работа была выполнена в среде разработки Visual Studio Code. Программа написана на Python версии 3.13.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
\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}
|
||||||
Reference in New Issue
Block a user