mirror of
https://github.com/roc-lang/roc.git
synced 2025-11-25 13:36:37 +00:00
moved all crates into seperate folder + related path fixes
This commit is contained in:
parent
12ef03bb86
commit
eee85fa45d
1063 changed files with 92 additions and 93 deletions
601
crates/compiler/fmt/src/annotation.rs
Normal file
601
crates/compiler/fmt/src/annotation.rs
Normal file
|
|
@ -0,0 +1,601 @@
|
|||
use crate::{
|
||||
collection::{fmt_collection, Braces},
|
||||
spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT},
|
||||
Buf,
|
||||
};
|
||||
use roc_parse::ast::{
|
||||
AssignedField, Collection, Derived, Expr, ExtractSpaces, HasClause, Tag, TypeAnnotation,
|
||||
TypeHeader,
|
||||
};
|
||||
use roc_parse::ident::UppercaseIdent;
|
||||
use roc_region::all::Loc;
|
||||
|
||||
/// Does an AST node need parens around it?
|
||||
///
|
||||
/// Usually not, but there are two cases where it may be required
|
||||
///
|
||||
/// 1. In a function type, function types are in parens
|
||||
///
|
||||
/// a -> b, c -> d
|
||||
/// (a -> b), c -> d
|
||||
///
|
||||
/// 2. In applications, applications are in brackets
|
||||
/// This is true in patterns, type annotations and expressions
|
||||
///
|
||||
/// Just (Just a)
|
||||
/// List (List a)
|
||||
/// reverse (reverse l)
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum Parens {
|
||||
NotNeeded,
|
||||
InFunctionType,
|
||||
InApply,
|
||||
}
|
||||
|
||||
/// In an AST node, do we show newlines around it
|
||||
///
|
||||
/// Sometimes, we only want to show comments, at other times
|
||||
/// we also want to show newlines. By default the formatter
|
||||
/// takes care of inserting newlines, but sometimes the user's
|
||||
/// newlines are taken into account.
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum Newlines {
|
||||
No,
|
||||
Yes,
|
||||
}
|
||||
|
||||
impl Newlines {
|
||||
pub fn from_bool(yes: bool) -> Self {
|
||||
if yes {
|
||||
Self::Yes
|
||||
} else {
|
||||
Self::No
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Formattable {
|
||||
fn is_multiline(&self) -> bool;
|
||||
|
||||
fn format_with_options<'buf>(
|
||||
&self,
|
||||
buf: &mut Buf<'buf>,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
self.format(buf, indent);
|
||||
}
|
||||
|
||||
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
|
||||
self.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
|
||||
}
|
||||
}
|
||||
|
||||
/// A reference to a formattable value is also formattable
|
||||
impl<'a, T> Formattable for &'a T
|
||||
where
|
||||
T: Formattable,
|
||||
{
|
||||
fn is_multiline(&self) -> bool {
|
||||
(*self).is_multiline()
|
||||
}
|
||||
|
||||
fn format_with_options<'buf>(
|
||||
&self,
|
||||
buf: &mut Buf<'buf>,
|
||||
parens: Parens,
|
||||
newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
(*self).format_with_options(buf, parens, newlines, indent)
|
||||
}
|
||||
|
||||
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
|
||||
(*self).format(buf, indent)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Formattable for Collection<'a, T>
|
||||
where
|
||||
T: Formattable,
|
||||
{
|
||||
fn is_multiline(&self) -> bool {
|
||||
// if there are any comments, they must go on their own line
|
||||
// because otherwise they'd comment out the closing delimiter
|
||||
!self.final_comments().is_empty() ||
|
||||
// if any of the items in the collection are multiline,
|
||||
// then the whole collection must be multiline
|
||||
self.items.iter().any(Formattable::is_multiline)
|
||||
}
|
||||
}
|
||||
|
||||
/// A Located formattable value is also formattable
|
||||
impl<T> Formattable for Loc<T>
|
||||
where
|
||||
T: Formattable,
|
||||
{
|
||||
fn is_multiline(&self) -> bool {
|
||||
self.value.is_multiline()
|
||||
}
|
||||
|
||||
fn format_with_options<'buf>(
|
||||
&self,
|
||||
buf: &mut Buf<'buf>,
|
||||
parens: Parens,
|
||||
newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
self.value
|
||||
.format_with_options(buf, parens, newlines, indent)
|
||||
}
|
||||
|
||||
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
|
||||
self.value.format(buf, indent)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for UppercaseIdent<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn format_with_options<'buf>(
|
||||
&self,
|
||||
buf: &mut Buf<'buf>,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
_indent: u16,
|
||||
) {
|
||||
buf.push_str((*self).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for TypeAnnotation<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
use roc_parse::ast::TypeAnnotation::*;
|
||||
|
||||
match self {
|
||||
// Return whether these spaces contain any Newlines
|
||||
SpaceBefore(_, spaces) | SpaceAfter(_, spaces) => {
|
||||
debug_assert!(!spaces.is_empty());
|
||||
|
||||
// "spaces" always contain either a newline or comment, and comments have newlines
|
||||
true
|
||||
}
|
||||
|
||||
Wildcard | Inferred | BoundVariable(_) | Malformed(_) => false,
|
||||
Function(args, result) => {
|
||||
(&result.value).is_multiline()
|
||||
|| args.iter().any(|loc_arg| (&loc_arg.value).is_multiline())
|
||||
}
|
||||
Apply(_, _, args) => args.iter().any(|loc_arg| loc_arg.value.is_multiline()),
|
||||
As(lhs, _, _) => lhs.value.is_multiline(),
|
||||
|
||||
Where(annot, has_clauses) => {
|
||||
annot.is_multiline() || has_clauses.iter().any(|has| has.is_multiline())
|
||||
}
|
||||
|
||||
Record { fields, ext } => {
|
||||
match ext {
|
||||
Some(ann) if ann.value.is_multiline() => return true,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
fields.items.iter().any(|field| field.value.is_multiline())
|
||||
}
|
||||
|
||||
TagUnion { tags, ext } => {
|
||||
match ext {
|
||||
Some(ann) if ann.value.is_multiline() => return true,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
tags.iter().any(|tag| tag.value.is_multiline())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_with_options<'buf>(
|
||||
&self,
|
||||
buf: &mut Buf<'buf>,
|
||||
parens: Parens,
|
||||
newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
use roc_parse::ast::TypeAnnotation::*;
|
||||
|
||||
match self {
|
||||
Function(arguments, result) => {
|
||||
let write_parens = parens != Parens::NotNeeded;
|
||||
|
||||
if write_parens {
|
||||
buf.push('(')
|
||||
}
|
||||
|
||||
let mut it = arguments.iter().enumerate().peekable();
|
||||
let should_add_newlines = newlines == Newlines::Yes;
|
||||
|
||||
while let Some((index, argument)) = it.next() {
|
||||
let is_first = index == 0;
|
||||
let is_multiline = &argument.value.is_multiline();
|
||||
|
||||
if !is_first && !is_multiline && should_add_newlines {
|
||||
buf.newline();
|
||||
}
|
||||
|
||||
(&argument.value).format_with_options(
|
||||
buf,
|
||||
Parens::InFunctionType,
|
||||
Newlines::No,
|
||||
indent,
|
||||
);
|
||||
|
||||
if it.peek().is_some() {
|
||||
buf.push_str(",");
|
||||
if !should_add_newlines {
|
||||
buf.spaces(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if should_add_newlines {
|
||||
buf.newline();
|
||||
buf.indent(indent);
|
||||
} else {
|
||||
buf.spaces(1);
|
||||
}
|
||||
|
||||
buf.push_str("->");
|
||||
buf.spaces(1);
|
||||
|
||||
(&result.value).format_with_options(
|
||||
buf,
|
||||
Parens::InFunctionType,
|
||||
Newlines::No,
|
||||
indent,
|
||||
);
|
||||
|
||||
if write_parens {
|
||||
buf.push(')')
|
||||
}
|
||||
}
|
||||
Apply(pkg, name, arguments) => {
|
||||
buf.indent(indent);
|
||||
// NOTE apply is never multiline
|
||||
let write_parens = parens == Parens::InApply && !arguments.is_empty();
|
||||
|
||||
if write_parens {
|
||||
buf.push('(')
|
||||
}
|
||||
|
||||
if !pkg.is_empty() {
|
||||
buf.push_str(pkg);
|
||||
buf.push('.');
|
||||
}
|
||||
|
||||
buf.push_str(name);
|
||||
|
||||
for argument in *arguments {
|
||||
buf.spaces(1);
|
||||
(&argument.value).format_with_options(
|
||||
buf,
|
||||
Parens::InApply,
|
||||
Newlines::No,
|
||||
indent,
|
||||
);
|
||||
}
|
||||
|
||||
if write_parens {
|
||||
buf.push(')')
|
||||
}
|
||||
}
|
||||
BoundVariable(v) => buf.push_str(v),
|
||||
Wildcard => buf.push('*'),
|
||||
Inferred => buf.push('_'),
|
||||
|
||||
TagUnion { tags, ext } => {
|
||||
fmt_collection(buf, indent, Braces::Square, *tags, newlines);
|
||||
|
||||
if let Some(loc_ext_ann) = *ext {
|
||||
loc_ext_ann.value.format(buf, indent);
|
||||
}
|
||||
}
|
||||
|
||||
Record { fields, ext } => {
|
||||
fmt_collection(buf, indent, Braces::Curly, *fields, newlines);
|
||||
|
||||
if let Some(loc_ext_ann) = *ext {
|
||||
loc_ext_ann.value.format(buf, indent);
|
||||
}
|
||||
}
|
||||
|
||||
As(lhs, _spaces, TypeHeader { name, vars }) => {
|
||||
// TODO use _spaces?
|
||||
lhs.value
|
||||
.format_with_options(buf, Parens::InFunctionType, Newlines::No, indent);
|
||||
buf.spaces(1);
|
||||
buf.push_str("as");
|
||||
buf.spaces(1);
|
||||
buf.push_str(name.value);
|
||||
for var in *vars {
|
||||
buf.spaces(1);
|
||||
var.value
|
||||
.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
|
||||
}
|
||||
}
|
||||
|
||||
Where(annot, has_clauses) => {
|
||||
annot.format_with_options(buf, parens, newlines, indent);
|
||||
buf.spaces(1);
|
||||
for (i, has) in has_clauses.iter().enumerate() {
|
||||
buf.push(if i == 0 { '|' } else { ',' });
|
||||
buf.spaces(1);
|
||||
has.format_with_options(buf, parens, newlines, indent);
|
||||
}
|
||||
}
|
||||
|
||||
SpaceBefore(ann, spaces) => {
|
||||
let is_function = matches!(ann, TypeAnnotation::Function(..));
|
||||
let next_newlines = if is_function && newlines == Newlines::Yes {
|
||||
Newlines::Yes
|
||||
} else {
|
||||
Newlines::No
|
||||
};
|
||||
|
||||
buf.newline();
|
||||
buf.indent(indent);
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
|
||||
ann.format_with_options(buf, parens, next_newlines, indent)
|
||||
}
|
||||
SpaceAfter(ann, spaces) => {
|
||||
ann.format_with_options(buf, parens, newlines, indent);
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
|
||||
}
|
||||
|
||||
Malformed(raw) => buf.push_str(raw),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fields are subtly different on the type and term level:
|
||||
///
|
||||
/// > type: { x : Int, y : Bool }
|
||||
/// > term: { x: 100, y: True }
|
||||
///
|
||||
/// So we need two instances, each having the specific separator
|
||||
impl<'a> Formattable for AssignedField<'a, TypeAnnotation<'a>> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
is_multiline_assigned_field_help(self)
|
||||
}
|
||||
|
||||
fn format_with_options<'buf>(
|
||||
&self,
|
||||
buf: &mut Buf<'buf>,
|
||||
parens: Parens,
|
||||
newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
// we abuse the `Newlines` type to decide between multiline or single-line layout
|
||||
format_assigned_field_help(self, buf, parens, indent, 1, newlines == Newlines::Yes);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for AssignedField<'a, Expr<'a>> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
is_multiline_assigned_field_help(self)
|
||||
}
|
||||
|
||||
fn format_with_options<'buf>(
|
||||
&self,
|
||||
buf: &mut Buf<'buf>,
|
||||
parens: Parens,
|
||||
newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
// we abuse the `Newlines` type to decide between multiline or single-line layout
|
||||
format_assigned_field_help(self, buf, parens, indent, 0, newlines == Newlines::Yes);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_multiline_assigned_field_help<T: Formattable>(afield: &AssignedField<'_, T>) -> bool {
|
||||
use self::AssignedField::*;
|
||||
|
||||
match afield {
|
||||
RequiredValue(_, spaces, ann) | OptionalValue(_, spaces, ann) => {
|
||||
!spaces.is_empty() || ann.value.is_multiline()
|
||||
}
|
||||
LabelOnly(_) => false,
|
||||
AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => true,
|
||||
Malformed(text) => text.chars().any(|c| c == '\n'),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_assigned_field_help<'a, 'buf, T>(
|
||||
zelf: &AssignedField<'a, T>,
|
||||
buf: &mut Buf<'buf>,
|
||||
parens: Parens,
|
||||
indent: u16,
|
||||
separator_spaces: usize,
|
||||
is_multiline: bool,
|
||||
) where
|
||||
T: Formattable,
|
||||
{
|
||||
use self::AssignedField::*;
|
||||
|
||||
match zelf {
|
||||
RequiredValue(name, spaces, ann) => {
|
||||
if is_multiline {
|
||||
buf.newline();
|
||||
}
|
||||
|
||||
buf.indent(indent);
|
||||
buf.push_str(name.value);
|
||||
|
||||
if !spaces.is_empty() {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
}
|
||||
|
||||
buf.spaces(separator_spaces);
|
||||
buf.push_str(":");
|
||||
buf.spaces(1);
|
||||
ann.value.format(buf, indent);
|
||||
}
|
||||
OptionalValue(name, spaces, ann) => {
|
||||
if is_multiline {
|
||||
buf.newline();
|
||||
buf.indent(indent);
|
||||
}
|
||||
|
||||
buf.push_str(name.value);
|
||||
|
||||
if !spaces.is_empty() {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
}
|
||||
|
||||
buf.spaces(separator_spaces);
|
||||
buf.push('?');
|
||||
ann.value.format(buf, indent);
|
||||
}
|
||||
LabelOnly(name) => {
|
||||
if is_multiline {
|
||||
buf.newline();
|
||||
buf.indent(indent);
|
||||
}
|
||||
|
||||
buf.push_str(name.value);
|
||||
}
|
||||
AssignedField::SpaceBefore(sub_field, spaces) => {
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
|
||||
format_assigned_field_help(
|
||||
sub_field,
|
||||
buf,
|
||||
parens,
|
||||
indent,
|
||||
separator_spaces,
|
||||
is_multiline,
|
||||
);
|
||||
}
|
||||
AssignedField::SpaceAfter(sub_field, spaces) => {
|
||||
format_assigned_field_help(
|
||||
sub_field,
|
||||
buf,
|
||||
parens,
|
||||
indent,
|
||||
separator_spaces,
|
||||
is_multiline,
|
||||
);
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
|
||||
}
|
||||
Malformed(raw) => {
|
||||
buf.push_str(raw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for Tag<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
use self::Tag::*;
|
||||
|
||||
match self {
|
||||
Apply { args, .. } => args.iter().any(|arg| (&arg.value).is_multiline()),
|
||||
Tag::SpaceBefore(_, _) | Tag::SpaceAfter(_, _) => true,
|
||||
Malformed(text) => text.chars().any(|c| c == '\n'),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_with_options<'buf>(
|
||||
&self,
|
||||
buf: &mut Buf<'buf>,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
let is_multiline = self.is_multiline();
|
||||
|
||||
match self {
|
||||
Tag::Apply { name, args } => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(name.value);
|
||||
if is_multiline {
|
||||
let arg_indent = indent + INDENT;
|
||||
|
||||
for arg in *args {
|
||||
buf.newline();
|
||||
arg.format_with_options(buf, Parens::InApply, Newlines::No, arg_indent);
|
||||
}
|
||||
} else {
|
||||
for arg in *args {
|
||||
buf.spaces(1);
|
||||
arg.format_with_options(buf, Parens::InApply, Newlines::No, indent);
|
||||
}
|
||||
}
|
||||
}
|
||||
Tag::SpaceBefore(_, _) | Tag::SpaceAfter(_, _) => unreachable!(),
|
||||
Tag::Malformed(raw) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(raw);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for HasClause<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
self.var.value.is_multiline() || self.ability.is_multiline()
|
||||
}
|
||||
|
||||
fn format_with_options<'buf>(
|
||||
&self,
|
||||
buf: &mut Buf<'buf>,
|
||||
parens: Parens,
|
||||
newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
buf.push_str(self.var.value.extract_spaces().item);
|
||||
buf.spaces(1);
|
||||
buf.push_str("has");
|
||||
buf.spaces(1);
|
||||
self.ability
|
||||
.format_with_options(buf, parens, newlines, indent);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for Derived<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
match self {
|
||||
Derived::SpaceAfter(..) | Derived::SpaceBefore(..) => true,
|
||||
Derived::Has(derived) => derived.is_multiline(),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_with_options<'buf>(
|
||||
&self,
|
||||
buf: &mut Buf<'buf>,
|
||||
parens: Parens,
|
||||
newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
match self {
|
||||
Derived::Has(derived) => {
|
||||
if newlines == Newlines::Yes {
|
||||
buf.newline();
|
||||
buf.indent(indent);
|
||||
}
|
||||
buf.push_str("has");
|
||||
buf.spaces(1);
|
||||
fmt_collection(buf, indent, Braces::Square, *derived, newlines);
|
||||
}
|
||||
Derived::SpaceBefore(derived, spaces) => {
|
||||
buf.newline();
|
||||
buf.indent(indent);
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
|
||||
derived.format_with_options(buf, parens, Newlines::No, indent)
|
||||
}
|
||||
Derived::SpaceAfter(derived, spaces) => {
|
||||
derived.format_with_options(buf, parens, newlines, indent);
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
110
crates/compiler/fmt/src/collection.rs
Normal file
110
crates/compiler/fmt/src/collection.rs
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
use roc_parse::ast::{Collection, ExtractSpaces};
|
||||
|
||||
use crate::{
|
||||
annotation::{Formattable, Newlines},
|
||||
spaces::{count_leading_newlines, fmt_comments_only, NewlineAt, INDENT},
|
||||
Buf,
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum Braces {
|
||||
Square,
|
||||
Curly,
|
||||
}
|
||||
|
||||
pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
|
||||
buf: &mut Buf<'buf>,
|
||||
indent: u16,
|
||||
braces: Braces,
|
||||
items: Collection<'a, T>,
|
||||
newline: Newlines,
|
||||
) where
|
||||
<T as ExtractSpaces<'a>>::Item: Formattable,
|
||||
{
|
||||
let start = match braces {
|
||||
Braces::Curly => '{',
|
||||
Braces::Square => '[',
|
||||
};
|
||||
|
||||
let end = match braces {
|
||||
Braces::Curly => '}',
|
||||
Braces::Square => ']',
|
||||
};
|
||||
|
||||
if items.is_multiline() {
|
||||
let braces_indent = indent;
|
||||
let item_indent = braces_indent + INDENT;
|
||||
if newline == Newlines::Yes {
|
||||
buf.newline();
|
||||
}
|
||||
buf.indent(braces_indent);
|
||||
buf.push(start);
|
||||
|
||||
for (index, item) in items.iter().enumerate() {
|
||||
let item = item.extract_spaces();
|
||||
let is_first_item = index == 0;
|
||||
|
||||
buf.newline();
|
||||
|
||||
if !item.before.is_empty() {
|
||||
let is_only_newlines = item.before.iter().all(|s| s.is_newline());
|
||||
|
||||
if !is_first_item
|
||||
&& !is_only_newlines
|
||||
&& count_leading_newlines(item.before.iter()) > 1
|
||||
{
|
||||
buf.newline();
|
||||
}
|
||||
|
||||
fmt_comments_only(buf, item.before.iter(), NewlineAt::Bottom, item_indent);
|
||||
|
||||
if !is_only_newlines && count_leading_newlines(item.before.iter().rev()) > 0 {
|
||||
buf.newline();
|
||||
}
|
||||
}
|
||||
|
||||
item.item.format(buf, item_indent);
|
||||
|
||||
buf.push(',');
|
||||
|
||||
if !item.after.is_empty() {
|
||||
fmt_comments_only(buf, item.after.iter(), NewlineAt::Top, item_indent);
|
||||
}
|
||||
}
|
||||
|
||||
if count_leading_newlines(items.final_comments().iter()) > 1 {
|
||||
buf.newline();
|
||||
}
|
||||
|
||||
fmt_comments_only(
|
||||
buf,
|
||||
items.final_comments().iter(),
|
||||
NewlineAt::Top,
|
||||
item_indent,
|
||||
);
|
||||
buf.newline();
|
||||
buf.indent(braces_indent);
|
||||
} else {
|
||||
// is_multiline == false
|
||||
// there is no comment to add
|
||||
buf.indent(indent);
|
||||
buf.push(start);
|
||||
let mut iter = items.iter().enumerate().peekable();
|
||||
while let Some((index, item)) = iter.next() {
|
||||
if braces == Braces::Curly || index != 0 {
|
||||
buf.spaces(1);
|
||||
}
|
||||
|
||||
item.format(buf, indent);
|
||||
if iter.peek().is_some() {
|
||||
buf.push(',');
|
||||
}
|
||||
}
|
||||
|
||||
if !items.is_empty() && braces == Braces::Curly {
|
||||
buf.spaces(1);
|
||||
}
|
||||
}
|
||||
|
||||
buf.push(end);
|
||||
}
|
||||
421
crates/compiler/fmt/src/def.rs
Normal file
421
crates/compiler/fmt/src/def.rs
Normal file
|
|
@ -0,0 +1,421 @@
|
|||
use crate::annotation::{Formattable, Newlines, Parens};
|
||||
use crate::pattern::fmt_pattern;
|
||||
use crate::spaces::{fmt_spaces, INDENT};
|
||||
use crate::Buf;
|
||||
use roc_parse::ast::{
|
||||
AbilityMember, Def, Defs, Expr, ExtractSpaces, Pattern, TypeAnnotation, TypeDef, TypeHeader,
|
||||
ValueDef,
|
||||
};
|
||||
use roc_region::all::Loc;
|
||||
|
||||
/// A Located formattable value is also formattable
|
||||
|
||||
impl<'a> Formattable for Defs<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
!self.tags.is_empty()
|
||||
}
|
||||
|
||||
fn format_with_options<'buf>(
|
||||
&self,
|
||||
buf: &mut Buf<'buf>,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
for (index, def) in self.defs().enumerate() {
|
||||
let spaces_before = &self.spaces[self.space_before[index].indices()];
|
||||
let spaces_after = &self.spaces[self.space_after[index].indices()];
|
||||
|
||||
fmt_spaces(buf, spaces_before.iter(), indent);
|
||||
|
||||
match def {
|
||||
Ok(type_def) => type_def.format(buf, indent),
|
||||
Err(value_def) => value_def.format(buf, indent),
|
||||
}
|
||||
|
||||
fmt_spaces(buf, spaces_after.iter(), indent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for TypeDef<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
use roc_parse::ast::TypeDef::*;
|
||||
|
||||
match self {
|
||||
Alias { ann, .. } => ann.is_multiline(),
|
||||
Opaque { typ, .. } => typ.is_multiline(),
|
||||
Ability { members, .. } => members.iter().any(|d| d.is_multiline()),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_with_options<'buf>(
|
||||
&self,
|
||||
buf: &mut Buf<'buf>,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
use roc_parse::ast::TypeDef::*;
|
||||
|
||||
match self {
|
||||
Alias {
|
||||
header: TypeHeader { name, vars },
|
||||
ann,
|
||||
} => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(name.value);
|
||||
|
||||
for var in *vars {
|
||||
buf.spaces(1);
|
||||
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
|
||||
}
|
||||
|
||||
buf.push_str(" :");
|
||||
buf.spaces(1);
|
||||
|
||||
ann.format(buf, indent + INDENT)
|
||||
}
|
||||
Opaque {
|
||||
header: TypeHeader { name, vars },
|
||||
typ: ann,
|
||||
derived,
|
||||
} => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(name.value);
|
||||
|
||||
for var in *vars {
|
||||
buf.spaces(1);
|
||||
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
|
||||
}
|
||||
|
||||
buf.push_str(" :=");
|
||||
buf.spaces(1);
|
||||
|
||||
let ann_is_where_clause =
|
||||
matches!(ann.extract_spaces().item, TypeAnnotation::Where(..));
|
||||
|
||||
let ann_has_spaces_before = matches!(&ann.value, TypeAnnotation::SpaceBefore(..));
|
||||
|
||||
// Always put the has-derived clause on a newline if it is itself multiline, or
|
||||
// the annotation has a where-has clause.
|
||||
let derived_multiline = if let Some(derived) = derived {
|
||||
!derived.value.is_empty() && (derived.is_multiline() || ann_is_where_clause)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let make_multiline = ann.is_multiline() || derived_multiline;
|
||||
|
||||
// If the annotation has spaces before, a newline will already be printed.
|
||||
if make_multiline && !ann_has_spaces_before {
|
||||
buf.newline();
|
||||
buf.indent(indent + INDENT);
|
||||
}
|
||||
|
||||
ann.format(buf, indent + INDENT);
|
||||
|
||||
if let Some(derived) = derived {
|
||||
if !make_multiline {
|
||||
buf.spaces(1);
|
||||
}
|
||||
|
||||
derived.format_with_options(
|
||||
buf,
|
||||
Parens::NotNeeded,
|
||||
Newlines::from_bool(make_multiline),
|
||||
indent + INDENT,
|
||||
);
|
||||
}
|
||||
}
|
||||
Ability {
|
||||
header: TypeHeader { name, vars },
|
||||
loc_has: _,
|
||||
members,
|
||||
} => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(name.value);
|
||||
for var in *vars {
|
||||
buf.spaces(1);
|
||||
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
|
||||
}
|
||||
|
||||
buf.push_str(" has");
|
||||
|
||||
if !self.is_multiline() {
|
||||
debug_assert_eq!(members.len(), 1);
|
||||
buf.push_str(" ");
|
||||
members[0].format(buf, indent + INDENT);
|
||||
} else {
|
||||
for demand in members.iter() {
|
||||
buf.newline();
|
||||
buf.indent(indent + INDENT);
|
||||
demand.format(buf, indent + INDENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for ValueDef<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
use roc_parse::ast::ValueDef::*;
|
||||
|
||||
match self {
|
||||
Annotation(loc_pattern, loc_annotation) => {
|
||||
loc_pattern.is_multiline() || loc_annotation.is_multiline()
|
||||
}
|
||||
Body(loc_pattern, loc_expr) => loc_pattern.is_multiline() || loc_expr.is_multiline(),
|
||||
AnnotatedBody { .. } => true,
|
||||
Expect(loc_expr) => loc_expr.is_multiline(),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_with_options<'buf>(
|
||||
&self,
|
||||
buf: &mut Buf<'buf>,
|
||||
_parens: Parens,
|
||||
_newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
use roc_parse::ast::ValueDef::*;
|
||||
match self {
|
||||
Annotation(loc_pattern, loc_annotation) => {
|
||||
loc_pattern.format(buf, indent);
|
||||
|
||||
if loc_annotation.is_multiline() {
|
||||
buf.push_str(" :");
|
||||
|
||||
let should_outdent = match loc_annotation.value {
|
||||
TypeAnnotation::SpaceBefore(sub_def, spaces) => match sub_def {
|
||||
TypeAnnotation::Record { .. } | TypeAnnotation::TagUnion { .. } => {
|
||||
let is_only_newlines = spaces.iter().all(|s| s.is_newline());
|
||||
is_only_newlines && sub_def.is_multiline()
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
TypeAnnotation::Record { .. } | TypeAnnotation::TagUnion { .. } => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if should_outdent {
|
||||
buf.spaces(1);
|
||||
match loc_annotation.value {
|
||||
TypeAnnotation::SpaceBefore(sub_def, _) => {
|
||||
sub_def.format_with_options(
|
||||
buf,
|
||||
Parens::NotNeeded,
|
||||
Newlines::No,
|
||||
indent,
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
loc_annotation.format_with_options(
|
||||
buf,
|
||||
Parens::NotNeeded,
|
||||
Newlines::No,
|
||||
indent,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
loc_annotation.format_with_options(
|
||||
buf,
|
||||
Parens::NotNeeded,
|
||||
Newlines::Yes,
|
||||
indent + INDENT,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
buf.spaces(1);
|
||||
buf.push_str(":");
|
||||
buf.spaces(1);
|
||||
loc_annotation.format_with_options(
|
||||
buf,
|
||||
Parens::NotNeeded,
|
||||
Newlines::No,
|
||||
indent,
|
||||
);
|
||||
}
|
||||
}
|
||||
Body(loc_pattern, loc_expr) => {
|
||||
fmt_body(buf, &loc_pattern.value, &loc_expr.value, indent);
|
||||
}
|
||||
Expect(condition) => fmt_expect(buf, condition, self.is_multiline(), indent),
|
||||
AnnotatedBody {
|
||||
ann_pattern,
|
||||
ann_type,
|
||||
comment,
|
||||
body_pattern,
|
||||
body_expr,
|
||||
} => {
|
||||
let is_type_multiline = ann_type.is_multiline();
|
||||
let is_type_function = matches!(
|
||||
ann_type.value,
|
||||
TypeAnnotation::Function(..)
|
||||
| TypeAnnotation::SpaceBefore(TypeAnnotation::Function(..), ..)
|
||||
| TypeAnnotation::SpaceAfter(TypeAnnotation::Function(..), ..)
|
||||
);
|
||||
|
||||
let next_indent = if is_type_multiline {
|
||||
indent + INDENT
|
||||
} else {
|
||||
indent
|
||||
};
|
||||
|
||||
ann_pattern.format(buf, indent);
|
||||
buf.push_str(" :");
|
||||
|
||||
if is_type_multiline && is_type_function {
|
||||
ann_type.format_with_options(
|
||||
buf,
|
||||
Parens::NotNeeded,
|
||||
Newlines::Yes,
|
||||
next_indent,
|
||||
);
|
||||
} else {
|
||||
buf.spaces(1);
|
||||
ann_type.format(buf, indent);
|
||||
}
|
||||
|
||||
if let Some(comment_str) = comment {
|
||||
buf.push_str(" #");
|
||||
buf.spaces(1);
|
||||
buf.push_str(comment_str.trim());
|
||||
}
|
||||
|
||||
buf.newline();
|
||||
fmt_body(buf, &body_pattern.value, &body_expr.value, indent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for Def<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
use roc_parse::ast::Def::*;
|
||||
|
||||
match self {
|
||||
Type(def) => def.is_multiline(),
|
||||
Value(def) => def.is_multiline(),
|
||||
SpaceBefore(sub_def, spaces) | SpaceAfter(sub_def, spaces) => {
|
||||
spaces.iter().any(|s| s.is_comment()) || sub_def.is_multiline()
|
||||
}
|
||||
NotYetImplemented(s) => todo!("{}", s),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_with_options<'buf>(
|
||||
&self,
|
||||
buf: &mut Buf<'buf>,
|
||||
parens: Parens,
|
||||
newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
use roc_parse::ast::Def::*;
|
||||
|
||||
match self {
|
||||
Type(def) => def.format_with_options(buf, parens, newlines, indent),
|
||||
Value(def) => def.format_with_options(buf, parens, newlines, indent),
|
||||
|
||||
SpaceBefore(sub_def, spaces) => {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
sub_def.format(buf, indent);
|
||||
}
|
||||
SpaceAfter(sub_def, spaces) => {
|
||||
sub_def.format(buf, indent);
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
}
|
||||
NotYetImplemented(s) => todo!("{}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_expect<'a, 'buf>(
|
||||
buf: &mut Buf<'buf>,
|
||||
condition: &'a Loc<Expr<'a>>,
|
||||
is_multiline: bool,
|
||||
indent: u16,
|
||||
) {
|
||||
let return_indent = if is_multiline {
|
||||
indent + INDENT
|
||||
} else {
|
||||
indent
|
||||
};
|
||||
|
||||
buf.push_str("expect");
|
||||
condition.format(buf, return_indent);
|
||||
}
|
||||
|
||||
pub fn fmt_value_def<'a, 'buf>(
|
||||
buf: &mut Buf<'buf>,
|
||||
def: &roc_parse::ast::ValueDef<'a>,
|
||||
indent: u16,
|
||||
) {
|
||||
def.format(buf, indent);
|
||||
}
|
||||
|
||||
pub fn fmt_type_def<'a, 'buf>(buf: &mut Buf<'buf>, def: &roc_parse::ast::TypeDef<'a>, indent: u16) {
|
||||
def.format(buf, indent);
|
||||
}
|
||||
|
||||
pub fn fmt_defs<'a, 'buf>(buf: &mut Buf<'buf>, defs: &Defs<'a>, indent: u16) {
|
||||
defs.format(buf, indent);
|
||||
}
|
||||
|
||||
pub fn fmt_body<'a, 'buf>(
|
||||
buf: &mut Buf<'buf>,
|
||||
pattern: &'a Pattern<'a>,
|
||||
body: &'a Expr<'a>,
|
||||
indent: u16,
|
||||
) {
|
||||
pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent);
|
||||
buf.push_str(" =");
|
||||
if body.is_multiline() {
|
||||
match body {
|
||||
Expr::SpaceBefore(sub_def, spaces) => {
|
||||
let should_outdent = match sub_def {
|
||||
Expr::Record { .. } | Expr::List { .. } => {
|
||||
let is_only_newlines = spaces.iter().all(|s| s.is_newline());
|
||||
is_only_newlines && sub_def.is_multiline()
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if should_outdent {
|
||||
buf.spaces(1);
|
||||
sub_def.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
|
||||
} else {
|
||||
body.format_with_options(
|
||||
buf,
|
||||
Parens::NotNeeded,
|
||||
Newlines::Yes,
|
||||
indent + INDENT,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
buf.spaces(1);
|
||||
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buf.spaces(1);
|
||||
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for AbilityMember<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
self.name.value.is_multiline() || self.typ.is_multiline()
|
||||
}
|
||||
|
||||
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
|
||||
buf.push_str(self.name.value.extract_spaces().item);
|
||||
buf.spaces(1);
|
||||
buf.push(':');
|
||||
buf.spaces(1);
|
||||
self.typ.value.format(buf, indent + INDENT);
|
||||
}
|
||||
}
|
||||
1304
crates/compiler/fmt/src/expr.rs
Normal file
1304
crates/compiler/fmt/src/expr.rs
Normal file
File diff suppressed because it is too large
Load diff
212
crates/compiler/fmt/src/lib.rs
Normal file
212
crates/compiler/fmt/src/lib.rs
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
#![warn(clippy::dbg_macro)]
|
||||
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
|
||||
#![allow(clippy::large_enum_variant)]
|
||||
pub mod annotation;
|
||||
pub mod collection;
|
||||
pub mod def;
|
||||
pub mod expr;
|
||||
pub mod module;
|
||||
pub mod pattern;
|
||||
pub mod spaces;
|
||||
|
||||
use bumpalo::{collections::String, Bump};
|
||||
use roc_parse::ast::Module;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Ast<'a> {
|
||||
pub module: Module<'a>,
|
||||
pub defs: roc_parse::ast::Defs<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Buf<'a> {
|
||||
text: String<'a>,
|
||||
spaces_to_flush: usize,
|
||||
beginning_of_line: bool,
|
||||
}
|
||||
|
||||
impl<'a> Buf<'a> {
|
||||
pub fn new_in(arena: &'a Bump) -> Buf<'a> {
|
||||
Buf {
|
||||
text: String::new_in(arena),
|
||||
spaces_to_flush: 0,
|
||||
beginning_of_line: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&'a self) -> &'a str {
|
||||
self.text.as_str()
|
||||
}
|
||||
|
||||
pub fn into_bump_str(self) -> &'a str {
|
||||
self.text.into_bump_str()
|
||||
}
|
||||
|
||||
pub fn indent(&mut self, indent: u16) {
|
||||
if self.beginning_of_line {
|
||||
for _ in 0..indent {
|
||||
self.text.push(' ');
|
||||
}
|
||||
}
|
||||
self.beginning_of_line = false;
|
||||
}
|
||||
|
||||
pub fn push(&mut self, ch: char) {
|
||||
debug_assert!(!self.beginning_of_line);
|
||||
debug_assert!(ch != '\n' && ch != ' ');
|
||||
|
||||
self.flush_spaces();
|
||||
|
||||
self.text.push(ch);
|
||||
}
|
||||
|
||||
pub fn push_str_allow_spaces(&mut self, s: &str) {
|
||||
debug_assert!(!self.beginning_of_line);
|
||||
|
||||
self.flush_spaces();
|
||||
|
||||
self.text.push_str(s);
|
||||
}
|
||||
|
||||
pub fn push_str(&mut self, s: &str) {
|
||||
debug_assert!(!self.beginning_of_line);
|
||||
debug_assert!(!s.contains('\n') && !s.ends_with(' '));
|
||||
|
||||
if !s.is_empty() {
|
||||
self.flush_spaces();
|
||||
}
|
||||
|
||||
self.text.push_str(s);
|
||||
}
|
||||
|
||||
pub fn spaces(&mut self, count: usize) {
|
||||
self.spaces_to_flush += count;
|
||||
}
|
||||
|
||||
pub fn newline(&mut self) {
|
||||
self.spaces_to_flush = 0;
|
||||
self.text.push('\n');
|
||||
self.beginning_of_line = true;
|
||||
}
|
||||
|
||||
fn flush_spaces(&mut self) {
|
||||
if self.spaces_to_flush > 0 {
|
||||
for _ in 0..self.spaces_to_flush {
|
||||
self.text.push(' ');
|
||||
}
|
||||
self.spaces_to_flush = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures the text ends in a newline with no whitespace preceding it.
|
||||
pub fn fmt_end_of_file(&mut self) {
|
||||
fmt_text_eof(&mut self.text)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures the text ends in a newline with no whitespace preceding it.
|
||||
fn fmt_text_eof(text: &mut bumpalo::collections::String<'_>) {
|
||||
let mut chars_rev = text.chars().rev();
|
||||
let mut last_whitespace = None;
|
||||
let mut last_whitespace_index = text.len();
|
||||
|
||||
// Keep going until we either run out of characters or encounter one
|
||||
// that isn't whitespace.
|
||||
loop {
|
||||
match chars_rev.next() {
|
||||
Some(ch) if ch.is_whitespace() => {
|
||||
last_whitespace = Some(ch);
|
||||
last_whitespace_index -= 1;
|
||||
}
|
||||
_ => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match last_whitespace {
|
||||
Some('\n') => {
|
||||
// There may have been more whitespace after this newline; remove it!
|
||||
text.truncate(last_whitespace_index + '\n'.len_utf8());
|
||||
}
|
||||
Some(_) => {
|
||||
// There's some whitespace at the end of this file, but the first
|
||||
// whitespace char after the last non-whitespace char isn't a newline.
|
||||
// So replace that whitespace char (and everything after it) with a newline.
|
||||
text.replace_range(last_whitespace_index.., "\n");
|
||||
}
|
||||
None => {
|
||||
debug_assert!(last_whitespace_index == text.len());
|
||||
debug_assert!(!text.ends_with(char::is_whitespace));
|
||||
|
||||
// This doesn't end in whitespace at all, so add a newline.
|
||||
text.push('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eof_text_ends_with_newline() {
|
||||
use bumpalo::{collections::String, Bump};
|
||||
|
||||
let arena = Bump::new();
|
||||
let input = "This should be a newline:\n";
|
||||
let mut text = String::from_str_in(input, &arena);
|
||||
|
||||
fmt_text_eof(&mut text);
|
||||
|
||||
// This should be unchanged!
|
||||
assert_eq!(text.as_str(), input);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eof_text_ends_with_whitespace() {
|
||||
use bumpalo::{collections::String, Bump};
|
||||
|
||||
let arena = Bump::new();
|
||||
let input = "This should be a newline: \t";
|
||||
let mut text = String::from_str_in(input, &arena);
|
||||
|
||||
fmt_text_eof(&mut text);
|
||||
|
||||
assert_eq!(text.as_str(), "This should be a newline:\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eof_text_ends_with_whitespace_then_newline() {
|
||||
use bumpalo::{collections::String, Bump};
|
||||
|
||||
let arena = Bump::new();
|
||||
let input = "This should be a newline: \n";
|
||||
let mut text = String::from_str_in(input, &arena);
|
||||
|
||||
fmt_text_eof(&mut text);
|
||||
|
||||
assert_eq!(text.as_str(), "This should be a newline:\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eof_text_ends_with_no_whitespace() {
|
||||
use bumpalo::{collections::String, Bump};
|
||||
|
||||
let arena = Bump::new();
|
||||
let input = "This should be a newline:";
|
||||
let mut text = String::from_str_in(input, &arena);
|
||||
|
||||
fmt_text_eof(&mut text);
|
||||
|
||||
assert_eq!(text.as_str(), "This should be a newline:\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eof_text_is_empty() {
|
||||
use bumpalo::{collections::String, Bump};
|
||||
|
||||
let arena = Bump::new();
|
||||
let input = "";
|
||||
let mut text = String::from_str_in(input, &arena);
|
||||
|
||||
fmt_text_eof(&mut text);
|
||||
|
||||
assert_eq!(text.as_str(), "\n");
|
||||
}
|
||||
407
crates/compiler/fmt/src/module.rs
Normal file
407
crates/compiler/fmt/src/module.rs
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
use crate::annotation::{Formattable, Newlines};
|
||||
use crate::collection::{fmt_collection, Braces};
|
||||
use crate::expr::fmt_str_literal;
|
||||
use crate::spaces::{fmt_default_spaces, fmt_spaces, INDENT};
|
||||
use crate::Buf;
|
||||
use roc_parse::ast::{Collection, Module, Spaced};
|
||||
use roc_parse::header::{
|
||||
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
|
||||
PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
|
||||
};
|
||||
use roc_parse::ident::UppercaseIdent;
|
||||
use roc_region::all::Loc;
|
||||
|
||||
pub fn fmt_module<'a>(buf: &mut Buf<'_>, module: &'a Module<'a>) {
|
||||
match module {
|
||||
Module::Interface { header } => {
|
||||
fmt_interface_header(buf, header);
|
||||
}
|
||||
Module::App { header } => {
|
||||
fmt_app_header(buf, header);
|
||||
}
|
||||
Module::Platform { header } => {
|
||||
fmt_platform_header(buf, header);
|
||||
}
|
||||
Module::Hosted { header } => {
|
||||
fmt_hosted_header(buf, header);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fmt_interface_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a InterfaceHeader<'a>) {
|
||||
let indent = INDENT;
|
||||
|
||||
buf.indent(0);
|
||||
buf.push_str("interface");
|
||||
|
||||
// module name
|
||||
fmt_default_spaces(buf, header.after_interface_keyword, indent);
|
||||
buf.push_str(header.name.value.as_str());
|
||||
|
||||
// exposes
|
||||
fmt_default_spaces(buf, header.before_exposes, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str("exposes");
|
||||
fmt_default_spaces(buf, header.after_exposes, indent);
|
||||
fmt_exposes(buf, header.exposes, indent);
|
||||
|
||||
// imports
|
||||
fmt_default_spaces(buf, header.before_imports, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str("imports");
|
||||
fmt_default_spaces(buf, header.after_imports, indent);
|
||||
fmt_imports(buf, header.imports, indent);
|
||||
}
|
||||
|
||||
pub fn fmt_hosted_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a HostedHeader<'a>) {
|
||||
let indent = INDENT;
|
||||
|
||||
buf.indent(0);
|
||||
buf.push_str("hosted");
|
||||
|
||||
// module name
|
||||
fmt_default_spaces(buf, header.after_hosted_keyword, indent);
|
||||
buf.push_str(header.name.value.as_str());
|
||||
|
||||
// exposes
|
||||
fmt_default_spaces(buf, header.before_exposes, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str("exposes");
|
||||
fmt_default_spaces(buf, header.after_exposes, indent);
|
||||
fmt_exposes(buf, header.exposes, indent);
|
||||
|
||||
// imports
|
||||
fmt_default_spaces(buf, header.before_imports, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str("imports");
|
||||
fmt_default_spaces(buf, header.after_imports, indent);
|
||||
fmt_imports(buf, header.imports, indent);
|
||||
|
||||
// generates
|
||||
fmt_default_spaces(buf, header.before_generates, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str("generates");
|
||||
fmt_default_spaces(buf, header.after_generates, indent);
|
||||
buf.push_str(header.generates.into());
|
||||
|
||||
// with
|
||||
fmt_default_spaces(buf, header.before_with, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str("with");
|
||||
fmt_default_spaces(buf, header.after_with, indent);
|
||||
fmt_exposes(buf, header.generates_with, indent);
|
||||
}
|
||||
|
||||
pub fn fmt_app_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a AppHeader<'a>) {
|
||||
let indent = INDENT;
|
||||
buf.indent(0);
|
||||
buf.push_str("app");
|
||||
|
||||
fmt_default_spaces(buf, header.after_app_keyword, indent);
|
||||
fmt_str_literal(buf, header.name.value, indent);
|
||||
|
||||
// packages
|
||||
fmt_default_spaces(buf, header.before_packages, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str("packages");
|
||||
fmt_default_spaces(buf, header.after_packages, indent);
|
||||
fmt_packages(buf, header.packages, indent);
|
||||
|
||||
// imports
|
||||
fmt_default_spaces(buf, header.before_imports, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str("imports");
|
||||
fmt_default_spaces(buf, header.after_imports, indent);
|
||||
fmt_imports(buf, header.imports, indent);
|
||||
|
||||
// provides
|
||||
fmt_default_spaces(buf, header.before_provides, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str("provides");
|
||||
fmt_default_spaces(buf, header.after_provides, indent);
|
||||
fmt_provides(buf, header.provides, header.provides_types, indent);
|
||||
fmt_default_spaces(buf, header.before_to, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str("to");
|
||||
fmt_default_spaces(buf, header.after_to, indent);
|
||||
fmt_to(buf, header.to.value, indent);
|
||||
}
|
||||
|
||||
pub fn fmt_platform_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a PlatformHeader<'a>) {
|
||||
let indent = INDENT;
|
||||
|
||||
buf.indent(0);
|
||||
buf.push_str("platform");
|
||||
|
||||
fmt_default_spaces(buf, header.after_platform_keyword, indent);
|
||||
fmt_package_name(buf, header.name.value, indent);
|
||||
|
||||
// requires
|
||||
fmt_default_spaces(buf, header.before_requires, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str("requires");
|
||||
fmt_default_spaces(buf, header.after_requires, indent);
|
||||
fmt_requires(buf, &header.requires, indent);
|
||||
|
||||
// exposes
|
||||
fmt_default_spaces(buf, header.before_exposes, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str("exposes");
|
||||
fmt_default_spaces(buf, header.after_exposes, indent);
|
||||
fmt_exposes(buf, header.exposes, indent);
|
||||
|
||||
// packages
|
||||
fmt_default_spaces(buf, header.before_packages, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str("packages");
|
||||
fmt_default_spaces(buf, header.after_packages, indent);
|
||||
fmt_packages(buf, header.packages, indent);
|
||||
|
||||
// imports
|
||||
fmt_default_spaces(buf, header.before_imports, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str("imports");
|
||||
fmt_default_spaces(buf, header.after_imports, indent);
|
||||
fmt_imports(buf, header.imports, indent);
|
||||
|
||||
// provides
|
||||
fmt_default_spaces(buf, header.before_provides, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str("provides");
|
||||
fmt_default_spaces(buf, header.after_provides, indent);
|
||||
fmt_provides(buf, header.provides, None, indent);
|
||||
}
|
||||
|
||||
fn fmt_requires<'a, 'buf>(buf: &mut Buf<'buf>, requires: &PlatformRequires<'a>, indent: u16) {
|
||||
fmt_collection(buf, indent, Braces::Curly, requires.rigids, Newlines::No);
|
||||
|
||||
buf.push_str(" {");
|
||||
buf.spaces(1);
|
||||
requires.signature.value.format(buf, indent);
|
||||
buf.push_str(" }");
|
||||
}
|
||||
|
||||
impl<'a> Formattable for TypedIdent<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
|
||||
buf.indent(indent);
|
||||
buf.push_str(self.ident.value);
|
||||
fmt_default_spaces(buf, self.spaces_before_colon, indent);
|
||||
buf.push_str(":");
|
||||
buf.spaces(1);
|
||||
self.ann.value.format(buf, indent);
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_package_name<'buf>(buf: &mut Buf<'buf>, name: PackageName, _indent: u16) {
|
||||
buf.push('"');
|
||||
buf.push_str_allow_spaces(name.to_str());
|
||||
buf.push('"');
|
||||
}
|
||||
|
||||
impl<'a, T: Formattable> Formattable for Spaced<'a, T> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
use Spaced::*;
|
||||
|
||||
match self {
|
||||
Item(formattable) => formattable.is_multiline(),
|
||||
SpaceBefore(formattable, spaces) | SpaceAfter(formattable, spaces) => {
|
||||
!spaces.is_empty() || formattable.is_multiline()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_with_options<'buf>(
|
||||
&self,
|
||||
buf: &mut Buf<'buf>,
|
||||
parens: crate::annotation::Parens,
|
||||
newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
match self {
|
||||
Spaced::Item(item) => {
|
||||
item.format_with_options(buf, parens, newlines, indent);
|
||||
}
|
||||
Spaced::SpaceBefore(item, spaces) => {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
item.format_with_options(buf, parens, newlines, indent);
|
||||
}
|
||||
Spaced::SpaceAfter(item, spaces) => {
|
||||
item.format_with_options(buf, parens, newlines, indent);
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_imports<'a, 'buf>(
|
||||
buf: &mut Buf<'buf>,
|
||||
loc_entries: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
|
||||
indent: u16,
|
||||
) {
|
||||
fmt_collection(
|
||||
buf,
|
||||
indent + INDENT,
|
||||
Braces::Square,
|
||||
loc_entries,
|
||||
Newlines::No,
|
||||
)
|
||||
}
|
||||
|
||||
fn fmt_provides<'a, 'buf>(
|
||||
buf: &mut Buf<'buf>,
|
||||
loc_exposed_names: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
|
||||
loc_provided_types: Option<Collection<'a, Loc<Spaced<'a, UppercaseIdent<'a>>>>>,
|
||||
indent: u16,
|
||||
) {
|
||||
fmt_collection(buf, indent, Braces::Square, loc_exposed_names, Newlines::No);
|
||||
if let Some(loc_provided) = loc_provided_types {
|
||||
fmt_default_spaces(buf, &[], indent);
|
||||
fmt_collection(
|
||||
buf,
|
||||
indent + INDENT,
|
||||
Braces::Curly,
|
||||
loc_provided,
|
||||
Newlines::No,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_to<'buf>(buf: &mut Buf<'buf>, to: To, indent: u16) {
|
||||
match to {
|
||||
To::ExistingPackage(name) => {
|
||||
buf.push_str(name);
|
||||
}
|
||||
To::NewPackage(package_name) => fmt_package_name(buf, package_name, indent),
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_exposes<'buf, N: Formattable + Copy>(
|
||||
buf: &mut Buf<'buf>,
|
||||
loc_entries: Collection<'_, Loc<Spaced<'_, N>>>,
|
||||
indent: u16,
|
||||
) {
|
||||
fmt_collection(
|
||||
buf,
|
||||
indent + INDENT,
|
||||
Braces::Square,
|
||||
loc_entries,
|
||||
Newlines::No,
|
||||
)
|
||||
}
|
||||
|
||||
pub trait FormatName {
|
||||
fn format<'buf>(&self, buf: &mut Buf<'buf>);
|
||||
}
|
||||
|
||||
impl<'a> FormatName for &'a str {
|
||||
fn format<'buf>(&self, buf: &mut Buf<'buf>) {
|
||||
buf.push_str(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FormatName for ModuleName<'a> {
|
||||
fn format<'buf>(&self, buf: &mut Buf<'buf>) {
|
||||
buf.push_str(self.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for ModuleName<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn format<'buf>(&self, buf: &mut Buf<'buf>, _indent: u16) {
|
||||
buf.push_str(self.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for ExposedName<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
|
||||
buf.indent(indent);
|
||||
buf.push_str(self.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FormatName for ExposedName<'a> {
|
||||
fn format<'buf>(&self, buf: &mut Buf<'buf>) {
|
||||
buf.push_str(self.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_packages<'a, 'buf>(
|
||||
buf: &mut Buf<'buf>,
|
||||
loc_entries: Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>,
|
||||
indent: u16,
|
||||
) {
|
||||
fmt_collection(buf, indent, Braces::Curly, loc_entries, Newlines::No)
|
||||
}
|
||||
|
||||
impl<'a> Formattable for PackageEntry<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
|
||||
fmt_packages_entry(buf, self, indent);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for ImportsEntry<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
|
||||
fmt_imports_entry(buf, self, indent);
|
||||
}
|
||||
}
|
||||
fn fmt_packages_entry<'a, 'buf>(buf: &mut Buf<'buf>, entry: &PackageEntry<'a>, indent: u16) {
|
||||
buf.push_str(entry.shorthand);
|
||||
buf.push(':');
|
||||
fmt_default_spaces(buf, entry.spaces_after_shorthand, indent);
|
||||
fmt_package_name(buf, entry.package_name.value, indent);
|
||||
}
|
||||
|
||||
fn fmt_imports_entry<'a, 'buf>(buf: &mut Buf<'buf>, entry: &ImportsEntry<'a>, indent: u16) {
|
||||
use roc_parse::header::ImportsEntry::*;
|
||||
|
||||
buf.indent(indent);
|
||||
|
||||
match entry {
|
||||
Module(module, loc_exposes_entries) => {
|
||||
buf.push_str(module.as_str());
|
||||
|
||||
if !loc_exposes_entries.is_empty() {
|
||||
buf.push('.');
|
||||
|
||||
fmt_collection(
|
||||
buf,
|
||||
indent,
|
||||
Braces::Curly,
|
||||
*loc_exposes_entries,
|
||||
Newlines::No,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Package(pkg, name, entries) => {
|
||||
buf.push_str(pkg);
|
||||
buf.push('.');
|
||||
buf.push_str(name.as_str());
|
||||
|
||||
if !entries.is_empty() {
|
||||
buf.push('.');
|
||||
|
||||
fmt_collection(buf, indent, Braces::Curly, *entries, Newlines::No)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
198
crates/compiler/fmt/src/pattern.rs
Normal file
198
crates/compiler/fmt/src/pattern.rs
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
use crate::annotation::{Formattable, Newlines, Parens};
|
||||
use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt};
|
||||
use crate::Buf;
|
||||
use roc_parse::ast::{Base, Pattern};
|
||||
|
||||
pub fn fmt_pattern<'a, 'buf>(
|
||||
buf: &mut Buf<'buf>,
|
||||
pattern: &'a Pattern<'a>,
|
||||
indent: u16,
|
||||
parens: Parens,
|
||||
) {
|
||||
pattern.format_with_options(buf, parens, Newlines::No, indent);
|
||||
}
|
||||
|
||||
impl<'a> Formattable for Pattern<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
// Theory: a pattern should only be multiline when it contains a comment
|
||||
match self {
|
||||
Pattern::SpaceBefore(_, spaces) | Pattern::SpaceAfter(_, spaces) => {
|
||||
debug_assert!(!spaces.is_empty());
|
||||
|
||||
spaces.iter().any(|s| s.is_comment())
|
||||
}
|
||||
|
||||
Pattern::RecordDestructure(fields) => fields.iter().any(|f| f.is_multiline()),
|
||||
Pattern::RequiredField(_, subpattern) => subpattern.is_multiline(),
|
||||
|
||||
Pattern::OptionalField(_, expr) => expr.is_multiline(),
|
||||
|
||||
Pattern::Identifier(_)
|
||||
| Pattern::Tag(_)
|
||||
| Pattern::OpaqueRef(_)
|
||||
| Pattern::Apply(_, _)
|
||||
| Pattern::NumLiteral(..)
|
||||
| Pattern::NonBase10Literal { .. }
|
||||
| Pattern::FloatLiteral(..)
|
||||
| Pattern::StrLiteral(_)
|
||||
| Pattern::SingleQuote(_)
|
||||
| Pattern::Underscore(_)
|
||||
| Pattern::Malformed(_)
|
||||
| Pattern::MalformedIdent(_, _)
|
||||
| Pattern::QualifiedIdentifier { .. } => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn format_with_options<'buf>(
|
||||
&self,
|
||||
buf: &mut Buf<'buf>,
|
||||
parens: Parens,
|
||||
newlines: Newlines,
|
||||
indent: u16,
|
||||
) {
|
||||
use self::Pattern::*;
|
||||
|
||||
match self {
|
||||
Identifier(string) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(string)
|
||||
}
|
||||
Tag(name) | OpaqueRef(name) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(name);
|
||||
}
|
||||
Apply(loc_pattern, loc_arg_patterns) => {
|
||||
buf.indent(indent);
|
||||
// Sometimes, an Apply pattern needs parens around it.
|
||||
// In particular when an Apply's argument is itself an Apply (> 0) arguments
|
||||
let parens = !loc_arg_patterns.is_empty() && parens == Parens::InApply;
|
||||
|
||||
if parens {
|
||||
buf.push('(');
|
||||
}
|
||||
|
||||
loc_pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent);
|
||||
|
||||
for loc_arg in loc_arg_patterns.iter() {
|
||||
buf.spaces(1);
|
||||
loc_arg.format_with_options(buf, Parens::InApply, Newlines::No, indent);
|
||||
}
|
||||
|
||||
if parens {
|
||||
buf.push(')');
|
||||
}
|
||||
}
|
||||
RecordDestructure(loc_patterns) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str("{");
|
||||
|
||||
if !loc_patterns.is_empty() {
|
||||
buf.spaces(1);
|
||||
let mut it = loc_patterns.iter().peekable();
|
||||
while let Some(loc_pattern) = it.next() {
|
||||
loc_pattern.format(buf, indent);
|
||||
|
||||
if it.peek().is_some() {
|
||||
buf.push_str(",");
|
||||
buf.spaces(1);
|
||||
}
|
||||
}
|
||||
buf.spaces(1);
|
||||
}
|
||||
|
||||
buf.push_str("}");
|
||||
}
|
||||
|
||||
RequiredField(name, loc_pattern) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(name);
|
||||
buf.push_str(":");
|
||||
buf.spaces(1);
|
||||
loc_pattern.format(buf, indent);
|
||||
}
|
||||
|
||||
OptionalField(name, loc_pattern) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(name);
|
||||
buf.push_str(" ?");
|
||||
buf.spaces(1);
|
||||
loc_pattern.format(buf, indent);
|
||||
}
|
||||
|
||||
&NumLiteral(string) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(string);
|
||||
}
|
||||
&NonBase10Literal {
|
||||
base,
|
||||
string,
|
||||
is_negative,
|
||||
} => {
|
||||
buf.indent(indent);
|
||||
if is_negative {
|
||||
buf.push('-');
|
||||
}
|
||||
|
||||
match base {
|
||||
Base::Hex => buf.push_str("0x"),
|
||||
Base::Octal => buf.push_str("0o"),
|
||||
Base::Binary => buf.push_str("0b"),
|
||||
Base::Decimal => { /* nothing */ }
|
||||
}
|
||||
|
||||
buf.push_str(string);
|
||||
}
|
||||
&FloatLiteral(string) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(string);
|
||||
}
|
||||
StrLiteral(literal) => {
|
||||
todo!("Format string literal: {:?}", literal);
|
||||
}
|
||||
SingleQuote(string) => {
|
||||
buf.push('\'');
|
||||
buf.push_str(string);
|
||||
buf.push('\'');
|
||||
}
|
||||
Underscore(name) => {
|
||||
buf.indent(indent);
|
||||
buf.push('_');
|
||||
buf.push_str(name);
|
||||
}
|
||||
|
||||
// Space
|
||||
SpaceBefore(sub_pattern, spaces) => {
|
||||
if !sub_pattern.is_multiline() {
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent)
|
||||
} else {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
}
|
||||
sub_pattern.format_with_options(buf, parens, newlines, indent);
|
||||
}
|
||||
SpaceAfter(sub_pattern, spaces) => {
|
||||
sub_pattern.format_with_options(buf, parens, newlines, indent);
|
||||
// if only_comments {
|
||||
if !sub_pattern.is_multiline() {
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent)
|
||||
} else {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
}
|
||||
}
|
||||
|
||||
// Malformed
|
||||
Malformed(string) | MalformedIdent(string, _) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(string);
|
||||
}
|
||||
QualifiedIdentifier { module_name, ident } => {
|
||||
buf.indent(indent);
|
||||
if !module_name.is_empty() {
|
||||
buf.push_str(module_name);
|
||||
buf.push('.');
|
||||
}
|
||||
|
||||
buf.push_str(ident);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
786
crates/compiler/fmt/src/spaces.rs
Normal file
786
crates/compiler/fmt/src/spaces.rs
Normal file
|
|
@ -0,0 +1,786 @@
|
|||
use bumpalo::collections::vec::Vec;
|
||||
use bumpalo::Bump;
|
||||
use roc_module::called_via::{BinOp, UnaryOp};
|
||||
use roc_parse::{
|
||||
ast::{
|
||||
AbilityMember, AssignedField, Collection, CommentOrNewline, Def, Derived, Expr, Has,
|
||||
HasClause, Module, Pattern, Spaced, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef,
|
||||
TypeHeader, ValueDef, WhenBranch,
|
||||
},
|
||||
header::{
|
||||
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName,
|
||||
PackageEntry, PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
|
||||
},
|
||||
ident::UppercaseIdent,
|
||||
};
|
||||
use roc_region::all::{Loc, Region};
|
||||
|
||||
use crate::{Ast, Buf};
|
||||
|
||||
/// The number of spaces to indent.
|
||||
pub const INDENT: u16 = 4;
|
||||
|
||||
pub fn fmt_default_spaces<'a, 'buf>(
|
||||
buf: &mut Buf<'buf>,
|
||||
spaces: &[CommentOrNewline<'a>],
|
||||
indent: u16,
|
||||
) {
|
||||
if spaces.is_empty() {
|
||||
buf.spaces(1);
|
||||
} else {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fmt_spaces<'a, 'buf, I>(buf: &mut Buf<'buf>, spaces: I, indent: u16)
|
||||
where
|
||||
I: Iterator<Item = &'a CommentOrNewline<'a>>,
|
||||
{
|
||||
use self::CommentOrNewline::*;
|
||||
|
||||
// Only ever print two newlines back to back.
|
||||
// (Two newlines renders as one blank line.)
|
||||
let mut consecutive_newlines = 0;
|
||||
|
||||
let mut encountered_comment = false;
|
||||
|
||||
for space in spaces {
|
||||
match space {
|
||||
Newline => {
|
||||
if !encountered_comment && (consecutive_newlines < 2) {
|
||||
buf.newline();
|
||||
|
||||
// Don't bother incrementing it if we're already over the limit.
|
||||
// There's no upside, and it might eventually overflow,
|
||||
consecutive_newlines += 1;
|
||||
}
|
||||
}
|
||||
LineComment(comment) => {
|
||||
buf.indent(indent);
|
||||
fmt_comment(buf, comment);
|
||||
buf.newline();
|
||||
|
||||
encountered_comment = true;
|
||||
}
|
||||
DocComment(docs) => {
|
||||
buf.indent(indent);
|
||||
fmt_docs(buf, docs);
|
||||
buf.newline();
|
||||
|
||||
encountered_comment = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug)]
|
||||
pub enum NewlineAt {
|
||||
Top,
|
||||
Bottom,
|
||||
Both,
|
||||
None,
|
||||
}
|
||||
|
||||
/// Like format_spaces, but remove newlines and keep only comments.
|
||||
/// The `new_line_at` argument describes how new lines should be inserted
|
||||
/// at the beginning or at the end of the block
|
||||
/// in the case of there is some comment in the `spaces` argument.
|
||||
pub fn fmt_comments_only<'a, 'buf, I>(
|
||||
buf: &mut Buf<'buf>,
|
||||
spaces: I,
|
||||
new_line_at: NewlineAt,
|
||||
indent: u16,
|
||||
) where
|
||||
I: Iterator<Item = &'a CommentOrNewline<'a>>,
|
||||
{
|
||||
use self::CommentOrNewline::*;
|
||||
use NewlineAt::*;
|
||||
|
||||
let mut comment_seen = false;
|
||||
|
||||
for space in spaces {
|
||||
match space {
|
||||
Newline => {}
|
||||
LineComment(comment) => {
|
||||
if comment_seen || new_line_at == Top || new_line_at == Both {
|
||||
buf.newline();
|
||||
}
|
||||
buf.indent(indent);
|
||||
fmt_comment(buf, comment);
|
||||
comment_seen = true;
|
||||
}
|
||||
DocComment(docs) => {
|
||||
if comment_seen || new_line_at == Top || new_line_at == Both {
|
||||
buf.newline();
|
||||
}
|
||||
buf.indent(indent);
|
||||
fmt_docs(buf, docs);
|
||||
comment_seen = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if comment_seen && (new_line_at == Bottom || new_line_at == Both) {
|
||||
buf.newline();
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_comment<'buf>(buf: &mut Buf<'buf>, comment: &str) {
|
||||
buf.push('#');
|
||||
if !comment.starts_with(' ') {
|
||||
buf.spaces(1);
|
||||
}
|
||||
buf.push_str(comment.trim_end());
|
||||
}
|
||||
|
||||
pub fn count_leading_newlines<'a, I>(data: I) -> u16
|
||||
where
|
||||
I: Iterator<Item = &'a CommentOrNewline<'a>>,
|
||||
{
|
||||
let mut count = 0;
|
||||
let mut allow_counting = false;
|
||||
|
||||
for (index, val) in data.enumerate() {
|
||||
let is_first = index == 0;
|
||||
let is_newline = matches!(val, CommentOrNewline::Newline);
|
||||
|
||||
if is_first && is_newline {
|
||||
allow_counting = true
|
||||
}
|
||||
|
||||
if is_newline && allow_counting {
|
||||
count += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
count
|
||||
}
|
||||
|
||||
fn fmt_docs<'buf>(buf: &mut Buf<'buf>, docs: &str) {
|
||||
buf.push_str("##");
|
||||
if !docs.is_empty() {
|
||||
buf.spaces(1);
|
||||
}
|
||||
buf.push_str(docs.trim_end());
|
||||
}
|
||||
|
||||
/// RemoveSpaces normalizes the ast to something that we _expect_ to be invariant under formatting.
|
||||
///
|
||||
/// Currently this consists of:
|
||||
/// * Removing newlines
|
||||
/// * Removing comments
|
||||
/// * Removing parens in Exprs
|
||||
///
|
||||
/// Long term, we actuall want this transform to preserve comments (so we can assert they're maintained by formatting)
|
||||
/// - but there are currently several bugs where they're _not_ preserved.
|
||||
/// TODO: ensure formatting retains comments
|
||||
pub trait RemoveSpaces<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self;
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Ast<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
Ast {
|
||||
module: self.module.remove_spaces(arena),
|
||||
defs: {
|
||||
let mut defs = self.defs.clone();
|
||||
|
||||
for type_def in defs.type_defs.iter_mut() {
|
||||
*type_def = type_def.remove_spaces(arena);
|
||||
}
|
||||
|
||||
for value_def in defs.value_defs.iter_mut() {
|
||||
*value_def = value_def.remove_spaces(arena);
|
||||
}
|
||||
|
||||
for region_def in defs.regions.iter_mut() {
|
||||
*region_def = region_def.remove_spaces(arena);
|
||||
}
|
||||
|
||||
defs
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Module<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match self {
|
||||
Module::Interface { header } => Module::Interface {
|
||||
header: InterfaceHeader {
|
||||
name: header.name.remove_spaces(arena),
|
||||
exposes: header.exposes.remove_spaces(arena),
|
||||
imports: header.imports.remove_spaces(arena),
|
||||
before_header: &[],
|
||||
after_interface_keyword: &[],
|
||||
before_exposes: &[],
|
||||
after_exposes: &[],
|
||||
before_imports: &[],
|
||||
after_imports: &[],
|
||||
},
|
||||
},
|
||||
Module::App { header } => Module::App {
|
||||
header: AppHeader {
|
||||
name: header.name.remove_spaces(arena),
|
||||
packages: header.packages.remove_spaces(arena),
|
||||
imports: header.imports.remove_spaces(arena),
|
||||
provides: header.provides.remove_spaces(arena),
|
||||
provides_types: header.provides_types.map(|ts| ts.remove_spaces(arena)),
|
||||
to: header.to.remove_spaces(arena),
|
||||
before_header: &[],
|
||||
after_app_keyword: &[],
|
||||
before_packages: &[],
|
||||
after_packages: &[],
|
||||
before_imports: &[],
|
||||
after_imports: &[],
|
||||
before_provides: &[],
|
||||
after_provides: &[],
|
||||
before_to: &[],
|
||||
after_to: &[],
|
||||
},
|
||||
},
|
||||
Module::Platform { header } => Module::Platform {
|
||||
header: PlatformHeader {
|
||||
name: header.name.remove_spaces(arena),
|
||||
requires: header.requires.remove_spaces(arena),
|
||||
exposes: header.exposes.remove_spaces(arena),
|
||||
packages: header.packages.remove_spaces(arena),
|
||||
imports: header.imports.remove_spaces(arena),
|
||||
provides: header.provides.remove_spaces(arena),
|
||||
before_header: &[],
|
||||
after_platform_keyword: &[],
|
||||
before_requires: &[],
|
||||
after_requires: &[],
|
||||
before_exposes: &[],
|
||||
after_exposes: &[],
|
||||
before_packages: &[],
|
||||
after_packages: &[],
|
||||
before_imports: &[],
|
||||
after_imports: &[],
|
||||
before_provides: &[],
|
||||
after_provides: &[],
|
||||
},
|
||||
},
|
||||
Module::Hosted { header } => Module::Hosted {
|
||||
header: HostedHeader {
|
||||
name: header.name.remove_spaces(arena),
|
||||
exposes: header.exposes.remove_spaces(arena),
|
||||
imports: header.imports.remove_spaces(arena),
|
||||
generates: header.generates.remove_spaces(arena),
|
||||
generates_with: header.generates_with.remove_spaces(arena),
|
||||
before_header: &[],
|
||||
after_hosted_keyword: &[],
|
||||
before_exposes: &[],
|
||||
after_exposes: &[],
|
||||
before_imports: &[],
|
||||
after_imports: &[],
|
||||
before_generates: &[],
|
||||
after_generates: &[],
|
||||
before_with: &[],
|
||||
after_with: &[],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Region {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
Region::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for &'a str {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a> + Copy> RemoveSpaces<'a> for Spaced<'a, T> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Spaced::Item(a) => Spaced::Item(a.remove_spaces(arena)),
|
||||
Spaced::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
Spaced::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ExposedName<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ModuleName<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for PackageName<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for To<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
To::ExistingPackage(a) => To::ExistingPackage(a),
|
||||
To::NewPackage(a) => To::NewPackage(a.remove_spaces(arena)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for TypedIdent<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
TypedIdent {
|
||||
ident: self.ident.remove_spaces(arena),
|
||||
spaces_before_colon: &[],
|
||||
ann: self.ann.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for PlatformRequires<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
PlatformRequires {
|
||||
rigids: self.rigids.remove_spaces(arena),
|
||||
signature: self.signature.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for UppercaseIdent<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for PackageEntry<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
PackageEntry {
|
||||
shorthand: self.shorthand,
|
||||
spaces_after_shorthand: &[],
|
||||
package_name: self.package_name.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ImportsEntry<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
ImportsEntry::Module(a, b) => ImportsEntry::Module(a, b.remove_spaces(arena)),
|
||||
ImportsEntry::Package(a, b, c) => ImportsEntry::Package(a, b, c.remove_spaces(arena)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Option<T> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
self.as_ref().map(|a| a.remove_spaces(arena))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for Loc<T> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
let res = self.value.remove_spaces(arena);
|
||||
Loc::at(Region::zero(), res)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A: RemoveSpaces<'a>, B: RemoveSpaces<'a>> RemoveSpaces<'a> for (A, B) {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
(self.0.remove_spaces(arena), self.1.remove_spaces(arena))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Collection<'a, T> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
let mut items = Vec::with_capacity_in(self.items.len(), arena);
|
||||
for item in self.items {
|
||||
items.push(item.remove_spaces(arena));
|
||||
}
|
||||
Collection::with_items(items.into_bump_slice())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for &'a [T] {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
let mut items = Vec::with_capacity_in(self.len(), arena);
|
||||
for item in *self {
|
||||
let res = item.remove_spaces(arena);
|
||||
items.push(res);
|
||||
}
|
||||
items.into_bump_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for UnaryOp {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for BinOp {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for &'a T {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
arena.alloc((*self).remove_spaces(arena))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for TypeDef<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
use TypeDef::*;
|
||||
|
||||
match *self {
|
||||
Alias {
|
||||
header: TypeHeader { name, vars },
|
||||
ann,
|
||||
} => Alias {
|
||||
header: TypeHeader {
|
||||
name: name.remove_spaces(arena),
|
||||
vars: vars.remove_spaces(arena),
|
||||
},
|
||||
ann: ann.remove_spaces(arena),
|
||||
},
|
||||
Opaque {
|
||||
header: TypeHeader { name, vars },
|
||||
typ,
|
||||
derived,
|
||||
} => Opaque {
|
||||
header: TypeHeader {
|
||||
name: name.remove_spaces(arena),
|
||||
vars: vars.remove_spaces(arena),
|
||||
},
|
||||
typ: typ.remove_spaces(arena),
|
||||
derived: derived.remove_spaces(arena),
|
||||
},
|
||||
Ability {
|
||||
header: TypeHeader { name, vars },
|
||||
loc_has,
|
||||
members,
|
||||
} => Ability {
|
||||
header: TypeHeader {
|
||||
name: name.remove_spaces(arena),
|
||||
vars: vars.remove_spaces(arena),
|
||||
},
|
||||
loc_has: loc_has.remove_spaces(arena),
|
||||
members: members.remove_spaces(arena),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ValueDef<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
use ValueDef::*;
|
||||
|
||||
match *self {
|
||||
Annotation(a, b) => Annotation(a.remove_spaces(arena), b.remove_spaces(arena)),
|
||||
Body(a, b) => Body(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
),
|
||||
AnnotatedBody {
|
||||
ann_pattern,
|
||||
ann_type,
|
||||
comment: _,
|
||||
body_pattern,
|
||||
body_expr,
|
||||
} => AnnotatedBody {
|
||||
ann_pattern: arena.alloc(ann_pattern.remove_spaces(arena)),
|
||||
ann_type: arena.alloc(ann_type.remove_spaces(arena)),
|
||||
comment: None,
|
||||
body_pattern: arena.alloc(body_pattern.remove_spaces(arena)),
|
||||
body_expr: arena.alloc(body_expr.remove_spaces(arena)),
|
||||
},
|
||||
Expect(a) => Expect(arena.alloc(a.remove_spaces(arena))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Def<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Def::Type(def) => Def::Type(def.remove_spaces(arena)),
|
||||
Def::Value(def) => Def::Value(def.remove_spaces(arena)),
|
||||
Def::NotYetImplemented(a) => Def::NotYetImplemented(a),
|
||||
Def::SpaceBefore(a, _) | Def::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Has<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
Has::Has
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for AbilityMember<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
AbilityMember {
|
||||
name: self.name.remove_spaces(arena),
|
||||
typ: self.typ.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for WhenBranch<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
WhenBranch {
|
||||
patterns: self.patterns.remove_spaces(arena),
|
||||
value: self.value.remove_spaces(arena),
|
||||
guard: self.guard.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a> + Copy + std::fmt::Debug> RemoveSpaces<'a> for AssignedField<'a, T> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
AssignedField::RequiredValue(a, _, c) => AssignedField::RequiredValue(
|
||||
a.remove_spaces(arena),
|
||||
arena.alloc([]),
|
||||
arena.alloc(c.remove_spaces(arena)),
|
||||
),
|
||||
AssignedField::OptionalValue(a, _, c) => AssignedField::OptionalValue(
|
||||
a.remove_spaces(arena),
|
||||
arena.alloc([]),
|
||||
arena.alloc(c.remove_spaces(arena)),
|
||||
),
|
||||
AssignedField::LabelOnly(a) => AssignedField::LabelOnly(a.remove_spaces(arena)),
|
||||
AssignedField::Malformed(a) => AssignedField::Malformed(a),
|
||||
AssignedField::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
AssignedField::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for StrLiteral<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
StrLiteral::PlainLine(t) => StrLiteral::PlainLine(t),
|
||||
StrLiteral::Line(t) => StrLiteral::Line(t.remove_spaces(arena)),
|
||||
StrLiteral::Block(t) => StrLiteral::Block(t.remove_spaces(arena)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for StrSegment<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
StrSegment::Plaintext(t) => StrSegment::Plaintext(t),
|
||||
StrSegment::Unicode(t) => StrSegment::Unicode(t.remove_spaces(arena)),
|
||||
StrSegment::EscapedChar(c) => StrSegment::EscapedChar(c),
|
||||
StrSegment::Interpolated(t) => StrSegment::Interpolated(t.remove_spaces(arena)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Expr<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Expr::Float(a) => Expr::Float(a),
|
||||
Expr::Num(a) => Expr::Num(a),
|
||||
Expr::NonBase10Int {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
} => Expr::NonBase10Int {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
},
|
||||
Expr::Str(a) => Expr::Str(a.remove_spaces(arena)),
|
||||
Expr::Access(a, b) => Expr::Access(arena.alloc(a.remove_spaces(arena)), b),
|
||||
Expr::AccessorFunction(a) => Expr::AccessorFunction(a),
|
||||
Expr::List(a) => Expr::List(a.remove_spaces(arena)),
|
||||
Expr::RecordUpdate { update, fields } => Expr::RecordUpdate {
|
||||
update: arena.alloc(update.remove_spaces(arena)),
|
||||
fields: fields.remove_spaces(arena),
|
||||
},
|
||||
Expr::Record(a) => Expr::Record(a.remove_spaces(arena)),
|
||||
Expr::Var { module_name, ident } => Expr::Var { module_name, ident },
|
||||
Expr::Underscore(a) => Expr::Underscore(a),
|
||||
Expr::Tag(a) => Expr::Tag(a),
|
||||
Expr::OpaqueRef(a) => Expr::OpaqueRef(a),
|
||||
Expr::Closure(a, b) => Expr::Closure(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
),
|
||||
Expr::Defs(a, b) => {
|
||||
let mut defs = a.clone();
|
||||
defs.space_before = vec![Default::default(); defs.len()];
|
||||
defs.space_after = vec![Default::default(); defs.len()];
|
||||
defs.regions = vec![Region::zero(); defs.len()];
|
||||
defs.spaces.clear();
|
||||
|
||||
for type_def in defs.type_defs.iter_mut() {
|
||||
*type_def = type_def.remove_spaces(arena);
|
||||
}
|
||||
|
||||
for value_def in defs.value_defs.iter_mut() {
|
||||
*value_def = value_def.remove_spaces(arena);
|
||||
}
|
||||
|
||||
Expr::Defs(arena.alloc(defs), arena.alloc(b.remove_spaces(arena)))
|
||||
}
|
||||
Expr::Backpassing(a, b, c) => Expr::Backpassing(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
arena.alloc(c.remove_spaces(arena)),
|
||||
),
|
||||
Expr::Expect(a, b) => Expr::Expect(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
),
|
||||
Expr::Apply(a, b, c) => Expr::Apply(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
b.remove_spaces(arena),
|
||||
c,
|
||||
),
|
||||
Expr::BinOps(a, b) => {
|
||||
Expr::BinOps(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena)))
|
||||
}
|
||||
Expr::UnaryOp(a, b) => {
|
||||
Expr::UnaryOp(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
|
||||
}
|
||||
Expr::If(a, b) => Expr::If(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena))),
|
||||
Expr::When(a, b) => {
|
||||
Expr::When(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
|
||||
}
|
||||
Expr::ParensAround(a) => {
|
||||
// The formatter can remove redundant parentheses, so also remove these when normalizing for comparison.
|
||||
a.remove_spaces(arena)
|
||||
}
|
||||
Expr::MalformedIdent(a, b) => Expr::MalformedIdent(a, b),
|
||||
Expr::MalformedClosure => Expr::MalformedClosure,
|
||||
Expr::PrecedenceConflict(a) => Expr::PrecedenceConflict(a),
|
||||
Expr::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
Expr::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
Expr::SingleQuote(a) => Expr::Num(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Pattern<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Pattern::Identifier(a) => Pattern::Identifier(a),
|
||||
Pattern::Tag(a) => Pattern::Tag(a),
|
||||
Pattern::OpaqueRef(a) => Pattern::OpaqueRef(a),
|
||||
Pattern::Apply(a, b) => Pattern::Apply(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
),
|
||||
Pattern::RecordDestructure(a) => Pattern::RecordDestructure(a.remove_spaces(arena)),
|
||||
Pattern::RequiredField(a, b) => {
|
||||
Pattern::RequiredField(a, arena.alloc(b.remove_spaces(arena)))
|
||||
}
|
||||
Pattern::OptionalField(a, b) => {
|
||||
Pattern::OptionalField(a, arena.alloc(b.remove_spaces(arena)))
|
||||
}
|
||||
Pattern::NumLiteral(a) => Pattern::NumLiteral(a),
|
||||
Pattern::NonBase10Literal {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
} => Pattern::NonBase10Literal {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
},
|
||||
Pattern::FloatLiteral(a) => Pattern::FloatLiteral(a),
|
||||
Pattern::StrLiteral(a) => Pattern::StrLiteral(a),
|
||||
Pattern::Underscore(a) => Pattern::Underscore(a),
|
||||
Pattern::Malformed(a) => Pattern::Malformed(a),
|
||||
Pattern::MalformedIdent(a, b) => Pattern::MalformedIdent(a, b),
|
||||
Pattern::QualifiedIdentifier { module_name, ident } => {
|
||||
Pattern::QualifiedIdentifier { module_name, ident }
|
||||
}
|
||||
Pattern::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
Pattern::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
Pattern::SingleQuote(a) => Pattern::NumLiteral(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
TypeAnnotation::Function(a, b) => TypeAnnotation::Function(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
),
|
||||
TypeAnnotation::Apply(a, b, c) => TypeAnnotation::Apply(a, b, c.remove_spaces(arena)),
|
||||
TypeAnnotation::BoundVariable(a) => TypeAnnotation::BoundVariable(a),
|
||||
TypeAnnotation::As(a, _, TypeHeader { name, vars }) => TypeAnnotation::As(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
&[],
|
||||
TypeHeader {
|
||||
name: name.remove_spaces(arena),
|
||||
vars: vars.remove_spaces(arena),
|
||||
},
|
||||
),
|
||||
TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record {
|
||||
fields: fields.remove_spaces(arena),
|
||||
ext: ext.remove_spaces(arena),
|
||||
},
|
||||
TypeAnnotation::TagUnion { ext, tags } => TypeAnnotation::TagUnion {
|
||||
ext: ext.remove_spaces(arena),
|
||||
tags: tags.remove_spaces(arena),
|
||||
},
|
||||
TypeAnnotation::Inferred => TypeAnnotation::Inferred,
|
||||
TypeAnnotation::Wildcard => TypeAnnotation::Wildcard,
|
||||
TypeAnnotation::Where(annot, has_clauses) => TypeAnnotation::Where(
|
||||
arena.alloc(annot.remove_spaces(arena)),
|
||||
arena.alloc(has_clauses.remove_spaces(arena)),
|
||||
),
|
||||
TypeAnnotation::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
TypeAnnotation::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
TypeAnnotation::Malformed(a) => TypeAnnotation::Malformed(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for HasClause<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
HasClause {
|
||||
var: self.var.remove_spaces(arena),
|
||||
ability: self.ability.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Tag<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Tag::Apply { name, args } => Tag::Apply {
|
||||
name: name.remove_spaces(arena),
|
||||
args: args.remove_spaces(arena),
|
||||
},
|
||||
Tag::Malformed(a) => Tag::Malformed(a),
|
||||
Tag::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
Tag::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Derived<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Derived::Has(derived) => Derived::Has(derived.remove_spaces(arena)),
|
||||
Derived::SpaceBefore(derived, _) | Derived::SpaceAfter(derived, _) => {
|
||||
derived.remove_spaces(arena)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue