aldrin-parser 0.13.0

Aldrin schema parser library.
Documentation
use super::{Error, ErrorKind};
use crate::diag::{Diagnostic, DiagnosticKind, Renderer};
use crate::grammar::Rule;
use crate::{Parser, Span};
use pest::error::InputLocation;
use std::borrow::Cow;
use std::collections::BTreeSet;

#[derive(Debug)]
pub(crate) struct InvalidSyntax {
    schema_name: String,
    pos: usize,
    expected: BTreeSet<Expected>,
}

impl InvalidSyntax {
    pub(crate) fn new<S>(schema_name: S, err: pest::error::Error<Rule>) -> Self
    where
        S: Into<String>,
    {
        use pest::error::ErrorVariant;

        let pos = match err.location {
            InputLocation::Pos(index) => index,
            InputLocation::Span((from, _)) => from,
        };

        let positives = match err.variant {
            ErrorVariant::ParsingError { positives, .. } => positives,
            ErrorVariant::CustomError { .. } => unreachable!(),
        };

        let mut expected = BTreeSet::new();
        for rule in positives {
            Expected::add(rule, &mut expected);
        }

        Self {
            schema_name: schema_name.into(),
            pos,
            expected,
        }
    }
}

impl Diagnostic for InvalidSyntax {
    fn kind(&self) -> DiagnosticKind {
        DiagnosticKind::Error
    }

    fn schema_name(&self) -> &str {
        &self.schema_name
    }

    fn render(&self, renderer: &Renderer, parser: &Parser) -> String {
        let mut title = "expected ".to_owned();
        let mut iter = self.expected.iter().peekable();
        let mut first = true;
        let mut eof = false;

        while let Some(expected) = iter.next() {
            let expected: Cow<'static, str> = match expected {
                Expected::Attribute => "an attribute".into(),
                Expected::AttributeInline => "an inline attribute".into(),
                Expected::DocString => "a doc string".into(),
                Expected::DocStringInline => "an inline doc string".into(),

                Expected::Eof => {
                    eof = true;
                    continue;
                }

                Expected::Ident => "an identifier".into(),
                Expected::Keyword(kw) => format!("`{kw}`").into(),
                Expected::LitInt => "an integer literal".into(),
                Expected::LitPosInt => "a positive integer literal".into(),
                Expected::LitString => "a string literal".into(),
                Expected::LitUuid => "a uuid literal".into(),
                Expected::Token(tok) => format!("`{tok}`").into(),
            };

            if first {
                first = false;
            } else if iter.peek().is_some() || eof {
                title.push_str(", ");
            } else {
                title.push_str(" or ");
            }

            title.push_str(&expected);
        }

        if eof {
            if first {
                title.push_str("end of file");
            } else {
                title.push_str(" or end of file");
            }
        }

        let mut report = renderer.error(title);

        if let Some(schema) = parser.get_schema(&self.schema_name) {
            let width = schema.source().unwrap()[self.pos..]
                .chars()
                .next()
                .map(char::len_utf8)
                .unwrap_or(1);

            let span = Span {
                start: self.pos,
                end: self.pos + width,
            };

            report = report.snippet(schema, span, "");
        }

        report.render()
    }
}

