Builtin string functions - downcase, len, match(regex), replace(regex), trim, upcase. New dependency regex.
This commit is contained in:
parent
a9c45307dd
commit
be54931f76
@ -16,11 +16,13 @@ name = "evalexpr"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
regex = { version = "1", optional = true}
|
||||
serde = { version = "1", optional = true}
|
||||
serde_derive = { version = "1", optional = true}
|
||||
|
||||
[features]
|
||||
serde_support = ["serde", "serde_derive"]
|
||||
regex_support = ["regex"]
|
||||
|
||||
[dev-dependencies]
|
||||
ron = "0.4"
|
||||
|
@ -214,6 +214,12 @@ This crate offers a set of builtin functions.
|
||||
|------------|-----------------|-------------|
|
||||
| min | >= 1 | Returns the minimum of the arguments |
|
||||
| max | >= 1 | Returns the maximum of the arguments |
|
||||
| downcase | 1 | Returns lower-case version of string |
|
||||
| len | 1 | Return the character length of string argument |
|
||||
| match | 2 | Returns true if first string argument matches regex in second |
|
||||
| replace | 3 | Returns string with matches replaced by third argument |
|
||||
| trim | 1 | Strips whitespace from start and end of string |
|
||||
| upcase | 1 | Returns upper-case version of string |
|
||||
|
||||
The `min` and `max` functions can deal with a mixture of integer and floating point arguments.
|
||||
They return the result as the type it was passed into the function.
|
||||
|
@ -84,6 +84,7 @@ impl fmt::Display for EvalexprError {
|
||||
ModulationError { dividend, divisor } => {
|
||||
write!(f, "Error modulating {} % {}", dividend, divisor)
|
||||
},
|
||||
InvalidRegex { regex, message } => write!(f, "Regular expression {} is invalid: {}", regex, message),
|
||||
ContextNotManipulable => write!(f, "Cannot manipulate context"),
|
||||
IllegalEscapeSequence(string) => write!(f, "Illegal escape sequence: {}", string),
|
||||
CustomMessage(message) => write!(f, "Error: {}", message),
|
||||
|
@ -167,6 +167,14 @@ pub enum EvalexprError {
|
||||
divisor: Value,
|
||||
},
|
||||
|
||||
/// This regular expression could not be parsed
|
||||
InvalidRegex {
|
||||
/// The invalid regular expression
|
||||
regex: String,
|
||||
/// Failure message from the regex engine
|
||||
message: String,
|
||||
},
|
||||
|
||||
/// A modification was attempted on a `Context` that does not allow modifications.
|
||||
ContextNotManipulable,
|
||||
|
||||
@ -279,6 +287,11 @@ impl EvalexprError {
|
||||
pub(crate) fn modulation_error(dividend: Value, divisor: Value) -> Self {
|
||||
EvalexprError::ModulationError { dividend, divisor }
|
||||
}
|
||||
|
||||
/// Constructs `EvalexprError::InvalidRegex(regex)`
|
||||
pub fn invalid_regex(regex: String, message: String) -> Self {
|
||||
EvalexprError::InvalidRegex{ regex, message }
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Ok(())` if the actual and expected parameters are equal, and `Err(Error::WrongOperatorArgumentAmount)` otherwise.
|
||||
|
@ -1,8 +1,27 @@
|
||||
#[cfg(feature = "regex_support")]
|
||||
use regex::Regex;
|
||||
|
||||
use crate::error::*;
|
||||
use value::{FloatType, IntType};
|
||||
use EvalexprError;
|
||||
use Function;
|
||||
use Value;
|
||||
|
||||
#[cfg(feature = "regex_support")]
|
||||
fn regex_with_local_errors(re_str: &str) -> Result<Regex, EvalexprError> {
|
||||
match Regex::new(re_str) {
|
||||
Ok(re) => Ok(re),
|
||||
Err(regex::Error::Syntax(message)) =>
|
||||
Err(EvalexprError::invalid_regex(re_str.to_string(), message)),
|
||||
Err(regex::Error::CompiledTooBig(max_size)) =>
|
||||
Err(EvalexprError::invalid_regex(
|
||||
re_str.to_string(),
|
||||
format!("Regex exceeded max size {}", max_size))
|
||||
),
|
||||
Err(err) => Err(EvalexprError::CustomMessage(err.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn builtin_function(identifier: &str) -> Option<Function> {
|
||||
match identifier {
|
||||
"min" => Some(Function::new(
|
||||
@ -53,6 +72,62 @@ pub fn builtin_function(identifier: &str) -> Option<Function> {
|
||||
}
|
||||
}),
|
||||
)),
|
||||
|
||||
// string functions
|
||||
|
||||
"downcase" => Some(Function::new(
|
||||
Some(1),
|
||||
Box::new(|arguments| {
|
||||
let subject = expect_string(&arguments[0])?;
|
||||
Ok(Value::from(subject.to_lowercase()))
|
||||
}),
|
||||
)),
|
||||
"len" => Some(Function::new(
|
||||
Some(1),
|
||||
Box::new(|arguments| {
|
||||
let subject = expect_string(&arguments[0])?;
|
||||
Ok(Value::from(subject.len() as i64))
|
||||
}),
|
||||
)),
|
||||
#[cfg(feature = "regex_support")]
|
||||
"match" => Some(Function::new(
|
||||
Some(2),
|
||||
Box::new(|arguments| {
|
||||
let subject = expect_string(&arguments[0])?;
|
||||
let re_str = expect_string(&arguments[1])?;
|
||||
match regex_with_local_errors(re_str) {
|
||||
Ok(re) => Ok(Value::Boolean(re.is_match(subject))),
|
||||
Err(err) => Err(err)
|
||||
}
|
||||
}),
|
||||
)),
|
||||
#[cfg(feature = "regex_support")]
|
||||
"replace" => Some(Function::new(
|
||||
Some(3),
|
||||
Box::new(|arguments| {
|
||||
let subject = expect_string(&arguments[0])?;
|
||||
let re_str = expect_string(&arguments[1])?;
|
||||
let repl = expect_string(&arguments[2])?;
|
||||
match regex_with_local_errors(re_str) {
|
||||
Ok(re) => Ok(Value::String(re.replace_all(subject, repl).to_string())),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}),
|
||||
)),
|
||||
"trim" => Some(Function::new(
|
||||
Some(1),
|
||||
Box::new(|arguments| {
|
||||
let subject = expect_string(&arguments[0])?;
|
||||
Ok(Value::from(subject.trim()))
|
||||
}),
|
||||
)),
|
||||
"upcase" => Some(Function::new(
|
||||
Some(1),
|
||||
Box::new(|arguments| {
|
||||
let subject = expect_string(&arguments[0])?;
|
||||
Ok(Value::from(subject.to_uppercase()))
|
||||
}),
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -347,6 +347,8 @@
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
#[cfg(feature = "regex_support")]
|
||||
extern crate regex;
|
||||
#[cfg(test)]
|
||||
extern crate ron;
|
||||
#[cfg(feature = "serde_support")]
|
||||
|
@ -275,15 +275,62 @@ fn test_n_ary_functions() {
|
||||
Ok(Value::Int(3))
|
||||
);
|
||||
assert_eq!(eval_with_context("count 5", &context), Ok(Value::Int(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builtin_functions() {
|
||||
assert_eq!(
|
||||
eval_with_context("min(4.0, 3)", &context),
|
||||
eval("min(4.0, 3)"),
|
||||
Ok(Value::Int(3))
|
||||
);
|
||||
assert_eq!(
|
||||
eval_with_context("max(4.0, 3)", &context),
|
||||
eval("max(4.0, 3)"),
|
||||
Ok(Value::Float(4.0))
|
||||
);
|
||||
assert_eq!(
|
||||
eval("downcase(\"FOOBAR\")"),
|
||||
Ok(Value::from("foobar"))
|
||||
);
|
||||
assert_eq!(
|
||||
eval("len(\"foobar\")"),
|
||||
Ok(Value::Int(6))
|
||||
);
|
||||
assert_eq!(
|
||||
eval("trim(\" foo bar \")"),
|
||||
Ok(Value::from("foo bar"))
|
||||
);
|
||||
assert_eq!(
|
||||
eval("upcase(\"foobar\")"),
|
||||
Ok(Value::from("FOOBAR"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "regex_support")]
|
||||
fn test_regex_functions() {
|
||||
assert_eq!(
|
||||
eval("match(\"foobar\", \"[ob]{3}\")"),
|
||||
Ok(Value::Boolean(true))
|
||||
);
|
||||
assert_eq!(
|
||||
eval("match(\"gazonk\", \"[ob]{3}\")"),
|
||||
Ok(Value::Boolean(false))
|
||||
);
|
||||
match eval("match(\"foo\", \"[\")") {
|
||||
Err(EvalexprError::InvalidRegex{ regex, message }) => {
|
||||
assert_eq!(regex, "[");
|
||||
assert!(message.contains("unclosed character class"));
|
||||
},
|
||||
v => panic!(v),
|
||||
};
|
||||
assert_eq!(
|
||||
eval("replace(\"foobar\", \".*?(o+)\", \"b$1\")"),
|
||||
Ok(Value::String("boobar".to_owned()))
|
||||
);
|
||||
assert_eq!(
|
||||
eval("replace(\"foobar\", \".*?(i+)\", \"b$1\")"),
|
||||
Ok(Value::String("foobar".to_owned()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
Loading…
Reference in New Issue
Block a user