Reorganize fmt and module

This commit is contained in:
Richard Feldman 2019-11-25 20:42:44 -05:00
parent ed23e23a54
commit 4926bfbc3a
17 changed files with 1192 additions and 771 deletions

View file

@ -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
} }

View file

@ -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
View 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
View 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
View 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
View 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
View 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
View 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);
}
}
}
}

View file

@ -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

View file

@ -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
View 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>]),
}

View file

@ -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(' ');
}
}

View file

@ -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> {

View file

@ -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

View file

@ -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)
}
)
} }

View file

@ -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 []
"#
));
}
} }

View file

@ -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!