Skip to content
This repository was archived by the owner on Feb 6, 2025. It is now read-only.

Commit 7cf1ec2

Browse files
committed
Add functions to create Identifier and TypeName in safe ways. #47
Those functions are not enforced (yet), as people can still use the From<&str> / From<String> interface, but we’re getting there.
1 parent 741fed6 commit 7cf1ec2

File tree

1 file changed

+78
-0
lines changed

1 file changed

+78
-0
lines changed

src/syntax.rs

+78
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,51 @@ use std::iter::once;
2020
#[derive(Clone, Debug, PartialEq)]
2121
pub struct NonEmpty<T>(pub Vec<T>);
2222

23+
/// Error that might occur when creating a new [`Identifier`].
24+
#[derive(Debug)]
25+
pub enum IdentifierError {
26+
StartsWithDigit,
27+
ContainsNonASCIIAlphaNum
28+
}
29+
30+
impl fmt::Display for IdentifierError {
31+
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
32+
match *self {
33+
IdentifierError::StartsWithDigit =>
34+
f.write_str("starts starts with a digit"),
35+
36+
IdentifierError::ContainsNonASCIIAlphaNum =>
37+
f.write_str("contains at least one non-alphanumeric ASCII character")
38+
}
39+
}
40+
}
41+
2342
/// A generic identifier.
2443
#[derive(Clone, Debug, PartialEq)]
2544
pub struct Identifier(pub String);
2645

46+
impl Identifier {
47+
/// Create a new [`Identifier`].
48+
///
49+
/// # Errors
50+
///
51+
/// This function will fail if the identifier starts with a digit or contains non-alphanumeric
52+
/// ASCII characters.
53+
pub fn new<N>(name: N) -> Result<Self, IdentifierError> where N: Into<String> {
54+
let name = name.into();
55+
56+
if name.chars().next().map(|c| c.is_ascii_alphabetic()) == Some(false) {
57+
// check the first letter is not a digit
58+
Err(IdentifierError::StartsWithDigit)
59+
} else if name.contains(|c: char| !(c.is_ascii_alphanumeric() || c == '_')) {
60+
// check we only have ASCII alphanumeric characters
61+
Err(IdentifierError::ContainsNonASCIIAlphaNum)
62+
} else {
63+
Ok(Identifier(name))
64+
}
65+
}
66+
}
67+
2768
impl<'a> From<&'a str> for Identifier {
2869
fn from(s: &str) -> Self {
2970
Identifier(s.to_owned())
@@ -46,6 +87,20 @@ impl fmt::Display for Identifier {
4687
#[derive(Clone, Debug, PartialEq)]
4788
pub struct TypeName(pub String);
4889

90+
impl TypeName {
91+
/// Create a new [`TypeName`].
92+
///
93+
/// # Errors
94+
///
95+
/// This function will fail if the type name starts with a digit or contains non-alphanumeric
96+
/// ASCII characters.
97+
pub fn new<N>(name: N) -> Result<Self, IdentifierError> where N: Into<String> {
98+
// build as identifier and unwrap into type name
99+
let Identifier(tn) = Identifier::new(name)?;
100+
Ok(TypeName(tn))
101+
}
102+
}
103+
49104
impl<'a> From<&'a str> for TypeName {
50105
fn from(s: &str) -> Self {
51106
TypeName(s.to_owned())
@@ -920,6 +975,29 @@ pub enum PreprocessorExtensionBehavior {
920975
mod tests {
921976
use super::*;
922977

978+
#[test]
979+
fn create_new_identifier() {
980+
assert!(Identifier::new("foo_bar").is_ok());
981+
assert!(Identifier::new("3foo_bar").is_err());
982+
assert!(Identifier::new("FooBar").is_ok());
983+
assert!(Identifier::new("_FooBar").is_err());
984+
assert!(Identifier::new("foo3").is_ok());
985+
assert!(Identifier::new("foo3_").is_ok());
986+
assert!(Identifier::new("fδo3_").is_err());
987+
}
988+
989+
#[test]
990+
fn create_new_type_name() {
991+
assert!(TypeName::new("foo_bar").is_ok());
992+
assert!(TypeName::new("FooBar").is_ok());
993+
assert!(TypeName::new("foo3").is_ok());
994+
assert!(TypeName::new("foo3_").is_ok());
995+
996+
assert!(TypeName::new("_FooBar").is_err());
997+
assert!(TypeName::new("3foo_bar").is_err());
998+
assert!(TypeName::new("fδo3_").is_err());
999+
}
1000+
9231001
// bool predicate(float x) {
9241002
// }
9251003
#[test]

0 commit comments

Comments
 (0)