kbvm 0.1.5

An implementation of the XKB specification
Documentation
use {
    crate::xkb::{
        code_slice::CodeSlice,
        compose::{
            parser::Parser,
            token::{Punctuation, Token},
        },
        diagnostic::DiagnosticKind,
        span::{Span, SpanExt, Spanned},
    },
    bstr::ByteSlice,
    debug_fn::debug_fn,
    std::fmt::{self, Formatter},
    thiserror::Error,
};

#[derive(Debug, Clone, Error)]
pub(crate) enum ParserError {
    #[error(
        "expected {}, but encountered EOF",
        debug_fn(|f| write_expected(f, .0.expected)),
    )]
    ExpectedButEof(ExpectedButEof),
    #[error(
        "expected {}, but found {}",
        debug_fn(|f| write_expected(f, .0.expected)),
        debug_fn(|f| write_actual(f, &.0.actual)),
    )]
    UnexpectedToken(UnexpectedToken),
    #[error(
        "expected end of line, but found {}",
        debug_fn(|f| write_actual(f, .0)),
    )]
    ExpectedEol(ActualToken),
    #[error(
        "unknown keysym {}",
        .0.as_bstr(),
    )]
    UnknownKeysym(CodeSlice<'static>),
}

impl ParserError {
    pub(crate) fn diagnostic_kind(&self) -> DiagnosticKind {
        match self {
            ParserError::ExpectedButEof(_) => DiagnosticKind::UnexpectedEof,
            ParserError::UnexpectedToken(_) => DiagnosticKind::UnexpectedToken,
            ParserError::ExpectedEol(_) => DiagnosticKind::ExpectedEol,
            ParserError::UnknownKeysym(_) => DiagnosticKind::UnknownKeysym,
        }
    }
}

fn write_actual(f: &mut Formatter<'_>, actual: &ActualToken) -> fmt::Result {
    match actual {
        ActualToken::Ident(i) => write!(f, "`{}`", i.as_bytes().as_bstr()),
        ActualToken::String(i) => write!(f, "{:?}", i.as_bytes().as_bstr()),
        ActualToken::Keysym(i) => write!(f, "<{}>", i.as_bytes().as_bstr()),
        ActualToken::Token(t) => match t {
            Token::Ident(_) => f.write_str("an identifier"),
            Token::Punctuation(p) => write!(f, "`{}`", punctuation_string(*p)),
            Token::String(_) => f.write_str("a string"),
            Token::Keysym(_) => f.write_str("a keysym"),
        },
    }
}

fn write_expected(f: &mut Formatter<'_>, expected: &[Expected]) -> fmt::Result {
    if let Some(e) = get_unique_expected(expected) {
        return write_single_expected(f, e);
    }
    write_expected_(f, expected, true)
}

fn write_expected_(f: &mut Formatter<'_>, expected: &[Expected], or_prefix: bool) -> fmt::Result {
    let last = expected.len() - 1;
    for (idx, e) in expected.iter().enumerate() {
        if idx > 0 {
            f.write_str(", ")?;
        }
        if or_prefix && idx == last {
            f.write_str("or ")?;
        }
        write_single_expected(f, e)?;
    }
    Ok(())
}

fn get_unique_expected(expected: &[Expected]) -> Option<&Expected> {
    if expected.len() != 1 {
        return None;
    }
    Some(&expected[0])
}

fn write_single_expected(f: &mut Formatter<'_>, expected: &Expected) -> fmt::Result {
    match expected {
        Expected::AnyIdent => f.write_str("an identifier"),
        Expected::AnyString => f.write_str("a string"),
        Expected::AnyModifier => f.write_str("a modifier"),
        Expected::AnyKeysym => f.write_str("a keysym"),
        Expected::Punctuation(p) => {
            write!(f, "`{}`", punctuation_string(*p))
        }
    }
}

fn punctuation_string(p: Punctuation) -> &'static str {
    match p {
        Punctuation::Exclam => "!",
        Punctuation::Tilde => "~",
        Punctuation::Colon => ":",
    }
}

#[derive(Debug, Clone)]
pub(crate) struct ExpectedButEof {
    expected: &'static [Expected],
}

#[derive(Debug, Clone)]
pub(crate) struct UnexpectedToken {
    expected: &'static [Expected],
    actual: ActualToken,
}

#[derive(Debug)]
pub(crate) enum Expected {
    AnyIdent,
    AnyString,
    AnyModifier,
    AnyKeysym,
    Punctuation(Punctuation),
}

#[derive(Clone, Debug, PartialEq)]
pub(crate) enum ActualToken {
    Ident(CodeSlice<'static>),
    String(CodeSlice<'static>),
    Keysym(CodeSlice<'static>),
    Token(Token),
}

impl Parser<'_, '_, '_> {
    pub(super) fn expected_but_eof(
        &self,
        span: Span,
        expected: &'static [Expected],
    ) -> Spanned<ParserError> {
        ParserError::ExpectedButEof(ExpectedButEof { expected }).spanned2(span)
    }

    fn token_to_actual(&self, token: Spanned<Token>) -> ActualToken {
        match token.val {
            Token::Ident(i) => ActualToken::Ident(self.interner.get(i).to_owned()),
            Token::Keysym(i) => ActualToken::Keysym(self.interner.get(i).to_owned()),
            Token::String(i) => ActualToken::String(self.interner.get(i).to_owned()),
            _ => ActualToken::Token(token.val),
        }
    }

    pub(super) fn expected_eol(&self, token: Spanned<Token>) -> Spanned<ParserError> {
        let actual = self.token_to_actual(token);
        ParserError::ExpectedEol(actual).spanned2(token.span)
    }

    pub(super) fn unexpected_token(
        &self,
        expected: &'static [Expected],
        token: Spanned<Token>,
    ) -> Spanned<ParserError> {
        let actual = self.token_to_actual(token);
        ParserError::UnexpectedToken(UnexpectedToken { expected, actual }).spanned2(token.span)
    }

    pub(super) fn unknown_keysym(&self, code: &CodeSlice<'_>, span: Span) -> Spanned<ParserError> {
        ParserError::UnknownKeysym(code.to_owned()).spanned2(span)
    }
}

pub(super) const LHS: &[Expected] = &[
    Expected::Punctuation(punctuation![!]),
    Expected::Punctuation(punctuation![~]),
    Expected::Punctuation(punctuation![:]),
    Expected::AnyKeysym,
    Expected::AnyModifier,
];