Merge pull request #413 from schungx/master

Enhancements and bug fixes.
This commit is contained in:
Stephen Chung 2021-05-15 18:17:30 +08:00 committed by GitHub
commit 5c00b89568
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 1140 additions and 694 deletions

View File

@ -1,6 +1,36 @@
Rhai Release Notes 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 Version 0.20.1
============== ==============

View File

@ -3,7 +3,7 @@ members = [".", "codegen"]
[package] [package]
name = "rhai" name = "rhai"
version = "0.20.1" version = "0.20.2"
edition = "2018" edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
description = "Embedded scripting for Rust" description = "Embedded scripting for Rust"
@ -32,7 +32,7 @@ no_float = [] # no floating-point
f32_float = [] # set FLOAT=f32 f32_float = [] # set FLOAT=f32
only_i32 = [] # set INT=i32 (useful for 32-bit systems) only_i32 = [] # set INT=i32 (useful for 32-bit systems)
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types 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_index = [] # no arrays and indexing
no_object = [] # no custom objects no_object = [] # no custom objects
no_function = ["no_closure"] # no script-defined functions (meaning no closures) 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"] wasm-bindgen = ["instant/wasm-bindgen"]
stdweb = ["instant/stdweb"] stdweb = ["instant/stdweb"]
# internal feature flags - volatile
no_smartstring = [] # Do not use SmartString
[profile.release] [profile.release]
lto = "fat" lto = "fat"
codegen-units = 1 codegen-units = 1
@ -89,8 +92,9 @@ default_features = false
optional = true optional = true
[dependencies.rust_decimal] [dependencies.rust_decimal]
version = "1.11" version = "1.13"
default_features = false default_features = false
features = ["maths"]
optional = true optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rhai_codegen" name = "rhai_codegen"
version = "0.3.5" version = "0.3.6"
edition = "2018" edition = "2018"
authors = ["jhwgh1968", "Stephen Chung"] authors = ["jhwgh1968", "Stephen Chung"]
description = "Procedural macros support package for Rhai, a scripting language and engine for Rust" description = "Procedural macros support package for Rhai, a scripting language and engine for Rust"

View File

