pomsky-macro 0.12.0

Macro for converting pomsky expressions to regexes
Documentation
use std::{fmt::Write, ops::Range};

use pomsky::diagnose::Diagnostic;
use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};

pub(crate) fn fmt(diagnostic: Diagnostic, _: &Group, input: &str) -> String {
    let mut buf = String::new();
    buf.push_str("error: ");
    buf.push_str(&diagnostic.msg);
    buf.push('\n');

    if let Some(range) = diagnostic.span.range() {
        let slice = &input[range.clone()];
        let Range { start, end } = range;

        let before = input[..start].lines().next_back().unwrap_or_default();
        let after = input[end..].lines().next().unwrap_or_default();

        let line_number = input[..start].lines().count().max(1);
        let line_number_len = (line_number as f32).log10().floor() as usize + 1;
        let before_len = before.chars().count();
        let arrow_len = slice.chars().count().max(1);

        write!(
            &mut buf,
            "\
{space:line_number_len$} |
{line_number} | {before}{slice}{after}
{space:line_number_len$} | {space:before_len$}{space:^<arrow_len$}",
            space = ""
        )
        .unwrap();
        buf.push('\n');
    }

    if let Some(help) = diagnostic.help {
        buf.push_str("help: ");
        buf.push_str(&help);
        buf.push('\n');
    }

    buf
}

pub(crate) fn error(s: &str, start: Span, end: Span) -> TokenStream {
    let group = vec![respan(Literal::string(s), Span::call_site())].into_iter().collect();

    vec![
        respan(Ident::new("compile_error", start), start),
        respan(Punct::new('!', Spacing::Alone), Span::call_site()),
        respan(Group::new(Delimiter::Brace, group), end),
    ]
    .into_iter()
    .collect()
}

fn respan<T: Into<TokenTree>>(t: T, span: Span) -> TokenTree {
    let mut t = t.into();
    t.set_span(span);
    t
}