use crate::{
AssociatedWhitespaceRules, CommentStyle, Cursor, Kind, KindSet, PairWise, QuoteStyle, SourceOffset, Whitespace,
constants::SINGLE_CHAR_KINDS,
};
use source_tools::SourceToken as SourceTokenTrait;
use std::char::REPLACEMENT_CHARACTER;
#[repr(C)]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Token(u32, u32);
impl Default for Token {
fn default() -> Self {
Self((Kind::Whitespace as u32) << 24, 0)
}
}
const KIND_MASK: u32 = !((1 << 24) - 1);
const LENGTH_MASK: u32 = (1 << 24) - 1;
const HALF_LENGTH_MASK: u32 = !((1 << 12) - 1);
#[allow(dead_code)] pub(crate) const ATOM_DYNAMIC_BIT: u32 = 23;
impl Token {
pub const EMPTY: Token = Token::new_whitespace(Whitespace::none(), 0);
pub const EOF: Token = Token(0b0, 0);
pub const CDO: Token = Token(((Kind::CdcOrCdo as u32) << 24) & KIND_MASK, 4);
pub const CDC: Token = Token((((Kind::CdcOrCdo as u32) | 0b001_00000) << 24) & KIND_MASK, 3);
pub const SPACE: Token = Token::new_whitespace(Whitespace::Space, 1);
pub const TAB: Token = Token::new_whitespace(Whitespace::Tab, 1);
pub const NEWLINE: Token = Token::new_whitespace(Whitespace::Newline, 1);
pub const NUMBER_ZERO: Token = Token((((Kind::Number as u32) | 0b100_00000) << 24) & KIND_MASK, 1);
pub const COLON: Token = Token::new_delim(':');
pub const SEMICOLON: Token = Token::new_delim(';');
pub const COMMA: Token = Token::new_delim(',');
pub const LEFT_SQUARE: Token = Token::new_delim('[');
pub const RIGHT_SQUARE: Token = Token::new_delim(']');
pub const LEFT_PAREN: Token = Token::new_delim('(');
pub const RIGHT_PAREN: Token = Token::new_delim(')');
pub const LEFT_CURLY: Token = Token::new_delim('{');
pub const RIGHT_CURLY: Token = Token::new_delim('}');
pub const BANG: Token = Token::new_delim('!');
pub const HASH: Token = Token::new_delim('#');
pub const DOLLAR: Token = Token::new_delim('$');
pub const PERCENT: Token = Token::new_delim('%');
pub const AMPERSAND: Token = Token::new_delim('&');
pub const ASTERISK: Token = Token::new_delim('*');
pub const PLUS: Token = Token::new_delim('+');
pub const DASH: Token = Token::new_delim('-');
pub const PERIOD: Token = Token::new_delim('.');
pub const SLASH: Token = Token::new_delim('/');
pub const LESS_THAN: Token = Token::new_delim('<');
pub const EQUALS: Token = Token::new_delim('=');
pub const GREATER_THAN: Token = Token::new_delim('>');
pub const QUESTION: Token = Token::new_delim('?');
pub const AT: Token = Token::new_delim('@');
pub const BACKSLASH: Token = Token::new_delim('\\');
pub const CARET: Token = Token::new_delim('^');
pub const UNDERSCORE: Token = Token::new_delim('_');
pub const BACKTICK: Token = Token::new_delim('\'');
pub const PIPE: Token = Token::new_delim('|');
pub const TILDE: Token = Token::new_delim('~');
pub const REPLACEMENT_CHARACTER: Token = Token::new_delim(REPLACEMENT_CHARACTER);
#[inline]
pub const fn dummy(kind: Kind) -> Self {
Self((kind as u32) << 24, 0).with_bad_flag()
}
#[inline]
pub const fn dummy_ident() -> Self {
Self((Kind::Ident as u32) << 24, 0).with_bad_flag()
}
#[inline]
pub(crate) const fn new_whitespace(style: Whitespace, len: u32) -> Self {
let flags: u32 = Kind::Whitespace as u32 | ((style.to_bits() as u32) << 5);
Self((flags << 24) & KIND_MASK, len)
}
#[inline]
pub(crate) const fn new_comment(style: CommentStyle, len: u32) -> Self {
let flags: u32 = Kind::Comment as u32 | ((style as u32) << 5);
Self((flags << 24) & KIND_MASK, len)
}
#[inline]
pub(crate) fn new_number(is_float: bool, has_sign: bool, len: u32, value: f32) -> Self {
let flags: u32 = Kind::Number as u32 | ((is_float as u32) << 5) | ((has_sign as u32) << 6);
Self((flags << 24) & KIND_MASK | (len & LENGTH_MASK), value.to_bits())
}
#[inline]
pub(crate) fn new_dimension(
is_float: bool,
has_sign: bool,
num_len: u32,
unit_len: u32,
value: f32,
atom: u8,
) -> Self {
debug_assert!(num_len <= 4097);
let num_len = (num_len << 12) & HALF_LENGTH_MASK;
let is_known_unit = if unit_len < 32 { ((atom != 0) as u32) << 7 } else { 0 };
let unit_len = if is_known_unit == 0 { unit_len } else { unit_len << 7 | (atom as u32 & 0b1111111) };
let flags: u32 = Kind::Dimension as u32 | is_known_unit | ((is_float as u32) << 5) | ((has_sign as u32) << 6);
Self(((flags << 24) & KIND_MASK) | ((num_len | unit_len) & LENGTH_MASK), value.to_bits())
}
#[inline]
pub(crate) fn new_bad_string(len: u32) -> Self {
Self(((Kind::BadString as u32) << 24) & KIND_MASK, len)
}
#[inline]
pub(crate) fn new_bad_url(len: u32) -> Self {
Self(((Kind::BadUrl as u32) << 24) & KIND_MASK, len)
}
#[inline]
pub(crate) fn new_ident(
contains_non_lower_ascii: bool,
dashed: bool,
contains_escape: bool,
atom: u32,
len: u32,
) -> Self {
let flags: u32 = Kind::Ident as u32
| ((contains_non_lower_ascii as u32) << 5)
| ((dashed as u32) << 6)
| ((contains_escape as u32) << 7);
debug_assert!(atom & LENGTH_MASK == atom);
Self((flags << 24) & KIND_MASK | atom, len)
}
#[inline]
pub(crate) fn new_function(
contains_non_lower_ascii: bool,
dashed: bool,
contains_escape: bool,
atom: u32,
len: u32,
) -> Self {
let flags: u32 = Kind::Function as u32
| ((contains_non_lower_ascii as u32) << 5)
| ((dashed as u32) << 6)
| ((contains_escape as u32) << 7);
debug_assert!(atom & LENGTH_MASK == atom);
Self((flags << 24) & KIND_MASK | atom, len)
}
#[inline]
pub(crate) fn new_atkeyword(
contains_non_lower_ascii: bool,
dashed: bool,
contains_escape: bool,
atom: u32,
len: u32,
) -> Self {
let flags: u32 = Kind::AtKeyword as u32
| ((contains_non_lower_ascii as u32) << 5)
| ((dashed as u32) << 6)
| ((contains_escape as u32) << 7);
debug_assert!(atom & LENGTH_MASK == atom);
Self((flags << 24) & KIND_MASK | atom, len)
}
#[inline]
pub(crate) fn new_hash(
contains_non_lower_ascii: bool,
first_is_ascii: bool,
contains_escape: bool,
len: u32,
hex_value: u32,
) -> Self {
let flags: u32 = Kind::Hash as u32
| ((contains_non_lower_ascii as u32) << 5)
| ((first_is_ascii as u32) << 6)
| ((contains_escape as u32) << 7);
debug_assert!(len < (1 << 24));
Self((flags << 24) & KIND_MASK | (len & LENGTH_MASK), hex_value)
}
#[inline]
pub(crate) fn new_string(quotes: QuoteStyle, has_close_quote: bool, contains_escape: bool, len: u32) -> Self {
debug_assert!(quotes != QuoteStyle::None);
let quotes = if quotes == QuoteStyle::Double { 0b001_00000 } else { 0b0 };
let flags: u32 =
Kind::String as u32 | quotes | ((has_close_quote as u32) << 6) | ((contains_escape as u32) << 7);
Self((flags << 24) & KIND_MASK, len)
}
#[inline]
pub(crate) fn new_url(
ends_with_paren: bool,
contains_whitespace_after_open_paren: bool,
contains_escape: bool,
leading_length: u32,
trailing_length: u32,
len: u32,
) -> Self {
let leading_length = (leading_length << 12) & HALF_LENGTH_MASK;
let flags: u32 = Kind::Url as u32
| ((ends_with_paren as u32) << 5)
| ((contains_whitespace_after_open_paren as u32) << 6)
| ((contains_escape as u32) << 7);
Self((flags << 24) & KIND_MASK | ((leading_length | trailing_length) & LENGTH_MASK), len)
}
#[inline]
pub(crate) fn new_unicode_range(start: u32, end: u32, len: u32) -> Self {
debug_assert!(start <= 0xFFFFFF);
debug_assert!(end <= 0xFFFFFF);
debug_assert!(len <= 255);
let flags: u32 = Kind::UnicodeRange as u32;
Self((flags << 24) & KIND_MASK | (start & LENGTH_MASK), (len << 24) | (end & LENGTH_MASK))
}
#[inline]
pub const fn unicode_range_start(&self) -> u32 {
debug_assert!(self.kind_bits() == Kind::UnicodeRange as u8);
self.0 & LENGTH_MASK
}
#[inline]
pub const fn unicode_range_end(&self) -> u32 {
debug_assert!(self.kind_bits() == Kind::UnicodeRange as u8);
self.1 & LENGTH_MASK
}
#[inline]
pub(crate) const fn new_delim(char: char) -> Self {
let flags: u32 = Kind::Delim as u32;
Self((flags << 24) & KIND_MASK, char as u32)
}
#[inline]
pub(crate) const fn new_delim_with_associated_whitespace(char: char, rules: AssociatedWhitespaceRules) -> Self {
let flags: u32 = Kind::Delim as u32 | ((rules.to_bits() as u32) << 5);
Self((flags << 24) & KIND_MASK, char as u32)
}
#[inline]
pub fn new_interned(kind: Kind, bits: u32, len: u32) -> Token {
debug_assert!(kind == KindSet::IDENT_LIKE);
debug_assert!(bits & LENGTH_MASK == bits);
debug_assert!(len > 0);
Self(((kind as u32) << 24) & KIND_MASK | (bits & LENGTH_MASK), len + ((kind != Kind::Ident) as u32))
}
#[inline(always)]
pub(crate) const fn kind_bits(&self) -> u8 {
(self.0 >> 24 & 0b1_1111) as u8
}
#[inline]
pub const fn kind(&self) -> Kind {
let kind_bits = if self.kind_bits() & 0b1111 == Kind::Delim as u8 {
let c = self.char().unwrap() as usize;
if c < 127 { SINGLE_CHAR_KINDS[c] as u8 } else { Kind::Delim as u8 }
} else {
self.kind_bits()
};
Kind::from_bits(if self.is_bad() { kind_bits | 0b1_0000 } else { kind_bits })
}
#[inline(always)]
const fn first_flag(&self) -> bool {
self.0 >> 31 == 1
}
#[inline(always)]
const fn second_flag(&self) -> bool {
self.0 >> 30 & 0b1 == 1
}
#[inline(always)]
const fn third_flag(&self) -> bool {
self.0 >> 29 & 0b1 == 1
}
#[inline(always)]
pub(crate) const fn is_ident_like(&self) -> bool {
self.kind_bits() & 0b1100 == 0b1000
}
#[inline(always)]
pub(crate) const fn is_delim_like(&self) -> bool {
self.kind_bits() & 0b1111 == Kind::Delim as u8
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.kind_bits() == Kind::Eof as u8
}
#[inline]
pub const fn len(&self) -> u32 {
if self.kind_bits() == Kind::Eof as u8 {
0
} else if self.is_delim_like() {
debug_assert!(matches!(
self.kind(),
Kind::Delim
| Kind::Colon | Kind::Semicolon
| Kind::Comma | Kind::LeftSquare
| Kind::RightSquare
| Kind::LeftParen
| Kind::RightParen
| Kind::LeftCurly
| Kind::RightCurly
));
self.char().unwrap().len_utf8() as u32
} else if self.kind_bits() & 0b1111 == Kind::Number as u8 {
self.numeric_len()
} else if self.kind_bits() & 0b1111 == Kind::Dimension as u8 {
if self.first_flag() {
self.numeric_len() + (self.0 >> 7 & 0b11111)
} else {
((self.0 & LENGTH_MASK) >> 12) + (self.0 & !HALF_LENGTH_MASK)
}
} else if self.kind_bits() & 0b1111 == Kind::Hash as u8 {
self.0 & LENGTH_MASK
} else if self.kind_bits() == Kind::UnicodeRange as u8 {
self.1 >> 24
} else {
self.1
}
}
pub const fn char(&self) -> Option<char> {
if self.is_delim_like() {
return char::from_u32(self.1);
}
None
}
#[inline]
pub const fn is_int(&self) -> bool {
self.kind_bits() & 0b1110 == 0b0100 && !self.third_flag()
}
#[inline]
pub const fn is_float(&self) -> bool {
self.kind_bits() & 0b1100 == 0b0100 && self.third_flag()
}
#[inline]
pub const fn has_sign(&self) -> bool {
self.kind_bits() & 0b1100 == 0b0100 && self.second_flag()
}
#[inline]
pub const fn sign_is_required(&self) -> bool {
debug_assert!(self.kind_bits() == Kind::Number as u8);
self.first_flag()
}
#[inline]
pub const fn with_sign_required(self) -> Token {
debug_assert!(self.kind_bits() == Kind::Number as u8);
Token(self.0 | (1 << 31), self.1)
}
#[inline]
pub const fn numeric_len(&self) -> u32 {
debug_assert!(matches!(self.kind(), Kind::Number | Kind::Dimension | Kind::BadNumber | Kind::BadDimension));
if self.kind_bits() & 0b1111 == Kind::Dimension as u8 {
(self.0 & LENGTH_MASK) >> 12
} else {
self.0 & LENGTH_MASK
}
}
#[inline]
pub fn value(&self) -> f32 {
debug_assert!(matches!(self.kind(), Kind::Number | Kind::Dimension));
f32::from_bits(self.1)
}
#[inline]
pub fn whitespace_style(&self) -> Whitespace {
if self.kind_bits() == Kind::Whitespace as u8 {
Whitespace::from_bits((self.0 >> 29) as u8)
} else {
Whitespace::none()
}
}
#[inline]
pub const fn whitespace_is_significant(&self) -> bool {
self.kind_bits() == Kind::Whitespace as u8 && self.0 & 1 == 1
}
#[inline]
pub const fn with_significant_whitespace(&self, significant: bool) -> Token {
if self.kind_bits() != Kind::Whitespace as u8 {
return *self;
}
if significant { Token(self.0 | 1, self.1) } else { Token(self.0 & !1, self.1) }
}
#[inline]
pub fn associated_whitespace(&self) -> AssociatedWhitespaceRules {
if self.is_delim_like() {
AssociatedWhitespaceRules::from_bits((self.0 >> 29) as u8)
} else {
AssociatedWhitespaceRules::none()
}
}
#[inline]
pub fn with_associated_whitespace(&self, rules: AssociatedWhitespaceRules) -> Token {
if !self.is_delim_like() {
return *self;
}
Token::new_delim_with_associated_whitespace(self.char().unwrap(), rules)
}
#[inline]
pub fn comment_style(&self) -> Option<CommentStyle> {
if self.kind_bits() == Kind::Comment as u8 { CommentStyle::from_bits((self.0 >> 29) as u8) } else { None }
}
#[inline]
pub fn quote_style(&self) -> QuoteStyle {
if self.kind_bits() == Kind::String as u8 {
if self.third_flag() {
return QuoteStyle::Double;
} else {
return QuoteStyle::Single;
}
}
QuoteStyle::None
}
#[inline]
pub fn with_quotes(&self, quote_style: QuoteStyle) -> Token {
debug_assert!(quote_style != QuoteStyle::None);
if self.kind_bits() != Kind::String as u8 || quote_style == self.quote_style() {
return *self;
}
Token::new_string(quote_style, self.has_close_quote(), self.contains_escape_chars(), self.len())
}
#[inline]
pub const fn has_close_quote(&self) -> bool {
debug_assert!(self.kind_bits() == Kind::String as u8);
self.second_flag()
}
#[inline]
pub const fn can_escape(&self) -> bool {
self.kind_bits() == Kind::String as u8
|| self.kind_bits() == Kind::Url as u8
|| self.kind_bits() == Kind::Dimension as u8
|| self.is_ident_like()
}
#[inline]
pub const fn contains_escape_chars(&self) -> bool {
if self.kind_bits() == Kind::Dimension as u8 {
return true;
}
self.can_escape() && self.first_flag()
}
#[inline]
pub const fn is_dashed_ident(&self) -> bool {
debug_assert!(self.is_ident_like());
self.second_flag()
}
#[inline]
pub const fn is_lower_case(&self) -> bool {
self.is_ident_like() && !self.third_flag()
}
#[inline]
pub fn atom_bits(&self) -> u32 {
if self.kind_bits() & 0b1111 == Kind::Dimension as u8 && self.first_flag() {
self.0 & 0b111_1111
} else if self.is_ident_like() && self.kind_bits() & 0b1111 != Kind::Hash as u8 {
self.0 & LENGTH_MASK
} else {
0
}
}
#[inline]
pub const fn is_trivia(&self) -> bool {
self.kind_bits() & 0b000011 == self.kind_bits()
}
#[inline]
pub const fn url_has_leading_space(&self) -> bool {
debug_assert!(self.kind_bits() == Kind::Url as u8);
self.second_flag()
}
#[inline]
pub const fn url_has_closing_paren(&self) -> bool {
debug_assert!(self.kind_bits() == Kind::Url as u8);
self.third_flag()
}
#[inline]
pub const fn hash_is_id_like(&self) -> bool {
debug_assert!(self.kind_bits() == Kind::Hash as u8);
self.second_flag()
}
#[inline]
pub const fn is_bad(&self) -> bool {
self.kind_bits() & 0b1_0000 == 0b1_0000
}
#[inline]
pub const fn with_bad_flag(&self) -> Self {
Self(self.0 | 1 << 28, self.1)
}
#[inline]
pub const fn is_cdc(&self) -> bool {
self.kind_bits() == (Kind::CdcOrCdo as u8) && self.third_flag()
}
pub fn leading_len(&self) -> u32 {
match self.kind() {
Kind::AtKeyword | Kind::Hash | Kind::String | Kind::BadAtKeyword | Kind::BadHash | Kind::BadString => 1,
Kind::Dimension | Kind::BadDimension => self.numeric_len(),
Kind::Comment | Kind::BadComment => 2,
Kind::Url | Kind::BadUrl => (self.0 & LENGTH_MASK) >> 12,
_ => 0,
}
}
pub fn trailing_len(&self) -> u32 {
match self.kind() {
Kind::Function | Kind::BadFunction => 1,
Kind::String | Kind::BadString => self.has_close_quote() as u32,
Kind::Comment | Kind::BadComment if self.comment_style().unwrap().is_block() => 2,
Kind::Url | Kind::BadUrl => self.0 & !HALF_LENGTH_MASK,
_ => 0,
}
}
#[inline]
pub fn to_pairwise(&self) -> Option<PairWise> {
PairWise::from_token(self)
}
#[inline(always)]
pub fn with_cursor(self, offset: SourceOffset) -> Cursor {
Cursor::new(offset, self)
}
#[inline(always)]
pub fn hex_value(self) -> u32 {
if self == Kind::Hash { self.1 } else { 0 }
}
pub fn needs_separator_for(&self, second: Token) -> bool {
if second == AssociatedWhitespaceRules::EnforceBefore && *self != Kind::Whitespace
|| *self == AssociatedWhitespaceRules::EnforceAfter && second != Kind::Whitespace
{
return true;
}
if *self == AssociatedWhitespaceRules::BanAfter {
return false;
}
match self.kind() {
Kind::Ident => {
(matches!(second.kind(), Kind::Number | Kind::Dimension) &&
(!second.has_sign() || second.value() < 0.0))
|| matches!(second.kind(), Kind::Ident | Kind::Function | Kind::Url | Kind::BadUrl)
|| matches!(second.char(), Some('(' | '-'))
|| second.is_cdc()
}
Kind::AtKeyword | Kind::Hash | Kind::Dimension => {
(matches!(second.kind(), Kind::Number | Kind::Dimension) &&
(!second.has_sign() || second.value() < 0.0))
|| matches!(second.kind(), Kind::Ident | Kind::Function | Kind::Url | Kind::BadUrl)
|| matches!(second.char(), Some('-'))
|| second.is_cdc()
}
Kind::Number => {
matches!(
second.kind(),
Kind::Ident | Kind::Function | Kind::Url | Kind::BadUrl | Kind::Number | Kind::Dimension
) || matches!(second.char(), Some('%'))
|| second.is_cdc()
}
_ => match self.char() {
Some('#') => {
matches!(
second.kind(),
Kind::Ident | Kind::Function | Kind::Url | Kind::BadUrl | Kind::Number | Kind::Dimension
) || matches!(second.char(), Some('-'))
|| second.is_cdc()
}
Some('-') => {
matches!(
second.kind(),
Kind::Ident | Kind::Function | Kind::Url | Kind::BadUrl | Kind::Number | Kind::Dimension
) || matches!(second.char(), Some('-'))
|| second.is_cdc()
}
Some('@') => {
matches!(second.kind(), Kind::Ident | Kind::Function | Kind::Url | Kind::BadUrl)
|| matches!(second.char(), Some('-'))
|| second.is_cdc()
}
Some('.') => matches!(second.kind(), Kind::Number | Kind::Dimension),
Some('+') => matches!(second.kind(), Kind::Number | Kind::Dimension),
Some('/') => matches!(second.char(), Some('*' | '/')),
_ => false,
},
}
}
pub fn to_bits(&self) -> u64 {
(self.0 as u64) << 32 | self.1 as u64
}
}
impl core::fmt::Debug for Token {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut d = f.debug_struct(format!("Token::{}", self.kind().as_str()).as_str());
match self.kind() {
Kind::Eof => &mut d,
Kind::Number => d.field("value", &self.value()).field("len", &self.numeric_len()),
Kind::Dimension => {
d.field("value", &self.value()).field("len", &self.numeric_len()).field("dimension_len", &self.len())
}
_ if self.is_delim_like() => {
d.field("char", &self.char().unwrap()).field("len", &self.len());
if !self.associated_whitespace().is_none() {
d.field("associated_whitespace", &self.associated_whitespace());
}
&mut d
}
Kind::String => d
.field("quote_style", &if self.first_flag() { "Double" } else { "Single" })
.field("has_close_quote", &self.second_flag())
.field("contains_escape_chars", &self.third_flag())
.field("len", &self.len()),
Kind::Ident | Kind::Function | Kind::AtKeyword => d
.field("is_lower_case", &self.first_flag())
.field("is_dashed_ident", &self.second_flag())
.field("contains_escape_chars", &self.third_flag())
.field("len", &self.len()),
Kind::Hash => d
.field("is_lower_case", &self.first_flag())
.field("hash_is_id_like", &self.second_flag())
.field("contains_escape_chars", &self.third_flag())
.field("len", &self.len()),
Kind::Url => d
.field("url_has_closing_paren", &self.first_flag())
.field("url_has_leading_space", &self.second_flag())
.field("contains_escape_chars", &self.third_flag())
.field("len", &self.len()),
Kind::UnicodeRange => d
.field("start", &format_args!("U+{:X}", self.unicode_range_start()))
.field("end", &format_args!("U+{:X}", self.unicode_range_end()))
.field("len", &self.len()),
Kind::CdcOrCdo => d.field("is_cdc", &self.first_flag()).field("len", &self.len()),
Kind::Whitespace => {
d.field("contains", &self.whitespace_style());
if self.whitespace_is_significant() {
d.field("significant", &true);
}
d.field("len", &self.len())
}
_ => d
.field("flag_0", &self.first_flag())
.field("flag_1", &self.second_flag())
.field("flag_2", &self.third_flag())
.field("len", &self.len()),
}
.finish()
}
}
impl std::fmt::Display for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.kind() {
Kind::Delim => write!(f, "Delim({})", self.char().unwrap()),
k => write!(f, "{}", k.as_str()),
}
}
}
#[cfg(feature = "serde")]
impl serde::ser::Serialize for Token {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
use serde::ser::SerializeStruct;
if *self == Self::EMPTY {
return serializer.serialize_none();
}
let mut state = serializer.serialize_struct("Token", 3)?;
state.serialize_field("kind", self.kind().as_str())?;
state.serialize_field("len", &self.len())?;
state.end()
}
}
impl From<Token> for Kind {
fn from(token: Token) -> Self {
token.kind()
}
}
impl PartialEq<Kind> for Token {
fn eq(&self, other: &Kind) -> bool {
self.kind() == *other
}
}
impl From<Token> for KindSet {
fn from(token: Token) -> Self {
KindSet::new(&[token.kind()])
}
}
impl PartialEq<KindSet> for Token {
fn eq(&self, other: &KindSet) -> bool {
other.contains(self.kind())
}
}
impl From<Token> for QuoteStyle {
fn from(token: Token) -> Self {
token.quote_style()
}
}
impl PartialEq<QuoteStyle> for Token {
fn eq(&self, other: &QuoteStyle) -> bool {
&self.quote_style() == other
}
}
impl From<Token> for Whitespace {
fn from(token: Token) -> Self {
token.whitespace_style()
}
}
impl PartialEq<Whitespace> for Token {
fn eq(&self, other: &Whitespace) -> bool {
self.whitespace_style().intersects(*other)
}
}
impl PartialEq<AssociatedWhitespaceRules> for Token {
fn eq(&self, other: &AssociatedWhitespaceRules) -> bool {
self.associated_whitespace().intersects(*other)
}
}
impl PartialEq<CommentStyle> for Token {
fn eq(&self, other: &CommentStyle) -> bool {
self.comment_style().map(|style| &style == other).unwrap_or(false)
}
}
impl PartialEq<PairWise> for Token {
fn eq(&self, other: &PairWise) -> bool {
self.to_pairwise().map(|style| &style == other).unwrap_or(false)
}
}
impl PartialEq<char> for Token {
fn eq(&self, other: &char) -> bool {
self.char().map(|char| char == *other).unwrap_or(false)
}
}
#[test]
fn size_test() {
assert_eq!(::std::mem::size_of::<Token>(), 8);
}
#[test]
fn test_new_whitespace() {
assert_eq!(Token::SPACE, Kind::Whitespace);
assert_eq!(Token::SPACE, Whitespace::Space);
assert_eq!(Token::TAB, Kind::Whitespace);
assert_eq!(Token::TAB, Whitespace::Tab);
assert_eq!(Token::NEWLINE, Kind::Whitespace);
assert_eq!(Token::NEWLINE, Whitespace::Newline);
assert_eq!(Token::new_whitespace(Whitespace::Space, 4), Kind::Whitespace);
assert_eq!(Token::new_whitespace(Whitespace::Space | Whitespace::Newline, 4), Whitespace::Space);
assert_eq!(Token::new_whitespace(Whitespace::Space, 4).len(), 4);
assert_eq!(Token::new_whitespace(Whitespace::Tab | Whitespace::Space, 4), Whitespace::Tab);
assert_eq!(Token::new_whitespace(Whitespace::Newline, 4), Whitespace::Newline);
assert_eq!(Token::new_whitespace(Whitespace::Newline, 4).len(), 4);
}
#[test]
fn test_new_comment() {
assert_eq!(Token::new_comment(CommentStyle::Block, 4), Kind::Comment);
assert_eq!(Token::new_comment(CommentStyle::Block, 4), CommentStyle::Block);
assert_eq!(Token::new_comment(CommentStyle::Single, 4), CommentStyle::Single);
}
#[test]
fn test_new_number() {
assert_eq!(Token::new_number(false, false, 3, 4.2), Kind::Number);
assert_eq!(Token::new_number(false, false, 3, 4.2).value(), 4.2);
assert_eq!(Token::new_number(false, false, 3, 4.2).len(), 3);
assert_eq!(Token::new_number(false, true, 9, 4.2), Kind::Number);
assert_eq!(Token::new_number(false, true, 9, 4.2).value(), 4.2);
assert_eq!(Token::new_number(false, true, 9, 4.2).len(), 9);
assert!(!Token::new_number(false, false, 3, 4.2).has_sign());
assert!(Token::new_number(false, true, 3, 4.2).has_sign());
assert!(!Token::new_number(false, true, 3, 4.0).is_float());
assert!(Token::new_number(true, false, 3, 4.2).is_float());
}
#[test]
fn test_new_string() {
assert_eq!(Token::new_string(QuoteStyle::Single, false, false, 4), Kind::String);
assert_eq!(Token::new_string(QuoteStyle::Single, false, false, 4), QuoteStyle::Single);
assert!(!Token::new_string(QuoteStyle::Single, false, false, 4).has_close_quote());
assert!(!Token::new_string(QuoteStyle::Single, false, false, 4).contains_escape_chars());
assert_eq!(Token::new_string(QuoteStyle::Single, false, false, 4).len(), 4);
assert_eq!(Token::new_string(QuoteStyle::Double, false, false, 4), Kind::String);
assert_eq!(Token::new_string(QuoteStyle::Double, false, false, 4), QuoteStyle::Double);
assert!(Token::new_string(QuoteStyle::Double, true, false, 4).has_close_quote());
assert!(!Token::new_string(QuoteStyle::Double, true, false, 4).contains_escape_chars());
assert_eq!(Token::new_string(QuoteStyle::Double, true, false, 5).len(), 5);
assert!(Token::new_string(QuoteStyle::Double, true, true, 4).contains_escape_chars());
assert!(Token::new_string(QuoteStyle::Double, false, true, 4).contains_escape_chars());
}
#[test]
fn test_new_hash() {
assert_eq!(Token::new_hash(false, false, false, 4, 0), Kind::Hash);
assert!(!Token::new_hash(false, false, false, 4, 0).contains_escape_chars());
assert!(Token::new_hash(false, false, true, 4, 0).contains_escape_chars());
assert!(Token::new_hash(false, false, true, 4, 0).is_lower_case());
assert!(!Token::new_hash(true, false, false, 4, 0).is_lower_case());
assert_eq!(Token::new_hash(true, false, false, 4, 0).len(), 4);
assert_eq!(Token::new_hash(true, false, false, 4, 0).hex_value(), 0);
assert_eq!(Token::new_hash(true, false, false, 4, 18).hex_value(), 18);
}
#[test]
#[should_panic]
fn test_new_string_with_quotes_none() {
Token::new_string(QuoteStyle::None, false, true, 4);
}
#[test]
fn test_new_delim() {
assert_eq!(Token::new_delim('>'), Kind::Delim);
assert_eq!(Token::new_delim('>'), '>');
assert_eq!(Token::new_delim('>').len(), 1);
assert_eq!(Token::new_delim('.'), Kind::Delim);
assert_eq!(Token::new_delim('.'), '.');
assert_eq!(Token::new_delim('.').len(), 1);
assert_eq!(Token::new_delim('ℝ'), Kind::Delim);
assert_eq!(Token::new_delim('ℝ'), 'ℝ');
assert_eq!(Token::new_delim('ℝ').len(), 3);
assert_eq!(Token::new_delim('💣'), Kind::Delim);
assert_eq!(Token::new_delim('💣'), '💣');
assert_eq!(Token::new_delim('💣').len(), 4);
assert_eq!(Token::new_delim('💣').len(), 4);
assert_eq!(Token::new_delim('💣').len(), 4);
}
#[test]
fn with_associated_whitespace() {
assert_eq!(
Token::new_delim('>').with_associated_whitespace(
AssociatedWhitespaceRules::EnforceBefore | AssociatedWhitespaceRules::EnforceAfter
),
AssociatedWhitespaceRules::EnforceBefore | AssociatedWhitespaceRules::EnforceBefore
);
assert_eq!(
Token::new_delim('>').with_associated_whitespace(AssociatedWhitespaceRules::BanAfter),
AssociatedWhitespaceRules::BanAfter
);
}
#[test]
fn test_with_quotes() {
assert_eq!(
Token::new_string(QuoteStyle::Single, false, false, 4).with_quotes(QuoteStyle::Double),
Token::new_string(QuoteStyle::Double, false, false, 4)
);
assert_eq!(
Token::new_string(QuoteStyle::Double, true, true, 8).with_quotes(QuoteStyle::Single),
Token::new_string(QuoteStyle::Single, true, true, 8),
);
}
#[test]
fn test_with_significant_whitespace() {
for len in [1, 3, 4, 255, 256, 0xFF_FFFF, u32::MAX] {
for style in [Whitespace::Space, Whitespace::Tab, Whitespace::Space | Whitespace::Newline] {
let token = Token::new_whitespace(style, len);
let significant = token.with_significant_whitespace(true);
assert!(!token.whitespace_is_significant());
assert!(significant.whitespace_is_significant());
assert_eq!(token.len(), len);
assert_eq!(significant.len(), len);
assert_eq!(significant.whitespace_style(), style);
assert_eq!(significant.kind(), Kind::Whitespace);
assert_eq!(significant.with_significant_whitespace(false), token);
}
}
let ident = Token::new_interned(Kind::Ident, 1, 3);
assert_eq!(ident.with_significant_whitespace(true), ident);
assert!(!ident.whitespace_is_significant());
}
#[test]
#[should_panic]
fn test_with_quotes_none() {
Token::new_string(QuoteStyle::Single, false, true, 4).with_quotes(QuoteStyle::None);
Token::new_string(QuoteStyle::Double, false, true, 4).with_quotes(QuoteStyle::None);
}
#[test]
fn test_new_dimension() {
{
let token = Token::new_dimension(false, false, 3, 3, 999.0, 0);
assert_eq!(token, Kind::Dimension);
assert_eq!(token.value(), 999.0);
assert_eq!(token.numeric_len(), 3);
assert_eq!(token.len(), 6);
assert!(!token.is_float());
assert!(!token.has_sign());
}
{
let token = Token::new_dimension(false, false, 5, 2, 8191.0, 0);
assert_eq!(token, Kind::Dimension);
assert_eq!(token.value(), 8191.0);
assert_eq!(token.numeric_len(), 5);
assert_eq!(token.len(), 7);
assert!(!token.is_float());
assert!(!token.has_sign());
}
for i in -8191..8191 {
let token = Token::new_dimension(false, false, 9, 3, i as f32, 0);
assert_eq!(token.value(), i as f32);
}
}
#[test]
fn test_bad_bits() {
let token = Token::new_dimension(false, false, 5, 2, 8191.0, 42);
assert!(!token.is_bad());
assert_eq!(token.kind(), Kind::Dimension);
assert_eq!(Kind::from_bits(token.kind_bits()), Kind::Dimension);
assert!(!token.is_bad());
assert_eq!(token.len(), 7);
assert_eq!(token.numeric_len(), 5);
let bad_token = token.with_bad_flag();
assert!(bad_token.is_bad());
assert_eq!(bad_token.kind(), Kind::BadDimension);
assert_eq!(Kind::from_bits(bad_token.kind_bits()), Kind::BadDimension);
assert_eq!(bad_token.len(), 7);
assert_eq!(bad_token.numeric_len(), 5);
assert_eq!(bad_token.atom_bits(), 42);
let token = Token::new_delim('(');
assert!(!token.is_bad());
let bad_token = token.with_bad_flag();
assert!(!token.is_bad());
assert!(bad_token.is_bad());
assert_eq!(bad_token.kind(), Kind::BadLeftParen);
let token = Token::new_delim('[');
assert_eq!(token, Kind::LeftSquare);
assert_eq!(token.with_bad_flag().kind(), Kind::BadLeftSquare);
}
impl SourceTokenTrait for Token {
type Kind = crate::Kind;
const EMPTY: Self = Token::EMPTY;
fn kind(self) -> Self::Kind {
Token::kind(&self)
}
fn len(self) -> u32 {
Token::len(&self)
}
fn kind_name(self) -> &'static str {
Token::kind(&self).as_str()
}
fn leading_len(self) -> u32 {
Token::leading_len(&self)
}
fn trailing_len(self) -> u32 {
Token::trailing_len(&self)
}
}