Merge pull request #511 from schungx/master

Fix bugs.
This commit is contained in:
Stephen Chung 2022-01-23 21:37:17 +08:00 committed by GitHub
commit 817d9aef34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 1257 additions and 292 deletions

View File

@ -67,9 +67,9 @@ jobs:
strategy: strategy:
matrix: matrix:
include: include:
- {os: ubuntu-latest, flags: "--profile unix -Z unstable-options", experimental: false} - {os: ubuntu-latest, flags: "--profile unix", experimental: false}
- {os: windows-latest, flags: "--profile windows -Z unstable-options", experimental: true} - {os: windows-latest, flags: "--profile windows", experimental: true}
- {os: macos-latest, flags: "--profile macos -Z unstable-options", experimental: false} - {os: macos-latest, flags: "--profile macos", experimental: false}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2

View File

@ -1,12 +1,13 @@
Rhai Release Notes Rhai Release Notes
================== ==================
Version 1.5.0 Version 1.4.1
============= =============
Bug fixes Bug fixes
--------- ---------
* Expressions such as `x = x + 1` no longer panics.
* Padding arrays with another array via `pad` no longer loops indefinitely. * Padding arrays with another array via `pad` no longer loops indefinitely.
* `chop` for arrays and BLOB's now works properly. * `chop` for arrays and BLOB's now works properly.
* `set_bit` for bit-flags with negative index now works correctly. * `set_bit` for bit-flags with negative index now works correctly.
@ -15,6 +16,8 @@ Bug fixes
* Missing `to_hex`, `to_octal` and `to_binary` for `i128` and `u128` are added. * Missing `to_hex`, `to_octal` and `to_binary` for `i128` and `u128` are added.
* `remove` for arrays and BLOB's now treat negative index correctly. * `remove` for arrays and BLOB's now treat negative index correctly.
* `parse_int` now works properly for negative numbers. * `parse_int` now works properly for negative numbers.
* `Engine::gen_fn_signatures` now generates signatures for external packages registered via `Engine::register_global_module`.
* `\r\n` pairs are now recognized correctly for doc-comments.
Enhancements Enhancements
------------ ------------
@ -22,6 +25,7 @@ Enhancements
* Formatting of return types in functions metadata info is improved. * Formatting of return types in functions metadata info is improved.
* Use `SmartString` for `Scope` variable names and remove `unsafe` lifetime casting. * Use `SmartString` for `Scope` variable names and remove `unsafe` lifetime casting.
* Functions in the standard library now have doc-comments (which can be obtained via `Engine::gen_fn_metadata_to_json`). * Functions in the standard library now have doc-comments (which can be obtained via `Engine::gen_fn_metadata_to_json`).
* `get` and `set` methods are added to arrays, BLOB's, object maps and strings.
Version 1.4.0 Version 1.4.0

View File

@ -3,7 +3,7 @@
///! Test evaluating with scope ///! Test evaluating with scope
extern crate test; extern crate test;
use rhai::{Engine, Module, OptimizationLevel}; use rhai::{Engine, Module, OptimizationLevel, Scope};
use test::Bencher; use test::Bencher;
#[bench] #[bench]
@ -18,7 +18,7 @@ fn bench_eval_module(bench: &mut Bencher) {
let ast = engine.compile(script).unwrap(); let ast = engine.compile(script).unwrap();
let module = Module::eval_ast_as_new(Default::default(), &ast, &engine).unwrap(); let module = Module::eval_ast_as_new(Scope::new(), &ast, &engine).unwrap();
engine.register_static_module("testing", module.into()); engine.register_static_module("testing", module.into());

View File

@ -142,15 +142,14 @@ pub fn inner_item_attributes<T: ExportedParams>(
} }
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
pub fn doc_attributes(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Vec<String>> { pub fn doc_attributes(attrs: &[syn::Attribute]) -> syn::Result<Vec<String>> {
// Find the #[doc] attribute which will turn be read for function documentation. // Find the #[doc] attribute which will turn be read for function documentation.
let mut comments = Vec::new(); let mut comments = Vec::new();
while let Some(index) = attrs for attr in attrs {
.iter() if let Some(i) = attr.path.get_ident() {
.position(|attr| attr.path.get_ident().map(|i| *i == "doc").unwrap_or(false)) if *i == "doc" {
{ match attr.parse_meta()? {
match attrs.remove(index).parse_meta()? {
syn::Meta::NameValue(syn::MetaNameValue { syn::Meta::NameValue(syn::MetaNameValue {
lit: syn::Lit::Str(s), lit: syn::Lit::Str(s),
.. ..
@ -168,7 +167,9 @@ pub fn doc_attributes(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Vec<String
comments.push(line); comments.push(line);
} }
_ => continue, _ => (),
}
}
} }
} }

View File

@ -407,7 +407,7 @@ impl Parse for ExportedFn {
params: Default::default(), params: Default::default(),
cfg_attrs, cfg_attrs,
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
comments: Default::default(), comments: Vec::new(),
}) })
} }
} }

View File

@ -127,7 +127,7 @@ impl Parse for Module {
f.set_cfg_attrs(crate::attrs::collect_cfg_attr(&item_fn.attrs)); f.set_cfg_attrs(crate::attrs::collect_cfg_attr(&item_fn.attrs));
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
f.set_comments(crate::attrs::doc_attributes(&mut item_fn.attrs)?); f.set_comments(crate::attrs::doc_attributes(&item_fn.attrs)?);
Ok(f) Ok(f)
})?; })?;
@ -144,12 +144,12 @@ impl Parse for Module {
attrs, attrs,
ty, ty,
.. ..
}) if matches!(vis, syn::Visibility::Public(_)) => consts.push(( }) if matches!(vis, syn::Visibility::Public(_)) => consts.push(ExportedConst {
ident.to_string(), name: ident.to_string(),
ty.clone(), typ: ty.clone(),
expr.as_ref().clone(), expr: expr.as_ref().clone(),
crate::attrs::collect_cfg_attr(&attrs), cfg_attrs: crate::attrs::collect_cfg_attr(&attrs),
)), }),
_ => {} _ => {}
} }
} }

View File

