commit
166e34fa0b
@ -15,6 +15,8 @@ Bug fixes
|
|||||||
|
|
||||||
* Fix bug when accessing properties in closures.
|
* Fix bug when accessing properties in closures.
|
||||||
* Fix bug when accessing a deep index with a function call.
|
* Fix bug when accessing a deep index with a function call.
|
||||||
|
* Fix bug that sometimes allow assigning to an invalid l-value.
|
||||||
|
* Fix off-by-one error with `Engine::set_max_call_levels`.
|
||||||
|
|
||||||
Breaking changes
|
Breaking changes
|
||||||
----------------
|
----------------
|
||||||
|
10
src/ast.rs
10
src/ast.rs
@ -88,6 +88,7 @@ pub struct ScriptFnDef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ScriptFnDef {
|
impl fmt::Display for ScriptFnDef {
|
||||||
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
@ -130,6 +131,7 @@ pub struct ScriptFnMetadata<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ScriptFnMetadata<'_> {
|
impl fmt::Display for ScriptFnMetadata<'_> {
|
||||||
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
@ -146,6 +148,7 @@ impl fmt::Display for ScriptFnMetadata<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Into<ScriptFnMetadata<'a>> for &'a ScriptFnDef {
|
impl<'a> Into<ScriptFnMetadata<'a>> for &'a ScriptFnDef {
|
||||||
|
#[inline(always)]
|
||||||
fn into(self) -> ScriptFnMetadata<'a> {
|
fn into(self) -> ScriptFnMetadata<'a> {
|
||||||
ScriptFnMetadata {
|
ScriptFnMetadata {
|
||||||
comments: self.comments.iter().map(|s| s.as_str()).collect(),
|
comments: self.comments.iter().map(|s| s.as_str()).collect(),
|
||||||
@ -172,6 +175,7 @@ pub struct AST {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AST {
|
impl Default for AST {
|
||||||
|
#[inline(always)]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
source: None,
|
source: None,
|
||||||
@ -208,14 +212,17 @@ impl AST {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Get the source.
|
/// Get the source.
|
||||||
|
#[inline(always)]
|
||||||
pub fn source(&self) -> Option<&str> {
|
pub fn source(&self) -> Option<&str> {
|
||||||
self.source.as_ref().map(|s| s.as_str())
|
self.source.as_ref().map(|s| s.as_str())
|
||||||
}
|
}
|
||||||
/// Clone the source.
|
/// Clone the source.
|
||||||
|
#[inline(always)]
|
||||||
pub(crate) fn clone_source(&self) -> Option<ImmutableString> {
|
pub(crate) fn clone_source(&self) -> Option<ImmutableString> {
|
||||||
self.source.clone()
|
self.source.clone()
|
||||||
}
|
}
|
||||||
/// Set the source.
|
/// Set the source.
|
||||||
|
#[inline(always)]
|
||||||
pub fn set_source<S: Into<ImmutableString>>(&mut self, source: Option<S>) {
|
pub fn set_source<S: Into<ImmutableString>>(&mut self, source: Option<S>) {
|
||||||
self.source = source.map(|s| s.into())
|
self.source = source.map(|s| s.into())
|
||||||
}
|
}
|
||||||
@ -655,6 +662,7 @@ pub struct Ident {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Ident {
|
impl fmt::Debug for Ident {
|
||||||
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "Ident({:?} @ {:?})", self.name, self.pos)
|
write!(f, "Ident({:?} @ {:?})", self.name, self.pos)
|
||||||
}
|
}
|
||||||
@ -736,6 +744,7 @@ impl Default for Stmt {
|
|||||||
|
|
||||||
impl Stmt {
|
impl Stmt {
|
||||||
/// Is this statement [`Noop`][Stmt::Noop]?
|
/// Is this statement [`Noop`][Stmt::Noop]?
|
||||||
|
#[inline(always)]
|
||||||
pub fn is_noop(&self) -> bool {
|
pub fn is_noop(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Noop(_) => true,
|
Self::Noop(_) => true,
|
||||||
@ -1048,6 +1057,7 @@ impl Expr {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
/// Is the expression a simple variable access?
|
/// Is the expression a simple variable access?
|
||||||
|
#[inline(always)]
|
||||||
pub(crate) fn get_variable_access(&self, non_qualified: bool) -> Option<&str> {
|
pub(crate) fn get_variable_access(&self, non_qualified: bool) -> Option<&str> {
|
||||||
match self {
|
match self {
|
||||||
Self::Variable(x) if !non_qualified || x.1.is_none() => Some((x.2).name.as_str()),
|
Self::Variable(x) if !non_qualified || x.1.is_none() => Some((x.2).name.as_str()),
|
||||||
|
@ -88,21 +88,27 @@ pub trait Variant: Any + Send + Sync + private::Sealed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Any + Clone + SendSync> Variant for T {
|
impl<T: Any + Clone + SendSync> Variant for T {
|
||||||
|
#[inline(always)]
|
||||||
fn as_any(&self) -> &dyn Any {
|
fn as_any(&self) -> &dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
#[inline(always)]
|
||||||
fn as_mut_any(&mut self) -> &mut dyn Any {
|
fn as_mut_any(&mut self) -> &mut dyn Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
#[inline(always)]
|
||||||
fn as_box_any(self: Box<Self>) -> Box<dyn Any> {
|
fn as_box_any(self: Box<Self>) -> Box<dyn Any> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
#[inline(always)]
|
||||||
fn type_name(&self) -> &'static str {
|
fn type_name(&self) -> &'static str {
|
||||||
type_name::<T>()
|
type_name::<T>()
|
||||||
}
|
}
|
||||||
|
#[inline(always)]
|
||||||
fn into_dynamic(self) -> Dynamic {
|
fn into_dynamic(self) -> Dynamic {
|
||||||
Dynamic::from(self)
|
Dynamic::from(self)
|
||||||
}
|
}
|
||||||
|
#[inline(always)]
|
||||||
fn clone_into_dynamic(&self) -> Dynamic {
|
fn clone_into_dynamic(&self) -> Dynamic {
|
||||||
Dynamic::from(self.clone())
|
Dynamic::from(self.clone())
|
||||||
}
|
}
|
||||||
@ -127,6 +133,7 @@ pub enum AccessMode {
|
|||||||
|
|
||||||
impl AccessMode {
|
impl AccessMode {
|
||||||
/// Is the access type [`ReadOnly`]?
|
/// Is the access type [`ReadOnly`]?
|
||||||
|
#[inline(always)]
|
||||||
pub fn is_read_only(self) -> bool {
|
pub fn is_read_only(self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::ReadWrite => false,
|
Self::ReadWrite => false,
|
||||||
|
117
src/engine.rs
117
src/engine.rs
@ -59,18 +59,22 @@ pub struct Imports(StaticVec<(ImmutableString, Shared<Module>)>);
|
|||||||
|
|
||||||
impl Imports {
|
impl Imports {
|
||||||
/// Get the length of this stack of imported [modules][Module].
|
/// Get the length of this stack of imported [modules][Module].
|
||||||
|
#[inline(always)]
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.0.len()
|
self.0.len()
|
||||||
}
|
}
|
||||||
/// Is this stack of imported [modules][Module] empty?
|
/// Is this stack of imported [modules][Module] empty?
|
||||||
|
#[inline(always)]
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.0.is_empty()
|
self.0.is_empty()
|
||||||
}
|
}
|
||||||
/// Get the imported [modules][Module] at a particular index.
|
/// Get the imported [modules][Module] at a particular index.
|
||||||
|
#[inline(always)]
|
||||||
pub fn get(&self, index: usize) -> Option<Shared<Module>> {
|
pub fn get(&self, index: usize) -> Option<Shared<Module>> {
|
||||||
self.0.get(index).map(|(_, m)| m).cloned()
|
self.0.get(index).map(|(_, m)| m).cloned()
|
||||||
}
|
}
|
||||||
/// Get the index of an imported [modules][Module] by name.
|
/// Get the index of an imported [modules][Module] by name.
|
||||||
|
#[inline(always)]
|
||||||
pub fn find(&self, name: &str) -> Option<usize> {
|
pub fn find(&self, name: &str) -> Option<usize> {
|
||||||
self.0
|
self.0
|
||||||
.iter()
|
.iter()
|
||||||
@ -80,15 +84,18 @@ impl Imports {
|
|||||||
.map(|(index, _)| index)
|
.map(|(index, _)| index)
|
||||||
}
|
}
|
||||||
/// Push an imported [modules][Module] onto the stack.
|
/// Push an imported [modules][Module] onto the stack.
|
||||||
|
#[inline(always)]
|
||||||
pub fn push(&mut self, name: impl Into<ImmutableString>, module: impl Into<Shared<Module>>) {
|
pub fn push(&mut self, name: impl Into<ImmutableString>, module: impl Into<Shared<Module>>) {
|
||||||
self.0.push((name.into(), module.into()));
|
self.0.push((name.into(), module.into()));
|
||||||
}
|
}
|
||||||
/// Truncate the stack of imported [modules][Module] to a particular length.
|
/// Truncate the stack of imported [modules][Module] to a particular length.
|
||||||
|
#[inline(always)]
|
||||||
pub fn truncate(&mut self, size: usize) {
|
pub fn truncate(&mut self, size: usize) {
|
||||||
self.0.truncate(size);
|
self.0.truncate(size);
|
||||||
}
|
}
|
||||||
/// Get an iterator to this stack of imported [modules][Module] in reverse order.
|
/// Get an iterator to this stack of imported [modules][Module] in reverse order.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
#[inline(always)]
|
||||||
pub fn iter<'a>(&'a self) -> impl Iterator<Item = (&'a str, &'a Module)> + 'a {
|
pub fn iter<'a>(&'a self) -> impl Iterator<Item = (&'a str, &'a Module)> + 'a {
|
||||||
self.0
|
self.0
|
||||||
.iter()
|
.iter()
|
||||||
@ -97,25 +104,30 @@ impl Imports {
|
|||||||
}
|
}
|
||||||
/// Get an iterator to this stack of imported [modules][Module] in reverse order.
|
/// Get an iterator to this stack of imported [modules][Module] in reverse order.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
#[inline(always)]
|
||||||
pub(crate) fn iter_raw<'a>(
|
pub(crate) fn iter_raw<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
) -> impl Iterator<Item = (&'a ImmutableString, &'a Shared<Module>)> + 'a {
|
) -> impl Iterator<Item = (&'a ImmutableString, &'a Shared<Module>)> + 'a {
|
||||||
self.0.iter().rev().map(|(n, m)| (n, m))
|
self.0.iter().rev().map(|(n, m)| (n, m))
|
||||||
}
|
}
|
||||||
/// Get a consuming iterator to this stack of imported [modules][Module] in reverse order.
|
/// Get a consuming iterator to this stack of imported [modules][Module] in reverse order.
|
||||||
|
#[inline(always)]
|
||||||
pub fn into_iter(self) -> impl Iterator<Item = (ImmutableString, Shared<Module>)> {
|
pub fn into_iter(self) -> impl Iterator<Item = (ImmutableString, Shared<Module>)> {
|
||||||
self.0.into_iter().rev()
|
self.0.into_iter().rev()
|
||||||
}
|
}
|
||||||
/// Add a stream of imported [modules][Module].
|
/// Add a stream of imported [modules][Module].
|
||||||
|
#[inline(always)]
|
||||||
pub fn extend(&mut self, stream: impl Iterator<Item = (ImmutableString, Shared<Module>)>) {
|
pub fn extend(&mut self, stream: impl Iterator<Item = (ImmutableString, Shared<Module>)>) {
|
||||||
self.0.extend(stream)
|
self.0.extend(stream)
|
||||||
}
|
}
|
||||||
/// 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)]
|
||||||
pub fn contains_fn(&self, hash: NonZeroU64) -> bool {
|
pub fn contains_fn(&self, hash: NonZeroU64) -> bool {
|
||||||
self.0.iter().any(|(_, m)| m.contains_qualified_fn(hash))
|
self.0.iter().any(|(_, m)| m.contains_qualified_fn(hash))
|
||||||
}
|
}
|
||||||
/// Get specified function via its hash key.
|
/// Get specified function via its hash key.
|
||||||
|
#[inline(always)]
|
||||||
pub fn get_fn(&self, hash: NonZeroU64) -> Option<&CallableFunction> {
|
pub fn get_fn(&self, hash: NonZeroU64) -> Option<&CallableFunction> {
|
||||||
self.0
|
self.0
|
||||||
.iter()
|
.iter()
|
||||||
@ -124,10 +136,12 @@ impl Imports {
|
|||||||
}
|
}
|
||||||
/// Does the specified [`TypeId`][std::any::TypeId] iterator exist in this stack of imported [modules][Module]?
|
/// Does the specified [`TypeId`][std::any::TypeId] iterator exist in this stack of imported [modules][Module]?
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
#[inline(always)]
|
||||||
pub fn contains_iter(&self, id: TypeId) -> bool {
|
pub fn contains_iter(&self, id: TypeId) -> bool {
|
||||||
self.0.iter().any(|(_, m)| m.contains_qualified_iter(id))
|
self.0.iter().any(|(_, m)| m.contains_qualified_iter(id))
|
||||||
}
|
}
|
||||||
/// Get the specified [`TypeId`][std::any::TypeId] iterator.
|
/// Get the specified [`TypeId`][std::any::TypeId] iterator.
|
||||||
|
#[inline(always)]
|
||||||
pub fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
|
pub fn get_iter(&self, id: TypeId) -> Option<IteratorFn> {
|
||||||
self.0
|
self.0
|
||||||
.iter()
|
.iter()
|
||||||
@ -137,6 +151,7 @@ impl Imports {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: IntoIterator<Item = (&'a ImmutableString, &'a Shared<Module>)>> From<T> for Imports {
|
impl<'a, T: IntoIterator<Item = (&'a ImmutableString, &'a Shared<Module>)>> From<T> for Imports {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: T) -> Self {
|
fn from(value: T) -> Self {
|
||||||
Self(
|
Self(
|
||||||
value
|
value
|
||||||
@ -147,6 +162,7 @@ impl<'a, T: IntoIterator<Item = (&'a ImmutableString, &'a Shared<Module>)>> From
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl FromIterator<(ImmutableString, Shared<Module>)> for Imports {
|
impl FromIterator<(ImmutableString, Shared<Module>)> for Imports {
|
||||||
|
#[inline(always)]
|
||||||
fn from_iter<T: IntoIterator<Item = (ImmutableString, Shared<Module>)>>(iter: T) -> Self {
|
fn from_iter<T: IntoIterator<Item = (ImmutableString, Shared<Module>)>>(iter: T) -> Self {
|
||||||
Self(iter.into_iter().collect())
|
Self(iter.into_iter().collect())
|
||||||
}
|
}
|
||||||
@ -154,6 +170,7 @@ impl FromIterator<(ImmutableString, Shared<Module>)> for Imports {
|
|||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub const MAX_CALL_STACK_DEPTH: usize = 8;
|
pub const MAX_CALL_STACK_DEPTH: usize = 8;
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
@ -165,6 +182,7 @@ pub const MAX_FUNCTION_EXPR_DEPTH: usize = 16;
|
|||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub const MAX_CALL_STACK_DEPTH: usize = 128;
|
pub const MAX_CALL_STACK_DEPTH: usize = 128;
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
@ -222,6 +240,7 @@ impl IndexChainValue {
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if not `IndexChainValue::Value`.
|
/// Panics if not `IndexChainValue::Value`.
|
||||||
|
#[inline(always)]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
pub fn as_value(self) -> Dynamic {
|
pub fn as_value(self) -> Dynamic {
|
||||||
match self {
|
match self {
|
||||||
@ -234,6 +253,7 @@ impl IndexChainValue {
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if not `IndexChainValue::FnCallArgs`.
|
/// Panics if not `IndexChainValue::FnCallArgs`.
|
||||||
|
#[inline(always)]
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
pub fn as_fn_call_args(self) -> StaticVec<Dynamic> {
|
pub fn as_fn_call_args(self) -> StaticVec<Dynamic> {
|
||||||
match self {
|
match self {
|
||||||
@ -245,6 +265,7 @@ impl IndexChainValue {
|
|||||||
|
|
||||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
impl From<StaticVec<Dynamic>> for IndexChainValue {
|
impl From<StaticVec<Dynamic>> for IndexChainValue {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: StaticVec<Dynamic>) -> Self {
|
fn from(value: StaticVec<Dynamic>) -> Self {
|
||||||
Self::FnCallArgs(value)
|
Self::FnCallArgs(value)
|
||||||
}
|
}
|
||||||
@ -252,6 +273,7 @@ impl From<StaticVec<Dynamic>> for IndexChainValue {
|
|||||||
|
|
||||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||||
impl From<Dynamic> for IndexChainValue {
|
impl From<Dynamic> for IndexChainValue {
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: Dynamic) -> Self {
|
fn from(value: Dynamic) -> Self {
|
||||||
Self::Value(value)
|
Self::Value(value)
|
||||||
}
|
}
|
||||||
@ -507,8 +529,8 @@ impl State {
|
|||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct Limits {
|
pub struct Limits {
|
||||||
/// Maximum levels of call-stack to prevent infinite recursion.
|
/// Maximum levels of call-stack to prevent infinite recursion.
|
||||||
///
|
/// Not available under `no_function`.
|
||||||
/// Defaults to 16 for debug builds and 128 for non-debug builds.
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub max_call_stack_depth: usize,
|
pub max_call_stack_depth: usize,
|
||||||
/// Maximum depth of statements/expressions at global level (0 = unlimited).
|
/// Maximum depth of statements/expressions at global level (0 = unlimited).
|
||||||
pub max_expr_depth: usize,
|
pub max_expr_depth: usize,
|
||||||
@ -789,6 +811,7 @@ impl Engine {
|
|||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
limits: Limits {
|
limits: Limits {
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
|
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
|
||||||
max_expr_depth: MAX_EXPR_DEPTH,
|
max_expr_depth: MAX_EXPR_DEPTH,
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
@ -844,6 +867,7 @@ impl Engine {
|
|||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
limits: Limits {
|
limits: Limits {
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
|
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
|
||||||
max_expr_depth: MAX_EXPR_DEPTH,
|
max_expr_depth: MAX_EXPR_DEPTH,
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
@ -1088,7 +1112,7 @@ impl Engine {
|
|||||||
ChainType::Dot => {
|
ChainType::Dot => {
|
||||||
match rhs {
|
match rhs {
|
||||||
// xxx.fn_name(arg_expr_list)
|
// xxx.fn_name(arg_expr_list)
|
||||||
Expr::FnCall(x, pos) if x.namespace.is_none() => {
|
Expr::FnCall(x, pos) if x.namespace.is_none() && new_val.is_none() => {
|
||||||
let FnCallExpr {
|
let FnCallExpr {
|
||||||
name,
|
name,
|
||||||
hash_script: hash,
|
hash_script: hash,
|
||||||
@ -1102,6 +1126,10 @@ impl Engine {
|
|||||||
level,
|
level,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// xxx.fn_name(...) = ???
|
||||||
|
Expr::FnCall(_, _) if new_val.is_some() => {
|
||||||
|
unreachable!("method call cannot be assigned to")
|
||||||
|
}
|
||||||
// xxx.module::fn_name(...) - syntax error
|
// xxx.module::fn_name(...) - syntax error
|
||||||
Expr::FnCall(_, _) => {
|
Expr::FnCall(_, _) => {
|
||||||
unreachable!("function call in dot chain should not be namespace-qualified")
|
unreachable!("function call in dot chain should not be namespace-qualified")
|
||||||
@ -1578,7 +1606,7 @@ impl Engine {
|
|||||||
let args = &mut [&mut lhs_value.clone(), value];
|
let args = &mut [&mut lhs_value.clone(), value];
|
||||||
|
|
||||||
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
|
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
|
||||||
let hash =
|
let hash_fn =
|
||||||
calc_native_fn_hash(empty(), OP_EQUALS, args.iter().map(|a| a.type_id()))
|
calc_native_fn_hash(empty(), OP_EQUALS, args.iter().map(|a| a.type_id()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@ -1586,7 +1614,8 @@ impl Engine {
|
|||||||
|
|
||||||
if self
|
if self
|
||||||
.call_native_fn(
|
.call_native_fn(
|
||||||
mods, state, lib, OP_EQUALS, hash, args, false, false, pos, def_value,
|
mods, state, lib, OP_EQUALS, hash_fn, args, false, false, pos,
|
||||||
|
def_value,
|
||||||
)?
|
)?
|
||||||
.0
|
.0
|
||||||
.as_bool()
|
.as_bool()
|
||||||
@ -1748,17 +1777,14 @@ impl Engine {
|
|||||||
Expr::StringConstant(x, _) => Ok(x.clone().into()),
|
Expr::StringConstant(x, _) => Ok(x.clone().into()),
|
||||||
Expr::CharConstant(x, _) => Ok((*x).into()),
|
Expr::CharConstant(x, _) => Ok((*x).into()),
|
||||||
Expr::FnPointer(x, _) => Ok(FnPtr::new_unchecked(x.clone(), Default::default()).into()),
|
Expr::FnPointer(x, _) => Ok(FnPtr::new_unchecked(x.clone(), Default::default()).into()),
|
||||||
Expr::Variable(x) if (x.2).name == KEYWORD_THIS => {
|
|
||||||
if let Some(val) = this_ptr {
|
Expr::Variable(x) if (x.2).name == KEYWORD_THIS => this_ptr
|
||||||
Ok(val.clone())
|
.as_deref()
|
||||||
} else {
|
.cloned()
|
||||||
EvalAltResult::ErrorUnboundThis((x.2).pos).into()
|
.ok_or_else(|| EvalAltResult::ErrorUnboundThis((x.2).pos).into()),
|
||||||
}
|
Expr::Variable(_) => self
|
||||||
}
|
.search_namespace(scope, mods, state, lib, this_ptr, expr)
|
||||||
Expr::Variable(_) => {
|
.map(|(val, _)| val.take_or_clone()),
|
||||||
let (val, _) = self.search_namespace(scope, mods, state, lib, this_ptr, expr)?;
|
|
||||||
Ok(val.take_or_clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Statement block
|
// Statement block
|
||||||
Expr::Stmt(x, _) => {
|
Expr::Stmt(x, _) => {
|
||||||
@ -1822,13 +1848,13 @@ impl Engine {
|
|||||||
let FnCallExpr {
|
let FnCallExpr {
|
||||||
name,
|
name,
|
||||||
namespace,
|
namespace,
|
||||||
hash_script: hash,
|
hash_script,
|
||||||
args,
|
args,
|
||||||
def_value,
|
def_value,
|
||||||
..
|
..
|
||||||
} = x.as_ref();
|
} = x.as_ref();
|
||||||
let namespace = namespace.as_ref();
|
let namespace = namespace.as_ref();
|
||||||
let hash = hash.unwrap();
|
let hash = hash_script.unwrap();
|
||||||
let def_value = def_value.as_ref();
|
let def_value = def_value.as_ref();
|
||||||
self.make_qualified_function_call(
|
self.make_qualified_function_call(
|
||||||
scope, mods, state, lib, this_ptr, namespace, name, args, def_value, hash,
|
scope, mods, state, lib, this_ptr, namespace, name, args, def_value, hash,
|
||||||
@ -2111,7 +2137,6 @@ impl Engine {
|
|||||||
)?;
|
)?;
|
||||||
Ok(Dynamic::UNIT)
|
Ok(Dynamic::UNIT)
|
||||||
}
|
}
|
||||||
// Non-lvalue expression (should be caught during parsing)
|
|
||||||
_ => unreachable!("cannot assign to expression: {:?}", lhs_expr),
|
_ => unreachable!("cannot assign to expression: {:?}", lhs_expr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2236,8 +2261,8 @@ impl Engine {
|
|||||||
|
|
||||||
for iter_value in func(iter_obj) {
|
for iter_value in func(iter_obj) {
|
||||||
let loop_var = scope.get_mut_by_index(index);
|
let loop_var = scope.get_mut_by_index(index);
|
||||||
|
|
||||||
let value = iter_value.flatten();
|
let value = iter_value.flatten();
|
||||||
|
|
||||||
if cfg!(not(feature = "no_closure")) && loop_var.is_shared() {
|
if cfg!(not(feature = "no_closure")) && loop_var.is_shared() {
|
||||||
*loop_var.write_lock().unwrap() = value;
|
*loop_var.write_lock().unwrap() = value;
|
||||||
} else {
|
} else {
|
||||||
@ -2272,7 +2297,7 @@ impl Engine {
|
|||||||
|
|
||||||
// Try/Catch statement
|
// Try/Catch statement
|
||||||
Stmt::TryCatch(x, _, _) => {
|
Stmt::TryCatch(x, _, _) => {
|
||||||
let (try_body, var_def, catch_body) = x.as_ref();
|
let (try_body, err_var, catch_body) = x.as_ref();
|
||||||
|
|
||||||
let result = self
|
let result = self
|
||||||
.eval_stmt(scope, mods, state, lib, this_ptr, try_body, level)
|
.eval_stmt(scope, mods, state, lib, this_ptr, try_body, level)
|
||||||
@ -2280,53 +2305,43 @@ impl Engine {
|
|||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => result,
|
Ok(_) => result,
|
||||||
Err(err) => match *err {
|
Err(err) if !err.is_catchable() => Err(err),
|
||||||
mut err @ EvalAltResult::ErrorRuntime(_, _) | mut err
|
Err(mut err) => {
|
||||||
if err.is_catchable() =>
|
let value = match *err {
|
||||||
{
|
EvalAltResult::ErrorRuntime(ref x, _) => x.clone(),
|
||||||
let value = if let EvalAltResult::ErrorRuntime(ref x, _) = err {
|
_ => {
|
||||||
x.clone()
|
|
||||||
} else {
|
|
||||||
err.set_position(Position::NONE);
|
err.set_position(Position::NONE);
|
||||||
err.to_string().into()
|
err.to_string().into()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let orig_scope_len = scope.len();
|
let orig_scope_len = scope.len();
|
||||||
state.scope_level += 1;
|
state.scope_level += 1;
|
||||||
|
|
||||||
if let Some(Ident { name, .. }) = var_def {
|
if let Some(Ident { name, .. }) = err_var {
|
||||||
let var_name: Cow<'_, str> = if state.is_global() {
|
scope.push(unsafe_cast_var_name_to_lifetime(&name), value);
|
||||||
name.to_string().into()
|
|
||||||
} else {
|
|
||||||
unsafe_cast_var_name_to_lifetime(&name).into()
|
|
||||||
};
|
|
||||||
scope.push(var_name, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut result = self
|
let result =
|
||||||
.eval_stmt(scope, mods, state, lib, this_ptr, catch_body, level)
|
self.eval_stmt(scope, mods, state, lib, this_ptr, catch_body, level);
|
||||||
.map(|_| ().into());
|
|
||||||
|
|
||||||
if let Some(result_err) = result.as_ref().err() {
|
|
||||||
if let EvalAltResult::ErrorRuntime(
|
|
||||||
Dynamic(Union::Unit(_, _)),
|
|
||||||
pos,
|
|
||||||
) = result_err.as_ref()
|
|
||||||
{
|
|
||||||
err.set_position(*pos);
|
|
||||||
result = Err(Box::new(err));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
state.scope_level -= 1;
|
state.scope_level -= 1;
|
||||||
scope.rewind(orig_scope_len);
|
scope.rewind(orig_scope_len);
|
||||||
|
|
||||||
result
|
match result {
|
||||||
|
Ok(_) => Ok(Dynamic::UNIT),
|
||||||
|
Err(result_err) => match *result_err {
|
||||||
|
// Re-throw exception
|
||||||
|
EvalAltResult::ErrorRuntime(Dynamic(Union::Unit(_, _)), pos) => {
|
||||||
|
err.set_position(pos);
|
||||||
|
Err(err)
|
||||||
}
|
}
|
||||||
_ => Err(err),
|
_ => Err(result_err),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Return value
|
// Return value
|
||||||
Stmt::Return((ReturnType::Return, pos), Some(expr), _) => EvalAltResult::Return(
|
Stmt::Return((ReturnType::Return, pos), Some(expr), _) => EvalAltResult::Return(
|
||||||
|
@ -971,7 +971,7 @@ impl Engine {
|
|||||||
optimization_level: OptimizationLevel,
|
optimization_level: OptimizationLevel,
|
||||||
) -> Result<AST, ParseError> {
|
) -> Result<AST, ParseError> {
|
||||||
let hash = calc_hash_for_scripts(scripts);
|
let hash = calc_hash_for_scripts(scripts);
|
||||||
let stream = self.lex(scripts, None);
|
let stream = self.lex(scripts);
|
||||||
self.parse(hash, &mut stream.peekable(), scope, optimization_level)
|
self.parse(hash, &mut stream.peekable(), scope, optimization_level)
|
||||||
}
|
}
|
||||||
/// Read the contents of a file into a string.
|
/// Read the contents of a file into a string.
|
||||||
@ -1129,18 +1129,20 @@ impl Engine {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let hash = calc_hash_for_scripts(&scripts);
|
let hash = calc_hash_for_scripts(&scripts);
|
||||||
let stream = self.lex(
|
|
||||||
|
let stream = self.lex_with_map(
|
||||||
&scripts,
|
&scripts,
|
||||||
if has_null {
|
if has_null {
|
||||||
Some(Box::new(|token| match token {
|
|token| match token {
|
||||||
// If `null` is present, make sure `null` is treated as a variable
|
// If `null` is present, make sure `null` is treated as a variable
|
||||||
Token::Reserved(s) if s == "null" => Token::Identifier(s),
|
Token::Reserved(s) if s == "null" => Token::Identifier(s),
|
||||||
_ => token,
|
_ => token,
|
||||||
}))
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
|t| t
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let ast = self.parse_global_expr(
|
let ast = self.parse_global_expr(
|
||||||
hash,
|
hash,
|
||||||
&mut stream.peekable(),
|
&mut stream.peekable(),
|
||||||
@ -1226,7 +1228,7 @@ impl Engine {
|
|||||||
) -> Result<AST, ParseError> {
|
) -> Result<AST, ParseError> {
|
||||||
let scripts = [script];
|
let scripts = [script];
|
||||||
let hash = calc_hash_for_scripts(&scripts);
|
let hash = calc_hash_for_scripts(&scripts);
|
||||||
let stream = self.lex(&scripts, None);
|
let stream = self.lex(&scripts);
|
||||||
|
|
||||||
let mut peekable = stream.peekable();
|
let mut peekable = stream.peekable();
|
||||||
self.parse_global_expr(hash, &mut peekable, scope, self.optimization_level)
|
self.parse_global_expr(hash, &mut peekable, scope, self.optimization_level)
|
||||||
@ -1384,7 +1386,7 @@ impl Engine {
|
|||||||
) -> Result<T, Box<EvalAltResult>> {
|
) -> Result<T, Box<EvalAltResult>> {
|
||||||
let scripts = [script];
|
let scripts = [script];
|
||||||
let hash = calc_hash_for_scripts(&scripts);
|
let hash = calc_hash_for_scripts(&scripts);
|
||||||
let stream = self.lex(&scripts, None);
|
let stream = self.lex(&scripts);
|
||||||
|
|
||||||
// No need to optimize a lone expression
|
// No need to optimize a lone expression
|
||||||
let ast =
|
let ast =
|
||||||
@ -1451,7 +1453,7 @@ impl Engine {
|
|||||||
) -> Result<T, Box<EvalAltResult>> {
|
) -> Result<T, Box<EvalAltResult>> {
|
||||||
let mods = &mut (&self.global_sub_modules).into();
|
let mods = &mut (&self.global_sub_modules).into();
|
||||||
|
|
||||||
let result = self.eval_ast_with_scope_raw(scope, mods, ast)?;
|
let result = self.eval_ast_with_scope_raw(scope, mods, ast, 0)?;
|
||||||
|
|
||||||
let typ = self.map_type_name(result.type_name());
|
let typ = self.map_type_name(result.type_name());
|
||||||
|
|
||||||
@ -1471,12 +1473,13 @@ impl Engine {
|
|||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
mods: &mut Imports,
|
mods: &mut Imports,
|
||||||
ast: &'a AST,
|
ast: &'a AST,
|
||||||
|
level: usize,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
let state = &mut State {
|
let state = &mut State {
|
||||||
source: ast.clone_source(),
|
source: ast.clone_source(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
self.eval_statements_raw(scope, mods, state, ast.statements(), &[ast.lib()])
|
self.eval_statements_raw(scope, mods, state, ast.statements(), &[ast.lib()], level)
|
||||||
}
|
}
|
||||||
/// Evaluate a file, but throw away the result and only return error (if any).
|
/// Evaluate a file, but throw away the result and only return error (if any).
|
||||||
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
/// Useful for when you don't need the result, but still need to keep track of possible errors.
|
||||||
@ -1517,7 +1520,7 @@ impl Engine {
|
|||||||
) -> Result<(), Box<EvalAltResult>> {
|
) -> Result<(), Box<EvalAltResult>> {
|
||||||
let scripts = [script];
|
let scripts = [script];
|
||||||
let hash = calc_hash_for_scripts(&scripts);
|
let hash = calc_hash_for_scripts(&scripts);
|
||||||
let stream = self.lex(&scripts, None);
|
let stream = self.lex(&scripts);
|
||||||
let ast = self.parse(hash, &mut stream.peekable(), scope, self.optimization_level)?;
|
let ast = self.parse(hash, &mut stream.peekable(), scope, self.optimization_level)?;
|
||||||
self.consume_ast_with_scope(scope, &ast)
|
self.consume_ast_with_scope(scope, &ast)
|
||||||
}
|
}
|
||||||
@ -1540,7 +1543,7 @@ impl Engine {
|
|||||||
source: ast.clone_source(),
|
source: ast.clone_source(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
self.eval_statements_raw(scope, mods, state, ast.statements(), &[ast.lib()])?;
|
self.eval_statements_raw(scope, mods, state, ast.statements(), &[ast.lib()], 0)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
/// Call a script function defined in an [`AST`] with multiple arguments.
|
/// Call a script function defined in an [`AST`] with multiple arguments.
|
||||||
|
@ -38,6 +38,7 @@ impl Engine {
|
|||||||
/// Set the maximum levels of function calls allowed for a script in order to avoid
|
/// Set the maximum levels of function calls allowed for a script in order to avoid
|
||||||
/// infinite recursion and stack overflows.
|
/// infinite recursion and stack overflows.
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set_max_call_levels(&mut self, levels: usize) -> &mut Self {
|
pub fn set_max_call_levels(&mut self, levels: usize) -> &mut Self {
|
||||||
self.limits.max_call_stack_depth = levels;
|
self.limits.max_call_stack_depth = levels;
|
||||||
@ -45,6 +46,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
/// The maximum levels of function calls allowed for a script.
|
/// The maximum levels of function calls allowed for a script.
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn max_call_levels(&self) -> usize {
|
pub fn max_call_levels(&self) -> usize {
|
||||||
self.limits.max_call_stack_depth
|
self.limits.max_call_stack_depth
|
||||||
|
@ -77,6 +77,7 @@ impl<'a> ArgBackup<'a> {
|
|||||||
/// This method blindly casts a reference to another lifetime, which saves allocation and string cloning.
|
/// This method blindly casts a reference to another lifetime, which saves allocation and string cloning.
|
||||||
///
|
///
|
||||||
/// If `restore_first_arg` is called before the end of the scope, the shorter lifetime will not leak.
|
/// If `restore_first_arg` is called before the end of the scope, the shorter lifetime will not leak.
|
||||||
|
#[inline(always)]
|
||||||
fn change_first_arg_to_copy(&mut self, normalize: bool, args: &mut FnCallArgs<'a>) {
|
fn change_first_arg_to_copy(&mut self, normalize: bool, args: &mut FnCallArgs<'a>) {
|
||||||
// Only do it for method calls with arguments.
|
// Only do it for method calls with arguments.
|
||||||
if !normalize || args.is_empty() {
|
if !normalize || args.is_empty() {
|
||||||
@ -106,6 +107,7 @@ impl<'a> ArgBackup<'a> {
|
|||||||
///
|
///
|
||||||
/// If `change_first_arg_to_copy` has been called, this function **MUST** be called _BEFORE_ exiting
|
/// If `change_first_arg_to_copy` has been called, this function **MUST** be called _BEFORE_ exiting
|
||||||
/// the current scope. Otherwise it is undefined behavior as the shorter lifetime will leak.
|
/// the current scope. Otherwise it is undefined behavior as the shorter lifetime will leak.
|
||||||
|
#[inline(always)]
|
||||||
fn restore_first_arg(&mut self, args: &mut FnCallArgs<'a>) {
|
fn restore_first_arg(&mut self, args: &mut FnCallArgs<'a>) {
|
||||||
if let Some(this_pointer) = self.orig_mut.take() {
|
if let Some(this_pointer) = self.orig_mut.take() {
|
||||||
args[0] = this_pointer;
|
args[0] = this_pointer;
|
||||||
@ -114,6 +116,7 @@ impl<'a> ArgBackup<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for ArgBackup<'_> {
|
impl Drop for ArgBackup<'_> {
|
||||||
|
#[inline(always)]
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// Panic if the shorter lifetime leaks.
|
// Panic if the shorter lifetime leaks.
|
||||||
assert!(
|
assert!(
|
||||||
@ -402,11 +405,11 @@ impl Engine {
|
|||||||
mods.extend(fn_def.mods.iter_raw().map(|(n, m)| (n.clone(), m.clone())));
|
mods.extend(fn_def.mods.iter_raw().map(|(n, m)| (n.clone(), m.clone())));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate the function at one higher level of call depth
|
// Evaluate the function
|
||||||
let stmt = &fn_def.body;
|
let stmt = &fn_def.body;
|
||||||
|
|
||||||
let result = self
|
let result = self
|
||||||
.eval_stmt(scope, mods, state, unified_lib, this_ptr, stmt, level + 1)
|
.eval_stmt(scope, mods, state, unified_lib, this_ptr, stmt, level)
|
||||||
.or_else(|err| match *err {
|
.or_else(|err| match *err {
|
||||||
// Convert return statement to return value
|
// Convert return statement to return value
|
||||||
EvalAltResult::Return(x, _) => Ok(x),
|
EvalAltResult::Return(x, _) => Ok(x),
|
||||||
@ -433,7 +436,7 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Has a system function an override?
|
// Has a system function an override?
|
||||||
#[inline]
|
#[inline(always)]
|
||||||
pub(crate) fn has_override_by_name_and_arguments(
|
pub(crate) fn has_override_by_name_and_arguments(
|
||||||
&self,
|
&self,
|
||||||
mods: Option<&Imports>,
|
mods: Option<&Imports>,
|
||||||
@ -583,6 +586,8 @@ impl Engine {
|
|||||||
|
|
||||||
mem::swap(&mut state.source, &mut source);
|
mem::swap(&mut state.source, &mut source);
|
||||||
|
|
||||||
|
let level = _level + 1;
|
||||||
|
|
||||||
let result = self.call_script_fn(
|
let result = self.call_script_fn(
|
||||||
scope,
|
scope,
|
||||||
mods,
|
mods,
|
||||||
@ -592,7 +597,7 @@ impl Engine {
|
|||||||
func,
|
func,
|
||||||
rest,
|
rest,
|
||||||
pos,
|
pos,
|
||||||
_level,
|
level,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Restore the original source
|
// Restore the original source
|
||||||
@ -607,8 +612,10 @@ impl Engine {
|
|||||||
|
|
||||||
mem::swap(&mut state.source, &mut source);
|
mem::swap(&mut state.source, &mut source);
|
||||||
|
|
||||||
|
let level = _level + 1;
|
||||||
|
|
||||||
let result = self.call_script_fn(
|
let result = self.call_script_fn(
|
||||||
scope, mods, state, lib, &mut None, func, args, pos, _level,
|
scope, mods, state, lib, &mut None, func, args, pos, level,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Restore the original source
|
// Restore the original source
|
||||||
@ -664,11 +671,12 @@ impl Engine {
|
|||||||
state: &mut State,
|
state: &mut State,
|
||||||
statements: impl IntoIterator<Item = &'a Stmt>,
|
statements: impl IntoIterator<Item = &'a Stmt>,
|
||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
|
level: usize,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
statements
|
statements
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.try_fold(().into(), |_, stmt| {
|
.try_fold(().into(), |_, stmt| {
|
||||||
self.eval_stmt(scope, mods, state, lib, &mut None, stmt, 0)
|
self.eval_stmt(scope, mods, state, lib, &mut None, stmt, level)
|
||||||
})
|
})
|
||||||
.or_else(|err| match *err {
|
.or_else(|err| match *err {
|
||||||
EvalAltResult::Return(out, _) => Ok(out),
|
EvalAltResult::Return(out, _) => Ok(out),
|
||||||
@ -688,7 +696,7 @@ impl Engine {
|
|||||||
lib: &[&Module],
|
lib: &[&Module],
|
||||||
script: &str,
|
script: &str,
|
||||||
pos: Position,
|
pos: Position,
|
||||||
_level: usize,
|
level: usize,
|
||||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
self.inc_operations(state, pos)?;
|
self.inc_operations(state, pos)?;
|
||||||
|
|
||||||
@ -697,13 +705,6 @@ impl Engine {
|
|||||||
return Ok(Dynamic::UNIT);
|
return Ok(Dynamic::UNIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for stack overflow
|
|
||||||
#[cfg(not(feature = "no_function"))]
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
|
||||||
if _level > self.max_call_levels() {
|
|
||||||
return Err(Box::new(EvalAltResult::ErrorStackOverflow(pos)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile the script text
|
// Compile the script text
|
||||||
// No optimizations because we only run it once
|
// No optimizations because we only run it once
|
||||||
let ast = self.compile_with_scope_and_optimization_level(
|
let ast = self.compile_with_scope_and_optimization_level(
|
||||||
@ -724,7 +725,8 @@ impl Engine {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = self.eval_statements_raw(scope, mods, &mut new_state, ast.statements(), lib);
|
let result =
|
||||||
|
self.eval_statements_raw(scope, mods, &mut new_state, ast.statements(), lib, level);
|
||||||
|
|
||||||
state.operations = new_state.operations;
|
state.operations = new_state.operations;
|
||||||
result
|
result
|
||||||
@ -1205,6 +1207,8 @@ impl Engine {
|
|||||||
let mut source = module.id_raw().clone();
|
let mut source = module.id_raw().clone();
|
||||||
mem::swap(&mut state.source, &mut source);
|
mem::swap(&mut state.source, &mut source);
|
||||||
|
|
||||||
|
let level = level + 1;
|
||||||
|
|
||||||
let result = self.call_script_fn(
|
let result = self.call_script_fn(
|
||||||
new_scope, mods, state, lib, &mut None, &fn_def, args, pos, level,
|
new_scope, mods, state, lib, &mut None, &fn_def, args, pos, level,
|
||||||
);
|
);
|
||||||
|
@ -65,6 +65,7 @@ impl<'e, 's, 'a, 'm, 'pm: 'm, M: AsRef<[&'pm Module]> + ?Sized>
|
|||||||
From<(&'e Engine, &'s Option<ImmutableString>, &'a Imports, &'m M)>
|
From<(&'e Engine, &'s Option<ImmutableString>, &'a Imports, &'m M)>
|
||||||
for NativeCallContext<'e, 's, 'a, 'm, 'pm>
|
for NativeCallContext<'e, 's, 'a, 'm, 'pm>
|
||||||
{
|
{
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: (&'e Engine, &'s Option<ImmutableString>, &'a Imports, &'m M)) -> Self {
|
fn from(value: (&'e Engine, &'s Option<ImmutableString>, &'a Imports, &'m M)) -> Self {
|
||||||
Self {
|
Self {
|
||||||
engine: value.0,
|
engine: value.0,
|
||||||
@ -78,6 +79,7 @@ impl<'e, 's, 'a, 'm, 'pm: 'm, M: AsRef<[&'pm Module]> + ?Sized>
|
|||||||
impl<'e, 'm, 'pm: 'm, M: AsRef<[&'pm Module]> + ?Sized> From<(&'e Engine, &'m M)>
|
impl<'e, 'm, 'pm: 'm, M: AsRef<[&'pm Module]> + ?Sized> From<(&'e Engine, &'m M)>
|
||||||
for NativeCallContext<'e, '_, '_, 'm, 'pm>
|
for NativeCallContext<'e, '_, '_, 'm, 'pm>
|
||||||
{
|
{
|
||||||
|
#[inline(always)]
|
||||||
fn from(value: (&'e Engine, &'m M)) -> Self {
|
fn from(value: (&'e Engine, &'m M)) -> Self {
|
||||||
Self {
|
Self {
|
||||||
engine: value.0,
|
engine: value.0,
|
||||||
@ -152,6 +154,7 @@ impl<'e, 's, 'a, 'm, 'pm> NativeCallContext<'e, 's, 'a, 'm, 'pm> {
|
|||||||
///
|
///
|
||||||
/// If `is_method` is [`true`], the first argument is assumed to be passed
|
/// If `is_method` is [`true`], the first argument is assumed to be passed
|
||||||
/// by reference and is not consumed.
|
/// by reference and is not consumed.
|
||||||
|
#[inline(always)]
|
||||||
pub fn call_fn_dynamic_raw(
|
pub fn call_fn_dynamic_raw(
|
||||||
&self,
|
&self,
|
||||||
fn_name: &str,
|
fn_name: &str,
|
||||||
@ -225,6 +228,7 @@ pub struct FnPtr(ImmutableString, StaticVec<Dynamic>);
|
|||||||
|
|
||||||
impl FnPtr {
|
impl FnPtr {
|
||||||
/// Create a new function pointer.
|
/// Create a new function pointer.
|
||||||
|
#[inline(always)]
|
||||||
pub fn new(name: impl Into<ImmutableString>) -> Result<Self, Box<EvalAltResult>> {
|
pub fn new(name: impl Into<ImmutableString>) -> Result<Self, Box<EvalAltResult>> {
|
||||||
name.into().try_into()
|
name.into().try_into()
|
||||||
}
|
}
|
||||||
@ -289,6 +293,7 @@ impl FnPtr {
|
|||||||
/// This is to avoid unnecessarily cloning the arguments.
|
/// This is to avoid unnecessarily cloning the arguments.
|
||||||
/// Do not use the arguments after this call. If they are needed afterwards,
|
/// Do not use the arguments after this call. If they are needed afterwards,
|
||||||
/// clone them _before_ calling this function.
|
/// clone them _before_ calling this function.
|
||||||
|
#[inline(always)]
|
||||||
pub fn call_dynamic(
|
pub fn call_dynamic(
|
||||||
&self,
|
&self,
|
||||||
ctx: NativeCallContext,
|
ctx: NativeCallContext,
|
||||||
@ -306,13 +311,13 @@ impl FnPtr {
|
|||||||
|
|
||||||
let mut args = args_data.iter_mut().collect::<StaticVec<_>>();
|
let mut args = args_data.iter_mut().collect::<StaticVec<_>>();
|
||||||
|
|
||||||
let has_this = this_ptr.is_some();
|
let is_method = this_ptr.is_some();
|
||||||
|
|
||||||
if let Some(obj) = this_ptr {
|
if let Some(obj) = this_ptr {
|
||||||
args.insert(0, obj);
|
args.insert(0, obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.call_fn_dynamic_raw(self.fn_name(), has_this, true, args.as_mut(), None)
|
ctx.call_fn_dynamic_raw(self.fn_name(), is_method, true, args.as_mut(), None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -424,6 +429,7 @@ pub enum CallableFunction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for CallableFunction {
|
impl fmt::Debug for CallableFunction {
|
||||||
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Pure(_) => write!(f, "NativePureFunction"),
|
Self::Pure(_) => write!(f, "NativePureFunction"),
|
||||||
@ -438,6 +444,7 @@ impl fmt::Debug for CallableFunction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for CallableFunction {
|
impl fmt::Display for CallableFunction {
|
||||||
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Pure(_) => write!(f, "NativePureFunction"),
|
Self::Pure(_) => write!(f, "NativePureFunction"),
|
||||||
@ -453,6 +460,7 @@ impl fmt::Display for CallableFunction {
|
|||||||
|
|
||||||
impl CallableFunction {
|
impl CallableFunction {
|
||||||
/// Is this a pure native Rust function?
|
/// Is this a pure native Rust function?
|
||||||
|
#[inline(always)]
|
||||||
pub fn is_pure(&self) -> bool {
|
pub fn is_pure(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Pure(_) => true,
|
Self::Pure(_) => true,
|
||||||
@ -465,6 +473,7 @@ impl CallableFunction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Is this a native Rust method function?
|
/// Is this a native Rust method function?
|
||||||
|
#[inline(always)]
|
||||||
pub fn is_method(&self) -> bool {
|
pub fn is_method(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Method(_) => true,
|
Self::Method(_) => true,
|
||||||
@ -477,6 +486,7 @@ impl CallableFunction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Is this an iterator function?
|
/// Is this an iterator function?
|
||||||
|
#[inline(always)]
|
||||||
pub fn is_iter(&self) -> bool {
|
pub fn is_iter(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Iterator(_) => true,
|
Self::Iterator(_) => true,
|
||||||
@ -487,6 +497,7 @@ impl CallableFunction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Is this a Rhai-scripted function?
|
/// Is this a Rhai-scripted function?
|
||||||
|
#[inline(always)]
|
||||||
pub fn is_script(&self) -> bool {
|
pub fn is_script(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
@ -496,6 +507,7 @@ impl CallableFunction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Is this a plugin function?
|
/// Is this a plugin function?
|
||||||
|
#[inline(always)]
|
||||||
pub fn is_plugin_fn(&self) -> bool {
|
pub fn is_plugin_fn(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Plugin(_) => true,
|
Self::Plugin(_) => true,
|
||||||
@ -506,6 +518,7 @@ impl CallableFunction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Is this a native Rust function?
|
/// Is this a native Rust function?
|
||||||
|
#[inline(always)]
|
||||||
pub fn is_native(&self) -> bool {
|
pub fn is_native(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Pure(_) | Self::Method(_) => true,
|
Self::Pure(_) | Self::Method(_) => true,
|
||||||
@ -517,6 +530,7 @@ impl CallableFunction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Get the access mode.
|
/// Get the access mode.
|
||||||
|
#[inline(always)]
|
||||||
pub fn access(&self) -> FnAccess {
|
pub fn access(&self) -> FnAccess {
|
||||||
match self {
|
match self {
|
||||||
Self::Plugin(_) => FnAccess::Public,
|
Self::Plugin(_) => FnAccess::Public,
|
||||||
@ -532,6 +546,7 @@ impl CallableFunction {
|
|||||||
///
|
///
|
||||||
/// Panics if the [`CallableFunction`] is not [`Pure`][CallableFunction::Pure] or
|
/// Panics if the [`CallableFunction`] is not [`Pure`][CallableFunction::Pure] or
|
||||||
/// [`Method`][CallableFunction::Method].
|
/// [`Method`][CallableFunction::Method].
|
||||||
|
#[inline(always)]
|
||||||
pub fn get_native_fn(&self) -> &FnAny {
|
pub fn get_native_fn(&self) -> &FnAny {
|
||||||
match self {
|
match self {
|
||||||
Self::Pure(f) | Self::Method(f) => f.as_ref(),
|
Self::Pure(f) | Self::Method(f) => f.as_ref(),
|
||||||
@ -547,6 +562,7 @@ impl CallableFunction {
|
|||||||
///
|
///
|
||||||
/// Panics if the [`CallableFunction`] is not [`Script`][CallableFunction::Script].
|
/// Panics if the [`CallableFunction`] is not [`Script`][CallableFunction::Script].
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
#[inline(always)]
|
||||||
pub fn get_fn_def(&self) -> &ScriptFnDef {
|
pub fn get_fn_def(&self) -> &ScriptFnDef {
|
||||||
match self {
|
match self {
|
||||||
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => {
|
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => {
|
||||||
@ -560,6 +576,7 @@ impl CallableFunction {
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if the [`CallableFunction`] is not [`Iterator`][CallableFunction::Iterator].
|
/// Panics if the [`CallableFunction`] is not [`Iterator`][CallableFunction::Iterator].
|
||||||
|
#[inline(always)]
|
||||||
pub fn get_iter_fn(&self) -> IteratorFn {
|
pub fn get_iter_fn(&self) -> IteratorFn {
|
||||||
match self {
|
match self {
|
||||||
Self::Iterator(f) => *f,
|
Self::Iterator(f) => *f,
|
||||||
@ -576,6 +593,7 @@ impl CallableFunction {
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if the [`CallableFunction`] is not [`Plugin`][CallableFunction::Plugin].
|
/// Panics if the [`CallableFunction`] is not [`Plugin`][CallableFunction::Plugin].
|
||||||
|
#[inline(always)]
|
||||||
pub fn get_plugin_fn<'s>(&'s self) -> &FnPlugin {
|
pub fn get_plugin_fn<'s>(&'s self) -> &FnPlugin {
|
||||||
match self {
|
match self {
|
||||||
Self::Plugin(f) => f.as_ref(),
|
Self::Plugin(f) => f.as_ref(),
|
||||||
|
@ -1720,7 +1720,7 @@ impl Module {
|
|||||||
let orig_mods_len = mods.len();
|
let orig_mods_len = mods.len();
|
||||||
|
|
||||||
// Run the script
|
// Run the script
|
||||||
engine.eval_ast_with_scope_raw(&mut scope, &mut mods, &ast)?;
|
engine.eval_ast_with_scope_raw(&mut scope, &mut mods, &ast, 0)?;
|
||||||
|
|
||||||
// Create new module
|
// Create new module
|
||||||
let mut module = Module::new();
|
let mut module = Module::new();
|
||||||
|
144
src/parser.rs
144
src/parser.rs
@ -19,7 +19,7 @@ use crate::stdlib::{
|
|||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
use crate::syntax::{CustomSyntax, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
|
use crate::syntax::{CustomSyntax, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT};
|
||||||
use crate::token::{is_doc_comment, is_keyword_function, is_valid_identifier, Token, TokenStream};
|
use crate::token::{is_keyword_function, is_valid_identifier, Token, TokenStream};
|
||||||
use crate::utils::{get_hasher, StraightHasherBuilder};
|
use crate::utils::{get_hasher, StraightHasherBuilder};
|
||||||
use crate::{
|
use crate::{
|
||||||
calc_script_fn_hash, Dynamic, Engine, ImmutableString, LexError, ParseError, ParseErrorType,
|
calc_script_fn_hash, Dynamic, Engine, ImmutableString, LexError, ParseError, ParseErrorType,
|
||||||
@ -882,7 +882,7 @@ fn parse_switch(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let stmt = parse_stmt(input, state, lib, settings.level_up()).map(Option::unwrap)?;
|
let stmt = parse_stmt(input, state, lib, settings.level_up())?;
|
||||||
|
|
||||||
let need_comma = !stmt.is_self_terminated();
|
let need_comma = !stmt.is_self_terminated();
|
||||||
|
|
||||||
@ -1014,10 +1014,11 @@ fn parse_primary(
|
|||||||
state.access_var(closure, *pos);
|
state.access_var(closure, *pos);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
lib.insert(
|
||||||
// Qualifiers (none) + function name + number of arguments.
|
// Qualifiers (none) + function name + number of arguments.
|
||||||
let hash = calc_script_fn_hash(empty(), &func.name, func.params.len()).unwrap();
|
calc_script_fn_hash(empty(), &func.name, func.params.len()).unwrap(),
|
||||||
|
func,
|
||||||
lib.insert(hash, func);
|
);
|
||||||
|
|
||||||
expr
|
expr
|
||||||
}
|
}
|
||||||
@ -1372,7 +1373,28 @@ fn make_assignment_stmt<'a>(
|
|||||||
rhs: Expr,
|
rhs: Expr,
|
||||||
op_pos: Position,
|
op_pos: Position,
|
||||||
) -> Result<Stmt, ParseError> {
|
) -> Result<Stmt, ParseError> {
|
||||||
|
fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Position {
|
||||||
|
match expr {
|
||||||
|
Expr::Index(x, _) | Expr::Dot(x, _) if parent_is_dot => match x.lhs {
|
||||||
|
Expr::Property(_) => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _))),
|
||||||
|
ref e => e.position(),
|
||||||
|
},
|
||||||
|
Expr::Index(x, _) | Expr::Dot(x, _) => match x.lhs {
|
||||||
|
Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"),
|
||||||
|
_ => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _))),
|
||||||
|
},
|
||||||
|
Expr::Property(_) if parent_is_dot => Position::NONE,
|
||||||
|
Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"),
|
||||||
|
e if parent_is_dot => e.position(),
|
||||||
|
_ => Position::NONE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match &lhs {
|
match &lhs {
|
||||||
|
// const_expr = rhs
|
||||||
|
expr if expr.is_constant() => {
|
||||||
|
Err(PERR::AssignmentToConstant("".into()).into_err(lhs.position()))
|
||||||
|
}
|
||||||
// var (non-indexed) = rhs
|
// var (non-indexed) = rhs
|
||||||
Expr::Variable(x) if x.0.is_none() => Ok(Stmt::Assignment(
|
Expr::Variable(x) if x.0.is_none() => Ok(Stmt::Assignment(
|
||||||
Box::new((lhs, fn_name.into(), rhs)),
|
Box::new((lhs, fn_name.into(), rhs)),
|
||||||
@ -1392,8 +1414,10 @@ fn make_assignment_stmt<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// xxx[???] = rhs, xxx.??? = rhs
|
// xxx[???]... = rhs, xxx.prop... = rhs
|
||||||
Expr::Index(x, _) | Expr::Dot(x, _) => match &x.lhs {
|
Expr::Index(x, _) | Expr::Dot(x, _) => {
|
||||||
|
match check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _))) {
|
||||||
|
Position::NONE => match &x.lhs {
|
||||||
// var[???] (non-indexed) = rhs, var.??? (non-indexed) = rhs
|
// var[???] (non-indexed) = rhs, var.??? (non-indexed) = rhs
|
||||||
Expr::Variable(x) if x.0.is_none() => Ok(Stmt::Assignment(
|
Expr::Variable(x) if x.0.is_none() => Ok(Stmt::Assignment(
|
||||||
Box::new((lhs, fn_name.into(), rhs)),
|
Box::new((lhs, fn_name.into(), rhs)),
|
||||||
@ -1414,11 +1438,12 @@ fn make_assignment_stmt<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// expr[???] = rhs, expr.??? = rhs
|
// expr[???] = rhs, expr.??? = rhs
|
||||||
_ => Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(x.lhs.position())),
|
expr => {
|
||||||
|
Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(expr.position()))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// const_expr = rhs
|
pos => Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(pos)),
|
||||||
expr if expr.is_constant() => {
|
}
|
||||||
Err(PERR::AssignmentToConstant("".into()).into_err(lhs.position()))
|
|
||||||
}
|
}
|
||||||
// ??? && ??? = rhs, ??? || ??? = rhs
|
// ??? && ??? = rhs, ??? || ??? = rhs
|
||||||
Expr::And(_, _) | Expr::Or(_, _) => Err(LexError::ImproperSymbol(
|
Expr::And(_, _) | Expr::Or(_, _) => Err(LexError::ImproperSymbol(
|
||||||
@ -2435,7 +2460,11 @@ fn parse_block(
|
|||||||
// Parse statements inside the block
|
// Parse statements inside the block
|
||||||
settings.is_global = false;
|
settings.is_global = false;
|
||||||
|
|
||||||
let stmt = parse_stmt(input, state, lib, settings.level_up()).map(Option::unwrap)?;
|
let stmt = parse_stmt(input, state, lib, settings.level_up())?;
|
||||||
|
|
||||||
|
if stmt.is_noop() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// See if it needs a terminating semicolon
|
// See if it needs a terminating semicolon
|
||||||
let need_semicolon = !stmt.is_self_terminated();
|
let need_semicolon = !stmt.is_self_terminated();
|
||||||
@ -2502,20 +2531,22 @@ fn parse_stmt(
|
|||||||
state: &mut ParseState,
|
state: &mut ParseState,
|
||||||
lib: &mut FunctionsLib,
|
lib: &mut FunctionsLib,
|
||||||
mut settings: ParseSettings,
|
mut settings: ParseSettings,
|
||||||
) -> Result<Option<Stmt>, ParseError> {
|
) -> Result<Stmt, ParseError> {
|
||||||
use AccessMode::{ReadOnly, ReadWrite};
|
use AccessMode::{ReadOnly, ReadWrite};
|
||||||
|
|
||||||
let mut comments: Vec<String> = Default::default();
|
let mut _comments: Vec<String> = Default::default();
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_function"))]
|
||||||
|
{
|
||||||
let mut comments_pos = Position::NONE;
|
let mut comments_pos = Position::NONE;
|
||||||
|
|
||||||
// Handle doc-comments.
|
// Handle doc-comments.
|
||||||
#[cfg(not(feature = "no_function"))]
|
while let (Token::Comment(ref comment), pos) = input.peek().unwrap() {
|
||||||
while let (Token::Comment(ref comment), comment_pos) = input.peek().unwrap() {
|
|
||||||
if comments_pos.is_none() {
|
if comments_pos.is_none() {
|
||||||
comments_pos = *comment_pos;
|
comments_pos = *pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !is_doc_comment(comment) {
|
if !crate::token::is_doc_comment(comment) {
|
||||||
unreachable!("expecting doc-comment, but gets {:?}", comment);
|
unreachable!("expecting doc-comment, but gets {:?}", comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2525,7 +2556,7 @@ fn parse_stmt(
|
|||||||
|
|
||||||
match input.next().unwrap().0 {
|
match input.next().unwrap().0 {
|
||||||
Token::Comment(comment) => {
|
Token::Comment(comment) => {
|
||||||
comments.push(comment);
|
_comments.push(comment);
|
||||||
|
|
||||||
match input.peek().unwrap() {
|
match input.peek().unwrap() {
|
||||||
(Token::Fn, _) | (Token::Private, _) => break,
|
(Token::Fn, _) | (Token::Private, _) => break,
|
||||||
@ -2536,9 +2567,10 @@ fn parse_stmt(
|
|||||||
t => unreachable!("expecting Token::Comment, but gets {:?}", t),
|
t => unreachable!("expecting Token::Comment, but gets {:?}", t),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let (token, token_pos) = match input.peek().unwrap() {
|
let (token, token_pos) = match input.peek().unwrap() {
|
||||||
(Token::EOF, pos) => return Ok(Some(Stmt::Noop(*pos))),
|
(Token::EOF, pos) => return Ok(Stmt::Noop(*pos)),
|
||||||
x => x,
|
x => x,
|
||||||
};
|
};
|
||||||
settings.pos = *token_pos;
|
settings.pos = *token_pos;
|
||||||
@ -2548,10 +2580,10 @@ fn parse_stmt(
|
|||||||
|
|
||||||
match token {
|
match token {
|
||||||
// ; - empty statement
|
// ; - empty statement
|
||||||
Token::SemiColon => Ok(Some(Stmt::Noop(settings.pos))),
|
Token::SemiColon => Ok(Stmt::Noop(settings.pos)),
|
||||||
|
|
||||||
// { - statements block
|
// { - statements block
|
||||||
Token::LeftBrace => Ok(Some(parse_block(input, state, lib, settings.level_up())?)),
|
Token::LeftBrace => Ok(parse_block(input, state, lib, settings.level_up())?),
|
||||||
|
|
||||||
// fn ...
|
// fn ...
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
@ -2589,14 +2621,15 @@ fn parse_stmt(
|
|||||||
pos: pos,
|
pos: pos,
|
||||||
};
|
};
|
||||||
|
|
||||||
let func = parse_fn(input, &mut new_state, lib, access, settings, comments)?;
|
let func = parse_fn(input, &mut new_state, lib, access, settings, _comments)?;
|
||||||
|
|
||||||
|
lib.insert(
|
||||||
// Qualifiers (none) + function name + number of arguments.
|
// Qualifiers (none) + function name + number of arguments.
|
||||||
let hash = calc_script_fn_hash(empty(), &func.name, func.params.len()).unwrap();
|
calc_script_fn_hash(empty(), &func.name, func.params.len()).unwrap(),
|
||||||
|
func,
|
||||||
|
);
|
||||||
|
|
||||||
lib.insert(hash, func);
|
Ok(Stmt::Noop(settings.pos))
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(_, pos) => Err(PERR::MissingToken(
|
(_, pos) => Err(PERR::MissingToken(
|
||||||
@ -2607,21 +2640,19 @@ fn parse_stmt(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Token::If => parse_if(input, state, lib, settings.level_up()).map(Some),
|
Token::If => parse_if(input, state, lib, settings.level_up()),
|
||||||
Token::Switch => parse_switch(input, state, lib, settings.level_up()).map(Some),
|
Token::Switch => parse_switch(input, state, lib, settings.level_up()),
|
||||||
Token::While | Token::Loop => {
|
Token::While | Token::Loop => parse_while_loop(input, state, lib, settings.level_up()),
|
||||||
parse_while_loop(input, state, lib, settings.level_up()).map(Some)
|
Token::Do => parse_do(input, state, lib, settings.level_up()),
|
||||||
}
|
Token::For => parse_for(input, state, lib, settings.level_up()),
|
||||||
Token::Do => parse_do(input, state, lib, settings.level_up()).map(Some),
|
|
||||||
Token::For => parse_for(input, state, lib, settings.level_up()).map(Some),
|
|
||||||
|
|
||||||
Token::Continue if settings.is_breakable => {
|
Token::Continue if settings.is_breakable => {
|
||||||
let pos = eat_token(input, Token::Continue);
|
let pos = eat_token(input, Token::Continue);
|
||||||
Ok(Some(Stmt::Continue(pos)))
|
Ok(Stmt::Continue(pos))
|
||||||
}
|
}
|
||||||
Token::Break if settings.is_breakable => {
|
Token::Break if settings.is_breakable => {
|
||||||
let pos = eat_token(input, Token::Break);
|
let pos = eat_token(input, Token::Break);
|
||||||
Ok(Some(Stmt::Break(pos)))
|
Ok(Stmt::Break(pos))
|
||||||
}
|
}
|
||||||
Token::Continue | Token::Break => Err(PERR::LoopBreak.into_err(settings.pos)),
|
Token::Continue | Token::Break => Err(PERR::LoopBreak.into_err(settings.pos)),
|
||||||
|
|
||||||
@ -2645,43 +2676,35 @@ fn parse_stmt(
|
|||||||
|
|
||||||
match input.peek().unwrap() {
|
match input.peek().unwrap() {
|
||||||
// `return`/`throw` at <EOF>
|
// `return`/`throw` at <EOF>
|
||||||
(Token::EOF, pos) => Ok(Some(Stmt::Return((return_type, token_pos), None, *pos))),
|
(Token::EOF, pos) => Ok(Stmt::Return((return_type, token_pos), None, *pos)),
|
||||||
// `return;` or `throw;`
|
// `return;` or `throw;`
|
||||||
(Token::SemiColon, _) => Ok(Some(Stmt::Return(
|
(Token::SemiColon, _) => {
|
||||||
(return_type, token_pos),
|
Ok(Stmt::Return((return_type, token_pos), None, settings.pos))
|
||||||
None,
|
}
|
||||||
settings.pos,
|
|
||||||
))),
|
|
||||||
// `return` or `throw` with expression
|
// `return` or `throw` with expression
|
||||||
(_, _) => {
|
(_, _) => {
|
||||||
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
let expr = parse_expr(input, state, lib, settings.level_up())?;
|
||||||
let pos = expr.position();
|
let pos = expr.position();
|
||||||
Ok(Some(Stmt::Return(
|
Ok(Stmt::Return((return_type, token_pos), Some(expr), pos))
|
||||||
(return_type, token_pos),
|
|
||||||
Some(expr),
|
|
||||||
pos,
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Token::Try => parse_try_catch(input, state, lib, settings.level_up()).map(Some),
|
Token::Try => parse_try_catch(input, state, lib, settings.level_up()),
|
||||||
|
|
||||||
Token::Let => parse_let(input, state, lib, ReadWrite, false, settings.level_up()).map(Some),
|
Token::Let => parse_let(input, state, lib, ReadWrite, false, settings.level_up()),
|
||||||
Token::Const => {
|
Token::Const => parse_let(input, state, lib, ReadOnly, false, settings.level_up()),
|
||||||
parse_let(input, state, lib, ReadOnly, false, settings.level_up()).map(Some)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Token::Import => parse_import(input, state, lib, settings.level_up()).map(Some),
|
Token::Import => parse_import(input, state, lib, settings.level_up()),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Token::Export if !settings.is_global => Err(PERR::WrongExport.into_err(settings.pos)),
|
Token::Export if !settings.is_global => Err(PERR::WrongExport.into_err(settings.pos)),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_module"))]
|
#[cfg(not(feature = "no_module"))]
|
||||||
Token::Export => parse_export(input, state, lib, settings.level_up()).map(Some),
|
Token::Export => parse_export(input, state, lib, settings.level_up()),
|
||||||
|
|
||||||
_ => parse_expr_stmt(input, state, lib, settings.level_up()).map(Some),
|
_ => parse_expr_stmt(input, state, lib, settings.level_up()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2951,7 +2974,7 @@ fn parse_anon_fn(
|
|||||||
|
|
||||||
// Parse function body
|
// Parse function body
|
||||||
settings.is_breakable = false;
|
settings.is_breakable = false;
|
||||||
let body = parse_stmt(input, state, lib, settings.level_up()).map(Option::unwrap)?;
|
let body = parse_stmt(input, state, lib, settings.level_up())?;
|
||||||
|
|
||||||
// External variables may need to be processed in a consistent order,
|
// External variables may need to be processed in a consistent order,
|
||||||
// so extract them into a list.
|
// so extract them into a list.
|
||||||
@ -3095,10 +3118,11 @@ impl Engine {
|
|||||||
pos: Position::NONE,
|
pos: Position::NONE,
|
||||||
};
|
};
|
||||||
|
|
||||||
let stmt = match parse_stmt(input, &mut state, &mut functions, settings)? {
|
let stmt = parse_stmt(input, &mut state, &mut functions, settings)?;
|
||||||
Some(s) => s,
|
|
||||||
None => continue,
|
if stmt.is_noop() {
|
||||||
};
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let need_semicolon = !stmt.is_self_terminated();
|
let need_semicolon = !stmt.is_self_terminated();
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ pub struct Scope<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Scope<'_> {
|
impl Default for Scope<'_> {
|
||||||
|
#[inline(always)]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
values: Vec::with_capacity(16),
|
values: Vec::with_capacity(16),
|
||||||
|
43
src/token.rs
43
src/token.rs
@ -6,7 +6,6 @@ use crate::engine::{
|
|||||||
};
|
};
|
||||||
use crate::stdlib::{
|
use crate::stdlib::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
boxed::Box,
|
|
||||||
char, fmt, format,
|
char, fmt, format,
|
||||||
iter::Peekable,
|
iter::Peekable,
|
||||||
str::{Chars, FromStr},
|
str::{Chars, FromStr},
|
||||||
@ -1116,7 +1115,7 @@ fn get_next_token_inner(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 0x????, 0o????, 0b????
|
// 0x????, 0o????, 0b????
|
||||||
ch @ 'x' | ch @ 'X' | ch @ 'o' | ch @ 'O' | ch @ 'b' | ch @ 'B'
|
ch @ 'x' | ch @ 'o' | ch @ 'b' | ch @ 'X' | ch @ 'O' | ch @ 'B'
|
||||||
if c == '0' =>
|
if c == '0' =>
|
||||||
{
|
{
|
||||||
result.push(next_char);
|
result.push(next_char);
|
||||||
@ -1126,14 +1125,14 @@ fn get_next_token_inner(
|
|||||||
'x' | 'X' => is_hex_char,
|
'x' | 'X' => is_hex_char,
|
||||||
'o' | 'O' => is_octal_char,
|
'o' | 'O' => is_octal_char,
|
||||||
'b' | 'B' => is_binary_char,
|
'b' | 'B' => is_binary_char,
|
||||||
_ => unreachable!("expecting 'x', 'o' or 'B', but gets {}", ch),
|
_ => unreachable!("expecting 'x', 'o' or 'b', but gets {}", ch),
|
||||||
};
|
};
|
||||||
|
|
||||||
radix_base = Some(match ch {
|
radix_base = Some(match ch {
|
||||||
'x' | 'X' => 16,
|
'x' | 'X' => 16,
|
||||||
'o' | 'O' => 8,
|
'o' | 'O' => 8,
|
||||||
'b' | 'B' => 2,
|
'b' | 'B' => 2,
|
||||||
_ => unreachable!("expecting 'x', 'o' or 'B', but gets {}", ch),
|
_ => unreachable!("expecting 'x', 'o' or 'b', but gets {}", ch),
|
||||||
});
|
});
|
||||||
|
|
||||||
while let Some(next_char_in_escape_seq) = stream.peek_next() {
|
while let Some(next_char_in_escape_seq) = stream.peek_next() {
|
||||||
@ -1184,7 +1183,12 @@ fn get_next_token_inner(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// letter or underscore ...
|
// letter or underscore ...
|
||||||
('A'..='Z', _) | ('a'..='z', _) | ('_', _) => {
|
#[cfg(not(feature = "unicode-xid-ident"))]
|
||||||
|
('a'..='z', _) | ('_', _) | ('A'..='Z', _) => {
|
||||||
|
return get_identifier(stream, pos, start_pos, c);
|
||||||
|
}
|
||||||
|
#[cfg(feature = "unicode-xid-ident")]
|
||||||
|
(ch, _) if unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' => {
|
||||||
return get_identifier(stream, pos, start_pos, c);
|
return get_identifier(stream, pos, start_pos, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1495,10 +1499,7 @@ fn get_next_token_inner(
|
|||||||
('$', _) => return Some((Token::Reserved("$".into()), start_pos)),
|
('$', _) => return Some((Token::Reserved("$".into()), start_pos)),
|
||||||
|
|
||||||
(ch, _) if ch.is_whitespace() => (),
|
(ch, _) if ch.is_whitespace() => (),
|
||||||
#[cfg(feature = "unicode-xid-ident")]
|
|
||||||
(ch, _) if unicode_xid::UnicodeXID::is_xid_start(ch) => {
|
|
||||||
return get_identifier(stream, pos, start_pos, c);
|
|
||||||
}
|
|
||||||
(ch, _) => {
|
(ch, _) => {
|
||||||
return Some((
|
return Some((
|
||||||
Token::LexError(LERR::UnexpectedInput(ch.to_string())),
|
Token::LexError(LERR::UnexpectedInput(ch.to_string())),
|
||||||
@ -1683,8 +1684,8 @@ pub struct TokenIterator<'a, 'e> {
|
|||||||
pos: Position,
|
pos: Position,
|
||||||
/// Input character stream.
|
/// Input character stream.
|
||||||
stream: MultiInputsStream<'a>,
|
stream: MultiInputsStream<'a>,
|
||||||
/// A processor function (if any) that maps a token to another.
|
/// A processor function that maps a token to another.
|
||||||
map: Option<Box<dyn Fn(Token) -> Token>>,
|
map: fn(Token) -> Token,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for TokenIterator<'a, '_> {
|
impl<'a> Iterator for TokenIterator<'a, '_> {
|
||||||
@ -1760,24 +1761,26 @@ impl<'a> Iterator for TokenIterator<'a, '_> {
|
|||||||
|
|
||||||
match token {
|
match token {
|
||||||
None => None,
|
None => None,
|
||||||
Some((token, pos)) => {
|
Some((token, pos)) => Some(((self.map)(token), pos)),
|
||||||
if let Some(ref map) = self.map {
|
|
||||||
Some((map(token), pos))
|
|
||||||
} else {
|
|
||||||
Some((token, pos))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
/// Tokenize an input text stream.
|
/// Tokenize an input text stream.
|
||||||
#[inline]
|
#[inline(always)]
|
||||||
pub fn lex<'a, 'e>(
|
pub fn lex<'a, 'e>(
|
||||||
&'e self,
|
&'e self,
|
||||||
input: impl IntoIterator<Item = &'a &'a str>,
|
input: impl IntoIterator<Item = &'a &'a str>,
|
||||||
map: Option<Box<dyn Fn(Token) -> Token>>,
|
) -> TokenIterator<'a, 'e> {
|
||||||
|
self.lex_with_map(input, |f| f)
|
||||||
|
}
|
||||||
|
/// Tokenize an input text stream with a mapping function.
|
||||||
|
#[inline]
|
||||||
|
pub fn lex_with_map<'a, 'e>(
|
||||||
|
&'e self,
|
||||||
|
input: impl IntoIterator<Item = &'a &'a str>,
|
||||||
|
map: fn(Token) -> Token,
|
||||||
) -> TokenIterator<'a, 'e> {
|
) -> TokenIterator<'a, 'e> {
|
||||||
TokenIterator {
|
TokenIterator {
|
||||||
engine: self,
|
engine: self,
|
||||||
|
@ -54,6 +54,7 @@ impl BuildHasher for StraightHasherBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create an instance of the default hasher.
|
/// Create an instance of the default hasher.
|
||||||
|
#[inline(always)]
|
||||||
pub fn get_hasher() -> impl Hasher {
|
pub fn get_hasher() -> impl Hasher {
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
let s: ahash::AHasher = Default::default();
|
let s: ahash::AHasher = Default::default();
|
||||||
|
82
tests/assignments.rs
Normal file
82
tests/assignments.rs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
use rhai::{Engine, EvalAltResult, ParseErrorType, INT};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_assignments() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let engine = Engine::new();
|
||||||
|
|
||||||
|
assert_eq!(engine.eval::<INT>("let x = 42; x = 123; x")?, 123);
|
||||||
|
assert_eq!(engine.eval::<INT>("let x = 42; x += 123; x")?, 165);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
assert_eq!(engine.eval::<INT>("let x = [42]; x[0] += 123; x[0]")?, 165);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
assert_eq!(engine.eval::<INT>("let x = #{a:42}; x.a += 123; x.a")?, 165);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_assignments_bad_lhs() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let engine = Engine::new();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
*engine.compile(r"(x+y) = 42;").expect_err("should error").0,
|
||||||
|
ParseErrorType::AssignmentToInvalidLHS("".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
*engine.compile(r"foo(x) = 42;").expect_err("should error").0,
|
||||||
|
ParseErrorType::AssignmentToInvalidLHS("".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
*engine.compile(r"true = 42;").expect_err("should error").0,
|
||||||
|
ParseErrorType::AssignmentToConstant("".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
*engine.compile(r"123 = 42;").expect_err("should error").0,
|
||||||
|
ParseErrorType::AssignmentToConstant("".to_string())
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
{
|
||||||
|
assert_eq!(
|
||||||
|
*engine
|
||||||
|
.compile(r"x.foo() = 42;")
|
||||||
|
.expect_err("should error")
|
||||||
|
.0,
|
||||||
|
ParseErrorType::AssignmentToInvalidLHS("".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
*engine
|
||||||
|
.compile(r"x.foo().x.y = 42;")
|
||||||
|
.expect_err("should error")
|
||||||
|
.0,
|
||||||
|
ParseErrorType::AssignmentToInvalidLHS("".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
*engine
|
||||||
|
.compile(r"x.y.z.foo() = 42;")
|
||||||
|
.expect_err("should error")
|
||||||
|
.0,
|
||||||
|
ParseErrorType::AssignmentToInvalidLHS("".to_string())
|
||||||
|
);
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
assert_eq!(
|
||||||
|
*engine
|
||||||
|
.compile(r"x.foo()[0] = 42;")
|
||||||
|
.expect_err("should error")
|
||||||
|
.0,
|
||||||
|
ParseErrorType::AssignmentToInvalidLHS("".to_string())
|
||||||
|
);
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
|
assert_eq!(
|
||||||
|
*engine
|
||||||
|
.compile(r"x[y].z.foo() = 42;")
|
||||||
|
.expect_err("should error")
|
||||||
|
.0,
|
||||||
|
ParseErrorType::AssignmentToInvalidLHS("".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -10,21 +10,24 @@ fn test_stack_overflow_fn_calls() -> Result<(), Box<EvalAltResult>> {
|
|||||||
engine.eval::<INT>(
|
engine.eval::<INT>(
|
||||||
r"
|
r"
|
||||||
fn foo(n) { if n <= 1 { 0 } else { n + foo(n-1) } }
|
fn foo(n) { if n <= 1 { 0 } else { n + foo(n-1) } }
|
||||||
foo(7)
|
foo(6)
|
||||||
",
|
",
|
||||||
)?,
|
)?,
|
||||||
27
|
20
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let max = engine.max_call_levels();
|
||||||
|
|
||||||
#[cfg(not(feature = "unchecked"))]
|
#[cfg(not(feature = "unchecked"))]
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
*engine
|
*engine
|
||||||
.eval::<()>(
|
.eval::<()>(&format!(
|
||||||
r"
|
r"
|
||||||
fn foo(n) { if n == 0 { 0 } else { n + foo(n-1) } }
|
fn foo(n) {{ if n == 0 {{ 0 }} else {{ n + foo(n-1) }} }}
|
||||||
foo(1000)
|
foo({})
|
||||||
"
|
",
|
||||||
)
|
max + 1
|
||||||
|
))
|
||||||
.expect_err("should error"),
|
.expect_err("should error"),
|
||||||
EvalAltResult::ErrorStackOverflow(_)
|
EvalAltResult::ErrorStackOverflow(_)
|
||||||
));
|
));
|
||||||
|
Loading…
Reference in New Issue
Block a user