rquickjs_core/value/
atom.rs

1//!  QuickJS atom functionality.
2
3use crate::{qjs, Ctx, Error, Result, String, Value};
4use alloc::string::{String as StdString, ToString as _};
5use core::{ffi::CStr, hash::Hash, ptr::null_mut};
6
7mod predefined;
8pub use predefined::PredefinedAtom;
9
10/// A QuickJS Atom.
11///
12/// In QuickJS atoms are similar to interned string but with additional uses.
13/// A symbol for instance is just an atom.
14///
15/// # Representation
16///
17/// Atoms in QuickJS are handled differently depending on what type of index the represent.
18/// When the atom represents a number like index, like `object[1]` the atom is just
19/// a normal number.
20/// However when the atom represents a string link index like `object["foo"]` or `object.foo`
21/// the atom represents a value in a hashmap.
22#[derive(Debug)]
23pub struct Atom<'js> {
24    pub(crate) atom: qjs::JSAtom,
25    pub(crate) ctx: Ctx<'js>,
26}
27
28impl<'js> PartialEq for Atom<'js> {
29    fn eq(&self, other: &Self) -> bool {
30        self.atom == other.atom
31    }
32}
33impl<'js> Eq for Atom<'js> {}
34
35impl<'js> Hash for Atom<'js> {
36    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
37        state.write_u32(self.atom)
38    }
39}
40
41impl<'js> Atom<'js> {
42    /// Create an atom from a JavaScript value.
43    pub fn from_value(ctx: Ctx<'js>, val: &Value<'js>) -> Result<Atom<'js>> {
44        let atom = unsafe { qjs::JS_ValueToAtom(ctx.as_ptr(), val.as_js_value()) };
45        if atom == qjs::JS_ATOM_NULL {
46            // A value can be anything, including an object which might contain a callback so check
47            // for panics.
48            return Err(ctx.raise_exception());
49        }
50        Ok(Atom { atom, ctx })
51    }
52
53    /// Create an atom from a `u32`
54    pub fn from_u32(ctx: Ctx<'js>, val: u32) -> Result<Atom<'js>> {
55        let atom = unsafe { qjs::JS_NewAtomUInt32(ctx.as_ptr(), val) };
56        if atom == qjs::JS_ATOM_NULL {
57            // Should never invoke a callback so no panics
58            return Err(Error::Exception);
59        }
60        Ok(Atom { atom, ctx })
61    }
62
63    /// Create an atom from an `i32` via value
64    pub fn from_i32(ctx: Ctx<'js>, val: i32) -> Result<Atom<'js>> {
65        let atom =
66            unsafe { qjs::JS_ValueToAtom(ctx.as_ptr(), qjs::JS_MKVAL(qjs::JS_TAG_INT, val)) };
67        if atom == qjs::JS_ATOM_NULL {
68            // Should never invoke a callback so no panics
69            return Err(Error::Exception);
70        }
71        Ok(Atom { atom, ctx })
72    }
73
74    /// Create an atom from a `bool` via value
75    pub fn from_bool(ctx: Ctx<'js>, val: bool) -> Result<Atom<'js>> {
76        let val = if val { qjs::JS_TRUE } else { qjs::JS_FALSE };
77        let atom = unsafe { qjs::JS_ValueToAtom(ctx.as_ptr(), val) };
78        if atom == qjs::JS_ATOM_NULL {
79            // Should never invoke a callback so no panics
80            return Err(Error::Exception);
81        }
82        Ok(Atom { atom, ctx })
83    }
84
85    /// Create an atom from a `f64` via value
86    pub fn from_f64(ctx: Ctx<'js>, val: f64) -> Result<Atom<'js>> {
87        let atom = unsafe { qjs::JS_ValueToAtom(ctx.as_ptr(), qjs::JS_NewFloat64(val)) };
88        if atom == qjs::JS_ATOM_NULL {
89            // Should never invoke a callback so no panics
90            return Err(Error::Exception);
91        }
92        Ok(Atom { atom, ctx })
93    }
94
95    /// Create an atom from a Rust string
96    pub fn from_str(ctx: Ctx<'js>, name: &str) -> Result<Atom<'js>> {
97        unsafe {
98            let ptr = name.as_ptr() as *const core::ffi::c_char;
99            let atom = qjs::JS_NewAtomLen(ctx.as_ptr(), ptr, name.len() as _);
100            if atom == qjs::JS_ATOM_NULL {
101                // Should never invoke a callback so no panics
102                return Err(Error::Exception);
103            }
104            Ok(Atom { atom, ctx })
105        }
106    }
107
108    /// Create an atom from a predefined atom.
109    pub fn from_predefined(ctx: Ctx<'js>, predefined: PredefinedAtom) -> Atom<'js> {
110        unsafe { Atom::from_atom_val(ctx, predefined as qjs::JSAtom) }
111    }
112
113    /// Convert the atom to a JavaScript string.
114    pub fn to_string(&self) -> Result<StdString> {
115        unsafe {
116            let c_str = qjs::JS_AtomToCStringLen(self.ctx.as_ptr(), null_mut(), self.atom);
117            if c_str.is_null() {
118                // Might not ever happen but I am not 100% sure
119                // so just incase check it.
120                qjs::JS_FreeCString(self.ctx.as_ptr(), c_str);
121                return Err(Error::Unknown);
122            }
123            let bytes = CStr::from_ptr(c_str).to_bytes();
124            // Safety: QuickJS should return valid utf8 so this should be safe.
125            let res = core::str::from_utf8_unchecked(bytes).to_string();
126            qjs::JS_FreeCString(self.ctx.as_ptr(), c_str);
127            Ok(res)
128        }
129    }
130
131    /// Convert the atom to a JavaScript string.
132    pub fn to_js_string(&self) -> Result<String<'js>> {
133        unsafe {
134            let val = qjs::JS_AtomToString(self.ctx.as_ptr(), self.atom);
135            let val = self.ctx.handle_exception(val)?;
136            Ok(String::from_js_value(self.ctx.clone(), val))
137        }
138    }
139
140    /// Convert the atom to a JavaScript value.
141    pub fn to_value(&self) -> Result<Value<'js>> {
142        self.to_js_string().map(|String(value)| value)
143    }
144
145    pub(crate) unsafe fn from_atom_val(ctx: Ctx<'js>, val: qjs::JSAtom) -> Self {
146        Atom { atom: val, ctx }
147    }
148
149    pub(crate) unsafe fn from_atom_val_dup(ctx: Ctx<'js>, val: qjs::JSAtom) -> Self {
150        qjs::JS_DupAtom(ctx.as_ptr(), val);
151        Atom { atom: val, ctx }
152    }
153}
154
155impl<'js> Clone for Atom<'js> {
156    fn clone(&self) -> Atom<'js> {
157        let atom = unsafe { qjs::JS_DupAtom(self.ctx.as_ptr(), self.atom) };
158        Atom {
159            atom,
160            ctx: self.ctx.clone(),
161        }
162    }
163}
164
165impl<'js> Drop for Atom<'js> {
166    fn drop(&mut self) {
167        unsafe {
168            qjs::JS_FreeAtom(self.ctx.as_ptr(), self.atom);
169        }
170    }
171}