kvs/квс_лексер.py

227 lines
7.2 KiB
Python
Raw Normal View History

2026-05-02 23:04:46 +03:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
2026-06-20 04:16:50 +03:00
Лексер КВС (русская версия)
2026-05-02 23:04:46 +03:00
Принимает: исходный .квс файл
Выдаёт: текстовый файл с токенами (по одному токену на строку)
Формат вывода: ТИП:ЗНАЧЕНИЕ
2026-06-20 04:16:50 +03:00
Все типы токенов переведены на русский язык:
- СТРОКА строковый литерал
- СЛОВО идентификатор
- ЧИСЛО числовой литерал
- ЗАПЯТАЯ разделитель
- ДВОЕТОЧИЕ для меток
- СК_ОТКР [
- СК_ЗАКР ]
- ПЛЮС +
- МИНУС -
- ЗВЕЗДА *
- НОВСТР маркер конца строки
Поддерживает сложную адресацию:
- [ ] - квадратные скобки
- + - плюс
- - - минус
- * - звёздочка (умножение/масштаб)
- NUMBER - числа (десятичные, шестнадцатеричные, отрицательные)
2026-05-02 23:04:46 +03:00
"""
import sys
def error_unterminated_string(line_num):
return f"Незакрытая кавычка в строке на строке {line_num}"
def is_hex_number(s):
"""Проверяет, является ли строка шестнадцатеричным числом (0x... или 0X...)"""
if len(s) < 3:
return False
if not (s[0] == '0' and (s[1] == 'x' or s[1] == 'X')):
return False
for ch in s[2:]:
if not (('0' <= ch <= '9') or ('a' <= ch <= 'f') or ('A' <= ch <= 'F')):
return False
return True
def is_dec_number(s):
"""Проверяет, является ли строка десятичным числом (возможно с минусом)"""
if not s:
return False
start = 0
if s[0] == '-':
if len(s) == 1:
return False
start = 1
for ch in s[start:]:
if ch < '0' or ch > '9':
return False
return True
2026-05-02 23:04:46 +03:00
def tokenize_line(line, line_num):
2026-06-20 04:16:50 +03:00
"""Разбирает строку в токены (русские названия типов)"""
# Удаляем комментарии
semi = -1
for idx in range(len(line)):
if line[idx] == ';':
semi = idx
break
2026-05-02 23:04:46 +03:00
if semi != -1:
line = line[:semi]
line = line.rstrip()
if not line:
return []
tokens = []
i = 0
n = len(line)
2026-05-02 23:04:46 +03:00
while i < n:
ch = line[i]
# Пропускаем пробелы
if ch == ' ' or ch == '\t':
2026-05-02 23:04:46 +03:00
i += 1
continue
# Строковые литералы
2026-05-02 23:04:46 +03:00
if ch == '"':
i += 1
s = ''
while i < n and line[i] != '"':
if line[i] == '\\' and i + 1 < n:
s += '\\' + line[i + 1]
i += 2
else:
s += line[i]
i += 1
if i >= n:
raise ValueError(error_unterminated_string(line_num))
i += 1
2026-06-20 04:16:50 +03:00
tokens.append(('СТРОКА', s))
2026-05-02 23:04:46 +03:00
continue
# Одиночные символы-разделители
2026-05-02 23:04:46 +03:00
if ch == ',':
2026-06-20 04:16:50 +03:00
tokens.append(('ЗАПЯТАЯ', ','))
2026-05-02 23:04:46 +03:00
i += 1
continue
if ch == ':':
2026-06-20 04:16:50 +03:00
tokens.append(('ДВОЕТОЧИЕ', ':'))
2026-05-02 23:04:46 +03:00
i += 1
continue
2026-06-20 04:16:50 +03:00
# Токены для сложной адресации
if ch == '[':
2026-06-20 04:16:50 +03:00
tokens.append(('СК_ОТКР', '['))
i += 1
continue
if ch == ']':
2026-06-20 04:16:50 +03:00
tokens.append(('СК_ЗАКР', ']'))
i += 1
continue
if ch == '+':
2026-06-20 04:16:50 +03:00
tokens.append(('ПЛЮС', '+'))
i += 1
continue
if ch == '-':
2026-06-20 04:16:50 +03:00
# Проверяем, не является ли минус частью отрицательного числа
if i + 1 < n and (line[i+1].isdigit() or line[i+1] == '0'):
j = i + 1
while j < n and (line[j].isdigit() or line[j] in 'xXabcdefABCDEF'):
j += 1
word = line[i:j]
if is_hex_number(word) or is_dec_number(word):
tokens.append(('ЧИСЛО', word))
i = j
continue
tokens.append(('МИНУС', '-'))
i += 1
continue
if ch == '*':
2026-06-20 04:16:50 +03:00
tokens.append(('ЗВЕЗДА', '*'))
i += 1
continue
# Слова, числа, метки
2026-05-02 23:04:46 +03:00
j = i
while j < n and not (line[j] in ' \t,;:[]+*-'):
2026-05-02 23:04:46 +03:00
j += 1
word = line[i:j]
2026-05-02 23:04:46 +03:00
if word:
# Определяем тип токена
if is_hex_number(word):
2026-06-20 04:16:50 +03:00
tokens.append(('ЧИСЛО', word))
elif is_dec_number(word):
2026-06-20 04:16:50 +03:00
tokens.append(('ЧИСЛО', word))
else:
2026-06-20 04:16:50 +03:00
tokens.append(('СЛОВО', word))
2026-05-02 23:04:46 +03:00
i = j
return tokens
def lex_source(source_text):
"""Лексирует весь исходный текст"""
lines = source_text.split('\n')
all_tokens = []
for line_num, line in enumerate(lines, start=1):
try:
tokens = tokenize_line(line, line_num)
if tokens:
2026-06-20 04:16:50 +03:00
all_tokens.append(('НОВСТР', str(line_num)))
2026-05-02 23:04:46 +03:00
for tok_type, tok_value in tokens:
all_tokens.append((tok_type, tok_value))
except ValueError as e:
print(f"Ошибка лексера: {e}", file=sys.stderr)
sys.exit(1)
return all_tokens
def write_tokens(tokens, output_file):
"""Записывает токены в файл"""
with open(output_file, 'w', encoding='utf-8') as f:
for tok_type, tok_value in tokens:
f.write(f"{tok_type}:{tok_value}\n")
def read_tokens(input_file):
"""Читает токены из файла"""
tokens = []
with open(input_file, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line:
continue
colon_pos = line.find(':')
if colon_pos == -1:
continue
tok_type = line[:colon_pos]
tok_value = line[colon_pos + 1:]
tokens.append((tok_type, tok_value))
return tokens
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Использование: python kvs_lexer.py <вход.квс> <выход.токены>")
sys.exit(1)
input_file = sys.argv[1]
output_file = sys.argv[2]
try:
with open(input_file, 'r', encoding='utf-8') as f:
source_text = f.read()
except FileNotFoundError:
print(f"Ошибка: файл '{input_file}' не найден.", file=sys.stderr)
sys.exit(1)
tokens = lex_source(source_text)
write_tokens(tokens, output_file)
print(f"Лексер: {len(tokens)} токенов записано в {output_file}")