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
//! Raw `*const char` pointer on C-level but ASCII `string` like in languages that support it.
//!
//! # Example
//!
//! In your library you can accept ASCII strings like this:
//!
//! ```
//! use interoptopus::ffi_function;
//! use interoptopus::patterns::string::AsciiPointer;
//!
//! #[ffi_function]
//! #[no_mangle]
//! pub extern "C" fn call_with_string(_string: AsciiPointer)  {
//!     //
//! }
//! ```
//!
//! Backends supporting this pattern might generate the equivalent to the following pseudo-code:
//!
//! ```csharp
//! void call_with_string(string _string);
//! ```
//!
//! Backends not supporting this pattern, and C FFI, will see the equivalent of the following C code:
//! ```c
//! void call_with_string(uint8_t* _string);
//! ```
//!
use crate::lang::c::CType;
use crate::lang::rust::CTypeInfo;
use crate::patterns::TypePattern;
use crate::Error;
use std::ffi::CStr;
use std::marker::PhantomData;
use std::option::Option::None;
use std::os::raw::c_char;
use std::ptr::null;

static EMPTY: &[u8] = b"\0";

/// Represents a `*const char` on FFI level pointing to an `0x0` terminated ASCII string.
#[repr(transparent)]
#[derive(Debug)]
pub struct AsciiPointer<'a> {
    ptr: *const c_char,
    _phandom: PhantomData<&'a ()>,
}

impl<'a> Default for AsciiPointer<'a> {
    fn default() -> Self {
        Self {
            ptr: null(),
            _phandom: Default::default(),
        }
    }
}

impl<'a> AsciiPointer<'a> {
    pub fn empty() -> Self {
        Self {
            ptr: EMPTY.as_ptr().cast(),
            _phandom: Default::default(),
        }
    }

    /// Create a pointer from a CStr.
    pub fn from_cstr(cstr: &'a CStr) -> Self {
        Self {
            ptr: cstr.as_ptr(),
            _phandom: Default::default(),
        }
    }

    /// Create a [`CStr`] for the pointer.
    pub fn as_c_str(&self) -> Option<&'a CStr> {
        if self.ptr.is_null() {
            None
        } else {
            // TODO: Write something about safety
            unsafe { Some(CStr::from_ptr(self.ptr)) }
        }
    }

    /// Attempts to return a Rust `str`.
    pub fn as_str(&self) -> Result<&'a str, Error> {
        Ok(self.as_c_str().ok_or(Error::Null)?.to_str()?)
    }
}

unsafe impl<'a> CTypeInfo for AsciiPointer<'a> {
    fn type_info() -> CType {
        CType::Pattern(TypePattern::AsciiPointer)
    }
}

#[cfg(test)]
mod test {
    use crate::patterns::string::AsciiPointer;
    use std::ffi::CString;

    #[test]
    fn can_create() {
        let s = "hello world";
        let cstr = CString::new(s).unwrap();

        let ptr_some = AsciiPointer::from_cstr(&cstr);

        assert_eq!(s, ptr_some.as_str().unwrap());
    }
}