commit
90a337d4c1
14
RELEASES.md
14
RELEASES.md
@ -7,12 +7,26 @@ Version 0.19.9
|
||||
This version removes the confusing differences between _packages_ and _modules_
|
||||
by unifying the terminology and API under the global umbrella of _modules_.
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
* Property access in
|
||||
|
||||
Breaking changes
|
||||
----------------
|
||||
|
||||
* `Engine::load_package` is renamed `Engine::register_global_module` and now must explicitly pass a shared [`Module`].
|
||||
* `Engine::register_module` is renamed `Engine::register_static_module` and now must explicitly pass a shared [`Module`].
|
||||
* `Package::get` is renamed `Package::as_shared_module`.
|
||||
* `Engine::set_module_resolver` now takes a straight module resolver instead of an `Option`. To disable module resolving, use the new `DummyModuleResolver`.
|
||||
|
||||
Enhancements
|
||||
------------
|
||||
|
||||
* `Scope` is now `Clone + Hash`.
|
||||
* `Engine::register_static_module` now supports sub-module paths (e.g. `foo::bar::baz`).
|
||||
* `Engine::register_custom_operator` now accepts reserved symbols.
|
||||
* `Engine::register_custom_operator` now returns an error if given a precedence of zero.
|
||||
|
||||
|
||||
Version 0.19.8
|
||||
|
@ -3,7 +3,7 @@ use syn::{
|
||||
spanned::Spanned,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum ExportScope {
|
||||
PubOnly,
|
||||
Prefix(String),
|
||||
@ -22,12 +22,14 @@ pub trait ExportedParams: Sized {
|
||||
fn from_info(info: ExportInfo) -> syn::Result<Self>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AttrItem {
|
||||
pub key: proc_macro2::Ident,
|
||||
pub value: Option<syn::LitStr>,
|
||||
pub span: proc_macro2::Span,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExportInfo {
|
||||
pub item_span: proc_macro2::Span,
|
||||
pub items: Vec<AttrItem>,
|
||||
|
@ -22,23 +22,30 @@ use crate::attrs::{ExportInfo, ExportScope, ExportedParams};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Copy, Hash)]
|
||||
pub enum FnNamespaceAccess {
|
||||
Unset,
|
||||
Global,
|
||||
Internal,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
impl Default for FnNamespaceAccess {
|
||||
fn default() -> Self {
|
||||
Self::Unset
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Copy, Hash)]
|
||||
pub enum Index {
|
||||
Get,
|
||||
Set,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub enum Property {
|
||||
Get(syn::Ident),
|
||||
Set(syn::Ident),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub enum FnSpecialAccess {
|
||||
None,
|
||||
Index(Index),
|
||||
@ -97,11 +104,11 @@ pub(crate) fn print_type(ty: &syn::Type) -> String {
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct ExportedFnParams {
|
||||
pub name: Option<Vec<String>>,
|
||||
pub name: Vec<String>,
|
||||
pub return_raw: bool,
|
||||
pub skip: bool,
|
||||
pub special: FnSpecialAccess,
|
||||
pub namespace: Option<FnNamespaceAccess>,
|
||||
pub namespace: FnNamespaceAccess,
|
||||
pub span: Option<proc_macro2::Span>,
|
||||
}
|
||||
|
||||
@ -138,7 +145,7 @@ impl ExportedParams for ExportedFnParams {
|
||||
let mut name = Vec::new();
|
||||
let mut return_raw = false;
|
||||
let mut skip = false;
|
||||
let mut namespace = None;
|
||||
let mut namespace = FnNamespaceAccess::Unset;
|
||||
let mut special = FnSpecialAccess::None;
|
||||
for attr in attrs {
|
||||
let crate::attrs::AttrItem {
|
||||
@ -226,22 +233,16 @@ impl ExportedParams for ExportedFnParams {
|
||||
("global", Some(s)) | ("internal", Some(s)) => {
|
||||
return Err(syn::Error::new(s.span(), "extraneous value"))
|
||||
}
|
||||
("global", None) => {
|
||||
if let Some(ns) = namespace {
|
||||
if ns != FnNamespaceAccess::Global {
|
||||
return Err(syn::Error::new(key.span(), "conflicting namespace"));
|
||||
}
|
||||
}
|
||||
namespace = Some(FnNamespaceAccess::Global);
|
||||
}
|
||||
("internal", None) => {
|
||||
if let Some(ns) = namespace {
|
||||
if ns != FnNamespaceAccess::Internal {
|
||||
return Err(syn::Error::new(key.span(), "conflicting namespace"));
|
||||
}
|
||||
}
|
||||
namespace = Some(FnNamespaceAccess::Internal);
|
||||
}
|
||||
("global", None) => match namespace {
|
||||
FnNamespaceAccess::Unset => namespace = FnNamespaceAccess::Global,
|
||||
FnNamespaceAccess::Global => (),
|
||||
_ => return Err(syn::Error::new(key.span(), "conflicting namespace")),
|
||||
},
|
||||
("internal", None) => match namespace {
|
||||
FnNamespaceAccess::Unset => namespace = FnNamespaceAccess::Internal,
|
||||
FnNamespaceAccess::Internal => (),
|
||||
_ => return Err(syn::Error::new(key.span(), "conflicting namespace")),
|
||||
},
|
||||
(attr, _) => {
|
||||
return Err(syn::Error::new(
|
||||
key.span(),
|
||||
@ -252,7 +253,7 @@ impl ExportedParams for ExportedFnParams {
|
||||
}
|
||||
|
||||
Ok(ExportedFnParams {
|
||||
name: if name.is_empty() { None } else { Some(name) },
|
||||
name,
|
||||
return_raw,
|
||||
skip,
|
||||
special,
|
||||
@ -455,16 +456,12 @@ impl ExportedFn {
|
||||
}
|
||||
|
||||
pub(crate) fn exported_names(&self) -> Vec<syn::LitStr> {
|
||||
let mut literals = self
|
||||
let mut literals: Vec<_> = self
|
||||
.params
|
||||
.name
|
||||
.as_ref()
|
||||
.map(|v| {
|
||||
v.iter()
|
||||
.map(|s| syn::LitStr::new(s, proc_macro2::Span::call_site()))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|| Vec::new());
|
||||
.iter()
|
||||
.map(|s| syn::LitStr::new(s, proc_macro2::Span::call_site()))
|
||||
.collect();
|
||||
|
||||
if let Some((s, _, span)) = self.params.special.get_fn_name() {
|
||||
literals.push(syn::LitStr::new(&s, span));
|
||||
@ -481,11 +478,10 @@ impl ExportedFn {
|
||||
}
|
||||
|
||||
pub(crate) fn exported_name<'n>(&'n self) -> Cow<'n, str> {
|
||||
if let Some(ref name) = self.params.name {
|
||||
name.last().unwrap().as_str().into()
|
||||
} else {
|
||||
self.signature.ident.to_string().into()
|
||||
}
|
||||
self.params.name.last().map_or_else(
|
||||
|| self.signature.ident.to_string().into(),
|
||||
|s| s.as_str().into(),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn arg_list(&self) -> impl Iterator<Item = &syn::FnArg> {
|
||||
@ -712,10 +708,12 @@ impl ExportedFn {
|
||||
|
||||
pub fn generate_impl(&self, on_type_name: &str) -> proc_macro2::TokenStream {
|
||||
let sig_name = self.name().clone();
|
||||
let name = self.params.name.as_ref().map_or_else(
|
||||
|| self.name().to_string(),
|
||||
|names| names.last().unwrap().clone(),
|
||||
);
|
||||
let name = self
|
||||
.params
|
||||
.name
|
||||
.last()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| self.name().to_string());
|
||||
|
||||
let arg_count = self.arg_count();
|
||||
let is_method_call = self.mutable_receiver();
|
||||
|
@ -21,7 +21,7 @@
|
||||
//! let m = exported_module!(advanced_math);
|
||||
//! let mut r = StaticModuleResolver::new();
|
||||
//! r.insert("Math::Advanced", m);
|
||||
//! engine.set_module_resolver(Some(r));
|
||||
//! engine.set_module_resolver(r);
|
||||
//!
|
||||
//! assert_eq!(engine.eval::<FLOAT>(
|
||||
//! r#"
|
||||
@ -51,7 +51,7 @@
|
||||
//! set_exported_fn!(m, "euclidean_distance", distance_function);
|
||||
//! let mut r = StaticModuleResolver::new();
|
||||
//! r.insert("Math::Advanced", m);
|
||||
//! engine.set_module_resolver(Some(r));
|
||||
//! engine.set_module_resolver(r);
|
||||
//!
|
||||
//! assert_eq!(engine.eval::<FLOAT>(
|
||||
//! r#"
|
||||
|
@ -19,9 +19,9 @@ use std::borrow::Cow;
|
||||
use crate::attrs::{AttrItem, ExportInfo, ExportScope, ExportedParams};
|
||||
use crate::function::ExportedFnParams;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
|
||||
pub(crate) struct ExportedModParams {
|
||||
pub name: Option<String>,
|
||||
pub name: String,
|
||||
skip: bool,
|
||||
pub scope: ExportScope,
|
||||
}
|
||||
@ -49,21 +49,32 @@ impl ExportedParams for ExportedModParams {
|
||||
|
||||
fn from_info(info: ExportInfo) -> syn::Result<Self> {
|
||||
let ExportInfo { items: attrs, .. } = info;
|
||||
let mut name = None;
|
||||
let mut name = Default::default();
|
||||
let mut skip = false;
|
||||
let mut scope = ExportScope::default();
|
||||
let mut scope = None;
|
||||
for attr in attrs {
|
||||
let AttrItem { key, value, .. } = attr;
|
||||
match (key.to_string().as_ref(), value) {
|
||||
("name", Some(s)) => name = Some(s.value()),
|
||||
("name", Some(s)) => {
|
||||
let new_name = s.value();
|
||||
if name == new_name {
|
||||
return Err(syn::Error::new(key.span(), "conflicting name"));
|
||||
}
|
||||
name = new_name;
|
||||
}
|
||||
("name", None) => return Err(syn::Error::new(key.span(), "requires value")),
|
||||
|
||||
("skip", None) => skip = true,
|
||||
("skip", Some(s)) => return Err(syn::Error::new(s.span(), "extraneous value")),
|
||||
("export_prefix", Some(s)) => scope = ExportScope::Prefix(s.value()),
|
||||
|
||||
("export_prefix", Some(_)) | ("export_all", None) if scope.is_some() => {
|
||||
return Err(syn::Error::new(key.span(), "duplicate export scope"));
|
||||
}
|
||||
("export_prefix", Some(s)) => scope = Some(ExportScope::Prefix(s.value())),
|
||||
("export_prefix", None) => {
|
||||
return Err(syn::Error::new(key.span(), "requires value"))
|
||||
}
|
||||
("export_all", None) => scope = ExportScope::All,
|
||||
("export_all", None) => scope = Some(ExportScope::All),
|
||||
("export_all", Some(s)) => {
|
||||
return Err(syn::Error::new(s.span(), "extraneous value"))
|
||||
}
|
||||
@ -79,7 +90,7 @@ impl ExportedParams for ExportedModParams {
|
||||
Ok(ExportedModParams {
|
||||
name,
|
||||
skip,
|
||||
scope,
|
||||
scope: scope.unwrap_or_default(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
@ -87,7 +98,7 @@ impl ExportedParams for ExportedModParams {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Module {
|
||||
mod_all: Option<syn::ItemMod>,
|
||||
mod_all: syn::ItemMod,
|
||||
fns: Vec<ExportedFn>,
|
||||
consts: Vec<ExportedConst>,
|
||||
submodules: Vec<Module>,
|
||||
@ -183,7 +194,7 @@ impl Parse for Module {
|
||||
fns = new_vec![];
|
||||
}
|
||||
Ok(Module {
|
||||
mod_all: Some(mod_all),
|
||||
mod_all,
|
||||
fns,
|
||||
consts,
|
||||
submodules,
|
||||
@ -194,39 +205,27 @@ impl Parse for Module {
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Module {
|
||||
pub fn attrs(&self) -> Option<&Vec<syn::Attribute>> {
|
||||
self.mod_all.as_ref().map(|m| &m.attrs)
|
||||
pub fn attrs(&self) -> &Vec<syn::Attribute> {
|
||||
&self.mod_all.attrs
|
||||
}
|
||||
|
||||
pub fn module_name(&self) -> Option<&syn::Ident> {
|
||||
self.mod_all.as_ref().map(|m| &m.ident)
|
||||
pub fn module_name(&self) -> &syn::Ident {
|
||||
&self.mod_all.ident
|
||||
}
|
||||
|
||||
pub fn exported_name(&self) -> Option<Cow<str>> {
|
||||
if let Some(ref s) = self.params.name {
|
||||
Some(s.into())
|
||||
pub fn exported_name(&self) -> Cow<str> {
|
||||
if !self.params.name.is_empty() {
|
||||
self.params.name.as_str().into()
|
||||
} else {
|
||||
self.module_name().map(|m| m.to_string().into())
|
||||
self.module_name().to_string().into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_scope(&mut self, parent_scope: &ExportScope) {
|
||||
let keep = match (self.params.skip, parent_scope) {
|
||||
(true, _) => false,
|
||||
(_, ExportScope::PubOnly) => {
|
||||
if let Some(ref mod_all) = self.mod_all {
|
||||
matches!(mod_all.vis, syn::Visibility::Public(_))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
(_, ExportScope::Prefix(s)) => {
|
||||
if let Some(ref mod_all) = self.mod_all {
|
||||
mod_all.ident.to_string().starts_with(s)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
(_, ExportScope::PubOnly) => matches!(self.mod_all.vis, syn::Visibility::Public(_)),
|
||||
(_, ExportScope::Prefix(s)) => self.mod_all.ident.to_string().starts_with(s),
|
||||
(_, ExportScope::All) => true,
|
||||
};
|
||||
self.params.skip = !keep;
|
||||
@ -249,14 +248,13 @@ impl Module {
|
||||
|
||||
// Extract the current structure of the module.
|
||||
let Module {
|
||||
mod_all,
|
||||
mut mod_all,
|
||||
mut fns,
|
||||
consts,
|
||||
mut submodules,
|
||||
params,
|
||||
..
|
||||
} = self;
|
||||
let mut mod_all = mod_all.unwrap();
|
||||
let mod_name = mod_all.ident.clone();
|
||||
let (_, orig_content) = mod_all.content.take().unwrap();
|
||||
let mod_attrs = mem::take(&mut mod_all.attrs);
|
||||
@ -299,8 +297,8 @@ impl Module {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<&syn::Ident> {
|
||||
self.mod_all.as_ref().map(|m| &m.ident)
|
||||
pub fn name(&self) -> &syn::Ident {
|
||||
&self.mod_all.ident
|
||||
}
|
||||
|
||||
pub fn consts(&self) -> &[ExportedConst] {
|
||||
@ -317,10 +315,10 @@ impl Module {
|
||||
|
||||
pub fn content(&self) -> Option<&Vec<syn::Item>> {
|
||||
match self.mod_all {
|
||||
Some(syn::ItemMod {
|
||||
syn::ItemMod {
|
||||
content: Some((_, ref vec)),
|
||||
..
|
||||
}) => Some(vec),
|
||||
} => Some(vec),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ pub(crate) fn generated_module_path(
|
||||
}
|
||||
|
||||
type RegisterMacroInput = (syn::Expr, proc_macro2::TokenStream, syn::Path);
|
||||
|
||||
pub fn parse_register_macro(
|
||||
args: proc_macro::TokenStream,
|
||||
) -> Result<RegisterMacroInput, syn::Error> {
|
||||
|
@ -40,15 +40,13 @@ pub(crate) fn generate_body(
|
||||
if itemmod.skipped() {
|
||||
continue;
|
||||
}
|
||||
let module_name = itemmod.module_name().unwrap();
|
||||
let exported_name: syn::LitStr = if let Some(name) = itemmod.exported_name() {
|
||||
syn::LitStr::new(&name, proc_macro2::Span::call_site())
|
||||
} else {
|
||||
syn::LitStr::new(&module_name.to_string(), proc_macro2::Span::call_site())
|
||||
};
|
||||
let module_name = itemmod.module_name();
|
||||
let exported_name: syn::LitStr = syn::LitStr::new(
|
||||
itemmod.exported_name().as_ref(),
|
||||
proc_macro2::Span::call_site(),
|
||||
);
|
||||
let cfg_attrs: Vec<&syn::Attribute> = itemmod
|
||||
.attrs()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|&a| a.path.get_ident().map(|i| *i == "cfg").unwrap_or(false))
|
||||
.collect();
|
||||
@ -158,12 +156,14 @@ pub(crate) fn generate_body(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ns) = function.params().namespace {
|
||||
namespace = ns;
|
||||
match function.params().namespace {
|
||||
FnNamespaceAccess::Unset => (),
|
||||
ns => namespace = ns,
|
||||
}
|
||||
|
||||
let ns_str = syn::Ident::new(
|
||||
match namespace {
|
||||
FnNamespaceAccess::Unset => unreachable!(),
|
||||
FnNamespaceAccess::Global => "Global",
|
||||
FnNamespaceAccess::Internal => "Internal",
|
||||
},
|
||||
@ -243,24 +243,24 @@ pub(crate) fn check_rename_collisions(fns: &Vec<ExportedFn>) -> Result<(), syn::
|
||||
let mut fn_defs = HashMap::<String, proc_macro2::Span>::new();
|
||||
|
||||
for itemfn in fns.iter() {
|
||||
if itemfn.params().name.is_some() || itemfn.params().special != FnSpecialAccess::None {
|
||||
let mut names = itemfn
|
||||
if !itemfn.params().name.is_empty() || itemfn.params().special != FnSpecialAccess::None {
|
||||
let mut names: Vec<_> = itemfn
|
||||
.params()
|
||||
.name
|
||||
.as_ref()
|
||||
.map(|v| v.iter().map(|n| (n.clone(), n.clone())).collect())
|
||||
.unwrap_or_else(|| Vec::new());
|
||||
.iter()
|
||||
.map(|n| (n.clone(), n.clone()))
|
||||
.collect();
|
||||
|
||||
if let Some((s, n, _)) = itemfn.params().special.get_fn_name() {
|
||||
names.push((s, n));
|
||||
}
|
||||
|
||||
for (name, fn_name) in names {
|
||||
let current_span = itemfn.params().span.as_ref().unwrap();
|
||||
let current_span = itemfn.params().span.unwrap();
|
||||
let key = make_key(&name, itemfn);
|
||||
if let Some(other_span) = renames.insert(key, *current_span) {
|
||||
if let Some(other_span) = renames.insert(key, current_span) {
|
||||
let mut err = syn::Error::new(
|
||||
*current_span,
|
||||
current_span,
|
||||
format!("duplicate Rhai signature for '{}'", &fn_name),
|
||||
);
|
||||
err.combine(syn::Error::new(
|
||||
|
@ -18,14 +18,11 @@ fn raw_fn_test() -> Result<(), Box<EvalAltResult>> {
|
||||
engine.register_fn("get_mystic_number", || 42 as FLOAT);
|
||||
let mut m = Module::new();
|
||||
rhai::set_exported_fn!(m, "euclidean_distance", raw_fn::distance_function);
|
||||
let mut r = StaticModuleResolver::new();
|
||||
r.insert("Math::Advanced", m);
|
||||
engine.set_module_resolver(Some(r));
|
||||
engine.register_static_module("Math::Advanced", m.into());
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<FLOAT>(
|
||||
r#"import "Math::Advanced" as math;
|
||||
let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"#
|
||||
r#"let x = Math::Advanced::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"#
|
||||
)?,
|
||||
41.0
|
||||
);
|
||||
@ -48,16 +45,13 @@ fn raw_fn_mut_test() -> Result<(), Box<EvalAltResult>> {
|
||||
engine.register_fn("get_mystic_number", || 42 as FLOAT);
|
||||
let mut m = Module::new();
|
||||
rhai::set_exported_fn!(m, "add_in_place", raw_fn_mut::add_in_place);
|
||||
let mut r = StaticModuleResolver::new();
|
||||
r.insert("Math::Advanced", m);
|
||||
engine.set_module_resolver(Some(r));
|
||||
engine.register_static_module("Math::Advanced", m.into());
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<FLOAT>(
|
||||
r#"import "Math::Advanced" as math;
|
||||
let x = get_mystic_number();
|
||||
math::add_in_place(x, 1.0);
|
||||
x"#
|
||||
r#"let x = get_mystic_number();
|
||||
Math::Advanced::add_in_place(x, 1.0);
|
||||
x"#
|
||||
)?,
|
||||
43.0
|
||||
);
|
||||
@ -80,16 +74,10 @@ fn raw_fn_str_test() -> Result<(), Box<EvalAltResult>> {
|
||||
engine.register_fn("get_mystic_number", || 42 as FLOAT);
|
||||
let mut m = Module::new();
|
||||
rhai::set_exported_fn!(m, "write_out_str", raw_fn_str::write_out_str);
|
||||
let mut r = StaticModuleResolver::new();
|
||||
r.insert("Host::IO", m);
|
||||
engine.set_module_resolver(Some(r));
|
||||
engine.register_static_module("Host::IO", m.into());
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<bool>(
|
||||
r#"import "Host::IO" as io;
|
||||
let x = io::write_out_str("hello world!");
|
||||
x"#
|
||||
)?,
|
||||
engine.eval::<bool>(r#"let x = Host::IO::write_out_str("hello world!"); x"#)?,
|
||||
true
|
||||
);
|
||||
Ok(())
|
||||
@ -138,18 +126,16 @@ fn mut_opaque_ref_test() -> Result<(), Box<EvalAltResult>> {
|
||||
rhai::set_exported_fn!(m, "new_message", mut_opaque_ref::new_message);
|
||||
rhai::set_exported_fn!(m, "new_os_message", mut_opaque_ref::new_os_message);
|
||||
rhai::set_exported_fn!(m, "write_out_message", mut_opaque_ref::write_out_message);
|
||||
let mut r = StaticModuleResolver::new();
|
||||
r.insert("Host::Msg", m);
|
||||
engine.set_module_resolver(Some(r));
|
||||
engine.register_static_module("Host::Msg", m.into());
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<bool>(
|
||||
r#"import "Host::Msg" as msg;
|
||||
let message1 = msg::new_message(true, "it worked");
|
||||
let ok1 = msg::write_out_message(message1);
|
||||
let message2 = msg::new_os_message(true, 0);
|
||||
let ok2 = msg::write_out_message(message2);
|
||||
ok1 && ok2"#
|
||||
r#"
|
||||
let message1 = Host::Msg::new_message(true, "it worked");
|
||||
let ok1 = Host::Msg::write_out_message(message1);
|
||||
let message2 = Host::Msg::new_os_message(true, 0);
|
||||
let ok2 = Host::Msg::write_out_message(message2);
|
||||
ok1 && ok2"#
|
||||
)?,
|
||||
true
|
||||
);
|
||||
@ -179,14 +165,11 @@ fn raw_returning_fn_test() -> Result<(), Box<EvalAltResult>> {
|
||||
engine.register_fn("get_mystic_number", || 42 as FLOAT);
|
||||
let mut m = Module::new();
|
||||
rhai::set_exported_fn!(m, "euclidean_distance", raw_returning_fn::distance_function);
|
||||
let mut r = StaticModuleResolver::new();
|
||||
r.insert("Math::Advanced", m);
|
||||
engine.set_module_resolver(Some(r));
|
||||
engine.register_static_module("Math::Advanced", m.into());
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<FLOAT>(
|
||||
r#"import "Math::Advanced" as math;
|
||||
let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"#
|
||||
r#"let x = Math::Advanced::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"#
|
||||
)?,
|
||||
41.0
|
||||
);
|
||||
|
@ -12,14 +12,8 @@ pub mod empty_module {
|
||||
fn empty_module_test() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
let m = rhai::exported_module!(crate::empty_module::EmptyModule);
|
||||
let mut r = StaticModuleResolver::new();
|
||||
r.insert("Module::Empty", m);
|
||||
engine.set_module_resolver(Some(r));
|
||||
engine.register_static_module("Module::Empty", m.into());
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(r#"import "Module::Empty" as m; 42"#)?,
|
||||
42
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -39,16 +33,10 @@ pub mod one_fn_module {
|
||||
fn one_fn_module_test() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
let m = rhai::exported_module!(crate::one_fn_module::advanced_math);
|
||||
let mut r = StaticModuleResolver::new();
|
||||
r.insert("Math::Advanced", m);
|
||||
engine.set_module_resolver(Some(r));
|
||||
engine.register_static_module("Math::Advanced", m.into());
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<FLOAT>(
|
||||
r#"import "Math::Advanced" as math;
|
||||
let m = math::get_mystic_number();
|
||||
m"#
|
||||
)?,
|
||||
engine.eval::<FLOAT>(r#"let m = Math::Advanced::get_mystic_number();m"#)?,
|
||||
42.0
|
||||
);
|
||||
Ok(())
|
||||
@ -73,16 +61,14 @@ pub mod one_fn_and_const_module {
|
||||
fn one_fn_and_const_module_test() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
let m = rhai::exported_module!(crate::one_fn_and_const_module::advanced_math);
|
||||
let mut r = StaticModuleResolver::new();
|
||||
r.insert("Math::Advanced", m);
|
||||
engine.set_module_resolver(Some(r));
|
||||
engine.register_static_module("Math::Advanced", m.into());
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<FLOAT>(
|
||||
r#"import "Math::Advanced" as math;
|
||||
let m = math::MYSTIC_NUMBER;
|
||||
let x = math::euclidean_distance(0.0, 1.0, 0.0, m);
|
||||
x"#
|
||||
r#"
|
||||
let m = Math::Advanced::MYSTIC_NUMBER;
|
||||
let x = Math::Advanced::euclidean_distance(0.0, 1.0, 0.0, m);
|
||||
x"#
|
||||
)?,
|
||||
41.0
|
||||
);
|
||||
@ -105,16 +91,10 @@ pub mod raw_fn_str_module {
|
||||
fn raw_fn_str_module_test() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
let m = rhai::exported_module!(crate::raw_fn_str_module::host_io);
|
||||
let mut r = StaticModuleResolver::new();
|
||||
r.insert("Host::IO", m);
|
||||
engine.set_module_resolver(Some(r));
|
||||
engine.register_static_module("Host::IO", m.into());
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<bool>(
|
||||
r#"import "Host::IO" as io;
|
||||
let x = io::write_out_str("hello world!");
|
||||
x"#
|
||||
)?,
|
||||
engine.eval::<bool>(r#"let x = Host::IO::write_out_str("hello world!"); x"#)?,
|
||||
true
|
||||
);
|
||||
Ok(())
|
||||
@ -162,19 +142,17 @@ pub mod mut_opaque_ref_module {
|
||||
fn mut_opaque_ref_test() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
let m = rhai::exported_module!(crate::mut_opaque_ref_module::host_msg);
|
||||
let mut r = StaticModuleResolver::new();
|
||||
r.insert("Host::Msg", m);
|
||||
engine.set_module_resolver(Some(r));
|
||||
engine.register_static_module("Host::Msg", m.into());
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<bool>(
|
||||
r#"import "Host::Msg" as msg;
|
||||
let success = "it worked";
|
||||
let message1 = msg::new_message(true, success);
|
||||
let ok1 = msg::write_out_message(message1);
|
||||
let message2 = msg::new_os_message(true, 0);
|
||||
let ok2 = msg::write_out_message(message2);
|
||||
ok1 && ok2"#
|
||||
r#"
|
||||
let success = "it worked";
|
||||
let message1 = Host::Msg::new_message(true, success);
|
||||
let ok1 = Host::Msg::write_out_message(message1);
|
||||
let message2 = Host::Msg::new_os_message(true, 0);
|
||||
let ok2 = Host::Msg::write_out_message(message2);
|
||||
ok1 && ok2"#
|
||||
)?,
|
||||
true
|
||||
);
|
||||
@ -204,18 +182,16 @@ fn duplicate_fn_rename_test() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
engine.register_fn("get_mystic_number", || 42 as FLOAT);
|
||||
let m = rhai::exported_module!(crate::duplicate_fn_rename::my_adds);
|
||||
let mut r = StaticModuleResolver::new();
|
||||
r.insert("Math::Advanced", m);
|
||||
engine.set_module_resolver(Some(r));
|
||||
engine.register_static_module("Math::Advanced", m.into());
|
||||
|
||||
let output_array = engine.eval::<Array>(
|
||||
r#"import "Math::Advanced" as math;
|
||||
let fx = get_mystic_number();
|
||||
let fy = math::add(fx, 1.0);
|
||||
let ix = 42;
|
||||
let iy = math::add(ix, 1);
|
||||
[fy, iy]
|
||||
"#,
|
||||
r#"
|
||||
let fx = get_mystic_number();
|
||||
let fy = Math::Advanced::add(fx, 1.0);
|
||||
let ix = 42;
|
||||
let iy = Math::Advanced::add(ix, 1);
|
||||
[fy, iy]
|
||||
"#,
|
||||
)?;
|
||||
assert_eq!(&output_array[0].as_float().unwrap(), &43.0);
|
||||
assert_eq!(&output_array[1].as_int().unwrap(), &43);
|
||||
@ -291,6 +267,7 @@ fn multiple_fn_rename_test() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
mod export_by_prefix {
|
||||
use rhai::plugin::*;
|
||||
|
||||
#[export_module(export_prefix = "foo_")]
|
||||
pub mod my_adds {
|
||||
use rhai::{FLOAT, INT};
|
||||
@ -328,20 +305,18 @@ mod export_by_prefix {
|
||||
fn export_by_prefix_test() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
let m = rhai::exported_module!(crate::export_by_prefix::my_adds);
|
||||
let mut r = StaticModuleResolver::new();
|
||||
r.insert("Math::Advanced", m);
|
||||
engine.set_module_resolver(Some(r));
|
||||
engine.register_static_module("Math::Advanced", m.into());
|
||||
|
||||
let output_array = engine.eval::<Array>(
|
||||
r#"import "Math::Advanced" as math;
|
||||
let ex = 41.0;
|
||||
let fx = math::foo_add_f(ex, 1.0);
|
||||
let gx = math::foo_m(41.0, 1.0);
|
||||
let ei = 41;
|
||||
let fi = math::bar_add_i(ei, 1);
|
||||
let gi = math::foo_n(41, 1);
|
||||
[fx, gx, fi, gi]
|
||||
"#,
|
||||
r#"
|
||||
let ex = 41.0;
|
||||
let fx = Math::Advanced::foo_add_f(ex, 1.0);
|
||||
let gx = Math::Advanced::foo_m(41.0, 1.0);
|
||||
let ei = 41;
|
||||
let fi = Math::Advanced::bar_add_i(ei, 1);
|
||||
let gi = Math::Advanced::foo_n(41, 1);
|
||||
[fx, gx, fi, gi]
|
||||
"#,
|
||||
)?;
|
||||
assert_eq!(&output_array[0].as_float().unwrap(), &42.0);
|
||||
assert_eq!(&output_array[1].as_float().unwrap(), &42.0);
|
||||
@ -349,30 +324,31 @@ fn export_by_prefix_test() -> Result<(), Box<EvalAltResult>> {
|
||||
assert_eq!(&output_array[3].as_int().unwrap(), &42);
|
||||
|
||||
assert!(matches!(*engine.eval::<FLOAT>(
|
||||
r#"import "Math::Advanced" as math;
|
||||
let ex = 41.0;
|
||||
let fx = math::foo_add_float2(ex, 1.0);
|
||||
fx
|
||||
"#).unwrap_err(),
|
||||
EvalAltResult::ErrorFunctionNotFound(s, p)
|
||||
if s == "math::foo_add_float2 (f64, f64)"
|
||||
&& p == rhai::Position::new(3, 23)));
|
||||
r#"
|
||||
let ex = 41.0;
|
||||
let fx = Math::Advanced::foo_add_float2(ex, 1.0);
|
||||
fx
|
||||
"#).unwrap_err(),
|
||||
EvalAltResult::ErrorFunctionNotFound(s, p)
|
||||
if s == "Math::Advanced::foo_add_float2 (f64, f64)"
|
||||
&& p == rhai::Position::new(3, 34)));
|
||||
|
||||
assert!(matches!(*engine.eval::<FLOAT>(
|
||||
r#"import "Math::Advanced" as math;
|
||||
let ex = 41.0;
|
||||
let fx = math::bar_m(ex, 1.0);
|
||||
fx
|
||||
"#).unwrap_err(),
|
||||
EvalAltResult::ErrorFunctionNotFound(s, p)
|
||||
if s == "math::bar_m (f64, f64)"
|
||||
&& p == rhai::Position::new(3, 23)));
|
||||
r#"
|
||||
let ex = 41.0;
|
||||
let fx = Math::Advanced::bar_m(ex, 1.0);
|
||||
fx
|
||||
"#).unwrap_err(),
|
||||
EvalAltResult::ErrorFunctionNotFound(s, p)
|
||||
if s == "Math::Advanced::bar_m (f64, f64)"
|
||||
&& p == rhai::Position::new(3, 34)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod export_all {
|
||||
use rhai::plugin::*;
|
||||
|
||||
#[export_module(export_all)]
|
||||
pub mod my_adds {
|
||||
use rhai::{FLOAT, INT};
|
||||
@ -411,20 +387,18 @@ mod export_all {
|
||||
fn export_all_test() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
let m = rhai::exported_module!(crate::export_all::my_adds);
|
||||
let mut r = StaticModuleResolver::new();
|
||||
r.insert("Math::Advanced", m);
|
||||
engine.set_module_resolver(Some(r));
|
||||
engine.register_static_module("Math::Advanced", m.into());
|
||||
|
||||
let output_array = engine.eval::<Array>(
|
||||
r#"import "Math::Advanced" as math;
|
||||
let ex = 41.0;
|
||||
let fx = math::foo_add_f(ex, 1.0);
|
||||
let gx = math::foo_m(41.0, 1.0);
|
||||
let ei = 41;
|
||||
let fi = math::foo_add_i(ei, 1);
|
||||
let gi = math::foo_n(41, 1);
|
||||
[fx, gx, fi, gi]
|
||||
"#,
|
||||
r#"
|
||||
let ex = 41.0;
|
||||
let fx = Math::Advanced::foo_add_f(ex, 1.0);
|
||||
let gx = Math::Advanced::foo_m(41.0, 1.0);
|
||||
let ei = 41;
|
||||
let fi = Math::Advanced::foo_add_i(ei, 1);
|
||||
let gi = Math::Advanced::foo_n(41, 1);
|
||||
[fx, gx, fi, gi]
|
||||
"#,
|
||||
)?;
|
||||
assert_eq!(&output_array[0].as_float().unwrap(), &42.0);
|
||||
assert_eq!(&output_array[1].as_float().unwrap(), &42.0);
|
||||
@ -432,14 +406,14 @@ fn export_all_test() -> Result<(), Box<EvalAltResult>> {
|
||||
assert_eq!(&output_array[3].as_int().unwrap(), &42);
|
||||
|
||||
assert!(matches!(*engine.eval::<INT>(
|
||||
r#"import "Math::Advanced" as math;
|
||||
let ex = 41;
|
||||
let fx = math::foo_p(ex, 1);
|
||||
fx
|
||||
"#).unwrap_err(),
|
||||
EvalAltResult::ErrorFunctionNotFound(s, p)
|
||||
if s == "math::foo_p (i64, i64)"
|
||||
&& p == rhai::Position::new(3, 23)));
|
||||
r#"
|
||||
let ex = 41;
|
||||
let fx = Math::Advanced::foo_p(ex, 1);
|
||||
fx
|
||||
"#).unwrap_err(),
|
||||
EvalAltResult::ErrorFunctionNotFound(s, p)
|
||||
if s == "Math::Advanced::foo_p (i64, i64)"
|
||||
&& p == rhai::Position::new(3, 34)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -20,16 +20,10 @@ pub mod one_fn_module_nested_attr {
|
||||
fn one_fn_module_nested_attr_test() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
let m = rhai::exported_module!(crate::one_fn_module_nested_attr::advanced_math);
|
||||
let mut r = StaticModuleResolver::new();
|
||||
r.insert("Math::Advanced", m);
|
||||
engine.set_module_resolver(Some(r));
|
||||
engine.register_static_module("Math::Advanced", m.into());
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<FLOAT>(
|
||||
r#"import "Math::Advanced" as math;
|
||||
let m = math::get_mystic_number();
|
||||
m"#
|
||||
)?,
|
||||
engine.eval::<FLOAT>(r#"let m = Math::Advanced::get_mystic_number(); m"#)?,
|
||||
42.0
|
||||
);
|
||||
Ok(())
|
||||
@ -56,16 +50,10 @@ pub mod one_fn_submodule_nested_attr {
|
||||
fn one_fn_submodule_nested_attr_test() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
let m = rhai::exported_module!(crate::one_fn_submodule_nested_attr::advanced_math);
|
||||
let mut r = StaticModuleResolver::new();
|
||||
r.insert("Math::Advanced", m);
|
||||
engine.set_module_resolver(Some(r));
|
||||
engine.register_static_module("Math::Advanced", m.into());
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<FLOAT>(
|
||||
r#"import "Math::Advanced" as math;
|
||||
let m = math::constants::get_mystic_number();
|
||||
m"#
|
||||
)?,
|
||||
engine.eval::<FLOAT>(r#"let m = Math::Advanced::constants::get_mystic_number(); m"#)?,
|
||||
42.0
|
||||
);
|
||||
Ok(())
|
||||
@ -73,8 +61,8 @@ fn one_fn_submodule_nested_attr_test() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
mod export_nested_by_prefix {
|
||||
use rhai::plugin::*;
|
||||
#[export_module(export_prefix = "foo_")]
|
||||
|
||||
#[export_module(export_prefix = "foo_")]
|
||||
pub mod my_adds {
|
||||
pub mod foo_first_adders {
|
||||
use rhai::{FLOAT, INT};
|
||||
@ -131,26 +119,24 @@ mod export_nested_by_prefix {
|
||||
fn export_nested_by_prefix_test() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
let m = rhai::exported_module!(crate::export_nested_by_prefix::my_adds);
|
||||
let mut r = StaticModuleResolver::new();
|
||||
r.insert("Math::Advanced", m);
|
||||
engine.set_module_resolver(Some(r));
|
||||
engine.register_static_module("Math::Advanced", m.into());
|
||||
|
||||
let output_array = engine.eval::<Array>(
|
||||
r#"import "Math::Advanced" as math;
|
||||
let ex = 41.0;
|
||||
let fx = math::foo_first_adders::add_float(ex, 1.0);
|
||||
r#"
|
||||
let ex = 41.0;
|
||||
let fx = Math::Advanced::foo_first_adders::add_float(ex, 1.0);
|
||||
|
||||
let ei = 41;
|
||||
let fi = math::foo_first_adders::add_int(ei, 1);
|
||||
let ei = 41;
|
||||
let fi = Math::Advanced::foo_first_adders::add_int(ei, 1);
|
||||
|
||||
let gx = 41.0;
|
||||
let hx = math::foo_second_adders::add_float(gx, 1.0);
|
||||
let gx = 41.0;
|
||||
let hx = Math::Advanced::foo_second_adders::add_float(gx, 1.0);
|
||||
|
||||
let gi = 41;
|
||||
let hi = math::foo_second_adders::add_int(gi, 1);
|
||||
let gi = 41;
|
||||
let hi = Math::Advanced::foo_second_adders::add_int(gi, 1);
|
||||
|
||||
[fx, hx, fi, hi]
|
||||
"#,
|
||||
[fx, hx, fi, hi]
|
||||
"#,
|
||||
)?;
|
||||
assert_eq!(&output_array[0].as_float().unwrap(), &42.0);
|
||||
assert_eq!(&output_array[1].as_float().unwrap(), &42.0);
|
||||
@ -158,44 +144,44 @@ fn export_nested_by_prefix_test() -> Result<(), Box<EvalAltResult>> {
|
||||
assert_eq!(&output_array[3].as_int().unwrap(), &42);
|
||||
|
||||
assert!(matches!(*engine.eval::<FLOAT>(
|
||||
r#"import "Math::Advanced" as math;
|
||||
let ex = 41.0;
|
||||
let fx = math::foo_third_adders::add_float(ex, 1.0);
|
||||
fx
|
||||
"#).unwrap_err(),
|
||||
EvalAltResult::ErrorFunctionNotFound(s, p)
|
||||
if s == "math::foo_third_adders::add_float (f64, f64)"
|
||||
&& p == rhai::Position::new(3, 41)));
|
||||
r#"
|
||||
let ex = 41.0;
|
||||
let fx = Math::Advanced::foo_third_adders::add_float(ex, 1.0);
|
||||
fx
|
||||
"#).unwrap_err(),
|
||||
EvalAltResult::ErrorFunctionNotFound(s, p)
|
||||
if s == "Math::Advanced::foo_third_adders::add_float (f64, f64)"
|
||||
&& p == rhai::Position::new(3, 52)));
|
||||
|
||||
assert!(matches!(*engine.eval::<FLOAT>(
|
||||
r#"import "Math::Advanced" as math;
|
||||
let ex = 41;
|
||||
let fx = math::foo_third_adders::add_int(ex, 1);
|
||||
fx
|
||||
"#).unwrap_err(),
|
||||
EvalAltResult::ErrorFunctionNotFound(s, p)
|
||||
if s == "math::foo_third_adders::add_int (i64, i64)"
|
||||
&& p == rhai::Position::new(3, 41)));
|
||||
r#"
|
||||
let ex = 41;
|
||||
let fx = Math::Advanced::foo_third_adders::add_int(ex, 1);
|
||||
fx
|
||||
"#).unwrap_err(),
|
||||
EvalAltResult::ErrorFunctionNotFound(s, p)
|
||||
if s == "Math::Advanced::foo_third_adders::add_int (i64, i64)"
|
||||
&& p == rhai::Position::new(3, 52)));
|
||||
|
||||
assert!(matches!(*engine.eval::<FLOAT>(
|
||||
r#"import "Math::Advanced" as math;
|
||||
let ex = 41;
|
||||
let fx = math::bar_fourth_adders::add_int(ex, 1);
|
||||
fx
|
||||
"#).unwrap_err(),
|
||||
EvalAltResult::ErrorFunctionNotFound(s, p)
|
||||
if s == "math::bar_fourth_adders::add_int (i64, i64)"
|
||||
&& p == rhai::Position::new(3, 42)));
|
||||
r#"
|
||||
let ex = 41;
|
||||
let fx = Math::Advanced::bar_fourth_adders::add_int(ex, 1);
|
||||
fx
|
||||
"#).unwrap_err(),
|
||||
EvalAltResult::ErrorFunctionNotFound(s, p)
|
||||
if s == "Math::Advanced::bar_fourth_adders::add_int (i64, i64)"
|
||||
&& p == rhai::Position::new(3, 53)));
|
||||
|
||||
assert!(matches!(*engine.eval::<FLOAT>(
|
||||
r#"import "Math::Advanced" as math;
|
||||
let ex = 41.0;
|
||||
let fx = math::bar_fourth_adders::add_float(ex, 1.0);
|
||||
fx
|
||||
"#).unwrap_err(),
|
||||
EvalAltResult::ErrorFunctionNotFound(s, p)
|
||||
if s == "math::bar_fourth_adders::add_float (f64, f64)"
|
||||
&& p == rhai::Position::new(3, 42)));
|
||||
r#"
|
||||
let ex = 41.0;
|
||||
let fx = Math::Advanced::bar_fourth_adders::add_float(ex, 1.0);
|
||||
fx
|
||||
"#).unwrap_err(),
|
||||
EvalAltResult::ErrorFunctionNotFound(s, p)
|
||||
if s == "Math::Advanced::bar_fourth_adders::add_float (f64, f64)"
|
||||
&& p == rhai::Position::new(3, 53)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -127,19 +127,22 @@ The Rhai Scripting Language
|
||||
7. [One Engine Instance Per Call](patterns/parallel.md)
|
||||
8. [Scriptable Event Handler with State](patterns/events.md)
|
||||
9. [Dynamic Constants Provider](patterns/dynamic-const.md)
|
||||
9. [Advanced Topics](advanced.md)
|
||||
10. [Capture Scope for Function Call](language/fn-capture.md)
|
||||
11. [Low-Level API](rust/register-raw.md)
|
||||
12. [Variable Resolver](engine/var.md)
|
||||
13. [Use as DSL](engine/dsl.md)
|
||||
9. [Advanced Topics](advanced.md)
|
||||
1. [Capture Scope for Function Call](language/fn-capture.md)
|
||||
2. [Low-Level API](rust/register-raw.md)
|
||||
3. [Variable Resolver](engine/var.md)
|
||||
4. [Use as DSL](engine/dsl.md)
|
||||
1. [Disable Keywords and/or Operators](engine/disable.md)
|
||||
2. [Custom Operators](engine/custom-op.md)
|
||||
3. [Extending with Custom Syntax](engine/custom-syntax.md)
|
||||
14. [Multiple Instantiation](patterns/multiple.md)
|
||||
15. [Functions Metadata](engine/metadata/index.md)
|
||||
4. [Generate Function Signatures](engine/metadata/gen_fn_sig.md)
|
||||
5. [Export Metadata to JSON](engine/metadata/export_to_json.md)
|
||||
10. [Appendix](appendix/index.md)
|
||||
5. [Multiple Instantiation](patterns/multiple.md)
|
||||
6. [Functions Metadata](engine/metadata/index.md)
|
||||
1. [Generate Function Signatures](engine/metadata/gen_fn_sig.md)
|
||||
2. [Export Metadata to JSON](engine/metadata/export_to_json.md)
|
||||
10. [External Tools](tools/index.md)
|
||||
1. [Online Playground](tools/playground.md)
|
||||
2. [`rhai-doc`](tools/rhai-doc.md)
|
||||
11. [Appendix](appendix/index.md)
|
||||
1. [Keywords](appendix/keywords.md)
|
||||
2. [Operators and Symbols](appendix/operators.md)
|
||||
3. [Literals](appendix/literals.md)
|
||||
|
@ -15,13 +15,19 @@ Online Resources for Rhai
|
||||
|
||||
* [`LIB.RS`](https://lib.rs/crates/rhai) - Rhai library info
|
||||
|
||||
* [Online Playground][playground] - Run scripts directly from editor
|
||||
|
||||
* [Discord Chat](https://discord.gg/HquqbYFcZ9) - Rhai channel
|
||||
|
||||
* [Reddit](https://www.reddit.com/r/Rhai) - Rhai community
|
||||
|
||||
|
||||
External Tools
|
||||
--------------
|
||||
|
||||
* [Online Playground][playground] - Run Rhai scripts directly from an editor in the browser
|
||||
|
||||
* [`rhai-doc`] - Rhai script documentation tool
|
||||
|
||||
|
||||
Other Cool Projects
|
||||
-------------------
|
||||
|
||||
|
@ -41,8 +41,8 @@ let mut scope = Scope::new();
|
||||
// If arguments of the wrong types are passed, the Engine will not find the function.
|
||||
|
||||
let result: i64 = engine.call_fn(&mut scope, &ast, "hello", ( String::from("abc"), 123_i64 ) )?;
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
// put arguments in a tuple
|
||||
// ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
// return type must be specified put arguments in a tuple
|
||||
|
||||
let result: i64 = engine.call_fn(&mut scope, &ast, "hello", (123_i64,) )?;
|
||||
// ^^^^^^^^^^ tuple of one
|
||||
|
@ -6,7 +6,8 @@ Custom Operators
|
||||
For use as a DSL (Domain-Specific Languages), it is sometimes more convenient to augment Rhai with
|
||||
customized operators performing specific logic.
|
||||
|
||||
`Engine::register_custom_operator` registers a keyword as a custom operator.
|
||||
`Engine::register_custom_operator` registers a keyword as a custom operator, giving it a particular
|
||||
_precedence_ (which cannot be zero).
|
||||
|
||||
|
||||
Example
|
||||
@ -52,15 +53,23 @@ into a syntax that uses the corresponding function calls.
|
||||
Using `Engine::register_custom_operator` merely enables a convenient shortcut.
|
||||
|
||||
|
||||
Must Follow Variable Naming
|
||||
--------------------------
|
||||
Must be a Valid Identifier or Reserved Symbol
|
||||
--------------------------------------------
|
||||
|
||||
All custom operators must be _identifiers_ that follow the same naming rules as [variables].
|
||||
|
||||
Alternatively, they can also be [reserved symbols]({{rootUrl}}/appendix/operators.md#symbols),
|
||||
[disabled operators or keywords][disable keywords and operators].
|
||||
|
||||
```rust
|
||||
engine.register_custom_operator("foo", 20); // 'foo' is a valid custom operator
|
||||
|
||||
engine.register_custom_operator("=>", 30); // <- error: '=>' is not a valid custom operator
|
||||
engine.register_custom_operator("#", 20); // the reserved symbol '#' is also
|
||||
// a valid custom operator
|
||||
|
||||
engine.register_custom_operator("+", 30); // <- error: '+' is an active operator
|
||||
|
||||
engine.register_custom_operator("=>", 30); // <- error: '=>' is an active symbol
|
||||
```
|
||||
|
||||
|
||||
|
@ -7,22 +7,24 @@ Create a Module from an AST
|
||||
`Module::eval_ast_as_new`
|
||||
------------------------
|
||||
|
||||
A _module_ can be created from a single script (or pre-compiled [`AST`]) containing global variables,
|
||||
A [module] can be created from a single script (or pre-compiled [`AST`]) containing global variables,
|
||||
functions and sub-modules via the `Module::eval_ast_as_new` method.
|
||||
|
||||
See the section on [_Exporting Variables, Functions and Sub-Modules_][`export`] for details on how to prepare
|
||||
a Rhai script for this purpose as well as to control which functions/variables to export.
|
||||
See the section on [_Exporting Variables, Functions and Sub-Modules_][`export`] for details on how to
|
||||
prepare a Rhai script for this purpose as well as to control which functions/variables to export.
|
||||
|
||||
When given an [`AST`], it is first evaluated, then the following items are exposed as members of the new module:
|
||||
When given an [`AST`], it is first evaluated, then the following items are exposed as members of the
|
||||
new [module]:
|
||||
|
||||
* Global variables - all variables exported via the `export` statement (those not exported remain hidden).
|
||||
|
||||
* Functions not specifically marked `private`.
|
||||
|
||||
* Global modules that remain in the [`Scope`] at the end of a script run.
|
||||
* Global modules that remain in the [`Scope`] at the end of a script run (become sub-modules).
|
||||
|
||||
`Module::eval_ast_as_new` encapsulates the entire `AST` into each function call, merging the module namespace
|
||||
with the global namespace. Therefore, functions defined within the same module script can cross-call each other.
|
||||
`Module::eval_ast_as_new` encapsulates the entire `AST` into each function call, merging the
|
||||
module namespace with the global namespace. Therefore, functions defined within the same module
|
||||
script can cross-call each other.
|
||||
|
||||
|
||||
Examples
|
||||
|
@ -25,7 +25,6 @@ Use Case 1 - Make the `Module` Globally Available
|
||||
`Engine::register_global_module` registers a shared [module] into the _global_ namespace.
|
||||
|
||||
All [functions] and [type iterators] can be accessed without _namespace qualifiers_.
|
||||
|
||||
Variables and sub-modules are **ignored**.
|
||||
|
||||
This is by far the easiest way to expose a module's functionalities to Rhai.
|
||||
@ -35,11 +34,11 @@ use rhai::{Engine, Module};
|
||||
|
||||
let mut module = Module::new(); // new module
|
||||
|
||||
// Use the 'set_fn_XXX' API to add functions.
|
||||
let hash = module.set_fn_1("inc", |x: i64| Ok(x+1));
|
||||
// Use the 'Module::set_fn_XXX' API to add functions.
|
||||
let hash = module.set_fn_1("inc", |x: i64| Ok(x + 1));
|
||||
|
||||
// Remember to update the parameter names/types and return type metadata.
|
||||
// 'set_fn_XXX' by default does not set function metadata.
|
||||
// 'Module::set_fn_XXX' by default does not set function metadata.
|
||||
module.update_fn_metadata(hash, ["x: i64", "i64"]);
|
||||
|
||||
// Register the module into the global namespace of the Engine.
|
||||
@ -49,6 +48,19 @@ engine.register_global_module(module.into());
|
||||
engine.eval::<i64>("inc(41)")? == 42; // no need to import module
|
||||
```
|
||||
|
||||
Registering a [module] via `Engine::register_global_module` is essentially the _same_
|
||||
as calling `Engine::register_fn` (or any of the `Engine::register_XXX` API) individually
|
||||
on each top-level function within that [module]. In fact, the actual implementation of
|
||||
`Engine::register_fn` etc. simply adds the function to an internal [module]!
|
||||
|
||||
```rust
|
||||
// The above is essentially the same as:
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.register_fn("inc", |x: i64| x + 1);
|
||||
|
||||
engine.eval::<i64>("inc(41)")? == 42; // no need to import module
|
||||
```
|
||||
|
||||
Use Case 2 - Make the `Module` a Static Module
|
||||
---------------------------------------------
|
||||
@ -60,25 +72,27 @@ use rhai::{Engine, Module};
|
||||
|
||||
let mut module = Module::new(); // new module
|
||||
|
||||
// Use the 'set_fn_XXX' API to add functions.
|
||||
let hash = module.set_fn_1("inc", |x: i64| Ok(x+1));
|
||||
// Use the 'Module::set_fn_XXX' API to add functions.
|
||||
let hash = module.set_fn_1("inc", |x: i64| Ok(x + 1));
|
||||
|
||||
// Remember to update the parameter names/types and return type metadata.
|
||||
// 'set_fn_XXX' by default does not set function metadata.
|
||||
// 'Module::set_fn_XXX' by default does not set function metadata.
|
||||
module.update_fn_metadata(hash, ["x: i64", "i64"]);
|
||||
|
||||
// Register the module into the Engine as a static module namespace 'calc'
|
||||
// Register the module into the Engine as the static module namespace path
|
||||
// 'services::calc'
|
||||
let mut engine = Engine::new();
|
||||
engine.register_static_module("calc", module.into());
|
||||
engine.register_static_module("services::calc", module.into());
|
||||
|
||||
engine.eval::<i64>("calc::inc(41)")? == 42; // refer to the 'Calc' module
|
||||
// refer to the 'services::calc' module
|
||||
engine.eval::<i64>("services::calc::inc(41)")? == 42;
|
||||
```
|
||||
|
||||
### Expose Functions to the Global Namespace
|
||||
|
||||
`Module::set_fn_mut` and `Module::set_fn_XXX_mut` can optionally expose functions (usually _methods_)
|
||||
in the module to the _global_ namespace, so [getters/setters] and [indexers] for [custom types]
|
||||
can work as expected.
|
||||
The `Module::set_fn_XXX_mut` API methods can optionally expose functions in the [module]
|
||||
to the _global_ namespace by setting the `namespace` parameter to `FnNamespace::Global`,
|
||||
so [getters/setters] and [indexers] for [custom types] can work as expected.
|
||||
|
||||
[Type iterators], because of their special nature, are _always_ exposed to the _global_ namespace.
|
||||
|
||||
@ -87,19 +101,24 @@ use rhai::{Engine, Module, FnNamespace};
|
||||
|
||||
let mut module = Module::new(); // new module
|
||||
|
||||
// Expose method 'inc' to the global namespace (default is 'Internal')
|
||||
let hash = module.set_fn_1_mut("inc", FnNamespace::Global, |x: &mut i64| Ok(x+1));
|
||||
// Expose method 'inc' to the global namespace (default is 'FnNamespace::Internal')
|
||||
let hash = module.set_fn_1_mut("inc", FnNamespace::Global, |x: &mut i64| Ok(x + 1));
|
||||
|
||||
// Remember to update the parameter names/types and return type metadata.
|
||||
// 'set_fn_XXX' by default does not set function metadata.
|
||||
// 'Module::set_fn_XXX_mut' by default does not set function metadata.
|
||||
module.update_fn_metadata(hash, ["x: &mut i64", "i64"]);
|
||||
|
||||
// Register the module into the Engine as a static module namespace 'calc'
|
||||
let mut engine = Engine::new();
|
||||
engine.register_static_module("calc", module.into());
|
||||
|
||||
// The method 'inc' works as expected because it is exposed to the global namespace
|
||||
// 'inc' works when qualified by the namespace
|
||||
engine.eval::<i64>("calc::inc(41)")? == 42;
|
||||
|
||||
// 'inc' also works without a namespace qualifier
|
||||
// because it is exposed to the global namespace
|
||||
engine.eval::<i64>("let x = 41; x.inc()")? == 42;
|
||||
engine.eval::<i64>("let x = 41; inc(x)")? == 42;
|
||||
```
|
||||
|
||||
|
||||
@ -118,7 +137,7 @@ use rhai::module_resolvers::StaticModuleResolver;
|
||||
|
||||
let mut module = Module::new(); // new module
|
||||
module.set_var("answer", 41_i64); // variable 'answer' under module
|
||||
module.set_fn_1("inc", |x: i64| Ok(x+1)); // use the 'set_fn_XXX' API to add functions
|
||||
module.set_fn_1("inc", |x: i64| Ok(x + 1)); // use the 'set_fn_XXX' API to add functions
|
||||
|
||||
// Create the module resolver
|
||||
let mut resolver = StaticModuleResolver::new();
|
||||
@ -129,7 +148,7 @@ resolver.insert("question", module);
|
||||
|
||||
// Set the module resolver into the 'Engine'
|
||||
let mut engine = Engine::new();
|
||||
engine.set_module_resolver(Some(resolver));
|
||||
engine.set_module_resolver(resolver);
|
||||
|
||||
// Use namespace-qualified variables
|
||||
engine.eval::<i64>(r#"import "question" as q; q::answer + 1"#)? == 42;
|
||||
|
@ -62,7 +62,7 @@ impl ModuleResolver for MyModuleResolver {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Set the custom module resolver into the 'Engine'.
|
||||
engine.set_module_resolver(Some(MyModuleResolver {}));
|
||||
engine.set_module_resolver(MyModuleResolver {});
|
||||
|
||||
engine.consume(r#"
|
||||
import "hello" as foo; // this 'import' statement will call
|
||||
|
@ -153,13 +153,19 @@ A collection of module resolvers. Modules will be resolved from each resolver in
|
||||
This is useful when multiple types of modules are needed simultaneously.
|
||||
|
||||
|
||||
`DummyResolversCollection`
|
||||
-------------------------
|
||||
|
||||
This module resolver acts as a _dummy_ and always fails all module resolution calls.
|
||||
|
||||
|
||||
Set into `Engine`
|
||||
-----------------
|
||||
|
||||
An [`Engine`]'s module resolver is set via a call to `Engine::set_module_resolver`:
|
||||
|
||||
```rust
|
||||
use rhai::module_resolvers::StaticModuleResolver;
|
||||
use rhai::module_resolvers::{DummyModuleResolver, StaticModuleResolver};
|
||||
|
||||
// Create a module resolver
|
||||
let resolver = StaticModuleResolver::new();
|
||||
@ -167,8 +173,9 @@ let resolver = StaticModuleResolver::new();
|
||||
// Register functions into 'resolver'...
|
||||
|
||||
// Use the module resolver
|
||||
engine.set_module_resolver(Some(resolver));
|
||||
engine.set_module_resolver(resolver);
|
||||
|
||||
// Effectively disable 'import' statements by setting module resolver to 'None'
|
||||
engine.set_module_resolver(None);
|
||||
// Effectively disable 'import' statements by setting module resolver to
|
||||
// the 'DummyModuleResolver' which acts as... well... a dummy.
|
||||
engine.set_module_resolver(DummyModuleResolver::new());
|
||||
```
|
||||
|
@ -3,18 +3,19 @@ Override a Built-in Function
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
Any similarly-named function defined in a script overrides any built-in or registered
|
||||
Any similarly-named function defined in a script _overrides_ any built-in or registered
|
||||
native Rust function of the same name and number of parameters.
|
||||
|
||||
```rust
|
||||
// Override the built-in function 'to_int'
|
||||
fn to_int(num) {
|
||||
print("Ha! Gotcha! " + num);
|
||||
// Override the built-in function 'to_float' when called as a method
|
||||
fn to_float() {
|
||||
print("Ha! Gotcha! " + this);
|
||||
42.0
|
||||
}
|
||||
|
||||
let x = (123).to_int();
|
||||
let x = 123.to_float();
|
||||
|
||||
print(x); // what happens?
|
||||
print(x); // what happens?
|
||||
```
|
||||
|
||||
A registered native Rust function, in turn, overrides any built-in function of the
|
||||
|
@ -11,13 +11,13 @@ A number of examples can be found in the `examples` directory:
|
||||
| [`custom_types_and_methods`]({{repoTree}}/examples/custom_types_and_methods.rs) | shows how to register a custom Rust type and methods for it |
|
||||
| [`hello`]({{repoTree}}/examples/hello.rs) | simple example that evaluates an expression and prints the result |
|
||||
| [`reuse_scope`]({{repoTree}}/examples/reuse_scope.rs) | evaluates two pieces of code in separate runs, but using a common [`Scope`] |
|
||||
| [`rhai_runner`]({{repoTree}}/examples/rhai_runner.rs) | runs each filename passed to it as a Rhai script |
|
||||
| [`rhai-repl`]({{repoTree}}/examples/rhai-repl.rs) | a simple REPL, interactively evaluate statements from stdin |
|
||||
| [`rhai-run`]({{repoTree}}/examples/rhai-run.rs) | runs each filename passed to it as a Rhai script |
|
||||
| [`serde`]({{repoTree}}/examples/serde.rs) | example to serialize and deserialize Rust types with [`serde`](https://crates.io/crates/serde).<br/>The [`serde`] feature is required to run |
|
||||
| [`simple_fn`]({{repoTree}}/examples/simple_fn.rs) | shows how to register a simple function |
|
||||
| [`strings`]({{repoTree}}/examples/strings.rs) | shows different ways to register functions taking string arguments |
|
||||
| [`repl`]({{repoTree}}/examples/repl.rs) | a simple REPL, interactively evaluate statements from stdin |
|
||||
|
||||
The `repl` example is a particularly good one as it allows one to interactively try out Rhai's
|
||||
The `rhai-repl` example is a particularly good one as it allows one to interactively try out Rhai's
|
||||
language features in a standard REPL (**R**ead-**E**val-**P**rint **L**oop).
|
||||
|
||||
|
||||
|
@ -46,8 +46,8 @@ The following scripts are for benchmarking the speed of Rhai:
|
||||
Running Example Scripts
|
||||
----------------------
|
||||
|
||||
The [`rhai_runner`](../examples/rust.md) example can be used to run the scripts:
|
||||
The [`rhai-run`](../examples/rust.md) example can be used to run the scripts:
|
||||
|
||||
```bash
|
||||
cargo run --example rhai_runner scripts/any_script.rhai
|
||||
cargo run --example rhai-run scripts/any_script.rhai
|
||||
```
|
||||
|
6
doc/src/tools/index.md
Normal file
6
doc/src/tools/index.md
Normal file
@ -0,0 +1,6 @@
|
||||
External Tools
|
||||
==============
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
External tools available to work with Rhai.
|
15
doc/src/tools/playground.md
Normal file
15
doc/src/tools/playground.md
Normal file
@ -0,0 +1,15 @@
|
||||
Online Playground
|
||||
=================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
|
||||
The Online Playground runs off a [WASM] build of Rhai and allows evaluating
|
||||
Rhai scripts directly within a browser editor window.
|
||||
|
||||
|
||||
Author : [`@alvinhochun`](https://github.com/alvinhochun)
|
||||
|
||||
Repo : [On GitHub](https://github.com/alvinhochun/rhai-playground)
|
||||
|
||||
URL : [Link to Online Playground][playground]
|
6
doc/src/tools/rhai-doc.md
Normal file
6
doc/src/tools/rhai-doc.md
Normal file
@ -0,0 +1,6 @@
|
||||
Rhai Script Documentation Tool
|
||||
=============================
|
||||
|
||||
{{#include ../links.md}}
|
||||
|
||||
<< TODO >>
|
@ -1,38 +1,40 @@
|
||||
use rhai::{Engine, RegisterFn, INT};
|
||||
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
struct TestStruct {
|
||||
x: INT,
|
||||
}
|
||||
|
||||
impl TestStruct {
|
||||
fn update(&mut self) {
|
||||
pub fn update(&mut self) {
|
||||
self.x += 1000;
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self { x: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
fn main() {
|
||||
fn main() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine
|
||||
.register_type::<TestStruct>()
|
||||
.register_fn("update", TestStruct::update)
|
||||
.register_fn("new_ts", TestStruct::new);
|
||||
.register_fn("new_ts", TestStruct::new)
|
||||
.register_fn("update", TestStruct::update);
|
||||
|
||||
println!(
|
||||
"{:?}",
|
||||
engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")
|
||||
engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?
|
||||
);
|
||||
println!(
|
||||
"{:?}",
|
||||
engine.eval::<TestStruct>("let x = [new_ts()]; x[0].update(); x[0]")
|
||||
engine.eval::<TestStruct>("let x = [new_ts()]; x[0].update(); x[0]")?
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "no_index", feature = "no_object"))]
|
||||
|
@ -1,16 +1,16 @@
|
||||
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Debug, Clone)]
|
||||
struct TestStruct {
|
||||
x: INT,
|
||||
}
|
||||
|
||||
impl TestStruct {
|
||||
fn update(&mut self) {
|
||||
pub fn update(&mut self) {
|
||||
self.x += 1000;
|
||||
}
|
||||
|
||||
fn new() -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self { x: 1 }
|
||||
}
|
||||
}
|
||||
@ -21,8 +21,8 @@ fn main() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
engine
|
||||
.register_type::<TestStruct>()
|
||||
.register_fn("update", TestStruct::update)
|
||||
.register_fn("new_ts", TestStruct::new);
|
||||
.register_fn("new_ts", TestStruct::new)
|
||||
.register_fn("update", TestStruct::update);
|
||||
|
||||
let result = engine.eval::<TestStruct>("let x = new_ts(); x.update(); x")?;
|
||||
|
||||
|
@ -43,7 +43,7 @@ fn print_help() {
|
||||
println!("quit, exit => quit");
|
||||
println!("scope => print all variables in the scope");
|
||||
println!("functions => print all functions defined");
|
||||
println!("ast => print the last AST");
|
||||
println!("ast => print the last AST (optimized)");
|
||||
println!("astu => print the last raw, un-optimized AST");
|
||||
println!(r"end a line with '\' to continue to the next line.");
|
||||
println!();
|
||||
@ -67,7 +67,7 @@ fn main() {
|
||||
print_help();
|
||||
|
||||
'main_loop: loop {
|
||||
print!("rhai> ");
|
||||
print!("rhai-repl> ");
|
||||
stdout().flush().expect("couldn't flush stdout");
|
||||
|
||||
input.clear();
|
||||
@ -126,12 +126,12 @@ fn main() {
|
||||
}
|
||||
"astu" => {
|
||||
// print the last un-optimized AST
|
||||
println!("{:#?}\n", &ast_u);
|
||||
println!("{:#?}\n", ast_u);
|
||||
continue;
|
||||
}
|
||||
"ast" => {
|
||||
// print the last AST
|
||||
println!("{:#?}\n", &ast);
|
||||
println!("{:#?}\n", ast);
|
||||
continue;
|
||||
}
|
||||
"functions" => {
|
@ -17,13 +17,13 @@ mod example {
|
||||
use rhai::{Dynamic, Engine, Map};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
struct Point {
|
||||
x: f64,
|
||||
y: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
struct MyStruct {
|
||||
a: i64,
|
||||
b: Vec<String>,
|
||||
@ -71,6 +71,18 @@ mod example {
|
||||
// Convert the 'Dynamic' object map into 'MyStruct'
|
||||
let x: MyStruct = from_dynamic(&result).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
x,
|
||||
MyStruct {
|
||||
a: 42,
|
||||
b: vec!["hello".into(), "world".into()],
|
||||
c: true,
|
||||
d: Point {
|
||||
x: 123.456,
|
||||
y: 999.0,
|
||||
},
|
||||
}
|
||||
);
|
||||
println!("Deserialized to struct: {:#?}", x);
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ fn main() -> Result<(), Box<EvalAltResult>> {
|
||||
display("Trimmed", x);
|
||||
display("Trimmed Length", x.len());
|
||||
display("Index of \"!!!\"", x.index_of("!!!"));
|
||||
"#,
|
||||
"#,
|
||||
)?;
|
||||
|
||||
println!();
|
||||
|
@ -7,14 +7,14 @@ Testing scripts written in Rhai.
|
||||
How to Run
|
||||
----------
|
||||
|
||||
Compile the `rhai_runner` example:
|
||||
Compile the `rhai-run` example:
|
||||
|
||||
```bash
|
||||
cargo build --example rhai_runner
|
||||
cargo build --example rhai-run
|
||||
```
|
||||
|
||||
Run it:
|
||||
|
||||
```bash
|
||||
./target/debug/examples/rhai_runner ./scripts/test_script_to_run.rhai
|
||||
./target/debug/examples/rhai-run ./scripts/test_script_to_run.rhai
|
||||
```
|
||||
|
56
src/ast.rs
56
src/ast.rs
@ -9,7 +9,7 @@ use crate::stdlib::{
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
hash::Hash,
|
||||
num::NonZeroUsize,
|
||||
num::{NonZeroU64, NonZeroUsize},
|
||||
ops::{Add, AddAssign},
|
||||
string::String,
|
||||
vec,
|
||||
@ -736,7 +736,7 @@ impl Stmt {
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
/// Get the [position][`Position`] of this statement.
|
||||
/// Get the [position][Position] of this statement.
|
||||
pub fn position(&self) -> Position {
|
||||
match self {
|
||||
Self::Noop(pos)
|
||||
@ -765,7 +765,7 @@ impl Stmt {
|
||||
Self::Share(x) => x.pos,
|
||||
}
|
||||
}
|
||||
/// Override the [position][`Position`] of this statement.
|
||||
/// Override the [position][Position] of this statement.
|
||||
pub fn set_position(&mut self, new_pos: Position) -> &mut Self {
|
||||
match self {
|
||||
Self::Noop(pos)
|
||||
@ -914,16 +914,14 @@ pub struct BinaryExpr {
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FnCallExpr {
|
||||
/// Pre-calculated hash for a script-defined function of the same name and number of parameters.
|
||||
pub hash: u64,
|
||||
/// Call native functions only? Set to [`true`] to skip searching for script-defined function overrides
|
||||
/// when it is certain that the function must be native (e.g. an operator).
|
||||
pub native_only: bool,
|
||||
/// None if native Rust only.
|
||||
pub hash_script: Option<NonZeroU64>,
|
||||
/// Does this function call capture the parent scope?
|
||||
pub capture: bool,
|
||||
/// Default value when the function is not found, mostly used to provide a default for comparison functions.
|
||||
pub def_value: Option<Dynamic>,
|
||||
/// Namespace of the function, if any. Boxed because it occurs rarely.
|
||||
pub namespace: Option<Box<NamespaceRef>>,
|
||||
pub namespace: Option<NamespaceRef>,
|
||||
/// Function name.
|
||||
/// Use [`Cow<'static, str>`][Cow] because a lot of operators (e.g. `==`, `>=`) are implemented as
|
||||
/// function calls and the function names are predictable, so no need to allocate a new [`String`].
|
||||
@ -963,13 +961,19 @@ pub enum Expr {
|
||||
Map(Box<StaticVec<(Ident, Expr)>>, Position),
|
||||
/// ()
|
||||
Unit(Position),
|
||||
/// Variable access - (optional index, optional modules, hash, variable name)
|
||||
Variable(Box<(Option<NonZeroUsize>, Option<Box<NamespaceRef>>, u64, Ident)>),
|
||||
/// Variable access - (optional index, optional (hash, modules), variable name)
|
||||
Variable(
|
||||
Box<(
|
||||
Option<NonZeroUsize>,
|
||||
Option<(NonZeroU64, NamespaceRef)>,
|
||||
Ident,
|
||||
)>,
|
||||
),
|
||||
/// Property access - (getter, setter), prop
|
||||
Property(Box<((ImmutableString, ImmutableString), Ident)>),
|
||||
Property(Box<(ImmutableString, ImmutableString, Ident)>),
|
||||
/// { [statement][Stmt] }
|
||||
Stmt(Box<StaticVec<Stmt>>, Position),
|
||||
/// Wrapped [expression][`Expr`] - should not be optimized away.
|
||||
/// Wrapped [expression][Expr] - should not be optimized away.
|
||||
Expr(Box<Expr>),
|
||||
/// func `(` expr `,` ... `)`
|
||||
FnCall(Box<FnCallExpr>, Position),
|
||||
@ -1044,11 +1048,11 @@ impl Expr {
|
||||
/// Is the expression a simple variable access?
|
||||
pub(crate) fn get_variable_access(&self, non_qualified: bool) -> Option<&str> {
|
||||
match self {
|
||||
Self::Variable(x) if !non_qualified || x.1.is_none() => Some((x.3).name.as_str()),
|
||||
Self::Variable(x) if !non_qualified || x.1.is_none() => Some((x.2).name.as_str()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
/// Get the [position][`Position`] of the expression.
|
||||
/// Get the [position][Position] of the expression.
|
||||
pub fn position(&self) -> Position {
|
||||
match self {
|
||||
Self::Expr(x) => x.position(),
|
||||
@ -1064,9 +1068,9 @@ impl Expr {
|
||||
Self::FnPointer(_, pos) => *pos,
|
||||
Self::Array(_, pos) => *pos,
|
||||
Self::Map(_, pos) => *pos,
|
||||
Self::Property(x) => (x.1).pos,
|
||||
Self::Property(x) => (x.2).pos,
|
||||
Self::Stmt(_, pos) => *pos,
|
||||
Self::Variable(x) => (x.3).pos,
|
||||
Self::Variable(x) => (x.2).pos,
|
||||
Self::FnCall(_, pos) => *pos,
|
||||
|
||||
Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => x.lhs.position(),
|
||||
@ -1078,7 +1082,7 @@ impl Expr {
|
||||
Self::Custom(_, pos) => *pos,
|
||||
}
|
||||
}
|
||||
/// Override the [position][`Position`] of the expression.
|
||||
/// Override the [position][Position] of the expression.
|
||||
pub fn set_position(&mut self, new_pos: Position) -> &mut Self {
|
||||
match self {
|
||||
Self::Expr(x) => {
|
||||
@ -1096,8 +1100,8 @@ impl Expr {
|
||||
Self::FnPointer(_, pos) => *pos = new_pos,
|
||||
Self::Array(_, pos) => *pos = new_pos,
|
||||
Self::Map(_, pos) => *pos = new_pos,
|
||||
Self::Variable(x) => (x.3).pos = new_pos,
|
||||
Self::Property(x) => (x.1).pos = new_pos,
|
||||
Self::Variable(x) => (x.2).pos = new_pos,
|
||||
Self::Property(x) => (x.2).pos = new_pos,
|
||||
Self::Stmt(_, pos) => *pos = new_pos,
|
||||
Self::FnCall(_, pos) => *pos = new_pos,
|
||||
Self::And(_, pos) | Self::Or(_, pos) | Self::In(_, pos) => *pos = new_pos,
|
||||
@ -1172,6 +1176,12 @@ impl Expr {
|
||||
}
|
||||
/// Is a particular [token][Token] allowed as a postfix operator to this expression?
|
||||
pub fn is_valid_postfix(&self, token: &Token) -> bool {
|
||||
match token {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Token::Period => return true,
|
||||
_ => (),
|
||||
}
|
||||
|
||||
match self {
|
||||
Self::Expr(x) => x.is_valid_postfix(token),
|
||||
|
||||
@ -1189,24 +1199,20 @@ impl Expr {
|
||||
| Self::Unit(_) => false,
|
||||
|
||||
Self::StringConstant(_, _)
|
||||
| Self::Stmt(_, _)
|
||||
| Self::FnCall(_, _)
|
||||
| Self::Stmt(_, _)
|
||||
| Self::Dot(_, _)
|
||||
| Self::Index(_, _)
|
||||
| Self::Array(_, _)
|
||||
| Self::Map(_, _) => match token {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Token::LeftBracket => true,
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Token::Period => true,
|
||||
_ => false,
|
||||
},
|
||||
|
||||
Self::Variable(_) => match token {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Token::LeftBracket => true,
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Token::Period => true,
|
||||
Token::LeftParen => true,
|
||||
Token::Bang => true,
|
||||
Token::DoubleColon => true,
|
||||
@ -1216,8 +1222,6 @@ impl Expr {
|
||||
Self::Property(_) => match token {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Token::LeftBracket => true,
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Token::Period => true,
|
||||
Token::LeftParen => true,
|
||||
_ => false,
|
||||
},
|
||||
|
@ -748,13 +748,16 @@ impl Dynamic {
|
||||
|
||||
Self(Union::Variant(Box::new(boxed), AccessMode::ReadWrite))
|
||||
}
|
||||
/// Turn the [`Dynamic`] value into a shared [`Dynamic`] value backed by an [`Rc`][std::rc::Rc]`<`[`RefCell`][std::cell::RefCell]`<`[`Dynamic`]`>>`
|
||||
/// or [`Arc`][std::sync::Arc]`<`[`RwLock`][std::sync::RwLock]`<`[`Dynamic`]`>>` depending on the `sync` feature.
|
||||
/// Turn the [`Dynamic`] value into a shared [`Dynamic`] value backed by an
|
||||
/// [`Rc`][std::rc::Rc]`<`[`RefCell`][std::cell::RefCell]`<`[`Dynamic`]`>>` or
|
||||
/// [`Arc`][std::sync::Arc]`<`[`RwLock`][std::sync::RwLock]`<`[`Dynamic`]`>>`
|
||||
/// depending on the `sync` feature.
|
||||
///
|
||||
/// Shared [`Dynamic`] values are relatively cheap to clone as they simply increment the
|
||||
/// reference counts.
|
||||
///
|
||||
/// Shared [`Dynamic`] values can be converted seamlessly to and from ordinary [`Dynamic`] values.
|
||||
/// Shared [`Dynamic`] values can be converted seamlessly to and from ordinary [`Dynamic`]
|
||||
/// values.
|
||||
///
|
||||
/// If the [`Dynamic`] value is already shared, this method returns itself.
|
||||
///
|
||||
@ -970,8 +973,8 @@ impl Dynamic {
|
||||
///
|
||||
/// If the [`Dynamic`] is not a shared value, it returns itself.
|
||||
///
|
||||
/// If the [`Dynamic`] is a shared value, it returns the shared value if there are
|
||||
/// no outstanding references, or a cloned copy.
|
||||
/// If the [`Dynamic`] is a shared value, it returns the shared value if there are no
|
||||
/// outstanding references, or a cloned copy.
|
||||
#[inline(always)]
|
||||
pub fn flatten(self) -> Self {
|
||||
match self.0 {
|
||||
|
223
src/engine.rs
223
src/engine.rs
@ -18,8 +18,8 @@ use crate::stdlib::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt, format,
|
||||
hash::{Hash, Hasher},
|
||||
iter::{empty, once},
|
||||
num::NonZeroUsize,
|
||||
iter::{empty, once, FromIterator},
|
||||
num::{NonZeroU64, NonZeroU8, NonZeroUsize},
|
||||
ops::DerefMut,
|
||||
string::{String, ToString},
|
||||
};
|
||||
@ -112,23 +112,15 @@ impl Imports {
|
||||
}
|
||||
/// Does the specified function hash key exist in this stack of imported [modules][Module]?
|
||||
#[allow(dead_code)]
|
||||
pub fn contains_fn(&self, hash: u64) -> bool {
|
||||
if hash == 0 {
|
||||
false
|
||||
} else {
|
||||
self.0.iter().any(|(_, m)| m.contains_qualified_fn(hash))
|
||||
}
|
||||
pub fn contains_fn(&self, hash: NonZeroU64) -> bool {
|
||||
self.0.iter().any(|(_, m)| m.contains_qualified_fn(hash))
|
||||
}
|
||||
/// Get specified function via its hash key.
|
||||
pub fn get_fn(&self, hash: u64) -> Option<&CallableFunction> {
|
||||
if hash == 0 {
|
||||
None
|
||||
} else {
|
||||
self.0
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|(_, m)| m.get_qualified_fn(hash))
|
||||
}
|
||||
pub fn get_fn(&self, hash: NonZeroU64) -> Option<&CallableFunction> {
|
||||
self.0
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|(_, m)| m.get_qualified_fn(hash))
|
||||
}
|
||||
/// Does the specified [`TypeId`][std::any::TypeId] iterator exist in this stack of imported [modules][Module]?
|
||||
#[allow(dead_code)]
|
||||
@ -144,6 +136,22 @@ impl Imports {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: IntoIterator<Item = (&'a ImmutableString, &'a Shared<Module>)>> From<T> for Imports {
|
||||
fn from(value: T) -> Self {
|
||||
Self(
|
||||
value
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl FromIterator<(ImmutableString, Shared<Module>)> for Imports {
|
||||
fn from_iter<T: IntoIterator<Item = (ImmutableString, Shared<Module>)>>(iter: T) -> Self {
|
||||
Self(iter.into_iter().collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
#[cfg(debug_assertions)]
|
||||
pub const MAX_CALL_STACK_DEPTH: usize = 8;
|
||||
@ -478,7 +486,7 @@ pub struct State {
|
||||
/// Number of modules loaded.
|
||||
pub modules: usize,
|
||||
/// Cached lookup values for function hashes.
|
||||
pub functions_cache: HashMap<u64, Option<CallableFunction>, StraightHasherBuilder>,
|
||||
pub functions_cache: HashMap<NonZeroU64, Option<CallableFunction>, StraightHasherBuilder>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
@ -616,11 +624,11 @@ pub struct Engine {
|
||||
/// A collection of all modules loaded into the global namespace of the Engine.
|
||||
pub(crate) global_modules: StaticVec<Shared<Module>>,
|
||||
/// A collection of all sub-modules directly loaded into the Engine.
|
||||
pub(crate) global_sub_modules: Imports,
|
||||
pub(crate) global_sub_modules: HashMap<ImmutableString, Shared<Module>>,
|
||||
|
||||
/// A module resolution service.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub(crate) module_resolver: Option<Box<dyn crate::ModuleResolver>>,
|
||||
pub(crate) module_resolver: Box<dyn crate::ModuleResolver>,
|
||||
|
||||
/// A hashmap mapping type names to pretty-print names.
|
||||
pub(crate) type_names: HashMap<String, String>,
|
||||
@ -628,7 +636,7 @@ pub struct Engine {
|
||||
/// A hashset containing symbols to disable.
|
||||
pub(crate) disabled_symbols: HashSet<String>,
|
||||
/// A hashmap containing custom keywords and precedence to recognize.
|
||||
pub(crate) custom_keywords: HashMap<String, Option<u8>>,
|
||||
pub(crate) custom_keywords: HashMap<String, Option<NonZeroU8>>,
|
||||
/// Custom syntax.
|
||||
pub(crate) custom_syntax: HashMap<ImmutableString, CustomSyntax>,
|
||||
/// Callback closure for resolving variable access.
|
||||
@ -752,7 +760,7 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
module_resolver: Some(Box::new(crate::module::resolvers::FileModuleResolver::new())),
|
||||
module_resolver: Box::new(crate::module::resolvers::FileModuleResolver::new()),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(any(feature = "no_std", target_arch = "wasm32",))]
|
||||
module_resolver: None,
|
||||
@ -815,7 +823,7 @@ impl Engine {
|
||||
global_sub_modules: Default::default(),
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
module_resolver: None,
|
||||
module_resolver: Box::new(crate::module::resolvers::DummyModuleResolver::new()),
|
||||
|
||||
type_names: Default::default(),
|
||||
disabled_symbols: Default::default(),
|
||||
@ -856,19 +864,19 @@ impl Engine {
|
||||
|
||||
/// Search for a variable within the scope or within imports,
|
||||
/// depending on whether the variable name is namespace-qualified.
|
||||
pub(crate) fn search_namespace<'s, 'a>(
|
||||
pub(crate) fn search_namespace<'s>(
|
||||
&self,
|
||||
scope: &'s mut Scope,
|
||||
mods: &mut Imports,
|
||||
state: &mut State,
|
||||
lib: &[&Module],
|
||||
this_ptr: &'s mut Option<&mut Dynamic>,
|
||||
expr: &'a Expr,
|
||||
) -> Result<(Target<'s>, &'a str, Position), Box<EvalAltResult>> {
|
||||
expr: &Expr,
|
||||
) -> Result<(Target<'s>, Position), Box<EvalAltResult>> {
|
||||
match expr {
|
||||
Expr::Variable(v) => match v.as_ref() {
|
||||
// Qualified variable
|
||||
(_, Some(modules), hash_var, Ident { name, pos }) => {
|
||||
(_, Some((hash_var, modules)), Ident { name, pos }) => {
|
||||
let module = search_imports(mods, state, modules)?;
|
||||
let target = module.get_qualified_var(*hash_var).map_err(|mut err| {
|
||||
match *err {
|
||||
@ -883,7 +891,7 @@ impl Engine {
|
||||
// Module variables are constant
|
||||
let mut target = target.clone();
|
||||
target.set_access_mode(AccessMode::ReadOnly);
|
||||
Ok((target.into(), name, *pos))
|
||||
Ok((target.into(), *pos))
|
||||
}
|
||||
// Normal variable access
|
||||
_ => self.search_scope_only(scope, mods, state, lib, this_ptr, expr),
|
||||
@ -893,16 +901,16 @@ impl Engine {
|
||||
}
|
||||
|
||||
/// Search for a variable within the scope
|
||||
pub(crate) fn search_scope_only<'s, 'a>(
|
||||
pub(crate) fn search_scope_only<'s>(
|
||||
&self,
|
||||
scope: &'s mut Scope,
|
||||
mods: &mut Imports,
|
||||
state: &mut State,
|
||||
lib: &[&Module],
|
||||
this_ptr: &'s mut Option<&mut Dynamic>,
|
||||
expr: &'a Expr,
|
||||
) -> Result<(Target<'s>, &'a str, Position), Box<EvalAltResult>> {
|
||||
let (index, _, _, Ident { name, pos }) = match expr {
|
||||
expr: &Expr,
|
||||
) -> Result<(Target<'s>, Position), Box<EvalAltResult>> {
|
||||
let (index, _, Ident { name, pos }) = match expr {
|
||||
Expr::Variable(v) => v.as_ref(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
@ -910,7 +918,7 @@ impl Engine {
|
||||
// Check if the variable is `this`
|
||||
if name.as_str() == KEYWORD_THIS {
|
||||
if let Some(val) = this_ptr {
|
||||
return Ok(((*val).into(), KEYWORD_THIS, *pos));
|
||||
return Ok(((*val).into(), *pos));
|
||||
} else {
|
||||
return EvalAltResult::ErrorUnboundThis(*pos).into();
|
||||
}
|
||||
@ -938,7 +946,7 @@ impl Engine {
|
||||
resolve_var(name, index, &context).map_err(|err| err.fill_position(*pos))?
|
||||
{
|
||||
result.set_access_mode(AccessMode::ReadOnly);
|
||||
return Ok((result.into(), name, *pos));
|
||||
return Ok((result.into(), *pos));
|
||||
}
|
||||
}
|
||||
|
||||
@ -952,7 +960,7 @@ impl Engine {
|
||||
.0
|
||||
};
|
||||
|
||||
let val = scope.get_mut(index);
|
||||
let val = scope.get_mut_by_index(index);
|
||||
|
||||
// Check for data race - probably not necessary because the only place it should conflict is in a method call
|
||||
// when the object variable is also used as a parameter.
|
||||
@ -960,7 +968,7 @@ impl Engine {
|
||||
// return EvalAltResult::ErrorDataRace(name.into(), *pos).into();
|
||||
// }
|
||||
|
||||
Ok((val.into(), name, *pos))
|
||||
Ok((val.into(), *pos))
|
||||
}
|
||||
|
||||
/// Chain-evaluate a dot/index chain.
|
||||
@ -980,7 +988,7 @@ impl Engine {
|
||||
new_val: Option<(Dynamic, Position)>,
|
||||
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
|
||||
if chain_type == ChainType::None {
|
||||
panic!();
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
let is_ref = target.is_ref();
|
||||
@ -1047,7 +1055,7 @@ impl Engine {
|
||||
let args = &mut [target_val, &mut idx_val2, &mut new_val.0];
|
||||
|
||||
self.exec_fn_call(
|
||||
mods, state, lib, FN_IDX_SET, 0, args, is_ref, true, false,
|
||||
mods, state, lib, FN_IDX_SET, None, args, is_ref, true, false,
|
||||
new_val.1, None, None, level,
|
||||
)
|
||||
.map_err(|err| match *err {
|
||||
@ -1083,23 +1091,22 @@ impl Engine {
|
||||
Expr::FnCall(x, pos) if x.namespace.is_none() => {
|
||||
let FnCallExpr {
|
||||
name,
|
||||
native_only: native,
|
||||
hash,
|
||||
hash_script: hash,
|
||||
def_value,
|
||||
..
|
||||
} = x.as_ref();
|
||||
let def_value = def_value.as_ref();
|
||||
let args = idx_val.as_fn_call_args();
|
||||
self.make_method_call(
|
||||
mods, state, lib, name, *hash, target, args, def_value, *native, false,
|
||||
*pos, level,
|
||||
mods, state, lib, name, *hash, target, args, def_value, false, *pos,
|
||||
level,
|
||||
)
|
||||
}
|
||||
// xxx.module::fn_name(...) - syntax error
|
||||
Expr::FnCall(_, _) => unreachable!(),
|
||||
// {xxx:map}.id = ???
|
||||
Expr::Property(x) if target_val.is::<Map>() && new_val.is_some() => {
|
||||
let Ident { name, pos } = &x.1;
|
||||
let Ident { name, pos } = &x.2;
|
||||
let index = name.clone().into();
|
||||
let mut val = self.get_indexed_mut(
|
||||
mods, state, lib, target_val, index, *pos, true, is_ref, false, level,
|
||||
@ -1112,7 +1119,7 @@ impl Engine {
|
||||
}
|
||||
// {xxx:map}.id
|
||||
Expr::Property(x) if target_val.is::<Map>() => {
|
||||
let Ident { name, pos } = &x.1;
|
||||
let Ident { name, pos } = &x.2;
|
||||
let index = name.clone().into();
|
||||
let val = self.get_indexed_mut(
|
||||
mods, state, lib, target_val, index, *pos, false, is_ref, false, level,
|
||||
@ -1122,11 +1129,11 @@ impl Engine {
|
||||
}
|
||||
// xxx.id = ???
|
||||
Expr::Property(x) if new_val.is_some() => {
|
||||
let ((_, setter), Ident { pos, .. }) = x.as_ref();
|
||||
let (_, setter, Ident { pos, .. }) = x.as_ref();
|
||||
let mut new_val = new_val;
|
||||
let mut args = [target_val, &mut new_val.as_mut().unwrap().0];
|
||||
self.exec_fn_call(
|
||||
mods, state, lib, setter, 0, &mut args, is_ref, true, false, *pos,
|
||||
mods, state, lib, setter, None, &mut args, is_ref, true, false, *pos,
|
||||
None, None, level,
|
||||
)
|
||||
.map(|(v, _)| (v, true))
|
||||
@ -1134,10 +1141,10 @@ impl Engine {
|
||||
}
|
||||
// xxx.id
|
||||
Expr::Property(x) => {
|
||||
let ((getter, _), Ident { pos, .. }) = x.as_ref();
|
||||
let (getter, _, Ident { pos, .. }) = x.as_ref();
|
||||
let mut args = [target_val];
|
||||
self.exec_fn_call(
|
||||
mods, state, lib, getter, 0, &mut args, is_ref, true, false, *pos,
|
||||
mods, state, lib, getter, None, &mut args, is_ref, true, false, *pos,
|
||||
None, None, level,
|
||||
)
|
||||
.map(|(v, _)| (v, false))
|
||||
@ -1147,7 +1154,7 @@ impl Engine {
|
||||
Expr::Index(x, x_pos) | Expr::Dot(x, x_pos) if target_val.is::<Map>() => {
|
||||
let mut val = match &x.lhs {
|
||||
Expr::Property(p) => {
|
||||
let Ident { name, pos } = &p.1;
|
||||
let Ident { name, pos } = &p.2;
|
||||
let index = name.clone().into();
|
||||
self.get_indexed_mut(
|
||||
mods, state, lib, target_val, index, *pos, false, is_ref, true,
|
||||
@ -1158,16 +1165,15 @@ impl Engine {
|
||||
Expr::FnCall(x, pos) if x.namespace.is_none() => {
|
||||
let FnCallExpr {
|
||||
name,
|
||||
native_only: native,
|
||||
hash,
|
||||
hash_script: hash,
|
||||
def_value,
|
||||
..
|
||||
} = x.as_ref();
|
||||
let def_value = def_value.as_ref();
|
||||
let args = idx_val.as_fn_call_args();
|
||||
let (val, _) = self.make_method_call(
|
||||
mods, state, lib, name, *hash, target, args, def_value,
|
||||
*native, false, *pos, level,
|
||||
mods, state, lib, name, *hash, target, args, def_value, false,
|
||||
*pos, level,
|
||||
)?;
|
||||
val.into()
|
||||
}
|
||||
@ -1188,12 +1194,12 @@ impl Engine {
|
||||
match &x.lhs {
|
||||
// xxx.prop[expr] | xxx.prop.expr
|
||||
Expr::Property(p) => {
|
||||
let ((getter, setter), Ident { pos, .. }) = p.as_ref();
|
||||
let (getter, setter, Ident { pos, .. }) = p.as_ref();
|
||||
let arg_values = &mut [target_val, &mut Default::default()];
|
||||
let args = &mut arg_values[..1];
|
||||
let (mut val, updated) = self
|
||||
.exec_fn_call(
|
||||
mods, state, lib, getter, 0, args, is_ref, true, false,
|
||||
mods, state, lib, getter, None, args, is_ref, true, false,
|
||||
*pos, None, None, level,
|
||||
)
|
||||
.map_err(|err| err.fill_position(*pos))?;
|
||||
@ -1220,7 +1226,7 @@ impl Engine {
|
||||
// Re-use args because the first &mut parameter will not be consumed
|
||||
arg_values[1] = val;
|
||||
self.exec_fn_call(
|
||||
mods, state, lib, setter, 0, arg_values, is_ref, true,
|
||||
mods, state, lib, setter, None, arg_values, is_ref, true,
|
||||
false, *pos, None, None, level,
|
||||
)
|
||||
.or_else(
|
||||
@ -1240,16 +1246,15 @@ impl Engine {
|
||||
Expr::FnCall(f, pos) if f.namespace.is_none() => {
|
||||
let FnCallExpr {
|
||||
name,
|
||||
native_only: native,
|
||||
hash,
|
||||
hash_script: hash,
|
||||
def_value,
|
||||
..
|
||||
} = f.as_ref();
|
||||
let def_value = def_value.as_ref();
|
||||
let args = idx_val.as_fn_call_args();
|
||||
let (mut val, _) = self.make_method_call(
|
||||
mods, state, lib, name, *hash, target, args, def_value,
|
||||
*native, false, *pos, level,
|
||||
mods, state, lib, name, *hash, target, args, def_value, false,
|
||||
*pos, level,
|
||||
)?;
|
||||
let val = &mut val;
|
||||
let target = &mut val.into();
|
||||
@ -1306,11 +1311,11 @@ impl Engine {
|
||||
let Ident {
|
||||
name: var_name,
|
||||
pos: var_pos,
|
||||
} = &x.3;
|
||||
} = &x.2;
|
||||
|
||||
self.inc_operations(state, *var_pos)?;
|
||||
|
||||
let (target, _, pos) =
|
||||
let (target, pos) =
|
||||
self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?;
|
||||
|
||||
// Constants cannot be modified
|
||||
@ -1501,8 +1506,8 @@ impl Engine {
|
||||
let mut idx = idx;
|
||||
let args = &mut [target, &mut idx];
|
||||
self.exec_fn_call(
|
||||
_mods, state, _lib, FN_IDX_GET, 0, args, _is_ref, true, false, idx_pos, None,
|
||||
None, _level,
|
||||
_mods, state, _lib, FN_IDX_GET, None, args, _is_ref, true, false, idx_pos,
|
||||
None, None, _level,
|
||||
)
|
||||
.map(|(v, _)| v.into())
|
||||
.map_err(|err| match *err {
|
||||
@ -1553,7 +1558,8 @@ impl Engine {
|
||||
|
||||
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
|
||||
let hash =
|
||||
calc_native_fn_hash(empty(), OP_EQUALS, args.iter().map(|a| a.type_id()));
|
||||
calc_native_fn_hash(empty(), OP_EQUALS, args.iter().map(|a| a.type_id()))
|
||||
.unwrap();
|
||||
|
||||
let pos = rhs.position();
|
||||
|
||||
@ -1603,7 +1609,7 @@ impl Engine {
|
||||
match expr {
|
||||
// var - point directly to the value
|
||||
Expr::Variable(_) => {
|
||||
let (mut target, _, pos) =
|
||||
let (mut target, pos) =
|
||||
self.search_namespace(scope, mods, state, lib, this_ptr, expr)?;
|
||||
|
||||
// If necessary, constants are cloned
|
||||
@ -1659,7 +1665,7 @@ impl Engine {
|
||||
|
||||
if target.is::<Map>() {
|
||||
// map.prop - point directly to the item
|
||||
let (_, Ident { name, pos }) = p.as_ref();
|
||||
let (_, _, Ident { name, pos }) = p.as_ref();
|
||||
let idx = name.clone().into();
|
||||
|
||||
if target.is_shared() || target.is_value() {
|
||||
@ -1677,10 +1683,10 @@ impl Engine {
|
||||
.map(|v| (v, *pos))
|
||||
} else {
|
||||
// var.prop - call property getter
|
||||
let ((getter, _), Ident { pos, .. }) = p.as_ref();
|
||||
let (getter, _, Ident { pos, .. }) = p.as_ref();
|
||||
let mut args = [target.as_mut()];
|
||||
self.exec_fn_call(
|
||||
mods, state, lib, getter, 0, &mut args, is_ref, true, false, *pos,
|
||||
mods, state, lib, getter, None, &mut args, is_ref, true, false, *pos,
|
||||
None, None, level,
|
||||
)
|
||||
.map(|(v, _)| (v.into(), *pos))
|
||||
@ -1721,15 +1727,15 @@ impl Engine {
|
||||
Expr::StringConstant(x, _) => Ok(x.clone().into()),
|
||||
Expr::CharConstant(x, _) => Ok((*x).into()),
|
||||
Expr::FnPointer(x, _) => Ok(FnPtr::new_unchecked(x.clone(), Default::default()).into()),
|
||||
Expr::Variable(x) if (x.3).name == KEYWORD_THIS => {
|
||||
Expr::Variable(x) if (x.2).name == KEYWORD_THIS => {
|
||||
if let Some(val) = this_ptr {
|
||||
Ok(val.clone())
|
||||
} else {
|
||||
EvalAltResult::ErrorUnboundThis((x.3).pos).into()
|
||||
EvalAltResult::ErrorUnboundThis((x.2).pos).into()
|
||||
}
|
||||
}
|
||||
Expr::Variable(_) => {
|
||||
let (val, _, _) = self.search_namespace(scope, mods, state, lib, this_ptr, expr)?;
|
||||
let (val, _) = self.search_namespace(scope, mods, state, lib, this_ptr, expr)?;
|
||||
Ok(val.take_or_clone())
|
||||
}
|
||||
Expr::Property(_) => unreachable!(),
|
||||
@ -1778,17 +1784,16 @@ impl Engine {
|
||||
Expr::FnCall(x, pos) if x.namespace.is_none() => {
|
||||
let FnCallExpr {
|
||||
name,
|
||||
native_only: native,
|
||||
capture: cap_scope,
|
||||
hash,
|
||||
hash_script: hash,
|
||||
args,
|
||||
def_value,
|
||||
..
|
||||
} = x.as_ref();
|
||||
let def_value = def_value.as_ref();
|
||||
self.make_function_call(
|
||||
scope, mods, state, lib, this_ptr, name, args, def_value, *hash, *native,
|
||||
false, *pos, *cap_scope, level,
|
||||
scope, mods, state, lib, this_ptr, name, args, def_value, *hash, false, *pos,
|
||||
*cap_scope, level,
|
||||
)
|
||||
}
|
||||
|
||||
@ -1797,15 +1802,16 @@ impl Engine {
|
||||
let FnCallExpr {
|
||||
name,
|
||||
namespace,
|
||||
hash,
|
||||
hash_script: hash,
|
||||
args,
|
||||
def_value,
|
||||
..
|
||||
} = x.as_ref();
|
||||
let namespace = namespace.as_ref().map(|v| v.as_ref());
|
||||
let namespace = namespace.as_ref();
|
||||
let hash = hash.unwrap();
|
||||
let def_value = def_value.as_ref();
|
||||
self.make_qualified_function_call(
|
||||
scope, mods, state, lib, this_ptr, namespace, name, args, def_value, *hash,
|
||||
scope, mods, state, lib, this_ptr, namespace, name, args, def_value, hash,
|
||||
*pos, level,
|
||||
)
|
||||
}
|
||||
@ -1935,11 +1941,15 @@ impl Engine {
|
||||
let mut rhs_val = self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?
|
||||
.flatten();
|
||||
let (mut lhs_ptr, name, pos) =
|
||||
let (mut lhs_ptr, pos) =
|
||||
self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?;
|
||||
|
||||
if !lhs_ptr.is_ref() {
|
||||
return EvalAltResult::ErrorAssignmentToConstant(name.to_string(), pos).into();
|
||||
return EvalAltResult::ErrorAssignmentToConstant(
|
||||
lhs_expr.get_variable_access(false).unwrap().to_string(),
|
||||
pos,
|
||||
)
|
||||
.into();
|
||||
}
|
||||
|
||||
self.inc_operations(state, pos)?;
|
||||
@ -1947,7 +1957,7 @@ impl Engine {
|
||||
if lhs_ptr.as_ref().is_read_only() {
|
||||
// Assignment to constant variable
|
||||
Err(Box::new(EvalAltResult::ErrorAssignmentToConstant(
|
||||
name.to_string(),
|
||||
lhs_expr.get_variable_access(false).unwrap().to_string(),
|
||||
pos,
|
||||
)))
|
||||
} else if op.is_empty() {
|
||||
@ -1966,7 +1976,7 @@ impl Engine {
|
||||
|
||||
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
|
||||
let arg_types = once(lhs_ptr.as_mut().type_id()).chain(once(rhs_val.type_id()));
|
||||
let hash_fn = calc_native_fn_hash(empty(), op, arg_types);
|
||||
let hash_fn = calc_native_fn_hash(empty(), op, arg_types).unwrap();
|
||||
|
||||
match self
|
||||
.global_namespace
|
||||
@ -2015,8 +2025,8 @@ impl Engine {
|
||||
|
||||
// Run function
|
||||
let (value, _) = self.exec_fn_call(
|
||||
mods, state, lib, op, 0, args, false, false, false, *op_pos, None,
|
||||
None, level,
|
||||
mods, state, lib, op, None, args, false, false, false, *op_pos,
|
||||
None, None, level,
|
||||
)?;
|
||||
|
||||
let value = value.flatten();
|
||||
@ -2051,7 +2061,7 @@ impl Engine {
|
||||
|
||||
let result = self
|
||||
.exec_fn_call(
|
||||
mods, state, lib, op, 0, args, false, false, false, *op_pos, None,
|
||||
mods, state, lib, op, None, args, false, false, false, *op_pos, None,
|
||||
None, level,
|
||||
)
|
||||
.map(|(v, _)| v)?;
|
||||
@ -2203,7 +2213,7 @@ impl Engine {
|
||||
state.scope_level += 1;
|
||||
|
||||
for iter_value in func(iter_obj) {
|
||||
let loop_var = scope.get_mut(index);
|
||||
let loop_var = scope.get_mut_by_index(index);
|
||||
|
||||
let value = iter_value.flatten();
|
||||
if cfg!(not(feature = "no_closure")) && loop_var.is_shared() {
|
||||
@ -2369,31 +2379,24 @@ impl Engine {
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, &expr, level)?
|
||||
.try_cast::<ImmutableString>()
|
||||
{
|
||||
if let Some(resolver) = &self.module_resolver {
|
||||
let module = resolver.resolve(self, &path, expr.position())?;
|
||||
let module = self.module_resolver.resolve(self, &path, expr.position())?;
|
||||
|
||||
if let Some(name_def) = alias {
|
||||
if !module.is_indexed() {
|
||||
// Index the module (making a clone copy if necessary) if it is not indexed
|
||||
let mut module = crate::fn_native::shared_take_or_clone(module);
|
||||
module.build_index();
|
||||
mods.push(name_def.name.clone(), module);
|
||||
} else {
|
||||
mods.push(name_def.name.clone(), module);
|
||||
}
|
||||
// When imports list is modified, clear the functions lookup cache
|
||||
state.functions_cache.clear();
|
||||
if let Some(name_def) = alias {
|
||||
if !module.is_indexed() {
|
||||
// Index the module (making a clone copy if necessary) if it is not indexed
|
||||
let mut module = crate::fn_native::shared_take_or_clone(module);
|
||||
module.build_index();
|
||||
mods.push(name_def.name.clone(), module);
|
||||
} else {
|
||||
mods.push(name_def.name.clone(), module);
|
||||
}
|
||||
|
||||
state.modules += 1;
|
||||
|
||||
Ok(Dynamic::UNIT)
|
||||
} else {
|
||||
Err(
|
||||
EvalAltResult::ErrorModuleNotFound(path.to_string(), expr.position())
|
||||
.into(),
|
||||
)
|
||||
// When imports list is modified, clear the functions lookup cache
|
||||
state.functions_cache.clear();
|
||||
}
|
||||
|
||||
state.modules += 1;
|
||||
|
||||
Ok(Dynamic::UNIT)
|
||||
} else {
|
||||
Err(self.make_type_mismatch_err::<ImmutableString>("", expr.position()))
|
||||
}
|
||||
@ -2419,7 +2422,7 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Stmt::Share(x) => {
|
||||
if let Some((index, _)) = scope.get_index(&x.name) {
|
||||
let val = scope.get_mut(index);
|
||||
let val = scope.get_mut_by_index(index);
|
||||
|
||||
if !val.is_shared() {
|
||||
// Replace the variable with a shared value.
|
||||
|
@ -209,9 +209,10 @@ impl Engine {
|
||||
pub fn register_get<T: Variant + Clone, U: Variant + Clone>(
|
||||
&mut self,
|
||||
name: &str,
|
||||
callback: impl Fn(&mut T) -> U + SendSync + 'static,
|
||||
get_fn: impl Fn(&mut T) -> U + SendSync + 'static,
|
||||
) -> &mut Self {
|
||||
crate::RegisterFn::register_fn(self, &crate::engine::make_getter(name), callback)
|
||||
use crate::{engine::make_getter, RegisterFn};
|
||||
self.register_fn(&make_getter(name), get_fn)
|
||||
}
|
||||
/// Register a getter function for a member of a registered type with the [`Engine`].
|
||||
///
|
||||
@ -255,13 +256,10 @@ impl Engine {
|
||||
pub fn register_get_result<T: Variant + Clone>(
|
||||
&mut self,
|
||||
name: &str,
|
||||
callback: impl Fn(&mut T) -> Result<Dynamic, Box<EvalAltResult>> + SendSync + 'static,
|
||||
get_fn: impl Fn(&mut T) -> Result<Dynamic, Box<EvalAltResult>> + SendSync + 'static,
|
||||
) -> &mut Self {
|
||||
crate::RegisterResultFn::register_result_fn(
|
||||
self,
|
||||
&crate::engine::make_getter(name),
|
||||
callback,
|
||||
)
|
||||
use crate::{engine::make_getter, RegisterResultFn};
|
||||
self.register_result_fn(&make_getter(name), get_fn)
|
||||
}
|
||||
/// Register a setter function for a member of a registered type with the [`Engine`].
|
||||
///
|
||||
@ -304,9 +302,10 @@ impl Engine {
|
||||
pub fn register_set<T: Variant + Clone, U: Variant + Clone>(
|
||||
&mut self,
|
||||
name: &str,
|
||||
callback: impl Fn(&mut T, U) + SendSync + 'static,
|
||||
set_fn: impl Fn(&mut T, U) + SendSync + 'static,
|
||||
) -> &mut Self {
|
||||
crate::RegisterFn::register_fn(self, &crate::engine::make_setter(name), callback)
|
||||
use crate::{engine::make_setter, RegisterFn};
|
||||
self.register_fn(&make_setter(name), set_fn)
|
||||
}
|
||||
/// Register a setter function for a member of a registered type with the [`Engine`].
|
||||
///
|
||||
@ -352,13 +351,12 @@ impl Engine {
|
||||
pub fn register_set_result<T: Variant + Clone, U: Variant + Clone>(
|
||||
&mut self,
|
||||
name: &str,
|
||||
callback: impl Fn(&mut T, U) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
|
||||
set_fn: impl Fn(&mut T, U) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
|
||||
) -> &mut Self {
|
||||
crate::RegisterResultFn::register_result_fn(
|
||||
self,
|
||||
&crate::engine::make_setter(name),
|
||||
move |obj: &mut T, value: U| callback(obj, value).map(Into::into),
|
||||
)
|
||||
use crate::{engine::make_setter, RegisterResultFn};
|
||||
self.register_result_fn(&make_setter(name), move |obj: &mut T, value: U| {
|
||||
set_fn(obj, value).map(Into::into)
|
||||
})
|
||||
}
|
||||
/// Short-hand for registering both getter and setter functions
|
||||
/// of a registered type with the [`Engine`].
|
||||
@ -453,7 +451,7 @@ impl Engine {
|
||||
#[inline(always)]
|
||||
pub fn register_indexer_get<T: Variant + Clone, X: Variant + Clone, U: Variant + Clone>(
|
||||
&mut self,
|
||||
callback: impl Fn(&mut T, X) -> U + SendSync + 'static,
|
||||
get_fn: impl Fn(&mut T, X) -> U + SendSync + 'static,
|
||||
) -> &mut Self {
|
||||
if TypeId::of::<T>() == TypeId::of::<Array>() {
|
||||
panic!("Cannot register indexer for arrays.");
|
||||
@ -469,7 +467,8 @@ impl Engine {
|
||||
panic!("Cannot register indexer for strings.");
|
||||
}
|
||||
|
||||
crate::RegisterFn::register_fn(self, crate::engine::FN_IDX_GET, callback)
|
||||
use crate::{engine::FN_IDX_GET, RegisterFn};
|
||||
self.register_fn(FN_IDX_GET, get_fn)
|
||||
}
|
||||
/// Register an index getter for a custom type with the [`Engine`].
|
||||
///
|
||||
@ -518,7 +517,7 @@ impl Engine {
|
||||
#[inline(always)]
|
||||
pub fn register_indexer_get_result<T: Variant + Clone, X: Variant + Clone>(
|
||||
&mut self,
|
||||
callback: impl Fn(&mut T, X) -> Result<Dynamic, Box<EvalAltResult>> + SendSync + 'static,
|
||||
get_fn: impl Fn(&mut T, X) -> Result<Dynamic, Box<EvalAltResult>> + SendSync + 'static,
|
||||
) -> &mut Self {
|
||||
if TypeId::of::<T>() == TypeId::of::<Array>() {
|
||||
panic!("Cannot register indexer for arrays.");
|
||||
@ -534,7 +533,8 @@ impl Engine {
|
||||
panic!("Cannot register indexer for strings.");
|
||||
}
|
||||
|
||||
crate::RegisterResultFn::register_result_fn(self, crate::engine::FN_IDX_GET, callback)
|
||||
use crate::{engine::FN_IDX_GET, RegisterResultFn};
|
||||
self.register_result_fn(FN_IDX_GET, get_fn)
|
||||
}
|
||||
/// Register an index setter for a custom type with the [`Engine`].
|
||||
///
|
||||
@ -581,7 +581,7 @@ impl Engine {
|
||||
#[inline(always)]
|
||||
pub fn register_indexer_set<T: Variant + Clone, X: Variant + Clone, U: Variant + Clone>(
|
||||
&mut self,
|
||||
callback: impl Fn(&mut T, X, U) + SendSync + 'static,
|
||||
set_fn: impl Fn(&mut T, X, U) + SendSync + 'static,
|
||||
) -> &mut Self {
|
||||
if TypeId::of::<T>() == TypeId::of::<Array>() {
|
||||
panic!("Cannot register indexer for arrays.");
|
||||
@ -597,7 +597,8 @@ impl Engine {
|
||||
panic!("Cannot register indexer for strings.");
|
||||
}
|
||||
|
||||
crate::RegisterFn::register_fn(self, crate::engine::FN_IDX_SET, callback)
|
||||
use crate::{engine::FN_IDX_SET, RegisterFn};
|
||||
self.register_fn(FN_IDX_SET, set_fn)
|
||||
}
|
||||
/// Register an index setter for a custom type with the [`Engine`].
|
||||
///
|
||||
@ -651,7 +652,7 @@ impl Engine {
|
||||
U: Variant + Clone,
|
||||
>(
|
||||
&mut self,
|
||||
callback: impl Fn(&mut T, X, U) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
|
||||
set_fn: impl Fn(&mut T, X, U) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
|
||||
) -> &mut Self {
|
||||
if TypeId::of::<T>() == TypeId::of::<Array>() {
|
||||
panic!("Cannot register indexer for arrays.");
|
||||
@ -667,11 +668,10 @@ impl Engine {
|
||||
panic!("Cannot register indexer for strings.");
|
||||
}
|
||||
|
||||
crate::RegisterResultFn::register_result_fn(
|
||||
self,
|
||||
crate::engine::FN_IDX_SET,
|
||||
move |obj: &mut T, index: X, value: U| callback(obj, index, value).map(Into::into),
|
||||
)
|
||||
use crate::{engine::FN_IDX_SET, RegisterResultFn};
|
||||
self.register_result_fn(FN_IDX_SET, move |obj: &mut T, index: X, value: U| {
|
||||
set_fn(obj, index, value).map(Into::into)
|
||||
})
|
||||
}
|
||||
/// Short-hand for register both index getter and setter functions for a custom type with the [`Engine`].
|
||||
///
|
||||
@ -723,21 +723,22 @@ impl Engine {
|
||||
self.register_indexer_get(getter)
|
||||
.register_indexer_set(setter)
|
||||
}
|
||||
/// Register a shared [`Module`][crate::Module] into the global namespace of [`Engine`].
|
||||
/// Register a shared [`Module`] into the global namespace of [`Engine`].
|
||||
///
|
||||
/// All functions and type iterators are automatically available to scripts without namespace qualifications.
|
||||
/// All functions and type iterators are automatically available to scripts without namespace
|
||||
/// qualifications.
|
||||
///
|
||||
/// Sub-modules and variables are **ignored**.
|
||||
///
|
||||
/// When searching for functions, modules loaded later are preferred.
|
||||
/// In other words, loaded modules are searched in reverse order.
|
||||
/// When searching for functions, modules loaded later are preferred. In other words, loaded
|
||||
/// modules are searched in reverse order.
|
||||
#[inline(always)]
|
||||
pub fn register_global_module(&mut self, module: Shared<Module>) -> &mut Self {
|
||||
// Insert the module into the front
|
||||
self.global_modules.insert(0, module);
|
||||
self
|
||||
}
|
||||
/// Register a shared [`Module`][crate::Module] into the global namespace of [`Engine`].
|
||||
/// Register a shared [`Module`] into the global namespace of [`Engine`].
|
||||
///
|
||||
/// ## Deprecated
|
||||
///
|
||||
@ -747,15 +748,17 @@ impl Engine {
|
||||
pub fn load_package(&mut self, module: impl Into<Shared<Module>>) -> &mut Self {
|
||||
self.register_global_module(module.into())
|
||||
}
|
||||
/// Register a shared [`Module`][crate::Module] as a static module namespace with the [`Engine`].
|
||||
/// Register a shared [`Module`] as a static module namespace with the
|
||||
/// [`Engine`].
|
||||
///
|
||||
/// Functions marked `FnNamespace::Global` and type iterators are exposed to scripts without namespace qualifications.
|
||||
/// Functions marked [`FnNamespace::Global`] and type iterators are exposed to scripts without
|
||||
/// namespace qualifications.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
|
||||
/// use rhai::{Engine, Module};
|
||||
/// use rhai::{Engine, Shared, Module};
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
@ -763,9 +766,18 @@ impl Engine {
|
||||
/// let mut module = Module::new();
|
||||
/// module.set_fn_1("calc", |x: i64| Ok(x + 1));
|
||||
///
|
||||
/// // Register the module as a fixed sub-module
|
||||
/// engine.register_static_module("CalcService", module.into());
|
||||
/// let module: Shared<Module> = module.into();
|
||||
///
|
||||
/// // Register the module as a fixed sub-module
|
||||
/// engine.register_static_module("foo::bar::baz", module.clone());
|
||||
///
|
||||
/// // Multiple registrations to the same partial path is also OK!
|
||||
/// engine.register_static_module("foo::bar::hello", module.clone());
|
||||
///
|
||||
/// engine.register_static_module("CalcService", module);
|
||||
///
|
||||
/// assert_eq!(engine.eval::<i64>("foo::bar::baz::calc(41)")?, 42);
|
||||
/// assert_eq!(engine.eval::<i64>("foo::bar::hello::calc(41)")?, 42);
|
||||
/// assert_eq!(engine.eval::<i64>("CalcService::calc(41)")?, 42);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
@ -773,20 +785,50 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub fn register_static_module(
|
||||
&mut self,
|
||||
name: impl Into<crate::ImmutableString>,
|
||||
name: impl AsRef<str>,
|
||||
module: Shared<Module>,
|
||||
) -> &mut Self {
|
||||
if !module.is_indexed() {
|
||||
// Index the module (making a clone copy if necessary) if it is not indexed
|
||||
let mut module = crate::fn_native::shared_take_or_clone(module);
|
||||
module.build_index();
|
||||
self.global_sub_modules.push(name, module);
|
||||
} else {
|
||||
self.global_sub_modules.push(name, module);
|
||||
fn register_static_module_raw(
|
||||
root: &mut crate::stdlib::collections::HashMap<crate::ImmutableString, Shared<Module>>,
|
||||
name: impl AsRef<str>,
|
||||
module: Shared<Module>,
|
||||
) {
|
||||
let separator = crate::token::Token::DoubleColon.syntax();
|
||||
|
||||
if !name.as_ref().contains(separator.as_ref()) {
|
||||
if !module.is_indexed() {
|
||||
// Index the module (making a clone copy if necessary) if it is not indexed
|
||||
let mut module = crate::fn_native::shared_take_or_clone(module);
|
||||
module.build_index();
|
||||
root.insert(name.as_ref().trim().into(), module.into());
|
||||
} else {
|
||||
root.insert(name.as_ref().trim().into(), module);
|
||||
}
|
||||
} else {
|
||||
let mut iter = name.as_ref().splitn(2, separator.as_ref());
|
||||
let sub_module = iter.next().unwrap().trim();
|
||||
let remainder = iter.next().unwrap().trim();
|
||||
|
||||
if !root.contains_key(sub_module) {
|
||||
let mut m: Module = Default::default();
|
||||
register_static_module_raw(m.sub_modules_mut(), remainder, module);
|
||||
m.build_index();
|
||||
root.insert(sub_module.into(), m.into());
|
||||
} else {
|
||||
let m = root.remove(sub_module).unwrap();
|
||||
let mut m = crate::fn_native::shared_take_or_clone(m);
|
||||
register_static_module_raw(m.sub_modules_mut(), remainder, module);
|
||||
m.build_index();
|
||||
root.insert(sub_module.into(), m.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
register_static_module_raw(&mut self.global_sub_modules, name.as_ref(), module);
|
||||
self
|
||||
}
|
||||
/// Register a shared [`Module`][crate::Module] as a static module namespace with the [`Engine`].
|
||||
|
||||
/// Register a shared [`Module`] as a static module namespace with the [`Engine`].
|
||||
///
|
||||
/// ## Deprecated
|
||||
///
|
||||
@ -796,7 +838,7 @@ impl Engine {
|
||||
#[deprecated = "use `register_static_module` instead"]
|
||||
pub fn register_module(
|
||||
&mut self,
|
||||
name: impl Into<crate::ImmutableString>,
|
||||
name: impl AsRef<str>,
|
||||
module: impl Into<Shared<Module>>,
|
||||
) -> &mut Self {
|
||||
self.register_static_module(name, module.into())
|
||||
@ -1407,7 +1449,7 @@ impl Engine {
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
) -> Result<T, Box<EvalAltResult>> {
|
||||
let mods = &mut self.global_sub_modules.clone();
|
||||
let mods = &mut (&self.global_sub_modules).into();
|
||||
|
||||
let result = self.eval_ast_with_scope_raw(scope, mods, ast)?;
|
||||
|
||||
@ -1493,7 +1535,7 @@ impl Engine {
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
let mods = &mut self.global_sub_modules.clone();
|
||||
let mods = &mut (&self.global_sub_modules).into();
|
||||
let state = &mut State {
|
||||
source: ast.clone_source(),
|
||||
..Default::default()
|
||||
@ -1539,12 +1581,12 @@ impl Engine {
|
||||
/// ```
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline]
|
||||
pub fn call_fn<A: crate::fn_args::FuncArgs, T: Variant + Clone>(
|
||||
pub fn call_fn<T: Variant + Clone>(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
ast: &AST,
|
||||
name: &str,
|
||||
args: A,
|
||||
args: impl crate::fn_args::FuncArgs,
|
||||
) -> Result<T, Box<EvalAltResult>> {
|
||||
let mut arg_values = args.into_vec();
|
||||
let mut args: crate::StaticVec<_> = arg_values.as_mut().iter_mut().collect();
|
||||
@ -1650,7 +1692,7 @@ impl Engine {
|
||||
.ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::NONE))?;
|
||||
|
||||
let mut state = Default::default();
|
||||
let mut mods = self.global_sub_modules.clone();
|
||||
let mut mods = (&self.global_sub_modules).into();
|
||||
|
||||
// Check for data race.
|
||||
if cfg!(not(feature = "no_closure")) {
|
||||
@ -1680,7 +1722,7 @@ impl Engine {
|
||||
/// With this method, it is no longer necessary to recompile a large script.
|
||||
/// The script [`AST`] can be compiled just once. Before evaluation,
|
||||
/// constants are passed into the [`Engine`] via an external scope
|
||||
/// (i.e. with [`scope.push_constant(...)`][crate::Scope::push_constant]).
|
||||
/// (i.e. with [`Scope::push_constant`]).
|
||||
/// Then, the [`AST`] is cloned and the copy re-optimized before running.
|
||||
#[cfg(not(feature = "no_optimize"))]
|
||||
#[inline]
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! Configuration settings for [`Engine`].
|
||||
|
||||
use crate::stdlib::{format, string::String};
|
||||
use crate::token::{is_valid_identifier, Token};
|
||||
use crate::stdlib::{format, num::NonZeroU8, string::String};
|
||||
use crate::token::Token;
|
||||
use crate::Engine;
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -168,9 +168,9 @@ impl Engine {
|
||||
#[inline(always)]
|
||||
pub fn set_module_resolver(
|
||||
&mut self,
|
||||
resolver: Option<impl crate::ModuleResolver + 'static>,
|
||||
resolver: impl crate::ModuleResolver + 'static,
|
||||
) -> &mut Self {
|
||||
self.module_resolver = resolver.map(|f| Box::new(f) as Box<dyn crate::ModuleResolver>);
|
||||
self.module_resolver = Box::new(resolver);
|
||||
self
|
||||
}
|
||||
/// Disable a particular keyword or operator in the language.
|
||||
@ -214,10 +214,12 @@ impl Engine {
|
||||
self.disabled_symbols.insert(symbol.into());
|
||||
self
|
||||
}
|
||||
/// Register a custom operator into the language.
|
||||
/// Register a custom operator with a precedence into the language.
|
||||
///
|
||||
/// The operator must be a valid identifier (i.e. it cannot be a symbol).
|
||||
///
|
||||
/// The precedence cannot be zero.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
@ -245,22 +247,38 @@ impl Engine {
|
||||
keyword: &str,
|
||||
precedence: u8,
|
||||
) -> Result<&mut Self, String> {
|
||||
if !is_valid_identifier(keyword.chars()) {
|
||||
return Err(format!("not a valid identifier: '{}'", keyword).into());
|
||||
let precedence = NonZeroU8::new(precedence);
|
||||
|
||||
if precedence.is_none() {
|
||||
return Err("precedence cannot be zero".into());
|
||||
}
|
||||
|
||||
match Token::lookup_from_syntax(keyword) {
|
||||
// Standard identifiers, reserved keywords and custom keywords are OK
|
||||
None | Some(Token::Reserved(_)) | Some(Token::Custom(_)) => (),
|
||||
// Disabled keywords are also OK
|
||||
Some(token) if !self.disabled_symbols.contains(token.syntax().as_ref()) => (),
|
||||
// Active standard keywords cannot be made custom
|
||||
Some(_) => return Err(format!("'{}' is a reserved keyword", keyword).into()),
|
||||
// Disabled keywords are OK
|
||||
Some(token) if token.is_keyword() => {
|
||||
if !self.disabled_symbols.contains(token.syntax().as_ref()) {
|
||||
return Err(format!("'{}' is a reserved keyword", keyword).into());
|
||||
}
|
||||
}
|
||||
// Active standard operators cannot be made custom
|
||||
Some(token) if token.is_operator() => {
|
||||
if !self.disabled_symbols.contains(token.syntax().as_ref()) {
|
||||
return Err(format!("'{}' is a reserved operator", keyword).into());
|
||||
}
|
||||
}
|
||||
// Active standard symbols cannot be made custom
|
||||
Some(token) if !self.disabled_symbols.contains(token.syntax().as_ref()) => {
|
||||
return Err(format!("'{}' is a reserved symbol", keyword).into())
|
||||
}
|
||||
// Disabled symbols are OK
|
||||
Some(_) => (),
|
||||
}
|
||||
|
||||
// Add to custom keywords
|
||||
self.custom_keywords
|
||||
.insert(keyword.into(), Some(precedence));
|
||||
self.custom_keywords.insert(keyword.into(), precedence);
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
100
src/fn_call.rs
100
src/fn_call.rs
@ -15,10 +15,12 @@ use crate::stdlib::{
|
||||
format,
|
||||
iter::{empty, once},
|
||||
mem,
|
||||
num::NonZeroU64,
|
||||
ops::Deref,
|
||||
string::ToString,
|
||||
vec::Vec,
|
||||
};
|
||||
use crate::utils::combine_hashes;
|
||||
use crate::{
|
||||
calc_native_fn_hash, calc_script_fn_hash, Dynamic, Engine, EvalAltResult, FnPtr,
|
||||
ImmutableString, Module, ParseErrorType, Position, Scope, StaticVec, INT,
|
||||
@ -161,7 +163,7 @@ impl Engine {
|
||||
state: &mut State,
|
||||
lib: &[&Module],
|
||||
fn_name: &str,
|
||||
hash_fn: u64,
|
||||
hash_fn: NonZeroU64,
|
||||
args: &mut FnCallArgs,
|
||||
is_ref: bool,
|
||||
pub_only: bool,
|
||||
@ -453,22 +455,22 @@ impl Engine {
|
||||
&self,
|
||||
mods: Option<&Imports>,
|
||||
lib: &[&Module],
|
||||
hash_fn: u64,
|
||||
hash_script: u64,
|
||||
hash_fn: Option<NonZeroU64>,
|
||||
hash_script: Option<NonZeroU64>,
|
||||
pub_only: bool,
|
||||
) -> bool {
|
||||
// First check script-defined functions
|
||||
(hash_script != 0 && lib.iter().any(|&m| m.contains_fn(hash_script, pub_only)))
|
||||
//|| lib.iter().any(|&m| m.contains_fn(hash_fn, pub_only))
|
||||
hash_script.map(|hash| lib.iter().any(|&m| m.contains_fn(hash, pub_only))).unwrap_or(false)
|
||||
//|| hash_fn.map(|hash| lib.iter().any(|&m| m.contains_fn(hash, pub_only))).unwrap_or(false)
|
||||
// Then check registered functions
|
||||
//|| (hash_script != 0 && self.global_namespace.contains_fn(hash_script, pub_only))
|
||||
|| self.global_namespace.contains_fn(hash_fn, false)
|
||||
//|| hash_script.map(|hash| self.global_namespace.contains_fn(hash, pub_only)).unwrap_or(false)
|
||||
|| hash_fn.map(|hash| self.global_namespace.contains_fn(hash, false)).unwrap_or(false)
|
||||
// Then check packages
|
||||
|| (hash_script != 0 && self.global_modules.iter().any(|m| m.contains_fn(hash_script, false)))
|
||||
|| self.global_modules.iter().any(|m| m.contains_fn(hash_fn, false))
|
||||
|| hash_script.map(|hash| self.global_modules.iter().any(|m| m.contains_fn(hash, false))).unwrap_or(false)
|
||||
|| hash_fn.map(|hash| self.global_modules.iter().any(|m| m.contains_fn(hash, false))).unwrap_or(false)
|
||||
// Then check imported modules
|
||||
|| (hash_script != 0 && mods.map(|m| m.contains_fn(hash_script)).unwrap_or(false))
|
||||
|| mods.map(|m| m.contains_fn(hash_fn)).unwrap_or(false)
|
||||
|| hash_script.map(|hash| mods.map(|m| m.contains_fn(hash)).unwrap_or(false)).unwrap_or(false)
|
||||
|| hash_fn.map(|hash| mods.map(|m| m.contains_fn(hash)).unwrap_or(false)).unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Perform an actual function call, native Rust or scripted, taking care of special functions.
|
||||
@ -484,7 +486,7 @@ impl Engine {
|
||||
state: &mut State,
|
||||
lib: &[&Module],
|
||||
fn_name: &str,
|
||||
hash_script: u64,
|
||||
hash_script: Option<NonZeroU64>,
|
||||
args: &mut FnCallArgs,
|
||||
is_ref: bool,
|
||||
_is_method: bool,
|
||||
@ -534,9 +536,11 @@ impl Engine {
|
||||
|
||||
// Script-like function found
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
_ if hash_script != 0
|
||||
&& self.has_override(Some(mods), lib, 0, hash_script, pub_only) =>
|
||||
_ if hash_script.is_some()
|
||||
&& self.has_override(Some(mods), lib, None, hash_script, pub_only) =>
|
||||
{
|
||||
let hash_script = hash_script.unwrap();
|
||||
|
||||
// Get function
|
||||
let (func, mut source) = lib
|
||||
.iter()
|
||||
@ -636,7 +640,16 @@ impl Engine {
|
||||
|
||||
// Normal native function call
|
||||
_ => self.call_native_fn(
|
||||
mods, state, lib, fn_name, hash_fn, args, is_ref, pub_only, pos, def_val,
|
||||
mods,
|
||||
state,
|
||||
lib,
|
||||
fn_name,
|
||||
hash_fn.unwrap(),
|
||||
args,
|
||||
is_ref,
|
||||
pub_only,
|
||||
pos,
|
||||
def_val,
|
||||
),
|
||||
}
|
||||
}
|
||||
@ -723,11 +736,10 @@ impl Engine {
|
||||
state: &mut State,
|
||||
lib: &[&Module],
|
||||
fn_name: &str,
|
||||
hash_script: u64,
|
||||
hash_script: Option<NonZeroU64>,
|
||||
target: &mut crate::engine::Target,
|
||||
mut call_args: StaticVec<Dynamic>,
|
||||
def_val: Option<&Dynamic>,
|
||||
native: bool,
|
||||
pub_only: bool,
|
||||
pos: Position,
|
||||
level: usize,
|
||||
@ -745,11 +757,7 @@ impl Engine {
|
||||
let fn_name = fn_ptr.fn_name();
|
||||
let args_len = call_args.len() + fn_ptr.curry().len();
|
||||
// Recalculate hash
|
||||
let hash = if native {
|
||||
0
|
||||
} else {
|
||||
calc_script_fn_hash(empty(), fn_name, args_len)
|
||||
};
|
||||
let hash = hash_script.and_then(|_| calc_script_fn_hash(empty(), fn_name, args_len));
|
||||
// Arguments are passed as-is, adding the curried arguments
|
||||
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
|
||||
let mut arg_values = curry
|
||||
@ -773,11 +781,7 @@ impl Engine {
|
||||
let fn_name = fn_ptr.fn_name();
|
||||
let args_len = call_args.len() + fn_ptr.curry().len();
|
||||
// Recalculate hash
|
||||
let hash = if native {
|
||||
0
|
||||
} else {
|
||||
calc_script_fn_hash(empty(), fn_name, args_len)
|
||||
};
|
||||
let hash = hash_script.and_then(|_| calc_script_fn_hash(empty(), fn_name, args_len));
|
||||
// Replace the first argument with the object pointer, adding the curried arguments
|
||||
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
|
||||
let mut arg_values = once(obj)
|
||||
@ -837,17 +841,14 @@ impl Engine {
|
||||
.enumerate()
|
||||
.for_each(|(i, v)| call_args.insert(i, v));
|
||||
// Recalculate the hash based on the new function name and new arguments
|
||||
hash = if native {
|
||||
0
|
||||
} else {
|
||||
calc_script_fn_hash(empty(), fn_name, call_args.len())
|
||||
};
|
||||
hash = hash_script
|
||||
.and_then(|_| calc_script_fn_hash(empty(), fn_name, call_args.len()));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if native {
|
||||
hash = 0;
|
||||
if hash_script.is_none() {
|
||||
hash = None;
|
||||
}
|
||||
|
||||
// Attached object pointer in front of the arguments
|
||||
@ -881,8 +882,7 @@ impl Engine {
|
||||
fn_name: &str,
|
||||
args_expr: impl AsRef<[Expr]>,
|
||||
def_val: Option<&Dynamic>,
|
||||
mut hash_script: u64,
|
||||
native: bool,
|
||||
mut hash_script: Option<NonZeroU64>,
|
||||
pub_only: bool,
|
||||
pos: Position,
|
||||
capture_scope: bool,
|
||||
@ -951,7 +951,7 @@ impl Engine {
|
||||
|
||||
if name == KEYWORD_FN_PTR_CALL
|
||||
&& args_expr.len() >= 1
|
||||
&& !self.has_override(Some(mods), lib, 0, hash_script, pub_only)
|
||||
&& !self.has_override(Some(mods), lib, None, hash_script, pub_only)
|
||||
{
|
||||
let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?;
|
||||
|
||||
@ -1041,7 +1041,7 @@ impl Engine {
|
||||
.map(|expr| self.eval_expr(scope, mods, state, lib, this_ptr, expr, level))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
let (mut target, _, pos) =
|
||||
let (mut target, pos) =
|
||||
self.search_namespace(scope, mods, state, lib, this_ptr, &args_expr[0])?;
|
||||
|
||||
if target.as_ref().is_read_only() {
|
||||
@ -1071,11 +1071,21 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
let hash = if native { 0 } else { hash_script };
|
||||
let args = args.as_mut();
|
||||
|
||||
self.exec_fn_call(
|
||||
mods, state, lib, name, hash, args, is_ref, false, pub_only, pos, capture, def_val,
|
||||
mods,
|
||||
state,
|
||||
lib,
|
||||
name,
|
||||
hash_script,
|
||||
args,
|
||||
is_ref,
|
||||
false,
|
||||
pub_only,
|
||||
pos,
|
||||
capture,
|
||||
def_val,
|
||||
level,
|
||||
)
|
||||
.map(|(v, _)| v)
|
||||
@ -1093,7 +1103,7 @@ impl Engine {
|
||||
fn_name: &str,
|
||||
args_expr: impl AsRef<[Expr]>,
|
||||
def_val: Option<&Dynamic>,
|
||||
hash_script: u64,
|
||||
hash_script: NonZeroU64,
|
||||
pos: Position,
|
||||
level: usize,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
@ -1127,7 +1137,7 @@ impl Engine {
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
// Get target reference to first argument
|
||||
let (target, _, pos) =
|
||||
let (target, pos) =
|
||||
self.search_scope_only(scope, mods, state, lib, this_ptr, &args_expr[0])?;
|
||||
|
||||
self.inc_operations(state, pos)?;
|
||||
@ -1167,9 +1177,9 @@ impl Engine {
|
||||
// 2) Calculate a second hash with no qualifiers, empty function name,
|
||||
// and the actual list of argument `TypeId`'.s
|
||||
let hash_fn_args =
|
||||
calc_native_fn_hash(empty(), "", args.iter().map(|a| a.type_id()));
|
||||
// 3) The final hash is the XOR of the two hashes.
|
||||
let hash_qualified_fn = hash_script ^ hash_fn_args;
|
||||
calc_native_fn_hash(empty(), "", args.iter().map(|a| a.type_id())).unwrap();
|
||||
// 3) The two hashes are combined.
|
||||
let hash_qualified_fn = combine_hashes(hash_script, hash_fn_args);
|
||||
|
||||
module.get_qualified_fn(hash_qualified_fn)
|
||||
}
|
||||
|
@ -27,15 +27,15 @@ pub trait Func<ARGS, RET> {
|
||||
/// // Func takes two type parameters:
|
||||
/// // 1) a tuple made up of the types of the script function's parameters
|
||||
/// // 2) the return type of the script function
|
||||
/// //
|
||||
///
|
||||
/// // 'func' will have type Box<dyn Fn(i64, String) -> Result<bool, Box<EvalAltResult>>> and is callable!
|
||||
/// let func = Func::<(i64, String), bool>::create_from_ast(
|
||||
/// // ^^^^^^^^^^^^^ function parameter types in tuple
|
||||
///
|
||||
/// engine, // the 'Engine' is consumed into the closure
|
||||
/// ast, // the 'AST'
|
||||
/// "calc" // the entry-point function name
|
||||
/// );
|
||||
/// engine, // the 'Engine' is consumed into the closure
|
||||
/// ast, // the 'AST'
|
||||
/// "calc" // the entry-point function name
|
||||
/// );
|
||||
///
|
||||
/// func(123, "hello".to_string())? == false; // call the anonymous function
|
||||
/// # Ok(())
|
||||
@ -58,15 +58,15 @@ pub trait Func<ARGS, RET> {
|
||||
/// // Func takes two type parameters:
|
||||
/// // 1) a tuple made up of the types of the script function's parameters
|
||||
/// // 2) the return type of the script function
|
||||
/// //
|
||||
///
|
||||
/// // 'func' will have type Box<dyn Fn(i64, String) -> Result<bool, Box<EvalAltResult>>> and is callable!
|
||||
/// let func = Func::<(i64, String), bool>::create_from_script(
|
||||
/// // ^^^^^^^^^^^^^ function parameter types in tuple
|
||||
///
|
||||
/// engine, // the 'Engine' is consumed into the closure
|
||||
/// script, // the script, notice number of parameters must match
|
||||
/// "calc" // the entry-point function name
|
||||
/// )?;
|
||||
/// engine, // the 'Engine' is consumed into the closure
|
||||
/// script, // the script, notice number of parameters must match
|
||||
/// "calc" // the entry-point function name
|
||||
/// )?;
|
||||
///
|
||||
/// func(123, "hello".to_string())? == false; // call the anonymous function
|
||||
/// # Ok(())
|
||||
|
@ -3,7 +3,14 @@
|
||||
use crate::ast::{FnAccess, ScriptFnDef};
|
||||
use crate::engine::Imports;
|
||||
use crate::plugin::PluginFunction;
|
||||
use crate::stdlib::{boxed::Box, convert::TryFrom, fmt, iter::empty, mem, string::String};
|
||||
use crate::stdlib::{
|
||||
boxed::Box,
|
||||
convert::{TryFrom, TryInto},
|
||||
fmt,
|
||||
iter::empty,
|
||||
mem,
|
||||
string::String,
|
||||
};
|
||||
use crate::token::is_valid_identifier;
|
||||
use crate::{
|
||||
calc_script_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ImmutableString, Module,
|
||||
@ -153,25 +160,13 @@ impl<'e, 's, 'a, 'm, 'pm> NativeCallContext<'e, 's, 'a, 'm, 'pm> {
|
||||
args: &mut [&mut Dynamic],
|
||||
def_value: Option<&Dynamic>,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
let mut mods = self.mods.cloned().unwrap_or_default();
|
||||
|
||||
let hash_script = calc_script_fn_hash(
|
||||
empty(),
|
||||
fn_name,
|
||||
if is_method {
|
||||
args.len() - 1
|
||||
} else {
|
||||
args.len()
|
||||
},
|
||||
);
|
||||
|
||||
self.engine()
|
||||
.exec_fn_call(
|
||||
&mut mods,
|
||||
&mut self.mods.cloned().unwrap_or_default(),
|
||||
&mut Default::default(),
|
||||
self.lib,
|
||||
fn_name,
|
||||
hash_script,
|
||||
calc_script_fn_hash(empty(), fn_name, args.len() - if is_method { 1 } else { 0 }),
|
||||
args,
|
||||
is_method,
|
||||
is_method,
|
||||
@ -225,11 +220,15 @@ pub type FnCallArgs<'a> = [&'a mut Dynamic];
|
||||
|
||||
/// A general function pointer, which may carry additional (i.e. curried) argument values
|
||||
/// to be passed onto a function during a call.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FnPtr(ImmutableString, StaticVec<Dynamic>);
|
||||
|
||||
impl FnPtr {
|
||||
/// Create a new function pointer.
|
||||
pub fn new(name: impl Into<ImmutableString>) -> Result<Self, Box<EvalAltResult>> {
|
||||
name.into().try_into()
|
||||
}
|
||||
/// Create a new function pointer without checking its parameters.
|
||||
#[inline(always)]
|
||||
pub(crate) fn new_unchecked(
|
||||
name: impl Into<ImmutableString>,
|
||||
@ -257,7 +256,24 @@ impl FnPtr {
|
||||
pub fn curry(&self) -> &[Dynamic] {
|
||||
self.1.as_ref()
|
||||
}
|
||||
/// Does this function pointer refer to an anonymous function?
|
||||
/// Add a new curried argument.
|
||||
#[inline(always)]
|
||||
pub fn add_curry(&mut self, value: Dynamic) -> &mut Self {
|
||||
self.1.push(value);
|
||||
self
|
||||
}
|
||||
/// Set curried arguments to the function pointer.
|
||||
#[inline(always)]
|
||||
pub fn set_curry(&mut self, values: impl IntoIterator<Item = Dynamic>) -> &mut Self {
|
||||
self.1 = values.into_iter().collect();
|
||||
self
|
||||
}
|
||||
/// Is the function pointer curried?
|
||||
#[inline(always)]
|
||||
pub fn is_curried(&self) -> bool {
|
||||
!self.1.is_empty()
|
||||
}
|
||||
/// Does the function pointer refer to an anonymous function?
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline(always)]
|
||||
pub fn is_anonymous(&self) -> bool {
|
||||
|
14
src/lib.rs
14
src/lib.rs
@ -1,14 +1,18 @@
|
||||
//! # Rhai - embedded scripting for Rust
|
||||
//!
|
||||
//! ![Rhai logo](https://schungx.github.io/rhai/images/logo/rhai-banner-transparent-colour.svg)
|
||||
//!
|
||||
//! Rhai is a tiny, simple and fast embedded scripting language for Rust
|
||||
//! that gives you a safe and easy way to add scripting to your applications.
|
||||
//! It provides a familiar syntax based on JavaScript and Rust and a simple Rust interface.
|
||||
//! Here is a quick example.
|
||||
//!
|
||||
//! First, the contents of `my_script.rhai`:
|
||||
//! It provides a familiar syntax based on JavaScript+Rust and a simple Rust interface.
|
||||
//!
|
||||
//! # A Quick Example
|
||||
//!
|
||||
//! ## Contents of `my_script.rhai`
|
||||
//!
|
||||
//! ```,ignore
|
||||
//! // Brute force factorial function
|
||||
//! /// Brute force factorial function
|
||||
//! fn factorial(x) {
|
||||
//! if x == 1 { return 1; }
|
||||
//! x * factorial(x - 1)
|
||||
@ -18,7 +22,7 @@
|
||||
//! compute(factorial(10))
|
||||
//! ```
|
||||
//!
|
||||
//! And the Rust part:
|
||||
//! ## The Rust part
|
||||
//!
|
||||
//! ```,no_run
|
||||
//! use rhai::{Engine, EvalAltResult, RegisterFn};
|
||||
|
@ -10,13 +10,14 @@ use crate::stdlib::{
|
||||
collections::HashMap,
|
||||
fmt, format,
|
||||
iter::empty,
|
||||
num::NonZeroU64,
|
||||
num::NonZeroUsize,
|
||||
ops::{Add, AddAssign, Deref, DerefMut},
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
use crate::token::Token;
|
||||
use crate::utils::StraightHasherBuilder;
|
||||
use crate::utils::{combine_hashes, StraightHasherBuilder};
|
||||
use crate::{
|
||||
Dynamic, EvalAltResult, ImmutableString, NativeCallContext, Position, Shared, StaticVec,
|
||||
};
|
||||
@ -34,9 +35,9 @@ use crate::Map;
|
||||
/// A type representing the namespace of a function.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub enum FnNamespace {
|
||||
/// Global namespace.
|
||||
/// Expose to global namespace.
|
||||
Global,
|
||||
/// Internal only.
|
||||
/// Module namespace only.
|
||||
Internal,
|
||||
}
|
||||
|
||||
@ -79,9 +80,9 @@ pub struct FuncInfo {
|
||||
/// Number of parameters.
|
||||
pub params: usize,
|
||||
/// Parameter types (if applicable).
|
||||
pub param_types: Option<StaticVec<TypeId>>,
|
||||
pub param_types: StaticVec<TypeId>,
|
||||
/// Parameter names (if available).
|
||||
pub param_names: Option<StaticVec<ImmutableString>>,
|
||||
pub param_names: StaticVec<ImmutableString>,
|
||||
}
|
||||
|
||||
impl FuncInfo {
|
||||
@ -89,13 +90,19 @@ impl FuncInfo {
|
||||
pub fn gen_signature(&self) -> String {
|
||||
let mut sig = format!("{}(", self.name);
|
||||
|
||||
if let Some(ref names) = self.param_names {
|
||||
let mut params: Vec<_> = names.iter().map(ImmutableString::to_string).collect();
|
||||
if !self.param_names.is_empty() {
|
||||
let mut params: Vec<_> = self
|
||||
.param_names
|
||||
.iter()
|
||||
.map(ImmutableString::to_string)
|
||||
.collect();
|
||||
let return_type = params.pop().unwrap_or_else(|| "()".to_string());
|
||||
sig.push_str(¶ms.join(", "));
|
||||
if return_type != "()" {
|
||||
sig.push_str(") -> ");
|
||||
sig.push_str(&return_type);
|
||||
} else if self.func.is_script() {
|
||||
sig.push_str(") -> Dynamic");
|
||||
} else {
|
||||
sig.push_str(")");
|
||||
}
|
||||
@ -106,7 +113,12 @@ impl FuncInfo {
|
||||
sig.push_str(", ");
|
||||
}
|
||||
}
|
||||
sig.push_str(") -> Dynamic");
|
||||
|
||||
if self.func.is_script() {
|
||||
sig.push_str(") -> Dynamic");
|
||||
} else {
|
||||
sig.push_str(") -> ?");
|
||||
}
|
||||
}
|
||||
|
||||
sig
|
||||
@ -126,12 +138,12 @@ pub struct Module {
|
||||
/// Module variables.
|
||||
variables: HashMap<ImmutableString, Dynamic>,
|
||||
/// Flattened collection of all module variables, including those in sub-modules.
|
||||
all_variables: HashMap<u64, Dynamic, StraightHasherBuilder>,
|
||||
all_variables: HashMap<NonZeroU64, Dynamic, StraightHasherBuilder>,
|
||||
/// External Rust functions.
|
||||
functions: HashMap<u64, FuncInfo, StraightHasherBuilder>,
|
||||
functions: HashMap<NonZeroU64, FuncInfo, StraightHasherBuilder>,
|
||||
/// Flattened collection of all external Rust functions, native or scripted.
|
||||
/// including those in sub-modules.
|
||||
all_functions: HashMap<u64, CallableFunction, StraightHasherBuilder>,
|
||||
all_functions: HashMap<NonZeroU64, CallableFunction, StraightHasherBuilder>,
|
||||
/// Iterator functions, keyed by the type producing the iterator.
|
||||
type_iterators: HashMap<TypeId, IteratorFn>,
|
||||
/// Flattened collection of iterator functions, including those in sub-modules.
|
||||
@ -344,7 +356,7 @@ impl Module {
|
||||
self.get_var(name).and_then(Dynamic::try_cast::<T>)
|
||||
}
|
||||
|
||||
/// Get a module variable as a [`Dynamic`][crate::Dynamic].
|
||||
/// Get a module variable as a [`Dynamic`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@ -387,16 +399,15 @@ impl Module {
|
||||
/// Get a reference to a namespace-qualified variable.
|
||||
/// Name and Position in `EvalAltResult` are None and must be set afterwards.
|
||||
///
|
||||
/// The [`u64`] hash is calculated by the function [`crate::calc_native_fn_hash`].
|
||||
/// The [`NonZeroU64`] hash is calculated by the function [`crate::calc_native_fn_hash`].
|
||||
#[inline(always)]
|
||||
pub(crate) fn get_qualified_var(&self, hash_var: u64) -> Result<&Dynamic, Box<EvalAltResult>> {
|
||||
if hash_var == 0 {
|
||||
Err(EvalAltResult::ErrorVariableNotFound(String::new(), Position::NONE).into())
|
||||
} else {
|
||||
self.all_variables.get(&hash_var).ok_or_else(|| {
|
||||
EvalAltResult::ErrorVariableNotFound(String::new(), Position::NONE).into()
|
||||
})
|
||||
}
|
||||
pub(crate) fn get_qualified_var(
|
||||
&self,
|
||||
hash_var: NonZeroU64,
|
||||
) -> Result<&Dynamic, Box<EvalAltResult>> {
|
||||
self.all_variables.get(&hash_var).ok_or_else(|| {
|
||||
EvalAltResult::ErrorVariableNotFound(String::new(), Position::NONE).into()
|
||||
})
|
||||
}
|
||||
|
||||
/// Set a script-defined function into the module.
|
||||
@ -404,12 +415,12 @@ impl Module {
|
||||
/// If there is an existing function of the same name and number of arguments, it is replaced.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline]
|
||||
pub(crate) fn set_script_fn(&mut self, fn_def: impl Into<Shared<ScriptFnDef>>) -> u64 {
|
||||
pub(crate) fn set_script_fn(&mut self, fn_def: impl Into<Shared<ScriptFnDef>>) -> NonZeroU64 {
|
||||
let fn_def = fn_def.into();
|
||||
|
||||
// None + function name + number of arguments.
|
||||
let num_params = fn_def.params.len();
|
||||
let hash_script = crate::calc_script_fn_hash(empty(), &fn_def.name, num_params);
|
||||
let hash_script = crate::calc_script_fn_hash(empty(), &fn_def.name, num_params).unwrap();
|
||||
let mut param_names: StaticVec<_> = fn_def.params.iter().cloned().collect();
|
||||
param_names.push("Dynamic".into());
|
||||
self.functions.insert(
|
||||
@ -419,8 +430,8 @@ impl Module {
|
||||
namespace: FnNamespace::Internal,
|
||||
access: fn_def.access,
|
||||
params: num_params,
|
||||
param_types: None,
|
||||
param_names: Some(param_names),
|
||||
param_types: Default::default(),
|
||||
param_names,
|
||||
func: fn_def.into(),
|
||||
},
|
||||
);
|
||||
@ -454,6 +465,24 @@ impl Module {
|
||||
.map(|FuncInfo { func, .. }| func.get_fn_def())
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the underlying [`HashMap`] of sub-modules.
|
||||
///
|
||||
/// ## Warning
|
||||
///
|
||||
/// By taking a mutable reference, it is assumed that some sub-modules will be modified.
|
||||
/// Thus the module is automatically set to be non-indexed.
|
||||
#[inline(always)]
|
||||
pub(crate) fn sub_modules_mut(&mut self) -> &mut HashMap<ImmutableString, Shared<Module>> {
|
||||
// We must assume that the user has changed the sub-modules
|
||||
// (otherwise why take a mutable reference?)
|
||||
self.all_functions.clear();
|
||||
self.all_variables.clear();
|
||||
self.all_type_iterators.clear();
|
||||
self.indexed = false;
|
||||
|
||||
&mut self.modules
|
||||
}
|
||||
|
||||
/// Does a sub-module exist in the module?
|
||||
///
|
||||
/// # Example
|
||||
@ -515,7 +544,7 @@ impl Module {
|
||||
|
||||
/// Does the particular Rust function exist in the module?
|
||||
///
|
||||
/// The [`u64`] hash is calculated by the function [`crate::calc_native_fn_hash`].
|
||||
/// The [`NonZeroU64`] hash is calculated by the function [`crate::calc_native_fn_hash`].
|
||||
/// It is also returned by the `set_fn_XXX` calls.
|
||||
///
|
||||
/// # Example
|
||||
@ -528,10 +557,8 @@ impl Module {
|
||||
/// assert!(module.contains_fn(hash, true));
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn contains_fn(&self, hash_fn: u64, public_only: bool) -> bool {
|
||||
if hash_fn == 0 {
|
||||
false
|
||||
} else if public_only {
|
||||
pub fn contains_fn(&self, hash_fn: NonZeroU64, public_only: bool) -> bool {
|
||||
if public_only {
|
||||
self.functions
|
||||
.get(&hash_fn)
|
||||
.map(|FuncInfo { access, .. }| access.is_public())
|
||||
@ -543,7 +570,7 @@ impl Module {
|
||||
|
||||
/// Update the metadata (parameter names/types and return type) of a registered function.
|
||||
///
|
||||
/// The [`u64`] hash is calculated either by the function [`crate::calc_native_fn_hash`] or
|
||||
/// The [`NonZeroU64`] hash is calculated either by the function [`crate::calc_native_fn_hash`] or
|
||||
/// the function [`crate::calc_script_fn_hash`].
|
||||
///
|
||||
/// Each parameter name/type pair should be a single string of the format: `var_name: type`.
|
||||
@ -552,20 +579,24 @@ impl Module {
|
||||
/// In other words, the number of entries should be one larger than the number of parameters.
|
||||
pub fn update_fn_metadata<'a>(
|
||||
&mut self,
|
||||
hash_fn: u64,
|
||||
hash_fn: NonZeroU64,
|
||||
arg_names: impl AsRef<[&'a str]>,
|
||||
) -> &mut Self {
|
||||
if let Some(f) = self.functions.get_mut(&hash_fn) {
|
||||
f.param_names = Some(arg_names.as_ref().iter().map(|&n| n.into()).collect());
|
||||
f.param_names = arg_names.as_ref().iter().map(|&n| n.into()).collect();
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Update the namespace of a registered function.
|
||||
///
|
||||
/// The [`u64`] hash is calculated either by the function [`crate::calc_native_fn_hash`] or
|
||||
/// The [`NonZeroU64`] hash is calculated either by the function [`crate::calc_native_fn_hash`] or
|
||||
/// the function [`crate::calc_script_fn_hash`].
|
||||
pub fn update_fn_namespace(&mut self, hash_fn: u64, namespace: FnNamespace) -> &mut Self {
|
||||
pub fn update_fn_namespace(
|
||||
&mut self,
|
||||
hash_fn: NonZeroU64,
|
||||
namespace: FnNamespace,
|
||||
) -> &mut Self {
|
||||
if let Some(f) = self.functions.get_mut(&hash_fn) {
|
||||
f.namespace = namespace;
|
||||
}
|
||||
@ -588,12 +619,13 @@ impl Module {
|
||||
arg_names: Option<&[&str]>,
|
||||
arg_types: &[TypeId],
|
||||
func: CallableFunction,
|
||||
) -> u64 {
|
||||
) -> NonZeroU64 {
|
||||
let name = name.into();
|
||||
|
||||
let hash_fn = crate::calc_native_fn_hash(empty(), &name, arg_types.iter().cloned());
|
||||
let hash_fn =
|
||||
crate::calc_native_fn_hash(empty(), &name, arg_types.iter().cloned()).unwrap();
|
||||
|
||||
let params = arg_types
|
||||
let param_types = arg_types
|
||||
.into_iter()
|
||||
.cloned()
|
||||
.map(|id| {
|
||||
@ -611,9 +643,13 @@ impl Module {
|
||||
name,
|
||||
namespace,
|
||||
access,
|
||||
params: params.len(),
|
||||
param_types: Some(params),
|
||||
param_names: arg_names.map(|p| p.iter().map(|&v| v.into()).collect()),
|
||||
params: param_types.len(),
|
||||
param_types,
|
||||
param_names: if let Some(p) = arg_names {
|
||||
p.iter().map(|&v| v.into()).collect()
|
||||
} else {
|
||||
Default::default()
|
||||
},
|
||||
func: func.into(),
|
||||
},
|
||||
);
|
||||
@ -624,7 +660,7 @@ impl Module {
|
||||
}
|
||||
|
||||
/// Set a Rust function taking a reference to the scripting [`Engine`][crate::Engine],
|
||||
/// the current set of functions, plus a list of mutable [`Dynamic`][crate::Dynamic] references
|
||||
/// the current set of functions, plus a list of mutable [`Dynamic`] references
|
||||
/// into the module, returning a hash key.
|
||||
///
|
||||
/// Use this to register a built-in function which must reference settings on the scripting
|
||||
@ -639,7 +675,7 @@ impl Module {
|
||||
///
|
||||
/// A list of [`TypeId`]'s is taken as the argument types.
|
||||
///
|
||||
/// Arguments are simply passed in as a mutable array of [`&mut Dynamic`][crate::Dynamic],
|
||||
/// Arguments are simply passed in as a mutable array of [`&mut Dynamic`],
|
||||
/// which is guaranteed to contain enough arguments of the correct types.
|
||||
///
|
||||
/// The function is assumed to be a _method_, meaning that the first argument should not be consumed.
|
||||
@ -697,7 +733,7 @@ impl Module {
|
||||
func: impl Fn(NativeCallContext, &mut FnCallArgs) -> Result<T, Box<EvalAltResult>>
|
||||
+ SendSync
|
||||
+ 'static,
|
||||
) -> u64 {
|
||||
) -> NonZeroU64 {
|
||||
let f =
|
||||
move |ctx: NativeCallContext, args: &mut FnCallArgs| func(ctx, args).map(Dynamic::from);
|
||||
|
||||
@ -733,7 +769,7 @@ impl Module {
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
func: impl Fn() -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
) -> NonZeroU64 {
|
||||
let f = move |_: NativeCallContext, _: &mut FnCallArgs| func().map(Dynamic::from);
|
||||
let arg_types = [];
|
||||
self.set_fn(
|
||||
@ -768,7 +804,7 @@ impl Module {
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
func: impl Fn(A) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
) -> NonZeroU64 {
|
||||
let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
|
||||
func(cast_arg::<A>(&mut args[0])).map(Dynamic::from)
|
||||
};
|
||||
@ -808,7 +844,7 @@ impl Module {
|
||||
name: impl Into<String>,
|
||||
namespace: FnNamespace,
|
||||
func: impl Fn(&mut A) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
) -> NonZeroU64 {
|
||||
let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
|
||||
func(&mut args[0].write_lock::<A>().unwrap()).map(Dynamic::from)
|
||||
};
|
||||
@ -847,7 +883,7 @@ impl Module {
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
func: impl Fn(&mut A) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
) -> NonZeroU64 {
|
||||
self.set_fn_1_mut(
|
||||
crate::engine::make_getter(&name.into()),
|
||||
FnNamespace::Global,
|
||||
@ -879,7 +915,7 @@ impl Module {
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
func: impl Fn(A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
) -> NonZeroU64 {
|
||||
let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
|
||||
let a = cast_arg::<A>(&mut args[0]);
|
||||
let b = cast_arg::<B>(&mut args[1]);
|
||||
@ -926,7 +962,7 @@ impl Module {
|
||||
name: impl Into<String>,
|
||||
namespace: FnNamespace,
|
||||
func: impl Fn(&mut A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
) -> NonZeroU64 {
|
||||
let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
|
||||
let b = cast_arg::<B>(&mut args[1]);
|
||||
let a = &mut args[0].write_lock::<A>().unwrap();
|
||||
@ -972,7 +1008,7 @@ impl Module {
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
func: impl Fn(&mut A, B) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
) -> NonZeroU64 {
|
||||
self.set_fn_2_mut(
|
||||
crate::engine::make_setter(&name.into()),
|
||||
FnNamespace::Global,
|
||||
@ -1011,7 +1047,7 @@ impl Module {
|
||||
pub fn set_indexer_get_fn<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
|
||||
&mut self,
|
||||
func: impl Fn(&mut A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
) -> NonZeroU64 {
|
||||
if TypeId::of::<A>() == TypeId::of::<Array>() {
|
||||
panic!("Cannot register indexer for arrays.");
|
||||
}
|
||||
@ -1058,7 +1094,7 @@ impl Module {
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
func: impl Fn(A, B, C) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
) -> NonZeroU64 {
|
||||
let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
|
||||
let a = cast_arg::<A>(&mut args[0]);
|
||||
let b = cast_arg::<B>(&mut args[1]);
|
||||
@ -1111,7 +1147,7 @@ impl Module {
|
||||
name: impl Into<String>,
|
||||
namespace: FnNamespace,
|
||||
func: impl Fn(&mut A, B, C) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
) -> NonZeroU64 {
|
||||
let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
|
||||
let b = cast_arg::<B>(&mut args[2]);
|
||||
let c = cast_arg::<C>(&mut args[3]);
|
||||
@ -1162,7 +1198,7 @@ impl Module {
|
||||
pub fn set_indexer_set_fn<A: Variant + Clone, B: Variant + Clone, C: Variant + Clone>(
|
||||
&mut self,
|
||||
func: impl Fn(&mut A, B, C) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
) -> NonZeroU64 {
|
||||
if TypeId::of::<A>() == TypeId::of::<Array>() {
|
||||
panic!("Cannot register indexer for arrays.");
|
||||
}
|
||||
@ -1234,7 +1270,7 @@ impl Module {
|
||||
&mut self,
|
||||
getter: impl Fn(&mut A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
|
||||
setter: impl Fn(&mut A, B, T) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
|
||||
) -> (u64, u64) {
|
||||
) -> (NonZeroU64, NonZeroU64) {
|
||||
(
|
||||
self.set_indexer_get_fn(getter),
|
||||
self.set_indexer_set_fn(setter),
|
||||
@ -1271,7 +1307,7 @@ impl Module {
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
func: impl Fn(A, B, C, D) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
) -> NonZeroU64 {
|
||||
let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
|
||||
let a = cast_arg::<A>(&mut args[0]);
|
||||
let b = cast_arg::<B>(&mut args[1]);
|
||||
@ -1331,7 +1367,7 @@ impl Module {
|
||||
name: impl Into<String>,
|
||||
namespace: FnNamespace,
|
||||
func: impl Fn(&mut A, B, C, D) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
) -> NonZeroU64 {
|
||||
let f = move |_: NativeCallContext, args: &mut FnCallArgs| {
|
||||
let b = cast_arg::<B>(&mut args[1]);
|
||||
let c = cast_arg::<C>(&mut args[2]);
|
||||
@ -1358,48 +1394,43 @@ impl Module {
|
||||
|
||||
/// Get a Rust function.
|
||||
///
|
||||
/// The [`u64`] hash is calculated by the function [`crate::calc_native_fn_hash`].
|
||||
/// The [`NonZeroU64`] hash is calculated by the function [`crate::calc_native_fn_hash`].
|
||||
/// It is also returned by the `set_fn_XXX` calls.
|
||||
#[inline(always)]
|
||||
pub(crate) fn get_fn(&self, hash_fn: u64, public_only: bool) -> Option<&CallableFunction> {
|
||||
if hash_fn == 0 {
|
||||
None
|
||||
} else {
|
||||
self.functions
|
||||
.get(&hash_fn)
|
||||
.and_then(|FuncInfo { access, func, .. }| match access {
|
||||
_ if !public_only => Some(func),
|
||||
FnAccess::Public => Some(func),
|
||||
FnAccess::Private => None,
|
||||
})
|
||||
}
|
||||
pub(crate) fn get_fn(
|
||||
&self,
|
||||
hash_fn: NonZeroU64,
|
||||
public_only: bool,
|
||||
) -> Option<&CallableFunction> {
|
||||
self.functions
|
||||
.get(&hash_fn)
|
||||
.and_then(|FuncInfo { access, func, .. }| match access {
|
||||
_ if !public_only => Some(func),
|
||||
FnAccess::Public => Some(func),
|
||||
FnAccess::Private => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Does the particular namespace-qualified function exist in the module?
|
||||
///
|
||||
/// The [`u64`] hash is calculated by the function [`crate::calc_native_fn_hash`] and must match
|
||||
/// The [`NonZeroU64`] hash is calculated by the function [`crate::calc_native_fn_hash`] and must match
|
||||
/// the hash calculated by [`build_index`][Module::build_index].
|
||||
#[inline]
|
||||
pub fn contains_qualified_fn(&self, hash_fn: u64) -> bool {
|
||||
if hash_fn == 0 {
|
||||
false
|
||||
} else {
|
||||
self.all_functions.contains_key(&hash_fn)
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn contains_qualified_fn(&self, hash_fn: NonZeroU64) -> bool {
|
||||
self.all_functions.contains_key(&hash_fn)
|
||||
}
|
||||
|
||||
/// Get a namespace-qualified function.
|
||||
/// Name and Position in `EvalAltResult` are None and must be set afterwards.
|
||||
///
|
||||
/// The [`u64`] hash is calculated by the function [`crate::calc_native_fn_hash`] and must match
|
||||
/// The [`NonZeroU64`] hash is calculated by the function [`crate::calc_native_fn_hash`] and must match
|
||||
/// the hash calculated by [`build_index`][Module::build_index].
|
||||
#[inline(always)]
|
||||
pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&CallableFunction> {
|
||||
if hash_qualified_fn == 0 {
|
||||
None
|
||||
} else {
|
||||
self.all_functions.get(&hash_qualified_fn)
|
||||
}
|
||||
pub(crate) fn get_qualified_fn(
|
||||
&self,
|
||||
hash_qualified_fn: NonZeroU64,
|
||||
) -> Option<&CallableFunction> {
|
||||
self.all_functions.get(&hash_qualified_fn)
|
||||
}
|
||||
|
||||
/// Combine another module into this module.
|
||||
@ -1686,7 +1717,7 @@ impl Module {
|
||||
ast: &crate::AST,
|
||||
engine: &crate::Engine,
|
||||
) -> Result<Self, Box<EvalAltResult>> {
|
||||
let mut mods = engine.global_sub_modules.clone();
|
||||
let mut mods: crate::engine::Imports = (&engine.global_sub_modules).into();
|
||||
let orig_mods_len = mods.len();
|
||||
|
||||
// Run the script
|
||||
@ -1745,8 +1776,8 @@ impl Module {
|
||||
fn index_module<'a>(
|
||||
module: &'a Module,
|
||||
qualifiers: &mut Vec<&'a str>,
|
||||
variables: &mut HashMap<u64, Dynamic, StraightHasherBuilder>,
|
||||
functions: &mut HashMap<u64, CallableFunction, StraightHasherBuilder>,
|
||||
variables: &mut HashMap<NonZeroU64, Dynamic, StraightHasherBuilder>,
|
||||
functions: &mut HashMap<NonZeroU64, CallableFunction, StraightHasherBuilder>,
|
||||
type_iterators: &mut HashMap<TypeId, IteratorFn>,
|
||||
) {
|
||||
module.modules.iter().for_each(|(name, m)| {
|
||||
@ -1760,7 +1791,7 @@ impl Module {
|
||||
module.variables.iter().for_each(|(var_name, value)| {
|
||||
// Qualifiers + variable name
|
||||
let hash_var =
|
||||
crate::calc_script_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0);
|
||||
crate::calc_script_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0).unwrap();
|
||||
variables.insert(hash_var, value.clone());
|
||||
});
|
||||
|
||||
@ -1781,7 +1812,7 @@ impl Module {
|
||||
name,
|
||||
namespace,
|
||||
params,
|
||||
param_types: types,
|
||||
param_types,
|
||||
func,
|
||||
..
|
||||
},
|
||||
@ -1793,9 +1824,10 @@ impl Module {
|
||||
|
||||
// Qualifiers + function name + number of arguments.
|
||||
let hash_qualified_script =
|
||||
crate::calc_script_fn_hash(qualifiers.iter().cloned(), name, *params);
|
||||
crate::calc_script_fn_hash(qualifiers.iter().cloned(), name, *params)
|
||||
.unwrap();
|
||||
|
||||
if let Some(param_types) = types {
|
||||
if !func.is_script() {
|
||||
assert_eq!(*params, param_types.len());
|
||||
|
||||
// Namespace-qualified Rust functions are indexed in two steps:
|
||||
@ -1807,9 +1839,11 @@ impl Module {
|
||||
empty(),
|
||||
"",
|
||||
param_types.iter().cloned(),
|
||||
);
|
||||
// 3) The final hash is the XOR of the two hashes.
|
||||
let hash_qualified_fn = hash_qualified_script ^ hash_fn_args;
|
||||
)
|
||||
.unwrap();
|
||||
// 3) The two hashes are combined.
|
||||
let hash_qualified_fn =
|
||||
combine_hashes(hash_qualified_script, hash_fn_args);
|
||||
|
||||
functions.insert(hash_qualified_fn, func.clone());
|
||||
} else if cfg!(not(feature = "no_function")) {
|
||||
@ -1897,7 +1931,7 @@ impl Module {
|
||||
/// _(INTERNALS)_ A chain of [module][Module] names to namespace-qualify a variable or function call.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// A [`u64`] hash key is cached for quick search purposes.
|
||||
/// A [`NonZeroU64`] offset to the current [`Scope`][crate::Scope] is cached for quick search purposes.
|
||||
///
|
||||
/// A [`StaticVec`] is used because most namespace-qualified access contains only one level,
|
||||
/// and it is wasteful to always allocate a [`Vec`] with one element.
|
||||
@ -1906,13 +1940,13 @@ impl Module {
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
#[derive(Clone, Eq, PartialEq, Default, Hash)]
|
||||
pub struct NamespaceRef(StaticVec<Ident>, Option<NonZeroUsize>);
|
||||
pub struct NamespaceRef(Option<NonZeroUsize>, StaticVec<Ident>);
|
||||
|
||||
impl fmt::Debug for NamespaceRef {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&self.0, f)?;
|
||||
fmt::Debug::fmt(&self.1, f)?;
|
||||
|
||||
if let Some(index) = self.1 {
|
||||
if let Some(index) = self.0 {
|
||||
write!(f, " -> {}", index)
|
||||
} else {
|
||||
Ok(())
|
||||
@ -1924,19 +1958,19 @@ impl Deref for NamespaceRef {
|
||||
type Target = StaticVec<Ident>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
&self.1
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for NamespaceRef {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
&mut self.1
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NamespaceRef {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for Ident { name, .. } in self.0.iter() {
|
||||
for Ident { name, .. } in self.1.iter() {
|
||||
write!(f, "{}{}", name, Token::DoubleColon.syntax())?;
|
||||
}
|
||||
Ok(())
|
||||
@ -1945,7 +1979,7 @@ impl fmt::Display for NamespaceRef {
|
||||
|
||||
impl From<StaticVec<Ident>> for NamespaceRef {
|
||||
fn from(modules: StaticVec<Ident>) -> Self {
|
||||
Self(modules, None)
|
||||
Self(None, modules)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1975,12 +2009,14 @@ impl<M: Into<Module>> AddAssign<M> for Module {
|
||||
}
|
||||
|
||||
impl NamespaceRef {
|
||||
/// Get the [`Scope`][crate::Scope] index offset.
|
||||
pub(crate) fn index(&self) -> Option<NonZeroUsize> {
|
||||
self.1
|
||||
self.0
|
||||
}
|
||||
/// Set the [`Scope`][crate::Scope] index offset.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub(crate) fn set_index(&mut self, index: Option<NonZeroUsize>) {
|
||||
self.1 = index
|
||||
self.0 = index
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
|
||||
/// collection.push(resolver);
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// engine.set_module_resolver(Some(collection));
|
||||
/// engine.set_module_resolver(collection);
|
||||
/// ```
|
||||
#[derive(Default)]
|
||||
pub struct ModuleResolversCollection(Vec<Box<dyn ModuleResolver>>);
|
||||
@ -36,16 +36,41 @@ impl ModuleResolversCollection {
|
||||
/// collection.push(resolver);
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// engine.set_module_resolver(Some(collection));
|
||||
/// engine.set_module_resolver(collection);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
/// Add a module keyed by its path.
|
||||
/// Append a module resolver to the end.
|
||||
#[inline(always)]
|
||||
pub fn push(&mut self, resolver: impl ModuleResolver + 'static) {
|
||||
pub fn push(&mut self, resolver: impl ModuleResolver + 'static) -> &mut Self {
|
||||
self.0.push(Box::new(resolver));
|
||||
self
|
||||
}
|
||||
/// Insert a module resolver to an offset index.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the index is out of bounds.
|
||||
#[inline(always)]
|
||||
pub fn insert(&mut self, index: usize, resolver: impl ModuleResolver + 'static) -> &mut Self {
|
||||
self.0.insert(index, Box::new(resolver));
|
||||
self
|
||||
}
|
||||
/// Remove the last module resolver from the end, if any.
|
||||
#[inline(always)]
|
||||
pub fn pop(&mut self) -> Option<Box<dyn ModuleResolver>> {
|
||||
self.0.pop()
|
||||
}
|
||||
/// Remove a module resolver at an offset index.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the index is out of bounds.
|
||||
#[inline(always)]
|
||||
pub fn remove(&mut self, index: usize) -> Box<dyn ModuleResolver> {
|
||||
self.0.remove(index)
|
||||
}
|
||||
/// Get an iterator of all the module resolvers.
|
||||
#[inline(always)]
|
||||
@ -59,8 +84,9 @@ impl ModuleResolversCollection {
|
||||
}
|
||||
/// Remove all module resolvers.
|
||||
#[inline(always)]
|
||||
pub fn clear(&mut self) {
|
||||
pub fn clear(&mut self) -> &mut Self {
|
||||
self.0.clear();
|
||||
self
|
||||
}
|
||||
/// Is this [`ModuleResolversCollection`] empty?
|
||||
#[inline(always)]
|
||||
@ -75,8 +101,9 @@ impl ModuleResolversCollection {
|
||||
/// Add another [`ModuleResolversCollection`] to the end of this collection.
|
||||
/// The other [`ModuleResolversCollection`] is consumed.
|
||||
#[inline(always)]
|
||||
pub fn append(&mut self, other: Self) {
|
||||
pub fn append(&mut self, other: Self) -> &mut Self {
|
||||
self.0.extend(other.0.into_iter());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
48
src/module/resolvers/dummy.rs
Normal file
48
src/module/resolvers/dummy.rs
Normal file
@ -0,0 +1,48 @@
|
||||
use crate::stdlib::boxed::Box;
|
||||
use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
|
||||
|
||||
/// Empty/disabled module resolution service that acts as a dummy.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::{Engine, Module};
|
||||
/// use rhai::module_resolvers::DummyModuleResolver;
|
||||
///
|
||||
/// let resolver = DummyModuleResolver::new();
|
||||
/// let mut engine = Engine::new();
|
||||
/// engine.set_module_resolver(resolver);
|
||||
/// ```
|
||||
#[derive(Debug, Copy, Eq, PartialEq, Clone, Default, Hash)]
|
||||
pub struct DummyModuleResolver;
|
||||
|
||||
impl DummyModuleResolver {
|
||||
/// Create a new [`DummyModuleResolver`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::{Engine, Module};
|
||||
/// use rhai::module_resolvers::DummyModuleResolver;
|
||||
///
|
||||
/// let resolver = DummyModuleResolver::new();
|
||||
/// let mut engine = Engine::new();
|
||||
/// engine.set_module_resolver(resolver);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleResolver for DummyModuleResolver {
|
||||
#[inline(always)]
|
||||
fn resolve(
|
||||
&self,
|
||||
_: &Engine,
|
||||
path: &str,
|
||||
pos: Position,
|
||||
) -> Result<Shared<Module>, Box<EvalAltResult>> {
|
||||
EvalAltResult::ErrorModuleNotFound(path.into(), pos).into()
|
||||
}
|
||||
}
|
@ -1,5 +1,9 @@
|
||||
use crate::stdlib::{
|
||||
boxed::Box, collections::HashMap, io::Error as IoError, path::PathBuf, string::String,
|
||||
boxed::Box,
|
||||
collections::HashMap,
|
||||
io::Error as IoError,
|
||||
path::{Path, PathBuf},
|
||||
string::String,
|
||||
};
|
||||
use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
|
||||
|
||||
@ -31,7 +35,7 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// engine.set_module_resolver(Some(resolver));
|
||||
/// engine.set_module_resolver(resolver);
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct FileModuleResolver {
|
||||
@ -65,10 +69,10 @@ impl FileModuleResolver {
|
||||
/// let resolver = FileModuleResolver::new_with_path("./scripts");
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// engine.set_module_resolver(Some(resolver));
|
||||
/// engine.set_module_resolver(resolver);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn new_with_path<P: Into<PathBuf>>(path: P) -> Self {
|
||||
pub fn new_with_path(path: impl Into<PathBuf>) -> Self {
|
||||
Self::new_with_path_and_extension(path, "rhai")
|
||||
}
|
||||
|
||||
@ -87,12 +91,12 @@ impl FileModuleResolver {
|
||||
/// let resolver = FileModuleResolver::new_with_path_and_extension("./scripts", "x");
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// engine.set_module_resolver(Some(resolver));
|
||||
/// engine.set_module_resolver(resolver);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn new_with_path_and_extension<P: Into<PathBuf>, E: Into<String>>(
|
||||
path: P,
|
||||
extension: E,
|
||||
pub fn new_with_path_and_extension(
|
||||
path: impl Into<PathBuf>,
|
||||
extension: impl Into<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
path: path.into(),
|
||||
@ -114,12 +118,64 @@ impl FileModuleResolver {
|
||||
/// let resolver = FileModuleResolver::new();
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// engine.set_module_resolver(Some(resolver));
|
||||
/// engine.set_module_resolver(resolver);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Get the base path for script files.
|
||||
#[inline(always)]
|
||||
pub fn path(&self) -> &Path {
|
||||
self.path.as_ref()
|
||||
}
|
||||
/// Set the base path for script files.
|
||||
#[inline(always)]
|
||||
pub fn set_path(&mut self, path: impl Into<PathBuf>) -> &mut Self {
|
||||
self.path = path.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the script file extension.
|
||||
#[inline(always)]
|
||||
pub fn extension(&self) -> &str {
|
||||
&self.extension
|
||||
}
|
||||
|
||||
/// Set the script file extension.
|
||||
#[inline(always)]
|
||||
pub fn set_extension(&mut self, extension: impl Into<String>) -> &mut Self {
|
||||
self.extension = extension.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Empty the internal cache.
|
||||
#[inline(always)]
|
||||
pub fn clear_cache(&mut self) {
|
||||
#[cfg(not(feature = "sync"))]
|
||||
self.cache.borrow_mut().clear();
|
||||
#[cfg(feature = "sync")]
|
||||
self.cache.write().unwrap().clear();
|
||||
}
|
||||
|
||||
/// Empty the internal cache.
|
||||
#[inline(always)]
|
||||
pub fn clear_cache_for_path(&mut self, path: impl AsRef<Path>) -> Option<Shared<Module>> {
|
||||
#[cfg(not(feature = "sync"))]
|
||||
return self
|
||||
.cache
|
||||
.borrow_mut()
|
||||
.remove_entry(path.as_ref())
|
||||
.map(|(_, v)| v);
|
||||
#[cfg(feature = "sync")]
|
||||
return self
|
||||
.cache
|
||||
.write()
|
||||
.unwrap()
|
||||
.remove_entry(path.as_ref())
|
||||
.map(|(_, v)| v);
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleResolver for FileModuleResolver {
|
||||
|
@ -2,6 +2,9 @@ use crate::fn_native::SendSync;
|
||||
use crate::stdlib::boxed::Box;
|
||||
use crate::{Engine, EvalAltResult, Module, Position, Shared};
|
||||
|
||||
mod dummy;
|
||||
pub use dummy::DummyModuleResolver;
|
||||
|
||||
mod collection;
|
||||
pub use collection::ModuleResolversCollection;
|
||||
|
||||
|
@ -16,7 +16,7 @@ use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// engine.set_module_resolver(Some(resolver));
|
||||
/// engine.set_module_resolver(resolver);
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct StaticModuleResolver(HashMap<String, Shared<Module>>);
|
||||
@ -36,7 +36,7 @@ impl StaticModuleResolver {
|
||||
/// resolver.insert("hello", module);
|
||||
///
|
||||
/// let mut engine = Engine::new();
|
||||
/// engine.set_module_resolver(Some(resolver));
|
||||
/// engine.set_module_resolver(resolver);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn new() -> Self {
|
||||
@ -60,8 +60,13 @@ impl StaticModuleResolver {
|
||||
}
|
||||
/// Get an iterator of all the modules.
|
||||
#[inline(always)]
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&str, Shared<Module>)> {
|
||||
self.0.iter().map(|(k, v)| (k.as_str(), v.clone()))
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&str, &Shared<Module>)> {
|
||||
self.0.iter().map(|(k, v)| (k.as_str(), v))
|
||||
}
|
||||
/// Get a mutable iterator of all the modules.
|
||||
#[inline(always)]
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut Shared<Module>)> {
|
||||
self.0.iter_mut().map(|(k, v)| (k.as_str(), v))
|
||||
}
|
||||
/// Get a mutable iterator of all the modules.
|
||||
#[inline(always)]
|
||||
@ -75,8 +80,8 @@ impl StaticModuleResolver {
|
||||
}
|
||||
/// Get an iterator of all the modules.
|
||||
#[inline(always)]
|
||||
pub fn values<'a>(&'a self) -> impl Iterator<Item = Shared<Module>> + 'a {
|
||||
self.0.values().map(|m| m.clone())
|
||||
pub fn values(&self) -> impl Iterator<Item = &Shared<Module>> {
|
||||
self.0.values().map(|m| m)
|
||||
}
|
||||
/// Remove all modules.
|
||||
#[inline(always)]
|
||||
@ -95,6 +100,8 @@ impl StaticModuleResolver {
|
||||
}
|
||||
/// Merge another [`StaticModuleResolver`] into this.
|
||||
/// The other [`StaticModuleResolver`] is consumed.
|
||||
///
|
||||
/// Existing modules of the same path name are overwritten.
|
||||
#[inline(always)]
|
||||
pub fn merge(&mut self, other: Self) {
|
||||
if !other.is_empty() {
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
use crate::ast::{Expr, ScriptFnDef, Stmt};
|
||||
use crate::dynamic::AccessMode;
|
||||
use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF};
|
||||
use crate::engine::{Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF};
|
||||
use crate::fn_call::run_builtin_binary_op;
|
||||
use crate::parser::map_dynamic_to_expr;
|
||||
use crate::stdlib::{
|
||||
@ -62,6 +62,8 @@ struct State<'a> {
|
||||
variables: Vec<(String, AccessMode, Expr)>,
|
||||
/// An [`Engine`] instance for eager function evaluation.
|
||||
engine: &'a Engine,
|
||||
/// Collection of sub-modules.
|
||||
mods: Imports,
|
||||
/// [Module] containing script-defined functions.
|
||||
lib: &'a [&'a Module],
|
||||
/// Optimization level.
|
||||
@ -76,6 +78,7 @@ impl<'a> State<'a> {
|
||||
changed: false,
|
||||
variables: vec![],
|
||||
engine,
|
||||
mods: (&engine.global_sub_modules).into(),
|
||||
lib,
|
||||
optimization_level: level,
|
||||
}
|
||||
@ -138,7 +141,7 @@ fn call_fn_with_constant_arguments(
|
||||
&mut Default::default(),
|
||||
state.lib,
|
||||
fn_name,
|
||||
hash_fn,
|
||||
hash_fn.unwrap(),
|
||||
arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(),
|
||||
false,
|
||||
true,
|
||||
@ -149,7 +152,7 @@ fn call_fn_with_constant_arguments(
|
||||
.map(|(v, _)| v)
|
||||
}
|
||||
|
||||
/// Optimize a block of [statements][crate::ast::Stmt].
|
||||
/// Optimize a block of [statements][Stmt].
|
||||
fn optimize_stmt_block(
|
||||
mut statements: Vec<Stmt>,
|
||||
pos: Position,
|
||||
@ -274,7 +277,7 @@ fn optimize_stmt_block(
|
||||
}
|
||||
}
|
||||
|
||||
/// Optimize a [statement][crate::ast::Stmt].
|
||||
/// Optimize a [statement][Stmt].
|
||||
fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
||||
match stmt {
|
||||
// expr op= expr
|
||||
@ -470,7 +473,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Optimize an [expression][crate::ast::Expr].
|
||||
/// Optimize an [expression][Expr].
|
||||
fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
||||
// These keywords are handled specially
|
||||
const DONT_EVAL_KEYWORDS: &[&str] = &[
|
||||
@ -501,7 +504,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
||||
Expr::Dot(x, _) => match (&mut x.lhs, &mut x.rhs) {
|
||||
// map.string
|
||||
(Expr::Map(m, pos), Expr::Property(p)) if m.iter().all(|(_, x)| x.is_pure()) => {
|
||||
let prop = &p.1.name;
|
||||
let prop = &p.2.name;
|
||||
// Map literal where everything is pure - promote the indexed item.
|
||||
// All other items can be thrown away.
|
||||
state.set_dirty();
|
||||
@ -655,7 +658,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
||||
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
|
||||
|
||||
// Search for overloaded operators (can override built-in).
|
||||
if !state.engine.has_override_by_name_and_arguments(Some(&state.engine.global_sub_modules), state.lib, x.name.as_ref(), arg_types.as_ref(), false) {
|
||||
if !state.engine.has_override_by_name_and_arguments(Some(&state.mods), state.lib, x.name.as_ref(), arg_types.as_ref(), false) {
|
||||
if let Some(result) = run_builtin_binary_op(x.name.as_ref(), &arg_values[0], &arg_values[1])
|
||||
.ok().flatten()
|
||||
.and_then(|result| map_dynamic_to_expr(result, *pos))
|
||||
@ -717,12 +720,12 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
||||
Expr::FnCall(x, _) => x.args.iter_mut().for_each(|a| optimize_expr(a, state)),
|
||||
|
||||
// constant-name
|
||||
Expr::Variable(x) if x.1.is_none() && state.find_constant(&x.3.name).is_some() => {
|
||||
Expr::Variable(x) if x.1.is_none() && state.find_constant(&x.2.name).is_some() => {
|
||||
state.set_dirty();
|
||||
|
||||
// Replace constant with value
|
||||
let mut result = state.find_constant(&x.3.name).unwrap().clone();
|
||||
result.set_position(x.3.pos);
|
||||
let mut result = state.find_constant(&x.2.name).unwrap().clone();
|
||||
result.set_position(x.2.pos);
|
||||
*expr = result;
|
||||
}
|
||||
|
||||
@ -734,7 +737,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Optimize a block of [statements][crate::ast::Stmt] at top level.
|
||||
/// Optimize a block of [statements][Stmt] at top level.
|
||||
fn optimize_top_level(
|
||||
mut statements: Vec<Stmt>,
|
||||
engine: &Engine,
|
||||
|
@ -32,7 +32,7 @@ mod fn_ptr_functions {
|
||||
} else {
|
||||
let hash_script = calc_script_fn_hash(empty(), fn_name, num_params as usize);
|
||||
ctx.engine()
|
||||
.has_override(ctx.mods, ctx.lib, 0, hash_script, true)
|
||||
.has_override(ctx.mods, ctx.lib, None, hash_script, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -91,13 +91,16 @@ pub enum ParseErrorType {
|
||||
UnknownOperator(String),
|
||||
/// Expecting a particular token but not finding one. Wrapped values are the token and description.
|
||||
MissingToken(String, String),
|
||||
/// An expression in function call arguments `()` has syntax error. Wrapped value is the error description (if any).
|
||||
/// An expression in function call arguments `()` has syntax error. Wrapped value is the error
|
||||
/// description (if any).
|
||||
MalformedCallExpr(String),
|
||||
/// An expression in indexing brackets `[]` has syntax error. Wrapped value is the error description (if any).
|
||||
/// An expression in indexing brackets `[]` has syntax error. Wrapped value is the error
|
||||
/// description (if any).
|
||||
///
|
||||
/// Never appears under the `no_index` feature.
|
||||
MalformedIndexExpr(String),
|
||||
/// An expression in an `in` expression has syntax error. Wrapped value is the error description (if any).
|
||||
/// An expression in an `in` expression has syntax error. Wrapped value is the error description
|
||||
/// (if any).
|
||||
///
|
||||
/// Never appears under the `no_object` and `no_index` features combination.
|
||||
MalformedInExpr(String),
|
||||
@ -137,7 +140,8 @@ pub enum ParseErrorType {
|
||||
///
|
||||
/// Never appears under the `no_function` feature.
|
||||
FnMissingParams(String),
|
||||
/// A function definition has duplicated parameters. Wrapped values are the function name and parameter name.
|
||||
/// A function definition has duplicated parameters. Wrapped values are the function name and
|
||||
/// parameter name.
|
||||
///
|
||||
/// Never appears under the `no_function` feature.
|
||||
FnDuplicatedParam(String, String),
|
||||
|
564
src/parser.rs
564
src/parser.rs
@ -13,7 +13,7 @@ use crate::stdlib::{
|
||||
format,
|
||||
hash::{Hash, Hasher},
|
||||
iter::empty,
|
||||
num::NonZeroUsize,
|
||||
num::{NonZeroU64, NonZeroUsize},
|
||||
string::{String, ToString},
|
||||
vec,
|
||||
vec::Vec,
|
||||
@ -34,7 +34,7 @@ use crate::FnAccess;
|
||||
|
||||
type PERR = ParseErrorType;
|
||||
|
||||
type FunctionsLib = HashMap<u64, ScriptFnDef, StraightHasherBuilder>;
|
||||
type FunctionsLib = HashMap<NonZeroU64, ScriptFnDef, StraightHasherBuilder>;
|
||||
|
||||
/// A type that encapsulates the current state of the parser.
|
||||
#[derive(Debug)]
|
||||
@ -238,10 +238,10 @@ impl Expr {
|
||||
fn into_property(self, state: &mut ParseState) -> Self {
|
||||
match self {
|
||||
Self::Variable(x) if x.1.is_none() => {
|
||||
let ident = x.3;
|
||||
let ident = x.2;
|
||||
let getter = state.get_interned_string(crate::engine::make_getter(&ident.name));
|
||||
let setter = state.get_interned_string(crate::engine::make_setter(&ident.name));
|
||||
Self::Property(Box::new(((getter, setter), ident.into())))
|
||||
Self::Property(Box::new((getter, setter, ident.into())))
|
||||
}
|
||||
_ => self,
|
||||
}
|
||||
@ -278,8 +278,11 @@ fn parse_paren_expr(
|
||||
input: &mut TokenStream,
|
||||
state: &mut ParseState,
|
||||
lib: &mut FunctionsLib,
|
||||
settings: ParseSettings,
|
||||
mut settings: ParseSettings,
|
||||
) -> Result<Expr, ParseError> {
|
||||
// ( ...
|
||||
settings.pos = eat_token(input, Token::LeftParen);
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||
|
||||
@ -310,7 +313,7 @@ fn parse_fn_call(
|
||||
lib: &mut FunctionsLib,
|
||||
id: ImmutableString,
|
||||
capture: bool,
|
||||
mut namespace: Option<Box<NamespaceRef>>,
|
||||
mut namespace: Option<NamespaceRef>,
|
||||
settings: ParseSettings,
|
||||
) -> Result<Expr, ParseError> {
|
||||
let (token, token_pos) = input.peek().unwrap();
|
||||
@ -335,7 +338,7 @@ fn parse_fn_call(
|
||||
Token::RightParen => {
|
||||
eat_token(input, Token::RightParen);
|
||||
|
||||
let hash_script = if let Some(modules) = namespace.as_mut() {
|
||||
let mut hash_script = if let Some(ref mut modules) = namespace {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
modules.set_index(state.find_module(&modules[0].name));
|
||||
|
||||
@ -352,13 +355,17 @@ fn parse_fn_call(
|
||||
calc_script_fn_hash(empty(), &id, 0)
|
||||
};
|
||||
|
||||
// script functions can only be valid identifiers
|
||||
if !is_valid_identifier(id.chars()) {
|
||||
hash_script = None;
|
||||
}
|
||||
|
||||
return Ok(Expr::FnCall(
|
||||
Box::new(FnCallExpr {
|
||||
name: id.to_string().into(),
|
||||
native_only: !is_valid_identifier(id.chars()), // script functions can only be valid identifiers
|
||||
capture,
|
||||
namespace,
|
||||
hash: hash_script,
|
||||
hash_script,
|
||||
args,
|
||||
..Default::default()
|
||||
}),
|
||||
@ -383,7 +390,7 @@ fn parse_fn_call(
|
||||
(Token::RightParen, _) => {
|
||||
eat_token(input, Token::RightParen);
|
||||
|
||||
let hash_script = if let Some(modules) = namespace.as_mut() {
|
||||
let mut hash_script = if let Some(modules) = namespace.as_mut() {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
modules.set_index(state.find_module(&modules[0].name));
|
||||
|
||||
@ -400,13 +407,17 @@ fn parse_fn_call(
|
||||
calc_script_fn_hash(empty(), &id, args.len())
|
||||
};
|
||||
|
||||
// script functions can only be valid identifiers
|
||||
if !is_valid_identifier(id.chars()) {
|
||||
hash_script = None;
|
||||
}
|
||||
|
||||
return Ok(Expr::FnCall(
|
||||
Box::new(FnCallExpr {
|
||||
name: id.to_string().into(),
|
||||
native_only: !is_valid_identifier(id.chars()), // script functions can only be valid identifiers
|
||||
capture,
|
||||
namespace,
|
||||
hash: hash_script,
|
||||
hash_script,
|
||||
args,
|
||||
..Default::default()
|
||||
}),
|
||||
@ -629,8 +640,11 @@ fn parse_array_literal(
|
||||
input: &mut TokenStream,
|
||||
state: &mut ParseState,
|
||||
lib: &mut FunctionsLib,
|
||||
settings: ParseSettings,
|
||||
mut settings: ParseSettings,
|
||||
) -> Result<Expr, ParseError> {
|
||||
// [ ...
|
||||
settings.pos = eat_token(input, Token::LeftBracket);
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||
|
||||
@ -696,8 +710,11 @@ fn parse_map_literal(
|
||||
input: &mut TokenStream,
|
||||
state: &mut ParseState,
|
||||
lib: &mut FunctionsLib,
|
||||
settings: ParseSettings,
|
||||
mut settings: ParseSettings,
|
||||
) -> Result<Expr, ParseError> {
|
||||
// #{ ...
|
||||
settings.pos = eat_token(input, Token::MapStart);
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||
|
||||
@ -939,105 +956,196 @@ fn parse_primary(
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||
|
||||
let (token, _) = match token {
|
||||
let mut root_expr = match token {
|
||||
Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)),
|
||||
|
||||
Token::IntegerConstant(_)
|
||||
| Token::CharConstant(_)
|
||||
| Token::StringConstant(_)
|
||||
| Token::True
|
||||
| Token::False => match input.next().unwrap().0 {
|
||||
Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos),
|
||||
Token::CharConstant(c) => Expr::CharConstant(c, settings.pos),
|
||||
Token::StringConstant(s) => {
|
||||
Expr::StringConstant(state.get_interned_string(s), settings.pos)
|
||||
}
|
||||
Token::True => Expr::BoolConstant(true, settings.pos),
|
||||
Token::False => Expr::BoolConstant(false, settings.pos),
|
||||
_ => unreachable!(),
|
||||
},
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Token::FloatConstant(x) => {
|
||||
let x = *x;
|
||||
input.next().unwrap();
|
||||
Expr::FloatConstant(x, settings.pos)
|
||||
}
|
||||
|
||||
// { - block statement as expression
|
||||
Token::LeftBrace if settings.allow_stmt_expr => {
|
||||
return parse_block(input, state, lib, settings.level_up()).map(|block| match block {
|
||||
match parse_block(input, state, lib, settings.level_up())? {
|
||||
Stmt::Block(statements, pos) => Expr::Stmt(Box::new(statements.into()), pos),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)),
|
||||
_ => input.next().unwrap(),
|
||||
};
|
||||
|
||||
let (next_token, _) = input.peek().unwrap();
|
||||
|
||||
let mut root_expr = match token {
|
||||
Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Token::FloatConstant(x) => Expr::FloatConstant(x, settings.pos),
|
||||
Token::CharConstant(c) => Expr::CharConstant(c, settings.pos),
|
||||
Token::StringConstant(s) => {
|
||||
Expr::StringConstant(state.get_interned_string(s), settings.pos)
|
||||
}
|
||||
|
||||
// Function call
|
||||
Token::Identifier(s) if *next_token == Token::LeftParen || *next_token == Token::Bang => {
|
||||
// Once the identifier consumed we must enable next variables capturing
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
{
|
||||
state.allow_capture = true;
|
||||
}
|
||||
let var_name_def = Ident {
|
||||
name: state.get_interned_string(s),
|
||||
pos: settings.pos,
|
||||
};
|
||||
Expr::Variable(Box::new((None, None, 0, var_name_def)))
|
||||
}
|
||||
// Namespace qualification
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Token::Identifier(s) if *next_token == Token::DoubleColon => {
|
||||
// Once the identifier consumed we must enable next variables capturing
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
{
|
||||
state.allow_capture = true;
|
||||
}
|
||||
let var_name_def = Ident {
|
||||
name: state.get_interned_string(s),
|
||||
pos: settings.pos,
|
||||
};
|
||||
Expr::Variable(Box::new((None, None, 0, var_name_def)))
|
||||
}
|
||||
// Normal variable access
|
||||
Token::Identifier(s) => {
|
||||
let index = state.access_var(&s, settings.pos);
|
||||
let var_name_def = Ident {
|
||||
name: state.get_interned_string(s),
|
||||
pos: settings.pos,
|
||||
};
|
||||
Expr::Variable(Box::new((index, None, 0, var_name_def)))
|
||||
}
|
||||
|
||||
// Function call is allowed to have reserved keyword
|
||||
Token::Reserved(s) if *next_token == Token::LeftParen || *next_token == Token::Bang => {
|
||||
if is_keyword_function(&s) {
|
||||
let var_name_def = Ident {
|
||||
name: state.get_interned_string(s),
|
||||
pos: settings.pos,
|
||||
};
|
||||
Expr::Variable(Box::new((None, None, 0, var_name_def)))
|
||||
} else {
|
||||
return Err(PERR::Reserved(s).into_err(settings.pos));
|
||||
}
|
||||
}
|
||||
|
||||
// Access to `this` as a variable is OK
|
||||
Token::Reserved(s) if s == KEYWORD_THIS && *next_token != Token::LeftParen => {
|
||||
if !settings.is_function_scope {
|
||||
let msg = format!("'{}' can only be used in functions", s);
|
||||
return Err(LexError::ImproperSymbol(s, msg).into_err(settings.pos));
|
||||
} else {
|
||||
let var_name_def = Ident {
|
||||
name: state.get_interned_string(s),
|
||||
pos: settings.pos,
|
||||
};
|
||||
Expr::Variable(Box::new((None, None, 0, var_name_def)))
|
||||
}
|
||||
}
|
||||
|
||||
Token::Reserved(s) if is_valid_identifier(s.chars()) => {
|
||||
return Err(PERR::Reserved(s).into_err(settings.pos));
|
||||
}
|
||||
|
||||
// ( - grouped expression
|
||||
Token::LeftParen => parse_paren_expr(input, state, lib, settings.level_up())?,
|
||||
|
||||
// If statement is allowed to act as expressions
|
||||
Token::If if settings.allow_if_expr => Expr::Stmt(
|
||||
Box::new(vec![parse_if(input, state, lib, settings.level_up())?].into()),
|
||||
settings.pos,
|
||||
),
|
||||
// Switch statement is allowed to act as expressions
|
||||
Token::Switch if settings.allow_switch_expr => Expr::Stmt(
|
||||
Box::new(vec![parse_switch(input, state, lib, settings.level_up())?].into()),
|
||||
settings.pos,
|
||||
),
|
||||
// | ...
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Token::Pipe | Token::Or if settings.allow_anonymous_fn => {
|
||||
let mut new_state = ParseState::new(
|
||||
state.engine,
|
||||
state.script_hash,
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
state.max_function_expr_depth,
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
state.max_function_expr_depth,
|
||||
);
|
||||
|
||||
let settings = ParseSettings {
|
||||
allow_if_expr: true,
|
||||
allow_switch_expr: true,
|
||||
allow_stmt_expr: true,
|
||||
allow_anonymous_fn: true,
|
||||
is_global: false,
|
||||
is_function_scope: true,
|
||||
is_breakable: false,
|
||||
level: 0,
|
||||
pos: settings.pos,
|
||||
};
|
||||
|
||||
let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?;
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
new_state.externals.iter().for_each(|(closure, pos)| {
|
||||
state.access_var(closure, *pos);
|
||||
});
|
||||
|
||||
// Qualifiers (none) + function name + number of arguments.
|
||||
let hash = calc_script_fn_hash(empty(), &func.name, func.params.len()).unwrap();
|
||||
|
||||
lib.insert(hash, func);
|
||||
|
||||
expr
|
||||
}
|
||||
|
||||
// Array literal
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Token::LeftBracket => parse_array_literal(input, state, lib, settings.level_up())?,
|
||||
|
||||
// Map literal
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Token::MapStart => parse_map_literal(input, state, lib, settings.level_up())?,
|
||||
Token::True => Expr::BoolConstant(true, settings.pos),
|
||||
Token::False => Expr::BoolConstant(false, settings.pos),
|
||||
Token::LexError(err) => return Err(err.into_err(settings.pos)),
|
||||
|
||||
// Identifier
|
||||
Token::Identifier(_) => {
|
||||
let s = match input.next().unwrap().0 {
|
||||
Token::Identifier(s) => s,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
match input.peek().unwrap().0 {
|
||||
// Function call
|
||||
Token::LeftParen | Token::Bang => {
|
||||
// Once the identifier consumed we must enable next variables capturing
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
{
|
||||
state.allow_capture = true;
|
||||
}
|
||||
let var_name_def = Ident {
|
||||
name: state.get_interned_string(s),
|
||||
pos: settings.pos,
|
||||
};
|
||||
Expr::Variable(Box::new((None, None, var_name_def)))
|
||||
}
|
||||
// Namespace qualification
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Token::DoubleColon => {
|
||||
// Once the identifier consumed we must enable next variables capturing
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
{
|
||||
state.allow_capture = true;
|
||||
}
|
||||
let var_name_def = Ident {
|
||||
name: state.get_interned_string(s),
|
||||
pos: settings.pos,
|
||||
};
|
||||
Expr::Variable(Box::new((None, None, var_name_def)))
|
||||
}
|
||||
// Normal variable access
|
||||
_ => {
|
||||
let index = state.access_var(&s, settings.pos);
|
||||
let var_name_def = Ident {
|
||||
name: state.get_interned_string(s),
|
||||
pos: settings.pos,
|
||||
};
|
||||
Expr::Variable(Box::new((index, None, var_name_def)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reserved keyword or symbol
|
||||
Token::Reserved(_) => {
|
||||
let s = match input.next().unwrap().0 {
|
||||
Token::Reserved(s) => s,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
match input.peek().unwrap().0 {
|
||||
// Function call is allowed to have reserved keyword
|
||||
Token::LeftParen | Token::Bang => {
|
||||
if s == KEYWORD_THIS {
|
||||
return Err(PERR::Reserved(s).into_err(settings.pos));
|
||||
} else if is_keyword_function(&s) {
|
||||
let var_name_def = Ident {
|
||||
name: state.get_interned_string(s),
|
||||
pos: settings.pos,
|
||||
};
|
||||
Expr::Variable(Box::new((None, None, var_name_def)))
|
||||
} else {
|
||||
return Err(PERR::Reserved(s).into_err(settings.pos));
|
||||
}
|
||||
}
|
||||
// Access to `this` as a variable is OK
|
||||
_ if s == KEYWORD_THIS => {
|
||||
if !settings.is_function_scope {
|
||||
let msg = format!("'{}' can only be used in functions", s);
|
||||
return Err(LexError::ImproperSymbol(s, msg).into_err(settings.pos));
|
||||
} else {
|
||||
let var_name_def = Ident {
|
||||
name: state.get_interned_string(s),
|
||||
pos: settings.pos,
|
||||
};
|
||||
Expr::Variable(Box::new((None, None, var_name_def)))
|
||||
}
|
||||
}
|
||||
_ if is_valid_identifier(s.chars()) => {
|
||||
return Err(PERR::Reserved(s).into_err(settings.pos));
|
||||
}
|
||||
_ => {
|
||||
return Err(LexError::UnexpectedInput(s).into_err(settings.pos));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Token::LexError(_) => {
|
||||
let err = match input.next().unwrap().0 {
|
||||
Token::LexError(err) => err,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
return Err(err.into_err(settings.pos));
|
||||
}
|
||||
|
||||
_ => {
|
||||
return Err(
|
||||
@ -1048,26 +1156,26 @@ fn parse_primary(
|
||||
|
||||
// Tail processing all possible postfix operators
|
||||
loop {
|
||||
let (token, _) = input.peek().unwrap();
|
||||
let (tail_token, _) = input.peek().unwrap();
|
||||
|
||||
if !root_expr.is_valid_postfix(token) {
|
||||
if !root_expr.is_valid_postfix(tail_token) {
|
||||
break;
|
||||
}
|
||||
|
||||
let (token, token_pos) = input.next().unwrap();
|
||||
settings.pos = token_pos;
|
||||
let (tail_token, tail_pos) = input.next().unwrap();
|
||||
settings.pos = tail_pos;
|
||||
|
||||
root_expr = match (root_expr, token) {
|
||||
root_expr = match (root_expr, tail_token) {
|
||||
// Qualified function call with !
|
||||
(Expr::Variable(x), Token::Bang) if x.1.is_some() => {
|
||||
return Err(if !match_token(input, Token::LeftParen).0 {
|
||||
LexError::UnexpectedInput(Token::Bang.syntax().to_string()).into_err(token_pos)
|
||||
LexError::UnexpectedInput(Token::Bang.syntax().to_string()).into_err(tail_pos)
|
||||
} else {
|
||||
LexError::ImproperSymbol(
|
||||
"!".to_string(),
|
||||
"'!' cannot be used to call module functions".to_string(),
|
||||
)
|
||||
.into_err(token_pos)
|
||||
.into_err(tail_pos)
|
||||
});
|
||||
}
|
||||
// Function call with !
|
||||
@ -1081,35 +1189,38 @@ fn parse_primary(
|
||||
.into_err(pos));
|
||||
}
|
||||
|
||||
let (_, modules, _, Ident { name, pos }) = *x;
|
||||
let (_, namespace, Ident { name, pos }) = *x;
|
||||
settings.pos = pos;
|
||||
parse_fn_call(input, state, lib, name, true, modules, settings.level_up())?
|
||||
let ns = namespace.map(|(_, ns)| ns);
|
||||
parse_fn_call(input, state, lib, name, true, ns, settings.level_up())?
|
||||
}
|
||||
// Function call
|
||||
(Expr::Variable(x), Token::LeftParen) => {
|
||||
let (_, modules, _, Ident { name, pos }) = *x;
|
||||
let (_, namespace, Ident { name, pos }) = *x;
|
||||
settings.pos = pos;
|
||||
parse_fn_call(input, state, lib, name, false, modules, settings.level_up())?
|
||||
let ns = namespace.map(|(_, ns)| ns);
|
||||
parse_fn_call(input, state, lib, name, false, ns, settings.level_up())?
|
||||
}
|
||||
(Expr::Property(_), _) => unreachable!(),
|
||||
// module access
|
||||
(Expr::Variable(x), Token::DoubleColon) => match input.next().unwrap() {
|
||||
(Token::Identifier(id2), pos2) => {
|
||||
let (index, mut modules, _, var_name_def) = *x;
|
||||
let (index, mut namespace, var_name_def) = *x;
|
||||
|
||||
if let Some(ref mut modules) = modules {
|
||||
modules.push(var_name_def);
|
||||
if let Some((_, ref mut namespace)) = namespace {
|
||||
namespace.push(var_name_def);
|
||||
} else {
|
||||
let mut m: NamespaceRef = Default::default();
|
||||
m.push(var_name_def);
|
||||
modules = Some(Box::new(m));
|
||||
let mut ns: NamespaceRef = Default::default();
|
||||
ns.push(var_name_def);
|
||||
let index = NonZeroU64::new(42).unwrap(); // Dummy
|
||||
namespace = Some((index, ns));
|
||||
}
|
||||
|
||||
let var_name_def = Ident {
|
||||
name: state.get_interned_string(id2),
|
||||
pos: pos2,
|
||||
};
|
||||
Expr::Variable(Box::new((index, modules, 0, var_name_def)))
|
||||
Expr::Variable(Box::new((index, namespace, var_name_def)))
|
||||
}
|
||||
(Token::Reserved(id2), pos2) if is_valid_identifier(id2.chars()) => {
|
||||
return Err(PERR::Reserved(id2).into_err(pos2));
|
||||
@ -1121,11 +1232,17 @@ fn parse_primary(
|
||||
(expr, Token::LeftBracket) => {
|
||||
parse_index_chain(input, state, lib, expr, settings.level_up())?
|
||||
}
|
||||
// Method access
|
||||
// Property access
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
(expr, Token::Period) => {
|
||||
// prevents capturing of the object properties as vars: xxx.<var>
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
if let (Token::Identifier(_), _) = input.peek().unwrap() {
|
||||
state.allow_capture = false;
|
||||
}
|
||||
|
||||
let rhs = parse_unary(input, state, lib, settings.level_up())?;
|
||||
make_dot_expr(state, expr, rhs, token_pos)?
|
||||
make_dot_expr(state, expr, rhs, tail_pos)?
|
||||
}
|
||||
// Unknown postfix operator
|
||||
(expr, token) => unreachable!(
|
||||
@ -1146,14 +1263,16 @@ fn parse_primary(
|
||||
_ => None,
|
||||
}
|
||||
.map(|x| {
|
||||
let (_, modules, hash, Ident { name, .. }) = x.as_mut();
|
||||
let namespace = modules.as_mut().unwrap();
|
||||
if let (_, Some((ref mut hash, ref mut namespace)), Ident { name, .. }) = x.as_mut() {
|
||||
// Qualifiers + variable name
|
||||
*hash =
|
||||
calc_script_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0).unwrap();
|
||||
|
||||
// Qualifiers + variable name
|
||||
*hash = calc_script_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0);
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
namespace.set_index(state.find_module(&namespace[0].name));
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
namespace.set_index(state.find_module(&namespace[0].name));
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
});
|
||||
|
||||
// Make sure identifiers are valid
|
||||
@ -1174,16 +1293,6 @@ fn parse_unary(
|
||||
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
|
||||
|
||||
match token {
|
||||
// If statement is allowed to act as expressions
|
||||
Token::If if settings.allow_if_expr => Ok(Expr::Stmt(
|
||||
Box::new(vec![parse_if(input, state, lib, settings.level_up())?].into()),
|
||||
settings.pos,
|
||||
)),
|
||||
// Switch statement is allowed to act as expressions
|
||||
Token::Switch if settings.allow_switch_expr => Ok(Expr::Stmt(
|
||||
Box::new(vec![parse_switch(input, state, lib, settings.level_up())?].into()),
|
||||
settings.pos,
|
||||
)),
|
||||
// -expr
|
||||
Token::UnaryMinus => {
|
||||
let pos = eat_token(input, Token::UnaryMinus);
|
||||
@ -1208,16 +1317,12 @@ fn parse_unary(
|
||||
// Call negative function
|
||||
expr => {
|
||||
let op = "-";
|
||||
let hash = calc_script_fn_hash(empty(), op, 1);
|
||||
let mut args = StaticVec::new();
|
||||
args.push(expr);
|
||||
|
||||
Ok(Expr::FnCall(
|
||||
Box::new(FnCallExpr {
|
||||
name: op.into(),
|
||||
native_only: true,
|
||||
namespace: None,
|
||||
hash,
|
||||
args,
|
||||
..Default::default()
|
||||
}),
|
||||
@ -1238,16 +1343,12 @@ fn parse_unary(
|
||||
// Call plus function
|
||||
expr => {
|
||||
let op = "+";
|
||||
let hash = calc_script_fn_hash(empty(), op, 1);
|
||||
let mut args = StaticVec::new();
|
||||
args.push(expr);
|
||||
|
||||
Ok(Expr::FnCall(
|
||||
Box::new(FnCallExpr {
|
||||
name: op.into(),
|
||||
native_only: true,
|
||||
namespace: None,
|
||||
hash,
|
||||
args,
|
||||
..Default::default()
|
||||
}),
|
||||
@ -1264,13 +1365,10 @@ fn parse_unary(
|
||||
args.push(expr);
|
||||
|
||||
let op = "!";
|
||||
let hash = calc_script_fn_hash(empty(), op, 1);
|
||||
|
||||
Ok(Expr::FnCall(
|
||||
Box::new(FnCallExpr {
|
||||
name: op.into(),
|
||||
native_only: true,
|
||||
hash,
|
||||
args,
|
||||
def_value: Some(false.into()), // NOT operator, when operating on invalid operand, defaults to false
|
||||
..Default::default()
|
||||
@ -1278,44 +1376,6 @@ fn parse_unary(
|
||||
pos,
|
||||
))
|
||||
}
|
||||
// | ...
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Token::Pipe | Token::Or if settings.allow_anonymous_fn => {
|
||||
let mut new_state = ParseState::new(
|
||||
state.engine,
|
||||
state.script_hash,
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
state.max_function_expr_depth,
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
state.max_function_expr_depth,
|
||||
);
|
||||
|
||||
let settings = ParseSettings {
|
||||
allow_if_expr: true,
|
||||
allow_switch_expr: true,
|
||||
allow_stmt_expr: true,
|
||||
allow_anonymous_fn: true,
|
||||
is_global: false,
|
||||
is_function_scope: true,
|
||||
is_breakable: false,
|
||||
level: 0,
|
||||
pos: *token_pos,
|
||||
};
|
||||
|
||||
let (expr, func) = parse_anon_fn(input, &mut new_state, lib, settings)?;
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
new_state.externals.iter().for_each(|(closure, pos)| {
|
||||
state.access_var(closure, *pos);
|
||||
});
|
||||
|
||||
// Qualifiers (none) + function name + number of arguments.
|
||||
let hash = calc_script_fn_hash(empty(), &func.name, func.params.len());
|
||||
|
||||
lib.insert(hash, func);
|
||||
|
||||
Ok(expr)
|
||||
}
|
||||
// <EOF>
|
||||
Token::EOF => Err(PERR::UnexpectedEOF.into_err(settings.pos)),
|
||||
// All other tokens
|
||||
@ -1323,6 +1383,7 @@ fn parse_unary(
|
||||
}
|
||||
}
|
||||
|
||||
/// Make an assignment statement.
|
||||
fn make_assignment_stmt<'a>(
|
||||
fn_name: Cow<'static, str>,
|
||||
state: &mut ParseState,
|
||||
@ -1338,7 +1399,7 @@ fn make_assignment_stmt<'a>(
|
||||
)),
|
||||
// var (indexed) = rhs
|
||||
Expr::Variable(x) => {
|
||||
let (index, _, _, Ident { name, pos }) = x.as_ref();
|
||||
let (index, _, Ident { name, pos }) = x.as_ref();
|
||||
match state.stack[(state.stack.len() - index.unwrap().get())].1 {
|
||||
AccessMode::ReadWrite => Ok(Stmt::Assignment(
|
||||
Box::new((lhs, fn_name.into(), rhs)),
|
||||
@ -1359,7 +1420,7 @@ fn make_assignment_stmt<'a>(
|
||||
)),
|
||||
// var[???] (indexed) = rhs, var.??? (indexed) = rhs
|
||||
Expr::Variable(x) => {
|
||||
let (index, _, _, Ident { name, pos }) = x.as_ref();
|
||||
let (index, _, Ident { name, pos }) = x.as_ref();
|
||||
match state.stack[(state.stack.len() - index.unwrap().get())].1 {
|
||||
AccessMode::ReadWrite => Ok(Stmt::Assignment(
|
||||
Box::new((lhs, fn_name.into(), rhs)),
|
||||
@ -1443,16 +1504,16 @@ fn make_dot_expr(
|
||||
}
|
||||
// lhs.id
|
||||
(lhs, Expr::Variable(x)) if x.1.is_none() => {
|
||||
let ident = x.3;
|
||||
let ident = x.2;
|
||||
let getter = state.get_interned_string(crate::engine::make_getter(&ident.name));
|
||||
let setter = state.get_interned_string(crate::engine::make_setter(&ident.name));
|
||||
let rhs = Expr::Property(Box::new(((getter, setter), ident)));
|
||||
let rhs = Expr::Property(Box::new((getter, setter, ident)));
|
||||
|
||||
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
|
||||
}
|
||||
// lhs.module::id - syntax error
|
||||
(_, Expr::Variable(x)) if x.1.is_some() => {
|
||||
return Err(PERR::PropertyExpected.into_err(x.1.unwrap()[0].pos));
|
||||
return Err(PERR::PropertyExpected.into_err(x.1.unwrap().1[0].pos));
|
||||
}
|
||||
// lhs.prop
|
||||
(lhs, prop @ Expr::Property(_)) => {
|
||||
@ -1662,15 +1723,24 @@ fn parse_binary_op(
|
||||
|
||||
loop {
|
||||
let (current_op, current_pos) = input.peek().unwrap();
|
||||
let precedence = if let Token::Custom(c) = current_op {
|
||||
// Custom operators
|
||||
if let Some(Some(p)) = state.engine.custom_keywords.get(c) {
|
||||
*p
|
||||
} else {
|
||||
return Err(PERR::Reserved(c.clone()).into_err(*current_pos));
|
||||
let precedence = match current_op {
|
||||
Token::Custom(c) => {
|
||||
if state
|
||||
.engine
|
||||
.custom_keywords
|
||||
.get(c)
|
||||
.map(Option::is_some)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
state.engine.custom_keywords.get(c).unwrap().unwrap().get()
|
||||
} else {
|
||||
return Err(PERR::Reserved(c.clone()).into_err(*current_pos));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
current_op.precedence()
|
||||
Token::Reserved(c) if !is_valid_identifier(c.chars()) => {
|
||||
return Err(PERR::UnknownOperator(c.into()).into_err(*current_pos))
|
||||
}
|
||||
_ => current_op.precedence(),
|
||||
};
|
||||
let bind_right = current_op.is_bind_right();
|
||||
|
||||
@ -1682,28 +1752,27 @@ fn parse_binary_op(
|
||||
|
||||
let (op_token, pos) = input.next().unwrap();
|
||||
|
||||
if cfg!(not(feature = "no_object")) && op_token == Token::Period {
|
||||
if let (Token::Identifier(_), _) = input.peek().unwrap() {
|
||||
// prevents capturing of the object properties as vars: xxx.<var>
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
{
|
||||
state.allow_capture = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let rhs = parse_unary(input, state, lib, settings)?;
|
||||
|
||||
let (next_op, next_pos) = input.peek().unwrap();
|
||||
let next_precedence = if let Token::Custom(c) = next_op {
|
||||
// Custom operators
|
||||
if let Some(Some(p)) = state.engine.custom_keywords.get(c) {
|
||||
*p
|
||||
} else {
|
||||
return Err(PERR::Reserved(c.clone()).into_err(*next_pos));
|
||||
let next_precedence = match next_op {
|
||||
Token::Custom(c) => {
|
||||
if state
|
||||
.engine
|
||||
.custom_keywords
|
||||
.get(c)
|
||||
.map(Option::is_some)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
state.engine.custom_keywords.get(c).unwrap().unwrap().get()
|
||||
} else {
|
||||
return Err(PERR::Reserved(c.clone()).into_err(*next_pos));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
next_op.precedence()
|
||||
Token::Reserved(c) if !is_valid_identifier(c.chars()) => {
|
||||
return Err(PERR::UnknownOperator(c.into()).into_err(*next_pos))
|
||||
}
|
||||
_ => next_op.precedence(),
|
||||
};
|
||||
|
||||
// Bind to right if the next operator has higher precedence
|
||||
@ -1723,11 +1792,9 @@ fn parse_binary_op(
|
||||
|
||||
let cmp_def = Some(false.into());
|
||||
let op = op_token.syntax();
|
||||
let hash = calc_script_fn_hash(empty(), &op, 2);
|
||||
|
||||
let op_base = FnCallExpr {
|
||||
name: op,
|
||||
native_only: true,
|
||||
capture: false,
|
||||
..Default::default()
|
||||
};
|
||||
@ -1747,19 +1814,11 @@ fn parse_binary_op(
|
||||
| Token::PowerOf
|
||||
| Token::Ampersand
|
||||
| Token::Pipe
|
||||
| Token::XOr => Expr::FnCall(
|
||||
Box::new(FnCallExpr {
|
||||
hash,
|
||||
args,
|
||||
..op_base
|
||||
}),
|
||||
pos,
|
||||
),
|
||||
| Token::XOr => Expr::FnCall(Box::new(FnCallExpr { args, ..op_base }), pos),
|
||||
|
||||
// '!=' defaults to true when passed invalid operands
|
||||
Token::NotEqualsTo => Expr::FnCall(
|
||||
Box::new(FnCallExpr {
|
||||
hash,
|
||||
args,
|
||||
def_value: Some(true.into()),
|
||||
..op_base
|
||||
@ -1774,7 +1833,6 @@ fn parse_binary_op(
|
||||
| Token::GreaterThan
|
||||
| Token::GreaterThanEqualsTo => Expr::FnCall(
|
||||
Box::new(FnCallExpr {
|
||||
hash,
|
||||
args,
|
||||
def_value: cmp_def,
|
||||
..op_base
|
||||
@ -1810,20 +1868,31 @@ fn parse_binary_op(
|
||||
make_in_expr(current_lhs, rhs, pos)?
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Token::Period => {
|
||||
let rhs = args.pop().unwrap();
|
||||
let current_lhs = args.pop().unwrap();
|
||||
make_dot_expr(state, current_lhs, rhs, pos)?
|
||||
}
|
||||
// #[cfg(not(feature = "no_object"))]
|
||||
// Token::Period => {
|
||||
// let rhs = args.pop().unwrap();
|
||||
// let current_lhs = args.pop().unwrap();
|
||||
// make_dot_expr(state, current_lhs, rhs, pos)?
|
||||
// }
|
||||
Token::Custom(s)
|
||||
if state
|
||||
.engine
|
||||
.custom_keywords
|
||||
.get(&s)
|
||||
.map(Option::is_some)
|
||||
.unwrap_or(false) =>
|
||||
{
|
||||
let hash_script = if is_valid_identifier(s.chars()) {
|
||||
// Accept non-native functions for custom operators
|
||||
calc_script_fn_hash(empty(), &s, 2)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Token::Custom(s) if state.engine.custom_keywords.contains_key(&s) => {
|
||||
// Accept non-native functions for custom operators
|
||||
Expr::FnCall(
|
||||
Box::new(FnCallExpr {
|
||||
hash,
|
||||
hash_script,
|
||||
args,
|
||||
native_only: false,
|
||||
..op_base
|
||||
}),
|
||||
pos,
|
||||
@ -1892,7 +1961,7 @@ fn parse_custom_syntax(
|
||||
segments.push(name.clone());
|
||||
tokens.push(state.get_interned_string(MARKER_IDENT));
|
||||
let var_name_def = Ident { name, pos };
|
||||
keywords.push(Expr::Variable(Box::new((None, None, 0, var_name_def))));
|
||||
keywords.push(Expr::Variable(Box::new((None, None, var_name_def))));
|
||||
}
|
||||
(Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => {
|
||||
return Err(PERR::Reserved(s).into_err(pos));
|
||||
@ -2549,7 +2618,7 @@ fn parse_stmt(
|
||||
let func = parse_fn(input, &mut new_state, lib, access, settings, comments)?;
|
||||
|
||||
// Qualifiers (none) + function name + number of arguments.
|
||||
let hash = calc_script_fn_hash(empty(), &func.name, func.params.len());
|
||||
let hash = calc_script_fn_hash(empty(), &func.name, func.params.len()).unwrap();
|
||||
|
||||
lib.insert(hash, func);
|
||||
|
||||
@ -2565,6 +2634,7 @@ fn parse_stmt(
|
||||
}
|
||||
|
||||
Token::If => parse_if(input, state, lib, settings.level_up()).map(Some),
|
||||
Token::Switch => parse_switch(input, state, lib, settings.level_up()).map(Some),
|
||||
Token::While | Token::Loop => {
|
||||
parse_while_loop(input, state, lib, settings.level_up()).map(Some)
|
||||
}
|
||||
@ -2812,22 +2882,22 @@ fn make_curry_from_externals(fn_expr: Expr, externals: StaticVec<Ident>, pos: Po
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
externals.iter().for_each(|x| {
|
||||
args.push(Expr::Variable(Box::new((None, None, 0, x.clone().into()))));
|
||||
args.push(Expr::Variable(Box::new((None, None, x.clone().into()))));
|
||||
});
|
||||
|
||||
#[cfg(feature = "no_closure")]
|
||||
externals.into_iter().for_each(|x| {
|
||||
args.push(Expr::Variable(Box::new((None, None, 0, x.clone().into()))));
|
||||
args.push(Expr::Variable(Box::new((None, None, x.clone().into()))));
|
||||
});
|
||||
|
||||
let curry_func = crate::engine::KEYWORD_FN_PTR_CURRY;
|
||||
|
||||
let hash = calc_script_fn_hash(empty(), curry_func, num_externals + 1);
|
||||
let hash_script = calc_script_fn_hash(empty(), curry_func, num_externals + 1);
|
||||
|
||||
let expr = Expr::FnCall(
|
||||
Box::new(FnCallExpr {
|
||||
name: curry_func.into(),
|
||||
hash,
|
||||
hash_script,
|
||||
args,
|
||||
..Default::default()
|
||||
}),
|
||||
|
@ -13,8 +13,8 @@ pub use rhai_codegen::*;
|
||||
pub use rhai_codegen::{export_fn, register_exported_fn};
|
||||
|
||||
/// Trait implemented by a _plugin function_.
|
||||
/// This trait should not be used directly.
|
||||
///
|
||||
/// This trait should not be used directly.
|
||||
/// Use the `#[export_module]` and `#[export_fn]` procedural attributes instead.
|
||||
pub trait PluginFunction {
|
||||
/// Call the plugin function with the arguments provided.
|
||||
|
@ -55,8 +55,8 @@ pub enum EvalAltResult {
|
||||
/// String indexing out-of-bounds.
|
||||
/// Wrapped values are the current number of characters in the string and the index number.
|
||||
ErrorStringBounds(usize, INT, Position),
|
||||
/// Trying to index into a type that is not an array, an object map, or a string, and has no indexer function defined.
|
||||
/// Wrapped value is the type name.
|
||||
/// Trying to index into a type that is not an array, an object map, or a string, and has no
|
||||
/// indexer function defined. Wrapped value is the type name.
|
||||
ErrorIndexingType(String, Position),
|
||||
/// Invalid arguments for `in` operator.
|
||||
ErrorInExpr(Position),
|
||||
|
42
src/scope.rs
42
src/scope.rs
@ -39,17 +39,18 @@ use crate::{Dynamic, ImmutableString, StaticVec};
|
||||
// # Implementation Notes
|
||||
//
|
||||
// [`Scope`] is implemented as two [`Vec`]'s of exactly the same length. Variables data (name, type, etc.)
|
||||
// is manually split into three equal-length arrays. That's because variable names take up the most space,
|
||||
// with [`Cow<str>`][Cow] being four words long, but in the vast majority of cases the name is NOT used to look up
|
||||
// a variable's value. Variable lookup is usually via direct index, by-passing the name altogether.
|
||||
// is manually split into two equal-length arrays. That's because variable names take up the most space,
|
||||
// with [`Cow<str>`][Cow] being four words long, but in the vast majority of cases the name is NOT used to
|
||||
// look up a variable. Variable lookup is usually via direct indexing, by-passing the name altogether.
|
||||
//
|
||||
// Since [`Dynamic`] is reasonably small, packing it tightly improves cache locality when variables are accessed.
|
||||
// The variable type is packed separately into another array because it is even smaller.
|
||||
#[derive(Debug)]
|
||||
//
|
||||
// The alias is `Box`'ed because it occurs infrequently.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct Scope<'a> {
|
||||
/// Current value of the entry.
|
||||
values: Vec<Dynamic>,
|
||||
/// (Name, aliases) of the entry. The list of aliases is Boxed because it occurs rarely.
|
||||
/// (Name, aliases) of the entry.
|
||||
names: Vec<(Cow<'a, str>, Box<StaticVec<ImmutableString>>)>,
|
||||
}
|
||||
|
||||
@ -357,8 +358,35 @@ impl<'a> Scope<'a> {
|
||||
self
|
||||
}
|
||||
/// Get a mutable reference to an entry in the [`Scope`].
|
||||
///
|
||||
/// If the entry by the specified name is not found, of if it is read-only,
|
||||
/// [`None`] is returned.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use rhai::Scope;
|
||||
///
|
||||
/// let mut my_scope = Scope::new();
|
||||
///
|
||||
/// my_scope.push("x", 42_i64);
|
||||
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 42);
|
||||
///
|
||||
/// let ptr = my_scope.get_mut("x").unwrap();
|
||||
/// *ptr = 123_i64.into();
|
||||
///
|
||||
/// assert_eq!(my_scope.get_value::<i64>("x").unwrap(), 123);
|
||||
/// ```
|
||||
pub fn get_mut(&mut self, name: &str) -> Option<&mut Dynamic> {
|
||||
self.get_index(name)
|
||||
.and_then(move |(index, access)| match access {
|
||||
AccessMode::ReadWrite => Some(self.get_mut_by_index(index)),
|
||||
AccessMode::ReadOnly => None,
|
||||
})
|
||||
}
|
||||
/// Get a mutable reference to an entry in the [`Scope`] based on the index.
|
||||
#[inline(always)]
|
||||
pub(crate) fn get_mut(&mut self, index: usize) -> &mut Dynamic {
|
||||
pub(crate) fn get_mut_by_index(&mut self, index: usize) -> &mut Dynamic {
|
||||
self.values.get_mut(index).expect("invalid index in Scope")
|
||||
}
|
||||
/// Update the access type of an entry in the [`Scope`].
|
||||
|
@ -90,7 +90,7 @@ struct FnMetadata {
|
||||
pub return_type: Option<String>,
|
||||
pub signature: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub doc_comments: Option<Vec<String>>,
|
||||
pub doc_comments: Vec<String>,
|
||||
}
|
||||
|
||||
impl PartialOrd for FnMetadata {
|
||||
@ -152,9 +152,9 @@ impl From<&crate::module::FuncInfo> for FnMetadata {
|
||||
},
|
||||
signature: info.gen_signature(),
|
||||
doc_comments: if info.func.is_script() {
|
||||
Some(info.func.get_fn_def().comments.clone())
|
||||
info.func.get_fn_def().comments.clone()
|
||||
} else {
|
||||
None
|
||||
Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -121,20 +121,26 @@ impl Engine {
|
||||
continue;
|
||||
}
|
||||
|
||||
let token = Token::lookup_from_syntax(s);
|
||||
|
||||
let seg = match s {
|
||||
// Markers not in first position
|
||||
MARKER_IDENT | MARKER_EXPR | MARKER_BLOCK if !segments.is_empty() => s.into(),
|
||||
// Standard or reserved keyword/symbol not in first position
|
||||
s if !segments.is_empty() && Token::lookup_from_syntax(s).is_some() => {
|
||||
// Make it a custom keyword/symbol
|
||||
if !self.custom_keywords.contains_key(s) {
|
||||
s if !segments.is_empty() && token.is_some() => {
|
||||
// Make it a custom keyword/symbol if it is disabled or reserved
|
||||
if (self.disabled_symbols.contains(s)
|
||||
|| matches!(token, Some(Token::Reserved(_))))
|
||||
&& !self.custom_keywords.contains_key(s)
|
||||
{
|
||||
self.custom_keywords.insert(s.into(), None);
|
||||
}
|
||||
s.into()
|
||||
}
|
||||
// Standard keyword in first position
|
||||
s if segments.is_empty()
|
||||
&& Token::lookup_from_syntax(s)
|
||||
&& token
|
||||
.as_ref()
|
||||
.map(|v| v.is_keyword() || v.is_reserved())
|
||||
.unwrap_or(false) =>
|
||||
{
|
||||
@ -151,7 +157,11 @@ impl Engine {
|
||||
}
|
||||
// Identifier in first position
|
||||
s if segments.is_empty() && is_valid_identifier(s.chars()) => {
|
||||
if !self.custom_keywords.contains_key(s) {
|
||||
// Make it a custom keyword/symbol if it is disabled or reserved
|
||||
if (self.disabled_symbols.contains(s)
|
||||
|| matches!(token, Some(Token::Reserved(_))))
|
||||
&& !self.custom_keywords.contains_key(s)
|
||||
{
|
||||
self.custom_keywords.insert(s.into(), None);
|
||||
}
|
||||
s.into()
|
||||
@ -208,7 +218,7 @@ impl Engine {
|
||||
/// * `parse` is the parsing function.
|
||||
/// * `func` is the implementation function.
|
||||
///
|
||||
/// All custom keywords must be manually registered via [`register_custom_operator`][Engine::register_custom_operator].
|
||||
/// All custom keywords must be manually registered via [`Engine::register_custom_operator`].
|
||||
/// Otherwise, custom keywords won't be recognized.
|
||||
pub fn register_custom_syntax_raw(
|
||||
&mut self,
|
||||
|
28
src/token.rs
28
src/token.rs
@ -532,10 +532,10 @@ impl Token {
|
||||
#[cfg(feature = "no_module")]
|
||||
"import" | "export" | "as" => Reserved(syntax.into()),
|
||||
|
||||
"===" | "!==" | "->" | "<-" | ":=" | "::<" | "(*" | "*)" | "#" | "public" | "new"
|
||||
| "use" | "module" | "package" | "var" | "static" | "begin" | "end" | "shared"
|
||||
| "with" | "each" | "then" | "goto" | "unless" | "exit" | "match" | "case"
|
||||
| "default" | "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync"
|
||||
"===" | "!==" | "->" | "<-" | ":=" | "**" | "::<" | "(*" | "*)" | "#" | "public"
|
||||
| "new" | "use" | "module" | "package" | "var" | "static" | "begin" | "end"
|
||||
| "shared" | "with" | "each" | "then" | "goto" | "unless" | "exit" | "match"
|
||||
| "case" | "default" | "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync"
|
||||
| "async" | "await" | "yield" => Reserved(syntax.into()),
|
||||
|
||||
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
|
||||
@ -1742,27 +1742,21 @@ impl<'a> Iterator for TokenIterator<'a, '_> {
|
||||
Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.contains_key(&s) => {
|
||||
Some((Token::Custom(s), pos))
|
||||
}
|
||||
// Custom standard keyword - must be disabled
|
||||
Some((token, pos)) if token.is_keyword() && self.engine.custom_keywords.contains_key(token.syntax().as_ref()) => {
|
||||
// Custom standard keyword/symbol - must be disabled
|
||||
Some((token, pos)) if self.engine.custom_keywords.contains_key(token.syntax().as_ref()) => {
|
||||
if self.engine.disabled_symbols.contains(token.syntax().as_ref()) {
|
||||
// Disabled standard keyword
|
||||
// Disabled standard keyword/symbol
|
||||
Some((Token::Custom(token.syntax().into()), pos))
|
||||
} else {
|
||||
// Active standard keyword - should never be a custom keyword!
|
||||
unreachable!()
|
||||
unreachable!("{:?}", token)
|
||||
}
|
||||
}
|
||||
// Disabled operator
|
||||
Some((token, pos)) if token.is_operator() && self.engine.disabled_symbols.contains(token.syntax().as_ref()) => {
|
||||
Some((
|
||||
Token::LexError(LexError::UnexpectedInput(token.syntax().into())),
|
||||
pos,
|
||||
))
|
||||
}
|
||||
// Disabled standard keyword
|
||||
Some((token, pos)) if token.is_keyword() && self.engine.disabled_symbols.contains(token.syntax().as_ref()) => {
|
||||
// Disabled symbol
|
||||
Some((token, pos)) if self.engine.disabled_symbols.contains(token.syntax().as_ref()) => {
|
||||
Some((Token::Reserved(token.syntax().into()), pos))
|
||||
}
|
||||
// Normal symbol
|
||||
r => r,
|
||||
};
|
||||
|
||||
|
62
src/utils.rs
62
src/utils.rs
@ -9,30 +9,34 @@ use crate::stdlib::{
|
||||
fmt,
|
||||
hash::{BuildHasher, Hash, Hasher},
|
||||
iter::{empty, FromIterator},
|
||||
num::NonZeroU64,
|
||||
ops::{Add, AddAssign, Deref},
|
||||
str::FromStr,
|
||||
string::{String, ToString},
|
||||
};
|
||||
use crate::Shared;
|
||||
|
||||
/// A hasher that only takes one single [`u64`] and returns it as a hash key.
|
||||
/// A hasher that only takes one single [`NonZeroU64`] and returns it as a hash key.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics when hashing any data type other than a [`u64`].
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
|
||||
pub struct StraightHasher(u64);
|
||||
/// Panics when hashing any data type other than a [`NonZeroU64`].
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct StraightHasher(NonZeroU64);
|
||||
|
||||
impl Hasher for StraightHasher {
|
||||
#[inline(always)]
|
||||
fn finish(&self) -> u64 {
|
||||
self.0
|
||||
self.0.get()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn write(&mut self, bytes: &[u8]) {
|
||||
let mut key = [0_u8; 8];
|
||||
key.copy_from_slice(&bytes[..8]); // Panics if fewer than 8 bytes
|
||||
self.0 = u64::from_le_bytes(key);
|
||||
|
||||
// HACK - If it so happens to hash directly to zero (OMG!) then change it to 42...
|
||||
self.0 = NonZeroU64::new(u64::from_le_bytes(key))
|
||||
.unwrap_or_else(|| NonZeroU64::new(42).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,11 +49,22 @@ impl BuildHasher for StraightHasherBuilder {
|
||||
|
||||
#[inline(always)]
|
||||
fn build_hasher(&self) -> Self::Hasher {
|
||||
Default::default()
|
||||
StraightHasher(NonZeroU64::new(42).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
/// _(INTERNALS)_ Calculate a [`u64`] hash key from a namespace-qualified function name and parameter types.
|
||||
/// Create an instance of the default hasher.
|
||||
pub fn get_hasher() -> impl Hasher {
|
||||
#[cfg(feature = "no_std")]
|
||||
let s: ahash::AHasher = Default::default();
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
let s = crate::stdlib::collections::hash_map::DefaultHasher::new();
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
/// _(INTERNALS)_ Calculate a [`NonZeroU64`] hash key from a namespace-qualified function name and
|
||||
/// parameter types.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// Module names are passed in via `&str` references from an iterator.
|
||||
@ -63,11 +78,11 @@ pub fn calc_native_fn_hash<'a>(
|
||||
modules: impl Iterator<Item = &'a str>,
|
||||
fn_name: &str,
|
||||
params: impl Iterator<Item = TypeId>,
|
||||
) -> u64 {
|
||||
) -> Option<NonZeroU64> {
|
||||
calc_fn_hash(modules, fn_name, None, params)
|
||||
}
|
||||
|
||||
/// _(INTERNALS)_ Calculate a [`u64`] hash key from a namespace-qualified function name
|
||||
/// _(INTERNALS)_ Calculate a [`NonZeroU64`] hash key from a namespace-qualified function name
|
||||
/// and the number of parameters, but no parameter types.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
@ -82,21 +97,11 @@ pub fn calc_script_fn_hash<'a>(
|
||||
modules: impl Iterator<Item = &'a str>,
|
||||
fn_name: &str,
|
||||
num: usize,
|
||||
) -> u64 {
|
||||
) -> Option<NonZeroU64> {
|
||||
calc_fn_hash(modules, fn_name, Some(num), empty())
|
||||
}
|
||||
|
||||
/// Create an instance of the default hasher.
|
||||
pub fn get_hasher() -> impl Hasher {
|
||||
#[cfg(feature = "no_std")]
|
||||
let s: ahash::AHasher = Default::default();
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
let s = crate::stdlib::collections::hash_map::DefaultHasher::new();
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
/// Calculate a [`u64`] hash key from a namespace-qualified function name and parameter types.
|
||||
/// Calculate a [`NonZeroU64`] hash key from a namespace-qualified function name and parameter types.
|
||||
///
|
||||
/// Module names are passed in via `&str` references from an iterator.
|
||||
/// Parameter types are passed in via [`TypeId`] values from an iterator.
|
||||
@ -104,12 +109,13 @@ pub fn get_hasher() -> impl Hasher {
|
||||
/// # Note
|
||||
///
|
||||
/// The first module name is skipped. Hashing starts from the _second_ module in the chain.
|
||||
#[inline(always)]
|
||||
fn calc_fn_hash<'a>(
|
||||
mut modules: impl Iterator<Item = &'a str>,
|
||||
fn_name: &str,
|
||||
num: Option<usize>,
|
||||
params: impl Iterator<Item = TypeId>,
|
||||
) -> u64 {
|
||||
) -> Option<NonZeroU64> {
|
||||
let s = &mut get_hasher();
|
||||
|
||||
// Hash a boolean indicating whether the hash is namespace-qualified.
|
||||
@ -122,7 +128,15 @@ fn calc_fn_hash<'a>(
|
||||
} else {
|
||||
params.for_each(|t| t.hash(s));
|
||||
}
|
||||
s.finish()
|
||||
// HACK - If it so happens to hash directly to zero (OMG!) then change it to 42...
|
||||
NonZeroU64::new(s.finish()).or_else(|| NonZeroU64::new(42))
|
||||
}
|
||||
|
||||
/// Combine two [`NonZeroU64`] hashes by taking the XOR of them.
|
||||
#[inline(always)]
|
||||
pub(crate) fn combine_hashes(a: NonZeroU64, b: NonZeroU64) -> NonZeroU64 {
|
||||
// HACK - If it so happens to hash directly to zero (OMG!) then change it to 42...
|
||||
NonZeroU64::new(a.get() ^ b.get()).unwrap_or_else(|| NonZeroU64::new(42).unwrap())
|
||||
}
|
||||
|
||||
/// The system immutable string type.
|
||||
|
@ -32,7 +32,7 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
|
||||
x.append(y);
|
||||
|
||||
x.len + r
|
||||
"
|
||||
"
|
||||
)?,
|
||||
14
|
||||
);
|
||||
@ -42,7 +42,7 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
|
||||
let x = [1, 2, 3];
|
||||
x += [4, 5];
|
||||
len(x)
|
||||
"
|
||||
"
|
||||
)?,
|
||||
5
|
||||
);
|
||||
@ -53,7 +53,7 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
|
||||
let x = [1, 2, 3];
|
||||
let y = [4, 5];
|
||||
x + y
|
||||
"
|
||||
"
|
||||
)?
|
||||
.len(),
|
||||
5
|
||||
|
@ -82,7 +82,7 @@ fn test_call_fn_private() -> Result<(), Box<EvalAltResult>> {
|
||||
let ast = engine.compile("private fn add(x, n, ) { x + n }")?;
|
||||
|
||||
assert!(matches!(
|
||||
*engine.call_fn::<_, INT>(&mut scope, &ast, "add", (40 as INT, 2 as INT))
|
||||
*(engine.call_fn(&mut scope, &ast, "add", (40 as INT, 2 as INT)) as Result<INT, Box<EvalAltResult>>)
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "add"
|
||||
));
|
||||
|
@ -54,6 +54,17 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
|
||||
ParseErrorType::BadInput(_)
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r"
|
||||
let foo = #{ x: 42 };
|
||||
let f = || { this.x };
|
||||
foo.call(f)
|
||||
",
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
r#"
|
||||
@ -239,7 +250,7 @@ fn test_closures_shared_obj() -> Result<(), Box<EvalAltResult>> {
|
||||
let f = move |p1: TestStruct, p2: TestStruct| -> Result<(), Box<EvalAltResult>> {
|
||||
let action_ptr = res["action"].clone().cast::<FnPtr>();
|
||||
let name = action_ptr.fn_name();
|
||||
engine.call_fn::<_, ()>(&mut Scope::new(), &ast, name, (p1, p2))
|
||||
engine.call_fn(&mut Scope::new(), &ast, name, (p1, p2))
|
||||
};
|
||||
|
||||
// Test closure
|
||||
|
@ -101,7 +101,7 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
|
||||
resolver.insert("hello", module);
|
||||
|
||||
let mut engine = Engine::new();
|
||||
engine.set_module_resolver(Some(resolver));
|
||||
engine.set_module_resolver(resolver);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
@ -295,13 +295,13 @@ fn test_module_from_ast() -> Result<(), Box<EvalAltResult>> {
|
||||
"#,
|
||||
)?;
|
||||
|
||||
engine.set_module_resolver(Some(resolver1));
|
||||
engine.set_module_resolver(resolver1);
|
||||
|
||||
let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine)?;
|
||||
|
||||
let mut resolver2 = StaticModuleResolver::new();
|
||||
resolver2.insert("testing", module);
|
||||
engine.set_module_resolver(Some(resolver2));
|
||||
engine.set_module_resolver(resolver2);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(r#"import "testing" as ttt; ttt::abc"#)?,
|
||||
@ -384,7 +384,7 @@ fn test_module_str() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
let mut static_modules = rhai::module_resolvers::StaticModuleResolver::new();
|
||||
static_modules.insert("test", module);
|
||||
engine.set_module_resolver(Some(static_modules));
|
||||
engine.set_module_resolver(static_modules);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(r#"import "test" as test; test::test("test");"#)?,
|
||||
@ -418,7 +418,7 @@ fn test_module_ast_namespace() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
let mut resolver = StaticModuleResolver::new();
|
||||
resolver.insert("testing", module);
|
||||
engine.set_module_resolver(Some(resolver));
|
||||
engine.set_module_resolver(resolver);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(r#"import "testing" as t; t::foo(41)"#)?,
|
||||
@ -466,7 +466,7 @@ fn test_module_ast_namespace2() -> Result<(), Box<EvalAltResult>> {
|
||||
let module = Module::eval_ast_as_new(Scope::new(), &module_ast, &engine)?;
|
||||
let mut static_modules = rhai::module_resolvers::StaticModuleResolver::new();
|
||||
static_modules.insert("test_module", module);
|
||||
engine.set_module_resolver(Some(static_modules));
|
||||
engine.set_module_resolver(static_modules);
|
||||
|
||||
engine.consume(SCRIPT)?;
|
||||
|
||||
|
@ -21,12 +21,17 @@ fn test_tokens_disabled() {
|
||||
.compile("let x = 40 + 2; x += 1;")
|
||||
.expect_err("should error")
|
||||
.0,
|
||||
ParseErrorType::BadInput(LexError::UnexpectedInput("+=".to_string()))
|
||||
ParseErrorType::UnknownOperator("+=".to_string())
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
*engine.compile("let x = += 0;").expect_err("should error").0,
|
||||
ParseErrorType::BadInput(LexError::UnexpectedInput(err)) if err == "+="
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tokens_custom_operator() -> Result<(), Box<EvalAltResult>> {
|
||||
fn test_tokens_custom_operator_identifiers() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register a custom operator called `foo` and give it
|
||||
@ -55,6 +60,29 @@ fn test_tokens_custom_operator() -> Result<(), Box<EvalAltResult>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tokens_custom_operator_symbol() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// Register a custom operator `#` and give it
|
||||
// a precedence of 160 (i.e. between +|- and *|/).
|
||||
engine.register_custom_operator("#", 160).unwrap();
|
||||
|
||||
// Register a binary function named `#`
|
||||
engine.register_fn("#", |x: INT, y: INT| (x * y) - (x + y));
|
||||
|
||||
assert_eq!(engine.eval_expression::<INT>("1 + 2 * 3 # 4 - 5 / 6")?, 15);
|
||||
|
||||
// Register a custom operator named `=>`
|
||||
assert!(engine.register_custom_operator("=>", 160).is_err());
|
||||
engine.disable_symbol("=>");
|
||||
engine.register_custom_operator("=>", 160).unwrap();
|
||||
engine.register_fn("=>", |x: INT, y: INT| (x * y) - (x + y));
|
||||
assert_eq!(engine.eval_expression::<INT>("1 + 2 * 3 => 4 - 5 / 6")?, 15);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tokens_unicode_xid_ident() -> Result<(), Box<EvalAltResult>> {
|
||||
let engine = Engine::new();
|
||||
|
Loading…
Reference in New Issue
Block a user