Skip to content
This repository has been archived by the owner on Aug 31, 2023. It is now read-only.

Commit

Permalink
refactor(formatter): Introduce write, format, and format_args m…
Browse files Browse the repository at this point in the history
…acros (#2634)

> tldr: Rust's allocation free `format`, `write` and `format_args` for Rome formatter!

First of all, I'm sorry for this massive PR. 

The motivation behind this PR is to change our formatting to work from left to right. Something that may become useful when formatting comments. The current formatter formats the most-grouped elements first rather than left to right, mainly because all IR elements like `group_elements`, `fill` etc. accept a `FormatElement` as a parameter and not a `Format` implementation. The other motivation behind this PR is to make all formatting macros allocation free compared to the current `formatted!` and `format_elements` macro that requires at least one allocation (except the compiler optimises it away). 

This PR enforces left to right formatting by changing Format's signature from:

```rust
fn format(&self, f: &Formatter<JsFormatOptions>) -> FormatResult<FormatElement>
```

to 

```rust
fn format(&self, f: &mut Formatter<JsFormatOptions>) -> FormatResult()
```

The main change is that `format` no longer returns the formatted result but instead writes it into the `Formatter`, similar to Rust's `Debug` and `Display` trait with the `write` and `format_args` macros. The fact that `format` now writes to a shared `FormatElement` buffer enforces format rules to write the element in order or they will appear out of order in the output. 

Second, the PR changes all builders (`group_elements`, `fill`, `space`) to return objects that implement `Format` rather than `FormatElement`s directly. This decouples the formatting DSL from the IR used by the printer. The other added benefit is that builders that accept some inner content no longer accept `FormatElement` but instead accept any object implementing `Format`. This should remove the need for many `formatted!` calls that were necessary just because some helper needs a `FormatElement` because it's the least common denominator. 

OK, but how do I write my formatting logic now: 


```rust
impl FormatNodeFields<JsFunctionBody> for FormatNodeRule<JsFunctionBody> {
    fn format_fields(
        node: &JsFunctionBody,
        f: &mut Formatter<JsFormatOptions>,
    ) -> FormatResult<()> {
        let JsFunctionBodyFields {
            l_curly_token,
            directives,
            statements,
            r_curly_token,
        } = node.as_fields();

        let format_statements = format_with(|f| {
            let mut join = f.join_nodes_with_hardline();

            for stmt in &statements {
                join.entry(stmt.syntax(), &stmt.format_or_verbatim());
            }

            join.finish()
        });

        write!(
            f,
            [f.delimited(
                &l_curly_token?,
                &format_args![directives.format(), format_statements],
                &r_curly_token?,
            )
            .block_indent()]
        )
    }
}
```

The main differences are 

* You call `write!(f, [])` instead of `formatted` if you want to write something to the document. 
* You use `format_args` if you want to pass multiple `Format` objects to a helper like `group_elements`. 
* The formatter now exposes helpers like `f.join()`, `f.join_with()`, `f.fill()` and `f.join_nodes_with_softline_break` etc to format a sequence of objects

Part of #2571
  • Loading branch information
MichaReiser committed Jun 6, 2022
1 parent e9a80a5 commit 941faf9
Show file tree
Hide file tree
Showing 424 changed files with 8,482 additions and 8,249 deletions.
154 changes: 154 additions & 0 deletions crates/rome_formatter/src/arguments.rs
@@ -0,0 +1,154 @@
use super::{Buffer, Format, Formatter};
use crate::FormatResult;
use std::ffi::c_void;
use std::marker::PhantomData;

/// Mono-morphed type to format an object. Used by the [rome_formatter::format], [rome_formatter::format_args], and
/// [rome_formatter::write] macros.
///
/// This struct is similar to a dynamic dispatch (using `dyn Format`) because it stores a pointer to the value.
/// However, it doesn't store the pointer to `dyn Format`'s vtable, instead it statically resolves the function
/// pointer of `Format::format` and stores it in `formatter`.
pub struct Argument<'fmt, Context> {
/// The value to format stored as a raw pointer where `lifetime` stores the value's lifetime.
value: *const c_void,

/// Stores the lifetime of the value. To get the most out of our dear borrow checker.
lifetime: PhantomData<&'fmt ()>,

/// The function pointer to `value`'s `Format::format` method
formatter: fn(*const c_void, &mut Formatter<'_, Context>) -> FormatResult<()>,
}

impl<Context> Clone for Argument<'_, Context> {
fn clone(&self) -> Self {
*self
}
}
impl<Context> Copy for Argument<'_, Context> {}

