mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 16:44:33 +00:00
Reorganize fmt and module
This commit is contained in:
parent
ed23e23a54
commit
4926bfbc3a
17 changed files with 1192 additions and 771 deletions
|
@ -1,4 +1,5 @@
|
||||||
use ident::VariantName;
|
use ident::{UnqualifiedIdent, VariantName};
|
||||||
|
use module::ModuleName;
|
||||||
|
|
||||||
/// A globally unique identifier, used for both vars and variants.
|
/// A globally unique identifier, used for both vars and variants.
|
||||||
/// It will be used directly in code gen.
|
/// It will be used directly in code gen.
|
||||||
|
@ -26,6 +27,13 @@ impl Symbol {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_module<'a>(
|
||||||
|
module_name: &'a ModuleName<'a>,
|
||||||
|
ident: &'a UnqualifiedIdent<'a>,
|
||||||
|
) -> Symbol {
|
||||||
|
Symbol(format!("{}.{}", module_name.as_str(), ident.as_str()).into())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn into_boxed_str(self) -> Box<str> {
|
pub fn into_boxed_str(self) -> Box<str> {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
85
src/fmt.rs
85
src/fmt.rs
|
@ -1,85 +0,0 @@
|
||||||
use parse::ast::{AssignedField, Expr, Pattern};
|
|
||||||
|
|
||||||
pub fn is_multiline_expr<'a>(expr: &'a Expr<'a>) -> bool {
|
|
||||||
use parse::ast::Expr::*;
|
|
||||||
// TODO cache these answers using a Map<Pointer, bool>, so
|
|
||||||
// we don't have to traverse subexpressions repeatedly
|
|
||||||
|
|
||||||
match expr {
|
|
||||||
// Return whether these spaces contain any Newlines
|
|
||||||
SpaceBefore(_, spaces) | SpaceAfter(_, spaces) => {
|
|
||||||
spaces.iter().any(|space| space.contains_newline())
|
|
||||||
}
|
|
||||||
|
|
||||||
// These expressions never have newlines
|
|
||||||
Float(_)
|
|
||||||
| Int(_)
|
|
||||||
| HexInt(_)
|
|
||||||
| OctalInt(_)
|
|
||||||
| BinaryInt(_)
|
|
||||||
| Str(_)
|
|
||||||
| Field(_, _)
|
|
||||||
| QualifiedField(_, _)
|
|
||||||
| AccessorFunction(_)
|
|
||||||
| Var(_, _)
|
|
||||||
| MalformedIdent(_)
|
|
||||||
| MalformedClosure
|
|
||||||
| Variant(_, _) => false,
|
|
||||||
|
|
||||||
// These expressions always have newlines
|
|
||||||
Defs(_, _) | Case(_, _) => true,
|
|
||||||
|
|
||||||
List(elems) => elems
|
|
||||||
.iter()
|
|
||||||
.any(|loc_expr| is_multiline_expr(&loc_expr.value)),
|
|
||||||
|
|
||||||
BlockStr(lines) => lines.len() > 1,
|
|
||||||
Apply(loc_expr, args, _) => {
|
|
||||||
is_multiline_expr(&loc_expr.value)
|
|
||||||
|| args.iter().any(|loc_arg| is_multiline_expr(&loc_arg.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
If((loc_cond, loc_if_true, loc_if_false)) => {
|
|
||||||
is_multiline_expr(&loc_cond.value)
|
|
||||||
|| is_multiline_expr(&loc_if_true.value)
|
|
||||||
|| is_multiline_expr(&loc_if_false.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
BinOp((loc_left, _, loc_right)) => {
|
|
||||||
is_multiline_expr(&loc_left.value) || is_multiline_expr(&loc_right.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
UnaryOp(loc_subexpr, _) | PrecedenceConflict(_, _, loc_subexpr) => {
|
|
||||||
is_multiline_expr(&loc_subexpr.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
ParensAround(subexpr) => is_multiline_expr(&subexpr),
|
|
||||||
|
|
||||||
Closure(loc_patterns, loc_body) => {
|
|
||||||
// check the body first because it's more likely to be multiline
|
|
||||||
is_multiline_expr(&loc_body.value)
|
|
||||||
|| loc_patterns
|
|
||||||
.iter()
|
|
||||||
.any(|loc_pattern| is_multiline_pattern(&loc_pattern.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
Record(loc_fields) => loc_fields
|
|
||||||
.iter()
|
|
||||||
.any(|loc_field| is_multiline_field(&loc_field.value)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_multiline_field<'a, Val>(field: &'a AssignedField<'a, Val>) -> bool {
|
|
||||||
use self::AssignedField::*;
|
|
||||||
|
|
||||||
match field {
|
|
||||||
LabeledValue(_, spaces, _) => !spaces.is_empty(),
|
|
||||||
LabelOnly(_) => false,
|
|
||||||
AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => true,
|
|
||||||
Malformed(text) => text.chars().any(|c| c == '\n'),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_multiline_pattern<'a>(_pattern: &'a Pattern<'a>) -> bool {
|
|
||||||
panic!("TODO return iff there are any newlines")
|
|
||||||
}
|
|
28
src/fmt/def.rs
Normal file
28
src/fmt/def.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
use fmt::expr::fmt_expr;
|
||||||
|
use fmt::pattern::fmt_pattern;
|
||||||
|
use fmt::spaces::fmt_spaces;
|
||||||
|
|
||||||
|
use bumpalo::collections::String;
|
||||||
|
use parse::ast::Def;
|
||||||
|
|
||||||
|
pub fn fmt_def<'a>(buf: &mut String<'a>, def: &'a Def<'a>, indent: u16) {
|
||||||
|
match def {
|
||||||
|
Def::Annotation(_, _) => panic!("TODO have format_def support Annotation"),
|
||||||
|
Def::Body(loc_pattern, loc_expr) => {
|
||||||
|
fmt_pattern(buf, &loc_pattern.value, indent, true);
|
||||||
|
buf.push_str(" = ");
|
||||||
|
fmt_expr(buf, &loc_expr.value, indent, false);
|
||||||
|
}
|
||||||
|
Def::CustomType(_, _) => panic!("TODO have format_def support CustomType"),
|
||||||
|
Def::TypeAlias(_, _) => panic!("TODO have format_def support TypeAlias"),
|
||||||
|
Def::SpaceBefore(sub_def, spaces) => {
|
||||||
|
fmt_spaces(buf, spaces.iter(), indent);
|
||||||
|
fmt_def(buf, sub_def, indent);
|
||||||
|
}
|
||||||
|
Def::SpaceAfter(sub_def, spaces) => {
|
||||||
|
fmt_def(buf, sub_def, indent);
|
||||||
|
|
||||||
|
fmt_spaces(buf, spaces.iter(), indent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
359
src/fmt/expr.rs
Normal file
359
src/fmt/expr.rs
Normal file
|
@ -0,0 +1,359 @@
|
||||||
|
use fmt::def::fmt_def;
|
||||||
|
use fmt::pattern::fmt_pattern;
|
||||||
|
use fmt::spaces::{add_spaces, fmt_comments_only, fmt_spaces, newline, INDENT};
|
||||||
|
|
||||||
|
use bumpalo::collections::String;
|
||||||
|
use parse::ast::{AssignedField, Expr, Pattern};
|
||||||
|
|
||||||
|
pub fn fmt_expr<'a>(
|
||||||
|
buf: &mut String<'a>,
|
||||||
|
expr: &'a Expr<'a>,
|
||||||
|
indent: u16,
|
||||||
|
apply_needs_parens: bool,
|
||||||
|
) {
|
||||||
|
use self::Expr::*;
|
||||||
|
|
||||||
|
match expr {
|
||||||
|
SpaceBefore(sub_expr, spaces) => {
|
||||||
|
fmt_spaces(buf, spaces.iter(), indent);
|
||||||
|
fmt_expr(buf, sub_expr, indent, apply_needs_parens);
|
||||||
|
}
|
||||||
|
SpaceAfter(sub_expr, spaces) => {
|
||||||
|
fmt_expr(buf, sub_expr, indent, apply_needs_parens);
|
||||||
|
fmt_spaces(buf, spaces.iter(), indent);
|
||||||
|
}
|
||||||
|
ParensAround(sub_expr) => {
|
||||||
|
buf.push('(');
|
||||||
|
fmt_expr(buf, sub_expr, indent, false);
|
||||||
|
buf.push(')');
|
||||||
|
}
|
||||||
|
Str(string) => {
|
||||||
|
buf.push('"');
|
||||||
|
buf.push_str(string);
|
||||||
|
buf.push('"');
|
||||||
|
}
|
||||||
|
Var(module_parts, name) => {
|
||||||
|
for part in module_parts.iter() {
|
||||||
|
buf.push_str(part);
|
||||||
|
buf.push('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push_str(name);
|
||||||
|
}
|
||||||
|
Apply(loc_expr, loc_args, _) => {
|
||||||
|
if apply_needs_parens {
|
||||||
|
buf.push('(');
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt_expr(buf, &loc_expr.value, indent, true);
|
||||||
|
|
||||||
|
for loc_arg in loc_args {
|
||||||
|
buf.push(' ');
|
||||||
|
|
||||||
|
fmt_expr(buf, &loc_arg.value, indent, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if apply_needs_parens {
|
||||||
|
buf.push(')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BlockStr(lines) => {
|
||||||
|
buf.push_str("\"\"\"");
|
||||||
|
for line in lines.iter() {
|
||||||
|
buf.push_str(line);
|
||||||
|
}
|
||||||
|
buf.push_str("\"\"\"");
|
||||||
|
}
|
||||||
|
Int(string) => buf.push_str(string),
|
||||||
|
Float(string) => buf.push_str(string),
|
||||||
|
HexInt(string) => {
|
||||||
|
buf.push('0');
|
||||||
|
buf.push('x');
|
||||||
|
buf.push_str(string);
|
||||||
|
}
|
||||||
|
BinaryInt(string) => {
|
||||||
|
buf.push('0');
|
||||||
|
buf.push('b');
|
||||||
|
buf.push_str(string);
|
||||||
|
}
|
||||||
|
OctalInt(string) => {
|
||||||
|
buf.push('0');
|
||||||
|
buf.push('o');
|
||||||
|
buf.push_str(string);
|
||||||
|
}
|
||||||
|
Record(loc_fields) => {
|
||||||
|
buf.push('{');
|
||||||
|
|
||||||
|
let is_multiline = loc_fields
|
||||||
|
.iter()
|
||||||
|
.any(|loc_field| is_multiline_field(&loc_field.value));
|
||||||
|
|
||||||
|
let mut iter = loc_fields.iter().peekable();
|
||||||
|
let field_indent = if is_multiline {
|
||||||
|
indent + INDENT
|
||||||
|
} else {
|
||||||
|
if !loc_fields.is_empty() {
|
||||||
|
buf.push(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
indent
|
||||||
|
};
|
||||||
|
|
||||||
|
while let Some(field) = iter.next() {
|
||||||
|
fmt_field(
|
||||||
|
buf,
|
||||||
|
&field.value,
|
||||||
|
is_multiline,
|
||||||
|
field_indent,
|
||||||
|
apply_needs_parens,
|
||||||
|
);
|
||||||
|
|
||||||
|
if iter.peek().is_some() {
|
||||||
|
buf.push(',');
|
||||||
|
|
||||||
|
if !is_multiline {
|
||||||
|
buf.push(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_multiline {
|
||||||
|
buf.push('\n');
|
||||||
|
} else if !loc_fields.is_empty() {
|
||||||
|
buf.push(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push('}');
|
||||||
|
}
|
||||||
|
Closure(loc_patterns, loc_ret) => {
|
||||||
|
buf.push('\\');
|
||||||
|
|
||||||
|
for loc_pattern in loc_patterns.iter() {
|
||||||
|
fmt_pattern(buf, &loc_pattern.value, indent, true);
|
||||||
|
|
||||||
|
buf.push(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_multiline = is_multiline_expr(&loc_ret.value);
|
||||||
|
|
||||||
|
// If the body is multiline, go down a line and indent.
|
||||||
|
let indent = if is_multiline {
|
||||||
|
indent + INDENT
|
||||||
|
} else {
|
||||||
|
indent
|
||||||
|
};
|
||||||
|
|
||||||
|
buf.push_str("->");
|
||||||
|
|
||||||
|
let newline_is_next = match &loc_ret.value {
|
||||||
|
SpaceBefore(_, _) => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if !newline_is_next {
|
||||||
|
// Push a space after the "->" preceding this.
|
||||||
|
buf.push(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt_expr(buf, &loc_ret.value, indent, false);
|
||||||
|
}
|
||||||
|
Defs(defs, ret) => {
|
||||||
|
// It should theoretically be impossible to *parse* an empty defs list.
|
||||||
|
// (Canonicalization can remove defs later, but that hasn't happened yet!)
|
||||||
|
debug_assert!(!defs.is_empty());
|
||||||
|
|
||||||
|
// The first def is located last in the list, because it gets added there
|
||||||
|
// with .push() for efficiency. (The order of parsed defs doesn't
|
||||||
|
// matter because canonicalization sorts them anyway.)
|
||||||
|
// The other defs in the list are in their usual order.
|
||||||
|
if let Some(loc_first_def) = defs.last() {
|
||||||
|
let other_spaced_defs = &defs[0..defs.len() - 1];
|
||||||
|
|
||||||
|
fmt_def(buf, &loc_first_def.value, indent);
|
||||||
|
|
||||||
|
for loc_def in other_spaced_defs.iter() {
|
||||||
|
fmt_def(buf, &loc_def.value, indent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Even if there were no defs, which theoretically should never happen,
|
||||||
|
// still print the return value.
|
||||||
|
fmt_expr(buf, &ret.value, indent, false);
|
||||||
|
}
|
||||||
|
If((loc_condition, loc_then, loc_else)) => {
|
||||||
|
buf.push_str("if ");
|
||||||
|
fmt_expr(buf, &loc_condition.value, indent, false);
|
||||||
|
buf.push_str(" then ");
|
||||||
|
fmt_expr(buf, &loc_then.value, indent, false);
|
||||||
|
buf.push_str(" else ");
|
||||||
|
fmt_expr(buf, &loc_else.value, indent, false);
|
||||||
|
}
|
||||||
|
Case(loc_condition, branches) => {
|
||||||
|
buf.push_str("case ");
|
||||||
|
fmt_expr(buf, &loc_condition.value, indent, false);
|
||||||
|
buf.push_str(" when\n");
|
||||||
|
|
||||||
|
let mut it = branches.iter().peekable();
|
||||||
|
while let Some((pattern, expr)) = it.next() {
|
||||||
|
add_spaces(buf, indent + INDENT);
|
||||||
|
|
||||||
|
match pattern.value {
|
||||||
|
Pattern::SpaceBefore(nested, spaces) => {
|
||||||
|
fmt_comments_only(buf, spaces.iter(), indent + INDENT);
|
||||||
|
fmt_pattern(buf, nested, indent + INDENT, false);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
fmt_pattern(buf, &pattern.value, indent + INDENT, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push_str(" ->\n");
|
||||||
|
|
||||||
|
add_spaces(buf, indent + (INDENT * 2));
|
||||||
|
match expr.value {
|
||||||
|
Expr::SpaceBefore(nested, spaces) => {
|
||||||
|
fmt_comments_only(buf, spaces.iter(), indent + (INDENT * 2));
|
||||||
|
fmt_expr(buf, &nested, indent + (INDENT * 2), false);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
fmt_expr(buf, &expr.value, indent + (INDENT * 2), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if it.peek().is_some() {
|
||||||
|
buf.push('\n');
|
||||||
|
buf.push('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => panic!("TODO implement Display for AST variant {:?}", other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fmt_field<'a>(
|
||||||
|
buf: &mut String<'a>,
|
||||||
|
assigned_field: &'a AssignedField<'a, Expr<'a>>,
|
||||||
|
is_multiline: bool,
|
||||||
|
indent: u16,
|
||||||
|
apply_needs_parens: bool,
|
||||||
|
) {
|
||||||
|
use self::AssignedField::*;
|
||||||
|
|
||||||
|
match assigned_field {
|
||||||
|
LabeledValue(name, spaces, value) => {
|
||||||
|
if is_multiline {
|
||||||
|
newline(buf, indent);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push_str(name.value);
|
||||||
|
|
||||||
|
if !spaces.is_empty() {
|
||||||
|
fmt_spaces(buf, spaces.iter(), indent);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push(':');
|
||||||
|
buf.push(' ');
|
||||||
|
fmt_expr(buf, &value.value, indent, apply_needs_parens);
|
||||||
|
}
|
||||||
|
LabelOnly(name) => {
|
||||||
|
if is_multiline {
|
||||||
|
newline(buf, indent);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push_str(name.value);
|
||||||
|
}
|
||||||
|
AssignedField::SpaceBefore(sub_expr, spaces) => {
|
||||||
|
fmt_comments_only(buf, spaces.iter(), indent);
|
||||||
|
fmt_field(buf, sub_expr, is_multiline, indent, apply_needs_parens);
|
||||||
|
}
|
||||||
|
AssignedField::SpaceAfter(sub_expr, spaces) => {
|
||||||
|
fmt_field(buf, sub_expr, is_multiline, indent, apply_needs_parens);
|
||||||
|
fmt_comments_only(buf, spaces.iter(), indent);
|
||||||
|
}
|
||||||
|
Malformed(string) => buf.push_str(string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_multiline_expr<'a>(expr: &'a Expr<'a>) -> bool {
|
||||||
|
use parse::ast::Expr::*;
|
||||||
|
// TODO cache these answers using a Map<Pointer, bool>, so
|
||||||
|
// we don't have to traverse subexpressions repeatedly
|
||||||
|
|
||||||
|
match expr {
|
||||||
|
// Return whether these spaces contain any Newlines
|
||||||
|
SpaceBefore(_, spaces) | SpaceAfter(_, spaces) => {
|
||||||
|
spaces.iter().any(|space| space.contains_newline())
|
||||||
|
}
|
||||||
|
|
||||||
|
// These expressions never have newlines
|
||||||
|
Float(_)
|
||||||
|
| Int(_)
|
||||||
|
| HexInt(_)
|
||||||
|
| OctalInt(_)
|
||||||
|
| BinaryInt(_)
|
||||||
|
| Str(_)
|
||||||
|
| Field(_, _)
|
||||||
|
| QualifiedField(_, _)
|
||||||
|
| AccessorFunction(_)
|
||||||
|
| Var(_, _)
|
||||||
|
| MalformedIdent(_)
|
||||||
|
| MalformedClosure
|
||||||
|
| Variant(_, _) => false,
|
||||||
|
|
||||||
|
// These expressions always have newlines
|
||||||
|
Defs(_, _) | Case(_, _) => true,
|
||||||
|
|
||||||
|
List(elems) => elems
|
||||||
|
.iter()
|
||||||
|
.any(|loc_expr| is_multiline_expr(&loc_expr.value)),
|
||||||
|
|
||||||
|
BlockStr(lines) => lines.len() > 1,
|
||||||
|
Apply(loc_expr, args, _) => {
|
||||||
|
is_multiline_expr(&loc_expr.value)
|
||||||
|
|| args.iter().any(|loc_arg| is_multiline_expr(&loc_arg.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
If((loc_cond, loc_if_true, loc_if_false)) => {
|
||||||
|
is_multiline_expr(&loc_cond.value)
|
||||||
|
|| is_multiline_expr(&loc_if_true.value)
|
||||||
|
|| is_multiline_expr(&loc_if_false.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
BinOp((loc_left, _, loc_right)) => {
|
||||||
|
is_multiline_expr(&loc_left.value) || is_multiline_expr(&loc_right.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
UnaryOp(loc_subexpr, _) | PrecedenceConflict(_, _, loc_subexpr) => {
|
||||||
|
is_multiline_expr(&loc_subexpr.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
ParensAround(subexpr) => is_multiline_expr(&subexpr),
|
||||||
|
|
||||||
|
Closure(loc_patterns, loc_body) => {
|
||||||
|
// check the body first because it's more likely to be multiline
|
||||||
|
is_multiline_expr(&loc_body.value)
|
||||||
|
|| loc_patterns
|
||||||
|
.iter()
|
||||||
|
.any(|loc_pattern| is_multiline_pattern(&loc_pattern.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
Record(loc_fields) => loc_fields
|
||||||
|
.iter()
|
||||||
|
.any(|loc_field| is_multiline_field(&loc_field.value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_multiline_field<'a, Val>(field: &'a AssignedField<'a, Val>) -> bool {
|
||||||
|
use self::AssignedField::*;
|
||||||
|
|
||||||
|
match field {
|
||||||
|
LabeledValue(_, spaces, _) => !spaces.is_empty(),
|
||||||
|
LabelOnly(_) => false,
|
||||||
|
AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => true,
|
||||||
|
Malformed(text) => text.chars().any(|c| c == '\n'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_multiline_pattern<'a>(_pattern: &'a Pattern<'a>) -> bool {
|
||||||
|
panic!("TODO return iff there are any newlines")
|
||||||
|
}
|
5
src/fmt/mod.rs
Normal file
5
src/fmt/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
pub mod def;
|
||||||
|
pub mod expr;
|
||||||
|
pub mod module;
|
||||||
|
pub mod pattern;
|
||||||
|
pub mod spaces;
|
123
src/fmt/module.rs
Normal file
123
src/fmt/module.rs
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
use bumpalo::collections::{String, Vec};
|
||||||
|
use fmt::def::fmt_def;
|
||||||
|
use fmt::spaces::{fmt_spaces, INDENT};
|
||||||
|
use parse::ast::{AppHeader, ExposesEntry, ImportsEntry, InterfaceHeader, Module};
|
||||||
|
use region::Located;
|
||||||
|
|
||||||
|
pub fn fmt_module<'a>(buf: &mut String<'a>, module: &'a Module<'a>) {
|
||||||
|
match module {
|
||||||
|
Module::Interface { header, defs } => {
|
||||||
|
fmt_interface_header(buf, header);
|
||||||
|
|
||||||
|
for loc_def in defs {
|
||||||
|
fmt_def(buf, &loc_def.value, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Module::App { header, defs } => {
|
||||||
|
fmt_app_header(buf, header);
|
||||||
|
for loc_def in defs {
|
||||||
|
fmt_def(buf, &loc_def.value, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fmt_interface_header<'a>(buf: &mut String<'a>, header: &'a InterfaceHeader<'a>) {
|
||||||
|
buf.push_str("interface");
|
||||||
|
|
||||||
|
// module name
|
||||||
|
if header.after_interface.is_empty() {
|
||||||
|
buf.push(' ');
|
||||||
|
} else {
|
||||||
|
fmt_spaces(buf, header.after_interface.iter(), INDENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push_str(header.name.value.as_str());
|
||||||
|
|
||||||
|
// exposes
|
||||||
|
if header.before_exposes.is_empty() {
|
||||||
|
buf.push(' ');
|
||||||
|
} else {
|
||||||
|
fmt_spaces(buf, header.before_exposes.iter(), INDENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push_str("exposes");
|
||||||
|
|
||||||
|
if header.after_exposes.is_empty() {
|
||||||
|
buf.push(' ');
|
||||||
|
} else {
|
||||||
|
fmt_spaces(buf, header.after_exposes.iter(), INDENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt_exposes(buf, &header.exposes);
|
||||||
|
|
||||||
|
// imports
|
||||||
|
if header.before_imports.is_empty() {
|
||||||
|
buf.push(' ');
|
||||||
|
} else {
|
||||||
|
fmt_spaces(buf, header.before_imports.iter(), INDENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push_str("imports");
|
||||||
|
|
||||||
|
if header.after_imports.is_empty() {
|
||||||
|
buf.push(' ');
|
||||||
|
} else {
|
||||||
|
fmt_spaces(buf, header.after_imports.iter(), INDENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt_imports(buf, &header.imports);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fmt_app_header<'a>(buf: &mut String<'a>, header: &'a AppHeader<'a>) {
|
||||||
|
buf.push_str("app");
|
||||||
|
|
||||||
|
// imports
|
||||||
|
fmt_spaces(buf, header.before_imports.iter(), INDENT);
|
||||||
|
fmt_imports(buf, &header.imports);
|
||||||
|
fmt_spaces(buf, header.after_imports.iter(), INDENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fmt_imports<'a>(buf: &mut String<'a>, loc_entries: &'a Vec<'a, Located<ImportsEntry<'a>>>) {
|
||||||
|
buf.push('[');
|
||||||
|
|
||||||
|
if !loc_entries.is_empty() {
|
||||||
|
buf.push(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
for loc_entry in loc_entries {
|
||||||
|
fmt_imports_entry(buf, &loc_entry.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !loc_entries.is_empty() {
|
||||||
|
buf.push(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push(']');
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fmt_imports_entry<'a>(buf: &mut String<'a>, entry: &'a ImportsEntry<'a>) {
|
||||||
|
panic!("TODO fmt import entry");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fmt_exposes<'a>(buf: &mut String<'a>, loc_entries: &'a Vec<'a, Located<ExposesEntry<'a>>>) {
|
||||||
|
buf.push('[');
|
||||||
|
|
||||||
|
if !loc_entries.is_empty() {
|
||||||
|
buf.push(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
for loc_entry in loc_entries {
|
||||||
|
fmt_exposes_entry(buf, &loc_entry.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !loc_entries.is_empty() {
|
||||||
|
buf.push(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push(']');
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fmt_exposes_entry<'a>(buf: &mut String<'a>, entry: &'a ExposesEntry<'a>) {
|
||||||
|
panic!("TODO fmt import entry");
|
||||||
|
}
|
99
src/fmt/pattern.rs
Normal file
99
src/fmt/pattern.rs
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
use fmt::spaces::fmt_spaces;
|
||||||
|
|
||||||
|
use bumpalo::collections::String;
|
||||||
|
use parse::ast::Pattern;
|
||||||
|
|
||||||
|
pub fn fmt_pattern<'a>(
|
||||||
|
buf: &mut String<'a>,
|
||||||
|
pattern: &'a Pattern<'a>,
|
||||||
|
indent: u16,
|
||||||
|
apply_needs_parens: bool,
|
||||||
|
) {
|
||||||
|
use self::Pattern::*;
|
||||||
|
|
||||||
|
match pattern {
|
||||||
|
Identifier(string) => buf.push_str(string),
|
||||||
|
Variant(module_parts, name) => {
|
||||||
|
for part in module_parts.iter() {
|
||||||
|
buf.push_str(part);
|
||||||
|
buf.push('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push_str(name);
|
||||||
|
}
|
||||||
|
Apply(loc_pattern, loc_arg_patterns) => {
|
||||||
|
if apply_needs_parens {
|
||||||
|
buf.push('(');
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt_pattern(buf, &loc_pattern.value, indent, true);
|
||||||
|
|
||||||
|
for loc_arg in loc_arg_patterns.iter() {
|
||||||
|
buf.push(' ');
|
||||||
|
fmt_pattern(buf, &loc_arg.value, indent, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if apply_needs_parens {
|
||||||
|
buf.push(')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RecordDestructure(loc_patterns) => {
|
||||||
|
buf.push_str("{ ");
|
||||||
|
|
||||||
|
let mut is_first = true;
|
||||||
|
|
||||||
|
for loc_pattern in loc_patterns {
|
||||||
|
if is_first {
|
||||||
|
is_first = false;
|
||||||
|
} else {
|
||||||
|
buf.push_str(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt_pattern(buf, &loc_pattern.value, indent, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push_str(" }");
|
||||||
|
}
|
||||||
|
|
||||||
|
RecordField(name, loc_pattern) => {
|
||||||
|
buf.push_str(name);
|
||||||
|
buf.push_str(": ");
|
||||||
|
fmt_pattern(buf, &loc_pattern.value, indent, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
IntLiteral(string) => buf.push_str(string),
|
||||||
|
HexIntLiteral(string) => buf.push_str(string),
|
||||||
|
OctalIntLiteral(string) => buf.push_str(string),
|
||||||
|
BinaryIntLiteral(string) => buf.push_str(string),
|
||||||
|
FloatLiteral(string) => buf.push_str(string),
|
||||||
|
StrLiteral(string) => buf.push_str(string),
|
||||||
|
BlockStrLiteral(lines) => {
|
||||||
|
for line in *lines {
|
||||||
|
buf.push_str(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EmptyRecordLiteral => buf.push_str("{}"),
|
||||||
|
Underscore => buf.push('_'),
|
||||||
|
|
||||||
|
// Space
|
||||||
|
SpaceBefore(sub_pattern, spaces) => {
|
||||||
|
fmt_spaces(buf, spaces.iter(), indent);
|
||||||
|
fmt_pattern(buf, sub_pattern, indent, apply_needs_parens);
|
||||||
|
}
|
||||||
|
SpaceAfter(sub_pattern, spaces) => {
|
||||||
|
fmt_pattern(buf, sub_pattern, indent, apply_needs_parens);
|
||||||
|
fmt_spaces(buf, spaces.iter(), indent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Malformed
|
||||||
|
Malformed(string) => buf.push_str(string),
|
||||||
|
QualifiedIdentifier(maybe_qualified) => {
|
||||||
|
for part in maybe_qualified.module_parts.iter() {
|
||||||
|
buf.push_str(part);
|
||||||
|
buf.push('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.push_str(maybe_qualified.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
76
src/fmt/spaces.rs
Normal file
76
src/fmt/spaces.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
use bumpalo::collections::String;
|
||||||
|
use parse::ast::CommentOrNewline;
|
||||||
|
|
||||||
|
/// The number of spaces to indent.
|
||||||
|
pub const INDENT: u16 = 4;
|
||||||
|
|
||||||
|
pub fn newline<'a>(buf: &mut String<'a>, indent: u16) {
|
||||||
|
buf.push('\n');
|
||||||
|
|
||||||
|
add_spaces(buf, indent);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_spaces<'a>(buf: &mut String<'a>, spaces: u16) {
|
||||||
|
for _ in 0..spaces {
|
||||||
|
buf.push(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fmt_spaces<'a, I>(buf: &mut String<'a>, spaces: I, indent: u16)
|
||||||
|
where
|
||||||
|
I: Iterator<Item = &'a CommentOrNewline<'a>>,
|
||||||
|
{
|
||||||
|
use self::CommentOrNewline::*;
|
||||||
|
|
||||||
|
let mut consecutive_newlines = 0;
|
||||||
|
let mut iter = spaces.peekable();
|
||||||
|
|
||||||
|
while let Some(space) = iter.next() {
|
||||||
|
match space {
|
||||||
|
Newline => {
|
||||||
|
// Only ever print two newlines back to back.
|
||||||
|
// (Two newlines renders as one blank line.)
|
||||||
|
if consecutive_newlines < 2 {
|
||||||
|
if iter.peek() == Some(&&Newline) {
|
||||||
|
buf.push('\n');
|
||||||
|
} else {
|
||||||
|
newline(buf, indent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.push('#');
|
||||||
|
buf.push_str(comment);
|
||||||
|
|
||||||
|
newline(buf, indent);
|
||||||
|
|
||||||
|
// Reset to 1 because we just printed a \n
|
||||||
|
consecutive_newlines = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like format_spaces, but remove newlines and keep only comments.
|
||||||
|
pub fn fmt_comments_only<'a, I>(buf: &mut String<'a>, spaces: I, indent: u16)
|
||||||
|
where
|
||||||
|
I: Iterator<Item = &'a CommentOrNewline<'a>>,
|
||||||
|
{
|
||||||
|
use self::CommentOrNewline::*;
|
||||||
|
|
||||||
|
for space in spaces {
|
||||||
|
match space {
|
||||||
|
Newline => {}
|
||||||
|
LineComment(comment) => {
|
||||||
|
buf.push('#');
|
||||||
|
buf.push_str(comment);
|
||||||
|
|
||||||
|
newline(buf, indent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
src/ident.rs
29
src/ident.rs
|
@ -1,5 +1,34 @@
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
|
||||||
|
/// An identifier, possibly fully-qualified with a module name
|
||||||
|
/// e.g. (Http.Request from http)
|
||||||
|
/// Parameterized on a phantom marker for whether it has been canonicalized
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
pub struct UnqualifiedIdent<'a>(&'a str);
|
||||||
|
|
||||||
|
impl<'a> Into<&'a str> for UnqualifiedIdent<'a> {
|
||||||
|
fn into(self) -> &'a str {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> UnqualifiedIdent<'a> {
|
||||||
|
pub fn new(name: &'a str) -> Self {
|
||||||
|
// Unqualified idents must always start with a lowercase character.
|
||||||
|
debug_assert!(name
|
||||||
|
.chars()
|
||||||
|
.next()
|
||||||
|
.expect("UnqualifiedIdent was empty")
|
||||||
|
.is_alphabetic());
|
||||||
|
|
||||||
|
UnqualifiedIdent(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&'a self) -> &'a str {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A variant name, possibly fully-qualified with a module name
|
/// A variant name, possibly fully-qualified with a module name
|
||||||
/// e.g. (Result.Ok)
|
/// e.g. (Result.Ok)
|
||||||
/// Parameterized on a phantom marker for whether it has been canonicalized
|
/// Parameterized on a phantom marker for whether it has been canonicalized
|
||||||
|
|
|
@ -18,15 +18,15 @@ pub mod string;
|
||||||
pub mod constrain;
|
pub mod constrain;
|
||||||
pub mod ena;
|
pub mod ena;
|
||||||
pub mod fmt;
|
pub mod fmt;
|
||||||
|
pub mod gen;
|
||||||
pub mod infer;
|
pub mod infer;
|
||||||
|
pub mod module;
|
||||||
pub mod pretty_print_types;
|
pub mod pretty_print_types;
|
||||||
pub mod solve;
|
pub mod solve;
|
||||||
pub mod subs;
|
pub mod subs;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod unify;
|
pub mod unify;
|
||||||
|
|
||||||
pub mod gen;
|
|
||||||
|
|
||||||
extern crate bumpalo;
|
extern crate bumpalo;
|
||||||
extern crate fraction;
|
extern crate fraction;
|
||||||
extern crate fxhash;
|
extern crate fxhash;
|
||||||
|
|
94
src/module.rs
Normal file
94
src/module.rs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
use bumpalo::collections::Vec;
|
||||||
|
use ident::UnqualifiedIdent;
|
||||||
|
use parse::ast::{CommentOrNewline, Def};
|
||||||
|
use region::Loc;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||||
|
pub struct ModuleName<'a>(&'a str);
|
||||||
|
|
||||||
|
impl<'a> Into<&'a str> for ModuleName<'a> {
|
||||||
|
fn into(self) -> &'a str {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ModuleName<'a> {
|
||||||
|
pub fn new(name: &'a str) -> Self {
|
||||||
|
ModuleName(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_str(&'a self) -> &'a str {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_to_path(&'a self, filename: &'a mut PathBuf) {
|
||||||
|
// Convert dots in module name to directories
|
||||||
|
for part in self.0.split(".") {
|
||||||
|
filename.push(part);
|
||||||
|
}
|
||||||
|
|
||||||
|
// End with .roc
|
||||||
|
filename.push(".roc");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Exposing {
|
||||||
|
Ident,
|
||||||
|
TypeAndVariants,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Module<'a> {
|
||||||
|
Interface {
|
||||||
|
header: InterfaceHeader<'a>,
|
||||||
|
defs: Vec<'a, Def<'a>>,
|
||||||
|
},
|
||||||
|
App {
|
||||||
|
header: AppHeader<'a>,
|
||||||
|
defs: Vec<'a, Def<'a>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct InterfaceHeader<'a> {
|
||||||
|
pub name: Loc<ModuleName<'a>>,
|
||||||
|
pub exposes: Vec<'a, Loc<Exposes<'a>>>,
|
||||||
|
pub imports: Vec<'a, (ModuleName<'a>, Vec<'a, Loc<Imports<'a>>>)>,
|
||||||
|
|
||||||
|
// Potential comments and newlines - these will typically all be empty.
|
||||||
|
pub after_interface: &'a [CommentOrNewline<'a>],
|
||||||
|
pub before_exposes: &'a [CommentOrNewline<'a>],
|
||||||
|
pub after_exposes: &'a [CommentOrNewline<'a>],
|
||||||
|
pub before_imports: &'a [CommentOrNewline<'a>],
|
||||||
|
pub after_imports: &'a [CommentOrNewline<'a>],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct AppHeader<'a> {
|
||||||
|
pub imports: Vec<'a, (ModuleName<'a>, Loc<Imports<'a>>)>,
|
||||||
|
|
||||||
|
// Potential comments and newlines - these will typically all be empty.
|
||||||
|
pub before_imports: &'a [CommentOrNewline<'a>],
|
||||||
|
pub after_imports: &'a [CommentOrNewline<'a>],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Exposes<'a> {
|
||||||
|
/// e.g. `Task`
|
||||||
|
Ident(UnqualifiedIdent<'a>),
|
||||||
|
|
||||||
|
// Spaces
|
||||||
|
SpaceBefore(&'a Exposes<'a>, &'a [CommentOrNewline<'a>]),
|
||||||
|
SpaceAfter(&'a Exposes<'a>, &'a [CommentOrNewline<'a>]),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Imports<'a> {
|
||||||
|
/// e.g. `Task` or `Task.{ Task, after }`
|
||||||
|
Ident(UnqualifiedIdent<'a>, Vec<'a, UnqualifiedIdent<'a>>),
|
||||||
|
|
||||||
|
// Spaces
|
||||||
|
SpaceBefore(&'a Imports<'a>, &'a [CommentOrNewline<'a>]),
|
||||||
|
SpaceAfter(&'a Imports<'a>, &'a [CommentOrNewline<'a>]),
|
||||||
|
}
|
563
src/parse/ast.rs
563
src/parse/ast.rs
|
@ -1,32 +1,30 @@
|
||||||
use bumpalo::collections::String;
|
use bumpalo::collections::String;
|
||||||
use bumpalo::collections::Vec;
|
use bumpalo::collections::Vec;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use fmt::{self, is_multiline_expr};
|
use ident::UnqualifiedIdent;
|
||||||
|
use module::ModuleName;
|
||||||
use operator::CalledVia;
|
use operator::CalledVia;
|
||||||
use operator::{BinOp, UnaryOp};
|
use operator::{BinOp, UnaryOp};
|
||||||
use parse::ident::Ident;
|
use parse::ident::Ident;
|
||||||
use region::{Loc, Region};
|
use region::{Loc, Region};
|
||||||
|
|
||||||
/// The number of spaces to indent.
|
|
||||||
const INDENT: u16 = 4;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum Module<'a> {
|
pub enum Module<'a> {
|
||||||
Interface {
|
Interface {
|
||||||
header: InterfaceHeader<'a>,
|
header: InterfaceHeader<'a>,
|
||||||
defs: Vec<'a, Def<'a>>,
|
defs: Vec<'a, Loc<Def<'a>>>,
|
||||||
},
|
},
|
||||||
App {
|
App {
|
||||||
header: AppHeader<'a>,
|
header: AppHeader<'a>,
|
||||||
defs: Vec<'a, Def<'a>>,
|
defs: Vec<'a, Loc<Def<'a>>>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct InterfaceHeader<'a> {
|
pub struct InterfaceHeader<'a> {
|
||||||
pub name: Loc<(&'a [&'a str], &'a str)>,
|
pub name: Loc<ModuleName<'a>>,
|
||||||
pub exposes: Vec<'a, Loc<HeaderEntry<'a>>>,
|
pub exposes: Vec<'a, Loc<ExposesEntry<'a>>>,
|
||||||
pub imports: Vec<'a, Loc<HeaderEntry<'a>>>,
|
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
|
||||||
|
|
||||||
// Potential comments and newlines - these will typically all be empty.
|
// Potential comments and newlines - these will typically all be empty.
|
||||||
pub after_interface: &'a [CommentOrNewline<'a>],
|
pub after_interface: &'a [CommentOrNewline<'a>],
|
||||||
|
@ -38,21 +36,31 @@ pub struct InterfaceHeader<'a> {
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct AppHeader<'a> {
|
pub struct AppHeader<'a> {
|
||||||
pub imports: Vec<'a, Loc<HeaderEntry<'a>>>,
|
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
|
||||||
|
|
||||||
// Potential comments and newlines - these will typically all be empty.
|
// Potential comments and newlines - these will typically all be empty.
|
||||||
pub after_app: &'a [CommentOrNewline<'a>],
|
|
||||||
pub before_imports: &'a [CommentOrNewline<'a>],
|
pub before_imports: &'a [CommentOrNewline<'a>],
|
||||||
pub after_imports: &'a [CommentOrNewline<'a>],
|
pub after_imports: &'a [CommentOrNewline<'a>],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum HeaderEntry<'a> {
|
pub enum ExposesEntry<'a> {
|
||||||
Val(&'a str),
|
/// e.g. `Task`
|
||||||
TypeOnly(&'a str),
|
Ident(UnqualifiedIdent<'a>),
|
||||||
TypeAndVariants(&'a str),
|
|
||||||
SpaceBefore(&'a HeaderEntry<'a>, &'a [CommentOrNewline<'a>]),
|
// Spaces
|
||||||
SpaceAfter(&'a HeaderEntry<'a>, &'a [CommentOrNewline<'a>]),
|
SpaceBefore(&'a ExposesEntry<'a>, &'a [CommentOrNewline<'a>]),
|
||||||
|
SpaceAfter(&'a ExposesEntry<'a>, &'a [CommentOrNewline<'a>]),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum ImportsEntry<'a> {
|
||||||
|
/// e.g. `Task` or `Task.{ Task, after }`
|
||||||
|
Module(ModuleName<'a>, Vec<'a, Loc<ExposesEntry<'a>>>),
|
||||||
|
|
||||||
|
// Spaces
|
||||||
|
SpaceBefore(&'a ImportsEntry<'a>, &'a [CommentOrNewline<'a>]),
|
||||||
|
SpaceAfter(&'a ImportsEntry<'a>, &'a [CommentOrNewline<'a>]),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An optional qualifier (the `Foo.Bar` in `Foo.Bar.baz`).
|
/// An optional qualifier (the `Foo.Bar` in `Foo.Bar.baz`).
|
||||||
|
@ -392,12 +400,21 @@ impl<'a> Spaceable<'a> for TypeAnnotation<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Spaceable<'a> for HeaderEntry<'a> {
|
impl<'a> Spaceable<'a> for ExposesEntry<'a> {
|
||||||
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
||||||
HeaderEntry::SpaceBefore(self, spaces)
|
ExposesEntry::SpaceBefore(self, spaces)
|
||||||
}
|
}
|
||||||
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
||||||
HeaderEntry::SpaceAfter(self, spaces)
|
ExposesEntry::SpaceAfter(self, spaces)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Spaceable<'a> for ImportsEntry<'a> {
|
||||||
|
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
||||||
|
ImportsEntry::SpaceBefore(self, spaces)
|
||||||
|
}
|
||||||
|
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
||||||
|
ImportsEntry::SpaceAfter(self, spaces)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -521,509 +538,3 @@ impl<'a> Expr<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format<'a>(
|
|
||||||
arena: &'a Bump,
|
|
||||||
expr: &'a Expr<'a>,
|
|
||||||
indent: u16,
|
|
||||||
apply_needs_parens: bool,
|
|
||||||
) -> String<'a> {
|
|
||||||
use self::Expr::*;
|
|
||||||
|
|
||||||
let mut buf = String::new_in(arena);
|
|
||||||
|
|
||||||
match expr {
|
|
||||||
SpaceBefore(sub_expr, spaces) => {
|
|
||||||
buf.push_str(&format_spaces(arena, spaces.iter(), indent));
|
|
||||||
buf.push_str(&format(arena, sub_expr, indent, apply_needs_parens));
|
|
||||||
}
|
|
||||||
SpaceAfter(sub_expr, spaces) => {
|
|
||||||
buf.push_str(&format(arena, sub_expr, indent, apply_needs_parens));
|
|
||||||
buf.push_str(&format_spaces(arena, spaces.iter(), indent));
|
|
||||||
}
|
|
||||||
ParensAround(sub_expr) => {
|
|
||||||
buf.push('(');
|
|
||||||
buf.push_str(&format(arena, sub_expr, indent, false));
|
|
||||||
buf.push(')');
|
|
||||||
}
|
|
||||||
Str(string) => {
|
|
||||||
buf.push('"');
|
|
||||||
buf.push_str(string);
|
|
||||||
buf.push('"');
|
|
||||||
}
|
|
||||||
Var(module_parts, name) => {
|
|
||||||
for part in module_parts.iter() {
|
|
||||||
buf.push_str(part);
|
|
||||||
buf.push('.');
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.push_str(name);
|
|
||||||
}
|
|
||||||
Apply(loc_expr, loc_args, _) => {
|
|
||||||
if apply_needs_parens {
|
|
||||||
buf.push('(');
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.push_str(&format(arena, &loc_expr.value, indent, true));
|
|
||||||
|
|
||||||
for loc_arg in loc_args {
|
|
||||||
buf.push(' ');
|
|
||||||
|
|
||||||
buf.push_str(&format(arena, &loc_arg.value, indent, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
if apply_needs_parens {
|
|
||||||
buf.push(')');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BlockStr(lines) => {
|
|
||||||
buf.push_str("\"\"\"");
|
|
||||||
for line in lines.iter() {
|
|
||||||
buf.push_str(line);
|
|
||||||
}
|
|
||||||
buf.push_str("\"\"\"");
|
|
||||||
}
|
|
||||||
Int(string) => buf.push_str(string),
|
|
||||||
Float(string) => buf.push_str(string),
|
|
||||||
HexInt(string) => {
|
|
||||||
buf.push('0');
|
|
||||||
buf.push('x');
|
|
||||||
buf.push_str(string);
|
|
||||||
}
|
|
||||||
BinaryInt(string) => {
|
|
||||||
buf.push('0');
|
|
||||||
buf.push('b');
|
|
||||||
buf.push_str(string);
|
|
||||||
}
|
|
||||||
OctalInt(string) => {
|
|
||||||
buf.push('0');
|
|
||||||
buf.push('o');
|
|
||||||
buf.push_str(string);
|
|
||||||
}
|
|
||||||
Record(loc_fields) => {
|
|
||||||
buf.push('{');
|
|
||||||
|
|
||||||
let is_multiline = loc_fields
|
|
||||||
.iter()
|
|
||||||
.any(|loc_field| fmt::is_multiline_field(&loc_field.value));
|
|
||||||
|
|
||||||
let mut iter = loc_fields.iter().peekable();
|
|
||||||
let field_indent = if is_multiline {
|
|
||||||
indent + INDENT
|
|
||||||
} else {
|
|
||||||
if !loc_fields.is_empty() {
|
|
||||||
buf.push(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
indent
|
|
||||||
};
|
|
||||||
|
|
||||||
while let Some(field) = iter.next() {
|
|
||||||
buf.push_str(&format_field(
|
|
||||||
arena,
|
|
||||||
&field.value,
|
|
||||||
is_multiline,
|
|
||||||
field_indent,
|
|
||||||
apply_needs_parens,
|
|
||||||
));
|
|
||||||
|
|
||||||
if iter.peek().is_some() {
|
|
||||||
buf.push(',');
|
|
||||||
|
|
||||||
if !is_multiline {
|
|
||||||
buf.push(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_multiline {
|
|
||||||
buf.push('\n');
|
|
||||||
} else if !loc_fields.is_empty() {
|
|
||||||
buf.push(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.push('}');
|
|
||||||
}
|
|
||||||
Closure(loc_patterns, loc_ret) => {
|
|
||||||
buf.push('\\');
|
|
||||||
|
|
||||||
for loc_pattern in loc_patterns.iter() {
|
|
||||||
buf.push_str(&format_pattern(arena, &loc_pattern.value, indent, true));
|
|
||||||
|
|
||||||
buf.push(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
let is_multiline = is_multiline_expr(&loc_ret.value);
|
|
||||||
|
|
||||||
// If the body is multiline, go down a line and indent.
|
|
||||||
let indent = if is_multiline {
|
|
||||||
indent + INDENT
|
|
||||||
} else {
|
|
||||||
indent
|
|
||||||
};
|
|
||||||
|
|
||||||
buf.push_str("->");
|
|
||||||
|
|
||||||
let newline_is_next = match &loc_ret.value {
|
|
||||||
SpaceBefore(_, _) => true,
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if !newline_is_next {
|
|
||||||
// Push a space after the "->" preceding this.
|
|
||||||
buf.push(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.push_str(&format(arena, &loc_ret.value, indent, false));
|
|
||||||
}
|
|
||||||
Defs(defs, ret) => {
|
|
||||||
// The first def is actually at the end of the list, because
|
|
||||||
// it gets added there with .push() for efficiency. (The order of parsed defs doesn't
|
|
||||||
// matter because canonicalization sorts them anyway.) The other
|
|
||||||
// defs in the list are in their usual order.
|
|
||||||
let loc_first_def = defs.last().unwrap_or_else(|| {
|
|
||||||
panic!("Tried to format Defs which somehow had an empty list of defs!")
|
|
||||||
});
|
|
||||||
let other_spaced_defs = &defs[0..defs.len() - 1];
|
|
||||||
|
|
||||||
buf.push_str(&format_def(arena, &loc_first_def.value, indent));
|
|
||||||
|
|
||||||
for loc_def in other_spaced_defs.iter() {
|
|
||||||
buf.push_str(&format_def(arena, &loc_def.value, indent));
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.push_str(&format(arena, &ret.value, indent, false));
|
|
||||||
}
|
|
||||||
If((loc_condition, loc_then, loc_else)) => {
|
|
||||||
buf.push_str("if ");
|
|
||||||
buf.push_str(&format(arena, &loc_condition.value, indent, false));
|
|
||||||
buf.push_str(" then ");
|
|
||||||
buf.push_str(&format(arena, &loc_then.value, indent, false));
|
|
||||||
buf.push_str(" else ");
|
|
||||||
buf.push_str(&format(arena, &loc_else.value, indent, false));
|
|
||||||
}
|
|
||||||
Case(loc_condition, branches) => {
|
|
||||||
buf.push_str("case ");
|
|
||||||
buf.push_str(&format(arena, &loc_condition.value, indent, false));
|
|
||||||
buf.push_str(" when\n");
|
|
||||||
|
|
||||||
let mut it = branches.iter().peekable();
|
|
||||||
while let Some((pattern, expr)) = it.next() {
|
|
||||||
add_spaces(&mut buf, indent + INDENT);
|
|
||||||
|
|
||||||
match pattern.value {
|
|
||||||
Pattern::SpaceBefore(nested, spaces) => {
|
|
||||||
buf.push_str(&format_comments_only(arena, spaces.iter(), indent + INDENT));
|
|
||||||
buf.push_str(&format_pattern(arena, nested, indent + INDENT, false));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
buf.push_str(&format_pattern(
|
|
||||||
arena,
|
|
||||||
&pattern.value,
|
|
||||||
indent + INDENT,
|
|
||||||
false,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.push_str(" ->\n");
|
|
||||||
|
|
||||||
add_spaces(&mut buf, indent + (INDENT * 2));
|
|
||||||
match expr.value {
|
|
||||||
Expr::SpaceBefore(nested, spaces) => {
|
|
||||||
buf.push_str(&format_comments_only(
|
|
||||||
arena,
|
|
||||||
spaces.iter(),
|
|
||||||
indent + (INDENT * 2),
|
|
||||||
));
|
|
||||||
buf.push_str(&format(arena, &nested, indent + (INDENT * 2), false));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
buf.push_str(&format(arena, &expr.value, indent + (INDENT * 2), false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if it.peek().is_some() {
|
|
||||||
buf.push('\n');
|
|
||||||
buf.push('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
other => panic!("TODO implement Display for AST variant {:?}", other),
|
|
||||||
}
|
|
||||||
|
|
||||||
buf
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn format_def<'a>(arena: &'a Bump, def: &'a Def<'a>, indent: u16) -> String<'a> {
|
|
||||||
let mut buf = String::new_in(arena);
|
|
||||||
|
|
||||||
match def {
|
|
||||||
Def::Annotation(_, _) => panic!("TODO have format_def support Annotation"),
|
|
||||||
Def::Body(loc_pattern, loc_expr) => {
|
|
||||||
buf.push_str(&format_pattern(arena, &loc_pattern.value, indent, true));
|
|
||||||
buf.push_str(" = ");
|
|
||||||
buf.push_str(&format(arena, &loc_expr.value, indent, false));
|
|
||||||
}
|
|
||||||
Def::CustomType(_, _) => panic!("TODO have format_def support CustomType"),
|
|
||||||
Def::TypeAlias(_, _) => panic!("TODO have format_def support TypeAlias"),
|
|
||||||
Def::SpaceBefore(sub_def, spaces) => {
|
|
||||||
buf.push_str(&format_spaces(arena, spaces.iter(), indent));
|
|
||||||
buf.push_str(&format_def(arena, sub_def, indent));
|
|
||||||
}
|
|
||||||
Def::SpaceAfter(sub_def, spaces) => {
|
|
||||||
buf.push_str(&format_def(arena, sub_def, indent));
|
|
||||||
|
|
||||||
buf.push_str(&format_spaces(arena, spaces.iter(), indent));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf
|
|
||||||
}
|
|
||||||
|
|
||||||
fn format_pattern<'a>(
|
|
||||||
arena: &'a Bump,
|
|
||||||
pattern: &'a Pattern<'a>,
|
|
||||||
indent: u16,
|
|
||||||
apply_needs_parens: bool,
|
|
||||||
) -> String<'a> {
|
|
||||||
use self::Pattern::*;
|
|
||||||
|
|
||||||
let mut buf = String::new_in(arena);
|
|
||||||
|
|
||||||
match pattern {
|
|
||||||
Identifier(string) => buf.push_str(string),
|
|
||||||
Variant(module_parts, name) => {
|
|
||||||
for part in module_parts.iter() {
|
|
||||||
buf.push_str(part);
|
|
||||||
buf.push('.');
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.push_str(name);
|
|
||||||
}
|
|
||||||
Apply(loc_pattern, loc_arg_patterns) => {
|
|
||||||
if apply_needs_parens {
|
|
||||||
buf.push('(');
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.push_str(&format_pattern(arena, &loc_pattern.value, indent, true));
|
|
||||||
|
|
||||||
for loc_arg in loc_arg_patterns.iter() {
|
|
||||||
buf.push(' ');
|
|
||||||
buf.push_str(&format_pattern(arena, &loc_arg.value, indent, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
if apply_needs_parens {
|
|
||||||
buf.push(')');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RecordDestructure(loc_patterns) => {
|
|
||||||
buf.push_str("{ ");
|
|
||||||
|
|
||||||
let mut is_first = true;
|
|
||||||
|
|
||||||
for loc_pattern in loc_patterns {
|
|
||||||
if is_first {
|
|
||||||
is_first = false;
|
|
||||||
} else {
|
|
||||||
buf.push_str(", ");
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.push_str(&format_pattern(arena, &loc_pattern.value, indent, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.push_str(" }");
|
|
||||||
}
|
|
||||||
|
|
||||||
RecordField(name, loc_pattern) => {
|
|
||||||
buf.push_str(name);
|
|
||||||
buf.push_str(": ");
|
|
||||||
buf.push_str(&format_pattern(arena, &loc_pattern.value, indent, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
IntLiteral(string) => buf.push_str(string),
|
|
||||||
HexIntLiteral(string) => buf.push_str(string),
|
|
||||||
OctalIntLiteral(string) => buf.push_str(string),
|
|
||||||
BinaryIntLiteral(string) => buf.push_str(string),
|
|
||||||
FloatLiteral(string) => buf.push_str(string),
|
|
||||||
StrLiteral(string) => buf.push_str(string),
|
|
||||||
BlockStrLiteral(lines) => {
|
|
||||||
for line in *lines {
|
|
||||||
buf.push_str(line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EmptyRecordLiteral => buf.push_str("{}"),
|
|
||||||
Underscore => buf.push('_'),
|
|
||||||
|
|
||||||
// Space
|
|
||||||
SpaceBefore(sub_pattern, spaces) => {
|
|
||||||
buf.push_str(&format_spaces(arena, spaces.iter(), indent));
|
|
||||||
buf.push_str(&format_pattern(
|
|
||||||
arena,
|
|
||||||
sub_pattern,
|
|
||||||
indent,
|
|
||||||
apply_needs_parens,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
SpaceAfter(sub_pattern, spaces) => {
|
|
||||||
buf.push_str(&format_pattern(
|
|
||||||
arena,
|
|
||||||
sub_pattern,
|
|
||||||
indent,
|
|
||||||
apply_needs_parens,
|
|
||||||
));
|
|
||||||
buf.push_str(&format_spaces(arena, spaces.iter(), indent));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Malformed
|
|
||||||
Malformed(string) => buf.push_str(string),
|
|
||||||
QualifiedIdentifier(maybe_qualified) => {
|
|
||||||
for part in maybe_qualified.module_parts.iter() {
|
|
||||||
buf.push_str(part);
|
|
||||||
buf.push('.');
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.push_str(maybe_qualified.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf
|
|
||||||
}
|
|
||||||
|
|
||||||
fn format_spaces<'a, I>(arena: &'a Bump, spaces: I, indent: u16) -> String<'a>
|
|
||||||
where
|
|
||||||
I: Iterator<Item = &'a CommentOrNewline<'a>>,
|
|
||||||
{
|
|
||||||
use self::CommentOrNewline::*;
|
|
||||||
|
|
||||||
let mut buf = String::new_in(arena);
|
|
||||||
let mut consecutive_newlines = 0;
|
|
||||||
let mut iter = spaces.peekable();
|
|
||||||
|
|
||||||
while let Some(space) = iter.next() {
|
|
||||||
match space {
|
|
||||||
Newline => {
|
|
||||||
// Only ever print two newlines back to back.
|
|
||||||
// (Two newlines renders as one blank line.)
|
|
||||||
if consecutive_newlines < 2 {
|
|
||||||
if iter.peek() == Some(&&Newline) {
|
|
||||||
buf.push('\n');
|
|
||||||
} else {
|
|
||||||
newline(&mut buf, indent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.push('#');
|
|
||||||
buf.push_str(comment);
|
|
||||||
|
|
||||||
newline(&mut buf, indent);
|
|
||||||
|
|
||||||
// Reset to 1 because we just printed a \n
|
|
||||||
consecutive_newlines = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Like format_spaces, but remove newlines and keep only comments.
|
|
||||||
fn format_comments_only<'a, I>(arena: &'a Bump, spaces: I, indent: u16) -> String<'a>
|
|
||||||
where
|
|
||||||
I: Iterator<Item = &'a CommentOrNewline<'a>>,
|
|
||||||
{
|
|
||||||
use self::CommentOrNewline::*;
|
|
||||||
|
|
||||||
let mut buf = String::new_in(arena);
|
|
||||||
|
|
||||||
for space in spaces {
|
|
||||||
match space {
|
|
||||||
Newline => {}
|
|
||||||
LineComment(comment) => {
|
|
||||||
buf.push('#');
|
|
||||||
buf.push_str(comment);
|
|
||||||
|
|
||||||
newline(&mut buf, indent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn format_field<'a>(
|
|
||||||
arena: &'a Bump,
|
|
||||||
assigned_field: &'a AssignedField<'a, Expr<'a>>,
|
|
||||||
is_multiline: bool,
|
|
||||||
indent: u16,
|
|
||||||
apply_needs_parens: bool,
|
|
||||||
) -> String<'a> {
|
|
||||||
use self::AssignedField::*;
|
|
||||||
|
|
||||||
let mut buf = String::new_in(arena);
|
|
||||||
|
|
||||||
match assigned_field {
|
|
||||||
LabeledValue(name, spaces, value) => {
|
|
||||||
if is_multiline {
|
|
||||||
newline(&mut buf, indent);
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.push_str(name.value);
|
|
||||||
|
|
||||||
if !spaces.is_empty() {
|
|
||||||
buf.push_str(&format_spaces(arena, spaces.iter(), indent));
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.push(':');
|
|
||||||
buf.push(' ');
|
|
||||||
buf.push_str(&format(arena, &value.value, indent, apply_needs_parens));
|
|
||||||
}
|
|
||||||
LabelOnly(name) => {
|
|
||||||
if is_multiline {
|
|
||||||
newline(&mut buf, indent);
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.push_str(name.value);
|
|
||||||
}
|
|
||||||
AssignedField::SpaceBefore(sub_expr, spaces) => {
|
|
||||||
buf.push_str(&format_comments_only(arena, spaces.iter(), indent));
|
|
||||||
buf.push_str(&format_field(
|
|
||||||
arena,
|
|
||||||
sub_expr,
|
|
||||||
is_multiline,
|
|
||||||
indent,
|
|
||||||
apply_needs_parens,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
AssignedField::SpaceAfter(sub_expr, spaces) => {
|
|
||||||
buf.push_str(&format_field(
|
|
||||||
arena,
|
|
||||||
sub_expr,
|
|
||||||
is_multiline,
|
|
||||||
indent,
|
|
||||||
apply_needs_parens,
|
|
||||||
));
|
|
||||||
buf.push_str(&format_comments_only(arena, spaces.iter(), indent));
|
|
||||||
}
|
|
||||||
Malformed(string) => buf.push_str(string),
|
|
||||||
}
|
|
||||||
|
|
||||||
buf
|
|
||||||
}
|
|
||||||
|
|
||||||
fn newline<'a>(buf: &mut String<'a>, indent: u16) {
|
|
||||||
buf.push('\n');
|
|
||||||
|
|
||||||
add_spaces(buf, indent);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_spaces<'a>(buf: &mut String<'a>, spaces: u16) {
|
|
||||||
for _ in 0..spaces {
|
|
||||||
buf.push(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ use bumpalo::collections::string::String;
|
||||||
use bumpalo::collections::vec::Vec;
|
use bumpalo::collections::vec::Vec;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use collections::arena_join;
|
use collections::arena_join;
|
||||||
|
use ident::UnqualifiedIdent;
|
||||||
use parse::ast::{Attempting, MaybeQualified};
|
use parse::ast::{Attempting, MaybeQualified};
|
||||||
use parse::parser::{unexpected, unexpected_eof, ParseResult, Parser, State};
|
use parse::parser::{unexpected, unexpected_eof, ParseResult, Parser, State};
|
||||||
|
|
||||||
|
@ -348,6 +349,13 @@ pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str> {
|
||||||
variant_or_ident(|first_char| first_char.is_lowercase())
|
variant_or_ident(|first_char| first_char.is_lowercase())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn unqualified_ident<'a>() -> impl Parser<'a, UnqualifiedIdent<'a>> {
|
||||||
|
map!(
|
||||||
|
variant_or_ident(|first_char| first_char.is_alphabetic()),
|
||||||
|
UnqualifiedIdent::new
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// TESTS
|
// TESTS
|
||||||
|
|
||||||
// fn test_parse<'a>(input: &'a str) -> Result<Ident<'a>, Fail> {
|
// fn test_parse<'a>(input: &'a str) -> Result<Ident<'a>, Fail> {
|
||||||
|
|
106
src/parse/mod.rs
106
src/parse/mod.rs
|
@ -16,10 +16,7 @@ use bumpalo::collections::Vec;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use operator::{BinOp, CalledVia, UnaryOp};
|
use operator::{BinOp, CalledVia, UnaryOp};
|
||||||
use parse;
|
use parse;
|
||||||
use parse::ast::{
|
use parse::ast::{AssignedField, Attempting, Def, Expr, MaybeQualified, Pattern, Spaceable};
|
||||||
AppHeader, AssignedField, Attempting, CommentOrNewline, Def, Expr, HeaderEntry,
|
|
||||||
InterfaceHeader, MaybeQualified, Module, Pattern, Spaceable,
|
|
||||||
};
|
|
||||||
use parse::blankspace::{
|
use parse::blankspace::{
|
||||||
space0, space0_after, space0_around, space0_before, space1, space1_after, space1_around,
|
space0, space0_after, space0_around, space0_before, space1, space1_after, space1_around,
|
||||||
space1_before,
|
space1_before,
|
||||||
|
@ -35,107 +32,6 @@ use parse::parser::{
|
||||||
use parse::record::record;
|
use parse::record::record;
|
||||||
use region::{Located, Region};
|
use region::{Located, Region};
|
||||||
|
|
||||||
pub fn module<'a>() -> impl Parser<'a, Module<'a>> {
|
|
||||||
one_of2(interface_module(), app_module())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn interface_module<'a>() -> impl Parser<'a, Module<'a>> {
|
|
||||||
map!(and!(interface_header(), module_defs()), |(header, defs)| {
|
|
||||||
Module::Interface { header, defs }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn app_module<'a>() -> impl Parser<'a, Module<'a>> {
|
|
||||||
map!(and!(app_header(), module_defs()), |(header, defs)| {
|
|
||||||
Module::App { header, defs }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>> {
|
|
||||||
parser::map(
|
|
||||||
and!(
|
|
||||||
skip_first(string("interface"), and!(space1(1), loc!(ident()))),
|
|
||||||
and!(mod_header_list("exposes"), mod_header_list("imports"))
|
|
||||||
),
|
|
||||||
|(
|
|
||||||
(after_interface, loc_name_ident),
|
|
||||||
(
|
|
||||||
((before_exposes, after_exposes), exposes),
|
|
||||||
((before_imports, after_imports), imports),
|
|
||||||
),
|
|
||||||
)| {
|
|
||||||
match loc_name_ident.value {
|
|
||||||
Ident::Variant(info) => {
|
|
||||||
let name = Located {
|
|
||||||
value: (info.module_parts, info.value),
|
|
||||||
region: loc_name_ident.region,
|
|
||||||
};
|
|
||||||
|
|
||||||
InterfaceHeader {
|
|
||||||
name,
|
|
||||||
exposes,
|
|
||||||
imports,
|
|
||||||
after_interface,
|
|
||||||
before_exposes,
|
|
||||||
after_exposes,
|
|
||||||
before_imports,
|
|
||||||
after_imports,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => panic!("TODO handle malformed module header"),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>> {
|
|
||||||
move |_, _| {
|
|
||||||
panic!("TODO parse app header");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Def<'a>>> {
|
|
||||||
move |_, _| {
|
|
||||||
panic!("TODO parse defs");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Either "imports" or "exposes" - they both work the same way.
|
|
||||||
#[inline(always)]
|
|
||||||
fn mod_header_list<'a>(
|
|
||||||
kw: &'static str,
|
|
||||||
) -> impl Parser<
|
|
||||||
'a,
|
|
||||||
(
|
|
||||||
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
|
|
||||||
Vec<'a, Located<HeaderEntry<'a>>>,
|
|
||||||
),
|
|
||||||
> {
|
|
||||||
and!(
|
|
||||||
and!(skip_second(space1(1), string(kw)), space1(1)),
|
|
||||||
collection(char('['), loc!(mod_header_entry()), char(','), char(']'), 1)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn mod_header_entry<'a>() -> impl Parser<'a, HeaderEntry<'a>> {
|
|
||||||
one_of2(
|
|
||||||
map!(unqualified_ident(), |ident| HeaderEntry::Val(ident)),
|
|
||||||
map!(
|
|
||||||
and!(unqualified_variant(), optional(string("..."))),
|
|
||||||
|(ident, opt_ellipsis)| match opt_ellipsis {
|
|
||||||
None => HeaderEntry::TypeOnly(ident),
|
|
||||||
Some(()) => HeaderEntry::TypeAndVariants(ident),
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn expr<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
pub fn expr<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
||||||
// Recursive parsers must not directly invoke functions which return (impl Parser),
|
// Recursive parsers must not directly invoke functions which return (impl Parser),
|
||||||
// as this causes rustc to stack overflow. Thus, parse_expr must be a
|
// as this causes rustc to stack overflow. Thus, parse_expr must be a
|
||||||
|
|
|
@ -1,16 +1,200 @@
|
||||||
use ident::Ident;
|
use bumpalo::collections::{String, Vec};
|
||||||
use parse::ast::{Expr, Pattern};
|
use module::ModuleName;
|
||||||
|
use parse::ast::{
|
||||||
|
AppHeader, Attempting, CommentOrNewline, Def, ExposesEntry, ImportsEntry, InterfaceHeader,
|
||||||
|
Module,
|
||||||
|
};
|
||||||
|
use parse::blankspace::{space1, space1_around};
|
||||||
|
use parse::collection::collection;
|
||||||
|
use parse::ident::unqualified_ident;
|
||||||
|
use parse::parse;
|
||||||
|
use parse::parser::{
|
||||||
|
self, char, loc, one_of2, optional, skip_first, skip_second, string, unexpected,
|
||||||
|
unexpected_eof, Parser, State,
|
||||||
|
};
|
||||||
|
use region::Located;
|
||||||
|
|
||||||
pub struct Module<'a> {
|
pub fn module<'a>() -> impl Parser<'a, Module<'a>> {
|
||||||
pub name: Ident,
|
one_of2(interface_module(), app_module())
|
||||||
pub exposes: Vec<Ident>,
|
|
||||||
pub uses: Vec<Ident>,
|
|
||||||
pub decls: Vec<Decl<'a>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[inline(always)]
|
||||||
pub enum Decl<'a> {
|
pub fn interface_module<'a>() -> impl Parser<'a, Module<'a>> {
|
||||||
Def(Pattern<'a>, Expr<'a>, Expr<'a>),
|
map!(and!(interface_header(), module_defs()), |(header, defs)| {
|
||||||
// TODO Alias
|
Module::Interface { header, defs }
|
||||||
// TODO SumType
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn app_module<'a>() -> impl Parser<'a, Module<'a>> {
|
||||||
|
map!(and!(app_header(), module_defs()), |(header, defs)| {
|
||||||
|
Module::App { header, defs }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>> {
|
||||||
|
parser::map(
|
||||||
|
and!(
|
||||||
|
skip_first(string("interface"), and!(space1(1), loc!(module_name()))),
|
||||||
|
and!(exposes(), imports())
|
||||||
|
),
|
||||||
|
|(
|
||||||
|
(after_interface, name),
|
||||||
|
(
|
||||||
|
((before_exposes, after_exposes), exposes),
|
||||||
|
((before_imports, after_imports), imports),
|
||||||
|
),
|
||||||
|
)| {
|
||||||
|
InterfaceHeader {
|
||||||
|
name,
|
||||||
|
exposes,
|
||||||
|
imports,
|
||||||
|
after_interface,
|
||||||
|
before_exposes,
|
||||||
|
after_exposes,
|
||||||
|
before_imports,
|
||||||
|
after_imports,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>> {
|
||||||
|
move |arena, state: State<'a>| {
|
||||||
|
let mut chars = state.input.chars();
|
||||||
|
|
||||||
|
let first_letter = match chars.next() {
|
||||||
|
Some(first_char) => {
|
||||||
|
// Module names must all be uppercase
|
||||||
|
if first_char.is_uppercase() {
|
||||||
|
first_char
|
||||||
|
} else {
|
||||||
|
return Err(unexpected(
|
||||||
|
first_char,
|
||||||
|
0,
|
||||||
|
state,
|
||||||
|
Attempting::RecordFieldLabel,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(unexpected_eof(0, Attempting::Identifier, state));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut buf = String::with_capacity_in(1, arena);
|
||||||
|
|
||||||
|
buf.push(first_letter);
|
||||||
|
|
||||||
|
while let Some(ch) = chars.next() {
|
||||||
|
// After the first character, only these are allowed:
|
||||||
|
//
|
||||||
|
// * Unicode alphabetic chars - you might include `鹏` if that's clear to your readers
|
||||||
|
// * ASCII digits - e.g. `1` but not `¾`, both of which pass .is_numeric()
|
||||||
|
// * A '.' separating module parts
|
||||||
|
if ch.is_alphabetic() || ch.is_ascii_digit() {
|
||||||
|
buf.push(ch);
|
||||||
|
} else if ch == '.' {
|
||||||
|
match chars.next() {
|
||||||
|
Some(next) => {
|
||||||
|
if next.is_uppercase() {
|
||||||
|
// If we hit another uppercase letter, keep going!
|
||||||
|
buf.push(next);
|
||||||
|
} else {
|
||||||
|
// We have finished parsing the module name.
|
||||||
|
//
|
||||||
|
// There may be an identifier after this '.',
|
||||||
|
// e.g. "baz" in `Foo.Bar.baz`
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// A module name can't end with a '.'
|
||||||
|
return Err(unexpected_eof(0, Attempting::Identifier, state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This is the end of the module name. We're done!
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let chars_parsed = buf.len();
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
ModuleName::new(buf.into_bump_str()),
|
||||||
|
state.advance_without_indenting(chars_parsed)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>> {
|
||||||
|
move |_, _| {
|
||||||
|
panic!("TODO parse app header");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn module_defs<'a>() -> impl Parser<'a, Vec<'a, Located<Def<'a>>>> {
|
||||||
|
zero_or_more!(space1_around(loc(parse::def(0)), 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn exposes<'a>() -> impl Parser<
|
||||||
|
'a,
|
||||||
|
(
|
||||||
|
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
|
||||||
|
Vec<'a, Located<ExposesEntry<'a>>>,
|
||||||
|
),
|
||||||
|
> {
|
||||||
|
and!(
|
||||||
|
and!(skip_second(space1(1), string("exposes")), space1(1)),
|
||||||
|
collection(char('['), loc!(exposes_entry()), char(','), char(']'), 1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn imports<'a>() -> impl Parser<
|
||||||
|
'a,
|
||||||
|
(
|
||||||
|
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
|
||||||
|
Vec<'a, Located<ImportsEntry<'a>>>,
|
||||||
|
),
|
||||||
|
> {
|
||||||
|
and!(
|
||||||
|
and!(skip_second(space1(1), string("imports")), space1(1)),
|
||||||
|
collection(char('['), loc!(imports_entry()), char(','), char(']'), 1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn exposes_entry<'a>() -> impl Parser<'a, ExposesEntry<'a>> {
|
||||||
|
map!(unqualified_ident(), ExposesEntry::Ident)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>> {
|
||||||
|
map_with_arena!(
|
||||||
|
and!(
|
||||||
|
// e.g. `Task`
|
||||||
|
module_name(),
|
||||||
|
// e.g. `.{ Task, after}`
|
||||||
|
optional(skip_first(
|
||||||
|
char('.'),
|
||||||
|
collection(char('{'), loc!(exposes_entry()), char(','), char('}'), 1)
|
||||||
|
))
|
||||||
|
),
|
||||||
|
|arena,
|
||||||
|
(module_name, opt_values): (
|
||||||
|
ModuleName<'a>,
|
||||||
|
Option<Vec<'a, Located<ExposesEntry<'a>>>>
|
||||||
|
)| {
|
||||||
|
let exposed_values = opt_values.unwrap_or_else(|| Vec::new_in(arena));
|
||||||
|
|
||||||
|
ImportsEntry::Module(module_name, exposed_values)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,14 @@ extern crate roc;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_format {
|
mod test_format {
|
||||||
|
use bumpalo::collections::String;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
|
use roc::fmt::expr::fmt_expr;
|
||||||
|
use roc::fmt::module::fmt_module;
|
||||||
use roc::parse;
|
use roc::parse;
|
||||||
use roc::parse::ast::{format, Attempting, Expr};
|
use roc::parse::ast::{Attempting, Expr};
|
||||||
use roc::parse::blankspace::space0_before;
|
use roc::parse::blankspace::space0_before;
|
||||||
|
use roc::parse::module::module;
|
||||||
use roc::parse::parser::{Fail, Parser, State};
|
use roc::parse::parser::{Fail, Parser, State};
|
||||||
|
|
||||||
fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Expr<'a>, Fail> {
|
fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Expr<'a>, Fail> {
|
||||||
|
@ -24,26 +28,53 @@ mod test_format {
|
||||||
.map_err(|(fail, _)| fail)
|
.map_err(|(fail, _)| fail)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_formats_to(input: &str, expected: &str) {
|
fn expr_formats_to(input: &str, expected: &str) {
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
let input = input.trim_end();
|
let input = input.trim_end();
|
||||||
let expected = expected.trim_end();
|
let expected = expected.trim_end();
|
||||||
|
|
||||||
match parse_with(&arena, input) {
|
match parse_with(&arena, input) {
|
||||||
Ok(actual) => assert_eq!(format(&arena, &actual, 0, false), expected),
|
Ok(actual) => {
|
||||||
|
let mut buf = String::new_in(&arena);
|
||||||
|
|
||||||
|
fmt_expr(&mut buf, &actual, 0, false);
|
||||||
|
|
||||||
|
assert_eq!(buf, expected)
|
||||||
|
},
|
||||||
Err(error) => panic!("Unexpected parse failure when parsing this for formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", input, error)
|
Err(error) => panic!("Unexpected parse failure when parsing this for formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", input, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_formats_same(input: &str) {
|
fn expr_formats_same(input: &str) {
|
||||||
assert_formats_to(input, input);
|
expr_formats_to(input, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn module_formats_to(src: &str, expected: &str) {
|
||||||
|
let arena = Bump::new();
|
||||||
|
let src = src.trim_end();
|
||||||
|
let expected = expected.trim_end();
|
||||||
|
|
||||||
|
match module().parse(&arena, State::new(&src, Attempting::Module)) {
|
||||||
|
Ok((actual, _)) => {
|
||||||
|
let mut buf = String::new_in(&arena);
|
||||||
|
|
||||||
|
fmt_module(&mut buf, &actual);
|
||||||
|
|
||||||
|
assert_eq!(buf, expected)
|
||||||
|
},
|
||||||
|
Err(error) => panic!("Unexpected parse failure when parsing this for formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn module_formats_same(input: &str) {
|
||||||
|
module_formats_to(input, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
// STRING LITERALS
|
// STRING LITERALS
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_string() {
|
fn empty_string() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
""
|
""
|
||||||
"#
|
"#
|
||||||
|
@ -52,7 +83,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn basic_string() {
|
fn basic_string() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
"blah"
|
"blah"
|
||||||
"#
|
"#
|
||||||
|
@ -61,7 +92,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn escaped_unicode_string() {
|
fn escaped_unicode_string() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
"unicode: \u{A00A}!"
|
"unicode: \u{A00A}!"
|
||||||
"#
|
"#
|
||||||
|
@ -70,7 +101,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn escaped_quote_string() {
|
fn escaped_quote_string() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
"\""
|
"\""
|
||||||
"#
|
"#
|
||||||
|
@ -79,7 +110,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_block_string() {
|
fn empty_block_string() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
""""""
|
""""""
|
||||||
"#
|
"#
|
||||||
|
@ -88,7 +119,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn basic_block_string() {
|
fn basic_block_string() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
"""blah"""
|
"""blah"""
|
||||||
"#
|
"#
|
||||||
|
@ -97,7 +128,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn newlines_block_string() {
|
fn newlines_block_string() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
"""blah
|
"""blah
|
||||||
spam
|
spam
|
||||||
|
@ -108,7 +139,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn quotes_block_string() {
|
fn quotes_block_string() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -121,7 +152,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn zero() {
|
fn zero() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
0
|
0
|
||||||
"#
|
"#
|
||||||
|
@ -130,7 +161,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn zero_point_zero() {
|
fn zero_point_zero() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
0.0
|
0.0
|
||||||
"#
|
"#
|
||||||
|
@ -139,7 +170,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn int_with_underscores() {
|
fn int_with_underscores() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
1_23_456
|
1_23_456
|
||||||
"#
|
"#
|
||||||
|
@ -148,7 +179,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn float_with_underscores() {
|
fn float_with_underscores() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
1_23_456.7_89_10
|
1_23_456.7_89_10
|
||||||
"#
|
"#
|
||||||
|
@ -157,7 +188,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multi_arg_closure() {
|
fn multi_arg_closure() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
\a b c -> a b c
|
\a b c -> a b c
|
||||||
"#
|
"#
|
||||||
|
@ -168,7 +199,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn single_def() {
|
fn single_def() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
x = 5
|
x = 5
|
||||||
|
|
||||||
|
@ -179,7 +210,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_defs() {
|
fn two_defs() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
x = 5
|
x = 5
|
||||||
y = 10
|
y = 10
|
||||||
|
@ -191,7 +222,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parenthetical_def() {
|
fn parenthetical_def() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
(UserId userId) = 5
|
(UserId userId) = 5
|
||||||
y = 10
|
y = 10
|
||||||
|
@ -203,7 +234,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn record_destructuring() {
|
fn record_destructuring() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
{ x, y } = 5
|
{ x, y } = 5
|
||||||
{ x: 5 } = { x: 5 }
|
{ x: 5 } = { x: 5 }
|
||||||
|
@ -215,7 +246,7 @@ mod test_format {
|
||||||
|
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn record_field_destructuring() {
|
// fn record_field_destructuring() {
|
||||||
// assert_formats_same(indoc!(
|
// expr_formats_same(indoc!(
|
||||||
// r#"
|
// r#"
|
||||||
// case foo of
|
// case foo of
|
||||||
// { x: 5 } -> 42
|
// { x: 5 } -> 42
|
||||||
|
@ -225,7 +256,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn def_closure() {
|
fn def_closure() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
identity = \a -> a
|
identity = \a -> a
|
||||||
|
|
||||||
|
@ -238,22 +269,22 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_record() {
|
fn empty_record() {
|
||||||
assert_formats_same("{}");
|
expr_formats_same("{}");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn one_field() {
|
fn one_field() {
|
||||||
assert_formats_same("{ x: 4 }");
|
expr_formats_same("{ x: 4 }");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_fields() {
|
fn two_fields() {
|
||||||
assert_formats_same("{ x: 4, y: 42 }");
|
expr_formats_same("{ x: 4, y: 42 }");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_fields_newline() {
|
fn two_fields_newline() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
{
|
{
|
||||||
x: 4,
|
x: 4,
|
||||||
|
@ -265,7 +296,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_fields_center_newline() {
|
fn two_fields_center_newline() {
|
||||||
assert_formats_to(
|
expr_formats_to(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
{ x: 4,
|
{ x: 4,
|
||||||
|
@ -286,7 +317,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn one_unnamed_field() {
|
fn one_unnamed_field() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
foo = 4
|
foo = 4
|
||||||
|
|
||||||
|
@ -299,13 +330,13 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn single_line_if() {
|
fn single_line_if() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
if foo bar then a b c else d e f
|
if foo bar then a b c else d e f
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
|
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
if foo (a b c) then a b c else d e f
|
if foo (a b c) then a b c else d e f
|
||||||
"#
|
"#
|
||||||
|
@ -316,7 +347,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn integer_case() {
|
fn integer_case() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
case b when
|
case b when
|
||||||
1 ->
|
1 ->
|
||||||
|
@ -330,7 +361,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn case_with_comments() {
|
fn case_with_comments() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
case b when
|
case b when
|
||||||
# look at cases
|
# look at cases
|
||||||
|
@ -351,7 +382,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn nested_case() {
|
fn nested_case() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
case b when
|
case b when
|
||||||
_ ->
|
_ ->
|
||||||
|
@ -364,7 +395,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn case_with_moving_comments() {
|
fn case_with_moving_comments() {
|
||||||
assert_formats_to(
|
expr_formats_to(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
case b when
|
case b when
|
||||||
|
@ -395,7 +426,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multiple_blank_lines_collapse_to_one() {
|
fn multiple_blank_lines_collapse_to_one() {
|
||||||
assert_formats_to(
|
expr_formats_to(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
x = 5
|
x = 5
|
||||||
|
@ -423,7 +454,7 @@ mod test_format {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn def_returning_closure() {
|
fn def_returning_closure() {
|
||||||
assert_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
f = \x -> x
|
f = \x -> x
|
||||||
g = \x -> x
|
g = \x -> x
|
||||||
|
@ -436,4 +467,26 @@ mod test_format {
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MODULES
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_interface() {
|
||||||
|
module_formats_same(indoc!(
|
||||||
|
r#"
|
||||||
|
interface Foo exposes [] imports []
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn interface_exposing() {
|
||||||
|
module_formats_same(indoc!(
|
||||||
|
r#"
|
||||||
|
interface Foo
|
||||||
|
exposes []
|
||||||
|
imports []
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,14 +17,16 @@ mod test_parse {
|
||||||
use bumpalo::collections::vec::Vec;
|
use bumpalo::collections::vec::Vec;
|
||||||
use bumpalo::{self, Bump};
|
use bumpalo::{self, Bump};
|
||||||
use helpers::parse_with;
|
use helpers::parse_with;
|
||||||
|
use roc::module::ModuleName;
|
||||||
use roc::operator::BinOp::*;
|
use roc::operator::BinOp::*;
|
||||||
use roc::operator::CalledVia;
|
use roc::operator::CalledVia;
|
||||||
use roc::operator::UnaryOp;
|
use roc::operator::UnaryOp;
|
||||||
use roc::parse::ast::CommentOrNewline::*;
|
use roc::parse::ast::CommentOrNewline::*;
|
||||||
use roc::parse::ast::Expr::{self, *};
|
use roc::parse::ast::Expr::{self, *};
|
||||||
use roc::parse::ast::Pattern::{self, *};
|
use roc::parse::ast::Pattern::{self, *};
|
||||||
use roc::parse::ast::{Attempting, Def, Spaceable};
|
use roc::parse::ast::{Attempting, Def, InterfaceHeader, Spaceable};
|
||||||
use roc::parse::parser::{Fail, FailReason};
|
use roc::parse::module::interface_header;
|
||||||
|
use roc::parse::parser::{Fail, FailReason, Parser, State};
|
||||||
use roc::region::{Located, Region};
|
use roc::region::{Located, Region};
|
||||||
use std::{f64, i64};
|
use std::{f64, i64};
|
||||||
|
|
||||||
|
@ -1105,6 +1107,37 @@ mod test_parse {
|
||||||
assert_eq!(Ok(expected), actual);
|
assert_eq!(Ok(expected), actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MODULE
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_module() {
|
||||||
|
let arena = Bump::new();
|
||||||
|
let exposes = Vec::new_in(&arena);
|
||||||
|
let imports = Vec::new_in(&arena);
|
||||||
|
let module_name = ModuleName::new("Foo");
|
||||||
|
let expected = InterfaceHeader {
|
||||||
|
name: Located::new(0, 0, 10, 13, module_name),
|
||||||
|
exposes,
|
||||||
|
imports,
|
||||||
|
|
||||||
|
after_interface: &[],
|
||||||
|
before_exposes: &[],
|
||||||
|
after_exposes: &[],
|
||||||
|
before_imports: &[],
|
||||||
|
after_imports: &[],
|
||||||
|
};
|
||||||
|
let src = indoc!(
|
||||||
|
r#"
|
||||||
|
interface Foo exposes [] imports []
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
let actual = interface_header()
|
||||||
|
.parse(&arena, State::new(&src, Attempting::Module))
|
||||||
|
.map(|tuple| tuple.0);
|
||||||
|
|
||||||
|
assert_eq!(Ok(expected), actual);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO test hex/oct/binary parsing
|
// TODO test hex/oct/binary parsing
|
||||||
//
|
//
|
||||||
// TODO test for \t \r and \n in string literals *outside* unicode escape sequence!
|
// TODO test for \t \r and \n in string literals *outside* unicode escape sequence!
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue