Merge pull request #422 from schungx/master

Fix optimizer bug.
This commit is contained in:
Stephen Chung 2021-07-01 12:55:29 +08:00 committed by GitHub
commit e98b578ee6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 806 additions and 842 deletions

View File

@ -1,6 +1,31 @@
Rhai Release Notes
==================
Version 1.0.0
=============
The official version `1.0`.
Almost the same version as `0.20.3` but with deprecated API's removed.
Bug fixes
---------
* Fixed infinite loop in certain script optimizations.
Breaking changes
----------------
* All deprecated API's (e.g. the `RegisterFn` and `RegisterResultFn` traits) are removed.
* `Module::set_id` is split into `Module::set_id` and `Module::clear_id` pair.
Enhancements
------------
* New methods `is_odd`, `is_even` for integers, and `is_zero` for all numbers.
* `From<BTreeSet>` and `From<HashSet>` are added for `Dynamic`, which create object maps with `()` values.
Version 0.20.3
==============

View File

@ -3,7 +3,7 @@ members = [".", "codegen"]
[package]
name = "rhai"
version = "0.20.3"
version = "1.0.0"
edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"]
description = "Embedded scripting for Rust"

View File

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

View File

@ -251,20 +251,20 @@ impl AST {
pub fn source(&self) -> Option<&str> {
self.source.as_ref().map(|s| s.as_str())
}
/// Clone the source, if any.
/// Get a reference to the source.
#[inline(always)]
#[must_use]
pub(crate) fn clone_source(&self) -> Option<Identifier> {
self.source.clone()
pub(crate) fn source_raw(&self) -> Option<&Identifier> {
self.source.as_ref()
}
/// Set the source.
#[inline(always)]
pub fn set_source(&mut self, source: impl Into<Identifier>) -> &mut Self {
let source = Some(source.into());
let source = source.into();
Shared::get_mut(&mut self.functions)
.as_mut()
.map(|m| m.set_id(source.clone()));
self.source = source;
self.source = Some(source);
self
}
/// Clear the source.
@ -911,7 +911,7 @@ impl StmtBlock {
/// Get the position of this statements block.
#[inline(always)]
#[must_use]
pub fn position(&self) -> Position {
pub const fn position(&self) -> Position {
self.1
}
/// Get the statements of this statements block.
@ -1044,7 +1044,7 @@ impl Stmt {
/// Is this statement [`Noop`][Stmt::Noop]?
#[inline(always)]
#[must_use]
pub fn is_noop(&self) -> bool {
pub const fn is_noop(&self) -> bool {
match self {
Self::Noop(_) => true,
_ => false,
@ -1052,7 +1052,7 @@ impl Stmt {
}
/// Get the [position][Position] of this statement.
#[must_use]
pub fn position(&self) -> Position {
pub const fn position(&self) -> Position {
match self {
Self::Noop(pos)
| Self::Continue(pos)
@ -1117,7 +1117,7 @@ impl Stmt {
}
/// Does this statement return a value?
#[must_use]
pub fn returns_value(&self) -> bool {
pub const fn returns_value(&self) -> bool {
match self {
Self::If(_, _, _)
| Self::Switch(_, _, _)
@ -1142,12 +1142,12 @@ impl Stmt {
Self::Import(_, _, _) | Self::Export(_, _) => false,
#[cfg(not(feature = "no_closure"))]
Self::Share(_) => unreachable!("Stmt::Share should not be parsed"),
Self::Share(_) => false,
}
}
/// Is this statement self-terminated (i.e. no need for a semicolon terminator)?
#[must_use]
pub fn is_self_terminated(&self) -> bool {
pub const fn is_self_terminated(&self) -> bool {
match self {
Self::If(_, _, _)
| Self::Switch(_, _, _)
@ -1173,7 +1173,7 @@ impl Stmt {
Self::Import(_, _, _) | Self::Export(_, _) => false,
#[cfg(not(feature = "no_closure"))]
Self::Share(_) => unreachable!("Stmt::Share should not be parsed"),
Self::Share(_) => false,
}
}
/// Is this statement _pure_?
@ -1247,7 +1247,7 @@ impl Stmt {
/// All statements following this statement will essentially be dead code.
#[inline(always)]
#[must_use]
pub fn is_control_flow_break(&self) -> bool {
pub const fn is_control_flow_break(&self) -> bool {
match self {
Self::Return(_, _, _) | Self::Break(_) | Self::Continue(_) => true,
_ => false,
@ -1512,7 +1512,7 @@ impl FnCallHashes {
/// Create a [`FnCallHashes`] with only the native Rust hash.
#[inline(always)]
#[must_use]
pub fn from_native(hash: u64) -> Self {
pub const fn from_native(hash: u64) -> Self {
Self {
script: None,
native: hash,
@ -1521,7 +1521,7 @@ impl FnCallHashes {
/// Create a [`FnCallHashes`] with both native Rust and script function hashes set to the same value.
#[inline(always)]
#[must_use]
pub fn from_script(hash: u64) -> Self {
pub const fn from_script(hash: u64) -> Self {
Self {
script: Some(hash),
native: hash,
@ -1530,7 +1530,7 @@ impl FnCallHashes {
/// Create a [`FnCallHashes`] with both native Rust and script function hashes.
#[inline(always)]
#[must_use]
pub fn from_script_and_native(script: u64, native: u64) -> Self {
pub const fn from_script_and_native(script: u64, native: u64) -> Self {
Self {
script: Some(script),
native,
@ -1539,7 +1539,7 @@ impl FnCallHashes {
/// Is this [`FnCallHashes`] native Rust only?
#[inline(always)]
#[must_use]
pub fn is_native_only(&self) -> bool {
pub const fn is_native_only(&self) -> bool {
self.script.is_none()
}
}
@ -1570,7 +1570,7 @@ impl FnCallExpr {
/// Does this function call contain a qualified namespace?
#[inline(always)]
#[must_use]
pub fn is_qualified(&self) -> bool {
pub const fn is_qualified(&self) -> bool {
self.namespace.is_some()
}
/// Convert this into a [`FnCall`][Expr::FnCall].
@ -1683,6 +1683,7 @@ impl<F: Float> FloatWrapper<F> {
/// Minimum floating-point number for natural display before switching to scientific notation.
pub const MIN_NATURAL_FLOAT_FOR_DISPLAY: f32 = 0.0000000000001;
/// Create a new [`FloatWrapper`].
#[inline(always)]
#[must_use]
pub fn new(value: F) -> Self {
@ -1692,6 +1693,7 @@ impl<F: Float> FloatWrapper<F> {
#[cfg(not(feature = "no_float"))]
impl FloatWrapper<FLOAT> {
/// Create a new [`FloatWrapper`].
#[inline(always)]
#[must_use]
pub(crate) const fn const_new(value: FLOAT) -> Self {
@ -1725,7 +1727,7 @@ pub enum Expr {
/// [String][ImmutableString] constant.
StringConstant(ImmutableString, Position),
/// An interpolated [string][ImmutableString].
InterpolatedString(Box<StaticVec<Expr>>),
InterpolatedString(Box<StaticVec<Expr>>, Position),
/// [ expr, ... ]
Array(Box<StaticVec<Expr>>, Position),
/// #{ name:expr, ... }
@ -1796,7 +1798,7 @@ impl fmt::Debug for Expr {
Self::StringConstant(value, _) => write!(f, "{:?}", value),
Self::Unit(_) => f.write_str("()"),
Self::InterpolatedString(x) => {
Self::InterpolatedString(x, _) => {
f.write_str("InterpolatedString")?;
return f.debug_list().entries(x.iter()).finish();
}
@ -1922,13 +1924,13 @@ impl Expr {
#[cfg(not(feature = "no_float"))]
Union::Float(f, _, _) => Self::FloatConstant(f, pos),
_ => Self::DynamicConstant(Box::new(value), pos),
_ => Self::DynamicConstant(value.into(), pos),
}
}
/// Is the expression a simple variable access?
#[inline(always)]
#[must_use]
pub(crate) fn is_variable_access(&self, non_qualified: bool) -> bool {
pub(crate) const fn is_variable_access(&self, non_qualified: bool) -> bool {
match self {
Self::Variable(_, _, x) => !non_qualified || x.1.is_none(),
_ => false,
@ -1946,7 +1948,7 @@ impl Expr {
/// Get the [position][Position] of the expression.
#[inline]
#[must_use]
pub fn position(&self) -> Position {
pub const fn position(&self) -> Position {
match self {
#[cfg(not(feature = "no_float"))]
Self::FloatConstant(_, pos) => *pos,
@ -1962,14 +1964,8 @@ impl Expr {
| Self::Variable(_, pos, _)
| Self::Stack(_, pos)
| Self::FnCall(_, pos)
| Self::Custom(_, pos) => *pos,
Self::InterpolatedString(x) => x
.first()
.expect(
"never fails because an interpolated string always contains at least one item",
)
.position(),
| Self::Custom(_, pos)
| Self::InterpolatedString(_, pos) => *pos,
Self::Property(x) => (x.2).1,
Self::Stmt(x) => x.1,
@ -2001,13 +1997,8 @@ impl Expr {
| Self::Variable(_, pos, _)
| Self::Stack(_, pos)
| Self::FnCall(_, pos)
| Self::Custom(_, pos) => *pos = new_pos,
Self::InterpolatedString(x) => {
x.first_mut()
.expect("never fails because an interpolated string always contains at least one item")
.set_position(new_pos);
}
| Self::Custom(_, pos)
| Self::InterpolatedString(_, pos) => *pos = new_pos,
Self::Property(x) => (x.2).1 = new_pos,
Self::Stmt(x) => x.1 = new_pos,
@ -2022,7 +2013,7 @@ impl Expr {
#[must_use]
pub fn is_pure(&self) -> bool {
match self {
Self::InterpolatedString(x) | Self::Array(x, _) => x.iter().all(Self::is_pure),
Self::InterpolatedString(x, _) | Self::Array(x, _) => x.iter().all(Self::is_pure),
Self::Map(x, _) => x.0.iter().map(|(_, v)| v).all(Self::is_pure),
@ -2038,7 +2029,7 @@ impl Expr {
/// Is the expression the unit `()` literal?
#[inline(always)]
#[must_use]
pub fn is_unit(&self) -> bool {
pub const fn is_unit(&self) -> bool {
match self {
Self::Unit(_) => true,
_ => false,
@ -2060,7 +2051,7 @@ impl Expr {
| Self::Unit(_)
| Self::Stack(_, _) => true,
Self::InterpolatedString(x) | Self::Array(x, _) => x.iter().all(Self::is_constant),
Self::InterpolatedString(x, _) | Self::Array(x, _) => x.iter().all(Self::is_constant),
Self::Map(x, _) => x.0.iter().map(|(_, expr)| expr).all(Self::is_constant),
@ -2070,7 +2061,7 @@ impl Expr {
/// Is a particular [token][Token] allowed as a postfix operator to this expression?
#[inline]
#[must_use]
pub fn is_valid_postfix(&self, token: &Token) -> bool {
pub const fn is_valid_postfix(&self, token: &Token) -> bool {
match token {
#[cfg(not(feature = "no_object"))]
Token::Period => return true,
@ -2090,7 +2081,7 @@ impl Expr {
Self::IntegerConstant(_, _)
| Self::StringConstant(_, _)
| Self::InterpolatedString(_)
| Self::InterpolatedString(_, _)
| Self::FnCall(_, _)
| Self::Stmt(_)
| Self::Dot(_, _)
@ -2120,7 +2111,7 @@ impl Expr {
Self::Custom(_, _) => false,
Self::Stack(_, _) => unreachable!("Expr::Stack should not occur naturally"),
Self::Stack(_, _) => false,
}
}
/// Recursively walk this expression.
@ -2145,7 +2136,7 @@ impl Expr {
}
}
}
Self::InterpolatedString(x) | Self::Array(x, _) => {
Self::InterpolatedString(x, _) | Self::Array(x, _) => {
for e in x.as_ref() {
if !e.walk(path, on_node) {
return false;
@ -2215,7 +2206,7 @@ mod tests {
96
}
);
assert_eq!(size_of::<Scope>(), 160);
assert_eq!(size_of::<Scope>(), 464);
assert_eq!(size_of::<LexError>(), 56);
assert_eq!(
size_of::<ParseError>(),

View File

@ -14,14 +14,21 @@ use std::any::TypeId;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
pub const MARKER_EXPR: &str = "$expr$";
pub const MARKER_BLOCK: &str = "$block$";
pub const MARKER_IDENT: &str = "$ident$";
pub const MARKER_STRING: &str = "$string$";
pub const MARKER_INT: &str = "$int$";
/// Special marker for matching an expression.
pub const CUSTOM_SYNTAX_MARKER_EXPR: &str = "$expr$";
/// Special marker for matching a statements block.
pub const CUSTOM_SYNTAX_MARKER_BLOCK: &str = "$block$";
/// Special marker for matching an identifier.
pub const CUSTOM_SYNTAX_MARKER_IDENT: &str = "$ident$";
/// Special marker for matching a string literal.
pub const CUSTOM_SYNTAX_MARKER_STRING: &str = "$string$";
/// Special marker for matching an integer number.
pub const CUSTOM_SYNTAX_MARKER_INT: &str = "$int$";
/// Special marker for matching a floating-point number.
#[cfg(not(feature = "no_float"))]
pub const MARKER_FLOAT: &str = "$float$";
pub const MARKER_BOOL: &str = "$bool$";
pub const CUSTOM_SYNTAX_MARKER_FLOAT: &str = "$float$";
/// Special marker for matching a boolean value.
pub const CUSTOM_SYNTAX_MARKER_BOOL: &str = "$bool$";
/// A general expression evaluation trait object.
#[cfg(not(feature = "sync"))]
@ -60,13 +67,13 @@ impl Expression<'_> {
/// Get the expression.
#[inline(always)]
#[must_use]
pub(crate) fn expr(&self) -> &Expr {
pub(crate) const fn expr(&self) -> &Expr {
&self.0
}
/// Get the position of this expression.
#[inline(always)]
#[must_use]
pub fn position(&self) -> Position {
pub const fn position(&self) -> Position {
self.0.position()
}
/// Get the value of this expression if it is a literal constant.
@ -120,7 +127,7 @@ impl Expression<'_> {
}
}
impl EvalContext<'_, '_, '_, '_, '_, '_, '_> {
impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_> {
/// Evaluate an [expression tree][Expression].
///
/// # WARNING - Low Level API
@ -185,7 +192,7 @@ impl Engine {
for s in keywords {
let s = s.as_ref().trim();
// skip empty keywords
// Skip empty keywords
if s.is_empty() {
continue;
}
@ -194,15 +201,19 @@ impl Engine {
let seg = match s {
// Markers not in first position
MARKER_IDENT | MARKER_EXPR | MARKER_BLOCK | MARKER_BOOL | MARKER_INT
| MARKER_STRING
CUSTOM_SYNTAX_MARKER_IDENT
| CUSTOM_SYNTAX_MARKER_EXPR
| CUSTOM_SYNTAX_MARKER_BLOCK
| CUSTOM_SYNTAX_MARKER_BOOL
| CUSTOM_SYNTAX_MARKER_INT
| CUSTOM_SYNTAX_MARKER_STRING
if !segments.is_empty() =>
{
s.into()
}
// Markers not in first position
#[cfg(not(feature = "no_float"))]
MARKER_FLOAT if !segments.is_empty() => s.into(),
CUSTOM_SYNTAX_MARKER_FLOAT if !segments.is_empty() => s.into(),
// Standard or reserved keyword/symbol not in first position
s if !segments.is_empty() && token.is_some() => {
// Make it a custom keyword/symbol if it is disabled or reserved
@ -307,11 +318,12 @@ impl Engine {
) -> &mut Self {
self.custom_syntax.insert(
key.into(),
Box::new(CustomSyntax {
CustomSyntax {
parse: Box::new(parse),
func: (Box::new(func) as Box<FnCustomSyntaxEval>).into(),
scope_changed,
}),
}
.into(),
);
self
}

View File

@ -33,6 +33,7 @@ use fmt::Debug;
#[cfg(any(target_arch = "wasm32", target_arch = "wasm64"))]
use instant::Instant;
/// The message: never fails because the type was checked
const CHECKED: &str = "never fails because the type was checked";
mod private {
@ -164,7 +165,7 @@ pub type Tag = i32;
pub type Tag = i16;
/// Default tag value for [`Dynamic`].
const DEFAULT_TAG: Tag = 0;
const DEFAULT_TAG_VALUE: Tag = 0;
/// Dynamic type containing any value.
pub struct Dynamic(pub(crate) Union);
@ -711,8 +712,6 @@ impl fmt::Debug for Dynamic {
let _value_any = (***value).as_any();
let _type_id = _value_any.type_id();
const CHECKED: &str = "never fails because the type was checked";
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
if _type_id == TypeId::of::<u8>() {
@ -819,28 +818,28 @@ impl Default for Dynamic {
impl Dynamic {
/// A [`Dynamic`] containing a `()`.
pub const UNIT: Dynamic = Self(Union::Unit((), DEFAULT_TAG, ReadWrite));
pub const UNIT: Dynamic = Self(Union::Unit((), DEFAULT_TAG_VALUE, ReadWrite));
/// A [`Dynamic`] containing a `true`.
pub const TRUE: Dynamic = Self(Union::Bool(true, DEFAULT_TAG, ReadWrite));
pub const TRUE: Dynamic = Self(Union::Bool(true, DEFAULT_TAG_VALUE, ReadWrite));
/// A [`Dynamic`] containing a [`false`].
pub const FALSE: Dynamic = Self(Union::Bool(false, DEFAULT_TAG, ReadWrite));
pub const FALSE: Dynamic = Self(Union::Bool(false, DEFAULT_TAG_VALUE, ReadWrite));
/// A [`Dynamic`] containing the integer zero.
pub const ZERO: Dynamic = Self(Union::Int(0, DEFAULT_TAG, ReadWrite));
pub const ZERO: Dynamic = Self(Union::Int(0, DEFAULT_TAG_VALUE, ReadWrite));
/// A [`Dynamic`] containing the integer one.
pub const ONE: Dynamic = Self(Union::Int(1, DEFAULT_TAG, ReadWrite));
pub const ONE: Dynamic = Self(Union::Int(1, DEFAULT_TAG_VALUE, ReadWrite));
/// A [`Dynamic`] containing the integer two.
pub const TWO: Dynamic = Self(Union::Int(2, DEFAULT_TAG, ReadWrite));
pub const TWO: Dynamic = Self(Union::Int(2, DEFAULT_TAG_VALUE, ReadWrite));
/// A [`Dynamic`] containing the integer ten.
pub const TEN: Dynamic = Self(Union::Int(10, DEFAULT_TAG, ReadWrite));
pub const TEN: Dynamic = Self(Union::Int(10, DEFAULT_TAG_VALUE, ReadWrite));
/// A [`Dynamic`] containing the integer negative one.
pub const NEGATIVE_ONE: Dynamic = Self(Union::Int(-1, DEFAULT_TAG, ReadWrite));
pub const NEGATIVE_ONE: Dynamic = Self(Union::Int(-1, DEFAULT_TAG_VALUE, ReadWrite));
/// A [`Dynamic`] containing `0.0`.
///
/// Not available under `no_float`.
#[cfg(not(feature = "no_float"))]
pub const FLOAT_ZERO: Dynamic = Self(Union::Float(
FloatWrapper::const_new(0.0),
DEFAULT_TAG,
DEFAULT_TAG_VALUE,
ReadWrite,
));
/// A [`Dynamic`] containing `1.0`.
@ -849,7 +848,7 @@ impl Dynamic {
#[cfg(not(feature = "no_float"))]
pub const FLOAT_ONE: Dynamic = Self(Union::Float(
FloatWrapper::const_new(1.0),
DEFAULT_TAG,
DEFAULT_TAG_VALUE,
ReadWrite,
));
/// A [`Dynamic`] containing `2.0`.
@ -858,7 +857,7 @@ impl Dynamic {
#[cfg(not(feature = "no_float"))]
pub const FLOAT_TWO: Dynamic = Self(Union::Float(
FloatWrapper::const_new(2.0),
DEFAULT_TAG,
DEFAULT_TAG_VALUE,
ReadWrite,
));
/// A [`Dynamic`] containing `10.0`.
@ -867,7 +866,7 @@ impl Dynamic {
#[cfg(not(feature = "no_float"))]
pub const FLOAT_TEN: Dynamic = Self(Union::Float(
FloatWrapper::const_new(10.0),
DEFAULT_TAG,
DEFAULT_TAG_VALUE,
ReadWrite,
));
/// A [`Dynamic`] containing the `-1.0`.
@ -876,7 +875,7 @@ impl Dynamic {
#[cfg(not(feature = "no_float"))]
pub const FLOAT_NEGATIVE_ONE: Dynamic = Self(Union::Float(
FloatWrapper::const_new(-1.0),
DEFAULT_TAG,
DEFAULT_TAG_VALUE,
ReadWrite,
));
@ -1043,8 +1042,6 @@ impl Dynamic {
pub fn from<T: Variant + Clone>(mut value: T) -> Self {
// Coded this way in order to maximally leverage potentials for dead-code removal.
const CHECKED: &str = "never fails because the type was checked";
if TypeId::of::<T>() == TypeId::of::<Dynamic>() {
return unsafe_try_cast::<_, Dynamic>(value).ok().expect(CHECKED);
}
@ -1125,7 +1122,7 @@ impl Dynamic {
Self(Union::Variant(
Box::new(Box::new(value)),
DEFAULT_TAG,
DEFAULT_TAG_VALUE,
ReadWrite,
))
}
@ -1153,7 +1150,7 @@ impl Dynamic {
Union::Shared(_, _, _) => self,
_ => Self(Union::Shared(
crate::Locked::new(self).into(),
DEFAULT_TAG,
DEFAULT_TAG_VALUE,
_access,
)),
}
@ -1825,23 +1822,6 @@ impl Dynamic {
/// Convert the [`Dynamic`] into a [`String`] and return it.
/// If there are other references to the same string, a cloned copy is returned.
/// Returns the name of the actual type if the cast fails.
///
/// # Deprecated
///
/// This method is deprecated and will be removed in the future.
/// Use [`as_string`][Dynamic::as_string] instead.
#[inline(always)]
#[must_use]
#[deprecated(
since = "0.20.3",
note = "this method is deprecated and will be removed in the future"
)]
pub fn take_string(self) -> Result<String, &'static str> {
self.as_string()
}
/// Convert the [`Dynamic`] into a [`String`] and return it.
/// If there are other references to the same string, a cloned copy is returned.
/// Returns the name of the actual type if the cast fails.
#[inline(always)]
#[must_use]
pub fn as_string(self) -> Result<String, &'static str> {
@ -1849,22 +1829,6 @@ impl Dynamic {
}
/// Convert the [`Dynamic`] into an [`ImmutableString`] and return it.
/// Returns the name of the actual type if the cast fails.
///
/// # Deprecated
///
/// This method is deprecated and will be removed in the future.
/// Use [`as_immutable_string`][Dynamic::as_immutable_string] instead.
#[inline(always)]
#[must_use]
#[deprecated(
since = "0.20.3",
note = "this method is deprecated and will be removed in the future"
)]
pub fn take_immutable_string(self) -> Result<ImmutableString, &'static str> {
self.as_immutable_string()
}
/// Convert the [`Dynamic`] into an [`ImmutableString`] and return it.
/// Returns the name of the actual type if the cast fails.
#[inline(always)]
#[must_use]
pub fn as_immutable_string(self) -> Result<ImmutableString, &'static str> {
@ -1890,56 +1854,52 @@ impl Dynamic {
impl From<()> for Dynamic {
#[inline(always)]
fn from(value: ()) -> Self {
Self(Union::Unit(value, DEFAULT_TAG, ReadWrite))
Self(Union::Unit(value, DEFAULT_TAG_VALUE, ReadWrite))
}
}
impl From<bool> for Dynamic {
#[inline(always)]
fn from(value: bool) -> Self {
Self(Union::Bool(value, DEFAULT_TAG, ReadWrite))
Self(Union::Bool(value, DEFAULT_TAG_VALUE, ReadWrite))
}
}
impl From<INT> for Dynamic {
#[inline(always)]
fn from(value: INT) -> Self {
Self(Union::Int(value, DEFAULT_TAG, ReadWrite))
Self(Union::Int(value, DEFAULT_TAG_VALUE, ReadWrite))
}
}
#[cfg(not(feature = "no_float"))]
impl From<FLOAT> for Dynamic {
#[inline(always)]
fn from(value: FLOAT) -> Self {
Self(Union::Float(value.into(), DEFAULT_TAG, ReadWrite))
Self(Union::Float(value.into(), DEFAULT_TAG_VALUE, ReadWrite))
}
}
#[cfg(not(feature = "no_float"))]
impl From<FloatWrapper<FLOAT>> for Dynamic {
#[inline(always)]
fn from(value: FloatWrapper<FLOAT>) -> Self {
Self(Union::Float(value, DEFAULT_TAG, ReadWrite))
Self(Union::Float(value, DEFAULT_TAG_VALUE, ReadWrite))
}
}
#[cfg(feature = "decimal")]
impl From<Decimal> for Dynamic {
#[inline(always)]
fn from(value: Decimal) -> Self {
Self(Union::Decimal(
Box::new(value.into()),
DEFAULT_TAG,
ReadWrite,
))
Self(Union::Decimal(value.into(), DEFAULT_TAG_VALUE, ReadWrite))
}
}
impl From<char> for Dynamic {
#[inline(always)]
fn from(value: char) -> Self {
Self(Union::Char(value, DEFAULT_TAG, ReadWrite))
Self(Union::Char(value, DEFAULT_TAG_VALUE, ReadWrite))
}
}
impl<S: Into<ImmutableString>> From<S> for Dynamic {
#[inline(always)]
fn from(value: S) -> Self {
Self(Union::Str(value.into(), DEFAULT_TAG, ReadWrite))
Self(Union::Str(value.into(), DEFAULT_TAG_VALUE, ReadWrite))
}
}
impl From<&ImmutableString> for Dynamic {
@ -1948,19 +1908,12 @@ impl From<&ImmutableString> for Dynamic {
value.clone().into()
}
}
#[cfg(not(feature = "no_smartstring"))]
impl From<&crate::Identifier> for Dynamic {
#[inline(always)]
fn from(value: &crate::Identifier) -> Self {
value.to_string().into()
}
}
#[cfg(not(feature = "no_index"))]
impl Dynamic {
/// Create a [`Dynamic`] from an [`Array`].
#[inline(always)]
pub(crate) fn from_array(array: Array) -> Self {
Self(Union::Array(Box::new(array), DEFAULT_TAG, ReadWrite))
Self(Union::Array(array.into(), DEFAULT_TAG_VALUE, ReadWrite))
}
}
#[cfg(not(feature = "no_index"))]
@ -1969,7 +1922,7 @@ impl<T: Variant + Clone> From<Vec<T>> for Dynamic {
fn from(value: Vec<T>) -> Self {
Self(Union::Array(
Box::new(value.into_iter().map(Dynamic::from).collect()),
DEFAULT_TAG,
DEFAULT_TAG_VALUE,
ReadWrite,
))
}
@ -1980,7 +1933,7 @@ impl<T: Variant + Clone> From<&[T]> for Dynamic {
fn from(value: &[T]) -> Self {
Self(Union::Array(
Box::new(value.iter().cloned().map(Dynamic::from).collect()),
DEFAULT_TAG,
DEFAULT_TAG_VALUE,
ReadWrite,
))
}
@ -1991,7 +1944,7 @@ impl<T: Variant + Clone> std::iter::FromIterator<T> for Dynamic {
fn from_iter<X: IntoIterator<Item = T>>(iter: X) -> Self {
Self(Union::Array(
Box::new(iter.into_iter().map(Dynamic::from).collect()),
DEFAULT_TAG,
DEFAULT_TAG_VALUE,
ReadWrite,
))
}
@ -2001,7 +1954,7 @@ impl Dynamic {
/// Create a [`Dynamic`] from a [`Map`].
#[inline(always)]
pub(crate) fn from_map(map: Map) -> Self {
Self(Union::Map(Box::new(map), DEFAULT_TAG, ReadWrite))
Self(Union::Map(map.into(), DEFAULT_TAG_VALUE, ReadWrite))
}
}
#[cfg(not(feature = "no_object"))]
@ -2018,7 +1971,24 @@ impl<K: Into<crate::Identifier>, T: Variant + Clone> From<std::collections::Hash
.map(|(k, v)| (k.into(), Dynamic::from(v)))
.collect(),
),
DEFAULT_TAG,
DEFAULT_TAG_VALUE,
ReadWrite,
))
}
}
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_std"))]
impl<K: Into<crate::Identifier>> From<std::collections::HashSet<K>> for Dynamic {
#[inline(always)]
fn from(value: std::collections::HashSet<K>) -> Self {
Self(Union::Map(
Box::new(
value
.into_iter()
.map(|k| (k.into(), Dynamic::UNIT))
.collect(),
),
DEFAULT_TAG_VALUE,
ReadWrite,
))
}
@ -2036,7 +2006,23 @@ impl<K: Into<crate::Identifier>, T: Variant + Clone> From<std::collections::BTre
.map(|(k, v)| (k.into(), Dynamic::from(v)))
.collect(),
),
DEFAULT_TAG,
DEFAULT_TAG_VALUE,
ReadWrite,
))
}
}
#[cfg(not(feature = "no_object"))]
impl<K: Into<crate::Identifier>> From<std::collections::BTreeSet<K>> for Dynamic {
#[inline(always)]
fn from(value: std::collections::BTreeSet<K>) -> Self {
Self(Union::Map(
Box::new(
value
.into_iter()
.map(|k| (k.into(), Dynamic::UNIT))
.collect(),
),
DEFAULT_TAG_VALUE,
ReadWrite,
))
}
@ -2044,26 +2030,26 @@ impl<K: Into<crate::Identifier>, T: Variant + Clone> From<std::collections::BTre
impl From<FnPtr> for Dynamic {
#[inline(always)]
fn from(value: FnPtr) -> Self {
Self(Union::FnPtr(Box::new(value), DEFAULT_TAG, ReadWrite))
Self(Union::FnPtr(value.into(), DEFAULT_TAG_VALUE, ReadWrite))
}
}
impl From<Box<FnPtr>> for Dynamic {
#[inline(always)]
fn from(value: Box<FnPtr>) -> Self {
Self(Union::FnPtr(value, DEFAULT_TAG, ReadWrite))
Self(Union::FnPtr(value, DEFAULT_TAG_VALUE, ReadWrite))
}
}
#[cfg(not(feature = "no_std"))]
impl From<Instant> for Dynamic {
#[inline(always)]
fn from(value: Instant) -> Self {
Self(Union::TimeStamp(Box::new(value), DEFAULT_TAG, ReadWrite))
Self(Union::TimeStamp(value.into(), DEFAULT_TAG_VALUE, ReadWrite))
}
}
#[cfg(not(feature = "no_closure"))]
impl From<crate::Shared<crate::Locked<Dynamic>>> for Dynamic {
#[inline(always)]
fn from(value: crate::Shared<crate::Locked<Self>>) -> Self {
Self(Union::Shared(value.into(), DEFAULT_TAG, ReadWrite))
Self(Union::Shared(value.into(), DEFAULT_TAG_VALUE, ReadWrite))
}
}

View File

@ -274,50 +274,34 @@ enum ChainArgument {
#[cfg(not(feature = "no_object"))]
Property(Position),
/// Arguments to a dot method call.
/// Wrapped values are the arguments plus the [position][Position] of the first argument.
#[cfg(not(feature = "no_object"))]
MethodCallArgs(StaticVec<Dynamic>, Position),
/// Index value.
/// Index value and [position][Position].
#[cfg(not(feature = "no_index"))]
IndexValue(Dynamic, Position),
}
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
impl ChainArgument {
/// Return the `Dynamic` value.
///
/// # Panics
///
/// Panics if not `ChainArgument::IndexValue`.
/// Return the index value.
#[inline(always)]
#[cfg(not(feature = "no_index"))]
#[must_use]
pub fn as_index_value(self) -> Dynamic {
pub fn as_index_value(self) -> Option<Dynamic> {
match self {
#[cfg(not(feature = "no_object"))]
Self::Property(_) | Self::MethodCallArgs(_, _) => {
panic!("expecting ChainArgument::IndexValue")
}
Self::IndexValue(value, _) => value,
Self::IndexValue(value, _) => Some(value),
_ => None,
}
}
/// Return the `StaticVec<Dynamic>` value.
///
/// # Panics
///
/// Panics if not `ChainArgument::MethodCallArgs`.
/// Return the list of method call arguments.
#[inline(always)]
#[cfg(not(feature = "no_object"))]
#[must_use]
pub fn as_fn_call_args(self) -> (StaticVec<Dynamic>, Position) {
pub fn as_fn_call_args(self) -> Option<(StaticVec<Dynamic>, Position)> {
match self {
Self::Property(_) => {
panic!("expecting ChainArgument::MethodCallArgs")
}
#[cfg(not(feature = "no_index"))]
Self::IndexValue(_, _) => {
panic!("expecting ChainArgument::MethodCallArgs")
}
Self::MethodCallArgs(values, pos) => (values, pos),
Self::MethodCallArgs(values, pos) => Some((values, pos)),
_ => None,
}
}
}
@ -631,7 +615,7 @@ pub struct State {
#[cfg(not(feature = "no_module"))]
pub resolver: Option<Shared<crate::module::resolvers::StaticModuleResolver>>,
/// Function resolution cache and free list.
fn_resolution_caches: (StaticVec<FnResolutionCache>, Vec<FnResolutionCache>),
fn_resolution_caches: StaticVec<FnResolutionCache>,
}
impl State {
@ -645,11 +629,11 @@ impl State {
#[inline(always)]
#[must_use]
pub fn fn_resolution_cache_mut(&mut self) -> &mut FnResolutionCache {
if self.fn_resolution_caches.0.is_empty() {
if self.fn_resolution_caches.is_empty() {
// Push a new function resolution cache if the stack is empty
self.fn_resolution_caches.0.push(BTreeMap::new());
self.fn_resolution_caches.push(Default::default());
}
self.fn_resolution_caches.0.last_mut().expect(
self.fn_resolution_caches.last_mut().expect(
"never fails because there is at least one function resolution cache by this point",
)
}
@ -657,9 +641,7 @@ impl State {
#[allow(dead_code)]
#[inline(always)]
pub fn push_fn_resolution_cache(&mut self) {
self.fn_resolution_caches
.0
.push(self.fn_resolution_caches.1.pop().unwrap_or_default());
self.fn_resolution_caches.push(Default::default());
}
/// Remove the current function resolution cache from the stack and make the last one current.
///
@ -668,13 +650,9 @@ impl State {
/// Panics if there are no more function resolution cache in the stack.
#[inline(always)]
pub fn pop_fn_resolution_cache(&mut self) {
let mut cache = self
.fn_resolution_caches
.0
self.fn_resolution_caches
.pop()
.expect("there should be at least one function resolution cache");
cache.clear();
self.fn_resolution_caches.1.push(cache);
}
}
@ -724,23 +702,44 @@ pub struct Limits {
pub max_map_size: Option<NonZeroUsize>,
}
#[cfg(not(feature = "unchecked"))]
impl Default for Limits {
fn default() -> Self {
Self {
#[cfg(not(feature = "no_function"))]
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
max_expr_depth: NonZeroUsize::new(MAX_EXPR_DEPTH),
#[cfg(not(feature = "no_function"))]
max_function_expr_depth: NonZeroUsize::new(MAX_FUNCTION_EXPR_DEPTH),
max_operations: None,
#[cfg(not(feature = "no_module"))]
max_modules: usize::MAX,
max_string_size: None,
#[cfg(not(feature = "no_index"))]
max_array_size: None,
#[cfg(not(feature = "no_object"))]
max_map_size: None,
}
}
}
/// Context of a script evaluation process.
#[derive(Debug)]
pub struct EvalContext<'a, 'x, 'px, 'm, 's, 't, 'pt> {
pub struct EvalContext<'a, 'x, 'px, 'm, 's, 'b, 't, 'pt> {
pub(crate) engine: &'a Engine,
pub(crate) scope: &'x mut Scope<'px>,
pub(crate) mods: &'m mut Imports,
pub(crate) state: &'s mut State,
pub(crate) lib: &'a [&'a Module],
pub(crate) lib: &'b [&'b Module],
pub(crate) this_ptr: &'t mut Option<&'pt mut Dynamic>,
pub(crate) level: usize,
}
impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_> {
impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, '_> {
/// The current [`Engine`].
#[inline(always)]
#[must_use]
pub fn engine(&self) -> &Engine {
pub const fn engine(&self) -> &Engine {
self.engine
}
/// The current source.
@ -752,7 +751,7 @@ impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_> {
/// The current [`Scope`].
#[inline(always)]
#[must_use]
pub fn scope(&self) -> &Scope {
pub const fn scope(&self) -> &Scope<'px> {
self.scope
}
/// Mutable reference to the current [`Scope`].
@ -774,7 +773,7 @@ impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_> {
#[cfg(not(feature = "no_module"))]
#[inline(always)]
#[must_use]
pub fn imports(&self) -> &Imports {
pub const fn imports(&self) -> &Imports {
self.mods
}
/// Get an iterator over the namespaces containing definition of all script-defined functions.
@ -788,7 +787,7 @@ impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_> {
#[cfg(feature = "internals")]
#[inline(always)]
#[must_use]
pub fn namespaces(&self) -> &[&Module] {
pub const fn namespaces(&self) -> &[&Module] {
self.lib
}
/// The current bound `this` pointer, if any.
@ -800,7 +799,7 @@ impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_> {
/// The current nesting level of function calls.
#[inline(always)]
#[must_use]
pub fn call_level(&self) -> usize {
pub const fn call_level(&self) -> usize {
self.level
}
}
@ -856,9 +855,9 @@ pub struct Engine {
pub(crate) resolve_var: Option<OnVarCallback>,
/// Callback closure for implementing the `print` command.
pub(crate) print: OnPrintCallback,
pub(crate) print: Option<OnPrintCallback>,
/// Callback closure for implementing the `debug` command.
pub(crate) debug: OnDebugCallback,
pub(crate) debug: Option<OnDebugCallback>,
/// Callback closure for progress reporting.
#[cfg(not(feature = "unchecked"))]
pub(crate) progress: Option<crate::fn_native::OnProgressCallback>,
@ -909,86 +908,50 @@ pub fn is_anonymous_fn(fn_name: &str) -> bool {
fn_name.starts_with(FN_ANONYMOUS)
}
/// Print to stdout
/// Print to `stdout`
#[inline(always)]
fn default_print(_s: &str) {
#[allow(unused_variables)]
fn print_to_stdout(s: &str) {
#[cfg(not(feature = "no_std"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
println!("{}", _s);
println!("{}", s);
}
/// Debug to stdout
/// Debug to `stdout`
#[inline(always)]
fn default_debug(_s: &str, _source: Option<&str>, _pos: Position) {
#[allow(unused_variables)]
fn debug_to_stdout(s: &str, source: Option<&str>, pos: Position) {
#[cfg(not(feature = "no_std"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
if let Some(source) = _source {
println!("{}{:?} | {}", source, _pos, _s);
} else if _pos.is_none() {
println!("{}", _s);
if let Some(source) = source {
println!("{}{:?} | {}", source, pos, s);
} else if pos.is_none() {
println!("{}", s);
} else {
println!("{:?} | {}", _pos, _s);
println!("{:?} | {}", pos, s);
}
}
impl Engine {
/// Create a new [`Engine`]
/// Create a new [`Engine`].
#[inline]
#[must_use]
pub fn new() -> Self {
// Create the new scripting Engine
let mut engine = Self {
global_namespace: Default::default(),
global_modules: Default::default(),
global_sub_modules: Default::default(),
let mut engine = Self::new_raw();
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_std"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
module_resolver: Some(Box::new(crate::module::resolvers::FileModuleResolver::new())),
#[cfg(not(feature = "no_module"))]
#[cfg(any(feature = "no_std", target_arch = "wasm32",))]
module_resolver: None,
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_std"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
{
engine.module_resolver =
Some(Box::new(crate::module::resolvers::FileModuleResolver::new()));
}
type_names: Default::default(),
empty_string: Default::default(),
disabled_symbols: Default::default(),
custom_keywords: Default::default(),
custom_syntax: Default::default(),
// default print/debug implementations
engine.print = Some(Box::new(print_to_stdout));
engine.debug = Some(Box::new(debug_to_stdout));
// variable resolver
resolve_var: None,
// default print/debug implementations
print: Box::new(default_print),
debug: Box::new(default_debug),
// progress callback
#[cfg(not(feature = "unchecked"))]
progress: None,
// optimization level
optimization_level: Default::default(),
#[cfg(not(feature = "unchecked"))]
limits: Limits {
#[cfg(not(feature = "no_function"))]
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
max_expr_depth: NonZeroUsize::new(MAX_EXPR_DEPTH),
#[cfg(not(feature = "no_function"))]
max_function_expr_depth: NonZeroUsize::new(MAX_FUNCTION_EXPR_DEPTH),
max_operations: None,
#[cfg(not(feature = "no_module"))]
max_modules: usize::MAX,
max_string_size: None,
#[cfg(not(feature = "no_index"))]
max_array_size: None,
#[cfg(not(feature = "no_object"))]
max_map_size: None,
},
};
engine.global_namespace.internal = true;
engine.register_global_module(StandardPackage::new().as_shared_module());
engine
@ -1016,8 +979,8 @@ impl Engine {
resolve_var: None,
print: Box::new(|_| {}),
debug: Box::new(|_, _, _| {}),
print: None,
debug: None,
#[cfg(not(feature = "unchecked"))]
progress: None,
@ -1025,21 +988,7 @@ impl Engine {
optimization_level: Default::default(),
#[cfg(not(feature = "unchecked"))]
limits: Limits {
#[cfg(not(feature = "no_function"))]
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
max_expr_depth: NonZeroUsize::new(MAX_EXPR_DEPTH),
#[cfg(not(feature = "no_function"))]
max_function_expr_depth: NonZeroUsize::new(MAX_FUNCTION_EXPR_DEPTH),
max_operations: None,
#[cfg(not(feature = "no_module"))]
max_modules: usize::MAX,
max_string_size: None,
#[cfg(not(feature = "no_index"))]
max_array_size: None,
#[cfg(not(feature = "no_object"))]
max_map_size: None,
},
limits: Default::default(),
};
engine.global_namespace.internal = true;
@ -1243,12 +1192,14 @@ impl Engine {
#[cfg(not(feature = "no_index"))]
ChainType::Index => {
let pos = rhs.position();
let idx_val = idx_val
.as_index_value()
.expect("never fails because `chain_type` is `ChainType::Index`");
match rhs {
// xxx[idx].expr... | xxx[idx][expr]...
Expr::Dot(x, x_pos) | Expr::Index(x, x_pos) => {
let idx_pos = x.lhs.position();
let idx_val = idx_val.as_index_value();
let obj_ptr = &mut self.get_indexed_mut(
mods, state, lib, target, idx_val, idx_pos, false, true, level,
)?;
@ -1264,7 +1215,6 @@ impl Engine {
_ if new_val.is_some() => {
let ((new_val, new_pos), (op_info, op_pos)) =
new_val.expect("never fails because `new_val` is `Some`");
let idx_val = idx_val.as_index_value();
let mut idx_val_for_setter = idx_val.clone();
let try_setter = match self.get_indexed_mut(
@ -1306,13 +1256,9 @@ impl Engine {
Ok((Dynamic::UNIT, true))
}
// xxx[rhs]
_ => {
let idx_val = idx_val.as_index_value();
self.get_indexed_mut(
mods, state, lib, target, idx_val, pos, false, true, level,
)
.map(|v| (v.take_or_clone(), false))
}
_ => self
.get_indexed_mut(mods, state, lib, target, idx_val, pos, false, true, level)
.map(|v| (v.take_or_clone(), false)),
}
}
@ -1322,9 +1268,11 @@ impl Engine {
// xxx.fn_name(arg_expr_list)
Expr::FnCall(x, pos) if !x.is_qualified() && new_val.is_none() => {
let FnCallExpr { name, hashes, .. } = x.as_ref();
let args = &mut idx_val.as_fn_call_args();
let call_args = &mut idx_val
.as_fn_call_args()
.expect("never fails because `chain_type` is `ChainType::Dot` with `Expr::FnCallExpr`");
self.make_method_call(
mods, state, lib, name, *hashes, target, args, *pos, level,
mods, state, lib, name, *hashes, target, call_args, *pos, level,
)
}
// xxx.fn_name(...) = ???
@ -1485,9 +1433,11 @@ impl Engine {
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
Expr::FnCall(ref x, pos) if !x.is_qualified() => {
let FnCallExpr { name, hashes, .. } = x.as_ref();
let args = &mut idx_val.as_fn_call_args();
let call_args = &mut idx_val
.as_fn_call_args()
.expect("never fails because `chain_type` is `ChainType::Dot` with `Expr::FnCallExpr`");
let (val, _) = self.make_method_call(
mods, state, lib, name, *hashes, target, args, pos, level,
mods, state, lib, name, *hashes, target, call_args, pos, level,
)?;
val.into()
}
@ -1604,7 +1554,9 @@ impl Engine {
Expr::FnCall(ref f, pos) if !f.is_qualified() => {
let FnCallExpr { name, hashes, .. } = f.as_ref();
let rhs_chain = match_chain_type(rhs);
let args = &mut idx_val.as_fn_call_args();
let args = &mut idx_val
.as_fn_call_args()
.expect("never fails because `chain_type` is `ChainType::Dot` with `Expr::FnCallExpr`");
let (mut val, _) = self.make_method_call(
mods, state, lib, name, *hashes, target, args, pos, level,
)?;
@ -1723,17 +1675,19 @@ impl Engine {
args, constants, ..
} = x.as_ref();
let mut arg_values = StaticVec::with_capacity(args.len());
let mut first_arg_pos = Position::NONE;
for index in 0..args.len() {
let (value, _) = self.get_arg_value(
let (value, pos) = self.get_arg_value(
scope, mods, state, lib, this_ptr, level, args, constants, index,
)?;
arg_values.push(value.flatten());
if index == 0 {
first_arg_pos = pos
}
}
let pos = x.args.get(0).map(Expr::position).unwrap_or_default();
idx_values.push((arg_values, pos).into());
idx_values.push((arg_values, first_arg_pos).into());
}
#[cfg(not(feature = "no_object"))]
Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dot => {
@ -1765,17 +1719,19 @@ impl Engine {
args, constants, ..
} = x.as_ref();
let mut arg_values = StaticVec::with_capacity(args.len());
let mut first_arg_pos = Position::NONE;
for index in 0..args.len() {
let (value, _) = self.get_arg_value(
let (value, pos) = self.get_arg_value(
scope, mods, state, lib, this_ptr, level, args, constants, index,
)?;
arg_values.push(value.flatten());
if index == 0 {
first_arg_pos = pos;
}
}
let pos = x.args.get(0).map(Expr::position).unwrap_or_default();
(arg_values, pos).into()
(arg_values, first_arg_pos).into()
}
#[cfg(not(feature = "no_object"))]
Expr::FnCall(_, _) if _parent_chain_type == ChainType::Dot => {
@ -2045,8 +2001,8 @@ impl Engine {
}
// `... ${...} ...`
Expr::InterpolatedString(x) => {
let mut pos = expr.position();
Expr::InterpolatedString(x, pos) => {
let mut pos = *pos;
let mut result: Dynamic = self.empty_string.clone().into();
for expr in x.iter() {
@ -2761,10 +2717,9 @@ impl Engine {
err_map.insert("message".into(), err.to_string().into());
state
.source
.as_ref()
.map(|source| err_map.insert("source".into(), source.into()));
if let Some(ref source) = state.source {
err_map.insert("source".into(), source.as_str().into());
}
if err_pos.is_none() {
// No position info

View File

@ -903,14 +903,6 @@ impl Engine {
self.global_modules.insert(0, module);
self
}
/// Register a shared [`Module`] into the global namespace of [`Engine`].
/// This function is deprecated and will be removed in the future.
/// Use [`register_global_module`][Engine::register_global_module] instead.
#[inline(always)]
#[deprecated(since = "0.19.9", note = "use `register_global_module` instead")]
pub fn load_package(&mut self, module: impl Into<Shared<Module>>) -> &mut Self {
self.register_global_module(module.into())
}
/// Register a shared [`Module`] as a static module namespace with the [`Engine`].
///
/// Functions marked [`FnNamespace::Global`] and type iterators are exposed to scripts without
@ -999,19 +991,6 @@ impl Engine {
self
}
/// Register a shared [`Module`] as a static module namespace with the [`Engine`].
/// This function is deprecated and will be removed in the future.
/// Use [`register_static_module`][Engine::register_static_module] instead.
#[cfg(not(feature = "no_module"))]
#[inline(always)]
#[deprecated(since = "0.19.9", note = "use `register_static_module` instead")]
pub fn register_module(
&mut self,
name: impl AsRef<str> + Into<Identifier>,
module: impl Into<Shared<Module>>,
) -> &mut Self {
self.register_static_module(name, module.into())
}
/// Compile a string into an [`AST`], which can be used later for evaluation.
///
/// # Example
@ -1335,7 +1314,8 @@ impl Engine {
Self::read_file(path).and_then(|contents| Ok(self.compile_with_scope(scope, &contents)?))
}
/// Parse a JSON string into an [object map][`Map`].
/// This is a light-weight alternative to using, say, [`serde_json`] to deserialize the JSON.
/// This is a light-weight alternative to using, say,
/// [`serde_json`](https://crates.io/crates/serde_json) to deserialize the JSON.
///
/// Not available under `no_object`.
///
@ -1770,7 +1750,7 @@ impl Engine {
level: usize,
) -> RhaiResult {
let mut state: State = Default::default();
state.source = ast.clone_source();
state.source = ast.source_raw().cloned();
#[cfg(not(feature = "no_module"))]
{
state.resolver = ast.resolver();
@ -1852,7 +1832,7 @@ impl Engine {
) -> Result<(), Box<EvalAltResult>> {
let mods = &mut Default::default();
let mut state: State = Default::default();
state.source = ast.clone_source();
state.source = ast.source_raw().cloned();
#[cfg(not(feature = "no_module"))]
{
state.resolver = ast.resolver();
@ -2084,7 +2064,12 @@ impl Engine {
.lib()
.iter_fn()
.filter(|f| f.func.is_script())
.map(|f| f.func.get_fn_def().clone())
.map(|f| {
f.func
.get_script_fn_def()
.expect("never fails because the function is scripted")
.clone()
})
.collect();
#[cfg(feature = "no_function")]
@ -2234,7 +2219,7 @@ impl Engine {
/// ```
#[inline(always)]
pub fn on_print(&mut self, callback: impl Fn(&str) + SendSync + 'static) -> &mut Self {
self.print = Box::new(callback);
self.print = Some(Box::new(callback));
self
}
/// Override default action of `debug` (print to stdout using [`println!`])
@ -2273,7 +2258,7 @@ impl Engine {
&mut self,
callback: impl Fn(&str, Option<&str>, Position) + SendSync + 'static,
) -> &mut Self {
self.debug = Box::new(callback);
self.debug = Some(Box::new(callback));
self
}
}

View File

@ -29,7 +29,7 @@ impl Engine {
#[cfg(not(feature = "no_optimize"))]
#[inline(always)]
#[must_use]
pub fn optimization_level(&self) -> crate::OptimizationLevel {
pub const fn optimization_level(&self) -> crate::OptimizationLevel {
self.optimization_level
}
/// Set the maximum levels of function calls allowed for a script in order to avoid
@ -50,7 +50,7 @@ impl Engine {
#[cfg(not(feature = "no_function"))]
#[inline(always)]
#[must_use]
pub fn max_call_levels(&self) -> usize {
pub const fn max_call_levels(&self) -> usize {
self.limits.max_call_stack_depth
}
/// Set the maximum number of operations allowed for a script to run to avoid
@ -69,8 +69,12 @@ impl Engine {
#[cfg(not(feature = "unchecked"))]
#[inline(always)]
#[must_use]
pub fn max_operations(&self) -> u64 {
self.limits.max_operations.map_or(0, NonZeroU64::get)
pub const fn max_operations(&self) -> u64 {
if let Some(n) = self.limits.max_operations {
n.get()
} else {
0
}
}
/// Set the maximum number of imported [modules][crate::Module] allowed for a script.
///
@ -89,7 +93,7 @@ impl Engine {
#[cfg(not(feature = "no_module"))]
#[inline(always)]
#[must_use]
pub fn max_modules(&self) -> usize {
pub const fn max_modules(&self) -> usize {
self.limits.max_modules
}
/// Set the depth limits for expressions (0 for unlimited).
@ -115,8 +119,12 @@ impl Engine {
#[cfg(not(feature = "unchecked"))]
#[inline(always)]
#[must_use]
pub fn max_expr_depth(&self) -> usize {
self.limits.max_expr_depth.map_or(0, NonZeroUsize::get)
pub const fn max_expr_depth(&self) -> usize {
if let Some(n) = self.limits.max_expr_depth {
n.get()
} else {
0
}
}
/// The depth limit for expressions in functions (0 for unlimited).
///
@ -125,10 +133,12 @@ impl Engine {
#[cfg(not(feature = "no_function"))]
#[inline(always)]
#[must_use]
pub fn max_function_expr_depth(&self) -> usize {
self.limits
.max_function_expr_depth
.map_or(0, NonZeroUsize::get)
pub const fn max_function_expr_depth(&self) -> usize {
if let Some(n) = self.limits.max_function_expr_depth {
n.get()
} else {
0
}
}
/// Set the maximum length of [strings][crate::ImmutableString] (0 for unlimited).
///
@ -145,8 +155,12 @@ impl Engine {
#[cfg(not(feature = "unchecked"))]
#[inline(always)]
#[must_use]
pub fn max_string_size(&self) -> usize {
self.limits.max_string_size.map_or(0, NonZeroUsize::get)
pub const fn max_string_size(&self) -> usize {
if let Some(n) = self.limits.max_string_size {
n.get()
} else {
0
}
}
/// Set the maximum length of [arrays][crate::Array] (0 for unlimited).
///
@ -165,8 +179,12 @@ impl Engine {
#[cfg(not(feature = "no_index"))]
#[inline(always)]
#[must_use]
pub fn max_array_size(&self) -> usize {
self.limits.max_array_size.map_or(0, NonZeroUsize::get)
pub const fn max_array_size(&self) -> usize {
if let Some(n) = self.limits.max_array_size {
n.get()
} else {
0
}
}
/// Set the maximum size of [object maps][crate::Map] (0 for unlimited).
///
@ -185,8 +203,12 @@ impl Engine {
#[cfg(not(feature = "no_object"))]
#[inline(always)]
#[must_use]
pub fn max_map_size(&self) -> usize {
self.limits.max_map_size.map_or(0, NonZeroUsize::get)
pub const fn max_map_size(&self) -> usize {
if let Some(n) = self.limits.max_map_size {
n.get()
} else {
0
}
}
/// Set the module resolution service used by the [`Engine`].
///

View File

@ -93,55 +93,13 @@ pub enum EvalAltResult {
Return(Dynamic, Position),
}
impl EvalAltResult {
#[must_use]
pub(crate) fn desc(&self) -> &str {
match self {
#[allow(deprecated)]
Self::ErrorSystem(_, s) => s.description(),
Self::ErrorParsing(p, _) => p.desc(),
Self::ErrorInFunctionCall(_, _, _, _) => "Error in called function",
Self::ErrorInModule(_, _, _) => "Error in module",
Self::ErrorFunctionNotFound(_, _) => "Function not found",
Self::ErrorUnboundThis(_) => "'this' is not bound",
Self::ErrorMismatchDataType(_, _, _) => "Data type is incorrect",
Self::ErrorIndexingType(_, _) => "No indexer of the appropriate types defined",
Self::ErrorArrayBounds(0, _, _) => "Empty array has nothing to access",
Self::ErrorArrayBounds(_, _, _) => "Array index out of bounds",
Self::ErrorStringBounds(0, _, _) => "Empty string has nothing to index",
Self::ErrorStringBounds(_, _, _) => "String index out of bounds",
Self::ErrorBitFieldBounds(_, _, _) => "Bit-field index out of bounds",
Self::ErrorFor(_) => "For loop expects a type with an iterator defined",
Self::ErrorVariableNotFound(_, _) => "Variable not found",
Self::ErrorModuleNotFound(_, _) => "Module not found",
Self::ErrorDataRace(_, _) => "Data race detected when accessing variable",
Self::ErrorAssignmentToConstant(_, _) => "Cannot modify a constant",
Self::ErrorMismatchOutputType(_, _, _) => "Output type is incorrect",
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
Self::ErrorArithmetic(_, _) => "Arithmetic error",
Self::ErrorTooManyOperations(_) => "Too many operations",
Self::ErrorTooManyModules(_) => "Too many modules imported",
Self::ErrorStackOverflow(_) => "Stack overflow",
Self::ErrorDataTooLarge(_, _) => "Data size exceeds maximum limit",
Self::ErrorTerminated(_, _) => "Script terminated.",
Self::ErrorRuntime(_, _) => "Runtime error",
Self::LoopBreak(true, _) => "Break statement not inside a loop",
Self::LoopBreak(false, _) => "Continue statement not inside a loop",
Self::Return(_, _) => "[Not Error] Function returns value",
}
}
}
impl Error for EvalAltResult {}
impl fmt::Display for EvalAltResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let desc = self.desc();
let pos = self.position();
match self {
Self::ErrorSystem(s, _) if s.is_empty() => f.write_str(desc)?,
Self::ErrorSystem(s, _) => write!(f, "{}: {}", s, desc)?,
Self::ErrorSystem(s, err) if s.is_empty() => write!(f, "{}", err)?,
Self::ErrorSystem(s, err) => write!(f, "{}: {}", s, err)?,
Self::ErrorParsing(p, _) => write!(f, "Syntax error: {}", p)?,
@ -164,32 +122,35 @@ impl fmt::Display for EvalAltResult {
}
Self::ErrorInModule(s, err, _) => write!(f, "Error in module '{}': {}", s, err)?,
Self::ErrorFunctionNotFound(s, _)
| Self::ErrorVariableNotFound(s, _)
| Self::ErrorDataRace(s, _) => write!(f, "{}: {}", desc, s)?,
Self::ErrorModuleNotFound(s, _) => write!(f, "{}: '{}'", desc, s)?,
Self::ErrorFunctionNotFound(s, _) => write!(f, "Function not found: {}", s)?,
Self::ErrorVariableNotFound(s, _) => write!(f, "Variable not found: {}", s)?,
Self::ErrorModuleNotFound(s, _) => write!(f, "Module not found: '{}'", s)?,
Self::ErrorDataRace(s, _) => {
write!(f, "Data race detected when accessing variable: {}", s)?
}
Self::ErrorDotExpr(s, _) if !s.is_empty() => f.write_str(s)?,
Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered for '{}'", s)?,
Self::ErrorUnboundThis(_)
| Self::ErrorFor(_)
| Self::ErrorDotExpr(_, _)
| Self::ErrorTooManyOperations(_)
| Self::ErrorTooManyModules(_)
| Self::ErrorStackOverflow(_)
| Self::ErrorTerminated(_, _) => f.write_str(desc)?,
Self::ErrorUnboundThis(_) => f.write_str("'this' is not bound")?,
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::ErrorTooManyModules(_) => f.write_str("Too many modules imported")?,
Self::ErrorStackOverflow(_) => f.write_str("Stack overflow")?,
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");
write!(f, "{}: {}", desc, if s.is_empty() { desc } else { s })?
if s.is_empty() {
f.write_str("Runtime error")?
} else {
write!(f, "Runtime error: {}", s)?
}
}
Self::ErrorRuntime(d, _) if d.is::<()>() => f.write_str(desc)?,
Self::ErrorRuntime(d, _) => write!(f, "{}: {}", desc, d)?,
Self::ErrorRuntime(d, _) if d.is::<()>() => f.write_str("Runtime error")?,
Self::ErrorRuntime(d, _) => write!(f, "Runtime error: {}", d)?,
Self::ErrorAssignmentToConstant(s, _) => write!(f, "Cannot modify constant {}", s)?,
Self::ErrorMismatchOutputType(s, r, _) => {
@ -206,8 +167,10 @@ impl fmt::Display for EvalAltResult {
}
Self::ErrorArithmetic(s, _) => f.write_str(s)?,
Self::LoopBreak(_, _) => f.write_str(desc)?,
Self::Return(_, _) => f.write_str(desc)?,
Self::LoopBreak(true, _) => f.write_str("Break statement not inside a loop")?,
Self::LoopBreak(false, _) => f.write_str("Continue statement not inside a loop")?,
Self::Return(_, _) => f.write_str("NOT AN ERROR - Function returns value")?,
Self::ErrorArrayBounds(0, index, _) => {
write!(f, "Array index {} out of bounds: array is empty", index)?
@ -244,8 +207,8 @@ impl fmt::Display for EvalAltResult {
}
// Do not write any position if None
if !pos.is_none() {
write!(f, " ({})", pos)?;
if !self.position().is_none() {
write!(f, " ({})", self.position())?;
}
Ok(())
@ -274,19 +237,15 @@ impl EvalAltResult {
///
/// [`LoopBreak`][EvalAltResult::LoopBreak] and [`Return`][EvalAltResult::Return] are pseudo errors.
#[must_use]
pub fn is_pseudo_error(&self) -> bool {
pub const fn is_pseudo_error(&self) -> bool {
match self {
Self::LoopBreak(_, _) | Self::Return(_, _) => true,
_ => false,
}
}
/// Can this error be caught?
///
/// # Panics
///
/// Panics when [`LoopBreak`][EvalAltResult::LoopBreak] or [`Return`][EvalAltResult::Return].
#[must_use]
pub fn is_catchable(&self) -> bool {
pub const fn is_catchable(&self) -> bool {
match self {
Self::ErrorSystem(_, _) => false,
Self::ErrorParsing(_, _) => false,
@ -316,17 +275,12 @@ impl EvalAltResult {
| Self::ErrorDataTooLarge(_, _)
| Self::ErrorTerminated(_, _) => false,
Self::LoopBreak(_, _) => panic!("EvalAltResult::LoopBreak should not occur naturally"),
Self::Return(_, _) => panic!("EvalAltResult::Return should not occur naturally"),
Self::LoopBreak(_, _) | Self::Return(_, _) => false,
}
}
/// Is this error a system exception?
///
/// # Panics
///
/// Panics when [`LoopBreak`][EvalAltResult::LoopBreak] or [`Return`][EvalAltResult::Return].
#[must_use]
pub fn is_system_exception(&self) -> bool {
pub const fn is_system_exception(&self) -> bool {
match self {
Self::ErrorSystem(_, _) => true,
Self::ErrorParsing(_, _) => true,
@ -338,9 +292,6 @@ impl EvalAltResult {
Self::ErrorTerminated(_, _) => true,
Self::LoopBreak(_, _) => panic!("EvalAltResult::LoopBreak should not occur naturally"),
Self::Return(_, _) => panic!("EvalAltResult::Return should not occur naturally"),
_ => false,
}
}
@ -414,7 +365,7 @@ impl EvalAltResult {
}
/// Get the [position][Position] of this error.
#[must_use]
pub fn position(&self) -> Position {
pub const fn position(&self) -> Position {
match self {
Self::ErrorSystem(_, _) => Position::NONE,

View File

@ -43,12 +43,16 @@ impl fmt::Display for LexError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnexpectedInput(s) => write!(f, "Unexpected '{}'", s),
Self::MalformedEscapeSequence(s) => write!(f, "{}: '{}'", self.desc(), s),
Self::MalformedNumber(s) => write!(f, "{}: '{}'", self.desc(), s),
Self::MalformedChar(s) => write!(f, "{}: '{}'", self.desc(), s),
Self::MalformedIdentifier(s) => write!(f, "{}: '{}'", self.desc(), s),
Self::UnterminatedString => f.write_str(self.desc()),
Self::StringTooLong(max) => write!(f, "{} ({})", self.desc(), max),
Self::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{}'", s),
Self::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s),
Self::MalformedChar(s) => write!(f, "Invalid character: '{}'", s),
Self::MalformedIdentifier(s) => write!(f, "Variable name is not proper: '{}'", s),
Self::UnterminatedString => f.write_str("Open string is not terminated"),
Self::StringTooLong(max) => write!(
f,
"Length of string literal exceeds the maximum limit ({})",
max
),
Self::ImproperSymbol(s, d) if d.is_empty() => {
write!(f, "Invalid symbol encountered: '{}'", s)
}
@ -58,19 +62,6 @@ impl fmt::Display for LexError {
}
impl LexError {
#[must_use]
pub(crate) fn desc(&self) -> &str {
match self {
Self::UnexpectedInput(_) => "Unexpected character encountered",
Self::UnterminatedString => "Open string is not terminated",
Self::StringTooLong(_) => "Length of string literal exceeds the maximum limit",
Self::MalformedEscapeSequence(_) => "Invalid escape sequence",
Self::MalformedNumber(_) => "Invalid number",
Self::MalformedChar(_) => "Invalid character",
Self::MalformedIdentifier(_) => "Variable name is not proper",
Self::ImproperSymbol(_, _) => "Invalid symbol encountered",
}
}
/// Convert a [`LexError`] into a [`ParseError`].
#[inline(always)]
#[must_use]
@ -192,44 +183,7 @@ impl ParseErrorType {
#[inline(always)]
#[must_use]
pub(crate) fn into_err(self, pos: Position) -> ParseError {
ParseError(Box::new(self), pos)
}
#[must_use]
pub(crate) fn desc(&self) -> &str {
match self {
Self::UnexpectedEOF => "Script is incomplete",
Self::BadInput(err) => err.desc(),
Self::UnknownOperator(_) => "Unknown operator",
Self::MissingToken(_, _) => "Expecting a certain token that is missing",
Self::MissingSymbol(_) => "Expecting a certain symbol that is missing",
Self::MalformedCallExpr(_) => "Invalid expression in function call arguments",
Self::MalformedIndexExpr(_) => "Invalid index in indexing expression",
Self::MalformedInExpr(_) => "Invalid 'in' expression",
Self::MalformedCapture(_) => "Invalid capturing",
Self::DuplicatedProperty(_) => "Duplicated property in object map literal",
Self::DuplicatedSwitchCase => "Duplicated switch case",
Self::DuplicatedVariable(_) => "Duplicated variable name",
Self::WrongSwitchDefaultCase => "Default switch case is not the last",
Self::WrongSwitchCaseCondition => "Default switch case cannot have condition",
Self::PropertyExpected => "Expecting name of a property",
Self::VariableExpected => "Expecting name of a variable",
Self::Reserved(_) => "Invalid use of reserved keyword",
Self::ExprExpected(_) => "Expecting an expression",
Self::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function",
Self::FnDuplicatedDefinition(_, _) => "Function already exists",
Self::FnMissingName => "Expecting function name in function declaration",
Self::FnMissingParams(_) => "Expecting parameters in function declaration",
Self::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration",
Self::FnMissingBody(_) => "Expecting body statement block for function declaration",
Self::WrongDocComment => "Doc-comment must be followed immediately by a function definition",
Self::WrongExport => "Export statement can only appear at global level",
Self::AssignmentToConstant(_) => "Cannot assign to a constant value",
Self::AssignmentToInvalidLHS(_) => "Expression cannot be assigned to",
Self::ExprTooDeep => "Expression exceeds maximum complexity",
Self::LiteralTooLarge(_, _) => "Literal exceeds maximum limit",
Self::LoopBreak => "Break statement should only be used inside a loop"
}
ParseError(self.into(), pos)
}
}
@ -238,12 +192,14 @@ impl fmt::Display for ParseErrorType {
match self {
Self::BadInput(err) => write!(f, "{}", err),
Self::MalformedCallExpr(s) => f.write_str(if s.is_empty() { self.desc() } else { s }),
Self::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s),
Self::UnknownOperator(s) => write!(f, "Unknown operator: '{}'", s),
Self::MalformedIndexExpr(s) | Self::MalformedInExpr(s) | Self::MalformedCapture(s) => {
f.write_str(if s.is_empty() { self.desc() } else { s })
}
Self::MalformedCallExpr(s) if s.is_empty() => f.write_str("Invalid expression in function call arguments"),
Self::MalformedIndexExpr(s) if s.is_empty() => f.write_str("Invalid index in indexing expression"),
Self::MalformedInExpr(s) if s.is_empty() => f.write_str("Invalid 'in' expression"),
Self::MalformedCapture(s) if s.is_empty() => f.write_str("Invalid capturing"),
Self::MalformedCallExpr(s) | Self::MalformedIndexExpr(s) | Self::MalformedInExpr(s) | Self::MalformedCapture(s) => f.write_str(s),
Self::FnDuplicatedDefinition(s, n) => {
write!(f, "Function '{}' with ", s)?;
@ -253,43 +209,39 @@ impl fmt::Display for ParseErrorType {
_ => write!(f, "{} parameters already exists", n),
}
}
Self::DuplicatedProperty(s) => {
write!(f, "Duplicated property '{}' for object map literal", s)
}
Self::DuplicatedSwitchCase => f.write_str(self.desc()),
Self::FnMissingBody(s) if s.is_empty() => f.write_str("Expecting body statement block for anonymous function"),
Self::FnMissingBody(s) => write!(f, "Expecting body statement block 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::DuplicatedProperty(s) => write!(f, "Duplicated property '{}' for object map literal", s),
Self::DuplicatedSwitchCase => f.write_str("Duplicated switch case"),
Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name '{}'", s),
Self::ExprExpected(s) => write!(f, "Expecting {} expression", s),
Self::FnMissingParams(s) => write!(f, "Expecting parameters for function '{}'", s),
Self::FnMissingBody(s) if s.is_empty() => {
f.write_str("Expecting body statement block for anonymous function")
}
Self::FnMissingBody(s) => {
write!(f, "Expecting body statement block for function '{}'", s)
}
Self::FnDuplicatedParam(s, arg) => {
write!(f, "Duplicated parameter '{}' for function '{}'", arg, s)
}
Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s),
Self::MissingSymbol(s) => f.write_str(s),
Self::AssignmentToConstant(s) if s.is_empty() => f.write_str(self.desc()),
Self::AssignmentToConstant(s) if s.is_empty() => f.write_str("Cannot assign to a constant value"),
Self::AssignmentToConstant(s) => write!(f, "Cannot assign to constant '{}'", s),
Self::AssignmentToInvalidLHS(s) if s.is_empty() => f.write_str(self.desc()),
Self::AssignmentToInvalidLHS(s) if s.is_empty() => f.write_str("Expression cannot be assigned to"),
Self::AssignmentToInvalidLHS(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),
_ => f.write_str(self.desc()),
Self::UnexpectedEOF => f.write_str("Script is incomplete"),
Self::WrongSwitchDefaultCase => f.write_str("Default switch case is not the last"),
Self::WrongSwitchCaseCondition => f.write_str("Default switch case cannot have condition"),
Self::PropertyExpected => f.write_str("Expecting name of a property"),
Self::VariableExpected => f.write_str("Expecting name of a variable"),
Self::WrongFnDefinition => f.write_str("Function definitions must be at global level and cannot be inside a block or another function"),
Self::FnMissingName => f.write_str("Expecting function name in function declaration"),
Self::WrongDocComment => f.write_str("Doc-comment must be followed immediately by a function definition"),
Self::WrongExport => f.write_str("Export statement can only appear at global level"),
Self::ExprTooDeep => f.write_str("Expression exceeds maximum complexity"),
Self::LoopBreak => f.write_str("Break statement should only be used inside a loop"),
}
}
}

View File

@ -4,7 +4,7 @@
#![allow(non_snake_case)]
use crate::dynamic::Variant;
use crate::{Dynamic, StaticVec};
use crate::Dynamic;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -70,13 +70,10 @@ macro_rules! impl_args {
impl<$($p: Variant + Clone),*> FuncArgs for ($($p,)*)
{
#[inline(always)]
#[allow(unused_variables)]
fn parse<CONTAINER: Extend<Dynamic>>(self, container: &mut CONTAINER) {
let ($($p,)*) = self;
let mut _v = StaticVec::new();
$(_v.push($p.into_dynamic());)*
container.extend(_v.into_iter());
$(container.extend(Some($p.into_dynamic()));)*
}
}

View File

@ -14,6 +14,7 @@ use crate::FLOAT;
#[cfg(feature = "decimal")]
use rust_decimal::Decimal;
/// The message: never fails because this is built-in code and the type is already checked
const BUILTIN: &str = "never fails because this is built-in code and the type is already checked";
/// Is the type a numeric type?

View File

@ -302,9 +302,13 @@ impl Engine {
let result = if func.is_plugin_fn() {
func.get_plugin_fn()
.expect("never fails because the function is a plugin")
.call((self, name, source, mods, lib).into(), args)
} else {
func.get_native_fn()((self, name, source, mods, lib).into(), args)
let func = func
.get_native_fn()
.expect("never fails because the function is native");
func((self, name, source, mods, lib).into(), args)
};
// Restore the original reference
@ -315,25 +319,33 @@ impl Engine {
// See if the function match print/debug (which requires special processing)
return Ok(match name {
KEYWORD_PRINT => {
let text = result.as_immutable_string().map_err(|typ| {
EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<ImmutableString>()).into(),
typ.into(),
pos,
)
})?;
((self.print)(&text).into(), false)
if let Some(ref print) = self.print {
let text = result.as_immutable_string().map_err(|typ| {
EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<ImmutableString>()).into(),
typ.into(),
pos,
)
})?;
(print(&text).into(), false)
} else {
(Dynamic::UNIT, false)
}
}
KEYWORD_DEBUG => {
let text = result.as_immutable_string().map_err(|typ| {
EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<ImmutableString>()).into(),
typ.into(),
pos,
)
})?;
let source = state.source.as_ref().map(|s| s.as_str());
((self.debug)(&text, source, pos).into(), false)
if let Some(ref debug) = self.debug {
let text = result.as_immutable_string().map_err(|typ| {
EvalAltResult::ErrorMismatchOutputType(
self.map_type_name(type_name::<ImmutableString>()).into(),
typ.into(),
pos,
)
})?;
let source = state.source.as_ref().map(|s| s.as_str());
(debug(&text, source, pos).into(), false)
} else {
(Dynamic::UNIT, false)
}
}
_ => (result, func.is_method()),
});
@ -681,7 +693,9 @@ impl Engine {
// Script function call
assert!(func.is_script());
let func = func.get_fn_def();
let func = func
.get_script_fn_def()
.expect("never fails because the function is scripted");
if func.body.is_empty() {
return Ok((Dynamic::UNIT, false));
@ -937,7 +951,7 @@ impl Engine {
fn_ptr.clone()
} else {
FnPtr::new_unchecked(
fn_ptr.get_fn_name().clone(),
fn_ptr.fn_name_raw().clone(),
fn_ptr
.curry()
.iter()
@ -967,7 +981,7 @@ impl Engine {
if let Some(val) = map.get(fn_name) {
if let Some(fn_ptr) = val.read_lock::<FnPtr>() {
// Remap the function name
_redirected = fn_ptr.get_fn_name().clone();
_redirected = fn_ptr.fn_name_raw().clone();
fn_name = &_redirected;
// Add curried arguments
if fn_ptr.is_curried() {
@ -1192,7 +1206,7 @@ impl Engine {
}
return result.map_err(|err| {
Box::new(EvalAltResult::ErrorInFunctionCall(
EvalAltResult::ErrorInFunctionCall(
KEYWORD_EVAL.to_string(),
state
.source
@ -1201,7 +1215,8 @@ impl Engine {
.unwrap_or_default(),
err,
pos,
))
)
.into()
});
}
@ -1384,7 +1399,9 @@ impl Engine {
match func {
#[cfg(not(feature = "no_function"))]
Some(f) if f.is_script() => {
let fn_def = f.get_fn_def();
let fn_def = f
.get_script_fn_def()
.expect("never fails because the function is scripted");
if fn_def.body.is_empty() {
Ok(Dynamic::UNIT)
@ -1408,12 +1425,16 @@ impl Engine {
Some(f) if f.is_plugin_fn() => f
.get_plugin_fn()
.expect("never fails because the function is a plugin")
.clone()
.call((self, fn_name, module.id(), &*mods, lib).into(), &mut args)
.map_err(|err| err.fill_position(pos)),
Some(f) if f.is_native() => {
f.get_native_fn()((self, fn_name, module.id(), &*mods, lib).into(), &mut args)
let func = f
.get_native_fn()
.expect("never fails because the function is native");
func((self, fn_name, module.id(), &*mods, lib).into(), &mut args)
.map_err(|err| err.fill_position(pos))
}

View File

@ -52,6 +52,33 @@ pub fn get_hasher() -> ahash::AHasher {
Default::default()
}
/// Calculate a [`u64`] hash key from a namespace-qualified variable name.
///
/// Module names are passed in via `&str` references from an iterator.
/// Parameter types are passed in via [`TypeId`] values from an iterator.
///
/// # Note
///
/// The first module name is skipped. Hashing starts from the _second_ module in the chain.
#[inline]
#[must_use]
pub fn calc_qualified_var_hash<'a>(
modules: impl Iterator<Item = &'a str>,
var_name: impl AsRef<str>,
) -> u64 {
let s = &mut get_hasher();
// We always skip the first module
let mut len = 0;
modules
.inspect(|_| len += 1)
.skip(1)
.for_each(|m| m.hash(s));
len.hash(s);
var_name.as_ref().hash(s);
s.finish()
}
/// Calculate a [`u64`] hash key from a namespace-qualified function name
/// and the number of parameters, but no parameter types.
///
@ -108,6 +135,6 @@ pub fn calc_fn_params_hash(params: impl Iterator<Item = TypeId>) -> u64 {
/// Combine two [`u64`] hashes by taking the XOR of them.
#[inline(always)]
#[must_use]
pub(crate) fn combine_hashes(a: u64, b: u64) -> u64 {
pub(crate) const fn combine_hashes(a: u64, b: u64) -> u64 {
a ^ b
}

View File

@ -89,7 +89,7 @@ impl<'a> NativeCallContext<'a> {
/// Create a new [`NativeCallContext`].
#[inline(always)]
#[must_use]
pub fn new(engine: &'a Engine, fn_name: &'a str, lib: &'a [&Module]) -> Self {
pub const fn new(engine: &'a Engine, fn_name: &'a str, lib: &'a [&Module]) -> Self {
Self {
engine,
fn_name,
@ -106,17 +106,17 @@ impl<'a> NativeCallContext<'a> {
#[cfg(not(feature = "no_module"))]
#[inline(always)]
#[must_use]
pub fn new_with_all_fields(
pub const fn new_with_all_fields(
engine: &'a Engine,
fn_name: &'a str,
source: &'a Option<&str>,
source: Option<&'a str>,
imports: &'a Imports,
lib: &'a [&Module],
) -> Self {
Self {
engine,
fn_name,
source: source.clone(),
source,
mods: Some(imports),
lib,
}
@ -124,19 +124,19 @@ impl<'a> NativeCallContext<'a> {
/// The current [`Engine`].
#[inline(always)]
#[must_use]
pub fn engine(&self) -> &Engine {
pub const fn engine(&self) -> &Engine {
self.engine
}
/// Name of the function called.
#[inline(always)]
#[must_use]
pub fn fn_name(&self) -> &str {
pub const fn fn_name(&self) -> &str {
self.fn_name
}
/// The current source.
#[inline(always)]
#[must_use]
pub fn source(&self) -> Option<&str> {
pub const fn source(&self) -> Option<&str> {
self.source
}
/// Get an iterator over the current set of modules imported via `import` statements.
@ -166,7 +166,7 @@ impl<'a> NativeCallContext<'a> {
#[cfg(not(feature = "no_module"))]
#[inline(always)]
#[must_use]
pub fn imports(&self) -> Option<&Imports> {
pub const fn imports(&self) -> Option<&Imports> {
self.mods
}
/// Get an iterator over the namespaces containing definitions of all script-defined functions.
@ -180,7 +180,7 @@ impl<'a> NativeCallContext<'a> {
#[cfg(feature = "internals")]
#[inline(always)]
#[must_use]
pub fn namespaces(&self) -> &[&Module] {
pub const fn namespaces(&self) -> &[&Module] {
self.lib
}
/// Call a function inside the call context.
@ -457,74 +457,51 @@ impl CallableFunction {
}
}
/// Get a shared reference to a native Rust function.
///
/// # Panics
///
/// Panics if the [`CallableFunction`] is not [`Pure`][CallableFunction::Pure] or
/// [`Method`][CallableFunction::Method].
#[inline(always)]
#[must_use]
pub fn get_native_fn(&self) -> &Shared<FnAny> {
pub fn get_native_fn(&self) -> Option<&Shared<FnAny>> {
match self {
Self::Pure(f) | Self::Method(f) => f,
Self::Iterator(_) | Self::Plugin(_) => panic!("function should be native"),
Self::Pure(f) | Self::Method(f) => Some(f),
Self::Iterator(_) | Self::Plugin(_) => None,
#[cfg(not(feature = "no_function"))]
Self::Script(_) => panic!("function should be native"),
Self::Script(_) => None,
}
}
/// Get a shared reference to a script-defined function definition.
///
/// Not available under `no_function`.
///
/// # Panics
///
/// Panics if the [`CallableFunction`] is not [`Script`][CallableFunction::Script].
#[cfg(not(feature = "no_function"))]
#[inline(always)]
#[must_use]
pub fn get_fn_def(&self) -> &Shared<crate::ast::ScriptFnDef> {
pub const fn get_script_fn_def(&self) -> Option<&Shared<crate::ast::ScriptFnDef>> {
match self {
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => {
panic!("function should be scripted")
}
Self::Script(f) => f,
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) | Self::Plugin(_) => None,
Self::Script(f) => Some(f),
}
}
/// Get a reference to an iterator function.
///
/// # Panics
///
/// Panics if the [`CallableFunction`] is not [`Iterator`][CallableFunction::Iterator].
#[inline(always)]
#[must_use]
pub fn get_iter_fn(&self) -> IteratorFn {
pub fn get_iter_fn(&self) -> Option<IteratorFn> {
match self {
Self::Iterator(f) => *f,
Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => {
panic!("function should an iterator")
}
Self::Iterator(f) => Some(*f),
Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => None,
#[cfg(not(feature = "no_function"))]
Self::Script(_) => panic!("function should be an iterator"),
Self::Script(_) => None,
}
}
/// Get a shared reference to a plugin function.
///
/// # Panics
///
/// Panics if the [`CallableFunction`] is not [`Plugin`][CallableFunction::Plugin].
#[inline(always)]
#[must_use]
pub fn get_plugin_fn<'s>(&'s self) -> &Shared<FnPlugin> {
pub fn get_plugin_fn<'s>(&'s self) -> Option<&Shared<FnPlugin>> {
match self {
Self::Plugin(f) => f,
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => {
panic!("function should a plugin")
}
Self::Plugin(f) => Some(f),
Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => None,
#[cfg(not(feature = "no_function"))]
Self::Script(_) => panic!("function should a plugin"),
Self::Script(_) => None,
}
}
/// Create a new [`CallableFunction::Pure`].

View File

@ -33,12 +33,12 @@ impl FnPtr {
#[inline(always)]
#[must_use]
pub fn fn_name(&self) -> &str {
self.get_fn_name().as_ref()
self.fn_name_raw().as_ref()
}
/// Get the name of the function.
#[inline(always)]
#[must_use]
pub(crate) fn get_fn_name(&self) -> &Identifier {
pub(crate) const fn fn_name_raw(&self) -> &Identifier {
&self.0
}
/// Get the underlying data of the function pointer.

View File

@ -106,12 +106,25 @@ impl From<String> for ImmutableString {
}
}
#[cfg(not(feature = "no_smartstring"))]
impl From<&SmartString> for ImmutableString {
#[inline(always)]
fn from(value: &SmartString) -> Self {
Self(Into::<SmartString>::into(value.as_str()).into())
}
}
#[cfg(not(feature = "no_smartstring"))]
impl From<SmartString> for ImmutableString {
#[inline(always)]
fn from(value: SmartString) -> Self {
Self(value.into())
}
}
impl From<&ImmutableString> for SmartString {
#[inline(always)]
fn from(value: &ImmutableString) -> Self {
value.as_str().into()
}
}
impl From<ImmutableString> for SmartString {
#[inline(always)]
fn from(mut value: ImmutableString) -> Self {

View File

@ -141,22 +141,6 @@ pub use module::{FnNamespace, Module};
pub use scope::Scope;
pub use token::Position;
/// A trait to enable registering Rust functions.
/// This trait is no longer needed and will be removed in the future.
#[deprecated(
since = "0.19.15",
note = "this trait is no longer needed and will be removed in the future"
)]
pub trait RegisterFn {}
/// A trait to enable registering Rust functions.
/// This trait is no longer needed and will be removed in the future.
#[deprecated(
since = "0.19.15",
note = "this trait is no longer needed and will be removed in the future"
)]
pub trait RegisterResultFn {}
/// An identifier in Rhai. [`SmartString`](https://crates.io/crates/smartstring) is used because most
/// identifiers are ASCII and short, fewer than 23 characters, so they can be stored inline.
#[cfg(not(feature = "internals"))]
@ -188,7 +172,8 @@ pub use fn_native::Shared;
use fn_native::Locked;
pub(crate) use fn_hash::{
calc_fn_hash, calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes,
calc_fn_hash, calc_fn_params_hash, calc_qualified_fn_hash, calc_qualified_var_hash,
combine_hashes,
};
pub use rhai_codegen::*;
@ -207,7 +192,7 @@ pub use ast::ScriptFnMetadata;
#[cfg(not(feature = "no_index"))]
pub type Array = Vec<Dynamic>;
/// Hash map of [`Dynamic`] values with [`ImmutableString`] keys.
/// Hash map of [`Dynamic`] values with [`SmartString`](https://crates.io/crates/smartstring) keys.
/// Not available under `no_object`.
#[cfg(not(feature = "no_object"))]
pub type Map = std::collections::BTreeMap<Identifier, Dynamic>;

View File

@ -235,8 +235,7 @@ impl Module {
/// # Example
///
/// ```
/// use rhai::Module;
///
/// # use rhai::Module;
/// let mut module = Module::new();
/// module.set_var("answer", 42_i64);
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
@ -265,10 +264,9 @@ impl Module {
/// # Example
///
/// ```
/// use rhai::Module;
///
/// # use rhai::Module;
/// let mut module = Module::new();
/// module.set_id(Some("hello"));
/// module.set_id("hello");
/// assert_eq!(module.id(), Some("hello"));
/// ```
#[inline(always)]
@ -278,19 +276,9 @@ impl Module {
}
/// Get the ID of the [`Module`] as an [`Identifier`], if any.
///
/// # Example
///
/// ```
/// use rhai::Module;
///
/// let mut module = Module::new();
/// module.set_id(Some("hello"));
/// assert_eq!(module.id_raw().map(|s| s.as_str()), Some("hello"));
/// ```
#[inline(always)]
#[must_use]
pub fn id_raw(&self) -> Option<&Identifier> {
pub(crate) const fn id_raw(&self) -> Option<&Identifier> {
self.id.as_ref()
}
@ -299,15 +287,31 @@ impl Module {
/// # Example
///
/// ```
/// use rhai::Module;
///
/// # use rhai::Module;
/// let mut module = Module::new();
/// module.set_id(Some("hello"));
/// module.set_id("hello");
/// assert_eq!(module.id(), Some("hello"));
/// ```
#[inline(always)]
pub fn set_id<S: Into<Identifier>>(&mut self, id: Option<S>) -> &mut Self {
self.id = id.map(|s| s.into());
pub fn set_id(&mut self, id: impl Into<Identifier>) -> &mut Self {
self.id = Some(id.into());
self
}
/// Clear the ID of the [`Module`].
///
/// # Example
///
/// ```
/// # use rhai::Module;
/// let mut module = Module::new();
/// module.set_id("hello");
/// assert_eq!(module.id(), Some("hello"));
/// module.clear_id();
/// assert_eq!(module.id(), None);
/// ```
#[inline(always)]
pub fn clear_id(&mut self) -> &mut Self {
self.id = None;
self
}
@ -316,8 +320,7 @@ impl Module {
/// # Example
///
/// ```
/// use rhai::Module;
///
/// # use rhai::Module;
/// let module = Module::new();
/// assert!(module.is_empty());
/// ```
@ -340,8 +343,7 @@ impl Module {
/// # Example
///
/// ```
/// use rhai::Module;
///
/// # use rhai::Module;
/// let mut module = Module::new();
/// assert!(module.is_indexed());
///
@ -356,7 +358,7 @@ impl Module {
/// ```
#[inline(always)]
#[must_use]
pub fn is_indexed(&self) -> bool {
pub const fn is_indexed(&self) -> bool {
self.indexed
}
@ -380,8 +382,7 @@ impl Module {
/// # Example
///
/// ```
/// use rhai::Module;
///
/// # use rhai::Module;
/// let mut module = Module::new();
/// module.set_var("answer", 42_i64);
/// assert!(module.contains_var("answer"));
@ -397,8 +398,7 @@ impl Module {
/// # Example
///
/// ```
/// use rhai::Module;
///
/// # use rhai::Module;
/// let mut module = Module::new();
/// module.set_var("answer", 42_i64);
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
@ -414,8 +414,7 @@ impl Module {
/// # Example
///
/// ```
/// use rhai::Module;
///
/// # use rhai::Module;
/// let mut module = Module::new();
/// module.set_var("answer", 42_i64);
/// assert_eq!(module.get_var("answer").unwrap().cast::<i64>(), 42);
@ -433,8 +432,7 @@ impl Module {
/// # Example
///
/// ```
/// use rhai::Module;
///
/// # use rhai::Module;
/// let mut module = Module::new();
/// module.set_var("answer", 42_i64);
/// assert_eq!(module.get_var_value::<i64>("answer").unwrap(), 42);
@ -449,7 +447,7 @@ impl Module {
let value = Dynamic::from(value);
if self.indexed {
let hash_var = crate::calc_qualified_fn_hash(once(""), &ident, 0);
let hash_var = crate::calc_qualified_var_hash(once(""), &ident);
self.all_variables.insert(hash_var, value.clone());
}
self.variables.insert(ident, value);
@ -481,7 +479,7 @@ impl Module {
param_names.push("Dynamic".into());
self.functions.insert(
hash_script,
Box::new(FuncInfo {
FuncInfo {
name: fn_def.name.clone(),
namespace: FnNamespace::Internal,
access: fn_def.access,
@ -490,7 +488,8 @@ impl Module {
#[cfg(feature = "metadata")]
param_names,
func: Into::<CallableFunction>::into(fn_def).into(),
}),
}
.into(),
);
self.indexed = false;
self.contains_indexed_global_functions = false;
@ -510,7 +509,7 @@ impl Module {
self.functions
.values()
.find(|f| f.params == num_params && f.name == name)
.map(|f| f.func.get_fn_def())
.and_then(|f| f.func.get_script_fn_def())
}
/// Get a mutable reference to the underlying [`BTreeMap`] of sub-modules.
@ -539,8 +538,7 @@ impl Module {
/// # Example
///
/// ```
/// use rhai::Module;
///
/// # use rhai::Module;
/// let mut module = Module::new();
/// let sub_module = Module::new();
/// module.set_sub_module("question", sub_module);
@ -557,8 +555,7 @@ impl Module {
/// # Example
///
/// ```
/// use rhai::Module;
///
/// # use rhai::Module;
/// let mut module = Module::new();
/// let sub_module = Module::new();
/// module.set_sub_module("question", sub_module);
@ -577,8 +574,7 @@ impl Module {
/// # Example
///
/// ```
/// use rhai::Module;
///
/// # use rhai::Module;
/// let mut module = Module::new();
/// let sub_module = Module::new();
/// module.set_sub_module("question", sub_module);
@ -603,8 +599,7 @@ impl Module {
/// # Example
///
/// ```
/// use rhai::Module;
///
/// # use rhai::Module;
/// let mut module = Module::new();
/// let hash = module.set_native_fn("calc", || Ok(42_i64));
/// assert!(module.contains_fn(hash));
@ -715,7 +710,7 @@ impl Module {
self.functions.insert(
hash_fn,
Box::new(FuncInfo {
FuncInfo {
name: self.identifiers.get(name),
namespace,
access,
@ -724,7 +719,8 @@ impl Module {
#[cfg(feature = "metadata")]
param_names,
func: func.into(),
}),
}
.into(),
);
self.indexed = false;
@ -845,8 +841,7 @@ impl Module {
/// # Example
///
/// ```
/// use rhai::Module;
///
/// # use rhai::Module;
/// let mut module = Module::new();
/// let hash = module.set_native_fn("calc", || Ok(42_i64));
/// assert!(module.contains_fn(hash));
@ -880,8 +875,7 @@ impl Module {
/// # Example
///
/// ```
/// use rhai::Module;
///
/// # use rhai::Module;
/// let mut module = Module::new();
/// let hash = module.set_getter_fn("value", |x: &mut i64| { Ok(*x) });
/// assert!(module.contains_fn(hash));
@ -1349,7 +1343,9 @@ impl Module {
f.access,
f.name.as_str(),
f.params,
f.func.get_fn_def(),
f.func
.get_script_fn_def()
.expect("never fails because the function is scripted"),
)
})
}
@ -1474,13 +1470,23 @@ impl Module {
.filter(|f| f.func.is_script())
.for_each(|f| {
// Encapsulate AST environment
let mut func = f.func.get_fn_def().as_ref().clone();
let mut func = f
.func
.get_script_fn_def()
.expect("never fails because the function is scripted")
.as_ref()
.clone();
func.lib = Some(ast.shared_lib());
func.mods = func_mods.clone();
module.set_script_fn(func);
});
module.set_id(ast.clone_source());
if let Some(s) = ast.source_raw() {
module.set_id(s.clone());
} else {
module.clear_id();
}
module.build_index();
Ok(module)
@ -1523,7 +1529,7 @@ impl Module {
// Index all variables
module.variables.iter().for_each(|(var_name, value)| {
let hash_var = crate::calc_qualified_fn_hash(path.iter().map(|&v| v), var_name, 0);
let hash_var = crate::calc_qualified_var_hash(path.iter().map(|&v| v), var_name);
variables.insert(hash_var, value.clone());
});
@ -1728,7 +1734,7 @@ impl NamespaceRef {
/// Get the [`Scope`][crate::Scope] index offset.
#[inline(always)]
#[must_use]
pub(crate) fn index(&self) -> Option<NonZeroUsize> {
pub(crate) const fn index(&self) -> Option<NonZeroUsize> {
self.index
}
/// Set the [`Scope`][crate::Scope] index offset.

View File

@ -345,17 +345,19 @@ impl ModuleResolver for FileModuleResolver {
let file_path = self.get_file_path(path, source_path);
// Load the script file and compile it
match engine.compile_file(file_path).map_err(|err| match *err {
EvalAltResult::ErrorSystem(_, err) if err.is::<IoError>() => {
Box::new(EvalAltResult::ErrorModuleNotFound(path.to_string(), pos))
}
_ => Box::new(EvalAltResult::ErrorInModule(path.to_string(), err, pos)),
}) {
Ok(mut ast) => {
ast.set_source(path);
Some(Ok(ast))
}
err => Some(err),
}
Some(
engine
.compile_file(file_path)
.map(|mut ast| {
ast.set_source(path);
ast
})
.map_err(|err| match *err {
EvalAltResult::ErrorSystem(_, err) if err.is::<IoError>() => {
EvalAltResult::ErrorModuleNotFound(path.to_string(), pos).into()
}
_ => EvalAltResult::ErrorInModule(path.to_string(), err, pos).into(),
}),
)
}
}

View File

@ -61,14 +61,14 @@ struct State<'a> {
impl<'a> State<'a> {
/// Create a new State.
#[inline(always)]
pub fn new(
pub const fn new(
engine: &'a Engine,
lib: &'a [&'a Module],
optimization_level: OptimizationLevel,
) -> Self {
Self {
changed: false,
variables: vec![],
variables: Vec::new(),
propagate_constants: true,
engine,
lib,
@ -87,7 +87,7 @@ impl<'a> State<'a> {
}
/// Is the [`AST`] dirty (i.e. changed)?
#[inline(always)]
pub fn is_dirty(&self) -> bool {
pub const fn is_dirty(&self) -> bool {
self.changed
}
/// Prune the list of constants back to a specified size.
@ -297,8 +297,14 @@ fn optimize_stmt_block(
Stmt::Noop(pos)
};
}
// { ...; stmt; noop } -> done
[.., ref second_last_stmt, Stmt::Noop(_)]
if second_last_stmt.returns_value() => {}
if second_last_stmt.returns_value() =>
{
break
}
// { ...; stmt_that_returns; pure_non_value_stmt } -> { ...; stmt_that_returns; noop }
// { ...; stmt; pure_non_value_stmt } -> { ...; stmt }
[.., ref second_last_stmt, ref last_stmt]
if !last_stmt.returns_value() && is_pure(last_stmt) =>
{
@ -413,7 +419,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
*stmt = if preserve_result {
// -> { expr, Noop }
Stmt::Block(Box::new([Stmt::Expr(expr), Stmt::Noop(pos)]), pos)
Stmt::Block([Stmt::Expr(expr), Stmt::Noop(pos)].into(), pos)
} else {
// -> expr
Stmt::Expr(expr)
@ -787,17 +793,17 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
#[cfg(not(feature = "no_index"))]
Expr::Index(x, _) => { optimize_expr(&mut x.lhs, state, false); optimize_expr(&mut x.rhs, state, _chaining); }
// ``
Expr::InterpolatedString(x) if x.is_empty() => {
Expr::InterpolatedString(x, pos) if x.is_empty() => {
state.set_dirty();
*expr = Expr::StringConstant(state.engine.empty_string.clone(), Position::NONE);
*expr = Expr::StringConstant(state.engine.empty_string.clone(), *pos);
}
// `...`
Expr::InterpolatedString(x) if x.len() == 1 && matches!(x[0], Expr::StringConstant(_, _)) => {
Expr::InterpolatedString(x, _) if x.len() == 1 && matches!(x[0], Expr::StringConstant(_, _)) => {
state.set_dirty();
*expr = mem::take(&mut x[0]);
}
// `... ${ ... } ...`
Expr::InterpolatedString(x) => {
Expr::InterpolatedString(x, _) => {
let mut n = 0;
// Merge consecutive strings
@ -843,7 +849,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
#[cfg(not(feature = "no_index"))]
Expr::Array(_, _) if expr.is_constant() => {
state.set_dirty();
*expr = Expr::DynamicConstant(Box::new(expr.get_literal_value().unwrap()), expr.position());
*expr = Expr::DynamicConstant(expr.get_literal_value().unwrap().into(), expr.position());
}
// [ items .. ]
#[cfg(not(feature = "no_index"))]
@ -852,7 +858,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) {
#[cfg(not(feature = "no_object"))]
Expr::Map(_, _) if expr.is_constant() => {
state.set_dirty();
*expr = Expr::DynamicConstant(Box::new(expr.get_literal_value().unwrap()), expr.position());
*expr = Expr::DynamicConstant(expr.get_literal_value().unwrap().into(), expr.position());
}
// #{ key:value, .. }
#[cfg(not(feature = "no_object"))]

View File

@ -121,6 +121,15 @@ macro_rules! gen_arithmetic_functions {
pub fn binary_xor(x: $arg_type, y: $arg_type) -> $arg_type {
x ^ y
}
pub fn is_zero(x: $arg_type) -> bool {
x == 0
}
pub fn is_odd(x: $arg_type) -> bool {
x % 2 != 0
}
pub fn is_even(x: $arg_type) -> bool {
x % 2 == 0
}
}
})* }
}
@ -174,6 +183,7 @@ macro_rules! reg_functions {
}
def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
combine_with_exported_module!(lib, "int", int_functions);
reg_functions!(lib += signed_basic; INT);
#[cfg(not(feature = "only_i32"))]
@ -201,6 +211,19 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
combine_with_exported_module!(lib, "decimal", decimal_functions);
});
#[export_module]
mod int_functions {
pub fn is_zero(x: INT) -> bool {
x == 0
}
pub fn is_odd(x: INT) -> bool {
x % 2 != 0
}
pub fn is_even(x: INT) -> bool {
x % 2 == 0
}
}
gen_arithmetic_functions!(arith_basic => INT);
#[cfg(not(feature = "only_i32"))]
@ -315,6 +338,9 @@ mod f32_functions {
1
}
}
pub fn is_zero(x: f32) -> bool {
x == 0.0
}
#[rhai_fn(name = "**", return_raw)]
pub fn pow_f_i(x: f32, y: INT) -> Result<f32, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) && y > (i32::MAX as INT) {
@ -420,6 +446,9 @@ mod f64_functions {
1
}
}
pub fn is_zero(x: f64) -> bool {
x == 0.0
}
}
#[cfg(feature = "decimal")]
@ -511,4 +540,7 @@ pub mod decimal_functions {
1
}
}
pub fn is_zero(x: Decimal) -> bool {
x.is_zero()
}
}

View File

@ -11,7 +11,7 @@ def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, {
mod fn_ptr_functions {
#[rhai_fn(name = "name", get = "name", pure)]
pub fn name(f: &mut FnPtr) -> ImmutableString {
f.get_fn_name().as_str().into()
f.fn_name_raw().into()
}
#[cfg(not(feature = "no_function"))]

View File

@ -27,10 +27,11 @@ where
return EvalAltResult::ErrorInFunctionCall(
"range".to_string(),
Default::default(),
Box::new(EvalAltResult::ErrorArithmetic(
EvalAltResult::ErrorArithmetic(
"step value cannot be zero".to_string(),
crate::Position::NONE,
)),
)
.into(),
crate::Position::NONE,
)
.into();
@ -314,7 +315,7 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
#[cfg(not(feature = "unchecked"))]
if step == 0.0 {
return EvalAltResult::ErrorInFunctionCall("range".to_string(), "".to_string(),
Box::new(EvalAltResult::ErrorArithmetic("step value cannot be zero".to_string(), crate::Position::NONE)),
EvalAltResult::ErrorArithmetic("step value cannot be zero".to_string(), crate::Position::NONE).into(),
crate::Position::NONE,
).into();
}
@ -376,7 +377,7 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
#[cfg(not(feature = "unchecked"))]
if step.is_zero() {
return EvalAltResult::ErrorInFunctionCall("range".to_string(), "".to_string(),
Box::new(EvalAltResult::ErrorArithmetic("step value cannot be zero".to_string(), crate::Position::NONE)),
EvalAltResult::ErrorArithmetic("step value cannot be zero".to_string(), crate::Position::NONE).into(),
crate::Position::NONE,
).into();
}

View File

@ -14,7 +14,7 @@ mod core_functions {
#[rhai_fn(name = "set_tag", set = "tag", return_raw)]
pub fn set_tag(value: &mut Dynamic, tag: INT) -> Result<(), Box<EvalAltResult>> {
if tag < Tag::MIN as INT {
Err(Box::new(EvalAltResult::ErrorArithmetic(
EvalAltResult::ErrorArithmetic(
format!(
"{} is too small to fit into a tag (must be between {} and {})",
tag,
@ -22,9 +22,10 @@ mod core_functions {
Tag::MAX
),
Position::NONE,
)))
)
.into()
} else if tag > Tag::MAX as INT {
Err(Box::new(EvalAltResult::ErrorArithmetic(
EvalAltResult::ErrorArithmetic(
format!(
"{} is too large to fit into a tag (must be between {} and {})",
tag,
@ -32,7 +33,8 @@ mod core_functions {
Tag::MAX
),
Position::NONE,
)))
)
.into()
} else {
value.set_tag(tag as Tag);
Ok(())

View File

@ -40,15 +40,6 @@ pub trait Package {
/// Retrieve the generic package library from this package.
#[must_use]
fn as_shared_module(&self) -> Shared<Module>;
/// Retrieve the generic package library from this package.
/// This method is deprecated and will be removed in the future.
/// Use [`as_shared_module`][Package::as_shared_module] instead.
#[must_use]
#[deprecated(since = "0.19.9", note = "use `as_shared_module` instead")]
fn get(&self) -> Shared<Module> {
self.as_shared_module()
}
}
/// Macro that makes it easy to define a _package_ (which is basically a shared [module][Module])

View File

@ -5,20 +5,20 @@ use crate::ast::{
ScriptFnDef, Stmt, StmtBlock,
};
use crate::custom_syntax::{
CustomSyntax, MARKER_BLOCK, MARKER_BOOL, MARKER_EXPR, MARKER_IDENT, MARKER_INT, MARKER_STRING,
CustomSyntax, CUSTOM_SYNTAX_MARKER_BLOCK, CUSTOM_SYNTAX_MARKER_BOOL, CUSTOM_SYNTAX_MARKER_EXPR,
CUSTOM_SYNTAX_MARKER_IDENT, CUSTOM_SYNTAX_MARKER_INT, CUSTOM_SYNTAX_MARKER_STRING,
};
use crate::dynamic::{AccessMode, Union};
use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS};
use crate::fn_hash::get_hasher;
use crate::module::NamespaceRef;
use crate::optimize::{optimize_into_ast, OptimizationLevel};
use crate::fn_hash::get_hasher;
use crate::token::{
is_keyword_function, is_valid_identifier, Token, TokenStream, TokenizerControl,
};
use crate::{
calc_fn_hash, calc_qualified_fn_hash, Dynamic, Engine, Identifier, LexError, ParseError,
ParseErrorType, Position, Scope, Shared, StaticVec, AST,
calc_fn_hash, calc_qualified_fn_hash, calc_qualified_var_hash, Dynamic, Engine, Identifier,
LexError, ParseError, ParseErrorType, Position, Scope, Shared, StaticVec, AST,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -29,7 +29,7 @@ use std::{
};
#[cfg(not(feature = "no_float"))]
use crate::{custom_syntax::MARKER_FLOAT, FLOAT};
use crate::{custom_syntax::CUSTOM_SYNTAX_MARKER_FLOAT, FLOAT};
#[cfg(not(feature = "no_function"))]
use crate::FnAccess;
@ -38,6 +38,10 @@ type PERR = ParseErrorType;
type FunctionsLib = BTreeMap<u64, Shared<ScriptFnDef>>;
/// Invalid variable name that acts as a search barrier in a [`Scope`].
const SCOPE_SEARCH_BARRIER_MARKER: &str = "$BARRIER$";
/// The message: never fails because `TokenStream` never ends
const NEVER_ENDS: &str = "never fails because `TokenStream` never ends";
/// A factory of identifiers from text strings.
@ -138,7 +142,7 @@ impl<'e> ParseState<'e> {
///
/// Return `None` when the variable name is not found in the `stack`.
#[inline(always)]
fn access_var(&mut self, name: &str, _pos: Position) -> Option<NonZeroUsize> {
pub fn access_var(&mut self, name: &str, _pos: Position) -> Option<NonZeroUsize> {
let mut barrier = false;
let index = self
@ -147,8 +151,8 @@ impl<'e> ParseState<'e> {
.rev()
.enumerate()
.find(|(_, (n, _))| {
if n.is_empty() {
// Do not go beyond empty variable names
if n == SCOPE_SEARCH_BARRIER_MARKER {
// Do not go beyond the barrier
barrier = true;
false
} else {
@ -230,7 +234,7 @@ impl ParseSettings {
/// Create a new `ParseSettings` with one higher expression level.
#[inline(always)]
#[must_use]
pub fn level_up(&self) -> Self {
pub const fn level_up(&self) -> Self {
Self {
level: self.level + 1,
..*self
@ -244,8 +248,10 @@ impl ParseSettings {
&self,
limit: Option<NonZeroUsize>,
) -> Result<(), ParseError> {
if limit.map(|limit| self.level > limit.get()).unwrap_or(false) {
return Err(PERR::ExprTooDeep.into_err(self.pos));
if let Some(limit) = limit {
if self.level > limit.get() {
return Err(PERR::ExprTooDeep.into_err(self.pos));
}
}
Ok(())
}
@ -508,7 +514,7 @@ fn parse_index_chain(
Expr::IntegerConstant(_, _)
| Expr::Array(_, _)
| Expr::StringConstant(_, _)
| Expr::InterpolatedString(_) => (),
| Expr::InterpolatedString(_, _) => (),
Expr::Map(_, _) => {
return Err(PERR::MalformedIndexExpr(
@ -540,10 +546,10 @@ fn parse_index_chain(
},
// lhs[string]
Expr::StringConstant(_, _) | Expr::InterpolatedString(_) => match lhs {
Expr::StringConstant(_, _) | Expr::InterpolatedString(_, _) => match lhs {
Expr::Map(_, _) => (),
Expr::Array(_, _) | Expr::StringConstant(_, _) | Expr::InterpolatedString(_) => {
Expr::Array(_, _) | Expr::StringConstant(_, _) | Expr::InterpolatedString(_, _) => {
return Err(PERR::MalformedIndexExpr(
"Array or string expects numeric index, not a string".into(),
)
@ -628,13 +634,13 @@ fn parse_index_chain(
parse_index_chain(input, state, lib, idx_expr, settings.level_up())?;
// Indexing binds to right
Ok(Expr::Index(
Box::new(BinaryExpr { lhs, rhs: idx_expr }),
BinaryExpr { lhs, rhs: idx_expr }.into(),
prev_pos,
))
}
// Otherwise terminate the indexing chain
_ => Ok(Expr::Index(
Box::new(BinaryExpr { lhs, rhs: idx_expr }),
BinaryExpr { lhs, rhs: idx_expr }.into(),
settings.pos,
)),
}
@ -834,7 +840,7 @@ fn parse_map_literal(
map.shrink_to_fit();
Ok(Expr::Map(Box::new((map, template)), settings.pos))
Ok(Expr::Map((map, template).into(), settings.pos))
}
/// Parse a switch expression.
@ -944,7 +950,7 @@ fn parse_switch(
let need_comma = !stmt.is_self_terminated();
def_stmt = if let Some(hash) = hash {
table.insert(hash, Box::new((condition, stmt.into())));
table.insert(hash, (condition, stmt.into()).into());
None
} else {
Some(stmt.into())
@ -973,12 +979,11 @@ fn parse_switch(
}
}
let def_stmt_block = def_stmt.unwrap_or_else(|| Stmt::Noop(Position::NONE).into());
Ok(Stmt::Switch(
item,
Box::new((
table,
def_stmt.unwrap_or_else(|| Stmt::Noop(Position::NONE).into()),
)),
(table, def_stmt_block).into(),
settings.pos,
))
}
@ -1126,7 +1131,7 @@ fn parse_primary(
}
segments.shrink_to_fit();
Expr::InterpolatedString(segments.into())
Expr::InterpolatedString(segments.into(), settings.pos)
}
// Array literal
@ -1155,7 +1160,7 @@ fn parse_primary(
Expr::Variable(
None,
settings.pos,
Box::new((None, None, state.get_identifier(s))),
(None, None, state.get_identifier(s)).into(),
)
}
// Namespace qualification
@ -1169,7 +1174,7 @@ fn parse_primary(
Expr::Variable(
None,
settings.pos,
Box::new((None, None, state.get_identifier(s))),
(None, None, state.get_identifier(s)).into(),
)
}
// Normal variable access
@ -1185,7 +1190,7 @@ fn parse_primary(
Expr::Variable(
short_index,
settings.pos,
Box::new((index, None, state.get_identifier(s))),
(index, None, state.get_identifier(s)).into(),
)
}
}
@ -1203,13 +1208,13 @@ fn parse_primary(
Token::LeftParen | Token::Bang if is_keyword_function(&s) => Expr::Variable(
None,
settings.pos,
Box::new((None, None, state.get_identifier(s))),
(None, None, state.get_identifier(s)).into(),
),
// Access to `this` as a variable is OK within a function scope
_ if s == KEYWORD_THIS && settings.is_function_scope => Expr::Variable(
None,
settings.pos,
Box::new((None, None, state.get_identifier(s))),
(None, None, state.get_identifier(s)).into(),
),
// Cannot access to `this` as a variable not in a function scope
_ if s == KEYWORD_THIS => {
@ -1300,7 +1305,7 @@ fn parse_primary(
Expr::Variable(
None,
pos2,
Box::new((None, namespace, state.get_identifier(id2))),
(None, namespace, state.get_identifier(id2)).into(),
)
}
// Indexing
@ -1348,7 +1353,7 @@ fn parse_primary(
}
.map(|x| match x {
(_, Some((namespace, hash)), name) => {
*hash = calc_qualified_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0);
*hash = calc_qualified_var_hash(namespace.iter().map(|v| v.name.as_str()), name);
#[cfg(not(feature = "no_module"))]
namespace.set_index(state.find_module(&namespace[0].name));
@ -1493,7 +1498,7 @@ fn make_assignment_stmt<'a>(
}
// var (non-indexed) = rhs
Expr::Variable(None, _, ref x) if x.0.is_none() => {
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos))
Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos))
}
// var (indexed) = rhs
Expr::Variable(i, var_pos, ref x) => {
@ -1509,9 +1514,7 @@ fn make_assignment_stmt<'a>(
|n| n.get() as usize,
);
match state.stack[state.stack.len() - index].1 {
AccessMode::ReadWrite => {
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos))
}
AccessMode::ReadWrite => Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos)),
// Constant values cannot be assigned to
AccessMode::ReadOnly => {
Err(PERR::AssignmentToConstant(name.to_string()).into_err(var_pos))
@ -1524,7 +1527,7 @@ fn make_assignment_stmt<'a>(
None => match x.lhs {
// var[???] = rhs, var.??? = rhs
Expr::Variable(_, _, _) => {
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos))
Ok(Stmt::Assignment((lhs, op_info, rhs).into(), op_pos))
}
// expr[???] = rhs, expr.??? = rhs
ref expr => {
@ -1601,7 +1604,7 @@ fn make_dot_expr(
(state.get_identifier(ident).into(), var_pos),
)));
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_pos)
}
// lhs.module::id - syntax error
(_, Expr::Variable(_, _, x)) if x.1.is_some() => {
@ -1609,20 +1612,19 @@ fn make_dot_expr(
.into_err(x.1.expect("never fails because the namespace is `Some`").0[0].pos))
}
// lhs.prop
(lhs, prop @ Expr::Property(_)) => {
Expr::Dot(Box::new(BinaryExpr { lhs, rhs: prop }), op_pos)
}
(lhs, prop @ Expr::Property(_)) => Expr::Dot(BinaryExpr { lhs, rhs: prop }.into(), op_pos),
// lhs.dot_lhs.dot_rhs
(lhs, Expr::Dot(x, pos)) => match x.lhs {
Expr::Variable(_, _, _) | Expr::Property(_) => {
let rhs = Expr::Dot(
Box::new(BinaryExpr {
BinaryExpr {
lhs: x.lhs.into_property(state),
rhs: x.rhs,
}),
}
.into(),
pos,
);
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_pos)
}
Expr::FnCall(mut func, func_pos) => {
// Recalculate hash
@ -1632,26 +1634,28 @@ fn make_dot_expr(
);
let rhs = Expr::Dot(
Box::new(BinaryExpr {
BinaryExpr {
lhs: Expr::FnCall(func, func_pos),
rhs: x.rhs,
}),
}
.into(),
pos,
);
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_pos)
}
_ => unreachable!("invalid dot expression: {:?}", x.lhs),
},
// lhs.idx_lhs[idx_rhs]
(lhs, Expr::Index(x, pos)) => {
let rhs = Expr::Index(
Box::new(BinaryExpr {
BinaryExpr {
lhs: x.lhs.into_property(state),
rhs: x.rhs,
}),
}
.into(),
pos,
);
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_pos)
}
// lhs.nnn::func(...)
(_, Expr::FnCall(x, _)) if x.is_qualified() => {
@ -1687,7 +1691,7 @@ fn make_dot_expr(
calc_fn_hash(&func.name, func.args.len() + 1),
);
let rhs = Expr::FnCall(func, func_pos);
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_pos)
}
// lhs.rhs
(_, rhs) => return Err(PERR::PropertyExpected.into_err(rhs.position())),
@ -1811,10 +1815,11 @@ fn parse_binary_op(
.pop()
.expect("never fails because `||` has two arguments");
Expr::Or(
Box::new(BinaryExpr {
BinaryExpr {
lhs: current_lhs,
rhs,
}),
}
.into(),
pos,
)
}
@ -1826,10 +1831,11 @@ fn parse_binary_op(
.pop()
.expect("never fails because `&&` has two arguments");
Expr::And(
Box::new(BinaryExpr {
BinaryExpr {
lhs: current_lhs,
rhs,
}),
}
.into(),
pos,
)
}
@ -1894,7 +1900,7 @@ fn parse_custom_syntax(
// Add an empty variable name to the stack.
// Empty variable names act as a barrier so earlier variables will not be matched.
// Variable searches stop at the first empty variable name.
let empty = state.get_identifier("");
let empty = state.get_identifier(SCOPE_SEARCH_BARRIER_MARKER);
state.stack.push((empty, AccessMode::ReadWrite));
}
@ -1915,32 +1921,32 @@ fn parse_custom_syntax(
};
match required_token.as_str() {
MARKER_IDENT => {
CUSTOM_SYNTAX_MARKER_IDENT => {
let (name, pos) = parse_var_name(input)?;
let name = state.get_identifier(name);
segments.push(name.clone().into());
tokens.push(state.get_identifier(MARKER_IDENT));
keywords.push(Expr::Variable(None, pos, Box::new((None, None, name))));
tokens.push(state.get_identifier(CUSTOM_SYNTAX_MARKER_IDENT));
keywords.push(Expr::Variable(None, pos, (None, None, name).into()));
}
MARKER_EXPR => {
CUSTOM_SYNTAX_MARKER_EXPR => {
keywords.push(parse_expr(input, state, lib, settings)?);
let keyword = state.get_identifier(MARKER_EXPR);
let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_EXPR);
segments.push(keyword.clone().into());
tokens.push(keyword);
}
MARKER_BLOCK => match parse_block(input, state, lib, settings)? {
CUSTOM_SYNTAX_MARKER_BLOCK => match parse_block(input, state, lib, settings)? {
block @ Stmt::Block(_, _) => {
keywords.push(Expr::Stmt(Box::new(block.into())));
let keyword = state.get_identifier(MARKER_BLOCK);
let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_BLOCK);
segments.push(keyword.clone().into());
tokens.push(keyword);
}
stmt => unreachable!("expecting Stmt::Block, but gets {:?}", stmt),
},
MARKER_BOOL => match input.next().expect(NEVER_ENDS) {
CUSTOM_SYNTAX_MARKER_BOOL => match input.next().expect(NEVER_ENDS) {
(b @ Token::True, pos) | (b @ Token::False, pos) => {
keywords.push(Expr::BoolConstant(b == Token::True, pos));
let keyword = state.get_identifier(MARKER_BOOL);
let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_BOOL);
segments.push(keyword.clone().into());
tokens.push(keyword);
}
@ -1951,10 +1957,10 @@ fn parse_custom_syntax(
)
}
},
MARKER_INT => match input.next().expect(NEVER_ENDS) {
CUSTOM_SYNTAX_MARKER_INT => match input.next().expect(NEVER_ENDS) {
(Token::IntegerConstant(i), pos) => {
keywords.push(Expr::IntegerConstant(i, pos));
let keyword = state.get_identifier(MARKER_INT);
let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_INT);
segments.push(keyword.clone().into());
tokens.push(keyword);
}
@ -1966,10 +1972,10 @@ fn parse_custom_syntax(
}
},
#[cfg(not(feature = "no_float"))]
MARKER_FLOAT => match input.next().expect(NEVER_ENDS) {
CUSTOM_SYNTAX_MARKER_FLOAT => match input.next().expect(NEVER_ENDS) {
(Token::FloatConstant(f), pos) => {
keywords.push(Expr::FloatConstant(f, pos));
let keyword = state.get_identifier(MARKER_FLOAT);
let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_FLOAT);
segments.push(keyword.clone().into());
tokens.push(keyword);
}
@ -1980,10 +1986,10 @@ fn parse_custom_syntax(
.into_err(pos))
}
},
MARKER_STRING => match input.next().expect(NEVER_ENDS) {
CUSTOM_SYNTAX_MARKER_STRING => match input.next().expect(NEVER_ENDS) {
(Token::StringConstant(s), pos) => {
keywords.push(Expr::StringConstant(state.get_identifier(s).into(), pos));
let keyword = state.get_identifier(MARKER_STRING);
let keyword = state.get_identifier(CUSTOM_SYNTAX_MARKER_STRING);
segments.push(keyword.clone().into());
tokens.push(keyword);
}
@ -2138,7 +2144,7 @@ fn parse_if(
Ok(Stmt::If(
guard,
Box::new((if_body.into(), else_body.into())),
(if_body.into(), else_body.into()).into(),
settings.pos,
))
}
@ -2829,7 +2835,7 @@ fn parse_try_catch(
let catch_body = parse_block(input, state, lib, settings.level_up())?;
Ok(Stmt::TryCatch(
Box::new((body.into(), var_def, catch_body.into())),
(body.into(), var_def, catch_body.into()).into(),
settings.pos,
))
}
@ -2960,7 +2966,7 @@ fn make_curry_from_externals(
externals
.iter()
.cloned()
.map(|x| Expr::Variable(None, Position::NONE, Box::new((None, None, x)))),
.map(|x| Expr::Variable(None, Position::NONE, (None, None, x).into())),
);
let expr = FnCallExpr {
@ -2979,7 +2985,7 @@ fn make_curry_from_externals(
let mut statements = StaticVec::with_capacity(externals.len() + 1);
statements.extend(externals.into_iter().map(Stmt::Share));
statements.push(Stmt::Expr(expr));
Expr::Stmt(Box::new(StmtBlock::new(statements, pos)))
Expr::Stmt(StmtBlock::new(statements, pos).into())
}
/// Parse an anonymous function definition.

View File

@ -7,7 +7,7 @@ use std::prelude::v1::*;
use std::{borrow::Cow, iter::Extend};
/// Keep a number of entries inline (since [`Dynamic`] is usually small enough).
const SCOPE_SIZE: usize = 8;
const SCOPE_ENTRIES_INLINED: usize = 8;
/// Type containing information about the current scope.
/// Useful for keeping state between [`Engine`][crate::Engine] evaluation runs.
@ -49,14 +49,14 @@ const SCOPE_SIZE: usize = 8;
// look up a variable. Variable lookup is usually via direct indexing, by-passing the name altogether.
//
// Since [`Dynamic`] is reasonably small, packing it tightly improves cache locality when variables are accessed.
//
// The alias is `Box`'ed because it occurs infrequently.
#[derive(Debug, Clone, Hash)]
pub struct Scope<'a> {
/// Current value of the entry.
values: smallvec::SmallVec<[Dynamic; SCOPE_SIZE]>,
values: smallvec::SmallVec<[Dynamic; SCOPE_ENTRIES_INLINED]>,
/// (Name, aliases) of the entry.
names: Vec<(Cow<'a, str>, Option<Box<StaticVec<Identifier>>>)>,
names: smallvec::SmallVec<
[(Cow<'a, str>, Option<Box<StaticVec<Identifier>>>); SCOPE_ENTRIES_INLINED],
>,
}
impl Default for Scope<'_> {
@ -161,7 +161,7 @@ impl<'a> Scope<'a> {
#[inline(always)]
#[must_use]
pub fn is_empty(&self) -> bool {
self.values.len() == 0
self.values.is_empty()
}
/// Add (push) a new entry to the [`Scope`].
///
@ -253,7 +253,7 @@ impl<'a> Scope<'a> {
access: AccessMode,
mut value: Dynamic,
) -> &mut Self {
self.names.push((name.into(), None));
self.names.push((name.into(), Default::default()));
value.set_access_mode(access);
self.values.push(value.into());
self
@ -436,20 +436,18 @@ impl<'a> Scope<'a> {
#[cfg(not(feature = "no_module"))]
#[inline(always)]
pub(crate) fn add_entry_alias(&mut self, index: usize, alias: Identifier) -> &mut Self {
let entry = self
let (_, aliases) = self
.names
.get_mut(index)
.expect("never fails unless the index is out of bounds");
if entry.1.is_none() {
// Initialize the alias list if it is empty.
entry.1 = Some(Default::default());
}
let list = entry
.1
.as_mut()
.expect("never fails because the list is initialized");
if !list.iter().any(|a| a == &alias) {
list.push(alias);
match aliases {
None => {
let mut list = StaticVec::new();
list.push(alias);
*aliases = Some(list.into());
}
Some(aliases) if !aliases.iter().any(|a| a == &alias) => aliases.push(alias),
Some(_) => (),
}
self
}
@ -534,7 +532,7 @@ impl<'a, K: Into<Cow<'a, str>>> Extend<(K, Dynamic)> for Scope<'a> {
#[inline(always)]
fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) {
iter.into_iter().for_each(|(name, value)| {
self.names.push((name.into(), None));
self.names.push((name.into(), Default::default()));
self.values.push(value);
});
}

View File

@ -146,7 +146,11 @@ impl From<&crate::module::FuncInfo> for FnMetadata {
}
#[cfg(not(feature = "no_function"))]
{
info.func.get_fn_def().comments.to_vec()
info.func
.get_script_fn_def()
.expect("never fails because the function is scripted")
.comments
.to_vec()
}
} else {
Default::default()

View File

@ -41,7 +41,7 @@ pub type TokenizerControl = Rc<Cell<TokenizerControlBlock>>;
type LERR = LexError;
/// Separator character for numbers.
const NUM_SEP: char = '_';
const NUMBER_SEPARATOR: char = '_';
/// A stream of tokens.
pub type TokenStream<'a> = Peekable<TokenIterator<'a>>;
@ -110,7 +110,7 @@ impl Position {
#[cfg(not(feature = "no_position"))]
return Some(self.line as usize);
#[cfg(feature = "no_position")]
unreachable!();
unreachable!("there is no Position");
}
}
/// Get the character position (1-based), or [`None`] if at beginning of a line.
@ -128,7 +128,7 @@ impl Position {
};
#[cfg(feature = "no_position")]
unreachable!();
unreachable!("there is no Position");
}
}
/// Advance by one character position.
@ -175,7 +175,7 @@ impl Position {
/// Is this [`Position`] at the beginning of a line?
#[inline(always)]
#[must_use]
pub fn is_beginning_of_line(self) -> bool {
pub const fn is_beginning_of_line(self) -> bool {
#[cfg(not(feature = "no_position"))]
return self.pos == 0 && !self.is_none();
#[cfg(feature = "no_position")]
@ -184,9 +184,9 @@ impl Position {
/// Is there no [`Position`]?
#[inline(always)]
#[must_use]
pub fn is_none(self) -> bool {
pub const fn is_none(self) -> bool {
#[cfg(not(feature = "no_position"))]
return self == Self::NONE;
return self.line == 0 && self.pos == 0;
#[cfg(feature = "no_position")]
return true;
}
@ -467,12 +467,8 @@ pub enum Token {
impl Token {
/// Get the syntax of the token if it is a keyword.
///
/// # Panics
///
/// Panics if the token is not a keyword.
#[must_use]
pub fn keyword_syntax(&self) -> &'static str {
pub const fn keyword_syntax(&self) -> &'static str {
use Token::*;
match self {
@ -556,7 +552,7 @@ impl Token {
#[cfg(not(feature = "no_module"))]
As => "as",
t => unreachable!("{:?} is not a keyword", t),
_ => "ERROR: NOT A KEYWORD",
}
}
@ -589,7 +585,7 @@ impl Token {
/// Is this token an op-assignment operator?
#[inline]
#[must_use]
pub fn is_op_assignment(&self) -> bool {
pub const fn is_op_assignment(&self) -> bool {
match self {
Self::PlusAssign
| Self::MinusAssign
@ -608,7 +604,7 @@ impl Token {
/// Get the corresponding operator of the token if it is an op-assignment operator.
#[must_use]
pub fn map_op_assignment(&self) -> Option<Self> {
pub const fn map_op_assignment(&self) -> Option<Self> {
Some(match self {
Self::PlusAssign => Self::Plus,
Self::MinusAssign => Self::Minus,
@ -628,7 +624,7 @@ impl Token {
/// Has this token a corresponding op-assignment operator?
#[inline]
#[must_use]
pub fn has_op_assignment(&self) -> bool {
pub const fn has_op_assignment(&self) -> bool {
match self {
Self::Plus
| Self::Minus
@ -647,7 +643,7 @@ impl Token {
/// Get the corresponding op-assignment operator of the token.
#[must_use]
pub fn make_op_assignment(&self) -> Option<Self> {
pub const fn make_op_assignment(&self) -> Option<Self> {
Some(match self {
Self::Plus => Self::PlusAssign,
Self::Minus => Self::MinusAssign,
@ -777,7 +773,7 @@ impl Token {
// Is this token [`EOF`][Token::EOF]?
#[inline(always)]
#[must_use]
pub fn is_eof(&self) -> bool {
pub const fn is_eof(&self) -> bool {
use Token::*;
match self {
@ -789,7 +785,7 @@ impl Token {
// If another operator is after these, it's probably an unary operator
// (not sure about `fn` name).
#[must_use]
pub fn is_next_unary(&self) -> bool {
pub const fn is_next_unary(&self) -> bool {
use Token::*;
match self {
@ -850,7 +846,7 @@ impl Token {
/// Get the precedence number of the token.
#[must_use]
pub fn precedence(&self) -> Option<Precedence> {
pub const fn precedence(&self) -> Option<Precedence> {
use Token::*;
Precedence::new(match self {
@ -885,7 +881,7 @@ impl Token {
/// Does an expression bind to the right (instead of left)?
#[must_use]
pub fn is_bind_right(&self) -> bool {
pub const fn is_bind_right(&self) -> bool {
use Token::*;
match self {
@ -906,7 +902,7 @@ impl Token {
/// Is this token a standard symbol used in the language?
#[must_use]
pub fn is_symbol(&self) -> bool {
pub const fn is_symbol(&self) -> bool {
use Token::*;
match self {
@ -924,7 +920,7 @@ impl Token {
/// Is this token an active standard keyword?
#[must_use]
pub fn is_keyword(&self) -> bool {
pub const fn is_keyword(&self) -> bool {
use Token::*;
match self {
@ -944,7 +940,7 @@ impl Token {
/// Is this token a reserved symbol?
#[inline(always)]
#[must_use]
pub fn is_reserved(&self) -> bool {
pub const fn is_reserved(&self) -> bool {
match self {
Self::Reserved(_) => true,
_ => false,
@ -964,7 +960,7 @@ impl Token {
/// Is this token a custom keyword?
#[inline(always)]
#[must_use]
pub fn is_custom(&self) -> bool {
pub const fn is_custom(&self) -> bool {
match self {
Self::Custom(_) => true,
_ => false,
@ -1435,7 +1431,7 @@ fn get_next_token_inner(
while let Some(next_char) = stream.peek_next() {
match next_char {
ch if valid(ch) || ch == NUM_SEP => {
ch if valid(ch) || ch == NUMBER_SEPARATOR => {
result.push(next_char);
eat_next(stream, pos);
}
@ -1536,8 +1532,11 @@ fn get_next_token_inner(
// Parse number
return Some((
if let Some(radix) = radix_base {
let out: String =
result.iter().skip(2).filter(|&&c| c != NUM_SEP).collect();
let out: String = result
.iter()
.skip(2)
.filter(|&&c| c != NUMBER_SEPARATOR)
.collect();
INT::from_str_radix(&out, radix)
.map(Token::IntegerConstant)
@ -1545,7 +1544,8 @@ fn get_next_token_inner(
Token::LexError(LERR::MalformedNumber(result.into_iter().collect()))
})
} else {
let out: String = result.iter().filter(|&&c| c != NUM_SEP).collect();
let out: String =
result.iter().filter(|&&c| c != NUMBER_SEPARATOR).collect();
let num = INT::from_str(&out).map(Token::IntegerConstant);
// If integer parsing is unnecessary, try float instead

View File

@ -2,6 +2,26 @@
use rhai::{Engine, EvalAltResult, OptimizationLevel, INT};
#[test]
fn test_optimizer() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
engine.set_optimization_level(OptimizationLevel::Full);
#[cfg(not(feature = "no_function"))]
assert_eq!(
engine.eval::<INT>(
"
fn foo(x) { print(x); return; }
fn foo2(x) { if x > 0 {} return; }
42
"
)?,
42
);
Ok(())
}
#[test]
fn test_optimizer_run() -> Result<(), Box<EvalAltResult>> {
fn run_test(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {

View File

@ -1,4 +1,4 @@
use rhai::{Dynamic, Engine, EvalAltResult, LexError, ParseError, ParseErrorType, Position, INT};
use rhai::{Dynamic, Engine, EvalAltResult, LexError, ParseErrorType, Position, INT};
#[test]
fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
@ -130,13 +130,9 @@ fn test_custom_syntax_raw() -> Result<(), Box<EvalAltResult>> {
1 => Ok(Some("$ident$".into())),
2 => match stream[1].as_str() {
"world" | "kitty" => Ok(None),
s => Err(ParseError(
Box::new(ParseErrorType::BadInput(LexError::ImproperSymbol(
s.to_string(),
Default::default(),
))),
Position::NONE,
)),
s => Err(LexError::ImproperSymbol(s.to_string(), Default::default())
.into_err(Position::NONE)
.into()),
},
_ => unreachable!(),
},