diff --git a/cli/src/format.rs b/cli/src/format.rs index 13f3edc71f..ea2f1afea0 100644 --- a/cli/src/format.rs +++ b/cli/src/format.rs @@ -12,10 +12,11 @@ use roc_parse::ast::{ }; use roc_parse::header::{ AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, - PackageName, PlatformHeader, PlatformRequires, PlatformRigid, To, TypedIdent, + PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, }; use roc_parse::{ ast::{Def, Module}, + ident::UppercaseIdent, module::{self, module_defs}, parser::{Parser, SyntaxError}, state::State, @@ -285,7 +286,7 @@ impl<'a> RemoveSpaces<'a> for PlatformRequires<'a> { } } -impl<'a> RemoveSpaces<'a> for PlatformRigid<'a> { +impl<'a> RemoveSpaces<'a> for UppercaseIdent<'a> { fn remove_spaces(&self, _arena: &'a Bump) -> Self { *self } diff --git a/compiler/fmt/src/annotation.rs b/compiler/fmt/src/annotation.rs index 638f2946e6..b36b4d56b6 100644 --- a/compiler/fmt/src/annotation.rs +++ b/compiler/fmt/src/annotation.rs @@ -4,6 +4,7 @@ use crate::{ Buf, }; use roc_parse::ast::{AliasHeader, AssignedField, Expr, Tag, TypeAnnotation}; +use roc_parse::ident::UppercaseIdent; use roc_region::all::Loc; /// Does an AST node need parens around it? @@ -107,6 +108,22 @@ where } } +impl<'a> Formattable for UppercaseIdent<'a> { + fn is_multiline(&self) -> bool { + false + } + + fn format_with_options<'buf>( + &self, + buf: &mut Buf<'buf>, + _parens: Parens, + _newlines: Newlines, + _indent: u16, + ) { + buf.push_str((*self).into()) + } +} + impl<'a> Formattable for TypeAnnotation<'a> { fn is_multiline(&self) -> bool { use roc_parse::ast::TypeAnnotation::*; diff --git a/compiler/fmt/src/module.rs b/compiler/fmt/src/module.rs index 42a5c303be..808cc6be9b 100644 --- a/compiler/fmt/src/module.rs +++ b/compiler/fmt/src/module.rs @@ -6,7 +6,7 @@ use crate::Buf; use roc_parse::ast::{Collection, Module, Spaced}; use roc_parse::header::{ AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, - PackageName, PlatformHeader, PlatformRequires, PlatformRigid, To, TypedIdent, + PackageName, PlatformHeader, PlatformRequires, To, TypedIdent, }; use roc_region::all::Loc; @@ -206,18 +206,6 @@ impl<'a, T: Formattable> Formattable for Spaced<'a, T> { } } -impl<'a> Formattable for PlatformRigid<'a> { - fn is_multiline(&self) -> bool { - false - } - - fn format<'buf>(&self, buf: &mut Buf<'buf>, _indent: u16) { - buf.push_str(self.rigid); - buf.push_str("=>"); - buf.push_str(self.alias); - } -} - fn fmt_imports<'a, 'buf>( buf: &mut Buf<'buf>, loc_entries: Collection<'a, Loc>>>, diff --git a/compiler/parse/src/header.rs b/compiler/parse/src/header.rs index 59aeda3751..0e45d42f39 100644 --- a/compiler/parse/src/header.rs +++ b/compiler/parse/src/header.rs @@ -1,6 +1,6 @@ use crate::ast::{Collection, CommentOrNewline, Spaced, StrLiteral, TypeAnnotation}; use crate::blankspace::space0_e; -use crate::ident::lowercase_ident; +use crate::ident::{lowercase_ident, UppercaseIdent}; use crate::parser::Progress::*; use crate::parser::{specialize, word1, EPackageEntry, EPackageName, Parser}; use crate::state::State; @@ -126,15 +126,9 @@ pub struct PackageHeader<'a> { pub after_imports: &'a [CommentOrNewline<'a>], } -#[derive(Copy, Clone, Debug, PartialEq)] -pub struct PlatformRigid<'a> { - pub rigid: &'a str, - pub alias: &'a str, -} - #[derive(Clone, Debug, PartialEq)] pub struct PlatformRequires<'a> { - pub rigids: Collection<'a, Loc>>>, + pub rigids: Collection<'a, Loc>>>, pub signature: Loc>>, } diff --git a/compiler/parse/src/ident.rs b/compiler/parse/src/ident.rs index 773d86b4f5..73044aa6a9 100644 --- a/compiler/parse/src/ident.rs +++ b/compiler/parse/src/ident.rs @@ -5,6 +5,23 @@ use bumpalo::collections::vec::Vec; use bumpalo::Bump; use roc_region::all::Position; +/// A global tag, for example. Must start with an uppercase letter +/// and then contain only letters and numbers afterwards - no dots allowed! +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct UppercaseIdent<'a>(&'a str); + +impl<'a> From<&'a str> for UppercaseIdent<'a> { + fn from(string: &'a str) -> Self { + UppercaseIdent(string) + } +} + +impl<'a> From> for &'a str { + fn from(ident: UppercaseIdent<'a>) -> Self { + ident.0 + } +} + /// The parser accepts all of these in any position where any one of them could /// appear. This way, canonicalization can give more helpful error messages like /// "you can't redefine this tag!" if you wrote `Foo = ...` or @@ -91,6 +108,21 @@ pub fn tag_name<'a>() -> impl Parser<'a, &'a str, ()> { } } +/// This could be: +/// +/// * A module name +/// * A type name +/// * A global tag +pub fn uppercase<'a>() -> impl Parser<'a, UppercaseIdent<'a>, ()> { + move |_, state: State<'a>| match chomp_uppercase_part(state.bytes()) { + Err(progress) => Err((progress, (), state)), + Ok(ident) => { + let width = ident.len(); + Ok((MadeProgress, ident.into(), state.advance(width))) + } + } +} + /// This could be: /// /// * A module name diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index 7b5f3206fa..d313ffa11d 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -2,13 +2,13 @@ use crate::ast::{Collection, CommentOrNewline, Def, Module, Spaced}; use crate::blankspace::{space0_around_ee, space0_before_e, space0_e}; use crate::header::{ package_entry, package_name, AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, - ModuleName, PackageEntry, PlatformHeader, PlatformRequires, PlatformRigid, To, TypedIdent, + ModuleName, PackageEntry, PlatformHeader, PlatformRequires, To, TypedIdent, }; -use crate::ident::{lowercase_ident, unqualified_ident, uppercase_ident}; +use crate::ident::{self, lowercase_ident, unqualified_ident, uppercase_ident, UppercaseIdent}; use crate::parser::Progress::{self, *}; use crate::parser::{ - backtrackable, specialize, specialize_region, word1, word2, EEffects, EExposes, EHeader, - EImports, EPackages, EProvides, ERequires, ETypedIdent, Parser, SourceError, SyntaxError, + backtrackable, specialize, specialize_region, word1, EEffects, EExposes, EHeader, EImports, + EPackages, EProvides, ERequires, ETypedIdent, Parser, SourceError, SyntaxError, }; use crate::state::State; use crate::string_literal; @@ -439,10 +439,13 @@ fn platform_requires<'a>() -> impl Parser<'a, PlatformRequires<'a>, ERequires<'a #[inline(always)] fn requires_rigids<'a>( min_indent: u32, -) -> impl Parser<'a, Collection<'a, Loc>>>, ERequires<'a>> { +) -> impl Parser<'a, Collection<'a, Loc>>>, ERequires<'a>> { collection_trailing_sep_e!( word1(b'{', ERequires::ListStart), - specialize(|_, pos| ERequires::Rigid(pos), loc!(requires_rigid())), + specialize( + |_, pos| ERequires::Rigid(pos), + loc!(map!(ident::uppercase(), Spaced::Item)) + ), word1(b',', ERequires::ListEnd), word1(b'}', ERequires::ListEnd), min_indent, @@ -453,17 +456,6 @@ fn requires_rigids<'a>( ) } -#[inline(always)] -fn requires_rigid<'a>() -> impl Parser<'a, Spaced<'a, PlatformRigid<'a>>, ()> { - map!( - and!( - lowercase_ident(), - skip_first!(word2(b'=', b'>', |_| ()), uppercase_ident()) - ), - |(rigid, alias)| Spaced::Item(PlatformRigid { rigid, alias }) - ) -} - #[inline(always)] fn requires_typed_ident<'a>() -> impl Parser<'a, Loc>>, ERequires<'a>> { skip_first!( diff --git a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast index a36b138082..5dddd9d923 100644 --- a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast +++ b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.result-ast @@ -5,15 +5,14 @@ Platform { ), requires: PlatformRequires { rigids: [ - @36-48 PlatformRigid { - rigid: "model", - alias: "Model", - }, + @36-41 UppercaseIdent( + "Model", + ), ], - signature: @52-61 TypedIdent { - ident: @52-56 "main", + signature: @45-54 TypedIdent { + ident: @45-49 "main", spaces_before_colon: [], - ann: @59-61 Record { + ann: @52-54 Record { fields: [], ext: None, }, @@ -21,17 +20,17 @@ Platform { }, exposes: [], packages: [ - @94-106 PackageEntry { + @87-99 PackageEntry { shorthand: "foo", spaces_after_shorthand: [], - package_name: @99-106 PackageName( + package_name: @92-99 PackageName( "./foo", ), }, ], imports: [], provides: [ - @139-150 ExposedName( + @132-143 ExposedName( "mainForHost", ), ], diff --git a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc index c38245cdc6..97673c692a 100644 --- a/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc +++ b/compiler/parse/tests/snapshots/pass/nonempty_platform_header.header.roc @@ -1,5 +1,5 @@ platform "foo/barbaz" - requires {model=>Model} { main : {} } + requires {Model} { main : {} } exposes [] packages { foo: "./foo" } imports [] diff --git a/compiler/parse/tests/snapshots/pass/requires_type.header.result-ast b/compiler/parse/tests/snapshots/pass/requires_type.header.result-ast new file mode 100644 index 0000000000..2195dcac19 --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/requires_type.header.result-ast @@ -0,0 +1,77 @@ +Platform { + header: PlatformHeader { + name: @9-21 PackageName( + "test/types", + ), + requires: PlatformRequires { + rigids: [ + @37-42 UppercaseIdent( + "Flags", + ), + @44-49 UppercaseIdent( + "Model", + ), + ], + signature: @55-77 TypedIdent { + ident: @55-59 "main", + spaces_before_colon: [], + ann: @62-77 Apply( + "", + "App", + [ + @66-71 Apply( + "", + "Flags", + [], + ), + @72-77 Apply( + "", + "Model", + [], + ), + ], + ), + }, + }, + exposes: [], + packages: [], + imports: [], + provides: [ + @141-152 ExposedName( + "mainForHost", + ), + ], + effects: Effects { + spaces_before_effects_keyword: [ + Newline, + ], + spaces_after_effects_keyword: [], + spaces_after_type_name: [], + effect_shortname: "fx", + effect_type_name: "Effect", + entries: [], + }, + before_header: [], + after_platform_keyword: [], + before_requires: [ + Newline, + ], + after_requires: [], + before_exposes: [ + Newline, + ], + after_exposes: [], + before_packages: [ + Newline, + ], + after_packages: [], + before_imports: [ + Newline, + ], + after_imports: [], + before_provides: [ + Newline, + ], + after_provides: [], + }, +} diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 28d1825a8f..d575d677a1 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -266,8 +266,14 @@ mod test_parse { let result = func(&input); let actual_result = if should_pass { + eprintln!("The source code for this test did not successfully parse!\n"); + result.unwrap() } else { + eprintln!( + "The source code for this test successfully parsed, but it was not expected to!\n" + ); + result.unwrap_err() };