use core::cmp::Ordering;
use core::fmt;
use core::hash::{Hash, Hasher};
use syntax_lang::TokenKind;
#[derive(Clone, Copy)]
pub struct Kind(u32);
const TRIVIA: u32 = 0x8000;
const INDEX: u32 = 0x7FFF;
const IDENTITY: u32 = 0xFFFF;
pub(crate) const MAX_KINDS: usize = TRIVIA as usize;
pub(crate) const MAX_LABELS: usize = 0xFFFF;
impl Kind {
#[inline]
pub(crate) const fn new(index: u16, trivia: bool) -> Self {
let index = index as u32 & INDEX;
Self(if trivia { index | TRIVIA } else { index })
}
#[inline]
pub(crate) const fn bits(self) -> u32 {
self.0
}
#[inline]
pub(crate) const fn from_bits(bits: u32) -> Self {
Self(bits)
}
#[inline]
#[must_use]
pub const fn index(self) -> u16 {
(self.0 & INDEX) as u16
}
#[inline]
pub(crate) const fn slot(self) -> usize {
(self.0 & INDEX) as usize
}
#[inline]
#[must_use]
pub const fn label(self) -> Option<u16> {
match self.0 >> 16 {
0 => None,
n => Some((n - 1) as u16),
}
}
#[inline]
#[must_use]
pub const fn unlabelled(self) -> Self {
Self(self.0 & IDENTITY)
}
#[inline]
pub(crate) const fn with_label(self, label: Option<u16>) -> Self {
match label {
None => Self(self.0 & IDENTITY),
Some(l) => Self((self.0 & IDENTITY) | ((l as u32 + 1) << 16)),
}
}
}
impl PartialEq for Kind {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.0 & IDENTITY == other.0 & IDENTITY
}
}
impl Eq for Kind {}
impl PartialOrd for Kind {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Kind {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
(self.0 & IDENTITY).cmp(&(other.0 & IDENTITY))
}
}
impl Hash for Kind {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
(self.0 & IDENTITY).hash(state);
}
}
impl TokenKind for Kind {
#[inline]
fn is_trivia(&self) -> bool {
self.0 & TRIVIA != 0
}
}
impl fmt::Debug for Kind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.label() {
None => write!(f, "Kind({})", self.index()),
Some(label) => write!(f, "Kind({}, label {label})", self.index()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kind_trivia_flag_is_independent_of_index() {
let plain = Kind::new(7, false);
let trivia = Kind::new(7, true);
assert_eq!(plain.index(), 7);
assert_eq!(trivia.index(), 7);
assert!(!plain.is_trivia());
assert!(trivia.is_trivia());
assert_ne!(plain, trivia);
}
#[test]
fn test_kind_debug_shows_index_only() {
assert_eq!(alloc::format!("{:?}", Kind::new(3, true)), "Kind(3)");
assert_eq!(
alloc::format!("{:?}", Kind::new(3, false).with_label(Some(2))),
"Kind(3, label 2)"
);
}
#[test]
fn test_kind_max_index_fits_below_flag() {
let last = Kind::new((MAX_KINDS - 1) as u16, false);
assert_eq!(last.slot(), MAX_KINDS - 1);
assert!(!last.is_trivia());
}
#[test]
fn test_kind_labels_do_not_affect_identity() {
use core::hash::BuildHasher;
let k = Kind::new(9, true);
let labelled = k.with_label(Some(0));
let other = k.with_label(Some(MAX_LABELS as u16 - 1));
assert_eq!(labelled.label(), Some(0));
assert_eq!(other.label(), Some(MAX_LABELS as u16 - 1));
assert_eq!(k.label(), None);
assert_eq!(k, labelled);
assert_eq!(labelled, other);
assert_eq!(labelled.cmp(&k), Ordering::Equal);
assert!(labelled.is_trivia());
assert_eq!(labelled.index(), 9);
assert_eq!(labelled.unlabelled().label(), None);
assert_eq!(labelled.with_label(None).label(), None);
let state = std::hash::RandomState::new();
assert_eq!(state.hash_one(k), state.hash_one(other));
assert_eq!(Kind::from_bits(other.bits()).label(), other.label());
}
}