mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 05:49:08 +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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue