Merge pull request #240 from schungx/master

Cleanup.
This commit is contained in:
Stephen Chung 2020-09-23 12:24:02 +08:00 committed by GitHub
commit 4d0b0ab4c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 545 additions and 253 deletions

29
.github/workflows/benchmark.yml vendored Normal file
View File

@ -0,0 +1,29 @@
name: Benchmark
on:
push:
branches:
- master
jobs:
benchmark:
name: Run Rust benchmark
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: rustup toolchain update nightly && rustup default nightly
- name: Run benchmark
run: cargo +nightly bench | tee output.txt
- name: Store benchmark result
uses: rhysd/github-action-benchmark@v1
with:
name: Rust Benchmark
tool: 'cargo'
output-file-path: output.txt
# Use personal access token instead of GITHUB_TOKEN due to https://github.community/t5/GitHub-Actions/Github-action-not-triggering-gh-pages-upon-push/td-p/26869/highlight/false
github-token: ${{ secrets.RHAI }}
auto-push: true
# Show alert with commit comment on detecting possible performance regression
alert-threshold: '200%'
comment-on-alert: true
fail-on-alert: true
alert-comment-cc-users: '@schungx'

View File

@ -77,6 +77,29 @@ jobs:
with:
command: build
args: --manifest-path=no_std/no_std_test/Cargo.toml ${{matrix.flags}}
rustfmt:
name: Check Formatting
runs-on: windows-latest
continue-on-error: true
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
override: true
components: rustfmt, clippy
- name: Run Rustfmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- name: Run Clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all -- -Aclippy::all -Dclippy::perf
codegen_build:
name: Codegen Build
runs-on: ${{matrix.os}}

View File

@ -23,7 +23,7 @@ keywords = [ "scripting" ]
categories = [ "no-std", "embedded", "wasm", "parser-implementations" ]
[dependencies]
smallvec = { version = "1.4.1", default-features = false }
smallvec = { version = "1.4.2", default-features = false }
rhai_codegen = { version = "0.1", path = "codegen" }
[features]
@ -81,7 +81,7 @@ features = ["compile-time-rng"]
optional = true
[dependencies.serde]
version = "1.0.111"
version = "1.0.116"
default_features = false
features = ["derive", "alloc"]
optional = true
@ -92,7 +92,7 @@ default_features = false
optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies]
instant= { version = "0.1.4", features = ["wasm-bindgen"] } # WASM implementation of std::time::Instant
instant= { version = "0.1.7", features = ["wasm-bindgen"] } # WASM implementation of std::time::Instant
[package.metadata.docs.rs]
features = [ "serde", "internals" ]

View File

@ -11,12 +11,20 @@ Bug fixes
* Fixes a bug in `Module::set_fn_4_mut`.
* Module API's now properly handle `&str` and `String` parameters.
* Indexers are available under `no_object`.
* Registered operator-assignment functions (e.g. `+=`) now work correctly.
Breaking changes
----------------
* `Engine::register_set_result` and `Engine::register_indexer_set_result` now take a function that returns `Result<(), Box<EvalAltResult>>`.
* `Engine::register_indexer_XXX` and `Module::set_indexer_XXX` panic when the type is `Arrary`, `Map` or `String`.
New features
------------
* Plugins support via procedural macros.
* Scripted functions are allowed in packages.
* `parse_int` and `parse_float` functions.
Version 0.18.3

View File

