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:
matrix:
include:
- {os: ubuntu-latest, flags: "--profile unix -Z unstable-options", experimental: false}
- {os: windows-latest, flags: "--profile windows -Z unstable-options", experimental: true}
- {os: macos-latest, flags: "--profile macos -Z unstable-options", experimental: false}
- {os: ubuntu-latest, flags: "--profile unix", experimental: false}
- {os: windows-latest, flags: "--profile windows", experimental: true}
- {os: macos-latest, flags: "--profile macos", experimental: false}
steps:
- name: Checkout
uses: actions/checkout@v2

View File

@ -1,12 +1,13 @@
Rhai Release Notes
==================
Version 1.5.0
Version 1.4.1
=============
Bug fixes
---------
* Expressions such as `x = x + 1` no longer panics.
* Padding arrays with another array via `pad` no longer loops indefinitely.
* `chop` for arrays and BLOB's now works properly.
* `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.
* `remove` for arrays and BLOB's now treat negative index correctly.
* `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
------------
@ -22,6 +25,7 @@ Enhancements
* Formatting of return types in functions metadata info is improved.
* 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`).
* `get` and `set` methods are added to arrays, BLOB's, object maps and strings.
Version 1.4.0

View File

@ -3,7 +3,7 @@
///! Test evaluating with scope
extern crate test;
use rhai::{Engine, Module, OptimizationLevel};
use rhai::{Engine, Module, OptimizationLevel, Scope};
use test::Bencher;
#[bench]
@ -18,7 +18,7 @@ fn bench_eval_module(bench: &mut Bencher) {
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());

View File

@ -142,33 +142,34 @@ pub fn inner_item_attributes<T: ExportedParams>(
}
#[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.
let mut comments = Vec::new();
while let Some(index) = attrs
.iter()
.position(|attr| attr.path.get_ident().map(|i| *i == "doc").unwrap_or(false))
{
match attrs.remove(index).parse_meta()? {
syn::Meta::NameValue(syn::MetaNameValue {
lit: syn::Lit::Str(s),
..
}) => {
let mut line = s.value();
for attr in attrs {
if let Some(i) = attr.path.get_ident() {
if *i == "doc" {
match attr.parse_meta()? {
syn::Meta::NameValue(syn::MetaNameValue {
lit: syn::Lit::Str(s),
..
}) => {
let mut line = s.value();
if line.contains('\n') {
// Must be a block comment `/** ... */`
line.insert_str(0, "/**");
line.push_str("*/");
} else {
// Single line - assume it is `///`
line.insert_str(0, "///");
if line.contains('\n') {
// Must be a block comment `/** ... */`
line.insert_str(0, "/**");
line.push_str("*/");
} else {
// Single line - assume it is `///`
line.insert_str(0, "///");
}
comments.push(line);
}
_ => (),
}
comments.push(line);
}
_ => continue,
}
}

View File

@ -407,7 +407,7 @@ impl Parse for ExportedFn {
params: Default::default(),
cfg_attrs,
#[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));
#[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)
})?;
@ -144,12 +144,12 @@ impl Parse for Module {
attrs,
ty,
..
}) if matches!(vis, syn::Visibility::Public(_)) => consts.push((
ident.to_string(),
ty.clone(),
expr.as_ref().clone(),
crate::attrs::collect_cfg_attr(&attrs),
)),
}) if matches!(vis, syn::Visibility::Public(_)) => consts.push(ExportedConst {
name: ident.to_string(),
typ: ty.clone(),
expr: expr.as_ref().clone(),
cfg_attrs: crate::attrs::collect_cfg_attr(&attrs),
}),
_ => {}
}
}

View File

