feat: базовая структура проекта; docs: грамматика формы бэкуса-наура
This commit is contained in:
202
src/utf8.rs
Normal file
202
src/utf8.rs
Normal file
@@ -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<char, DecodeError>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// 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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user