mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 00:24:34 +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.
|
||||
/// 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> {
|
||||
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};
|
||||
|
||||
/// 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
|
||||
/// e.g. (Result.Ok)
|
||||
/// Parameterized on a phantom marker for whether it has been canonicalized
|
||||
|
|
|
@ -18,15 +18,15 @@ pub mod string;
|
|||
pub mod constrain;
|
||||
pub mod ena;
|
||||
pub mod fmt;
|
||||
pub mod gen;
|
||||
pub mod infer;
|
||||
pub mod module;
|
||||
pub mod pretty_print_types;
|
||||
pub mod solve;
|
||||
pub mod subs;
|
||||
pub mod types;
|
||||
pub mod unify;
|
||||
|
||||
pub mod gen;
|
||||
|
||||
extern crate bumpalo;
|
||||
extern crate fraction;
|
||||
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::Vec;
|
||||
use bumpalo::Bump;
|
||||
use fmt::{self, is_multiline_expr};
|
||||
use ident::UnqualifiedIdent;
|
||||
use module::ModuleName;
|
||||
use operator::CalledVia;
|
||||
use operator::{BinOp, UnaryOp};
|
||||
use parse::ident::Ident;
|
||||
use region::{Loc, Region};
|
||||
|
||||
/// The number of spaces to indent.
|
||||
const INDENT: u16 = 4;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Module<'a> {
|
||||
Interface {
|
||||
header: InterfaceHeader<'a>,
|
||||
defs: Vec<'a, Def<'a>>,
|
||||
defs: Vec<'a, Loc<Def<'a>>>,
|
||||
},
|
||||
App {
|
||||
header: AppHeader<'a>,
|
||||
defs: Vec<'a, Def<'a>>,
|
||||
defs: Vec<'a, Loc<Def<'a>>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct InterfaceHeader<'a> {
|
||||
pub name: Loc<(&'a [&'a str], &'a str)>,
|
||||
pub exposes: Vec<'a, Loc<HeaderEntry<'a>>>,
|
||||
pub imports: Vec<'a, Loc<HeaderEntry<'a>>>,
|
||||
pub name: Loc<ModuleName<'a>>,
|
||||
pub exposes: Vec<'a, Loc<ExposesEntry<'a>>>,
|
||||
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
|
||||
|
||||
// Potential comments and newlines - these will typically all be empty.
|
||||
pub after_interface: &'a [CommentOrNewline<'a>],
|
||||
|
@ -38,21 +36,31 @@ pub struct InterfaceHeader<'a> {
|
|||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
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.
|
||||
pub after_app: &'a [CommentOrNewline<'a>],
|
||||
pub before_imports: &'a [CommentOrNewline<'a>],
|
||||
pub after_imports: &'a [CommentOrNewline<'a>],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum HeaderEntry<'a> {
|
||||
Val(&'a str),
|
||||
TypeOnly(&'a str),
|
||||
TypeAndVariants(&'a str),
|
||||
SpaceBefore(&'a HeaderEntry<'a>, &'a [CommentOrNewline<'a>]),
|
||||
SpaceAfter(&'a HeaderEntry<'a>, &'a [CommentOrNewline<'a>]),
|
||||
pub enum ExposesEntry<'a> {
|
||||
/// e.g. `Task`
|
||||
Ident(UnqualifiedIdent<'a>),
|
||||
|
||||
// Spaces
|
||||
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`).
|
||||
|
@ -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 {
|
||||
HeaderEntry::SpaceBefore(self, spaces)
|
||||
ExposesEntry::SpaceBefore(self, spaces)
|
||||
}
|
||||
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::Bump;
|
||||
use collections::arena_join;
|
||||
use ident::UnqualifiedIdent;
|
||||
use parse::ast::{Attempting, MaybeQualified};
|
||||
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())
|
||||
}
|
||||
|
||||
pub fn unqualified_ident<'a>() -> impl Parser<'a, UnqualifiedIdent<'a>> {
|
||||
map!(
|
||||
variant_or_ident(|first_char| first_char.is_alphabetic()),
|
||||
UnqualifiedIdent::new
|
||||
)
|
||||
}
|
||||
|
||||
// TESTS
|
||||
|
||||
// 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 operator::{BinOp, CalledVia, UnaryOp};
|
||||
use parse;
|
||||
use parse::ast::{
|
||||
AppHeader, AssignedField, Attempting, CommentOrNewline, Def, Expr, HeaderEntry,
|
||||
InterfaceHeader, MaybeQualified, Module, Pattern, Spaceable,
|
||||
};
|
||||
use parse::ast::{AssignedField, Attempting, Def, Expr, MaybeQualified, Pattern, Spaceable};
|
||||
use parse::blankspace::{
|
||||
space0, space0_after, space0_around, space0_before, space1, space1_after, space1_around,
|
||||
space1_before,
|
||||
|
@ -35,107 +32,6 @@ use parse::parser::{
|
|||
use parse::record::record;
|
||||
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>> {
|
||||
// Recursive parsers must not directly invoke functions which return (impl Parser),
|
||||
// as this causes rustc to stack overflow. Thus, parse_expr must be a
|
||||
|
|
|
@ -1,16 +1,200 @@
|
|||
use ident::Ident;
|
||||
use parse::ast::{Expr, Pattern};
|
||||
use bumpalo::collections::{String, Vec};
|
||||
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 name: Ident,
|
||||
pub exposes: Vec<Ident>,
|
||||
pub uses: Vec<Ident>,
|
||||
pub decls: Vec<Decl<'a>>,
|
||||
pub fn module<'a>() -> impl Parser<'a, Module<'a>> {
|
||||
one_of2(interface_module(), app_module())
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Decl<'a> {
|
||||
Def(Pattern<'a>, Expr<'a>, Expr<'a>),
|
||||
// TODO Alias
|
||||
// TODO SumType
|
||||
#[inline(always)]
|
||||
pub fn interface_module<'a>() -> impl Parser<'a, Module<'a>> {
|
||||
map!(and!(interface_header(), module_defs()), |(header, defs)| {
|
||||
Module::Interface { header, defs }
|
||||
})
|
||||
}
|
||||
|
||||
#[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)]
|
||||
mod test_format {
|
||||
use bumpalo::collections::String;
|
||||
use bumpalo::Bump;
|
||||
use roc::fmt::expr::fmt_expr;
|
||||
use roc::fmt::module::fmt_module;
|
||||
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::module::module;
|
||||
use roc::parse::parser::{Fail, Parser, State};
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fn assert_formats_to(input: &str, expected: &str) {
|
||||
fn expr_formats_to(input: &str, expected: &str) {
|
||||
let arena = Bump::new();
|
||||
let input = input.trim_end();
|
||||
let expected = expected.trim_end();
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_formats_same(input: &str) {
|
||||
assert_formats_to(input, input);
|
||||
fn expr_formats_same(input: &str) {
|
||||
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
|
||||
|
||||
#[test]
|
||||
fn empty_string() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
""
|
||||
"#
|
||||
|
@ -52,7 +83,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn basic_string() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
"blah"
|
||||
"#
|
||||
|
@ -61,7 +92,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn escaped_unicode_string() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
"unicode: \u{A00A}!"
|
||||
"#
|
||||
|
@ -70,7 +101,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn escaped_quote_string() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
"\""
|
||||
"#
|
||||
|
@ -79,7 +110,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn empty_block_string() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
""""""
|
||||
"#
|
||||
|
@ -88,7 +119,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn basic_block_string() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
"""blah"""
|
||||
"#
|
||||
|
@ -97,7 +128,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn newlines_block_string() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
"""blah
|
||||
spam
|
||||
|
@ -108,7 +139,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn quotes_block_string() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
"""
|
||||
|
||||
|
@ -121,7 +152,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn zero() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
0
|
||||
"#
|
||||
|
@ -130,7 +161,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn zero_point_zero() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
0.0
|
||||
"#
|
||||
|
@ -139,7 +170,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn int_with_underscores() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
1_23_456
|
||||
"#
|
||||
|
@ -148,7 +179,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn float_with_underscores() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
1_23_456.7_89_10
|
||||
"#
|
||||
|
@ -157,7 +188,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn multi_arg_closure() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
\a b c -> a b c
|
||||
"#
|
||||
|
@ -168,7 +199,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn single_def() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
x = 5
|
||||
|
||||
|
@ -179,7 +210,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn two_defs() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
x = 5
|
||||
y = 10
|
||||
|
@ -191,7 +222,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn parenthetical_def() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
(UserId userId) = 5
|
||||
y = 10
|
||||
|
@ -203,7 +234,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn record_destructuring() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
{ x, y } = 5
|
||||
{ x: 5 } = { x: 5 }
|
||||
|
@ -215,7 +246,7 @@ mod test_format {
|
|||
|
||||
// #[test]
|
||||
// fn record_field_destructuring() {
|
||||
// assert_formats_same(indoc!(
|
||||
// expr_formats_same(indoc!(
|
||||
// r#"
|
||||
// case foo of
|
||||
// { x: 5 } -> 42
|
||||
|
@ -225,7 +256,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn def_closure() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
identity = \a -> a
|
||||
|
||||
|
@ -238,22 +269,22 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn empty_record() {
|
||||
assert_formats_same("{}");
|
||||
expr_formats_same("{}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_field() {
|
||||
assert_formats_same("{ x: 4 }");
|
||||
expr_formats_same("{ x: 4 }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_fields() {
|
||||
assert_formats_same("{ x: 4, y: 42 }");
|
||||
expr_formats_same("{ x: 4, y: 42 }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_fields_newline() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
{
|
||||
x: 4,
|
||||
|
@ -265,7 +296,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn two_fields_center_newline() {
|
||||
assert_formats_to(
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x: 4,
|
||||
|
@ -286,7 +317,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn one_unnamed_field() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
foo = 4
|
||||
|
||||
|
@ -299,13 +330,13 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn single_line_if() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
if foo bar then a b c else d e f
|
||||
"#
|
||||
));
|
||||
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
if foo (a b c) then a b c else d e f
|
||||
"#
|
||||
|
@ -316,7 +347,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn integer_case() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
case b when
|
||||
1 ->
|
||||
|
@ -330,7 +361,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn case_with_comments() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
case b when
|
||||
# look at cases
|
||||
|
@ -351,7 +382,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn nested_case() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
case b when
|
||||
_ ->
|
||||
|
@ -364,7 +395,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn case_with_moving_comments() {
|
||||
assert_formats_to(
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
case b when
|
||||
|
@ -395,7 +426,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn multiple_blank_lines_collapse_to_one() {
|
||||
assert_formats_to(
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
x = 5
|
||||
|
@ -423,7 +454,7 @@ mod test_format {
|
|||
|
||||
#[test]
|
||||
fn def_returning_closure() {
|
||||
assert_formats_same(indoc!(
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
f = \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::{self, Bump};
|
||||
use helpers::parse_with;
|
||||
use roc::module::ModuleName;
|
||||
use roc::operator::BinOp::*;
|
||||
use roc::operator::CalledVia;
|
||||
use roc::operator::UnaryOp;
|
||||
use roc::parse::ast::CommentOrNewline::*;
|
||||
use roc::parse::ast::Expr::{self, *};
|
||||
use roc::parse::ast::Pattern::{self, *};
|
||||
use roc::parse::ast::{Attempting, Def, Spaceable};
|
||||
use roc::parse::parser::{Fail, FailReason};
|
||||
use roc::parse::ast::{Attempting, Def, InterfaceHeader, Spaceable};
|
||||
use roc::parse::module::interface_header;
|
||||
use roc::parse::parser::{Fail, FailReason, Parser, State};
|
||||
use roc::region::{Located, Region};
|
||||
use std::{f64, i64};
|
||||
|
||||
|
@ -1105,6 +1107,37 @@ mod test_parse {
|
|||
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 for \t \r and \n in string literals *outside* unicode escape sequence!
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue