portable_link_section 0.1.0

A portable version of the #[link_section] macro.
Documentation
use core::fmt;
use proc_macro2::{
    Ident as Ident2, Span as Span2, TokenStream as TokenStream2, TokenTree as TokenTree2,
};
use quote::quote_spanned;

/// An error that might occur when using the `#[portable_link_section]` macro.
pub enum Error {
    EmptyParams,
    SegmentParameterTypeMismatch(TokenTree2),
    InvalidSegmentValue(Ident2),
    GroupParameterTypeMismatch(Span2),
    ReservedGroupParameter(Span2),
    InvalidGroupDelimiter(Span2),
    InvalidGroupValue(Ident2, InvalidGroupParameterReason),
    MissingGroupParameter(Span2),
    ExtraneousParameter(TokenTree2),
}

#[derive(Copy, Clone)]
pub enum InvalidGroupParameterReason {
    TooLong(usize),
    InvalidSymbol(char),
}

impl fmt::Display for InvalidGroupParameterReason {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::TooLong(len) => f.write_fmt(format_args!(
                "`group` string value contains more than 15 characters: {len}"
            )),
            Self::InvalidSymbol(ch) => f.write_fmt(format_args!(
                "\
                `group` string contains invalid symbol: {ch}\n\
                - allowed characters: [a-z][0-9]_\n\
                "
            )),
        }
    }
}

impl quote::ToTokens for Error {
    fn to_tokens(&self, tokens: &mut TokenStream2) {
        let span = self.span();
        let message = self.to_string();
        tokens.extend(quote_spanned!(span=>
            const _: () = {
                ::core::compile_error!(
                    ::core::concat!("#[portable_link_section]: ", #message)
                )
            };
        ))
    }
}

impl Error {
    fn span(&self) -> Span2 {
        match self {
            Self::EmptyParams => Span2::call_site(),
            Self::SegmentParameterTypeMismatch(invalid) => invalid.span(),
            Self::InvalidSegmentValue(invalid) => invalid.span(),
            Self::InvalidGroupDelimiter(span) => *span,
            Self::GroupParameterTypeMismatch(span) => *span,
            Self::ReservedGroupParameter(span) => *span,
            Self::InvalidGroupValue(invalid, _reason) => invalid.span(),
            Self::MissingGroupParameter(span) => *span,
            Self::ExtraneousParameter(extraneous) => extraneous.span(),
        }
    }
}

struct Indent;
impl fmt::Display for Indent {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str("    ")
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let indent = Indent;
        match self {
            Self::EmptyParams => f.write_fmt(format_args!(
                "\
                missing segment parameter, example usage:\n\
                {indent}- #[portable_link_section(text(group))]\n\
                {indent}- #[portable_link_section(data(group))]\n\
                {indent}- #[portable_link_section(rodata)]\n\
                {indent}- #[portable_link_section(bss)]\n\
                "
            )),
            Self::SegmentParameterTypeMismatch(invalid) => f.write_fmt(format_args!(
                "expected identifier for `segment` parameter but found: {invalid}"
            )),
            Self::InvalidSegmentValue(invalid) => f.write_fmt(format_args!(
                "\
                invalid value for `segment` parameter: {invalid}\n\
                {indent}- accepted values: text, data, rodata, bss\n\
                "
            )),
            Self::InvalidGroupDelimiter(_span) => f.write_fmt(format_args!(
                "\
                segment group parameter must be given inside `(` and `)`\n\
                {indent}- example usage: #[portable_link_section(text(hot))]\n\
                "
            )),
            Self::GroupParameterTypeMismatch(_span) => f.write_fmt(format_args!(
                "\
                expected segment group identifier, example usage\n\
                {indent}- #[portable_link_section(text(group))]\n\
                {indent}- #[portable_link_section(data(group))]\n\
                "
            )),
            Self::ReservedGroupParameter(_span) => f.write_str("reserved group parameter"),
            Self::InvalidGroupValue(invalid, reason) => f.write_fmt(format_args!(
                "\
                invalid value for `group` parameter: {invalid}\n\
                {indent}- reason: {reason}
                "
            )),
            Self::MissingGroupParameter(_) => f.write_fmt(format_args!(
                "\
                missing segment group parameter. example usage:\n\
                {indent}- #[portable_link_section(text(group))]\n\
                {indent}- #[portable_link_section(data(group))]\n\
                "
            )),
            Self::ExtraneousParameter(extraneous) => f.write_fmt(format_args!(
                "\
                unexpected extraneous parameter: {extraneous} example usage:\n\
                {indent}- #[portable_link_section(text(group))]\n\
                {indent}- #[portable_link_section(data(group))]\n\
                {indent}- #[portable_link_section(rodata)]\n\
                {indent}- #[portable_link_section(bss)]\n\
                "
            )),
        }
    }
}