Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(ast/estree): ESTree serializer use CodeBuffer #9331

Merged
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 Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion crates/oxc_estree/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ workspace = true
doctest = false

[dependencies]
oxc_data_structures = { workspace = true, optional = true }

itoa = { workspace = true, optional = true }
ryu-js = { workspace = true, optional = true }

[features]
default = []
serialize = ["dep:itoa", "dep:ryu-js"]
serialize = ["dep:oxc_data_structures", "dep:itoa", "dep:ryu-js"]
2 changes: 1 addition & 1 deletion crates/oxc_estree/src/serialize/blanket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl<T: ESTree> ESTree for Option<T> {
if let Some(value) = self {
value.serialize(serializer);
} else {
serializer.buffer_mut().push_str("null");
serializer.buffer_mut().print_str("null");
}
}
}
Expand Down
51 changes: 0 additions & 51 deletions crates/oxc_estree/src/serialize/buffer.rs

This file was deleted.

40 changes: 21 additions & 19 deletions crates/oxc_estree/src/serialize/formatter.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use super::Buffer;
use std::iter;

use oxc_data_structures::code_buffer::CodeBuffer;

/// Formatter trait.
pub trait Formatter {
Expand All @@ -7,18 +9,18 @@ pub trait Formatter {

/// Called before the first field of a struct or element of a sequence.
/// If the struct/sequence has no fields/elements, this is not called.
fn before_first_element(&mut self, buffer: &mut Buffer);
fn before_first_element(&mut self, buffer: &mut CodeBuffer);

/// Called before a later field of a struct or element of a sequence
/// (i.e. not the first field/element).
fn before_later_element(&mut self, buffer: &mut Buffer);
fn before_later_element(&mut self, buffer: &mut CodeBuffer);

/// Called after the key of a struct field.
fn before_field_value(&mut self, buffer: &mut Buffer);
fn before_field_value(&mut self, buffer: &mut CodeBuffer);

/// Called after the last element of a sequence / last element of a struct.
/// If the struct/sequence has no fields/elements, this is not called.
fn after_last_element(&mut self, buffer: &mut Buffer);
fn after_last_element(&mut self, buffer: &mut CodeBuffer);
}

/// Compact formatter.
Expand All @@ -36,16 +38,16 @@ impl Formatter for CompactFormatter {
}

#[inline(always)]
fn before_first_element(&mut self, _buffer: &mut Buffer) {}
fn before_first_element(&mut self, _buffer: &mut CodeBuffer) {}

#[inline(always)]
fn before_later_element(&mut self, _buffer: &mut Buffer) {}
fn before_later_element(&mut self, _buffer: &mut CodeBuffer) {}

#[inline(always)]
fn before_field_value(&mut self, _buffer: &mut Buffer) {}
fn before_field_value(&mut self, _buffer: &mut CodeBuffer) {}

#[inline(always)]
fn after_last_element(&mut self, _buffer: &mut Buffer) {}
fn after_last_element(&mut self, _buffer: &mut CodeBuffer) {}
}

/// Pretty-print formatter.
Expand Down Expand Up @@ -76,29 +78,29 @@ impl Formatter for PrettyFormatter {
Self { indent: 0 }
}

