From 26ad454cb1036dc0f7baac12f1fb07cf5c0f0896 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 10 Apr 2023 18:47:53 +0800 Subject: [PATCH 01/10] Streamline data types. --- src/ast/stmt.rs | 6 +++--- src/eval/stmt.rs | 8 +++++--- src/module/mod.rs | 20 +++++++++++++++---- src/optimizer.rs | 50 ++++++++++++++++------------------------------ src/parser.rs | 6 +++--- tests/optimizer.rs | 2 -- 6 files changed, 44 insertions(+), 48 deletions(-) diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index 65634092..79db87b2 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -721,7 +721,7 @@ pub enum Stmt { /// This variant does not map to any language structure. It is currently only used only to /// convert normal variables into shared variables when they are _captured_ by a closure. #[cfg(not(feature = "no_closure"))] - Share(Box, Position)>>), + Share(Box)>>), } impl Default for Stmt { @@ -816,7 +816,7 @@ impl Stmt { Self::Export(.., pos) => *pos, #[cfg(not(feature = "no_closure"))] - Self::Share(x) => x[0].2, + Self::Share(x) => x[0].0.pos, } } /// Override the [position][Position] of this statement. @@ -848,7 +848,7 @@ impl Stmt { Self::Export(.., pos) => *pos = new_pos, #[cfg(not(feature = "no_closure"))] - Self::Share(x) => x.iter_mut().for_each(|(_, _, pos)| *pos = new_pos), + Self::Share(x) => x.iter_mut().for_each(|(x, _)| x.pos = new_pos), } self diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 5d090b76..29ded4a1 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -927,10 +927,10 @@ impl Engine { // Share statement #[cfg(not(feature = "no_closure"))] Stmt::Share(x) => { - for (name, index, pos) in &**x { + for (var, index) in &**x { if let Some(index) = index .map(|n| scope.len() - n.get()) - .or_else(|| scope.search(name)) + .or_else(|| scope.search(&var.name)) { let val = scope.get_mut_by_index(index); @@ -939,7 +939,9 @@ impl Engine { *val = std::mem::take(val).into_shared(); } } else { - return Err(ERR::ErrorVariableNotFound(name.to_string(), *pos).into()); + return Err( + ERR::ErrorVariableNotFound(var.name.to_string(), var.pos).into() + ); } } diff --git a/src/module/mod.rs b/src/module/mod.rs index 26dbca16..6e7fe262 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1986,23 +1986,35 @@ impl Module { } /// Get an iterator to the sub-modules in the [`Module`]. - #[inline] + #[inline(always)] pub fn iter_sub_modules(&self) -> impl Iterator { + self.iter_sub_modules_raw().map(|(k, m)| (k.as_str(), m)) + } + /// Get an iterator to the sub-modules in the [`Module`]. + #[inline] + pub(crate) fn iter_sub_modules_raw( + &self, + ) -> impl Iterator { self.modules .as_ref() .into_iter() .flatten() - .map(|(k, m)| (k.as_str(), m)) + .map(|(k, m)| (k, m)) } /// Get an iterator to the variables in the [`Module`]. - #[inline] + #[inline(always)] pub fn iter_var(&self) -> impl Iterator { + self.iter_var_raw().map(|(k, v)| (k.as_str(), v)) + } + /// Get an iterator to the variables in the [`Module`]. + #[inline] + pub(crate) fn iter_var_raw(&self) -> impl Iterator { self.variables .as_ref() .into_iter() .flatten() - .map(|(k, v)| (k.as_str(), v)) + .map(|(k, v)| (k, v)) } /// Get an iterator to the functions in the [`Module`]. diff --git a/src/optimizer.rs b/src/optimizer.rs index 80e9d5e0..5300fd0c 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -14,10 +14,9 @@ use crate::func::builtin::get_builtin_binary_op_fn; use crate::func::hashing::get_hasher; use crate::module::ModuleFlags; use crate::tokenizer::Token; -use crate::types::dynamic::AccessMode; use crate::{ - calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnPtr, Identifier, ImmutableString, Position, - Scope, StaticVec, AST, + calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnPtr, ImmutableString, Position, Scope, + StaticVec, AST, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -54,8 +53,8 @@ impl Default for OptimizationLevel { struct OptimizerState<'a> { /// Has the [`AST`] been changed during this pass? changed: bool, - /// Collection of constants to use for eager function evaluations. - variables: StaticVec<(Identifier, AccessMode, Option)>, + /// Collection of variables/constants to use for eager function evaluations. + variables: StaticVec<(ImmutableString, Option)>, /// Activate constants propagation? propagate_constants: bool, /// An [`Engine`] instance for eager function evaluation. @@ -115,14 +114,11 @@ impl<'a> OptimizerState<'a> { self.variables.truncate(len); } /// Add a new variable to the list. + /// + /// `Some(value)` if constant, `None` or otherwise. #[inline(always)] - pub fn push_var( - &mut self, - name: impl Into, - access: AccessMode, - value: Option, - ) { - self.variables.push((name.into(), access, value)); + pub fn push_var(&mut self, name: ImmutableString, value: Option) { + self.variables.push((name, value)); } /// Look up a constant from the list. #[inline] @@ -131,12 +127,9 @@ impl<'a> OptimizerState<'a> { return None; } - for (n, access, value) in self.variables.iter().rev() { - if n == name { - return match access { - AccessMode::ReadWrite => None, - AccessMode::ReadOnly => value.as_ref(), - }; + for (n, value) in self.variables.iter().rev() { + if n.as_str() == name { + return value.as_ref(); } } @@ -237,16 +230,13 @@ fn optimize_stmt_block( optimize_expr(&mut x.1, state, false); if x.1.is_constant() { - state.push_var( - x.0.as_str(), - AccessMode::ReadOnly, - x.1.get_literal_value(), - ); + state + .push_var(x.0.name.clone(), Some(x.1.get_literal_value().unwrap())); } } else { // Add variables into the state optimize_expr(&mut x.1, state, false); - state.push_var(x.0.as_str(), AccessMode::ReadWrite, None); + state.push_var(x.0.name.clone(), None); } } // Optimize the statement @@ -858,7 +848,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b #[cfg(not(feature = "no_closure"))] Stmt::Share(x) => { let len = x.len(); - x.retain(|(v, _, _)| !state.find_constant(v).is_some()); + x.retain(|(v, _)| !state.find_constant(v).is_some()); if x.len() != len { state.set_dirty(); } @@ -1335,20 +1325,14 @@ impl Engine { .iter() .rev() .flat_map(|m| m.iter_var()) - .for_each(|(name, value)| { - state.push_var(name, AccessMode::ReadOnly, Some(value.clone())) - }); + .for_each(|(name, value)| state.push_var(name.into(), Some(value.clone()))); // Add constants and variables from the scope scope .into_iter() .flat_map(Scope::iter) .for_each(|(name, constant, value)| { - if constant { - state.push_var(name, AccessMode::ReadOnly, Some(value)); - } else { - state.push_var(name, AccessMode::ReadWrite, None); - } + state.push_var(name.into(), if constant { Some(value) } else { None }); }); optimize_stmt_block(statements, &mut state, true, false, true) diff --git a/src/parser.rs b/src/parser.rs index 2300b402..00fd7240 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3781,9 +3781,9 @@ impl Engine { statements.push(Stmt::Share( externals .into_iter() - .map(|Ident { name, pos }| { - let (index, _) = parent.access_var(&name, lib, pos); - (name, index, pos) + .map(|var| { + let (index, _) = parent.access_var(&var.name, lib, var.pos); + (var, index) }) .collect::>() .into(), diff --git a/tests/optimizer.rs b/tests/optimizer.rs index 80b8ccf1..780eb771 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -182,8 +182,6 @@ fn test_optimizer_reoptimize() -> Result<(), Box> { let scope: Scope = ast.iter_literal_variables(true, false).collect(); let ast = engine.optimize_ast(&scope, ast, OptimizationLevel::Simple); - println!("{ast:#?}"); - assert_eq!(engine.eval_ast::(&ast)?, 84); Ok(()) From 20c535ecd3f5a2bb806e7c779c3096cead884d45 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 10 Apr 2023 22:29:44 +0800 Subject: [PATCH 02/10] Add full optimization test. --- tests/optimizer.rs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/optimizer.rs b/tests/optimizer.rs index 780eb771..6e2c36d3 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -186,3 +186,39 @@ fn test_optimizer_reoptimize() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_optimizer_full() -> Result<(), Box> { + #[derive(Debug, Clone)] + struct TestStruct(INT); + + const SCRIPT: &str = " + const FOO = ts(40) + ts(2); + value(FOO) + "; + + let mut engine = Engine::new(); + let mut scope = Scope::new(); + + engine.set_optimization_level(OptimizationLevel::Full); + + engine + .register_type_with_name::("TestStruct") + .register_fn("ts", |n: INT| TestStruct(n)) + .register_fn("value", |ts: &mut TestStruct| ts.0) + .register_fn("+", |ts1: &mut TestStruct, ts2: TestStruct| { + TestStruct(ts1.0 + ts2.0) + }); + + let ast = engine.compile(SCRIPT)?; + + assert_eq!(ast.statements().len(), 2); + + assert_eq!(engine.eval_ast_with_scope::(&mut scope, &ast)?, 42); + + assert_eq!(scope.len(), 1); + + assert_eq!(scope.get_value::("FOO").unwrap().0, 42); + + Ok(()) +} From 8662ffec623a6b20f5e4552cf14e2ba1d19c646d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 10 Apr 2023 23:23:59 +0800 Subject: [PATCH 03/10] Use unwrap_err. --- tests/arrays.rs | 19 +++++------------- tests/assignments.rs | 45 +++++++++--------------------------------- tests/closures.rs | 7 ++----- tests/constants.rs | 4 ++-- tests/custom_syntax.rs | 13 +++++------- tests/data_size.rs | 34 +++++++++++++++---------------- tests/eval.rs | 2 +- tests/fn_ptr.rs | 2 +- tests/functions.rs | 4 ++-- tests/internal_fn.rs | 8 ++++---- tests/looping.rs | 9 +++------ tests/maps.rs | 32 ++++++++++++------------------ tests/method_call.rs | 4 ++-- tests/mismatched_op.rs | 8 ++++---- tests/modules.rs | 10 +++++----- tests/operations.rs | 14 ++++++------- tests/ops.rs | 6 +++--- tests/plugins.rs | 2 +- tests/stack.rs | 8 ++++---- tests/string.rs | 2 +- tests/switch.rs | 8 ++++---- tests/tokens.rs | 6 +++--- tests/unit.rs | 2 +- tests/var_scope.rs | 4 ++-- 24 files changed, 100 insertions(+), 153 deletions(-) diff --git a/tests/arrays.rs b/tests/arrays.rs index 8820a54a..23d0f98f 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -210,34 +210,25 @@ fn test_array_index_types() -> Result<(), Box> { engine.compile("[1, 2, 3][0]['x']")?; assert!(matches!( - engine - .compile("[1, 2, 3]['x']") - .expect_err("should error") - .err_type(), + engine.compile("[1, 2, 3]['x']").unwrap_err().err_type(), ParseErrorType::MalformedIndexExpr(..) )); #[cfg(not(feature = "no_float"))] assert!(matches!( - engine - .compile("[1, 2, 3][123.456]") - .expect_err("should error") - .err_type(), + engine.compile("[1, 2, 3][123.456]").unwrap_err().err_type(), ParseErrorType::MalformedIndexExpr(..) )); assert!(matches!( - engine - .compile("[1, 2, 3][()]") - .expect_err("should error") - .err_type(), + engine.compile("[1, 2, 3][()]").unwrap_err().err_type(), ParseErrorType::MalformedIndexExpr(..) )); assert!(matches!( engine .compile(r#"[1, 2, 3]["hello"]"#) - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::MalformedIndexExpr(..) )); @@ -245,7 +236,7 @@ fn test_array_index_types() -> Result<(), Box> { assert!(matches!( engine .compile("[1, 2, 3][true && false]") - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::MalformedIndexExpr(..) )); diff --git a/tests/assignments.rs b/tests/assignments.rs index e2a90d4f..08846845 100644 --- a/tests/assignments.rs +++ b/tests/assignments.rs @@ -21,71 +21,44 @@ fn test_assignments_bad_lhs() -> Result<(), Box> { let engine = Engine::new(); assert_eq!( - *engine - .compile("(x+y) = 42;") - .expect_err("should error") - .err_type(), + *engine.compile("(x+y) = 42;").unwrap_err().err_type(), ParseErrorType::AssignmentToInvalidLHS(String::new()) ); assert_eq!( - *engine - .compile("foo(x) = 42;") - .expect_err("should error") - .err_type(), + *engine.compile("foo(x) = 42;").unwrap_err().err_type(), ParseErrorType::AssignmentToInvalidLHS(String::new()) ); assert_eq!( - *engine - .compile("true = 42;") - .expect_err("should error") - .err_type(), + *engine.compile("true = 42;").unwrap_err().err_type(), ParseErrorType::AssignmentToConstant(String::new()) ); assert_eq!( - *engine - .compile("123 = 42;") - .expect_err("should error") - .err_type(), + *engine.compile("123 = 42;").unwrap_err().err_type(), ParseErrorType::AssignmentToConstant(String::new()) ); #[cfg(not(feature = "no_object"))] { assert_eq!( - *engine - .compile("x.foo() = 42;") - .expect_err("should error") - .err_type(), + *engine.compile("x.foo() = 42;").unwrap_err().err_type(), ParseErrorType::AssignmentToInvalidLHS(String::new()) ); assert_eq!( - *engine - .compile("x.foo().x.y = 42;") - .expect_err("should error") - .err_type(), + *engine.compile("x.foo().x.y = 42;").unwrap_err().err_type(), ParseErrorType::AssignmentToInvalidLHS(String::new()) ); assert_eq!( - *engine - .compile("x.y.z.foo() = 42;") - .expect_err("should error") - .err_type(), + *engine.compile("x.y.z.foo() = 42;").unwrap_err().err_type(), ParseErrorType::AssignmentToInvalidLHS(String::new()) ); #[cfg(not(feature = "no_index"))] assert_eq!( - *engine - .compile("x.foo()[0] = 42;") - .expect_err("should error") - .err_type(), + *engine.compile("x.foo()[0] = 42;").unwrap_err().err_type(), ParseErrorType::AssignmentToInvalidLHS(String::new()) ); #[cfg(not(feature = "no_index"))] assert_eq!( - *engine - .compile("x[y].z.foo() = 42;") - .expect_err("should error") - .err_type(), + *engine.compile("x[y].z.foo() = 42;").unwrap_err().err_type(), ParseErrorType::AssignmentToInvalidLHS(String::new()) ); } diff --git a/tests/closures.rs b/tests/closures.rs index 0e934c30..a0511456 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -47,10 +47,7 @@ fn test_closures() -> Result<(), Box> { scope.push("x", 42 as INT); assert!(matches!( - engine - .compile_expression("|x| {}") - .expect_err("should error") - .err_type(), + engine.compile_expression("|x| {}").unwrap_err().err_type(), ParseErrorType::BadInput(..) )); @@ -292,7 +289,7 @@ fn test_closures_data_race() -> Result<(), Box> { a " ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorDataRace(..) )); diff --git a/tests/constants.rs b/tests/constants.rs index bc49e8ee..943b518e 100644 --- a/tests/constants.rs +++ b/tests/constants.rs @@ -86,7 +86,7 @@ fn test_constant_mut() -> Result<(), Box> { MY_NUMBER.value = 42; " ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorNonPureMethodCallOnConstant(..) )); @@ -119,7 +119,7 @@ fn test_constant_mut() -> Result<(), Box> { assert!(matches!( *engine .run_with_scope(&mut scope, "MY_NUMBER.value = 42;") - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorNonPureMethodCallOnConstant(..) )); diff --git a/tests/custom_syntax.rs b/tests/custom_syntax.rs index ec71e904..85072baf 100644 --- a/tests/custom_syntax.rs +++ b/tests/custom_syntax.rs @@ -13,11 +13,11 @@ fn test_custom_syntax() -> Result<(), Box> { // Disable 'while' and make sure it still works with custom syntax engine.disable_symbol("while"); assert!(matches!( - engine.compile("while false {}").expect_err("should error").err_type(), + engine.compile("while false {}").unwrap_err().err_type(), ParseErrorType::Reserved(err) if err == "while" )); assert!(matches!( - engine.compile("let while = 0").expect_err("should error").err_type(), + engine.compile("let while = 0").unwrap_err().err_type(), ParseErrorType::Reserved(err) if err == "while" )); @@ -127,7 +127,7 @@ fn test_custom_syntax() -> Result<(), Box> { assert!(matches!( *engine .run("let foo = (exec [x<<15] -> { x += 2 } while x < 42) * 10;") - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorRuntime(..) )); @@ -195,7 +195,7 @@ fn test_custom_syntax() -> Result<(), Box> { assert_eq!( *engine .register_custom_syntax(["!"], false, |_, _| Ok(Dynamic::UNIT)) - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::BadInput(LexError::ImproperSymbol( "!".to_string(), @@ -364,10 +364,7 @@ fn test_custom_syntax_raw() -> Result<(), Box> { ); assert_eq!(engine.eval::("(hello kitty) + foo")?, 1041); assert_eq!( - *engine - .compile("hello hey") - .expect_err("should error") - .err_type(), + *engine.compile("hello hey").unwrap_err().err_type(), ParseErrorType::BadInput(LexError::ImproperSymbol("hey".to_string(), String::new())) ); diff --git a/tests/data_size.rs b/tests/data_size.rs index 50f35f50..feb44199 100644 --- a/tests/data_size.rs +++ b/tests/data_size.rs @@ -15,7 +15,7 @@ fn test_max_string_size() -> Result<(), Box> { assert_eq!( *engine .compile(r#"let x = "hello, world!";"#) - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::LiteralTooLarge("Length of string".to_string(), 10) ); @@ -23,7 +23,7 @@ fn test_max_string_size() -> Result<(), Box> { assert_eq!( *engine .compile(r#"let x = "朝に紅顔、暮に白骨";"#) - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::LiteralTooLarge("Length of string".to_string(), 10) ); @@ -37,7 +37,7 @@ fn test_max_string_size() -> Result<(), Box> { x + y "# ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorDataTooLarge(..) )); @@ -51,7 +51,7 @@ fn test_max_string_size() -> Result<(), Box> { x "# ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorDataTooLarge(..) )); @@ -83,7 +83,7 @@ fn test_max_array_size() -> Result<(), Box> { assert_eq!( *engine .compile("let x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];") - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::LiteralTooLarge("Size of array literal".to_string(), 10) ); @@ -97,7 +97,7 @@ fn test_max_array_size() -> Result<(), Box> { x + y " ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorDataTooLarge(..) )); @@ -130,7 +130,7 @@ fn test_max_array_size() -> Result<(), Box> { } " ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorDataTooLarge(..) )); @@ -142,7 +142,7 @@ fn test_max_array_size() -> Result<(), Box> { loop { x[0] = x; } " ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorDataTooLarge(..) )); @@ -168,7 +168,7 @@ fn test_max_array_size() -> Result<(), Box> { x " ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorDataTooLarge(..) )); @@ -190,7 +190,7 @@ fn test_max_array_size() -> Result<(), Box> { [x, x, x, x] " ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorDataTooLarge(..) )); @@ -203,7 +203,7 @@ fn test_max_array_size() -> Result<(), Box> { [x, x, x, x] " ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorDataTooLarge(..) )); @@ -217,7 +217,7 @@ fn test_max_array_size() -> Result<(), Box> { [z, z, z] " ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorDataTooLarge(..) )); @@ -265,7 +265,7 @@ fn test_max_map_size() -> Result<(), Box> { .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};" ) - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::LiteralTooLarge( "Number of properties in object map literal".to_string(), @@ -281,7 +281,7 @@ fn test_max_map_size() -> Result<(), Box> { loop { x.a = x; } " ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorDataTooLarge(..) )); @@ -294,7 +294,7 @@ fn test_max_map_size() -> Result<(), Box> { x + y " ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorDataTooLarge(..) )); @@ -306,7 +306,7 @@ fn test_max_map_size() -> Result<(), Box> { #{u:x, v:x, w:x, z:x} " ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorDataTooLarge(..) )); @@ -319,7 +319,7 @@ fn test_max_map_size() -> Result<(), Box> { #{u:x, v:x, w:x, z:x} " ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorDataTooLarge(..) )); diff --git a/tests/eval.rs b/tests/eval.rs index a7b834f9..bd55e72f 100644 --- a/tests/eval.rs +++ b/tests/eval.rs @@ -169,7 +169,7 @@ fn test_eval_disabled() -> Result<(), Box> { assert!(matches!( engine .compile(r#"eval("40 + 2")"#) - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::BadInput(LexError::ImproperSymbol(err, ..)) if err == "eval" )); diff --git a/tests/fn_ptr.rs b/tests/fn_ptr.rs index 1e2baa84..1d587e2b 100644 --- a/tests/fn_ptr.rs +++ b/tests/fn_ptr.rs @@ -72,7 +72,7 @@ fn test_fn_ptr() -> Result<(), Box> { x "# ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorInFunctionCall(fn_name, _, err, ..) if fn_name == "foo" && matches!(*err, EvalAltResult::ErrorUnboundThis(..)) )); diff --git a/tests/functions.rs b/tests/functions.rs index a079bbdd..ef873abd 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -93,7 +93,7 @@ fn test_functions_global_module() -> Result<(), Box> { const ANSWER = 42; foo() } - ").expect_err("should error"), + ").unwrap_err(), EvalAltResult::ErrorInFunctionCall(.., err, _) if matches!(&*err, EvalAltResult::ErrorVariableNotFound(v, ..) if v == "global::ANSWER") )); @@ -112,7 +112,7 @@ fn test_functions_global_module() -> Result<(), Box> { const LOCAL_VALUE = 42; global::LOCAL_VALUE }); - ").expect_err("should error"), + ").unwrap_err(), EvalAltResult::ErrorInFunctionCall(.., err, _) if matches!(&*err, EvalAltResult::ErrorVariableNotFound(v, ..) if v == "global::LOCAL_VALUE") )); diff --git a/tests/internal_fn.rs b/tests/internal_fn.rs index ddd8ff76..f440e479 100644 --- a/tests/internal_fn.rs +++ b/tests/internal_fn.rs @@ -109,7 +109,7 @@ fn test_internal_fn_overloading() -> Result<(), Box> { fn abc(x) { x - 42 } " ) - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::FnDuplicatedDefinition("abc".to_string(), 1) ); @@ -125,7 +125,7 @@ fn test_internal_fn_params() -> Result<(), Box> { assert_eq!( *engine .compile("fn hello(x, x) { x }") - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::FnDuplicatedParam("hello".to_string(), "x".to_string()) ); @@ -169,7 +169,7 @@ fn test_function_pointers() -> Result<(), Box> { #[cfg(not(feature = "no_object"))] assert!(matches!( - *engine.eval::(r#"let f = Fn("abc"); f.call(0)"#).expect_err("should error"), + *engine.eval::(r#"let f = Fn("abc"); f.call(0)"#).unwrap_err(), EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("abc (") )); @@ -247,7 +247,7 @@ fn test_internal_fn_bang() -> Result<(), Box> { y.foo!(); " ) - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::MalformedCapture(..) )); diff --git a/tests/looping.rs b/tests/looping.rs index e011eb6b..191b7e85 100644 --- a/tests/looping.rs +++ b/tests/looping.rs @@ -27,10 +27,7 @@ fn test_loop() -> Result<(), Box> { ); assert_eq!( - *engine - .compile("let x = 0; break;") - .expect_err("should error") - .err_type(), + *engine.compile("let x = 0; break;").unwrap_err().err_type(), ParseErrorType::LoopBreak ); @@ -38,7 +35,7 @@ fn test_loop() -> Result<(), Box> { assert_eq!( *engine .compile("loop { let f = || { break; } }") - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::LoopBreak ); @@ -46,7 +43,7 @@ fn test_loop() -> Result<(), Box> { assert_eq!( *engine .compile("let x = 0; if x > 0 { continue; }") - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::LoopBreak ); diff --git a/tests/maps.rs b/tests/maps.rs index 2bfab623..d72b6356 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -52,7 +52,7 @@ b`: 1}; y["a\nb"] assert!(matches!( *engine .eval::("let y = #{`a${1}`: 1}; y.a1") - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorParsing(ParseErrorType::PropertyExpected, ..) )); @@ -116,7 +116,7 @@ fn test_map_prop() -> Result<(), Box> { engine.set_fail_on_invalid_map_property(true); assert!( - matches!(*engine.eval::<()>("let x = #{a: 42}; x.b").expect_err("should error"), + matches!(*engine.eval::<()>("let x = #{a: 42}; x.b").unwrap_err(), EvalAltResult::ErrorPropertyNotFound(prop, _) if prop == "b" ) ); @@ -134,7 +134,7 @@ fn test_map_index_types() -> Result<(), Box> { assert!(matches!( engine .compile("#{a:1, b:2, c:3}['x']") - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::MalformedIndexExpr(..) )); @@ -142,7 +142,7 @@ fn test_map_index_types() -> Result<(), Box> { assert!(matches!( engine .compile("#{a:1, b:2, c:3}[1]") - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::MalformedIndexExpr(..) )); @@ -151,7 +151,7 @@ fn test_map_index_types() -> Result<(), Box> { assert!(matches!( engine .compile("#{a:1, b:2, c:3}[123.456]") - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::MalformedIndexExpr(..) )); @@ -159,7 +159,7 @@ fn test_map_index_types() -> Result<(), Box> { assert!(matches!( engine .compile("#{a:1, b:2, c:3}[()]") - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::MalformedIndexExpr(..) )); @@ -167,7 +167,7 @@ fn test_map_index_types() -> Result<(), Box> { assert!(matches!( engine .compile("#{a:1, b:2, c:3}[true && false]") - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::MalformedIndexExpr(..) )); @@ -272,38 +272,32 @@ fn test_map_json() -> Result<(), Box> { engine.parse_json(json, true)?; assert!(matches!( - *engine.parse_json("123", true).expect_err("should error"), + *engine.parse_json("123", true).unwrap_err(), EvalAltResult::ErrorMismatchOutputType(..) )); assert!(matches!( - *engine.parse_json("{a:42}", true).expect_err("should error"), + *engine.parse_json("{a:42}", true).unwrap_err(), EvalAltResult::ErrorParsing(..) )); assert!(matches!( - *engine - .parse_json("#{a:123}", true) - .expect_err("should error"), + *engine.parse_json("#{a:123}", true).unwrap_err(), EvalAltResult::ErrorParsing(..) )); assert!(matches!( - *engine.parse_json("{a:()}", true).expect_err("should error"), + *engine.parse_json("{a:()}", true).unwrap_err(), EvalAltResult::ErrorParsing(..) )); assert!(matches!( - *engine - .parse_json("#{a:123+456}", true) - .expect_err("should error"), + *engine.parse_json("#{a:123+456}", true).unwrap_err(), EvalAltResult::ErrorParsing(..) )); assert!(matches!( - *engine - .parse_json("{a:`hello${world}`}", true) - .expect_err("should error"), + *engine.parse_json("{a:`hello${world}`}", true).unwrap_err(), EvalAltResult::ErrorParsing(..) )); diff --git a/tests/method_call.rs b/tests/method_call.rs index 577447e7..75c19792 100644 --- a/tests/method_call.rs +++ b/tests/method_call.rs @@ -127,7 +127,7 @@ fn test_method_call_typed() -> Result<(), Box> { foo(1000); "# ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("foo") )); @@ -142,7 +142,7 @@ fn test_method_call_typed() -> Result<(), Box> { x.foo(1000); "# ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("foo") )); diff --git a/tests/mismatched_op.rs b/tests/mismatched_op.rs index 85d31857..dd094a45 100644 --- a/tests/mismatched_op.rs +++ b/tests/mismatched_op.rs @@ -36,21 +36,21 @@ fn test_mismatched_op_custom_type() -> Result<(), Box> { let x = new_ts(); let y = new_ts(); x == y - ").expect_err("should error"), + ").unwrap_err(), EvalAltResult::ErrorFunctionNotFound(f, ..) if f == "== (TestStruct, TestStruct)")); assert!( - matches!(*engine.eval::("new_ts() == 42").expect_err("should error"), + matches!(*engine.eval::("new_ts() == 42").unwrap_err(), EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("== (TestStruct, ")) ); assert!(matches!( - *engine.eval::("60 + new_ts()").expect_err("should error"), + *engine.eval::("60 + new_ts()").unwrap_err(), EvalAltResult::ErrorFunctionNotFound(f, ..) if f == format!("+ ({}, TestStruct)", std::any::type_name::()) )); assert!(matches!( - *engine.eval::("42").expect_err("should error"), + *engine.eval::("42").unwrap_err(), EvalAltResult::ErrorMismatchOutputType(need, actual, ..) if need == "TestStruct" && actual == std::any::type_name::() )); diff --git a/tests/modules.rs b/tests/modules.rs index 447917a3..f296d78b 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -227,7 +227,7 @@ fn test_module_resolver() -> Result<(), Box> { sum "# ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorTooManyModules(..) )); @@ -250,7 +250,7 @@ fn test_module_resolver() -> Result<(), Box> { sum "# ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorInFunctionCall(fn_name, ..) if fn_name == "foo" )); @@ -403,7 +403,7 @@ fn test_module_from_ast() -> Result<(), Box> { assert!(matches!( *engine .run(r#"import "testing" as ttt; ttt::hidden()"#) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorFunctionNotFound(fn_name, ..) if fn_name == "ttt::hidden ()" )); @@ -415,13 +415,13 @@ fn test_module_export() -> Result<(), Box> { let engine = Engine::new(); assert!(matches!( - engine.compile("let x = 10; { export x; }").expect_err("should error"), + engine.compile("let x = 10; { export x; }").unwrap_err(), ParseError(x, ..) if *x == ParseErrorType::WrongExport )); #[cfg(not(feature = "no_function"))] assert!(matches!( - engine.compile("fn abc(x) { export x; }").expect_err("should error"), + engine.compile("fn abc(x) { export x; }").unwrap_err(), ParseError(x, ..) if *x == ParseErrorType::WrongExport )); diff --git a/tests/operations.rs b/tests/operations.rs index c4eb37ab..b179e3e5 100644 --- a/tests/operations.rs +++ b/tests/operations.rs @@ -18,7 +18,7 @@ fn test_max_operations() -> Result<(), Box> { engine.run("let x = 0; while x < 20 { x += 1; }")?; assert!(matches!( - *engine.run("for x in 0..500 {}").expect_err("should error"), + *engine.run("for x in 0..500 {}").unwrap_err(), EvalAltResult::ErrorTooManyOperations(..) )); @@ -41,9 +41,7 @@ fn test_max_operations_literal() -> Result<(), Box> { #[cfg(not(feature = "no_index"))] assert!(matches!( - *engine - .run("[1, 2, 3, 4, 5, 6, 7, 8, 9]") - .expect_err("should error"), + *engine.run("[1, 2, 3, 4, 5, 6, 7, 8, 9]").unwrap_err(), EvalAltResult::ErrorTooManyOperations(..) )); @@ -54,7 +52,7 @@ fn test_max_operations_literal() -> Result<(), Box> { assert!(matches!( *engine .run("#{a:1, b:2, c:3, d:4, e:5, f:6, g:7, h:8, i:9}") - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorTooManyOperations(..) )); @@ -110,7 +108,7 @@ fn test_max_operations_functions() -> Result<(), Box> { } "#, ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorTooManyOperations(..) )); @@ -137,7 +135,7 @@ fn test_max_operations_eval() -> Result<(), Box> { eval(script); "# ) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorInFunctionCall(.., err, _) if matches!(*err, EvalAltResult::ErrorTooManyOperations(..)) )); @@ -162,7 +160,7 @@ fn test_max_operations_progress() -> Result<(), Box> { assert!(matches!( *engine .run("for x in 0..500 {}") - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorTerminated(x, ..) if x.as_int()? == 42 )); diff --git a/tests/ops.rs b/tests/ops.rs index ce6c5611..7c8e0077 100644 --- a/tests/ops.rs +++ b/tests/ops.rs @@ -26,17 +26,17 @@ fn test_ops_other_number_types() -> Result<(), Box> { scope.push("x", 42_u16); assert!(matches!( - *engine.eval_with_scope::(&mut scope, "x == 42").expect_err("should error"), + *engine.eval_with_scope::(&mut scope, "x == 42").unwrap_err(), EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("== (u16,") )); #[cfg(not(feature = "no_float"))] assert!(matches!( - *engine.eval_with_scope::(&mut scope, "x == 42.0").expect_err("should error"), + *engine.eval_with_scope::(&mut scope, "x == 42.0").unwrap_err(), EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("== (u16,") )); assert!( - matches!(*engine.eval_with_scope::(&mut scope, r#"x == "hello""#).expect_err("should error"), + matches!(*engine.eval_with_scope::(&mut scope, r#"x == "hello""#).unwrap_err(), EvalAltResult::ErrorFunctionNotFound(f, ..) if f.starts_with("== (u16,") ) ); diff --git a/tests/plugins.rs b/tests/plugins.rs index ab752bc3..7b1214cb 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -118,7 +118,7 @@ fn test_plugins_package() -> Result<(), Box> { engine.run("const A = [1, 2, 3]; A.no_effect = 42;")?; assert!( - matches!(*engine.run("const A = [1, 2, 3]; A.test(42);").expect_err("should error"), + matches!(*engine.run("const A = [1, 2, 3]; A.test(42);").unwrap_err(), EvalAltResult::ErrorNonPureMethodCallOnConstant(x, ..) if x == "test") ) } diff --git a/tests/stack.rs b/tests/stack.rs index a63a5dbd..49e28224 100644 --- a/tests/stack.rs +++ b/tests/stack.rs @@ -28,7 +28,7 @@ fn test_stack_overflow_fn_calls() -> Result<(), Box> { ", max + 1 )) - .expect_err("should error"), + .unwrap_err(), EvalAltResult::ErrorStackOverflow(..) )); @@ -44,7 +44,7 @@ fn test_stack_overflow_parsing() -> Result<(), Box> { " let a = (1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+1)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) " - ).expect_err("should error").0, + ).unwrap_err().0, ParseErrorType::ExprTooDeep ); @@ -71,7 +71,7 @@ fn test_stack_overflow_parsing() -> Result<(), Box> { 1 + 2 " ) - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::ExprTooDeep ); @@ -113,7 +113,7 @@ fn test_stack_overflow_parsing() -> Result<(), Box> { 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 " ) - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::ExprTooDeep ); diff --git a/tests/string.rs b/tests/string.rs index 37988600..6b150b96 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -116,7 +116,7 @@ fn test_string_mut() -> Result<(), Box> { assert_eq!(engine.eval::(r#"foo("hello")"#)?, 5); assert_eq!(engine.eval::(r#"bar("hello")"#)?, 5); assert!( - matches!(*engine.eval::(r#"baz("hello")"#).expect_err("should error"), + matches!(*engine.eval::(r#"baz("hello")"#).unwrap_err(), EvalAltResult::ErrorFunctionNotFound(f, ..) if f == "baz (&str | ImmutableString | String)" ) ); diff --git a/tests/switch.rs b/tests/switch.rs index f37dacc0..67569b5d 100644 --- a/tests/switch.rs +++ b/tests/switch.rs @@ -110,7 +110,7 @@ fn test_switch_errors() -> Result<(), Box> { assert!(matches!( engine .compile("switch x { _ => 123, 1 => 42 }") - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::WrongSwitchDefaultCase )); @@ -174,7 +174,7 @@ fn test_switch_condition() -> Result<(), Box> { assert!(matches!( engine .compile("switch x { 1 => 123, _ if true => 42 }") - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::WrongSwitchCaseCondition )); @@ -269,14 +269,14 @@ fn test_switch_ranges() -> Result<(), Box> { assert!(matches!( engine.compile( "switch x { 10..20 => (), 20..=42 => 'a', 25..45 => 'z', 42 => 'x', 30..100 => true }" - ).expect_err("should error").err_type(), + ).unwrap_err().err_type(), ParseErrorType::WrongSwitchIntegerCase )); #[cfg(not(feature = "no_float"))] assert!(matches!( engine.compile( "switch x { 10..20 => (), 20..=42 => 'a', 25..45 => 'z', 42.0 => 'x', 30..100 => true }" - ).expect_err("should error").err_type(), + ).unwrap_err().err_type(), ParseErrorType::WrongSwitchIntegerCase )); assert_eq!( diff --git a/tests/tokens.rs b/tests/tokens.rs index f6fac1aa..9a7b7d89 100644 --- a/tests/tokens.rs +++ b/tests/tokens.rs @@ -9,7 +9,7 @@ fn test_tokens_disabled() { assert!(matches!( engine .compile("let x = if true { 42 } else { 0 };") - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::Reserved(err) if err == "if" )); @@ -19,13 +19,13 @@ fn test_tokens_disabled() { assert_eq!( *engine .compile("let x = 40 + 2; x += 1;") - .expect_err("should error") + .unwrap_err() .err_type(), ParseErrorType::UnknownOperator("+=".to_string()) ); assert!(matches!( - engine.compile("let x = += 0;").expect_err("should error").err_type(), + engine.compile("let x = += 0;").unwrap_err().err_type(), ParseErrorType::Reserved(err) if err == "+=" )); } diff --git a/tests/unit.rs b/tests/unit.rs index d2abd3c2..04c078bd 100644 --- a/tests/unit.rs +++ b/tests/unit.rs @@ -17,6 +17,6 @@ fn test_unit_eq() -> Result<(), Box> { #[test] fn test_unit_with_spaces() -> Result<(), Box> { let engine = Engine::new(); - let _ = engine.run("let x = ( ); x").expect_err("should error"); + let _ = engine.run("let x = ( ); x").unwrap_err(); Ok(()) } diff --git a/tests/var_scope.rs b/tests/var_scope.rs index 32db7db7..5a3120ba 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -209,7 +209,7 @@ fn test_var_resolver() -> Result<(), Box> { assert_eq!(engine.eval_with_scope::(&mut scope, "chameleon")?, 1); assert!( - matches!(*engine.eval_with_scope::(&mut scope, "DO_NOT_USE").expect_err("should error"), + matches!(*engine.eval_with_scope::(&mut scope, "DO_NOT_USE").unwrap_err(), EvalAltResult::ErrorVariableNotFound(n, ..) if n == "DO_NOT_USE") ); @@ -235,7 +235,7 @@ fn test_var_def_filter() -> Result<(), Box> { ); assert!(matches!( - engine.compile("let x = 42;").expect_err("should error").err_type(), + engine.compile("let x = 42;").unwrap_err().err_type(), ParseErrorType::ForbiddenVariable(s) if s == "x" )); assert!(matches!( From 407d376a61193e6d95b5859195331d201e5b498c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 11 Apr 2023 10:26:23 +0800 Subject: [PATCH 04/10] Encode pure in CallableFunction variant. --- src/api/register.rs | 10 ++++----- src/func/call.rs | 30 ++++++++++++++------------- src/func/callable_function.rs | 9 ++++++-- src/func/register.rs | 39 ++++++++--------------------------- src/module/mod.rs | 19 +++++++++-------- 5 files changed, 47 insertions(+), 60 deletions(-) diff --git a/src/api/register.rs b/src/api/register.rs index 8f4d4833..285f959a 100644 --- a/src/api/register.rs +++ b/src/api/register.rs @@ -90,15 +90,15 @@ impl Engine { let param_type_names: Option<&[&str]> = None; let fn_name = name.as_ref(); - let no_const = false; + let is_pure = true; #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - let no_const = no_const || (F::num_params() == 3 && fn_name == crate::engine::FN_IDX_SET); + let is_pure = is_pure && (F::num_params() != 3 || fn_name != crate::engine::FN_IDX_SET); #[cfg(not(feature = "no_object"))] - let no_const = - no_const || (F::num_params() == 2 && fn_name.starts_with(crate::engine::FN_SET)); + let is_pure = + is_pure && (F::num_params() != 2 || !fn_name.starts_with(crate::engine::FN_SET)); - let func = func.into_callable_function(fn_name.into(), no_const); + let func = func.into_callable_function(fn_name.into(), is_pure); self.global_namespace_mut().set_fn( name, diff --git a/src/func/call.rs b/src/func/call.rs index dac38df3..6c8413d0 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -276,6 +276,7 @@ impl Engine { func: CallableFunction::Method { func: Shared::new(f), has_context, + is_pure: false, }, source: None, }) @@ -285,6 +286,7 @@ impl Engine { func: CallableFunction::Method { func: Shared::new(f), has_context, + is_pure: true, }, source: None, }), @@ -372,8 +374,8 @@ impl Engine { let backup = &mut ArgBackup::new(); - // Calling pure function but the first argument is a reference? - let swap = is_ref_mut && func.is_pure() && !args.is_empty(); + // Calling non-method function but the first argument is a reference? + let swap = is_ref_mut && !func.is_method() && !args.is_empty(); if swap { // Clone the first argument @@ -400,12 +402,11 @@ impl Engine { .has_context() .then(|| (self, name, src, &*global, pos).into()); - let mut _result = if let Some(f) = func.get_plugin_fn() { - if !f.is_pure() && !args.is_empty() && args[0].is_read_only() { - Err(ERR::ErrorNonPureMethodCallOnConstant(name.to_string(), pos).into()) - } else { - f.call(context, args) - } + let mut _result = if !func.is_pure() && !args.is_empty() && args[0].is_read_only() { + // If function is not pure, there must be at least one argument + Err(ERR::ErrorNonPureMethodCallOnConstant(name.to_string(), pos).into()) + } else if let Some(f) = func.get_plugin_fn() { + f.call(context, args) } else if let Some(f) = func.get_native_fn() { f(context, args) } else { @@ -1493,17 +1494,18 @@ impl Engine { self.call_script_fn(global, caches, scope, None, environ, f, args, true, pos) } + Some(f) if !f.is_pure() && args[0].is_read_only() => { + // If function is not pure, there must be at least one argument + Err(ERR::ErrorNonPureMethodCallOnConstant(fn_name.to_string(), pos).into()) + } + Some(f) if f.is_plugin_fn() => { let f = f.get_plugin_fn().expect("plugin function"); let context = f .has_context() .then(|| (self, fn_name, module.id(), &*global, pos).into()); - if !f.is_pure() && !args.is_empty() && args[0].is_read_only() { - Err(ERR::ErrorNonPureMethodCallOnConstant(fn_name.to_string(), pos).into()) - } else { - f.call(context, args) - .and_then(|r| self.check_data_size(r, pos)) - } + f.call(context, args) + .and_then(|r| self.check_data_size(r, pos)) } Some(f) if f.is_native() => { diff --git a/src/func/callable_function.rs b/src/func/callable_function.rs index 21a7ae4d..3b66d9a1 100644 --- a/src/func/callable_function.rs +++ b/src/func/callable_function.rs @@ -39,6 +39,8 @@ pub enum CallableFunction { func: Shared, /// Does the function take a [`NativeCallContext`][crate::NativeCallContext] parameter? has_context: bool, + /// This is a dummy field and is not used. + is_pure: bool, }, /// A native Rust object method with the first argument passed by reference, /// and the rest passed by value. @@ -47,6 +49,8 @@ pub enum CallableFunction { func: Shared, /// Does the function take a [`NativeCallContext`][crate::NativeCallContext] parameter? has_context: bool, + /// Allow operating on constants? + is_pure: bool, }, /// An iterator function. Iterator { @@ -105,9 +109,10 @@ impl CallableFunction { pub fn is_pure(&self) -> bool { match self { Self::Pure { .. } => true, - Self::Method { .. } | Self::Iterator { .. } => false, + Self::Method { is_pure, .. } => *is_pure, + Self::Iterator { .. } => true, - Self::Plugin { func, .. } => !func.is_method_call(), + Self::Plugin { func, .. } => func.is_pure(), #[cfg(not(feature = "no_function"))] Self::Script { .. } => false, diff --git a/src/func/register.rs b/src/func/register.rs index de5d835c..ea9ef6d5 100644 --- a/src/func/register.rs +++ b/src/func/register.rs @@ -84,7 +84,7 @@ pub trait RegisterNativeFunction< { /// Convert this function into a [`CallableFunction`]. #[must_use] - fn into_callable_function(self, name: Identifier, no_const: bool) -> CallableFunction; + fn into_callable_function(self, name: Identifier, is_pure: bool) -> CallableFunction; /// Get the type ID's of this function's parameters. #[must_use] fn param_types() -> [TypeId; N]; @@ -127,19 +127,6 @@ pub trait RegisterNativeFunction< } } -macro_rules! check_constant { - ($abi:ident, $n:expr, $fn_name:ident, $no_const:ident, $args:ident) => { - #[cfg(any(not(feature = "no_object"), not(feature = "no_index")))] - if stringify!($abi) == "Method" && $no_const && $args[0].is_read_only() { - return Err(crate::ERR::ErrorNonPureMethodCallOnConstant( - $fn_name.to_string(), - crate::Position::NONE, - ) - .into()); - } - }; -} - macro_rules! def_register { () => { def_register!(imp Pure : 0;); @@ -160,11 +147,9 @@ macro_rules! def_register { > RegisterNativeFunction<($($mark,)*), $n, false, RET, false> for FN { #[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] } - #[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction { + #[inline(always)] fn into_callable_function(self, fn_name: Identifier, is_pure: bool) -> CallableFunction { CallableFunction::$abi { func: Shared::new(move |_, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! - check_constant!($abi, $n, fn_name, no_const, args); - let mut drain = args.iter_mut(); $(let mut $par = $clone(drain.next().unwrap()); )* @@ -173,7 +158,7 @@ macro_rules! def_register { // Map the result Ok(Dynamic::from(r)) - }), has_context: false } + }), has_context: false, is_pure } } } @@ -184,13 +169,11 @@ macro_rules! def_register { > RegisterNativeFunction<($($mark,)*), $n, true, RET, false> for FN { #[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] } - #[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction { + #[inline(always)] fn into_callable_function(self, fn_name: Identifier, is_pure: bool) -> CallableFunction { CallableFunction::$abi { func: Shared::new(move |ctx: Option, args: &mut FnCallArgs| { let ctx = ctx.unwrap(); // The arguments are assumed to be of the correct number and types! - check_constant!($abi, $n, fn_name, no_const, args); - let mut drain = args.iter_mut(); $(let mut $par = $clone(drain.next().unwrap()); )* @@ -199,7 +182,7 @@ macro_rules! def_register { // Map the result Ok(Dynamic::from(r)) - }), has_context: true } + }), has_context: true, is_pure } } } @@ -211,17 +194,15 @@ macro_rules! def_register { #[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { type_name::>() } - #[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction { + #[inline(always)] fn into_callable_function(self, fn_name: Identifier, is_pure: bool) -> CallableFunction { CallableFunction::$abi { func: Shared::new(move |_, args: &mut FnCallArgs| { // The arguments are assumed to be of the correct number and types! - check_constant!($abi, $n, fn_name, no_const, args); - let mut drain = args.iter_mut(); $(let mut $par = $clone(drain.next().unwrap()); )* // Call the function with each argument value self($($arg),*).map(Dynamic::from) - }), has_context: false } + }), has_context: false, is_pure } } } @@ -233,19 +214,17 @@ macro_rules! def_register { #[inline(always)] fn param_types() -> [TypeId;$n] { [$(TypeId::of::<$par>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn param_names() -> [&'static str;$n] { [$(type_name::<$param>()),*] } #[cfg(feature = "metadata")] #[inline(always)] fn return_type_name() -> &'static str { type_name::>() } - #[inline(always)] fn into_callable_function(self, fn_name: Identifier, no_const: bool) -> CallableFunction { + #[inline(always)] fn into_callable_function(self, fn_name: Identifier, is_pure: bool) -> CallableFunction { CallableFunction::$abi { func: Shared::new(move |ctx: Option, args: &mut FnCallArgs| { let ctx = ctx.unwrap(); // The arguments are assumed to be of the correct number and types! - check_constant!($abi, $n, fn_name, no_const, args); - let mut drain = args.iter_mut(); $(let mut $par = $clone(drain.next().unwrap()); )* // Call the function with each argument value self(ctx, $($arg),*).map(Dynamic::from) - }), has_context: true } + }), has_context: true, is_pure } } } diff --git a/src/module/mod.rs b/src/module/mod.rs index 6e7fe262..eeb6874e 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1266,6 +1266,7 @@ impl Module { CallableFunction::Method { func: Shared::new(f), has_context: true, + is_pure: false, }, ) } @@ -1303,15 +1304,15 @@ impl Module { F: RegisterNativeFunction, { let fn_name = name.into(); - let no_const = false; + let is_pure = true; #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] - let no_const = no_const || (F::num_params() == 3 && fn_name == crate::engine::FN_IDX_SET); + let is_pure = is_pure && (F::num_params() != 3 || fn_name != crate::engine::FN_IDX_SET); #[cfg(not(feature = "no_object"))] - let no_const = - no_const || (F::num_params() == 2 && fn_name.starts_with(crate::engine::FN_SET)); + let is_pure = + is_pure && (F::num_params() != 2 || !fn_name.starts_with(crate::engine::FN_SET)); - let func = func.into_callable_function(fn_name.clone(), no_const); + let func = func.into_callable_function(fn_name.clone(), is_pure); self.set_fn( fn_name, @@ -1350,7 +1351,7 @@ impl Module { F: RegisterNativeFunction<(Mut,), 1, C, T, true> + SendSync + 'static, { let fn_name = crate::engine::make_getter(name.as_ref()); - let func = func.into_callable_function(fn_name.clone(), false); + let func = func.into_callable_function(fn_name.clone(), true); self.set_fn( fn_name, @@ -1394,7 +1395,7 @@ impl Module { F: RegisterNativeFunction<(Mut, T), 2, C, (), true> + SendSync + 'static, { let fn_name = crate::engine::make_setter(name.as_ref()); - let func = func.into_callable_function(fn_name.clone(), true); + let func = func.into_callable_function(fn_name.clone(), false); self.set_fn( fn_name, @@ -1510,7 +1511,7 @@ impl Module { FnAccess::Public, None, F::param_types(), - func.into_callable_function(crate::engine::FN_IDX_GET.into(), false), + func.into_callable_function(crate::engine::FN_IDX_GET.into(), true), ) } @@ -1571,7 +1572,7 @@ impl Module { FnAccess::Public, None, F::param_types(), - func.into_callable_function(crate::engine::FN_IDX_SET.into(), true), + func.into_callable_function(crate::engine::FN_IDX_SET.into(), false), ) } From dd0d1dd7ca4bd460627f2ca795c23a55f4c33459 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 11 Apr 2023 10:35:24 +0800 Subject: [PATCH 05/10] Fix test. --- tests/optimizer.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/optimizer.rs b/tests/optimizer.rs index 6e2c36d3..4d69d6ea 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -212,6 +212,7 @@ fn test_optimizer_full() -> Result<(), Box> { let ast = engine.compile(SCRIPT)?; + #[cfg(feature = "internals")] assert_eq!(ast.statements().len(), 2); assert_eq!(engine.eval_ast_with_scope::(&mut scope, &ast)?, 42); From 0206f776db21b20d4f04e10c4450edc46c772065 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 11 Apr 2023 11:38:48 +0800 Subject: [PATCH 06/10] Add is_symbol_disabled and is_custom_keyword. --- CHANGELOG.md | 5 +++ src/api/custom_syntax.rs | 25 +++------------ src/api/definitions/mod.rs | 7 +--- src/api/mod.rs | 66 +++++++++++++++++++++++++------------- src/eval/stmt.rs | 20 ++++++------ src/func/call.rs | 37 ++++++++++++++------- src/parser.rs | 8 +---- src/tokenizer.rs | 12 +++---- 8 files changed, 95 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7627368d..7ee71de3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,11 @@ New features * It is now possible to require a specific _type_ to the `this` pointer for a particular script-defined function so that it is called only when the `this` pointer contains the specified type. * `is_def_fn` is extended to support checking for typed methods, with syntax `is_def_fn(this_type, fn_name, arity)` +Enhancements +------------ + +* `Engine::is_symbol_disabled` is added to test whether a particular keyword/symbol is disabled. + Version 1.13.0 ============== diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index e5dfb56c..762b5dcc 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -259,15 +259,9 @@ impl Engine { // Keyword/symbol not in first position _ if !segments.is_empty() && token.is_some() => { // Make it a custom keyword/symbol if it is disabled or reserved - if (self - .disabled_symbols - .as_ref() - .map_or(false, |m| m.contains(s)) + if (self.is_symbol_disabled(s) || token.as_ref().map_or(false, Token::is_reserved)) - && !self - .custom_keywords - .as_ref() - .map_or(false, |m| m.contains_key(s)) + && !self.is_custom_keyword(s) { self.custom_keywords .get_or_insert_with(Default::default) @@ -279,10 +273,7 @@ impl Engine { // Standard keyword in first position but not disabled _ if segments.is_empty() && token.as_ref().map_or(false, Token::is_standard_keyword) - && !self - .disabled_symbols - .as_ref() - .map_or(false, |m| m.contains(s)) => + && !self.is_symbol_disabled(s) => { return Err(LexError::ImproperSymbol( s.to_string(), @@ -299,15 +290,9 @@ impl Engine { && (is_valid_identifier(s) || is_reserved_keyword_or_symbol(s).0) => { // Make it a custom keyword/symbol if it is disabled or reserved - if self - .disabled_symbols - .as_ref() - .map_or(false, |m| m.contains(s)) + if self.is_symbol_disabled(s) || (token.as_ref().map_or(false, Token::is_reserved) - && !self - .custom_keywords - .as_ref() - .map_or(false, |m| m.contains_key(s))) + && !self.is_custom_keyword(s)) { self.custom_keywords .get_or_insert_with(Default::default) diff --git a/src/api/definitions/mod.rs b/src/api/definitions/mod.rs index 69224b27..f4fae1d6 100644 --- a/src/api/definitions/mod.rs +++ b/src/api/definitions/mod.rs @@ -457,12 +457,7 @@ impl Module { !f.metadata.name.contains('$') && !is_valid_function_name(&f.metadata.name); #[cfg(not(feature = "no_custom_syntax"))] - let operator = operator - || def - .engine - .custom_keywords - .as_ref() - .map_or(false, |m| m.contains_key(f.metadata.name.as_str())); + let operator = operator || def.engine.is_custom_keyword(f.metadata.name.as_str()); f.write_definition(writer, def, operator)?; } diff --git a/src/api/mod.rs b/src/api/mod.rs index 21238f5a..44df4213 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -109,7 +109,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` - #[inline(always)] + #[inline] pub fn disable_symbol(&mut self, symbol: impl Into) -> &mut Self { self.disabled_symbols .get_or_insert_with(Default::default) @@ -117,6 +117,27 @@ impl Engine { self } + /// Is a particular keyword or operator disabled? + /// + /// # Examples + /// + /// ```rust + /// # fn main() -> Result<(), rhai::ParseError> { + /// use rhai::Engine; + /// + /// let mut engine = Engine::new(); + /// + /// engine.disable_symbol("if"); // disable the 'if' keyword + /// + /// assert!(engine.is_symbol_disabled("if")); + /// ``` + #[inline] + pub fn is_symbol_disabled(&self, symbol: &str) -> bool { + self.disabled_symbols + .as_ref() + .map_or(false, |m| m.contains(symbol)) + } + /// Register a custom operator with a precedence into the language. /// /// Not available under `no_custom_syntax`. @@ -168,32 +189,21 @@ impl Engine { Some(Token::Custom(..)) => (), // Active standard keywords cannot be made custom // Disabled keywords are OK - Some(token) if token.is_standard_keyword() => { - if !self - .disabled_symbols - .as_ref() - .map_or(false, |m| m.contains(token.literal_syntax())) - { - return Err(format!("'{keyword}' is a reserved keyword")); - } - } - // Active standard symbols cannot be made custom - Some(token) if token.is_standard_symbol() => { - if !self - .disabled_symbols - .as_ref() - .map_or(false, |m| m.contains(token.literal_syntax())) - { - return Err(format!("'{keyword}' is a reserved operator")); - } + Some(token) + if token.is_standard_keyword() + && !self.is_symbol_disabled(token.literal_syntax()) => + { + return Err(format!("'{keyword}' is a reserved keyword")) } // Active standard symbols cannot be made custom Some(token) - if !self - .disabled_symbols - .as_ref() - .map_or(false, |m| m.contains(token.literal_syntax())) => + if token.is_standard_symbol() + && !self.is_symbol_disabled(token.literal_syntax()) => { + return Err(format!("'{keyword}' is a reserved operator")) + } + // Active standard symbols cannot be made custom + Some(token) if !self.is_symbol_disabled(token.literal_syntax()) => { return Err(format!("'{keyword}' is a reserved symbol")) } // Disabled symbols are OK @@ -207,6 +217,16 @@ impl Engine { Ok(self) } + /// Is a keyword registered as a custom keyword? + /// + /// Not available under `no_custom_syntax`. + #[cfg(not(feature = "no_custom_syntax"))] + #[inline] + pub(crate) fn is_custom_keyword(&self, keyword: &str) -> bool { + self.custom_keywords + .as_ref() + .map_or(false, |m| m.contains_key(keyword)) + } /// Get the default value of the custom state for each evaluation run. #[inline(always)] diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 29ded4a1..9c88f1cf 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -928,20 +928,18 @@ impl Engine { #[cfg(not(feature = "no_closure"))] Stmt::Share(x) => { for (var, index) in &**x { - if let Some(index) = index + let index = index .map(|n| scope.len() - n.get()) .or_else(|| scope.search(&var.name)) - { - let val = scope.get_mut_by_index(index); + .ok_or_else(|| { + Box::new(ERR::ErrorVariableNotFound(var.name.to_string(), var.pos)) + })?; - if !val.is_shared() { - // Replace the variable with a shared value. - *val = std::mem::take(val).into_shared(); - } - } else { - return Err( - ERR::ErrorVariableNotFound(var.name.to_string(), var.pos).into() - ); + let val = scope.get_mut_by_index(index); + + if !val.is_shared() { + // Replace the variable with a shared value. + *val = std::mem::take(val).into_shared(); } } diff --git a/src/func/call.rs b/src/func/call.rs index 6c8413d0..2cd36208 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -604,8 +604,11 @@ impl Engine { }; if error { - let sig = self.gen_fn_call_signature(fn_name, args); - return Err(ERR::ErrorFunctionNotFound(sig, pos).into()); + return Err(ERR::ErrorFunctionNotFound( + self.gen_fn_call_signature(fn_name, args), + pos, + ) + .into()); } } @@ -815,12 +818,16 @@ impl Engine { // Handle obj.call(fn_ptr, ...) KEYWORD_FN_PTR_CALL => { if call_args.is_empty() { - let typ = self.map_type_name(target.type_name()); - return Err(self.make_type_mismatch_err::(typ, fn_call_pos)); + return Err(self.make_type_mismatch_err::( + self.map_type_name(target.type_name()), + fn_call_pos, + )); } if !call_args[0].is_fnptr() { - let typ = self.map_type_name(call_args[0].type_name()); - return Err(self.make_type_mismatch_err::(typ, first_arg_pos)); + return Err(self.make_type_mismatch_err::( + self.map_type_name(call_args[0].type_name()), + first_arg_pos, + )); } // FnPtr call on object @@ -904,8 +911,10 @@ impl Engine { } KEYWORD_FN_PTR_CURRY => { if !target.is_fnptr() { - let typ = self.map_type_name(target.type_name()); - return Err(self.make_type_mismatch_err::(typ, fn_call_pos)); + return Err(self.make_type_mismatch_err::( + self.map_type_name(target.type_name()), + fn_call_pos, + )); } let mut fn_ptr = target.read_lock::().expect("`FnPtr`").clone(); @@ -1036,8 +1045,10 @@ impl Engine { self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), arg)?; if !arg_value.is_fnptr() { - let typ = self.map_type_name(arg_value.type_name()); - return Err(self.make_type_mismatch_err::(typ, arg_pos)); + return Err(self.make_type_mismatch_err::( + self.map_type_name(arg_value.type_name()), + arg_pos, + )); } let fn_ptr = arg_value.cast::(); @@ -1122,8 +1133,10 @@ impl Engine { self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), first)?; if !arg_value.is_fnptr() { - let typ = self.map_type_name(arg_value.type_name()); - return Err(self.make_type_mismatch_err::(typ, arg_pos)); + return Err(self.make_type_mismatch_err::( + self.map_type_name(arg_value.type_name()), + arg_pos, + )); } let mut fn_ptr = arg_value.cast::(); diff --git a/src/parser.rs b/src/parser.rs index 00fd7240..a5ba7f1d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2431,13 +2431,7 @@ impl Engine { } #[cfg(not(feature = "no_custom_syntax"))] - Token::Custom(s) - if self - .custom_keywords - .as_ref() - .and_then(|m| m.get(s.as_str())) - .map_or(false, Option::is_some) => - { + Token::Custom(s) if self.is_custom_keyword(s.as_str()) => { op_base.hashes = if native_only { FnCallHashes::from_native_only(calc_fn_hash(None, &s, 2)) } else { diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 2e0e009c..0d855cbf 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -2492,7 +2492,7 @@ impl<'a> Iterator for TokenIterator<'a> { Some((Token::Reserved(s), pos)) => (match (s.as_str(), #[cfg(not(feature = "no_custom_syntax"))] - self.engine.custom_keywords.as_ref().map_or(false, |m| m.contains_key(&*s)), + self.engine.is_custom_keyword(&*s), #[cfg(feature = "no_custom_syntax")] false ) @@ -2529,7 +2529,7 @@ impl<'a> Iterator for TokenIterator<'a> { #[cfg(feature = "no_custom_syntax")] (.., true) => unreachable!("no custom operators"), // Reserved keyword that is not custom and disabled. - (token, false) if self.engine.disabled_symbols.as_ref().map_or(false,|m| m.contains(token)) => { + (token, false) if self.engine.is_symbol_disabled(token) => { let msg = format!("reserved {} '{token}' is disabled", if is_valid_identifier(token) { "keyword"} else {"symbol"}); Token::LexError(LERR::ImproperSymbol(s.to_string(), msg).into()) }, @@ -2538,13 +2538,13 @@ impl<'a> Iterator for TokenIterator<'a> { }, pos), // Custom keyword #[cfg(not(feature = "no_custom_syntax"))] - Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.as_ref().map_or(false,|m| m.contains_key(&*s)) => { + Some((Token::Identifier(s), pos)) if self.engine.is_custom_keyword(&*s) => { (Token::Custom(s), pos) } // Custom keyword/symbol - must be disabled #[cfg(not(feature = "no_custom_syntax"))] - Some((token, pos)) if token.is_literal() && self.engine.custom_keywords.as_ref().map_or(false,|m| m.contains_key(token.literal_syntax())) => { - if self.engine.disabled_symbols.as_ref().map_or(false,|m| m.contains(token.literal_syntax())) { + Some((token, pos)) if token.is_literal() && self.engine.is_custom_keyword(token.literal_syntax()) => { + if self.engine.is_symbol_disabled(token.literal_syntax()) { // Disabled standard keyword/symbol (Token::Custom(Box::new(token.literal_syntax().into())), pos) } else { @@ -2553,7 +2553,7 @@ impl<'a> Iterator for TokenIterator<'a> { } } // Disabled symbol - Some((token, pos)) if token.is_literal() && self.engine.disabled_symbols.as_ref().map_or(false,|m| m.contains(token.literal_syntax())) => { + Some((token, pos)) if token.is_literal() && self.engine.is_symbol_disabled(token.literal_syntax()) => { (Token::Reserved(Box::new(token.literal_syntax().into())), pos) } // Normal symbol From 094eb5878ad3bfc02bc1da20366de93c9ea865eb Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 12 Apr 2023 12:09:14 +0800 Subject: [PATCH 07/10] Add info into README's. --- src/README.md | 26 ++++++++++++++------------ tools/README.md | 10 ++++++---- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/README.md b/src/README.md index fc1d7bbf..e02fd0b2 100644 --- a/src/README.md +++ b/src/README.md @@ -11,21 +11,23 @@ Root Sources | `tokenizer.rs` | Script tokenizer/lexer | | `parser.rs` | Script parser | | `optimizer.rs` | Script optimizer | +| `defer.rs` | Utilities for deferred clean-up of resources | | `reify.rs` | Utilities for making generic types concrete | -| `tests.rs` | Unit tests (not integration tests, which are in the `rhai/tests` sub-directory) | +| `tests.rs` | Unit tests (not integration tests, which are in the main `tests` sub-directory) | Sub-Directories --------------- -| Sub-directory | Description | -| ------------- | ----------------------------------------------------- | -| `types` | Common data types (e.g. `Dynamic`, errors) | -| `api` | Public API for the scripting engine | -| `ast` | AST definition | -| `module` | Support for modules | -| `packages` | Pre-defined packages | -| `func` | Support for function calls | -| `eval` | Evaluation engine | -| `serde` | Support for [`serde`](https://crates.io/crates/serde) | -| `bin` | Pre-built CLI binaries (e.g. `rhai-run`, `rhai-repl`) | +| Sub-directory | Description | +| ------------- | ------------------------------------------------------------------ | +| `config` | Configuration | +| `types` | Common data types (e.g. `Dynamic`, errors) | +| `api` | Public API for the scripting engine | +| `ast` | AST definition | +| `module` | Support for modules | +| `packages` | Pre-defined packages | +| `func` | Registering and calling functions (native Rust and script-defined) | +| `eval` | AST evaluation | +| `serde` | Support for [`serde`](https://crates.io/crates/serde) and metadata | +| `bin` | Pre-built CLI binaries | diff --git a/tools/README.md b/tools/README.md index ff8fe844..9b96f6cc 100644 --- a/tools/README.md +++ b/tools/README.md @@ -1,7 +1,9 @@ Build Tools =========== -| File | Description | -| -------------- | ------------------------------------------- | -| `keywords.txt` | Input file for GNU gperf for the tokenizer. | -| `reserved.txt` | Input file for GNU gperf for the tokenizer. | +This directory contains input files for various build tools required for building Rhai. + +| File | Build tool | Description | +| -------------- | :---------: | ------------------------------------------------------------------ | +| `keywords.txt` | GNU `gperf` | Input file for the tokenizer – keywords recognition. | +| `reserved.txt` | GNU `gperf` | Input file for the tokenizer – reserved symbols recognition. | From 0699f47ff98112cb3129bb63b3bb67d778b3d1d0 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 19 Apr 2023 21:38:01 +0800 Subject: [PATCH 08/10] Fix Dynamic::from examples. --- src/types/dynamic.rs | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index 49cafcf5..acc691a0 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -1017,18 +1017,23 @@ impl Dynamic { } /// Create a [`Dynamic`] from any type. A [`Dynamic`] value is simply returned as is. /// - /// # Notes + /// # Arrays /// /// Beware that you need to pass in an [`Array`][crate::Array] type for it to be recognized as /// an [`Array`][crate::Array]. A [`Vec`][Vec] does not get automatically converted to an - /// [`Array`][crate::Array], but will be a custom type instead (stored as a trait object). Use - /// [`Dynamic::from_array`] to convert a [`Vec`][Vec] into a [`Dynamic`] as an - /// [`Array`][crate::Array] value. + /// [`Array`][crate::Array], but will be a custom type instead (stored as a trait object). + /// + /// Use `array.into()` or `array.into_iter()` to convert a [`Vec`][Vec] into a [`Dynamic`] as + /// an [`Array`][crate::Array] value. See the examples for details. + /// + /// # Hash Maps /// /// Similarly, passing in a [`HashMap`][std::collections::HashMap] or /// [`BTreeMap`][std::collections::BTreeMap] will not get a [`Map`][crate::Map] but a - /// custom type. Again, use [`Dynamic::from_map`] to get a [`Dynamic`] with a [`Map`][crate::Map] - /// value. + /// custom type. + /// + /// Again, use `map.into()` to get a [`Dynamic`] with a [`Map`][crate::Map] value. + /// See the examples for details. /// /// # Examples /// @@ -1046,6 +1051,33 @@ impl Dynamic { /// let new_result = Dynamic::from(result); /// assert_eq!(new_result.type_name(), "string"); /// assert_eq!(new_result.to_string(), "hello"); + /// + /// # #[cfg(not(feature = "no_index"))] + /// # { + /// // Arrays - this is a custom object! + /// let result = Dynamic::from(vec![1_i64, 2, 3]); + /// assert_eq!(result.type_name(), "alloc::vec::Vec"); + /// + /// // Use '.into()' to convert a Vec into an Array + /// let result: Dynamic = vec![1_i64, 2, 3].into(); + /// assert_eq!(result.type_name(), "array"); + /// # } + /// + /// # #[cfg(not(feature = "no_object"))] + /// # { + /// # use std::collections::HashMap; + /// // Hash map + /// let mut map = HashMap::new(); + /// map.insert("a".to_string(), 1_i64); + /// + /// // This is a custom object! + /// let result = Dynamic::from(map.clone()); + /// assert_eq!(result.type_name(), "std::collections::hash::map::HashMap"); + /// + /// // Use '.into()' to convert a HashMap into an object map + /// let result: Dynamic = map.into(); + /// assert_eq!(result.type_name(), "map"); + /// # } /// ``` #[inline] pub fn from(value: T) -> Self { From fb88b79178cecd37f6738bfaa454cd51447ec7b6 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 19 Apr 2023 23:17:54 +0800 Subject: [PATCH 09/10] Fix bug in parsing index chains. --- CHANGELOG.md | 1 + src/parser.rs | 10 ++++++++-- tests/arrays.rs | 28 ++++++++++++++++++++++------ 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ee71de3..eee62da7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Buf fixes * `is_shared` is a reserved keyword and is now handled properly (e.g. it cannot be the target of a function pointer). * Re-optimizing an AST via `optimize_ast` with constants now works correctly for closures. Previously the hidden `Share` nodes are not removed and causes variable-not-found errors during runtime if the constants are not available in the scope. +* Expressions such as `(v[0].func()).prop` now parse correctly. New features ------------ diff --git a/src/parser.rs b/src/parser.rs index a5ba7f1d..a34c851c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1728,6 +1728,9 @@ impl Engine { ) -> ParseResult { let mut settings = settings; + // Break just in case `lhs` is `Expr::Dot` or `Expr::Index` + let mut parent_options = ASTFlags::BREAK; + // Tail processing all possible postfix operators loop { let (tail_token, ..) = input.peek().expect(NEVER_ENDS); @@ -1842,13 +1845,16 @@ impl Engine { let rhs = self.parse_primary(input, state, lib, settings.level_up()?, options)?; - Self::make_dot_expr(state, expr, rhs, ASTFlags::empty(), op_flags, tail_pos)? + Self::make_dot_expr(state, expr, rhs, parent_options, op_flags, tail_pos)? } // Unknown postfix operator (expr, token) => { unreachable!("unknown postfix operator '{}' for {:?}", token, expr) } - } + }; + + // The chain is now extended + parent_options = ASTFlags::empty(); } // Cache the hash key for namespace-qualified variables diff --git a/tests/arrays.rs b/tests/arrays.rs index 23d0f98f..d8b24f4f 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -203,6 +203,22 @@ fn test_arrays() -> Result<(), Box> { Ok(()) } +#[cfg(not(feature = "no_float"))] +#[cfg(not(feature = "no_object"))] +#[test] +fn test_array_chaining() -> Result<(), Box> { + let engine = Engine::new(); + + assert!(engine.eval::( + " + let v = [ PI() ]; + ( v[0].cos() ).sin() == v[0].cos().sin() + " + )?); + + Ok(()) +} + #[test] fn test_array_index_types() -> Result<(), Box> { let engine = Engine::new(); @@ -508,9 +524,9 @@ fn test_arrays_map_reduce() -> Result<(), Box> { engine.eval::<()>( " - let x = [1, 2, 3, 2, 1]; - x.find(|v| v > 4) - ", + let x = [1, 2, 3, 2, 1]; + x.find(|v| v > 4) + ", )?; assert_eq!( @@ -525,9 +541,9 @@ fn test_arrays_map_reduce() -> Result<(), Box> { engine.eval::<()>( " - let x = [#{alice: 1}, #{bob: 2}, #{clara: 3}]; - x.find_map(|v| v.dave) - ", + let x = [#{alice: 1}, #{bob: 2}, #{clara: 3}]; + x.find_map(|v| v.dave) + ", )?; Ok(()) From 60ba27e2d65929e23ad0ebbf829f749873174ed5 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 19 Apr 2023 23:28:37 +0800 Subject: [PATCH 10/10] Fix doc test bug. --- src/api/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index 44df4213..bc6fa1a6 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -122,7 +122,6 @@ impl Engine { /// # Examples /// /// ```rust - /// # fn main() -> Result<(), rhai::ParseError> { /// use rhai::Engine; /// /// let mut engine = Engine::new();