tcpp/
lib.rs

1use std::ffi::{CStr, CString};
2
3/// Foreign functions interface (FFI) definitions
4pub mod ffi;
5
6#[repr(C)]
7struct CallbackSite {
8    pub error: *mut libc::c_void,
9    pub include: *mut libc::c_void,
10}
11
12mod tcpp {
13    use std::os::raw::c_char;
14
15    use crate::{CallbackSite, ffi};
16
17    extern "C" {
18        pub(crate) fn process_with_specs(data: *mut c_char, callbacks: *const libc::c_void
19                              , error: unsafe extern fn(*const CallbackSite, *const ffi::TErrorInfo)
20                              , include: unsafe extern fn(*const CallbackSite, *const libc::c_char, bool) -> *const libc::c_void) -> *mut c_char;
21
22        pub(crate) fn process(data: *mut c_char) -> *mut c_char;
23    }
24}
25
26/// Process c/cpp strings to its expanded form using the `tcpp` preprocessor directly.
27    ///
28    /// This function call cannot handle any errors or inclusions and simply halts when
29    /// a preprocessor error was detected, which makes it not recommended to be used.
30    ///
31    /// You should probably check [`process_with`] instead
32pub fn process(data: String) -> Option<String> {
33    let cstring = CString::new(data).ok()?.into_raw();
34    let result_raw = unsafe {
35        CStr::from_ptr(tcpp::process(cstring))
36    };
37    let _ = unsafe {
38        CString::from_raw(cstring) // freed after ffi cross-border (to prevent memory leak)
39    };
40    Some(result_raw.to_str().ok()?.to_owned())
41}
42
43unsafe extern "C" fn callback_error<T: FnMut(ffi::TErrorInfo)>(callbacks: *const CallbackSite, error: *const ffi::TErrorInfo) {
44    let callbacks = std::ptr::read(callbacks);
45    let closure = callbacks.error as *mut T;
46    (*closure)(std::ptr::read(error));
47}
48
49unsafe 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 {
50    let callbacks = std::ptr::read(callbacks);
51    let closure = callbacks.include as *mut F;
52    let file = CStr::from_ptr(file).to_str().unwrap().to_owned();
53    (*closure)(file, boolean).handler
54}
55
56/// This function calls the `tcpp` preprocessor with two callback functions.
57///
58/// While performs the same as function [`process`], this functions accepts two
59/// closures to handle errors and inclusion (i.e. `#include`) respectively.
60/// which gives more flexibility and supports further multi-file processing
61///
62/// # Example
63///
64/// ```
65/// use tcpp::*;
66/// use tcpp::ffi::*;
67///
68/// fn main() {
69///     // read content from source file
70///     let content = String::from_utf8(std::fs::read("main.c").unwrap()).unwrap();
71///     let result = process_with(content,
72///         |error| { // error processor
73///             panic!("Preprocessor error: {} at line {}"
74///                     , error.get_message().unwrap() // get description of the error
75///                     , error.get_line()); // get line number of the error
76///         },
77///         |_, _|  { // inclusion processor
78///             // we just ignore inclusions and returns a default (null) stream
79///             IInputStream::default()
80///         });
81/// }
82/// ```
83///
84pub fn process_with<T, F>(data: String, error: T, include: F) -> Option<String>
85    where T: FnMut(ffi::TErrorInfo),
86          F: FnMut(String, bool) -> ffi::IInputStream {
87    let cstring = CString::new(data).ok()?.into_raw();
88    let callbacks = Box::new(
89        CallbackSite {
90            include: Box::into_raw(Box::new(include)) as *mut _,
91            error: Box::into_raw(Box::new(error)) as *mut _,
92        });
93    let result_raw = unsafe {
94        CStr::from_ptr(tcpp::process_with_specs(cstring, Box::into_raw(callbacks) as *const _
95                                                 , callback_error::<T>, callback_include::<F>))
96    };
97    let _ = unsafe {
98        CString::from_raw(cstring) // freed after ffi cross-border (to prevent memory leak)
99    };
100    Some(result_raw.to_str().ok()?.to_owned())
101}
102
103
104#[cfg(test)]
105mod tests {
106    use crate::*;
107    use crate::ffi::IInputStream;
108
109    #[test]
110    fn it_works() {
111        let str = String::from("\
112        #define TCPP_VALUE 10\n\
113        \
114        int main(int * argc,char ** argv) {\n\
115        #if (TCPP_VALUE < 5)\n\
116            printf(\"Hello tcpp TCPP_VALUE\");\n
117        #else\n\
118            printf(\"Hello Greater tcpp TCPP_VALUE\");\n\
119        #endif\n\
120        }\
121        ");
122        eprintln!("{:?}", process(str));
123    }
124
125    #[test]
126    fn reports_error() {
127        let str = String::from("\
128        #defined TCPP_VALUE 10\n\
129        \
130        int main(int * argc,char ** argv) {\n\
131        #if (TCPP_VALUE < 5)\n\
132            printf(\"Hello tcpp TCPP_VALUE\");\n
133        #else\n\
134            printf(\"Hello Greater tcpp TCPP_VALUE\");\n\
135        #endif\n\
136        }\
137        ");
138        eprintln!("{:?}", process_with(str, |err| {
139            eprintln!("error! {:?}", err.get_message());
140        }, |_, _| {
141            IInputStream::default()
142        }));
143    }
144}