rustycpp 0.1.6

An attempt to implement the C++20 standard. This is mostly to have fun & learn rust
//! Implementation of the custom macros currently supported. These macros don't
//! have a fixed expansion, and can't be generated by only the C++ preprocessor.

#![allow(non_camel_case_types, clippy::string_to_string)]

use std::collections::{HashMap, VecDeque};
use std::sync::Mutex;

use crate::grammars::defineast::{DefineAst, IsVariadic};
use crate::preprocessor::pretoken::PreToken;
use crate::utils::structs::{CompileMsg, FileTokPos};

use chrono::Local;
use lazy_static::lazy_static;

use super::Preprocessor;
use crate::preprocessor::structs::ExpandData;

/// Trait of a custom macro
trait CustomMacro {
    /// Macro information
    fn macroInfo() -> DefineAst;
    /// Expand function
    fn expand(expandData: ExpandData) -> Result<VecDeque<FileTokPos<PreToken>>, CompileMsg>;
}

/// Trait of a custom macro
macro_rules! declCMVar {
    ($x:ident, $expand:expr) => {
        struct $x {}
        impl CustomMacro for $x {
            fn macroInfo() -> DefineAst {
                DefineAst {
                    id: stringify!($x).to_string(),
                    param: None,
                    variadic: IsVariadic::False,
                    replacement: vec![],
                    expandFunc: &Self::expand,
                }
            }
            fn expand(
                expandData: ExpandData,
            ) -> Result<VecDeque<FileTokPos<PreToken>>, CompileMsg> {
                let expanded = $expand;
                let mut res = VecDeque::new();
                res.push_back(FileTokPos::new_meta_c(
                    PreToken::DisableMacro(stringify!($x).to_string()),
                    &expandData.newToken,
                ));
                res.push_back(FileTokPos::new_meta_c(
                    PreToken::RawStringLiteral((expanded)(&expandData).to_string()),
                    &expandData.newToken,
                ));
                res.push_back(FileTokPos::new_meta_c(
                    PreToken::EnableMacro(stringify!($x).to_string()),
                    &expandData.newToken,
                ));
                Ok(res)
            }
        }
    };
}
declCMVar! {__DATE__, |_| Local::now().format("%b %e %y")}
declCMVar! {__FILE__, |expandData: &ExpandData| expandData.newToken.file.path().to_string()}
declCMVar! {__LINE__, |expandData: &ExpandData| expandData.newToken.file.getRowColumn(expandData.newToken.tokPos.start).0}
declCMVar! {__STDC_HOSTED__, |_| "1"}
declCMVar! {__STDCPP_DEFAULT_NEW_ALIGNMENT__, |_| "1"}
declCMVar! {__TIME__, |_| Local::now().format("%H:%M:%S")}

#[doc(hidden)]
struct __cplusplus;

impl CustomMacro for __cplusplus {
    fn macroInfo() -> DefineAst {
        DefineAst {
            id: stringify!(__cplusplus).to_string(),
            param: None,
            variadic: IsVariadic::False,
            replacement: vec![],
            expandFunc: &Self::expand,
        }
    }

    fn expand(expandData: ExpandData) -> Result<VecDeque<FileTokPos<PreToken>>, CompileMsg> {
        let mut res = VecDeque::new();
        res.push_back(FileTokPos::new_meta_c(
            PreToken::DisableMacro(stringify!(__cplusplus).to_string()),
            expandData.newToken,
        ));

        res.push_back(FileTokPos::new_meta_c(
            PreToken::PPNumber("202002L".to_owned()),
            expandData.newToken,
        ));
        res.push_back(FileTokPos::new_meta_c(
            PreToken::EnableMacro(stringify!(__cplusplus).to_string()),
            expandData.newToken,
        ));
        Ok(res)
    }
}

#[doc(hidden)]
struct __has_include;

impl CustomMacro for __has_include {
    fn macroInfo() -> DefineAst {
        DefineAst {
            id: stringify!(__has_include).to_string(),
            param: Some(vec![]),
            variadic: IsVariadic::True(String::new()),
            replacement: vec![],
            expandFunc: &Self::expand,
        }
    }

