From a54fbb32ffbc85036ade91481f07703bb4087882 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 22 Apr 2021 23:02:25 +0800 Subject: [PATCH] Add no_position feature. --- .github/workflows/build.yml | 5 +- CHANGELOG.md | 1 + Cargo.toml | 3 +- src/ast.rs | 81 ++++++++++++++++++----- src/bin/README.md | 2 +- src/engine.rs | 2 + src/parser.rs | 14 ++-- src/token.rs | 127 +++++++++++++++++++++++++++--------- tests/maps.rs | 2 +- tests/optimizer.rs | 1 + tests/print.rs | 6 +- tests/string.rs | 6 +- 12 files changed, 189 insertions(+), 61 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8687601c..24607668 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,6 +21,7 @@ jobs: - "--features metadata,serde,internals" - "--features unchecked" - "--features sync" + - "--features no_position" - "--features no_optimize" - "--features no_float" - "--features f32_float,serde,metadata,internals" @@ -34,8 +35,8 @@ jobs: - "--features no_module" - "--features no_closure" - "--features unicode-xid-ident" - - "--features sync,no_function,no_float,no_optimize,no_module,no_closure,metadata,serde,unchecked" - - "--features no_function,no_float,no_index,no_object,no_optimize,no_module,no_closure,unchecked" + - "--features sync,no_function,no_float,no_position,no_optimize,no_module,no_closure,metadata,serde,unchecked" + - "--features no_function,no_float,no_position,no_index,no_object,no_optimize,no_module,no_closure,unchecked" toolchain: [stable] experimental: [false] include: diff --git a/CHANGELOG.md b/CHANGELOG.md index 142f71ce..65c55117 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ New features ------------ * A module called `global` is automatically created to hold global-level constants, which can then be accessed from functions. +* A new feature `no_position` is added to turn off position tracking during parsing to squeeze out the last drop of performance. Version 0.20.0 diff --git a/Cargo.toml b/Cargo.toml index e97fc7f7..eb0872db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,9 +23,10 @@ smartstring = { version = "0.2.6", default_features = false } rhai_codegen = { version = "0.3.4", path = "codegen", default_features = false } [features] -default = ["smartstring/std", "ahash/std", "num-traits/std"] # remove 'smartstring/std' when smartstring is updated to support no-std +default = ["no_position", "smartstring/std", "ahash/std", "num-traits/std"] # remove 'smartstring/std' when smartstring is updated to support no-std unchecked = [] # unchecked arithmetic sync = [] # restrict to only types that implement Send + Sync +no_position = [] # do not track position in the parser no_optimize = [] # no script optimizer no_float = [] # no floating-point f32_float = [] # set FLOAT=f32 diff --git a/src/ast.rs b/src/ast.rs index ddaba81a..0b56c64b 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -780,7 +780,12 @@ pub struct Ident { impl fmt::Debug for Ident { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?} @ {:?}", self.name, self.pos) + #[cfg(not(feature = "no_position"))] + write!(f, "{:?} @ {:?}", self.name, self.pos)?; + #[cfg(feature = "no_position")] + write!(f, "{:?}", self.name)?; + + Ok(()) } } @@ -1687,31 +1692,59 @@ impl Default for Expr { impl fmt::Debug for Expr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + #[cfg(not(feature = "no_position"))] Self::DynamicConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos), + #[cfg(not(feature = "no_position"))] Self::BoolConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos), + #[cfg(not(feature = "no_position"))] Self::IntegerConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos), #[cfg(not(feature = "no_float"))] + #[cfg(not(feature = "no_position"))] Self::FloatConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos), + #[cfg(not(feature = "no_position"))] Self::CharConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos), + #[cfg(not(feature = "no_position"))] Self::StringConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos), + #[cfg(not(feature = "no_position"))] Self::Unit(pos) => write!(f, "() @ {:?}", pos), + + #[cfg(feature = "no_position")] + Self::DynamicConstant(value, _) => write!(f, "{:?}", value), + #[cfg(feature = "no_position")] + Self::BoolConstant(value, _) => write!(f, "{:?}", value), + #[cfg(feature = "no_position")] + Self::IntegerConstant(value, _) => write!(f, "{:?}", value), + #[cfg(not(feature = "no_float"))] + #[cfg(feature = "no_position")] + Self::FloatConstant(value, _) => write!(f, "{:?}", value), + #[cfg(feature = "no_position")] + Self::CharConstant(value, _) => write!(f, "{:?}", value), + #[cfg(feature = "no_position")] + Self::StringConstant(value, _) => write!(f, "{:?}", value), + #[cfg(feature = "no_position")] + Self::Unit(_) => f.write_str("()"), + Self::InterpolatedString(x) => { f.write_str("InterpolatedString")?; f.debug_list().entries(x.iter()).finish() } - Self::Array(x, pos) => { + Self::Array(x, _pos) => { f.write_str("Array")?; f.debug_list().entries(x.iter()).finish()?; - write!(f, " @ {:?}", pos) + #[cfg(not(feature = "no_position"))] + write!(f, " @ {:?}", _pos)?; + Ok(()) } - Self::Map(x, pos) => { + Self::Map(x, _pos) => { f.write_str("Map")?; f.debug_map() .entries(x.0.iter().map(|(k, v)| (k, v))) .finish()?; - write!(f, " @ {:?}", pos) + #[cfg(not(feature = "no_position"))] + write!(f, " @ {:?}", _pos)?; + Ok(()) } - Self::Variable(i, pos, x) => { + Self::Variable(i, _pos, x) => { f.write_str("Variable(")?; match x.1 { Some((_, ref namespace)) => write!(f, "{}", namespace)?, @@ -1722,15 +1755,23 @@ impl fmt::Debug for Expr { Some(n) => write!(f, ", {}", n)?, _ => (), } - write!(f, ") @ {:?}", pos) + f.write_str(")")?; + #[cfg(not(feature = "no_position"))] + write!(f, " @ {:?}", _pos)?; + Ok(()) } + #[cfg(not(feature = "no_position"))] Self::Property(x) => write!(f, "Property({:?} @ {:?})", x.2.name, x.2.pos), + #[cfg(feature = "no_position")] + Self::Property(x) => write!(f, "Property({:?})", x.2.name), Self::Stmt(x) => { f.write_str("Stmt")?; f.debug_list().entries(x.0.iter()).finish()?; - write!(f, " @ {:?}", x.1) + #[cfg(not(feature = "no_position"))] + write!(f, " @ {:?}", x.1)?; + Ok(()) } - Self::FnCall(x, pos) => { + Self::FnCall(x, _pos) => { let mut ff = f.debug_struct("FnCall"); if let Some(ref ns) = x.namespace { ff.field("namespace", ns); @@ -1745,9 +1786,11 @@ impl fmt::Debug for Expr { ff.field("capture", &x.capture); } ff.finish()?; - write!(f, " @ {:?}", pos) + #[cfg(not(feature = "no_position"))] + write!(f, " @ {:?}", _pos)?; + Ok(()) } - Self::Dot(x, pos) | Self::Index(x, pos) | Self::And(x, pos) | Self::Or(x, pos) => { + Self::Dot(x, _pos) | Self::Index(x, _pos) | Self::And(x, _pos) | Self::Or(x, _pos) => { let op_name = match self { Self::Dot(_, _) => "Dot", Self::Index(_, _) => "Index", @@ -1760,11 +1803,15 @@ impl fmt::Debug for Expr { .field("lhs", &x.lhs) .field("rhs", &x.rhs) .finish()?; - write!(f, " @ {:?}", pos) + #[cfg(not(feature = "no_position"))] + write!(f, " @ {:?}", _pos)?; + Ok(()) } - Self::Custom(x, pos) => { + Self::Custom(x, _pos) => { f.debug_tuple("Custom").field(x).finish()?; - write!(f, " @ {:?}", pos) + #[cfg(not(feature = "no_position"))] + write!(f, " @ {:?}", _pos)?; + Ok(()) } } } @@ -2058,6 +2105,7 @@ mod tests { assert_eq!(size_of::(), 16); assert_eq!(size_of::>(), 16); + #[cfg(not(feature = "no_position"))] assert_eq!(size_of::(), 4); assert_eq!(size_of::(), 16); assert_eq!(size_of::>(), 16); @@ -2066,7 +2114,10 @@ mod tests { assert_eq!(size_of::(), 96); assert_eq!(size_of::(), 288); assert_eq!(size_of::(), 56); - assert_eq!(size_of::(), 16); + assert_eq!( + size_of::(), + if cfg!(feature = "no_position") { 8 } else { 16 } + ); assert_eq!(size_of::(), 72); } } diff --git a/src/bin/README.md b/src/bin/README.md index 1ba7e865..a0afbd8f 100644 --- a/src/bin/README.md +++ b/src/bin/README.md @@ -1,7 +1,7 @@ Rhai Tools ========== -Tools written in Rhai. +Tools for running Rhai scripts. How to Run diff --git a/src/engine.rs b/src/engine.rs index 2f5cf085..aaaa9300 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -839,6 +839,8 @@ fn default_debug(_s: &str, _source: Option<&str>, _pos: Position) { #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] if let Some(source) = _source { println!("{} @ {:?} | {}", source, _pos, _s); + } else if _pos.is_none() { + println!("{}", _s); } else { println!("{:?} | {}", _pos, _s); } diff --git a/src/parser.rs b/src/parser.rs index 7bb42b49..87082f7e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1415,20 +1415,20 @@ fn make_assignment_stmt<'a>( rhs: Expr, op_pos: Position, ) -> Result { - fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Position { + fn check_lvalue(expr: &Expr, parent_is_dot: bool) -> Option { match expr { Expr::Index(x, _) | Expr::Dot(x, _) if parent_is_dot => match x.lhs { Expr::Property(_) => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _))), - ref e => e.position(), + ref e => Some(e.position()), }, Expr::Index(x, _) | Expr::Dot(x, _) => match x.lhs { Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"), _ => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _))), }, - Expr::Property(_) if parent_is_dot => Position::NONE, + Expr::Property(_) if parent_is_dot => None, Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"), - e if parent_is_dot => e.position(), - _ => Position::NONE, + e if parent_is_dot => Some(e.position()), + _ => None, } } @@ -1464,7 +1464,7 @@ fn make_assignment_stmt<'a>( // xxx[???]... = rhs, xxx.prop... = rhs Expr::Index(x, _) | Expr::Dot(x, _) => { match check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _))) { - Position::NONE => match &x.lhs { + None => match &x.lhs { // var[???] (non-indexed) = rhs, var.??? (non-indexed) = rhs Expr::Variable(None, _, x) if x.0.is_none() => { Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos)) @@ -1488,7 +1488,7 @@ fn make_assignment_stmt<'a>( Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(expr.position())) } }, - pos => Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(pos)), + Some(pos) => Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(pos)), } } // ??? && ??? = rhs, ??? || ??? = rhs diff --git a/src/token.rs b/src/token.rs index 7b497665..72ee99bc 100644 --- a/src/token.rs +++ b/src/token.rs @@ -57,16 +57,28 @@ pub type TokenStream<'a> = Peekable>; #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] pub struct Position { /// Line number - 0 = none + #[cfg(not(feature = "no_position"))] line: u16, /// Character position - 0 = BOL + #[cfg(not(feature = "no_position"))] pos: u16, } impl Position { /// A [`Position`] representing no position. - pub const NONE: Self = Self { line: 0, pos: 0 }; + pub const NONE: Self = Self { + #[cfg(not(feature = "no_position"))] + line: 0, + #[cfg(not(feature = "no_position"))] + pos: 0, + }; /// A [`Position`] representing the first position. - pub const START: Self = Self { line: 1, pos: 0 }; + pub const START: Self = Self { + #[cfg(not(feature = "no_position"))] + line: 1, + #[cfg(not(feature = "no_position"))] + pos: 0, + }; /// Create a new [`Position`]. /// @@ -77,12 +89,14 @@ impl Position { /// /// Panics if `line` is zero. #[inline(always)] - pub fn new(line: u16, position: u16) -> Self { + pub fn new(line: u16, _position: u16) -> Self { assert!(line != 0, "line cannot be zero"); Self { + #[cfg(not(feature = "no_position"))] line, - pos: position, + #[cfg(not(feature = "no_position"))] + pos: _position, } } /// Get the line number (1-based), or [`None`] if there is no position. @@ -91,26 +105,40 @@ impl Position { if self.is_none() { None } else { - Some(self.line as usize) + #[cfg(not(feature = "no_position"))] + return Some(self.line as usize); + #[cfg(feature = "no_position")] + unreachable!(); } } /// Get the character position (1-based), or [`None`] if at beginning of a line. #[inline(always)] pub fn position(self) -> Option { - if self.is_none() || self.pos == 0 { + if self.is_none() { None } else { - Some(self.pos as usize) + #[cfg(not(feature = "no_position"))] + return if self.pos == 0 { + None + } else { + Some(self.pos as usize) + }; + + #[cfg(feature = "no_position")] + unreachable!(); } } /// Advance by one character position. #[inline(always)] pub(crate) fn advance(&mut self) { - assert!(!self.is_none(), "cannot advance Position::none"); + #[cfg(not(feature = "no_position"))] + { + assert!(!self.is_none(), "cannot advance Position::none"); - // Advance up to maximum position - if self.pos < u16::MAX { - self.pos += 1; + // Advance up to maximum position + if self.pos < u16::MAX { + self.pos += 1; + } } } /// Go backwards by one character position. @@ -120,30 +148,42 @@ impl Position { /// Panics if already at beginning of a line - cannot rewind to a previous line. #[inline(always)] pub(crate) fn rewind(&mut self) { - assert!(!self.is_none(), "cannot rewind Position::none"); - assert!(self.pos > 0, "cannot rewind at position 0"); - self.pos -= 1; + #[cfg(not(feature = "no_position"))] + { + assert!(!self.is_none(), "cannot rewind Position::none"); + assert!(self.pos > 0, "cannot rewind at position 0"); + self.pos -= 1; + } } /// Advance to the next line. #[inline(always)] pub(crate) fn new_line(&mut self) { - assert!(!self.is_none(), "cannot advance Position::none"); + #[cfg(not(feature = "no_position"))] + { + assert!(!self.is_none(), "cannot advance Position::none"); - // Advance up to maximum position - if self.line < u16::MAX { - self.line += 1; - self.pos = 0; + // Advance up to maximum position + if self.line < u16::MAX { + self.line += 1; + self.pos = 0; + } } } /// Is this [`Position`] at the beginning of a line? #[inline(always)] pub fn is_beginning_of_line(self) -> bool { - self.pos == 0 && !self.is_none() + #[cfg(not(feature = "no_position"))] + return self.pos == 0 && !self.is_none(); + #[cfg(feature = "no_position")] + return false; } /// Is there no [`Position`]? #[inline(always)] pub fn is_none(self) -> bool { - self == Self::NONE + #[cfg(not(feature = "no_position"))] + return self == Self::NONE; + #[cfg(feature = "no_position")] + return true; } } @@ -158,17 +198,27 @@ impl fmt::Display for Position { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_none() { - write!(f, "none") + write!(f, "none")?; } else { - write!(f, "line {}, position {}", self.line, self.pos) + #[cfg(not(feature = "no_position"))] + write!(f, "line {}, position {}", self.line, self.pos)?; + #[cfg(feature = "no_position")] + unreachable!(); } + + Ok(()) } } impl fmt::Debug for Position { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}:{}", self.line, self.pos) + #[cfg(not(feature = "no_position"))] + write!(f, "{}:{}", self.line, self.pos)?; + #[cfg(feature = "no_position")] + f.write_str("none")?; + + Ok(()) } } @@ -179,14 +229,17 @@ impl Add for Position { if rhs.is_none() { self } else { - Self { + #[cfg(not(feature = "no_position"))] + return Self { line: self.line + rhs.line - 1, pos: if rhs.is_beginning_of_line() { self.pos } else { self.pos + rhs.pos - 1 }, - } + }; + #[cfg(feature = "no_position")] + unreachable!(); } } } @@ -908,8 +961,9 @@ pub fn parse_string_literal( let mut escape = String::with_capacity(12); let start = *pos; - let mut skip_whitespace_until = 0; let mut interpolated = false; + #[cfg(not(feature = "no_position"))] + let mut skip_whitespace_until = 0; state.is_within_text_terminated_by = Some(termination_char); @@ -1044,7 +1098,11 @@ pub fn parse_string_literal( assert_eq!(escape, "\\", "unexpected escape {} at end of line", escape); escape.clear(); pos.new_line(); - skip_whitespace_until = start.position().unwrap() + 1; + + #[cfg(not(feature = "no_position"))] + { + skip_whitespace_until = start.position().unwrap() + 1; + } } // Unterminated string @@ -1062,13 +1120,18 @@ pub fn parse_string_literal( } // Whitespace to skip + #[cfg(not(feature = "no_position"))] _ if next_char.is_whitespace() && pos.position().unwrap() < skip_whitespace_until => {} // All other characters _ => { escape.clear(); result.push(next_char); - skip_whitespace_until = 0; + + #[cfg(not(feature = "no_position"))] + { + skip_whitespace_until = 0; + } } } } @@ -1241,7 +1304,7 @@ fn get_next_token_inner( ); } - let mut negated_pos = Position::NONE; + let mut negated: Option = None; while let Some(c) = stream.get_next() { pos.advance(); @@ -1350,7 +1413,7 @@ fn get_next_token_inner( } } - let num_pos = if !negated_pos.is_none() { + let num_pos = if let Some(negated_pos) = negated { result.insert(0, '-'); negated_pos } else { @@ -1511,7 +1574,7 @@ fn get_next_token_inner( ('+', _) if !state.non_unary => return Some((Token::UnaryPlus, start_pos)), ('+', _) => return Some((Token::Plus, start_pos)), - ('-', '0'..='9') if !state.non_unary => negated_pos = start_pos, + ('-', '0'..='9') if !state.non_unary => negated = Some(start_pos), ('-', '0'..='9') => return Some((Token::Minus, start_pos)), ('-', '=') => { eat_next(stream, pos); diff --git a/tests/maps.rs b/tests/maps.rs index f668bc30..4efea70f 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -206,7 +206,7 @@ fn test_map_json() -> Result<(), Box> { assert!(matches!( *engine.parse_json(" 123", true).expect_err("should error"), EvalAltResult::ErrorParsing(ParseErrorType::MissingToken(token, _), pos) - if token == "{" && pos.position() == Some(4) + if token == "{" )); Ok(()) diff --git a/tests/optimizer.rs b/tests/optimizer.rs index 2a457bff..91b29a5c 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -49,6 +49,7 @@ fn test_optimizer_run() -> Result<(), Box> { } #[cfg(not(feature = "no_module"))] +#[cfg(not(feature = "no_position"))] #[test] fn test_optimizer_parse() -> Result<(), Box> { let mut engine = Engine::new(); diff --git a/tests/print.rs b/tests/print.rs index d574d5fe..0bfcefcf 100644 --- a/tests/print.rs +++ b/tests/print.rs @@ -60,7 +60,11 @@ fn test_print_debug() -> Result<(), Box> { assert_eq!(logbook.read().unwrap()[0], "entry: 42"); assert_eq!( logbook.read().unwrap()[1], - r#"DEBUG of world at 1:19: "hello!""# + if cfg!(not(feature = "no_position")) { + r#"DEBUG of world at 1:19: "hello!""# + } else { + r#"DEBUG of world at none: "hello!""# + } ); for entry in logbook.read().unwrap().iter() { diff --git a/tests/string.rs b/tests/string.rs index 139b87a8..d60be0dc 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -14,7 +14,11 @@ fn test_string() -> Result<(), Box> { ); assert_eq!( engine.eval::(" \"Test string: \\u2764\\\n hello, world!\"")?, - "Test string: ❤ hello, world!" + if cfg!(not(feature = "no_position")) { + "Test string: ❤ hello, world!" + } else { + "Test string: ❤ hello, world!" + } ); assert_eq!( engine.eval::(" `Test string: \\u2764\nhello,\\nworld!`")?,