1use crate::compiler::prelude::*;
2use std::sync::LazyLock;
3
4static DEFAULT_ALL: LazyLock<Value> = LazyLock::new(|| Value::Boolean(false));
5
6static PARAMETERS: LazyLock<Vec<Parameter>> = LazyLock::new(|| {
7 vec![
8 Parameter::required("value", kind::ARRAY, "The array."),
9 Parameter::required(
10 "pattern",
11 kind::REGEX,
12 "The regular expression pattern to match against.",
13 ),
14 Parameter::optional(
15 "all",
16 kind::BOOLEAN,
17 "Whether to match on all elements of `value`.",
18 )
19 .default(&DEFAULT_ALL),
20 ]
21});
22
23fn match_array(list: Value, pattern: Value, all: Value) -> Resolved {
24 let pattern = pattern.try_regex()?;
25 let list = list.try_array()?;
26 let all = all.try_boolean()?;
27 let matcher = |i: &Value| match i.try_bytes_utf8_lossy() {
28 Ok(v) => pattern.is_match(&v),
29 _ => false,
30 };
31 let included = if all {
32 list.iter().all(matcher)
33 } else {
34 list.iter().any(matcher)
35 };
36 Ok(included.into())
37}
38
39#[derive(Clone, Copy, Debug)]
40pub struct MatchArray;
41
42impl Function for MatchArray {
43 fn identifier(&self) -> &'static str {
44 "match_array"
45 }
46
47 fn usage(&self) -> &'static str {
48 "Determines whether the elements in the `value` array matches the `pattern`. By default, it checks that at least one element matches, but can be set to determine if all the elements match."
49 }
50
51 fn category(&self) -> &'static str {
52 Category::Enumerate.as_ref()
53 }
54
55 fn return_kind(&self) -> u16 {
56 kind::BOOLEAN
57 }
58
59 fn examples(&self) -> &'static [Example] {
60 &[
61 example! {
62 title: "Match at least one element",
63 source: r#"match_array(["foobar", "bazqux"], r'foo')"#,
64 result: Ok("true"),
65 },
66 example! {
67 title: "Match all elements",
68 source: r#"match_array(["foo", "foobar", "barfoo"], r'foo', all: true)"#,
69 result: Ok("true"),
70 },
71 example! {
72 title: "No matches",
73 source: r#"match_array(["bazqux", "xyz"], r'foo')"#,
74 result: Ok("false"),
75 },
76 example! {
77 title: "Not all elements match",
78 source: r#"match_array(["foo", "foobar", "baz"], r'foo', all: true)"#,
79 result: Ok("false"),
80 },
81 ]
82 }
83
84 fn compile(
85 &self,
86 _state: &state::TypeState,
87 _ctx: &mut FunctionCompileContext,
88 arguments: ArgumentList,
89 ) -> Compiled {
90 let value = arguments.required("value");
91 let pattern = arguments.required("pattern");
92 let all = arguments.optional("all");
93
94 Ok(MatchArrayFn {
95 value,
96 pattern,
97 all,
98 }
99 .as_expr())
100 }
101
102 fn parameters(&self) -> &'static [Parameter] {
103 PARAMETERS.as_slice()
104 }
105}
106
107#[derive(Debug, Clone)]
108pub(crate) struct MatchArrayFn {
109 value: Box<dyn Expression>,
110 pattern: Box<dyn Expression>,
111 all: Option<Box<dyn Expression>>,
112}
113
114impl FunctionExpression for MatchArrayFn {
115 fn resolve(&self, ctx: &mut Context) -> Resolved {
116 let list = self.value.resolve(ctx)?;
117 let pattern = self.pattern.resolve(ctx)?;
118 let all = self
119 .all
120 .map_resolve_with_default(ctx, || DEFAULT_ALL.clone())?;
121
122 match_array(list, pattern, all)
123 }
124
125 fn type_def(&self, _: &state::TypeState) -> TypeDef {
126 TypeDef::boolean().infallible()
127 }
128}
129
130#[cfg(test)]
131#[allow(clippy::trivial_regex)]
132mod tests {
133 use super::*;
134 use crate::value;
135 use regex::Regex;
136
137 test_function![
138 match_array => MatchArray;
139
140 default {
141 args: func_args![
142 value: value!(["foo", "foobar", "barfoo"]),
143 pattern: Value::Regex(Regex::new("foo").unwrap().into())
144 ],
145 want: Ok(value!(true)),
146 tdef: TypeDef::boolean().infallible(),
147 }
148
149 all {
150 args: func_args![
151 value: value!(["foo", "foobar", "barfoo"]),
152 pattern: Value::Regex(Regex::new("foo").unwrap().into()),
153 all: value!(true),
154 ],
155 want: Ok(value!(true)),
156 tdef: TypeDef::boolean().infallible(),
157 }
158
159 not_all {
160 args: func_args![
161 value: value!(["foo", "foobar", "baz"]),
162 pattern: Value::Regex(Regex::new("foo").unwrap().into()),
163 all: value!(true),
164 ],
165 want: Ok(value!(false)),
166 tdef: TypeDef::boolean().infallible(),
167 }
168
169 mixed_values {
170 args: func_args![
171 value: value!(["foo", "123abc", 1, true, [1,2,3]]),
172 pattern: Value::Regex(Regex::new("abc").unwrap().into())
173 ],
174 want: Ok(value!(true)),
175 tdef: TypeDef::boolean().infallible(),
176 }
177
178 mixed_values_no_match {
179 args: func_args![
180 value: value!(["foo", "123abc", 1, true, [1,2,3]]),
181 pattern: Value::Regex(Regex::new("xyz").unwrap().into()),
182 ],
183 want: Ok(value!(false)),
184 tdef: TypeDef::boolean().infallible(),
185 }
186
187 mixed_values_no_match_all {
188 args: func_args![
189 value: value!(["foo", "123abc", 1, true, [1,2,3]]),
190 pattern: Value::Regex(Regex::new("abc`").unwrap().into()),
191 all: value!(true),
192 ],
193 want: Ok(value!(false)),
194 tdef: TypeDef::boolean().infallible(),
195 }
196 ];
197}