    fn expand(expandData: ExpandData) -> Result<VecDeque<FileTokPos<PreToken>>, CompileMsg> {
        let mut res = VecDeque::new();
        res.push_back(FileTokPos::new_meta_c(
            PreToken::DisableMacro(stringify!(__has_include).to_string()),
            expandData.newToken,
        ));

        let mut tokensPath = VecDeque::new();
        for posVariadic in 0..expandData.variadic.len() {
            for v in &expandData.variadic[posVariadic] {
                tokensPath.push_back(v.clone());
            }
            if posVariadic + 1 != expandData.variadic.len() {
                tokensPath.push_back(FileTokPos::new_meta_c(
                    PreToken::OperatorPunctuator(","),
                    expandData.newToken,
                ));
            }
        }

        let path = Preprocessor::tokensToValidIncludeablePath(
            expandData.lexer,
            expandData.definitions,
            expandData.disabledMacros,
            expandData.newToken,
            tokensPath,
        )?;

        res.push_back(FileTokPos::new_meta_c(
            PreToken::PPNumber(if expandData.lexer.hasFileAccess(&path) {
                "1".to_owned()
            } else {
                "0".to_owned()
            }),
            expandData.newToken,
        ));
        res.push_back(FileTokPos::new_meta_c(
            PreToken::EnableMacro(stringify!(__has_include).to_string()),
            expandData.newToken,
        ));
        Ok(res)
    }
}

#[doc(hidden)]
struct __has_cpp_attribute;

impl CustomMacro for __has_cpp_attribute {
    fn macroInfo() -> DefineAst {
        DefineAst {
            id: stringify!(__has_include).to_string(),
            param: Some(vec![]),
            variadic: IsVariadic::True(String::new()),
            replacement: vec![],
            expandFunc: &__has_include::expand,
        }
    }

    fn expand(expandData: ExpandData) -> Result<VecDeque<FileTokPos<PreToken>>, CompileMsg> {
        let mut res = VecDeque::new();
        res.push_back(FileTokPos::new_meta_c(
            PreToken::DisableMacro(stringify!(__has_include).to_string()),
            expandData.newToken,
        ));

        res.push_back(FileTokPos::new_meta_c(
            PreToken::PPNumber("0".to_owned()),
            expandData.newToken,
        ));
        res.push_back(FileTokPos::new_meta_c(
            PreToken::EnableMacro(stringify!(__has_include).to_string()),
            expandData.newToken,
        ));
        Ok(res)
    }
}

#[doc(hidden)]
macro_rules! registerMacro_ {
    ($hashMap:ident) => {};
    ($hashMap:ident, $x:ty) => {{
        let info = <$x>::macroInfo();
        $hashMap.insert(info.id.clone(), info);
    }};
    ($hashMap:ident, $x:ty, $($others:ty),*) => {
        registerMacro_!($hashMap, $x);
        registerMacro_!($hashMap, $($others),*);
    };
}

#[doc(hidden)]
macro_rules! registerMacro {
    ($($o:ty),*) => {{
        let mut hashMap = HashMap::new();
        registerMacro_!(hashMap, $( $o ),*);
        hashMap
    }};
}
impl Preprocessor {
    /// Register all the custom macros defined here
    fn generateCustomMacro() -> HashMap<String, DefineAst> {
        registerMacro!(
            __cplusplus,
            __DATE__,
            __FILE__,
            __LINE__,
            __STDC_HOSTED__,
            __STDCPP_DEFAULT_NEW_ALIGNMENT__,
            __TIME__,
            __has_include,
            __has_cpp_attribute
        )
    }

    /// Register all the custom macros to current Preprocessor
    pub fn initCustomMacros(mut self) -> Self {
        lazy_static! {
            static ref CUSTOM_MACROS: Mutex<HashMap<String, DefineAst>> =
                Mutex::new(Preprocessor::generateCustomMacro());
        }
        self.definitions.extend(
            CUSTOM_MACROS
                .lock()
                .unwrap()
                .iter()
                .map(|(k, v)| (k.clone(), v.clone())),
        );
        self.disabledMacros.insert("__has_include".to_string());
        self.disabledMacros
            .insert("__has_cpp_attribute".to_string());
        self
    }
}