use crate::{qjs, Ctx, Error, Result, StdString, Value};
use std::{ffi::c_char, mem, ptr::NonNull, slice, str};
#[derive(Debug, Clone, PartialEq, Hash)]
#[repr(transparent)]
pub struct String<'js>(pub(crate) Value<'js>);
impl<'js> String<'js> {
pub fn to_string(&self) -> Result<StdString> {
let mut len = mem::MaybeUninit::uninit();
let ptr = unsafe {
qjs::JS_ToCStringLen(self.0.ctx.as_ptr(), len.as_mut_ptr(), self.0.as_js_value())
};
if ptr.is_null() {
return Err(Error::Unknown);
}
let len = unsafe { len.assume_init() };
let bytes: &[u8] = unsafe { slice::from_raw_parts(ptr as _, len as _) };
let result = str::from_utf8(bytes).map(|s| s.into());
unsafe { qjs::JS_FreeCString(self.0.ctx.as_ptr(), ptr) };
Ok(result?)
}
pub fn to_cstring(self) -> Result<CString<'js>> {
CString::from_string(self)
}
pub fn from_str(ctx: Ctx<'js>, s: &str) -> Result<Self> {
let len = s.as_bytes().len();
let ptr = s.as_ptr();
Ok(unsafe {
let js_val = qjs::JS_NewStringLen(ctx.as_ptr(), ptr as _, len as _);
let js_val = ctx.handle_exception(js_val)?;
String::from_js_value(ctx, js_val)
})
}
}
#[derive(Debug)]
pub struct CString<'js> {
ptr: NonNull<c_char>,
len: usize,
ctx: Ctx<'js>,
}
impl<'js> CString<'js> {
pub fn from_string(string: String<'js>) -> Result<Self> {
let mut len = mem::MaybeUninit::uninit();
let ptr = unsafe {
qjs::JS_ToCStringLen(string.0.ctx.as_ptr(), len.as_mut_ptr(), string.as_raw())
};
if ptr.is_null() {
return Err(Error::Unknown);
}
let len = unsafe { len.assume_init() };
Ok(Self {
ptr: unsafe { NonNull::new_unchecked(ptr as *mut _) },
len,
ctx: string.0.ctx.clone(),
})
}
pub fn as_ptr(&self) -> *const c_char {
self.ptr.as_ptr() as *const _
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn as_str(&self) -> &str {
let bytes = unsafe { slice::from_raw_parts(self.ptr.as_ptr() as *const u8, self.len) };
unsafe { str::from_utf8_unchecked(bytes) }
}
}
impl<'js> Drop for CString<'js> {
fn drop(&mut self) {
unsafe { qjs::JS_FreeCString(self.ctx.as_ptr(), self.ptr.as_ptr()) };
}
}
#[cfg(test)]
mod test {
use crate::{prelude::*, *};
#[test]
fn from_javascript() {
test_with(|ctx| {
let s: String = ctx.eval(" 'foo bar baz' ").unwrap();
assert_eq!(s.to_string().unwrap(), "foo bar baz");
});
}
#[test]
fn to_javascript() {
test_with(|ctx| {
let string = String::from_str(ctx.clone(), "foo").unwrap();
let func: Function = ctx.eval("x => x + 'bar'").unwrap();
let text: StdString = (string,).apply(&func).unwrap();
assert_eq!(text, "foobar".to_string());
});
}
#[test]
fn from_javascript_c() {
test_with(|ctx| {
let s: CString = ctx.eval(" 'foo bar baz' ").unwrap();
assert_eq!(s.as_str(), "foo bar baz");
});
}
#[test]
fn to_javascript_c() {
test_with(|ctx| {
let string = String::from_str(ctx.clone(), "foo")
.unwrap()
.to_cstring()
.unwrap();
let func: Function = ctx.eval("x => x + 'bar'").unwrap();
let text: StdString = (string,).apply(&func).unwrap();
assert_eq!(text, "foobar".to_string());
});
}
}