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::register_type_XXX` are now available even under `no_object`.
|
||||
* 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
|
||||
|
||||
@ -42,6 +43,7 @@ Enhancements
|
||||
|
||||
* `SmartString` now uses `LazyCompact` instead of `Compact` to minimize allocations.
|
||||
* Added `pop` for strings.
|
||||
* Added `ImmutableString::ptr_eq` to test if two strings point to the same allocation.
|
||||
|
||||
### `Scope` API
|
||||
|
||||
@ -57,9 +59,22 @@ Enhancements
|
||||
* `StaticVec` is changed to keep three items inline instead of four.
|
||||
|
||||
|
||||
Version 1.0.7
|
||||
=============
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
|
@ -84,6 +84,7 @@ Below is the standard _Fibonacci_ example for scripting languages:
|
||||
|
||||
const TARGET = 28;
|
||||
const REPEAT = 5;
|
||||
const ANSWER = 317_811;
|
||||
|
||||
fn fib(n) {
|
||||
if n < 2 {
|
||||
@ -107,8 +108,8 @@ print(`Finished. Run time = ${now.elapsed} seconds.`);
|
||||
|
||||
print(`Fibonacci number #${TARGET} = ${result}`);
|
||||
|
||||
if result != 317_811 {
|
||||
print("The answer is WRONG! Should be 317,811!");
|
||||
if result != ANSWER {
|
||||
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
|
||||
|
|
||||
11 | use core::fmt::Pointer;
|
||||
|
|
||||
11 | use std::fmt::Pointer;
|
||||
|
|
||||
11 | use syn::__private::fmt::Pointer;
|
||||
|
|
||||
11 | use core::fmt::Pointer;
|
||||
|
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
const TARGET = 28;
|
||||
const REPEAT = 5;
|
||||
const ANSWER = 317_811;
|
||||
|
||||
fn fib(n) {
|
||||
if n < 2 {
|
||||
@ -26,6 +27,6 @@ print(`Finished. Run time = ${now.elapsed} seconds.`);
|
||||
|
||||
print(`Fibonacci number #${TARGET} = ${result}`);
|
||||
|
||||
if result != 317_811 {
|
||||
print("The answer is WRONG! Should be 317,811!");
|
||||
if result != ANSWER {
|
||||
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`].
|
||||
/// 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(
|
||||
&mut self,
|
||||
key: impl Into<Identifier>,
|
||||
|
@ -1877,7 +1877,7 @@ impl Dynamic {
|
||||
_ => 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.
|
||||
///
|
||||
/// # Panics
|
||||
@ -1888,7 +1888,7 @@ impl Dynamic {
|
||||
match self.0 {
|
||||
Union::Str(ref s, _, _) => Ok(s),
|
||||
#[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()),
|
||||
}
|
||||
}
|
||||
|
157
src/engine.rs
157
src/engine.rs
@ -3,7 +3,7 @@
|
||||
use crate::ast::{Expr, FnCallExpr, Ident, OpAssignment, Stmt, AST_OPTION_FLAGS::*};
|
||||
use crate::custom_syntax::CustomSyntax;
|
||||
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::{
|
||||
CallableFunction, IteratorFn, OnDebugCallback, OnParseTokenCallback, OnPrintCallback,
|
||||
OnVarCallback,
|
||||
@ -336,6 +336,20 @@ impl ChainArgument {
|
||||
_ => 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"))]
|
||||
@ -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.
|
||||
#[derive(Debug)]
|
||||
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.
|
||||
pub(crate) type_names: BTreeMap<Identifier, Box<Identifier>>,
|
||||
|
||||
/// An empty [`ImmutableString`] for cloning purposes.
|
||||
pub(crate) empty_string: ImmutableString,
|
||||
/// Useful constants
|
||||
pub(crate) constants: GlobalConstants,
|
||||
|
||||
/// A set of symbols to disable.
|
||||
pub(crate) disabled_symbols: BTreeSet<Identifier>,
|
||||
@ -1042,7 +1081,7 @@ impl Engine {
|
||||
module_resolver: None,
|
||||
|
||||
type_names: Default::default(),
|
||||
empty_string: Default::default(),
|
||||
constants: Default::default(),
|
||||
disabled_symbols: Default::default(),
|
||||
custom_keywords: Default::default(),
|
||||
custom_syntax: Default::default(),
|
||||
@ -1070,6 +1109,16 @@ impl 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.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
@ -1244,6 +1293,7 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
ChainType::Indexing => {
|
||||
let pos = rhs.position();
|
||||
let root_pos = idx_val.position();
|
||||
let idx_val = idx_val
|
||||
.into_index_value()
|
||||
.expect("`chain_type` is `ChainType::Index`");
|
||||
@ -1253,17 +1303,50 @@ impl Engine {
|
||||
Expr::Dot(x, term, x_pos) | Expr::Index(x, term, x_pos)
|
||||
if !_terminate_chaining =>
|
||||
{
|
||||
let mut idx_val_for_setter = idx_val.clone();
|
||||
let idx_pos = x.lhs.position();
|
||||
let obj_ptr = &mut self.get_indexed_mut(
|
||||
mods, state, lib, target, idx_val, idx_pos, false, true, level,
|
||||
)?;
|
||||
|
||||
let rhs_chain = match_chaining_type(rhs);
|
||||
self.eval_dot_index_chain_helper(
|
||||
mods, state, lib, this_ptr, obj_ptr, root, &x.rhs, *term, idx_values,
|
||||
rhs_chain, level, new_val,
|
||||
)
|
||||
.map_err(|err| err.fill_position(*x_pos))
|
||||
|
||||
let (try_setter, result) = {
|
||||
let mut obj = self.get_indexed_mut(
|
||||
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;
|
||||
|
||||
match self.eval_dot_index_chain_helper(
|
||||
mods, state, lib, this_ptr, obj_ptr, root, &x.rhs, *term,
|
||||
idx_values, rhs_chain, level, new_val,
|
||||
) {
|
||||
Ok((result, true)) if is_obj_temp_val => {
|
||||
(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
|
||||
_ if new_val.is_some() => {
|
||||
@ -1294,13 +1377,12 @@ impl Engine {
|
||||
if let Some(mut new_val) = try_setter {
|
||||
// Try to call index setter
|
||||
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 pos = Position::NONE;
|
||||
|
||||
self.exec_fn_call(
|
||||
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(_, _) => {
|
||||
let args = &mut [target, &mut name.into(), &mut new_val];
|
||||
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;
|
||||
|
||||
self.exec_fn_call(
|
||||
@ -1471,6 +1553,7 @@ impl Engine {
|
||||
}
|
||||
_ => Err(err),
|
||||
},
|
||||
// Assume getters are always pure
|
||||
|(v, _)| Ok((v, false)),
|
||||
)
|
||||
}
|
||||
@ -1524,7 +1607,9 @@ impl Engine {
|
||||
let hash_set = FnCallHashes::from_native(*hash_set);
|
||||
let mut arg_values = [target.as_mut(), &mut Default::default()];
|
||||
let args = &mut arg_values[..1];
|
||||
let (mut val, updated) = self
|
||||
|
||||
// Assume getters are always pure
|
||||
let (mut val, _) = self
|
||||
.exec_fn_call(
|
||||
mods, state, lib, getter, hash_get, args, is_ref_mut, true,
|
||||
*pos, None, level,
|
||||
@ -1568,7 +1653,7 @@ impl Engine {
|
||||
.map_err(|err| err.fill_position(*x_pos))?;
|
||||
|
||||
// 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
|
||||
let mut arg_values = [target.as_mut(), val];
|
||||
let args = &mut arg_values;
|
||||
@ -1583,7 +1668,7 @@ impl Engine {
|
||||
let args =
|
||||
&mut [target.as_mut(), &mut name.into(), val];
|
||||
let hash_set = FnCallHashes::from_native(
|
||||
crate::calc_fn_hash(FN_IDX_SET, 3),
|
||||
self.constants.fn_hash_idx_set,
|
||||
);
|
||||
self.exec_fn_call(
|
||||
mods, state, lib, FN_IDX_SET, hash_set, args,
|
||||
@ -1987,7 +2072,7 @@ impl Engine {
|
||||
|
||||
_ if use_indexers => {
|
||||
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;
|
||||
|
||||
self.exec_fn_call(
|
||||
@ -2059,7 +2144,7 @@ impl Engine {
|
||||
// `... ${...} ...`
|
||||
Expr::InterpolatedString(x, 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() {
|
||||
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];
|
||||
|
||||
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`
|
||||
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.
|
||||
#[cfg(feature = "unchecked")]
|
||||
#[inline(always)]
|
||||
fn check_return_value(&self, result: RhaiResult) -> RhaiResult {
|
||||
result
|
||||
}
|
||||
fn check_return_value(&self, mut result: RhaiResult) -> RhaiResult {
|
||||
if let Ok(ref mut r) = result {
|
||||
// Concentrate all empty strings into one instance to save memory
|
||||
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"))]
|
||||
#[inline(always)]
|
||||
fn check_return_value(&self, result: RhaiResult) -> RhaiResult {
|
||||
result.and_then(|r| self.check_data_size(&r).map(|_| r))
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
self.check_data_size(&r)?;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(feature = "unchecked")]
|
||||
|
@ -1368,15 +1368,17 @@ impl Engine {
|
||||
};
|
||||
let (stream, tokenizer_control) = engine.lex_raw(
|
||||
&scripts,
|
||||
Some(if has_null {
|
||||
&|token| match token {
|
||||
// If `null` is present, make sure `null` is treated as a variable
|
||||
Token::Reserved(s) if s == "null" => Token::Identifier(s),
|
||||
_ => token,
|
||||
}
|
||||
if has_null {
|
||||
Some(&|token, _, _| {
|
||||
match token {
|
||||
// If `null` is present, make sure `null` is treated as a variable
|
||||
Token::Reserved(s) if s == "null" => Token::Identifier(s),
|
||||
_ => token,
|
||||
}
|
||||
})
|
||||
} else {
|
||||
&|t| t
|
||||
}),
|
||||
None
|
||||
},
|
||||
);
|
||||
let mut state = ParseState::new(engine, tokenizer_control);
|
||||
let ast = engine.parse_global_expr(
|
||||
@ -2078,12 +2080,28 @@ impl Engine {
|
||||
}
|
||||
/// 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.
|
||||
/// Return `Ok(Some(Dynamic))` as the variable's value.
|
||||
/// The callback function signature takes the following form:
|
||||
///
|
||||
/// # 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.
|
||||
///
|
||||
@ -2121,6 +2139,22 @@ impl Engine {
|
||||
/// _(internals)_ Provide a callback that will be invoked during parsing to remap certain tokens.
|
||||
/// 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
|
||||
///
|
||||
/// ```
|
||||
@ -2130,7 +2164,7 @@ impl Engine {
|
||||
/// let mut engine = Engine::new();
|
||||
///
|
||||
/// // Register a token mapper.
|
||||
/// engine.on_parse_token(|token| {
|
||||
/// engine.on_parse_token(|token, _, _| {
|
||||
/// match token {
|
||||
/// // Convert all integer literals to strings
|
||||
/// Token::IntegerConstant(n) => Token::StringConstant(n.to_string()),
|
||||
@ -2153,8 +2187,12 @@ impl Engine {
|
||||
#[inline(always)]
|
||||
pub fn on_parse_token(
|
||||
&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 {
|
||||
use std::string::ParseError;
|
||||
|
||||
self.token_mapper = Some(Box::new(callback));
|
||||
self
|
||||
}
|
||||
@ -2162,6 +2200,17 @@ impl Engine {
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
/// ```
|
||||
@ -2234,6 +2283,17 @@ impl Engine {
|
||||
}
|
||||
/// 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
|
||||
///
|
||||
/// ```
|
||||
|
@ -4,7 +4,7 @@ use crate::ast::{FnAccess, FnCallHashes};
|
||||
use crate::engine::Imports;
|
||||
use crate::fn_call::FnCallArgs;
|
||||
use crate::plugin::PluginFunction;
|
||||
use crate::token::Token;
|
||||
use crate::token::{Token, TokenizeState};
|
||||
use crate::{
|
||||
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.
|
||||
#[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.
|
||||
#[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.
|
||||
#[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 T is `&str`, data must be `ImmutableString`, so map directly to it
|
||||
data.flatten_in_place();
|
||||
let ref_str = data
|
||||
.as_str_ref()
|
||||
.expect("argument passed by value is not shared");
|
||||
let ref_str = data.as_str_ref().expect("argument type is &str");
|
||||
let ref_t = unsafe { mem::transmute::<_, &T>(&ref_str) };
|
||||
ref_t.clone()
|
||||
} else if TypeId::of::<T>() == TypeId::of::<String>() {
|
||||
|
@ -538,4 +538,27 @@ impl ImmutableString {
|
||||
pub(crate) fn make_mut(&mut self) -> &mut SmartString {
|
||||
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() => {
|
||||
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(_, _)) => {
|
||||
|
@ -126,7 +126,7 @@ mod string_functions {
|
||||
len: INT,
|
||||
) -> ImmutableString {
|
||||
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);
|
||||
@ -305,13 +305,13 @@ mod string_functions {
|
||||
len: INT,
|
||||
) -> ImmutableString {
|
||||
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 offset = if string.is_empty() || len <= 0 {
|
||||
return ctx.engine().empty_string.clone();
|
||||
return ctx.engine().const_empty_string();
|
||||
} else if start < 0 {
|
||||
if let Some(n) = start.checked_abs() {
|
||||
chars.extend(string.chars());
|
||||
@ -324,7 +324,7 @@ mod string_functions {
|
||||
0
|
||||
}
|
||||
} else if start as usize >= string.chars().count() {
|
||||
return ctx.engine().empty_string.clone();
|
||||
return ctx.engine().const_empty_string();
|
||||
} else {
|
||||
start as usize
|
||||
};
|
||||
@ -354,7 +354,7 @@ mod string_functions {
|
||||
start: INT,
|
||||
) -> ImmutableString {
|
||||
if string.is_empty() {
|
||||
ctx.engine().empty_string.clone()
|
||||
ctx.engine().const_empty_string()
|
||||
} else {
|
||||
let len = string.len() as INT;
|
||||
sub_string(ctx, string, start, len)
|
||||
@ -571,14 +571,14 @@ mod string_functions {
|
||||
if let Some(n) = start.checked_abs() {
|
||||
let num_chars = string.chars().count();
|
||||
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 {
|
||||
let prefix: String = string.chars().take(num_chars - n as usize).collect();
|
||||
let prefix_len = prefix.len();
|
||||
vec![prefix.into(), string[prefix_len..].into()]
|
||||
}
|
||||
} else {
|
||||
vec![ctx.engine().empty_string.clone().into(), string.into()]
|
||||
vec![ctx.engine().const_empty_string().into(), string.into()]
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
|
||||
/// _(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)]
|
||||
pub struct TokenizerControlBlock {
|
||||
/// Is the current tokenizer position within an interpolated text string?
|
||||
@ -992,7 +996,7 @@ pub struct TokenizeState {
|
||||
/// Maximum length of a string.
|
||||
pub max_string_size: Option<NonZeroUsize>,
|
||||
/// 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?
|
||||
pub comment_level: usize,
|
||||
/// Include comments?
|
||||
@ -1327,7 +1331,7 @@ pub fn get_next_token(
|
||||
|
||||
// Save the last token's state
|
||||
if let Some((ref token, _)) = result {
|
||||
state.non_unary = !token.is_next_unary();
|
||||
state.next_token_cannot_be_unary = !token.is_next_unary();
|
||||
}
|
||||
|
||||
result
|
||||
@ -1678,10 +1682,12 @@ fn get_next_token_inner(
|
||||
eat_next(stream, 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)),
|
||||
|
||||
('-', '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)),
|
||||
('-', '=') => {
|
||||
eat_next(stream, pos);
|
||||
@ -1695,7 +1701,9 @@ fn get_next_token_inner(
|
||||
eat_next(stream, 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)),
|
||||
|
||||
('*', ')') => {
|
||||
@ -2117,6 +2125,10 @@ impl InputStream for MultiInputsStream<'_> {
|
||||
|
||||
/// _(internals)_ An iterator on a [`Token`] stream.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// # Volatile Data Structure
|
||||
///
|
||||
/// This type is volatile and may change.
|
||||
pub struct TokenIterator<'a> {
|
||||
/// Reference to the scripting `Engine`.
|
||||
pub engine: &'a Engine,
|
||||
@ -2224,7 +2236,7 @@ impl<'a> Iterator for TokenIterator<'a> {
|
||||
|
||||
// Run the mapper, if any
|
||||
let token = if let Some(map_func) = self.token_mapper {
|
||||
map_func(token)
|
||||
map_func(token, pos, &self.state)
|
||||
} else {
|
||||
token
|
||||
};
|
||||
@ -2278,7 +2290,7 @@ impl Engine {
|
||||
max_string_size: self.limits.max_string_size,
|
||||
#[cfg(feature = "unchecked")]
|
||||
max_string_size: None,
|
||||
non_unary: false,
|
||||
next_token_cannot_be_unary: false,
|
||||
comment_level: 0,
|
||||
include_comments: false,
|
||||
is_within_text_terminated_by: None,
|
||||
|
@ -1,6 +1,6 @@
|
||||
#![cfg(not(feature = "no_object"))]
|
||||
|
||||
use rhai::{Engine, EvalAltResult, INT};
|
||||
use rhai::{Engine, EvalAltResult, Scope, INT};
|
||||
|
||||
#[test]
|
||||
fn test_get_set() -> Result<(), Box<EvalAltResult>> {
|
||||
@ -70,7 +70,7 @@ fn test_get_set() -> Result<(), Box<EvalAltResult>> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_set_chain() -> Result<(), Box<EvalAltResult>> {
|
||||
fn test_get_set_chain_with_write_back() -> Result<(), Box<EvalAltResult>> {
|
||||
#[derive(Clone)]
|
||||
struct TestChild {
|
||||
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("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!(
|
||||
engine.eval::<INT>("let a = new_tp(); a.child.x = 500; a.child.x")?,
|
||||
500
|
||||
engine.eval::<INT>("let a = new_tp(); a.child.x = 42; a.child.x")?,
|
||||
42
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@ -131,6 +142,18 @@ fn test_get_set_chain() -> Result<(), Box<EvalAltResult>> {
|
||||
"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(())
|
||||
}
|
||||
|
||||
@ -166,3 +189,68 @@ fn test_get_set_op_assignment() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
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…
Reference in New Issue
Block a user