1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7macro_rules! syntax_enum {
8 ($name:ident { $($variant:ident => $label:literal),+ $(,)? }) => {
9 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
10 pub enum $name {
11 $($variant),+
12 }
13
14 impl $name {
15 pub const ALL: &'static [Self] = &[$(Self::$variant),+];
16
17 pub const fn as_str(self) -> &'static str {
18 match self {
19 $(Self::$variant => $label),+
20 }
21 }
22 }
23
24 impl fmt::Display for $name {
25 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
26 formatter.write_str(self.as_str())
27 }
28 }
29
30 impl FromStr for $name {
31 type Err = PhpSyntaxError;
32
33 fn from_str(input: &str) -> Result<Self, Self::Err> {
34 match normalized_label(input)?.as_str() {
35 $($label => Ok(Self::$variant),)+
36 _ => Err(PhpSyntaxError::UnknownLabel),
37 }
38 }
39 }
40 };
41}
42
43syntax_enum!(PhpKeyword {
44 Abstract => "abstract",
45 And => "and",
46 Array => "array",
47 As => "as",
48 Break => "break",
49 Callable => "callable",
50 Case => "case",
51 Catch => "catch",
52 Class => "class",
53 Clone => "clone",
54 Const => "const",
55 Continue => "continue",
56 Declare => "declare",
57 Default => "default",
58 Do => "do",
59 Echo => "echo",
60 Else => "else",
61 Enum => "enum",
62 Extends => "extends",
63 Final => "final",
64 Finally => "finally",
65 Fn => "fn",
66 For => "for",
67 Foreach => "foreach",
68 Function => "function",
69 Global => "global",
70 If => "if",
71 Implements => "implements",
72 Interface => "interface",
73 Match => "match",
74 Namespace => "namespace",
75 New => "new",
76 Or => "or",
77 Private => "private",
78 Protected => "protected",
79 Public => "public",
80 Readonly => "readonly",
81 Return => "return",
82 Static => "static",
83 Switch => "switch",
84 Throw => "throw",
85 Trait => "trait",
86 Try => "try",
87 Use => "use",
88 While => "while",
89 Yield => "yield",
90});
91
92syntax_enum!(PhpVisibility {
93 Public => "public",
94 Protected => "protected",
95 Private => "private",
96});
97
98syntax_enum!(PhpDeclarationKind {
99 Class => "class",
100 Interface => "interface",
101 Trait => "trait",
102 Enum => "enum",
103 Function => "function",
104 Method => "method",
105 Property => "property",
106 Constant => "constant",
107});
108
109syntax_enum!(PhpModifier {
110 Static => "static",
111 Final => "final",
112 Abstract => "abstract",
113 Readonly => "readonly",
114});
115
116syntax_enum!(PhpControlFlowLabel {
117 If => "if",
118 Else => "else",
119 ElseIf => "elseif",
120 For => "for",
121 Foreach => "foreach",
122 While => "while",
123 DoWhile => "do-while",
124 Switch => "switch",
125 Match => "match",
126 Try => "try",
127 Catch => "catch",
128 Finally => "finally",
129 Break => "break",
130 Continue => "continue",
131 Return => "return",
132 Throw => "throw",
133 Yield => "yield",
134});
135
136#[derive(Clone, Copy, Debug, Eq, PartialEq)]
138pub enum PhpSyntaxError {
139 Empty,
140 UnknownLabel,
141}
142
143impl fmt::Display for PhpSyntaxError {
144 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
145 match self {
146 Self::Empty => formatter.write_str("PHP syntax metadata cannot be empty"),
147 Self::UnknownLabel => formatter.write_str("unknown PHP syntax metadata label"),
148 }
149 }
150}
151
152impl Error for PhpSyntaxError {}
153
154pub fn is_php_keyword(input: &str) -> bool {
155 input.parse::<PhpKeyword>().is_ok()
156}
157
158pub fn is_php_modifier(input: &str) -> bool {
159 input.parse::<PhpModifier>().is_ok()
160}
161
162fn normalized_label(input: &str) -> Result<String, PhpSyntaxError> {
163 let trimmed = input.trim();
164 if trimmed.is_empty() {
165 Err(PhpSyntaxError::Empty)
166 } else {
167 Ok(trimmed.to_ascii_lowercase().replace(['_', ' '], "-"))
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::{
174 PhpControlFlowLabel, PhpDeclarationKind, PhpKeyword, PhpModifier, PhpSyntaxError,
175 is_php_keyword, is_php_modifier,
176 };
177
178 #[test]
179 fn parses_common_syntax_labels() -> Result<(), PhpSyntaxError> {
180 assert!(is_php_keyword("readonly"));
181 assert!(is_php_modifier("final"));
182 assert_eq!("class".parse::<PhpKeyword>()?, PhpKeyword::Class);
183 assert_eq!(
184 "trait".parse::<PhpDeclarationKind>()?,
185 PhpDeclarationKind::Trait
186 );
187 assert_eq!(
188 "do while".parse::<PhpControlFlowLabel>()?,
189 PhpControlFlowLabel::DoWhile
190 );
191 Ok(())
192 }
193
194 #[test]
195 fn display_returns_source_labels() {
196 assert_eq!(PhpModifier::Readonly.to_string(), "readonly");
197 }
198}