ginko 0.0.4

A device-tree source parser and analyzer
Documentation
use crate::dts::ast::CompilerDirective;
use crate::dts::data::{HasSpan, Span};
use crate::dts::lexer::TokenKind;
use itertools::Itertools;
use std::fmt::{Display, Formatter};
use std::io::Error;
use std::num::ParseIntError;

#[derive(PartialEq, Debug, Clone)]
pub enum NameContext {
    Label,
    NodeName,
    PropertyName,
    UnitAddress,
}

#[derive(PartialEq, Debug, Clone)]
pub enum DiagnosticKind {
    UnexpectedEOF,
    Expected(Vec<TokenKind>),
    ExpectedName(NameContext),
    OddNumberOfBytestringElements,
    IntError(ParseIntError),
    NonDtsV1,
    NameTooLong(usize, NameContext),
    IllegalChar(char, NameContext),
    IllegalStart(char, NameContext),
    UnresolvedReference,
    PropertyReferencedByNode,
    NonStringInCompatible,
    PathCannotBeEmpty,
    PropertyAfterNode,
    UnbalancedParentheses,
    MisplacedDtsHeader,
    DuplicateDirective(CompilerDirective),
    ParserError(String),
    IOError(String),
    ErrorsInInclude,
}

pub enum SeverityLevel {
    Error,
    Warning,
    Hint,
}

impl DiagnosticKind {
    pub fn default_severity_level(&self) -> SeverityLevel {
        match self {
            DiagnosticKind::UnexpectedEOF => SeverityLevel::Error,
            DiagnosticKind::Expected(_) => SeverityLevel::Error,
            DiagnosticKind::ExpectedName(_) => SeverityLevel::Error,
            DiagnosticKind::OddNumberOfBytestringElements => SeverityLevel::Error,
            DiagnosticKind::IntError(_) => SeverityLevel::Error,
            DiagnosticKind::NonDtsV1 => SeverityLevel::Error,
            DiagnosticKind::NameTooLong(_, _) => SeverityLevel::Warning,
            DiagnosticKind::IllegalChar(_, _) => SeverityLevel::Error,
            DiagnosticKind::IllegalStart(_, _) => SeverityLevel::Error,
            DiagnosticKind::PathCannotBeEmpty => SeverityLevel::Error,
            DiagnosticKind::PropertyAfterNode => SeverityLevel::Error,
            DiagnosticKind::DuplicateDirective(_) => SeverityLevel::Warning,
            DiagnosticKind::UnbalancedParentheses => SeverityLevel::Error,
            DiagnosticKind::MisplacedDtsHeader => SeverityLevel::Error,
            DiagnosticKind::NonStringInCompatible => SeverityLevel::Warning,
            DiagnosticKind::UnresolvedReference => SeverityLevel::Error,
            DiagnosticKind::PropertyReferencedByNode => SeverityLevel::Error,
            DiagnosticKind::ParserError(_) => SeverityLevel::Error,
            DiagnosticKind::IOError(_) => SeverityLevel::Error,
            DiagnosticKind::ErrorsInInclude => SeverityLevel::Error,
        }
    }
}

impl Display for NameContext {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            NameContext::Label => write!(f, "label"),
            NameContext::NodeName => write!(f, "node name"),
            NameContext::PropertyName => write!(f, "property"),
            NameContext::UnitAddress => write!(f, "unit address"),
        }
    }
}

impl Display for TokenKind {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            TokenKind::Semicolon => write!(f, "';'"),
            TokenKind::Slash => write!(f, "'/'"),
            TokenKind::Equal => write!(f, "'='"),
            TokenKind::OpenBracket => write!(f, "'['"),
            TokenKind::CloseBracket => write!(f, "']'"),
            TokenKind::OpenParen => write!(f, "'('"),
            TokenKind::CloseParen => write!(f, "')'"),
            TokenKind::ChevronLeft => write!(f, "'<'"),
            TokenKind::ChevronRight => write!(f, "'>'"),
            TokenKind::Comma => write!(f, "','"),
            TokenKind::OpenBrace => write!(f, "'{{'"),
            TokenKind::CloseBrace => write!(f, "'}}'"),
            TokenKind::Ident(_) => write!(f, "identifier"),
            TokenKind::Label(_) => write!(f, "label"),
            TokenKind::String(_) => write!(f, "string"),
            TokenKind::UnparsedNumber(_) => write!(f, "number"),
            TokenKind::Directive(directive) => write!(f, "{directive}"),
            TokenKind::Ref(_) => write!(f, "reference"),
            TokenKind::Comment(_) => write!(f, "comment"),
            TokenKind::Unknown(_) => write!(f, "unknown"),
        }
    }
}

