Add sub_string, crop and index_of to String.

This commit is contained in:
Stephen Chung 2020-04-13 12:29:22 +08:00
parent 50a0f14bfc
commit 2e9a5f7a89
3 changed files with 232 additions and 14 deletions

View File

@ -1152,14 +1152,17 @@ record == "Bob X. Davis: age 42 ❤\n";
The following standard methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on strings:
| Function | Parameter(s) | Description |
| ---------- | ------------------------------------- | -------------------------------------------------------------------- |
| ------------ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
| `len` | _none_ | returns the number of characters (not number of bytes) in the string |
| `pad` | character to pad, target length | pads the string with an character to a specified length |
| `pad` | character to pad, target length | pads the string with an character to at least a specified length |
| `append` | character/string to append | Adds a character or a string to the end of another string |
| `clear` | _none_ | empties the string |
| `truncate` | target length | cuts off the string at exactly a specified number of characters |
| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string |
| `replace` | target sub-string, replacement string | replaces a substring with another |
| `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found |
| `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) |
| `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) |
| `replace` | target sub-string, replacement string | replaces a sub-string with another |
| `trim` | _none_ | trims the string of whitespace at the beginning and end |
### Examples
@ -1176,17 +1179,30 @@ full_name.pad(15, '$');
full_name.len() == 15;
full_name == "Bob C. Davis$$$";
let n = full_name.index_of('$');
n == 12;
full_name.index_of("$$", n + 1) == 13;
full_name.sub_string(n, 3) == "$$$";
full_name.truncate(6);
full_name.len() == 6;
full_name == "Bob C.";
full_name.replace("Bob", "John");
full_name.len() == 7;
full_name = "John C.";
full_name == "John C.";
full_name.contains('C') == true;
full_name.contains("John") == true;
full_name.crop(5);
full_name == "C.";
full_name.crop(0, 1);
full_name == "C";
full_name.clear();
full_name.len() == 0;
```
@ -1220,7 +1236,7 @@ The following methods (defined in the standard library but excluded if using a [
| `shift` | _none_ | removes the first element and returns it ([`()`] if empty) |
| `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid |
| `len` | _none_ | returns the number of elements |
| `pad` | element to pad, target length | pads the array with an element until a specified length |
| `pad` | element to pad, target length | pads the array with an element to at least a specified length |
| `clear` | _none_ | empties the array |
| `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) |

View File

@ -1000,17 +1000,113 @@ impl Engine<'_> {
}
// Register string utility functions
fn sub_string(s: &mut String, start: INT, len: INT) -> String {
let offset = if s.is_empty() || len <= 0 {
return "".to_string();
} else if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
return "".to_string();
} else {
start as usize
};
let chars: Vec<_> = s.chars().collect();
let len = if offset + (len as usize) > chars.len() {
chars.len() - offset
} else {
len as usize
};
chars[offset..][..len].into_iter().collect::<String>()
}
fn crop_string(s: &mut String, start: INT, len: INT) {
let offset = if s.is_empty() || len <= 0 {
s.clear();
return;
} else if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
s.clear();
return;
} else {
start as usize
};
let chars: Vec<_> = s.chars().collect();
let len = if offset + (len as usize) > chars.len() {
chars.len() - offset
} else {
len as usize
};
s.clear();
chars[offset..][..len]
.into_iter()
.for_each(|&ch| s.push(ch));
}
self.register_fn("len", |s: &mut String| s.chars().count() as INT);
self.register_fn("contains", |s: &mut String, ch: char| s.contains(ch));
self.register_fn("contains", |s: &mut String, find: String| s.contains(&find));
self.register_fn("index_of", |s: &mut String, ch: char, start: INT| {
let start = if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
return -1 as INT;
} else {
s.chars().take(start as usize).collect::<String>().len()
};
s[start..]
.find(ch)
.map(|index| s[0..start + index].chars().count() as INT)
.unwrap_or(-1 as INT)
});
self.register_fn("index_of", |s: &mut String, ch: char| {
s.find(ch)
.map(|index| s[0..index].chars().count() as INT)
.unwrap_or(-1 as INT)
});
self.register_fn("index_of", |s: &mut String, find: String, start: INT| {
let start = if start < 0 {
0
} else if (start as usize) >= s.chars().count() {
return -1 as INT;
} else {
s.chars().take(start as usize).collect::<String>().len()
};
s[start..]
.find(&find)
.map(|index| s[0..start + index].chars().count() as INT)
.unwrap_or(-1 as INT)
});
self.register_fn("index_of", |s: &mut String, find: String| {
s.find(&find)
.map(|index| s[0..index].chars().count() as INT)
.unwrap_or(-1 as INT)
});
self.register_fn("clear", |s: &mut String| s.clear());
self.register_fn("append", |s: &mut String, ch: char| s.push(ch));
self.register_fn("append", |s: &mut String, add: String| s.push_str(&add));
self.register_fn("sub_string", sub_string);
self.register_fn("sub_string", |s: &mut String, start: INT| {
sub_string(s, start, s.len() as INT)
});
self.register_fn("crop", crop_string);
self.register_fn("crop", |s: &mut String, start: INT| {
crop_string(s, start, s.len() as INT)
});
self.register_fn("truncate", |s: &mut String, len: INT| {
if len >= 0 {
let chars: Vec<_> = s.chars().take(len as usize).collect();
s.clear();
chars.iter().for_each(|&ch| s.push(ch));
chars.into_iter().for_each(|ch| s.push(ch));
} else {
s.clear();
}

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult};
use rhai::{Engine, EvalAltResult, INT};
#[test]
fn test_string() -> Result<(), EvalAltResult> {
@ -33,3 +33,109 @@ fn test_string() -> Result<(), EvalAltResult> {
Ok(())
}
#[cfg(not(feature = "no_stdlib"))]
#[test]
fn test_string_substring() -> Result<(), EvalAltResult> {
let engine = Engine::new();
assert_eq!(
engine.eval::<String>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(-1, 2)"#
)?,
"❤❤"
);
assert_eq!(
engine.eval::<String>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(1, 5)"#
)?,
"❤❤ he"
);
assert_eq!(
engine.eval::<String>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(1)"#
)?,
"❤❤ hello! ❤❤❤"
);
assert_eq!(
engine.eval::<String>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(99)"#
)?,
""
);
assert_eq!(
engine.eval::<String>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(1, -1)"#
)?,
""
);
assert_eq!(
engine.eval::<String>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(1, 999)"#
)?,
"❤❤ hello! ❤❤❤"
);
assert_eq!(
engine.eval::<String>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.crop(1, -1); x"#
)?,
""
);
assert_eq!(
engine.eval::<String>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.crop(4, 6); x"#
)?,
"hello!"
);
assert_eq!(
engine.eval::<String>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.crop(1, 999); x"#
)?,
"❤❤ hello! ❤❤❤"
);
assert_eq!(
engine.eval::<INT>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('\u2764')"#
)?,
0
);
assert_eq!(
engine.eval::<INT>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('\u2764', 5)"#
)?,
11
);
assert_eq!(
engine.eval::<INT>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('\u2764', -1)"#
)?,
0
);
assert_eq!(
engine.eval::<INT>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('\u2764', 999)"#
)?,
-1
);
assert_eq!(
engine.eval::<INT>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('x')"#
)?,
-1
);
Ok(())
}