algosul_core/codegen/
ident.rs

1use std::{
2    borrow::Borrow,
3    ffi::{CStr, CString, OsStr, OsString},
4    str::FromStr,
5};
6
7use unicode_xid::UnicodeXID;
8pub trait StrExt {
9    type Owned: Borrow<Self>;
10    fn is_valid_ident(&self) -> bool;
11    fn to_valid_ident(&self) -> Self::Owned;
12}
13impl StrExt for str {
14    type Owned = String;
15
16    fn is_valid_ident(&self) -> bool {
17        let mut chars = self.chars();
18        match chars.next() {
19            Some(c) if c.is_xid_start() || c == '_' => {
20                chars.all(|c| c.is_xid_continue())
21            }
22            _ => false,
23        }
24    }
25
26    fn to_valid_ident(&self) -> Self::Owned {
27        let mut chars = self.chars();
28        let mut buffer = String::new();
29        let first = chars.next();
30        let first = first
31            .filter(|&c| {
32                if c.is_ascii_digit() {
33                    buffer.push('_');
34                    return true;
35                };
36                c.is_xid_start()
37            })
38            .unwrap_or('_');
39        buffer.push(first);
40        chars.for_each(|c| {
41            if c.is_xid_continue() { buffer.push(c) } else { buffer.push('_') }
42        });
43        buffer
44    }
45}
46impl StrExt for OsStr {
47    type Owned = OsString;
48
49    fn is_valid_ident(&self) -> bool {
50        self.to_str().map(str::is_valid_ident).unwrap_or(false)
51    }
52
53    fn to_valid_ident(&self) -> Self::Owned {
54        OsString::from(self.to_string_lossy().to_valid_ident())
55    }
56}
57impl StrExt for CStr {
58    type Owned = CString;
59
60    fn is_valid_ident(&self) -> bool {
61        self.to_str().map(str::is_valid_ident).unwrap_or(false)
62    }
63
64    fn to_valid_ident(&self) -> Self::Owned {
65        CString::from_str(&self.to_string_lossy().to_valid_ident()).unwrap()
66    }
67}
68#[cfg(test)]
69mod tests {
70    use crate::codegen::ident::StrExt;
71    #[test]
72    fn test_is_valid_ident() {
73        let valids = ["_", "_abc", "_123", "abc_", "hello_world"];
74        for s in valids {
75            assert!(s.is_valid_ident(), "{s:?} should be valid");
76        }
77        assert!("_".is_valid_ident());
78        assert!("_abc".is_valid_ident());
79        assert!("_123".is_valid_ident());
80        assert!("abc".is_valid_ident());
81        let invalids = ["", "0", " ", "_0123-", "-", "!hello", "hello-world"];
82        for s in invalids {
83            assert!(!s.is_valid_ident(), "{s:?} should not be valid");
84        }
85    }
86}