@ -541,8 +541,10 @@ impl ExportedFn {
"property setter requires exactly 2 parameters", "property setter requires exactly 2 parameters",
)) ))
} }
// 3b. Property setters must return nothing. // 3b. Non-raw property setters must return nothing.
FnSpecialAccess::Property(Property::Set(_)) if self.return_type().is_some() => { FnSpecialAccess::Property(Property::Set(_))
if params.return_raw.is_none() && self.return_type().is_some() =>
{
return Err(syn::Error::new( return Err(syn::Error::new(
self.signature.output.span(), self.signature.output.span(),
"property setter cannot return any value", "property setter cannot return any value",
@ -569,8 +571,10 @@ impl ExportedFn {
"index setter requires exactly 3 parameters", "index setter requires exactly 3 parameters",
)) ))
} }
// 5b. Index setters must return nothing. // 5b. Non-raw index setters must return nothing.
FnSpecialAccess::Index(Index::Set) if self.return_type().is_some() => { FnSpecialAccess::Index(Index::Set)
if params.return_raw.is_none() && self.return_type().is_some() =>
{
return Err(syn::Error::new( return Err(syn::Error::new(
self.signature.output.span(), self.signature.output.span(),
"index setter cannot return any value", "index setter cannot return any value",
@ -693,9 +697,7 @@ impl ExportedFn {
unpack_statements.push( unpack_statements.push(
syn::parse2::<syn::Stmt>(quote! { syn::parse2::<syn::Stmt>(quote! {
if args[0usize].is_read_only() { if args[0usize].is_read_only() {
return Err(Box::new( return EvalAltResult::ErrorAssignmentToConstant(#arg_lit_str.to_string(), Position::NONE).into();
EvalAltResult::ErrorAssignmentToConstant(#arg_lit_str.to_string(), Position::NONE)
));
} }
}) })
.unwrap(), .unwrap(),

View File

@ -492,9 +492,7 @@ mod generate_tests {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
if args[0usize].is_read_only() { if args[0usize].is_read_only() {
return Err(Box::new( return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
));
} }
let arg1 = mem::take(args[1usize]).cast::<usize>(); let arg1 = mem::take(args[1usize]).cast::<usize>();
let arg0 = &mut args[0usize].write_lock::<usize>().unwrap(); let arg0 = &mut args[0usize].write_lock::<usize>().unwrap();

View File

@ -1107,9 +1107,7 @@ mod generate_tests {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
if args[0usize].is_read_only() { if args[0usize].is_read_only() {
return Err(Box::new( return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
));
} }
let arg0 = &mut args[0usize].write_lock::<FLOAT>().unwrap(); let arg0 = &mut args[0usize].write_lock::<FLOAT>().unwrap();
Ok(Dynamic::from(increment(arg0))) Ok(Dynamic::from(increment(arg0)))
@ -1169,9 +1167,7 @@ mod generate_tests {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
if args[0usize].is_read_only() { if args[0usize].is_read_only() {
return Err(Box::new( return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
));
} }
let arg0 = &mut args[0usize].write_lock::<FLOAT>().unwrap(); let arg0 = &mut args[0usize].write_lock::<FLOAT>().unwrap();
Ok(Dynamic::from(increment(arg0))) Ok(Dynamic::from(increment(arg0)))
@ -1252,9 +1248,7 @@ mod generate_tests {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
if args[0usize].is_read_only() { if args[0usize].is_read_only() {
return Err(Box::new( return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
));
} }
let arg0 = &mut args[0usize].write_lock::<FLOAT>().unwrap(); let arg0 = &mut args[0usize].write_lock::<FLOAT>().unwrap();
Ok(Dynamic::from(increment(arg0))) Ok(Dynamic::from(increment(arg0)))
@ -1336,9 +1330,7 @@ mod generate_tests {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
if args[0usize].is_read_only() { if args[0usize].is_read_only() {
return Err(Box::new( return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
));
} }
let arg0 = &mut args[0usize].write_lock::<u64>().unwrap(); let arg0 = &mut args[0usize].write_lock::<u64>().unwrap();
Ok(Dynamic::from(int_foo(arg0))) Ok(Dynamic::from(int_foo(arg0)))
@ -1399,9 +1391,7 @@ mod generate_tests {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
if args[0usize].is_read_only() { if args[0usize].is_read_only() {
return Err(Box::new( return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
));
} }
let arg0 = &mut args[0usize].write_lock::<u64>().unwrap(); let arg0 = &mut args[0usize].write_lock::<u64>().unwrap();
Ok(Dynamic::from(int_foo(arg0))) Ok(Dynamic::from(int_foo(arg0)))
@ -1459,9 +1449,7 @@ mod generate_tests {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
if args[0usize].is_read_only() { if args[0usize].is_read_only() {
return Err(Box::new( return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
));
} }
let arg1 = mem::take(args[1usize]).cast::<u64>(); let arg1 = mem::take(args[1usize]).cast::<u64>();
let arg0 = &mut args[0usize].write_lock::<u64>().unwrap(); let arg0 = &mut args[0usize].write_lock::<u64>().unwrap();
@ -1523,9 +1511,7 @@ mod generate_tests {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
if args[0usize].is_read_only() { if args[0usize].is_read_only() {
return Err(Box::new( return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
));
} }
let arg1 = mem::take(args[1usize]).cast::<u64>(); let arg1 = mem::take(args[1usize]).cast::<u64>();
let arg0 = &mut args[0usize].write_lock::<u64>().unwrap(); let arg0 = &mut args[0usize].write_lock::<u64>().unwrap();
@ -1584,9 +1570,7 @@ mod generate_tests {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
if args[0usize].is_read_only() { if args[0usize].is_read_only() {
return Err(Box::new( return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
));
} }
let arg1 = mem::take(args[1usize]).cast::<u64>(); let arg1 = mem::take(args[1usize]).cast::<u64>();
let arg0 = &mut args[0usize].write_lock::<MyCollection>().unwrap(); let arg0 = &mut args[0usize].write_lock::<MyCollection>().unwrap();
@ -1648,9 +1632,7 @@ mod generate_tests {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
if args[0usize].is_read_only() { if args[0usize].is_read_only() {
return Err(Box::new( return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
));
} }
let arg1 = mem::take(args[1usize]).cast::<u64>(); let arg1 = mem::take(args[1usize]).cast::<u64>();
let arg0 = &mut args[0usize].write_lock::<MyCollection>().unwrap(); let arg0 = &mut args[0usize].write_lock::<MyCollection>().unwrap();
@ -1709,9 +1691,7 @@ mod generate_tests {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
if args[0usize].is_read_only() { if args[0usize].is_read_only() {
return Err(Box::new( return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
));
} }
let arg1 = mem::take(args[1usize]).cast::<u64>(); let arg1 = mem::take(args[1usize]).cast::<u64>();
let arg2 = mem::take(args[2usize]).cast::<FLOAT>(); let arg2 = mem::take(args[2usize]).cast::<FLOAT>();
@ -1774,9 +1754,7 @@ mod generate_tests {
#[inline(always)] #[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
if args[0usize].is_read_only() { if args[0usize].is_read_only() {
return Err(Box::new( return EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into();
EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE)
));
} }
let arg1 = mem::take(args[1usize]).cast::<u64>(); let arg1 = mem::take(args[1usize]).cast::<u64>();
let arg2 = mem::take(args[2usize]).cast::<FLOAT>(); let arg2 = mem::take(args[2usize]).cast::<FLOAT>();

View File

@ -39,4 +39,4 @@ lto = "fat"
[patch.crates-io] [patch.crates-io]
# Patch smartstring wth a PR fix because it doesn't properly handle no-std builds. # 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" }

View File

@ -14,6 +14,7 @@ use std::{
fmt, fmt,
hash::Hash, hash::Hash,
iter::empty, iter::empty,
mem,
num::{NonZeroU8, NonZeroUsize}, num::{NonZeroU8, NonZeroUsize},
ops::{Add, AddAssign, Deref, DerefMut}, ops::{Add, AddAssign, Deref, DerefMut},
}; };
@ -52,6 +53,8 @@ pub struct ScriptFnDef {
/// Encapsulated running environment, if any. /// Encapsulated running environment, if any.
pub lib: Option<Shared<Module>>, pub lib: Option<Shared<Module>>,
/// Encapsulated imported modules. /// Encapsulated imported modules.
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
pub mods: crate::engine::Imports, pub mods: crate::engine::Imports,
/// Function name. /// Function name.
@ -61,9 +64,14 @@ pub struct ScriptFnDef {
/// Names of function parameters. /// Names of function parameters.
pub params: StaticVec<Identifier>, pub params: StaticVec<Identifier>,
/// Access to external variables. /// Access to external variables.
///
/// Not available under `no_closure`.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
pub externals: std::collections::BTreeSet<Identifier>, 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(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
pub comments: StaticVec<String>, pub comments: StaticVec<String>,
@ -97,7 +105,10 @@ impl fmt::Display for ScriptFnDef {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[derive(Debug, Eq, PartialEq, Clone, Hash)] #[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub struct ScriptFnMetadata<'a> { 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. /// 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. /// Compiled AST (abstract syntax tree) of a Rhai script.
/// ///
/// # Thread Safety /// # Thread Safety
@ -162,6 +190,8 @@ pub struct AST {
/// Script-defined functions. /// Script-defined functions.
functions: Shared<Module>, functions: Shared<Module>,
/// Embedded module resolver, if any. /// Embedded module resolver, if any.
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
resolver: Option<Shared<crate::module::resolvers::StaticModuleResolver>>, resolver: Option<Shared<crate::module::resolvers::StaticModuleResolver>>,
} }
@ -186,9 +216,10 @@ impl AST {
statements: impl IntoIterator<Item = Stmt>, statements: impl IntoIterator<Item = Stmt>,
functions: impl Into<Shared<Module>>, functions: impl Into<Shared<Module>>,
) -> Self { ) -> Self {
let statements: StaticVec<_> = statements.into_iter().collect();
Self { Self {
source: None, source: None,
body: StmtBlock(statements.into_iter().collect(), Position::NONE), body: StmtBlock::new(statements, Position::NONE),
functions: functions.into(), functions: functions.into(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
resolver: None, resolver: None,
@ -201,9 +232,10 @@ impl AST {
functions: impl Into<Shared<Module>>, functions: impl Into<Shared<Module>>,
source: impl Into<Identifier>, source: impl Into<Identifier>,
) -> Self { ) -> Self {
let statements: StaticVec<_> = statements.into_iter().collect();
Self { Self {
source: Some(source.into()), source: Some(source.into()),
body: StmtBlock(statements.into_iter().collect(), Position::NONE), body: StmtBlock::new(statements, Position::NONE),
functions: functions.into(), functions: functions.into(),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
resolver: None, resolver: None,
@ -267,7 +299,7 @@ impl AST {
/// _(INTERNALS)_ Get the internal shared [`Module`] containing all script-defined functions. /// _(INTERNALS)_ Get the internal shared [`Module`] containing all script-defined functions.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///
/// Not available under `no_function`. /// Not available under `no_function` or `no_module`.
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[deprecated = "this method is volatile and may change"] #[deprecated = "this method is volatile and may change"]
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -303,6 +335,8 @@ impl AST {
} }
/// _(INTERNALS)_ Get the embedded [module resolver][crate::ModuleResolver]. /// _(INTERNALS)_ Get the embedded [module resolver][crate::ModuleResolver].
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[inline(always)] #[inline(always)]
@ -835,7 +869,9 @@ pub struct StmtBlock(StaticVec<Stmt>, Position);
impl StmtBlock { impl StmtBlock {
/// Create a new [`StmtBlock`]. /// Create a new [`StmtBlock`].
pub fn new(statements: impl Into<StaticVec<Stmt>>, pos: Position) -> Self { 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? /// Is this statements block empty?
#[inline(always)] #[inline(always)]
@ -882,7 +918,7 @@ impl fmt::Debug for StmtBlock {
impl From<StmtBlock> for Stmt { impl From<StmtBlock> for Stmt {
fn from(block: StmtBlock) -> Self { fn from(block: StmtBlock) -> Self {
let block_pos = block.position(); 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. /// function call forming one statement.
FnCall(Box<FnCallExpr>, Position), FnCall(Box<FnCallExpr>, Position),
/// `{` stmt`;` ... `}` /// `{` stmt`;` ... `}`
Block(Vec<Stmt>, Position), Block(Box<[Stmt]>, Position),
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}` /// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
TryCatch( TryCatch(
Box<(StmtBlock, Option<Ident>, StmtBlock)>, Box<(StmtBlock, Option<Ident>, StmtBlock)>,
@ -938,12 +974,18 @@ pub enum Stmt {
/// `return`/`throw` /// `return`/`throw`
Return(ReturnType, Option<Expr>, Position), Return(ReturnType, Option<Expr>, Position),
/// `import` expr `as` var /// `import` expr `as` var
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Import(Expr, Option<Box<Ident>>, Position), Import(Expr, Option<Box<Ident>>, Position),
/// `export` var `as` var `,` ... /// `export` var `as` var `,` ...
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Export(Vec<(Ident, Option<Ident>)>, Position), Export(Box<[(Ident, Option<Ident>)]>, Position),
/// Convert a variable to shared. /// Convert a variable to shared.
///
/// Not available under `no_closure`.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Share(Identifier), Share(Identifier),
} }
@ -959,7 +1001,9 @@ impl From<Stmt> for StmtBlock {
#[inline(always)] #[inline(always)]
fn from(stmt: Stmt) -> Self { fn from(stmt: Stmt) -> Self {
match stmt { 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), Stmt::Noop(pos) => Self(Default::default(), pos),
_ => { _ => {
let pos = stmt.position(); let pos = stmt.position();
@ -1265,7 +1309,7 @@ impl Stmt {
} }
} }
Self::Block(x, _) => { Self::Block(x, _) => {
for s in x { for s in x.iter() {
if !s.walk(path, on_node) { if !s.walk(path, on_node) {
return false; return false;
} }
@ -1313,10 +1357,10 @@ impl Stmt {
pub struct CustomExpr { pub struct CustomExpr {
/// List of keywords. /// List of keywords.
pub keywords: StaticVec<Expr>, pub keywords: StaticVec<Expr>,
/// Is the current [`Scope`][crate::Scope] modified?
pub scope_changed: bool,
/// List of tokens actually parsed. /// List of tokens actually parsed.
pub tokens: Vec<Identifier>, pub tokens: StaticVec<Identifier>,
/// Delta number of variables in the scope.
pub scope_delta: isize,
} }
/// _(INTERNALS)_ A binary expression. /// _(INTERNALS)_ A binary expression.
@ -1419,7 +1463,7 @@ impl fmt::Debug for FnCallHashes {
} }
impl FnCallHashes { impl FnCallHashes {
/// Create a [`FnCallHash`] with only the native Rust hash. /// Create a [`FnCallHashes`] with only the native Rust hash.
#[inline(always)] #[inline(always)]
pub fn from_native(hash: u64) -> Self { pub fn from_native(hash: u64) -> Self {
Self { Self {
@ -1427,7 +1471,7 @@ impl FnCallHashes {
native: hash, 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)] #[inline(always)]
pub fn from_script(hash: u64) -> Self { pub fn from_script(hash: u64) -> Self {
Self { Self {
@ -1435,7 +1479,7 @@ impl FnCallHashes {
native: hash, 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)] #[inline(always)]
pub fn from_script_and_native(script: u64, native: u64) -> Self { pub fn from_script_and_native(script: u64, native: u64) -> Self {
Self { Self {
@ -1443,21 +1487,21 @@ impl FnCallHashes {
native, native,
} }
} }
/// Is this [`FnCallHash`] native Rust only? /// Is this [`FnCallHashes`] native Rust only?
#[inline(always)] #[inline(always)]
pub fn is_native_only(&self) -> bool { pub fn is_native_only(&self) -> bool {
self.script.is_none() self.script.is_none()
} }
/// Get the script function hash from this [`FnCallHash`]. /// Get the script function hash from this [`FnCallHashes`].
/// ///
/// # Panics /// # Panics
/// ///
/// Panics if the [`FnCallHash`] is native Rust only. /// Panics if the [`FnCallHashes`] is native Rust only.
#[inline(always)] #[inline(always)]
pub fn script_hash(&self) -> u64 { pub fn script_hash(&self) -> u64 {
self.script.unwrap() self.script.unwrap()
} }
/// Get the naive Rust function hash from this [`FnCallHash`]. /// Get the naive Rust function hash from this [`FnCallHashes`].
#[inline(always)] #[inline(always)]
pub fn native_hash(&self) -> u64 { pub fn native_hash(&self) -> u64 {
self.native self.native
@ -1479,7 +1523,7 @@ pub struct FnCallExpr {
/// List of function call argument expressions. /// List of function call argument expressions.
pub args: StaticVec<Expr>, pub args: StaticVec<Expr>,
/// List of function call arguments that are constants. /// 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. /// Function name.
pub name: Identifier, pub name: Identifier,
/// Does this function call capture the parent scope? /// Does this function call capture the parent scope?
@ -1495,16 +1539,18 @@ impl FnCallExpr {
/// Are there no arguments to this function call? /// Are there no arguments to this function call?
#[inline(always)] #[inline(always)]
pub fn is_args_empty(&self) -> bool { 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. /// Get the number of arguments to this function call.
#[inline(always)] #[inline(always)]
pub fn args_count(&self) -> usize { 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`]. /// A type that wraps a floating-point number and implements [`Hash`].
///
/// Not available under `no_float`.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[derive(Clone, Copy, PartialEq, PartialOrd)] #[derive(Clone, Copy, PartialEq, PartialOrd)]
pub struct FloatWrapper<F>(F); pub struct FloatWrapper<F>(F);
@ -1635,6 +1681,8 @@ pub enum Expr {
/// Integer constant. /// Integer constant.
IntegerConstant(INT, Position), IntegerConstant(INT, Position),
/// Floating-point constant. /// Floating-point constant.
///
/// Not available under `no_float`.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
FloatConstant(FloatWrapper<FLOAT>, Position), FloatConstant(FloatWrapper<FLOAT>, Position),
/// Character constant. /// Character constant.
@ -1745,8 +1793,8 @@ impl fmt::Debug for Expr {
ff.field("name", &x.name) ff.field("name", &x.name)
.field("hash", &x.hashes) .field("hash", &x.hashes)
.field("args", &x.args); .field("args", &x.args);
if !x.constant_args.is_empty() { if !x.literal_args.is_empty() {
ff.field("constant_args", &x.constant_args); ff.field("literal_args", &x.literal_args);
} }
if x.capture { if x.capture {
ff.field("capture", &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::<Option<ast::Expr>>(), 16);
assert_eq!(size_of::<ast::Stmt>(), 32); assert_eq!(size_of::<ast::Stmt>(), 32);
assert_eq!(size_of::<Option<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::<Scope>(), 288);
assert_eq!(size_of::<LexError>(), 56); assert_eq!(size_of::<LexError>(), 56);
assert_eq!( assert_eq!(

View File

@ -106,7 +106,7 @@ fn main() {
.compile(&contents) .compile(&contents)
.map_err(|err| err.into()) .map_err(|err| err.into())
.and_then(|mut ast| { .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) Module::eval_ast_as_new(Default::default(), &ast, &engine)
}) { }) {
Err(err) => { Err(err) => {

View File

@ -85,7 +85,7 @@ fn main() {
.compile(contents) .compile(contents)
.map_err(|err| Box::new(err.into()) as Box<EvalAltResult>) .map_err(|err| Box::new(err.into()) as Box<EvalAltResult>)
.and_then(|mut ast| { .and_then(|mut ast| {
ast.set_source(filename.to_string_lossy()); ast.set_source(filename.to_string_lossy().to_string());
engine.consume_ast(&ast) engine.consume_ast(&ast)
}) })
{ {

File diff suppressed because it is too large Load Diff

View File

@ -558,7 +558,7 @@ pub struct FnResolutionCacheEntry {
} }
/// A function resolution cache. /// 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`]. /// _(INTERNALS)_ A type that holds all the current states of the [`Engine`].
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
@ -776,7 +776,7 @@ pub struct Engine {
pub(crate) module_resolver: Box<dyn crate::ModuleResolver>, pub(crate) module_resolver: Box<dyn crate::ModuleResolver>,
/// A map mapping type names to pretty-print names. /// 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. /// An empty [`ImmutableString`] for cloning purposes.
pub(crate) empty_string: ImmutableString, pub(crate) empty_string: ImmutableString,
@ -786,7 +786,7 @@ pub struct Engine {
/// A map containing custom keywords and precedence to recognize. /// A map containing custom keywords and precedence to recognize.
pub(crate) custom_keywords: BTreeMap<Identifier, Option<Precedence>>, pub(crate) custom_keywords: BTreeMap<Identifier, Option<Precedence>>,
/// Custom syntax. /// Custom syntax.
pub(crate) custom_syntax: BTreeMap<Identifier, CustomSyntax>, pub(crate) custom_syntax: BTreeMap<Identifier, Box<CustomSyntax>>,
/// Callback closure for resolving variable access. /// Callback closure for resolving variable access.
pub(crate) resolve_var: Option<OnVarCallback>, pub(crate) resolve_var: Option<OnVarCallback>,
@ -804,11 +804,6 @@ pub struct Engine {
/// Max limits. /// Max limits.
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
pub(crate) limits: Limits, 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 { impl fmt::Debug for Engine {
@ -926,13 +921,9 @@ impl Engine {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
max_map_size: None, 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.register_global_module(StandardPackage::new().as_shared_module());
engine engine
@ -987,13 +978,9 @@ impl Engine {
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
max_map_size: None, 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 engine
} }
@ -1144,6 +1131,7 @@ impl Engine {
lib: &[&Module], lib: &[&Module],
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
target: &mut Target, target: &mut Target,
root: (&str, Position),
rhs: &Expr, rhs: &Expr,
idx_values: &mut StaticVec<ChainArgument>, idx_values: &mut StaticVec<ChainArgument>,
chain_type: ChainType, chain_type: ChainType,
@ -1179,8 +1167,8 @@ impl Engine {
let rhs_chain = rhs_chain.unwrap(); let rhs_chain = rhs_chain.unwrap();
self.eval_dot_index_chain_helper( self.eval_dot_index_chain_helper(
mods, state, lib, this_ptr, obj_ptr, &x.rhs, idx_values, rhs_chain, mods, state, lib, this_ptr, obj_ptr, root, &x.rhs, idx_values,
level, new_val, rhs_chain, level, new_val,
) )
.map_err(|err| err.fill_position(*x_pos)) .map_err(|err| err.fill_position(*x_pos))
} }
@ -1199,7 +1187,8 @@ impl Engine {
Ok(obj_ptr) => { Ok(obj_ptr) => {
let ((new_val, new_pos), (op_info, op_pos)) = new_val.unwrap(); let ((new_val, new_pos), (op_info, op_pos)) = new_val.unwrap();
self.eval_op_assignment( 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 None
} }
@ -1282,7 +1271,7 @@ impl Engine {
)?; )?;
let ((new_val, new_pos), (op_info, op_pos)) = new_val.unwrap(); let ((new_val, new_pos), (op_info, op_pos)) = new_val.unwrap();
self.eval_op_assignment( 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)) Ok((Dynamic::UNIT, true))
} }
@ -1311,7 +1300,7 @@ impl Engine {
)?; )?;
let obj_ptr = (&mut orig_val).into(); let obj_ptr = (&mut orig_val).into();
self.eval_op_assignment( 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; new_val = orig_val;
} }
@ -1365,8 +1354,8 @@ impl Engine {
let rhs_chain = rhs_chain.unwrap(); let rhs_chain = rhs_chain.unwrap();
self.eval_dot_index_chain_helper( self.eval_dot_index_chain_helper(
mods, state, lib, this_ptr, &mut val, &x.rhs, idx_values, rhs_chain, mods, state, lib, this_ptr, &mut val, root, &x.rhs, idx_values,
level, new_val, rhs_chain, level, new_val,
) )
.map_err(|err| err.fill_position(*x_pos)) .map_err(|err| err.fill_position(*x_pos))
} }
@ -1396,6 +1385,7 @@ impl Engine {
lib, lib,
this_ptr, this_ptr,
&mut val.into(), &mut val.into(),
root,
&x.rhs, &x.rhs,
idx_values, idx_values,
rhs_chain, rhs_chain,
@ -1438,7 +1428,7 @@ impl Engine {
let target = &mut val.into(); let target = &mut val.into();
self.eval_dot_index_chain_helper( 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, rhs_chain, level, new_val,
) )
.map_err(|err| err.fill_position(*pos)) .map_err(|err| err.fill_position(*pos))
@ -1487,21 +1477,16 @@ impl Engine {
match lhs { match lhs {
// id.??? or id[???] // id.??? or id[???]
Expr::Variable(_, _var_pos, x) => { Expr::Variable(_, var_pos, x) => {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, *_var_pos)?; self.inc_operations(state, *var_pos)?;
let (target, pos) = let (target, _) = self.search_namespace(scope, mods, state, lib, this_ptr, lhs)?;
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 obj_ptr = &mut target.into(); let obj_ptr = &mut target.into();
let root = (x.2.as_str(), *var_pos);
self.eval_dot_index_chain_helper( 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, new_val,
) )
.map(|(v, _)| v) .map(|(v, _)| v)
@ -1513,8 +1498,9 @@ impl Engine {
expr => { expr => {
let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; let value = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?;
let obj_ptr = &mut value.into(); let obj_ptr = &mut value.into();
let root = ("", expr.position());
self.eval_dot_index_chain_helper( 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, new_val,
) )
.map(|(v, _)| v) .map(|(v, _)| v)
@ -1558,7 +1544,7 @@ impl Engine {
}) })
.collect::<Result<StaticVec<_>, _>>()?; .collect::<Result<StaticVec<_>, _>>()?;
x.constant_args x.literal_args
.iter() .iter()
.inspect(|(_, pos)| arg_positions.push(*pos)) .inspect(|(_, pos)| arg_positions.push(*pos))
.for_each(|(v, _)| arg_values.push(v.clone())); .for_each(|(v, _)| arg_values.push(v.clone()));
@ -1603,7 +1589,7 @@ impl Engine {
}) })
.collect::<Result<StaticVec<_>, _>>()?; .collect::<Result<StaticVec<_>, _>>()?;
x.constant_args x.literal_args
.iter() .iter()
.inspect(|(_, pos)| arg_positions.push(*pos)) .inspect(|(_, pos)| arg_positions.push(*pos))
.for_each(|(v, _)| arg_values.push(v.clone())); .for_each(|(v, _)| arg_values.push(v.clone()));
@ -1676,7 +1662,7 @@ impl Engine {
match target { match target {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(arr, _)) => { Dynamic(Union::Array(arr, _, _)) => {
// val_array[idx] // val_array[idx]
let index = _idx let index = _idx
.as_int() .as_int()
@ -1718,7 +1704,7 @@ impl Engine {
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Dynamic(Union::Map(map, _)) => { Dynamic(Union::Map(map, _, _)) => {
// val_map[idx] // val_map[idx]
let index = &*_idx.read_lock::<ImmutableString>().ok_or_else(|| { let index = &*_idx.read_lock::<ImmutableString>().ok_or_else(|| {
self.make_type_mismatch_err::<ImmutableString>(_idx.type_name(), idx_pos) self.make_type_mismatch_err::<ImmutableString>(_idx.type_name(), idx_pos)
@ -1735,7 +1721,7 @@ impl Engine {
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Str(s, _)) => { Dynamic(Union::Str(s, _, _)) => {
// val_string[idx] // val_string[idx]
let index = _idx let index = _idx
.as_int() .as_int()
@ -1861,6 +1847,7 @@ impl Engine {
Some(OpAssignment::new(TOKEN_OP_CONCAT)), Some(OpAssignment::new(TOKEN_OP_CONCAT)),
pos, pos,
(&mut result).into(), (&mut result).into(),
("", Position::NONE),
item, item,
expr.position(), expr.position(),
)?; )?;
@ -1905,7 +1892,7 @@ impl Engine {
namespace, namespace,
hashes, hashes,
args, args,
constant_args: c_args, literal_args: c_args,
.. ..
} = x.as_ref(); } = x.as_ref();
let namespace = namespace.as_ref(); let namespace = namespace.as_ref();
@ -1923,7 +1910,7 @@ impl Engine {
capture, capture,
hashes, hashes,
args, args,
constant_args: c_args, literal_args: c_args,
.. ..
} = x.as_ref(); } = x.as_ref();
self.make_function_call( self.make_function_call(
@ -2077,11 +2064,13 @@ impl Engine {
op_info: Option<OpAssignment>, op_info: Option<OpAssignment>,
op_pos: Position, op_pos: Position,
mut target: Target, mut target: Target,
root: (&str, Position),
mut new_value: Dynamic, mut new_value: Dynamic,
new_value_pos: Position, new_value_pos: Position,
) -> Result<(), Box<EvalAltResult>> { ) -> Result<(), Box<EvalAltResult>> {
if target.is_read_only() { 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 { if let Some(OpAssignment {
@ -2180,26 +2169,18 @@ impl Engine {
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
self.inc_operations(state, pos)?; self.inc_operations(state, pos)?;
if lhs_ptr.is_read_only() { self.eval_op_assignment(
// Assignment to constant variable mods,
EvalAltResult::ErrorAssignmentToConstant( state,
lhs_expr.get_variable_name(false).unwrap().to_string(), lib,
pos, op_info.clone(),
) *op_pos,
.into() lhs_ptr,
} else { (lhs_expr.get_variable_name(false).unwrap(), pos),
self.eval_op_assignment( rhs_val,
mods, rhs_expr.position(),
state, )?;
lib, Ok(Dynamic::UNIT)
op_info.clone(),
*op_pos,
lhs_ptr,
rhs_val,
rhs_expr.position(),
)?;
Ok(Dynamic::UNIT)
}
} }
// lhs op= rhs // lhs op= rhs
@ -2467,7 +2448,7 @@ impl Engine {
namespace, namespace,
hashes, hashes,
args, args,
constant_args: c_args, literal_args: c_args,
.. ..
} = x.as_ref(); } = x.as_ref();
let namespace = namespace.as_ref(); let namespace = namespace.as_ref();
@ -2485,7 +2466,7 @@ impl Engine {
capture, capture,
hashes, hashes,
args, args,
constant_args: c_args, literal_args: c_args,
.. ..
} = x.as_ref(); } = x.as_ref();
self.make_function_call( self.make_function_call(
@ -2569,7 +2550,7 @@ impl Engine {
Ok(_) => Ok(Dynamic::UNIT), Ok(_) => Ok(Dynamic::UNIT),
Err(result_err) => match *result_err { Err(result_err) => match *result_err {
// Re-throw exception // Re-throw exception
EvalAltResult::ErrorRuntime(Dynamic(Union::Unit(_, _)), pos) => { EvalAltResult::ErrorRuntime(Dynamic(Union::Unit(_, _, _)), pos) => {
err.set_position(pos); err.set_position(pos);
Err(err) Err(err)
} }
@ -2623,18 +2604,15 @@ impl Engine {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
if entry_type == AccessMode::ReadOnly && lib.iter().any(|&m| !m.is_empty()) { if entry_type == AccessMode::ReadOnly && lib.iter().any(|&m| !m.is_empty()) {
let global = if let Some(index) = mods.find(KEYWORD_GLOBAL) { let global = if let Some(index) = mods.find(KEYWORD_GLOBAL) {
let global = mods.get_mut(index).unwrap(); match mods.get_mut(index).unwrap() {
m if m.internal => Some(m),
if !global.is_internal() { _ => None,
None
} else {
Some(global)
} }
} else { } else {
// Create automatic global module // Create automatic global module
let mut global = Module::new(); let mut global = Module::new();
global.set_internal(true); global.internal = true;
mods.push(crate::engine::KEYWORD_GLOBAL, global); mods.push(KEYWORD_GLOBAL, global);
Some(mods.get_mut(mods.len() - 1).unwrap()) Some(mods.get_mut(mods.len() - 1).unwrap())
}; };
@ -2777,18 +2755,18 @@ impl Engine {
fn calc_size(value: &Dynamic) -> (usize, usize, usize) { fn calc_size(value: &Dynamic) -> (usize, usize, usize) {
match value { match value {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(arr, _)) => { Dynamic(Union::Array(arr, _, _)) => {
let mut arrays = 0; let mut arrays = 0;
let mut maps = 0; let mut maps = 0;
arr.iter().for_each(|value| match value { arr.iter().for_each(|value| match value {
Dynamic(Union::Array(_, _)) => { Dynamic(Union::Array(_, _, _)) => {
let (a, m, _) = calc_size(value); let (a, m, _) = calc_size(value);
arrays += a; arrays += a;
maps += m; maps += m;
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Dynamic(Union::Map(_, _)) => { Dynamic(Union::Map(_, _, _)) => {
let (a, m, _) = calc_size(value); let (a, m, _) = calc_size(value);
arrays += a; arrays += a;
maps += m; maps += m;
@ -2799,18 +2777,18 @@ impl Engine {
(arrays, maps, 0) (arrays, maps, 0)
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Dynamic(Union::Map(map, _)) => { Dynamic(Union::Map(map, _, _)) => {
let mut arrays = 0; let mut arrays = 0;
let mut maps = 0; let mut maps = 0;
map.values().for_each(|value| match value { map.values().for_each(|value| match value {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(_, _)) => { Dynamic(Union::Array(_, _, _)) => {
let (a, m, _) = calc_size(value); let (a, m, _) = calc_size(value);
arrays += a; arrays += a;
maps += m; maps += m;
} }
Dynamic(Union::Map(_, _)) => { Dynamic(Union::Map(_, _, _)) => {
let (a, m, _) = calc_size(value); let (a, m, _) = calc_size(value);
arrays += a; arrays += a;
maps += m; maps += m;
@ -2820,7 +2798,7 @@ impl Engine {
(arrays, maps, 0) (arrays, maps, 0)
} }
Dynamic(Union::Str(s, _)) => (0, 0, s.len()), Dynamic(Union::Str(s, _, _)) => (0, 0, s.len()),
_ => (0, 0, 0), _ => (0, 0, 0),
} }
} }

View File

@ -183,6 +183,8 @@ impl Engine {
/// Register a custom type for use with the [`Engine`]. /// Register a custom type for use with the [`Engine`].
/// The type must implement [`Clone`]. /// The type must implement [`Clone`].
/// ///
/// Not available under `no_object`.
///
/// # Example /// # Example
/// ///
/// ``` /// ```
@ -224,6 +226,8 @@ impl Engine {
/// Register a custom type for use with the [`Engine`], with a pretty-print name /// Register a custom type for use with the [`Engine`], with a pretty-print name
/// for the `type_of` function. The type must implement [`Clone`]. /// for the `type_of` function. The type must implement [`Clone`].
/// ///
/// Not available under `no_object`.
///
/// # Example /// # Example
/// ///
/// ``` /// ```
@ -265,7 +269,8 @@ impl Engine {
#[inline(always)] #[inline(always)]
pub fn register_type_with_name<T: Variant + Clone>(&mut self, name: &str) -> &mut Self { pub fn register_type_with_name<T: Variant + Clone>(&mut self, name: &str) -> &mut Self {
// Add the pretty-print type name into the map // 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 self
} }
/// Register an type iterator for an iterable type with the [`Engine`]. /// 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`. /// The function signature must start with `&mut self` and not `&self`.
/// ///
/// Not available under `no_object`.
///
/// # Example /// # Example
/// ///
/// ``` /// ```
@ -328,6 +335,8 @@ impl Engine {
/// ///
/// The function signature must start with `&mut self` and not `&self`. /// The function signature must start with `&mut self` and not `&self`.
/// ///
/// Not available under `no_object`.
///
/// # Example /// # Example
/// ///
/// ``` /// ```
@ -373,6 +382,8 @@ impl Engine {
} }
/// Register a setter function for a member of a registered type with the [`Engine`]. /// Register a setter function for a member of a registered type with the [`Engine`].
/// ///
/// Not available under `no_object`.
///
/// # Example /// # Example
/// ///
/// ``` /// ```
@ -419,6 +430,8 @@ impl Engine {
} }
/// Register a setter function for a member of a registered type with the [`Engine`]. /// Register a setter function for a member of a registered type with the [`Engine`].
/// ///
/// Not available under `no_object`.
///
/// # Example /// # Example
/// ///
/// ``` /// ```
@ -473,6 +486,8 @@ impl Engine {
/// ///
/// All function signatures must start with `&mut self` and not `&self`. /// All function signatures must start with `&mut self` and not `&self`.
/// ///
/// Not available under `no_object`.
///
/// # Example /// # Example
/// ///
/// ``` /// ```
@ -521,6 +536,8 @@ impl Engine {
/// ///
/// The function signature must start with `&mut self` and not `&self`. /// The function signature must start with `&mut self` and not `&self`.
/// ///
/// Not available under `no_index`.
///
/// # Panics /// # Panics
/// ///
/// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`. /// 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`. /// The function signature must start with `&mut self` and not `&self`.
/// ///
/// Not available under `no_index`.
///
/// # Panics /// # Panics
/// ///
/// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`. /// 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`]. /// Register an index setter for a custom type with the [`Engine`].
/// ///
/// Not available under `no_index`.
///
/// # Panics /// # Panics
/// ///
/// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`. /// 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`]. /// Register an index setter for a custom type with the [`Engine`].
/// ///
/// Not available under `no_index`.
///
/// # Panics /// # Panics
/// ///
/// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`. /// 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`]. /// Short-hand for register both index getter and setter functions for a custom type with the [`Engine`].
/// ///
/// Not available under `no_index`.
///
/// # Panics /// # Panics
/// ///
/// Panics if the type is [`Array`], [`Map`], [`String`], [`ImmutableString`][crate::ImmutableString] or `&str`. /// 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 /// Functions marked [`FnNamespace::Global`] and type iterators are exposed to scripts without
/// namespace qualifications. /// namespace qualifications.
/// ///
/// Not available under `no_module`.
///
/// # Example /// # Example
/// ///
/// ``` /// ```
@ -1023,6 +1050,8 @@ impl Engine {
/// Compile a string into an [`AST`] using own scope, which can be used later for evaluation, /// Compile a string into an [`AST`] using own scope, which can be used later for evaluation,
/// embedding all imported modules. /// embedding all imported modules.
/// ///
/// Not available under `no_module`.
///
/// Modules referred by `import` statements containing literal string paths are eagerly resolved /// Modules referred by `import` statements containing literal string paths are eagerly resolved
/// via the current [module resolver][crate::ModuleResolver] and embedded into the resultant /// via the current [module resolver][crate::ModuleResolver] and embedded into the resultant
/// [`AST`]. When it is evaluated later, `import` statement directly recall pre-resolved /// [`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`]. /// Parse a JSON string into an [object map][`Map`].
/// This is a light-weight alternative to using, say, [`serde_json`] to deserialize the JSON. /// 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. /// 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 `()`. /// 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. /// Call a script function defined in an [`AST`] with multiple arguments.
/// Arguments are passed as a tuple. /// Arguments are passed as a tuple.
/// ///
/// Not available under `no_function`.
///
/// The [`AST`] is evaluated before calling the function. /// The [`AST`] is evaluated before calling the function.
/// This allows a script to load the necessary modules. /// This allows a script to load the necessary modules.
/// This is usually desired. If not, a specialized [`AST`] can be prepared that contains only /// 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 /// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments
/// and optionally a value for binding to the `this` pointer. /// 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. /// There is an option to evaluate the [`AST`] to load necessary modules before calling the function.
/// ///
/// # WARNING /// # WARNING
@ -1970,6 +2005,8 @@ impl Engine {
/// Optimize the [`AST`] with constants defined in an external Scope. /// Optimize the [`AST`] with constants defined in an external Scope.
/// An optimized copy of the [`AST`] is returned while the original [`AST`] is consumed. /// 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 /// 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 /// _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 /// 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()); let stmt = std::mem::take(ast.statements_mut());
crate::optimize::optimize_into_ast(self, scope, stmt.into_vec(), lib, optimization_level) 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. /// Exported under the `metadata` feature only.
/// ///
/// Functions from the following sources are included, in order: /// Functions from the following sources are included, in order:
@ -2073,6 +2110,8 @@ impl Engine {
} }
/// Register a callback for script evaluation progress. /// Register a callback for script evaluation progress.
/// ///
/// Not available under `unchecked`.
///
/// # Example /// # Example
/// ///
/// ``` /// ```

View File

@ -31,16 +31,6 @@ impl Engine {
pub fn optimization_level(&self) -> crate::OptimizationLevel { pub fn optimization_level(&self) -> crate::OptimizationLevel {
self.optimization_level 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 /// Set the maximum levels of function calls allowed for a script in order to avoid
/// infinite recursion and stack overflows. /// infinite recursion and stack overflows.
/// ///

View File

@ -184,15 +184,19 @@ pub fn get_builtin_binary_op_fn(
"*" => impl_op!(from Decimal => multiply($xx, $yy)), "*" => impl_op!(from Decimal => multiply($xx, $yy)),
"/" => impl_op!(from Decimal => divide($xx, $yy)), "/" => impl_op!(from Decimal => divide($xx, $yy)),
"%" => impl_op!(from Decimal => modulo($xx, $yy)), "%" => impl_op!(from Decimal => modulo($xx, $yy)),
"**" => impl_op!(from Decimal => power($xx, $yy)),
_ => () _ => ()
} }
} else { } else {
use rust_decimal::MathematicalOps;
match op { 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 * $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 => multiply($xx, $yy)),
"/=" => impl_op!(from $x => divide($xx, $yy)), "/=" => impl_op!(from $x => divide($xx, $yy)),
"%=" => impl_op!(from $x => modulo($xx, $yy)), "%=" => impl_op!(from $x => modulo($xx, $yy)),
"**=" => impl_op!(from $x => power($xx, $yy)),
_ => return None, _ => return None,
} }
} else { } else {
use rust_decimal::MathematicalOps;
match op { 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 *= $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, _ => return None,
} }
} }

View File

@ -161,7 +161,7 @@ impl Engine {
args: Option<&mut FnCallArgs>, args: Option<&mut FnCallArgs>,
allow_dynamic: bool, allow_dynamic: bool,
is_op_assignment: bool, is_op_assignment: bool,
) -> &'s Option<FnResolutionCacheEntry> { ) -> &'s Option<Box<FnResolutionCacheEntry>> {
let mut hash = if let Some(ref args) = args { let mut hash = if let Some(ref args) = args {
let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id())); let hash_params = calc_fn_params_hash(args.iter().map(|a| a.type_id()));
combine_hashes(hash_script, hash_params) combine_hashes(hash_script, hash_params)
@ -222,7 +222,7 @@ impl Engine {
match func { match func {
// Specific version found // Specific version found
Some(f) => return Some(f), Some(f) => return Some(Box::new(f)),
// Stop when all permutations are exhausted // Stop when all permutations are exhausted
None if bitmask >= max_bitmask => { None if bitmask >= max_bitmask => {
@ -250,6 +250,7 @@ impl Engine {
}, },
) )
} }
.map(Box::new)
}); });
} }
@ -311,7 +312,8 @@ impl Engine {
is_op_assignment, 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()); assert!(func.is_native());
// Calling pure function but the first argument is a reference? // Calling pure function but the first argument is a reference?
@ -717,10 +719,11 @@ impl Engine {
}; };
#[cfg(not(feature = "no_function"))] #[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) self.resolve_function(mods, state, lib, fn_name, hash, None, false, false)
.clone() .clone()
}) { }) {
let FnResolutionCacheEntry { func, source } = *f;
// Script function call // Script function call
assert!(func.is_script()); assert!(func.is_script());
@ -1062,7 +1065,7 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>, this_ptr: &mut Option<&mut Dynamic>,
fn_name: &str, fn_name: &str,
args_expr: &[Expr], args_expr: &[Expr],
constant_args: &[(Dynamic, Position)], literal_args: &[(Dynamic, Position)],
mut hashes: FnCallHashes, mut hashes: FnCallHashes,
pos: Position, pos: Position,
capture_scope: bool, capture_scope: bool,
@ -1071,8 +1074,8 @@ impl Engine {
// Handle call() - Redirect function call // Handle call() - Redirect function call
let redirected; let redirected;
let mut args_expr = args_expr; let mut args_expr = args_expr;
let mut constant_args = constant_args; let mut literal_args = literal_args;
let mut total_args = args_expr.len() + constant_args.len(); let mut total_args = args_expr.len() + literal_args.len();
let mut curry = StaticVec::new(); let mut curry = StaticVec::new();
let mut name = fn_name; let mut name = fn_name;
@ -1080,7 +1083,7 @@ impl Engine {
// Handle call() // Handle call()
KEYWORD_FN_PTR_CALL if total_args >= 1 => { KEYWORD_FN_PTR_CALL if total_args >= 1 => {
let (arg, arg_pos) = args_expr.get(0).map_or_else( let (arg, arg_pos) = args_expr.get(0).map_or_else(
|| Ok(constant_args[0].clone()), || Ok(literal_args[0].clone()),
|arg| { |arg| {
self.eval_expr(scope, mods, state, lib, this_ptr, arg, level) self.eval_expr(scope, mods, state, lib, this_ptr, arg, level)
.map(|v| (v, arg.position())) .map(|v| (v, arg.position()))
@ -1105,7 +1108,7 @@ impl Engine {
if !args_expr.is_empty() { if !args_expr.is_empty() {
args_expr = &args_expr[1..]; args_expr = &args_expr[1..];
} else { } else {
constant_args = &constant_args[1..]; literal_args = &literal_args[1..];
} }
total_args -= 1; total_args -= 1;
@ -1120,7 +1123,7 @@ impl Engine {
// Handle Fn() // Handle Fn()
KEYWORD_FN_PTR if total_args == 1 => { KEYWORD_FN_PTR if total_args == 1 => {
let (arg, arg_pos) = args_expr.get(0).map_or_else( let (arg, arg_pos) = args_expr.get(0).map_or_else(
|| Ok(constant_args[0].clone()), || Ok(literal_args[0].clone()),
|arg| { |arg| {
self.eval_expr(scope, mods, state, lib, this_ptr, arg, level) self.eval_expr(scope, mods, state, lib, this_ptr, arg, level)
.map(|v| (v, arg.position())) .map(|v| (v, arg.position()))
@ -1139,7 +1142,7 @@ impl Engine {
// Handle curry() // Handle curry()
KEYWORD_FN_PTR_CURRY if total_args > 1 => { KEYWORD_FN_PTR_CURRY if total_args > 1 => {
let (arg, arg_pos) = args_expr.get(0).map_or_else( let (arg, arg_pos) = args_expr.get(0).map_or_else(
|| Ok(constant_args[0].clone()), || Ok(literal_args[0].clone()),
|arg| { |arg| {
self.eval_expr(scope, mods, state, lib, this_ptr, arg, level) self.eval_expr(scope, mods, state, lib, this_ptr, arg, level)
.map(|v| (v, arg.position())) .map(|v| (v, arg.position()))
@ -1161,9 +1164,9 @@ impl Engine {
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
.map(|value| fn_curry.push(value)) .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 { } 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()); return Ok(FnPtr::new_unchecked(name, fn_curry).into());
@ -1173,7 +1176,7 @@ impl Engine {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
crate::engine::KEYWORD_IS_SHARED if total_args == 1 => { crate::engine::KEYWORD_IS_SHARED if total_args == 1 => {
let arg = args_expr.get(0).map_or_else( 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), |arg| self.eval_expr(scope, mods, state, lib, this_ptr, arg, level),
)?; )?;
return Ok(arg.is_shared().into()); return Ok(arg.is_shared().into());
@ -1188,7 +1191,7 @@ impl Engine {
args_expr[0].position(), args_expr[0].position(),
) )
} else { } else {
constant_args[0].clone() literal_args[0].clone()
}; };
let fn_name = arg let fn_name = arg
@ -1201,7 +1204,7 @@ impl Engine {
args_expr[1].position(), args_expr[1].position(),
) )
} else { } 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 let num_params = arg
@ -1220,7 +1223,7 @@ impl Engine {
// Handle is_def_var() // Handle is_def_var()
KEYWORD_IS_DEF_VAR if total_args == 1 => { KEYWORD_IS_DEF_VAR if total_args == 1 => {
let (arg, arg_pos) = args_expr.get(0).map_or_else( let (arg, arg_pos) = args_expr.get(0).map_or_else(
|| Ok(constant_args[0].clone()), || Ok(literal_args[0].clone()),
|arg| { |arg| {
self.eval_expr(scope, mods, state, lib, this_ptr, arg, level) self.eval_expr(scope, mods, state, lib, this_ptr, arg, level)
.map(|v| (v, arg.position())) .map(|v| (v, arg.position()))
@ -1237,7 +1240,7 @@ impl Engine {
// eval - only in function call style // eval - only in function call style
let prev_len = scope.len(); let prev_len = scope.len();
let (script, script_pos) = args_expr.get(0).map_or_else( let (script, script_pos) = args_expr.get(0).map_or_else(
|| Ok(constant_args[0].clone()), || Ok(literal_args[0].clone()),
|script_expr| { |script_expr| {
self.eval_expr(scope, mods, state, lib, this_ptr, script_expr, level) self.eval_expr(scope, mods, state, lib, this_ptr, script_expr, level)
.map(|v| (v, script_expr.position())) .map(|v| (v, script_expr.position()))
@ -1289,7 +1292,7 @@ impl Engine {
None 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 // No arguments
args = Default::default(); args = Default::default();
} else { } else {
@ -1305,7 +1308,7 @@ impl Engine {
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
.map(Dynamic::flatten) .map(Dynamic::flatten)
}) })
.chain(constant_args.iter().map(|(v, _)| Ok(v.clone()))) .chain(literal_args.iter().map(|(v, _)| Ok(v.clone())))
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
let (mut target, _pos) = let (mut target, _pos) =
@ -1341,7 +1344,7 @@ impl Engine {
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
.map(Dynamic::flatten) .map(Dynamic::flatten)
}) })
.chain(constant_args.iter().map(|(v, _)| Ok(v.clone()))) .chain(literal_args.iter().map(|(v, _)| Ok(v.clone())))
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
args = curry.iter_mut().chain(arg_values.iter_mut()).collect(); args = curry.iter_mut().chain(arg_values.iter_mut()).collect();
@ -1365,7 +1368,7 @@ impl Engine {
namespace: Option<&NamespaceRef>, namespace: Option<&NamespaceRef>,
fn_name: &str, fn_name: &str,
args_expr: &[Expr], args_expr: &[Expr],
constant_args: &[(Dynamic, Position)], literal_args: &[(Dynamic, Position)],
hash: u64, hash: u64,
pos: Position, pos: Position,
level: usize, level: usize,
@ -1375,7 +1378,7 @@ impl Engine {
let mut first_arg_value = None; let mut first_arg_value = None;
let mut args: StaticVec<_>; let mut args: StaticVec<_>;
if args_expr.is_empty() && constant_args.is_empty() { if args_expr.is_empty() && literal_args.is_empty() {
// No arguments // No arguments
args = Default::default(); args = Default::default();
} else { } else {
@ -1396,7 +1399,7 @@ impl Engine {
.map(Dynamic::flatten) .map(Dynamic::flatten)
} }
}) })
.chain(constant_args.iter().map(|(v, _)| Ok(v.clone()))) .chain(literal_args.iter().map(|(v, _)| Ok(v.clone())))
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
// Get target reference to first argument // Get target reference to first argument
@ -1429,7 +1432,7 @@ impl Engine {
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)
.map(Dynamic::flatten) .map(Dynamic::flatten)
}) })
.chain(constant_args.iter().map(|(v, _)| Ok(v.clone()))) .chain(literal_args.iter().map(|(v, _)| Ok(v.clone())))
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>()?;
args = arg_values.iter_mut().collect(); args = arg_values.iter_mut().collect();

View File

@ -5,8 +5,8 @@ use crate::engine::Imports;
use crate::plugin::PluginFunction; use crate::plugin::PluginFunction;
use crate::token::is_valid_identifier; use crate::token::is_valid_identifier;
use crate::{ use crate::{
calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Identifier, ImmutableString, Module, calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Identifier, Module, Position,
Position, RhaiResult, StaticVec, RhaiResult, StaticVec,
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -39,10 +39,14 @@ pub use std::rc::Rc as Shared;
pub use std::sync::Arc as Shared; pub use std::sync::Arc as Shared;
/// Synchronized shared object. /// Synchronized shared object.
///
/// Not available under `no_closure`.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub use std::cell::RefCell as Locked; pub use std::cell::RefCell as Locked;
/// Synchronized shared object. /// Synchronized shared object.
///
/// Not available under `no_closure`.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub use std::sync::RwLock as Locked; pub use std::sync::RwLock as Locked;
@ -101,6 +105,8 @@ impl<'a> NativeCallContext<'a> {
} }
/// _(INTERNALS)_ Create a new [`NativeCallContext`]. /// _(INTERNALS)_ Create a new [`NativeCallContext`].
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
///
/// Not available under `no_module`.
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
@ -135,6 +141,8 @@ impl<'a> NativeCallContext<'a> {
self.source self.source
} }
/// Get an iterator over the current set of modules imported via `import` statements. /// Get an iterator over the current set of modules imported via `import` statements.
///
/// Not available under `no_module`.
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
pub fn iter_imports(&self) -> impl Iterator<Item = (&str, &Module)> { 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. /// _(INTERNALS)_ The current set of modules imported via `import` statements.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
///
/// Not available under `no_module`.
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
#[inline(always)] #[inline(always)]
@ -303,6 +313,8 @@ impl FnPtr {
!self.1.is_empty() !self.1.is_empty()
} }
/// Does the function pointer refer to an anonymous function? /// Does the function pointer refer to an anonymous function?
///
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline(always)] #[inline(always)]
pub fn is_anonymous(&self) -> bool { pub fn is_anonymous(&self) -> bool {
@ -367,16 +379,17 @@ impl TryFrom<Identifier> for FnPtr {
if is_valid_identifier(value.chars()) { if is_valid_identifier(value.chars()) {
Ok(Self(value, Default::default())) Ok(Self(value, Default::default()))
} else { } 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>; type Error = Box<EvalAltResult>;
#[inline(always)] #[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(); let s: Identifier = value.into();
Self::try_from(s) Self::try_from(s)
} }
@ -466,6 +479,8 @@ pub enum CallableFunction {
/// A plugin function, /// A plugin function,
Plugin(Shared<FnPlugin>), Plugin(Shared<FnPlugin>),
/// A script-defined function. /// A script-defined function.
///
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Script(Shared<crate::ast::ScriptFnDef>), Script(Shared<crate::ast::ScriptFnDef>),
} }
@ -600,6 +615,8 @@ impl CallableFunction {
} }
/// Get a shared reference to a script-defined function definition. /// Get a shared reference to a script-defined function definition.
/// ///
/// Not available under `no_function`.
///
/// # Panics /// # Panics
/// ///
/// Panics if the [`CallableFunction`] is not [`Script`][CallableFunction::Script]. /// Panics if the [`CallableFunction`] is not [`Script`][CallableFunction::Script].

View File

@ -5,6 +5,7 @@
use crate::dynamic::{DynamicWriteLock, Variant}; use crate::dynamic::{DynamicWriteLock, Variant};
use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync}; use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync};
use crate::r#unsafe::unsafe_try_cast; use crate::r#unsafe::unsafe_try_cast;
use crate::token::Position;
use crate::{Dynamic, EvalAltResult, NativeCallContext}; use crate::{Dynamic, EvalAltResult, NativeCallContext};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -61,20 +62,33 @@ pub trait RegisterNativeFunction<Args, Result> {
fn into_callable_function(self) -> CallableFunction; fn into_callable_function(self) -> CallableFunction;
/// Get the type ID's of this function's parameters. /// Get the type ID's of this function's parameters.
fn param_types() -> Box<[TypeId]>; 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. /// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
fn param_names() -> Box<[&'static str]>; 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. /// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
fn return_type() -> TypeId; 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. /// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
fn return_type_name() -> &'static str; 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 { macro_rules! def_register {
() => { () => {
def_register!(imp from_pure :); 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() -> TypeId { TypeId::of::<RET>() }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<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 { #[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! // The arguments are assumed to be of the correct number and types!
let mut _drain = args.iter_mut(); let mut _drain = args.iter_mut();
$($let $par = ($clone)(_drain.next().unwrap()); )* $($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>() } #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RET>() }
#[inline(always)] fn into_callable_function(self) -> CallableFunction { #[inline(always)] fn into_callable_function(self) -> CallableFunction {
CallableFunction::$abi(Box::new(move |ctx: 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! // The arguments are assumed to be of the correct number and types!
let mut _drain = args.iter_mut(); let mut _drain = args.iter_mut();
$($let $par = ($clone)(_drain.next().unwrap()); )* $($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() -> 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>>>() } #[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 { #[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! // The arguments are assumed to be of the correct number and types!
let mut _drain = args.iter_mut(); let mut _drain = args.iter_mut();
$($let $par = ($clone)(_drain.next().unwrap()); )* $($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>>>() } #[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 { #[inline(always)] fn into_callable_function(self) -> CallableFunction {
CallableFunction::$abi(Box::new(move |ctx: 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! // The arguments are assumed to be of the correct number and types!
let mut _drain = args.iter_mut(); let mut _drain = args.iter_mut();
$($let $par = ($clone)(_drain.next().unwrap()); )* $($let $par = ($clone)(_drain.next().unwrap()); )*

View File

@ -140,11 +140,11 @@ pub use utils::ImmutableString;
/// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most /// 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. /// 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; pub type Identifier = SmartString;
/// An identifier in Rhai. /// An identifier in Rhai.
#[cfg(feature = "no_smartstring_for_identifier")] #[cfg(feature = "no_smartstring")]
pub type Identifier = ImmutableString; pub type Identifier = ImmutableString;
/// A trait to enable registering Rust functions. /// A trait to enable registering Rust functions.
@ -207,15 +207,17 @@ pub use optimize::OptimizationLevel;
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[deprecated = "this type is volatile and may change"] #[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. // Expose internal data structures.
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[deprecated = "this type is volatile and may change"] #[deprecated = "this type is volatile and may change"]
pub use token::{ pub use token::{InputStream, Token, TokenizeState, TokenizerControl, TokenizerControlBlock};
get_next_token, parse_string_literal, InputStream, Token, TokenizeState, TokenizerControl,
TokenizerControlBlock,
};
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[deprecated = "this type is volatile and may change"] #[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]>; pub type StaticVec<T> = smallvec::SmallVec<[T; 4]>;
#[cfg(not(feature = "internals"))] #[cfg(not(feature = "internals"))]
#[cfg(not(feature = "no_smartstring"))]
pub(crate) type SmartString = smartstring::SmartString<smartstring::Compact>; pub(crate) type SmartString = smartstring::SmartString<smartstring::Compact>;
#[cfg(feature = "no_smartstring")]
pub(crate) type SmartString = String;
#[cfg(feature = "internals")] #[cfg(feature = "internals")]
#[cfg(not(feature = "no_smartstring"))]
pub type SmartString = smartstring::SmartString<smartstring::Compact>; pub type SmartString = smartstring::SmartString<smartstring::Compact>;
// Compiler guards against mutually-exclusive feature flags // Compiler guards against mutually-exclusive feature flags

View File

@ -48,7 +48,7 @@ impl Default for FnNamespace {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FuncInfo { pub struct FuncInfo {
/// Function instance. /// Function instance.
pub func: CallableFunction, pub func: Shared<CallableFunction>,
/// Function namespace. /// Function namespace.
pub namespace: FnNamespace, pub namespace: FnNamespace,
/// Function access mode. /// Function access mode.
@ -129,7 +129,7 @@ pub struct Module {
/// ID identifying the module. /// ID identifying the module.
id: Option<Identifier>, id: Option<Identifier>,
/// Is this module internal? /// Is this module internal?
internal: bool, pub(crate) internal: bool,
/// Sub-modules. /// Sub-modules.
modules: BTreeMap<Identifier, Shared<Module>>, modules: BTreeMap<Identifier, Shared<Module>>,
/// [`Module`] variables. /// [`Module`] variables.
@ -140,7 +140,7 @@ pub struct Module {
functions: BTreeMap<u64, Box<FuncInfo>>, functions: BTreeMap<u64, Box<FuncInfo>>,
/// Flattened collection of all external Rust functions, native or scripted. /// Flattened collection of all external Rust functions, native or scripted.
/// including those in sub-modules. /// 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. /// Iterator functions, keyed by the type producing the iterator.
type_iterators: BTreeMap<TypeId, IteratorFn>, type_iterators: BTreeMap<TypeId, IteratorFn>,
/// Flattened collection of iterator functions, including those in sub-modules. /// Flattened collection of iterator functions, including those in sub-modules.
@ -309,20 +309,6 @@ impl Module {
self 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? /// Is the [`Module`] empty?
/// ///
/// # Example /// # Example
@ -476,10 +462,7 @@ impl Module {
/// If there is an existing function of the same name and number of arguments, it is replaced. /// If there is an existing function of the same name and number of arguments, it is replaced.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline] #[inline]
pub(crate) fn set_script_fn( pub fn set_script_fn(&mut self, fn_def: impl Into<Shared<crate::ast::ScriptFnDef>>) -> u64 {
&mut self,
fn_def: impl Into<Shared<crate::ast::ScriptFnDef>>,
) -> u64 {
let fn_def = fn_def.into(); let fn_def = fn_def.into();
// None + function name + number of arguments. // None + function name + number of arguments.
@ -497,7 +480,7 @@ impl Module {
param_types: Default::default(), param_types: Default::default(),
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
param_names, param_names,
func: fn_def.into(), func: Into::<CallableFunction>::into(fn_def).into(),
}), }),
); );
self.indexed = false; self.indexed = false;
@ -695,19 +678,22 @@ impl Module {
) -> u64 { ) -> u64 {
let is_method = func.is_method(); let is_method = func.is_method();
let param_types: StaticVec<_> = arg_types let mut param_types: StaticVec<_> = arg_types
.iter() .iter()
.cloned() .cloned()
.enumerate() .enumerate()
.map(|(i, type_id)| Self::map_type(!is_method || i > 0, type_id)) .map(|(i, type_id)| Self::map_type(!is_method || i > 0, type_id))
.collect(); .collect();
param_types.shrink_to_fit();
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
let param_names = _arg_names let mut param_names: StaticVec<_> = _arg_names
.iter() .iter()
.flat_map(|p| p.iter()) .flat_map(|p| p.iter())
.map(|&arg| self.identifiers.get(arg)) .map(|&arg| self.identifiers.get(arg))
.collect(); .collect();
#[cfg(feature = "metadata")]
param_names.shrink_to_fit();
let hash_fn = calc_native_fn_hash(empty(), &name, &param_types); let hash_fn = calc_native_fn_hash(empty(), &name, &param_types);
@ -721,7 +707,7 @@ impl Module {
param_types, param_types,
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
param_names, 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. /// The [`u64`] hash is returned by the [`set_native_fn`][Module::set_native_fn] call.
#[inline(always)] #[inline(always)]
pub(crate) fn get_fn(&self, hash_fn: u64) -> Option<&CallableFunction> { 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`]? /// 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]. /// The [`u64`] hash is calculated by [`build_index`][Module::build_index].
#[inline(always)] #[inline(always)]
pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&CallableFunction> { 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`]. /// Combine another [`Module`] into this [`Module`].
@ -1454,7 +1442,7 @@ impl Module {
.filter(|f| f.func.is_script()) .filter(|f| f.func.is_script())
.for_each(|f| { .for_each(|f| {
// Encapsulate AST environment // 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.lib = Some(ast.shared_lib());
func.mods = func_mods.clone(); func.mods = func_mods.clone();
module.set_script_fn(func); module.set_script_fn(func);
@ -1486,7 +1474,7 @@ impl Module {
module: &'a Module, module: &'a Module,
path: &mut Vec<&'a str>, path: &mut Vec<&'a str>,
variables: &mut BTreeMap<u64, Dynamic>, variables: &mut BTreeMap<u64, Dynamic>,
functions: &mut BTreeMap<u64, CallableFunction>, functions: &mut BTreeMap<u64, Shared<CallableFunction>>,
type_iterators: &mut BTreeMap<TypeId, IteratorFn>, type_iterators: &mut BTreeMap<TypeId, IteratorFn>,
) -> bool { ) -> bool {
let mut contains_indexed_global_functions = false; let mut contains_indexed_global_functions = false;
@ -1690,6 +1678,8 @@ impl DerefMut for NamespaceRef {
impl From<StaticVec<Ident>> for NamespaceRef { impl From<StaticVec<Ident>> for NamespaceRef {
#[inline(always)] #[inline(always)]
fn from(path: StaticVec<Ident>) -> Self { fn from(path: StaticVec<Ident>) -> Self {
let mut path = path;
path.shrink_to_fit();
Self { index: None, path } Self { index: None, path }
} }
} }

View File

@ -390,7 +390,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
x.2 = if x2.args.len() > 1 { x.2 = if x2.args.len() > 1 {
mem::take(&mut x2.args[1]) mem::take(&mut x2.args[1])
} else { } 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) 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 { *stmt = if preserve_result {
// -> { expr, Noop } // -> { expr, Noop }
Stmt::Block(vec![Stmt::Expr(expr), Stmt::Noop(pos)], pos) Stmt::Block(Box::new([Stmt::Expr(expr), Stmt::Noop(pos)]), pos)
} else { } else {
// -> expr // -> expr
Stmt::Expr(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(); let else_block = mem::take(&mut *x.1).into_vec();
*stmt = match optimize_stmt_block(else_block, state, preserve_result, true, false) { *stmt = match optimize_stmt_block(else_block, state, preserve_result, true, false) {
statements if statements.is_empty() => Stmt::Noop(x.1.position()), 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 // 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(); let if_block = mem::take(&mut *x.0).into_vec();
*stmt = match optimize_stmt_block(if_block, state, preserve_result, true, false) { *stmt = match optimize_stmt_block(if_block, state, preserve_result, true, false) {
statements if statements.is_empty() => Stmt::Noop(x.0.position()), 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 } // if expr { if_block } else { else_block }
@ -484,7 +484,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
condition, condition,
Box::new(( Box::new((
mem::take(&mut block.1), 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(), 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 = mem::take(&mut *block.1);
let statements = let statements =
optimize_stmt_block(statements.into_vec(), state, true, true, false); 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 { } else {
// Promote the default case // Promote the default case
@ -505,7 +505,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
} else { } else {
x.1.position() x.1.position()
}; };
*stmt = Stmt::Block(def_stmt, def_pos); *stmt = Stmt::Block(def_stmt.into_boxed_slice(), def_pos);
} }
} }
// switch // switch
@ -572,7 +572,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
if preserve_result { if preserve_result {
statements.push(Stmt::Noop(pos)) statements.push(Stmt::Noop(pos))
} }
*stmt = Stmt::Block(statements, pos); *stmt = Stmt::Block(statements.into_boxed_slice(), pos);
} else { } else {
*stmt = Stmt::Noop(pos); *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_pos = body.position();
let block = mem::take(body.statements()).into_vec(); let block = mem::take(body.statements()).into_vec();
*stmt = Stmt::Block( *stmt = Stmt::Block(
optimize_stmt_block(block, state, false, true, false), optimize_stmt_block(block, state, false, true, false).into_boxed_slice(),
block_pos, 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), Stmt::Import(expr, _, _) => optimize_expr(expr, state),
// { block } // { block }
Stmt::Block(statements, pos) => { Stmt::Block(statements, pos) => {
let mut block = let statements = mem::take(statements).into_vec();
optimize_stmt_block(mem::take(statements), state, preserve_result, true, false); let mut block = optimize_stmt_block(statements, state, preserve_result, true, false);
match block.as_mut_slice() { match block.as_mut_slice() {
[] => { [] => {
@ -624,7 +624,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
state.set_dirty(); state.set_dirty();
*stmt = mem::take(s); *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 // 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_pos = x.0.position();
let try_block = mem::take(&mut *x.0).into_vec(); let try_block = mem::take(&mut *x.0).into_vec();
*stmt = Stmt::Block( *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, try_pos,
); );
} }
@ -820,6 +820,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
} }
} }
} }
x.shrink_to_fit();
} }
// [ constant .. ] // [ constant .. ]
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -893,12 +895,12 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
if !x.is_qualified() // Non-qualified if !x.is_qualified() // Non-qualified
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations && state.optimization_level == OptimizationLevel::Simple // simple optimizations
&& x.args_count() == 1 && x.args_count() == 1
&& x.constant_args.len() == 1 && x.literal_args.len() == 1
&& x.constant_args[0].0.is::<ImmutableString>() && x.literal_args[0].0.is::<ImmutableString>()
&& x.name == KEYWORD_FN_PTR && x.name == KEYWORD_FN_PTR
=> { => {
state.set_dirty(); 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); *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 //&& !is_valid_identifier(x.name.chars()) // cannot be scripted
=> { => {
let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()) 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(); .collect();
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).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) { while x.args.last().map(Expr::is_constant).unwrap_or(false) {
let arg = x.args.pop().unwrap(); let arg = x.args.pop().unwrap();
let arg_pos = arg.position(); 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.args.shrink_to_fit();
x.literal_args.shrink_to_fit();
} }
// Eagerly call functions // Eagerly call functions
@ -963,7 +966,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
if !has_script_fn { if !has_script_fn {
let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()) 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(); .collect();
// Save the typename of the first argument if it is `type_of()` // 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) { while x.args.last().map(Expr::is_constant).unwrap_or(false) {
let arg = x.args.pop().unwrap(); let arg = x.args.pop().unwrap();
let arg_pos = arg.position(); 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.args.shrink_to_fit();
x.literal_args.shrink_to_fit();
} }
// constant-name // constant-name
@ -1020,7 +1024,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
// Custom syntax // Custom syntax
Expr::Custom(x, _) => { Expr::Custom(x, _) => {
if x.scope_delta != 0 { if x.scope_changed {
state.propagate_constants = false; state.propagate_constants = false;
} }
x.keywords.iter_mut().for_each(|expr| optimize_expr(expr, state)); 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)| { scope.iter().for_each(|(name, constant, value)| {
if !constant { if !constant {
state.push_var(name, AccessMode::ReadWrite, Expr::Unit(Position::NONE)); 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 { } else {
state.push_var(name, AccessMode::ReadOnly, Expr::Unit(Position::NONE)); state.push_var(
name,
AccessMode::ReadOnly,
Expr::DynamicConstant(Box::new(value), Position::NONE),
);
} }
}); });

View File

@ -5,9 +5,6 @@ use crate::{def_package, EvalAltResult, Position, INT};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
#[cfg(not(feature = "no_float"))]
use crate::FLOAT;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use num_traits::Float; use num_traits::Float;
@ -77,7 +74,7 @@ macro_rules! gen_arithmetic_functions {
} else if y < 0 { } else if y < 0 {
Err(make_err(format!("Integer raised to a negative index: {} ~ {}", x, y))) Err(make_err(format!("Integer raised to a negative index: {} ~ {}", x, y)))
} else { } 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 { } else {
Ok(x.pow(y as u32)) Ok(x.pow(y as u32))
@ -423,23 +420,13 @@ mod f64_functions {
1 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")] #[cfg(feature = "decimal")]
#[export_module] #[export_module]
pub mod decimal_functions { 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)] #[rhai_fn(skip, return_raw)]
pub fn add(x: Decimal, y: Decimal) -> Result<Decimal, Box<EvalAltResult>> { pub fn add(x: Decimal, y: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
@ -495,6 +482,15 @@ pub mod decimal_functions {
Ok(x % y) 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 = "-")] #[rhai_fn(name = "-")]
pub fn neg(x: Decimal) -> Decimal { pub fn neg(x: Decimal) -> Decimal {
-x -x

View File

@ -26,7 +26,7 @@ where
if r == from { if r == from {
return EvalAltResult::ErrorInFunctionCall( return EvalAltResult::ErrorInFunctionCall(
"range".to_string(), "range".to_string(),
"".to_string(), Default::default(),
Box::new(EvalAltResult::ErrorArithmetic( Box::new(EvalAltResult::ErrorArithmetic(
"step value cannot be zero".to_string(), "step value cannot be zero".to_string(),
crate::Position::NONE, crate::Position::NONE,
@ -246,7 +246,6 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
{ {
use rust_decimal::Decimal; use rust_decimal::Decimal;
use num_traits::Zero;
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
struct StepDecimalRange(Decimal, Decimal, Decimal); struct StepDecimalRange(Decimal, Decimal, Decimal);

45
src/packages/lang_core.rs Normal file
View 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);
});

View File

@ -222,6 +222,7 @@ mod float_functions {
pub fn log(x: FLOAT, base: FLOAT) -> FLOAT { pub fn log(x: FLOAT, base: FLOAT) -> FLOAT {
x.log(base) x.log(base)
} }
#[rhai_fn(name = "log")]
pub fn log10(x: FLOAT) -> FLOAT { pub fn log10(x: FLOAT) -> FLOAT {
x.log10() x.log10()
} }
@ -305,10 +306,33 @@ mod float_functions {
mod decimal_functions { mod decimal_functions {
use rust_decimal::{ use rust_decimal::{
prelude::{FromStr, RoundingStrategy}, 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")] #[rhai_fn(name = "floor", get = "floor")]
pub fn floor(x: Decimal) -> Decimal { pub fn floor(x: Decimal) -> Decimal {
x.floor() x.floor()
@ -424,6 +448,8 @@ mod decimal_functions {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub mod float { pub mod float {
use std::convert::TryFrom;
#[rhai_fn(name = "to_decimal", return_raw)] #[rhai_fn(name = "to_decimal", return_raw)]
pub fn f32_to_decimal(x: f32) -> Result<Decimal, Box<EvalAltResult>> { pub fn f32_to_decimal(x: f32) -> Result<Decimal, Box<EvalAltResult>> {
Decimal::try_from(x).map_err(|_| { Decimal::try_from(x).map_err(|_| {

View File

@ -6,6 +6,7 @@ pub(crate) mod arithmetic;
mod array_basic; mod array_basic;
mod fn_basic; mod fn_basic;
mod iter_basic; mod iter_basic;
mod lang_core;
mod logic; mod logic;
mod map_basic; mod map_basic;
mod math_basic; mod math_basic;
@ -86,6 +87,7 @@ macro_rules! def_package {
} }
impl $package { impl $package {
#[allow(dead_code)]
pub fn new() -> Self { pub fn new() -> Self {
let mut module = $root::Module::new(); let mut module = $root::Module::new();
<Self as $root::packages::Package>::init(&mut module); <Self as $root::packages::Package>::init(&mut module);

View File

@ -1,6 +1,7 @@
use super::arithmetic::ArithmeticPackage; use super::arithmetic::ArithmeticPackage;
use super::fn_basic::BasicFnPackage; use super::fn_basic::BasicFnPackage;
use super::iter_basic::BasicIteratorPackage; use super::iter_basic::BasicIteratorPackage;
use super::lang_core::LanguageCorePackage;
use super::logic::LogicPackage; use super::logic::LogicPackage;
use super::string_basic::BasicStringPackage; use super::string_basic::BasicStringPackage;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
@ -9,6 +10,7 @@ use std::prelude::v1::*;
use crate::def_package; use crate::def_package;
def_package!(crate:CorePackage:"_Core_ package containing basic facilities.", lib, { def_package!(crate:CorePackage:"_Core_ package containing basic facilities.", lib, {
LanguageCorePackage::init(lib);
ArithmeticPackage::init(lib); ArithmeticPackage::init(lib);
LogicPackage::init(lib); LogicPackage::init(lib);
BasicStringPackage::init(lib); BasicStringPackage::init(lib);

View File

@ -348,6 +348,8 @@ fn parse_fn_call(
FnCallHashes::from_native(hash) FnCallHashes::from_native(hash)
}; };
args.shrink_to_fit();
return Ok(Expr::FnCall( return Ok(Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
name: state.get_identifier(id), name: state.get_identifier(id),
@ -393,6 +395,8 @@ fn parse_fn_call(
FnCallHashes::from_native(hash) FnCallHashes::from_native(hash)
}; };
args.shrink_to_fit();
return Ok(Expr::FnCall( return Ok(Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
name: state.get_identifier(id), name: state.get_identifier(id),
@ -1066,6 +1070,7 @@ fn parse_primary(
} }
} }
segments.shrink_to_fit();
Expr::InterpolatedString(Box::new(segments)) Expr::InterpolatedString(Box::new(segments))
} }
@ -1344,6 +1349,7 @@ fn parse_unary(
expr => { expr => {
let mut args = StaticVec::new(); let mut args = StaticVec::new();
args.push(expr); args.push(expr);
args.shrink_to_fit();
Ok(Expr::FnCall( Ok(Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
@ -1370,6 +1376,7 @@ fn parse_unary(
expr => { expr => {
let mut args = StaticVec::new(); let mut args = StaticVec::new();
args.push(expr); args.push(expr);
args.shrink_to_fit();
Ok(Expr::FnCall( Ok(Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
@ -1387,8 +1394,8 @@ fn parse_unary(
Token::Bang => { Token::Bang => {
let pos = eat_token(input, Token::Bang); let pos = eat_token(input, Token::Bang);
let mut args = StaticVec::new(); let mut args = StaticVec::new();
let expr = parse_unary(input, state, lib, settings.level_up())?; args.push(parse_unary(input, state, lib, settings.level_up())?);
args.push(expr); args.shrink_to_fit();
Ok(Expr::FnCall( Ok(Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
@ -1461,24 +1468,10 @@ fn make_assignment_stmt<'a>(
Expr::Index(x, _) | Expr::Dot(x, _) => { Expr::Index(x, _) | Expr::Dot(x, _) => {
match check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _))) { match check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _))) {
None => match &x.lhs { None => match &x.lhs {
// var[???] (non-indexed) = rhs, var.??? (non-indexed) = rhs // var[???] = rhs, var.??? = rhs
Expr::Variable(None, _, x) if x.0.is_none() => { Expr::Variable(_, _, _) => {
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos)) 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[???] = rhs, expr.??? = rhs
expr => { expr => {
Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(expr.position())) Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(expr.position()))
@ -1730,6 +1723,7 @@ fn parse_binary_op(
let mut args = StaticVec::new(); let mut args = StaticVec::new();
args.push(root); args.push(root);
args.push(rhs); args.push(rhs);
args.shrink_to_fit();
root = match op_token { root = match op_token {
Token::Plus Token::Plus
@ -1782,6 +1776,7 @@ fn parse_binary_op(
// Swap the arguments // Swap the arguments
let current_lhs = args.remove(0); let current_lhs = args.remove(0);
args.push(current_lhs); args.push(current_lhs);
args.shrink_to_fit();
// Convert into a call to `contains` // Convert into a call to `contains`
let hash = calc_fn_hash(empty(), OP_CONTAINS, 2); let hash = calc_fn_hash(empty(), OP_CONTAINS, 2);
@ -1836,25 +1831,15 @@ fn parse_custom_syntax(
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
let mut keywords: StaticVec<Expr> = Default::default(); let mut keywords: StaticVec<Expr> = Default::default();
let mut segments: StaticVec<_> = Default::default(); let mut segments: StaticVec<_> = Default::default();
let mut tokens: Vec<_> = Default::default(); let mut tokens: StaticVec<_> = Default::default();
// Adjust the variables stack // Adjust the variables stack
match syntax.scope_delta { if syntax.scope_changed {
delta if delta > 0 => { // Add an empty variable name to the stack.
// Add enough empty variable names to the stack. // Empty variable names act as a barrier so earlier variables will not be matched.
// Empty variable names act as a barrier so earlier variables will not be matched. // Variable searches stop at the first empty variable name.
// Variable searches stop at the first empty variable name. let empty = state.get_identifier("");
let empty = state.get_identifier(""); state.stack.push((empty, AccessMode::ReadWrite));
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),
_ => (),
} }
let parse_func = &syntax.parse; let parse_func = &syntax.parse;
@ -1920,11 +1905,14 @@ fn parse_custom_syntax(
} }
} }
keywords.shrink_to_fit();
tokens.shrink_to_fit();
Ok(Expr::Custom( Ok(Expr::Custom(
Box::new(CustomExpr { Box::new(CustomExpr {
keywords, keywords,
tokens, tokens,
scope_delta: syntax.scope_delta, scope_changed: syntax.scope_changed,
}), }),
pos, 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. /// Parse a statement block.
@ -2467,7 +2455,7 @@ fn parse_block(
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
state.modules.truncate(prev_mods_len); 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. /// Parse an expression as a statement.
@ -2820,7 +2808,8 @@ fn parse_fn(
} }
.into(); .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"))] #[cfg(not(feature = "no_closure"))]
let externals = state let externals = state
@ -2874,6 +2863,8 @@ fn make_curry_from_externals(
)); ));
}); });
args.shrink_to_fit();
let expr = Expr::FnCall( let expr = Expr::FnCall(
Box::new(FnCallExpr { Box::new(FnCallExpr {
name: state.get_identifier(crate::engine::KEYWORD_FN_PTR_CURRY), name: state.get_identifier(crate::engine::KEYWORD_FN_PTR_CURRY),
@ -2967,7 +2958,7 @@ fn parse_anon_fn(
Default::default() Default::default()
}; };
let params: StaticVec<_> = if cfg!(not(feature = "no_closure")) { let mut params: StaticVec<_> = if cfg!(not(feature = "no_closure")) {
externals externals
.iter() .iter()
.cloned() .cloned()
@ -2976,6 +2967,7 @@ fn parse_anon_fn(
} else { } else {
params.into_iter().map(|(v, _)| v).collect() params.into_iter().map(|(v, _)| v).collect()
}; };
params.shrink_to_fit();
// Create unique function name by hashing the script body plus the parameters. // Create unique function name by hashing the script body plus the parameters.
let hasher = &mut get_hasher(); let hasher = &mut get_hasher();
@ -3135,22 +3127,22 @@ impl Engine {
pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option<Expr> { pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option<Expr> {
match value.0 { match value.0 {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(value, _) => Some(Expr::FloatConstant(value, pos)), Union::Float(value, _, _) => Some(Expr::FloatConstant(value, pos)),
#[cfg(feature = "decimal")] #[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::Unit(_, _, _) => Some(Expr::Unit(pos)),
Union::Int(value, _) => Some(Expr::IntegerConstant(value, pos)), Union::Int(value, _, _) => Some(Expr::IntegerConstant(value, pos)),
Union::Char(value, _) => Some(Expr::CharConstant(value, pos)), Union::Char(value, _, _) => Some(Expr::CharConstant(value, pos)),
Union::Str(value, _) => Some(Expr::StringConstant(value, pos)), Union::Str(value, _, _) => Some(Expr::StringConstant(value, pos)),
Union::Bool(value, _) => Some(Expr::BoolConstant(value, pos)), Union::Bool(value, _, _) => Some(Expr::BoolConstant(value, pos)),
#[cfg(not(feature = "no_index"))] #[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"))] #[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, _ => None,
} }

View File

@ -115,7 +115,7 @@ pub fn from_dynamic<'de, T: Deserialize<'de>>(
impl Error for Box<EvalAltResult> { impl Error for Box<EvalAltResult> {
fn custom<T: fmt::Display>(err: T) -> Self { 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_err(Position::NONE)
.into() .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>> { fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Box<EvalAltResult>> {
match &self.value.0 { match &self.value.0 {
Union::Unit(_, _) => self.deserialize_unit(visitor), Union::Unit(_, _, _) => self.deserialize_unit(visitor),
Union::Bool(_, _) => self.deserialize_bool(visitor), Union::Bool(_, _, _) => self.deserialize_bool(visitor),
Union::Str(_, _) => self.deserialize_str(visitor), Union::Str(_, _, _) => self.deserialize_str(visitor),
Union::Char(_, _) => self.deserialize_char(visitor), Union::Char(_, _, _) => self.deserialize_char(visitor),
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
Union::Int(_, _) => self.deserialize_i64(visitor), Union::Int(_, _, _) => self.deserialize_i64(visitor),
#[cfg(feature = "only_i32")] #[cfg(feature = "only_i32")]
Union::Int(_, _) => self.deserialize_i32(visitor), Union::Int(_, _, _) => self.deserialize_i32(visitor),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "f32_float"))] #[cfg(not(feature = "f32_float"))]
Union::Float(_, _) => self.deserialize_f64(visitor), Union::Float(_, _, _) => self.deserialize_f64(visitor),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[cfg(feature = "f32_float")] #[cfg(feature = "f32_float")]
Union::Float(_, _) => self.deserialize_f32(visitor), Union::Float(_, _, _) => self.deserialize_f32(visitor),
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
#[cfg(not(feature = "f32_float"))] #[cfg(not(feature = "f32_float"))]
Union::Decimal(_, _) => self.deserialize_f64(visitor), Union::Decimal(_, _, _) => self.deserialize_f64(visitor),
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
#[cfg(feature = "f32_float")] #[cfg(feature = "f32_float")]
Union::Decimal(_, _) => self.deserialize_f32(visitor), Union::Decimal(_, _, _) => self.deserialize_f32(visitor),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(_, _) => self.deserialize_seq(visitor), Union::Array(_, _, _) => self.deserialize_seq(visitor),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(_, _) => self.deserialize_map(visitor), Union::Map(_, _, _) => self.deserialize_map(visitor),
Union::FnPtr(_, _) => self.type_error(), Union::FnPtr(_, _, _) => self.type_error(),
#[cfg(not(feature = "no_std"))] #[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::<i8>() => self.deserialize_i8(visitor),
Union::Variant(value, _) if value.is::<i16>() => self.deserialize_i16(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::<i32>() => self.deserialize_i32(visitor),
Union::Variant(value, _) if value.is::<i64>() => self.deserialize_i64(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::<i128>() => self.deserialize_i128(visitor),
Union::Variant(value, _) if value.is::<u8>() => self.deserialize_u8(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::<u16>() => self.deserialize_u16(visitor),
Union::Variant(value, _) if value.is::<u32>() => self.deserialize_u32(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::<u64>() => self.deserialize_u64(visitor),
Union::Variant(value, _) if value.is::<u128>() => self.deserialize_u128(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"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(_, _) => self.type_error(), Union::Shared(_, _, _) => self.type_error(),
} }
} }

View File

@ -12,26 +12,26 @@ use serde::ser::SerializeMap;
impl Serialize for Dynamic { impl Serialize for Dynamic {
fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> { fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
match &self.0 { match &self.0 {
Union::Unit(_, _) => ser.serialize_unit(), Union::Unit(_, _, _) => ser.serialize_unit(),
Union::Bool(x, _) => ser.serialize_bool(*x), Union::Bool(x, _, _) => ser.serialize_bool(*x),
Union::Str(s, _) => ser.serialize_str(s.as_str()), Union::Str(s, _, _) => ser.serialize_str(s.as_str()),
Union::Char(c, _) => ser.serialize_str(&c.to_string()), Union::Char(c, _, _) => ser.serialize_str(&c.to_string()),
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
Union::Int(x, _) => ser.serialize_i64(*x), Union::Int(x, _, _) => ser.serialize_i64(*x),
#[cfg(feature = "only_i32")] #[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 = "no_float"))]
#[cfg(not(feature = "f32_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(not(feature = "no_float"))]
#[cfg(feature = "f32_float")] #[cfg(feature = "f32_float")]
Union::Float(x, _) => ser.serialize_f32(**x), Union::Float(x, _, _) => ser.serialize_f32(**x),
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
#[cfg(not(feature = "f32_float"))] #[cfg(not(feature = "f32_float"))]
Union::Decimal(x, _) => { Union::Decimal(x, _, _) => {
use rust_decimal::prelude::ToPrimitive; use rust_decimal::prelude::ToPrimitive;
if let Some(v) = x.to_f64() { if let Some(v) = x.to_f64() {
@ -42,7 +42,7 @@ impl Serialize for Dynamic {
} }
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
#[cfg(feature = "f32_float")] #[cfg(feature = "f32_float")]
Union::Decimal(x, _) => { Union::Decimal(x, _, _) => {
use rust_decimal::prelude::ToPrimitive; use rust_decimal::prelude::ToPrimitive;
if let Some(v) = x.to_f32() { if let Some(v) = x.to_f32() {
@ -53,27 +53,27 @@ impl Serialize for Dynamic {
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(a, _) => (**a).serialize(ser), Union::Array(a, _, _) => (**a).serialize(ser),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(m, _) => { Union::Map(m, _, _) => {
let mut map = ser.serialize_map(Some(m.len()))?; let mut map = ser.serialize_map(Some(m.len()))?;
for (k, v) in m.iter() { for (k, v) in m.iter() {
map.serialize_entry(k.as_str(), v)?; map.serialize_entry(k.as_str(), v)?;
} }
map.end() 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"))] #[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 = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
Union::Shared(cell, _) => cell.borrow().serialize(ser), Union::Shared(cell, _, _) => cell.borrow().serialize(ser),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Union::Shared(cell, _) => cell.read().unwrap().serialize(ser), Union::Shared(cell, _, _) => cell.read().unwrap().serialize(ser),
} }
} }
} }

View File

@ -87,28 +87,33 @@ pub struct CustomSyntax {
pub parse: Box<FnCustomSyntaxParse>, pub parse: Box<FnCustomSyntaxParse>,
/// Custom syntax implementation function. /// Custom syntax implementation function.
pub func: Shared<FnCustomSyntaxEval>, pub func: Shared<FnCustomSyntaxEval>,
/// Delta number of variables in the scope. /// Any variables added/removed in the scope?
pub scope_delta: isize, pub scope_changed: bool,
} }
impl Engine { impl Engine {
/// Register a custom syntax with the [`Engine`]. /// Register a custom syntax with the [`Engine`].
/// ///
/// * `keywords` holds a slice of strings that define the custom syntax. /// * `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. /// * `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 /// If `scope_changed` is `true`, then the current [`Scope`][crate::Scope] is assumed to be
/// current [`Scope`][crate::Scope]. /// modified by this custom syntax.
/// ///
/// If `new_vars` is negative, then it is expected that only the top `new_vars` variables in the /// Adding new variables and/or removing variables are allowed. Simply modifying the values of
/// current [`Scope`][crate::Scope] will be _popped_. Do not randomly remove variables. /// 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>>( pub fn register_custom_syntax<S: AsRef<str> + Into<Identifier>>(
&mut self, &mut self,
keywords: &[S], keywords: &[S],
new_vars: isize, scope_changed: bool,
func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static, func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static,
) -> Result<&mut Self, ParseError> { ) -> Result<&mut Self, ParseError> {
let keywords = keywords.as_ref(); let keywords = keywords.as_ref();
@ -203,7 +208,7 @@ impl Engine {
Ok(Some(segments[stream.len()].clone())) Ok(Some(segments[stream.len()].clone()))
} }
}, },
new_vars, scope_changed,
func, func,
); );
@ -215,7 +220,7 @@ impl Engine {
/// ///
/// This function is very low level. /// 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. /// * `parse` is the parsing function.
/// * `func` is the implementation function. /// * `func` is the implementation function.
/// ///
@ -227,16 +232,17 @@ impl Engine {
parse: impl Fn(&[ImmutableString], &str) -> Result<Option<ImmutableString>, ParseError> parse: impl Fn(&[ImmutableString], &str) -> Result<Option<ImmutableString>, ParseError>
+ SendSync + SendSync
+ 'static, + 'static,
new_vars: isize, scope_changed: bool,
func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static, func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static,
) -> &mut Self { ) -> &mut Self {
let syntax = CustomSyntax { self.custom_syntax.insert(
parse: Box::new(parse), key.into(),
func: (Box::new(func) as Box<FnCustomSyntaxEval>).into(), Box::new(CustomSyntax {
scope_delta: new_vars, parse: Box::new(parse),
}; func: (Box::new(func) as Box<FnCustomSyntaxEval>).into(),
scope_changed,
self.custom_syntax.insert(key.into(), syntax); }),
);
self self
} }
} }

View File

@ -973,10 +973,6 @@ pub struct TokenizeState {
pub comment_level: usize, pub comment_level: usize,
/// Include comments? /// Include comments?
pub include_comments: bool, 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? /// Is the current tokenizer position within the text stream of an interpolated string?
pub is_within_text_terminated_by: Option<char>, pub is_within_text_terminated_by: Option<char>,
} }
@ -1357,12 +1353,11 @@ fn get_next_token_inner(
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
let include_comments = let include_comments = if is_doc_comment(comment.as_ref().unwrap()) {
if !state.disable_doc_comments && is_doc_comment(comment.as_ref().unwrap()) { true
true } else {
} else { include_comments
include_comments };
};
if include_comments { if include_comments {
return Some((Token::Comment(comment.unwrap()), start_pos)); return Some((Token::Comment(comment.unwrap()), start_pos));
@ -1705,7 +1700,7 @@ fn get_next_token_inner(
let mut comment = match stream.peek_next() { let mut comment = match stream.peek_next() {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
Some('/') if !state.disable_doc_comments => { Some('/') => {
eat_next(stream, pos); eat_next(stream, pos);
// Long streams of `///...` are not doc-comments // Long streams of `///...` are not doc-comments
@ -1740,7 +1735,7 @@ fn get_next_token_inner(
let mut comment = match stream.peek_next() { let mut comment = match stream.peek_next() {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
Some('*') if !state.disable_doc_comments => { Some('*') => {
eat_next(stream, pos); eat_next(stream, pos);
// Long streams of `/****...` are not doc-comments // Long streams of `/****...` are not doc-comments
@ -2239,9 +2234,6 @@ impl Engine {
non_unary: false, non_unary: false,
comment_level: 0, comment_level: 0,
include_comments: false, include_comments: false,
#[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")]
disable_doc_comments: self.disable_doc_comments,
is_within_text_terminated_by: None, is_within_text_terminated_by: None,
}, },
pos: Position::new(1, 0), pos: Position::new(1, 0),

View File

@ -198,6 +198,7 @@ impl From<String> for ImmutableString {
Self(Into::<SmartString>::into(value).into()) Self(Into::<SmartString>::into(value).into())
} }
} }
#[cfg(not(feature = "no_smartstring"))]
impl From<SmartString> for ImmutableString { impl From<SmartString> for ImmutableString {
#[inline(always)] #[inline(always)]
fn from(value: SmartString) -> Self { 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 { impl fmt::Display for ImmutableString {
#[inline(always)] #[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -618,17 +627,17 @@ impl ImmutableString {
/// yet interned. /// yet interned.
#[derive(Debug, Clone, Default, Hash)] #[derive(Debug, Clone, Default, Hash)]
pub struct IdentifierBuilder( pub struct IdentifierBuilder(
#[cfg(feature = "no_smartstring_for_identifier")] std::collections::BTreeSet<Identifier>, #[cfg(feature = "no_smartstring")] std::collections::BTreeSet<Identifier>,
); );
impl IdentifierBuilder { impl IdentifierBuilder {
/// Get an identifier from a text string. /// Get an identifier from a text string.
#[inline(always)] #[inline(always)]
pub fn get(&mut self, text: impl AsRef<str> + Into<Identifier>) -> Identifier { 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(); 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(|| { return self.0.get(text.as_ref()).cloned().unwrap_or_else(|| {
let s: Identifier = text.into(); let s: Identifier = text.into();
self.0.insert(s.clone()); self.0.insert(s.clone());

View File

@ -30,7 +30,7 @@ fn test_comments() -> Result<(), Box<EvalAltResult>> {
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
#[test] #[test]
fn test_comments_doc() -> Result<(), Box<EvalAltResult>> { fn test_comments_doc() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let engine = Engine::new();
let ast = engine.compile( let ast = engine.compile(
" "
@ -89,17 +89,5 @@ fn test_comments_doc() -> Result<(), Box<EvalAltResult>> {
) )
.is_err()); .is_err());
engine.enable_doc_comments(false);
engine.compile(
"
/// Hello world!
let x = 42;
/** Hello world! */
let x = 42;
",
)?;
Ok(()) Ok(())
} }

View File

@ -16,7 +16,7 @@ fn test_constant() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
assert!(matches!( assert!(matches!(
*engine.consume("const x = [1, 2, 3, 4, 5]; x[2] = 42;").expect_err("expects error"), *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(()) Ok(())
@ -45,17 +45,66 @@ fn test_constant_mut() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
fn set_value(obj: &mut TestStruct, value: INT) {
obj.0 = value;
}
engine engine
.register_type_with_name::<TestStruct>("TestStruct") .register_type_with_name::<TestStruct>("TestStruct")
.register_fn("new_ts", || TestStruct(123))
.register_get("value", |obj: &mut TestStruct| obj.0) .register_get("value", |obj: &mut TestStruct| obj.0)
.register_fn("update_value", |obj: &mut TestStruct, value: INT| { .register_set("value", set_value)
obj.0 = 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(); let mut scope = Scope::new();
scope.push_constant("MY_NUMBER", TestStruct(123)); 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!( assert_eq!(
engine.eval_with_scope::<INT>( engine.eval_with_scope::<INT>(
&mut scope, &mut scope,
@ -67,5 +116,12 @@ fn test_constant_mut() -> Result<(), Box<EvalAltResult>> {
42 42
); );
assert!(matches!(
*engine
.consume_with_scope(&mut scope, "MY_NUMBER.value = 42;")
.expect_err("should error"),
EvalAltResult::ErrorAssignmentToConstant(_, _)
));
Ok(()) Ok(())
} }

View File

@ -31,15 +31,18 @@ mod test {
} }
#[rhai_fn(name = "test", name = "hi")] #[rhai_fn(name = "test", name = "hi")]
#[inline(always)]
pub fn len(array: &mut Array, mul: INT) -> INT { pub fn len(array: &mut Array, mul: INT) -> INT {
(array.len() as INT) * mul (array.len() as INT) * mul
} }
#[rhai_fn(name = "+")] #[rhai_fn(name = "+")]
#[inline(always)]
pub fn funky_add(x: INT, y: INT) -> INT { pub fn funky_add(x: INT, y: INT) -> INT {
x / 2 + y * 2 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)); reg_functions!(engine += greet::single(INT, bool, char));
#[cfg(not(feature = "no_object"))] #[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#"hash("hello")"#)?, 42);
assert_eq!(engine.eval::<INT>(r#"hash2("hello")"#)?, 42); assert_eq!(engine.eval::<INT>(r#"hash2("hello")"#)?, 42);

View File

@ -19,15 +19,15 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
engine.register_custom_syntax( engine.register_custom_syntax(
&[ &[
"exec", "|", "$ident$", "|", "->", "$block$", "while", "$expr$", "exec", "[", "$ident$", "]", "->", "$block$", "while", "$expr$",
], ],
1, true,
|context, inputs| { |context, inputs| {
let var_name = inputs[0].get_variable_name().unwrap().to_string(); let var_name = inputs[0].get_variable_name().unwrap().to_string();
let stmt = inputs.get(1).unwrap(); let stmt = inputs.get(1).unwrap();
let condition = inputs.get(2).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; let mut count: INT = 0;
@ -35,6 +35,10 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
context.eval_expression_tree(stmt)?; context.eval_expression_tree(stmt)?;
count += 1; count += 1;
context
.scope_mut()
.push(format!("{}{}", var_name, count), count);
let stop = !context let stop = !context
.eval_expression_tree(condition)? .eval_expression_tree(condition)?
.as_bool() .as_bool()
@ -59,7 +63,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
engine.eval::<INT>( engine.eval::<INT>(
" "
let x = 0; let x = 0;
let foo = (exec |x| -> { x += 2 } while x < 42) * 10; let foo = (exec [x] -> { x += 2 } while x < 42) * 10;
foo foo
" "
)?, )?,
@ -69,7 +73,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
engine.eval::<INT>( engine.eval::<INT>(
" "
let x = 0; let x = 0;
exec |x| -> { x += 1 } while x < 42; exec [x] -> { x += 1 } while x < 42;
x x
" "
)?, )?,
@ -78,17 +82,27 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
" "
exec |x| -> { x += 1 } while x < 42; exec [x] -> { x += 1 } while x < 42;
x x
" "
)?, )?,
42 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 // The first symbol must be an identifier
assert_eq!( assert_eq!(
*engine *engine
.register_custom_syntax(&["!"], 0, |_, _| Ok(Dynamic::UNIT)) .register_custom_syntax(&["!"], false, |_, _| Ok(Dynamic::UNIT))
.expect_err("should error") .expect_err("should error")
.0, .0,
ParseErrorType::BadInput(LexError::ImproperSymbol( ParseErrorType::BadInput(LexError::ImproperSymbol(
@ -114,14 +128,14 @@ fn test_custom_syntax_raw() -> Result<(), Box<EvalAltResult>> {
s => Err(ParseError( s => Err(ParseError(
Box::new(ParseErrorType::BadInput(LexError::ImproperSymbol( Box::new(ParseErrorType::BadInput(LexError::ImproperSymbol(
s.to_string(), s.to_string(),
"".to_string(), Default::default(),
))), ))),
Position::NONE, Position::NONE,
)), )),
}, },
_ => unreachable!(), _ => unreachable!(),
}, },
1, true,
|context, inputs| { |context, inputs| {
context.scope_mut().push("foo", 999 as INT); context.scope_mut().push("foo", 999 as INT);