lab1
This commit is contained in:
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 # идентификатор с цифры
|
||||
Reference in New Issue
Block a user