@ -10,7 +10,13 @@ use crate::function::{
};
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(
fns: &mut [ExportedFn],
@ -25,7 +31,12 @@ pub fn generate_body(
let str_type_path = syn::parse2::<syn::Path>(quote! { str }).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_ref = syn::Ident::new(&const_name, Span::call_site());

View File

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

View File

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

View File

@ -3,9 +3,9 @@
///
/// # Parameters
///
/// `x` - `i64`
/// `y` - `string`
/// `z` - `bool`
/// * `x` - `i64`
/// * `y` - `string`
/// * `z` - `bool`
///
/// # Notes
///
@ -13,6 +13,13 @@
///
/// An example is the `rhai-doc` app.
///
/// # Example
///
/// ```rhai
/// let x = foo(42, "hello", true);
///
/// print(x); // prints 47
/// ```
fn foo(x, y, z) {
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];
// Loop over array with counter
for (a, i) in arr {
for (b, j) in ['x', 42, (), 123, 99, 0.5] {
if b > 100 { continue; }

View File

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

View File

@ -1,3 +1,5 @@
// This script runs for-loops with closures.
const MAX = 100;
const CHECK = ((MAX - 1) ** 2) * MAX;
@ -9,6 +11,7 @@ print(`Creating ${MAX} closures...`);
let list = [];
// Loop over range
for i in 0..MAX {
list.push(|| i ** 2);
}
@ -18,6 +21,7 @@ print(`Summing ${MAX} closures...`);
let sum = 0;
// Loop over array
for f in list {
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;
}
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;
fn addme(a, b) {
fn add(a, b) {
a = 42; // notice that 'a' is passed by value
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;
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);

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
fn action(x, y) {

View File

@ -1,3 +1,5 @@
// This script runs if statements.
let a = 42;
let b = 123;
let x = 999;
@ -7,7 +9,7 @@ if a > b {
} else if a < b {
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
} else {
print("Oops! a == b");

View File

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

View File

@ -1,12 +1,14 @@
// This script simulates multi-dimensional matrix calculations.
const SIZE = 50;
fn new_mat(x, y) {
let row = [];
row.pad(y, 0.0);
let matrix = [];
matrix.pad(x, row);
matrix
}
@ -20,13 +22,13 @@ fn mat_gen() {
m[i][j] = tmp * (i - j) * (i + j);
}
}
m
}
fn mat_mul(a, b) {
let b2 = new_mat(a[0].len, b[0].len);
for i in 0..a[0].len {
for j in 0..b[0].len {
b2[j][i] = b[i][j];
@ -38,7 +40,7 @@ fn mat_mul(a, b) {
for i in 0..c.len {
for j in 0..c[i].len {
c[i][j] = 0.0;
for z in 0..a[i].len {
c[i][j] += a[i][z] * b2[j][z];
}

View File

@ -1,3 +1,5 @@
// This script imports an external script as a module.
import "loop" as x;
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(34 + 12);

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,5 @@
// This script tests object maps and strings.
print("Ready... Go!");
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];
for item in arr {
switch item {
// Match single integer
42 => print("The Answer!"),
// Match single floating-point number
123.456 => print(`Floating point... ${item}`),
// Match single string
"hello" => print(`${item} world!`),
// Match another integer
999 => print(`Got 999: ${item}`),
// Match range with condition
0..100 if item % 2 == 0 => print(`A small even number: ${item}`),
// Match another range
0..100 => print(`A small odd number: ${item}`),
// Default case
_ => 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;

View File

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

View File

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

View File

@ -45,7 +45,7 @@ impl OpAssignment<'_> {
#[must_use]
pub fn new_from_token(op: Token) -> Self {
let op_raw = op
.map_op_assignment()
.get_base_op_from_assignment()
.expect("op-assignment operator")
.literal_syntax();
Self {
@ -54,6 +54,26 @@ impl OpAssignment<'_> {
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.

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::{
env,
@ -46,6 +46,7 @@ fn print_help() {
println!("help => print this help");
println!("quit, exit => quit");
println!("scope => print all variables in the scope");
println!("strict => toggle on/off Strict Variables Mode");
#[cfg(feature = "metadata")]
println!("functions => print all functions defined");
#[cfg(feature = "metadata")]
@ -56,6 +57,30 @@ fn print_help() {
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() {
let title = format!("Rhai REPL tool (version {})", env!("CARGO_PKG_VERSION"));
println!("{}", title);
@ -155,8 +180,12 @@ fn main() {
engine.set_module_resolver(resolver);
}
// Make Engine immutable
let engine = 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
let mut scope = Scope::new();
@ -208,26 +237,18 @@ fn main() {
continue;
}
"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
.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!();
print_scope(&scope);
continue;
}
"astu" => {

View File

@ -329,13 +329,47 @@ impl Engine {
///
/// 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.
///
/// # Panics
///
/// Panics if the type name is `&mut`.
#[inline]
#[must_use]
pub fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
self.type_names
.get(name)
.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]`>`.

View File

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

View File

@ -7,15 +7,15 @@ use std::prelude::v1::*;
/// Context of a script evaluation process.
#[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`].
pub(crate) engine: &'a Engine,
/// The current [`Scope`].
pub(crate) scope: &'x mut Scope<'px>,
/// The current [`GlobalRuntimeState`].
pub(crate) global: &'m mut GlobalRuntimeState,
pub(crate) global: &'m mut GlobalRuntimeState<'pm>,
/// 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].
pub(crate) lib: &'b [&'b Module],
/// 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,
}
impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, 'pt> {
impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, '_, '_, 'pt> {
/// The current [`Engine`].
#[inline(always)]
#[must_use]
@ -56,7 +56,7 @@ impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, 'pt> {
#[cfg(not(feature = "no_module"))]
#[inline(always)]
pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &Module)> {
self.global.iter_modules()
self.global.iter_imports()
}
/// _(internals)_ The current [`GlobalRuntimeState`].
/// Exported under the `internals` feature only.

View File

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

View File

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

View File

@ -8,6 +8,7 @@ use std::{
any::TypeId,
fmt,
iter::{FromIterator, Rev, Zip},
marker::PhantomData,
};
/// _(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
// corresponds to that key.
#[derive(Clone)]
pub struct GlobalRuntimeState {
pub struct GlobalRuntimeState<'a> {
/// Stack of module names.
//
// 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"))]
constants:
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)]
fn default() -> Self {
Self::new()
}
}
impl GlobalRuntimeState {
impl GlobalRuntimeState<'_> {
/// Create a new [`GlobalRuntimeState`].
#[inline(always)]
#[must_use]
pub const fn new() -> Self {
pub fn new() -> Self {
Self {
keys: StaticVec::new_const(),
modules: StaticVec::new_const(),
@ -72,31 +75,32 @@ impl GlobalRuntimeState {
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_function"))]
constants: None,
dummy: PhantomData::default(),
}
}
/// Get the length of the stack of globally-imported [modules][Module].
#[inline(always)]
#[must_use]
pub fn num_imported_modules(&self) -> usize {
pub fn num_imports(&self) -> usize {
self.keys.len()
}
/// Get the globally-imported [module][Module] at a particular index.
#[inline(always)]
#[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()
}
/// Get a mutable reference to the globally-imported [module][Module] at a particular index.
#[allow(dead_code)]
#[inline(always)]
#[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)
}
/// Get the index of a globally-imported [module][Module] by name.
#[inline]
#[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();
self.keys.iter().rev().enumerate().find_map(|(i, key)| {
@ -109,20 +113,20 @@ impl GlobalRuntimeState {
}
/// Push an imported [module][Module] onto the stack.
#[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.modules.push(module.into());
}
/// Truncate the stack of globally-imported [modules][Module] to a particular length.
#[inline(always)]
pub fn truncate_modules(&mut self, size: usize) {
pub fn truncate_imports(&mut self, size: usize) {
self.keys.truncate(size);
self.modules.truncate(size);
}
/// Get an iterator to the stack of globally-imported [modules][Module] in reverse order.
#[allow(dead_code)]
#[inline]
pub fn iter_modules(&self) -> impl Iterator<Item = (&str, &Module)> {
pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &Module)> {
self.keys
.iter()
.rev()
@ -132,26 +136,26 @@ impl GlobalRuntimeState {
/// Get an iterator to the stack of globally-imported [modules][Module] in reverse order.
#[allow(dead_code)]
#[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())
}
/// Get an iterator to the stack of globally-imported [modules][Module] in forward order.
#[allow(dead_code)]
#[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())
}
/// Does the specified function hash key exist in the stack of globally-imported [modules][Module]?
#[allow(dead_code)]
#[inline]
#[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))
}
/// Get the specified function via its hash key from the stack of globally-imported [modules][Module].
#[inline]
#[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
.iter()
.rev()
@ -230,7 +234,7 @@ impl GlobalRuntimeState {
}
}
impl IntoIterator for GlobalRuntimeState {
impl IntoIterator for GlobalRuntimeState<'_> {
type Item = (Identifier, Shared<Module>);
type IntoIter =
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]
fn from_iter<T: IntoIterator<Item = (K, M)>>(iter: T) -> Self {
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]
fn extend<T: IntoIterator<Item = (K, M)>>(&mut self, iter: T) {
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]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
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_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();
if restore_orig_state {
@ -38,7 +38,7 @@ impl Engine {
let mut result = Dynamic::UNIT;
for stmt in statements {
let _mods_len = global.num_imported_modules();
let _mods_len = global.num_imports();
result = self.eval_stmt(
scope,
@ -56,7 +56,7 @@ impl Engine {
// Get the extra modules - see if any functions are marked global.
// Without global functions, the extra modules never affect function resolution.
if global
.scan_modules_raw()
.scan_imports_raw()
.skip(_mods_len)
.any(|(_, m)| m.contains_indexed_global_functions())
{
@ -82,7 +82,7 @@ impl Engine {
if restore_orig_state {
scope.rewind(orig_scope_len);
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
// 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
let mut module = crate::func::native::shared_take_or_clone(module);
module.build_index();
global.push_module(name, module);
global.push_import(name, module);
} else {
global.push_module(name, module);
global.push_import(name, module);
}
}

View File

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

View File

@ -65,7 +65,7 @@ pub struct NativeCallContext<'a> {
/// Function source, if any.
source: Option<&'a str>,
/// The current [`GlobalRuntimeState`], if any.
global: Option<&'a GlobalRuntimeState>,
global: Option<&'a GlobalRuntimeState<'a>>,
/// The current stack of loaded [modules][Module].
lib: &'a [&'a Module],
/// [Position] of the function call.
@ -77,7 +77,7 @@ impl<'a, M: AsRef<[&'a Module]> + ?Sized, S: AsRef<str> + 'a + ?Sized>
&'a Engine,
&'a S,
Option<&'a S>,
&'a GlobalRuntimeState,
&'a GlobalRuntimeState<'a>,
&'a M,
Position,
)> for NativeCallContext<'a>
@ -199,7 +199,7 @@ impl<'a> NativeCallContext<'a> {
#[cfg(not(feature = "no_module"))]
#[inline]
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.
#[cfg(not(feature = "no_module"))]
@ -208,7 +208,7 @@ impl<'a> NativeCallContext<'a> {
pub(crate) fn iter_imports_raw(
&self,
) -> 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.
/// Exported under the `internals` feature only.

View File

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

View File

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

View File

@ -92,18 +92,60 @@ impl FuncInfo {
/// `()` is cleared.
/// [`RhaiResult`][crate::RhaiResult] and [`RhaiResultOf<T>`] are expanded.
#[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_EXPAND: &str = "Result<Dynamic, Box<EvalAltResult>>";
const RHAI_RESULT_OF_TYPE: &str = "RhaiResultOf<";
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 {
"" | "()" => "".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(),
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('>') => {
let inner = &ty[RHAI_RESULT_OF_TYPE.len()..ty.len() - 1];
RHAI_RESULT_OF_TYPE_EXPAND
.replace("{}", ty[RHAI_RESULT_OF_TYPE.len()..ty.len() - 1].trim())
.replace("{}", Self::format_type(inner, false).trim())
.into()
}
ty => ty.into(),
@ -116,22 +158,30 @@ impl FuncInfo {
pub fn gen_signature(&self) -> String {
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() {
let params: StaticVec<_> = self
.metadata
.params_info
.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();
sig.push_str(&params.join(", "));
sig.push(')');
if !return_type.is_empty() {
sig.push_str(" -> ");
sig.push_str(&return_type);
}
} else {
for x in 0..self.metadata.params {
sig.push('_');
@ -139,17 +189,12 @@ impl FuncInfo {
sig.push_str(", ");
}
}
sig.push(')');
}
if !self.func.is_script() {
sig.push(')');
if !return_type.is_empty() {
sig.push_str(" -> ");
sig.push_str(&return_type);
}
}
if !self.func.is_script() && !return_type.is_empty() {
sig.push_str(" -> ");
sig.push_str(&return_type);
}
sig
@ -541,7 +586,7 @@ impl Module {
#[cfg(feature = "metadata")]
params_info,
#[cfg(feature = "metadata")]
return_type: "Dynamic".into(),
return_type: "".into(),
#[cfg(feature = "metadata")]
comments: None,
},
@ -705,7 +750,7 @@ impl Module {
let return_type = param_names.pop().unwrap();
(param_names, return_type)
} else {
(param_names, Default::default())
(param_names, crate::SmartString::new_const())
};
f.metadata.params_info = param_names;
f.metadata.return_type = return_type_name;
@ -837,7 +882,7 @@ impl Module {
let return_type = if names.len() > arg_types.as_ref().len() {
names.pop().unwrap()
} else {
Default::default()
crate::SmartString::new_const()
};
names.shrink_to_fit();
(names, return_type)
@ -1402,14 +1447,14 @@ impl Module {
/// Merge another [`Module`] into this [`Module`].
#[inline(always)]
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.
pub(crate) fn merge_filtered(
&mut self,
other: &Self,
_filter: &impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool,
_filter: impl Fn(FnNamespace, FnAccess, bool, &str, usize) -> bool + Copy,
) -> &mut Self {
#[cfg(not(feature = "no_function"))]
other.modules.iter().for_each(|(k, v)| {
@ -1620,7 +1665,7 @@ impl Module {
) -> RhaiResultOf<Self> {
let mut scope = scope;
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
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) {
self.variables.truncate(len)
}
/// Add a new constant to the list.
/// Add a new variable to the list.
#[inline(always)]
pub fn push_var(
&mut self,
@ -423,7 +423,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b
match x.2 {
Expr::FnCall(ref mut x2, _) => {
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]);

View File

@ -31,6 +31,70 @@ pub mod array_functions {
pub fn len(array: &mut Array) -> 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.
///
/// If `item` is `Array`, then `append` is more specific and will be called instead.
@ -1929,7 +1993,7 @@ pub mod array_functions {
) -> RhaiResultOf<Array> {
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
///
@ -1954,7 +2018,7 @@ pub mod array_functions {
let end = INT::max(range.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
///
@ -2125,7 +2189,7 @@ pub mod array_functions {
) -> RhaiResultOf<Array> {
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
///
@ -2150,7 +2214,7 @@ pub mod array_functions {
let end = INT::max(range.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
///

View File

@ -100,6 +100,75 @@ pub mod blob_functions {
pub fn len(blob: &mut Blob) -> 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.
///
/// 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]"
/// ```
pub fn push(blob: &mut Blob, value: INT) {
let value = (value & 0x000000ff) as u8;
blob.push(value);
blob.push((value & 0x000000ff) as u8);
}
/// Add another BLOB to the end of the BLOB.
///
@ -396,7 +464,7 @@ pub mod blob_functions {
blob.reverse();
}
}
/// Replace an exclusive range of the BLOB with another BLOB.
/// Replace an exclusive `range` of the BLOB with another BLOB.
///
/// # Example
///
@ -414,7 +482,7 @@ pub mod blob_functions {
let end = INT::max(range.end, start);
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
///
@ -468,7 +536,7 @@ pub mod blob_functions {
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
///
@ -487,7 +555,7 @@ pub mod blob_functions {
let end = INT::max(range.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
///
@ -608,7 +676,7 @@ pub mod blob_functions {
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
///
@ -635,7 +703,7 @@ pub mod blob_functions {
let end = INT::max(range.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
///
@ -702,7 +770,7 @@ pub mod blob_functions {
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
///
@ -729,7 +797,7 @@ pub mod blob_functions {
let end = INT::max(range.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
///
@ -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")]
pub fn parse_le_int_range(blob: &mut Blob, range: ExclusiveRange) -> INT {
let start = INT::max(range.start, 0);
let end = INT::max(range.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")]
pub fn parse_le_int_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> INT {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
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 {
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")]
pub fn parse_be_int_range(blob: &mut Blob, range: ExclusiveRange) -> INT {
let start = INT::max(range.start, 0);
let end = INT::max(range.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")]
pub fn parse_be_int_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> INT {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
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 {
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")]
pub fn parse_le_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT {
let start = INT::max(range.start, 0);
let end = INT::max(range.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")]
pub fn parse_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
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 {
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")]
pub fn parse_be_float_range(blob: &mut Blob, range: ExclusiveRange) -> FLOAT {
let start = INT::max(range.start, 0);
let end = INT::max(range.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")]
pub fn parse_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange) -> FLOAT {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
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 {
parse_float(blob, start, len, false)
}
@ -943,34 +1155,124 @@ mod write_int_functions {
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")]
pub fn write_le_int_range(blob: &mut Blob, range: ExclusiveRange, value: INT) {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
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")]
pub fn write_le_int_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: INT) {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
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")]
pub fn write_le_int(blob: &mut Blob, start: INT, len: INT, value: INT) {
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")]
pub fn write_be_int_range(blob: &mut Blob, range: ExclusiveRange, value: INT) {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
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")]
pub fn write_be_int_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: INT) {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
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")]
pub fn write_be_int(blob: &mut Blob, start: INT, len: INT, value: INT) {
write_int(blob, start, len, value, false)
@ -1001,34 +1303,76 @@ mod write_float_functions {
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")]
pub fn write_le_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
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")]
pub fn write_le_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
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")]
pub fn write_le_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) {
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")]
pub fn write_be_float_range(blob: &mut Blob, range: ExclusiveRange, value: FLOAT) {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
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")]
pub fn write_be_float_range_inclusive(blob: &mut Blob, range: InclusiveRange, value: FLOAT) {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
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")]
pub fn write_be_float(blob: &mut Blob, start: INT, len: INT, value: FLOAT) {
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]);
}
}
#[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 a string to the bytes within an exclusive `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 "[00e69c9de3000000]"
/// ```
#[rhai_fn(name = "write_utf8")]
pub fn write_utf8_string_range(blob: &mut Blob, range: ExclusiveRange, string: &str) {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
write_string(blob, start, end - start, string, false)
}
/// 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")]
pub fn write_utf8_string_range_inclusive(blob: &mut Blob, range: InclusiveRange, string: &str) {
let start = INT::max(*range.start(), 0);
let end = INT::max(*range.end(), start);
write_string(blob, start, end - start + 1, string, false)
}
#[rhai_fn(name = "write_ascii")]
pub fn write_ascii_string(blob: &mut Blob, start: INT, len: INT, string: &str) {
write_string(blob, start, len, string, true)
/// Write a string to the bytes within an inclusive `range` in the BLOB in UTF-8 encoding.
///
/// * 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")]
pub fn write_ascii_string_range(blob: &mut Blob, range: ExclusiveRange, string: &str) {
let start = INT::max(range.start, 0);
let end = INT::max(range.end, start);
write_string(blob, start, end - start, string, true)
}
/// 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")]
pub fn write_ascii_string_range_inclusive(
blob: &mut Blob,
@ -1099,4 +1511,26 @@ mod write_string_functions {
let end = INT::max(*range.end(), start);
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
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 from = INT::max(range.start, 0);
let to = INT::max(range.end, from);
@ -489,7 +495,7 @@ def_package! {
#[cfg(feature = "metadata")]
lib.update_fn_metadata_with_comments(
_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.",
"///",
@ -511,7 +517,7 @@ def_package! {
#[cfg(feature = "metadata")]
lib.update_fn_metadata_with_comments(
_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.",
"///",
@ -621,7 +627,7 @@ def_package! {
#[cfg(feature = "metadata")]
lib.update_fn_metadata_with_comments(
_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.",
"///",
@ -645,7 +651,7 @@ def_package! {
#[cfg(feature = "metadata")]
lib.update_fn_metadata_with_comments(
_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.",
"///",
@ -733,7 +739,7 @@ def_package! {
#[cfg(feature = "metadata")]
lib.update_fn_metadata_with_comments(
_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.",
"///",

View File

@ -10,16 +10,17 @@ def_package! {
crate::LanguageCorePackage => |lib| {
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]
mod core_functions {
#[rhai_fn(name = "!")]
pub fn not(x: bool) -> bool {
!x
}
#[rhai_fn(name = "tag", get = "tag", pure)]
pub fn get_tag(value: &mut Dynamic) -> INT {
value.tag() as INT
@ -53,19 +54,38 @@ mod core_functions {
Ok(())
}
}
}
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_function"))]
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
#[export_module]
mod reflection_functions {
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_index"))]
#[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 std::collections::BTreeSet;
@ -125,13 +145,26 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
.map(|&s| s.into())
.collect();
let mut _list = ctx.iter_namespaces().flat_map(Module::iter_script_fn).fold(
Array::new(),
|mut list, (_, _, _, _, f)| {
list.push(make_metadata(&dict, None, f).into());
list
},
);
let mut list = Array::new();
ctx.iter_namespaces()
.flat_map(Module::iter_script_fn)
.filter(|(s, a, n, p, f)| filter(*s, *a, n, *p, f))
.for_each(|(_, _, _, _, f)| list.push(make_metadata(&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"))]
{
@ -141,10 +174,21 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
dict: &BTreeSet<Identifier>,
namespace: Identifier,
module: &Module,
filter: impl Fn(
FnNamespace,
FnAccess,
&str,
usize,
&crate::Shared<crate::ast::ScriptFnDef>,
) -> bool
+ Copy,
) {
module.iter_script_fn().for_each(|(_, _, _, _, f)| {
list.push(make_metadata(dict, Some(namespace.clone()), f).into())
});
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())
});
module.iter_sub_modules().for_each(|(ns, m)| {
let ns = format!(
"{}{}{}",
@ -152,13 +196,13 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array {
crate::tokenizer::Token::DoubleColon.literal_syntax(),
ns
);
scan_module(list, dict, ns.into(), m.as_ref())
scan_module(list, dict, ns.into(), m.as_ref(), filter)
});
}
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);
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")]
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"))]
#[export_module]
mod f32_functions {

View File

@ -25,6 +25,51 @@ mod map_functions {
pub fn len(map: &mut Map) -> 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.
pub fn clear(map: &mut Map) {
if !map.is_empty() {
@ -46,9 +91,9 @@ mod map_functions {
///
/// 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() {
map.remove(name.as_str()).unwrap_or_else(|| Dynamic::UNIT)
map.remove(property).unwrap_or_else(|| Dynamic::UNIT)
} else {
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.
///
/// # 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
///
@ -702,7 +781,7 @@ mod string_functions {
let end = INT::max(range.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
///

View File

@ -7,7 +7,7 @@ use crate::{calc_fn_hash, Engine, AST};
use serde::{Deserialize, Serialize};
#[cfg(feature = "no_std")]
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)]
#[serde(rename_all = "camelCase")]
@ -54,7 +54,7 @@ struct FnParam<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<&'a str>,
#[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)]
@ -125,12 +125,12 @@ impl<'a> From<&'a FuncInfo> for FnMetadata<'a> {
"_" => None,
s => Some(s),
};
let typ = seg.next().map(&str::trim);
let typ = seg.next().map(|s| FuncInfo::format_type(s, false));
FnParam { name, typ }
})
.collect(),
_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(),
doc_comments: if info.func.is_script() {
#[cfg(feature = "no_function")]
@ -196,11 +196,12 @@ impl Engine {
/// 1) Functions defined in an [`AST`][crate::AST]
/// 2) Functions registered into the global namespace
/// 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(
&self,
ast: &AST,
include_global: bool,
include_packages: bool,
) -> serde_json::Result<String> {
let _ast = ast;
let mut global = ModuleMetadata::new();
@ -211,14 +212,20 @@ impl Engine {
self.global_modules
.iter()
.take(if include_global { usize::MAX } else { 1 })
.filter(|m| include_packages || !m.standard)
.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"))]
_ast.shared_lib()
.iter_fn()
.for_each(|f| global.functions.push(f.into()));
_ast.shared_lib().iter_fn().for_each(|f| {
let mut meta: FnMetadata = f.into();
meta.namespace = FnNamespace::Global;
global.functions.push(meta);
});
global.functions.sort();
@ -233,7 +240,7 @@ impl Engine {
/// 2) Functions in static modules
/// 3) Functions in global modules (optional)
#[inline(always)]
pub fn gen_fn_metadata_to_json(&self, include_global: bool) -> serde_json::Result<String> {
self.gen_fn_metadata_with_ast_to_json(&AST::empty(), include_global)
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_packages)
}
}

View File

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

View File

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

View File

@ -254,3 +254,51 @@ fn test_get_set_chain_without_write_back() -> Result<(), Box<EvalAltResult>> {
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>("(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(())
}