From 6969b13ebcc1388a64b8c3017bad64b0e420285c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 3 Mar 2020 10:01:48 +0800 Subject: [PATCH 01/24] Remove TODO in favor of tracking issue. --- TODO | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 TODO diff --git a/TODO b/TODO deleted file mode 100644 index 8ae9f602..00000000 --- a/TODO +++ /dev/null @@ -1,7 +0,0 @@ -pre 1.0: - - basic threads - - stdlib -1.0: - - decide on postfix/prefix operators - - advanced threads + actors - - more literals From fa13588f6927d7b5ef902d78cb47f8c798ea5b3d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 3 Mar 2020 13:30:46 +0800 Subject: [PATCH 02/24] Limit script size to avoid overflowing positions. --- src/parser.rs | 351 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 250 insertions(+), 101 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 2037679c..3361994d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -5,14 +5,14 @@ use std::fmt; use std::iter::Peekable; use std::str::Chars; -#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] +#[derive(Debug, Eq, PartialEq, Hash, Clone)] pub enum LexError { UnexpectedChar(char), UnterminatedString, MalformedEscapeSequence, MalformedNumber, MalformedChar, - Nothing, + InputError(String), } type LERR = LexError; @@ -25,7 +25,7 @@ impl Error for LexError { LERR::MalformedEscapeSequence => "Unexpected values in escape sequence", LERR::MalformedNumber => "Unexpected characters in number", LERR::MalformedChar => "Char constant not a single character", - LERR::Nothing => "This error is for internal use only", + LERR::InputError(_) => "Imput error", } } } @@ -34,6 +34,7 @@ impl fmt::Display for LexError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { LERR::UnexpectedChar(c) => write!(f, "Unexpected '{}'", c), + LERR::InputError(s) => write!(f, "{}", s), _ => write!(f, "{}", self.description()), } } @@ -57,27 +58,67 @@ pub enum ParseErrorType { type PERR = ParseErrorType; -#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] +const MAX_LINES: u16 = 65535; +const MAX_POS: u16 = 65535; + +#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] pub struct Position { - pub line: usize, - pub pos: usize, + line: u16, + pos: u16, } impl Position { - pub fn advance(&mut self) { - self.pos += 1; + pub fn new() -> Self { + Self { line: 1, pos: 0 } } - pub fn rewind(&mut self) { - // Beware, should not rewind at zero position - self.pos -= 1; + + pub fn line(&self) -> u16 { + self.line } - pub fn new_line(&mut self) { - self.line += 1; - self.pos = 0; + + pub fn position(&self) -> u16 { + self.pos } + + pub(crate) fn advance(&mut self) -> Result { + if self.pos >= MAX_POS { + Err(LERR::InputError(format!( + "cannot advance beyond maximum line length ({})", + MAX_POS + ))) + } else { + self.pos += 1; + Ok(self.pos) + } + } + + pub(crate) fn rewind(&mut self) -> Result { + if self.pos == 0 { + Err(LERR::InputError("cannot rewind at position 0".into())) + } else { + self.pos -= 1; + Ok(self.pos) + } + } + + pub(crate) fn new_line(&mut self) -> Result { + if self.line > MAX_LINES { + Err(LERR::InputError(format!( + "reached maximum number of lines ({})", + MAX_LINES + ))) + } else { + self.line += 1; + self.pos = 0; + + Ok(self.pos) + } + } + pub fn eof() -> Self { Self { line: 0, pos: 0 } } + pub fn is_eof(&self) -> bool { self.line == 0 } @@ -85,9 +126,20 @@ impl Position { impl std::fmt::Display for Position { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { - match self.line { - 0 => write!(f, "EOF"), - _ => write!(f, "line {}, position {}", self.line, self.pos), + if self.is_eof() { + write!(f, "EOF") + } else { + write!(f, "line {}, position {}", self.line, self.pos) + } + } +} + +impl std::fmt::Debug for Position { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { + if self.is_eof() { + write!(f, "(EOF)") + } else { + write!(f, "({}:{})", self.line, self.pos) } } } @@ -99,11 +151,11 @@ impl ParseError { pub fn error_type(&self) -> &PERR { &self.0 } - pub fn line(&self) -> usize { - self.1.line + pub fn line(&self) -> u16 { + self.1.line() } - pub fn position(&self) -> usize { - self.1.pos + pub fn position(&self) -> u16 { + self.1.position() } pub fn is_eof(&self) -> bool { self.1.is_eof() @@ -383,14 +435,21 @@ pub struct TokenIterator<'a> { } impl<'a> TokenIterator<'a> { - fn advance(&mut self) { - self.pos.advance(); + fn advance(&mut self) -> Result { + self.pos.advance().map_err(|err| (err, self.pos)) } - fn rewind(&mut self) { - self.pos.rewind(); + fn rewind(&mut self) -> Result { + self.pos.rewind().map_err(|err| (err, self.pos)) } - fn new_line(&mut self) { - self.pos.new_line(); + fn new_line(&mut self) -> Result { + self.pos.new_line().map_err(|err| (err, self.pos)) + } + + fn advance_token(&mut self) -> Result { + self.advance().map_err(|err| (Token::LexErr(err.0), err.1)) + } + fn new_line_token(&mut self) -> Result { + self.new_line().map_err(|err| (Token::LexErr(err.0), err.1)) } pub fn parse_string_const( @@ -407,7 +466,7 @@ impl<'a> TokenIterator<'a> { return Err((LERR::UnterminatedString, Position::eof())); } - self.advance(); + self.advance()?; match next_char.unwrap() { '\\' if !escape => escape = true, @@ -441,7 +500,7 @@ impl<'a> TokenIterator<'a> { } else { return Err((LERR::MalformedEscapeSequence, self.pos)); } - self.advance(); + self.advance()?; } if let Some(r) = char::from_u32(out_val) { @@ -464,7 +523,7 @@ impl<'a> TokenIterator<'a> { } else { return Err((LERR::MalformedEscapeSequence, self.pos)); } - self.advance(); + self.advance()?; } if let Some(r) = char::from_u32(out_val) { @@ -487,7 +546,7 @@ impl<'a> TokenIterator<'a> { } else { return Err((LERR::MalformedEscapeSequence, self.pos)); } - self.advance(); + self.advance()?; } if let Some(r) = char::from_u32(out_val) { @@ -500,7 +559,7 @@ impl<'a> TokenIterator<'a> { x if enclosing_char == x && !escape => break, _ if escape => return Err((LERR::MalformedEscapeSequence, self.pos)), '\n' => { - self.rewind(); + self.rewind()?; return Err((LERR::UnterminatedString, self.pos)); } x => { @@ -516,49 +575,65 @@ impl<'a> TokenIterator<'a> { fn inner_next(&mut self) -> Option<(Token, Position)> { while let Some(c) = self.char_stream.next() { - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } let pos = self.pos; match c { - '\n' => self.new_line(), + '\n' => { + if let Err(err) = self.new_line_token() { + return Some(err); + } + } '0'..='9' => { let mut result = Vec::new(); let mut radix_base: Option = None; result.push(c); - while let Some(&nxt) = self.char_stream.peek() { - match nxt { + while let Some(&next_char) = self.char_stream.peek() { + match next_char { '0'..='9' => { - result.push(nxt); + result.push(next_char); self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } } '.' => { - result.push(nxt); + result.push(next_char); self.char_stream.next(); - self.advance(); - while let Some(&nxt_float) = self.char_stream.peek() { - match nxt_float { + if let Err(err) = self.advance_token() { + return Some(err); + } + while let Some(&next_char_in_float) = self.char_stream.peek() { + match next_char_in_float { '0'..='9' => { - result.push(nxt_float); + result.push(next_char_in_float); self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } } _ => break, } } } 'x' | 'X' => { - result.push(nxt); + result.push(next_char); self.char_stream.next(); - self.advance(); - while let Some(&nxt_hex) = self.char_stream.peek() { - match nxt_hex { + if let Err(err) = self.advance_token() { + return Some(err); + } + while let Some(&next_char_in_hex) = self.char_stream.peek() { + match next_char_in_hex { '0'..='9' | 'a'..='f' | 'A'..='F' => { - result.push(nxt_hex); + result.push(next_char_in_hex); self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } } _ => break, } @@ -566,15 +641,19 @@ impl<'a> TokenIterator<'a> { radix_base = Some(16); } 'o' | 'O' => { - result.push(nxt); + result.push(next_char); self.char_stream.next(); - self.advance(); - while let Some(&nxt_oct) = self.char_stream.peek() { - match nxt_oct { + if let Err(err) = self.advance_token() { + return Some(err); + } + while let Some(&next_char_in_oct) = self.char_stream.peek() { + match next_char_in_oct { '0'..='8' => { - result.push(nxt_oct); + result.push(next_char_in_oct); self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } } _ => break, } @@ -582,15 +661,19 @@ impl<'a> TokenIterator<'a> { radix_base = Some(8); } 'b' | 'B' => { - result.push(nxt); + result.push(next_char); self.char_stream.next(); - self.advance(); - while let Some(&nxt_bin) = self.char_stream.peek() { - match nxt_bin { + if let Err(err) = self.advance_token() { + return Some(err); + } + while let Some(&next_char_in_binary) = self.char_stream.peek() { + match next_char_in_binary { '0' | '1' | '_' => { - result.push(nxt_bin); + result.push(next_char_in_binary); self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } } _ => break, } @@ -608,6 +691,7 @@ impl<'a> TokenIterator<'a> { .skip(2) .filter(|c| c != &'_') .collect(); + if let Ok(val) = i64::from_str_radix(&out, radix) { return Some((Token::IntegerConstant(val), pos)); } @@ -630,12 +714,14 @@ impl<'a> TokenIterator<'a> { let mut result = Vec::new(); result.push(c); - while let Some(&nxt) = self.char_stream.peek() { - match nxt { - x if x.is_alphanumeric() || x == '_' => { + while let Some(&next_char) = self.char_stream.peek() { + match next_char { + x if x.is_ascii_alphanumeric() || x == '_' => { result.push(x); self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } } _ => break, } @@ -673,11 +759,11 @@ impl<'a> TokenIterator<'a> { let mut chars = result.chars(); return Some(( - if let Some(out) = chars.next() { + if let Some(first_char) = chars.next() { if chars.count() != 0 { Token::LexErr(LERR::MalformedChar) } else { - Token::CharConstant(out) + Token::CharConstant(first_char) } } else { Token::LexErr(LERR::MalformedChar) @@ -698,7 +784,9 @@ impl<'a> TokenIterator<'a> { match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } Token::PlusAssign } _ if self.last.is_next_unary() => Token::UnaryPlus, @@ -712,7 +800,9 @@ impl<'a> TokenIterator<'a> { match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } Token::MinusAssign } _ if self.last.is_next_unary() => Token::UnaryMinus, @@ -726,7 +816,9 @@ impl<'a> TokenIterator<'a> { match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } Token::MultiplyAssign } _ => Token::Multiply, @@ -737,37 +829,58 @@ impl<'a> TokenIterator<'a> { '/' => match self.char_stream.peek() { Some(&'/') => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } while let Some(c) = self.char_stream.next() { - if c == '\n' { - self.new_line(); - break; - } else { - self.advance(); + match c { + '\n' => { + if let Err(err) = self.new_line_token() { + return Some(err); + } + break; + } + _ => { + if let Err(err) = self.advance_token() { + return Some(err); + } + } } } } Some(&'*') => { let mut level = 1; self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } while let Some(c) = self.char_stream.next() { - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } match c { '/' => { if let Some('*') = self.char_stream.next() { level += 1; } - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } } '*' => { if let Some('/') = self.char_stream.next() { level -= 1; } - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } + } + '\n' => { + if let Err(err) = self.new_line_token() { + return Some(err); + } } - '\n' => self.new_line(), _ => (), } @@ -778,7 +891,9 @@ impl<'a> TokenIterator<'a> { } Some(&'=') => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } return Some((Token::DivideAssign, pos)); } _ => return Some((Token::Divide, pos)), @@ -790,7 +905,9 @@ impl<'a> TokenIterator<'a> { '=' => match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } return Some((Token::EqualsTo, pos)); } _ => return Some((Token::Equals, pos)), @@ -798,21 +915,29 @@ impl<'a> TokenIterator<'a> { '<' => match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } return Some((Token::LessThanEqualsTo, pos)); } Some(&'<') => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } return match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } Some((Token::LeftShiftAssign, pos)) } _ => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } Some((Token::LeftShift, pos)) } }; @@ -824,21 +949,29 @@ impl<'a> TokenIterator<'a> { match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } Token::GreaterThanEqualsTo } Some(&'>') => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } Token::RightShiftAssign } _ => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } Token::RightShift } } @@ -853,7 +986,9 @@ impl<'a> TokenIterator<'a> { match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } Token::NotEqualsTo } _ => Token::Bang, @@ -866,12 +1001,16 @@ impl<'a> TokenIterator<'a> { match self.char_stream.peek() { Some(&'|') => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } Token::Or } Some(&'=') => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } Token::OrAssign } _ => Token::Pipe, @@ -884,12 +1023,16 @@ impl<'a> TokenIterator<'a> { match self.char_stream.peek() { Some(&'&') => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } Token::And } Some(&'=') => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } Token::AndAssign } _ => Token::Ampersand, @@ -902,7 +1045,9 @@ impl<'a> TokenIterator<'a> { match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } Token::XOrAssign } _ => Token::XOr, @@ -915,7 +1060,9 @@ impl<'a> TokenIterator<'a> { match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } Token::ModuloAssign } _ => Token::Modulo, @@ -928,7 +1075,9 @@ impl<'a> TokenIterator<'a> { match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - self.advance(); + if let Err(err) = self.advance_token() { + return Some(err); + } Token::PowerOfAssign } _ => Token::PowerOf, @@ -959,7 +1108,7 @@ impl<'a> Iterator for TokenIterator<'a> { pub fn lex(input: &str) -> TokenIterator<'_> { TokenIterator { - last: Token::LexErr(LERR::Nothing), + last: Token::LexErr(LERR::InputError("".into())), pos: Position { line: 1, pos: 0 }, char_stream: input.chars().peekable(), } From a1591ae45b806e7cd0de6bade4c27b038344285c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 3 Mar 2020 15:20:20 +0800 Subject: [PATCH 03/24] Better API for Scope. --- README.md | 24 +++++++++-- src/api.rs | 3 +- src/builtin.rs | 5 --- src/engine.rs | 60 +++++++++----------------- src/lib.rs | 4 +- src/scope.rs | 103 +++++++++++++++++++++++++++++++++++++++++++++ tests/var_scope.rs | 35 +++++++++++++++ 7 files changed, 184 insertions(+), 50 deletions(-) create mode 100644 src/scope.rs diff --git a/README.md b/README.md index 6f9c78ae..290aa9fc 100644 --- a/README.md +++ b/README.md @@ -391,11 +391,11 @@ a.x.change(); // Only a COPY of 'a.x' is changed. 'a.x' is NOT changed. a.x == 500; ``` -# Maintaining state +# Initializing and maintaining state By default, Rhai treats each engine invocation as a fresh one, persisting only the functions that have been defined but no top-level state. This gives each one a fairly clean starting place. Sometimes, though, you want to continue using the same top-level state from one invocation to the next. -In this example, we thread the same state through multiple invocations: +In this example, we first create a state with a few initialized variables, then thread the same state through multiple invocations: ```rust extern crate rhai; @@ -403,13 +403,29 @@ use rhai::{Engine, Scope}; fn main() { let mut engine = Engine::new(); + + // First create the state let mut scope = Scope::new(); - if let Ok(_) = engine.eval_with_scope::<()>(&mut scope, "let x = 4 + 5") { } else { assert!(false); } + // 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); + // First invocation + engine.eval_with_scope::<()>(&mut scope, r" + let x = 4 + 5 - y + z; + y = 1; + ").expect("y and z not found?"); + + // Second invocation using the same state if let Ok(result) = engine.eval_with_scope::(&mut scope, "x") { - println!("result: {}", result); + println!("result: {}", result); // should print 966 } + + // Variable y is changed in the script + assert_eq!(scope.get_value::("y").unwrap(), 1); } ``` diff --git a/src/api.rs b/src/api.rs index f1c32155..916a0858 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,6 +1,7 @@ use crate::any::{Any, AnyExt, Dynamic}; -use crate::engine::{Engine, EvalAltResult, FnIntExt, FnSpec, Scope}; +use crate::engine::{Engine, EvalAltResult, FnIntExt, FnSpec}; use crate::parser::{lex, parse, ParseError, Position, AST}; +use crate::scope::Scope; use std::sync::Arc; impl Engine { diff --git a/src/builtin.rs b/src/builtin.rs index da23abe2..e8fb2f00 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -174,11 +174,6 @@ impl Engine { self.register_fn("+", concat); self.register_fn("==", unit_eq); - // self.register_fn("[]", idx); - // FIXME? Registering array lookups are a special case because we want to return boxes - // directly let ent = self.fns.entry("[]".to_string()).or_insert_with(Vec::new); - // (*ent).push(FnType::ExternalFn2(Box::new(idx))); - // Register conversion functions self.register_fn("to_float", |x: i8| x as f64); self.register_fn("to_float", |x: u8| x as f64); diff --git a/src/engine.rs b/src/engine.rs index 89544c16..750d548e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -8,6 +8,7 @@ use crate::any::{Any, AnyExt, Dynamic, Variant}; use crate::call::FunArgs; use crate::fn_register::RegisterFn; use crate::parser::{Expr, FnDef, ParseError, Position, Stmt}; +use crate::scope::Scope; pub type Array = Vec; pub type FnCallArgs<'a> = Vec<&'a mut Variant>; @@ -162,22 +163,6 @@ pub enum FnIntExt { pub type FnAny = dyn Fn(FnCallArgs, Position) -> Result; -/// A type containing information about current scope. -/// Useful for keeping state between `Engine` runs -/// -/// ```rust -/// use rhai::{Engine, Scope}; -/// -/// let mut engine = Engine::new(); -/// let mut my_scope = Scope::new(); -/// -/// assert!(engine.eval_with_scope::<()>(&mut my_scope, "let x = 5;").is_ok()); -/// assert_eq!(engine.eval_with_scope::(&mut my_scope, "x + 1").unwrap(), 6); -/// ``` -/// -/// Between runs, `Engine` only remembers functions when not using own `Scope`. -pub type Scope = Vec<(String, Dynamic)>; - impl Engine { pub fn call_fn<'a, I, A, T>(&self, ident: I, args: A) -> Result where @@ -185,7 +170,7 @@ impl Engine { A: FunArgs<'a>, T: Any + Clone, { - let pos = Position { line: 0, pos: 0 }; + let pos = Position::new(); self.call_fn_raw(ident.into(), args.into_vec(), None, pos) .and_then(|b| { @@ -438,19 +423,16 @@ impl Engine { } } - fn search_scope<'a, T>( - scope: &'a mut Scope, + fn search_scope( + scope: &Scope, id: &str, - map: impl FnOnce(&'a mut Variant) -> Result, + map: impl FnOnce(&Variant) -> Result, begin: Position, ) -> Result<(usize, T), EvalAltResult> { scope - .iter_mut() - .enumerate() - .rev() - .find(|&(_, &mut (ref name, _))| id == name) + .get(id) .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin)) - .and_then(move |(idx, &mut (_, ref mut val))| map(val.as_mut()).map(|val| (idx, val))) + .and_then(move |(idx, _, val)| map(val.as_ref()).map(|v| (idx, v))) } fn indexed_value( @@ -471,7 +453,7 @@ impl Engine { scope, id, |val| { - if let Some(arr) = (*val).downcast_mut() as Option<&mut Array> { + if let Some(arr) = (*val).downcast_ref() as Option<&Array> { is_array = true; if idx >= 0 { @@ -481,7 +463,7 @@ impl Engine { } else { Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, begin)) } - } else if let Some(s) = (*val).downcast_mut() as Option<&mut String> { + } else if let Some(s) = (*val).downcast_ref() as Option<&String> { is_array = false; if idx >= 0 { @@ -537,7 +519,7 @@ impl Engine { // In case the expression mutated `target`, we need to reassign it because // of the above `clone`. - scope[sc_idx].1 = target; + *scope.get_mut(id, sc_idx) = target; value } @@ -551,10 +533,10 @@ impl Engine { // of the above `clone`. if is_array { - scope[sc_idx].1.downcast_mut::().unwrap()[idx] = target; + scope.get_mut(id, sc_idx).downcast_mut::().unwrap()[idx] = target; } else { Self::str_replace_char( - scope[sc_idx].1.downcast_mut::().unwrap(), // Root is a string + scope.get_mut(id, sc_idx).downcast_mut::().unwrap(), // Root is a string idx, *target.downcast::().unwrap(), // Target should be a char ); @@ -617,7 +599,7 @@ impl Engine { // In case the expression mutated `target`, we need to reassign it because // of the above `clone`. - scope[sc_idx].1 = target; + *scope.get_mut(id, sc_idx) = target; value } @@ -630,10 +612,10 @@ impl Engine { // In case the expression mutated `target`, we need to reassign it because // of the above `clone`. if is_array { - scope[sc_idx].1.downcast_mut::().unwrap()[idx] = target; + scope.get_mut(id, sc_idx).downcast_mut::().unwrap()[idx] = target; } else { Self::str_replace_char( - scope[sc_idx].1.downcast_mut::().unwrap(), // Root is a string + scope.get_mut(id, sc_idx).downcast_mut::().unwrap(), // Root is a string idx, *target.downcast::().unwrap(), // Target should be a char ); @@ -871,11 +853,11 @@ impl Engine { let tid = Any::type_id(&*arr); if let Some(iter_fn) = self.type_iterators.get(&tid) { - scope.push((name.clone(), Box::new(()))); + scope.push(name.clone(), ()); let idx = scope.len() - 1; for a in iter_fn(&arr) { - scope[idx].1 = a; + *scope.get_mut(name, idx) = a; match self.eval_stmt(scope, body) { Err(EvalAltResult::LoopBreak) => break, @@ -883,7 +865,7 @@ impl Engine { _ => (), } } - scope.remove(idx); + scope.pop(); Ok(Box::new(())) } else { return Err(EvalAltResult::ErrorFor(expr.position())); @@ -901,10 +883,10 @@ impl Engine { Stmt::Let(name, init, _) => { if let Some(v) = init { - let i = self.eval_expr(scope, v)?; - scope.push((name.clone(), i)); + let val = self.eval_expr(scope, v)?; + scope.push_dynamic(name.clone(), val); } else { - scope.push((name.clone(), Box::new(()))); + scope.push(name.clone(), ()); } Ok(Box::new(())) } diff --git a/src/lib.rs b/src/lib.rs index a7ac4d8e..074abf23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,8 +46,10 @@ mod call; mod engine; mod fn_register; mod parser; +mod scope; pub use any::Dynamic; -pub use engine::{Array, Engine, EvalAltResult, Scope}; +pub use engine::{Array, Engine, EvalAltResult}; +pub use scope::Scope; pub use fn_register::{RegisterDynamicFn, RegisterFn}; pub use parser::{ParseError, ParseErrorType, AST}; diff --git a/src/scope.rs b/src/scope.rs new file mode 100644 index 00000000..16035f48 --- /dev/null +++ b/src/scope.rs @@ -0,0 +1,103 @@ +use crate::any::{Any, Dynamic}; + +/// A type containing information about current scope. +/// Useful for keeping state between `Engine` runs +/// +/// ```rust +/// use rhai::{Engine, Scope}; +/// +/// let mut engine = Engine::new(); +/// let mut my_scope = Scope::new(); +/// +/// assert!(engine.eval_with_scope::<()>(&mut my_scope, "let x = 5;").is_ok()); +/// assert_eq!(engine.eval_with_scope::(&mut my_scope, "x + 1").unwrap(), 6); +/// ``` +/// +/// Between runs, `Engine` only remembers functions when not using own `Scope`. + +pub struct Scope(Vec<(String, Dynamic)>); + +impl Scope { + /// Create a new Scope. + pub fn new() -> Self { + Self(Vec::new()) + } + + /// Empty the Scope. + pub fn clear(&mut self) { + self.0.clear(); + } + + /// Get the number of variables inside the Scope. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Add (push) a new variable to the Scope. + pub fn push(&mut self, key: String, value: T) { + self.0.push((key, 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)); + } + + /// Remove (pop) the last variable from the Scope. + pub fn pop(&mut self) -> Option<(String, Dynamic)> { + self.0.pop() + } + + /// Truncate (rewind) the Scope to a previous size. + pub fn rewind(&mut self, size: usize) { + self.0.truncate(size); + } + + /// Find a variable in the Scope, starting from the last. + pub fn get(&self, key: &str) -> Option<(usize, String, Dynamic)> { + self.0 + .iter() + .enumerate() + .rev() + .find(|(_, (n, _))| n == key) + .map(|(i, (n, v))| (i, n.clone(), v.clone())) + } + + /// Get the value of a variable in the Scope, starting from the last. + pub fn get_value(&self, key: &str) -> Option { + self.0 + .iter() + .enumerate() + .rev() + .find(|(_, (n, _))| n == key) + .map(|(_, (_, v))| v.downcast_ref() as Option<&T>) + .map(|v| v.unwrap().clone()) + } + + /// Get a mutable reference to a variable in the Scope. + pub(crate) fn get_mut(&mut self, key: &str, index: usize) -> &mut Dynamic { + let entry = self.0.get_mut(index).expect("invalid index in Scope"); + + if entry.0 != key { + panic!("incorrect key at Scope entry"); + } + + &mut entry.1 + } + + /// Get an iterator to variables in the Scope. + pub fn iter(&self) -> std::slice::Iter<(String, Dynamic)> { + self.0.iter() + } + + /// Get a mutable iterator to variables in the Scope. + pub(crate) fn iter_mut(&mut self) -> std::slice::IterMut<(String, Dynamic)> { + self.0.iter_mut() + } +} + +impl std::iter::Extend<(String, Dynamic)> for Scope { + fn extend>(&mut self, iter: T) { + self.0.extend(iter); + } +} diff --git a/tests/var_scope.rs b/tests/var_scope.rs index 6b378299..5522ba67 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -14,3 +14,38 @@ fn test_var_scope() -> Result<(), EvalAltResult> { Ok(()) } + +#[test] +fn test_scope_eval() -> Result<(), EvalAltResult> { + let mut engine = Engine::new(); + + // First create the state + let mut scope = Scope::new(); + + // 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); + + // First invocation + engine + .eval_with_scope::<()>( + &mut scope, + r" + let x = 4 + 5 - y + z; + y = 1; + ", + ) + .expect("y and z not found?"); + + // Second invocation using the same state + if let Ok(result) = engine.eval_with_scope::(&mut scope, "x") { + println!("result: {}", result); // should print 966 + } + + // Variable y is changed in the script + assert_eq!(scope.get_value::("y").unwrap(), 1); + + Ok(()) +} From 95d0b2e620d4846b5e8781c29feaf4b9e3eb5e89 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 3 Mar 2020 16:23:55 +0800 Subject: [PATCH 04/24] Better error messages for function definitions. --- README.md | 33 ++++- src/parser.rs | 351 +++++++++++++++++++++++++++++++------------------- 2 files changed, 250 insertions(+), 134 deletions(-) diff --git a/README.md b/README.md index 290aa9fc..2677e2ae 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Rhai's current feature set: * Support for overloaded functions * No additional dependencies -**Note:** Currently, the version is 0.10.0-alpha1, so the language and APIs may change before they stabilize.* +**Note:** Currently, the version is 0.10.1, so the language and APIs may change before they stabilize.* ## Installation @@ -19,7 +19,7 @@ You can install Rhai using crates by adding this line to your dependencies: ```toml [dependencies] -rhai = "0.10.0" +rhai = "0.10.1" ``` or simply: @@ -190,7 +190,7 @@ fn main() { } ``` -To return a `Dynamic` value, simply `Box` it and return it. +To return a [`Dynamic`] value, simply `Box` it and return it. ```rust fn decide(yes_no: bool) -> Dynamic { @@ -514,7 +514,7 @@ fn add(x, y) { return x + y; } -print(add(2, 3)) +print(add(2, 3)); ``` Just like in Rust, you can also use an implicit return. @@ -524,9 +524,29 @@ fn add(x, y) { x + y } -print(add(2, 3)) +print(add(2, 3)); ``` +Remember that functions defined in script always take [`Dynamic`] arguments (i.e. the arguments can be of any type). +Furthermore, functions can only be defined at the top level, never inside a block or another function. + +```rust +// Top level is OK +fn add(x, y) { + x + y +} + +// The following will not compile +fn do_addition(x) { + fn add_y(n) { // functions cannot be defined inside another function + n + y + } + + add_y(x) +} +``` + + ## Arrays You can create arrays of values, and then access them with numeric indices. @@ -740,3 +760,6 @@ my_str += 12345; my_str == "abcABC12345" ``` + + +[`Dynamic`]: #values-and-types diff --git a/src/parser.rs b/src/parser.rs index 3361994d..d0f0c580 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -25,7 +25,7 @@ impl Error for LexError { LERR::MalformedEscapeSequence => "Unexpected values in escape sequence", LERR::MalformedNumber => "Unexpected characters in number", LERR::MalformedChar => "Char constant not a single character", - LERR::InputError(_) => "Imput error", + LERR::InputError(_) => "Input error", } } } @@ -52,6 +52,7 @@ pub enum ParseErrorType { MalformedCallExpr, MalformedIndexExpr, VarExpectsIdentifier, + WrongFnDefinition, FnMissingName, FnMissingParams, } @@ -177,6 +178,7 @@ impl Error for ParseError { PERR::VarExpectsIdentifier => "Expecting name of a variable", PERR::FnMissingName => "Expecting name in function declaration", PERR::FnMissingParams => "Expecting parameters in function declaration", + PERR::WrongFnDefinition => "Function definitions must be at top level and cannot be inside a block or another function", } } @@ -330,6 +332,80 @@ pub enum Token { } impl Token { + pub fn syntax(&self) -> std::borrow::Cow<'static, 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(), + LexErr(ref err) => err.to_string().into(), + + ref token => (match token { + StringConst(_) => "string", + LeftBrace => "{", + RightBrace => "}", + LeftParen => "(", + RightParen => ")", + LeftBracket => "[", + RightBracket => "]", + Plus => "+", + UnaryPlus => "+", + Minus => "-", + UnaryMinus => "-", + Multiply => "*", + Divide => "/", + SemiColon => ";", + Colon => ":", + Comma => ",", + Period => ".", + Equals => "=", + True => "true", + False => "false", + Let => "let", + If => "if", + Else => "else", + While => "while", + Loop => "loop", + LessThan => "<", + GreaterThan => ">", + Bang => "!", + LessThanEqualsTo => "<=", + GreaterThanEqualsTo => ">=", + EqualsTo => "==", + NotEqualsTo => "!=", + Pipe => "|", + Or => "||", + Ampersand => "&", + And => "&&", + Fn => "fn", + Break => "break", + Return => "return", + PlusAssign => "+=", + MinusAssign => "-=", + MultiplyAssign => "*=", + DivideAssign => "/=", + LeftShiftAssign => "<<=", + RightShiftAssign => ">>=", + AndAssign => "&=", + OrAssign => "|=", + XOrAssign => "^=", + LeftShift => "<<", + RightShift => ">>", + XOr => "^", + Modulo => "%", + ModuloAssign => "%=", + PowerOf => "~", + PowerOfAssign => "~=", + For => "for", + In => "in", + _ => panic!(), + }) + .into(), + } + } + // if another operator is after these, it's probably an unary operator // not sure about fn's name pub fn is_next_unary(&self) -> bool { @@ -1240,21 +1316,20 @@ fn parse_array_expr<'a>( ) -> Result { let mut arr = Vec::new(); - let skip_contents = match input.peek() { - Some(&(Token::RightBracket, _)) => true, - _ => false, - }; + match input.peek() { + Some(&(Token::RightBracket, _)) => (), - if !skip_contents { - while let Some(_) = input.peek() { - arr.push(parse_expr(input)?); + _ => { + while input.peek().is_some() { + arr.push(parse_expr(input)?); - if let Some(&(Token::Comma, _)) = input.peek() { - input.next(); - } + if let Some(&(Token::Comma, _)) = input.peek() { + input.next(); + } - if let Some(&(Token::RightBracket, _)) = input.peek() { - break; + if let Some(&(Token::RightBracket, _)) = input.peek() { + break; + } } } } @@ -1271,34 +1346,27 @@ fn parse_array_expr<'a>( fn parse_primary<'a>(input: &mut Peekable>) -> Result { match input.next() { - Some((token, pos)) => match token { - Token::IntegerConstant(x) => Ok(Expr::IntegerConstant(x, pos)), - Token::FloatConstant(x) => Ok(Expr::FloatConstant(x, pos)), - Token::StringConst(s) => Ok(Expr::StringConstant(s, pos)), - Token::CharConstant(c) => Ok(Expr::CharConstant(c, pos)), - Token::Identifier(s) => parse_ident_expr(s, input, pos), - Token::LeftParen => parse_paren_expr(input, pos), - Token::LeftBracket => parse_array_expr(input, pos), - Token::True => Ok(Expr::True(pos)), - Token::False => Ok(Expr::False(pos)), - Token::LexErr(le) => Err(ParseError(PERR::BadInput(le.to_string()), pos)), - _ => Err(ParseError( - PERR::BadInput(format!("Unexpected {:?} token", token)), - pos, - )), - }, + Some((Token::IntegerConstant(x), pos)) => Ok(Expr::IntegerConstant(x, pos)), + Some((Token::FloatConstant(x), pos)) => Ok(Expr::FloatConstant(x, pos)), + Some((Token::StringConst(s), pos)) => Ok(Expr::StringConstant(s, pos)), + Some((Token::CharConstant(c), pos)) => Ok(Expr::CharConstant(c, pos)), + Some((Token::Identifier(s), pos)) => parse_ident_expr(s, input, pos), + Some((Token::LeftParen, pos)) => parse_paren_expr(input, pos), + Some((Token::LeftBracket, pos)) => parse_array_expr(input, pos), + Some((Token::True, pos)) => Ok(Expr::True(pos)), + Some((Token::False, pos)) => Ok(Expr::False(pos)), + Some((Token::LexErr(le), pos)) => Err(ParseError(PERR::BadInput(le.to_string()), pos)), + Some((token, pos)) => Err(ParseError( + PERR::BadInput(format!("Unexpected '{}'", token.syntax())), + pos, + )), None => Err(ParseError(PERR::InputPastEndOfFile, Position::eof())), } } fn parse_unary<'a>(input: &mut Peekable>) -> Result { - let (token, pos) = match input.peek() { - Some((tok, tok_pos)) => (tok.clone(), *tok_pos), - None => return Err(ParseError(PERR::InputPastEndOfFile, Position::eof())), - }; - - match token { - Token::UnaryMinus => { + match input.peek() { + Some(&(Token::UnaryMinus, pos)) => { input.next(); Ok(Expr::FunctionCall( @@ -1308,11 +1376,11 @@ fn parse_unary<'a>(input: &mut Peekable>) -> Result { + Some(&(Token::UnaryPlus, _)) => { input.next(); parse_primary(input) } - Token::Bang => { + Some(&(Token::Bang, pos)) => { input.next(); Ok(Expr::FunctionCall( @@ -1326,22 +1394,22 @@ fn parse_unary<'a>(input: &mut Peekable>) -> Result( +fn parse_binary_op<'a>( input: &mut Peekable>, - prec: i8, + precedence: i8, lhs: Expr, ) -> Result { - let mut lhs_curr = lhs; + let mut current_lhs = lhs; loop { - let mut curr_prec = -1; + let mut current_precedence = -1; - if let Some(&(ref curr_op, _)) = input.peek() { - curr_prec = get_precedence(curr_op); + if let Some(&(ref current_op, _)) = input.peek() { + current_precedence = get_precedence(current_op); } - if curr_prec < prec { - return Ok(lhs_curr); + if current_precedence < precedence { + return Ok(current_lhs); } if let Some((op_token, pos)) = input.next() { @@ -1349,30 +1417,32 @@ fn parse_binop<'a>( let mut rhs = parse_unary(input)?; - let mut next_prec = -1; + let mut next_precedence = -1; if let Some(&(ref next_op, _)) = input.peek() { - next_prec = get_precedence(next_op); + next_precedence = get_precedence(next_op); } - if curr_prec < next_prec { - rhs = parse_binop(input, curr_prec + 1, rhs)?; - } else if curr_prec >= 100 { + if current_precedence < next_precedence { + rhs = parse_binary_op(input, current_precedence + 1, rhs)?; + } else if current_precedence >= 100 { // Always bind right to left for precedence over 100 - rhs = parse_binop(input, curr_prec, rhs)?; + rhs = parse_binary_op(input, current_precedence, rhs)?; } - lhs_curr = match op_token { - Token::Plus => Expr::FunctionCall("+".into(), vec![lhs_curr, rhs], None, pos), - Token::Minus => Expr::FunctionCall("-".into(), vec![lhs_curr, rhs], None, pos), - Token::Multiply => Expr::FunctionCall("*".into(), vec![lhs_curr, rhs], None, pos), - Token::Divide => Expr::FunctionCall("/".into(), vec![lhs_curr, rhs], None, pos), + current_lhs = match op_token { + Token::Plus => Expr::FunctionCall("+".into(), vec![current_lhs, rhs], None, pos), + Token::Minus => Expr::FunctionCall("-".into(), vec![current_lhs, rhs], None, pos), + Token::Multiply => { + 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(lhs_curr), Box::new(rhs)), + Token::Equals => Expr::Assignment(Box::new(current_lhs), Box::new(rhs)), Token::PlusAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( "+".into(), vec![lhs_copy, rhs], @@ -1382,9 +1452,9 @@ fn parse_binop<'a>( ) } Token::MinusAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( "-".into(), vec![lhs_copy, rhs], @@ -1393,35 +1463,53 @@ fn parse_binop<'a>( )), ) } - Token::Period => Expr::Dot(Box::new(lhs_curr), Box::new(rhs)), + Token::Period => Expr::Dot(Box::new(current_lhs), Box::new(rhs)), // Comparison operators default to false when passed invalid operands - Token::EqualsTo => { - Expr::FunctionCall("==".into(), vec![lhs_curr, rhs], Some(Box::new(false)), pos) - } - Token::NotEqualsTo => { - Expr::FunctionCall("!=".into(), vec![lhs_curr, rhs], Some(Box::new(false)), pos) - } - Token::LessThan => { - Expr::FunctionCall("<".into(), vec![lhs_curr, rhs], Some(Box::new(false)), pos) - } - Token::LessThanEqualsTo => { - Expr::FunctionCall("<=".into(), vec![lhs_curr, rhs], Some(Box::new(false)), pos) - } - Token::GreaterThan => { - Expr::FunctionCall(">".into(), vec![lhs_curr, rhs], Some(Box::new(false)), pos) - } - Token::GreaterThanEqualsTo => { - Expr::FunctionCall(">=".into(), vec![lhs_curr, rhs], Some(Box::new(false)), pos) - } + Token::EqualsTo => Expr::FunctionCall( + "==".into(), + vec![current_lhs, rhs], + Some(Box::new(false)), + pos, + ), + Token::NotEqualsTo => Expr::FunctionCall( + "!=".into(), + vec![current_lhs, rhs], + Some(Box::new(false)), + pos, + ), + Token::LessThan => Expr::FunctionCall( + "<".into(), + vec![current_lhs, rhs], + Some(Box::new(false)), + pos, + ), + Token::LessThanEqualsTo => Expr::FunctionCall( + "<=".into(), + vec![current_lhs, rhs], + Some(Box::new(false)), + pos, + ), + Token::GreaterThan => Expr::FunctionCall( + ">".into(), + vec![current_lhs, rhs], + Some(Box::new(false)), + pos, + ), + Token::GreaterThanEqualsTo => Expr::FunctionCall( + ">=".into(), + vec![current_lhs, rhs], + Some(Box::new(false)), + pos, + ), - Token::Or => Expr::Or(Box::new(lhs_curr), Box::new(rhs)), - Token::And => Expr::And(Box::new(lhs_curr), Box::new(rhs)), - Token::XOr => Expr::FunctionCall("^".into(), vec![lhs_curr, rhs], None, pos), + Token::Or => Expr::Or(Box::new(current_lhs), Box::new(rhs)), + Token::And => Expr::And(Box::new(current_lhs), Box::new(rhs)), + Token::XOr => Expr::FunctionCall("^".into(), vec![current_lhs, rhs], None, pos), Token::OrAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( "|".into(), vec![lhs_copy, rhs], @@ -1431,9 +1519,9 @@ fn parse_binop<'a>( ) } Token::AndAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( "&".into(), vec![lhs_copy, rhs], @@ -1443,9 +1531,9 @@ fn parse_binop<'a>( ) } Token::XOrAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( "^".into(), vec![lhs_copy, rhs], @@ -1455,9 +1543,9 @@ fn parse_binop<'a>( ) } Token::MultiplyAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( "*".into(), vec![lhs_copy, rhs], @@ -1467,9 +1555,9 @@ fn parse_binop<'a>( ) } Token::DivideAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( "/".into(), vec![lhs_copy, rhs], @@ -1478,15 +1566,17 @@ fn parse_binop<'a>( )), ) } - Token::Pipe => Expr::FunctionCall("|".into(), vec![lhs_curr, rhs], None, pos), - Token::LeftShift => Expr::FunctionCall("<<".into(), vec![lhs_curr, rhs], None, pos), + Token::Pipe => Expr::FunctionCall("|".into(), vec![current_lhs, rhs], None, pos), + Token::LeftShift => { + Expr::FunctionCall("<<".into(), vec![current_lhs, rhs], None, pos) + } Token::RightShift => { - Expr::FunctionCall(">>".into(), vec![lhs_curr, rhs], None, pos) + Expr::FunctionCall(">>".into(), vec![current_lhs, rhs], None, pos) } Token::LeftShiftAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( "<<".into(), vec![lhs_copy, rhs], @@ -1496,9 +1586,9 @@ fn parse_binop<'a>( ) } Token::RightShiftAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( ">>".into(), vec![lhs_copy, rhs], @@ -1507,12 +1597,14 @@ fn parse_binop<'a>( )), ) } - Token::Ampersand => Expr::FunctionCall("&".into(), vec![lhs_curr, rhs], None, pos), - Token::Modulo => Expr::FunctionCall("%".into(), vec![lhs_curr, rhs], None, pos), + Token::Ampersand => { + Expr::FunctionCall("&".into(), vec![current_lhs, rhs], None, pos) + } + Token::Modulo => Expr::FunctionCall("%".into(), vec![current_lhs, rhs], None, pos), Token::ModuloAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( "%".into(), vec![lhs_copy, rhs], @@ -1521,11 +1613,11 @@ fn parse_binop<'a>( )), ) } - Token::PowerOf => Expr::FunctionCall("~".into(), vec![lhs_curr, rhs], None, pos), + Token::PowerOf => Expr::FunctionCall("~".into(), vec![current_lhs, rhs], None, pos), Token::PowerOfAssign => { - let lhs_copy = lhs_curr.clone(); + let lhs_copy = current_lhs.clone(); Expr::Assignment( - Box::new(lhs_curr), + Box::new(current_lhs), Box::new(Expr::FunctionCall( "~".into(), vec![lhs_copy, rhs], @@ -1542,7 +1634,7 @@ fn parse_binop<'a>( fn parse_expr<'a>(input: &mut Peekable>) -> Result { let lhs = parse_unary(input)?; - parse_binop(input, 0, lhs) + parse_binary_op(input, 0, lhs) } fn parse_if<'a>(input: &mut Peekable>) -> Result { @@ -1640,23 +1732,24 @@ fn parse_block<'a>(input: &mut Peekable>) -> Result true, - _ => false, - }; + match input.peek() { + Some(&(Token::RightBrace, _)) => (), // empty block + Some(&(Token::Fn, pos)) => return Err(ParseError(PERR::WrongFnDefinition, pos)), - if !skip_body { - while let Some(_) = input.peek() { - stmts.push(parse_stmt(input)?); + _ => { + while input.peek().is_some() { + // Parse statements inside the block + statements.push(parse_stmt(input)?); - if let Some(&(Token::SemiColon, _)) = input.peek() { - input.next(); - } + if let Some(&(Token::SemiColon, _)) = input.peek() { + input.next(); + } - if let Some(&(Token::RightBrace, _)) = input.peek() { - break; + if let Some(&(Token::RightBrace, _)) = input.peek() { + break; + } } } } @@ -1664,7 +1757,7 @@ fn parse_block<'a>(input: &mut Peekable>) -> Result { input.next(); - Ok(Stmt::Block(stmts)) + Ok(Stmt::Block(statements)) } Some(&(_, pos)) => Err(ParseError(PERR::MissingRightBrace, pos)), None => Err(ParseError(PERR::MissingRightBrace, Position::eof())), @@ -1757,13 +1850,13 @@ fn parse_fn<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { - let mut stmts = Vec::new(); - let mut fndefs = Vec::new(); + let mut statements = Vec::new(); + let mut functions = Vec::new(); - while let Some(_) = input.peek() { + while input.peek().is_some() { match input.peek() { - Some(&(Token::Fn, _)) => fndefs.push(parse_fn(input)?), - _ => stmts.push(parse_stmt(input)?), + Some(&(Token::Fn, _)) => functions.push(parse_fn(input)?), + _ => statements.push(parse_stmt(input)?), } if let Some(&(Token::SemiColon, _)) = input.peek() { @@ -1771,7 +1864,7 @@ fn parse_top_level<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { From 71ec23e6214640ca4e12f48fef672d28b9cfce11 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 3 Mar 2020 16:24:03 +0800 Subject: [PATCH 05/24] Minor code refactoring. --- src/any.rs | 6 ++--- src/api.rs | 20 ++++++++------ src/engine.rs | 67 +++++++++++++++++++++------------------------- src/fn_register.rs | 50 +++++++++++++++++----------------- 4 files changed, 71 insertions(+), 72 deletions(-) diff --git a/src/any.rs b/src/any.rs index c3c969cb..232423fd 100644 --- a/src/any.rs +++ b/src/any.rs @@ -7,7 +7,7 @@ pub type Dynamic = Box; pub trait Any: StdAny { fn type_id(&self) -> TypeId; - fn type_name(&self) -> String; + fn type_name(&self) -> &'static str; fn into_dynamic(&self) -> Dynamic; @@ -25,8 +25,8 @@ where TypeId::of::() } - fn type_name(&self) -> String { - type_name::().to_string() + fn type_name(&self) -> &'static str { + type_name::() } #[inline] diff --git a/src/api.rs b/src/api.rs index 916a0858..fadbe73b 100644 --- a/src/api.rs +++ b/src/api.rs @@ -88,15 +88,19 @@ impl Engine { self.script_fns.clear(); // Clean up engine match result { - Err(EvalAltResult::Return(out, pos)) => Ok(*out.downcast::().map_err(|a| { - let name = self.map_type_name((*a).type_name()); - EvalAltResult::ErrorMismatchOutputType(name, pos) - })?), + Err(EvalAltResult::Return(out, pos)) => out.downcast::().map(|v| *v).map_err(|a| { + EvalAltResult::ErrorMismatchOutputType( + self.map_type_name((*a).type_name()).to_string(), + pos, + ) + }), - Ok(out) => Ok(*out.downcast::().map_err(|a| { - let name = self.map_type_name((*a).type_name()); - EvalAltResult::ErrorMismatchOutputType(name, Position::eof()) - })?), + Ok(out) => out.downcast::().map(|v| *v).map_err(|a| { + EvalAltResult::ErrorMismatchOutputType( + self.map_type_name((*a).type_name()).to_string(), + Position::eof(), + ) + }), Err(err) => Err(err), } diff --git a/src/engine.rs b/src/engine.rs index 750d548e..2a876228 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -175,8 +175,10 @@ impl Engine { self.call_fn_raw(ident.into(), args.into_vec(), None, pos) .and_then(|b| { b.downcast().map(|b| *b).map_err(|a| { - let name = self.map_type_name((*a).type_name()); - EvalAltResult::ErrorMismatchOutputType(name, pos) + EvalAltResult::ErrorMismatchOutputType( + self.map_type_name((*a).type_name()).into(), + pos, + ) }) }) } @@ -191,17 +193,16 @@ impl Engine { pos: Position, ) -> Result { debug_println!( - "Trying to call function {:?} with args {:?}", + "Calling {}({})", ident, args.iter() - .map(|x| { self.map_type_name((**x).type_name()) }) + .map(|x| (*x).type_name()) + .map(|name| self.map_type_name(name)) .collect::>() + .join(", ") ); - let mut spec = FnSpec { - ident: ident.clone(), - args: None, - }; + let mut spec = FnSpec { ident, args: None }; // First search in script-defined functions (can override built-in), // then in built-in's @@ -219,7 +220,7 @@ impl Engine { return r; } - let callback = match ident.as_str() { + let callback = match spec.ident.as_str() { "print" => &self.on_print, "debug" => &self.on_debug, _ => return r, @@ -240,7 +241,7 @@ impl Engine { f.params .iter() .cloned() - .zip(args.iter().map(|x| (&**x).into_dynamic())), + .zip(args.iter().map(|x| (*x).into_dynamic())), ); match self.eval_stmt(&mut scope, &*f.body) { @@ -255,12 +256,12 @@ impl Engine { } else { let types_list = args .iter() - .map(|x| (*(&**x).into_dynamic()).type_name()) + .map(|x| (*x).type_name()) .map(|name| self.map_type_name(name)) .collect::>(); Err(EvalAltResult::ErrorFunctionNotFound( - format!("{} ({})", ident, types_list.join(", ")), + format!("{} ({})", spec.ident, types_list.join(", ")), pos, )) } @@ -363,7 +364,7 @@ impl Engine { let mut val = self.call_fn_raw(get_fn_name, vec![this_ptr], None, *pos)?; - if let Some(arr) = (*val).downcast_mut() as Option<&mut Array> { + if let Some(arr) = val.downcast_mut() as Option<&mut Array> { if idx >= 0 { arr.get(idx as usize) .cloned() @@ -371,7 +372,7 @@ impl Engine { } else { Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, *pos)) } - } else if let Some(s) = (*val).downcast_mut() as Option<&mut String> { + } else if let Some(s) = val.downcast_mut() as Option<&mut String> { if idx >= 0 { s.chars() .nth(idx as usize) @@ -453,7 +454,7 @@ impl Engine { scope, id, |val| { - if let Some(arr) = (*val).downcast_ref() as Option<&Array> { + if let Some(arr) = val.downcast_ref() as Option<&Array> { is_array = true; if idx >= 0 { @@ -463,7 +464,7 @@ impl Engine { } else { Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, begin)) } - } else if let Some(s) = (*val).downcast_ref() as Option<&String> { + } else if let Some(s) = val.downcast_ref() as Option<&String> { is_array = false; if idx >= 0 { @@ -636,11 +637,8 @@ impl Engine { Expr::CharConstant(c, _) => Ok(Box::new(*c)), Expr::Identifier(id, pos) => scope - .iter() - .rev() - .filter(|(name, _)| id == name) - .next() - .map(|(_, val)| val.clone()) + .get(id) + .map(|(_, _, val)| val) .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.clone(), *pos)), Expr::Index(id, idx_raw, pos) => self @@ -651,17 +649,14 @@ impl Engine { let rhs_val = self.eval_expr(scope, rhs)?; match **id { - Expr::Identifier(ref n, pos) => scope - .iter_mut() - .rev() - .filter(|(name, _)| n == name) - .next() - .map(|(_, val)| { - *val = rhs_val; - Box::new(()) as Dynamic - }) - .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(n.clone(), pos)), - + Expr::Identifier(ref name, pos) => { + if let Some((idx, _, _)) = scope.get(name) { + *scope.get_mut(name, idx) = rhs_val; + Ok(Box::new(()) as Dynamic) + } else { + Err(EvalAltResult::ErrorVariableNotFound(name.clone(), pos)) + } + } Expr::Index(ref id, ref idx_raw, pos) => { let idx_pos = idx_raw.position(); @@ -893,11 +888,11 @@ impl Engine { } } - pub(crate) fn map_type_name(&self, name: String) -> String { + pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str { self.type_names - .get(&name) - .map(|x| x.clone()) - .unwrap_or(name.to_string()) + .get(name) + .map(|s| s.as_str()) + .unwrap_or(name) } /// Make a new engine diff --git a/src/fn_register.rs b/src/fn_register.rs index f340f5bc..9fbd0529 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -39,20 +39,20 @@ macro_rules! def_register { const NUM_ARGS: usize = count_args!($($par)*); if args.len() != NUM_ARGS { - return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, pos)); + Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, pos)) + } else { + #[allow(unused_variables, unused_mut)] + let mut drain = args.drain(..); + $( + // Downcast every element, return in case of a type mismatch + let $par = (drain.next().unwrap().downcast_mut() as Option<&mut $par>).unwrap(); + )* + + // Call the user-supplied function using ($clone) to + // potentially clone the value, otherwise pass the reference. + let r = f($(($clone)($par)),*); + Ok(Box::new(r) as Dynamic) } - - #[allow(unused_variables, unused_mut)] - let mut drain = args.drain(..); - $( - // Downcast every element, return in case of a type mismatch - let $par = ((*drain.next().unwrap()).downcast_mut() as Option<&mut $par>).unwrap(); - )* - - // Call the user-supplied function using ($clone) to - // potentially clone the value, otherwise pass the reference. - let r = f($(($clone)($par)),*); - Ok(Box::new(r) as Dynamic) }; self.register_fn_raw(name.into(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); } @@ -72,19 +72,19 @@ macro_rules! def_register { const NUM_ARGS: usize = count_args!($($par)*); if args.len() != NUM_ARGS { - return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, pos)); + Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, pos)) + } else { + #[allow(unused_variables, unused_mut)] + let mut drain = args.drain(..); + $( + // Downcast every element, return in case of a type mismatch + let $par = (drain.next().unwrap().downcast_mut() as Option<&mut $par>).unwrap(); + )* + + // Call the user-supplied function using ($clone) to + // potentially clone the value, otherwise pass the reference. + Ok(f($(($clone)($par)),*)) } - - #[allow(unused_variables, unused_mut)] - let mut drain = args.drain(..); - $( - // Downcast every element, return in case of a type mismatch - let $par = ((*drain.next().unwrap()).downcast_mut() as Option<&mut $par>).unwrap(); - )* - - // Call the user-supplied function using ($clone) to - // potentially clone the value, otherwise pass the reference. - Ok(f($(($clone)($par)),*)) }; self.register_fn_raw(name.into(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); } From e2cb111e4b15db832c8649a7d8d2ee33a5521b72 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 3 Mar 2020 17:28:38 +0800 Subject: [PATCH 06/24] Add type_of function. --- README.md | 35 ++++++++++++++++++++---------- src/api.rs | 6 +++--- src/builtin.rs | 10 +++++---- src/engine.rs | 58 +++++++++++++++++++++++++++++--------------------- tests/types.rs | 17 +++++++++++++++ 5 files changed, 84 insertions(+), 42 deletions(-) create mode 100644 tests/types.rs diff --git a/README.md b/README.md index 2677e2ae..976ac0ae 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Rhai - embedded scripting for Rust +# Rhai - Embedded Scripting for Rust Rhai is an embedded scripting language for Rust that gives you a safe and easy way to add scripting to your applications. @@ -7,11 +7,11 @@ Rhai's current feature set: * Easy integration with Rust functions and data types * Fairly efficient (1 mil iterations in 0.75 sec on my 5 year old laptop) * Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app) -* Easy-to-use language based on JS+Rust +* Easy-to-use language similar to JS+Rust * Support for overloaded functions * No additional dependencies -**Note:** Currently, the version is 0.10.1, so the language and APIs may change before they stabilize.* +**Note:** Currently, the version is 0.10.1, so the language and API may change before they stabilize. ## Installation @@ -144,6 +144,8 @@ All types are treated strictly separate by Rhai, meaning that `i32` and `i64` an There is a `to_float` function to convert a supported number to an `f64`, and a `to_int` function to convert a supported number to `i64` and that's about it. For other conversions you can register your own conversion functions. +There is also a `type_of` function to detect the type of a value. + ```rust let x = 42; let y = x * 100.0; // error: cannot multiply i64 with f64 @@ -151,7 +153,16 @@ let y = x.to_float() * 100.0; // works let z = y.to_int() + x; // works let c = 'X'; // character -print("c is '" + c + "' and its code is " + c.to_int()); +print("c is '" + c + "' and its code is " + c.to_int()); // prints "c is 'X' and its code is 88" + +// Use 'type_of' to get the type of variables +type_of(c) == "char"; +type_of(x) == "i64"; +y.type_of() == "f64"; + +if z.type_of() == "string" { + do_something_with_strong(z); +} ``` # Working with functions @@ -190,7 +201,7 @@ fn main() { } ``` -To return a [`Dynamic`] value, simply `Box` it and return it. +To return a `Dynamic` value, simply `Box` it and return it. ```rust fn decide(yes_no: bool) -> Dynamic { @@ -335,6 +346,12 @@ if let Ok(result) = engine.eval::("let x = new_ts(); x.foo()") { } ``` +`type_of` works fine with custom types and returns the name of the type: + +```rust +let x = new_ts(); +print(x.type_of()); // prints "foo::bar::TestStruct" +``` # Getters and setters @@ -527,7 +544,7 @@ fn add(x, y) { print(add(2, 3)); ``` -Remember that functions defined in script always take [`Dynamic`] arguments (i.e. the arguments can be of any type). +Remember that functions defined in script always take `Dynamic` arguments (i.e. the arguments can be of any type). Furthermore, functions can only be defined at the top level, never inside a block or another function. ```rust @@ -546,7 +563,6 @@ fn do_addition(x) { } ``` - ## Arrays You can create arrays of values, and then access them with numeric indices. @@ -606,7 +622,7 @@ engine.register_fn("push", ); ``` -The type of a Rhai array is `rhai::Array`. +The type of a Rhai array is `rhai::Array`. `type_of()` returns `"array"`. ## For loops @@ -760,6 +776,3 @@ my_str += 12345; my_str == "abcABC12345" ``` - - -[`Dynamic`]: #values-and-types diff --git a/src/api.rs b/src/api.rs index fadbe73b..062d28cc 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,4 +1,4 @@ -use crate::any::{Any, AnyExt, Dynamic}; +use crate::any::{Any, AnyExt}; use crate::engine::{Engine, EvalAltResult, FnIntExt, FnSpec}; use crate::parser::{lex, parse, ParseError, Position, AST}; use crate::scope::Scope; @@ -83,7 +83,7 @@ impl Engine { let result = os .iter() - .try_fold(Box::new(()) as Dynamic, |_, o| self.eval_stmt(scope, o)); + .try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o)); self.script_fns.clear(); // Clean up engine @@ -160,7 +160,7 @@ impl Engine { let val = os .iter() - .try_fold(Box::new(()) as Dynamic, |_, o| self.eval_stmt(scope, o)) + .try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o)) .map(|_| ()); self.script_fns.clear(); // Clean up engine diff --git a/src/builtin.rs b/src/builtin.rs index e8fb2f00..538a6f5b 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -1,4 +1,4 @@ -use crate::{any::Any, Array, Dynamic, Engine, RegisterDynamicFn, RegisterFn}; +use crate::{any::Any, Array, Engine, RegisterDynamicFn, RegisterFn}; use std::fmt::{Debug, Display}; use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub}; @@ -239,12 +239,14 @@ impl Engine { reg_func3!(self, "pad", pad, &mut Array, i64, (), bool, char); reg_func3!(self, "pad", pad, &mut Array, i64, (), String, Array, ()); - self.register_dynamic_fn("pop", |list: &mut Array| list.pop().unwrap_or(Box::new(()))); + self.register_dynamic_fn("pop", |list: &mut Array| { + list.pop().unwrap_or(().into_dynamic()) + }); self.register_dynamic_fn("shift", |list: &mut Array| { if list.len() > 0 { list.remove(0) } else { - Box::new(()) + ().into_dynamic() } }); self.register_fn("len", |list: &mut Array| list.len() as i64); @@ -322,7 +324,7 @@ impl Engine { a.downcast_ref::>() .unwrap() .clone() - .map(|n| Box::new(n) as Dynamic), + .map(|n| n.into_dynamic()), ) }); diff --git a/src/engine.rs b/src/engine.rs index 2a876228..bccd33a7 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -13,6 +13,10 @@ use crate::scope::Scope; pub type Array = Vec; pub type FnCallArgs<'a> = Vec<&'a mut Variant>; +const KEYWORD_PRINT: &'static str = "print"; +const KEYWORD_DEBUG: &'static str = "debug"; +const KEYWORD_TYPE_OF: &'static str = "type_of"; + #[derive(Debug)] pub enum EvalAltResult { ErrorParsing(ParseError), @@ -221,18 +225,19 @@ impl Engine { } let callback = match spec.ident.as_str() { - "print" => &self.on_print, - "debug" => &self.on_debug, + KEYWORD_PRINT => &self.on_print, + KEYWORD_DEBUG => &self.on_debug, _ => return r, }; - Ok(Box::new(callback( + Ok(callback( r.unwrap() .downcast::() .map(|x| *x) .unwrap_or("error: not a string".into()) .as_str(), - ))) + ) + .into_dynamic()) } FnIntExt::Int(ref f) => { let mut scope = Scope::new(); @@ -250,6 +255,11 @@ impl Engine { } } } + } else if spec.ident == KEYWORD_TYPE_OF && args.len() == 1 { + Ok(self + .map_type_name(args[0].type_name()) + .to_string() + .into_dynamic()) } else if let Some(val) = def_value { // Return default value Ok(val.clone()) @@ -376,7 +386,7 @@ impl Engine { if idx >= 0 { s.chars() .nth(idx as usize) - .map(|ch| Box::new(ch) as Dynamic) + .map(|ch| ch.into_dynamic()) .ok_or_else(|| { EvalAltResult::ErrorStringBounds(s.chars().count(), idx, *pos) }) @@ -470,7 +480,7 @@ impl Engine { if idx >= 0 { s.chars() .nth(idx as usize) - .map(|ch| Box::new(ch) as Dynamic) + .map(|ch| ch.into_dynamic()) .ok_or_else(|| { EvalAltResult::ErrorStringBounds(s.chars().count(), idx, begin) }) @@ -631,10 +641,10 @@ impl Engine { fn eval_expr(&self, scope: &mut Scope, expr: &Expr) -> Result { match expr { - Expr::IntegerConstant(i, _) => Ok(Box::new(*i)), - Expr::FloatConstant(i, _) => Ok(Box::new(*i)), - Expr::StringConstant(s, _) => Ok(Box::new(s.clone())), - Expr::CharConstant(c, _) => Ok(Box::new(*c)), + Expr::IntegerConstant(i, _) => Ok((*i).into_dynamic()), + Expr::FloatConstant(i, _) => Ok((*i).into_dynamic()), + Expr::StringConstant(s, _) => Ok(s.into_dynamic()), + Expr::CharConstant(c, _) => Ok((*c).into_dynamic()), Expr::Identifier(id, pos) => scope .get(id) @@ -652,7 +662,7 @@ impl Engine { Expr::Identifier(ref name, pos) => { if let Some((idx, _, _)) = scope.get(name) { *scope.get_mut(name, idx) = rhs_val; - Ok(Box::new(()) as Dynamic) + Ok(().into_dynamic()) } else { Err(EvalAltResult::ErrorVariableNotFound(name.clone(), pos)) } @@ -684,7 +694,7 @@ impl Engine { Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos)) } else { arr[idx as usize] = rhs_val; - Ok(Box::new(())) + Ok(().into_dynamic()) } } else if let Some(s) = val.downcast_mut() as Option<&mut String> { let s_len = s.chars().count(); @@ -699,7 +709,7 @@ impl Engine { idx as usize, *rhs_val.downcast::().unwrap(), ); - Ok(Box::new(())) + Ok(().into_dynamic()) } } else { Err(EvalAltResult::ErrorIndexExpr(idx_pos)) @@ -770,9 +780,9 @@ impl Engine { })?, )), - Expr::True(_) => Ok(Box::new(true)), - Expr::False(_) => Ok(Box::new(false)), - Expr::Unit(_) => Ok(Box::new(())), + Expr::True(_) => Ok(true.into_dynamic()), + Expr::False(_) => Ok(false.into_dynamic()), + Expr::Unit(_) => Ok(().into_dynamic()), } } @@ -786,7 +796,7 @@ impl Engine { Stmt::Block(block) => { let prev_len = scope.len(); - let mut last_result: Result = Ok(Box::new(())); + let mut last_result: Result = Ok(().into_dynamic()); for block_stmt in block.iter() { last_result = self.eval_stmt(scope, block_stmt); @@ -814,7 +824,7 @@ impl Engine { } else if else_body.is_some() { self.eval_stmt(scope, else_body.as_ref().unwrap()) } else { - Ok(Box::new(())) + Ok(().into_dynamic()) } }), @@ -823,12 +833,12 @@ impl Engine { Ok(guard_val) => { if *guard_val { match self.eval_stmt(scope, body) { - Err(EvalAltResult::LoopBreak) => return Ok(Box::new(())), + Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()), Err(x) => return Err(x), _ => (), } } else { - return Ok(Box::new(())); + return Ok(().into_dynamic()); } } Err(_) => return Err(EvalAltResult::ErrorIfGuard(guard.position())), @@ -837,7 +847,7 @@ impl Engine { Stmt::Loop(body) => loop { match self.eval_stmt(scope, body) { - Err(EvalAltResult::LoopBreak) => return Ok(Box::new(())), + Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()), Err(x) => return Err(x), _ => (), } @@ -861,7 +871,7 @@ impl Engine { } } scope.pop(); - Ok(Box::new(())) + Ok(().into_dynamic()) } else { return Err(EvalAltResult::ErrorFor(expr.position())); } @@ -869,7 +879,7 @@ impl Engine { Stmt::Break(_) => Err(EvalAltResult::LoopBreak), - Stmt::Return(pos) => Err(EvalAltResult::Return(Box::new(()), *pos)), + Stmt::Return(pos) => Err(EvalAltResult::Return(().into_dynamic(), *pos)), Stmt::ReturnWithVal(a, pos) => { let result = self.eval_expr(scope, a)?; @@ -883,7 +893,7 @@ impl Engine { } else { scope.push(name.clone(), ()); } - Ok(Box::new(())) + Ok(().into_dynamic()) } } } diff --git a/tests/types.rs b/tests/types.rs new file mode 100644 index 00000000..1cf303ad --- /dev/null +++ b/tests/types.rs @@ -0,0 +1,17 @@ +use rhai::{Engine, EvalAltResult}; + +#[test] +fn test_type_of() -> Result<(), EvalAltResult> { + let mut engine = Engine::new(); + + assert_eq!(engine.eval::("type_of(60 + 5)")?, "i64"); + assert_eq!(engine.eval::("type_of(1.0 + 2.0)")?, "f64"); + assert_eq!( + engine.eval::(r#"type_of([1.0, 2, "hello"])"#)?, + "array" + ); + assert_eq!(engine.eval::(r#"type_of("hello")"#)?, "string"); + assert_eq!(engine.eval::("let x = 123; x.type_of()")?, "i64"); + + Ok(()) +} From 9f80bf03c4aab2a4fa341d94577b650b77106ec9 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 3 Mar 2020 18:15:20 +0800 Subject: [PATCH 07/24] Add throw. --- README.md | 34 ++++++++++++++++++++++++++++++++++ src/engine.rs | 32 ++++++++++++++++++++++++++++---- src/parser.rs | 29 ++++++++++++++++++++++------- tests/throw.rs | 18 ++++++++++++++++++ 4 files changed, 102 insertions(+), 11 deletions(-) create mode 100644 tests/throw.rs diff --git a/README.md b/README.md index 976ac0ae..f1c4aa93 100644 --- a/README.md +++ b/README.md @@ -563,6 +563,40 @@ fn do_addition(x) { } ``` +## Return + +```rust +return; + +return 123 + 456; +``` + +## Errors and Exceptions + +```rust +if error != "" { + throw error; // `throw` takes a string to form the exception text +} + +throw; // no exception text +``` + +All of `Engine`'s evaluation/consuming methods return `Result` with `EvalAltResult` holding error information. + +Exceptions thrown via `throw` in the script can be captured by matching `Err(EvalAltResult::ErrorRuntime(reason, position))` with the exception text captured by the `reason` parameter. + +```rust +let result = engine.eval::(&mut scope, r#" + let x = 42; + + if x > 0 { + throw x + " is too large!"; + } +"#); + +println!(result); // prints "Runtime error: 42 is too large! (line 5, position 15)" +``` + ## Arrays You can create arrays of values, and then access them with numeric indices. diff --git a/src/engine.rs b/src/engine.rs index bccd33a7..8388f2e5 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -35,6 +35,7 @@ pub enum EvalAltResult { ErrorCantOpenScriptFile(String, std::io::Error), ErrorDotExpr(Position), ErrorArithmetic(String, Position), + ErrorRuntime(String, Position), LoopBreak, Return(Dynamic, Position), } @@ -70,6 +71,7 @@ impl Error for EvalAltResult { Self::ErrorCantOpenScriptFile(_, _) => "Cannot open script file", Self::ErrorDotExpr(_) => "Malformed dot expression", Self::ErrorArithmetic(_, _) => "Arithmetic error", + Self::ErrorRuntime(_, _) => "Runtime error", Self::LoopBreak => "[Not Error] Breaks out of loop", Self::Return(_, _) => "[Not Error] Function returns value", } @@ -95,6 +97,8 @@ impl std::fmt::Display for EvalAltResult { Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos), Self::ErrorDotExpr(pos) => write!(f, "{} ({})", desc, pos), Self::ErrorArithmetic(s, pos) => write!(f, "{}: {} ({})", desc, s, pos), + Self::ErrorRuntime(s, pos) if s.is_empty() => write!(f, "{} ({})", desc, pos), + Self::ErrorRuntime(s, pos) => write!(f, "{}: {} ({})", desc, s, pos), Self::LoopBreak => write!(f, "{}", desc), Self::Return(_, pos) => write!(f, "{} ({})", desc, pos), Self::ErrorCantOpenScriptFile(filename, err) => { @@ -879,11 +883,31 @@ impl Engine { Stmt::Break(_) => Err(EvalAltResult::LoopBreak), - Stmt::Return(pos) => Err(EvalAltResult::Return(().into_dynamic(), *pos)), + // Empty return + Stmt::ReturnWithVal(None, true, pos) => { + Err(EvalAltResult::Return(().into_dynamic(), *pos)) + } - Stmt::ReturnWithVal(a, pos) => { - let result = self.eval_expr(scope, a)?; - Err(EvalAltResult::Return(result, *pos)) + // Return value + Stmt::ReturnWithVal(Some(a), true, pos) => { + Err(EvalAltResult::Return(self.eval_expr(scope, a)?, *pos)) + } + + // Empty throw + Stmt::ReturnWithVal(None, false, pos) => { + Err(EvalAltResult::ErrorRuntime("".into(), *pos)) + } + + // Throw value + Stmt::ReturnWithVal(Some(a), false, pos) => { + let val = self.eval_expr(scope, a)?; + Err(EvalAltResult::ErrorRuntime( + (val.downcast_ref() as Option<&String>) + .map(|s| s.as_ref()) + .unwrap_or("") + .to_string(), + *pos, + )) } Stmt::Let(name, init, _) => { diff --git a/src/parser.rs b/src/parser.rs index d0f0c580..228ecc70 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -222,8 +222,7 @@ pub enum Stmt { Block(Vec), Expr(Box), Break(Position), - Return(Position), - ReturnWithVal(Box, Position), + ReturnWithVal(Option>, bool, Position), } #[derive(Debug, Clone)] @@ -310,6 +309,7 @@ pub enum Token { Fn, Break, Return, + Throw, PlusAssign, MinusAssign, MultiplyAssign, @@ -382,6 +382,7 @@ impl Token { Fn => "fn", Break => "break", Return => "return", + Throw => "throw", PlusAssign => "+=", MinusAssign => "-=", MultiplyAssign => "*=", @@ -456,6 +457,7 @@ impl Token { Modulo | ModuloAssign | Return | + Throw | PowerOf | In | PowerOfAssign => true, @@ -498,7 +500,7 @@ impl Token { use self::Token::*; match *self { - UnaryPlus | UnaryMinus | Equals | Bang | Return => true, + UnaryPlus | UnaryMinus | Equals | Bang | Return | Throw => true, _ => false, } } @@ -816,6 +818,7 @@ impl<'a> TokenIterator<'a> { "loop" => Token::Loop, "break" => Token::Break, "return" => Token::Return, + "throw" => Token::Throw, "fn" => Token::Fn, "for" => Token::For, "in" => Token::In, @@ -1743,6 +1746,7 @@ fn parse_block<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { + Some(&(ref token @ Token::Return, _)) | Some(&(ref token @ Token::Throw, _)) => { + let is_return = match token { + Token::Return => true, + Token::Throw => false, + _ => panic!(), + }; + input.next(); + match input.peek() { - Some(&(Token::SemiColon, pos)) => Ok(Stmt::Return(pos)), + // return; or throw; + Some(&(Token::SemiColon, pos)) => Ok(Stmt::ReturnWithVal(None, is_return, pos)), + // Just a return/throw without anything at the end of script + None => Ok(Stmt::ReturnWithVal(None, is_return, Position::eof())), + // return or throw with expression Some(&(_, pos)) => { let ret = parse_expr(input)?; - Ok(Stmt::ReturnWithVal(Box::new(ret), pos)) + Ok(Stmt::ReturnWithVal(Some(Box::new(ret)), is_return, pos)) } - _ => parse_expr_stmt(input), } } Some(&(Token::LeftBrace, _)) => parse_block(input), @@ -1859,6 +1873,7 @@ fn parse_top_level<'a>(input: &mut Peekable>) -> Result statements.push(parse_stmt(input)?), } + // Notice semicolons are optional if let Some(&(Token::SemiColon, _)) = input.peek() { input.next(); } diff --git a/tests/throw.rs b/tests/throw.rs new file mode 100644 index 00000000..ca14cb3f --- /dev/null +++ b/tests/throw.rs @@ -0,0 +1,18 @@ +use rhai::{Engine, EvalAltResult}; + +#[test] +fn test_throw() { + let mut engine = Engine::new(); + + match engine.eval::(r#"if true { throw "hello" }"#) { + Ok(_) => panic!("not an error"), + Err(EvalAltResult::ErrorRuntime(s, _)) if s == "hello" => (), + Err(err) => panic!("wrong error: {}", err), + } + + match engine.eval::(r#"throw;"#) { + Ok(_) => panic!("not an error"), + Err(EvalAltResult::ErrorRuntime(s, _)) if s == "" => (), + Err(err) => panic!("wrong error: {}", err), + } +} From 71a3c7991546b8d07e44cee30d7831ae201c1f10 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 3 Mar 2020 21:39:25 +0800 Subject: [PATCH 08/24] Fix number parsing. --- README.md | 28 ++++++++-- src/lib.rs | 23 ++++++-- src/parser.rs | 139 +++++++++++++++++++++++++++--------------------- tests/string.rs | 12 ++++- 4 files changed, 133 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index f1c4aa93..20fb8d1e 100644 --- a/README.md +++ b/README.md @@ -683,12 +683,29 @@ a.x = 500; a.update(); ``` +## Numbers + +```rust +let x = 123; // i64 +let x = 123.4; // f64 +let x = 123_456_789; // separators can be put anywhere inside the number + +let x = 0x12abcd; // i64 in hex +let x = 0o777; // i64 in oct +let x = 0b1010_1111; // i64 in binary +``` + +Conversion functions: + +* `to_int` - converts an `f32` or `f64` to `i64` +* `to_float` - converts an integer type to `f64` + ## Strings and Chars ```rust let name = "Bob"; let middle_initial = 'C'; -let last = 'Davis'; +let last = "Davis"; let full_name = name + " " + middle_initial + ". " + last; full_name == "Bob C. Davis"; @@ -706,9 +723,13 @@ let c = "foo"[0]; // a syntax error for now - cannot index into literals let c = ts.s[0]; // a syntax error for now - cannot index into properties let c = record[0]; // this works +// Escape sequences in strings +record += " \u2764\n"; // escape sequence of '❤' in Unicode +record == "Bob C. Davis: age 42 ❤\n"; // '\n' = new-line + // Unlike Rust, Rhai strings can be modified -record[4] = 'Z'; -record == "Bob Z. Davis: age 42"; +record[4] = '\x58'; // 0x58 = 'X' +record == "Bob X. Davis: age 42 ❤\n"; ``` The following standard functions operate on strings: @@ -727,6 +748,7 @@ full_name.len() == 14; full_name.trim(); full_name.len() == 12; +full_name == "Bob C. Davis"; full_name.pad(15, '$'); full_name.len() == 15; diff --git a/src/lib.rs b/src/lib.rs index 074abf23..7ceebfd8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,9 +34,24 @@ // needs to be here, because order matters for macros macro_rules! debug_println { - () => (#[cfg(feature = "debug_msgs")] {print!("\n")}); - ($fmt:expr) => (#[cfg(feature = "debug_msgs")] {print!(concat!($fmt, "\n"))}); - ($fmt:expr, $($arg:tt)*) => (#[cfg(feature = "debug_msgs")] {print!(concat!($fmt, "\n"), $($arg)*)}); + () => ( + #[cfg(feature = "debug_msgs")] + { + print!("\n"); + } + ); + ($fmt:expr) => ( + #[cfg(feature = "debug_msgs")] + { + print!(concat!($fmt, "\n")); + } + ); + ($fmt:expr, $($arg:tt)*) => ( + #[cfg(feature = "debug_msgs")] + { + print!(concat!($fmt, "\n"), $($arg)*); + } + ); } mod any; @@ -50,6 +65,6 @@ mod scope; pub use any::Dynamic; pub use engine::{Array, Engine, EvalAltResult}; -pub use scope::Scope; pub use fn_register::{RegisterDynamicFn, RegisterFn}; pub use parser::{ParseError, ParseErrorType, AST}; +pub use scope::Scope; diff --git a/src/parser.rs b/src/parser.rs index 228ecc70..f4890997 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -9,9 +9,9 @@ use std::str::Chars; pub enum LexError { UnexpectedChar(char), UnterminatedString, - MalformedEscapeSequence, - MalformedNumber, - MalformedChar, + MalformedEscapeSequence(String), + MalformedNumber(String), + MalformedChar(String), InputError(String), } @@ -22,9 +22,9 @@ impl Error for LexError { match *self { LERR::UnexpectedChar(_) => "Unexpected character", LERR::UnterminatedString => "Open string is not terminated", - LERR::MalformedEscapeSequence => "Unexpected values in escape sequence", - LERR::MalformedNumber => "Unexpected characters in number", - LERR::MalformedChar => "Char constant not a single character", + LERR::MalformedEscapeSequence(_) => "Unexpected values in escape sequence", + LERR::MalformedNumber(_) => "Unexpected characters in number", + LERR::MalformedChar(_) => "Char constant not a single character", LERR::InputError(_) => "Input error", } } @@ -34,6 +34,9 @@ impl fmt::Display for LexError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { LERR::UnexpectedChar(c) => write!(f, "Unexpected '{}'", c), + LERR::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{}'", s), + LERR::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s), + LERR::MalformedChar(s) => write!(f, "Invalid character: '{}'", s), LERR::InputError(s) => write!(f, "{}", s), _ => write!(f, "{}", self.description()), } @@ -535,7 +538,7 @@ impl<'a> TokenIterator<'a> { enclosing_char: char, ) -> Result { let mut result = Vec::new(); - let mut escape = false; + let mut escape = String::with_capacity(12); loop { let next_char = self.char_stream.next(); @@ -547,107 +550,123 @@ impl<'a> TokenIterator<'a> { self.advance()?; match next_char.unwrap() { - '\\' if !escape => escape = true, - '\\' if escape => { - escape = false; + '\\' if escape.is_empty() => { + escape.push('\\'); + } + '\\' if !escape.is_empty() => { + escape.clear(); result.push('\\'); } - 't' if escape => { - escape = false; + 't' if !escape.is_empty() => { + escape.clear(); result.push('\t'); } - 'n' if escape => { - escape = false; + 'n' if !escape.is_empty() => { + escape.clear(); result.push('\n'); } - 'r' if escape => { - escape = false; + 'r' if !escape.is_empty() => { + escape.clear(); result.push('\r'); } - 'x' if escape => { - escape = false; + 'x' if !escape.is_empty() => { + let mut seq = escape.clone(); + seq.push('x'); + escape.clear(); let mut out_val: u32 = 0; for _ in 0..2 { if let Some(c) = self.char_stream.next() { + seq.push(c); + self.advance()?; + if let Some(d1) = c.to_digit(16) { out_val *= 16; out_val += d1; } else { - return Err((LERR::MalformedEscapeSequence, self.pos)); + return Err((LERR::MalformedEscapeSequence(seq), self.pos)); } } else { - return Err((LERR::MalformedEscapeSequence, self.pos)); + return Err((LERR::MalformedEscapeSequence(seq), self.pos)); } - self.advance()?; } if let Some(r) = char::from_u32(out_val) { result.push(r); } else { - return Err((LERR::MalformedEscapeSequence, self.pos)); + return Err((LERR::MalformedEscapeSequence(seq), self.pos)); } } - 'u' if escape => { - escape = false; + 'u' if !escape.is_empty() => { + let mut seq = escape.clone(); + seq.push('u'); + escape.clear(); let mut out_val: u32 = 0; for _ in 0..4 { if let Some(c) = self.char_stream.next() { + seq.push(c); + self.advance()?; + if let Some(d1) = c.to_digit(16) { out_val *= 16; out_val += d1; } else { - return Err((LERR::MalformedEscapeSequence, self.pos)); + return Err((LERR::MalformedEscapeSequence(seq), self.pos)); } } else { - return Err((LERR::MalformedEscapeSequence, self.pos)); + return Err((LERR::MalformedEscapeSequence(seq), self.pos)); } - self.advance()?; } if let Some(r) = char::from_u32(out_val) { result.push(r); } else { - return Err((LERR::MalformedEscapeSequence, self.pos)); + return Err((LERR::MalformedEscapeSequence(seq), self.pos)); } } - 'U' if escape => { - escape = false; + 'U' if !escape.is_empty() => { + let mut seq = escape.clone(); + seq.push('U'); + escape.clear(); let mut out_val: u32 = 0; for _ in 0..8 { if let Some(c) = self.char_stream.next() { + seq.push(c); + self.advance()?; + if let Some(d1) = c.to_digit(16) { out_val *= 16; out_val += d1; } else { - return Err((LERR::MalformedEscapeSequence, self.pos)); + return Err((LERR::MalformedEscapeSequence(seq), self.pos)); } } else { - return Err((LERR::MalformedEscapeSequence, self.pos)); + return Err((LERR::MalformedEscapeSequence(seq), self.pos)); } - self.advance()?; } if let Some(r) = char::from_u32(out_val) { result.push(r); } else { - return Err((LERR::MalformedEscapeSequence, self.pos)); + return Err((LERR::MalformedEscapeSequence(seq), self.pos)); } } - x if enclosing_char == x && escape => result.push(x), - x if enclosing_char == x && !escape => break, - _ if escape => return Err((LERR::MalformedEscapeSequence, self.pos)), + x if enclosing_char == x && !escape.is_empty() => result.push(x), + x if enclosing_char == x && escape.is_empty() => break, + _ if !escape.is_empty() => { + return Err((LERR::MalformedEscapeSequence(escape), self.pos)) + } '\n' => { self.rewind()?; return Err((LERR::UnterminatedString, self.pos)); } x => { - escape = false; + escape.clear(); result.push(x); } } } - let out: String = result.iter().cloned().collect(); + let out: String = result.iter().collect(); Ok(out) } @@ -672,7 +691,7 @@ impl<'a> TokenIterator<'a> { while let Some(&next_char) = self.char_stream.peek() { match next_char { - '0'..='9' => { + '0'..='9' | '_' => { result.push(next_char); self.char_stream.next(); if let Err(err) = self.advance_token() { @@ -687,7 +706,7 @@ impl<'a> TokenIterator<'a> { } while let Some(&next_char_in_float) = self.char_stream.peek() { match next_char_in_float { - '0'..='9' => { + '0'..='9' | '_' => { result.push(next_char_in_float); self.char_stream.next(); if let Err(err) = self.advance_token() { @@ -698,7 +717,7 @@ impl<'a> TokenIterator<'a> { } } } - 'x' | 'X' => { + 'x' | 'X' if c == '0' => { result.push(next_char); self.char_stream.next(); if let Err(err) = self.advance_token() { @@ -706,7 +725,7 @@ impl<'a> TokenIterator<'a> { } while let Some(&next_char_in_hex) = self.char_stream.peek() { match next_char_in_hex { - '0'..='9' | 'a'..='f' | 'A'..='F' => { + '0'..='9' | 'a'..='f' | 'A'..='F' | '_' => { result.push(next_char_in_hex); self.char_stream.next(); if let Err(err) = self.advance_token() { @@ -718,7 +737,7 @@ impl<'a> TokenIterator<'a> { } radix_base = Some(16); } - 'o' | 'O' => { + 'o' | 'O' if c == '0' => { result.push(next_char); self.char_stream.next(); if let Err(err) = self.advance_token() { @@ -726,7 +745,7 @@ impl<'a> TokenIterator<'a> { } while let Some(&next_char_in_oct) = self.char_stream.peek() { match next_char_in_oct { - '0'..='8' => { + '0'..='8' | '_' => { result.push(next_char_in_oct); self.char_stream.next(); if let Err(err) = self.advance_token() { @@ -738,7 +757,7 @@ impl<'a> TokenIterator<'a> { } radix_base = Some(8); } - 'b' | 'B' => { + 'b' | 'B' if c == '0' => { result.push(next_char); self.char_stream.next(); if let Err(err) = self.advance_token() { @@ -763,19 +782,19 @@ impl<'a> TokenIterator<'a> { } if let Some(radix) = radix_base { - let out: String = result - .iter() - .cloned() - .skip(2) - .filter(|c| c != &'_') - .collect(); + let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect(); - if let Ok(val) = i64::from_str_radix(&out, radix) { - return Some((Token::IntegerConstant(val), pos)); - } + return Some(( + if let Ok(val) = i64::from_str_radix(&out, radix) { + Token::IntegerConstant(val) + } else { + Token::LexErr(LERR::MalformedNumber(result.iter().collect())) + }, + pos, + )); } - let out: String = result.iter().cloned().collect(); + let out: String = result.iter().filter(|&&c| c != '_').collect(); return Some(( if let Ok(val) = out.parse::() { @@ -783,7 +802,7 @@ impl<'a> TokenIterator<'a> { } else if let Ok(val) = out.parse::() { Token::FloatConstant(val) } else { - Token::LexErr(LERR::MalformedNumber) + Token::LexErr(LERR::MalformedNumber(result.iter().collect())) }, pos, )); @@ -805,7 +824,7 @@ impl<'a> TokenIterator<'a> { } } - let out: String = result.iter().cloned().collect(); + let out: String = result.iter().collect(); return Some(( match out.as_str() { @@ -840,12 +859,12 @@ impl<'a> TokenIterator<'a> { return Some(( if let Some(first_char) = chars.next() { if chars.count() != 0 { - Token::LexErr(LERR::MalformedChar) + Token::LexErr(LERR::MalformedChar(format!("'{}'", result))) } else { Token::CharConstant(first_char) } } else { - Token::LexErr(LERR::MalformedChar) + Token::LexErr(LERR::MalformedChar(format!("'{}'", result))) }, pos, )); diff --git a/tests/string.rs b/tests/string.rs index e7df5d05..2032154b 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -5,13 +5,21 @@ fn test_string() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); assert_eq!( - engine.eval::("\"Test string: \\u2764\"")?, + engine.eval::(r#""Test string: \u2764""#)?, "Test string: ❤".to_string() ); assert_eq!( - engine.eval::("\"foo\" + \"bar\"")?, + engine.eval::(r#""Test string: \x58""#)?, + "Test string: X".to_string() + ); + assert_eq!( + engine.eval::(r#""foo" + "bar""#)?, "foobar".to_string() ); + assert_eq!( + engine.eval::(r#""foo" + 123.4556"#)?, + "foo123.4556".to_string() + ); Ok(()) } From 01bee6e16e07add5e71586f1d81827d65fbd3cd3 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 3 Mar 2020 23:17:42 +0800 Subject: [PATCH 09/24] Accept up to 20 parameters in functions. --- src/fn_register.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fn_register.rs b/src/fn_register.rs index 9fbd0529..ee08dfd1 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -106,4 +106,4 @@ macro_rules! def_register { } #[cfg_attr(rustfmt, rustfmt_skip)] -def_register!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S); +def_register!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T); From 3af421ae5f1b0f0210e506bc45f1a06dadf5f615 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 3 Mar 2020 23:31:16 +0800 Subject: [PATCH 10/24] Fix problems with script-defined functions. --- README.md | 3 +++ src/api.rs | 5 ----- src/engine.rs | 21 ++++++++++++++++----- src/fn_register.rs | 4 ++-- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 20fb8d1e..a0fb6836 100644 --- a/README.md +++ b/README.md @@ -545,6 +545,9 @@ print(add(2, 3)); ``` Remember that functions defined in script always take `Dynamic` arguments (i.e. the arguments can be of any type). + +Arguments are passed by value, so all functions are _pure_ (i.e. they never modify their arguments). + Furthermore, functions can only be defined at the top level, never inside a block or another function. ```rust diff --git a/src/api.rs b/src/api.rs index 062d28cc..99054a31 100644 --- a/src/api.rs +++ b/src/api.rs @@ -144,11 +144,6 @@ impl Engine { .map_err(|err| EvalAltResult::ErrorParsing(err)) .and_then(|AST(ref os, ref fns)| { for f in fns { - // FIX - Why are functions limited to 6 parameters? - if f.params.len() > 6 { - return Ok(()); - } - self.script_fns.insert( FnSpec { ident: f.name.clone(), diff --git a/src/engine.rs b/src/engine.rs index 8388f2e5..dfdf0309 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -21,7 +21,7 @@ const KEYWORD_TYPE_OF: &'static str = "type_of"; pub enum EvalAltResult { ErrorParsing(ParseError), ErrorFunctionNotFound(String, Position), - ErrorFunctionArgsMismatch(String, usize, Position), + ErrorFunctionArgsMismatch(String, usize, usize, Position), ErrorBooleanArgMismatch(String, Position), ErrorArrayBounds(usize, i64, Position), ErrorStringBounds(usize, i64, Position), @@ -45,7 +45,7 @@ impl Error for EvalAltResult { match self { Self::ErrorParsing(p) => p.description(), Self::ErrorFunctionNotFound(_, _) => "Function not found", - Self::ErrorFunctionArgsMismatch(_, _, _) => { + Self::ErrorFunctionArgsMismatch(_, _, _, _) => { "Function call with wrong number of arguments" } Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands", @@ -105,9 +105,11 @@ impl std::fmt::Display for EvalAltResult { write!(f, "{} '{}': {}", desc, filename, err) } Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p), - Self::ErrorFunctionArgsMismatch(fun, n, pos) => { - write!(f, "Function '{}' expects {} argument(s) ({})", fun, n, pos) - } + Self::ErrorFunctionArgsMismatch(fun, need, n, pos) => write!( + f, + "Function '{}' expects {} argument(s) but {} found ({})", + fun, need, n, pos + ), Self::ErrorBooleanArgMismatch(op, pos) => { write!(f, "{} operator expects boolean operands ({})", op, pos) } @@ -244,6 +246,15 @@ impl Engine { .into_dynamic()) } FnIntExt::Int(ref f) => { + if f.params.len() != args.len() { + return Err(EvalAltResult::ErrorFunctionArgsMismatch( + spec.ident, + f.params.len(), + args.len(), + pos, + )); + } + let mut scope = Scope::new(); scope.extend( diff --git a/src/fn_register.rs b/src/fn_register.rs index ee08dfd1..4e28b05f 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -39,7 +39,7 @@ macro_rules! def_register { const NUM_ARGS: usize = count_args!($($par)*); if args.len() != NUM_ARGS { - Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, pos)) + Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)) } else { #[allow(unused_variables, unused_mut)] let mut drain = args.drain(..); @@ -72,7 +72,7 @@ macro_rules! def_register { const NUM_ARGS: usize = count_args!($($par)*); if args.len() != NUM_ARGS { - Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, pos)) + Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)) } else { #[allow(unused_variables, unused_mut)] let mut drain = args.drain(..); From b421c8ac501f98753471eb030501e1b323919884 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 3 Mar 2020 23:31:29 +0800 Subject: [PATCH 11/24] Minor refactor. --- src/parser.rs | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index f4890997..9f76de24 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -5,6 +5,9 @@ use std::fmt; use std::iter::Peekable; use std::str::Chars; +const MAX_LINES: u16 = 65535; +const MAX_POS: u16 = 65535; + #[derive(Debug, Eq, PartialEq, Hash, Clone)] pub enum LexError { UnexpectedChar(char), @@ -47,7 +50,7 @@ impl fmt::Display for LexError { pub enum ParseErrorType { BadInput(String), InputPastEndOfFile, - UnknownOperator, + UnknownOperator(String), MissingRightParen, MissingLeftBrace, MissingRightBrace, @@ -57,14 +60,11 @@ pub enum ParseErrorType { VarExpectsIdentifier, WrongFnDefinition, FnMissingName, - FnMissingParams, + FnMissingParams(String), } type PERR = ParseErrorType; -const MAX_LINES: u16 = 65535; -const MAX_POS: u16 = 65535; - #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] pub struct Position { line: u16, @@ -171,7 +171,7 @@ impl Error for ParseError { match self.0 { PERR::BadInput(ref p) => p, PERR::InputPastEndOfFile => "Script is incomplete", - PERR::UnknownOperator => "Unknown operator", + PERR::UnknownOperator(_) => "Unknown operator", PERR::MissingRightParen => "Expecting ')'", PERR::MissingLeftBrace => "Expecting '{'", PERR::MissingRightBrace => "Expecting '}'", @@ -180,7 +180,7 @@ impl Error for ParseError { PERR::MalformedIndexExpr => "Invalid index in indexing expression", PERR::VarExpectsIdentifier => "Expecting name of a variable", PERR::FnMissingName => "Expecting name in function declaration", - PERR::FnMissingParams => "Expecting parameters in function declaration", + PERR::FnMissingParams(_) => "Expecting parameters in function declaration", PERR::WrongFnDefinition => "Function definitions must be at top level and cannot be inside a block or another function", } } @@ -194,6 +194,8 @@ impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.0 { PERR::BadInput(ref s) => write!(f, "{}", s)?, + PERR::UnknownOperator(ref s) => write!(f, "{}: '{}'", self.description(), s)?, + PERR::FnMissingParams(ref s) => write!(f, "Missing parameters for function '{}'", s)?, _ => write!(f, "{}", self.description())?, } @@ -1648,7 +1650,12 @@ fn parse_binary_op<'a>( )), ) } - _ => return Err(ParseError(PERR::UnknownOperator, pos)), + token => { + return Err(ParseError( + PERR::UnknownOperator(token.syntax().to_string()), + pos, + )) + } }; } } @@ -1844,22 +1851,17 @@ fn parse_fn<'a>(input: &mut Peekable>) -> Result { input.next(); } - Some(&(_, pos)) => return Err(ParseError(PERR::FnMissingParams, pos)), - None => return Err(ParseError(PERR::FnMissingParams, Position::eof())), + Some(&(_, pos)) => return Err(ParseError(PERR::FnMissingParams(name), pos)), + None => return Err(ParseError(PERR::FnMissingParams(name), Position::eof())), } let mut params = Vec::new(); - let skip_params = match input.peek() { + match input.peek() { Some(&(Token::RightParen, _)) => { input.next(); - true } - _ => false, - }; - - if !skip_params { - loop { + _ => loop { match input.next() { Some((Token::RightParen, _)) => break, Some((Token::Comma, _)) => (), @@ -1869,7 +1871,7 @@ fn parse_fn<'a>(input: &mut Peekable>) -> Result return Err(ParseError(PERR::MalformedCallExpr, pos)), None => return Err(ParseError(PERR::MalformedCallExpr, Position::eof())), } - } + }, } let body = parse_block(input)?; From b4d56accd49a1964ba05ba37f086c9cfc8e6634b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 4 Mar 2020 09:33:35 +0800 Subject: [PATCH 12/24] Reverse commit fa13588f6927d7b5ef902d78cb47f8c798ea5b3d. Limit input script size not meaningful. --- src/parser.rs | 246 +++++++++++++------------------------------------- 1 file changed, 65 insertions(+), 181 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 9f76de24..94f67cad 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -5,9 +5,6 @@ use std::fmt; use std::iter::Peekable; use std::str::Chars; -const MAX_LINES: u16 = 65535; -const MAX_POS: u16 = 65535; - #[derive(Debug, Eq, PartialEq, Hash, Clone)] pub enum LexError { UnexpectedChar(char), @@ -67,8 +64,8 @@ type PERR = ParseErrorType; #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] pub struct Position { - line: u16, - pos: u16, + line: usize, + pos: usize, } impl Position { @@ -76,47 +73,29 @@ impl Position { Self { line: 1, pos: 0 } } - pub fn line(&self) -> u16 { + pub fn line(&self) -> usize { self.line } - pub fn position(&self) -> u16 { + pub fn position(&self) -> usize { self.pos } - pub(crate) fn advance(&mut self) -> Result { - if self.pos >= MAX_POS { - Err(LERR::InputError(format!( - "cannot advance beyond maximum line length ({})", - MAX_POS - ))) - } else { - self.pos += 1; - Ok(self.pos) - } + pub(crate) fn advance(&mut self) { + self.pos += 1; } - pub(crate) fn rewind(&mut self) -> Result { + pub(crate) fn rewind(&mut self) { if self.pos == 0 { - Err(LERR::InputError("cannot rewind at position 0".into())) + panic!("cannot rewind at position 0"); } else { self.pos -= 1; - Ok(self.pos) } } - pub(crate) fn new_line(&mut self) -> Result { - if self.line > MAX_LINES { - Err(LERR::InputError(format!( - "reached maximum number of lines ({})", - MAX_LINES - ))) - } else { - self.line += 1; - self.pos = 0; - - Ok(self.pos) - } + pub(crate) fn new_line(&mut self) { + self.line += 1; + self.pos = 0; } pub fn eof() -> Self { @@ -155,10 +134,10 @@ impl ParseError { pub fn error_type(&self) -> &PERR { &self.0 } - pub fn line(&self) -> u16 { + pub fn line(&self) -> usize { self.1.line() } - pub fn position(&self) -> u16 { + pub fn position(&self) -> usize { self.1.position() } pub fn is_eof(&self) -> bool { @@ -518,21 +497,14 @@ pub struct TokenIterator<'a> { } impl<'a> TokenIterator<'a> { - fn advance(&mut self) -> Result { - self.pos.advance().map_err(|err| (err, self.pos)) + fn advance(&mut self) { + self.pos.advance(); } - fn rewind(&mut self) -> Result { - self.pos.rewind().map_err(|err| (err, self.pos)) + fn rewind(&mut self) { + self.pos.rewind(); } - fn new_line(&mut self) -> Result { - self.pos.new_line().map_err(|err| (err, self.pos)) - } - - fn advance_token(&mut self) -> Result { - self.advance().map_err(|err| (Token::LexErr(err.0), err.1)) - } - fn new_line_token(&mut self) -> Result { - self.new_line().map_err(|err| (Token::LexErr(err.0), err.1)) + fn new_line(&mut self) { + self.pos.new_line() } pub fn parse_string_const( @@ -549,7 +521,7 @@ impl<'a> TokenIterator<'a> { return Err((LERR::UnterminatedString, Position::eof())); } - self.advance()?; + self.advance(); match next_char.unwrap() { '\\' if escape.is_empty() => { @@ -579,7 +551,7 @@ impl<'a> TokenIterator<'a> { for _ in 0..2 { if let Some(c) = self.char_stream.next() { seq.push(c); - self.advance()?; + self.advance(); if let Some(d1) = c.to_digit(16) { out_val *= 16; @@ -606,7 +578,7 @@ impl<'a> TokenIterator<'a> { for _ in 0..4 { if let Some(c) = self.char_stream.next() { seq.push(c); - self.advance()?; + self.advance(); if let Some(d1) = c.to_digit(16) { out_val *= 16; @@ -633,7 +605,7 @@ impl<'a> TokenIterator<'a> { for _ in 0..8 { if let Some(c) = self.char_stream.next() { seq.push(c); - self.advance()?; + self.advance(); if let Some(d1) = c.to_digit(16) { out_val *= 16; @@ -658,7 +630,7 @@ impl<'a> TokenIterator<'a> { return Err((LERR::MalformedEscapeSequence(escape), self.pos)) } '\n' => { - self.rewind()?; + self.rewind(); return Err((LERR::UnterminatedString, self.pos)); } x => { @@ -674,18 +646,12 @@ impl<'a> TokenIterator<'a> { fn inner_next(&mut self) -> Option<(Token, Position)> { while let Some(c) = self.char_stream.next() { - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); let pos = self.pos; match c { - '\n' => { - if let Err(err) = self.new_line_token() { - return Some(err); - } - } + '\n' => self.new_line(), '0'..='9' => { let mut result = Vec::new(); let mut radix_base: Option = None; @@ -696,24 +662,18 @@ impl<'a> TokenIterator<'a> { '0'..='9' | '_' => { result.push(next_char); self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); } '.' => { result.push(next_char); self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); while let Some(&next_char_in_float) = self.char_stream.peek() { match next_char_in_float { '0'..='9' | '_' => { result.push(next_char_in_float); self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); } _ => break, } @@ -722,17 +682,13 @@ impl<'a> TokenIterator<'a> { 'x' | 'X' if c == '0' => { result.push(next_char); self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); while let Some(&next_char_in_hex) = self.char_stream.peek() { match next_char_in_hex { '0'..='9' | 'a'..='f' | 'A'..='F' | '_' => { result.push(next_char_in_hex); self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); } _ => break, } @@ -742,17 +698,13 @@ impl<'a> TokenIterator<'a> { 'o' | 'O' if c == '0' => { result.push(next_char); self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); while let Some(&next_char_in_oct) = self.char_stream.peek() { match next_char_in_oct { '0'..='8' | '_' => { result.push(next_char_in_oct); self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); } _ => break, } @@ -762,17 +714,13 @@ impl<'a> TokenIterator<'a> { 'b' | 'B' if c == '0' => { result.push(next_char); self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); while let Some(&next_char_in_binary) = self.char_stream.peek() { match next_char_in_binary { '0' | '1' | '_' => { result.push(next_char_in_binary); self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); } _ => break, } @@ -818,9 +766,7 @@ impl<'a> TokenIterator<'a> { x if x.is_ascii_alphanumeric() || x == '_' => { result.push(x); self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); } _ => break, } @@ -884,9 +830,7 @@ impl<'a> TokenIterator<'a> { match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); Token::PlusAssign } _ if self.last.is_next_unary() => Token::UnaryPlus, @@ -900,9 +844,7 @@ impl<'a> TokenIterator<'a> { match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); Token::MinusAssign } _ if self.last.is_next_unary() => Token::UnaryMinus, @@ -916,9 +858,7 @@ impl<'a> TokenIterator<'a> { match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); Token::MultiplyAssign } _ => Token::Multiply, @@ -929,58 +869,38 @@ impl<'a> TokenIterator<'a> { '/' => match self.char_stream.peek() { Some(&'/') => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); while let Some(c) = self.char_stream.next() { match c { '\n' => { - if let Err(err) = self.new_line_token() { - return Some(err); - } + self.advance(); break; } - _ => { - if let Err(err) = self.advance_token() { - return Some(err); - } - } + _ => self.advance(), } } } Some(&'*') => { let mut level = 1; self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); while let Some(c) = self.char_stream.next() { - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); match c { '/' => { if let Some('*') = self.char_stream.next() { level += 1; } - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); } '*' => { if let Some('/') = self.char_stream.next() { level -= 1; } - if let Err(err) = self.advance_token() { - return Some(err); - } - } - '\n' => { - if let Err(err) = self.new_line_token() { - return Some(err); - } + self.advance(); } + '\n' => self.advance(), _ => (), } @@ -991,9 +911,7 @@ impl<'a> TokenIterator<'a> { } Some(&'=') => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); return Some((Token::DivideAssign, pos)); } _ => return Some((Token::Divide, pos)), @@ -1005,9 +923,7 @@ impl<'a> TokenIterator<'a> { '=' => match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); return Some((Token::EqualsTo, pos)); } _ => return Some((Token::Equals, pos)), @@ -1015,29 +931,21 @@ impl<'a> TokenIterator<'a> { '<' => match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); return Some((Token::LessThanEqualsTo, pos)); } Some(&'<') => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); return match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); Some((Token::LeftShiftAssign, pos)) } _ => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); Some((Token::LeftShift, pos)) } }; @@ -1049,29 +957,21 @@ impl<'a> TokenIterator<'a> { match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); Token::GreaterThanEqualsTo } Some(&'>') => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); Token::RightShiftAssign } _ => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); Token::RightShift } } @@ -1086,9 +986,7 @@ impl<'a> TokenIterator<'a> { match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); Token::NotEqualsTo } _ => Token::Bang, @@ -1101,16 +999,12 @@ impl<'a> TokenIterator<'a> { match self.char_stream.peek() { Some(&'|') => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); Token::Or } Some(&'=') => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); Token::OrAssign } _ => Token::Pipe, @@ -1123,16 +1017,12 @@ impl<'a> TokenIterator<'a> { match self.char_stream.peek() { Some(&'&') => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); Token::And } Some(&'=') => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); Token::AndAssign } _ => Token::Ampersand, @@ -1145,9 +1035,7 @@ impl<'a> TokenIterator<'a> { match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); Token::XOrAssign } _ => Token::XOr, @@ -1160,9 +1048,7 @@ impl<'a> TokenIterator<'a> { match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); Token::ModuloAssign } _ => Token::Modulo, @@ -1175,9 +1061,7 @@ impl<'a> TokenIterator<'a> { match self.char_stream.peek() { Some(&'=') => { self.char_stream.next(); - if let Err(err) = self.advance_token() { - return Some(err); - } + self.advance(); Token::PowerOfAssign } _ => Token::PowerOf, From bb56a7a843d04250bf4fa4d7a630091b258ccdff Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 4 Mar 2020 22:00:01 +0800 Subject: [PATCH 13/24] Code refactor, bug fixes, code docs. --- README.md | 36 ++- src/any.rs | 69 ++++-- src/api.rs | 204 ++++++++++++++-- src/builtin.rs | 6 +- src/call.rs | 18 +- src/engine.rs | 581 ++++++++++++++------------------------------- src/error.rs | 142 +++++++++++ src/fn_register.rs | 56 ++++- src/lib.rs | 11 +- src/parser.rs | 300 ++++++++--------------- src/result.rs | 153 ++++++++++++ src/scope.rs | 28 ++- tests/engine.rs | 14 ++ tests/get_set.rs | 7 +- 14 files changed, 949 insertions(+), 676 deletions(-) create mode 100644 src/error.rs create mode 100644 src/result.rs create mode 100644 tests/engine.rs diff --git a/README.md b/README.md index a0fb6836..35a5acbc 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,6 @@ cargo run --example rhai_runner scripts/any_script.rhai To get going with Rhai, you create an instance of the scripting engine and then run eval. ```rust -extern crate rhai; use rhai::Engine; fn main() { @@ -170,7 +169,6 @@ if z.type_of() == "string" { Rhai's scripting engine is very lightweight. It gets its ability from the functions in your program. To call these functions, you need to register them with the scripting engine. ```rust -extern crate rhai; use rhai::{Dynamic, Engine, RegisterFn}; // Normal function @@ -220,7 +218,6 @@ Generic functions can be used in Rhai, but you'll need to register separate inst ```rust use std::fmt::Display; -extern crate rhai; use rhai::{Engine, RegisterFn}; fn showit(x: &mut T) -> () { @@ -256,7 +253,6 @@ print(to_int(123)); // what will happen? Here's an more complete example of working with Rust. First the example, then we'll break it into parts: ```rust -extern crate rhai; use rhai::{Engine, RegisterFn}; #[derive(Clone)] @@ -353,6 +349,8 @@ let x = new_ts(); print(x.type_of()); // prints "foo::bar::TestStruct" ``` +If you use `register_type_with_name` to register the custom type with a special pretty-print name, `type_of` will return that instead. + # Getters and setters Similarly, you can work with members of your custom types. This works by registering a 'get' or a 'set' function for working with your struct. @@ -415,7 +413,6 @@ By default, Rhai treats each engine invocation as a fresh one, persisting only t In this example, we first create a state with a few initialized variables, then thread the same state through multiple invocations: ```rust -extern crate rhai; use rhai::{Engine, Scope}; fn main() { @@ -620,8 +617,12 @@ y[1] = 42; print(y[1]); // prints 42 -let foo = [1, 2, 3][0]; // a syntax error for now - cannot index into literals -let foo = ts.list[0]; // a syntax error for now - cannot index into properties +ts.list = y; // arrays can be assigned completely (by value copy) +let foo = ts.list[1]; // indexing into properties is ok +foo == 42; + +let foo = [1, 2, 3][0]; // a syntax error (for now) - cannot index into literals +let foo = abc()[0]; // a syntax error (for now) - cannot index into function call return values let foo = y[0]; // this works y.push(4); // 4 elements @@ -722,9 +723,11 @@ record == "Bob C. Davis: age 42"; let c = record[4]; c == 'C'; -let c = "foo"[0]; // a syntax error for now - cannot index into literals -let c = ts.s[0]; // a syntax error for now - cannot index into properties -let c = record[0]; // this works +ts.s = record; +let c = ts.s[4]; // indexing into properties is ok +c == 'C'; + +let c = "foo"[0]; // a syntax error (for now) - cannot index into literals // Escape sequences in strings record += " \u2764\n"; // escape sequence of '❤' in Unicode @@ -785,8 +788,17 @@ debug("world!"); // prints "world!" to stdout using debug formatting ```rust // Any function that takes a &str argument can be used to override print and debug -engine.on_print(|x: &str| println!("hello: {}", x)); -engine.on_debug(|x: &str| println!("DEBUG: {}", x)); +engine.on_print(|x| println!("hello: {}", x)); +engine.on_debug(|x| println!("DEBUG: {}", x)); + +// Redirect logging output to somewhere else +let mut log: Vec = Vec::new(); +engine.on_print(|x| log.push(format!("log: {}", x))); +engine.on_debug(|x| log.push(format!("DEBUG: {}", x))); + : + eval script + : +println!("{:?}", log); // 'log' captures all the 'print' and 'debug' results. ``` ## Comments diff --git a/src/any.rs b/src/any.rs index 232423fd..c53c8c74 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,14 +1,21 @@ -use std::any::{type_name, Any as StdAny, TypeId}; +use std::any::{type_name, TypeId}; use std::fmt; +/// An raw value of any type. pub type Variant = dyn Any; + +/// A boxed dynamic type containing any value. pub type Dynamic = Box; -pub trait Any: StdAny { +/// A trait covering any type. +pub trait Any: std::any::Any { + /// Get the `TypeId` of this type. fn type_id(&self) -> TypeId; + /// Get the name of this type. fn type_name(&self) -> &'static str; + /// Convert into `Dynamic`. fn into_dynamic(&self) -> Dynamic; /// This type may only be implemented by `rhai`. @@ -16,11 +23,7 @@ pub trait Any: StdAny { fn _closed(&self) -> _Private; } -impl Any for T -where - T: Clone + StdAny + ?Sized, -{ - #[inline] +impl Any for T { fn type_id(&self) -> TypeId { TypeId::of::() } @@ -29,7 +32,6 @@ where type_name::() } - #[inline] fn into_dynamic(&self) -> Dynamic { Box::new(self.clone()) } @@ -40,20 +42,16 @@ where } impl Variant { - //#[inline] - // fn into_dynamic(&self) -> Box { - // Any::into_dynamic(self) - // } - #[inline] - pub fn is(&self) -> bool { + /// Is this `Variant` a specific type? + pub(crate) fn is(&self) -> bool { let t = TypeId::of::(); let boxed = ::type_id(self); t == boxed } - #[inline] - pub fn downcast_ref(&self) -> Option<&T> { + /// Get a reference of a specific type to the `Variant`. + pub(crate) fn downcast_ref(&self) -> Option<&T> { if self.is::() { unsafe { Some(&*(self as *const Variant as *const T)) } } else { @@ -61,8 +59,8 @@ impl Variant { } } - #[inline] - pub fn downcast_mut(&mut self) -> Option<&mut T> { + /// Get a mutable reference of a specific type to the `Variant`. + pub(crate) fn downcast_mut(&mut self) -> Option<&mut T> { if self.is::() { unsafe { Some(&mut *(self as *mut Variant as *mut T)) } } else { @@ -71,23 +69,40 @@ impl Variant { } } -impl Clone for Dynamic { - fn clone(&self) -> Self { - Any::into_dynamic(self.as_ref()) - } -} - impl fmt::Debug for Variant { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.pad("?") } } +impl Clone for Dynamic { + fn clone(&self) -> Self { + Any::into_dynamic(self.as_ref()) + } +} + +/// An extension trait that allows down-casting a `Dynamic` value to a specific type. pub trait AnyExt: Sized { + /// Get a copy of a `Dynamic` value as a specific type. fn downcast(self) -> Result, Self>; + + /// This type may only be implemented by `rhai`. + #[doc(hidden)] + fn _closed(&self) -> _Private; } impl AnyExt for Dynamic { + /// Get a copy of the `Dynamic` value as a specific type. + /// + /// # Example + /// + /// ```rust + /// use rhai::{Dynamic, Any, AnyExt}; + /// + /// let x: Dynamic = 42_u32.into_dynamic(); + /// + /// assert_eq!(*x.downcast::().unwrap(), 42); + /// ``` fn downcast(self) -> Result, Self> { if self.is::() { unsafe { @@ -98,9 +113,13 @@ impl AnyExt for Dynamic { Err(self) } } + + fn _closed(&self) -> _Private { + _Private + } } -/// Private type which ensures that `rhai::Any` can only +/// Private type which ensures that `rhai::Any` and `rhai::AnyExt` can only /// be implemented by this crate. #[doc(hidden)] pub struct _Private; diff --git a/src/api.rs b/src/api.rs index 99054a31..49cadfcd 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,10 +1,95 @@ -use crate::any::{Any, AnyExt}; -use crate::engine::{Engine, EvalAltResult, FnIntExt, FnSpec}; -use crate::parser::{lex, parse, ParseError, Position, AST}; +use crate::any::{Any, AnyExt, Dynamic}; +use crate::call::FuncArgs; +use crate::engine::{Engine, FnAny, FnIntExt, FnSpec}; +use crate::error::ParseError; +use crate::fn_register::RegisterFn; +use crate::parser::{lex, parse, Position, AST}; +use crate::result::EvalAltResult; use crate::scope::Scope; +use std::any::TypeId; use std::sync::Arc; -impl Engine { +impl<'a> Engine<'a> { + pub(crate) fn register_fn_raw( + &mut self, + fn_name: &str, + args: Option>, + f: Box, + ) { + debug_println!( + "Register function: {} ({})", + fn_name, + args.iter() + .map(|x| (*x).type_name()) + .map(|name| self.map_type_name(name)) + .collect::>() + .join(", ") + ); + + let spec = FnSpec { + name: fn_name.to_string().into(), + args, + }; + + self.fns.insert(spec, Arc::new(FnIntExt::Ext(f))); + } + + /// Register a custom type for use with the `Engine`. + /// The type must be `Clone`. + pub fn register_type(&mut self) { + self.register_type_with_name::(std::any::type_name::()); + } + + /// Register a custom type for use with the `Engine` with a name for the `type_of` function. + /// The type must be `Clone`. + pub fn register_type_with_name(&mut self, type_name: &str) { + // Add the pretty-print type name into the map + self.type_names.insert( + std::any::type_name::().to_string(), + type_name.to_string(), + ); + } + + /// Register an iterator adapter for a type with the `Engine`. + pub fn register_iterator(&mut self, f: F) + where + F: Fn(&Dynamic) -> Box> + 'static, + { + self.type_iterators.insert(TypeId::of::(), Arc::new(f)); + } + + /// Register a getter function for a member of a registered type with the `Engine`. + pub fn register_get( + &mut self, + name: &str, + get_fn: impl Fn(&mut T) -> U + 'static, + ) { + let get_name = "get$".to_string() + name; + self.register_fn(&get_name, get_fn); + } + + /// Register a setter function for a member of a registered type with the `Engine`. + pub fn register_set( + &mut self, + name: &str, + set_fn: impl Fn(&mut T, U) -> () + 'static, + ) { + let set_name = "set$".to_string() + name; + self.register_fn(&set_name, set_fn); + } + + /// Shorthand for registering both getter and setter functions + /// of a registered type with the `Engine`. + pub fn register_get_set( + &mut self, + name: &str, + get_fn: impl Fn(&mut T) -> U + 'static, + set_fn: impl Fn(&mut T, U) -> () + 'static, + ) { + self.register_get(name, get_fn); + self.register_set(name, set_fn); + } + /// Compile a string into an AST pub fn compile(input: &str) -> Result { let tokens = lex(input); @@ -17,12 +102,12 @@ impl Engine { use std::io::prelude::*; let mut f = File::open(filename) - .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))?; + .map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?; let mut contents = String::new(); f.read_to_string(&mut contents) - .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err)) + .map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err)) .and_then(|_| Self::compile(&contents).map_err(EvalAltResult::ErrorParsing)) } @@ -32,12 +117,12 @@ impl Engine { use std::io::prelude::*; let mut f = File::open(filename) - .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))?; + .map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?; let mut contents = String::new(); f.read_to_string(&mut contents) - .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err)) + .map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err)) .and_then(|_| self.eval::(&contents)) } @@ -74,7 +159,7 @@ impl Engine { fns.iter().for_each(|f| { self.script_fns.insert( FnSpec { - ident: f.name.clone(), + name: f.name.clone().into(), args: None, }, Arc::new(FnIntExt::Int(f.clone())), @@ -106,7 +191,7 @@ impl Engine { } } - /// Evaluate a file, but only return errors, if there are any. + /// 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 pub fn consume_file(&mut self, filename: &str) -> Result<(), EvalAltResult> { @@ -114,23 +199,23 @@ impl Engine { use std::io::prelude::*; let mut f = File::open(filename) - .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err))?; + .map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err))?; let mut contents = String::new(); f.read_to_string(&mut contents) - .map_err(|err| EvalAltResult::ErrorCantOpenScriptFile(filename.into(), err)) + .map_err(|err| EvalAltResult::ErrorReadingScriptFile(filename.into(), err)) .and_then(|_| self.consume(&contents)) } - /// Evaluate a string, but only return errors, if there are any. + /// 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 pub fn consume(&mut self, input: &str) -> Result<(), EvalAltResult> { self.consume_with_scope(&mut Scope::new(), input) } - /// Evaluate a string with own scope, but only return errors, if there are any. + /// 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 pub fn consume_with_scope( @@ -146,7 +231,7 @@ impl Engine { for f in fns { self.script_fns.insert( FnSpec { - ident: f.name.clone(), + name: f.name.clone().into(), args: None, }, Arc::new(FnIntExt::Int(f.clone())), @@ -164,13 +249,94 @@ impl Engine { }) } - /// Overrides `on_print` - pub fn on_print(&mut self, callback: impl Fn(&str) + 'static) { + /// Call a script function defined in a compiled AST. + /// + /// # Example + /// + /// ```rust + /// # use rhai::{Engine, EvalAltResult}; + /// # fn main() -> Result<(), EvalAltResult> { + /// let mut engine = Engine::new(); + /// + /// let ast = Engine::compile("fn add(x, y) { x.len() + y }")?; + /// + /// let result: i64 = engine.call_fn("add", ast, (&mut String::from("abc"), &mut 123_i64))?; + /// + /// assert_eq!(result, 126); + /// # Ok(()) + /// # } + /// ``` + pub fn call_fn<'f, A: FuncArgs<'f>, T: Any + Clone>( + &mut self, + name: &str, + ast: AST, + args: A, + ) -> Result { + let pos = Default::default(); + + ast.1.iter().for_each(|f| { + self.script_fns.insert( + FnSpec { + name: f.name.clone().into(), + args: None, + }, + Arc::new(FnIntExt::Int(f.clone())), + ); + }); + + let result = self + .call_fn_raw(name, args.into_vec(), None, pos) + .and_then(|b| { + b.downcast().map(|b| *b).map_err(|a| { + EvalAltResult::ErrorMismatchOutputType( + self.map_type_name((*a).type_name()).into(), + pos, + ) + }) + }); + + self.script_fns.clear(); // Clean up engine + + result + } + + /// Override default action of `print` (print to stdout using `println!`) + /// + /// # Example + /// + /// ```rust + /// # use rhai::Engine; + /// let mut result = String::from(""); + /// { + /// let mut engine = Engine::new(); + /// + /// // Override action of 'print' function + /// engine.on_print(|s| result.push_str(s)); + /// engine.consume("print(40 + 2);").unwrap(); + /// } + /// assert_eq!(result, "42"); + /// ``` + pub fn on_print(&mut self, callback: impl FnMut(&str) + 'a) { self.on_print = Box::new(callback); } - /// Overrides `on_debug` - pub fn on_debug(&mut self, callback: impl Fn(&str) + 'static) { + /// Override default action of `debug` (print to stdout using `println!`) + /// + /// # Example + /// + /// ```rust + /// # use rhai::Engine; + /// let mut result = String::from(""); + /// { + /// let mut engine = Engine::new(); + /// + /// // Override action of 'debug' function + /// engine.on_debug(|s| result.push_str(s)); + /// engine.consume(r#"debug("hello");"#).unwrap(); + /// } + /// assert_eq!(result, "\"hello\""); + /// ``` + pub fn on_debug(&mut self, callback: impl FnMut(&str) + 'a) { self.on_debug = Box::new(callback); } } diff --git a/src/builtin.rs b/src/builtin.rs index 538a6f5b..42d736a3 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -1,4 +1,6 @@ -use crate::{any::Any, Array, Engine, RegisterDynamicFn, RegisterFn}; +use crate::any::Any; +use crate::engine::{Array, Engine}; +use crate::fn_register::{RegisterDynamicFn, RegisterFn}; use std::fmt::{Debug, Display}; use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub}; @@ -58,7 +60,7 @@ macro_rules! reg_func3 { ) } -impl Engine { +impl Engine<'_> { /// Register the built-in library. pub(crate) fn register_builtins(&mut self) { fn add(x: T, y: T) -> ::Output { diff --git a/src/call.rs b/src/call.rs index d7565cd9..b287f2bf 100644 --- a/src/call.rs +++ b/src/call.rs @@ -3,13 +3,27 @@ use crate::any::{Any, Variant}; -pub trait FunArgs<'a> { +/// Trait that represent arguments to a function call. +pub trait FuncArgs<'a> { + /// Convert to a `Vec` of `Variant` arguments. fn into_vec(self) -> Vec<&'a mut Variant>; } +impl<'a> FuncArgs<'a> for Vec<&'a mut Variant> { + fn into_vec(self) -> Self { + self + } +} + +impl<'a, T: Any> FuncArgs<'a> for &'a mut Vec { + fn into_vec(self) -> Vec<&'a mut Variant> { + self.iter_mut().map(|x| x as &mut Variant).collect() + } +} + macro_rules! impl_args { ($($p:ident),*) => { - impl<'a, $($p: Any + Clone),*> FunArgs<'a> for ($(&'a mut $p,)*) + impl<'a, $($p: Any + Clone),*> FuncArgs<'a> for ($(&'a mut $p,)*) { fn into_vec(self) -> Vec<&'a mut Variant> { let ($($p,)*) = self; diff --git a/src/engine.rs b/src/engine.rs index dfdf0309..4f4396b0 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,148 +1,34 @@ use std::any::TypeId; +use std::borrow::Cow; use std::cmp::{PartialEq, PartialOrd}; use std::collections::HashMap; -use std::error::Error; use std::sync::Arc; use crate::any::{Any, AnyExt, Dynamic, Variant}; -use crate::call::FunArgs; -use crate::fn_register::RegisterFn; -use crate::parser::{Expr, FnDef, ParseError, Position, Stmt}; +use crate::parser::{Expr, FnDef, Position, Stmt}; +use crate::result::EvalAltResult; use crate::scope::Scope; +/// An dynamic array of `Dynamic` values. pub type Array = Vec; + pub type FnCallArgs<'a> = Vec<&'a mut Variant>; const KEYWORD_PRINT: &'static str = "print"; const KEYWORD_DEBUG: &'static str = "debug"; const KEYWORD_TYPE_OF: &'static str = "type_of"; -#[derive(Debug)] -pub enum EvalAltResult { - ErrorParsing(ParseError), - ErrorFunctionNotFound(String, Position), - ErrorFunctionArgsMismatch(String, usize, usize, Position), - ErrorBooleanArgMismatch(String, Position), - ErrorArrayBounds(usize, i64, Position), - ErrorStringBounds(usize, i64, Position), - ErrorIndexing(Position), - ErrorIndexExpr(Position), - ErrorIfGuard(Position), - ErrorFor(Position), - ErrorVariableNotFound(String, Position), - ErrorAssignmentToUnknownLHS(Position), - ErrorMismatchOutputType(String, Position), - ErrorCantOpenScriptFile(String, std::io::Error), - ErrorDotExpr(Position), - ErrorArithmetic(String, Position), - ErrorRuntime(String, Position), - LoopBreak, - Return(Dynamic, Position), -} - -impl Error for EvalAltResult { - fn description(&self) -> &str { - match self { - Self::ErrorParsing(p) => p.description(), - Self::ErrorFunctionNotFound(_, _) => "Function not found", - Self::ErrorFunctionArgsMismatch(_, _, _, _) => { - "Function call with wrong number of arguments" - } - Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands", - Self::ErrorIndexExpr(_) => "Indexing into an array or string expects an integer index", - Self::ErrorIndexing(_) => "Indexing can only be performed on an array or a string", - Self::ErrorArrayBounds(_, index, _) if *index < 0 => { - "Array access expects non-negative index" - } - Self::ErrorArrayBounds(max, _, _) if *max == 0 => "Access of empty array", - Self::ErrorArrayBounds(_, _, _) => "Array index out of bounds", - Self::ErrorStringBounds(_, index, _) if *index < 0 => { - "Indexing a string expects a non-negative index" - } - Self::ErrorStringBounds(max, _, _) if *max == 0 => "Indexing of empty string", - Self::ErrorStringBounds(_, _, _) => "String index out of bounds", - Self::ErrorIfGuard(_) => "If guard expects boolean expression", - Self::ErrorFor(_) => "For loop expects array or range", - Self::ErrorVariableNotFound(_, _) => "Variable not found", - Self::ErrorAssignmentToUnknownLHS(_) => { - "Assignment to an unsupported left-hand side expression" - } - Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect", - Self::ErrorCantOpenScriptFile(_, _) => "Cannot open script file", - Self::ErrorDotExpr(_) => "Malformed dot expression", - Self::ErrorArithmetic(_, _) => "Arithmetic error", - Self::ErrorRuntime(_, _) => "Runtime error", - Self::LoopBreak => "[Not Error] Breaks out of loop", - Self::Return(_, _) => "[Not Error] Function returns value", - } - } - - fn cause(&self) -> Option<&dyn Error> { - None - } -} - -impl std::fmt::Display for EvalAltResult { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let desc = self.description(); - - match self { - Self::ErrorFunctionNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), - Self::ErrorVariableNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), - Self::ErrorIndexing(pos) => write!(f, "{} ({})", desc, pos), - Self::ErrorIndexExpr(pos) => write!(f, "{} ({})", desc, pos), - Self::ErrorIfGuard(pos) => write!(f, "{} ({})", desc, pos), - Self::ErrorFor(pos) => write!(f, "{} ({})", desc, pos), - Self::ErrorAssignmentToUnknownLHS(pos) => write!(f, "{} ({})", desc, pos), - Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos), - Self::ErrorDotExpr(pos) => write!(f, "{} ({})", desc, pos), - Self::ErrorArithmetic(s, pos) => write!(f, "{}: {} ({})", desc, s, pos), - Self::ErrorRuntime(s, pos) if s.is_empty() => write!(f, "{} ({})", desc, pos), - Self::ErrorRuntime(s, pos) => write!(f, "{}: {} ({})", desc, s, pos), - Self::LoopBreak => write!(f, "{}", desc), - Self::Return(_, pos) => write!(f, "{} ({})", desc, pos), - Self::ErrorCantOpenScriptFile(filename, err) => { - write!(f, "{} '{}': {}", desc, filename, err) - } - Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p), - Self::ErrorFunctionArgsMismatch(fun, need, n, pos) => write!( - f, - "Function '{}' expects {} argument(s) but {} found ({})", - fun, need, n, pos - ), - Self::ErrorBooleanArgMismatch(op, pos) => { - write!(f, "{} operator expects boolean operands ({})", op, pos) - } - Self::ErrorArrayBounds(_, index, pos) if *index < 0 => { - write!(f, "{}: {} < 0 ({})", desc, index, pos) - } - Self::ErrorArrayBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), - Self::ErrorArrayBounds(max, index, pos) => { - write!(f, "{} (max {}): {} ({})", desc, max - 1, index, pos) - } - Self::ErrorStringBounds(_, index, pos) if *index < 0 => { - write!(f, "{}: {} < 0 ({})", desc, index, pos) - } - Self::ErrorStringBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), - Self::ErrorStringBounds(max, index, pos) => { - write!(f, "{} (max {}): {} ({})", desc, max - 1, index, pos) - } - } - } -} - #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub struct FnSpec { - pub ident: String, +pub struct FnSpec<'a> { + pub name: Cow<'a, str>, pub args: Option>, } type IteratorFn = dyn Fn(&Dynamic) -> Box>; -/// Rhai's engine type. This is what you use to run Rhai scripts +/// Rhai main scripting engine. /// /// ```rust -/// extern crate rhai; /// use rhai::Engine; /// /// fn main() { @@ -153,17 +39,17 @@ type IteratorFn = dyn Fn(&Dynamic) -> Box>; /// } /// } /// ``` -pub struct Engine { +pub struct Engine<'a> { /// A hashmap containing all compiled functions known to the engine - fns: HashMap>, + pub(crate) fns: HashMap, Arc>, /// A hashmap containing all script-defined functions - pub(crate) script_fns: HashMap>, + pub(crate) script_fns: HashMap, Arc>, /// A hashmap containing all iterators known to the engine - type_iterators: HashMap>, - type_names: HashMap, + pub(crate) type_iterators: HashMap>, + pub(crate) type_names: HashMap, - pub(crate) on_print: Box, - pub(crate) on_debug: Box, + pub(crate) on_print: Box, + pub(crate) on_debug: Box, } pub enum FnIntExt { @@ -173,38 +59,19 @@ pub enum FnIntExt { pub type FnAny = dyn Fn(FnCallArgs, Position) -> Result; -impl Engine { - pub fn call_fn<'a, I, A, T>(&self, ident: I, args: A) -> Result - where - I: Into, - A: FunArgs<'a>, - T: Any + Clone, - { - let pos = Position::new(); - - self.call_fn_raw(ident.into(), args.into_vec(), None, pos) - .and_then(|b| { - b.downcast().map(|b| *b).map_err(|a| { - EvalAltResult::ErrorMismatchOutputType( - self.map_type_name((*a).type_name()).into(), - pos, - ) - }) - }) - } - +impl Engine<'_> { /// Universal method for calling functions, that are either /// registered with the `Engine` or written in Rhai - fn call_fn_raw( - &self, - ident: String, + pub(crate) fn call_fn_raw( + &mut self, + fn_name: &str, args: FnCallArgs, def_value: Option<&Dynamic>, pos: Position, ) -> Result { debug_println!( - "Calling {}({})", - ident, + "Calling function: {} ({})", + fn_name, args.iter() .map(|x| (*x).type_name()) .map(|name| self.map_type_name(name)) @@ -212,17 +79,24 @@ impl Engine { .join(", ") ); - let mut spec = FnSpec { ident, args: None }; + let mut spec = FnSpec { + name: fn_name.into(), + args: None, + }; // First search in script-defined functions (can override built-in), // then in built-in's - let fn_def = self.script_fns.get(&spec).or_else(|| { - spec.args = Some(args.iter().map(|a| Any::type_id(&**a)).collect()); - self.fns.get(&spec) - }); + let fn_def = self + .script_fns + .get(&spec) + .or_else(|| { + spec.args = Some(args.iter().map(|a| Any::type_id(&**a)).collect()); + self.fns.get(&spec) + }) + .map(|f| f.clone()); if let Some(f) = fn_def { - match **f { + match *f { FnIntExt::Ext(ref f) => { let r = f(args, pos); @@ -230,25 +104,24 @@ impl Engine { return r; } - let callback = match spec.ident.as_str() { - KEYWORD_PRINT => &self.on_print, - KEYWORD_DEBUG => &self.on_debug, + let callback = match spec.name.as_ref() { + KEYWORD_PRINT => self.on_print.as_mut(), + KEYWORD_DEBUG => self.on_debug.as_mut(), _ => return r, }; Ok(callback( - r.unwrap() + &r.unwrap() .downcast::() - .map(|x| *x) - .unwrap_or("error: not a string".into()) - .as_str(), + .map(|s| *s) + .unwrap_or("error: not a string".into()), ) .into_dynamic()) } FnIntExt::Int(ref f) => { if f.params.len() != args.len() { return Err(EvalAltResult::ErrorFunctionArgsMismatch( - spec.ident, + spec.name.into(), f.params.len(), args.len(), pos, @@ -270,7 +143,7 @@ impl Engine { } } } - } else if spec.ident == KEYWORD_TYPE_OF && args.len() == 1 { + } else if spec.name == KEYWORD_TYPE_OF && args.len() == 1 { Ok(self .map_type_name(args[0].type_name()) .to_string() @@ -286,72 +159,14 @@ impl Engine { .collect::>(); Err(EvalAltResult::ErrorFunctionNotFound( - format!("{} ({})", spec.ident, types_list.join(", ")), + format!("{} ({})", spec.name, types_list.join(", ")), pos, )) } } - pub(crate) fn register_fn_raw( - &mut self, - ident: String, - args: Option>, - f: Box, - ) { - debug_println!("Register; {:?} with args {:?}", ident, args); - - let spec = FnSpec { ident, args }; - - self.fns.insert(spec, Arc::new(FnIntExt::Ext(f))); - } - - /// Register a type for use with Engine. Keep in mind that - /// your type must implement Clone. - pub fn register_type(&mut self) { - // currently a no-op, exists for future extensibility - } - - /// Register an iterator adapter for a type. - pub fn register_iterator(&mut self, f: F) - where - F: Fn(&Dynamic) -> Box> + 'static, - { - self.type_iterators.insert(TypeId::of::(), Arc::new(f)); - } - - /// Register a get function for a member of a registered type - pub fn register_get( - &mut self, - name: &str, - get_fn: impl Fn(&mut T) -> U + 'static, - ) { - let get_name = "get$".to_string() + name; - self.register_fn(&get_name, get_fn); - } - - /// Register a set function for a member of a registered type - pub fn register_set( - &mut self, - name: &str, - set_fn: impl Fn(&mut T, U) -> () + 'static, - ) { - let set_name = "set$".to_string() + name; - self.register_fn(&set_name, set_fn); - } - - /// Shorthand for registering both getters and setters - pub fn register_get_set( - &mut self, - name: &str, - get_fn: impl Fn(&mut T) -> U + 'static, - set_fn: impl Fn(&mut T, U) -> () + 'static, - ) { - self.register_get(name, get_fn); - self.register_set(name, set_fn); - } - fn get_dot_val_helper( - &self, + &mut self, scope: &mut Scope, this_ptr: &mut Variant, dot_rhs: &Expr, @@ -369,78 +184,44 @@ impl Engine { .chain(args.iter_mut().map(|b| b.as_mut())) .collect(); - self.call_fn_raw(fn_name.into(), args, def_value.as_ref(), *pos) + self.call_fn_raw(fn_name, args, def_value.as_ref(), *pos) } Expr::Identifier(id, pos) => { - let get_fn_name = "get$".to_string() + id; + let get_fn_name = format!("get${}", id); - self.call_fn_raw(get_fn_name, vec![this_ptr], None, *pos) + self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) } - Expr::Index(id, idx_raw, pos) => { - let idx = self - .eval_expr(scope, idx_raw)? - .downcast_ref::() - .map(|i| *i) - .ok_or(EvalAltResult::ErrorIndexExpr(idx_raw.position()))?; + Expr::Index(id, idx_expr, pos) => { + let idx = *self + .eval_expr(scope, idx_expr)? + .downcast::() + .map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position()))?; - let get_fn_name = "get$".to_string() + id; - - let mut val = self.call_fn_raw(get_fn_name, vec![this_ptr], None, *pos)?; - - if let Some(arr) = val.downcast_mut() as Option<&mut Array> { - if idx >= 0 { - arr.get(idx as usize) - .cloned() - .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx, *pos)) - } else { - Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, *pos)) - } - } else if let Some(s) = val.downcast_mut() as Option<&mut String> { - if idx >= 0 { - s.chars() - .nth(idx as usize) - .map(|ch| ch.into_dynamic()) - .ok_or_else(|| { - EvalAltResult::ErrorStringBounds(s.chars().count(), idx, *pos) - }) - } else { - Err(EvalAltResult::ErrorStringBounds( - s.chars().count(), - idx, - *pos, - )) - } - } else { - Err(EvalAltResult::ErrorIndexing(*pos)) - } + let get_fn_name = format!("get${}", id); + let val = self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?; + Self::get_indexed_value(val, idx, *pos).map(|(v, _)| v) } - Expr::Dot(inner_lhs, inner_rhs) => match **inner_lhs { - Expr::Identifier(ref id, pos) => { - let get_fn_name = "get$".to_string() + id; - let value = self - .call_fn_raw(get_fn_name, vec![this_ptr], None, pos) - .and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), inner_rhs))?; + Expr::Dot(inner_lhs, inner_rhs) => match inner_lhs.as_ref() { + Expr::Identifier(id, pos) => { + let get_fn_name = format!("get${}", id); - // TODO - Should propagate changes back in this scenario: - // - // fn update(p) { p = something_else; } - // obj.prop.update(); - // - // Right now, a copy of the object's property value is mutated, but not propagated - // back to the property via $set. - - Ok(value) + self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) + .and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), inner_rhs)) } - Expr::Index(_, _, pos) => { - // TODO - Handle Expr::Index for these scenarios: - // - // let x = obj.prop[2].x; - // obj.prop[3] = 42; - // - Err(EvalAltResult::ErrorDotExpr(pos)) + Expr::Index(id, idx_expr, pos) => { + let idx = *self + .eval_expr(scope, idx_expr)? + .downcast::() + .map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position()))?; + + let get_fn_name = format!("get${}", id); + let val = self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?; + Self::get_indexed_value(val, idx, *pos).and_then(|(mut v, _)| { + self.get_dot_val_helper(scope, v.as_mut(), inner_rhs) + }) } _ => Err(EvalAltResult::ErrorDotExpr(inner_lhs.position())), }, @@ -452,17 +233,53 @@ impl Engine { fn search_scope( scope: &Scope, id: &str, - map: impl FnOnce(&Variant) -> Result, + map: impl FnOnce(Dynamic) -> Result, begin: Position, ) -> Result<(usize, T), EvalAltResult> { scope .get(id) .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin)) - .and_then(move |(idx, _, val)| map(val.as_ref()).map(|v| (idx, v))) + .and_then(move |(idx, _, val)| map(val).map(|v| (idx, v))) } - fn indexed_value( - &self, + fn get_indexed_value( + val: Dynamic, + idx: i64, + pos: Position, + ) -> Result<(Dynamic, bool), EvalAltResult> { + if val.is::() { + let arr = val.downcast::().unwrap(); + + if idx >= 0 { + arr.get(idx as usize) + .cloned() + .map(|v| (v, true)) + .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx, pos)) + } else { + Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, pos)) + } + } else if val.is::() { + let s = val.downcast::().unwrap(); + + if idx >= 0 { + s.chars() + .nth(idx as usize) + .map(|ch| (ch.into_dynamic(), false)) + .ok_or_else(|| EvalAltResult::ErrorStringBounds(s.chars().count(), idx, pos)) + } else { + Err(EvalAltResult::ErrorStringBounds( + s.chars().count(), + idx, + pos, + )) + } + } else { + Err(EvalAltResult::ErrorIndexing(pos)) + } + } + + fn eval_index_expr( + &mut self, scope: &mut Scope, id: &str, idx: &Expr, @@ -473,46 +290,13 @@ impl Engine { .downcast::() .map_err(|_| EvalAltResult::ErrorIndexExpr(idx.position()))?; - let mut is_array = false; - Self::search_scope( scope, id, - |val| { - if let Some(arr) = val.downcast_ref() as Option<&Array> { - is_array = true; - - if idx >= 0 { - arr.get(idx as usize) - .cloned() - .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx, begin)) - } else { - Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, begin)) - } - } else if let Some(s) = val.downcast_ref() as Option<&String> { - is_array = false; - - if idx >= 0 { - s.chars() - .nth(idx as usize) - .map(|ch| ch.into_dynamic()) - .ok_or_else(|| { - EvalAltResult::ErrorStringBounds(s.chars().count(), idx, begin) - }) - } else { - Err(EvalAltResult::ErrorStringBounds( - s.chars().count(), - idx, - begin, - )) - } - } else { - Err(EvalAltResult::ErrorIndexing(begin)) - } - }, + |val| Self::get_indexed_value(val, idx, begin), begin, ) - .map(|(idx_sc, val)| (is_array, idx_sc, idx as usize, val)) + .map(|(idx_sc, (val, is_array))| (is_array, idx_sc, idx as usize, val)) } fn str_replace_char(s: &mut String, idx: usize, new_ch: char) { @@ -532,15 +316,14 @@ impl Engine { } fn get_dot_val( - &self, + &mut self, scope: &mut Scope, dot_lhs: &Expr, dot_rhs: &Expr, ) -> Result { match dot_lhs { Expr::Identifier(id, pos) => { - let (sc_idx, mut target) = - Self::search_scope(scope, id, |x| Ok(x.into_dynamic()), *pos)?; + let (sc_idx, mut target) = Self::search_scope(scope, id, Ok, *pos)?; let value = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs); // In case the expression mutated `target`, we need to reassign it because @@ -550,9 +333,9 @@ impl Engine { value } - Expr::Index(id, idx_raw, pos) => { + Expr::Index(id, idx_expr, pos) => { let (is_array, sc_idx, idx, mut target) = - self.indexed_value(scope, id, idx_raw, *pos)?; + self.eval_index_expr(scope, id, idx_expr, *pos)?; let value = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs); // In case the expression mutated `target`, we need to reassign it because @@ -576,31 +359,36 @@ impl Engine { } fn set_dot_val_helper( - &self, + &mut self, this_ptr: &mut Variant, dot_rhs: &Expr, mut source_val: Dynamic, ) -> Result { match dot_rhs { Expr::Identifier(id, pos) => { - let set_fn_name = "set$".to_string() + id; + let set_fn_name = format!("set${}", id); - self.call_fn_raw(set_fn_name, vec![this_ptr, source_val.as_mut()], None, *pos) + self.call_fn_raw( + &set_fn_name, + vec![this_ptr, source_val.as_mut()], + None, + *pos, + ) } - Expr::Dot(inner_lhs, inner_rhs) => match **inner_lhs { - Expr::Identifier(ref id, pos) => { - let get_fn_name = "get$".to_string() + id; + Expr::Dot(inner_lhs, inner_rhs) => match inner_lhs.as_ref() { + Expr::Identifier(id, pos) => { + let get_fn_name = format!("get${}", id); - self.call_fn_raw(get_fn_name, vec![this_ptr], None, pos) + self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) .and_then(|mut v| { self.set_dot_val_helper(v.as_mut(), inner_rhs, source_val) .map(|_| v) // Discard Ok return value }) .and_then(|mut v| { - let set_fn_name = "set$".to_string() + id; + let set_fn_name = format!("set${}", id); - self.call_fn_raw(set_fn_name, vec![this_ptr, v.as_mut()], None, pos) + self.call_fn_raw(&set_fn_name, vec![this_ptr, v.as_mut()], None, *pos) }) } _ => Err(EvalAltResult::ErrorDotExpr(inner_lhs.position())), @@ -611,7 +399,7 @@ impl Engine { } fn set_dot_val( - &self, + &mut self, scope: &mut Scope, dot_lhs: &Expr, dot_rhs: &Expr, @@ -619,8 +407,7 @@ impl Engine { ) -> Result { match dot_lhs { Expr::Identifier(id, pos) => { - let (sc_idx, mut target) = - Self::search_scope(scope, id, |x| Ok(x.into_dynamic()), *pos)?; + let (sc_idx, mut target) = Self::search_scope(scope, id, Ok, *pos)?; let value = self.set_dot_val_helper(target.as_mut(), dot_rhs, source_val); // In case the expression mutated `target`, we need to reassign it because @@ -630,9 +417,9 @@ impl Engine { value } - Expr::Index(id, idx_raw, pos) => { + Expr::Index(id, iex_expr, pos) => { let (is_array, sc_idx, idx, mut target) = - self.indexed_value(scope, id, idx_raw, *pos)?; + self.eval_index_expr(scope, id, iex_expr, *pos)?; let value = self.set_dot_val_helper(target.as_mut(), dot_rhs, source_val); // In case the expression mutated `target`, we need to reassign it because @@ -654,7 +441,7 @@ impl Engine { } } - fn eval_expr(&self, scope: &mut Scope, expr: &Expr) -> Result { + fn eval_expr(&mut self, scope: &mut Scope, expr: &Expr) -> Result { match expr { Expr::IntegerConstant(i, _) => Ok((*i).into_dynamic()), Expr::FloatConstant(i, _) => Ok((*i).into_dynamic()), @@ -666,40 +453,35 @@ impl Engine { .map(|(_, _, val)| val) .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.clone(), *pos)), - Expr::Index(id, idx_raw, pos) => self - .indexed_value(scope, id, idx_raw, *pos) + Expr::Index(id, idx_expr, pos) => self + .eval_index_expr(scope, id, idx_expr, *pos) .map(|(_, _, _, x)| x), Expr::Assignment(ref id, rhs) => { let rhs_val = self.eval_expr(scope, rhs)?; - match **id { - Expr::Identifier(ref name, pos) => { + match id.as_ref() { + Expr::Identifier(name, pos) => { if let Some((idx, _, _)) = scope.get(name) { *scope.get_mut(name, idx) = rhs_val; Ok(().into_dynamic()) } else { - Err(EvalAltResult::ErrorVariableNotFound(name.clone(), pos)) + Err(EvalAltResult::ErrorVariableNotFound(name.clone(), *pos)) } } - Expr::Index(ref id, ref idx_raw, pos) => { - let idx_pos = idx_raw.position(); + Expr::Index(id, idx_expr, pos) => { + let idx_pos = idx_expr.position(); - let idx = *match self.eval_expr(scope, &idx_raw)?.downcast_ref::() { - Some(x) => x, + let idx = *match self.eval_expr(scope, &idx_expr)?.downcast::() { + Ok(x) => x, _ => return Err(EvalAltResult::ErrorIndexExpr(idx_pos)), }; - let variable = &mut scope - .iter_mut() - .rev() - .filter(|(name, _)| id == name) - .map(|(_, val)| val) - .next(); - - let val = match variable { - Some(v) => v, - _ => return Err(EvalAltResult::ErrorVariableNotFound(id.clone(), pos)), + let val = match scope.get(id) { + Some((idx, _, _)) => scope.get_mut(id, idx), + _ => { + return Err(EvalAltResult::ErrorVariableNotFound(id.clone(), *pos)) + } }; if let Some(arr) = val.downcast_mut() as Option<&mut Array> { @@ -731,7 +513,7 @@ impl Engine { } } - Expr::Dot(ref dot_lhs, ref dot_rhs) => { + Expr::Dot(dot_lhs, dot_rhs) => { self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val) } @@ -744,26 +526,30 @@ impl Engine { Expr::Array(contents, _) => { let mut arr = Vec::new(); - contents.iter().try_for_each(|item| { - let arg = self.eval_expr(scope, item)?; - arr.push(arg); - Ok(()) - })?; + contents + .iter() + .try_for_each::<_, Result<_, EvalAltResult>>(|item| { + let arg = self.eval_expr(scope, item)?; + arr.push(arg); + Ok(()) + })?; Ok(Box::new(arr)) } - Expr::FunctionCall(fn_name, args, def_value, pos) => self.call_fn_raw( - fn_name.into(), - args.iter() + Expr::FunctionCall(fn_name, args, def_value, pos) => { + let mut args = args + .iter() .map(|expr| self.eval_expr(scope, expr)) - .collect::>()? - .iter_mut() - .map(|b| b.as_mut()) - .collect(), - def_value.as_ref(), - *pos, - ), + .collect::>()?; + + self.call_fn_raw( + fn_name, + args.iter_mut().map(|b| b.as_mut()).collect(), + def_value.as_ref(), + *pos, + ) + } Expr::And(lhs, rhs) => Ok(Box::new( *self @@ -802,7 +588,7 @@ impl Engine { } pub(crate) fn eval_stmt( - &self, + &mut self, scope: &mut Scope, stmt: &Stmt, ) -> Result { @@ -913,10 +699,9 @@ impl Engine { Stmt::ReturnWithVal(Some(a), false, pos) => { let val = self.eval_expr(scope, a)?; Err(EvalAltResult::ErrorRuntime( - (val.downcast_ref() as Option<&String>) - .map(|s| s.as_ref()) - .unwrap_or("") - .to_string(), + val.downcast::() + .map(|s| *s) + .unwrap_or("".to_string()), *pos, )) } @@ -941,27 +726,27 @@ impl Engine { } /// Make a new engine - pub fn new() -> Engine { + pub fn new<'a>() -> Engine<'a> { + use std::any::type_name; + // User-friendly names for built-in types let type_names = [ - ("alloc::string::String", "string"), - ( - "alloc::vec::Vec>", - "array", - ), - ("alloc::boxed::Box", "dynamic"), + (type_name::(), "string"), + (type_name::(), "array"), + (type_name::(), "dynamic"), ] .iter() .map(|(k, v)| (k.to_string(), v.to_string())) .collect(); + // Create the new scripting Engine let mut engine = Engine { fns: HashMap::new(), script_fns: HashMap::new(), type_iterators: HashMap::new(), type_names, - on_print: Box::new(|x: &str| println!("{}", x)), - on_debug: Box::new(|x: &str| println!("{}", x)), + on_print: Box::new(|x| println!("{}", x)), // default print/debug implementations + on_debug: Box::new(|x| println!("{}", x)), }; engine.register_builtins(); diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..6314a031 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,142 @@ +use crate::parser::Position; +use std::char; +use std::error::Error; +use std::fmt; + +/// Error when tokenizing the script text. +#[derive(Debug, Eq, PartialEq, Hash, Clone)] +pub enum LexError { + /// An unexpected character is encountered when tokenizing the script text. + UnexpectedChar(char), + /// A string literal is not terminated before a new-line or EOF. + UnterminatedString, + /// An string/character/numeric escape sequence is in an invalid format. + MalformedEscapeSequence(String), + /// An numeric literal is in an invalid format. + MalformedNumber(String), + /// An character literal is in an invalid format. + MalformedChar(String), + /// Error in the script text. + InputError(String), +} + +impl Error for LexError { + fn description(&self) -> &str { + match *self { + Self::UnexpectedChar(_) => "Unexpected character", + Self::UnterminatedString => "Open string is not terminated", + Self::MalformedEscapeSequence(_) => "Unexpected values in escape sequence", + Self::MalformedNumber(_) => "Unexpected characters in number", + Self::MalformedChar(_) => "Char constant not a single character", + Self::InputError(_) => "Input error", + } + } +} + +impl fmt::Display for LexError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::UnexpectedChar(c) => write!(f, "Unexpected '{}'", c), + Self::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{}'", s), + Self::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s), + Self::MalformedChar(s) => write!(f, "Invalid character: '{}'", s), + Self::InputError(s) => write!(f, "{}", s), + _ => write!(f, "{}", self.description()), + } + } +} + +/// Type of error encountered when parsing a script. +#[derive(Debug, PartialEq, Clone)] +pub enum ParseErrorType { + /// Error in the script text. Wrapped value is the error message. + BadInput(String), + /// The script ends prematurely. + InputPastEndOfFile, + /// An unknown operator is encountered. Wrapped value is the operator. + UnknownOperator(String), + /// An open `(` is missing the corresponding closing `)`. + MissingRightParen, + /// Expecting `(` but not finding one. + MissingLeftBrace, + /// An open `{` is missing the corresponding closing `}`. + MissingRightBrace, + /// An open `[` is missing the corresponding closing `]`. + MissingRightBracket, + /// An expression in function call arguments `()` has syntax error. + MalformedCallExpr, + /// An expression in indexing brackets `[]` has syntax error. + MalformedIndexExpr, + /// Missing a variable name after the `let` keyword. + VarExpectsIdentifier, + /// Defining a function `fn` in an appropriate place (e.g. inside another function). + WrongFnDefinition, + /// Missing a function name after the `fn` keyword. + FnMissingName, + /// A function definition is missing the parameters list. Wrapped value is the function name. + FnMissingParams(String), +} + +/// Error when parsing a script. +#[derive(Debug, PartialEq, Clone)] +pub struct ParseError(ParseErrorType, Position); + +impl ParseError { + /// Create a new `ParseError`. + pub(crate) fn new(err: ParseErrorType, pos: Position) -> Self { + Self(err, pos) + } + + /// Get the parse error. + pub fn error_type(&self) -> &ParseErrorType { + &self.0 + } + + /// Get the location in the script of the error. + pub fn position(&self) -> Position { + self.1 + } +} + +impl Error for ParseError { + fn description(&self) -> &str { + match self.0 { + ParseErrorType::BadInput(ref p) => p, + ParseErrorType::InputPastEndOfFile => "Script is incomplete", + ParseErrorType::UnknownOperator(_) => "Unknown operator", + ParseErrorType::MissingRightParen => "Expecting ')'", + ParseErrorType::MissingLeftBrace => "Expecting '{'", + ParseErrorType::MissingRightBrace => "Expecting '}'", + ParseErrorType::MissingRightBracket => "Expecting ']'", + ParseErrorType::MalformedCallExpr => "Invalid expression in function call arguments", + ParseErrorType::MalformedIndexExpr => "Invalid index in indexing expression", + ParseErrorType::VarExpectsIdentifier => "Expecting name of a variable", + 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", + } + } + + fn cause(&self) -> Option<&dyn Error> { + None + } +} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + ParseErrorType::BadInput(ref s) => write!(f, "{}", s)?, + ParseErrorType::UnknownOperator(ref s) => write!(f, "{}: '{}'", self.description(), s)?, + ParseErrorType::FnMissingParams(ref s) => { + write!(f, "Missing parameters for function '{}'", s)? + } + _ => write!(f, "{}", self.description())?, + } + + if !self.1.is_eof() { + write!(f, " ({})", self.1) + } else { + write!(f, " at the end of the script but there is no more input") + } + } +} diff --git a/src/fn_register.rs b/src/fn_register.rs index 4e28b05f..ac73060b 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -1,13 +1,59 @@ use std::any::TypeId; use crate::any::{Any, Dynamic}; -use crate::engine::{Engine, EvalAltResult, FnCallArgs}; +use crate::engine::{Engine, FnCallArgs}; use crate::parser::Position; +use crate::result::EvalAltResult; +/// A trait to register custom functions with the `Engine`. +/// +/// # Example +/// +/// ```rust +/// use rhai::{Engine, RegisterFn}; +/// +/// // Normal function +/// fn add(x: i64, y: i64) -> i64 { +/// x + y +/// } +/// +/// let mut engine = Engine::new(); +/// +/// // You must use the trait rhai::RegisterFn to get this method. +/// engine.register_fn("add", add); +/// +/// if let Ok(result) = engine.eval::("add(40, 2)") { +/// println!("Answer: {}", result); // prints 42 +/// } +/// ``` pub trait RegisterFn { + /// Register a custom function with the `Engine`. fn register_fn(&mut self, name: &str, f: FN); } + +/// A trait to register custom functions that return `Dynamic` values with the `Engine`. +/// +/// # Example +/// +/// ```rust +/// use rhai::{Engine, RegisterDynamicFn, Dynamic}; +/// +/// // Function that returns a Dynamic value +/// fn get_an_any(x: i64) -> Dynamic { +/// Box::new(x) +/// } +/// +/// let mut engine = Engine::new(); +/// +/// // You must use the trait rhai::RegisterDynamicFn to get this method. +/// engine.register_dynamic_fn("get_an_any", get_an_any); +/// +/// if let Ok(result) = engine.eval::("get_an_any(42)") { +/// println!("Answer: {}", result); // prints 42 +/// } +/// ``` pub trait RegisterDynamicFn { + /// Register a custom function returning `Dynamic` values with the `Engine`. fn register_dynamic_fn(&mut self, name: &str, f: FN); } @@ -28,7 +74,7 @@ macro_rules! def_register { $($par: Any + Clone,)* FN: Fn($($param),*) -> RET + 'static, RET: Any - > RegisterFn for Engine + > RegisterFn for Engine<'_> { fn register_fn(&mut self, name: &str, f: FN) { let fn_name = name.to_string(); @@ -54,14 +100,14 @@ macro_rules! def_register { Ok(Box::new(r) as Dynamic) } }; - self.register_fn_raw(name.into(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); + self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); } } impl< $($par: Any + Clone,)* FN: Fn($($param),*) -> Dynamic + 'static, - > RegisterDynamicFn for Engine + > RegisterDynamicFn for Engine<'_> { fn register_dynamic_fn(&mut self, name: &str, f: FN) { let fn_name = name.to_string(); @@ -86,7 +132,7 @@ macro_rules! def_register { Ok(f($(($clone)($par)),*)) } }; - self.register_fn_raw(name.into(), Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); + self.register_fn_raw(name, Some(vec![$(TypeId::of::<$par>()),*]), Box::new(fun)); } } diff --git a/src/lib.rs b/src/lib.rs index 7ceebfd8..5c947e95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,12 +59,17 @@ mod api; mod builtin; mod call; mod engine; +mod error; mod fn_register; mod parser; +mod result; mod scope; -pub use any::Dynamic; -pub use engine::{Array, Engine, EvalAltResult}; +pub use any::{Any, AnyExt, Dynamic, Variant}; +pub use call::FuncArgs; +pub use engine::{Array, Engine}; +pub use error::{ParseError, ParseErrorType}; pub use fn_register::{RegisterDynamicFn, RegisterFn}; -pub use parser::{ParseError, ParseErrorType, AST}; +pub use parser::{Position, AST}; +pub use result::EvalAltResult; pub use scope::Scope; diff --git a/src/parser.rs b/src/parser.rs index 94f67cad..ad68bc77 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,67 +1,13 @@ -use crate::Dynamic; +use crate::any::Dynamic; +use crate::error::{LexError, ParseError, ParseErrorType}; use std::char; -use std::error::Error; -use std::fmt; use std::iter::Peekable; use std::str::Chars; -#[derive(Debug, Eq, PartialEq, Hash, Clone)] -pub enum LexError { - UnexpectedChar(char), - UnterminatedString, - MalformedEscapeSequence(String), - MalformedNumber(String), - MalformedChar(String), - InputError(String), -} - type LERR = LexError; - -impl Error for LexError { - fn description(&self) -> &str { - match *self { - LERR::UnexpectedChar(_) => "Unexpected character", - LERR::UnterminatedString => "Open string is not terminated", - LERR::MalformedEscapeSequence(_) => "Unexpected values in escape sequence", - LERR::MalformedNumber(_) => "Unexpected characters in number", - LERR::MalformedChar(_) => "Char constant not a single character", - LERR::InputError(_) => "Input error", - } - } -} - -impl fmt::Display for LexError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - LERR::UnexpectedChar(c) => write!(f, "Unexpected '{}'", c), - LERR::MalformedEscapeSequence(s) => write!(f, "Invalid escape sequence: '{}'", s), - LERR::MalformedNumber(s) => write!(f, "Invalid number: '{}'", s), - LERR::MalformedChar(s) => write!(f, "Invalid character: '{}'", s), - LERR::InputError(s) => write!(f, "{}", s), - _ => write!(f, "{}", self.description()), - } - } -} - -#[derive(Debug, PartialEq, Clone)] -pub enum ParseErrorType { - BadInput(String), - InputPastEndOfFile, - UnknownOperator(String), - MissingRightParen, - MissingLeftBrace, - MissingRightBrace, - MissingRightBracket, - MalformedCallExpr, - MalformedIndexExpr, - VarExpectsIdentifier, - WrongFnDefinition, - FnMissingName, - FnMissingParams(String), -} - type PERR = ParseErrorType; +/// A location (line number + character position) in the input script. #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] pub struct Position { line: usize, @@ -69,22 +15,41 @@ pub struct Position { } impl Position { - pub fn new() -> Self { - Self { line: 1, pos: 0 } + /// Create a new `Position`. + pub fn new(line: usize, position: usize) -> Self { + Self { + line, + pos: position, + } } - pub fn line(&self) -> usize { - self.line + /// Get the line number (1-based), or `None` if EOF. + pub fn line(&self) -> Option { + match self.line { + 0 => None, + x => Some(x), + } } - pub fn position(&self) -> usize { - self.pos + /// Get the character position (1-based), or `None` if at beginning of a line. + pub fn position(&self) -> Option { + match self.pos { + 0 => None, + x => Some(x), + } } + /// Advance by one character position. pub(crate) fn advance(&mut self) { self.pos += 1; } + /// Go backwards by one character position. + /// + /// # Panics + /// + /// Panics if already at beginning of a line - cannot rewind to a previous line. + /// pub(crate) fn rewind(&mut self) { if self.pos == 0 { panic!("cannot rewind at position 0"); @@ -93,22 +58,31 @@ impl Position { } } + /// Advance to the next line. pub(crate) fn new_line(&mut self) { self.line += 1; self.pos = 0; } - pub fn eof() -> Self { + /// Create a `Position` at EOF. + pub(crate) fn eof() -> Self { Self { line: 0, pos: 0 } } + /// Is the `Position` at EOF? pub fn is_eof(&self) -> bool { self.line == 0 } } +impl Default for Position { + fn default() -> Self { + Self::new(1, 0) + } +} + impl std::fmt::Display for Position { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.is_eof() { write!(f, "EOF") } else { @@ -118,7 +92,7 @@ impl std::fmt::Display for Position { } impl std::fmt::Debug for Position { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.is_eof() { write!(f, "(EOF)") } else { @@ -127,65 +101,7 @@ impl std::fmt::Debug for Position { } } -#[derive(Debug, PartialEq, Clone)] -pub struct ParseError(PERR, Position); - -impl ParseError { - pub fn error_type(&self) -> &PERR { - &self.0 - } - pub fn line(&self) -> usize { - self.1.line() - } - pub fn position(&self) -> usize { - self.1.position() - } - pub fn is_eof(&self) -> bool { - self.1.is_eof() - } -} - -impl Error for ParseError { - fn description(&self) -> &str { - match self.0 { - PERR::BadInput(ref p) => p, - PERR::InputPastEndOfFile => "Script is incomplete", - PERR::UnknownOperator(_) => "Unknown operator", - PERR::MissingRightParen => "Expecting ')'", - PERR::MissingLeftBrace => "Expecting '{'", - PERR::MissingRightBrace => "Expecting '}'", - PERR::MissingRightBracket => "Expecting ']'", - PERR::MalformedCallExpr => "Invalid expression in function call arguments", - PERR::MalformedIndexExpr => "Invalid index in indexing expression", - PERR::VarExpectsIdentifier => "Expecting name of a variable", - PERR::FnMissingName => "Expecting name in function declaration", - PERR::FnMissingParams(_) => "Expecting parameters in function declaration", - PERR::WrongFnDefinition => "Function definitions must be at top level and cannot be inside a block or another function", - } - } - - fn cause(&self) -> Option<&dyn Error> { - None - } -} - -impl fmt::Display for ParseError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.0 { - PERR::BadInput(ref s) => write!(f, "{}", s)?, - PERR::UnknownOperator(ref s) => write!(f, "{}: '{}'", self.description(), s)?, - PERR::FnMissingParams(ref s) => write!(f, "Missing parameters for function '{}'", s)?, - _ => write!(f, "{}", self.description())?, - } - - if !self.is_eof() { - write!(f, " ({})", self.1) - } else { - write!(f, " at the end of the script but there is no more input") - } - } -} - +/// Compiled AST (abstract syntax tree) of a Rhai script. pub struct AST(pub(crate) Vec, pub(crate) Vec); #[derive(Debug, Clone)] @@ -312,7 +228,7 @@ pub enum Token { PowerOfAssign, For, In, - LexErr(LexError), + LexError(LexError), } impl Token { @@ -324,7 +240,7 @@ impl Token { FloatConstant(ref s) => s.to_string().into(), Identifier(ref s) => s.to_string().into(), CharConstant(ref s) => s.to_string().into(), - LexErr(ref err) => err.to_string().into(), + LexError(ref err) => err.to_string().into(), ref token => (match token { StringConst(_) => "string", @@ -450,37 +366,19 @@ impl Token { } #[allow(dead_code)] - pub fn is_bin_op(&self) -> bool { + pub fn is_binary_op(&self) -> bool { use self::Token::*; match *self { - RightBrace | - RightParen | - RightBracket | - Plus | - Minus | - Multiply | - Divide | - Comma | - // Period | <- does period count? - Equals | - LessThan | - GreaterThan | - LessThanEqualsTo | - GreaterThanEqualsTo | - EqualsTo | - NotEqualsTo | - Pipe | - Or | - Ampersand | - And | - PowerOf => true, + RightBrace | RightParen | RightBracket | Plus | Minus | Multiply | Divide | Comma + | Equals | LessThan | GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo + | EqualsTo | NotEqualsTo | Pipe | Or | Ampersand | And | PowerOf => true, _ => false, } } #[allow(dead_code)] - pub fn is_un_op(&self) -> bool { + pub fn is_unary_op(&self) -> bool { use self::Token::*; match *self { @@ -738,7 +636,7 @@ impl<'a> TokenIterator<'a> { if let Ok(val) = i64::from_str_radix(&out, radix) { Token::IntegerConstant(val) } else { - Token::LexErr(LERR::MalformedNumber(result.iter().collect())) + Token::LexError(LERR::MalformedNumber(result.iter().collect())) }, pos, )); @@ -752,7 +650,7 @@ impl<'a> TokenIterator<'a> { } else if let Ok(val) = out.parse::() { Token::FloatConstant(val) } else { - Token::LexErr(LERR::MalformedNumber(result.iter().collect())) + Token::LexError(LERR::MalformedNumber(result.iter().collect())) }, pos, )); @@ -797,7 +695,7 @@ impl<'a> TokenIterator<'a> { '"' => { return match self.parse_string_const('"') { Ok(out) => Some((Token::StringConst(out), pos)), - Err(e) => Some((Token::LexErr(e.0), e.1)), + Err(e) => Some((Token::LexError(e.0), e.1)), } } '\'' => match self.parse_string_const('\'') { @@ -807,17 +705,17 @@ impl<'a> TokenIterator<'a> { return Some(( if let Some(first_char) = chars.next() { if chars.count() != 0 { - Token::LexErr(LERR::MalformedChar(format!("'{}'", result))) + Token::LexError(LERR::MalformedChar(format!("'{}'", result))) } else { Token::CharConstant(first_char) } } else { - Token::LexErr(LERR::MalformedChar(format!("'{}'", result))) + Token::LexError(LERR::MalformedChar(format!("'{}'", result))) }, pos, )); } - Err(e) => return Some((Token::LexErr(e.0), e.1)), + Err(e) => return Some((Token::LexError(e.0), e.1)), }, '{' => return Some((Token::LeftBrace, pos)), '}' => return Some((Token::RightBrace, pos)), @@ -873,7 +771,7 @@ impl<'a> TokenIterator<'a> { while let Some(c) = self.char_stream.next() { match c { '\n' => { - self.advance(); + self.new_line(); break; } _ => self.advance(), @@ -900,7 +798,7 @@ impl<'a> TokenIterator<'a> { } self.advance(); } - '\n' => self.advance(), + '\n' => self.new_line(), _ => (), } @@ -1070,7 +968,7 @@ impl<'a> TokenIterator<'a> { )) } x if x.is_whitespace() => (), - x => return Some((Token::LexErr(LERR::UnexpectedChar(x)), pos)), + x => return Some((Token::LexError(LERR::UnexpectedChar(x)), pos)), } } @@ -1092,8 +990,8 @@ impl<'a> Iterator for TokenIterator<'a> { pub fn lex(input: &str) -> TokenIterator<'_> { TokenIterator { - last: Token::LexErr(LERR::InputError("".into())), - pos: Position { line: 1, pos: 0 }, + last: Token::LexError(LERR::InputError("".into())), + pos: Position::new(1, 0), char_stream: input.chars().peekable(), } } @@ -1145,7 +1043,7 @@ fn parse_paren_expr<'a>( match input.next() { Some((Token::RightParen, _)) => Ok(expr), - _ => Err(ParseError(PERR::MissingRightParen, Position::eof())), + _ => Err(ParseError::new(PERR::MissingRightParen, Position::eof())), } } @@ -1170,8 +1068,8 @@ fn parse_call_expr<'a>( return Ok(Expr::FunctionCall(id, args, None, begin)); } Some(&(Token::Comma, _)) => (), - Some(&(_, pos)) => return Err(ParseError(PERR::MalformedCallExpr, pos)), - None => return Err(ParseError(PERR::MalformedCallExpr, Position::eof())), + Some(&(_, pos)) => return Err(ParseError::new(PERR::MalformedCallExpr, pos)), + None => return Err(ParseError::new(PERR::MalformedCallExpr, Position::eof())), } input.next(); @@ -1189,13 +1087,10 @@ fn parse_index_expr<'a>( input.next(); return Ok(Expr::Index(id, Box::new(idx), begin)); } - Some(&(_, pos)) => return Err(ParseError(PERR::MalformedIndexExpr, pos)), - None => return Err(ParseError(PERR::MalformedIndexExpr, Position::eof())), + Some(&(_, pos)) => return Err(ParseError::new(PERR::MalformedIndexExpr, pos)), + None => return Err(ParseError::new(PERR::MalformedIndexExpr, Position::eof())), }, - Err(mut err) => { - err.0 = PERR::MalformedIndexExpr; - return Err(err); - } + Err(err) => return Err(ParseError::new(PERR::MalformedIndexExpr, err.position())), } } @@ -1205,13 +1100,13 @@ fn parse_ident_expr<'a>( begin: Position, ) -> Result { match input.peek() { - Some(&(Token::LeftParen, pos)) => { + Some(&(Token::LeftParen, _)) => { input.next(); - parse_call_expr(id, input, pos) + parse_call_expr(id, input, begin) } - Some(&(Token::LeftBracket, pos)) => { + Some(&(Token::LeftBracket, _)) => { input.next(); - parse_index_expr(id, input, pos) + parse_index_expr(id, input, begin) } Some(_) => Ok(Expr::Identifier(id, begin)), None => Ok(Expr::Identifier(id, Position::eof())), @@ -1247,8 +1142,8 @@ fn parse_array_expr<'a>( input.next(); Ok(Expr::Array(arr, begin)) } - Some(&(_, pos)) => Err(ParseError(PERR::MissingRightBracket, pos)), - None => Err(ParseError(PERR::MissingRightBracket, Position::eof())), + Some(&(_, pos)) => Err(ParseError::new(PERR::MissingRightBracket, pos)), + None => Err(ParseError::new(PERR::MissingRightBracket, Position::eof())), } } @@ -1263,12 +1158,14 @@ fn parse_primary<'a>(input: &mut Peekable>) -> Result parse_array_expr(input, pos), Some((Token::True, pos)) => Ok(Expr::True(pos)), Some((Token::False, pos)) => Ok(Expr::False(pos)), - Some((Token::LexErr(le), pos)) => Err(ParseError(PERR::BadInput(le.to_string()), pos)), - Some((token, pos)) => Err(ParseError( + Some((Token::LexError(le), pos)) => { + Err(ParseError::new(PERR::BadInput(le.to_string()), pos)) + } + Some((token, pos)) => Err(ParseError::new( PERR::BadInput(format!("Unexpected '{}'", token.syntax())), pos, )), - None => Err(ParseError(PERR::InputPastEndOfFile, Position::eof())), + None => Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())), } } @@ -1535,7 +1432,7 @@ fn parse_binary_op<'a>( ) } token => { - return Err(ParseError( + return Err(ParseError::new( PERR::UnknownOperator(token.syntax().to_string()), pos, )) @@ -1597,14 +1494,14 @@ fn parse_for<'a>(input: &mut Peekable>) -> Result s, - Some((_, pos)) => return Err(ParseError(PERR::VarExpectsIdentifier, pos)), - None => return Err(ParseError(PERR::VarExpectsIdentifier, Position::eof())), + Some((_, pos)) => return Err(ParseError::new(PERR::VarExpectsIdentifier, pos)), + None => return Err(ParseError::new(PERR::VarExpectsIdentifier, Position::eof())), }; match input.next() { Some((Token::In, _)) => {} - Some((_, pos)) => return Err(ParseError(PERR::VarExpectsIdentifier, pos)), - None => return Err(ParseError(PERR::VarExpectsIdentifier, Position::eof())), + Some((_, pos)) => return Err(ParseError::new(PERR::VarExpectsIdentifier, pos)), + None => return Err(ParseError::new(PERR::VarExpectsIdentifier, Position::eof())), } let expr = parse_expr(input)?; @@ -1617,13 +1514,13 @@ fn parse_for<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { let pos = match input.next() { Some((_, tok_pos)) => tok_pos, - _ => return Err(ParseError(PERR::InputPastEndOfFile, Position::eof())), + _ => return Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())), }; let name = match input.next() { Some((Token::Identifier(s), _)) => s, - Some((_, pos)) => return Err(ParseError(PERR::VarExpectsIdentifier, pos)), - None => return Err(ParseError(PERR::VarExpectsIdentifier, Position::eof())), + Some((_, pos)) => return Err(ParseError::new(PERR::VarExpectsIdentifier, pos)), + None => return Err(ParseError::new(PERR::VarExpectsIdentifier, Position::eof())), }; match input.peek() { @@ -1639,8 +1536,8 @@ fn parse_var<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { match input.peek() { Some(&(Token::LeftBrace, _)) => (), - Some(&(_, pos)) => return Err(ParseError(PERR::MissingLeftBrace, pos)), - None => return Err(ParseError(PERR::MissingLeftBrace, Position::eof())), + Some(&(_, pos)) => return Err(ParseError::new(PERR::MissingLeftBrace, pos)), + None => return Err(ParseError::new(PERR::MissingLeftBrace, Position::eof())), } input.next(); @@ -1649,7 +1546,7 @@ fn parse_block<'a>(input: &mut Peekable>) -> Result (), // empty block - Some(&(Token::Fn, pos)) => return Err(ParseError(PERR::WrongFnDefinition, pos)), + Some(&(Token::Fn, pos)) => return Err(ParseError::new(PERR::WrongFnDefinition, pos)), _ => { while input.peek().is_some() { @@ -1673,8 +1570,8 @@ fn parse_block<'a>(input: &mut Peekable>) -> Result Err(ParseError(PERR::MissingRightBrace, pos)), - None => Err(ParseError(PERR::MissingRightBrace, Position::eof())), + Some(&(_, pos)) => Err(ParseError::new(PERR::MissingRightBrace, pos)), + None => Err(ParseError::new(PERR::MissingRightBrace, Position::eof())), } } @@ -1722,21 +1619,26 @@ fn parse_stmt<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { let pos = match input.next() { Some((_, tok_pos)) => tok_pos, - _ => return Err(ParseError(PERR::InputPastEndOfFile, Position::eof())), + _ => return Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())), }; let name = match input.next() { Some((Token::Identifier(s), _)) => s, - Some((_, pos)) => return Err(ParseError(PERR::FnMissingName, pos)), - None => return Err(ParseError(PERR::FnMissingName, Position::eof())), + Some((_, pos)) => return Err(ParseError::new(PERR::FnMissingName, pos)), + None => return Err(ParseError::new(PERR::FnMissingName, Position::eof())), }; match input.peek() { Some(&(Token::LeftParen, _)) => { input.next(); } - Some(&(_, pos)) => return Err(ParseError(PERR::FnMissingParams(name), pos)), - None => return Err(ParseError(PERR::FnMissingParams(name), Position::eof())), + Some(&(_, pos)) => return Err(ParseError::new(PERR::FnMissingParams(name), pos)), + None => { + return Err(ParseError::new( + PERR::FnMissingParams(name), + Position::eof(), + )) + } } let mut params = Vec::new(); @@ -1752,8 +1654,8 @@ fn parse_fn<'a>(input: &mut Peekable>) -> Result { params.push(s); } - Some((_, pos)) => return Err(ParseError(PERR::MalformedCallExpr, pos)), - None => return Err(ParseError(PERR::MalformedCallExpr, Position::eof())), + Some((_, pos)) => return Err(ParseError::new(PERR::MalformedCallExpr, pos)), + None => return Err(ParseError::new(PERR::MalformedCallExpr, Position::eof())), } }, } diff --git a/src/result.rs b/src/result.rs new file mode 100644 index 00000000..eb69c985 --- /dev/null +++ b/src/result.rs @@ -0,0 +1,153 @@ +use std::error::Error; + +use crate::any::Dynamic; +use crate::error::ParseError; +use crate::parser::Position; + +/// Evaluation result. +/// +/// All wrapped `Position` values represent the location in the script where the error occurs. +#[derive(Debug)] +pub enum EvalAltResult { + /// Syntax error. + ErrorParsing(ParseError), + /// Call to an unknown function. Wrapped value is the name of the function. + ErrorFunctionNotFound(String, Position), + /// Function call has incorrect number of arguments. + /// Wrapped values are the name of the function, the number of parameters required + /// and the actual number of arguments passed. + ErrorFunctionArgsMismatch(String, usize, usize, Position), + /// Non-boolean operand encountered for boolean operator. Wrapped value is the operator. + ErrorBooleanArgMismatch(String, Position), + /// Array access out-of-bounds. + /// Wrapped values are the current number of elements in the array and the index number. + ErrorArrayBounds(usize, i64, Position), + /// String indexing out-of-bounds. + /// Wrapped values are the current number of characters in the string and the index number. + ErrorStringBounds(usize, i64, Position), + /// Trying to index into a type that is not an array and not a string. + ErrorIndexing(Position), + /// Trying to index into an array or string with an index that is not `i64`. + ErrorIndexExpr(Position), + /// The guard expression in an `if` statement does not return a boolean value. + ErrorIfGuard(Position), + /// The `for` statement encounters a type that is not an iterator. + ErrorFor(Position), + /// Usage of an unknown variable. Wrapped value is the name of the variable. + ErrorVariableNotFound(String, Position), + /// Assignment to an inappropriate LHS (left-hand-side) expression. + ErrorAssignmentToUnknownLHS(Position), + /// Returned type is not the same as the required output type. + /// Wrapped value is the type of the actual result. + ErrorMismatchOutputType(String, Position), + /// Error reading from a script file. Wrapped value is the path of the script file. + ErrorReadingScriptFile(String, std::io::Error), + /// Inappropriate member access. + ErrorDotExpr(Position), + /// Arithmetic error encountered. Wrapped value is the error message. + ErrorArithmetic(String, Position), + /// Run-time error encountered. Wrapped value is the error message. + ErrorRuntime(String, Position), + /// Internal use: Breaking out of loops. + LoopBreak, + /// Not an error: Value returned from a script via the `return` keyword. + /// Wrapped value is the result value. + Return(Dynamic, Position), +} + +impl Error for EvalAltResult { + fn description(&self) -> &str { + match self { + Self::ErrorParsing(p) => p.description(), + Self::ErrorFunctionNotFound(_, _) => "Function not found", + Self::ErrorFunctionArgsMismatch(_, _, _, _) => { + "Function call with wrong number of arguments" + } + Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands", + Self::ErrorIndexExpr(_) => "Indexing into an array or string expects an integer index", + Self::ErrorIndexing(_) => "Indexing can only be performed on an array or a string", + Self::ErrorArrayBounds(_, index, _) if *index < 0 => { + "Array access expects non-negative index" + } + Self::ErrorArrayBounds(max, _, _) if *max == 0 => "Access of empty array", + Self::ErrorArrayBounds(_, _, _) => "Array index out of bounds", + Self::ErrorStringBounds(_, index, _) if *index < 0 => { + "Indexing a string expects a non-negative index" + } + Self::ErrorStringBounds(max, _, _) if *max == 0 => "Indexing of empty string", + Self::ErrorStringBounds(_, _, _) => "String index out of bounds", + Self::ErrorIfGuard(_) => "If guard expects boolean expression", + Self::ErrorFor(_) => "For loop expects array or range", + Self::ErrorVariableNotFound(_, _) => "Variable not found", + Self::ErrorAssignmentToUnknownLHS(_) => { + "Assignment to an unsupported left-hand side expression" + } + Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect", + Self::ErrorReadingScriptFile(_, _) => "Cannot read from script file", + Self::ErrorDotExpr(_) => "Malformed dot expression", + Self::ErrorArithmetic(_, _) => "Arithmetic error", + Self::ErrorRuntime(_, _) => "Runtime error", + Self::LoopBreak => "[Not Error] Breaks out of loop", + Self::Return(_, _) => "[Not Error] Function returns value", + } + } + + fn cause(&self) -> Option<&dyn Error> { + None + } +} + +impl std::fmt::Display for EvalAltResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let desc = self.description(); + + match self { + Self::ErrorFunctionNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), + Self::ErrorVariableNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), + Self::ErrorIndexing(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorIndexExpr(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorIfGuard(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorFor(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorAssignmentToUnknownLHS(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos), + Self::ErrorDotExpr(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorArithmetic(s, pos) => write!(f, "{}: {} ({})", desc, s, pos), + Self::ErrorRuntime(s, pos) if s.is_empty() => write!(f, "{} ({})", desc, pos), + Self::ErrorRuntime(s, pos) => write!(f, "{}: {} ({})", desc, s, pos), + Self::LoopBreak => write!(f, "{}", desc), + Self::Return(_, pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorReadingScriptFile(filename, err) => { + write!(f, "{} '{}': {}", desc, filename, err) + } + Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p), + Self::ErrorFunctionArgsMismatch(fun, need, n, pos) => write!( + f, + "Function '{}' expects {} argument(s) but {} found ({})", + fun, need, n, pos + ), + Self::ErrorBooleanArgMismatch(op, pos) => { + write!(f, "{} operator expects boolean operands ({})", op, pos) + } + Self::ErrorArrayBounds(_, index, pos) if *index < 0 => { + write!(f, "{}: {} < 0 ({})", desc, index, pos) + } + Self::ErrorArrayBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), + Self::ErrorArrayBounds(max, index, pos) => { + write!(f, "{} (max {}): {} ({})", desc, max - 1, index, pos) + } + Self::ErrorStringBounds(_, index, pos) if *index < 0 => { + write!(f, "{}: {} < 0 ({})", desc, index, pos) + } + Self::ErrorStringBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), + Self::ErrorStringBounds(max, index, pos) => { + write!(f, "{} (max {}): {} ({})", desc, max - 1, index, pos) + } + } + } +} + +impl From for EvalAltResult { + fn from(err: ParseError) -> Self { + Self::ErrorParsing(err) + } +} diff --git a/src/scope.rs b/src/scope.rs index 16035f48..a08e912f 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -3,6 +3,8 @@ use crate::any::{Any, Dynamic}; /// A type containing information about current scope. /// Useful for keeping state between `Engine` runs /// +/// # Example +/// /// ```rust /// use rhai::{Engine, Scope}; /// @@ -13,8 +15,8 @@ use crate::any::{Any, Dynamic}; /// assert_eq!(engine.eval_with_scope::(&mut my_scope, "x + 1").unwrap(), 6); /// ``` /// -/// Between runs, `Engine` only remembers functions when not using own `Scope`. - +/// 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)>); impl Scope { @@ -58,7 +60,7 @@ impl Scope { self.0 .iter() .enumerate() - .rev() + .rev() // Always search a Scope in reverse order .find(|(_, (n, _))| n == key) .map(|(i, (n, v))| (i, n.clone(), v.clone())) } @@ -68,10 +70,10 @@ impl Scope { self.0 .iter() .enumerate() - .rev() + .rev() // Always search a Scope in reverse order .find(|(_, (n, _))| n == key) - .map(|(_, (_, v))| v.downcast_ref() as Option<&T>) - .map(|v| v.unwrap().clone()) + .and_then(|(_, (_, v))| v.downcast_ref::()) + .map(|v| v.clone()) } /// Get a mutable reference to a variable in the Scope. @@ -86,13 +88,19 @@ impl Scope { } /// Get an iterator to variables in the Scope. - pub fn iter(&self) -> std::slice::Iter<(String, Dynamic)> { - self.0.iter() + pub fn iter(&self) -> impl Iterator { + self.0 + .iter() + .rev() // Always search a Scope in reverse order + .map(|(key, val)| (key.as_str(), val)) } /// Get a mutable iterator to variables in the Scope. - pub(crate) fn iter_mut(&mut self) -> std::slice::IterMut<(String, Dynamic)> { - self.0.iter_mut() + pub fn iter_mut(&mut self) -> impl Iterator { + self.0 + .iter_mut() + .rev() // Always search a Scope in reverse order + .map(|(key, val)| (key.as_str(), val)) } } diff --git a/tests/engine.rs b/tests/engine.rs new file mode 100644 index 00000000..082daaa4 --- /dev/null +++ b/tests/engine.rs @@ -0,0 +1,14 @@ +use rhai::{Engine, EvalAltResult}; + +#[test] +fn test_engine_call_fn() -> Result<(), EvalAltResult> { + let mut engine = Engine::new(); + + let ast = Engine::compile("fn hello(x, y) { x.len() + y }")?; + + let result: i64 = engine.call_fn("hello", ast, (&mut String::from("abc"), &mut 123_i64))?; + + assert_eq!(result, 126); + + Ok(()) +} diff --git a/tests/get_set.rs b/tests/get_set.rs index d2718a50..0a9fdd16 100644 --- a/tests/get_set.rs +++ b/tests/get_set.rs @@ -78,7 +78,7 @@ fn test_big_get_set() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); engine.register_type::(); - engine.register_type::(); + engine.register_type_with_name::("TestParent"); engine.register_get_set("x", TestChild::get_x, TestChild::set_x); engine.register_get_set("child", TestParent::get_child, TestParent::set_child); @@ -90,5 +90,10 @@ fn test_big_get_set() -> Result<(), EvalAltResult> { 500 ); + assert_eq!( + engine.eval::("let a = new_tp(); a.type_of()")?, + "TestParent" + ); + Ok(()) } From ba2aac4960194aec4e9d40f94576ff3a35903cd9 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 4 Mar 2020 23:06:05 +0800 Subject: [PATCH 14/24] General code cleanup. --- Cargo.toml | 2 +- src/api.rs | 35 ++++++++++++++++++----------------- src/engine.rs | 12 ++++++------ src/fn_register.rs | 10 ++++------ src/parser.rs | 11 +++++------ src/scope.rs | 18 ++++++++---------- 6 files changed, 42 insertions(+), 46 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 54678e04..b4ff53e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai" -version = "0.10.1" +version = "0.10.2" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"] description = "Embedded scripting for Rust" diff --git a/src/api.rs b/src/api.rs index 49cadfcd..49553624 100644 --- a/src/api.rs +++ b/src/api.rs @@ -31,7 +31,8 @@ impl<'a> Engine<'a> { args, }; - self.fns.insert(spec, Arc::new(FnIntExt::Ext(f))); + self.external_functions + .insert(spec, Arc::new(FnIntExt::Ext(f))); } /// Register a custom type for use with the `Engine`. @@ -62,20 +63,20 @@ impl<'a> Engine<'a> { pub fn register_get( &mut self, name: &str, - get_fn: impl Fn(&mut T) -> U + 'static, + callback: impl Fn(&mut T) -> U + 'static, ) { let get_name = "get$".to_string() + name; - self.register_fn(&get_name, get_fn); + self.register_fn(&get_name, callback); } /// Register a setter function for a member of a registered type with the `Engine`. pub fn register_set( &mut self, name: &str, - set_fn: impl Fn(&mut T, U) -> () + 'static, + callback: impl Fn(&mut T, U) -> () + 'static, ) { let set_name = "set$".to_string() + name; - self.register_fn(&set_name, set_fn); + self.register_fn(&set_name, callback); } /// Shorthand for registering both getter and setter functions @@ -154,10 +155,10 @@ impl<'a> Engine<'a> { scope: &mut Scope, ast: &AST, ) -> Result { - let AST(os, fns) = ast; + let AST(statements, functions) = ast; - fns.iter().for_each(|f| { - self.script_fns.insert( + functions.iter().for_each(|f| { + self.script_functions.insert( FnSpec { name: f.name.clone().into(), args: None, @@ -166,11 +167,11 @@ impl<'a> Engine<'a> { ); }); - let result = os + let result = statements .iter() .try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o)); - self.script_fns.clear(); // Clean up engine + self.script_functions.clear(); // Clean up engine match result { Err(EvalAltResult::Return(out, pos)) => out.downcast::().map(|v| *v).map_err(|a| { @@ -227,9 +228,9 @@ impl<'a> Engine<'a> { parse(&mut tokens.peekable()) .map_err(|err| EvalAltResult::ErrorParsing(err)) - .and_then(|AST(ref os, ref fns)| { - for f in fns { - self.script_fns.insert( + .and_then(|AST(ref statements, ref functions)| { + for f in functions { + self.script_functions.insert( FnSpec { name: f.name.clone().into(), args: None, @@ -238,12 +239,12 @@ impl<'a> Engine<'a> { ); } - let val = os + let val = statements .iter() .try_fold(().into_dynamic(), |_, o| self.eval_stmt(scope, o)) .map(|_| ()); - self.script_fns.clear(); // Clean up engine + self.script_functions.clear(); // Clean up engine val }) @@ -275,7 +276,7 @@ impl<'a> Engine<'a> { let pos = Default::default(); ast.1.iter().for_each(|f| { - self.script_fns.insert( + self.script_functions.insert( FnSpec { name: f.name.clone().into(), args: None, @@ -295,7 +296,7 @@ impl<'a> Engine<'a> { }) }); - self.script_fns.clear(); // Clean up engine + self.script_functions.clear(); // Clean up engine result } diff --git a/src/engine.rs b/src/engine.rs index 4f4396b0..b2e62bd6 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -41,9 +41,9 @@ type IteratorFn = dyn Fn(&Dynamic) -> Box>; /// ``` pub struct Engine<'a> { /// A hashmap containing all compiled functions known to the engine - pub(crate) fns: HashMap, Arc>, + pub(crate) external_functions: HashMap, Arc>, /// A hashmap containing all script-defined functions - pub(crate) script_fns: HashMap, Arc>, + pub(crate) script_functions: HashMap, Arc>, /// A hashmap containing all iterators known to the engine pub(crate) type_iterators: HashMap>, pub(crate) type_names: HashMap, @@ -87,11 +87,11 @@ impl Engine<'_> { // First search in script-defined functions (can override built-in), // then in built-in's let fn_def = self - .script_fns + .script_functions .get(&spec) .or_else(|| { spec.args = Some(args.iter().map(|a| Any::type_id(&**a)).collect()); - self.fns.get(&spec) + self.external_functions.get(&spec) }) .map(|f| f.clone()); @@ -741,8 +741,8 @@ impl Engine<'_> { // Create the new scripting Engine let mut engine = Engine { - fns: HashMap::new(), - script_fns: HashMap::new(), + external_functions: HashMap::new(), + script_functions: HashMap::new(), type_iterators: HashMap::new(), type_names, on_print: Box::new(|x| println!("{}", x)), // default print/debug implementations diff --git a/src/fn_register.rs b/src/fn_register.rs index ac73060b..9e84492f 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -61,8 +61,8 @@ pub struct Ref(A); pub struct Mut(A); macro_rules! count_args { - () => {0usize}; - ($head:ident $($tail:ident)*) => {1usize + count_args!($($tail)*)}; + () => { 0_usize }; + ( $head:ident $($tail:ident)* ) => { 1_usize + count_args!($($tail)*) }; } macro_rules! def_register { @@ -80,8 +80,7 @@ macro_rules! def_register { let fn_name = name.to_string(); let fun = move |mut args: FnCallArgs, pos: Position| { - // Check for length at the beginning to avoid - // per-element bound checks. + // Check for length at the beginning to avoid per-element bound checks. const NUM_ARGS: usize = count_args!($($par)*); if args.len() != NUM_ARGS { @@ -113,8 +112,7 @@ macro_rules! def_register { let fn_name = name.to_string(); let fun = move |mut args: FnCallArgs, pos: Position| { - // Check for length at the beginning to avoid - // per-element bound checks. + // Check for length at the beginning to avoid per-element bound checks. const NUM_ARGS: usize = count_args!($($par)*); if args.len() != NUM_ARGS { diff --git a/src/parser.rs b/src/parser.rs index ad68bc77..0b2c347c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -51,11 +51,8 @@ impl Position { /// Panics if already at beginning of a line - cannot rewind to a previous line. /// pub(crate) fn rewind(&mut self) { - if self.pos == 0 { - panic!("cannot rewind at position 0"); - } else { - self.pos -= 1; - } + assert!(self.pos > 0, "cannot rewind at position 0"); + self.pos -= 1; } /// Advance to the next line. @@ -159,7 +156,9 @@ impl Expr { | Expr::False(pos) | Expr::Unit(pos) => *pos, - Expr::Assignment(_, _) | Expr::Dot(_, _) | Expr::And(_, _) | Expr::Or(_, _) => panic!(), + Expr::Assignment(e, _) | Expr::Dot(e, _) | Expr::And(e, _) | Expr::Or(e, _) => { + e.position() + } } } } diff --git a/src/scope.rs b/src/scope.rs index a08e912f..bafba6ed 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -61,8 +61,8 @@ impl Scope { .iter() .enumerate() .rev() // Always search a Scope in reverse order - .find(|(_, (n, _))| n == key) - .map(|(i, (n, v))| (i, n.clone(), v.clone())) + .find(|(_, (name, _))| name == key) + .map(|(i, (name, value))| (i, name.clone(), value.clone())) } /// Get the value of a variable in the Scope, starting from the last. @@ -71,18 +71,16 @@ impl Scope { .iter() .enumerate() .rev() // Always search a Scope in reverse order - .find(|(_, (n, _))| n == key) - .and_then(|(_, (_, v))| v.downcast_ref::()) - .map(|v| v.clone()) + .find(|(_, (name, _))| name == key) + .and_then(|(_, (_, value))| value.downcast_ref::()) + .map(|value| value.clone()) } /// Get a mutable reference to a variable in the Scope. pub(crate) fn get_mut(&mut self, key: &str, index: usize) -> &mut Dynamic { let entry = self.0.get_mut(index).expect("invalid index in Scope"); - if entry.0 != key { - panic!("incorrect key at Scope entry"); - } + assert_eq!(entry.0, key, "incorrect key at Scope entry"); &mut entry.1 } @@ -92,7 +90,7 @@ impl Scope { self.0 .iter() .rev() // Always search a Scope in reverse order - .map(|(key, val)| (key.as_str(), val)) + .map(|(key, value)| (key.as_str(), value)) } /// Get a mutable iterator to variables in the Scope. @@ -100,7 +98,7 @@ impl Scope { self.0 .iter_mut() .rev() // Always search a Scope in reverse order - .map(|(key, val)| (key.as_str(), val)) + .map(|(key, value)| (key.as_str(), value)) } } From 347f6d607a92ee317dc6a74294149d89b422e7f9 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 4 Mar 2020 23:44:32 +0800 Subject: [PATCH 15/24] Add no-std and stdlib features. --- Cargo.toml | 2 + src/builtin.rs | 101 ++++++++++++++++++++++++++----------------------- src/engine.rs | 19 ++++++++-- 3 files changed, 72 insertions(+), 50 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b4ff53e9..fae83a04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,5 @@ include = [ [features] debug_msgs = [] +no-std = [] +stdlib = [] \ No newline at end of file diff --git a/src/builtin.rs b/src/builtin.rs index 42d736a3..4e8810c4 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -1,9 +1,12 @@ use crate::any::Any; use crate::engine::{Array, Engine}; -use crate::fn_register::{RegisterDynamicFn, RegisterFn}; +use crate::fn_register::RegisterFn; use std::fmt::{Debug, Display}; use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub}; +#[cfg(not(feature = "no-std"))] +use crate::fn_register::RegisterDynamicFn; + macro_rules! reg_op { ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( $( @@ -36,6 +39,7 @@ macro_rules! reg_func1 { ) } +#[cfg(any(not(feature = "no-std"), feature = "stdlib"))] macro_rules! reg_func2x { ($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( $( @@ -44,6 +48,7 @@ macro_rules! reg_func2x { ) } +#[cfg(any(not(feature = "no-std"), feature = "stdlib"))] macro_rules! reg_func2y { ($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( $( @@ -52,6 +57,7 @@ macro_rules! reg_func2y { ) } +#[cfg(any(not(feature = "no-std"), feature = "stdlib"))] macro_rules! reg_func3 { ($self:expr, $x:expr, $op:expr, $v:ty, $w:ty, $r:ty, $( $y:ty ),*) => ( $( @@ -61,8 +67,8 @@ macro_rules! reg_func3 { } impl Engine<'_> { - /// Register the built-in library. - pub(crate) fn register_builtins(&mut self) { + /// Register the core built-in library. + pub(crate) fn register_core_lib(&mut self) { fn add(x: T, y: T) -> ::Output { x + y } @@ -176,6 +182,47 @@ impl Engine<'_> { self.register_fn("+", concat); self.register_fn("==", unit_eq); + // Register print and debug + fn print_debug(x: T) -> String { + format!("{:?}", x) + } + fn print(x: T) -> String { + format!("{}", x) + } + + reg_func1!(self, "print", print, String, i8, u8, i16, u16); + reg_func1!(self, "print", print, String, i32, i64, u32, u64); + reg_func1!(self, "print", print, String, f32, f64, bool, char, String); + reg_func1!(self, "print", print_debug, String, Array); + self.register_fn("print", || "".to_string()); + self.register_fn("print", |_: ()| "".to_string()); + + reg_func1!(self, "debug", print_debug, String, i8, u8, i16, u16); + reg_func1!(self, "debug", print_debug, String, i32, i64, u32, u64); + reg_func1!(self, "debug", print_debug, String, f32, f64, bool, char); + reg_func1!(self, "debug", print_debug, String, String, Array, ()); + + // Register array iterator + self.register_iterator::(|a| { + Box::new(a.downcast_ref::().unwrap().clone().into_iter()) + }); + + // Register range function + self.register_iterator::, _>(|a| { + Box::new( + a.downcast_ref::>() + .unwrap() + .clone() + .map(|n| n.into_dynamic()), + ) + }); + + self.register_fn("range", |i1: i64, i2: i64| (i1..i2)); + } + + /// Register the built-in library. + #[cfg(any(not(feature = "no-std"), feature = "stdlib"))] + pub(crate) fn register_stdlib(&mut self) { // Register conversion functions self.register_fn("to_float", |x: i8| x as f64); self.register_fn("to_float", |x: u8| x as f64); @@ -199,26 +246,6 @@ impl Engine<'_> { self.register_fn("to_int", |ch: char| ch as i64); - // Register print and debug - fn print_debug(x: T) -> String { - format!("{:?}", x) - } - fn print(x: T) -> String { - format!("{}", x) - } - - reg_func1!(self, "print", print, String, i8, u8, i16, u16); - reg_func1!(self, "print", print, String, i32, i64, u32, u64); - reg_func1!(self, "print", print, String, f32, f64, bool, char, String); - reg_func1!(self, "print", print_debug, String, Array); - self.register_fn("print", || "".to_string()); - self.register_fn("print", |_: ()| "".to_string()); - - reg_func1!(self, "debug", print_debug, String, i8, u8, i16, u16); - reg_func1!(self, "debug", print_debug, String, i32, i64, u32, u64); - reg_func1!(self, "debug", print_debug, String, f32, f64, bool, char); - reg_func1!(self, "debug", print_debug, String, String, Array, ()); - // Register array utility functions fn push(list: &mut Array, item: T) { list.push(Box::new(item)); @@ -242,14 +269,11 @@ impl Engine<'_> { reg_func3!(self, "pad", pad, &mut Array, i64, (), String, Array, ()); self.register_dynamic_fn("pop", |list: &mut Array| { - list.pop().unwrap_or(().into_dynamic()) + list.pop().unwrap_or_else(|| ().into_dynamic()) }); - self.register_dynamic_fn("shift", |list: &mut Array| { - if list.len() > 0 { - list.remove(0) - } else { - ().into_dynamic() - } + self.register_dynamic_fn("shift", |list: &mut Array| match list.len() { + 0 => ().into_dynamic(), + _ => list.remove(0), }); self.register_fn("len", |list: &mut Array| list.len() as i64); self.register_fn("clear", |list: &mut Array| list.clear()); @@ -314,22 +338,5 @@ impl Engine<'_> { chars.iter().for_each(|&ch| s.push(ch)); } }); - - // Register array iterator - self.register_iterator::(|a| { - Box::new(a.downcast_ref::().unwrap().clone().into_iter()) - }); - - // Register range function - self.register_iterator::, _>(|a| { - Box::new( - a.downcast_ref::>() - .unwrap() - .clone() - .map(|n| n.into_dynamic()), - ) - }); - - self.register_fn("range", |i1: i64, i2: i64| (i1..i2)); } } diff --git a/src/engine.rs b/src/engine.rs index b2e62bd6..60e38b08 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -745,12 +745,25 @@ impl Engine<'_> { script_functions: HashMap::new(), type_iterators: HashMap::new(), type_names, - on_print: Box::new(|x| println!("{}", x)), // default print/debug implementations - on_debug: Box::new(|x| println!("{}", x)), + on_print: Box::new(default_print), // default print/debug implementations + on_debug: Box::new(default_print), }; - engine.register_builtins(); + engine.register_core_lib(); + + #[cfg(any(not(feature = "no-std"), feature = "stdlib"))] + engine.register_stdlib(); // Register the standard library when not no-std or stdlib is set engine } } + +/// Print/debug to stdout +#[cfg(not(feature = "no-std"))] +fn default_print(s: &str) { + println!("{}", s); +} + +/// No-op +#[cfg(feature = "no-std")] +fn default_print(_: &str) {} From c9395049e23b1cd05ba1647a7af072d89d6de794 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 5 Mar 2020 10:18:46 +0800 Subject: [PATCH 16/24] Remove no-std feature and rename stdlib to no_stdlib. --- Cargo.toml | 3 +-- README.md | 18 ++++++++++++++---- src/builtin.rs | 23 ++++++++--------------- src/engine.rs | 8 ++++---- 4 files changed, 27 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fae83a04..f586d1f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,5 +16,4 @@ include = [ [features] debug_msgs = [] -no-std = [] -stdlib = [] \ No newline at end of file +no_stdlib = [] \ No newline at end of file diff --git a/README.md b/README.md index 35a5acbc..f0ab4f1a 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,16 @@ to use the latest version. Beware that in order to use pre-releases (alpha and beta) you need to specify the exact version in your `Cargo.toml`. +## Optional Features + +### `debug_msgs` + +Print debug messages to stdout (using `println!`) related to function registrations and function calls. + +### `no_stdlib` + +Exclude the standard library of utility functions in the build, and only include the minimum necessary functionalities. + ## Related Other cool projects to check out: @@ -601,7 +611,7 @@ println!(result); // prints "Runtime error: 42 is too large! (line 5, position You can create arrays of values, and then access them with numeric indices. -The following standard functions operate on arrays: +The following functions (defined in the standard library but excluded if you use the `no_stdlib` feature) operate on arrays: * `push` - inserts an element at the end * `pop` - removes the last element and returns it (() if empty) @@ -699,7 +709,7 @@ let x = 0o777; // i64 in oct let x = 0b1010_1111; // i64 in binary ``` -Conversion functions: +Conversion functions (defined in the standard library but excluded if you use the `no_stdlib` feature): * `to_int` - converts an `f32` or `f64` to `i64` * `to_float` - converts an integer type to `f64` @@ -714,7 +724,7 @@ let last = "Davis"; let full_name = name + " " + middle_initial + ". " + last; full_name == "Bob C. Davis"; -// String building with different types +// String building with different types (not available if 'no_stdlib' features is used) let age = 42; let record = full_name + ": age " + age; record == "Bob C. Davis: age 42"; @@ -738,7 +748,7 @@ record[4] = '\x58'; // 0x58 = 'X' record == "Bob X. Davis: age 42 ❤\n"; ``` -The following standard functions operate on strings: +The following standard functions (defined in the standard library but excluded if you use the `no_stdlib` feature) operate on strings: * `len` - returns the number of characters (not number of bytes) in the string * `pad` - pads the string with an character until a specified number of characters diff --git a/src/builtin.rs b/src/builtin.rs index 4e8810c4..ba175ea0 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -4,9 +4,6 @@ use crate::fn_register::RegisterFn; use std::fmt::{Debug, Display}; use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub}; -#[cfg(not(feature = "no-std"))] -use crate::fn_register::RegisterDynamicFn; - macro_rules! reg_op { ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( $( @@ -39,7 +36,7 @@ macro_rules! reg_func1 { ) } -#[cfg(any(not(feature = "no-std"), feature = "stdlib"))] +#[cfg(not(feature = "no_stdlib"))] macro_rules! reg_func2x { ($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( $( @@ -48,7 +45,7 @@ macro_rules! reg_func2x { ) } -#[cfg(any(not(feature = "no-std"), feature = "stdlib"))] +#[cfg(not(feature = "no_stdlib"))] macro_rules! reg_func2y { ($self:expr, $x:expr, $op:expr, $v:ty, $r:ty, $( $y:ty ),*) => ( $( @@ -57,7 +54,7 @@ macro_rules! reg_func2y { ) } -#[cfg(any(not(feature = "no-std"), feature = "stdlib"))] +#[cfg(not(feature = "no_stdlib"))] macro_rules! reg_func3 { ($self:expr, $x:expr, $op:expr, $v:ty, $w:ty, $r:ty, $( $y:ty ),*) => ( $( @@ -111,9 +108,6 @@ impl Engine<'_> { fn not(x: bool) -> bool { !x } - fn concat(x: String, y: String) -> String { - x + &y - } fn binary_and(x: T, y: T) -> ::Output { x & y } @@ -141,9 +135,6 @@ impl Engine<'_> { fn pow_f64_i64(x: f64, y: i64) -> f64 { x.powi(y as i32) } - fn unit_eq(_a: (), _b: ()) -> bool { - true - } reg_op!(self, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64); reg_op!(self, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, f32, f64); @@ -179,8 +170,8 @@ impl Engine<'_> { reg_un!(self, "-", neg, i8, i16, i32, i64, f32, f64); reg_un!(self, "!", not, bool); - self.register_fn("+", concat); - self.register_fn("==", unit_eq); + self.register_fn("+", |x: String, y: String| x + &y); // String + String + self.register_fn("==", |_: (), _: ()| true); // () == () // Register print and debug fn print_debug(x: T) -> String { @@ -221,8 +212,10 @@ impl Engine<'_> { } /// Register the built-in library. - #[cfg(any(not(feature = "no-std"), feature = "stdlib"))] + #[cfg(not(feature = "no_stdlib"))] pub(crate) fn register_stdlib(&mut self) { + use crate::fn_register::RegisterDynamicFn; + // Register conversion functions self.register_fn("to_float", |x: i8| x as f64); self.register_fn("to_float", |x: u8| x as f64); diff --git a/src/engine.rs b/src/engine.rs index 60e38b08..813a335b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -751,19 +751,19 @@ impl Engine<'_> { engine.register_core_lib(); - #[cfg(any(not(feature = "no-std"), feature = "stdlib"))] - engine.register_stdlib(); // Register the standard library when not no-std or stdlib is set + #[cfg(not(feature = "no_stdlib"))] + engine.register_stdlib(); // Register the standard library when no_stdlib is not set engine } } /// Print/debug to stdout -#[cfg(not(feature = "no-std"))] +#[cfg(not(feature = "no_stdlib"))] fn default_print(s: &str) { println!("{}", s); } /// No-op -#[cfg(feature = "no-std")] +#[cfg(feature = "no_stdlib")] fn default_print(_: &str) {} From 883f08c0260d1ecbadf9428c244e71eaf6de91e7 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 5 Mar 2020 20:28:03 +0800 Subject: [PATCH 17/24] Enable more indexing expressions. --- README.md | 30 +++++-- src/engine.rs | 243 +++++++++++++++++++++++++++++++------------------- src/parser.rs | 60 +++++++++---- src/result.rs | 20 +++-- 4 files changed, 230 insertions(+), 123 deletions(-) diff --git a/README.md b/README.md index f0ab4f1a..07d19912 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Rhai's current feature set: * Support for overloaded functions * No additional dependencies -**Note:** Currently, the version is 0.10.1, so the language and API may change before they stabilize. +**Note:** Currently, the version is 0.10.1, so the language and API's may change before they stabilize. ## Installation @@ -585,7 +585,7 @@ return 123 + 456; ```rust if error != "" { - throw error; // `throw` takes a string to form the exception text + throw error; // 'throw' takes a string to form the exception text } throw; // no exception text @@ -628,12 +628,19 @@ y[1] = 42; print(y[1]); // prints 42 ts.list = y; // arrays can be assigned completely (by value copy) -let foo = ts.list[1]; // indexing into properties is ok +let foo = ts.list[1]; foo == 42; -let foo = [1, 2, 3][0]; // a syntax error (for now) - cannot index into literals -let foo = abc()[0]; // a syntax error (for now) - cannot index into function call return values -let foo = y[0]; // this works +let foo = [1, 2, 3][0]; +foo == 1; + +fn abc() { [42, 43, 44] } + +let foo = abc()[0]; +foo == 42; + +let foo = y[0]; +foo == 1; y.push(4); // 4 elements y.push(5); // 5 elements @@ -734,14 +741,19 @@ let c = record[4]; c == 'C'; ts.s = record; -let c = ts.s[4]; // indexing into properties is ok + +let c = ts.s[4]; c == 'C'; -let c = "foo"[0]; // a syntax error (for now) - cannot index into literals +let c = "foo"[0]; +c == 'f'; + +let c = ("foo" + "bar")[5]; +c == 'r'; // Escape sequences in strings record += " \u2764\n"; // escape sequence of '❤' in Unicode -record == "Bob C. Davis: age 42 ❤\n"; // '\n' = new-line +record == "Bob C. Davis: age 42 ❤\n"; // '\n' = new-line // Unlike Rust, Rhai strings can be modified record[4] = '\x58'; // 0x58 = 'X' diff --git a/src/engine.rs b/src/engine.rs index 813a335b..74654cd5 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -18,6 +18,13 @@ const KEYWORD_PRINT: &'static str = "print"; const KEYWORD_DEBUG: &'static str = "debug"; const KEYWORD_TYPE_OF: &'static str = "type_of"; +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +enum VariableType { + Array, + String, + Expression, +} + #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct FnSpec<'a> { pub name: Cow<'a, str>, @@ -193,15 +200,24 @@ impl Engine<'_> { self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) } - Expr::Index(id, idx_expr, pos) => { + Expr::Index(lhs, idx_expr) => { let idx = *self .eval_expr(scope, idx_expr)? .downcast::() .map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position()))?; - let get_fn_name = format!("get${}", id); - let val = self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?; - Self::get_indexed_value(val, idx, *pos).map(|(v, _)| v) + let (lhs_value, pos) = match lhs.as_ref() { + Expr::Identifier(id, pos) => { + let get_fn_name = format!("get${}", id); + ( + self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?, + *pos, + ) + } + expr => return Err(EvalAltResult::ErrorDotExpr(expr.position())), + }; + + Self::get_indexed_value(lhs_value, idx, pos).map(|(v, _)| v) } Expr::Dot(inner_lhs, inner_rhs) => match inner_lhs.as_ref() { @@ -211,16 +227,25 @@ impl Engine<'_> { self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) .and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), inner_rhs)) } - Expr::Index(id, idx_expr, pos) => { + Expr::Index(lhs, idx_expr) => { let idx = *self .eval_expr(scope, idx_expr)? .downcast::() .map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position()))?; - let get_fn_name = format!("get${}", id); - let val = self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?; - Self::get_indexed_value(val, idx, *pos).and_then(|(mut v, _)| { - self.get_dot_val_helper(scope, v.as_mut(), inner_rhs) + let (lhs_value, pos) = match lhs.as_ref() { + Expr::Identifier(id, pos) => { + let get_fn_name = format!("get${}", id); + ( + self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?, + *pos, + ) + } + expr => return Err(EvalAltResult::ErrorDotExpr(expr.position())), + }; + + Self::get_indexed_value(lhs_value, idx, pos).and_then(|(mut value, _)| { + self.get_dot_val_helper(scope, value.as_mut(), inner_rhs) }) } _ => Err(EvalAltResult::ErrorDotExpr(inner_lhs.position())), @@ -246,14 +271,14 @@ impl Engine<'_> { val: Dynamic, idx: i64, pos: Position, - ) -> Result<(Dynamic, bool), EvalAltResult> { + ) -> Result<(Dynamic, VariableType), EvalAltResult> { if val.is::() { let arr = val.downcast::().unwrap(); if idx >= 0 { arr.get(idx as usize) .cloned() - .map(|v| (v, true)) + .map(|v| (v, VariableType::Array)) .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx, pos)) } else { Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, pos)) @@ -264,7 +289,7 @@ impl Engine<'_> { if idx >= 0 { s.chars() .nth(idx as usize) - .map(|ch| (ch.into_dynamic(), false)) + .map(|ch| (ch.into_dynamic(), VariableType::String)) .ok_or_else(|| EvalAltResult::ErrorStringBounds(s.chars().count(), idx, pos)) } else { Err(EvalAltResult::ErrorStringBounds( @@ -281,22 +306,28 @@ impl Engine<'_> { fn eval_index_expr( &mut self, scope: &mut Scope, - id: &str, - idx: &Expr, - begin: Position, - ) -> Result<(bool, usize, usize, Dynamic), EvalAltResult> { + lhs: &Expr, + idx_expr: &Expr, + ) -> Result<(VariableType, Option<(String, usize)>, usize, Dynamic), EvalAltResult> { let idx = *self - .eval_expr(scope, idx)? + .eval_expr(scope, idx_expr)? .downcast::() - .map_err(|_| EvalAltResult::ErrorIndexExpr(idx.position()))?; + .map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position()))?; - Self::search_scope( - scope, - id, - |val| Self::get_indexed_value(val, idx, begin), - begin, - ) - .map(|(idx_sc, (val, is_array))| (is_array, idx_sc, idx as usize, val)) + match lhs { + Expr::Identifier(id, _) => Self::search_scope( + scope, + &id, + |val| Self::get_indexed_value(val, idx, lhs.position()), + lhs.position(), + ) + .map(|(src_idx, (val, source_type))| { + (source_type, Some((id.clone(), src_idx)), idx as usize, val) + }), + + expr => Self::get_indexed_value(self.eval_expr(scope, expr)?, idx, lhs.position()) + .map(|(val, _)| (VariableType::Expression, None, idx as usize, val)), + } } fn str_replace_char(s: &mut String, idx: usize, new_ch: char) { @@ -333,22 +364,37 @@ impl Engine<'_> { value } - Expr::Index(id, idx_expr, pos) => { - let (is_array, sc_idx, idx, mut target) = - self.eval_index_expr(scope, id, idx_expr, *pos)?; + Expr::Index(lhs, idx_expr) => { + let (source_type, src, idx, mut target) = + self.eval_index_expr(scope, lhs, idx_expr)?; let value = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs); // In case the expression mutated `target`, we need to reassign it because // of the above `clone`. - if is_array { - scope.get_mut(id, sc_idx).downcast_mut::().unwrap()[idx] = target; - } else { - Self::str_replace_char( - scope.get_mut(id, sc_idx).downcast_mut::().unwrap(), // Root is a string - idx, - *target.downcast::().unwrap(), // Target should be a char - ); + match source_type { + VariableType::Array => { + let src = src.unwrap(); + scope + .get_mut(&src.0, src.1) + .downcast_mut::() + .unwrap()[idx] = target + } + + VariableType::String => { + let src = src.unwrap(); + + Self::str_replace_char( + scope + .get_mut(&src.0, src.1) + .downcast_mut::() + .unwrap(), // Root is a string + idx, + *target.downcast::().unwrap(), // Target should be a char + ) + } + + _ => panic!("source_type must be either Array or String"), } value @@ -417,21 +463,38 @@ impl Engine<'_> { value } - Expr::Index(id, iex_expr, pos) => { - let (is_array, sc_idx, idx, mut target) = - self.eval_index_expr(scope, id, iex_expr, *pos)?; + Expr::Index(lhs, idx_expr) => { + let (source_type, src, idx, mut target) = + self.eval_index_expr(scope, lhs, idx_expr)?; let value = self.set_dot_val_helper(target.as_mut(), dot_rhs, source_val); // In case the expression mutated `target`, we need to reassign it because // of the above `clone`. - if is_array { - scope.get_mut(id, sc_idx).downcast_mut::().unwrap()[idx] = target; - } else { - Self::str_replace_char( - scope.get_mut(id, sc_idx).downcast_mut::().unwrap(), // Root is a string - idx, - *target.downcast::().unwrap(), // Target should be a char - ); + + match source_type { + VariableType::Array => { + let src = src.unwrap(); + let val = scope + .get_mut(&src.0, src.1) + .downcast_mut::() + .unwrap(); + val[idx] = target + } + + VariableType::String => { + let src = src.unwrap(); + + Self::str_replace_char( + scope + .get_mut(&src.0, src.1) + .downcast_mut::() + .unwrap(), // Root is a string + idx, + *target.downcast::().unwrap(), // Target should be a char + ) + } + + _ => panic!("source_type must be either Array or String"), } value @@ -447,20 +510,17 @@ impl Engine<'_> { Expr::FloatConstant(i, _) => Ok((*i).into_dynamic()), Expr::StringConstant(s, _) => Ok(s.into_dynamic()), Expr::CharConstant(c, _) => Ok((*c).into_dynamic()), - - Expr::Identifier(id, pos) => scope - .get(id) - .map(|(_, _, val)| val) - .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.clone(), *pos)), - - Expr::Index(id, idx_expr, pos) => self - .eval_index_expr(scope, id, idx_expr, *pos) + Expr::Identifier(id, pos) => { + Self::search_scope(scope, id, Ok, *pos).map(|(_, val)| val) + } + Expr::Index(lhs, idx_expr) => self + .eval_index_expr(scope, lhs, idx_expr) .map(|(_, _, _, x)| x), - Expr::Assignment(ref id, rhs) => { + Expr::Assignment(lhs, rhs) => { let rhs_val = self.eval_expr(scope, rhs)?; - match id.as_ref() { + match lhs.as_ref() { Expr::Identifier(name, pos) => { if let Some((idx, _, _)) = scope.get(name) { *scope.get_mut(name, idx) = rhs_val; @@ -469,47 +529,44 @@ impl Engine<'_> { Err(EvalAltResult::ErrorVariableNotFound(name.clone(), *pos)) } } - Expr::Index(id, idx_expr, pos) => { - let idx_pos = idx_expr.position(); - let idx = *match self.eval_expr(scope, &idx_expr)?.downcast::() { - Ok(x) => x, - _ => return Err(EvalAltResult::ErrorIndexExpr(idx_pos)), - }; + Expr::Index(idx_lhs, idx_expr) => { + let (source_type, src, idx, _) = + self.eval_index_expr(scope, idx_lhs, idx_expr)?; - let val = match scope.get(id) { - Some((idx, _, _)) => scope.get_mut(id, idx), - _ => { - return Err(EvalAltResult::ErrorVariableNotFound(id.clone(), *pos)) + match source_type { + VariableType::Array => { + let src = src.unwrap(); + scope + .get_mut(&src.0, src.1) + .downcast_mut::() + .map(|arr| (arr[idx as usize] = rhs_val).into_dynamic()) + .ok_or_else(|| { + EvalAltResult::ErrorIndexExpr(idx_lhs.position()) + }) } - }; - if let Some(arr) = val.downcast_mut() as Option<&mut Array> { - if idx < 0 { - Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos)) - } else if idx as usize >= arr.len() { - Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos)) - } else { - arr[idx as usize] = rhs_val; - Ok(().into_dynamic()) + VariableType::String => { + let src = src.unwrap(); + scope + .get_mut(&src.0, src.1) + .downcast_mut::() + .map(|s| { + Self::str_replace_char( + s, + idx as usize, + *rhs_val.downcast::().unwrap(), + ) + .into_dynamic() + }) + .ok_or_else(|| { + EvalAltResult::ErrorIndexExpr(idx_lhs.position()) + }) } - } else if let Some(s) = val.downcast_mut() as Option<&mut String> { - let s_len = s.chars().count(); - if idx < 0 { - Err(EvalAltResult::ErrorStringBounds(s_len, idx, idx_pos)) - } else if idx as usize >= s_len { - Err(EvalAltResult::ErrorStringBounds(s_len, idx, idx_pos)) - } else { - Self::str_replace_char( - s, - idx as usize, - *rhs_val.downcast::().unwrap(), - ); - Ok(().into_dynamic()) - } - } else { - Err(EvalAltResult::ErrorIndexExpr(idx_pos)) + _ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS( + idx_lhs.position(), + )), } } @@ -517,7 +574,7 @@ impl Engine<'_> { self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val) } - _ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS(id.position())), + _ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS(lhs.position())), } } diff --git a/src/parser.rs b/src/parser.rs index 0b2c347c..855c2bca 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -132,7 +132,7 @@ pub enum Expr { FunctionCall(String, Vec, Option, Position), Assignment(Box, Box), Dot(Box, Box), - Index(String, Box, Position), + Index(Box, Box), Array(Vec, Position), And(Box, Box), Or(Box, Box), @@ -150,15 +150,16 @@ impl Expr { | Expr::CharConstant(_, pos) | Expr::StringConstant(_, pos) | Expr::FunctionCall(_, _, _, pos) - | Expr::Index(_, _, pos) | Expr::Array(_, pos) | Expr::True(pos) | Expr::False(pos) | Expr::Unit(pos) => *pos, - Expr::Assignment(e, _) | Expr::Dot(e, _) | Expr::And(e, _) | Expr::Or(e, _) => { - e.position() - } + Expr::Index(e, _) + | Expr::Assignment(e, _) + | Expr::Dot(e, _) + | Expr::And(e, _) + | Expr::Or(e, _) => e.position(), } } } @@ -1076,15 +1077,14 @@ fn parse_call_expr<'a>( } fn parse_index_expr<'a>( - id: String, + lhs: Box, input: &mut Peekable>, - begin: Position, ) -> Result { match parse_expr(input) { - Ok(idx) => match input.peek() { + Ok(idx_expr) => match input.peek() { Some(&(Token::RightBracket, _)) => { input.next(); - return Ok(Expr::Index(id, Box::new(idx), begin)); + return Ok(Expr::Index(lhs, Box::new(idx_expr))); } Some(&(_, pos)) => return Err(ParseError::new(PERR::MalformedIndexExpr, pos)), None => return Err(ParseError::new(PERR::MalformedIndexExpr, Position::eof())), @@ -1105,7 +1105,7 @@ fn parse_ident_expr<'a>( } Some(&(Token::LeftBracket, _)) => { input.next(); - parse_index_expr(id, input, begin) + parse_index_expr(Box::new(Expr::Identifier(id, begin)), input) } Some(_) => Ok(Expr::Identifier(id, begin)), None => Ok(Expr::Identifier(id, Position::eof())), @@ -1147,14 +1147,30 @@ fn parse_array_expr<'a>( } fn parse_primary<'a>(input: &mut Peekable>) -> Result { - match input.next() { + let token = input.next(); + + let mut follow_on = false; + + let r = match token { Some((Token::IntegerConstant(x), pos)) => Ok(Expr::IntegerConstant(x, pos)), Some((Token::FloatConstant(x), pos)) => Ok(Expr::FloatConstant(x, pos)), - Some((Token::StringConst(s), pos)) => Ok(Expr::StringConstant(s, pos)), Some((Token::CharConstant(c), pos)) => Ok(Expr::CharConstant(c, pos)), - Some((Token::Identifier(s), pos)) => parse_ident_expr(s, input, pos), - Some((Token::LeftParen, pos)) => parse_paren_expr(input, pos), - Some((Token::LeftBracket, pos)) => parse_array_expr(input, pos), + Some((Token::StringConst(s), pos)) => { + follow_on = true; + Ok(Expr::StringConstant(s, pos)) + } + Some((Token::Identifier(s), pos)) => { + follow_on = true; + parse_ident_expr(s, input, pos) + } + Some((Token::LeftParen, pos)) => { + follow_on = true; + parse_paren_expr(input, pos) + } + Some((Token::LeftBracket, pos)) => { + follow_on = true; + parse_array_expr(input, pos) + } Some((Token::True, pos)) => Ok(Expr::True(pos)), Some((Token::False, pos)) => Ok(Expr::False(pos)), Some((Token::LexError(le), pos)) => { @@ -1165,6 +1181,20 @@ fn parse_primary<'a>(input: &mut Peekable>) -> Result Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())), + }?; + + if !follow_on { + return Ok(r); + } + + // Post processing + match input.peek() { + Some(&(Token::LeftBracket, _)) => { + // Possible indexing + input.next(); + parse_index_expr(Box::new(r), input) + } + _ => Ok(r), } } diff --git a/src/result.rs b/src/result.rs index eb69c985..f7d6a8b9 100644 --- a/src/result.rs +++ b/src/result.rs @@ -132,16 +132,24 @@ impl std::fmt::Display for EvalAltResult { write!(f, "{}: {} < 0 ({})", desc, index, pos) } Self::ErrorArrayBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), - Self::ErrorArrayBounds(max, index, pos) => { - write!(f, "{} (max {}): {} ({})", desc, max - 1, index, pos) - } + Self::ErrorArrayBounds(max, index, pos) => write!( + f, + "Array index {} is out of bounds: max {} elements ({})", + index, + max - 1, + pos + ), Self::ErrorStringBounds(_, index, pos) if *index < 0 => { write!(f, "{}: {} < 0 ({})", desc, index, pos) } Self::ErrorStringBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), - Self::ErrorStringBounds(max, index, pos) => { - write!(f, "{} (max {}): {} ({})", desc, max - 1, index, pos) - } + Self::ErrorStringBounds(max, index, pos) => write!( + f, + "String index {} is out of bounds: max {} characters ({})", + index, + max - 1, + pos + ), } } } From 3d3b939ba68147918ae7b8add365613686e0862c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 6 Mar 2020 01:05:02 +0800 Subject: [PATCH 18/24] Simplify code, document logic, refactor and better error messages. --- src/engine.rs | 264 ++++++++++++++++++++++++++------------------------ src/error.rs | 17 ++-- src/parser.rs | 82 ++++++++++++---- src/scope.rs | 7 ++ 4 files changed, 220 insertions(+), 150 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 74654cd5..2b9abf1c 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -105,21 +105,16 @@ impl Engine<'_> { if let Some(f) = fn_def { match *f { FnIntExt::Ext(ref f) => { - let r = f(args, pos); - - if r.is_err() { - return r; - } + let r = f(args, pos)?; let callback = match spec.name.as_ref() { KEYWORD_PRINT => self.on_print.as_mut(), KEYWORD_DEBUG => self.on_debug.as_mut(), - _ => return r, + _ => return Ok(r), }; Ok(callback( - &r.unwrap() - .downcast::() + &r.downcast::() .map(|s| *s) .unwrap_or("error: not a string".into()), ) @@ -172,6 +167,7 @@ impl Engine<'_> { } } + /// Chain-evaluate a dot setter fn get_dot_val_helper( &mut self, scope: &mut Scope, @@ -181,6 +177,7 @@ impl Engine<'_> { use std::iter::once; match dot_rhs { + // xxx.fn_name(args) Expr::FunctionCall(fn_name, args, def_value, pos) => { let mut args: Array = args .iter() @@ -194,17 +191,16 @@ impl Engine<'_> { self.call_fn_raw(fn_name, args, def_value.as_ref(), *pos) } + // xxx.id Expr::Identifier(id, pos) => { let get_fn_name = format!("get${}", id); self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) } + // xxx.lhs[idx_expr] Expr::Index(lhs, idx_expr) => { - let idx = *self - .eval_expr(scope, idx_expr)? - .downcast::() - .map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position()))?; + let idx = self.eval_index_value(scope, idx_expr)?; let (lhs_value, pos) = match lhs.as_ref() { Expr::Identifier(id, pos) => { @@ -220,18 +216,18 @@ impl Engine<'_> { Self::get_indexed_value(lhs_value, idx, pos).map(|(v, _)| v) } - Expr::Dot(inner_lhs, inner_rhs) => match inner_lhs.as_ref() { + // xxx.lhs.rhs + Expr::Dot(lhs, rhs) => match lhs.as_ref() { + // xxx.id.rhs Expr::Identifier(id, pos) => { let get_fn_name = format!("get${}", id); self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) - .and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), inner_rhs)) + .and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), rhs)) } + // xxx.lhs[idx_expr].rhs Expr::Index(lhs, idx_expr) => { - let idx = *self - .eval_expr(scope, idx_expr)? - .downcast::() - .map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position()))?; + let idx = self.eval_index_value(scope, idx_expr)?; let (lhs_value, pos) = match lhs.as_ref() { Expr::Identifier(id, pos) => { @@ -245,16 +241,19 @@ impl Engine<'_> { }; Self::get_indexed_value(lhs_value, idx, pos).and_then(|(mut value, _)| { - self.get_dot_val_helper(scope, value.as_mut(), inner_rhs) + self.get_dot_val_helper(scope, value.as_mut(), rhs) }) } - _ => Err(EvalAltResult::ErrorDotExpr(inner_lhs.position())), + // Syntax error + _ => Err(EvalAltResult::ErrorDotExpr(lhs.position())), }, + // Syntax error _ => Err(EvalAltResult::ErrorDotExpr(dot_rhs.position())), } } + /// Search for a variable within the scope, returning its value and index inside the Scope fn search_scope( scope: &Scope, id: &str, @@ -267,13 +266,26 @@ impl Engine<'_> { .and_then(move |(idx, _, val)| map(val).map(|v| (idx, v))) } + /// Evaluate the value of an index (must evaluate to i64) + fn eval_index_value( + &mut self, + scope: &mut Scope, + idx_expr: &Expr, + ) -> Result { + self.eval_expr(scope, idx_expr)? + .downcast::() + .map(|v| *v) + .map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position())) + } + + /// Get the value at the indexed position of a base type fn get_indexed_value( val: Dynamic, idx: i64, pos: Position, ) -> Result<(Dynamic, VariableType), EvalAltResult> { if val.is::() { - let arr = val.downcast::().unwrap(); + let arr = val.downcast::().expect("Array expected"); if idx >= 0 { arr.get(idx as usize) @@ -284,7 +296,7 @@ impl Engine<'_> { Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, pos)) } } else if val.is::() { - let s = val.downcast::().unwrap(); + let s = val.downcast::().expect("String expected"); if idx >= 0 { s.chars() @@ -303,16 +315,14 @@ impl Engine<'_> { } } + /// Evaluate an index expression fn eval_index_expr( &mut self, scope: &mut Scope, lhs: &Expr, idx_expr: &Expr, ) -> Result<(VariableType, Option<(String, usize)>, usize, Dynamic), EvalAltResult> { - let idx = *self - .eval_expr(scope, idx_expr)? - .downcast::() - .map_err(|_| EvalAltResult::ErrorIndexExpr(idx_expr.position()))?; + let idx = self.eval_index_value(scope, idx_expr)?; match lhs { Expr::Identifier(id, _) => Self::search_scope( @@ -330,9 +340,10 @@ impl Engine<'_> { } } + /// Replace a character at an index position in a mutable string fn str_replace_char(s: &mut String, idx: usize, new_ch: char) { // The new character - let ch = s.chars().nth(idx).unwrap(); + let ch = s.chars().nth(idx).expect("string index out of bounds"); // See if changed - if so, update the String if ch == new_ch { @@ -346,6 +357,35 @@ impl Engine<'_> { chars.iter().for_each(|&ch| s.push(ch)); } + /// Update the value at an index position in a variable inside the scope + fn update_indexed_variable_in_scope( + source_type: VariableType, + scope: &mut Scope, + id: &str, + src_idx: usize, + idx: usize, + val: Dynamic, + ) -> Option { + match source_type { + VariableType::Array => { + let arr = scope.get_mut_by_type::(id, src_idx); + Some((arr[idx as usize] = val).into_dynamic()) + } + + VariableType::String => { + let s = scope.get_mut_by_type::(id, src_idx); + // Value must be a character + let ch = *val + .downcast::() + .expect("value to update an index position in a string must be a char"); + Some(Self::str_replace_char(s, idx as usize, ch).into_dynamic()) + } + + _ => None, + } + } + + /// Evaluate a dot chain getter fn get_dot_val( &mut self, scope: &mut Scope, @@ -353,6 +393,7 @@ impl Engine<'_> { dot_rhs: &Expr, ) -> Result { match dot_lhs { + // xxx.??? Expr::Identifier(id, pos) => { let (sc_idx, mut target) = Self::search_scope(scope, id, Ok, *pos)?; let value = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs); @@ -364,6 +405,7 @@ impl Engine<'_> { value } + // lhs[idx_expr].??? Expr::Index(lhs, idx_expr) => { let (source_type, src, idx, mut target) = self.eval_index_expr(scope, lhs, idx_expr)?; @@ -371,39 +413,27 @@ impl Engine<'_> { // In case the expression mutated `target`, we need to reassign it because // of the above `clone`. - - match source_type { - VariableType::Array => { - let src = src.unwrap(); - scope - .get_mut(&src.0, src.1) - .downcast_mut::() - .unwrap()[idx] = target - } - - VariableType::String => { - let src = src.unwrap(); - - Self::str_replace_char( - scope - .get_mut(&src.0, src.1) - .downcast_mut::() - .unwrap(), // Root is a string - idx, - *target.downcast::().unwrap(), // Target should be a char - ) - } - - _ => panic!("source_type must be either Array or String"), + if let Some((id, src_idx)) = src { + Self::update_indexed_variable_in_scope( + source_type, + scope, + &id, + src_idx, + idx, + target, + ) + .expect("source_type must be either Array or String"); } value } + // Syntax error _ => Err(EvalAltResult::ErrorDotExpr(dot_lhs.position())), } } + /// Chain-evaluate a dot setter fn set_dot_val_helper( &mut self, this_ptr: &mut Variant, @@ -411,6 +441,7 @@ impl Engine<'_> { mut source_val: Dynamic, ) -> Result { match dot_rhs { + // xxx.id Expr::Identifier(id, pos) => { let set_fn_name = format!("set${}", id); @@ -422,13 +453,14 @@ impl Engine<'_> { ) } - Expr::Dot(inner_lhs, inner_rhs) => match inner_lhs.as_ref() { + // xxx.lhs.rhs + Expr::Dot(lhs, rhs) => match lhs.as_ref() { Expr::Identifier(id, pos) => { let get_fn_name = format!("get${}", id); self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) .and_then(|mut v| { - self.set_dot_val_helper(v.as_mut(), inner_rhs, source_val) + self.set_dot_val_helper(v.as_mut(), rhs, source_val) .map(|_| v) // Discard Ok return value }) .and_then(|mut v| { @@ -437,13 +469,15 @@ impl Engine<'_> { self.call_fn_raw(&set_fn_name, vec![this_ptr, v.as_mut()], None, *pos) }) } - _ => Err(EvalAltResult::ErrorDotExpr(inner_lhs.position())), + _ => Err(EvalAltResult::ErrorDotExpr(lhs.position())), }, + // Syntax error _ => Err(EvalAltResult::ErrorDotExpr(dot_rhs.position())), } } + // Evaluate a dot chain setter fn set_dot_val( &mut self, scope: &mut Scope, @@ -452,6 +486,7 @@ impl Engine<'_> { source_val: Dynamic, ) -> Result { match dot_lhs { + // id.??? Expr::Identifier(id, pos) => { let (sc_idx, mut target) = Self::search_scope(scope, id, Ok, *pos)?; let value = self.set_dot_val_helper(target.as_mut(), dot_rhs, source_val); @@ -463,6 +498,7 @@ impl Engine<'_> { value } + // lhs[idx_expr].??? Expr::Index(lhs, idx_expr) => { let (source_type, src, idx, mut target) = self.eval_index_expr(scope, lhs, idx_expr)?; @@ -471,39 +507,27 @@ impl Engine<'_> { // In case the expression mutated `target`, we need to reassign it because // of the above `clone`. - match source_type { - VariableType::Array => { - let src = src.unwrap(); - let val = scope - .get_mut(&src.0, src.1) - .downcast_mut::() - .unwrap(); - val[idx] = target - } - - VariableType::String => { - let src = src.unwrap(); - - Self::str_replace_char( - scope - .get_mut(&src.0, src.1) - .downcast_mut::() - .unwrap(), // Root is a string - idx, - *target.downcast::().unwrap(), // Target should be a char - ) - } - - _ => panic!("source_type must be either Array or String"), + if let Some((id, src_idx)) = src { + Self::update_indexed_variable_in_scope( + source_type, + scope, + &id, + src_idx, + idx, + target, + ) + .expect("source_type must be either Array or String"); } value } + // Syntax error _ => Err(EvalAltResult::ErrorDotExpr(dot_lhs.position())), } } + /// Evaluate an expression fn eval_expr(&mut self, scope: &mut Scope, expr: &Expr) -> Result { match expr { Expr::IntegerConstant(i, _) => Ok((*i).into_dynamic()), @@ -517,10 +541,12 @@ impl Engine<'_> { .eval_index_expr(scope, lhs, idx_expr) .map(|(_, _, _, x)| x), + // lhs = rhs Expr::Assignment(lhs, rhs) => { let rhs_val = self.eval_expr(scope, rhs)?; match lhs.as_ref() { + // name = rhs Expr::Identifier(name, pos) => { if let Some((idx, _, _)) = scope.get(name) { *scope.get_mut(name, idx) = rhs_val; @@ -530,50 +556,34 @@ impl Engine<'_> { } } + // idx_lhs[idx_expr] = rhs Expr::Index(idx_lhs, idx_expr) => { let (source_type, src, idx, _) = self.eval_index_expr(scope, idx_lhs, idx_expr)?; - match source_type { - VariableType::Array => { - let src = src.unwrap(); - scope - .get_mut(&src.0, src.1) - .downcast_mut::() - .map(|arr| (arr[idx as usize] = rhs_val).into_dynamic()) - .ok_or_else(|| { - EvalAltResult::ErrorIndexExpr(idx_lhs.position()) - }) - } - - VariableType::String => { - let src = src.unwrap(); - scope - .get_mut(&src.0, src.1) - .downcast_mut::() - .map(|s| { - Self::str_replace_char( - s, - idx as usize, - *rhs_val.downcast::().unwrap(), - ) - .into_dynamic() - }) - .ok_or_else(|| { - EvalAltResult::ErrorIndexExpr(idx_lhs.position()) - }) - } - - _ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS( - idx_lhs.position(), - )), + if let Some((id, src_idx)) = src { + Self::update_indexed_variable_in_scope( + source_type, + scope, + &id, + src_idx, + idx, + rhs_val, + ) + } else { + None } + .ok_or_else(|| { + EvalAltResult::ErrorAssignmentToUnknownLHS(idx_lhs.position()) + }) } + // dot_lhs.dot_rhs = rhs Expr::Dot(dot_lhs, dot_rhs) => { self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val) } + // Syntax error _ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS(lhs.position())), } } @@ -615,12 +625,13 @@ impl Engine<'_> { .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position()) })? - && *self - .eval_expr(scope, &*rhs)? - .downcast::() - .map_err(|_| { - EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position()) - })?, + && // Short-circuit using && + *self + .eval_expr(scope, &*rhs)? + .downcast::() + .map_err(|_| { + EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position()) + })?, )), Expr::Or(lhs, rhs) => Ok(Box::new( @@ -630,12 +641,13 @@ impl Engine<'_> { .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position()) })? - || *self - .eval_expr(scope, &*rhs)? - .downcast::() - .map_err(|_| { - EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position()) - })?, + || // Short-circuit using || + *self + .eval_expr(scope, &*rhs)? + .downcast::() + .map_err(|_| { + EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position()) + })?, )), Expr::True(_) => Ok(true.into_dynamic()), @@ -644,6 +656,7 @@ impl Engine<'_> { } } + /// Evaluate a statement pub(crate) fn eval_stmt( &mut self, scope: &mut Scope, @@ -679,8 +692,8 @@ impl Engine<'_> { .and_then(|guard_val| { if *guard_val { self.eval_stmt(scope, body) - } else if else_body.is_some() { - self.eval_stmt(scope, else_body.as_ref().unwrap()) + } else if let Some(stmt) = else_body { + self.eval_stmt(scope, stmt.as_ref()) } else { Ok(().into_dynamic()) } @@ -775,6 +788,7 @@ impl Engine<'_> { } } + /// Map a type_name into a pretty-print name pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str { self.type_names .get(name) diff --git a/src/error.rs b/src/error.rs index 6314a031..d6410031 100644 --- a/src/error.rs +++ b/src/error.rs @@ -56,13 +56,13 @@ pub enum ParseErrorType { /// An unknown operator is encountered. Wrapped value is the operator. UnknownOperator(String), /// An open `(` is missing the corresponding closing `)`. - MissingRightParen, + MissingRightParen(String), /// Expecting `(` but not finding one. MissingLeftBrace, /// An open `{` is missing the corresponding closing `}`. - MissingRightBrace, + MissingRightBrace(String), /// An open `[` is missing the corresponding closing `]`. - MissingRightBracket, + MissingRightBracket(String), /// An expression in function call arguments `()` has syntax error. MalformedCallExpr, /// An expression in indexing brackets `[]` has syntax error. @@ -104,10 +104,10 @@ impl Error for ParseError { ParseErrorType::BadInput(ref p) => p, ParseErrorType::InputPastEndOfFile => "Script is incomplete", ParseErrorType::UnknownOperator(_) => "Unknown operator", - ParseErrorType::MissingRightParen => "Expecting ')'", + ParseErrorType::MissingRightParen(_) => "Expecting ')'", ParseErrorType::MissingLeftBrace => "Expecting '{'", - ParseErrorType::MissingRightBrace => "Expecting '}'", - ParseErrorType::MissingRightBracket => "Expecting ']'", + ParseErrorType::MissingRightBrace(_) => "Expecting '}'", + ParseErrorType::MissingRightBracket(_) => "Expecting ']'", ParseErrorType::MalformedCallExpr => "Invalid expression in function call arguments", ParseErrorType::MalformedIndexExpr => "Invalid index in indexing expression", ParseErrorType::VarExpectsIdentifier => "Expecting name of a variable", @@ -130,6 +130,11 @@ impl fmt::Display for ParseError { ParseErrorType::FnMissingParams(ref s) => { write!(f, "Missing parameters for function '{}'", s)? } + ParseErrorType::MissingRightParen(ref s) + | ParseErrorType::MissingRightBrace(ref s) + | ParseErrorType::MissingRightBracket(ref s) => { + write!(f, "{} for {}", self.description(), s)? + } _ => write!(f, "{}", self.description())?, } diff --git a/src/parser.rs b/src/parser.rs index 855c2bca..4a634d90 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1043,7 +1043,16 @@ fn parse_paren_expr<'a>( match input.next() { Some((Token::RightParen, _)) => Ok(expr), - _ => Err(ParseError::new(PERR::MissingRightParen, Position::eof())), + Some((_, pos)) => { + return Err(ParseError::new( + PERR::MissingRightParen("a matching ( in the expression".into()), + pos, + )) + } + None => Err(ParseError::new( + PERR::MissingRightParen("a matching ( in the expression".into()), + Position::eof(), + )), } } @@ -1068,8 +1077,24 @@ fn parse_call_expr<'a>( return Ok(Expr::FunctionCall(id, args, None, begin)); } Some(&(Token::Comma, _)) => (), - Some(&(_, pos)) => return Err(ParseError::new(PERR::MalformedCallExpr, pos)), - None => return Err(ParseError::new(PERR::MalformedCallExpr, Position::eof())), + Some(&(_, pos)) => { + return Err(ParseError::new( + PERR::MissingRightParen(format!( + "closing the arguments list to function call of '{}'", + id + )), + pos, + )) + } + None => { + return Err(ParseError::new( + PERR::MissingRightParen(format!( + "closing the arguments list to function call of '{}'", + id + )), + Position::eof(), + )) + } } input.next(); @@ -1080,17 +1105,24 @@ fn parse_index_expr<'a>( lhs: Box, input: &mut Peekable>, ) -> Result { - match parse_expr(input) { - Ok(idx_expr) => match input.peek() { - Some(&(Token::RightBracket, _)) => { - input.next(); - return Ok(Expr::Index(lhs, Box::new(idx_expr))); - } - Some(&(_, pos)) => return Err(ParseError::new(PERR::MalformedIndexExpr, pos)), - None => return Err(ParseError::new(PERR::MalformedIndexExpr, Position::eof())), - }, - Err(err) => return Err(ParseError::new(PERR::MalformedIndexExpr, err.position())), - } + parse_expr(input).and_then(|idx_expr| match input.peek() { + Some(&(Token::RightBracket, _)) => { + input.next(); + return Ok(Expr::Index(lhs, Box::new(idx_expr))); + } + Some(&(_, pos)) => { + return Err(ParseError::new( + PERR::MissingRightBracket("index expression".into()), + pos, + )) + } + None => { + return Err(ParseError::new( + PERR::MissingRightBracket("index expression".into()), + Position::eof(), + )) + } + }) } fn parse_ident_expr<'a>( @@ -1141,8 +1173,14 @@ fn parse_array_expr<'a>( input.next(); Ok(Expr::Array(arr, begin)) } - Some(&(_, pos)) => Err(ParseError::new(PERR::MissingRightBracket, pos)), - None => Err(ParseError::new(PERR::MissingRightBracket, Position::eof())), + Some(&(_, pos)) => Err(ParseError::new( + PERR::MissingRightBracket("the end of array literal".into()), + pos, + )), + None => Err(ParseError::new( + PERR::MissingRightBracket("the end of array literal".into()), + Position::eof(), + )), } } @@ -1462,7 +1500,7 @@ fn parse_binary_op<'a>( } token => { return Err(ParseError::new( - PERR::UnknownOperator(token.syntax().to_string()), + PERR::UnknownOperator(token.syntax().into()), pos, )) } @@ -1599,8 +1637,14 @@ fn parse_block<'a>(input: &mut Peekable>) -> Result Err(ParseError::new(PERR::MissingRightBrace, pos)), - None => Err(ParseError::new(PERR::MissingRightBrace, Position::eof())), + Some(&(_, pos)) => Err(ParseError::new( + PERR::MissingRightBrace("end of block".into()), + pos, + )), + None => Err(ParseError::new( + PERR::MissingRightBrace("end of block".into()), + Position::eof(), + )), } } diff --git a/src/scope.rs b/src/scope.rs index bafba6ed..9e7f63dc 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -85,6 +85,13 @@ impl Scope { &mut entry.1 } + /// Get a mutable reference to a variable in the Scope and downcast it to a specific type + pub(crate) fn get_mut_by_type(&mut self, key: &str, index: usize) -> &mut T { + self.get_mut(key, index) + .downcast_mut::() + .expect("wrong type cast") + } + /// Get an iterator to variables in the Scope. pub fn iter(&self) -> impl Iterator { self.0 From c7801e1d7e787cd9a5b679a9fa65d31625143d29 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 6 Mar 2020 09:55:00 +0800 Subject: [PATCH 19/24] Fix off-by-one error message on indexing. --- src/result.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/result.rs b/src/result.rs index f7d6a8b9..ae05ee4b 100644 --- a/src/result.rs +++ b/src/result.rs @@ -134,9 +134,10 @@ impl std::fmt::Display for EvalAltResult { Self::ErrorArrayBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), Self::ErrorArrayBounds(max, index, pos) => write!( f, - "Array index {} is out of bounds: max {} elements ({})", + "Array index {} is out of bounds: only {} element{} in the array ({})", index, - max - 1, + max, + if *max > 1 { "s" } else { "" }, pos ), Self::ErrorStringBounds(_, index, pos) if *index < 0 => { @@ -145,9 +146,10 @@ impl std::fmt::Display for EvalAltResult { Self::ErrorStringBounds(max, _, pos) if *max == 0 => write!(f, "{} ({})", desc, pos), Self::ErrorStringBounds(max, index, pos) => write!( f, - "String index {} is out of bounds: max {} characters ({})", + "String index {} is out of bounds: only {} character{} in the string ({})", index, - max - 1, + max, + if *max > 1 { "s" } else { "" }, pos ), } From d8ec7ed14183b980d8107ef44f883680504d63b5 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 6 Mar 2020 10:50:20 +0800 Subject: [PATCH 20/24] Rephrase error messages. --- src/engine.rs | 30 ++++++++++++++++-------------- src/error.rs | 2 +- src/result.rs | 6 +++--- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 2b9abf1c..48b57199 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -202,7 +202,7 @@ impl Engine<'_> { Expr::Index(lhs, idx_expr) => { let idx = self.eval_index_value(scope, idx_expr)?; - let (lhs_value, pos) = match lhs.as_ref() { + let (lhs_value, _) = match lhs.as_ref() { Expr::Identifier(id, pos) => { let get_fn_name = format!("get${}", id); ( @@ -213,7 +213,7 @@ impl Engine<'_> { expr => return Err(EvalAltResult::ErrorDotExpr(expr.position())), }; - Self::get_indexed_value(lhs_value, idx, pos).map(|(v, _)| v) + Self::get_indexed_value(lhs_value, idx, idx_expr.position()).map(|(v, _)| v) } // xxx.lhs.rhs @@ -229,7 +229,7 @@ impl Engine<'_> { Expr::Index(lhs, idx_expr) => { let idx = self.eval_index_value(scope, idx_expr)?; - let (lhs_value, pos) = match lhs.as_ref() { + let (lhs_value, _) = match lhs.as_ref() { Expr::Identifier(id, pos) => { let get_fn_name = format!("get${}", id); ( @@ -240,9 +240,9 @@ impl Engine<'_> { expr => return Err(EvalAltResult::ErrorDotExpr(expr.position())), }; - Self::get_indexed_value(lhs_value, idx, pos).and_then(|(mut value, _)| { - self.get_dot_val_helper(scope, value.as_mut(), rhs) - }) + Self::get_indexed_value(lhs_value, idx, idx_expr.position()).and_then( + |(mut value, _)| self.get_dot_val_helper(scope, value.as_mut(), rhs), + ) } // Syntax error _ => Err(EvalAltResult::ErrorDotExpr(lhs.position())), @@ -285,7 +285,7 @@ impl Engine<'_> { pos: Position, ) -> Result<(Dynamic, VariableType), EvalAltResult> { if val.is::() { - let arr = val.downcast::().expect("Array expected"); + let arr = val.downcast::().expect("array expected"); if idx >= 0 { arr.get(idx as usize) @@ -296,7 +296,7 @@ impl Engine<'_> { Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, pos)) } } else if val.is::() { - let s = val.downcast::().expect("String expected"); + let s = val.downcast::().expect("string expected"); if idx >= 0 { s.chars() @@ -311,7 +311,7 @@ impl Engine<'_> { )) } } else { - Err(EvalAltResult::ErrorIndexing(pos)) + Err(EvalAltResult::ErrorIndexingType(pos)) } } @@ -325,17 +325,19 @@ impl Engine<'_> { let idx = self.eval_index_value(scope, idx_expr)?; match lhs { + // id[idx_expr] Expr::Identifier(id, _) => Self::search_scope( scope, &id, - |val| Self::get_indexed_value(val, idx, lhs.position()), + |val| Self::get_indexed_value(val, idx, idx_expr.position()), lhs.position(), ) .map(|(src_idx, (val, source_type))| { (source_type, Some((id.clone(), src_idx)), idx as usize, val) }), - expr => Self::get_indexed_value(self.eval_expr(scope, expr)?, idx, lhs.position()) + // (expr)[idx_expr] + expr => Self::get_indexed_value(self.eval_expr(scope, expr)?, idx, idx_expr.position()) .map(|(val, _)| (VariableType::Expression, None, idx as usize, val)), } } @@ -377,7 +379,7 @@ impl Engine<'_> { // Value must be a character let ch = *val .downcast::() - .expect("value to update an index position in a string must be a char"); + .expect("char value expected to update an index position in a string"); Some(Self::str_replace_char(s, idx as usize, ch).into_dynamic()) } @@ -422,7 +424,7 @@ impl Engine<'_> { idx, target, ) - .expect("source_type must be either Array or String"); + .expect("array or string source type expected for indexing"); } value @@ -516,7 +518,7 @@ impl Engine<'_> { idx, target, ) - .expect("source_type must be either Array or String"); + .expect("array or string source_type expected for indexing"); } value diff --git a/src/error.rs b/src/error.rs index d6410031..3096040e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -128,7 +128,7 @@ impl fmt::Display for ParseError { ParseErrorType::BadInput(ref s) => write!(f, "{}", s)?, ParseErrorType::UnknownOperator(ref s) => write!(f, "{}: '{}'", self.description(), s)?, ParseErrorType::FnMissingParams(ref s) => { - write!(f, "Missing parameters for function '{}'", s)? + write!(f, "Expecting parameters for function '{}'", s)? } ParseErrorType::MissingRightParen(ref s) | ParseErrorType::MissingRightBrace(ref s) diff --git a/src/result.rs b/src/result.rs index ae05ee4b..1d869c8b 100644 --- a/src/result.rs +++ b/src/result.rs @@ -26,7 +26,7 @@ pub enum EvalAltResult { /// Wrapped values are the current number of characters in the string and the index number. ErrorStringBounds(usize, i64, Position), /// Trying to index into a type that is not an array and not a string. - ErrorIndexing(Position), + ErrorIndexingType(Position), /// Trying to index into an array or string with an index that is not `i64`. ErrorIndexExpr(Position), /// The guard expression in an `if` statement does not return a boolean value. @@ -65,7 +65,7 @@ impl Error for EvalAltResult { } Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands", Self::ErrorIndexExpr(_) => "Indexing into an array or string expects an integer index", - Self::ErrorIndexing(_) => "Indexing can only be performed on an array or a string", + Self::ErrorIndexingType(_) => "Indexing can only be performed on an array or a string", Self::ErrorArrayBounds(_, index, _) if *index < 0 => { "Array access expects non-negative index" } @@ -104,7 +104,7 @@ impl std::fmt::Display for EvalAltResult { match self { Self::ErrorFunctionNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), Self::ErrorVariableNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), - Self::ErrorIndexing(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorIndexingType(pos) => write!(f, "{} ({})", desc, pos), Self::ErrorIndexExpr(pos) => write!(f, "{} ({})", desc, pos), Self::ErrorIfGuard(pos) => write!(f, "{} ({})", desc, pos), Self::ErrorFor(pos) => write!(f, "{} ({})", desc, pos), From 07e396b3f52da2a027173253ca26e1449243253a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 6 Mar 2020 10:50:52 +0800 Subject: [PATCH 21/24] Allow chaining of array indexing operations. --- src/parser.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 4a634d90..204998b0 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1189,7 +1189,7 @@ fn parse_primary<'a>(input: &mut Peekable>) -> Result Ok(Expr::IntegerConstant(x, pos)), Some((Token::FloatConstant(x), pos)) => Ok(Expr::FloatConstant(x, pos)), Some((Token::CharConstant(c), pos)) => Ok(Expr::CharConstant(c, pos)), @@ -1222,18 +1222,16 @@ fn parse_primary<'a>(input: &mut Peekable>) -> Result { - // Possible indexing - input.next(); - parse_index_expr(Box::new(r), input) - } - _ => Ok(r), + // Tail processing all possible indexing + while let Some(&(Token::LeftBracket, _)) = input.peek() { + input.next(); + root_expr = parse_index_expr(Box::new(root_expr), input)?; } + + Ok(root_expr) } fn parse_unary<'a>(input: &mut Peekable>) -> Result { From 42fe5e8b95ce7f6585906205e75470a6bbfb1db4 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 6 Mar 2020 11:26:58 +0800 Subject: [PATCH 22/24] Add documentation of `call_fn` to README. --- README.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 07d19912..c78534a0 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Rhai's current feature set: * Support for overloaded functions * No additional dependencies -**Note:** Currently, the version is 0.10.1, so the language and API's may change before they stabilize. +**Note:** Currently, the version is 0.10.2, so the language and API's may change before they stabilize. ## Installation @@ -19,7 +19,7 @@ You can install Rhai using crates by adding this line to your dependencies: ```toml [dependencies] -rhai = "0.10.1" +rhai = "0.10.2" ``` or simply: @@ -117,7 +117,7 @@ You can also evaluate a script file: if let Ok(result) = engine.eval_file::("hello_world.rhai") { ... } ``` -If you want to repeatedly evaluate a script, you can compile it first into an AST form: +If you want to repeatedly evaluate a script, you can _compile_ it first into an AST (abstract syntax tree) form: ```rust // Compile to an AST and store it for later evaluations @@ -130,12 +130,25 @@ for _ in 0..42 { } ``` -Compiling a script file into AST is also supported: +Compiling a script file is also supported: ```rust let ast = Engine::compile_file("hello_world.rhai").unwrap(); ``` +Rhai also allows you to work _backwards_ from the other direction - i.e. calling a Rhai-scripted function from Rust. +You do this via `call_fn`, which takes a compiled AST (output from `compile`) and the +function call arguments: + +```rust +// Define a function in a script and compile to AST +let ast = Engine::compile("fn hello(x, y) { x.len() + y }")?; + +// Evaluate the function in the AST, passing arguments into the script as a tuple +// (beware, arguments must be of the correct types because Rhai does not have built-in type conversions) +let result: i64 = engine.call_fn("hello", ast, (&mut String::from("abc"), &mut 123_i64))?; +``` + # Values and types The following primitive types are supported natively: From cc39e559ae8814b7c7e0e117fbb1b3bdf389ef6f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 6 Mar 2020 15:49:38 +0800 Subject: [PATCH 23/24] Change debug print info for function registration. --- src/api.rs | 12 ++++++------ src/engine.rs | 7 ++++++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/api.rs b/src/api.rs index 49553624..b27378f2 100644 --- a/src/api.rs +++ b/src/api.rs @@ -17,13 +17,13 @@ impl<'a> Engine<'a> { f: Box, ) { debug_println!( - "Register function: {} ({})", + "Register function: {} for {} parameter(s)", fn_name, - args.iter() - .map(|x| (*x).type_name()) - .map(|name| self.map_type_name(name)) - .collect::>() - .join(", ") + if let Some(a) = &args { + format!("{}", a.len()) + } else { + "no".to_string() + } ); let spec = FnSpec { diff --git a/src/engine.rs b/src/engine.rs index 48b57199..b00db1f9 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -430,8 +430,13 @@ impl Engine<'_> { value } + // {expr}.??? + expr => { + let mut target = self.eval_expr(scope, expr)?; + self.get_dot_val_helper(scope, target.as_mut(), dot_rhs) + } // Syntax error - _ => Err(EvalAltResult::ErrorDotExpr(dot_lhs.position())), + //_ => Err(EvalAltResult::ErrorDotExpr(dot_lhs.position())), } } From d2296ab33bda8e4de7cb68f6a8daf17512e00494 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 6 Mar 2020 15:49:38 +0800 Subject: [PATCH 24/24] Allow dot operator on function results; change debug print info for function registration. --- src/api.rs | 12 ++++++------ src/engine.rs | 7 ++++++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/api.rs b/src/api.rs index 49553624..b27378f2 100644 --- a/src/api.rs +++ b/src/api.rs @@ -17,13 +17,13 @@ impl<'a> Engine<'a> { f: Box, ) { debug_println!( - "Register function: {} ({})", + "Register function: {} for {} parameter(s)", fn_name, - args.iter() - .map(|x| (*x).type_name()) - .map(|name| self.map_type_name(name)) - .collect::>() - .join(", ") + if let Some(a) = &args { + format!("{}", a.len()) + } else { + "no".to_string() + } ); let spec = FnSpec { diff --git a/src/engine.rs b/src/engine.rs index 48b57199..b00db1f9 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -430,8 +430,13 @@ impl Engine<'_> { value } + // {expr}.??? + expr => { + let mut target = self.eval_expr(scope, expr)?; + self.get_dot_val_helper(scope, target.as_mut(), dot_rhs) + } // Syntax error - _ => Err(EvalAltResult::ErrorDotExpr(dot_lhs.position())), + //_ => Err(EvalAltResult::ErrorDotExpr(dot_lhs.position())), } }