1use std::collections::BTreeSet;
30use std::ops::Range;
31
32use crate::ast::BinOpKind;
33use crate::parser::binding_power;
34
35#[derive(Debug, Clone, PartialEq)]
37pub struct Predicate {
38 pub kind: PredicateKind,
40 pub span: Range<usize>,
43}
44
45#[derive(Debug, Clone, PartialEq)]
47pub enum PredicateKind {
48 Or(Vec<Predicate>),
50 And(Vec<Predicate>),
52 Not(Box<Predicate>),
54 Compare(Comparison, Box<Predicate>, Box<Predicate>),
56 In(Box<Predicate>, Vec<Predicate>),
58 Element(String),
61 Literal(PredicateLiteral),
63 Arith(BinOpKind, Box<Predicate>, Box<Predicate>),
66 Expr,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum Comparison {
73 Eq,
75 Ne,
77 Lt,
79 Le,
81 Gt,
83 Ge,
85}
86
87impl Comparison {
88 pub fn as_str(self) -> &'static str {
90 match self {
91 Comparison::Eq => "==",
92 Comparison::Ne => "!=",
93 Comparison::Lt => "<",
94 Comparison::Le => "<=",
95 Comparison::Gt => ">",
96 Comparison::Ge => ">=",
97 }
98 }
99
100 pub fn swapped(self) -> Self {
102 match self {
103 Comparison::Lt => Comparison::Gt,
104 Comparison::Le => Comparison::Ge,
105 Comparison::Gt => Comparison::Lt,
106 Comparison::Ge => Comparison::Le,
107 other => other,
108 }
109 }
110}
111
112#[derive(Debug, Clone, PartialEq)]
114pub enum PredicateLiteral {
115 Int(i128),
117 Float(f64),
119 Str(String),
121 Bool(bool),
123}
124
125impl Predicate {
126 pub fn text<'a>(&self, source: &'a str) -> &'a str {
128 &source[self.span.clone()]
129 }
130}
131
132pub fn parse_predicate(text: &str) -> Result<Predicate, String> {
136 let tokens = tokenize(text)?;
137 let mut parser = Parser { tokens, pos: 0 };
138 let predicate = parser.expression(0)?;
139 match parser.tokens.get(parser.pos) {
140 None => Ok(predicate),
141 Some(token) => Err(format!(
142 "unexpected `{}` in predicate `{text}`",
143 &text[token.span.clone()]
144 )),
145 }
146}
147
148#[derive(Debug, Clone, Default, PartialEq, Eq)]
150pub struct PredicateReads {
151 pub elements: BTreeSet<String>,
154 pub bare: BTreeSet<String>,
157}
158
159pub fn predicate_reads(text: &str) -> PredicateReads {
165 let mut reads = PredicateReads::default();
166 match parse_predicate(text) {
167 Ok(tree) => collect_reads(&tree, text, &mut reads),
168 Err(_) => expression_reads(text, &mut reads),
169 }
170 reads
171}
172
173fn collect_reads(node: &Predicate, text: &str, reads: &mut PredicateReads) {
174 match &node.kind {
175 PredicateKind::Or(parts) | PredicateKind::And(parts) => {
176 for part in parts {
177 collect_reads(part, text, reads);
178 }
179 }
180 PredicateKind::Not(inner) => collect_reads(inner, text, reads),
181 PredicateKind::Compare(_, a, b) | PredicateKind::Arith(_, a, b) => {
182 collect_reads(a, text, reads);
183 collect_reads(b, text, reads);
184 }
185 PredicateKind::In(needle, items) => {
186 collect_reads(needle, text, reads);
187 for item in items {
188 collect_reads(item, text, reads);
189 }
190 }
191 PredicateKind::Element(name) => {
192 reads.elements.insert(name.clone());
193 }
194 PredicateKind::Literal(_) => {}
195 PredicateKind::Expr => expression_reads(node.text(text), reads),
196 }
197}
198
199fn expression_reads(text: &str, reads: &mut PredicateReads) {
204 let mut interpolated = BTreeSet::new();
205 crate::refs::collect_string_interpolation_refs(text, &mut interpolated);
206 let mut plain = String::with_capacity(text.len());
207 let mut at = 0;
208 if let Ok(tokens) = tokenize(text) {
209 for token in tokens {
210 if let Tok::Element(_) = token.tok {
211 plain.push_str(&text[at..token.span.start]);
212 plain.push('0');
213 at = token.span.end;
214 }
215 }
216 }
217 plain.push_str(&text[at..]);
218 reads.bare.extend(
219 crate::refs::referenced_names(&plain)
220 .into_iter()
221 .filter(|name| !interpolated.contains(name)),
222 );
223 reads.elements.extend(interpolated);
224}
225
226#[derive(Debug, Clone)]
227enum Tok {
228 Element(String),
229 Ident(String),
230 Int(i128),
231 Float(f64),
232 Str(String),
233 Op(BinOpKind),
234 Bang,
235 LParen,
236 RParen,
237 LBracket,
238 RBracket,
239 Comma,
240 Other,
241}
242
243#[derive(Debug, Clone)]
244struct Token {
245 tok: Tok,
246 span: Range<usize>,
247}
248
249fn is_ident_start(c: char) -> bool {
250 c.is_ascii_alphabetic() || c == '_'
251}
252
253fn is_ident_char(c: char) -> bool {
254 c.is_ascii_alphanumeric() || c == '_'
255}
256
257fn tokenize(text: &str) -> Result<Vec<Token>, String> {
258 const OPERATORS: [(&str, Option<BinOpKind>); 22] = [
259 ("||", Some(BinOpKind::Or)),
260 ("&&", Some(BinOpKind::And)),
261 ("==", Some(BinOpKind::Eq)),
262 ("!=", Some(BinOpKind::Ne)),
263 ("<=", Some(BinOpKind::Le)),
264 (">=", Some(BinOpKind::Ge)),
265 ("<<", Some(BinOpKind::Shl)),
266 (">>", Some(BinOpKind::Shr)),
267 ("**", Some(BinOpKind::Pow)),
268 ("|", Some(BinOpKind::BitOr)),
269 ("&", Some(BinOpKind::BitAnd)),
270 ("^", Some(BinOpKind::BitXor)),
271 ("<", Some(BinOpKind::Lt)),
272 (">", Some(BinOpKind::Gt)),
273 ("+", Some(BinOpKind::Add)),
274 ("-", Some(BinOpKind::Sub)),
275 ("*", Some(BinOpKind::Mul)),
276 ("/", Some(BinOpKind::Div)),
277 ("%", Some(BinOpKind::Mod)),
278 ("!", None),
279 ("(", None),
280 (")", None),
281 ];
282 let mut tokens = Vec::new();
283 let bytes = text.as_bytes();
284 let mut i = 0;
285 'scan: while i < text.len() {
286 let c = text[i..].chars().next().expect("in bounds");
287 let start = i;
288 if c.is_whitespace() {
289 i += c.len_utf8();
290 continue;
291 }
292 if c == '{' {
293 let rest = &text[i + 1..];
294 let name_len = rest
295 .find(|ch: char| !is_ident_char(ch))
296 .unwrap_or(rest.len());
297 if name_len > 0 && rest[name_len..].starts_with('}') {
298 tokens.push(Token {
299 tok: Tok::Element(rest[..name_len].to_string()),
300 span: start..i + name_len + 2,
301 });
302 i += name_len + 2;
303 continue;
304 }
305 }
306 if c == '"' || c == '\'' {
307 let mut value = String::new();
308 let mut j = i + 1;
309 loop {
310 let Some(ch) = text[j..].chars().next() else {
311 return Err(format!("unterminated string in predicate `{text}`"));
312 };
313 j += ch.len_utf8();
314 if ch == c {
315 break;
316 }
317 if ch == '\\'
318 && let Some(escaped) = text[j..].chars().next()
319 {
320 value.push(escaped);
321 j += escaped.len_utf8();
322 continue;
323 }
324 value.push(ch);
325 }
326 tokens.push(Token {
327 tok: Tok::Str(value),
328 span: start..j,
329 });
330 i = j;
331 continue;
332 }
333 if c.is_ascii_digit() {
334 let mut j = i;
335 while j < text.len() && bytes[j].is_ascii_digit() {
336 j += 1;
337 }
338 let mut float = false;
339 if j + 1 < text.len() && bytes[j] == b'.' && bytes[j + 1].is_ascii_digit() {
340 float = true;
341 j += 1;
342 while j < text.len() && bytes[j].is_ascii_digit() {
343 j += 1;
344 }
345 }
346 if j < text.len() && (bytes[j] == b'e' || bytes[j] == b'E') {
347 let mut k = j + 1;
348 if k < text.len() && (bytes[k] == b'+' || bytes[k] == b'-') {
349 k += 1;
350 }
351 if k < text.len() && bytes[k].is_ascii_digit() {
352 float = true;
353 j = k;
354 while j < text.len() && bytes[j].is_ascii_digit() {
355 j += 1;
356 }
357 }
358 }
359 let digits = &text[i..j];
360 let tok = if float {
361 Tok::Float(
362 digits
363 .parse()
364 .map_err(|e| format!("bad number `{digits}`: {e}"))?,
365 )
366 } else {
367 Tok::Int(
368 digits
369 .parse()
370 .map_err(|e| format!("bad number `{digits}`: {e}"))?,
371 )
372 };
373 tokens.push(Token { tok, span: i..j });
374 i = j;
375 continue;
376 }
377 if is_ident_start(c) {
378 let rest = &text[i..];
379 let len = rest
380 .find(|ch: char| !is_ident_char(ch))
381 .unwrap_or(rest.len());
382 tokens.push(Token {
383 tok: Tok::Ident(rest[..len].to_string()),
384 span: i..i + len,
385 });
386 i += len;
387 continue;
388 }
389 for (spelling, op) in OPERATORS {
390 if text[i..].starts_with(spelling) {
391 let tok = match (spelling, op) {
392 (_, Some(op)) => Tok::Op(op),
393 ("!", None) => Tok::Bang,
394 ("(", None) => Tok::LParen,
395 _ => Tok::RParen,
396 };
397 tokens.push(Token {
398 tok,
399 span: i..i + spelling.len(),
400 });
401 i += spelling.len();
402 continue 'scan;
403 }
404 }
405 let tok = match c {
406 '[' => Tok::LBracket,
407 ']' => Tok::RBracket,
408 ',' => Tok::Comma,
409 _ => Tok::Other,
410 };
411 tokens.push(Token {
412 tok,
413 span: i..i + c.len_utf8(),
414 });
415 i += c.len_utf8();
416 }
417 Ok(tokens)
418}
419
420struct Parser {
421 tokens: Vec<Token>,
422 pos: usize,
423}
424
425#[derive(Clone, Copy)]
427enum Infix {
428 Binary(BinOpKind),
429 In,
430}
431
432impl Parser {
433 fn peek(&self) -> Option<&Tok> {
434 self.tokens.get(self.pos).map(|t| &t.tok)
435 }
436
437 fn peek_at(&self, offset: usize) -> Option<&Tok> {
438 self.tokens.get(self.pos + offset).map(|t| &t.tok)
439 }
440
441 fn next(&mut self) -> Result<Token, String> {
442 let token = self
443 .tokens
444 .get(self.pos)
445 .cloned()
446 .ok_or_else(|| "the predicate ends early".to_string())?;
447 self.pos += 1;
448 Ok(token)
449 }
450
451 fn expression(&mut self, min_bp: u8) -> Result<Predicate, String> {
454 let mut lhs = self.prefixed()?;
455 lhs = self.postfix_as(lhs)?;
456 loop {
457 let infix = match self.peek() {
458 Some(Tok::Op(op)) => Infix::Binary(*op),
459 Some(Tok::Ident(word)) if word == "in" => Infix::In,
460 _ => break,
461 };
462 let (l_bp, r_bp) = match infix {
463 Infix::Binary(op) => binding_power(op),
464 Infix::In => binding_power(BinOpKind::Lt),
465 };
466 if l_bp < min_bp {
467 break;
468 }
469 self.pos += 1;
470 lhs = match infix {
471 Infix::In => {
472 let (items, end) = self.list()?;
473 Predicate {
474 span: lhs.span.start..end,
475 kind: PredicateKind::In(Box::new(lhs), items),
476 }
477 }
478 Infix::Binary(op) => {
479 let rhs = self.expression(r_bp)?;
480 combine(op, lhs, rhs)
481 }
482 };
483 }
484 Ok(lhs)
485 }
486
487 fn prefixed(&mut self) -> Result<Predicate, String> {
489 match self.peek() {
490 Some(Tok::Bang) => {
491 let start = self.next()?.span.start;
492 let operand = self.prefixed()?;
493 Ok(Predicate {
494 span: start..operand.span.end,
495 kind: PredicateKind::Not(Box::new(operand)),
496 })
497 }
498 Some(Tok::Op(BinOpKind::Sub)) => {
499 let start = self.next()?.span.start;
500 let operand = self.prefixed()?;
501 let span = start..operand.span.end;
502 let kind = match operand.kind {
503 PredicateKind::Literal(PredicateLiteral::Int(n)) => {
504 PredicateKind::Literal(PredicateLiteral::Int(-n))
505 }
506 PredicateKind::Literal(PredicateLiteral::Float(f)) => {
507 PredicateKind::Literal(PredicateLiteral::Float(-f))
508 }
509 _ => PredicateKind::Expr,
510 };
511 Ok(Predicate { kind, span })
512 }
513 _ => self.atom(),
514 }
515 }
516
517 fn postfix_as(&mut self, mut operand: Predicate) -> Result<Predicate, String> {
519 while matches!(self.peek(), Some(Tok::Ident(w)) if w == "as")
520 && matches!(self.peek_at(1), Some(Tok::Ident(_)))
521 {
522 self.pos += 1;
523 let end = self.next()?.span.end;
524 operand = Predicate {
525 span: operand.span.start..end,
526 kind: PredicateKind::Expr,
527 };
528 }
529 Ok(operand)
530 }
531
532 fn atom(&mut self) -> Result<Predicate, String> {
533 let token = self.next()?;
534 let span = token.span.clone();
535 let kind = match token.tok {
536 Tok::LParen => {
537 let inner = self.expression(0)?;
538 let close = self.next()?;
539 if !matches!(close.tok, Tok::RParen) {
540 return Err("expected `)` in predicate".to_string());
541 }
542 return Ok(Predicate {
543 kind: inner.kind,
544 span: span.start..close.span.end,
545 });
546 }
547 Tok::Element(name) => PredicateKind::Element(name),
548 Tok::Int(n) => PredicateKind::Literal(PredicateLiteral::Int(n)),
549 Tok::Float(f) => PredicateKind::Literal(PredicateLiteral::Float(f)),
550 Tok::Str(s) => PredicateKind::Literal(PredicateLiteral::Str(s)),
551 Tok::Ident(word) if matches!(self.peek(), Some(Tok::LParen)) => {
552 let _ = word;
555 let end = self.skip_group()?;
556 return Ok(Predicate {
557 kind: PredicateKind::Expr,
558 span: span.start..end,
559 });
560 }
561 Tok::Ident(word) => match word.as_str() {
562 "true" => PredicateKind::Literal(PredicateLiteral::Bool(true)),
563 "false" => PredicateKind::Literal(PredicateLiteral::Bool(false)),
564 _ => PredicateKind::Expr,
567 },
568 _ => return Err("expected a value in predicate".to_string()),
569 };
570 Ok(Predicate { kind, span })
571 }
572
573 fn skip_group(&mut self) -> Result<usize, String> {
575 let mut depth = 0usize;
576 loop {
577 let token = self.next()?;
578 match token.tok {
579 Tok::LParen => depth += 1,
580 Tok::RParen => {
581 depth -= 1;
582 if depth == 0 {
583 return Ok(token.span.end);
584 }
585 }
586 _ => {}
587 }
588 }
589 }
590
591 fn list(&mut self) -> Result<(Vec<Predicate>, usize), String> {
593 if !matches!(self.next()?.tok, Tok::LBracket) {
594 return Err("expected `[` after `in`".to_string());
595 }
596 let mut items = Vec::new();
597 if matches!(self.peek(), Some(Tok::RBracket)) {
598 return Ok((items, self.next()?.span.end));
599 }
600 loop {
601 items.push(self.expression(0)?);
602 let token = self.next()?;
603 match token.tok {
604 Tok::Comma => {}
605 Tok::RBracket => return Ok((items, token.span.end)),
606 _ => return Err("expected `,` or `]` in an `in` list".to_string()),
607 }
608 }
609 }
610}
611
612fn combine(op: BinOpKind, lhs: Predicate, rhs: Predicate) -> Predicate {
616 let span = lhs.span.start..rhs.span.end;
617 let comparison = match op {
618 BinOpKind::Eq => Comparison::Eq,
619 BinOpKind::Ne => Comparison::Ne,
620 BinOpKind::Lt => Comparison::Lt,
621 BinOpKind::Le => Comparison::Le,
622 BinOpKind::Gt => Comparison::Gt,
623 BinOpKind::Ge => Comparison::Ge,
624 BinOpKind::Or => {
625 let parts = gather(lhs, rhs, |k| match k {
626 PredicateKind::Or(parts) => Some(parts),
627 _ => None,
628 });
629 return Predicate {
630 kind: PredicateKind::Or(parts),
631 span,
632 };
633 }
634 BinOpKind::And => {
635 let parts = gather(lhs, rhs, |k| match k {
636 PredicateKind::And(parts) => Some(parts),
637 _ => None,
638 });
639 return Predicate {
640 kind: PredicateKind::And(parts),
641 span,
642 };
643 }
644 BinOpKind::Add
645 | BinOpKind::Sub
646 | BinOpKind::Mul
647 | BinOpKind::Div
648 | BinOpKind::Mod
649 | BinOpKind::Pow => {
650 return Predicate {
651 kind: PredicateKind::Arith(op, Box::new(lhs), Box::new(rhs)),
652 span,
653 };
654 }
655 _ => {
656 return Predicate {
657 kind: PredicateKind::Expr,
658 span,
659 };
660 }
661 };
662 Predicate {
663 kind: PredicateKind::Compare(comparison, Box::new(lhs), Box::new(rhs)),
664 span,
665 }
666}
667
668fn gather(
671 lhs: Predicate,
672 rhs: Predicate,
673 parts_of: impl Fn(PredicateKind) -> Option<Vec<Predicate>>,
674) -> Vec<Predicate> {
675 let span = lhs.span.clone();
676 let mut parts = match parts_of(lhs.kind.clone()) {
677 Some(parts) if parts.first().map(|p| p.span.start) == Some(span.start) => parts,
678 _ => vec![lhs],
679 };
680 parts.push(rhs);
681 parts
682}
683
684#[cfg(test)]
685mod tests {
686 use super::*;
687
688 fn shape(text: &str) -> String {
691 fn walk(p: &Predicate, text: &str) -> String {
692 match &p.kind {
693 PredicateKind::Or(parts) => format!(
694 "({})",
695 parts
696 .iter()
697 .map(|q| walk(q, text))
698 .collect::<Vec<_>>()
699 .join(" || ")
700 ),
701 PredicateKind::And(parts) => format!(
702 "({})",
703 parts
704 .iter()
705 .map(|q| walk(q, text))
706 .collect::<Vec<_>>()
707 .join(" && ")
708 ),
709 PredicateKind::Not(inner) => format!("(!{})", walk(inner, text)),
710 PredicateKind::Compare(c, a, b) => {
711 format!("({} {} {})", walk(a, text), c.as_str(), walk(b, text))
712 }
713 PredicateKind::In(a, items) => format!(
714 "({} in [{}])",
715 walk(a, text),
716 items
717 .iter()
718 .map(|q| walk(q, text))
719 .collect::<Vec<_>>()
720 .join(", ")
721 ),
722 PredicateKind::Element(name) => format!("{{{name}}}"),
723 PredicateKind::Literal(PredicateLiteral::Int(n)) => n.to_string(),
724 PredicateKind::Literal(PredicateLiteral::Float(f)) => f.to_string(),
725 PredicateKind::Literal(PredicateLiteral::Str(s)) => format!("'{s}'"),
726 PredicateKind::Literal(PredicateLiteral::Bool(b)) => b.to_string(),
727 PredicateKind::Arith(..) | PredicateKind::Expr => format!("<{}>", p.text(text)),
728 }
729 }
730 walk(&parse_predicate(text).unwrap(), text)
731 }
732
733 #[test]
736 fn not_binds_tighter_than_and_and_or() {
737 assert_eq!(shape("!{done} || {retry}"), "((!{done}) || {retry})");
738 assert_eq!(shape("!{a} && {b}"), "((!{a}) && {b})");
739 assert_eq!(shape("{a} || !{b}"), "({a} || (!{b}))");
740 assert_eq!(shape("!({a} || {b})"), "(!({a} || {b}))");
741 assert_eq!(shape("!!{a}"), "(!(!{a}))");
742 }
743
744 #[test]
746 fn every_operator_pair_binds_by_the_one_table() {
747 let cases = [
748 ("{a} || {b} && {c}", "({a} || ({b} && {c}))"),
749 ("{a} && {b} || {c}", "(({a} && {b}) || {c})"),
750 ("{a} && {b} && {c}", "({a} && {b} && {c})"),
751 ("{a} || {b} || {c}", "({a} || {b} || {c})"),
752 ("({a} || {b}) && {c}", "(({a} || {b}) && {c})"),
753 ("{a} == 1 && {b} != 2", "(({a} == 1) && ({b} != 2))"),
754 ("{a} < 1 || {b} >= 2", "(({a} < 1) || ({b} >= 2))"),
755 ("{a} < {b} == {c}", "(({a} < {b}) == {c})"),
756 ("{a} == {b} < {c}", "({a} == ({b} < {c}))"),
757 ("{a} <= 1 && {a} > -1", "(({a} <= 1) && ({a} > -1))"),
758 ("!{a} == {b}", "((!{a}) == {b})"),
759 ("!{a} < 3", "((!{a}) < 3)"),
760 ("{a} + 1 > {b} * 2", "(<{a} + 1> > <{b} * 2>)"),
761 ("{a} + {b} * 2 == 7", "(<{a} + {b} * 2> == 7)"),
762 (
763 "{a} % 2 == 0 || {b} ** 2 > 3",
764 "((<{a} % 2> == 0) || (<{b} ** 2> > 3))",
765 ),
766 ("{a} & 1 == 1", "(<{a} & 1> == 1)"),
767 ("{a} | 1 < 3", "(<{a} | 1> < 3)"),
768 ("{a} << 1 >= 4 && true", "((<{a} << 1> >= 4) && true)"),
769 (
770 "{a} in [1, 2] && {b} == x",
771 "(({a} in [1, 2]) && ({b} == <x>))",
772 ),
773 ("{a} in [1] || !{b}", "(({a} in [1]) || (!{b}))"),
774 ("u64_add({a}, {b}) > 7", "(<u64_add({a}, {b})> > 7)"),
775 ("!is_even({a}) && {b}", "((!<is_even({a})>) && {b})"),
776 ("{a} as f64 > 1.5", "(<{a} as f64> > 1.5)"),
777 ("-{a} < 0", "(<-{a}> < 0)"),
778 ("\"s0\" != 2", "('s0' != 2)"),
779 ("{b} == 'us-east'", "({b} == 'us-east')"),
780 ("{b} == us-east", "({b} == <us-east>)"),
782 ("{b} in [s0, \"s1\"]", "({b} in [<s0>, 's1'])"),
783 ];
784 for (text, expected) in cases {
785 assert_eq!(shape(text), expected, "{text}");
786 }
787 }
788
789 #[test]
792 fn spans_cover_each_operand_as_written() {
793 let text = "({a} > 1 || {b} < 2) && !{c}";
794 let p = parse_predicate(text).unwrap();
795 let PredicateKind::And(parts) = &p.kind else {
796 panic!("{p:?}")
797 };
798 assert_eq!(parts[0].text(text), "({a} > 1 || {b} < 2)");
799 assert_eq!(parts[1].text(text), "!{c}");
800 assert_eq!(p.text(text), text);
801 }
802
803 #[test]
806 fn arithmetic_keeps_its_operands() {
807 let text = "{a} + {b} * 2 > 3";
808 let p = parse_predicate(text).unwrap();
809 let PredicateKind::Compare(Comparison::Gt, lhs, _) = &p.kind else {
810 panic!("{p:?}")
811 };
812 let PredicateKind::Arith(BinOpKind::Add, a, product) = &lhs.kind else {
813 panic!("{lhs:?}")
814 };
815 assert_eq!(a.kind, PredicateKind::Element("a".into()));
816 assert!(matches!(
817 &product.kind,
818 PredicateKind::Arith(BinOpKind::Mul, b, two)
819 if b.kind == PredicateKind::Element("b".into())
820 && two.kind == PredicateKind::Literal(PredicateLiteral::Int(2))
821 ));
822 for opaque in ["{a} & 1", "u64_add({a}, 1)", "{a} as f64"] {
823 assert_eq!(parse_predicate(opaque).unwrap().kind, PredicateKind::Expr);
824 }
825 }
826
827 #[test]
828 fn what_the_grammar_does_not_accept_is_an_error() {
829 for text in ["", "{a} >", "({a} > 1", "{a} in 3", "{a} . b", "\"open"] {
830 assert!(parse_predicate(text).is_err(), "{text}");
831 }
832 }
833}