Merge pull request #413 from schungx/master
Enhancements and bug fixes.
This commit is contained in:
commit
5c00b89568
30
CHANGELOG.md
30
CHANGELOG.md
@ -1,6 +1,36 @@
|
||||
Rhai Release Notes
|
||||
==================
|
||||
|
||||
Version 0.20.2
|
||||
==============
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
* Constant propagation during optimization for constants held in a custom scope now works properly instead of always replacing by `()`.
|
||||
|
||||
Breaking changes
|
||||
----------------
|
||||
|
||||
* `Engine::disable_doc_comments` is removed because doc-comments are now placed under the `metadata` feature flag.
|
||||
* Registering a custom syntax now only requires specifying whether the `Scope` is adjusted (i.e. whether variables are added or removed). There is no need to specify the number of variables added/removed.
|
||||
* Assigning to a property of a constant is now allowed and no longer raise an `EvalAltResult::ErrorAssignmentToConstant` error. This is to facilitate the Singleton pattern. Registered setter functions are automatically guarded against setters calling on constants and will continue to raise errors unless the `pure` attribute is present (for plugins).
|
||||
|
||||
New features
|
||||
------------
|
||||
|
||||
* Each `Dynamic` value can now contain arbitrary data (type `i16`) in the form of a _tag_. This is to use up otherwise wasted space in the `Dynamic` type.
|
||||
* A new internal feature `no_smartstring` to turn off `SmartString` for those rare cases that it is needed.
|
||||
* `DynamicReadLock` and `DynamicWriteLoc` are exposed under `internals`.
|
||||
* `From<Shared<Locked<Dynamic>>>` is added for `Dynamic` mapping directly to a shared value, together with support for `Dynamic::from`.
|
||||
|
||||
Enhancements
|
||||
------------
|
||||
|
||||
* Registering a custom syntax now only requires specifying whether the `Scope` is adjusted (i.e. whether variables are added or removed). This allows more flexibility for cases where the number of new variables declared depends on internal logic.
|
||||
* Putting a `pure` attribute on a plugin property/index setter now enables it to be used on constants.
|
||||
|
||||
|
||||
Version 0.20.1
|
||||
==============
|
||||
|
||||
|
10
Cargo.toml
10
Cargo.toml
@ -3,7 +3,7 @@ members = [".", "codegen"]
|
||||
|
||||
[package]
|
||||
name = "rhai"
|
||||
version = "0.20.1"
|
||||
version = "0.20.2"
|
||||
edition = "2018"
|
||||
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
|
||||
description = "Embedded scripting for Rust"
|
||||
@ -32,7 +32,7 @@ no_float = [] # no floating-point
|
||||
f32_float = [] # set FLOAT=f32
|
||||
only_i32 = [] # set INT=i32 (useful for 32-bit systems)
|
||||
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types
|
||||
decimal = ["rust_decimal"] # add the Decimal number type
|
||||
decimal = ["rust_decimal/std"] # add the Decimal number type
|
||||
no_index = [] # no arrays and indexing
|
||||
no_object = [] # no custom objects
|
||||
no_function = ["no_closure"] # no script-defined functions (meaning no closures)
|
||||
@ -48,6 +48,9 @@ no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "ahash/compi
|
||||
wasm-bindgen = ["instant/wasm-bindgen"]
|
||||
stdweb = ["instant/stdweb"]
|
||||
|
||||
# internal feature flags - volatile
|
||||
no_smartstring = [] # Do not use SmartString
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
@ -89,8 +92,9 @@ default_features = false
|
||||
optional = true
|
||||
|
||||
[dependencies.rust_decimal]
|
||||
version = "1.11"
|
||||
version = "1.13"
|
||||
default_features = false
|
||||
features = ["maths"]
|
||||
optional = true
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "rhai_codegen"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
edition = "2018"
|
||||
authors = ["jhwgh1968", "Stephen Chung"]
|
||||
description = "Procedural macros support package for Rhai, a scripting language and engine for Rust"
|
||||
|
@ -541,8 +541,10 @@ impl ExportedFn {
|
||||
"property setter requires exactly 2 parameters",
|
||||
))
|
||||
}
|
||||
// 3b. Property setters must return nothing.
|
||||
FnSpecialAccess::Property(Property::Set(_)) if self.return_type().is_some() => {
|
||||
// 3b. Non-raw property setters must return nothing.
|
||||
FnSpecialAccess::Property(Property::Set(_))
|
||||
if params.return_raw.is_none() && self.return_type().is_some() =>
|
||||
{
|
||||
return Err(syn::Error::new(
|
||||
self.signature.output.span(),
|
||||
"property setter cannot return any value",
|
||||
@ -569,8 +571,10 @@ impl ExportedFn {
|
||||
"index setter requires exactly 3 parameters",
|
||||
))
|
||||
}
|
||||
// 5b. Index setters must return nothing.
|
||||
FnSpecialAccess::Index(Index::Set) if self.return_type().is_some() => {
|
||||
// 5b. Non-raw index setters must return nothing.
|
||||
FnSpecialAccess::Index(Index::Set)
|
||||
if params.return_raw.is_none() && self.return_type().is_some() =>
|
||||
{
|
||||
return Err(syn::Error::new(
|
||||
self.signature.output.span(),
|
||||
"index setter cannot return any value",
|
||||
@ -693,9 +697,7 @@ impl ExportedFn {
|
||||
unpack_statements.push(
|
||||
syn::parse2::<syn::Stmt>(quote! {
|
||||
if args[0usize].is_read_only() {
|
||||
return Err(Box::new(
|
||||
EvalAltResult::ErrorAssignmentToConstant(#arg_lit_str.to_string(), Position::NONE)
|
||||
));
|
||||
return EvalAltResult::ErrorAssignmentToConstant(#arg_lit_str.to_string(), Position::NONE).into();
|
||||
}
|
||||
})
|
||||
.unwrap(),
|
||||
|
@ -492,9 +492,7 @@ mod generate_tests {
|
||||
#[inline(always)]
|
||||
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
|
||||
if args[0usize].is_read_only() {
|
||||
return Err(Box::new(
|
||||
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
|
||||
));
|
||||
return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
|
||||
}
|
||||
let arg1 = mem::take(args[1usize]).cast::<usize>();
|
||||
let arg0 = &mut args[0usize].write_lock::<usize>().unwrap();
|
||||
|
@ -1107,9 +1107,7 @@ mod generate_tests {
|
||||
#[inline(always)]
|
||||
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
|
||||
if args[0usize].is_read_only() {
|
||||
return Err(Box::new(
|
||||
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
|
||||
));
|
||||
return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
|
||||
}
|
||||
let arg0 = &mut args[0usize].write_lock::<FLOAT>().unwrap();
|
||||
Ok(Dynamic::from(increment(arg0)))
|
||||
@ -1169,9 +1167,7 @@ mod generate_tests {
|
||||
#[inline(always)]
|
||||
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
|
||||
if args[0usize].is_read_only() {
|
||||
return Err(Box::new(
|
||||
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
|
||||
));
|
||||
return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
|
||||
}
|
||||
let arg0 = &mut args[0usize].write_lock::<FLOAT>().unwrap();
|
||||
Ok(Dynamic::from(increment(arg0)))
|
||||
@ -1252,9 +1248,7 @@ mod generate_tests {
|
||||
#[inline(always)]
|
||||
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
|
||||
if args[0usize].is_read_only() {
|
||||
return Err(Box::new(
|
||||
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
|
||||
));
|
||||
return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
|
||||
}
|
||||
let arg0 = &mut args[0usize].write_lock::<FLOAT>().unwrap();
|
||||
Ok(Dynamic::from(increment(arg0)))
|
||||
@ -1336,9 +1330,7 @@ mod generate_tests {
|
||||
#[inline(always)]
|
||||
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
|
||||
if args[0usize].is_read_only() {
|
||||
return Err(Box::new(
|
||||
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
|
||||
));
|
||||
return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
|
||||
}
|
||||
let arg0 = &mut args[0usize].write_lock::<u64>().unwrap();
|
||||
Ok(Dynamic::from(int_foo(arg0)))
|
||||
@ -1399,9 +1391,7 @@ mod generate_tests {
|
||||
#[inline(always)]
|
||||
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
|
||||
if args[0usize].is_read_only() {
|
||||
return Err(Box::new(
|
||||
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
|
||||
));
|
||||
return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
|
||||
}
|
||||
let arg0 = &mut args[0usize].write_lock::<u64>().unwrap();
|
||||
Ok(Dynamic::from(int_foo(arg0)))
|
||||
@ -1459,9 +1449,7 @@ mod generate_tests {
|
||||
#[inline(always)]
|
||||
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
|
||||
if args[0usize].is_read_only() {
|
||||
return Err(Box::new(
|
||||
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
|
||||
));
|
||||
return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
|
||||
}
|
||||
let arg1 = mem::take(args[1usize]).cast::<u64>();
|
||||
let arg0 = &mut args[0usize].write_lock::<u64>().unwrap();
|
||||
@ -1523,9 +1511,7 @@ mod generate_tests {
|
||||
#[inline(always)]
|
||||
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
|
||||
if args[0usize].is_read_only() {
|
||||
return Err(Box::new(
|
||||
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
|
||||
));
|
||||
return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
|
||||
}
|
||||
let arg1 = mem::take(args[1usize]).cast::<u64>();
|
||||
let arg0 = &mut args[0usize].write_lock::<u64>().unwrap();
|
||||
@ -1584,9 +1570,7 @@ mod generate_tests {
|
||||
#[inline(always)]
|
||||
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
|
||||
if args[0usize].is_read_only() {
|
||||
return Err(Box::new(
|
||||
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
|
||||
));
|
||||
return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
|
||||
}
|
||||
let arg1 = mem::take(args[1usize]).cast::<u64>();
|
||||
let arg0 = &mut args[0usize].write_lock::<MyCollection>().unwrap();
|
||||
@ -1648,9 +1632,7 @@ mod generate_tests {
|
||||
#[inline(always)]
|
||||
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
|
||||
if args[0usize].is_read_only() {
|
||||
return Err(Box::new(
|
||||
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
|
||||
));
|
||||
return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
|
||||
}
|
||||
let arg1 = mem::take(args[1usize]).cast::<u64>();
|
||||
let arg0 = &mut args[0usize].write_lock::<MyCollection>().unwrap();
|
||||
@ -1709,9 +1691,7 @@ mod generate_tests {
|
||||
#[inline(always)]
|
||||
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
|
||||
if args[0usize].is_read_only() {
|
||||
return Err(Box::new(
|
||||
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
|
||||
));
|
||||
return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
|
||||
}
|
||||
let arg1 = mem::take(args[1usize]).cast::<u64>();
|
||||
let arg2 = mem::take(args[2usize]).cast::<FLOAT>();
|
||||
@ -1774,9 +1754,7 @@ mod generate_tests {
|
||||
#[inline(always)]
|
||||
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
|
||||
if args[0usize].is_read_only() {
|
||||
return Err(Box::new(
|
||||
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
|
||||
));
|
||||
return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
|
||||
}
|
||||
let arg1 = mem::take(args[1usize]).cast::<u64>();
|
||||
let arg2 = mem::take(args[2usize]).cast::<FLOAT>();
|
||||
|
@ -39,4 +39,4 @@ lto = "fat"
|
||||
|
||||
[patch.crates-io]
|
||||
# Patch smartstring wth a PR fix because it doesn't properly handle no-std builds.
|
||||
smartstring = { git = "https://github.com/okready/smartstring", branch = "fix-no_std-builds" }
|
||||
smartstring = { git = "https://github.com/rhaiscript/smartstring" }
|
||||
|
109
src/ast.rs
109
src/ast.rs
@ -14,6 +14,7 @@ use std::{
|
||||
fmt,
|
||||
hash::Hash,
|
||||
iter::empty,
|
||||
mem,
|
||||
num::{NonZeroU8, NonZeroUsize},
|
||||
ops::{Add, AddAssign, Deref, DerefMut},
|
||||
};
|
||||
@ -52,6 +53,8 @@ pub struct ScriptFnDef {
|
||||
/// Encapsulated running environment, if any.
|
||||
pub lib: Option<Shared<Module>>,
|
||||
/// Encapsulated imported modules.
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub mods: crate::engine::Imports,
|
||||
/// Function name.
|
||||
@ -61,9 +64,14 @@ pub struct ScriptFnDef {
|
||||
/// Names of function parameters.
|
||||
pub params: StaticVec<Identifier>,
|
||||
/// Access to external variables.
|
||||
///
|
||||
/// Not available under `no_closure`.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
pub externals: std::collections::BTreeSet<Identifier>,
|
||||
/// Function doc-comments (if any).
|
||||
/// _(METADATA)_ Function doc-comments (if any).
|
||||
/// Exported under the `metadata` feature only.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
pub comments: StaticVec<String>,
|
||||
@ -97,7 +105,10 @@ impl fmt::Display for ScriptFnDef {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
pub struct ScriptFnMetadata<'a> {
|
||||
/// Function doc-comments (if any).
|
||||
/// _(METADATA)_ Function doc-comments (if any).
|
||||
/// Exported under the `metadata` feature only.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
///
|
||||
/// Block doc-comments are kept in a single string slice with line-breaks within.
|
||||
///
|
||||
@ -148,6 +159,23 @@ impl<'a> Into<ScriptFnMetadata<'a>> for &'a ScriptFnDef {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
impl std::cmp::PartialOrd for ScriptFnMetadata<'_> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
impl std::cmp::Ord for ScriptFnMetadata<'_> {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
match self.name.cmp(other.name) {
|
||||
std::cmp::Ordering::Equal => self.params.len().cmp(&other.params.len()),
|
||||
cmp => cmp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compiled AST (abstract syntax tree) of a Rhai script.
|
||||
///
|
||||
/// # Thread Safety
|
||||
@ -162,6 +190,8 @@ pub struct AST {
|
||||
/// Script-defined functions.
|
||||
functions: Shared<Module>,
|
||||
/// Embedded module resolver, if any.
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: Option<Shared<crate::module::resolvers::StaticModuleResolver>>,
|
||||
}
|
||||
@ -186,9 +216,10 @@ impl AST {
|
||||
statements: impl IntoIterator<Item = Stmt>,
|
||||
functions: impl Into<Shared<Module>>,
|
||||
) -> Self {
|
||||
let statements: StaticVec<_> = statements.into_iter().collect();
|
||||
Self {
|
||||
source: None,
|
||||
body: StmtBlock(statements.into_iter().collect(), Position::NONE),
|
||||
body: StmtBlock::new(statements, Position::NONE),
|
||||
functions: functions.into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: None,
|
||||
@ -201,9 +232,10 @@ impl AST {
|
||||
functions: impl Into<Shared<Module>>,
|
||||
source: impl Into<Identifier>,
|
||||
) -> Self {
|
||||
let statements: StaticVec<_> = statements.into_iter().collect();
|
||||
Self {
|
||||
source: Some(source.into()),
|
||||
body: StmtBlock(statements.into_iter().collect(), Position::NONE),
|
||||
body: StmtBlock::new(statements, Position::NONE),
|
||||
functions: functions.into(),
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
resolver: None,
|
||||
@ -267,7 +299,7 @@ impl AST {
|
||||
/// _(INTERNALS)_ Get the internal shared [`Module`] containing all script-defined functions.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
/// Not available under `no_function` or `no_module`.
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated = "this method is volatile and may change"]
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
@ -303,6 +335,8 @@ impl AST {
|
||||
}
|
||||
/// _(INTERNALS)_ Get the embedded [module resolver][crate::ModuleResolver].
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[cfg(feature = "internals")]
|
||||
#[inline(always)]
|
||||
@ -835,7 +869,9 @@ pub struct StmtBlock(StaticVec<Stmt>, Position);
|
||||
impl StmtBlock {
|
||||
/// Create a new [`StmtBlock`].
|
||||
pub fn new(statements: impl Into<StaticVec<Stmt>>, pos: Position) -> Self {
|
||||
Self(statements.into(), pos)
|
||||
let mut statements = statements.into();
|
||||
statements.shrink_to_fit();
|
||||
Self(statements, pos)
|
||||
}
|
||||
/// Is this statements block empty?
|
||||
#[inline(always)]
|
||||
@ -882,7 +918,7 @@ impl fmt::Debug for StmtBlock {
|
||||
impl From<StmtBlock> for Stmt {
|
||||
fn from(block: StmtBlock) -> Self {
|
||||
let block_pos = block.position();
|
||||
Self::Block(block.0.into_vec(), block_pos)
|
||||
Self::Block(block.0.into_boxed_slice(), block_pos)
|
||||
}
|
||||
}
|
||||
|
||||
@ -922,7 +958,7 @@ pub enum Stmt {
|
||||
/// function call forming one statement.
|
||||
FnCall(Box<FnCallExpr>, Position),
|
||||
/// `{` stmt`;` ... `}`
|
||||
Block(Vec<Stmt>, Position),
|
||||
Block(Box<[Stmt]>, Position),
|
||||
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
|
||||
TryCatch(
|
||||
Box<(StmtBlock, Option<Ident>, StmtBlock)>,
|
||||
@ -938,12 +974,18 @@ pub enum Stmt {
|
||||
/// `return`/`throw`
|
||||
Return(ReturnType, Option<Expr>, Position),
|
||||
/// `import` expr `as` var
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Import(Expr, Option<Box<Ident>>, Position),
|
||||
/// `export` var `as` var `,` ...
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
Export(Vec<(Ident, Option<Ident>)>, Position),
|
||||
Export(Box<[(Ident, Option<Ident>)]>, Position),
|
||||
/// Convert a variable to shared.
|
||||
///
|
||||
/// Not available under `no_closure`.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Share(Identifier),
|
||||
}
|
||||
@ -959,7 +1001,9 @@ impl From<Stmt> for StmtBlock {
|
||||
#[inline(always)]
|
||||
fn from(stmt: Stmt) -> Self {
|
||||
match stmt {
|
||||
Stmt::Block(block, pos) => Self(block.into(), pos),
|
||||
Stmt::Block(mut block, pos) => {
|
||||
Self(block.iter_mut().map(|v| mem::take(v)).collect(), pos)
|
||||
}
|
||||
Stmt::Noop(pos) => Self(Default::default(), pos),
|
||||
_ => {
|
||||
let pos = stmt.position();
|
||||
@ -1265,7 +1309,7 @@ impl Stmt {
|
||||
}
|
||||
}
|
||||
Self::Block(x, _) => {
|
||||
for s in x {
|
||||
for s in x.iter() {
|
||||
if !s.walk(path, on_node) {
|
||||
return false;
|
||||
}
|
||||
@ -1313,10 +1357,10 @@ impl Stmt {
|
||||
pub struct CustomExpr {
|
||||
/// List of keywords.
|
||||
pub keywords: StaticVec<Expr>,
|
||||
/// Is the current [`Scope`][crate::Scope] modified?
|
||||
pub scope_changed: bool,
|
||||
/// List of tokens actually parsed.
|
||||
pub tokens: Vec<Identifier>,
|
||||
/// Delta number of variables in the scope.
|
||||
pub scope_delta: isize,
|
||||
pub tokens: StaticVec<Identifier>,
|
||||
}
|
||||
|
||||
/// _(INTERNALS)_ A binary expression.
|
||||
@ -1419,7 +1463,7 @@ impl fmt::Debug for FnCallHashes {
|
||||
}
|
||||
|
||||
impl FnCallHashes {
|
||||
/// Create a [`FnCallHash`] with only the native Rust hash.
|
||||
/// Create a [`FnCallHashes`] with only the native Rust hash.
|
||||
#[inline(always)]
|
||||
pub fn from_native(hash: u64) -> Self {
|
||||
Self {
|
||||
@ -1427,7 +1471,7 @@ impl FnCallHashes {
|
||||
native: hash,
|
||||
}
|
||||
}
|
||||
/// Create a [`FnCallHash`] with both native Rust and script function hashes set to the same value.
|
||||
/// Create a [`FnCallHashes`] with both native Rust and script function hashes set to the same value.
|
||||
#[inline(always)]
|
||||
pub fn from_script(hash: u64) -> Self {
|
||||
Self {
|
||||
@ -1435,7 +1479,7 @@ impl FnCallHashes {
|
||||
native: hash,
|
||||
}
|
||||
}
|
||||
/// Create a [`FnCallHash`] with both native Rust and script function hashes.
|
||||
/// Create a [`FnCallHashes`] with both native Rust and script function hashes.
|
||||
#[inline(always)]
|
||||
pub fn from_script_and_native(script: u64, native: u64) -> Self {
|
||||
Self {
|
||||
@ -1443,21 +1487,21 @@ impl FnCallHashes {
|
||||
native,
|
||||
}
|
||||
}
|
||||
/// Is this [`FnCallHash`] native Rust only?
|
||||
/// Is this [`FnCallHashes`] native Rust only?
|
||||
#[inline(always)]
|
||||
pub fn is_native_only(&self) -> bool {
|
||||
self.script.is_none()
|
||||
}
|
||||
/// Get the script function hash from this [`FnCallHash`].
|
||||
/// Get the script function hash from this [`FnCallHashes`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the [`FnCallHash`] is native Rust only.
|
||||
/// Panics if the [`FnCallHashes`] is native Rust only.
|
||||
#[inline(always)]
|
||||
pub fn script_hash(&self) -> u64 {
|
||||
self.script.unwrap()
|
||||
}
|
||||
/// Get the naive Rust function hash from this [`FnCallHash`].
|
||||
/// Get the naive Rust function hash from this [`FnCallHashes`].
|
||||
#[inline(always)]
|
||||
pub fn native_hash(&self) -> u64 {
|
||||
self.native
|
||||
@ -1479,7 +1523,7 @@ pub struct FnCallExpr {
|
||||
/// List of function call argument expressions.
|
||||
pub args: StaticVec<Expr>,
|
||||
/// List of function call arguments that are constants.
|
||||
pub constant_args: smallvec::SmallVec<[(Dynamic, Position); 2]>,
|
||||
pub literal_args: smallvec::SmallVec<[(Dynamic, Position); 2]>,
|
||||
/// Function name.
|
||||
pub name: Identifier,
|
||||
/// Does this function call capture the parent scope?
|
||||
@ -1495,16 +1539,18 @@ impl FnCallExpr {
|
||||
/// Are there no arguments to this function call?
|
||||
#[inline(always)]
|
||||
pub fn is_args_empty(&self) -> bool {
|
||||
self.args.is_empty() && self.constant_args.is_empty()
|
||||
self.args.is_empty() && self.literal_args.is_empty()
|
||||
}
|
||||
/// Get the number of arguments to this function call.
|
||||
#[inline(always)]
|
||||
pub fn args_count(&self) -> usize {
|
||||
self.args.len() + self.constant_args.len()
|
||||
self.args.len() + self.literal_args.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that wraps a floating-point number and implements [`Hash`].
|
||||
///
|
||||
/// Not available under `no_float`.
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
#[derive(Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub struct FloatWrapper<F>(F);
|
||||
@ -1635,6 +1681,8 @@ pub enum Expr {
|
||||
/// Integer constant.
|
||||
IntegerConstant(INT, Position),
|
||||
/// Floating-point constant.
|
||||
///
|
||||
/// Not available under `no_float`.
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
FloatConstant(FloatWrapper<FLOAT>, Position),
|
||||
/// Character constant.
|
||||
@ -1745,8 +1793,8 @@ impl fmt::Debug for Expr {
|
||||
ff.field("name", &x.name)
|
||||
.field("hash", &x.hashes)
|
||||
.field("args", &x.args);
|
||||
if !x.constant_args.is_empty() {
|
||||
ff.field("constant_args", &x.constant_args);
|
||||
if !x.literal_args.is_empty() {
|
||||
ff.field("literal_args", &x.literal_args);
|
||||
}
|
||||
if x.capture {
|
||||
ff.field("capture", &x.capture);
|
||||
@ -2072,7 +2120,14 @@ mod tests {
|
||||
assert_eq!(size_of::<Option<ast::Expr>>(), 16);
|
||||
assert_eq!(size_of::<ast::Stmt>(), 32);
|
||||
assert_eq!(size_of::<Option<ast::Stmt>>(), 32);
|
||||
assert_eq!(size_of::<FnPtr>(), 96);
|
||||
assert_eq!(
|
||||
size_of::<FnPtr>(),
|
||||
if cfg!(feature = "no_smartstring") {
|
||||
80
|
||||
} else {
|
||||
96
|
||||
}
|
||||
);
|
||||
assert_eq!(size_of::<Scope>(), 288);
|
||||
assert_eq!(size_of::<LexError>(), 56);
|
||||
assert_eq!(
|
||||
|
@ -106,7 +106,7 @@ fn main() {
|
||||
.compile(&contents)
|
||||
.map_err(|err| err.into())
|
||||
.and_then(|mut ast| {
|
||||
ast.set_source(filename.to_string_lossy());
|
||||
ast.set_source(filename.to_string_lossy().to_string());
|
||||
Module::eval_ast_as_new(Default::default(), &ast, &engine)
|
||||
}) {
|
||||
Err(err) => {
|
||||
|
@ -85,7 +85,7 @@ fn main() {
|
||||
.compile(contents)
|
||||
.map_err(|err| Box::new(err.into()) as Box<EvalAltResult>)
|
||||
.and_then(|mut ast| {
|
||||
ast.set_source(filename.to_string_lossy());
|
||||
ast.set_source(filename.to_string_lossy().to_string());
|
||||
engine.consume_ast(&ast)
|
||||
})
|
||||
{
|
||||
|
668
src/dynamic.rs
668
src/dynamic.rs
File diff suppressed because it is too large
Load Diff
144
src/engine.rs
144
src/engine.rs
@ -558,7 +558,7 @@ pub struct FnResolutionCacheEntry {
|
||||
}
|
||||
|
||||
/// A function resolution cache.
|
||||
pub type FnResolutionCache = BTreeMap<u64, Option<FnResolutionCacheEntry>>;
|
||||
pub type FnResolutionCache = BTreeMap<u64, Option<Box<FnResolutionCacheEntry>>>;
|
||||
|
||||
/// _(INTERNALS)_ A type that holds all the current states of the [`Engine`].
|
||||
/// Exported under the `internals` feature only.
|
||||
@ -776,7 +776,7 @@ pub struct Engine {
|
||||
pub(crate) module_resolver: Box<dyn crate::ModuleResolver>,
|
||||
|
||||
/// A map mapping type names to pretty-print names.
|
||||
pub(crate) type_names: BTreeMap<Identifier, Identifier>,
|
||||
pub(crate) type_names: BTreeMap<Identifier, Box<Identifier>>,
|
||||
|
||||
/// An empty [`ImmutableString`] for cloning purposes.
|
||||
pub(crate) empty_string: ImmutableString,
|
||||
@ -786,7 +786,7 @@ pub struct Engine {
|
||||
/// A map containing custom keywords and precedence to recognize.
|
||||
pub(crate) custom_keywords: BTreeMap<Identifier, Option<Precedence>>,
|
||||
/// Custom syntax.
|
||||
pub(crate) custom_syntax: BTreeMap<Identifier, CustomSyntax>,
|
||||
pub(crate) custom_syntax: BTreeMap<Identifier, Box<CustomSyntax>>,
|
||||
/// Callback closure for resolving variable access.
|
||||
pub(crate) resolve_var: Option<OnVarCallback>,
|
||||
|
||||
@ -804,11 +804,6 @@ pub struct Engine {
|
||||
/// Max limits.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
pub(crate) limits: Limits,
|
||||
|
||||
/// Disable doc-comments?
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
pub(crate) disable_doc_comments: bool,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Engine {
|
||||
@ -926,13 +921,9 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
max_map_size: None,
|
||||
},
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
disable_doc_comments: false,
|
||||
};
|
||||
|
||||
engine.global_namespace.set_internal(true);
|
||||
engine.global_namespace.internal = true;
|
||||
engine.register_global_module(StandardPackage::new().as_shared_module());
|
||||
|
||||
engine
|
||||
@ -987,13 +978,9 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
max_map_size: None,
|
||||
},
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
disable_doc_comments: false,
|
||||
};
|
||||
|
||||
engine.global_namespace.set_internal(true);
|
||||
engine.global_namespace.internal = true;
|
||||
|
||||
engine
|
||||
}
|
||||
@ -1144,6 +1131,7 @@ impl Engine {
|
||||
lib: &[&Module],
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
target: &mut Target,
|
||||
root: (&str, Position),
|
||||
rhs: &Expr,
|
||||
idx_values: &mut StaticVec<ChainArgument>,
|
||||
chain_type: ChainType,
|
||||
@ -1179,8 +1167,8 @@ impl Engine {
|
||||
let rhs_chain = rhs_chain.unwrap();
|
||||
|
||||
self.eval_dot_index_chain_helper(
|
||||
mods, state, lib, this_ptr, obj_ptr, &x.rhs, idx_values, rhs_chain,
|
||||
level, new_val,
|
||||
mods, state, lib, this_ptr, obj_ptr, root, &x.rhs, idx_values,
|
||||
rhs_chain, level, new_val,
|
||||
)
|
||||
.map_err(|err| err.fill_position(*x_pos))
|
||||
}
|
||||
@ -1199,7 +1187,8 @@ impl Engine {
|
||||
Ok(obj_ptr) => {
|
||||
let ((new_val, new_pos), (op_info, op_pos)) = new_val.unwrap();
|
||||
self.eval_op_assignment(
|
||||
mods, state, lib, op_info, op_pos, obj_ptr, new_val, new_pos,
|
||||
mods, state, lib, op_info, op_pos, obj_ptr, root, new_val,
|
||||
new_pos,
|
||||
)?;
|
||||
None
|
||||
}
|
||||
@ -1282,7 +1271,7 @@ impl Engine {
|
||||
)?;
|
||||
let ((new_val, new_pos), (op_info, op_pos)) = new_val.unwrap();
|
||||
self.eval_op_assignment(
|
||||
mods, state, lib, op_info, op_pos, val, new_val, new_pos,
|
||||
mods, state, lib, op_info, op_pos, val, root, new_val, new_pos,
|
||||
)?;
|
||||
Ok((Dynamic::UNIT, true))
|
||||
}
|
||||
@ -1311,7 +1300,7 @@ impl Engine {
|
||||
)?;
|
||||
let obj_ptr = (&mut orig_val).into();
|
||||
self.eval_op_assignment(
|
||||
mods, state, lib, op_info, op_pos, obj_ptr, new_val, new_pos,
|
||||
mods, state, lib, op_info, op_pos, obj_ptr, root, new_val, new_pos,
|
||||
)?;
|
||||
new_val = orig_val;
|
||||
}
|
||||
@ -1365,8 +1354,8 @@ impl Engine {
|
||||
let rhs_chain = rhs_chain.unwrap();
|
||||
|
||||
self.eval_dot_index_chain_helper(
|
||||
mods, state, lib, this_ptr, &mut val, &x.rhs, idx_values, rhs_chain,
|
||||
level, new_val,
|
||||
mods, state, lib, this_ptr, &mut val, root, &x.rhs, idx_values,
|
||||
rhs_chain, level, new_val,
|
||||
)
|
||||
.map_err(|err| err.fill_position(*x_pos))
|
||||
}
|
||||
@ -1396,6 +1385,7 @@ impl Engine {
|
||||
lib,
|
||||
this_ptr,
|
||||
&mut val.into(),
|
||||
root,
|
||||
&x.rhs,
|
||||
idx_values,
|
||||
rhs_chain,
|
||||
@ -1438,7 +1428,7 @@ impl Engine {
|
||||
let target = &mut val.into();
|
||||
|
||||
self.eval_dot_index_chain_helper(
|
||||
mods, state, lib, this_ptr, target, &x.rhs, idx_values,
|
||||
mods, state, lib, this_ptr, target, root, &x.rhs, idx_values,
|
||||
rhs_chain, level, new_val,
|
||||
)
|
||||
.map_err(|err| err.fill_position(*pos))
|
||||
@ -1487,21 +1477,16 @@ impl Engine {
|
||||
|
||||
match lhs {
|
||||
// id.??? or id[???]
|
||||
Expr::Variable(_, _var_pos, x) => {
|
||||
Expr::Variable(_, var_pos, x) => {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
self.inc_operations(state, *_var_pos)?;
|
||||
self.inc_operations(state, *var_pos)?;
|
||||
|
||||
let (target, pos) =
|
||||
self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?;
|
||||
|
||||
// Constants cannot be modified
|
||||
if target.is_read_only() && new_val.is_some() {
|
||||
return EvalAltResult::ErrorAssignmentToConstant(x.2.to_string(), pos).into();
|
||||
}
|
||||
let (target, _) = self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?;
|
||||
|
||||
let obj_ptr = &mut target.into();
|
||||
let root = (x.2.as_str(), *var_pos);
|
||||
self.eval_dot_index_chain_helper(
|
||||
mods, state, lib, &mut None, obj_ptr, rhs, idx_values, chain_type, level,
|
||||
mods, state, lib, &mut None, obj_ptr, root, rhs, idx_values, chain_type, level,
|
||||
new_val,
|
||||
)
|
||||
.map(|(v, _)| v)
|
||||
@ -1513,8 +1498,9 @@ impl Engine {
|
||||
expr => {
|
||||
let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
|
||||
let obj_ptr = &mut value.into();
|
||||
let root = ("", expr.position());
|
||||
self.eval_dot_index_chain_helper(
|
||||
mods, state, lib, this_ptr, obj_ptr, rhs, idx_values, chain_type, level,
|
||||
mods, state, lib, this_ptr, obj_ptr, root, rhs, idx_values, chain_type, level,
|
||||
new_val,
|
||||
)
|
||||
.map(|(v, _)| v)
|
||||
@ -1558,7 +1544,7 @@ impl Engine {
|
||||
})
|
||||
.collect::<Result<StaticVec<_>, _>>()?;
|
||||
|
||||
x.constant_args
|
||||
x.literal_args
|
||||
.iter()
|
||||
.inspect(|(_, pos)| arg_positions.push(*pos))
|
||||
.for_each(|(v, _)| arg_values.push(v.clone()));
|
||||
@ -1603,7 +1589,7 @@ impl Engine {
|
||||
})
|
||||
.collect::<Result<StaticVec<_>, _>>()?;
|
||||
|
||||
x.constant_args
|
||||
x.literal_args
|
||||
.iter()
|
||||
.inspect(|(_, pos)| arg_positions.push(*pos))
|
||||
.for_each(|(v, _)| arg_values.push(v.clone()));
|
||||
@ -1676,7 +1662,7 @@ impl Engine {
|
||||
|
||||
match target {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Dynamic(Union::Array(arr, _)) => {
|
||||
Dynamic(Union::Array(arr, _, _)) => {
|
||||
// val_array[idx]
|
||||
let index = _idx
|
||||
.as_int()
|
||||
@ -1718,7 +1704,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Dynamic(Union::Map(map, _)) => {
|
||||
Dynamic(Union::Map(map, _, _)) => {
|
||||
// val_map[idx]
|
||||
let index = &*_idx.read_lock::<ImmutableString>().ok_or_else(|| {
|
||||
self.make_type_mismatch_err::<ImmutableString>(_idx.type_name(), idx_pos)
|
||||
@ -1735,7 +1721,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Dynamic(Union::Str(s, _)) => {
|
||||
Dynamic(Union::Str(s, _, _)) => {
|
||||
// val_string[idx]
|
||||
let index = _idx
|
||||
.as_int()
|
||||
@ -1861,6 +1847,7 @@ impl Engine {
|
||||
Some(OpAssignment::new(TOKEN_OP_CONCAT)),
|
||||
pos,
|
||||
(&mut result).into(),
|
||||
("", Position::NONE),
|
||||
item,
|
||||
expr.position(),
|
||||
)?;
|
||||
@ -1905,7 +1892,7 @@ impl Engine {
|
||||
namespace,
|
||||
hashes,
|
||||
args,
|
||||
constant_args: c_args,
|
||||
literal_args: c_args,
|
||||
..
|
||||
} = x.as_ref();
|
||||
let namespace = namespace.as_ref();
|
||||
@ -1923,7 +1910,7 @@ impl Engine {
|
||||
capture,
|
||||
hashes,
|
||||
args,
|
||||
constant_args: c_args,
|
||||
literal_args: c_args,
|
||||
..
|
||||
} = x.as_ref();
|
||||
self.make_function_call(
|
||||
@ -2077,11 +2064,13 @@ impl Engine {
|
||||
op_info: Option<OpAssignment>,
|
||||
op_pos: Position,
|
||||
mut target: Target,
|
||||
root: (&str, Position),
|
||||
mut new_value: Dynamic,
|
||||
new_value_pos: Position,
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
if target.is_read_only() {
|
||||
unreachable!("LHS should not be read-only");
|
||||
// Assignment to constant variable
|
||||
return EvalAltResult::ErrorAssignmentToConstant(root.0.to_string(), root.1).into();
|
||||
}
|
||||
|
||||
if let Some(OpAssignment {
|
||||
@ -2180,26 +2169,18 @@ impl Engine {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
self.inc_operations(state, pos)?;
|
||||
|
||||
if lhs_ptr.is_read_only() {
|
||||
// Assignment to constant variable
|
||||
EvalAltResult::ErrorAssignmentToConstant(
|
||||
lhs_expr.get_variable_name(false).unwrap().to_string(),
|
||||
pos,
|
||||
)
|
||||
.into()
|
||||
} else {
|
||||
self.eval_op_assignment(
|
||||
mods,
|
||||
state,
|
||||
lib,
|
||||
op_info.clone(),
|
||||
*op_pos,
|
||||
lhs_ptr,
|
||||
rhs_val,
|
||||
rhs_expr.position(),
|
||||
)?;
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
self.eval_op_assignment(
|
||||
mods,
|
||||
state,
|
||||
lib,
|
||||
op_info.clone(),
|
||||
*op_pos,
|
||||
lhs_ptr,
|
||||
(lhs_expr.get_variable_name(false).unwrap(), pos),
|
||||
rhs_val,
|
||||
rhs_expr.position(),
|
||||
)?;
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
|
||||
// lhs op= rhs
|
||||
@ -2467,7 +2448,7 @@ impl Engine {
|
||||
namespace,
|
||||
hashes,
|
||||
args,
|
||||
constant_args: c_args,
|
||||
literal_args: c_args,
|
||||
..
|
||||
} = x.as_ref();
|
||||
let namespace = namespace.as_ref();
|
||||
@ -2485,7 +2466,7 @@ impl Engine {
|
||||
capture,
|
||||
hashes,
|
||||
args,
|
||||
constant_args: c_args,
|
||||
literal_args: c_args,
|
||||
..
|
||||
} = x.as_ref();
|
||||
self.make_function_call(
|
||||
@ -2569,7 +2550,7 @@ impl Engine {
|
||||
Ok(_) => Ok(Dynamic::UNIT),
|
||||
Err(result_err) => match *result_err {
|
||||
// Re-throw exception
|
||||
EvalAltResult::ErrorRuntime(Dynamic(Union::Unit(_, _)), pos) => {
|
||||
EvalAltResult::ErrorRuntime(Dynamic(Union::Unit(_, _, _)), pos) => {
|
||||
err.set_position(pos);
|
||||
Err(err)
|
||||
}
|
||||
@ -2623,18 +2604,15 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if entry_type == AccessMode::ReadOnly && lib.iter().any(|&m| !m.is_empty()) {
|
||||
let global = if let Some(index) = mods.find(KEYWORD_GLOBAL) {
|
||||
let global = mods.get_mut(index).unwrap();
|
||||
|
||||
if !global.is_internal() {
|
||||
None
|
||||
} else {
|
||||
Some(global)
|
||||
match mods.get_mut(index).unwrap() {
|
||||
m if m.internal => Some(m),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
// Create automatic global module
|
||||
let mut global = Module::new();
|
||||
global.set_internal(true);
|
||||
mods.push(crate::engine::KEYWORD_GLOBAL, global);
|
||||
global.internal = true;
|
||||
mods.push(KEYWORD_GLOBAL, global);
|
||||
Some(mods.get_mut(mods.len() - 1).unwrap())
|
||||
};
|
||||
|
||||
@ -2777,18 +2755,18 @@ impl Engine {
|
||||
fn calc_size(value: &Dynamic) -> (usize, usize, usize) {
|
||||
match value {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Dynamic(Union::Array(arr, _)) => {
|
||||
Dynamic(Union::Array(arr, _, _)) => {
|
||||
let mut arrays = 0;
|
||||
let mut maps = 0;
|
||||
|
||||
arr.iter().for_each(|value| match value {
|
||||
Dynamic(Union::Array(_, _)) => {
|
||||
Dynamic(Union::Array(_, _, _)) => {
|
||||
let (a, m, _) = calc_size(value);
|
||||
arrays += a;
|
||||
maps += m;
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Dynamic(Union::Map(_, _)) => {
|
||||
Dynamic(Union::Map(_, _, _)) => {
|
||||
let (a, m, _) = calc_size(value);
|
||||
arrays += a;
|
||||
maps += m;
|
||||
@ -2799,18 +2777,18 @@ impl Engine {
|
||||
(arrays, maps, 0)
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Dynamic(Union::Map(map, _)) => {
|
||||
Dynamic(Union::Map(map, _, _)) => {
|
||||
let mut arrays = 0;
|
||||
let mut maps = 0;
|
||||
|
||||
map.values().for_each(|value| match value {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Dynamic(Union::Array(_, _)) => {
|
||||
Dynamic(Union::Array(_, _, _)) => {
|
||||
let (a, m, _) = calc_size(value);
|
||||
arrays += a;
|
||||
maps += m;
|
||||
}
|
||||
Dynamic(Union::Map(_, _)) => {
|
||||
Dynamic(Union::Map(_, _, _)) => {
|
||||
let (a, m, _) = calc_size(value);
|
||||
arrays += a;
|
||||
maps += m;
|
||||
@ -2820,7 +2798,7 @@ impl Engine {
|
||||
|
||||
(arrays, maps, 0)
|
||||
}
|
||||
Dynamic(Union::Str(s, _)) => (0, 0, s.len()),
|
||||
Dynamic(Union::Str(s, _, _)) => (0, 0, s.len()),
|
||||
_ => (0, 0, 0),
|
||||
}
|
||||
}
|
||||
|
@ -183,6 +183,8 @@ impl Engine {
|
||||
/// Register a custom type for use with the [`Engine`].
|
||||
/// The type must implement [`Clone`].
|
||||
///
|
||||
/// Not available under `no_object`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
@ -224,6 +226,8 @@ impl Engine {
|
||||
/// Register a custom type for use with the [`Engine`], with a pretty-print name
|
||||
/// for the `type_of` function. The type must implement [`Clone`].
|
||||
///
|
||||
/// Not available under `no_object`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
@ -265,7 +269,8 @@ impl Engine {
|
||||
#[inline(always)]
|
||||
pub fn register_type_with_name<T: Variant + Clone>(&mut self, name: &str) -> &mut Self {
|
||||
// Add the pretty-print type name into the map
|
||||
self.type_names.insert(type_name::<T>().into(), name.into());
|
||||
self.type_names
|
||||
.insert(type_name::<T>().into(), Box::new(name.into()));
|
||||
self
|
||||
}
|
||||
/// Register an type iterator for an iterable type with the [`Engine`].
|
||||
@ -283,6 +288,8 @@ impl Engine {
|
||||
///
|
||||
/// The function signature must start with `&mut self` and not `&self`.
|
||||
///
|
||||
/// Not available under `no_object`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
@ -328,6 +335,8 @@ impl Engine {
|
||||
///
|
||||
/// The function signature must start with `&mut self` and not `&self`.
|
||||
///
|
||||
/// Not available under `no_object`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
@ -373,6 +382,8 @@ impl Engine {
|
||||
}
|
||||
/// Register a setter function for a member of a registered type with the [`Engine`].
|
||||
///
|
||||
/// Not available under `no_object`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
@ -419,6 +430,8 @@ impl Engine {
|
||||
}
|
||||
/// Register a setter function for a member of a registered type with the [`Engine`].
|
||||
///
|
||||
/// Not available under `no_object`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
@ -473,6 +486,8 @@ impl Engine {
|
||||
///
|
||||
/// All function signatures must start with `&mut self` and not `&self`.
|
||||
///
|
||||
/// Not available under `no_object`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
@ -521,6 +536,8 @@ impl Engine {
|
||||
///
|
||||
/// The function signature must start with `&mut self` and not `&self`.
|
||||
///
|
||||
/// Not available under `no_index`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`.
|
||||
@ -585,6 +602,8 @@ impl Engine {
|
||||
///
|
||||
/// The function signature must start with `&mut self` and not `&self`.
|
||||
///
|
||||
/// Not available under `no_index`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`.
|
||||
@ -653,6 +672,8 @@ impl Engine {
|
||||
}
|
||||
/// Register an index setter for a custom type with the [`Engine`].
|
||||
///
|
||||
/// Not available under `no_index`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`.
|
||||
@ -717,6 +738,8 @@ impl Engine {
|
||||
}
|
||||
/// Register an index setter for a custom type with the [`Engine`].
|
||||
///
|
||||
/// Not available under `no_index`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`.
|
||||
@ -791,6 +814,8 @@ impl Engine {
|
||||
}
|
||||
/// Short-hand for register both index getter and setter functions for a custom type with the [`Engine`].
|
||||
///
|
||||
/// Not available under `no_index`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`.
|
||||
@ -869,6 +894,8 @@ impl Engine {
|
||||
/// Functions marked [`FnNamespace::Global`] and type iterators are exposed to scripts without
|
||||
/// namespace qualifications.
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
@ -1023,6 +1050,8 @@ impl Engine {
|
||||
/// Compile a string into an [`AST`] using own scope, which can be used later for evaluation,
|
||||
/// embedding all imported modules.
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
///
|
||||
/// Modules referred by `import` statements containing literal string paths are eagerly resolved
|
||||
/// via the current [module resolver][crate::ModuleResolver] and embedded into the resultant
|
||||
/// [`AST`]. When it is evaluated later, `import` statement directly recall pre-resolved
|
||||
@ -1271,6 +1300,8 @@ impl Engine {
|
||||
/// Parse a JSON string into an [object map][`Map`].
|
||||
/// This is a light-weight alternative to using, say, [`serde_json`] to deserialize the JSON.
|
||||
///
|
||||
/// Not available under `no_object`.
|
||||
///
|
||||
/// The JSON string must be an object hash. It cannot be a simple scalar value.
|
||||
///
|
||||
/// Set `has_null` to `true` in order to map `null` values to `()`.
|
||||
@ -1787,6 +1818,8 @@ impl Engine {
|
||||
/// Call a script function defined in an [`AST`] with multiple arguments.
|
||||
/// Arguments are passed as a tuple.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
///
|
||||
/// The [`AST`] is evaluated before calling the function.
|
||||
/// This allows a script to load the necessary modules.
|
||||
/// This is usually desired. If not, a specialized [`AST`] can be prepared that contains only
|
||||
@ -1854,6 +1887,8 @@ impl Engine {
|
||||
/// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments
|
||||
/// and optionally a value for binding to the `this` pointer.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
///
|
||||
/// There is an option to evaluate the [`AST`] to load necessary modules before calling the function.
|
||||
///
|
||||
/// # WARNING
|
||||
@ -1970,6 +2005,8 @@ impl Engine {
|
||||
/// Optimize the [`AST`] with constants defined in an external Scope.
|
||||
/// An optimized copy of the [`AST`] is returned while the original [`AST`] is consumed.
|
||||
///
|
||||
/// Not available under `no_optimize`.
|
||||
///
|
||||
/// Although optimization is performed by default during compilation, sometimes it is necessary to
|
||||
/// _re_-optimize an [`AST`]. For example, when working with constants that are passed in via an
|
||||
/// external scope, it will be more efficient to optimize the [`AST`] once again to take advantage
|
||||
@ -2002,7 +2039,7 @@ impl Engine {
|
||||
let stmt = std::mem::take(ast.statements_mut());
|
||||
crate::optimize::optimize_into_ast(self, scope, stmt.into_vec(), lib, optimization_level)
|
||||
}
|
||||
/// Generate a list of all registered functions.
|
||||
/// _(METADATA)_ Generate a list of all registered functions.
|
||||
/// Exported under the `metadata` feature only.
|
||||
///
|
||||
/// Functions from the following sources are included, in order:
|
||||
@ -2073,6 +2110,8 @@ impl Engine {
|
||||
}
|
||||
/// Register a callback for script evaluation progress.
|
||||
///
|
||||
/// Not available under `unchecked`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
|
@ -31,16 +31,6 @@ impl Engine {
|
||||
pub fn optimization_level(&self) -> crate::OptimizationLevel {
|
||||
self.optimization_level
|
||||
}
|
||||
/// _(METADATA)_ Enable/disable doc-comments for functions.
|
||||
/// Exported under the `metadata` feature only.
|
||||
/// Not available under `no_function`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
#[inline(always)]
|
||||
pub fn enable_doc_comments(&mut self, enable: bool) -> &mut Self {
|
||||
self.disable_doc_comments = !enable;
|
||||
self
|
||||
}
|
||||
/// Set the maximum levels of function calls allowed for a script in order to avoid
|
||||
/// infinite recursion and stack overflows.
|
||||
///
|
||||
|
@ -184,15 +184,19 @@ pub fn get_builtin_binary_op_fn(
|
||||
"*" => impl_op!(from Decimal => multiply($xx, $yy)),
|
||||
"/" => impl_op!(from Decimal => divide($xx, $yy)),
|
||||
"%" => impl_op!(from Decimal => modulo($xx, $yy)),
|
||||
"**" => impl_op!(from Decimal => power($xx, $yy)),
|
||||
_ => ()
|
||||
}
|
||||
} else {
|
||||
use rust_decimal::MathematicalOps;
|
||||
|
||||
match op {
|
||||
"+" => impl_op!(from Decimal => $xx + $yy),
|
||||
"-" => impl_op!(from Decimal => $xx - $yy),
|
||||
"*" => impl_op!(from Decimal => $xx * $yy),
|
||||
"/" => impl_op!(from Decimal => $xx / $yy),
|
||||
"%" => impl_op!(from Decimal => $xx % $yy),
|
||||
"**" => impl_op!(from Decimal => $xx.powd($yy)),
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
@ -522,15 +526,19 @@ pub fn get_builtin_op_assignment_fn(
|
||||
"*=" => impl_op!(from $x => multiply($xx, $yy)),
|
||||
"/=" => impl_op!(from $x => divide($xx, $yy)),
|
||||
"%=" => impl_op!(from $x => modulo($xx, $yy)),
|
||||
"**=" => impl_op!(from $x => power($xx, $yy)),
|
||||
_ => return None,
|
||||
}
|
||||
} else {
|
||||
use rust_decimal::MathematicalOps;
|
||||
|
||||
match op {
|
||||
"+=" => impl_op!(from $x += $yy),
|
||||
"-=" => impl_op!(from $x -= $yy),
|
||||
"*=" => impl_op!(from $x *= $yy),
|
||||
"/=" => impl_op!(from $x /= $yy),
|
||||
"%=" => impl_op!(from $x %= $yy),
|
||||
"**=" => impl_op!(from $x => $xx.powd($yy)),
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
|
@ -161,7 +161,7 @@ impl Engine {
|
||||
args: Option<&mut FnCallArgs>,
|
||||
allow_dynamic: bool,
|
||||
is_op_assignment: bool,
|
||||
) -> &'s Option<FnResolutionCacheEntry> {
|
||||
) -> &'s Option<Box<FnResolutionCacheEntry>> {
|
||||
let mut hash = if let Some(ref args) = args {
|
||||
let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id()));
|
||||
combine_hashes(hash_script, hash_params)
|
||||
@ -222,7 +222,7 @@ impl Engine {
|
||||
|
||||
match func {
|
||||
// Specific version found
|
||||
Some(f) => return Some(f),
|
||||
Some(f) => return Some(Box::new(f)),
|
||||
|
||||
// Stop when all permutations are exhausted
|
||||
None if bitmask >= max_bitmask => {
|
||||
@ -250,6 +250,7 @@ impl Engine {
|
||||
},
|
||||
)
|
||||
}
|
||||
.map(Box::new)
|
||||
});
|
||||
}
|
||||
|
||||
@ -311,7 +312,8 @@ impl Engine {
|
||||
is_op_assignment,
|
||||
);
|
||||
|
||||
if let Some(FnResolutionCacheEntry { func, source }) = func {
|
||||
if let Some(f) = func {
|
||||
let FnResolutionCacheEntry { func, source } = f.as_ref();
|
||||
assert!(func.is_native());
|
||||
|
||||
// Calling pure function but the first argument is a reference?
|
||||
@ -717,10 +719,11 @@ impl Engine {
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
if let Some(FnResolutionCacheEntry { func, source }) = hash_script.and_then(|hash| {
|
||||
if let Some(f) = hash_script.and_then(|hash| {
|
||||
self.resolve_function(mods, state, lib, fn_name, hash, None, false, false)
|
||||
.clone()
|
||||
}) {
|
||||
let FnResolutionCacheEntry { func, source } = *f;
|
||||
// Script function call
|
||||
assert!(func.is_script());
|
||||
|
||||
@ -1062,7 +1065,7 @@ impl Engine {
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
fn_name: &str,
|
||||
args_expr: &[Expr],
|
||||
constant_args: &[(Dynamic, Position)],
|
||||
literal_args: &[(Dynamic, Position)],
|
||||
mut hashes: FnCallHashes,
|
||||
pos: Position,
|
||||
capture_scope: bool,
|
||||
@ -1071,8 +1074,8 @@ impl Engine {
|
||||
// Handle call() - Redirect function call
|
||||
let redirected;
|
||||
let mut args_expr = args_expr;
|
||||
let mut constant_args = constant_args;
|
||||
let mut total_args = args_expr.len() + constant_args.len();
|
||||
let mut literal_args = literal_args;
|
||||
let mut total_args = args_expr.len() + literal_args.len();
|
||||
let mut curry = StaticVec::new();
|
||||
let mut name = fn_name;
|
||||
|
||||
@ -1080,7 +1083,7 @@ impl Engine {
|
||||
// Handle call()
|
||||
KEYWORD_FN_PTR_CALL if total_args >= 1 => {
|
||||
let (arg, arg_pos) = args_expr.get(0).map_or_else(
|
||||
|| Ok(constant_args[0].clone()),
|
||||
|| Ok(literal_args[0].clone()),
|
||||
|arg| {
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, arg, level)
|
||||
.map(|v| (v, arg.position()))
|
||||
@ -1105,7 +1108,7 @@ impl Engine {
|
||||
if !args_expr.is_empty() {
|
||||
args_expr = &args_expr[1..];
|
||||
} else {
|
||||
constant_args = &constant_args[1..];
|
||||
literal_args = &literal_args[1..];
|
||||
}
|
||||
total_args -= 1;
|
||||
|
||||
@ -1120,7 +1123,7 @@ impl Engine {
|
||||
// Handle Fn()
|
||||
KEYWORD_FN_PTR if total_args == 1 => {
|
||||
let (arg, arg_pos) = args_expr.get(0).map_or_else(
|
||||
|| Ok(constant_args[0].clone()),
|
||||
|| Ok(literal_args[0].clone()),
|
||||
|arg| {
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, arg, level)
|
||||
.map(|v| (v, arg.position()))
|
||||
@ -1139,7 +1142,7 @@ impl Engine {
|
||||
// Handle curry()
|
||||
KEYWORD_FN_PTR_CURRY if total_args > 1 => {
|
||||
let (arg, arg_pos) = args_expr.get(0).map_or_else(
|
||||
|| Ok(constant_args[0].clone()),
|
||||
|| Ok(literal_args[0].clone()),
|
||||
|arg| {
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, arg, level)
|
||||
.map(|v| (v, arg.position()))
|
||||
@ -1161,9 +1164,9 @@ impl Engine {
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
|
||||
.map(|value| fn_curry.push(value))
|
||||
})?;
|
||||
fn_curry.extend(constant_args.iter().map(|(v, _)| v.clone()));
|
||||
fn_curry.extend(literal_args.iter().map(|(v, _)| v.clone()));
|
||||
} else {
|
||||
fn_curry.extend(constant_args.iter().skip(1).map(|(v, _)| v.clone()));
|
||||
fn_curry.extend(literal_args.iter().skip(1).map(|(v, _)| v.clone()));
|
||||
}
|
||||
|
||||
return Ok(FnPtr::new_unchecked(name, fn_curry).into());
|
||||
@ -1173,7 +1176,7 @@ impl Engine {
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
crate::engine::KEYWORD_IS_SHARED if total_args == 1 => {
|
||||
let arg = args_expr.get(0).map_or_else(
|
||||
|| Ok(constant_args[0].0.clone()),
|
||||
|| Ok(literal_args[0].0.clone()),
|
||||
|arg| self.eval_expr(scope, mods, state, lib, this_ptr, arg, level),
|
||||
)?;
|
||||
return Ok(arg.is_shared().into());
|
||||
@ -1188,7 +1191,7 @@ impl Engine {
|
||||
args_expr[0].position(),
|
||||
)
|
||||
} else {
|
||||
constant_args[0].clone()
|
||||
literal_args[0].clone()
|
||||
};
|
||||
|
||||
let fn_name = arg
|
||||
@ -1201,7 +1204,7 @@ impl Engine {
|
||||
args_expr[1].position(),
|
||||
)
|
||||
} else {
|
||||
constant_args[if args_expr.is_empty() { 1 } else { 0 }].clone()
|
||||
literal_args[if args_expr.is_empty() { 1 } else { 0 }].clone()
|
||||
};
|
||||
|
||||
let num_params = arg
|
||||
@ -1220,7 +1223,7 @@ impl Engine {
|
||||
// Handle is_def_var()
|
||||
KEYWORD_IS_DEF_VAR if total_args == 1 => {
|
||||
let (arg, arg_pos) = args_expr.get(0).map_or_else(
|
||||
|| Ok(constant_args[0].clone()),
|
||||
|| Ok(literal_args[0].clone()),
|
||||
|arg| {
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, arg, level)
|
||||
.map(|v| (v, arg.position()))
|
||||
@ -1237,7 +1240,7 @@ impl Engine {
|
||||
// eval - only in function call style
|
||||
let prev_len = scope.len();
|
||||
let (script, script_pos) = args_expr.get(0).map_or_else(
|
||||
|| Ok(constant_args[0].clone()),
|
||||
|| Ok(literal_args[0].clone()),
|
||||
|script_expr| {
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, script_expr, level)
|
||||
.map(|v| (v, script_expr.position()))
|
||||
@ -1289,7 +1292,7 @@ impl Engine {
|
||||
None
|
||||
};
|
||||
|
||||
if args_expr.is_empty() && constant_args.is_empty() && curry.is_empty() {
|
||||
if args_expr.is_empty() && literal_args.is_empty() && curry.is_empty() {
|
||||
// No arguments
|
||||
args = Default::default();
|
||||
} else {
|
||||
@ -1305,7 +1308,7 @@ impl Engine {
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
|
||||
.map(Dynamic::flatten)
|
||||
})
|
||||
.chain(constant_args.iter().map(|(v, _)| Ok(v.clone())))
|
||||
.chain(literal_args.iter().map(|(v, _)| Ok(v.clone())))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
let (mut target, _pos) =
|
||||
@ -1341,7 +1344,7 @@ impl Engine {
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
|
||||
.map(Dynamic::flatten)
|
||||
})
|
||||
.chain(constant_args.iter().map(|(v, _)| Ok(v.clone())))
|
||||
.chain(literal_args.iter().map(|(v, _)| Ok(v.clone())))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
args = curry.iter_mut().chain(arg_values.iter_mut()).collect();
|
||||
@ -1365,7 +1368,7 @@ impl Engine {
|
||||
namespace: Option<&NamespaceRef>,
|
||||
fn_name: &str,
|
||||
args_expr: &[Expr],
|
||||
constant_args: &[(Dynamic, Position)],
|
||||
literal_args: &[(Dynamic, Position)],
|
||||
hash: u64,
|
||||
pos: Position,
|
||||
level: usize,
|
||||
@ -1375,7 +1378,7 @@ impl Engine {
|
||||
let mut first_arg_value = None;
|
||||
let mut args: StaticVec<_>;
|
||||
|
||||
if args_expr.is_empty() && constant_args.is_empty() {
|
||||
if args_expr.is_empty() && literal_args.is_empty() {
|
||||
// No arguments
|
||||
args = Default::default();
|
||||
} else {
|
||||
@ -1396,7 +1399,7 @@ impl Engine {
|
||||
.map(Dynamic::flatten)
|
||||
}
|
||||
})
|
||||
.chain(constant_args.iter().map(|(v, _)| Ok(v.clone())))
|
||||
.chain(literal_args.iter().map(|(v, _)| Ok(v.clone())))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
// Get target reference to first argument
|
||||
@ -1429,7 +1432,7 @@ impl Engine {
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
|
||||
.map(Dynamic::flatten)
|
||||
})
|
||||
.chain(constant_args.iter().map(|(v, _)| Ok(v.clone())))
|
||||
.chain(literal_args.iter().map(|(v, _)| Ok(v.clone())))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
args = arg_values.iter_mut().collect();
|
||||
|
@ -5,8 +5,8 @@ use crate::engine::Imports;
|
||||
use crate::plugin::PluginFunction;
|
||||
use crate::token::is_valid_identifier;
|
||||
use crate::{
|
||||
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Identifier, ImmutableString, Module,
|
||||
Position, RhaiResult, StaticVec,
|
||||
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Identifier, Module, Position,
|
||||
RhaiResult, StaticVec,
|
||||
};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -39,10 +39,14 @@ pub use std::rc::Rc as Shared;
|
||||
pub use std::sync::Arc as Shared;
|
||||
|
||||
/// Synchronized shared object.
|
||||
///
|
||||
/// Not available under `no_closure`.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub use std::cell::RefCell as Locked;
|
||||
/// Synchronized shared object.
|
||||
///
|
||||
/// Not available under `no_closure`.
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(feature = "sync")]
|
||||
pub use std::sync::RwLock as Locked;
|
||||
@ -101,6 +105,8 @@ impl<'a> NativeCallContext<'a> {
|
||||
}
|
||||
/// _(INTERNALS)_ Create a new [`NativeCallContext`].
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(feature = "internals")]
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline(always)]
|
||||
@ -135,6 +141,8 @@ impl<'a> NativeCallContext<'a> {
|
||||
self.source
|
||||
}
|
||||
/// Get an iterator over the current set of modules imported via `import` statements.
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline(always)]
|
||||
pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &Module)> {
|
||||
@ -151,6 +159,8 @@ impl<'a> NativeCallContext<'a> {
|
||||
}
|
||||
/// _(INTERNALS)_ The current set of modules imported via `import` statements.
|
||||
/// Exported under the `internals` feature only.
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(feature = "internals")]
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline(always)]
|
||||
@ -303,6 +313,8 @@ impl FnPtr {
|
||||
!self.1.is_empty()
|
||||
}
|
||||
/// Does the function pointer refer to an anonymous function?
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline(always)]
|
||||
pub fn is_anonymous(&self) -> bool {
|
||||
@ -367,16 +379,17 @@ impl TryFrom<Identifier> for FnPtr {
|
||||
if is_valid_identifier(value.chars()) {
|
||||
Ok(Self(value, Default::default()))
|
||||
} else {
|
||||
EvalAltResult::ErrorFunctionNotFound(value.into(), Position::NONE).into()
|
||||
EvalAltResult::ErrorFunctionNotFound(value.to_string(), Position::NONE).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ImmutableString> for FnPtr {
|
||||
#[cfg(not(feature = "no_smartstring"))]
|
||||
impl TryFrom<crate::ImmutableString> for FnPtr {
|
||||
type Error = Box<EvalAltResult>;
|
||||
|
||||
#[inline(always)]
|
||||
fn try_from(value: ImmutableString) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: crate::ImmutableString) -> Result<Self, Self::Error> {
|
||||
let s: Identifier = value.into();
|
||||
Self::try_from(s)
|
||||
}
|
||||
@ -466,6 +479,8 @@ pub enum CallableFunction {
|
||||
/// A plugin function,
|
||||
Plugin(Shared<FnPlugin>),
|
||||
/// A script-defined function.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
Script(Shared<crate::ast::ScriptFnDef>),
|
||||
}
|
||||
@ -600,6 +615,8 @@ impl CallableFunction {
|
||||
}
|
||||
/// Get a shared reference to a script-defined function definition.
|
||||
///
|
||||
/// Not available under `no_function`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the [`CallableFunction`] is not [`Script`][CallableFunction::Script].
|
||||
|
@ -5,6 +5,7 @@
|
||||
use crate::dynamic::{DynamicWriteLock, Variant};
|
||||
use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync};
|
||||
use crate::r#unsafe::unsafe_try_cast;
|
||||
use crate::token::Position;
|
||||
use crate::{Dynamic, EvalAltResult, NativeCallContext};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
@ -61,20 +62,33 @@ pub trait RegisterNativeFunction<Args, Result> {
|
||||
fn into_callable_function(self) -> CallableFunction;
|
||||
/// Get the type ID's of this function's parameters.
|
||||
fn param_types() -> Box<[TypeId]>;
|
||||
/// Get the type names of this function's parameters.
|
||||
/// _(METADATA)_ Get the type names of this function's parameters.
|
||||
/// Exported under the `metadata` feature only.
|
||||
#[cfg(feature = "metadata")]
|
||||
fn param_names() -> Box<[&'static str]>;
|
||||
/// Get the type ID of this function's return value.
|
||||
/// _(METADATA)_ Get the type ID of this function's return value.
|
||||
/// Exported under the `metadata` feature only.
|
||||
#[cfg(feature = "metadata")]
|
||||
fn return_type() -> TypeId;
|
||||
/// Get the type name of this function's return value.
|
||||
/// _(METADATA)_ Get the type name of this function's return value.
|
||||
/// Exported under the `metadata` feature only.
|
||||
#[cfg(feature = "metadata")]
|
||||
fn return_type_name() -> &'static str;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn is_setter(_fn_name: &str) -> bool {
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
if _fn_name.starts_with(crate::engine::FN_SET) {
|
||||
return true;
|
||||
}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
if _fn_name.starts_with(crate::engine::FN_IDX_SET) {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
macro_rules! def_register {
|
||||
() => {
|
||||
def_register!(imp from_pure :);
|
||||
@ -97,7 +111,11 @@ macro_rules! def_register {
|
||||
#[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<RET>() }
|
||||
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RET>() }
|
||||
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
|
||||
CallableFunction::$abi(Box::new(move |_: NativeCallContext, args: &mut FnCallArgs| {
|
||||
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
|
||||
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
|
||||
return EvalAltResult::ErrorAssignmentToConstant(Default::default(), Position::NONE).into();
|
||||
}
|
||||
|
||||
// The arguments are assumed to be of the correct number and types!
|
||||
let mut _drain = args.iter_mut();
|
||||
$($let $par = ($clone)(_drain.next().unwrap()); )*
|
||||
@ -122,6 +140,10 @@ macro_rules! def_register {
|
||||
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RET>() }
|
||||
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
|
||||
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
|
||||
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
|
||||
return EvalAltResult::ErrorAssignmentToConstant(Default::default(), Position::NONE).into();
|
||||
}
|
||||
|
||||
// The arguments are assumed to be of the correct number and types!
|
||||
let mut _drain = args.iter_mut();
|
||||
$($let $par = ($clone)(_drain.next().unwrap()); )*
|
||||
@ -145,7 +167,11 @@ macro_rules! def_register {
|
||||
#[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<Result<RET, Box<EvalAltResult>>>() }
|
||||
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<Result<RET, Box<EvalAltResult>>>() }
|
||||
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
|
||||
CallableFunction::$abi(Box::new(move |_: NativeCallContext, args: &mut FnCallArgs| {
|
||||
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
|
||||
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
|
||||
return EvalAltResult::ErrorAssignmentToConstant(Default::default(), Position::NONE).into();
|
||||
}
|
||||
|
||||
// The arguments are assumed to be of the correct number and types!
|
||||
let mut _drain = args.iter_mut();
|
||||
$($let $par = ($clone)(_drain.next().unwrap()); )*
|
||||
@ -167,6 +193,10 @@ macro_rules! def_register {
|
||||
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<Result<RET, Box<EvalAltResult>>>() }
|
||||
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
|
||||
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
|
||||
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
|
||||
return EvalAltResult::ErrorAssignmentToConstant(Default::default(), Position::NONE).into();
|
||||
}
|
||||
|
||||
// The arguments are assumed to be of the correct number and types!
|
||||
let mut _drain = args.iter_mut();
|
||||
$($let $par = ($clone)(_drain.next().unwrap()); )*
|
||||
|
21
src/lib.rs
21
src/lib.rs
@ -140,11 +140,11 @@ pub use utils::ImmutableString;
|
||||
|
||||
/// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most
|
||||
/// identifiers are ASCII and short, fewer than 23 characters, so they can be stored inline.
|
||||
#[cfg(not(feature = "no_smartstring_for_identifier"))]
|
||||
#[cfg(not(feature = "no_smartstring"))]
|
||||
pub type Identifier = SmartString;
|
||||
|
||||
/// An identifier in Rhai.
|
||||
#[cfg(feature = "no_smartstring_for_identifier")]
|
||||
#[cfg(feature = "no_smartstring")]
|
||||
pub type Identifier = ImmutableString;
|
||||
|
||||
/// A trait to enable registering Rust functions.
|
||||
@ -207,15 +207,17 @@ pub use optimize::OptimizationLevel;
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated = "this type is volatile and may change"]
|
||||
pub use dynamic::Variant;
|
||||
pub use dynamic::{DynamicReadLock, DynamicWriteLock, Variant};
|
||||
|
||||
// Expose internal data structures.
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated = "this function is volatile and may change"]
|
||||
pub use token::{get_next_token, parse_string_literal};
|
||||
|
||||
// Expose internal data structures.
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated = "this type is volatile and may change"]
|
||||
pub use token::{
|
||||
get_next_token, parse_string_literal, InputStream, Token, TokenizeState, TokenizerControl,
|
||||
TokenizerControlBlock,
|
||||
};
|
||||
pub use token::{InputStream, Token, TokenizeState, TokenizerControl, TokenizerControlBlock};
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[deprecated = "this type is volatile and may change"]
|
||||
@ -306,9 +308,14 @@ type StaticVec<T> = smallvec::SmallVec<[T; 4]>;
|
||||
pub type StaticVec<T> = smallvec::SmallVec<[T; 4]>;
|
||||
|
||||
#[cfg(not(feature = "internals"))]
|
||||
#[cfg(not(feature = "no_smartstring"))]
|
||||
pub(crate) type SmartString = smartstring::SmartString<smartstring::Compact>;
|
||||
|
||||
#[cfg(feature = "no_smartstring")]
|
||||
pub(crate) type SmartString = String;
|
||||
|
||||
#[cfg(feature = "internals")]
|
||||
#[cfg(not(feature = "no_smartstring"))]
|
||||
pub type SmartString = smartstring::SmartString<smartstring::Compact>;
|
||||
|
||||
// Compiler guards against mutually-exclusive feature flags
|
||||
|
@ -48,7 +48,7 @@ impl Default for FnNamespace {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FuncInfo {
|
||||
/// Function instance.
|
||||
pub func: CallableFunction,
|
||||
pub func: Shared<CallableFunction>,
|
||||
/// Function namespace.
|
||||
pub namespace: FnNamespace,
|
||||
/// Function access mode.
|
||||
@ -129,7 +129,7 @@ pub struct Module {
|
||||
/// ID identifying the module.
|
||||
id: Option<Identifier>,
|
||||
/// Is this module internal?
|
||||
internal: bool,
|
||||
pub(crate) internal: bool,
|
||||
/// Sub-modules.
|
||||
modules: BTreeMap<Identifier, Shared<Module>>,
|
||||
/// [`Module`] variables.
|
||||
@ -140,7 +140,7 @@ pub struct Module {
|
||||
functions: BTreeMap<u64, Box<FuncInfo>>,
|
||||
/// Flattened collection of all external Rust functions, native or scripted.
|
||||
/// including those in sub-modules.
|
||||
all_functions: BTreeMap<u64, CallableFunction>,
|
||||
all_functions: BTreeMap<u64, Shared<CallableFunction>>,
|
||||
/// Iterator functions, keyed by the type producing the iterator.
|
||||
type_iterators: BTreeMap<TypeId, IteratorFn>,
|
||||
/// Flattened collection of iterator functions, including those in sub-modules.
|
||||
@ -309,20 +309,6 @@ impl Module {
|
||||
self
|
||||
}
|
||||
|
||||
/// Is the [`Module`] internal?
|
||||
#[allow(dead_code)]
|
||||
#[inline(always)]
|
||||
pub(crate) fn is_internal(&self) -> bool {
|
||||
self.internal
|
||||
}
|
||||
|
||||
/// Set the internal status of the [`Module`].
|
||||
#[inline(always)]
|
||||
pub(crate) fn set_internal(&mut self, value: bool) -> &mut Self {
|
||||
self.internal = value;
|
||||
self
|
||||
}
|
||||
|
||||
/// Is the [`Module`] empty?
|
||||
///
|
||||
/// # Example
|
||||
@ -476,10 +462,7 @@ impl Module {
|
||||
/// If there is an existing function of the same name and number of arguments, it is replaced.
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[inline]
|
||||
pub(crate) fn set_script_fn(
|
||||
&mut self,
|
||||
fn_def: impl Into<Shared<crate::ast::ScriptFnDef>>,
|
||||
) -> u64 {
|
||||
pub fn set_script_fn(&mut self, fn_def: impl Into<Shared<crate::ast::ScriptFnDef>>) -> u64 {
|
||||
let fn_def = fn_def.into();
|
||||
|
||||
// None + function name + number of arguments.
|
||||
@ -497,7 +480,7 @@ impl Module {
|
||||
param_types: Default::default(),
|
||||
#[cfg(feature = "metadata")]
|
||||
param_names,
|
||||
func: fn_def.into(),
|
||||
func: Into::<CallableFunction>::into(fn_def).into(),
|
||||
}),
|
||||
);
|
||||
self.indexed = false;
|
||||
@ -695,19 +678,22 @@ impl Module {
|
||||
) -> u64 {
|
||||
let is_method = func.is_method();
|
||||
|
||||
let param_types: StaticVec<_> = arg_types
|
||||
let mut param_types: StaticVec<_> = arg_types
|
||||
.iter()
|
||||
.cloned()
|
||||
.enumerate()
|
||||
.map(|(i, type_id)| Self::map_type(!is_method || i > 0, type_id))
|
||||
.collect();
|
||||
param_types.shrink_to_fit();
|
||||
|
||||
#[cfg(feature = "metadata")]
|
||||
let param_names = _arg_names
|
||||
let mut param_names: StaticVec<_> = _arg_names
|
||||
.iter()
|
||||
.flat_map(|p| p.iter())
|
||||
.map(|&arg| self.identifiers.get(arg))
|
||||
.collect();
|
||||
#[cfg(feature = "metadata")]
|
||||
param_names.shrink_to_fit();
|
||||
|
||||
let hash_fn = calc_native_fn_hash(empty(), &name, ¶m_types);
|
||||
|
||||
@ -721,7 +707,7 @@ impl Module {
|
||||
param_types,
|
||||
#[cfg(feature = "metadata")]
|
||||
param_names,
|
||||
func,
|
||||
func: func.into(),
|
||||
}),
|
||||
);
|
||||
|
||||
@ -1119,7 +1105,7 @@ impl Module {
|
||||
/// The [`u64`] hash is returned by the [`set_native_fn`][Module::set_native_fn] call.
|
||||
#[inline(always)]
|
||||
pub(crate) fn get_fn(&self, hash_fn: u64) -> Option<&CallableFunction> {
|
||||
self.functions.get(&hash_fn).map(|f| &f.func)
|
||||
self.functions.get(&hash_fn).map(|f| f.func.as_ref())
|
||||
}
|
||||
|
||||
/// Does the particular namespace-qualified function exist in the [`Module`]?
|
||||
@ -1135,7 +1121,9 @@ impl Module {
|
||||
/// The [`u64`] hash is calculated by [`build_index`][Module::build_index].
|
||||
#[inline(always)]
|
||||
pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&CallableFunction> {
|
||||
self.all_functions.get(&hash_qualified_fn)
|
||||
self.all_functions
|
||||
.get(&hash_qualified_fn)
|
||||
.map(|f| f.as_ref())
|
||||
}
|
||||
|
||||
/// Combine another [`Module`] into this [`Module`].
|
||||
@ -1454,7 +1442,7 @@ impl Module {
|
||||
.filter(|f| f.func.is_script())
|
||||
.for_each(|f| {
|
||||
// Encapsulate AST environment
|
||||
let mut func = crate::fn_native::shared_take_or_clone(f.func.get_fn_def().clone());
|
||||
let mut func = f.func.get_fn_def().as_ref().clone();
|
||||
func.lib = Some(ast.shared_lib());
|
||||
func.mods = func_mods.clone();
|
||||
module.set_script_fn(func);
|
||||
@ -1486,7 +1474,7 @@ impl Module {
|
||||
module: &'a Module,
|
||||
path: &mut Vec<&'a str>,
|
||||
variables: &mut BTreeMap<u64, Dynamic>,
|
||||
functions: &mut BTreeMap<u64, CallableFunction>,
|
||||
functions: &mut BTreeMap<u64, Shared<CallableFunction>>,
|
||||
type_iterators: &mut BTreeMap<TypeId, IteratorFn>,
|
||||
) -> bool {
|
||||
let mut contains_indexed_global_functions = false;
|
||||
@ -1690,6 +1678,8 @@ impl DerefMut for NamespaceRef {
|
||||
impl From<StaticVec<Ident>> for NamespaceRef {
|
||||
#[inline(always)]
|
||||
fn from(path: StaticVec<Ident>) -> Self {
|
||||
let mut path = path;
|
||||
path.shrink_to_fit();
|
||||
Self { index: None, path }
|
||||
}
|
||||
}
|
||||
|
@ -390,7 +390,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
||||
x.2 = if x2.args.len() > 1 {
|
||||
mem::take(&mut x2.args[1])
|
||||
} else {
|
||||
let (value, pos) = mem::take(&mut x2.constant_args[0]);
|
||||
let (value, pos) = mem::take(&mut x2.literal_args[0]);
|
||||
Expr::DynamicConstant(Box::new(value), pos)
|
||||
};
|
||||
}
|
||||
@ -417,7 +417,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
||||
|
||||
*stmt = if preserve_result {
|
||||
// -> { expr, Noop }
|
||||
Stmt::Block(vec![Stmt::Expr(expr), Stmt::Noop(pos)], pos)
|
||||
Stmt::Block(Box::new([Stmt::Expr(expr), Stmt::Noop(pos)]), pos)
|
||||
} else {
|
||||
// -> expr
|
||||
Stmt::Expr(expr)
|
||||
@ -434,7 +434,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
||||
let else_block = mem::take(&mut *x.1).into_vec();
|
||||
*stmt = match optimize_stmt_block(else_block, state, preserve_result, true, false) {
|
||||
statements if statements.is_empty() => Stmt::Noop(x.1.position()),
|
||||
statements => Stmt::Block(statements, x.1.position()),
|
||||
statements => Stmt::Block(statements.into_boxed_slice(), x.1.position()),
|
||||
}
|
||||
}
|
||||
// if true { if_block } else { else_block } -> if_block
|
||||
@ -443,7 +443,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
||||
let if_block = mem::take(&mut *x.0).into_vec();
|
||||
*stmt = match optimize_stmt_block(if_block, state, preserve_result, true, false) {
|
||||
statements if statements.is_empty() => Stmt::Noop(x.0.position()),
|
||||
statements => Stmt::Block(statements, x.0.position()),
|
||||
statements => Stmt::Block(statements.into_boxed_slice(), x.0.position()),
|
||||
}
|
||||
}
|
||||
// if expr { if_block } else { else_block }
|
||||
@ -484,7 +484,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
||||
condition,
|
||||
Box::new((
|
||||
mem::take(&mut block.1),
|
||||
Stmt::Block(def_stmt, def_pos).into(),
|
||||
Stmt::Block(def_stmt.into_boxed_slice(), def_pos).into(),
|
||||
)),
|
||||
match_expr.position(),
|
||||
);
|
||||
@ -494,7 +494,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
||||
let statements = mem::take(&mut *block.1);
|
||||
let statements =
|
||||
optimize_stmt_block(statements.into_vec(), state, true, true, false);
|
||||
*stmt = Stmt::Block(statements, new_pos);
|
||||
*stmt = Stmt::Block(statements.into_boxed_slice(), new_pos);
|
||||
}
|
||||
} else {
|
||||
// Promote the default case
|
||||
@ -505,7 +505,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
||||
} else {
|
||||
x.1.position()
|
||||
};
|
||||
*stmt = Stmt::Block(def_stmt, def_pos);
|
||||
*stmt = Stmt::Block(def_stmt.into_boxed_slice(), def_pos);
|
||||
}
|
||||
}
|
||||
// switch
|
||||
@ -572,7 +572,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
||||
if preserve_result {
|
||||
statements.push(Stmt::Noop(pos))
|
||||
}
|
||||
*stmt = Stmt::Block(statements, pos);
|
||||
*stmt = Stmt::Block(statements.into_boxed_slice(), pos);
|
||||
} else {
|
||||
*stmt = Stmt::Noop(pos);
|
||||
};
|
||||
@ -588,7 +588,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
||||
let block_pos = body.position();
|
||||
let block = mem::take(body.statements()).into_vec();
|
||||
*stmt = Stmt::Block(
|
||||
optimize_stmt_block(block, state, false, true, false),
|
||||
optimize_stmt_block(block, state, false, true, false).into_boxed_slice(),
|
||||
block_pos,
|
||||
);
|
||||
}
|
||||
@ -611,8 +611,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
||||
Stmt::Import(expr, _, _) => optimize_expr(expr, state),
|
||||
// { block }
|
||||
Stmt::Block(statements, pos) => {
|
||||
let mut block =
|
||||
optimize_stmt_block(mem::take(statements), state, preserve_result, true, false);
|
||||
let statements = mem::take(statements).into_vec();
|
||||
let mut block = optimize_stmt_block(statements, state, preserve_result, true, false);
|
||||
|
||||
match block.as_mut_slice() {
|
||||
[] => {
|
||||
@ -624,7 +624,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
||||
state.set_dirty();
|
||||
*stmt = mem::take(s);
|
||||
}
|
||||
_ => *stmt = Stmt::Block(block, *pos),
|
||||
_ => *stmt = Stmt::Block(block.into_boxed_slice(), *pos),
|
||||
}
|
||||
}
|
||||
// try { pure try_block } catch ( var ) { catch_block } -> try_block
|
||||
@ -634,7 +634,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
||||
let try_pos = x.0.position();
|
||||
let try_block = mem::take(&mut *x.0).into_vec();
|
||||
*stmt = Stmt::Block(
|
||||
optimize_stmt_block(try_block, state, false, true, false),
|
||||
optimize_stmt_block(try_block, state, false, true, false).into_boxed_slice(),
|
||||
try_pos,
|
||||
);
|
||||
}
|
||||
@ -820,6 +820,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
x.shrink_to_fit();
|
||||
}
|
||||
// [ constant .. ]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
@ -893,12 +895,12 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
||||
if !x.is_qualified() // Non-qualified
|
||||
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations
|
||||
&& x.args_count() == 1
|
||||
&& x.constant_args.len() == 1
|
||||
&& x.constant_args[0].0.is::<ImmutableString>()
|
||||
&& x.literal_args.len() == 1
|
||||
&& x.literal_args[0].0.is::<ImmutableString>()
|
||||
&& x.name == KEYWORD_FN_PTR
|
||||
=> {
|
||||
state.set_dirty();
|
||||
let fn_ptr = FnPtr::new_unchecked(mem::take(&mut x.constant_args[0].0).as_str_ref().unwrap().into(), Default::default());
|
||||
let fn_ptr = FnPtr::new_unchecked(mem::take(&mut x.literal_args[0].0).as_str_ref().unwrap().into(), Default::default());
|
||||
*expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos);
|
||||
}
|
||||
|
||||
@ -916,7 +918,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
||||
//&& !is_valid_identifier(x.name.chars()) // cannot be scripted
|
||||
=> {
|
||||
let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap())
|
||||
.chain(x.constant_args.iter().map(|(v, _)| v).cloned())
|
||||
.chain(x.literal_args.iter().map(|(v, _)| v).cloned())
|
||||
.collect();
|
||||
|
||||
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
|
||||
@ -943,10 +945,11 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
||||
while x.args.last().map(Expr::is_constant).unwrap_or(false) {
|
||||
let arg = x.args.pop().unwrap();
|
||||
let arg_pos = arg.position();
|
||||
x.constant_args.insert(0, (arg.get_constant_value().unwrap(), arg_pos));
|
||||
x.literal_args.insert(0, (arg.get_constant_value().unwrap(), arg_pos));
|
||||
}
|
||||
|
||||
x.args.shrink_to_fit();
|
||||
x.literal_args.shrink_to_fit();
|
||||
}
|
||||
|
||||
// Eagerly call functions
|
||||
@ -963,7 +966,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
||||
|
||||
if !has_script_fn {
|
||||
let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap())
|
||||
.chain(x.constant_args.iter().map(|(v, _)| v).cloned())
|
||||
.chain(x.literal_args.iter().map(|(v, _)| v).cloned())
|
||||
.collect();
|
||||
|
||||
// Save the typename of the first argument if it is `type_of()`
|
||||
@ -1002,10 +1005,11 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
||||
while x.args.last().map(Expr::is_constant).unwrap_or(false) {
|
||||
let arg = x.args.pop().unwrap();
|
||||
let arg_pos = arg.position();
|
||||
x.constant_args.insert(0, (arg.get_constant_value().unwrap(), arg_pos));
|
||||
x.literal_args.insert(0, (arg.get_constant_value().unwrap(), arg_pos));
|
||||
}
|
||||
|
||||
x.args.shrink_to_fit();
|
||||
x.literal_args.shrink_to_fit();
|
||||
}
|
||||
|
||||
// constant-name
|
||||
@ -1020,7 +1024,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
||||
|
||||
// Custom syntax
|
||||
Expr::Custom(x, _) => {
|
||||
if x.scope_delta != 0 {
|
||||
if x.scope_changed {
|
||||
state.propagate_constants = false;
|
||||
}
|
||||
x.keywords.iter_mut().for_each(|expr| optimize_expr(expr, state));
|
||||
@ -1052,10 +1056,12 @@ fn optimize_top_level(
|
||||
scope.iter().for_each(|(name, constant, value)| {
|
||||
if !constant {
|
||||
state.push_var(name, AccessMode::ReadWrite, Expr::Unit(Position::NONE));
|
||||
} else if let Some(val) = map_dynamic_to_expr(value, Position::NONE) {
|
||||
state.push_var(name, AccessMode::ReadOnly, val);
|
||||
} else {
|
||||
state.push_var(name, AccessMode::ReadOnly, Expr::Unit(Position::NONE));
|
||||
state.push_var(
|
||||
name,
|
||||
AccessMode::ReadOnly,
|
||||
Expr::DynamicConstant(Box::new(value), Position::NONE),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -5,9 +5,6 @@ use crate::{def_package, EvalAltResult, Position, INT};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use crate::FLOAT;
|
||||
|
||||
#[cfg(feature = "no_std")]
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
use num_traits::Float;
|
||||
@ -77,7 +74,7 @@ macro_rules! gen_arithmetic_functions {
|
||||
} else if y < 0 {
|
||||
Err(make_err(format!("Integer raised to a negative index: {} ~ {}", x, y)))
|
||||
} else {
|
||||
x.checked_pow(y as u32).ok_or_else(|| make_err(format!("Power overflow: {} ~ {}", x, y)))
|
||||
x.checked_pow(y as u32).ok_or_else(|| make_err(format!("Exponential overflow: {} ~ {}", x, y)))
|
||||
}
|
||||
} else {
|
||||
Ok(x.pow(y as u32))
|
||||
@ -423,23 +420,13 @@ mod f64_functions {
|
||||
1
|
||||
}
|
||||
}
|
||||
#[rhai_fn(name = "**", return_raw)]
|
||||
pub fn pow_f_i(x: FLOAT, y: INT) -> Result<FLOAT, Box<EvalAltResult>> {
|
||||
if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) {
|
||||
Err(make_err(format!(
|
||||
"Number raised to too large an index: {} ~ {}",
|
||||
x, y
|
||||
)))
|
||||
} else {
|
||||
Ok(x.powi(y as i32))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "decimal")]
|
||||
#[export_module]
|
||||
pub mod decimal_functions {
|
||||
use rust_decimal::{prelude::Zero, Decimal};
|
||||
use num_traits::Pow;
|
||||
use rust_decimal::{prelude::Zero, Decimal, MathematicalOps};
|
||||
|
||||
#[rhai_fn(skip, return_raw)]
|
||||
pub fn add(x: Decimal, y: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
|
||||
@ -495,6 +482,15 @@ pub mod decimal_functions {
|
||||
Ok(x % y)
|
||||
}
|
||||
}
|
||||
#[rhai_fn(skip, return_raw)]
|
||||
pub fn power(x: Decimal, y: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
|
||||
if cfg!(not(feature = "unchecked")) {
|
||||
x.checked_powd(y)
|
||||
.ok_or_else(|| make_err(format!("Exponential overflow: {} + {}", x, y)))
|
||||
} else {
|
||||
Ok(x.pow(y))
|
||||
}
|
||||
}
|
||||
#[rhai_fn(name = "-")]
|
||||
pub fn neg(x: Decimal) -> Decimal {
|
||||
-x
|
||||
|
@ -26,7 +26,7 @@ where
|
||||
if r == from {
|
||||
return EvalAltResult::ErrorInFunctionCall(
|
||||
"range".to_string(),
|
||||
"".to_string(),
|
||||
Default::default(),
|
||||
Box::new(EvalAltResult::ErrorArithmetic(
|
||||
"step value cannot be zero".to_string(),
|
||||
crate::Position::NONE,
|
||||
@ -246,7 +246,6 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
|
||||
#[cfg(feature = "decimal")]
|
||||
{
|
||||
use rust_decimal::Decimal;
|
||||
use num_traits::Zero;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
|
||||
struct StepDecimalRange(Decimal, Decimal, Decimal);
|
||||
|
45
src/packages/lang_core.rs
Normal file
45
src/packages/lang_core.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use crate::def_package;
|
||||
use crate::dynamic::Tag;
|
||||
use crate::plugin::*;
|
||||
use crate::{Dynamic, EvalAltResult, INT};
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
|
||||
#[export_module]
|
||||
mod core_functions {
|
||||
#[rhai_fn(name = "tag", get = "tag", pure)]
|
||||
pub fn get_tag(value: &mut Dynamic) -> INT {
|
||||
value.tag() as INT
|
||||
}
|
||||
#[rhai_fn(name = "set_tag", set = "tag", return_raw)]
|
||||
pub fn set_tag(value: &mut Dynamic, tag: INT) -> Result<(), Box<EvalAltResult>> {
|
||||
if tag < Tag::MIN as INT {
|
||||
Err(Box::new(EvalAltResult::ErrorArithmetic(
|
||||
format!(
|
||||
"{} is too small to fit into a tag (must be between {} and {})",
|
||||
tag,
|
||||
Tag::MIN,
|
||||
Tag::MAX
|
||||
),
|
||||
Position::NONE,
|
||||
)))
|
||||
} else if tag > Tag::MAX as INT {
|
||||
Err(Box::new(EvalAltResult::ErrorArithmetic(
|
||||
format!(
|
||||
"{} is too large to fit into a tag (must be between {} and {})",
|
||||
tag,
|
||||
Tag::MIN,
|
||||
Tag::MAX
|
||||
),
|
||||
Position::NONE,
|
||||
)))
|
||||
} else {
|
||||
value.set_tag(tag as Tag);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def_package!(crate:LanguageCorePackage:"Language core functions.", lib, {
|
||||
combine_with_exported_module!(lib, "language_core", core_functions);
|
||||
});
|
@ -222,6 +222,7 @@ mod float_functions {
|
||||
pub fn log(x: FLOAT, base: FLOAT) -> FLOAT {
|
||||
x.log(base)
|
||||
}
|
||||
#[rhai_fn(name = "log")]
|
||||
pub fn log10(x: FLOAT) -> FLOAT {
|
||||
x.log10()
|
||||
}
|
||||
@ -305,10 +306,33 @@ mod float_functions {
|
||||
mod decimal_functions {
|
||||
use rust_decimal::{
|
||||
prelude::{FromStr, RoundingStrategy},
|
||||
Decimal,
|
||||
Decimal, MathematicalOps,
|
||||
};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[rhai_fn(return_raw)]
|
||||
pub fn sqrt(x: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
|
||||
if cfg!(not(feature = "unchecked")) {
|
||||
x.sqrt()
|
||||
.ok_or_else(|| make_err(format!("Error taking the square root of {}", x,)))
|
||||
} else {
|
||||
Ok(x.sqrt().unwrap())
|
||||
}
|
||||
}
|
||||
#[rhai_fn(return_raw)]
|
||||
pub fn exp(x: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
|
||||
if cfg!(not(feature = "unchecked")) {
|
||||
if x > Decimal::from_parts(117578, 0, 0, false, 4) {
|
||||
Err(make_err(format!("Exponential overflow: e ** {}", x,)))
|
||||
} else {
|
||||
Ok(x.exp())
|
||||
}
|
||||
} else {
|
||||
Ok(x.exp())
|
||||
}
|
||||
}
|
||||
pub fn ln(x: Decimal) -> Decimal {
|
||||
x.ln()
|
||||
}
|
||||
#[rhai_fn(name = "floor", get = "floor")]
|
||||
pub fn floor(x: Decimal) -> Decimal {
|
||||
x.floor()
|
||||
@ -424,6 +448,8 @@ mod decimal_functions {
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
pub mod float {
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[rhai_fn(name = "to_decimal", return_raw)]
|
||||
pub fn f32_to_decimal(x: f32) -> Result<Decimal, Box<EvalAltResult>> {
|
||||
Decimal::try_from(x).map_err(|_| {
|
||||
|
@ -6,6 +6,7 @@ pub(crate) mod arithmetic;
|
||||
mod array_basic;
|
||||
mod fn_basic;
|
||||
mod iter_basic;
|
||||
mod lang_core;
|
||||
mod logic;
|
||||
mod map_basic;
|
||||
mod math_basic;
|
||||
@ -86,6 +87,7 @@ macro_rules! def_package {
|
||||
}
|
||||
|
||||
impl $package {
|
||||
#[allow(dead_code)]
|
||||
pub fn new() -> Self {
|
||||
let mut module = $root::Module::new();
|
||||
<Self as $root::packages::Package>::init(&mut module);
|
||||
|
@ -1,6 +1,7 @@
|
||||
use super::arithmetic::ArithmeticPackage;
|
||||
use super::fn_basic::BasicFnPackage;
|
||||
use super::iter_basic::BasicIteratorPackage;
|
||||
use super::lang_core::LanguageCorePackage;
|
||||
use super::logic::LogicPackage;
|
||||
use super::string_basic::BasicStringPackage;
|
||||
#[cfg(feature = "no_std")]
|
||||
@ -9,6 +10,7 @@ use std::prelude::v1::*;
|
||||
use crate::def_package;
|
||||
|
||||
def_package!(crate:CorePackage:"_Core_ package containing basic facilities.", lib, {
|
||||
LanguageCorePackage::init(lib);
|
||||
ArithmeticPackage::init(lib);
|
||||
LogicPackage::init(lib);
|
||||
BasicStringPackage::init(lib);
|
||||
|
@ -348,6 +348,8 @@ fn parse_fn_call(
|
||||
FnCallHashes::from_native(hash)
|
||||
};
|
||||
|
||||
args.shrink_to_fit();
|
||||
|
||||
return Ok(Expr::FnCall(
|
||||
Box::new(FnCallExpr {
|
||||
name: state.get_identifier(id),
|
||||
@ -393,6 +395,8 @@ fn parse_fn_call(
|
||||
FnCallHashes::from_native(hash)
|
||||
};
|
||||
|
||||
args.shrink_to_fit();
|
||||
|
||||
return Ok(Expr::FnCall(
|
||||
Box::new(FnCallExpr {
|
||||
name: state.get_identifier(id),
|
||||
@ -1066,6 +1070,7 @@ fn parse_primary(
|
||||
}
|
||||
}
|
||||
|
||||
segments.shrink_to_fit();
|
||||
Expr::InterpolatedString(Box::new(segments))
|
||||
}
|
||||
|
||||
@ -1344,6 +1349,7 @@ fn parse_unary(
|
||||
expr => {
|
||||
let mut args = StaticVec::new();
|
||||
args.push(expr);
|
||||
args.shrink_to_fit();
|
||||
|
||||
Ok(Expr::FnCall(
|
||||
Box::new(FnCallExpr {
|
||||
@ -1370,6 +1376,7 @@ fn parse_unary(
|
||||
expr => {
|
||||
let mut args = StaticVec::new();
|
||||
args.push(expr);
|
||||
args.shrink_to_fit();
|
||||
|
||||
Ok(Expr::FnCall(
|
||||
Box::new(FnCallExpr {
|
||||
@ -1387,8 +1394,8 @@ fn parse_unary(
|
||||
Token::Bang => {
|
||||
let pos = eat_token(input, Token::Bang);
|
||||
let mut args = StaticVec::new();
|
||||
let expr = parse_unary(input, state, lib, settings.level_up())?;
|
||||
args.push(expr);
|
||||
args.push(parse_unary(input, state, lib, settings.level_up())?);
|
||||
args.shrink_to_fit();
|
||||
|
||||
Ok(Expr::FnCall(
|
||||
Box::new(FnCallExpr {
|
||||
@ -1461,24 +1468,10 @@ fn make_assignment_stmt<'a>(
|
||||
Expr::Index(x, _) | Expr::Dot(x, _) => {
|
||||
match check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _))) {
|
||||
None => match &x.lhs {
|
||||
// var[???] (non-indexed) = rhs, var.??? (non-indexed) = rhs
|
||||
Expr::Variable(None, _, x) if x.0.is_none() => {
|
||||
// var[???] = rhs, var.??? = rhs
|
||||
Expr::Variable(_, _, _) => {
|
||||
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos))
|
||||
}
|
||||
// var[???] (indexed) = rhs, var.??? (indexed) = rhs
|
||||
Expr::Variable(i, var_pos, x) => {
|
||||
let (index, _, name) = x.as_ref();
|
||||
let index = i.map_or_else(|| index.unwrap().get(), |n| n.get() as usize);
|
||||
match state.stack[state.stack.len() - index].1 {
|
||||
AccessMode::ReadWrite => {
|
||||
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos))
|
||||
}
|
||||
// Constant values cannot be assigned to
|
||||
AccessMode::ReadOnly => {
|
||||
Err(PERR::AssignmentToConstant(name.to_string()).into_err(*var_pos))
|
||||
}
|
||||
}
|
||||
}
|
||||
// expr[???] = rhs, expr.??? = rhs
|
||||
expr => {
|
||||
Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(expr.position()))
|
||||
@ -1730,6 +1723,7 @@ fn parse_binary_op(
|
||||
let mut args = StaticVec::new();
|
||||
args.push(root);
|
||||
args.push(rhs);
|
||||
args.shrink_to_fit();
|
||||
|
||||
root = match op_token {
|
||||
Token::Plus
|
||||
@ -1782,6 +1776,7 @@ fn parse_binary_op(
|
||||
// Swap the arguments
|
||||
let current_lhs = args.remove(0);
|
||||
args.push(current_lhs);
|
||||
args.shrink_to_fit();
|
||||
|
||||
// Convert into a call to `contains`
|
||||
let hash = calc_fn_hash(empty(), OP_CONTAINS, 2);
|
||||
@ -1836,25 +1831,15 @@ fn parse_custom_syntax(
|
||||
) -> Result<Expr, ParseError> {
|
||||
let mut keywords: StaticVec<Expr> = Default::default();
|
||||
let mut segments: StaticVec<_> = Default::default();
|
||||
let mut tokens: Vec<_> = Default::default();
|
||||
let mut tokens: StaticVec<_> = Default::default();
|
||||
|
||||
// Adjust the variables stack
|
||||
match syntax.scope_delta {
|
||||
delta if delta > 0 => {
|
||||
// Add enough empty variable names to the stack.
|
||||
// Empty variable names act as a barrier so earlier variables will not be matched.
|
||||
// Variable searches stop at the first empty variable name.
|
||||
let empty = state.get_identifier("");
|
||||
state.stack.resize(
|
||||
state.stack.len() + delta as usize,
|
||||
(empty, AccessMode::ReadWrite),
|
||||
);
|
||||
}
|
||||
delta if delta < 0 && state.stack.len() <= delta.abs() as usize => state.stack.clear(),
|
||||
delta if delta < 0 => state
|
||||
.stack
|
||||
.truncate(state.stack.len() - delta.abs() as usize),
|
||||
_ => (),
|
||||
if syntax.scope_changed {
|
||||
// Add an empty variable name to the stack.
|
||||
// Empty variable names act as a barrier so earlier variables will not be matched.
|
||||
// Variable searches stop at the first empty variable name.
|
||||
let empty = state.get_identifier("");
|
||||
state.stack.push((empty, AccessMode::ReadWrite));
|
||||
}
|
||||
|
||||
let parse_func = &syntax.parse;
|
||||
@ -1920,11 +1905,14 @@ fn parse_custom_syntax(
|
||||
}
|
||||
}
|
||||
|
||||
keywords.shrink_to_fit();
|
||||
tokens.shrink_to_fit();
|
||||
|
||||
Ok(Expr::Custom(
|
||||
Box::new(CustomExpr {
|
||||
keywords,
|
||||
tokens,
|
||||
scope_delta: syntax.scope_delta,
|
||||
scope_changed: syntax.scope_changed,
|
||||
}),
|
||||
pos,
|
||||
))
|
||||
@ -2366,7 +2354,7 @@ fn parse_export(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Stmt::Export(exports, settings.pos))
|
||||
Ok(Stmt::Export(exports.into_boxed_slice(), settings.pos))
|
||||
}
|
||||
|
||||
/// Parse a statement block.
|
||||
@ -2467,7 +2455,7 @@ fn parse_block(
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
state.modules.truncate(prev_mods_len);
|
||||
|
||||
Ok(Stmt::Block(statements, settings.pos))
|
||||
Ok(Stmt::Block(statements.into_boxed_slice(), settings.pos))
|
||||
}
|
||||
|
||||
/// Parse an expression as a statement.
|
||||
@ -2820,7 +2808,8 @@ fn parse_fn(
|
||||
}
|
||||
.into();
|
||||
|
||||
let params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect();
|
||||
let mut params: StaticVec<_> = params.into_iter().map(|(p, _)| p).collect();
|
||||
params.shrink_to_fit();
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
let externals = state
|
||||
@ -2874,6 +2863,8 @@ fn make_curry_from_externals(
|
||||
));
|
||||
});
|
||||
|
||||
args.shrink_to_fit();
|
||||
|
||||
let expr = Expr::FnCall(
|
||||
Box::new(FnCallExpr {
|
||||
name: state.get_identifier(crate::engine::KEYWORD_FN_PTR_CURRY),
|
||||
@ -2967,7 +2958,7 @@ fn parse_anon_fn(
|
||||
Default::default()
|
||||
};
|
||||
|
||||
let params: StaticVec<_> = if cfg!(not(feature = "no_closure")) {
|
||||
let mut params: StaticVec<_> = if cfg!(not(feature = "no_closure")) {
|
||||
externals
|
||||
.iter()
|
||||
.cloned()
|
||||
@ -2976,6 +2967,7 @@ fn parse_anon_fn(
|
||||
} else {
|
||||
params.into_iter().map(|(v, _)| v).collect()
|
||||
};
|
||||
params.shrink_to_fit();
|
||||
|
||||
// Create unique function name by hashing the script body plus the parameters.
|
||||
let hasher = &mut get_hasher();
|
||||
@ -3135,22 +3127,22 @@ impl Engine {
|
||||
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option<Expr> {
|
||||
match value.0 {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Union::Float(value, _) => Some(Expr::FloatConstant(value, pos)),
|
||||
Union::Float(value, _, _) => Some(Expr::FloatConstant(value, pos)),
|
||||
|
||||
#[cfg(feature = "decimal")]
|
||||
Union::Decimal(value, _) => Some(Expr::DynamicConstant(Box::new((*value).into()), pos)),
|
||||
Union::Decimal(value, _, _) => Some(Expr::DynamicConstant(Box::new((*value).into()), pos)),
|
||||
|
||||
Union::Unit(_, _) => Some(Expr::Unit(pos)),
|
||||
Union::Int(value, _) => Some(Expr::IntegerConstant(value, pos)),
|
||||
Union::Char(value, _) => Some(Expr::CharConstant(value, pos)),
|
||||
Union::Str(value, _) => Some(Expr::StringConstant(value, pos)),
|
||||
Union::Bool(value, _) => Some(Expr::BoolConstant(value, pos)),
|
||||
Union::Unit(_, _, _) => Some(Expr::Unit(pos)),
|
||||
Union::Int(value, _, _) => Some(Expr::IntegerConstant(value, pos)),
|
||||
Union::Char(value, _, _) => Some(Expr::CharConstant(value, pos)),
|
||||
Union::Str(value, _, _) => Some(Expr::StringConstant(value, pos)),
|
||||
Union::Bool(value, _, _) => Some(Expr::BoolConstant(value, pos)),
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(array, _) => Some(Expr::DynamicConstant(Box::new((*array).into()), pos)),
|
||||
Union::Array(array, _, _) => Some(Expr::DynamicConstant(Box::new((*array).into()), pos)),
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(map, _) => Some(Expr::DynamicConstant(Box::new((*map).into()), pos)),
|
||||
Union::Map(map, _, _) => Some(Expr::DynamicConstant(Box::new((*map).into()), pos)),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
|
@ -115,7 +115,7 @@ pub fn from_dynamic<'de, T: Deserialize<'de>>(
|
||||
|
||||
impl Error for Box<EvalAltResult> {
|
||||
fn custom<T: fmt::Display>(err: T) -> Self {
|
||||
LexError::ImproperSymbol("".to_string(), err.to_string())
|
||||
LexError::ImproperSymbol(Default::default(), err.to_string())
|
||||
.into_err(Position::NONE)
|
||||
.into()
|
||||
}
|
||||
@ -126,53 +126,53 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
|
||||
|
||||
fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
|
||||
match &self.value.0 {
|
||||
Union::Unit(_, _) => self.deserialize_unit(visitor),
|
||||
Union::Bool(_, _) => self.deserialize_bool(visitor),
|
||||
Union::Str(_, _) => self.deserialize_str(visitor),
|
||||
Union::Char(_, _) => self.deserialize_char(visitor),
|
||||
Union::Unit(_, _, _) => self.deserialize_unit(visitor),
|
||||
Union::Bool(_, _, _) => self.deserialize_bool(visitor),
|
||||
Union::Str(_, _, _) => self.deserialize_str(visitor),
|
||||
Union::Char(_, _, _) => self.deserialize_char(visitor),
|
||||
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
Union::Int(_, _) => self.deserialize_i64(visitor),
|
||||
Union::Int(_, _, _) => self.deserialize_i64(visitor),
|
||||
#[cfg(feature = "only_i32")]
|
||||
Union::Int(_, _) => self.deserialize_i32(visitor),
|
||||
Union::Int(_, _, _) => self.deserialize_i32(visitor),
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
#[cfg(not(feature = "f32_float"))]
|
||||
Union::Float(_, _) => self.deserialize_f64(visitor),
|
||||
Union::Float(_, _, _) => self.deserialize_f64(visitor),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
#[cfg(feature = "f32_float")]
|
||||
Union::Float(_, _) => self.deserialize_f32(visitor),
|
||||
Union::Float(_, _, _) => self.deserialize_f32(visitor),
|
||||
|
||||
#[cfg(feature = "decimal")]
|
||||
#[cfg(not(feature = "f32_float"))]
|
||||
Union::Decimal(_, _) => self.deserialize_f64(visitor),
|
||||
Union::Decimal(_, _, _) => self.deserialize_f64(visitor),
|
||||
#[cfg(feature = "decimal")]
|
||||
#[cfg(feature = "f32_float")]
|
||||
Union::Decimal(_, _) => self.deserialize_f32(visitor),
|
||||
Union::Decimal(_, _, _) => self.deserialize_f32(visitor),
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(_, _) => self.deserialize_seq(visitor),
|
||||
Union::Array(_, _, _) => self.deserialize_seq(visitor),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(_, _) => self.deserialize_map(visitor),
|
||||
Union::FnPtr(_, _) => self.type_error(),
|
||||
Union::Map(_, _, _) => self.deserialize_map(visitor),
|
||||
Union::FnPtr(_, _, _) => self.type_error(),
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
Union::TimeStamp(_, _) => self.type_error(),
|
||||
Union::TimeStamp(_, _, _) => self.type_error(),
|
||||
|
||||
Union::Variant(value, _) if value.is::<i8>() => self.deserialize_i8(visitor),
|
||||
Union::Variant(value, _) if value.is::<i16>() => self.deserialize_i16(visitor),
|
||||
Union::Variant(value, _) if value.is::<i32>() => self.deserialize_i32(visitor),
|
||||
Union::Variant(value, _) if value.is::<i64>() => self.deserialize_i64(visitor),
|
||||
Union::Variant(value, _) if value.is::<i128>() => self.deserialize_i128(visitor),
|
||||
Union::Variant(value, _) if value.is::<u8>() => self.deserialize_u8(visitor),
|
||||
Union::Variant(value, _) if value.is::<u16>() => self.deserialize_u16(visitor),
|
||||
Union::Variant(value, _) if value.is::<u32>() => self.deserialize_u32(visitor),
|
||||
Union::Variant(value, _) if value.is::<u64>() => self.deserialize_u64(visitor),
|
||||
Union::Variant(value, _) if value.is::<u128>() => self.deserialize_u128(visitor),
|
||||
Union::Variant(value, _, _) if value.is::<i8>() => self.deserialize_i8(visitor),
|
||||
Union::Variant(value, _, _) if value.is::<i16>() => self.deserialize_i16(visitor),
|
||||
Union::Variant(value, _, _) if value.is::<i32>() => self.deserialize_i32(visitor),
|
||||
Union::Variant(value, _, _) if value.is::<i64>() => self.deserialize_i64(visitor),
|
||||
Union::Variant(value, _, _) if value.is::<i128>() => self.deserialize_i128(visitor),
|
||||
Union::Variant(value, _, _) if value.is::<u8>() => self.deserialize_u8(visitor),
|
||||
Union::Variant(value, _, _) if value.is::<u16>() => self.deserialize_u16(visitor),
|
||||
Union::Variant(value, _, _) if value.is::<u32>() => self.deserialize_u32(visitor),
|
||||
Union::Variant(value, _, _) if value.is::<u64>() => self.deserialize_u64(visitor),
|
||||
Union::Variant(value, _, _) if value.is::<u128>() => self.deserialize_u128(visitor),
|
||||
|
||||
Union::Variant(_, _) => self.type_error(),
|
||||
Union::Variant(_, _, _) => self.type_error(),
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
Union::Shared(_, _) => self.type_error(),
|
||||
Union::Shared(_, _, _) => self.type_error(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,26 +12,26 @@ use serde::ser::SerializeMap;
|
||||
impl Serialize for Dynamic {
|
||||
fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
|
||||
match &self.0 {
|
||||
Union::Unit(_, _) => ser.serialize_unit(),
|
||||
Union::Bool(x, _) => ser.serialize_bool(*x),
|
||||
Union::Str(s, _) => ser.serialize_str(s.as_str()),
|
||||
Union::Char(c, _) => ser.serialize_str(&c.to_string()),
|
||||
Union::Unit(_, _, _) => ser.serialize_unit(),
|
||||
Union::Bool(x, _, _) => ser.serialize_bool(*x),
|
||||
Union::Str(s, _, _) => ser.serialize_str(s.as_str()),
|
||||
Union::Char(c, _, _) => ser.serialize_str(&c.to_string()),
|
||||
|
||||
#[cfg(not(feature = "only_i32"))]
|
||||
Union::Int(x, _) => ser.serialize_i64(*x),
|
||||
Union::Int(x, _, _) => ser.serialize_i64(*x),
|
||||
#[cfg(feature = "only_i32")]
|
||||
Union::Int(x, _) => ser.serialize_i32(*x),
|
||||
Union::Int(x, _, _) => ser.serialize_i32(*x),
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
#[cfg(not(feature = "f32_float"))]
|
||||
Union::Float(x, _) => ser.serialize_f64(**x),
|
||||
Union::Float(x, _, _) => ser.serialize_f64(**x),
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
#[cfg(feature = "f32_float")]
|
||||
Union::Float(x, _) => ser.serialize_f32(**x),
|
||||
Union::Float(x, _, _) => ser.serialize_f32(**x),
|
||||
|
||||
#[cfg(feature = "decimal")]
|
||||
#[cfg(not(feature = "f32_float"))]
|
||||
Union::Decimal(x, _) => {
|
||||
Union::Decimal(x, _, _) => {
|
||||
use rust_decimal::prelude::ToPrimitive;
|
||||
|
||||
if let Some(v) = x.to_f64() {
|
||||
@ -42,7 +42,7 @@ impl Serialize for Dynamic {
|
||||
}
|
||||
#[cfg(feature = "decimal")]
|
||||
#[cfg(feature = "f32_float")]
|
||||
Union::Decimal(x, _) => {
|
||||
Union::Decimal(x, _, _) => {
|
||||
use rust_decimal::prelude::ToPrimitive;
|
||||
|
||||
if let Some(v) = x.to_f32() {
|
||||
@ -53,27 +53,27 @@ impl Serialize for Dynamic {
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Union::Array(a, _) => (**a).serialize(ser),
|
||||
Union::Array(a, _, _) => (**a).serialize(ser),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Union::Map(m, _) => {
|
||||
Union::Map(m, _, _) => {
|
||||
let mut map = ser.serialize_map(Some(m.len()))?;
|
||||
for (k, v) in m.iter() {
|
||||
map.serialize_entry(k.as_str(), v)?;
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
Union::FnPtr(f, _) => ser.serialize_str(f.fn_name()),
|
||||
Union::FnPtr(f, _, _) => ser.serialize_str(f.fn_name()),
|
||||
#[cfg(not(feature = "no_std"))]
|
||||
Union::TimeStamp(x, _) => ser.serialize_str(x.as_ref().type_name()),
|
||||
Union::TimeStamp(x, _, _) => ser.serialize_str(x.as_ref().type_name()),
|
||||
|
||||
Union::Variant(v, _) => ser.serialize_str((***v).type_name()),
|
||||
Union::Variant(v, _, _) => ser.serialize_str((***v).type_name()),
|
||||
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(not(feature = "sync"))]
|
||||
Union::Shared(cell, _) => cell.borrow().serialize(ser),
|
||||
Union::Shared(cell, _, _) => cell.borrow().serialize(ser),
|
||||
#[cfg(not(feature = "no_closure"))]
|
||||
#[cfg(feature = "sync")]
|
||||
Union::Shared(cell, _) => cell.read().unwrap().serialize(ser),
|
||||
Union::Shared(cell, _, _) => cell.read().unwrap().serialize(ser),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -87,28 +87,33 @@ pub struct CustomSyntax {
|
||||
pub parse: Box<FnCustomSyntaxParse>,
|
||||
/// Custom syntax implementation function.
|
||||
pub func: Shared<FnCustomSyntaxEval>,
|
||||
/// Delta number of variables in the scope.
|
||||
pub scope_delta: isize,
|
||||
/// Any variables added/removed in the scope?
|
||||
pub scope_changed: bool,
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
/// Register a custom syntax with the [`Engine`].
|
||||
///
|
||||
/// * `keywords` holds a slice of strings that define the custom syntax.
|
||||
/// * `new_vars` is the number of new variables declared by this custom syntax, or the number of variables removed (if negative).
|
||||
/// * `scope_changed` specifies variables have been added/removed by this custom syntax.
|
||||
/// * `func` is the implementation function.
|
||||
///
|
||||
/// # Notes
|
||||
/// # Caveat - Do not change beyond block scope
|
||||
///
|
||||
/// If `new_vars` is positive, then a number of new variables are expected to be pushed into the
|
||||
/// current [`Scope`][crate::Scope].
|
||||
/// If `scope_changed` is `true`, then the current [`Scope`][crate::Scope] is assumed to be
|
||||
/// modified by this custom syntax.
|
||||
///
|
||||
/// If `new_vars` is negative, then it is expected that only the top `new_vars` variables in the
|
||||
/// current [`Scope`][crate::Scope] will be _popped_. Do not randomly remove variables.
|
||||
/// Adding new variables and/or removing variables are allowed. Simply modifying the values of
|
||||
/// variables does NOT count, so `false` should be passed in this case.
|
||||
///
|
||||
/// However, only variables declared within the current _block scope_ should be touched,
|
||||
/// since they all go away at the end of the block.
|
||||
///
|
||||
/// Variables in parent blocks should be left untouched as they persist beyond the current block.
|
||||
pub fn register_custom_syntax<S: AsRef<str> + Into<Identifier>>(
|
||||
&mut self,
|
||||
keywords: &[S],
|
||||
new_vars: isize,
|
||||
scope_changed: bool,
|
||||
func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static,
|
||||
) -> Result<&mut Self, ParseError> {
|
||||
let keywords = keywords.as_ref();
|
||||
@ -203,7 +208,7 @@ impl Engine {
|
||||
Ok(Some(segments[stream.len()].clone()))
|
||||
}
|
||||
},
|
||||
new_vars,
|
||||
scope_changed,
|
||||
func,
|
||||
);
|
||||
|
||||
@ -215,7 +220,7 @@ impl Engine {
|
||||
///
|
||||
/// This function is very low level.
|
||||
///
|
||||
/// * `new_vars` is the number of new variables declared by this custom syntax, or the number of variables removed (if negative).
|
||||
/// * `scope_changed` specifies variables have been added/removed by this custom syntax.
|
||||
/// * `parse` is the parsing function.
|
||||
/// * `func` is the implementation function.
|
||||
///
|
||||
@ -227,16 +232,17 @@ impl Engine {
|
||||
parse: impl Fn(&[ImmutableString], &str) -> Result<Option<ImmutableString>, ParseError>
|
||||
+ SendSync
|
||||
+ 'static,
|
||||
new_vars: isize,
|
||||
scope_changed: bool,
|
||||
func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static,
|
||||
) -> &mut Self {
|
||||
let syntax = CustomSyntax {
|
||||
parse: Box::new(parse),
|
||||
func: (Box::new(func) as Box<FnCustomSyntaxEval>).into(),
|
||||
scope_delta: new_vars,
|
||||
};
|
||||
|
||||
self.custom_syntax.insert(key.into(), syntax);
|
||||
self.custom_syntax.insert(
|
||||
key.into(),
|
||||
Box::new(CustomSyntax {
|
||||
parse: Box::new(parse),
|
||||
func: (Box::new(func) as Box<FnCustomSyntaxEval>).into(),
|
||||
scope_changed,
|
||||
}),
|
||||
);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
22
src/token.rs
22
src/token.rs
@ -973,10 +973,6 @@ pub struct TokenizeState {
|
||||
pub comment_level: usize,
|
||||
/// Include comments?
|
||||
pub include_comments: bool,
|
||||
/// Disable doc-comments?
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
pub disable_doc_comments: bool,
|
||||
/// Is the current tokenizer position within the text stream of an interpolated string?
|
||||
pub is_within_text_terminated_by: Option<char>,
|
||||
}
|
||||
@ -1357,12 +1353,11 @@ fn get_next_token_inner(
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
let include_comments =
|
||||
if !state.disable_doc_comments && is_doc_comment(comment.as_ref().unwrap()) {
|
||||
true
|
||||
} else {
|
||||
include_comments
|
||||
};
|
||||
let include_comments = if is_doc_comment(comment.as_ref().unwrap()) {
|
||||
true
|
||||
} else {
|
||||
include_comments
|
||||
};
|
||||
|
||||
if include_comments {
|
||||
return Some((Token::Comment(comment.unwrap()), start_pos));
|
||||
@ -1705,7 +1700,7 @@ fn get_next_token_inner(
|
||||
let mut comment = match stream.peek_next() {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
Some('/') if !state.disable_doc_comments => {
|
||||
Some('/') => {
|
||||
eat_next(stream, pos);
|
||||
|
||||
// Long streams of `///...` are not doc-comments
|
||||
@ -1740,7 +1735,7 @@ fn get_next_token_inner(
|
||||
let mut comment = match stream.peek_next() {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
Some('*') if !state.disable_doc_comments => {
|
||||
Some('*') => {
|
||||
eat_next(stream, pos);
|
||||
|
||||
// Long streams of `/****...` are not doc-comments
|
||||
@ -2239,9 +2234,6 @@ impl Engine {
|
||||
non_unary: false,
|
||||
comment_level: 0,
|
||||
include_comments: false,
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
#[cfg(feature = "metadata")]
|
||||
disable_doc_comments: self.disable_doc_comments,
|
||||
is_within_text_terminated_by: None,
|
||||
},
|
||||
pos: Position::new(1, 0),
|
||||
|
15
src/utils.rs
15
src/utils.rs
@ -198,6 +198,7 @@ impl From<String> for ImmutableString {
|
||||
Self(Into::<SmartString>::into(value).into())
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "no_smartstring"))]
|
||||
impl From<SmartString> for ImmutableString {
|
||||
#[inline(always)]
|
||||
fn from(value: SmartString) -> Self {
|
||||
@ -248,6 +249,14 @@ impl<'a> FromIterator<String> for ImmutableString {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_smartstring"))]
|
||||
impl<'a> FromIterator<SmartString> for ImmutableString {
|
||||
#[inline(always)]
|
||||
fn from_iter<T: IntoIterator<Item = SmartString>>(iter: T) -> Self {
|
||||
Self(iter.into_iter().collect::<SmartString>().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ImmutableString {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
@ -618,17 +627,17 @@ impl ImmutableString {
|
||||
/// yet interned.
|
||||
#[derive(Debug, Clone, Default, Hash)]
|
||||
pub struct IdentifierBuilder(
|
||||
#[cfg(feature = "no_smartstring_for_identifier")] std::collections::BTreeSet<Identifier>,
|
||||
#[cfg(feature = "no_smartstring")] std::collections::BTreeSet<Identifier>,
|
||||
);
|
||||
|
||||
impl IdentifierBuilder {
|
||||
/// Get an identifier from a text string.
|
||||
#[inline(always)]
|
||||
pub fn get(&mut self, text: impl AsRef<str> + Into<Identifier>) -> Identifier {
|
||||
#[cfg(not(feature = "no_smartstring_for_identifier"))]
|
||||
#[cfg(not(feature = "no_smartstring"))]
|
||||
return text.as_ref().into();
|
||||
|
||||
#[cfg(feature = "no_smartstring_for_identifier")]
|
||||
#[cfg(feature = "no_smartstring")]
|
||||
return self.0.get(text.as_ref()).cloned().unwrap_or_else(|| {
|
||||
let s: Identifier = text.into();
|
||||
self.0.insert(s.clone());
|
||||
|
@ -30,7 +30,7 @@ fn test_comments() -> Result<(), Box<EvalAltResult>> {
|
||||
#[cfg(feature = "metadata")]
|
||||
#[test]
|
||||
fn test_comments_doc() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
let engine = Engine::new();
|
||||
|
||||
let ast = engine.compile(
|
||||
"
|
||||
@ -89,17 +89,5 @@ fn test_comments_doc() -> Result<(), Box<EvalAltResult>> {
|
||||
)
|
||||
.is_err());
|
||||
|
||||
engine.enable_doc_comments(false);
|
||||
|
||||
engine.compile(
|
||||
"
|
||||
/// Hello world!
|
||||
let x = 42;
|
||||
|
||||
/** Hello world! */
|
||||
let x = 42;
|
||||
",
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ fn test_constant() -> Result<(), Box<EvalAltResult>> {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
assert!(matches!(
|
||||
*engine.consume("const x = [1, 2, 3, 4, 5]; x[2] = 42;").expect_err("expects error"),
|
||||
EvalAltResult::ErrorParsing(ParseErrorType::AssignmentToConstant(x), _) if x == "x"
|
||||
EvalAltResult::ErrorAssignmentToConstant(x, _) if x == "x"
|
||||
));
|
||||
|
||||
Ok(())
|
||||
@ -45,17 +45,66 @@ fn test_constant_mut() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
let mut engine = Engine::new();
|
||||
|
||||
fn set_value(obj: &mut TestStruct, value: INT) {
|
||||
obj.0 = value;
|
||||
}
|
||||
|
||||
engine
|
||||
.register_type_with_name::<TestStruct>("TestStruct")
|
||||
.register_fn("new_ts", || TestStruct(123))
|
||||
.register_get("value", |obj: &mut TestStruct| obj.0)
|
||||
.register_fn("update_value", |obj: &mut TestStruct, value: INT| {
|
||||
obj.0 = value
|
||||
});
|
||||
.register_set("value", set_value)
|
||||
.register_fn("update_value", set_value);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
"
|
||||
const MY_NUMBER = new_ts();
|
||||
MY_NUMBER.update_value(42);
|
||||
MY_NUMBER.value
|
||||
",
|
||||
)?,
|
||||
42
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
"
|
||||
const MY_NUMBER = new_ts();
|
||||
update_value(MY_NUMBER, 42);
|
||||
MY_NUMBER.value
|
||||
",
|
||||
)?,
|
||||
123
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.consume(
|
||||
"
|
||||
const MY_NUMBER = new_ts();
|
||||
MY_NUMBER.value = 42;
|
||||
"
|
||||
)
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorAssignmentToConstant(_, _)
|
||||
));
|
||||
|
||||
let mut scope = Scope::new();
|
||||
|
||||
scope.push_constant("MY_NUMBER", TestStruct(123));
|
||||
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<INT>(
|
||||
&mut scope,
|
||||
"
|
||||
update_value(MY_NUMBER, 42);
|
||||
MY_NUMBER.value
|
||||
",
|
||||
)?,
|
||||
123
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval_with_scope::<INT>(
|
||||
&mut scope,
|
||||
@ -67,5 +116,12 @@ fn test_constant_mut() -> Result<(), Box<EvalAltResult>> {
|
||||
42
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.consume_with_scope(&mut scope, "MY_NUMBER.value = 42;")
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorAssignmentToConstant(_, _)
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -31,15 +31,18 @@ mod test {
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "test", name = "hi")]
|
||||
#[inline(always)]
|
||||
pub fn len(array: &mut Array, mul: INT) -> INT {
|
||||
(array.len() as INT) * mul
|
||||
}
|
||||
#[rhai_fn(name = "+")]
|
||||
#[inline(always)]
|
||||
pub fn funky_add(x: INT, y: INT) -> INT {
|
||||
x / 2 + y * 2
|
||||
}
|
||||
#[rhai_fn(name = "no_effect", set = "no_effect", pure)]
|
||||
pub fn no_effect(array: &mut Array, value: INT) {
|
||||
// array is not modified
|
||||
println!("Array = {:?}, Value = {}", array, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,7 +85,16 @@ fn test_plugins_package() -> Result<(), Box<EvalAltResult>> {
|
||||
reg_functions!(engine += greet::single(INT, bool, char));
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; a.foo")?, 1);
|
||||
{
|
||||
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; a.foo")?, 1);
|
||||
engine.consume("const A = [1, 2, 3]; A.no_effect(42);")?;
|
||||
engine.consume("const A = [1, 2, 3]; A.no_effect = 42;")?;
|
||||
|
||||
assert!(
|
||||
matches!(*engine.consume("const A = [1, 2, 3]; A.test(42);").expect_err("should error"),
|
||||
EvalAltResult::ErrorAssignmentToConstant(x, _) if x == "array")
|
||||
)
|
||||
}
|
||||
|
||||
assert_eq!(engine.eval::<INT>(r#"hash("hello")"#)?, 42);
|
||||
assert_eq!(engine.eval::<INT>(r#"hash2("hello")"#)?, 42);
|
||||
|
@ -19,15 +19,15 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
engine.register_custom_syntax(
|
||||
&[
|
||||
"exec", "|", "$ident$", "|", "->", "$block$", "while", "$expr$",
|
||||
"exec", "[", "$ident$", "]", "->", "$block$", "while", "$expr$",
|
||||
],
|
||||
1,
|
||||
true,
|
||||
|context, inputs| {
|
||||
let var_name = inputs[0].get_variable_name().unwrap().to_string();
|
||||
let stmt = inputs.get(1).unwrap();
|
||||
let condition = inputs.get(2).unwrap();
|
||||
|
||||
context.scope_mut().push(var_name, 0 as INT);
|
||||
context.scope_mut().push(var_name.clone(), 0 as INT);
|
||||
|
||||
let mut count: INT = 0;
|
||||
|
||||
@ -35,6 +35,10 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
||||
context.eval_expression_tree(stmt)?;
|
||||
count += 1;
|
||||
|
||||
context
|
||||
.scope_mut()
|
||||
.push(format!("{}{}", var_name, count), count);
|
||||
|
||||
let stop = !context
|
||||
.eval_expression_tree(condition)?
|
||||
.as_bool()
|
||||
@ -59,7 +63,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
||||
engine.eval::<INT>(
|
||||
"
|
||||
let x = 0;
|
||||
let foo = (exec |x| -> { x += 2 } while x < 42) * 10;
|
||||
let foo = (exec [x] -> { x += 2 } while x < 42) * 10;
|
||||
foo
|
||||
"
|
||||
)?,
|
||||
@ -69,7 +73,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
||||
engine.eval::<INT>(
|
||||
"
|
||||
let x = 0;
|
||||
exec |x| -> { x += 1 } while x < 42;
|
||||
exec [x] -> { x += 1 } while x < 42;
|
||||
x
|
||||
"
|
||||
)?,
|
||||
@ -78,17 +82,27 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
"
|
||||
exec |x| -> { x += 1 } while x < 42;
|
||||
exec [x] -> { x += 1 } while x < 42;
|
||||
x
|
||||
"
|
||||
)?,
|
||||
42
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval::<INT>(
|
||||
"
|
||||
let foo = 123;
|
||||
exec [x] -> { x += 1 } while x < 42;
|
||||
foo + x + x1 + x2 + x3
|
||||
"
|
||||
)?,
|
||||
171
|
||||
);
|
||||
|
||||
// The first symbol must be an identifier
|
||||
assert_eq!(
|
||||
*engine
|
||||
.register_custom_syntax(&["!"], 0, |_, _| Ok(Dynamic::UNIT))
|
||||
.register_custom_syntax(&["!"], false, |_, _| Ok(Dynamic::UNIT))
|
||||
.expect_err("should error")
|
||||
.0,
|
||||
ParseErrorType::BadInput(LexError::ImproperSymbol(
|
||||
@ -114,14 +128,14 @@ fn test_custom_syntax_raw() -> Result<(), Box<EvalAltResult>> {
|
||||
s => Err(ParseError(
|
||||
Box::new(ParseErrorType::BadInput(LexError::ImproperSymbol(
|
||||
s.to_string(),
|
||||
"".to_string(),
|
||||
Default::default(),
|
||||
))),
|
||||
Position::NONE,
|
||||
)),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
},
|
||||
1,
|
||||
true,
|
||||
|context, inputs| {
|
||||
context.scope_mut().push("foo", 999 as INT);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user