Skip to content

Commit

Permalink
perf(ast/estree): ESTree serializer use CodeBuffer
Browse files Browse the repository at this point in the history
  • Loading branch information
overlookmotel committed Feb 24, 2025
1 parent d69b85c commit 3b06cc3
Show file tree
Hide file tree
Showing 10 changed files with 67 additions and 109 deletions.
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

0 comments on commit 3b06cc3

Please sign in to comment.