1use regex::Regex;
2
3use crate::compiler::prelude::*;
4
5fn match_(value: &Value, pattern: Value) -> Resolved {
6 let string = value.try_bytes_utf8_lossy()?;
7 let pattern = pattern.try_regex()?;
8 Ok(pattern.is_match(&string).into())
9}
10
11fn match_static(value: &Value, pattern: &Regex) -> Resolved {
12 let string = value.try_bytes_utf8_lossy()?;
13 Ok(pattern.is_match(&string).into())
14}
15
16#[derive(Clone, Copy, Debug)]
17pub struct Match;
18
19impl Function for Match {
20 fn identifier(&self) -> &'static str {
21 "match"
22 }
23
24 fn usage(&self) -> &'static str {
25 "Determines whether the `value` matches the `pattern`."
26 }
27
28 fn category(&self) -> &'static str {
29 Category::String.as_ref()
30 }
31
32 fn return_kind(&self) -> u16 {
33 kind::BOOLEAN
34 }
35
36 fn parameters(&self) -> &'static [Parameter] {
37 const PARAMETERS: &[Parameter] = &[
38 Parameter::required("value", kind::BYTES, "The value to match."),
39 Parameter::required(
40 "pattern",
41 kind::REGEX,
42 "The regular expression pattern to match against.",
43 ),
44 ];
45 PARAMETERS
46 }
47
48 fn examples(&self) -> &'static [Example] {
49 &[
50 example! {
51 title: "Regex match on a string",
52 source: r#"match("I'm a little teapot", r'teapot')"#,
53 result: Ok("true"),
54 },
55 example! {
56 title: "String does not match the regular expression",
57 source: r#"match("I'm a little teapot", r'.*balloon')"#,
58 result: Ok("false"),
59 },
60 ]
61 }
62
63 fn compile(
64 &self,
65 state: &state::TypeState,
66 _ctx: &mut FunctionCompileContext,
67 arguments: ArgumentList,
68 ) -> Compiled {
69 let value = arguments.required("value");
70 let pattern = arguments.required("pattern");
71
72 match pattern.resolve_constant(state) {
73 Some(pattern) => {
74 let pattern = pattern
75 .try_regex()
76 .map_err(|e| Box::new(e) as Box<dyn DiagnosticMessage>)?;
77
78 let pattern = Regex::new(pattern.as_str()).map_err(|e| {
79 Box::new(ExpressionError::from(e.to_string())) as Box<dyn DiagnosticMessage>
80 })?;
81
82 Ok(MatchStaticFn { value, pattern }.as_expr())
83 }
84 None => Ok(MatchFn { value, pattern }.as_expr()),
85 }
86 }
87}
88
89#[derive(Debug, Clone)]
90pub(crate) struct MatchFn {
91 value: Box<dyn Expression>,
92 pattern: Box<dyn Expression>,
93}
94
95impl FunctionExpression for MatchFn {
96 fn resolve(&self, ctx: &mut Context) -> Resolved {
97 let value = self.value.resolve(ctx)?;
98 let pattern = self.pattern.resolve(ctx)?;
99
100 match_(&value, pattern)
101 }
102
103 fn type_def(&self, _: &state::TypeState) -> TypeDef {
104 TypeDef::boolean().infallible()
105 }
106}
107
108#[derive(Debug, Clone)]
109pub(crate) struct MatchStaticFn {
110 value: Box<dyn Expression>,
111 pattern: Regex,
112}
113
114impl FunctionExpression for MatchStaticFn {
115 fn resolve(&self, ctx: &mut Context) -> Resolved {
116 let value = self.value.resolve(ctx)?;
117
118 match_static(&value, &self.pattern)
119 }
120
121 fn type_def(&self, _: &state::TypeState) -> TypeDef {
122 TypeDef::boolean().infallible()
123 }
124}
125
126#[cfg(test)]
127#[allow(clippy::trivial_regex)]
128mod tests {
129 use crate::value;
130
131 use super::*;
132
133 test_function![
134 r#match => Match;
135
136 yes {
137 args: func_args![value: "foobar",
138 pattern: Value::Regex(Regex::new("\\s\\w+").unwrap().into())],
139 want: Ok(value!(false)),
140 tdef: TypeDef::boolean().infallible(),
141 }
142
143 no {
144 args: func_args![value: "foo 2 bar",
145 pattern: Value::Regex(Regex::new("foo \\d bar").unwrap().into())],
146 want: Ok(value!(true)),
147 tdef: TypeDef::boolean().infallible(),
148 }
149 ];
150}