include_tt 1.1.1

Macros for ultra-flexible injection of token trees, literals, or binary data into Rust code from external files during compilation.
Documentation
use crate::{
    exprs::literal::ExprLit,
    points::file_dep_tracker::FileDepTracker,
    trees::{
        group::stream_stringify_with_fns,
        loader::{LoadFileAndAutoMakeTreeErr, load_file_and_automake_tree_with_fns},
        null::make_null_group,
        result::TreeResult,
    },
};
use proc_macro2::{
    Delimiter, Group, Literal, Span, TokenStream as TokenStream2, TokenTree as TokenTree2,
};
use quote::ToTokens;
use std::{borrow::Cow, io::Error as IOError, path::Path};
use std::{fs::File, io::Read};

/// Trait defining the behavior for data injection directives (like #tt, #str, #arr).
pub trait BehMacroInclude {
    /// The resulting output of the injection, usually a `TokenTree2`.
    type Result;

    /// Constructs the final tree by processing the input literal and tracking dependencies.
    fn make_tree(
        input: &ExprLit,
        tracker: Option<&mut FileDepTracker>,
        //
        span: Span,
    ) -> TreeResult<Self::Result>;

    /// Generates a valid "empty" state for this injection type.
    /// Used as a fallback or to represent a null operation.
    fn make_empty_tree(group_span: Span) -> Self::Result;
}

/// Easily include trees from a file in your
/// final custom macro code.
pub enum InjectTT {}

impl BehMacroInclude for InjectTT {
    type Result = TokenTree2;

    #[inline]
    fn make_empty_tree(group_span: Span) -> Self::Result {
        make_null_group(group_span)
    }

    fn make_tree(
        sspath: &ExprLit,
        tracker: Option<&mut FileDepTracker>,

        span: Span,
    ) -> TreeResult<Self::Result> {
        load_file_and_automake_tree_with_fns(
            Path::new(sspath.as_str()),
            tracker,
            |_| {}, /* skip_prepare */
            |fs_tt| {
                let ett = fs_tt.map_or_else(TokenStream2::new, TokenStream2::from_iter);

                let mut ngroup = Group::new(Delimiter::None, ett);
                ngroup.set_span(span);

                TreeResult::Ok(TokenTree2::Group(ngroup))
            },
            |e| TreeResult::Err(e.into_tt_err(span)),
        )
    }
}

/// Regular macro `include_tt` with find and replace
/// invalid tokens breaking the parser.
///
/// (Implemented specifically for C-like languages using `\` as a line code string)
pub enum InjectCTT {}

impl BehMacroInclude for InjectCTT {
    type Result = TokenTree2;

    #[inline]
    fn make_empty_tree(group_span: Span) -> Self::Result {
        make_null_group(group_span)
    }

    fn make_tree(
        sspath: &ExprLit,
        tracker: Option<&mut FileDepTracker>,

        span: Span,
    ) -> TreeResult<Self::Result> {
        load_file_and_automake_tree_with_fns(
            Path::new(sspath),
            tracker,
            |p_string| {
                /* fix unk start token */
                let mut p_str = p_string.as_mut();
                while let Some(pos) = p_str.find('\\' /* one symb */) {
                    let right = unsafe {
                        debug_assert!(p_str.get(pos..).is_some());

                        p_str.get_unchecked_mut(pos..)
                    };

                    if right.len() >= 2 {
                        let (c_symbol, new_pstr) = right.split_at_mut(2);
                        let c_array = unsafe { c_symbol.as_bytes_mut() };
                        debug_assert_eq!(c_array.len(), 2);
                        debug_assert_eq!(
                            {
                                #[allow(clippy::get_first)]
                                // why?, this is done to be completely analogous to an unsafe function.
                                c_array.get(0)
                            },
                            Some(&b'\\')
                        );
                        debug_assert!(c_array.get(1).is_some());

                        match unsafe { c_array.get_unchecked(1) } {
                            b'\n' | b'\t' | b'\r' | b' ' => {
                                // This is generally safe as the '/'
                                // characters were found using utf-8 lookups.
                                let a_repl = unsafe { c_array.get_unchecked_mut(0) };
                                *a_repl = b' ';
                            }
                            /*b'\\' => {
                                // This is generally safe as the '/'
                                // characters were found using utf-8 lookups.
                                for a in c_array {
                                    *a = b' ';
                                }
                            },*/
                            _ => {}
                        }

                        if new_pstr.is_empty() {
                            break;
                        }
                        p_str = new_pstr;
                    } else {
                        // This is generally safe as the '/'
                        // characters were found using utf-8 lookups.
                        let array = unsafe { right.as_bytes_mut() }.iter_mut();
                        for a in array {
                            *a = b' ';
                        }

                        break;
                    }
                }
            },
            |fs_tt| {
                let ett = fs_tt.map_or_else(TokenStream2::new, TokenStream2::from_iter);

                let mut ngroup = Group::new(Delimiter::None, ett);
                ngroup.set_span(span);

                TreeResult::Ok(TokenTree2::Group(ngroup))
            },
            |e| TreeResult::Err(e.into_tt_err(span)),
        )
    }
}

