1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
use std::ffi::{CStr, CString};

/// Foreign functions interface (FFI) definitions
pub mod ffi;

#[repr(C)]
struct CallbackSite {
    pub error: *mut libc::c_void,
    pub include: *mut libc::c_void,
}

mod tcpp {
    use std::os::raw::c_char;

    use crate::{CallbackSite, ffi};

    extern "C" {
        pub(crate) fn process_with_specs(data: *mut c_char, callbacks: *const libc::c_void
                              , error: unsafe extern fn(*const CallbackSite, *const ffi::TErrorInfo)
                              , include: unsafe extern fn(*const CallbackSite, *const libc::c_char, bool) -> *const libc::c_void) -> *mut c_char;

        pub(crate) fn process(data: *mut c_char) -> *mut c_char;
    }
}

/// Process c/cpp strings to its expanded form using the `tcpp` preprocessor directly.
    ///
    /// This function call cannot handle any errors or inclusions and simply halts when
    /// a preprocessor error was detected, which makes it not recommended to be used.
    ///
    /// You should probably check [`process_with`] instead
pub fn process(data: String) -> Option<String> {
    let cstring = CString::new(data).ok()?.into_raw();
    let result_raw = unsafe {
        CStr::from_ptr(tcpp::process(cstring))
    };
    let _ = unsafe {
        CString::from_raw(cstring) // freed after ffi cross-border (to prevent memory leak)
    };
    Some(result_raw.to_str().ok()?.to_owned())
}

unsafe extern "C" fn callback_error<T: FnMut(ffi::TErrorInfo)>(callbacks: *const CallbackSite, error: *const ffi::TErrorInfo) {
    let callbacks = std::ptr::read(callbacks);
    let closure = callbacks.error as *mut T;
    (*closure)(std::ptr::read(error));
}

unsafe extern "C" fn callback_include<F: FnMut(String, bool) -> ffi::IInputStream>(callbacks: *const CallbackSite, file: *const libc::c_char, boolean: bool) -> *const libc::c_void {
    let callbacks = std::ptr::read(callbacks);
    let closure = callbacks.include as *mut F;
    let file = CStr::from_ptr(file).to_str().unwrap().to_owned();
    (*closure)(file, boolean).handler
}

/// This function calls the `tcpp` preprocessor with two callback functions.
///
/// While performs the same as function [`process`], this functions accepts two
/// closures to handle errors and inclusion (i.e. `#include`) respectively.
/// which gives more flexibility and supports further multi-file processing
///
/// # Example
///
/// ```
/// use tcpp::*;
/// use tcpp::ffi::*;
///
/// fn main() {
///     // read content from source file
///     let content = String::from_utf8(std::fs::read("main.c").unwrap()).unwrap();
///     let result = process_with(content,
///         |error| { // error processor
///             panic!("Preprocessor error: {} at line {}"
///                     , error.get_message().unwrap() // get description of the error
///                     , error.get_line()); // get line number of the error
///         },
///         |_, _|  { // inclusion processor
///             // we just ignore inclusions and returns a default (null) stream
///             IInputStream::default()
///         });
/// }
/// ```
///
pub fn process_with<T, F>(data: String, error: T, include: F) -> Option<String>
    where T: FnMut(ffi::TErrorInfo),
          F: FnMut(String, bool) -> ffi::IInputStream {
    let cstring = CString::new(data).ok()?.into_raw();
    let callbacks = Box::new(
        CallbackSite {
            include: Box::into_raw(Box::new(include)) as *mut _,
            error: Box::into_raw(Box::new(error)) as *mut _,
        });
    let result_raw = unsafe {
        CStr::from_ptr(tcpp::process_with_specs(cstring, Box::into_raw(callbacks) as *const _
                                                 , callback_error::<T>, callback_include::<F>))
    };
    let _ = unsafe {
        CString::from_raw(cstring) // freed after ffi cross-border (to prevent memory leak)
    };
    Some(result_raw.to_str().ok()?.to_owned())
}


#[cfg(test)]
mod tests {
    use crate::*;
    use crate::ffi::IInputStream;

    #[test]
    fn it_works() {
        let str = String::from("\
        #define TCPP_VALUE 10\n\
        \
        int main(int * argc,char ** argv) {\n\
        #if (TCPP_VALUE < 5)\n\
            printf(\"Hello tcpp TCPP_VALUE\");\n
        #else\n\
            printf(\"Hello Greater tcpp TCPP_VALUE\");\n\
        #endif\n\
        }\
        ");
        eprintln!("{:?}", process(str));
    }

    #[test]
    fn reports_error() {
        let str = String::from("\
        #defined TCPP_VALUE 10\n\
        \
        int main(int * argc,char ** argv) {\n\
        #if (TCPP_VALUE < 5)\n\
            printf(\"Hello tcpp TCPP_VALUE\");\n
        #else\n\
            printf(\"Hello Greater tcpp TCPP_VALUE\");\n\
        #endif\n\
        }\
        ");
        eprintln!("{:?}", process_with(str, |err| {
            eprintln!("error! {:?}", err.get_message());
        }, |_, _| {
            IInputStream::default()
        }));
    }
}