Skip to main content

spo_rhai/packages/
lang_core.rs

1use crate::def_package;
2use crate::module::ModuleFlags;
3use crate::plugin::*;
4use crate::types::dynamic::Tag;
5use crate::{Dynamic, RhaiResult, RhaiResultOf, ERR, INT};
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.flags |= ModuleFlags::STANDARD_LIB;
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        #[allow(clippy::cast_sign_loss)]
164        std::thread::sleep(std::time::Duration::from_secs(seconds as u64));
165    }
166
167    /// Parse a JSON string into a value.
168    ///
169    /// # Example
170    ///
171    /// ```rhai
172    /// let m = parse_json(`{"a":1, "b":2, "c":3}`);
173    ///
174    /// print(m);       // prints #{"a":1, "b":2, "c":3}
175    /// ```
176    #[cfg(not(feature = "no_index"))]
177    #[cfg(not(feature = "no_object"))]
178    #[cfg(feature = "metadata")]
179    #[rhai_fn(return_raw)]
180    pub fn parse_json(_ctx: NativeCallContext, json: &str) -> RhaiResultOf<Dynamic> {
181        serde_json::from_str(json).map_err(|err| err.to_string().into())
182    }
183}
184
185#[cfg(not(feature = "no_function"))]
186#[cfg(not(feature = "no_index"))]
187#[cfg(not(feature = "no_object"))]
188#[export_module]
189mod reflection_functions {
190    use crate::Array;
191
192    /// Return an array of object maps containing metadata of all script-defined functions.
193    pub fn get_fn_metadata_list(ctx: NativeCallContext) -> Array {
194        collect_fn_metadata(&ctx, |_, _, _, _, _| true)
195    }
196    /// Return an array of object maps containing metadata of all script-defined functions
197    /// matching the specified name.
198    #[rhai_fn(name = "get_fn_metadata_list")]
199    pub fn get_fn_metadata(ctx: NativeCallContext, name: &str) -> Array {
200        collect_fn_metadata(&ctx, |_, _, n, _, _| n == name)
201    }
202    /// Return an array of object maps containing metadata of all script-defined functions
203    /// matching the specified name and arity (number of parameters).
204    #[rhai_fn(name = "get_fn_metadata_list")]
205    #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
206    pub fn get_fn_metadata2(ctx: NativeCallContext, name: &str, params: INT) -> Array {
207        if !(0..=crate::MAX_USIZE_INT).contains(&params) {
208            return Array::new();
209        }
210
211        collect_fn_metadata(&ctx, |_, _, n, p, _| p == (params as usize) && n == name)
212    }
213}
214
215#[cfg(not(feature = "no_function"))]
216#[cfg(not(feature = "no_index"))]
217#[cfg(not(feature = "no_object"))]
218fn collect_fn_metadata(
219    ctx: &NativeCallContext,
220    filter: impl Fn(FnNamespace, FnAccess, &str, usize, &crate::Shared<crate::ast::ScriptFuncDef>) -> bool
221        + Copy,
222) -> crate::Array {
223    #[cfg(not(feature = "no_module"))]
224    use crate::Identifier;
225    use crate::{ast::ScriptFuncDef, engine::FN_ANONYMOUS, Array, Map};
226
227    // Create a metadata record for a function.
228    fn make_metadata(
229        engine: &Engine,
230        #[cfg(not(feature = "no_module"))] namespace: Identifier,
231        func: &ScriptFuncDef,
232    ) -> Map {
233        let mut map = Map::new();
234
235        #[cfg(not(feature = "no_module"))]
236        if !namespace.is_empty() {
237            map.insert(
238                "namespace".into(),
239                engine.get_interned_string(namespace).into(),
240            );
241        }
242        map.insert(
243            "name".into(),
244            engine.get_interned_string(func.name.clone()).into(),
245        );
246        map.insert(
247            "access".into(),
248            engine
249                .get_interned_string(match func.access {
250                    FnAccess::Public => "public",
251                    FnAccess::Private => "private",
252                })
253                .into(),
254        );
255        map.insert(
256            "is_anonymous".into(),
257            func.name.starts_with(FN_ANONYMOUS).into(),
258        );
259        #[cfg(not(feature = "no_object"))]
260        if let Some(ref this_type) = func.this_type {
261            map.insert("this_type".into(), this_type.into());
262        }
263        map.insert(
264            "params".into(),
265            func.params
266                .iter()
267                .map(|p| engine.get_interned_string(p.clone()).into())
268                .collect::<Array>()
269                .into(),
270        );
271        #[cfg(feature = "metadata")]
272        if !func.comments.is_empty() {
273            map.insert(
274                "comments".into(),
275                func.comments
276                    .iter()
277                    .map(|s| engine.get_interned_string(s).into())
278                    .collect::<Array>()
279                    .into(),
280            );
281        }
282
283        map
284    }
285
286    let engine = ctx.engine();
287    let mut list = Array::new();
288
289    ctx.iter_namespaces()
290        .flat_map(Module::iter_script_fn)
291        .filter(|(s, a, n, p, f)| filter(*s, *a, n, *p, f))
292        .for_each(|(.., f)| {
293            list.push(
294                make_metadata(
295                    engine,
296                    #[cfg(not(feature = "no_module"))]
297                    Identifier::new_const(),
298                    f,
299                )
300                .into(),
301            );
302        });
303
304    ctx.engine()
305        .global_modules
306        .iter()
307        .flat_map(|m| m.iter_script_fn())
308        .filter(|(ns, a, n, p, f)| filter(*ns, *a, n, *p, f))
309        .for_each(|(.., f)| {
310            list.push(
311                make_metadata(
312                    engine,
313                    #[cfg(not(feature = "no_module"))]
314                    Identifier::new_const(),
315                    f,
316                )
317                .into(),
318            );
319        });
320
321    #[cfg(not(feature = "no_module"))]
322    ctx.engine()
323        .global_sub_modules
324        .values()
325        .flat_map(|m| m.iter_script_fn())
326        .filter(|(ns, a, n, p, f)| filter(*ns, *a, n, *p, f))
327        .for_each(|(.., f)| {
328            list.push(
329                make_metadata(
330                    engine,
331                    #[cfg(not(feature = "no_module"))]
332                    Identifier::new_const(),
333                    f,
334                )
335                .into(),
336            );
337        });
338
339    #[cfg(not(feature = "no_module"))]
340    {
341        use crate::engine::NAMESPACE_SEPARATOR;
342        use crate::{Shared, SmartString};
343
344        // Recursively scan modules for script-defined functions.
345        fn scan_module(
346            engine: &Engine,
347            list: &mut Array,
348            namespace: &str,
349            module: &Module,
350            filter: impl Fn(FnNamespace, FnAccess, &str, usize, &Shared<ScriptFuncDef>) -> bool + Copy,
351        ) {
352            module
353                .iter_script_fn()
354                .filter(|(s, a, n, p, f)| filter(*s, *a, n, *p, f))
355                .for_each(|(.., f)| list.push(make_metadata(engine, namespace.into(), f).into()));
356            for (name, m) in module.iter_sub_modules() {
357                use std::fmt::Write;
358
359                let mut ns = SmartString::new_const();
360                write!(&mut ns, "{namespace}{NAMESPACE_SEPARATOR}{name}").unwrap();
361                scan_module(engine, list, &ns, m, filter);
362            }
363        }
364
365        for (ns, m) in ctx.global_runtime_state().iter_imports_raw() {
366            scan_module(engine, &mut list, ns, m, filter);
367        }
368    }
369
370    list
371}