1use crate::compiler::prelude::*;
2use regex::bytes::RegexSet;
3
4fn match_any(value: Value, pattern: &RegexSet) -> Resolved {
5 let bytes = value.try_bytes()?;
6 Ok(pattern.is_match(&bytes).into())
7}
8
9#[derive(Clone, Copy, Debug)]
10pub struct MatchAny;
11
12impl Function for MatchAny {
13 fn identifier(&self) -> &'static str {
14 "match_any"
15 }
16
17 fn usage(&self) -> &'static str {
18 "Determines whether `value` matches any of the given `patterns`. All patterns are checked in a single pass over the target string, giving this function a potential performance advantage over the multiple calls in the `match` function."
19 }
20
21 fn category(&self) -> &'static str {
22 Category::String.as_ref()
23 }
24
25 fn return_kind(&self) -> u16 {
26 kind::BOOLEAN
27 }
28
29 fn parameters(&self) -> &'static [Parameter] {
30 const PARAMETERS: &[Parameter] = &[
31 Parameter::required("value", kind::BYTES, "The value to match."),
32 Parameter::required(
33 "patterns",
34 kind::ARRAY,
35 "The array of regular expression patterns to match against.",
36 ),
37 ];
38 PARAMETERS
39 }
40
41 fn examples(&self) -> &'static [Example] {
42 &[
43 example! {
44 title: "Regex match on a string",
45 source: r#"match_any("I'm a little teapot", [r'frying pan', r'teapot'])"#,
46 result: Ok("true"),
47 },
48 example! {
49 title: "No match",
50 source: r#"match_any("My name is John Doe", patterns: [r'\d+', r'Jane'])"#,
51 result: Ok("false"),
52 },
53 ]
54 }
55
56 fn compile(
57 &self,
58 state: &state::TypeState,
59 _ctx: &mut FunctionCompileContext,
60 arguments: ArgumentList,
61 ) -> Compiled {
62 let value = arguments.required("value");
63 let patterns = arguments.required_array("patterns")?;
64
65 let mut re_strings = Vec::with_capacity(patterns.len());
66 for expr in patterns {
67 let value =
68 expr.resolve_constant(state)
69 .ok_or(function::Error::ExpectedStaticExpression {
70 keyword: "patterns",
71 expr,
72 })?;
73
74 let re = value
75 .try_regex()
76 .map_err(|e| Box::new(e) as Box<dyn DiagnosticMessage>)?;
77 re_strings.push(re.to_string());
78 }
79
80 let regex_set = RegexSet::new(re_strings).expect("regex were already valid");
81
82 Ok(MatchAnyFn { value, regex_set }.as_expr())
83 }
84}
85
86#[derive(Clone, Debug)]
87struct MatchAnyFn {
88 value: Box<dyn Expression>,
89 regex_set: RegexSet,
90}
91
92impl FunctionExpression for MatchAnyFn {
93 fn resolve(&self, ctx: &mut Context) -> Resolved {
94 let value = self.value.resolve(ctx)?;
95 match_any(value, &self.regex_set)
96 }
97
98 fn type_def(&self, _: &state::TypeState) -> TypeDef {
99 TypeDef::boolean().infallible()
100 }
101}
102
103#[cfg(test)]
104#[allow(clippy::trivial_regex)]
105mod tests {
106 use super::*;
107 use crate::value;
108 use regex::Regex;
109
110 test_function![
111 r#match_any => MatchAny;
112
113 yes {
114 args: func_args![value: "foobar",
115 patterns: Value::Array(vec![
116 Value::Regex(Regex::new("foo").unwrap().into()),
117 Value::Regex(Regex::new("bar").unwrap().into()),
118 Value::Regex(Regex::new("baz").unwrap().into()),
119 ])],
120 want: Ok(value!(true)),
121 tdef: TypeDef::boolean().infallible(),
122 }
123
124 no {
125 args: func_args![value: "foo 2 bar",
126 patterns: Value::Array(vec![
127 Value::Regex(Regex::new("baz|quux").unwrap().into()),
128 Value::Regex(Regex::new("foobar").unwrap().into()),
129 ])],
130 want: Ok(value!(false)),
131 tdef: TypeDef::boolean().infallible(),
132 }
133 ];
134}