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-05-11 15:08:03 +03:00
|
|
|
|
|
2026-06-20 04:16:50 +03:00
|
|
|
|
Все типы токенов переведены на русский язык:
|
|
|
|
|
|
- СТРОКА → строковый литерал
|
|
|
|
|
|
- СЛОВО → идентификатор
|
|
|
|
|
|
- ЧИСЛО → числовой литерал
|
|
|
|
|
|
- ЗАПЯТАЯ → разделитель
|
|
|
|
|
|
- ДВОЕТОЧИЕ → для меток
|
|
|
|
|
|
- СК_ОТКР → [
|
|
|
|
|
|
- СК_ЗАКР → ]
|
|
|
|
|
|
- ПЛЮС → +
|
|
|
|
|
|
- МИНУС → -
|
|
|
|
|
|
- ЗВЕЗДА → *
|
|
|
|
|
|
- НОВСТР → маркер конца строки
|
|
|
|
|
|
|
2026-05-11 15:08:03 +03:00
|
|
|
|
Поддерживает сложную адресацию:
|
|
|
|
|
|
- [ ] - квадратные скобки
|
|
|
|
|
|
- + - плюс
|
|
|
|
|
|
- - - минус
|
|
|
|
|
|
- * - звёздочка (умножение/масштаб)
|
|
|
|
|
|
- NUMBER - числа (десятичные, шестнадцатеричные, отрицательные)
|
2026-05-02 23:04:46 +03:00
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
|
|
def error_unterminated_string(line_num):
|
|
|
|
|
|
return f"Незакрытая кавычка в строке на строке {line_num}"
|
|
|
|
|
|
|
2026-05-11 15:08:03 +03:00
|
|
|
|
def is_hex_number(s):
|
|
|
|
|
|
"""Проверяет, является ли строка шестнадцатеричным числом (0x... или 0X...)"""
|
2026-05-17 12:21:28 +03:00
|
|
|
|
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
|
2026-05-11 15:08:03 +03:00
|
|
|
|
|
|
|
|
|
|
def is_dec_number(s):
|
|
|
|
|
|
"""Проверяет, является ли строка десятичным числом (возможно с минусом)"""
|
2026-05-17 12:21:28 +03:00
|
|
|
|
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-11 15:08:03 +03:00
|
|
|
|
|
2026-05-02 23:04:46 +03:00
|
|
|
|
def tokenize_line(line, line_num):
|
2026-06-20 04:16:50 +03:00
|
|
|
|
"""Разбирает строку в токены (русские названия типов)"""
|
2026-05-11 15:08:03 +03:00
|
|
|
|
# Удаляем комментарии
|
2026-05-17 12:21:28 +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-11 15:08:03 +03:00
|
|
|
|
|
2026-05-02 23:04:46 +03:00
|
|
|
|
while i < n:
|
|
|
|
|
|
ch = line[i]
|
2026-05-11 15:08:03 +03:00
|
|
|
|
|
|
|
|
|
|
# Пропускаем пробелы
|
2026-05-17 12:21:28 +03:00
|
|
|
|
if ch == ' ' or ch == '\t':
|
2026-05-02 23:04:46 +03:00
|
|
|
|
i += 1
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
2026-05-11 15:08:03 +03:00
|
|
|
|
# Строковые литералы
|
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-11 15:08:03 +03:00
|
|
|
|
# Одиночные символы-разделители
|
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
|
|
|
|
# Токены для сложной адресации
|
2026-05-11 15:08:03 +03:00
|
|
|
|
if ch == '[':
|
2026-06-20 04:16:50 +03:00
|
|
|
|
tokens.append(('СК_ОТКР', '['))
|
2026-05-11 15:08:03 +03:00
|
|
|
|
i += 1
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
if ch == ']':
|
2026-06-20 04:16:50 +03:00
|
|
|
|
tokens.append(('СК_ЗАКР', ']'))
|
2026-05-11 15:08:03 +03:00
|
|
|
|
i += 1
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
if ch == '+':
|
2026-06-20 04:16:50 +03:00
|
|
|
|
tokens.append(('ПЛЮС', '+'))
|
2026-05-11 15:08:03 +03:00
|
|
|
|
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(('МИНУС', '-'))
|
2026-05-11 15:08:03 +03:00
|
|
|
|
i += 1
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
if ch == '*':
|
2026-06-20 04:16:50 +03:00
|
|
|
|
tokens.append(('ЗВЕЗДА', '*'))
|
2026-05-11 15:08:03 +03:00
|
|
|
|
i += 1
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
# Слова, числа, метки
|
2026-05-02 23:04:46 +03:00
|
|
|
|
j = i
|
2026-05-17 12:21:28 +03:00
|
|
|
|
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-11 15:08:03 +03:00
|
|
|
|
|
2026-05-02 23:04:46 +03:00
|
|
|
|
if word:
|
2026-05-11 15:08:03 +03:00
|
|
|
|
# Определяем тип токена
|
|
|
|
|
|
if is_hex_number(word):
|
2026-06-20 04:16:50 +03:00
|
|
|
|
tokens.append(('ЧИСЛО', word))
|
2026-05-11 15:08:03 +03:00
|
|
|
|
elif is_dec_number(word):
|
2026-06-20 04:16:50 +03:00
|
|
|
|
tokens.append(('ЧИСЛО', word))
|
2026-05-11 15:08:03 +03:00
|
|
|
|
else:
|
2026-06-20 04:16:50 +03:00
|
|
|
|
tokens.append(('СЛОВО', word))
|
2026-05-11 15:08:03 +03:00
|
|
|
|
|
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}")
|