Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add str::substring #158

Merged
merged 5 commits into from
Dec 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ Symmetrically, the `EmptyContextWithBuiltinFunctions` has builtin functions enab
| `str::to_uppercase` | 1 | String | Returns the upper-case version of the string |
| `str::trim` | 1 | String | Strips whitespace from the start and the end of the string |
| `str::from` | >= 0 | Any | Returns passed value as string |
| `str::substring` | 3 | String, Int, Int | Returns a substring of the first argument, starting at the second argument and ending at the third argument. If the last argument is omitted, the substring extends to the end of the string |
| `bitand` | 2 | Int | Computes the bitwise and of the given integers |
| `bitor` | 2 | Int | Computes the bitwise or of the given integers |
| `bitxor` | 2 | Int | Computes the bitwise xor of the given integers |
Expand Down
1 change: 1 addition & 0 deletions src/error/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ impl fmt::Display for EvalexprError {
write!(f, "This context does not allow disabling builtin functions")
},
IllegalEscapeSequence(string) => write!(f, "Illegal escape sequence: {}", string),
OutOfBoundsAccess => write!(f, "Tried to access a tuple or string at an invalid index"),
CustomMessage(message) => write!(f, "Error: {}", message),
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ pub enum EvalexprError {
/// This context does not allow disabling builtin functions.
BuiltinFunctionsCannotBeDisabled,

/// Out of bounds sequence access.
OutOfBoundsAccess,

/// A custom error explained by its message.
CustomMessage(String),
}
Expand Down
26 changes: 25 additions & 1 deletion src/function/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ use crate::{
value::{FloatType, IntType},
EvalexprError, Function, Value, ValueType,
};
use std::ops::{BitAnd, BitOr, BitXor, Not, Shl, Shr};
use std::{
convert::TryFrom,
ops::{BitAnd, BitOr, BitXor, Not, Shl, Shr},
};

macro_rules! simple_math {
($func:ident) => {
Expand Down Expand Up @@ -267,6 +270,27 @@ pub fn builtin_function(identifier: &str) -> Option<Function> {
"str::from" => Some(Function::new(|argument| {
Ok(Value::String(argument.to_string()))
})),
"str::substring" => Some(Function::new(|argument| {
let args = argument.as_tuple()?;
lovasoa marked this conversation as resolved.
Show resolved Hide resolved
let subject = args
.get(0)
.ok_or_else(|| EvalexprError::wrong_function_argument_amount(0, 2))?
.as_string()?;
let start = args
.get(1)
.ok_or_else(|| EvalexprError::wrong_function_argument_amount(1, 2))?
.as_int()?;
let start = usize::try_from(start).map_err(|_| EvalexprError::OutOfBoundsAccess)?;
let end = if let Some(end) = args.get(2) {
usize::try_from(end.as_int()?).map_err(|_| EvalexprError::OutOfBoundsAccess)?
} else {
subject.len()
};
if start > end || end > subject.len() {
return Err(EvalexprError::OutOfBoundsAccess);
}
Ok(Value::from(&subject[start..end]))
})),
#[cfg(feature = "rand")]
"random" => Some(Function::new(|argument| {
argument.as_empty()?;
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@
//! | `str::to_uppercase` | 1 | String | Returns the upper-case version of the string |
//! | `str::trim` | 1 | String | Strips whitespace from the start and the end of the string |
//! | `str::from` | >= 0 | Any | Returns passed value as string |
//! | `str::substring` | 3 | String, Int, Int | Returns a substring of the first argument, starting at the second argument and ending at the third argument. If the last argument is omitted, the substring extends to the end of the string |
//! | `bitand` | 2 | Int | Computes the bitwise and of the given integers |
//! | `bitor` | 2 | Int | Computes the bitwise or of the given integers |
//! | `bitxor` | 2 | Int | Computes the bitwise xor of the given integers |
Expand Down Expand Up @@ -565,7 +566,7 @@
//! ## License
//!
//! This crate is primarily distributed under the terms of the MIT license.
//! See [LICENSE](LICENSE) for details.

Check warning on line 569 in src/lib.rs

View workflow job for this annotation

GitHub Actions / Check, test, doc, format and lint with all features (ubuntu-latest, stable)

unresolved link to `LICENSE`

Check warning on line 569 in src/lib.rs

View workflow job for this annotation

GitHub Actions / Check, test, doc, format and lint with all features (ubuntu-latest, nightly)

unresolved link to `LICENSE`

Check warning on line 569 in src/lib.rs

View workflow job for this annotation

GitHub Actions / Check, test, doc, format and lint with all features (ubuntu-latest, beta)

unresolved link to `LICENSE`
//!

#![deny(missing_docs)]
Expand Down
18 changes: 18 additions & 0 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,24 @@ fn test_builtin_functions() {
Ok(Value::String(String::from("(1, 2, 3)")))
);
assert_eq!(eval("str::from()"), Ok(Value::String(String::from("()"))));
assert_eq!(
eval("str::substring(\"foobar\", 3)"),
Ok(Value::String(String::from("bar")))
);
assert_eq!(
eval("str::substring(\"foobar\", 3, 3)"),
Ok(Value::String(String::from("")))
);
assert_eq!(
eval("str::substring(\"foobar\", 3, 4)"),
Ok(Value::String(String::from("b")))
);
assert!(eval("str::substring()").is_err());
assert!(eval("str::substring(\"foobar\")").is_err());
assert!(eval("str::substring(\"foobar\", 2, 1)").is_err());
assert!(eval("str::substring(\"foobar\", 99999)").is_err());
assert!(eval("str::substring(\"foobar\", -1)").is_err());
assert!(eval("str::substring(\"foobar\", 0, -1)").is_err());
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think currently, the function accepts also the case where it gets four or more arguments, and silently drops them. Let's add a test case for that, expecting an error. And then if the function does not return an error in that case, let's make it return one.

// Bitwise
assert_eq!(eval("bitand(5, -1)"), Ok(Value::Int(5)));
assert_eq!(eval("bitand(6, 5)"), Ok(Value::Int(4)));
Expand Down
Loading