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
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
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
pub(crate) type_iterators: HashMap<TypeId, Arc<IteratorFn>>,
pub(crate) type_names: HashMap<String, String>,
pub(crate) on_print: Box<dyn FnMut(&str) + 'a>,
pub(crate) on_debug: Box<dyn FnMut(&str) + 'a>,
pub(crate) on_print: Box<dyn FnMut(&str) + 'e>,
pub(crate) on_debug: Box<dyn FnMut(&str) + 'e>,
}
pub enum FnIntExt {
pub enum FnIntExt<'a> {
Ext(Box<FnAny>),
Int(FnDef),
Int(FnDef<'a>),
}
pub type FnAny = dyn Fn(FnCallArgs, Position) -> Result<Dynamic, EvalAltResult>;
@ -104,27 +104,27 @@ impl Engine<'_> {
if let Some(f) = fn_def {
match *f {
FnIntExt::Ext(ref f) => {
let r = f(args, pos)?;
FnIntExt::Ext(ref func) => {
let result = func(args, pos)?;
let callback = match spec.name.as_ref() {
KEYWORD_PRINT => self.on_print.as_mut(),
KEYWORD_DEBUG => self.on_debug.as_mut(),
_ => return Ok(r),
_ => return Ok(result),
};
Ok(callback(
&r.downcast::<String>()
.map(|s| *s)
.unwrap_or("error: not a string".into()),
)
.into_dynamic())
let val = &result
.downcast::<String>()
.map(|s| *s)
.unwrap_or("error: not a string".into());
Ok(callback(val).into_dynamic())
}
FnIntExt::Int(ref f) => {
if f.params.len() != args.len() {
FnIntExt::Int(ref func) => {
if func.params.len() != args.len() {
return Err(EvalAltResult::ErrorFunctionArgsMismatch(
spec.name.into(),
f.params.len(),
func.params.len(),
args.len(),
pos,
));
@ -133,13 +133,13 @@ impl Engine<'_> {
let mut scope = Scope::new();
scope.extend(
f.params
func.params
.iter()
.cloned()
.map(|s| s.clone())
.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),
other => other,
}
@ -319,12 +319,12 @@ impl Engine<'_> {
}
/// Evaluate an index expression
fn eval_index_expr(
fn eval_index_expr<'a>(
&mut self,
scope: &mut Scope,
lhs: &Expr,
lhs: &'a 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)?;
match lhs {
@ -336,7 +336,7 @@ impl Engine<'_> {
lhs.position(),
)
.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]
@ -371,7 +371,8 @@ impl Engine<'_> {
// array_id[idx] = val
IndexSourceType::Array => {
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
@ -381,7 +382,8 @@ impl Engine<'_> {
let ch = *val
.downcast::<char>()
.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
@ -419,7 +421,7 @@ impl Engine<'_> {
// of the above `clone`.
if let Some((id, src_idx)) = src {
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 {
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
fn eval_expr(&mut self, scope: &mut Scope, expr: &Expr) -> Result<Dynamic, EvalAltResult> {
match expr {
Expr::IntegerConstant(i, _) => Ok((*i).into_dynamic()),
Expr::FloatConstant(i, _) => Ok((*i).into_dynamic()),
Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()),
Expr::FloatConstant(f, _) => Ok(f.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) => {
Self::search_scope(scope, id, Ok, *pos).map(|(_, val)| val)
}

View File

@ -75,6 +75,8 @@ pub enum ParseErrorType {
FnMissingName,
/// A function definition is missing the parameters list. Wrapped value is the function name.
FnMissingParams(String),
/// Assignment to an inappropriate LHS (left-hand-side) expression.
AssignmentToInvalidLHS,
}
/// Error when parsing a script.
@ -114,6 +116,7 @@ impl Error for ParseError {
ParseErrorType::FnMissingName => "Expecting name 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::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 std::char;
use std::iter::Peekable;
use std::str::Chars;
use std::{borrow::Cow, str::Chars};
type LERR = LexError;
type PERR = ParseErrorType;
@ -99,12 +99,12 @@ impl std::fmt::Debug for Position {
}
/// 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)]
pub struct FnDef {
pub name: String,
pub params: Vec<String>,
pub struct FnDef<'a> {
pub name: Cow<'a, str>,
pub params: Vec<Cow<'a, str>>,
pub body: Box<Stmt>,
pub pos: Position,
}
@ -232,14 +232,14 @@ pub enum Token {
}
impl Token {
pub fn syntax(&self) -> std::borrow::Cow<'static, str> {
pub fn syntax<'a>(&'a self) -> Cow<'a, str> {
use self::Token::*;
match *self {
IntegerConstant(ref s) => s.to_string().into(),
FloatConstant(ref s) => s.to_string().into(),
Identifier(ref s) => s.to_string().into(),
CharConstant(ref s) => s.to_string().into(),
IntegerConstant(ref i) => i.to_string().into(),
FloatConstant(ref f) => f.to_string().into(),
Identifier(ref s) => s.into(),
CharConstant(ref c) => c.to_string().into(),
LexError(ref err) => err.to_string().into(),
ref token => (match token {
@ -301,7 +301,7 @@ impl Token {
PowerOfAssign => "~=",
For => "for",
In => "in",
_ => panic!(),
_ => panic!("operator should be match in outer scope"),
})
.into(),
}
@ -538,8 +538,7 @@ impl<'a> TokenIterator<'a> {
}
}
let out: String = result.iter().collect();
Ok(out)
Ok(result.iter().collect())
}
fn inner_next(&mut self) -> Option<(Token, Position)> {
@ -640,20 +639,20 @@ impl<'a> TokenIterator<'a> {
},
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' | '_' => {
let mut result = Vec::new();
@ -687,7 +686,7 @@ impl<'a> TokenIterator<'a> {
"fn" => Token::Fn,
"for" => Token::For,
"in" => Token::In,
x => Token::Identifier(x.into()),
_ => Token::Identifier(out),
},
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>(
input: &mut Peekable<TokenIterator<'a>>,
precedence: i8,
@ -1308,7 +1322,7 @@ fn parse_binary_op<'a>(
}
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 => {
let lhs_copy = current_lhs.clone();
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() {
Some((_, tok_pos)) => tok_pos,
_ => 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::Comma, _)) => (),
Some((Token::Identifier(s), _)) => {
params.push(s);
params.push(s.into());
}
Some((_, pos)) => return Err(ParseError::new(PERR::MalformedCallExpr, pos)),
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)?;
Ok(FnDef {
name: name,
name: name.into(),
params: params,
body: Box::new(body),
pos: pos,

View File

@ -1,4 +1,5 @@
use crate::any::{Any, Dynamic};
use std::borrow::Cow;
/// A type containing information about current scope.
/// 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,
/// 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.
pub fn new() -> Self {
Self(Vec::new())
@ -36,18 +37,18 @@ impl Scope {
}
/// Add (push) a new variable to the Scope.
pub fn push<T: Any>(&mut self, key: String, value: T) {
self.0.push((key, Box::new(value)));
pub fn push<K: Into<Cow<'a, str>>, T: Any>(&mut self, key: K, value: T) {
self.0.push((key.into(), Box::new(value)));
}
/// Add (push) a new variable to the Scope.
pub(crate) fn push_dynamic(&mut self, key: String, value: Dynamic) {
self.0.push((key, value));
pub(crate) fn push_dynamic<K: Into<Cow<'a, str>>>(&mut self, key: K, value: Dynamic) {
self.0.push((key.into(), value));
}
/// Remove (pop) the last variable from the Scope.
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.
@ -56,13 +57,13 @@ impl Scope {
}
/// 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
.iter()
.enumerate()
.rev() // Always search a Scope in reverse order
.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.
@ -97,20 +98,26 @@ impl Scope {
self.0
.iter()
.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.
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&str, &mut Dynamic)> {
self.0
.iter_mut()
.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 {
fn extend<T: IntoIterator<Item = (String, Dynamic)>>(&mut self, iter: T) {
self.0.extend(iter);
impl<'a, K> std::iter::Extend<(K, Dynamic)> for Scope<'a>
where
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
// 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.
scope.push("y".into(), 42_i64);
scope.push("z".into(), 999_i64);
scope.push("y", 42_i64);
scope.push("z", 999_i64);
// First invocation
engine