hugr_core/hugr/
ident.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use std::borrow::Borrow;

use derive_more::Display;
use lazy_static::lazy_static;
use regex::Regex;
use smol_str::SmolStr;
use thiserror::Error;

pub static PATH_COMPONENT_REGEX_STR: &str = r"[\w--\d]\w*";
lazy_static! {
    pub static ref PATH_REGEX: Regex =
        Regex::new(&format!(r"^{0}(\.{0})*$", PATH_COMPONENT_REGEX_STR)).unwrap();
}

#[derive(
    Clone,
    Debug,
    Display,
    PartialEq,
    Eq,
    Hash,
    PartialOrd,
    Ord,
    serde::Serialize,
    serde::Deserialize,
)]

/// A non-empty dot-separated list of valid identifiers
pub struct IdentList(SmolStr);

impl IdentList {
    /// Makes an IdentList, checking the supplied string is well-formed
    pub fn new(n: impl Into<SmolStr>) -> Result<Self, InvalidIdentifier> {
        let n = n.into();
        if PATH_REGEX.is_match(n.as_str()) {
            Ok(IdentList(n))
        } else {
            Err(InvalidIdentifier(n))
        }
    }

    /// Split off the last component of the path, returning the prefix and suffix.
    ///
    /// # Example
    ///
    /// ```
    /// # use hugr_core::hugr::IdentList;
    /// assert_eq!(
    ///     IdentList::new("foo.bar.baz").unwrap().split_last(),
    ///     Some((IdentList::new_unchecked("foo.bar"), "baz".into()))
    /// );
    /// assert_eq!(
    ///    IdentList::new("foo").unwrap().split_last(),
    ///    None
    /// );
    /// ```
    pub fn split_last(&self) -> Option<(IdentList, SmolStr)> {
        let (prefix, suffix) = self.0.rsplit_once('.')?;
        let prefix = Self::new_unchecked(prefix);
        let suffix = suffix.into();
        Some((prefix, suffix))
    }

    /// Create a new [IdentList] *without* doing the well-formedness check.
    /// This is a backdoor to be used sparingly, as we rely upon callers to
    /// validate names themselves. In tests, instead the [crate::const_extension_ids]
    /// macro is strongly encouraged as this ensures the name validity check
    /// is done properly.
    pub const fn new_unchecked(n: &str) -> Self {
        IdentList(SmolStr::new_inline(n))
    }
}

impl Borrow<str> for IdentList {
    fn borrow(&self) -> &str {
        self.0.borrow()
    }
}

impl std::ops::Deref for IdentList {
    type Target = str;

    fn deref(&self) -> &str {
        self.0.deref()
    }
}

impl TryInto<IdentList> for &str {
    type Error = InvalidIdentifier;

    fn try_into(self) -> Result<IdentList, InvalidIdentifier> {
        IdentList::new(SmolStr::new(self))
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Error)]
#[error("Invalid identifier {0}")]
/// Error indicating a string was not valid as an [IdentList]
pub struct InvalidIdentifier(SmolStr);

#[cfg(test)]
mod test {

    mod proptest {
        use crate::hugr::ident::IdentList;
        use ::proptest::prelude::*;
        impl Arbitrary for super::IdentList {
            type Parameters = ();
            type Strategy = BoxedStrategy<Self>;
            fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
                use crate::proptest::any_ident_string;
                use proptest::collection::vec;
                vec(any_ident_string(), 1..2)
                    .prop_map(|vs| {
                        IdentList::new(
                            itertools::intersperse(
                                vs.into_iter().map(Into::<String>::into),
                                ".".into(),
                            )
                            .collect::<String>(),
                        )
                        .unwrap()
                    })
                    .boxed()
            }
        }
        proptest! {
            #[test]
            fn arbitrary_identlist_valid((IdentList(ident_list)): IdentList) {
                assert!(IdentList::new(ident_list).is_ok())
            }
        }
    }

    use super::IdentList;

    #[test]
    fn test_idents() {
        IdentList::new("foo").unwrap();
        IdentList::new("_foo").unwrap();
        IdentList::new("Bar_xyz67").unwrap();
        IdentList::new("foo.bar").unwrap();
        IdentList::new("foo.bar.baz").unwrap();

        IdentList::new("42").unwrap_err();
        IdentList::new("foo.42").unwrap_err();
        IdentList::new("xyz-5").unwrap_err();
        IdentList::new("foo..bar").unwrap_err();
        IdentList::new(".foo").unwrap_err();
    }
}