pr47 0.1.4-CHARLIE

A semi-experimental programming language. Still working in progress.
Documentation
pub mod diag_data;
pub mod location;
pub mod source;

#[cfg(feature = "compiler-pretty-diag")] pub mod prettier;

use std::fmt::Formatter;

use smallvec::SmallVec;
use xjbutil::display2;
use xjbutil::display2::{Display2, ToString2};

use crate::diag::location::{SourceLoc, SourceRange};

#[derive(Clone, Copy)]
#[repr(u8)]
pub enum DiagLevel {
    Info,
    Warn,
    Error,
    Fatal
}

pub struct DiagMark {
    pub range: SourceRange,
    pub comment: Option<&'static str>
}

impl DiagMark {
    pub fn new(file_id: u32, offset_begin: u32, offset_end: u32) -> Self {
        Self::from(SourceRange::new(file_id, offset_begin, offset_end))
    }

    pub fn add_comment(mut self, comment: &'static str) -> Self {
        let opt: Option<&'static str> = self.comment.replace(comment);
        debug_assert!(opt.is_none());
        self
    }
}

impl From<SourceLoc> for DiagMark {
    fn from(loc: SourceLoc) -> Self {
        Self::from(SourceRange::from(loc))
    }
}

impl From<SourceRange> for DiagMark {
    fn from(range: SourceRange) -> Self {
        Self {
            range, comment: None
        }
    }
}

pub struct DiagDetail {
    pub detail_id: usize,
    pub mark: DiagMark,
    pub args: SmallVec<[String; 4]>,
}

pub struct DiagDetailBuilder {
    detail: DiagDetail
}

impl<'a> DiagDetail {
    #[must_use] pub fn builder(
        detail_id: usize,
        mark: DiagMark
    ) -> DiagDetailBuilder {
        DiagDetailBuilder {
            detail: Self {
                detail_id,
                mark,
                args: SmallVec::new(),
            }
        }
    }
}

impl<'a> DiagDetailBuilder {
    #[must_use] pub fn add_arg(mut self, arg: impl ToString) -> Self {
        self.detail.args.push(arg.to_string());
        self
    }

    #[must_use] pub fn build(self) -> DiagDetail {
        self.detail
    }
}

pub struct Diagnostic {
    pub location: SourceLoc,
    pub diag_id: u32,
    pub args: SmallVec<[String; 4]>,

    pub marks: SmallVec<[DiagMark; 2]>,
    pub details: SmallVec<[DiagDetail; 1]>
}

impl Diagnostic {
    #[must_use] pub fn builder(location: SourceLoc, diag_id: u32) -> DiagBuilder {
        DiagBuilder {
            diag: Self {
                location,
                diag_id,
                args: SmallVec::new(),

                marks: SmallVec::new(),
                details: SmallVec::new()
            }
        }
    }
}

pub struct DiagBuilder {
    diag: Diagnostic
}

impl DiagBuilder {
    #[must_use] pub fn add_arg(mut self, arg: impl ToString) -> Self {
        self.diag.args.push(arg.to_string());
        self
    }

    #[must_use] pub fn add_arg2(mut self, arg: impl ToString2) -> Self {
        self.diag.args.push(arg.to_string2());
        self
    }

    #[must_use] pub fn add_mark(mut self, mark: DiagMark) -> Self {
        self.diag.marks.push(mark);
        self
    }

    #[must_use] pub fn add_detail(mut self, detail: DiagDetail) -> Self {
        self.diag.details.push(detail);
        self
    }

    #[must_use] pub fn build(self) -> Diagnostic {
        self.diag
    }
}

pub struct DiagContext {
    diags: Vec<Diagnostic>,

    has_diag: bool,
    has_error: bool
}

pub struct DiagBuilderCtx<'a> {
    diag_context: &'a mut DiagContext,
    diag_builder: DiagBuilder
}

impl DiagContext {
    pub fn new() -> Self {
        Self {
            diags: vec![],
            has_diag: false,
            has_error: false
        }
    }

    #[cfg_attr(test, allow(unreachable_code))]
    #[must_use]
    pub fn diag(&mut self, location: SourceLoc, diag_id: u32) -> DiagBuilderCtx {
        #[cfg(test)] panic!(
            "#[cfg(test)] diag panic: location = {:?}, diag_id = {}",
            location,
            diag_id
        );

        let diag_builder: DiagBuilder = Diagnostic::builder(location, diag_id);
        DiagBuilderCtx {
            diag_context: self,
            diag_builder
        }
    }

    pub fn add_diag(&mut self, diag: Diagnostic) {
        if diag_data::is_error(diag.diag_id) {
            self.has_error = true;
        }
        self.has_diag = true;
        self.diags.push(diag);
    }

    pub fn has_diag(&self) -> bool { self.has_diag }

    pub fn has_error(&self) -> bool { self.has_error }

    #[must_use] pub fn clear_reset(&mut self) -> Vec<Diagnostic> {
        self.has_diag = false;
        self.has_error = false;

        std::mem::take(&mut self.diags)
    }
}

impl<'a> DiagBuilderCtx<'a> {
    #[must_use] pub fn add_arg(mut self, arg: impl ToString) -> Self {
        self.diag_builder = self.diag_builder.add_arg(arg);
        self
    }

    #[must_use] pub fn add_arg2(mut self, arg: impl ToString2) -> Self {
        self.diag_builder = self.diag_builder.add_arg2(arg);
        self
    }

    #[must_use] pub fn add_mark(mut self, mark: DiagMark) -> Self {
        self.diag_builder = self.diag_builder.add_mark(mark);
        self
    }

    #[must_use] pub fn add_detail(mut self, detail: DiagDetail) -> Self {
        self.diag_builder = self.diag_builder.add_detail(detail);
        self
    }

    pub fn emit(self) {
        self.diag_context.add_diag(self.diag_builder.build())
    }
}

pub struct ArrayWrapper<T, const N: usize> {
    inner: [T; N]
}

impl<T, const N: usize> ArrayWrapper<T, N> {
    pub fn new(input: [T; N]) -> Self {
        Self {
            inner: input
        }
    }
}

impl<T: Display2, const N: usize> Display2 for ArrayWrapper<T, N> {
    fn fmt2(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result {
        if N <= 1 {
            panic!("please do not use ArrayWrapper<T, N> in such a way!")
        } else if N == 2 {
            write!(fmt, "{} or {}", display2!(&self.inner[0]), display2!(&self.inner[1]))
        } else {
            for i in 0..N-2 {
                write!(fmt, "{}, ", display2!(&self.inner[i]))?;
            }
            write!(fmt, "{} or {}", display2!(&self.inner[N - 2]), display2!(&self.inner[N - 1]))
        }
    }
}

#[macro_export] macro_rules! awa {
    ($($content:expr),+) => {
        $crate::diag::ArrayWrapper::new([$($content),+])
    }
}