impl From<InvalidSyntax> for Error {
    fn from(e: InvalidSyntax) -> Self {
        Self {
            kind: ErrorKind::InvalidSyntax(e),
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum Expected {
    Attribute,
    AttributeInline,
    DocString,
    DocStringInline,
    Eof,
    Ident,
    Keyword(&'static str),
    LitInt,
    LitPosInt,
    LitString,
    LitUuid,
    Token(&'static str),
}

impl Expected {
    fn add(rule: Rule, set: &mut BTreeSet<Self>) {
        const CONST_VALUE: &[Expected] = &[
            Expected::Keyword("i16"),
            Expected::Keyword("i32"),
            Expected::Keyword("i64"),
            Expected::Keyword("i8"),
            Expected::Keyword("string"),
            Expected::Keyword("u16"),
            Expected::Keyword("u32"),
            Expected::Keyword("u64"),
            Expected::Keyword("u8"),
            Expected::Keyword("uuid"),
        ];

        const DEF: &[Expected] = &[
            Expected::Attribute,
            Expected::DocString,
            Expected::Keyword("const"),
            Expected::Keyword("enum"),
            Expected::Keyword("newtype"),
            Expected::Keyword("service"),
            Expected::Keyword("struct"),
        ];

        const SERVICE_ITEM: &[Expected] = &[
            Expected::DocString,
            Expected::Keyword("event"),
            Expected::Keyword("fn"),
        ];

        const TYPE_NAME: &[Expected] = &[
            Expected::Ident,
            Expected::Keyword("bool"),
            Expected::Keyword("bytes"),
            Expected::Keyword("f32"),
            Expected::Keyword("f64"),
            Expected::Keyword("i16"),
            Expected::Keyword("i32"),
            Expected::Keyword("i64"),
            Expected::Keyword("i8"),
            Expected::Keyword("lifetime"),
            Expected::Keyword("map"),
            Expected::Keyword("object_id"),
            Expected::Keyword("option"),
            Expected::Keyword("receiver"),
            Expected::Keyword("result"),
            Expected::Keyword("sender"),
            Expected::Keyword("service_id"),
            Expected::Keyword("set"),
            Expected::Keyword("string"),
            Expected::Keyword("u16"),
            Expected::Keyword("u32"),
            Expected::Keyword("u64"),
            Expected::Keyword("u8"),
            Expected::Keyword("unit"),
            Expected::Keyword("uuid"),
            Expected::Keyword("value"),
            Expected::Keyword("vec"),
            Expected::Token("["),
        ];

        const INLINE: &[Expected] = &[Expected::Keyword("enum"), Expected::Keyword("struct")];
        const ARRAY_LEN: &[Expected] = &[Expected::Ident, Expected::LitPosInt];

        #[allow(clippy::use_self)]
        let add: &[&[Self]] = match rule {
            Rule::EOI => &[&[Expected::Eof]],
            Rule::array_len => &[ARRAY_LEN],
            Rule::attribute => &[&[Expected::Attribute]],
            Rule::attribute_inline => &[&[Expected::AttributeInline]],
            Rule::const_value => &[CONST_VALUE],
            Rule::def => &[DEF],
            Rule::doc_string => &[&[Expected::DocString]],
            Rule::doc_string_inline => &[&[Expected::DocStringInline]],
            Rule::event_fallback => &[&[Expected::DocString, Expected::Keyword("event")]],
            Rule::fn_fallback => &[&[Expected::DocString, Expected::Keyword("fn")]],
            Rule::ident => &[&[Expected::Ident]],
            Rule::kw_args => &[&[Expected::Keyword("args")]],
            Rule::kw_const => &[&[Expected::Keyword("const")]],
            Rule::kw_enum => &[&[Expected::Keyword("enum")]],
            Rule::kw_err => &[&[Expected::Keyword("err")]],
            Rule::kw_fallback => &[&[Expected::Keyword("fallback")]],
            Rule::kw_import => &[&[Expected::Keyword("import")]],
            Rule::kw_newtype => &[&[Expected::Keyword("newtype")]],
            Rule::kw_object_id => &[&[Expected::Keyword("object_id")]],
            Rule::kw_ok => &[&[Expected::Keyword("ok")]],
            Rule::kw_service => &[&[Expected::Keyword("service")]],
            Rule::kw_service_id => &[&[Expected::Keyword("service_id")]],
            Rule::kw_struct => &[&[Expected::Keyword("struct")]],
            Rule::kw_uuid | Rule::service_uuid => &[&[Expected::Keyword("uuid")]],
            Rule::kw_version | Rule::service_version => &[&[Expected::Keyword("version")]],
            Rule::lit_int => &[&[Expected::LitInt]],
            Rule::lit_string => &[&[Expected::LitString]],
            Rule::lit_uuid => &[&[Expected::LitUuid]],
            Rule::service_item | Rule::service_fallback => &[SERVICE_ITEM],
            Rule::struct_field => &[&[Expected::Keyword("required"), Expected::Ident]],
            Rule::tok_ang_close => &[&[Expected::Token(">")]],
            Rule::tok_ang_open => &[&[Expected::Token("<")]],
            Rule::tok_arrow => &[&[Expected::Token("->")]],
            Rule::tok_at => &[&[Expected::Token("@")]],
            Rule::tok_comma => &[&[Expected::Token(",")]],
            Rule::tok_cur_close => &[&[Expected::Token("}")]],
            Rule::tok_cur_open => &[&[Expected::Token("{")]],
            Rule::tok_eq => &[&[Expected::Token("=")]],
            Rule::tok_excl => &[&[Expected::Token("!")]],
            Rule::tok_par_close => &[&[Expected::Token(")")]],
            Rule::tok_par_open => &[&[Expected::Token("(")]],
            Rule::tok_scope => &[&[Expected::Token("::")]],
            Rule::tok_squ_close => &[&[Expected::Token("]")]],
            Rule::tok_squ_open => &[&[Expected::Token("[")]],
            Rule::tok_term => &[&[Expected::Token(";")]],
            Rule::type_name => &[TYPE_NAME],
            Rule::type_name_or_inline => &[TYPE_NAME, INLINE],
            _ => return,
        };

        for &slice in add {
            set.extend(slice);
        }
    }
}