ecma_syntax_cat/
identifier.rs1use crate::error::Error;
12
13#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub struct Identifier(String);
19
20impl Identifier {
21 pub fn new(name: impl Into<String>) -> Result<Self, Error> {
28 let name = name.into();
29 valid_identifier(&name)
30 .then_some(Self(name.clone()))
31 .ok_or(Error::InvalidIdentifier {
32 attempted: name,
33 reason: "must match [$_A-Za-z][$_A-Za-z0-9]*",
34 })
35 }
36
37 #[must_use]
39 pub fn as_str(&self) -> &str {
40 &self.0
41 }
42}
43
44impl std::fmt::Display for Identifier {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 f.write_str(&self.0)
47 }
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
52pub struct PrivateIdentifier(String);
53
54impl PrivateIdentifier {
55 pub fn new(name: impl Into<String>) -> Result<Self, Error> {
62 let name = name.into();
63 valid_identifier(&name)
64 .then_some(Self(name.clone()))
65 .ok_or(Error::InvalidIdentifier {
66 attempted: name,
67 reason: "private identifier must match [$_A-Za-z][$_A-Za-z0-9]* after the leading `#`",
68 })
69 }
70
71 #[must_use]
73 pub fn as_str(&self) -> &str {
74 &self.0
75 }
76}
77
78impl std::fmt::Display for PrivateIdentifier {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 write!(f, "#{}", self.0)
81 }
82}
83
84fn valid_identifier(name: &str) -> bool {
85 let bytes = name.as_bytes();
86 if bytes.first().is_some_and(|&b| is_ident_start(b)) {
87 bytes.iter().skip(1).all(|&b| is_ident_continue(b))
88 } else {
89 false
90 }
91}
92
93fn is_ident_start(b: u8) -> bool {
94 b.is_ascii_alphabetic() || b == b'_' || b == b'$'
95}
96
97fn is_ident_continue(b: u8) -> bool {
98 b.is_ascii_alphanumeric() || b == b'_' || b == b'$'
99}