@ -10,7 +10,13 @@ use crate::function::{
}; };
use crate::module::Module; use crate::module::Module;
pub type ExportedConst = (String, Box<syn::Type>, syn::Expr, Vec<syn::Attribute>); #[derive(Debug)]
pub struct ExportedConst {
pub name: String,
pub typ: Box<syn::Type>,
pub expr: syn::Expr,
pub cfg_attrs: Vec<syn::Attribute>,
}
pub fn generate_body( pub fn generate_body(
fns: &mut [ExportedFn], fns: &mut [ExportedFn],
@ -25,7 +31,12 @@ pub fn generate_body(
let str_type_path = syn::parse2::<syn::Path>(quote! { str }).unwrap(); let str_type_path = syn::parse2::<syn::Path>(quote! { str }).unwrap();
let string_type_path = syn::parse2::<syn::Path>(quote! { String }).unwrap(); let string_type_path = syn::parse2::<syn::Path>(quote! { String }).unwrap();
for (const_name, _, _, cfg_attrs) in consts { for ExportedConst {
name: const_name,
cfg_attrs,
..
} in consts
{
let const_literal = syn::LitStr::new(&const_name, Span::call_site()); let const_literal = syn::LitStr::new(&const_name, Span::call_site());
let const_ref = syn::Ident::new(&const_name, Span::call_site()); let const_ref = syn::Ident::new(&const_name, Span::call_site());

View File

@ -38,6 +38,7 @@ mod module_tests {
} }
#[test] #[test]
#[cfg(feature = "metadata")]
fn one_factory_fn_with_comments_module() { fn one_factory_fn_with_comments_module() {
let input_tokens: TokenStream = quote! { let input_tokens: TokenStream = quote! {
pub mod one_fn { pub mod one_fn {
@ -150,9 +151,9 @@ mod module_tests {
assert!(item_mod.fns().is_empty()); assert!(item_mod.fns().is_empty());
assert!(item_mod.consts().is_empty()); assert!(item_mod.consts().is_empty());
assert_eq!(item_mod.sub_modules().len(), 1); assert_eq!(item_mod.sub_modules().len(), 1);
assert_eq!(&item_mod.sub_modules()[0].consts()[0].0, "MYSTIC_NUMBER"); assert_eq!(&item_mod.sub_modules()[0].consts()[0].name, "MYSTIC_NUMBER");
assert_eq!( assert_eq!(
item_mod.sub_modules()[0].consts()[0].2, item_mod.sub_modules()[0].consts()[0].expr,
syn::parse2::<syn::Expr>(quote! { 42 }).unwrap() syn::parse2::<syn::Expr>(quote! { 42 }).unwrap()
); );
} }
@ -211,9 +212,9 @@ mod module_tests {
let item_mod = syn::parse2::<Module>(input_tokens).unwrap(); let item_mod = syn::parse2::<Module>(input_tokens).unwrap();
assert!(item_mod.fns().is_empty()); assert!(item_mod.fns().is_empty());
assert_eq!(item_mod.consts().len(), 1); assert_eq!(item_mod.consts().len(), 1);
assert_eq!(&item_mod.consts()[0].0, "MYSTIC_NUMBER"); assert_eq!(&item_mod.consts()[0].name, "MYSTIC_NUMBER");
assert_eq!( assert_eq!(
item_mod.consts()[0].2, item_mod.consts()[0].expr,
syn::parse2::<syn::Expr>(quote! { 42 }).unwrap() syn::parse2::<syn::Expr>(quote! { 42 }).unwrap()
); );
} }
@ -386,6 +387,14 @@ mod generate_tests {
let expected_tokens = quote! { let expected_tokens = quote! {
pub mod one_fn { 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 { pub fn get_mystic_number() -> INT {
42 42
} }
@ -401,8 +410,13 @@ mod generate_tests {
#[allow(unused_mut)] #[allow(unused_mut)]
pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) {
m.set_fn_with_comments("get_mystic_number", FnNamespace::Internal, FnAccess::Public, 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 */"], Some(get_mystic_number_token::PARAM_NAMES), &[], &[
get_mystic_number_token().into()); "/// 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 {} if flatten {} else {}
} }
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]

View File

@ -1,5 +1,5 @@
print("x should be 78:"); // This script contains a single assignment statement.
let x = 78; let x = 78;
print(x); print(`x should be 78: ${x}`);

View File

@ -3,9 +3,9 @@
/// ///
/// # Parameters /// # Parameters
/// ///
/// `x` - `i64` /// * `x` - `i64`
/// `y` - `string` /// * `y` - `string`
/// `z` - `bool` /// * `z` - `bool`
/// ///
/// # Notes /// # Notes
/// ///
@ -13,6 +13,13 @@
/// ///
/// An example is the `rhai-doc` app. /// An example is the `rhai-doc` app.
/// ///
/// # Example
///
/// ```rhai
/// let x = foo(42, "hello", true);
///
/// print(x); // prints 47
/// ```
fn foo(x, y, z) { fn foo(x, y, z) {
print(`hello, world! ${if z { x + y.len() } else { x } }`); print(`hello, world! ${if z { x + y.len() } else { x } }`);
} }

View File

@ -1,7 +1,8 @@
// This script runs for-loops // This script runs for-loops.
let arr = [1, true, 123.456, "hello", 3, 42]; let arr = [1, true, 123.456, "hello", 3, 42];
// Loop over array with counter
for (a, i) in arr { for (a, i) in arr {
for (b, j) in ['x', 42, (), 123, 99, 0.5] { for (b, j) in ['x', 42, (), 123, 99, 0.5] {
if b > 100 { continue; } if b > 100 { continue; }

View File

@ -1,3 +1,5 @@
// This script runs for-loops
const MAX = 1_000_000; const MAX = 1_000_000;
print(`Iterating an array with ${MAX} items...`); print(`Iterating an array with ${MAX} items...`);
@ -8,6 +10,7 @@ let now = timestamp();
let list = []; let list = [];
// Loop over range
for i in 0..MAX { for i in 0..MAX {
list.push(i); list.push(i);
} }
@ -16,6 +19,7 @@ print(`Time = ${now.elapsed} seconds...`);
let sum = 0; let sum = 0;
// Loop over array
for i in list { for i in list {
sum += i; sum += i;
} }

View File

@ -1,3 +1,5 @@
// This script runs for-loops with closures.
const MAX = 100; const MAX = 100;
const CHECK = ((MAX - 1) ** 2) * MAX; const CHECK = ((MAX - 1) ** 2) * MAX;
@ -9,6 +11,7 @@ print(`Creating ${MAX} closures...`);
let list = []; let list = [];
// Loop over range
for i in 0..MAX { for i in 0..MAX {
list.push(|| i ** 2); list.push(|| i ** 2);
} }
@ -18,6 +21,7 @@ print(`Summing ${MAX} closures...`);
let sum = 0; let sum = 0;
// Loop over array
for f in list { for f in list {
sum += f.call(); sum += f.call();
} }

View File

@ -1,9 +1,9 @@
// This script defines a function and calls it // This script defines a function and calls it.
fn bob() { fn call_me() {
return 3; return 3;
} }
let result = bob(); let result = call_me();
print(`bob() should be 3: ${result}`); print(`call_me() should be 3: ${result}`);

View File

@ -1,14 +1,14 @@
// This script defines a function with two parameters // This script defines a function with two parameters and local variables.
let a = 3; let a = 3;
fn addme(a, b) { fn add(a, b) {
a = 42; // notice that 'a' is passed by value a = 42; // notice that 'a' is passed by value
a + b; // notice that the last value is returned even if terminated by a semicolon a + b; // notice that the last value is returned even if terminated by a semicolon
} }
let result = addme(a, 4); let result = add(a, 4);
print(`addme(a, 4) should be 46: ${result}`); print(`add(a, 4) should be 46: ${result}`);
print(`a should still be 3: ${a}`); // should print 3 - 'a' is never changed print(`a should still be 3: ${a}`); // prints 3: 'a' is never changed

View File

@ -1,9 +1,11 @@
// This script defines a function with many parameters and calls it // This script defines a function with many parameters.
//
const KEY = 38; const KEY = 38;
fn f(a, b, c, d, e, f) { fn f(a, b, c, d, e, f) {
a - b * c - d * e - f + global::KEY let x = global::KEY; // <- access global module
a - b * c - d * e - f + x
} }
let result = f(100, 5, 2, 9, 6, 32); let result = f(100, 5, 2, 9, 6, 32);

View File

@ -1,4 +1,4 @@
// This script defines a function that acts as a method // This script defines a function that acts as a method.
// Use 'this' to refer to the object of a method call // Use 'this' to refer to the object of a method call
fn action(x, y) { fn action(x, y) {

View File

@ -1,3 +1,5 @@
// This script runs if statements.
let a = 42; let a = 42;
let b = 123; let b = 123;
let x = 999; let x = 999;
@ -7,7 +9,7 @@ if a > b {
} else if a < b { } else if a < b {
print("a < b, x should be 0"); print("a < b, x should be 0");
let x = 0; // this 'x' shadows the global 'x' let x = 0; // <- this 'x' shadows the global 'x'
print(x); // should print 0 print(x); // should print 0
} else { } else {
print("Oops! a == b"); print("Oops! a == b");

View File

@ -1,7 +1,9 @@
// This script runs an if expression.
let a = 42; let a = 42;
let b = 123; let b = 123;
let x = if a <= b { // if-expression let x = if a <= b { // <- if-expression
b - a b - a
} else { } else {
a - b a - b

View File

@ -1,4 +1,4 @@
// This script runs an infinite loop, ending it with a break statement // This script runs an infinite loop, ending it with a break statement.
let x = 10; let x = 10;

View File

@ -1,3 +1,5 @@
// This script simulates multi-dimensional matrix calculations.
const SIZE = 50; const SIZE = 50;
fn new_mat(x, y) { fn new_mat(x, y) {

View File

@ -1,3 +1,5 @@
// This script imports an external script as a module.
import "loop" as x; import "loop" as x;
print(`Module test! foo = ${x::foo}`); print(`Module test! foo = ${x::foo}`);

View File

@ -1,3 +1,5 @@
// This script runs a single expression.
print("The result should be 46:"); print("The result should be 46:");
print(34 + 12); print(34 + 12);

View File

@ -1,3 +1,5 @@
// This script runs a complex expression.
print("The result should be 182:"); print("The result should be 182:");
let x = 12 + 34 * 5; let x = 12 + 34 * 5;

View File

@ -1,3 +1,5 @@
// This script runs a complex expression.
print("The result should be 230:"); print("The result should be 230:");
let x = (12 + 34) * 5; let x = (12 + 34) * 5;

View File

@ -1,5 +1,4 @@
// This script runs 1 million iterations // This script runs 1 million iterations to test the speed of the scripting engine.
// to test the speed of the scripting engine.
let now = timestamp(); let now = timestamp();
let x = 1_000_000; let x = 1_000_000;

View File

@ -1,4 +1,4 @@
// This script tests string operations // This script tests string operations.
print("hello"); print("hello");
print("this\nis \\ nice"); // escape sequences print("this\nis \\ nice"); // escape sequences

View File

@ -1,3 +1,5 @@
// This script tests object maps and strings.
print("Ready... Go!"); print("Ready... Go!");
let now = timestamp(); let now = timestamp();

View File

@ -1,13 +1,22 @@
// This script runs a switch statement in a for-loop.
let arr = [42, 123.456, "hello", true, "hey", 'x', 999, 1, 2, 3, 4]; let arr = [42, 123.456, "hello", true, "hey", 'x', 999, 1, 2, 3, 4];
for item in arr { for item in arr {
switch item { switch item {
// Match single integer
42 => print("The Answer!"), 42 => print("The Answer!"),
// Match single floating-point number
123.456 => print(`Floating point... ${item}`), 123.456 => print(`Floating point... ${item}`),
// Match single string
"hello" => print(`${item} world!`), "hello" => print(`${item} world!`),
// Match another integer
999 => print(`Got 999: ${item}`), 999 => print(`Got 999: ${item}`),
// Match range with condition
0..100 if item % 2 == 0 => print(`A small even number: ${item}`), 0..100 if item % 2 == 0 => print(`A small even number: ${item}`),
// Match another range
0..100 => print(`A small odd number: ${item}`), 0..100 => print(`A small odd number: ${item}`),
// Default case
_ => print(`Something else: <${item}> is ${type_of(item)}`) _ => print(`Something else: <${item}> is ${type_of(item)}`)
} }
} }

View File

@ -1,4 +1,4 @@
// This script runs a while loop // This script runs a while loop.
let x = 10; let x = 10;

View File

@ -151,7 +151,7 @@ impl Deref for Expression<'_> {
} }
} }
impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_> { impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_, '_, '_> {
/// Evaluate an [expression tree][Expression]. /// Evaluate an [expression tree][Expression].
/// ///
/// # WARNING - Low Level API /// # WARNING - Low Level API

View File

@ -60,12 +60,12 @@ impl Engine {
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
let mut param_type_names: crate::StaticVec<_> = F::param_names() let mut param_type_names: crate::StaticVec<_> = F::param_names()
.iter() .iter()
.map(|ty| format!("_: {}", self.map_type_name(ty))) .map(|ty| format!("_: {}", self.format_type_name(ty)))
.collect(); .collect();
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
if F::return_type() != TypeId::of::<()>() { if F::return_type() != TypeId::of::<()>() {
param_type_names.push(self.map_type_name(F::return_type_name()).into()); param_type_names.push(self.format_type_name(F::return_type_name()).into());
} }
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
@ -122,9 +122,9 @@ impl Engine {
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
let param_type_names: crate::StaticVec<_> = F::param_names() let param_type_names: crate::StaticVec<_> = F::param_names()
.iter() .iter()
.map(|ty| format!("_: {}", self.map_type_name(ty))) .map(|ty| format!("_: {}", self.format_type_name(ty)))
.chain(std::iter::once( .chain(std::iter::once(
self.map_type_name(F::return_type_name()).into(), self.format_type_name(F::return_type_name()).into(),
)) ))
.collect(); .collect();
@ -1027,7 +1027,8 @@ impl Engine {
/// Functions from the following sources are included, in order: /// Functions from the following sources are included, in order:
/// 1) Functions registered into the global namespace /// 1) Functions registered into the global namespace
/// 2) Functions in registered sub-modules /// 2) Functions in registered sub-modules
/// 3) Functions in packages (optional) /// 3) Functions in registered packages
/// 4) Functions in standard packages (optional)
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
#[inline] #[inline]
#[must_use] #[must_use]
@ -1040,14 +1041,13 @@ impl Engine {
signatures.extend(m.gen_fn_signatures().map(|f| format!("{}::{}", name, f))) signatures.extend(m.gen_fn_signatures().map(|f| format!("{}::{}", name, f)))
}); });
if include_packages {
signatures.extend( signatures.extend(
self.global_modules self.global_modules
.iter() .iter()
.skip(1) .skip(1)
.filter(|m| !m.internal && (include_packages || !m.standard))
.flat_map(|m| m.gen_fn_signatures()), .flat_map(|m| m.gen_fn_signatures()),
); );
}
signatures signatures
} }

View File

@ -45,7 +45,7 @@ impl OpAssignment<'_> {
#[must_use] #[must_use]
pub fn new_from_token(op: Token) -> Self { pub fn new_from_token(op: Token) -> Self {
let op_raw = op let op_raw = op
.map_op_assignment() .get_base_op_from_assignment()
.expect("op-assignment operator") .expect("op-assignment operator")
.literal_syntax(); .literal_syntax();
Self { Self {
@ -54,6 +54,26 @@ impl OpAssignment<'_> {
op: op.literal_syntax(), op: op.literal_syntax(),
} }
} }
/// Create a new [`OpAssignment`] from a base operator.
///
/// # Panics
///
/// Panics if the name is not an operator that can be converted into an op-operator.
#[must_use]
#[inline(always)]
pub fn new_from_base(name: &str) -> Self {
Self::new_from_base_token(Token::lookup_from_syntax(name).expect("operator"))
}
/// Convert a [`Token`] into a new [`OpAssignment`].
///
/// # Panics
///
/// Panics if the token is cannot be converted into an op-assignment operator.
#[inline(always)]
#[must_use]
pub fn new_from_base_token(op: Token) -> Self {
Self::new_from_token(op.convert_to_op_assignment().expect("operator"))
}
} }
/// _(internals)_ A scoped block of statements. /// _(internals)_ A scoped block of statements.

View File

@ -1,4 +1,4 @@
use rhai::{Dynamic, Engine, EvalAltResult, Module, Scope, AST}; use rhai::{Dynamic, Engine, EvalAltResult, Module, Scope, AST, INT};
use std::{ use std::{
env, env,
@ -46,6 +46,7 @@ fn print_help() {
println!("help => print this help"); println!("help => print this help");
println!("quit, exit => quit"); println!("quit, exit => quit");
println!("scope => print all variables in the scope"); println!("scope => print all variables in the scope");
println!("strict => toggle on/off Strict Variables Mode");
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
println!("functions => print all functions defined"); println!("functions => print all functions defined");
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
@ -56,6 +57,30 @@ fn print_help() {
println!(); println!();
} }
/// Display the scope.
fn print_scope(scope: &Scope) {
scope
.iter_raw()
.enumerate()
.for_each(|(i, (name, constant, value))| {
#[cfg(not(feature = "no_closure"))]
let value_is_shared = if value.is_shared() { " (shared)" } else { "" };
#[cfg(feature = "no_closure")]
let value_is_shared = "";
println!(
"[{}] {}{}{} = {:?}",
i + 1,
if constant { "const " } else { "" },
name,
value_is_shared,
*value.read_lock::<Dynamic>().unwrap(),
)
});
println!();
}
fn main() { fn main() {
let title = format!("Rhai REPL tool (version {})", env!("CARGO_PKG_VERSION")); let title = format!("Rhai REPL tool (version {})", env!("CARGO_PKG_VERSION"));
println!("{}", title); println!("{}", title);
@ -155,8 +180,12 @@ fn main() {
engine.set_module_resolver(resolver); engine.set_module_resolver(resolver);
} }
// Make Engine immutable engine
let engine = engine; .register_fn("test", |x: INT, y: INT| format!("{} {}", x, y))
.register_fn("test", |x: &mut INT, y: INT, z: &str| {
*x += y;
println!("{} {} {}", x, y, z);
});
// Create scope // Create scope
let mut scope = Scope::new(); let mut scope = Scope::new();
@ -208,26 +237,18 @@ fn main() {
continue; continue;
} }
"exit" | "quit" => break, // quit "exit" | "quit" => break, // quit
"strict" if engine.strict_variables() => {
engine.set_strict_variables(false);
println!("Strict Variables Mode turned OFF.");
continue;
}
"strict" => {
engine.set_strict_variables(true);
println!("Strict Variables Mode turned ON.");
continue;
}
"scope" => { "scope" => {
scope print_scope(&scope);
.iter_raw()
.enumerate()
.for_each(|(i, (name, constant, value))| {
#[cfg(not(feature = "no_closure"))]
let value_is_shared = if value.is_shared() { " (shared)" } else { "" };
#[cfg(feature = "no_closure")]
let value_is_shared = "";
println!(
"[{}] {}{}{} = {:?}",
i + 1,
if constant { "const " } else { "" },
name,
value_is_shared,
*value.read_lock::<Dynamic>().unwrap(),
)
});
println!();
continue; continue;
} }
"astu" => { "astu" => {

View File

@ -329,13 +329,47 @@ impl Engine {
/// ///
/// If a type is registered via [`register_type_with_name`][Engine::register_type_with_name], /// If a type is registered via [`register_type_with_name`][Engine::register_type_with_name],
/// the type name provided for the registration will be used. /// the type name provided for the registration will be used.
///
/// # Panics
///
/// Panics if the type name is `&mut`.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str { pub fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
self.type_names self.type_names
.get(name) .get(name)
.map(|s| s.as_str()) .map(|s| s.as_str())
.unwrap_or_else(|| map_std_type_name(name)) .unwrap_or_else(|| map_std_type_name(name, true))
}
/// Format a type name.
///
/// If a type is registered via [`register_type_with_name`][Engine::register_type_with_name],
/// the type name provided for the registration will be used.
#[cfg(feature = "metadata")]
#[inline]
#[must_use]
pub(crate) fn format_type_name<'a>(&'a self, name: &'a str) -> std::borrow::Cow<'a, str> {
if name.starts_with("&mut ") {
let x = &name[5..];
let r = self.format_type_name(x);
return if x != r {
format!("&mut {}", r).into()
} else {
name.into()
};
}
self.type_names
.get(name)
.map(|s| s.as_str())
.unwrap_or_else(|| match name {
"INT" => return type_name::<crate::INT>(),
#[cfg(not(feature = "no_float"))]
"FLOAT" => return type_name::<crate::FLOAT>(),
_ => map_std_type_name(name, false),
})
.into()
} }
/// Make a `Box<`[`EvalAltResult<ErrorMismatchDataType>`][ERR::ErrorMismatchDataType]`>`. /// Make a `Box<`[`EvalAltResult<ErrorMismatchDataType>`][ERR::ErrorMismatchDataType]`>`.

View File

@ -968,18 +968,11 @@ impl Engine {
_ if use_indexers => { _ if use_indexers => {
let args = &mut [target, &mut idx]; let args = &mut [target, &mut idx];
let hash_get = crate::ast::FnCallHashes::from_native(global.hash_idx_get()); let hash_get = crate::ast::FnCallHashes::from_native(global.hash_idx_get());
let fn_name = crate::engine::FN_IDX_GET;
let pos = Position::NONE;
self.exec_fn_call( self.exec_fn_call(
global, global, state, lib, fn_name, hash_get, args, true, true, pos, None, level,
state,
lib,
crate::engine::FN_IDX_GET,
hash_get,
args,
true,
true,
Position::NONE,
None,
level,
) )
.map(|(v, _)| v.into()) .map(|(v, _)| v.into())
} }

View File

@ -7,15 +7,15 @@ use std::prelude::v1::*;
/// Context of a script evaluation process. /// Context of a script evaluation process.
#[derive(Debug)] #[derive(Debug)]
pub struct EvalContext<'a, 'x, 'px, 'm, 's, 'b, 't, 'pt> { pub struct EvalContext<'a, 'x, 'px, 'm, 'pm, 's, 'ps, 'b, 't, 'pt> {
/// The current [`Engine`]. /// The current [`Engine`].
pub(crate) engine: &'a Engine, pub(crate) engine: &'a Engine,
/// The current [`Scope`]. /// The current [`Scope`].
pub(crate) scope: &'x mut Scope<'px>, pub(crate) scope: &'x mut Scope<'px>,
/// The current [`GlobalRuntimeState`]. /// The current [`GlobalRuntimeState`].
pub(crate) global: &'m mut GlobalRuntimeState, pub(crate) global: &'m mut GlobalRuntimeState<'pm>,
/// The current [evaluation state][EvalState]. /// The current [evaluation state][EvalState].
pub(crate) state: &'s mut EvalState, pub(crate) state: &'s mut EvalState<'ps>,
/// The current stack of imported [modules][Module]. /// The current stack of imported [modules][Module].
pub(crate) lib: &'b [&'b Module], pub(crate) lib: &'b [&'b Module],
/// The current bound `this` pointer, if any. /// The current bound `this` pointer, if any.
@ -24,7 +24,7 @@ pub struct EvalContext<'a, 'x, 'px, 'm, 's, 'b, 't, 'pt> {
pub(crate) level: usize, pub(crate) level: usize,
} }
impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, 'pt> { impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, '_, '_, 'pt> {
/// The current [`Engine`]. /// The current [`Engine`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
@ -56,7 +56,7 @@ impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, 'pt> {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &Module)> { pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &Module)> {
self.global.iter_modules() self.global.iter_imports()
} }
/// _(internals)_ The current [`GlobalRuntimeState`]. /// _(internals)_ The current [`GlobalRuntimeState`].
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.

View File

@ -3,13 +3,14 @@
use crate::func::call::FnResolutionCache; use crate::func::call::FnResolutionCache;
use crate::StaticVec; use crate::StaticVec;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::marker::PhantomData;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
/// _(internals)_ A type that holds all the current states of the [`Engine`]. /// _(internals)_ A type that holds all the current states of the [`Engine`].
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct EvalState { pub struct EvalState<'a> {
/// Force a [`Scope`] search by name. /// Force a [`Scope`] search by name.
/// ///
/// Normally, access to variables are parsed with a relative offset into the [`Scope`] to avoid a lookup. /// Normally, access to variables are parsed with a relative offset into the [`Scope`] to avoid a lookup.
@ -25,17 +26,20 @@ pub struct EvalState {
pub scope_level: usize, pub scope_level: usize,
/// Stack of function resolution caches. /// Stack of function resolution caches.
fn_resolution_caches: StaticVec<FnResolutionCache>, fn_resolution_caches: StaticVec<FnResolutionCache>,
/// Take care of the lifetime parameter
dummy: PhantomData<Option<&'a ()>>,
} }
impl EvalState { impl EvalState<'_> {
/// Create a new [`EvalState`]. /// Create a new [`EvalState`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub const fn new() -> Self { pub fn new() -> Self {
Self { Self {
always_search_scope: false, always_search_scope: false,
scope_level: 0, scope_level: 0,
fn_resolution_caches: StaticVec::new_const(), fn_resolution_caches: StaticVec::new_const(),
dummy: PhantomData::default(),
} }
} }
/// Get the number of function resolution cache(s) in the stack. /// Get the number of function resolution cache(s) in the stack.

View File

@ -32,12 +32,12 @@ impl Engine {
}; };
if let Some(index) = index { if let Some(index) = index {
let offset = global.num_imported_modules() - index.get(); let offset = global.num_imports() - index.get();
Some(global.get_shared_module(offset).unwrap()) Some(global.get_shared_import(offset).unwrap())
} else { } else {
global global
.find_module(root) .find_import(root)
.map(|n| global.get_shared_module(n).unwrap()) .map(|n| global.get_shared_import(n).unwrap())
.or_else(|| self.global_sub_modules.get(root).cloned()) .or_else(|| self.global_sub_modules.get(root).cloned())
} }
} }

View File

@ -8,6 +8,7 @@ use std::{
any::TypeId, any::TypeId,
fmt, fmt,
iter::{FromIterator, Rev, Zip}, iter::{FromIterator, Rev, Zip},
marker::PhantomData,
}; };
/// _(internals)_ A stack of imported [modules][Module] plus mutable global runtime states. /// _(internals)_ A stack of imported [modules][Module] plus mutable global runtime states.
@ -19,7 +20,7 @@ use std::{
// Most usage will be looking up a particular key from the list and then getting the module that // Most usage will be looking up a particular key from the list and then getting the module that
// corresponds to that key. // corresponds to that key.
#[derive(Clone)] #[derive(Clone)]
pub struct GlobalRuntimeState { pub struct GlobalRuntimeState<'a> {
/// Stack of module names. /// Stack of module names.
// //
// We cannot use Cow<str> here because `eval` may load a [module][Module] and // We cannot use Cow<str> here because `eval` may load a [module][Module] and
@ -45,20 +46,22 @@ pub struct GlobalRuntimeState {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
constants: constants:
Option<Shared<crate::Locked<std::collections::BTreeMap<Identifier, crate::Dynamic>>>>, Option<Shared<crate::Locked<std::collections::BTreeMap<Identifier, crate::Dynamic>>>>,
/// Take care of the lifetime parameter.
dummy: PhantomData<&'a ()>,
} }
impl Default for GlobalRuntimeState { impl Default for GlobalRuntimeState<'_> {
#[inline(always)] #[inline(always)]
fn default() -> Self { fn default() -> Self {
Self::new() Self::new()
} }
} }
impl GlobalRuntimeState { impl GlobalRuntimeState<'_> {
/// Create a new [`GlobalRuntimeState`]. /// Create a new [`GlobalRuntimeState`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub const fn new() -> Self { pub fn new() -> Self {
Self { Self {
keys: StaticVec::new_const(), keys: StaticVec::new_const(),
modules: StaticVec::new_const(), modules: StaticVec::new_const(),
@ -72,31 +75,32 @@ impl GlobalRuntimeState {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
constants: None, constants: None,
dummy: PhantomData::default(),
} }
} }
/// Get the length of the stack of globally-imported [modules][Module]. /// Get the length of the stack of globally-imported [modules][Module].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn num_imported_modules(&self) -> usize { pub fn num_imports(&self) -> usize {
self.keys.len() self.keys.len()
} }
/// Get the globally-imported [module][Module] at a particular index. /// Get the globally-imported [module][Module] at a particular index.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn get_shared_module(&self, index: usize) -> Option<Shared<Module>> { pub fn get_shared_import(&self, index: usize) -> Option<Shared<Module>> {
self.modules.get(index).cloned() self.modules.get(index).cloned()
} }
/// Get a mutable reference to the globally-imported [module][Module] at a particular index. /// Get a mutable reference to the globally-imported [module][Module] at a particular index.
#[allow(dead_code)] #[allow(dead_code)]
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub(crate) fn get_shared_module_mut(&mut self, index: usize) -> Option<&mut Shared<Module>> { pub(crate) fn get_shared_import_mut(&mut self, index: usize) -> Option<&mut Shared<Module>> {
self.modules.get_mut(index) self.modules.get_mut(index)
} }
/// Get the index of a globally-imported [module][Module] by name. /// Get the index of a globally-imported [module][Module] by name.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn find_module(&self, name: &str) -> Option<usize> { pub fn find_import(&self, name: &str) -> Option<usize> {
let len = self.keys.len(); let len = self.keys.len();
self.keys.iter().rev().enumerate().find_map(|(i, key)| { self.keys.iter().rev().enumerate().find_map(|(i, key)| {
@ -109,20 +113,20 @@ impl GlobalRuntimeState {
} }
/// Push an imported [module][Module] onto the stack. /// Push an imported [module][Module] onto the stack.
#[inline(always)] #[inline(always)]
pub fn push_module(&mut self, name: impl Into<Identifier>, module: impl Into<Shared<Module>>) { pub fn push_import(&mut self, name: impl Into<Identifier>, module: impl Into<Shared<Module>>) {
self.keys.push(name.into()); self.keys.push(name.into());
self.modules.push(module.into()); self.modules.push(module.into());
} }
/// Truncate the stack of globally-imported [modules][Module] to a particular length. /// Truncate the stack of globally-imported [modules][Module] to a particular length.
#[inline(always)] #[inline(always)]
pub fn truncate_modules(&mut self, size: usize) { pub fn truncate_imports(&mut self, size: usize) {
self.keys.truncate(size); self.keys.truncate(size);
self.modules.truncate(size); self.modules.truncate(size);
} }
/// Get an iterator to the stack of globally-imported [modules][Module] in reverse order. /// Get an iterator to the stack of globally-imported [modules][Module] in reverse order.
#[allow(dead_code)] #[allow(dead_code)]
#[inline] #[inline]
pub fn iter_modules(&self) -> impl Iterator<Item = (&str, &Module)> { pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &Module)> {
self.keys self.keys
.iter() .iter()
.rev() .rev()
@ -132,26 +136,26 @@ impl GlobalRuntimeState {
/// Get an iterator to the stack of globally-imported [modules][Module] in reverse order. /// Get an iterator to the stack of globally-imported [modules][Module] in reverse order.
#[allow(dead_code)] #[allow(dead_code)]
#[inline] #[inline]
pub(crate) fn iter_modules_raw(&self) -> impl Iterator<Item = (&Identifier, &Shared<Module>)> { pub(crate) fn iter_imports_raw(&self) -> impl Iterator<Item = (&Identifier, &Shared<Module>)> {
self.keys.iter().rev().zip(self.modules.iter().rev()) self.keys.iter().rev().zip(self.modules.iter().rev())
} }
/// Get an iterator to the stack of globally-imported [modules][Module] in forward order. /// Get an iterator to the stack of globally-imported [modules][Module] in forward order.
#[allow(dead_code)] #[allow(dead_code)]
#[inline] #[inline]
pub(crate) fn scan_modules_raw(&self) -> impl Iterator<Item = (&Identifier, &Shared<Module>)> { pub(crate) fn scan_imports_raw(&self) -> impl Iterator<Item = (&Identifier, &Shared<Module>)> {
self.keys.iter().zip(self.modules.iter()) self.keys.iter().zip(self.modules.iter())
} }
/// Does the specified function hash key exist in the stack of globally-imported [modules][Module]? /// Does the specified function hash key exist in the stack of globally-imported [modules][Module]?
#[allow(dead_code)] #[allow(dead_code)]
#[inline] #[inline]
#[must_use] #[must_use]
pub fn contains_fn(&self, hash: u64) -> bool { pub fn contains_qualified_fn(&self, hash: u64) -> bool {
self.modules.iter().any(|m| m.contains_qualified_fn(hash)) self.modules.iter().any(|m| m.contains_qualified_fn(hash))
} }
/// Get the specified function via its hash key from the stack of globally-imported [modules][Module]. /// Get the specified function via its hash key from the stack of globally-imported [modules][Module].
#[inline] #[inline]
#[must_use] #[must_use]
pub fn get_fn(&self, hash: u64) -> Option<(&CallableFunction, Option<&str>)> { pub fn get_qualified_fn(&self, hash: u64) -> Option<(&CallableFunction, Option<&str>)> {
self.modules self.modules
.iter() .iter()
.rev() .rev()
@ -230,7 +234,7 @@ impl GlobalRuntimeState {
} }
} }
impl IntoIterator for GlobalRuntimeState { impl IntoIterator for GlobalRuntimeState<'_> {
type Item = (Identifier, Shared<Module>); type Item = (Identifier, Shared<Module>);
type IntoIter = type IntoIter =
Zip<Rev<smallvec::IntoIter<[Identifier; 3]>>, Rev<smallvec::IntoIter<[Shared<Module>; 3]>>>; Zip<Rev<smallvec::IntoIter<[Identifier; 3]>>, Rev<smallvec::IntoIter<[Shared<Module>; 3]>>>;
@ -244,7 +248,7 @@ impl IntoIterator for GlobalRuntimeState {
} }
} }
impl<K: Into<Identifier>, M: Into<Shared<Module>>> FromIterator<(K, M)> for GlobalRuntimeState { impl<K: Into<Identifier>, M: Into<Shared<Module>>> FromIterator<(K, M)> for GlobalRuntimeState<'_> {
#[inline] #[inline]
fn from_iter<T: IntoIterator<Item = (K, M)>>(iter: T) -> Self { fn from_iter<T: IntoIterator<Item = (K, M)>>(iter: T) -> Self {
let mut lib = Self::new(); let mut lib = Self::new();
@ -253,7 +257,7 @@ impl<K: Into<Identifier>, M: Into<Shared<Module>>> FromIterator<(K, M)> for Glob
} }
} }
impl<K: Into<Identifier>, M: Into<Shared<Module>>> Extend<(K, M)> for GlobalRuntimeState { impl<K: Into<Identifier>, M: Into<Shared<Module>>> Extend<(K, M)> for GlobalRuntimeState<'_> {
#[inline] #[inline]
fn extend<T: IntoIterator<Item = (K, M)>>(&mut self, iter: T) { fn extend<T: IntoIterator<Item = (K, M)>>(&mut self, iter: T) {
iter.into_iter().for_each(|(k, m)| { iter.into_iter().for_each(|(k, m)| {
@ -263,7 +267,7 @@ impl<K: Into<Identifier>, M: Into<Shared<Module>>> Extend<(K, M)> for GlobalRunt
} }
} }
impl fmt::Debug for GlobalRuntimeState { impl fmt::Debug for GlobalRuntimeState<'_> {
#[inline] #[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut f = f.debug_struct("GlobalRuntimeState"); let mut f = f.debug_struct("GlobalRuntimeState");

View File

@ -28,7 +28,7 @@ impl Engine {
let orig_always_search_scope = state.always_search_scope; let orig_always_search_scope = state.always_search_scope;
let orig_scope_len = scope.len(); let orig_scope_len = scope.len();
let orig_mods_len = global.num_imported_modules(); let orig_mods_len = global.num_imports();
let orig_fn_resolution_caches_len = state.fn_resolution_caches_len(); let orig_fn_resolution_caches_len = state.fn_resolution_caches_len();
if restore_orig_state { if restore_orig_state {
@ -38,7 +38,7 @@ impl Engine {
let mut result = Dynamic::UNIT; let mut result = Dynamic::UNIT;
for stmt in statements { for stmt in statements {
let _mods_len = global.num_imported_modules(); let _mods_len = global.num_imports();
result = self.eval_stmt( result = self.eval_stmt(
scope, scope,
@ -56,7 +56,7 @@ impl Engine {
// Get the extra modules - see if any functions are marked global. // Get the extra modules - see if any functions are marked global.
// Without global functions, the extra modules never affect function resolution. // Without global functions, the extra modules never affect function resolution.
if global if global
.scan_modules_raw() .scan_imports_raw()
.skip(_mods_len) .skip(_mods_len)
.any(|(_, m)| m.contains_indexed_global_functions()) .any(|(_, m)| m.contains_indexed_global_functions())
{ {
@ -82,7 +82,7 @@ impl Engine {
if restore_orig_state { if restore_orig_state {
scope.rewind(orig_scope_len); scope.rewind(orig_scope_len);
state.scope_level -= 1; state.scope_level -= 1;
global.truncate_modules(orig_mods_len); global.truncate_imports(orig_mods_len);
// The impact of new local variables goes away at the end of a block // The impact of new local variables goes away at the end of a block
// because any new variables introduced will go out of scope // because any new variables introduced will go out of scope
@ -778,9 +778,9 @@ impl Engine {
// Index the module (making a clone copy if necessary) if it is not 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); let mut module = crate::func::native::shared_take_or_clone(module);
module.build_index(); module.build_index();
global.push_module(name, module); global.push_import(name, module);
} else { } else {
global.push_module(name, module); global.push_import(name, module);
} }
} }

View File

@ -179,9 +179,9 @@ impl Engine {
/// Search order: /// Search order:
/// 1) AST - script functions in the AST /// 1) AST - script functions in the AST
/// 2) Global namespace - functions registered via Engine::register_XXX /// 2) Global namespace - functions registered via Engine::register_XXX
/// 3) Global modules - packages /// 3) Global registered modules - packages
/// 4) Imported modules - functions marked with global namespace /// 4) Imported modules - functions marked with global namespace
/// 5) Global sub-modules - functions marked with global namespace /// 5) Static registered modules
#[must_use] #[must_use]
fn resolve_fn<'s>( fn resolve_fn<'s>(
&self, &self,
@ -220,7 +220,7 @@ impl Engine {
loop { loop {
let func = lib let func = lib
.iter() .iter()
.find_map(|m| { .find_map(|&m| {
m.get_fn(hash).cloned().map(|func| FnResolutionCacheEntry { m.get_fn(hash).cloned().map(|func| FnResolutionCacheEntry {
func, func,
source: m.id_raw().clone(), source: m.id_raw().clone(),
@ -235,12 +235,12 @@ impl Engine {
}) })
}) })
.or_else(|| { .or_else(|| {
global global.get_qualified_fn(hash).map(|(func, source)| {
.get_fn(hash) FnResolutionCacheEntry {
.map(|(func, source)| FnResolutionCacheEntry {
func: func.clone(), func: func.clone(),
source: source source: source
.map_or_else(|| Identifier::new_const(), Into::into), .map_or_else(|| Identifier::new_const(), Into::into),
}
}) })
}) })
.or_else(|| { .or_else(|| {

View File

@ -65,7 +65,7 @@ pub struct NativeCallContext<'a> {
/// Function source, if any. /// Function source, if any.
source: Option<&'a str>, source: Option<&'a str>,
/// The current [`GlobalRuntimeState`], if any. /// The current [`GlobalRuntimeState`], if any.
global: Option<&'a GlobalRuntimeState>, global: Option<&'a GlobalRuntimeState<'a>>,
/// The current stack of loaded [modules][Module]. /// The current stack of loaded [modules][Module].
lib: &'a [&'a Module], lib: &'a [&'a Module],
/// [Position] of the function call. /// [Position] of the function call.
@ -77,7 +77,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
&'a Engine, &'a Engine,
&'a S, &'a S,
Option<&'a S>, Option<&'a S>,
&'a GlobalRuntimeState, &'a GlobalRuntimeState<'a>,
&'a M, &'a M,
Position, Position,
)> for NativeCallContext<'a> )> for NativeCallContext<'a>
@ -199,7 +199,7 @@ impl<'a> NativeCallContext<'a> {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline] #[inline]
pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &Module)> { pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &Module)> {
self.global.iter().flat_map(|&m| m.iter_modules()) self.global.iter().flat_map(|&m| m.iter_imports())
} }
/// Get an iterator over the current set of modules imported via `import` statements in reverse order. /// Get an iterator over the current set of modules imported via `import` statements in reverse order.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -208,7 +208,7 @@ impl<'a> NativeCallContext<'a> {
pub(crate) fn iter_imports_raw( pub(crate) fn iter_imports_raw(
&self, &self,
) -> impl Iterator<Item = (&crate::Identifier, &Shared<Module>)> { ) -> impl Iterator<Item = (&crate::Identifier, &Shared<Module>)> {
self.global.iter().flat_map(|&m| m.iter_modules_raw()) self.global.iter().flat_map(|&m| m.iter_imports_raw())
} }
/// _(internals)_ The current [`GlobalRuntimeState`], if any. /// _(internals)_ The current [`GlobalRuntimeState`], if any.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.

View File

@ -107,8 +107,8 @@ macro_rules! def_register {
// ^ function ABI type // ^ function ABI type
// ^ function parameter generic type name (A, B, C etc.) // ^ function parameter generic type name (A, B, C etc.)
// ^ call argument(like A, *B, &mut C etc) // ^ call argument(like A, *B, &mut C etc)
// ^ function parameter marker type (T, Ref<T> or Mut<T>) // ^ function parameter marker type (A, Ref<B> or Mut<C>)
// ^ function parameter actual type (T, &T or &mut T) // ^ function parameter actual type (A, &B or &mut C)
// ^ argument let statement // ^ argument let statement
impl< impl<
@ -117,7 +117,7 @@ macro_rules! def_register {
RET: Variant + Clone RET: Variant + Clone
> RegisterNativeFunction<($($mark,)*), ()> for FN { > RegisterNativeFunction<($($mark,)*), ()> for FN {
#[inline(always)] fn param_types() -> Box<[TypeId]> { vec![$(TypeId::of::<$par>()),*].into_boxed_slice() } #[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 param_names() -> Box<[&'static str]> { vec![$(std::any::type_name::<$param>()),*].into_boxed_slice() }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<RET>() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<RET>() }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RET>() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RET>() }
#[inline(always)] fn into_callable_function(self) -> CallableFunction { #[inline(always)] fn into_callable_function(self) -> CallableFunction {
@ -145,7 +145,7 @@ macro_rules! def_register {
RET: Variant + Clone RET: Variant + Clone
> RegisterNativeFunction<(NativeCallContext<'static>, $($mark,)*), ()> for FN { > RegisterNativeFunction<(NativeCallContext<'static>, $($mark,)*), ()> for FN {
#[inline(always)] fn param_types() -> Box<[TypeId]> { vec![$(TypeId::of::<$par>()),*].into_boxed_slice() } #[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 param_names() -> Box<[&'static str]> { vec![$(std::any::type_name::<$param>()),*].into_boxed_slice() }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<RET>() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<RET>() }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RET>() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RET>() }
#[inline(always)] fn into_callable_function(self) -> CallableFunction { #[inline(always)] fn into_callable_function(self) -> CallableFunction {
@ -173,7 +173,7 @@ macro_rules! def_register {
RET: Variant + Clone RET: Variant + Clone
> RegisterNativeFunction<($($mark,)*), RhaiResultOf<RET>> for FN { > RegisterNativeFunction<($($mark,)*), RhaiResultOf<RET>> for FN {
#[inline(always)] fn param_types() -> Box<[TypeId]> { vec![$(TypeId::of::<$par>()),*].into_boxed_slice() } #[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 param_names() -> Box<[&'static str]> { vec![$(std::any::type_name::<$param>()),*].into_boxed_slice() }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<RhaiResultOf<RET>>() } #[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>>() } #[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 { #[inline(always)] fn into_callable_function(self) -> CallableFunction {
@ -198,7 +198,7 @@ macro_rules! def_register {
RET: Variant + Clone RET: Variant + Clone
> RegisterNativeFunction<(NativeCallContext<'static>, $($mark,)*), RhaiResultOf<RET>> for FN { > RegisterNativeFunction<(NativeCallContext<'static>, $($mark,)*), RhaiResultOf<RET>> for FN {
#[inline(always)] fn param_types() -> Box<[TypeId]> { vec![$(TypeId::of::<$par>()),*].into_boxed_slice() } #[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 param_names() -> Box<[&'static str]> { vec![$(std::any::type_name::<$param>()),*].into_boxed_slice() }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<RhaiResultOf<RET>>() } #[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>>() } #[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 { #[inline(always)] fn into_callable_function(self) -> CallableFunction {

View File

@ -70,7 +70,7 @@ impl Engine {
} }
let orig_scope_len = scope.len(); let orig_scope_len = scope.len();
let orig_mods_len = global.num_imported_modules(); let orig_mods_len = global.num_imports();
// Put arguments into scope as variables // Put arguments into scope as variables
scope.extend(fn_def.params.iter().cloned().zip(args.into_iter().map(|v| { scope.extend(fn_def.params.iter().cloned().zip(args.into_iter().map(|v| {
@ -100,7 +100,7 @@ impl Engine {
modules modules
.iter() .iter()
.cloned() .cloned()
.for_each(|(n, m)| global.push_module(n, m)); .for_each(|(n, m)| global.push_import(n, m));
} }
// Evaluate the function // Evaluate the function
@ -144,7 +144,7 @@ impl Engine {
// Remove arguments only, leaving new variables in the scope // Remove arguments only, leaving new variables in the scope
scope.remove_range(orig_scope_len, args.len()) scope.remove_range(orig_scope_len, args.len())
} }
global.truncate_modules(orig_mods_len); global.truncate_imports(orig_mods_len);
// Restore state // Restore state
state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len); state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len);
@ -172,7 +172,7 @@ impl Engine {
// Then check the global namespace and packages // Then check the global namespace and packages
|| self.global_modules.iter().any(|m| m.contains_fn(hash_script)) || self.global_modules.iter().any(|m| m.contains_fn(hash_script))
// Then check imported modules // Then check imported modules
|| global.map_or(false, |m| m.contains_fn(hash_script)) || global.map_or(false, |m| m.contains_qualified_fn(hash_script))
// Then check sub-modules // Then check sub-modules
|| self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash_script)); || self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash_script));

View File

@ -92,18 +92,60 @@ impl FuncInfo {
/// `()` is cleared. /// `()` is cleared.
/// [`RhaiResult`][crate::RhaiResult] and [`RhaiResultOf<T>`] are expanded. /// [`RhaiResult`][crate::RhaiResult] and [`RhaiResultOf<T>`] are expanded.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
pub fn format_return_type(typ: &str) -> std::borrow::Cow<str> { pub fn format_type(typ: &str, is_return_type: bool) -> std::borrow::Cow<str> {
const RHAI_RESULT_TYPE: &str = "RhaiResult"; const RHAI_RESULT_TYPE: &str = "RhaiResult";
const RHAI_RESULT_TYPE_EXPAND: &str = "Result<Dynamic, Box<EvalAltResult>>"; const RHAI_RESULT_TYPE_EXPAND: &str = "Result<Dynamic, Box<EvalAltResult>>";
const RHAI_RESULT_OF_TYPE: &str = "RhaiResultOf<"; const RHAI_RESULT_OF_TYPE: &str = "RhaiResultOf<";
const RHAI_RESULT_OF_TYPE_EXPAND: &str = "Result<{}, Box<EvalAltResult>>"; const RHAI_RESULT_OF_TYPE_EXPAND: &str = "Result<{}, Box<EvalAltResult>>";
const RHAI_RANGE: &str = "ExclusiveRange";
const RHAI_RANGE_TYPE: &str = "Range<";
const RHAI_RANGE_EXPAND: &str = "Range<{}>";
const RHAI_INCLUSIVE_RANGE: &str = "InclusiveRange";
const RHAI_INCLUSIVE_RANGE_TYPE: &str = "RangeInclusive<";
const RHAI_INCLUSIVE_RANGE_EXPAND: &str = "RangeInclusive<{}>";
let typ = typ.trim();
if typ.starts_with("rhai::") {
return Self::format_type(&typ[6..], is_return_type);
} else if typ.starts_with("&mut ") {
let x = &typ[5..];
let r = Self::format_type(x, false);
return if r == x {
typ.into()
} else {
format!("&mut {}", r).into()
};
}
match typ { match typ {
"" | "()" => "".into(), "" | "()" if is_return_type => "".into(),
"INT" => std::any::type_name::<crate::INT>().into(),
#[cfg(not(feature = "no_float"))]
"FLOAT" => std::any::type_name::<crate::FLOAT>().into(),
RHAI_RANGE => RHAI_RANGE_EXPAND
.replace("{}", std::any::type_name::<crate::INT>())
.into(),
RHAI_INCLUSIVE_RANGE => RHAI_INCLUSIVE_RANGE_EXPAND
.replace("{}", std::any::type_name::<crate::INT>())
.into(),
RHAI_RESULT_TYPE => RHAI_RESULT_TYPE_EXPAND.into(), RHAI_RESULT_TYPE => RHAI_RESULT_TYPE_EXPAND.into(),
ty if ty.starts_with(RHAI_RANGE_TYPE) && ty.ends_with('>') => {
let inner = &ty[RHAI_RANGE_TYPE.len()..ty.len() - 1];
RHAI_RANGE_EXPAND
.replace("{}", Self::format_type(inner, false).trim())
.into()
}
ty if ty.starts_with(RHAI_INCLUSIVE_RANGE_TYPE) && ty.ends_with('>') => {
let inner = &ty[RHAI_INCLUSIVE_RANGE_TYPE.len()..ty.len() - 1];
RHAI_INCLUSIVE_RANGE_EXPAND
.replace("{}", Self::format_type(inner, false).trim())
.into()
}
ty if ty.starts_with(RHAI_RESULT_OF_TYPE) && ty.ends_with('>') => { ty if ty.starts_with(RHAI_RESULT_OF_TYPE) && ty.ends_with('>') => {
let inner = &ty[RHAI_RESULT_OF_TYPE.len()..ty.len() - 1];
RHAI_RESULT_OF_TYPE_EXPAND RHAI_RESULT_OF_TYPE_EXPAND
.replace("{}", ty[RHAI_RESULT_OF_TYPE.len()..ty.len() - 1].trim()) .replace("{}", Self::format_type(inner, false).trim())
.into() .into()
} }
ty => ty.into(), ty => ty.into(),
@ -116,22 +158,30 @@ impl FuncInfo {
pub fn gen_signature(&self) -> String { pub fn gen_signature(&self) -> String {
let mut sig = format!("{}(", self.metadata.name); let mut sig = format!("{}(", self.metadata.name);
let return_type = Self::format_return_type(&self.metadata.return_type); let return_type = Self::format_type(&self.metadata.return_type, true);
if !self.metadata.params_info.is_empty() { if !self.metadata.params_info.is_empty() {
let params: StaticVec<_> = self let params: StaticVec<_> = self
.metadata .metadata
.params_info .params_info
.iter() .iter()
.map(|s| s.as_str()) .map(|s| {
let mut seg = s.splitn(2, ':');
let name = match seg.next().unwrap().trim() {
"" => "_",
s => s,
};
let result: std::borrow::Cow<str> = match seg.next() {
Some(typ) => {
format!("{}: {}", name, FuncInfo::format_type(typ, false)).into()
}
None => name.into(),
};
result
})
.collect(); .collect();
sig.push_str(&params.join(", ")); sig.push_str(&params.join(", "));
sig.push(')'); sig.push(')');
if !return_type.is_empty() {
sig.push_str(" -> ");
sig.push_str(&return_type);
}
} else { } else {
for x in 0..self.metadata.params { for x in 0..self.metadata.params {
sig.push('_'); sig.push('_');
@ -139,18 +189,13 @@ impl FuncInfo {
sig.push_str(", "); sig.push_str(", ");
} }
} }
sig.push(')'); sig.push(')');
}
if !self.func.is_script() { if !self.func.is_script() && !return_type.is_empty() {
sig.push(')');
if !return_type.is_empty() {
sig.push_str(" -> "); sig.push_str(" -> ");
sig.push_str(&return_type); sig.push_str(&return_type);
} }
}
}
sig sig
} }
@ -541,7 +586,7 @@ impl Module {
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
params_info, params_info,
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
return_type: "Dynamic".into(), return_type: "".into(),
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
comments: None, comments: None,
}, },
@ -705,7 +750,7 @@ impl Module {
let return_type = param_names.pop().unwrap(); let return_type = param_names.pop().unwrap();
(param_names, return_type) (param_names, return_type)
} else { } else {
(param_names, Default::default()) (param_names, crate::SmartString::new_const())
}; };
f.metadata.params_info = param_names; f.metadata.params_info = param_names;
f.metadata.return_type = return_type_name; f.metadata.return_type = return_type_name;
@ -837,7 +882,7 @@ impl Module {
let return_type = if names.len() > arg_types.as_ref().len() { let return_type = if names.len() > arg_types.as_ref().len() {
names.pop().unwrap() names.pop().unwrap()
} else { } else {
Default::default() crate::SmartString::new_const()
}; };
names.shrink_to_fit(); names.shrink_to_fit();
(names, return_type) (names, return_type)
@ -1402,14 +1447,14 @@ impl Module {
/// Merge another [`Module`] into this [`Module`]. /// Merge another [`Module`] into this [`Module`].
#[inline(always)] #[inline(always)]
pub fn merge(&mut self, other: &Self) -> &mut Self { pub fn merge(&mut self, other: &Self) -> &mut Self {
self.merge_filtered(other, &|_, _, _, _, _| true) self.merge_filtered(other, |_, _, _, _, _| true)
} }
/// Merge another [`Module`] into this [`Module`] based on a filter predicate. /// Merge another [`Module`] into this [`Module`] based on a filter predicate.
pub(crate) fn merge_filtered( pub(crate) fn merge_filtered(
&mut self, &mut self,
other: &Self, other: &Self,
_filter: &impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool, _filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool + Copy,
) -> &mut Self { ) -> &mut Self {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
other.modules.iter().for_each(|(k, v)| { other.modules.iter().for_each(|(k, v)| {
@ -1620,7 +1665,7 @@ impl Module {
) -> RhaiResultOf<Self> { ) -> RhaiResultOf<Self> {
let mut scope = scope; let mut scope = scope;
let mut global = crate::eval::GlobalRuntimeState::new(); let mut global = crate::eval::GlobalRuntimeState::new();
let orig_mods_len = global.num_imported_modules(); let orig_mods_len = global.num_imports();
// Run the script // Run the script
engine.eval_ast_with_scope_raw(&mut scope, &mut global, &ast, 0)?; engine.eval_ast_with_scope_raw(&mut scope, &mut global, &ast, 0)?;

View File

@ -96,7 +96,7 @@ impl<'a> OptimizerState<'a> {
pub fn restore_var(&mut self, len: usize) { pub fn restore_var(&mut self, len: usize) {
self.variables.truncate(len) self.variables.truncate(len)
} }
/// Add a new constant to the list. /// Add a new variable to the list.
#[inline(always)] #[inline(always)]
pub fn push_var( pub fn push_var(
&mut self, &mut self,
@ -423,7 +423,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
match x.2 { match x.2 {
Expr::FnCall(ref mut x2, _) => { Expr::FnCall(ref mut x2, _) => {
state.set_dirty(); state.set_dirty();
x.1 = Some(OpAssignment::new(&x2.name)); x.1 = Some(OpAssignment::new_from_base(&x2.name));
let value = mem::take(&mut x2.args[1]); let value = mem::take(&mut x2.args[1]);

View File

@ -31,6 +31,70 @@ pub mod array_functions {
pub fn len(array: &mut Array) -> INT { pub fn len(array: &mut Array) -> INT {
array.len() as INT array.len() as INT
} }
/// Get a copy of the element at the `index` position in the array.
///
/// * If `index` < 0, position counts from the end of the array (`-1` is the last element).
/// * If `index` < -length of array, `()` is returned.
/// * If `index` ≥ length of array, `()` is returned.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3];
///
/// print(x.get(0)); // prints 1
///
/// print(x.get(-1)); // prints 3
///
/// print(x.get(99)); // prints empty (for '()')
/// ```
pub fn get(array: &mut Array, index: INT) -> Dynamic {
if array.is_empty() {
return Dynamic::UNIT;
}
let (index, _) = calc_offset_len(array.len(), index, 0);
if index >= array.len() {
Dynamic::UNIT
} else {
array[index].clone()
}
}
/// Set the element at the `index` position in the array to a new `value`.
///
/// * If `index` < 0, position counts from the end of the array (`-1` is the last element).
/// * If `index` < -length of array, the array is not modified.
/// * If `index` ≥ length of array, the array is not modified.
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3];
///
/// x.set(0, 42);
///
/// print(x); // prints "[42, 2, 3]"
///
/// x.set(-3, 0);
///
/// print(x); // prints "[0, 2, 3]"
///
/// x.set(99, 123);
///
/// print(x); // prints "[0, 2, 3]"
/// ```
pub fn set(array: &mut Array, index: INT, value: Dynamic) {
if array.is_empty() {
return;
}
let (index, _) = calc_offset_len(array.len(), index, 0);
if index < array.len() {
array[index] = value;
}
}
/// Add a new element, which is not another array, to the end of the array. /// Add a new element, which is not another array, to the end of the array.
/// ///
/// If `item` is `Array`, then `append` is more specific and will be called instead. /// If `item` is `Array`, then `append` is more specific and will be called instead.
@ -1929,7 +1993,7 @@ pub mod array_functions {
) -> RhaiResultOf<Array> { ) -> RhaiResultOf<Array> {
drain(ctx, array, FnPtr::new(filter)?) drain(ctx, array, FnPtr::new(filter)?)
} }
/// Remove all elements in the array within an exclusive range and return them as a new array. /// Remove all elements in the array within an exclusive `range` and return them as a new array.
/// ///
/// # Example /// # Example
/// ///
@ -1954,7 +2018,7 @@ pub mod array_functions {
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
drain_range(array, start, end - start) drain_range(array, start, end - start)
} }
/// Remove all elements in the array within an inclusive range and return them as a new array. /// Remove all elements in the array within an inclusive `range` and return them as a new array.
/// ///
/// # Example /// # Example
/// ///
@ -2125,7 +2189,7 @@ pub mod array_functions {
) -> RhaiResultOf<Array> { ) -> RhaiResultOf<Array> {
retain(ctx, array, FnPtr::new(filter)?) retain(ctx, array, FnPtr::new(filter)?)
} }
/// Remove all elements in the array not within an exclusive range and return them as a new array. /// Remove all elements in the array not within an exclusive `range` and return them as a new array.
/// ///
/// # Example /// # Example
/// ///
@ -2150,7 +2214,7 @@ pub mod array_functions {
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
retain_range(array, start, end - start) retain_range(array, start, end - start)
} }
/// Remove all elements in the array not within an inclusive range and return them as a new array. /// Remove all elements in the array not within an inclusive `range` and return them as a new array.
/// ///
/// # Example /// # Example
/// ///

View File

@ -100,6 +100,75 @@ pub mod blob_functions {
pub fn len(blob: &mut Blob) -> INT { pub fn len(blob: &mut Blob) -> INT {
blob.len() as INT blob.len() as INT
} }
/// Get the byte value at the `index` position in the BLOB.
///
/// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last element).
/// * If `index` < -length of BLOB, zero is returned.
/// * If `index` ≥ length of BLOB, zero is returned.
///
/// # Example
///
/// ```rhai
/// let b = blob();
///
/// b += 1; b += 2; b += 3; b += 4; b += 5;
///
/// print(b.get(0)); // prints 1
///
/// print(b.get(-1)); // prints 5
///
/// print(b.get(99)); // prints 0
/// ```
pub fn get(blob: &mut Blob, index: INT) -> INT {
if blob.is_empty() {
return 0;
}
let (index, _) = calc_offset_len(blob.len(), index, 0);
if index >= blob.len() {
0
} else {
blob[index] as INT
}
}
/// Set the particular `index` position in the BLOB to a new byte `value`.
///
/// * If `index` < 0, position counts from the end of the BLOB (`-1` is the last byte).
/// * If `index` < -length of BLOB, the BLOB is not modified.
/// * If `index` ≥ length of BLOB, the BLOB is not modified.
///
/// # Example
///
/// ```rhai
/// let b = blob();
///
/// b += 1; b += 2; b += 3; b += 4; b += 5;
///
/// b.set(0, 0x42);
///
/// print(b); // prints "[4202030405]"
///
/// b.set(-3, 0);
///
/// print(b); // prints "[4202000405]"
///
/// b.set(99, 123);
///
/// print(b); // prints "[4202000405]"
/// ```
pub fn set(blob: &mut Blob, index: INT, value: INT) {
if blob.is_empty() {
return;
}
let (index, _) = calc_offset_len(blob.len(), index, 0);
if index < blob.len() {
blob[index] = (value & 0x000000ff) as u8;
}
}
/// Add a new byte `value` to the end of the BLOB. /// Add a new byte `value` to the end of the BLOB.
/// ///
/// Only the lower 8 bits of the `value` are used; all other bits are ignored. /// Only the lower 8 bits of the `value` are used; all other bits are ignored.
@ -114,8 +183,7 @@ pub mod blob_functions {
/// print(b); // prints "[42]" /// print(b); // prints "[42]"
/// ``` /// ```
pub fn push(blob: &mut Blob, value: INT) { pub fn push(blob: &mut Blob, value: INT) {
let value = (value & 0x000000ff) as u8; blob.push((value & 0x000000ff) as u8);
blob.push(value);
} }
/// Add another BLOB to the end of the BLOB. /// Add another BLOB to the end of the BLOB.
/// ///
@ -396,7 +464,7 @@ pub mod blob_functions {
blob.reverse(); blob.reverse();
} }
} }
/// Replace an exclusive range of the BLOB with another BLOB. /// Replace an exclusive `range` of the BLOB with another BLOB.
/// ///
/// # Example /// # Example
/// ///
@ -414,7 +482,7 @@ pub mod blob_functions {
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
splice(blob, start, end - start, replace) splice(blob, start, end - start, replace)
} }
/// Replace an inclusive range of the BLOB with another BLOB. /// Replace an inclusive `range` of the BLOB with another BLOB.
/// ///
/// # Example /// # Example
/// ///
@ -468,7 +536,7 @@ pub mod blob_functions {
blob.splice(start..start + len, replace); blob.splice(start..start + len, replace);
} }
} }
/// Copy an exclusive range of the BLOB and return it as a new BLOB. /// Copy an exclusive `range` of the BLOB and return it as a new BLOB.
/// ///
/// # Example /// # Example
/// ///
@ -487,7 +555,7 @@ pub mod blob_functions {
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
extract(blob, start, end - start) extract(blob, start, end - start)
} }
/// Copy an inclusive range of the BLOB and return it as a new BLOB. /// Copy an inclusive `range` of the BLOB and return it as a new BLOB.
/// ///
/// # Example /// # Example
/// ///
@ -608,7 +676,7 @@ pub mod blob_functions {
result result
} }
} }
/// Remove all bytes in the BLOB within an exclusive range and return them as a new BLOB. /// Remove all bytes in the BLOB within an exclusive `range` and return them as a new BLOB.
/// ///
/// # Example /// # Example
/// ///
@ -635,7 +703,7 @@ pub mod blob_functions {
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
drain(blob, start, end - start) drain(blob, start, end - start)
} }
/// Remove all bytes in the BLOB within an inclusive range and return them as a new BLOB. /// Remove all bytes in the BLOB within an inclusive `range` and return them as a new BLOB.
/// ///
/// # Example /// # Example
/// ///
@ -702,7 +770,7 @@ pub mod blob_functions {
blob.drain(start..start + len).collect() blob.drain(start..start + len).collect()
} }
} }
/// Remove all bytes in the BLOB not within an exclusive range and return them as a new BLOB. /// Remove all bytes in the BLOB not within an exclusive `range` and return them as a new BLOB.
/// ///
/// # Example /// # Example
/// ///
@ -729,7 +797,7 @@ pub mod blob_functions {
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
retain(blob, start, end - start) retain(blob, start, end - start)
} }
/// Remove all bytes in the BLOB not within an inclusive range and return them as a new BLOB. /// Remove all bytes in the BLOB not within an inclusive `range` and return them as a new BLOB.
/// ///
/// # Example /// # Example
/// ///
@ -827,33 +895,135 @@ mod parse_int_functions {
} }
} }
/// Parse the bytes within an exclusive `range` in the BLOB as an `INT`
/// in little-endian byte order.
///
/// * If number of bytes in `range` < number of bytes for `INT`, zeros are padded.
/// * If number of bytes in `range` > number of bytes for `INT`, extra bytes are ignored.
///
/// ```ignore
/// let b = blob();
///
/// b += 1; b += 2; b += 3; b += 4; b += 5;
///
/// let x = b.parse_le_int(1..3); // parse two bytes
///
/// print(x.to_hex()); // prints "0302"
/// ```
#[rhai_fn(name = "parse_le_int")] #[rhai_fn(name = "parse_le_int")]
pub fn parse_le_int_range(blob: &mut Blob, range: ExclusiveRange) -> INT { pub fn parse_le_int_range(blob: &mut Blob, range: ExclusiveRange) -> INT {
let start = INT::max(range.start, 0); let start = INT::max(range.start, 0);
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
parse_le_int(blob, start, end - start) parse_le_int(blob, start, end - start)
} }
/// Parse the bytes within an inclusive `range` in the BLOB as an `INT`
/// in little-endian byte order.
///
/// * If number of bytes in `range` < number of bytes for `INT`, zeros are padded.
/// * If number of bytes in `range` > number of bytes for `INT`, extra bytes are ignored.
///
/// ```ignore
/// let b = blob();
///
/// b += 1; b += 2; b += 3; b += 4; b += 5;
///
/// let x = b.parse_le_int(1..=3); // parse three bytes
///
/// print(x.to_hex()); // prints "040302"
/// ```
#[rhai_fn(name = "parse_le_int")] #[rhai_fn(name = "parse_le_int")]
pub fn parse_le_int_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> INT { pub fn parse_le_int_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> INT {
let start = INT::max(*range.start(), 0); let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start); let end = INT::max(*range.end(), start);
parse_le_int(blob, start, end - start + 1) parse_le_int(blob, start, end - start + 1)
} }
/// Parse the bytes beginning at the `start` position in the BLOB as an `INT`
/// in little-endian byte order.
///
/// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte).
/// * If `start` < -length of BLOB, position counts from the beginning of the BLOB.
/// * If `start` ≥ length of BLOB, zero is returned.
/// * If `len` ≤ 0, zero is returned.
/// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed.
///
/// * If number of bytes in range < number of bytes for `INT`, zeros are padded.
/// * If number of bytes in range > number of bytes for `INT`, extra bytes are ignored.
///
/// ```ignore
/// let b = blob();
///
/// b += 1; b += 2; b += 3; b += 4; b += 5;
///
/// let x = b.parse_le_int(1, 2);
///
/// print(x.to_hex()); // prints "0302"
/// ```
pub fn parse_le_int(blob: &mut Blob, start: INT, len: INT) -> INT { pub fn parse_le_int(blob: &mut Blob, start: INT, len: INT) -> INT {
parse_int(blob, start, len, true) parse_int(blob, start, len, true)
} }
/// Parse the bytes within an exclusive `range` in the BLOB as an `INT`
/// in big-endian byte order.
///
/// * If number of bytes in `range` < number of bytes for `INT`, zeros are padded.
/// * If number of bytes in `range` > number of bytes for `INT`, extra bytes are ignored.
///
/// ```ignore
/// let b = blob();
///
/// b += 1; b += 2; b += 3; b += 4; b += 5;
///
/// let x = b.parse_be_int(1..3); // parse two bytes
///
/// print(x.to_hex()); // prints "02030000...00"
/// ```
#[rhai_fn(name = "parse_be_int")] #[rhai_fn(name = "parse_be_int")]
pub fn parse_be_int_range(blob: &mut Blob, range: ExclusiveRange) -> INT { pub fn parse_be_int_range(blob: &mut Blob, range: ExclusiveRange) -> INT {
let start = INT::max(range.start, 0); let start = INT::max(range.start, 0);
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
parse_be_int(blob, start, end - start) parse_be_int(blob, start, end - start)
} }
/// Parse the bytes within an inclusive `range` in the BLOB as an `INT`
/// in big-endian byte order.
///
/// * If number of bytes in `range` < number of bytes for `INT`, zeros are padded.
/// * If number of bytes in `range` > number of bytes for `INT`, extra bytes are ignored.
///
/// ```ignore
/// let b = blob();
///
/// b += 1; b += 2; b += 3; b += 4; b += 5;
///
/// let x = b.parse_be_int(1..=3); // parse three bytes
///
/// print(x.to_hex()); // prints "0203040000...00"
/// ```
#[rhai_fn(name = "parse_be_int")] #[rhai_fn(name = "parse_be_int")]
pub fn parse_be_int_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> INT { pub fn parse_be_int_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> INT {
let start = INT::max(*range.start(), 0); let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start); let end = INT::max(*range.end(), start);
parse_be_int(blob, start, end - start + 1) parse_be_int(blob, start, end - start + 1)
} }
/// Parse the bytes beginning at the `start` position in the BLOB as an `INT`
/// in big-endian byte order.
///
/// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte).
/// * If `start` < -length of BLOB, position counts from the beginning of the BLOB.
/// * If `start` ≥ length of BLOB, zero is returned.
/// * If `len` ≤ 0, zero is returned.
/// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed.
///
/// * If number of bytes in range < number of bytes for `INT`, zeros are padded.
/// * If number of bytes in range > number of bytes for `INT`, extra bytes are ignored.
///
/// ```ignore
/// let b = blob();
///
/// b += 1; b += 2; b += 3; b += 4; b += 5;
///
/// let x = b.parse_be_int(1, 2);
///
/// print(x.to_hex()); // prints "02030000...00"
/// ```
pub fn parse_be_int(blob: &mut Blob, start: INT, len: INT) -> INT { pub fn parse_be_int(blob: &mut Blob, start: INT, len: INT) -> INT {
parse_int(blob, start, len, false) parse_int(blob, start, len, false)
} }
@ -887,33 +1057,75 @@ mod parse_float_functions {
} }
} }
/// Parse the bytes within an exclusive `range` in the BLOB as a `FLOAT`
/// in little-endian byte order.
///
/// * If number of bytes in `range` < number of bytes for `FLOAT`, zeros are padded.
/// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes are ignored.
#[rhai_fn(name = "parse_le_float")] #[rhai_fn(name = "parse_le_float")]
pub fn parse_le_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT { pub fn parse_le_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT {
let start = INT::max(range.start, 0); let start = INT::max(range.start, 0);
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
parse_le_float(blob, start, end - start) parse_le_float(blob, start, end - start)
} }
/// Parse the bytes within an inclusive `range` in the BLOB as a `FLOAT`
/// in little-endian byte order.
///
/// * If number of bytes in `range` < number of bytes for `FLOAT`, zeros are padded.
/// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes are ignored.
#[rhai_fn(name = "parse_le_float")] #[rhai_fn(name = "parse_le_float")]
pub fn parse_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT { pub fn parse_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT {
let start = INT::max(*range.start(), 0); let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start); let end = INT::max(*range.end(), start);
parse_le_float(blob, start, end - start + 1) parse_le_float(blob, start, end - start + 1)
} }
/// Parse the bytes beginning at the `start` position in the BLOB as a `FLOAT`
/// in little-endian byte order.
///
/// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte).
/// * If `start` < -length of BLOB, position counts from the beginning of the BLOB.
/// * If `start` ≥ length of BLOB, zero is returned.
/// * If `len` ≤ 0, zero is returned.
/// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed.
///
/// * If number of bytes in range < number of bytes for `FLOAT`, zeros are padded.
/// * If number of bytes in range > number of bytes for `FLOAT`, extra bytes are ignored.
pub fn parse_le_float(blob: &mut Blob, start: INT, len: INT) -> FLOAT { pub fn parse_le_float(blob: &mut Blob, start: INT, len: INT) -> FLOAT {
parse_float(blob, start, len, true) parse_float(blob, start, len, true)
} }
/// Parse the bytes within an exclusive `range` in the BLOB as a `FLOAT`
/// in big-endian byte order.
///
/// * If number of bytes in `range` < number of bytes for `FLOAT`, zeros are padded.
/// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes are ignored.
#[rhai_fn(name = "parse_be_float")] #[rhai_fn(name = "parse_be_float")]
pub fn parse_be_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT { pub fn parse_be_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT {
let start = INT::max(range.start, 0); let start = INT::max(range.start, 0);
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
parse_be_float(blob, start, end - start) parse_be_float(blob, start, end - start)
} }
/// Parse the bytes within an inclusive `range` in the BLOB as a `FLOAT`
/// in big-endian byte order.
///
/// * If number of bytes in `range` < number of bytes for `FLOAT`, zeros are padded.
/// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes are ignored.
#[rhai_fn(name = "parse_be_float")] #[rhai_fn(name = "parse_be_float")]
pub fn parse_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT { pub fn parse_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT {
let start = INT::max(*range.start(), 0); let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start); let end = INT::max(*range.end(), start);
parse_be_float(blob, start, end - start + 1) parse_be_float(blob, start, end - start + 1)
} }
/// Parse the bytes beginning at the `start` position in the BLOB as a `FLOAT`
/// in big-endian byte order.
///
/// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte).
/// * If `start` < -length of BLOB, position counts from the beginning of the BLOB.
/// * If `start` ≥ length of BLOB, zero is returned.
/// * If `len` ≤ 0, zero is returned.
/// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed.
///
/// * If number of bytes in range < number of bytes for `FLOAT`, zeros are padded.
/// * If number of bytes in range > number of bytes for `FLOAT`, extra bytes are ignored.
pub fn parse_be_float(blob: &mut Blob, start: INT, len: INT) -> FLOAT { pub fn parse_be_float(blob: &mut Blob, start: INT, len: INT) -> FLOAT {
parse_float(blob, start, len, false) parse_float(blob, start, len, false)
} }
@ -943,34 +1155,124 @@ mod write_int_functions {
blob[start..][..len].copy_from_slice(&buf[..len]); blob[start..][..len].copy_from_slice(&buf[..len]);
} }
/// Write an `INT` value to the bytes within an exclusive `range` in the BLOB
/// in little-endian byte order.
///
/// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written.
/// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified.
///
/// ```ignore
/// let b = blob(8);
///
/// b.write_le_int(1..3, 0x12345678);
///
/// print(b); // prints "[0078560000000000]"
/// ```
#[rhai_fn(name = "write_le")] #[rhai_fn(name = "write_le")]
pub fn write_le_int_range(blob: &mut Blob, range: ExclusiveRange, value: INT) { pub fn write_le_int_range(blob: &mut Blob, range: ExclusiveRange, value: INT) {
let start = INT::max(range.start, 0); let start = INT::max(range.start, 0);
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
write_le_int(blob, start, end - start, value) write_le_int(blob, start, end - start, value)
} }
/// Write an `INT` value to the bytes within an inclusive `range` in the BLOB
/// in little-endian byte order.
///
/// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written.
/// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified.
///
/// ```ignore
/// let b = blob(8);
///
/// b.write_le_int(1..=3, 0x12345678);
///
/// print(b); // prints "[0078563400000000]"
/// ```
#[rhai_fn(name = "write_le")] #[rhai_fn(name = "write_le")]
pub fn write_le_int_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: INT) { pub fn write_le_int_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: INT) {
let start = INT::max(*range.start(), 0); let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start); let end = INT::max(*range.end(), start);
write_le_int(blob, start, end - start + 1, value) write_le_int(blob, start, end - start + 1, value)
} }
/// Write an `INT` value to the bytes beginning at the `start` position in the BLOB
/// in little-endian byte order.
///
/// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte).
/// * If `start` < -length of BLOB, position counts from the beginning of the BLOB.
/// * If `start` ≥ length of BLOB, zero is returned.
/// * If `len` ≤ 0, zero is returned.
/// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed.
///
/// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written.
/// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified.
///
/// ```ignore
/// let b = blob(8);
///
/// b.write_le_int(1, 3, 0x12345678);
///
/// print(b); // prints "[0078563400000000]"
/// ```
#[rhai_fn(name = "write_le")] #[rhai_fn(name = "write_le")]
pub fn write_le_int(blob: &mut Blob, start: INT, len: INT, value: INT) { pub fn write_le_int(blob: &mut Blob, start: INT, len: INT, value: INT) {
write_int(blob, start, len, value, true) write_int(blob, start, len, value, true)
} }
/// Write an `INT` value to the bytes within an exclusive `range` in the BLOB
/// in big-endian byte order.
///
/// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written.
/// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified.
///
/// ```ignore
/// let b = blob(8, 0x42);
///
/// b.write_be_int(1..3, 0x99);
///
/// print(b); // prints "[4200004242424242]"
/// ```
#[rhai_fn(name = "write_be")] #[rhai_fn(name = "write_be")]
pub fn write_be_int_range(blob: &mut Blob, range: ExclusiveRange, value: INT) { pub fn write_be_int_range(blob: &mut Blob, range: ExclusiveRange, value: INT) {
let start = INT::max(range.start, 0); let start = INT::max(range.start, 0);
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
write_be_int(blob, start, end - start, value) write_be_int(blob, start, end - start, value)
} }
/// Write an `INT` value to the bytes within an inclusive `range` in the BLOB
/// in big-endian byte order.
///
/// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written.
/// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified.
///
/// ```ignore
/// let b = blob(8, 0x42);
///
/// b.write_be_int(1..=3, 0x99);
///
/// print(b); // prints "[4200000042424242]"
/// ```
#[rhai_fn(name = "write_be")] #[rhai_fn(name = "write_be")]
pub fn write_be_int_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: INT) { pub fn write_be_int_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: INT) {
let start = INT::max(*range.start(), 0); let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start); let end = INT::max(*range.end(), start);
write_be_int(blob, start, end - start + 1, value) write_be_int(blob, start, end - start + 1, value)
} }
/// Write an `INT` value to the bytes beginning at the `start` position in the BLOB
/// in big-endian byte order.
///
/// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte).
/// * If `start` < -length of BLOB, position counts from the beginning of the BLOB.
/// * If `start` ≥ length of BLOB, zero is returned.
/// * If `len` ≤ 0, zero is returned.
/// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed.
///
/// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written.
/// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified.
///
/// ```ignore
/// let b = blob(8, 0x42);
///
/// b.write_be_int(1, 3, 0x99);
///
/// print(b); // prints "[4200000042424242]"
/// ```
#[rhai_fn(name = "write_be")] #[rhai_fn(name = "write_be")]
pub fn write_be_int(blob: &mut Blob, start: INT, len: INT, value: INT) { pub fn write_be_int(blob: &mut Blob, start: INT, len: INT, value: INT) {
write_int(blob, start, len, value, false) write_int(blob, start, len, value, false)
@ -1001,34 +1303,76 @@ mod write_float_functions {
blob[start..][..len].copy_from_slice(&buf[..len]); blob[start..][..len].copy_from_slice(&buf[..len]);
} }
/// Write a `FLOAT` value to the bytes within an exclusive `range` in the BLOB
/// in little-endian byte order.
///
/// * If number of bytes in `range` < number of bytes for `FLOAT`, extra bytes in `FLOAT` are not written.
/// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes in `range` are not modified.
#[rhai_fn(name = "write_le")] #[rhai_fn(name = "write_le")]
pub fn write_le_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) { pub fn write_le_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) {
let start = INT::max(range.start, 0); let start = INT::max(range.start, 0);
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
write_le_float(blob, start, end - start, value) write_le_float(blob, start, end - start, value)
} }
/// Write a `FLOAT` value to the bytes within an inclusive `range` in the BLOB
/// in little-endian byte order.
///
/// * If number of bytes in `range` < number of bytes for `FLOAT`, extra bytes in `FLOAT` are not written.
/// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes in `range` are not modified.
#[rhai_fn(name = "write_le")] #[rhai_fn(name = "write_le")]
pub fn write_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) { pub fn write_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) {
let start = INT::max(*range.start(), 0); let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start); let end = INT::max(*range.end(), start);
write_le_float(blob, start, end - start + 1, value) write_le_float(blob, start, end - start + 1, value)
} }
/// Write a `FLOAT` value to the bytes beginning at the `start` position in the BLOB
/// in little-endian byte order.
///
/// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte).
/// * If `start` < -length of BLOB, position counts from the beginning of the BLOB.
/// * If `start` ≥ length of BLOB, zero is returned.
/// * If `len` ≤ 0, zero is returned.
/// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed.
///
/// * If number of bytes in `range` < number of bytes for `FLOAT`, extra bytes in `FLOAT` are not written.
/// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes in `range` are not modified.
#[rhai_fn(name = "write_le")] #[rhai_fn(name = "write_le")]
pub fn write_le_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) { pub fn write_le_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) {
write_float(blob, start, len, value, true) write_float(blob, start, len, value, true)
} }
/// Write a `FLOAT` value to the bytes within an exclusive `range` in the BLOB
/// in big-endian byte order.
///
/// * If number of bytes in `range` < number of bytes for `FLOAT`, extra bytes in `FLOAT` are not written.
/// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes in `range` are not modified.
#[rhai_fn(name = "write_be")] #[rhai_fn(name = "write_be")]
pub fn write_be_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) { pub fn write_be_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) {
let start = INT::max(range.start, 0); let start = INT::max(range.start, 0);
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
write_be_float(blob, start, end - start, value) write_be_float(blob, start, end - start, value)
} }
/// Write a `FLOAT` value to the bytes within an inclusive `range` in the BLOB
/// in big-endian byte order.
///
/// * If number of bytes in `range` < number of bytes for `FLOAT`, extra bytes in `FLOAT` are not written.
/// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes in `range` are not modified.
#[rhai_fn(name = "write_be")] #[rhai_fn(name = "write_be")]
pub fn write_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) { pub fn write_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) {
let start = INT::max(*range.start(), 0); let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start); let end = INT::max(*range.end(), start);
write_be_float(blob, start, end - start + 1, value) write_be_float(blob, start, end - start + 1, value)
} }
/// Write a `FLOAT` value to the bytes beginning at the `start` position in the BLOB
/// in big-endian byte order.
///
/// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte).
/// * If `start` < -length of BLOB, position counts from the beginning of the BLOB.
/// * If `start` ≥ length of BLOB, zero is returned.
/// * If `len` ≤ 0, zero is returned.
/// * If `start` position + `len` ≥ length of BLOB, entire portion of the BLOB after the `start` position is parsed.
///
/// * If number of bytes in `range` < number of bytes for `FLOAT`, extra bytes in `FLOAT` are not written.
/// * If number of bytes in `range` > number of bytes for `FLOAT`, extra bytes in `range` are not modified.
#[rhai_fn(name = "write_be")] #[rhai_fn(name = "write_be")]
pub fn write_be_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) { pub fn write_be_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) {
write_float(blob, start, len, value, false) write_float(blob, start, len, value, false)
@ -1063,32 +1407,100 @@ mod write_string_functions {
blob[start..][..len].copy_from_slice(&string.as_bytes()[..len]); blob[start..][..len].copy_from_slice(&string.as_bytes()[..len]);
} }
} }
#[rhai_fn(name = "write_utf8")] /// Write a string to the bytes within an exclusive `range` in the BLOB in UTF-8 encoding.
pub fn write_utf8_string(blob: &mut Blob, start: INT, len: INT, string: &str) { ///
write_string(blob, start, len, string, false) /// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written.
} /// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified.
///
/// ```ignore
/// let b = blob(8);
///
/// b.write_utf8(1..5, "朝には紅顔ありて夕べには白骨となる");
///
/// print(b); // prints "[00e69c9de3000000]"
/// ```
#[rhai_fn(name = "write_utf8")] #[rhai_fn(name = "write_utf8")]
pub fn write_utf8_string_range(blob: &mut Blob, range: ExclusiveRange, string: &str) { pub fn write_utf8_string_range(blob: &mut Blob, range: ExclusiveRange, string: &str) {
let start = INT::max(range.start, 0); let start = INT::max(range.start, 0);
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
write_string(blob, start, end - start, string, false) write_string(blob, start, end - start, string, false)
} }
/// Write a string to the bytes within an inclusive `range` in the BLOB in UTF-8 encoding.
///
/// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written.
/// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified.
///
/// ```ignore
/// let b = blob(8);
///
/// b.write_utf8(1..=5, "朝には紅顔ありて夕べには白骨となる");
///
/// print(b); // prints "[00e69c9de3810000]"
/// ```
#[rhai_fn(name = "write_utf8")] #[rhai_fn(name = "write_utf8")]
pub fn write_utf8_string_range_inclusive(blob: &mut Blob, range: InclusiveRange, string: &str) { pub fn write_utf8_string_range_inclusive(blob: &mut Blob, range: InclusiveRange, string: &str) {
let start = INT::max(*range.start(), 0); let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start); let end = INT::max(*range.end(), start);
write_string(blob, start, end - start + 1, string, false) write_string(blob, start, end - start + 1, string, false)
} }
#[rhai_fn(name = "write_ascii")] /// Write a string to the bytes within an inclusive `range` in the BLOB in UTF-8 encoding.
pub fn write_ascii_string(blob: &mut Blob, start: INT, len: INT, string: &str) { ///
write_string(blob, start, len, string, true) /// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte).
/// * If `start` < -length of BLOB, position counts from the beginning of the BLOB.
/// * If `start` ≥ length of BLOB, the BLOB is not modified.
/// * If `len` ≤ 0, the BLOB is not modified.
/// * If `start` position + `len` ≥ length of BLOB, only the portion of the BLOB after the `start` position is modified.
///
/// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written.
/// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified.
///
/// ```ignore
/// let b = blob(8);
///
/// b.write_utf8(1, 5, "朝には紅顔ありて夕べには白骨となる");
///
/// print(b); // prints "[00e69c9de3810000]"
/// ```
#[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)
} }
/// Write an ASCII string to the bytes within an exclusive `range` in the BLOB.
///
/// Each ASCII character encodes to one single byte in the BLOB.
/// Non-ASCII characters are ignored.
///
/// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written.
/// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified.
///
/// ```ignore
/// let b = blob(8);
///
/// b.write_ascii(1..5, "hello, world!");
///
/// print(b); // prints "[0068656c6c000000]"
/// ```
#[rhai_fn(name = "write_ascii")] #[rhai_fn(name = "write_ascii")]
pub fn write_ascii_string_range(blob: &mut Blob, range: ExclusiveRange, string: &str) { pub fn write_ascii_string_range(blob: &mut Blob, range: ExclusiveRange, string: &str) {
let start = INT::max(range.start, 0); let start = INT::max(range.start, 0);
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
write_string(blob, start, end - start, string, true) write_string(blob, start, end - start, string, true)
} }
/// Write an ASCII string to the bytes within an inclusive `range` in the BLOB.
///
/// Each ASCII character encodes to one single byte in the BLOB.
/// Non-ASCII characters are ignored.
///
/// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written.
/// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified.
///
/// ```ignore
/// let b = blob(8);
///
/// b.write_ascii(1..=5, "hello, world!");
///
/// print(b); // prints "[0068656c6c6f0000]"
/// ```
#[rhai_fn(name = "write_ascii")] #[rhai_fn(name = "write_ascii")]
pub fn write_ascii_string_range_inclusive( pub fn write_ascii_string_range_inclusive(
blob: &mut Blob, blob: &mut Blob,
@ -1099,4 +1511,26 @@ mod write_string_functions {
let end = INT::max(*range.end(), start); let end = INT::max(*range.end(), start);
write_string(blob, start, end - start + 1, string, true) write_string(blob, start, end - start + 1, string, true)
} }
/// Write an ASCII string to the bytes within an exclusive `range` in the BLOB.
///
/// * If `start` < 0, position counts from the end of the BLOB (`-1` is the last byte).
/// * If `start` < -length of BLOB, position counts from the beginning of the BLOB.
/// * If `start` ≥ length of BLOB, the BLOB is not modified.
/// * If `len` ≤ 0, the BLOB is not modified.
/// * If `start` position + `len` ≥ length of BLOB, only the portion of the BLOB after the `start` position is modified.
///
/// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written.
/// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified.
///
/// ```ignore
/// let b = blob(8);
///
/// b.write_ascii(1, 5, "hello, world!");
///
/// print(b); // prints "[0068656c6c6f0000]"
/// ```
#[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)
}
} }

View File

@ -481,6 +481,12 @@ def_package! {
// Register string iterator // Register string iterator
lib.set_iterator::<CharsStream>(); lib.set_iterator::<CharsStream>();
#[cfg(feature = "metadata")]
let (range_type, range_inclusive_type) = (
format!("range: Range<{}>", std::any::type_name::<INT>()),
format!("range: RangeInclusive<{}>", std::any::type_name::<INT>()),
);
let _hash = lib.set_native_fn("chars", |string, range: ExclusiveRange| { let _hash = lib.set_native_fn("chars", |string, range: ExclusiveRange| {
let from = INT::max(range.start, 0); let from = INT::max(range.start, 0);
let to = INT::max(range.end, from); let to = INT::max(range.end, from);
@ -489,7 +495,7 @@ def_package! {
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata_with_comments( lib.update_fn_metadata_with_comments(
_hash, _hash,
["string: &str", "range: Range<INT>", "Iterator<Item=char>"], ["string: &str", &range_type, "Iterator<Item=char>"],
[ [
"/// Return an iterator over an exclusive range of characters in the string.", "/// Return an iterator over an exclusive range of characters in the string.",
"///", "///",
@ -511,7 +517,7 @@ def_package! {
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata_with_comments( lib.update_fn_metadata_with_comments(
_hash, _hash,
["string: &str", "range: RangeInclusive<INT>", "Iterator<Item=char>"], ["string: &str", &range_inclusive_type, "Iterator<Item=char>"],
[ [
"/// Return an iterator over an inclusive range of characters in the string.", "/// Return an iterator over an inclusive range of characters in the string.",
"///", "///",
@ -621,7 +627,7 @@ def_package! {
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata_with_comments( lib.update_fn_metadata_with_comments(
_hash, _hash,
["value: INT", "range: Range<INT>", "Iterator<Item=bool>"], ["value: INT", &range_type, "Iterator<Item=bool>"],
[ [
"/// Return an iterator over an exclusive range of bits in the number.", "/// Return an iterator over an exclusive range of bits in the number.",
"///", "///",
@ -645,7 +651,7 @@ def_package! {
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata_with_comments( lib.update_fn_metadata_with_comments(
_hash, _hash,
["value: INT", "range: RangeInclusive<INT>", "Iterator<Item=bool>"], ["value: INT", &range_inclusive_type, "Iterator<Item=bool>"],
[ [
"/// Return an iterator over an inclusive range of bits in the number.", "/// Return an iterator over an inclusive range of bits in the number.",
"///", "///",
@ -733,7 +739,7 @@ def_package! {
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
lib.update_fn_metadata_with_comments( lib.update_fn_metadata_with_comments(
_hash, _hash,
["value: &mut INT", "range: Range<INT>", "Iterator<Item=bool>"], ["value: &mut INT", "Iterator<Item=bool>"],
[ [
"/// Return an iterator over all the bits in the number.", "/// Return an iterator over all the bits in the number.",
"///", "///",

View File

@ -10,16 +10,17 @@ def_package! {
crate::LanguageCorePackage => |lib| { crate::LanguageCorePackage => |lib| {
lib.standard = true; lib.standard = true;
combine_with_exported_module!(lib, "language_core", core_functions); combine_with_exported_module!(lib, "core", core_functions);
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
combine_with_exported_module!(lib, "reflection", reflection_functions);
} }
} }
#[export_module] #[export_module]
mod core_functions { mod core_functions {
#[rhai_fn(name = "!")]
pub fn not(x: bool) -> bool {
!x
}
#[rhai_fn(name = "tag", get = "tag", pure)] #[rhai_fn(name = "tag", get = "tag", pure)]
pub fn get_tag(value: &mut Dynamic) -> INT { pub fn get_tag(value: &mut Dynamic) -> INT {
value.tag() as INT value.tag() as INT
@ -53,19 +54,38 @@ mod core_functions {
Ok(()) Ok(())
} }
} }
}
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[export_module]
mod reflection_functions {
pub fn get_fn_metadata_list(ctx: NativeCallContext) -> crate::Array { pub fn get_fn_metadata_list(ctx: NativeCallContext) -> crate::Array {
collect_fn_metadata(ctx) collect_fn_metadata(ctx, |_, _, _, _, _| true)
}
#[rhai_fn(name = "get_fn_metadata_list")]
pub fn get_fn_metadata(ctx: NativeCallContext, name: &str) -> crate::Array {
collect_fn_metadata(ctx, |_, _, n, _, _| n == name)
}
#[rhai_fn(name = "get_fn_metadata_list")]
pub fn get_fn_metadata2(ctx: NativeCallContext, name: &str, params: INT) -> crate::Array {
if params < 0 {
crate::Array::new()
} else {
collect_fn_metadata(ctx, |_, _, n, p, _| p == (params as usize) && n == name)
}
} }
} }
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { fn collect_fn_metadata(
ctx: NativeCallContext,
filter: impl Fn(FnNamespace, FnAccess, &str, usize, &crate::Shared<crate::ast::ScriptFnDef>) -> bool
+ Copy,
) -> crate::Array {
use crate::{ast::ScriptFnDef, Array, Identifier, Map}; use crate::{ast::ScriptFnDef, Array, Identifier, Map};
use std::collections::BTreeSet; use std::collections::BTreeSet;
@ -125,13 +145,26 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
.map(|&s| s.into()) .map(|&s| s.into())
.collect(); .collect();
let mut _list = ctx.iter_namespaces().flat_map(Module::iter_script_fn).fold( let mut list = Array::new();
Array::new(),
|mut list, (_, _, _, _, f)| { ctx.iter_namespaces()
list.push(make_metadata(&dict, None, f).into()); .flat_map(Module::iter_script_fn)
list .filter(|(s, a, n, p, f)| filter(*s, *a, n, *p, f))
}, .for_each(|(_, _, _, _, f)| list.push(make_metadata(&dict, None, f).into()));
);
ctx.engine()
.global_modules
.iter()
.flat_map(|m| m.iter_script_fn())
.filter(|(ns, a, n, p, f)| filter(*ns, *a, n, *p, f))
.for_each(|(_, _, _, _, f)| list.push(make_metadata(&dict, None, f).into()));
ctx.engine()
.global_sub_modules
.values()
.flat_map(|m| m.iter_script_fn())
.filter(|(ns, a, n, p, f)| filter(*ns, *a, n, *p, f))
.for_each(|(_, _, _, _, f)| list.push(make_metadata(&dict, None, f).into()));
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
{ {
@ -141,8 +174,19 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
dict: &BTreeSet<Identifier>, dict: &BTreeSet<Identifier>,
namespace: Identifier, namespace: Identifier,
module: &Module, module: &Module,
filter: impl Fn(
FnNamespace,
FnAccess,
&str,
usize,
&crate::Shared<crate::ast::ScriptFnDef>,
) -> bool
+ Copy,
) { ) {
module.iter_script_fn().for_each(|(_, _, _, _, f)| { module
.iter_script_fn()
.filter(|(s, a, n, p, f)| filter(*s, *a, n, *p, f))
.for_each(|(_, _, _, _, f)| {
list.push(make_metadata(dict, Some(namespace.clone()), f).into()) list.push(make_metadata(dict, Some(namespace.clone()), f).into())
}); });
module.iter_sub_modules().for_each(|(ns, m)| { module.iter_sub_modules().for_each(|(ns, m)| {
@ -152,13 +196,13 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
crate::tokenizer::Token::DoubleColon.literal_syntax(), crate::tokenizer::Token::DoubleColon.literal_syntax(),
ns ns
); );
scan_module(list, dict, ns.into(), m.as_ref()) scan_module(list, dict, ns.into(), m.as_ref(), filter)
}); });
} }
ctx.iter_imports_raw() ctx.iter_imports_raw()
.for_each(|(ns, m)| scan_module(&mut _list, &dict, ns.clone(), m.as_ref())); .for_each(|(ns, m)| scan_module(&mut list, &dict, ns.clone(), m.as_ref(), filter));
} }
_list list
} }

View File

@ -62,6 +62,8 @@ def_package! {
reg_functions!(lib += float; f64); reg_functions!(lib += float; f64);
combine_with_exported_module!(lib, "f64", f64_functions); combine_with_exported_module!(lib, "f64", f64_functions);
} }
combine_with_exported_module!(lib, "logic", logic_functions);
} }
} }
@ -83,6 +85,14 @@ gen_cmp_functions!(float => f32);
#[cfg(feature = "f32_float")] #[cfg(feature = "f32_float")]
gen_cmp_functions!(float => f64); gen_cmp_functions!(float => f64);
#[export_module]
mod logic_functions {
#[rhai_fn(name = "!")]
pub fn not(x: bool) -> bool {
!x
}
}
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[export_module] #[export_module]
mod f32_functions { mod f32_functions {

View File

@ -25,6 +25,51 @@ mod map_functions {
pub fn len(map: &mut Map) -> INT { pub fn len(map: &mut Map) -> INT {
map.len() as INT map.len() as INT
} }
/// Get the value of the `property` in the object map and return a copy.
///
/// If `property` does not exist in the object map, `()` is returned.
///
/// # Example
///
/// ```rhai
/// let m = #{a: 1, b: 2, c: 3};
///
/// print(m.get("b")); // prints 2
///
/// print(m.get("x")); // prints empty (for '()')
/// ```
pub fn get(map: &mut Map, property: &str) -> Dynamic {
if map.is_empty() {
return Dynamic::UNIT;
}
map.get(property).cloned().unwrap_or(Dynamic::UNIT)
}
/// Set the value of the `property` in the object map to a new `value`.
///
/// If `property` does not exist in the object map, it is added.
///
/// # Example
///
/// ```rhai
/// let m = #{a: 1, b: 2, c: 3};
///
/// m.set("b", 42)'
///
/// print(m); // prints "#{a: 1, b: 42, c: 3}"
///
/// x.set("x", 0);
///
/// print(m); // prints "#{a: 1, b: 42, c: 3, x: 0}"
/// ```
pub fn set(map: &mut Map, property: &str, value: Dynamic) {
if let Some(value_ref) = map.get_mut(property) {
*value_ref = value;
} else {
map.insert(property.into(), value);
}
}
/// Clear the object map. /// Clear the object map.
pub fn clear(map: &mut Map) { pub fn clear(map: &mut Map) {
if !map.is_empty() { if !map.is_empty() {
@ -46,9 +91,9 @@ mod map_functions {
/// ///
/// print(m); // prints "#{a:1, c:3}" /// print(m); // prints "#{a:1, c:3}"
/// ``` /// ```
pub fn remove(map: &mut Map, name: ImmutableString) -> Dynamic { pub fn remove(map: &mut Map, property: &str) -> Dynamic {
if !map.is_empty() { if !map.is_empty() {
map.remove(name.as_str()).unwrap_or_else(|| Dynamic::UNIT) map.remove(property).unwrap_or_else(|| Dynamic::UNIT)
} else { } else {
Dynamic::UNIT Dynamic::UNIT
} }

View File

@ -551,6 +551,85 @@ mod string_functions {
} }
} }
/// Get the character at the `index` position in the string.
///
/// * If `index` < 0, position counts from the end of the string (`-1` is the last character).
/// * If `index` < -length of string, zero is returned.
/// * If `index` ≥ length of string, zero is returned.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world!";
///
/// print(text.get(0)); // prints 'h'
///
/// print(text.get(-1)); // prints '!'
///
/// print(text.get(99)); // prints empty (for '()')'
/// ```
pub fn get(string: &str, index: INT) -> Dynamic {
if index >= 0 {
string
.chars()
.nth(index as usize)
.map_or_else(|| Dynamic::UNIT, Into::into)
} else if let Some(abs_index) = index.checked_abs() {
// Count from end if negative
string
.chars()
.rev()
.nth((abs_index as usize) - 1)
.map_or_else(|| Dynamic::UNIT, Into::into)
} else {
Dynamic::UNIT
}
}
/// Set the `index` position in the string to a new `character`.
///
/// * If `index` < 0, position counts from the end of the string (`-1` is the last character).
/// * If `index` < -length of string, the string is not modified.
/// * If `index` ≥ length of string, the string is not modified.
///
/// # Example
///
/// ```rhai
/// let text = "hello, world!";
///
/// text.set(3, 'x');
///
/// print(text); // prints "helxo, world!"
///
/// text.set(-3, 'x');
///
/// print(text); // prints "hello, worxd!"
///
/// text.set(99, 'x');
///
/// print(text); // prints "hello, worxd!"
/// ```
pub fn set(string: &mut ImmutableString, index: INT, character: char) {
if index >= 0 {
let index = index as usize;
*string = string
.chars()
.enumerate()
.map(|(i, ch)| if i == index { character } else { ch })
.collect();
} else if let Some(abs_index) = index.checked_abs() {
let string_len = string.chars().count();
if abs_index as usize <= string_len {
let index = string_len - (abs_index as usize);
*string = string
.chars()
.enumerate()
.map(|(i, ch)| if i == index { character } else { ch })
.collect();
}
}
}
/// Copy an exclusive range of characters from the string and return it as a new string. /// Copy an exclusive range of characters from the string and return it as a new string.
/// ///
/// # Example /// # Example
@ -685,7 +764,7 @@ mod string_functions {
} }
} }
/// Remove all characters from the string except those within an exclusive range. /// Remove all characters from the string except those within an exclusive `range`.
/// ///
/// # Example /// # Example
/// ///
@ -702,7 +781,7 @@ mod string_functions {
let end = INT::max(range.end, start); let end = INT::max(range.end, start);
crop(string, start, end - start) crop(string, start, end - start)
} }
/// Remove all characters from the string except those within an inclusive range. /// Remove all characters from the string except those within an inclusive `range`.
/// ///
/// # Example /// # Example
/// ///

View File

@ -7,7 +7,7 @@ use crate::{calc_fn_hash, Engine, AST};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{cmp::Ordering, collections::BTreeMap, iter::empty}; use std::{borrow::Cow, cmp::Ordering, collections::BTreeMap, iter::empty};
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -54,7 +54,7 @@ struct FnParam<'a> {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<&'a str>, pub name: Option<&'a str>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")] #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub typ: Option<&'a str>, pub typ: Option<Cow<'a, str>>,
} }
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
@ -125,12 +125,12 @@ impl<'a> From<&'a FuncInfo> for FnMetadata<'a> {
"_" => None, "_" => None,
s => Some(s), s => Some(s),
}; };
let typ = seg.next().map(&str::trim); let typ = seg.next().map(|s| FuncInfo::format_type(s, false));
FnParam { name, typ } FnParam { name, typ }
}) })
.collect(), .collect(),
_dummy: None, _dummy: None,
return_type: FuncInfo::format_return_type(&info.metadata.return_type).into_owned(), return_type: FuncInfo::format_type(&info.metadata.return_type, true).into_owned(),
signature: info.gen_signature(), signature: info.gen_signature(),
doc_comments: if info.func.is_script() { doc_comments: if info.func.is_script() {
#[cfg(feature = "no_function")] #[cfg(feature = "no_function")]
@ -196,11 +196,12 @@ impl Engine {
/// 1) Functions defined in an [`AST`][crate::AST] /// 1) Functions defined in an [`AST`][crate::AST]
/// 2) Functions registered into the global namespace /// 2) Functions registered into the global namespace
/// 3) Functions in static modules /// 3) Functions in static modules
/// 4) Functions in global modules (optional) /// 4) Functions in registered global packages
/// 5) Functions in standard packages (optional)
pub fn gen_fn_metadata_with_ast_to_json( pub fn gen_fn_metadata_with_ast_to_json(
&self, &self,
ast: &AST, ast: &AST,
include_global: bool, include_packages: bool,
) -> serde_json::Result<String> { ) -> serde_json::Result<String> {
let _ast = ast; let _ast = ast;
let mut global = ModuleMetadata::new(); let mut global = ModuleMetadata::new();
@ -211,14 +212,20 @@ impl Engine {
self.global_modules self.global_modules
.iter() .iter()
.take(if include_global { usize::MAX } else { 1 }) .filter(|m| include_packages || !m.standard)
.flat_map(|m| m.iter_fn()) .flat_map(|m| m.iter_fn())
.for_each(|f| global.functions.push(f.into())); .for_each(|f| {
let mut meta: FnMetadata = f.into();
meta.namespace = FnNamespace::Global;
global.functions.push(meta);
});
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
_ast.shared_lib() _ast.shared_lib().iter_fn().for_each(|f| {
.iter_fn() let mut meta: FnMetadata = f.into();
.for_each(|f| global.functions.push(f.into())); meta.namespace = FnNamespace::Global;
global.functions.push(meta);
});
global.functions.sort(); global.functions.sort();
@ -233,7 +240,7 @@ impl Engine {
/// 2) Functions in static modules /// 2) Functions in static modules
/// 3) Functions in global modules (optional) /// 3) Functions in global modules (optional)
#[inline(always)] #[inline(always)]
pub fn gen_fn_metadata_to_json(&self, include_global: bool) -> serde_json::Result<String> { pub fn gen_fn_metadata_to_json(&self, include_packages: bool) -> serde_json::Result<String> {
self.gen_fn_metadata_with_ast_to_json(&AST::empty(), include_global) self.gen_fn_metadata_with_ast_to_json(&AST::empty(), include_packages)
} }
} }

View File

@ -636,7 +636,7 @@ impl Token {
/// Get the corresponding operator of the token if it is an op-assignment operator. /// Get the corresponding operator of the token if it is an op-assignment operator.
#[must_use] #[must_use]
pub const fn map_op_assignment(&self) -> Option<Self> { pub const fn get_base_op_from_assignment(&self) -> Option<Self> {
Some(match self { Some(match self {
Self::PlusAssign => Self::Plus, Self::PlusAssign => Self::Plus,
Self::MinusAssign => Self::Minus, Self::MinusAssign => Self::Minus,
@ -675,7 +675,7 @@ impl Token {
/// Get the corresponding op-assignment operator of the token. /// Get the corresponding op-assignment operator of the token.
#[must_use] #[must_use]
pub const fn make_op_assignment(&self) -> Option<Self> { pub const fn convert_to_op_assignment(&self) -> Option<Self> {
Some(match self { Some(match self {
Self::Plus => Self::PlusAssign, Self::Plus => Self::PlusAssign,
Self::Minus => Self::MinusAssign, Self::Minus => Self::MinusAssign,
@ -1594,7 +1594,7 @@ fn get_next_token_inner(
eat_next(stream, pos); eat_next(stream, pos);
pos.new_line(); pos.new_line();
// `\r\n // `\r\n
if stream.peek_next().map(|ch| ch == '\n').unwrap_or(false) { if let Some('\n') = stream.peek_next() {
eat_next(stream, pos); eat_next(stream, pos);
} }
} }
@ -1761,6 +1761,14 @@ fn get_next_token_inner(
}; };
while let Some(c) = stream.get_next() { while let Some(c) = stream.get_next() {
if c == '\r' {
pos.new_line();
// \r\n
if let Some('\n') = stream.peek_next() {
eat_next(stream, pos);
}
break;
}
if c == '\n' { if c == '\n' {
pos.new_line(); pos.new_line();
break; break;

View File

@ -546,44 +546,66 @@ impl Hash for Dynamic {
/// Map the name of a standard type into a friendly form. /// Map the name of a standard type into a friendly form.
#[inline] #[inline]
#[must_use] #[must_use]
pub(crate) fn map_std_type_name(name: &str) -> &str { pub(crate) fn map_std_type_name(name: &str, shorthands: bool) -> &str {
let name = name.trim();
if name.starts_with("rhai::") {
return map_std_type_name(&name[6..], shorthands);
}
if name == type_name::<String>() { if name == type_name::<String>() {
return "string"; return if shorthands { "string" } else { "String" };
} }
if name == type_name::<ImmutableString>() { if name == type_name::<ImmutableString>() {
return "string"; return if shorthands {
"string"
} else {
"ImmutableString"
};
} }
if name == type_name::<&str>() { if name == type_name::<&str>() {
return "string"; return if shorthands { "string" } else { "&str" };
}
if name == type_name::<FnPtr>() {
return "Fn";
} }
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
if name == type_name::<rust_decimal::Decimal>() { if name == type_name::<rust_decimal::Decimal>() {
return "decimal"; return if shorthands { "decimal" } else { "Decimal" };
}
if name == type_name::<FnPtr>() {
return if shorthands { "Fn" } else { "FnPtr" };
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if name == type_name::<crate::Array>() { if name == type_name::<crate::Array>() {
return "array"; return if shorthands { "array" } else { "Array" };
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if name == type_name::<crate::Blob>() { if name == type_name::<crate::Blob>() {
return "blob"; return if shorthands { "blob" } else { "Blob" };
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
if name == type_name::<crate::Map>() { if name == type_name::<crate::Map>() {
return "map"; return if shorthands { "map" } else { "Map" };
} }
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
if name == type_name::<Instant>() { if name == type_name::<Instant>() {
return "timestamp"; return if shorthands { "timestamp" } else { "Instant" };
} }
if name == type_name::<ExclusiveRange>() { if name == type_name::<ExclusiveRange>() || name == "ExclusiveRange" {
return "range"; return if shorthands {
"range"
} else if cfg!(feature = "only_i32") {
"Range<i32>"
} else {
"Range<i64>"
};
} }
if name == type_name::<InclusiveRange>() { if name == type_name::<InclusiveRange>() || name == "InclusiveRange" {
return "range="; return if shorthands {
"range="
} else if cfg!(feature = "only_i32") {
"RangeInclusive<i32>"
} else {
"RangeInclusive<i64>"
};
} }
name name

View File

@ -254,3 +254,51 @@ fn test_get_set_chain_without_write_back() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
fn test_get_set_collection() -> Result<(), Box<EvalAltResult>> {
type MyItem = INT;
type MyBag = std::collections::BTreeSet<MyItem>;
let mut engine = Engine::new();
engine
.register_type_with_name::<MyBag>("MyBag")
.register_iterator::<MyBag>()
.register_fn("new_bag", || MyBag::new())
.register_fn("len", |col: &mut MyBag| col.len() as INT)
.register_get("len", |col: &mut MyBag| col.len() as INT)
.register_fn("clear", |col: &mut MyBag| col.clear())
.register_fn("contains", |col: &mut MyBag, item: INT| col.contains(&item))
.register_fn("add", |col: &mut MyBag, item: MyItem| col.insert(item))
.register_fn("+=", |col: &mut MyBag, item: MyItem| col.insert(item))
.register_fn("remove", |col: &mut MyBag, item: MyItem| col.remove(&item))
.register_fn("-=", |col: &mut MyBag, item: MyItem| col.remove(&item))
.register_fn("+", |mut col1: MyBag, col2: MyBag| {
col1.extend(col2.into_iter());
col1
});
let result = engine.eval::<INT>(
"
let bag = new_bag();
bag += 1;
bag += 2;
bag += 39;
bag -= 2;
if !bag.contains(2) {
let sum = 0;
for n in bag { sum += n; }
sum + bag.len
} else {
-1
}
",
)?;
assert_eq!(result, 42);
Ok(())
}

View File

@ -6,6 +6,11 @@ fn test_ops() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>("60 + 5")?, 65); assert_eq!(engine.eval::<INT>("60 + 5")?, 65);
assert_eq!(engine.eval::<INT>("(1 + 2) * (6 - 4) / 2")?, 3); assert_eq!(engine.eval::<INT>("(1 + 2) * (6 - 4) / 2")?, 3);
assert_eq!(engine.eval::<INT>("let x = 41; x = x + 1; x")?, 42);
assert_eq!(
engine.eval::<String>(r#"let s = "hello"; s = s + 42; s"#)?,
"hello42"
);
Ok(()) Ok(())
} }