Avoid string copying.

This commit is contained in:
Stephen Chung 2020-03-07 10:15:42 +08:00
parent ea82ee81d6
commit 024133ae2d
5 changed files with 106 additions and 80 deletions

View File

@ -46,22 +46,22 @@ type IteratorFn = dyn Fn(&Dynamic) -> Box<dyn Iterator<Item = Dynamic>>;
/// } /// }
/// } /// }
/// ``` /// ```
pub struct Engine<'a> { pub struct Engine<'e> {
/// A hashmap containing all compiled functions known to the engine /// A hashmap containing all compiled functions known to the engine
pub(crate) external_functions: HashMap<FnSpec<'a>, Arc<FnIntExt>>, pub(crate) external_functions: HashMap<FnSpec<'e>, Arc<FnIntExt<'e>>>,
/// A hashmap containing all script-defined functions /// A hashmap containing all script-defined functions
pub(crate) script_functions: HashMap<FnSpec<'a>, Arc<FnIntExt>>, pub(crate) script_functions: HashMap<FnSpec<'e>, Arc<FnIntExt<'e>>>,
/// A hashmap containing all iterators known to the engine /// A hashmap containing all iterators known to the engine
pub(crate) type_iterators: HashMap<TypeId, Arc<IteratorFn>>, pub(crate) type_iterators: HashMap<TypeId, Arc<IteratorFn>>,
pub(crate) type_names: HashMap<String, String>, pub(crate) type_names: HashMap<String, String>,
pub(crate) on_print: Box<dyn FnMut(&str) + 'a>, pub(crate) on_print: Box<dyn FnMut(&str) + 'e>,
pub(crate) on_debug: Box<dyn FnMut(&str) + 'a>, pub(crate) on_debug: Box<dyn FnMut(&str) + 'e>,
} }
pub enum FnIntExt { pub enum FnIntExt<'a> {
Ext(Box<FnAny>), Ext(Box<FnAny>),
Int(FnDef), Int(FnDef<'a>),
} }
pub type FnAny = dyn Fn(FnCallArgs, Position) -> Result<Dynamic, EvalAltResult>; pub type FnAny = dyn Fn(FnCallArgs, Position) -> Result<Dynamic, EvalAltResult>;
@ -104,27 +104,27 @@ impl Engine<'_> {
if let Some(f) = fn_def { if let Some(f) = fn_def {
match *f { match *f {
FnIntExt::Ext(ref f) => { FnIntExt::Ext(ref func) => {
let r = f(args, pos)?; let result = func(args, pos)?;
let callback = match spec.name.as_ref() { let callback = match spec.name.as_ref() {
KEYWORD_PRINT => self.on_print.as_mut(), KEYWORD_PRINT => self.on_print.as_mut(),
KEYWORD_DEBUG => self.on_debug.as_mut(), KEYWORD_DEBUG => self.on_debug.as_mut(),
_ => return Ok(r), _ => return Ok(result),
}; };
Ok(callback( let val = &result
&r.downcast::<String>() .downcast::<String>()
.map(|s| *s) .map(|s| *s)
.unwrap_or("error: not a string".into()), .unwrap_or("error: not a string".into());
)
.into_dynamic()) Ok(callback(val).into_dynamic())
} }
FnIntExt::Int(ref f) => { FnIntExt::Int(ref func) => {
if f.params.len() != args.len() { if func.params.len() != args.len() {
return Err(EvalAltResult::ErrorFunctionArgsMismatch( return Err(EvalAltResult::ErrorFunctionArgsMismatch(
spec.name.into(), spec.name.into(),
f.params.len(), func.params.len(),
args.len(), args.len(),
pos, pos,
)); ));
@ -133,13 +133,13 @@ impl Engine<'_> {
let mut scope = Scope::new(); let mut scope = Scope::new();
scope.extend( scope.extend(
f.params func.params
.iter() .iter()
.cloned() .map(|s| s.clone())
.zip(args.iter().map(|x| (*x).into_dynamic())), .zip(args.iter().map(|x| (*x).into_dynamic())),
); );
match self.eval_stmt(&mut scope, &*f.body) { match self.eval_stmt(&mut scope, &*func.body) {
Err(EvalAltResult::Return(x, _)) => Ok(x), Err(EvalAltResult::Return(x, _)) => Ok(x),
other => other, other => other,
} }
@ -319,12 +319,12 @@ impl Engine<'_> {
} }
/// Evaluate an index expression /// Evaluate an index expression
fn eval_index_expr( fn eval_index_expr<'a>(
&mut self, &mut self,
scope: &mut Scope, scope: &mut Scope,
lhs: &Expr, lhs: &'a Expr,
idx_expr: &Expr, idx_expr: &Expr,
) -> Result<(IndexSourceType, Option<(String, usize)>, usize, Dynamic), EvalAltResult> { ) -> Result<(IndexSourceType, Option<(&'a str, usize)>, usize, Dynamic), EvalAltResult> {
let idx = self.eval_index_value(scope, idx_expr)?; let idx = self.eval_index_value(scope, idx_expr)?;
match lhs { match lhs {
@ -336,7 +336,7 @@ impl Engine<'_> {
lhs.position(), lhs.position(),
) )
.map(|(src_idx, (val, src_type))| { .map(|(src_idx, (val, src_type))| {
(src_type, Some((id.clone(), src_idx)), idx as usize, val) (src_type, Some((id.as_str(), src_idx)), idx as usize, val)
}), }),
// (expr)[idx_expr] // (expr)[idx_expr]
@ -371,7 +371,8 @@ impl Engine<'_> {
// array_id[idx] = val // array_id[idx] = val
IndexSourceType::Array => { IndexSourceType::Array => {
let arr = scope.get_mut_by_type::<Array>(id, src_idx); let arr = scope.get_mut_by_type::<Array>(id, src_idx);
(arr[idx as usize] = val).into_dynamic() arr[idx as usize] = val;
().into_dynamic()
} }
// string_id[idx] = val // string_id[idx] = val
@ -381,7 +382,8 @@ impl Engine<'_> {
let ch = *val let ch = *val
.downcast::<char>() .downcast::<char>()
.expect("char value expected to update an index position in a string"); .expect("char value expected to update an index position in a string");
Self::str_replace_char(s, idx as usize, ch).into_dynamic() Self::str_replace_char(s, idx as usize, ch);
().into_dynamic()
} }
// All other variable types should be an error // All other variable types should be an error
@ -419,7 +421,7 @@ impl Engine<'_> {
// of the above `clone`. // of the above `clone`.
if let Some((id, src_idx)) = src { if let Some((id, src_idx)) = src {
Self::update_indexed_variable_in_scope( Self::update_indexed_variable_in_scope(
src_type, scope, &id, src_idx, idx, target, src_type, scope, id, src_idx, idx, target,
); );
} }
@ -510,7 +512,7 @@ impl Engine<'_> {
if let Some((id, src_idx)) = src { if let Some((id, src_idx)) = src {
Self::update_indexed_variable_in_scope( Self::update_indexed_variable_in_scope(
src_type, scope, &id, src_idx, idx, target, src_type, scope, id, src_idx, idx, target,
); );
} }
@ -525,10 +527,10 @@ impl Engine<'_> {
/// Evaluate an expression /// Evaluate an expression
fn eval_expr(&mut self, scope: &mut Scope, expr: &Expr) -> Result<Dynamic, EvalAltResult> { fn eval_expr(&mut self, scope: &mut Scope, expr: &Expr) -> Result<Dynamic, EvalAltResult> {
match expr { match expr {
Expr::IntegerConstant(i, _) => Ok((*i).into_dynamic()), Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()),
Expr::FloatConstant(i, _) => Ok((*i).into_dynamic()), Expr::FloatConstant(f, _) => Ok(f.into_dynamic()),
Expr::StringConstant(s, _) => Ok(s.into_dynamic()), Expr::StringConstant(s, _) => Ok(s.into_dynamic()),
Expr::CharConstant(c, _) => Ok((*c).into_dynamic()), Expr::CharConstant(c, _) => Ok(c.into_dynamic()),
Expr::Identifier(id, pos) => { Expr::Identifier(id, pos) => {
Self::search_scope(scope, id, Ok, *pos).map(|(_, val)| val) Self::search_scope(scope, id, Ok, *pos).map(|(_, val)| val)
} }

View File

@ -75,6 +75,8 @@ pub enum ParseErrorType {
FnMissingName, FnMissingName,
/// A function definition is missing the parameters list. Wrapped value is the function name. /// A function definition is missing the parameters list. Wrapped value is the function name.
FnMissingParams(String), FnMissingParams(String),
/// Assignment to an inappropriate LHS (left-hand-side) expression.
AssignmentToInvalidLHS,
} }
/// Error when parsing a script. /// Error when parsing a script.
@ -114,6 +116,7 @@ impl Error for ParseError {
ParseErrorType::FnMissingName => "Expecting name in function declaration", ParseErrorType::FnMissingName => "Expecting name in function declaration",
ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration", ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration",
ParseErrorType::WrongFnDefinition => "Function definitions must be at top level and cannot be inside a block or another function", ParseErrorType::WrongFnDefinition => "Function definitions must be at top level and cannot be inside a block or another function",
ParseErrorType::AssignmentToInvalidLHS => "Assignment to an unsupported left-hand side expression"
} }
} }

View File

@ -2,7 +2,7 @@ use crate::any::Dynamic;
use crate::error::{LexError, ParseError, ParseErrorType}; use crate::error::{LexError, ParseError, ParseErrorType};
use std::char; use std::char;
use std::iter::Peekable; use std::iter::Peekable;
use std::str::Chars; use std::{borrow::Cow, str::Chars};
type LERR = LexError; type LERR = LexError;
type PERR = ParseErrorType; type PERR = ParseErrorType;
@ -99,12 +99,12 @@ impl std::fmt::Debug for Position {
} }
/// Compiled AST (abstract syntax tree) of a Rhai script. /// Compiled AST (abstract syntax tree) of a Rhai script.
pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<FnDef>); pub struct AST(pub(crate) Vec<Stmt>, pub(crate) Vec<FnDef<'static>>);
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FnDef { pub struct FnDef<'a> {
pub name: String, pub name: Cow<'a, str>,
pub params: Vec<String>, pub params: Vec<Cow<'a, str>>,
pub body: Box<Stmt>, pub body: Box<Stmt>,
pub pos: Position, pub pos: Position,
} }
@ -232,14 +232,14 @@ pub enum Token {
} }
impl Token { impl Token {
pub fn syntax(&self) -> std::borrow::Cow<'static, str> { pub fn syntax<'a>(&'a self) -> Cow<'a, str> {
use self::Token::*; use self::Token::*;
match *self { match *self {
IntegerConstant(ref s) => s.to_string().into(), IntegerConstant(ref i) => i.to_string().into(),
FloatConstant(ref s) => s.to_string().into(), FloatConstant(ref f) => f.to_string().into(),
Identifier(ref s) => s.to_string().into(), Identifier(ref s) => s.into(),
CharConstant(ref s) => s.to_string().into(), CharConstant(ref c) => c.to_string().into(),
LexError(ref err) => err.to_string().into(), LexError(ref err) => err.to_string().into(),
ref token => (match token { ref token => (match token {
@ -301,7 +301,7 @@ impl Token {
PowerOfAssign => "~=", PowerOfAssign => "~=",
For => "for", For => "for",
In => "in", In => "in",
_ => panic!(), _ => panic!("operator should be match in outer scope"),
}) })
.into(), .into(),
} }
@ -538,8 +538,7 @@ impl<'a> TokenIterator<'a> {
} }
} }
let out: String = result.iter().collect(); Ok(result.iter().collect())
Ok(out)
} }
fn inner_next(&mut self) -> Option<(Token, Position)> { fn inner_next(&mut self) -> Option<(Token, Position)> {
@ -640,20 +639,20 @@ impl<'a> TokenIterator<'a> {
}, },
pos, pos,
)); ));
} else {
let out: String = result.iter().filter(|&&c| c != '_').collect();
return Some((
if let Ok(val) = out.parse::<i64>() {
Token::IntegerConstant(val)
} else if let Ok(val) = out.parse::<f64>() {
Token::FloatConstant(val)
} else {
Token::LexError(LERR::MalformedNumber(result.iter().collect()))
},
pos,
));
} }
let out: String = result.iter().filter(|&&c| c != '_').collect();
return Some((
if let Ok(val) = out.parse::<i64>() {
Token::IntegerConstant(val)
} else if let Ok(val) = out.parse::<f64>() {
Token::FloatConstant(val)
} else {
Token::LexError(LERR::MalformedNumber(result.iter().collect()))
},
pos,
));
} }
'A'..='Z' | 'a'..='z' | '_' => { 'A'..='Z' | 'a'..='z' | '_' => {
let mut result = Vec::new(); let mut result = Vec::new();
@ -687,7 +686,7 @@ impl<'a> TokenIterator<'a> {
"fn" => Token::Fn, "fn" => Token::Fn,
"for" => Token::For, "for" => Token::For,
"in" => Token::In, "in" => Token::In,
x => Token::Identifier(x.into()), _ => Token::Identifier(out),
}, },
pos, pos,
)); ));
@ -1264,6 +1263,21 @@ fn parse_unary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pars
} }
} }
fn parse_assignment(lhs: Expr, rhs: Expr) -> Result<Expr, ParseError> {
match lhs {
// Only assignments to a variable, and index erxpression and a dot expression is valid LHS
Expr::Identifier(_, _) | Expr::Index(_, _) | Expr::Dot(_, _) => {
Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs)))
}
// All other LHS cannot be assigned to
_ => Err(ParseError::new(
PERR::AssignmentToInvalidLHS,
lhs.position(),
)),
}
}
fn parse_binary_op<'a>( fn parse_binary_op<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
precedence: i8, precedence: i8,
@ -1308,7 +1322,7 @@ fn parse_binary_op<'a>(
} }
Token::Divide => Expr::FunctionCall("/".into(), vec![current_lhs, rhs], None, pos), Token::Divide => Expr::FunctionCall("/".into(), vec![current_lhs, rhs], None, pos),
Token::Equals => Expr::Assignment(Box::new(current_lhs), Box::new(rhs)), Token::Equals => parse_assignment(current_lhs, rhs)?,
Token::PlusAssign => { Token::PlusAssign => {
let lhs_copy = current_lhs.clone(); let lhs_copy = current_lhs.clone();
Expr::Assignment( Expr::Assignment(
@ -1687,7 +1701,7 @@ fn parse_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
} }
} }
fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseError> { fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef<'static>, ParseError> {
let pos = match input.next() { let pos = match input.next() {
Some((_, tok_pos)) => tok_pos, Some((_, tok_pos)) => tok_pos,
_ => return Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())), _ => return Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())),
@ -1723,7 +1737,7 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
Some((Token::RightParen, _)) => break, Some((Token::RightParen, _)) => break,
Some((Token::Comma, _)) => (), Some((Token::Comma, _)) => (),
Some((Token::Identifier(s), _)) => { Some((Token::Identifier(s), _)) => {
params.push(s); params.push(s.into());
} }
Some((_, pos)) => return Err(ParseError::new(PERR::MalformedCallExpr, pos)), Some((_, pos)) => return Err(ParseError::new(PERR::MalformedCallExpr, pos)),
None => return Err(ParseError::new(PERR::MalformedCallExpr, Position::eof())), None => return Err(ParseError::new(PERR::MalformedCallExpr, Position::eof())),
@ -1734,7 +1748,7 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
let body = parse_block(input)?; let body = parse_block(input)?;
Ok(FnDef { Ok(FnDef {
name: name, name: name.into(),
params: params, params: params,
body: Box::new(body), body: Box::new(body),
pos: pos, pos: pos,

View File

@ -1,4 +1,5 @@
use crate::any::{Any, Dynamic}; use crate::any::{Any, Dynamic};
use std::borrow::Cow;
/// A type containing information about current scope. /// A type containing information about current scope.
/// Useful for keeping state between `Engine` runs /// Useful for keeping state between `Engine` runs
@ -17,9 +18,9 @@ use crate::any::{Any, Dynamic};
/// ///
/// When searching for variables, newly-added variables are found before similarly-named but older variables, /// When searching for variables, newly-added variables are found before similarly-named but older variables,
/// allowing for automatic _shadowing_ of variables. /// allowing for automatic _shadowing_ of variables.
pub struct Scope(Vec<(String, Dynamic)>); pub struct Scope<'a>(Vec<(Cow<'a, str>, Dynamic)>);
impl Scope { impl<'a> Scope<'a> {
/// Create a new Scope. /// Create a new Scope.
pub fn new() -> Self { pub fn new() -> Self {
Self(Vec::new()) Self(Vec::new())
@ -36,18 +37,18 @@ impl Scope {
} }
/// Add (push) a new variable to the Scope. /// Add (push) a new variable to the Scope.
pub fn push<T: Any>(&mut self, key: String, value: T) { pub fn push<K: Into<Cow<'a, str>>, T: Any>(&mut self, key: K, value: T) {
self.0.push((key, Box::new(value))); self.0.push((key.into(), Box::new(value)));
} }
/// Add (push) a new variable to the Scope. /// Add (push) a new variable to the Scope.
pub(crate) fn push_dynamic(&mut self, key: String, value: Dynamic) { pub(crate) fn push_dynamic<K: Into<Cow<'a, str>>>(&mut self, key: K, value: Dynamic) {
self.0.push((key, value)); self.0.push((key.into(), value));
} }
/// Remove (pop) the last variable from the Scope. /// Remove (pop) the last variable from the Scope.
pub fn pop(&mut self) -> Option<(String, Dynamic)> { pub fn pop(&mut self) -> Option<(String, Dynamic)> {
self.0.pop() self.0.pop().map(|(key, value)| (key.to_string(), value))
} }
/// Truncate (rewind) the Scope to a previous size. /// Truncate (rewind) the Scope to a previous size.
@ -56,13 +57,13 @@ impl Scope {
} }
/// Find a variable in the Scope, starting from the last. /// Find a variable in the Scope, starting from the last.
pub fn get(&self, key: &str) -> Option<(usize, String, Dynamic)> { pub fn get(&self, key: &str) -> Option<(usize, &str, Dynamic)> {
self.0 self.0
.iter() .iter()
.enumerate() .enumerate()
.rev() // Always search a Scope in reverse order .rev() // Always search a Scope in reverse order
.find(|(_, (name, _))| name == key) .find(|(_, (name, _))| name == key)
.map(|(i, (name, value))| (i, name.clone(), value.clone())) .map(|(i, (name, value))| (i, name.as_ref(), value.clone()))
} }
/// Get the value of a variable in the Scope, starting from the last. /// Get the value of a variable in the Scope, starting from the last.
@ -97,20 +98,26 @@ impl Scope {
self.0 self.0
.iter() .iter()
.rev() // Always search a Scope in reverse order .rev() // Always search a Scope in reverse order
.map(|(key, value)| (key.as_str(), value)) .map(|(key, value)| (key.as_ref(), value))
} }
/*
/// Get a mutable iterator to variables in the Scope. /// Get a mutable iterator to variables in the Scope.
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut Dynamic)> { pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut Dynamic)> {
self.0 self.0
.iter_mut() .iter_mut()
.rev() // Always search a Scope in reverse order .rev() // Always search a Scope in reverse order
.map(|(key, value)| (key.as_str(), value)) .map(|(key, value)| (key.as_ref(), value))
} }
*/
} }
impl std::iter::Extend<(String, Dynamic)> for Scope { impl<'a, K> std::iter::Extend<(K, Dynamic)> for Scope<'a>
fn extend<T: IntoIterator<Item = (String, Dynamic)>>(&mut self, iter: T) { where
self.0.extend(iter); K: Into<Cow<'a, str>>,
{
fn extend<T: IntoIterator<Item = (K, Dynamic)>>(&mut self, iter: T) {
self.0
.extend(iter.into_iter().map(|(key, value)| (key.into(), value)));
} }
} }

View File

@ -25,8 +25,8 @@ fn test_scope_eval() -> Result<(), EvalAltResult> {
// Then push some initialized variables into the state // Then push some initialized variables into the state
// NOTE: Remember the default numbers used by Rhai are i64 and f64. // NOTE: Remember the default numbers used by Rhai are i64 and f64.
// Better stick to them or it gets hard to work with other variables in the script. // Better stick to them or it gets hard to work with other variables in the script.
scope.push("y".into(), 42_i64); scope.push("y", 42_i64);
scope.push("z".into(), 999_i64); scope.push("z", 999_i64);
// First invocation // First invocation
engine engine