mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 15:51:12 +00:00
Merge pull request #6708 from roc-lang/module-params-syntax
Parse and format module params
This commit is contained in:
commit
e5ea6dc461
46 changed files with 989 additions and 162 deletions
|
@ -9,8 +9,8 @@ use roc_module::called_via::{BinOp, CalledVia};
|
|||
use roc_module::ident::ModuleName;
|
||||
use roc_parse::ast::Expr::{self, *};
|
||||
use roc_parse::ast::{
|
||||
AssignedField, Collection, Pattern, RecordBuilderField, StrLiteral, StrSegment, ValueDef,
|
||||
WhenBranch,
|
||||
AssignedField, Collection, ModuleImportParams, Pattern, RecordBuilderField, StrLiteral,
|
||||
StrSegment, ValueDef, WhenBranch,
|
||||
};
|
||||
use roc_region::all::{LineInfo, Loc, Region};
|
||||
|
||||
|
@ -131,11 +131,26 @@ fn desugar_value_def<'a>(
|
|||
}
|
||||
}
|
||||
ModuleImport(roc_parse::ast::ModuleImport {
|
||||
before_name: _,
|
||||
name: _,
|
||||
alias: _,
|
||||
exposed: _,
|
||||
}) => *def,
|
||||
before_name,
|
||||
name,
|
||||
params,
|
||||
alias,
|
||||
exposed,
|
||||
}) => {
|
||||
let desugared_params =
|
||||
params.map(|ModuleImportParams { before, params }| ModuleImportParams {
|
||||
before,
|
||||
params: desugar_field_collection(arena, params, src, line_info, module_path),
|
||||
});
|
||||
|
||||
ModuleImport(roc_parse::ast::ModuleImport {
|
||||
before_name,
|
||||
name: *name,
|
||||
params: desugared_params,
|
||||
alias: *alias,
|
||||
exposed: *exposed,
|
||||
})
|
||||
}
|
||||
IngestedFileImport(_) => *def,
|
||||
|
||||
Stmt(stmt_expr) => {
|
||||
|
@ -383,15 +398,7 @@ pub fn desugar_expr<'a>(
|
|||
})
|
||||
}
|
||||
Record(fields) => {
|
||||
let mut allocated = Vec::with_capacity_in(fields.len(), arena);
|
||||
for field in fields.iter() {
|
||||
let value = desugar_field(arena, &field.value, src, line_info, module_path);
|
||||
allocated.push(Loc {
|
||||
value,
|
||||
region: field.region,
|
||||
});
|
||||
}
|
||||
let fields = fields.replace_items(allocated.into_bump_slice());
|
||||
let fields = desugar_field_collection(arena, *fields, src, line_info, module_path);
|
||||
arena.alloc(Loc {
|
||||
region: loc_expr.region,
|
||||
value: Record(fields),
|
||||
|
@ -825,6 +832,24 @@ fn desugar_str_segments<'a>(
|
|||
.into_bump_slice()
|
||||
}
|
||||
|
||||
fn desugar_field_collection<'a>(
|
||||
arena: &'a Bump,
|
||||
fields: Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>,
|
||||
src: &'a str,
|
||||
line_info: &mut Option<LineInfo>,
|
||||
module_path: &str,
|
||||
) -> Collection<'a, Loc<AssignedField<'a, Expr<'a>>>> {
|
||||
let mut allocated = Vec::with_capacity_in(fields.len(), arena);
|
||||
|
||||
for field in fields.iter() {
|
||||
let value = desugar_field(arena, &field.value, src, line_info, module_path);
|
||||
|
||||
allocated.push(Loc::at(field.region, value));
|
||||
}
|
||||
|
||||
fields.replace_items(allocated.into_bump_slice())
|
||||
}
|
||||
|
||||
fn desugar_field<'a>(
|
||||
arena: &'a Bump,
|
||||
field: &'a AssignedField<'a, Expr<'a>>,
|
||||
|
|
|
@ -6,8 +6,8 @@ use crate::spaces::{fmt_default_newline, fmt_default_spaces, fmt_spaces, INDENT}
|
|||
use crate::Buf;
|
||||
use roc_parse::ast::{
|
||||
AbilityMember, Defs, Expr, ExtractSpaces, ImportAlias, ImportAsKeyword, ImportExposingKeyword,
|
||||
ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport, Pattern, Spaces,
|
||||
StrLiteral, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
|
||||
ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport,
|
||||
ModuleImportParams, Pattern, Spaces, StrLiteral, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
|
||||
};
|
||||
use roc_parse::header::Keyword;
|
||||
use roc_region::all::Loc;
|
||||
|
@ -192,12 +192,14 @@ impl<'a> Formattable for ModuleImport<'a> {
|
|||
let Self {
|
||||
before_name,
|
||||
name,
|
||||
params,
|
||||
alias,
|
||||
exposed,
|
||||
} = self;
|
||||
|
||||
!before_name.is_empty()
|
||||
|| name.is_multiline()
|
||||
|| params.is_multiline()
|
||||
|| alias.is_multiline()
|
||||
|| match exposed {
|
||||
Some(a) => a.keyword.is_multiline() || is_collection_multiline(&a.item),
|
||||
|
@ -215,6 +217,7 @@ impl<'a> Formattable for ModuleImport<'a> {
|
|||
let Self {
|
||||
before_name,
|
||||
name,
|
||||
params,
|
||||
alias,
|
||||
exposed,
|
||||
} = self;
|
||||
|
@ -222,35 +225,44 @@ impl<'a> Formattable for ModuleImport<'a> {
|
|||
buf.indent(indent);
|
||||
buf.push_str("import");
|
||||
|
||||
let indent = indent + INDENT;
|
||||
let indent = if !before_name.is_empty()
|
||||
|| (params.is_multiline() && exposed.is_some())
|
||||
|| alias.is_multiline()
|
||||
|| exposed.map_or(false, |e| e.keyword.is_multiline())
|
||||
{
|
||||
indent + INDENT
|
||||
} else {
|
||||
indent
|
||||
};
|
||||
|
||||
fmt_default_spaces(buf, before_name, indent);
|
||||
|
||||
buf.indent(indent);
|
||||
name.format(buf, indent);
|
||||
|
||||
if let Some(alias) = alias {
|
||||
alias.format(buf, indent);
|
||||
}
|
||||
params.format(buf, indent);
|
||||
alias.format(buf, indent);
|
||||
|
||||
if let Some(exposed) = exposed {
|
||||
exposed.keyword.format(buf, indent);
|
||||
|
||||
let list_indent = if !before_name.is_empty()
|
||||
|| alias.is_multiline()
|
||||
|| exposed.keyword.is_multiline()
|
||||
{
|
||||
indent
|
||||
} else {
|
||||
// Align list with import keyword
|
||||
indent - INDENT
|
||||
};
|
||||
|
||||
fmt_collection(buf, list_indent, Braces::Square, exposed.item, Newlines::No);
|
||||
fmt_collection(buf, indent, Braces::Square, exposed.item, Newlines::No);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for ModuleImportParams<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
let ModuleImportParams { before, params } = self;
|
||||
|
||||
!before.is_empty() || is_collection_multiline(params)
|
||||
}
|
||||
|
||||
fn format_with_options(&self, buf: &mut Buf, _parens: Parens, newlines: Newlines, indent: u16) {
|
||||
let ModuleImportParams { before, params } = self;
|
||||
|
||||
fmt_default_spaces(buf, before, indent);
|
||||
fmt_collection(buf, indent, Braces::Curly, *params, newlines);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for IngestedFileImport<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
let Self {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::cmp::max;
|
||||
|
||||
use crate::annotation::{is_collection_multiline, Formattable, Newlines, Parens};
|
||||
use crate::collection::{fmt_collection, Braces};
|
||||
use crate::expr::fmt_str_literal;
|
||||
|
@ -177,7 +179,20 @@ pub fn fmt_module_header<'a>(buf: &mut Buf, header: &'a ModuleHeader<'a>) {
|
|||
buf.indent(0);
|
||||
buf.push_str("module");
|
||||
|
||||
let indent = fmt_spaces_with_outdent(buf, header.before_exposes, INDENT);
|
||||
let mut indent = fmt_spaces_with_outdent(buf, header.after_keyword, 0);
|
||||
|
||||
if let Some(params) = &header.params {
|
||||
if is_collection_multiline(¶ms.params) {
|
||||
indent = INDENT;
|
||||
}
|
||||
|
||||
fmt_collection(buf, indent, Braces::Curly, params.params, Newlines::Yes);
|
||||
|
||||
indent = fmt_spaces_with_outdent(buf, params.before_arrow, indent);
|
||||
buf.push_str("->");
|
||||
indent = fmt_spaces_with_outdent(buf, params.after_arrow, indent);
|
||||
}
|
||||
|
||||
fmt_exposes(buf, header.exposes, indent);
|
||||
}
|
||||
|
||||
|
@ -202,18 +217,19 @@ pub fn fmt_app_header<'a>(buf: &mut Buf, header: &'a AppHeader<'a>) {
|
|||
buf.indent(0);
|
||||
buf.push_str("app");
|
||||
|
||||
let indent = fmt_spaces_with_outdent(buf, header.before_provides, INDENT);
|
||||
let indent = fmt_spaces_with_outdent(buf, header.before_provides, 0);
|
||||
fmt_exposes(buf, header.provides, indent);
|
||||
|
||||
let indent = fmt_spaces_with_outdent(buf, header.before_packages, INDENT);
|
||||
let indent = fmt_spaces_with_outdent(buf, header.before_packages, indent);
|
||||
fmt_packages(buf, header.packages.value, indent);
|
||||
}
|
||||
|
||||
pub fn fmt_spaces_with_outdent(buf: &mut Buf, spaces: &[CommentOrNewline], indent: u16) -> u16 {
|
||||
if spaces.iter().all(|c| c.is_newline()) {
|
||||
buf.spaces(1);
|
||||
0
|
||||
indent
|
||||
} else {
|
||||
let indent = max(INDENT, indent + INDENT);
|
||||
fmt_default_spaces(buf, spaces, indent);
|
||||
indent
|
||||
}
|
||||
|
@ -223,10 +239,10 @@ pub fn fmt_package_header<'a>(buf: &mut Buf, header: &'a PackageHeader<'a>) {
|
|||
buf.indent(0);
|
||||
buf.push_str("package");
|
||||
|
||||
let indent = fmt_spaces_with_outdent(buf, header.before_exposes, INDENT);
|
||||
let indent = fmt_spaces_with_outdent(buf, header.before_exposes, 0);
|
||||
fmt_exposes(buf, header.exposes, indent);
|
||||
|
||||
let indent = fmt_spaces_with_outdent(buf, header.before_packages, INDENT);
|
||||
let indent = fmt_spaces_with_outdent(buf, header.before_packages, indent);
|
||||
fmt_packages(buf, header.packages.value, indent);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,14 +6,14 @@ use roc_parse::{
|
|||
AbilityImpls, AbilityMember, AssignedField, Collection, CommentOrNewline, Defs, Expr,
|
||||
Header, Implements, ImplementsAbilities, ImplementsAbility, ImplementsClause, ImportAlias,
|
||||
ImportAsKeyword, ImportExposingKeyword, ImportedModuleName, IngestedFileAnnotation,
|
||||
IngestedFileImport, Module, ModuleImport, Pattern, PatternAs, RecordBuilderField, Spaced,
|
||||
Spaces, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
|
||||
WhenBranch,
|
||||
IngestedFileImport, Module, ModuleImport, ModuleImportParams, Pattern, PatternAs,
|
||||
RecordBuilderField, Spaced, Spaces, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef,
|
||||
TypeHeader, ValueDef, WhenBranch,
|
||||
},
|
||||
header::{
|
||||
AppHeader, ExposedName, HostedHeader, ImportsEntry, KeywordItem, ModuleHeader, ModuleName,
|
||||
PackageEntry, PackageHeader, PackageName, PlatformHeader, PlatformRequires, ProvidesTo, To,
|
||||
TypedIdent,
|
||||
ModuleParams, PackageEntry, PackageHeader, PackageName, PlatformHeader, PlatformRequires,
|
||||
ProvidesTo, To, TypedIdent,
|
||||
},
|
||||
ident::{BadIdent, UppercaseIdent},
|
||||
};
|
||||
|
@ -285,7 +285,8 @@ impl<'a> RemoveSpaces<'a> for Module<'a> {
|
|||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
let header = match &self.header {
|
||||
Header::Module(header) => Header::Module(ModuleHeader {
|
||||
before_exposes: &[],
|
||||
after_keyword: &[],
|
||||
params: header.params.remove_spaces(arena),
|
||||
exposes: header.exposes.remove_spaces(arena),
|
||||
interface_imports: header.interface_imports.remove_spaces(arena),
|
||||
}),
|
||||
|
@ -330,6 +331,16 @@ impl<'a> RemoveSpaces<'a> for Module<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ModuleParams<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
ModuleParams {
|
||||
params: self.params.remove_spaces(arena),
|
||||
before_arrow: &[],
|
||||
after_arrow: &[],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Region {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
Region::zero()
|
||||
|
@ -589,12 +600,22 @@ impl<'a> RemoveSpaces<'a> for ModuleImport<'a> {
|
|||
ModuleImport {
|
||||
before_name: &[],
|
||||
name: self.name.remove_spaces(arena),
|
||||
params: self.params.remove_spaces(arena),
|
||||
alias: self.alias.remove_spaces(arena),
|
||||
exposed: self.exposed.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ModuleImportParams<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
ModuleImportParams {
|
||||
before: &[],
|
||||
params: self.params.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for IngestedFileImport<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
IngestedFileImport {
|
||||
|
|
|
@ -4943,6 +4943,67 @@ mod test_reporting {
|
|||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
weird_import_params_record,
|
||||
indoc!(
|
||||
r"
|
||||
import Menu { x = 4 }
|
||||
"
|
||||
),@r###"
|
||||
── RECORD PARSE PROBLEM in tmp/weird_import_params_record/Test.roc ─────────────
|
||||
|
||||
I am partway through parsing a record, but I got stuck here:
|
||||
|
||||
4│ import Menu { x = 4 }
|
||||
^
|
||||
|
||||
TODO provide more context.
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
record_builder_in_module_params,
|
||||
indoc!(
|
||||
r"
|
||||
import Menu {
|
||||
echo,
|
||||
name: <- applyName
|
||||
}
|
||||
"
|
||||
),@r###"
|
||||
── RECORD BUILDER IN MODULE PARAMS in ...ord_builder_in_module_params/Test.roc ─
|
||||
|
||||
I was partway through parsing module params, but I got stuck here:
|
||||
|
||||
4│ import Menu {
|
||||
5│ echo,
|
||||
6│ name: <- applyName
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This looks like a record builder field, but those are not allowed in
|
||||
module params.
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
record_update_in_module_params,
|
||||
indoc!(
|
||||
r"
|
||||
import Menu { myParams & echo: echoFn }
|
||||
"
|
||||
),@r###"
|
||||
── RECORD UPDATE IN MODULE PARAMS in ...ecord_update_in_module_params/Test.roc ─
|
||||
|
||||
I was partway through parsing module params, but I got stuck here:
|
||||
|
||||
4│ import Menu { myParams & echo: echoFn }
|
||||
^^^^^^^^
|
||||
|
||||
It looks like you're trying to update a record, but module params
|
||||
require a standalone record literal.
|
||||
"###
|
||||
);
|
||||
|
||||
test_report!(
|
||||
unfinished_import_as_or_exposing,
|
||||
indoc!(
|
||||
|
@ -4965,6 +5026,10 @@ mod test_reporting {
|
|||
Or the `exposing` keyword, like:
|
||||
|
||||
import svg.Path exposing [arc, rx]
|
||||
|
||||
Or module params, like:
|
||||
|
||||
import Menu { echo, read }
|
||||
"###
|
||||
);
|
||||
|
||||
|
@ -6210,6 +6275,31 @@ In roc, functions are always written as a lambda, like{}
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_params_with_missing_arrow() {
|
||||
report_header_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
module {echo, read} [menu]
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── WEIRD MODULE PARAMS in /code/proj/Main.roc ──────────────────────────────────
|
||||
|
||||
I am partway through parsing a module header, but I got stuck here:
|
||||
|
||||
1│ module {echo, read} [menu]
|
||||
^
|
||||
|
||||
I am expecting `->` next, like:
|
||||
|
||||
module { echo, read } -> [menu]
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn platform_requires_rigids() {
|
||||
report_header_problem_as(
|
||||
|
|
|
@ -246,6 +246,7 @@ impl<'a> Module<'a> {
|
|||
name,
|
||||
},
|
||||
},
|
||||
params: None,
|
||||
alias: None,
|
||||
exposed: new_exposed,
|
||||
})
|
||||
|
@ -784,43 +785,6 @@ pub enum ValueDef<'a> {
|
|||
}
|
||||
|
||||
impl<'a> ValueDef<'a> {
|
||||
pub fn expr(&self) -> Option<&'a Expr<'a>> {
|
||||
match self {
|
||||
ValueDef::Body(_, body) => Some(&body.value),
|
||||
|
||||
ValueDef::AnnotatedBody {
|
||||
ann_pattern: _,
|
||||
ann_type: _,
|
||||
comment: _,
|
||||
body_pattern: _,
|
||||
body_expr,
|
||||
} => Some(&body_expr.value),
|
||||
|
||||
ValueDef::Dbg {
|
||||
condition,
|
||||
preceding_comment: _,
|
||||
}
|
||||
| ValueDef::Expect {
|
||||
condition,
|
||||
preceding_comment: _,
|
||||
}
|
||||
| ValueDef::ExpectFx {
|
||||
condition,
|
||||
preceding_comment: _,
|
||||
} => Some(&condition.value),
|
||||
|
||||
ValueDef::Annotation(_, _)
|
||||
| ValueDef::ModuleImport(ModuleImport {
|
||||
before_name: _,
|
||||
name: _,
|
||||
alias: _,
|
||||
exposed: _,
|
||||
})
|
||||
| ValueDef::IngestedFileImport(_) => None,
|
||||
ValueDef::Stmt(loc_expr) => Some(&loc_expr.value),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace_expr(&mut self, new_expr: &'a Loc<Expr<'a>>) {
|
||||
match self {
|
||||
ValueDef::Body(_, expr) => *expr = new_expr,
|
||||
|
@ -1019,8 +983,47 @@ impl<'a, 'b> Iterator for RecursiveValueDefIter<'a, 'b> {
|
|||
let def = &self.current.value_defs[def_index.index()];
|
||||
let region = &self.current.regions[self.index];
|
||||
|
||||
if let Some(expr) = def.expr() {
|
||||
self.push_pending_from_expr(expr);
|
||||
match def {
|
||||
ValueDef::Body(_, body) => self.push_pending_from_expr(&body.value),
|
||||
|
||||
ValueDef::AnnotatedBody {
|
||||
ann_pattern: _,
|
||||
ann_type: _,
|
||||
comment: _,
|
||||
body_pattern: _,
|
||||
body_expr,
|
||||
} => self.push_pending_from_expr(&body_expr.value),
|
||||
|
||||
ValueDef::Dbg {
|
||||
condition,
|
||||
preceding_comment: _,
|
||||
}
|
||||
| ValueDef::Expect {
|
||||
condition,
|
||||
preceding_comment: _,
|
||||
}
|
||||
| ValueDef::ExpectFx {
|
||||
condition,
|
||||
preceding_comment: _,
|
||||
} => self.push_pending_from_expr(&condition.value),
|
||||
|
||||
ValueDef::ModuleImport(ModuleImport {
|
||||
before_name: _,
|
||||
name: _,
|
||||
alias: _,
|
||||
exposed: _,
|
||||
params,
|
||||
}) => {
|
||||
if let Some(ModuleImportParams { before: _, params }) = params {
|
||||
for loc_assigned_field in params.items {
|
||||
if let Some(expr) = loc_assigned_field.value.value() {
|
||||
self.push_pending_from_expr(&expr.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ValueDef::Stmt(loc_expr) => self.push_pending_from_expr(&loc_expr.value),
|
||||
ValueDef::Annotation(_, _) | ValueDef::IngestedFileImport(_) => {}
|
||||
}
|
||||
|
||||
self.index += 1;
|
||||
|
@ -1046,6 +1049,7 @@ impl<'a, 'b> Iterator for RecursiveValueDefIter<'a, 'b> {
|
|||
pub struct ModuleImport<'a> {
|
||||
pub before_name: &'a [CommentOrNewline<'a>],
|
||||
pub name: Loc<ImportedModuleName<'a>>,
|
||||
pub params: Option<ModuleImportParams<'a>>,
|
||||
pub alias: Option<header::KeywordItem<'a, ImportAsKeyword, Loc<ImportAlias<'a>>>>,
|
||||
pub exposed: Option<
|
||||
header::KeywordItem<
|
||||
|
@ -1056,6 +1060,12 @@ pub struct ModuleImport<'a> {
|
|||
>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct ModuleImportParams<'a> {
|
||||
pub before: &'a [CommentOrNewline<'a>],
|
||||
pub params: Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct IngestedFileImport<'a> {
|
||||
pub before_path: &'a [CommentOrNewline<'a>],
|
||||
|
@ -1537,6 +1547,20 @@ pub enum AssignedField<'a, Val> {
|
|||
Malformed(&'a str),
|
||||
}
|
||||
|
||||
impl<'a, Val> AssignedField<'a, Val> {
|
||||
pub fn value(&self) -> Option<&Loc<Val>> {
|
||||
let mut current = self;
|
||||
|
||||
loop {
|
||||
match current {
|
||||
Self::RequiredValue(_, _, val) | Self::OptionalValue(_, _, val) => break Some(val),
|
||||
Self::LabelOnly(_) | Self::Malformed(_) => break None,
|
||||
Self::SpaceBefore(next, _) | Self::SpaceAfter(next, _) => current = *next,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum RecordBuilderField<'a> {
|
||||
// A field with a value, e.g. `{ name: "blah" }`
|
||||
|
@ -2635,9 +2659,10 @@ impl<'a> Malformed for ValueDef<'a> {
|
|||
ValueDef::ModuleImport(ModuleImport {
|
||||
before_name: _,
|
||||
name: _,
|
||||
params,
|
||||
alias: _,
|
||||
exposed: _,
|
||||
}) => false,
|
||||
}) => params.is_malformed(),
|
||||
ValueDef::IngestedFileImport(IngestedFileImport {
|
||||
before_path: _,
|
||||
path,
|
||||
|
@ -2649,6 +2674,14 @@ impl<'a> Malformed for ValueDef<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> Malformed for ModuleImportParams<'a> {
|
||||
fn is_malformed(&self) -> bool {
|
||||
let Self { before: _, params } = self;
|
||||
|
||||
params.is_malformed()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Malformed for TypeAnnotation<'a> {
|
||||
fn is_malformed(&self) -> bool {
|
||||
match self {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use crate::ast::{
|
||||
is_expr_suffixed, AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces,
|
||||
Implements, ImplementsAbilities, ImportAlias, ImportAsKeyword, ImportExposingKeyword,
|
||||
ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport, Pattern,
|
||||
RecordBuilderField, Spaceable, Spaced, Spaces, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
|
||||
ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport,
|
||||
ModuleImportParams, Pattern, RecordBuilderField, Spaceable, Spaced, Spaces, TypeAnnotation,
|
||||
TypeDef, TypeHeader, ValueDef,
|
||||
};
|
||||
use crate::blankspace::{
|
||||
space0_after_e, space0_around_e_no_after_indent_check, space0_around_ee, space0_before_e,
|
||||
|
@ -15,8 +16,8 @@ use crate::module::module_name_help;
|
|||
use crate::parser::{
|
||||
self, backtrackable, byte, byte_indent, increment_min_indent, line_min_indent, optional,
|
||||
reset_min_indent, sep_by1, sep_by1_e, set_min_indent, specialize_err, specialize_err_ref, then,
|
||||
two_bytes, EClosure, EExpect, EExpr, EIf, EImport, EInParens, EList, ENumber, EPattern,
|
||||
ERecord, EString, EType, EWhen, Either, ParseResult, Parser,
|
||||
two_bytes, EClosure, EExpect, EExpr, EIf, EImport, EImportParams, EInParens, EList, ENumber,
|
||||
EPattern, ERecord, EString, EType, EWhen, Either, ParseResult, Parser,
|
||||
};
|
||||
use crate::pattern::{closure_param, loc_implements_parser};
|
||||
use crate::state::State;
|
||||
|
@ -979,6 +980,7 @@ fn import_body<'a>() -> impl Parser<'a, ValueDef<'a>, EImport<'a>> {
|
|||
record!(ModuleImport {
|
||||
before_name: space0_e(EImport::IndentStart),
|
||||
name: loc!(imported_module_name()),
|
||||
params: optional(specialize_err(EImport::Params, import_params())),
|
||||
alias: optional(import_as()),
|
||||
exposed: optional(import_exposing())
|
||||
}),
|
||||
|
@ -986,6 +988,37 @@ fn import_body<'a>() -> impl Parser<'a, ValueDef<'a>, EImport<'a>> {
|
|||
)
|
||||
}
|
||||
|
||||
fn import_params<'a>() -> impl Parser<'a, ModuleImportParams<'a>, EImportParams<'a>> {
|
||||
then(
|
||||
and!(
|
||||
backtrackable(space0_e(EImportParams::Indent)),
|
||||
specialize_err(EImportParams::Record, record_help())
|
||||
),
|
||||
|arena, state, _, (before, record): (_, RecordHelp<'a>)| {
|
||||
if let Some(update) = record.update {
|
||||
return Err((
|
||||
MadeProgress,
|
||||
EImportParams::RecordUpdateFound(update.region),
|
||||
));
|
||||
}
|
||||
|
||||
let params = record.fields.map_items_result(arena, |loc_field| {
|
||||
match loc_field.value.to_assigned_field(arena) {
|
||||
Ok(field) => Ok(Loc::at(loc_field.region, field)),
|
||||
Err(FoundApplyValue) => Err((
|
||||
MadeProgress,
|
||||
EImportParams::RecordApplyFound(loc_field.region),
|
||||
)),
|
||||
}
|
||||
})?;
|
||||
|
||||
let import_params = ModuleImportParams { before, params };
|
||||
|
||||
Ok((MadeProgress, import_params, state))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn imported_module_name<'a>() -> impl Parser<'a, ImportedModuleName<'a>, EImport<'a>> {
|
||||
record!(ImportedModuleName {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::ast::{
|
||||
Collection, CommentOrNewline, Malformed, Spaced, Spaces, StrLiteral, TypeAnnotation,
|
||||
Collection, CommentOrNewline, Malformed, Pattern, Spaced, Spaces, StrLiteral, TypeAnnotation,
|
||||
};
|
||||
use crate::blankspace::space0_e;
|
||||
use crate::expr::merge_spaces;
|
||||
|
@ -242,13 +242,21 @@ pub struct KeywordItem<'a, K, V> {
|
|||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ModuleHeader<'a> {
|
||||
pub before_exposes: &'a [CommentOrNewline<'a>],
|
||||
pub after_keyword: &'a [CommentOrNewline<'a>],
|
||||
pub params: Option<ModuleParams<'a>>,
|
||||
pub exposes: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
|
||||
|
||||
// Keeping this so we can format old interface header into module headers
|
||||
pub interface_imports: Option<KeywordItem<'a, ImportsKeyword, ImportsCollection<'a>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ModuleParams<'a> {
|
||||
pub params: Collection<'a, Loc<Pattern<'a>>>,
|
||||
pub before_arrow: &'a [CommentOrNewline<'a>],
|
||||
pub after_arrow: &'a [CommentOrNewline<'a>],
|
||||
}
|
||||
|
||||
pub type ImportsKeywordItem<'a> = KeywordItem<'a, ImportsKeyword, ImportsCollection<'a>>;
|
||||
pub type ImportsCollection<'a> = Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>;
|
||||
|
||||
|
|
|
@ -4,17 +4,18 @@ use crate::expr::merge_spaces;
|
|||
use crate::header::{
|
||||
package_entry, package_name, AppHeader, ExposedName, ExposesKeyword, GeneratesKeyword,
|
||||
HostedHeader, ImportsCollection, ImportsEntry, ImportsKeyword, ImportsKeywordItem, Keyword,
|
||||
KeywordItem, ModuleHeader, ModuleName, PackageEntry, PackageHeader, PackagesKeyword,
|
||||
PlatformHeader, PlatformRequires, ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword,
|
||||
TypedIdent, WithKeyword,
|
||||
KeywordItem, ModuleHeader, ModuleName, ModuleParams, PackageEntry, PackageHeader,
|
||||
PackagesKeyword, PlatformHeader, PlatformRequires, ProvidesKeyword, ProvidesTo,
|
||||
RequiresKeyword, To, ToKeyword, TypedIdent, WithKeyword,
|
||||
};
|
||||
use crate::ident::{self, lowercase_ident, unqualified_ident, uppercase, UppercaseIdent};
|
||||
use crate::parser::Progress::{self, *};
|
||||
use crate::parser::{
|
||||
backtrackable, byte, increment_min_indent, optional, reset_min_indent, specialize_err,
|
||||
two_bytes, EExposes, EGenerates, EGeneratesWith, EHeader, EImports, EPackages, EProvides,
|
||||
ERequires, ETypedIdent, Parser, SourceError, SpaceProblem, SyntaxError,
|
||||
two_bytes, EExposes, EGenerates, EGeneratesWith, EHeader, EImports, EPackages, EParams,
|
||||
EProvides, ERequires, ETypedIdent, Parser, SourceError, SpaceProblem, SyntaxError,
|
||||
};
|
||||
use crate::pattern::record_pattern_fields;
|
||||
use crate::state::State;
|
||||
use crate::string_literal::{self, parse_str_literal};
|
||||
use crate::type_annotation;
|
||||
|
@ -111,13 +112,25 @@ pub fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> {
|
|||
#[inline(always)]
|
||||
fn module_header<'a>() -> impl Parser<'a, ModuleHeader<'a>, EHeader<'a>> {
|
||||
record!(ModuleHeader {
|
||||
before_exposes: space0_e(EHeader::IndentStart),
|
||||
after_keyword: space0_e(EHeader::IndentStart),
|
||||
params: optional(specialize_err(EHeader::Params, module_params())),
|
||||
exposes: specialize_err(EHeader::Exposes, exposes_list()),
|
||||
interface_imports: succeed!(None)
|
||||
})
|
||||
.trace("module_header")
|
||||
}
|
||||
|
||||
fn module_params<'a>() -> impl Parser<'a, ModuleParams<'a>, EParams<'a>> {
|
||||
record!(ModuleParams {
|
||||
params: specialize_err(EParams::Pattern, record_pattern_fields()),
|
||||
before_arrow: skip_second!(
|
||||
space0_e(EParams::BeforeArrow),
|
||||
loc!(two_bytes(b'-', b'>', EParams::Arrow))
|
||||
),
|
||||
after_arrow: space0_e(EParams::AfterArrow),
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! merge_n_spaces {
|
||||
($arena:expr, $($slice:expr),*) => {
|
||||
{
|
||||
|
@ -131,7 +144,7 @@ macro_rules! merge_n_spaces {
|
|||
/// Parse old interface headers so we can format them into module headers
|
||||
#[inline(always)]
|
||||
fn interface_header<'a>() -> impl Parser<'a, ModuleHeader<'a>, EHeader<'a>> {
|
||||
let before_exposes = map_with_arena!(
|
||||
let after_keyword = map_with_arena!(
|
||||
and!(
|
||||
skip_second!(
|
||||
space0_e(EHeader::IndentStart),
|
||||
|
@ -146,7 +159,8 @@ fn interface_header<'a>() -> impl Parser<'a, ModuleHeader<'a>, EHeader<'a>> {
|
|||
);
|
||||
|
||||
record!(ModuleHeader {
|
||||
before_exposes: before_exposes,
|
||||
after_keyword: after_keyword,
|
||||
params: succeed!(None),
|
||||
exposes: specialize_err(EHeader::Exposes, exposes_list()).trace("exposes_list"),
|
||||
interface_imports: map!(
|
||||
specialize_err(EHeader::Imports, imports()),
|
||||
|
|
|
@ -88,7 +88,9 @@ impl_space_problem! {
|
|||
EHeader<'a>,
|
||||
EIf<'a>,
|
||||
EImport<'a>,
|
||||
EParams<'a>,
|
||||
EImports,
|
||||
EImportParams<'a>,
|
||||
EInParens<'a>,
|
||||
EClosure<'a>,
|
||||
EList<'a>,
|
||||
|
@ -115,6 +117,7 @@ impl_space_problem! {
|
|||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EHeader<'a> {
|
||||
Provides(EProvides<'a>, Position),
|
||||
Params(EParams<'a>, Position),
|
||||
Exposes(EExposes, Position),
|
||||
Imports(EImports, Position),
|
||||
Requires(ERequires<'a>, Position),
|
||||
|
@ -149,6 +152,15 @@ pub enum EProvides<'a> {
|
|||
Space(BadInputError, Position),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EParams<'a> {
|
||||
Pattern(PRecord<'a>, Position),
|
||||
BeforeArrow(Position),
|
||||
Arrow(Position),
|
||||
AfterArrow(Position),
|
||||
Space(BadInputError, Position),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum EExposes {
|
||||
Exposes(Position),
|
||||
|
@ -530,6 +542,7 @@ pub enum EImport<'a> {
|
|||
PackageShorthand(Position),
|
||||
PackageShorthandDot(Position),
|
||||
ModuleName(Position),
|
||||
Params(EImportParams<'a>, Position),
|
||||
IndentAs(Position),
|
||||
As(Position),
|
||||
IndentAlias(Position),
|
||||
|
@ -552,6 +565,15 @@ pub enum EImport<'a> {
|
|||
EndNewline(Position),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EImportParams<'a> {
|
||||
Indent(Position),
|
||||
Record(ERecord<'a>, Position),
|
||||
RecordUpdateFound(Region),
|
||||
RecordApplyFound(Region),
|
||||
Space(BadInputError, Position),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EPattern<'a> {
|
||||
Record(PRecord<'a>, Position),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::ast::{Implements, Pattern, PatternAs, Spaceable};
|
||||
use crate::ast::{Collection, Implements, Pattern, PatternAs, Spaceable};
|
||||
use crate::blankspace::{space0_e, spaces, spaces_before};
|
||||
use crate::ident::{lowercase_ident, parse_ident, Accessor, Ident};
|
||||
use crate::keyword;
|
||||
|
@ -468,15 +468,17 @@ fn lowercase_ident_pattern<'a>() -> impl Parser<'a, &'a str, EPattern<'a>> {
|
|||
|
||||
#[inline(always)]
|
||||
fn record_pattern_help<'a>() -> impl Parser<'a, Pattern<'a>, PRecord<'a>> {
|
||||
map!(
|
||||
collection_trailing_sep_e!(
|
||||
byte(b'{', PRecord::Open),
|
||||
record_pattern_field(),
|
||||
byte(b',', PRecord::End),
|
||||
byte(b'}', PRecord::End),
|
||||
Pattern::SpaceBefore
|
||||
),
|
||||
Pattern::RecordDestructure
|
||||
map!(record_pattern_fields(), Pattern::RecordDestructure)
|
||||
}
|
||||
|
||||
pub fn record_pattern_fields<'a>() -> impl Parser<'a, Collection<'a, Loc<Pattern<'a>>>, PRecord<'a>>
|
||||
{
|
||||
collection_trailing_sep_e!(
|
||||
byte(b'{', PRecord::Open),
|
||||
record_pattern_field(),
|
||||
byte(b',', PRecord::End),
|
||||
byte(b'}', PRecord::End),
|
||||
Pattern::SpaceBefore
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Header(Params(Arrow(@22), @7))
|
|
@ -0,0 +1 @@
|
|||
module { echo, name } [menu]
|
|
@ -0,0 +1 @@
|
|||
Header(Params(Pattern(End(@20), @7), @7))
|
|
@ -0,0 +1 @@
|
|||
module { echo, name -> [menu]
|
|
@ -2,7 +2,8 @@ Module {
|
|||
comments: [],
|
||||
header: Module(
|
||||
ModuleHeader {
|
||||
before_exposes: [],
|
||||
after_keyword: [],
|
||||
params: None,
|
||||
exposes: [],
|
||||
interface_imports: None,
|
||||
},
|
||||
|
|
|
@ -23,6 +23,7 @@ Defs {
|
|||
"Json",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: None,
|
||||
exposed: None,
|
||||
},
|
||||
|
|
|
@ -41,6 +41,7 @@ Defs {
|
|||
"Decode",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: None,
|
||||
exposed: None,
|
||||
},
|
||||
|
@ -56,6 +57,7 @@ Defs {
|
|||
"Decode",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: Some(
|
||||
KeywordItem {
|
||||
keyword: Spaces {
|
||||
|
@ -82,6 +84,7 @@ Defs {
|
|||
"Decode",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: None,
|
||||
exposed: Some(
|
||||
KeywordItem {
|
||||
|
@ -118,6 +121,7 @@ Defs {
|
|||
"Decode",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: Some(
|
||||
KeywordItem {
|
||||
keyword: Spaces {
|
||||
|
|
|
@ -29,6 +29,7 @@ Defs {
|
|||
"JsonEncode",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: Some(
|
||||
KeywordItem {
|
||||
keyword: Spaces {
|
||||
|
@ -53,6 +54,7 @@ Defs {
|
|||
"Bytes.Decode",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: Some(
|
||||
KeywordItem {
|
||||
keyword: Spaces {
|
||||
|
|
|
@ -104,6 +104,7 @@ Defs {
|
|||
"Json",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: Some(
|
||||
KeywordItem {
|
||||
keyword: Spaces {
|
||||
|
@ -141,6 +142,7 @@ Defs {
|
|||
"Json",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: Some(
|
||||
KeywordItem {
|
||||
keyword: Spaces {
|
||||
|
@ -183,6 +185,7 @@ Defs {
|
|||
"Json",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: Some(
|
||||
KeywordItem {
|
||||
keyword: Spaces {
|
||||
|
@ -212,6 +215,7 @@ Defs {
|
|||
"Json",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: Some(
|
||||
KeywordItem {
|
||||
keyword: Spaces {
|
||||
|
@ -254,6 +258,7 @@ Defs {
|
|||
"Json",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: Some(
|
||||
KeywordItem {
|
||||
keyword: Spaces {
|
||||
|
@ -304,6 +309,7 @@ Defs {
|
|||
"Json",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: None,
|
||||
exposed: Some(
|
||||
KeywordItem {
|
||||
|
@ -343,6 +349,7 @@ Defs {
|
|||
"Json",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: Some(
|
||||
KeywordItem {
|
||||
keyword: Spaces {
|
||||
|
@ -385,6 +392,7 @@ Defs {
|
|||
"Json",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: None,
|
||||
exposed: Some(
|
||||
KeywordItem {
|
||||
|
@ -416,6 +424,7 @@ Defs {
|
|||
"Json",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: Some(
|
||||
KeywordItem {
|
||||
keyword: Spaces {
|
||||
|
@ -458,6 +467,7 @@ Defs {
|
|||
"Json",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: Some(
|
||||
KeywordItem {
|
||||
keyword: Spaces {
|
||||
|
@ -513,6 +523,7 @@ Defs {
|
|||
"Json",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: Some(
|
||||
KeywordItem {
|
||||
keyword: Spaces {
|
||||
|
@ -583,6 +594,7 @@ Defs {
|
|||
"A",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: None,
|
||||
exposed: None,
|
||||
},
|
||||
|
@ -596,6 +608,7 @@ Defs {
|
|||
"B",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: None,
|
||||
exposed: None,
|
||||
},
|
||||
|
|
|
@ -34,6 +34,7 @@ Defs {
|
|||
"Json",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: None,
|
||||
exposed: Some(
|
||||
KeywordItem {
|
||||
|
@ -63,6 +64,7 @@ Defs {
|
|||
"Json",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: None,
|
||||
exposed: Some(
|
||||
KeywordItem {
|
||||
|
@ -97,6 +99,7 @@ Defs {
|
|||
"Json",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: None,
|
||||
exposed: Some(
|
||||
KeywordItem {
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import pf.Menu { echo, read }
|
||||
import Menu {
|
||||
echo,
|
||||
read,
|
||||
}
|
||||
import Menu {
|
||||
echo,
|
||||
read,
|
||||
} as M
|
||||
import Menu { echo, read } as M exposing [
|
||||
main,
|
||||
credits,
|
||||
]
|
|
@ -0,0 +1,193 @@
|
|||
Defs {
|
||||
tags: [
|
||||
Index(2147483648),
|
||||
Index(2147483649),
|
||||
Index(2147483650),
|
||||
Index(2147483651),
|
||||
],
|
||||
regions: [
|
||||
@0-29,
|
||||
@30-60,
|
||||
@61-96,
|
||||
@97-157,
|
||||
],
|
||||
space_before: [
|
||||
Slice(start = 0, length = 0),
|
||||
Slice(start = 1, length = 0),
|
||||
Slice(start = 2, length = 0),
|
||||
Slice(start = 3, length = 0),
|
||||
],
|
||||
space_after: [
|
||||
Slice(start = 0, length = 1),
|
||||
Slice(start = 1, length = 1),
|
||||
Slice(start = 2, length = 1),
|
||||
Slice(start = 3, length = 0),
|
||||
],
|
||||
spaces: [
|
||||
Newline,
|
||||
Newline,
|
||||
Newline,
|
||||
],
|
||||
type_defs: [],
|
||||
value_defs: [
|
||||
ModuleImport(
|
||||
ModuleImport {
|
||||
before_name: [],
|
||||
name: @7-14 ImportedModuleName {
|
||||
package: Some(
|
||||
"pf",
|
||||
),
|
||||
name: ModuleName(
|
||||
"Menu",
|
||||
),
|
||||
},
|
||||
params: Some(
|
||||
ModuleImportParams {
|
||||
before: [],
|
||||
params: [
|
||||
@17-21 LabelOnly(
|
||||
@17-21 "echo",
|
||||
),
|
||||
@23-28 LabelOnly(
|
||||
@23-27 "read",
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
alias: None,
|
||||
exposed: None,
|
||||
},
|
||||
),
|
||||
ModuleImport(
|
||||
ModuleImport {
|
||||
before_name: [],
|
||||
name: @37-41 ImportedModuleName {
|
||||
package: None,
|
||||
name: ModuleName(
|
||||
"Menu",
|
||||
),
|
||||
},
|
||||
params: Some(
|
||||
ModuleImportParams {
|
||||
before: [],
|
||||
params: [
|
||||
@44-48 LabelOnly(
|
||||
@44-48 "echo",
|
||||
),
|
||||
@54-59 SpaceBefore(
|
||||
LabelOnly(
|
||||
@54-58 "read",
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
alias: None,
|
||||
exposed: None,
|
||||
},
|
||||
),
|
||||
ModuleImport(
|
||||
ModuleImport {
|
||||
before_name: [],
|
||||
name: @68-72 ImportedModuleName {
|
||||
package: None,
|
||||
name: ModuleName(
|
||||
"Menu",
|
||||
),
|
||||
},
|
||||
params: Some(
|
||||
ModuleImportParams {
|
||||
before: [],
|
||||
params: [
|
||||
@75-79 LabelOnly(
|
||||
@75-79 "echo",
|
||||
),
|
||||
@85-90 SpaceBefore(
|
||||
LabelOnly(
|
||||
@85-89 "read",
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
alias: Some(
|
||||
KeywordItem {
|
||||
keyword: Spaces {
|
||||
before: [],
|
||||
item: ImportAsKeyword,
|
||||
after: [],
|
||||
},
|
||||
item: @95-96 ImportAlias(
|
||||
"M",
|
||||
),
|
||||
},
|
||||
),
|
||||
exposed: None,
|
||||
},
|
||||
),
|
||||
ModuleImport(
|
||||
ModuleImport {
|
||||
before_name: [],
|
||||
name: @104-108 ImportedModuleName {
|
||||
package: None,
|
||||
name: ModuleName(
|
||||
"Menu",
|
||||
),
|
||||
},
|
||||
params: Some(
|
||||
ModuleImportParams {
|
||||
before: [],
|
||||
params: [
|
||||
@111-115 LabelOnly(
|
||||
@111-115 "echo",
|
||||
),
|
||||
@117-122 LabelOnly(
|
||||
@117-121 "read",
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
alias: Some(
|
||||
KeywordItem {
|
||||
keyword: Spaces {
|
||||
before: [],
|
||||
item: ImportAsKeyword,
|
||||
after: [],
|
||||
},
|
||||
item: @127-128 ImportAlias(
|
||||
"M",
|
||||
),
|
||||
},
|
||||
),
|
||||
exposed: Some(
|
||||
KeywordItem {
|
||||
keyword: Spaces {
|
||||
before: [],
|
||||
item: ImportExposingKeyword,
|
||||
after: [],
|
||||
},
|
||||
item: [
|
||||
@140-144 ExposedName(
|
||||
"main",
|
||||
),
|
||||
@148-155 SpaceBefore(
|
||||
ExposedName(
|
||||
"credits",
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import pf.Menu { echo, read }
|
||||
import Menu { echo,
|
||||
read }
|
||||
import Menu { echo,
|
||||
read } as M
|
||||
import Menu { echo, read } as M exposing [ main,
|
||||
credits ]
|
|
@ -32,6 +32,7 @@ Defs(
|
|||
"Json",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: None,
|
||||
exposed: Some(
|
||||
KeywordItem {
|
||||
|
@ -58,6 +59,7 @@ Defs(
|
|||
"Json.Encode",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: Some(
|
||||
KeywordItem {
|
||||
keyword: Spaces {
|
||||
|
|
|
@ -2,7 +2,8 @@ Module {
|
|||
comments: [],
|
||||
header: Module(
|
||||
ModuleHeader {
|
||||
before_exposes: [],
|
||||
after_keyword: [],
|
||||
params: None,
|
||||
exposes: [
|
||||
@8-9 ExposedName(
|
||||
"a",
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
module
|
||||
{
|
||||
echo,
|
||||
# comment before param
|
||||
read,
|
||||
} -> [
|
||||
mainMenu,
|
||||
credits,
|
||||
]
|
|
@ -0,0 +1,44 @@
|
|||
Module {
|
||||
comments: [],
|
||||
header: Module(
|
||||
ModuleHeader {
|
||||
after_keyword: [],
|
||||
params: Some(
|
||||
ModuleParams {
|
||||
params: [
|
||||
@8-12 Identifier {
|
||||
ident: "echo",
|
||||
},
|
||||
@39-43 SpaceBefore(
|
||||
Identifier {
|
||||
ident: "read",
|
||||
},
|
||||
[
|
||||
Newline,
|
||||
LineComment(
|
||||
" comment before param",
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
before_arrow: [],
|
||||
after_arrow: [],
|
||||
},
|
||||
),
|
||||
exposes: [
|
||||
@50-58 ExposedName(
|
||||
"mainMenu",
|
||||
),
|
||||
@64-71 SpaceBefore(
|
||||
ExposedName(
|
||||
"credits",
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
],
|
||||
interface_imports: None,
|
||||
},
|
||||
),
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
module {echo,
|
||||
# comment before param
|
||||
read } -> [mainMenu,
|
||||
credits ]
|
|
@ -2,7 +2,8 @@ Module {
|
|||
comments: [],
|
||||
header: Module(
|
||||
ModuleHeader {
|
||||
before_exposes: [],
|
||||
after_keyword: [],
|
||||
params: None,
|
||||
exposes: [],
|
||||
interface_imports: None,
|
||||
},
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
module { x, y ? 0 } -> [menu]
|
|
@ -0,0 +1,31 @@
|
|||
Module {
|
||||
comments: [],
|
||||
header: Module(
|
||||
ModuleHeader {
|
||||
after_keyword: [],
|
||||
params: Some(
|
||||
ModuleParams {
|
||||
params: [
|
||||
@9-10 Identifier {
|
||||
ident: "x",
|
||||
},
|
||||
@12-18 OptionalField(
|
||||
"y",
|
||||
@17-18 Num(
|
||||
"0",
|
||||
),
|
||||
),
|
||||
],
|
||||
before_arrow: [],
|
||||
after_arrow: [],
|
||||
},
|
||||
),
|
||||
exposes: [
|
||||
@25-29 ExposedName(
|
||||
"menu",
|
||||
),
|
||||
],
|
||||
interface_imports: None,
|
||||
},
|
||||
),
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
module { x, y ? 0 } -> [menu]
|
|
@ -0,0 +1 @@
|
|||
module { echo, read } -> [menu]
|
|
@ -0,0 +1,28 @@
|
|||
Module {
|
||||
comments: [],
|
||||
header: Module(
|
||||
ModuleHeader {
|
||||
after_keyword: [],
|
||||
params: Some(
|
||||
ModuleParams {
|
||||
params: [
|
||||
@8-12 Identifier {
|
||||
ident: "echo",
|
||||
},
|
||||
@14-18 Identifier {
|
||||
ident: "read",
|
||||
},
|
||||
],
|
||||
before_arrow: [],
|
||||
after_arrow: [],
|
||||
},
|
||||
),
|
||||
exposes: [
|
||||
@26-30 ExposedName(
|
||||
"menu",
|
||||
),
|
||||
],
|
||||
interface_imports: None,
|
||||
},
|
||||
),
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
module {echo, read } -> [menu]
|
|
@ -0,0 +1,4 @@
|
|||
module { echo, read } -> [
|
||||
mainMenu,
|
||||
credits,
|
||||
]
|
|
@ -0,0 +1,36 @@
|
|||
Module {
|
||||
comments: [],
|
||||
header: Module(
|
||||
ModuleHeader {
|
||||
after_keyword: [],
|
||||
params: Some(
|
||||
ModuleParams {
|
||||
params: [
|
||||
@8-12 Identifier {
|
||||
ident: "echo",
|
||||
},
|
||||
@14-18 Identifier {
|
||||
ident: "read",
|
||||
},
|
||||
],
|
||||
before_arrow: [],
|
||||
after_arrow: [],
|
||||
},
|
||||
),
|
||||
exposes: [
|
||||
@25-33 ExposedName(
|
||||
"mainMenu",
|
||||
),
|
||||
@39-46 SpaceBefore(
|
||||
ExposedName(
|
||||
"credits",
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
],
|
||||
interface_imports: None,
|
||||
},
|
||||
),
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
module {echo, read } -> [mainMenu,
|
||||
credits ]
|
|
@ -81,6 +81,7 @@ Full {
|
|||
"Stdout",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: None,
|
||||
exposed: None,
|
||||
},
|
||||
|
|
|
@ -2,7 +2,8 @@ Module {
|
|||
comments: [],
|
||||
header: Module(
|
||||
ModuleHeader {
|
||||
before_exposes: [],
|
||||
after_keyword: [],
|
||||
params: None,
|
||||
exposes: [
|
||||
@23-26 ExposedName(
|
||||
"Foo",
|
||||
|
|
|
@ -71,6 +71,7 @@ Full {
|
|||
"Stdout",
|
||||
),
|
||||
},
|
||||
params: None,
|
||||
alias: None,
|
||||
exposed: None,
|
||||
},
|
||||
|
|
|
@ -212,6 +212,8 @@ mod test_snapshots {
|
|||
fail/list_pattern_not_terminated.expr,
|
||||
fail/list_pattern_weird_rest_pattern.expr,
|
||||
fail/list_without_end.expr,
|
||||
fail/module_params_with_missing_arrow.header,
|
||||
fail/module_with_unfinished_params.header,
|
||||
fail/multi_no_end.expr,
|
||||
fail/pattern_binds_keyword.expr,
|
||||
fail/pattern_in_parens_end.expr,
|
||||
|
@ -330,6 +332,7 @@ mod test_snapshots {
|
|||
pass/import_with_alias.moduledefs,
|
||||
pass/import_with_comments.moduledefs,
|
||||
pass/import_with_exposed.moduledefs,
|
||||
pass/import_with_params.moduledefs,
|
||||
pass/ingested_file.moduledefs,
|
||||
pass/inline_import.expr,
|
||||
pass/inline_ingested_file.expr,
|
||||
|
@ -350,7 +353,11 @@ mod test_snapshots {
|
|||
pass/mixed_docs.expr,
|
||||
pass/module_def_newline.moduledefs,
|
||||
pass/module_multiline_exposes.header,
|
||||
pass/module_with_multiline_params_and_exposes.header,
|
||||
pass/module_with_newline.header,
|
||||
pass/module_with_optional_param.header,
|
||||
pass/module_with_params.header,
|
||||
pass/module_with_params_and_multiline_exposes.header,
|
||||
pass/multi_backpassing.expr,
|
||||
pass/multi_backpassing_in_def.moduledefs,
|
||||
pass/multi_backpassing_with_apply.expr,
|
||||
|
|
|
@ -11,8 +11,9 @@ use roc_parse::{
|
|||
WhenBranch,
|
||||
},
|
||||
header::{
|
||||
AppHeader, ExposedName, HostedHeader, ImportsEntry, ModuleHeader, ModuleName, PackageEntry,
|
||||
PackageHeader, PackageName, PlatformHeader, PlatformRequires, ProvidesTo, To, TypedIdent,
|
||||
AppHeader, ExposedName, HostedHeader, ImportsEntry, ModuleHeader, ModuleName, ModuleParams,
|
||||
PackageEntry, PackageHeader, PackageName, PlatformHeader, PlatformRequires, ProvidesTo, To,
|
||||
TypedIdent,
|
||||
},
|
||||
ident::{Accessor, UppercaseIdent},
|
||||
};
|
||||
|
@ -213,12 +214,41 @@ impl IterTokens for Header<'_> {
|
|||
impl IterTokens for ModuleHeader<'_> {
|
||||
fn iter_tokens<'a>(&self, arena: &'a Bump) -> BumpVec<'a, Loc<Token>> {
|
||||
let Self {
|
||||
before_exposes: _,
|
||||
after_keyword: _,
|
||||
params,
|
||||
exposes,
|
||||
interface_imports: _,
|
||||
} = self;
|
||||
|
||||
exposes.iter_tokens(arena)
|
||||
params
|
||||
.iter_tokens(arena)
|
||||
.into_iter()
|
||||
.chain(exposes.iter_tokens(arena))
|
||||
.collect_in(arena)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IterTokens for Option<T>
|
||||
where
|
||||
T: IterTokens,
|
||||
{
|
||||
fn iter_tokens<'a>(&self, arena: &'a Bump) -> BumpVec<'a, Loc<Token>> {
|
||||
match self {
|
||||
Some(params) => params.iter_tokens(arena),
|
||||
None => bumpvec![in arena;],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IterTokens for ModuleParams<'_> {
|
||||
fn iter_tokens<'a>(&self, arena: &'a Bump) -> BumpVec<'a, Loc<Token>> {
|
||||
let Self {
|
||||
params,
|
||||
before_arrow: _,
|
||||
after_arrow: _,
|
||||
} = self;
|
||||
|
||||
params.iter_tokens(arena)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -540,22 +540,8 @@ fn to_expr_report<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
EExpr::Record(_erecord, pos) => {
|
||||
let surroundings = Region::new(start, *pos);
|
||||
let region = LineColumnRegion::from_pos(lines.convert_pos(*pos));
|
||||
|
||||
let doc = alloc.stack([
|
||||
alloc.reflow(r"I am partway through parsing a record, but I got stuck here:"),
|
||||
alloc.region_with_subregion(lines.convert_region(surroundings), region),
|
||||
alloc.concat([alloc.reflow("TODO provide more context.")]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "RECORD PARSE PROBLEM".to_string(),
|
||||
severity: Severity::RuntimeError,
|
||||
}
|
||||
EExpr::Record(erecord, pos) => {
|
||||
to_record_report(alloc, lines, filename, erecord, *pos, start)
|
||||
}
|
||||
|
||||
EExpr::OptionalValueInRecordBuilder(region) => {
|
||||
|
@ -711,6 +697,31 @@ fn to_expr_report<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
fn to_record_report<'a>(
|
||||
alloc: &'a RocDocAllocator<'a>,
|
||||
lines: &LineInfo,
|
||||
filename: PathBuf,
|
||||
_parse_problem: &roc_parse::parser::ERecord<'a>,
|
||||
pos: Position,
|
||||
start: Position,
|
||||
) -> Report<'a> {
|
||||
let surroundings = Region::new(start, pos);
|
||||
let region = LineColumnRegion::from_pos(lines.convert_pos(pos));
|
||||
|
||||
let doc = alloc.stack([
|
||||
alloc.reflow(r"I am partway through parsing a record, but I got stuck here:"),
|
||||
alloc.region_with_subregion(lines.convert_region(surroundings), region),
|
||||
alloc.concat([alloc.reflow("TODO provide more context.")]),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "RECORD PARSE PROBLEM".to_string(),
|
||||
severity: Severity::RuntimeError,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_lambda_report<'a>(
|
||||
alloc: &'a RocDocAllocator<'a>,
|
||||
lines: &LineInfo,
|
||||
|
@ -1450,6 +1461,7 @@ fn to_import_report<'a>(
|
|||
start: Position,
|
||||
) -> Report<'a> {
|
||||
use roc_parse::parser::EImport::*;
|
||||
use roc_parse::parser::EImportParams;
|
||||
|
||||
match parse_problem {
|
||||
Import(_pos) => unreachable!("another branch would be taken"),
|
||||
|
@ -1475,32 +1487,76 @@ fn to_import_report<'a>(
|
|||
.indent(4),
|
||||
]),
|
||||
),
|
||||
IndentAs(pos) | As(pos) | IndentExposing(pos) | Exposing(pos) | EndNewline(pos) => {
|
||||
to_unfinished_import_report(
|
||||
alloc,
|
||||
lines,
|
||||
filename,
|
||||
*pos,
|
||||
start,
|
||||
alloc.stack([
|
||||
alloc.concat([
|
||||
alloc.reflow("I was expecting to see the "),
|
||||
alloc.keyword("as"),
|
||||
alloc.reflow(" keyword, like:"),
|
||||
]),
|
||||
alloc
|
||||
.parser_suggestion("import svg.Path as SvgPath")
|
||||
.indent(4),
|
||||
alloc.concat([
|
||||
alloc.reflow("Or the "),
|
||||
alloc.keyword("exposing"),
|
||||
alloc.reflow(" keyword, like:"),
|
||||
]),
|
||||
alloc
|
||||
.parser_suggestion("import svg.Path exposing [arc, rx]")
|
||||
.indent(4),
|
||||
Params(EImportParams::Indent(pos), _)
|
||||
| IndentAs(pos)
|
||||
| As(pos)
|
||||
| IndentExposing(pos)
|
||||
| Exposing(pos)
|
||||
| EndNewline(pos) => to_unfinished_import_report(
|
||||
alloc,
|
||||
lines,
|
||||
filename,
|
||||
*pos,
|
||||
start,
|
||||
alloc.stack([
|
||||
alloc.concat([
|
||||
alloc.reflow("I was expecting to see the "),
|
||||
alloc.keyword("as"),
|
||||
alloc.reflow(" keyword, like:"),
|
||||
]),
|
||||
)
|
||||
alloc
|
||||
.parser_suggestion("import svg.Path as SvgPath")
|
||||
.indent(4),
|
||||
alloc.concat([
|
||||
alloc.reflow("Or the "),
|
||||
alloc.keyword("exposing"),
|
||||
alloc.reflow(" keyword, like:"),
|
||||
]),
|
||||
alloc
|
||||
.parser_suggestion("import svg.Path exposing [arc, rx]")
|
||||
.indent(4),
|
||||
alloc.reflow("Or module params, like:"),
|
||||
alloc
|
||||
.parser_suggestion("import Menu { echo, read }")
|
||||
.indent(4),
|
||||
]),
|
||||
),
|
||||
Params(EImportParams::Record(problem, pos), _) => {
|
||||
to_record_report(alloc, lines, filename, problem, *pos, start)
|
||||
}
|
||||
Params(EImportParams::RecordApplyFound(region), _) => {
|
||||
let surroundings = Region::new(start, region.end());
|
||||
let region = lines.convert_region(*region);
|
||||
|
||||
let doc = alloc.stack([
|
||||
alloc.reflow("I was partway through parsing module params, but I got stuck here:"),
|
||||
alloc.region_with_subregion(lines.convert_region(surroundings), region),
|
||||
alloc.reflow("This looks like a record builder field, but those are not allowed in module params."),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "RECORD BUILDER IN MODULE PARAMS".to_string(),
|
||||
severity: Severity::RuntimeError,
|
||||
}
|
||||
}
|
||||
Params(EImportParams::RecordUpdateFound(region), _) => {
|
||||
let surroundings = Region::new(start, region.end());
|
||||
let region = lines.convert_region(*region);
|
||||
|
||||
let doc = alloc.stack([
|
||||
alloc.reflow("I was partway through parsing module params, but I got stuck here:"),
|
||||
alloc.region_with_subregion(lines.convert_region(surroundings), region),
|
||||
alloc.reflow("It looks like you're trying to update a record, but module params require a standalone record literal."),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "RECORD UPDATE IN MODULE PARAMS".to_string(),
|
||||
severity: Severity::RuntimeError,
|
||||
}
|
||||
}
|
||||
IndentAlias(pos) | Alias(pos) => to_unfinished_import_report(
|
||||
alloc,
|
||||
|
@ -1593,7 +1649,9 @@ fn to_import_report<'a>(
|
|||
.indent(4),
|
||||
]),
|
||||
),
|
||||
Space(problem, pos) => to_space_report(alloc, lines, filename, problem, *pos),
|
||||
Space(problem, pos) | Params(EImportParams::Space(problem, pos), _) => {
|
||||
to_space_report(alloc, lines, filename, problem, *pos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3392,6 +3450,8 @@ fn to_header_report<'a>(
|
|||
to_provides_report(alloc, lines, filename, provides, *pos)
|
||||
}
|
||||
|
||||
EHeader::Params(params, pos) => to_params_report(alloc, lines, filename, params, *pos),
|
||||
|
||||
EHeader::Exposes(exposes, pos) => to_exposes_report(alloc, lines, filename, exposes, *pos),
|
||||
|
||||
EHeader::Imports(imports, pos) => to_imports_report(alloc, lines, filename, imports, *pos),
|
||||
|
@ -3786,6 +3846,48 @@ fn to_provides_report<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
fn to_params_report<'a>(
|
||||
alloc: &'a RocDocAllocator<'a>,
|
||||
lines: &LineInfo,
|
||||
filename: PathBuf,
|
||||
parse_problem: &roc_parse::parser::EParams<'a>,
|
||||
start: Position,
|
||||
) -> Report<'a> {
|
||||
use roc_parse::parser::EParams;
|
||||
|
||||
match parse_problem {
|
||||
EParams::Pattern(error, pos) => to_precord_report(alloc, lines, filename, error, *pos),
|
||||
|
||||
EParams::BeforeArrow(pos) | EParams::Arrow(pos) | EParams::AfterArrow(pos) => {
|
||||
let surroundings = Region::new(start, *pos);
|
||||
let region = LineColumnRegion::from_pos(lines.convert_pos(*pos));
|
||||
|
||||
let doc = alloc.stack([
|
||||
alloc
|
||||
.reflow(r"I am partway through parsing a module header, but I got stuck here:"),
|
||||
alloc.region_with_subregion(lines.convert_region(surroundings), region),
|
||||
alloc.concat([
|
||||
alloc.reflow("I am expecting "),
|
||||
alloc.keyword("->"),
|
||||
alloc.reflow(" next, like:"),
|
||||
]),
|
||||
alloc
|
||||
.parser_suggestion("module { echo, read } -> [menu]")
|
||||
.indent(4),
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename,
|
||||
doc,
|
||||
title: "WEIRD MODULE PARAMS".to_string(),
|
||||
severity: Severity::RuntimeError,
|
||||
}
|
||||
}
|
||||
|
||||
EParams::Space(error, pos) => to_space_report(alloc, lines, filename, error, *pos),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_exposes_report<'a>(
|
||||
alloc: &'a RocDocAllocator<'a>,
|
||||
lines: &LineInfo,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue