Merge pull request #503 from schungx/master

Efficiency refactor.
This commit is contained in:
Stephen Chung 2022-01-07 10:50:24 +08:00 committed by GitHub
commit 595636add4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
106 changed files with 8477 additions and 7145 deletions

View File

@ -5,12 +5,59 @@ Version 1.4.0
=============
This version adds support for integer _ranges_ via the `..` and `..=` operators.
Many standard API's are extended with range parameters where appropriate.
Script-breaking changes
-----------------------
* `is` is (pun intended) now a reserved keyword to prepare for possible future type checking expressions (e.g. `x is "string"`).
Breaking changes
----------------
* `LogicPackage` is removed from `CorePackage`.
* Bit-field functions are moved into a new `BitFieldPackage` (used to be in `LogicPackage`) which makes more sense.
Bug fixes
---------
* Constructing a literal array or object map now checks for size limits for each item instead of at the very end when it is already too late.
* Non-`INT` integer types are now treated exactly as custom types under `only_i64` and `only_i32`.
* Calling `pad` on an array now checks for total size over limit after each item added.
New features
------------
* Added support for integer _ranges_ via the `..` and `..=` operators.
Enhancements
------------
* A new syntax is introduced for `def_package!` that will replace the old syntax in future versions.
* Added `NativeCallContext::call_fn` to easily call a function.
* Doc-comments on plugin module functions are extracted into the functions' metadata.
Deprecated API's
----------------
* `Expression::get_variable_name` is deprecated in favor of the new `Expression::get_string_value`.
* The old syntax of `def_package!` is deprecated in favor of the new syntax.
Version 1.3.1
=============
Bug fixes
---------
* Custom syntax now works properly inside binary expressions and with method calls.
* Hex numbers with the high-bit set now parse correctly into negative integer numbers.
Enhancements
------------
* `BLOB`'s are refined to display in a more compact hex format.
Version 1.3.0
=============
@ -71,8 +118,8 @@ Bug fixes
Version 1.2.0
=============
Bug fixes with breaking script changes
-------------------------------------
Bug fixes (potentially script-breaking)
--------------------------------------
* As originally intended, function calls with a bang (`!`) now operates directly on the caller's scope, allowing variables inside the scope to be mutated.
* As originally intended, `Engine::XXX_with_scope` API's now properly propagate constants within the provided scope also to _functions_ in the script.
@ -104,7 +151,7 @@ Deprecated API's
----------------
* `NativeCallContext::call_fn_dynamic_raw` is deprecated and `NativeCallContext::call_fn_raw` is added.
* `From<EvalAltResult>` for `Result<T, Box<EvalAltResult>>` is deprecated so it will no longer be possible to do `EvalAltResult::ErrorXXXXX.into()` to convert to a `Result`; instead, `Err(EvalAltResult:ErrorXXXXX.into())` must be used. Code is clearer if errors are explicitly wrapped in `Err`.
* `From<EvalAltResult>` for `Result<T, Box<EvalAltResult> >` is deprecated so it will no longer be possible to do `EvalAltResult::ErrorXXXXX.into()` to convert to a `Result`; instead, `Err(EvalAltResult:ErrorXXXXX.into())` must be used. Code is clearer if errors are explicitly wrapped in `Err`.
Version 1.1.2
@ -177,7 +224,7 @@ Enhancements
### `Scope` API
* `Scope::set_value` now takes anything that implements `Into<Cow<str>>`.
* `Scope::set_value` now takes anything that implements `Into<Cow<str> >`.
* Added `Scope::is_constant` to check if a variable is constant.
* Added `Scope::set_or_push` to add a new variable only if one doesn't already exist.
@ -462,7 +509,8 @@ Enhancements
* Replaced all `HashMap` usage with `BTreeMap` for better performance because collections in Rhai are tiny.
* `Engine::register_result_fn` no longer requires the successful return type to be `Dynamic`. It can now be any clonable type.
* `#[rhai_fn(return_raw)]` can now return `Result<T, Box<EvalAltResult>>` where `T` is any clonable type instead of `Result<Dynamic, Box<EvalAltResult>>`.
* `#[rhai_fn(return_raw)]` can now return `Result<T, Box<EvalAltResult> >` where `T` is any clonable
type instead of `Result<Dynamic, Box<EvalAltResult> >`.
* `Dynamic::clone_cast` is added to simplify casting from a `&Dynamic`.
@ -932,7 +980,7 @@ Breaking changes
----------------
* `AST::iter_functions` now returns an iterator instead of taking a closure.
* `Module::get_script_function_by_signature` renamed to `Module::get_script_fn` and returns `&<Shared<ScriptFnDef>>`.
* `Module::get_script_function_by_signature` renamed to `Module::get_script_fn` and returns `&<Shared<ScriptFnDef> >`.
* `Module::num_fn`, `Module::num_var` and `Module::num_iter` are removed and merged into `Module::count`.
* The `merge_namespaces` parameter to `Module::eval_ast_as_new` is removed and now defaults to `true`.
* `GlobalFileModuleResolver` is removed because its performance gain over the `FileModuleResolver` is no longer very significant.
@ -980,7 +1028,7 @@ Bug fixes
Breaking changes
----------------
* `Engine::register_set_result` and `Engine::register_indexer_set_result` now take a function that returns `Result<(), Box<EvalAltResult>>`.
* `Engine::register_set_result` and `Engine::register_indexer_set_result` now take a function that returns `Result<(), Box<EvalAltResult> >`.
* `Engine::register_indexer_XXX` and `Module::set_indexer_XXX` panic when the type is `Array`, `Map` or `String`.
* `EvalAltResult` has a new variant `ErrorInModule` which holds errors when loading an external module.
* `Module::eval_ast_as_new` now takes an extra boolean parameter, indicating whether to encapsulate the entire module into a separate namespace.
@ -1101,7 +1149,7 @@ Breaking changes
* `EvalAltResult::ErrorMismatchOutputType` has an extra argument containing the name of the requested type.
* `Engine::call_fn_dynamic` take an extra argument, allowing a `Dynamic` value to be bound to the `this` pointer.
* Precedence of the `%` (modulo) operator is lowered to below `<<` ad `>>`. This is to handle the case of `x << 3 % 10`.
* Precedence of the `%` (modulo) operator is lowered to below bit shifts. This is to handle the case of `x < < 3 % 10`.
New features
------------

View File

@ -79,7 +79,7 @@ The [`scripts`](https://github.com/rhaiscript/rhai/tree/master/scripts) subdirec
Below is the standard _Fibonacci_ example for scripting languages:
```js
```ts
// This Rhai script calculates the n-th Fibonacci number using a
// really dumb algorithm to test the speed of the scripting engine.

View File

@ -64,7 +64,7 @@ fn bench_eval_array_large_set(bench: &mut Bencher) {
#[bench]
fn bench_eval_array_loop(bench: &mut Bencher) {
let script = r#"
let script = "
let list = [];
for i in 0..10_000 {
@ -76,7 +76,7 @@ fn bench_eval_array_loop(bench: &mut Bencher) {
for i in list {
sum += i;
}
"#;
";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);

View File

@ -108,12 +108,12 @@ fn bench_eval_call(bench: &mut Bencher) {
#[bench]
fn bench_eval_loop_number(bench: &mut Bencher) {
let script = r#"
let script = "
let s = 0;
for x in 0..10000 {
s += 1;
}
"#;
";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
@ -159,7 +159,7 @@ fn bench_eval_loop_strings_no_build(bench: &mut Bencher) {
#[bench]
fn bench_eval_switch(bench: &mut Bencher) {
let script = r#"
let script = "
let sum = 0;
let rem = 0;
@ -179,7 +179,7 @@ fn bench_eval_switch(bench: &mut Bencher) {
9 => 1,
}
}
"#;
";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
@ -191,7 +191,7 @@ fn bench_eval_switch(bench: &mut Bencher) {
#[bench]
fn bench_eval_nested_if(bench: &mut Bencher) {
let script = r#"
let script = "
let sum = 0;
let rem = 0;
@ -209,7 +209,7 @@ fn bench_eval_nested_if(bench: &mut Bencher) {
else if rem == 8 { 42 }
else if rem == 9 { 1 };
}
"#;
";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);

View File

@ -8,10 +8,10 @@ use test::Bencher;
#[bench]
fn bench_eval_module(bench: &mut Bencher) {
let script = r#"
let script = "
fn foo(x) { x + 1 }
fn bar(x) { foo(x) }
"#;
";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
@ -24,10 +24,10 @@ fn bench_eval_module(bench: &mut Bencher) {
let ast = engine
.compile(
r#"
"
fn foo(x) { x - 1 }
testing::bar(41)
"#,
",
)
.unwrap();
@ -41,11 +41,11 @@ fn bench_eval_function_call(bench: &mut Bencher) {
let ast = engine
.compile(
r#"
"
fn foo(x) { x - 1 }
fn bar(x) { foo(x) }
bar(41)
"#,
",
)
.unwrap();

View File

@ -8,13 +8,13 @@ use test::Bencher;
#[bench]
fn bench_iterations_1000(bench: &mut Bencher) {
let script = r#"
let script = "
let x = 1_000;
while x > 0 {
x -= 1;
}
"#;
";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
@ -26,7 +26,7 @@ fn bench_iterations_1000(bench: &mut Bencher) {
#[bench]
fn bench_iterations_fibonacci(bench: &mut Bencher) {
let script = r#"
let script = "
fn fibonacci(n) {
if n < 2 {
n
@ -36,7 +36,7 @@ fn bench_iterations_fibonacci(bench: &mut Bencher) {
}
fibonacci(20)
"#;
";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
@ -48,11 +48,11 @@ fn bench_iterations_fibonacci(bench: &mut Bencher) {
#[bench]
fn bench_iterations_array(bench: &mut Bencher) {
let script = r#"
let script = "
let x = [];
x.pad(1000, 0);
for i in 0..1000 { x[i] = i % 256; }
"#;
";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);
@ -64,10 +64,10 @@ fn bench_iterations_array(bench: &mut Bencher) {
#[bench]
fn bench_iterations_blob(bench: &mut Bencher) {
let script = r#"
let script = "
let x = blob(1000, 0);
for i in 0..1000 { x[i] = i % 256; }
"#;
";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);

View File

@ -64,7 +64,7 @@ fn bench_parse_map(bench: &mut Bencher) {
#[bench]
fn bench_parse_primes(bench: &mut Bencher) {
let script = r#"
let script = "
// This script uses the Sieve of Eratosthenes to calculate prime numbers.
let now = timestamp();
@ -79,7 +79,7 @@ fn bench_parse_primes(bench: &mut Bencher) {
let total_primes_found = 0;
for p in 2..=MAX_NUMBER_TO_CHECK {
for p in 2..MAX_NUMBER_TO_CHECK {
if prime_mask[p] {
print(p);
@ -95,7 +95,7 @@ fn bench_parse_primes(bench: &mut Bencher) {
print(`Total ${total_primes_found} primes.`);
print(`Run time = ${now.elapsed} seconds.`);
"#;
";
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::None);

View File

@ -8,29 +8,29 @@ use test::Bencher;
// This script uses the Sieve of Eratosthenes to calculate prime numbers.
const SCRIPT: &str = r#"
const MAX_NUMBER_TO_CHECK = 1_000; // 168 primes <= 1000
const SCRIPT: &str = "
const MAX_NUMBER_TO_CHECK = 1_000; // 168 primes <= 1000
let prime_mask = [];
prime_mask.pad(MAX_NUMBER_TO_CHECK, true);
let prime_mask = [];
prime_mask.pad(MAX_NUMBER_TO_CHECK, true);
prime_mask[0] = false;
prime_mask[1] = false;
prime_mask[0] = false;
prime_mask[1] = false;
let total_primes_found = 0;
let total_primes_found = 0;
for p in 2..=MAX_NUMBER_TO_CHECK {
if prime_mask[p] {
total_primes_found += 1;
let i = 2 * p;
for p in 2..MAX_NUMBER_TO_CHECK {
if prime_mask[p] {
total_primes_found += 1;
let i = 2 * p;
while i < MAX_NUMBER_TO_CHECK {
prime_mask[i] = false;
i += p;
while i < MAX_NUMBER_TO_CHECK {
prime_mask[i] = false;
i += p;
}
}
}
}
"#;
";
#[bench]
fn bench_eval_primes(bench: &mut Bencher) {

View File

@ -1,6 +1,6 @@
[package]
name = "rhai_codegen"
version = "1.2.0"
version = "1.3.0"
edition = "2018"
authors = ["jhwgh1968", "Stephen Chung"]
description = "Procedural macros support package for Rhai, a scripting language and engine for Rust"
@ -16,7 +16,7 @@ default = []
metadata = []
[dev-dependencies]
rhai = { path = "..", version = "1.1" }
rhai = { path = "..", version = "1.4" }
trybuild = "1"
[dependencies]

View File

@ -117,18 +117,64 @@ pub fn inner_item_attributes<T: ExportedParams>(
attrs: &mut Vec<syn::Attribute>,
attr_name: &str,
) -> syn::Result<T> {
// Find the #[rhai_fn] attribute which will turn be read for the function parameters.
if let Some(rhai_fn_idx) = attrs
// Find the #[rhai_fn] attribute which will turn be read for function parameters.
if let Some(index) = attrs
.iter()
.position(|a| a.path.get_ident().map(|i| *i == attr_name).unwrap_or(false))
{
let rhai_fn_attr = attrs.remove(rhai_fn_idx);
let rhai_fn_attr = attrs.remove(index);
// Cannot have more than one #[rhai_fn]
if let Some(duplicate) = attrs
.iter()
.find(|a| a.path.get_ident().map(|i| *i == attr_name).unwrap_or(false))
{
return Err(syn::Error::new(
duplicate.span(),
format!("duplicated attribute '{}'", attr_name),
));
}
rhai_fn_attr.parse_args_with(T::parse_stream)
} else {
Ok(T::no_attrs())
}
}
#[cfg(feature = "metadata")]
pub fn doc_attributes(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Vec<String>> {
// Find the #[doc] attribute which will turn be read for function documentation.
let mut comments = Vec::new();
while let Some(index) = attrs
.iter()
.position(|attr| attr.path.get_ident().map(|i| *i == "doc").unwrap_or(false))
{
match attrs.remove(index).parse_meta()? {
syn::Meta::NameValue(syn::MetaNameValue {
lit: syn::Lit::Str(s),
..
}) => {
let mut line = s.value();
if line.contains('\n') {
// Must be a block comment `/** ... */`
line.insert_str(0, "/**");
line.push_str("*/");
} else {
// Single line - assume it is `///`
line.insert_str(0, "///");
}
comments.push(line);
}
_ => continue,
}
}
Ok(comments)
}
pub fn collect_cfg_attr(attrs: &[syn::Attribute]) -> Vec<syn::Attribute> {
attrs
.iter()

View File

@ -282,6 +282,8 @@ pub struct ExportedFn {
mut_receiver: bool,
params: ExportedFnParams,
cfg_attrs: Vec<syn::Attribute>,
#[cfg(feature = "metadata")]
comments: Vec<String>,
}
impl Parse for ExportedFn {
@ -404,6 +406,8 @@ impl Parse for ExportedFn {
mut_receiver,
params: Default::default(),
cfg_attrs,
#[cfg(feature = "metadata")]
comments: Default::default(),
})
}
}
@ -503,6 +507,16 @@ impl ExportedFn {
}
}
#[cfg(feature = "metadata")]
pub fn comments(&self) -> &[String] {
&self.comments
}
#[cfg(feature = "metadata")]
pub fn set_comments(&mut self, comments: Vec<String>) {
self.comments = comments
}
pub fn set_cfg_attrs(&mut self, cfg_attrs: Vec<syn::Attribute>) {
self.cfg_attrs = cfg_attrs
}

View File

@ -117,18 +117,22 @@ impl Parse for Module {
syn::Item::Fn(f) => Some(f),
_ => None,
})
.try_fold(Vec::new(), |mut vec, item_fn| {
.try_fold(Vec::new(), |mut vec, item_fn| -> syn::Result<_> {
let params =
crate::attrs::inner_item_attributes(&mut item_fn.attrs, "rhai_fn")?;
syn::parse2::<ExportedFn>(item_fn.to_token_stream())
.and_then(|mut f| {
let f =
syn::parse2(item_fn.to_token_stream()).and_then(|mut f: ExportedFn| {
f.set_params(params)?;
f.set_cfg_attrs(crate::attrs::collect_cfg_attr(&item_fn.attrs));
#[cfg(feature = "metadata")]
f.set_comments(crate::attrs::doc_attributes(&mut item_fn.attrs)?);
Ok(f)
})
.map(|f| vec.push(f))
.map(|_| vec)
})?;
vec.push(f);
Ok(vec)
})?;
// Gather and parse constants definitions.
for item in content.iter() {

View File

@ -166,20 +166,32 @@ pub fn generate_body(
);
#[cfg(feature = "metadata")]
let param_names = quote! {
Some(#fn_token_name::PARAM_NAMES)
};
let (param_names, comments) = (
quote! { Some(#fn_token_name::PARAM_NAMES) },
function
.comments()
.iter()
.map(|s| syn::LitStr::new(s, Span::call_site()))
.collect::<Vec<_>>(),
);
#[cfg(not(feature = "metadata"))]
let param_names = quote! { None };
let (param_names, comments) = (quote! { None }, Vec::<syn::LitStr>::new());
set_fn_statements.push(
set_fn_statements.push(if comments.is_empty() {
syn::parse2::<syn::Stmt>(quote! {
#(#cfg_attrs)*
m.set_fn(#fn_literal, FnNamespace::#ns_str, FnAccess::Public,
#param_names, &[#(#fn_input_types),*], #fn_token_name().into());
})
.unwrap(),
);
.unwrap()
} else {
syn::parse2::<syn::Stmt>(quote! {
#(#cfg_attrs)*
m.set_fn_with_comments(#fn_literal, FnNamespace::#ns_str, FnAccess::Public,
#param_names, &[#(#fn_input_types),*], &[#(#comments),*], #fn_token_name().into());
})
.unwrap()
});
}
gen_fn_tokens.push(quote! {

View File

@ -37,6 +37,49 @@ mod module_tests {
);
}
#[test]
fn one_factory_fn_with_comments_module() {
let input_tokens: TokenStream = quote! {
pub mod one_fn {
/// This is a doc-comment.
/// Another line.
/** block doc-comment */
// Regular comment
/// Final line.
/** doc-comment
in multiple lines
*/
pub fn get_mystic_number() -> INT {
42
}
}
};
let item_mod = syn::parse2::<Module>(input_tokens).unwrap();
assert!(item_mod.consts().is_empty());
assert_eq!(item_mod.fns().len(), 1);
assert_eq!(item_mod.fns()[0].name().to_string(), "get_mystic_number");
assert_eq!(
item_mod.fns()[0]
.comments()
.iter()
.cloned()
.collect::<Vec<_>>(),
vec![
"/// This is a doc-comment.",
"/// Another line.",
"/// block doc-comment ",
"/// Final line.",
"/** doc-comment\n in multiple lines\n */"
]
);
assert_eq!(item_mod.fns()[0].arg_count(), 0);
assert_eq!(
item_mod.fns()[0].return_type().unwrap(),
&syn::parse2::<syn::Type>(quote! { INT }).unwrap()
);
}
#[test]
fn one_single_arg_fn_module() {
let input_tokens: TokenStream = quote! {
@ -323,6 +366,66 @@ mod generate_tests {
assert_streams_eq(item_mod.generate(), expected_tokens);
}
#[test]
fn one_factory_fn_with_comments_module() {
let input_tokens: TokenStream = quote! {
pub mod one_fn {
/// This is a doc-comment.
/// Another line.
/** block doc-comment */
// Regular comment
/// Final line.
/** doc-comment
in multiple lines
*/
pub fn get_mystic_number() -> INT {
42
}
}
};
let expected_tokens = quote! {
pub mod one_fn {
pub fn get_mystic_number() -> INT {
42
}
#[allow(unused_imports)]
use super::*;
pub fn rhai_module_generate() -> Module {
let mut m = Module::new();
rhai_generate_into_module(&mut m, false);
m.build_index();
m
}
#[allow(unused_mut)]
pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) {
m.set_fn_with_comments("get_mystic_number", FnNamespace::Internal, FnAccess::Public,
Some(get_mystic_number_token::PARAM_NAMES), &[], &["/// This is a doc-comment.","/// Another line.","/// block doc-comment ","/// Final line.","/** doc-comment\n in multiple lines\n */"],
get_mystic_number_token().into());
if flatten {} else {}
}
#[allow(non_camel_case_types)]
pub struct get_mystic_number_token();
impl get_mystic_number_token {
pub const PARAM_NAMES: &'static [&'static str] = &["INT"];
#[inline(always)] pub fn param_types() -> [TypeId; 0usize] { [] }
}
impl PluginFunction for get_mystic_number_token {
#[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
Ok(Dynamic::from(get_mystic_number()))
}
#[inline(always)] fn is_method_call(&self) -> bool { false }
}
}
};
let item_mod = syn::parse2::<Module>(input_tokens).unwrap();
assert_streams_eq(item_mod.generate(), expected_tokens);
}
#[test]
fn one_single_arg_global_fn_module() {
let input_tokens: TokenStream = quote! {

View File

@ -0,0 +1,18 @@
use rhai::plugin::*;
#[export_module]
pub mod test_module {
#[rhai_fn(name = "test")]
#[rhai_fn(pure)]
pub fn test_fn(input: Point) -> bool {
input.x > input.y
}
}
fn main() {
if test_module::test_fn(n) {
println!("yes");
} else {
println!("no");
}
}

View File

@ -0,0 +1,17 @@
error: duplicated attribute 'rhai_fn'
--> ui_tests/rhai_fn_duplicate_attr.rs:6:5
|
6 | #[rhai_fn(pure)]
| ^^^^^^^^^^^^^^^^
error[E0433]: failed to resolve: use of undeclared crate or module `test_module`
--> ui_tests/rhai_fn_duplicate_attr.rs:13:8
|
13 | if test_module::test_fn(n) {
| ^^^^^^^^^^^ use of undeclared crate or module `test_module`
error[E0425]: cannot find value `n` in this scope
--> ui_tests/rhai_fn_duplicate_attr.rs:13:29
|
13 | if test_module::test_fn(n) {
| ^ not found in this scope

View File

@ -10,5 +10,5 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
note: required by a bound in `rhai::Dynamic::from`
--> $WORKSPACE/src/types/dynamic.rs
|
| pub fn from<T: Variant + Clone>(mut value: T) -> Self {
| pub fn from<T: Variant + Clone>(value: T) -> Self {
| ^^^^^ required by this bound in `rhai::Dynamic::from`

View File

@ -10,5 +10,5 @@ error[E0277]: the trait bound `NonClonable: Clone` is not satisfied
note: required by a bound in `rhai::Dynamic::from`
--> $WORKSPACE/src/types/dynamic.rs
|
| pub fn from<T: Variant + Clone>(mut value: T) -> Self {
| pub fn from<T: Variant + Clone>(value: T) -> Self {
| ^^^^^ required by this bound in `rhai::Dynamic::from`

20
scripts/doc-comments.rhai Normal file
View File

@ -0,0 +1,20 @@
/// The function `foo`, which prints `hello, world!` and a magic number,
/// accepts three parameters.
///
/// # Parameters
///
/// `x` - `i64`
/// `y` - `string`
/// `z` - `bool`
///
/// # Notes
///
/// This is a doc-comment. It can be obtained with the `metadata` feature.
///
/// An example is the `rhai-doc` app.
///
fn foo(x, y, z) {
print(`hello, world! ${if z { x + y.len() } else { x } }`);
}
foo(39, "bar", true);

View File

@ -11,7 +11,7 @@ for (a, i) in arr {
if a == 3 { break; }
}
//print(a); // <- if you uncomment this line, the script will fail to run
//print(a); // <- if you uncomment this line, the script will fail to compile
// because 'a' is not defined here
for i in range(5, 0, -1) { // runs from 5 down to 1

26
scripts/for3.rhai Normal file
View File

@ -0,0 +1,26 @@
const MAX = 100;
const CHECK = ((MAX - 1) ** 2) * MAX;
print("Ready... Go!");
let now = timestamp();
print(`Creating ${MAX} closures...`);
let list = [];
for i in 0..MAX {
list.push(|| i ** 2);
}
print(`Time = ${now.elapsed} seconds...`);
print(`Summing ${MAX} closures...`);
let sum = 0;
for f in list {
sum += f.call();
}
print(`Sum = ${sum} (should be ${CHECK})`);
print(`Finished. Total run time = ${now.elapsed} seconds.`);

View File

@ -1,11 +1,13 @@
let arr = [42, 123.456, "hello", true, 'x', 999, 1];
let arr = [42, 123.456, "hello", true, "hey", 'x', 999, 1, 2, 3, 4];
for item in arr {
switch item {
42 => print("The Answer!"),
123.456 => print(`Floating point... ${item}`),
"hello" => print(`${item} world!`),
999 => print(`A number: ${item}`),
_ => print(`Something else: <${item}>`)
999 => print(`Got 999: ${item}`),
0..100 if item % 2 == 0 => print(`A small even number: ${item}`),
0..100 => print(`A small odd number: ${item}`),
_ => print(`Something else: <${item}> is ${type_of(item)}`)
}
}

View File

@ -1,10 +1,10 @@
//! Module that defines the `call_fn` API of [`Engine`].
#![cfg(not(feature = "no_function"))]
use crate::engine::{EvalState, Imports};
use crate::engine::{EvalState, GlobalRuntimeState};
use crate::types::dynamic::Variant;
use crate::{
Dynamic, Engine, EvalAltResult, FuncArgs, Position, RhaiResult, Scope, StaticVec, AST,
Dynamic, Engine, FuncArgs, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, AST, ERR,
};
use std::any::type_name;
#[cfg(feature = "no_std")]
@ -60,7 +60,7 @@ impl Engine {
ast: &AST,
name: impl AsRef<str>,
args: impl FuncArgs,
) -> Result<T, Box<EvalAltResult>> {
) -> RhaiResultOf<T> {
let mut arg_values = StaticVec::new_const();
args.parse(&mut arg_values);
@ -69,7 +69,7 @@ impl Engine {
let typ = self.map_type_name(result.type_name());
result.try_cast().ok_or_else(|| {
EvalAltResult::ErrorMismatchOutputType(
ERR::ErrorMismatchOutputType(
self.map_type_name(type_name::<T>()).into(),
typ.into(),
Position::NONE,
@ -90,10 +90,11 @@ impl Engine {
///
/// This function is very low level.
///
/// ## Arguments
/// # Arguments
///
/// All the arguments are _consumed_, meaning that they're replaced by `()`.
/// This is to avoid unnecessarily cloning the arguments.
///
/// Do not use the arguments after this call. If they are needed afterwards,
/// clone them _before_ calling this function.
///
@ -129,10 +130,10 @@ impl Engine {
/// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "bar", None, [])?;
/// assert_eq!(result.cast::<i64>(), 21);
///
/// let mut value: Dynamic = 1_i64.into();
/// let mut value = 1_i64.into();
/// let result = engine.call_fn_raw(&mut scope, &ast, true, true, "action", Some(&mut value), [ 41_i64.into() ])?;
/// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer
/// assert_eq!(value.as_int().expect("value should be INT"), 42);
/// assert_eq!(value.as_int().unwrap(), 42);
///
/// engine.call_fn_raw(&mut scope, &ast, true, false, "decl", None, [ 42_i64.into() ])?;
/// // ^^^^^ do not rewind scope
@ -153,14 +154,13 @@ impl Engine {
arg_values: impl AsMut<[Dynamic]>,
) -> RhaiResult {
let state = &mut EvalState::new();
let mods = &mut Imports::new();
let global = &mut GlobalRuntimeState::new();
let statements = ast.statements();
let orig_scope_len = scope.len();
if eval_ast && !statements.is_empty() {
// Make sure new variables introduced at global level do not _spill_ into the function call
self.eval_global_statements(scope, mods, state, statements, &[ast.as_ref()], 0)?;
self.eval_global_statements(scope, global, state, statements, &[ast.as_ref()], 0)?;
if rewind_scope {
scope.rewind(orig_scope_len);
@ -175,7 +175,7 @@ impl Engine {
let fn_def = ast
.shared_lib()
.get_script_fn(name, args.len())
.ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::NONE))?;
.ok_or_else(|| ERR::ErrorFunctionNotFound(name.into(), Position::NONE))?;
// Check for data race.
#[cfg(not(feature = "no_closure"))]
@ -183,7 +183,7 @@ impl Engine {
let result = self.call_script_fn(
scope,
mods,
global,
state,
&[ast.as_ref()],
&mut this_ptr,

View File

@ -1,7 +1,7 @@
//! Module that defines the public compilation API of [`Engine`].
use crate::parser::ParseState;
use crate::{Engine, ParseError, Scope, AST};
use crate::parser::{ParseResult, ParseState};
use crate::{Engine, Scope, AST};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -26,7 +26,7 @@ impl Engine {
/// # }
/// ```
#[inline(always)]
pub fn compile(&self, script: impl AsRef<str>) -> Result<AST, ParseError> {
pub fn compile(&self, script: impl AsRef<str>) -> ParseResult<AST> {
self.compile_with_scope(&Scope::new(), script)
}
/// Compile a string into an [`AST`] using own scope, which can be used later for evaluation.
@ -67,11 +67,7 @@ impl Engine {
/// # }
/// ```
#[inline(always)]
pub fn compile_with_scope(
&self,
scope: &Scope,
script: impl AsRef<str>,
) -> Result<AST, ParseError> {
pub fn compile_with_scope(&self, scope: &Scope, script: impl AsRef<str>) -> ParseResult<AST> {
self.compile_scripts_with_scope(scope, &[script])
}
/// Compile a string into an [`AST`] using own scope, which can be used later for evaluation,
@ -88,7 +84,7 @@ impl Engine {
&self,
scope: &Scope,
script: impl AsRef<str>,
) -> Result<AST, Box<crate::EvalAltResult>> {
) -> crate::RhaiResultOf<AST> {
use crate::{
ast::{ASTNode, Expr, Stmt},
func::native::shared_take_or_clone,
@ -101,18 +97,16 @@ impl Engine {
resolver: &StaticModuleResolver,
imports: &mut BTreeSet<crate::Identifier>,
) {
ast.walk(
&mut |path| match path.last().expect("contains current node") {
// Collect all `import` statements with a string constant path
ASTNode::Stmt(Stmt::Import(Expr::StringConstant(s, _), _, _))
if !resolver.contains_path(s) && !imports.contains(s.as_str()) =>
{
imports.insert(s.clone().into());
true
}
_ => true,
},
);
ast.walk(&mut |path| match path.last().unwrap() {
// Collect all `import` statements with a string constant path
ASTNode::Stmt(Stmt::Import(Expr::StringConstant(s, _), _, _))
if !resolver.contains_path(s) && !imports.contains(s.as_str()) =>
{
imports.insert(s.clone().into());
true
}
_ => true,
});
}
let mut ast = self.compile_scripts_with_scope(scope, &[script])?;
@ -197,11 +191,11 @@ impl Engine {
/// # }
/// ```
#[inline(always)]
pub fn compile_scripts_with_scope(
pub fn compile_scripts_with_scope<S: AsRef<str>>(
&self,
scope: &Scope,
scripts: &[impl AsRef<str>],
) -> Result<AST, ParseError> {
scripts: impl AsRef<[S]>,
) -> ParseResult<AST> {
self.compile_with_scope_and_optimization_level(
scope,
scripts,
@ -217,14 +211,16 @@ impl Engine {
/// throughout the script _including_ functions. This allows functions to be optimized based on
/// dynamic global constants.
#[inline]
pub(crate) fn compile_with_scope_and_optimization_level(
pub(crate) fn compile_with_scope_and_optimization_level<S: AsRef<str>>(
&self,
scope: &Scope,
scripts: &[impl AsRef<str>],
scripts: impl AsRef<[S]>,
#[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel,
) -> Result<AST, ParseError> {
let (stream, tokenizer_control) =
self.lex_raw(scripts, self.token_mapper.as_ref().map(Box::as_ref));
) -> ParseResult<AST> {
let (stream, tokenizer_control) = self.lex_raw(
scripts.as_ref(),
self.token_mapper.as_ref().map(Box::as_ref),
);
let mut state = ParseState::new(self, tokenizer_control);
self.parse(
&mut stream.peekable(),
@ -255,7 +251,7 @@ impl Engine {
/// # }
/// ```
#[inline(always)]
pub fn compile_expression(&self, script: impl AsRef<str>) -> Result<AST, ParseError> {
pub fn compile_expression(&self, script: impl AsRef<str>) -> ParseResult<AST> {
self.compile_expression_with_scope(&Scope::new(), script)
}
/// Compile a string containing an expression into an [`AST`] using own scope,
@ -295,7 +291,7 @@ impl Engine {
&self,
scope: &Scope,
script: impl AsRef<str>,
) -> Result<AST, ParseError> {
) -> ParseResult<AST> {
let scripts = [script];
let (stream, tokenizer_control) =
self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref));
@ -359,14 +355,14 @@ impl Engine {
&self,
json: impl AsRef<str>,
has_null: bool,
) -> Result<crate::Map, Box<crate::EvalAltResult>> {
) -> crate::RhaiResultOf<crate::Map> {
use crate::tokenizer::Token;
fn parse_json_inner(
engine: &Engine,
json: &str,
has_null: bool,
) -> Result<crate::Map, Box<crate::EvalAltResult>> {
) -> crate::RhaiResultOf<crate::Map> {
let mut scope = Scope::new();
let json_text = json.trim_start();
let scripts = if json_text.starts_with(Token::MapStart.literal_syntax()) {
@ -374,7 +370,7 @@ impl Engine {
} else if json_text.starts_with(Token::LeftBrace.literal_syntax()) {
["#", json_text]
} else {
return Err(crate::ParseErrorType::MissingToken(
return Err(crate::PERR::MissingToken(
Token::LeftBrace.syntax().into(),
"to start a JSON object hash".into(),
)

View File

@ -3,12 +3,12 @@
use crate::ast::Expr;
use crate::engine::EvalContext;
use crate::func::native::SendSync;
use crate::parser::ParseResult;
use crate::r#unsafe::unsafe_try_cast;
use crate::tokenizer::{is_valid_identifier, Token};
use crate::types::dynamic::Variant;
use crate::{
Engine, Identifier, ImmutableString, LexError, ParseError, Position, RhaiResult, Shared,
StaticVec, INT,
Engine, Identifier, ImmutableString, LexError, Position, RhaiResult, Shared, StaticVec, INT,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -47,11 +47,11 @@ pub type FnCustomSyntaxEval = dyn Fn(&mut EvalContext, &[Expression]) -> RhaiRes
/// A general expression parsing trait object.
#[cfg(not(feature = "sync"))]
pub type FnCustomSyntaxParse =
dyn Fn(&[ImmutableString], &str) -> Result<Option<ImmutableString>, ParseError>;
dyn Fn(&[ImmutableString], &str) -> ParseResult<Option<ImmutableString>>;
/// A general expression parsing trait object.
#[cfg(feature = "sync")]
pub type FnCustomSyntaxParse =
dyn Fn(&[ImmutableString], &str) -> Result<Option<ImmutableString>, ParseError> + Send + Sync;
dyn Fn(&[ImmutableString], &str) -> ParseResult<Option<ImmutableString>> + Send + Sync;
/// An expression sub-tree in an [`AST`][crate::AST].
#[derive(Debug, Clone)]
@ -65,11 +65,17 @@ impl<'a> From<&'a Expr> for Expression<'a> {
}
impl Expression<'_> {
/// If this expression is a variable name, return it. Otherwise [`None`].
/// Get the value of this expression if it is a variable name or a string constant.
///
/// Returns [`None`] also if the constant is not of the specified type.
#[inline(always)]
#[must_use]
pub fn get_variable_name(&self) -> Option<&str> {
self.0.get_variable_name(true)
pub fn get_string_value(&self) -> Option<&str> {
match self.0 {
Expr::Variable(_, _, x) if x.1.is_none() => Some(x.2.as_str()),
Expr::StringConstant(x, _) => Some(x.as_str()),
_ => None,
}
}
/// Get the position of this expression.
#[inline(always)]
@ -89,38 +95,39 @@ impl Expression<'_> {
if TypeId::of::<T>() == TypeId::of::<INT>() {
return match self.0 {
Expr::IntegerConstant(x, _) => unsafe_try_cast(*x).ok(),
Expr::IntegerConstant(x, _) => unsafe_try_cast(*x),
_ => None,
};
}
#[cfg(not(feature = "no_float"))]
if TypeId::of::<T>() == TypeId::of::<crate::FLOAT>() {
return match self.0 {
Expr::FloatConstant(x, _) => unsafe_try_cast(*x).ok(),
Expr::FloatConstant(x, _) => unsafe_try_cast(*x),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<char>() {
return match self.0 {
Expr::CharConstant(x, _) => unsafe_try_cast(*x).ok(),
Expr::CharConstant(x, _) => unsafe_try_cast(*x),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<ImmutableString>() {
return match self.0 {
Expr::StringConstant(x, _) => unsafe_try_cast(x.clone()).ok(),
Expr::StringConstant(x, _) => unsafe_try_cast(x.clone()),
Expr::Variable(_, _, x) => unsafe_try_cast(Into::<ImmutableString>::into(&x.2)),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<bool>() {
return match self.0 {
Expr::BoolConstant(x, _) => unsafe_try_cast(*x).ok(),
Expr::BoolConstant(x, _) => unsafe_try_cast(*x),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<()>() {
return match self.0 {
Expr::Unit(_) => unsafe_try_cast(()).ok(),
Expr::Unit(_) => unsafe_try_cast(()),
_ => None,
};
}
@ -154,7 +161,7 @@ impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_> {
pub fn eval_expression_tree(&mut self, expr: &Expression) -> RhaiResult {
self.engine.eval_expr(
self.scope,
self.mods,
self.global,
self.state,
self.lib,
self.this_ptr,
@ -204,15 +211,15 @@ impl Engine {
/// does NOT count, so `false` should be passed.
pub fn register_custom_syntax<S: AsRef<str> + Into<Identifier>>(
&mut self,
symbols: &[S],
symbols: impl AsRef<[S]>,
scope_may_be_changed: bool,
func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static,
) -> Result<&mut Self, ParseError> {
) -> ParseResult<&mut Self> {
use markers::*;
let mut segments = StaticVec::<ImmutableString>::new();
for s in symbols {
for s in symbols.as_ref() {
let s = s.as_ref().trim();
// Skip empty symbols
@ -344,11 +351,11 @@ impl Engine {
///
/// * `Ok(None)`: parsing complete and there are no more symbols to match.
/// * `Ok(Some(symbol))`: the next symbol to match, which can also be `$expr$`, `$ident$` or `$block$`.
/// * `Err(ParseError)`: error that is reflected back to the [`Engine`], normally `ParseError(ParseErrorType::BadInput(LexError::ImproperSymbol(message)), Position::NONE)` to indicate a syntax error, but it can be any [`ParseError`].
/// * `Err(ParseError)`: error that is reflected back to the [`Engine`], normally `ParseError(ParseErrorType::BadInput(LexError::ImproperSymbol(message)), Position::NONE)` to indicate a syntax error, but it can be any [`ParseError`][crate::ParseError].
pub fn register_custom_syntax_raw(
&mut self,
key: impl Into<Identifier>,
parse: impl Fn(&[ImmutableString], &str) -> Result<Option<ImmutableString>, ParseError>
parse: impl Fn(&[ImmutableString], &str) -> ParseResult<Option<ImmutableString>>
+ SendSync
+ 'static,
scope_may_be_changed: bool,

View File

@ -1,8 +1,8 @@
//! Module containing all deprecated API that will be removed in the next major version.
use crate::{
Dynamic, Engine, EvalAltResult, FnPtr, ImmutableString, NativeCallContext, RhaiResult, Scope,
AST,
Dynamic, Engine, EvalAltResult, Expression, FnPtr, ImmutableString, NativeCallContext,
RhaiResult, RhaiResultOf, Scope, AST,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -20,9 +20,10 @@ impl Engine {
/// This method will be removed in the next major version.
#[deprecated(since = "1.1.0", note = "use `run_file` instead")]
#[cfg(not(feature = "no_std"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
#[inline(always)]
pub fn consume_file(&self, path: std::path::PathBuf) -> Result<(), Box<EvalAltResult>> {
pub fn consume_file(&self, path: std::path::PathBuf) -> RhaiResultOf<()> {
self.run_file(path)
}
@ -38,13 +39,14 @@ impl Engine {
/// This method will be removed in the next major version.
#[deprecated(since = "1.1.0", note = "use `run_file_with_scope` instead")]
#[cfg(not(feature = "no_std"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
#[inline(always)]
pub fn consume_file_with_scope(
&self,
scope: &mut Scope,
path: std::path::PathBuf,
) -> Result<(), Box<EvalAltResult>> {
) -> RhaiResultOf<()> {
self.run_file_with_scope(scope, path)
}
@ -58,7 +60,7 @@ impl Engine {
/// This method will be removed in the next major version.
#[deprecated(since = "1.1.0", note = "use `run` instead")]
#[inline(always)]
pub fn consume(&self, script: &str) -> Result<(), Box<EvalAltResult>> {
pub fn consume(&self, script: &str) -> RhaiResultOf<()> {
self.run(script)
}
@ -72,11 +74,7 @@ impl Engine {
/// This method will be removed in the next major version.
#[deprecated(since = "1.1.0", note = "use `run_with_scope` instead")]
#[inline(always)]
pub fn consume_with_scope(
&self,
scope: &mut Scope,
script: &str,
) -> Result<(), Box<EvalAltResult>> {
pub fn consume_with_scope(&self, scope: &mut Scope, script: &str) -> RhaiResultOf<()> {
self.run_with_scope(scope, script)
}
@ -90,7 +88,7 @@ impl Engine {
/// This method will be removed in the next major version.
#[deprecated(since = "1.1.0", note = "use `run_ast` instead")]
#[inline(always)]
pub fn consume_ast(&self, ast: &AST) -> Result<(), Box<EvalAltResult>> {
pub fn consume_ast(&self, ast: &AST) -> RhaiResultOf<()> {
self.run_ast(ast)
}
@ -104,11 +102,7 @@ impl Engine {
/// This method will be removed in the next major version.
#[deprecated(since = "1.1.0", note = "use `run_ast_with_scope` instead")]
#[inline(always)]
pub fn consume_ast_with_scope(
&self,
scope: &mut Scope,
ast: &AST,
) -> Result<(), Box<EvalAltResult>> {
pub fn consume_ast_with_scope(&self, scope: &mut Scope, ast: &AST) -> RhaiResultOf<()> {
self.run_ast_with_scope(scope, ast)
}
/// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments
@ -128,10 +122,11 @@ impl Engine {
///
/// This function is very low level.
///
/// ## Arguments
/// # Arguments
///
/// All the arguments are _consumed_, meaning that they're replaced by `()`.
/// This is to avoid unnecessarily cloning the arguments.
///
/// Do not use the arguments after this call. If they are needed afterwards,
/// clone them _before_ calling this function.
///
@ -228,7 +223,7 @@ impl NativeCallContext<'_> {
///
/// This function is very low level.
///
/// ## Arguments
/// # Arguments
///
/// All arguments may be _consumed_, meaning that they may be replaced by `()`. This is to avoid
/// unnecessarily cloning the arguments.
@ -258,7 +253,7 @@ impl NativeCallContext<'_> {
#[allow(useless_deprecated)]
#[deprecated(since = "1.2.0", note = "explicitly wrap `EvalAltResult` in `Err`")]
impl<T> From<EvalAltResult> for Result<T, Box<EvalAltResult>> {
impl<T> From<EvalAltResult> for RhaiResultOf<T> {
#[inline(always)]
fn from(err: EvalAltResult) -> Self {
Err(err.into())
@ -284,10 +279,11 @@ impl FnPtr {
///
/// This function is very low level.
///
/// ## Arguments
/// # Arguments
///
/// All the arguments are _consumed_, meaning that they're replaced by `()`.
/// This is to avoid unnecessarily cloning the arguments.
///
/// Do not use the arguments after this call. If they are needed afterwards,
/// clone them _before_ calling this function.
#[deprecated(
@ -304,3 +300,19 @@ impl FnPtr {
self.call_raw(context, this_ptr, arg_values)
}
}
impl Expression<'_> {
/// If this expression is a variable name, return it. Otherwise [`None`].
///
/// # Deprecated
///
/// This method is deprecated. Use [`get_string_value`][Expression::get_string_value] instead.
///
/// This method will be removed in the next major version.
#[deprecated(since = "1.4.0", note = "use `get_string_value` instead")]
#[inline(always)]
#[must_use]
pub fn get_variable_name(&self) -> Option<&str> {
self.get_string_value()
}
}

View File

@ -1,9 +1,9 @@
//! Module that defines the public evaluation API of [`Engine`].
use crate::engine::{EvalState, Imports};
use crate::engine::{EvalState, GlobalRuntimeState};
use crate::parser::ParseState;
use crate::types::dynamic::Variant;
use crate::{Dynamic, Engine, EvalAltResult, Module, Position, RhaiResult, Scope, AST};
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR};
use std::any::type_name;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -24,7 +24,7 @@ impl Engine {
/// # }
/// ```
#[inline(always)]
pub fn eval<T: Variant + Clone>(&self, script: &str) -> Result<T, Box<EvalAltResult>> {
pub fn eval<T: Variant + Clone>(&self, script: &str) -> RhaiResultOf<T> {
self.eval_with_scope(&mut Scope::new(), script)
}
/// Evaluate a string with own scope.
@ -60,7 +60,7 @@ impl Engine {
&self,
scope: &mut Scope,
script: &str,
) -> Result<T, Box<EvalAltResult>> {
) -> RhaiResultOf<T> {
let ast = self.compile_with_scope_and_optimization_level(
scope,
&[script],
@ -84,10 +84,7 @@ impl Engine {
/// # }
/// ```
#[inline(always)]
pub fn eval_expression<T: Variant + Clone>(
&self,
script: &str,
) -> Result<T, Box<EvalAltResult>> {
pub fn eval_expression<T: Variant + Clone>(&self, script: &str) -> RhaiResultOf<T> {
self.eval_expression_with_scope(&mut Scope::new(), script)
}
/// Evaluate a string containing an expression with own scope.
@ -113,7 +110,7 @@ impl Engine {
&self,
scope: &mut Scope,
script: &str,
) -> Result<T, Box<EvalAltResult>> {
) -> RhaiResultOf<T> {
let scripts = [script];
let (stream, tokenizer_control) =
self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref));
@ -149,7 +146,7 @@ impl Engine {
/// # }
/// ```
#[inline(always)]
pub fn eval_ast<T: Variant + Clone>(&self, ast: &AST) -> Result<T, Box<EvalAltResult>> {
pub fn eval_ast<T: Variant + Clone>(&self, ast: &AST) -> RhaiResultOf<T> {
self.eval_ast_with_scope(&mut Scope::new(), ast)
}
/// Evaluate an [`AST`] with own scope.
@ -186,15 +183,15 @@ impl Engine {
&self,
scope: &mut Scope,
ast: &AST,
) -> Result<T, Box<EvalAltResult>> {
let mods = &mut Imports::new();
) -> RhaiResultOf<T> {
let global = &mut GlobalRuntimeState::new();
let result = self.eval_ast_with_scope_raw(scope, mods, ast, 0)?;
let result = self.eval_ast_with_scope_raw(scope, global, ast, 0)?;
let typ = self.map_type_name(result.type_name());
result.try_cast::<T>().ok_or_else(|| {
EvalAltResult::ErrorMismatchOutputType(
ERR::ErrorMismatchOutputType(
self.map_type_name(type_name::<T>()).into(),
typ.into(),
Position::NONE,
@ -207,17 +204,16 @@ impl Engine {
pub(crate) fn eval_ast_with_scope_raw<'a>(
&self,
scope: &mut Scope,
mods: &mut Imports,
global: &mut GlobalRuntimeState,
ast: &'a AST,
level: usize,
) -> RhaiResult {
let mut state = EvalState::new();
if ast.source_raw().is_some() {
mods.source = ast.source_raw().cloned();
}
global.source = ast.source_raw().clone();
#[cfg(not(feature = "no_module"))]
{
mods.embedded_module_resolver = ast.resolver().cloned();
global.embedded_module_resolver = ast.resolver().cloned();
}
let statements = ast.statements();
@ -235,6 +231,6 @@ impl Engine {
} else {
&lib
};
self.eval_global_statements(scope, mods, &mut state, statements, lib, level)
self.eval_global_statements(scope, global, &mut state, statements, lib, level)
}
}

View File

@ -2,7 +2,7 @@
use crate::engine::EvalContext;
use crate::func::SendSync;
use crate::{Dynamic, Engine, EvalAltResult, Position};
use crate::{Dynamic, Engine, Position, RhaiResultOf};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -58,7 +58,7 @@ impl Engine {
#[inline(always)]
pub fn on_var(
&mut self,
callback: impl Fn(&str, usize, &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>
callback: impl Fn(&str, usize, &EvalContext) -> RhaiResultOf<Option<Dynamic>>
+ SendSync
+ 'static,
) -> &mut Self {

View File

@ -1,19 +1,20 @@
//! Module that defines the public file-based API of [`Engine`].
#![cfg(not(feature = "no_std"))]
#![cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#![cfg(not(target_arch = "wasm32"))]
#![cfg(not(target_arch = "wasm64"))]
use crate::types::dynamic::Variant;
use crate::{Engine, EvalAltResult, Scope, AST};
use crate::{Engine, RhaiResultOf, Scope, AST, ERR};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
impl Engine {
/// Read the contents of a file into a string.
fn read_file(path: std::path::PathBuf) -> Result<String, Box<EvalAltResult>> {
fn read_file(path: std::path::PathBuf) -> RhaiResultOf<String> {
use std::io::Read;
let mut f = std::fs::File::open(path.clone()).map_err(|err| {
EvalAltResult::ErrorSystem(
ERR::ErrorSystem(
format!("Cannot open script file '{}'", path.to_string_lossy()),
err.into(),
)
@ -22,7 +23,7 @@ impl Engine {
let mut contents = String::new();
f.read_to_string(&mut contents).map_err(|err| {
EvalAltResult::ErrorSystem(
ERR::ErrorSystem(
format!("Cannot read script file '{}'", path.to_string_lossy()),
err.into(),
)
@ -62,7 +63,7 @@ impl Engine {
/// # }
/// ```
#[inline(always)]
pub fn compile_file(&self, path: std::path::PathBuf) -> Result<AST, Box<EvalAltResult>> {
pub fn compile_file(&self, path: std::path::PathBuf) -> RhaiResultOf<AST> {
self.compile_file_with_scope(&Scope::new(), path)
}
/// Compile a script file into an [`AST`] using own scope, which can be used later for evaluation.
@ -103,7 +104,7 @@ impl Engine {
&self,
scope: &Scope,
path: std::path::PathBuf,
) -> Result<AST, Box<EvalAltResult>> {
) -> RhaiResultOf<AST> {
Self::read_file(path).and_then(|contents| Ok(self.compile_with_scope(scope, &contents)?))
}
/// Evaluate a script file.
@ -124,10 +125,7 @@ impl Engine {
/// # }
/// ```
#[inline]
pub fn eval_file<T: Variant + Clone>(
&self,
path: std::path::PathBuf,
) -> Result<T, Box<EvalAltResult>> {
pub fn eval_file<T: Variant + Clone>(&self, path: std::path::PathBuf) -> RhaiResultOf<T> {
Self::read_file(path).and_then(|contents| self.eval::<T>(&contents))
}
/// Evaluate a script file with own scope.
@ -162,16 +160,14 @@ impl Engine {
&self,
scope: &mut Scope,
path: std::path::PathBuf,
) -> Result<T, Box<EvalAltResult>> {
) -> RhaiResultOf<T> {
Self::read_file(path).and_then(|contents| self.eval_with_scope(scope, &contents))
}
/// Evaluate a file, returning any error (if any).
///
/// Not available under `no_std` or `WASM`.
#[cfg(not(feature = "no_std"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[inline]
pub fn run_file(&self, path: std::path::PathBuf) -> Result<(), Box<EvalAltResult>> {
pub fn run_file(&self, path: std::path::PathBuf) -> RhaiResultOf<()> {
Self::read_file(path).and_then(|contents| self.run(&contents))
}
/// Evaluate a file with own scope, returning any error (if any).
@ -183,14 +179,12 @@ impl Engine {
/// If not [`OptimizationLevel::None`][crate::OptimizationLevel::None], constants defined within
/// the scope are propagated throughout the script _including_ functions. This allows functions
/// to be optimized based on dynamic global constants.
#[cfg(not(feature = "no_std"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[inline]
pub fn run_file_with_scope(
&self,
scope: &mut Scope,
path: std::path::PathBuf,
) -> Result<(), Box<EvalAltResult>> {
) -> RhaiResultOf<()> {
Self::read_file(path).and_then(|contents| self.run_with_scope(scope, &contents))
}
}

View File

@ -1,6 +1,7 @@
//! Settings for [`Engine`]'s limitations.
#![cfg(not(feature = "unchecked"))]
use super::default_limits;
use crate::Engine;
use std::num::{NonZeroU64, NonZeroUsize};
#[cfg(feature = "no_std")]
@ -56,10 +57,10 @@ impl Limits {
pub const fn new() -> Self {
Self {
#[cfg(not(feature = "no_function"))]
max_call_stack_depth: crate::engine::MAX_CALL_STACK_DEPTH,
max_expr_depth: NonZeroUsize::new(crate::engine::MAX_EXPR_DEPTH),
max_call_stack_depth: default_limits::MAX_CALL_STACK_DEPTH,
max_expr_depth: NonZeroUsize::new(default_limits::MAX_EXPR_DEPTH),
#[cfg(not(feature = "no_function"))]
max_function_expr_depth: NonZeroUsize::new(crate::engine::MAX_FUNCTION_EXPR_DEPTH),
max_function_expr_depth: NonZeroUsize::new(default_limits::MAX_FUNCTION_EXPR_DEPTH),
max_operations: None,
#[cfg(not(feature = "no_module"))]
max_modules: usize::MAX,

View File

@ -18,6 +18,8 @@ pub mod limits;
pub mod events;
pub mod custom_syntax;
pub mod deprecated;
use crate::engine::Precedence;
@ -27,6 +29,34 @@ use crate::{Engine, Identifier};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
pub mod default_limits {
#[cfg(not(feature = "unchecked"))]
#[cfg(debug_assertions)]
#[cfg(not(feature = "no_function"))]
pub const MAX_CALL_STACK_DEPTH: usize = 8;
#[cfg(not(feature = "unchecked"))]
#[cfg(debug_assertions)]
pub const MAX_EXPR_DEPTH: usize = 32;
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_function"))]
#[cfg(debug_assertions)]
pub const MAX_FUNCTION_EXPR_DEPTH: usize = 16;
#[cfg(not(feature = "unchecked"))]
#[cfg(not(debug_assertions))]
#[cfg(not(feature = "no_function"))]
pub const MAX_CALL_STACK_DEPTH: usize = 64;
#[cfg(not(feature = "unchecked"))]
#[cfg(not(debug_assertions))]
pub const MAX_EXPR_DEPTH: usize = 64;
#[cfg(not(feature = "unchecked"))]
#[cfg(not(feature = "no_function"))]
#[cfg(not(debug_assertions))]
pub const MAX_FUNCTION_EXPR_DEPTH: usize = 32;
pub const MAX_DYNAMIC_PARAMETERS: usize = 16;
}
/// Script optimization API.
#[cfg(not(feature = "no_optimize"))]
impl Engine {
@ -83,17 +113,15 @@ impl Engine {
.map(|f| {
f.func
.get_script_fn_def()
.expect("scripted function")
.expect("script-defined function")
.clone()
})
.collect();
let statements = std::mem::take(ast.statements_mut());
crate::optimizer::optimize_into_ast(
self,
scope,
statements,
ast.take_statements(),
#[cfg(not(feature = "no_function"))]
lib,
optimization_level,
@ -185,7 +213,7 @@ impl Engine {
/// ```
pub fn register_custom_operator(
&mut self,
keyword: impl AsRef<str> + Into<Identifier>,
keyword: impl AsRef<str>,
precedence: u8,
) -> Result<&mut Self, String> {
let precedence = Precedence::new(precedence);
@ -219,7 +247,8 @@ impl Engine {
}
// Add to custom keywords
self.custom_keywords.insert(keyword.into(), precedence);
self.custom_keywords
.insert(keyword.as_ref().into(), precedence);
Ok(self)
}

View File

@ -3,8 +3,7 @@
use crate::func::{FnCallArgs, RegisterNativeFunction, SendSync};
use crate::types::dynamic::Variant;
use crate::{
Engine, EvalAltResult, FnAccess, FnNamespace, Identifier, Module, NativeCallContext, Shared,
SmartString,
Engine, FnAccess, FnNamespace, Identifier, Module, NativeCallContext, RhaiResultOf, Shared,
};
use std::any::{type_name, TypeId};
#[cfg(feature = "no_std")]
@ -15,13 +14,14 @@ impl Engine {
#[inline(always)]
#[allow(dead_code)]
pub(crate) fn global_namespace(&self) -> &Module {
self.global_modules.first().expect("not empty")
self.global_modules.first().unwrap()
}
/// Get a mutable reference to the global namespace module
/// (which is the first module in `global_modules`).
#[inline(always)]
pub(crate) fn global_namespace_mut(&mut self) -> &mut Module {
Shared::get_mut(self.global_modules.first_mut().expect("not empty")).expect("not shared")
let module = self.global_modules.first_mut().unwrap();
Shared::get_mut(module).expect("not shared")
}
/// Register a custom function with the [`Engine`].
///
@ -69,18 +69,20 @@ impl Engine {
}
#[cfg(feature = "metadata")]
let param_type_names: Option<crate::StaticVec<_>> =
Some(param_type_names.iter().map(|ty| ty.as_str()).collect());
let param_type_names: crate::StaticVec<_> =
param_type_names.iter().map(|ty| ty.as_str()).collect();
#[cfg(feature = "metadata")]
let param_type_names = Some(param_type_names.as_ref());
#[cfg(not(feature = "metadata"))]
let param_type_names: Option<[&str; 0]> = None;
let param_type_names: Option<&[&str]> = None;
self.global_namespace_mut().set_fn(
name,
FnNamespace::Global,
FnAccess::Public,
param_type_names.as_ref().map(|v| v.as_ref()),
&param_types,
param_type_names,
param_types,
func.into_callable_function(),
);
self
@ -113,7 +115,7 @@ impl Engine {
pub fn register_result_fn<N, A, F, R>(&mut self, name: N, func: F) -> &mut Self
where
N: AsRef<str> + Into<Identifier>,
F: RegisterNativeFunction<A, Result<R, Box<EvalAltResult>>>,
F: RegisterNativeFunction<A, RhaiResultOf<R>>,
{
let param_types = F::param_types();
@ -127,18 +129,20 @@ impl Engine {
.collect();
#[cfg(feature = "metadata")]
let param_type_names: Option<crate::StaticVec<_>> =
Some(param_type_names.iter().map(|ty| ty.as_str()).collect());
let param_type_names: crate::StaticVec<_> =
param_type_names.iter().map(|ty| ty.as_str()).collect();
#[cfg(feature = "metadata")]
let param_type_names = Some(param_type_names.as_ref());
#[cfg(not(feature = "metadata"))]
let param_type_names: Option<[&str; 0]> = None;
let param_type_names: Option<&[&str]> = None;
self.global_namespace_mut().set_fn(
name,
FnNamespace::Global,
FnAccess::Public,
param_type_names.as_ref().map(|v| v.as_ref()),
&param_types,
param_type_names,
param_types,
func.into_callable_function(),
);
self
@ -150,7 +154,7 @@ impl Engine {
/// This function is very low level. It takes a list of [`TypeId`][std::any::TypeId]'s
/// indicating the actual types of the parameters.
///
/// ## Arguments
/// # Arguments
///
/// Arguments are simply passed in as a mutable array of [`&mut Dynamic`][crate::Dynamic].
/// The arguments are guaranteed to be of the correct types matching the [`TypeId`][std::any::TypeId]'s.
@ -165,10 +169,8 @@ impl Engine {
pub fn register_raw_fn<N, T>(
&mut self,
name: N,
arg_types: &[TypeId],
func: impl Fn(NativeCallContext, &mut FnCallArgs) -> Result<T, Box<EvalAltResult>>
+ SendSync
+ 'static,
arg_types: impl AsRef<[TypeId]>,
func: impl Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResultOf<T> + SendSync + 'static,
) -> &mut Self
where
N: AsRef<str> + Into<Identifier>,
@ -195,8 +197,12 @@ impl Engine {
/// }
///
/// impl TestStruct {
/// fn new() -> Self { Self { field: 1 } }
/// fn update(&mut self, offset: i64) { self.field += offset; }
/// fn new() -> Self {
/// Self { field: 1 }
/// }
/// fn update(&mut self, offset: i64) {
/// self.field += offset;
/// }
/// }
///
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
@ -235,7 +241,9 @@ impl Engine {
/// }
///
/// impl TestStruct {
/// fn new() -> Self { Self { field: 1 } }
/// fn new() -> Self {
/// Self { field: 1 }
/// }
/// }
///
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
@ -276,8 +284,8 @@ impl Engine {
#[inline(always)]
pub fn register_type_with_name_raw(
&mut self,
fully_qualified_type_path: impl Into<SmartString>,
name: impl Into<SmartString>,
fully_qualified_type_path: impl Into<Identifier>,
name: impl Into<Identifier>,
) -> &mut Self {
// Add the pretty-print type name into the map
self.type_names
@ -310,9 +318,13 @@ impl Engine {
/// }
///
/// impl TestStruct {
/// fn new() -> Self { Self { field: 1 } }
/// fn new() -> Self {
/// Self { field: 1 }
/// }
/// // Even a getter must start with `&mut self` and not `&self`.
/// fn get_field(&mut self) -> i64 { self.field }
/// fn get_field(&mut self) -> i64 {
/// self.field
/// }
/// }
///
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
@ -338,7 +350,7 @@ impl Engine {
name: impl AsRef<str>,
get_fn: impl Fn(&mut T) -> V + SendSync + 'static,
) -> &mut Self {
self.register_fn(&crate::engine::make_getter(name), get_fn)
self.register_fn(crate::engine::make_getter(name.as_ref()).as_str(), get_fn)
}
/// Register a getter function for a member of a registered type with the [`Engine`].
///
@ -357,7 +369,9 @@ impl Engine {
/// }
///
/// impl TestStruct {
/// fn new() -> Self { Self { field: 1 } }
/// fn new() -> Self {
/// Self { field: 1 }
/// }
/// // Even a getter must start with `&mut self` and not `&self`.
/// fn get_field(&mut self) -> Result<i64, Box<EvalAltResult>> {
/// Ok(self.field)
@ -383,9 +397,9 @@ impl Engine {
pub fn register_get_result<T: Variant + Clone, V: Variant + Clone>(
&mut self,
name: impl AsRef<str>,
get_fn: impl Fn(&mut T) -> Result<V, Box<EvalAltResult>> + SendSync + 'static,
get_fn: impl Fn(&mut T) -> RhaiResultOf<V> + SendSync + 'static,
) -> &mut Self {
self.register_result_fn(&crate::engine::make_getter(name), get_fn)
self.register_result_fn(crate::engine::make_getter(name.as_ref()).as_str(), get_fn)
}
/// Register a setter function for a member of a registered type with the [`Engine`].
///
@ -400,8 +414,12 @@ impl Engine {
/// }
///
/// impl TestStruct {
/// fn new() -> Self { Self { field: 1 } }
/// fn set_field(&mut self, new_val: i64) { self.field = new_val; }
/// fn new() -> Self {
/// Self { field: 1 }
/// }
/// fn set_field(&mut self, new_val: i64) {
/// self.field = new_val;
/// }
/// }
///
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
@ -431,7 +449,7 @@ impl Engine {
name: impl AsRef<str>,
set_fn: impl Fn(&mut T, V) + SendSync + 'static,
) -> &mut Self {
self.register_fn(&crate::engine::make_setter(name), set_fn)
self.register_fn(crate::engine::make_setter(name.as_ref()).as_str(), set_fn)
}
/// Register a setter function for a member of a registered type with the [`Engine`].
///
@ -448,8 +466,10 @@ impl Engine {
/// }
///
/// impl TestStruct {
/// fn new() -> Self { Self { field: 1 } }
/// fn set_field(&mut self, new_val: i64) -> Result<(), Box<EvalAltResult>> {
/// fn new() -> Self {
/// Self { field: 1 }
/// }
/// fn set_field(&mut self, new_val: i64) -> Result<(), Box<rhai::EvalAltResult>> {
/// self.field = new_val;
/// Ok(())
/// }
@ -478,9 +498,9 @@ impl Engine {
pub fn register_set_result<T: Variant + Clone, V: Variant + Clone>(
&mut self,
name: impl AsRef<str>,
set_fn: impl Fn(&mut T, V) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
set_fn: impl Fn(&mut T, V) -> RhaiResultOf<()> + SendSync + 'static,
) -> &mut Self {
self.register_result_fn(&crate::engine::make_setter(name), set_fn)
self.register_result_fn(crate::engine::make_setter(name.as_ref()).as_str(), set_fn)
}
/// Short-hand for registering both getter and setter functions
/// of a registered type with the [`Engine`].
@ -498,10 +518,16 @@ impl Engine {
/// }
///
/// impl TestStruct {
/// fn new() -> Self { Self { field: 1 } }
/// fn new() -> Self {
/// Self { field: 1 }
/// }
/// // Even a getter must start with `&mut self` and not `&self`.
/// fn get_field(&mut self) -> i64 { self.field }
/// fn set_field(&mut self, new_val: i64) { self.field = new_val; }
/// fn get_field(&mut self) -> i64 {
/// self.field
/// }
/// fn set_field(&mut self, new_val: i64) {
/// self.field = new_val;
/// }
/// }
///
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
@ -552,9 +578,13 @@ impl Engine {
/// }
///
/// impl TestStruct {
/// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } }
/// fn new() -> Self {
/// Self { fields: vec![1, 2, 3, 4, 5] }
/// }
/// // Even a getter must start with `&mut self` and not `&self`.
/// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] }
/// fn get_field(&mut self, index: i64) -> i64 {
/// self.fields[index as usize]
/// }
/// }
///
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
@ -625,7 +655,9 @@ impl Engine {
/// }
///
/// impl TestStruct {
/// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } }
/// fn new() -> Self {
/// Self { fields: vec![1, 2, 3, 4, 5] }
/// }
/// // Even a getter must start with `&mut self` and not `&self`.
/// fn get_field(&mut self, index: i64) -> Result<i64, Box<EvalAltResult>> {
/// Ok(self.fields[index as usize])
@ -657,7 +689,7 @@ impl Engine {
V: Variant + Clone,
>(
&mut self,
get_fn: impl Fn(&mut T, X) -> Result<V, Box<EvalAltResult>> + SendSync + 'static,
get_fn: impl Fn(&mut T, X) -> RhaiResultOf<V> + SendSync + 'static,
) -> &mut Self {
#[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<crate::Array>() {
@ -698,8 +730,12 @@ impl Engine {
/// }
///
/// impl TestStruct {
/// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } }
/// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; }
/// fn new() -> Self {
/// Self { fields: vec![1, 2, 3, 4, 5] }
/// }
/// fn set_field(&mut self, index: i64, value: i64) {
/// self.fields[index as usize] = value;
/// }
/// }
///
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
@ -717,10 +753,10 @@ impl Engine {
/// .register_indexer_set(TestStruct::set_field);
///
/// # #[cfg(not(feature = "no_index"))]
/// assert_eq!(
/// engine.eval::<TestStruct>("let a = new_ts(); a[2] = 42; a")?.fields[2],
/// 42
/// );
/// let result = engine.eval::<TestStruct>("let a = new_ts(); a[2] = 42; a")?;
///
/// # #[cfg(not(feature = "no_index"))]
/// assert_eq!(result.fields[2], 42);
/// # Ok(())
/// # }
/// ```
@ -771,8 +807,10 @@ impl Engine {
/// }
///
/// impl TestStruct {
/// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } }
/// fn set_field(&mut self, index: i64, value: i64) -> Result<(), Box<EvalAltResult>> {
/// fn new() -> Self {
/// Self { fields: vec![1, 2, 3, 4, 5] }
/// }
/// fn set_field(&mut self, index: i64, value: i64) -> Result<(), Box<rhai::EvalAltResult>> {
/// self.fields[index as usize] = value;
/// Ok(())
/// }
@ -791,10 +829,10 @@ impl Engine {
/// .register_indexer_set_result(TestStruct::set_field);
///
/// # #[cfg(not(feature = "no_index"))]
/// assert_eq!(
/// engine.eval::<TestStruct>("let a = new_ts(); a[2] = 42; a")?.fields[2],
/// 42
/// );
/// let result = engine.eval::<TestStruct>("let a = new_ts(); a[2] = 42; a")?;
///
/// # #[cfg(not(feature = "no_index"))]
/// assert_eq!(result.fields[2], 42);
/// # Ok(())
/// # }
/// ```
@ -806,7 +844,7 @@ impl Engine {
V: Variant + Clone,
>(
&mut self,
set_fn: impl Fn(&mut T, X, V) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
set_fn: impl Fn(&mut T, X, V) -> RhaiResultOf<()> + SendSync + 'static,
) -> &mut Self {
#[cfg(not(feature = "no_index"))]
if TypeId::of::<T>() == TypeId::of::<crate::Array>() {
@ -847,10 +885,16 @@ impl Engine {
/// }
///
/// impl TestStruct {
/// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } }
/// fn new() -> Self {
/// Self { fields: vec![1, 2, 3, 4, 5] }
/// }
/// // Even a getter must start with `&mut self` and not `&self`.
/// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] }
/// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; }
/// fn get_field(&mut self, index: i64) -> i64 {
/// self.fields[index as usize]
/// }
/// fn set_field(&mut self, index: i64, value: i64) {
/// self.fields[index as usize] = value;
/// }
/// }
///
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
@ -935,17 +979,17 @@ impl Engine {
#[cfg(not(feature = "no_module"))]
pub fn register_static_module(
&mut self,
name: impl AsRef<str> + Into<Identifier>,
name: impl AsRef<str>,
module: Shared<Module>,
) -> &mut Self {
fn register_static_module_raw(
root: &mut std::collections::BTreeMap<Identifier, Shared<Module>>,
name: impl AsRef<str> + Into<Identifier>,
name: &str,
module: Shared<Module>,
) {
let separator = crate::tokenizer::Token::DoubleColon.syntax();
if !name.as_ref().contains(separator.as_ref()) {
if !name.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::func::native::shared_take_or_clone(module);
@ -955,7 +999,7 @@ impl Engine {
root.insert(name.into(), module);
}
} else {
let mut iter = name.as_ref().splitn(2, separator.as_ref());
let mut iter = name.splitn(2, separator.as_ref());
let sub_module = iter.next().expect("contains separator").trim();
let remainder = iter.next().expect("contains separator").trim();
@ -974,7 +1018,7 @@ impl Engine {
}
}
register_static_module_raw(&mut self.global_sub_modules, name, module);
register_static_module_raw(&mut self.global_sub_modules, name.as_ref(), module);
self
}
/// _(metadata)_ Generate a list of all registered functions.

View File

@ -1,15 +1,15 @@
//! Module that defines the public evaluation API of [`Engine`].
use crate::engine::{EvalState, Imports};
use crate::engine::{EvalState, GlobalRuntimeState};
use crate::parser::ParseState;
use crate::{Engine, EvalAltResult, Module, Scope, AST};
use crate::{Engine, Module, RhaiResultOf, Scope, AST};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
impl Engine {
/// Evaluate a script, returning any error (if any).
#[inline(always)]
pub fn run(&self, script: &str) -> Result<(), Box<EvalAltResult>> {
pub fn run(&self, script: &str) -> RhaiResultOf<()> {
self.run_with_scope(&mut Scope::new(), script)
}
/// Evaluate a script with own scope, returning any error (if any).
@ -20,11 +20,7 @@ impl Engine {
/// the scope are propagated throughout the script _including_ functions. This allows functions
/// to be optimized based on dynamic global constants.
#[inline]
pub fn run_with_scope(
&self,
scope: &mut Scope,
script: &str,
) -> Result<(), Box<EvalAltResult>> {
pub fn run_with_scope(&self, scope: &mut Scope, script: &str) -> RhaiResultOf<()> {
let scripts = [script];
let (stream, tokenizer_control) =
self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref));
@ -42,24 +38,19 @@ impl Engine {
}
/// Evaluate an [`AST`], returning any error (if any).
#[inline(always)]
pub fn run_ast(&self, ast: &AST) -> Result<(), Box<EvalAltResult>> {
pub fn run_ast(&self, ast: &AST) -> RhaiResultOf<()> {
self.run_ast_with_scope(&mut Scope::new(), ast)
}
/// Evaluate an [`AST`] with own scope, returning any error (if any).
#[inline]
pub fn run_ast_with_scope(
&self,
scope: &mut Scope,
ast: &AST,
) -> Result<(), Box<EvalAltResult>> {
let mods = &mut Imports::new();
pub fn run_ast_with_scope(&self, scope: &mut Scope, ast: &AST) -> RhaiResultOf<()> {
let global = &mut GlobalRuntimeState::new();
let mut state = EvalState::new();
if ast.source_raw().is_some() {
mods.source = ast.source_raw().cloned();
}
global.source = ast.source_raw().clone();
#[cfg(not(feature = "no_module"))]
{
mods.embedded_module_resolver = ast.resolver().cloned();
global.embedded_module_resolver = ast.resolver().cloned();
}
let statements = ast.statements();
@ -73,7 +64,7 @@ impl Engine {
} else {
&lib
};
self.eval_global_statements(scope, mods, &mut state, statements, lib, 0)?;
self.eval_global_statements(scope, global, &mut state, statements, lib, 0)?;
}
Ok(())
}

2585
src/ast.rs

File diff suppressed because it is too large Load Diff

863
src/ast/ast.rs Normal file
View File

@ -0,0 +1,863 @@
//! Module defining the AST (abstract syntax tree).
use super::{Expr, FnAccess, Stmt, StmtBlock, AST_OPTION_FLAGS};
use crate::{Dynamic, FnNamespace, Identifier, Position, StaticVec};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
hash::Hash,
ops::{Add, AddAssign},
};
/// Compiled AST (abstract syntax tree) of a Rhai script.
///
/// # Thread Safety
///
/// Currently, [`AST`] is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.
#[derive(Debug, Clone)]
pub struct AST {
/// Source of the [`AST`].
/// No source if string is empty.
source: Identifier,
/// Global statements.
body: StmtBlock,
/// Script-defined functions.
#[cfg(not(feature = "no_function"))]
functions: crate::Shared<crate::Module>,
/// Embedded module resolver, if any.
#[cfg(not(feature = "no_module"))]
resolver: Option<crate::Shared<crate::module::resolvers::StaticModuleResolver>>,
}
impl Default for AST {
#[inline(always)]
fn default() -> Self {
Self::empty()
}
}
impl AST {
/// Create a new [`AST`].
#[cfg(not(feature = "internals"))]
#[inline(always)]
#[must_use]
pub(crate) fn new(
statements: impl IntoIterator<Item = Stmt>,
#[cfg(not(feature = "no_function"))] functions: impl Into<crate::Shared<crate::Module>>,
) -> Self {
Self {
source: Identifier::new_const(),
body: StmtBlock::new(statements, Position::NONE),
#[cfg(not(feature = "no_function"))]
functions: functions.into(),
#[cfg(not(feature = "no_module"))]
resolver: None,
}
}
/// _(internals)_ Create a new [`AST`].
/// Exported under the `internals` feature only.
#[cfg(feature = "internals")]
#[inline(always)]
#[must_use]
pub fn new(
statements: impl IntoIterator<Item = Stmt>,
#[cfg(not(feature = "no_function"))] functions: impl Into<crate::Shared<crate::Module>>,
) -> Self {
Self {
source: Identifier::new_const(),
body: StmtBlock::new(statements, Position::NONE),
#[cfg(not(feature = "no_function"))]
functions: functions.into(),
#[cfg(not(feature = "no_module"))]
resolver: None,
}
}
/// Create a new [`AST`] with a source name.
#[cfg(not(feature = "internals"))]
#[inline(always)]
#[must_use]
pub(crate) fn new_with_source(
statements: impl IntoIterator<Item = Stmt>,
#[cfg(not(feature = "no_function"))] functions: impl Into<crate::Shared<crate::Module>>,
source: impl Into<Identifier>,
) -> Self {
let mut ast = Self::new(
statements,
#[cfg(not(feature = "no_function"))]
functions,
);
ast.set_source(source);
ast
}
/// _(internals)_ Create a new [`AST`] with a source name.
/// Exported under the `internals` feature only.
#[cfg(feature = "internals")]
#[inline(always)]
#[must_use]
pub fn new_with_source(
statements: impl IntoIterator<Item = Stmt>,
#[cfg(not(feature = "no_function"))] functions: impl Into<crate::Shared<crate::Module>>,
source: impl Into<Identifier>,
) -> Self {
let mut ast = Self::new(
statements,
#[cfg(not(feature = "no_function"))]
functions,
);
ast.set_source(source);
ast
}
/// Create an empty [`AST`].
#[inline]
#[must_use]
pub fn empty() -> Self {
Self {
source: Identifier::new_const(),
body: StmtBlock::NONE,
#[cfg(not(feature = "no_function"))]
functions: crate::Module::new().into(),
#[cfg(not(feature = "no_module"))]
resolver: None,
}
}
/// Get the source, if any.
#[inline(always)]
#[must_use]
pub fn source(&self) -> Option<&str> {
match self.source.as_str() {
"" => None,
s => Some(s),
}
}
/// Get a reference to the source.
#[inline(always)]
#[must_use]
pub(crate) fn source_raw(&self) -> &Identifier {
&self.source
}
/// Set the source.
#[inline]
pub fn set_source(&mut self, source: impl Into<Identifier>) -> &mut Self {
let source = source.into();
#[cfg(not(feature = "no_function"))]
crate::Shared::get_mut(&mut self.functions)
.as_mut()
.map(|m| m.set_id(source.clone()));
self.source = source;
self
}
/// Clear the source.
#[inline(always)]
pub fn clear_source(&mut self) -> &mut Self {
self.source.clear();
self
}
/// Get the statements.
#[cfg(not(feature = "internals"))]
#[inline(always)]
#[must_use]
pub(crate) fn statements(&self) -> &[Stmt] {
self.body.statements()
}
/// _(internals)_ Get the statements.
/// Exported under the `internals` feature only.
#[cfg(feature = "internals")]
#[inline(always)]
#[must_use]
pub fn statements(&self) -> &[Stmt] {
self.body.statements()
}
/// Extract the statements.
#[allow(dead_code)]
#[inline(always)]
#[must_use]
pub(crate) fn take_statements(&mut self) -> StaticVec<Stmt> {
self.body.take_statements()
}
/// Does this [`AST`] contain script-defined functions?
///
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
#[inline(always)]
#[must_use]
pub fn has_functions(&self) -> bool {
!self.functions.is_empty()
}
/// Get the internal shared [`Module`][crate::Module] containing all script-defined functions.
#[cfg(not(feature = "internals"))]
#[cfg(not(feature = "no_function"))]
#[inline(always)]
#[must_use]
pub(crate) fn shared_lib(&self) -> &crate::Shared<crate::Module> {
&self.functions
}
/// _(internals)_ Get the internal shared [`Module`][crate::Module] containing all script-defined functions.
/// Exported under the `internals` feature only.
///
/// Not available under `no_function`.
#[cfg(feature = "internals")]
#[cfg(not(feature = "no_function"))]
#[inline(always)]
#[must_use]
pub fn shared_lib(&self) -> &crate::Shared<crate::Module> {
&self.functions
}
/// Get the embedded [module resolver][`ModuleResolver`].
#[cfg(not(feature = "internals"))]
#[cfg(not(feature = "no_module"))]
#[inline(always)]
#[must_use]
pub(crate) fn resolver(
&self,
) -> Option<&crate::Shared<crate::module::resolvers::StaticModuleResolver>> {
self.resolver.as_ref()
}
/// _(internals)_ Get the embedded [module resolver][crate::ModuleResolver].
/// Exported under the `internals` feature only.
///
/// Not available under `no_module`.
#[cfg(feature = "internals")]
#[cfg(not(feature = "no_module"))]
#[inline(always)]
#[must_use]
pub fn resolver(
&self,
) -> Option<&crate::Shared<crate::module::resolvers::StaticModuleResolver>> {
self.resolver.as_ref()
}
/// Set the embedded [module resolver][`ModuleResolver`].
#[cfg(not(feature = "no_module"))]
#[inline(always)]
pub(crate) fn set_resolver(
&mut self,
resolver: impl Into<crate::Shared<crate::module::resolvers::StaticModuleResolver>>,
) -> &mut Self {
self.resolver = Some(resolver.into());
self
}
/// Clone the [`AST`]'s functions into a new [`AST`].
/// No statements are cloned.
///
/// Not available under `no_function`.
///
/// This operation is cheap because functions are shared.
#[cfg(not(feature = "no_function"))]
#[inline(always)]
#[must_use]
pub fn clone_functions_only(&self) -> Self {
self.clone_functions_only_filtered(|_, _, _, _, _| true)
}
/// Clone the [`AST`]'s functions into a new [`AST`] based on a filter predicate.
/// No statements are cloned.
///
/// Not available under `no_function`.
///
/// This operation is cheap because functions are shared.
#[cfg(not(feature = "no_function"))]
#[inline]
#[must_use]
pub fn clone_functions_only_filtered(
&self,
filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
) -> Self {
let mut functions = crate::Module::new();
functions.merge_filtered(&self.functions, &filter);
Self {
source: self.source.clone(),
body: StmtBlock::NONE,
functions: functions.into(),
#[cfg(not(feature = "no_module"))]
resolver: self.resolver.clone(),
}
}
/// Clone the [`AST`]'s script statements into a new [`AST`].
/// No functions are cloned.
#[inline(always)]
#[must_use]
pub fn clone_statements_only(&self) -> Self {
Self {
source: self.source.clone(),
body: self.body.clone(),
#[cfg(not(feature = "no_function"))]
functions: crate::Module::new().into(),
#[cfg(not(feature = "no_module"))]
resolver: self.resolver.clone(),
}
}
/// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged,
/// version is returned.
///
/// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_.
/// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried.
/// Of course, if the first [`AST`] uses a `return` statement at the end, then
/// the second [`AST`] will essentially be dead code.
///
/// All script-defined functions in the second [`AST`] overwrite similarly-named functions
/// in the first [`AST`] with the same number of parameters.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # #[cfg(not(feature = "no_function"))]
/// # {
/// use rhai::Engine;
///
/// let engine = Engine::new();
///
/// let ast1 = engine.compile("
/// fn foo(x) { 42 + x }
/// foo(1)
/// ")?;
///
/// let ast2 = engine.compile(r#"
/// fn foo(n) { `hello${n}` }
/// foo("!")
/// "#)?;
///
/// let ast = ast1.merge(&ast2); // Merge 'ast2' into 'ast1'
///
/// // Notice that using the '+' operator also works:
/// // let ast = &ast1 + &ast2;
///
/// // 'ast' is essentially:
/// //
/// // fn foo(n) { `hello${n}` } // <- definition of first 'foo' is overwritten
/// // foo(1) // <- notice this will be "hello1" instead of 43,
/// // // but it is no longer the return value
/// // foo("!") // returns "hello!"
///
/// // Evaluate it
/// assert_eq!(engine.eval_ast::<String>(&ast)?, "hello!");
/// # }
/// # Ok(())
/// # }
/// ```
#[inline(always)]
#[must_use]
pub fn merge(&self, other: &Self) -> Self {
self.merge_filtered_impl(other, |_, _, _, _, _| true)
}
/// Combine one [`AST`] with another. The second [`AST`] is consumed.
///
/// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_.
/// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried.
/// Of course, if the first [`AST`] uses a `return` statement at the end, then
/// the second [`AST`] will essentially be dead code.
///
/// All script-defined functions in the second [`AST`] overwrite similarly-named functions
/// in the first [`AST`] with the same number of parameters.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # #[cfg(not(feature = "no_function"))]
/// # {
/// use rhai::Engine;
///
/// let engine = Engine::new();
///
/// let mut ast1 = engine.compile("
/// fn foo(x) { 42 + x }
/// foo(1)
/// ")?;
///
/// let ast2 = engine.compile(r#"
/// fn foo(n) { `hello${n}` }
/// foo("!")
/// "#)?;
///
/// ast1.combine(ast2); // Combine 'ast2' into 'ast1'
///
/// // Notice that using the '+=' operator also works:
/// // ast1 += ast2;
///
/// // 'ast1' is essentially:
/// //
/// // fn foo(n) { `hello${n}` } // <- definition of first 'foo' is overwritten
/// // foo(1) // <- notice this will be "hello1" instead of 43,
/// // // but it is no longer the return value
/// // foo("!") // returns "hello!"
///
/// // Evaluate it
/// assert_eq!(engine.eval_ast::<String>(&ast1)?, "hello!");
/// # }
/// # Ok(())
/// # }
/// ```
#[inline(always)]
pub fn combine(&mut self, other: Self) -> &mut Self {
self.combine_filtered_impl(other, |_, _, _, _, _| true)
}
/// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version
/// is returned.
///
/// Not available under `no_function`.
///
/// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_.
/// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried.
/// Of course, if the first [`AST`] uses a `return` statement at the end, then
/// the second [`AST`] will essentially be dead code.
///
/// All script-defined functions in the second [`AST`] are first selected based on a filter
/// predicate, then overwrite similarly-named functions in the first [`AST`] with the
/// same number of parameters.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// use rhai::Engine;
///
/// let engine = Engine::new();
///
/// let ast1 = engine.compile("
/// fn foo(x) { 42 + x }
/// foo(1)
/// ")?;
///
/// let ast2 = engine.compile(r#"
/// fn foo(n) { `hello${n}` }
/// fn error() { 0 }
/// foo("!")
/// "#)?;
///
/// // Merge 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1'
/// let ast = ast1.merge_filtered(&ast2, |_, _, script, name, params|
/// script && name == "error" && params == 0);
///
/// // 'ast' is essentially:
/// //
/// // fn foo(n) { 42 + n } // <- definition of 'ast1::foo' is not overwritten
/// // // because 'ast2::foo' is filtered away
/// // foo(1) // <- notice this will be 43 instead of "hello1",
/// // // but it is no longer the return value
/// // fn error() { 0 } // <- this function passes the filter and is merged
/// // foo("!") // <- returns "42!"
///
/// // Evaluate it
/// assert_eq!(engine.eval_ast::<String>(&ast)?, "42!");
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "no_function"))]
#[inline(always)]
#[must_use]
pub fn merge_filtered(
&self,
other: &Self,
filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
) -> Self {
self.merge_filtered_impl(other, filter)
}
/// Merge two [`AST`] into one. Both [`AST`]'s are untouched and a new, merged, version
/// is returned.
#[inline]
#[must_use]
fn merge_filtered_impl(
&self,
other: &Self,
_filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
) -> Self {
let merged = match (self.body.is_empty(), other.body.is_empty()) {
(false, false) => {
let mut body = self.body.clone();
body.extend(other.body.iter().cloned());
body
}
(false, true) => self.body.clone(),
(true, false) => other.body.clone(),
(true, true) => StmtBlock::NONE,
};
#[cfg(not(feature = "no_function"))]
let functions = {
let mut functions = self.functions.as_ref().clone();
functions.merge_filtered(&other.functions, &_filter);
functions
};
if !other.source.is_empty() {
Self::new_with_source(
merged,
#[cfg(not(feature = "no_function"))]
functions,
other.source.clone(),
)
} else {
Self::new(
merged,
#[cfg(not(feature = "no_function"))]
functions,
)
}
}
/// Combine one [`AST`] with another. The second [`AST`] is consumed.
///
/// Not available under `no_function`.
///
/// Statements in the second [`AST`] are simply appended to the end of the first _without any processing_.
/// Thus, the return value of the first [`AST`] (if using expression-statement syntax) is buried.
/// Of course, if the first [`AST`] uses a `return` statement at the end, then
/// the second [`AST`] will essentially be dead code.
///
/// All script-defined functions in the second [`AST`] are first selected based on a filter
/// predicate, then overwrite similarly-named functions in the first [`AST`] with the
/// same number of parameters.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// use rhai::Engine;
///
/// let engine = Engine::new();
///
/// let mut ast1 = engine.compile("
/// fn foo(x) { 42 + x }
/// foo(1)
/// ")?;
///
/// let ast2 = engine.compile(r#"
/// fn foo(n) { `hello${n}` }
/// fn error() { 0 }
/// foo("!")
/// "#)?;
///
/// // Combine 'ast2', picking only 'error()' but not 'foo(_)', into 'ast1'
/// ast1.combine_filtered(ast2, |_, _, script, name, params|
/// script && name == "error" && params == 0);
///
/// // 'ast1' is essentially:
/// //
/// // fn foo(n) { 42 + n } // <- definition of 'ast1::foo' is not overwritten
/// // // because 'ast2::foo' is filtered away
/// // foo(1) // <- notice this will be 43 instead of "hello1",
/// // // but it is no longer the return value
/// // fn error() { 0 } // <- this function passes the filter and is merged
/// // foo("!") // <- returns "42!"
///
/// // Evaluate it
/// assert_eq!(engine.eval_ast::<String>(&ast1)?, "42!");
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "no_function"))]
#[inline(always)]
pub fn combine_filtered(
&mut self,
other: Self,
filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
) -> &mut Self {
self.combine_filtered_impl(other, filter)
}
/// Combine one [`AST`] with another. The second [`AST`] is consumed.
#[inline]
fn combine_filtered_impl(
&mut self,
other: Self,
_filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
) -> &mut Self {
self.body.extend(other.body.into_iter());
#[cfg(not(feature = "no_function"))]
if !other.functions.is_empty() {
crate::func::native::shared_make_mut(&mut self.functions)
.merge_filtered(&other.functions, &_filter);
}
self
}
/// Filter out the functions, retaining only some based on a filter predicate.
///
/// Not available under `no_function`.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// # #[cfg(not(feature = "no_function"))]
/// # {
/// use rhai::Engine;
///
/// let engine = Engine::new();
///
/// let mut ast = engine.compile(r#"
/// fn foo(n) { n + 1 }
/// fn bar() { print("hello"); }
/// "#)?;
///
/// // Remove all functions except 'foo(_)'
/// ast.retain_functions(|_, _, name, params| name == "foo" && params == 1);
/// # }
/// # Ok(())
/// # }
/// ```
#[cfg(not(feature = "no_function"))]
#[inline]
pub fn retain_functions(
&mut self,
filter: impl Fn(FnNamespace, FnAccess, &str, usize) -> bool,
) -> &mut Self {
if !self.functions.is_empty() {
crate::func::native::shared_make_mut(&mut self.functions)
.retain_script_functions(filter);
}
self
}
/// Iterate through all function definitions.
///
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
#[allow(dead_code)]
#[inline]
pub(crate) fn iter_fn_def(&self) -> impl Iterator<Item = &super::ScriptFnDef> {
self.functions
.iter_script_fn()
.map(|(_, _, _, _, fn_def)| fn_def.as_ref())
}
/// Iterate through all function definitions.
///
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
#[inline]
pub fn iter_functions<'a>(&'a self) -> impl Iterator<Item = super::ScriptFnMetadata> + 'a {
self.functions
.iter_script_fn()
.map(|(_, _, _, _, fn_def)| fn_def.as_ref().into())
}
/// Clear all function definitions in the [`AST`].
///
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
#[inline(always)]
pub fn clear_functions(&mut self) -> &mut Self {
self.functions = crate::Module::new().into();
self
}
/// Clear all statements in the [`AST`], leaving only function definitions.
#[inline(always)]
pub fn clear_statements(&mut self) -> &mut Self {
self.body = StmtBlock::NONE;
self
}
/// Extract all top-level literal constant and/or variable definitions.
/// This is useful for extracting all global constants from a script without actually running it.
///
/// A literal constant/variable definition takes the form of:
/// `const VAR = `_value_`;` and `let VAR = `_value_`;`
/// where _value_ is a literal expression or will be optimized into a literal.
///
/// # Example
///
/// ```
/// # fn main() -> Result<(), Box<rhai::EvalAltResult>> {
/// use rhai::{Engine, Scope};
///
/// let engine = Engine::new();
///
/// let ast = engine.compile(
/// "
/// const A = 40 + 2; // constant that optimizes into a literal
/// let b = 123; // literal variable
/// const B = b * A; // non-literal constant
/// const C = 999; // literal constant
/// b = A + C; // expression
///
/// { // <- new block scope
/// const Z = 0; // <- literal constant not at top-level
/// }
/// ")?;
///
/// let mut iter = ast.iter_literal_variables(true, false)
/// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap()));
///
/// # #[cfg(not(feature = "no_optimize"))]
/// assert_eq!(iter.next(), Some(("A", true, 42)));
/// assert_eq!(iter.next(), Some(("C", true, 999)));
/// assert_eq!(iter.next(), None);
///
/// let mut iter = ast.iter_literal_variables(false, true)
/// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap()));
///
/// assert_eq!(iter.next(), Some(("b", false, 123)));
/// assert_eq!(iter.next(), None);
///
/// let mut iter = ast.iter_literal_variables(true, true)
/// .map(|(name, is_const, value)| (name, is_const, value.as_int().unwrap()));
///
/// # #[cfg(not(feature = "no_optimize"))]
/// assert_eq!(iter.next(), Some(("A", true, 42)));
/// assert_eq!(iter.next(), Some(("b", false, 123)));
/// assert_eq!(iter.next(), Some(("C", true, 999)));
/// assert_eq!(iter.next(), None);
///
/// let scope: Scope = ast.iter_literal_variables(true, false).collect();
///
/// # #[cfg(not(feature = "no_optimize"))]
/// assert_eq!(scope.len(), 2);
///
/// Ok(())
/// # }
/// ```
pub fn iter_literal_variables(
&self,
include_constants: bool,
include_variables: bool,
) -> impl Iterator<Item = (&str, bool, Dynamic)> {
self.statements().iter().filter_map(move |stmt| match stmt {
Stmt::Var(expr, name, options, _)
if options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT) && include_constants
|| !options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT)
&& include_variables =>
{
if let Some(value) = expr.get_literal_value() {
Some((
name.as_str(),
options.contains(AST_OPTION_FLAGS::AST_OPTION_CONSTANT),
value,
))
} else {
None
}
}
_ => None,
})
}
/// Recursively walk the [`AST`], including function bodies (if any).
/// Return `false` from the callback to terminate the walk.
#[cfg(not(feature = "internals"))]
#[cfg(not(feature = "no_module"))]
#[inline]
pub(crate) fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool {
let path = &mut Vec::new();
for stmt in self.statements() {
if !stmt.walk(path, on_node) {
return false;
}
}
#[cfg(not(feature = "no_function"))]
for stmt in self.iter_fn_def().flat_map(|f| f.body.iter()) {
if !stmt.walk(path, on_node) {
return false;
}
}
true
}
/// _(internals)_ Recursively walk the [`AST`], including function bodies (if any).
/// Return `false` from the callback to terminate the walk.
/// Exported under the `internals` feature only.
#[cfg(feature = "internals")]
#[inline]
pub fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool {
let path = &mut Vec::new();
for stmt in self.statements() {
if !stmt.walk(path, on_node) {
return false;
}
}
#[cfg(not(feature = "no_function"))]
for stmt in self.iter_fn_def().flat_map(|f| f.body.iter()) {
if !stmt.walk(path, on_node) {
return false;
}
}
true
}
}
impl<A: AsRef<AST>> Add<A> for &AST {
type Output = AST;
#[inline(always)]
fn add(self, rhs: A) -> Self::Output {
self.merge(rhs.as_ref())
}
}
impl<A: Into<AST>> AddAssign<A> for AST {
#[inline(always)]
fn add_assign(&mut self, rhs: A) {
self.combine(rhs.into());
}
}
impl AsRef<[Stmt]> for AST {
#[inline(always)]
fn as_ref(&self) -> &[Stmt] {
self.statements()
}
}
#[cfg(not(feature = "no_function"))]
impl AsRef<crate::Module> for AST {
#[inline(always)]
fn as_ref(&self) -> &crate::Module {
self.shared_lib().as_ref()
}
}
#[cfg(not(feature = "no_function"))]
impl AsRef<crate::Shared<crate::Module>> for AST {
#[inline(always)]
fn as_ref(&self) -> &crate::Shared<crate::Module> {
self.shared_lib()
}
}
/// _(internals)_ An [`AST`] node, consisting of either an [`Expr`] or a [`Stmt`].
/// Exported under the `internals` feature only.
#[derive(Debug, Clone, Hash)]
pub enum ASTNode<'a> {
/// A statement ([`Stmt`]).
Stmt(&'a Stmt),
/// An expression ([`Expr`]).
Expr(&'a Expr),
}
impl<'a> From<&'a Stmt> for ASTNode<'a> {
fn from(stmt: &'a Stmt) -> Self {
Self::Stmt(stmt)
}
}
impl<'a> From<&'a Expr> for ASTNode<'a> {
fn from(expr: &'a Expr) -> Self {
Self::Expr(expr)
}
}
impl ASTNode<'_> {
/// Get the [`Position`] of this [`ASTNode`].
pub const fn position(&self) -> Position {
match self {
ASTNode::Stmt(stmt) => stmt.position(),
ASTNode::Expr(expr) => expr.position(),
}
}
}
impl AST {
/// _(internals)_ Get the internal [`Module`][crate::Module] containing all script-defined functions.
/// Exported under the `internals` feature only.
///
/// Not available under `no_function`.
///
/// # Deprecated
///
/// This method is deprecated. Use [`shared_lib`][AST::shared_lib] instead.
///
/// This method will be removed in the next major version.
#[deprecated(since = "1.3.0", note = "use `shared_lib` instead")]
#[cfg(feature = "internals")]
#[cfg(not(feature = "no_function"))]
#[inline(always)]
#[must_use]
pub fn lib(&self) -> &crate::Module {
&self.functions
}
}

831
src/ast/expr.rs Normal file
View File

@ -0,0 +1,831 @@
//! Module defining script expressions.
use super::{ASTNode, Ident, Stmt, StmtBlock};
use crate::engine::{OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE};
use crate::func::hashing::ALT_ZERO_HASH;
use crate::module::Namespace;
use crate::tokenizer::Token;
use crate::types::dynamic::Union;
use crate::{Dynamic, Identifier, ImmutableString, Position, StaticVec, INT};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
collections::BTreeMap,
fmt,
hash::Hash,
num::{NonZeroU8, NonZeroUsize},
};
#[cfg(not(feature = "no_float"))]
use std::str::FromStr;
#[cfg(not(feature = "no_float"))]
use num_traits::Float;
/// _(internals)_ A binary expression.
/// Exported under the `internals` feature only.
#[derive(Debug, Clone, Hash)]
pub struct BinaryExpr {
/// LHS expression.
pub lhs: Expr,
/// RHS expression.
pub rhs: Expr,
}
/// _(internals)_ A custom syntax expression.
/// Exported under the `internals` feature only.
#[derive(Debug, Clone, Hash)]
pub struct CustomExpr {
/// List of keywords.
pub inputs: StaticVec<Expr>,
/// List of tokens actually parsed.
pub tokens: StaticVec<Identifier>,
/// Is the current [`Scope`][crate::Scope] possibly modified by this custom statement
/// (e.g. introducing a new variable)?
pub scope_may_be_changed: bool,
/// Is this custom syntax self-terminated?
pub self_terminated: bool,
}
impl CustomExpr {
/// Is this custom syntax self-terminated (i.e. no need for a semicolon terminator)?
///
/// A self-terminated custom syntax always ends in `$block$`, `}` or `;`
#[must_use]
#[inline(always)]
pub const fn is_self_terminated(&self) -> bool {
self.self_terminated
}
}
/// _(internals)_ A set of function call hashes. Exported under the `internals` feature only.
///
/// Two separate hashes are pre-calculated because of the following patterns:
///
/// ```js
/// func(a, b, c); // Native: func(a, b, c) - 3 parameters
/// // Script: func(a, b, c) - 3 parameters
///
/// a.func(b, c); // Native: func(&mut a, b, c) - 3 parameters
/// // Script: func(b, c) - 2 parameters
/// ```
///
/// For normal function calls, the native hash equals the script hash.
///
/// For method-style calls, the script hash contains one fewer parameter.
///
/// Function call hashes are used in the following manner:
///
/// * First, the script hash is tried, which contains only the called function's name plus the
/// number of parameters.
///
/// * Next, the actual types of arguments are hashed and _combined_ with the native hash, which is
/// then used to search for a native function. In other words, a complete native function call
/// hash always contains the called function's name plus the types of the arguments. This is due
/// to possible function overloading for different parameter types.
#[derive(Clone, Copy, Eq, PartialEq, Hash, Default)]
pub struct FnCallHashes {
/// Pre-calculated hash for a script-defined function (zero if native functions only).
#[cfg(not(feature = "no_function"))]
pub script: u64,
/// Pre-calculated hash for a native Rust function with no parameter types.
pub native: u64,
}
impl fmt::Debug for FnCallHashes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(not(feature = "no_function"))]
if self.script != 0 {
return if self.script == self.native {
fmt::Debug::fmt(&self.native, f)
} else {
write!(f, "({}, {})", self.script, self.native)
};
}
write!(f, "{} (native only)", self.native)
}
}
impl From<u64> for FnCallHashes {
#[inline(always)]
fn from(hash: u64) -> Self {
let hash = if hash == 0 { ALT_ZERO_HASH } else { hash };
Self {
#[cfg(not(feature = "no_function"))]
script: hash,
native: hash,
}
}
}
impl FnCallHashes {
/// Create a [`FnCallHashes`] with only the native Rust hash.
#[inline(always)]
#[must_use]
pub const fn from_native(hash: u64) -> Self {
Self {
#[cfg(not(feature = "no_function"))]
script: 0,
native: if hash == 0 { ALT_ZERO_HASH } else { hash },
}
}
/// Create a [`FnCallHashes`] with both native Rust and script function hashes.
#[inline(always)]
#[must_use]
pub const fn from_all(#[cfg(not(feature = "no_function"))] script: u64, native: u64) -> Self {
Self {
#[cfg(not(feature = "no_function"))]
script: if script == 0 { ALT_ZERO_HASH } else { script },
native: if native == 0 { ALT_ZERO_HASH } else { native },
}
}
/// Is this [`FnCallHashes`] native Rust only?
#[inline(always)]
#[must_use]
pub const fn is_native_only(&self) -> bool {
#[cfg(not(feature = "no_function"))]
return self.script == 0;
#[cfg(feature = "no_function")]
return true;
}
}
/// _(internals)_ A function call.
/// Exported under the `internals` feature only.
#[derive(Debug, Clone, Default, Hash)]
pub struct FnCallExpr {
/// Namespace of the function, if any.
pub namespace: Option<Namespace>,
/// Function name.
pub name: Identifier,
/// Pre-calculated hashes.
pub hashes: FnCallHashes,
/// List of function call argument expressions.
pub args: StaticVec<Expr>,
/// List of function call arguments that are constants.
///
/// Any arguments in `args` that is [`Expr::Stack`] indexes into this
/// array to find the constant for use as its argument value.
///
/// # Notes
///
/// Constant arguments are very common in function calls, and keeping each constant in
/// an [`Expr::DynamicConstant`] involves an additional allocation. Keeping the constant
/// values in an inlined array avoids these extra allocations.
pub constants: StaticVec<Dynamic>,
/// Does this function call capture the parent scope?
pub capture_parent_scope: bool,
}
impl FnCallExpr {
/// Does this function call contain a qualified namespace?
#[inline(always)]
#[must_use]
pub const fn is_qualified(&self) -> bool {
self.namespace.is_some()
}
/// Convert this into an [`Expr::FnCall`].
#[inline(always)]
#[must_use]
pub fn into_fn_call_expr(self, pos: Position) -> Expr {
Expr::FnCall(self.into(), pos)
}
}
/// A type that wraps a floating-point number and implements [`Hash`].
///
/// Not available under `no_float`.
#[cfg(not(feature = "no_float"))]
#[derive(Clone, Copy, PartialEq, PartialOrd)]
pub struct FloatWrapper<F>(F);
#[cfg(not(feature = "no_float"))]
impl Hash for FloatWrapper<crate::FLOAT> {
#[inline(always)]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.to_ne_bytes().hash(state);
}
}
#[cfg(not(feature = "no_float"))]
impl<F: Float> AsRef<F> for FloatWrapper<F> {
#[inline(always)]
fn as_ref(&self) -> &F {
&self.0
}
}
#[cfg(not(feature = "no_float"))]
impl<F: Float> AsMut<F> for FloatWrapper<F> {
#[inline(always)]
fn as_mut(&mut self) -> &mut F {
&mut self.0
}
}
#[cfg(not(feature = "no_float"))]
impl<F: Float> std::ops::Deref for FloatWrapper<F> {
type Target = F;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(not(feature = "no_float"))]
impl<F: Float> std::ops::DerefMut for FloatWrapper<F> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[cfg(not(feature = "no_float"))]
impl<F: Float + fmt::Debug> fmt::Debug for FloatWrapper<F> {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
#[cfg(not(feature = "no_float"))]
impl<F: Float + fmt::Display + fmt::LowerExp + From<f32>> fmt::Display for FloatWrapper<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let abs = self.0.abs();
if abs.is_zero() {
f.write_str("0.0")
} else if abs > Self::MAX_NATURAL_FLOAT_FOR_DISPLAY.into()
|| abs < Self::MIN_NATURAL_FLOAT_FOR_DISPLAY.into()
{
write!(f, "{:e}", self.0)
} else {
fmt::Display::fmt(&self.0, f)?;
if abs.fract().is_zero() {
f.write_str(".0")?;
}
Ok(())
}
}
}
#[cfg(not(feature = "no_float"))]
impl<F: Float> From<F> for FloatWrapper<F> {
#[inline(always)]
fn from(value: F) -> Self {
Self::new(value)
}
}
#[cfg(not(feature = "no_float"))]
impl<F: Float + FromStr> FromStr for FloatWrapper<F> {
type Err = <F as FromStr>::Err;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
F::from_str(s).map(Into::into)
}
}
#[cfg(not(feature = "no_float"))]
impl<F: Float> FloatWrapper<F> {
/// Maximum floating-point number for natural display before switching to scientific notation.
pub const MAX_NATURAL_FLOAT_FOR_DISPLAY: f32 = 10000000000000.0;
/// Minimum floating-point number for natural display before switching to scientific notation.
pub const MIN_NATURAL_FLOAT_FOR_DISPLAY: f32 = 0.0000000000001;
/// Create a new [`FloatWrapper`].
#[inline(always)]
#[must_use]
pub fn new(value: F) -> Self {
Self(value)
}
}
#[cfg(not(feature = "no_float"))]
impl FloatWrapper<crate::FLOAT> {
/// Create a new [`FloatWrapper`].
#[inline(always)]
#[must_use]
pub const fn new_const(value: crate::FLOAT) -> Self {
Self(value)
}
}
/// _(internals)_ An expression sub-tree.
/// Exported under the `internals` feature only.
#[derive(Clone, Hash)]
pub enum Expr {
/// Dynamic constant.
///
/// Used to hold complex constants such as [`Array`][crate::Array] or [`Map`][crate::Map] for quick cloning.
/// Primitive data types should use the appropriate variants to avoid an allocation.
DynamicConstant(Box<Dynamic>, Position),
/// Boolean constant.
BoolConstant(bool, Position),
/// Integer constant.
IntegerConstant(INT, Position),
/// Floating-point constant.
///
/// Not available under `no_float`.
#[cfg(not(feature = "no_float"))]
FloatConstant(FloatWrapper<crate::FLOAT>, Position),
/// Character constant.
CharConstant(char, Position),
/// [String][ImmutableString] constant.
StringConstant(ImmutableString, Position),
/// An interpolated [string][ImmutableString].
InterpolatedString(Box<StaticVec<Expr>>, Position),
/// [ expr, ... ]
Array(Box<StaticVec<Expr>>, Position),
/// #{ name:expr, ... }
Map(
Box<(StaticVec<(Ident, Expr)>, BTreeMap<Identifier, Dynamic>)>,
Position,
),
/// ()
Unit(Position),
/// Variable access - optional short index, position, (optional index, optional (hash, modules), variable name)
///
/// The short index is [`u8`] which is used when the index is <= 255, which should be the vast
/// majority of cases (unless there are more than 255 variables defined!).
/// This is to avoid reading a pointer redirection during each variable access.
Variable(
Option<NonZeroU8>,
Position,
Box<(Option<NonZeroUsize>, Option<(Namespace, u64)>, Identifier)>,
),
/// Property access - ((getter, hash), (setter, hash), prop)
Property(
Box<(
(Identifier, u64),
(Identifier, u64),
(ImmutableString, Position),
)>,
),
/// Stack slot for function calls. See [`FnCallExpr`] for more details.
///
/// This variant does not map to any language structure. It is used in function calls with
/// constant arguments where the `usize` number indexes into an array containing a list of
/// constant arguments for the function call.
Stack(usize, Position),
/// { [statement][Stmt] ... }
Stmt(Box<StmtBlock>),
/// func `(` expr `,` ... `)`
FnCall(Box<FnCallExpr>, Position),
/// lhs `.` rhs - bool variable is a dummy
Dot(Box<BinaryExpr>, bool, Position),
/// expr `[` expr `]` - boolean indicates whether the dotting/indexing chain stops
Index(Box<BinaryExpr>, bool, Position),
/// lhs `&&` rhs
And(Box<BinaryExpr>, Position),
/// lhs `||` rhs
Or(Box<BinaryExpr>, Position),
/// Custom syntax
Custom(Box<CustomExpr>, Position),
}
impl Default for Expr {
#[inline(always)]
fn default() -> Self {
Self::Unit(Position::NONE)
}
}
impl fmt::Debug for Expr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut display_pos = self.position();
match self {
Self::DynamicConstant(value, _) => write!(f, "{:?}", value),
Self::BoolConstant(value, _) => write!(f, "{:?}", value),
Self::IntegerConstant(value, _) => write!(f, "{:?}", value),
#[cfg(not(feature = "no_float"))]
Self::FloatConstant(value, _) => write!(f, "{:?}", value),
Self::CharConstant(value, _) => write!(f, "{:?}", value),
Self::StringConstant(value, _) => write!(f, "{:?}", value),
Self::Unit(_) => f.write_str("()"),
Self::InterpolatedString(x, _) => {
f.write_str("InterpolatedString")?;
return f.debug_list().entries(x.iter()).finish();
}
Self::Array(x, _) => {
f.write_str("Array")?;
f.debug_list().entries(x.iter()).finish()
}
Self::Map(x, _) => {
f.write_str("Map")?;
f.debug_map()
.entries(x.0.iter().map(|(k, v)| (k, v)))
.finish()
}
Self::Variable(i, _, x) => {
f.write_str("Variable(")?;
if let Some((_, ref namespace)) = x.1 {
write!(f, "{}{}", namespace, Token::DoubleColon.literal_syntax())?
}
f.write_str(&x.2)?;
if let Some(n) = i.map_or_else(|| x.0, |n| NonZeroUsize::new(n.get() as usize)) {
write!(f, " #{}", n)?
}
f.write_str(")")
}
Self::Property(x) => write!(f, "Property({})", (x.2).0),
Self::Stack(x, _) => write!(f, "StackSlot({})", x),
Self::Stmt(x) => {
f.write_str("ExprStmtBlock")?;
f.debug_list().entries(x.iter()).finish()
}
Self::FnCall(x, _) => {
let mut ff = f.debug_struct("FnCall");
x.namespace.as_ref().map(|ns| ff.field("namespace", ns));
ff.field("name", &x.name)
.field("hash", &x.hashes)
.field("args", &x.args);
if !x.constants.is_empty() {
ff.field("constants", &x.constants);
}
if x.capture_parent_scope {
ff.field("capture_parent_scope", &x.capture_parent_scope);
}
ff.finish()
}
Self::Index(x, term, pos) => {
display_pos = *pos;
f.debug_struct("Index")
.field("lhs", &x.lhs)
.field("rhs", &x.rhs)
.field("terminate", term)
.finish()
}
Self::Dot(x, _, pos) | Self::And(x, pos) | Self::Or(x, pos) => {
let op_name = match self {
Self::Dot(_, _, _) => "Dot",
Self::And(_, _) => "And",
Self::Or(_, _) => "Or",
expr => unreachable!(
"Self::Dot or Self::And or Self::Or expected but gets {:?}",
expr
),
};
display_pos = *pos;
f.debug_struct(op_name)
.field("lhs", &x.lhs)
.field("rhs", &x.rhs)
.finish()
}
Self::Custom(x, _) => f.debug_tuple("Custom").field(x).finish(),
}?;
display_pos.debug_print(f)
}
}
impl Expr {
/// Get the [`Dynamic`] value of a literal constant expression.
///
/// Returns [`None`] if the expression is not a literal constant.
#[inline]
#[must_use]
pub fn get_literal_value(&self) -> Option<Dynamic> {
Some(match self {
Self::DynamicConstant(x, _) => x.as_ref().clone(),
Self::IntegerConstant(x, _) => (*x).into(),
#[cfg(not(feature = "no_float"))]
Self::FloatConstant(x, _) => (*x).into(),
Self::CharConstant(x, _) => (*x).into(),
Self::StringConstant(x, _) => x.clone().into(),
Self::BoolConstant(x, _) => (*x).into(),
Self::Unit(_) => Dynamic::UNIT,
#[cfg(not(feature = "no_index"))]
Self::Array(x, _) if self.is_constant() => {
let mut arr = crate::Array::with_capacity(x.len());
arr.extend(x.iter().map(|v| v.get_literal_value().unwrap()));
Dynamic::from_array(arr)
}
#[cfg(not(feature = "no_object"))]
Self::Map(x, _) if self.is_constant() => {
Dynamic::from_map(x.0.iter().fold(x.1.clone(), |mut map, (k, v)| {
let value_ref = map.get_mut(k.name.as_str()).unwrap();
*value_ref = v.get_literal_value().unwrap();
map
}))
}
// Binary operators
Self::FnCall(x, _) if x.args.len() == 2 => match x.name.as_str() {
// x..y
OP_EXCLUSIVE_RANGE => {
if let Expr::IntegerConstant(ref start, _) = x.args[0] {
if let Expr::IntegerConstant(ref end, _) = x.args[1] {
(*start..*end).into()
} else {
return None;
}
} else {
return None;
}
}
// x..=y
OP_INCLUSIVE_RANGE => {
if let Expr::IntegerConstant(ref start, _) = x.args[0] {
if let Expr::IntegerConstant(ref end, _) = x.args[1] {
(*start..=*end).into()
} else {
return None;
}
} else {
return None;
}
}
_ => return None,
},
_ => return None,
})
}
/// Create an [`Expr`] from a [`Dynamic`] value.
#[inline]
#[must_use]
pub fn from_dynamic(value: Dynamic, pos: Position) -> Self {
match value.0 {
Union::Unit(_, _, _) => Self::Unit(pos),
Union::Bool(b, _, _) => Self::BoolConstant(b, pos),
Union::Str(s, _, _) => Self::StringConstant(s, pos),
Union::Char(c, _, _) => Self::CharConstant(c, pos),
Union::Int(i, _, _) => Self::IntegerConstant(i, pos),
#[cfg(feature = "decimal")]
Union::Decimal(value, _, _) => Self::DynamicConstant(Box::new((*value).into()), pos),
#[cfg(not(feature = "no_float"))]
Union::Float(f, _, _) => Self::FloatConstant(f, pos),
#[cfg(not(feature = "no_index"))]
Union::Array(a, _, _) => Self::DynamicConstant(Box::new((*a).into()), pos),
#[cfg(not(feature = "no_object"))]
Union::Map(m, _, _) => Self::DynamicConstant(Box::new((*m).into()), pos),
_ => Self::DynamicConstant(value.into(), pos),
}
}
/// Is the expression a simple variable access?
#[inline]
#[must_use]
pub(crate) const fn is_variable_access(&self, non_qualified: bool) -> bool {
match self {
Self::Variable(_, _, x) => !non_qualified || x.1.is_none(),
_ => false,
}
}
/// Return the variable name if the expression a simple variable access.
#[inline]
#[must_use]
pub(crate) fn get_variable_name(&self, non_qualified: bool) -> Option<&str> {
match self {
Self::Variable(_, _, x) if !non_qualified || x.1.is_none() => Some(x.2.as_str()),
_ => None,
}
}
/// Get the [position][Position] of the expression.
#[inline]
#[must_use]
pub const fn position(&self) -> Position {
match self {
#[cfg(not(feature = "no_float"))]
Self::FloatConstant(_, pos) => *pos,
Self::DynamicConstant(_, pos)
| Self::BoolConstant(_, pos)
| Self::IntegerConstant(_, pos)
| Self::CharConstant(_, pos)
| Self::Unit(pos)
| Self::StringConstant(_, pos)
| Self::Array(_, pos)
| Self::Map(_, pos)
| Self::Variable(_, pos, _)
| Self::Stack(_, pos)
| Self::FnCall(_, pos)
| Self::Custom(_, pos)
| Self::InterpolatedString(_, pos) => *pos,
Self::Property(x) => (x.2).1,
Self::Stmt(x) => x.position(),
Self::And(x, _) | Self::Or(x, _) | Self::Dot(x, _, _) | Self::Index(x, _, _) => {
x.lhs.position()
}
}
}
/// Override the [position][Position] of the expression.
#[inline]
pub fn set_position(&mut self, new_pos: Position) -> &mut Self {
match self {
#[cfg(not(feature = "no_float"))]
Self::FloatConstant(_, pos) => *pos = new_pos,
Self::DynamicConstant(_, pos)
| Self::BoolConstant(_, pos)
| Self::IntegerConstant(_, pos)
| Self::CharConstant(_, pos)
| Self::Unit(pos)
| Self::StringConstant(_, pos)
| Self::Array(_, pos)
| Self::Map(_, pos)
| Self::And(_, pos)
| Self::Or(_, pos)
| Self::Dot(_, _, pos)
| Self::Index(_, _, pos)
| Self::Variable(_, pos, _)
| Self::Stack(_, pos)
| Self::FnCall(_, pos)
| Self::Custom(_, pos)
| Self::InterpolatedString(_, pos) => *pos = new_pos,
Self::Property(x) => (x.2).1 = new_pos,
Self::Stmt(x) => x.set_position(new_pos),
}
self
}
/// Is the expression pure?
///
/// A pure expression has no side effects.
#[inline]
#[must_use]
pub fn is_pure(&self) -> bool {
match self {
Self::InterpolatedString(x, _) | Self::Array(x, _) => x.iter().all(Self::is_pure),
Self::Map(x, _) => x.0.iter().map(|(_, v)| v).all(Self::is_pure),
Self::And(x, _) | Self::Or(x, _) => x.lhs.is_pure() && x.rhs.is_pure(),
Self::Stmt(x) => x.iter().all(Stmt::is_pure),
Self::Variable(_, _, _) | Self::Stack(_, _) => true,
_ => self.is_constant(),
}
}
/// Is the expression the unit `()` literal?
#[inline(always)]
#[must_use]
pub const fn is_unit(&self) -> bool {
matches!(self, Self::Unit(_))
}
/// Is the expression a constant?
#[inline]
#[must_use]
pub fn is_constant(&self) -> bool {
match self {
#[cfg(not(feature = "no_float"))]
Self::FloatConstant(_, _) => true,
Self::DynamicConstant(_, _)
| Self::BoolConstant(_, _)
| Self::IntegerConstant(_, _)
| Self::CharConstant(_, _)
| Self::StringConstant(_, _)
| Self::Unit(_)
| Self::Stack(_, _) => true,
Self::InterpolatedString(x, _) | Self::Array(x, _) => x.iter().all(Self::is_constant),
Self::Map(x, _) => x.0.iter().map(|(_, expr)| expr).all(Self::is_constant),
_ => false,
}
}
/// Is a particular [token][Token] allowed as a postfix operator to this expression?
#[inline]
#[must_use]
pub const fn is_valid_postfix(&self, token: &Token) -> bool {
match token {
#[cfg(not(feature = "no_object"))]
Token::Period => return true,
_ => (),
}
match self {
#[cfg(not(feature = "no_float"))]
Self::FloatConstant(_, _) => false,
Self::DynamicConstant(_, _)
| Self::BoolConstant(_, _)
| Self::CharConstant(_, _)
| Self::And(_, _)
| Self::Or(_, _)
| Self::Unit(_) => false,
Self::IntegerConstant(_, _)
| Self::StringConstant(_, _)
| Self::InterpolatedString(_, _)
| Self::FnCall(_, _)
| Self::Stmt(_)
| Self::Dot(_, _, _)
| Self::Index(_, _, _)
| Self::Array(_, _)
| Self::Map(_, _)
| Self::Custom(_, _) => match token {
#[cfg(not(feature = "no_index"))]
Token::LeftBracket => true,
_ => false,
},
Self::Variable(_, _, _) => match token {
#[cfg(not(feature = "no_index"))]
Token::LeftBracket => true,
Token::LeftParen => true,
Token::Bang => true,
Token::DoubleColon => true,
_ => false,
},
Self::Property(_) => match token {
#[cfg(not(feature = "no_index"))]
Token::LeftBracket => true,
Token::LeftParen => true,
_ => false,
},
Self::Stack(_, _) => false,
}
}
/// Recursively walk this expression.
/// Return `false` from the callback to terminate the walk.
pub fn walk<'a>(
&'a self,
path: &mut Vec<ASTNode<'a>>,
on_node: &mut impl FnMut(&[ASTNode]) -> bool,
) -> bool {
// Push the current node onto the path
path.push(self.into());
if !on_node(path) {
return false;
}
match self {
Self::Stmt(x) => {
for s in x.iter() {
if !s.walk(path, on_node) {
return false;
}
}
}
Self::InterpolatedString(x, _) | Self::Array(x, _) => {
for e in x.as_ref() {
if !e.walk(path, on_node) {
return false;
}
}
}
Self::Map(x, _) => {
for (_, e) in &x.0 {
if !e.walk(path, on_node) {
return false;
}
}
}
Self::Index(x, _, _) | Self::Dot(x, _, _) | Expr::And(x, _) | Expr::Or(x, _) => {
if !x.lhs.walk(path, on_node) {
return false;
}
if !x.rhs.walk(path, on_node) {
return false;
}
}
Self::FnCall(x, _) => {
for e in &x.args {
if !e.walk(path, on_node) {
return false;
}
}
}
Self::Custom(x, _) => {
for e in &x.inputs {
if !e.walk(path, on_node) {
return false;
}
}
}
_ => (),
}
path.pop().unwrap();
true
}
}

169
src/ast/flags.rs Normal file
View File

@ -0,0 +1,169 @@
//! Module defining script options.
use std::ops::{Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, Not, Sub, SubAssign};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
/// A type representing the access mode of a function.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum FnAccess {
/// Public function.
Public,
/// Private function.
Private,
}
/// A type that holds a configuration option with bit-flags.
/// Exported under the `internals` feature only.
#[derive(PartialEq, Eq, Copy, Clone, Hash, Default)]
pub struct OptionFlags(u8);
impl OptionFlags {
/// Does this [`OptionFlags`] contain a particular option flag?
#[inline(always)]
#[must_use]
pub const fn contains(self, flag: Self) -> bool {
self.0 & flag.0 != 0
}
}
impl Not for OptionFlags {
type Output = Self;
/// Return the negation of the [`OptionFlags`].
#[inline(always)]
fn not(self) -> Self::Output {
Self(!self.0) & AST_OPTION_FLAGS::AST_OPTION_ALL
}
}
impl Add for OptionFlags {
type Output = Self;
/// Return the union of two [`OptionFlags`].
#[inline(always)]
fn add(self, rhs: Self) -> Self::Output {
Self(self.0 | rhs.0)
}
}
impl AddAssign for OptionFlags {
/// Add the option flags in one [`OptionFlags`] to another.
#[inline(always)]
fn add_assign(&mut self, rhs: Self) {
self.0 |= rhs.0
}
}
impl BitOr for OptionFlags {
type Output = Self;
/// Return the union of two [`OptionFlags`].
#[inline(always)]
fn bitor(self, rhs: Self) -> Self::Output {
Self(self.0 | rhs.0)
}
}
impl BitOrAssign for OptionFlags {
/// Add the option flags in one [`OptionFlags`] to another.
#[inline(always)]
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0
}
}
impl Sub for OptionFlags {
type Output = Self;
/// Return the difference of two [`OptionFlags`].
#[inline(always)]
fn sub(self, rhs: Self) -> Self::Output {
Self(self.0 & !rhs.0)
}
}
impl SubAssign for OptionFlags {
/// Remove the option flags in one [`OptionFlags`] from another.
#[inline(always)]
fn sub_assign(&mut self, rhs: Self) {
self.0 &= !rhs.0
}
}
impl BitAnd for OptionFlags {
type Output = Self;
/// Return the intersection of two [`OptionFlags`].
#[inline(always)]
fn bitand(self, rhs: Self) -> Self::Output {
Self(self.0 & !rhs.0)
}
}
impl BitAndAssign for OptionFlags {
/// Keep only the intersection of one [`OptionFlags`] with another.
#[inline(always)]
fn bitand_assign(&mut self, rhs: Self) {
self.0 &= !rhs.0
}
}
/// Option bit-flags for [`AST`][super::AST] nodes.
#[allow(non_snake_case)]
pub mod AST_OPTION_FLAGS {
use super::OptionFlags;
/// _(internals)_ No options for the [`AST`][crate::AST] node.
/// Exported under the `internals` feature only.
pub const AST_OPTION_NONE: OptionFlags = OptionFlags(0b0000_0000);
/// _(internals)_ The [`AST`][crate::AST] node is constant.
/// Exported under the `internals` feature only.
pub const AST_OPTION_CONSTANT: OptionFlags = OptionFlags(0b0000_0001);
/// _(internals)_ The [`AST`][crate::AST] node is public.
/// Exported under the `internals` feature only.
pub const AST_OPTION_PUBLIC: OptionFlags = OptionFlags(0b0000_0010);
/// _(internals)_ The [`AST`][crate::AST] node is in negated mode.
/// Exported under the `internals` feature only.
pub const AST_OPTION_NEGATED: OptionFlags = OptionFlags(0b0000_0100);
/// _(internals)_ The [`AST`][crate::AST] node breaks out of normal control flow.
/// Exported under the `internals` feature only.
pub const AST_OPTION_BREAK_OUT: OptionFlags = OptionFlags(0b0000_1000);
/// _(internals)_ Mask of all options.
/// Exported under the `internals` feature only.
pub(crate) const AST_OPTION_ALL: OptionFlags = OptionFlags(
AST_OPTION_CONSTANT.0 | AST_OPTION_PUBLIC.0 | AST_OPTION_NEGATED.0 | AST_OPTION_BREAK_OUT.0,
);
impl std::fmt::Debug for OptionFlags {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn write_option(
options: &OptionFlags,
f: &mut std::fmt::Formatter<'_>,
num_flags: &mut usize,
flag: OptionFlags,
name: &str,
) -> std::fmt::Result {
if options.contains(flag) {
if *num_flags > 0 {
f.write_str("+")?;
}
f.write_str(name)?;
*num_flags += 1;
}
Ok(())
}
let num_flags = &mut 0;
f.write_str("(")?;
write_option(self, f, num_flags, AST_OPTION_CONSTANT, "Constant")?;
write_option(self, f, num_flags, AST_OPTION_PUBLIC, "Public")?;
write_option(self, f, num_flags, AST_OPTION_NEGATED, "Negated")?;
write_option(self, f, num_flags, AST_OPTION_BREAK_OUT, "Break")?;
f.write_str(")")?;
Ok(())
}
}
}

37
src/ast/ident.rs Normal file
View File

@ -0,0 +1,37 @@
//! Module defining script identifiers.
use crate::{Identifier, Position};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{fmt, hash::Hash};
/// _(internals)_ An identifier containing a name and a [position][Position].
/// Exported under the `internals` feature only.
#[derive(Clone, Eq, PartialEq, Hash)]
pub struct Ident {
/// Identifier name.
pub name: Identifier,
/// Position.
pub pos: Position,
}
impl fmt::Debug for Ident {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.name)?;
self.pos.debug_print(f)
}
}
impl AsRef<str> for Ident {
#[inline(always)]
fn as_ref(&self) -> &str {
self.name.as_ref()
}
}
impl Ident {
#[inline(always)]
pub fn as_str(&self) -> &str {
self.name.as_str()
}
}

22
src/ast/mod.rs Normal file
View File

@ -0,0 +1,22 @@
//! Module defining the AST (abstract syntax tree).
pub mod ast;
pub mod expr;
pub mod flags;
pub mod ident;
pub mod script_fn;
pub mod stmt;
pub use ast::{ASTNode, AST};
pub use expr::{BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes};
pub use flags::{FnAccess, OptionFlags, AST_OPTION_FLAGS};
pub use ident::Ident;
#[cfg(not(feature = "no_function"))]
pub use script_fn::{ScriptFnDef, ScriptFnMetadata};
pub use stmt::{OpAssignment, Stmt, StmtBlock};
#[cfg(not(feature = "no_float"))]
pub use expr::FloatWrapper;
#[cfg(feature = "no_function")]
pub struct ScriptFnDef;

128
src/ast/script_fn.rs Normal file
View File

@ -0,0 +1,128 @@
//! Module defining script-defined functions.
#![cfg(not(feature = "no_function"))]
use super::{FnAccess, StmtBlock};
use crate::{Identifier, Module, Shared, StaticVec};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{fmt, hash::Hash};
/// _(internals)_ A type containing information on a script-defined function.
/// Exported under the `internals` feature only.
#[derive(Debug, Clone)]
pub struct ScriptFnDef {
/// Function body.
pub body: StmtBlock,
/// Encapsulated running environment, if any.
pub lib: Option<Shared<Module>>,
/// Encapsulated stack of imported modules, if any.
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
pub global: Option<Box<[(Identifier, Shared<Module>)]>>,
/// Function name.
pub name: Identifier,
/// Function access mode.
pub access: FnAccess,
/// Names of function parameters.
pub params: StaticVec<Identifier>,
/// _(metadata)_ Function doc-comments (if any).
/// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")]
pub comments: Option<Box<[Box<str>]>>,
}
impl fmt::Display for ScriptFnDef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}{}({})",
match self.access {
FnAccess::Public => "",
FnAccess::Private => "private ",
},
self.name,
self.params
.iter()
.map(|s| s.as_str())
.collect::<StaticVec<_>>()
.join(", ")
)
}
}
/// A type containing the metadata of a script-defined function.
///
/// Not available under `no_function`.
///
/// Created by [`AST::iter_functions`][super::AST::iter_functions].
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub struct ScriptFnMetadata<'a> {
/// _(metadata)_ Function doc-comments (if any).
/// Exported under the `metadata` feature only.
///
/// Block doc-comments are kept in a single string slice with line-breaks within.
///
/// Line doc-comments are kept in one string slice per line without the termination line-break.
///
/// Leading white-spaces are stripped, and each string slice always starts with the corresponding
/// doc-comment leader: `///` or `/**`.
#[cfg(feature = "metadata")]
pub comments: Vec<&'a str>,
/// Function access mode.
pub access: FnAccess,
/// Function name.
pub name: &'a str,
/// Function parameters (if any).
pub params: Vec<&'a str>,
}
impl fmt::Display for ScriptFnMetadata<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}{}({})",
match self.access {
FnAccess::Public => "",
FnAccess::Private => "private ",
},
self.name,
self.params
.iter()
.cloned()
.collect::<StaticVec<_>>()
.join(", ")
)
}
}
impl<'a> From<&'a ScriptFnDef> for ScriptFnMetadata<'a> {
#[inline]
fn from(value: &'a ScriptFnDef) -> Self {
Self {
#[cfg(feature = "metadata")]
comments: value
.comments
.as_ref()
.map_or_else(|| Vec::new(), |v| v.iter().map(Box::as_ref).collect()),
access: value.access,
name: &value.name,
params: value.params.iter().map(|s| s.as_str()).collect(),
}
}
}
impl std::cmp::PartialOrd for ScriptFnMetadata<'_> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::cmp::Ord for ScriptFnMetadata<'_> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match self.name.cmp(other.name) {
std::cmp::Ordering::Equal => self.params.len().cmp(&other.params.len()),
cmp => cmp,
}
}
}

667
src/ast/stmt.rs Normal file
View File

@ -0,0 +1,667 @@
//! Module defining script statements.
use super::{ASTNode, Expr, FnCallExpr, Ident, OptionFlags, AST_OPTION_FLAGS};
use crate::engine::KEYWORD_EVAL;
use crate::tokenizer::Token;
use crate::{calc_fn_hash, Position, StaticVec, INT};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
collections::BTreeMap,
fmt,
hash::Hash,
mem,
ops::{Deref, DerefMut},
};
/// _(internals)_ An op-assignment operator.
/// Exported under the `internals` feature only.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct OpAssignment<'a> {
/// Hash of the op-assignment call.
pub hash_op_assign: u64,
/// Hash of the underlying operator call (for fallback).
pub hash_op: u64,
/// Op-assignment operator.
pub op: &'a str,
}
impl OpAssignment<'_> {
/// Create a new [`OpAssignment`].
///
/// # Panics
///
/// Panics if the name is not an op-assignment operator.
#[must_use]
#[inline(always)]
pub fn new(name: &str) -> Self {
Self::new_from_token(Token::lookup_from_syntax(name).expect("operator"))
}
/// Create a new [`OpAssignment`] from a [`Token`].
///
/// # Panics
///
/// Panics if the token is not an op-assignment operator.
#[must_use]
pub fn new_from_token(op: Token) -> Self {
let op_raw = op
.map_op_assignment()
.expect("op-assignment operator")
.literal_syntax();
Self {
hash_op_assign: calc_fn_hash(op.literal_syntax(), 2),
hash_op: calc_fn_hash(op_raw, 2),
op: op.literal_syntax(),
}
}
}
/// _(internals)_ A scoped block of statements.
/// Exported under the `internals` feature only.
#[derive(Clone, Hash, Default)]
pub struct StmtBlock(StaticVec<Stmt>, Position);
impl StmtBlock {
/// A [`StmtBlock`] that does not exist.
pub const NONE: Self = Self::empty(Position::NONE);
/// Create a new [`StmtBlock`].
#[must_use]
pub fn new(statements: impl IntoIterator<Item = Stmt>, pos: Position) -> Self {
let mut statements: StaticVec<_> = statements.into_iter().collect();
statements.shrink_to_fit();
Self(statements, pos)
}
/// Create an empty [`StmtBlock`].
#[inline(always)]
#[must_use]
pub const fn empty(pos: Position) -> Self {
Self(StaticVec::new_const(), pos)
}
/// Is this statements block empty?
#[inline(always)]
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Number of statements in this statements block.
#[inline(always)]
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
/// Get the statements of this statements block.
#[inline(always)]
#[must_use]
pub fn statements(&self) -> &[Stmt] {
&self.0
}
/// Extract the statements.
#[inline(always)]
#[must_use]
pub(crate) fn take_statements(&mut self) -> StaticVec<Stmt> {
mem::take(&mut self.0)
}
/// Get an iterator over the statements of this statements block.
#[inline(always)]
#[must_use]
pub fn iter(&self) -> impl Iterator<Item = &Stmt> {
self.0.iter()
}
/// Get the position (location of the beginning `{`) of this statements block.
#[inline(always)]
#[must_use]
pub const fn position(&self) -> Position {
self.1
}
/// Set the position (location of the beginning `{`) of this statements block.
#[inline(always)]
pub fn set_position(&mut self, pos: Position) {
self.1 = pos;
}
}
impl Deref for StmtBlock {
type Target = StaticVec<Stmt>;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for StmtBlock {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl fmt::Debug for StmtBlock {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Block")?;
fmt::Debug::fmt(&self.0, f)?;
self.1.debug_print(f)
}
}
impl From<Stmt> for StmtBlock {
#[inline]
fn from(stmt: Stmt) -> Self {
match stmt {
Stmt::Block(mut block, pos) => Self(block.iter_mut().map(mem::take).collect(), pos),
Stmt::Noop(pos) => Self(StaticVec::new_const(), pos),
_ => {
let pos = stmt.position();
Self(vec![stmt].into(), pos)
}
}
}
}
impl IntoIterator for StmtBlock {
type Item = Stmt;
type IntoIter = smallvec::IntoIter<[Stmt; 3]>;
#[inline(always)]
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl Extend<Stmt> for StmtBlock {
#[inline(always)]
fn extend<T: IntoIterator<Item = Stmt>>(&mut self, iter: T) {
self.0.extend(iter)
}
}
/// _(internals)_ A statement.
/// Exported under the `internals` feature only.
#[derive(Debug, Clone, Hash)]
pub enum Stmt {
/// No-op.
Noop(Position),
/// `if` expr `{` stmt `}` `else` `{` stmt `}`
If(Expr, Box<(StmtBlock, StmtBlock)>, Position),
/// `switch` expr `{` literal or range or _ `if` condition `=>` stmt `,` ... `}`
///
/// ### Data Structure
///
/// 0) Hash table for (condition, block)
/// 1) Default block
/// 2) List of ranges: (start, end, inclusive, condition, statement)
Switch(
Expr,
Box<(
BTreeMap<u64, Box<(Option<Expr>, StmtBlock)>>,
StmtBlock,
StaticVec<(INT, INT, bool, Option<Expr>, StmtBlock)>,
)>,
Position,
),
/// `while` expr `{` stmt `}` | `loop` `{` stmt `}`
///
/// If the guard expression is [`UNIT`][Expr::Unit], then it is a `loop` statement.
While(Expr, Box<StmtBlock>, Position),
/// `do` `{` stmt `}` `while`|`until` expr
///
/// ### Option Flags
///
/// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `while`
/// * [`AST_OPTION_NEGATED`][AST_OPTION_FLAGS::AST_OPTION_NEGATED] = `until`
Do(Box<StmtBlock>, Expr, OptionFlags, Position),
/// `for` `(` id `,` counter `)` `in` expr `{` stmt `}`
For(Expr, Box<(Ident, Option<Ident>, StmtBlock)>, Position),
/// \[`export`\] `let`|`const` id `=` expr
///
/// ### Option Flags
///
/// * [`AST_OPTION_PUBLIC`][AST_OPTION_FLAGS::AST_OPTION_PUBLIC] = `export`
/// * [`AST_OPTION_CONSTANT`][AST_OPTION_FLAGS::AST_OPTION_CONSTANT] = `const`
Var(Expr, Box<Ident>, OptionFlags, Position),
/// expr op`=` expr
Assignment(Box<(Expr, Option<OpAssignment<'static>>, Expr)>, Position),
/// func `(` expr `,` ... `)`
///
/// Note - this is a duplicate of [`Expr::FnCall`] to cover the very common pattern of a single
/// function call forming one statement.
FnCall(Box<FnCallExpr>, Position),
/// `{` stmt`;` ... `}`
Block(Box<[Stmt]>, Position),
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
TryCatch(Box<(StmtBlock, Option<Ident>, StmtBlock)>, Position),
/// [expression][Expr]
Expr(Expr),
/// `continue`/`break`
///
/// ### Option Flags
///
/// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `continue`
/// * [`AST_OPTION_BREAK_OUT`][AST_OPTION_FLAGS::AST_OPTION_BREAK_OUT] = `break`
BreakLoop(OptionFlags, Position),
/// `return`/`throw`
///
/// ### Option Flags
///
/// * [`AST_OPTION_NONE`][AST_OPTION_FLAGS::AST_OPTION_NONE] = `return`
/// * [`AST_OPTION_BREAK_OUT`][AST_OPTION_FLAGS::AST_OPTION_BREAK_OUT] = `throw`
Return(OptionFlags, Option<Expr>, Position),
/// `import` expr `as` var
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
Import(Expr, Option<Box<Ident>>, Position),
/// `export` var `as` var `,` ...
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
Export(Box<[(Ident, Ident)]>, Position),
/// Convert a variable to shared.
///
/// Not available under `no_closure`.
///
/// # Notes
///
/// This variant does not map to any language structure. It is currently only used only to
/// convert a normal variable into a shared variable when the variable is _captured_ by a closure.
#[cfg(not(feature = "no_closure"))]
Share(crate::Identifier),
}
impl Default for Stmt {
#[inline(always)]
fn default() -> Self {
Self::Noop(Position::NONE)
}
}
impl From<StmtBlock> for Stmt {
#[inline(always)]
fn from(block: StmtBlock) -> Self {
Self::Block(block.0.into_boxed_slice(), block.1)
}
}
impl Stmt {
/// Is this statement [`Noop`][Stmt::Noop]?
#[inline(always)]
#[must_use]
pub const fn is_noop(&self) -> bool {
matches!(self, Self::Noop(_))
}
/// Get the [position][Position] of this statement.
#[must_use]
pub const fn position(&self) -> Position {
match self {
Self::Noop(pos)
| Self::BreakLoop(_, pos)
| Self::Block(_, pos)
| Self::Assignment(_, pos)
| Self::FnCall(_, pos)
| Self::If(_, _, pos)
| Self::Switch(_, _, pos)
| Self::While(_, _, pos)
| Self::Do(_, _, _, pos)
| Self::For(_, _, pos)
| Self::Return(_, _, pos)
| Self::Var(_, _, _, pos)
| Self::TryCatch(_, pos) => *pos,
Self::Expr(x) => x.position(),
#[cfg(not(feature = "no_module"))]
Self::Import(_, _, pos) => *pos,
#[cfg(not(feature = "no_module"))]
Self::Export(_, pos) => *pos,
#[cfg(not(feature = "no_closure"))]
Self::Share(_) => Position::NONE,
}
}
/// Override the [position][Position] of this statement.
pub fn set_position(&mut self, new_pos: Position) -> &mut Self {
match self {
Self::Noop(pos)
| Self::BreakLoop(_, pos)
| Self::Block(_, pos)
| Self::Assignment(_, pos)
| Self::FnCall(_, pos)
| Self::If(_, _, pos)
| Self::Switch(_, _, pos)
| Self::While(_, _, pos)
| Self::Do(_, _, _, pos)
| Self::For(_, _, pos)
| Self::Return(_, _, pos)
| Self::Var(_, _, _, pos)
| Self::TryCatch(_, pos) => *pos = new_pos,
Self::Expr(x) => {
x.set_position(new_pos);
}
#[cfg(not(feature = "no_module"))]
Self::Import(_, _, pos) => *pos = new_pos,
#[cfg(not(feature = "no_module"))]
Self::Export(_, pos) => *pos = new_pos,
#[cfg(not(feature = "no_closure"))]
Self::Share(_) => (),
}
self
}
/// Does this statement return a value?
#[must_use]
pub const fn returns_value(&self) -> bool {
match self {
Self::If(_, _, _)
| Self::Switch(_, _, _)
| Self::Block(_, _)
| Self::Expr(_)
| Self::FnCall(_, _) => true,
Self::Noop(_)
| Self::While(_, _, _)
| Self::Do(_, _, _, _)
| Self::For(_, _, _)
| Self::TryCatch(_, _) => false,
Self::Var(_, _, _, _)
| Self::Assignment(_, _)
| Self::BreakLoop(_, _)
| Self::Return(_, _, _) => false,
#[cfg(not(feature = "no_module"))]
Self::Import(_, _, _) | Self::Export(_, _) => false,
#[cfg(not(feature = "no_closure"))]
Self::Share(_) => false,
}
}
/// Is this statement self-terminated (i.e. no need for a semicolon terminator)?
#[must_use]
pub const fn is_self_terminated(&self) -> bool {
match self {
Self::If(_, _, _)
| Self::Switch(_, _, _)
| Self::While(_, _, _)
| Self::For(_, _, _)
| Self::Block(_, _)
| Self::TryCatch(_, _) => true,
// A No-op requires a semicolon in order to know it is an empty statement!
Self::Noop(_) => false,
Self::Expr(Expr::Custom(x, _)) if x.is_self_terminated() => true,
Self::Var(_, _, _, _)
| Self::Assignment(_, _)
| Self::Expr(_)
| Self::FnCall(_, _)
| Self::Do(_, _, _, _)
| Self::BreakLoop(_, _)
| Self::Return(_, _, _) => false,
#[cfg(not(feature = "no_module"))]
Self::Import(_, _, _) | Self::Export(_, _) => false,
#[cfg(not(feature = "no_closure"))]
Self::Share(_) => false,
}
}
/// Is this statement _pure_?
///
/// A pure statement has no side effects.
#[must_use]
pub fn is_pure(&self) -> bool {
match self {
Self::Noop(_) => true,
Self::Expr(expr) => expr.is_pure(),
Self::If(condition, x, _) => {
condition.is_pure()
&& (x.0).0.iter().all(Stmt::is_pure)
&& (x.1).0.iter().all(Stmt::is_pure)
}
Self::Switch(expr, x, _) => {
expr.is_pure()
&& x.0.values().all(|block| {
block.0.as_ref().map(Expr::is_pure).unwrap_or(true)
&& (block.1).0.iter().all(Stmt::is_pure)
})
&& (x.2).iter().all(|(_, _, _, condition, stmt)| {
condition.as_ref().map(Expr::is_pure).unwrap_or(true)
&& stmt.0.iter().all(Stmt::is_pure)
})
&& (x.1).0.iter().all(Stmt::is_pure)
}
// Loops that exit can be pure because it can never be infinite.
Self::While(Expr::BoolConstant(false, _), _, _) => true,
Self::Do(body, Expr::BoolConstant(x, _), options, _)
if *x == options.contains(AST_OPTION_FLAGS::AST_OPTION_NEGATED) =>
{
body.iter().all(Stmt::is_pure)
}
// Loops are never pure since they can be infinite - and that's a side effect.
Self::While(_, _, _) | Self::Do(_, _, _, _) => false,
// For loops can be pure because if the iterable is pure, it is finite,
// so infinite loops can never occur.
Self::For(iterable, x, _) => iterable.is_pure() && (x.2).0.iter().all(Stmt::is_pure),
Self::Var(_, _, _, _) | Self::Assignment(_, _) | Self::FnCall(_, _) => false,
Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()),
Self::BreakLoop(_, _) | Self::Return(_, _, _) => false,
Self::TryCatch(x, _) => {
(x.0).0.iter().all(Stmt::is_pure) && (x.2).0.iter().all(Stmt::is_pure)
}
#[cfg(not(feature = "no_module"))]
Self::Import(_, _, _) => false,
#[cfg(not(feature = "no_module"))]
Self::Export(_, _) => false,
#[cfg(not(feature = "no_closure"))]
Self::Share(_) => false,
}
}
/// Does this statement's behavior depend on its containing block?
///
/// A statement that depends on its containing block behaves differently when promoted
/// to an upper block.
///
/// Currently only variable definitions (i.e. `let` and `const`), `import`/`export` statements,
/// and `eval` calls (which may in turn call define variables) fall under this category.
#[inline]
#[must_use]
pub fn is_block_dependent(&self) -> bool {
match self {
Self::Var(_, _, _, _) => true,
Self::Expr(Expr::Stmt(s)) => s.iter().all(Stmt::is_block_dependent),
Self::FnCall(x, _) | Self::Expr(Expr::FnCall(x, _)) => {
x.namespace.is_none() && x.name == KEYWORD_EVAL
}
#[cfg(not(feature = "no_module"))]
Self::Import(_, _, _) | Self::Export(_, _) => true,
_ => false,
}
}
/// Is this statement _pure_ within the containing block?
///
/// An internally pure statement only has side effects that disappear outside the block.
///
/// Currently only variable definitions (i.e. `let` and `const`) and `import`/`export`
/// statements are internally pure, other than pure expressions.
#[inline]
#[must_use]
pub fn is_internally_pure(&self) -> bool {
match self {
Self::Var(expr, _, _, _) => expr.is_pure(),
Self::Expr(Expr::Stmt(s)) => s.iter().all(Stmt::is_internally_pure),
#[cfg(not(feature = "no_module"))]
Self::Import(expr, _, _) => expr.is_pure(),
#[cfg(not(feature = "no_module"))]
Self::Export(_, _) => true,
_ => self.is_pure(),
}
}
/// Does this statement break the current control flow through the containing block?
///
/// Currently this is only true for `return`, `throw`, `break` and `continue`.
///
/// All statements following this statement will essentially be dead code.
#[inline]
#[must_use]
pub const fn is_control_flow_break(&self) -> bool {
match self {
Self::Return(_, _, _) | Self::BreakLoop(_, _) => true,
_ => false,
}
}
/// Recursively walk this statement.
/// Return `false` from the callback to terminate the walk.
pub fn walk<'a>(
&'a self,
path: &mut Vec<ASTNode<'a>>,
on_node: &mut impl FnMut(&[ASTNode]) -> bool,
) -> bool {
// Push the current node onto the path
path.push(self.into());
if !on_node(path) {
return false;
}
match self {
Self::Var(e, _, _, _) => {
if !e.walk(path, on_node) {
return false;
}
}
Self::If(e, x, _) => {
if !e.walk(path, on_node) {
return false;
}
for s in &(x.0).0 {
if !s.walk(path, on_node) {
return false;
}
}
for s in &(x.1).0 {
if !s.walk(path, on_node) {
return false;
}
}
}
Self::Switch(e, x, _) => {
if !e.walk(path, on_node) {
return false;
}
for b in x.0.values() {
if !b.0.as_ref().map(|e| e.walk(path, on_node)).unwrap_or(true) {
return false;
}
for s in &(b.1).0 {
if !s.walk(path, on_node) {
return false;
}
}
}
for (_, _, _, c, stmt) in &x.2 {
if !c.as_ref().map(|e| e.walk(path, on_node)).unwrap_or(true) {
return false;
}
for s in &stmt.0 {
if !s.walk(path, on_node) {
return false;
}
}
}
for s in &(x.1).0 {
if !s.walk(path, on_node) {
return false;
}
}
}
Self::While(e, s, _) | Self::Do(s, e, _, _) => {
if !e.walk(path, on_node) {
return false;
}
for s in &s.0 {
if !s.walk(path, on_node) {
return false;
}
}
}
Self::For(e, x, _) => {
if !e.walk(path, on_node) {
return false;
}
for s in &(x.2).0 {
if !s.walk(path, on_node) {
return false;
}
}
}
Self::Assignment(x, _) => {
if !x.0.walk(path, on_node) {
return false;
}
if !x.2.walk(path, on_node) {
return false;
}
}
Self::FnCall(x, _) => {
for s in &x.args {
if !s.walk(path, on_node) {
return false;
}
}
}
Self::Block(x, _) => {
for s in x.iter() {
if !s.walk(path, on_node) {
return false;
}
}
}
Self::TryCatch(x, _) => {
for s in &(x.0).0 {
if !s.walk(path, on_node) {
return false;
}
}
for s in &(x.2).0 {
if !s.walk(path, on_node) {
return false;
}
}
}
Self::Expr(e) | Self::Return(_, Some(e), _) => {
if !e.walk(path, on_node) {
return false;
}
}
#[cfg(not(feature = "no_module"))]
Self::Import(e, _, _) => {
if !e.walk(path, on_node) {
return false;
}
}
_ => (),
}
path.pop().unwrap();
true
}
}

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ use std::prelude::v1::*;
/// Trait that parses arguments to a function call.
///
/// Any data type can implement this trait in order to pass arguments to a function call.
/// Any data type can implement this trait in order to pass arguments to [`Engine::call_fn`][crate::Engine::call_fn].
pub trait FuncArgs {
/// Parse function call arguments into a container.
///
@ -26,10 +26,10 @@ pub trait FuncArgs {
/// }
///
/// impl FuncArgs for Options {
/// fn parse<CONTAINER: Extend<Dynamic>>(self, container: &mut CONTAINER) {
/// container.extend(std::iter::once(self.foo.into()));
/// container.extend(std::iter::once(self.bar.into()));
/// container.extend(std::iter::once(self.baz.into()));
/// fn parse<ARGS: Extend<Dynamic>>(self, args: &mut ARGS) {
/// args.extend(std::iter::once(self.foo.into()));
/// args.extend(std::iter::once(self.bar.into()));
/// args.extend(std::iter::once(self.baz.into()));
/// }
/// }
///
@ -55,13 +55,13 @@ pub trait FuncArgs {
/// # Ok(())
/// # }
/// ```
fn parse<CONTAINER: Extend<Dynamic>>(self, container: &mut CONTAINER);
fn parse<ARGS: Extend<Dynamic>>(self, args: &mut ARGS);
}
impl<T: Variant + Clone> FuncArgs for Vec<T> {
#[inline]
fn parse<CONTAINER: Extend<Dynamic>>(self, container: &mut CONTAINER) {
container.extend(self.into_iter().map(Variant::into_dynamic));
fn parse<ARGS: Extend<Dynamic>>(self, args: &mut ARGS) {
args.extend(self.into_iter().map(Dynamic::from));
}
}
@ -73,9 +73,9 @@ macro_rules! impl_args {
{
#[inline]
#[allow(unused_variables)]
fn parse<CONTAINER: Extend<Dynamic>>(self, container: &mut CONTAINER) {
fn parse<ARGS: Extend<Dynamic>>(self, args: &mut ARGS) {
let ($($p,)*) = self;
$(container.extend(Some($p.into_dynamic()));)*
$(args.extend(Some(Dynamic::from($p)));)*
}
}

View File

@ -1,8 +1,9 @@
//! Built-in implementations for common operators.
use super::call::FnCallArgs;
use super::native::FnBuiltin;
use crate::engine::OP_CONTAINS;
use crate::{Dynamic, ImmutableString, NativeCallContext, RhaiResult, INT};
use crate::{Dynamic, ExclusiveRange, ImmutableString, InclusiveRange, INT};
use std::any::TypeId;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -24,7 +25,12 @@ const BUILTIN: &str = "data type was checked";
#[inline]
#[must_use]
fn is_numeric(type_id: TypeId) -> bool {
let result = type_id == TypeId::of::<u8>()
let result = false;
#[cfg(not(feature = "only_i64"))]
#[cfg(not(feature = "only_i32"))]
let result = result
|| type_id == TypeId::of::<u8>()
|| type_id == TypeId::of::<u16>()
|| type_id == TypeId::of::<u32>()
|| type_id == TypeId::of::<u64>()
@ -33,7 +39,10 @@ fn is_numeric(type_id: TypeId) -> bool {
|| type_id == TypeId::of::<i32>()
|| type_id == TypeId::of::<i64>();
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[cfg(not(feature = "only_i64"))]
#[cfg(not(feature = "only_i32"))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
let result = result || type_id == TypeId::of::<u128>() || type_id == TypeId::of::<i128>();
#[cfg(not(feature = "no_float"))]
@ -46,33 +55,13 @@ fn is_numeric(type_id: TypeId) -> bool {
}
/// Build in common binary operator implementations to avoid the cost of calling a registered function.
///
/// The return function will be registered as a _method_, so the first parameter cannot be consumed.
#[must_use]
pub fn get_builtin_binary_op_fn(
op: &str,
x: &Dynamic,
y: &Dynamic,
) -> Option<fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult> {
pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option<FnBuiltin> {
let type1 = x.type_id();
let type2 = y.type_id();
// One of the operands is a custom type, so it is never built-in
if x.is_variant() || y.is_variant() {
return if is_numeric(type1) && is_numeric(type2) {
// Disallow comparisons between different numeric types
None
} else if type1 != type2 {
// If the types are not the same, default to not compare
match op {
"!=" => Some(|_, _| Ok(Dynamic::TRUE)),
"==" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)),
_ => None,
}
} else {
// Disallow comparisons between the same type
None
};
}
let types_pair = (type1, type2);
macro_rules! impl_op {
@ -108,7 +97,7 @@ pub fn get_builtin_binary_op_fn(
($base:ty => $func:ident ( $xx:ident, $yy:ident )) => { |_, args| {
let x = args[0].$xx().expect(BUILTIN) as $base;
let y = args[1].$yy().expect(BUILTIN) as $base;
$func(x, y).map(Into::<Dynamic>::into)
$func(x, y).map(Into::into)
} };
(from $base:ty => $xx:ident $op:tt $yy:ident) => { |_, args| {
let x = <$base>::from(args[0].$xx().expect(BUILTIN));
@ -123,7 +112,7 @@ pub fn get_builtin_binary_op_fn(
(from $base:ty => $func:ident ( $xx:ident, $yy:ident )) => { |_, args| {
let x = <$base>::from(args[0].$xx().expect(BUILTIN));
let y = <$base>::from(args[1].$yy().expect(BUILTIN));
$func(x, y).map(Into::<Dynamic>::into)
$func(x, y).map(Into::into)
} };
}
@ -266,7 +255,7 @@ pub fn get_builtin_binary_op_fn(
OP_CONTAINS => Some(|_, args| {
let s = &*args[0].read_lock::<ImmutableString>().expect(BUILTIN);
let c = args[1].as_char().expect(BUILTIN);
Ok((s.contains(c)).into())
Ok(s.contains(c).into())
}),
_ => None,
};
@ -290,6 +279,30 @@ pub fn get_builtin_binary_op_fn(
};
}
// blob
#[cfg(not(feature = "no_index"))]
if type1 == TypeId::of::<crate::Blob>() {
use crate::Blob;
if type2 == TypeId::of::<INT>() {
return match op {
OP_CONTAINS => Some(|_, args| {
let blob = &*args[0].read_lock::<Blob>().expect(BUILTIN);
let x = (args[1].as_int().expect("`INT`") & 0x000000ff) as u8;
Ok(blob.contains(&x).into())
}),
_ => None,
};
}
if type1 == type2 {
return match op {
"==" => Some(impl_op!(Blob == Blob)),
"!=" => Some(impl_op!(Blob != Blob)),
_ => None,
};
}
}
// map op string
#[cfg(not(feature = "no_object"))]
if types_pair == (TypeId::of::<crate::Map>(), TypeId::of::<ImmutableString>()) {
@ -301,6 +314,84 @@ pub fn get_builtin_binary_op_fn(
};
}
// Non-compatible ranges
if types_pair
== (
TypeId::of::<ExclusiveRange>(),
TypeId::of::<InclusiveRange>(),
)
|| types_pair
== (
TypeId::of::<InclusiveRange>(),
TypeId::of::<ExclusiveRange>(),
)
{
return match op {
"!=" => Some(|_, _| Ok(Dynamic::TRUE)),
"==" => Some(|_, _| Ok(Dynamic::FALSE)),
_ => None,
};
}
// Handle ranges here because ranges are implemented as custom type
if type1 == TypeId::of::<ExclusiveRange>() {
if type2 == TypeId::of::<INT>() {
return match op {
OP_CONTAINS => Some(|_, args| {
let range = &*args[0].read_lock::<ExclusiveRange>().expect(BUILTIN);
let x = args[1].as_int().expect("`INT`");
Ok(range.contains(&x).into())
}),
_ => None,
};
}
if type1 == type2 {
return match op {
"==" => Some(impl_op!(ExclusiveRange == ExclusiveRange)),
"!=" => Some(impl_op!(ExclusiveRange != ExclusiveRange)),
_ => None,
};
}
}
if type1 == TypeId::of::<InclusiveRange>() {
if type2 == TypeId::of::<INT>() {
return match op {
OP_CONTAINS => Some(|_, args| {
let range = &*args[0].read_lock::<InclusiveRange>().expect(BUILTIN);
let x = args[1].as_int().expect("`INT`");
Ok(range.contains(&x).into())
}),
_ => None,
};
}
if type1 == type2 {
return match op {
"==" => Some(impl_op!(InclusiveRange == InclusiveRange)),
"!=" => Some(impl_op!(InclusiveRange != InclusiveRange)),
_ => None,
};
}
}
// One of the operands is a custom type, so it is never built-in
if x.is_variant() || y.is_variant() {
return if is_numeric(type1) && is_numeric(type2) {
// Disallow comparisons between different numeric types
None
} else if type1 != type2 {
// If the types are not the same, default to not compare
match op {
"!=" => Some(|_, _| Ok(Dynamic::TRUE)),
"==" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)),
_ => None,
}
} else {
// Disallow comparisons between the same type
None
};
}
// Default comparison operators for different types
if type2 != type1 {
return match op {
@ -391,11 +482,7 @@ pub fn get_builtin_binary_op_fn(
">=" => Some(impl_op!(ImmutableString >= ImmutableString)),
"<" => Some(impl_op!(ImmutableString < ImmutableString)),
"<=" => Some(impl_op!(ImmutableString <= ImmutableString)),
OP_CONTAINS => Some(|_, args| {
let s1 = &*args[0].read_lock::<ImmutableString>().expect(BUILTIN);
let s2 = &*args[1].read_lock::<ImmutableString>().expect(BUILTIN);
Ok((s1.contains(s2.as_str())).into())
}),
OP_CONTAINS => Some(impl_op!(ImmutableString.contains(ImmutableString.as_str()))),
_ => None,
};
}
@ -429,12 +516,10 @@ pub fn get_builtin_binary_op_fn(
}
/// Build in common operator assignment implementations to avoid the cost of calling a registered function.
///
/// The return function is registered as a _method_, so the first parameter cannot be consumed.
#[must_use]
pub fn get_builtin_op_assignment_fn(
op: &str,
x: &Dynamic,
y: &Dynamic,
) -> Option<fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult> {
pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option<FnBuiltin> {
let type1 = x.type_id();
let type2 = y.type_id();
@ -571,6 +656,48 @@ pub fn get_builtin_op_assignment_fn(
};
}
// array op= any
#[cfg(not(feature = "no_index"))]
if type1 == TypeId::of::<crate::Array>() {
use crate::packages::array_basic::array_functions::*;
use crate::Array;
if type2 == TypeId::of::<crate::Array>() {
return match op {
"+=" => Some(|_, args| {
let array2 = std::mem::take(args[1]).cast::<Array>();
let array1 = &mut *args[0].write_lock::<Array>().expect(BUILTIN);
Ok(append(array1, array2).into())
}),
_ => None,
};
} else {
return match op {
"+=" => Some(|_, args| {
let x = std::mem::take(args[1]);
let array = &mut *args[0].write_lock::<Array>().expect(BUILTIN);
Ok(push(array, x).into())
}),
_ => None,
};
}
}
// blob op= int
#[cfg(not(feature = "no_index"))]
if types_pair == (TypeId::of::<crate::Blob>(), TypeId::of::<INT>()) {
use crate::Blob;
return match op {
"+=" => Some(|_, args| {
let x = (args[1].as_int().expect("`INT`") & 0x000000ff) as u8;
let mut blob = args[0].write_lock::<Blob>().expect(BUILTIN);
Ok(blob.push(x).into())
}),
_ => None,
};
}
// No built-in op-assignments for different types.
if type2 != type1 {
return None;
@ -652,5 +779,20 @@ pub fn get_builtin_op_assignment_fn(
};
}
#[cfg(not(feature = "no_index"))]
if type1 == TypeId::of::<crate::Blob>() {
use crate::packages::blob_basic::blob_functions::*;
use crate::Blob;
return match op {
"+=" => Some(|_, args| {
let blob2 = std::mem::take(args[1]).cast::<Blob>();
let blob1 = &mut *args[0].write_lock::<Blob>().expect(BUILTIN);
Ok(append(blob1, blob2).into())
}),
_ => None,
};
}
None
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,256 @@
//! Module defining the standard Rhai function type.
use super::native::{FnAny, FnPlugin, IteratorFn, SendSync};
use crate::ast::FnAccess;
use crate::plugin::PluginFunction;
use crate::Shared;
use std::fmt;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
/// A type encapsulating a function callable by Rhai.
#[derive(Clone)]
pub enum CallableFunction {
/// A pure native Rust function with all arguments passed by value.
Pure(Shared<FnAny>),
/// A native Rust object method with the first argument passed by reference,
/// and the rest passed by value.
Method(Shared<FnAny>),
/// An iterator function.
Iterator(IteratorFn),
/// A plugin function,
Plugin(Shared<FnPlugin>),
/// A script-defined function.
///
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
Script(Shared<crate::ast::ScriptFnDef>),
}
impl fmt::Debug for CallableFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Pure(_) => write!(f, "NativePureFunction"),
Self::Method(_) => write!(f, "NativeMethod"),
Self::Iterator(_) => write!(f, "NativeIterator"),
Self::Plugin(_) => write!(f, "PluginFunction"),
#[cfg(not(feature = "no_function"))]
Self::Script(fn_def) => fmt::Debug::fmt(fn_def, f),
}
}
}
impl fmt::Display for CallableFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Pure(_) => write!(f, "NativePureFunction"),
Self::Method(_) => write!(f, "NativeMethod"),
Self::Iterator(_) => write!(f, "NativeIterator"),
Self::Plugin(_) => write!(f, "PluginFunction"),
#[cfg(not(feature = "no_function"))]
Self::Script(s) => fmt::Display::fmt(s, f),
}
}
}
impl CallableFunction {
/// Is this a pure native Rust function?
#[inline]
#[must_use]
pub fn is_pure(&self) -> bool {
match self {
Self::Pure(_) => true,
Self::Method(_) | Self::Iterator(_) => false,
Self::Plugin(p) => !p.is_method_call(),
#[cfg(not(feature = "no_function"))]
Self::Script(_) => false,
}
}
/// Is this a native Rust method function?
#[inline]
#[must_use]
pub fn is_method(&self) -> bool {
match self {
Self::Method(_) => true,
Self::Pure(_) | Self::Iterator(_) => false,
Self::Plugin(p) => p.is_method_call(),
#[cfg(not(feature = "no_function"))]
Self::Script(_) => false,
}
}
/// Is this an iterator function?
#[inline]
#[must_use]
pub const fn is_iter(&self) -> bool {
match self {
Self::Iterator(_) => true,
Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => false,
#[cfg(not(feature = "no_function"))]
Self::Script(_) => false,
}
}
/// Is this a script-defined function?
#[inline]
#[must_use]
pub const fn is_script(&self) -> bool {
#[cfg(feature = "no_function")]
return false;
#[cfg(not(feature = "no_function"))]
match self {
Self::Script(_) => true,
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => false,
}
}
/// Is this a plugin function?
#[inline]
#[must_use]
pub const fn is_plugin_fn(&self) -> bool {
match self {
Self::Plugin(_) => true,
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false,
#[cfg(not(feature = "no_function"))]
Self::Script(_) => false,
}
}
/// Is this a native Rust function?
#[inline]
#[must_use]
pub const fn is_native(&self) -> bool {
#[cfg(feature = "no_function")]
return true;
#[cfg(not(feature = "no_function"))]
match self {
Self::Pure(_) | Self::Method(_) => true,
Self::Plugin(_) => true,
Self::Iterator(_) => true,
Self::Script(_) => false,
}
}
/// Get the access mode.
#[inline]
#[must_use]
pub fn access(&self) -> FnAccess {
#[cfg(feature = "no_function")]
return FnAccess::Public;
#[cfg(not(feature = "no_function"))]
match self {
Self::Plugin(_) => FnAccess::Public,
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => FnAccess::Public,
Self::Script(f) => f.access,
}
}
/// Get a shared reference to a native Rust function.
#[inline]
#[must_use]
pub fn get_native_fn(&self) -> Option<&Shared<FnAny>> {
match self {
Self::Pure(f) | Self::Method(f) => Some(f),
Self::Iterator(_) | Self::Plugin(_) => None,
#[cfg(not(feature = "no_function"))]
Self::Script(_) => None,
}
}
/// Get a shared reference to a script-defined function definition.
///
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
#[inline]
#[must_use]
pub const fn get_script_fn_def(&self) -> Option<&Shared<crate::ast::ScriptFnDef>> {
match self {
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => None,
Self::Script(f) => Some(f),
}
}
/// Get a reference to an iterator function.
#[inline]
#[must_use]
pub fn get_iter_fn(&self) -> Option<IteratorFn> {
match self {
Self::Iterator(f) => Some(*f),
Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => None,
#[cfg(not(feature = "no_function"))]
Self::Script(_) => None,
}
}
/// Get a shared reference to a plugin function.
#[inline]
#[must_use]
pub fn get_plugin_fn(&self) -> Option<&Shared<FnPlugin>> {
match self {
Self::Plugin(f) => Some(f),
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => None,
#[cfg(not(feature = "no_function"))]
Self::Script(_) => None,
}
}
/// Create a new [`CallableFunction::Pure`].
#[inline(always)]
#[must_use]
pub fn from_pure(func: Box<FnAny>) -> Self {
Self::Pure(func.into())
}
/// Create a new [`CallableFunction::Method`].
#[inline(always)]
#[must_use]
pub fn from_method(func: Box<FnAny>) -> Self {
Self::Method(func.into())
}
/// Create a new [`CallableFunction::Plugin`].
#[inline(always)]
#[must_use]
pub fn from_plugin(func: impl PluginFunction + 'static + SendSync) -> Self {
Self::Plugin((Box::new(func) as Box<FnPlugin>).into())
}
}
impl From<IteratorFn> for CallableFunction {
#[inline(always)]
fn from(func: IteratorFn) -> Self {
Self::Iterator(func)
}
}
#[cfg(not(feature = "no_function"))]
impl From<crate::ast::ScriptFnDef> for CallableFunction {
#[inline(always)]
fn from(_func: crate::ast::ScriptFnDef) -> Self {
Self::Script(_func.into())
}
}
#[cfg(not(feature = "no_function"))]
impl From<Shared<crate::ast::ScriptFnDef>> for CallableFunction {
#[inline(always)]
fn from(_func: Shared<crate::ast::ScriptFnDef>) -> Self {
Self::Script(_func)
}
}
impl<T: PluginFunction + 'static + SendSync> From<T> for CallableFunction {
#[inline(always)]
fn from(func: T) -> Self {
Self::from_plugin(func)
}
}
impl From<Shared<FnPlugin>> for CallableFunction {
#[inline(always)]
fn from(func: Shared<FnPlugin>) -> Self {
Self::Plugin(func)
}
}

View File

@ -3,8 +3,9 @@
#![cfg(not(feature = "no_function"))]
#![allow(non_snake_case)]
use crate::parser::ParseResult;
use crate::types::dynamic::Variant;
use crate::{Engine, EvalAltResult, ParseError, Scope, SmartString, AST};
use crate::{Engine, RhaiResultOf, Scope, SmartString, AST};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -77,11 +78,7 @@ pub trait Func<ARGS, RET> {
/// # Ok(())
/// # }
/// ```
fn create_from_script(
self,
script: &str,
entry_point: &str,
) -> Result<Self::Output, ParseError>;
fn create_from_script(self, script: &str, entry_point: &str) -> ParseResult<Self::Output>;
}
macro_rules! def_anonymous_fn {
@ -92,9 +89,9 @@ macro_rules! def_anonymous_fn {
impl<$($par: Variant + Clone,)* RET: Variant + Clone> Func<($($par,)*), RET> for Engine
{
#[cfg(feature = "sync")]
type Output = Box<dyn Fn($($par),*) -> Result<RET, Box<EvalAltResult>> + Send + Sync>;
type Output = Box<dyn Fn($($par),*) -> RhaiResultOf<RET> + Send + Sync>;
#[cfg(not(feature = "sync"))]
type Output = Box<dyn Fn($($par),*) -> Result<RET, Box<EvalAltResult>>>;
type Output = Box<dyn Fn($($par),*) -> RhaiResultOf<RET>>;
#[inline]
fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output {
@ -103,7 +100,7 @@ macro_rules! def_anonymous_fn {
}
#[inline]
fn create_from_script(self, script: &str, entry_point: &str) -> Result<Self::Output, ParseError> {
fn create_from_script(self, script: &str, entry_point: &str) -> ParseResult<Self::Output> {
let ast = self.compile(script)?;
Ok(Func::<($($par,)*), RET>::create_from_ast(self, ast, entry_point))
}

View File

@ -87,10 +87,7 @@ pub fn get_hasher() -> ahash::AHasher {
/// The first module name is skipped. Hashing starts from the _second_ module in the chain.
#[inline]
#[must_use]
pub fn calc_qualified_var_hash<'a>(
modules: impl Iterator<Item = impl AsRef<str> + 'a>,
var_name: impl AsRef<str>,
) -> u64 {
pub fn calc_qualified_var_hash<'a>(modules: impl Iterator<Item = &'a str>, var_name: &str) -> u64 {
let s = &mut get_hasher();
// We always skip the first module
@ -98,9 +95,9 @@ pub fn calc_qualified_var_hash<'a>(
modules
.inspect(|_| len += 1)
.skip(1)
.for_each(|m| m.as_ref().hash(s));
.for_each(|m| m.hash(s));
len.hash(s);
var_name.as_ref().hash(s);
var_name.hash(s);
match s.finish() {
0 => ALT_ZERO_HASH,
@ -123,9 +120,9 @@ pub fn calc_qualified_var_hash<'a>(
/// The first module name is skipped. Hashing starts from the _second_ module in the chain.
#[inline]
#[must_use]
pub fn calc_qualified_fn_hash(
modules: impl Iterator<Item = impl AsRef<str>>,
fn_name: impl AsRef<str>,
pub fn calc_qualified_fn_hash<'a>(
modules: impl Iterator<Item = &'a str>,
fn_name: &str,
num: usize,
) -> u64 {
let s = &mut get_hasher();
@ -135,9 +132,9 @@ pub fn calc_qualified_fn_hash(
modules
.inspect(|_| len += 1)
.skip(1)
.for_each(|m| m.as_ref().hash(s));
.for_each(|m| m.hash(s));
len.hash(s);
fn_name.as_ref().hash(s);
fn_name.hash(s);
num.hash(s);
match s.finish() {
@ -156,8 +153,8 @@ pub fn calc_qualified_fn_hash(
/// If the hash happens to be zero, it is mapped to `DEFAULT_HASH`.
#[inline(always)]
#[must_use]
pub fn calc_fn_hash(fn_name: impl AsRef<str>, num: usize) -> u64 {
calc_qualified_fn_hash(empty::<&str>(), fn_name, num)
pub fn calc_fn_hash(fn_name: &str, num: usize) -> u64 {
calc_qualified_fn_hash(empty(), fn_name, num)
}
/// Calculate a non-zero [`u64`] hash key from a list of parameter types.

View File

@ -3,6 +3,7 @@
pub mod args;
pub mod builtin;
pub mod call;
pub mod callable_function;
pub mod func;
pub mod hashing;
pub mod native;
@ -13,6 +14,7 @@ pub mod script;
pub use args::FuncArgs;
pub use builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn};
pub use call::FnCallArgs;
pub use callable_function::CallableFunction;
#[cfg(not(feature = "no_function"))]
pub use func::Func;
pub use hashing::{
@ -20,8 +22,8 @@ pub use hashing::{
combine_hashes, get_hasher,
};
pub use native::{
shared_make_mut, shared_take, shared_take_or_clone, shared_try_take, shared_write_lock,
CallableFunction, FnAny, FnPlugin, IteratorFn, Locked, NativeCallContext, SendSync, Shared,
shared_make_mut, shared_take, shared_take_or_clone, shared_try_take, shared_write_lock, FnAny,
FnPlugin, IteratorFn, Locked, NativeCallContext, SendSync, Shared,
};
pub use plugin::PluginFunction;
pub use register::RegisterNativeFunction;

View File

@ -1,14 +1,16 @@
//! Module defining interfaces to native-Rust functions.
use super::call::FnCallArgs;
use crate::ast::{FnAccess, FnCallHashes};
use crate::engine::{EvalState, Imports};
use crate::ast::FnCallHashes;
use crate::engine::{EvalState, GlobalRuntimeState};
use crate::plugin::PluginFunction;
use crate::tokenizer::{Token, TokenizeState};
use crate::types::dynamic::Variant;
use crate::{
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Module, Position, RhaiResult,
calc_fn_hash, Dynamic, Engine, EvalContext, FuncArgs, Module, Position, RhaiResult,
RhaiResultOf, StaticVec, ERR,
};
use std::fmt;
use std::any::type_name;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -56,11 +58,17 @@ pub type LockGuard<'a, T> = std::sync::RwLockWriteGuard<'a, T>;
/// Context of a native Rust function call.
#[derive(Debug)]
pub struct NativeCallContext<'a> {
/// The current [`Engine`].
engine: &'a Engine,
/// Name of function called.
fn_name: &'a str,
/// Function source, if any.
source: Option<&'a str>,
mods: Option<&'a Imports>,
/// The current [`GlobalRuntimeState`], if any.
global: Option<&'a GlobalRuntimeState>,
/// The current stack of loaded [modules][Module].
lib: &'a [&'a Module],
/// [Position] of the function call.
pos: Position,
}
@ -69,7 +77,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
&'a Engine,
&'a S,
Option<&'a S>,
&'a Imports,
&'a GlobalRuntimeState,
&'a M,
Position,
)> for NativeCallContext<'a>
@ -80,7 +88,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
&'a Engine,
&'a S,
Option<&'a S>,
&'a Imports,
&'a GlobalRuntimeState,
&'a M,
Position,
),
@ -88,8 +96,8 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
Self {
engine: value.0,
fn_name: value.1.as_ref(),
source: value.2.map(|v| v.as_ref()),
mods: Some(value.3),
source: value.2.map(S::as_ref),
global: Some(value.3),
lib: value.4.as_ref(),
pos: value.5,
}
@ -105,7 +113,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
engine: value.0,
fn_name: value.1.as_ref(),
source: None,
mods: None,
global: None,
lib: value.2.as_ref(),
pos: Position::NONE,
}
@ -130,7 +138,7 @@ impl<'a> NativeCallContext<'a> {
engine,
fn_name: fn_name.as_ref(),
source: None,
mods: None,
global: None,
lib,
pos: Position::NONE,
}
@ -147,15 +155,15 @@ impl<'a> NativeCallContext<'a> {
engine: &'a Engine,
fn_name: &'a (impl AsRef<str> + 'a + ?Sized),
source: Option<&'a (impl AsRef<str> + 'a + ?Sized)>,
imports: &'a Imports,
global: &'a GlobalRuntimeState,
lib: &'a [&Module],
pos: Position,
) -> Self {
Self {
engine,
fn_name: fn_name.as_ref(),
source: source.map(|v| v.as_ref()),
mods: Some(imports),
source: source.map(<_>::as_ref),
global: Some(global),
lib,
pos,
}
@ -172,7 +180,7 @@ impl<'a> NativeCallContext<'a> {
pub const fn fn_name(&self) -> &str {
self.fn_name
}
/// [Position][`Position`] of the function call.
/// [Position] of the function call.
#[inline(always)]
#[must_use]
pub const fn position(&self) -> Position {
@ -184,24 +192,25 @@ impl<'a> NativeCallContext<'a> {
pub const fn source(&self) -> Option<&str> {
self.source
}
/// Get an iterator over the current set of modules imported via `import` statements.
/// Get an iterator over the current set of modules imported via `import` statements
/// in reverse order.
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))]
#[inline]
pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &Module)> {
self.mods.iter().flat_map(|&m| m.iter())
self.global.iter().flat_map(|&m| m.iter_modules())
}
/// Get an iterator over the current set of modules imported via `import` statements.
/// Get an iterator over the current set of modules imported via `import` statements in reverse order.
#[cfg(not(feature = "no_module"))]
#[allow(dead_code)]
#[inline]
pub(crate) fn iter_imports_raw(
&self,
) -> impl Iterator<Item = (&crate::Identifier, &Shared<Module>)> {
self.mods.iter().flat_map(|&m| m.iter_raw())
self.global.iter().flat_map(|&m| m.iter_modules_raw())
}
/// _(internals)_ The current set of modules imported via `import` statements.
/// _(internals)_ The current [`GlobalRuntimeState`], if any.
/// Exported under the `internals` feature only.
///
/// Not available under `no_module`.
@ -209,13 +218,14 @@ impl<'a> NativeCallContext<'a> {
#[cfg(not(feature = "no_module"))]
#[inline(always)]
#[must_use]
pub const fn imports(&self) -> Option<&Imports> {
self.mods
pub const fn global_runtime_state(&self) -> Option<&GlobalRuntimeState> {
self.global
}
/// Get an iterator over the namespaces containing definitions of all script-defined functions.
/// Get an iterator over the namespaces containing definitions of all script-defined functions
/// in reverse order.
#[inline]
pub fn iter_namespaces(&self) -> impl Iterator<Item = &Module> {
self.lib.iter().cloned()
self.lib.iter().rev().cloned()
}
/// _(internals)_ The current set of namespaces containing definitions of all script-defined functions.
/// Exported under the `internals` feature only.
@ -225,6 +235,31 @@ impl<'a> NativeCallContext<'a> {
pub const fn namespaces(&self) -> &[&Module] {
self.lib
}
/// Call a function inside the call context with the provided arguments.
#[inline]
pub fn call_fn<T: Variant + Clone>(
&self,
fn_name: impl AsRef<str>,
args: impl FuncArgs,
) -> RhaiResultOf<T> {
let mut arg_values = StaticVec::new_const();
args.parse(&mut arg_values);
let mut args: StaticVec<_> = arg_values.iter_mut().collect();
let result = self.call_fn_raw(fn_name, false, false, &mut args)?;
let typ = self.engine().map_type_name(result.type_name());
result.try_cast().ok_or_else(|| {
ERR::ErrorMismatchOutputType(
self.engine().map_type_name(type_name::<T>()).into(),
typ.into(),
Position::NONE,
)
.into()
})
}
/// Call a function inside the call context.
///
/// If `is_method_call` is [`true`], the first argument is assumed to be the `this` pointer for
@ -250,22 +285,26 @@ impl<'a> NativeCallContext<'a> {
is_ref_mut: bool,
is_method_call: bool,
args: &mut [&mut Dynamic],
) -> Result<Dynamic, Box<EvalAltResult>> {
) -> RhaiResult {
let fn_name = fn_name.as_ref();
let len = args.len();
let hash = if is_method_call {
FnCallHashes::from_all(
#[cfg(not(feature = "no_function"))]
calc_fn_hash(fn_name, args.len() - 1),
calc_fn_hash(fn_name, args.len()),
calc_fn_hash(fn_name, len - 1),
calc_fn_hash(fn_name, len),
)
} else {
calc_fn_hash(fn_name, args.len()).into()
calc_fn_hash(fn_name, len).into()
};
self.engine()
.exec_fn_call(
&mut self.mods.cloned().unwrap_or_else(|| Imports::new()),
&mut self
.global
.cloned()
.unwrap_or_else(|| GlobalRuntimeState::new()),
&mut EvalState::new(),
self.lib,
fn_name,
@ -336,6 +375,9 @@ pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult;
#[cfg(feature = "sync")]
pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult + Send + Sync;
/// A trail object for built-in functions.
pub type FnBuiltin = fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult;
/// A standard function that gets an iterator from a type.
pub type IteratorFn = fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
@ -378,253 +420,8 @@ pub type OnParseTokenCallback =
/// A standard callback function for variable access.
#[cfg(not(feature = "sync"))]
pub type OnVarCallback =
Box<dyn Fn(&str, usize, &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>> + 'static>;
Box<dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf<Option<Dynamic>> + 'static>;
/// A standard callback function for variable access.
#[cfg(feature = "sync")]
pub type OnVarCallback = Box<
dyn Fn(&str, usize, &EvalContext) -> Result<Option<Dynamic>, Box<EvalAltResult>>
+ Send
+ Sync
+ 'static,
>;
/// A type encapsulating a function callable by Rhai.
#[derive(Clone)]
pub enum CallableFunction {
/// A pure native Rust function with all arguments passed by value.
Pure(Shared<FnAny>),
/// A native Rust object method with the first argument passed by reference,
/// and the rest passed by value.
Method(Shared<FnAny>),
/// An iterator function.
Iterator(IteratorFn),
/// A plugin function,
Plugin(Shared<FnPlugin>),
/// A script-defined function.
///
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
Script(Shared<crate::ast::ScriptFnDef>),
}
impl fmt::Debug for CallableFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Pure(_) => write!(f, "NativePureFunction"),
Self::Method(_) => write!(f, "NativeMethod"),
Self::Iterator(_) => write!(f, "NativeIterator"),
Self::Plugin(_) => write!(f, "PluginFunction"),
#[cfg(not(feature = "no_function"))]
Self::Script(fn_def) => fmt::Debug::fmt(fn_def, f),
}
}
}
impl fmt::Display for CallableFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Pure(_) => write!(f, "NativePureFunction"),
Self::Method(_) => write!(f, "NativeMethod"),
Self::Iterator(_) => write!(f, "NativeIterator"),
Self::Plugin(_) => write!(f, "PluginFunction"),
#[cfg(not(feature = "no_function"))]
CallableFunction::Script(s) => fmt::Display::fmt(s, f),
}
}
}
impl CallableFunction {
/// Is this a pure native Rust function?
#[inline]
#[must_use]
pub fn is_pure(&self) -> bool {
match self {
Self::Pure(_) => true,
Self::Method(_) | Self::Iterator(_) => false,
Self::Plugin(p) => !p.is_method_call(),
#[cfg(not(feature = "no_function"))]
Self::Script(_) => false,
}
}
/// Is this a native Rust method function?
#[inline]
#[must_use]
pub fn is_method(&self) -> bool {
match self {
Self::Method(_) => true,
Self::Pure(_) | Self::Iterator(_) => false,
Self::Plugin(p) => p.is_method_call(),
#[cfg(not(feature = "no_function"))]
Self::Script(_) => false,
}
}
/// Is this an iterator function?
#[inline]
#[must_use]
pub const fn is_iter(&self) -> bool {
match self {
Self::Iterator(_) => true,
Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => false,
#[cfg(not(feature = "no_function"))]
Self::Script(_) => false,
}
}
/// Is this a Rhai-scripted function?
#[inline]
#[must_use]
pub const fn is_script(&self) -> bool {
match self {
#[cfg(not(feature = "no_function"))]
Self::Script(_) => true,
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => false,
}
}
/// Is this a plugin function?
#[inline]
#[must_use]
pub const fn is_plugin_fn(&self) -> bool {
match self {
Self::Plugin(_) => true,
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false,
#[cfg(not(feature = "no_function"))]
Self::Script(_) => false,
}
}
/// Is this a native Rust function?
#[inline]
#[must_use]
pub const fn is_native(&self) -> bool {
match self {
Self::Pure(_) | Self::Method(_) => true,
Self::Plugin(_) => true,
Self::Iterator(_) => true,
#[cfg(not(feature = "no_function"))]
Self::Script(_) => false,
}
}
/// Get the access mode.
#[inline]
#[must_use]
pub fn access(&self) -> FnAccess {
match self {
Self::Plugin(_) => FnAccess::Public,
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => FnAccess::Public,
#[cfg(not(feature = "no_function"))]
Self::Script(f) => f.access,
}
}
/// Get a shared reference to a native Rust function.
#[inline]
#[must_use]
pub fn get_native_fn(&self) -> Option<&Shared<FnAny>> {
match self {
Self::Pure(f) | Self::Method(f) => Some(f),
Self::Iterator(_) | Self::Plugin(_) => None,
#[cfg(not(feature = "no_function"))]
Self::Script(_) => None,
}
}
/// Get a shared reference to a script-defined function definition.
///
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
#[inline]
#[must_use]
pub const fn get_script_fn_def(&self) -> Option<&Shared<crate::ast::ScriptFnDef>> {
match self {
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => None,
Self::Script(f) => Some(f),
}
}
/// Get a reference to an iterator function.
#[inline]
#[must_use]
pub fn get_iter_fn(&self) -> Option<IteratorFn> {
match self {
Self::Iterator(f) => Some(*f),
Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => None,
#[cfg(not(feature = "no_function"))]
Self::Script(_) => None,
}
}
/// Get a shared reference to a plugin function.
#[inline]
#[must_use]
pub fn get_plugin_fn(&self) -> Option<&Shared<FnPlugin>> {
match self {
Self::Plugin(f) => Some(f),
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => None,
#[cfg(not(feature = "no_function"))]
Self::Script(_) => None,
}
}
/// Create a new [`CallableFunction::Pure`].
#[inline(always)]
#[must_use]
pub fn from_pure(func: Box<FnAny>) -> Self {
Self::Pure(func.into())
}
/// Create a new [`CallableFunction::Method`].
#[inline(always)]
#[must_use]
pub fn from_method(func: Box<FnAny>) -> Self {
Self::Method(func.into())
}
/// Create a new [`CallableFunction::Plugin`].
#[inline(always)]
#[must_use]
pub fn from_plugin(func: impl PluginFunction + 'static + SendSync) -> Self {
Self::Plugin((Box::new(func) as Box<FnPlugin>).into())
}
}
impl From<IteratorFn> for CallableFunction {
#[inline(always)]
fn from(func: IteratorFn) -> Self {
Self::Iterator(func)
}
}
#[cfg(not(feature = "no_function"))]
impl From<crate::ast::ScriptFnDef> for CallableFunction {
#[inline(always)]
fn from(_func: crate::ast::ScriptFnDef) -> Self {
Self::Script(_func.into())
}
}
#[cfg(not(feature = "no_function"))]
impl From<Shared<crate::ast::ScriptFnDef>> for CallableFunction {
#[inline(always)]
fn from(_func: Shared<crate::ast::ScriptFnDef>) -> Self {
Self::Script(_func)
}
}
impl<T: PluginFunction + 'static + SendSync> From<T> for CallableFunction {
#[inline(always)]
fn from(func: T) -> Self {
Self::from_plugin(func)
}
}
impl From<Shared<FnPlugin>> for CallableFunction {
#[inline(always)]
fn from(func: Shared<FnPlugin>) -> Self {
Self::Plugin(func)
}
}
pub type OnVarCallback =
Box<dyn Fn(&str, usize, &EvalContext) -> RhaiResultOf<Option<Dynamic>> + Send + Sync + 'static>;

View File

@ -10,7 +10,7 @@ pub use crate::{
use std::prelude::v1::*;
pub use std::{any::TypeId, mem};
pub type RhaiResult = Result<Dynamic, Box<EvalAltResult>>;
pub type RhaiResult = crate::RhaiResult;
#[cfg(not(features = "no_module"))]
pub use rhai_codegen::*;

View File

@ -2,12 +2,13 @@
#![allow(non_snake_case)]
use crate::func::call::FnCallArgs;
use crate::func::native::{CallableFunction, FnAny, SendSync};
use crate::r#unsafe::unsafe_try_cast;
use super::call::FnCallArgs;
use super::callable_function::CallableFunction;
use super::native::{FnAny, SendSync};
use crate::r#unsafe::unsafe_cast;
use crate::tokenizer::Position;
use crate::types::dynamic::{DynamicWriteLock, Variant};
use crate::{Dynamic, EvalAltResult, NativeCallContext};
use crate::{Dynamic, NativeCallContext, RhaiResultOf, ERR};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{any::TypeId, mem};
@ -49,8 +50,7 @@ pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
ref_t.clone()
} else if TypeId::of::<T>() == TypeId::of::<String>() {
// If T is `String`, data must be `ImmutableString`, so map directly to it
let value = mem::take(data).into_string().expect("`ImmutableString`");
unsafe_try_cast(value).expect("checked")
unsafe_cast(mem::take(data).into_string().expect("`ImmutableString`"))
} else {
// We consume the argument and then replace it with () - the argument is not supposed to be used again.
// This way, we avoid having to clone the argument again, because it is already a clone when passed here.
@ -123,7 +123,7 @@ macro_rules! def_register {
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
return Err(EvalAltResult::ErrorAssignmentToConstant(String::new(), Position::NONE).into());
return Err(ERR::ErrorAssignmentToConstant(String::new(), Position::NONE).into());
}
// The arguments are assumed to be of the correct number and types!
@ -134,7 +134,7 @@ macro_rules! def_register {
let r = self($($arg),*);
// Map the result
Ok(r.into_dynamic())
Ok(Dynamic::from(r))
}) as Box<FnAny>)
}
}
@ -151,7 +151,7 @@ macro_rules! def_register {
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
return Err(EvalAltResult::ErrorAssignmentToConstant(String::new(), Position::NONE).into());
return Err(ERR::ErrorAssignmentToConstant(String::new(), Position::NONE).into());
}
// The arguments are assumed to be of the correct number and types!
@ -162,24 +162,24 @@ macro_rules! def_register {
let r = self(ctx, $($arg),*);
// Map the result
Ok(r.into_dynamic())
Ok(Dynamic::from(r))
}) as Box<FnAny>)
}
}
impl<
FN: Fn($($param),*) -> Result<RET, Box<EvalAltResult>> + SendSync + 'static,
FN: Fn($($param),*) -> RhaiResultOf<RET> + SendSync + 'static,
$($par: Variant + Clone,)*
RET: Variant + Clone
> RegisterNativeFunction<($($mark,)*), Result<RET, Box<EvalAltResult>>> for FN {
> RegisterNativeFunction<($($mark,)*), RhaiResultOf<RET>> for FN {
#[inline(always)] fn param_types() -> Box<[TypeId]> { vec![$(TypeId::of::<$par>()),*].into_boxed_slice() }
#[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> Box<[&'static str]> { vec![$(std::any::type_name::<$par>()),*].into_boxed_slice() }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<Result<RET, Box<EvalAltResult>>>() }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<Result<RET, Box<EvalAltResult>>>() }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<RhaiResultOf<RET>>() }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RhaiResultOf<RET>>() }
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
return Err(EvalAltResult::ErrorAssignmentToConstant(String::new(), Position::NONE).into());
return Err(ERR::ErrorAssignmentToConstant(String::new(), Position::NONE).into());
}
// The arguments are assumed to be of the correct number and types!
@ -193,18 +193,18 @@ macro_rules! def_register {
}
impl<
FN: for<'a> Fn(NativeCallContext<'a>, $($param),*) -> Result<RET, Box<EvalAltResult>> + SendSync + 'static,
FN: for<'a> Fn(NativeCallContext<'a>, $($param),*) -> RhaiResultOf<RET> + SendSync + 'static,
$($par: Variant + Clone,)*
RET: Variant + Clone
> RegisterNativeFunction<(NativeCallContext<'static>, $($mark,)*), Result<RET, Box<EvalAltResult>>> for FN {
> RegisterNativeFunction<(NativeCallContext<'static>, $($mark,)*), RhaiResultOf<RET>> for FN {
#[inline(always)] fn param_types() -> Box<[TypeId]> { vec![$(TypeId::of::<$par>()),*].into_boxed_slice() }
#[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> Box<[&'static str]> { vec![$(std::any::type_name::<$par>()),*].into_boxed_slice() }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<Result<RET, Box<EvalAltResult>>>() }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<Result<RET, Box<EvalAltResult>>>() }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<RhaiResultOf<RET>>() }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RhaiResultOf<RET>>() }
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
return Err(EvalAltResult::ErrorAssignmentToConstant(String::new(), Position::NONE).into());
return Err(ERR::ErrorAssignmentToConstant(String::new(), Position::NONE).into());
}
// The arguments are assumed to be of the correct number and types!

View File

@ -1,11 +1,11 @@
//! Implement script function-calling mechanism for [`Engine`].
#![cfg(not(feature = "no_function"))]
use super::call::FnCallArgs;
use crate::ast::ScriptFnDef;
use crate::engine::{EvalState, Imports};
use crate::func::call::FnCallArgs;
use crate::engine::{EvalState, GlobalRuntimeState};
use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
use crate::{Dynamic, Engine, EvalAltResult, Module, Position, RhaiResult, Scope, StaticVec};
use crate::{Dynamic, Engine, Module, Position, RhaiError, RhaiResult, Scope, StaticVec, ERR};
use std::mem;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -24,7 +24,7 @@ impl Engine {
pub(crate) fn call_script_fn(
&self,
scope: &mut Scope,
mods: &mut Imports,
global: &mut GlobalRuntimeState,
state: &mut EvalState,
lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>,
@ -38,18 +38,17 @@ impl Engine {
fn make_error(
name: String,
fn_def: &ScriptFnDef,
mods: &Imports,
err: Box<EvalAltResult>,
global: &GlobalRuntimeState,
err: RhaiError,
pos: Position,
) -> RhaiResult {
Err(EvalAltResult::ErrorInFunctionCall(
Err(ERR::ErrorInFunctionCall(
name,
fn_def
.lib
.as_ref()
.and_then(|m| m.id().map(|id| id.to_string()))
.or_else(|| mods.source.as_ref().map(|s| s.to_string()))
.unwrap_or_default(),
.and_then(|m| m.id().map(str::to_string))
.unwrap_or_else(|| global.source.to_string()),
err,
pos,
)
@ -59,7 +58,7 @@ impl Engine {
assert!(fn_def.params.len() == args.len());
#[cfg(not(feature = "unchecked"))]
self.inc_operations(&mut mods.num_operations, pos)?;
self.inc_operations(&mut global.num_operations, pos)?;
if fn_def.body.is_empty() {
return Ok(Dynamic::UNIT);
@ -68,11 +67,11 @@ impl Engine {
// Check for stack overflow
#[cfg(not(feature = "unchecked"))]
if level > self.max_call_levels() {
return Err(EvalAltResult::ErrorStackOverflow(pos).into());
return Err(ERR::ErrorStackOverflow(pos).into());
}
let orig_scope_len = scope.len();
let orig_mods_len = mods.len();
let orig_mods_len = global.num_imported_modules();
// Put arguments into scope as variables
// Actually consume the arguments instead of cloning them
@ -106,39 +105,37 @@ impl Engine {
};
#[cfg(not(feature = "no_module"))]
if !fn_def.mods.is_empty() {
fn_def
.mods
.iter_raw()
.for_each(|(n, m)| mods.push(n.clone(), m.clone()));
if let Some(ref modules) = fn_def.global {
modules
.iter()
.cloned()
.for_each(|(n, m)| global.push_module(n, m));
}
// Evaluate the function
let body = &fn_def.body;
let result = self
.eval_stmt_block(
scope,
mods,
global,
state,
lib,
this_ptr,
body,
true,
&fn_def.body,
rewind_scope,
level,
)
.or_else(|err| match *err {
// Convert return statement to return value
EvalAltResult::Return(x, _) => Ok(x),
ERR::Return(x, _) => Ok(x),
// Error in sub function call
EvalAltResult::ErrorInFunctionCall(name, src, err, _) => {
ERR::ErrorInFunctionCall(name, src, err, _) => {
let fn_name = if src.is_empty() {
format!("{} < {}", name, fn_def.name)
} else {
format!("{} @ '{}' < {}", name, src, fn_def.name)
};
make_error(fn_name, fn_def, mods, err, pos)
make_error(fn_name, fn_def, global, err, pos)
}
// System errors are passed straight-through
mut err if err.is_system_exception() => {
@ -146,28 +143,29 @@ impl Engine {
Err(err.into())
}
// Other errors are wrapped in `ErrorInFunctionCall`
_ => make_error(fn_def.name.to_string(), fn_def, mods, err, pos),
_ => make_error(fn_def.name.to_string(), fn_def, global, err, pos),
});
// Remove all local variables
// Remove all local variables and imported modules
if rewind_scope {
scope.rewind(orig_scope_len);
} else if !args.is_empty() {
// Remove arguments only, leaving new variables in the scope
scope.remove_range(orig_scope_len, args.len())
}
global.truncate_modules(orig_mods_len);
mods.truncate(orig_mods_len);
// Restore state
state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len);
result
}
// Does a scripted function exist?
// Does a script-defined function exist?
#[must_use]
pub(crate) fn has_script_fn(
&self,
mods: Option<&Imports>,
global: Option<&GlobalRuntimeState>,
state: &mut EvalState,
lib: &[&Module],
hash_script: u64,
@ -183,7 +181,7 @@ impl Engine {
// Then check the global namespace and packages
|| self.global_modules.iter().any(|m| m.contains_fn(hash_script))
// Then check imported modules
|| mods.map_or(false, |m| m.contains_fn(hash_script))
|| global.map_or(false, |m| m.contains_fn(hash_script))
// Then check sub-modules
|| self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash_script));

View File

@ -41,12 +41,12 @@
//! engine.register_fn("compute", compute_something);
//!
//! # #[cfg(not(feature = "no_std"))]
//! # #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
//! assert_eq!(
//! // Evaluate the script, expects a 'bool' return
//! engine.eval_file::<bool>("my_script.rhai".into())?,
//! true
//! );
//! # #[cfg(not(target_arch = "wasm32"))]
//! # #[cfg(not(target_arch = "wasm64"))]
//! // Evaluate the script, expecting a 'bool' result
//! let result = engine.eval_file::<bool>("my_script.rhai".into())?;
//!
//! assert_eq!(result, true);
//!
//! Ok(())
//! }
@ -71,7 +71,6 @@ use std::prelude::v1::*;
mod api;
mod ast;
mod custom_syntax;
mod engine;
mod func;
mod module;
@ -83,7 +82,16 @@ mod tokenizer;
mod types;
mod r#unsafe;
type RhaiResult = Result<Dynamic, Box<EvalAltResult>>;
/// Error encountered when parsing a script.
type PERR = ParseErrorType;
/// Evaluation result.
type ERR = EvalAltResult;
/// General evaluation error for Rhai scripts.
type RhaiError = Box<ERR>;
/// Generic [`Result`] type for Rhai functions.
type RhaiResultOf<T> = Result<T, RhaiError>;
/// General [`Result`] type for Rhai functions returning [`Dynamic`] values.
type RhaiResult = RhaiResultOf<Dynamic>;
/// The system integer type. It is defined as [`i64`].
///
@ -98,6 +106,20 @@ pub type INT = i64;
#[cfg(feature = "only_i32")]
pub type INT = i32;
/// The unsigned system base integer type. It is defined as [`u64`].
///
/// If the `only_i32` feature is enabled, this will be [`u32`] instead.
#[cfg(not(feature = "only_i32"))]
#[allow(non_camel_case_types)]
type UNSIGNED_INT = u64;
/// The unsigned system integer base type.
/// It is defined as [`u32`] since the `only_i32` feature is used.
///
/// If the `only_i32` feature is not used, this will be `u64` instead.
#[cfg(feature = "only_i32")]
#[allow(non_camel_case_types)]
type UNSIGNED_INT = u32;
/// The system floating-point type. It is defined as [`f64`].
/// Not available under `no_float`.
///
@ -115,14 +137,15 @@ pub type FLOAT = f64;
#[cfg(feature = "f32_float")]
pub type FLOAT = f32;
pub type ExclusiveRange = std::ops::Range<INT>;
pub type InclusiveRange = std::ops::RangeInclusive<INT>;
/// An exclusive integer range.
type ExclusiveRange = std::ops::Range<INT>;
/// An inclusive integer range.
type InclusiveRange = std::ops::RangeInclusive<INT>;
pub use api::custom_syntax::Expression;
pub use ast::{FnAccess, AST};
pub use custom_syntax::Expression;
pub use engine::{
Engine, EvalContext, OP_CONTAINS, OP_EQUALS, OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE,
};
pub use engine::{Engine, EvalContext, OP_CONTAINS, OP_EQUALS};
pub use func::{NativeCallContext, RegisterNativeFunction};
pub use module::{FnNamespace, Module};
pub use tokenizer::Position;
@ -171,8 +194,11 @@ pub type Array = Vec<Dynamic>;
#[cfg(not(feature = "no_index"))]
pub type Blob = Vec<u8>;
/// Hash map of [`Dynamic`] values with [`SmartString`](https://crates.io/crates/smartstring) keys.
/// A dictionary of [`Dynamic`] values with string keys.
/// Not available under `no_object`.
///
/// [`SmartString`](https://crates.io/crates/smartstring) is used as the key type because most
/// property names are ASCII and short, fewer than 23 characters, so they can be stored inline.
#[cfg(not(feature = "no_object"))]
pub type Map = std::collections::BTreeMap<Identifier, Dynamic>;
@ -204,7 +230,10 @@ pub use tokenizer::{
};
#[cfg(feature = "internals")]
pub use parser::{IdentifierBuilder, ParseState};
pub use types::StringsInterner;
#[cfg(feature = "internals")]
pub use parser::ParseState;
#[cfg(feature = "internals")]
pub use ast::{
@ -217,10 +246,13 @@ pub use ast::{
pub use ast::FloatWrapper;
#[cfg(feature = "internals")]
pub use engine::{EvalState, FnResolutionCache, FnResolutionCacheEntry, Imports};
pub use engine::{EvalState, GlobalRuntimeState};
#[cfg(feature = "internals")]
pub use module::NamespaceRef;
pub use func::call::{FnResolutionCache, FnResolutionCacheEntry};
#[cfg(feature = "internals")]
pub use module::Namespace;
/// Alias to [`smallvec::SmallVec<[T; 3]>`](https://crates.io/crates/smallvec), which is a
/// specialized [`Vec`] backed by a small, inline, fixed-size array when there are ≤ 3 items stored.
@ -291,6 +323,26 @@ type StaticVec<T> = smallvec::SmallVec<[T; 3]>;
#[cfg(feature = "internals")]
pub type StaticVec<T> = smallvec::SmallVec<[T; 3]>;
/// Inline arguments storage for function calls.
///
/// # Notes
///
/// Since most usage of this is during a function call to gather up arguments, this is mostly
/// allocated on the stack, so we can tolerate a larger number of values stored inline.
///
/// Most functions have few parameters, but closures with a lot of captured variables can
/// potentially have many. Having a larger inline storage for arguments reduces allocations in
/// scripts with heavy closure usage.
///
/// Under `no_closure`, this type aliases to [`StaticVec`][crate::StaticVec] instead.
#[cfg(not(feature = "no_closure"))]
type FnArgsVec<T> = smallvec::SmallVec<[T; 8]>;
/// Inline arguments storage for function calls.
/// This type aliases to [`StaticVec`][crate::StaticVec].
#[cfg(feature = "no_closure")]
type FnArgsVec<T> = crate::StaticVec<T>;
pub(crate) type SmartString = smartstring::SmartString<smartstring::LazyCompact>;
// Compiler guards against mutually-exclusive feature flags
@ -315,11 +367,13 @@ compile_error!("`stdweb` cannot be used with `no-std`");
#[cfg(feature = "no_std")]
compile_error!("`no_std` cannot be used for WASM target");
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
#[cfg(feature = "wasm-bindgen")]
compile_error!("`wasm-bindgen` cannot be used for non-WASM target");
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
#[cfg(feature = "stdweb")]
compile_error!("`stdweb` cannot be used non-WASM target");

View File

@ -5,12 +5,11 @@ use crate::func::{
shared_take_or_clone, CallableFunction, FnCallArgs, IteratorFn, RegisterNativeFunction,
SendSync,
};
use crate::parser::IdentifierBuilder;
use crate::tokenizer::Token;
use crate::types::dynamic::Variant;
use crate::{
calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, Dynamic, EvalAltResult,
Identifier, ImmutableString, NativeCallContext, Shared, StaticVec,
calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, Dynamic, Identifier,
ImmutableString, NativeCallContext, RhaiResultOf, Shared, StaticVec,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -47,9 +46,15 @@ pub struct FuncInfo {
pub params: usize,
/// Parameter types (if applicable).
pub param_types: StaticVec<TypeId>,
/// Parameter names (if available).
/// Parameter names and types (if available).
#[cfg(feature = "metadata")]
pub param_names: StaticVec<Identifier>,
pub param_names_and_types: StaticVec<Identifier>,
/// Return type name.
#[cfg(feature = "metadata")]
pub return_type_name: Identifier,
/// Comments.
#[cfg(feature = "metadata")]
pub comments: Option<Box<[Box<str>]>>,
}
impl FuncInfo {
@ -60,16 +65,21 @@ impl FuncInfo {
pub fn gen_signature(&self) -> String {
let mut sig = format!("{}(", self.name);
if !self.param_names.is_empty() {
let mut params: StaticVec<Box<str>> =
self.param_names.iter().map(|s| s.as_str().into()).collect();
let return_type = params.pop().unwrap_or_else(|| "()".into());
if !self.param_names_and_types.is_empty() {
let params: StaticVec<_> = self
.param_names_and_types
.iter()
.map(|s| s.as_str())
.collect();
sig.push_str(&params.join(", "));
if &*return_type != "()" {
sig.push_str(") -> ");
sig.push_str(&return_type);
} else {
sig.push(')');
sig.push_str(")");
match self.return_type_name.as_str() {
"" | "()" => (),
ty => {
sig.push_str(" -> ");
sig.push_str(ty);
}
}
} else {
for x in 0..self.params {
@ -82,7 +92,12 @@ impl FuncInfo {
if self.func.is_script() {
sig.push(')');
} else {
sig.push_str(") -> ?");
sig.push_str(")");
match self.return_type_name.as_str() {
"()" => (),
_ => sig.push_str(" -> ?"),
}
}
}
@ -100,12 +115,12 @@ impl FuncInfo {
///
/// The first module name is skipped. Hashing starts from the _second_ module in the chain.
#[inline]
pub fn calc_native_fn_hash(
modules: impl Iterator<Item = impl AsRef<str>>,
fn_name: impl AsRef<str>,
pub fn calc_native_fn_hash<'a>(
modules: impl Iterator<Item = &'a str>,
fn_name: &str,
params: &[TypeId],
) -> u64 {
let hash_script = calc_qualified_fn_hash(modules, fn_name.as_ref(), params.len());
let hash_script = calc_qualified_fn_hash(modules, fn_name, params.len());
let hash_params = calc_fn_params_hash(params.iter().cloned());
combine_hashes(hash_script, hash_params)
}
@ -115,7 +130,8 @@ pub fn calc_native_fn_hash(
#[derive(Clone)]
pub struct Module {
/// ID identifying the module.
id: Option<Identifier>,
/// No ID if string is empty.
id: Identifier,
/// Is this module internal?
pub(crate) internal: bool,
/// Is this module part of a standard library?
@ -139,8 +155,6 @@ pub struct Module {
indexed: bool,
/// Does the [`Module`] contain indexed functions that have been exposed to the global namespace?
contains_indexed_global_functions: bool,
/// Interned strings
identifiers: IdentifierBuilder,
}
impl Default for Module {
@ -154,8 +168,9 @@ impl fmt::Debug for Module {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_struct("Module");
self.id.as_ref().map(|id| d.field("id", id));
if !self.id.is_empty() {
d.field("id", &self.id);
}
if !self.modules.is_empty() {
d.field(
"modules",
@ -225,7 +240,7 @@ impl Module {
#[must_use]
pub fn new() -> Self {
Self {
id: None,
id: Identifier::new_const(),
internal: false,
standard: false,
modules: BTreeMap::new(),
@ -237,7 +252,6 @@ impl Module {
all_type_iterators: BTreeMap::new(),
indexed: true,
contains_indexed_global_functions: false,
identifiers: IdentifierBuilder::new(),
}
}
@ -254,18 +268,24 @@ impl Module {
#[inline]
#[must_use]
pub fn id(&self) -> Option<&str> {
self.id_raw().map(|s| s.as_str())
if self.id_raw().is_empty() {
None
} else {
Some(self.id_raw())
}
}
/// Get the ID of the [`Module`] as an [`Identifier`], if any.
#[inline(always)]
#[must_use]
pub(crate) const fn id_raw(&self) -> Option<&Identifier> {
self.id.as_ref()
pub(crate) const fn id_raw(&self) -> &Identifier {
&self.id
}
/// Set the ID of the [`Module`].
///
/// If the string is empty, it is equivalent to clearing the ID.
///
/// # Example
///
/// ```
@ -276,7 +296,7 @@ impl Module {
/// ```
#[inline(always)]
pub fn set_id(&mut self, id: impl Into<Identifier>) -> &mut Self {
self.id = Some(id.into());
self.id = id.into();
self
}
/// Clear the ID of the [`Module`].
@ -293,7 +313,7 @@ impl Module {
/// ```
#[inline(always)]
pub fn clear_id(&mut self) -> &mut Self {
self.id = None;
self.id.clear();
self
}
@ -440,9 +460,9 @@ impl Module {
/// Name and Position in [`EvalAltResult`] are [`None`] and [`NONE`][Position::NONE] and must be set afterwards.
#[cfg(not(feature = "no_module"))]
#[inline]
pub(crate) fn get_qualified_var(&self, hash_var: u64) -> Result<&Dynamic, Box<EvalAltResult>> {
pub(crate) fn get_qualified_var(&self, hash_var: u64) -> RhaiResultOf<&Dynamic> {
self.all_variables.get(&hash_var).ok_or_else(|| {
EvalAltResult::ErrorVariableNotFound(String::new(), crate::Position::NONE).into()
crate::ERR::ErrorVariableNotFound(String::new(), crate::Position::NONE).into()
})
}
@ -457,8 +477,8 @@ impl Module {
// None + function name + number of arguments.
let num_params = fn_def.params.len();
let hash_script = crate::calc_fn_hash(&fn_def.name, num_params);
let mut param_names = fn_def.params.clone();
param_names.push("Dynamic".into());
#[cfg(feature = "metadata")]
let param_names_and_types = fn_def.params.iter().cloned().collect();
self.functions.insert(
hash_script,
FuncInfo {
@ -468,7 +488,11 @@ impl Module {
params: num_params,
param_types: StaticVec::new_const(),
#[cfg(feature = "metadata")]
param_names,
param_names_and_types,
#[cfg(feature = "metadata")]
return_type_name: "Dynamic".into(),
#[cfg(feature = "metadata")]
comments: None,
func: Into::<CallableFunction>::into(fn_def).into(),
}
.into(),
@ -597,7 +621,7 @@ impl Module {
self.functions.contains_key(&hash_fn)
}
/// Update the metadata (parameter names/types and return type) of a registered function.
/// _(metadata)_ Update the metadata (parameter names/types and return type) of a registered function.
/// Exported under the `metadata` feature only.
///
/// The [`u64`] hash is returned by the [`set_native_fn`][Module::set_native_fn] call.
@ -612,14 +636,69 @@ impl Module {
/// In other words, the number of entries should be one larger than the number of parameters.
#[cfg(feature = "metadata")]
#[inline]
pub fn update_fn_metadata(&mut self, hash_fn: u64, arg_names: &[impl AsRef<str>]) -> &mut Self {
let param_names = arg_names
pub fn update_fn_metadata<S: AsRef<str>>(
&mut self,
hash_fn: u64,
arg_names: impl AsRef<[S]>,
) -> &mut Self {
let mut param_names: StaticVec<_> = arg_names
.as_ref()
.iter()
.map(|name| self.identifiers.get(name.as_ref()))
.map(|s| s.as_ref().into())
.collect();
if let Some(f) = self.functions.get_mut(&hash_fn) {
f.param_names = param_names;
let (param_names, return_type_name) = if param_names.len() > f.params {
let return_type = param_names.pop().unwrap();
(param_names, return_type)
} else {
(param_names, Default::default())
};
f.param_names_and_types = param_names;
f.return_type_name = return_type_name;
}
self
}
/// _(metadata)_ Update the metadata (parameter names/types, return type and doc-comments) of a
/// registered function.
/// Exported under the `metadata` feature only.
///
/// The [`u64`] hash is returned by the [`set_native_fn`][Module::set_native_fn] call.
///
/// ## Parameter Names and Types
///
/// Each parameter name/type pair should be a single string of the format: `var_name: type`.
///
/// ## Return Type
///
/// The _last entry_ in the list should be the _return type_ of the function. In other words,
/// the number of entries should be one larger than the number of parameters.
///
/// ## Comments
///
/// Block doc-comments should be kept in a single line.
///
/// Line doc-comments should be kept in one string slice per line without the termination line-break.
///
/// Leading white-spaces should be stripped, and each string slice always starts with the corresponding
/// doc-comment leader: `///` or `/**`.
#[cfg(feature = "metadata")]
#[inline]
pub fn update_fn_metadata_with_comments<A: AsRef<str>, C: AsRef<str>>(
&mut self,
hash_fn: u64,
arg_names: impl AsRef<[A]>,
comments: impl AsRef<[C]>,
) -> &mut Self {
self.update_fn_metadata(hash_fn, arg_names);
let comments = comments.as_ref();
if !comments.is_empty() {
let f = self.functions.get_mut(&hash_fn).unwrap();
f.comments = Some(comments.iter().map(|s| s.as_ref().into()).collect());
}
self
@ -664,20 +743,30 @@ impl Module {
/// # WARNING - Low Level API
///
/// This function is very low level.
///
/// ## Parameter Names and Types
///
/// Each parameter name/type pair should be a single string of the format: `var_name: type`.
///
/// ## Return Type
///
/// The _last entry_ in the list should be the _return type_ of the function.
/// In other words, the number of entries should be one larger than the number of parameters.
#[inline]
pub fn set_fn(
&mut self,
name: impl AsRef<str> + Into<Identifier>,
name: impl AsRef<str>,
namespace: FnNamespace,
access: FnAccess,
arg_names: Option<&[&str]>,
arg_types: &[TypeId],
arg_types: impl AsRef<[TypeId]>,
func: CallableFunction,
) -> u64 {
let _arg_names = arg_names;
let is_method = func.is_method();
let mut param_types: StaticVec<_> = arg_types
.as_ref()
.iter()
.cloned()
.enumerate()
@ -686,26 +775,37 @@ impl Module {
param_types.shrink_to_fit();
#[cfg(feature = "metadata")]
let mut param_names: StaticVec<_> = _arg_names
.iter()
.flat_map(|&p| p.iter())
.map(|&arg| self.identifiers.get(arg))
.collect();
#[cfg(feature = "metadata")]
param_names.shrink_to_fit();
let (param_names, return_type_name) = {
let mut names = _arg_names
.iter()
.flat_map(|&p| p.iter())
.map(|&s| s.into())
.collect::<StaticVec<_>>();
let return_type = if names.len() > arg_types.as_ref().len() {
names.pop().unwrap()
} else {
Default::default()
};
names.shrink_to_fit();
(names, return_type)
};
let hash_fn = calc_native_fn_hash(empty::<&str>(), name.as_ref(), &param_types);
self.functions.insert(
hash_fn,
FuncInfo {
name: self.identifiers.get(name),
name: name.as_ref().into(),
namespace,
access,
params: param_types.len(),
param_types,
#[cfg(feature = "metadata")]
param_names,
param_names_and_types: param_names,
#[cfg(feature = "metadata")]
return_type_name,
#[cfg(feature = "metadata")]
comments: None,
func: func.into(),
}
.into(),
@ -717,6 +817,56 @@ impl Module {
hash_fn
}
/// _(metadata)_ Set a Rust function into the [`Module`], returning a non-zero hash key.
/// Exported under the `metadata` feature only.
///
/// If there is an existing Rust function of the same hash, it is replaced.
///
/// # WARNING - Low Level API
///
/// This function is very low level.
///
/// ## Parameter Names and Types
///
/// Each parameter name/type pair should be a single string of the format: `var_name: type`.
///
/// ## Return Type
///
/// The _last entry_ in the list should be the _return type_ of the function.
/// In other words, the number of entries should be one larger than the number of parameters.
///
/// ## Comments
///
/// Block doc-comments should be kept in a single line.
///
/// Line doc-comments should be kept in one string slice per line without the termination line-break.
///
/// Leading white-spaces should be stripped, and each string slice always starts with the corresponding
/// doc-comment leader: `///` or `/**`.
#[cfg(feature = "metadata")]
#[inline]
pub fn set_fn_with_comments<S: AsRef<str>>(
&mut self,
name: impl AsRef<str>,
namespace: FnNamespace,
access: FnAccess,
arg_names: Option<&[&str]>,
arg_types: impl AsRef<[TypeId]>,
comments: impl AsRef<[S]>,
func: CallableFunction,
) -> u64 {
let hash = self.set_fn(name, namespace, access, arg_names, arg_types, func);
let comments = comments.as_ref();
if !comments.is_empty() {
let f = self.functions.get_mut(&hash).unwrap();
f.comments = Some(comments.iter().map(|s| s.as_ref().into()).collect());
}
hash
}
/// Set a Rust function taking a reference to the scripting [`Engine`][crate::Engine],
/// the current set of functions, plus a list of mutable [`Dynamic`] references
/// into the [`Module`], returning a non-zero hash key.
@ -731,7 +881,7 @@ impl Module {
///
/// This function is very low level.
///
/// ## Arguments
/// # Arguments
///
/// A list of [`TypeId`]'s is taken as the argument types.
///
@ -750,7 +900,7 @@ impl Module {
///
/// # Function Metadata
///
/// No metadata for the function is registered. Use `update_fn_metadata` to add metadata.
/// No metadata for the function is registered. Use [`update_fn_metadata`][Module::update_fn_metadata] to add metadata.
///
/// # Example
///
@ -778,26 +928,23 @@ impl Module {
/// *x *= 2; // the first argument can be mutated
/// }
///
/// Ok(orig) // return Result<T, Box<EvalAltResult>>
/// Ok(orig) // return RhaiResult<T>
/// });
///
/// assert!(module.contains_fn(hash));
/// ```
#[inline(always)]
pub fn set_raw_fn<N, T, F>(
pub fn set_raw_fn<T, F>(
&mut self,
name: N,
name: impl AsRef<str>,
namespace: FnNamespace,
access: FnAccess,
arg_types: &[TypeId],
arg_types: impl AsRef<[TypeId]>,
func: F,
) -> u64
where
N: AsRef<str> + Into<Identifier>,
T: Variant + Clone,
F: Fn(NativeCallContext, &mut FnCallArgs) -> Result<T, Box<EvalAltResult>>
+ SendSync
+ 'static,
F: Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResultOf<T> + SendSync + 'static,
{
let f =
move |ctx: NativeCallContext, args: &mut FnCallArgs| func(ctx, args).map(Dynamic::from);
@ -839,7 +986,7 @@ impl Module {
where
N: AsRef<str> + Into<Identifier>,
T: Variant + Clone,
F: RegisterNativeFunction<ARGS, Result<T, Box<EvalAltResult>>>,
F: RegisterNativeFunction<ARGS, RhaiResultOf<T>>,
{
self.set_fn(
name,
@ -858,7 +1005,8 @@ impl Module {
///
/// # Function Metadata
///
/// No metadata for the function is registered. Use `update_fn_metadata` to add metadata.
/// No metadata for the function is registered.
/// Use [`update_fn_metadata`][Module::update_fn_metadata] to add metadata.
///
/// # Example
///
@ -874,11 +1022,11 @@ impl Module {
where
A: Variant + Clone,
T: Variant + Clone,
F: RegisterNativeFunction<ARGS, Result<T, Box<EvalAltResult>>>,
F: Fn(&mut A) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
F: RegisterNativeFunction<ARGS, RhaiResultOf<T>>,
F: Fn(&mut A) -> RhaiResultOf<T> + SendSync + 'static,
{
self.set_fn(
&crate::engine::make_getter(name),
crate::engine::make_getter(name.as_ref()).as_str(),
FnNamespace::Global,
FnAccess::Public,
None,
@ -895,7 +1043,8 @@ impl Module {
///
/// # Function Metadata
///
/// No metadata for the function is registered. Use `update_fn_metadata` to add metadata.
/// No metadata for the function is registered.
/// Use [`update_fn_metadata`][Module::update_fn_metadata] to add metadata.
///
/// # Example
///
@ -915,11 +1064,11 @@ impl Module {
where
A: Variant + Clone,
B: Variant + Clone,
F: RegisterNativeFunction<ARGS, Result<(), Box<EvalAltResult>>>,
F: Fn(&mut A, B) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
F: RegisterNativeFunction<ARGS, RhaiResultOf<()>>,
F: Fn(&mut A, B) -> RhaiResultOf<()> + SendSync + 'static,
{
self.set_fn(
&crate::engine::make_setter(name),
crate::engine::make_setter(name.as_ref()).as_str(),
FnNamespace::Global,
FnAccess::Public,
None,
@ -941,7 +1090,8 @@ impl Module {
///
/// # Function Metadata
///
/// No metadata for the function is registered. Use `update_fn_metadata` to add metadata.
/// No metadata for the function is registered.
/// Use [`update_fn_metadata`][Module::update_fn_metadata] to add metadata.
///
/// # Example
///
@ -961,8 +1111,8 @@ impl Module {
A: Variant + Clone,
B: Variant + Clone,
T: Variant + Clone,
F: RegisterNativeFunction<ARGS, Result<T, Box<EvalAltResult>>>,
F: Fn(&mut A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
F: RegisterNativeFunction<ARGS, RhaiResultOf<T>>,
F: Fn(&mut A, B) -> RhaiResultOf<T> + SendSync + 'static,
{
#[cfg(not(feature = "no_index"))]
if TypeId::of::<A>() == TypeId::of::<crate::Array>() {
@ -1002,7 +1152,8 @@ impl Module {
///
/// # Function Metadata
///
/// No metadata for the function is registered. Use `update_fn_metadata` to add metadata.
/// No metadata for the function is registered.
/// Use [`update_fn_metadata`][Module::update_fn_metadata] to add metadata.
///
/// # Example
///
@ -1022,8 +1173,8 @@ impl Module {
A: Variant + Clone,
B: Variant + Clone,
C: Variant + Clone,
F: RegisterNativeFunction<ARGS, Result<(), Box<EvalAltResult>>>,
F: Fn(&mut A, B, C) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
F: RegisterNativeFunction<ARGS, RhaiResultOf<()>>,
F: Fn(&mut A, B, C) -> RhaiResultOf<()> + SendSync + 'static,
{
#[cfg(not(feature = "no_index"))]
if TypeId::of::<A>() == TypeId::of::<crate::Array>() {
@ -1063,7 +1214,8 @@ impl Module {
///
/// # Function Metadata
///
/// No metadata for the function is registered. Use `update_fn_metadata` to add metadata.
/// No metadata for the function is registered.
/// Use [`update_fn_metadata`][Module::update_fn_metadata] to add metadata.
///
/// # Example
///
@ -1086,8 +1238,8 @@ impl Module {
#[inline(always)]
pub fn set_indexer_get_set_fn<A, B, T>(
&mut self,
get_fn: impl Fn(&mut A, B) -> Result<T, Box<EvalAltResult>> + SendSync + 'static,
set_fn: impl Fn(&mut A, B, T) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
get_fn: impl Fn(&mut A, B) -> RhaiResultOf<T> + SendSync + 'static,
set_fn: impl Fn(&mut A, B, T) -> RhaiResultOf<()> + SendSync + 'static,
) -> (u64, u64)
where
A: Variant + Clone,
@ -1142,7 +1294,6 @@ impl Module {
self.all_type_iterators.clear();
self.indexed = false;
self.contains_indexed_global_functions = false;
self.identifiers += other.identifiers;
self
}
@ -1162,7 +1313,6 @@ impl Module {
self.all_type_iterators.clear();
self.indexed = false;
self.contains_indexed_global_functions = false;
self.identifiers += other.identifiers;
self
}
@ -1191,7 +1341,6 @@ impl Module {
self.all_type_iterators.clear();
self.indexed = false;
self.contains_indexed_global_functions = false;
self.identifiers.merge(&other.identifiers);
self
}
@ -1241,7 +1390,6 @@ impl Module {
self.all_type_iterators.clear();
self.indexed = false;
self.contains_indexed_global_functions = false;
self.identifiers.merge(&other.identifiers);
self
}
@ -1284,8 +1432,8 @@ impl Module {
/// Get an iterator to the sub-modules in the [`Module`].
#[inline]
pub fn iter_sub_modules(&self) -> impl Iterator<Item = (&str, Shared<Module>)> {
self.modules.iter().map(|(k, m)| (k.as_str(), m.clone()))
pub fn iter_sub_modules(&self) -> impl Iterator<Item = (&str, &Shared<Module>)> {
self.modules.iter().map(|(k, m)| (k.as_str(), m))
}
/// Get an iterator to the variables in the [`Module`].
@ -1328,7 +1476,7 @@ impl Module {
f.access,
f.name.as_str(),
f.params,
f.func.get_script_fn_def().expect("scripted function"),
f.func.get_script_fn_def().expect("script-defined function"),
)
})
}
@ -1404,13 +1552,13 @@ impl Module {
scope: crate::Scope,
ast: &crate::AST,
engine: &crate::Engine,
) -> Result<Self, Box<EvalAltResult>> {
) -> RhaiResultOf<Self> {
let mut scope = scope;
let mut mods = crate::engine::Imports::new();
let orig_mods_len = mods.len();
let mut global = crate::engine::GlobalRuntimeState::new();
let orig_mods_len = global.num_imported_modules();
// Run the script
engine.eval_ast_with_scope_raw(&mut scope, &mut mods, &ast, 0)?;
engine.eval_ast_with_scope_raw(&mut scope, &mut global, &ast, 0)?;
// Create new module
let mut module =
@ -1421,11 +1569,11 @@ impl Module {
match aliases.len() {
0 => (),
1 => {
let alias = aliases.pop().expect("not empty");
let alias = aliases.pop().unwrap();
module.set_var(alias, value);
}
_ => {
let last_alias = aliases.pop().expect("not empty");
let last_alias = aliases.pop().unwrap();
aliases.into_iter().for_each(|alias| {
module.set_var(alias, value.clone());
});
@ -1437,13 +1585,18 @@ impl Module {
});
// Extra modules left in the scope become sub-modules
let mut func_mods = crate::engine::Imports::new();
let mut func_global = None;
mods.into_iter().skip(orig_mods_len).for_each(|(alias, m)| {
func_mods.push(alias.clone(), m.clone());
module.set_sub_module(alias, m);
global.into_iter().skip(orig_mods_len).for_each(|kv| {
if func_global.is_none() {
func_global = Some(StaticVec::new());
}
func_global.as_mut().expect("`Some`").push(kv.clone());
module.set_sub_module(kv.0, kv.1);
});
let func_global = func_global.map(|v| v.into_boxed_slice());
// Non-private functions defined become module functions
#[cfg(not(feature = "no_function"))]
if ast.has_functions() {
@ -1459,20 +1612,16 @@ impl Module {
let mut func = f
.func
.get_script_fn_def()
.expect("scripted function")
.expect("script-defined function")
.as_ref()
.clone();
func.lib = Some(ast.shared_lib().clone());
func.mods = func_mods.clone();
func.global = func_global.clone();
module.set_script_fn(func);
});
}
if let Some(s) = ast.source_raw() {
module.set_id(s.clone());
} else {
module.clear_id();
}
module.set_id(ast.source_raw().clone());
module.build_index();
@ -1647,20 +1796,21 @@ impl Module {
}
}
/// _(internals)_ A chain of [module][Module] names to namespace-qualify a variable or function call.
/// Exported under the `internals` feature only.
/// _(internals)_ A chain of [module][Module] names to namespace-qualify a variable or function
/// call. Exported under the `internals` feature only.
///
/// A [`u64`] offset to the current [`Scope`][crate::Scope] is cached for quick search purposes.
/// A [`u64`] offset to the current [stack of imported modules][crate::GlobalRuntimeState] 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.
/// A [`StaticVec`] is used because the vast majority of namespace-qualified access contains only
/// one level, and it is wasteful to always allocate a [`Vec`] with one element.
#[derive(Clone, Eq, PartialEq, Default, Hash)]
pub struct NamespaceRef {
pub struct Namespace {
index: Option<NonZeroUsize>,
path: StaticVec<Ident>,
}
impl fmt::Debug for NamespaceRef {
impl fmt::Debug for Namespace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(index) = self.index {
write!(f, "{} -> ", index)?;
@ -1677,7 +1827,7 @@ impl fmt::Debug for NamespaceRef {
}
}
impl fmt::Display for NamespaceRef {
impl fmt::Display for Namespace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(
&self
@ -1690,7 +1840,7 @@ impl fmt::Display for NamespaceRef {
}
}
impl Deref for NamespaceRef {
impl Deref for Namespace {
type Target = StaticVec<Ident>;
#[inline(always)]
@ -1699,14 +1849,14 @@ impl Deref for NamespaceRef {
}
}
impl DerefMut for NamespaceRef {
impl DerefMut for Namespace {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.path
}
}
impl From<Vec<Ident>> for NamespaceRef {
impl From<Vec<Ident>> for Namespace {
#[inline(always)]
fn from(mut path: Vec<Ident>) -> Self {
path.shrink_to_fit();
@ -1717,7 +1867,7 @@ impl From<Vec<Ident>> for NamespaceRef {
}
}
impl From<StaticVec<Ident>> for NamespaceRef {
impl From<StaticVec<Ident>> for Namespace {
#[inline(always)]
fn from(mut path: StaticVec<Ident>) -> Self {
path.shrink_to_fit();
@ -1725,8 +1875,8 @@ impl From<StaticVec<Ident>> for NamespaceRef {
}
}
impl NamespaceRef {
/// Create a new [`NamespaceRef`].
impl Namespace {
/// Create a new [`Namespace`].
#[inline(always)]
#[must_use]
pub const fn new() -> Self {

View File

@ -1,4 +1,4 @@
use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
use crate::{Engine, Module, ModuleResolver, Position, RhaiResultOf, Shared, ERR};
use std::ops::AddAssign;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -77,7 +77,7 @@ impl ModuleResolversCollection {
/// Get an iterator of all the [module resolvers][ModuleResolver].
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &dyn ModuleResolver> {
self.0.iter().map(|v| v.as_ref())
self.0.iter().map(<_>::as_ref)
}
/// Remove all [module resolvers][ModuleResolver].
#[inline(always)]
@ -123,19 +123,19 @@ impl ModuleResolver for ModuleResolversCollection {
source_path: Option<&str>,
path: &str,
pos: Position,
) -> Result<Shared<Module>, Box<EvalAltResult>> {
) -> RhaiResultOf<Shared<Module>> {
for resolver in self.0.iter() {
match resolver.resolve(engine, source_path, path, pos) {
Ok(module) => return Ok(module),
Err(err) => match *err {
EvalAltResult::ErrorModuleNotFound(_, _) => continue,
EvalAltResult::ErrorInModule(_, err, _) => return Err(err),
ERR::ErrorModuleNotFound(_, _) => continue,
ERR::ErrorInModule(_, err, _) => return Err(err),
_ => panic!("ModuleResolver::resolve returns error that is not ErrorModuleNotFound or ErrorInModule"),
},
}
}
Err(EvalAltResult::ErrorModuleNotFound(path.into(), pos).into())
Err(ERR::ErrorModuleNotFound(path.into(), pos).into())
}
}

View File

@ -1,4 +1,4 @@
use crate::{Engine, EvalAltResult, Module, ModuleResolver, Position, Shared};
use crate::{Engine, Module, ModuleResolver, Position, RhaiResultOf, Shared, ERR};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -44,7 +44,7 @@ impl ModuleResolver for DummyModuleResolver {
_: Option<&str>,
path: &str,
pos: Position,
) -> Result<Shared<Module>, Box<EvalAltResult>> {
Err(EvalAltResult::ErrorModuleNotFound(path.into(), pos).into())
) -> RhaiResultOf<Shared<Module>> {
Err(ERR::ErrorModuleNotFound(path.into(), pos).into())
}
}

View File

@ -1,8 +1,11 @@
#![cfg(not(feature = "no_std"))]
#![cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#![cfg(not(target_arch = "wasm32"))]
#![cfg(not(target_arch = "wasm64"))]
use crate::func::native::shared_write_lock;
use crate::{Engine, EvalAltResult, Identifier, Module, ModuleResolver, Position, Scope, Shared};
use crate::{
Engine, Identifier, Module, ModuleResolver, Position, RhaiResultOf, Scope, Shared, ERR,
};
use std::{
collections::BTreeMap,
@ -223,7 +226,7 @@ impl FileModuleResolver {
path: impl AsRef<str>,
source_path: Option<impl AsRef<str>>,
) -> Option<Shared<Module>> {
let file_path = self.get_file_path(path.as_ref(), source_path.as_ref().map(|v| v.as_ref()));
let file_path = self.get_file_path(path.as_ref(), source_path.as_ref().map(<_>::as_ref));
shared_write_lock(&self.cache)
.remove_entry(&file_path)
@ -259,7 +262,7 @@ impl ModuleResolver for FileModuleResolver {
source_path: Option<&str>,
path: &str,
pos: Position,
) -> Result<Shared<Module>, Box<EvalAltResult>> {
) -> RhaiResultOf<Shared<Module>> {
// Load relative paths from source if there is no base path specified
let source_path =
source_path.and_then(|p| Path::new(p).parent().map(|p| p.to_string_lossy()));
@ -285,17 +288,17 @@ impl ModuleResolver for FileModuleResolver {
let mut ast = engine
.compile_file(file_path.clone())
.map_err(|err| match *err {
EvalAltResult::ErrorSystem(_, err) if err.is::<IoError>() => {
Box::new(EvalAltResult::ErrorModuleNotFound(path.to_string(), pos))
ERR::ErrorSystem(_, err) if err.is::<IoError>() => {
Box::new(ERR::ErrorModuleNotFound(path.to_string(), pos))
}
_ => Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos)),
_ => Box::new(ERR::ErrorInModule(path.to_string(), err, pos)),
})?;
ast.set_source(path);
// Make a module from the AST
let m: Shared<Module> = Module::eval_ast_as_new(scope, &ast, engine)
.map_err(|err| Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos)))?
.map_err(|err| Box::new(ERR::ErrorInModule(path.to_string(), err, pos)))?
.into();
// Put it into the cache
@ -315,7 +318,7 @@ impl ModuleResolver for FileModuleResolver {
source_path: Option<&str>,
path: &str,
pos: Position,
) -> Option<Result<crate::AST, Box<EvalAltResult>>> {
) -> Option<RhaiResultOf<crate::AST>> {
// Construct the script file path
let file_path = self.get_file_path(path, source_path);
@ -328,10 +331,10 @@ impl ModuleResolver for FileModuleResolver {
ast
})
.map_err(|err| match *err {
EvalAltResult::ErrorSystem(_, err) if err.is::<IoError>() => {
EvalAltResult::ErrorModuleNotFound(path.to_string(), pos).into()
ERR::ErrorSystem(_, err) if err.is::<IoError>() => {
ERR::ErrorModuleNotFound(path.to_string(), pos).into()
}
_ => EvalAltResult::ErrorInModule(path.to_string(), err, pos).into(),
_ => ERR::ErrorInModule(path.to_string(), err, pos).into(),
}),
)
}

View File

@ -1,5 +1,5 @@
use crate::func::native::SendSync;
use crate::{Engine, EvalAltResult, Module, Position, Shared, AST};
use crate::{Engine, Module, Position, RhaiResultOf, Shared, AST};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -11,7 +11,8 @@ mod stat;
pub use collection::ModuleResolversCollection;
pub use dummy::DummyModuleResolver;
#[cfg(not(feature = "no_std"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
pub use file::FileModuleResolver;
pub use stat::StaticModuleResolver;
@ -24,7 +25,7 @@ pub trait ModuleResolver: SendSync {
source_path: Option<&str>,
path: &str,
pos: Position,
) -> Result<Shared<Module>, Box<EvalAltResult>>;
) -> RhaiResultOf<Shared<Module>>;
/// Resolve an `AST` based on a path string.
///
@ -43,7 +44,7 @@ pub trait ModuleResolver: SendSync {
source_path: Option<&str>,
path: &str,
pos: Position,
) -> Option<Result<AST, Box<EvalAltResult>>> {
) -> Option<RhaiResultOf<AST>> {
None
}
}

View File

@ -1,5 +1,5 @@
use crate::{
Engine, EvalAltResult, Identifier, Module, ModuleResolver, Position, Shared, SmartString,
Engine, Identifier, Module, ModuleResolver, Position, RhaiResultOf, Shared, SmartString, ERR,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -131,11 +131,11 @@ impl ModuleResolver for StaticModuleResolver {
_: Option<&str>,
path: &str,
pos: Position,
) -> Result<Shared<Module>, Box<EvalAltResult>> {
) -> RhaiResultOf<Shared<Module>> {
self.0
.get(path)
.cloned()
.ok_or_else(|| EvalAltResult::ErrorModuleNotFound(path.into(), pos).into())
.ok_or_else(|| ERR::ErrorModuleNotFound(path.into(), pos).into())
}
}

View File

@ -3,20 +3,24 @@
use crate::ast::{Expr, OpAssignment, Stmt, AST_OPTION_FLAGS::*};
use crate::engine::{
EvalState, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
EvalState, GlobalRuntimeState, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT,
KEYWORD_TYPE_OF,
};
use crate::func::builtin::get_builtin_binary_op_fn;
use crate::func::hashing::get_hasher;
use crate::tokenizer::Token;
use crate::types::dynamic::AccessMode;
use crate::{calc_fn_hash, Dynamic, Engine, FnPtr, Position, Scope, StaticVec, AST, INT};
use crate::{
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnPtr, Position, Scope,
StaticVec, AST, INT,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
any::TypeId,
convert::TryFrom,
hash::{Hash, Hasher},
mem,
ops::DerefMut,
};
/// Level of optimization performed.
@ -106,13 +110,11 @@ impl<'a> OptimizerState<'a> {
}
/// Look up a constant from the list.
#[inline]
pub fn find_constant(&self, name: impl AsRef<str>) -> Option<&Dynamic> {
pub fn find_constant(&self, name: &str) -> Option<&Dynamic> {
if !self.propagate_constants {
return None;
}
let name = name.as_ref();
for (n, access, value) in self.variables.iter().rev() {
if n == name {
return match access {
@ -128,7 +130,7 @@ impl<'a> OptimizerState<'a> {
#[inline]
pub fn call_fn_with_constant_arguments(
&self,
fn_name: impl AsRef<str>,
fn_name: &str,
arg_values: &mut [Dynamic],
) -> Option<Dynamic> {
#[cfg(not(feature = "no_function"))]
@ -138,10 +140,10 @@ impl<'a> OptimizerState<'a> {
self.engine
.call_native_fn(
&mut Imports::new(),
&mut GlobalRuntimeState::new(),
&mut EvalState::new(),
lib,
&fn_name,
fn_name,
calc_fn_hash(&fn_name, arg_values.len()),
&mut arg_values.iter_mut().collect::<StaticVec<_>>(),
false,
@ -153,6 +155,22 @@ impl<'a> OptimizerState<'a> {
}
}
// Has a system function a Rust-native override?
fn has_native_fn_override(
engine: &Engine,
hash_script: u64,
arg_types: impl AsRef<[TypeId]>,
) -> bool {
let hash_params = calc_fn_params_hash(arg_types.as_ref().iter().cloned());
let hash = combine_hashes(hash_script, hash_params);
// First check the global namespace and packages, but skip modules that are standard because
// they should never conflict with system functions.
engine.global_modules.iter().filter(|m| !m.standard).any(|m| m.contains_fn(hash))
// Then check sub-modules
|| engine.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash))
}
/// Optimize a block of [statements][Stmt].
fn optimize_stmt_block(
mut statements: StaticVec<Stmt>,
@ -173,6 +191,32 @@ fn optimize_stmt_block(
Stmt::is_pure
};
// Flatten blocks
loop {
if let Some(n) = statements.iter().enumerate().find_map(|(i, s)| match s {
Stmt::Block(block, _) if !block.iter().any(Stmt::is_block_dependent) => Some(i),
_ => None,
}) {
let (first, second) = statements.split_at_mut(n);
let stmt = mem::take(&mut second[0]);
let mut stmts = match stmt {
Stmt::Block(block, _) => block,
stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt),
};
statements = first
.iter_mut()
.map(mem::take)
.chain(stmts.iter_mut().map(mem::take))
.chain(second.iter_mut().skip(1).map(mem::take))
.collect();
} else {
break;
}
is_dirty = true;
}
// Optimize
loop {
state.clear_dirty();
@ -282,14 +326,14 @@ fn optimize_stmt_block(
&& !last_stmt.returns_value() =>
{
state.set_dirty();
statements.pop().expect(">= 2 elements");
statements.pop().unwrap();
}
// { ...; return val; } -> { ...; val }
[.., Stmt::Return(options, ref mut expr, pos)]
if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) =>
{
state.set_dirty();
*statements.last_mut().expect(">= 2 elements") = expr
*statements.last_mut().unwrap() = expr
.as_mut()
.map_or_else(|| Stmt::Noop(pos), |e| Stmt::Expr(mem::take(e)));
}
@ -306,10 +350,9 @@ fn optimize_stmt_block(
{
state.set_dirty();
if second_last_stmt.returns_value() {
*statements.last_mut().expect(">= 2 elements") =
Stmt::Noop(last_stmt.position());
*statements.last_mut().unwrap() = Stmt::Noop(last_stmt.position());
} else {
statements.pop().expect(">= 2 elements");
statements.pop().unwrap();
}
}
_ => break,
@ -327,7 +370,7 @@ fn optimize_stmt_block(
if reduce_return && !options.contains(AST_OPTION_BREAK_OUT) =>
{
state.set_dirty();
statements.pop().expect(">= 2 elements");
statements.pop().unwrap();
}
// { ...; return pure_val; } -> { ... }
[.., Stmt::Return(options, Some(ref expr), _)]
@ -336,11 +379,11 @@ fn optimize_stmt_block(
&& expr.is_pure() =>
{
state.set_dirty();
statements.pop().expect(">= 2 elements");
statements.pop().unwrap();
}
[.., ref last_stmt] if is_pure(last_stmt) => {
state.set_dirty();
statements.pop().expect("not empty");
statements.pop().unwrap();
}
_ => break,
}
@ -382,20 +425,18 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
match x.2 {
Expr::FnCall(ref mut x2, _) => {
state.set_dirty();
let op = Token::lookup_from_syntax(&x2.name).expect("operator");
let op_assignment = op.make_op_assignment().expect("operator");
x.1 = Some(OpAssignment::new(op_assignment));
x.1 = Some(OpAssignment::new(&x2.name));
let value = mem::take(&mut x2.args[1]);
if let Expr::Stack(slot, pos) = value {
let value = mem::take(x2.constants.get_mut(slot).expect("valid slot"));
x.2 = Expr::from_dynamic(value, pos);
x.2 =
Expr::from_dynamic(mem::take(x2.constants.get_mut(slot).unwrap()), pos);
} else {
x.2 = value;
}
}
_ => unreachable!(),
ref expr => unreachable!("Expr::FnCall expected but gets {:?}", expr),
}
}
@ -432,33 +473,33 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// if false { if_block } else { else_block } -> else_block
Stmt::If(Expr::BoolConstant(false, _), x, _) => {
state.set_dirty();
let else_block = mem::take(&mut *x.1);
*stmt = match optimize_stmt_block(else_block, state, preserve_result, true, false) {
statements if statements.is_empty() => Stmt::Noop(x.1.position()),
statements => Stmt::Block(statements.into_boxed_slice(), x.1.position()),
}
*stmt =
match optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false)
{
statements if statements.is_empty() => Stmt::Noop(x.1.position()),
statements => Stmt::Block(statements.into_boxed_slice(), x.1.position()),
}
}
// if true { if_block } else { else_block } -> if_block
Stmt::If(Expr::BoolConstant(true, _), x, _) => {
state.set_dirty();
let if_block = mem::take(&mut *x.0);
*stmt = match optimize_stmt_block(if_block, state, preserve_result, true, false) {
statements if statements.is_empty() => Stmt::Noop(x.0.position()),
statements => Stmt::Block(statements.into_boxed_slice(), x.0.position()),
}
*stmt =
match optimize_stmt_block(mem::take(&mut *x.0), state, preserve_result, true, false)
{
statements if statements.is_empty() => Stmt::Noop(x.0.position()),
statements => Stmt::Block(statements.into_boxed_slice(), x.0.position()),
}
}
// if expr { if_block } else { else_block }
Stmt::If(condition, x, _) => {
optimize_expr(condition, state, false);
let if_block = mem::take(x.0.deref_mut());
*x.0 = optimize_stmt_block(if_block, state, preserve_result, true, false);
let else_block = mem::take(x.1.deref_mut());
*x.1 = optimize_stmt_block(else_block, state, preserve_result, true, false);
*x.0 = optimize_stmt_block(mem::take(&mut *x.0), state, preserve_result, true, false);
*x.1 = optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false);
}
// switch const { ... }
Stmt::Switch(match_expr, x, pos) if match_expr.is_constant() => {
let value = match_expr.get_literal_value().expect("constant");
let value = match_expr.get_literal_value().unwrap();
let hasher = &mut get_hasher();
value.hash(hasher);
let hash = hasher.finish();
@ -471,11 +512,6 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def }
optimize_expr(&mut condition, state, false);
let def_pos = if x.1.position().is_none() {
*pos
} else {
x.1.position()
};
let def_stmt =
optimize_stmt_block(mem::take(&mut *x.1), state, true, true, false);
@ -483,16 +519,16 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
condition,
Box::new((
mem::take(&mut block.1),
Stmt::Block(def_stmt.into_boxed_slice(), def_pos).into(),
Stmt::Block(def_stmt.into_boxed_slice(), x.1.position().or_else(*pos))
.into(),
)),
match_expr.position(),
);
} else {
// Promote the matched case
let new_pos = block.1.position();
let statements =
optimize_stmt_block(mem::take(&mut *block.1), state, true, true, false);
*stmt = Stmt::Block(statements.into_boxed_slice(), new_pos);
*stmt = Stmt::Block(statements.into_boxed_slice(), block.1.position());
}
state.set_dirty();
@ -519,29 +555,27 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def }
optimize_expr(&mut condition, state, false);
let def_block = mem::take(&mut *x.1);
let def_stmt = optimize_stmt_block(def_block, state, true, true, false);
let def_pos = if x.1.position().is_none() {
*pos
} else {
x.1.position()
};
let def_stmt =
optimize_stmt_block(mem::take(&mut *x.1), state, true, true, false);
*stmt = Stmt::If(
condition,
Box::new((
mem::take(stmt_block),
Stmt::Block(def_stmt.into_boxed_slice(), def_pos).into(),
Stmt::Block(
def_stmt.into_boxed_slice(),
x.1.position().or_else(*pos),
)
.into(),
)),
match_expr.position(),
);
} else {
// Promote the matched case
let new_pos = stmt_block.position();
let statements = mem::take(&mut **stmt_block);
let statements =
optimize_stmt_block(statements, state, true, true, false);
*stmt = Stmt::Block(statements.into_boxed_slice(), new_pos);
*stmt =
Stmt::Block(statements.into_boxed_slice(), stmt_block.position());
}
state.set_dirty();
@ -551,10 +585,9 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// Multiple ranges - clear the table and just keep the right ranges
if !table.is_empty() {
state.set_dirty();
table.clear();
}
table.clear();
let old_ranges_len = ranges.len();
ranges.retain(|&mut (start, end, inclusive, _, _)| {
@ -585,19 +618,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// Promote the default case
state.set_dirty();
let def_pos = if x.1.position().is_none() {
*pos
} else {
x.1.position()
};
let def_stmt = optimize_stmt_block(mem::take(&mut *x.1), state, true, true, false);
*stmt = Stmt::Block(def_stmt.into_boxed_slice(), def_pos);
*stmt = Stmt::Block(def_stmt.into_boxed_slice(), x.1.position().or_else(*pos));
}
// switch
Stmt::Switch(match_expr, x, _) => {
optimize_expr(match_expr, state, false);
x.0.values_mut().for_each(|block| {
let statements = mem::take(block.1.deref_mut());
let statements = mem::take(&mut *block.1);
*block.1 = optimize_stmt_block(statements, state, preserve_result, true, false);
if let Some(mut condition) = mem::take(&mut block.0) {
@ -618,8 +646,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
x.0.remove(&key);
}
let def_block = mem::take(x.1.deref_mut());
*x.1 = optimize_stmt_block(def_block, state, preserve_result, true, false);
*x.1 = optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false);
}
// while false { block } -> Noop
@ -633,8 +660,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
if let Expr::BoolConstant(true, pos) = condition {
*condition = Expr::Unit(*pos);
}
let block = mem::take(body.as_mut().deref_mut());
*body.as_mut().deref_mut() = optimize_stmt_block(block, state, false, true, false);
***body = optimize_stmt_block(mem::take(&mut **body), state, false, true, false);
if body.len() == 1 {
match body[0] {
@ -661,24 +687,21 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
if *x == options.contains(AST_OPTION_NEGATED) =>
{
state.set_dirty();
let block_pos = body.position();
let block = mem::take(body.as_mut().deref_mut());
*stmt = Stmt::Block(
optimize_stmt_block(block, state, false, true, false).into_boxed_slice(),
block_pos,
optimize_stmt_block(mem::take(&mut **body), state, false, true, false)
.into_boxed_slice(),
body.position(),
);
}
// do { block } while|until expr
Stmt::Do(body, condition, _, _) => {
optimize_expr(condition, state, false);
let block = mem::take(body.as_mut().deref_mut());
*body.as_mut().deref_mut() = optimize_stmt_block(block, state, false, true, false);
***body = optimize_stmt_block(mem::take(&mut **body), state, false, true, false);
}
// for id in expr { block }
Stmt::For(iterable, x, _) => {
optimize_expr(iterable, state, false);
let body = mem::take(x.2.deref_mut());
*x.2 = optimize_stmt_block(body, state, false, true, false);
*x.2 = optimize_stmt_block(mem::take(&mut *x.2), state, false, true, false);
}
// let id = expr;
Stmt::Var(expr, _, options, _) if !options.contains(AST_OPTION_CONSTANT) => {
@ -697,8 +720,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
state.set_dirty();
*stmt = Stmt::Noop(*pos);
}
// Only one statement - promote
[s] => {
// Only one statement which is not block-dependent - promote
[s] if !s.is_block_dependent() => {
state.set_dirty();
*stmt = mem::take(s);
}
@ -709,19 +732,16 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
Stmt::TryCatch(x, _) if x.0.iter().all(Stmt::is_pure) => {
// If try block is pure, there will never be any exceptions
state.set_dirty();
let try_pos = x.0.position();
let try_block = mem::take(&mut *x.0);
*stmt = Stmt::Block(
optimize_stmt_block(try_block, state, false, true, false).into_boxed_slice(),
try_pos,
optimize_stmt_block(mem::take(&mut *x.0), state, false, true, false)
.into_boxed_slice(),
x.0.position(),
);
}
// try { try_block } catch ( var ) { catch_block }
Stmt::TryCatch(x, _) => {
let try_block = mem::take(x.0.deref_mut());
*x.0 = optimize_stmt_block(try_block, state, false, true, false);
let catch_block = mem::take(x.2.deref_mut());
*x.2 = optimize_stmt_block(catch_block, state, false, true, false);
*x.0 = optimize_stmt_block(mem::take(&mut *x.0), state, false, true, false);
*x.2 = optimize_stmt_block(mem::take(&mut *x.2), state, false, true, false);
}
// func(...)
Stmt::Expr(expr @ Expr::FnCall(_, _)) => {
@ -742,7 +762,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
// {...};
Stmt::Expr(Expr::Stmt(x)) => {
state.set_dirty();
*stmt = mem::take(x.as_mut()).into();
*stmt = mem::take(&mut **x).into();
}
// expr;
Stmt::Expr(expr) => optimize_expr(expr, state, false),
@ -770,8 +790,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
Expr::Stmt(x) if x.is_empty() => { state.set_dirty(); *expr = Expr::Unit(x.position()) }
// { stmt; ... } - do not count promotion as dirty because it gets turned back into an array
Expr::Stmt(x) => {
*x.as_mut().deref_mut() =
optimize_stmt_block(mem::take(x.as_mut().deref_mut()), state, true, true, false);
***x = optimize_stmt_block(mem::take(&mut **x), state, true, true, false);
// { Stmt(Expr) } - promote
match x.as_mut().as_mut() {
@ -805,9 +824,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
#[cfg(not(feature = "no_index"))]
Expr::Index(x, _, _) if !_chaining => match (&mut x.lhs, &mut x.rhs) {
// array[int]
(Expr::Array(a, pos), Expr::IntegerConstant(i, _))
if *i >= 0 && (*i as usize) < a.len() && a.iter().all(Expr::is_pure) =>
{
(Expr::Array(a, pos), Expr::IntegerConstant(i, _)) if *i >= 0 && (*i as usize) < a.len() && a.iter().all(Expr::is_pure) => {
// Array literal where everything is pure - promote the indexed item.
// All other items can be thrown away.
state.set_dirty();
@ -816,9 +833,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
*expr = result;
}
// array[-int]
(Expr::Array(a, pos), Expr::IntegerConstant(i, _))
if *i < 0 && i.checked_abs().map(|n| n as usize <= a.len()).unwrap_or(false) && a.iter().all(Expr::is_pure) =>
{
(Expr::Array(a, pos), Expr::IntegerConstant(i, _)) if *i < 0 && i.checked_abs().map(|n| n as usize <= a.len()).unwrap_or(false) && a.iter().all(Expr::is_pure) => {
// Array literal where everything is pure - promote the indexed item.
// All other items can be thrown away.
state.set_dirty();
@ -852,13 +867,13 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
(Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _)) if *i >= 0 && (*i as usize) < s.chars().count() => {
// String literal indexing - get the character
state.set_dirty();
*expr = Expr::CharConstant(s.chars().nth(*i as usize).expect("valid index"), *pos);
*expr = Expr::CharConstant(s.chars().nth(*i as usize).unwrap(), *pos);
}
// string[-int]
(Expr::StringConstant(s, pos), Expr::IntegerConstant(i, _)) if *i < 0 && i.checked_abs().map(|n| n as usize <= s.chars().count()).unwrap_or(false) => {
// String literal indexing - get the character
state.set_dirty();
*expr = Expr::CharConstant(s.chars().rev().nth(i.abs() as usize - 1).expect("valid index"), *pos);
*expr = Expr::CharConstant(s.chars().rev().nth(i.abs() as usize - 1).unwrap(), *pos);
}
// var[rhs]
(Expr::Variable(_, _, _), rhs) => optimize_expr(rhs, state, true),
@ -885,39 +900,14 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
let mut n = 0;
// Merge consecutive strings
while n < x.len()-1 {
while n < x.len() - 1 {
match (mem::take(&mut x[n]), mem::take(&mut x[n+1])) {
(Expr::StringConstant(mut s1, pos), Expr::StringConstant(s2, _)) => {
s1 += s2;
x[n] = Expr::StringConstant(s1, pos);
x.remove(n+1);
state.set_dirty();
}
(expr1, Expr::Unit(_)) => {
x[n] = expr1;
x.remove(n+1);
state.set_dirty();
}
(Expr::Unit(_), expr2) => {
x[n+1] = expr2;
x.remove(n);
state.set_dirty();
}
(expr1, Expr::StringConstant(s, _)) if s.is_empty() => {
x[n] = expr1;
x.remove(n+1);
state.set_dirty();
}
(Expr::StringConstant(s, _), expr2) if s.is_empty()=> {
x[n+1] = expr2;
x.remove(n);
state.set_dirty();
}
(expr1, expr2) => {
x[n] = expr1;
x[n+1] = expr2;
n += 1;
}
(Expr::StringConstant(mut s1, pos), Expr::StringConstant(s2, _)) => { s1 += s2; x[n] = Expr::StringConstant(s1, pos); x.remove(n+1); state.set_dirty(); }
(expr1, Expr::Unit(_)) => { x[n] = expr1; x.remove(n+1); state.set_dirty(); }
(Expr::Unit(_), expr2) => { x[n+1] = expr2; x.remove(n); state.set_dirty(); }
(expr1, Expr::StringConstant(s, _)) if s.is_empty() => { x[n] = expr1; x.remove(n+1); state.set_dirty(); }
(Expr::StringConstant(s, _), expr2) if s.is_empty()=> { x[n+1] = expr2; x.remove(n); state.set_dirty(); }
(expr1, expr2) => { x[n] = expr1; x[n+1] = expr2; n += 1; }
}
}
@ -927,7 +917,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
#[cfg(not(feature = "no_index"))]
Expr::Array(_, _) if expr.is_constant() => {
state.set_dirty();
*expr = Expr::DynamicConstant(expr.get_literal_value().expect("constant").into(), expr.position());
*expr = Expr::DynamicConstant(expr.get_literal_value().unwrap().into(), expr.position());
}
// [ items .. ]
#[cfg(not(feature = "no_index"))]
@ -936,7 +926,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
#[cfg(not(feature = "no_object"))]
Expr::Map(_, _) if expr.is_constant() => {
state.set_dirty();
*expr = Expr::DynamicConstant(expr.get_literal_value().expect("constant").into(), expr.position());
*expr = Expr::DynamicConstant(expr.get_literal_value().unwrap().into(), expr.position());
}
// #{ key:value, .. }
#[cfg(not(feature = "no_object"))]
@ -944,52 +934,24 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
// lhs && rhs
Expr::And(x, _) => match (&mut x.lhs, &mut x.rhs) {
// true && rhs -> rhs
(Expr::BoolConstant(true, _), rhs) => {
state.set_dirty();
optimize_expr(rhs, state, false);
*expr = mem::take(rhs);
}
(Expr::BoolConstant(true, _), rhs) => { state.set_dirty(); optimize_expr(rhs, state, false); *expr = mem::take(rhs); }
// false && rhs -> false
(Expr::BoolConstant(false, pos), _) => {
state.set_dirty();
*expr = Expr::BoolConstant(false, *pos);
}
(Expr::BoolConstant(false, pos), _) => { state.set_dirty(); *expr = Expr::BoolConstant(false, *pos); }
// lhs && true -> lhs
(lhs, Expr::BoolConstant(true, _)) => {
state.set_dirty();
optimize_expr(lhs, state, false);
*expr = mem::take(lhs);
}
(lhs, Expr::BoolConstant(true, _)) => { state.set_dirty(); optimize_expr(lhs, state, false); *expr = mem::take(lhs); }
// lhs && rhs
(lhs, rhs) => {
optimize_expr(lhs, state, false);
optimize_expr(rhs, state, false);
}
(lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, false); }
},
// lhs || rhs
Expr::Or(ref mut x, _) => match (&mut x.lhs, &mut x.rhs) {
// false || rhs -> rhs
(Expr::BoolConstant(false, _), rhs) => {
state.set_dirty();
optimize_expr(rhs, state, false);
*expr = mem::take(rhs);
}
(Expr::BoolConstant(false, _), rhs) => { state.set_dirty(); optimize_expr(rhs, state, false); *expr = mem::take(rhs); }
// true || rhs -> true
(Expr::BoolConstant(true, pos), _) => {
state.set_dirty();
*expr = Expr::BoolConstant(true, *pos);
}
(Expr::BoolConstant(true, pos), _) => { state.set_dirty(); *expr = Expr::BoolConstant(true, *pos); }
// lhs || false
(lhs, Expr::BoolConstant(false, _)) => {
state.set_dirty();
optimize_expr(lhs, state, false);
*expr = mem::take(lhs);
}
(lhs, Expr::BoolConstant(false, _)) => { state.set_dirty(); optimize_expr(lhs, state, false); *expr = mem::take(lhs); }
// lhs || rhs
(lhs, rhs) => {
optimize_expr(lhs, state, false);
optimize_expr(rhs, state, false);
}
(lhs, rhs) => { optimize_expr(lhs, state, false); optimize_expr(rhs, state, false); }
},
// eval!
@ -1032,7 +994,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
=> {
let arg_values = &mut x.args.iter().map(|e| match e {
Expr::Stack(slot, _) => x.constants[*slot].clone(),
_ => e.get_literal_value().expect("constant")
_ => e.get_literal_value().unwrap()
}).collect::<StaticVec<_>>();
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
@ -1050,7 +1012,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
return;
}
// Overloaded operators can override built-in.
_ if x.args.len() == 2 && !state.engine.has_native_fn_override(x.hashes.native, arg_types.as_ref()) => {
_ if x.args.len() == 2 && !has_native_fn_override(state.engine, x.hashes.native, arg_types.as_ref()) => {
if let Some(result) = get_builtin_binary_op_fn(x.name.as_ref(), &arg_values[0], &arg_values[1])
.and_then(|f| {
#[cfg(not(feature = "no_function"))]
@ -1059,7 +1021,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
let lib = &[];
let context = (state.engine, x.name.as_str(), lib).into();
let (first, second) = arg_values.split_first_mut().expect("not empty");
let (first, second) = arg_values.split_first_mut().unwrap();
(f)(context, &mut [ first, &mut second[0] ]).ok()
}) {
state.set_dirty();
@ -1098,7 +1060,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
if !has_script_fn {
let arg_values = &mut x.args.iter().map(|e| match e {
Expr::Stack(slot, _) => x.constants[*slot].clone(),
_ => e.get_literal_value().expect("constant")
_ => e.get_literal_value().unwrap()
}).collect::<StaticVec<_>>();
let result = match x.name.as_str() {
@ -1133,7 +1095,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
// constant-name
Expr::Variable(_, pos, x) if x.1.is_none() && state.find_constant(&x.2).is_some() => {
// Replace constant with value
*expr = Expr::from_dynamic(state.find_constant(&x.2).expect("exists").clone(), *pos);
*expr = Expr::from_dynamic(state.find_constant(&x.2).unwrap().clone(), *pos);
state.set_dirty();
}
@ -1218,7 +1180,7 @@ pub fn optimize_into_ast(
params: fn_def.params.clone(),
lib: None,
#[cfg(not(feature = "no_module"))]
mods: Imports::new(),
global: None,
#[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")]
comments: None,
@ -1235,7 +1197,7 @@ pub fn optimize_into_ast(
let mut fn_def = crate::func::native::shared_take_or_clone(fn_def);
// Optimize the function body
let body = mem::take(fn_def.body.deref_mut());
let body = mem::take(&mut *fn_def.body);
*fn_def.body =
optimize_top_level(body, engine, scope, lib2, optimization_level);

View File

@ -1,7 +1,7 @@
#![allow(non_snake_case)]
use crate::plugin::*;
use crate::{def_package, EvalAltResult, Position, INT};
use crate::{def_package, Position, RhaiError, RhaiResultOf, ERR, INT};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -9,9 +9,9 @@ use std::prelude::v1::*;
#[cfg(not(feature = "no_float"))]
use num_traits::Float;
#[inline(never)]
pub fn make_err(msg: impl Into<String>) -> Box<EvalAltResult> {
EvalAltResult::ErrorArithmetic(msg.into(), Position::NONE).into()
#[inline]
pub fn make_err(msg: impl Into<String>) -> RhaiError {
ERR::ErrorArithmetic(msg.into(), Position::NONE).into()
}
macro_rules! gen_arithmetic_functions {
@ -22,7 +22,7 @@ macro_rules! gen_arithmetic_functions {
#[export_module]
pub mod functions {
#[rhai_fn(name = "+", return_raw)]
pub fn add(x: $arg_type, y: $arg_type) -> Result<$arg_type, Box<EvalAltResult>> {
pub fn add(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) {
x.checked_add(y).ok_or_else(|| make_err(format!("Addition overflow: {} + {}", x, y)))
} else {
@ -30,7 +30,7 @@ macro_rules! gen_arithmetic_functions {
}
}
#[rhai_fn(name = "-", return_raw)]
pub fn subtract(x: $arg_type, y: $arg_type) -> Result<$arg_type, Box<EvalAltResult>> {
pub fn subtract(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) {
x.checked_sub(y).ok_or_else(|| make_err(format!("Subtraction overflow: {} - {}", x, y)))
} else {
@ -38,7 +38,7 @@ macro_rules! gen_arithmetic_functions {
}
}
#[rhai_fn(name = "*", return_raw)]
pub fn multiply(x: $arg_type, y: $arg_type) -> Result<$arg_type, Box<EvalAltResult>> {
pub fn multiply(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) {
x.checked_mul(y).ok_or_else(|| make_err(format!("Multiplication overflow: {} * {}", x, y)))
} else {
@ -46,7 +46,7 @@ macro_rules! gen_arithmetic_functions {
}
}
#[rhai_fn(name = "/", return_raw)]
pub fn divide(x: $arg_type, y: $arg_type) -> Result<$arg_type, Box<EvalAltResult>> {
pub fn divide(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) {
// Detect division by zero
if y == 0 {
@ -59,7 +59,7 @@ macro_rules! gen_arithmetic_functions {
}
}
#[rhai_fn(name = "%", return_raw)]
pub fn modulo(x: $arg_type, y: $arg_type) -> Result<$arg_type, Box<EvalAltResult>> {
pub fn modulo(x: $arg_type, y: $arg_type) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) {
x.checked_rem(y).ok_or_else(|| make_err(format!("Modulo division by zero or overflow: {} % {}", x, y)))
} else {
@ -67,7 +67,7 @@ macro_rules! gen_arithmetic_functions {
}
}
#[rhai_fn(name = "**", return_raw)]
pub fn power(x: $arg_type, y: INT) -> Result<$arg_type, Box<EvalAltResult>> {
pub fn power(x: $arg_type, y: INT) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) {
if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) {
Err(make_err(format!("Integer raised to too large an index: {} ~ {}", x, y)))
@ -82,7 +82,7 @@ macro_rules! gen_arithmetic_functions {
}
#[rhai_fn(name = "<<", return_raw)]
pub fn shift_left(x: $arg_type, y: INT) -> Result<$arg_type, Box<EvalAltResult>> {
pub fn shift_left(x: $arg_type, y: INT) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) {
if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) {
Err(make_err(format!("Left-shift by too many bits: {} << {}", x, y)))
@ -96,7 +96,7 @@ macro_rules! gen_arithmetic_functions {
}
}
#[rhai_fn(name = ">>", return_raw)]
pub fn shift_right(x: $arg_type, y: INT) -> Result<$arg_type, Box<EvalAltResult>> {
pub fn shift_right(x: $arg_type, y: INT) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) {
if cfg!(not(feature = "only_i32")) && y > (u32::MAX as INT) {
Err(make_err(format!("Right-shift by too many bits: {} >> {}", x, y)))
@ -146,7 +146,7 @@ macro_rules! gen_signed_functions {
#[export_module]
pub mod functions {
#[rhai_fn(name = "-", return_raw)]
pub fn neg(x: $arg_type) -> Result<$arg_type, Box<EvalAltResult>> {
pub fn neg(x: $arg_type) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) {
x.checked_neg().ok_or_else(|| make_err(format!("Negation overflow: -{}", x)))
} else {
@ -158,7 +158,7 @@ macro_rules! gen_signed_functions {
x
}
#[rhai_fn(return_raw)]
pub fn abs(x: $arg_type) -> Result<$arg_type, Box<EvalAltResult>> {
pub fn abs(x: $arg_type) -> RhaiResultOf<$arg_type> {
if cfg!(not(feature = "unchecked")) {
x.checked_abs().ok_or_else(|| make_err(format!("Negation overflow: -{}", x)))
} else {
@ -179,36 +179,40 @@ macro_rules! reg_functions {
)* }
}
def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
lib.standard = true;
def_package! {
/// Basic arithmetic package.
crate::ArithmeticPackage => |lib| {
lib.standard = true;
combine_with_exported_module!(lib, "int", int_functions);
reg_functions!(lib += signed_basic; INT);
combine_with_exported_module!(lib, "int", int_functions);
reg_functions!(lib += signed_basic; INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_functions!(lib += arith_numbers; i8, u8, i16, u16, i32, u32, u64);
reg_functions!(lib += signed_numbers; i8, i16, i32);
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_functions!(lib += arith_num_128; i128, u128);
reg_functions!(lib += signed_num_128; i128);
reg_functions!(lib += arith_numbers; i8, u8, i16, u16, i32, u32, u64);
reg_functions!(lib += signed_numbers; i8, i16, i32);
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
{
reg_functions!(lib += arith_num_128; i128, u128);
reg_functions!(lib += signed_num_128; i128);
}
}
}
// Basic arithmetic for floating-point
#[cfg(not(feature = "no_float"))]
{
combine_with_exported_module!(lib, "f32", f32_functions);
combine_with_exported_module!(lib, "f64", f64_functions);
}
// Basic arithmetic for floating-point
#[cfg(not(feature = "no_float"))]
{
combine_with_exported_module!(lib, "f32", f32_functions);
combine_with_exported_module!(lib, "f64", f64_functions);
}
// Decimal functions
#[cfg(feature = "decimal")]
combine_with_exported_module!(lib, "decimal", decimal_functions);
});
// Decimal functions
#[cfg(feature = "decimal")]
combine_with_exported_module!(lib, "decimal", decimal_functions);
}
}
#[export_module]
mod int_functions {
@ -234,7 +238,8 @@ gen_arithmetic_functions!(arith_numbers => i8, u8, i16, u16, i32, u32, u64);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
gen_arithmetic_functions!(arith_num_128 => i128, u128);
gen_signed_functions!(signed_basic => INT);
@ -245,14 +250,13 @@ gen_signed_functions!(signed_numbers => i8, i16, i32);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
gen_signed_functions!(signed_num_128 => i128);
#[cfg(not(feature = "no_float"))]
#[export_module]
mod f32_functions {
use crate::EvalAltResult;
#[cfg(not(feature = "f32_float"))]
pub mod basic_arithmetic {
#[rhai_fn(name = "+")]
@ -334,7 +338,7 @@ mod f32_functions {
x.abs()
}
#[rhai_fn(return_raw)]
pub fn sign(x: f32) -> Result<INT, Box<EvalAltResult>> {
pub fn sign(x: f32) -> RhaiResultOf<INT> {
match x.signum() {
_ if x == 0.0 => Ok(0),
x if x.is_nan() => Err(make_err("Sign of NaN is undefined")),
@ -346,7 +350,7 @@ mod f32_functions {
x == 0.0
}
#[rhai_fn(name = "**", return_raw)]
pub fn pow_f_i(x: f32, y: INT) -> Result<f32, Box<EvalAltResult>> {
pub fn pow_f_i(x: f32, y: INT) -> RhaiResultOf<f32> {
if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) {
Err(make_err(format!(
"Number raised to too large an index: {} ~ {}",
@ -442,7 +446,7 @@ mod f64_functions {
x.abs()
}
#[rhai_fn(return_raw)]
pub fn sign(x: f64) -> Result<INT, Box<EvalAltResult>> {
pub fn sign(x: f64) -> RhaiResultOf<INT> {
match x.signum() {
_ if x == 0.0 => Ok(0),
x if x.is_nan() => Err(make_err("Sign of NaN is undefined")),
@ -462,7 +466,7 @@ pub mod decimal_functions {
use rust_decimal::{prelude::Zero, Decimal, MathematicalOps};
#[rhai_fn(skip, return_raw)]
pub fn add(x: Decimal, y: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
pub fn add(x: Decimal, y: Decimal) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) {
x.checked_add(y)
.ok_or_else(|| make_err(format!("Addition overflow: {} + {}", x, y)))
@ -471,7 +475,7 @@ pub mod decimal_functions {
}
}
#[rhai_fn(skip, return_raw)]
pub fn subtract(x: Decimal, y: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
pub fn subtract(x: Decimal, y: Decimal) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) {
x.checked_sub(y)
.ok_or_else(|| make_err(format!("Subtraction overflow: {} - {}", x, y)))
@ -480,7 +484,7 @@ pub mod decimal_functions {
}
}
#[rhai_fn(skip, return_raw)]
pub fn multiply(x: Decimal, y: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
pub fn multiply(x: Decimal, y: Decimal) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) {
x.checked_mul(y)
.ok_or_else(|| make_err(format!("Multiplication overflow: {} * {}", x, y)))
@ -489,7 +493,7 @@ pub mod decimal_functions {
}
}
#[rhai_fn(skip, return_raw)]
pub fn divide(x: Decimal, y: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
pub fn divide(x: Decimal, y: Decimal) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) {
// Detect division by zero
if y == Decimal::zero() {
@ -503,7 +507,7 @@ pub mod decimal_functions {
}
}
#[rhai_fn(skip, return_raw)]
pub fn modulo(x: Decimal, y: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
pub fn modulo(x: Decimal, y: Decimal) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) {
x.checked_rem(y).ok_or_else(|| {
make_err(format!(
@ -516,7 +520,7 @@ pub mod decimal_functions {
}
}
#[rhai_fn(skip, return_raw)]
pub fn power(x: Decimal, y: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
pub fn power(x: Decimal, y: Decimal) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) {
x.checked_powd(y)
.ok_or_else(|| make_err(format!("Exponential overflow: {} + {}", x, y)))

View File

@ -4,52 +4,56 @@
use crate::engine::OP_EQUALS;
use crate::plugin::*;
use crate::{
def_package, Array, Dynamic, EvalAltResult, ExclusiveRange, FnPtr, InclusiveRange,
NativeCallContext, Position, INT,
def_package, Array, Dynamic, ExclusiveRange, FnPtr, InclusiveRange, NativeCallContext,
Position, RhaiResultOf, ERR, INT,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{any::TypeId, cmp::Ordering, mem};
def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
lib.standard = true;
def_package! {
/// Package of basic array utilities.
crate::BasicArrayPackage => |lib| {
lib.standard = true;
combine_with_exported_module!(lib, "array", array_functions);
combine_with_exported_module!(lib, "array", array_functions);
// Register array iterator
lib.set_iterable::<Array>();
});
// Register array iterator
lib.set_iterable::<Array>();
}
}
#[export_module]
mod array_functions {
pub mod array_functions {
#[rhai_fn(name = "len", get = "len", pure)]
pub fn len(array: &mut Array) -> INT {
array.len() as INT
}
#[rhai_fn(name = "push", name = "+=")]
pub fn push(array: &mut Array, item: Dynamic) {
array.push(item);
}
#[rhai_fn(name = "append", name = "+=")]
pub fn append(array: &mut Array, y: Array) {
if !y.is_empty() {
if array.is_empty() {
*array = y;
pub fn append(array1: &mut Array, array2: Array) {
if !array2.is_empty() {
if array1.is_empty() {
*array1 = array2;
} else {
array.extend(y);
array1.extend(array2);
}
}
}
#[rhai_fn(name = "+")]
pub fn concat(mut array: Array, y: Array) -> Array {
if !y.is_empty() {
if array.is_empty() {
array = y;
pub fn concat(array1: Array, array2: Array) -> Array {
if !array2.is_empty() {
if array1.is_empty() {
array2
} else {
array.extend(y);
let mut array = array1;
array.extend(array2);
array
}
} else {
array1
}
array
}
pub fn insert(array: &mut Array, position: INT, item: Dynamic) {
if array.is_empty() {
@ -76,25 +80,45 @@ mod array_functions {
array: &mut Array,
len: INT,
item: Dynamic,
) -> Result<(), Box<EvalAltResult>> {
if len <= 0 {
) -> RhaiResultOf<()> {
if len <= 0 || (len as usize) <= array.len() {
return Ok(());
}
let _ctx = ctx;
let len = len as usize;
// Check if array will be over max size limit
#[cfg(not(feature = "unchecked"))]
if _ctx.engine().max_array_size() > 0 && (len as usize) > _ctx.engine().max_array_size() {
return Err(EvalAltResult::ErrorDataTooLarge(
"Size of array".to_string(),
Position::NONE,
)
.into());
if _ctx.engine().max_array_size() > 0 && len > _ctx.engine().max_array_size() {
return Err(ERR::ErrorDataTooLarge("Size of array".to_string(), Position::NONE).into());
}
if len as usize > array.len() {
array.resize(len as usize, item);
#[cfg(not(feature = "unchecked"))]
let check_sizes = match item.0 {
crate::types::dynamic::Union::Array(_, _, _)
| crate::types::dynamic::Union::Str(_, _, _) => true,
#[cfg(not(feature = "no_object"))]
crate::types::dynamic::Union::Map(_, _, _) => true,
_ => false,
};
#[cfg(feature = "unchecked")]
let check_sizes = false;
if check_sizes {
let arr = mem::take(array);
let mut arr = Dynamic::from_array(arr);
while array.len() < len {
arr.write_lock::<Array>().unwrap().push(item.clone());
#[cfg(not(feature = "unchecked"))]
_ctx.engine().ensure_data_size_within_limits(&arr)?;
}
*array = arr.into_array().unwrap();
} else {
array.resize(len, item);
}
Ok(())
@ -170,9 +194,9 @@ mod array_functions {
let arr_len = array.len();
start
.checked_abs()
.map_or(0, |n| arr_len - (n as usize).min(arr_len))
.map_or(0, |n| arr_len - usize::min(n as usize, arr_len))
} else if start as usize >= array.len() {
array.extend(replace.into_iter());
array.extend(replace);
return;
} else {
start as usize
@ -209,7 +233,7 @@ mod array_functions {
let arr_len = array.len();
start
.checked_abs()
.map_or(0, |n| arr_len - (n as usize).min(arr_len))
.map_or(0, |n| arr_len - usize::min(n as usize, arr_len))
} else if start as usize >= array.len() {
return Array::new();
} else {
@ -259,11 +283,7 @@ mod array_functions {
}
}
#[rhai_fn(return_raw, pure)]
pub fn map(
ctx: NativeCallContext,
array: &mut Array,
mapper: FnPtr,
) -> Result<Array, Box<EvalAltResult>> {
pub fn map(ctx: NativeCallContext, array: &mut Array, mapper: FnPtr) -> RhaiResultOf<Array> {
if array.is_empty() {
return Ok(array.clone());
}
@ -275,7 +295,7 @@ mod array_functions {
mapper
.call_raw(&ctx, None, [item.clone()])
.or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
ERR::ErrorFunctionNotFound(fn_sig, _)
if fn_sig.starts_with(mapper.fn_name()) =>
{
mapper.call_raw(&ctx, None, [item.clone(), (i as INT).into()])
@ -283,7 +303,7 @@ mod array_functions {
_ => Err(err),
})
.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
Box::new(ERR::ErrorInFunctionCall(
"map".to_string(),
ctx.source().unwrap_or("").to_string(),
err,
@ -300,16 +320,12 @@ mod array_functions {
ctx: NativeCallContext,
array: &mut Array,
mapper: &str,
) -> Result<Array, Box<EvalAltResult>> {
) -> RhaiResultOf<Array> {
map(ctx, array, FnPtr::new(mapper)?)
}
#[rhai_fn(return_raw, pure)]
pub fn filter(
ctx: NativeCallContext,
array: &mut Array,
filter: FnPtr,
) -> Result<Array, Box<EvalAltResult>> {
pub fn filter(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf<Array> {
if array.is_empty() {
return Ok(array.clone());
}
@ -320,7 +336,7 @@ mod array_functions {
if filter
.call_raw(&ctx, None, [item.clone()])
.or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
ERR::ErrorFunctionNotFound(fn_sig, _)
if fn_sig.starts_with(filter.fn_name()) =>
{
filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()])
@ -328,7 +344,7 @@ mod array_functions {
_ => Err(err),
})
.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
Box::new(ERR::ErrorInFunctionCall(
"filter".to_string(),
ctx.source().unwrap_or("").to_string(),
err,
@ -349,7 +365,7 @@ mod array_functions {
ctx: NativeCallContext,
array: &mut Array,
filter_func: &str,
) -> Result<Array, Box<EvalAltResult>> {
) -> RhaiResultOf<Array> {
filter(ctx, array, FnPtr::new(filter_func)?)
}
#[rhai_fn(return_raw, pure)]
@ -357,7 +373,7 @@ mod array_functions {
ctx: NativeCallContext,
array: &mut Array,
value: Dynamic,
) -> Result<bool, Box<EvalAltResult>> {
) -> RhaiResultOf<bool> {
if array.is_empty() {
return Ok(false);
}
@ -366,9 +382,7 @@ mod array_functions {
if ctx
.call_fn_raw(OP_EQUALS, true, false, &mut [item, &mut value.clone()])
.or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _)
if fn_sig.starts_with(OP_EQUALS) =>
{
ERR::ErrorFunctionNotFound(ref fn_sig, _) if fn_sig.starts_with(OP_EQUALS) => {
if item.type_id() == value.type_id() {
// No default when comparing same type
Err(err)
@ -392,7 +406,7 @@ mod array_functions {
ctx: NativeCallContext,
array: &mut Array,
value: Dynamic,
) -> Result<INT, Box<EvalAltResult>> {
) -> RhaiResultOf<INT> {
if array.is_empty() {
Ok(-1)
} else {
@ -405,7 +419,7 @@ mod array_functions {
array: &mut Array,
value: Dynamic,
start: INT,
) -> Result<INT, Box<EvalAltResult>> {
) -> RhaiResultOf<INT> {
if array.is_empty() {
return Ok(-1);
}
@ -414,7 +428,7 @@ mod array_functions {
let arr_len = array.len();
start
.checked_abs()
.map_or(0, |n| arr_len - (n as usize).min(arr_len))
.map_or(0, |n| arr_len - usize::min(n as usize, arr_len))
} else if start as usize >= array.len() {
return Ok(-1);
} else {
@ -425,9 +439,7 @@ mod array_functions {
if ctx
.call_fn_raw(OP_EQUALS, true, false, &mut [item, &mut value.clone()])
.or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _)
if fn_sig.starts_with(OP_EQUALS) =>
{
ERR::ErrorFunctionNotFound(ref fn_sig, _) if fn_sig.starts_with(OP_EQUALS) => {
if item.type_id() == value.type_id() {
// No default when comparing same type
Err(err)
@ -451,7 +463,7 @@ mod array_functions {
ctx: NativeCallContext,
array: &mut Array,
filter: &str,
) -> Result<INT, Box<EvalAltResult>> {
) -> RhaiResultOf<INT> {
index_of_filter(ctx, array, FnPtr::new(filter)?)
}
#[rhai_fn(name = "index_of", return_raw, pure)]
@ -459,7 +471,7 @@ mod array_functions {
ctx: NativeCallContext,
array: &mut Array,
filter: FnPtr,
) -> Result<INT, Box<EvalAltResult>> {
) -> RhaiResultOf<INT> {
if array.is_empty() {
Ok(-1)
} else {
@ -472,7 +484,7 @@ mod array_functions {
array: &mut Array,
filter: FnPtr,
start: INT,
) -> Result<INT, Box<EvalAltResult>> {
) -> RhaiResultOf<INT> {
if array.is_empty() {
return Ok(-1);
}
@ -481,7 +493,7 @@ mod array_functions {
let arr_len = array.len();
start
.checked_abs()
.map_or(0, |n| arr_len - (n as usize).min(arr_len))
.map_or(0, |n| arr_len - usize::min(n as usize, arr_len))
} else if start as usize >= array.len() {
return Ok(-1);
} else {
@ -492,7 +504,7 @@ mod array_functions {
if filter
.call_raw(&ctx, None, [item.clone()])
.or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
ERR::ErrorFunctionNotFound(fn_sig, _)
if fn_sig.starts_with(filter.fn_name()) =>
{
filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()])
@ -500,7 +512,7 @@ mod array_functions {
_ => Err(err),
})
.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
Box::new(ERR::ErrorInFunctionCall(
"index_of".to_string(),
ctx.source().unwrap_or("").to_string(),
err,
@ -522,15 +534,11 @@ mod array_functions {
array: &mut Array,
filter: &str,
start: INT,
) -> Result<INT, Box<EvalAltResult>> {
) -> RhaiResultOf<INT> {
index_of_filter_starting_from(ctx, array, FnPtr::new(filter)?, start)
}
#[rhai_fn(return_raw, pure)]
pub fn some(
ctx: NativeCallContext,
array: &mut Array,
filter: FnPtr,
) -> Result<bool, Box<EvalAltResult>> {
pub fn some(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf<bool> {
if array.is_empty() {
return Ok(false);
}
@ -539,7 +547,7 @@ mod array_functions {
if filter
.call_raw(&ctx, None, [item.clone()])
.or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
ERR::ErrorFunctionNotFound(fn_sig, _)
if fn_sig.starts_with(filter.fn_name()) =>
{
filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()])
@ -547,7 +555,7 @@ mod array_functions {
_ => Err(err),
})
.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
Box::new(ERR::ErrorInFunctionCall(
"some".to_string(),
ctx.source().unwrap_or("").to_string(),
err,
@ -568,15 +576,11 @@ mod array_functions {
ctx: NativeCallContext,
array: &mut Array,
filter: &str,
) -> Result<bool, Box<EvalAltResult>> {
) -> RhaiResultOf<bool> {
some(ctx, array, FnPtr::new(filter)?)
}
#[rhai_fn(return_raw, pure)]
pub fn all(
ctx: NativeCallContext,
array: &mut Array,
filter: FnPtr,
) -> Result<bool, Box<EvalAltResult>> {
pub fn all(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf<bool> {
if array.is_empty() {
return Ok(true);
}
@ -585,7 +589,7 @@ mod array_functions {
if !filter
.call_raw(&ctx, None, [item.clone()])
.or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
ERR::ErrorFunctionNotFound(fn_sig, _)
if fn_sig.starts_with(filter.fn_name()) =>
{
filter.call_raw(&ctx, None, [item.clone(), (i as INT).into()])
@ -593,7 +597,7 @@ mod array_functions {
_ => Err(err),
})
.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
Box::new(ERR::ErrorInFunctionCall(
"all".to_string(),
ctx.source().unwrap_or("").to_string(),
err,
@ -614,11 +618,11 @@ mod array_functions {
ctx: NativeCallContext,
array: &mut Array,
filter: &str,
) -> Result<bool, Box<EvalAltResult>> {
) -> RhaiResultOf<bool> {
all(ctx, array, FnPtr::new(filter)?)
}
#[rhai_fn(return_raw)]
pub fn dedup(ctx: NativeCallContext, array: &mut Array) -> Result<(), Box<EvalAltResult>> {
pub fn dedup(ctx: NativeCallContext, array: &mut Array) -> RhaiResultOf<()> {
dedup_with_fn_name(ctx, array, OP_EQUALS)
}
#[rhai_fn(name = "dedup", return_raw)]
@ -626,7 +630,7 @@ mod array_functions {
ctx: NativeCallContext,
array: &mut Array,
comparer: FnPtr,
) -> Result<(), Box<EvalAltResult>> {
) -> RhaiResultOf<()> {
if array.is_empty() {
return Ok(());
}
@ -646,15 +650,11 @@ mod array_functions {
ctx: NativeCallContext,
array: &mut Array,
comparer: &str,
) -> Result<(), Box<EvalAltResult>> {
) -> RhaiResultOf<()> {
dedup_by_comparer(ctx, array, FnPtr::new(comparer)?)
}
#[rhai_fn(return_raw, pure)]
pub fn reduce(
ctx: NativeCallContext,
array: &mut Array,
reducer: FnPtr,
) -> Result<Dynamic, Box<EvalAltResult>> {
pub fn reduce(ctx: NativeCallContext, array: &mut Array, reducer: FnPtr) -> RhaiResult {
reduce_with_initial(ctx, array, reducer, Dynamic::UNIT)
}
#[rhai_fn(name = "reduce", return_raw, pure)]
@ -662,7 +662,7 @@ mod array_functions {
ctx: NativeCallContext,
array: &mut Array,
reducer: &str,
) -> Result<Dynamic, Box<EvalAltResult>> {
) -> RhaiResult {
reduce(ctx, array, FnPtr::new(reducer)?)
}
#[rhai_fn(name = "reduce", return_raw, pure)]
@ -671,7 +671,7 @@ mod array_functions {
array: &mut Array,
reducer: FnPtr,
initial: Dynamic,
) -> Result<Dynamic, Box<EvalAltResult>> {
) -> RhaiResult {
if array.is_empty() {
return Ok(initial);
}
@ -684,7 +684,7 @@ mod array_functions {
result = reducer
.call_raw(&ctx, None, [result.clone(), item.clone()])
.or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
ERR::ErrorFunctionNotFound(fn_sig, _)
if fn_sig.starts_with(reducer.fn_name()) =>
{
reducer.call_raw(&ctx, None, [result, item, (i as INT).into()])
@ -692,7 +692,7 @@ mod array_functions {
_ => Err(err),
})
.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
Box::new(ERR::ErrorInFunctionCall(
"reduce".to_string(),
ctx.source().unwrap_or("").to_string(),
err,
@ -709,15 +709,11 @@ mod array_functions {
array: &mut Array,
reducer: &str,
initial: Dynamic,
) -> Result<Dynamic, Box<EvalAltResult>> {
) -> RhaiResult {
reduce_with_initial(ctx, array, FnPtr::new(reducer)?, initial)
}
#[rhai_fn(return_raw, pure)]
pub fn reduce_rev(
ctx: NativeCallContext,
array: &mut Array,
reducer: FnPtr,
) -> Result<Dynamic, Box<EvalAltResult>> {
pub fn reduce_rev(ctx: NativeCallContext, array: &mut Array, reducer: FnPtr) -> RhaiResult {
reduce_rev_with_initial(ctx, array, reducer, Dynamic::UNIT)
}
#[rhai_fn(name = "reduce_rev", return_raw, pure)]
@ -725,7 +721,7 @@ mod array_functions {
ctx: NativeCallContext,
array: &mut Array,
reducer: &str,
) -> Result<Dynamic, Box<EvalAltResult>> {
) -> RhaiResult {
reduce_rev(ctx, array, FnPtr::new(reducer)?)
}
#[rhai_fn(name = "reduce_rev", return_raw, pure)]
@ -734,7 +730,7 @@ mod array_functions {
array: &mut Array,
reducer: FnPtr,
initial: Dynamic,
) -> Result<Dynamic, Box<EvalAltResult>> {
) -> RhaiResult {
if array.is_empty() {
return Ok(initial);
}
@ -748,7 +744,7 @@ mod array_functions {
result = reducer
.call_raw(&ctx, None, [result.clone(), item.clone()])
.or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
ERR::ErrorFunctionNotFound(fn_sig, _)
if fn_sig.starts_with(reducer.fn_name()) =>
{
reducer.call_raw(&ctx, None, [result, item, ((len - 1 - i) as INT).into()])
@ -756,7 +752,7 @@ mod array_functions {
_ => Err(err),
})
.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
Box::new(ERR::ErrorInFunctionCall(
"reduce_rev".to_string(),
ctx.source().unwrap_or("").to_string(),
err,
@ -773,7 +769,7 @@ mod array_functions {
array: &mut Array,
reducer: &str,
initial: Dynamic,
) -> Result<Dynamic, Box<EvalAltResult>> {
) -> RhaiResult {
reduce_rev_with_initial(ctx, array, FnPtr::new(reducer)?, initial)
}
#[rhai_fn(name = "sort", return_raw)]
@ -781,15 +777,11 @@ mod array_functions {
ctx: NativeCallContext,
array: &mut Array,
comparer: &str,
) -> Result<(), Box<EvalAltResult>> {
) -> RhaiResultOf<()> {
sort(ctx, array, FnPtr::new(comparer)?)
}
#[rhai_fn(return_raw)]
pub fn sort(
ctx: NativeCallContext,
array: &mut Array,
comparer: FnPtr,
) -> Result<(), Box<EvalAltResult>> {
pub fn sort(ctx: NativeCallContext, array: &mut Array, comparer: FnPtr) -> RhaiResultOf<()> {
if array.len() <= 1 {
return Ok(());
}
@ -803,7 +795,7 @@ mod array_functions {
v if v > 0 => Ordering::Greater,
v if v < 0 => Ordering::Less,
0 => Ordering::Equal,
_ => unreachable!(),
_ => unreachable!("v is {}", v),
})
.unwrap_or_else(|| x.type_id().cmp(&y.type_id()))
});
@ -811,7 +803,7 @@ mod array_functions {
Ok(())
}
#[rhai_fn(name = "sort", return_raw)]
pub fn sort_with_builtin(array: &mut Array) -> Result<(), Box<EvalAltResult>> {
pub fn sort_with_builtin(array: &mut Array) -> RhaiResultOf<()> {
if array.len() <= 1 {
return Ok(());
}
@ -819,7 +811,7 @@ mod array_functions {
let type_id = array[0].type_id();
if array.iter().any(|a| a.type_id() != type_id) {
return Err(EvalAltResult::ErrorFunctionNotFound(
return Err(ERR::ErrorFunctionNotFound(
"sort() cannot be called with elements of different types".into(),
Position::NONE,
)
@ -883,11 +875,7 @@ mod array_functions {
Ok(())
}
#[rhai_fn(return_raw)]
pub fn drain(
ctx: NativeCallContext,
array: &mut Array,
filter: FnPtr,
) -> Result<Array, Box<EvalAltResult>> {
pub fn drain(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf<Array> {
if array.is_empty() {
return Ok(Array::new());
}
@ -901,7 +889,7 @@ mod array_functions {
if filter
.call_raw(&ctx, None, [array[x].clone()])
.or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
ERR::ErrorFunctionNotFound(fn_sig, _)
if fn_sig.starts_with(filter.fn_name()) =>
{
filter.call_raw(&ctx, None, [array[x].clone(), (i as INT).into()])
@ -909,7 +897,7 @@ mod array_functions {
_ => Err(err),
})
.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
Box::new(ERR::ErrorInFunctionCall(
"drain".to_string(),
ctx.source().unwrap_or("").to_string(),
err,
@ -934,7 +922,7 @@ mod array_functions {
ctx: NativeCallContext,
array: &mut Array,
filter: &str,
) -> Result<Array, Box<EvalAltResult>> {
) -> RhaiResultOf<Array> {
drain(ctx, array, FnPtr::new(filter)?)
}
#[rhai_fn(name = "drain")]
@ -959,7 +947,7 @@ mod array_functions {
let arr_len = array.len();
start
.checked_abs()
.map_or(0, |n| arr_len - (n as usize).min(arr_len))
.map_or(0, |n| arr_len - usize::min(n as usize, arr_len))
} else if start as usize >= array.len() {
return Array::new();
} else {
@ -977,11 +965,7 @@ mod array_functions {
array.drain(start..start + len).collect()
}
#[rhai_fn(return_raw)]
pub fn retain(
ctx: NativeCallContext,
array: &mut Array,
filter: FnPtr,
) -> Result<Array, Box<EvalAltResult>> {
pub fn retain(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf<Array> {
if array.is_empty() {
return Ok(Array::new());
}
@ -995,7 +979,7 @@ mod array_functions {
if !filter
.call_raw(&ctx, None, [array[x].clone()])
.or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
ERR::ErrorFunctionNotFound(fn_sig, _)
if fn_sig.starts_with(filter.fn_name()) =>
{
filter.call_raw(&ctx, None, [array[x].clone(), (i as INT).into()])
@ -1003,7 +987,7 @@ mod array_functions {
_ => Err(err),
})
.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
Box::new(ERR::ErrorInFunctionCall(
"retain".to_string(),
ctx.source().unwrap_or("").to_string(),
err,
@ -1028,7 +1012,7 @@ mod array_functions {
ctx: NativeCallContext,
array: &mut Array,
filter: &str,
) -> Result<Array, Box<EvalAltResult>> {
) -> RhaiResultOf<Array> {
retain(ctx, array, FnPtr::new(filter)?)
}
#[rhai_fn(name = "retain")]
@ -1053,7 +1037,7 @@ mod array_functions {
let arr_len = array.len();
start
.checked_abs()
.map_or(0, |n| arr_len - (n as usize).min(arr_len))
.map_or(0, |n| arr_len - usize::min(n as usize, arr_len))
} else if start as usize >= array.len() {
return mem::take(array);
} else {
@ -1074,11 +1058,7 @@ mod array_functions {
drained
}
#[rhai_fn(name = "==", return_raw, pure)]
pub fn equals(
ctx: NativeCallContext,
array1: &mut Array,
array2: Array,
) -> Result<bool, Box<EvalAltResult>> {
pub fn equals(ctx: NativeCallContext, array1: &mut Array, array2: Array) -> RhaiResultOf<bool> {
if array1.len() != array2.len() {
return Ok(false);
}
@ -1092,9 +1072,7 @@ mod array_functions {
if !ctx
.call_fn_raw(OP_EQUALS, true, false, &mut [a1, a2])
.or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _)
if fn_sig.starts_with(OP_EQUALS) =>
{
ERR::ErrorFunctionNotFound(ref fn_sig, _) if fn_sig.starts_with(OP_EQUALS) => {
if a1.type_id() == a2.type_id() {
// No default when comparing same type
Err(err)
@ -1118,7 +1096,7 @@ mod array_functions {
ctx: NativeCallContext,
array1: &mut Array,
array2: Array,
) -> Result<bool, Box<EvalAltResult>> {
) -> RhaiResultOf<bool> {
equals(ctx, array1, array2).map(|r| !r)
}
}

198
src/packages/bit_field.rs Normal file
View File

@ -0,0 +1,198 @@
#![allow(non_snake_case)]
use crate::plugin::*;
use crate::{def_package, ExclusiveRange, InclusiveRange, Position, RhaiResultOf, ERR, INT};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
def_package! {
/// Package of basic bit-field utilities.
crate::BitFieldPackage => |lib| {
lib.standard = true;
combine_with_exported_module!(lib, "bit_field", bit_field_functions);
}
}
#[export_module]
mod bit_field_functions {
const BITS: usize = std::mem::size_of::<INT>() * 8;
#[rhai_fn(return_raw)]
pub fn get_bit(value: INT, index: INT) -> RhaiResultOf<bool> {
if index >= 0 {
let offset = index as usize;
if offset >= BITS {
Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into())
} else {
Ok((value & (1 << offset)) != 0)
}
} else if let Some(abs_index) = index.checked_abs() {
let offset = abs_index as usize;
// Count from end if negative
if offset > BITS {
Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into())
} else {
Ok((value & (1 << (BITS - offset))) != 0)
}
} else {
Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into())
}
}
#[rhai_fn(return_raw)]
pub fn set_bit(value: &mut INT, index: INT, new_value: bool) -> RhaiResultOf<()> {
if index >= 0 {
let offset = index as usize;
if offset >= BITS {
Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into())
} else {
let mask = 1 << offset;
if new_value {
*value |= mask;
} else {
*value &= !mask;
}
Ok(())
}
} else if let Some(abs_index) = index.checked_abs() {
let offset = abs_index as usize;
// Count from end if negative
if offset > BITS {
Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into())
} else {
let mask = 1 << offset;
if new_value {
*value |= mask;
} else {
*value &= !mask;
}
Ok(())
}
} else {
Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into())
}
}
#[rhai_fn(name = "get_bits", return_raw)]
pub fn get_bits_range(value: INT, range: ExclusiveRange) -> RhaiResultOf<INT> {
let from = INT::max(range.start, 0);
let to = INT::max(range.end, from);
get_bits(value, from, to - from)
}
#[rhai_fn(name = "get_bits", return_raw)]
pub fn get_bits_range_inclusive(value: INT, range: InclusiveRange) -> RhaiResultOf<INT> {
let from = INT::max(*range.start(), 0);
let to = INT::max(*range.end(), from - 1);
get_bits(value, from, to - from + 1)
}
#[rhai_fn(return_raw)]
pub fn get_bits(value: INT, index: INT, bits: INT) -> RhaiResultOf<INT> {
if bits < 1 {
return Ok(0);
}
let offset = if index >= 0 {
let offset = index as usize;
if offset >= BITS {
return Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into());
}
offset
} else if let Some(abs_index) = index.checked_abs() {
let offset = abs_index as usize;
// Count from end if negative
if offset > BITS {
return Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into());
}
BITS - offset
} else {
return Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into());
};
let bits = if offset + bits as usize > BITS {
BITS - offset
} else {
bits as usize
};
let mut base = 1;
let mut mask = 0;
for _ in 0..bits {
mask |= base;
base <<= 1;
}
Ok(((value & (mask << index)) >> index) & mask)
}
#[rhai_fn(name = "set_bits", return_raw)]
pub fn set_bits_range(
value: &mut INT,
range: ExclusiveRange,
new_value: INT,
) -> RhaiResultOf<()> {
let from = INT::max(range.start, 0);
let to = INT::max(range.end, from);
set_bits(value, from, to - from, new_value)
}
#[rhai_fn(name = "set_bits", return_raw)]
pub fn set_bits_range_inclusive(
value: &mut INT,
range: InclusiveRange,
new_value: INT,
) -> RhaiResultOf<()> {
let from = INT::max(*range.start(), 0);
let to = INT::max(*range.end(), from - 1);
set_bits(value, from, to - from + 1, new_value)
}
#[rhai_fn(return_raw)]
pub fn set_bits(value: &mut INT, index: INT, bits: INT, new_value: INT) -> RhaiResultOf<()> {
if bits < 1 {
return Ok(());
}
let offset = if index >= 0 {
let offset = index as usize;
if offset >= BITS {
return Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into());
}
offset
} else if let Some(abs_index) = index.checked_abs() {
let offset = abs_index as usize;
// Count from end if negative
if offset > BITS {
return Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into());
}
BITS - offset
} else {
return Err(ERR::ErrorBitFieldBounds(BITS, index, Position::NONE).into());
};
let bits = if offset + bits as usize > BITS {
BITS - offset
} else {
bits as usize
};
let mut base = 1;
let mut mask = 0;
for _ in 0..bits {
mask |= base;
base <<= 1;
}
*value &= !(mask << index);
*value |= (new_value & mask) << index;
Ok(())
}
}

View File

@ -3,8 +3,8 @@
use crate::plugin::*;
use crate::{
def_package, Blob, Dynamic, EvalAltResult, ExclusiveRange, InclusiveRange, NativeCallContext,
Position, INT,
def_package, Blob, Dynamic, ExclusiveRange, InclusiveRange, NativeCallContext, Position,
RhaiResultOf, INT,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -13,25 +13,25 @@ use std::{any::TypeId, mem};
#[cfg(not(feature = "no_float"))]
use crate::FLOAT;
def_package!(crate:BasicBlobPackage:"Basic BLOB utilities.", lib, {
lib.standard = true;
def_package! {
/// Package of basic BLOB utilities.
crate::BasicBlobPackage => |lib| {
lib.standard = true;
combine_with_exported_module!(lib, "blob", blob_functions);
combine_with_exported_module!(lib, "blob", blob_functions);
// Register blob iterator
lib.set_iterable::<Blob>();
});
// Register blob iterator
lib.set_iterable::<Blob>();
}
}
#[export_module]
mod blob_functions {
pub fn blob() -> Blob {
pub mod blob_functions {
pub const fn blob() -> Blob {
Blob::new()
}
#[rhai_fn(name = "blob", return_raw)]
pub fn blob_with_capacity(
ctx: NativeCallContext,
len: INT,
) -> Result<Blob, Box<EvalAltResult>> {
pub fn blob_with_capacity(ctx: NativeCallContext, len: INT) -> RhaiResultOf<Blob> {
blob_with_capacity_and_value(ctx, len, 0)
}
#[rhai_fn(name = "blob", return_raw)]
@ -39,34 +39,30 @@ mod blob_functions {
ctx: NativeCallContext,
len: INT,
value: INT,
) -> Result<Blob, Box<EvalAltResult>> {
) -> RhaiResultOf<Blob> {
let len = if len < 0 { 0 } else { len as usize };
let _ctx = ctx;
// Check if blob will be over max size limit
#[cfg(not(feature = "unchecked"))]
if _ctx.engine().max_array_size() > 0 && len > _ctx.engine().max_array_size() {
return Err(EvalAltResult::ErrorDataTooLarge(
"Size of BLOB".to_string(),
Position::NONE,
)
.into());
return Err(
crate::ERR::ErrorDataTooLarge("Size of BLOB".to_string(), Position::NONE).into(),
);
}
let mut blob = Blob::new();
blob.resize(len, (value & 0x00ff) as u8);
blob.resize(len, (value & 0x000000ff) as u8);
Ok(blob)
}
#[rhai_fn(name = "len", get = "len", pure)]
pub fn len(blob: &mut Blob) -> INT {
blob.len() as INT
}
#[rhai_fn(name = "push", name = "+=")]
pub fn push(blob: &mut Blob, item: INT) {
let item = (item & 0x00ff) as u8;
let item = (item & 0x000000ff) as u8;
blob.push(item);
}
#[rhai_fn(name = "append", name = "+=")]
pub fn append(blob: &mut Blob, y: Blob) {
if !y.is_empty() {
if blob.is_empty() {
@ -77,18 +73,21 @@ mod blob_functions {
}
}
#[rhai_fn(name = "+")]
pub fn concat(mut blob: Blob, y: Blob) -> Blob {
if !y.is_empty() {
if blob.is_empty() {
blob = y;
pub fn concat(blob1: Blob, blob2: Blob) -> Blob {
if !blob2.is_empty() {
if blob1.is_empty() {
blob2
} else {
blob.extend(y);
let mut blob = blob1;
blob.extend(blob2);
blob
}
} else {
blob1
}
blob
}
pub fn insert(blob: &mut Blob, position: INT, item: INT) {
let item = (item & 0x00ff) as u8;
let item = (item & 0x000000ff) as u8;
if blob.is_empty() {
blob.push(item);
@ -109,27 +108,20 @@ mod blob_functions {
}
}
#[rhai_fn(return_raw)]
pub fn pad(
ctx: NativeCallContext,
blob: &mut Blob,
len: INT,
item: INT,
) -> Result<(), Box<EvalAltResult>> {
pub fn pad(ctx: NativeCallContext, blob: &mut Blob, len: INT, item: INT) -> RhaiResultOf<()> {
if len <= 0 {
return Ok(());
}
let item = (item & 0x00ff) as u8;
let item = (item & 0x000000ff) as u8;
let _ctx = ctx;
// Check if blob will be over max size limit
#[cfg(not(feature = "unchecked"))]
if _ctx.engine().max_array_size() > 0 && (len as usize) > _ctx.engine().max_array_size() {
return Err(EvalAltResult::ErrorDataTooLarge(
"Size of BLOB".to_string(),
Position::NONE,
)
.into());
return Err(
crate::ERR::ErrorDataTooLarge("Size of BLOB".to_string(), Position::NONE).into(),
);
}
if len as usize > blob.len() {
@ -209,9 +201,9 @@ mod blob_functions {
let start = if start < 0 {
start
.checked_abs()
.map_or(0, |n| blob_len - (n as usize).min(blob_len))
.map_or(0, |n| blob_len - usize::min(n as usize, blob_len))
} else if start as usize >= blob_len {
blob.extend(replace.into_iter());
blob.extend(replace);
return;
} else {
start as usize
@ -248,7 +240,7 @@ mod blob_functions {
let start = if start < 0 {
start
.checked_abs()
.map_or(0, |n| blob_len - (n as usize).min(blob_len))
.map_or(0, |n| blob_len - usize::min(n as usize, blob_len))
} else if start as usize >= blob_len {
return Blob::new();
} else {
@ -312,7 +304,7 @@ mod blob_functions {
let start = if start < 0 {
start
.checked_abs()
.map_or(0, |n| blob_len - (n as usize).min(blob_len))
.map_or(0, |n| blob_len - usize::min(n as usize, blob_len))
} else if start as usize >= blob_len {
return Blob::new();
} else {
@ -348,7 +340,7 @@ mod blob_functions {
let start = if start < 0 {
start
.checked_abs()
.map_or(0, |n| blob_len - (n as usize).min(blob_len))
.map_or(0, |n| blob_len - usize::min(n as usize, blob_len))
} else if start as usize >= blob_len {
return mem::take(blob);
} else {
@ -366,26 +358,6 @@ mod blob_functions {
drained
}
pub fn contains(blob: &mut Blob, value: Dynamic) -> bool {
if blob.is_empty() {
return false;
}
let value = match value.as_int() {
Ok(value) => value as u8,
_ => return false,
};
blob.contains(&value)
}
#[rhai_fn(name = "==", pure)]
pub fn equals(blob1: &mut Blob, blob2: Blob) -> bool {
&*blob1 == &blob2
}
#[rhai_fn(name = "!=", pure)]
pub fn not_equals(blob1: &mut Blob, blob2: Blob) -> bool {
&*blob1 != &blob2
}
#[inline]
fn parse_int(blob: &mut Blob, start: INT, len: INT, is_le: bool) -> INT {
@ -397,7 +369,7 @@ mod blob_functions {
let start = if start < 0 {
start
.checked_abs()
.map_or(0, |n| blob_len - (n as usize).min(blob_len))
.map_or(0, |n| blob_len - usize::min(n as usize, blob_len))
} else if start as usize >= blob_len {
return 0;
} else {
@ -466,7 +438,7 @@ mod blob_functions {
let start = if start < 0 {
start
.checked_abs()
.map_or(0, |n| blob_len - (n as usize).min(blob_len))
.map_or(0, |n| blob_len - usize::min(n as usize, blob_len))
} else if start as usize >= blob_len {
return;
} else {
@ -483,13 +455,11 @@ mod blob_functions {
let len = usize::min(len, INT_BYTES);
let mut buf = [0_u8; INT_BYTES];
buf.copy_from_slice(&if is_le {
let buf = if is_le {
value.to_le_bytes()
} else {
value.to_be_bytes()
});
};
blob[start..][..len].copy_from_slice(&buf[..len]);
}
@ -537,7 +507,7 @@ mod blob_functions {
let start = if start < 0 {
start
.checked_abs()
.map_or(0, |n| blob_len - (n as usize).min(blob_len))
.map_or(0, |n| blob_len - usize::min(n as usize, blob_len))
} else if start as usize >= blob_len {
return 0.0;
} else {
@ -613,7 +583,7 @@ mod blob_functions {
let start = if start < 0 {
start
.checked_abs()
.map_or(0, |n| blob_len - (n as usize).min(blob_len))
.map_or(0, |n| blob_len - usize::min(n as usize, blob_len))
} else if start as usize >= blob_len {
return;
} else {
@ -629,14 +599,11 @@ mod blob_functions {
const FLOAT_BYTES: usize = mem::size_of::<FLOAT>();
let len = usize::min(len, FLOAT_BYTES);
let mut buf = [0_u8; FLOAT_BYTES];
buf.copy_from_slice(&if is_le {
let buf = if is_le {
value.to_le_bytes()
} else {
value.to_be_bytes()
});
};
blob[start..][..len].copy_from_slice(&buf[..len]);
}
@ -678,4 +645,77 @@ mod blob_functions {
pub fn write_be_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) {
write_float(blob, start, len, value, false)
}
#[inline]
fn write_string(blob: &mut Blob, start: INT, len: INT, string: &str, ascii_only: bool) {
if len <= 0 || blob.is_empty() || string.is_empty() {
return;
}
let blob_len = blob.len();
let start = if start < 0 {
start
.checked_abs()
.map_or(0, |n| blob_len - usize::min(n as usize, blob_len))
} else if start as usize >= blob_len {
return;
} else {
start as usize
};
let len = if len as usize > blob_len - start {
blob_len - start
} else {
len as usize
};
let len = usize::min(len, string.len());
if ascii_only {
string
.chars()
.filter(char::is_ascii)
.take(len)
.map(|ch| ch as u8)
.enumerate()
.for_each(|(i, x)| blob[start + i] = x);
} else {
blob[start..][..len].copy_from_slice(&string.as_bytes()[..len]);
}
}
#[rhai_fn(name = "write_utf8")]
pub fn write_utf8_string(blob: &mut Blob, start: INT, len: INT, string: &str) {
write_string(blob, start, len, string, false)
}
#[rhai_fn(name = "write_utf8")]
pub fn write_utf8_string_range(blob: &mut Blob, range: ExclusiveRange, string: &str) {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
write_string(blob, start, end - start, string, false)
}
#[rhai_fn(name = "write_utf8")]
pub fn write_utf8_string_range_inclusive(blob: &mut Blob, range: InclusiveRange, string: &str) {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
write_string(blob, start, end - start + 1, string, false)
}
#[rhai_fn(name = "write_ascii")]
pub fn write_ascii_string(blob: &mut Blob, start: INT, len: INT, string: &str) {
write_string(blob, start, len, string, true)
}
#[rhai_fn(name = "write_ascii")]
pub fn write_ascii_string_range(blob: &mut Blob, range: ExclusiveRange, string: &str) {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
write_string(blob, start, end - start, string, true)
}
#[rhai_fn(name = "write_ascii")]
pub fn write_ascii_string_range_inclusive(
blob: &mut Blob,
range: InclusiveRange,
string: &str,
) {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
write_string(blob, start, end - start + 1, string, true)
}
}

View File

@ -3,11 +3,14 @@ use crate::{def_package, FnPtr, ImmutableString, NativeCallContext};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, {
lib.standard = true;
def_package! {
/// Package of basic function pointer utilities.
crate::BasicFnPackage => |lib| {
lib.standard = true;
combine_with_exported_module!(lib, "FnPtr", fn_ptr_functions);
});
combine_with_exported_module!(lib, "FnPtr", fn_ptr_functions);
}
}
#[export_module]
mod fn_ptr_functions {
@ -41,7 +44,7 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
fn make_metadata(
dict: &BTreeSet<Identifier>,
namespace: Option<Identifier>,
f: &ScriptFnDef,
func: &ScriptFnDef,
) -> Map {
const DICT: &str = "key exists";
@ -50,10 +53,13 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
if let Some(ns) = namespace {
map.insert(dict.get("namespace").expect(DICT).clone(), ns.into());
}
map.insert(dict.get("name").expect(DICT).clone(), f.name.clone().into());
map.insert(
dict.get("name").expect(DICT).clone(),
func.name.clone().into(),
);
map.insert(
dict.get("access").expect(DICT).clone(),
match f.access {
match func.access {
FnAccess::Public => dict.get("public").expect(DICT).clone(),
FnAccess::Private => dict.get("private").expect(DICT).clone(),
}
@ -61,14 +67,14 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
);
map.insert(
dict.get("is_anonymous").expect(DICT).clone(),
f.name.starts_with(crate::engine::FN_ANONYMOUS).into(),
func.name.starts_with(crate::engine::FN_ANONYMOUS).into(),
);
map.insert(
dict.get("params").expect(DICT).clone(),
f.params
func.params
.iter()
.cloned()
.map(Into::<Dynamic>::into)
.map(Into::into)
.collect::<Array>()
.into(),
);

View File

@ -1,10 +1,12 @@
use crate::plugin::*;
use crate::types::dynamic::Variant;
use crate::{def_package, EvalAltResult, ExclusiveRange, InclusiveRange, INT};
use std::iter::{ExactSizeIterator, FusedIterator};
use std::ops::{Range, RangeInclusive};
use crate::{def_package, ExclusiveRange, InclusiveRange, RhaiResultOf, INT};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
iter::{ExactSizeIterator, FusedIterator},
ops::{Range, RangeInclusive},
};
#[cfg(not(feature = "unchecked"))]
use num_traits::{CheckedAdd as Add, CheckedSub as Sub};
@ -22,19 +24,19 @@ impl<T> StepRange<T>
where
T: Variant + Copy + PartialOrd + Add<Output = T> + Sub<Output = T>,
{
pub fn new(from: T, to: T, step: T) -> Result<Self, Box<EvalAltResult>> {
pub fn new(from: T, to: T, step: T) -> RhaiResultOf<Self> {
#[cfg(not(feature = "unchecked"))]
if let Some(r) = from.checked_add(&step) {
if r == from {
return Err(EvalAltResult::ErrorInFunctionCall(
return Err(crate::ERR::ErrorInFunctionCall(
"range".to_string(),
String::new(),
EvalAltResult::ErrorArithmetic(
crate::ERR::ErrorArithmetic(
"step value cannot be zero".to_string(),
crate::Position::NONE,
Position::NONE,
)
.into(),
crate::Position::NONE,
Position::NONE,
)
.into());
}
@ -117,33 +119,24 @@ struct BitRange(INT, INT, usize);
const BITS: usize = std::mem::size_of::<INT>() * 8;
impl BitRange {
pub fn new(value: INT, from: INT, len: INT) -> Result<Self, Box<EvalAltResult>> {
pub fn new(value: INT, from: INT, len: INT) -> RhaiResultOf<Self> {
let from = if from >= 0 {
let offset = from as usize;
#[cfg(not(feature = "unchecked"))]
if offset >= BITS {
return Err(
EvalAltResult::ErrorBitFieldBounds(BITS, from, crate::Position::NONE).into(),
);
return Err(crate::ERR::ErrorBitFieldBounds(BITS, from, Position::NONE).into());
}
offset
} else {
#[cfg(not(feature = "unchecked"))]
if let Some(abs_from) = from.checked_abs() {
if (abs_from as usize) > BITS {
return Err(EvalAltResult::ErrorBitFieldBounds(
BITS,
from,
crate::Position::NONE,
)
.into());
return Err(crate::ERR::ErrorBitFieldBounds(BITS, from, Position::NONE).into());
}
BITS - (abs_from as usize)
} else {
return Err(
EvalAltResult::ErrorBitFieldBounds(BITS, from, crate::Position::NONE).into(),
);
return Err(crate::ERR::ErrorBitFieldBounds(BITS, from, Position::NONE).into());
}
#[cfg(feature = "unchecked")]
@ -168,12 +161,10 @@ impl Iterator for BitRange {
type Item = bool;
fn next(&mut self) -> Option<Self::Item> {
let Self(value, mask, len) = *self;
if len == 0 {
if self.2 == 0 {
None
} else {
let r = (value & mask) != 0;
let r = (self.0 & self.1) != 0;
self.1 <<= 1;
self.2 -= 1;
Some(r)
@ -272,7 +263,6 @@ macro_rules! reg_range {
($lib:ident | $x:expr => $( $y:ty ),*) => {
$(
$lib.set_iterator::<Range<$y>>();
$lib.set_iterator::<RangeInclusive<$y>>();
let _hash = $lib.set_native_fn($x, |from: $y, to: $y| Ok(from..to));
#[cfg(feature = "metadata")]
@ -281,6 +271,8 @@ macro_rules! reg_range {
concat!("to: ", stringify!($y)),
concat!("Iterator<Item=", stringify!($y), ">")
]);
$lib.set_iterator::<RangeInclusive<$y>>();
)*
};
($lib:ident | step $x:expr => $( $y:ty ),*) => {
@ -299,268 +291,275 @@ macro_rules! reg_range {
};
}
def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
lib.standard = true;
def_package! {
/// Package of basic range iterators
crate::BasicIteratorPackage => |lib| {
lib.standard = true;
reg_range!(lib | "range" => INT);
reg_range!(lib | "range" => INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_range!(lib | "range" => i8, u8, i16, u16, i32, u32, i64, u64);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_range!(lib | "range" => i8, u8, i16, u16, i32, u32, i64, u64);
#[cfg(not(target_arch = "wasm32"))]
reg_range!(lib | "range" => i128, u128);
}
reg_range!(lib | step "range" => INT);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_range!(lib | step "range" => i8, u8, i16, u16, i32, u32, i64, u64);
#[cfg(not(target_arch = "wasm32"))]
reg_range!(lib | step "range" => i128, u128);
}
#[cfg(not(feature = "no_float"))]
{
use crate::FLOAT;
#[derive(Debug, Clone, Copy, PartialEq)]
struct StepFloatRange(FLOAT, FLOAT, FLOAT);
impl StepFloatRange {
pub fn new(from: FLOAT, to: FLOAT, step: FLOAT) -> Result<Self, Box<EvalAltResult>> {
#[cfg(not(feature = "unchecked"))]
if step == 0.0 {
return Err(EvalAltResult::ErrorInFunctionCall("range".to_string(), "".to_string(),
EvalAltResult::ErrorArithmetic("step value cannot be zero".to_string(), crate::Position::NONE).into(),
crate::Position::NONE,
).into());
}
Ok(Self(from, to, step))
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
reg_range!(lib | "range" => i128, u128);
}
impl Iterator for StepFloatRange {
type Item = FLOAT;
reg_range!(lib | step "range" => INT);
fn next(&mut self) -> Option<FLOAT> {
if self.0 == self.1 {
None
} else if self.0 < self.1 {
#[cfg(not(feature = "unchecked"))]
if self.2 < 0.0 {
return None;
}
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_range!(lib | step "range" => i8, u8, i16, u16, i32, u32, i64, u64);
let v = self.0;
let n = self.0 + self.2;
self.0 = if n >= self.1 { self.1 } else { n };
Some(v)
} else {
#[cfg(not(feature = "unchecked"))]
if self.2 > 0.0 {
return None;
}
let v = self.0;
let n = self.0 + self.2;
self.0 = if n <= self.1 { self.1 } else { n };
Some(v)
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
reg_range!(lib | step "range" => i128, u128);
}
impl FusedIterator for StepFloatRange {}
#[cfg(not(feature = "no_float"))]
{
use crate::FLOAT;
lib.set_iterator::<StepFloatRange>();
#[derive(Debug, Clone, Copy, PartialEq)]
struct StepFloatRange(FLOAT, FLOAT, FLOAT);
let _hash = lib.set_native_fn("range", StepFloatRange::new);
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["from: FLOAT", "to: FLOAT", "step: FLOAT", "Iterator<Item=FLOAT>"]);
}
#[cfg(feature = "decimal")]
{
use rust_decimal::Decimal;
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
struct StepDecimalRange(Decimal, Decimal, Decimal);
impl StepDecimalRange {
pub fn new(from: Decimal, to: Decimal, step: Decimal) -> Result<Self, Box<EvalAltResult>> {
#[cfg(not(feature = "unchecked"))]
if step.is_zero() {
return Err(EvalAltResult::ErrorInFunctionCall("range".to_string(), "".to_string(),
EvalAltResult::ErrorArithmetic("step value cannot be zero".to_string(), crate::Position::NONE).into(),
crate::Position::NONE,
).into());
}
Ok(Self(from, to, step))
}
}
impl Iterator for StepDecimalRange {
type Item = Decimal;
fn next(&mut self) -> Option<Decimal> {
if self.0 == self.1 {
None
} else if self.0 < self.1 {
impl StepFloatRange {
pub fn new(from: FLOAT, to: FLOAT, step: FLOAT) -> RhaiResultOf<Self> {
#[cfg(not(feature = "unchecked"))]
if self.2.is_sign_negative() {
return None;
if step == 0.0 {
return Err(crate::ERR::ErrorInFunctionCall("range".to_string(), "".to_string(),
crate::ERR::ErrorArithmetic("step value cannot be zero".to_string(), Position::NONE).into(),
Position::NONE,
).into());
}
let v = self.0;
let n = self.0 + self.2;
self.0 = if n >= self.1 { self.1 } else { n };
Some(v)
} else {
#[cfg(not(feature = "unchecked"))]
if self.2.is_sign_positive() {
return None;
}
let v = self.0;
let n = self.0 + self.2;
self.0 = if n <= self.1 { self.1 } else { n };
Some(v)
Ok(Self(from, to, step))
}
}
impl Iterator for StepFloatRange {
type Item = FLOAT;
fn next(&mut self) -> Option<FLOAT> {
if self.0 == self.1 {
None
} else if self.0 < self.1 {
#[cfg(not(feature = "unchecked"))]
if self.2 < 0.0 {
return None;
}
let v = self.0;
let n = self.0 + self.2;
self.0 = if n >= self.1 { self.1 } else { n };
Some(v)
} else {
#[cfg(not(feature = "unchecked"))]
if self.2 > 0.0 {
return None;
}
let v = self.0;
let n = self.0 + self.2;
self.0 = if n <= self.1 { self.1 } else { n };
Some(v)
}
}
}
impl FusedIterator for StepFloatRange {}
lib.set_iterator::<StepFloatRange>();
let _hash = lib.set_native_fn("range", StepFloatRange::new);
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["from: FLOAT", "to: FLOAT", "step: FLOAT", "Iterator<Item=FLOAT>"]);
}
impl FusedIterator for StepDecimalRange {}
#[cfg(feature = "decimal")]
{
use rust_decimal::Decimal;
lib.set_iterator::<StepDecimalRange>();
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
struct StepDecimalRange(Decimal, Decimal, Decimal);
let _hash = lib.set_native_fn("range", StepDecimalRange::new);
impl StepDecimalRange {
pub fn new(from: Decimal, to: Decimal, step: Decimal) -> RhaiResultOf<Self> {
#[cfg(not(feature = "unchecked"))]
if step.is_zero() {
return Err(crate::ERR::ErrorInFunctionCall("range".to_string(), "".to_string(),
crate::ERR::ErrorArithmetic("step value cannot be zero".to_string(), Position::NONE).into(),
Position::NONE,
).into());
}
Ok(Self(from, to, step))
}
}
impl Iterator for StepDecimalRange {
type Item = Decimal;
fn next(&mut self) -> Option<Decimal> {
if self.0 == self.1 {
None
} else if self.0 < self.1 {
#[cfg(not(feature = "unchecked"))]
if self.2.is_sign_negative() {
return None;
}
let v = self.0;
let n = self.0 + self.2;
self.0 = if n >= self.1 { self.1 } else { n };
Some(v)
} else {
#[cfg(not(feature = "unchecked"))]
if self.2.is_sign_positive() {
return None;
}
let v = self.0;
let n = self.0 + self.2;
self.0 = if n <= self.1 { self.1 } else { n };
Some(v)
}
}
}
impl FusedIterator for StepDecimalRange {}
lib.set_iterator::<StepDecimalRange>();
let _hash = lib.set_native_fn("range", StepDecimalRange::new);
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["from: Decimal", "to: Decimal", "step: Decimal", "Iterator<Item=Decimal>"]);
}
// Register string iterator
lib.set_iterator::<CharsStream>();
let _hash = lib.set_native_fn("chars", |string, range: ExclusiveRange| {
let from = INT::max(range.start, 0);
let to = INT::max(range.end, from);
Ok(CharsStream::new(string, from, to - from))
});
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["from: Decimal", "to: Decimal", "step: Decimal", "Iterator<Item=Decimal>"]);
}
lib.update_fn_metadata(_hash, &["string: &str", "range: Range<INT>", "Iterator<Item=char>"]);
// Register string iterator
lib.set_iterator::<CharsStream>();
let _hash = lib.set_native_fn("chars", |string, range: ExclusiveRange| {
let from = INT::max(range.start, 0);
let to = INT::max(range.end, from);
Ok(CharsStream::new(string, from, to - from))
});
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &str", "range: Range", "Iterator<Item=char>"]);
let _hash = lib.set_native_fn("chars", |string, range: InclusiveRange| {
let from = INT::max(*range.start(), 0);
let to = INT::max(*range.end(), from - 1);
Ok(CharsStream::new(string, from, to-from + 1))
});
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &str", "range: RangeInclusive", "Iterator<Item=char>"]);
let _hash = lib.set_native_fn("chars", |string, from, len| Ok(CharsStream::new(string, from, len)));
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &str", "from: INT", "len: INT", "Iterator<Item=char>"]);
let _hash = lib.set_native_fn("chars", |string, from| Ok(CharsStream::new(string, from, INT::MAX)));
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &str", "from: INT", "Iterator<Item=char>"]);
let _hash = lib.set_native_fn("chars", |string| Ok(CharsStream::new(string, 0, INT::MAX)));
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &str", "Iterator<Item=char>"]);
#[cfg(not(feature = "no_object"))]
{
let _hash = lib.set_getter_fn("chars", |string: &mut ImmutableString| Ok(CharsStream::new(string, 0, INT::MAX)));
let _hash = lib.set_native_fn("chars", |string, range: InclusiveRange| {
let from = INT::max(*range.start(), 0);
let to = INT::max(*range.end(), from - 1);
Ok(CharsStream::new(string, from, to-from + 1))
});
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &mut ImmutableString", "Iterator<Item=char>"]);
}
lib.update_fn_metadata(_hash, &["string: &str", "range: RangeInclusive<INT>", "Iterator<Item=char>"]);
// Register bit-field iterator
lib.set_iterator::<BitRange>();
let _hash = lib.set_native_fn("bits", |value, range: ExclusiveRange| {
let from = INT::max(range.start, 0);
let to = INT::max(range.end, from);
BitRange::new(value, from, to - from)
});
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "range: Range", "Iterator<Item=bool>"]);
let _hash = lib.set_native_fn("bits", |value, range: InclusiveRange| {
let from = INT::max(*range.start(), 0);
let to = INT::max(*range.end(), from - 1);
BitRange::new(value, from, to - from + 1)
});
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "range: RangeInclusive", "Iterator<Item=bool>"]);
let _hash = lib.set_native_fn("bits", BitRange::new);
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "len: INT", "Iterator<Item=bool>"]);
let _hash = lib.set_native_fn("bits", |value, from| BitRange::new(value, from, INT::MAX));
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "Iterator<Item=bool>"]);
let _hash = lib.set_native_fn("bits", |value| BitRange::new(value, 0, INT::MAX) );
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "Iterator<Item=bool>"]);
#[cfg(not(feature = "no_object"))]
{
let _hash = lib.set_getter_fn("bits", |value: &mut INT| BitRange::new(*value, 0, INT::MAX) );
let _hash = lib.set_native_fn("chars", |string, from, len| Ok(CharsStream::new(string, from, len)));
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: &mut INT", "range: Range", "Iterator<Item=bool>"]);
}
lib.update_fn_metadata(_hash, &["string: &str", "from: INT", "len: INT", "Iterator<Item=char>"]);
combine_with_exported_module!(lib, "range", range_functions);
});
let _hash = lib.set_native_fn("chars", |string, from| Ok(CharsStream::new(string, from, INT::MAX)));
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &str", "from: INT", "Iterator<Item=char>"]);
let _hash = lib.set_native_fn("chars", |string| Ok(CharsStream::new(string, 0, INT::MAX)));
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &str", "Iterator<Item=char>"]);
#[cfg(not(feature = "no_object"))]
{
let _hash = lib.set_getter_fn("chars", |string: &mut ImmutableString| Ok(CharsStream::new(string, 0, INT::MAX)));
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["string: &mut ImmutableString", "Iterator<Item=char>"]);
}
// Register bit-field iterator
lib.set_iterator::<BitRange>();
let _hash = lib.set_native_fn("bits", |value, range: ExclusiveRange| {
let from = INT::max(range.start, 0);
let to = INT::max(range.end, from);
BitRange::new(value, from, to - from)
});
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "range: Range<INT>", "Iterator<Item=bool>"]);
let _hash = lib.set_native_fn("bits", |value, range: InclusiveRange| {
let from = INT::max(*range.start(), 0);
let to = INT::max(*range.end(), from - 1);
BitRange::new(value, from, to - from + 1)
});
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "range: RangeInclusive<INT>", "Iterator<Item=bool>"]);
let _hash = lib.set_native_fn("bits", BitRange::new);
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "len: INT", "Iterator<Item=bool>"]);
let _hash = lib.set_native_fn("bits", |value, from| BitRange::new(value, from, INT::MAX));
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "from: INT", "Iterator<Item=bool>"]);
let _hash = lib.set_native_fn("bits", |value| BitRange::new(value, 0, INT::MAX) );
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: INT", "Iterator<Item=bool>"]);
#[cfg(not(feature = "no_object"))]
{
let _hash = lib.set_getter_fn("bits", |value: &mut INT| BitRange::new(*value, 0, INT::MAX) );
#[cfg(feature = "metadata")]
lib.update_fn_metadata(_hash, &["value: &mut INT", "range: Range<INT>", "Iterator<Item=bool>"]);
}
combine_with_exported_module!(lib, "range", range_functions);
}
}
#[export_module]
mod range_functions {
#[rhai_fn(get = "start", name = "start", pure)]
pub fn range_start(range: &mut ExclusiveRange) -> INT {
pub fn start(range: &mut ExclusiveRange) -> INT {
range.start
}
#[rhai_fn(get = "end", name = "end", pure)]
pub fn range_end(range: &mut ExclusiveRange) -> INT {
pub fn end(range: &mut ExclusiveRange) -> INT {
range.end
}
#[rhai_fn(name = "contains", pure)]
pub fn range_contains(range: &mut ExclusiveRange, value: INT) -> bool {
range.contains(&value)
}
#[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)]
pub fn range_is_inclusive(range: &mut ExclusiveRange) -> bool {
pub fn is_inclusive(range: &mut ExclusiveRange) -> bool {
let _range = range;
false
}
#[rhai_fn(get = "start", name = "start", pure)]
pub fn range_inclusive_start(range: &mut InclusiveRange) -> INT {
*range.start()
}
#[rhai_fn(get = "end", name = "end", pure)]
pub fn range_inclusive_end(range: &mut InclusiveRange) -> INT {
*range.end()
}
#[rhai_fn(name = "contains", pure)]
pub fn range_inclusive_contains(range: &mut InclusiveRange, value: INT) -> bool {
range.contains(&value)
}
#[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)]
pub fn range_inclusive_is_inclusive(range: &mut InclusiveRange) -> bool {
#[rhai_fn(get = "is_exclusive", name = "is_exclusive", pure)]
pub fn is_exclusive(range: &mut ExclusiveRange) -> bool {
let _range = range;
true
}
#[rhai_fn(get = "start", name = "start", pure)]
pub fn start_inclusive(range: &mut InclusiveRange) -> INT {
*range.start()
}
#[rhai_fn(get = "end", name = "end", pure)]
pub fn end_inclusive(range: &mut InclusiveRange) -> INT {
*range.end()
}
#[rhai_fn(get = "is_inclusive", name = "is_inclusive", pure)]
pub fn is_inclusive_inclusive(range: &mut InclusiveRange) -> bool {
let _range = range;
true
}
#[rhai_fn(get = "is_exclusive", name = "is_exclusive", pure)]
pub fn is_exclusive_inclusive(range: &mut InclusiveRange) -> bool {
let _range = range;
false
}
}

View File

@ -1,20 +1,33 @@
use crate::def_package;
use crate::plugin::*;
use crate::types::dynamic::Tag;
use crate::{Dynamic, EvalAltResult, INT};
use crate::{Dynamic, RhaiResultOf, ERR, INT};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
def_package! {
/// Package of core language features.
crate::LanguageCorePackage => |lib| {
lib.standard = true;
combine_with_exported_module!(lib, "language_core", core_functions);
}
}
#[export_module]
mod core_functions {
#[rhai_fn(name = "!")]
pub fn not(x: bool) -> bool {
!x
}
#[rhai_fn(name = "tag", get = "tag", pure)]
pub fn get_tag(value: &mut Dynamic) -> INT {
value.tag() as INT
}
#[rhai_fn(name = "set_tag", set = "tag", return_raw)]
pub fn set_tag(value: &mut Dynamic, tag: INT) -> Result<(), Box<EvalAltResult>> {
pub fn set_tag(value: &mut Dynamic, tag: INT) -> RhaiResultOf<()> {
if tag < Tag::MIN as INT {
Err(EvalAltResult::ErrorArithmetic(
Err(ERR::ErrorArithmetic(
format!(
"{} is too small to fit into a tag (must be between {} and {})",
tag,
@ -25,7 +38,7 @@ mod core_functions {
)
.into())
} else if tag > Tag::MAX as INT {
Err(EvalAltResult::ErrorArithmetic(
Err(ERR::ErrorArithmetic(
format!(
"{} is too large to fit into a tag (must be between {} and {})",
tag,
@ -41,9 +54,3 @@ mod core_functions {
}
}
}
def_package!(crate:LanguageCorePackage:"Language core functions.", lib, {
lib.standard = true;
combine_with_exported_module!(lib, "language_core", core_functions);
});

View File

@ -1,7 +1,7 @@
#![allow(non_snake_case)]
use crate::plugin::*;
use crate::{def_package, EvalAltResult, ExclusiveRange, InclusiveRange, INT};
use crate::{def_package, INT};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -37,38 +37,32 @@ macro_rules! reg_functions {
)* }
}
def_package!(crate:LogicPackage:"Logical operators.", lib, {
lib.standard = true;
def_package! {
/// Package of basic logic operators.
crate::LogicPackage => |lib| {
lib.standard = true;
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_functions!(lib += numbers; i8, u8, i16, u16, i32, u32, u64);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_functions!(lib += numbers; i8, u8, i16, u16, i32, u32, u64);
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
reg_functions!(lib += num_128; i128, u128);
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
reg_functions!(lib += num_128; i128, u128);
}
#[cfg(not(feature = "no_float"))]
{
#[cfg(not(feature = "f32_float"))]
reg_functions!(lib += float; f32);
combine_with_exported_module!(lib, "f32", f32_functions);
#[cfg(feature = "f32_float")]
reg_functions!(lib += float; f64);
combine_with_exported_module!(lib, "f64", f64_functions);
}
}
#[cfg(not(feature = "no_float"))]
{
#[cfg(not(feature = "f32_float"))]
reg_functions!(lib += float; f32);
combine_with_exported_module!(lib, "f32", f32_functions);
#[cfg(feature = "f32_float")]
reg_functions!(lib += float; f64);
combine_with_exported_module!(lib, "f64", f64_functions);
}
set_exported_fn!(lib, "!", not);
combine_with_exported_module!(lib, "bit_field", bit_field_functions);
});
// Logic operators
#[export_fn]
fn not(x: bool) -> bool {
!x
}
#[cfg(not(feature = "only_i32"))]
@ -77,7 +71,8 @@ gen_cmp_functions!(numbers => i8, u8, i16, u16, i32, u32, u64);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
gen_cmp_functions!(num_128 => i128, u128);
#[cfg(not(feature = "no_float"))]
@ -193,194 +188,3 @@ mod f64_functions {
(x as f64) <= (y as f64)
}
}
#[export_module]
mod bit_field_functions {
const BITS: usize = std::mem::size_of::<INT>() * 8;
#[rhai_fn(return_raw)]
pub fn get_bit(value: INT, index: INT) -> Result<bool, Box<EvalAltResult>> {
if index >= 0 {
let offset = index as usize;
if offset >= BITS {
Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into())
} else {
Ok((value & (1 << offset)) != 0)
}
} else if let Some(abs_index) = index.checked_abs() {
let offset = abs_index as usize;
// Count from end if negative
if offset > BITS {
Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into())
} else {
Ok((value & (1 << (BITS - offset))) != 0)
}
} else {
Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into())
}
}
#[rhai_fn(return_raw)]
pub fn set_bit(value: &mut INT, index: INT, new_value: bool) -> Result<(), Box<EvalAltResult>> {
if index >= 0 {
let offset = index as usize;
if offset >= BITS {
Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into())
} else {
let mask = 1 << offset;
if new_value {
*value |= mask;
} else {
*value &= !mask;
}
Ok(())
}
} else if let Some(abs_index) = index.checked_abs() {
let offset = abs_index as usize;
// Count from end if negative
if offset > BITS {
Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into())
} else {
let mask = 1 << offset;
if new_value {
*value |= mask;
} else {
*value &= !mask;
}
Ok(())
}
} else {
Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into())
}
}
#[rhai_fn(name = "get_bits", return_raw)]
pub fn get_bits_range(value: INT, range: ExclusiveRange) -> Result<INT, Box<EvalAltResult>> {
let from = INT::max(range.start, 0);
let to = INT::max(range.end, from);
get_bits(value, from, to - from)
}
#[rhai_fn(name = "get_bits", return_raw)]
pub fn get_bits_range_inclusive(
value: INT,
range: InclusiveRange,
) -> Result<INT, Box<EvalAltResult>> {
let from = INT::max(*range.start(), 0);
let to = INT::max(*range.end(), from - 1);
get_bits(value, from, to - from + 1)
}
#[rhai_fn(return_raw)]
pub fn get_bits(value: INT, index: INT, bits: INT) -> Result<INT, Box<EvalAltResult>> {
if bits < 1 {
return Ok(0);
}
let offset = if index >= 0 {
let offset = index as usize;
if offset >= BITS {
return Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into());
}
offset
} else if let Some(abs_index) = index.checked_abs() {
let offset = abs_index as usize;
// Count from end if negative
if offset > BITS {
return Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into());
}
BITS - offset
} else {
return Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into());
};
let bits = if offset + bits as usize > BITS {
BITS - offset
} else {
bits as usize
};
let mut base = 1;
let mut mask = 0;
for _ in 0..bits {
mask |= base;
base <<= 1;
}
Ok(((value & (mask << index)) >> index) & mask)
}
#[rhai_fn(name = "set_bits", return_raw)]
pub fn set_bits_range(
value: &mut INT,
range: ExclusiveRange,
new_value: INT,
) -> Result<(), Box<EvalAltResult>> {
let from = INT::max(range.start, 0);
let to = INT::max(range.end, from);
set_bits(value, from, to - from, new_value)
}
#[rhai_fn(name = "set_bits", return_raw)]
pub fn set_bits_range_inclusive(
value: &mut INT,
range: InclusiveRange,
new_value: INT,
) -> Result<(), Box<EvalAltResult>> {
let from = INT::max(*range.start(), 0);
let to = INT::max(*range.end(), from - 1);
set_bits(value, from, to - from + 1, new_value)
}
#[rhai_fn(return_raw)]
pub fn set_bits(
value: &mut INT,
index: INT,
bits: INT,
new_value: INT,
) -> Result<(), Box<EvalAltResult>> {
if bits < 1 {
return Ok(());
}
let offset = if index >= 0 {
let offset = index as usize;
if offset >= BITS {
return Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into());
}
offset
} else if let Some(abs_index) = index.checked_abs() {
let offset = abs_index as usize;
// Count from end if negative
if offset > BITS {
return Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into());
}
BITS - offset
} else {
return Err(EvalAltResult::ErrorBitFieldBounds(BITS, index, Position::NONE).into());
};
let bits = if offset + bits as usize > BITS {
BITS - offset
} else {
bits as usize
};
let mut base = 1;
let mut mask = 0;
for _ in 0..bits {
mask |= base;
base <<= 1;
}
*value &= !(mask << index);
*value |= (new_value & mask) << index;
Ok(())
}
}

View File

@ -2,29 +2,24 @@
use crate::engine::OP_EQUALS;
use crate::plugin::*;
use crate::{def_package, Dynamic, ImmutableString, Map, INT};
use crate::{def_package, Dynamic, ImmutableString, Map, RhaiResultOf, INT};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
#[cfg(not(feature = "no_index"))]
use crate::Array;
def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
lib.standard = true;
def_package! {
/// Package of basic object map utilities.
crate::BasicMapPackage => |lib| {
lib.standard = true;
combine_with_exported_module!(lib, "map", map_functions);
});
combine_with_exported_module!(lib, "map", map_functions);
}
}
#[export_module]
mod map_functions {
#[rhai_fn(name = "has", pure)]
pub fn contains(map: &mut Map, prop: ImmutableString) -> bool {
if map.is_empty() {
false
} else {
map.contains_key(prop.as_str())
}
}
#[rhai_fn(pure)]
pub fn len(map: &mut Map) -> INT {
map.len() as INT
@ -71,11 +66,7 @@ mod map_functions {
}
}
#[rhai_fn(name = "==", return_raw, pure)]
pub fn equals(
ctx: NativeCallContext,
map1: &mut Map,
map2: Map,
) -> Result<bool, Box<EvalAltResult>> {
pub fn equals(ctx: NativeCallContext, map1: &mut Map, map2: Map) -> RhaiResultOf<bool> {
if map1.len() != map2.len() {
return Ok(false);
}
@ -101,11 +92,7 @@ mod map_functions {
Ok(true)
}
#[rhai_fn(name = "!=", return_raw, pure)]
pub fn not_equals(
ctx: NativeCallContext,
map1: &mut Map,
map2: Map,
) -> Result<bool, Box<EvalAltResult>> {
pub fn not_equals(ctx: NativeCallContext, map1: &mut Map, map2: Map) -> RhaiResultOf<bool> {
equals(ctx, map1, map2).map(|r| !r)
}
@ -115,7 +102,7 @@ mod map_functions {
if map.is_empty() {
Array::new()
} else {
map.keys().cloned().map(Into::<Dynamic>::into).collect()
map.keys().cloned().map(Into::into).collect()
}
}
#[cfg(not(feature = "no_index"))]

View File

@ -1,7 +1,7 @@
#![allow(non_snake_case)]
use crate::plugin::*;
use crate::{def_package, Position, INT};
use crate::{def_package, Position, RhaiResultOf, ERR, INT, UNSIGNED_INT};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -18,13 +18,6 @@ use rust_decimal::Decimal;
#[cfg(feature = "decimal")]
use super::arithmetic::make_err;
#[allow(dead_code)]
#[cfg(feature = "only_i32")]
pub const MAX_INT: INT = i32::MAX;
#[allow(dead_code)]
#[cfg(not(feature = "only_i32"))]
pub const MAX_INT: INT = i64::MAX;
macro_rules! gen_conversion_as_functions {
($root:ident => $func_name:ident ( $($arg_type:ident),+ ) -> $result_type:ty) => {
pub mod $root { $(pub mod $arg_type {
@ -58,78 +51,85 @@ macro_rules! reg_functions {
)* }
}
def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
lib.standard = true;
def_package! {
/// Basic mathematical package.
crate::BasicMathPackage => |lib| {
lib.standard = true;
// Integer functions
combine_with_exported_module!(lib, "int", int_functions);
// Integer functions
combine_with_exported_module!(lib, "int", int_functions);
reg_functions!(lib += basic_to_int::to_int(char));
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_functions!(lib += numbers_to_int::to_int(i8, u8, i16, u16, i32, u32, i64, u64));
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
reg_functions!(lib += num_128_to_int::to_int(i128, u128));
}
#[cfg(not(feature = "no_float"))]
{
// Floating point functions
combine_with_exported_module!(lib, "float", float_functions);
// Trig functions
combine_with_exported_module!(lib, "trig", trig_functions);
reg_functions!(lib += basic_to_float::to_float(INT));
reg_functions!(lib += basic_to_int::to_int(char));
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_functions!(lib += numbers_to_float::to_float(i8, u8, i16, u16, i32, u32, i64, u32));
reg_functions!(lib += numbers_to_int::to_int(i8, u8, i16, u16, i32, u32, i64, u64));
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
reg_functions!(lib += num_128_to_float::to_float(i128, u128));
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
reg_functions!(lib += num_128_to_int::to_int(i128, u128));
}
#[cfg(not(feature = "no_float"))]
{
// Floating point functions
combine_with_exported_module!(lib, "float", float_functions);
// Trig functions
combine_with_exported_module!(lib, "trig", trig_functions);
reg_functions!(lib += basic_to_float::to_float(INT));
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_functions!(lib += numbers_to_float::to_float(i8, u8, i16, u16, i32, u32, i64, u32));
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
reg_functions!(lib += num_128_to_float::to_float(i128, u128));
}
}
// Decimal functions
#[cfg(feature = "decimal")]
{
combine_with_exported_module!(lib, "decimal", decimal_functions);
reg_functions!(lib += basic_to_decimal::to_decimal(INT));
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
reg_functions!(lib += numbers_to_decimal::to_decimal(i8, u8, i16, u16, i32, u32, i64, u64));
}
}
// Decimal functions
#[cfg(feature = "decimal")]
{
combine_with_exported_module!(lib, "decimal", decimal_functions);
reg_functions!(lib += basic_to_decimal::to_decimal(INT));
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
reg_functions!(lib += numbers_to_decimal::to_decimal(i8, u8, i16, u16, i32, u32, i64, u64));
}
});
}
#[export_module]
mod int_functions {
#[rhai_fn(name = "parse_int", return_raw)]
pub fn parse_int_radix(string: &str, radix: INT) -> Result<INT, Box<EvalAltResult>> {
pub fn parse_int_radix(string: &str, radix: INT) -> RhaiResultOf<INT> {
if !(2..=36).contains(&radix) {
return Err(EvalAltResult::ErrorArithmetic(
return Err(ERR::ErrorArithmetic(
format!("Invalid radix: '{}'", radix),
Position::NONE,
)
.into());
}
INT::from_str_radix(string.trim(), radix as u32).map_err(|err| {
EvalAltResult::ErrorArithmetic(
format!("Error parsing integer number '{}': {}", string, err),
Position::NONE,
)
.into()
})
UNSIGNED_INT::from_str_radix(string.trim(), radix as u32)
.map(|v| v as INT)
.map_err(|err| {
ERR::ErrorArithmetic(
format!("Error parsing integer number '{}': {}", string, err),
Position::NONE,
)
.into()
})
}
#[rhai_fn(name = "parse_int", return_raw)]
pub fn parse_int(string: &str) -> Result<INT, Box<EvalAltResult>> {
pub fn parse_int(string: &str) -> RhaiResultOf<INT> {
parse_int_radix(string, 10)
}
}
@ -258,33 +258,31 @@ mod float_functions {
x.is_infinite()
}
#[rhai_fn(name = "to_int", return_raw)]
pub fn f32_to_int(x: f32) -> Result<INT, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) && x > (MAX_INT as f32) {
Err(EvalAltResult::ErrorArithmetic(
format!("Integer overflow: to_int({})", x),
Position::NONE,
pub fn f32_to_int(x: f32) -> RhaiResultOf<INT> {
if cfg!(not(feature = "unchecked")) && x > (INT::MAX as f32) {
Err(
ERR::ErrorArithmetic(format!("Integer overflow: to_int({})", x), Position::NONE)
.into(),
)
.into())
} else {
Ok(x.trunc() as INT)
}
}
#[rhai_fn(name = "to_int", return_raw)]
pub fn f64_to_int(x: f64) -> Result<INT, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) && x > (MAX_INT as f64) {
Err(EvalAltResult::ErrorArithmetic(
format!("Integer overflow: to_int({})", x),
Position::NONE,
pub fn f64_to_int(x: f64) -> RhaiResultOf<INT> {
if cfg!(not(feature = "unchecked")) && x > (INT::MAX as f64) {
Err(
ERR::ErrorArithmetic(format!("Integer overflow: to_int({})", x), Position::NONE)
.into(),
)
.into())
} else {
Ok(x.trunc() as INT)
}
}
#[rhai_fn(return_raw)]
pub fn parse_float(string: &str) -> Result<FLOAT, Box<EvalAltResult>> {
pub fn parse_float(string: &str) -> RhaiResultOf<FLOAT> {
string.trim().parse::<FLOAT>().map_err(|err| {
EvalAltResult::ErrorArithmetic(
ERR::ErrorArithmetic(
format!("Error parsing floating-point number '{}': {}", string, err),
Position::NONE,
)
@ -320,7 +318,7 @@ mod decimal_functions {
}
#[cfg(feature = "no_float")]
#[rhai_fn(return_raw)]
pub fn parse_float(s: &str) -> Result<Decimal, Box<EvalAltResult>> {
pub fn parse_float(s: &str) -> RhaiResultOf<Decimal> {
parse_decimal(s)
}
@ -334,12 +332,12 @@ mod decimal_functions {
x.tan()
}
#[rhai_fn(return_raw)]
pub fn sqrt(x: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
pub fn sqrt(x: Decimal) -> RhaiResultOf<Decimal> {
x.sqrt()
.ok_or_else(|| make_err(format!("Error taking the square root of {}", x,)))
}
#[rhai_fn(return_raw)]
pub fn exp(x: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
pub fn exp(x: Decimal) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) {
x.checked_exp()
.ok_or_else(|| make_err(format!("Exponential overflow: e ** {}", x,)))
@ -348,7 +346,7 @@ mod decimal_functions {
}
}
#[rhai_fn(return_raw)]
pub fn ln(x: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
pub fn ln(x: Decimal) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) {
x.checked_ln()
.ok_or_else(|| make_err(format!("Error taking the natural log of {}", x)))
@ -357,7 +355,7 @@ mod decimal_functions {
}
}
#[rhai_fn(name = "log", return_raw)]
pub fn log10(x: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
pub fn log10(x: Decimal) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) {
x.checked_log10()
.ok_or_else(|| make_err(format!("Error taking the log of {}", x)))
@ -378,7 +376,7 @@ mod decimal_functions {
x.round()
}
#[rhai_fn(name = "round", return_raw)]
pub fn round_dp(x: Decimal, dp: INT) -> Result<Decimal, Box<EvalAltResult>> {
pub fn round_dp(x: Decimal, dp: INT) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) {
if dp < 0 {
return Err(make_err(format!(
@ -394,7 +392,7 @@ mod decimal_functions {
Ok(x.round_dp(dp as u32))
}
#[rhai_fn(return_raw)]
pub fn round_up(x: Decimal, dp: INT) -> Result<Decimal, Box<EvalAltResult>> {
pub fn round_up(x: Decimal, dp: INT) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) {
if dp < 0 {
return Err(make_err(format!(
@ -410,7 +408,7 @@ mod decimal_functions {
Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::AwayFromZero))
}
#[rhai_fn(return_raw)]
pub fn round_down(x: Decimal, dp: INT) -> Result<Decimal, Box<EvalAltResult>> {
pub fn round_down(x: Decimal, dp: INT) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) {
if dp < 0 {
return Err(make_err(format!(
@ -426,7 +424,7 @@ mod decimal_functions {
Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::ToZero))
}
#[rhai_fn(return_raw)]
pub fn round_half_up(x: Decimal, dp: INT) -> Result<Decimal, Box<EvalAltResult>> {
pub fn round_half_up(x: Decimal, dp: INT) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) {
if dp < 0 {
return Err(make_err(format!(
@ -442,7 +440,7 @@ mod decimal_functions {
Ok(x.round_dp_with_strategy(dp as u32, RoundingStrategy::MidpointAwayFromZero))
}
#[rhai_fn(return_raw)]
pub fn round_half_down(x: Decimal, dp: INT) -> Result<Decimal, Box<EvalAltResult>> {
pub fn round_half_down(x: Decimal, dp: INT) -> RhaiResultOf<Decimal> {
if cfg!(not(feature = "unchecked")) {
if dp < 0 {
return Err(make_err(format!(
@ -466,11 +464,11 @@ mod decimal_functions {
x.fract()
}
#[rhai_fn(return_raw)]
pub fn parse_decimal(string: &str) -> Result<Decimal, Box<EvalAltResult>> {
pub fn parse_decimal(string: &str) -> RhaiResultOf<Decimal> {
Decimal::from_str(string)
.or_else(|_| Decimal::from_scientific(string))
.map_err(|err| {
EvalAltResult::ErrorArithmetic(
ERR::ErrorArithmetic(
format!("Error parsing decimal number '{}': {}", string, err),
Position::NONE,
)
@ -480,9 +478,9 @@ mod decimal_functions {
#[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "to_decimal", return_raw)]
pub fn f32_to_decimal(x: f32) -> Result<Decimal, Box<EvalAltResult>> {
pub fn f32_to_decimal(x: f32) -> RhaiResultOf<Decimal> {
Decimal::try_from(x).map_err(|_| {
EvalAltResult::ErrorArithmetic(
ERR::ErrorArithmetic(
format!("Cannot convert to Decimal: to_decimal({})", x),
Position::NONE,
)
@ -491,9 +489,9 @@ mod decimal_functions {
}
#[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "to_decimal", return_raw)]
pub fn f64_to_decimal(x: f64) -> Result<Decimal, Box<EvalAltResult>> {
pub fn f64_to_decimal(x: f64) -> RhaiResultOf<Decimal> {
Decimal::try_from(x).map_err(|_| {
EvalAltResult::ErrorArithmetic(
ERR::ErrorArithmetic(
format!("Cannot convert to Decimal: to_decimal({})", x),
Position::NONE,
)
@ -502,9 +500,9 @@ mod decimal_functions {
}
#[cfg(not(feature = "no_float"))]
#[rhai_fn(return_raw)]
pub fn to_float(x: Decimal) -> Result<FLOAT, Box<EvalAltResult>> {
pub fn to_float(x: Decimal) -> RhaiResultOf<FLOAT> {
FLOAT::try_from(x).map_err(|_| {
EvalAltResult::ErrorArithmetic(
ERR::ErrorArithmetic(
format!("Cannot convert to floating-point: to_float({})", x),
Position::NONE,
)
@ -524,7 +522,8 @@ gen_conversion_as_functions!(numbers_to_float => to_float (i8, u8, i16, u16, i32
#[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
gen_conversion_as_functions!(num_128_to_float => to_float (i128, u128) -> FLOAT);
gen_conversion_as_functions!(basic_to_int => to_int (char) -> INT);
@ -535,7 +534,8 @@ gen_conversion_as_functions!(numbers_to_int => to_int (i8, u8, i16, u16, i32, u3
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
gen_conversion_as_functions!(num_128_to_int => to_int (i128, u128) -> INT);
#[cfg(feature = "decimal")]

View File

@ -3,8 +3,9 @@
use crate::{Module, Shared};
pub(crate) mod arithmetic;
mod array_basic;
mod blob_basic;
pub(crate) mod array_basic;
mod bit_field;
pub(crate) mod blob_basic;
mod fn_basic;
mod iter_basic;
mod lang_core;
@ -20,10 +21,12 @@ mod time_basic;
pub use arithmetic::ArithmeticPackage;
#[cfg(not(feature = "no_index"))]
pub use array_basic::BasicArrayPackage;
pub use bit_field::BitFieldPackage;
#[cfg(not(feature = "no_index"))]
pub use blob_basic::BasicBlobPackage;
pub use fn_basic::BasicFnPackage;
pub use iter_basic::BasicIteratorPackage;
pub use lang_core::LanguageCorePackage;
pub use logic::LogicPackage;
#[cfg(not(feature = "no_object"))]
pub use map_basic::BasicMapPackage;
@ -69,8 +72,8 @@ pub trait Package {
/// ```
#[macro_export]
macro_rules! def_package {
($root:ident : $package:ident : $comment:expr , $lib:ident , $block:stmt) => {
#[doc=$comment]
($($(#[$outer:meta])* $root:ident :: $package:ident => | $lib:ident | $block:block)+) => { $(
$(#[$outer])*
pub struct $package($root::Shared<$root::Module>);
impl $root::packages::Package for $package {
@ -88,6 +91,42 @@ macro_rules! def_package {
}
}
impl $package {
pub fn new() -> Self {
let mut module = $root::Module::new();
<Self as $root::packages::Package>::init(&mut module);
module.build_index();
Self(module.into())
}
}
)* };
($root:ident : $package:ident : $comment:expr , $lib:ident , $block:stmt) => {
#[deprecated(since = "1.4.0", note = "this is an old syntax of `def_package!` and is deprecated; use the new syntax of `def_package!` instead")]
#[doc=$comment]
///
/// # Deprecated
///
/// This old syntax of `def_package!` is deprecated. Use the new syntax instead.
///
/// This syntax will be removed in the next major version.
pub struct $package($root::Shared<$root::Module>);
impl $root::packages::Package for $package {
fn as_shared_module(&self) -> $root::Shared<$root::Module> {
#[allow(deprecated)]
self.0.clone()
}
fn init($lib: &mut $root::Module) {
$block
}
}
impl Default for $package {
fn default() -> Self {
Self::new()
}
}
impl $package {
pub fn new() -> Self {
let mut module = $root::Module::new();

View File

@ -1,21 +1,25 @@
use super::arithmetic::ArithmeticPackage;
use super::fn_basic::BasicFnPackage;
use super::iter_basic::BasicIteratorPackage;
use super::lang_core::LanguageCorePackage;
use super::logic::LogicPackage;
use super::string_basic::BasicStringPackage;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use crate::def_package;
def_package!(crate:CorePackage:"_Core_ package containing basic facilities.", lib, {
lib.standard = true;
def_package! {
/// Core package containing basic facilities.
///
/// # Contents
///
/// * [`LanguageCorePackage`][super::LanguageCorePackage]
/// * [`ArithmeticPackage`][super::ArithmeticPackage]
/// * [`BasicStringPackage`][super::BasicStringPackage]
/// * [`BasicIteratorPackage`][super::BasicIteratorPackage]
/// * [`BasicFnPackage`][super::BasicFnPackage]
crate::CorePackage => |lib| {
lib.standard = true;
LanguageCorePackage::init(lib);
ArithmeticPackage::init(lib);
LogicPackage::init(lib);
BasicStringPackage::init(lib);
BasicIteratorPackage::init(lib);
BasicFnPackage::init(lib);
});
super::LanguageCorePackage::init(lib);
super::ArithmeticPackage::init(lib);
super::BasicStringPackage::init(lib);
super::BasicIteratorPackage::init(lib);
super::BasicFnPackage::init(lib);
}
}

View File

@ -1,32 +1,37 @@
#[cfg(not(feature = "no_index"))]
use super::array_basic::BasicArrayPackage;
#[cfg(not(feature = "no_index"))]
use super::blob_basic::BasicBlobPackage;
#[cfg(not(feature = "no_object"))]
use super::map_basic::BasicMapPackage;
use super::math_basic::BasicMathPackage;
use super::pkg_core::CorePackage;
use super::string_more::MoreStringPackage;
#[cfg(not(feature = "no_std"))]
use super::time_basic::BasicTimePackage;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use crate::def_package;
def_package!(crate:StandardPackage:"_Standard_ package containing all built-in features.", lib, {
lib.standard = true;
def_package! {
/// Standard package containing all built-in features.
///
/// # Contents
///
/// * [`CorePackage`][super::CorePackage]
/// * [`BitFieldPackage`][super::BitFieldPackage]
/// * [`LogicPackage`][super::LogicPackage]
/// * [`BasicMathPackage`][super::BasicMathPackage]
/// * [`BasicArrayPackage`][super::BasicArrayPackage]
/// * [`BasicBlobPackage`][super::BasicBlobPackage]
/// * [`BasicMapPackage`][super::BasicMapPackage]
/// * [`BasicTimePackage`][super::BasicTimePackage]
/// * [`MoreStringPackage`][super::MoreStringPackage]
crate::StandardPackage => |lib| {
lib.standard = true;
CorePackage::init(lib);
BasicMathPackage::init(lib);
#[cfg(not(feature = "no_index"))]
{
BasicArrayPackage::init(lib);
BasicBlobPackage::init(lib);
super::CorePackage::init(lib);
super::BitFieldPackage::init(lib);
super::LogicPackage::init(lib);
super::BasicMathPackage::init(lib);
#[cfg(not(feature = "no_index"))]
super::BasicArrayPackage::init(lib);
#[cfg(not(feature = "no_index"))]
super::BasicBlobPackage::init(lib);
#[cfg(not(feature = "no_object"))]
super::BasicMapPackage::init(lib);
#[cfg(not(feature = "no_std"))]
super::BasicTimePackage::init(lib);
super::MoreStringPackage::init(lib);
}
#[cfg(not(feature = "no_object"))]
BasicMapPackage::init(lib);
#[cfg(not(feature = "no_std"))]
BasicTimePackage::init(lib);
MoreStringPackage::init(lib);
});
}

View File

@ -15,12 +15,15 @@ use crate::Map;
pub const FUNC_TO_STRING: &str = "to_string";
pub const FUNC_TO_DEBUG: &str = "to_debug";
def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, {
lib.standard = true;
def_package! {
/// Package of basic string utilities (e.g. printing)
crate::BasicStringPackage => |lib| {
lib.standard = true;
combine_with_exported_module!(lib, "print_debug", print_debug_functions);
combine_with_exported_module!(lib, "number_formatting", number_formatting);
});
combine_with_exported_module!(lib, "print_debug", print_debug_functions);
combine_with_exported_module!(lib, "number_formatting", number_formatting);
}
}
// Register print and debug
@ -271,7 +274,8 @@ mod number_formatting {
to_binary(value)
}
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
pub mod num_128 {
#[rhai_fn(name = "to_hex")]
pub fn u128_to_hex(value: u128) -> ImmutableString {

View File

@ -1,18 +1,21 @@
#![allow(non_snake_case)]
use crate::plugin::*;
use crate::{def_package, Dynamic, ExclusiveRange, InclusiveRange, StaticVec, INT};
use crate::{def_package, Dynamic, ExclusiveRange, InclusiveRange, RhaiResultOf, StaticVec, INT};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{any::TypeId, mem};
use super::string_basic::{print_with_func, FUNC_TO_STRING};
def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, {
lib.standard = true;
def_package! {
/// Package of additional string utilities over [`BasicStringPackage`][super::BasicStringPackage]
crate::MoreStringPackage => |lib| {
lib.standard = true;
combine_with_exported_module!(lib, "string", string_functions);
});
combine_with_exported_module!(lib, "string", string_functions);
}
}
#[export_module]
mod string_functions {
@ -175,7 +178,7 @@ mod string_functions {
#[rhai_fn(name = "to_upper")]
pub fn to_upper_char(character: char) -> char {
let mut stream = character.to_uppercase();
let ch = stream.next().expect("not empty");
let ch = stream.next().unwrap();
if stream.next().is_some() {
character
} else {
@ -189,7 +192,7 @@ mod string_functions {
#[rhai_fn(name = "to_lower")]
pub fn to_lower_char(character: char) -> char {
let mut stream = character.to_lowercase();
let ch = stream.next().expect("not empty");
let ch = stream.next().unwrap();
if stream.next().is_some() {
character
} else {
@ -497,7 +500,7 @@ mod string_functions {
string: &mut ImmutableString,
len: INT,
character: char,
) -> Result<(), Box<crate::EvalAltResult>> {
) -> RhaiResultOf<()> {
if len <= 0 {
return Ok(());
}
@ -506,7 +509,7 @@ mod string_functions {
// Check if string will be over max size limit
#[cfg(not(feature = "unchecked"))]
if _ctx.engine().max_string_size() > 0 && len as usize > _ctx.engine().max_string_size() {
return Err(crate::EvalAltResult::ErrorDataTooLarge(
return Err(crate::ERR::ErrorDataTooLarge(
"Length of string".to_string(),
crate::Position::NONE,
)
@ -525,7 +528,7 @@ mod string_functions {
#[cfg(not(feature = "unchecked"))]
if _ctx.engine().max_string_size() > 0 && string.len() > _ctx.engine().max_string_size()
{
return Err(crate::EvalAltResult::ErrorDataTooLarge(
return Err(crate::ERR::ErrorDataTooLarge(
"Length of string".to_string(),
crate::Position::NONE,
)
@ -541,7 +544,7 @@ mod string_functions {
string: &mut ImmutableString,
len: INT,
padding: &str,
) -> Result<(), Box<crate::EvalAltResult>> {
) -> RhaiResultOf<()> {
if len <= 0 {
return Ok(());
}
@ -550,7 +553,7 @@ mod string_functions {
// Check if string will be over max size limit
#[cfg(not(feature = "unchecked"))]
if _ctx.engine().max_string_size() > 0 && len as usize > _ctx.engine().max_string_size() {
return Err(crate::EvalAltResult::ErrorDataTooLarge(
return Err(crate::ERR::ErrorDataTooLarge(
"Length of string".to_string(),
crate::Position::NONE,
)
@ -576,7 +579,7 @@ mod string_functions {
#[cfg(not(feature = "unchecked"))]
if _ctx.engine().max_string_size() > 0 && string.len() > _ctx.engine().max_string_size()
{
return Err(crate::EvalAltResult::ErrorDataTooLarge(
return Err(crate::ERR::ErrorDataTooLarge(
"Length of string".to_string(),
crate::Position::NONE,
)
@ -596,7 +599,7 @@ mod string_functions {
if string.is_empty() {
Array::new()
} else {
string.chars().map(Into::<Dynamic>::into).collect()
string.chars().map(Into::into).collect()
}
}
#[rhai_fn(name = "split")]
@ -621,57 +624,39 @@ mod string_functions {
}
}
pub fn split(string: &str, delimiter: &str) -> Array {
string.split(delimiter).map(Into::<Dynamic>::into).collect()
string.split(delimiter).map(Into::into).collect()
}
#[rhai_fn(name = "split")]
pub fn splitn(string: &str, delimiter: &str, segments: INT) -> Array {
let pieces: usize = if segments < 1 { 1 } else { segments as usize };
string
.splitn(pieces, delimiter)
.map(Into::<Dynamic>::into)
.collect()
string.splitn(pieces, delimiter).map(Into::into).collect()
}
#[rhai_fn(name = "split")]
pub fn split_char(string: &str, delimiter: char) -> Array {
string.split(delimiter).map(Into::<Dynamic>::into).collect()
string.split(delimiter).map(Into::into).collect()
}
#[rhai_fn(name = "split")]
pub fn splitn_char(string: &str, delimiter: char, segments: INT) -> Array {
let pieces: usize = if segments < 1 { 1 } else { segments as usize };
string
.splitn(pieces, delimiter)
.map(Into::<Dynamic>::into)
.collect()
string.splitn(pieces, delimiter).map(Into::into).collect()
}
#[rhai_fn(name = "split_rev")]
pub fn rsplit(string: &str, delimiter: &str) -> Array {
string
.rsplit(delimiter)
.map(Into::<Dynamic>::into)
.collect()
string.rsplit(delimiter).map(Into::into).collect()
}
#[rhai_fn(name = "split_rev")]
pub fn rsplitn(string: &str, delimiter: &str, segments: INT) -> Array {
let pieces: usize = if segments < 1 { 1 } else { segments as usize };
string
.rsplitn(pieces, delimiter)
.map(Into::<Dynamic>::into)
.collect()
string.rsplitn(pieces, delimiter).map(Into::into).collect()
}
#[rhai_fn(name = "split_rev")]
pub fn rsplit_char(string: &str, delimiter: char) -> Array {
string
.rsplit(delimiter)
.map(Into::<Dynamic>::into)
.collect()
string.rsplit(delimiter).map(Into::into).collect()
}
#[rhai_fn(name = "split_rev")]
pub fn rsplitn_char(string: &str, delimiter: char, segments: INT) -> Array {
let pieces: usize = if segments < 1 { 1 } else { segments as usize };
string
.rsplitn(pieces, delimiter)
.map(Into::<Dynamic>::into)
.collect()
string.rsplitn(pieces, delimiter).map(Into::into).collect()
}
}
}

View File

@ -1,24 +1,28 @@
#![cfg(not(feature = "no_std"))]
use super::{arithmetic::make_err as make_arithmetic_err, math_basic::MAX_INT};
use super::arithmetic::make_err as make_arithmetic_err;
use crate::plugin::*;
use crate::{def_package, Dynamic, EvalAltResult, INT};
use crate::{def_package, Dynamic, EvalAltResult, RhaiResult, RhaiResultOf, INT};
#[cfg(not(feature = "no_float"))]
use crate::FLOAT;
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[cfg(not(target_arch = "wasm32"))]
#[cfg(not(target_arch = "wasm64"))]
use std::time::{Duration, Instant};
#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
use instant::{Duration, Instant};
def_package!(crate:BasicTimePackage:"Basic timing utilities.", lib, {
lib.standard = true;
def_package! {
/// Package of basic timing utilities.
crate::BasicTimePackage => |lib| {
lib.standard = true;
// Register date/time functions
combine_with_exported_module!(lib, "time", time_functions);
});
// Register date/time functions
combine_with_exported_module!(lib, "time", time_functions);
}
}
#[export_module]
mod time_functions {
@ -27,7 +31,7 @@ mod time_functions {
}
#[rhai_fn(name = "elapsed", get = "elapsed", return_raw)]
pub fn elapsed(timestamp: Instant) -> Result<Dynamic, Box<EvalAltResult>> {
pub fn elapsed(timestamp: Instant) -> RhaiResult {
#[cfg(not(feature = "no_float"))]
if timestamp > Instant::now() {
Err(make_arithmetic_err("Time-stamp is later than now"))
@ -39,7 +43,7 @@ mod time_functions {
{
let seconds = timestamp.elapsed().as_secs();
if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) {
if cfg!(not(feature = "unchecked")) && seconds > (INT::MAX as u64) {
Err(make_arithmetic_err(format!(
"Integer overflow for timestamp.elapsed: {}",
seconds
@ -53,10 +57,7 @@ mod time_functions {
}
#[rhai_fn(return_raw, name = "-")]
pub fn time_diff(
timestamp1: Instant,
timestamp2: Instant,
) -> Result<Dynamic, Box<EvalAltResult>> {
pub fn time_diff(timestamp1: Instant, timestamp2: Instant) -> RhaiResult {
#[cfg(not(feature = "no_float"))]
return Ok(if timestamp2 > timestamp1 {
-(timestamp2 - timestamp1).as_secs_f64() as FLOAT
@ -69,7 +70,7 @@ mod time_functions {
if timestamp2 > timestamp1 {
let seconds = (timestamp2 - timestamp1).as_secs();
if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) {
if cfg!(not(feature = "unchecked")) && seconds > (INT::MAX as u64) {
Err(make_arithmetic_err(format!(
"Integer overflow for timestamp duration: -{}",
seconds
@ -80,7 +81,7 @@ mod time_functions {
} else {
let seconds = (timestamp1 - timestamp2).as_secs();
if cfg!(not(feature = "unchecked")) && seconds > (MAX_INT as u64) {
if cfg!(not(feature = "unchecked")) && seconds > (INT::MAX as u64) {
Err(make_arithmetic_err(format!(
"Integer overflow for timestamp duration: {}",
seconds
@ -93,11 +94,11 @@ mod time_functions {
#[cfg(not(feature = "no_float"))]
pub mod float_functions {
fn add_impl(timestamp: Instant, seconds: FLOAT) -> Result<Instant, Box<EvalAltResult>> {
fn add_impl(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf<Instant> {
if seconds < 0.0 {
subtract_impl(timestamp, -seconds)
} else if cfg!(not(feature = "unchecked")) {
if seconds > (MAX_INT as FLOAT) {
if seconds > (INT::MAX as FLOAT) {
Err(make_arithmetic_err(format!(
"Integer overflow for timestamp add: {}",
seconds
@ -116,14 +117,11 @@ mod time_functions {
Ok(timestamp + Duration::from_millis((seconds * 1000.0) as u64))
}
}
fn subtract_impl(
timestamp: Instant,
seconds: FLOAT,
) -> Result<Instant, Box<EvalAltResult>> {
fn subtract_impl(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf<Instant> {
if seconds < 0.0 {
add_impl(timestamp, -seconds)
} else if cfg!(not(feature = "unchecked")) {
if seconds > (MAX_INT as FLOAT) {
if seconds > (INT::MAX as FLOAT) {
Err(make_arithmetic_err(format!(
"Integer overflow for timestamp add: {}",
seconds
@ -144,32 +142,26 @@ mod time_functions {
}
#[rhai_fn(return_raw, name = "+")]
pub fn add(timestamp: Instant, seconds: FLOAT) -> Result<Instant, Box<EvalAltResult>> {
pub fn add(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf<Instant> {
add_impl(timestamp, seconds)
}
#[rhai_fn(return_raw, name = "+=")]
pub fn add_assign(
timestamp: &mut Instant,
seconds: FLOAT,
) -> Result<(), Box<EvalAltResult>> {
pub fn add_assign(timestamp: &mut Instant, seconds: FLOAT) -> RhaiResultOf<()> {
*timestamp = add_impl(*timestamp, seconds)?;
Ok(())
}
#[rhai_fn(return_raw, name = "-")]
pub fn subtract(timestamp: Instant, seconds: FLOAT) -> Result<Instant, Box<EvalAltResult>> {
pub fn subtract(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf<Instant> {
subtract_impl(timestamp, seconds)
}
#[rhai_fn(return_raw, name = "-=")]
pub fn subtract_assign(
timestamp: &mut Instant,
seconds: FLOAT,
) -> Result<(), Box<EvalAltResult>> {
pub fn subtract_assign(timestamp: &mut Instant, seconds: FLOAT) -> RhaiResultOf<()> {
*timestamp = subtract_impl(*timestamp, seconds)?;
Ok(())
}
}
fn add_impl(timestamp: Instant, seconds: INT) -> Result<Instant, Box<EvalAltResult>> {
fn add_impl(timestamp: Instant, seconds: INT) -> RhaiResultOf<Instant> {
if seconds < 0 {
subtract_impl(timestamp, -seconds)
} else if cfg!(not(feature = "unchecked")) {
@ -185,7 +177,7 @@ mod time_functions {
Ok(timestamp + Duration::from_secs(seconds as u64))
}
}
fn subtract_impl(timestamp: Instant, seconds: INT) -> Result<Instant, Box<EvalAltResult>> {
fn subtract_impl(timestamp: Instant, seconds: INT) -> RhaiResultOf<Instant> {
if seconds < 0 {
add_impl(timestamp, -seconds)
} else if cfg!(not(feature = "unchecked")) {
@ -203,23 +195,20 @@ mod time_functions {
}
#[rhai_fn(return_raw, name = "+")]
pub fn add(timestamp: Instant, seconds: INT) -> Result<Instant, Box<EvalAltResult>> {
pub fn add(timestamp: Instant, seconds: INT) -> RhaiResultOf<Instant> {
add_impl(timestamp, seconds)
}
#[rhai_fn(return_raw, name = "+=")]
pub fn add_assign(timestamp: &mut Instant, seconds: INT) -> Result<(), Box<EvalAltResult>> {
pub fn add_assign(timestamp: &mut Instant, seconds: INT) -> RhaiResultOf<()> {
*timestamp = add_impl(*timestamp, seconds)?;
Ok(())
}
#[rhai_fn(return_raw, name = "-")]
pub fn subtract(timestamp: Instant, seconds: INT) -> Result<Instant, Box<EvalAltResult>> {
pub fn subtract(timestamp: Instant, seconds: INT) -> RhaiResultOf<Instant> {
subtract_impl(timestamp, seconds)
}
#[rhai_fn(return_raw, name = "-=")]
pub fn subtract_assign(
timestamp: &mut Instant,
seconds: INT,
) -> Result<(), Box<EvalAltResult>> {
pub fn subtract_assign(timestamp: &mut Instant, seconds: INT) -> RhaiResultOf<()> {
*timestamp = subtract_impl(*timestamp, seconds)?;
Ok(())
}

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
//! Implement deserialization support of [`Dynamic`][crate::Dynamic] for [`serde`].
use crate::types::dynamic::Union;
use crate::{Dynamic, EvalAltResult, ImmutableString, LexError, Position};
use serde::de::{DeserializeSeed, Error, IntoDeserializer, Visitor};
use crate::{Dynamic, ImmutableString, LexError, Position, RhaiError, RhaiResultOf, ERR};
use serde::de::{Error, IntoDeserializer, Visitor};
use serde::{Deserialize, Deserializer};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -26,12 +26,12 @@ impl<'de> DynamicDeserializer<'de> {
Self { value }
}
/// Shortcut for a type conversion error.
fn type_error<T>(&self) -> Result<T, Box<EvalAltResult>> {
fn type_error<T>(&self) -> RhaiResultOf<T> {
self.type_error_str(type_name::<T>())
}
/// Shortcut for a type conversion error.
fn type_error_str<T>(&self, error: &str) -> Result<T, Box<EvalAltResult>> {
Err(EvalAltResult::ErrorMismatchOutputType(
fn type_error_str<T>(&self, error: &str) -> RhaiResultOf<T> {
Err(ERR::ErrorMismatchOutputType(
error.into(),
self.value.type_name().into(),
Position::NONE,
@ -42,7 +42,7 @@ impl<'de> DynamicDeserializer<'de> {
&mut self,
v: crate::INT,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
) -> RhaiResultOf<V::Value> {
#[cfg(not(feature = "only_i32"))]
return visitor.visit_i64(v);
#[cfg(feature = "only_i32")]
@ -101,13 +101,11 @@ impl<'de> DynamicDeserializer<'de> {
/// # Ok(())
/// # }
/// ```
pub fn from_dynamic<'de, T: Deserialize<'de>>(
value: &'de Dynamic,
) -> Result<T, Box<EvalAltResult>> {
pub fn from_dynamic<'de, T: Deserialize<'de>>(value: &'de Dynamic) -> RhaiResultOf<T> {
T::deserialize(&mut DynamicDeserializer::from_dynamic(value))
}
impl Error for Box<EvalAltResult> {
impl Error for RhaiError {
fn custom<T: fmt::Display>(err: T) -> Self {
LexError::ImproperSymbol(String::new(), err.to_string())
.into_err(Position::NONE)
@ -116,9 +114,9 @@ impl Error for Box<EvalAltResult> {
}
impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
type Error = Box<EvalAltResult>;
type Error = RhaiError;
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
match self.value.0 {
Union::Unit(_, _, _) => self.deserialize_unit(visitor),
Union::Bool(_, _, _) => self.deserialize_bool(visitor),
@ -172,11 +170,11 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
}
}
fn deserialize_bool<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_bool<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
visitor.visit_bool(self.value.as_bool().or_else(|_| self.type_error())?)
}
fn deserialize_i8<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_i8<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
if let Ok(v) = self.value.as_int() {
self.deserialize_int(v, visitor)
} else {
@ -186,7 +184,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
}
}
fn deserialize_i16<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_i16<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
if let Ok(v) = self.value.as_int() {
self.deserialize_int(v, visitor)
} else {
@ -196,7 +194,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
}
}
fn deserialize_i32<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_i32<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
if let Ok(v) = self.value.as_int() {
self.deserialize_int(v, visitor)
} else if cfg!(feature = "only_i32") {
@ -208,7 +206,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
}
}
fn deserialize_i64<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_i64<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
if let Ok(v) = self.value.as_int() {
self.deserialize_int(v, visitor)
} else if cfg!(not(feature = "only_i32")) {
@ -220,7 +218,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
}
}
fn deserialize_i128<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_i128<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
if let Ok(v) = self.value.as_int() {
self.deserialize_int(v, visitor)
} else if cfg!(not(feature = "only_i32")) {
@ -232,7 +230,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
}
}
fn deserialize_u8<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_u8<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
if let Ok(v) = self.value.as_int() {
self.deserialize_int(v, visitor)
} else {
@ -242,7 +240,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
}
}
fn deserialize_u16<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_u16<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
if let Ok(v) = self.value.as_int() {
self.deserialize_int(v, visitor)
} else {
@ -252,7 +250,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
}
}
fn deserialize_u32<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_u32<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
if let Ok(v) = self.value.as_int() {
self.deserialize_int(v, visitor)
} else {
@ -262,7 +260,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
}
}
fn deserialize_u64<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_u64<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
if let Ok(v) = self.value.as_int() {
self.deserialize_int(v, visitor)
} else {
@ -272,7 +270,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
}
}
fn deserialize_u128<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_u128<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
if let Ok(v) = self.value.as_int() {
self.deserialize_int(v, visitor)
} else {
@ -282,7 +280,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
}
}
fn deserialize_f32<V: Visitor<'de>>(self, _visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_f32<V: Visitor<'de>>(self, _visitor: V) -> RhaiResultOf<V::Value> {
#[cfg(not(feature = "no_float"))]
return self
.value
@ -306,7 +304,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
return self.type_error_str("f32");
}
fn deserialize_f64<V: Visitor<'de>>(self, _visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_f64<V: Visitor<'de>>(self, _visitor: V) -> RhaiResultOf<V::Value> {
#[cfg(not(feature = "no_float"))]
return self
.value
@ -330,30 +328,24 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
return self.type_error_str("f64");
}
fn deserialize_char<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_char<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
self.value
.downcast_ref::<char>()
.map_or_else(|| self.type_error(), |&x| visitor.visit_char(x))
}
fn deserialize_str<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_str<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
self.value.downcast_ref::<ImmutableString>().map_or_else(
|| self.type_error(),
|x| visitor.visit_borrowed_str(x.as_str()),
)
}
fn deserialize_string<V: Visitor<'de>>(
self,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_string<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
self.deserialize_str(visitor)
}
fn deserialize_bytes<V: Visitor<'de>>(
self,
_visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_bytes<V: Visitor<'de>>(self, _visitor: V) -> RhaiResultOf<V::Value> {
#[cfg(not(feature = "no_index"))]
return self
.value
@ -364,17 +356,11 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
return self.type_error();
}
fn deserialize_byte_buf<V: Visitor<'de>>(
self,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_byte_buf<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
self.deserialize_bytes(visitor)
}
fn deserialize_option<V: Visitor<'de>>(
self,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
if self.value.is::<()>() {
visitor.visit_none()
} else {
@ -382,7 +368,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
}
}
fn deserialize_unit<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_unit<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
self.value
.downcast_ref::<()>()
.map_or_else(|| self.type_error(), |_| visitor.visit_unit())
@ -392,7 +378,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
) -> RhaiResultOf<V::Value> {
self.deserialize_unit(visitor)
}
@ -400,11 +386,11 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
self,
_name: &'static str,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
) -> RhaiResultOf<V::Value> {
visitor.visit_newtype_struct(self)
}
fn deserialize_seq<V: Visitor<'de>>(self, _visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_seq<V: Visitor<'de>>(self, _visitor: V) -> RhaiResultOf<V::Value> {
#[cfg(not(feature = "no_index"))]
return self.value.downcast_ref::<crate::Array>().map_or_else(
|| self.type_error(),
@ -415,11 +401,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
return self.type_error();
}
fn deserialize_tuple<V: Visitor<'de>>(
self,
_len: usize,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_tuple<V: Visitor<'de>>(self, _len: usize, visitor: V) -> RhaiResultOf<V::Value> {
self.deserialize_seq(visitor)
}
@ -428,11 +410,11 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
_name: &'static str,
_len: usize,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
) -> RhaiResultOf<V::Value> {
self.deserialize_seq(visitor)
}
fn deserialize_map<V: Visitor<'de>>(self, _visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_map<V: Visitor<'de>>(self, _visitor: V) -> RhaiResultOf<V::Value> {
#[cfg(not(feature = "no_object"))]
return self.value.downcast_ref::<crate::Map>().map_or_else(
|| self.type_error(),
@ -453,7 +435,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
_name: &'static str,
_fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
) -> RhaiResultOf<V::Value> {
self.deserialize_map(visitor)
}
@ -462,7 +444,7 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
_name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
) -> RhaiResultOf<V::Value> {
if let Some(s) = self.value.read_lock::<ImmutableString>() {
visitor.visit_enum(s.as_str().into_deserializer())
} else {
@ -487,17 +469,11 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
}
}
fn deserialize_identifier<V: Visitor<'de>>(
self,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_identifier<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
self.deserialize_str(visitor)
}
fn deserialize_ignored_any<V: Visitor<'de>>(
self,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_ignored_any<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
self.deserialize_any(visitor)
}
}
@ -521,12 +497,12 @@ impl<'a, ITER: Iterator<Item = &'a Dynamic>> IterateDynamicArray<'a, ITER> {
impl<'a: 'de, 'de, ITER: Iterator<Item = &'a Dynamic>> serde::de::SeqAccess<'de>
for IterateDynamicArray<'a, ITER>
{
type Error = Box<EvalAltResult>;
type Error = RhaiError;
fn next_element_seed<T: DeserializeSeed<'de>>(
fn next_element_seed<T: serde::de::DeserializeSeed<'de>>(
&mut self,
seed: T,
) -> Result<Option<T::Value>, Box<EvalAltResult>> {
) -> RhaiResultOf<Option<T::Value>> {
// Deserialize each item coming out of the iterator.
match self.iter.next() {
None => Ok(None),
@ -568,12 +544,12 @@ where
KEYS: Iterator<Item = &'a str>,
VALUES: Iterator<Item = &'a Dynamic>,
{
type Error = Box<EvalAltResult>;
type Error = RhaiError;
fn next_key_seed<K: DeserializeSeed<'de>>(
fn next_key_seed<K: serde::de::DeserializeSeed<'de>>(
&mut self,
seed: K,
) -> Result<Option<K::Value>, Box<EvalAltResult>> {
) -> RhaiResultOf<Option<K::Value>> {
// Deserialize each `Identifier` key coming out of the keys iterator.
match self.keys.next() {
None => Ok(None),
@ -583,13 +559,13 @@ where
}
}
fn next_value_seed<V: DeserializeSeed<'de>>(
fn next_value_seed<V: serde::de::DeserializeSeed<'de>>(
&mut self,
seed: V,
) -> Result<V::Value, Box<EvalAltResult>> {
) -> RhaiResultOf<V::Value> {
// Deserialize each value item coming out of the iterator.
seed.deserialize(&mut DynamicDeserializer::from_dynamic(
self.values.next().expect("exists"),
self.values.next().unwrap(),
))
}
}
@ -602,13 +578,13 @@ struct EnumDeserializer<'t, 'de: 't> {
#[cfg(not(feature = "no_object"))]
impl<'t, 'de> serde::de::EnumAccess<'de> for EnumDeserializer<'t, 'de> {
type Error = Box<EvalAltResult>;
type Error = RhaiError;
type Variant = Self;
fn variant_seed<V: DeserializeSeed<'de>>(
fn variant_seed<V: serde::de::DeserializeSeed<'de>>(
self,
seed: V,
) -> Result<(V::Value, Self::Variant), Self::Error> {
) -> RhaiResultOf<(V::Value, Self::Variant)> {
seed.deserialize(self.tag.into_deserializer())
.map(|v| (v, self))
}
@ -616,24 +592,20 @@ impl<'t, 'de> serde::de::EnumAccess<'de> for EnumDeserializer<'t, 'de> {
#[cfg(not(feature = "no_object"))]
impl<'t, 'de> serde::de::VariantAccess<'de> for EnumDeserializer<'t, 'de> {
type Error = Box<EvalAltResult>;
type Error = RhaiError;
fn unit_variant(mut self) -> Result<(), Self::Error> {
fn unit_variant(mut self) -> RhaiResultOf<()> {
Deserialize::deserialize(&mut self.content)
}
fn newtype_variant_seed<T: DeserializeSeed<'de>>(
fn newtype_variant_seed<T: serde::de::DeserializeSeed<'de>>(
mut self,
seed: T,
) -> Result<T::Value, Self::Error> {
) -> RhaiResultOf<T::Value> {
seed.deserialize(&mut self.content)
}
fn tuple_variant<V: Visitor<'de>>(
mut self,
len: usize,
visitor: V,
) -> Result<V::Value, Self::Error> {
fn tuple_variant<V: Visitor<'de>>(mut self, len: usize, visitor: V) -> RhaiResultOf<V::Value> {
self.content.deserialize_tuple(len, visitor)
}
@ -641,7 +613,7 @@ impl<'t, 'de> serde::de::VariantAccess<'de> for EnumDeserializer<'t, 'de> {
mut self,
fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error> {
) -> RhaiResultOf<V::Value> {
self.content.deserialize_struct("", fields, visitor)
}
}

View File

@ -1,3 +1,5 @@
//! Serialization of functions metadata.
#![cfg(feature = "metadata")]
use crate::module::calc_native_fn_hash;
@ -48,33 +50,24 @@ impl From<crate::FnAccess> for FnAccess {
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct FnParam {
pub name: Box<str>,
struct FnParam<'a> {
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub typ: Option<Box<str>>,
pub name: Option<&'a str>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub typ: Option<&'a str>,
}
impl PartialOrd for FnParam {
impl PartialOrd for FnParam<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(match self.name.partial_cmp(&other.name).expect("succeed") {
Ordering::Less => Ordering::Less,
Ordering::Greater => Ordering::Greater,
Ordering::Equal => match (self.typ.is_none(), other.typ.is_none()) {
(true, true) => Ordering::Equal,
(true, false) => Ordering::Greater,
(false, true) => Ordering::Less,
(false, false) => self
.typ
.as_ref()
.expect("`Some`")
.partial_cmp(other.typ.as_ref().expect("`Some`"))
.expect("succeed"),
},
Ordering::Equal => self.typ.partial_cmp(&other.typ).expect("succeed"),
})
}
}
impl Ord for FnParam {
impl Ord for FnParam<'_> {
fn cmp(&self, other: &Self) -> Ordering {
match self.name.cmp(&other.name) {
Ordering::Equal => self.typ.cmp(&other.typ),
@ -85,7 +78,7 @@ impl Ord for FnParam {
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct FnMetadata {
struct FnMetadata<'a> {
pub base_hash: u64,
pub full_hash: u64,
pub namespace: FnNamespace,
@ -95,21 +88,21 @@ struct FnMetadata {
pub typ: FnType,
pub num_params: usize,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub params: Vec<FnParam>,
pub params: Vec<FnParam<'a>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub return_type: Option<Box<str>>,
pub return_type_name: Option<&'a str>,
pub signature: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub doc_comments: Vec<Box<str>>,
pub doc_comments: Vec<&'a str>,
}
impl PartialOrd for FnMetadata {
impl PartialOrd for FnMetadata<'_> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for FnMetadata {
impl Ord for FnMetadata<'_> {
fn cmp(&self, other: &Self) -> Ordering {
match self.name.cmp(&other.name) {
Ordering::Equal => match self.num_params.cmp(&other.num_params) {
@ -121,8 +114,8 @@ impl Ord for FnMetadata {
}
}
impl From<&crate::module::FuncInfo> for FnMetadata {
fn from(info: &crate::module::FuncInfo) -> Self {
impl<'a> From<&'a crate::module::FuncInfo> for FnMetadata<'a> {
fn from(info: &'a crate::module::FuncInfo) -> Self {
let base_hash = calc_fn_hash(&info.name, info.params);
let (typ, full_hash) = if info.func.is_script() {
(FnType::Script, base_hash)
@ -142,41 +135,38 @@ impl From<&crate::module::FuncInfo> for FnMetadata {
typ,
num_params: info.params,
params: info
.param_names
.param_names_and_types
.iter()
.take(info.params)
.map(|s| {
let mut seg = s.splitn(2, ':');
let name = seg
.next()
.map(|s| s.trim().into())
.unwrap_or_else(|| "_".into());
let typ = seg.next().map(|s| s.trim().into());
let name = match seg.next().map(&str::trim).unwrap_or("_") {
"_" => None,
s => Some(s),
};
let typ = seg.next().map(&str::trim);
FnParam { name, typ }
})
.collect(),
return_type: info
.param_names
.last()
.map(|s| s.as_str().into())
.or_else(|| Some("()".into())),
return_type_name: match info.return_type_name.as_str() {
"" | "()" => None,
ty => Some(ty),
},
signature: info.gen_signature(),
doc_comments: if info.func.is_script() {
#[cfg(feature = "no_function")]
{
unreachable!("scripted functions should not exist under no_function")
}
unreachable!("script-defined functions should not exist under no_function");
#[cfg(not(feature = "no_function"))]
{
info.func
.get_script_fn_def()
.expect("scripted function")
.comments
.as_ref()
.map_or_else(|| Vec::new(), |v| v.to_vec())
}
info.func
.get_script_fn_def()
.expect("script-defined function")
.comments
.as_ref()
.map_or_else(|| Vec::new(), |v| v.iter().map(|s| &**s).collect())
} else {
Vec::new()
info.comments
.as_ref()
.map_or_else(|| Vec::new(), |v| v.iter().map(|s| &**s).collect())
},
}
}
@ -184,14 +174,14 @@ impl From<&crate::module::FuncInfo> for FnMetadata {
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
struct ModuleMetadata {
struct ModuleMetadata<'a> {
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
pub modules: BTreeMap<String, Self>,
pub modules: BTreeMap<&'a str, Self>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub functions: Vec<FnMetadata>,
pub functions: Vec<FnMetadata<'a>>,
}
impl ModuleMetadata {
impl ModuleMetadata<'_> {
#[inline(always)]
pub fn new() -> Self {
Self {
@ -201,22 +191,21 @@ impl ModuleMetadata {
}
}
impl From<&crate::Module> for ModuleMetadata {
fn from(module: &crate::Module) -> Self {
impl<'a> From<&'a crate::Module> for ModuleMetadata<'a> {
fn from(module: &'a crate::Module) -> Self {
let mut functions: Vec<_> = module.iter_fn().map(|f| f.into()).collect();
functions.sort();
Self {
modules: module
.iter_sub_modules()
.map(|(name, m)| (name.to_string(), m.as_ref().into()))
.map(|(name, m)| (name, m.as_ref().into()))
.collect(),
functions,
}
}
}
#[cfg(feature = "metadata")]
impl Engine {
/// _(metadata)_ Generate a list of all functions (including those defined in an
/// [`AST`][crate::AST]) in JSON format.
@ -235,20 +224,14 @@ impl Engine {
let _ast = ast;
let mut global = ModuleMetadata::new();
if include_global {
self.global_modules
.iter()
.take(self.global_modules.len() - 1)
.flat_map(|m| m.iter_fn())
.for_each(|f| global.functions.push(f.into()));
}
self.global_sub_modules.iter().for_each(|(name, m)| {
global.modules.insert(name.to_string(), m.as_ref().into());
global.modules.insert(name, m.as_ref().into());
});
self.global_namespace()
.iter_fn()
self.global_modules
.iter()
.take(if include_global { usize::MAX } else { 1 })
.flat_map(|m| m.iter_fn())
.for_each(|f| global.functions.push(f.into()));
#[cfg(not(feature = "no_function"))]

View File

@ -1,6 +1,6 @@
//! Implement serialization support of [`Dynamic`][crate::Dynamic] for [`serde`].
use crate::{Dynamic, EvalAltResult, Position, RhaiResult};
use crate::{Dynamic, Position, RhaiError, RhaiResult, RhaiResultOf, ERR};
use serde::ser::{
Error, SerializeMap, SerializeSeq, SerializeStruct, SerializeTuple, SerializeTupleStruct,
};
@ -81,55 +81,56 @@ pub fn to_dynamic<T: Serialize>(value: T) -> RhaiResult {
value.serialize(&mut s)
}
impl Error for Box<EvalAltResult> {
impl Error for RhaiError {
fn custom<T: fmt::Display>(err: T) -> Self {
EvalAltResult::ErrorRuntime(err.to_string().into(), Position::NONE).into()
ERR::ErrorRuntime(err.to_string().into(), Position::NONE).into()
}
}
impl Serializer for &mut DynamicSerializer {
type Ok = Dynamic;
type Error = Box<EvalAltResult>;
type Error = RhaiError;
type SerializeSeq = DynamicSerializer;
type SerializeTuple = DynamicSerializer;
type SerializeTupleStruct = DynamicSerializer;
#[cfg(not(any(feature = "no_object", feature = "no_index")))]
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
type SerializeTupleVariant = TupleVariantSerializer;
#[cfg(any(feature = "no_object", feature = "no_index"))]
type SerializeTupleVariant = serde::ser::Impossible<Dynamic, Box<EvalAltResult>>;
type SerializeTupleVariant = serde::ser::Impossible<Dynamic, RhaiError>;
type SerializeMap = DynamicSerializer;
type SerializeStruct = DynamicSerializer;
#[cfg(not(feature = "no_object"))]
type SerializeStructVariant = StructVariantSerializer;
#[cfg(feature = "no_object")]
type SerializeStructVariant = serde::ser::Impossible<Dynamic, Box<EvalAltResult>>;
type SerializeStructVariant = serde::ser::Impossible<Dynamic, RhaiError>;
fn serialize_bool(self, v: bool) -> Result<Self::Ok, Box<EvalAltResult>> {
fn serialize_bool(self, v: bool) -> RhaiResultOf<Self::Ok> {
Ok(v.into())
}
fn serialize_i8(self, v: i8) -> Result<Self::Ok, Box<EvalAltResult>> {
fn serialize_i8(self, v: i8) -> RhaiResultOf<Self::Ok> {
#[cfg(not(feature = "only_i32"))]
return self.serialize_i64(i64::from(v));
#[cfg(feature = "only_i32")]
return self.serialize_i32(i32::from(v));
}
fn serialize_i16(self, v: i16) -> Result<Self::Ok, Box<EvalAltResult>> {
fn serialize_i16(self, v: i16) -> RhaiResultOf<Self::Ok> {
#[cfg(not(feature = "only_i32"))]
return self.serialize_i64(i64::from(v));
#[cfg(feature = "only_i32")]
return self.serialize_i32(i32::from(v));
}
fn serialize_i32(self, v: i32) -> Result<Self::Ok, Box<EvalAltResult>> {
fn serialize_i32(self, v: i32) -> RhaiResultOf<Self::Ok> {
#[cfg(not(feature = "only_i32"))]
return self.serialize_i64(i64::from(v));
#[cfg(feature = "only_i32")]
return Ok(v.into());
}
fn serialize_i64(self, v: i64) -> Result<Self::Ok, Box<EvalAltResult>> {
fn serialize_i64(self, v: i64) -> RhaiResultOf<Self::Ok> {
#[cfg(not(feature = "only_i32"))]
{
Ok(v.into())
@ -142,7 +143,7 @@ impl Serializer for &mut DynamicSerializer {
}
}
fn serialize_i128(self, v: i128) -> Result<Self::Ok, Box<EvalAltResult>> {
fn serialize_i128(self, v: i128) -> RhaiResultOf<Self::Ok> {
#[cfg(not(feature = "only_i32"))]
if v > i64::MAX as i128 {
Ok(Dynamic::from(v))
@ -157,21 +158,21 @@ impl Serializer for &mut DynamicSerializer {
}
}
fn serialize_u8(self, v: u8) -> Result<Self::Ok, Box<EvalAltResult>> {
fn serialize_u8(self, v: u8) -> RhaiResultOf<Self::Ok> {
#[cfg(not(feature = "only_i32"))]
return self.serialize_i64(i64::from(v));
#[cfg(feature = "only_i32")]
return self.serialize_i32(i32::from(v));
}
fn serialize_u16(self, v: u16) -> Result<Self::Ok, Box<EvalAltResult>> {
fn serialize_u16(self, v: u16) -> RhaiResultOf<Self::Ok> {
#[cfg(not(feature = "only_i32"))]
return self.serialize_i64(i64::from(v));
#[cfg(feature = "only_i32")]
return self.serialize_i32(i32::from(v));
}
fn serialize_u32(self, v: u32) -> Result<Self::Ok, Box<EvalAltResult>> {
fn serialize_u32(self, v: u32) -> RhaiResultOf<Self::Ok> {
#[cfg(not(feature = "only_i32"))]
{
self.serialize_i64(i64::from(v))
@ -184,7 +185,7 @@ impl Serializer for &mut DynamicSerializer {
}
}
fn serialize_u64(self, v: u64) -> Result<Self::Ok, Box<EvalAltResult>> {
fn serialize_u64(self, v: u64) -> RhaiResultOf<Self::Ok> {
#[cfg(not(feature = "only_i32"))]
if v > i64::MAX as u64 {
Ok(Dynamic::from(v))
@ -199,7 +200,7 @@ impl Serializer for &mut DynamicSerializer {
}
}
fn serialize_u128(self, v: u128) -> Result<Self::Ok, Box<EvalAltResult>> {
fn serialize_u128(self, v: u128) -> RhaiResultOf<Self::Ok> {
#[cfg(not(feature = "only_i32"))]
if v > i64::MAX as u128 {
Ok(Dynamic::from(v))
@ -214,7 +215,7 @@ impl Serializer for &mut DynamicSerializer {
}
}
fn serialize_f32(self, v: f32) -> Result<Self::Ok, Box<EvalAltResult>> {
fn serialize_f32(self, v: f32) -> RhaiResultOf<Self::Ok> {
#[cfg(any(not(feature = "no_float"), not(feature = "decimal")))]
return Ok(Dynamic::from(v));
@ -230,7 +231,7 @@ impl Serializer for &mut DynamicSerializer {
}
}
fn serialize_f64(self, v: f64) -> Result<Self::Ok, Box<EvalAltResult>> {
fn serialize_f64(self, v: f64) -> RhaiResultOf<Self::Ok> {
#[cfg(any(not(feature = "no_float"), not(feature = "decimal")))]
return Ok(Dynamic::from(v));
@ -246,20 +247,20 @@ impl Serializer for &mut DynamicSerializer {
}
}
fn serialize_char(self, v: char) -> Result<Self::Ok, Box<EvalAltResult>> {
fn serialize_char(self, v: char) -> RhaiResultOf<Self::Ok> {
Ok(v.into())
}
fn serialize_str(self, v: &str) -> Result<Self::Ok, Box<EvalAltResult>> {
fn serialize_str(self, v: &str) -> RhaiResultOf<Self::Ok> {
Ok(v.into())
}
fn serialize_bytes(self, _v: &[u8]) -> Result<Self::Ok, Box<EvalAltResult>> {
fn serialize_bytes(self, _v: &[u8]) -> RhaiResultOf<Self::Ok> {
#[cfg(not(feature = "no_index"))]
return Ok(Dynamic::from_blob(_v.to_vec()));
#[cfg(feature = "no_index")]
return Err(EvalAltResult::ErrorMismatchDataType(
return Err(ERR::ErrorMismatchDataType(
"".into(),
"BLOB's are not supported with 'no_index'".into(),
Position::NONE,
@ -267,22 +268,19 @@ impl Serializer for &mut DynamicSerializer {
.into());
}
fn serialize_none(self) -> Result<Self::Ok, Box<EvalAltResult>> {
fn serialize_none(self) -> RhaiResultOf<Self::Ok> {
Ok(Dynamic::UNIT)
}
fn serialize_some<T: ?Sized + Serialize>(
self,
value: &T,
) -> Result<Self::Ok, Box<EvalAltResult>> {
fn serialize_some<T: ?Sized + Serialize>(self, value: &T) -> RhaiResultOf<Self::Ok> {
value.serialize(&mut *self)
}
fn serialize_unit(self) -> Result<Self::Ok, Box<EvalAltResult>> {
fn serialize_unit(self) -> RhaiResultOf<Self::Ok> {
Ok(Dynamic::UNIT)
}
fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok, Box<EvalAltResult>> {
fn serialize_unit_struct(self, _name: &'static str) -> RhaiResultOf<Self::Ok> {
self.serialize_unit()
}
@ -291,7 +289,7 @@ impl Serializer for &mut DynamicSerializer {
_name: &'static str,
_variant_index: u32,
variant: &'static str,
) -> Result<Self::Ok, Box<EvalAltResult>> {
) -> RhaiResultOf<Self::Ok> {
self.serialize_str(variant)
}
@ -299,7 +297,7 @@ impl Serializer for &mut DynamicSerializer {
self,
_name: &'static str,
value: &T,
) -> Result<Self::Ok, Box<EvalAltResult>> {
) -> RhaiResultOf<Self::Ok> {
value.serialize(&mut *self)
}
@ -309,14 +307,14 @@ impl Serializer for &mut DynamicSerializer {
_variant_index: u32,
_variant: &'static str,
_value: &T,
) -> Result<Self::Ok, Box<EvalAltResult>> {
) -> RhaiResultOf<Self::Ok> {
#[cfg(not(feature = "no_object"))]
{
let content = to_dynamic(_value)?;
make_variant(_variant, content)
}
#[cfg(feature = "no_object")]
return Err(EvalAltResult::ErrorMismatchDataType(
return Err(ERR::ErrorMismatchDataType(
"".into(),
"object maps are not supported with 'no_object'".into(),
Position::NONE,
@ -324,11 +322,11 @@ impl Serializer for &mut DynamicSerializer {
.into());
}
fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq, Box<EvalAltResult>> {
fn serialize_seq(self, _len: Option<usize>) -> RhaiResultOf<Self::SerializeSeq> {
#[cfg(not(feature = "no_index"))]
return Ok(DynamicSerializer::new(crate::Array::new().into()));
#[cfg(feature = "no_index")]
return Err(EvalAltResult::ErrorMismatchDataType(
return Err(ERR::ErrorMismatchDataType(
"".into(),
"arrays are not supported with 'no_index'".into(),
Position::NONE,
@ -336,7 +334,7 @@ impl Serializer for &mut DynamicSerializer {
.into());
}
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Box<EvalAltResult>> {
fn serialize_tuple(self, len: usize) -> RhaiResultOf<Self::SerializeTuple> {
self.serialize_seq(Some(len))
}
@ -344,7 +342,7 @@ impl Serializer for &mut DynamicSerializer {
self,
_name: &'static str,
len: usize,
) -> Result<Self::SerializeTupleStruct, Box<EvalAltResult>> {
) -> RhaiResultOf<Self::SerializeTupleStruct> {
self.serialize_seq(Some(len))
}
@ -354,7 +352,7 @@ impl Serializer for &mut DynamicSerializer {
_variant_index: u32,
_variant: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleVariant, Box<EvalAltResult>> {
) -> RhaiResultOf<Self::SerializeTupleVariant> {
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
return Ok(TupleVariantSerializer {
@ -362,7 +360,7 @@ impl Serializer for &mut DynamicSerializer {
array: crate::Array::with_capacity(_len),
});
#[cfg(any(feature = "no_object", feature = "no_index"))]
return Err(EvalAltResult::ErrorMismatchDataType(
return Err(ERR::ErrorMismatchDataType(
"".into(),
"tuples are not supported with 'no_index' or 'no_object'".into(),
Position::NONE,
@ -370,11 +368,11 @@ impl Serializer for &mut DynamicSerializer {
.into());
}
fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap, Box<EvalAltResult>> {
fn serialize_map(self, _len: Option<usize>) -> RhaiResultOf<Self::SerializeMap> {
#[cfg(not(feature = "no_object"))]
return Ok(DynamicSerializer::new(crate::Map::new().into()));
#[cfg(feature = "no_object")]
return Err(EvalAltResult::ErrorMismatchDataType(
return Err(ERR::ErrorMismatchDataType(
"".into(),
"object maps are not supported with 'no_object'".into(),
Position::NONE,
@ -386,7 +384,7 @@ impl Serializer for &mut DynamicSerializer {
self,
_name: &'static str,
len: usize,
) -> Result<Self::SerializeStruct, Box<EvalAltResult>> {
) -> RhaiResultOf<Self::SerializeStruct> {
self.serialize_map(Some(len))
}
@ -396,14 +394,14 @@ impl Serializer for &mut DynamicSerializer {
_variant_index: u32,
_variant: &'static str,
_len: usize,
) -> Result<Self::SerializeStructVariant, Box<EvalAltResult>> {
) -> RhaiResultOf<Self::SerializeStructVariant> {
#[cfg(not(feature = "no_object"))]
return Ok(StructVariantSerializer {
variant: _variant,
map: crate::Map::new(),
});
#[cfg(feature = "no_object")]
return Err(EvalAltResult::ErrorMismatchDataType(
return Err(ERR::ErrorMismatchDataType(
"".into(),
"object maps are not supported with 'no_object'".into(),
Position::NONE,
@ -414,12 +412,9 @@ impl Serializer for &mut DynamicSerializer {
impl SerializeSeq for DynamicSerializer {
type Ok = Dynamic;
type Error = Box<EvalAltResult>;
type Error = RhaiError;
fn serialize_element<T: ?Sized + Serialize>(
&mut self,
_value: &T,
) -> Result<(), Box<EvalAltResult>> {
fn serialize_element<T: ?Sized + Serialize>(&mut self, _value: &T) -> RhaiResultOf<()> {
#[cfg(not(feature = "no_index"))]
{
let _value = _value.serialize(&mut *self)?;
@ -428,7 +423,7 @@ impl SerializeSeq for DynamicSerializer {
Ok(())
}
#[cfg(feature = "no_index")]
return Err(EvalAltResult::ErrorMismatchDataType(
return Err(ERR::ErrorMismatchDataType(
"".into(),
"arrays are not supported with 'no_index'".into(),
Position::NONE,
@ -437,11 +432,11 @@ impl SerializeSeq for DynamicSerializer {
}
// Close the sequence.
fn end(self) -> Result<Self::Ok, Box<EvalAltResult>> {
fn end(self) -> RhaiResultOf<Self::Ok> {
#[cfg(not(feature = "no_index"))]
return Ok(self._value);
#[cfg(feature = "no_index")]
return Err(EvalAltResult::ErrorMismatchDataType(
return Err(ERR::ErrorMismatchDataType(
"".into(),
"arrays are not supported with 'no_index'".into(),
Position::NONE,
@ -452,12 +447,9 @@ impl SerializeSeq for DynamicSerializer {
impl SerializeTuple for DynamicSerializer {
type Ok = Dynamic;
type Error = Box<EvalAltResult>;
type Error = RhaiError;
fn serialize_element<T: ?Sized + Serialize>(
&mut self,
_value: &T,
) -> Result<(), Box<EvalAltResult>> {
fn serialize_element<T: ?Sized + Serialize>(&mut self, _value: &T) -> RhaiResultOf<()> {
#[cfg(not(feature = "no_index"))]
{
let _value = _value.serialize(&mut *self)?;
@ -466,7 +458,7 @@ impl SerializeTuple for DynamicSerializer {
Ok(())
}
#[cfg(feature = "no_index")]
return Err(EvalAltResult::ErrorMismatchDataType(
return Err(ERR::ErrorMismatchDataType(
"".into(),
"tuples are not supported with 'no_index'".into(),
Position::NONE,
@ -474,11 +466,11 @@ impl SerializeTuple for DynamicSerializer {
.into());
}
fn end(self) -> Result<Self::Ok, Box<EvalAltResult>> {
fn end(self) -> RhaiResultOf<Self::Ok> {
#[cfg(not(feature = "no_index"))]
return Ok(self._value);
#[cfg(feature = "no_index")]
return Err(EvalAltResult::ErrorMismatchDataType(
return Err(ERR::ErrorMismatchDataType(
"".into(),
"tuples are not supported with 'no_index'".into(),
Position::NONE,
@ -489,12 +481,9 @@ impl SerializeTuple for DynamicSerializer {
impl SerializeTupleStruct for DynamicSerializer {
type Ok = Dynamic;
type Error = Box<EvalAltResult>;
type Error = RhaiError;
fn serialize_field<T: ?Sized + Serialize>(
&mut self,
_value: &T,
) -> Result<(), Box<EvalAltResult>> {
fn serialize_field<T: ?Sized + Serialize>(&mut self, _value: &T) -> RhaiResultOf<()> {
#[cfg(not(feature = "no_index"))]
{
let _value = _value.serialize(&mut *self)?;
@ -503,7 +492,7 @@ impl SerializeTupleStruct for DynamicSerializer {
Ok(())
}
#[cfg(feature = "no_index")]
return Err(EvalAltResult::ErrorMismatchDataType(
return Err(ERR::ErrorMismatchDataType(
"".into(),
"tuples are not supported with 'no_index'".into(),
Position::NONE,
@ -511,11 +500,11 @@ impl SerializeTupleStruct for DynamicSerializer {
.into());
}
fn end(self) -> Result<Self::Ok, Box<EvalAltResult>> {
fn end(self) -> RhaiResultOf<Self::Ok> {
#[cfg(not(feature = "no_index"))]
return Ok(self._value);
#[cfg(feature = "no_index")]
return Err(EvalAltResult::ErrorMismatchDataType(
return Err(ERR::ErrorMismatchDataType(
"".into(),
"tuples are not supported with 'no_index'".into(),
Position::NONE,
@ -526,16 +515,16 @@ impl SerializeTupleStruct for DynamicSerializer {
impl SerializeMap for DynamicSerializer {
type Ok = Dynamic;
type Error = Box<EvalAltResult>;
type Error = RhaiError;
fn serialize_key<T: ?Sized + Serialize>(&mut self, _key: &T) -> Result<(), Box<EvalAltResult>> {
fn serialize_key<T: ?Sized + Serialize>(&mut self, _key: &T) -> RhaiResultOf<()> {
#[cfg(not(feature = "no_object"))]
{
self._key = _key.serialize(&mut *self)?;
Ok(())
}
#[cfg(feature = "no_object")]
return Err(EvalAltResult::ErrorMismatchDataType(
return Err(ERR::ErrorMismatchDataType(
"".into(),
"object maps are not supported with 'no_object'".into(),
Position::NONE,
@ -543,20 +532,13 @@ impl SerializeMap for DynamicSerializer {
.into());
}
fn serialize_value<T: ?Sized + Serialize>(
&mut self,
_value: &T,
) -> Result<(), Box<EvalAltResult>> {
fn serialize_value<T: ?Sized + Serialize>(&mut self, _value: &T) -> RhaiResultOf<()> {
#[cfg(not(feature = "no_object"))]
{
let key = std::mem::take(&mut self._key)
.into_immutable_string()
.map_err(|typ| {
EvalAltResult::ErrorMismatchDataType(
"string".into(),
typ.into(),
Position::NONE,
)
ERR::ErrorMismatchDataType("string".into(), typ.into(), Position::NONE)
})?;
let _value = _value.serialize(&mut *self)?;
let map = self._value.downcast_mut::<crate::Map>().unwrap();
@ -564,7 +546,7 @@ impl SerializeMap for DynamicSerializer {
Ok(())
}
#[cfg(feature = "no_object")]
return Err(EvalAltResult::ErrorMismatchDataType(
return Err(ERR::ErrorMismatchDataType(
"".into(),
"object maps are not supported with 'no_object'".into(),
Position::NONE,
@ -576,12 +558,12 @@ impl SerializeMap for DynamicSerializer {
&mut self,
_key: &K,
_value: &T,
) -> Result<(), Box<EvalAltResult>> {
) -> RhaiResultOf<()> {
#[cfg(not(feature = "no_object"))]
{
let _key: Dynamic = _key.serialize(&mut *self)?;
let _key = _key.into_immutable_string().map_err(|typ| {
EvalAltResult::ErrorMismatchDataType("string".into(), typ.into(), Position::NONE)
ERR::ErrorMismatchDataType("string".into(), typ.into(), Position::NONE)
})?;
let _value = _value.serialize(&mut *self)?;
let map = self._value.downcast_mut::<crate::Map>().unwrap();
@ -589,7 +571,7 @@ impl SerializeMap for DynamicSerializer {
Ok(())
}
#[cfg(feature = "no_object")]
return Err(EvalAltResult::ErrorMismatchDataType(
return Err(ERR::ErrorMismatchDataType(
"".into(),
"object maps are not supported with 'no_object'".into(),
Position::NONE,
@ -597,11 +579,11 @@ impl SerializeMap for DynamicSerializer {
.into());
}
fn end(self) -> Result<Self::Ok, Box<EvalAltResult>> {
fn end(self) -> RhaiResultOf<Self::Ok> {
#[cfg(not(feature = "no_object"))]
return Ok(self._value);
#[cfg(feature = "no_object")]
return Err(EvalAltResult::ErrorMismatchDataType(
return Err(ERR::ErrorMismatchDataType(
"".into(),
"object maps are not supported with 'no_object'".into(),
Position::NONE,
@ -612,13 +594,13 @@ impl SerializeMap for DynamicSerializer {
impl SerializeStruct for DynamicSerializer {
type Ok = Dynamic;
type Error = Box<EvalAltResult>;
type Error = RhaiError;
fn serialize_field<T: ?Sized + Serialize>(
&mut self,
_key: &'static str,
_value: &T,
) -> Result<(), Box<EvalAltResult>> {
) -> RhaiResultOf<()> {
#[cfg(not(feature = "no_object"))]
{
let _value = _value.serialize(&mut *self)?;
@ -627,7 +609,7 @@ impl SerializeStruct for DynamicSerializer {
Ok(())
}
#[cfg(feature = "no_object")]
return Err(EvalAltResult::ErrorMismatchDataType(
return Err(ERR::ErrorMismatchDataType(
"".into(),
"object maps are not supported with 'no_object'".into(),
Position::NONE,
@ -635,11 +617,11 @@ impl SerializeStruct for DynamicSerializer {
.into());
}
fn end(self) -> Result<Self::Ok, Box<EvalAltResult>> {
fn end(self) -> RhaiResultOf<Self::Ok> {
#[cfg(not(feature = "no_object"))]
return Ok(self._value);
#[cfg(feature = "no_object")]
return Err(EvalAltResult::ErrorMismatchDataType(
return Err(ERR::ErrorMismatchDataType(
"".into(),
"object maps are not supported with 'no_object'".into(),
Position::NONE,
@ -648,27 +630,26 @@ impl SerializeStruct for DynamicSerializer {
}
}
#[cfg(not(any(feature = "no_object", feature = "no_index")))]
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
struct TupleVariantSerializer {
variant: &'static str,
array: crate::Array,
}
#[cfg(not(any(feature = "no_object", feature = "no_index")))]
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
impl serde::ser::SerializeTupleVariant for TupleVariantSerializer {
type Ok = Dynamic;
type Error = Box<EvalAltResult>;
type Error = RhaiError;
fn serialize_field<T: ?Sized + Serialize>(
&mut self,
value: &T,
) -> Result<(), Box<EvalAltResult>> {
fn serialize_field<T: ?Sized + Serialize>(&mut self, value: &T) -> RhaiResultOf<()> {
let value = to_dynamic(value)?;
self.array.push(value);
Ok(())
}
fn end(self) -> Result<Self::Ok, Box<EvalAltResult>> {
fn end(self) -> RhaiResultOf<Self::Ok> {
make_variant(self.variant, self.array.into())
}
}
@ -682,19 +663,19 @@ struct StructVariantSerializer {
#[cfg(not(feature = "no_object"))]
impl serde::ser::SerializeStructVariant for StructVariantSerializer {
type Ok = Dynamic;
type Error = Box<EvalAltResult>;
type Error = RhaiError;
fn serialize_field<T: ?Sized + Serialize>(
&mut self,
key: &'static str,
value: &T,
) -> Result<(), Box<EvalAltResult>> {
) -> RhaiResultOf<()> {
let value = to_dynamic(value)?;
self.map.insert(key.into(), value);
Ok(())
}
fn end(self) -> Result<Self::Ok, Box<EvalAltResult>> {
fn end(self) -> RhaiResultOf<Self::Ok> {
make_variant(self.variant, self.map.into())
}
}

View File

@ -1,6 +1,6 @@
//! Implement deserialization support of [`ImmutableString`][crate::ImmutableString] for [`serde`].
use crate::{EvalAltResult, Position};
use crate::{Position, RhaiError, RhaiResultOf, ERR};
use serde::de::{Deserializer, Visitor};
use std::any::type_name;
#[cfg(feature = "no_std")]
@ -18,102 +18,93 @@ impl<'a> StringSliceDeserializer<'a> {
Self { value }
}
/// Shortcut for a type conversion error.
fn type_error<T>(&self) -> Result<T, Box<EvalAltResult>> {
Err(EvalAltResult::ErrorMismatchOutputType(
type_name::<T>().into(),
"string".into(),
Position::NONE,
fn type_error<T>(&self) -> RhaiResultOf<T> {
Err(
ERR::ErrorMismatchOutputType(type_name::<T>().into(), "string".into(), Position::NONE)
.into(),
)
.into())
}
}
impl<'de> Deserializer<'de> for &mut StringSliceDeserializer<'de> {
type Error = Box<EvalAltResult>;
type Error = RhaiError;
fn deserialize_any<V: Visitor<'de>>(self, v: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_any<V: Visitor<'de>>(self, v: V) -> RhaiResultOf<V::Value> {
self.deserialize_str(v)
}
fn deserialize_bool<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_bool<V: Visitor<'de>>(self, _: V) -> RhaiResultOf<V::Value> {
self.type_error()
}
fn deserialize_i8<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_i8<V: Visitor<'de>>(self, _: V) -> RhaiResultOf<V::Value> {
self.type_error()
}
fn deserialize_i16<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_i16<V: Visitor<'de>>(self, _: V) -> RhaiResultOf<V::Value> {
self.type_error()
}
fn deserialize_i32<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_i32<V: Visitor<'de>>(self, _: V) -> RhaiResultOf<V::Value> {
self.type_error()
}
fn deserialize_i64<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_i64<V: Visitor<'de>>(self, _: V) -> RhaiResultOf<V::Value> {
self.type_error()
}
fn deserialize_u8<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_u8<V: Visitor<'de>>(self, _: V) -> RhaiResultOf<V::Value> {
self.type_error()
}
fn deserialize_u16<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_u16<V: Visitor<'de>>(self, _: V) -> RhaiResultOf<V::Value> {
self.type_error()
}
fn deserialize_u32<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_u32<V: Visitor<'de>>(self, _: V) -> RhaiResultOf<V::Value> {
self.type_error()
}
fn deserialize_u64<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_u64<V: Visitor<'de>>(self, _: V) -> RhaiResultOf<V::Value> {
self.type_error()
}
fn deserialize_f32<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_f32<V: Visitor<'de>>(self, _: V) -> RhaiResultOf<V::Value> {
self.type_error()
}
fn deserialize_f64<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_f64<V: Visitor<'de>>(self, _: V) -> RhaiResultOf<V::Value> {
self.type_error()
}
fn deserialize_char<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_char<V: Visitor<'de>>(self, _: V) -> RhaiResultOf<V::Value> {
self.type_error()
}
fn deserialize_str<V: Visitor<'de>>(self, v: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_str<V: Visitor<'de>>(self, v: V) -> RhaiResultOf<V::Value> {
// Only allow deserialization into a string.
v.visit_borrowed_str(self.value)
}
fn deserialize_string<V: Visitor<'de>>(
self,
visitor: V,
) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_string<V: Visitor<'de>>(self, visitor: V) -> RhaiResultOf<V::Value> {
self.deserialize_str(visitor)
}
fn deserialize_bytes<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_bytes<V: Visitor<'de>>(self, _: V) -> RhaiResultOf<V::Value> {
self.type_error()
}
fn deserialize_byte_buf<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_byte_buf<V: Visitor<'de>>(self, _: V) -> RhaiResultOf<V::Value> {
self.type_error()
}
fn deserialize_option<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_option<V: Visitor<'de>>(self, _: V) -> RhaiResultOf<V::Value> {
self.type_error()
}
fn deserialize_unit<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_unit<V: Visitor<'de>>(self, _: V) -> RhaiResultOf<V::Value> {
self.type_error()
}
fn deserialize_unit_struct<V: Visitor<'de>>(
self,
_name: &'static str,
v: V,
) -> Result<V::Value, Box<EvalAltResult>> {
) -> RhaiResultOf<V::Value> {
self.deserialize_unit(v)
}
fn deserialize_newtype_struct<V: Visitor<'de>>(
self,
_name: &'static str,
v: V,
) -> Result<V::Value, Box<EvalAltResult>> {
) -> RhaiResultOf<V::Value> {
v.visit_newtype_struct(self)
}
fn deserialize_seq<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_seq<V: Visitor<'de>>(self, _: V) -> RhaiResultOf<V::Value> {
self.type_error()
}
fn deserialize_tuple<V: Visitor<'de>>(
self,
_len: usize,
v: V,
) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_tuple<V: Visitor<'de>>(self, _len: usize, v: V) -> RhaiResultOf<V::Value> {
self.deserialize_seq(v)
}
fn deserialize_tuple_struct<V: Visitor<'de>>(
@ -121,10 +112,10 @@ impl<'de> Deserializer<'de> for &mut StringSliceDeserializer<'de> {
_name: &'static str,
_len: usize,
v: V,
) -> Result<V::Value, Box<EvalAltResult>> {
) -> RhaiResultOf<V::Value> {
self.deserialize_seq(v)
}
fn deserialize_map<V: Visitor<'de>>(self, _: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_map<V: Visitor<'de>>(self, _: V) -> RhaiResultOf<V::Value> {
self.type_error()
}
fn deserialize_struct<V: Visitor<'de>>(
@ -132,7 +123,7 @@ impl<'de> Deserializer<'de> for &mut StringSliceDeserializer<'de> {
_name: &'static str,
_fields: &'static [&'static str],
v: V,
) -> Result<V::Value, Box<EvalAltResult>> {
) -> RhaiResultOf<V::Value> {
self.deserialize_map(v)
}
fn deserialize_enum<V: Visitor<'de>>(
@ -140,16 +131,13 @@ impl<'de> Deserializer<'de> for &mut StringSliceDeserializer<'de> {
_name: &'static str,
_variants: &'static [&'static str],
_: V,
) -> Result<V::Value, Box<EvalAltResult>> {
) -> RhaiResultOf<V::Value> {
self.type_error()
}
fn deserialize_identifier<V: Visitor<'de>>(self, v: V) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_identifier<V: Visitor<'de>>(self, v: V) -> RhaiResultOf<V::Value> {
self.deserialize_str(v)
}
fn deserialize_ignored_any<V: Visitor<'de>>(
self,
v: V,
) -> Result<V::Value, Box<EvalAltResult>> {
fn deserialize_ignored_any<V: Visitor<'de>>(self, v: V) -> RhaiResultOf<V::Value> {
self.deserialize_any(v)
}
}

View File

@ -5,7 +5,7 @@ use crate::engine::{
KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF,
};
use crate::func::native::OnParseTokenCallback;
use crate::{Engine, LexError, StaticVec, INT};
use crate::{Engine, LexError, StaticVec, INT, UNSIGNED_INT};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{
@ -213,6 +213,16 @@ impl Position {
#[cfg(feature = "no_position")]
return true;
}
/// Returns an fallback [`Position`] if it is [`NONE`][Position::NONE]?
#[inline]
#[must_use]
pub const fn or_else(self, pos: Self) -> Self {
if self.is_none() {
pos
} else {
self
}
}
/// Print this [`Position`] for debug purposes.
#[inline]
pub(crate) fn debug_print(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -240,7 +250,7 @@ impl fmt::Display for Position {
#[cfg(not(feature = "no_position"))]
write!(f, "line {}, position {}", self.line, self.pos)?;
#[cfg(feature = "no_position")]
unreachable!();
unreachable!("no position");
}
Ok(())
@ -275,7 +285,7 @@ impl Add for Position {
},
};
#[cfg(feature = "no_position")]
unreachable!();
unreachable!("no position");
}
}
}
@ -684,11 +694,9 @@ impl Token {
/// Reverse lookup a token from a piece of syntax.
#[must_use]
pub fn lookup_from_syntax(syntax: impl AsRef<str>) -> Option<Self> {
pub fn lookup_from_syntax(syntax: &str) -> Option<Self> {
use Token::*;
let syntax = syntax.as_ref();
Some(match syntax {
"{" => LeftBrace,
"}" => RightBrace,
@ -776,11 +784,16 @@ impl Token {
#[cfg(feature = "no_module")]
"import" | "export" | "as" => Reserved(syntax.into()),
"===" | "!==" | "->" | "<-" | ":=" | "~" | "::<" | "(*" | "*)" | "#" | "#!"
| "public" | "protected" | "super" | "new" | "use" | "module" | "package" | "var"
| "static" | "shared" | "with" | "goto" | "exit" | "match" | "case" | "default"
| "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync" | "async" | "await"
| "yield" => Reserved(syntax.into()),
// List of reserved operators
"===" | "!==" | "->" | "<-" | ":=" | "~" | "::<" | "(*" | "*)" | "#" | "#!" => {
Reserved(syntax.into())
}
// List of reserved keywords
"public" | "protected" | "super" | "new" | "use" | "module" | "package" | "var"
| "static" | "shared" | "with" | "is" | "goto" | "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
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS | KEYWORD_IS_DEF_VAR => {
@ -873,13 +886,6 @@ impl Token {
use Token::*;
Precedence::new(match self {
// Assignments are not considered expressions - set to zero
Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | PowerOfAssign
| LeftShiftAssign | RightShiftAssign | AndAssign | OrAssign | XOrAssign
| ModuloAssign => 0,
ExclusiveRange | InclusiveRange => 10,
Or | XOr | Pipe => 30,
And | Ampersand => 60,
@ -890,6 +896,8 @@ impl Token {
LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo => 130,
ExclusiveRange | InclusiveRange => 140,
Plus | Minus => 150,
Divide | Multiply | Modulo => 180,
@ -898,8 +906,6 @@ impl Token {
LeftShift | RightShift => 210,
Period => 240,
_ => 0,
})
}
@ -910,14 +916,6 @@ impl Token {
use Token::*;
match self {
// Assignments bind to the right
Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | PowerOfAssign
| LeftShiftAssign | RightShiftAssign | AndAssign | OrAssign | XOrAssign
| ModuloAssign => true,
// Property access binds to the right
Period => true,
// Exponentiation binds to the right
PowerOf => true,
@ -1026,7 +1024,7 @@ pub trait InputStream {
fn peek_next(&mut self) -> Option<char>;
}
/// _(internals)_ Parse a string literal ended by `termination_char`.
/// _(internals)_ Parse a string literal ended by a specified termination character.
/// Exported under the `internals` feature only.
///
/// Returns the parsed string and a boolean indicating whether the string is
@ -1034,7 +1032,7 @@ pub trait InputStream {
///
/// # Returns
///
/// |Type |Return Value |`state.is_within_text_terminated_by`|
/// | Type | Return Value |`state.is_within_text_terminated_by`|
/// |---------------------------------|:--------------------------:|:----------------------------------:|
/// |`"hello"` |`StringConstant("hello")` |`None` |
/// |`"hello`_{LF}_ or _{EOF}_ |`LexError` |`None` |
@ -1061,8 +1059,8 @@ pub fn parse_string_literal(
state: &mut TokenizeState,
pos: &mut Position,
termination_char: char,
continuation: bool,
verbatim: bool,
allow_line_continuation: bool,
allow_interpolation: bool,
) -> Result<(Box<str>, bool), (LexError, Position)> {
let mut result = String::with_capacity(12);
@ -1091,7 +1089,7 @@ pub fn parse_string_literal(
pos.advance();
break;
}
None if continuation && !escape.is_empty() => {
None if allow_line_continuation && !escape.is_empty() => {
assert_eq!(escape, "\\", "unexpected escape {} at end of line", escape);
pos.advance();
break;
@ -1159,7 +1157,7 @@ pub fn parse_string_literal(
'x' => 2,
'u' => 4,
'U' => 8,
_ => unreachable!(),
c => unreachable!("x or u or U expected but gets '{}'", c),
};
for _ in 0..len {
@ -1211,14 +1209,14 @@ pub fn parse_string_literal(
}
// Line continuation
'\n' if continuation && !escape.is_empty() => {
'\n' if allow_line_continuation && !escape.is_empty() => {
assert_eq!(escape, "\\", "unexpected escape {} at end of line", escape);
escape.clear();
pos.new_line();
#[cfg(not(feature = "no_position"))]
{
let start_position = start.position().expect("start position");
let start_position = start.position().unwrap();
skip_whitespace_until = start_position + 1;
}
}
@ -1239,8 +1237,7 @@ pub fn parse_string_literal(
// Whitespace to skip
#[cfg(not(feature = "no_position"))]
_ if next_char.is_whitespace()
&& pos.position().expect("position") < skip_whitespace_until => {}
_ if next_char.is_whitespace() && pos.position().unwrap() < skip_whitespace_until => {}
// All other characters
_ => {
@ -1319,7 +1316,7 @@ fn scan_block_comment(
level
}
/// _(internals)_ Get the next token from the `stream`.
/// _(internals)_ Get the next token from the input stream.
/// Exported under the `internals` feature only.
#[inline]
#[must_use]
@ -1355,9 +1352,7 @@ fn is_numeric_digit(c: char) -> bool {
#[cfg(feature = "metadata")]
#[inline]
#[must_use]
pub fn is_doc_comment(comment: impl AsRef<str>) -> bool {
let comment = comment.as_ref();
pub fn is_doc_comment(comment: &str) -> bool {
(comment.starts_with("///") && !comment.starts_with("////"))
|| (comment.starts_with("/**") && !comment.starts_with("/***"))
}
@ -1400,7 +1395,7 @@ fn get_next_token_inner(
if let Some(ch) = state.is_within_text_terminated_by.take() {
let start_pos = *pos;
return parse_string_literal(stream, state, pos, ch, false, true, true).map_or_else(
return parse_string_literal(stream, state, pos, ch, true, false, true).map_or_else(
|(err, err_pos)| Some((Token::LexError(err), err_pos)),
|(result, interpolated)| {
if interpolated {
@ -1506,14 +1501,14 @@ fn get_next_token_inner(
'x' | 'X' => is_hex_digit,
'o' | 'O' => is_numeric_digit,
'b' | 'B' => is_numeric_digit,
_ => unreachable!(),
c => unreachable!("x/X or o/O or b/B expected but gets '{}'", c),
};
radix_base = Some(match ch {
'x' | 'X' => 16,
'o' | 'O' => 8,
'b' | 'B' => 2,
_ => unreachable!(),
c => unreachable!("x/X or o/O or b/B expected but gets '{}'", c),
});
}
@ -1535,7 +1530,8 @@ fn get_next_token_inner(
.filter(|&&c| c != NUMBER_SEPARATOR)
.collect();
INT::from_str_radix(&out, radix)
UNSIGNED_INT::from_str_radix(&out, radix)
.map(|v| v as INT)
.map(Token::IntegerConstant)
.unwrap_or_else(|_| {
Token::LexError(LERR::MalformedNumber(result.into_iter().collect()))
@ -1583,7 +1579,7 @@ fn get_next_token_inner(
// " - string literal
('"', _) => {
return parse_string_literal(stream, state, pos, c, true, false, false)
return parse_string_literal(stream, state, pos, c, false, true, false)
.map_or_else(
|(err, err_pos)| Some((Token::LexError(err), err_pos)),
|(result, _)| Some((Token::StringConstant(result), start_pos)),
@ -1610,7 +1606,7 @@ fn get_next_token_inner(
_ => (),
}
return parse_string_literal(stream, state, pos, c, false, true, true).map_or_else(
return parse_string_literal(stream, state, pos, c, true, false, true).map_or_else(
|(err, err_pos)| Some((Token::LexError(err), err_pos)),
|(result, interpolated)| {
if interpolated {
@ -1635,7 +1631,7 @@ fn get_next_token_inner(
|(err, err_pos)| (Token::LexError(err), err_pos),
|(result, _)| {
let mut chars = result.chars();
let first = chars.next().expect("not empty");
let first = chars.next().unwrap();
if chars.next().is_some() {
(
@ -1675,6 +1671,17 @@ fn get_next_token_inner(
// Shebang
('#', '!') => return Some((Token::Reserved("#!".into()), start_pos)),
('#', ' ') => {
eat_next(stream, pos);
let token = if stream.peek_next() == Some('{') {
eat_next(stream, pos);
"# {"
} else {
"#"
};
return Some((Token::Reserved(token.into()), start_pos));
}
('#', _) => return Some((Token::Reserved("#".into()), start_pos)),
// Operators
@ -2006,8 +2013,8 @@ fn get_identifier(
/// Is this keyword allowed as a function?
#[inline]
#[must_use]
pub fn is_keyword_function(name: impl AsRef<str>) -> bool {
match name.as_ref() {
pub fn is_keyword_function(name: &str) -> bool {
match name {
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR => true,
@ -2036,11 +2043,11 @@ pub fn is_valid_identifier(name: impl Iterator<Item = char>) -> bool {
first_alphabetic
}
/// Is a text string a valid scripted function name?
/// Is a text string a valid script-defined function name?
#[inline(always)]
#[must_use]
pub fn is_valid_function_name(name: impl AsRef<str>) -> bool {
is_valid_identifier(name.as_ref().chars())
pub fn is_valid_function_name(name: &str) -> bool {
is_valid_identifier(name.chars())
}
/// Is a character valid to start an identifier?
@ -2202,19 +2209,14 @@ impl<'a> Iterator for TokenIterator<'a> {
("(*", false) | ("*)", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
"'(* .. *)' is not a valid comment format. This is not Pascal! Should it be '/* .. */'?".to_string(),
)),
("#", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
("# {", false) => Token::LexError(LERR::ImproperSymbol(s.to_string(),
"'#' is not a valid symbol. Should it be '#{'?".to_string(),
)),
// Reserved keyword/operator that is custom.
(_, true) => Token::Custom(s),
// Reserved operator that is not custom.
(token, false) if !is_valid_identifier(token.chars()) => {
let msg = format!("'{}' is a reserved symbol", token);
Token::LexError(LERR::ImproperSymbol(s.to_string(), msg))
},
// Reserved keyword that is not custom and disabled.
(token, false) if self.engine.disabled_symbols.contains(token) => {
let msg = format!("reserved symbol '{}' is disabled", token);
let msg = format!("reserved {} '{}' is disabled", if is_valid_identifier(token.chars()) { "keyword"} else {"symbol"}, token);
Token::LexError(LERR::ImproperSymbol(s.to_string(), msg))
},
// Reserved keyword/operator that is not custom.
@ -2224,7 +2226,7 @@ impl<'a> Iterator for TokenIterator<'a> {
Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.contains_key(&*s) => {
(Token::Custom(s), pos)
}
// Custom standard keyword/symbol - must be disabled
// Custom keyword/symbol - must be disabled
Some((token, pos)) if self.engine.custom_keywords.contains_key(&*token.syntax()) => {
if self.engine.disabled_symbols.contains(&*token.syntax()) {
// Disabled standard keyword/symbol

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
//! Module containing error definitions for the evaluation process.
use crate::{Dynamic, ImmutableString, ParseErrorType, Position, INT};
use crate::{Dynamic, ImmutableString, ParseErrorType, Position, RhaiError, INT};
#[cfg(feature = "no_std")]
use core_error::Error;
#[cfg(not(feature = "no_std"))]
@ -36,12 +36,12 @@ pub enum EvalAltResult {
ErrorFunctionNotFound(String, Position),
/// An error has occurred inside a called function.
/// Wrapped values are the function name, function source, and the interior error.
ErrorInFunctionCall(String, String, Box<EvalAltResult>, Position),
ErrorInFunctionCall(String, String, RhaiError, Position),
/// Usage of an unknown [module][crate::Module]. Wrapped value is the [module][crate::Module] name.
ErrorModuleNotFound(String, Position),
/// An error has occurred while loading a [module][crate::Module].
/// Wrapped value are the [module][crate::Module] name and the interior error.
ErrorInModule(String, Box<EvalAltResult>, Position),
ErrorInModule(String, RhaiError, Position),
/// Access to `this` that is not bound.
ErrorUnboundThis(Position),
/// Data is not of the required type.
@ -222,7 +222,7 @@ impl<T: AsRef<str>> From<T> for EvalAltResult {
}
}
impl<T: AsRef<str>> From<T> for Box<EvalAltResult> {
impl<T: AsRef<str>> From<T> for RhaiError {
#[inline(never)]
fn from(err: T) -> Self {
EvalAltResult::ErrorRuntime(err.as_ref().to_string().into(), Position::NONE).into()
@ -300,7 +300,7 @@ impl EvalAltResult {
format!("{:?}", self)
.split('(')
.next()
.expect("debug format of error is `ErrorXXX(...)`")
.expect("`ErrorXXX(...)`")
.into(),
);

View File

@ -3,8 +3,8 @@
use crate::tokenizer::is_valid_identifier;
use crate::types::dynamic::Variant;
use crate::{
Dynamic, Engine, EvalAltResult, FuncArgs, Identifier, Module, NativeCallContext, Position,
RhaiResult, StaticVec, AST,
Dynamic, Engine, FuncArgs, Identifier, Module, NativeCallContext, Position, RhaiError,
RhaiResult, RhaiResultOf, StaticVec, AST, ERR,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -24,7 +24,12 @@ impl fmt::Debug for FnPtr {
if !self.is_curried() {
write!(f, "Fn({})", self.fn_name())
} else {
f.debug_tuple("Fn").field(&self.0).field(&self.1).finish()
self.1
.iter()
.fold(f.debug_tuple("Fn").field(&self.0), |f, curry| {
f.field(curry)
})
.finish()
}
}
}
@ -32,7 +37,7 @@ impl fmt::Debug for FnPtr {
impl FnPtr {
/// Create a new function pointer.
#[inline(always)]
pub fn new(name: impl Into<Identifier>) -> Result<Self, Box<EvalAltResult>> {
pub fn new(name: impl Into<Identifier>) -> RhaiResultOf<Self> {
name.into().try_into()
}
/// Create a new function pointer without checking its parameters.
@ -135,7 +140,7 @@ impl FnPtr {
engine: &Engine,
ast: &AST,
args: impl FuncArgs,
) -> Result<T, Box<EvalAltResult>> {
) -> RhaiResultOf<T> {
let _ast = ast;
let mut arg_values = crate::StaticVec::new_const();
args.parse(&mut arg_values);
@ -157,7 +162,7 @@ impl FnPtr {
let typ = engine.map_type_name(result.type_name());
result.try_cast().ok_or_else(|| {
EvalAltResult::ErrorMismatchOutputType(
ERR::ErrorMismatchOutputType(
engine.map_type_name(type_name::<T>()).into(),
typ.into(),
Position::NONE,
@ -176,7 +181,7 @@ impl FnPtr {
&self,
context: &NativeCallContext,
args: impl FuncArgs,
) -> Result<T, Box<EvalAltResult>> {
) -> RhaiResultOf<T> {
let mut arg_values = crate::StaticVec::new_const();
args.parse(&mut arg_values);
@ -185,7 +190,7 @@ impl FnPtr {
let typ = context.engine().map_type_name(result.type_name());
result.try_cast().ok_or_else(|| {
EvalAltResult::ErrorMismatchOutputType(
ERR::ErrorMismatchOutputType(
context.engine().map_type_name(type_name::<T>()).into(),
typ.into(),
Position::NONE,
@ -204,10 +209,11 @@ impl FnPtr {
///
/// This function is very low level.
///
/// ## Arguments
/// # Arguments
///
/// All the arguments are _consumed_, meaning that they're replaced by `()`.
/// This is to avoid unnecessarily cloning the arguments.
///
/// Do not use the arguments after this call. If they are needed afterwards,
/// clone them _before_ calling this function.
#[inline]
@ -247,53 +253,53 @@ impl fmt::Display for FnPtr {
}
impl TryFrom<Identifier> for FnPtr {
type Error = Box<EvalAltResult>;
type Error = RhaiError;
#[inline]
fn try_from(value: Identifier) -> Result<Self, Self::Error> {
fn try_from(value: Identifier) -> RhaiResultOf<Self> {
if is_valid_identifier(value.chars()) {
Ok(Self(value, StaticVec::new_const()))
} else {
Err(EvalAltResult::ErrorFunctionNotFound(value.to_string(), Position::NONE).into())
Err(ERR::ErrorFunctionNotFound(value.to_string(), Position::NONE).into())
}
}
}
impl TryFrom<crate::ImmutableString> for FnPtr {
type Error = Box<EvalAltResult>;
type Error = RhaiError;
#[inline(always)]
fn try_from(value: crate::ImmutableString) -> Result<Self, Self::Error> {
fn try_from(value: crate::ImmutableString) -> RhaiResultOf<Self> {
let s: Identifier = value.into();
Self::try_from(s)
}
}
impl TryFrom<String> for FnPtr {
type Error = Box<EvalAltResult>;
type Error = RhaiError;
#[inline(always)]
fn try_from(value: String) -> Result<Self, Self::Error> {
fn try_from(value: String) -> RhaiResultOf<Self> {
let s: Identifier = value.into();
Self::try_from(s)
}
}
impl TryFrom<Box<str>> for FnPtr {
type Error = Box<EvalAltResult>;
type Error = RhaiError;
#[inline(always)]
fn try_from(value: Box<str>) -> Result<Self, Self::Error> {
fn try_from(value: Box<str>) -> RhaiResultOf<Self> {
let s: Identifier = value.into();
Self::try_from(s)
}
}
impl TryFrom<&str> for FnPtr {
type Error = Box<EvalAltResult>;
type Error = RhaiError;
#[inline(always)]
fn try_from(value: &str) -> Result<Self, Self::Error> {
fn try_from(value: &str) -> RhaiResultOf<Self> {
let s: Identifier = value.into();
Self::try_from(s)
}

95
src/types/interner.rs Normal file
View File

@ -0,0 +1,95 @@
use crate::{Identifier, ImmutableString};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
use std::{collections::BTreeMap, ops::AddAssign};
/// _(internals)_ A factory of identifiers from text strings.
/// Exported under the `internals` feature only.
///
/// Normal identifiers, property getters and setters are interned separately.
#[derive(Debug, Clone, Default, Hash)]
pub struct StringsInterner {
/// Normal strings.
strings: BTreeMap<Identifier, ImmutableString>,
/// Property getters.
#[cfg(not(feature = "no_object"))]
getters: BTreeMap<Identifier, ImmutableString>,
/// Property setters.
#[cfg(not(feature = "no_object"))]
setters: BTreeMap<Identifier, ImmutableString>,
}
impl StringsInterner {
/// Create a new [`StringsInterner`].
#[inline]
#[must_use]
pub fn new() -> Self {
Self {
strings: BTreeMap::new(),
#[cfg(not(feature = "no_object"))]
getters: BTreeMap::new(),
#[cfg(not(feature = "no_object"))]
setters: BTreeMap::new(),
}
}
/// Get an identifier from a text string and prefix, adding it to the interner if necessary.
///
/// # Prefix
///
/// Currently recognized prefixes are:
///
/// * `""` - None (normal string)
/// * `"get$"` - Property getter, not available under `no_object`
/// * `"set$"` - Property setter, not available under `no_object`
///
/// # Panics
///
/// Panics if the prefix is not recognized.
#[inline]
#[must_use]
pub fn get(&mut self, prefix: impl AsRef<str>, text: impl AsRef<str>) -> ImmutableString {
let (dict, mapper): (_, fn(&str) -> Identifier) = match prefix.as_ref() {
"" => (&mut self.strings, |s| s.into()),
#[cfg(not(feature = "no_object"))]
crate::engine::FN_GET => (&mut self.getters, crate::engine::make_getter),
#[cfg(not(feature = "no_object"))]
crate::engine::FN_SET => (&mut self.setters, crate::engine::make_setter),
_ => unreachable!("unsupported prefix {}", prefix.as_ref()),
};
if dict.contains_key(text.as_ref()) {
dict.get(text.as_ref()).unwrap().clone()
} else {
let value: ImmutableString = mapper(text.as_ref()).into();
dict.insert(text.as_ref().into(), value.clone());
value
}
}
}
impl AddAssign<Self> for StringsInterner {
#[inline(always)]
fn add_assign(&mut self, rhs: Self) {
self.strings.extend(rhs.strings.into_iter());
#[cfg(not(feature = "no_object"))]
self.getters.extend(rhs.getters.into_iter());
#[cfg(not(feature = "no_object"))]
self.setters.extend(rhs.setters.into_iter());
}
}
impl AddAssign<&Self> for StringsInterner {
#[inline(always)]
fn add_assign(&mut self, rhs: &Self) {
self.strings
.extend(rhs.strings.iter().map(|(k, v)| (k.clone(), v.clone())));
#[cfg(not(feature = "no_object"))]
self.getters
.extend(rhs.getters.iter().map(|(k, v)| (k.clone(), v.clone())));
#[cfg(not(feature = "no_object"))]
self.setters
.extend(rhs.setters.iter().map(|(k, v)| (k.clone(), v.clone())));
}
}

View File

@ -4,6 +4,7 @@ pub mod dynamic;
pub mod error;
pub mod fn_ptr;
pub mod immutable_string;
pub mod interner;
pub mod parse_error;
pub mod scope;
@ -11,5 +12,6 @@ pub use dynamic::Dynamic;
pub use error::EvalAltResult;
pub use fn_ptr::FnPtr;
pub use immutable_string::ImmutableString;
pub use interner::StringsInterner;
pub use parse_error::{LexError, ParseError, ParseErrorType};
pub use scope::Scope;

View File

@ -1,6 +1,7 @@
//! Module containing error definitions for the parsing process.
use crate::{EvalAltResult, Position};
use crate::tokenizer::is_valid_identifier;
use crate::{Position, RhaiError, ERR};
#[cfg(feature = "no_std")]
use core_error::Error;
#[cfg(not(feature = "no_std"))]
@ -65,7 +66,7 @@ impl LexError {
}
}
/// Type of error encountered when parsing a script.
/// Error encountered when parsing a script.
///
/// Some errors never appear when certain features are turned on.
/// They still exist so that the application can turn features on and off without going through
@ -108,9 +109,9 @@ pub enum ParseErrorType {
DuplicatedSwitchCase,
/// A variable name is duplicated. Wrapped value is the variable name.
DuplicatedVariable(String),
/// An integer case of a `switch` statement is after a range case.
/// An integer case of a `switch` statement is in an appropriate place.
WrongSwitchIntegerCase,
/// The default case of a `switch` statement is not the last.
/// The default case of a `switch` statement is in an appropriate place.
WrongSwitchDefaultCase,
/// The case condition of a `switch` statement is not appropriate.
WrongSwitchCaseCondition,
@ -120,7 +121,7 @@ pub enum ParseErrorType {
PropertyExpected,
/// Missing a variable name after the `let`, `const`, `for` or `catch` keywords.
VariableExpected,
/// An identifier is a reserved keyword.
/// An identifier is a reserved symbol.
Reserved(String),
/// An expression is of the wrong type.
/// Wrapped values are the type requested and type of the actual result.
@ -260,7 +261,8 @@ impl fmt::Display for ParseErrorType {
},
Self::LiteralTooLarge(typ, max) => write!(f, "{} exceeds the maximum limit ({})", typ, max),
Self::Reserved(s) => write!(f, "'{}' is a reserved keyword", s),
Self::Reserved(s) if is_valid_identifier(s.chars()) => write!(f, "'{}' is a reserved keyword", s),
Self::Reserved(s) => write!(f, "'{}' is a reserved symbol", s),
Self::UnexpectedEOF => f.write_str("Script is incomplete"),
Self::WrongSwitchIntegerCase => f.write_str("Integer switch case cannot follow a range case"),
Self::WrongSwitchDefaultCase => f.write_str("Default switch case must be the last"),
@ -308,30 +310,30 @@ impl fmt::Display for ParseError {
}
}
impl From<ParseErrorType> for Box<EvalAltResult> {
impl From<ParseErrorType> for RhaiError {
#[inline(always)]
fn from(err: ParseErrorType) -> Self {
Box::new(err.into())
}
}
impl From<ParseErrorType> for EvalAltResult {
impl From<ParseErrorType> for ERR {
#[inline(always)]
fn from(err: ParseErrorType) -> Self {
EvalAltResult::ErrorParsing(err, Position::NONE)
ERR::ErrorParsing(err, Position::NONE)
}
}
impl From<ParseError> for Box<EvalAltResult> {
impl From<ParseError> for RhaiError {
#[inline(always)]
fn from(err: ParseError) -> Self {
Box::new(err.into())
}
}
impl From<ParseError> for EvalAltResult {
impl From<ParseError> for ERR {
#[inline(always)]
fn from(err: ParseError) -> Self {
EvalAltResult::ErrorParsing(*err.0, err.1)
ERR::ErrorParsing(*err.0, err.1)
}
}

View File

@ -406,7 +406,7 @@ impl<'a> Scope<'a> {
self.push(name, value);
}
Some((index, AccessMode::ReadWrite)) => {
let value_ref = self.values.get_mut(index).expect("valid index");
let value_ref = self.values.get_mut(index).unwrap();
*value_ref = Dynamic::from(value);
}
}
@ -446,7 +446,7 @@ impl<'a> Scope<'a> {
}
Some((_, AccessMode::ReadOnly)) => panic!("variable {} is constant", name.as_ref()),
Some((index, AccessMode::ReadWrite)) => {
let value_ref = self.values.get_mut(index).expect("valid index");
let value_ref = self.values.get_mut(index).unwrap();
*value_ref = Dynamic::from(value);
}
}
@ -492,7 +492,7 @@ impl<'a> Scope<'a> {
#[inline]
#[must_use]
pub(crate) fn get_mut_by_index(&mut self, index: usize) -> &mut Dynamic {
self.values.get_mut(index).expect("valid index")
self.values.get_mut(index).unwrap()
}
/// Add an alias to an entry in the [`Scope`].
///
@ -502,7 +502,7 @@ impl<'a> Scope<'a> {
#[cfg(not(feature = "no_module"))]
#[inline]
pub(crate) fn add_entry_alias(&mut self, index: usize, alias: Identifier) -> &mut Self {
let (_, aliases) = self.names.get_mut(index).expect("valid index");
let (_, aliases) = self.names.get_mut(index).unwrap();
match aliases {
None => {
let mut list = StaticVec::new_const();

View File

@ -8,35 +8,48 @@ use std::{
};
/// Cast a type into another type.
#[inline]
pub fn unsafe_try_cast<A: Any, B: Any>(a: A) -> Result<B, A> {
///
/// # Undefined Behavior
///
/// It is UB if the types are not compatible.
#[inline(always)]
#[must_use]
pub fn unsafe_cast<A: Any, B: Any>(a: A) -> B {
unsafe {
let ret: B = ptr::read(&a as *const _ as *const B);
// We explicitly forget the value immediately after moving out,
// removing any chance of a destructor running or value otherwise
// being used again.
mem::forget(a);
ret
}
}
/// Cast a type into another type.
#[inline(always)]
#[must_use]
pub fn unsafe_try_cast<A: Any, B: Any>(a: A) -> Option<B> {
if TypeId::of::<B>() == a.type_id() {
// SAFETY: Just checked we have the right type. We explicitly forget the
// value immediately after moving out, removing any chance of a destructor
// running or value otherwise being used again.
unsafe {
let ret: B = ptr::read(&a as *const _ as *const B);
mem::forget(a);
Ok(ret)
}
// SAFETY: Just checked we have the right type.
Some(unsafe_cast(a))
} else {
Err(a)
None
}
}
/// Cast a Boxed type into another type.
#[inline]
pub fn unsafe_cast_box<X: Any, T: Any>(item: Box<X>) -> Result<Box<T>, Box<X>> {
#[inline(always)]
#[must_use]
pub fn unsafe_cast_box<X: Any, T: Any>(item: Box<X>) -> Option<T> {
// Only allow casting to the exact same type
if TypeId::of::<X>() == TypeId::of::<T>() {
// SAFETY: just checked whether we are pointing to the correct type
unsafe {
let raw: *mut dyn Any = Box::into_raw(item as Box<dyn Any>);
Ok(Box::from_raw(raw as *mut T))
Some(*Box::from_raw(raw as *mut T))
}
} else {
// Return the consumed item for chaining.
Err(item)
None
}
}

View File

@ -162,6 +162,13 @@ fn test_blobs_parse() -> Result<(), Box<EvalAltResult>> {
0x1cf588
);
assert_eq!(
engine.eval::<Blob>(
"let x = blob(16, 0); write_be(x, 0, 8, 0x1234567890abcdef); write_be(x, 8, 8, 0xabcdef1234567890); x"
)?,
vec![0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90]
);
Ok(())
}
@ -233,3 +240,37 @@ fn test_blobs_parse() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[test]
fn test_blobs_write_string() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(
engine.eval::<Blob>(r#"let x = blob(16, 0); write_ascii(x, 0, 14, "hello, world!"); x"#)?,
"hello, world!\0\0\0".as_bytes()
);
assert_eq!(
engine.eval::<Blob>(r#"let x = blob(10, 0); write_ascii(x, 3..8, "hello, world!"); x"#)?,
"\0\0\0hello\0\0".as_bytes()
);
assert_eq!(
engine.eval::<Blob>(
r#"let x = blob(10, 0); write_ascii(x, 0..9, "❤ hello, ❤ world! ❤❤❤"); x"#
)?,
" hello, \0".as_bytes()
);
assert_eq!(
engine.eval::<Blob>(r#"let x = blob(10, 0); write_utf8(x, 3..9, "❤❤❤❤"); x"#)?,
"\0\0\0\u{2764}\u{2764}\0".as_bytes()
);
assert_eq!(
engine.eval::<Blob>(r#"let x = blob(10, 0); write_utf8(x, 3..7, "❤❤❤❤"); x"#)?,
vec![0, 0, 0, 226, 157, 164, 226, 0, 0, 0]
);
Ok(())
}

View File

@ -69,6 +69,36 @@ fn test_call_fn() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[test]
fn test_call_fn_scope() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
let mut scope = Scope::new();
let ast = engine.compile(
"
fn foo(x) {
let hello = 42;
bar + hello + x
}
let bar = 123;
",
)?;
for _ in 0..50 {
assert_eq!(
engine
.call_fn_raw(&mut scope, &ast, true, false, "foo", None, [Dynamic::THREE])?
.as_int()?,
168
);
}
assert_eq!(scope.len(), 100);
Ok(())
}
struct Options {
pub foo: bool,
pub bar: String,
@ -296,7 +326,7 @@ fn test_call_fn_events() -> Result<(), Box<EvalAltResult>> {
"end" => engine.call_fn(scope, ast, "end", (event_data,)).unwrap(),
// The 'update' event maps to function 'update'.
// This event provides a default implementation when the scripted function is not found.
// This event provides a default implementation when the script-defined function is not found.
"update" => engine
.call_fn(scope, ast, "update", (event_data,))
.or_else(|err| match *err {

View File

@ -25,13 +25,13 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
],
true,
|context, inputs| {
let var_name = inputs[0].get_variable_name().unwrap().to_string();
let var_name = inputs[0].get_string_value().unwrap();
let op = inputs[1].get_literal_value::<ImmutableString>().unwrap();
let max = inputs[2].get_literal_value::<INT>().unwrap();
let stmt = &inputs[3];
let condition = &inputs[4];
context.scope_mut().push(var_name.clone(), 0 as INT);
context.scope_mut().push(var_name.to_string(), 0 as INT);
let mut count: INT = 0;
@ -151,14 +151,14 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
&["var", "$ident$", "=", "$expr$"],
true,
|context, inputs| {
let var_name = inputs[0].get_variable_name().unwrap().to_string();
let var_name = inputs[0].get_string_value().unwrap();
let expr = &inputs[1];
// Evaluate the expression
let value = context.eval_expression_tree(expr)?;
if !context.scope().is_constant(&var_name).unwrap_or(false) {
context.scope_mut().set_value(var_name, value);
if !context.scope().is_constant(var_name).unwrap_or(false) {
context.scope_mut().set_value(var_name.to_string(), value);
Ok(Dynamic::UNIT)
} else {
Err(format!("variable {} is constant", var_name).into())
@ -206,7 +206,7 @@ fn test_custom_syntax_raw() -> Result<(), Box<EvalAltResult>> {
|context, inputs| {
context.scope_mut().push("foo", 999 as INT);
Ok(match inputs[0].get_variable_name().unwrap() {
Ok(match inputs[0].get_string_value().unwrap() {
"world"
if inputs
.last()
@ -239,3 +239,38 @@ fn test_custom_syntax_raw() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[test]
fn test_custom_syntax_raw2() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine.register_custom_syntax_raw(
"#",
|symbols, lookahead| match symbols.len() {
1 if lookahead == "-" => Ok(Some("$symbol$".into())),
1 => Ok(Some("$int$".into())),
2 if symbols[1] == "-" => Ok(Some("$int$".into())),
2 => Ok(None),
3 => Ok(None),
_ => unreachable!(),
},
false,
move |_, inputs| {
let id = if inputs.len() == 2 {
-inputs[1].get_literal_value::<INT>().unwrap()
} else {
inputs[0].get_literal_value::<INT>().unwrap()
};
Ok(id.into())
},
);
assert_eq!(engine.eval::<INT>("#-1")?, -1);
assert_eq!(engine.eval::<INT>("let x = 41; x + #1")?, 42);
#[cfg(not(feature = "no_object"))]
assert_eq!(engine.eval::<INT>("#-42.abs()")?, 42);
assert_eq!(engine.eval::<INT>("#42/2")?, 21);
assert_eq!(engine.eval::<INT>("sign(#1)")?, 1);
Ok(())
}

View File

@ -30,7 +30,7 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
assert!(matches!(
*engine
.eval::<String>(
.run(
r#"
let x = "hello, ";
let y = "world!";
@ -44,7 +44,7 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
assert!(matches!(
*engine
.eval::<String>(
.run(
r#"
let x = "hello";
x.pad(100, '!');
@ -90,7 +90,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
assert!(matches!(
*engine
.eval::<Array>(
.run(
"
let x = [1,2,3,4,5,6];
let y = [7,8,9,10,11,12];
@ -101,10 +101,22 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
EvalAltResult::ErrorDataTooLarge(_, _)
));
assert!(matches!(
*engine
.run(
"
let x = [ 42 ];
loop { x[0] = x; }
"
)
.expect_err("should error"),
EvalAltResult::ErrorDataTooLarge(_, _)
));
#[cfg(not(feature = "no_object"))]
assert!(matches!(
*engine
.eval::<Array>(
.run(
"
let x = [1,2,3,4,5,6];
x.pad(100, 42);
@ -117,7 +129,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
assert!(matches!(
*engine
.eval::<Array>(
.run(
"
let x = [1,2,3];
[x, x, x, x]
@ -130,7 +142,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_object"))]
assert!(matches!(
*engine
.eval::<Array>(
.run(
"
let x = #{a:1, b:2, c:3};
[x, x, x, x]
@ -142,7 +154,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
assert!(matches!(
*engine
.eval::<Array>(
.run(
"
let x = [1];
let y = [x, x];
@ -220,7 +232,7 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
assert!(matches!(
*engine
.eval::<Map>(
.run(
"
let x = #{a:1,b:2,c:3,d:4,e:5,f:6};
let y = #{g:7,h:8,i:9,j:10,k:11,l:12};
@ -233,7 +245,7 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
assert!(matches!(
*engine
.eval::<Map>(
.run(
"
let x = #{a:1,b:2,c:3};
#{u:x, v:x, w:x, z:x}
@ -246,7 +258,7 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))]
assert!(matches!(
*engine
.eval::<Map>(
.run(
"
let x = [1, 2, 3];
#{u:x, v:x, w:x, z:x}

View File

@ -6,6 +6,20 @@ fn test_eval() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>(r#"eval("40 + 2")"#)?, 42);
assert_eq!(
engine.eval::<INT>(
r#"
let foo = 42;
eval("let foo = 123");
eval("let xyz = 10");
foo + xyz
"#
)?,
133
);
Ok(())
}
@ -18,9 +32,9 @@ fn test_eval_blocks() -> Result<(), Box<EvalAltResult>> {
r#"
let x = 999;
eval("let x = x + 123");
eval("let x = x - 1000");
let y = if x > 0 {
let y = if x < 0 {
eval("let x = 42");
x
} else {
@ -30,7 +44,73 @@ fn test_eval_blocks() -> Result<(), Box<EvalAltResult>> {
x + y
"#
)?,
1164
41
);
assert_eq!(
engine.eval::<INT>(
r#"
let foo = 42;
eval("{ let foo = 123; }");
foo
"#
)?,
42
);
assert_eq!(
engine.eval::<INT>(
r#"
let foo = 42;
{ { {
eval("let foo = 123");
} } }
foo
"#
)?,
42
);
Ok(())
}
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_module"))]
#[test]
fn test_eval_globals() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(
engine.eval::<INT>(
r#"
const XYZ = 123;
fn foo() { global::XYZ }
{
eval("const XYZ = 42;");
}
foo()
"#
)?,
123
);
assert_eq!(
engine.eval::<INT>(
r#"
const XYZ = 123;
fn foo() { global::XYZ }
eval("const XYZ = 42;");
foo()
"#
)?,
42
);
Ok(())

View File

@ -371,3 +371,32 @@ fn test_for_module_iterator() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[test]
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_closure"))]
fn test_for_capture() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(
engine.eval::<INT>(
"
let a = [];
for (x, i) in 100..110 {
a += || i + x;
}
let sum = 0;
for fp in a {
sum += call(fp);
}
sum
"
)?,
1180
);
Ok(())
}

View File

@ -181,5 +181,43 @@ fn test_functions_bang() -> Result<(), Box<EvalAltResult>> {
123
);
assert_eq!(
engine.eval::<INT>(
"
fn foo() {
let hello = bar + 42;
}
let bar = 999;
let hello = 123;
foo!();
hello
",
)?,
123
);
assert_eq!(
engine.eval::<INT>(
r#"
fn foo(x) {
let hello = bar + 42 + x;
}
let bar = 999;
let hello = 123;
let f = Fn("foo");
call!(f, 1);
hello
"#,
)?,
123
);
Ok(())
}

View File

@ -27,6 +27,11 @@ fn test_hex_literal() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>("let x = 0Xf; x")?, 15);
assert_eq!(engine.eval::<INT>("let x = 0xff; x")?, 255);
#[cfg(not(feature = "only_i32"))]
assert_eq!(engine.eval::<INT>("let x = 0xffffffffffffffff; x")?, -1);
#[cfg(feature = "only_i32")]
assert_eq!(engine.eval::<INT>("let x = 0xffffffff; x")?, -1);
Ok(())
}
@ -51,6 +56,18 @@ fn test_binary_literal() -> Result<(), Box<EvalAltResult>> {
engine.eval::<INT>("let x = 0b0011_1100_1010_0101; x")?,
15525
);
#[cfg(not(feature = "only_i32"))]
assert_eq!(
engine.eval::<INT>(
"let x = 0b11111111_11111111_11111111_11111111_11111111_11111111_11111111_11111111; x"
)?,
-1
);
#[cfg(feature = "only_i32")]
assert_eq!(
engine.eval::<INT>("let x = 0b11111111_11111111_11111111_11111111; x")?,
-1
);
Ok(())
}

View File

@ -10,8 +10,10 @@ fn test_ops() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
#[test]
fn test_ops_numbers() -> Result<(), Box<EvalAltResult>> {
fn test_ops_other_number_types() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
let mut scope = Scope::new();

Some files were not shown because too many files have changed in this diff Show More