numbat 1.23.0

A statically typed programming language for scientific computations with first class support for physical dimensions and units.
Documentation
use std::fmt::Display;

use compact_str::CompactString;

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum FormatType {
    Whitespace,
    Emphasized,
    Dimmed,
    Text,
    String,
    Keyword,
    Value,
    Unit,
    Identifier,
    TypeIdentifier,
    Operator,
    Decorator,
}

#[derive(Debug, Clone, PartialEq)]
pub enum OutputType {
    Normal,
    Optional,
}

#[derive(Debug, Clone, PartialEq)]
pub enum CompactStrCow {
    Owned(CompactString),
    Static(&'static str),
}

impl std::fmt::Display for CompactStrCow {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            CompactStrCow::Owned(s) => write!(f, "{s}"),
            CompactStrCow::Static(s) => write!(f, "{s}"),
        }
    }
}

impl From<CompactStrCow> for CompactString {
    fn from(value: CompactStrCow) -> Self {
        match value {
            CompactStrCow::Owned(compact_string) => compact_string,
            CompactStrCow::Static(s) => CompactString::const_new(s),
        }
    }
}

impl std::ops::Deref for CompactStrCow {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        match self {
            CompactStrCow::Owned(compact_string) => compact_string,
            CompactStrCow::Static(s) => s,
        }
    }
}

impl From<CompactString> for CompactStrCow {
    fn from(value: CompactString) -> Self {
        Self::Owned(value)
    }
}

impl From<&'static str> for CompactStrCow {
    fn from(value: &'static str) -> Self {
        Self::Static(value)
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct FormattedString(pub OutputType, pub FormatType, pub CompactStrCow);

#[derive(Debug, Clone, Default, PartialEq)]
pub struct Markup(pub Vec<FormattedString>);

impl Markup {
    pub fn from(f: FormattedString) -> Self {
        Self(vec![f])
    }
}

impl Display for Markup {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", PlainTextFormatter {}.format(self, false))
    }
}

impl std::ops::Add for Markup {
    type Output = Markup;

    fn add(mut self, rhs: Self) -> Self::Output {
        self.0.extend(rhs.0);
        self
    }
}

impl std::ops::AddAssign for Markup {
    fn add_assign(&mut self, rhs: Self) {
        self.0.extend(rhs.0)
    }
}

impl std::iter::Sum for Markup {
    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
        iter.fold(empty(), |acc, n| acc + n)
    }
}

pub fn space() -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Whitespace,
        " ".into(),
    ))
}

pub fn empty() -> Markup {
    Markup::default()
}

pub fn whitespace(text: impl Into<CompactStrCow>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Whitespace,
        text.into(),
    ))
}

pub fn emphasized(text: impl Into<CompactStrCow>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Emphasized,
        text.into(),
    ))
}

pub fn dimmed(text: impl Into<CompactStrCow>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Dimmed,
        text.into(),
    ))
}

pub fn text(text: impl Into<CompactStrCow>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Text,
        text.into(),
    ))
}

pub fn string(text: impl Into<CompactStrCow>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::String,
        text.into(),
    ))
}

pub fn keyword(text: impl Into<CompactStrCow>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Keyword,
        text.into(),
    ))
}

pub fn value(text: impl Into<CompactStrCow>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Value,
        text.into(),
    ))
}

pub fn unit(text: impl Into<CompactStrCow>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Unit,
        text.into(),
    ))
}

pub fn identifier(text: impl Into<CompactStrCow>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Identifier,
        text.into(),
    ))
}

pub fn type_identifier(text: impl Into<CompactStrCow>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::TypeIdentifier,
        text.into(),
    ))
}

pub fn operator(text: impl Into<CompactStrCow>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Operator,
        text.into(),
    ))
}

pub fn decorator(text: impl Into<CompactStrCow>) -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Decorator,
        text.into(),
    ))
}

pub fn nl() -> Markup {
    Markup::from(FormattedString(
        OutputType::Normal,
        FormatType::Whitespace,
        "\n".into(),
    ))
}

pub trait Formatter {
    fn format_part(&self, part: &FormattedString) -> CompactString;

    fn format(&self, markup: &Markup, indent: bool) -> CompactString {
        let spaces = self.format_part(&FormattedString(
            OutputType::Normal,
            FormatType::Whitespace,
            "  ".into(),
        ));

        let mut output = CompactString::with_capacity(spaces.len() + markup.0.len());
        if indent {
            output.push_str(&spaces);
        }
        for part in &markup.0 {
            output.push_str(&self.format_part(part));
            if indent && part.2.contains('\n') {
                output.push_str(&spaces);
            }
        }
        output
    }
}

pub struct PlainTextFormatter;

impl Formatter for PlainTextFormatter {
    fn format_part(&self, FormattedString(_, _, text): &FormattedString) -> CompactString {
        text.clone().into()
    }
}

pub fn plain_text_format(m: &Markup, indent: bool) -> CompactString {
    PlainTextFormatter {}.format(m, indent)
}