mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 05:49:08 +00:00
489 lines
14 KiB
Rust
489 lines
14 KiB
Rust
use bumpalo::{collections::Vec, Bump};
|
|
use roc_parse::ast::{CommentOrNewline, Pattern, TypeAnnotation};
|
|
|
|
use crate::{
|
|
annotation::{Formattable, Newlines, Parens},
|
|
collection::Braces,
|
|
expr::merge_spaces_conservative,
|
|
spaces::{fmt_comments_only, fmt_spaces, fmt_spaces_no_blank_lines, NewlineAt, INDENT},
|
|
Buf, MigrationFlags,
|
|
};
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub struct Sp<'a> {
|
|
pub default_space: bool, // if true and comments is empty, use a space (' ')
|
|
pub force_newline: bool, // if true, force a newline (irrespectively of comments)
|
|
pub comments: &'a [CommentOrNewline<'a>],
|
|
}
|
|
|
|
impl<'a> Sp<'a> {
|
|
pub fn empty() -> Sp<'a> {
|
|
Sp {
|
|
force_newline: false,
|
|
default_space: false,
|
|
comments: &[],
|
|
}
|
|
}
|
|
pub fn space() -> Sp<'a> {
|
|
Sp {
|
|
force_newline: false,
|
|
default_space: true,
|
|
comments: &[],
|
|
}
|
|
}
|
|
|
|
pub fn with_space(sp: &'a [CommentOrNewline<'a>]) -> Self {
|
|
Sp {
|
|
force_newline: false,
|
|
default_space: true,
|
|
comments: sp,
|
|
}
|
|
}
|
|
|
|
pub fn maybe_with_space(space: bool, sp: &'a [CommentOrNewline<'a>]) -> Sp<'a> {
|
|
Sp {
|
|
force_newline: false,
|
|
default_space: space,
|
|
comments: sp,
|
|
}
|
|
}
|
|
|
|
pub fn force_newline(sp: &'a [CommentOrNewline<'a>]) -> Self {
|
|
Sp {
|
|
force_newline: true,
|
|
default_space: false,
|
|
comments: sp,
|
|
}
|
|
}
|
|
|
|
pub fn is_multiline(&self) -> bool {
|
|
self.force_newline || !self.comments.is_empty()
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a [CommentOrNewline<'a>]> for Sp<'a> {
|
|
fn from(comments: &'a [CommentOrNewline<'a>]) -> Self {
|
|
Sp {
|
|
force_newline: false,
|
|
default_space: false,
|
|
comments,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub enum Node<'a> {
|
|
Literal(&'a str),
|
|
Sequence {
|
|
first: &'a Node<'a>,
|
|
extra_indent_for_rest: bool,
|
|
rest: &'a [(Sp<'a>, Node<'a>)],
|
|
},
|
|
DelimitedSequence {
|
|
braces: Braces,
|
|
indent_items: bool,
|
|
items: &'a [DelimitedItem<'a>],
|
|
after: Sp<'a>,
|
|
},
|
|
CommaSequence {
|
|
allow_blank_lines: bool,
|
|
allow_newlines: bool,
|
|
indent_rest: bool,
|
|
first: &'a Node<'a>,
|
|
rest: &'a [Item<'a>],
|
|
},
|
|
|
|
// Temporary! TODO: translate these into proper Node elements
|
|
TypeAnnotation(TypeAnnotation<'a>),
|
|
Pattern(Pattern<'a>),
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub struct DelimitedItem<'a> {
|
|
pub before: &'a [CommentOrNewline<'a>],
|
|
pub newline: bool,
|
|
pub space: bool,
|
|
pub node: Node<'a>,
|
|
pub comma_after: bool,
|
|
}
|
|
|
|
impl<'a> DelimitedItem<'a> {
|
|
fn is_multiline(&self) -> bool {
|
|
self.newline || !self.before.is_empty() || self.node.is_multiline()
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub struct Item<'a> {
|
|
pub comma_before: bool,
|
|
pub before: &'a [CommentOrNewline<'a>],
|
|
pub newline: bool,
|
|
pub space: bool,
|
|
pub node: Node<'a>,
|
|
}
|
|
|
|
impl<'a> Item<'a> {
|
|
fn is_multiline(&self, allow_newlines: bool) -> bool {
|
|
let has_newlines = if allow_newlines {
|
|
!self.before.is_empty()
|
|
} else {
|
|
self.before.iter().any(|c| c.is_comment())
|
|
};
|
|
self.newline || has_newlines || self.node.is_multiline()
|
|
}
|
|
}
|
|
|
|
impl<'a> Node<'a> {
|
|
pub fn space_seq_3(
|
|
arena: &'a Bump,
|
|
a: Node<'a>,
|
|
b_sp: &'a [CommentOrNewline<'a>],
|
|
c: Node<'a>,
|
|
d_sp: &'a [CommentOrNewline<'a>],
|
|
e: Node<'a>,
|
|
) -> Node<'a> {
|
|
Node::Sequence {
|
|
first: arena.alloc(a),
|
|
extra_indent_for_rest: true,
|
|
rest: arena.alloc_slice_copy(&[(Sp::with_space(b_sp), c), (Sp::with_space(d_sp), e)]),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn parens_around_node<'b, 'a: 'b>(
|
|
arena: &'b Bump,
|
|
item: NodeInfo<'a>,
|
|
allow_space_before: bool,
|
|
) -> NodeInfo<'b> {
|
|
NodeInfo {
|
|
before: if allow_space_before { item.before } else { &[] },
|
|
node: Node::DelimitedSequence {
|
|
braces: Braces::Round,
|
|
indent_items: true,
|
|
items: arena.alloc_slice_copy(&[DelimitedItem {
|
|
before: if allow_space_before { &[] } else { item.before },
|
|
node: item.node,
|
|
newline: false,
|
|
space: false,
|
|
comma_after: false,
|
|
}]),
|
|
after: Sp::empty(),
|
|
},
|
|
// We move the comments/newlines to the outer scope, since they tend to migrate there when re-parsed
|
|
after: item.after,
|
|
needs_indent: true, // Maybe want to make parens outdentable?
|
|
prec: Prec::Term,
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub struct NodeInfo<'b> {
|
|
pub before: &'b [CommentOrNewline<'b>],
|
|
pub node: Node<'b>,
|
|
pub after: &'b [CommentOrNewline<'b>],
|
|
pub needs_indent: bool,
|
|
pub prec: Prec,
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
|
pub enum Prec {
|
|
Term,
|
|
Apply,
|
|
AsType,
|
|
FunctionType,
|
|
Outer,
|
|
}
|
|
|
|
impl From<Parens> for Prec {
|
|
fn from(parens: Parens) -> Self {
|
|
match parens {
|
|
Parens::NotNeeded => Prec::Outer,
|
|
Parens::InClosurePattern => Prec::Outer,
|
|
Parens::InApply => Prec::Apply,
|
|
Parens::InApplyLastArg => Prec::Apply,
|
|
Parens::InCollection => Prec::FunctionType,
|
|
Parens::InFunctionType => Prec::FunctionType,
|
|
Parens::InOperator => Prec::FunctionType,
|
|
Parens::InAsPattern => Prec::AsType,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'b> NodeInfo<'b> {
|
|
pub fn item(text: Node<'b>) -> NodeInfo<'b> {
|
|
NodeInfo {
|
|
before: &[],
|
|
node: text,
|
|
after: &[],
|
|
needs_indent: true,
|
|
prec: Prec::Term,
|
|
}
|
|
}
|
|
|
|
pub fn apply(
|
|
arena: &'b Bump,
|
|
first: NodeInfo<'b>,
|
|
args: impl Iterator<Item = NodeInfo<'b>>,
|
|
) -> NodeInfo<'b> {
|
|
let mut last_after = first.after;
|
|
let mut rest = Vec::with_capacity_in(args.size_hint().0, arena);
|
|
|
|
let mut multiline = false;
|
|
let mut indent_rest = true;
|
|
|
|
let mut it = args.peekable();
|
|
while let Some(arg) = it.next() {
|
|
let is_last = it.peek().is_none();
|
|
let arg = arg.add_parens(arena, Parens::InApply);
|
|
let before = merge_spaces_conservative(arena, last_after, arg.before);
|
|
|
|
if is_last
|
|
&& !multiline
|
|
&& arg.node.is_multiline()
|
|
&& !arg.needs_indent
|
|
&& before.is_empty()
|
|
{
|
|
// We can outdent the last argument, e.g.:
|
|
// foo {
|
|
// a:b,
|
|
// }
|
|
// In this case, the argument does its own indentation.
|
|
indent_rest = false;
|
|
}
|
|
|
|
multiline |= arg.node.is_multiline() || !before.is_empty();
|
|
last_after = arg.after;
|
|
rest.push(Item {
|
|
before,
|
|
comma_before: false,
|
|
newline: false,
|
|
space: true,
|
|
node: arg.node,
|
|
});
|
|
}
|
|
|
|
NodeInfo {
|
|
before: first.before,
|
|
prec: if rest.is_empty() {
|
|
Prec::Term
|
|
} else {
|
|
Prec::Apply
|
|
},
|
|
node: Node::CommaSequence {
|
|
allow_blank_lines: false,
|
|
allow_newlines: true,
|
|
indent_rest,
|
|
first: arena.alloc(first.node),
|
|
rest: rest.into_bump_slice(),
|
|
},
|
|
after: last_after,
|
|
needs_indent: true,
|
|
}
|
|
}
|
|
|
|
pub fn add_parens<'a>(&self, arena: &'a Bump, parens: Parens) -> NodeInfo<'a>
|
|
where
|
|
'b: 'a,
|
|
{
|
|
if self.prec < parens.into() {
|
|
*self
|
|
} else {
|
|
parens_around_node(arena, *self, true)
|
|
}
|
|
}
|
|
|
|
pub fn add_ty_ext_parens<'a>(&self, arena: &'a Bump) -> NodeInfo<'a>
|
|
where
|
|
'b: 'a,
|
|
{
|
|
if self.prec <= Prec::Term && self.before.is_empty() {
|
|
*self
|
|
} else {
|
|
parens_around_node(arena, *self, false)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub trait Nodify<'a> {
|
|
fn to_node<'b>(&'a self, arena: &'b Bump, flags: MigrationFlags) -> NodeInfo<'b>
|
|
where
|
|
'a: 'b;
|
|
}
|
|
|
|
fn fmt_sp(buf: &mut Buf, sp: Sp<'_>, indent: u16) {
|
|
if !sp.comments.is_empty() {
|
|
fmt_spaces(buf, sp.comments.iter(), indent);
|
|
} else if sp.force_newline {
|
|
buf.ensure_ends_with_newline();
|
|
} else if sp.default_space {
|
|
buf.spaces(1);
|
|
}
|
|
}
|
|
|
|
impl<'a> Formattable for Node<'a> {
|
|
fn is_multiline(&self) -> bool {
|
|
match self {
|
|
Node::Sequence {
|
|
first,
|
|
extra_indent_for_rest: _,
|
|
rest,
|
|
} => {
|
|
first.is_multiline()
|
|
|| rest
|
|
.iter()
|
|
.any(|(sp, l)| l.is_multiline() || sp.is_multiline())
|
|
}
|
|
Node::DelimitedSequence {
|
|
braces: _,
|
|
indent_items: _,
|
|
items,
|
|
after,
|
|
} => after.is_multiline() || items.iter().any(|item| item.is_multiline()),
|
|
Node::CommaSequence {
|
|
allow_blank_lines: _,
|
|
allow_newlines,
|
|
indent_rest: _,
|
|
first,
|
|
rest,
|
|
} => first.is_multiline() || rest.iter().any(|item| item.is_multiline(*allow_newlines)),
|
|
Node::Literal(_) => false,
|
|
Node::TypeAnnotation(type_annotation) => type_annotation.is_multiline(),
|
|
Node::Pattern(pat) => pat.is_multiline(),
|
|
}
|
|
}
|
|
|
|
fn format_with_options(&self, buf: &mut Buf, parens: Parens, newlines: Newlines, indent: u16) {
|
|
match self {
|
|
Node::DelimitedSequence {
|
|
braces,
|
|
indent_items,
|
|
items: lefts,
|
|
after: right,
|
|
} => {
|
|
buf.indent(indent);
|
|
buf.push(braces.start());
|
|
|
|
let inner_indent = if *indent_items {
|
|
indent + INDENT
|
|
} else {
|
|
indent
|
|
};
|
|
|
|
for item in *lefts {
|
|
fmt_spaces(buf, item.before.iter(), inner_indent);
|
|
if item.newline {
|
|
buf.ensure_ends_with_newline();
|
|
} else if item.space {
|
|
buf.ensure_ends_with_whitespace();
|
|
}
|
|
item.node
|
|
.format_with_options(buf, parens, newlines, inner_indent);
|
|
if item.comma_after {
|
|
buf.push(',');
|
|
}
|
|
}
|
|
fmt_sp(buf, *right, inner_indent);
|
|
|
|
buf.indent(indent);
|
|
buf.push(braces.end());
|
|
}
|
|
Node::Sequence {
|
|
first,
|
|
extra_indent_for_rest,
|
|
rest,
|
|
} => {
|
|
buf.indent(indent);
|
|
let cur_indent = buf.cur_line_indent();
|
|
first.format_with_options(buf, parens, newlines, indent);
|
|
let next_indent = if *extra_indent_for_rest {
|
|
cur_indent + INDENT
|
|
} else {
|
|
indent
|
|
};
|
|
|
|
for (sp, l) in *rest {
|
|
fmt_sp(buf, *sp, next_indent);
|
|
l.format_with_options(buf, parens, newlines, next_indent);
|
|
}
|
|
}
|
|
Node::CommaSequence {
|
|
allow_blank_lines,
|
|
allow_newlines,
|
|
indent_rest,
|
|
first,
|
|
rest,
|
|
} => {
|
|
buf.indent(indent);
|
|
let inner_indent = if *indent_rest {
|
|
indent + INDENT
|
|
} else {
|
|
indent
|
|
};
|
|
first.format_with_options(buf, parens, newlines, indent);
|
|
|
|
for item in *rest {
|
|
if item.comma_before {
|
|
buf.push(',');
|
|
}
|
|
if *allow_blank_lines {
|
|
fmt_spaces(buf, item.before.iter(), indent);
|
|
} else if *allow_newlines {
|
|
fmt_spaces_no_blank_lines(buf, item.before.iter(), inner_indent);
|
|
} else {
|
|
fmt_comments_only(buf, item.before.iter(), NewlineAt::Bottom, inner_indent);
|
|
}
|
|
if item.newline {
|
|
buf.ensure_ends_with_newline();
|
|
} else if item.space {
|
|
buf.ensure_ends_with_whitespace();
|
|
}
|
|
item.node
|
|
.format_with_options(buf, parens, newlines, inner_indent);
|
|
}
|
|
}
|
|
Node::Literal(text) => {
|
|
buf.indent(indent);
|
|
buf.push_str(text);
|
|
}
|
|
Node::TypeAnnotation(type_annotation) => {
|
|
type_annotation.format_with_options(buf, parens, newlines, indent);
|
|
}
|
|
Node::Pattern(pat) => {
|
|
pat.format_with_options(buf, parens, newlines, indent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct NodeSequenceBuilder<'a> {
|
|
first: Node<'a>,
|
|
extra_indent_for_rest: bool,
|
|
rest: Vec<'a, (Sp<'a>, Node<'a>)>,
|
|
}
|
|
|
|
impl<'a> NodeSequenceBuilder<'a> {
|
|
pub fn new(
|
|
arena: &'a Bump,
|
|
first: Node<'a>,
|
|
capacity: usize,
|
|
extra_indent_for_rest: bool,
|
|
) -> Self {
|
|
Self {
|
|
first,
|
|
extra_indent_for_rest,
|
|
rest: Vec::with_capacity_in(capacity, arena),
|
|
}
|
|
}
|
|
|
|
pub fn push(&mut self, sp: Sp<'a>, literal: Node<'a>) {
|
|
self.rest.push((sp, literal));
|
|
}
|
|
|
|
pub fn build(self) -> Node<'a> {
|
|
Node::Sequence {
|
|
first: self.rest.bump().alloc(self.first),
|
|
extra_indent_for_rest: self.extra_indent_for_rest,
|
|
rest: self.rest.into_bump_slice(),
|
|
}
|
|
}
|
|
}
|