@ -32,7 +32,10 @@ pub struct ExportInfo {
pub fn parse_attr_items(args: ParseStream) -> syn::Result<ExportInfo> {
if args.is_empty() {
return Ok(ExportInfo { item_span: args.span(), items: Vec::new()});
return Ok(ExportInfo {
item_span: args.span(),
items: Vec::new(),
});
}
let arg_list = args
.call(syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_separated_nonempty)?;
@ -80,10 +83,17 @@ pub fn parse_punctuated_items(
.ok_or_else(|| syn::Error::new(attr_path.span(), "expecting attribute name"))?,
x => return Err(syn::Error::new(x.span(), "expecting identifier")),
};
attrs.push(AttrItem { key, value, span: arg_span });
attrs.push(AttrItem {
key,
value,
span: arg_span,
});
}
Ok(ExportInfo { item_span: list_span, items: attrs })
Ok(ExportInfo {
item_span: list_span,
items: attrs,
})
}
pub(crate) fn outer_item_attributes<T: ExportedParams>(

View File

@ -16,6 +16,7 @@ use quote::{quote, quote_spanned};
use syn::{parse::Parse, parse::ParseStream, parse::Parser, spanned::Spanned};
use crate::attrs::{ExportInfo, ExportScope, ExportedParams};
use crate::rhai_module::flatten_type_groups;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Index {
@ -223,7 +224,8 @@ impl Parse for ExportedFn {
syn::FnArg::Receiver(syn::Receiver {
reference: Some(_), ..
}) => true,
syn::FnArg::Typed(syn::PatType { ref ty, .. }) => match ty.as_ref() {
syn::FnArg::Typed(syn::PatType { ref ty, .. }) => {
match flatten_type_groups(ty.as_ref()) {
&syn::Type::Reference(syn::TypeReference {
mutability: Some(_),
..
@ -232,7 +234,7 @@ impl Parse for ExportedFn {
mutability: None,
ref elem,
..
}) => match elem.as_ref() {
}) => match flatten_type_groups(elem.as_ref()) {
&syn::Type::Path(ref p) if p.path == str_type_path => false,
_ => {
return Err(syn::Error::new(
@ -243,7 +245,8 @@ impl Parse for ExportedFn {
}
},
_ => false,
},
}
}
_ => false,
}
} else {
@ -257,7 +260,7 @@ impl Parse for ExportedFn {
syn::FnArg::Typed(syn::PatType { ref ty, .. }) => ty,
_ => panic!("internal error: receiver argument outside of first position!?"),
};
let is_ok = match ty.as_ref() {
let is_ok = match flatten_type_groups(ty.as_ref()) {
&syn::Type::Reference(syn::TypeReference {
mutability: Some(_),
..
@ -266,7 +269,9 @@ impl Parse for ExportedFn {
mutability: None,
ref elem,
..
}) => matches!(elem.as_ref(), &syn::Type::Path(ref p) if p.path == str_type_path),
}) => {
matches!(flatten_type_groups(elem.as_ref()), &syn::Type::Path(ref p) if p.path == str_type_path)
}
&syn::Type::Verbatim(_) => false,
_ => true,
};
@ -605,13 +610,9 @@ impl ExportedFn {
let var = syn::Ident::new("arg0", proc_macro2::Span::call_site());
match first_arg {
syn::FnArg::Typed(pattern) => {
let arg_type: &syn::Type = {
match pattern.ty.as_ref() {
&syn::Type::Reference(syn::TypeReference { ref elem, .. }) => {
elem.as_ref()
}
ref p => p,
}
let arg_type: &syn::Type = match flatten_type_groups(pattern.ty.as_ref()) {
&syn::Type::Reference(syn::TypeReference { ref elem, .. }) => elem.as_ref(),
p => p,
};
let downcast_span = quote_spanned!(
arg_type.span()=> &mut args[0usize].write_lock::<#arg_type>().unwrap());
@ -648,12 +649,12 @@ impl ExportedFn {
match arg {
syn::FnArg::Typed(pattern) => {
let arg_type: &syn::Type = pattern.ty.as_ref();
let downcast_span = match pattern.ty.as_ref() {
let downcast_span = match flatten_type_groups(pattern.ty.as_ref()) {
&syn::Type::Reference(syn::TypeReference {
mutability: None,
ref elem,
..
}) => match elem.as_ref() {
}) => match flatten_type_groups(elem.as_ref()) {
&syn::Type::Path(ref p) if p.path == str_type_path => {
is_string = true;
is_ref = true;
@ -672,7 +673,7 @@ impl ExportedFn {
is_string = false;
is_ref = false;
quote_spanned!(arg_type.span()=>
mem::take(args[#i]).clone().cast::<#arg_type>())
mem::take(args[#i]).cast::<#arg_type>())
}
};

View File

@ -85,12 +85,12 @@ pub(crate) fn generate_body(
.map(|fnarg| match fnarg {
syn::FnArg::Receiver(_) => panic!("internal error: receiver fn outside impl!?"),
syn::FnArg::Typed(syn::PatType { ref ty, .. }) => {
let arg_type = match ty.as_ref() {
let arg_type = match flatten_type_groups(ty.as_ref()) {
syn::Type::Reference(syn::TypeReference {
mutability: None,
ref elem,
..
}) => match elem.as_ref() {
}) => match flatten_type_groups(elem.as_ref()) {
syn::Type::Path(ref p) if p.path == str_type_path => {
syn::parse2::<syn::Type>(quote! {
ImmutableString })
@ -107,11 +107,11 @@ pub(crate) fn generate_body(
mutability: Some(_),
ref elem,
..
}) => match elem.as_ref() {
}) => match flatten_type_groups(elem.as_ref()) {
syn::Type::Path(ref p) => syn::parse2::<syn::Type>(quote! {
#p })
.unwrap(),
_ => panic!("internal error: non-string shared reference!?"),
_ => panic!("internal error: invalid mutable reference!?"),
},
t => t.clone(),
};
@ -174,6 +174,14 @@ pub(crate) fn generate_body(
}
}
pub(crate) fn flatten_type_groups(ty: &syn::Type) -> &syn::Type {
match ty {
syn::Type::Group(syn::TypeGroup { ref elem, .. })
| syn::Type::Paren(syn::TypeParen { ref elem, .. }) => flatten_type_groups(elem.as_ref()),
_ => ty,
}
}
pub(crate) fn check_rename_collisions(fns: &Vec<ExportedFn>) -> Result<(), syn::Error> {
let mut renames = HashMap::<String, proc_macro2::Span>::new();
let mut names = HashMap::<String, proc_macro2::Span>::new();

View File

@ -323,7 +323,7 @@ mod generate_tests {
) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 1usize,
"wrong arg count: {} != {}", args.len(), 1usize);
let arg0 = mem::take(args[0usize]).clone().cast::<usize>();
let arg0 = mem::take(args[0usize]).cast::<usize>();
Ok(Dynamic::from(do_something(arg0)))
}
@ -364,7 +364,7 @@ mod generate_tests {
) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 1usize,
"wrong arg count: {} != {}", args.len(), 1usize);
let arg0 = mem::take(args[0usize]).clone().cast::<usize>();
let arg0 = mem::take(args[0usize]).cast::<usize>();
Ok(Dynamic::from(do_something(arg0)))
}
@ -398,8 +398,8 @@ mod generate_tests {
) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 2usize,
"wrong arg count: {} != {}", args.len(), 2usize);
let arg0 = mem::take(args[0usize]).clone().cast::<usize>();
let arg1 = mem::take(args[1usize]).clone().cast::<usize>();
let arg0 = mem::take(args[0usize]).cast::<usize>();
let arg1 = mem::take(args[1usize]).cast::<usize>();
Ok(Dynamic::from(add_together(arg0, arg1)))
}
@ -445,7 +445,7 @@ mod generate_tests {
) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 2usize,
"wrong arg count: {} != {}", args.len(), 2usize);
let arg1 = mem::take(args[1usize]).clone().cast::<usize>();
let arg1 = mem::take(args[1usize]).cast::<usize>();
let arg0: &mut _ = &mut args[0usize].write_lock::<usize>().unwrap();
Ok(Dynamic::from(increment(arg0, arg1)))
}

View File

@ -369,7 +369,7 @@ mod generate_tests {
) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 1usize,
"wrong arg count: {} != {}", args.len(), 1usize);
let arg0 = mem::take(args[0usize]).clone().cast::<INT>();
let arg0 = mem::take(args[0usize]).cast::<INT>();
Ok(Dynamic::from(add_one_to(arg0)))
}
@ -446,7 +446,7 @@ mod generate_tests {
) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 1usize,
"wrong arg count: {} != {}", args.len(), 1usize);
let arg0 = mem::take(args[0usize]).clone().cast::<INT>();
let arg0 = mem::take(args[0usize]).cast::<INT>();
Ok(Dynamic::from(add_one_to(arg0)))
}
@ -474,8 +474,8 @@ mod generate_tests {
) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 2usize,
"wrong arg count: {} != {}", args.len(), 2usize);
let arg0 = mem::take(args[0usize]).clone().cast::<INT>();
let arg1 = mem::take(args[1usize]).clone().cast::<INT>();
let arg0 = mem::take(args[0usize]).cast::<INT>();
let arg1 = mem::take(args[1usize]).cast::<INT>();
Ok(Dynamic::from(add_n_to(arg0, arg1)))
}
@ -540,8 +540,8 @@ mod generate_tests {
) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 2usize,
"wrong arg count: {} != {}", args.len(), 2usize);
let arg0 = mem::take(args[0usize]).clone().cast::<INT>();
let arg1 = mem::take(args[1usize]).clone().cast::<INT>();
let arg0 = mem::take(args[0usize]).cast::<INT>();
let arg1 = mem::take(args[1usize]).cast::<INT>();
Ok(Dynamic::from(add_together(arg0, arg1)))
}
@ -613,8 +613,8 @@ mod generate_tests {
) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 2usize,
"wrong arg count: {} != {}", args.len(), 2usize);
let arg0 = mem::take(args[0usize]).clone().cast::<INT>();
let arg1 = mem::take(args[1usize]).clone().cast::<INT>();
let arg0 = mem::take(args[0usize]).cast::<INT>();
let arg1 = mem::take(args[1usize]).cast::<INT>();
Ok(Dynamic::from(add_together(arg0, arg1)))
}
@ -1447,7 +1447,7 @@ mod generate_tests {
) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 2usize,
"wrong arg count: {} != {}", args.len(), 2usize);
let arg1 = mem::take(args[1usize]).clone().cast::<u64>();
let arg1 = mem::take(args[1usize]).cast::<u64>();
let arg0: &mut _ = &mut args[0usize].write_lock::<u64>().unwrap();
Ok(Dynamic::from(int_foo(arg0, arg1)))
}
@ -1518,7 +1518,7 @@ mod generate_tests {
) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 2usize,
"wrong arg count: {} != {}", args.len(), 2usize);
let arg1 = mem::take(args[1usize]).clone().cast::<u64>();
let arg1 = mem::take(args[1usize]).cast::<u64>();
let arg0: &mut _ = &mut args[0usize].write_lock::<u64>().unwrap();
Ok(Dynamic::from(int_foo(arg0, arg1)))
}
@ -1585,7 +1585,7 @@ mod generate_tests {
) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 2usize,
"wrong arg count: {} != {}", args.len(), 2usize);
let arg1 = mem::take(args[1usize]).clone().cast::<u64>();
let arg1 = mem::take(args[1usize]).cast::<u64>();
let arg0: &mut _ = &mut args[0usize].write_lock::<MyCollection>().unwrap();
Ok(Dynamic::from(get_by_index(arg0, arg1)))
}
@ -1657,7 +1657,7 @@ mod generate_tests {
) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 2usize,
"wrong arg count: {} != {}", args.len(), 2usize);
let arg1 = mem::take(args[1usize]).clone().cast::<u64>();
let arg1 = mem::take(args[1usize]).cast::<u64>();
let arg0: &mut _ = &mut args[0usize].write_lock::<MyCollection>().unwrap();
Ok(Dynamic::from(get_by_index(arg0, arg1)))
}
@ -1726,8 +1726,8 @@ mod generate_tests {
) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 3usize,
"wrong arg count: {} != {}", args.len(), 3usize);
let arg1 = mem::take(args[1usize]).clone().cast::<u64>();
let arg2 = mem::take(args[2usize]).clone().cast::<FLOAT>();
let arg1 = mem::take(args[1usize]).cast::<u64>();
let arg2 = mem::take(args[2usize]).cast::<FLOAT>();
let arg0: &mut _ = &mut args[0usize].write_lock::<MyCollection>().unwrap();
Ok(Dynamic::from(set_by_index(arg0, arg1, arg2)))
}
@ -1802,8 +1802,8 @@ mod generate_tests {
) -> Result<Dynamic, Box<EvalAltResult>> {
debug_assert_eq!(args.len(), 3usize,
"wrong arg count: {} != {}", args.len(), 3usize);
let arg1 = mem::take(args[1usize]).clone().cast::<u64>();
let arg2 = mem::take(args[2usize]).clone().cast::<FLOAT>();
let arg1 = mem::take(args[1usize]).cast::<u64>();
let arg2 = mem::take(args[2usize]).cast::<FLOAT>();
let arg0: &mut _ = &mut args[0usize].write_lock::<MyCollection>().unwrap();
Ok(Dynamic::from(set_by_index(arg0, arg1, arg2)))
}

