mirror of
https://github.com/gorules/zen.git
synced 2026-10-04 00:02:18 +00:00
feat: add regular expression functions; (#52)
* feat: add regular expression functions; * add cargo.toml regex * fix cargo fmt
This commit is contained in:
@@ -15,6 +15,7 @@ fastrand = "1.9.0"
|
||||
humantime = "2.1.0"
|
||||
once_cell = "1.17.2"
|
||||
phf = "0.11.1"
|
||||
regex = "1.8.4"
|
||||
serde_json = "1.0.96"
|
||||
hashbrown = "0.13.2"
|
||||
rust_decimal = "1.29.1"
|
||||
|
||||
@@ -17,6 +17,7 @@ humantime = { workspace = true }
|
||||
fastrand = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
phf = { workspace = true, features = ["macros"] }
|
||||
regex = { workspace = true }
|
||||
serde_json = { workspace = true, features = ["arbitrary_precision"] }
|
||||
thiserror = { workspace = true }
|
||||
rust_decimal = { workspace = true, features = ["maths-nopanic"] }
|
||||
|
||||
@@ -321,6 +321,16 @@ impl<'a> Compiler<'a> {
|
||||
self.compile_argument(name, arguments, 1)?;
|
||||
Ok(self.emit(Opcode::Contains))
|
||||
}
|
||||
"matches" => {
|
||||
self.compile_argument(name, arguments, 0)?;
|
||||
self.compile_argument(name, arguments, 1)?;
|
||||
Ok(self.emit(Opcode::Matches))
|
||||
}
|
||||
"extract" => {
|
||||
self.compile_argument(name, arguments, 0)?;
|
||||
self.compile_argument(name, arguments, 1)?;
|
||||
Ok(self.emit(Opcode::Extract))
|
||||
}
|
||||
"flatten" => {
|
||||
self.compile_argument(name, arguments, 0)?;
|
||||
Ok(self.emit(Opcode::Flatten))
|
||||
|
||||
@@ -108,6 +108,8 @@ pub enum Opcode<'a> {
|
||||
Lowercase,
|
||||
StartsWith,
|
||||
EndsWith,
|
||||
Matches,
|
||||
Extract,
|
||||
Slice,
|
||||
Array,
|
||||
Len,
|
||||
|
||||
@@ -44,6 +44,9 @@ pub(crate) static BUILT_INS: Lazy<HashMap<&'static str, BuiltIn, ADefHasher>> =
|
||||
"startsWith" => BuiltIn { arity: Arity::Dual },
|
||||
"endsWith" => BuiltIn { arity: Arity::Dual },
|
||||
"contains" => BuiltIn { arity: Arity::Dual },
|
||||
"matches" => BuiltIn { arity: Arity::Dual },
|
||||
"extract" => BuiltIn { arity: Arity::Dual },
|
||||
|
||||
"all" => BuiltIn { arity: Arity::Closure },
|
||||
"some" => BuiltIn { arity: Arity::Closure },
|
||||
"none" => BuiltIn { arity: Arity::Closure },
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use bumpalo::Bump;
|
||||
use chrono::NaiveDateTime;
|
||||
use chrono::{Datelike, Timelike};
|
||||
use regex::Regex;
|
||||
use rust_decimal::prelude::ToPrimitive;
|
||||
use rust_decimal::{Decimal, MathematicalOps};
|
||||
use thiserror::Error;
|
||||
@@ -833,6 +834,56 @@ impl<'a> VM<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Opcode::Matches => {
|
||||
let b = self.pop()?;
|
||||
let a = self.pop()?;
|
||||
|
||||
let (String(a), String(b)) = (a, b) else {
|
||||
return Err(OpcodeErr {
|
||||
opcode: "Matches".into(),
|
||||
message: "Unsupported type".into(),
|
||||
})
|
||||
};
|
||||
|
||||
let regex = Regex::new(b).map_err(|_| OpcodeErr {
|
||||
opcode: "Matches".into(),
|
||||
message: "Invalid regular expression".into(),
|
||||
})?;
|
||||
|
||||
self.push(Bool(regex.is_match(a)));
|
||||
}
|
||||
Opcode::Extract => {
|
||||
let b = self.pop()?;
|
||||
let a = self.pop()?;
|
||||
|
||||
let (String(a), String(b)) = (a, b) else {
|
||||
return Err(OpcodeErr {
|
||||
opcode: "Matches".into(),
|
||||
message: "Unsupported type".into(),
|
||||
})
|
||||
};
|
||||
|
||||
let regex = Regex::new(b).map_err(|_| OpcodeErr {
|
||||
opcode: "Matches".into(),
|
||||
message: "Invalid regular expression".into(),
|
||||
})?;
|
||||
|
||||
let captures = regex
|
||||
.captures(a)
|
||||
.map(|capture| {
|
||||
capture
|
||||
.iter()
|
||||
.map(|c| c.map(|c| c.as_str()))
|
||||
.filter_map(|c| c)
|
||||
.map(|s| {
|
||||
self.bump.alloc(String(self.bump.alloc_str(s))) as &Variable
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
self.push(Array(self.bump.alloc_slice_copy(captures.as_slice())));
|
||||
}
|
||||
Opcode::DateManipulation(operation) => {
|
||||
let timestamp = self.pop()?;
|
||||
|
||||
|
||||
@@ -486,6 +486,27 @@ fn isolate_standard_test() {
|
||||
},
|
||||
]),
|
||||
},
|
||||
TestEnv {
|
||||
env: json!({}),
|
||||
cases: Vec::from([
|
||||
TestCase {
|
||||
expr: r#"extract("2022-02-01", "(\d{4})-(\d{2})-(\d{2})")"#,
|
||||
result: json!(["2022-02-01", "2022", "02", "01"]),
|
||||
},
|
||||
TestCase {
|
||||
expr: r#"all(["babble", "bebble", "bibble", "bobble", "bubble"], matches(#, "b[aeiou]bble"))"#,
|
||||
result: json!(true),
|
||||
},
|
||||
TestCase {
|
||||
expr: r#"none(["babble", "bebble", "bibble", "bobble", "bubble"], matches(#, "b[aeiou]bblo"))"#,
|
||||
result: json!(true),
|
||||
},
|
||||
TestCase {
|
||||
expr: r#"extract("foo.bar", "(\w+)\.(\w+)")"#,
|
||||
result: json!(["foo.bar", "foo", "bar"]),
|
||||
},
|
||||
]),
|
||||
},
|
||||
]);
|
||||
|
||||
let isolate = Isolate::default();
|
||||
|
||||
Reference in New Issue
Block a user