Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix infinite loop when using `@variant` inside `@custom-variant` ([#19633](https://github.com/tailwindlabs/tailwindcss/pull/19633))
- Allow multiples of `.25` in `aspect-*` fractions ([#19688](https://github.com/tailwindlabs/tailwindcss/pull/19688))
- Ensure changes to external files listed via `@source` trigger a full page reload when using `@tailwindcss/vite` ([#19670](https://github.com/tailwindlabs/tailwindcss/pull/19670))
- Improve performance Oxide scanner in bigger projects ([#19632](https://github.com/tailwindlabs/tailwindcss/pull/19632))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix grammar for clarity in changelog entry.

Wording is a bit off; consider “Improve performance of the Oxide scanner in bigger projects” for readability.

✏️ Suggested tweak
-- Improve performance Oxide scanner in bigger projects ([`#19632`](https://github.com/tailwindlabs/tailwindcss/pull/19632))
+- Improve performance of the Oxide scanner in bigger projects ([`#19632`](https://github.com/tailwindlabs/tailwindcss/pull/19632))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- Improve performance Oxide scanner in bigger projects ([#19632](https://github.com/tailwindlabs/tailwindcss/pull/19632))
- Improve performance of the Oxide scanner in bigger projects ([`#19632`](https://github.com/tailwindlabs/tailwindcss/pull/19632))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGELOG.md` at line 31, Update the changelog entry string "Improve
performance Oxide scanner in bigger projects
([`#19632`](https://github.com/tailwindlabs/tailwindcss/pull/19632))" to read
"Improve performance of the Oxide scanner in bigger projects
([`#19632`](https://github.com/tailwindlabs/tailwindcss/pull/19632))" to fix the
grammar; locate and edit the exact line containing that phrase in CHANGELOG.md.


### Deprecated

Expand Down
130 changes: 52 additions & 78 deletions crates/oxide/src/cursor.rs
Original file line number Diff line number Diff line change
@@ -1,44 +1,49 @@
use std::{ascii::escape_default, fmt::Display};

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy)]
pub struct Cursor<'a> {
// The input we're scanning
pub input: &'a [u8],

// The location of the cursor in the input
pub pos: usize,

/// Is the cursor at the start of the input
pub at_start: bool,

/// Is the cursor at the end of the input
pub at_end: bool,

/// The previously consumed character
/// If `at_start` is true, this will be NUL
pub prev: u8,

/// The current character
pub curr: u8,

/// The upcoming character (if any)
/// If `at_end` is true, this will be NUL
pub next: u8,
}

impl<'a> Cursor<'a> {
#[inline(always)]
pub fn new(input: &'a [u8]) -> Self {
let mut cursor = Self {
input,
pos: 0,
at_start: true,
at_end: false,
prev: 0x00,
curr: 0x00,
next: 0x00,
};
cursor.move_to(0);
cursor
Self { input, pos: 0 }
}

/// The current byte at `pos`, or 0x00 if past the end.
#[inline(always)]
pub fn curr(&self) -> u8 {
if self.pos < self.input.len() {
unsafe { *self.input.get_unchecked(self.pos) }
} else {
0x00
}
}

/// The next byte at `pos + 1`, or 0x00 if past the end.
#[inline(always)]
pub fn next(&self) -> u8 {
let next_pos = self.pos + 1;
if next_pos < self.input.len() {
unsafe { *self.input.get_unchecked(next_pos) }
} else {
0x00
}
}

/// The previous byte at `pos - 1`, or 0x00 if at the start.
#[inline(always)]
pub fn prev(&self) -> u8 {
if self.pos > 0 {
unsafe { *self.input.get_unchecked(self.pos - 1) }
} else {
0x00
}
}

pub fn advance_by(&mut self, amount: usize) {
Expand All @@ -48,38 +53,15 @@ impl<'a> Cursor<'a> {
#[inline(always)]
pub fn advance(&mut self) {
self.pos += 1;

self.prev = self.curr;
self.curr = self.next;
self.next = *self
.input
.get(self.pos.saturating_add(1))
.unwrap_or(&0x00u8);
}

#[inline(always)]
pub fn advance_twice(&mut self) {
self.pos += 2;

self.prev = self.next;
self.curr = *self.input.get(self.pos).unwrap_or(&0x00u8);
self.next = *self
.input
.get(self.pos.saturating_add(1))
.unwrap_or(&0x00u8);
}

pub fn move_to(&mut self, pos: usize) {
let len = self.input.len();
let pos = pos.clamp(0, len);

self.pos = pos;
self.at_start = pos == 0;
self.at_end = pos + 1 >= len;

self.prev = *self.input.get(pos.wrapping_sub(1)).unwrap_or(&0x00u8);
self.curr = *self.input.get(pos).unwrap_or(&0x00u8);
self.next = *self.input.get(pos.saturating_add(1)).unwrap_or(&0x00u8);
self.pos = pos.min(self.input.len());
}
}

Expand All @@ -90,9 +72,9 @@ impl Display for Cursor<'_> {
let pos = format!("{: >len_count$}", self.pos, len_count = len.len());
write!(f, "{}/{} ", pos, len)?;

if self.at_start {
if self.pos == 0 {
write!(f, "S ")?;
} else if self.at_end {
} else if self.pos + 1 >= self.input.len() {
write!(f, "E ")?;
} else {
write!(f, "M ")?;
Expand All @@ -109,9 +91,9 @@ impl Display for Cursor<'_> {
write!(
f,
"[{} {} {}]",
to_str(self.prev),
to_str(self.curr),
to_str(self.next)
to_str(self.prev()),
to_str(self.curr()),
to_str(self.next())
)
}
}
Expand All @@ -125,36 +107,28 @@ mod test {
fn test_cursor() {
let mut cursor = Cursor::new(b"hello world");
assert_eq!(cursor.pos, 0);
assert!(cursor.at_start);
assert!(!cursor.at_end);
assert_eq!(cursor.prev, 0x00);
assert_eq!(cursor.curr, b'h');
assert_eq!(cursor.next, b'e');
assert_eq!(cursor.prev(), 0x00);
assert_eq!(cursor.curr(), b'h');
assert_eq!(cursor.next(), b'e');

cursor.advance_by(1);
assert_eq!(cursor.pos, 1);
assert!(!cursor.at_start);
assert!(!cursor.at_end);
assert_eq!(cursor.prev, b'h');
assert_eq!(cursor.curr, b'e');
assert_eq!(cursor.next, b'l');
assert_eq!(cursor.prev(), b'h');
assert_eq!(cursor.curr(), b'e');
assert_eq!(cursor.next(), b'l');

// Advancing too far should stop at the end
cursor.advance_by(10);
assert_eq!(cursor.pos, 11);
assert!(!cursor.at_start);
assert!(cursor.at_end);
assert_eq!(cursor.prev, b'd');
assert_eq!(cursor.curr, 0x00);
assert_eq!(cursor.next, 0x00);
assert_eq!(cursor.prev(), b'd');
assert_eq!(cursor.curr(), 0x00);
assert_eq!(cursor.next(), 0x00);

// Can't advance past the end
cursor.advance_by(1);
assert_eq!(cursor.pos, 11);
assert!(!cursor.at_start);
assert!(cursor.at_end);
assert_eq!(cursor.prev, b'd');
assert_eq!(cursor.curr, 0x00);
assert_eq!(cursor.next, 0x00);
assert_eq!(cursor.prev(), b'd');
assert_eq!(cursor.curr(), 0x00);
assert_eq!(cursor.next(), 0x00);
}
}
18 changes: 9 additions & 9 deletions crates/oxide/src/extractor/arbitrary_property_machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ impl Machine for ArbitraryPropertyMachine<IdleState> {

#[inline]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
match cursor.curr.into() {
match cursor.curr().into() {
// Start of an arbitrary property
Class::OpenBracket => {
self.start_pos = cursor.pos;
Expand All @@ -97,8 +97,8 @@ impl Machine for ArbitraryPropertyMachine<ParsingPropertyState> {
let len = cursor.input.len();

while cursor.pos < len {
match cursor.curr.into() {
Class::Dash => match cursor.next.into() {
match cursor.curr().into() {
Class::Dash => match cursor.next().into() {
// Start of a CSS variable
//
// E.g.: `[--my-color:red]`
Expand Down Expand Up @@ -137,7 +137,7 @@ impl ArbitraryPropertyMachine<ParsingPropertyState> {
fn parse_property_variable(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
match self.css_variable_machine.next(cursor) {
MachineState::Idle => self.restart(),
MachineState::Done(_) => match cursor.next.into() {
MachineState::Done(_) => match cursor.next().into() {
// End of the CSS variable, must be followed by a `:`
//
// E.g.: `[--my-color:red]`
Expand Down Expand Up @@ -165,8 +165,8 @@ impl Machine for ArbitraryPropertyMachine<ParsingValueState> {
let len = cursor.input.len();
let start_of_value_pos = cursor.pos;
while cursor.pos < len {
match cursor.curr.into() {
Class::Escape => match cursor.next.into() {
match cursor.curr().into() {
Class::Escape => match cursor.next().into() {
// An escaped whitespace character is not allowed
//
// E.g.: `[color:var(--my-\ color)]`
Expand All @@ -181,7 +181,7 @@ impl Machine for ArbitraryPropertyMachine<ParsingValueState> {
},

Class::OpenParen | Class::OpenBracket | Class::OpenCurly => {
if !self.bracket_stack.push(cursor.curr) {
if !self.bracket_stack.push(cursor.curr()) {
return self.restart();
}
cursor.advance();
Expand All @@ -190,7 +190,7 @@ impl Machine for ArbitraryPropertyMachine<ParsingValueState> {
Class::CloseParen | Class::CloseBracket | Class::CloseCurly
if !self.bracket_stack.is_empty() =>
{
if !self.bracket_stack.pop(cursor.curr) {
if !self.bracket_stack.pop(cursor.curr()) {
return self.restart();
}
cursor.advance();
Expand Down Expand Up @@ -227,7 +227,7 @@ impl Machine for ArbitraryPropertyMachine<ParsingValueState> {
Class::Slash if start_of_value_pos == cursor.pos => return self.restart(),

// String interpolation-like syntax is not allowed. E.g.: `[${x}]`
Class::Dollar if matches!(cursor.next.into(), Class::OpenCurly) => {
Class::Dollar if matches!(cursor.next().into(), Class::OpenCurly) => {
return self.restart()
}

Expand Down
12 changes: 6 additions & 6 deletions crates/oxide/src/extractor/arbitrary_value_machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ impl Machine for ArbitraryValueMachine {
#[inline]
fn next(&mut self, cursor: &mut cursor::Cursor<'_>) -> MachineState {
// An arbitrary value must start with an open bracket
if Class::OpenBracket != cursor.curr.into() {
if Class::OpenBracket != cursor.curr().into() {
return MachineState::Idle;
}

Expand All @@ -42,8 +42,8 @@ impl Machine for ArbitraryValueMachine {
let len = cursor.input.len();

while cursor.pos < len {
match cursor.curr.into() {
Class::Escape => match cursor.next.into() {
match cursor.curr().into() {
Class::Escape => match cursor.next().into() {
// An escaped whitespace character is not allowed
//
// E.g.: `[color:var(--my-\ color)]`
Expand All @@ -61,7 +61,7 @@ impl Machine for ArbitraryValueMachine {
},

Class::OpenParen | Class::OpenBracket | Class::OpenCurly => {
if !self.bracket_stack.push(cursor.curr) {
if !self.bracket_stack.push(cursor.curr()) {
return self.restart();
}
cursor.advance();
Expand All @@ -70,7 +70,7 @@ impl Machine for ArbitraryValueMachine {
Class::CloseParen | Class::CloseBracket | Class::CloseCurly
if !self.bracket_stack.is_empty() =>
{
if !self.bracket_stack.pop(cursor.curr) {
if !self.bracket_stack.pop(cursor.curr()) {
return self.restart();
}
cursor.advance();
Expand All @@ -96,7 +96,7 @@ impl Machine for ArbitraryValueMachine {
Class::Whitespace => return self.restart(),

// String interpolation-like syntax is not allowed. E.g.: `[${x}]`
Class::Dollar if matches!(cursor.next.into(), Class::OpenCurly) => {
Class::Dollar if matches!(cursor.next().into(), Class::OpenCurly) => {
return self.restart()
}

Expand Down
Loading