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
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))
        }
    }

    /// 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();
    }
}