impl<'fmt, Context> Argument<'fmt, Context> {
/// Called by the [rome_formatter::format_args] macro. Creates a mono-morphed value for formatting
/// an object.
#[doc(hidden)]
#[inline]
pub fn new<F: Format<Context>>(value: &'fmt F) -> Self {
fn formatter<F: Format<Context>, Context>(
ptr: *const c_void,
fmt: &mut Formatter<Context>,
) -> FormatResult<()> {
// SAFETY: Safe because the 'fmt lifetime is captured by the 'lifetime' field.
F::fmt(unsafe { &*(ptr as *const F) }, fmt)
}

Self {
value: value as *const F as *const c_void,
lifetime: PhantomData,
formatter: formatter::<F, Context>,
}
}

/// Formats the value stored by this argument using the given formatter.
#[inline]
pub(super) fn format(&self, f: &mut Formatter<Context>) -> FormatResult<()> {
(self.formatter)(self.value, f)
}
}

/// Sequence of objects that should be formatted in the specified order.
///
/// The [`format_args!`] macro will safely create an instance of this structure.
///
/// You can use the `Arguments<a>` that [`format_args!]` return in `Format` context as seen below.
/// It will call the `format` function for every of it's objects.
///
/// ```rust
/// use rome_formatter::prelude::*;
/// use rome_formatter::{format, format_args};
///
/// let formatted = format!(SimpleFormatContext::default(), [
/// format_args!(token("a"), space_token(), token("b"))
/// ]).unwrap();
///
/// assert_eq!("a b", formatted.print().as_code());
/// ```
pub struct Arguments<'fmt, Context>(pub &'fmt [Argument<'fmt, Context>]);

impl<'fmt, Context> Arguments<'fmt, Context> {
#[doc(hidden)]
#[inline]
pub fn new(arguments: &'fmt [Argument<'fmt, Context>]) -> Self {
Self(arguments)
}

/// Returns the arguments
#[inline]
pub(super) fn items(&self) -> &'fmt [Argument<'fmt, Context>] {
self.0
}
}

impl<Context> Copy for Arguments<'_, Context> {}

impl<Context> Clone for Arguments<'_, Context> {
fn clone(&self) -> Self {
Self(self.0)
}
}

impl<Context> Format<Context> for Arguments<'_, Context> {
#[inline]
fn fmt(&self, formatter: &mut Formatter<Context>) -> FormatResult<()> {
formatter.write_fmt(*self)
}
}

impl<Context> std::fmt::Debug for Arguments<'_, Context> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Arguments[...]")
}
}

impl<'fmt, Context> From<&'fmt Argument<'fmt, Context>> for Arguments<'fmt, Context> {
fn from(argument: &'fmt Argument<'fmt, Context>) -> Self {
Arguments::new(std::slice::from_ref(argument))
}
}

#[cfg(test)]
mod tests {
use crate::prelude::*;
use crate::{format_args, write, FormatState, VecBuffer};

#[test]
fn test_nesting() {
let mut context = FormatState::new(());
let mut buffer = VecBuffer::new(&mut context);

write!(
&mut buffer,
[
token("function"),
space_token(),
token("a"),
space_token(),
group_elements(&format_args!(token("("), token(")")))
]
)
.unwrap();

assert_eq!(
buffer.into_element(),
FormatElement::List(List::new(vec![
FormatElement::Token(Token::Static { text: "function" }),
FormatElement::Space,
FormatElement::Token(Token::Static { text: "a" }),
FormatElement::Space,
FormatElement::Group(Group::new(FormatElement::List(List::new(vec![
FormatElement::Token(Token::Static { text: "(" }),
FormatElement::Token(Token::Static { text: ")" }),
]))))
]))
);
}
}

0 comments on commit 941faf9

Please sign in to comment.