use crate::cell::AllocationDropGuard;
use crate::SpirvCrossError;
use std::borrow::Cow;
use std::ffi::{c_char, CStr, CString};
use std::fmt::{Debug, Display, Formatter};
use std::ops::Deref;
pub struct CompilerStr<'a> {
pointer: Option<ContextPointer<'a>>,
cow: Cow<'a, str>,
}
unsafe impl Send for CompilerStr<'_> {}
unsafe impl Sync for CompilerStr<'_> {}
impl Clone for CompilerStr<'_> {
fn clone(&self) -> Self {
Self {
pointer: self.pointer.clone(),
cow: self.cow.clone(),
}
}
}
pub(crate) enum ContextPointer<'a> {
FromContext {
pointer: *const c_char,
context: AllocationDropGuard,
},
BorrowedCStr(&'a CStr),
}
impl ContextPointer<'_> {
pub const fn pointer(&self) -> *const c_char {
match self {
ContextPointer::FromContext { pointer, .. } => *pointer,
ContextPointer::BorrowedCStr(cstr) => cstr.as_ptr(),
}
}
}
impl Clone for ContextPointer<'_> {
fn clone(&self) -> Self {
match self {
ContextPointer::FromContext { pointer, context } => ContextPointer::FromContext {
pointer: *pointer,
context: context.clone(),
},
ContextPointer::BorrowedCStr(cstr) => ContextPointer::BorrowedCStr(cstr),
}
}
}
impl<'a> Display for CompilerStr<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.cow)
}
}
impl<'a> Debug for CompilerStr<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.cow)
}
}
pub(crate) enum MaybeOwnedCString<'a> {
Owned(CString),
Borrowed(ContextPointer<'a>),
}
impl MaybeOwnedCString<'_> {
pub fn as_ptr(&self) -> *const c_char {
match self {
MaybeOwnedCString::Owned(c) => c.as_ptr(),
MaybeOwnedCString::Borrowed(ptr) => ptr.pointer(),
}
}
}
impl AsRef<str> for CompilerStr<'_> {
fn as_ref(&self) -> &str {
self.cow.as_ref()
}
}
impl Deref for CompilerStr<'_> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.cow.deref()
}
}
impl<'a, 'b> PartialEq<CompilerStr<'a>> for CompilerStr<'b> {
fn eq(&self, other: &CompilerStr<'a>) -> bool {
self.cow.eq(&other.cow)
}
}
impl<'a, 'b> PartialEq<&'a str> for CompilerStr<'b> {
fn eq(&self, other: &&'a str) -> bool {
self.cow.eq(other)
}
}
impl<'b> PartialEq<CompilerStr<'b>> for &str {
fn eq(&self, other: &CompilerStr<'b>) -> bool {
self.eq(&other.cow)
}
}
impl PartialEq<str> for CompilerStr<'_> {
fn eq(&self, other: &str) -> bool {
self.cow.eq(other)
}
}
impl PartialEq<CompilerStr<'_>> for str {
fn eq(&self, other: &CompilerStr<'_>) -> bool {
self.eq(&other.cow)
}
}
impl PartialEq<CompilerStr<'_>> for String {
fn eq(&self, other: &CompilerStr<'_>) -> bool {
self.eq(&other.cow)
}
}
impl PartialEq<String> for CompilerStr<'_> {
fn eq(&self, other: &String) -> bool {
self.cow.eq(other)
}
}
impl Eq for CompilerStr<'_> {}
impl From<String> for CompilerStr<'_> {
fn from(value: String) -> Self {
Self::from_string(value)
}
}
impl<'a> From<&'a str> for CompilerStr<'a> {
fn from(value: &'a str) -> Self {
Self::from_str(value)
}
}
impl<'a> From<&'a CStr> for CompilerStr<'a> {
fn from(value: &'a CStr) -> Self {
Self::from_cstr(value)
}
}
impl<'a> CompilerStr<'a> {
pub(crate) unsafe fn from_ptr<'b>(
ptr: *const c_char,
arena: AllocationDropGuard,
) -> CompilerStr<'b>
where
'a: 'b,
{
let cstr = CStr::from_ptr(ptr);
let maybe = cstr.to_string_lossy();
if matches!(&maybe, &Cow::Borrowed(_)) {
Self {
pointer: Some(ContextPointer::FromContext {
pointer: ptr,
context: arena,
}),
cow: maybe,
}
} else {
Self {
pointer: None,
cow: maybe,
}
}
}
pub(crate) fn from_str(str: &'a str) -> Self {
Self {
pointer: None,
cow: Cow::Borrowed(str),
}
}
pub(crate) fn from_string(str: String) -> Self {
Self {
pointer: None,
cow: Cow::Owned(str),
}
}
pub(crate) fn from_cstr(cstr: &'a CStr) -> Self {
Self {
pointer: Some(ContextPointer::BorrowedCStr(cstr)),
cow: cstr.to_string_lossy(),
}
}
pub(crate) fn into_cstring_ptr(self) -> Result<MaybeOwnedCString<'a>, SpirvCrossError> {
if let Some(ptr) = &self.pointer {
Ok(MaybeOwnedCString::Borrowed(ptr.clone()))
} else {
let cstring = match self.cow {
Cow::Borrowed(s) => CString::new(s.to_string()),
Cow::Owned(s) => CString::new(s),
};
match cstring {
Ok(cstring) => Ok(MaybeOwnedCString::Owned(cstring)),
Err(e) => {
let string = e.into_vec();
let string = unsafe { String::from_utf8_unchecked(string) };
Err(SpirvCrossError::InvalidString(string))
}
}
}
}
}
#[cfg(test)]
mod test {
use crate::string::CompilerStr;
use std::ffi::{c_char, CStr, CString};
use std::sync::Arc;
struct LifetimeContext(*mut c_char);
impl LifetimeContext {
pub fn new() -> Self {
let cstring = CString::new(String::from("hello")).unwrap().into_raw();
Self(cstring)
}
}
impl Drop for LifetimeContext {
fn drop(&mut self) {
unsafe {
drop(CString::from_raw(self.0));
}
}
}
}