Merge pull request #407 from schungx/master

Add no_position feature.
This commit is contained in:
Stephen Chung 2021-04-22 23:21:51 +08:00 committed by GitHub
commit d579c1b208
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 600 additions and 393 deletions

View File

@ -21,6 +21,7 @@ jobs:
- "--features metadata,serde,internals"
- "--features unchecked"
- "--features sync"
- "--features no_position"
- "--features no_optimize"
- "--features no_float"
- "--features f32_float,serde,metadata,internals"
@ -34,8 +35,8 @@ jobs:
- "--features no_module"
- "--features no_closure"
- "--features unicode-xid-ident"
- "--features sync,no_function,no_float,no_optimize,no_module,no_closure,metadata,serde,unchecked"
- "--features no_function,no_float,no_index,no_object,no_optimize,no_module,no_closure,unchecked"
- "--features sync,no_function,no_float,no_position,no_optimize,no_module,no_closure,metadata,serde,unchecked"
- "--features no_function,no_float,no_position,no_index,no_object,no_optimize,no_module,no_closure,unchecked"
toolchain: [stable]
experimental: [false]
include:

View File

@ -26,6 +26,7 @@ New features
------------
* A module called `global` is automatically created to hold global-level constants, which can then be accessed from functions.
* A new feature `no_position` is added to turn off position tracking during parsing to squeeze out the last drop of performance.
Version 0.20.0

View File

@ -26,6 +26,7 @@ rhai_codegen = { version = "0.3.4", path = "codegen", default_features = false }
default = ["smartstring/std", "ahash/std", "num-traits/std"] # remove 'smartstring/std' when smartstring is updated to support no-std
unchecked = [] # unchecked arithmetic
sync = [] # restrict to only types that implement Send + Sync
no_position = [] # do not track position in the parser
no_optimize = [] # no script optimizer
no_float = [] # no floating-point
f32_float = [] # set FLOAT=f32

View File

