rhai/src/packages/lang_core.rs
2023-03-23 13:37:10 +08:00

326 lines
9.7 KiB
Rust

use crate::def_package;
use crate::module::ModuleFlags;
use crate::plugin::*;
use crate::types::dynamic::Tag;
use crate::{Dynamic, RhaiResultOf, ERR, INT};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
#[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "no_std"))]
use crate::FLOAT;
def_package! {
/// Package of core language features.
pub LanguageCorePackage(lib) {
lib.flags |= ModuleFlags::STANDARD_LIB;
combine_with_exported_module!(lib, "core", core_functions);
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
combine_with_exported_module!(lib, "reflection", reflection_functions);
}
}
#[export_module]
mod core_functions {
/// Return the _tag_ of a `Dynamic` value.
///
/// # Example
///
/// ```rhai
/// let x = "hello, world!";
///
/// x.tag = 42;
///
/// print(x.tag); // prints 42
/// ```
#[rhai_fn(name = "tag", get = "tag", pure)]
pub fn get_tag(value: &mut Dynamic) -> INT {
value.tag() as INT
}
/// Set the _tag_ of a `Dynamic` value.
///
/// # Example
///
/// ```rhai
/// let x = "hello, world!";
///
/// x.tag = 42;
///
/// print(x.tag); // prints 42
/// ```
#[rhai_fn(name = "set_tag", set = "tag", return_raw)]
pub fn set_tag(value: &mut Dynamic, tag: INT) -> RhaiResultOf<()> {
const TAG_MIN: Tag = Tag::MIN;
const TAG_MAX: Tag = Tag::MAX;
if tag < TAG_MIN as INT {
Err(ERR::ErrorArithmetic(
format!(
"{tag} is too small to fit into a tag (must be between {TAG_MIN} and {TAG_MAX})"
),
Position::NONE,
)
.into())
} else if tag > TAG_MAX as INT {
Err(ERR::ErrorArithmetic(
format!(
"{tag} is too large to fit into a tag (must be between {TAG_MIN} and {TAG_MAX})"
),
Position::NONE,
)
.into())
} else {
value.set_tag(tag as Tag);
Ok(())
}
}
/// Block the current thread for a particular number of `seconds`.
///
/// # Example
///
/// ```rhai
/// // Do nothing for 10 seconds!
/// sleep(10.0);
/// ```
#[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "no_std"))]
#[rhai_fn(name = "sleep")]
pub fn sleep_float(seconds: FLOAT) {
if seconds <= 0.0 {
return;
}
#[cfg(not(feature = "f32_float"))]
std::thread::sleep(std::time::Duration::from_secs_f64(seconds));
#[cfg(feature = "f32_float")]
std::thread::sleep(std::time::Duration::from_secs_f32(seconds));
}
/// Block the current thread for a particular number of `seconds`.
///
/// # Example
///
/// ```rhai
/// // Do nothing for 10 seconds!
/// sleep(10);
/// ```
#[cfg(not(feature = "no_std"))]
pub fn sleep(seconds: INT) {
if seconds > 0 {
#[allow(clippy::cast_sign_loss)]
std::thread::sleep(std::time::Duration::from_secs(seconds as u64));
}
}
/// Parse a JSON string into a value.
///
/// # Example
///
/// ```rhai
/// let m = parse_json(`{"a":1, "b":2, "c":3}`);
///
/// print(m); // prints #{"a":1, "b":2, "c":3}
/// ```
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
#[cfg(feature = "metadata")]
#[rhai_fn(return_raw)]
pub fn parse_json(_ctx: NativeCallContext, json: &str) -> RhaiResultOf<Dynamic> {
serde_json::from_str(json).map_err(|err| err.to_string().into())
}
}
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
#[export_module]
mod reflection_functions {
use crate::Array;
/// Return an array of object maps containing metadata of all script-defined functions.
pub fn get_fn_metadata_list(ctx: NativeCallContext) -> Array {
collect_fn_metadata(&ctx, |_, _, _, _, _| true)
}
/// Return an array of object maps containing metadata of all script-defined functions
/// matching the specified name.
#[rhai_fn(name = "get_fn_metadata_list")]
pub fn get_fn_metadata(ctx: NativeCallContext, name: &str) -> Array {
collect_fn_metadata(&ctx, |_, _, n, _, _| n == name)
}
/// Return an array of object maps containing metadata of all script-defined functions
/// matching the specified name and arity (number of parameters).
#[rhai_fn(name = "get_fn_metadata_list")]
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
pub fn get_fn_metadata2(ctx: NativeCallContext, name: &str, params: INT) -> Array {
if (0..=crate::MAX_USIZE_INT).contains(&params) {
collect_fn_metadata(&ctx, |_, _, n, p, _| p == (params as usize) && n == name)
} else {
Array::new()
}
}
}
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
fn collect_fn_metadata(
ctx: &NativeCallContext,
filter: impl Fn(FnNamespace, FnAccess, &str, usize, &crate::Shared<crate::ast::ScriptFnDef>) -> bool
+ Copy,
) -> crate::Array {
#[cfg(not(feature = "no_module"))]
use crate::Identifier;
use crate::{ast::ScriptFnDef, engine::FN_ANONYMOUS, Array, Map};
// Create a metadata record for a function.
fn make_metadata(
engine: &Engine,
#[cfg(not(feature = "no_module"))] namespace: Identifier,
func: &ScriptFnDef,
) -> Map {
let mut map = Map::new();
#[cfg(not(feature = "no_module"))]
if !namespace.is_empty() {
map.insert(
"namespace".into(),
engine.get_interned_string(namespace).into(),
);
}
map.insert(
"name".into(),
engine.get_interned_string(func.name.clone()).into(),
);
map.insert(
"access".into(),
engine
.get_interned_string(match func.access {
FnAccess::Public => "public",
FnAccess::Private => "private",
})
.into(),
);
map.insert(
"is_anonymous".into(),
func.name.starts_with(FN_ANONYMOUS).into(),
);
#[cfg(not(feature = "no_object"))]
if let Some(ref this_type) = func.this_type {
map.insert("this_type".into(), this_type.into());
}
map.insert(
"params".into(),
func.params
.iter()
.map(|p| engine.get_interned_string(p.clone()).into())
.collect::<Array>()
.into(),
);
#[cfg(feature = "metadata")]
if !func.comments.is_empty() {
map.insert(
"comments".into(),
func.comments
.iter()
.map(|s| engine.get_interned_string(s.as_str()).into())
.collect::<Array>()
.into(),
);
}
map
}
let engine = ctx.engine();
let mut list = Array::new();
ctx.iter_namespaces()
.flat_map(Module::iter_script_fn)
.filter(|(s, a, n, p, f)| filter(*s, *a, n, *p, f))
.for_each(|(.., f)| {
list.push(
make_metadata(
engine,
#[cfg(not(feature = "no_module"))]
Identifier::new_const(),
f,
)
.into(),
);
});
ctx.engine()
.global_modules
.iter()
.flat_map(|m| m.iter_script_fn())
.filter(|(ns, a, n, p, f)| filter(*ns, *a, n, *p, f))
.for_each(|(.., f)| {
list.push(
make_metadata(
engine,
#[cfg(not(feature = "no_module"))]
Identifier::new_const(),
f,
)
.into(),
);
});
#[cfg(not(feature = "no_module"))]
ctx.engine()
.global_sub_modules
.as_ref()
.into_iter()
.flatten()
.flat_map(|(_, m)| m.iter_script_fn())
.filter(|(ns, a, n, p, f)| filter(*ns, *a, n, *p, f))
.for_each(|(.., f)| {
list.push(
make_metadata(
engine,
#[cfg(not(feature = "no_module"))]
Identifier::new_const(),
f,
)
.into(),
);
});
#[cfg(not(feature = "no_module"))]
{
use crate::engine::NAMESPACE_SEPARATOR;
use crate::{Shared, SmartString};
// Recursively scan modules for script-defined functions.
fn scan_module(
engine: &Engine,
list: &mut Array,
namespace: &str,
module: &Module,
filter: impl Fn(FnNamespace, FnAccess, &str, usize, &Shared<ScriptFnDef>) -> bool + Copy,
) {
module
.iter_script_fn()
.filter(|(s, a, n, p, f)| filter(*s, *a, n, *p, f))
.for_each(|(.., f)| list.push(make_metadata(engine, namespace.into(), f).into()));
for (name, m) in module.iter_sub_modules() {
use std::fmt::Write;
let mut ns = SmartString::new_const();
write!(&mut ns, "{namespace}{}{name}", NAMESPACE_SEPARATOR).unwrap();
scan_module(engine, list, &ns, m, filter);
}
}
for (ns, m) in ctx.iter_imports_raw() {
scan_module(engine, &mut list, ns, m, filter);
}
}
list
}