This commit is contained in:
2025-04-15 15:46:24 +03:00
commit 19159eb98d
17 changed files with 908 additions and 0 deletions

35
lab1/programm/example.txt Normal file
View 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)

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

Binary file not shown.

2
lab1/programm/test1.txt Normal file
View 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
View File

@@ -0,0 +1,3 @@
abc + +100E-3.1415
# some_id_ :=:= 100
ThisIdentifierIsTooLong :=:=

4
lab1/programm/test3.txt Normal file
View File

@@ -0,0 +1,4 @@
x := 2.5E+3 + y1 |
z := 3.1E+ | # число с ошибкой
x := x + -2.5E+3 |
1_first # идентификатор с цифры