quil_rs/validation/
identifier.rs

1//! Types and functions related to validating Quil identifiers
2use std::str::FromStr;
3
4use once_cell::sync::Lazy;
5use regex::Regex;
6use thiserror;
7
8use crate::reserved::ReservedToken;
9
10#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
11pub enum IdentifierValidationError {
12    #[error("{0} is a reserved token")]
13    Reserved(ReservedToken),
14
15    #[error("{0} is not a valid identifier")]
16    Invalid(String),
17}
18
19/// A regex that matches only valid Quil identifiers
20const IDENTIFIER_REGEX_STRING: &str = r"^([A-Za-z_]|[A-Za-z_][A-Za-z0-9\-_]*[A-Za-z0-9_])$";
21
22static IDENTIFIER_REGEX: Lazy<Regex> =
23    Lazy::new(|| Regex::new(IDENTIFIER_REGEX_STRING).expect("regex should be valid"));
24
25/// Returns an error if the given identifier is not a valid Quil Identifier
26pub fn validate_identifier(ident: &str) -> Result<(), IdentifierValidationError> {
27    match IDENTIFIER_REGEX.is_match(ident) {
28        true => Ok(()),
29        false => Err(IdentifierValidationError::Invalid(ident.to_string())),
30    }
31}
32
33/// Returns an error if the given identifier is reserved, or if it is not a valid Quil identifier
34pub fn validate_user_identifier(ident: &str) -> Result<(), IdentifierValidationError> {
35    ReservedToken::from_str(ident).map_or(validate_identifier(ident), |t| {
36        Err(IdentifierValidationError::Reserved(t))
37    })
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43    use rstest::rstest;
44
45    #[rstest]
46    #[case("Good_Ident1f1er-0", true)]
47    #[case("H", true)]
48    #[case("-Cant-start-with-dash", false)]
49    #[case("Cant-end-with-dash-", false)]
50    #[case("1-Cant-start-with-number", false)]
51    fn test_validate_identifier(#[case] input: &str, #[case] ok: bool) {
52        assert_eq!(validate_identifier(input).is_ok(), ok)
53    }
54
55    #[rstest]
56    #[case("Good_Ident1f1er-0", true)]
57    #[case("DEFGATE", false)]
58    #[case("AS", false)]
59    #[case("pi", false)]
60    #[case("PAULI-SUM", false)]
61    #[case("H", false)]
62    #[case("G", true)]
63    fn test_validate_user_identifier(#[case] input: &str, #[case] ok: bool) {
64        assert_eq!(validate_user_identifier(input).is_ok(), ok)
65    }
66}