Merge branch 'master' into plugins
This commit is contained in:
commit
9bd2d4c981
36
README.md
36
README.md
@ -2207,7 +2207,7 @@ let a = new_ts(); // constructor function
|
||||
a.field = 500; // property setter
|
||||
a.update(); // method call, 'a' can be modified
|
||||
|
||||
update(a); // <- this de-sugars to 'a.update()' this if 'a' is a simple variable
|
||||
update(a); // <- this de-sugars to 'a.update()' thus if 'a' is a simple variable
|
||||
// unlike scripted functions, 'a' can be modified and is not a copy
|
||||
|
||||
let array = [ a ];
|
||||
@ -2480,11 +2480,16 @@ engine.set_max_string_size(500); // allow strings only up to 500 byte
|
||||
engine.set_max_string_size(0); // allow unlimited string length
|
||||
```
|
||||
|
||||
A script attempting to create a string literal longer than the maximum will terminate with a parse error.
|
||||
A script attempting to create a string literal longer than the maximum length will terminate with a parse error.
|
||||
Any script operation that produces a string longer than the maximum also terminates the script with an error result.
|
||||
This check can be disabled via the [`unchecked`] feature for higher performance
|
||||
(but higher risks as well).
|
||||
|
||||
Be conservative when setting a maximum limit and always consider the fact that a registered function may grow
|
||||
a string's length without Rhai noticing until the very end. For instance, the built-in '`+`' operator for strings
|
||||
concatenates two strings together to form one longer string; if both strings are _slightly_ below the maximum
|
||||
length limit, the resultant string may be almost _twice_ the maximum length.
|
||||
|
||||
### Maximum size of arrays
|
||||
|
||||
Rhai by default does not limit how large an [array] can be.
|
||||
@ -2503,6 +2508,16 @@ Any script operation that produces an array larger than the maximum also termina
|
||||
This check can be disabled via the [`unchecked`] feature for higher performance
|
||||
(but higher risks as well).
|
||||
|
||||
Be conservative when setting a maximum limit and always consider the fact that a registered function may grow
|
||||
an array's size without Rhai noticing until the very end.
|
||||
For instance, the built-in '`+`' operator for arrays concatenates two arrays together to form one larger array;
|
||||
if both arrays are _slightly_ below the maximum size limit, the resultant array may be almost _twice_ the maximum size.
|
||||
|
||||
As a malicious script may create a deeply-nested array which consumes huge amounts of memory while each individual
|
||||
array still stays under the maximum size limit, Rhai also recursively adds up the sizes of all strings, arrays
|
||||
and object maps contained within each array to make sure that the _aggregate_ sizes of none of these data structures
|
||||
exceed their respective maximum size limits (if any).
|
||||
|
||||
### Maximum size of object maps
|
||||
|
||||
Rhai by default does not limit how large (i.e. the number of properties) an [object map] can be.
|
||||
@ -2521,6 +2536,16 @@ Any script operation that produces an object map with more properties than the m
|
||||
This check can be disabled via the [`unchecked`] feature for higher performance
|
||||
(but higher risks as well).
|
||||
|
||||
Be conservative when setting a maximum limit and always consider the fact that a registered function may grow
|
||||
an object map's size without Rhai noticing until the very end. For instance, the built-in '`+`' operator for object maps
|
||||
concatenates two object maps together to form one larger object map; if both object maps are _slightly_ below the maximum
|
||||
size limit, the resultant object map may be almost _twice_ the maximum size.
|
||||
|
||||
As a malicious script may create a deeply-nested object map which consumes huge amounts of memory while each individual
|
||||
object map still stays under the maximum size limit, Rhai also recursively adds up the sizes of all strings, arrays
|
||||
and object maps contained within each object map to make sure that the _aggregate_ sizes of none of these data structures
|
||||
exceed their respective maximum size limits (if any).
|
||||
|
||||
### Maximum number of operations
|
||||
|
||||
Rhai by default does not limit how much time or CPU a script consumes.
|
||||
@ -2582,14 +2607,17 @@ total number of operations for a typical run.
|
||||
### Maximum number of modules
|
||||
|
||||
Rhai by default does not limit how many [modules] can be loaded via [`import`] statements.
|
||||
This can be changed via the `Engine::set_max_modules` method, with zero being unlimited (the default).
|
||||
This can be changed via the `Engine::set_max_modules` method. Notice that setting the maximum number
|
||||
of modules to zero does _not_ indicate unlimited modules, but disallows loading any module altogether.
|
||||
|
||||
```rust
|
||||
let mut engine = Engine::new();
|
||||
|
||||
engine.set_max_modules(5); // allow loading only up to 5 modules
|
||||
|
||||
engine.set_max_modules(0); // allow unlimited modules
|
||||
engine.set_max_modules(0); // disallow loading any module (maximum = zero)
|
||||
|
||||
engine.set_max_modules(1000); // set to a large number for effectively unlimited modules
|
||||
```
|
||||
|
||||
A script attempting to load more than the maximum number of modules will terminate with an error result.
|
||||
|
10
src/api.rs
10
src/api.rs
@ -751,7 +751,6 @@ impl Engine {
|
||||
) -> Result<AST, ParseError> {
|
||||
let scripts = [script];
|
||||
let stream = lex(&scripts, self.max_string_size);
|
||||
|
||||
{
|
||||
let mut peekable = stream.peekable();
|
||||
self.parse_global_expr(&mut peekable, scope, self.optimization_level)
|
||||
@ -906,11 +905,8 @@ impl Engine {
|
||||
let scripts = [script];
|
||||
let stream = lex(&scripts, self.max_string_size);
|
||||
|
||||
let ast = self.parse_global_expr(
|
||||
&mut stream.peekable(),
|
||||
scope,
|
||||
OptimizationLevel::None, // No need to optimize a lone expression
|
||||
)?;
|
||||
// No need to optimize a lone expression
|
||||
let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?;
|
||||
|
||||
self.eval_ast_with_scope(scope, &ast)
|
||||
}
|
||||
@ -983,6 +979,7 @@ impl Engine {
|
||||
});
|
||||
}
|
||||
|
||||
/// Evaluate an `AST` with own scope.
|
||||
pub(crate) fn eval_ast_with_scope_raw(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
@ -1035,7 +1032,6 @@ impl Engine {
|
||||
) -> Result<(), Box<EvalAltResult>> {
|
||||
let scripts = [script];
|
||||
let stream = lex(&scripts, self.max_string_size);
|
||||
|
||||
let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?;
|
||||
self.consume_ast_with_scope(scope, &ast)
|
||||
}
|
||||
|
189
src/engine.rs
189
src/engine.rs
@ -63,16 +63,9 @@ pub const MAX_FUNCTION_EXPR_DEPTH: usize = 32;
|
||||
#[cfg(feature = "unchecked")]
|
||||
pub const MAX_CALL_STACK_DEPTH: usize = usize::MAX;
|
||||
#[cfg(feature = "unchecked")]
|
||||
pub const MAX_EXPR_DEPTH: usize = usize::MAX;
|
||||
pub const MAX_EXPR_DEPTH: usize = 0;
|
||||
#[cfg(feature = "unchecked")]
|
||||
pub const MAX_FUNCTION_EXPR_DEPTH: usize = usize::MAX;
|
||||
|
||||
#[cfg(feature = "unchecked")]
|
||||
pub const MAX_STRING_SIZE: usize = usize::MAX;
|
||||
#[cfg(feature = "unchecked")]
|
||||
pub const MAX_ARRAY_SIZE: usize = usize::MAX;
|
||||
#[cfg(feature = "unchecked")]
|
||||
pub const MAX_MAP_SIZE: usize = usize::MAX;
|
||||
pub const MAX_FUNCTION_EXPR_DEPTH: usize = 0;
|
||||
|
||||
pub const KEYWORD_PRINT: &str = "print";
|
||||
pub const KEYWORD_DEBUG: &str = "debug";
|
||||
@ -189,7 +182,7 @@ pub struct State {
|
||||
/// Number of operations performed.
|
||||
pub operations: u64,
|
||||
/// Number of modules loaded.
|
||||
pub modules: u64,
|
||||
pub modules: usize,
|
||||
}
|
||||
|
||||
impl State {
|
||||
@ -268,7 +261,7 @@ pub struct Engine {
|
||||
/// Maximum number of operations allowed to run.
|
||||
pub(crate) max_operations: u64,
|
||||
/// Maximum number of modules allowed to load.
|
||||
pub(crate) max_modules: u64,
|
||||
pub(crate) max_modules: usize,
|
||||
/// Maximum length of a string.
|
||||
pub(crate) max_string_size: usize,
|
||||
/// Maximum length of an array.
|
||||
@ -309,11 +302,11 @@ impl Default for Engine {
|
||||
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
|
||||
max_expr_depth: MAX_EXPR_DEPTH,
|
||||
max_function_expr_depth: MAX_FUNCTION_EXPR_DEPTH,
|
||||
max_operations: u64::MAX,
|
||||
max_modules: u64::MAX,
|
||||
max_string_size: usize::MAX,
|
||||
max_array_size: usize::MAX,
|
||||
max_map_size: usize::MAX,
|
||||
max_operations: 0,
|
||||
max_modules: usize::MAX,
|
||||
max_string_size: 0,
|
||||
max_array_size: 0,
|
||||
max_map_size: 0,
|
||||
};
|
||||
|
||||
engine.load_package(StandardPackage::new().get());
|
||||
@ -456,11 +449,11 @@ impl Engine {
|
||||
max_call_stack_depth: MAX_CALL_STACK_DEPTH,
|
||||
max_expr_depth: MAX_EXPR_DEPTH,
|
||||
max_function_expr_depth: MAX_FUNCTION_EXPR_DEPTH,
|
||||
max_operations: u64::MAX,
|
||||
max_modules: u64::MAX,
|
||||
max_string_size: usize::MAX,
|
||||
max_array_size: usize::MAX,
|
||||
max_map_size: usize::MAX,
|
||||
max_operations: 0,
|
||||
max_modules: usize::MAX,
|
||||
max_string_size: 0,
|
||||
max_array_size: 0,
|
||||
max_map_size: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -501,17 +494,13 @@ impl Engine {
|
||||
/// consuming too much resources (0 for unlimited).
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
pub fn set_max_operations(&mut self, operations: u64) {
|
||||
self.max_operations = if operations == 0 {
|
||||
u64::MAX
|
||||
} else {
|
||||
operations
|
||||
};
|
||||
self.max_operations = operations;
|
||||
}
|
||||
|
||||
/// Set the maximum number of imported modules allowed for a script (0 for unlimited).
|
||||
/// Set the maximum number of imported modules allowed for a script.
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
pub fn set_max_modules(&mut self, modules: u64) {
|
||||
self.max_modules = if modules == 0 { u64::MAX } else { modules };
|
||||
pub fn set_max_modules(&mut self, modules: usize) {
|
||||
self.max_modules = modules;
|
||||
}
|
||||
|
||||
/// Set the depth limits for expressions/statements (0 for unlimited).
|
||||
@ -656,7 +645,7 @@ impl Engine {
|
||||
return Ok((result, false));
|
||||
} else {
|
||||
// Run external function
|
||||
let result = func.get_native_fn()(args)?;
|
||||
let result = func.get_native_fn()(self, args)?;
|
||||
|
||||
// Restore the original reference
|
||||
restore_first_arg(old_this_ptr, args);
|
||||
@ -1485,7 +1474,7 @@ impl Engine {
|
||||
.or_else(|| self.packages.get_fn(hash_fn))
|
||||
{
|
||||
// Overriding exact implementation
|
||||
func(&mut [lhs_ptr, &mut rhs_val])?;
|
||||
func(self, &mut [lhs_ptr, &mut rhs_val])?;
|
||||
} else if run_builtin_op_assignment(op, lhs_ptr, &rhs_val)?.is_none() {
|
||||
// Not built in, map to `var = var op rhs`
|
||||
let op = &op[..op.len() - 1]; // extract operator without =
|
||||
@ -1717,7 +1706,9 @@ impl Engine {
|
||||
.map_err(|err| EvalAltResult::new_position(err, *pos))
|
||||
}
|
||||
Ok(f) if f.is_plugin_fn() => f.get_plugin_fn().call(args.as_mut(), *pos),
|
||||
Ok(f) => f.get_native_fn()(args.as_mut()).map_err(|err| err.new_position(*pos)),
|
||||
Ok(f) => {
|
||||
f.get_native_fn()(self, args.as_mut()).map_err(|err| err.new_position(*pos))
|
||||
}
|
||||
Err(err)
|
||||
if def_val.is_some()
|
||||
&& matches!(*err, EvalAltResult::ErrorFunctionNotFound(_, _)) =>
|
||||
@ -1773,11 +1764,7 @@ impl Engine {
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if let Ok(val) = &result {
|
||||
self.check_data_size(val)?;
|
||||
}
|
||||
|
||||
result
|
||||
self.check_data_size(result)
|
||||
}
|
||||
|
||||
/// Evaluate a statement
|
||||
@ -2046,50 +2033,102 @@ impl Engine {
|
||||
}
|
||||
};
|
||||
|
||||
if let Ok(val) = &result {
|
||||
self.check_data_size(val)?;
|
||||
}
|
||||
|
||||
result
|
||||
self.check_data_size(result)
|
||||
}
|
||||
|
||||
/// Check a `Dynamic` value to ensure that its size is within allowable limit.
|
||||
fn check_data_size(&self, value: &Dynamic) -> Result<(), Box<EvalAltResult>> {
|
||||
/// Check a result to ensure that the data size is within allowable limit.
|
||||
fn check_data_size(
|
||||
&self,
|
||||
result: Result<Dynamic, Box<EvalAltResult>>,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
#[cfg(feature = "unchecked")]
|
||||
return Ok(());
|
||||
return result;
|
||||
|
||||
match value {
|
||||
Dynamic(Union::Str(s))
|
||||
if self.max_string_size > 0 && s.len() > self.max_string_size =>
|
||||
{
|
||||
Err(Box::new(EvalAltResult::ErrorDataTooLarge(
|
||||
"Length of string".to_string(),
|
||||
self.max_string_size,
|
||||
s.len(),
|
||||
Position::none(),
|
||||
)))
|
||||
// If no data size limits, just return
|
||||
if self.max_string_size + self.max_array_size + self.max_map_size == 0 {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Recursively calculate the size of a value (especially `Array` and `Map`)
|
||||
fn calc_size(value: &Dynamic) -> (usize, usize, usize) {
|
||||
match value {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Dynamic(Union::Array(arr)) => {
|
||||
let mut arrays = 0;
|
||||
let mut maps = 0;
|
||||
|
||||
arr.iter().for_each(|value| match value {
|
||||
Dynamic(Union::Array(_)) | Dynamic(Union::Map(_)) => {
|
||||
let (a, m, _) = calc_size(value);
|
||||
arrays += a;
|
||||
maps += m;
|
||||
}
|
||||
_ => arrays += 1,
|
||||
});
|
||||
|
||||
(arrays, maps, 0)
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Dynamic(Union::Map(map)) => {
|
||||
let mut arrays = 0;
|
||||
let mut maps = 0;
|
||||
|
||||
map.values().for_each(|value| match value {
|
||||
Dynamic(Union::Array(_)) | Dynamic(Union::Map(_)) => {
|
||||
let (a, m, _) = calc_size(value);
|
||||
arrays += a;
|
||||
maps += m;
|
||||
}
|
||||
_ => maps += 1,
|
||||
});
|
||||
|
||||
(arrays, maps, 0)
|
||||
}
|
||||
Dynamic(Union::Str(s)) => (0, 0, s.len()),
|
||||
_ => (0, 0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
match result {
|
||||
// Simply return all errors
|
||||
Err(_) => return result,
|
||||
// String with limit
|
||||
Ok(Dynamic(Union::Str(_))) if self.max_string_size > 0 => (),
|
||||
// Array with limit
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Dynamic(Union::Array(arr))
|
||||
if self.max_array_size > 0 && arr.len() > self.max_array_size =>
|
||||
{
|
||||
Err(Box::new(EvalAltResult::ErrorDataTooLarge(
|
||||
"Length of array".to_string(),
|
||||
self.max_array_size,
|
||||
arr.len(),
|
||||
Position::none(),
|
||||
)))
|
||||
}
|
||||
Ok(Dynamic(Union::Array(_))) if self.max_array_size > 0 => (),
|
||||
// Map with limit
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Dynamic(Union::Map(map)) if self.max_map_size > 0 && map.len() > self.max_map_size => {
|
||||
Err(Box::new(EvalAltResult::ErrorDataTooLarge(
|
||||
"Number of properties in object map".to_string(),
|
||||
self.max_map_size,
|
||||
map.len(),
|
||||
Position::none(),
|
||||
)))
|
||||
}
|
||||
_ => Ok(()),
|
||||
Ok(Dynamic(Union::Map(_))) if self.max_map_size > 0 => (),
|
||||
// Everything else is simply returned
|
||||
Ok(_) => return result,
|
||||
};
|
||||
|
||||
let (arr, map, s) = calc_size(result.as_ref().unwrap());
|
||||
|
||||
if s > self.max_string_size {
|
||||
Err(Box::new(EvalAltResult::ErrorDataTooLarge(
|
||||
"Length of string".to_string(),
|
||||
self.max_string_size,
|
||||
s,
|
||||
Position::none(),
|
||||
)))
|
||||
} else if arr > self.max_array_size {
|
||||
Err(Box::new(EvalAltResult::ErrorDataTooLarge(
|
||||
"Size of array".to_string(),
|
||||
self.max_array_size,
|
||||
arr,
|
||||
Position::none(),
|
||||
)))
|
||||
} else if map > self.max_map_size {
|
||||
Err(Box::new(EvalAltResult::ErrorDataTooLarge(
|
||||
"Number of properties in object map".to_string(),
|
||||
self.max_map_size,
|
||||
map,
|
||||
Position::none(),
|
||||
)))
|
||||
} else {
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
@ -2101,7 +2140,7 @@ impl Engine {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
{
|
||||
// Guard against too many operations
|
||||
if state.operations > self.max_operations {
|
||||
if self.max_operations > 0 && state.operations > self.max_operations {
|
||||
return Err(Box::new(EvalAltResult::ErrorTooManyOperations(
|
||||
Position::none(),
|
||||
)));
|
||||
|
24
src/error.rs
24
src/error.rs
@ -22,8 +22,8 @@ pub enum LexError {
|
||||
MalformedChar(String),
|
||||
/// An identifier is in an invalid format.
|
||||
MalformedIdentifier(String),
|
||||
/// Bad keyword encountered when tokenizing the script text.
|
||||
ImproperKeyword(String),
|
||||
/// Bad symbol encountered when tokenizing the script text.
|
||||
ImproperSymbol(String),
|
||||
}
|
||||
|
||||
impl Error for LexError {}
|
||||
@ -42,11 +42,18 @@ impl fmt::Display for LexError {
|
||||
"Length of string literal exceeds the maximum limit ({})",
|
||||
max
|
||||
),
|
||||
Self::ImproperKeyword(s) => write!(f, "{}", s),
|
||||
Self::ImproperSymbol(s) => write!(f, "{}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LexError {
|
||||
/// Convert a `LexError` into a `ParseError`.
|
||||
pub fn into_err(&self, pos: Position) -> ParseError {
|
||||
ParseError(Box::new(self.into()), pos)
|
||||
}
|
||||
}
|
||||
|
||||
/// Type of error encountered when parsing a script.
|
||||
///
|
||||
/// Some errors never appear when certain features are turned on.
|
||||
@ -217,6 +224,17 @@ impl fmt::Display for ParseErrorType {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&LexError> for ParseErrorType {
|
||||
fn from(err: &LexError) -> Self {
|
||||
match err {
|
||||
LexError::StringTooLong(max) => {
|
||||
Self::LiteralTooLarge("Length of string literal".to_string(), *max)
|
||||
}
|
||||
_ => Self::BadInput(err.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error when parsing a script.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||
pub struct ParseError(pub Box<ParseErrorType>, pub Position);
|
||||
|
@ -1,4 +1,5 @@
|
||||
use crate::any::Dynamic;
|
||||
use crate::engine::Engine;
|
||||
use crate::parser::ScriptFnDef;
|
||||
use crate::plugin::PluginFunction;
|
||||
use crate::result::EvalAltResult;
|
||||
@ -52,9 +53,10 @@ pub fn shared_take<T: Clone>(value: Shared<T>) -> T {
|
||||
pub type FnCallArgs<'a> = [&'a mut Dynamic];
|
||||
|
||||
#[cfg(not(feature = "sync"))]
|
||||
pub type FnAny = dyn Fn(&mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>>;
|
||||
pub type FnAny = dyn Fn(&Engine, &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>>;
|
||||
#[cfg(feature = "sync")]
|
||||
pub type FnAny = dyn Fn(&mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync;
|
||||
pub type FnAny =
|
||||
dyn Fn(&Engine, &mut FnCallArgs) -> Result<Dynamic, Box<EvalAltResult>> + Send + Sync;
|
||||
|
||||
pub type IteratorFn = fn(Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
//! Module which defines the function registration mechanism.
|
||||
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use crate::any::{Dynamic, Variant};
|
||||
@ -206,7 +205,7 @@ macro_rules! make_func {
|
||||
// ^ function parameter generic type name (A, B, C etc.)
|
||||
// ^ dereferencing function
|
||||
|
||||
Box::new(move |args: &mut FnCallArgs| {
|
||||
Box::new(move |_: &Engine, args: &mut FnCallArgs| {
|
||||
// The arguments are assumed to be of the correct number and types!
|
||||
|
||||
#[allow(unused_variables, unused_mut)]
|
||||
|
@ -305,6 +305,29 @@ impl Module {
|
||||
hash_fn
|
||||
}
|
||||
|
||||
/// Set a Rust function taking a reference to the scripting `Engine`, plus a list of
|
||||
/// mutable `Dynamic` references into the module, returning a hash key.
|
||||
/// A list of `TypeId`'s is taken as the argument types.
|
||||
///
|
||||
/// Use this to register a built-in function which must reference settings on the scripting
|
||||
/// `Engine` (e.g. to prevent growing an array beyond the allowed maximum size).
|
||||
///
|
||||
/// If there is a similar existing Rust function, it is replaced.
|
||||
pub(crate) fn set_fn_var_args<T: Variant + Clone>(
|
||||
&mut self,
|
||||
name: impl Into<String>,
|
||||
args: &[TypeId],
|
||||
func: impl Fn(&Engine, &mut [&mut Dynamic]) -> FuncReturn<T> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |engine: &Engine, args: &mut FnCallArgs| func(engine, args).map(Dynamic::from);
|
||||
self.set_fn(
|
||||
name,
|
||||
Public,
|
||||
args,
|
||||
CallableFunction::from_method(Box::new(f)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Set a Rust function taking no parameters into the module, returning a hash key.
|
||||
///
|
||||
/// If there is a similar existing Rust function, it is replaced.
|
||||
@ -323,7 +346,7 @@ impl Module {
|
||||
name: impl Into<String>,
|
||||
func: impl Fn() -> FuncReturn<T> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |_: &mut FnCallArgs| func().map(Dynamic::from);
|
||||
let f = move |_: &Engine, _: &mut FnCallArgs| func().map(Dynamic::from);
|
||||
let args = [];
|
||||
self.set_fn(
|
||||
name,
|
||||
@ -351,8 +374,9 @@ impl Module {
|
||||
name: impl Into<String>,
|
||||
func: impl Fn(A) -> FuncReturn<T> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
let f =
|
||||
move |args: &mut FnCallArgs| func(mem::take(args[0]).cast::<A>()).map(Dynamic::from);
|
||||
let f = move |_: &Engine, args: &mut FnCallArgs| {
|
||||
func(mem::take(args[0]).cast::<A>()).map(Dynamic::from)
|
||||
};
|
||||
let args = [TypeId::of::<A>()];
|
||||
self.set_fn(
|
||||
name,
|
||||
@ -380,7 +404,7 @@ impl Module {
|
||||
name: impl Into<String>,
|
||||
func: impl Fn(&mut A) -> FuncReturn<T> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |args: &mut FnCallArgs| {
|
||||
let f = move |_: &Engine, args: &mut FnCallArgs| {
|
||||
func(args[0].downcast_mut::<A>().unwrap()).map(Dynamic::from)
|
||||
};
|
||||
let args = [TypeId::of::<A>()];
|
||||
@ -434,7 +458,7 @@ impl Module {
|
||||
name: impl Into<String>,
|
||||
func: impl Fn(A, B) -> FuncReturn<T> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |args: &mut FnCallArgs| {
|
||||
let f = move |_: &Engine, args: &mut FnCallArgs| {
|
||||
let a = mem::take(args[0]).cast::<A>();
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
|
||||
@ -470,7 +494,7 @@ impl Module {
|
||||
name: impl Into<String>,
|
||||
func: impl Fn(&mut A, B) -> FuncReturn<T> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |args: &mut FnCallArgs| {
|
||||
let f = move |_: &Engine, args: &mut FnCallArgs| {
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let a = args[0].downcast_mut::<A>().unwrap();
|
||||
|
||||
@ -561,7 +585,7 @@ impl Module {
|
||||
name: impl Into<String>,
|
||||
func: impl Fn(A, B, C) -> FuncReturn<T> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |args: &mut FnCallArgs| {
|
||||
let f = move |_: &Engine, args: &mut FnCallArgs| {
|
||||
let a = mem::take(args[0]).cast::<A>();
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let c = mem::take(args[2]).cast::<C>();
|
||||
@ -603,7 +627,7 @@ impl Module {
|
||||
name: impl Into<String>,
|
||||
func: impl Fn(&mut A, B, C) -> FuncReturn<T> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |args: &mut FnCallArgs| {
|
||||
let f = move |_: &Engine, args: &mut FnCallArgs| {
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let c = mem::take(args[2]).cast::<C>();
|
||||
let a = args[0].downcast_mut::<A>().unwrap();
|
||||
@ -640,7 +664,7 @@ impl Module {
|
||||
&mut self,
|
||||
func: impl Fn(&mut A, B, A) -> FuncReturn<()> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |args: &mut FnCallArgs| {
|
||||
let f = move |_: &Engine, args: &mut FnCallArgs| {
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let c = mem::take(args[2]).cast::<A>();
|
||||
let a = args[0].downcast_mut::<A>().unwrap();
|
||||
@ -682,7 +706,7 @@ impl Module {
|
||||
name: impl Into<String>,
|
||||
func: impl Fn(A, B, C, D) -> FuncReturn<T> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |args: &mut FnCallArgs| {
|
||||
let f = move |_: &Engine, args: &mut FnCallArgs| {
|
||||
let a = mem::take(args[0]).cast::<A>();
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let c = mem::take(args[2]).cast::<C>();
|
||||
@ -731,7 +755,7 @@ impl Module {
|
||||
name: impl Into<String>,
|
||||
func: impl Fn(&mut A, B, C, D) -> FuncReturn<T> + SendSync + 'static,
|
||||
) -> u64 {
|
||||
let f = move |args: &mut FnCallArgs| {
|
||||
let f = move |_: &Engine, args: &mut FnCallArgs| {
|
||||
let b = mem::take(args[1]).cast::<B>();
|
||||
let c = mem::take(args[2]).cast::<C>();
|
||||
let d = mem::take(args[3]).cast::<D>();
|
||||
@ -1019,7 +1043,7 @@ pub trait ModuleResolver: SendSync {
|
||||
/// Resolve a module based on a path string.
|
||||
fn resolve(
|
||||
&self,
|
||||
engine: &Engine,
|
||||
_: &Engine,
|
||||
scope: Scope,
|
||||
path: &str,
|
||||
pos: Position,
|
||||
|
@ -2,9 +2,11 @@
|
||||
|
||||
use crate::any::{Dynamic, Variant};
|
||||
use crate::def_package;
|
||||
use crate::engine::Array;
|
||||
use crate::engine::{Array, Engine};
|
||||
use crate::module::FuncReturn;
|
||||
use crate::parser::{ImmutableString, INT};
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::token::Position;
|
||||
|
||||
use crate::stdlib::{any::TypeId, boxed::Box};
|
||||
|
||||
@ -23,13 +25,28 @@ fn ins<T: Variant + Clone>(list: &mut Array, position: INT, item: T) -> FuncRetu
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn pad<T: Variant + Clone>(list: &mut Array, len: INT, item: T) -> FuncReturn<()> {
|
||||
if len >= 0 {
|
||||
fn pad<T: Variant + Clone>(engine: &Engine, args: &mut [&mut Dynamic]) -> FuncReturn<()> {
|
||||
let len = *args[1].downcast_ref::<INT>().unwrap();
|
||||
|
||||
// Check if array will be over max size limit
|
||||
if engine.max_array_size > 0 && len > 0 && (len as usize) > engine.max_array_size {
|
||||
Err(Box::new(EvalAltResult::ErrorDataTooLarge(
|
||||
"Size of array".to_string(),
|
||||
engine.max_array_size,
|
||||
len as usize,
|
||||
Position::none(),
|
||||
)))
|
||||
} else if len >= 0 {
|
||||
let item = args[2].downcast_ref::<T>().unwrap().clone();
|
||||
let list = args[0].downcast_mut::<Array>().unwrap();
|
||||
|
||||
while list.len() < len as usize {
|
||||
push(list, item.clone())?;
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
macro_rules! reg_op {
|
||||
@ -42,11 +59,21 @@ macro_rules! reg_tri {
|
||||
$( $lib.set_fn_3_mut($op, $func::<$par>); )*
|
||||
};
|
||||
}
|
||||
macro_rules! reg_pad {
|
||||
($lib:expr, $op:expr, $func:ident, $($par:ty),*) => {
|
||||
$({
|
||||
$lib.set_fn_var_args($op,
|
||||
&[TypeId::of::<Array>(), TypeId::of::<INT>(), TypeId::of::<$par>()],
|
||||
$func::<$par>
|
||||
);
|
||||
})*
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
|
||||
reg_op!(lib, "push", push, INT, bool, char, ImmutableString, Array, ());
|
||||
reg_tri!(lib, "pad", pad, INT, bool, char, ImmutableString, Array, ());
|
||||
reg_pad!(lib, "pad", pad, INT, bool, char, ImmutableString, Array, ());
|
||||
reg_tri!(lib, "insert", ins, INT, bool, char, ImmutableString, Array, ());
|
||||
|
||||
lib.set_fn_2_mut("append", |x: &mut Array, y: Array| {
|
||||
@ -69,14 +96,14 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, {
|
||||
#[cfg(not(feature = "only_i64"))]
|
||||
{
|
||||
reg_op!(lib, "push", push, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
|
||||
reg_tri!(lib, "pad", pad, i8, u8, i16, u16, i32, u32, i64, u64, i128, u128);
|
||||
reg_pad!(lib, "pad", pad, i8, u8, i16, u16, i32, u32, i64, u64, i128, u128);
|
||||
reg_tri!(lib, "insert", ins, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
{
|
||||
reg_op!(lib, "push", push, f32, f64);
|
||||
reg_tri!(lib, "pad", pad, f32, f64);
|
||||
reg_pad!(lib, "pad", pad, f32, f64);
|
||||
reg_tri!(lib, "insert", ins, f32, f64);
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,17 @@
|
||||
use crate::any::Dynamic;
|
||||
use crate::def_package;
|
||||
use crate::engine::Engine;
|
||||
use crate::module::FuncReturn;
|
||||
use crate::parser::{ImmutableString, INT};
|
||||
use crate::result::EvalAltResult;
|
||||
use crate::token::Position;
|
||||
use crate::utils::StaticVec;
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
use crate::engine::Array;
|
||||
|
||||
use crate::stdlib::{
|
||||
any::TypeId,
|
||||
fmt::Display,
|
||||
format,
|
||||
string::{String, ToString},
|
||||
@ -210,14 +215,40 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
lib.set_fn_3_mut(
|
||||
lib.set_fn_var_args(
|
||||
"pad",
|
||||
|s: &mut ImmutableString, len: INT, ch: char| {
|
||||
let copy = s.make_mut();
|
||||
for _ in 0..copy.chars().count() - len as usize {
|
||||
copy.push(ch);
|
||||
&[TypeId::of::<ImmutableString>(), TypeId::of::<INT>(), TypeId::of::<char>()],
|
||||
|engine: &Engine, args: &mut [&mut Dynamic]| {
|
||||
let len = *args[1].downcast_ref::< INT>().unwrap();
|
||||
|
||||
// Check if string will be over max size limit
|
||||
if engine.max_string_size > 0 && len > 0 && (len as usize) > engine.max_string_size {
|
||||
Err(Box::new(EvalAltResult::ErrorDataTooLarge(
|
||||
"Length of string".to_string(),
|
||||
engine.max_string_size,
|
||||
len as usize,
|
||||
Position::none(),
|
||||
)))
|
||||
} else {
|
||||
let ch = *args[2].downcast_ref::< char>().unwrap();
|
||||
let s = args[0].downcast_mut::<ImmutableString>().unwrap();
|
||||
|
||||
let copy = s.make_mut();
|
||||
for _ in 0..copy.chars().count() - len as usize {
|
||||
copy.push(ch);
|
||||
}
|
||||
|
||||
if engine.max_string_size > 0 && copy.len() > engine.max_string_size {
|
||||
Err(Box::new(EvalAltResult::ErrorDataTooLarge(
|
||||
"Length of string".to_string(),
|
||||
engine.max_string_size,
|
||||
copy.len(),
|
||||
Position::none(),
|
||||
)))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
lib.set_fn_3_mut(
|
||||
|
@ -298,7 +298,9 @@ impl ParseSettings {
|
||||
}
|
||||
/// Make sure that the current level of expression nesting is within the maximum limit.
|
||||
pub fn ensure_level_within_max_limit(&self, limit: usize) -> Result<(), ParseError> {
|
||||
if self.level > limit {
|
||||
if limit == 0 {
|
||||
Ok(())
|
||||
} else if self.level > limit {
|
||||
Err(PERR::ExprTooDeep.into_err(self.pos))
|
||||
} else {
|
||||
Ok(())
|
||||
@ -624,7 +626,7 @@ impl Expr {
|
||||
|
||||
Self::Variable(_) => true,
|
||||
|
||||
expr => expr.is_constant(),
|
||||
_ => self.is_constant(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -765,7 +767,7 @@ fn parse_paren_expr(
|
||||
// ( xxx )
|
||||
(Token::RightParen, _) => Ok(expr),
|
||||
// ( <error>
|
||||
(Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)),
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
// ( xxx ???
|
||||
(_, pos) => Err(PERR::MissingToken(
|
||||
Token::RightParen.into(),
|
||||
@ -798,7 +800,7 @@ fn parse_call_expr(
|
||||
.into_err(settings.pos))
|
||||
}
|
||||
// id <error>
|
||||
Token::LexError(err) => return Err(PERR::BadInput(err.to_string()).into_err(settings.pos)),
|
||||
Token::LexError(err) => return Err(err.into_err(settings.pos)),
|
||||
// id()
|
||||
Token::RightParen => {
|
||||
eat_token(input, Token::RightParen);
|
||||
@ -878,9 +880,7 @@ fn parse_call_expr(
|
||||
.into_err(*pos))
|
||||
}
|
||||
// id(...args <error>
|
||||
(Token::LexError(err), pos) => {
|
||||
return Err(PERR::BadInput(err.to_string()).into_err(*pos))
|
||||
}
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(*pos)),
|
||||
// id(...args ???
|
||||
(_, pos) => {
|
||||
return Err(PERR::MissingToken(
|
||||
@ -1063,7 +1063,7 @@ fn parse_index_chain(
|
||||
}
|
||||
}
|
||||
}
|
||||
(Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(*pos)),
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(*pos)),
|
||||
(_, pos) => Err(PERR::MissingToken(
|
||||
Token::RightBracket.into(),
|
||||
"for a matching [ in this index expression".into(),
|
||||
@ -1108,9 +1108,7 @@ fn parse_array_literal(
|
||||
)
|
||||
.into_err(*pos))
|
||||
}
|
||||
(Token::LexError(err), pos) => {
|
||||
return Err(PERR::BadInput(err.to_string()).into_err(*pos))
|
||||
}
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(*pos)),
|
||||
(_, pos) => {
|
||||
return Err(PERR::MissingToken(
|
||||
Token::Comma.into(),
|
||||
@ -1142,9 +1140,7 @@ fn parse_map_literal(
|
||||
let (name, pos) = match input.next().unwrap() {
|
||||
(Token::Identifier(s), pos) => (s, pos),
|
||||
(Token::StringConst(s), pos) => (s, pos),
|
||||
(Token::LexError(err), pos) => {
|
||||
return Err(PERR::BadInput(err.to_string()).into_err(pos))
|
||||
}
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
(_, pos) if map.is_empty() => {
|
||||
return Err(
|
||||
PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into())
|
||||
@ -1162,9 +1158,7 @@ fn parse_map_literal(
|
||||
|
||||
match input.next().unwrap() {
|
||||
(Token::Colon, _) => (),
|
||||
(Token::LexError(err), pos) => {
|
||||
return Err(PERR::BadInput(err.to_string()).into_err(pos))
|
||||
}
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
(_, pos) => {
|
||||
return Err(PERR::MissingToken(
|
||||
Token::Colon.into(),
|
||||
@ -1203,9 +1197,7 @@ fn parse_map_literal(
|
||||
)
|
||||
.into_err(*pos))
|
||||
}
|
||||
(Token::LexError(err), pos) => {
|
||||
return Err(PERR::BadInput(err.to_string()).into_err(*pos))
|
||||
}
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(*pos)),
|
||||
(_, pos) => {
|
||||
return Err(
|
||||
PERR::MissingToken(Token::RightBrace.into(), MISSING_RBRACE.into())
|
||||
@ -1267,8 +1259,8 @@ fn parse_primary(
|
||||
Token::MapStart => parse_map_literal(input, state, settings.level_up())?,
|
||||
Token::True => Expr::True(settings.pos),
|
||||
Token::False => Expr::False(settings.pos),
|
||||
Token::LexError(err) => return Err(PERR::BadInput(err.to_string()).into_err(settings.pos)),
|
||||
token => {
|
||||
Token::LexError(err) => return Err(err.into_err(settings.pos)),
|
||||
_ => {
|
||||
return Err(
|
||||
PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(settings.pos)
|
||||
)
|
||||
@ -1378,12 +1370,7 @@ fn parse_unary(
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
PERR::BadInput(
|
||||
LexError::MalformedNumber(format!("-{}", x.0)).to_string(),
|
||||
)
|
||||
.into_err(pos)
|
||||
})
|
||||
.ok_or_else(|| LexError::MalformedNumber(format!("-{}", x.0)).into_err(pos))
|
||||
}
|
||||
|
||||
// Negative float
|
||||
@ -1846,7 +1833,7 @@ fn parse_binary_op(
|
||||
make_dot_expr(current_lhs, rhs, pos)?
|
||||
}
|
||||
|
||||
token => return Err(PERR::UnknownOperator(token.into()).into_err(pos)),
|
||||
op_token => return Err(PERR::UnknownOperator(op_token.into()).into_err(pos)),
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -1988,7 +1975,7 @@ fn parse_for(
|
||||
// Variable name
|
||||
(Token::Identifier(s), _) => s,
|
||||
// Bad identifier
|
||||
(Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)),
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
// EOF
|
||||
(Token::EOF, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||
// Not a variable name
|
||||
@ -1998,7 +1985,7 @@ fn parse_for(
|
||||
// for name in ...
|
||||
match input.next().unwrap() {
|
||||
(Token::In, _) => (),
|
||||
(Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)),
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
(_, pos) => {
|
||||
return Err(
|
||||
PERR::MissingToken(Token::In.into(), "after the iteration variable".into())
|
||||
@ -2036,7 +2023,7 @@ fn parse_let(
|
||||
// let name ...
|
||||
let (name, pos) = match input.next().unwrap() {
|
||||
(Token::Identifier(s), pos) => (s, pos),
|
||||
(Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)),
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||
};
|
||||
|
||||
@ -2107,7 +2094,7 @@ fn parse_import(
|
||||
// import expr as name ...
|
||||
let (name, _) = match input.next().unwrap() {
|
||||
(Token::Identifier(s), pos) => (s, pos),
|
||||
(Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)),
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||
};
|
||||
|
||||
@ -2130,9 +2117,7 @@ fn parse_export(
|
||||
loop {
|
||||
let (id, id_pos) = match input.next().unwrap() {
|
||||
(Token::Identifier(s), pos) => (s.clone(), pos),
|
||||
(Token::LexError(err), pos) => {
|
||||
return Err(PERR::BadInput(err.to_string()).into_err(pos))
|
||||
}
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
(_, pos) => return Err(PERR::VariableExpected.into_err(pos)),
|
||||
};
|
||||
|
||||
@ -2187,7 +2172,7 @@ fn parse_block(
|
||||
// Must start with {
|
||||
settings.pos = match input.next().unwrap() {
|
||||
(Token::LeftBrace, pos) => pos,
|
||||
(Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)),
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
(_, pos) => {
|
||||
return Err(PERR::MissingToken(
|
||||
Token::LeftBrace.into(),
|
||||
@ -2227,9 +2212,7 @@ fn parse_block(
|
||||
// { ... { stmt } ???
|
||||
(_, _) if !need_semicolon => (),
|
||||
// { ... stmt <error>
|
||||
(Token::LexError(err), pos) => {
|
||||
return Err(PERR::BadInput(err.to_string()).into_err(*pos))
|
||||
}
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(*pos)),
|
||||
// { ... stmt ???
|
||||
(_, pos) => {
|
||||
// Semicolons are not optional between statements
|
||||
@ -2378,9 +2361,7 @@ fn parse_fn(
|
||||
state.push((s.clone(), ScopeEntryType::Normal));
|
||||
params.push((s, pos))
|
||||
}
|
||||
(Token::LexError(err), pos) => {
|
||||
return Err(PERR::BadInput(err.to_string()).into_err(pos))
|
||||
}
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
(_, pos) => {
|
||||
return Err(PERR::MissingToken(Token::RightParen.into(), end_err).into_err(pos))
|
||||
}
|
||||
@ -2392,9 +2373,7 @@ fn parse_fn(
|
||||
(Token::Identifier(_), pos) => {
|
||||
return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos))
|
||||
}
|
||||
(Token::LexError(err), pos) => {
|
||||
return Err(PERR::BadInput(err.to_string()).into_err(pos))
|
||||
}
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(pos)),
|
||||
(_, pos) => {
|
||||
return Err(PERR::MissingToken(Token::Comma.into(), sep_err).into_err(pos))
|
||||
}
|
||||
@ -2565,9 +2544,7 @@ impl Engine {
|
||||
// { stmt } ???
|
||||
(_, _) if !need_semicolon => (),
|
||||
// stmt <error>
|
||||
(Token::LexError(err), pos) => {
|
||||
return Err(PERR::BadInput(err.to_string()).into_err(*pos))
|
||||
}
|
||||
(Token::LexError(err), pos) => return Err(err.into_err(*pos)),
|
||||
// stmt ???
|
||||
(_, pos) => {
|
||||
// Semicolons are not optional between statements
|
||||
|
38
src/token.rs
38
src/token.rs
@ -862,6 +862,14 @@ impl<'a> TokenIterator<'a> {
|
||||
self.eat_next();
|
||||
return Some((Token::MinusAssign, pos));
|
||||
}
|
||||
('-', '>') => {
|
||||
return Some((
|
||||
Token::LexError(Box::new(LERR::ImproperSymbol(
|
||||
"'->' is not a valid symbol. This is not C or C++!".to_string(),
|
||||
))),
|
||||
pos,
|
||||
))
|
||||
}
|
||||
('-', _) if self.can_be_unary => return Some((Token::UnaryMinus, pos)),
|
||||
('-', _) => return Some((Token::Minus, pos)),
|
||||
|
||||
@ -931,7 +939,7 @@ impl<'a> TokenIterator<'a> {
|
||||
// Warn against `===`
|
||||
if self.peek_next() == Some('=') {
|
||||
return Some((
|
||||
Token::LexError(Box::new(LERR::ImproperKeyword(
|
||||
Token::LexError(Box::new(LERR::ImproperSymbol(
|
||||
"'===' is not a valid operator. This is not JavaScript! Should it be '=='?"
|
||||
.to_string(),
|
||||
))),
|
||||
@ -941,18 +949,44 @@ impl<'a> TokenIterator<'a> {
|
||||
|
||||
return Some((Token::EqualsTo, pos));
|
||||
}
|
||||
('=', '>') => {
|
||||
return Some((
|
||||
Token::LexError(Box::new(LERR::ImproperSymbol(
|
||||
"'=>' is not a valid symbol. This is not Rust! Should it be '>='?"
|
||||
.to_string(),
|
||||
))),
|
||||
pos,
|
||||
))
|
||||
}
|
||||
('=', _) => return Some((Token::Equals, pos)),
|
||||
|
||||
(':', ':') => {
|
||||
self.eat_next();
|
||||
return Some((Token::DoubleColon, pos));
|
||||
}
|
||||
(':', '=') => {
|
||||
return Some((
|
||||
Token::LexError(Box::new(LERR::ImproperSymbol(
|
||||
"':=' is not a valid assignment operator. This is not Pascal! Should it be simply '='?"
|
||||
.to_string(),
|
||||
))),
|
||||
pos,
|
||||
))
|
||||
}
|
||||
(':', _) => return Some((Token::Colon, pos)),
|
||||
|
||||
('<', '=') => {
|
||||
self.eat_next();
|
||||
return Some((Token::LessThanEqualsTo, pos));
|
||||
}
|
||||
('<', '-') => {
|
||||
return Some((
|
||||
Token::LexError(Box::new(LERR::ImproperSymbol(
|
||||
"'<-' is not a valid symbol. Should it be '<='?".to_string(),
|
||||
))),
|
||||
pos,
|
||||
))
|
||||
}
|
||||
('<', '<') => {
|
||||
self.eat_next();
|
||||
|
||||
@ -993,7 +1027,7 @@ impl<'a> TokenIterator<'a> {
|
||||
// Warn against `!==`
|
||||
if self.peek_next() == Some('=') {
|
||||
return Some((
|
||||
Token::LexError(Box::new(LERR::ImproperKeyword(
|
||||
Token::LexError(Box::new(LERR::ImproperSymbol(
|
||||
"'!==' is not a valid operator. This is not JavaScript! Should it be '!='?"
|
||||
.to_string(),
|
||||
))),
|
||||
|
@ -14,7 +14,12 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
assert!(matches!(
|
||||
engine.compile(r#"let x = "hello, world!";"#).expect_err("should error"),
|
||||
ParseError(x, _) if *x == ParseErrorType::BadInput("Length of string literal exceeds the maximum limit (10)".to_string())
|
||||
ParseError(x, _) if *x == ParseErrorType::LiteralTooLarge("Length of string literal".to_string(), 10)
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
engine.compile(r#"let x = "朝に紅顔、暮に白骨";"#).expect_err("should error"),
|
||||
ParseError(x, _) if *x == ParseErrorType::LiteralTooLarge("Length of string literal".to_string(), 10)
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
@ -30,6 +35,19 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
|
||||
EvalAltResult::ErrorDataTooLarge(_, 10, 13, _)
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.eval::<String>(
|
||||
r#"
|
||||
let x = "hello";
|
||||
x.pad(100, '!');
|
||||
x
|
||||
"#
|
||||
)
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorDataTooLarge(_, 10, 100, _)
|
||||
));
|
||||
|
||||
engine.set_max_string_size(0);
|
||||
|
||||
assert_eq!(
|
||||
@ -38,7 +56,7 @@ fn test_max_string_size() -> Result<(), Box<EvalAltResult>> {
|
||||
let x = "hello, ";
|
||||
let y = "world!";
|
||||
x + y
|
||||
"#
|
||||
"#
|
||||
)?,
|
||||
"hello, world!"
|
||||
);
|
||||
@ -52,6 +70,9 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
engine.set_max_array_size(10);
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
engine.set_max_map_size(10);
|
||||
|
||||
assert!(matches!(
|
||||
engine
|
||||
.compile("let x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];")
|
||||
@ -71,6 +92,57 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorDataTooLarge(_, 10, 12, _)
|
||||
));
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.eval::<Array>(
|
||||
r"
|
||||
let x = [1,2,3,4,5,6];
|
||||
x.pad(100, 42);
|
||||
x
|
||||
"
|
||||
)
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorDataTooLarge(_, 10, 100, _)
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.eval::<Array>(
|
||||
r"
|
||||
let x = [1,2,3];
|
||||
[x, x, x, x]
|
||||
"
|
||||
)
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorDataTooLarge(_, 10, 12, _)
|
||||
));
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.eval::<Array>(
|
||||
r"
|
||||
let x = #{a:1, b:2, c:3};
|
||||
[x, x, x, x]
|
||||
"
|
||||
)
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorDataTooLarge(_, 10, 12, _)
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.eval::<Array>(
|
||||
r"
|
||||
let x = [1];
|
||||
let y = [x, x];
|
||||
let z = [y, y];
|
||||
[z, z, z]
|
||||
"
|
||||
)
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorDataTooLarge(_, 10, 12, _)
|
||||
));
|
||||
|
||||
engine.set_max_array_size(0);
|
||||
|
||||
@ -81,12 +153,24 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
|
||||
let x = [1,2,3,4,5,6];
|
||||
let y = [7,8,9,10,11,12];
|
||||
x + y
|
||||
"
|
||||
"
|
||||
)?
|
||||
.len(),
|
||||
12
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine
|
||||
.eval::<Array>(
|
||||
r"
|
||||
let x = [1,2,3];
|
||||
[x, x, x, x]
|
||||
"
|
||||
)?
|
||||
.len(),
|
||||
4
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -96,6 +180,9 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
engine.set_max_map_size(10);
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
engine.set_max_array_size(10);
|
||||
|
||||
assert!(matches!(
|
||||
engine
|
||||
.compile("let x = #{a:1,b:2,c:3,d:4,e:5,f:6,g:7,h:8,i:9,j:10,k:11,l:12,m:13,n:14,o:15};")
|
||||
@ -116,6 +203,31 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
|
||||
EvalAltResult::ErrorDataTooLarge(_, 10, 12, _)
|
||||
));
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.eval::<Map>(
|
||||
r"
|
||||
let x = #{a:1,b:2,c:3};
|
||||
#{u:x, v:x, w:x, z:x}
|
||||
"
|
||||
)
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorDataTooLarge(_, 10, 12, _)
|
||||
));
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.eval::<Map>(
|
||||
r"
|
||||
let x = [1, 2, 3];
|
||||
#{u:x, v:x, w:x, z:x}
|
||||
"
|
||||
)
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorDataTooLarge(_, 10, 12, _)
|
||||
));
|
||||
|
||||
engine.set_max_map_size(0);
|
||||
|
||||
assert_eq!(
|
||||
@ -125,11 +237,23 @@ fn test_max_map_size() -> Result<(), Box<EvalAltResult>> {
|
||||
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
|
||||
"
|
||||
"
|
||||
)?
|
||||
.len(),
|
||||
12
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
engine
|
||||
.eval::<Map>(
|
||||
r"
|
||||
let x = #{a:1,b:2,c:3};
|
||||
#{u:x, v:x, w:x, z:x}
|
||||
"
|
||||
)?
|
||||
.len(),
|
||||
4
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
#![cfg(not(feature = "no_module"))]
|
||||
use rhai::{
|
||||
module_resolvers, Engine, EvalAltResult, Module, ParseError, ParseErrorType, Scope, INT,
|
||||
module_resolvers, Dynamic, Engine, EvalAltResult, Module, ParseError, ParseErrorType, Scope,
|
||||
INT,
|
||||
};
|
||||
use std::any::TypeId;
|
||||
|
||||
#[test]
|
||||
fn test_module() {
|
||||
@ -20,6 +22,7 @@ fn test_module_sub_module() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
let mut sub_module2 = Module::new();
|
||||
sub_module2.set_var("answer", 41 as INT);
|
||||
|
||||
let hash_inc = sub_module2.set_fn_1("inc", |x: INT| Ok(x + 1));
|
||||
|
||||
sub_module.set_sub_module("universe", sub_module2);
|
||||
@ -130,7 +133,7 @@ fn test_module_resolver() -> Result<(), Box<EvalAltResult>> {
|
||||
EvalAltResult::ErrorInFunctionCall(fn_name, _, _) if fn_name == "foo"
|
||||
));
|
||||
|
||||
engine.set_max_modules(0);
|
||||
engine.set_max_modules(1000);
|
||||
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
engine.eval::<()>(
|
||||
|
@ -111,3 +111,20 @@ fn test_max_operations_eval() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_max_operations_progress() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
engine.set_max_operations(500);
|
||||
|
||||
engine.on_progress(|&count| count < 100);
|
||||
|
||||
assert!(matches!(
|
||||
*engine
|
||||
.eval::<()>("for x in range(0, 500) {}")
|
||||
.expect_err("should error"),
|
||||
EvalAltResult::ErrorTerminated(_)
|
||||
));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user