use std::{
borrow::Borrow,
fmt,
mem::{self, ManuallyDrop},
ops, ptr,
};
use crate::green::trivia::GreenTrivia;
use crate::{
TextSize,
arc::{Arc, HeaderSlice, ThinArc},
green::RawSyntaxKind,
};
#[derive(PartialEq, Eq, Hash)]
struct GreenTokenHead {
kind: RawSyntaxKind,
leading: GreenTrivia,
trailing: GreenTrivia,
#[cfg(feature = "countme")]
_c: countme::Count<GreenToken>,
}
#[cfg(feature = "countme")]
pub(crate) fn has_live() -> bool {
countme::get::<GreenToken>().live > 0
}
type Repr = HeaderSlice<GreenTokenHead, [u8]>;
type ReprThin = HeaderSlice<GreenTokenHead, [u8; 0]>;
#[repr(transparent)]
pub(crate) struct GreenTokenData {
data: ReprThin,
}
impl PartialEq for GreenTokenData {
fn eq(&self, other: &Self) -> bool {
self.kind() == other.kind() && self.text() == other.text()
}
}
#[derive(PartialEq, Eq, Hash, Clone)]
#[repr(transparent)]
pub(crate) struct GreenToken {
ptr: ThinArc<GreenTokenHead, u8>,
}
impl ToOwned for GreenTokenData {
type Owned = GreenToken;
#[inline]
fn to_owned(&self) -> GreenToken {
unsafe {
let green = GreenToken::from_raw(ptr::NonNull::from(self));
let green = ManuallyDrop::new(green);
GreenToken::clone(&green)
}
}
}
impl Borrow<GreenTokenData> for GreenToken {
#[inline]
fn borrow(&self) -> &GreenTokenData {
self
}
}
impl fmt::Debug for GreenTokenData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GreenToken")
.field("kind", &self.kind())
.field("text", &self.text())
.field("leading", &self.leading_trivia())
.field("trailing", &self.trailing_trivia())
.finish()
}
}
impl fmt::Debug for GreenToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let data: &GreenTokenData = self;
fmt::Debug::fmt(data, f)
}
}
impl fmt::Display for GreenToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let data: &GreenTokenData = self;
fmt::Display::fmt(data, f)
}
}
impl fmt::Display for GreenTokenData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.text())
}
}
impl GreenTokenData {
#[inline]
pub fn kind(&self) -> RawSyntaxKind {
self.data.header.kind
}
#[inline]
pub fn text(&self) -> &str {
unsafe { std::str::from_utf8_unchecked(self.data.slice()) }
}
pub(crate) fn leading_trailing_total_len(&self) -> (TextSize, TextSize, TextSize) {
let leading_len = self.data.header.leading.text_len();
let trailing_len = self.data.header.trailing.text_len();
let total_len = self.data.slice().len() as u32;
(leading_len, trailing_len, total_len.into())
}
#[inline]
pub fn text_trimmed(&self) -> &str {
let (leading_len, trailing_len, total_len) = self.leading_trailing_total_len();
let start: usize = leading_len.into();
let end: usize = (total_len - trailing_len).into();
let text = unsafe { std::str::from_utf8_unchecked(self.data.slice()) };
&text[start..end]
}
#[inline]
pub fn text_len(&self) -> TextSize {
TextSize::of(self.text())
}
#[inline]
pub fn leading_trivia(&self) -> &GreenTrivia {
&self.data.header.leading
}
#[inline]
pub fn trailing_trivia(&self) -> &GreenTrivia {
&self.data.header.trailing
}
}
impl GreenToken {
#[inline]
pub fn new_raw(kind: RawSyntaxKind, text: &str) -> Self {
let leading = GreenTrivia::empty();
let trailing = leading.clone();
Self::with_trivia(kind, text, leading, trailing)
}
#[inline]
pub fn with_trivia(
kind: RawSyntaxKind,
text: &str,
leading: GreenTrivia,
trailing: GreenTrivia,
) -> Self {
let head = GreenTokenHead {
kind,
leading,
trailing,
#[cfg(feature = "countme")]
_c: countme::Count::new(),
};
let ptr = ThinArc::from_header_and_iter(head, text.bytes());
Self { ptr }
}
#[inline]
pub(crate) fn into_raw(self) -> ptr::NonNull<GreenTokenData> {
Arc::from_thin(self.ptr).into_raw().cast()
}
#[inline]
pub(crate) unsafe fn from_raw(ptr: ptr::NonNull<GreenTokenData>) -> Self {
let arc = unsafe {
let arc = Arc::from_raw(&ptr.as_ref().data as *const ReprThin);
mem::transmute::<Arc<ReprThin>, ThinArc<GreenTokenHead, u8>>(arc)
};
Self { ptr: arc }
}
}
impl ops::Deref for GreenToken {
type Target = GreenTokenData;
#[inline]
fn deref(&self) -> &GreenTokenData {
unsafe {
let repr: &Repr = &self.ptr;
let repr: &ReprThin = &*(repr as *const Repr as *const ReprThin);
mem::transmute::<&ReprThin, &GreenTokenData>(repr)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use quickcheck_macros::*;
#[test]
fn green_token_text_and_len() {
let t = GreenToken::with_trivia(
RawSyntaxKind(0),
"\n\t let \t\t",
GreenTrivia::whitespace(3),
GreenTrivia::whitespace(3),
);
assert_eq!("\n\t let \t\t", t.text());
assert_eq!(TextSize::from(9), t.text_len());
assert_eq!("let", t.text_trimmed());
assert_eq!("\n\t let \t\t", format!("{t}"));
}
#[test]
fn empty_text_len() {
assert_eq!(TextSize::from(0), GreenTrivia::empty().text_len());
}
#[quickcheck]
fn whitespace_and_comments_text_len(len: u32) {
let len = TextSize::from(len);
assert_eq!(len, GreenTrivia::whitespace(len).text_len());
assert_eq!(len, GreenTrivia::single_line_comment(len).text_len());
}
#[test]
fn sizes() {
assert_eq!(24, std::mem::size_of::<GreenTokenHead>());
assert_eq!(8, std::mem::size_of::<GreenToken>());
}
}