commit
ae5a4a8165
@ -12,6 +12,8 @@ Bug fixes
|
|||||||
---------
|
---------
|
||||||
|
|
||||||
* Fixed infinite loop in certain script optimizations.
|
* Fixed infinite loop in certain script optimizations.
|
||||||
|
* Building for `no-std` no longer requires patching `smartstring`.
|
||||||
|
* Parsing a lone `return` or `throw` without a semicolon at the end of a block no longer raises an error.
|
||||||
|
|
||||||
Breaking changes
|
Breaking changes
|
||||||
----------------
|
----------------
|
||||||
|
@ -205,7 +205,6 @@ impl Parse for Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl Module {
|
impl Module {
|
||||||
pub fn attrs(&self) -> &Vec<syn::Attribute> {
|
pub fn attrs(&self) -> &Vec<syn::Attribute> {
|
||||||
&self.mod_all.attrs
|
&self.mod_all.attrs
|
||||||
@ -300,22 +299,27 @@ impl Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn name(&self) -> &syn::Ident {
|
pub fn name(&self) -> &syn::Ident {
|
||||||
&self.mod_all.ident
|
&self.mod_all.ident
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn consts(&self) -> &[ExportedConst] {
|
pub fn consts(&self) -> &[ExportedConst] {
|
||||||
&self.consts
|
&self.consts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn fns(&self) -> &[ExportedFn] {
|
pub fn fns(&self) -> &[ExportedFn] {
|
||||||
&self.fns
|
&self.fns
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn sub_modules(&self) -> &[Module] {
|
pub fn sub_modules(&self) -> &[Module] {
|
||||||
&self.sub_modules
|
&self.sub_modules
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn content(&self) -> Option<&Vec<syn::Item>> {
|
pub fn content(&self) -> Option<&Vec<syn::Item>> {
|
||||||
match self.mod_all {
|
match self.mod_all {
|
||||||
syn::ItemMod {
|
syn::ItemMod {
|
||||||
|
@ -36,7 +36,3 @@ inherits = "release"
|
|||||||
[profile.macos]
|
[profile.macos]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
lto = "fat"
|
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/rhaiscript/smartstring" }
|
|
||||||
|
@ -12,7 +12,7 @@ To Compile
|
|||||||
The nightly compiler is required:
|
The nightly compiler is required:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo +nightly build --release --profile unix -Z unstable-features
|
cargo +nightly build --profile unix -Z unstable-options
|
||||||
```
|
```
|
||||||
|
|
||||||
Available profiles are: `unix`, `windows` and `macos`.
|
Available profiles are: `unix`, `windows` and `macos`.
|
||||||
|
11
src/ast.rs
11
src/ast.rs
@ -1394,20 +1394,11 @@ pub struct CustomExpr {
|
|||||||
/// List of keywords.
|
/// List of keywords.
|
||||||
pub keywords: StaticVec<Expr>,
|
pub keywords: StaticVec<Expr>,
|
||||||
/// Is the current [`Scope`][crate::Scope] modified?
|
/// Is the current [`Scope`][crate::Scope] modified?
|
||||||
pub scope_changed: bool,
|
pub scope_may_be_changed: bool,
|
||||||
/// List of tokens actually parsed.
|
/// List of tokens actually parsed.
|
||||||
pub tokens: StaticVec<Identifier>,
|
pub tokens: StaticVec<Identifier>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CustomExpr {
|
|
||||||
/// Convert this into a [`Expr::Custom`].
|
|
||||||
#[inline(always)]
|
|
||||||
#[must_use]
|
|
||||||
pub fn into_custom_syntax_expr(self, pos: Position) -> Expr {
|
|
||||||
Expr::Custom(self.into(), pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// _(INTERNALS)_ A binary expression.
|
/// _(INTERNALS)_ A binary expression.
|
||||||
/// Exported under the `internals` feature only.
|
/// Exported under the `internals` feature only.
|
||||||
///
|
///
|
||||||
|
@ -156,33 +156,34 @@ pub struct CustomSyntax {
|
|||||||
/// Custom syntax implementation function.
|
/// Custom syntax implementation function.
|
||||||
pub func: Shared<FnCustomSyntaxEval>,
|
pub func: Shared<FnCustomSyntaxEval>,
|
||||||
/// Any variables added/removed in the scope?
|
/// Any variables added/removed in the scope?
|
||||||
pub scope_changed: bool,
|
pub scope_may_be_changed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
/// Register a custom syntax with the [`Engine`].
|
/// Register a custom syntax with the [`Engine`].
|
||||||
///
|
///
|
||||||
/// * `keywords` holds a slice of strings that define the custom syntax.
|
/// * `keywords` holds a slice of strings that define the custom syntax.
|
||||||
/// * `scope_changed` specifies variables have been added/removed by this custom syntax.
|
/// * `scope_may_be_changed` specifies variables _may_ be added/removed by this custom syntax.
|
||||||
/// * `func` is the implementation function.
|
/// * `func` is the implementation function.
|
||||||
///
|
///
|
||||||
/// # Caveat - Do not change beyond block scope
|
/// ## Note on `scope_may_be_changed`
|
||||||
///
|
///
|
||||||
/// If `scope_changed` is `true`, then the current [`Scope`][crate::Scope] is assumed to be
|
/// If `scope_may_be_changed` is `true`, then _size_ of the current [`Scope`][crate::Scope]
|
||||||
/// modified by this custom syntax.
|
/// _may_ be modified by this custom syntax.
|
||||||
///
|
///
|
||||||
/// Adding new variables and/or removing variables are allowed. Simply modifying the values of
|
/// Adding new variables and/or removing variables count.
|
||||||
/// variables does NOT count, so `false` should be passed in this case.
|
|
||||||
///
|
///
|
||||||
/// However, only variables declared within the current _block scope_ should be touched,
|
/// Simply modifying the values of existing variables does NOT count, as the _size_ of the
|
||||||
/// since they all go away at the end of the block.
|
/// current [`Scope`][crate::Scope] is unchanged, so `false` should be passed.
|
||||||
///
|
///
|
||||||
/// Variables in parent blocks should be left untouched as they persist beyond the current block.
|
/// Replacing one variable with another (i.e. adding a new variable and removing one variable at
|
||||||
|
/// the same time so that the total _size_ of the [`Scope`][crate::Scope] is unchanged) also
|
||||||
|
/// does NOT count, so `false` should be passed.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn register_custom_syntax<S: AsRef<str> + Into<Identifier>>(
|
pub fn register_custom_syntax<S: AsRef<str> + Into<Identifier>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
keywords: &[S],
|
keywords: &[S],
|
||||||
scope_changed: bool,
|
scope_may_be_changed: bool,
|
||||||
func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static,
|
func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static,
|
||||||
) -> Result<&mut Self, ParseError> {
|
) -> Result<&mut Self, ParseError> {
|
||||||
let keywords = keywords.as_ref();
|
let keywords = keywords.as_ref();
|
||||||
@ -289,7 +290,7 @@ impl Engine {
|
|||||||
Ok(Some(segments[stream.len()].clone()))
|
Ok(Some(segments[stream.len()].clone()))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scope_changed,
|
scope_may_be_changed,
|
||||||
func,
|
func,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -301,7 +302,7 @@ impl Engine {
|
|||||||
///
|
///
|
||||||
/// This function is very low level.
|
/// This function is very low level.
|
||||||
///
|
///
|
||||||
/// * `scope_changed` specifies variables have been added/removed by this custom syntax.
|
/// * `scope_may_be_changed` specifies variables have been added/removed by this custom syntax.
|
||||||
/// * `parse` is the parsing function.
|
/// * `parse` is the parsing function.
|
||||||
/// * `func` is the implementation function.
|
/// * `func` is the implementation function.
|
||||||
///
|
///
|
||||||
@ -313,7 +314,7 @@ impl Engine {
|
|||||||
parse: impl Fn(&[ImmutableString], &str) -> Result<Option<ImmutableString>, ParseError>
|
parse: impl Fn(&[ImmutableString], &str) -> Result<Option<ImmutableString>, ParseError>
|
||||||
+ SendSync
|
+ SendSync
|
||||||
+ 'static,
|
+ 'static,
|
||||||
scope_changed: bool,
|
scope_may_be_changed: bool,
|
||||||
func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static,
|
func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.custom_syntax.insert(
|
self.custom_syntax.insert(
|
||||||
@ -321,7 +322,7 @@ impl Engine {
|
|||||||
CustomSyntax {
|
CustomSyntax {
|
||||||
parse: Box::new(parse),
|
parse: Box::new(parse),
|
||||||
func: (Box::new(func) as Box<FnCustomSyntaxEval>).into(),
|
func: (Box::new(func) as Box<FnCustomSyntaxEval>).into(),
|
||||||
scope_changed,
|
scope_may_be_changed,
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
@ -508,6 +508,8 @@ impl Hash for Dynamic {
|
|||||||
Union::Int(ref i, _, _) => i.hash(state),
|
Union::Int(ref i, _, _) => i.hash(state),
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Union::Float(ref f, _, _) => f.hash(state),
|
Union::Float(ref f, _, _) => f.hash(state),
|
||||||
|
#[cfg(feature = "decimal")]
|
||||||
|
Union::Decimal(ref d, _, _) => d.hash(state),
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Union::Array(ref a, _, _) => a.as_ref().hash(state),
|
Union::Array(ref a, _, _) => a.as_ref().hash(state),
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
@ -522,10 +524,11 @@ impl Hash for Dynamic {
|
|||||||
#[cfg(feature = "sync")]
|
#[cfg(feature = "sync")]
|
||||||
Union::Shared(ref cell, _, _) => (*cell.read().unwrap()).hash(state),
|
Union::Shared(ref cell, _, _) => (*cell.read().unwrap()).hash(state),
|
||||||
|
|
||||||
|
Union::Variant(ref _value, _, _) => {
|
||||||
#[cfg(not(feature = "only_i32"))]
|
#[cfg(not(feature = "only_i32"))]
|
||||||
#[cfg(not(feature = "only_i64"))]
|
#[cfg(not(feature = "only_i64"))]
|
||||||
Union::Variant(ref value, _, _) => {
|
{
|
||||||
let value_any = (***value).as_any();
|
let value_any = (***_value).as_any();
|
||||||
let type_id = value_any.type_id();
|
let type_id = value_any.type_id();
|
||||||
|
|
||||||
if type_id == TypeId::of::<u8>() {
|
if type_id == TypeId::of::<u8>() {
|
||||||
@ -564,7 +567,11 @@ impl Hash for Dynamic {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => unimplemented!("{} cannot be hashed", self.type_name()),
|
unimplemented!("a custom type cannot be hashed")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_std"))]
|
||||||
|
Union::TimeStamp(_, _, _) => unimplemented!("{} cannot be hashed", self.type_name()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
159
src/engine.rs
159
src/engine.rs
@ -24,6 +24,7 @@ use std::{
|
|||||||
collections::{BTreeMap, BTreeSet},
|
collections::{BTreeMap, BTreeSet},
|
||||||
fmt,
|
fmt,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
|
iter::{FromIterator, Rev, Zip},
|
||||||
num::{NonZeroU8, NonZeroUsize},
|
num::{NonZeroU8, NonZeroUsize},
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
};
|
};
|
||||||
@ -62,6 +63,12 @@ pub struct Imports {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Imports {
|
impl Imports {
|
||||||
|
/// Create a new stack of imported [modules][Module].
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
/// Get the length of this stack of imported [modules][Module].
|
/// Get the length of this stack of imported [modules][Module].
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -74,20 +81,20 @@ impl Imports {
|
|||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.keys.is_empty()
|
self.keys.is_empty()
|
||||||
}
|
}
|
||||||
/// Get the imported [modules][Module] at a particular index.
|
/// Get the imported [module][Module] at a particular index.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get(&self, index: usize) -> Option<Shared<Module>> {
|
pub fn get(&self, index: usize) -> Option<Shared<Module>> {
|
||||||
self.modules.get(index).cloned()
|
self.modules.get(index).cloned()
|
||||||
}
|
}
|
||||||
/// Get the imported [modules][Module] at a particular index.
|
/// Get the imported [module][Module] at a particular index.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn get_mut(&mut self, index: usize) -> Option<&mut Shared<Module>> {
|
pub(crate) fn get_mut(&mut self, index: usize) -> Option<&mut Shared<Module>> {
|
||||||
self.modules.get_mut(index)
|
self.modules.get_mut(index)
|
||||||
}
|
}
|
||||||
/// Get the index of an imported [modules][Module] by name.
|
/// Get the index of an imported [module][Module] by name.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn find(&self, name: &str) -> Option<usize> {
|
pub fn find(&self, name: &str) -> Option<usize> {
|
||||||
@ -97,7 +104,7 @@ impl Imports {
|
|||||||
.rev()
|
.rev()
|
||||||
.find_map(|(i, key)| if key == name { Some(i) } else { None })
|
.find_map(|(i, key)| if key == name { Some(i) } else { None })
|
||||||
}
|
}
|
||||||
/// Push an imported [modules][Module] onto the stack.
|
/// Push an imported [module][Module] onto the stack.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn push(&mut self, name: impl Into<Identifier>, module: impl Into<Shared<Module>>) {
|
pub fn push(&mut self, name: impl Into<Identifier>, module: impl Into<Shared<Module>>) {
|
||||||
self.keys.push(name.into());
|
self.keys.push(name.into());
|
||||||
@ -134,15 +141,6 @@ impl Imports {
|
|||||||
pub(crate) fn scan_raw(&self) -> impl Iterator<Item = (&Identifier, &Shared<Module>)> {
|
pub(crate) fn scan_raw(&self) -> impl Iterator<Item = (&Identifier, &Shared<Module>)> {
|
||||||
self.keys.iter().zip(self.modules.iter())
|
self.keys.iter().zip(self.modules.iter())
|
||||||
}
|
}
|
||||||
/// Get a consuming iterator to this stack of imported [modules][Module] in reverse order.
|
|
||||||
#[inline(always)]
|
|
||||||
#[must_use]
|
|
||||||
pub fn into_iter(self) -> impl Iterator<Item = (Identifier, Shared<Module>)> {
|
|
||||||
self.keys
|
|
||||||
.into_iter()
|
|
||||||
.rev()
|
|
||||||
.zip(self.modules.into_iter().rev())
|
|
||||||
}
|
|
||||||
/// Does the specified function hash key exist in this stack of imported [modules][Module]?
|
/// Does the specified function hash key exist in this stack of imported [modules][Module]?
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -150,7 +148,7 @@ impl Imports {
|
|||||||
pub fn contains_fn(&self, hash: u64) -> bool {
|
pub fn contains_fn(&self, hash: u64) -> bool {
|
||||||
self.modules.iter().any(|m| m.contains_qualified_fn(hash))
|
self.modules.iter().any(|m| m.contains_qualified_fn(hash))
|
||||||
}
|
}
|
||||||
/// Get specified function via its hash key.
|
/// Get the specified function via its hash key from this stack of imported [modules][Module].
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_fn(&self, hash: u64) -> Option<(&CallableFunction, Option<&Identifier>)> {
|
pub fn get_fn(&self, hash: u64) -> Option<(&CallableFunction, Option<&Identifier>)> {
|
||||||
@ -167,7 +165,8 @@ impl Imports {
|
|||||||
pub fn contains_iter(&self, id: TypeId) -> bool {
|
pub fn contains_iter(&self, id: TypeId) -> bool {
|
||||||
self.modules.iter().any(|m| m.contains_qualified_iter(id))
|
self.modules.iter().any(|m| m.contains_qualified_iter(id))
|
||||||
}
|
}
|
||||||
/// Get the specified [`TypeId`][std::any::TypeId] iterator.
|
/// Get the specified [`TypeId`][std::any::TypeId] iterator from this stack of imported
|
||||||
|
/// [modules][Module].
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
|
pub fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
|
||||||
@ -178,6 +177,37 @@ impl Imports {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for Imports {
|
||||||
|
type Item = (Identifier, Shared<Module>);
|
||||||
|
type IntoIter =
|
||||||
|
Zip<Rev<smallvec::IntoIter<[Identifier; 4]>>, Rev<smallvec::IntoIter<[Shared<Module>; 4]>>>;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.keys
|
||||||
|
.into_iter()
|
||||||
|
.rev()
|
||||||
|
.zip(self.modules.into_iter().rev())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: Into<Identifier>, M: Into<Shared<Module>>> FromIterator<(K, M)> for Imports {
|
||||||
|
fn from_iter<T: IntoIterator<Item = (K, M)>>(iter: T) -> Self {
|
||||||
|
let mut lib = Self::new();
|
||||||
|
lib.extend(iter);
|
||||||
|
lib
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: Into<Identifier>, M: Into<Shared<Module>>> Extend<(K, M)> for Imports {
|
||||||
|
fn extend<T: IntoIterator<Item = (K, M)>>(&mut self, iter: T) {
|
||||||
|
iter.into_iter().for_each(|(k, m)| {
|
||||||
|
self.keys.push(k.into());
|
||||||
|
self.modules.push(m.into());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Imports {
|
impl fmt::Debug for Imports {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.write_str("Imports")?;
|
f.write_str("Imports")?;
|
||||||
@ -348,7 +378,7 @@ impl<'a> Target<'a> {
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_ref(&self) -> bool {
|
pub const fn is_ref(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::RefMut(_) => true,
|
Self::RefMut(_) => true,
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
@ -363,7 +393,7 @@ impl<'a> Target<'a> {
|
|||||||
/// Is the `Target` a temp value?
|
/// Is the `Target` a temp value?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_temp_value(&self) -> bool {
|
pub const fn is_temp_value(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::RefMut(_) => false,
|
Self::RefMut(_) => false,
|
||||||
#[cfg(not(feature = "no_closure"))]
|
#[cfg(not(feature = "no_closure"))]
|
||||||
@ -578,7 +608,12 @@ impl<T: Into<Dynamic>> From<T> for Target<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An entry in a function resolution cache.
|
/// _(INTERNALS)_ An entry in a function resolution cache.
|
||||||
|
/// Exported under the `internals` feature only.
|
||||||
|
///
|
||||||
|
/// # Volatile Data Structure
|
||||||
|
///
|
||||||
|
/// This type is volatile and may change.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FnResolutionCacheEntry {
|
pub struct FnResolutionCacheEntry {
|
||||||
/// Function.
|
/// Function.
|
||||||
@ -587,7 +622,12 @@ pub struct FnResolutionCacheEntry {
|
|||||||
pub source: Option<Identifier>,
|
pub source: Option<Identifier>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A function resolution cache.
|
/// _(INTERNALS)_ A function resolution cache.
|
||||||
|
/// Exported under the `internals` feature only.
|
||||||
|
///
|
||||||
|
/// # Volatile Data Structure
|
||||||
|
///
|
||||||
|
/// This type is volatile and may change.
|
||||||
pub type FnResolutionCache = BTreeMap<u64, Option<Box<FnResolutionCacheEntry>>>;
|
pub type FnResolutionCache = BTreeMap<u64, Option<Box<FnResolutionCacheEntry>>>;
|
||||||
|
|
||||||
/// _(INTERNALS)_ A type that holds all the current states of the [`Engine`].
|
/// _(INTERNALS)_ A type that holds all the current states of the [`Engine`].
|
||||||
@ -597,15 +637,15 @@ pub type FnResolutionCache = BTreeMap<u64, Option<Box<FnResolutionCacheEntry>>>;
|
|||||||
///
|
///
|
||||||
/// This type is volatile and may change.
|
/// This type is volatile and may change.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct State {
|
pub struct EvalState {
|
||||||
/// Source of the current context.
|
/// Source of the current context.
|
||||||
pub source: Option<Identifier>,
|
pub source: Option<Identifier>,
|
||||||
/// Normally, access to variables are parsed with a relative offset into the scope to avoid a lookup.
|
/// Normally, access to variables are parsed with a relative offset into the [`Scope`] to avoid a lookup.
|
||||||
/// In some situation, e.g. after running an `eval` statement, subsequent offsets become mis-aligned.
|
/// In some situation, e.g. after running an `eval` statement, subsequent offsets may become mis-aligned.
|
||||||
/// When that happens, this flag is turned on to force a scope lookup by name.
|
/// When that happens, this flag is turned on to force a [`Scope`] search by name.
|
||||||
pub always_search: bool,
|
pub always_search_scope: bool,
|
||||||
/// Level of the current scope. The global (root) level is zero, a new block
|
/// Level of the current scope. The global (root) level is zero, a new block (or function call)
|
||||||
/// (or function call) is one level higher, and so on.
|
/// is one level higher, and so on.
|
||||||
pub scope_level: usize,
|
pub scope_level: usize,
|
||||||
/// Number of operations performed.
|
/// Number of operations performed.
|
||||||
pub operations: u64,
|
pub operations: u64,
|
||||||
@ -614,15 +654,30 @@ pub struct State {
|
|||||||
/// Embedded module resolver.
|
/// Embedded module resolver.
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
pub resolver: Option<Shared<crate::module::resolvers::StaticModuleResolver>>,
|
pub resolver: Option<Shared<crate::module::resolvers::StaticModuleResolver>>,
|
||||||
/// Function resolution cache and free list.
|
/// Stack of function resolution caches.
|
||||||
fn_resolution_caches: StaticVec<FnResolutionCache>,
|
fn_resolution_caches: Vec<FnResolutionCache>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl EvalState {
|
||||||
|
/// Create a new [`EvalState`].
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
source: None,
|
||||||
|
always_search_scope: false,
|
||||||
|
scope_level: 0,
|
||||||
|
operations: 0,
|
||||||
|
modules: 0,
|
||||||
|
#[cfg(not(feature = "no_module"))]
|
||||||
|
resolver: None,
|
||||||
|
fn_resolution_caches: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Is the state currently at global (root) level?
|
/// Is the state currently at global (root) level?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_global(&self) -> bool {
|
pub const fn is_global(&self) -> bool {
|
||||||
self.scope_level == 0
|
self.scope_level == 0
|
||||||
}
|
}
|
||||||
/// Get a mutable reference to the current function resolution cache.
|
/// Get a mutable reference to the current function resolution cache.
|
||||||
@ -729,13 +784,13 @@ pub struct EvalContext<'a, 'x, 'px, 'm, 's, 'b, 't, 'pt> {
|
|||||||
pub(crate) engine: &'a Engine,
|
pub(crate) engine: &'a Engine,
|
||||||
pub(crate) scope: &'x mut Scope<'px>,
|
pub(crate) scope: &'x mut Scope<'px>,
|
||||||
pub(crate) mods: &'m mut Imports,
|
pub(crate) mods: &'m mut Imports,
|
||||||
pub(crate) state: &'s mut State,
|
pub(crate) state: &'s mut EvalState,
|
||||||
pub(crate) lib: &'b [&'b Module],
|
pub(crate) lib: &'b [&'b Module],
|
||||||
pub(crate) this_ptr: &'t mut Option<&'pt mut Dynamic>,
|
pub(crate) this_ptr: &'t mut Option<&'pt mut Dynamic>,
|
||||||
pub(crate) level: usize,
|
pub(crate) level: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, '_> {
|
impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, 'pt> {
|
||||||
/// The current [`Engine`].
|
/// The current [`Engine`].
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -796,6 +851,12 @@ impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, '_> {
|
|||||||
pub fn this_ptr(&self) -> Option<&Dynamic> {
|
pub fn this_ptr(&self) -> Option<&Dynamic> {
|
||||||
self.this_ptr.as_ref().map(|v| &**v)
|
self.this_ptr.as_ref().map(|v| &**v)
|
||||||
}
|
}
|
||||||
|
/// Mutable reference to the current bound `this` pointer, if any.
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub fn this_ptr_mut(&mut self) -> Option<&mut &'pt mut Dynamic> {
|
||||||
|
self.this_ptr.as_mut()
|
||||||
|
}
|
||||||
/// The current nesting level of function calls.
|
/// The current nesting level of function calls.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@ -1002,13 +1063,13 @@ impl Engine {
|
|||||||
pub(crate) fn search_imports(
|
pub(crate) fn search_imports(
|
||||||
&self,
|
&self,
|
||||||
mods: &Imports,
|
mods: &Imports,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
namespace: &NamespaceRef,
|
namespace: &NamespaceRef,
|
||||||
) -> Option<Shared<Module>> {
|
) -> Option<Shared<Module>> {
|
||||||
let root = &namespace[0].name;
|
let root = &namespace[0].name;
|
||||||
|
|
||||||
// Qualified - check if the root module is directly indexed
|
// Qualified - check if the root module is directly indexed
|
||||||
let index = if state.always_search {
|
let index = if state.always_search_scope {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
namespace.index()
|
namespace.index()
|
||||||
@ -1037,7 +1098,7 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
scope: &'s mut Scope,
|
scope: &'s mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
this_ptr: &'s mut Option<&mut Dynamic>,
|
this_ptr: &'s mut Option<&mut Dynamic>,
|
||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
@ -1087,7 +1148,7 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
scope: &'s mut Scope,
|
scope: &'s mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
this_ptr: &'s mut Option<&mut Dynamic>,
|
this_ptr: &'s mut Option<&mut Dynamic>,
|
||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
@ -1103,7 +1164,7 @@ impl Engine {
|
|||||||
EvalAltResult::ErrorUnboundThis(*pos).into()
|
EvalAltResult::ErrorUnboundThis(*pos).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ if state.always_search => (0, expr.position()),
|
_ if state.always_search_scope => (0, expr.position()),
|
||||||
Expr::Variable(Some(i), pos, _) => (i.get() as usize, *pos),
|
Expr::Variable(Some(i), pos, _) => (i.get() as usize, *pos),
|
||||||
Expr::Variable(None, pos, v) => (v.0.map(NonZeroUsize::get).unwrap_or(0), *pos),
|
Expr::Variable(None, pos, v) => (v.0.map(NonZeroUsize::get).unwrap_or(0), *pos),
|
||||||
_ => unreachable!("Expr::Variable expected, but gets {:?}", expr),
|
_ => unreachable!("Expr::Variable expected, but gets {:?}", expr),
|
||||||
@ -1160,7 +1221,7 @@ impl Engine {
|
|||||||
fn eval_dot_index_chain_helper(
|
fn eval_dot_index_chain_helper(
|
||||||
&self,
|
&self,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
target: &mut Target,
|
target: &mut Target,
|
||||||
@ -1592,7 +1653,7 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
@ -1656,7 +1717,7 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
@ -1785,7 +1846,7 @@ impl Engine {
|
|||||||
fn get_indexed_mut<'t>(
|
fn get_indexed_mut<'t>(
|
||||||
&self,
|
&self,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
target: &'t mut Dynamic,
|
target: &'t mut Dynamic,
|
||||||
mut idx: Dynamic,
|
mut idx: Dynamic,
|
||||||
@ -1843,7 +1904,7 @@ impl Engine {
|
|||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Dynamic(Union::Map(map, _, _)) => {
|
Dynamic(Union::Map(map, _, _)) => {
|
||||||
// val_map[idx]
|
// val_map[idx]
|
||||||
let index = &*idx.read_lock::<ImmutableString>().ok_or_else(|| {
|
let index = idx.read_lock::<ImmutableString>().ok_or_else(|| {
|
||||||
self.make_type_mismatch_err::<ImmutableString>(idx.type_name(), idx_pos)
|
self.make_type_mismatch_err::<ImmutableString>(idx.type_name(), idx_pos)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@ -1957,7 +2018,7 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
@ -2158,7 +2219,7 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
statements: &[Stmt],
|
statements: &[Stmt],
|
||||||
@ -2170,7 +2231,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut _extra_fn_resolution_cache = false;
|
let mut _extra_fn_resolution_cache = false;
|
||||||
let prev_always_search = state.always_search;
|
let prev_always_search_scope = state.always_search_scope;
|
||||||
let prev_scope_len = scope.len();
|
let prev_scope_len = scope.len();
|
||||||
let prev_mods_len = mods.len();
|
let prev_mods_len = mods.len();
|
||||||
|
|
||||||
@ -2223,7 +2284,7 @@ impl Engine {
|
|||||||
|
|
||||||
// The impact of new local variables goes away at the end of a block
|
// The impact of new local variables goes away at the end of a block
|
||||||
// because any new variables introduced will go out of scope
|
// because any new variables introduced will go out of scope
|
||||||
state.always_search = prev_always_search;
|
state.always_search_scope = prev_always_search_scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
@ -2235,7 +2296,7 @@ impl Engine {
|
|||||||
pub(crate) fn eval_op_assignment(
|
pub(crate) fn eval_op_assignment(
|
||||||
&self,
|
&self,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
op_info: Option<OpAssignment>,
|
op_info: Option<OpAssignment>,
|
||||||
op_pos: Position,
|
op_pos: Position,
|
||||||
@ -2310,7 +2371,7 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
stmt: &Stmt,
|
stmt: &Stmt,
|
||||||
@ -3077,7 +3138,7 @@ impl Engine {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub(crate) fn inc_operations(
|
pub(crate) fn inc_operations(
|
||||||
&self,
|
&self,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
) -> Result<(), Box<EvalAltResult>> {
|
) -> Result<(), Box<EvalAltResult>> {
|
||||||
state.operations += 1;
|
state.operations += 1;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
//! Module that defines the extern API of [`Engine`].
|
//! Module that defines the extern API of [`Engine`].
|
||||||
|
|
||||||
use crate::dynamic::Variant;
|
use crate::dynamic::Variant;
|
||||||
use crate::engine::{EvalContext, Imports, State};
|
use crate::engine::{EvalContext, EvalState, Imports};
|
||||||
use crate::fn_call::FnCallArgs;
|
use crate::fn_call::FnCallArgs;
|
||||||
use crate::fn_native::SendSync;
|
use crate::fn_native::SendSync;
|
||||||
use crate::fn_register::RegisterNativeFunction;
|
use crate::fn_register::RegisterNativeFunction;
|
||||||
@ -1749,7 +1749,7 @@ impl Engine {
|
|||||||
ast: &'a AST,
|
ast: &'a AST,
|
||||||
level: usize,
|
level: usize,
|
||||||
) -> RhaiResult {
|
) -> RhaiResult {
|
||||||
let mut state: State = Default::default();
|
let mut state = EvalState::new();
|
||||||
state.source = ast.source_raw().cloned();
|
state.source = ast.source_raw().cloned();
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
{
|
{
|
||||||
@ -1831,7 +1831,7 @@ impl Engine {
|
|||||||
ast: &AST,
|
ast: &AST,
|
||||||
) -> Result<(), Box<EvalAltResult>> {
|
) -> Result<(), Box<EvalAltResult>> {
|
||||||
let mods = &mut Default::default();
|
let mods = &mut Default::default();
|
||||||
let mut state: State = Default::default();
|
let mut state = EvalState::new();
|
||||||
state.source = ast.source_raw().cloned();
|
state.source = ast.source_raw().cloned();
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
{
|
{
|
||||||
@ -2004,7 +2004,7 @@ impl Engine {
|
|||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
args: &mut FnCallArgs,
|
args: &mut FnCallArgs,
|
||||||
) -> RhaiResult {
|
) -> RhaiResult {
|
||||||
let state = &mut Default::default();
|
let state = &mut EvalState::new();
|
||||||
let mods = &mut Default::default();
|
let mods = &mut Default::default();
|
||||||
let lib = &[ast.lib()];
|
let lib = &[ast.lib()];
|
||||||
let statements = ast.statements();
|
let statements = ast.statements();
|
||||||
|
90
src/error.rs
90
src/error.rs
@ -98,8 +98,10 @@ impl Error for EvalAltResult {}
|
|||||||
impl fmt::Display for EvalAltResult {
|
impl fmt::Display for EvalAltResult {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::ErrorSystem(s, err) if s.is_empty() => write!(f, "{}", err)?,
|
Self::ErrorSystem(s, err) => match s.as_str() {
|
||||||
Self::ErrorSystem(s, err) => write!(f, "{}: {}", s, err)?,
|
"" => write!(f, "{}", err),
|
||||||
|
s => write!(f, "{}: {}", s, err),
|
||||||
|
}?,
|
||||||
|
|
||||||
Self::ErrorParsing(p, _) => write!(f, "Syntax error: {}", p)?,
|
Self::ErrorParsing(p, _) => write!(f, "Syntax error: {}", p)?,
|
||||||
|
|
||||||
@ -128,76 +130,74 @@ impl fmt::Display for EvalAltResult {
|
|||||||
Self::ErrorDataRace(s, _) => {
|
Self::ErrorDataRace(s, _) => {
|
||||||
write!(f, "Data race detected when accessing variable: {}", s)?
|
write!(f, "Data race detected when accessing variable: {}", s)?
|
||||||
}
|
}
|
||||||
Self::ErrorDotExpr(s, _) if !s.is_empty() => f.write_str(s)?,
|
Self::ErrorDotExpr(s, _) => match s.as_str() {
|
||||||
|
"" => f.write_str("Malformed dot expression"),
|
||||||
|
s => f.write_str(s),
|
||||||
|
}?,
|
||||||
Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered for '{}'", s)?,
|
Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered for '{}'", s)?,
|
||||||
Self::ErrorUnboundThis(_) => f.write_str("'this' is not bound")?,
|
Self::ErrorUnboundThis(_) => f.write_str("'this' is not bound")?,
|
||||||
Self::ErrorFor(_) => f.write_str("For loop expects a type with an iterator defined")?,
|
Self::ErrorFor(_) => f.write_str("For loop expects a type with an iterator defined")?,
|
||||||
Self::ErrorDotExpr(_, _) => f.write_str("Malformed dot expression")?,
|
|
||||||
Self::ErrorTooManyOperations(_) => f.write_str("Too many operations")?,
|
Self::ErrorTooManyOperations(_) => f.write_str("Too many operations")?,
|
||||||
Self::ErrorTooManyModules(_) => f.write_str("Too many modules imported")?,
|
Self::ErrorTooManyModules(_) => f.write_str("Too many modules imported")?,
|
||||||
Self::ErrorStackOverflow(_) => f.write_str("Stack overflow")?,
|
Self::ErrorStackOverflow(_) => f.write_str("Stack overflow")?,
|
||||||
Self::ErrorTerminated(_, _) => f.write_str("Script terminated")?,
|
Self::ErrorTerminated(_, _) => f.write_str("Script terminated")?,
|
||||||
|
|
||||||
Self::ErrorRuntime(d, _) if d.is::<ImmutableString>() => {
|
|
||||||
let s = &*d
|
|
||||||
.read_lock::<ImmutableString>()
|
|
||||||
.expect("never fails because the type was checked");
|
|
||||||
|
|
||||||
if s.is_empty() {
|
|
||||||
f.write_str("Runtime error")?
|
|
||||||
} else {
|
|
||||||
write!(f, "Runtime error: {}", s)?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::ErrorRuntime(d, _) if d.is::<()>() => f.write_str("Runtime error")?,
|
Self::ErrorRuntime(d, _) if d.is::<()>() => f.write_str("Runtime error")?,
|
||||||
|
Self::ErrorRuntime(d, _)
|
||||||
|
if d.read_lock::<ImmutableString>()
|
||||||
|
.map_or(false, |v| v.is_empty()) =>
|
||||||
|
{
|
||||||
|
write!(f, "Runtime error")?
|
||||||
|
}
|
||||||
Self::ErrorRuntime(d, _) => write!(f, "Runtime error: {}", d)?,
|
Self::ErrorRuntime(d, _) => write!(f, "Runtime error: {}", d)?,
|
||||||
|
|
||||||
Self::ErrorAssignmentToConstant(s, _) => write!(f, "Cannot modify constant {}", s)?,
|
Self::ErrorAssignmentToConstant(s, _) => write!(f, "Cannot modify constant {}", s)?,
|
||||||
Self::ErrorMismatchOutputType(s, r, _) => {
|
Self::ErrorMismatchOutputType(s, r, _) => match (r.as_str(), s.as_str()) {
|
||||||
write!(f, "Output type is incorrect: {} (expecting {})", r, s)?
|
("", s) => write!(f, "Output type is incorrect, expecting {}", s),
|
||||||
}
|
(r, "") => write!(f, "Output type is incorrect: {}", r),
|
||||||
Self::ErrorMismatchDataType(s, r, _) if r.is_empty() => {
|
(r, s) => write!(f, "Output type is incorrect: {} (expecting {})", r, s),
|
||||||
write!(f, "Data type is incorrect, expecting {}", s)?
|
}?,
|
||||||
}
|
Self::ErrorMismatchDataType(s, r, _) => match (r.as_str(), s.as_str()) {
|
||||||
Self::ErrorMismatchDataType(s, r, _) if s.is_empty() => {
|
("", s) => write!(f, "Data type is incorrect, expecting {}", s),
|
||||||
write!(f, "Data type is incorrect: {}", r)?
|
(r, "") => write!(f, "Data type is incorrect: {}", r),
|
||||||
}
|
(r, s) => write!(f, "Data type is incorrect: {} (expecting {})", r, s),
|
||||||
Self::ErrorMismatchDataType(s, r, _) => {
|
}?,
|
||||||
write!(f, "Data type is incorrect: {} (expecting {})", r, s)?
|
Self::ErrorArithmetic(s, _) => match s.as_str() {
|
||||||
}
|
"" => f.write_str("Arithmetic error"),
|
||||||
Self::ErrorArithmetic(s, _) => f.write_str(s)?,
|
s => f.write_str(s),
|
||||||
|
}?,
|
||||||
|
|
||||||
Self::LoopBreak(true, _) => f.write_str("Break statement not inside a loop")?,
|
Self::LoopBreak(true, _) => f.write_str("'break' not inside a loop")?,
|
||||||
Self::LoopBreak(false, _) => f.write_str("Continue statement not inside a loop")?,
|
Self::LoopBreak(false, _) => f.write_str("'continue' not inside a loop")?,
|
||||||
|
|
||||||
Self::Return(_, _) => f.write_str("NOT AN ERROR - Function returns value")?,
|
Self::Return(_, _) => f.write_str("NOT AN ERROR - function returns value")?,
|
||||||
|
|
||||||
Self::ErrorArrayBounds(0, index, _) => {
|
Self::ErrorArrayBounds(max, index, _) => match max {
|
||||||
write!(f, "Array index {} out of bounds: array is empty", index)?
|
0 => write!(f, "Array index {} out of bounds: array is empty", index),
|
||||||
}
|
1 => write!(
|
||||||
Self::ErrorArrayBounds(1, index, _) => write!(
|
|
||||||
f,
|
f,
|
||||||
"Array index {} out of bounds: only 1 element in the array",
|
"Array index {} out of bounds: only 1 element in the array",
|
||||||
index
|
index
|
||||||
)?,
|
),
|
||||||
Self::ErrorArrayBounds(max, index, _) => write!(
|
_ => write!(
|
||||||
f,
|
f,
|
||||||
"Array index {} out of bounds: only {} elements in the array",
|
"Array index {} out of bounds: only {} elements in the array",
|
||||||
index, max
|
index, max
|
||||||
)?,
|
),
|
||||||
Self::ErrorStringBounds(0, index, _) => {
|
}?,
|
||||||
write!(f, "String index {} out of bounds: string is empty", index)?
|
Self::ErrorStringBounds(max, index, _) => match max {
|
||||||
}
|
0 => write!(f, "String index {} out of bounds: string is empty", index),
|
||||||
Self::ErrorStringBounds(1, index, _) => write!(
|
1 => write!(
|
||||||
f,
|
f,
|
||||||
"String index {} out of bounds: only 1 character in the string",
|
"String index {} out of bounds: only 1 character in the string",
|
||||||
index
|
index
|
||||||
)?,
|
),
|
||||||
Self::ErrorStringBounds(max, index, _) => write!(
|
_ => write!(
|
||||||
f,
|
f,
|
||||||
"String index {} out of bounds: only {} characters in the string",
|
"String index {} out of bounds: only {} characters in the string",
|
||||||
index, max
|
index, max
|
||||||
)?,
|
),
|
||||||
|
}?,
|
||||||
Self::ErrorBitFieldBounds(max, index, _) => write!(
|
Self::ErrorBitFieldBounds(max, index, _) => write!(
|
||||||
f,
|
f,
|
||||||
"Bit-field index {} out of bounds: only {} bits in the bit-field",
|
"Bit-field index {} out of bounds: only {} bits in the bit-field",
|
||||||
|
@ -125,6 +125,9 @@ pub enum ParseErrorType {
|
|||||||
VariableExpected,
|
VariableExpected,
|
||||||
/// An identifier is a reserved keyword.
|
/// An identifier is a reserved keyword.
|
||||||
Reserved(String),
|
Reserved(String),
|
||||||
|
/// An expression is of the wrong type.
|
||||||
|
/// Wrapped values are the type requested and type of the actual result.
|
||||||
|
MismatchedType(String, String),
|
||||||
/// Missing an expression. Wrapped value is the expression type.
|
/// Missing an expression. Wrapped value is the expression type.
|
||||||
ExprExpected(String),
|
ExprExpected(String),
|
||||||
/// Defining a doc-comment in an appropriate place (e.g. not at global level).
|
/// Defining a doc-comment in an appropriate place (e.g. not at global level).
|
||||||
@ -194,12 +197,22 @@ impl fmt::Display for ParseErrorType {
|
|||||||
|
|
||||||
Self::UnknownOperator(s) => write!(f, "Unknown operator: '{}'", s),
|
Self::UnknownOperator(s) => write!(f, "Unknown operator: '{}'", s),
|
||||||
|
|
||||||
Self::MalformedCallExpr(s) if s.is_empty() => f.write_str("Invalid expression in function call arguments"),
|
Self::MalformedCallExpr(s) => match s.as_str() {
|
||||||
Self::MalformedIndexExpr(s) if s.is_empty() => f.write_str("Invalid index in indexing expression"),
|
"" => f.write_str("Invalid expression in function call arguments"),
|
||||||
Self::MalformedInExpr(s) if s.is_empty() => f.write_str("Invalid 'in' expression"),
|
s => f.write_str(s)
|
||||||
Self::MalformedCapture(s) if s.is_empty() => f.write_str("Invalid capturing"),
|
},
|
||||||
|
Self::MalformedIndexExpr(s) => match s.as_str() {
|
||||||
Self::MalformedCallExpr(s) | Self::MalformedIndexExpr(s) | Self::MalformedInExpr(s) | Self::MalformedCapture(s) => f.write_str(s),
|
"" => f.write_str("Invalid index in indexing expression"),
|
||||||
|
s => f.write_str(s)
|
||||||
|
},
|
||||||
|
Self::MalformedInExpr(s) => match s.as_str() {
|
||||||
|
"" => f.write_str("Invalid 'in' expression"),
|
||||||
|
s => f.write_str(s)
|
||||||
|
},
|
||||||
|
Self::MalformedCapture(s) => match s.as_str() {
|
||||||
|
"" => f.write_str("Invalid capturing"),
|
||||||
|
s => f.write_str(s)
|
||||||
|
},
|
||||||
|
|
||||||
Self::FnDuplicatedDefinition(s, n) => {
|
Self::FnDuplicatedDefinition(s, n) => {
|
||||||
write!(f, "Function '{}' with ", s)?;
|
write!(f, "Function '{}' with ", s)?;
|
||||||
@ -209,25 +222,30 @@ impl fmt::Display for ParseErrorType {
|
|||||||
_ => write!(f, "{} parameters already exists", n),
|
_ => write!(f, "{} parameters already exists", n),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Self::FnMissingBody(s) => match s.as_str() {
|
||||||
Self::FnMissingBody(s) if s.is_empty() => f.write_str("Expecting body statement block for anonymous function"),
|
"" => f.write_str("Expecting body statement block for anonymous function"),
|
||||||
Self::FnMissingBody(s) => write!(f, "Expecting body statement block for function '{}'", s),
|
s => write!(f, "Expecting body statement block for function '{}'", s)
|
||||||
|
},
|
||||||
Self::FnMissingParams(s) => write!(f, "Expecting parameters for function '{}'", s),
|
Self::FnMissingParams(s) => write!(f, "Expecting parameters for function '{}'", s),
|
||||||
Self::FnDuplicatedParam(s, arg) => write!(f, "Duplicated parameter '{}' for function '{}'", arg, s),
|
Self::FnDuplicatedParam(s, arg) => write!(f, "Duplicated parameter '{}' for function '{}'", arg, s),
|
||||||
|
|
||||||
Self::DuplicatedProperty(s) => write!(f, "Duplicated property '{}' for object map literal", s),
|
Self::DuplicatedProperty(s) => write!(f, "Duplicated property '{}' for object map literal", s),
|
||||||
Self::DuplicatedSwitchCase => f.write_str("Duplicated switch case"),
|
Self::DuplicatedSwitchCase => f.write_str("Duplicated switch case"),
|
||||||
Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name '{}'", s),
|
Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name '{}'", s),
|
||||||
|
|
||||||
|
Self::MismatchedType(r, a) => write!(f, "Expecting {}, not {}", r, a),
|
||||||
Self::ExprExpected(s) => write!(f, "Expecting {} expression", s),
|
Self::ExprExpected(s) => write!(f, "Expecting {} expression", s),
|
||||||
Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s),
|
Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s),
|
||||||
Self::MissingSymbol(s) => f.write_str(s),
|
Self::MissingSymbol(s) => f.write_str(s),
|
||||||
|
|
||||||
Self::AssignmentToConstant(s) if s.is_empty() => f.write_str("Cannot assign to a constant value"),
|
Self::AssignmentToConstant(s) => match s.as_str() {
|
||||||
Self::AssignmentToConstant(s) => write!(f, "Cannot assign to constant '{}'", s),
|
"" => f.write_str("Cannot assign to a constant value"),
|
||||||
|
s => write!(f, "Cannot assign to constant '{}'", s)
|
||||||
Self::AssignmentToInvalidLHS(s) if s.is_empty() => f.write_str("Expression cannot be assigned to"),
|
},
|
||||||
Self::AssignmentToInvalidLHS(s) => f.write_str(s),
|
Self::AssignmentToInvalidLHS(s) => match s.as_str() {
|
||||||
|
"" => f.write_str("Expression cannot be assigned to"),
|
||||||
|
s => f.write_str(s)
|
||||||
|
},
|
||||||
|
|
||||||
Self::LiteralTooLarge(typ, max) => write!(f, "{} exceeds the maximum limit ({})", typ, max),
|
Self::LiteralTooLarge(typ, max) => write!(f, "{} exceeds the maximum limit ({})", typ, max),
|
||||||
Self::Reserved(s) => write!(f, "'{}' is a reserved keyword", s),
|
Self::Reserved(s) => write!(f, "'{}' is a reserved keyword", s),
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use crate::ast::FnCallHashes;
|
use crate::ast::FnCallHashes;
|
||||||
use crate::engine::{
|
use crate::engine::{
|
||||||
FnResolutionCacheEntry, Imports, State, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR,
|
EvalState, FnResolutionCacheEntry, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR,
|
||||||
KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
|
||||||
MAX_DYNAMIC_PARAMETERS,
|
MAX_DYNAMIC_PARAMETERS,
|
||||||
};
|
};
|
||||||
@ -162,7 +162,7 @@ impl Engine {
|
|||||||
fn resolve_fn<'s>(
|
fn resolve_fn<'s>(
|
||||||
&self,
|
&self,
|
||||||
mods: &Imports,
|
mods: &Imports,
|
||||||
state: &'s mut State,
|
state: &'s mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
hash_script: u64,
|
hash_script: u64,
|
||||||
@ -266,7 +266,7 @@ impl Engine {
|
|||||||
pub(crate) fn call_native_fn(
|
pub(crate) fn call_native_fn(
|
||||||
&self,
|
&self,
|
||||||
mods: &Imports,
|
mods: &Imports,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
name: &str,
|
name: &str,
|
||||||
hash: u64,
|
hash: u64,
|
||||||
@ -439,7 +439,7 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
fn_def: &crate::ast::ScriptFnDef,
|
fn_def: &crate::ast::ScriptFnDef,
|
||||||
@ -451,7 +451,7 @@ impl Engine {
|
|||||||
fn make_error(
|
fn make_error(
|
||||||
name: String,
|
name: String,
|
||||||
fn_def: &crate::ast::ScriptFnDef,
|
fn_def: &crate::ast::ScriptFnDef,
|
||||||
state: &State,
|
state: &EvalState,
|
||||||
err: Box<EvalAltResult>,
|
err: Box<EvalAltResult>,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
) -> RhaiResult {
|
) -> RhaiResult {
|
||||||
@ -569,7 +569,7 @@ impl Engine {
|
|||||||
pub(crate) fn has_script_fn(
|
pub(crate) fn has_script_fn(
|
||||||
&self,
|
&self,
|
||||||
mods: Option<&Imports>,
|
mods: Option<&Imports>,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
hash_script: u64,
|
hash_script: u64,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
@ -608,7 +608,7 @@ impl Engine {
|
|||||||
pub(crate) fn exec_fn_call(
|
pub(crate) fn exec_fn_call(
|
||||||
&self,
|
&self,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
hashes: FnCallHashes,
|
hashes: FnCallHashes,
|
||||||
@ -644,7 +644,7 @@ impl Engine {
|
|||||||
crate::engine::KEYWORD_IS_DEF_FN
|
crate::engine::KEYWORD_IS_DEF_FN
|
||||||
if args.len() == 2 && args[0].is::<FnPtr>() && args[1].is::<crate::INT>() =>
|
if args.len() == 2 && args[0].is::<FnPtr>() && args[1].is::<crate::INT>() =>
|
||||||
{
|
{
|
||||||
let fn_name = &*args[0]
|
let fn_name = args[0]
|
||||||
.read_lock::<ImmutableString>()
|
.read_lock::<ImmutableString>()
|
||||||
.expect("never fails because `args[0]` is `FnPtr`");
|
.expect("never fails because `args[0]` is `FnPtr`");
|
||||||
let num_params = args[1]
|
let num_params = args[1]
|
||||||
@ -655,7 +655,7 @@ impl Engine {
|
|||||||
if num_params < 0 {
|
if num_params < 0 {
|
||||||
Dynamic::FALSE
|
Dynamic::FALSE
|
||||||
} else {
|
} else {
|
||||||
let hash_script = calc_fn_hash(fn_name, num_params as usize);
|
let hash_script = calc_fn_hash(fn_name.as_str(), num_params as usize);
|
||||||
self.has_script_fn(Some(mods), state, lib, hash_script)
|
self.has_script_fn(Some(mods), state, lib, hash_script)
|
||||||
.into()
|
.into()
|
||||||
},
|
},
|
||||||
@ -788,7 +788,7 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
statements: &[Stmt],
|
statements: &[Stmt],
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
level: usize,
|
level: usize,
|
||||||
@ -809,7 +809,7 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
script: &str,
|
script: &str,
|
||||||
_pos: Position,
|
_pos: Position,
|
||||||
@ -842,7 +842,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate the AST
|
// Evaluate the AST
|
||||||
let mut new_state: State = Default::default();
|
let mut new_state = EvalState::new();
|
||||||
new_state.source = state.source.clone();
|
new_state.source = state.source.clone();
|
||||||
new_state.operations = state.operations;
|
new_state.operations = state.operations;
|
||||||
|
|
||||||
@ -860,7 +860,7 @@ impl Engine {
|
|||||||
pub(crate) fn make_method_call(
|
pub(crate) fn make_method_call(
|
||||||
&self,
|
&self,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
mut hash: FnCallHashes,
|
mut hash: FnCallHashes,
|
||||||
@ -1024,7 +1024,7 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
level: usize,
|
level: usize,
|
||||||
@ -1046,7 +1046,7 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
@ -1202,7 +1202,7 @@ impl Engine {
|
|||||||
// IMPORTANT! If the eval defines new variables in the current scope,
|
// IMPORTANT! If the eval defines new variables in the current scope,
|
||||||
// all variable offsets from this point on will be mis-aligned.
|
// all variable offsets from this point on will be mis-aligned.
|
||||||
if scope.len() != prev_len {
|
if scope.len() != prev_len {
|
||||||
state.always_search = true;
|
state.always_search_scope = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.map_err(|err| {
|
return result.map_err(|err| {
|
||||||
@ -1298,7 +1298,7 @@ impl Engine {
|
|||||||
&self,
|
&self,
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
state: &mut State,
|
state: &mut EvalState,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
this_ptr: &mut Option<&mut Dynamic>,
|
this_ptr: &mut Option<&mut Dynamic>,
|
||||||
namespace: &NamespaceRef,
|
namespace: &NamespaceRef,
|
||||||
|
@ -399,7 +399,7 @@ impl CallableFunction {
|
|||||||
/// Is this an iterator function?
|
/// Is this an iterator function?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_iter(&self) -> bool {
|
pub const fn is_iter(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Iterator(_) => true,
|
Self::Iterator(_) => true,
|
||||||
Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => false,
|
Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => false,
|
||||||
@ -411,7 +411,7 @@ impl CallableFunction {
|
|||||||
/// Is this a Rhai-scripted function?
|
/// Is this a Rhai-scripted function?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_script(&self) -> bool {
|
pub const fn is_script(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
Self::Script(_) => true,
|
Self::Script(_) => true,
|
||||||
@ -422,7 +422,7 @@ impl CallableFunction {
|
|||||||
/// Is this a plugin function?
|
/// Is this a plugin function?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_plugin_fn(&self) -> bool {
|
pub const fn is_plugin_fn(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Plugin(_) => true,
|
Self::Plugin(_) => true,
|
||||||
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false,
|
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false,
|
||||||
@ -434,7 +434,7 @@ impl CallableFunction {
|
|||||||
/// Is this a native Rust function?
|
/// Is this a native Rust function?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_native(&self) -> bool {
|
pub const fn is_native(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Pure(_) | Self::Method(_) => true,
|
Self::Pure(_) | Self::Method(_) => true,
|
||||||
Self::Plugin(_) => true,
|
Self::Plugin(_) => true,
|
||||||
|
@ -522,6 +522,11 @@ impl PartialOrd<ImmutableString> for String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ImmutableString {
|
impl ImmutableString {
|
||||||
|
/// Create a new [`ImmutableString`].
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(SmartString::new().into())
|
||||||
|
}
|
||||||
/// Consume the [`ImmutableString`] and convert it into a [`String`].
|
/// Consume the [`ImmutableString`] and convert it into a [`String`].
|
||||||
/// If there are other references to the same string, a cloned copy is returned.
|
/// If there are other references to the same string, a cloned copy is returned.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -233,10 +233,11 @@ pub use ast::{
|
|||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
#[deprecated = "this type is volatile and may change"]
|
#[deprecated = "this type is volatile and may change"]
|
||||||
pub use engine::{Imports, State as EvalState};
|
pub use engine::{EvalState, FnResolutionCache, FnResolutionCacheEntry, Imports};
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
#[deprecated = "this type is volatile and may change"]
|
||||||
pub use engine::Limits;
|
pub use engine::Limits;
|
||||||
|
|
||||||
#[cfg(feature = "internals")]
|
#[cfg(feature = "internals")]
|
||||||
|
@ -1731,6 +1731,12 @@ impl From<StaticVec<Ident>> for NamespaceRef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl NamespaceRef {
|
impl NamespaceRef {
|
||||||
|
/// Create a new [`NamespaceRef`].
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(&self) -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
/// Get the [`Scope`][crate::Scope] index offset.
|
/// Get the [`Scope`][crate::Scope] index offset.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
@ -43,7 +43,7 @@ impl Default for OptimizationLevel {
|
|||||||
|
|
||||||
/// Mutable state throughout an optimization pass.
|
/// Mutable state throughout an optimization pass.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct State<'a> {
|
struct OptimizerState<'a> {
|
||||||
/// Has the [`AST`] been changed during this pass?
|
/// Has the [`AST`] been changed during this pass?
|
||||||
changed: bool,
|
changed: bool,
|
||||||
/// Collection of constants to use for eager function evaluations.
|
/// Collection of constants to use for eager function evaluations.
|
||||||
@ -58,7 +58,7 @@ struct State<'a> {
|
|||||||
optimization_level: OptimizationLevel,
|
optimization_level: OptimizationLevel,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> State<'a> {
|
impl<'a> OptimizerState<'a> {
|
||||||
/// Create a new State.
|
/// Create a new State.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub const fn new(
|
pub const fn new(
|
||||||
@ -121,7 +121,7 @@ impl<'a> State<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Has a system function a Rust-native override?
|
// Has a system function a Rust-native override?
|
||||||
fn has_native_fn(state: &State, hash_script: u64, arg_types: &[TypeId]) -> bool {
|
fn has_native_fn(state: &OptimizerState, hash_script: u64, arg_types: &[TypeId]) -> bool {
|
||||||
let hash_params = calc_fn_params_hash(arg_types.iter().cloned());
|
let hash_params = calc_fn_params_hash(arg_types.iter().cloned());
|
||||||
let hash = combine_hashes(hash_script, hash_params);
|
let hash = combine_hashes(hash_script, hash_params);
|
||||||
|
|
||||||
@ -135,7 +135,7 @@ fn has_native_fn(state: &State, hash_script: u64, arg_types: &[TypeId]) -> bool
|
|||||||
|
|
||||||
/// Call a registered function
|
/// Call a registered function
|
||||||
fn call_fn_with_constant_arguments(
|
fn call_fn_with_constant_arguments(
|
||||||
state: &State,
|
state: &OptimizerState,
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
arg_values: &mut [Dynamic],
|
arg_values: &mut [Dynamic],
|
||||||
) -> Option<Dynamic> {
|
) -> Option<Dynamic> {
|
||||||
@ -159,7 +159,7 @@ fn call_fn_with_constant_arguments(
|
|||||||
/// Optimize a block of [statements][Stmt].
|
/// Optimize a block of [statements][Stmt].
|
||||||
fn optimize_stmt_block(
|
fn optimize_stmt_block(
|
||||||
mut statements: Vec<Stmt>,
|
mut statements: Vec<Stmt>,
|
||||||
state: &mut State,
|
state: &mut OptimizerState,
|
||||||
preserve_result: bool,
|
preserve_result: bool,
|
||||||
is_internal: bool,
|
is_internal: bool,
|
||||||
reduce_return: bool,
|
reduce_return: bool,
|
||||||
@ -368,7 +368,7 @@ fn optimize_stmt_block(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Optimize a [statement][Stmt].
|
/// Optimize a [statement][Stmt].
|
||||||
fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: bool) {
|
||||||
match stmt {
|
match stmt {
|
||||||
// var = var op expr => var op= expr
|
// var = var op expr => var op= expr
|
||||||
Stmt::Assignment(x, _)
|
Stmt::Assignment(x, _)
|
||||||
@ -682,7 +682,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Optimize an [expression][Expr].
|
/// Optimize an [expression][Expr].
|
||||||
fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
|
fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
|
||||||
// These keywords are handled specially
|
// These keywords are handled specially
|
||||||
const DONT_EVAL_KEYWORDS: &[&str] = &[
|
const DONT_EVAL_KEYWORDS: &[&str] = &[
|
||||||
KEYWORD_PRINT, // side effects
|
KEYWORD_PRINT, // side effects
|
||||||
@ -1051,7 +1051,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
|
|||||||
|
|
||||||
// Custom syntax
|
// Custom syntax
|
||||||
Expr::Custom(x, _) => {
|
Expr::Custom(x, _) => {
|
||||||
if x.scope_changed {
|
if x.scope_may_be_changed {
|
||||||
state.propagate_constants = false;
|
state.propagate_constants = false;
|
||||||
}
|
}
|
||||||
x.keywords.iter_mut().for_each(|expr| optimize_expr(expr, state, false));
|
x.keywords.iter_mut().for_each(|expr| optimize_expr(expr, state, false));
|
||||||
@ -1077,7 +1077,7 @@ fn optimize_top_level(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set up the state
|
// Set up the state
|
||||||
let mut state = State::new(engine, lib, optimization_level);
|
let mut state = OptimizerState::new(engine, lib, optimization_level);
|
||||||
|
|
||||||
// Add constants and variables from the scope
|
// Add constants and variables from the scope
|
||||||
scope.iter().for_each(|(name, constant, value)| {
|
scope.iter().for_each(|(name, constant, value)| {
|
||||||
@ -1142,7 +1142,7 @@ pub fn optimize_into_ast(
|
|||||||
let mut fn_def = crate::fn_native::shared_take_or_clone(fn_def);
|
let mut fn_def = crate::fn_native::shared_take_or_clone(fn_def);
|
||||||
|
|
||||||
// Optimize the function body
|
// Optimize the function body
|
||||||
let state = &mut State::new(engine, lib2, level);
|
let state = &mut OptimizerState::new(engine, lib2, level);
|
||||||
|
|
||||||
let body = mem::take(fn_def.body.statements_mut()).into_vec();
|
let body = mem::take(fn_def.body.statements_mut()).into_vec();
|
||||||
|
|
||||||
|
@ -173,6 +173,10 @@ mod number_formatting {
|
|||||||
pub fn int_to_octal(value: INT) -> ImmutableString {
|
pub fn int_to_octal(value: INT) -> ImmutableString {
|
||||||
to_octal(value)
|
to_octal(value)
|
||||||
}
|
}
|
||||||
|
#[rhai_fn(name = "to_binary")]
|
||||||
|
pub fn int_to_binary(value: INT) -> ImmutableString {
|
||||||
|
to_binary(value)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "only_i32"))]
|
#[cfg(not(feature = "only_i32"))]
|
||||||
#[cfg(not(feature = "only_i64"))]
|
#[cfg(not(feature = "only_i64"))]
|
||||||
|
152
src/parse.rs
152
src/parse.rs
@ -137,7 +137,7 @@ impl<'e> ParseState<'e> {
|
|||||||
///
|
///
|
||||||
/// If the variable is not present in the scope adds it to the list of external variables
|
/// If the variable is not present in the scope adds it to the list of external variables
|
||||||
///
|
///
|
||||||
/// The return value is the offset to be deducted from `ParseState::stack::len`,
|
/// The return value is the offset to be deducted from `ParseState::stack::len()`,
|
||||||
/// i.e. the top element of [`ParseState`]'s variables stack is offset 1.
|
/// i.e. the top element of [`ParseState`]'s variables stack is offset 1.
|
||||||
///
|
///
|
||||||
/// Return `None` when the variable name is not found in the `stack`.
|
/// Return `None` when the variable name is not found in the `stack`.
|
||||||
@ -156,7 +156,7 @@ impl<'e> ParseState<'e> {
|
|||||||
barrier = true;
|
barrier = true;
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
*n == name
|
n == name
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.and_then(|(i, _)| NonZeroUsize::new(i + 1));
|
.and_then(|(i, _)| NonZeroUsize::new(i + 1));
|
||||||
@ -281,9 +281,73 @@ impl Expr {
|
|||||||
_ => self,
|
_ => self,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Raise an error if the expression can never yield a boolean value.
|
||||||
|
fn ensure_bool_expr(self) -> Result<Expr, ParseError> {
|
||||||
|
let type_name = match self {
|
||||||
|
Expr::Unit(_) => "()",
|
||||||
|
Expr::DynamicConstant(ref v, _) if !v.is::<bool>() => v.type_name(),
|
||||||
|
Expr::IntegerConstant(_, _) => "a number",
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
Expr::FloatConstant(_, _) => "a floating-point number",
|
||||||
|
Expr::CharConstant(_, _) => "a character",
|
||||||
|
Expr::StringConstant(_, _) => "a string",
|
||||||
|
Expr::InterpolatedString(_, _) => "a string",
|
||||||
|
Expr::Array(_, _) => "an array",
|
||||||
|
Expr::Map(_, _) => "an object map",
|
||||||
|
_ => return Ok(self),
|
||||||
|
};
|
||||||
|
|
||||||
|
Err(
|
||||||
|
PERR::MismatchedType("a boolean expression".to_string(), type_name.to_string())
|
||||||
|
.into_err(self.position()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/// Raise an error if the expression can never yield an iterable value.
|
||||||
|
fn ensure_iterable(self) -> Result<Expr, ParseError> {
|
||||||
|
let type_name = match self {
|
||||||
|
Expr::Unit(_) => "()",
|
||||||
|
Expr::BoolConstant(_, _) => "a boolean",
|
||||||
|
Expr::IntegerConstant(_, _) => "a number",
|
||||||
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
Expr::FloatConstant(_, _) => "a floating-point number",
|
||||||
|
Expr::CharConstant(_, _) => "a character",
|
||||||
|
Expr::StringConstant(_, _) => "a string",
|
||||||
|
Expr::InterpolatedString(_, _) => "a string",
|
||||||
|
Expr::Map(_, _) => "an object map",
|
||||||
|
_ => return Ok(self),
|
||||||
|
};
|
||||||
|
|
||||||
|
Err(
|
||||||
|
PERR::MismatchedType("an iterable value".to_string(), type_name.to_string())
|
||||||
|
.into_err(self.position()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make sure that the next expression is not a statement expression (i.e. wrapped in `{}`).
|
||||||
|
#[inline(always)]
|
||||||
|
fn ensure_not_statement_expr(input: &mut TokenStream, type_name: &str) -> Result<(), ParseError> {
|
||||||
|
match input.peek().expect(NEVER_ENDS) {
|
||||||
|
(Token::LeftBrace, pos) => Err(PERR::ExprExpected(type_name.to_string()).into_err(*pos)),
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make sure that the next expression is not a mis-typed assignment (i.e. `a = b` instead of `a == b`).
|
||||||
|
#[inline(always)]
|
||||||
|
fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> {
|
||||||
|
match input.peek().expect(NEVER_ENDS) {
|
||||||
|
(Token::Equals, pos) => Err(LexError::ImproperSymbol(
|
||||||
|
"=".to_string(),
|
||||||
|
"Possibly a typo of '=='?".to_string(),
|
||||||
|
)
|
||||||
|
.into_err(*pos)),
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consume a particular [token][Token], checking that it is the expected one.
|
/// Consume a particular [token][Token], checking that it is the expected one.
|
||||||
|
#[inline]
|
||||||
fn eat_token(input: &mut TokenStream, token: Token) -> Position {
|
fn eat_token(input: &mut TokenStream, token: Token) -> Position {
|
||||||
let (t, pos) = input.next().expect(NEVER_ENDS);
|
let (t, pos) = input.next().expect(NEVER_ENDS);
|
||||||
|
|
||||||
@ -299,6 +363,7 @@ fn eat_token(input: &mut TokenStream, token: Token) -> Position {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Match a particular [token][Token], consuming it if matched.
|
/// Match a particular [token][Token], consuming it if matched.
|
||||||
|
#[inline]
|
||||||
fn match_token(input: &mut TokenStream, token: Token) -> (bool, Position) {
|
fn match_token(input: &mut TokenStream, token: Token) -> (bool, Position) {
|
||||||
let (t, pos) = input.peek().expect(NEVER_ENDS);
|
let (t, pos) = input.peek().expect(NEVER_ENDS);
|
||||||
if *t == token {
|
if *t == token {
|
||||||
@ -1816,8 +1881,8 @@ fn parse_binary_op(
|
|||||||
.expect("never fails because `||` has two arguments");
|
.expect("never fails because `||` has two arguments");
|
||||||
Expr::Or(
|
Expr::Or(
|
||||||
BinaryExpr {
|
BinaryExpr {
|
||||||
lhs: current_lhs,
|
lhs: current_lhs.ensure_bool_expr()?,
|
||||||
rhs,
|
rhs: rhs.ensure_bool_expr()?,
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
pos,
|
pos,
|
||||||
@ -1832,8 +1897,8 @@ fn parse_binary_op(
|
|||||||
.expect("never fails because `&&` has two arguments");
|
.expect("never fails because `&&` has two arguments");
|
||||||
Expr::And(
|
Expr::And(
|
||||||
BinaryExpr {
|
BinaryExpr {
|
||||||
lhs: current_lhs,
|
lhs: current_lhs.ensure_bool_expr()?,
|
||||||
rhs,
|
rhs: rhs.ensure_bool_expr()?,
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
pos,
|
pos,
|
||||||
@ -1896,10 +1961,9 @@ fn parse_custom_syntax(
|
|||||||
let mut tokens: StaticVec<_> = Default::default();
|
let mut tokens: StaticVec<_> = Default::default();
|
||||||
|
|
||||||
// Adjust the variables stack
|
// Adjust the variables stack
|
||||||
if syntax.scope_changed {
|
if syntax.scope_may_be_changed {
|
||||||
// Add an empty variable name to the stack.
|
// Add a barrier variable to the stack so earlier variables will not be matched.
|
||||||
// Empty variable names act as a barrier so earlier variables will not be matched.
|
// Variable searches stop at the first barrier.
|
||||||
// Variable searches stop at the first empty variable name.
|
|
||||||
let empty = state.get_identifier(SCOPE_SEARCH_BARRIER_MARKER);
|
let empty = state.get_identifier(SCOPE_SEARCH_BARRIER_MARKER);
|
||||||
state.stack.push((empty, AccessMode::ReadWrite));
|
state.stack.push((empty, AccessMode::ReadWrite));
|
||||||
}
|
}
|
||||||
@ -2017,12 +2081,15 @@ fn parse_custom_syntax(
|
|||||||
keywords.shrink_to_fit();
|
keywords.shrink_to_fit();
|
||||||
tokens.shrink_to_fit();
|
tokens.shrink_to_fit();
|
||||||
|
|
||||||
Ok(CustomExpr {
|
Ok(Expr::Custom(
|
||||||
|
CustomExpr {
|
||||||
keywords,
|
keywords,
|
||||||
tokens,
|
tokens,
|
||||||
scope_changed: syntax.scope_changed,
|
scope_may_be_changed: syntax.scope_may_be_changed,
|
||||||
}
|
}
|
||||||
.into_custom_syntax_expr(pos))
|
.into(),
|
||||||
|
pos,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse an expression.
|
/// Parse an expression.
|
||||||
@ -2070,46 +2137,6 @@ fn parse_expr(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make sure that the expression is not a statement expression (i.e. wrapped in `{}`).
|
|
||||||
fn ensure_not_statement_expr(input: &mut TokenStream, type_name: &str) -> Result<(), ParseError> {
|
|
||||||
match input.peek().expect(NEVER_ENDS) {
|
|
||||||
// Disallow statement expressions
|
|
||||||
(Token::LeftBrace, pos) | (Token::EOF, pos) => {
|
|
||||||
Err(PERR::ExprExpected(type_name.to_string()).into_err(*pos))
|
|
||||||
}
|
|
||||||
// No need to check for others at this time - leave it for the expr parser
|
|
||||||
_ => Ok(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Make sure that the expression is not a mis-typed assignment (i.e. `a = b` instead of `a == b`).
|
|
||||||
fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> {
|
|
||||||
match input.peek().expect(NEVER_ENDS) {
|
|
||||||
(Token::Equals, pos) => Err(LexError::ImproperSymbol(
|
|
||||||
"=".to_string(),
|
|
||||||
"Possibly a typo of '=='?".to_string(),
|
|
||||||
)
|
|
||||||
.into_err(*pos)),
|
|
||||||
(token @ Token::PlusAssign, pos)
|
|
||||||
| (token @ Token::MinusAssign, pos)
|
|
||||||
| (token @ Token::MultiplyAssign, pos)
|
|
||||||
| (token @ Token::DivideAssign, pos)
|
|
||||||
| (token @ Token::LeftShiftAssign, pos)
|
|
||||||
| (token @ Token::RightShiftAssign, pos)
|
|
||||||
| (token @ Token::ModuloAssign, pos)
|
|
||||||
| (token @ Token::PowerOfAssign, pos)
|
|
||||||
| (token @ Token::AndAssign, pos)
|
|
||||||
| (token @ Token::OrAssign, pos)
|
|
||||||
| (token @ Token::XOrAssign, pos) => Err(LexError::ImproperSymbol(
|
|
||||||
token.syntax().to_string(),
|
|
||||||
"Expecting a boolean expression, not an assignment".to_string(),
|
|
||||||
)
|
|
||||||
.into_err(*pos)),
|
|
||||||
|
|
||||||
_ => Ok(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse an if statement.
|
/// Parse an if statement.
|
||||||
fn parse_if(
|
fn parse_if(
|
||||||
input: &mut TokenStream,
|
input: &mut TokenStream,
|
||||||
@ -2125,7 +2152,7 @@ fn parse_if(
|
|||||||
|
|
||||||
// if guard { if_body }
|
// if guard { if_body }
|
||||||
ensure_not_statement_expr(input, "a boolean")?;
|
ensure_not_statement_expr(input, "a boolean")?;
|
||||||
let guard = parse_expr(input, state, lib, settings.level_up())?;
|
let guard = parse_expr(input, state, lib, settings.level_up())?.ensure_bool_expr()?;
|
||||||
ensure_not_assignment(input)?;
|
ensure_not_assignment(input)?;
|
||||||
let if_body = parse_block(input, state, lib, settings.level_up())?;
|
let if_body = parse_block(input, state, lib, settings.level_up())?;
|
||||||
|
|
||||||
@ -2163,16 +2190,16 @@ fn parse_while_loop(
|
|||||||
let (guard, token_pos) = match input.next().expect(NEVER_ENDS) {
|
let (guard, token_pos) = match input.next().expect(NEVER_ENDS) {
|
||||||
(Token::While, pos) => {
|
(Token::While, pos) => {
|
||||||
ensure_not_statement_expr(input, "a boolean")?;
|
ensure_not_statement_expr(input, "a boolean")?;
|
||||||
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
let expr = parse_expr(input, state, lib, settings.level_up())?.ensure_bool_expr()?;
|
||||||
|
ensure_not_assignment(input)?;
|
||||||
(expr, pos)
|
(expr, pos)
|
||||||
}
|
}
|
||||||
(Token::Loop, pos) => (Expr::Unit(Position::NONE), pos),
|
(Token::Loop, pos) => (Expr::Unit(Position::NONE), pos),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
settings.pos = token_pos;
|
settings.pos = token_pos;
|
||||||
|
|
||||||
ensure_not_assignment(input)?;
|
|
||||||
settings.is_breakable = true;
|
settings.is_breakable = true;
|
||||||
|
|
||||||
let body = parse_block(input, state, lib, settings.level_up())?;
|
let body = parse_block(input, state, lib, settings.level_up())?;
|
||||||
|
|
||||||
Ok(Stmt::While(guard, Box::new(body.into()), settings.pos))
|
Ok(Stmt::While(guard, Box::new(body.into()), settings.pos))
|
||||||
@ -2206,9 +2233,10 @@ fn parse_do(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ensure_not_statement_expr(input, "a boolean")?;
|
|
||||||
settings.is_breakable = false;
|
settings.is_breakable = false;
|
||||||
let guard = parse_expr(input, state, lib, settings.level_up())?;
|
|
||||||
|
ensure_not_statement_expr(input, "a boolean")?;
|
||||||
|
let guard = parse_expr(input, state, lib, settings.level_up())?.ensure_bool_expr()?;
|
||||||
ensure_not_assignment(input)?;
|
ensure_not_assignment(input)?;
|
||||||
|
|
||||||
Ok(Stmt::Do(
|
Ok(Stmt::Do(
|
||||||
@ -2279,7 +2307,7 @@ fn parse_for(
|
|||||||
|
|
||||||
// for name in expr { body }
|
// for name in expr { body }
|
||||||
ensure_not_statement_expr(input, "a boolean")?;
|
ensure_not_statement_expr(input, "a boolean")?;
|
||||||
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
let expr = parse_expr(input, state, lib, settings.level_up())?.ensure_iterable()?;
|
||||||
|
|
||||||
let prev_stack_len = state.stack.len();
|
let prev_stack_len = state.stack.len();
|
||||||
|
|
||||||
@ -2758,6 +2786,10 @@ fn parse_stmt(
|
|||||||
match input.peek().expect(NEVER_ENDS) {
|
match input.peek().expect(NEVER_ENDS) {
|
||||||
// `return`/`throw` at <EOF>
|
// `return`/`throw` at <EOF>
|
||||||
(Token::EOF, _) => Ok(Stmt::Return(return_type, None, token_pos)),
|
(Token::EOF, _) => Ok(Stmt::Return(return_type, None, token_pos)),
|
||||||
|
// `return`/`throw` at end of block
|
||||||
|
(Token::RightBrace, _) if !settings.is_global => {
|
||||||
|
Ok(Stmt::Return(return_type, None, token_pos))
|
||||||
|
}
|
||||||
// `return;` or `throw;`
|
// `return;` or `throw;`
|
||||||
(Token::SemiColon, _) => Ok(Stmt::Return(return_type, None, token_pos)),
|
(Token::SemiColon, _) => Ok(Stmt::Return(return_type, None, token_pos)),
|
||||||
// `return` or `throw` with expression
|
// `return` or `throw` with expression
|
||||||
|
@ -25,9 +25,9 @@ fn test_bool_op3() -> Result<(), Box<EvalAltResult>> {
|
|||||||
let engine = Engine::new();
|
let engine = Engine::new();
|
||||||
|
|
||||||
assert!(engine.eval::<bool>("true && (false || 123)").is_err());
|
assert!(engine.eval::<bool>("true && (false || 123)").is_err());
|
||||||
assert_eq!(engine.eval::<bool>("true && (true || 123)")?, true);
|
assert_eq!(engine.eval::<bool>("true && (true || { throw })")?, true);
|
||||||
assert!(engine.eval::<bool>("123 && (false || true)").is_err());
|
assert!(engine.eval::<bool>("123 && (false || true)").is_err());
|
||||||
assert_eq!(engine.eval::<bool>("false && (true || 123)")?, false);
|
assert_eq!(engine.eval::<bool>("false && (true || { throw })")?, false);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ fn test_native_context_fn_name() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
let mut engine = Engine::new();
|
let mut engine = Engine::new();
|
||||||
|
|
||||||
|
#[allow(deprecated)]
|
||||||
engine
|
engine
|
||||||
.register_raw_fn(
|
.register_raw_fn(
|
||||||
"add_double",
|
"add_double",
|
||||||
|
@ -160,6 +160,7 @@ mod test_switch_enum {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use rhai::Array;
|
use rhai::Array;
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
#[allow(dead_code)]
|
||||||
enum MyEnum {
|
enum MyEnum {
|
||||||
Foo,
|
Foo,
|
||||||
Bar(INT),
|
Bar(INT),
|
||||||
|
Loading…
Reference in New Issue
Block a user