From 06164cff09999c85357d1a7b9984ef7e9ee41288 Mon Sep 17 00:00:00 2001 From: ShishkaDanil <70168790+ShishkaDanil@users.noreply.github.com> Date: Sat, 3 Jan 2026 19:31:30 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D1=8F=20=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83=D1=80=D0=B0?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B0;=20docs:=20=D0=B3?= =?UTF-8?q?=D1=80=D0=B0=D0=BC=D0=BC=D0=B0=D1=82=D0=B8=D0=BA=D0=B0=20=D1=84?= =?UTF-8?q?=D0=BE=D1=80=D0=BC=D1=8B=20=D0=B1=D1=8D=D0=BA=D1=83=D1=81=D0=B0?= =?UTF-8?q?-=D0=BD=D0=B0=D1=83=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 24 +-- Cargo.toml | 38 +++++ benches/tokenizer_bench.rs | 34 +++++ docs/grammar.md | 262 +++++++++++++++++++++++++++++++ examples/basic_tokenizer.rs | 63 ++++++++ src/comment.rs | 176 +++++++++++++++++++++ src/dfa.rs | 181 ++++++++++++++++++++++ src/error.rs | 124 +++++++++++++++ src/grammar.rs | 262 +++++++++++++++++++++++++++++++ src/lib.rs | 34 +++++ src/position.rs | 113 ++++++++++++++ src/token.rs | 296 ++++++++++++++++++++++++++++++++++++ src/tokenizer.rs | 215 ++++++++++++++++++++++++++ src/utf8.rs | 202 ++++++++++++++++++++++++ tests/integration_tests.rs | 25 +++ tests/test_comments.rs | 51 +++++++ tests/test_cyrillic.rs | 50 ++++++ tests/test_latin.rs | 50 ++++++ tests/test_operators.rs | 51 +++++++ tests/test_positions.rs | 51 +++++++ 20 files changed, 2291 insertions(+), 11 deletions(-) create mode 100644 Cargo.toml create mode 100644 benches/tokenizer_bench.rs create mode 100644 docs/grammar.md create mode 100644 examples/basic_tokenizer.rs create mode 100644 src/comment.rs create mode 100644 src/dfa.rs create mode 100644 src/error.rs create mode 100644 src/grammar.rs create mode 100644 src/lib.rs create mode 100644 src/position.rs create mode 100644 src/token.rs create mode 100644 src/tokenizer.rs create mode 100644 src/utf8.rs create mode 100644 tests/integration_tests.rs create mode 100644 tests/test_comments.rs create mode 100644 tests/test_cyrillic.rs create mode 100644 tests/test_latin.rs create mode 100644 tests/test_operators.rs create mode 100644 tests/test_positions.rs diff --git a/.gitignore b/.gitignore index 6985cf1..ed7d8f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,16 @@ -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +# Rust +/target/ +**/*.rs.bk +*.pdb Cargo.lock -# These are backup files generated by rustfmt -**/*.rs.bk +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb +# OS +.DS_Store +Thumbs.db diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..946f25b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "tverd-plus-tokenizer" +version = "0.1.0" +edition = "2021" +authors = ["Твердь Project Team"] +description = "Доверенный лексический анализатор языка Ъ+ (системный базис Ъ++). Реализация на Rust (no_std) для архитектур Эльбрус и x86." +license = "MIT OR Apache-2.0" +repository = "" +documentation = "" +readme = "README.md" + +[lib] +name = "tverd_plus_tokenizer" +crate-type = ["lib"] + +# Настройка no_std окружения +[profile.release] +opt-level = 3 +lto = true +codegen-units = 1 +panic = 'abort' + +[profile.dev] +opt-level = 0 +debug = true + +# Зависимости отсутствуют согласно требованиям безопасности +# (no external dynamic libraries allowed) +[dependencies] + +# Опциональные зависимости для тестирования (только в dev режиме) +[dev-dependencies] + +# Бенчмарки (опционально) +[[bench]] +name = "tokenizer_bench" +harness = false + diff --git a/benches/tokenizer_bench.rs b/benches/tokenizer_bench.rs new file mode 100644 index 0000000..bef7636 --- /dev/null +++ b/benches/tokenizer_bench.rs @@ -0,0 +1,34 @@ +//! Бенчмарки производительности токенизатора. + +// TODO: Добавить бенчмарки производительности +// - Бенчмарк токенизации простого кода +// - Бенчмарк токенизации сложного кода +// - Бенчмарк обработки комментариев +// - Бенчмарк обработки идентификаторов + +/* +Пример использования: + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use tverd_plus_tokenizer::Tokenizer; + +fn bench_tokenize_simple(c: &mut Criterion) { + let source = "Ъ+ @переменная :метка |> << Ъ-"; + c.bench_function("tokenize_simple", |b| { + b.iter(|| { + let tokenizer = Tokenizer::new(black_box(source)); + tokenizer.collect::>() + }); + }); +} + +criterion_group!(benches, bench_tokenize_simple); +criterion_main!(benches); +*/ + +fn main() { + // TODO: Реализовать бенчмарки + println!("Бенчмарки производительности токенизатора"); + println!("TODO: Реализовать бенчмарки с использованием criterion"); +} + diff --git a/docs/grammar.md b/docs/grammar.md new file mode 100644 index 0000000..8caa1b9 --- /dev/null +++ b/docs/grammar.md @@ -0,0 +1,262 @@ +# Грамматика языка Ъ+ (EBNF) + +Формальное описание грамматики языка «Ъ+» в форме Бэкуса — Наура (EBNF). + +## Основные принципы + +- **Кириллица обязательна** для ключевых конструкций (блоков) +- **Латинский алфавит опционален**, используется только для имен переменных +- **Конвейерный стиль** (`|>`) — основной способ передачи данных +- **Префиксация сущностей** (`@`, `:`, `?`) для однозначного разбора +- **Разделение мутации и вычисления** — разные операторы для сдвигов и записи в буфер + +## Базовые символы + +```ebnf +digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"; +hex_digit = digit | "a" | "b" | "c" | "d" | "e" | "f" | "A" | "B" | "C" | "D" | "E" | "F"; +binary_digit = "0" | "1"; +cyrillic_letter = "а".."я" | "А".."Я" | "ё" | "Ё"; +latin_letter = "a".."z" | "A".."Z"; +letter = cyrillic_letter | latin_letter; +``` + +## 1. Структура программы + +```ebnf +program = { item }; +item = function_definition | constant_definition | type_definition; + +type_definition = "!:ТИП", identifier_body, "=", ... +constant_definition = "!:КОНСТ", identifier_body, "=", ... +``` + +## 2. Идентификаторы + +```ebnf +variable_identifier = "@", identifier_body; +label_identifier = ":", identifier_body; +predicate_identifier = "?", identifier_body; +identifier_body = letter, {letter | digit | "_"}; +``` + +## 3. Литералы + +### 3.1. Числовые литералы + +```ebnf +integer_literal = (decimal_literal | hex_literal | binary_literal), [type_suffix]; +decimal_literal = digit, [{digit | "_"}, digit]; +hex_literal = "0x", hex_digit, [{hex_digit | "_"}, hex_digit]; +binary_literal = "0b", binary_digit, [{binary_digit | "_"}, binary_digit]; + +type_suffix = "_", type_suffix_raw; +type_suffix_raw = + "б8" | "ц8" | + "б16" | "ц16" | + "б32" | "ц32" | + "б64" | "ц64" | + "бр" | "цр" | + "в32" | "в64"; + +float_literal = (decimal_literal, ".", [decimal_literal] | ".", decimal_literal), [type_suffix]; +``` + +### 3.2. Строки и коллекции + +```ebnf +string_literal = '"', {string_char | escape_sequence}, '"'; +string_char = ? любой символ Unicode, кроме '"' и '\' ?; +escape_sequence = "\", ("n" | "t" | "r" | "\\" | '"' | "0", hex_digit, hex_digit | "u{", hex_digit, {hex_digit}, "}"); + +array_literal = array_fixed | array_list; +array_fixed = "[", expression, ";", expression, "]"; +array_list = "[", [expression, {",", expression}, [","]], "]"; + +tuple_literal = "(", [expression, {",", expression}, [","]], ")"; +``` + +## 4. Операторы + +### 4.1. Арифметические и логические +```ebnf + +arithmetic_operator = "+" | "-" | "*" | "/" | "%"; +logical_operator = "&&" | "||" | "!"; +comparison_operator = "==" | "!=" | "<" | ">" | "<=" | ">="; +``` + +### 4.2. Битовые операторы +```ebnf +bitwise_operator = "&" | "|" | "^" | "~" | "<<<" | ">>>"; +``` + +### 4.3. Специальные операторы +```ebnf +special_operator = "|>" | "<<" | "="; +``` + +## 5. Управляющие конструкции + +### 5.1. Блоки и инструкции +```ebnf +block_start = "Ъ+"; +block_end = "Ъ-"; +block = block_start, {statement}, block_end; + +statement = + binding_statement + | mutation_statement + | (expression, [";"]); + +binding_statement = variable_identifier, "=", expression, ";"; +mutation_statement = variable_identifier, "<<", expression, ";"; +``` + +### 5.2. Условие (Предикативное ветвление) +```ebnf +conditional_expr = "?", expression, ( block_branch | arrow_branch ); + +block_branch = block, [":", block]; +arrow_branch = "->", expression, [":", expression]; +``` + +### 5.3. Цикл (Итеративный предикат) +```ebnf +loop_expr = "?*", expression, block; +``` + +### 5.4. Сопоставление с образцом +```ebnf +match_expr = "?", variable_identifier, "{", match_arm, {match_arm}, "}"; +match_arm = "|", pattern, "->", (expression | block), [","]; +pattern = literal | variable_identifier | "_" ; +``` + +## 6. Функции и Типы + +### 6.1. Определение функции и Типов +```ebnf +function_definition = ":", identifier_body, [parameter_list], ["->", type_descriptor], block; +parameter_list = variable_identifier, {",", variable_identifier}; + +type_descriptor = type_suffix_raw | "@пусто" | array_type | tuple_type | function_type | variable_identifier; +array_type = "[", type_descriptor, ";", expression, "]"; +tuple_type = "(", [type_descriptor, {",", type_descriptor}], ")"; +function_type = ":", "(", [type_descriptor, {",", type_descriptor}], ")", ["->", type_descriptor]; +``` + +### 6.2. Вызовы +```ebnf +function_call = label_identifier, "(", [argument_list], ")"; +argument_list = expression, {",", expression}; +``` + +### 6.3. Лямбды +```ebnf +lambda = "@", "(", [parameter_list], "->", (expression | block), ")"; +``` + +## 7. Выражения и Приоритеты + +```ebnf +expression = mutation_expr; + +(* 13: Мутация *) +mutation_expr = binding_expr, [ "<<", mutation_expr ]; + +(* 12: Связывание *) +binding_expr = pipeline_expr, [ "=", binding_expr ]; + +(* 11: Конвейер *) +pipeline_expr = logical_or_expr, { "|>", (function_call | lambda | label_identifier) }; + +(* 10: Логическое ИЛИ *) +logical_or_expr = logical_and_expr, { "||", logical_and_expr }; + +(* 9: Логическое И *) +logical_and_expr = comparison_expr, { "&&", comparison_expr }; + +(* 8: Сравнение *) +comparison_expr = bitwise_or_expr, [ comparison_operator, bitwise_or_expr ]; + +(* 7: Битовое ИЛИ *) +bitwise_or_expr = bitwise_xor_expr, { "|", bitwise_xor_expr }; + +(* 6: Битовое XOR *) +bitwise_xor_expr = bitwise_and_expr, { "^", bitwise_and_expr }; + +(* 5: Битовое И *) +bitwise_and_expr = shift_expr, { "&", shift_expr }; + +(* 4: Сдвиги *) +shift_expr = additive_expr, { ("<<<" | ">>>"), additive_expr }; + +(* 3: Сложение / Вычитание *) +additive_expr = multiplicative_expr, { ("+" | "-"), multiplicative_expr }; + +(* 2: Умножение / Деление / Остаток (Уровень power_expr удален) *) +multiplicative_expr = unary_expr, { ("*" | "/" | "%"), unary_expr }; + +(* 1: Унарные операторы и Постфиксные выражения *) +unary_expr = ("-" | "!" | "~"), unary_expr | postfix_expr; + +(* 0: Постфиксные (Индексация, Доступ к полям) и Первичные *) +postfix_expr = primary_expr, { index_access | member_access }; +index_access = "[", expression, "]"; +member_access = ".", identifier_body; + +primary_expr = + literal + | variable_identifier + | function_call + | lambda + | parenthesized_expr + | block + | conditional_expr + | match_expr + | loop_expr; + +parenthesized_expr = "(", expression, ")"; +literal = integer_literal | float_literal | string_literal | array_literal | tuple_literal; +``` + +## 8. Комментарии + +```ebnf +comment = "<[", {comment_content | comment}, "]>"; +comment_content = ? любой символ, кроме последовательности "]>" и начала вложенного "<[" ?; +``` + +## 9. Пример кода (Валидация по грамматике) + +```rust +<[ Пример функции вычисления CRC ]> +:Вычислить_ЦРЦ @данные, @длина -> @црц Ъ+ + @црц = 0xFFFFFFFF_бр; + @и = 0_бр; + + ?* (@и < @длина) Ъ+ + @байт = @данные[@и]; + @црц = @црц ^ @байт; + @к = 0_б8; + + ?* (@к < 8_б8) Ъ+ + ? (@црц & 1_бр) -> (@црц >>> 1) ^ 0xEDB88320_бр : (@црц >>> 1); + @к << @к + 1_б8; + Ъ- + + @и << @и + 1_бр; + Ъ- + + @црц = ~@црц; +Ъ- +``` + +## Примечания + +- **Кодировка**: UTF-8 (ISO/IEC 10646) +- **Отслеживание позиций**: Каждый токен имеет координаты (строка, столбец) для сквозного аудита +- **Детерминизм**: Язык предназначен для создания детерминированного системного ПО +- **Безопасность**: Минимизация вектора атак, использование no_std окружения +- **Аппаратная поддержка**: Оптимизация для архитектур «Эльбрус» (предикатное исполнение) и x86 diff --git a/examples/basic_tokenizer.rs b/examples/basic_tokenizer.rs new file mode 100644 index 0000000..faaab54 --- /dev/null +++ b/examples/basic_tokenizer.rs @@ -0,0 +1,63 @@ +//! Базовый пример использования токенизатора языка Ъ+. + +// TODO: Реализовать пример использования токенизатора +// - Создание токенизатора +// - Итерация по токенам +// - Обработка ошибок +// - Вывод информации о токенах + +/* +Пример использования: + +use tverd_plus_tokenizer::Tokenizer; + +fn main() { + let source = r#" + @порт = 0xAF42 + @данные = [0; 64] + @коэф = 1.25 + + Ъ+ + @знач = вх(@порт) + ? @знач < 5 : @знач = 0 + @знач = @знач * @коэф + @данные << @знач + ? длина(@данные) < 64 : Ъ+ + Ъ- + + выдать(@данные) + "#; + + let mut tokenizer = Tokenizer::new(source); + + println!("Токены:"); + for result in tokenizer { + match result { + Ok(token) => { + println!( + " {:?} на позиции {}:{} - '{}'", + token.kind, + token.position.line, + token.position.column, + token.lexeme + ); + } + Err(error) => { + eprintln!( + "Ошибка на позиции {}:{} - {}", + error.position.line, + error.position.column, + error.description() + ); + } + } + } +} +*/ + +fn main() { + // TODO: Реализовать пример использования токенизатора + println!("Пример использования токенизатора языка Ъ+"); + println!("TODO: Реализовать демонстрацию токенизации"); +} + diff --git a/src/comment.rs b/src/comment.rs new file mode 100644 index 0000000..9c38b4b --- /dev/null +++ b/src/comment.rs @@ -0,0 +1,176 @@ +//! Модуль обработки вложенных комментариев. +//! +//! Обеспечивает распознавание и обработку комментариев с парными ограничителями +//! `<[` и `]>` с поддержкой рекурсивной вложенности без жесткого ограничения +//! уровня вложенности. + +use crate::grammar::{COMMENT_END, COMMENT_START}; + +/// Состояние обработки комментариев. +/// +/// Используется для отслеживания уровня вложенности комментариев +/// при лексическом анализе. +#[derive(Debug, Clone, Default)] +pub struct CommentState { + /// Текущий уровень вложенности комментариев (0 = вне комментария) + nesting_level: u32, +} + +impl CommentState { + /// Создает новое состояние комментариев (вне комментария). + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::comment::CommentState; + /// let state = CommentState::new(); + /// assert!(!state.is_in_comment()); + /// ``` + pub fn new() -> Self { + // TODO: Реализовать создание состояния + Self { nesting_level: 0 } + } + + /// Проверяет, находится ли анализатор внутри комментария. + /// + /// # Возвращает + /// `true`, если текущий уровень вложенности > 0 + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::comment::CommentState; + /// let state = CommentState::new(); + /// assert!(!state.is_in_comment()); + /// ``` + pub fn is_in_comment(&self) -> bool { + // TODO: Реализовать проверку нахождения в комментарии + self.nesting_level > 0 + } + + /// Обрабатывает начало комментария (`<[`). + /// + /// Увеличивает уровень вложенности. + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::comment::CommentState; + /// let mut state = CommentState::new(); + /// state.enter_comment(); + /// assert!(state.is_in_comment()); + /// ``` + pub fn enter_comment(&mut self) { + // TODO: Реализовать вход в комментарий + self.nesting_level += 1; + } + + /// Обрабатывает конец комментария (`]>`). + /// + /// Уменьшает уровень вложенности. + /// + /// # Возвращает + /// `Ok(())` если комментарий закрыт успешно, + /// `Err(())` если попытка закрыть комментарий вне комментария + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::comment::CommentState; + /// let mut state = CommentState::new(); + /// state.enter_comment(); + /// assert!(state.exit_comment().is_ok()); + /// assert!(!state.is_in_comment()); + /// ``` + pub fn exit_comment(&mut self) -> Result<(), ()> { + // TODO: Реализовать выход из комментария + if self.nesting_level > 0 { + self.nesting_level -= 1; + Ok(()) + } else { + Err(()) + } + } + + /// Получает текущий уровень вложенности комментариев. + /// + /// # Возвращает + /// Текущий уровень вложенности (0 = вне комментария) + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::comment::CommentState; + /// let mut state = CommentState::new(); + /// state.enter_comment(); + /// assert_eq!(state.nesting_level(), 1); + /// ``` + pub fn nesting_level(&self) -> u32 { + // TODO: Реализовать получение уровня вложенности + self.nesting_level + } + + /// Сбрасывает состояние комментариев (выходит из всех вложенных комментариев). + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::comment::CommentState; + /// let mut state = CommentState::new(); + /// state.enter_comment(); + /// state.enter_comment(); + /// state.reset(); + /// assert!(!state.is_in_comment()); + /// ``` + pub fn reset(&mut self) { + // TODO: Реализовать сброс состояния + self.nesting_level = 0; + } +} + +/// Проверяет, является ли строка началом комментария (`<[`). +/// +/// # Параметры +/// - `text`: проверяемая строка +/// +/// # Возвращает +/// `true`, если строка начинается с `<[` +/// +/// # Пример +/// ``` +/// use tverd_plus_tokenizer::comment::is_comment_start; +/// assert!(is_comment_start("<[ комментарий ]>")); +/// assert!(!is_comment_start("не комментарий")); +/// ``` +pub fn is_comment_start(text: &str) -> bool { + // TODO: Реализовать проверку начала комментария + text.starts_with(COMMENT_START) +} + +/// Проверяет, является ли строка концом комментария (`]>`). +/// +/// # Параметры +/// - `text`: проверяемая строка +/// +/// # Возвращает +/// `true`, если строка начинается с `]>` +/// +/// # Пример +/// ``` +/// use tverd_plus_tokenizer::comment::is_comment_end; +/// assert!(is_comment_end("]> конец")); +/// assert!(!is_comment_end("не конец")); +/// ``` +pub fn is_comment_end(text: &str) -> bool { + // TODO: Реализовать проверку конца комментария + text.starts_with(COMMENT_END) +} + +#[cfg(test)] +mod tests { + use super::*; + + // TODO: Написать тесты для обработки комментариев + // - Тест создания CommentState + // - Тест is_in_comment + // - Тест enter_comment + // - Тест exit_comment (успешный и неуспешный) + // - Тест вложенных комментариев + // - Тест is_comment_start + // - Тест is_comment_end +} + diff --git a/src/dfa.rs b/src/dfa.rs new file mode 100644 index 0000000..59c073e --- /dev/null +++ b/src/dfa.rs @@ -0,0 +1,181 @@ +//! Модуль состояний детерминированного конечного автомата (ДКА). +//! +//! Определяет состояния и переходы ДКА для распознавания токенов +//! языка Ъ+ в процессе лексического анализа. + +/// Состояние детерминированного конечного автомата. +/// +/// Представляет текущее состояние ДКА при обработке входного потока символов. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum DfaState { + /// Начальное состояние (ожидание начала токена) + Initial, + + // Состояния идентификаторов + /// Состояние обработки идентификатора переменной (после `@`) + VariableIdentifier, + /// Состояние обработки идентификатора метки (после `:`) + LabelIdentifier, + /// Состояние обработки предиката (после `?`) + PredicateIdentifier, + + // Состояния блоков и комментариев + /// Состояние обработки начала оператора блока (`Ъ`) + BlockStartCandidate, + /// Состояние обработки оператора блока (`Ъ+` или `Ъ-`) + BlockOperator, + /// Состояние обработки начала комментария (после `<`) + CommentStartCandidate, + /// Состояние внутри комментария + InComment, + /// Состояние обработки конца комментария (после `]`) + CommentEndCandidate, + + // Состояния операторов + /// Состояние обработки оператора конвейера (после `|`) + PipelineCandidate, + /// Состояние обработки оператора записи/сдвига (после первой `<`) + WriteOrShiftLeftCandidate, + /// Состояние обработки оператора сдвига вправо (после первой `>`) + ShiftRightCandidate, + /// Состояние обработки оператора сравнения/присваивания (после `=`) + EqualOrAssignCandidate, + /// Состояние обработки оператора неравенства (после `!`) + NotEqualCandidate, + /// Состояние обработки оператора сравнения (после `<` или `>`) + ComparisonCandidate, + /// Состояние обработки логического И/битового И (после `&`) + LogicalAndCandidate, + /// Состояние обработки логического ИЛИ/битового ИЛИ (после `|`) + LogicalOrCandidate, + /// Состояние обработки оператора мутации (после `:`) + MutateCandidate, + /// Состояние обработки оператора возведения в степень (после первой `*`) + PowerCandidate, + + // Состояния литералов + /// Состояние обработки десятичного числового литерала + DecimalLiteral, + /// Состояние обработки вещественного литерала (после `.`) + FloatLiteral, + /// Состояние обработки шестнадцатеричного литерала (после `0x`) + HexLiteral, + /// Состояние обработки двоичного литерала (после `0b`) + BinaryLiteral, + /// Состояние обработки суффикса типа литерала (после `_`) + TypeSuffixCandidate, + /// Состояние обработки строкового литерала + StringLiteral, + /// Состояние обработки escape-последовательности в строке + StringEscape, + /// Состояние обработки шестнадцатеричного escape в строке + StringHexEscape, + + /// Финальное состояние (токен распознан) + Accept, + /// Состояние ошибки (некорректный токен) + Error, +} + +impl DfaState { + /// Проверяет, является ли состояние финальным (токен распознан). + /// + /// # Возвращает + /// `true`, если состояние является финальным + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::dfa::DfaState; + /// assert!(DfaState::Accept.is_final()); + /// assert!(!DfaState::Initial.is_final()); + /// ``` + pub fn is_final(&self) -> bool { + // TODO: Реализовать проверку финального состояния + matches!(self, DfaState::Accept) + } + + /// Проверяет, является ли состояние ошибочным. + /// + /// # Возвращает + /// `true`, если состояние является ошибочным + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::dfa::DfaState; + /// assert!(DfaState::Error.is_error()); + /// assert!(!DfaState::Initial.is_error()); + /// ``` + pub fn is_error(&self) -> bool { + // TODO: Реализовать проверку ошибочного состояния + matches!(self, DfaState::Error) + } + + /// Возвращает следующее состояние ДКА на основе текущего состояния и входного символа. + /// + /// # Параметры + /// - `ch`: входной символ для обработки + /// - `in_comment`: флаг, указывающий, находится ли анализатор внутри комментария + /// + /// # Возвращает + /// Новое состояние ДКА после обработки символа + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::dfa::DfaState; + /// let next_state = DfaState::Initial.next_state('@', false); + /// ``` + pub fn next_state(&self, ch: char, in_comment: bool) -> DfaState { + // TODO: Реализовать полные переходы ДКА для всех операторов и конструкций + // Это упрощенная заглушка - реальная реализация должна обрабатывать все переходы + match (self, ch, in_comment) { + // Переходы из начального состояния + (DfaState::Initial, '@', false) => DfaState::VariableIdentifier, + (DfaState::Initial, ':', false) => DfaState::LabelIdentifier, + (DfaState::Initial, '?', false) => DfaState::PredicateIdentifier, + (DfaState::Initial, 'Ъ', false) => DfaState::BlockStartCandidate, + (DfaState::Initial, '|', false) => DfaState::PipelineCandidate, + (DfaState::Initial, '<', false) => DfaState::WriteOrShiftLeftCandidate, + (DfaState::Initial, '>', false) => DfaState::ShiftRightCandidate, + (DfaState::Initial, '=', false) => DfaState::EqualOrAssignCandidate, + (DfaState::Initial, '!', false) => DfaState::NotEqualCandidate, + (DfaState::Initial, '&', false) => DfaState::LogicalAndCandidate, + (DfaState::Initial, '"', false) => DfaState::StringLiteral, + (DfaState::Initial, '0'..='9', false) => DfaState::DecimalLiteral, + (DfaState::Initial, '+', false) | (DfaState::Initial, '-', false) | + (DfaState::Initial, '*', false) | (DfaState::Initial, '/', false) | + (DfaState::Initial, '%', false) | (DfaState::Initial, '^', false) | + (DfaState::Initial, '~', false) => DfaState::Accept, + + // Переходы для операторов (упрощенные) + (DfaState::PipelineCandidate, '>', false) => DfaState::Accept, + (DfaState::WriteOrShiftLeftCandidate, '<', false) => DfaState::Accept, + (DfaState::EqualOrAssignCandidate, '=', false) => DfaState::Accept, + (DfaState::NotEqualCandidate, '=', false) => DfaState::Accept, + (DfaState::LogicalAndCandidate, '&', false) => DfaState::Accept, + (DfaState::LogicalOrCandidate, '|', false) => DfaState::Accept, + (DfaState::PowerCandidate, '*', false) => DfaState::Accept, + + _ => DfaState::Error, + } + } +} + +impl Default for DfaState { + fn default() -> Self { + DfaState::Initial + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // TODO: Написать тесты для ДКА + // - Тест is_final + // - Тест is_error + // - Тест next_state для различных переходов + // - Тест переходов из начального состояния + // - Тест обработки комментариев + // - Тест обработки операторов +} + diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..e382b91 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,124 @@ +//! Модуль обработки ошибок лексического анализа. +//! +//! Определяет типы ошибок, возникающих в процессе токенизации, +//! с привязкой к позиции в исходном коде. + +use crate::position::Position; + +/// Тип ошибки лексического анализа. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ErrorKind { + /// Неожиданный символ в исходном коде + UnexpectedChar(char), + /// Неожиданный конец файла + UnexpectedEof, + /// Некорректная UTF-8 последовательность + InvalidUtf8, + /// Несоответствие закрывающих скобок комментариев + UnmatchedCommentClose, + /// Превышена максимальная глубина вложенности комментариев + CommentNestingTooDeep, + /// Другая ошибка (с описанием) + Other(&'static str), +} + +/// Ошибка лексического анализа. +/// +/// Содержит информацию о типе ошибки и позиции её возникновения +/// в исходном коде. +#[derive(Debug, Clone, PartialEq)] +pub struct Error { + /// Тип ошибки + pub kind: ErrorKind, + /// Позиция в исходном коде, где произошла ошибка + pub position: Position, + /// Дополнительное сообщение об ошибке + pub message: Option<&'static str>, +} + +impl Error { + /// Создает новую ошибку. + /// + /// # Параметры + /// - `kind`: тип ошибки + /// - `position`: позиция возникновения ошибки + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::error::{Error, ErrorKind}; + /// use tverd_plus_tokenizer::Position; + /// + /// let err = Error::new(ErrorKind::UnexpectedChar('@'), Position::start()); + /// ``` + pub fn new(kind: ErrorKind, position: Position) -> Self { + // TODO: Реализовать создание ошибки + Self { + kind, + position, + message: None, + } + } + + /// Создает новую ошибку с дополнительным сообщением. + /// + /// # Параметры + /// - `kind`: тип ошибки + /// - `position`: позиция возникновения ошибки + /// - `message`: дополнительное сообщение + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::error::{Error, ErrorKind}; + /// use tverd_plus_tokenizer::Position; + /// + /// let err = Error::with_message( + /// ErrorKind::InvalidUtf8, + /// Position::start(), + /// "Некорректная последовательность байтов" + /// ); + /// ``` + pub fn with_message(kind: ErrorKind, position: Position, message: &'static str) -> Self { + // TODO: Реализовать создание ошибки с сообщением + Self { + kind, + position, + message: Some(message), + } + } + + /// Возвращает человекочитаемое описание ошибки. + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::error::{Error, ErrorKind}; + /// use tverd_plus_tokenizer::Position; + /// + /// let err = Error::new(ErrorKind::UnexpectedChar('@'), Position::new(5, 10)); + /// let description = err.description(); + /// ``` + pub fn description(&self) -> &'static str { + // TODO: Реализовать формирование описания ошибки + match self.kind { + ErrorKind::UnexpectedChar(_) => "Неожиданный символ", + ErrorKind::UnexpectedEof => "Неожиданный конец файла", + ErrorKind::InvalidUtf8 => "Некорректная UTF-8 последовательность", + ErrorKind::UnmatchedCommentClose => "Несоответствие закрывающих скобок комментария", + ErrorKind::CommentNestingTooDeep => "Превышена максимальная глубина вложенности комментариев", + ErrorKind::Other(msg) => msg, + } + } +} + +// TODO: Реализовать trait core::fmt::Display для Error +// TODO: Реализовать trait core::error::Error для Error (если требуется) + +#[cfg(test)] +mod tests { + use super::*; + + // TODO: Написать тесты для Error + // - Тест создания ошибки + // - Тест создания ошибки с сообщением + // - Тест description +} + diff --git a/src/grammar.rs b/src/grammar.rs new file mode 100644 index 0000000..01724de --- /dev/null +++ b/src/grammar.rs @@ -0,0 +1,262 @@ +//! Модуль констант и правил грамматики языка Ъ+. +//! +//! Содержит определения специальных символов, операторов и ключевых слов +//! языка для использования в лексическом анализаторе. + +/// Начало блока (открытие исполняемого контекста) +pub const BLOCK_START: &str = "Ъ+"; + +/// Конец блока (закрытие исполняемого контекста) +pub const BLOCK_END: &str = "Ъ-"; + +/// Оператор конвейера (передача данных между функциями) +pub const PIPELINE_OP: &str = "|>"; + +/// Оператор записи (инструкция заполнения буфера данных) +pub const WRITE_OP: &str = "<<"; + +/// Начало комментария +pub const COMMENT_START: &str = "<["; + +/// Конец комментария +pub const COMMENT_END: &str = "]>"; + +/// Префикс идентификатора переменной (объекта данных) +pub const VAR_PREFIX: char = '@'; + +/// Префикс идентификатора метки управления (адресация переходов) +pub const LABEL_PREFIX: char = ':'; + +/// Префикс оператора логического ветвления (предиката) +pub const PREDICATE_PREFIX: char = '?'; + +/// Оператор цикла +pub const LOOP_OP: char = '>'; + +/// Арифметические операторы +pub const OP_PLUS: char = '+'; +pub const OP_MINUS: char = '-'; +pub const OP_MULTIPLY: char = '*'; +pub const OP_DIVIDE: char = '/'; +pub const OP_MODULO: char = '%'; +pub const OP_POWER: &str = "**"; + +/// Операторы сравнения +pub const OP_EQUAL: &str = "=="; +pub const OP_NOT_EQUAL: &str = "!="; +pub const OP_LESS: char = '<'; +pub const OP_GREATER: char = '>'; +pub const OP_LESS_EQUAL: &str = "<="; +pub const OP_GREATER_EQUAL: &str = ">="; + +/// Логические операторы +pub const OP_LOGICAL_AND: &str = "&&"; +pub const OP_LOGICAL_OR: &str = "||"; +pub const OP_LOGICAL_NOT: char = '!'; + +/// Битовые операторы +pub const OP_BITWISE_AND: char = '&'; +pub const OP_BITWISE_OR: char = '|'; +pub const OP_BITWISE_XOR: char = '^'; +pub const OP_BITWISE_NOT: char = '~'; +pub const OP_SHIFT_LEFT: &str = "<<"; +pub const OP_SHIFT_RIGHT: &str = ">>"; + +/// Операторы присваивания +pub const OP_ASSIGN: char = '='; +pub const OP_MUTATE: &str = ":="; + +/// Специальные символы +pub const ARROW: &str = "->"; +pub const COMMA: char = ','; +pub const SEMICOLON: char = ';'; + +/// Префиксы числовых литералов +pub const HEX_PREFIX: &str = "0x"; +pub const BINARY_PREFIX: &str = "0b"; + +/// Разделитель разрядов в числовых литералах +pub const DIGIT_SEPARATOR: char = '_'; + +/// Допустимые типы для литералов +pub const TYPE_SUFFIXES: &[&str] = &["u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "f32", "f64"]; + +/// Проверяет, является ли символ префиксом идентификатора. +/// +/// # Параметры +/// - `ch`: проверяемый символ +/// +/// # Возвращает +/// `true`, если символ является префиксом идентификатора (`@`, `:`, `?`) +/// +/// # Пример +/// ``` +/// use tverd_plus_tokenizer::grammar::is_identifier_prefix; +/// assert!(is_identifier_prefix('@')); +/// assert!(is_identifier_prefix(':')); +/// assert!(is_identifier_prefix('?')); +/// assert!(!is_identifier_prefix('a')); +/// ``` +pub fn is_identifier_prefix(ch: char) -> bool { + // TODO: Реализовать проверку префикса идентификатора + ch == VAR_PREFIX || ch == LABEL_PREFIX || ch == PREDICATE_PREFIX +} + +/// Проверяет, является ли символ началом оператора блока. +/// +/// # Параметры +/// - `ch`: проверяемый символ +/// +/// # Возвращает +/// `true`, если символ может быть началом оператора блока (`Ъ`) +/// +/// # Пример +/// ``` +/// use tverd_plus_tokenizer::grammar::is_block_op_start; +/// assert!(is_block_op_start('Ъ')); +/// ``` +pub fn is_block_op_start(ch: char) -> bool { + // TODO: Реализовать проверку начала оператора блока + ch == 'Ъ' +} + +/// Проверяет, является ли символ началом комментария. +/// +/// # Параметры +/// - `ch`: проверяемый символ +/// +/// # Возвращает +/// `true`, если символ может быть началом комментария (`<`) +/// +/// # Пример +/// ``` +/// use tverd_plus_tokenizer::grammar::is_comment_start_char; +/// assert!(is_comment_start_char('<')); +/// ``` +pub fn is_comment_start_char(ch: char) -> bool { + // TODO: Реализовать проверку начала комментария + ch == '<' +} + +/// Проверяет, является ли символ концом комментария. +/// +/// # Параметры +/// - `ch`: проверяемый символ +/// +/// # Возвращает +/// `true`, если символ может быть концом комментария (`]`) +/// +/// # Пример +/// ``` +/// use tverd_plus_tokenizer::grammar::is_comment_end_char; +/// assert!(is_comment_end_char(']')); +/// ``` +pub fn is_comment_end_char(ch: char) -> bool { + // TODO: Реализовать проверку конца комментария + ch == ']' +} + +/// Проверяет, является ли символ частью идентификатора. +/// +/// Идентификаторы могут содержать символы кириллического и латинского алфавитов. +/// +/// # Параметры +/// - `ch`: проверяемый символ +/// +/// # Возвращает +/// `true`, если символ может быть частью идентификатора +/// +/// # Пример +/// ``` +/// use tverd_plus_tokenizer::grammar::is_identifier_char; +/// assert!(is_identifier_char('а')); +/// assert!(is_identifier_char('a')); +/// assert!(is_identifier_char('Я')); +/// assert!(is_identifier_char('Z')); +/// ``` +pub fn is_identifier_char(ch: char) -> bool { + ch.is_alphabetic() || ch == '_' || ch.is_ascii_digit() +} + +/// Проверяет, является ли символ началом арифметического оператора. +pub fn is_arithmetic_op_start(ch: char) -> bool { + matches!(ch, '+' | '-' | '*' | '/' | '%') +} + +/// Проверяет, является ли символ началом оператора сравнения. +pub fn is_comparison_op_start(ch: char) -> bool { + matches!(ch, '=' | '!' | '<' | '>') +} + +/// Проверяет, является ли символ началом логического оператора. +pub fn is_logical_op_start(ch: char) -> bool { + matches!(ch, '&' | '|' | '!') +} + +/// Проверяет, является ли символ началом битового оператора. +pub fn is_bitwise_op_start(ch: char) -> bool { + matches!(ch, '&' | '|' | '^' | '~' | '<' | '>') +} + +/// Проверяет, является ли символ началом оператора присваивания. +pub fn is_assignment_op_start(ch: char) -> bool { + ch == OP_ASSIGN || ch == LABEL_PREFIX +} + +/// Проверяет, является ли символ оператором присваивания или мутации. +pub fn is_assignment_operator(ch: char) -> bool { + ch == OP_ASSIGN +} + +/// Проверяет, является ли строка оператором мутации. +pub fn is_mutate_operator(s: &str) -> bool { + s == OP_MUTATE +} + +/// Проверяет, является ли символ началом числового литерала. +pub fn is_digit_start(ch: char) -> bool { + ch.is_ascii_digit() +} + +/// Проверяет, является ли символ разделителем разрядов. +pub fn is_digit_separator(ch: char) -> bool { + ch == DIGIT_SEPARATOR +} + +/// Проверяет, является ли символ шестнадцатеричной цифрой. +pub fn is_hex_digit(ch: char) -> bool { + ch.is_ascii_hexdigit() +} + +/// Проверяет, является ли символ двоичной цифрой. +pub fn is_binary_digit(ch: char) -> bool { + matches!(ch, '0' | '1') +} + +/// Проверяет, является ли строка допустимым суффиксом типа. +pub fn is_type_suffix(s: &str) -> bool { + TYPE_SUFFIXES.contains(&s) +} + +/// Проверяет, является ли символ началом оператора конвейера или записи. +pub fn is_pipeline_or_write_start(ch: char) -> bool { + matches!(ch, '|' | '<') +} + +/// Проверяет, является ли символ оператором цикла. +pub fn is_loop_operator(ch: char) -> bool { + ch == LOOP_OP +} + +#[cfg(test)] +mod tests { + use super::*; + + // TODO: Написать тесты для функций грамматики + // - Тест is_identifier_prefix + // - Тест is_block_op_start + // - Тест is_comment_start_char + // - Тест is_comment_end_char + // - Тест is_identifier_char (кириллица, латиница, цифры) +} + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..45f1354 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,34 @@ +//! # Токенизатор языка Ъ+ +//! +//! Доверенный лексический анализатор языка «Ъ+» (системный базис «Ъ++»). +//! Реализация на Rust (no_std) для архитектур «Эльбрус» и x86. +//! +//! ## Основные компоненты +//! +//! - Лексический анализ входных потоков данных +//! - Преобразование в последовательность токенов на основе ДКА +//! - Поддержка UTF-8 кодировки (ISO/IEC 10646) +//! - Обработка кириллических и латинских идентификаторов +//! - Отслеживание позиций токенов (строка, столбец) + +#![no_std] +#![deny(missing_docs)] + +// TODO: Настроить обработку ошибок для no_std окружения +// TODO: Добавить необходимые re-exports для core типов + +pub mod comment; +pub mod dfa; +pub mod error; +pub mod grammar; +pub mod position; +pub mod token; +pub mod tokenizer; +pub mod utf8; + +// Re-export основных типов для удобства использования +pub use error::{Error, ErrorKind}; +pub use position::Position; +pub use token::{Token, TokenKind}; +pub use tokenizer::Tokenizer; + diff --git a/src/position.rs b/src/position.rs new file mode 100644 index 0000000..dfc5545 --- /dev/null +++ b/src/position.rs @@ -0,0 +1,113 @@ +//! Модуль отслеживания позиций токенов в исходном коде. +//! +//! Обеспечивает фиксацию координат (строка, столбец) каждого токена +//! для реализации сквозного аудита кода. + +/// Позиция токена в исходном коде. +/// +/// Хранит координаты (строка, столбец) для отслеживания местоположения +/// токена в исходном тексте. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Position { + /// Номер строки (начинается с 1) + pub line: u32, + /// Номер столбца в строке (начинается с 1) + pub column: u32, +} + +impl Position { + /// Создает новую позицию в начале файла (строка 1, столбец 1). + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::Position; + /// let pos = Position::start(); + /// assert_eq!(pos.line, 1); + /// assert_eq!(pos.column, 1); + /// ``` + pub fn start() -> Self { + // TODO: Реализовать создание начальной позиции + Self { line: 1, column: 1 } + } + + /// Создает позицию с указанными координатами. + /// + /// # Параметры + /// - `line`: номер строки (должен быть >= 1) + /// - `column`: номер столбца (должен быть >= 1) + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::Position; + /// let pos = Position::new(5, 10); + /// ``` + pub fn new(line: u32, column: u32) -> Self { + // TODO: Добавить проверку валидности координат + Self { line, column } + } + + /// Перемещает позицию на один символ вправо (увеличивает столбец). + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::Position; + /// let mut pos = Position::start(); + /// pos.advance_char(); + /// assert_eq!(pos.column, 2); + /// ``` + pub fn advance_char(&mut self) { + // TODO: Реализовать перемещение на один символ + self.column += 1; + } + + /// Перемещение позиции на новую строку (увеличивает номер строки, сбрасывает столбец). + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::Position; + /// let mut pos = Position::new(1, 10); + /// pos.advance_line(); + /// assert_eq!(pos.line, 2); + /// assert_eq!(pos.column, 1); + /// ``` + pub fn advance_line(&mut self) { + // TODO: Реализовать перемещение на новую строку + self.line += 1; + self.column = 1; + } + + /// Перемещение позиции на указанное количество символов. + /// + /// # Параметры + /// - `count`: количество символов для перемещения + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::Position; + /// let mut pos = Position::start(); + /// pos.advance_by(5); + /// assert_eq!(pos.column, 6); + /// ``` + pub fn advance_by(&mut self, count: u32) { + // TODO: Реализовать перемещение на несколько символов + self.column += count; + } +} + +impl Default for Position { + fn default() -> Self { + Self::start() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // TODO: Написать тесты для Position + // - Тест создания начальной позиции + // - Тест advance_char + // - Тест advance_line + // - Тест advance_by +} + diff --git a/src/token.rs b/src/token.rs new file mode 100644 index 0000000..05732d9 --- /dev/null +++ b/src/token.rs @@ -0,0 +1,296 @@ +//! Модуль определений типов токенов. +//! +//! Содержит определения типов `Token` и `TokenKind` для представления +//! результатов лексического анализа. + +use crate::position::Position; + +/// Тип токена языка Ъ+. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum TokenKind { + // Идентификаторы с префиксами + /// Идентификатор переменной (префикс `@`) + VariableIdentifier(String), + /// Идентификатор метки управления (префикс `:`) + LabelIdentifier(String), + /// Оператор логического ветвления (префикс `?`) + PredicateIdentifier(String), + + // Управляющие конструкции + /// Начало блока (`Ъ+`) + BlockStart, + /// Конец блока (`Ъ-`) + BlockEnd, + /// Оператор конвейера (`|>`) + PipelineOperator, + /// Оператор записи (`<<`) + WriteOperator, + /// Цикл (`>`) + LoopOperator, + + // Арифметические операторы + /// Сложение (`+`) + Plus, + /// Вычитание (`-`) + Minus, + /// Умножение (`*`) + Multiply, + /// Деление (`/`) + Divide, + /// Остаток от деления (`%`) + Modulo, + /// Возведение в степень (`**`) + Power, + + // Операторы сравнения + /// Равенство (`==`) + Equal, + /// Неравенство (`!=`) + NotEqual, + /// Меньше (`<`) + Less, + /// Больше (`>`) + Greater, + /// Меньше или равно (`<=`) + LessEqual, + /// Больше или равно (`>=`) + GreaterEqual, + + // Логические операторы + /// Логическое И (`&&`) + LogicalAnd, + /// Логическое ИЛИ (`||`) + LogicalOr, + /// Логическое НЕ (`!`) + LogicalNot, + + // Битовые операторы + /// Битовое И (`&`) + BitwiseAnd, + /// Битовое ИЛИ (`|`) + BitwiseOr, + /// Битовое исключающее ИЛИ (`^`) + BitwiseXor, + /// Битовое НЕ (`~`) + BitwiseNot, + /// Сдвиг влево (`<<`) + ShiftLeft, + /// Сдвиг вправо (`>>`) + ShiftRight, + + // Операторы присваивания + /// Присваивание (`=`) + Assign, + /// Мутация (`:=`) + Mutate, + + // Литералы + /// Целочисленный литерал + IntegerLiteral(u64), + /// Типизированный целочисленный литерал (значение, тип как строка) + TypedInteger(u64, String), + /// Вещественный литерал + FloatLiteral(f64), + /// Типизированный вещественный литерал (значение, тип как строка) + TypedFloat(f64, String), + /// Строковый литерал + StringLiteral(String), + /// Шестнадцатеричный литерал + HexLiteral(u64), + /// Двоичный литерал + BinaryLiteral(u64), + + // Специальные символы + /// Двоеточие (`:`) + Colon, + /// Запятая (`,`) + Comma, + /// Точка с запятой (`;`) + Semicolon, + /// Левая круглая скобка (`(`) + LeftParen, + /// Правая круглая скобка (`)`) + RightParen, + /// Левая фигурная скобка (`{`) + LeftBrace, + /// Правая фигурная скобка (`}`) + RightBrace, + /// Левая квадратная скобка (`[`) + LeftBracket, + /// Правая квадратная скобка (`]`) + RightBracket, + /// Стрелка (`->`) + Arrow, + + // Конструкции (для парсера) + /// Сопоставление с образцом (match) + Match, + /// Лямбда-функция + Lambda, + + // Специальные токены + /// Конец файла + Eof, + /// Неизвестный токен (для обработки ошибок) + Unknown, +} + +/// Токен языка Ъ+. +/// +/// Представляет результат лексического анализа с информацией о типе токена, +/// его позиции в исходном коде и исходной строке (лексеме). +#[derive(Debug, Clone, PartialEq)] +pub struct Token { + /// Тип токена + pub kind: TokenKind, + /// Позиция начала токена в исходном коде + pub position: Position, + /// Исходная строка (лексема) токена + pub lexeme: String, +} + +impl Token { + /// Создает новый токен. + /// + /// # Параметры + /// - `kind`: тип токена + /// - `position`: позиция начала токена + /// - `lexeme`: исходная строка токена + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::token::{Token, TokenKind}; + /// use tverd_plus_tokenizer::Position; + /// + /// let token = Token::new( + /// TokenKind::BlockStart, + /// Position::start(), + /// "Ъ+".to_string() + /// ); + /// ``` + pub fn new(kind: TokenKind, position: Position, lexeme: String) -> Self { + // TODO: Реализовать создание токена + Self { + kind, + position, + lexeme, + } + } + + /// Проверяет, является ли токен идентификатором. + /// + /// # Возвращает + /// `true`, если токен является одним из типов идентификаторов + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::token::{Token, TokenKind}; + /// use tverd_plus_tokenizer::Position; + /// + /// let token = Token::new( + /// TokenKind::VariableIdentifier("test".to_string()), + /// Position::start(), + /// "@test".to_string() + /// ); + /// assert!(token.is_identifier()); + /// ``` + pub fn is_identifier(&self) -> bool { + // TODO: Реализовать проверку идентификатора + matches!( + self.kind, + TokenKind::VariableIdentifier(_) + | TokenKind::LabelIdentifier(_) + | TokenKind::PredicateIdentifier(_) + ) + } + + /// Проверяет, является ли токен оператором. + /// + /// # Возвращает + /// `true`, если токен является оператором + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::token::{Token, TokenKind}; + /// use tverd_plus_tokenizer::Position; + /// + /// let token = Token::new( + /// TokenKind::PipelineOperator, + /// Position::start(), + /// "|>".to_string() + /// ); + /// assert!(token.is_operator()); + /// ``` + pub fn is_operator(&self) -> bool { + matches!( + self.kind, + TokenKind::PipelineOperator + | TokenKind::WriteOperator + | TokenKind::BlockStart + | TokenKind::BlockEnd + | TokenKind::LoopOperator + | TokenKind::Plus + | TokenKind::Minus + | TokenKind::Multiply + | TokenKind::Divide + | TokenKind::Modulo + | TokenKind::Power + | TokenKind::Equal + | TokenKind::NotEqual + | TokenKind::Less + | TokenKind::Greater + | TokenKind::LessEqual + | TokenKind::GreaterEqual + | TokenKind::LogicalAnd + | TokenKind::LogicalOr + | TokenKind::LogicalNot + | TokenKind::BitwiseAnd + | TokenKind::BitwiseOr + | TokenKind::BitwiseXor + | TokenKind::BitwiseNot + | TokenKind::ShiftLeft + | TokenKind::ShiftRight + | TokenKind::Assign + | TokenKind::Mutate + ) + } + + /// Получает текст идентификатора, если токен является идентификатором. + /// + /// # Возвращает + /// `Some(&str)` с текстом идентификатора, или `None` если токен не идентификатор + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::token::{Token, TokenKind}; + /// use tverd_plus_tokenizer::Position; + /// + /// let token = Token::new( + /// TokenKind::VariableIdentifier("test".to_string()), + /// Position::start(), + /// "@test".to_string() + /// ); + /// assert_eq!(token.identifier_text(), Some("test")); + /// ``` + pub fn identifier_text(&self) -> Option<&str> { + // TODO: Реализовать получение текста идентификатора + match &self.kind { + TokenKind::VariableIdentifier(s) + | TokenKind::LabelIdentifier(s) + | TokenKind::PredicateIdentifier(s) => Some(s), + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // TODO: Написать тесты для Token + // - Тест создания токена + // - Тест is_identifier + // - Тест is_operator + // - Тест identifier_text +} + diff --git a/src/tokenizer.rs b/src/tokenizer.rs new file mode 100644 index 0000000..d5651c1 --- /dev/null +++ b/src/tokenizer.rs @@ -0,0 +1,215 @@ +//! Модуль основного лексического анализатора (токенизатора). +//! +//! Реализует однопроходный сканер на основе детерминированного конечного автомата (ДКА) +//! для преобразования входного потока данных в последовательность токенов. + +use crate::comment::CommentState; +use crate::dfa::DfaState; +use crate::error::{Error, ErrorKind}; +use crate::grammar; +use crate::position::Position; +use crate::token::{Token, TokenKind}; +use crate::utf8; + +/// Лексический анализатор языка Ъ+. +/// +/// Выполняет однопроходное сканирование входного потока и преобразует его +/// в последовательность токенов с отслеживанием позиций. +/// +/// # Пример +/// ``` +/// use tverd_plus_tokenizer::Tokenizer; +/// +/// let source = "Ъ+ @переменная :метка |>"; +/// let mut tokenizer = Tokenizer::new(source); +/// // Использование итератора для получения токенов +/// ``` +pub struct Tokenizer<'a> { + /// Исходный текст для анализа + source: &'a str, + /// Текущая позиция в исходном тексте (в байтах) + position: usize, + /// Текущая позиция в исходном коде (строка, столбец) + current_position: Position, + /// Состояние обработки комментариев + comment_state: CommentState, + /// Текущее состояние ДКА + state: DfaState, +} + +impl<'a> Tokenizer<'a> { + /// Создает новый токенизатор для указанного исходного текста. + /// + /// # Параметры + /// - `source`: исходный текст для лексического анализа + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::Tokenizer; + /// + /// let source = "Ъ+ @переменная"; + /// let tokenizer = Tokenizer::new(source); + /// ``` + pub fn new(source: &'a str) -> Self { + // TODO: Реализовать создание токенизатора + Self { + source, + position: 0, + current_position: Position::start(), + comment_state: CommentState::new(), + state: DfaState::Initial, + } + } + + /// Читает следующий символ из исходного текста без перемещения позиции. + /// + /// # Возвращает + /// `Option` - следующий символ, или `None` если достигнут конец файла + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::Tokenizer; + /// + /// let source = "Ъ+"; + /// let tokenizer = Tokenizer::new(source); + /// // peek_char() используется внутренне + /// ``` + fn peek_char(&self) -> Option { + // TODO: Реализовать просмотр следующего символа + self.source[self.position..].chars().next() + } + + /// Читает следующий символ из исходного текста с перемещением позиции. + /// + /// # Возвращает + /// `Option` - следующий символ, или `None` если достигнут конец файла + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::Tokenizer; + /// + /// let source = "Ъ+"; + /// let mut tokenizer = Tokenizer::new(source); + /// // next_char() используется внутренне + /// ``` + fn next_char(&mut self) -> Option { + // TODO: Реализовать чтение следующего символа с обновлением позиции + let mut chars = self.source[self.position..].char_indices(); + if let Some((_, ch)) = chars.next() { + let char_len = ch.len_utf8(); + self.position += char_len; + + // Обновляем позицию (строка, столбец) + if ch == '\n' { + self.current_position.advance_line(); + } else { + self.current_position.advance_char(); + } + + Some(ch) + } else { + None + } + } + + /// Пропускает пробельные символы и символы новой строки. + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::Tokenizer; + /// + /// let source = " \n @переменная"; + /// let mut tokenizer = Tokenizer::new(source); + /// // skip_whitespace() используется внутренне + /// ``` + fn skip_whitespace(&mut self) { + // TODO: Реализовать пропуск пробельных символов + while let Some(ch) = self.peek_char() { + if ch.is_whitespace() { + self.next_char(); + } else { + break; + } + } + } + + /// Распознает следующий токен из исходного текста. + /// + /// # Возвращает + /// `Result, Error>` - следующий токен или ошибка + /// - `Ok(Some(token))` - успешно распознан токен + /// - `Ok(None)` - достигнут конец файла + /// - `Err(error)` - произошла ошибка лексического анализа + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::Tokenizer; + /// + /// let source = "Ъ+"; + /// let mut tokenizer = Tokenizer::new(source); + /// match tokenizer.next_token() { + /// Ok(Some(token)) => println!("Токен: {:?}", token), + /// Ok(None) => println!("Конец файла"), + /// Err(e) => println!("Ошибка: {:?}", e), + /// } + /// ``` + pub fn next_token(&mut self) -> Result, Error> { + // TODO: Реализовать распознавание следующего токена + // Алгоритм: + // 1. Пропустить пробельные символы + // 2. Проверить, достигнут ли конец файла + // 3. Проверить, находимся ли внутри комментария + // 4. Использовать ДКА для распознавания токена + // 5. Вернуть распознанный токен или ошибку + + self.skip_whitespace(); + + if self.position >= self.source.len() { + return Ok(Some(Token::new( + TokenKind::Eof, + self.current_position, + String::new(), + ))); + } + + // Заглушка - возвращает ошибку + Err(Error::new( + ErrorKind::Other("Не реализовано"), + self.current_position, + )) + } +} + +impl<'a> Iterator for Tokenizer<'a> { + type Item = Result; + + fn next(&mut self) -> Option { + // TODO: Реализовать итератор токенов + match self.next_token() { + Ok(Some(token)) => { + if matches!(token.kind, TokenKind::Eof) { + None + } else { + Some(Ok(token)) + } + } + Ok(None) => None, + Err(e) => Some(Err(e)), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // TODO: Написать тесты для Tokenizer + // - Тест создания токенизатора + // - Тест next_token для различных типов токенов + // - Тест обработки комментариев + // - Тест обработки идентификаторов (кириллица, латиница) + // - Тест обработки операторов + // - Тест отслеживания позиций + // - Тест итератора +} + diff --git a/src/utf8.rs b/src/utf8.rs new file mode 100644 index 0000000..cfd0d84 --- /dev/null +++ b/src/utf8.rs @@ -0,0 +1,202 @@ +//! Модуль обработки UTF-8 последовательностей для no_std окружения. +//! +//! Обеспечивает декодирование UTF-8 байтов в Unicode кодпоинты +//! без использования стандартной библиотеки. + +/// Результат декодирования UTF-8 символа. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DecodeResult { + /// Успешно декодирован символ + /// - первый параметр: Unicode кодпоинт (char) + /// - второй параметр: количество байт, использованных для декодирования (1-4) + Ok(char, usize), + /// Неполная последовательность (нужно больше байт) + Incomplete, + /// Некорректная UTF-8 последовательность + Invalid, +} + +/// Декодирует следующий UTF-8 символ из байтового буфера. +/// +/// # Параметры +/// - `bytes`: слайс байтов, начинающийся с первого байта UTF-8 последовательности +/// +/// # Возвращает +/// - `DecodeResult::Ok(char, usize)`: успешно декодированный символ и количество использованных байт +/// - `DecodeResult::Incomplete`: последовательность неполная (нужно больше байт) +/// - `DecodeResult::Invalid`: некорректная UTF-8 последовательность +/// +/// # Пример +/// ``` +/// use tverd_plus_tokenizer::utf8::{decode_utf8, DecodeResult}; +/// +/// let bytes = "Ъ".as_bytes(); +/// match decode_utf8(bytes) { +/// DecodeResult::Ok(ch, len) => { +/// assert_eq!(ch, 'Ъ'); +/// assert_eq!(len, 2); +/// } +/// _ => panic!("Ожидался успешный результат"), +/// } +/// ``` +pub fn decode_utf8(bytes: &[u8]) -> DecodeResult { + // TODO: Реализовать декодирование UTF-8 без использования std + // Алгоритм: + // 1. Проверить первый байт для определения длины последовательности + // 2. Проверить, что в буфере достаточно байт + // 3. Проверить корректность старших бит последующих байт + // 4. Вычислить кодпоинт и вернуть DecodeResult::Ok + // 5. Вернуть DecodeResult::Incomplete если не хватает байт + // 6. Вернуть DecodeResult::Invalid при некорректной последовательности + + if bytes.is_empty() { + return DecodeResult::Incomplete; + } + + let first = bytes[0]; + + // ASCII символ (0xxxxxxx) + if first & 0x80 == 0 { + return DecodeResult::Ok(first as char, 1); + } + + // Двухбайтовая последовательность (110xxxxx 10xxxxxx) + if first & 0xE0 == 0xC0 { + if bytes.len() < 2 { + return DecodeResult::Incomplete; + } + if bytes[1] & 0xC0 != 0x80 { + return DecodeResult::Invalid; + } + let code_point = ((first & 0x1F) as u32) << 6 | ((bytes[1] & 0x3F) as u32); + if code_point < 0x80 { + return DecodeResult::Invalid; // Overlong encoding + } + if let Some(ch) = core::char::from_u32(code_point) { + return DecodeResult::Ok(ch, 2); + } + return DecodeResult::Invalid; + } + + // Трехбайтовая последовательность (1110xxxx 10xxxxxx 10xxxxxx) + if first & 0xF0 == 0xE0 { + if bytes.len() < 3 { + return DecodeResult::Incomplete; + } + if bytes[1] & 0xC0 != 0x80 || bytes[2] & 0xC0 != 0x80 { + return DecodeResult::Invalid; + } + let code_point = ((first & 0x0F) as u32) << 12 + | ((bytes[1] & 0x3F) as u32) << 6 + | ((bytes[2] & 0x3F) as u32); + if code_point < 0x800 { + return DecodeResult::Invalid; // Overlong encoding + } + if let Some(ch) = core::char::from_u32(code_point) { + return DecodeResult::Ok(ch, 3); + } + return DecodeResult::Invalid; + } + + // Четырехбайтовая последовательность (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx) + if first & 0xF8 == 0xF0 { + if bytes.len() < 4 { + return DecodeResult::Incomplete; + } + if bytes[1] & 0xC0 != 0x80 || bytes[2] & 0xC0 != 0x80 || bytes[3] & 0xC0 != 0x80 { + return DecodeResult::Invalid; + } + let code_point = ((first & 0x07) as u32) << 18 + | ((bytes[1] & 0x3F) as u32) << 12 + | ((bytes[2] & 0x3F) as u32) << 6 + | ((bytes[3] & 0x3F) as u32); + if code_point < 0x10000 || code_point > 0x10FFFF { + return DecodeResult::Invalid; + } + if let Some(ch) = core::char::from_u32(code_point) { + return DecodeResult::Ok(ch, 4); + } + return DecodeResult::Invalid; + } + + DecodeResult::Invalid +} + +/// Итератор по UTF-8 символам в байтовом слайсе. +/// +/// Позволяет последовательно декодировать UTF-8 символы из байтового буфера. +pub struct Utf8Chars<'a> { + bytes: &'a [u8], + position: usize, +} + +impl<'a> Utf8Chars<'a> { + /// Создает новый итератор UTF-8 символов. + /// + /// # Параметры + /// - `bytes`: байтовый слайс для декодирования + /// + /// # Пример + /// ``` + /// use tverd_plus_tokenizer::utf8::Utf8Chars; + /// + /// let text = "Ъ+"; + /// let mut chars = Utf8Chars::new(text.as_bytes()); + /// ``` + pub fn new(bytes: &'a [u8]) -> Self { + // TODO: Реализовать создание итератора + Self { + bytes, + position: 0, + } + } +} + +impl<'a> Iterator for Utf8Chars<'a> { + type Item = Result; + + fn next(&mut self) -> Option { + // TODO: Реализовать итерацию по UTF-8 символам + if self.position >= self.bytes.len() { + return None; + } + + let remaining = &self.bytes[self.position..]; + match decode_utf8(remaining) { + DecodeResult::Ok(ch, len) => { + self.position += len; + Some(Ok(ch)) + } + DecodeResult::Incomplete => { + // Неполная последовательность в конце буфера + None + } + DecodeResult::Invalid => { + self.position += 1; // Пропускаем некорректный байт + Some(Err(DecodeError::InvalidUtf8)) + } + } + } +} + +/// Ошибка декодирования UTF-8. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DecodeError { + /// Некорректная UTF-8 последовательность + InvalidUtf8, +} + +#[cfg(test)] +mod tests { + use super::*; + + // TODO: Написать тесты для UTF-8 декодирования + // - Тест декодирования ASCII символов + // - Тест декодирования двухбайтовых последовательностей (кириллица) + // - Тест декодирования трехбайтовых последовательностей + // - Тест декодирования четырехбайтовых последовательностей + // - Тест обработки неполных последовательностей + // - Тест обработки некорректных последовательностей + // - Тест итератора Utf8Chars +} + diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs new file mode 100644 index 0000000..37fb9c1 --- /dev/null +++ b/tests/integration_tests.rs @@ -0,0 +1,25 @@ +//! Интеграционные тесты для токенизатора языка Ъ+. + +// TODO: Добавить интеграционные тесты +// - Тест полного цикла токенизации простого примера +// - Тест обработки различных типов токенов в одном файле +// - Тест обработки больших файлов +// - Тест производительности токенизации + +#[cfg(test)] +mod tests { + // use tverd_plus_tokenizer::*; + + #[test] + fn test_integration_basic() { + // TODO: Реализовать базовый интеграционный тест + // Пример: токенизация простого кода с несколькими типами токенов + } + + #[test] + fn test_integration_complex() { + // TODO: Реализовать сложный интеграционный тест + // Пример: токенизация кода с вложенными комментариями и различными идентификаторами + } +} + diff --git a/tests/test_comments.rs b/tests/test_comments.rs new file mode 100644 index 0000000..97ec516 --- /dev/null +++ b/tests/test_comments.rs @@ -0,0 +1,51 @@ +//! Тесты для вложенных комментариев. + +// TODO: Добавить тесты для комментариев +// - Тест простого комментария (<[ комментарий ]>) +// - Тест вложенных комментариев (<[ внешний <[ внутренний ]> внешний ]>) +// - Тест многоуровневой вложенности +// - Тест комментариев с различным содержимым +// - Тест ошибок несоответствия комментариев +// - Тест комментариев в различных позициях кода + +#[cfg(test)] +mod tests { + // use tverd_plus_tokenizer::*; + + #[test] + fn test_simple_comment() { + // TODO: Тест простого комментария + // Пример: "<[ комментарий ]>" + } + + #[test] + fn test_nested_comments() { + // TODO: Тест вложенных комментариев + // Пример: "<[ внешний <[ внутренний ]> внешний ]>" + } + + #[test] + fn test_multiple_nesting_levels() { + // TODO: Тест многоуровневой вложенности + // Пример: "<[ 1 <[ 2 <[ 3 ]> 2 ]> 1 ]>" + } + + #[test] + fn test_comments_with_content() { + // TODO: Тест комментариев с различным содержимым + // Пример: "<[ комментарий с @символами и операторами |> ]>" + } + + #[test] + fn test_unmatched_comment_close() { + // TODO: Тест ошибки несоответствия комментариев + // Пример: "]> без открытия" + } + + #[test] + fn test_comments_in_code() { + // TODO: Тест комментариев в различных позициях кода + // Пример: "Ъ+ <[ комментарий ]> @переменная" + } +} + diff --git a/tests/test_cyrillic.rs b/tests/test_cyrillic.rs new file mode 100644 index 0000000..6a7a67a --- /dev/null +++ b/tests/test_cyrillic.rs @@ -0,0 +1,50 @@ +//! Тесты для верификации кириллических идентификаторов. + +// TODO: Добавить тесты для кириллических идентификаторов +// - Тест идентификаторов переменных с кириллицей (@переменная) +// - Тест идентификаторов меток с кириллицей (:метка) +// - Тест предикатов с кириллицей (?предикат) +// - Тест смешанных идентификаторов (кириллица + латиница) +// - Тест различных регистров кириллицы (заглавные, строчные, ё/Ё) + +#[cfg(test)] +mod tests { + // use tverd_plus_tokenizer::*; + + #[test] + fn test_cyrillic_variable_identifier() { + // TODO: Тест идентификатора переменной с кириллицей + // Пример: "@переменная" + } + + #[test] + fn test_cyrillic_label_identifier() { + // TODO: Тест идентификатора метки с кириллицей + // Пример: ":метка" + } + + #[test] + fn test_cyrillic_predicate_identifier() { + // TODO: Тест предиката с кириллицей + // Пример: "?предикат" + } + + #[test] + fn test_mixed_cyrillic_latin() { + // TODO: Тест смешанных идентификаторов + // Пример: "@переменнаяVar" + } + + #[test] + fn test_cyrillic_case_sensitivity() { + // TODO: Тест различных регистров кириллицы + // Пример: "@Переменная", "@ПЕРЕМЕННАЯ", "@переменная" + } + + #[test] + fn test_cyrillic_yo_letter() { + // TODO: Тест буквы ё/Ё + // Пример: "@пёс", "@ПЁС" + } +} + diff --git a/tests/test_latin.rs b/tests/test_latin.rs new file mode 100644 index 0000000..63f74e1 --- /dev/null +++ b/tests/test_latin.rs @@ -0,0 +1,50 @@ +//! Тесты для верификации латинских идентификаторов. + +// TODO: Добавить тесты для латинских идентификаторов +// - Тест идентификаторов переменных с латиницей (@variable) +// - Тест идентификаторов меток с латиницей (:label) +// - Тест предикатов с латиницей (?predicate) +// - Тест смешанного регистра (camelCase, snake_case) +// - Тест цифр в идентификаторах (после первого символа) + +#[cfg(test)] +mod tests { + // use tverd_plus_tokenizer::*; + + #[test] + fn test_latin_variable_identifier() { + // TODO: Тест идентификатора переменной с латиницей + // Пример: "@variable" + } + + #[test] + fn test_latin_label_identifier() { + // TODO: Тест идентификатора метки с латиницей + // Пример: ":label" + } + + #[test] + fn test_latin_predicate_identifier() { + // TODO: Тест предиката с латиницей + // Пример: "?predicate" + } + + #[test] + fn test_camel_case() { + // TODO: Тест camelCase идентификаторов + // Пример: "@camelCase" + } + + #[test] + fn test_snake_case() { + // TODO: Тест snake_case идентификаторов + // Пример: "@snake_case" + } + + #[test] + fn test_identifier_with_digits() { + // TODO: Тест идентификаторов с цифрами + // Пример: "@var123", "@var1_2" + } +} + diff --git a/tests/test_operators.rs b/tests/test_operators.rs new file mode 100644 index 0000000..3555c51 --- /dev/null +++ b/tests/test_operators.rs @@ -0,0 +1,51 @@ +//! Тесты для операторов языка Ъ+. + +// TODO: Добавить тесты для операторов +// - Тест оператора начала блока (Ъ+) +// - Тест оператора конца блока (Ъ-) +// - Тест оператора конвейера (|>) +// - Тест оператора записи (<<) +// - Тест позиций операторов +// - Тест комбинаций операторов + +#[cfg(test)] +mod tests { + // use tverd_plus_tokenizer::*; + + #[test] + fn test_block_start_operator() { + // TODO: Тест оператора начала блока + // Пример: "Ъ+" + } + + #[test] + fn test_block_end_operator() { + // TODO: Тест оператора конца блока + // Пример: "Ъ-" + } + + #[test] + fn test_pipeline_operator() { + // TODO: Тест оператора конвейера + // Пример: "|>" + } + + #[test] + fn test_write_operator() { + // TODO: Тест оператора записи + // Пример: "<<" + } + + #[test] + fn test_operator_combinations() { + // TODO: Тест комбинаций операторов + // Пример: "Ъ+ @var |> <<" + } + + #[test] + fn test_operator_positions() { + // TODO: Тест отслеживания позиций операторов + // Проверить, что позиции операторов корректно отслеживаются + } +} + diff --git a/tests/test_positions.rs b/tests/test_positions.rs new file mode 100644 index 0000000..22eb1d0 --- /dev/null +++ b/tests/test_positions.rs @@ -0,0 +1,51 @@ +//! Тесты для отслеживания позиций токенов. + +// TODO: Добавить тесты для отслеживания позиций +// - Тест позиции первого токена +// - Тест позиции после пробелов +// - Тест позиции после новой строки +// - Тест позиции в многострочном коде +// - Тест позиции в комментариях +// - Тест позиции ошибок + +#[cfg(test)] +mod tests { + // use tverd_plus_tokenizer::*; + + #[test] + fn test_first_token_position() { + // TODO: Тест позиции первого токена + // Проверить, что первый токен имеет позицию (1, 1) + } + + #[test] + fn test_position_after_whitespace() { + // TODO: Тест позиции после пробелов + // Пример: " @var" - проверить позицию @var + } + + #[test] + fn test_position_after_newline() { + // TODO: Тест позиции после новой строки + // Пример: "\n@var" - проверить, что @var на строке 2 + } + + #[test] + fn test_multiline_positions() { + // TODO: Тест позиций в многострочном коде + // Пример: "Ъ+\n @var\n :метка" + } + + #[test] + fn test_comment_positions() { + // TODO: Тест позиций комментариев + // Проверить, что комментарии имеют корректные позиции + } + + #[test] + fn test_error_positions() { + // TODO: Тест позиций ошибок + // Проверить, что ошибки содержат корректные позиции + } +} +