use crate::records::ast_node::AstNode;
use crate::records::cst_node::CstNode;
pub const fn ast_rtti_index(name: &str) -> i32 {
let bytes = name.as_bytes();
let mut hash: u32 = 0x811c_9dc5;
let mut i = 0;
while i < bytes.len() {
hash ^= bytes[i] as u32;
hash = hash.wrapping_mul(0x0100_0193);
i += 1;
}
(hash & 0x7fff_ffff) as i32
}
pub trait AstNodeClass {
const CLASS_INDEX: i32;
}
pub trait AstNodeRef {
fn class_index(self) -> Option<i32>;
}
impl AstNodeRef for &AstNode {
#[inline]
fn class_index(self) -> Option<i32> {
Some(self.class_index)
}
}
impl AstNodeRef for *mut AstNode {
#[inline]
fn class_index(self) -> Option<i32> {
if self.is_null() {
None
} else {
unsafe { Some((*self).class_index) }
}
}
}
impl AstNodeRef for *const AstNode {
#[inline]
fn class_index(self) -> Option<i32> {
if self.is_null() {
None
} else {
unsafe { Some((*self).class_index) }
}
}
}
impl AstNodeRef for &crate::records::ast_type::AstType {
#[inline]
fn class_index(self) -> Option<i32> {
Some(self.base.class_index)
}
}
impl AstNodeRef for &crate::records::ast_expr::AstExpr {
#[inline]
fn class_index(self) -> Option<i32> {
Some(self.base.class_index)
}
}
impl AstNodeRef for &crate::records::ast_stat::AstStat {
#[inline]
fn class_index(self) -> Option<i32> {
Some(self.base.class_index)
}
}
impl AstNodeRef for &crate::records::ast_type_pack::AstTypePack {
#[inline]
fn class_index(self) -> Option<i32> {
Some(self.base.class_index)
}
}
#[inline]
pub fn ast_node_is<T: AstNodeClass>(node: impl AstNodeRef) -> bool {
node.class_index() == Some(T::CLASS_INDEX)
}
#[inline]
pub unsafe fn ast_node_as<T: AstNodeClass>(node: *mut AstNode) -> *mut T {
if !node.is_null() && (*node).class_index == T::CLASS_INDEX {
node.cast::<T>()
} else {
core::ptr::null_mut()
}
}
#[inline]
pub unsafe fn ast_node_as_const<T: AstNodeClass>(node: *const AstNode) -> *const T {
if !node.is_null() && (*node).class_index == T::CLASS_INDEX {
node.cast::<T>()
} else {
core::ptr::null()
}
}
#[inline]
pub const fn cst_rtti_index(name: &str) -> i32 {
ast_rtti_index(name)
}
pub trait CstNodeClass {
const CLASS_INDEX: i32;
}
#[inline]
pub fn cst_node_is<T: CstNodeClass>(node: &CstNode) -> bool {
node.class_index == T::CLASS_INDEX
}
#[inline]
pub unsafe fn cst_node_as<T: CstNodeClass>(node: *mut CstNode) -> *mut T {
if !node.is_null() && (*node).class_index == T::CLASS_INDEX {
node.cast::<T>()
} else {
core::ptr::null_mut()
}
}
#[cfg(test)]
mod tests {
use super::ast_rtti_index;
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
const RTTI_NAMES: &[&str] = &[
"AstAttr",
"AstGenericType",
"AstGenericTypePack",
"AstExprGroup",
"AstExprConstantNil",
"AstExprConstantBool",
"AstExprConstantNumber",
"AstExprConstantString",
"AstExprLocal",
"AstExprGlobal",
"AstExprVarargs",
"AstExprCall",
"AstExprIndexName",
"AstExprIndexExpr",
"AstExprFunction",
"AstExprTable",
"AstExprUnary",
"AstExprBinary",
"AstExprTypeAssertion",
"AstExprIfElse",
"AstExprInterpString",
"AstExprError",
"AstStatBlock",
"AstStatIf",
"AstStatWhile",
"AstStatRepeat",
"AstStatBreak",
"AstStatContinue",
"AstStatReturn",
"AstStatExpr",
"AstStatLocal",
"AstStatFor",
"AstStatForIn",
"AstStatAssign",
"AstStatCompoundAssign",
"AstStatFunction",
"AstStatLocalFunction",
"AstStatTypeAlias",
"AstStatTypeFunction",
"AstStatDeclareGlobal",
"AstStatDeclareFunction",
"AstStatDeclareExternType",
"AstStatError",
"AstTypeReference",
"AstTypeTable",
"AstTypeFunction",
"AstTypeTypeof",
"AstTypeOptional",
"AstTypeUnion",
"AstTypeIntersection",
"AstTypeSingletonBool",
"AstTypeSingletonString",
"AstTypeGroup",
"AstTypeError",
"AstTypePackExplicit",
"AstTypePackVariadic",
"AstTypePackGeneric",
];
#[test]
fn rtti_indices_unique() {
let mut seen: BTreeMap<i32, &str> = BTreeMap::new();
let mut collisions: Vec<(&str, &str, i32)> = Vec::new();
for &name in RTTI_NAMES {
let idx = ast_rtti_index(name);
if let Some(&prev) = seen.get(&idx) {
collisions.push((prev, name, idx));
} else {
seen.insert(idx, name);
}
}
assert!(
collisions.is_empty(),
"AST RTTI index collisions: {collisions:?}"
);
}
#[test]
fn rtti_index_is_stable_and_positive() {
assert_eq!(
ast_rtti_index("AstExprGroup"),
ast_rtti_index("AstExprGroup")
);
assert!(ast_rtti_index("AstExprGroup") >= 0);
}
const CST_RTTI_NAMES: &[&str] = &[
"CstExprGroup",
"CstExprConstantNumber",
"CstExprConstantInteger",
"CstExprConstantString",
"CstExprCall",
"CstExprIndexExpr",
"CstExprFunction",
"CstExprTable",
"CstExprOp",
"CstExprTypeAssertion",
"CstExprIfElse",
"CstExprInterpString",
"CstExprExplicitTypeInstantiation",
"CstStatDo",
"CstStatRepeat",
"CstStatReturn",
"CstStatLocal",
"CstStatFor",
"CstStatForIn",
"CstStatAssign",
"CstStatCompoundAssign",
"CstStatFunction",
"CstStatLocalFunction",
"CstGenericType",
"CstGenericTypePack",
"CstStatTypeAlias",
"CstStatTypeFunction",
"CstTypeReference",
"CstTypeTable",
"CstTypeFunction",
"CstTypeTypeof",
"CstTypeUnion",
"CstTypeIntersection",
"CstTypeSingletonString",
"CstTypeGroup",
"CstTypePackExplicit",
"CstTypePackGeneric",
];
#[test]
fn cst_rtti_indices_unique() {
let mut seen: BTreeMap<i32, &str> = BTreeMap::new();
let mut collisions: Vec<(&str, &str, i32)> = Vec::new();
for &name in CST_RTTI_NAMES {
let idx = ast_rtti_index(name);
if let Some(&prev) = seen.get(&idx) {
collisions.push((prev, name, idx));
} else {
seen.insert(idx, name);
}
}
assert!(
collisions.is_empty(),
"CST RTTI index collisions: {collisions:?}"
);
}
}