impl Display for DiagnosticKind {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            DiagnosticKind::NonDtsV1 => {
                write!(f, "Files without the '/dts-v1/' Header are not supported")
            }
            DiagnosticKind::UnexpectedEOF => write!(f, "Unexpected End of File"),
            DiagnosticKind::ExpectedName(name) => write!(f, "Expected {name}"),
            DiagnosticKind::Expected(kinds) => {
                if kinds.len() == 1 {
                    write!(f, "Expected {}", kinds[0])
                } else {
                    write!(
                        f,
                        "Expected one of {}",
                        kinds.iter().map(|kind| format!("{kind}")).join(", ")
                    )
                }
            }
            DiagnosticKind::OddNumberOfBytestringElements => {
                write!(f, "Number of elements in byte string must be even")
            }
            DiagnosticKind::IntError(err) => write!(f, "{}", err),
            DiagnosticKind::NameTooLong(size, context) => write!(
                f,
                "{context} should only have 31 characters but has {size} characters",
            ),
            DiagnosticKind::IllegalChar(ch, context) => {
                write!(f, "Illegal char '{ch}' in {context}")
            }
            DiagnosticKind::IllegalStart(ch, context) => {
                write!(f, "{context} may not start with {ch}")
            }
            DiagnosticKind::UnresolvedReference => {
                write!(f, "Reference cannot be resolved")
            }
            DiagnosticKind::NonStringInCompatible => {
                write!(f, "compatible property should only contain strings")
            }
            DiagnosticKind::PathCannotBeEmpty => {
                write!(f, "Path cannot be empty")
            }
            DiagnosticKind::PropertyAfterNode => {
                write!(f, "Properties must be placed before nodes")
            }
            DiagnosticKind::DuplicateDirective(directive) => {
                write!(f, "Duplicate compiler directive {}", directive)
            }
            DiagnosticKind::UnbalancedParentheses => write!(f, "Unbalanced parentheses"),
            DiagnosticKind::MisplacedDtsHeader => {
                write!(f, "dts-v1 header must be placed on top of the file")
            }
            DiagnosticKind::PropertyReferencedByNode => {
                write!(f, "Reference points to a property, not a node")
            }
            DiagnosticKind::ParserError(str) => {
                write!(f, "{str}")
            }
            DiagnosticKind::IOError(msg) => {
                write!(f, "{msg}")
            }
            DiagnosticKind::ErrorsInInclude => {
                write!(f, "Included file contains non-recoverable errors")
            }
        }
    }
}

impl From<Error> for DiagnosticKind {
    fn from(value: Error) -> Self {
        DiagnosticKind::IOError(format!("{value}"))
    }
}

impl From<ParseIntError> for DiagnosticKind {
    fn from(value: ParseIntError) -> Self {
        DiagnosticKind::IntError(value)
    }
}

#[derive(PartialEq, Debug, Clone)]
pub struct Diagnostic {
    kind: DiagnosticKind,
    span: Span,
}

impl Diagnostic {
    pub fn new(span: Span, kind: impl Into<DiagnosticKind>) -> Diagnostic {
        Diagnostic {
            kind: kind.into(),
            span,
        }
    }

    pub fn kind(&self) -> &DiagnosticKind {
        &self.kind
    }

    pub fn default_severity(&self) -> SeverityLevel {
        self.kind().default_severity_level()
    }
}

impl HasSpan for Diagnostic {
    fn span(&self) -> Span {
        self.span
    }
}

pub struct DiagnosticPrinter<'a> {
    pub diagnostics: &'a [Diagnostic],
    pub file_name: String,
    pub code: Vec<String>,
}

impl<'a> DiagnosticPrinter<'a> {
    fn fmt_diagnostic(&self, f: &mut Formatter<'_>, diagnostic: &Diagnostic) -> std::fmt::Result {
        let start = diagnostic.span.start();
        let end = diagnostic.span.end();
        debug_assert!(start.line() == end.line());
        let empty_string = "".to_string();
        let line = self
            .code
            .get(start.line() as usize)
            .unwrap_or(&empty_string)
            .clone();
        // take tabs into consideration
        let line_empty: String = line
            .chars()
            .map(|ch| if !ch.is_ascii_whitespace() { ' ' } else { ch })
            .take(start.character() as usize)
            .collect();

        let prefix = format!("{}", start.line() + 1);
        let prefix_empty = " ".repeat(prefix.len());
        writeln!(
            f,
            " --> {}:{}:{}",
            self.file_name,
            start.line() + 1,
            start.character() + 1
        )?;
        writeln!(f, "{} |", prefix_empty)?;
        writeln!(f, "{} | {}", prefix, line)?;
        let len = if start.character() == end.character() {
            1
        } else {
            end.character() - start.character()
        };
        write!(
            f,
            "{} | {}{}",
            prefix_empty,
            line_empty,
            "^".repeat(len as usize)
        )?;
        write!(f, " {}", diagnostic.kind)?;
        Ok(())
    }
}

impl<'a> Display for DiagnosticPrinter<'a> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        for diagnostic in self.diagnostics.iter() {
            self.fmt_diagnostic(f, diagnostic)?;
            writeln!(f)?;
            writeln!(f)?;
        }
        Ok(())
    }
}