use alloc::{ffi::CString, vec::Vec};
use crate::{qjs, Atom, Ctx, Result, Value};
#[derive(Debug, Clone, PartialEq, Hash)]
#[repr(transparent)]
pub struct Symbol<'js>(pub(crate) Value<'js>);
impl<'js> Symbol<'js> {
fn new_inner(
ctx: Ctx<'js>,
description: Option<impl Into<Vec<u8>>>,
is_global: bool,
) -> Result<Self> {
let c_str = description.map(CString::new).transpose()?;
let ptr = c_str.as_ref().map_or(core::ptr::null(), |s| s.as_ptr());
unsafe {
let val = qjs::JS_NewSymbol(ctx.as_ptr(), ptr, is_global);
let val = ctx.handle_exception(val)?;
Ok(Symbol(Value::from_js_value(ctx, val)))
}
}
pub fn new(ctx: Ctx<'js>) -> Result<Self> {
Self::new_inner(ctx, None::<&str>, false)
}
pub fn with_description(ctx: Ctx<'js>, description: impl Into<Vec<u8>>) -> Result<Self> {
Self::new_inner(ctx, Some(description), false)
}
pub fn new_global(ctx: Ctx<'js>, description: impl Into<Vec<u8>>) -> Result<Self> {
Self::new_inner(ctx, Some(description), true)
}
pub fn description(&self) -> Result<Value<'js>> {
let atom = Atom::from_str(self.0.ctx.clone(), "description")?;
unsafe {
let val = qjs::JS_GetProperty(self.0.ctx.as_ptr(), self.0.as_js_value(), atom.atom);
let val = self.0.ctx.handle_exception(val)?;
Ok(Value::from_js_value(self.0.ctx.clone(), val))
}
}
pub fn as_atom(&self) -> Atom<'js> {
Atom::from_value(self.0.ctx().clone(), &self.0)
.expect("symbols should always convert to atoms")
}
}
macro_rules! impl_symbols {
($($(#[$m:meta])? $fn_name:ident => $const_name:ident)*) => {
impl<'js> Symbol<'js> {
$(
$(#[$m])*
pub fn $fn_name(ctx: Ctx<'js>) -> Self {
let v = unsafe {
let v = qjs::JS_AtomToValue(ctx.as_ptr(),qjs::$const_name as qjs::JSAtom);
Value::from_js_value(ctx, v)
};
v.into_symbol().unwrap()
}
)*
}
};
}
impl_symbols! {
to_primitive => JS_ATOM_Symbol_toPrimitive
iterator => JS_ATOM_Symbol_iterator
r#match => JS_ATOM_Symbol_match
match_all => JS_ATOM_Symbol_matchAll
replace => JS_ATOM_Symbol_replace
search => JS_ATOM_Symbol_search
split => JS_ATOM_Symbol_split
has_instance => JS_ATOM_Symbol_hasInstance
species => JS_ATOM_Symbol_species
unscopables => JS_ATOM_Symbol_unscopables
async_iterator => JS_ATOM_Symbol_asyncIterator
}
#[cfg(test)]
mod test {
use crate::*;
#[test]
fn description() {
test_with(|ctx| {
let s: Symbol<'_> = ctx.eval("Symbol('foo bar baz')").unwrap();
assert_eq!(
s.description()
.unwrap()
.into_string()
.unwrap()
.to_string()
.unwrap(),
"foo bar baz"
);
let s: Symbol<'_> = ctx.eval("Symbol()").unwrap();
assert!(s.description().unwrap().is_undefined());
});
}
#[test]
fn new_without_description() {
test_with(|ctx| {
let s = Symbol::new(ctx).unwrap();
assert!(s.description().unwrap().is_undefined());
});
}
#[test]
fn new_with_description() {
test_with(|ctx| {
let s = Symbol::with_description(ctx, "test").unwrap();
assert_eq!(
s.description()
.unwrap()
.into_string()
.unwrap()
.to_string()
.unwrap(),
"test"
);
});
}
#[test]
fn new_unique() {
test_with(|ctx| {
let a = Symbol::with_description(ctx.clone(), "same").unwrap();
let b = Symbol::with_description(ctx, "same").unwrap();
assert_ne!(a, b);
});
}
#[test]
fn new_global() {
test_with(|ctx| {
let a = Symbol::new_global(ctx.clone(), "shared").unwrap();
let b = Symbol::new_global(ctx.clone(), "shared").unwrap();
assert_eq!(a, b);
let c: Symbol<'_> = ctx.eval("Symbol.for('shared')").unwrap();
assert_eq!(a, c);
});
}
}