1use nom::{
42 IResult, Parser,
43 branch::alt,
44 bytes::complete::{tag, tag_no_case},
45 character::complete::{char, satisfy},
46 combinator::{map, not, opt, peek, value},
47 multi::separated_list0,
48 sequence::delimited,
49};
50
51use crate::ast::{
52 BinaryModifier, BinaryOp, GroupModifier, GroupSide, VectorMatching, VectorMatchingOp,
53};
54use crate::lexer::{identifier::label_name, whitespace::ws_opt};
55
56fn word_boundary(input: &str) -> IResult<&str, ()> {
58 not(peek(satisfy(|c| c.is_alphanumeric() || c == '_'))).parse(input)
59}
60
61pub fn binary_op(input: &str) -> IResult<&str, BinaryOp> {
66 alt((
67 value(BinaryOp::Eq, tag("==")),
69 value(BinaryOp::Ne, tag("!=")),
70 value(BinaryOp::Le, tag("<=")),
71 value(BinaryOp::Ge, tag(">=")),
72 value(BinaryOp::Add, tag("+")),
74 value(BinaryOp::Sub, tag("-")),
75 value(BinaryOp::Mul, tag("*")),
76 value(BinaryOp::Div, tag("/")),
77 value(BinaryOp::Mod, tag("%")),
78 value(BinaryOp::Pow, tag("^")),
79 value(BinaryOp::Lt, tag("<")),
80 value(BinaryOp::Gt, tag(">")),
81 keyword_binary_op,
83 ))
84 .parse(input)
85}
86
87fn keyword_binary_op(input: &str) -> IResult<&str, BinaryOp> {
89 (
91 alt((
92 value(BinaryOp::And, tag_no_case("and")),
93 value(BinaryOp::Or, tag_no_case("or")),
94 value(BinaryOp::Unless, tag_no_case("unless")),
95 value(BinaryOp::Atan2, tag_no_case("atan2")),
96 )),
97 word_boundary,
98 )
99 .map(|(op, _)| op)
100 .parse(input)
101}
102
103fn bool_modifier(input: &str) -> IResult<&str, bool> {
105 (tag_no_case("bool"), word_boundary)
106 .map(|_| true)
107 .parse(input)
108}
109
110fn vector_matching_op(input: &str) -> IResult<&str, VectorMatchingOp> {
112 (
113 alt((
114 value(VectorMatchingOp::On, tag_no_case("on")),
115 value(VectorMatchingOp::Ignoring, tag_no_case("ignoring")),
116 )),
117 word_boundary,
118 )
119 .map(|(op, _)| op)
120 .parse(input)
121}
122
123fn label_list(input: &str) -> IResult<&str, Vec<String>> {
125 delimited(
126 (char('('), ws_opt),
127 separated_list0(
128 delimited(ws_opt, char(','), ws_opt),
129 map(label_name, |s| s.to_string()),
130 ),
131 (ws_opt, char(')')),
132 )
133 .parse(input)
134}
135
136fn group_modifier(input: &str) -> IResult<&str, GroupModifier> {
138 (
139 alt((
140 value(GroupSide::Left, tag_no_case("group_left")),
141 value(GroupSide::Right, tag_no_case("group_right")),
142 )),
143 word_boundary,
144 ws_opt,
145 opt(label_list),
146 )
147 .map(|(side, _, _, labels)| GroupModifier {
148 side,
149 labels: labels.unwrap_or_default(),
150 })
151 .parse(input)
152}
153
154fn vector_matching(input: &str) -> IResult<&str, VectorMatching> {
156 (
157 vector_matching_op,
158 ws_opt,
159 label_list,
160 ws_opt,
161 opt(group_modifier),
162 )
163 .map(|(op, _, labels, _, group)| VectorMatching { op, labels, group })
164 .parse(input)
165}
166
167pub(crate) fn binary_modifier(input: &str) -> IResult<&str, BinaryModifier> {
172 let (rest, (_, return_bool, _, matching)) =
173 (ws_opt, opt(bool_modifier), ws_opt, opt(vector_matching)).parse(input)?;
174
175 if return_bool.is_none() && matching.is_none() {
177 return Err(nom::Err::Error(nom::error::Error::new(
178 input,
179 nom::error::ErrorKind::Tag,
180 )));
181 }
182
183 Ok((
184 rest,
185 BinaryModifier {
186 return_bool: return_bool.unwrap_or(false),
187 matching,
188 },
189 ))
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
198 fn test_binary_op_arithmetic() {
199 assert_eq!(binary_op("+").unwrap().1, BinaryOp::Add);
200 assert_eq!(binary_op("-").unwrap().1, BinaryOp::Sub);
201 assert_eq!(binary_op("*").unwrap().1, BinaryOp::Mul);
202 assert_eq!(binary_op("/").unwrap().1, BinaryOp::Div);
203 assert_eq!(binary_op("%").unwrap().1, BinaryOp::Mod);
204 assert_eq!(binary_op("^").unwrap().1, BinaryOp::Pow);
205 }
206
207 #[test]
208 fn test_binary_op_comparison() {
209 assert_eq!(binary_op("==").unwrap().1, BinaryOp::Eq);
210 assert_eq!(binary_op("!=").unwrap().1, BinaryOp::Ne);
211 assert_eq!(binary_op("<").unwrap().1, BinaryOp::Lt);
212 assert_eq!(binary_op("<=").unwrap().1, BinaryOp::Le);
213 assert_eq!(binary_op(">").unwrap().1, BinaryOp::Gt);
214 assert_eq!(binary_op(">=").unwrap().1, BinaryOp::Ge);
215 }
216
217 #[test]
218 fn test_binary_op_keywords() {
219 assert_eq!(binary_op("and").unwrap().1, BinaryOp::And);
220 assert_eq!(binary_op("AND").unwrap().1, BinaryOp::And);
221 assert_eq!(binary_op("or").unwrap().1, BinaryOp::Or);
222 assert_eq!(binary_op("OR").unwrap().1, BinaryOp::Or);
223 assert_eq!(binary_op("unless").unwrap().1, BinaryOp::Unless);
224 assert_eq!(binary_op("UNLESS").unwrap().1, BinaryOp::Unless);
225 assert_eq!(binary_op("atan2").unwrap().1, BinaryOp::Atan2);
226 assert_eq!(binary_op("ATAN2").unwrap().1, BinaryOp::Atan2);
227 }
228
229 #[test]
230 fn test_binary_op_word_boundary() {
231 assert!(binary_op("andy").is_err());
233 assert!(binary_op("orange").is_err());
235 assert!(binary_op("atan2x").is_err());
237 }
238
239 #[test]
240 fn test_binary_op_with_remaining() {
241 let (rest, op) = binary_op("+ foo").unwrap();
242 assert_eq!(op, BinaryOp::Add);
243 assert_eq!(rest, " foo");
244
245 let (rest, op) = binary_op("and bar").unwrap();
246 assert_eq!(op, BinaryOp::And);
247 assert_eq!(rest, " bar");
248 }
249
250 #[test]
252 fn test_vector_matching_on() {
253 let (rest, vm) = vector_matching("on(job, instance)").unwrap();
254 assert!(rest.is_empty());
255 assert_eq!(vm.op, VectorMatchingOp::On);
256 assert_eq!(vm.labels, vec!["job", "instance"]);
257 assert!(vm.group.is_none());
258 }
259
260 #[test]
261 fn test_vector_matching_ignoring() {
262 let (rest, vm) = vector_matching("ignoring(instance)").unwrap();
263 assert!(rest.is_empty());
264 assert_eq!(vm.op, VectorMatchingOp::Ignoring);
265 assert_eq!(vm.labels, vec!["instance"]);
266 }
267
268 #[test]
269 fn test_vector_matching_empty() {
270 let (rest, vm) = vector_matching("on()").unwrap();
271 assert!(rest.is_empty());
272 assert_eq!(vm.op, VectorMatchingOp::On);
273 assert!(vm.labels.is_empty());
274 }
275
276 #[test]
277 fn test_vector_matching_with_group_left() {
278 let (rest, vm) = vector_matching("on(job) group_left").unwrap();
279 assert!(rest.is_empty());
280 assert_eq!(vm.op, VectorMatchingOp::On);
281 let group = vm.group.unwrap();
282 assert_eq!(group.side, GroupSide::Left);
283 assert!(group.labels.is_empty());
284 }
285
286 #[test]
287 fn test_vector_matching_with_group_right_labels() {
288 let (rest, vm) = vector_matching("ignoring(instance) group_right(job)").unwrap();
289 assert!(rest.is_empty());
290 assert_eq!(vm.op, VectorMatchingOp::Ignoring);
291 let group = vm.group.unwrap();
292 assert_eq!(group.side, GroupSide::Right);
293 assert_eq!(group.labels, vec!["job"]);
294 }
295
296 #[test]
297 fn test_vector_matching_case_insensitive() {
298 let (_, vm) = vector_matching("ON(job)").unwrap();
299 assert_eq!(vm.op, VectorMatchingOp::On);
300
301 let (_, vm) = vector_matching("IGNORING(job)").unwrap();
302 assert_eq!(vm.op, VectorMatchingOp::Ignoring);
303
304 let (_, vm) = vector_matching("on(job) GROUP_LEFT").unwrap();
305 assert!(vm.group.is_some());
306 }
307
308 #[test]
310 fn test_binary_modifier_bool_only() {
311 let (rest, m) = binary_modifier(" bool").unwrap();
312 assert!(rest.is_empty() || rest.chars().all(|c| c.is_whitespace()));
313 assert!(m.return_bool);
314 assert!(m.matching.is_none());
315 }
316
317 #[test]
318 fn test_binary_modifier_matching_only() {
319 let (rest, m) = binary_modifier(" on(job)").unwrap();
320 assert!(rest.is_empty());
321 assert!(!m.return_bool);
322 assert!(m.matching.is_some());
323 }
324
325 #[test]
326 fn test_binary_modifier_bool_and_matching() {
327 let (rest, m) = binary_modifier(" bool on(job)").unwrap();
328 assert!(rest.is_empty());
329 assert!(m.return_bool);
330 assert!(m.matching.is_some());
331 }
332
333 #[test]
334 fn test_binary_modifier_fails_on_empty() {
335 assert!(binary_modifier("foo").is_err());
336 }
337
338 #[test]
340 fn test_vector_matching_display() {
341 let vm = VectorMatching {
342 op: VectorMatchingOp::On,
343 labels: vec!["job".to_string()],
344 group: None,
345 };
346 assert_eq!(vm.to_string(), "on (job)");
347
348 let vm = VectorMatching {
349 op: VectorMatchingOp::Ignoring,
350 labels: vec!["job".to_string(), "instance".to_string()],
351 group: Some(GroupModifier {
352 side: GroupSide::Left,
353 labels: vec![],
354 }),
355 };
356 assert_eq!(vm.to_string(), "ignoring (job, instance) group_left");
358 }
359
360 #[test]
361 fn test_binary_modifier_display() {
362 let m = BinaryModifier {
363 return_bool: true,
364 matching: None,
365 };
366 assert_eq!(m.to_string(), "bool");
367
368 let m = BinaryModifier {
369 return_bool: false,
370 matching: Some(VectorMatching {
371 op: VectorMatchingOp::On,
372 labels: vec!["job".to_string()],
373 group: None,
374 }),
375 };
376 assert_eq!(m.to_string(), "on (job)");
377 }
378}