commit
817d9aef34
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@ -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
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
|
||||
|
@ -142,15 +142,14 @@ 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()? {
|
||||
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),
|
||||
..
|
||||
@ -168,7 +167,9 @@ pub fn doc_attributes(attrs: &mut Vec<syn::Attribute>) -> syn::Result<Vec<String
|
||||
|
||||
comments.push(line);
|
||||
}
|
||||
_ => continue,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -407,7 +407,7 @@ impl Parse for ExportedFn {
|
||||
params: Default::default(),
|
||||
cfg_attrs,
|
||||
#[cfg(feature = "metadata")]
|
||||
comments: Default::default(),
|
||||
comments: Vec::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
}),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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)]
|
||||
|
@ -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}`);
|
||||
|
@ -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 } }`);
|
||||
}
|
||||
|
@ -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; }
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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}`);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
// This script simulates multi-dimensional matrix calculations.
|
||||
|
||||
const SIZE = 50;
|
||||
|
||||
fn new_mat(x, y) {
|
||||
|
@ -1,3 +1,5 @@
|
||||
// This script imports an external script as a module.
|
||||
|
||||
import "loop" as x;
|
||||
|
||||
print(`Module test! foo = ${x::foo}`);
|
||||
|
@ -1,3 +1,5 @@
|
||||
// This script runs a single expression.
|
||||
|
||||
print("The result should be 46:");
|
||||
|
||||
print(34 + 12);
|
||||
|
@ -1,3 +1,5 @@
|
||||
// This script runs a complex expression.
|
||||
|
||||
print("The result should be 182:");
|
||||
|
||||
let x = 12 + 34 * 5;
|
||||
|
@ -1,3 +1,5 @@
|
||||
// This script runs a complex expression.
|
||||
|
||||
print("The result should be 230:");
|
||||
|
||||
let x = (12 + 34) * 5;
|
||||
|
@ -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;
|
||||
|
@ -1,4 +1,4 @@
|
||||
// This script tests string operations
|
||||
// This script tests string operations.
|
||||
|
||||
print("hello");
|
||||
print("this\nis \\ nice"); // escape sequences
|
||||
|
@ -1,3 +1,5 @@
|
||||
// This script tests object maps and strings.
|
||||
|
||||
print("Ready... Go!");
|
||||
|
||||
let now = timestamp();
|
||||
|
@ -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)}`)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// This script runs a while loop
|
||||
// This script runs a while loop.
|
||||
|
||||
let x = 10;
|
||||
|
||||
|
@ -151,7 +151,7 @@ impl Deref for Expression<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_> {
|
||||
impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_, '_, '_> {
|
||||
/// Evaluate an [expression tree][Expression].
|
||||
///
|
||||
/// # WARNING - Low Level API
|
||||
|
@ -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)
|
||||
.filter(|m| !m.internal && (include_packages || !m.standard))
|
||||
.flat_map(|m| m.gen_fn_signatures()),
|
||||
);
|
||||
}
|
||||
|
||||
signatures
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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" => {
|
||||
|
@ -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]`>`.
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,12 +235,12 @@ 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(|| {
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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(¶ms.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,18 +189,13 @@ impl FuncInfo {
|
||||
sig.push_str(", ");
|
||||
}
|
||||
}
|
||||
|
||||
sig.push(')');
|
||||
}
|
||||
|
||||
if !self.func.is_script() {
|
||||
sig.push(')');
|
||||
|
||||
if !return_type.is_empty() {
|
||||
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)?;
|
||||
|
@ -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]);
|
||||
|
||||
|
@ -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
|
||||
///
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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.",
|
||||
"///",
|
||||
|
@ -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"))]
|
||||
#[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,8 +174,19 @@ 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)| {
|
||||
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)| {
|
||||
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
///
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user