rquickjs_core/value/
atom.rs

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