rhai/packages/
lang_core.rs

1use crate::def_package;
2use crate::plugin::*;
3use crate::types::dynamic::Tag;
4use crate::{Dynamic, RhaiResult, RhaiResultOf, ERR, INT};
5use std::convert::TryFrom;
6#[cfg(feature = "no_std")]
7use std::prelude::v1::*;
8
9#[cfg(not(feature = "no_float"))]
10#[cfg(not(feature = "no_std"))]
11use crate::FLOAT;
12
13def_package! {
14    /// Package of core language features.
15    pub LanguageCorePackage(lib) {
16        lib.set_standard_lib(true);
17
18        combine_with_exported_module!(lib, "core", core_functions);
19
20        #[cfg(not(feature = "no_function"))]
21        #[cfg(not(feature = "no_index"))]
22        #[cfg(not(feature = "no_object"))]
23        combine_with_exported_module!(lib, "reflection", reflection_functions);
24    }
25}
26
27#[export_module]
28mod core_functions {
29    /// Exit the script evaluation immediately with a value.
30    ///
31    /// # Example
32    /// ```rhai
33    /// exit(42);
34    /// ```
35    #[rhai_fn(name = "exit", volatile, return_raw)]
36    pub fn exit_with_value(value: Dynamic) -> RhaiResult {
37        Err(ERR::Exit(value, Position::NONE).into())
38    }
39    /// Exit the script evaluation immediately with `()` as exit value.
40    ///
41    /// # Example
42    /// ```rhai
43    /// exit();
44    /// ```
45    #[rhai_fn(volatile, return_raw)]
46    pub fn exit() -> RhaiResult {
47        Err(ERR::Exit(Dynamic::UNIT, Position::NONE).into())
48    }
49    /// Take ownership of the data in a `Dynamic` value and return it.
50    /// The data is _NOT_ cloned.
51    ///
52    /// The original value is replaced with `()`.
53    ///
54    /// # Example
55    ///
56    /// ```rhai
57    /// let x = 42;
58    ///
59    /// print(take(x));         // prints 42
60    ///
61    /// print(x);               // prints ()
62    /// ```
63    #[rhai_fn(return_raw)]
64    pub fn take(value: &mut Dynamic) -> RhaiResult {
65        if value.is_read_only() {
66            return Err(
67                ERR::ErrorNonPureMethodCallOnConstant("take".to_string(), Position::NONE).into(),
68            );
69        }
70
71        Ok(std::mem::take(value))
72    }
73    /// Return the _tag_ of a `Dynamic` value.
74    ///
75    /// # Example
76    ///
77    /// ```rhai
78    /// let x = "hello, world!";
79    ///
80    /// x.tag = 42;
81    ///
82    /// print(x.tag);           // prints 42
83    /// ```
84    #[rhai_fn(name = "tag", get = "tag", pure)]
85    pub fn get_tag(value: &mut Dynamic) -> INT {
86        value.tag() as INT
87    }
88    /// Set the _tag_ of a `Dynamic` value.
89    ///
90    /// # Example
91    ///
92    /// ```rhai
93    /// let x = "hello, world!";
94    ///
95    /// x.tag = 42;
96    ///
97    /// print(x.tag);           // prints 42
98    /// ```
99    #[rhai_fn(name = "set_tag", set = "tag", return_raw)]
100    pub fn set_tag(value: &mut Dynamic, tag: INT) -> RhaiResultOf<()> {
101        const TAG_MIN: Tag = Tag::MIN;
102        const TAG_MAX: Tag = Tag::MAX;
103
104        if tag < TAG_MIN as INT {
105            return Err(ERR::ErrorArithmetic(
106                format!(
107                    "{tag} is too small to fit into a tag (must be between {TAG_MIN} and {TAG_MAX})"
108                ),
109                Position::NONE,
110            )
111            .into());
112        }
113        if tag > TAG_MAX as INT {
114            return Err(ERR::ErrorArithmetic(
115                format!(
116                    "{tag} is too large to fit into a tag (must be between {TAG_MIN} and {TAG_MAX})"
117                ),
118                Position::NONE,
119            )
120            .into());
121        }
122
123        value.set_tag(tag as Tag);
124        Ok(())
125    }
126
127    /// Block the current thread for a particular number of `seconds`.
128    ///
129    /// # Example
130    ///
131    /// ```rhai
132    /// // Do nothing for 10 seconds!
133    /// sleep(10.0);
134    /// ```
135    #[cfg(not(feature = "no_float"))]
136    #[cfg(not(feature = "no_std"))]
137    #[rhai_fn(name = "sleep", volatile)]
138    pub fn sleep_float(seconds: FLOAT) {
139        if !seconds.is_normal() || seconds.is_sign_negative() {
140            return;
141        }
142
143        #[cfg(not(feature = "f32_float"))]
144        std::thread::sleep(std::time::Duration::from_secs_f64(seconds));
145        #[cfg(feature = "f32_float")]
146        std::thread::sleep(std::time::Duration::from_secs_f32(seconds));
147    }
148    /// Block the current thread for a particular number of `seconds`.
149    ///
150    /// # Example
151    ///
152    /// ```rhai
153    /// // Do nothing for 10 seconds!
154    /// sleep(10);
155    /// ```
156    #[cfg(not(feature = "no_std"))]
157    #[rhai_fn(volatile)]
158    pub fn sleep(seconds: INT) {
159        if seconds <= 0 {
160            return;
161        }
162
163        std::thread::sleep(std::time::Duration::from_secs(
164            u64::try_from(seconds).unwrap(),
165        ));
166    }
167
168    /// Parse a JSON string into a value.
169    ///
170    /// # Example
171    ///
172    /// ```rhai
173    /// let m = parse_json(`{"a":1, "b":2, "c":3}`);
174    ///
175    /// print(m);       // prints #{"a":1, "b":2, "c":3}
176    /// ```
177    #[cfg(not(feature = "no_object"))]
178    #[rhai_fn(return_raw)]
179    pub fn parse_json(_ctx: NativeCallContext, json: &str) -> RhaiResultOf<Dynamic> {
180        #[cfg(feature = "metadata")]
181        let out = serde_json::from_str(json).map_err(|err| err.to_string().into());
182
183        #[cfg(not(feature = "metadata"))]
184        let out = _ctx.engine().parse_json(json, true).map(Dynamic::from);
185
186        out
187    }
188}
189
190#[cfg(not(feature = "no_function"))]
191#[cfg(not(feature = "no_index"))]
192#[cfg(not(feature = "no_object"))]
193#[export_module]
194mod reflection_functions {
195    use crate::module::FuncInfo;
196    use crate::{Array, Map, ScriptFnMetadata};
197
198    #[cfg(not(feature = "no_function"))]
199    #[cfg(not(feature = "no_index"))]
200    #[cfg(not(feature = "no_object"))]
201    fn collect(
202        ctx: NativeCallContext,
203        filter: impl Fn(FnNamespace, FnAccess, &str, usize, &ScriptFnMetadata) -> bool,
204    ) -> Array {
205        let engine = ctx.engine();
206
207        engine.collect_fn_metadata_impl(
208            Some(&ctx),
209            |FuncInfo {
210                 metadata,
211                 #[cfg(not(feature = "no_module"))]
212                 namespace,
213                 script,
214             }|
215             -> Option<Dynamic> {
216                let func = script.as_ref()?;
217
218                if !filter(
219                    metadata.namespace,
220                    func.access,
221                    func.name,
222                    func.params.len(),
223                    func,
224                ) {
225                    return None;
226                }
227
228                let mut map = Map::new();
229
230                #[cfg(not(feature = "no_module"))]
231                if !namespace.is_empty() {
232                    map.insert(
233                        "namespace".into(),
234                        engine.get_interned_string(namespace).into(),
235                    );
236                }
237                map.insert("name".into(), engine.get_interned_string(func.name).into());
238                map.insert(
239                    "access".into(),
240                    engine
241                        .get_interned_string(match func.access {
242                            FnAccess::Public => "public",
243                            FnAccess::Private => "private",
244                        })
245                        .into(),
246                );
247                map.insert(
248                    "is_anonymous".into(),
249                    func.name.starts_with(crate::engine::FN_ANONYMOUS).into(),
250                );
251                if let Some(this_type) = func.this_type {
252                    map.insert("this_type".into(), this_type.into());
253                }
254                map.insert(
255                    "params".into(),
256                    func.params
257                        .iter()
258                        .map(|&p| engine.get_interned_string(p).into())
259                        .collect::<Array>()
260                        .into(),
261                );
262                #[cfg(feature = "metadata")]
263                if !func.comments.is_empty() {
264                    map.insert(
265                        "comments".into(),
266                        func.comments
267                            .iter()
268                            .map(|&s| engine.get_interned_string(s).into())
269                            .collect::<Array>()
270                            .into(),
271                    );
272                }
273
274                Some(Dynamic::from_map(map))
275            },
276            false,
277        )
278    }
279
280    /// Return an array of object maps containing metadata of all script-defined functions.
281    #[rhai_fn(name = "get_fn_metadata_list", volatile)]
282    pub fn get_fn_metadata_list(ctx: NativeCallContext) -> Array {
283        collect(ctx, |_, _, _, _, _| true)
284    }
285    /// Return an array of object maps containing metadata of all script-defined functions
286    /// matching the specified name.
287    #[rhai_fn(name = "get_fn_metadata_list", volatile)]
288    pub fn get_fn_metadata(ctx: NativeCallContext, name: &str) -> Array {
289        collect(ctx, |_, _, n, _, _| n == name)
290    }
291    /// Return an array of object maps containing metadata of all script-defined functions
292    /// matching the specified name and arity (number of parameters).
293    #[rhai_fn(name = "get_fn_metadata_list", volatile)]
294    pub fn get_fn_metadata2(ctx: NativeCallContext, name: &str, params: INT) -> Array {
295        if params < 0 {
296            return Array::new();
297        }
298        let Ok(params) = usize::try_from(params) else {
299            return Array::new();
300        };
301
302        collect(ctx, |_, _, n, p, _| p == params && n == name)
303    }
304}