keymaps 1.2.0

A rust crate which provides a standardized encoding for key codes
Documentation
// keymaps - A rust crate which provides a standardized encoding for key codes
//
// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// This file is part of the keymaps crate.
//
// You should have received a copy of the License along with this program.
// If not, see <https://www.gnu.org/licenses/lgpl-3.0.txt>.

use std::fmt::Display;

use crate::error;

pub(crate) struct Tokenizer<'a> {
    input: &'a str,
    index: usize,
    pub mode: Mode,
}

impl<'a> Tokenizer<'a> {
    pub(crate) fn new(input: &'a str) -> Self {
        Self {
            input,
            index: 0,
            mode: Mode::Default,
        }
    }
    pub(crate) fn index(&self) -> usize {
        self.index
    }
    pub(crate) fn is_empty(&self) -> bool {
        self.index == self.input.chars().count()
    }

    /// Consumes tokens while a predicate evaluates to true.
    pub(crate) fn take_while<F>(&mut self, mut pred: F) -> Result<Vec<Token>, error::KeyParse>
    where
        F: FnMut(Token) -> bool,
    {
        let mut buffer = vec![];

        loop {
            let ch = self.next()?;
            let should_continue = pred(ch);

            if !should_continue {
                break;
            }
            self.chomp();

            buffer.push(ch);
        }

        Ok(buffer)
    }

    fn next(&self) -> Result<Token, error::KeyParse> {
        if let Some(next) = self.maybe_next() {
            next
        } else {
            Err(error::KeyParse::UnexpectedEnd)
        }
    }
    fn maybe_next(&self) -> Option<Result<Token, error::KeyParse>> {
        self.maybe_get(self.index)
    }

    fn maybe_get(&self, index: usize) -> Option<Result<Token, error::KeyParse>> {
        let next = self.input.chars().nth(index)?;

        if self.mode == Mode::NormalValue {
            return Some(Ok(Token::Char(next)));
        }
        match next {
            '<' => Some(Ok(Token::AngularBracketOpen)),
            '>' => Some(Ok(Token::AngularBracketClose)),
            '-' => Some(Ok(Token::Dash)),
            other => Some(Ok(Token::Char(other))),
        }
    }

    pub(crate) fn find(&self, token: Token) -> Result<Option<usize>, error::KeyParse> {
        let mut current_index = self.index;
        loop {
            if let Some(next) = self.maybe_get(current_index) {
                if next? == token {
                    return Ok(Some(current_index));
                }

                current_index += 1;
            } else {
                return Ok(None);
            }
        }
    }

    pub(crate) fn peek(&self, token: Token) -> Result<bool, error::KeyParse> {
        if self.next()? == token {
            Ok(true)
        } else {
            Ok(false)
        }
    }

    pub(crate) fn expect_char(&mut self) -> Result<char, error::KeyParse> {
        let next = self.next()?;
        if let Token::Char(output) = next {
            self.chomp();
            Ok(output)
        } else {
            Err(error::KeyParse::ExpectedCharFound(next))
        }
    }
    pub(crate) fn expect(&mut self, token: Token) -> Result<(), error::KeyParse> {
        let next = self.next()?;
        if next == token {
            self.chomp();
            Ok(())
        } else {
            Err(error::KeyParse::ExpectedButFound {
                expected: token,
                found: next,
            })
        }
    }

    fn chomp(&mut self) {
        self.index += 1;
    }
}

#[derive(PartialEq, Eq)]
pub(crate) enum Mode {
    NormalValue,
    Default,
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub enum Token {
    AngularBracketOpen,
    AngularBracketClose,
    Dash,
    Char(char),
}

impl Display for Token {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        <Self as std::fmt::Debug>::fmt(self, f)
    }
}

impl Token {
    pub(crate) fn to_char(self) -> char {
        match self {
            Token::AngularBracketOpen => '<',
            Token::AngularBracketClose => '>',
            Token::Dash => '-',
            Token::Char(ch) => ch,
        }
    }
}