View File

@ -187,7 +187,6 @@ fn export_nested_by_prefix_test() -> Result<(), Box<EvalAltResult>> {
if s == "math::bar_fourth_adders::add_int (i64, i64)"
&& p == rhai::Position::new(3, 42)));
assert!(matches!(*engine.eval::<FLOAT>(
r#"import "Math::Advanced" as math;
let ex = 41.0;

View File

@ -1,13 +0,0 @@
[package]
name = "diag_test"
version = "0.1.0"
authors = ["J Henry Waugh <jhwgh1968@protonmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]
name = "test_template"
path = "test_template.rs"
[dependencies]
rhai = { version = "*", path = ".." }

View File

@ -1,35 +0,0 @@
use rhai::plugin::*;
#[derive(Clone)]
pub struct Point {
x: f32,
y: f32,
}
#[export_module]
pub mod test_module {
#[rhai_mod(name = "bar")]
pub mod test_mod {
#[rhai_fn(name = "foo")]
pub fn test_fn(input: Point) -> bool {
input.x > input.y
}
#[rhai_fn(return_raw)]
pub fn test_fn_raw(input: Point) -> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
Ok(Dynamic::from(input.x > input.y))
}
}
}
fn main() {
let n = Point {
x: 0.0,
y: 10.0,
};
if test_module::test_mod::test_fn(n) {
println!("yes");
} else {
println!("no");
}
}

View File

@ -33,7 +33,9 @@ The following methods (mostly defined in the [`BasicArrayPackage`][packages] but
| Function | Parameter(s) | Description |
| ------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `push` | element to insert | inserts an element at the end |
| `+=` operator, `append` | array to append | concatenates the second array to the end of the first |
| `+=` operator | array, element to insert (not another array) | inserts an element at the end |
| `append` | array to append | concatenates the second array to the end of the first |
| `+=` operator | array, array to append | concatenates the second array to the end of the first |
| `+` operator | first array, second array | concatenates the first array with the second |
| `insert` | element to insert, position<br/>(beginning if <= 0, end if >= length) | insert an element at a certain index |
| `pop` | _none_ | removes the last element and returns it ([`()`] if empty) |
@ -49,7 +51,9 @@ Use Custom Types With Arrays
---------------------------
To use a [custom type] with arrays, a number of array functions need to be manually implemented,
in particular `push`, `pad` and the `==` operator (in order to support the `in` operator).
in particular `push`, `pad` and the `+=` operator. In addition, the `==` operator must be
implemented for the [custom type] in order to support the `in` operator which uses `==` to
compare elements.
See the section on [custom types] for more details.
@ -104,7 +108,7 @@ let foo = y[0];
foo == 1;
y.push(4); // 4 elements
y.push(5); // 5 elements
y += 5; // 5 elements
y.len == 5;

View File

@ -9,11 +9,11 @@ Integer Functions
The following standard functions (defined in the [`BasicMathPackage`][packages] but excluded if using a [raw `Engine`])
operate on `i8`, `i16`, `i32`, `i64`, `f32` and `f64` only:
| Function | Description |
| ------------ | ----------------------------------------------------------------------- |
| `abs` | absolute value |
| `sign` | returns -1 (`INT`) if the number is negative, +1 if positive, 0 if zero |
| [`to_float`] | converts an integer type to `FLOAT` |
| Function | No available under | Description |
| -------- | :----------------: | ---------------------------------------------------------------------- |
| `abs` | | absolute value |
| `sign` | | return -1 (`INT`) if the number is negative, +1 if positive, 0 if zero |
Floating-Point Functions
-----------------------
@ -31,3 +31,16 @@ operate on `f64` only:
| Rounding | `floor`, `ceiling`, `round`, `int`, `fraction` methods and properties |
| Conversion | [`to_int`] |
| Testing | `is_nan`, `is_finite`, `is_infinite` methods and properties |
Conversion Functions
-------------------
The following standard functions (defined in the [`BasicMathPackage`][packages] but excluded if using a [raw `Engine`])
parse numbers:
| Function | No available under | Description |
| --------------- | :----------------: | -------------------------------------------------- |
| [`to_float`] | [`no_float`] | convert an integer type to `FLOAT` |
| [`parse_int`] | | convert a [string] to `INT` with an optional radix |
| [`parse_float`] | [`no_float`] | convert a [string] to `FLOAT` |

View File

@ -161,13 +161,15 @@ x.type_of() == "Hello";
Use the Custom Type With Arrays
------------------------------
The `push` and `pad` functions for [arrays] are only defined for standard built-in types.
For custom types, type-specific versions must be registered:
The `push` and `pad` functions, as well as the `+=` operator, for [arrays] are only defined for
standard built-in types. For custom types, type-specific versions must be registered:
```rust
engine
.register_fn("push", |list: &mut Array, item: TestStruct| {
list.push(Dynamic::from(item));
}).register_fn("+=", |list: &mut Array, item: TestStruct| {
list.push(Dynamic::from(item));
}).register_fn("pad", |list: &mut Array, len: i64, item: TestStruct| {
if len as usize > list.len() {
list.resize(len as usize, item);
@ -176,7 +178,7 @@ engine
```
In particular, in order to use the `in` operator with a custom type for an [array],
the `==` operator must be registered for that custom type:
the `==` operator must be registered for the custom type:
```rust
// Assume 'TestStruct' implements `PartialEq`

View File

@ -12,10 +12,19 @@ Getters and setters are disabled when the [`no_object`] feature is used.
| `Engine` API | Description | Return Value of Function |
| --------------------- | ------------------------------------------------- | :-----------------------------------: |
| `register_get` | Register a getter | _Any_ |
| `register_set` | Register a setter | _Any_ |
| `register_set` | Register a setter | _None_ |
| `register_get_set` | Short-hand to register both a getter and a setter | _None_ |
| `register_get_result` | Register a getter | `Result<Dynamic, Box<EvalAltResult>>` |
| `register_set_result` | Register a setter | `Result<Dynamic, Box<EvalAltResult>>` |
| `register_set_result` | Register a setter | `Result<(), Box<EvalAltResult>>` |
Cannot Override Object Maps
--------------------------
Getters and setters are only intended for [custom types].
Any getter or setter function registered for [object maps] is simply ignored because
the get/set calls will be interpreted as properties on the [object maps].
Examples
@ -28,15 +37,13 @@ struct TestStruct {
}
impl TestStruct {
// Returning a 'String' is OK - Rhai converts it into 'ImmutableString'
// Remember &mut must be used even for getters
fn get_field(&mut self) -> String {
self.field.clone()
}
// Remember Rhai uses 'ImmutableString' or '&str' instead of 'String'
fn set_field(&mut self, new_val: ImmutableString) {
// Get a 'String' from an 'ImmutableString'
self.field = (*new_val).clone();
fn set_field(&mut self, new_val: &str) {
self.field = new_val.to_string();
}
fn new() -> Self {
@ -51,7 +58,6 @@ let mut engine = Engine::new();
.register_get_set("xyz", TestStruct::get_field, TestStruct::set_field)
.register_fn("new_ts", TestStruct::new);
// Return result can be 'String' - Rhai will automatically convert it from 'ImmutableString'
let result = engine.eval::<String>(r#"let a = new_ts(); a.xyz = "42"; a.xyz"#)?;
println!("Answer: {}", result); // prints 42

View File

@ -13,16 +13,22 @@ Like getters and setters, indexers take a `&mut` reference to the first paramete
Indexers are disabled when the [`no_index`] feature is used.
For efficiency reasons, indexers **cannot** be used to overload (i.e. override) built-in indexing operations for
[arrays] and [object maps].
| `Engine` API | Description | Return Value of Function |
| ----------------------------- | -------------------------------------------------------- | :-----------------------------------: |
| `register_indexer_get` | Register an index getter | _Any_ |
| `register_indexer_set` | Register an index setter | _Any_ |
| `register_indexer_set` | Register an index setter | _None_ |
| `register_indexer_get_set` | Short-hand to register both an index getter and a setter | _None_ |
| `register_indexer_get_result` | Register an index getter | `Result<Dynamic, Box<EvalAltResult>>` |
| `register_indexer_set_result` | Register an index setter | `Result<Dynamic, Box<EvalAltResult>>` |
| `register_indexer_set_result` | Register an index setter | `Result<(), Box<EvalAltResult>>` |
Cannot Override Arrays, Object Maps and Strings
----------------------------------------------
For efficiency reasons, indexers **cannot** be used to overload (i.e. override)
built-in indexing operations for [arrays], [object maps] and [strings].
Attempting to register indexers for an [array], [object map] or [string] panics.
Examples
@ -35,6 +41,7 @@ struct TestStruct {
}
impl TestStruct {
// Remember &mut must be used even for getters
fn get_field(&mut self, index: i64) -> i64 {
self.fields[index as usize]
}
@ -60,3 +67,5 @@ let result = engine.eval::<i64>("let a = new_ts(); a[2] = 42; a[2]")?;
println!("Answer: {}", result); // prints 42
```
**IMPORTANT: Rhai does NOT support normal references (i.e. `&T`) as parameters.**

View File

@ -12,7 +12,10 @@ use crate::scope::Scope;
use crate::token::{lex, Position};
#[cfg(not(feature = "no_index"))]
use crate::engine::{FN_IDX_GET, FN_IDX_SET};
use crate::{
engine::{Array, FN_IDX_GET, FN_IDX_SET},
utils::ImmutableString,
};
#[cfg(not(feature = "no_object"))]
use crate::{
@ -36,6 +39,7 @@ use crate::optimize::optimize_into_ast;
use crate::stdlib::{
any::{type_name, TypeId},
boxed::Box,
string::String,
};
#[cfg(not(feature = "no_optimize"))]
@ -191,7 +195,6 @@ impl Engine {
///
/// impl TestStruct {
/// fn new() -> Self { TestStruct { field: 1 } }
///
/// // Even a getter must start with `&mut self` and not `&self`.
/// fn get_field(&mut self) -> i64 { self.field }
/// }
@ -243,7 +246,6 @@ impl Engine {
///
/// impl TestStruct {
/// fn new() -> Self { TestStruct { field: 1 } }
///
/// // Even a getter must start with `&mut self` and not `&self`.
/// fn get_field(&mut self) -> Result<Dynamic, Box<EvalAltResult>> {
/// Ok(self.field.into())
@ -324,7 +326,7 @@ impl Engine {
}
/// Register a setter function for a member of a registered type with the `Engine`.
/// Returns `Result<Dynamic, Box<EvalAltResult>>`.
/// Returns `Result<(), Box<EvalAltResult>>`.
///
/// # Example
///
@ -338,9 +340,9 @@ impl Engine {
///
/// impl TestStruct {
/// fn new() -> Self { TestStruct { field: 1 } }
/// fn set_field(&mut self, new_val: i64) -> Result<Dynamic, Box<EvalAltResult>> {
/// fn set_field(&mut self, new_val: i64) -> Result<(), Box<EvalAltResult>> {
/// self.field = new_val;
/// Ok(().into())
/// Ok(())
/// }
/// }
///
@ -367,13 +369,16 @@ impl Engine {
pub fn register_set_result<T, U>(
&mut self,
name: &str,
callback: impl Fn(&mut T, U) -> Result<Dynamic, Box<EvalAltResult>> + SendSync + 'static,
callback: impl Fn(&mut T, U) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
) -> &mut Self
where
T: Variant + Clone,
U: Variant + Clone,
{
self.register_result_fn(&make_setter(name), callback)
self.register_result_fn(&make_setter(name), move |obj: &mut T, value: U| {
callback(obj, value)?;
Ok(().into())
})
}
/// Short-hand for registering both getter and setter functions
@ -391,8 +396,8 @@ impl Engine {
///
/// impl TestStruct {
/// fn new() -> Self { TestStruct { field: 1 } }
/// fn get_field(&mut self) -> i64 { self.field }
/// // Even a getter must start with `&mut self` and not `&self`.
/// fn get_field(&mut self) -> i64 { self.field }
/// fn set_field(&mut self, new_val: i64) { self.field = new_val; }
/// }
///
@ -432,6 +437,11 @@ impl Engine {
///
/// The function signature must start with `&mut self` and not `&self`.
///
/// # Panics
///
/// Panics if the type is `Array` or `Map`.
/// Indexers for arrays, object maps and strings cannot be registered.
///
/// # Example
///
/// ```
@ -442,7 +452,6 @@ impl Engine {
///
/// impl TestStruct {
/// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } }
///
/// // Even a getter must start with `&mut self` and not `&self`.
/// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] }
/// }
@ -475,6 +484,20 @@ impl Engine {
U: Variant + Clone,
X: Variant + Clone,
{
if TypeId::of::<T>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays.");
}
#[cfg(not(feature = "no_object"))]
if TypeId::of::<T>() == TypeId::of::<Map>() {
panic!("Cannot register indexer for object maps.");
}
if TypeId::of::<T>() == TypeId::of::<String>()
|| TypeId::of::<T>() == TypeId::of::<&str>()
|| TypeId::of::<T>() == TypeId::of::<ImmutableString>()
{
panic!("Cannot register indexer for strings.");
}
self.register_fn(FN_IDX_GET, callback)
}
@ -483,6 +506,11 @@ impl Engine {
///
/// The function signature must start with `&mut self` and not `&self`.
///
/// # Panics
///
/// Panics if the type is `Array` or `Map`.
/// Indexers for arrays, object maps and strings cannot be registered.
///
/// # Example
///
/// ```
@ -495,7 +523,6 @@ impl Engine {
///
/// impl TestStruct {
/// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } }
///
/// // Even a getter must start with `&mut self` and not `&self`.
/// fn get_field(&mut self, index: i64) -> Result<Dynamic, Box<EvalAltResult>> {
/// Ok(self.fields[index as usize].into())
@ -527,11 +554,30 @@ impl Engine {
T: Variant + Clone,
X: Variant + Clone,
{
if TypeId::of::<T>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays.");
}
#[cfg(not(feature = "no_object"))]
if TypeId::of::<T>() == TypeId::of::<Map>() {
panic!("Cannot register indexer for object maps.");
}
if TypeId::of::<T>() == TypeId::of::<String>()
|| TypeId::of::<T>() == TypeId::of::<&str>()
|| TypeId::of::<T>() == TypeId::of::<ImmutableString>()
{
panic!("Cannot register indexer for strings.");
}
self.register_result_fn(FN_IDX_GET, callback)
}
/// Register an index setter for a custom type with the `Engine`.
///
/// # Panics
///
/// Panics if the type is `Array` or `Map`.
/// Indexers for arrays, object maps and strings cannot be registered.
///
/// # Example
///
/// ```
@ -569,18 +615,37 @@ impl Engine {
#[cfg(not(feature = "no_index"))]
pub fn register_indexer_set<T, X, U>(
&mut self,
callback: impl Fn(&mut T, X, U) -> () + SendSync + 'static,
callback: impl Fn(&mut T, X, U) + SendSync + 'static,
) -> &mut Self
where
T: Variant + Clone,
U: Variant + Clone,
X: Variant + Clone,
{
if TypeId::of::<T>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays.");
}
#[cfg(not(feature = "no_object"))]
if TypeId::of::<T>() == TypeId::of::<Map>() {
panic!("Cannot register indexer for object maps.");
}
if TypeId::of::<T>() == TypeId::of::<String>()
|| TypeId::of::<T>() == TypeId::of::<&str>()
|| TypeId::of::<T>() == TypeId::of::<ImmutableString>()
{
panic!("Cannot register indexer for strings.");
}
self.register_fn(FN_IDX_SET, callback)
}
/// Register an index setter for a custom type with the `Engine`.
/// Returns `Result<Dynamic, Box<EvalAltResult>>`.
/// Returns `Result<(), Box<EvalAltResult>>`.
///
/// # Panics
///
/// Panics if the type is `Array` or `Map`.
/// Indexers for arrays, object maps and strings cannot be registered.
///
/// # Example
///
@ -594,9 +659,9 @@ impl Engine {
///
/// impl TestStruct {
/// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } }
/// fn set_field(&mut self, index: i64, value: i64) -> Result<Dynamic, Box<EvalAltResult>> {
/// fn set_field(&mut self, index: i64, value: i64) -> Result<(), Box<EvalAltResult>> {
/// self.fields[index as usize] = value;
/// Ok(().into())
/// Ok(())
/// }
/// }
///
@ -622,18 +687,40 @@ impl Engine {
#[cfg(not(feature = "no_index"))]
pub fn register_indexer_set_result<T, X, U>(
&mut self,
callback: impl Fn(&mut T, X, U) -> Result<Dynamic, Box<EvalAltResult>> + SendSync + 'static,
callback: impl Fn(&mut T, X, U) -> Result<(), Box<EvalAltResult>> + SendSync + 'static,
) -> &mut Self
where
T: Variant + Clone,
U: Variant + Clone,
X: Variant + Clone,
{
self.register_result_fn(FN_IDX_SET, callback)
if TypeId::of::<T>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays.");
}
#[cfg(not(feature = "no_object"))]
if TypeId::of::<T>() == TypeId::of::<Map>() {
panic!("Cannot register indexer for object maps.");
}
if TypeId::of::<T>() == TypeId::of::<String>()
|| TypeId::of::<T>() == TypeId::of::<&str>()
|| TypeId::of::<T>() == TypeId::of::<ImmutableString>()
{
panic!("Cannot register indexer for strings.");
}
self.register_result_fn(FN_IDX_SET, move |obj: &mut T, index: X, value: U| {
callback(obj, index, value)?;
Ok(().into())
})
}
/// Short-hand for register both index getter and setter functions for a custom type with the `Engine`.
///
/// # Panics
///
/// Panics if the type is `Array` or `Map`.
/// Indexers for arrays, object maps and strings cannot be registered.
///
/// # Example
///
/// ```
@ -644,6 +731,7 @@ impl Engine {
///
/// impl TestStruct {
/// fn new() -> Self { TestStruct { fields: vec![1, 2, 3, 4, 5] } }
/// // Even a getter must start with `&mut self` and not `&self`.
/// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] }
/// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; }
/// }

View File

@ -3,7 +3,7 @@
use crate::any::{map_std_type_name, Dynamic, Union};
use crate::calc_fn_hash;
use crate::fn_call::run_builtin_op_assignment;
use crate::fn_native::{CallableFunction, Callback, FnPtr};
use crate::fn_native::{Callback, FnPtr};
use crate::module::{Module, ModuleRef};
use crate::optimize::OptimizationLevel;
use crate::packages::{Package, PackagesCollection, StandardPackage};
@ -756,10 +756,7 @@ impl Engine {
Err(err) => match *err {
// No index getter - try to call an index setter
#[cfg(not(feature = "no_index"))]
EvalAltResult::ErrorIndexingType(_, _) => {
// Raise error if there is no index getter nor setter
Some(new_val.unwrap())
}
EvalAltResult::ErrorIndexingType(_, _) => Some(new_val.unwrap()),
// Any other error - return
err => return Err(Box::new(err)),
},
@ -1168,7 +1165,7 @@ impl Engine {
.take_immutable_string()
.map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_pos))?;
map.entry(index).or_insert(Default::default()).into()
map.entry(index).or_insert_with(Default::default).into()
} else {
let index = idx
.read_lock::<ImmutableString>()
@ -1360,23 +1357,36 @@ impl Engine {
let arg_types = once(lhs_ptr.type_id()).chain(once(rhs_val.type_id()));
let hash_fn = calc_fn_hash(empty(), op, 2, arg_types);
if let Some(CallableFunction::Method(func)) = self
match self
.global_module
.get_fn(hash_fn, false)
.or_else(|| self.packages.get_fn(hash_fn, false))
{
// op= function registered as method
Some(func) if func.is_method() => {
let mut lock_guard;
let lhs_ptr_inner;
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
let mut lock_guard = lhs_ptr.write_lock::<Dynamic>().unwrap();
let lhs_ptr_inner = lock_guard.deref_mut();
lock_guard = lhs_ptr.write_lock::<Dynamic>().unwrap();
lhs_ptr_inner = lock_guard.deref_mut();
} else {
lhs_ptr_inner = lhs_ptr;
}
let args = &mut [lhs_ptr_inner, &mut rhs_val];
// Overriding exact implementation
func(self, lib, &mut [lhs_ptr_inner, &mut rhs_val])?;
if func.is_plugin_fn() {
func.get_plugin_fn().call(args)?;
} else {
// Overriding exact implementation
func(self, lib, &mut [lhs_ptr, &mut rhs_val])?;
func.get_native_fn()(self, lib, args)?;
}
} else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() {
// Not built in, map to `var = var op rhs`
}
// Built-in op-assignment function
_ if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_some() => {}
// Not built-in: expand to `var = var op rhs`
_ => {
let op = &op[..op.len() - 1]; // extract operator without =
// Clone the LHS value
@ -1385,17 +1395,20 @@ impl Engine {
// Run function
let (value, _) = self
.exec_fn_call(
state, lib, op, 0, args, false, false, false, None, None, level,
state, lib, op, 0, args, false, false, false, None, None,
level,
)
.map_err(|err| err.new_position(*op_pos))?;
let value = value.flatten();
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
*lhs_ptr.write_lock::<Dynamic>().unwrap() = value;
} else {
*lhs_ptr = value;
}
}
}
Ok(Default::default())
}
}
@ -2014,6 +2027,6 @@ impl Engine {
self.type_names
.as_ref()
.and_then(|t| t.get(name).map(String::as_str))
.unwrap_or(map_std_type_name(name))
.unwrap_or_else(|| map_std_type_name(name))
}
}

View File

@ -21,11 +21,10 @@ use crate::{
};
#[cfg(not(feature = "no_index"))]
#[cfg(not(feature = "no_object"))]
use crate::engine::{FN_IDX_GET, FN_IDX_SET};
use crate::engine::{Array, FN_IDX_GET, FN_IDX_SET};
#[cfg(not(feature = "no_object"))]
use crate::engine::{make_getter, make_setter};
use crate::engine::{make_getter, make_setter, Map};
use crate::stdlib::{
any::TypeId,
@ -675,6 +674,11 @@ impl Module {
///
/// If there is a similar existing setter Rust function, it is replaced.
///
/// # Panics
///
/// Panics if the type is `Array` or `Map`.
/// Indexers for arrays, object maps and strings cannot be registered.
///
/// # Examples
///
/// ```
@ -686,12 +690,25 @@ impl Module {
/// });
/// assert!(module.contains_fn(hash, true));
/// ```
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
pub fn set_indexer_get_fn<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self,
func: impl Fn(&mut A, B) -> FuncReturn<T> + SendSync + 'static,
) -> u64 {
if TypeId::of::<A>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays.");
}
#[cfg(not(feature = "no_object"))]
if TypeId::of::<A>() == TypeId::of::<Map>() {
panic!("Cannot register indexer for object maps.");
}
if TypeId::of::<A>() == TypeId::of::<String>()
|| TypeId::of::<A>() == TypeId::of::<&str>()
|| TypeId::of::<A>() == TypeId::of::<ImmutableString>()
{
panic!("Cannot register indexer for strings.");
}
self.set_fn_2_mut(FN_IDX_GET, func)
}
@ -773,6 +790,11 @@ impl Module {
///
/// If there is a similar existing Rust function, it is replaced.
///
/// # Panics
///
/// Panics if the type is `Array` or `Map`.
/// Indexers for arrays, object maps and strings cannot be registered.
///
/// # Examples
///
/// ```
@ -785,12 +807,25 @@ impl Module {
/// });
/// assert!(module.contains_fn(hash, true));
/// ```
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
pub fn set_indexer_set_fn<A: Variant + Clone, B: Variant + Clone, C: Variant + Clone>(
&mut self,
func: impl Fn(&mut A, B, C) -> FuncReturn<()> + SendSync + 'static,
) -> u64 {
if TypeId::of::<A>() == TypeId::of::<Array>() {
panic!("Cannot register indexer for arrays.");
}
#[cfg(not(feature = "no_object"))]
if TypeId::of::<A>() == TypeId::of::<Map>() {
panic!("Cannot register indexer for object maps.");
}
if TypeId::of::<A>() == TypeId::of::<String>()
|| TypeId::of::<A>() == TypeId::of::<&str>()
|| TypeId::of::<A>() == TypeId::of::<ImmutableString>()
{
panic!("Cannot register indexer for strings.");
}
let f = move |_: &Engine, _: &Module, args: &mut FnCallArgs| {
let b = cast_arg::<B>(&mut args[1]);
let c = cast_arg::<C>(&mut args[2]);
@ -812,6 +847,11 @@ impl Module {
///
/// If there are similar existing Rust functions, they are replaced.
///
/// # Panics
///
/// Panics if the type is `Array` or `Map`.
/// Indexers for arrays, object maps and strings cannot be registered.
///
/// # Examples
///
/// ```
@ -830,7 +870,6 @@ impl Module {
/// assert!(module.contains_fn(hash_get, true));
/// assert!(module.contains_fn(hash_set, true));
/// ```
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
pub fn set_indexer_get_set_fn<A: Variant + Clone, B: Variant + Clone, T: Variant + Clone>(
&mut self,

View File

@ -26,13 +26,14 @@ macro_rules! gen_array_functions {
pub mod $root { $( pub mod $arg_type {
use super::super::*;
#[export_fn]
#[export_module]
pub mod functions {
#[rhai_fn(name = "push", name = "+=")]
#[inline(always)]
pub fn push(list: &mut Array, item: $arg_type) {
list.push(Dynamic::from(item));
}
#[export_fn]
pub fn insert(list: &mut Array, position: INT, item: $arg_type) {
if position <= 0 {
list.insert(0, Dynamic::from(item));
@ -42,14 +43,14 @@ macro_rules! gen_array_functions {
list.insert(position as usize, Dynamic::from(item));
}
}
}
})* }
}
}
macro_rules! reg_functions {
($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $(
set_exported_fn!($mod_name, "push", $root::$arg_type::push);
set_exported_fn!($mod_name, "insert", $root::$arg_type::insert);
combine_with_exported_module!($mod_name, "array_functions", $root::$arg_type::functions);
$mod_name.set_raw_fn("pad",
&[TypeId::of::<Array>(), TypeId::of::<INT>(), TypeId::of::<$arg_type>()],
@ -58,8 +59,6 @@ macro_rules! reg_functions {
}
def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
combine_with_exported_module!(lib, "array", array_functions);
reg_functions!(lib += basic; INT, bool, char, ImmutableString, FnPtr, Array, Unit);
#[cfg(not(feature = "only_i32"))]
@ -77,6 +76,9 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
#[cfg(not(feature = "no_object"))]
reg_functions!(lib += map; Map);
// Merge in the module at the end to override `+=` for arrays
combine_with_exported_module!(lib, "array", array_functions);
// Register array iterator
lib.set_iter(
TypeId::of::<Array>(),

View File

@ -18,7 +18,6 @@ mod map_functions {
pub fn has(map: &mut Map, prop: ImmutableString) -> bool {
map.contains_key(&prop)
}
#[rhai_fn(name = "len", get = "len")]
#[inline(always)]
pub fn len(map: &mut Map) -> INT {
map.len() as INT
@ -46,9 +45,7 @@ mod map_functions {
}
pub fn fill_with(map1: &mut Map, map2: Map) {
map2.into_iter().for_each(|(key, value)| {
if !map1.contains_key(&key) {
map1.insert(key, value);
}
map1.entry(key).or_insert(value);
});
}

View File

@ -3,12 +3,13 @@
use crate::def_package;
use crate::parser::INT;
use crate::plugin::*;
use crate::token::Position;
#[cfg(not(feature = "no_float"))]
use crate::parser::FLOAT;
#[cfg(not(feature = "no_float"))]
use crate::{result::EvalAltResult, token::Position};
use crate::result::EvalAltResult;
#[cfg(feature = "no_std")]
#[cfg(not(feature = "no_float"))]
@ -67,6 +68,9 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
reg_functions!(lib += basic_to_int::to_int(char));
set_exported_fn!(lib, "parse_int", parse_int);
set_exported_fn!(lib, "parse_int", parse_int_radix);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
@ -223,6 +227,21 @@ mod float_functions {
Ok((x.trunc() as INT).into())
}
}
#[rhai_fn(return_raw)]
#[inline]
pub fn parse_float(s: &str) -> Result<Dynamic, Box<EvalAltResult>> {
s.trim()
.parse::<FLOAT>()
.map(Into::<Dynamic>::into)
.map_err(|err| {
EvalAltResult::ErrorArithmetic(
format!("Error parsing floating-point number '{}': {}", s, err),
Position::none(),
)
.into()
})
}
}
#[cfg(not(feature = "no_float"))]
@ -249,3 +268,30 @@ gen_conversion_functions!(numbers_to_int => to_int (i8, u8, i16, u16, i32, u32,
#[cfg(not(feature = "only_i64"))]
#[cfg(not(target_arch = "wasm32"))]
gen_conversion_functions!(num_128_to_int => to_int (i128, u128) -> INT);
#[export_fn(return_raw)]
fn parse_int_radix(s: &str, radix: INT) -> Result<Dynamic, Box<EvalAltResult>> {
if radix < 2 || radix > 36 {
return EvalAltResult::ErrorArithmetic(
format!("Invalid radix: '{}'", radix),
Position::none(),
)
.into();
}
INT::from_str_radix(s.trim(), radix as u32)
.map(Into::<Dynamic>::into)
.map_err(|err| {
EvalAltResult::ErrorArithmetic(
format!("Error parsing integer number '{}': {}", s, err),
Position::none(),
)
.into()
})
}
#[export_fn(return_raw)]
#[inline(always)]
fn parse_int(s: &str) -> Result<Dynamic, Box<EvalAltResult>> {
parse_int_radix(s, 10)
}

View File

@ -151,6 +151,7 @@ fn to_string<T: Display>(x: &mut T) -> ImmutableString {
fn to_debug<T: Debug>(x: &mut T) -> ImmutableString {
format!("{:?}", x).into()
}
#[cfg(not(feature = "no_object"))]
mod format_map {
use super::*;

View File

@ -20,25 +20,27 @@ macro_rules! gen_concat_functions {
pub mod $root { $( pub mod $arg_type {
use super::super::*;
#[export_fn]
#[export_module]
pub mod functions {
#[rhai_fn(name = "+")]
#[inline]
pub fn append_func(x: &mut ImmutableString, y: $arg_type) -> String {
pub fn append_func(x: &str, y: $arg_type) -> String {
format!("{}{}", x, y)
}
#[export_fn]
#[rhai_fn(name = "+")]
#[inline]
pub fn prepend_func(x: &mut $arg_type, y: ImmutableString) -> String {
pub fn prepend_func(x: &mut $arg_type, y: &str) -> String {
format!("{}{}", x, y)
}
}
} )* }
}
}
macro_rules! reg_functions {
($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $(
set_exported_fn!($mod_name, "+", $root::$arg_type::append_func);
set_exported_fn!($mod_name, "+", $root::$arg_type::prepend_func);
combine_with_exported_module!($mod_name, "strings_concat", $root::$arg_type::functions);
)* }
}
@ -154,7 +156,7 @@ mod string_functions {
#[rhai_fn(name = "len", get = "len")]
#[inline(always)]
pub fn len(s: &mut ImmutableString) -> INT {
pub fn len(s: &str) -> INT {
s.chars().count() as INT
}
@ -182,17 +184,17 @@ mod string_functions {
#[rhai_fn(name = "contains")]
#[inline(always)]
pub fn contains_char(s: &mut ImmutableString, ch: char) -> bool {
pub fn contains_char(s: &str, ch: char) -> bool {
s.contains(ch)
}
#[rhai_fn(name = "contains")]
#[inline(always)]
pub fn contains_string(s: &mut ImmutableString, find: ImmutableString) -> bool {
pub fn contains_string(s: &str, find: ImmutableString) -> bool {
s.contains(find.as_str())
}
#[rhai_fn(name = "index_of")]
pub fn index_of_char_starting_from(s: &mut ImmutableString, ch: char, start: INT) -> INT {
pub fn index_of_char_starting_from(s: &str, ch: char, start: INT) -> INT {
let start = if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
@ -207,17 +209,13 @@ mod string_functions {
.unwrap_or(-1 as INT)
}
#[rhai_fn(name = "index_of")]
pub fn index_of_char(s: &mut ImmutableString, ch: char) -> INT {
pub fn index_of_char(s: &str, ch: char) -> INT {
s.find(ch)
.map(|index| s[0..index].chars().count() as INT)
.unwrap_or(-1 as INT)
}
#[rhai_fn(name = "index_of")]
pub fn index_of_string_starting_from(
s: &mut ImmutableString,
find: ImmutableString,
start: INT,
) -> INT {
pub fn index_of_string_starting_from(s: &str, find: ImmutableString, start: INT) -> INT {
let start = if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
@ -232,14 +230,14 @@ mod string_functions {
.unwrap_or(-1 as INT)
}
#[rhai_fn(name = "index_of")]
pub fn index_of_string(s: &mut ImmutableString, find: ImmutableString) -> INT {
pub fn index_of_string(s: &str, find: ImmutableString) -> INT {
s.find(find.as_str())
.map(|index| s[0..index].chars().count() as INT)
.unwrap_or(-1 as INT)
}
#[rhai_fn(name = "sub_string")]
pub fn sub_string(s: ImmutableString, start: INT, len: INT) -> ImmutableString {
pub fn sub_string(s: &str, start: INT, len: INT) -> ImmutableString {
let offset = if s.is_empty() || len <= 0 {
return "".to_string().into();
} else if start < 0 {
@ -268,7 +266,7 @@ mod string_functions {
}
#[rhai_fn(name = "sub_string")]
#[inline(always)]
pub fn sub_string_starting_from(s: ImmutableString, start: INT) -> ImmutableString {
pub fn sub_string_starting_from(s: &str, start: INT) -> ImmutableString {
let len = s.len() as INT;
sub_string(s, start, len)
}
@ -332,13 +330,29 @@ mod string_functions {
#[rhai_fn(name = "+")]
#[inline]
pub fn append(x: &mut ImmutableString, y: Array) -> String {
pub fn append(x: &str, y: Array) -> String {
format!("{}{:?}", x, y)
}
#[rhai_fn(name = "+")]
#[inline]
pub fn prepend(x: &mut Array, y: ImmutableString) -> String {
pub fn prepend(x: &mut Array, y: &str) -> String {
format!("{:?}{}", x, y)
}
}
#[cfg(not(feature = "no_object"))]
pub mod maps {
use crate::engine::Map;
#[rhai_fn(name = "+")]
#[inline]
pub fn append(x: &str, y: Map) -> String {
format!("{}#{:?}", x, y)
}
#[rhai_fn(name = "+")]
#[inline]
pub fn prepend(x: &mut Map, y: &str) -> String {
format!("#{:?}{}", x, y)
}
}
}

View File

@ -3479,7 +3479,7 @@ pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option<Expr> {
Union::Unit(_) => Some(Expr::Unit(pos)),
Union::Int(value) => Some(Expr::IntegerConstant(Box::new((value, pos)))),
Union::Char(value) => Some(Expr::CharConstant(Box::new((value, pos)))),
Union::Str(value) => Some(Expr::StringConstant(Box::new((value.clone(), pos)))),
Union::Str(value) => Some(Expr::StringConstant(Box::new((value, pos)))),
Union::Bool(true) => Some(Expr::True(pos)),
Union::Bool(false) => Some(Expr::False(pos)),
#[cfg(not(feature = "no_index"))]

View File

@ -752,8 +752,10 @@ pub fn parse_string_literal(
let mut result: StaticVec<char> = Default::default();
let mut escape: StaticVec<char> = Default::default();
let start = *pos;
loop {
let next_char = stream.get_next().ok_or((LERR::UnterminatedString, *pos))?;
let next_char = stream.get_next().ok_or((LERR::UnterminatedString, start))?;
pos.advance();
@ -838,17 +840,19 @@ pub fn parse_string_literal(
ch if enclosing_char == ch && escape.is_empty() => break,
// Unknown escape sequence
_ if !escape.is_empty() => {
ch if !escape.is_empty() => {
escape.push(ch);
return Err((
LERR::MalformedEscapeSequence(escape.into_iter().collect()),
*pos,
))
));
}
// Cannot have new-lines inside string literals
'\n' => {
pos.rewind();
return Err((LERR::UnterminatedString, *pos));
return Err((LERR::UnterminatedString, start));
}
// All other characters

View File

@ -13,6 +13,10 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
'3'
);
assert!(engine.eval::<bool>("let y = [1, 2, 3]; 2 in y")?);
assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y += 4; y[3]")?, 4);
#[cfg(not(feature = "no_object"))]
assert_eq!(engine.eval::<INT>("let y = [1, 2, 3]; y.push(4); y[3]")?, 4);
#[cfg(not(feature = "no_object"))]
assert_eq!(

View File

@ -20,6 +20,15 @@ fn test_float() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[test]
fn test_float_parse() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert!((engine.eval::<FLOAT>(r#"parse_float("9.9999")"#)? - 9.9999 as FLOAT).abs() < EPSILON);
Ok(())
}
#[test]
#[cfg(not(feature = "no_object"))]
fn test_struct_with_float() -> Result<(), Box<EvalAltResult>> {

View File

@ -108,3 +108,14 @@ fn test_math() -> Result<(), Box<EvalAltResult>> {
Ok(())
}
#[test]
fn test_math_parse() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(engine.eval::<INT>(r#"parse_int("42")"#)?, 42);
assert_eq!(engine.eval::<INT>(r#"parse_int("42", 16)"#)?, 0x42);
assert_eq!(engine.eval::<INT>(r#"parse_int("abcdef", 16)"#)?, 0xabcdef);
Ok(())
}