From bae99462914e9168fd7706e229231c2b82be292a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 10 Mar 2020 09:30:12 +0800 Subject: [PATCH] Allow engine to retain functions across runs. --- examples/reuse_scope.rs | 4 +-- src/api.rs | 55 +++++++++++++++++++++------------- src/engine.rs | 5 ++++ src/error.rs | 2 +- src/result.rs | 65 ++++++++++++++++++++++++++++------------- src/scope.rs | 6 ++-- tests/var_scope.rs | 25 +++++++--------- 7 files changed, 101 insertions(+), 61 deletions(-) diff --git a/examples/reuse_scope.rs b/examples/reuse_scope.rs index a6509ffe..c83d0a72 100644 --- a/examples/reuse_scope.rs +++ b/examples/reuse_scope.rs @@ -4,9 +4,9 @@ fn main() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); let mut scope = Scope::new(); - engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?; + engine.eval_with_scope::<()>(&mut scope, false, "let x = 4 + 5")?; - let result = engine.eval_with_scope::(&mut scope, "x")?; + let result = engine.eval_with_scope::(&mut scope, false, "x")?; println!("result: {}", result); diff --git a/src/api.rs b/src/api.rs index 34c6d2ef..ca6fe88f 100644 --- a/src/api.rs +++ b/src/api.rs @@ -94,13 +94,13 @@ impl<'e> Engine<'e> { self.register_set(name, set_fn); } - /// Compile a string into an AST + /// Compile a string into an AST. pub fn compile(&self, input: &str) -> Result { let tokens = lex(input); parse(&mut tokens.peekable(), self.optimize) } - /// Compile a file into an AST + /// Compile a file into an AST. pub fn compile_file(&self, filename: &str) -> Result { use std::fs::File; use std::io::prelude::*; @@ -115,7 +115,7 @@ impl<'e> Engine<'e> { .and_then(|_| self.compile(&contents).map_err(EvalAltResult::ErrorParsing)) } - /// Evaluate a file + /// Evaluate a file. pub fn eval_file(&mut self, filename: &str) -> Result { use std::fs::File; use std::io::prelude::*; @@ -130,32 +130,40 @@ impl<'e> Engine<'e> { .and_then(|_| self.eval::(&contents)) } - /// Evaluate a string + /// Evaluate a string. pub fn eval(&mut self, input: &str) -> Result { let mut scope = Scope::new(); - self.eval_with_scope(&mut scope, input) + self.eval_with_scope(&mut scope, false, input) } - /// Evaluate a string with own scope + /// Evaluate a string with own scope. + /// + /// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ + /// and not cleared from run to run. pub fn eval_with_scope( &mut self, scope: &mut Scope, + retain_functions: bool, input: &str, ) -> Result { let ast = self.compile(input).map_err(EvalAltResult::ErrorParsing)?; - self.eval_ast_with_scope(scope, &ast) + self.eval_ast_with_scope(scope, retain_functions, &ast) } - /// Evaluate an AST + /// Evaluate an AST. pub fn eval_ast(&mut self, ast: &AST) -> Result { let mut scope = Scope::new(); - self.eval_ast_with_scope(&mut scope, ast) + self.eval_ast_with_scope(&mut scope, false, ast) } - /// Evaluate an AST with own scope + /// Evaluate an AST with own scope. + /// + /// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ + /// and not cleared from run to run. pub fn eval_ast_with_scope( &mut self, scope: &mut Scope, + retain_functions: bool, ast: &AST, ) -> Result { let AST(statements, functions) = ast; @@ -174,7 +182,9 @@ impl<'e> Engine<'e> { .iter() .try_fold(().into_dynamic(), |_, stmt| self.eval_stmt(scope, stmt)); - self.script_functions.clear(); // Clean up engine + if !retain_functions { + self.clear_functions(); + } match result { Err(EvalAltResult::Return(out, pos)) => out.downcast::().map(|v| *v).map_err(|a| { @@ -196,8 +206,7 @@ impl<'e> Engine<'e> { } /// Evaluate a file, but throw away the result and only return error (if any). - /// Useful for when you don't need the result, but still need - /// to keep track of possible errors + /// Useful for when you don't need the result, but still need to keep track of possible errors. pub fn consume_file(&mut self, filename: &str) -> Result<(), EvalAltResult> { use std::fs::File; use std::io::prelude::*; @@ -213,18 +222,20 @@ impl<'e> Engine<'e> { } /// Evaluate a string, but throw away the result and only return error (if any). - /// Useful for when you don't need the result, but still need - /// to keep track of possible errors + /// Useful for when you don't need the result, but still need to keep track of possible errors. pub fn consume(&mut self, input: &str) -> Result<(), EvalAltResult> { - self.consume_with_scope(&mut Scope::new(), input) + self.consume_with_scope(&mut Scope::new(), false, input) } - /// Evaluate a string with own scope, but throw away the result and only return error (if any). - /// Useful for when you don't need the result, but still need - /// to keep track of possible errors + /// Evaluate a string, but throw away the result and only return error (if any). + /// Useful for when you don't need the result, but still need to keep track of possible errors. + /// + /// Note - if `retain_functions` is set to `true`, functions defined by previous scripts are _retained_ + /// and not cleared from run to run. pub fn consume_with_scope( &mut self, scope: &mut Scope, + retain_functions: bool, input: &str, ) -> Result<(), EvalAltResult> { let tokens = lex(input); @@ -247,7 +258,9 @@ impl<'e> Engine<'e> { .try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o)) .map(|_| ()); - self.script_functions.clear(); // Clean up engine + if !retain_functions { + self.clear_functions(); + } val }) @@ -300,7 +313,7 @@ impl<'e> Engine<'e> { }) }); - self.script_functions.clear(); // Clean up engine + self.clear_functions(); result } diff --git a/src/engine.rs b/src/engine.rs index 1177a044..5806abb2 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1030,6 +1030,11 @@ impl Engine<'_> { .map(|s| s.as_str()) .unwrap_or(name) } + + /// Clean up all script-defined functions within the `Engine`. + pub fn clear_functions(&mut self) { + self.script_functions.clear(); + } } /// Print/debug to stdout diff --git a/src/error.rs b/src/error.rs index 5c4419d4..8733682b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -81,7 +81,7 @@ pub enum ParseErrorType { /// Error when parsing a script. #[derive(Debug, PartialEq, Clone)] -pub struct ParseError(ParseErrorType, Position); +pub struct ParseError(pub(crate) ParseErrorType, pub(crate) Position); impl ParseError { /// Create a new `ParseError`. diff --git a/src/result.rs b/src/result.rs index 95b5ee12..224c8232 100644 --- a/src/result.rs +++ b/src/result.rs @@ -174,29 +174,54 @@ impl From for EvalAltResult { } impl EvalAltResult { + pub fn position(&self) -> Position { + match self { + Self::ErrorReadingScriptFile(_, _) | Self::LoopBreak => Position::none(), + + Self::ErrorParsing(err) => err.position(), + + Self::ErrorFunctionNotFound(_, pos) + | Self::ErrorFunctionArgsMismatch(_, _, _, pos) + | Self::ErrorBooleanArgMismatch(_, pos) + | Self::ErrorCharMismatch(pos) + | Self::ErrorArrayBounds(_, _, pos) + | Self::ErrorStringBounds(_, _, pos) + | Self::ErrorIndexingType(_, pos) + | Self::ErrorIndexExpr(pos) + | Self::ErrorIfGuard(pos) + | Self::ErrorFor(pos) + | Self::ErrorVariableNotFound(_, pos) + | Self::ErrorAssignmentToUnknownLHS(pos) + | Self::ErrorMismatchOutputType(_, pos) + | Self::ErrorDotExpr(_, pos) + | Self::ErrorArithmetic(_, pos) + | Self::ErrorRuntime(_, pos) + | Self::Return(_, pos) => *pos, + } + } + pub(crate) fn set_position(&mut self, new_position: Position) { match self { - EvalAltResult::ErrorReadingScriptFile(_, _) - | EvalAltResult::LoopBreak - | EvalAltResult::ErrorParsing(_) => (), + Self::ErrorReadingScriptFile(_, _) | Self::LoopBreak => (), - EvalAltResult::ErrorFunctionNotFound(_, ref mut pos) - | EvalAltResult::ErrorFunctionArgsMismatch(_, _, _, ref mut pos) - | EvalAltResult::ErrorBooleanArgMismatch(_, ref mut pos) - | EvalAltResult::ErrorCharMismatch(ref mut pos) - | EvalAltResult::ErrorArrayBounds(_, _, ref mut pos) - | EvalAltResult::ErrorStringBounds(_, _, ref mut pos) - | EvalAltResult::ErrorIndexingType(_, ref mut pos) - | EvalAltResult::ErrorIndexExpr(ref mut pos) - | EvalAltResult::ErrorIfGuard(ref mut pos) - | EvalAltResult::ErrorFor(ref mut pos) - | EvalAltResult::ErrorVariableNotFound(_, ref mut pos) - | EvalAltResult::ErrorAssignmentToUnknownLHS(ref mut pos) - | EvalAltResult::ErrorMismatchOutputType(_, ref mut pos) - | EvalAltResult::ErrorDotExpr(_, ref mut pos) - | EvalAltResult::ErrorArithmetic(_, ref mut pos) - | EvalAltResult::ErrorRuntime(_, ref mut pos) - | EvalAltResult::Return(_, ref mut pos) => *pos = new_position, + Self::ErrorParsing(ParseError(_, ref mut pos)) + | Self::ErrorFunctionNotFound(_, ref mut pos) + | Self::ErrorFunctionArgsMismatch(_, _, _, ref mut pos) + | Self::ErrorBooleanArgMismatch(_, ref mut pos) + | Self::ErrorCharMismatch(ref mut pos) + | Self::ErrorArrayBounds(_, _, ref mut pos) + | Self::ErrorStringBounds(_, _, ref mut pos) + | Self::ErrorIndexingType(_, ref mut pos) + | Self::ErrorIndexExpr(ref mut pos) + | Self::ErrorIfGuard(ref mut pos) + | Self::ErrorFor(ref mut pos) + | Self::ErrorVariableNotFound(_, ref mut pos) + | Self::ErrorAssignmentToUnknownLHS(ref mut pos) + | Self::ErrorMismatchOutputType(_, ref mut pos) + | Self::ErrorDotExpr(_, ref mut pos) + | Self::ErrorArithmetic(_, ref mut pos) + | Self::ErrorRuntime(_, ref mut pos) + | Self::Return(_, ref mut pos) => *pos = new_position, } } } diff --git a/src/scope.rs b/src/scope.rs index 2c791faa..2e2c492b 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -4,7 +4,7 @@ use crate::any::{Any, Dynamic}; use std::borrow::Cow; /// A type containing information about current scope. -/// Useful for keeping state between `Engine` runs +/// Useful for keeping state between `Engine` runs. /// /// # Example /// @@ -15,9 +15,9 @@ use std::borrow::Cow; /// let mut engine = Engine::new(); /// let mut my_scope = Scope::new(); /// -/// engine.eval_with_scope::<()>(&mut my_scope, "let x = 5;")?; +/// engine.eval_with_scope::<()>(&mut my_scope, false, "let x = 5;")?; /// -/// assert_eq!(engine.eval_with_scope::(&mut my_scope, "x + 1")?, 6); +/// assert_eq!(engine.eval_with_scope::(&mut my_scope, false, "x + 1")?, 6); /// # Ok(()) /// # } /// ``` diff --git a/tests/var_scope.rs b/tests/var_scope.rs index 9028022a..f8371bb3 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -5,12 +5,15 @@ fn test_var_scope() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); let mut scope = Scope::new(); - engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5")?; - assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 9); - engine.eval_with_scope::<()>(&mut scope, "x = x + 1; x = x + 2;")?; - assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 12); - assert_eq!(engine.eval_with_scope::<()>(&mut scope, "{let x = 3}")?, ()); - assert_eq!(engine.eval_with_scope::(&mut scope, "x")?, 12); + engine.eval_with_scope::<()>(&mut scope, false, "let x = 4 + 5")?; + assert_eq!(engine.eval_with_scope::(&mut scope, false, "x")?, 9); + engine.eval_with_scope::<()>(&mut scope, false, "x = x + 1; x = x + 2;")?; + assert_eq!(engine.eval_with_scope::(&mut scope, false, "x")?, 12); + assert_eq!( + engine.eval_with_scope::<()>(&mut scope, false, "{let x = 3}")?, + () + ); + assert_eq!(engine.eval_with_scope::(&mut scope, false, "x")?, 12); Ok(()) } @@ -30,17 +33,11 @@ fn test_scope_eval() -> Result<(), EvalAltResult> { // First invocation engine - .eval_with_scope::<()>( - &mut scope, - r" - let x = 4 + 5 - y + z; - y = 1; - ", - ) + .eval_with_scope::<()>(&mut scope, false, " let x = 4 + 5 - y + z; y = 1;") .expect("y and z not found?"); // Second invocation using the same state - let result = engine.eval_with_scope::(&mut scope, "x")?; + let result = engine.eval_with_scope::(&mut scope, false, "x")?; println!("result: {}", result); // should print 966