/// Includes the entire file as a single line,
/// similar to 'include_str'.
pub enum InjectStr {}

impl BehMacroInclude for InjectStr {
    type Result = TokenTree2;

    fn make_empty_tree(group_span: Span) -> Self::Result {
        let mut lit = Literal::string("");
        lit.set_span(group_span);

        TokenTree2::Literal(lit)
    }

    fn make_tree(
        sspath: &ExprLit,
        tracker: Option<&mut FileDepTracker>,

        span: Span,
    ) -> TreeResult<Self::Result> {
        let path = Path::new(sspath);

        match std::fs::read_to_string(path) {
            Ok(data) => {
                if let Some(tracker) = tracker {
                    tracker.append_track_file(path);
                }
                let mut lit = Literal::string(&data);
                lit.set_span(span);

                TreeResult::Ok(TokenTree2::Literal(lit))
            }
            Err(e) => {
                let path = path
                    .canonicalize()
                    .map_or_else(|_| Cow::Borrowed(path), Cow::Owned);

                TreeResult::Err(
                    LoadFileAndAutoMakeTreeErr::read_to_string(e, path).into_tt_err(span),
                )
            }
        }
    }
}

/// Includes the entire file as a binary array,
/// similar to 'include_str'.
pub enum InjectArr {}

impl BehMacroInclude for InjectArr {
    type Result = TokenTree2;

    fn make_empty_tree(group_span: Span) -> Self::Result {
        let mut lit = Literal::byte_string(&[]);
        lit.set_span(group_span);

        TokenTree2::Literal(lit)
    }

    fn make_tree(
        sspath: &ExprLit,
        tracker: Option<&mut FileDepTracker>,

        span: Span,
    ) -> TreeResult<Self::Result> {
        let path = Path::new(sspath);
        let vec = {
            let make_err = |e: IOError| {
                let path = path
                    .canonicalize()
                    .map_or_else(|_| Cow::Borrowed(path), Cow::Owned);
                TreeResult::from(
                    LoadFileAndAutoMakeTreeErr::read_to_string(e, path).into_tt_err(span),
                )
            };
            let mut file = match File::open(path) {
                Ok(a) => a,
                Err(e) => return make_err(e),
            };

            let mut vec = Vec::new(); // capacity is not required.
            if let Err(e) = file.read_to_end(&mut vec) {
                return make_err(e);
            };

            vec
        };

        if let Some(tracker) = tracker {
            tracker.append_track_file(path);
        }
        let mut lit = Literal::byte_string(&vec);
        lit.set_span(span);

        TreeResult::Ok(TokenTree2::Literal(lit))
    }
}

/// Build macro `include`/`include_str`/`include_arr`.
pub fn macro_rule_include<A>(
    group: &'_ Group,
    tracker: Option<&mut FileDepTracker>,
) -> TreeResult<A::Result>
where
    A: BehMacroInclude,
{
    let span = group.span();
    let stream = group.into_token_stream();
    stream_stringify_with_fns(
        stream,
        |stringify| {
            let exprlit = unsafe { ExprLit::new_unchecked(&stringify) };

            A::make_tree(exprlit, tracker, span)
        },
        // Empty
        || TreeResult::Ok(A::make_empty_tree(span)),
        // Err
        TreeResult::Err,
    )
}