@ -25,7 +25,7 @@ fn main() -> Result<(), Box<EvalAltResult>> {
.register_fn("update", TestStruct::update);
let result = engine.eval::<TestStruct>(
r"
"
let x = new_ts();
x.update();
x
@ -35,7 +35,7 @@ fn main() -> Result<(), Box<EvalAltResult>> {
println!("{:?}", result);
let result = engine.eval::<TestStruct>(
r"
"
let x = [ new_ts() ];
x[0].update();
x[0]

View File

@ -25,7 +25,7 @@ fn main() -> Result<(), Box<EvalAltResult>> {
.register_fn("update", TestStruct::update);
let result = engine.eval::<TestStruct>(
r"
"
let x = new_ts();
x.update();
x

View File

@ -1,13 +1,11 @@
//! Module defining the AST (abstract syntax tree).
use crate::dynamic::{AccessMode, Union};
use crate::fn_native::shared_make_mut;
use crate::module::NamespaceRef;
use crate::token::Token;
use crate::utils::calc_fn_hash;
use crate::{
Dynamic, FnNamespace, FnPtr, Identifier, ImmutableString, Module, Position, Shared, StaticVec,
INT,
Dynamic, FnNamespace, Identifier, ImmutableString, Module, Position, Shared, StaticVec, INT,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -68,7 +66,7 @@ pub struct ScriptFnDef {
/// Function doc-comments (if any).
#[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")]
pub comments: StaticVec<std::string::String>,
pub comments: StaticVec<String>,
}
impl fmt::Display for ScriptFnDef {
@ -782,7 +780,12 @@ pub struct Ident {
impl fmt::Debug for Ident {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?} @ {:?}", self.name, self.pos)
#[cfg(not(feature = "no_position"))]
write!(f, "{:?} @ {:?}", self.name, self.pos)?;
#[cfg(feature = "no_position")]
write!(f, "{:?}", self.name)?;
Ok(())
}
}
@ -920,6 +923,11 @@ pub enum Stmt {
Const(Expr, Box<Ident>, bool, Position),
/// expr op`=` expr
Assignment(Box<(Expr, Option<OpAssignment>, Expr)>, Position),
/// func `(` expr `,` ... `)`
///
/// Note - this is a duplicate of [`Expr::FnCall`] to cover the very common pattern of a single
/// function call forming one statement.
FnCall(Box<FnCallExpr>, Position),
/// `{` stmt`;` ... `}`
Block(Vec<Stmt>, Position),
/// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}`
@ -985,6 +993,7 @@ impl Stmt {
| Self::Break(pos)
| Self::Block(_, pos)
| Self::Assignment(_, pos)
| Self::FnCall(_, pos)
| Self::If(_, _, pos)
| Self::Switch(_, _, pos)
| Self::While(_, _, pos)
@ -1014,6 +1023,7 @@ impl Stmt {
| Self::Break(pos)
| Self::Block(_, pos)
| Self::Assignment(_, pos)
| Self::FnCall(_, pos)
| Self::If(_, _, pos)
| Self::Switch(_, _, pos)
| Self::While(_, _, pos)
@ -1042,7 +1052,11 @@ impl Stmt {
/// Does this statement return a value?
pub fn returns_value(&self) -> bool {
match self {
Self::If(_, _, _) | Self::Switch(_, _, _) | Self::Block(_, _) | Self::Expr(_) => true,
Self::If(_, _, _)
| Self::Switch(_, _, _)
| Self::Block(_, _)
| Self::Expr(_)
| Self::FnCall(_, _) => true,
Self::Noop(_)
| Self::While(_, _, _)
@ -1080,6 +1094,7 @@ impl Stmt {
Self::Let(_, _, _, _)
| Self::Const(_, _, _, _)
| Self::Assignment(_, _)
| Self::FnCall(_, _)
| Self::Expr(_)
| Self::Do(_, _, _, _)
| Self::Continue(_)
@ -1117,7 +1132,10 @@ impl Stmt {
condition.is_pure() && block.0.iter().all(Stmt::is_pure)
}
Self::For(iterable, x, _) => iterable.is_pure() && (x.1).0.iter().all(Stmt::is_pure),
Self::Let(_, _, _, _) | Self::Const(_, _, _, _) | Self::Assignment(_, _) => false,
Self::Let(_, _, _, _)
| Self::Const(_, _, _, _)
| Self::Assignment(_, _)
| Self::FnCall(_, _) => false,
Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()),
Self::Continue(_) | Self::Break(_) | Self::Return(_, _, _) => false,
Self::TryCatch(x, _, _) => {
@ -1246,6 +1264,13 @@ impl Stmt {
return false;
}
}
Self::FnCall(x, _) => {
for s in &x.args {
if !s.walk(path, on_node) {
return false;
}
}
}
Self::Block(x, _) => {
for s in x {
if !s.walk(path, on_node) {
@ -1370,14 +1395,14 @@ impl OpAssignment {
///
/// This type is volatile and may change.
#[derive(Clone, Copy, Eq, PartialEq, Hash, Default)]
pub struct FnCallHash {
pub struct FnCallHashes {
/// Pre-calculated hash for a script-defined function ([`None`] if native functions only).
pub script: Option<u64>,
/// Pre-calculated hash for a native Rust function with no parameter types.
pub native: u64,
}
impl fmt::Debug for FnCallHash {
impl fmt::Debug for FnCallHashes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(script) = self.script {
if script == self.native {
@ -1391,7 +1416,7 @@ impl fmt::Debug for FnCallHash {
}
}
impl FnCallHash {
impl FnCallHashes {
/// Create a [`FnCallHash`] with only the native Rust hash.
#[inline(always)]
pub fn from_native(hash: u64) -> Self {
@ -1443,23 +1468,28 @@ impl FnCallHash {
/// # Volatile Data Structure
///
/// This type is volatile and may change.
#[derive(Clone, Default, Hash)]
#[derive(Debug, Clone, Default, Hash)]
pub struct FnCallExpr {
/// Pre-calculated hash.
pub hash: FnCallHash,
/// Does this function call capture the parent scope?
pub capture: bool,
/// Namespace of the function, if any.
pub namespace: Option<NamespaceRef>,
/// Pre-calculated hashes.
pub hashes: FnCallHashes,
/// List of function call argument expressions.
pub args: StaticVec<Expr>,
/// List of function call arguments that are constants.
pub constant_args: smallvec::SmallVec<[(Dynamic, Position); 2]>,
/// Namespace of the function, if any. Boxed because it occurs rarely.
pub namespace: Option<NamespaceRef>,
/// Function name.
pub name: Identifier,
/// Does this function call capture the parent scope?
pub capture: bool,
}
impl FnCallExpr {
/// Does this function call contain a qualified namespace?
#[inline(always)]
pub fn is_qualified(&self) -> bool {
self.namespace.is_some()
}
/// Are there no arguments to this function call?
#[inline(always)]
pub fn is_args_empty(&self) -> bool {
@ -1467,7 +1497,7 @@ impl FnCallExpr {
}
/// Get the number of arguments to this function call.
#[inline(always)]
pub fn num_args(&self) -> usize {
pub fn args_count(&self) -> usize {
self.args.len() + self.constant_args.len()
}
}
@ -1609,8 +1639,6 @@ pub enum Expr {
CharConstant(char, Position),
/// [String][ImmutableString] constant.
StringConstant(ImmutableString, Position),
/// [`FnPtr`] constant.
FnPointer(ImmutableString, Position),
/// An interpolated [string][ImmutableString].
InterpolatedString(Box<StaticVec<Expr>>),
/// [ expr, ... ]
@ -1664,32 +1692,59 @@ impl Default for Expr {
impl fmt::Debug for Expr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
#[cfg(not(feature = "no_position"))]
Self::DynamicConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos),
#[cfg(not(feature = "no_position"))]
Self::BoolConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos),
#[cfg(not(feature = "no_position"))]
Self::IntegerConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos),
#[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "no_position"))]
Self::FloatConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos),
#[cfg(not(feature = "no_position"))]
Self::CharConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos),
#[cfg(not(feature = "no_position"))]
Self::StringConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos),
Self::FnPointer(value, pos) => write!(f, "Fn({:?}) @ {:?}", value, pos),
#[cfg(not(feature = "no_position"))]
Self::Unit(pos) => write!(f, "() @ {:?}", pos),
#[cfg(feature = "no_position")]
Self::DynamicConstant(value, _) => write!(f, "{:?}", value),
#[cfg(feature = "no_position")]
Self::BoolConstant(value, _) => write!(f, "{:?}", value),
#[cfg(feature = "no_position")]
Self::IntegerConstant(value, _) => write!(f, "{:?}", value),
#[cfg(not(feature = "no_float"))]
#[cfg(feature = "no_position")]
Self::FloatConstant(value, _) => write!(f, "{:?}", value),
#[cfg(feature = "no_position")]
Self::CharConstant(value, _) => write!(f, "{:?}", value),
#[cfg(feature = "no_position")]
Self::StringConstant(value, _) => write!(f, "{:?}", value),
#[cfg(feature = "no_position")]
Self::Unit(_) => f.write_str("()"),
Self::InterpolatedString(x) => {
f.write_str("InterpolatedString")?;
f.debug_list().entries(x.iter()).finish()
}
Self::Array(x, pos) => {
Self::Array(x, _pos) => {
f.write_str("Array")?;
f.debug_list().entries(x.iter()).finish()?;
write!(f, " @ {:?}", pos)
#[cfg(not(feature = "no_position"))]
write!(f, " @ {:?}", _pos)?;
Ok(())
}
Self::Map(x, pos) => {
Self::Map(x, _pos) => {
f.write_str("Map")?;
f.debug_map()
.entries(x.0.iter().map(|(k, v)| (k, v)))
.finish()?;
write!(f, " @ {:?}", pos)
#[cfg(not(feature = "no_position"))]
write!(f, " @ {:?}", _pos)?;
Ok(())
}
Self::Variable(i, pos, x) => {
Self::Variable(i, _pos, x) => {
f.write_str("Variable(")?;
match x.1 {
Some((_, ref namespace)) => write!(f, "{}", namespace)?,
@ -1700,21 +1755,29 @@ impl fmt::Debug for Expr {
Some(n) => write!(f, ", {}", n)?,
_ => (),
}
write!(f, ") @ {:?}", pos)
f.write_str(")")?;
#[cfg(not(feature = "no_position"))]
write!(f, " @ {:?}", _pos)?;
Ok(())
}
#[cfg(not(feature = "no_position"))]
Self::Property(x) => write!(f, "Property({:?} @ {:?})", x.2.name, x.2.pos),
#[cfg(feature = "no_position")]
Self::Property(x) => write!(f, "Property({:?})", x.2.name),
Self::Stmt(x) => {
f.write_str("Stmt")?;
f.debug_list().entries(x.0.iter()).finish()?;
write!(f, " @ {:?}", x.1)
#[cfg(not(feature = "no_position"))]
write!(f, " @ {:?}", x.1)?;
Ok(())
}
Self::FnCall(x, pos) => {
Self::FnCall(x, _pos) => {
let mut ff = f.debug_struct("FnCall");
if let Some(ref ns) = x.namespace {
ff.field("namespace", ns);
}
ff.field("name", &x.name)
.field("hash", &x.hash)
.field("hash", &x.hashes)
.field("args", &x.args);
if !x.constant_args.is_empty() {
ff.field("constant_args", &x.constant_args);
@ -1723,9 +1786,11 @@ impl fmt::Debug for Expr {
ff.field("capture", &x.capture);
}
ff.finish()?;
write!(f, " @ {:?}", pos)
#[cfg(not(feature = "no_position"))]
write!(f, " @ {:?}", _pos)?;
Ok(())
}
Self::Dot(x, pos) | Self::Index(x, pos) | Self::And(x, pos) | Self::Or(x, pos) => {
Self::Dot(x, _pos) | Self::Index(x, _pos) | Self::And(x, _pos) | Self::Or(x, _pos) => {
let op_name = match self {
Self::Dot(_, _) => "Dot",
Self::Index(_, _) => "Index",
@ -1738,11 +1803,15 @@ impl fmt::Debug for Expr {
.field("lhs", &x.lhs)
.field("rhs", &x.rhs)
.finish()?;
write!(f, " @ {:?}", pos)
#[cfg(not(feature = "no_position"))]
write!(f, " @ {:?}", _pos)?;
Ok(())
}
Self::Custom(x, pos) => {
Self::Custom(x, _pos) => {
f.debug_tuple("Custom").field(x).finish()?;
write!(f, " @ {:?}", pos)
#[cfg(not(feature = "no_position"))]
write!(f, " @ {:?}", _pos)?;
Ok(())
}
}
}
@ -1761,10 +1830,6 @@ impl Expr {
Self::FloatConstant(x, _) => (*x).into(),
Self::CharConstant(x, _) => (*x).into(),
Self::StringConstant(x, _) => x.clone().into(),
Self::FnPointer(x, _) => Dynamic(Union::FnPtr(
Box::new(FnPtr::new_unchecked(x.clone(), Default::default())),
AccessMode::ReadOnly,
)),
Self::BoolConstant(x, _) => (*x).into(),
Self::Unit(_) => Dynamic::UNIT,
@ -1772,7 +1837,7 @@ impl Expr {
Self::Array(x, _) if self.is_constant() => {
let mut arr = Array::with_capacity(x.len());
arr.extend(x.iter().map(|v| v.get_constant_value().unwrap()));
arr.into()
Dynamic::from_array(arr)
}
#[cfg(not(feature = "no_object"))]
@ -1781,7 +1846,7 @@ impl Expr {
x.0.iter().for_each(|(k, v)| {
*map.get_mut(k.name.as_str()).unwrap() = v.get_constant_value().unwrap()
});
map.into()
Dynamic::from_map(map)
}
_ => return None,
@ -1816,7 +1881,6 @@ impl Expr {
Self::CharConstant(_, pos) => *pos,
Self::StringConstant(_, pos) => *pos,
Self::InterpolatedString(x) => x.first().unwrap().position(),
Self::FnPointer(_, pos) => *pos,
Self::Array(_, pos) => *pos,
Self::Map(_, pos) => *pos,
Self::Property(x) => (x.2).pos,
@ -1848,7 +1912,6 @@ impl Expr {
Self::InterpolatedString(x) => {
x.first_mut().unwrap().set_position(new_pos);
}
Self::FnPointer(_, pos) => *pos = new_pos,
Self::Array(_, pos) => *pos = new_pos,
Self::Map(_, pos) => *pos = new_pos,
Self::Variable(_, pos, _) => *pos = new_pos,
@ -1904,7 +1967,6 @@ impl Expr {
| Self::IntegerConstant(_, _)
| Self::CharConstant(_, _)
| Self::StringConstant(_, _)
| Self::FnPointer(_, _)
| Self::Unit(_) => true,
Self::InterpolatedString(x) | Self::Array(x, _) => x.iter().all(Self::is_constant),
@ -1931,7 +1993,6 @@ impl Expr {
| Self::BoolConstant(_, _)
| Self::IntegerConstant(_, _)
| Self::CharConstant(_, _)
| Self::FnPointer(_, _)
| Self::And(_, _)
| Self::Or(_, _)
| Self::Unit(_) => false,
@ -2044,6 +2105,7 @@ mod tests {
assert_eq!(size_of::<Dynamic>(), 16);
assert_eq!(size_of::<Option<Dynamic>>(), 16);
#[cfg(not(feature = "no_position"))]
assert_eq!(size_of::<Position>(), 4);
assert_eq!(size_of::<ast::Expr>(), 16);
assert_eq!(size_of::<Option<ast::Expr>>(), 16);
@ -2052,7 +2114,10 @@ mod tests {
assert_eq!(size_of::<FnPtr>(), 96);
assert_eq!(size_of::<Scope>(), 288);
assert_eq!(size_of::<LexError>(), 56);
assert_eq!(size_of::<ParseError>(), 16);
assert_eq!(
size_of::<ParseError>(),
if cfg!(feature = "no_position") { 8 } else { 16 }
);
assert_eq!(size_of::<EvalAltResult>(), 72);
}
}

View File

@ -1,7 +1,7 @@
Rhai Tools
==========
Tools written in Rhai.
Tools for running Rhai scripts.
How to Run

View File

@ -55,8 +55,9 @@ fn print_help() {
}
fn main() {
println!("Rhai REPL tool");
println!("==============");
let title = format!("Rhai REPL tool (version {})", env!("CARGO_PKG_VERSION"));
println!("{}", title);
println!("{0:=<1$}", "", title.len());
print_help();
// Initialize scripting engine

View File

@ -407,6 +407,13 @@ impl Hash for Dynamic {
value.hash(state);
})
}
Union::FnPtr(f, _) if f.is_curried() => {
unimplemented!(
"{} with curried arguments cannot be hashed",
self.type_name()
)
}
Union::FnPtr(f, _) => f.fn_name().hash(state),
#[cfg(not(feature = "no_closure"))]
Union::Shared(cell, _) => {
@ -1717,13 +1724,21 @@ impl From<&ImmutableString> for Dynamic {
impl From<&crate::Identifier> for Dynamic {
#[inline(always)]
fn from(value: &crate::Identifier) -> Self {
std::string::ToString::to_string(value).into()
value.to_string().into()
}
}
#[cfg(not(feature = "no_index"))]
impl<T: Variant + Clone> From<std::vec::Vec<T>> for Dynamic {
impl Dynamic {
/// Create a [`Dynamc`] from an [`Array`].
#[inline(always)]
fn from(value: std::vec::Vec<T>) -> Self {
pub(crate) fn from_array(array: Array) -> Self {
Self(Union::Array(Box::new(array), AccessMode::ReadWrite))
}
}
#[cfg(not(feature = "no_index"))]
impl<T: Variant + Clone> From<Vec<T>> for Dynamic {
#[inline(always)]
fn from(value: Vec<T>) -> Self {
Self(Union::Array(
Box::new(value.into_iter().map(Dynamic::from).collect()),
AccessMode::ReadWrite,
@ -1751,6 +1766,14 @@ impl<T: Variant + Clone> std::iter::FromIterator<T> for Dynamic {
}
}
#[cfg(not(feature = "no_object"))]
impl Dynamic {
/// Create a [`Dynamc`] from a [`Map`].
#[inline(always)]
pub(crate) fn from_map(map: Map) -> Self {
Self(Union::Map(Box::new(map), AccessMode::ReadWrite))
}
}
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_std"))]
impl<K: Into<crate::Identifier>, T: Variant + Clone> From<std::collections::HashMap<K, T>>
for Dynamic

View File

@ -13,8 +13,8 @@ use crate::r#unsafe::unsafe_cast_var_name_to_lifetime;
use crate::syntax::CustomSyntax;
use crate::utils::get_hasher;
use crate::{
Dynamic, EvalAltResult, FnPtr, Identifier, ImmutableString, Module, Position, RhaiResult,
Scope, Shared, StaticVec,
Dynamic, EvalAltResult, Identifier, ImmutableString, Module, Position, RhaiResult, Scope,
Shared, StaticVec,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -35,7 +35,7 @@ use crate::{calc_fn_hash, Array};
use crate::Map;
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
use crate::ast::FnCallHash;
use crate::ast::FnCallHashes;
pub type Precedence = NonZeroU8;
@ -574,6 +574,7 @@ impl State {
self.scope_level == 0
}
/// Get a mutable reference to the current function resolution cache.
#[inline(always)]
pub fn fn_resolution_cache_mut(&mut self) -> &mut FnResolutionCache {
if self.fn_resolution_caches.0.is_empty() {
self.fn_resolution_caches.0.push(BTreeMap::new());
@ -582,6 +583,7 @@ impl State {
}
/// Push an empty function resolution cache onto the stack and make it current.
#[allow(dead_code)]
#[inline(always)]
pub fn push_fn_resolution_cache(&mut self) {
self.fn_resolution_caches
.0
@ -592,6 +594,7 @@ impl State {
/// # Panics
///
/// 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.pop().unwrap();
cache.clear();
@ -836,6 +839,8 @@ fn default_debug(_s: &str, _source: Option<&str>, _pos: Position) {
#[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);
} else {
println!("{:?} | {}", _pos, _s);
}
@ -1186,7 +1191,7 @@ impl Engine {
let val_type_name = target.type_name();
let ((_, val_pos), _) = new_val;
let hash_set = FnCallHash::from_native(calc_fn_hash(
let hash_set = FnCallHashes::from_native(calc_fn_hash(
std::iter::empty(),
FN_IDX_SET,
3,
@ -1227,11 +1232,11 @@ impl Engine {
ChainType::Dot => {
match rhs {
// xxx.fn_name(arg_expr_list)
Expr::FnCall(x, pos) if x.namespace.is_none() && new_val.is_none() => {
let FnCallExpr { name, hash, .. } = x.as_ref();
Expr::FnCall(x, pos) if !x.is_qualified() && new_val.is_none() => {
let FnCallExpr { name, hashes, .. } = x.as_ref();
let mut args = idx_val.as_fn_call_args();
self.make_method_call(
mods, state, lib, name, *hash, target, &mut args, *pos, level,
mods, state, lib, name, *hashes, target, &mut args, *pos, level,
)
}
// xxx.fn_name(...) = ???
@ -1272,7 +1277,7 @@ impl Engine {
let ((mut new_val, new_pos), (op_info, op_pos)) = new_val.unwrap();
if op_info.is_some() {
let hash = FnCallHash::from_native(*hash_get);
let hash = FnCallHashes::from_native(*hash_get);
let mut args = [target.as_mut()];
let (mut orig_val, _) = self.exec_fn_call(
mods, state, lib, getter, hash, &mut args, is_ref, true, *pos,
@ -1285,7 +1290,7 @@ impl Engine {
new_val = orig_val;
}
let hash = FnCallHash::from_native(*hash_set);
let hash = FnCallHashes::from_native(*hash_set);
let mut args = [target.as_mut(), &mut new_val];
self.exec_fn_call(
mods, state, lib, setter, hash, &mut args, is_ref, true, *pos, None,
@ -1296,7 +1301,7 @@ impl Engine {
// xxx.id
Expr::Property(x) => {
let ((getter, hash_get), _, Ident { pos, .. }) = x.as_ref();
let hash = FnCallHash::from_native(*hash_get);
let hash = FnCallHashes::from_native(*hash_get);
let mut args = [target.as_mut()];
self.exec_fn_call(
mods, state, lib, getter, hash, &mut args, is_ref, true, *pos, None,
@ -1316,11 +1321,11 @@ impl Engine {
)?
}
// {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr
Expr::FnCall(x, pos) if x.namespace.is_none() => {
let FnCallExpr { name, hash, .. } = x.as_ref();
Expr::FnCall(x, pos) if !x.is_qualified() => {
let FnCallExpr { name, hashes, .. } = x.as_ref();
let mut args = idx_val.as_fn_call_args();
let (val, _) = self.make_method_call(
mods, state, lib, name, *hash, target, &mut args, *pos, level,
mods, state, lib, name, *hashes, target, &mut args, *pos, level,
)?;
val.into()
}
@ -1345,8 +1350,8 @@ impl Engine {
Expr::Property(p) => {
let ((getter, hash_get), (setter, hash_set), Ident { pos, .. }) =
p.as_ref();
let hash_get = FnCallHash::from_native(*hash_get);
let hash_set = FnCallHash::from_native(*hash_set);
let hash_get = FnCallHashes::from_native(*hash_get);
let hash_set = FnCallHashes::from_native(*hash_set);
let arg_values = &mut [target.as_mut(), &mut Default::default()];
let args = &mut arg_values[..1];
let (mut val, updated) = self.exec_fn_call(
@ -1394,11 +1399,11 @@ impl Engine {
Ok((result, may_be_changed))
}
// xxx.fn_name(arg_expr_list)[expr] | xxx.fn_name(arg_expr_list).expr
Expr::FnCall(f, pos) if f.namespace.is_none() => {
let FnCallExpr { name, hash, .. } = f.as_ref();
Expr::FnCall(f, pos) if !f.is_qualified() => {
let FnCallExpr { name, hashes, .. } = f.as_ref();
let mut args = idx_val.as_fn_call_args();
let (mut val, _) = self.make_method_call(
mods, state, lib, name, *hash, target, &mut args, *pos, level,
mods, state, lib, name, *hashes, target, &mut args, *pos, level,
)?;
let val = &mut val;
let target = &mut val.into();
@ -1508,7 +1513,7 @@ impl Engine {
self.inc_operations(state, expr.position())?;
match expr {
Expr::FnCall(x, _) if parent_chain_type == ChainType::Dot && x.namespace.is_none() => {
Expr::FnCall(x, _) if parent_chain_type == ChainType::Dot && !x.is_qualified() => {
let mut arg_positions: StaticVec<_> = Default::default();
let mut arg_values = x
@ -1547,7 +1552,7 @@ impl Engine {
}
Expr::Property(_) => unreachable!("unexpected Expr::Property for indexing"),
Expr::FnCall(x, _)
if parent_chain_type == ChainType::Dot && x.namespace.is_none() =>
if parent_chain_type == ChainType::Dot && !x.is_qualified() =>
{
let mut arg_positions: StaticVec<_> = Default::default();
@ -1704,7 +1709,7 @@ impl Engine {
let type_name = target.type_name();
let args = &mut [target, &mut _idx];
let hash_get =
FnCallHash::from_native(calc_fn_hash(std::iter::empty(), FN_IDX_GET, 2));
FnCallHashes::from_native(calc_fn_hash(std::iter::empty(), FN_IDX_GET, 2));
self.exec_fn_call(
_mods, state, _lib, FN_IDX_GET, hash_get, args, _is_ref, true, idx_pos, None,
_level,
@ -1749,7 +1754,6 @@ impl Engine {
Expr::FloatConstant(x, _) => Ok((*x).into()),
Expr::StringConstant(x, _) => Ok(x.clone().into()),
Expr::CharConstant(x, _) => Ok((*x).into()),
Expr::FnPointer(x, _) => Ok(FnPtr::new_unchecked(x.clone(), Default::default()).into()),
Expr::Variable(None, var_pos, x) if x.0.is_none() && x.2 == KEYWORD_THIS => this_ptr
.as_deref()
@ -1828,40 +1832,40 @@ impl Engine {
Ok(map.into())
}
// Normal function call
Expr::FnCall(x, pos) if x.namespace.is_none() => {
let FnCallExpr {
name,
capture,
hash,
args,
constant_args: c_args,
..
} = x.as_ref();
self.make_function_call(
scope, mods, state, lib, this_ptr, name, args, c_args, *hash, *pos, *capture,
level,
)
}
// Namespace-qualified function call
Expr::FnCall(x, pos) if x.namespace.is_some() => {
Expr::FnCall(x, pos) if x.is_qualified() => {
let FnCallExpr {
name,
namespace,
hash,
hashes,
args,
constant_args: c_args,
..
} = x.as_ref();
let namespace = namespace.as_ref();
let hash = hash.native_hash();
let hash = hashes.native_hash();
self.make_qualified_function_call(
scope, mods, state, lib, this_ptr, namespace, name, args, c_args, hash, *pos,
level,
)
}
// Normal function call
Expr::FnCall(x, pos) => {
let FnCallExpr {
name,
capture,
hashes,
args,
constant_args: c_args,
..
} = x.as_ref();
self.make_function_call(
scope, mods, state, lib, this_ptr, name, args, c_args, *hashes, *pos, *capture,
level,
)
}
Expr::And(x, _) => {
Ok((self
.eval_expr(scope, mods, state, lib, this_ptr, &x.lhs, level)?
@ -1916,7 +1920,11 @@ impl Engine {
_ => unreachable!("expression cannot be evaluated: {:?}", expr),
};
self.check_data_size(result, expr.position())
#[cfg(not(feature = "unchecked"))]
self.check_data_size(&result)
.map_err(|err| err.fill_position(expr.position()))?;
result
}
/// Evaluate a statements block.
@ -2167,29 +2175,26 @@ impl Engine {
}
// If statement
Stmt::If(expr, x, _) => self
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.as_bool()
.map_err(|err| self.make_type_mismatch_err::<bool>(err, expr.position()))
.and_then(|guard_val| {
if guard_val {
if !x.0.is_empty() {
self.eval_stmt_block(
scope, mods, state, lib, this_ptr, &x.0, true, level,
)
} else {
Ok(Dynamic::UNIT)
}
Stmt::If(expr, x, _) => {
let guard_val = self
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.as_bool()
.map_err(|err| self.make_type_mismatch_err::<bool>(err, expr.position()))?;
if guard_val {
if !x.0.is_empty() {
self.eval_stmt_block(scope, mods, state, lib, this_ptr, &x.0, true, level)
} else {
if !x.1.is_empty() {
self.eval_stmt_block(
scope, mods, state, lib, this_ptr, &x.1, true, level,
)
} else {
Ok(Dynamic::UNIT)
}
Ok(Dynamic::UNIT)
}
}),
} else {
if !x.1.is_empty() {
self.eval_stmt_block(scope, mods, state, lib, this_ptr, &x.1, true, level)
} else {
Ok(Dynamic::UNIT)
}
}
}
// Switch statement
Stmt::Switch(match_expr, x, _) => {
@ -2259,17 +2264,16 @@ impl Engine {
if !condition {
return Ok(Dynamic::UNIT);
}
if body.is_empty() {
continue;
}
match self.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level) {
Ok(_) => (),
Err(err) => match *err {
EvalAltResult::LoopBreak(false, _) => (),
EvalAltResult::LoopBreak(true, _) => return Ok(Dynamic::UNIT),
_ => return Err(err),
},
if !body.is_empty() {
match self.eval_stmt_block(scope, mods, state, lib, this_ptr, body, true, level)
{
Ok(_) => (),
Err(err) => match *err {
EvalAltResult::LoopBreak(false, _) => (),
EvalAltResult::LoopBreak(true, _) => return Ok(Dynamic::UNIT),
_ => return Err(err),
},
}
}
},
@ -2287,18 +2291,13 @@ impl Engine {
}
}
if self
let condition = self
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.as_bool()
.map_err(|err| self.make_type_mismatch_err::<bool>(err, expr.position()))?
{
if !*is_while {
return Ok(Dynamic::UNIT);
}
} else {
if *is_while {
return Ok(Dynamic::UNIT);
}
.map_err(|err| self.make_type_mismatch_err::<bool>(err, expr.position()))?;
if condition ^ *is_while {
return Ok(Dynamic::UNIT);
}
},
@ -2365,9 +2364,11 @@ impl Engine {
continue;
}
match self.eval_stmt_block(
let result = self.eval_stmt_block(
scope, mods, state, lib, this_ptr, statements, true, level,
) {
);
match result {
Ok(_) => (),
Err(err) => match *err {
EvalAltResult::LoopBreak(false, _) => (),
@ -2391,6 +2392,40 @@ impl Engine {
// Break statement
Stmt::Break(pos) => EvalAltResult::LoopBreak(true, *pos).into(),
// Namespace-qualified function call
Stmt::FnCall(x, pos) if x.is_qualified() => {
let FnCallExpr {
name,
namespace,
hashes,
args,
constant_args: c_args,
..
} = x.as_ref();
let namespace = namespace.as_ref();
let hash = hashes.native_hash();
self.make_qualified_function_call(
scope, mods, state, lib, this_ptr, namespace, name, args, c_args, hash, *pos,
level,
)
}
// Normal function call
Stmt::FnCall(x, pos) => {
let FnCallExpr {
name,
capture,
hashes,
args,
constant_args: c_args,
..
} = x.as_ref();
self.make_function_call(
scope, mods, state, lib, this_ptr, name, args, c_args, *hashes, *pos, *capture,
level,
)
}
// Try/Catch statement
Stmt::TryCatch(x, _, _) => {
let (try_stmt, err_var, catch_stmt) = x.as_ref();
@ -2478,25 +2513,25 @@ impl Engine {
}
// Return value
Stmt::Return(ReturnType::Return, Some(expr), pos) => {
let value = self
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.flatten();
EvalAltResult::Return(value, *pos).into()
}
Stmt::Return(ReturnType::Return, Some(expr), pos) => EvalAltResult::Return(
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.flatten(),
*pos,
)
.into(),
// Empty return
Stmt::Return(ReturnType::Return, None, pos) => {
EvalAltResult::Return(Default::default(), *pos).into()
EvalAltResult::Return(Dynamic::UNIT, *pos).into()
}
// Throw value
Stmt::Return(ReturnType::Exception, Some(expr), pos) => {
let value = self
.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.flatten();
EvalAltResult::ErrorRuntime(value, *pos).into()
}
Stmt::Return(ReturnType::Exception, Some(expr), pos) => EvalAltResult::ErrorRuntime(
self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
.flatten(),
*pos,
)
.into(),
// Empty throw
Stmt::Return(ReturnType::Exception, None, pos) => {
@ -2641,24 +2676,18 @@ impl Engine {
}
};
self.check_data_size(result, stmt.position())
}
#[cfg(not(feature = "unchecked"))]
self.check_data_size(&result)
.map_err(|err| err.fill_position(stmt.position()))?;
/// Check a result to ensure that the data size is within allowable limit.
/// [`Position`] in [`EvalAltResult`] may be None and should be set afterwards.
#[cfg(feature = "unchecked")]
#[inline(always)]
fn check_data_size(&self, result: RhaiResult, _pos: Position) -> RhaiResult {
result
}
/// Check a result to ensure that the data size is within allowable limit.
#[cfg(not(feature = "unchecked"))]
#[inline(always)]
fn check_data_size(&self, result: RhaiResult, pos: Position) -> RhaiResult {
// Simply return all errors
fn check_data_size(&self, result: &RhaiResult) -> Result<(), Box<EvalAltResult>> {
if result.is_err() {
return result;
return Ok(());
}
// If no data size limits, just return
@ -2673,7 +2702,7 @@ impl Engine {
}
if !_has_limit {
return result;
return Ok(());
}
// Recursively calculate the size of a value (especially `Array` and `Map`)
@ -2735,7 +2764,11 @@ impl Engine {
.max_string_size
.map_or(usize::MAX, NonZeroUsize::get)
{
return EvalAltResult::ErrorDataTooLarge("Length of string".to_string(), pos).into();
return EvalAltResult::ErrorDataTooLarge(
"Length of string".to_string(),
Position::NONE,
)
.into();
}
#[cfg(not(feature = "no_index"))]
@ -2745,7 +2778,8 @@ impl Engine {
.max_array_size
.map_or(usize::MAX, NonZeroUsize::get)
{
return EvalAltResult::ErrorDataTooLarge("Size of array".to_string(), pos).into();
return EvalAltResult::ErrorDataTooLarge("Size of array".to_string(), Position::NONE)
.into();
}
#[cfg(not(feature = "no_object"))]
@ -2755,14 +2789,17 @@ impl Engine {
.max_map_size
.map_or(usize::MAX, NonZeroUsize::get)
{
return EvalAltResult::ErrorDataTooLarge("Size of object map".to_string(), pos).into();
return EvalAltResult::ErrorDataTooLarge(
"Size of object map".to_string(),
Position::NONE,
)
.into();
}
result
Ok(())
}
/// Check if the number of operations stay within limit.
#[inline]
pub(crate) fn inc_operations(
&self,
state: &mut State,

View File

@ -59,7 +59,7 @@ impl Engine {
#[cfg(feature = "metadata")]
let mut param_type_names: crate::StaticVec<_> = F::param_names()
.iter()
.map(|ty| std::format!("_: {}", self.map_type_name(ty)))
.map(|ty| format!("_: {}", self.map_type_name(ty)))
.collect();
#[cfg(feature = "metadata")]
@ -119,7 +119,7 @@ impl Engine {
#[cfg(feature = "metadata")]
let param_type_names: crate::StaticVec<_> = F::param_names()
.iter()
.map(|ty| std::format!("_: {}", self.map_type_name(ty)))
.map(|ty| format!("_: {}", self.map_type_name(ty)))
.chain(std::iter::once(
self.map_type_name(F::return_type_name()).into(),
))
@ -1172,7 +1172,7 @@ impl Engine {
let mut f = std::fs::File::open(path.clone()).map_err(|err| {
EvalAltResult::ErrorSystem(
std::format!("Cannot open script file '{}'", path.to_string_lossy()),
format!("Cannot open script file '{}'", path.to_string_lossy()),
err.into(),
)
})?;
@ -1181,7 +1181,7 @@ impl Engine {
f.read_to_string(&mut contents).map_err(|err| {
EvalAltResult::ErrorSystem(
std::format!("Cannot read script file '{}'", path.to_string_lossy()),
format!("Cannot read script file '{}'", path.to_string_lossy()),
err.into(),
)
})?;
@ -1802,7 +1802,7 @@ impl Engine {
///
/// let engine = Engine::new();
///
/// let ast = engine.compile(r"
/// let ast = engine.compile("
/// fn add(x, y) { len(x) + y + foo }
/// fn add1(x) { len(x) + 1 + foo }
/// fn bar() { foo/2 }
@ -1873,7 +1873,7 @@ impl Engine {
///
/// let engine = Engine::new();
///
/// let ast = engine.compile(r"
/// let ast = engine.compile("
/// fn add(x, y) { len(x) + y + foo }
/// fn add1(x) { len(x) + 1 + foo }
/// fn bar() { foo/2 }
@ -2010,16 +2010,13 @@ impl Engine {
/// 2) Functions in registered sub-modules
/// 3) Functions in packages (optional)
#[cfg(feature = "metadata")]
pub fn gen_fn_signatures(&self, include_packages: bool) -> std::vec::Vec<String> {
let mut signatures: std::vec::Vec<_> = Default::default();
pub fn gen_fn_signatures(&self, include_packages: bool) -> Vec<String> {
let mut signatures: Vec<_> = Default::default();
signatures.extend(self.global_namespace.gen_fn_signatures());
self.global_sub_modules.iter().for_each(|(name, m)| {
signatures.extend(
m.gen_fn_signatures()
.map(|f| std::format!("{}::{}", name, f)),
)
signatures.extend(m.gen_fn_signatures().map(|f| format!("{}::{}", name, f)))
});
if include_packages {
@ -2169,7 +2166,10 @@ impl Engine {
/// ast.set_source("world");
/// engine.consume_ast(&ast)?;
///
/// #[cfg(not(feature = "no_position"))]
/// assert_eq!(*result.read().unwrap(), r#"world @ 1:18 > "hello""#);
/// #[cfg(feature = "no_position")]
/// assert_eq!(*result.read().unwrap(), r#"world @ none > "hello""#);
/// # Ok(())
/// # }
/// ```

View File

@ -5,7 +5,8 @@
use crate::dynamic::Variant;
use crate::{Dynamic, StaticVec};
use std::vec::Vec;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
/// Trait that parses arguments to a function call.
///

View File

@ -1,6 +1,6 @@
//! Implement function-calling mechanism for [`Engine`].
use crate::ast::FnCallHash;
use crate::ast::FnCallHashes;
use crate::engine::{
FnResolutionCacheEntry, Imports, State, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR,
KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
@ -463,7 +463,7 @@ impl Engine {
) -> RhaiResult {
#[inline(always)]
fn make_error(
name: std::string::String,
name: String,
fn_def: &crate::ast::ScriptFnDef,
state: &State,
err: Box<EvalAltResult>,
@ -623,7 +623,7 @@ impl Engine {
state: &mut State,
lib: &[&Module],
fn_name: &str,
hash: FnCallHash,
hash: FnCallHashes,
args: &mut FnCallArgs,
is_ref: bool,
_is_method: bool,
@ -893,7 +893,7 @@ impl Engine {
state: &mut State,
lib: &[&Module],
fn_name: &str,
mut hash: FnCallHash,
mut hash: FnCallHashes,
target: &mut crate::engine::Target,
(call_args, call_arg_positions): &mut (StaticVec<Dynamic>, StaticVec<Position>),
pos: Position,
@ -913,7 +913,7 @@ impl Engine {
let fn_name = fn_ptr.fn_name();
let args_len = call_args.len() + fn_ptr.curry().len();
// Recalculate hashes
let new_hash = FnCallHash::from_script(calc_fn_hash(empty(), fn_name, args_len));
let new_hash = FnCallHashes::from_script(calc_fn_hash(empty(), fn_name, args_len));
// Arguments are passed as-is, adding the curried arguments
let mut curry = fn_ptr.curry().iter().cloned().collect::<StaticVec<_>>();
let mut args = curry
@ -948,7 +948,7 @@ impl Engine {
let fn_name = fn_ptr.fn_name();
let args_len = call_args.len() + fn_ptr.curry().len();
// Recalculate hash
let new_hash = FnCallHash::from_script_and_native(
let new_hash = FnCallHashes::from_script_and_native(
calc_fn_hash(empty(), fn_name, args_len),
calc_fn_hash(empty(), fn_name, args_len + 1),
);
@ -1022,7 +1022,7 @@ impl Engine {
call_arg_positions.insert(i, Position::NONE);
});
// Recalculate the hash based on the new function name and new arguments
hash = FnCallHash::from_script_and_native(
hash = FnCallHashes::from_script_and_native(
calc_fn_hash(empty(), fn_name, call_args.len()),
calc_fn_hash(empty(), fn_name, call_args.len() + 1),
);
@ -1060,7 +1060,7 @@ impl Engine {
fn_name: &str,
args_expr: &[Expr],
constant_args: &[(Dynamic, Position)],
mut hash: FnCallHash,
mut hashes: FnCallHashes,
pos: Position,
capture_scope: bool,
level: usize,
@ -1108,10 +1108,10 @@ impl Engine {
// Recalculate hash
let args_len = total_args + curry.len();
hash = if !hash.is_native_only() {
FnCallHash::from_script(calc_fn_hash(empty(), name, args_len))
hashes = if !hashes.is_native_only() {
FnCallHashes::from_script(calc_fn_hash(empty(), name, args_len))
} else {
FnCallHash::from_native(calc_fn_hash(empty(), name, args_len))
FnCallHashes::from_native(calc_fn_hash(empty(), name, args_len))
};
}
// Handle Fn()
@ -1345,7 +1345,7 @@ impl Engine {
}
self.exec_fn_call(
mods, state, lib, name, hash, &mut args, is_ref, false, pos, capture, level,
mods, state, lib, name, hashes, &mut args, is_ref, false, pos, capture, level,
)
.map(|(v, _)| v)
}

View File

@ -1,6 +1,6 @@
//! Module defining interfaces to native-Rust functions.
use crate::ast::{FnAccess, FnCallHash};
use crate::ast::{FnAccess, FnCallHashes};
use crate::engine::Imports;
use crate::plugin::PluginFunction;
use crate::token::is_valid_identifier;
@ -191,12 +191,12 @@ impl<'a> NativeCallContext<'a> {
let fn_name = fn_name.as_ref();
let hash = if is_method {
FnCallHash::from_script_and_native(
FnCallHashes::from_script_and_native(
calc_fn_hash(empty(), fn_name, args.len() - 1),
calc_fn_hash(empty(), fn_name, args.len()),
)
} else {
FnCallHash::from_script(calc_fn_hash(empty(), fn_name, args.len()))
FnCallHashes::from_script(calc_fn_hash(empty(), fn_name, args.len()))
};
self.engine()
@ -262,7 +262,7 @@ impl FnPtr {
}
/// Create a new function pointer without checking its parameters.
#[inline(always)]
pub(crate) fn new_unchecked(name: impl Into<Identifier>, curry: StaticVec<Dynamic>) -> Self {
pub(crate) fn new_unchecked(name: Identifier, curry: StaticVec<Dynamic>) -> Self {
Self(name.into(), curry)
}
/// Get the name of the function.

View File

@ -220,7 +220,7 @@ pub use token::{
#[cfg(feature = "internals")]
#[deprecated = "this type is volatile and may change"]
pub use ast::{
ASTNode, BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallExpr, FnCallHash, Ident,
ASTNode, BinaryExpr, CustomExpr, Expr, FloatWrapper, FnCallExpr, FnCallHashes, Ident,
OpAssignment, ReturnType, ScriptFnDef, Stmt, StmtBlock,
};

View File

@ -72,7 +72,7 @@ impl FuncInfo {
let mut sig = format!("{}(", self.name);
if !self.param_names.is_empty() {
let mut params: std::vec::Vec<String> =
let mut params: Vec<String> =
self.param_names.iter().map(|s| s.as_str().into()).collect();
let return_type = params.pop().unwrap_or_else(|| "()".into());
sig.push_str(&params.join(", "));
@ -200,7 +200,7 @@ impl fmt::Debug for Module {
&self
.functions
.values()
.map(|f| std::string::ToString::to_string(&f.func))
.map(|f| f.func.to_string())
.collect::<BTreeSet<_>>(),
);
}

View File

@ -7,8 +7,8 @@ use crate::fn_builtin::get_builtin_binary_op_fn;
use crate::parser::map_dynamic_to_expr;
use crate::utils::get_hasher;
use crate::{
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, ImmutableString, Module,
Position, Scope, StaticVec, AST,
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnPtr, ImmutableString,
Module, Position, Scope, StaticVec, AST,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -628,6 +628,17 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
let catch_block = mem::take(x.2.statements()).into_vec();
*x.2.statements() = optimize_stmt_block(catch_block, state, false, true, false).into();
}
// func(...)
Stmt::Expr(expr @ Expr::FnCall(_, _)) => {
optimize_expr(expr, state);
match expr {
Expr::FnCall(x, pos) => {
state.set_dirty();
*stmt = Stmt::FnCall(mem::take(x), *pos);
}
_ => (),
}
}
// {}
Stmt::Expr(Expr::Stmt(x)) if x.is_empty() => {
state.set_dirty();
@ -862,15 +873,16 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
}
// Fn
Expr::FnCall(x, pos)
if x.namespace.is_none() // Non-qualified
if !x.is_qualified() // Non-qualified
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations
&& x.num_args() == 1
&& x.args_count() == 1
&& x.constant_args.len() == 1
&& x.constant_args[0].0.is::<ImmutableString>()
&& x.name == KEYWORD_FN_PTR
=> {
state.set_dirty();
*expr = Expr::FnPointer(mem::take(&mut x.constant_args[0].0).take_immutable_string().unwrap(), *pos);
let fn_ptr = FnPtr::new_unchecked(mem::take(&mut x.constant_args[0].0).as_str_ref().unwrap().into(), Default::default());
*expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), *pos);
}
// Do not call some special keywords
@ -880,9 +892,9 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
// Call built-in operators
Expr::FnCall(x, pos)
if x.namespace.is_none() // Non-qualified
if !x.is_qualified() // Non-qualified
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations
&& x.num_args() == 2 // binary call
&& x.args_count() == 2 // binary call
&& x.args.iter().all(Expr::is_constant) // all arguments are constants
//&& !is_valid_identifier(x.name.chars()) // cannot be scripted
=> {
@ -893,7 +905,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
// Search for overloaded operators (can override built-in).
if !has_native_fn(state, x.hash.native_hash(), arg_types.as_ref()) {
if !has_native_fn(state, x.hashes.native_hash(), arg_types.as_ref()) {
if let Some(result) = get_builtin_binary_op_fn(x.name.as_ref(), &arg_values[0], &arg_values[1])
.and_then(|f| {
let ctx = (state.engine, x.name.as_ref(), state.lib).into();
@ -922,13 +934,13 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
// Eagerly call functions
Expr::FnCall(x, pos)
if x.namespace.is_none() // Non-qualified
if !x.is_qualified() // Non-qualified
&& state.optimization_level == OptimizationLevel::Full // full optimizations
&& x.args.iter().all(Expr::is_constant) // all arguments are constants
=> {
// First search for script-defined functions (can override built-in)
#[cfg(not(feature = "no_function"))]
let has_script_fn = state.lib.iter().any(|&m| m.get_script_fn(x.name.as_ref(), x.num_args()).is_some());
let has_script_fn = state.lib.iter().any(|&m| m.get_script_fn(x.name.as_ref(), x.args_count()).is_some());
#[cfg(feature = "no_function")]
let has_script_fn = false;

View File

@ -4,9 +4,6 @@ use std::ops::Range;
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
#[cfg(not(feature = "unchecked"))]
use std::string::ToString;
#[cfg(not(feature = "unchecked"))]
use num_traits::{CheckedAdd as Add, CheckedSub as Sub};
@ -209,8 +206,6 @@ def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
pub fn new(from: Decimal, to: Decimal, step: Decimal) -> Result<Self, Box<EvalAltResult>> {
#[cfg(not(feature = "unchecked"))]
if step.is_zero() {
use std::string::ToString;
return EvalAltResult::ErrorInFunctionCall("range".to_string(), "".to_string(),
Box::new(EvalAltResult::ErrorArithmetic("step value cannot be zero".to_string(), crate::Position::NONE)),
crate::Position::NONE,

View File

@ -103,7 +103,7 @@ mod print_debug_functions {
)]
pub fn format_array(ctx: NativeCallContext, array: &mut Array) -> ImmutableString {
let len = array.len();
let mut result = std::string::String::with_capacity(len * 5 + 2);
let mut result = String::with_capacity(len * 5 + 2);
result.push_str("[");
array.iter_mut().enumerate().for_each(|(i, x)| {
@ -130,7 +130,7 @@ mod print_debug_functions {
)]
pub fn format_map(ctx: NativeCallContext, map: &mut Map) -> ImmutableString {
let len = map.len();
let mut result = std::string::String::with_capacity(len * 5 + 3);
let mut result = String::with_capacity(len * 5 + 3);
result.push_str("#{");
map.iter_mut().enumerate().for_each(|(i, (k, v))| {

View File

@ -1,7 +1,7 @@
//! Main module defining the lexer and parser.
use crate::ast::{
BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHash, Ident, OpAssignment, ReturnType,
BinaryExpr, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ReturnType,
ScriptFnDef, Stmt, StmtBlock,
};
use crate::dynamic::{AccessMode, Union};
@ -15,8 +15,8 @@ use crate::token::{
};
use crate::utils::{get_hasher, IdentifierBuilder};
use crate::{
calc_fn_hash, Dynamic, Engine, Identifier, LexError, ParseError, ParseErrorType, Position,
Scope, Shared, StaticVec, AST,
calc_fn_hash, Dynamic, Engine, FnPtr, Identifier, LexError, ParseError, ParseErrorType,
Position, Scope, Shared, StaticVec, AST,
};
#[cfg(feature = "no_std")]
use std::prelude::v1::*;
@ -342,10 +342,10 @@ fn parse_fn_call(
calc_fn_hash(empty(), &id, 0)
};
let hash = if is_valid_identifier(id.chars()) {
FnCallHash::from_script(hash)
let hashes = if is_valid_identifier(id.chars()) {
FnCallHashes::from_script(hash)
} else {
FnCallHash::from_native(hash)
FnCallHashes::from_native(hash)
};
return Ok(Expr::FnCall(
@ -353,7 +353,7 @@ fn parse_fn_call(
name: state.get_identifier(id),
capture,
namespace,
hash,
hashes,
args,
..Default::default()
}),
@ -387,10 +387,10 @@ fn parse_fn_call(
calc_fn_hash(empty(), &id, args.len())
};
let hash = if is_valid_identifier(id.chars()) {
FnCallHash::from_script(hash)
let hashes = if is_valid_identifier(id.chars()) {
FnCallHashes::from_script(hash)
} else {
FnCallHash::from_native(hash)
FnCallHashes::from_native(hash)
};
return Ok(Expr::FnCall(
@ -398,7 +398,7 @@ fn parse_fn_call(
name: state.get_identifier(id),
capture,
namespace,
hash,
hashes,
args,
..Default::default()
}),
@ -1348,7 +1348,7 @@ fn parse_unary(
Ok(Expr::FnCall(
Box::new(FnCallExpr {
name: state.get_identifier("-"),
hash: FnCallHash::from_native(calc_fn_hash(empty(), "-", 1)),
hashes: FnCallHashes::from_native(calc_fn_hash(empty(), "-", 1)),
args,
..Default::default()
}),
@ -1374,7 +1374,7 @@ fn parse_unary(
Ok(Expr::FnCall(
Box::new(FnCallExpr {
name: state.get_identifier("+"),
hash: FnCallHash::from_native(calc_fn_hash(empty(), "+", 1)),
hashes: FnCallHashes::from_native(calc_fn_hash(empty(), "+", 1)),
args,
..Default::default()
}),
@ -1393,7 +1393,7 @@ fn parse_unary(
Ok(Expr::FnCall(
Box::new(FnCallExpr {
name: state.get_identifier("!"),
hash: FnCallHash::from_native(calc_fn_hash(empty(), "!", 1)),
hashes: FnCallHashes::from_native(calc_fn_hash(empty(), "!", 1)),
args,
..Default::default()
}),
@ -1415,20 +1415,20 @@ fn make_assignment_stmt<'a>(
rhs: Expr,
op_pos: Position,
) -> Result<Stmt, ParseError> {
fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Position {
fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Option<Position> {
match expr {
Expr::Index(x, _) | Expr::Dot(x, _) if parent_is_dot => match x.lhs {
Expr::Property(_) => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _))),
ref e => e.position(),
ref e => Some(e.position()),
},
Expr::Index(x, _) | Expr::Dot(x, _) => match x.lhs {
Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"),
_ => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _))),
},
Expr::Property(_) if parent_is_dot => Position::NONE,
Expr::Property(_) if parent_is_dot => None,
Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"),
e if parent_is_dot => e.position(),
_ => Position::NONE,
e if parent_is_dot => Some(e.position()),
_ => None,
}
}
@ -1464,7 +1464,7 @@ fn make_assignment_stmt<'a>(
// xxx[???]... = rhs, xxx.prop... = rhs
Expr::Index(x, _) | Expr::Dot(x, _) => {
match check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _))) {
Position::NONE => match &x.lhs {
None => match &x.lhs {
// var[???] (non-indexed) = rhs, var.??? (non-indexed) = rhs
Expr::Variable(None, _, x) if x.0.is_none() => {
Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos))
@ -1488,7 +1488,7 @@ fn make_assignment_stmt<'a>(
Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(expr.position()))
}
},
pos => Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(pos)),
Some(pos) => Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(pos)),
}
}
// ??? && ??? = rhs, ??? || ??? = rhs
@ -1595,9 +1595,9 @@ fn make_dot_expr(
}
Expr::FnCall(mut func, func_pos) => {
// Recalculate hash
func.hash = FnCallHash::from_script_and_native(
calc_fn_hash(empty(), &func.name, func.num_args()),
calc_fn_hash(empty(), &func.name, func.num_args() + 1),
func.hashes = FnCallHashes::from_script_and_native(
calc_fn_hash(empty(), &func.name, func.args_count()),
calc_fn_hash(empty(), &func.name, func.args_count() + 1),
);
let rhs = Expr::Dot(
@ -1623,7 +1623,7 @@ fn make_dot_expr(
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
}
// lhs.nnn::func(...)
(_, Expr::FnCall(x, _)) if x.namespace.is_some() => {
(_, Expr::FnCall(x, _)) if x.is_qualified() => {
unreachable!("method call should not be namespace-qualified")
}
// lhs.Fn() or lhs.eval()
@ -1651,9 +1651,9 @@ fn make_dot_expr(
// lhs.func(...)
(lhs, Expr::FnCall(mut func, func_pos)) => {
// Recalculate hash
func.hash = FnCallHash::from_script_and_native(
calc_fn_hash(empty(), &func.name, func.num_args()),
calc_fn_hash(empty(), &func.name, func.num_args() + 1),
func.hashes = FnCallHashes::from_script_and_native(
calc_fn_hash(empty(), &func.name, func.args_count()),
calc_fn_hash(empty(), &func.name, func.args_count() + 1),
);
let rhs = Expr::FnCall(func, func_pos);
Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos)
@ -1739,7 +1739,7 @@ fn parse_binary_op(
let op_base = FnCallExpr {
name: state.get_identifier(op.as_ref()),
hash: FnCallHash::from_native(hash),
hashes: FnCallHashes::from_native(hash),
capture: false,
..Default::default()
};
@ -1804,7 +1804,7 @@ fn parse_binary_op(
let hash = calc_fn_hash(empty(), OP_CONTAINS, 2);
Expr::FnCall(
Box::new(FnCallExpr {
hash: FnCallHash::from_script(hash),
hashes: FnCallHashes::from_script(hash),
args,
name: state.get_identifier(OP_CONTAINS),
..op_base
@ -1824,10 +1824,10 @@ fn parse_binary_op(
Expr::FnCall(
Box::new(FnCallExpr {
hash: if is_valid_identifier(s.chars()) {
FnCallHash::from_script(hash)
hashes: if is_valid_identifier(s.chars()) {
FnCallHashes::from_script(hash)
} else {
FnCallHash::from_native(hash)
FnCallHashes::from_native(hash)
},
args,
..op_base
@ -2516,7 +2516,7 @@ fn parse_stmt(
#[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")]
let comments = {
let mut comments: StaticVec<std::string::String> = Default::default();
let mut comments: StaticVec<String> = Default::default();
let mut comments_pos = Position::NONE;
// Handle doc-comments.
@ -2771,7 +2771,7 @@ fn parse_fn(
mut settings: ParseSettings,
#[cfg(not(feature = "no_function"))]
#[cfg(feature = "metadata")]
comments: StaticVec<std::string::String>,
comments: StaticVec<String>,
) -> Result<ScriptFnDef, ParseError> {
#[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
@ -2894,7 +2894,7 @@ fn make_curry_from_externals(
let expr = Expr::FnCall(
Box::new(FnCallExpr {
name: state.get_identifier(crate::engine::KEYWORD_FN_PTR_CURRY),
hash: FnCallHash::from_native(calc_fn_hash(
hashes: FnCallHashes::from_native(calc_fn_hash(
empty(),
crate::engine::KEYWORD_FN_PTR_CURRY,
num_externals + 1,
@ -3018,7 +3018,8 @@ fn parse_anon_fn(
comments: Default::default(),
};
let expr = Expr::FnPointer(fn_name.into(), settings.pos);
let fn_ptr = FnPtr::new_unchecked(fn_name.into(), Default::default());
let expr = Expr::DynamicConstant(Box::new(fn_ptr.into()), settings.pos);
#[cfg(not(feature = "no_closure"))]
let expr = make_curry_from_externals(state, expr, externals, settings.pos);

View File

@ -57,16 +57,28 @@ pub type TokenStream<'a> = Peekable<TokenIterator<'a>>;
#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
pub struct Position {
/// Line number - 0 = none
#[cfg(not(feature = "no_position"))]
line: u16,
/// Character position - 0 = BOL
#[cfg(not(feature = "no_position"))]
pos: u16,
}
impl Position {
/// A [`Position`] representing no position.
pub const NONE: Self = Self { line: 0, pos: 0 };
pub const NONE: Self = Self {
#[cfg(not(feature = "no_position"))]
line: 0,
#[cfg(not(feature = "no_position"))]
pos: 0,
};
/// A [`Position`] representing the first position.
pub const START: Self = Self { line: 1, pos: 0 };
pub const START: Self = Self {
#[cfg(not(feature = "no_position"))]
line: 1,
#[cfg(not(feature = "no_position"))]
pos: 0,
};
/// Create a new [`Position`].
///
@ -77,12 +89,14 @@ impl Position {
///
/// Panics if `line` is zero.
#[inline(always)]
pub fn new(line: u16, position: u16) -> Self {
pub fn new(line: u16, _position: u16) -> Self {
assert!(line != 0, "line cannot be zero");
Self {
#[cfg(not(feature = "no_position"))]
line,
pos: position,
#[cfg(not(feature = "no_position"))]
pos: _position,
}
}
/// Get the line number (1-based), or [`None`] if there is no position.
@ -91,26 +105,40 @@ impl Position {
if self.is_none() {
None
} else {
Some(self.line as usize)
#[cfg(not(feature = "no_position"))]
return Some(self.line as usize);
#[cfg(feature = "no_position")]
unreachable!();
}
}
/// Get the character position (1-based), or [`None`] if at beginning of a line.
#[inline(always)]
pub fn position(self) -> Option<usize> {
if self.is_none() || self.pos == 0 {
if self.is_none() {
None
} else {
Some(self.pos as usize)
#[cfg(not(feature = "no_position"))]
return if self.pos == 0 {
None
} else {
Some(self.pos as usize)
};
#[cfg(feature = "no_position")]
unreachable!();
}
}
/// Advance by one character position.
#[inline(always)]
pub(crate) fn advance(&mut self) {
assert!(!self.is_none(), "cannot advance Position::none");
#[cfg(not(feature = "no_position"))]
{
assert!(!self.is_none(), "cannot advance Position::none");
// Advance up to maximum position
if self.pos < u16::MAX {
self.pos += 1;
// Advance up to maximum position
if self.pos < u16::MAX {
self.pos += 1;
}
}
}
/// Go backwards by one character position.
@ -120,30 +148,42 @@ impl Position {
/// Panics if already at beginning of a line - cannot rewind to a previous line.
#[inline(always)]
pub(crate) fn rewind(&mut self) {
assert!(!self.is_none(), "cannot rewind Position::none");
assert!(self.pos > 0, "cannot rewind at position 0");
self.pos -= 1;
#[cfg(not(feature = "no_position"))]
{
assert!(!self.is_none(), "cannot rewind Position::none");
assert!(self.pos > 0, "cannot rewind at position 0");
self.pos -= 1;
}
}
/// Advance to the next line.
#[inline(always)]
pub(crate) fn new_line(&mut self) {
assert!(!self.is_none(), "cannot advance Position::none");
#[cfg(not(feature = "no_position"))]
{
assert!(!self.is_none(), "cannot advance Position::none");
// Advance up to maximum position
if self.line < u16::MAX {
self.line += 1;
self.pos = 0;
// Advance up to maximum position
if self.line < u16::MAX {
self.line += 1;
self.pos = 0;
}
}
}
/// Is this [`Position`] at the beginning of a line?
#[inline(always)]
pub fn is_beginning_of_line(self) -> bool {
self.pos == 0 && !self.is_none()
#[cfg(not(feature = "no_position"))]
return self.pos == 0 && !self.is_none();
#[cfg(feature = "no_position")]
return false;
}
/// Is there no [`Position`]?
#[inline(always)]
pub fn is_none(self) -> bool {
self == Self::NONE
#[cfg(not(feature = "no_position"))]
return self == Self::NONE;
#[cfg(feature = "no_position")]
return true;
}
}
@ -158,17 +198,27 @@ impl fmt::Display for Position {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_none() {
write!(f, "none")
write!(f, "none")?;
} else {
write!(f, "line {}, position {}", self.line, self.pos)
#[cfg(not(feature = "no_position"))]
write!(f, "line {}, position {}", self.line, self.pos)?;
#[cfg(feature = "no_position")]
unreachable!();
}
Ok(())
}
}
impl fmt::Debug for Position {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.line, self.pos)
#[cfg(not(feature = "no_position"))]
write!(f, "{}:{}", self.line, self.pos)?;
#[cfg(feature = "no_position")]
f.write_str("none")?;
Ok(())
}
}
@ -179,14 +229,17 @@ impl Add for Position {
if rhs.is_none() {
self
} else {
Self {
#[cfg(not(feature = "no_position"))]
return Self {
line: self.line + rhs.line - 1,
pos: if rhs.is_beginning_of_line() {
self.pos
} else {
self.pos + rhs.pos - 1
},
}
};
#[cfg(feature = "no_position")]
unreachable!();
}
}
}
@ -908,8 +961,9 @@ pub fn parse_string_literal(
let mut escape = String::with_capacity(12);
let start = *pos;
let mut skip_whitespace_until = 0;
let mut interpolated = false;
#[cfg(not(feature = "no_position"))]
let mut skip_whitespace_until = 0;
state.is_within_text_terminated_by = Some(termination_char);
@ -1044,7 +1098,11 @@ pub fn parse_string_literal(
assert_eq!(escape, "\\", "unexpected escape {} at end of line", escape);
escape.clear();
pos.new_line();
skip_whitespace_until = start.position().unwrap() + 1;
#[cfg(not(feature = "no_position"))]
{
skip_whitespace_until = start.position().unwrap() + 1;
}
}
// Unterminated string
@ -1062,13 +1120,18 @@ pub fn parse_string_literal(
}
// Whitespace to skip
#[cfg(not(feature = "no_position"))]
_ if next_char.is_whitespace() && pos.position().unwrap() < skip_whitespace_until => {}
// All other characters
_ => {
escape.clear();
result.push(next_char);
skip_whitespace_until = 0;
#[cfg(not(feature = "no_position"))]
{
skip_whitespace_until = 0;
}
}
}
}
@ -1241,7 +1304,7 @@ fn get_next_token_inner(
);
}
let mut negated_pos = Position::NONE;
let mut negated: Option<Position> = None;
while let Some(c) = stream.get_next() {
pos.advance();
@ -1350,7 +1413,7 @@ fn get_next_token_inner(
}
}
let num_pos = if !negated_pos.is_none() {
let num_pos = if let Some(negated_pos) = negated {
result.insert(0, '-');
negated_pos
} else {
@ -1511,7 +1574,7 @@ fn get_next_token_inner(
('+', _) if !state.non_unary => return Some((Token::UnaryPlus, start_pos)),
('+', _) => return Some((Token::Plus, start_pos)),
('-', '0'..='9') if !state.non_unary => negated_pos = start_pos,
('-', '0'..='9') if !state.non_unary => negated = Some(start_pos),
('-', '0'..='9') => return Some((Token::Minus, start_pos)),
('-', '=') => {
eat_next(stream, pos);

View File

@ -56,7 +56,7 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
convert_to_vec::<INT>(engine.eval(
r"
"
let x = [2, 9];
x.insert(-1, 1);
x.insert(999, 3);
@ -76,7 +76,7 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
convert_to_vec::<INT>(engine.eval(
r"
"
let x = [1, 2, 3];
x += [4, 5];
x
@ -86,7 +86,7 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
);
assert_eq!(
convert_to_vec::<INT>(engine.eval(
r"
"
let x = [1, 2, 3];
let y = [4, 5];
x + y
@ -136,7 +136,7 @@ fn test_array_with_structs() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
r"
"
let a = [new_ts()];
a[0].x = 100;
a[0].update();
@ -158,7 +158,7 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
convert_to_vec::<INT>(engine.eval(
r"
"
let x = [1, 2, 3];
x.filter(|v| v > 2)
"
@ -168,7 +168,7 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
convert_to_vec::<INT>(engine.eval(
r"
"
let x = [1, 2, 3];
x.filter(|v, i| v > i)
"
@ -178,7 +178,7 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
convert_to_vec::<INT>(engine.eval(
r"
"
let x = [1, 2, 3];
x.map(|v| v * 2)
"
@ -188,7 +188,7 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
convert_to_vec::<INT>(engine.eval(
r"
"
let x = [1, 2, 3];
x.map(|v, i| v * i)
"

View File

@ -21,41 +21,38 @@ fn test_assignments_bad_lhs() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(
*engine.compile(r"(x+y) = 42;").expect_err("should error").0,
*engine.compile("(x+y) = 42;").expect_err("should error").0,
ParseErrorType::AssignmentToInvalidLHS("".to_string())
);
assert_eq!(
*engine.compile(r"foo(x) = 42;").expect_err("should error").0,
*engine.compile("foo(x) = 42;").expect_err("should error").0,
ParseErrorType::AssignmentToInvalidLHS("".to_string())
);
assert_eq!(
*engine.compile(r"true = 42;").expect_err("should error").0,
*engine.compile("true = 42;").expect_err("should error").0,
ParseErrorType::AssignmentToConstant("".to_string())
);
assert_eq!(
*engine.compile(r"123 = 42;").expect_err("should error").0,
*engine.compile("123 = 42;").expect_err("should error").0,
ParseErrorType::AssignmentToConstant("".to_string())
);
#[cfg(not(feature = "no_object"))]
{
assert_eq!(
*engine.compile("x.foo() = 42;").expect_err("should error").0,
ParseErrorType::AssignmentToInvalidLHS("".to_string())
);
assert_eq!(
*engine
.compile(r"x.foo() = 42;")
.compile("x.foo().x.y = 42;")
.expect_err("should error")
.0,
ParseErrorType::AssignmentToInvalidLHS("".to_string())
);
assert_eq!(
*engine
.compile(r"x.foo().x.y = 42;")
.expect_err("should error")
.0,
ParseErrorType::AssignmentToInvalidLHS("".to_string())
);
assert_eq!(
*engine
.compile(r"x.y.z.foo() = 42;")
.compile("x.y.z.foo() = 42;")
.expect_err("should error")
.0,
ParseErrorType::AssignmentToInvalidLHS("".to_string())
@ -63,7 +60,7 @@ fn test_assignments_bad_lhs() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))]
assert_eq!(
*engine
.compile(r"x.foo()[0] = 42;")
.compile("x.foo()[0] = 42;")
.expect_err("should error")
.0,
ParseErrorType::AssignmentToInvalidLHS("".to_string())
@ -71,7 +68,7 @@ fn test_assignments_bad_lhs() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_index"))]
assert_eq!(
*engine
.compile(r"x[y].z.foo() = 42;")
.compile("x[y].z.foo() = 42;")
.expect_err("should error")
.0,
ParseErrorType::AssignmentToInvalidLHS("".to_string())

View File

@ -38,7 +38,7 @@ fn test_bool_op_short_circuit() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<bool>(
r"
"
let x = true;
x || { throw; };
"
@ -48,7 +48,7 @@ fn test_bool_op_short_circuit() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<bool>(
r"
"
let x = false;
x && { throw; };
"
@ -65,7 +65,7 @@ fn test_bool_op_no_short_circuit1() {
assert!(engine
.eval::<bool>(
r"
"
let x = true;
x | { throw; }
"
@ -79,7 +79,7 @@ fn test_bool_op_no_short_circuit2() {
assert!(engine
.eval::<bool>(
r"
"
let x = false;
x & { throw; }
"

View File

@ -10,7 +10,7 @@ fn test_call_fn() -> Result<(), Box<EvalAltResult>> {
scope.push("foo", 42 as INT);
let ast = engine.compile(
r"
"
fn hello(x, y) {
x + y
}

View File

@ -7,7 +7,7 @@ fn test_chars() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<char>("'y'")?, 'y');
assert_eq!(engine.eval::<char>(r"'\''")?, '\'');
assert_eq!(engine.eval::<char>(r#"'"'"#)?, '"');
assert_eq!(engine.eval::<char>("'\\u2764'")?, '❤');
assert_eq!(engine.eval::<char>(r"'\u2764'")?, '❤');
#[cfg(not(feature = "no_index"))]
{

View File

@ -54,7 +54,7 @@ fn test_closures() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
r"
"
let foo = #{ x: 42 };
let f = || { this.x };
foo.call(f)

View File

@ -33,7 +33,7 @@ fn test_comments_doc() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();
let ast = engine.compile(
r"
"
/// Hello world
@ -48,7 +48,7 @@ fn test_comments_doc() -> Result<(), Box<EvalAltResult>> {
assert!(engine
.compile(
r"
"
/// Hello world
let x = 42;
"
@ -56,7 +56,7 @@ fn test_comments_doc() -> Result<(), Box<EvalAltResult>> {
.is_err());
engine.compile(
r"
"
///////////////
let x = 42;
@ -66,7 +66,7 @@ fn test_comments_doc() -> Result<(), Box<EvalAltResult>> {
)?;
let ast = engine.compile(
r"
"
/** Hello world
** how are you?
**/
@ -82,7 +82,7 @@ fn test_comments_doc() -> Result<(), Box<EvalAltResult>> {
assert!(engine
.compile(
r"
"
/** Hello world */
let x = 42;
"
@ -92,7 +92,7 @@ fn test_comments_doc() -> Result<(), Box<EvalAltResult>> {
engine.enable_doc_comments(false);
engine.compile(
r"
"
/// Hello world!
let x = 42;

View File

@ -59,7 +59,7 @@ fn test_constant_mut() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval_with_scope::<INT>(
&mut scope,
r"
"
MY_NUMBER.update_value(42);
MY_NUMBER.value
",

View File

@ -91,7 +91,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
assert!(matches!(
*engine
.eval::<Array>(
r"
"
let x = [1,2,3,4,5,6];
let y = [7,8,9,10,11,12];
x + y
@ -105,7 +105,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
assert!(matches!(
*engine
.eval::<Array>(
r"
"
let x = [1,2,3,4,5,6];
x.pad(100, 42);
x
@ -118,7 +118,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
assert!(matches!(
*engine
.eval::<Array>(
r"
"
let x = [1,2,3];
[x, x, x, x]
"
@ -131,7 +131,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
assert!(matches!(
*engine
.eval::<Array>(
r"
"
let x = #{a:1, b:2, c:3};
[x, x, x, x]
"
@ -143,7 +143,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
assert!(matches!(
*engine
.eval::<Array>(
r"
"
let x = [1];
let y = [x, x];
let z = [y, y];
@ -159,7 +159,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine
.eval::<Array>(
r"
"
let x = [1,2,3,4,5,6];
let y = [7,8,9,10,11,12];
x + y
@ -172,7 +172,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine
.eval::<Array>(
r"
"
let x = [1,2,3];
[x, x, x, x]
"
@ -209,7 +209,7 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
assert!(matches!(
*engine
.eval::<Map>(
r"
"
let x = #{a:1,b:2,c:3,d:4,e:5,f:6};
let y = #{g:7,h:8,i:9,j:10,k:11,l:12};
x + y
@ -222,7 +222,7 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
assert!(matches!(
*engine
.eval::<Map>(
r"
"
let x = #{a:1,b:2,c:3};
#{u:x, v:x, w:x, z:x}
"
@ -235,7 +235,7 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
assert!(matches!(
*engine
.eval::<Map>(
r"
"
let x = [1, 2, 3];
#{u:x, v:x, w:x, z:x}
"
@ -249,7 +249,7 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine
.eval::<Map>(
r"
"
let x = #{a:1,b:2,c:3,d:4,e:5,f:6};
let y = #{g:7,h:8,i:9,j:10,k:11,l:12};
x + y
@ -262,7 +262,7 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine
.eval::<Map>(
r"
"
let x = #{a:1,b:2,c:3};
#{u:x, v:x, w:x, z:x}
"

View File

@ -5,7 +5,7 @@ use rhai::{Engine, EvalAltResult, Module, INT};
fn test_for() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
let script = r"
let script = "
let sum1 = 0;
let sum2 = 0;
let inputs = [1, 2, 3, 4, 5];
@ -29,7 +29,7 @@ fn test_for() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
r"
"
let sum = 0;
for x in range(1, 10, 2) { sum += x; }
sum
@ -40,7 +40,7 @@ fn test_for() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
r"
"
let sum = 0;
for x in range(10, 1, 2) { sum += x; }
sum
@ -51,7 +51,7 @@ fn test_for() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
r"
"
let sum = 0;
for x in range(1, 10, -2) { sum += x; }
sum
@ -62,7 +62,7 @@ fn test_for() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
r"
"
let sum = 0;
for x in range(10, 1, -2) { sum += x; }
sum
@ -80,7 +80,7 @@ fn test_for_overflow() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
#[cfg(not(feature = "only_i32"))]
let script = r"
let script = "
let sum = 0;
for x in range(9223372036854775807, 0, 9223372036854775807) {
@ -90,7 +90,7 @@ fn test_for_overflow() -> Result<(), Box<EvalAltResult>> {
sum
";
#[cfg(feature = "only_i32")]
let script = r"
let script = "
let sum = 0;
for x in range(2147483647 , 0, 2147483647 ) {

View File

@ -67,7 +67,7 @@ fn test_functions_namespaces() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
r"
"
const ANSWER = 42;
fn foo() { global::ANSWER }

View File

@ -13,7 +13,7 @@ fn test_if() -> Result<(), Box<EvalAltResult>> {
);
assert_eq!(
engine.eval::<INT>(
r"
"
if false { 55 }
else if false { 33 }
else if false { 66 }
@ -34,7 +34,7 @@ fn test_if_expr() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
r"
"
let x = 42;
let y = 1 + if x > 40 { 100 } else { 0 } / x;
y

View File

@ -70,7 +70,7 @@ fn test_internal_fn_big() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
r"
"
fn math_me(a, b, c, d, e, f) {
a - b * c + d * e - f
}
@ -89,7 +89,7 @@ fn test_internal_fn_overloading() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
r"
"
fn abc(x,y,z) { 2*x + 3*y + 4*z + 888 }
fn abc(x,y) { x + 2*y + 88 }
fn abc() { 42 }
@ -104,7 +104,7 @@ fn test_internal_fn_overloading() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
*engine
.compile(
r"
"
fn abc(x) { x + 42 }
fn abc(x) { x - 42 }
"

View File

@ -6,7 +6,7 @@ fn test_loop() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
r"
"
let x = 0;
let i = 0;

View File

@ -72,7 +72,7 @@ b`: 1}; y["a\nb"]
);
assert_eq!(
engine.eval::<INT>(
r"
"
let x = #{a: 1, b: 2, c: 3};
let y = #{b: 42, d: 9};
x.mixin(y);
@ -83,7 +83,7 @@ b`: 1}; y["a\nb"]
);
assert_eq!(
engine.eval::<INT>(
r"
"
let x = #{a: 1, b: 2, c: 3};
x += #{b: 42, d: 9};
x.len() + x.b
@ -94,7 +94,7 @@ b`: 1}; y["a\nb"]
assert_eq!(
engine
.eval::<Map>(
r"
"
let x = #{a: 1, b: 2, c: 3};
let y = #{b: 42, d: 9};
x + y
@ -206,7 +206,7 @@ fn test_map_json() -> Result<(), Box<EvalAltResult>> {
assert!(matches!(
*engine.parse_json(" 123", true).expect_err("should error"),
EvalAltResult::ErrorParsing(ParseErrorType::MissingToken(token, _), pos)
if token == "{" && pos.position() == Some(4)
if token == "{"
));
Ok(())

View File

@ -30,7 +30,7 @@ fn test_mismatched_op_custom_type() -> Result<(), Box<EvalAltResult>> {
.register_type_with_name::<TestStruct>("TestStruct")
.register_fn("new_ts", TestStruct::new);
assert!(matches!(*engine.eval::<bool>(r"
assert!(matches!(*engine.eval::<bool>("
let x = new_ts();
let y = new_ts();
x == y

View File

@ -381,13 +381,13 @@ fn test_module_export() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert!(matches!(
engine.compile(r"let x = 10; { export x; }").expect_err("should error"),
engine.compile("let x = 10; { export x; }").expect_err("should error"),
ParseError(x, _) if *x == ParseErrorType::WrongExport
));
#[cfg(not(feature = "no_function"))]
assert!(matches!(
engine.compile(r"fn abc(x) { export x; }").expect_err("should error"),
engine.compile("fn abc(x) { export x; }").expect_err("should error"),
ParseError(x, _) if *x == ParseErrorType::WrongExport
));

View File

@ -72,7 +72,7 @@ fn test_max_operations_functions() -> Result<(), Box<EvalAltResult>> {
fn inc(x) { x + 1 }
let x = 0;
while x < 31 {
while x < 36 {
print(x);
x = inc(x);
}

View File

@ -5,9 +5,9 @@ use rhai::{Engine, EvalAltResult, OptimizationLevel, INT};
#[test]
fn test_optimizer_run() -> Result<(), Box<EvalAltResult>> {
fn run_test(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>(r"if true { 42 } else { 123 }")?, 42);
assert_eq!(engine.eval::<INT>("if true { 42 } else { 123 }")?, 42);
assert_eq!(
engine.eval::<INT>(r"if 1 == 1 || 2 > 3 { 42 } else { 123 }")?,
engine.eval::<INT>("if 1 == 1 || 2 > 3 { 42 } else { 123 }")?,
42
);
assert_eq!(
@ -34,14 +34,14 @@ fn test_optimizer_run() -> Result<(), Box<EvalAltResult>> {
engine.set_optimization_level(OptimizationLevel::Simple);
assert_eq!(
engine.eval::<INT>(r"if 1 == 1 || 2 > 3 { 42 } else { 123 }")?,
engine.eval::<INT>("if 1 == 1 || 2 > 3 { 42 } else { 123 }")?,
123
);
engine.set_optimization_level(OptimizationLevel::Full);
assert_eq!(
engine.eval::<INT>(r"if 1 == 1 || 2 > 3 { 42 } else { 123 }")?,
engine.eval::<INT>("if 1 == 1 || 2 > 3 { 42 } else { 123 }")?,
123
);
@ -49,6 +49,7 @@ fn test_optimizer_run() -> Result<(), Box<EvalAltResult>> {
}
#[cfg(not(feature = "no_module"))]
#[cfg(not(feature = "no_position"))]
#[test]
fn test_optimizer_parse() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new();

View File

@ -60,7 +60,11 @@ fn test_print_debug() -> Result<(), Box<EvalAltResult>> {
assert_eq!(logbook.read().unwrap()[0], "entry: 42");
assert_eq!(
logbook.read().unwrap()[1],
r#"DEBUG of world at 1:19: "hello!""#
if cfg!(not(feature = "no_position")) {
r#"DEBUG of world at 1:19: "hello!""#
} else {
r#"DEBUG of world at none: "hello!""#
}
);
for entry in logbook.read().unwrap().iter() {

View File

@ -49,7 +49,7 @@ fn test_side_effects_command() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval_with_scope::<INT>(
&mut scope,
r"
"
// Drive the command object via the wrapper
Command.action(30);
Command.value

File diff suppressed because one or more lines are too long

View File

@ -14,7 +14,11 @@ fn test_string() -> Result<(), Box<EvalAltResult>> {
);
assert_eq!(
engine.eval::<String>(" \"Test string: \\u2764\\\n hello, world!\"")?,
"Test string: ❤ hello, world!"
if cfg!(not(feature = "no_position")) {
"Test string: ❤ hello, world!"
} else {
"Test string: ❤ hello, world!"
}
);
assert_eq!(
engine.eval::<String>(" `Test string: \\u2764\nhello,\\nworld!`")?,
@ -319,7 +323,7 @@ fn test_string_interpolated() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<String>(
r"
"
let x = 40;
`hello ${x+2} worlds!`
"
@ -339,7 +343,7 @@ fn test_string_interpolated() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<String>(
r"
"
const x = 42;
`hello ${x} worlds!`
"
@ -351,7 +355,7 @@ fn test_string_interpolated() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<String>(
r"
"
const x = 42;
`${x} worlds!`
"
@ -361,7 +365,7 @@ fn test_string_interpolated() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<String>(
r"
"
const x = 42;
`hello ${x}`
"
@ -371,7 +375,7 @@ fn test_string_interpolated() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<String>(
r"
"
const x = 20;
`hello ${let y = x + 1; `${y * 2}`} worlds!`
"

View File

@ -33,7 +33,7 @@ fn test_switch() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval_with_scope::<INT>(
&mut scope,
r"
"
let y = [1, 2, 3];
switch y {
@ -50,7 +50,7 @@ fn test_switch() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval_with_scope::<INT>(
&mut scope,
r"
"
let y = #{a:1, b:true, c:'x'};
switch y {
@ -98,7 +98,7 @@ fn test_switch_condition() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval_with_scope::<INT>(
&mut scope,
r"
"
switch x / 2 {
21 if x > 40 => 1,
0 if x < 100 => 2,
@ -113,7 +113,7 @@ fn test_switch_condition() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval_with_scope::<INT>(
&mut scope,
r"
"
switch x / 2 {
21 if x < 40 => 1,
0 if x < 100 => 2,
@ -128,7 +128,7 @@ fn test_switch_condition() -> Result<(), Box<EvalAltResult>> {
assert!(matches!(
*engine
.compile(
r"
"
switch x {
21 if x < 40 => 1,
21 if x == 10 => 10,

View File

@ -57,7 +57,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
r"
"
let x = 0;
let foo = (exec |x| -> { x += 2 } while x < 42) * 10;
foo
@ -67,7 +67,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
);
assert_eq!(
engine.eval::<INT>(
r"
"
let x = 0;
exec |x| -> { x += 1 } while x < 42;
x
@ -77,7 +77,7 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
);
assert_eq!(
engine.eval::<INT>(
r"
"
exec |x| -> { x += 1 } while x < 42;
x
"

View File

@ -40,7 +40,7 @@ fn test_timestamp() -> Result<(), Box<EvalAltResult>> {
);
assert!(engine.eval::<bool>(
r"
"
let time1 = timestamp();
for x in range(0, 10000) {}
let time2 = timestamp();

View File

@ -49,7 +49,7 @@ fn test_tokens_custom_operator_identifiers() -> Result<(), Box<EvalAltResult>> {
#[cfg(not(feature = "no_function"))]
assert_eq!(
engine.eval::<INT>(
r"
"
fn foo(x, y) { y - x }
1 + 2 * 3 foo 4 - 5 / 6
"
@ -87,7 +87,7 @@ fn test_tokens_custom_operator_symbol() -> Result<(), Box<EvalAltResult>> {
fn test_tokens_unicode_xid_ident() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
let result = engine.eval::<INT>(
r"
"
fn () { 42 }
()
",
@ -99,7 +99,7 @@ fn test_tokens_unicode_xid_ident() -> Result<(), Box<EvalAltResult>> {
assert!(result.is_err());
let result = engine.eval::<INT>(
r"
"
fn _1() { 1 }
_1()
",

View File

@ -6,7 +6,7 @@ fn test_while() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
r"
"
let x = 0;
while x < 10 {
@ -31,7 +31,7 @@ fn test_do() -> Result<(), Box<EvalAltResult>> {
assert_eq!(
engine.eval::<INT>(
r"
"
let x = 0;
do {