diff --git a/compiler/fmt/src/collection.rs b/compiler/fmt/src/collection.rs index 3128c4dcf3..40a6f52d24 100644 --- a/compiler/fmt/src/collection.rs +++ b/compiler/fmt/src/collection.rs @@ -14,7 +14,7 @@ pub struct CollectionConfig { pub fn fmt_collection<'a, F: Formattable<'a>>( buf: &mut String<'a>, - items: Collection<'a, F>, + items: Collection<'_, F>, indent: u16, config: CollectionConfig, ) { diff --git a/compiler/fmt/src/module.rs b/compiler/fmt/src/module.rs index 3a9a8a495b..43fe1cf9b2 100644 --- a/compiler/fmt/src/module.rs +++ b/compiler/fmt/src/module.rs @@ -5,8 +5,8 @@ use crate::spaces::{fmt_default_spaces, fmt_spaces, INDENT}; use bumpalo::collections::String; use roc_parse::ast::{Collection, Module}; use roc_parse::header::{ - AppHeader, ExposesEntry, ImportsEntry, InterfaceHeader, PackageEntry, PackageOrPath, - PlatformHeader, To, + AppHeader, Effects, ExposesEntry, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, + PackageName, PackageOrPath, PlatformHeader, PlatformRequires, PlatformRigid, To, TypedIdent, }; use roc_region::all::Located; @@ -37,7 +37,7 @@ pub fn fmt_interface_header<'a>(buf: &mut String<'a>, header: &'a InterfaceHeade fmt_default_spaces(buf, header.before_exposes, " ", indent); buf.push_str("exposes"); fmt_default_spaces(buf, header.after_exposes, " ", indent); - fmt_exposes(buf, &header.exposes, indent); + fmt_exposes(buf, header.exposes, indent); // imports fmt_default_spaces(buf, header.before_imports, " ", indent); @@ -77,8 +77,156 @@ pub fn fmt_app_header<'a>(buf: &mut String<'a>, header: &'a AppHeader<'a>) { fmt_to(buf, header.to.value, indent); } -pub fn fmt_platform_header<'a>(_buf: &mut String<'a>, _header: &'a PlatformHeader<'a>) { - todo!("TODO fmt platform header"); +pub fn fmt_platform_header<'a>(buf: &mut String<'a>, header: &'a PlatformHeader<'a>) { + let indent = INDENT; + + buf.push_str("platform"); + + fmt_default_spaces(buf, header.after_platform_keyword, " ", indent); + fmt_package_name(buf, header.name.value); + + // requires + fmt_default_spaces(buf, header.before_requires, " ", indent); + buf.push_str("requires"); + fmt_default_spaces(buf, header.after_requires, " ", indent); + fmt_requires(buf, &header.requires, indent); + + // exposes + fmt_default_spaces(buf, header.before_exposes, " ", indent); + buf.push_str("exposes"); + fmt_default_spaces(buf, header.after_exposes, " ", indent); + fmt_exposes(buf, header.exposes, indent); + + // packages + fmt_default_spaces(buf, header.before_packages, " ", indent); + buf.push_str("packages"); + fmt_default_spaces(buf, header.after_packages, " ", indent); + fmt_packages(buf, header.packages, indent); + + // imports + fmt_default_spaces(buf, header.before_imports, " ", indent); + buf.push_str("imports"); + fmt_default_spaces(buf, header.after_imports, " ", indent); + fmt_imports(buf, header.imports, indent); + + // provides + fmt_default_spaces(buf, header.before_provides, " ", indent); + buf.push_str("provides"); + fmt_default_spaces(buf, header.after_provides, " ", indent); + fmt_provides(buf, header.provides, indent); + + fmt_effects(buf, &header.effects, indent); +} + +fn fmt_requires<'a>(buf: &mut String<'a>, requires: &PlatformRequires<'a>, indent: u16) { + fmt_collection( + buf, + requires.rigids, + indent, + CollectionConfig { + begin: '{', + end: '}', + delimiter: ',', + }, + ); + + buf.push_str(" { "); + fmt_typed_ident(buf, &requires.signature.value, indent); + buf.push_str(" }"); +} + +fn fmt_effects<'a>(buf: &mut String<'a>, effects: &Effects<'a>, indent: u16) { + fmt_default_spaces(buf, effects.spaces_before_effects_keyword, " ", indent); + buf.push_str("effects"); + fmt_default_spaces(buf, effects.spaces_after_effects_keyword, " ", indent); + + buf.push_str(effects.effect_shortname); + buf.push('.'); + buf.push_str(effects.effect_type_name); + + fmt_default_spaces(buf, effects.spaces_after_type_name, " ", indent); + + fmt_collection( + buf, + effects.entries, + indent, + CollectionConfig { + begin: '{', + end: '}', + delimiter: ',', + }, + ); +} + +fn fmt_typed_ident<'a>(buf: &mut String<'a>, entry: &TypedIdent<'a>, indent: u16) { + use TypedIdent::*; + match entry { + Entry { + ident, + spaces_before_colon, + ann, + } => { + buf.push_str(ident.value); + fmt_default_spaces(buf, spaces_before_colon, " ", indent); + buf.push(':'); + ann.value.format(buf, indent); + } + SpaceBefore(sub_entry, spaces) => { + fmt_spaces(buf, spaces.iter(), indent); + fmt_typed_ident(buf, sub_entry, indent); + } + SpaceAfter(sub_entry, spaces) => { + fmt_typed_ident(buf, sub_entry, indent); + fmt_spaces(buf, spaces.iter(), indent); + } + } +} + +impl<'a> Formattable<'a> for TypedIdent<'a> { + fn is_multiline(&self) -> bool { + false + } + + fn format(&self, buf: &mut String<'a>, indent: u16) { + fmt_typed_ident(buf, self, indent); + } +} + +impl<'a> Formattable<'a> for PlatformRigid<'a> { + fn is_multiline(&self) -> bool { + false + } + + fn format(&self, buf: &mut String<'a>, indent: u16) { + fmt_platform_rigid(buf, self, indent); + } +} + +fn fmt_package_name<'a>(buf: &mut String<'a>, name: PackageName) { + buf.push_str(name.account); + buf.push('/'); + buf.push_str(name.pkg); +} + +fn fmt_platform_rigid<'a>(buf: &mut String<'a>, entry: &PlatformRigid<'a>, indent: u16) { + use roc_parse::header::PlatformRigid::*; + + match entry { + Entry { rigid, alias } => { + buf.push_str(rigid); + buf.push_str("=>"); + buf.push_str(alias); + } + + SpaceBefore(sub_entry, spaces) => { + fmt_spaces(buf, spaces.iter(), indent); + fmt_platform_rigid(buf, sub_entry, indent); + } + SpaceAfter(sub_entry, spaces) => { + fmt_platform_rigid(buf, sub_entry, indent); + fmt_spaces(buf, spaces.iter(), indent); + } + } } fn fmt_imports<'a>( @@ -124,14 +272,14 @@ fn fmt_to<'a>(buf: &mut String<'a>, to: To<'a>, indent: u16) { } } -fn fmt_exposes<'a>( +fn fmt_exposes<'a, N: FormatName + 'a>( buf: &mut String<'a>, - loc_entries: &'a Collection<'a, Located>>, + loc_entries: Collection<'_, Located>>, indent: u16, ) { fmt_collection( buf, - *loc_entries, + loc_entries, indent, CollectionConfig { begin: '[', @@ -141,7 +289,7 @@ fn fmt_exposes<'a>( ); } -impl<'a> Formattable<'a> for ExposesEntry<'a, &'a str> { +impl<'a, 'b, N: FormatName> Formattable<'a> for ExposesEntry<'b, N> { fn is_multiline(&self) -> bool { false } @@ -151,11 +299,31 @@ impl<'a> Formattable<'a> for ExposesEntry<'a, &'a str> { } } -fn fmt_exposes_entry<'a>(buf: &mut String<'a>, entry: &ExposesEntry<'a, &'a str>, indent: u16) { +pub trait FormatName { + fn format<'a>(&self, buf: &mut String<'a>); +} + +impl<'a> FormatName for &'a str { + fn format<'b>(&self, buf: &mut String<'b>) { + buf.push_str(self) + } +} + +impl<'a> FormatName for ModuleName<'a> { + fn format<'b>(&self, buf: &mut String<'b>) { + buf.push_str(self.as_str()); + } +} + +fn fmt_exposes_entry<'a, 'b, N: FormatName>( + buf: &mut String<'a>, + entry: &ExposesEntry<'b, N>, + indent: u16, +) { use roc_parse::header::ExposesEntry::*; match entry { - Exposed(ident) => buf.push_str(ident), + Exposed(ident) => ident.format(buf), SpaceBefore(sub_entry, spaces) => { fmt_spaces(buf, spaces.iter(), indent); @@ -260,8 +428,25 @@ fn fmt_imports_entry<'a>(buf: &mut String<'a>, entry: &ImportsEntry<'a>, indent: } } - Package(_pkg, _name, _entries) => { - todo!("TODO Format imported package"); + Package(pkg, name, entries) => { + buf.push_str(pkg); + buf.push('.'); + buf.push_str(name.as_str()); + + if !entries.is_empty() { + buf.push('.'); + + fmt_collection( + buf, + *entries, + indent, + CollectionConfig { + begin: '{', + end: '}', + delimiter: ',', + }, + ); + } } SpaceBefore(sub_entry, spaces) => { diff --git a/compiler/fmt/src/spaces.rs b/compiler/fmt/src/spaces.rs index afb8bc52db..1103449590 100644 --- a/compiler/fmt/src/spaces.rs +++ b/compiler/fmt/src/spaces.rs @@ -29,9 +29,9 @@ pub fn fmt_default_spaces<'a>( } } -pub fn fmt_spaces<'b, 'a: 'b, I>(buf: &mut String<'a>, spaces: I, indent: u16) +pub fn fmt_spaces<'a, 'b, I>(buf: &mut String<'a>, spaces: I, indent: u16) where - I: Iterator>, + I: Iterator>, { use self::CommentOrNewline::*; @@ -85,13 +85,13 @@ pub enum NewlineAt { /// The `new_line_at` argument describes how new lines should be inserted /// at the beginning or at the end of the block /// in the case of there is some comment in the `spaces` argument. -pub fn fmt_comments_only<'a, I>( +pub fn fmt_comments_only<'a, 'b, I>( buf: &mut String<'a>, spaces: I, new_line_at: NewlineAt, indent: u16, ) where - I: Iterator>, + I: Iterator>, { use self::CommentOrNewline::*; use NewlineAt::*; @@ -122,7 +122,7 @@ pub fn fmt_comments_only<'a, I>( } } -fn fmt_comment<'a>(buf: &mut String<'a>, comment: &'a str) { +fn fmt_comment<'a>(buf: &mut String<'a>, comment: &str) { buf.push('#'); if !comment.starts_with(' ') { buf.push(' '); @@ -130,7 +130,7 @@ fn fmt_comment<'a>(buf: &mut String<'a>, comment: &'a str) { buf.push_str(comment); } -fn fmt_docs<'a>(buf: &mut String<'a>, docs: &'a str) { +fn fmt_docs<'a>(buf: &mut String<'a>, docs: &str) { buf.push_str("##"); if !docs.starts_with(' ') { buf.push(' '); diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index 689784559b..6442748d5b 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -2489,6 +2489,30 @@ mod test_fmt { )); } + #[test] + fn single_line_platform() { + // There are many places that there should probably be spaces, e.g.: + // requires { model=>Model, msg=>Msg } { main : Effect {} } + // ^ + // putLine : Str -> Effect {}, + // ^ + // TODO: improve spacing + module_formats_same( + "platform folkertdev/foo \ + requires { model=>Model, msg=>Msg } { main :Effect {} } \ + exposes [] \ + packages {} \ + imports [ Task.{ Task } ] \ + provides [ mainForHost ] \ + effects fx.Effect \ + { \ + putLine :Str -> Effect {}, \ + putInt :I64 -> Effect {}, \ + getInt :Effect { value : I64, errorCode : [ A, B ], isError : Bool } \ + } ", + ); + } + /// Annotations and aliases #[test] diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index d0a79e8169..4ac1f371e1 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -3437,7 +3437,7 @@ fn fabricate_effects_module<'a>( let module_id: ModuleId; - let effect_entries = unpack_exposes_entries(arena, effects.entries); + let effect_entries = unpack_exposes_entries(arena, effects.entries.items); let name = effects.effect_type_name; let declared_name: ModuleName = name.into(); diff --git a/compiler/parse/src/header.rs b/compiler/parse/src/header.rs index 404e0cc601..abcf0aafd8 100644 --- a/compiler/parse/src/header.rs +++ b/compiler/parse/src/header.rs @@ -174,7 +174,7 @@ pub struct Effects<'a> { pub spaces_after_type_name: &'a [CommentOrNewline<'a>], pub effect_shortname: &'a str, pub effect_type_name: &'a str, - pub entries: &'a [Loc>], + pub entries: Collection<'a, Loc>>, } #[derive(Copy, Clone, Debug, PartialEq)] diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index f0d2200e49..256a3c18e6 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -718,7 +718,7 @@ fn effects<'a>() -> impl Parser<'a, Effects<'a>, EEffects<'a>> { spaces_after_type_name, effect_shortname: type_shortname, effect_type_name: type_name, - entries: entries.items, + entries, }, state, ))