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
==================
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
==============

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -39,4 +39,4 @@ lto = "fat"
[patch.crates-io]
# Patch smartstring wth a PR fix because it doesn't properly handle no-std builds.
smartstring = { git = "https://github.com/okready/smartstring", branch = "fix-no_std-builds" }
smartstring = { git = "https://github.com/rhaiscript/smartstring" }

View File

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

View File

@ -106,7 +106,7 @@ fn main() {
.compile(&contents)
.map_err(|err| err.into())
.and_then(|mut ast| {
ast.set_source(filename.to_string_lossy());
ast.set_source(filename.to_string_lossy().to_string());
Module::eval_ast_as_new(Default::default(), &ast, &engine)
}) {
Err(err) => {

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -31,16 +31,6 @@ impl Engine {
pub fn optimization_level(&self) -> crate::OptimizationLevel {
self.optimization_level
}
/// _(METADATA)_ Enable/disable doc-comments for functions.
/// Exported under the `metadata` feature only.
/// Not available under `no_function`.
#[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")]
#[inline(always)]
pub fn enable_doc_comments(&mut self, enable: bool) -> &mut Self {
self.disable_doc_comments = !enable;
self
}
/// Set the maximum levels of function calls allowed for a script in order to avoid
/// infinite recursion and stack overflows.
///

View File

@ -184,15 +184,19 @@ pub fn get_builtin_binary_op_fn(
"*" => impl_op!(from Decimal => multiply($xx, $yy)),
"/" => impl_op!(from Decimal => divide($xx, $yy)),
"%" => impl_op!(from Decimal => modulo($xx, $yy)),
"**" => impl_op!(from Decimal => power($xx, $yy)),
_ => ()
}
} else {
use rust_decimal::MathematicalOps;
match op {
"+" => impl_op!(from Decimal => $xx + $yy),
"-" => impl_op!(from Decimal => $xx - $yy),
"*" => impl_op!(from Decimal => $xx * $yy),
"/" => impl_op!(from Decimal => $xx / $yy),
"%" => impl_op!(from Decimal => $xx % $yy),
"**" => impl_op!(from Decimal => $xx.powd($yy)),
_ => ()
}
}
@ -522,15 +526,19 @@ pub fn get_builtin_op_assignment_fn(
"*=" => impl_op!(from $x => multiply($xx, $yy)),
"/=" => impl_op!(from $x => divide($xx, $yy)),
"%=" => impl_op!(from $x => modulo($xx, $yy)),
"**=" => impl_op!(from $x => power($xx, $yy)),
_ => return None,
}
} else {
use rust_decimal::MathematicalOps;
match op {
"+=" => impl_op!(from $x += $yy),
"-=" => impl_op!(from $x -= $yy),
"*=" => impl_op!(from $x *= $yy),
"/=" => impl_op!(from $x /= $yy),
"%=" => impl_op!(from $x %= $yy),
"**=" => impl_op!(from $x => $xx.powd($yy)),
_ => return None,
}
}

View File

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

View File

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

View File

@ -5,6 +5,7 @@
use crate::dynamic::{DynamicWriteLock, Variant};
use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync};
use crate::r#unsafe::unsafe_try_cast;
use crate::token::Position;
use crate::{Dynamic, EvalAltResult, NativeCallContext};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -61,20 +62,33 @@ pub trait RegisterNativeFunction<Args, Result> {
fn into_callable_function(self) -> CallableFunction;
/// Get the type ID's of this function's parameters.
fn param_types() -> Box<[TypeId]>;
/// Get the type names of this function's parameters.
/// _(METADATA)_ Get the type names of this function's parameters.
/// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")]
fn param_names() -> Box<[&'static str]>;
/// Get the type ID of this function's return value.
/// _(METADATA)_ Get the type ID of this function's return value.
/// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")]
fn return_type() -> TypeId;
/// Get the type name of this function's return value.
/// _(METADATA)_ Get the type name of this function's return value.
/// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")]
fn return_type_name() -> &'static str;
}
#[inline(always)]
fn is_setter(_fn_name: &str) -> bool {
#[cfg(not(feature = "no_object"))]
if _fn_name.starts_with(crate::engine::FN_SET) {
return true;
}
#[cfg(not(feature = "no_index"))]
if _fn_name.starts_with(crate::engine::FN_IDX_SET) {
return true;
}
false
}
macro_rules! def_register {
() => {
def_register!(imp from_pure :);
@ -97,7 +111,11 @@ macro_rules! def_register {
#[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<RET>() }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RET>() }
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
CallableFunction::$abi(Box::new(move |_: NativeCallContext, args: &mut FnCallArgs| {
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
return EvalAltResult::ErrorAssignmentToConstant(Default::default(), Position::NONE).into();
}
// The arguments are assumed to be of the correct number and types!
let mut _drain = args.iter_mut();
$($let $par = ($clone)(_drain.next().unwrap()); )*
@ -122,6 +140,10 @@ macro_rules! def_register {
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<RET>() }
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
return EvalAltResult::ErrorAssignmentToConstant(Default::default(), Position::NONE).into();
}
// The arguments are assumed to be of the correct number and types!
let mut _drain = args.iter_mut();
$($let $par = ($clone)(_drain.next().unwrap()); )*
@ -145,7 +167,11 @@ macro_rules! def_register {
#[cfg(feature = "metadata")] #[inline(always)] fn return_type() -> TypeId { TypeId::of::<Result<RET, Box<EvalAltResult>>>() }
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<Result<RET, Box<EvalAltResult>>>() }
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
CallableFunction::$abi(Box::new(move |_: NativeCallContext, args: &mut FnCallArgs| {
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
return EvalAltResult::ErrorAssignmentToConstant(Default::default(), Position::NONE).into();
}
// The arguments are assumed to be of the correct number and types!
let mut _drain = args.iter_mut();
$($let $par = ($clone)(_drain.next().unwrap()); )*
@ -167,6 +193,10 @@ macro_rules! def_register {
#[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { std::any::type_name::<Result<RET, Box<EvalAltResult>>>() }
#[inline(always)] fn into_callable_function(self) -> CallableFunction {
CallableFunction::$abi(Box::new(move |ctx: NativeCallContext, args: &mut FnCallArgs| {
if args.len() == 2 && args[0].is_read_only() && is_setter(ctx.fn_name()) {
return EvalAltResult::ErrorAssignmentToConstant(Default::default(), Position::NONE).into();
}
// The arguments are assumed to be of the correct number and types!
let mut _drain = args.iter_mut();
$($let $par = ($clone)(_drain.next().unwrap()); )*

View File

@ -140,11 +140,11 @@ pub use utils::ImmutableString;
/// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most
/// identifiers are ASCII and short, fewer than 23 characters, so they can be stored inline.
#[cfg(not(feature = "no_smartstring_for_identifier"))]
#[cfg(not(feature = "no_smartstring"))]
pub type Identifier = SmartString;
/// An identifier in Rhai.
#[cfg(feature = "no_smartstring_for_identifier")]
#[cfg(feature = "no_smartstring")]
pub type Identifier = ImmutableString;
/// A trait to enable registering Rust functions.
@ -207,15 +207,17 @@ pub use optimize::OptimizationLevel;
#[cfg(feature = "internals")]
#[deprecated = "this type is volatile and may change"]
pub use dynamic::Variant;
pub use dynamic::{DynamicReadLock, DynamicWriteLock, Variant};
// Expose internal data structures.
#[cfg(feature = "internals")]
#[deprecated = "this function is volatile and may change"]
pub use token::{get_next_token, parse_string_literal};
// Expose internal data structures.
#[cfg(feature = "internals")]
#[deprecated = "this type is volatile and may change"]
pub use token::{
get_next_token, parse_string_literal, InputStream, Token, TokenizeState, TokenizerControl,
TokenizerControlBlock,
};
pub use token::{InputStream, Token, TokenizeState, TokenizerControl, TokenizerControlBlock};
#[cfg(feature = "internals")]
#[deprecated = "this type is volatile and may change"]
@ -306,9 +308,14 @@ type StaticVec<T> = smallvec::SmallVec<[T; 4]>;
pub type StaticVec<T> = smallvec::SmallVec<[T; 4]>;
#[cfg(not(feature = "internals"))]
#[cfg(not(feature = "no_smartstring"))]
pub(crate) type SmartString = smartstring::SmartString<smartstring::Compact>;
#[cfg(feature = "no_smartstring")]
pub(crate) type SmartString = String;
#[cfg(feature = "internals")]
#[cfg(not(feature = "no_smartstring"))]
pub type SmartString = smartstring::SmartString<smartstring::Compact>;
// Compiler guards against mutually-exclusive feature flags

View File

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

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

View File

@ -5,9 +5,6 @@ use crate::{def_package, EvalAltResult, Position, INT};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
#[cfg(not(feature = "no_float"))]
use crate::FLOAT;
#[cfg(feature = "no_std")]
#[cfg(not(feature = "no_float"))]
use num_traits::Float;
@ -77,7 +74,7 @@ macro_rules! gen_arithmetic_functions {
} else if y < 0 {
Err(make_err(format!("Integer raised to a negative index: {} ~ {}", x, y)))
} else {
x.checked_pow(y as u32).ok_or_else(|| make_err(format!("Power overflow: {} ~ {}", x, y)))
x.checked_pow(y as u32).ok_or_else(|| make_err(format!("Exponential overflow: {} ~ {}", x, y)))
}
} else {
Ok(x.pow(y as u32))
@ -423,23 +420,13 @@ mod f64_functions {
1
}
}
#[rhai_fn(name = "**", return_raw)]
pub fn pow_f_i(x: FLOAT, y: INT) -> Result<FLOAT, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) {
Err(make_err(format!(
"Number raised to too large an index: {} ~ {}",
x, y
)))
} else {
Ok(x.powi(y as i32))
}
}
}
#[cfg(feature = "decimal")]
#[export_module]
pub mod decimal_functions {
use rust_decimal::{prelude::Zero, Decimal};
use num_traits::Pow;
use rust_decimal::{prelude::Zero, Decimal, MathematicalOps};
#[rhai_fn(skip, return_raw)]
pub fn add(x: Decimal, y: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
@ -495,6 +482,15 @@ pub mod decimal_functions {
Ok(x % y)
}
}
#[rhai_fn(skip, return_raw)]
pub fn power(x: Decimal, y: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) {
x.checked_powd(y)
.ok_or_else(|| make_err(format!("Exponential overflow: {} + {}", x, y)))
} else {
Ok(x.pow(y))
}
}
#[rhai_fn(name = "-")]
pub fn neg(x: Decimal) -> Decimal {
-x

View File

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

45
src/packages/lang_core.rs Normal file
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 {
x.log(base)
}
#[rhai_fn(name = "log")]
pub fn log10(x: FLOAT) -> FLOAT {
x.log10()
}
@ -305,10 +306,33 @@ mod float_functions {
mod decimal_functions {
use rust_decimal::{
prelude::{FromStr, RoundingStrategy},
Decimal,
Decimal, MathematicalOps,
};
use std::convert::TryFrom;
#[rhai_fn(return_raw)]
pub fn sqrt(x: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) {
x.sqrt()
.ok_or_else(|| make_err(format!("Error taking the square root of {}", x,)))
} else {
Ok(x.sqrt().unwrap())
}
}
#[rhai_fn(return_raw)]
pub fn exp(x: Decimal) -> Result<Decimal, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) {
if x > Decimal::from_parts(117578, 0, 0, false, 4) {
Err(make_err(format!("Exponential overflow: e ** {}", x,)))
} else {
Ok(x.exp())
}
} else {
Ok(x.exp())
}
}
pub fn ln(x: Decimal) -> Decimal {
x.ln()
}
#[rhai_fn(name = "floor", get = "floor")]
pub fn floor(x: Decimal) -> Decimal {
x.floor()
@ -424,6 +448,8 @@ mod decimal_functions {
#[cfg(not(feature = "no_float"))]
pub mod float {
use std::convert::TryFrom;
#[rhai_fn(name = "to_decimal", return_raw)]
pub fn f32_to_decimal(x: f32) -> Result<Decimal, Box<EvalAltResult>> {
Decimal::try_from(x).map_err(|_| {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -87,28 +87,33 @@ pub struct CustomSyntax {
pub parse: Box<FnCustomSyntaxParse>,
/// Custom syntax implementation function.
pub func: Shared<FnCustomSyntaxEval>,
/// Delta number of variables in the scope.
pub scope_delta: isize,
/// Any variables added/removed in the scope?
pub scope_changed: bool,
}
impl Engine {
/// Register a custom syntax with the [`Engine`].
///
/// * `keywords` holds a slice of strings that define the custom syntax.
/// * `new_vars` is the number of new variables declared by this custom syntax, or the number of variables removed (if negative).
/// * `scope_changed` specifies variables have been added/removed by this custom syntax.
/// * `func` is the implementation function.
///
/// # Notes
/// # Caveat - Do not change beyond block scope
///
/// If `new_vars` is positive, then a number of new variables are expected to be pushed into the
/// current [`Scope`][crate::Scope].
/// If `scope_changed` is `true`, then the current [`Scope`][crate::Scope] is assumed to be
/// modified by this custom syntax.
///
/// If `new_vars` is negative, then it is expected that only the top `new_vars` variables in the
/// current [`Scope`][crate::Scope] will be _popped_. Do not randomly remove variables.
/// Adding new variables and/or removing variables are allowed. Simply modifying the values of
/// variables does NOT count, so `false` should be passed in this case.
///
/// However, only variables declared within the current _block scope_ should be touched,
/// since they all go away at the end of the block.
///
/// Variables in parent blocks should be left untouched as they persist beyond the current block.
pub fn register_custom_syntax<S: AsRef<str> + Into<Identifier>>(
&mut self,
keywords: &[S],
new_vars: isize,
scope_changed: bool,
func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static,
) -> Result<&mut Self, ParseError> {
let keywords = keywords.as_ref();
@ -203,7 +208,7 @@ impl Engine {
Ok(Some(segments[stream.len()].clone()))
}
},
new_vars,
scope_changed,
func,
);
@ -215,7 +220,7 @@ impl Engine {
///
/// This function is very low level.
///
/// * `new_vars` is the number of new variables declared by this custom syntax, or the number of variables removed (if negative).
/// * `scope_changed` specifies variables have been added/removed by this custom syntax.
/// * `parse` is the parsing function.
/// * `func` is the implementation function.
///
@ -227,16 +232,17 @@ impl Engine {
parse: impl Fn(&[ImmutableString], &str) -> Result<Option<ImmutableString>, ParseError>
+ SendSync
+ 'static,
new_vars: isize,
scope_changed: bool,
func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static,
) -> &mut Self {
let syntax = CustomSyntax {
self.custom_syntax.insert(
key.into(),
Box::new(CustomSyntax {
parse: Box::new(parse),
func: (Box::new(func) as Box<FnCustomSyntaxEval>).into(),
scope_delta: new_vars,
};
self.custom_syntax.insert(key.into(), syntax);
scope_changed,
}),
);
self
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -31,15 +31,18 @@ mod test {
}
#[rhai_fn(name = "test", name = "hi")]
#[inline(always)]
pub fn len(array: &mut Array, mul: INT) -> INT {
(array.len() as INT) * mul
}
#[rhai_fn(name = "+")]
#[inline(always)]
pub fn funky_add(x: INT, y: INT) -> INT {
x / 2 + y * 2
}
#[rhai_fn(name = "no_effect", set = "no_effect", pure)]
pub fn no_effect(array: &mut Array, value: INT) {
// array is not modified
println!("Array = {:?}, Value = {}", array, value);
}
}
}
@ -82,7 +85,16 @@ fn test_plugins_package() -> Result<(), Box<EvalAltResult>> {
reg_functions!(engine += greet::single(INT, bool, char));
#[cfg(not(feature = "no_object"))]
{
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; a.foo")?, 1);
engine.consume("const A = [1, 2, 3]; A.no_effect(42);")?;
engine.consume("const A = [1, 2, 3]; A.no_effect = 42;")?;
assert!(
matches!(*engine.consume("const A = [1, 2, 3]; A.test(42);").expect_err("should error"),
EvalAltResult::ErrorAssignmentToConstant(x, _) if x == "array")
)
}
assert_eq!(engine.eval::<INT>(r#"hash("hello")"#)?, 42);
assert_eq!(engine.eval::<INT>(r#"hash2("hello")"#)?, 42);

View File

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