plotnik_bytecode/
predicate_op.rs

1//! Predicate operators for bytecode.
2//!
3//! Extracted from parser for use by both compiler and runtime.
4//! The runtime needs to decode predicate operators from bytecode.
5
6/// Predicate operator for node text filtering.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum PredicateOp {
9    /// `==` - equals
10    Eq,
11    /// `!=` - not equals
12    Ne,
13    /// `^=` - starts with
14    StartsWith,
15    /// `$=` - ends with
16    EndsWith,
17    /// `*=` - contains
18    Contains,
19    /// `=~` - regex match
20    RegexMatch,
21    /// `!~` - regex no match
22    RegexNoMatch,
23}
24
25impl PredicateOp {
26    /// Decode from bytecode representation.
27    pub fn from_byte(b: u8) -> Self {
28        match b {
29            0 => Self::Eq,
30            1 => Self::Ne,
31            2 => Self::StartsWith,
32            3 => Self::EndsWith,
33            4 => Self::Contains,
34            5 => Self::RegexMatch,
35            6 => Self::RegexNoMatch,
36            _ => panic!("invalid predicate op byte: {b}"),
37        }
38    }
39
40    /// Encode for bytecode.
41    pub fn to_byte(self) -> u8 {
42        match self {
43            Self::Eq => 0,
44            Self::Ne => 1,
45            Self::StartsWith => 2,
46            Self::EndsWith => 3,
47            Self::Contains => 4,
48            Self::RegexMatch => 5,
49            Self::RegexNoMatch => 6,
50        }
51    }
52
53    /// Operator as display string.
54    pub fn as_str(self) -> &'static str {
55        match self {
56            Self::Eq => "==",
57            Self::Ne => "!=",
58            Self::StartsWith => "^=",
59            Self::EndsWith => "$=",
60            Self::Contains => "*=",
61            Self::RegexMatch => "=~",
62            Self::RegexNoMatch => "!~",
63        }
64    }
65
66    /// Check if this is a regex operator.
67    pub fn is_regex_op(&self) -> bool {
68        matches!(self, Self::RegexMatch | Self::RegexNoMatch)
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75
76    #[test]
77    fn byte_roundtrip() {
78        for op in [
79            PredicateOp::Eq,
80            PredicateOp::Ne,
81            PredicateOp::StartsWith,
82            PredicateOp::EndsWith,
83            PredicateOp::Contains,
84            PredicateOp::RegexMatch,
85            PredicateOp::RegexNoMatch,
86        ] {
87            assert_eq!(PredicateOp::from_byte(op.to_byte()), op);
88        }
89    }
90
91    #[test]
92    fn as_str() {
93        assert_eq!(PredicateOp::Eq.as_str(), "==");
94        assert_eq!(PredicateOp::Ne.as_str(), "!=");
95        assert_eq!(PredicateOp::StartsWith.as_str(), "^=");
96        assert_eq!(PredicateOp::EndsWith.as_str(), "$=");
97        assert_eq!(PredicateOp::Contains.as_str(), "*=");
98        assert_eq!(PredicateOp::RegexMatch.as_str(), "=~");
99        assert_eq!(PredicateOp::RegexNoMatch.as_str(), "!~");
100    }
101
102    #[test]
103    fn is_regex_op() {
104        assert!(!PredicateOp::Eq.is_regex_op());
105        assert!(!PredicateOp::Ne.is_regex_op());
106        assert!(!PredicateOp::StartsWith.is_regex_op());
107        assert!(!PredicateOp::EndsWith.is_regex_op());
108        assert!(!PredicateOp::Contains.is_regex_op());
109        assert!(PredicateOp::RegexMatch.is_regex_op());
110        assert!(PredicateOp::RegexNoMatch.is_regex_op());
111    }
112}