css_lexer/token.rs
1use crate::{
2 AssociatedWhitespaceRules, CommentStyle, Cursor, Kind, KindSet, PairWise, QuoteStyle, SourceOffset, Whitespace,
3 constants::SINGLE_CHAR_KINDS,
4};
5use source_tools::SourceToken as SourceTokenTrait;
6use std::char::REPLACEMENT_CHARACTER;
7
8/// An abstract representation of the chunk of the source text, retaining certain "facts" about the source.
9///
10/// # Design
11///
12/// The [Token] type is an immutable packing of two [u32s][u32] that represents a unit in the source text, but without
13/// the associated offset data that points to its position in the source text. This is important because it means that
14/// equivalent [Tokens][Token] are equal even in different parts of the document. For the most part a [Token] doesn't
15/// represent data that can be put into a text file because it lacks the underlying character data. It is lossy. For
16/// example a [Token] with [Kind::Ident] just represents _an_ ident, but it doesn't retain what the keyword is).
17/// Storing raw-character data would require either storing tokens on the heap (and therefore they couldn't be [Sized])
18/// or by keeping a reference to `&'a str` which means larger token sizes and lifetime tracking. By _not_ storing
19/// character data we can keep [Token] [Sized] and keep it to `size_of` `8`, avoiding the heap, avoiding
20/// references/lifetimes, and keeping [Token] entirely in the stack. For a lot of tokens this is _fine_ because the
21/// underlying character data isn't that useful past a certain point.
22///
23/// A [Token] retains certain "facts" about the underlying unit of text, though. For example it retains the [Kind], how
24/// many characters the token consumed, and various other pieces of information, depending on the [Kind]. In some
25/// cases, it's entirely possible to represent the full token, including character data, into the available bits (for
26/// example [Kind::Delim] stores its [char], [Kind::Number] stores its [f32]). Taking the time in the tokenizer to
27/// gather these facts and values can keep cache-lines hot, which speeds up subsequent checks in the parser.
28///
29/// If you're familiar with "red green" syntax trees such as [Swiftlang's libsyntax][1], or [Rust-Analyzer's Rowan][2]
30/// or [Roslyn][3] this might be a little familiar in some concepts. However [Token] does not represent a tree, and
31/// relies on resorting back to the string data to find out keyword values.
32///
33/// [1]: https://gh.io/AAtdqpg
34/// [2]: https://gh.io/AAtf8pt
35/// [3]: https://gh.io/AAtab90
36///
37/// This representation of facts, kind, length, or other metadata can be quite complex - so here's a
38/// full breakdown:
39///
40/// # Anatomy of Token
41///
42/// A [Token] is a struct of `(u32, u32)`. The second u32 is _usually_ the token length (hence keeping them separate).
43/// The first [u32], however, is split into 3 (sometimes 5) parts. The two u32s can be thought of like so:
44///
45/// ```md
46/// |------|------|--------------------------|---------------------------------|
47/// | TF | K | VD | Value |
48/// 0b| 0000 | 0000 | 000000000000000000000000 | 0000000000000000000000000000000 |
49/// |------|------|--------------------------|---------------------------------|
50/// | 4--- | 4--- | 24---------------------- | 32----------------------------- |
51/// ```
52///
53/// ## TF = Type Flags (or "Token Facts")
54///
55/// This represents a bit-mask in the upper-most 3 bits. The flags are general purpose and change meaning depending on
56/// the Token's [Kind]. Each flag generally maps to a method so it's not necessary to remenber the contents of this
57/// table, but it can serve as a useful reference. Note that not all methods return a [bool], so footnotes have been
58/// added to explain these further.
59///
60/// | Kind:: | Flag | Description | Method |
61/// |---------------------|--------|-----------------------------|------------------------------------------|
62/// | [Kind::Number] | `0001` | Error/Recovery token | [Token::is_bad()][^bad] |
63/// | | `0010` | Floating Point | [Token::is_float()] |
64/// | | `0100` | Has a "Sign" (-/+) | [Token::has_sign()] |
65/// | | `1000` | Sign is required | [Token::sign_is_required()] |
66/// | [Kind::Dimension] | `0001` | Error/Recovery token | [Token::is_bad()][^bad] |
67/// | | `0010` | Floating Point | [Token::is_float()] |
68/// | | `0100` | Has a "Sign" (-/+) | [Token::has_sign()] |
69/// | | `1000` | Unit is a known dimension | [Token::atom_bits()][^dimension] |
70/// | [Kind::String] | `0001` | Error/Recovery token | [Token::is_bad()][^bad] |
71/// | | `0010` | Uses Double Quotes | [Token::quote_style()][^quotes] |
72/// | | `0100` | Has a closing quote | [Token::has_close_quote()] |
73/// | | `1000` | Contains escape characters | [Token::contains_escape_chars()] |
74/// | [Kind::Ident] | `0001` | Error/Recovery token | [Token::is_bad()][^bad] |
75/// | | `0010` | Contains non-lower-ASCII | [Token::is_lower_case()] |
76/// | | `0100` | Is a "Dashed Ident" | [Token::is_dashed_ident()] |
77/// | | `1000` | Contains escape characters | [Token::contains_escape_chars()] |
78/// | [Kind::Function] | `0001` | Error/Recovery token | [Token::is_bad()][^bad] |
79/// | | `0010` | Contains non-lower-ASCII | [Token::is_lower_case()] |
80/// | | `0100` | Is a "Dashed Ident" | [Token::is_dashed_ident()] |
81/// | | `1000` | Contains escape characters | [Token::contains_escape_chars()] |
82/// | [Kind::AtKeyword] | `0001` | Error/Recovery token | [Token::is_bad()][^bad] |
83/// | | `0010` | Contains non-lower-ASCII | [Token::is_lower_case()] |
84/// | | `0100` | Is a "Dashed Ident" | [Token::is_dashed_ident()] |
85/// | | `1000` | Contains escape characters | [Token::contains_escape_chars()] |
86/// | [Kind::Hash] | `0001` | Error/Recovery token | [Token::is_bad()][^bad] |
87/// | | `0010` | Contains non-lower-ASCII | [Token::is_lower_case()] |
88/// | | `0100` | First character is ASCII | [Token::hash_is_id_like()] |
89/// | | `1000` | Contains escape characters | [Token::contains_escape_chars()] |
90/// | [Kind::Url] | `0001` | Error/Recovery token | [Token::is_bad()][^bad] |
91/// | | `0010` | Has a closing paren ) | [Token::url_has_closing_paren()] |
92/// | | `0100` | Contains whitespace after ( | [Token::url_has_leading_space()] |
93/// | | `1000` | Contains escape characters | [Token::contains_escape_chars()] |
94/// | [Kind::UnicodeRange]| `0001` | Error/Recovery token | [Token::is_bad()][^bad] |
95/// | | `0010` | (Reserved) | -- |
96/// | | `0100` | (Reserved) | -- |
97/// | | `1000` | (Reserved) | -- |
98/// | [Kind::CdcOrCdo] | `0001` | Error/Recovery token | [Token::is_bad()][^bad] |
99/// | | `0010` | Is CDO (`000` would be CDC) | [Token::is_cdc()] |
100/// | | `0100` | (Reserved) | -- |
101/// | | `1000` | (Reserved) | -- |
102/// | [Kind::Whitespace] | `0001` | Error/Recovery token | [Token::is_bad()][^bad] |
103/// | | `???0` | Whitespace style | [Token::whitespace_style()][^whitespace] |
104/// | [Kind::Delim] | `0001` | Error/Recovery token | [Token::is_bad()][^bad] |
105/// | | `???0` | Associate whitespace rules | [Token::associated_whitespace()][^delim] |
106/// | [Kind::Comment] | `0001` | Error/Recovery token | [Token::is_bad()][^bad] |
107/// | | `???0` | (Special) | [Token::comment_style()][^comments] |
108///
109/// [^bad]: All tokens use the 4th bit to denote if this token is a "Erorr/Recovery Token". These tokens are not going
110/// to be emitted by the lexer (except in the case of BadString & BadUrl), but a Parser can set this flag on a token to
111/// help differentiate between tokens emitted by the lexer and tokens that were either emitted by the lexer but in an
112/// unexpected position, or tokens _constructed_ by the parser in order to aid in recovering the Parser into a state to
113/// resume.
114/// [^quotes]: Strings do not have a [bool] returning method for whether or not the quote is using double or single
115/// quotes, instead the [Token::quote_style()] method will returning the [QuoteStyle] enum for better readability.
116/// [^whitespace]: Whitespace tokens to not have a [bool] returning method, instead [Token::whitespace_style()] will
117/// return the [Whitespace] enum for improved readability.
118/// [^comments]: Rather than using the 3 bits as a bit-mask, Comment tokens use the data to store the [CommentStyle]
119/// enum, which is capable of representing 8 discrete comment styles.
120/// [^delim]: Delims can be used in interesting ways inside of CSS syntax. At higher levels CSS is _sometimes_
121/// whitespace sensitive, for example the whitespace inside of a CSS selector _sometimes_ represents the descendant
122/// combinator, meanwhile delimiters inside calc() are sensitive to whitespace collapse (`calc(1px + 1px)` is valid
123/// while `calc(1px+1px)` is a parse error). Further to this, introducing whitespace (say through a formatter) might
124/// break in interesting ways due to some combinations of Delims & Idents - for example Pseudo Classes like `:hover`,
125/// or CSS like languages such as SASS using `$var` style syntax. While `:hover` and `$var` are comprised of two tokens
126/// they're considered one conceptual unit. Having a way to express these relationships at the token level can be useful
127/// for other low level machinery such as formatters/minifiers, rather than introducing complex state at higher levels.
128/// For these reasons, Delim tokens have the ability to express their whitespace association. The lexer will always
129/// produce a token with empty whitespace rules, but parsers can replace this token with a more complex set of rules.
130///
131/// ## K = Kind Bits
132///
133/// The `K` value - upper-most bits 4-9 stores the 5-bit [Kind].
134///
135/// ## VD = Value Data
136///
137/// The `VD` value - the lower-most 24-bits - stores data depending on the [Token] [Kind]. For most kinds this data is
138/// reserved (just 0s). The value data cannot be interrogated manually, but it packs in additional data about the
139/// underlying string to make the string easier to parse without doing the same lookups that the tokenizer already had
140/// to - such as determining lengths of the various parts of the token, or packing values so that consulting the string
141/// can be avoided (which keeps cache-lines hot).
142///
143/// Below describes the special kinds which use the Value Data to store yet more information about the token...
144///
145/// ### Value Data for [Kind::Ident], [Kind::Function], [Kind::AtKeyword]
146///
147/// If the [Kind] is [Kind::Ident], [Kind::Function], or [Kind::AtKeyword] then Value Data represents the Ident's "Atom
148/// Data". When lexing one of these tokens the Lexer will pass the string slice to [DynAtomSet][crate::DynAtomSet] and
149/// set this bits accordingly. This allows implementations to provide a [DynAtomSet][crate::DynAtomSet] of interned
150/// strings to improve performance of string comparisons. The `ATOM_DYNAMIC_BIT` can be used to dynamically intern
151/// strings during runtime (this behaviour is abstracted by [DynAtomRegistry][crate::DynAtomRegistry]). This 24-bits
152/// allows for ~16MM unique strings, but with the `ATOM_DYNAMIC_BIT` this becomes ~8MM static atoms and ~8MM dynamic
153/// atoms (very unlikely CSS will ever reach even 10k predefined keywords, and most CSS files will have less than 1000
154/// unique strings).
155///
156/// ### Value Data for [Kind::Number]
157///
158/// If the [Kind] is [Kind::Number], Value Data represents the length of that number (this means the parser is
159/// restricted from representing numbers longer than 16,777,216 characters which is probably an acceptable limit). Note
160/// that this does not affect the _value_ of a number, just the characters in a string. Numbers in CSS are [f32]. The
161/// vast majority of [f32s][f32] can be represented in 16MM characters, but it's possible to author a document that
162/// contains a set of numeric characters longer than 16MM code points. These scenarios are considered [undefined
163/// behaviour][1].
164///
165/// [4]: https://en.wikipedia.org/wiki/Undefined_behavior
166///
167/// ### Value Data for [Kind::Hash]
168///
169/// If the [Kind] is [Kind::Hash], Value Data represents the length of that hash (this means the parser is restricted
170/// from representing IDs and hex codes longer than 16,777,216 characters which is probably an acceptable limit). Note
171/// that this restriction means that ID selectors have a much tigher limit than other tokens, such as strings or
172/// idents, but it's very unlikely to see a 16million character ID in CSS (String, maybe).
173///
174/// ### Value Data for [Kind::Url]
175///
176/// If the [Kind] is [Kind::Url], Value Data represents the "leading length" and "trailing length" of the URL. This
177/// means the value data is split into two 12 bit numbers:
178///
179/// ```md
180/// |--------------|--------------|
181/// | LL | TL |
182/// | 000000000000 | 000000000000 |
183/// |--------------|--------------|
184/// | 12---------- | 12---------- |
185/// ```
186///
187/// The "leading" length represents the `url(` part of the token. Typically this will be `4`, however it's possible
188/// (for legacy compatibility reasons within CSS) to add whitespace between the opening parenthesis and the URL value.
189/// It's also possible to escape the `url` ident portion. This means `\75\52\6c( ` is also a valid leading section of
190/// a URL ident (which has a character length of 13), as is `\000075 \000052 \00006c ( ` (28 characters). 12 bits
191/// allows for a maximum character length of 4,096. It is not possible to represent a URL token's leading section using
192/// 4,096 characters so there is some headroom (wasted bytes) here.
193///
194/// The "trailing" length represents the `)` part of the token. Typically this will be `1`, however it's possible to
195/// add any number of whitespace characters between the end of the URL and the closing parenthesis. If a CSS document
196/// contains more than 4095 whitespace characters then this is considered [undefined behaviour][4].
197///
198/// ### Value Data for [Kind::Dimension]
199///
200/// If K is a Dimension, then this represents both the number of characters in the numeric portion of the dimension
201/// and the length of the ident portion of the dimension... or the dimension unit itself (more on that below). This
202/// means the value data is split into two 12 bit numbers:
203///
204/// ```md
205/// |--------------|--------------|
206/// | NL | DUL |
207/// | 000000000000 | 000000000000 |
208/// |--------------|--------------|
209/// | 12---------- | 12---------- |
210///
211/// |--------------|-------| --------|
212/// | NL | KDUL | KNOWN |
213/// | 000000000000 | 00000 | 0000000 |
214/// |--------------|-------| --------|
215/// | 12---------- | 5---- | 7------ |
216/// ```
217///
218/// The NL portion - the numeric length - represents the length of characters the number contains. This means the
219/// numeric portion of a dimension can only be 4,096 characters long. This is dramatically shorter than the 16MM
220/// allowed for numbers but it's still also incredibly generous such that it's highly unlikely to ever be hit unless
221/// someone is intentionally trying to break the parser. The [Lexer][super::Lexer] encountering a dimension with a
222/// numeric portion longer than 4,096 characters is considered [undefined behaviour][4].
223///
224/// The DUL portion (if `TF & 100 == 0`) will represent the length of characters the ident portion of the dimension
225/// (aka the dimension unit) contains. This means the ident portion of a dimension can only be 4,096 characters long.
226/// For practical purposes CSS has a fixed set of dimensions - the longest of which (at the time of writing) are 5
227/// characters long (e.g. `svmax`). Through the use of escaping shenanigans it's possible to create a valid CSS
228/// dimension longer than 5 characters though (every ident can be made 8 times longer by using escape characters, e.g.
229/// `1svmax` at 6 characters can be instead written as `1\000073 \000076 \00006d \000061 \000078` at 40 characters). In
230/// addition to these factors, it's worth pointing out that there is scope for further dimensions and some [proposals
231/// for "custom" dimensions][5], and lastly this library is designed for CSS _and CSS-alike_ languages, which may
232/// invent their own dimension units. In other words being too restrictive on dimension ident length could be costly
233/// in the future, therefore 4,096 characters seems like a reasonable, if generous, trade-off.
234///
235/// There's a giant caveat here though. If `TF & 1000 != 0`, then the dimension is considered "known" and DUL will be
236/// encoded differently. Instead of just containing the dimension unit length, which requires consulting the underlying
237/// `&str` to get the actual dimension, it will be used to store an Atom - but only the first 7 bits (the KNOWN
238/// portion), which for an Atom must be a Dimension atom (an assummption made on anything that implements
239/// [AtomSet][crate::AtomSet] is that all dimension units should be stored in the byte values of 1-127, so that they
240/// can be encoded in this space). Dimension units _can_ be escape encoded, and so the underlying character data may
241/// differ from the unescaped unit length, as such 5-bit KDUL portion represents character data length, in other words
242/// `KNOWN.len()` may not always equal KDUL`.
243///
244/// [5]: https://github.com/w3c/csswg-drafts/issues/7379
245///
246/// ## Value
247///
248/// The `Value` portion of [Token] represents the length of the token for most token kinds. However, for some tokens
249/// their length is already packed into the first u32. So it would make more sense to use this u32 to store more
250/// interesting data.
251///
252/// ## Value for [Kind::Delim] and single character tokens
253///
254/// [Kind::Delim] and single-character tokens (i.e. [Kind::Colon]->[Kind::RightCurly]) typically have a length of `1`
255/// ([Kind::Delim] can have a varied length for surrogate pairs). Instead of storing the length and wasting a whole
256/// [u32], this region stores the [char]. Calling [Token::char()] will return an [Option] which will always be [Some]
257/// for [Kind::Delim] and single-character tokens.
258///
259/// ## Value for [Kind::Hash]
260///
261/// The length of a hash is stored in its `VD` portion, leaving 32bits to storing other data. It just so happens that
262/// a 8-character hex code (#ffaabbcc) fits nicely inside of 32-bits. During tokenization we can eagerly parse the hex
263/// code and stuff it here, so it can be more easily reasoned about in upstream code (rather than
264/// reading the character data).
265///
266/// ## Value for [Kind::Number] and [Kind::Dimension]
267///
268/// As these tokens store their length data in the `VD` portion, this [u32] instead stores the _value_ of the number,
269/// stored as [f32::to_bits()].
270///
271/// ## Value data for other tokens.
272///
273/// In all other cases, this represents the length of the token as utf-8 bytes. This means the token length is
274/// 4,294,967,296 aka ~4GB. This sounds very long but also CSS can host very large image data and browsers will
275/// accomodate very large URLs. [An mdn article on Data URLs][6] claims that Firefox supports 32mb Data URLs, Chrome
276/// supports over 512mb, and Safari over 2gb. The reality is that if someone has such a large data URL in their CSS
277/// they probably should split it out, but we have a whole 32 bits to store the length so we may as well use it...
278///
279/// [6]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs#common_problems
280#[repr(C)]
281#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
282pub struct Token(u32, u32);
283
284impl Default for Token {
285 fn default() -> Self {
286 Self((Kind::Whitespace as u32) << 24, 0)
287 }
288}
289
290const KIND_MASK: u32 = !((1 << 24) - 1);
291const LENGTH_MASK: u32 = (1 << 24) - 1;
292const HALF_LENGTH_MASK: u32 = !((1 << 12) - 1);
293
294/// The bit position used to distinguish between static and dynamic atoms.
295/// - Static atoms have this bit = 0 (values 0 to 8,388,607)
296/// - Dynamic atoms have this bit = 1 (values 8,388,608 to 16,777,215)
297///
298/// This allows atoms to use the full 24-bit space available in token data.
299#[allow(dead_code)] // Used in dyn_atom_registry module
300pub(crate) const ATOM_DYNAMIC_BIT: u32 = 23;
301
302impl Token {
303 /// Represents an empty token.
304 pub const EMPTY: Token = Token::new_whitespace(Whitespace::none(), 0);
305
306 /// Represents an EOF token.
307 pub const EOF: Token = Token(0b0, 0);
308
309 /// Represents a CDO (`<!--`) token.
310 pub const CDO: Token = Token(((Kind::CdcOrCdo as u32) << 24) & KIND_MASK, 4);
311
312 /// Represents a CDC (`-->`) token.
313 pub const CDC: Token = Token((((Kind::CdcOrCdo as u32) | 0b001_00000) << 24) & KIND_MASK, 3);
314
315 /// Represents a single ' ' space token.
316 pub const SPACE: Token = Token::new_whitespace(Whitespace::Space, 1);
317
318 /// Represents a single Tab token.
319 pub const TAB: Token = Token::new_whitespace(Whitespace::Tab, 1);
320
321 /// Represents a single `\n` token.
322 pub const NEWLINE: Token = Token::new_whitespace(Whitespace::Newline, 1);
323
324 /// Represents the Number `0`. This is not equal to other representations of zero, such as `00`, `0e0`, `0.0` and so
325 /// on.
326 pub const NUMBER_ZERO: Token = Token((((Kind::Number as u32) | 0b100_00000) << 24) & KIND_MASK, 1);
327
328 /// Represents the `:` token.
329 pub const COLON: Token = Token::new_delim(':');
330
331 /// Represents the `;` token.
332 pub const SEMICOLON: Token = Token::new_delim(';');
333
334 /// Represents the `,` token.
335 pub const COMMA: Token = Token::new_delim(',');
336
337 /// Represents the `[` token.
338 pub const LEFT_SQUARE: Token = Token::new_delim('[');
339
340 /// Represents the `]` token.
341 pub const RIGHT_SQUARE: Token = Token::new_delim(']');
342
343 /// Represents the `(` token.
344 pub const LEFT_PAREN: Token = Token::new_delim('(');
345
346 /// Represents the `)` token.
347 pub const RIGHT_PAREN: Token = Token::new_delim(')');
348
349 /// Represents the `{` token.
350 pub const LEFT_CURLY: Token = Token::new_delim('{');
351
352 /// Represents the `}` token.
353 pub const RIGHT_CURLY: Token = Token::new_delim('}');
354
355 /// Represents a `!` [Kind::Delim] token.
356 pub const BANG: Token = Token::new_delim('!');
357
358 /// Represents a `#` [Kind::Delim] token.
359 pub const HASH: Token = Token::new_delim('#');
360
361 /// Represents a `$` [Kind::Delim] token.
362 pub const DOLLAR: Token = Token::new_delim('$');
363
364 /// Represents a `%` [Kind::Delim] token - not to be confused with the `%` dimension.
365 pub const PERCENT: Token = Token::new_delim('%');
366
367 /// Represents a `&` [Kind::Delim] token.
368 pub const AMPERSAND: Token = Token::new_delim('&');
369
370 /// Represents a `*` [Kind::Delim] token.
371 pub const ASTERISK: Token = Token::new_delim('*');
372
373 /// Represents a `+` [Kind::Delim] token.
374 pub const PLUS: Token = Token::new_delim('+');
375
376 /// Represents a `-` [Kind::Delim] token.
377 pub const DASH: Token = Token::new_delim('-');
378
379 /// Represents a `.` [Kind::Delim] token.
380 pub const PERIOD: Token = Token::new_delim('.');
381
382 /// Represents a `/` [Kind::Delim] token.
383 pub const SLASH: Token = Token::new_delim('/');
384
385 /// Represents a `<` [Kind::Delim] token.
386 pub const LESS_THAN: Token = Token::new_delim('<');
387
388 /// Represents a `=` [Kind::Delim] token.
389 pub const EQUALS: Token = Token::new_delim('=');
390
391 /// Represents a `>` [Kind::Delim] token.
392 pub const GREATER_THAN: Token = Token::new_delim('>');
393
394 /// Represents a `?` [Kind::Delim] token.
395 pub const QUESTION: Token = Token::new_delim('?');
396
397 /// Represents a `@` [Kind::Delim] token. Not to be confused with the @keyword token.
398 pub const AT: Token = Token::new_delim('@');
399
400 /// Represents a `\\` [Kind::Delim] token.
401 pub const BACKSLASH: Token = Token::new_delim('\\');
402
403 /// Represents a `^` [Kind::Delim] token.
404 pub const CARET: Token = Token::new_delim('^');
405
406 /// Represents a `_` [Kind::Delim] token.
407 pub const UNDERSCORE: Token = Token::new_delim('_');
408
409 /// Represents a `\`` [Kind::Delim] token.
410 pub const BACKTICK: Token = Token::new_delim('\'');
411
412 /// Represents a `|` [Kind::Delim] token.
413 pub const PIPE: Token = Token::new_delim('|');
414
415 /// Represents a `~` [Kind::Delim] token.
416 pub const TILDE: Token = Token::new_delim('~');
417
418 /// Represents a replacement character [Kind::Delim] token.
419 pub const REPLACEMENT_CHARACTER: Token = Token::new_delim(REPLACEMENT_CHARACTER);
420
421 /// Creates a "Dummy" token with no additional data, just the [Kind].
422 #[inline]
423 pub const fn dummy(kind: Kind) -> Self {
424 Self((kind as u32) << 24, 0).with_bad_flag()
425 }
426
427 /// Creates a "Dummy" token with no additional data, just [Kind::Ident].
428 #[inline]
429 pub const fn dummy_ident() -> Self {
430 Self((Kind::Ident as u32) << 24, 0).with_bad_flag()
431 }
432
433 /// Creates a [Kind::Whitesapce] token.
434 #[inline]
435 pub(crate) const fn new_whitespace(style: Whitespace, len: u32) -> Self {
436 let flags: u32 = Kind::Whitespace as u32 | ((style.to_bits() as u32) << 5);
437 Self((flags << 24) & KIND_MASK, len)
438 }
439
440 /// Creates a [Kind::Comment] token.
441 #[inline]
442 pub(crate) const fn new_comment(style: CommentStyle, len: u32) -> Self {
443 let flags: u32 = Kind::Comment as u32 | ((style as u32) << 5);
444 Self((flags << 24) & KIND_MASK, len)
445 }
446
447 /// Creates a [Kind::Number] token.
448 #[inline]
449 pub(crate) fn new_number(is_float: bool, has_sign: bool, len: u32, value: f32) -> Self {
450 let flags: u32 = Kind::Number as u32 | ((is_float as u32) << 5) | ((has_sign as u32) << 6);
451 Self((flags << 24) & KIND_MASK | (len & LENGTH_MASK), value.to_bits())
452 }
453
454 /// Creates a new [Kind::Dimension] token.
455 #[inline]
456 pub(crate) fn new_dimension(
457 is_float: bool,
458 has_sign: bool,
459 num_len: u32,
460 unit_len: u32,
461 value: f32,
462 atom: u8,
463 ) -> Self {
464 debug_assert!(num_len <= 4097);
465 let num_len = (num_len << 12) & HALF_LENGTH_MASK;
466 let is_known_unit = if unit_len < 32 { ((atom != 0) as u32) << 7 } else { 0 };
467 let unit_len = if is_known_unit == 0 { unit_len } else { unit_len << 7 | (atom as u32 & 0b1111111) };
468 let flags: u32 = Kind::Dimension as u32 | is_known_unit | ((is_float as u32) << 5) | ((has_sign as u32) << 6);
469 Self(((flags << 24) & KIND_MASK) | ((num_len | unit_len) & LENGTH_MASK), value.to_bits())
470 }
471
472 /// Creates a new [Kind::BadString] token. Bad Strings are like String tokens but during lexing they failed to fully tokenize
473 /// into a proper string token, usually due to containing newline characters.
474 #[inline]
475 pub(crate) fn new_bad_string(len: u32) -> Self {
476 Self(((Kind::BadString as u32) << 24) & KIND_MASK, len)
477 }
478
479 /// Creates a new [Kind::BadUrl] token. Bad URLs are like URL tokens but during lexing they failed to fully tokenize into a
480 /// proper URL token, usually due to containing newline characters.
481 #[inline]
482 pub(crate) fn new_bad_url(len: u32) -> Self {
483 Self(((Kind::BadUrl as u32) << 24) & KIND_MASK, len)
484 }
485
486 /// Creates a new [Kind::Ident] token.
487 #[inline]
488 pub(crate) fn new_ident(
489 contains_non_lower_ascii: bool,
490 dashed: bool,
491 contains_escape: bool,
492 atom: u32,
493 len: u32,
494 ) -> Self {
495 let flags: u32 = Kind::Ident as u32
496 | ((contains_non_lower_ascii as u32) << 5)
497 | ((dashed as u32) << 6)
498 | ((contains_escape as u32) << 7);
499 debug_assert!(atom & LENGTH_MASK == atom);
500 Self((flags << 24) & KIND_MASK | atom, len)
501 }
502
503 /// Creates a new [Kind::Function] token.
504 #[inline]
505 pub(crate) fn new_function(
506 contains_non_lower_ascii: bool,
507 dashed: bool,
508 contains_escape: bool,
509 atom: u32,
510 len: u32,
511 ) -> Self {
512 let flags: u32 = Kind::Function as u32
513 | ((contains_non_lower_ascii as u32) << 5)
514 | ((dashed as u32) << 6)
515 | ((contains_escape as u32) << 7);
516 debug_assert!(atom & LENGTH_MASK == atom);
517 Self((flags << 24) & KIND_MASK | atom, len)
518 }
519
520 /// Creates a new [Kind::AtKeyword] token.
521 #[inline]
522 pub(crate) fn new_atkeyword(
523 contains_non_lower_ascii: bool,
524 dashed: bool,
525 contains_escape: bool,
526 atom: u32,
527 len: u32,
528 ) -> Self {
529 let flags: u32 = Kind::AtKeyword as u32
530 | ((contains_non_lower_ascii as u32) << 5)
531 | ((dashed as u32) << 6)
532 | ((contains_escape as u32) << 7);
533 debug_assert!(atom & LENGTH_MASK == atom);
534 Self((flags << 24) & KIND_MASK | atom, len)
535 }
536
537 /// Creates a new [Kind::Hash] token.
538 #[inline]
539 pub(crate) fn new_hash(
540 contains_non_lower_ascii: bool,
541 first_is_ascii: bool,
542 contains_escape: bool,
543 len: u32,
544 hex_value: u32,
545 ) -> Self {
546 let flags: u32 = Kind::Hash as u32
547 | ((contains_non_lower_ascii as u32) << 5)
548 | ((first_is_ascii as u32) << 6)
549 | ((contains_escape as u32) << 7);
550 debug_assert!(len < (1 << 24));
551 Self((flags << 24) & KIND_MASK | (len & LENGTH_MASK), hex_value)
552 }
553
554 /// Creates a new [Kind::String] token.
555 #[inline]
556 pub(crate) fn new_string(quotes: QuoteStyle, has_close_quote: bool, contains_escape: bool, len: u32) -> Self {
557 debug_assert!(quotes != QuoteStyle::None);
558 let quotes = if quotes == QuoteStyle::Double { 0b001_00000 } else { 0b0 };
559 let flags: u32 =
560 Kind::String as u32 | quotes | ((has_close_quote as u32) << 6) | ((contains_escape as u32) << 7);
561 Self((flags << 24) & KIND_MASK, len)
562 }
563
564 /// Creates a new [Kind::Url] token.
565 #[inline]
566 pub(crate) fn new_url(
567 ends_with_paren: bool,
568 contains_whitespace_after_open_paren: bool,
569 contains_escape: bool,
570 leading_length: u32,
571 trailing_length: u32,
572 len: u32,
573 ) -> Self {
574 let leading_length = (leading_length << 12) & HALF_LENGTH_MASK;
575 let flags: u32 = Kind::Url as u32
576 | ((ends_with_paren as u32) << 5)
577 | ((contains_whitespace_after_open_paren as u32) << 6)
578 | ((contains_escape as u32) << 7);
579 Self((flags << 24) & KIND_MASK | ((leading_length | trailing_length) & LENGTH_MASK), len)
580 }
581
582 /// Creates a new [Kind::UnicodeRange] token.
583 #[inline]
584 pub(crate) fn new_unicode_range(start: u32, end: u32, len: u32) -> Self {
585 debug_assert!(start <= 0xFFFFFF);
586 debug_assert!(end <= 0xFFFFFF);
587 debug_assert!(len <= 255);
588 let flags: u32 = Kind::UnicodeRange as u32;
589 Self((flags << 24) & KIND_MASK | (start & LENGTH_MASK), (len << 24) | (end & LENGTH_MASK))
590 }
591
592 /// If the [Token] is [Kind::UnicodeRange], returns the start value of the range.
593 /// This value can be up to 0xFFFFFF (6 hex digits).
594 ///
595 /// Asserts: The token is [Kind::UnicodeRange].
596 #[inline]
597 pub const fn unicode_range_start(&self) -> u32 {
598 debug_assert!(self.kind_bits() == Kind::UnicodeRange as u8);
599 self.0 & LENGTH_MASK
600 }
601
602 /// If the [Token] is [Kind::UnicodeRange], returns the end value of the range.
603 /// This value can be up to 0xFFFFFF (6 hex digits).
604 ///
605 /// Asserts: The token is [Kind::UnicodeRange].
606 #[inline]
607 pub const fn unicode_range_end(&self) -> u32 {
608 debug_assert!(self.kind_bits() == Kind::UnicodeRange as u8);
609 self.1 & LENGTH_MASK
610 }
611
612 /// Creates a new [Kind::Delim] token.
613 #[inline]
614 pub(crate) const fn new_delim(char: char) -> Self {
615 let flags: u32 = Kind::Delim as u32;
616 Self((flags << 24) & KIND_MASK, char as u32)
617 }
618
619 /// Creates a new [Kind::Delim] token with associated whitespace.
620 #[inline]
621 pub(crate) const fn new_delim_with_associated_whitespace(char: char, rules: AssociatedWhitespaceRules) -> Self {
622 let flags: u32 = Kind::Delim as u32 | ((rules.to_bits() as u32) << 5);
623 Self((flags << 24) & KIND_MASK, char as u32)
624 }
625
626 /// \[private\]
627 /// Creates a new Token with an interned string.
628 #[inline]
629 pub fn new_interned(kind: Kind, bits: u32, len: u32) -> Token {
630 debug_assert!(kind == KindSet::IDENT_LIKE);
631 debug_assert!(bits & LENGTH_MASK == bits);
632 debug_assert!(len > 0);
633 Self(((kind as u32) << 24) & KIND_MASK | (bits & LENGTH_MASK), len + ((kind != Kind::Ident) as u32))
634 }
635
636 /// Returns the raw bits representing the [Kind].
637 #[inline(always)]
638 pub(crate) const fn kind_bits(&self) -> u8 {
639 (self.0 >> 24 & 0b1_1111) as u8
640 }
641
642 /// Returns the [Kind].
643 #[inline]
644 pub const fn kind(&self) -> Kind {
645 let kind_bits = if self.kind_bits() & 0b1111 == Kind::Delim as u8 {
646 let c = self.char().unwrap() as usize;
647 if c < 127 { SINGLE_CHAR_KINDS[c] as u8 } else { Kind::Delim as u8 }
648 } else {
649 self.kind_bits()
650 };
651 Kind::from_bits(if self.is_bad() { kind_bits | 0b1_0000 } else { kind_bits })
652 }
653
654 /// Check if the TF upper-most bit is set.
655 #[inline(always)]
656 const fn first_flag(&self) -> bool {
657 self.0 >> 31 == 1
658 }
659
660 /// Check if the TF second-upper-most bit is set.
661 #[inline(always)]
662 const fn second_flag(&self) -> bool {
663 self.0 >> 30 & 0b1 == 1
664 }
665
666 /// Check if the TF third-upper-most bit is set.
667 #[inline(always)]
668 const fn third_flag(&self) -> bool {
669 self.0 >> 29 & 0b1 == 1
670 }
671
672 /// Check if the [Kind] is "Ident Like", i.e. it is [Kind::Ident], [Kind::AtKeyword], [Kind::Function], [Kind::Hash].
673 #[inline(always)]
674 pub(crate) const fn is_ident_like(&self) -> bool {
675 self.kind_bits() & 0b1100 == 0b1000
676 }
677
678 /// Check if the [Kind] is "Delim Like", i.e. it is [Kind::Delim], [Kind::Colon], [Kind::Semicolon], [Kind::Comma],
679 /// [Kind::LeftSquare], [Kind::RightSquare], [Kind::LeftParen], [Kind::RightParen], [Kind::LeftCurly],
680 /// [Kind::RightCurly].
681 #[inline(always)]
682 pub(crate) const fn is_delim_like(&self) -> bool {
683 self.kind_bits() & 0b1111 == Kind::Delim as u8
684 }
685
686 /// The only token with an empty length is EOF, but this method is available for symmetry with `len()`.
687 #[inline]
688 pub const fn is_empty(&self) -> bool {
689 self.kind_bits() == Kind::Eof as u8
690 }
691
692 /// Returns the amount of characters (utf-8 code points) this Token represents in the underlying source text.
693 #[inline]
694 pub const fn len(&self) -> u32 {
695 if self.kind_bits() == Kind::Eof as u8 {
696 0
697 } else if self.is_delim_like() {
698 debug_assert!(matches!(
699 self.kind(),
700 Kind::Delim
701 | Kind::Colon | Kind::Semicolon
702 | Kind::Comma | Kind::LeftSquare
703 | Kind::RightSquare
704 | Kind::LeftParen
705 | Kind::RightParen
706 | Kind::LeftCurly
707 | Kind::RightCurly
708 ));
709 self.char().unwrap().len_utf8() as u32
710 } else if self.kind_bits() & 0b1111 == Kind::Number as u8 {
711 self.numeric_len()
712 } else if self.kind_bits() & 0b1111 == Kind::Dimension as u8 {
713 if self.first_flag() {
714 self.numeric_len() + (self.0 >> 7 & 0b11111)
715 } else {
716 ((self.0 & LENGTH_MASK) >> 12) + (self.0 & !HALF_LENGTH_MASK)
717 }
718 } else if self.kind_bits() & 0b1111 == Kind::Hash as u8 {
719 self.0 & LENGTH_MASK
720 } else if self.kind_bits() == Kind::UnicodeRange as u8 {
721 self.1 >> 24
722 } else {
723 self.1
724 }
725 }
726
727 /// If the [Kind] is "Delim Like" (i.e. it is [Kind::Delim], [Kind::Colon], [Kind::Semicolon], [Kind::Comma],
728 /// [Kind::LeftSquare], [Kind::RightSquare], [Kind::LeftParen], [Kind::RightParen], [Kind::LeftCurly],
729 /// [Kind::RightCurly]) then this will return a [Some] with a [char] representing the value.
730 /// For non-delim-like tokens this will return [None].
731 pub const fn char(&self) -> Option<char> {
732 if self.is_delim_like() {
733 return char::from_u32(self.1);
734 }
735 None
736 }
737
738 /// The [Token] is a [Kind::Dimension] or [Kind::Number] and is an integer - i.e. it has no `.`.
739 #[inline]
740 pub const fn is_int(&self) -> bool {
741 self.kind_bits() & 0b1110 == 0b0100 && !self.third_flag()
742 }
743
744 /// The [Token] is a [Kind::Dimension] or [Kind::Number] and is a float - i.e. it has decimal places. This will be
745 /// `true` even if the decimal places are 0. e.g. `0.0`.
746 #[inline]
747 pub const fn is_float(&self) -> bool {
748 self.kind_bits() & 0b1100 == 0b0100 && self.third_flag()
749 }
750
751 /// The [Token] is a [Kind::Dimension] or [Kind::Number] and the underlying character data included a `-` or `+`
752 /// character. Note that a positive number may not necessarily have a sign, e.g. `3` will return false, while `+3`
753 /// will return `true`.
754 #[inline]
755 pub const fn has_sign(&self) -> bool {
756 self.kind_bits() & 0b1100 == 0b0100 && self.second_flag()
757 }
758
759 /// The [Token] is a [Kind::Number] and the `+` sign is semantically required and should be preserved during
760 /// minification. This is used for numbers in `an+b` syntax (e.g., `:nth-child(+5)`) where the `+` sign
761 /// distinguishes the value from other syntactic forms.
762 ///
763 /// Asserts: the `kind()` is [Kind::Number].
764 #[inline]
765 pub const fn sign_is_required(&self) -> bool {
766 debug_assert!(self.kind_bits() == Kind::Number as u8);
767 self.first_flag()
768 }
769
770 /// Returns a new [Token] with the `sign_is_required` flag set. This indicates that the `+` sign
771 /// should be preserved during minification (e.g., for `an+b` syntax).
772 ///
773 /// Asserts: the `kind()` is [Kind::Number].
774 #[inline]
775 pub const fn with_sign_required(self) -> Token {
776 debug_assert!(self.kind_bits() == Kind::Number as u8);
777 Token(self.0 | (1 << 31), self.1)
778 }
779
780 /// If the [Token] is a [Kind::Dimension] or [Kind::Number] then this returns the amount of characters used to
781 /// represent this number in the underlying source text. Numbers may be inefficiently encoded in the source text,
782 /// e.g. `0.0000`.
783 ///
784 /// Asserts: the `kind()` is [Kind::Dimension] or [Kind::Number].
785 #[inline]
786 pub const fn numeric_len(&self) -> u32 {
787 debug_assert!(matches!(self.kind(), Kind::Number | Kind::Dimension | Kind::BadNumber | Kind::BadDimension));
788 if self.kind_bits() & 0b1111 == Kind::Dimension as u8 {
789 (self.0 & LENGTH_MASK) >> 12
790 } else {
791 self.0 & LENGTH_MASK
792 }
793 }
794
795 /// If the [Token] is a [Kind::Dimension] or [Kind::Number] then this returns the [f32] representation of the number's
796 /// value.
797 ///
798 /// Asserts: the `kind()` is [Kind::Dimension] or [Kind::Number].
799 #[inline]
800 pub fn value(&self) -> f32 {
801 debug_assert!(matches!(self.kind(), Kind::Number | Kind::Dimension));
802 f32::from_bits(self.1)
803 }
804
805 /// Returns the [Whitespace].
806 ///
807 /// If the [Token] is not a [Kind::Whitespace] this will return [Whitespace::none()].
808 #[inline]
809 pub fn whitespace_style(&self) -> Whitespace {
810 if self.kind_bits() == Kind::Whitespace as u8 {
811 Whitespace::from_bits((self.0 >> 29) as u8)
812 } else {
813 Whitespace::none()
814 }
815 }
816
817 /// If the [Token] is a [Kind::Whitespace] then this returns true if that whitespace is significant; i.e. it must be
818 /// preserved during minification. Descendant combinators (`a b`) and the space in `@charset "utf-8";` are examples
819 /// of this.
820 ///
821 /// If the [Token] is not a [Kind::Whitespace] this will return `false`.
822 #[inline]
823 pub const fn whitespace_is_significant(&self) -> bool {
824 self.kind_bits() == Kind::Whitespace as u8 && self.0 & 1 == 1
825 }
826
827 /// Returns a new [Token] with the `whitespace_is_significant` flag set.
828 ///
829 /// If the [Token] is not a [Kind::Whitespace] this will return the same [Token].
830 #[inline]
831 pub const fn with_significant_whitespace(&self, significant: bool) -> Token {
832 if self.kind_bits() != Kind::Whitespace as u8 {
833 return *self;
834 }
835 if significant { Token(self.0 | 1, self.1) } else { Token(self.0 & !1, self.1) }
836 }
837
838 /// Returns the [AssociatedWhitespaceRules].
839 ///
840 /// If the [Kind] is not "Delim Like" (i.e. it is not [Kind::Delim], [Kind::Colon], [Kind::Semicolon], [Kind::Comma],
841 /// [Kind::LeftSquare], [Kind::RightSquare], [Kind::LeftParen], [Kind::RightParen], [Kind::LeftCurly],
842 /// [Kind::RightCurly]) then this will always return `AssociatedWhitespaceRules::none()`.
843 #[inline]
844 pub fn associated_whitespace(&self) -> AssociatedWhitespaceRules {
845 if self.is_delim_like() {
846 AssociatedWhitespaceRules::from_bits((self.0 >> 29) as u8)
847 } else {
848 AssociatedWhitespaceRules::none()
849 }
850 }
851
852 /// Returns a new [Token] with the [AssociatedWhitespaceRules] set to the given [AssociatedWhitespaceRules],
853 /// if possible.
854 ///
855 /// If the [Kind] is not "Delim Like" (i.e. it is not [Kind::Delim], [Kind::Colon], [Kind::Semicolon], [Kind::Comma],
856 /// [Kind::LeftSquare], [Kind::RightSquare], [Kind::LeftParen], [Kind::RightParen], [Kind::LeftCurly],
857 /// [Kind::RightCurly]) then this will return the same [Token].
858 /// If the [AssociatedWhitespaceRules] is different it will return a new [Token].
859 #[inline]
860 pub fn with_associated_whitespace(&self, rules: AssociatedWhitespaceRules) -> Token {
861 if !self.is_delim_like() {
862 return *self;
863 }
864 Token::new_delim_with_associated_whitespace(self.char().unwrap(), rules)
865 }
866
867 /// Returns the [CommentStyle].
868 ///
869 /// If the [Token] is not a [Kind::Comment] this will return [None].
870 #[inline]
871 pub fn comment_style(&self) -> Option<CommentStyle> {
872 if self.kind_bits() == Kind::Comment as u8 { CommentStyle::from_bits((self.0 >> 29) as u8) } else { None }
873 }
874
875 /// Returns the [QuoteStyle].
876 ///
877 /// If the [Token] is not a [Kind::String] this will return [QuoteStyle::None].
878 #[inline]
879 pub fn quote_style(&self) -> QuoteStyle {
880 if self.kind_bits() == Kind::String as u8 {
881 if self.third_flag() {
882 return QuoteStyle::Double;
883 } else {
884 return QuoteStyle::Single;
885 }
886 }
887 QuoteStyle::None
888 }
889
890 /// Returns a new [Token] with the [QuoteStyle] set to the given [QuoteStyle], if possible.
891 ///
892 /// If the [Token] is not a [Kind::String], or the [QuoteStyle] is already the given [QuoteStyle] this will return the same [Token].
893 /// If the [QuoteStyle] is different it will return a new [Token].
894 /// [QuoteStyle] must not be [QuoteStyle::None]
895 #[inline]
896 pub fn with_quotes(&self, quote_style: QuoteStyle) -> Token {
897 debug_assert!(quote_style != QuoteStyle::None);
898 if self.kind_bits() != Kind::String as u8 || quote_style == self.quote_style() {
899 return *self;
900 }
901 Token::new_string(quote_style, self.has_close_quote(), self.contains_escape_chars(), self.len())
902 }
903
904 /// If the [Token] is a [Kind::String] this checks if the string ended in a close quote.
905 /// It is possible to have a valid String token that does not end in a close quote, by eliding the quote at the end of
906 /// a file.
907 ///
908 /// Asserts: The [Kind] is [Kind::String].
909 #[inline]
910 pub const fn has_close_quote(&self) -> bool {
911 debug_assert!(self.kind_bits() == Kind::String as u8);
912 self.second_flag()
913 }
914
915 /// Checks if it is possible for the [Token] to contain escape characters. Numbers, for example, cannot. Idents can.
916 #[inline]
917 pub const fn can_escape(&self) -> bool {
918 self.kind_bits() == Kind::String as u8
919 || self.kind_bits() == Kind::Url as u8
920 || self.kind_bits() == Kind::Dimension as u8
921 || self.is_ident_like()
922 }
923
924 /// If the [Token] can escape, checks if the underlying source text contained escape characters.
925 ///
926 /// Asserts: The token can escape ([Token::can_escape()]).
927 #[inline]
928 pub const fn contains_escape_chars(&self) -> bool {
929 if self.kind_bits() == Kind::Dimension as u8 {
930 // Always assume Dimension contains escape because we have other fast paths to handle dimension units
931 return true;
932 }
933 self.can_escape() && self.first_flag()
934 }
935
936 /// If the [Token] is Ident like, checks if the first two code points are HYPHEN-MINUS (`-`).
937 ///
938 /// Asserts: The token is "ident like", i.e. it is [Kind::Ident], [Kind::AtKeyword], [Kind::Function], [Kind::Hash].
939 #[inline]
940 pub const fn is_dashed_ident(&self) -> bool {
941 debug_assert!(self.is_ident_like());
942 self.second_flag()
943 }
944
945 /// Checks if the [Token] is Ident like and none of the characters are ASCII upper-case.
946 #[inline]
947 pub const fn is_lower_case(&self) -> bool {
948 self.is_ident_like() && !self.third_flag()
949 }
950
951 #[inline]
952 pub fn atom_bits(&self) -> u32 {
953 if self.kind_bits() & 0b1111 == Kind::Dimension as u8 && self.first_flag() {
954 self.0 & 0b111_1111
955 } else if self.is_ident_like() && self.kind_bits() & 0b1111 != Kind::Hash as u8 {
956 self.0 & LENGTH_MASK
957 } else {
958 0
959 }
960 }
961
962 /// Checks if the [Token] is Trivia-like, that is [Kind::Comment], [Kind::Whitespace], [Kind::Eof]
963 #[inline]
964 pub const fn is_trivia(&self) -> bool {
965 self.kind_bits() & 0b000011 == self.kind_bits()
966 }
967
968 /// If the [Token] is [Kind::Url], checks if there are leading Whitespace characters before the inner value.
969 ///
970 /// Asserts: The token is [Kind::Url].
971 #[inline]
972 pub const fn url_has_leading_space(&self) -> bool {
973 debug_assert!(self.kind_bits() == Kind::Url as u8);
974 self.second_flag()
975 }
976
977 /// If the [Token] is [Kind::Url], checks if the closing parenthesis is present.
978 ///
979 /// Asserts: The token is [Kind::Url].
980 #[inline]
981 pub const fn url_has_closing_paren(&self) -> bool {
982 debug_assert!(self.kind_bits() == Kind::Url as u8);
983 self.third_flag()
984 }
985
986 /// If the [Token] is [Kind::Hash], checks if the Hash is "ID-like" (i.e its first character is ASCII).
987 ///
988 /// Asserts: The token is [Kind::Hash].
989 #[inline]
990 pub const fn hash_is_id_like(&self) -> bool {
991 debug_assert!(self.kind_bits() == Kind::Hash as u8);
992 self.second_flag()
993 }
994
995 /// Checks if the [Token] is [Kind::BadString] or [Kind::BadUrl], or the "bad flag" has been set.
996 #[inline]
997 pub const fn is_bad(&self) -> bool {
998 self.kind_bits() & 0b1_0000 == 0b1_0000
999 }
1000
1001 /// Returns a new token with the bad/recovery flag set.
1002 /// This is used by the parser to mark tokens as problematic during error recovery.
1003 #[inline]
1004 pub const fn with_bad_flag(&self) -> Self {
1005 Self(self.0 | 1 << 28, self.1)
1006 }
1007
1008 /// Checks if the [Token] is [Kind::CdcOrCdo] and is the CDC variant of that token.
1009 #[inline]
1010 pub const fn is_cdc(&self) -> bool {
1011 self.kind_bits() == (Kind::CdcOrCdo as u8) && self.third_flag()
1012 }
1013
1014 /// Some tokens may have a "leading" part:
1015 /// - [Kind::AtKeyword] always starts with a `@`,
1016 /// - [Kind::Hash] with a `#`.
1017 /// - [Kind::String] with a `"` or `'`.
1018 /// - [Kind::Comment] with a leading `/*` (or `//`).
1019 /// - [Kind::Dimension] has a leading numeric portion.
1020 /// - [Kind::Url] has the leading `url(` ident (which may vary in exact representation).
1021 ///
1022 /// This function returns the length of that, irrespective of the [Kind]. For other kinds not listed, this will return
1023 /// `0`, but for the above kinds it will calculate the leading length. This is useful for parsing out the underlying
1024 /// data which is likely to be of greater use.
1025 pub fn leading_len(&self) -> u32 {
1026 match self.kind() {
1027 Kind::AtKeyword | Kind::Hash | Kind::String | Kind::BadAtKeyword | Kind::BadHash | Kind::BadString => 1,
1028 Kind::Dimension | Kind::BadDimension => self.numeric_len(),
1029 Kind::Comment | Kind::BadComment => 2,
1030 Kind::Url | Kind::BadUrl => (self.0 & LENGTH_MASK) >> 12,
1031 _ => 0,
1032 }
1033 }
1034
1035 /// Some tokens may have a "trailing" part:
1036 /// - [Kind::Function] will always have an opening `(`.
1037 /// - [Kind::String] may have a closing `"` or `'`.
1038 /// - [Kind::Comment] may have a closing `*/`
1039 /// - [Kind::Url] may have a clsoing `)`.
1040 ///
1041 /// This function returns the length of that, irrespective of the [Kind]. For other kinds not listed, this will return
1042 /// `0`, but for the above kinds it will calculate the leading length. This is useful for parsing out the underlying
1043 /// data which is likely to be of greater use.
1044 pub fn trailing_len(&self) -> u32 {
1045 match self.kind() {
1046 Kind::Function | Kind::BadFunction => 1,
1047 Kind::String | Kind::BadString => self.has_close_quote() as u32,
1048 Kind::Comment | Kind::BadComment if self.comment_style().unwrap().is_block() => 2,
1049 Kind::Url | Kind::BadUrl => self.0 & !HALF_LENGTH_MASK,
1050 _ => 0,
1051 }
1052 }
1053
1054 /// Certain kinds have a [PairWise] equivalent:
1055 /// - [Kind::LeftParen] has [Kind::RightParen]
1056 /// - [Kind::LeftCurly] has [Kind::RightCurly]
1057 /// - [Kind::LeftSquare] has [Kind::RightSquare]
1058 ///
1059 /// This function returns the [PairWise] enum, if the [Token] is one of the above listed [Kinds][Kind]. For any other
1060 /// [Kind] this returns [None].
1061 #[inline]
1062 pub fn to_pairwise(&self) -> Option<PairWise> {
1063 PairWise::from_token(self)
1064 }
1065
1066 /// A convenience function for `Cursor::new(offset, token)`.
1067 #[inline(always)]
1068 pub fn with_cursor(self, offset: SourceOffset) -> Cursor {
1069 Cursor::new(offset, self)
1070 }
1071
1072 /// If the [Kind] is [Kind::Hash] then this token may have had the opportunity to be parsed as a `<hex-value>` (e.g.
1073 /// `#fff`). When this happens the character data is parsed during tokenization into a u32 which stores the
1074 /// RR,GG,BB,AA values.
1075 #[inline(always)]
1076 pub fn hex_value(self) -> u32 {
1077 if self == Kind::Hash { self.1 } else { 0 }
1078 }
1079
1080 /// If this [Token] is preceded by the [Token] `other` then a separating token (e.g. a comment) will need to be
1081 /// inserted between these the two tokens during serialization, in order for them to be able to be re-tokenized as
1082 /// the same tokens. For example an Ident ("a") adjacent to an Ident ("b"), if serialized without whitespace, would
1083 /// create a single Ident ("ab"). The rules for estbalishing whether or not these tokens needs whitespace are quite
1084 /// simple and are effectively [defined in the serialization section of the spec][1]. To reproduce the table:
1085 ///
1086 /// [1]: https://drafts.csswg.org/css-syntax/#serialization
1087 ///
1088 /// | | ident | function | url | bad url | - | number | percentage | dimension | CDC | ( | * | % |
1089 /// |:-----------|:-----:|:--------:|:---:|:-------:|:-:|:------:|:----------:|:---------:|:---:|:-:|:-:|:-:|
1090 /// | ident | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | | |
1091 /// | at-keyword | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | | | |
1092 /// | hash | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | | | |
1093 /// | dimension | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | | | |
1094 /// | # | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | | | |
1095 /// | \- | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | | | |
1096 /// | number | ✗ | ✗ | ✗ | ✗ | | ✗ | ✗ | ✗ | ✗ | | | ✗ |
1097 /// | @ | ✗ | ✗ | ✗ | ✗ | ✗ | | | | ✗ | | | |
1098 /// | . | | | | | | ✗ | ✗ | ✗ | | | | |
1099 /// | + | | | | | | ✗ | ✗ | ✗ | | | | |
1100 /// | / | | | | | | | | | | | ✗ | |
1101 ///
1102 /// The one exception not in this table is that two consecutive `/` characters should also be separated by spaces in
1103 /// order to avoid abmiguities with CSS-alike languages that treat two consecutive `/` characters as a single line
1104 /// comment.
1105 ///
1106 /// # Example
1107 ///
1108 /// ```
1109 /// use css_lexer::*;
1110 /// let mut lexer = Lexer::new(&EmptyAtomSet::ATOMS, "10 %");
1111 /// let first = lexer.advance();
1112 /// let _ = lexer.advance(); // Whitespace
1113 /// let second = lexer.advance();
1114 /// assert!(first.needs_separator_for(second));
1115 /// ```
1116 pub fn needs_separator_for(&self, second: Token) -> bool {
1117 if second == AssociatedWhitespaceRules::EnforceBefore && *self != Kind::Whitespace
1118 || *self == AssociatedWhitespaceRules::EnforceAfter && second != Kind::Whitespace
1119 {
1120 // We need whitespace after, unless the next token is actually whitespace.
1121 return true;
1122 }
1123 if *self == AssociatedWhitespaceRules::BanAfter {
1124 return false;
1125 }
1126 match self.kind() {
1127 Kind::Ident => {
1128 (matches!(second.kind(), Kind::Number | Kind::Dimension) &&
1129 // numbers with a `-` need separating, but with `+` they do not.
1130 (!second.has_sign() || second.value() < 0.0))
1131 || matches!(second.kind(), Kind::Ident | Kind::Function | Kind::Url | Kind::BadUrl)
1132 || matches!(second.char(), Some('(' | '-'))
1133 || second.is_cdc()
1134 }
1135 Kind::AtKeyword | Kind::Hash | Kind::Dimension => {
1136 (matches!(second.kind(), Kind::Number | Kind::Dimension) &&
1137 // numbers with a `-` need separating, but with `+` they do not.
1138 (!second.has_sign() || second.value() < 0.0))
1139 || matches!(second.kind(), Kind::Ident | Kind::Function | Kind::Url | Kind::BadUrl)
1140 || matches!(second.char(), Some('-'))
1141 || second.is_cdc()
1142 }
1143 Kind::Number => {
1144 matches!(
1145 second.kind(),
1146 Kind::Ident | Kind::Function | Kind::Url | Kind::BadUrl | Kind::Number | Kind::Dimension
1147 ) || matches!(second.char(), Some('%'))
1148 || second.is_cdc()
1149 }
1150 _ => match self.char() {
1151 Some('#') => {
1152 matches!(
1153 second.kind(),
1154 Kind::Ident | Kind::Function | Kind::Url | Kind::BadUrl | Kind::Number | Kind::Dimension
1155 ) || matches!(second.char(), Some('-'))
1156 || second.is_cdc()
1157 }
1158 Some('-') => {
1159 matches!(
1160 second.kind(),
1161 Kind::Ident | Kind::Function | Kind::Url | Kind::BadUrl | Kind::Number | Kind::Dimension
1162 ) || matches!(second.char(), Some('-'))
1163 || second.is_cdc()
1164 }
1165 Some('@') => {
1166 matches!(second.kind(), Kind::Ident | Kind::Function | Kind::Url | Kind::BadUrl)
1167 || matches!(second.char(), Some('-'))
1168 || second.is_cdc()
1169 }
1170 Some('.') => matches!(second.kind(), Kind::Number | Kind::Dimension),
1171 Some('+') => matches!(second.kind(), Kind::Number | Kind::Dimension),
1172 Some('/') => matches!(second.char(), Some('*' | '/')),
1173 _ => false,
1174 },
1175 }
1176 }
1177
1178 pub fn to_bits(&self) -> u64 {
1179 (self.0 as u64) << 32 | self.1 as u64
1180 }
1181}
1182
1183impl core::fmt::Debug for Token {
1184 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1185 let mut d = f.debug_struct(format!("Token::{}", self.kind().as_str()).as_str());
1186 match self.kind() {
1187 Kind::Eof => &mut d,
1188 Kind::Number => d.field("value", &self.value()).field("len", &self.numeric_len()),
1189 Kind::Dimension => {
1190 d.field("value", &self.value()).field("len", &self.numeric_len()).field("dimension_len", &self.len())
1191 }
1192 _ if self.is_delim_like() => {
1193 d.field("char", &self.char().unwrap()).field("len", &self.len());
1194 if !self.associated_whitespace().is_none() {
1195 d.field("associated_whitespace", &self.associated_whitespace());
1196 }
1197 &mut d
1198 }
1199 Kind::String => d
1200 .field("quote_style", &if self.first_flag() { "Double" } else { "Single" })
1201 .field("has_close_quote", &self.second_flag())
1202 .field("contains_escape_chars", &self.third_flag())
1203 .field("len", &self.len()),
1204 Kind::Ident | Kind::Function | Kind::AtKeyword => d
1205 .field("is_lower_case", &self.first_flag())
1206 .field("is_dashed_ident", &self.second_flag())
1207 .field("contains_escape_chars", &self.third_flag())
1208 .field("len", &self.len()),
1209 Kind::Hash => d
1210 .field("is_lower_case", &self.first_flag())
1211 .field("hash_is_id_like", &self.second_flag())
1212 .field("contains_escape_chars", &self.third_flag())
1213 .field("len", &self.len()),
1214 Kind::Url => d
1215 .field("url_has_closing_paren", &self.first_flag())
1216 .field("url_has_leading_space", &self.second_flag())
1217 .field("contains_escape_chars", &self.third_flag())
1218 .field("len", &self.len()),
1219 Kind::UnicodeRange => d
1220 .field("start", &format_args!("U+{:X}", self.unicode_range_start()))
1221 .field("end", &format_args!("U+{:X}", self.unicode_range_end()))
1222 .field("len", &self.len()),
1223 Kind::CdcOrCdo => d.field("is_cdc", &self.first_flag()).field("len", &self.len()),
1224 Kind::Whitespace => {
1225 d.field("contains", &self.whitespace_style());
1226 if self.whitespace_is_significant() {
1227 d.field("significant", &true);
1228 }
1229 d.field("len", &self.len())
1230 }
1231 _ => d
1232 .field("flag_0", &self.first_flag())
1233 .field("flag_1", &self.second_flag())
1234 .field("flag_2", &self.third_flag())
1235 .field("len", &self.len()),
1236 }
1237 .finish()
1238 }
1239}
1240
1241impl std::fmt::Display for Token {
1242 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1243 match self.kind() {
1244 Kind::Delim => write!(f, "Delim({})", self.char().unwrap()),
1245 k => write!(f, "{}", k.as_str()),
1246 }
1247 }
1248}
1249
1250#[cfg(feature = "serde")]
1251impl serde::ser::Serialize for Token {
1252 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1253 where
1254 S: serde::ser::Serializer,
1255 {
1256 use serde::ser::SerializeStruct;
1257 if *self == Self::EMPTY {
1258 return serializer.serialize_none();
1259 }
1260 let mut state = serializer.serialize_struct("Token", 3)?;
1261 state.serialize_field("kind", self.kind().as_str())?;
1262 state.serialize_field("len", &self.len())?;
1263 state.end()
1264 }
1265}
1266
1267impl From<Token> for Kind {
1268 fn from(token: Token) -> Self {
1269 token.kind()
1270 }
1271}
1272
1273impl PartialEq<Kind> for Token {
1274 fn eq(&self, other: &Kind) -> bool {
1275 self.kind() == *other
1276 }
1277}
1278
1279impl From<Token> for KindSet {
1280 fn from(token: Token) -> Self {
1281 KindSet::new(&[token.kind()])
1282 }
1283}
1284
1285impl PartialEq<KindSet> for Token {
1286 fn eq(&self, other: &KindSet) -> bool {
1287 other.contains(self.kind())
1288 }
1289}
1290
1291impl From<Token> for QuoteStyle {
1292 fn from(token: Token) -> Self {
1293 token.quote_style()
1294 }
1295}
1296
1297impl PartialEq<QuoteStyle> for Token {
1298 fn eq(&self, other: &QuoteStyle) -> bool {
1299 &self.quote_style() == other
1300 }
1301}
1302
1303impl From<Token> for Whitespace {
1304 fn from(token: Token) -> Self {
1305 token.whitespace_style()
1306 }
1307}
1308
1309impl PartialEq<Whitespace> for Token {
1310 fn eq(&self, other: &Whitespace) -> bool {
1311 self.whitespace_style().intersects(*other)
1312 }
1313}
1314
1315impl PartialEq<AssociatedWhitespaceRules> for Token {
1316 fn eq(&self, other: &AssociatedWhitespaceRules) -> bool {
1317 self.associated_whitespace().intersects(*other)
1318 }
1319}
1320
1321impl PartialEq<CommentStyle> for Token {
1322 fn eq(&self, other: &CommentStyle) -> bool {
1323 self.comment_style().map(|style| &style == other).unwrap_or(false)
1324 }
1325}
1326
1327impl PartialEq<PairWise> for Token {
1328 fn eq(&self, other: &PairWise) -> bool {
1329 self.to_pairwise().map(|style| &style == other).unwrap_or(false)
1330 }
1331}
1332
1333impl PartialEq<char> for Token {
1334 fn eq(&self, other: &char) -> bool {
1335 self.char().map(|char| char == *other).unwrap_or(false)
1336 }
1337}
1338
1339#[test]
1340fn size_test() {
1341 assert_eq!(::std::mem::size_of::<Token>(), 8);
1342}
1343
1344#[test]
1345fn test_new_whitespace() {
1346 assert_eq!(Token::SPACE, Kind::Whitespace);
1347 assert_eq!(Token::SPACE, Whitespace::Space);
1348 assert_eq!(Token::TAB, Kind::Whitespace);
1349 assert_eq!(Token::TAB, Whitespace::Tab);
1350 assert_eq!(Token::NEWLINE, Kind::Whitespace);
1351 assert_eq!(Token::NEWLINE, Whitespace::Newline);
1352 assert_eq!(Token::new_whitespace(Whitespace::Space, 4), Kind::Whitespace);
1353 assert_eq!(Token::new_whitespace(Whitespace::Space | Whitespace::Newline, 4), Whitespace::Space);
1354 assert_eq!(Token::new_whitespace(Whitespace::Space, 4).len(), 4);
1355 assert_eq!(Token::new_whitespace(Whitespace::Tab | Whitespace::Space, 4), Whitespace::Tab);
1356 assert_eq!(Token::new_whitespace(Whitespace::Newline, 4), Whitespace::Newline);
1357 assert_eq!(Token::new_whitespace(Whitespace::Newline, 4).len(), 4);
1358}
1359
1360#[test]
1361fn test_new_comment() {
1362 assert_eq!(Token::new_comment(CommentStyle::Block, 4), Kind::Comment);
1363 assert_eq!(Token::new_comment(CommentStyle::Block, 4), CommentStyle::Block);
1364 assert_eq!(Token::new_comment(CommentStyle::Single, 4), CommentStyle::Single);
1365}
1366
1367#[test]
1368fn test_new_number() {
1369 assert_eq!(Token::new_number(false, false, 3, 4.2), Kind::Number);
1370 assert_eq!(Token::new_number(false, false, 3, 4.2).value(), 4.2);
1371 assert_eq!(Token::new_number(false, false, 3, 4.2).len(), 3);
1372 assert_eq!(Token::new_number(false, true, 9, 4.2), Kind::Number);
1373 assert_eq!(Token::new_number(false, true, 9, 4.2).value(), 4.2);
1374 assert_eq!(Token::new_number(false, true, 9, 4.2).len(), 9);
1375 assert!(!Token::new_number(false, false, 3, 4.2).has_sign());
1376 assert!(Token::new_number(false, true, 3, 4.2).has_sign());
1377 assert!(!Token::new_number(false, true, 3, 4.0).is_float());
1378 assert!(Token::new_number(true, false, 3, 4.2).is_float());
1379}
1380
1381#[test]
1382fn test_new_string() {
1383 assert_eq!(Token::new_string(QuoteStyle::Single, false, false, 4), Kind::String);
1384 assert_eq!(Token::new_string(QuoteStyle::Single, false, false, 4), QuoteStyle::Single);
1385 assert!(!Token::new_string(QuoteStyle::Single, false, false, 4).has_close_quote());
1386 assert!(!Token::new_string(QuoteStyle::Single, false, false, 4).contains_escape_chars());
1387 assert_eq!(Token::new_string(QuoteStyle::Single, false, false, 4).len(), 4);
1388 assert_eq!(Token::new_string(QuoteStyle::Double, false, false, 4), Kind::String);
1389 assert_eq!(Token::new_string(QuoteStyle::Double, false, false, 4), QuoteStyle::Double);
1390 assert!(Token::new_string(QuoteStyle::Double, true, false, 4).has_close_quote());
1391 assert!(!Token::new_string(QuoteStyle::Double, true, false, 4).contains_escape_chars());
1392 assert_eq!(Token::new_string(QuoteStyle::Double, true, false, 5).len(), 5);
1393 assert!(Token::new_string(QuoteStyle::Double, true, true, 4).contains_escape_chars());
1394 assert!(Token::new_string(QuoteStyle::Double, false, true, 4).contains_escape_chars());
1395}
1396
1397#[test]
1398fn test_new_hash() {
1399 assert_eq!(Token::new_hash(false, false, false, 4, 0), Kind::Hash);
1400 assert!(!Token::new_hash(false, false, false, 4, 0).contains_escape_chars());
1401 assert!(Token::new_hash(false, false, true, 4, 0).contains_escape_chars());
1402 assert!(Token::new_hash(false, false, true, 4, 0).is_lower_case());
1403 assert!(!Token::new_hash(true, false, false, 4, 0).is_lower_case());
1404 assert_eq!(Token::new_hash(true, false, false, 4, 0).len(), 4);
1405 assert_eq!(Token::new_hash(true, false, false, 4, 0).hex_value(), 0);
1406 assert_eq!(Token::new_hash(true, false, false, 4, 18).hex_value(), 18);
1407}
1408
1409#[test]
1410#[should_panic]
1411fn test_new_string_with_quotes_none() {
1412 Token::new_string(QuoteStyle::None, false, true, 4);
1413}
1414
1415#[test]
1416fn test_new_delim() {
1417 assert_eq!(Token::new_delim('>'), Kind::Delim);
1418 assert_eq!(Token::new_delim('>'), '>');
1419 assert_eq!(Token::new_delim('>').len(), 1);
1420 assert_eq!(Token::new_delim('.'), Kind::Delim);
1421 assert_eq!(Token::new_delim('.'), '.');
1422 assert_eq!(Token::new_delim('.').len(), 1);
1423 assert_eq!(Token::new_delim('ℝ'), Kind::Delim);
1424 assert_eq!(Token::new_delim('ℝ'), 'ℝ');
1425 assert_eq!(Token::new_delim('ℝ').len(), 3);
1426 assert_eq!(Token::new_delim('💣'), Kind::Delim);
1427 assert_eq!(Token::new_delim('💣'), '💣');
1428 assert_eq!(Token::new_delim('💣').len(), 4);
1429 assert_eq!(Token::new_delim('💣').len(), 4);
1430 assert_eq!(Token::new_delim('💣').len(), 4);
1431}
1432
1433#[test]
1434fn with_associated_whitespace() {
1435 assert_eq!(
1436 Token::new_delim('>').with_associated_whitespace(
1437 AssociatedWhitespaceRules::EnforceBefore | AssociatedWhitespaceRules::EnforceAfter
1438 ),
1439 AssociatedWhitespaceRules::EnforceBefore | AssociatedWhitespaceRules::EnforceBefore
1440 );
1441 assert_eq!(
1442 Token::new_delim('>').with_associated_whitespace(AssociatedWhitespaceRules::BanAfter),
1443 AssociatedWhitespaceRules::BanAfter
1444 );
1445}
1446
1447#[test]
1448fn test_with_quotes() {
1449 assert_eq!(
1450 Token::new_string(QuoteStyle::Single, false, false, 4).with_quotes(QuoteStyle::Double),
1451 Token::new_string(QuoteStyle::Double, false, false, 4)
1452 );
1453 assert_eq!(
1454 Token::new_string(QuoteStyle::Double, true, true, 8).with_quotes(QuoteStyle::Single),
1455 Token::new_string(QuoteStyle::Single, true, true, 8),
1456 );
1457}
1458
1459#[test]
1460fn test_with_significant_whitespace() {
1461 for len in [1, 3, 4, 255, 256, 0xFF_FFFF, u32::MAX] {
1462 for style in [Whitespace::Space, Whitespace::Tab, Whitespace::Space | Whitespace::Newline] {
1463 let token = Token::new_whitespace(style, len);
1464 let significant = token.with_significant_whitespace(true);
1465 assert!(!token.whitespace_is_significant());
1466 assert!(significant.whitespace_is_significant());
1467 assert_eq!(token.len(), len);
1468 assert_eq!(significant.len(), len);
1469 assert_eq!(significant.whitespace_style(), style);
1470 assert_eq!(significant.kind(), Kind::Whitespace);
1471 assert_eq!(significant.with_significant_whitespace(false), token);
1472 }
1473 }
1474 let ident = Token::new_interned(Kind::Ident, 1, 3);
1475 assert_eq!(ident.with_significant_whitespace(true), ident);
1476 assert!(!ident.whitespace_is_significant());
1477}
1478
1479#[test]
1480#[should_panic]
1481fn test_with_quotes_none() {
1482 Token::new_string(QuoteStyle::Single, false, true, 4).with_quotes(QuoteStyle::None);
1483 Token::new_string(QuoteStyle::Double, false, true, 4).with_quotes(QuoteStyle::None);
1484}
1485
1486#[test]
1487fn test_new_dimension() {
1488 {
1489 let token = Token::new_dimension(false, false, 3, 3, 999.0, 0);
1490 assert_eq!(token, Kind::Dimension);
1491 assert_eq!(token.value(), 999.0);
1492 assert_eq!(token.numeric_len(), 3);
1493 assert_eq!(token.len(), 6);
1494 assert!(!token.is_float());
1495 assert!(!token.has_sign());
1496 }
1497 {
1498 let token = Token::new_dimension(false, false, 5, 2, 8191.0, 0);
1499 assert_eq!(token, Kind::Dimension);
1500 assert_eq!(token.value(), 8191.0);
1501 assert_eq!(token.numeric_len(), 5);
1502 assert_eq!(token.len(), 7);
1503 assert!(!token.is_float());
1504 assert!(!token.has_sign());
1505 }
1506 for i in -8191..8191 {
1507 let token = Token::new_dimension(false, false, 9, 3, i as f32, 0);
1508 assert_eq!(token.value(), i as f32);
1509 }
1510}
1511
1512#[test]
1513fn test_bad_bits() {
1514 let token = Token::new_dimension(false, false, 5, 2, 8191.0, 42);
1515 assert!(!token.is_bad());
1516 assert_eq!(token.kind(), Kind::Dimension);
1517 assert_eq!(Kind::from_bits(token.kind_bits()), Kind::Dimension);
1518 assert!(!token.is_bad());
1519 assert_eq!(token.len(), 7);
1520 assert_eq!(token.numeric_len(), 5);
1521 let bad_token = token.with_bad_flag();
1522 assert!(bad_token.is_bad());
1523 assert_eq!(bad_token.kind(), Kind::BadDimension);
1524 assert_eq!(Kind::from_bits(bad_token.kind_bits()), Kind::BadDimension);
1525 assert_eq!(bad_token.len(), 7);
1526 assert_eq!(bad_token.numeric_len(), 5);
1527 assert_eq!(bad_token.atom_bits(), 42);
1528
1529 let token = Token::new_delim('(');
1530 assert!(!token.is_bad());
1531 let bad_token = token.with_bad_flag();
1532 assert!(!token.is_bad());
1533 assert!(bad_token.is_bad());
1534 assert_eq!(bad_token.kind(), Kind::BadLeftParen);
1535
1536 let token = Token::new_delim('[');
1537 assert_eq!(token, Kind::LeftSquare);
1538 assert_eq!(token.with_bad_flag().kind(), Kind::BadLeftSquare);
1539}
1540
1541impl SourceTokenTrait for Token {
1542 type Kind = crate::Kind;
1543 const EMPTY: Self = Token::EMPTY;
1544
1545 fn kind(self) -> Self::Kind {
1546 Token::kind(&self)
1547 }
1548
1549 fn len(self) -> u32 {
1550 Token::len(&self)
1551 }
1552
1553 fn kind_name(self) -> &'static str {
1554 Token::kind(&self).as_str()
1555 }
1556
1557 fn leading_len(self) -> u32 {
1558 Token::leading_len(&self)
1559 }
1560
1561 fn trailing_len(self) -> u32 {
1562 Token::trailing_len(&self)
1563 }
1564}