veryl-parser 0.20.0

A modern hardware description language
Documentation
use miette::{self, Diagnostic, NamedSource, SourceSpan};
use parol_runtime::{ParolError, TokenVec};
use thiserror::Error;

#[derive(Error, Diagnostic, Debug)]
pub enum ParserError {
    #[error(transparent)]
    #[diagnostic(transparent)]
    SyntaxError(Box<SyntaxError>),

    #[error(transparent)]
    ParserError(#[from] parol_runtime::ParserError),

    #[error(transparent)]
    LexerError(#[from] parol_runtime::LexerError),

    #[error(transparent)]
    UserError(#[from] anyhow::Error),
}

#[derive(Error, Diagnostic, Debug)]
#[diagnostic(code(ParserError::SyntaxError))]
pub struct SyntaxError {
    pub cause: String,
    #[source_code]
    input: NamedSource<FileSource>,
    #[label("Error location")]
    pub error_location: SourceSpan,
    pub unexpected_tokens: Vec<UnexpectedToken>,
    pub expected_tokens: ExpectedTokens,
    #[help]
    pub help: String,
}

impl std::fmt::Display for SyntaxError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if !self.unexpected_tokens.is_empty() {
            let token = self.unexpected_tokens[0].token_type;
            f.write_str(&format!("Unexpected token: '{token}'"))
        } else {
            f.write_str("Syntax Error")
        }
    }
}

fn l_angle(unexpected_token: TokenType, _expected_tokens: &ExpectedTokens) -> bool {
    unexpected_token == TokenType::LAngle
}

fn r_angle(unexpected_token: TokenType, _expected_tokens: &ExpectedTokens) -> bool {
    unexpected_token == TokenType::RAngle
}

fn colon_instead_of_in(unexpected_token: TokenType, expected_tokens: &ExpectedTokens) -> bool {
    unexpected_token == TokenType::Colon && expected_tokens.any(TokenType::In)
}

fn comma_instead_of_assignment_operator(
    unexpected_token: TokenType,
    expected_tokens: &ExpectedTokens,
) -> bool {
    unexpected_token == TokenType::Comma && expected_tokens.any(TokenType::AssignmentOperator)
}

fn l_brace_instead_of_colon(unexpected_token: TokenType, expected_tokens: &ExpectedTokens) -> bool {
    unexpected_token == TokenType::LBrace && expected_tokens.any(TokenType::Colon)
}

fn keyword_as_identifier(unexpected_token: TokenType, expected_tokens: &ExpectedTokens) -> bool {
    unexpected_token.is_keyword() && expected_tokens.any(TokenType::Identifier)
}

impl From<parol_runtime::SyntaxError> for SyntaxError {
    fn from(value: parol_runtime::SyntaxError) -> Self {
        let unexpected_tokens: Vec<_> = UnexpectedTokens(value.unexpected_tokens).into();
        let expected_tokens: ExpectedTokens = (&value.expected_tokens).into();

        let mut help = String::new();
        if !unexpected_tokens.is_empty() {
            let token = unexpected_tokens[0].token_type;
            if l_angle(token, &expected_tokens) {
                help = "If you mean \"less than operator\", please use '<:'".to_string();
            } else if r_angle(token, &expected_tokens) {
                help = "If you mean \"greater than operator\", please use '>:'".to_string();
            } else if colon_instead_of_in(token, &expected_tokens) {
                help = "for declaration doesn't need type specifier (e.g. 'for i in 0..10 {')"
                    .to_string();
            } else if comma_instead_of_assignment_operator(token, &expected_tokens) {
                help = "single case statement with bit concatenation at the left-hand side is not allowed,\nplease surround it by '{}' (e.g. 'x: { {a, b} = 1; }')".to_string();
            } else if l_brace_instead_of_colon(token, &expected_tokens) {
                help =
                    "The first arm of generate-if declaration needs label (e.g. 'if x :label {')"
                        .to_string();
            } else if keyword_as_identifier(token, &expected_tokens) {
                help = format!(
                    "'{}' is a reserved keyword and cannot be used as an identifier",
                    token
                );
            }
        }

        Self {
            cause: value.cause,
            input: value.input.map(|e| FileSource(*e).into()).unwrap(),
            error_location: Location(*value.error_location).into(),
            unexpected_tokens,
            expected_tokens,
            help,
        }
    }
}

#[derive(Error, Diagnostic, Debug)]
#[error("Unexpected token: {name} ({token_type})")]
#[diagnostic(help("Unexpected token"), code(parol_runtime::unexpected_token))]
pub struct UnexpectedToken {
    name: String,
    token_type: TokenType,
    #[label("Unexpected token")]
    pub(crate) token: SourceSpan,
}

include!("generated/token_type_generated.rs");

impl From<ParolError> for ParserError {
    fn from(x: ParolError) -> ParserError {
        match x {
            ParolError::ParserError(x) => match x {
                parol_runtime::ParserError::SyntaxErrors { mut entries } if !entries.is_empty() => {
                    ParserError::SyntaxError(Box::new(entries.remove(0).into()))
                }
                _ => ParserError::ParserError(x),
            },
            ParolError::LexerError(x) => ParserError::LexerError(x),
            ParolError::UserError(x) => ParserError::UserError(x),
        }
    }
}

struct FileSource(parol_runtime::FileSource);

impl miette::SourceCode for FileSource {
    fn read_span<'a>(
        &'a self,
        span: &SourceSpan,
        context_lines_before: usize,
        context_lines_after: usize,
    ) -> Result<Box<dyn miette::SpanContents<'a> + 'a>, miette::MietteError> {
        <str as miette::SourceCode>::read_span(
            &self.0.input,
            span,
            context_lines_before,
            context_lines_after,
        )
    }
}

impl From<FileSource> for NamedSource<FileSource> {
    fn from(file_source: FileSource) -> Self {
        let file_name = file_source.0.file_name.clone();
        let file_name = file_name.to_str().unwrap_or("<Bad file name>");
        Self::new(file_name, file_source)
    }
}

struct Location(parol_runtime::Location);

impl From<Location> for SourceSpan {
    fn from(location: Location) -> Self {
        SourceSpan::new((location.0.start as usize).into(), location.0.len())
    }
}

struct UnexpectedTokens(Vec<parol_runtime::UnexpectedToken>);

impl From<UnexpectedTokens> for Vec<UnexpectedToken> {
    fn from(value: UnexpectedTokens) -> Self {
        value
            .0
            .into_iter()
            .map(|v| UnexpectedToken {
                name: v.name,
                token_type: v.token_type.as_str().into(),
                token: Location(v.token).into(),
            })
            .collect::<Vec<UnexpectedToken>>()
    }
}

#[derive(Debug)]
pub struct ExpectedTokens(Vec<TokenType>);

impl ExpectedTokens {
    pub fn any(&self, x: TokenType) -> bool {
        self.0.contains(&x)
    }
}

impl From<&TokenVec> for ExpectedTokens {
    fn from(value: &TokenVec) -> Self {
        ExpectedTokens(value.iter().map(|x| x.as_str().into()).collect())
    }
}