From 3f4dba9dbc4c998164cb14f226ce9bb6ef2bb73a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 25 Feb 2021 13:29:49 +0800 Subject: [PATCH] Build in operators between string and char. --- RELEASES.md | 4 +- src/fn_call.rs | 114 +++++++++++++++++++++++--------- src/packages/string_more.rs | 44 ++++++++----- src/utils.rs | 127 +++++++++++++++++++++++++++++++++++- tests/decrement.rs | 8 +-- tests/increment.rs | 2 +- tests/ops.rs | 14 +++- tests/string.rs | 13 ++++ 8 files changed, 271 insertions(+), 55 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 7158e27c..fc50ab94 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -22,7 +22,6 @@ New features * Functions are now allowed to have `Dynamic` arguments. * `#[rhai_fn(pure)]` attribute to mark a plugin function with `&mut` parameter as _pure_ so constants can be passed to it. Without it, passing a constant value into the `&mut` parameter will now raise an error. -* Comparisons between `FLOAT`/[`Decimal`](https://crates.io/crates/rust_decimal) and `INT` are now built in. Enhancements ------------ @@ -31,8 +30,11 @@ Enhancements * Error position in `eval` statements is now wrapped in an `EvalAltResult::ErrorInFunctionCall`. * `Position` now implements `Add` and `AddAssign`. * `Scope` now implements `IntoIterator`. +* Strings now have the `-`/`-=` operators and the `remove` method to delete a sub-string/character. * Strings now have the `split_rev` method and variations of `split` with maximum number of segments. * Arrays now have the `split` method. +* Comparisons between `FLOAT`/[`Decimal`](https://crates.io/crates/rust_decimal) and `INT` are now built in. +* Comparisons between string and `char` are now built in. Version 0.19.12 diff --git a/src/fn_call.rs b/src/fn_call.rs index a7fc94ba..26956964 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -17,7 +17,6 @@ use crate::stdlib::{ iter::{empty, once}, mem, num::NonZeroU64, - ops::Deref, string::ToString, vec::Vec, }; @@ -1470,28 +1469,60 @@ pub fn run_builtin_binary_op( } } + // char op string + if types_pair == (TypeId::of::(), TypeId::of::()) { + let x = x.clone().cast::(); + let y = &*y.read_lock::().unwrap(); + + match op { + "+" => return Ok(Some(format!("{}{}", x, y).into())), + "==" | "!=" | ">" | ">=" | "<" | "<=" => { + let s1 = [x, '\0']; + let mut y = y.chars(); + let s2 = [y.next().unwrap_or('\0'), y.next().unwrap_or('\0')]; + + match op { + "==" => return Ok(Some((s1 == s2).into())), + "!=" => return Ok(Some((s1 != s2).into())), + ">" => return Ok(Some((s1 > s2).into())), + ">=" => return Ok(Some((s1 >= s2).into())), + "<" => return Ok(Some((s1 < s2).into())), + "<=" => return Ok(Some((s1 <= s2).into())), + _ => unreachable!(), + } + } + _ => return Ok(None), + } + } + // string op char + if types_pair == (TypeId::of::(), TypeId::of::()) { + let x = &*x.read_lock::().unwrap(); + let y = y.clone().cast::(); + + match op { + "+" => return Ok(Some((x + y).into())), + "-" => return Ok(Some((x - y).into())), + "==" | "!=" | ">" | ">=" | "<" | "<=" => { + let mut x = x.chars(); + let s1 = [x.next().unwrap_or('\0'), x.next().unwrap_or('\0')]; + let s2 = [y, '\0']; + + match op { + "==" => return Ok(Some((s1 == s2).into())), + "!=" => return Ok(Some((s1 != s2).into())), + ">" => return Ok(Some((s1 > s2).into())), + ">=" => return Ok(Some((s1 >= s2).into())), + "<" => return Ok(Some((s1 < s2).into())), + "<=" => return Ok(Some((s1 <= s2).into())), + _ => unreachable!(), + } + } + _ => return Ok(None), + } + } + + // Default comparison operators for different types if type2 != type1 { - // char op string - if types_pair == (TypeId::of::(), TypeId::of::()) { - let x = x.clone().cast::(); - let y = &*y.read_lock::().unwrap(); - - match op { - "+" => return Ok(Some(format!("{}{}", x, y).into())), - _ => return Ok(None), - } - } - // string op char - if types_pair == (TypeId::of::(), TypeId::of::()) { - let x = &*x.read_lock::().unwrap(); - let y = y.clone().cast::(); - - match op { - "+" => return Ok(Some((x + y).into())), - _ => return Ok(None), - } - } - // Default comparison operators for different types return Ok(match op { "!=" => Some(Dynamic::TRUE), "==" | ">" | ">=" | "<" | "<=" => Some(Dynamic::FALSE), @@ -1567,6 +1598,7 @@ pub fn run_builtin_binary_op( match op { "+" => return Ok(Some((x + y).into())), + "-" => return Ok(Some((x - y).into())), "==" => return Ok(Some((x == y).into())), "!=" => return Ok(Some((x != y).into())), ">" => return Ok(Some((x > y).into())), @@ -1673,17 +1705,34 @@ pub fn run_builtin_op_assignment( } } - if type2 != type1 { - if types_pair == (TypeId::of::(), TypeId::of::()) { - let y = y.read_lock::().unwrap().deref().clone(); - let mut x = x.write_lock::().unwrap(); + // string op= char + if types_pair == (TypeId::of::(), TypeId::of::()) { + let y = y.clone().cast::(); + let mut x = x.write_lock::().unwrap(); - match op { - "+=" => return Ok(Some(*x += y)), - _ => return Ok(None), - } + match op { + "+=" => return Ok(Some(*x += y)), + "-=" => return Ok(Some(*x -= y)), + _ => return Ok(None), } + } + // char op= string + if types_pair == (TypeId::of::(), TypeId::of::()) { + let y = y.read_lock::().unwrap(); + let mut ch = x.read_lock::().unwrap().to_string(); + let mut x = x.write_lock::().unwrap(); + match op { + "+=" => { + ch.push_str(y.as_str()); + return Ok(Some(*x = ch.into())); + } + _ => return Ok(None), + } + } + + // No built-in op-assignments for different types. + if type2 != type1 { return Ok(None); } @@ -1741,7 +1790,7 @@ pub fn run_builtin_op_assignment( } if type1 == TypeId::of::() { - let y = y.read_lock::().unwrap().deref().clone(); + let y = y.clone().cast::(); let mut x = x.write_lock::().unwrap(); match op { @@ -1751,11 +1800,12 @@ pub fn run_builtin_op_assignment( } if type1 == TypeId::of::() { - let y = y.read_lock::().unwrap().deref().clone(); + let y = &*y.read_lock::().unwrap(); let mut x = x.write_lock::().unwrap(); match op { "+=" => return Ok(Some(*x += y)), + "-=" => return Ok(Some(*x -= y)), _ => return Ok(None), } } diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 7925c0ea..b800d90d 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -20,7 +20,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str mod string_functions { use crate::ImmutableString; - #[rhai_fn(name = "+")] + #[rhai_fn(name = "+", name = "append")] pub fn add_append(string: &str, item: Dynamic) -> ImmutableString { format!("{}{}", string, item).into() } @@ -42,6 +42,13 @@ mod string_functions { pub fn len(string: &str) -> INT { string.chars().count() as INT } + pub fn remove(string: &mut ImmutableString, sub_string: ImmutableString) { + *string -= sub_string; + } + #[rhai_fn(name = "remove")] + pub fn remove_char(string: &mut ImmutableString, character: char) { + *string -= character; + } pub fn clear(string: &mut ImmutableString) { string.make_mut().clear(); } @@ -64,15 +71,15 @@ mod string_functions { } #[rhai_fn(name = "contains")] - pub fn contains_char(string: &str, ch: char) -> bool { - string.contains(ch) + pub fn contains_char(string: &str, character: char) -> bool { + string.contains(character) } pub fn contains(string: &str, find_string: &str) -> bool { string.contains(find_string) } #[rhai_fn(name = "index_of")] - pub fn index_of_char_starting_from(string: &str, ch: char, start: INT) -> INT { + pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT { let start = if start < 0 { 0 } else if start as usize >= string.chars().count() { @@ -86,14 +93,14 @@ mod string_functions { }; string[start..] - .find(ch) + .find(character) .map(|index| string[0..start + index].chars().count() as INT) .unwrap_or(-1 as INT) } #[rhai_fn(name = "index_of")] - pub fn index_of_char(string: &str, ch: char) -> INT { + pub fn index_of_char(string: &str, character: char) -> INT { string - .find(ch) + .find(character) .map(|index| string[0..index].chars().count() as INT) .unwrap_or(-1 as INT) } @@ -196,26 +203,33 @@ mod string_functions { pub fn replace_string_with_char( string: &mut ImmutableString, find_string: &str, - substitute_char: char, + substitute_character: char, ) { *string = string - .replace(find_string, &substitute_char.to_string()) + .replace(find_string, &substitute_character.to_string()) .into(); } #[rhai_fn(name = "replace")] pub fn replace_char_with_string( string: &mut ImmutableString, - find_char: char, + find_character: char, substitute_string: &str, ) { *string = string - .replace(&find_char.to_string(), substitute_string) + .replace(&find_character.to_string(), substitute_string) .into(); } #[rhai_fn(name = "replace")] - pub fn replace_char(string: &mut ImmutableString, find_char: char, substitute_char: char) { + pub fn replace_char( + string: &mut ImmutableString, + find_character: char, + substitute_character: char, + ) { *string = string - .replace(&find_char.to_string(), &substitute_char.to_string()) + .replace( + &find_character.to_string(), + &substitute_character.to_string(), + ) .into(); } @@ -224,7 +238,7 @@ mod string_functions { _ctx: NativeCallContext, string: &mut ImmutableString, len: INT, - ch: char, + character: char, ) -> Result> { // Check if string will be over max size limit #[cfg(not(feature = "unchecked"))] @@ -243,7 +257,7 @@ mod string_functions { let p = string.make_mut(); for _ in 0..(len as usize - orig_len) { - p.push(ch); + p.push(character); } #[cfg(not(feature = "unchecked"))] diff --git a/src/utils.rs b/src/utils.rs index 7cbed35c..a6a2a0eb 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -12,7 +12,7 @@ use crate::stdlib::{ hash::{BuildHasher, Hash, Hasher}, iter::{empty, FromIterator}, num::NonZeroU64, - ops::{Add, AddAssign, Deref, DerefMut}, + ops::{Add, AddAssign, Deref, DerefMut, Sub, SubAssign}, str::FromStr, string::{String, ToString}, vec::Vec, @@ -468,6 +468,13 @@ impl Add for &ImmutableString { } } +impl AddAssign for ImmutableString { + #[inline(always)] + fn add_assign(&mut self, rhs: String) { + self.make_mut().push_str(&rhs); + } +} + impl Add for ImmutableString { type Output = Self; @@ -496,6 +503,124 @@ impl AddAssign for ImmutableString { } } +impl Sub for ImmutableString { + type Output = Self; + + #[inline] + fn sub(self, rhs: Self) -> Self::Output { + if rhs.is_empty() { + self + } else if self.is_empty() { + rhs + } else { + self.replace(rhs.as_str(), "").into() + } + } +} + +impl Sub for &ImmutableString { + type Output = ImmutableString; + + #[inline] + fn sub(self, rhs: Self) -> Self::Output { + if rhs.is_empty() { + self.clone() + } else if self.is_empty() { + rhs.clone() + } else { + self.replace(rhs.as_str(), "").into() + } + } +} + +impl SubAssign<&ImmutableString> for ImmutableString { + #[inline] + fn sub_assign(&mut self, rhs: &ImmutableString) { + if !rhs.is_empty() { + if self.is_empty() { + self.0 = rhs.0.clone(); + } else { + self.0 = self.replace(rhs.as_str(), "").into(); + } + } + } +} + +impl SubAssign for ImmutableString { + #[inline] + fn sub_assign(&mut self, rhs: ImmutableString) { + if !rhs.is_empty() { + if self.is_empty() { + self.0 = rhs.0; + } else { + self.0 = self.replace(rhs.as_str(), "").into(); + } + } + } +} + +impl Sub for ImmutableString { + type Output = Self; + + #[inline] + fn sub(self, rhs: String) -> Self::Output { + if rhs.is_empty() { + self + } else if self.is_empty() { + rhs.into() + } else { + self.replace(&rhs, "").into() + } + } +} + +impl Sub for &ImmutableString { + type Output = ImmutableString; + + #[inline] + fn sub(self, rhs: String) -> Self::Output { + if rhs.is_empty() { + self.clone() + } else if self.is_empty() { + rhs.into() + } else { + self.replace(&rhs, "").into() + } + } +} + +impl SubAssign for ImmutableString { + #[inline(always)] + fn sub_assign(&mut self, rhs: String) { + self.0 = self.replace(&rhs, "").into(); + } +} + +impl Sub for ImmutableString { + type Output = Self; + + #[inline(always)] + fn sub(self, rhs: char) -> Self::Output { + self.replace(rhs, "").into() + } +} + +impl Sub for &ImmutableString { + type Output = ImmutableString; + + #[inline(always)] + fn sub(self, rhs: char) -> Self::Output { + self.replace(rhs, "").into() + } +} + +impl SubAssign for ImmutableString { + #[inline(always)] + fn sub_assign(&mut self, rhs: char) { + self.0 = self.replace(rhs, "").into(); + } +} + impl> PartialEq for ImmutableString { #[inline(always)] fn eq(&self, other: &S) -> bool { diff --git a/tests/decrement.rs b/tests/decrement.rs index 0691eddf..25a80b2a 100644 --- a/tests/decrement.rs +++ b/tests/decrement.rs @@ -6,10 +6,10 @@ fn test_decrement() -> Result<(), Box> { assert_eq!(engine.eval::("let x = 10; x -= 7; x")?, 3); - assert!(matches!( - *engine.eval::(r#"let s = "test"; s -= "ing"; s"#).expect_err("expects error"), - EvalAltResult::ErrorFunctionNotFound(err, _) if err == "- (&str | ImmutableString | String, &str | ImmutableString | String)" - )); + assert_eq!( + engine.eval::(r#"let s = "test"; s -= 's'; s"#)?, + "tet" + ); Ok(()) } diff --git a/tests/increment.rs b/tests/increment.rs index f622ab21..4ab59117 100644 --- a/tests/increment.rs +++ b/tests/increment.rs @@ -7,7 +7,7 @@ fn test_increment() -> Result<(), Box> { assert_eq!(engine.eval::("let x = 1; x += 2; x")?, 3); assert_eq!( - engine.eval::("let s = \"test\"; s += \"ing\"; s")?, + engine.eval::(r#"let s = "test"; s += "ing"; s"#)?, "testing" ); diff --git a/tests/ops.rs b/tests/ops.rs index 72c823cb..290c9ada 100644 --- a/tests/ops.rs +++ b/tests/ops.rs @@ -34,7 +34,19 @@ fn test_ops_numbers() -> Result<(), Box> { } #[test] -fn test_op_precedence() -> Result<(), Box> { +fn test_ops_strings() -> Result<(), Box> { + let engine = Engine::new(); + + assert!(engine.eval::(r#""hello" > 'c'"#)?); + assert!(engine.eval::(r#""" < 'c'"#)?); + assert!(engine.eval::(r#"'x' > "hello""#)?); + assert!(engine.eval::(r#""hello" > "foo""#)?); + + Ok(()) +} + +#[test] +fn test_ops_precedence() -> Result<(), Box> { let engine = Engine::new(); assert_eq!( diff --git a/tests/string.rs b/tests/string.rs index cd4aa6cb..6880c195 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -132,6 +132,19 @@ fn test_string_substring() -> Result<(), Box> { "❤❤ hello! ❤❤❤" ); + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x -= 'l'; x"# + )?, + "❤❤❤ heo! ❤❤❤" + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x -= "\u2764\u2764"; x"# + )?, + "❤ hello! ❤" + ); assert_eq!( engine.eval::( r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('\u2764')"#