1use crate::records::ast_node::AstNode;
21use crate::records::cst_node::CstNode;
22
23pub const fn ast_rtti_index(name: &str) -> i32 {
27 let bytes = name.as_bytes();
28 let mut hash: u32 = 0x811c_9dc5;
29 let mut i = 0;
30 while i < bytes.len() {
31 hash ^= bytes[i] as u32;
32 hash = hash.wrapping_mul(0x0100_0193);
33 i += 1;
34 }
35 (hash & 0x7fff_ffff) as i32
36}
37
38pub trait AstNodeClass {
44 const CLASS_INDEX: i32;
46}
47
48pub trait AstNodeRef {
50 fn class_index(self) -> Option<i32>;
51}
52
53impl AstNodeRef for &AstNode {
54 #[inline]
55 fn class_index(self) -> Option<i32> {
56 Some(self.class_index)
57 }
58}
59
60impl AstNodeRef for *mut AstNode {
61 #[inline]
62 fn class_index(self) -> Option<i32> {
63 if self.is_null() {
64 None
65 } else {
66 unsafe { Some((*self).class_index) }
67 }
68 }
69}
70
71impl AstNodeRef for *const AstNode {
72 #[inline]
73 fn class_index(self) -> Option<i32> {
74 if self.is_null() {
75 None
76 } else {
77 unsafe { Some((*self).class_index) }
78 }
79 }
80}
81
82impl AstNodeRef for &crate::records::ast_type::AstType {
83 #[inline]
84 fn class_index(self) -> Option<i32> {
85 Some(self.base.class_index)
86 }
87}
88
89impl AstNodeRef for &crate::records::ast_expr::AstExpr {
90 #[inline]
91 fn class_index(self) -> Option<i32> {
92 Some(self.base.class_index)
93 }
94}
95
96impl AstNodeRef for &crate::records::ast_stat::AstStat {
97 #[inline]
98 fn class_index(self) -> Option<i32> {
99 Some(self.base.class_index)
100 }
101}
102
103impl AstNodeRef for &crate::records::ast_type_pack::AstTypePack {
104 #[inline]
105 fn class_index(self) -> Option<i32> {
106 Some(self.base.class_index)
107 }
108}
109
110#[inline]
111pub fn ast_node_is<T: AstNodeClass>(node: impl AstNodeRef) -> bool {
112 node.class_index() == Some(T::CLASS_INDEX)
113}
114
115#[inline]
123pub unsafe fn ast_node_as<T: AstNodeClass>(node: *mut AstNode) -> *mut T {
124 if !node.is_null() && (*node).class_index == T::CLASS_INDEX {
125 node.cast::<T>()
126 } else {
127 core::ptr::null_mut()
128 }
129}
130
131#[inline]
136pub unsafe fn ast_node_as_const<T: AstNodeClass>(node: *const AstNode) -> *const T {
137 if !node.is_null() && (*node).class_index == T::CLASS_INDEX {
138 node.cast::<T>()
139 } else {
140 core::ptr::null()
141 }
142}
143
144#[inline]
148pub const fn cst_rtti_index(name: &str) -> i32 {
149 ast_rtti_index(name)
150}
151
152pub trait CstNodeClass {
158 const CLASS_INDEX: i32;
160}
161
162#[inline]
164pub fn cst_node_is<T: CstNodeClass>(node: &CstNode) -> bool {
165 node.class_index == T::CLASS_INDEX
166}
167
168#[inline]
175pub unsafe fn cst_node_as<T: CstNodeClass>(node: *mut CstNode) -> *mut T {
176 if !node.is_null() && (*node).class_index == T::CLASS_INDEX {
177 node.cast::<T>()
178 } else {
179 core::ptr::null_mut()
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::ast_rtti_index;
186 use alloc::collections::BTreeMap;
187 use alloc::vec::Vec;
188
189 const RTTI_NAMES: &[&str] = &[
194 "AstAttr",
195 "AstGenericType",
196 "AstGenericTypePack",
197 "AstExprGroup",
198 "AstExprConstantNil",
199 "AstExprConstantBool",
200 "AstExprConstantNumber",
201 "AstExprConstantString",
202 "AstExprLocal",
203 "AstExprGlobal",
204 "AstExprVarargs",
205 "AstExprCall",
206 "AstExprIndexName",
207 "AstExprIndexExpr",
208 "AstExprFunction",
209 "AstExprTable",
210 "AstExprUnary",
211 "AstExprBinary",
212 "AstExprTypeAssertion",
213 "AstExprIfElse",
214 "AstExprInterpString",
215 "AstExprError",
216 "AstStatBlock",
217 "AstStatIf",
218 "AstStatWhile",
219 "AstStatRepeat",
220 "AstStatBreak",
221 "AstStatContinue",
222 "AstStatReturn",
223 "AstStatExpr",
224 "AstStatLocal",
225 "AstStatFor",
226 "AstStatForIn",
227 "AstStatAssign",
228 "AstStatCompoundAssign",
229 "AstStatFunction",
230 "AstStatLocalFunction",
231 "AstStatTypeAlias",
232 "AstStatTypeFunction",
233 "AstStatDeclareGlobal",
234 "AstStatDeclareFunction",
235 "AstStatDeclareExternType",
236 "AstStatError",
237 "AstTypeReference",
238 "AstTypeTable",
239 "AstTypeFunction",
240 "AstTypeTypeof",
241 "AstTypeOptional",
242 "AstTypeUnion",
243 "AstTypeIntersection",
244 "AstTypeSingletonBool",
245 "AstTypeSingletonString",
246 "AstTypeGroup",
247 "AstTypeError",
248 "AstTypePackExplicit",
249 "AstTypePackVariadic",
250 "AstTypePackGeneric",
251 ];
252
253 #[test]
254 fn rtti_indices_unique() {
255 let mut seen: BTreeMap<i32, &str> = BTreeMap::new();
256 let mut collisions: Vec<(&str, &str, i32)> = Vec::new();
257 for &name in RTTI_NAMES {
258 let idx = ast_rtti_index(name);
259 if let Some(&prev) = seen.get(&idx) {
260 collisions.push((prev, name, idx));
261 } else {
262 seen.insert(idx, name);
263 }
264 }
265 assert!(
266 collisions.is_empty(),
267 "AST RTTI index collisions: {collisions:?}"
268 );
269 }
270
271 #[test]
272 fn rtti_index_is_stable_and_positive() {
273 assert_eq!(
275 ast_rtti_index("AstExprGroup"),
276 ast_rtti_index("AstExprGroup")
277 );
278 assert!(ast_rtti_index("AstExprGroup") >= 0);
280 }
281
282 const CST_RTTI_NAMES: &[&str] = &[
286 "CstExprGroup",
287 "CstExprConstantNumber",
288 "CstExprConstantInteger",
289 "CstExprConstantString",
290 "CstExprCall",
291 "CstExprIndexExpr",
292 "CstExprFunction",
293 "CstExprTable",
294 "CstExprOp",
295 "CstExprTypeAssertion",
296 "CstExprIfElse",
297 "CstExprInterpString",
298 "CstExprExplicitTypeInstantiation",
299 "CstStatDo",
300 "CstStatRepeat",
301 "CstStatReturn",
302 "CstStatLocal",
303 "CstStatFor",
304 "CstStatForIn",
305 "CstStatAssign",
306 "CstStatCompoundAssign",
307 "CstStatFunction",
308 "CstStatLocalFunction",
309 "CstGenericType",
310 "CstGenericTypePack",
311 "CstStatTypeAlias",
312 "CstStatTypeFunction",
313 "CstTypeReference",
314 "CstTypeTable",
315 "CstTypeFunction",
316 "CstTypeTypeof",
317 "CstTypeUnion",
318 "CstTypeIntersection",
319 "CstTypeSingletonString",
320 "CstTypeGroup",
321 "CstTypePackExplicit",
322 "CstTypePackGeneric",
323 ];
324
325 #[test]
326 fn cst_rtti_indices_unique() {
327 let mut seen: BTreeMap<i32, &str> = BTreeMap::new();
328 let mut collisions: Vec<(&str, &str, i32)> = Vec::new();
329 for &name in CST_RTTI_NAMES {
330 let idx = ast_rtti_index(name);
331 if let Some(&prev) = seen.get(&idx) {
332 collisions.push((prev, name, idx));
333 } else {
334 seen.insert(idx, name);
335 }
336 }
337 assert!(
338 collisions.is_empty(),
339 "CST RTTI index collisions: {collisions:?}"
340 );
341 }
342}