mathic 0.2.0

A compiler with builtin support of symbolic operations, built with LLVM/MLIR
use std::{
    fmt::{Display, Formatter},
    path::Path,
};

use ariadne::{Report, ReportBuilder, ReportKind};
use thiserror::Error;

use crate::{
    diagnostics::ReportSpan,
    parser::{Span, lexer::SpannedToken, token::Token},
};

#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum LexError {
    #[default]
    TokenError,
    InvalidCharacter(char),
    UnterminatedString,
    UnterminatedComment,
    InvalidNumber(String),
}

#[derive(Debug, Clone)]
pub struct FoundToken {
    pub lexeme: String,
    pub span: Span,
}

impl<'a> From<SpannedToken<'a>> for FoundToken {
    fn from(token: SpannedToken<'a>) -> Self {
        Self {
            lexeme: token.lexeme.to_string(),
            span: token.span,
        }
    }
}

#[derive(Debug, Clone)]
pub enum ExpectedToken {
    Statement,
    Identifier,
    Expression,
    Token(Token),
    Custom(String),
}

impl Display for ExpectedToken {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            ExpectedToken::Statement => write!(f, "statement"),
            ExpectedToken::Expression => write!(f, "expression"),
            ExpectedToken::Identifier => write!(f, "identifier"),
            ExpectedToken::Token(t) => write!(f, "{}", t),
            ExpectedToken::Custom(s) => write!(f, "{}", s),
        }
    }
}

impl ExpectedToken {
    pub fn help(&self) -> &'static str {
        match self {
            ExpectedToken::Statement => {
                "valid statements include: function declarations, if/while/for, return, or blocks"
            }
            ExpectedToken::Identifier => {
                "only variable or function names can be called, e.g., 'foo()' or 'bar()'"
            }
            ExpectedToken::Expression => {
                "expressions can be: numbers, booleans, identifiers, or parenthesized expressions"
            }
            ExpectedToken::Token(_) | ExpectedToken::Custom(_) => "",
        }
    }
}

#[derive(Debug, Clone)]
pub enum SyntaxError {
    UnexpectedToken {
        found: FoundToken,
        expected: ExpectedToken,
    },
    UnexpectedEnd {
        span: Span,
    },
    MissingToken {
        expected: Token,
        span: Span,
    },
}

#[derive(Debug, Error)]
pub enum ParseError {
    #[error("Lexical error")]
    Lexical(LexError, Span),
    #[error("Syntax error")]
    Syntax(SyntaxError),
}

impl LexError {
    pub fn from_lexer(lexer: &logos::Lexer<Token>) -> LexError {
        let source = lexer.source();
        let span = lexer.span();
        let slice = &source[span];

        let first = slice.chars().next().unwrap_or('\0');

        if slice.starts_with('"') {
            LexError::UnterminatedString
        } else if slice.starts_with("/*") {
            LexError::UnterminatedComment
        } else if first.is_numeric() {
            LexError::InvalidNumber(slice.to_string())
        } else if !first.is_alphanumeric() && !first.is_whitespace() {
            LexError::InvalidCharacter(first)
        } else {
            LexError::TokenError
        }
    }
}

pub fn format_parse_error<'err>(
    file_path: &Path,
    error: &ParseError,
) -> ReportBuilder<'err, ReportSpan> {
    let path = file_path.display().to_string();

    let (code, msg, span, help) = match error {
        ParseError::Lexical(lex_error, span) => {
            let (code, msg) = match lex_error {
                LexError::TokenError => ("L001", "unknown token".to_string()),
                LexError::InvalidCharacter(c) => ("L002", format!("invalid character: '{}'", c)),
                LexError::UnterminatedString => ("L003", "unterminated string".to_string()),
                LexError::UnterminatedComment => ("L004", "unterminated comment".to_string()),
                LexError::InvalidNumber(n) => ("L005", format!("invalid number: {}", n)),
            };
            (code, msg, *span, "Lexical Error".to_string())
        }
        ParseError::Syntax(syntax_error) => match syntax_error {
            SyntaxError::UnexpectedToken { found, expected } => (
                "E001",
                format!("expected {}, found '{}'", expected, found.lexeme),
                found.span,
                expected.help().to_string(),
            ),
            SyntaxError::UnexpectedEnd { span } => (
                "E002",
                "found an unexpected end of file".to_string(),
                *span,
                String::new(),
            ),
            SyntaxError::MissingToken { expected, span } => (
                "E003",
                format!("expected '{}'", expected),
                *span,
                format!("add '{}' here to complete the syntax", expected),
            ),
        },
    };

    let report_span = ReportSpan { path, span };

    Report::build(ReportKind::Error, report_span.clone())
        .with_code(code)
        .with_message("Syntax Error")
        .with_label(
            ariadne::Label::new(report_span)
                .with_color(ariadne::Color::Red)
                .with_message(msg),
        )
        .with_help(help)
}