commit
c977140ecb
15
CHANGELOG.md
15
CHANGELOG.md
@ -19,6 +19,7 @@ Enhancements
|
|||||||
* `Engine::consume_XXX` methods are renamed to `Engine::run_XXX` to make meanings clearer. The `consume_XXX` API is deprecated.
|
* `Engine::consume_XXX` methods are renamed to `Engine::run_XXX` to make meanings clearer. The `consume_XXX` API is deprecated.
|
||||||
* `Engine::register_type_XXX` are now available even under `no_object`.
|
* `Engine::register_type_XXX` are now available even under `no_object`.
|
||||||
* Added `Engine::on_parse_token` to allow remapping certain tokens during parsing.
|
* Added `Engine::on_parse_token` to allow remapping certain tokens during parsing.
|
||||||
|
* Added `Engine::const_empty_string` to merge empty strings into a single instance.
|
||||||
|
|
||||||
### Custom Syntax
|
### Custom Syntax
|
||||||
|
|
||||||
@ -42,6 +43,7 @@ Enhancements
|
|||||||
|
|
||||||
* `SmartString` now uses `LazyCompact` instead of `Compact` to minimize allocations.
|
* `SmartString` now uses `LazyCompact` instead of `Compact` to minimize allocations.
|
||||||
* Added `pop` for strings.
|
* Added `pop` for strings.
|
||||||
|
* Added `ImmutableString::ptr_eq` to test if two strings point to the same allocation.
|
||||||
|
|
||||||
### `Scope` API
|
### `Scope` API
|
||||||
|
|
||||||
@ -57,9 +59,22 @@ Enhancements
|
|||||||
* `StaticVec` is changed to keep three items inline instead of four.
|
* `StaticVec` is changed to keep three items inline instead of four.
|
||||||
|
|
||||||
|
|
||||||
|
Version 1.0.7
|
||||||
|
=============
|
||||||
|
|
||||||
|
|
||||||
Version 1.0.6
|
Version 1.0.6
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
Bug fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
* Eliminate unnecessary property write-back when accessed via a getter since property getters are assumed to be _pure_.
|
||||||
|
* Writing to a property of an indexed valued obtained via an indexer now works properly by writing back the changed value via an index setter.
|
||||||
|
|
||||||
|
Enhancements
|
||||||
|
------------
|
||||||
|
|
||||||
* `MultiInputsStream`, `ParseState`, `TokenIterator`, `IdentifierBuilder` and `AccessMode` are exported under the `internals` feature.
|
* `MultiInputsStream`, `ParseState`, `TokenIterator`, `IdentifierBuilder` and `AccessMode` are exported under the `internals` feature.
|
||||||
|
|
||||||
|
|
||||||
|
@ -84,6 +84,7 @@ Below is the standard _Fibonacci_ example for scripting languages:
|
|||||||
|
|
||||||
const TARGET = 28;
|
const TARGET = 28;
|
||||||
const REPEAT = 5;
|
const REPEAT = 5;
|
||||||
|
const ANSWER = 317_811;
|
||||||
|
|
||||||
fn fib(n) {
|
fn fib(n) {
|
||||||
if n < 2 {
|
if n < 2 {
|
||||||
@ -107,8 +108,8 @@ print(`Finished. Run time = ${now.elapsed} seconds.`);
|
|||||||
|
|
||||||
print(`Fibonacci number #${TARGET} = ${result}`);
|
print(`Fibonacci number #${TARGET} = ${result}`);
|
||||||
|
|
||||||
if result != 317_811 {
|
if result != ANSWER {
|
||||||
print("The answer is WRONG! Should be 317,811!");
|
print(`The answer is WRONG! Should be ${ANSWER}!`);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -13,9 +13,9 @@ help: a struct with a similar name exists
|
|||||||
| ~~~~~
|
| ~~~~~
|
||||||
help: consider importing one of these items
|
help: consider importing one of these items
|
||||||
|
|
|
|
||||||
11 | use core::fmt::Pointer;
|
|
||||||
|
|
|
||||||
11 | use std::fmt::Pointer;
|
11 | use std::fmt::Pointer;
|
||||||
|
|
|
|
||||||
11 | use syn::__private::fmt::Pointer;
|
11 | use syn::__private::fmt::Pointer;
|
||||||
|
|
|
|
||||||
|
11 | use core::fmt::Pointer;
|
||||||
|
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
const TARGET = 28;
|
const TARGET = 28;
|
||||||
const REPEAT = 5;
|
const REPEAT = 5;
|
||||||
|
const ANSWER = 317_811;
|
||||||
|
|
||||||
fn fib(n) {
|
fn fib(n) {
|
||||||
if n < 2 {
|
if n < 2 {
|
||||||
@ -26,6 +27,6 @@ print(`Finished. Run time = ${now.elapsed} seconds.`);
|
|||||||
|
|
||||||
print(`Fibonacci number #${TARGET} = ${result}`);
|
print(`Fibonacci number #${TARGET} = ${result}`);
|
||||||
|
|
||||||
if result != 317_811 {
|
if result != ANSWER {
|
||||||
print("The answer is WRONG! Should be 317,811!");
|
print(`The answer is WRONG! Should be ${ANSWER}!`);
|
||||||
}
|
}
|
||||||
|
@ -316,6 +316,24 @@ impl Engine {
|
|||||||
///
|
///
|
||||||
/// All custom keywords used as symbols must be manually registered via [`Engine::register_custom_operator`].
|
/// All custom keywords used as symbols must be manually registered via [`Engine::register_custom_operator`].
|
||||||
/// Otherwise, they won't be recognized.
|
/// Otherwise, they won't be recognized.
|
||||||
|
///
|
||||||
|
/// # Implementation Function Signature
|
||||||
|
///
|
||||||
|
/// The implementation function has the following signature:
|
||||||
|
///
|
||||||
|
/// > `Fn(symbols: &[ImmutableString], look_ahead: &str) -> Result<Option<ImmutableString>, ParseError>`
|
||||||
|
///
|
||||||
|
/// where:
|
||||||
|
/// * `symbols`: a slice of symbols that have been parsed so far, possibly containing `$expr$` and/or `$block$`;
|
||||||
|
/// `$ident$` and other literal markers are replaced by the actual text
|
||||||
|
/// * `look_ahead`: a string slice containing the next symbol that is about to be read
|
||||||
|
///
|
||||||
|
/// ## Return value
|
||||||
|
///
|
||||||
|
/// * `Ok(None)`: parsing complete and there are no more symbols to match.
|
||||||
|
/// * `Ok(Some(symbol))`: the next symbol to match, which can also be `$expr$`, `$ident$` or `$block$`.
|
||||||
|
/// * `Err(ParseError)`: error that is reflected back to the [`Engine`], normally `ParseError(ParseErrorType::BadInput(LexError::ImproperSymbol(message)), Position::NONE)` to indicate a syntax error, but it can be any [`ParseError`].
|
||||||
|
///
|
||||||
pub fn register_custom_syntax_raw(
|
pub fn register_custom_syntax_raw(
|
||||||
&mut self,
|
&mut self,
|
||||||
key: impl Into<Identifier>,
|
key: impl Into<Identifier>,
|
||||||
|
@ -1877,7 +1877,7 @@ impl Dynamic {
|
|||||||
_ => Err(self.type_name()),
|
_ => Err(self.type_name()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Cast the [`Dynamic`] as an [`ImmutableString`] and return it.
|
/// Cast the [`Dynamic`] as an [`ImmutableString`] and return it as a string slice.
|
||||||
/// Returns the name of the actual type if the cast fails.
|
/// Returns the name of the actual type if the cast fails.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
@ -1888,7 +1888,7 @@ impl Dynamic {
|
|||||||
match self.0 {
|
match self.0 {
|
||||||
Union::Str(ref s, _, _) => Ok(s),
|
Union::Str(ref s, _, _) => Ok(s),
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
Union::Shared(_, _, _) => panic!("as_str() cannot be called on shared values"),
|
Union::Shared(_, _, _) => panic!("as_str_ref() cannot be called on shared values"),
|
||||||
_ => Err(self.type_name()),
|
_ => Err(self.type_name()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
149
src/engine.rs
149
src/engine.rs
@ -3,7 +3,7 @@
|
|||||||
use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, Stmt, AST_OPTION_FLAGS::*};
|
use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, Stmt, AST_OPTION_FLAGS::*};
|
||||||
use crate::custom_syntax::CustomSyntax;
|
use crate::custom_syntax::CustomSyntax;
|
||||||
use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant};
|
use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant};
|
||||||
use crate::fn_hash::get_hasher;
|
use crate::fn_hash::{calc_fn_hash, get_hasher};
|
||||||
use crate::fn_native::{
|
use crate::fn_native::{
|
||||||
CallableFunction, IteratorFn, OnDebugCallback, OnParseTokenCallback, OnPrintCallback,
|
CallableFunction, IteratorFn, OnDebugCallback, OnParseTokenCallback, OnPrintCallback,
|
||||||
OnVarCallback,
|
OnVarCallback,
|
||||||
@ -336,6 +336,20 @@ impl ChainArgument {
|
|||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Return the [position][Position].
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub const fn position(&self) -> Position {
|
||||||
|
match self {
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
ChainArgument::Property(pos) => *pos,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
ChainArgument::MethodCallArgs(_, pos) => *pos,
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
ChainArgument::IndexValue(_, pos) => *pos,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
@ -790,6 +804,31 @@ impl Default for Limits {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A type containing useful constants for the [`Engine`].
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GlobalConstants {
|
||||||
|
/// An empty [`ImmutableString`] for cloning purposes.
|
||||||
|
pub(crate) empty_string: ImmutableString,
|
||||||
|
/// Function call hash to FN_IDX_GET
|
||||||
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
|
pub(crate) fn_hash_idx_get: u64,
|
||||||
|
/// Function call hash to FN_IDX_SET
|
||||||
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
|
pub(crate) fn_hash_idx_set: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for GlobalConstants {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
empty_string: Default::default(),
|
||||||
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
|
fn_hash_idx_get: calc_fn_hash(FN_IDX_GET, 2),
|
||||||
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
|
fn_hash_idx_set: calc_fn_hash(FN_IDX_SET, 3),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Context of a script evaluation process.
|
/// Context of a script evaluation process.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EvalContext<'a, 'x, 'px, 'm, 's, 'b, 't, 'pt> {
|
pub struct EvalContext<'a, 'x, 'px, 'm, 's, 'b, 't, 'pt> {
|
||||||
@ -911,8 +950,8 @@ pub struct Engine {
|
|||||||
/// A map mapping type names to pretty-print names.
|
/// A map mapping type names to pretty-print names.
|
||||||
pub(crate) type_names: BTreeMap<Identifier, Box<Identifier>>,
|
pub(crate) type_names: BTreeMap<Identifier, Box<Identifier>>,
|
||||||
|
|
||||||
/// An empty [`ImmutableString`] for cloning purposes.
|
/// Useful constants
|
||||||
pub(crate) empty_string: ImmutableString,
|
pub(crate) constants: GlobalConstants,
|
||||||
|
|
||||||
/// A set of symbols to disable.
|
/// A set of symbols to disable.
|
||||||
pub(crate) disabled_symbols: BTreeSet<Identifier>,
|
pub(crate) disabled_symbols: BTreeSet<Identifier>,
|
||||||
@ -1042,7 +1081,7 @@ impl Engine {
|
|||||||
module_resolver: None,
|
module_resolver: None,
|
||||||
|
|
||||||
type_names: Default::default(),
|
type_names: Default::default(),
|
||||||
empty_string: Default::default(),
|
constants: Default::default(),
|
||||||
disabled_symbols: Default::default(),
|
disabled_symbols: Default::default(),
|
||||||
custom_keywords: Default::default(),
|
custom_keywords: Default::default(),
|
||||||
custom_syntax: Default::default(),
|
custom_syntax: Default::default(),
|
||||||
@ -1070,6 +1109,16 @@ impl Engine {
|
|||||||
engine
|
engine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get an empty [`ImmutableString`].
|
||||||
|
///
|
||||||
|
/// [`Engine`] keeps a single instance of an empty [`ImmutableString`] and uses this to create
|
||||||
|
/// shared instances for subsequent uses. This minimizes unnecessary allocations for empty strings.
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub fn const_empty_string(&self) -> ImmutableString {
|
||||||
|
self.constants.empty_string.clone()
|
||||||
|
}
|
||||||
|
|
||||||
/// Search for a module within an imports stack.
|
/// Search for a module within an imports stack.
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -1244,6 +1293,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
ChainType::Indexing => {
|
ChainType::Indexing => {
|
||||||
let pos = rhs.position();
|
let pos = rhs.position();
|
||||||
|
let root_pos = idx_val.position();
|
||||||
let idx_val = idx_val
|
let idx_val = idx_val
|
||||||
.into_index_value()
|
.into_index_value()
|
||||||
.expect("`chain_type` is `ChainType::Index`");
|
.expect("`chain_type` is `ChainType::Index`");
|
||||||
@ -1253,17 +1303,50 @@ impl Engine {
|
|||||||
Expr::Dot(x, term, x_pos) | Expr::Index(x, term, x_pos)
|
Expr::Dot(x, term, x_pos) | Expr::Index(x, term, x_pos)
|
||||||
if !_terminate_chaining =>
|
if !_terminate_chaining =>
|
||||||
{
|
{
|
||||||
|
let mut idx_val_for_setter = idx_val.clone();
|
||||||
let idx_pos = x.lhs.position();
|
let idx_pos = x.lhs.position();
|
||||||
let obj_ptr = &mut self.get_indexed_mut(
|
let rhs_chain = match_chaining_type(rhs);
|
||||||
|
|
||||||
|
let (try_setter, result) = {
|
||||||
|
let mut obj = self.get_indexed_mut(
|
||||||
mods, state, lib, target, idx_val, idx_pos, false, true, level,
|
mods, state, lib, target, idx_val, idx_pos, false, true, level,
|
||||||
)?;
|
)?;
|
||||||
|
let is_obj_temp_val = obj.is_temp_value();
|
||||||
|
let obj_ptr = &mut obj;
|
||||||
|
|
||||||
let rhs_chain = match_chaining_type(rhs);
|
match self.eval_dot_index_chain_helper(
|
||||||
self.eval_dot_index_chain_helper(
|
mods, state, lib, this_ptr, obj_ptr, root, &x.rhs, *term,
|
||||||
mods, state, lib, this_ptr, obj_ptr, root, &x.rhs, *term, idx_values,
|
idx_values, rhs_chain, level, new_val,
|
||||||
rhs_chain, level, new_val,
|
) {
|
||||||
)
|
Ok((result, true)) if is_obj_temp_val => {
|
||||||
.map_err(|err| err.fill_position(*x_pos))
|
(Some(obj.take_or_clone()), (result, true))
|
||||||
|
}
|
||||||
|
Ok(result) => (None, result),
|
||||||
|
Err(err) => return Err(err.fill_position(*x_pos)),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(mut new_val) = try_setter {
|
||||||
|
// Try to call index setter if value is changed
|
||||||
|
let hash_set =
|
||||||
|
FnCallHashes::from_native(self.constants.fn_hash_idx_set);
|
||||||
|
let args = &mut [target, &mut idx_val_for_setter, &mut new_val];
|
||||||
|
|
||||||
|
if let Err(err) = self.exec_fn_call(
|
||||||
|
mods, state, lib, FN_IDX_SET, hash_set, args, is_ref_mut, true,
|
||||||
|
root_pos, None, level,
|
||||||
|
) {
|
||||||
|
// Just ignore if there is no index setter
|
||||||
|
if !matches!(*err, EvalAltResult::ErrorFunctionNotFound(_, _)) {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.check_data_size(target.as_ref())
|
||||||
|
.map_err(|err| err.fill_position(root.1))?;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
// xxx[rhs] op= new_val
|
// xxx[rhs] op= new_val
|
||||||
_ if new_val.is_some() => {
|
_ if new_val.is_some() => {
|
||||||
@ -1294,13 +1377,12 @@ impl Engine {
|
|||||||
if let Some(mut new_val) = try_setter {
|
if let Some(mut new_val) = try_setter {
|
||||||
// Try to call index setter
|
// Try to call index setter
|
||||||
let hash_set =
|
let hash_set =
|
||||||
FnCallHashes::from_native(crate::calc_fn_hash(FN_IDX_SET, 3));
|
FnCallHashes::from_native(self.constants.fn_hash_idx_set);
|
||||||
let args = &mut [target, &mut idx_val_for_setter, &mut new_val];
|
let args = &mut [target, &mut idx_val_for_setter, &mut new_val];
|
||||||
let pos = Position::NONE;
|
|
||||||
|
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
mods, state, lib, FN_IDX_SET, hash_set, args, is_ref_mut, true,
|
mods, state, lib, FN_IDX_SET, hash_set, args, is_ref_mut, true,
|
||||||
pos, None, level,
|
root_pos, None, level,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1427,7 +1509,7 @@ impl Engine {
|
|||||||
EvalAltResult::ErrorDotExpr(_, _) => {
|
EvalAltResult::ErrorDotExpr(_, _) => {
|
||||||
let args = &mut [target, &mut name.into(), &mut new_val];
|
let args = &mut [target, &mut name.into(), &mut new_val];
|
||||||
let hash_set =
|
let hash_set =
|
||||||
FnCallHashes::from_native(crate::calc_fn_hash(FN_IDX_SET, 3));
|
FnCallHashes::from_native(self.constants.fn_hash_idx_set);
|
||||||
let pos = Position::NONE;
|
let pos = Position::NONE;
|
||||||
|
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
@ -1471,6 +1553,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
_ => Err(err),
|
_ => Err(err),
|
||||||
},
|
},
|
||||||
|
// Assume getters are always pure
|
||||||
|(v, _)| Ok((v, false)),
|
|(v, _)| Ok((v, false)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1524,7 +1607,9 @@ impl Engine {
|
|||||||
let hash_set = FnCallHashes::from_native(*hash_set);
|
let hash_set = FnCallHashes::from_native(*hash_set);
|
||||||
let mut arg_values = [target.as_mut(), &mut Default::default()];
|
let mut arg_values = [target.as_mut(), &mut Default::default()];
|
||||||
let args = &mut arg_values[..1];
|
let args = &mut arg_values[..1];
|
||||||
let (mut val, updated) = self
|
|
||||||
|
// Assume getters are always pure
|
||||||
|
let (mut val, _) = self
|
||||||
.exec_fn_call(
|
.exec_fn_call(
|
||||||
mods, state, lib, getter, hash_get, args, is_ref_mut, true,
|
mods, state, lib, getter, hash_get, args, is_ref_mut, true,
|
||||||
*pos, None, level,
|
*pos, None, level,
|
||||||
@ -1568,7 +1653,7 @@ impl Engine {
|
|||||||
.map_err(|err| err.fill_position(*x_pos))?;
|
.map_err(|err| err.fill_position(*x_pos))?;
|
||||||
|
|
||||||
// Feed the value back via a setter just in case it has been updated
|
// Feed the value back via a setter just in case it has been updated
|
||||||
if updated || may_be_changed {
|
if may_be_changed {
|
||||||
// Re-use args because the first &mut parameter will not be consumed
|
// Re-use args because the first &mut parameter will not be consumed
|
||||||
let mut arg_values = [target.as_mut(), val];
|
let mut arg_values = [target.as_mut(), val];
|
||||||
let args = &mut arg_values;
|
let args = &mut arg_values;
|
||||||
@ -1583,7 +1668,7 @@ impl Engine {
|
|||||||
let args =
|
let args =
|
||||||
&mut [target.as_mut(), &mut name.into(), val];
|
&mut [target.as_mut(), &mut name.into(), val];
|
||||||
let hash_set = FnCallHashes::from_native(
|
let hash_set = FnCallHashes::from_native(
|
||||||
crate::calc_fn_hash(FN_IDX_SET, 3),
|
self.constants.fn_hash_idx_set,
|
||||||
);
|
);
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
mods, state, lib, FN_IDX_SET, hash_set, args,
|
mods, state, lib, FN_IDX_SET, hash_set, args,
|
||||||
@ -1987,7 +2072,7 @@ impl Engine {
|
|||||||
|
|
||||||
_ if use_indexers => {
|
_ if use_indexers => {
|
||||||
let args = &mut [target, &mut idx];
|
let args = &mut [target, &mut idx];
|
||||||
let hash_get = FnCallHashes::from_native(crate::calc_fn_hash(FN_IDX_GET, 2));
|
let hash_get = FnCallHashes::from_native(self.constants.fn_hash_idx_get);
|
||||||
let idx_pos = Position::NONE;
|
let idx_pos = Position::NONE;
|
||||||
|
|
||||||
self.exec_fn_call(
|
self.exec_fn_call(
|
||||||
@ -2059,7 +2144,7 @@ impl Engine {
|
|||||||
// `... ${...} ...`
|
// `... ${...} ...`
|
||||||
Expr::InterpolatedString(x, pos) => {
|
Expr::InterpolatedString(x, pos) => {
|
||||||
let mut pos = *pos;
|
let mut pos = *pos;
|
||||||
let mut result: Dynamic = self.empty_string.clone().into();
|
let mut result: Dynamic = self.const_empty_string().into();
|
||||||
|
|
||||||
for expr in x.iter() {
|
for expr in x.iter() {
|
||||||
let item = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
let item = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||||
@ -2330,7 +2415,7 @@ impl Engine {
|
|||||||
let args = &mut [lhs_ptr_inner, &mut new_val];
|
let args = &mut [lhs_ptr_inner, &mut new_val];
|
||||||
|
|
||||||
match self.call_native_fn(mods, state, lib, op, hash, args, true, true, op_pos) {
|
match self.call_native_fn(mods, state, lib, op, hash, args, true, true, op_pos) {
|
||||||
Err(err) if matches!(err.as_ref(), EvalAltResult::ErrorFunctionNotFound(f, _) if f.starts_with(op)) =>
|
Err(err) if matches!(*err, EvalAltResult::ErrorFunctionNotFound(ref f, _) if f.starts_with(op)) =>
|
||||||
{
|
{
|
||||||
// Expand to `var = var op rhs`
|
// Expand to `var = var op rhs`
|
||||||
let op = &op[..op.len() - 1]; // extract operator without =
|
let op = &op[..op.len() - 1]; // extract operator without =
|
||||||
@ -3006,17 +3091,23 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Check a result to ensure that the data size is within allowable limit.
|
/// Check a result to ensure that the data size is within allowable limit.
|
||||||
#[cfg(feature = "unchecked")]
|
fn check_return_value(&self, mut result: RhaiResult) -> RhaiResult {
|
||||||
#[inline(always)]
|
if let Ok(ref mut r) = result {
|
||||||
fn check_return_value(&self, result: RhaiResult) -> RhaiResult {
|
// Concentrate all empty strings into one instance to save memory
|
||||||
result
|
if let Dynamic(crate::dynamic::Union::Str(s, _, _)) = r {
|
||||||
|
if s.is_empty() {
|
||||||
|
if !s.ptr_eq(&self.constants.empty_string) {
|
||||||
|
*s = self.const_empty_string();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check a result to ensure that the data size is within allowable limit.
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[inline(always)]
|
self.check_data_size(&r)?;
|
||||||
fn check_return_value(&self, result: RhaiResult) -> RhaiResult {
|
}
|
||||||
result.and_then(|r| self.check_data_size(&r).map(|_| r))
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "unchecked")]
|
#[cfg(feature = "unchecked")]
|
||||||
|
@ -1368,15 +1368,17 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
let (stream, tokenizer_control) = engine.lex_raw(
|
let (stream, tokenizer_control) = engine.lex_raw(
|
||||||
&scripts,
|
&scripts,
|
||||||
Some(if has_null {
|
if has_null {
|
||||||
&|token| match token {
|
Some(&|token, _, _| {
|
||||||
|
match token {
|
||||||
// If `null` is present, make sure `null` is treated as a variable
|
// If `null` is present, make sure `null` is treated as a variable
|
||||||
Token::Reserved(s) if s == "null" => Token::Identifier(s),
|
Token::Reserved(s) if s == "null" => Token::Identifier(s),
|
||||||
_ => token,
|
_ => token,
|
||||||
}
|
}
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
&|t| t
|
None
|
||||||
}),
|
},
|
||||||
);
|
);
|
||||||
let mut state = ParseState::new(engine, tokenizer_control);
|
let mut state = ParseState::new(engine, tokenizer_control);
|
||||||
let ast = engine.parse_global_expr(
|
let ast = engine.parse_global_expr(
|
||||||
@ -2078,12 +2080,28 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
/// Provide a callback that will be invoked before each variable access.
|
/// Provide a callback that will be invoked before each variable access.
|
||||||
///
|
///
|
||||||
/// # Return Value of Callback
|
/// # Callback Function Signature
|
||||||
///
|
///
|
||||||
/// Return `Ok(None)` to continue with normal variable access.
|
/// The callback function signature takes the following form:
|
||||||
/// Return `Ok(Some(Dynamic))` as the variable's value.
|
|
||||||
///
|
///
|
||||||
/// # Errors in Callback
|
/// > `Fn(name: &str, index: usize, context: &EvalContext)`
|
||||||
|
/// > ` -> Result<Option<Dynamic>, Box<EvalAltResult>> + 'static`
|
||||||
|
///
|
||||||
|
/// where:
|
||||||
|
/// * `index`: an offset from the bottom of the current [`Scope`] that the variable is supposed
|
||||||
|
/// to reside. Offsets start from 1, with 1 meaning the last variable in the current
|
||||||
|
/// [`Scope`]. Essentially the correct variable is at position `scope.len() - index`.
|
||||||
|
/// If `index` is zero, then there is no pre-calculated offset position and a search through the
|
||||||
|
/// current [`Scope`] must be performed.
|
||||||
|
///
|
||||||
|
/// * `context`: the current [evaluation context][`EvalContext`].
|
||||||
|
///
|
||||||
|
/// ## Return value
|
||||||
|
///
|
||||||
|
/// * `Ok(None)`: continue with normal variable access.
|
||||||
|
/// * `Ok(Some(Dynamic))`: the variable's value.
|
||||||
|
///
|
||||||
|
/// ## Raising errors
|
||||||
///
|
///
|
||||||
/// Return `Err(...)` if there is an error.
|
/// Return `Err(...)` if there is an error.
|
||||||
///
|
///
|
||||||
@ -2121,6 +2139,22 @@ impl Engine {
|
|||||||
/// _(internals)_ Provide a callback that will be invoked during parsing to remap certain tokens.
|
/// _(internals)_ Provide a callback that will be invoked during parsing to remap certain tokens.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
///
|
///
|
||||||
|
/// # Callback Function Signature
|
||||||
|
///
|
||||||
|
/// The callback function signature takes the following form:
|
||||||
|
///
|
||||||
|
/// > `Fn(token: Token, pos: Position, state: &TokenizeState) -> Token`
|
||||||
|
///
|
||||||
|
/// where:
|
||||||
|
/// * [`token`][crate::token::Token]: current token parsed
|
||||||
|
/// * [`pos`][`Position`]: location of the token
|
||||||
|
/// * [`state`][crate::token::TokenizeState]: current state of the tokenizer
|
||||||
|
///
|
||||||
|
/// ## Raising errors
|
||||||
|
///
|
||||||
|
/// It is possible to raise a parsing error by returning
|
||||||
|
/// [`Token::LexError`][crate::token::Token::LexError] as the mapped token.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
@ -2130,7 +2164,7 @@ impl Engine {
|
|||||||
/// let mut engine = Engine::new();
|
/// let mut engine = Engine::new();
|
||||||
///
|
///
|
||||||
/// // Register a token mapper.
|
/// // Register a token mapper.
|
||||||
/// engine.on_parse_token(|token| {
|
/// engine.on_parse_token(|token, _, _| {
|
||||||
/// match token {
|
/// match token {
|
||||||
/// // Convert all integer literals to strings
|
/// // Convert all integer literals to strings
|
||||||
/// Token::IntegerConstant(n) => Token::StringConstant(n.to_string()),
|
/// Token::IntegerConstant(n) => Token::StringConstant(n.to_string()),
|
||||||
@ -2153,8 +2187,12 @@ impl Engine {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn on_parse_token(
|
pub fn on_parse_token(
|
||||||
&mut self,
|
&mut self,
|
||||||
callback: impl Fn(crate::token::Token) -> crate::token::Token + SendSync + 'static,
|
callback: impl Fn(crate::token::Token, Position, &crate::token::TokenizeState) -> crate::token::Token
|
||||||
|
+ SendSync
|
||||||
|
+ 'static,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
|
use std::string::ParseError;
|
||||||
|
|
||||||
self.token_mapper = Some(Box::new(callback));
|
self.token_mapper = Some(Box::new(callback));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -2162,6 +2200,17 @@ impl Engine {
|
|||||||
///
|
///
|
||||||
/// Not available under `unchecked`.
|
/// Not available under `unchecked`.
|
||||||
///
|
///
|
||||||
|
/// # Callback Function Signature
|
||||||
|
///
|
||||||
|
/// The callback function signature takes the following form:
|
||||||
|
///
|
||||||
|
/// > `Fn(counter: u64) -> Option<Dynamic>`
|
||||||
|
///
|
||||||
|
/// ## Return value
|
||||||
|
///
|
||||||
|
/// * `None`: continue running the script.
|
||||||
|
/// * `Some(Dynamic)`: terminate the script with the specified exception value.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
@ -2234,6 +2283,17 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
/// Override default action of `debug` (print to stdout using [`println!`])
|
/// Override default action of `debug` (print to stdout using [`println!`])
|
||||||
///
|
///
|
||||||
|
/// # Callback Function Signature
|
||||||
|
///
|
||||||
|
/// The callback function signature passed takes the following form:
|
||||||
|
///
|
||||||
|
/// > `Fn(text: &str, source: Option<&str>, pos: Position)`
|
||||||
|
///
|
||||||
|
/// where:
|
||||||
|
/// * `text`: the text to display
|
||||||
|
/// * `source`: current source, if any
|
||||||
|
/// * [`pos`][`Position`]: location of the `debug` call
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -4,7 +4,7 @@ use crate::ast::{FnAccess, FnCallHashes};
|
|||||||
use crate::engine::Imports;
|
use crate::engine::Imports;
|
||||||
use crate::fn_call::FnCallArgs;
|
use crate::fn_call::FnCallArgs;
|
||||||
use crate::plugin::PluginFunction;
|
use crate::plugin::PluginFunction;
|
||||||
use crate::token::Token;
|
use crate::token::{Token, TokenizeState};
|
||||||
use crate::{
|
use crate::{
|
||||||
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Module, Position, RhaiResult,
|
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Module, Position, RhaiResult,
|
||||||
};
|
};
|
||||||
@ -310,10 +310,11 @@ pub type OnDebugCallback = Box<dyn Fn(&str, Option<&str>, Position) + Send + Syn
|
|||||||
|
|
||||||
/// A standard callback function for mapping tokens during parsing.
|
/// A standard callback function for mapping tokens during parsing.
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
pub type OnParseTokenCallback = dyn Fn(Token) -> Token;
|
pub type OnParseTokenCallback = dyn Fn(Token, Position, &TokenizeState) -> Token;
|
||||||
/// A standard callback function for mapping tokens during parsing.
|
/// A standard callback function for mapping tokens during parsing.
|
||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
pub type OnParseTokenCallback = dyn Fn(Token) -> Token + Send + Sync + 'static;
|
pub type OnParseTokenCallback =
|
||||||
|
dyn Fn(Token, Position, &TokenizeState) -> Token + Send + Sync + 'static;
|
||||||
|
|
||||||
/// A standard callback function for variable access.
|
/// A standard callback function for variable access.
|
||||||
#[cfg(not(feature = "sync"))]
|
#[cfg(not(feature = "sync"))]
|
||||||
|
@ -44,9 +44,7 @@ pub fn by_value<T: Variant + Clone>(data: &mut Dynamic) -> T {
|
|||||||
if TypeId::of::<T>() == TypeId::of::<&str>() {
|
if TypeId::of::<T>() == TypeId::of::<&str>() {
|
||||||
// If T is `&str`, data must be `ImmutableString`, so map directly to it
|
// If T is `&str`, data must be `ImmutableString`, so map directly to it
|
||||||
data.flatten_in_place();
|
data.flatten_in_place();
|
||||||
let ref_str = data
|
let ref_str = data.as_str_ref().expect("argument type is &str");
|
||||||
.as_str_ref()
|
|
||||||
.expect("argument passed by value is not shared");
|
|
||||||
let ref_t = unsafe { mem::transmute::<_, &T>(&ref_str) };
|
let ref_t = unsafe { mem::transmute::<_, &T>(&ref_str) };
|
||||||
ref_t.clone()
|
ref_t.clone()
|
||||||
} else if TypeId::of::<T>() == TypeId::of::<String>() {
|
} else if TypeId::of::<T>() == TypeId::of::<String>() {
|
||||||
|
@ -538,4 +538,27 @@ impl ImmutableString {
|
|||||||
pub(crate) fn make_mut(&mut self) -> &mut SmartString {
|
pub(crate) fn make_mut(&mut self) -> &mut SmartString {
|
||||||
shared_make_mut(&mut self.0)
|
shared_make_mut(&mut self.0)
|
||||||
}
|
}
|
||||||
|
/// Returns `true` if the two [`ImmutableString`]'s point to the same allocation.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use rhai::ImmutableString;
|
||||||
|
///
|
||||||
|
/// let s1: ImmutableString = "hello".into();
|
||||||
|
/// let s2 = s1.clone();
|
||||||
|
/// let s3: ImmutableString = "hello".into();
|
||||||
|
///
|
||||||
|
/// assert_eq!(s1, s2);
|
||||||
|
/// assert_eq!(s1, s3);
|
||||||
|
/// assert_eq!(s2, s3);
|
||||||
|
///
|
||||||
|
/// assert!(s1.ptr_eq(&s2));
|
||||||
|
/// assert!(!s1.ptr_eq(&s3));
|
||||||
|
/// assert!(!s2.ptr_eq(&s3));
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn ptr_eq(&self, other: &Self) -> bool {
|
||||||
|
Shared::ptr_eq(&self.0, &other.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -822,7 +822,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, chaining: bool) {
|
|||||||
// ``
|
// ``
|
||||||
Expr::InterpolatedString(x, pos) if x.is_empty() => {
|
Expr::InterpolatedString(x, pos) if x.is_empty() => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
*expr = Expr::StringConstant(state.engine.empty_string.clone(), *pos);
|
*expr = Expr::StringConstant(state.engine.const_empty_string(), *pos);
|
||||||
}
|
}
|
||||||
// `...`
|
// `...`
|
||||||
Expr::InterpolatedString(x, _) if x.len() == 1 && matches!(x[0], Expr::StringConstant(_, _)) => {
|
Expr::InterpolatedString(x, _) if x.len() == 1 && matches!(x[0], Expr::StringConstant(_, _)) => {
|
||||||
|
@ -126,7 +126,7 @@ mod string_functions {
|
|||||||
len: INT,
|
len: INT,
|
||||||
) -> ImmutableString {
|
) -> ImmutableString {
|
||||||
if string.is_empty() || len <= 0 {
|
if string.is_empty() || len <= 0 {
|
||||||
return ctx.engine().empty_string.clone();
|
return ctx.engine().const_empty_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut chars = StaticVec::<char>::with_capacity(len as usize);
|
let mut chars = StaticVec::<char>::with_capacity(len as usize);
|
||||||
@ -305,13 +305,13 @@ mod string_functions {
|
|||||||
len: INT,
|
len: INT,
|
||||||
) -> ImmutableString {
|
) -> ImmutableString {
|
||||||
if string.is_empty() {
|
if string.is_empty() {
|
||||||
return ctx.engine().empty_string.clone();
|
return ctx.engine().const_empty_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut chars = StaticVec::with_capacity(string.len());
|
let mut chars = StaticVec::with_capacity(string.len());
|
||||||
|
|
||||||
let offset = if string.is_empty() || len <= 0 {
|
let offset = if string.is_empty() || len <= 0 {
|
||||||
return ctx.engine().empty_string.clone();
|
return ctx.engine().const_empty_string();
|
||||||
} else if start < 0 {
|
} else if start < 0 {
|
||||||
if let Some(n) = start.checked_abs() {
|
if let Some(n) = start.checked_abs() {
|
||||||
chars.extend(string.chars());
|
chars.extend(string.chars());
|
||||||
@ -324,7 +324,7 @@ mod string_functions {
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
} else if start as usize >= string.chars().count() {
|
} else if start as usize >= string.chars().count() {
|
||||||
return ctx.engine().empty_string.clone();
|
return ctx.engine().const_empty_string();
|
||||||
} else {
|
} else {
|
||||||
start as usize
|
start as usize
|
||||||
};
|
};
|
||||||
@ -354,7 +354,7 @@ mod string_functions {
|
|||||||
start: INT,
|
start: INT,
|
||||||
) -> ImmutableString {
|
) -> ImmutableString {
|
||||||
if string.is_empty() {
|
if string.is_empty() {
|
||||||
ctx.engine().empty_string.clone()
|
ctx.engine().const_empty_string()
|
||||||
} else {
|
} else {
|
||||||
let len = string.len() as INT;
|
let len = string.len() as INT;
|
||||||
sub_string(ctx, string, start, len)
|
sub_string(ctx, string, start, len)
|
||||||
@ -571,14 +571,14 @@ mod string_functions {
|
|||||||
if let Some(n) = start.checked_abs() {
|
if let Some(n) = start.checked_abs() {
|
||||||
let num_chars = string.chars().count();
|
let num_chars = string.chars().count();
|
||||||
if n as usize > num_chars {
|
if n as usize > num_chars {
|
||||||
vec![ctx.engine().empty_string.clone().into(), string.into()]
|
vec![ctx.engine().const_empty_string().into(), string.into()]
|
||||||
} else {
|
} else {
|
||||||
let prefix: String = string.chars().take(num_chars - n as usize).collect();
|
let prefix: String = string.chars().take(num_chars - n as usize).collect();
|
||||||
let prefix_len = prefix.len();
|
let prefix_len = prefix.len();
|
||||||
vec![prefix.into(), string[prefix_len..].into()]
|
vec![prefix.into(), string[prefix_len..].into()]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
vec![ctx.engine().empty_string.clone().into(), string.into()]
|
vec![ctx.engine().const_empty_string().into(), string.into()]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let prefix: String = string.chars().take(start as usize).collect();
|
let prefix: String = string.chars().take(start as usize).collect();
|
||||||
|
26
src/token.rs
26
src/token.rs
@ -29,6 +29,10 @@ use rust_decimal::Decimal;
|
|||||||
use crate::engine::KEYWORD_IS_DEF_FN;
|
use crate::engine::KEYWORD_IS_DEF_FN;
|
||||||
|
|
||||||
/// _(internals)_ A type containing commands to control the tokenizer.
|
/// _(internals)_ A type containing commands to control the tokenizer.
|
||||||
|
///
|
||||||
|
/// # Volatile Data Structure
|
||||||
|
///
|
||||||
|
/// This type is volatile and may change.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy, Default)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy, Default)]
|
||||||
pub struct TokenizerControlBlock {
|
pub struct TokenizerControlBlock {
|
||||||
/// Is the current tokenizer position within an interpolated text string?
|
/// Is the current tokenizer position within an interpolated text string?
|
||||||
@ -992,7 +996,7 @@ pub struct TokenizeState {
|
|||||||
/// Maximum length of a string.
|
/// Maximum length of a string.
|
||||||
pub max_string_size: Option<NonZeroUsize>,
|
pub max_string_size: Option<NonZeroUsize>,
|
||||||
/// Can the next token be a unary operator?
|
/// Can the next token be a unary operator?
|
||||||
pub non_unary: bool,
|
pub next_token_cannot_be_unary: bool,
|
||||||
/// Is the tokenizer currently inside a block comment?
|
/// Is the tokenizer currently inside a block comment?
|
||||||
pub comment_level: usize,
|
pub comment_level: usize,
|
||||||
/// Include comments?
|
/// Include comments?
|
||||||
@ -1327,7 +1331,7 @@ pub fn get_next_token(
|
|||||||
|
|
||||||
// Save the last token's state
|
// Save the last token's state
|
||||||
if let Some((ref token, _)) = result {
|
if let Some((ref token, _)) = result {
|
||||||
state.non_unary = !token.is_next_unary();
|
state.next_token_cannot_be_unary = !token.is_next_unary();
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
@ -1678,10 +1682,12 @@ fn get_next_token_inner(
|
|||||||
eat_next(stream, pos);
|
eat_next(stream, pos);
|
||||||
return Some((Token::Reserved("++".into()), start_pos));
|
return Some((Token::Reserved("++".into()), start_pos));
|
||||||
}
|
}
|
||||||
('+', _) if !state.non_unary => return Some((Token::UnaryPlus, start_pos)),
|
('+', _) if !state.next_token_cannot_be_unary => {
|
||||||
|
return Some((Token::UnaryPlus, start_pos))
|
||||||
|
}
|
||||||
('+', _) => return Some((Token::Plus, start_pos)),
|
('+', _) => return Some((Token::Plus, start_pos)),
|
||||||
|
|
||||||
('-', '0'..='9') if !state.non_unary => negated = Some(start_pos),
|
('-', '0'..='9') if !state.next_token_cannot_be_unary => negated = Some(start_pos),
|
||||||
('-', '0'..='9') => return Some((Token::Minus, start_pos)),
|
('-', '0'..='9') => return Some((Token::Minus, start_pos)),
|
||||||
('-', '=') => {
|
('-', '=') => {
|
||||||
eat_next(stream, pos);
|
eat_next(stream, pos);
|
||||||
@ -1695,7 +1701,9 @@ fn get_next_token_inner(
|
|||||||
eat_next(stream, pos);
|
eat_next(stream, pos);
|
||||||
return Some((Token::Reserved("--".into()), start_pos));
|
return Some((Token::Reserved("--".into()), start_pos));
|
||||||
}
|
}
|
||||||
('-', _) if !state.non_unary => return Some((Token::UnaryMinus, start_pos)),
|
('-', _) if !state.next_token_cannot_be_unary => {
|
||||||
|
return Some((Token::UnaryMinus, start_pos))
|
||||||
|
}
|
||||||
('-', _) => return Some((Token::Minus, start_pos)),
|
('-', _) => return Some((Token::Minus, start_pos)),
|
||||||
|
|
||||||
('*', ')') => {
|
('*', ')') => {
|
||||||
@ -2117,6 +2125,10 @@ impl InputStream for MultiInputsStream<'_> {
|
|||||||
|
|
||||||
/// _(internals)_ An iterator on a [`Token`] stream.
|
/// _(internals)_ An iterator on a [`Token`] stream.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
|
///
|
||||||
|
/// # Volatile Data Structure
|
||||||
|
///
|
||||||
|
/// This type is volatile and may change.
|
||||||
pub struct TokenIterator<'a> {
|
pub struct TokenIterator<'a> {
|
||||||
/// Reference to the scripting `Engine`.
|
/// Reference to the scripting `Engine`.
|
||||||
pub engine: &'a Engine,
|
pub engine: &'a Engine,
|
||||||
@ -2224,7 +2236,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
|||||||
|
|
||||||
// Run the mapper, if any
|
// Run the mapper, if any
|
||||||
let token = if let Some(map_func) = self.token_mapper {
|
let token = if let Some(map_func) = self.token_mapper {
|
||||||
map_func(token)
|
map_func(token, pos, &self.state)
|
||||||
} else {
|
} else {
|
||||||
token
|
token
|
||||||
};
|
};
|
||||||
@ -2278,7 +2290,7 @@ impl Engine {
|
|||||||
max_string_size: self.limits.max_string_size,
|
max_string_size: self.limits.max_string_size,
|
||||||
#[cfg(feature = "unchecked")]
|
#[cfg(feature = "unchecked")]
|
||||||
max_string_size: None,
|
max_string_size: None,
|
||||||
non_unary: false,
|
next_token_cannot_be_unary: false,
|
||||||
comment_level: 0,
|
comment_level: 0,
|
||||||
include_comments: false,
|
include_comments: false,
|
||||||
is_within_text_terminated_by: None,
|
is_within_text_terminated_by: None,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#![cfg(not(feature = "no_object"))]
|
#![cfg(not(feature = "no_object"))]
|
||||||
|
|
||||||
use rhai::{Engine, EvalAltResult, INT};
|
use rhai::{Engine, EvalAltResult, Scope, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_set() -> Result<(), Box<EvalAltResult>> {
|
fn test_get_set() -> Result<(), Box<EvalAltResult>> {
|
||||||
@ -70,7 +70,7 @@ fn test_get_set() -> Result<(), Box<EvalAltResult>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_set_chain() -> Result<(), Box<EvalAltResult>> {
|
fn test_get_set_chain_with_write_back() -> Result<(), Box<EvalAltResult>> {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct TestChild {
|
struct TestChild {
|
||||||
x: INT,
|
x: INT,
|
||||||
@ -119,11 +119,22 @@ fn test_get_set_chain() -> Result<(), Box<EvalAltResult>> {
|
|||||||
engine.register_get_set("x", TestChild::get_x, TestChild::set_x);
|
engine.register_get_set("x", TestChild::get_x, TestChild::set_x);
|
||||||
engine.register_get_set("child", TestParent::get_child, TestParent::set_child);
|
engine.register_get_set("child", TestParent::get_child, TestParent::set_child);
|
||||||
|
|
||||||
engine.register_fn("new_tp", TestParent::new);
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
engine.register_indexer_get_set(
|
||||||
|
|parent: &mut TestParent, _: INT| parent.child.clone(),
|
||||||
|
|parent: &mut TestParent, n: INT, mut new_child: TestChild| {
|
||||||
|
new_child.x *= n;
|
||||||
|
parent.child = new_child;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
engine.register_fn("new_tp", TestParent::new);
|
||||||
|
engine.register_fn("new_tc", TestChild::new);
|
||||||
|
|
||||||
|
assert_eq!(engine.eval::<INT>("let a = new_tp(); a.child.x")?, 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>("let a = new_tp(); a.child.x = 500; a.child.x")?,
|
engine.eval::<INT>("let a = new_tp(); a.child.x = 42; a.child.x")?,
|
||||||
500
|
42
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -131,6 +142,18 @@ fn test_get_set_chain() -> Result<(), Box<EvalAltResult>> {
|
|||||||
"TestParent"
|
"TestParent"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>("let a = new_tp(); let c = new_tc(); c.x = 123; a[2] = c; a.child.x")?,
|
||||||
|
246
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval::<INT>("let a = new_tp(); a[2].x = 42; a.child.x")?,
|
||||||
|
84
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,3 +189,68 @@ fn test_get_set_op_assignment() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_set_chain_without_write_back() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Outer {
|
||||||
|
pub inner: Inner,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Inner {
|
||||||
|
pub value: INT,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
|
||||||
|
scope.push(
|
||||||
|
"outer",
|
||||||
|
Outer {
|
||||||
|
inner: Inner { value: 42 },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
engine
|
||||||
|
.register_type::<Inner>()
|
||||||
|
.register_get_set(
|
||||||
|
"value",
|
||||||
|
|t: &mut Inner| t.value,
|
||||||
|
|_: &mut Inner, new: INT| panic!("Inner::value setter called with {}", new),
|
||||||
|
)
|
||||||
|
.register_type::<Outer>()
|
||||||
|
.register_get_set(
|
||||||
|
"inner",
|
||||||
|
|t: &mut Outer| t.inner.clone(),
|
||||||
|
|_: &mut Outer, new: Inner| panic!("Outer::inner setter called with {:?}", new),
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
engine.register_indexer_get_set(
|
||||||
|
|t: &mut Outer, n: INT| Inner {
|
||||||
|
value: t.inner.value * n,
|
||||||
|
},
|
||||||
|
|_: &mut Outer, n: INT, new: Inner| {
|
||||||
|
panic!("Outer::inner index setter called with {} and {:?}", n, new)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval_with_scope::<INT>(&mut scope, "outer.inner.value")?,
|
||||||
|
42
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
assert_eq!(
|
||||||
|
engine.eval_with_scope::<INT>(&mut scope, "outer[2].value")?,
|
||||||
|
84
|
||||||
|
);
|
||||||
|
|
||||||
|
engine.run_with_scope(&mut scope, "print(outer.inner.value)")?;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
engine.run_with_scope(&mut scope, "print(outer[0].value)")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user