Skip to main content

ppvm_pauli_word/pattern/
parse.rs

1// SPDX-FileCopyrightText: 2026 The PPVM Authors
2// SPDX-License-Identifier: Apache-2.0
3
4use anyhow::Result;
5use std::collections::BTreeSet;
6
7use super::data::{Decorated, NotIdentity, OpPattern, PauliPattern};
8
9impl PauliPattern {
10    /// Parse a pattern from its textual form.
11    ///
12    /// The grammar supports literal Paulis (`X`, `Y`, `Z`), the
13    /// don't-care symbol `_`, alternation in brackets (`[XY]`),
14    /// optional-identity suffix (`X?`), star repetition (`X*`),
15    /// counted repetition (`X{3}`), and absolute-position anchoring
16    /// (`X3` matches an `X` at qubit 3).
17    ///
18    /// # Examples
19    ///
20    /// ```
21    /// use ppvm_pauli_word::pattern::{Contains, PauliPattern};
22    /// use ppvm_pauli_word::word::PauliWord;
23    ///
24    /// let pat = PauliPattern::parse("X0Y1Z2").unwrap();
25    /// let w: PauliWord<u64> = "XYZ".into();
26    /// assert!(pat.contains(&w));
27    ///
28    /// let alt = PauliPattern::parse("[XY]0Y1Z2").unwrap();
29    /// let w2: PauliWord<u64> = "YYZ".into();
30    /// assert!(alt.contains(&w2));
31    /// ```
32    pub fn parse(input: impl AsRef<str>) -> Result<Self> {
33        let mut patterns = Vec::new();
34        let mut chars = input.as_ref().trim().chars().peekable();
35        while let Some(c) = chars.next() {
36            let op = match c {
37                '[' => {
38                    let mut alter = BTreeSet::new();
39                    while let Some(ch) = chars.peek() {
40                        let op = match ch {
41                            ']' => {
42                                chars.next(); // consume ']'
43                                break;
44                            }
45                            'X' => {
46                                chars.next();
47                                NotIdentity::X
48                            }
49                            'Y' => {
50                                chars.next();
51                                NotIdentity::Y
52                            }
53                            'Z' => {
54                                chars.next();
55                                NotIdentity::Z
56                            }
57                            _ => Err(anyhow::anyhow!("Expected X, Y, or Z in []"))?,
58                        };
59                        alter.insert(op);
60                    } // while
61
62                    let mut alter_iter = alter.iter();
63                    match alter.len() {
64                        1 => OpPattern::Single(*alter_iter.next().unwrap()),
65                        2 => OpPattern::Double(
66                            *alter_iter.next().unwrap(),
67                            *alter_iter.next().unwrap(),
68                        ),
69                        3 => OpPattern::AnyNonIdentity,
70                        _ => Err(anyhow::anyhow!("Too many Pauli characters in pattern"))?,
71                    }
72                }
73                '_' => OpPattern::AnyPauliOrIdentity,
74                'X' => OpPattern::Single(NotIdentity::X),
75                'Y' => OpPattern::Single(NotIdentity::Y),
76                'Z' => OpPattern::Single(NotIdentity::Z),
77                _ => Err(anyhow::anyhow!("Expected X, Y, or Z in Pauli pattern"))?,
78            }; // op
79
80            let op = match chars.peek() {
81                Some('?') => {
82                    chars.next();
83                    op.add_identity()
84                }
85                _ => op,
86            };
87
88            match chars.peek() {
89                Some('*') => {
90                    chars.next(); // consume '*'
91                    patterns.push(Decorated::Star(op));
92                }
93                Some('{') => {
94                    chars.next(); // consume '{'
95                    let mut num = String::new();
96                    while let Some(ch) = chars.peek() {
97                        if ch.is_ascii_digit() {
98                            num.push(*ch);
99                            chars.next();
100                        } else if *ch == '}' {
101                            chars.next(); // consume '}'
102                            break;
103                        } else {
104                            Err(anyhow::anyhow!("Expected digit or '}}' after '{{'"))?;
105                        }
106                    }
107                    let count = num.parse::<usize>()?;
108                    patterns.push(Decorated::Repeat(op, count));
109                }
110                Some(x) if x.is_ascii_digit() => {
111                    let mut num = String::new();
112                    while let Some(ch) = chars.peek() {
113                        if ch.is_ascii_digit() {
114                            num.push(*ch);
115                            chars.next();
116                        } else {
117                            break;
118                        }
119                    }
120                    let position = num.parse::<usize>()?;
121                    patterns.push(Decorated::Position(op, position));
122                }
123                _ => Err(anyhow::anyhow!("Expected '*' or digit after Pauli pattern"))?,
124            }
125        }
126        Ok(PauliPattern(patterns))
127    }
128
129    pub(crate) fn iter(&self) -> impl Iterator<Item = &Decorated> {
130        self.0.iter()
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137    use Decorated::*;
138    use NotIdentity::*;
139    use OpPattern::*;
140
141    #[test]
142    fn test_parse_patterns() {
143        let answer = PauliPattern::parse("X1Y2Z3").unwrap();
144        let expect = PauliPattern(vec![
145            Position(Single(X), 1),
146            Position(Single(Y), 2),
147            Position(Single(Z), 3),
148        ]);
149        assert_eq!(answer, expect);
150
151        let answer = PauliPattern::parse("[XY]1Y2_3").unwrap();
152        let expect = PauliPattern(vec![
153            Position(Double(X, Y), 1),
154            Position(Single(Y), 2),
155            Position(AnyPauliOrIdentity, 3),
156        ]);
157        assert_eq!(answer, expect);
158
159        let answer = PauliPattern::parse("X?1Y2Z3").unwrap();
160        let expect = PauliPattern(vec![
161            Position(SingleOrIdentity(X), 1),
162            Position(Single(Y), 2),
163            Position(Single(Z), 3),
164        ]);
165        assert_eq!(answer, expect);
166
167        let answer = PauliPattern::parse("[XY]*Z5").unwrap();
168        let expect = PauliPattern(vec![Star(Double(X, Y)), Position(Single(Z), 5)]);
169        assert_eq!(answer, expect);
170
171        let answer = PauliPattern::parse("[XY]{3}Z5").unwrap();
172        let expect = PauliPattern(vec![Repeat(Double(X, Y), 3), Position(Single(Z), 5)]);
173        assert_eq!(answer, expect);
174    }
175
176    #[test]
177    #[cfg(feature = "bincode")]
178    fn test_bincode() {
179        use bincode;
180        let pat = PauliPattern::parse("[XY]{2}Z3").unwrap();
181        let mut encoded = [0u8; 100];
182        bincode::encode_into_slice(&pat, &mut encoded[..], bincode::config::standard()).unwrap();
183        let (decoded, _) =
184            bincode::decode_from_slice(&encoded, bincode::config::standard()).unwrap();
185        assert_eq!(pat, decoded);
186    }
187}