fn before_first_element(&mut self, buffer: &mut Buffer) {
self.indent += 1;
fn before_first_element(&mut self, buffer: &mut CodeBuffer) {
self.indent += 2;
self.push_new_line_and_indent(buffer);
}

fn before_later_element(&mut self, buffer: &mut Buffer) {
fn before_later_element(&mut self, buffer: &mut CodeBuffer) {
self.push_new_line_and_indent(buffer);
}

fn before_field_value(&mut self, buffer: &mut Buffer) {
buffer.push_ascii_byte(b' ');
fn before_field_value(&mut self, buffer: &mut CodeBuffer) {
buffer.print_ascii_byte(b' ');
}

fn after_last_element(&mut self, buffer: &mut Buffer) {
self.indent -= 1;
fn after_last_element(&mut self, buffer: &mut CodeBuffer) {
self.indent -= 2;
self.push_new_line_and_indent(buffer);
}
}

impl PrettyFormatter {
fn push_new_line_and_indent(&self, buffer: &mut Buffer) {
buffer.push_ascii_byte(b'\n');
fn push_new_line_and_indent(&self, buffer: &mut CodeBuffer) {
buffer.print_ascii_byte(b'\n');
// SAFETY: Spaces are ASCII
unsafe { buffer.push_bytes(&b" ".repeat(self.indent)) };
unsafe { buffer.print_bytes_iter_unchecked(iter::repeat_n(b' ', self.indent)) };
}
}
16 changes: 8 additions & 8 deletions crates/oxc_estree/src/serialize/mod.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// Methods which are trivial or just delegate to other methods are marked `#[inline(always)]`
#![expect(clippy::inline_always)]

use oxc_data_structures::code_buffer::CodeBuffer;

mod blanket;
mod buffer;
mod config;
mod formatter;
mod primitives;
mod sequences;
mod strings;
mod structs;
use buffer::Buffer;
use config::{Config, ConfigJS, ConfigTS};
use formatter::{CompactFormatter, Formatter, PrettyFormatter};
use sequences::ESTreeSequenceSerializer;
Expand Down Expand Up @@ -47,10 +47,10 @@ trait SerializerPrivate: Sized {
type Formatter: Formatter;

/// Get mutable reference to buffer.
fn buffer_mut(&mut self) -> &mut Buffer;
fn buffer_mut(&mut self) -> &mut CodeBuffer;

/// Get mutable references to buffer and formatter.
fn buffer_and_formatter_mut(&mut self) -> (&mut Buffer, &mut Self::Formatter);
fn buffer_and_formatter_mut(&mut self) -> (&mut CodeBuffer, &mut Self::Formatter);
}

/// ESTree serializer which produces compact JSON, including TypeScript fields.
Expand All @@ -67,7 +67,7 @@ pub type PrettyJSSerializer = ESTreeSerializer<ConfigJS, PrettyFormatter>;

/// ESTree serializer.
pub struct ESTreeSerializer<C: Config, F: Formatter> {
buffer: Buffer,
buffer: CodeBuffer,
formatter: F,
#[expect(unused)]
config: C,
Expand All @@ -76,7 +76,7 @@ pub struct ESTreeSerializer<C: Config, F: Formatter> {
impl<C: Config, F: Formatter> ESTreeSerializer<C, F> {
/// Create new [`ESTreeSerializer`].
pub fn new() -> Self {
Self { buffer: Buffer::new(), formatter: F::new(), config: C::new() }
Self { buffer: CodeBuffer::new(), formatter: F::new(), config: C::new() }
}

/// Consume this [`ESTreeSerializer`] and convert buffer to string.
Expand Down Expand Up @@ -114,13 +114,13 @@ impl<C: Config, F: Formatter> SerializerPrivate for &mut ESTreeSerializer<C, F>

/// Get mutable reference to buffer.
#[inline(always)]
fn buffer_mut(&mut self) -> &mut Buffer {
fn buffer_mut(&mut self) -> &mut CodeBuffer {
&mut self.buffer
}

/// Get mutable references to buffer and formatter.
#[inline(always)]
fn buffer_and_formatter_mut(&mut self) -> (&mut Buffer, &mut F) {
fn buffer_and_formatter_mut(&mut self) -> (&mut CodeBuffer, &mut F) {
(&mut self.buffer, &mut self.formatter)
}
}
14 changes: 7 additions & 7 deletions crates/oxc_estree/src/serialize/primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use super::{ESTree, Serializer};
/// [`ESTree`] implementation for `bool`.
impl ESTree for bool {
fn serialize<S: Serializer>(&self, mut serializer: S) {
serializer.buffer_mut().push_str(if *self { "true" } else { "false" });
serializer.buffer_mut().print_str(if *self { "true" } else { "false" });
}
}

Expand All @@ -18,17 +18,17 @@ macro_rules! impl_float {
if self.is_finite() {
let mut buffer = RyuBuffer::new();
let s = buffer.format_finite(*self);
serializer.buffer_mut().push_str(s);
serializer.buffer_mut().print_str(s);
} else if self.is_nan() {
// Serialize `NAN` as `null`
// TODO: Throw an error? Use a sentinel value?
serializer.buffer_mut().push_str("null");
serializer.buffer_mut().print_str("null");
} else if *self == $ty::INFINITY {
// Serialize `INFINITY` as `1e+400. `JSON.parse` deserializes this as `Infinity`.
serializer.buffer_mut().push_str("1e+400");
serializer.buffer_mut().print_str("1e+400");
} else {
// Serialize `-INFINITY` as `-1e+400`. `JSON.parse` deserializes this as `-Infinity`.
serializer.buffer_mut().push_str("-1e+400");
serializer.buffer_mut().print_str("-1e+400");
}
}
}
Expand All @@ -45,7 +45,7 @@ macro_rules! impl_integer {
fn serialize<S: Serializer>(&self, mut serializer: S) {
let mut buffer = ItoaBuffer::new();
let s = buffer.format(*self);
serializer.buffer_mut().push_str(s);
serializer.buffer_mut().print_str(s);
}
}
};
Expand All @@ -67,7 +67,7 @@ impl_integer!(isize);
/// [`ESTree`] implementation for `()`.
impl ESTree for () {
fn serialize<S: Serializer>(&self, mut serializer: S) {
serializer.buffer_mut().push_str("null");
serializer.buffer_mut().print_str("null");
}
}

Expand Down
6 changes: 3 additions & 3 deletions crates/oxc_estree/src/serialize/sequences.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub struct ESTreeSequenceSerializer<'s, C: Config, F: Formatter> {
impl<'s, C: Config, F: Formatter> ESTreeSequenceSerializer<'s, C, F> {
/// Create new [`ESTreeSequenceSerializer`].
pub(super) fn new(mut serializer: &'s mut ESTreeSerializer<C, F>) -> Self {
serializer.buffer_mut().push_ascii_byte(b'[');
serializer.buffer_mut().print_ascii_byte(b'[');
Self { serializer, state: SequenceState::Empty }
}
}
Expand All @@ -36,7 +36,7 @@ impl<C: Config, F: Formatter> SequenceSerializer for ESTreeSequenceSerializer<'_
self.state = SequenceState::HasEntries;
formatter.before_first_element(buffer);
} else {
buffer.push_ascii_byte(b',');
buffer.print_ascii_byte(b',');
formatter.before_later_element(buffer);
}

Expand All @@ -49,7 +49,7 @@ impl<C: Config, F: Formatter> SequenceSerializer for ESTreeSequenceSerializer<'_
if self.state == SequenceState::HasEntries {
formatter.after_last_element(buffer);
}
buffer.push_ascii_byte(b']');
buffer.print_ascii_byte(b']');
}
}

Expand Down
22 changes: 12 additions & 10 deletions crates/oxc_estree/src/serialize/strings.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use super::{Buffer, ESTree, Serializer};
use oxc_data_structures::code_buffer::CodeBuffer;

use super::{ESTree, Serializer};

/// [`ESTree`] implementation for string slice.
impl ESTree for str {
Expand Down Expand Up @@ -62,8 +64,8 @@ static ESCAPE: [Escape; 256] = {
/// Write string to buffer.
/// String is wrapped in `"`s, and with any characters which are not valid in JSON escaped.
#[inline(always)]
fn write_str(s: &str, buffer: &mut Buffer) {
buffer.push_ascii_byte(b'"');
fn write_str(s: &str, buffer: &mut CodeBuffer) {
buffer.print_ascii_byte(b'"');

let bytes = s.as_bytes();

Expand All @@ -80,7 +82,7 @@ fn write_str(s: &str, buffer: &mut Buffer) {
// Therefore current `index` must mark the end of a valid UTF8 character sequence.
// `start` is either the start of string, or after an ASCII character,
// therefore always the start of a valid UTF8 character sequence.
unsafe { buffer.push_bytes(&bytes[start..index]) };
unsafe { buffer.print_bytes_unchecked(&bytes[start..index]) };
}

write_char_escape(escape, byte, buffer);
Expand All @@ -92,18 +94,18 @@ fn write_str(s: &str, buffer: &mut Buffer) {
// SAFETY: `bytes` is derived from a `&str`.
// `start` is either the start of string, or after an ASCII character,
// therefore always the start of a valid UTF8 character sequence.
unsafe { buffer.push_bytes(&bytes[start..]) };
unsafe { buffer.print_bytes_unchecked(&bytes[start..]) };
}

buffer.push_ascii_byte(b'"');
buffer.print_ascii_byte(b'"');
}

fn write_char_escape(escape: Escape, byte: u8, buffer: &mut Buffer) {
fn write_char_escape(escape: Escape, byte: u8, buffer: &mut CodeBuffer) {
#[expect(clippy::if_not_else)]
if escape != Escape::UU {
buffer.push_ascii_byte(b'\\');
buffer.print_ascii_byte(b'\\');
// SAFETY: All values of `Escape` are ASCII
unsafe { buffer.push_ascii_byte_unchecked(escape as u8) };
unsafe { buffer.print_byte_unchecked(escape as u8) };
} else {
static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";
let bytes = [
Expand All @@ -115,7 +117,7 @@ fn write_char_escape(escape: Escape, byte: u8, buffer: &mut Buffer) {
HEX_DIGITS[(byte & 0xF) as usize],
];
// SAFETY: `bytes` contains only ASCII bytes
unsafe { buffer.push_bytes(&bytes) }
unsafe { buffer.print_bytes_unchecked(&bytes) }
}
}

Expand Down
Loading
Loading