Merge remote-tracking branch 'upstream/main' into unmacro_parsers

This commit is contained in:
Anton-4 2024-06-22 20:02:30 +02:00
commit ab217ede3f
No known key found for this signature in database
GPG key ID: 0971D718C0A9B937
680 changed files with 15718 additions and 14181 deletions

View file

@ -1,6 +1,10 @@
use bumpalo::Bump;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use roc_parse::{module, module::module_defs, parser::Parser, state::State};
use roc_parse::{
ast::Defs,
module::{self, parse_module_defs},
state::State,
};
use std::path::PathBuf;
pub fn parse_benchmark(c: &mut Criterion) {
@ -18,11 +22,7 @@ pub fn parse_benchmark(c: &mut Criterion) {
let (_actual, state) =
module::parse_header(&arena, State::new(src.as_bytes())).unwrap();
let min_indent = 0;
let res = module_defs()
.parse(&arena, state, min_indent)
.map(|tuple| tuple.1)
.unwrap();
let res = parse_module_defs(&arena, state, Defs::default()).unwrap();
black_box(res.len());
})
@ -43,11 +43,7 @@ pub fn parse_benchmark(c: &mut Criterion) {
let (_actual, state) =
module::parse_header(&arena, State::new(src.as_bytes())).unwrap();
let min_indent = 0;
let res = module_defs()
.parse(&arena, state, min_indent)
.map(|tuple| tuple.1)
.unwrap();
let res = parse_module_defs(&arena, state, Defs::default()).unwrap();
black_box(res.len());
})

View file

@ -1,7 +1,9 @@
use std::fmt::Debug;
use std::path::Path;
use crate::header::{AppHeader, HostedHeader, InterfaceHeader, PackageHeader, PlatformHeader};
use crate::expr::merge_spaces;
use crate::header::{
self, AppHeader, HostedHeader, ModuleHeader, ModuleName, PackageHeader, PlatformHeader,
};
use crate::ident::Accessor;
use crate::parser::ESingleQuote;
use bumpalo::collections::{String, Vec};
@ -9,10 +11,10 @@ use bumpalo::Bump;
use roc_collections::soa::{EitherIndex, Index, Slice};
use roc_error_macros::internal_error;
use roc_module::called_via::{BinOp, CalledVia, UnaryOp};
use roc_module::ident::ModuleName;
use roc_module::ident::QualifiedModuleName;
use roc_region::all::{Loc, Position, Region};
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Spaces<'a, T> {
pub before: &'a [CommentOrNewline<'a>],
pub item: T,
@ -47,6 +49,18 @@ impl<'a, T> Spaced<'a, T> {
Spaced::SpaceBefore(next, _spaces) | Spaced::SpaceAfter(next, _spaces) => next.item(),
}
}
pub fn map<U, F: Fn(&T) -> U>(&self, arena: &'a Bump, f: F) -> Spaced<'a, U> {
match self {
Spaced::Item(item) => Spaced::Item(f(item)),
Spaced::SpaceBefore(next, spaces) => {
Spaced::SpaceBefore(arena.alloc(next.map(arena, f)), spaces)
}
Spaced::SpaceAfter(next, spaces) => {
Spaced::SpaceAfter(arena.alloc(next.map(arena, f)), spaces)
}
}
}
}
impl<'a, T: Debug> Debug for Spaced<'a, T> {
@ -97,9 +111,151 @@ pub struct Module<'a> {
pub header: Header<'a>,
}
impl<'a> Module<'a> {
pub fn upgrade_header_imports(self, arena: &'a Bump) -> (Self, Defs<'a>) {
let (header, defs) = match self.header {
Header::Module(header) => (
Header::Module(ModuleHeader {
interface_imports: None,
..header
}),
Self::header_imports_to_defs(arena, header.interface_imports),
),
Header::App(header) => (
Header::App(AppHeader {
old_imports: None,
..header
}),
Self::header_imports_to_defs(arena, header.old_imports),
),
Header::Package(_) | Header::Platform(_) | Header::Hosted(_) => {
(self.header, Defs::default())
}
};
(Module { header, ..self }, defs)
}
pub fn header_imports_to_defs(
arena: &'a Bump,
imports: Option<
header::KeywordItem<'a, header::ImportsKeyword, header::ImportsCollection<'a>>,
>,
) -> Defs<'a> {
let mut defs = Defs::default();
if let Some(imports) = imports {
let len = imports.item.len();
for (index, import) in imports.item.iter().enumerate() {
let spaced = import.extract_spaces();
let value_def = match spaced.item {
header::ImportsEntry::Package(pkg_name, name, exposed) => {
Self::header_import_to_value_def(
Some(pkg_name),
name,
exposed,
import.region,
)
}
header::ImportsEntry::Module(name, exposed) => {
Self::header_import_to_value_def(None, name, exposed, import.region)
}
header::ImportsEntry::IngestedFile(path, typed_ident) => {
let typed_ident = typed_ident.extract_spaces();
ValueDef::IngestedFileImport(IngestedFileImport {
before_path: &[],
path: Loc {
value: path,
region: import.region,
},
name: header::KeywordItem {
keyword: Spaces {
before: &[],
item: ImportAsKeyword,
after: &[],
},
item: typed_ident.item.ident,
},
annotation: Some(IngestedFileAnnotation {
before_colon: merge_spaces(
arena,
typed_ident.before,
typed_ident.item.spaces_before_colon,
),
annotation: typed_ident.item.ann,
}),
})
}
};
defs.push_value_def(
value_def,
import.region,
if index == 0 {
let mut before = vec![CommentOrNewline::Newline, CommentOrNewline::Newline];
before.extend(spaced.before);
arena.alloc(before)
} else {
spaced.before
},
if index == len - 1 {
let mut after = spaced.after.to_vec();
after.extend_from_slice(imports.item.final_comments());
after.push(CommentOrNewline::Newline);
after.push(CommentOrNewline::Newline);
arena.alloc(after)
} else {
spaced.after
},
);
}
}
defs
}
fn header_import_to_value_def(
pkg_name: Option<&'a str>,
name: header::ModuleName<'a>,
exposed: Collection<'a, Loc<Spaced<'a, header::ExposedName<'a>>>>,
region: Region,
) -> ValueDef<'a> {
use crate::header::KeywordItem;
let new_exposed = if exposed.is_empty() {
None
} else {
Some(KeywordItem {
keyword: Spaces {
before: &[],
item: ImportExposingKeyword,
after: &[],
},
item: exposed,
})
};
ValueDef::ModuleImport(ModuleImport {
before_name: &[],
name: Loc {
region,
value: ImportedModuleName {
package: pkg_name,
name,
},
},
params: None,
alias: None,
exposed: new_exposed,
})
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Header<'a> {
Interface(InterfaceHeader<'a>),
Module(ModuleHeader<'a>),
App(AppHeader<'a>),
Package(PackageHeader<'a>),
Platform(PlatformHeader<'a>),
@ -252,7 +408,7 @@ pub enum Expr<'a> {
is_negative: bool,
},
// String Literals
/// String Literals
Str(StrLiteral<'a>), // string without escapes in it
/// eg 'b'
SingleQuote(&'a str),
@ -266,6 +422,9 @@ pub enum Expr<'a> {
/// Look up exactly one field on a tuple, e.g. `(x, y).1`.
TupleAccess(&'a Expr<'a>, &'a str),
/// Task await bang - i.e. the ! in `File.readUtf8! path`
TaskAwaitBang(&'a Expr<'a>),
// Collection Literals
List(Collection<'a, &'a Loc<Expr<'a>>>),
@ -281,14 +440,10 @@ pub enum Expr<'a> {
// Record Builders
RecordBuilder(Collection<'a, Loc<RecordBuilderField<'a>>>),
// The name of a file to be ingested directly into a variable.
IngestedFile(&'a Path, &'a Loc<TypeAnnotation<'a>>),
// Lookups
Var {
module_name: &'a str, // module_name will only be filled if the original Roc code stated something like `5 + SomeModule.myVar`, module_name will be blank if it was `5 + myVar`
ident: &'a str,
suffixed: u8, // how many `!` suffixes, for example `doTheThing!!` executes a Task that returns a Task
},
Underscore(&'a str),
@ -355,17 +510,6 @@ pub enum Expr<'a> {
}
impl Expr<'_> {
pub fn increment_var_suffix(&mut self, count: u8) {
match self {
Expr::Var { suffixed, .. } => {
*suffixed += count;
}
_ => {
internal_error!("increment_var_suffix called on non-Var expression");
}
}
}
pub fn get_region_spanning_binops(&self) -> Region {
match self {
Expr::BinOps(firsts, last) => {
@ -392,45 +536,43 @@ pub fn split_loc_exprs_around<'a>(
(before, after)
}
pub fn is_loc_expr_suffixed(loc_expr: &Loc<Expr>) -> bool {
match loc_expr.value.extract_spaces().item {
pub fn is_expr_suffixed(expr: &Expr) -> bool {
match expr {
// expression without arguments, `read!`
Expr::Var { suffixed, .. } => suffixed > 0,
Expr::Var { .. } => false,
Expr::TaskAwaitBang(..) => true,
// expression with arguments, `line! "Foo"`
Expr::Apply(sub_loc_expr, apply_args, _) => {
let is_function_suffixed = is_loc_expr_suffixed(sub_loc_expr);
let any_args_suffixed = apply_args.iter().any(|arg| is_loc_expr_suffixed(arg));
let is_function_suffixed = is_expr_suffixed(&sub_loc_expr.value);
let any_args_suffixed = apply_args.iter().any(|arg| is_expr_suffixed(&arg.value));
any_args_suffixed || is_function_suffixed
}
// expression in a pipeline, `"hi" |> say!`
Expr::BinOps(firsts, last) => {
let is_expr_suffixed = is_loc_expr_suffixed(last);
let any_chain_suffixed = firsts
firsts
.iter()
.any(|(chain_loc_expr, _)| is_loc_expr_suffixed(chain_loc_expr));
is_expr_suffixed || any_chain_suffixed
.any(|(chain_loc_expr, _)| is_expr_suffixed(&chain_loc_expr.value))
|| is_expr_suffixed(&last.value)
}
// expression in a if-then-else, `if isOk! then "ok" else doSomething!`
Expr::If(if_thens, final_else) => {
let any_if_thens_suffixed = if_thens.iter().any(|(if_then, else_expr)| {
is_loc_expr_suffixed(if_then) || is_loc_expr_suffixed(else_expr)
is_expr_suffixed(&if_then.value) || is_expr_suffixed(&else_expr.value)
});
is_loc_expr_suffixed(final_else) || any_if_thens_suffixed
is_expr_suffixed(&final_else.value) || any_if_thens_suffixed
}
// expression in parens `(read!)`
Expr::ParensAround(sub_loc_expr) => {
is_loc_expr_suffixed(&Loc::at(loc_expr.region, *sub_loc_expr))
}
Expr::ParensAround(sub_loc_expr) => is_expr_suffixed(sub_loc_expr),
// expression in a closure
Expr::Closure(_, sub_loc_expr) => is_loc_expr_suffixed(sub_loc_expr),
Expr::Closure(_, sub_loc_expr) => is_expr_suffixed(&sub_loc_expr.value),
// expressions inside a Defs
// note we ignore the final expression as it should not be suffixed
@ -438,35 +580,91 @@ pub fn is_loc_expr_suffixed(loc_expr: &Loc<Expr>) -> bool {
let any_defs_suffixed = defs.tags.iter().any(|tag| match tag.split() {
Ok(_) => false,
Err(value_index) => match defs.value_defs[value_index.index()] {
ValueDef::Body(_, loc_expr) => is_loc_expr_suffixed(loc_expr),
ValueDef::AnnotatedBody { body_expr, .. } => is_loc_expr_suffixed(body_expr),
ValueDef::Body(_, loc_expr) => is_expr_suffixed(&loc_expr.value),
ValueDef::AnnotatedBody { body_expr, .. } => is_expr_suffixed(&body_expr.value),
_ => false,
},
});
any_defs_suffixed
}
_ => false,
Expr::Float(_) => false,
Expr::Num(_) => false,
Expr::NonBase10Int { .. } => false,
Expr::Str(_) => false,
Expr::SingleQuote(_) => false,
Expr::RecordAccess(a, _) => is_expr_suffixed(a),
Expr::AccessorFunction(_) => false,
Expr::TupleAccess(a, _) => is_expr_suffixed(a),
Expr::List(items) => items.iter().any(|x| is_expr_suffixed(&x.value)),
Expr::RecordUpdate { update, fields } => {
is_expr_suffixed(&update.value)
|| fields
.iter()
.any(|field| is_assigned_value_suffixed(&field.value))
}
Expr::Record(items) => items
.iter()
.any(|field| is_assigned_value_suffixed(&field.value)),
Expr::Tuple(items) => items.iter().any(|x| is_expr_suffixed(&x.value)),
Expr::RecordBuilder(items) => items
.iter()
.any(|rbf| is_record_builder_field_suffixed(&rbf.value)),
Expr::Underscore(_) => false,
Expr::Crash => false,
Expr::Tag(_) => false,
Expr::OpaqueRef(_) => false,
Expr::EmptyDefsFinal => false,
Expr::Backpassing(_, _, _) => false, // TODO: we might want to check this?
Expr::Expect(a, b) | Expr::Dbg(a, b) => {
is_expr_suffixed(&a.value) || is_expr_suffixed(&b.value)
}
Expr::LowLevelDbg(_, a, b) => is_expr_suffixed(&a.value) || is_expr_suffixed(&b.value),
Expr::UnaryOp(a, _) => is_expr_suffixed(&a.value),
Expr::When(cond, branches) => {
is_expr_suffixed(&cond.value) || branches.iter().any(|x| is_when_branch_suffixed(x))
}
Expr::SpaceBefore(a, _) => is_expr_suffixed(a),
Expr::SpaceAfter(a, _) => is_expr_suffixed(a),
Expr::MalformedIdent(_, _) => false,
Expr::MalformedClosure => false,
Expr::MalformedSuffixed(_) => false,
Expr::PrecedenceConflict(_) => false,
Expr::MultipleRecordBuilders(_) => false,
Expr::UnappliedRecordBuilder(_) => false,
}
}
pub fn wrap_in_task_ok<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc<Expr<'a>> {
arena.alloc(Loc::at(
loc_expr.region,
Expr::Apply(
arena.alloc(Loc::at(
loc_expr.region,
Expr::Var {
module_name: ModuleName::TASK,
ident: "ok",
suffixed: 0,
},
)),
arena.alloc([loc_expr]),
CalledVia::BangSuffix,
),
))
fn is_when_branch_suffixed(branch: &WhenBranch<'_>) -> bool {
is_expr_suffixed(&branch.value.value)
|| branch
.guard
.map(|x| is_expr_suffixed(&x.value))
.unwrap_or(false)
}
fn is_assigned_value_suffixed<'a>(value: &AssignedField<'a, Expr<'a>>) -> bool {
match value {
AssignedField::RequiredValue(_, _, a) | AssignedField::OptionalValue(_, _, a) => {
is_expr_suffixed(&a.value)
}
AssignedField::LabelOnly(_) => false,
AssignedField::SpaceBefore(a, _) | AssignedField::SpaceAfter(a, _) => {
is_assigned_value_suffixed(a)
}
AssignedField::Malformed(_) => false,
}
}
fn is_record_builder_field_suffixed(field: &RecordBuilderField<'_>) -> bool {
match field {
RecordBuilderField::Value(_, _, a) => is_expr_suffixed(&a.value),
RecordBuilderField::ApplyValue(_, _, _, a) => is_expr_suffixed(&a.value),
RecordBuilderField::LabelOnly(_) => false,
RecordBuilderField::SpaceBefore(a, _) => is_record_builder_field_suffixed(a),
RecordBuilderField::SpaceAfter(a, _) => is_record_builder_field_suffixed(a),
RecordBuilderField::Malformed(_) => false,
}
}
pub fn split_around<T>(items: &[T], target: usize) -> (&[T], &[T]) {
@ -587,6 +785,12 @@ pub enum ValueDef<'a> {
preceding_comment: Region,
},
/// e.g. `import InternalHttp as Http exposing [Req]`.
ModuleImport(ModuleImport<'a>),
/// e.g. `import "path/to/my/file.txt" as myFile : Str`
IngestedFileImport(IngestedFileImport<'a>),
Stmt(&'a Loc<Expr<'a>>),
}
@ -600,8 +804,344 @@ impl<'a> ValueDef<'a> {
}
}
pub struct RecursiveValueDefIter<'a, 'b> {
current: &'b Defs<'a>,
index: usize,
pending: std::vec::Vec<&'b Defs<'a>>,
}
impl<'a, 'b> RecursiveValueDefIter<'a, 'b> {
pub fn new(defs: &'b Defs<'a>) -> Self {
Self {
current: defs,
index: 0,
pending: vec![],
}
}
fn push_pending_from_expr(&mut self, expr: &'b Expr<'a>) {
let mut expr_stack = vec![expr];
use Expr::*;
macro_rules! push_stack_from_record_fields {
($fields:expr) => {
for field in $fields.items {
let mut current = field.value;
loop {
use AssignedField::*;
match current {
RequiredValue(_, _, loc_val) => break expr_stack.push(&loc_val.value),
OptionalValue(_, _, loc_val) => break expr_stack.push(&loc_val.value),
SpaceBefore(next, _) => current = *next,
SpaceAfter(next, _) => current = *next,
LabelOnly(_) | Malformed(_) => break,
}
}
}
};
}
while let Some(next) = expr_stack.pop() {
match next {
Defs(defs, cont) => {
self.pending.push(defs);
// We purposefully don't push the exprs inside defs here
// because they will be traversed when the iterator
// gets to their parent def.
expr_stack.push(&cont.value);
}
List(list) => {
expr_stack.reserve(list.len());
for loc_expr in list.items {
expr_stack.push(&loc_expr.value);
}
}
RecordUpdate { update, fields } => {
expr_stack.reserve(fields.len() + 1);
expr_stack.push(&update.value);
push_stack_from_record_fields!(fields);
}
Record(fields) => {
expr_stack.reserve(fields.len());
push_stack_from_record_fields!(fields);
}
Tuple(fields) => {
expr_stack.reserve(fields.len());
for loc_expr in fields.items {
expr_stack.push(&loc_expr.value);
}
}
RecordBuilder(fields) => {
expr_stack.reserve(fields.len());
for loc_record_builder_field in fields.items {
let mut current_field = loc_record_builder_field.value;
loop {
use RecordBuilderField::*;
match current_field {
Value(_, _, loc_val) => break expr_stack.push(&loc_val.value),
ApplyValue(_, _, _, loc_val) => {
break expr_stack.push(&loc_val.value)
}
SpaceBefore(next_field, _) => current_field = *next_field,
SpaceAfter(next_field, _) => current_field = *next_field,
LabelOnly(_) | Malformed(_) => break,
}
}
}
}
Closure(_, body) => expr_stack.push(&body.value),
Backpassing(_, a, b) => {
expr_stack.reserve(2);
expr_stack.push(&a.value);
expr_stack.push(&b.value);
}
Expect(condition, cont)
| Dbg(condition, cont)
| LowLevelDbg(_, condition, cont) => {
expr_stack.reserve(2);
expr_stack.push(&condition.value);
expr_stack.push(&cont.value);
}
Apply(fun, args, _) => {
expr_stack.reserve(args.len() + 1);
expr_stack.push(&fun.value);
for loc_expr in args.iter() {
expr_stack.push(&loc_expr.value);
}
}
BinOps(ops, expr) => {
expr_stack.reserve(ops.len() + 1);
for (a, _) in ops.iter() {
expr_stack.push(&a.value);
}
expr_stack.push(&expr.value);
}
UnaryOp(expr, _) => expr_stack.push(&expr.value),
If(ifs, alternate) => {
expr_stack.reserve(ifs.len() * 2 + 1);
for (condition, consequent) in ifs.iter() {
expr_stack.push(&condition.value);
expr_stack.push(&consequent.value);
}
expr_stack.push(&alternate.value);
}
When(condition, branches) => {
expr_stack.reserve(branches.len() + 1);
expr_stack.push(&condition.value);
for WhenBranch {
patterns: _,
value,
guard,
} in branches.iter()
{
expr_stack.push(&value.value);
match guard {
None => {}
Some(guard) => expr_stack.push(&guard.value),
}
}
}
RecordAccess(expr, _)
| TupleAccess(expr, _)
| TaskAwaitBang(expr)
| SpaceBefore(expr, _)
| SpaceAfter(expr, _)
| ParensAround(expr) => expr_stack.push(expr),
MultipleRecordBuilders(loc_expr) | UnappliedRecordBuilder(loc_expr) => {
expr_stack.push(&loc_expr.value)
}
Float(_)
| Num(_)
| NonBase10Int { .. }
| Str(_)
| SingleQuote(_)
| AccessorFunction(_)
| Var { .. }
| Underscore(_)
| Crash
| Tag(_)
| OpaqueRef(_)
| MalformedIdent(_, _)
| MalformedClosure
| PrecedenceConflict(_)
| MalformedSuffixed(_)
| EmptyDefsFinal => { /* terminal */ }
}
}
}
}
impl<'a, 'b> Iterator for RecursiveValueDefIter<'a, 'b> {
type Item = (&'b ValueDef<'a>, &'b Region);
fn next(&mut self) -> Option<Self::Item> {
match self.current.tags.get(self.index) {
Some(tag) => {
if let Err(def_index) = tag.split() {
let def = &self.current.value_defs[def_index.index()];
let region = &self.current.regions[self.index];
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;
Some((def, region))
} else {
// Not a value def, try next
self.index += 1;
self.next()
}
}
None => {
self.current = self.pending.pop()?;
self.index = 0;
self.next()
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
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<
'a,
ImportExposingKeyword,
Collection<'a, Loc<Spaced<'a, header::ExposedName<'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>],
pub path: Loc<StrLiteral<'a>>,
pub name: header::KeywordItem<'a, ImportAsKeyword, Loc<&'a str>>,
pub annotation: Option<IngestedFileAnnotation<'a>>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct IngestedFileAnnotation<'a> {
pub before_colon: &'a [CommentOrNewline<'a>],
pub annotation: Loc<TypeAnnotation<'a>>,
}
impl<'a> Malformed for IngestedFileAnnotation<'a> {
fn is_malformed(&self) -> bool {
self.annotation.value.is_malformed()
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct ImportAsKeyword;
impl header::Keyword for ImportAsKeyword {
const KEYWORD: &'static str = "as";
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct ImportExposingKeyword;
impl header::Keyword for ImportExposingKeyword {
const KEYWORD: &'static str = "exposing";
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ImportedModuleName<'a> {
pub package: Option<&'a str>,
pub name: ModuleName<'a>,
}
impl<'a> From<ImportedModuleName<'a>> for QualifiedModuleName<'a> {
fn from(imported: ImportedModuleName<'a>) -> Self {
Self {
opt_package: imported.package,
module: imported.name.into(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ImportAlias<'a>(&'a str);
impl<'a> ImportAlias<'a> {
pub const fn new(name: &'a str) -> Self {
ImportAlias(name)
}
pub const fn as_str(&'a self) -> &'a str {
self.0
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Defs<'a> {
/// A collection of references by index to either `type_defs` or `value_defs`
/// It's an entry point for actual definitions, while `type_defs` and `value_defs` are append-only collections
pub tags: std::vec::Vec<EitherIndex<TypeDef<'a>, ValueDef<'a>>>,
pub regions: std::vec::Vec<Region>,
pub space_before: std::vec::Vec<Slice<CommentOrNewline<'a>>>,
@ -666,13 +1206,13 @@ impl<'a> Defs<'a> {
..
},
loc_expr,
) if collection.is_empty() && is_loc_expr_suffixed(loc_expr) => {
) if collection.is_empty() && is_expr_suffixed(&loc_expr.value) => {
let mut new_defs = self.clone();
new_defs.remove_value_def(tag_index);
return Some((new_defs, loc_expr));
}
ValueDef::Stmt(loc_expr) if is_loc_expr_suffixed(loc_expr) => {
ValueDef::Stmt(loc_expr) if is_expr_suffixed(&loc_expr.value) => {
let mut new_defs = self.clone();
new_defs.remove_value_def(tag_index);
@ -1019,6 +1559,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" }`
@ -1095,12 +1649,10 @@ pub enum Pattern<'a> {
// Identifier
Identifier {
ident: &'a str,
suffixed: u8,
},
QualifiedIdentifier {
module_name: &'a str,
ident: &'a str,
suffixed: u8,
},
Tag(&'a str),
@ -1218,21 +1770,11 @@ impl<'a> Pattern<'a> {
// { x, y } : { x : Int, y ? Bool }
// { x, y ? False } = rec
OptionalField(x, _) => match other {
Identifier {
ident: y,
suffixed: 0,
}
| OptionalField(y, _) => x == y,
Identifier { ident: y } | OptionalField(y, _) => x == y,
_ => false,
},
Identifier {
ident: x,
suffixed: a,
} => match other {
Identifier {
ident: y,
suffixed: b,
} => x == y && a == b,
Identifier { ident: x } => match other {
Identifier { ident: y } => x == y,
OptionalField(y, _) => x == y,
_ => false,
},
@ -1294,15 +1836,13 @@ impl<'a> Pattern<'a> {
QualifiedIdentifier {
module_name: a,
ident: x,
suffixed: i,
} => {
if let QualifiedIdentifier {
module_name: b,
ident: y,
suffixed: j,
} = other
{
a == b && x == y && i == j
a == b && x == y
} else {
false
}
@ -1367,15 +1907,6 @@ impl<'a> Pattern<'a> {
}
}
}
// used to check if a pattern is suffixed to report as an error
pub fn is_suffixed(&self) -> bool {
match self {
Pattern::Identifier { suffixed, .. } => *suffixed > 0,
Pattern::QualifiedIdentifier { suffixed, .. } => *suffixed > 0,
_ => false,
}
}
}
#[derive(Copy, Clone)]
pub struct Collection<'a, T> {
@ -1632,13 +2163,11 @@ impl<'a> Expr<'a> {
pub const REPL_OPAQUE_FUNCTION: Self = Expr::Var {
module_name: "",
ident: "<function>",
suffixed: 0,
};
pub const REPL_RUNTIME_CRASH: Self = Expr::Var {
module_name: "",
ident: "*",
suffixed: 0,
};
pub fn loc_ref(&'a self, region: Region) -> Loc<&'a Self> {
@ -1837,7 +2366,7 @@ impl<'a> Malformed for Module<'a> {
impl<'a> Malformed for Header<'a> {
fn is_malformed(&self) -> bool {
match self {
Header::Interface(header) => header.is_malformed(),
Header::Module(header) => header.is_malformed(),
Header::App(header) => header.is_malformed(),
Header::Package(header) => header.is_malformed(),
Header::Platform(header) => header.is_malformed(),
@ -1866,14 +2395,14 @@ impl<'a> Malformed for Expr<'a> {
Tag(_) |
OpaqueRef(_) |
SingleQuote(_) | // This is just a &str - not a bunch of segments
IngestedFile(_, _) |
EmptyDefsFinal |
Crash => false,
Str(inner) => inner.is_malformed(),
RecordAccess(inner, _) |
TupleAccess(inner, _) => inner.is_malformed(),
TupleAccess(inner, _) |
TaskAwaitBang(inner) => inner.is_malformed(),
List(items) => items.is_malformed(),
@ -2139,11 +2668,32 @@ impl<'a> Malformed for ValueDef<'a> {
condition,
preceding_comment: _,
} => condition.is_malformed(),
ValueDef::ModuleImport(ModuleImport {
before_name: _,
name: _,
params,
alias: _,
exposed: _,
}) => params.is_malformed(),
ValueDef::IngestedFileImport(IngestedFileImport {
before_path: _,
path,
name: _,
annotation,
}) => path.is_malformed() || annotation.is_malformed(),
ValueDef::Stmt(loc_expr) => loc_expr.is_malformed(),
}
}
}
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 {

View file

@ -323,43 +323,6 @@ pub fn fast_eat_until_control_character(bytes: &[u8]) -> usize {
simple_eat_until_control_character(&bytes[i..]) + i
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn test_eat_whitespace_simple() {
let bytes = &[0, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(simple_eat_whitespace(bytes), fast_eat_whitespace(bytes));
}
proptest! {
#[test]
fn test_eat_whitespace(bytes in proptest::collection::vec(any::<u8>(), 0..100)) {
prop_assert_eq!(simple_eat_whitespace(&bytes), fast_eat_whitespace(&bytes));
}
}
#[test]
fn test_eat_until_control_character_simple() {
let bytes = &[32, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(
simple_eat_until_control_character(bytes),
fast_eat_until_control_character(bytes)
);
}
proptest! {
#[test]
fn test_eat_until_control_character(bytes in proptest::collection::vec(any::<u8>(), 0..100)) {
prop_assert_eq!(
simple_eat_until_control_character(&bytes),
fast_eat_until_control_character(&bytes));
}
}
}
pub fn space0_e<'a, E>(
indent_problem: fn(Position) -> E,
) -> impl Parser<'a, &'a [CommentOrNewline<'a>], E>
@ -515,3 +478,40 @@ where
Ok((progress, state))
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn test_eat_whitespace_simple() {
let bytes = &[0, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(simple_eat_whitespace(bytes), fast_eat_whitespace(bytes));
}
proptest! {
#[test]
fn test_eat_whitespace(bytes in proptest::collection::vec(any::<u8>(), 0..100)) {
prop_assert_eq!(simple_eat_whitespace(&bytes), fast_eat_whitespace(&bytes));
}
}
#[test]
fn test_eat_until_control_character_simple() {
let bytes = &[32, 0, 0, 0, 0, 0, 0, 0];
assert_eq!(
simple_eat_until_control_character(bytes),
fast_eat_until_control_character(bytes)
);
}
proptest! {
#[test]
fn test_eat_until_control_character(bytes in proptest::collection::vec(any::<u8>(), 0..100)) {
prop_assert_eq!(
simple_eat_until_control_character(&bytes),
fast_eat_until_control_character(&bytes));
}
}
}

View file

@ -1,26 +1,31 @@
use crate::ast::{
is_loc_expr_suffixed, AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces,
Implements, ImplementsAbilities, Pattern, RecordBuilderField, Spaceable, Spaces,
TypeAnnotation, TypeDef, TypeHeader, ValueDef,
is_expr_suffixed, AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces,
Implements, ImplementsAbilities, ImportAlias, ImportAsKeyword, ImportExposingKeyword,
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,
space0_before_optional_after, space0_e, spaces, spaces_around, spaces_before,
};
use crate::ident::{integer_ident, lowercase_ident, parse_ident, Accessor, Ident};
use crate::keyword;
use crate::ident::{
integer_ident, lowercase_ident, parse_ident, unqualified_ident, Accessor, Ident, Suffix,
};
use crate::module::module_name_help;
use crate::parser::{
self, and, backtrackable, between, byte, byte_indent, collection_inner,
collection_trailing_sep_e, either, increment_min_indent, indented_seq, line_min_indent, loc,
collection_trailing_sep_e, either, increment_min_indent, line_min_indent, loc,
map, map_with_arena, optional, reset_min_indent, sep_by1, sep_by1_e, set_min_indent,
skip_first, skip_second, specialize_err, specialize_err_ref, then, two_bytes, zero_or_more,
EClosure, EExpect, EExpr, EIf, EInParens, EList, ENumber, EPattern, ERecord, EString, EType,
EWhen, Either, ParseResult, Parser,
EWhen, Either, ParseResult, Parser, EImport, EImportParams, indented_seq_skip_first
};
use crate::pattern::{closure_param, loc_implements_parser};
use crate::state::State;
use crate::string_literal::StrLikeLiteral;
use crate::type_annotation;
use crate::string_literal::{self, StrLikeLiteral};
use crate::{header, keyword};
use crate::{module, type_annotation};
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_collections::soa::Slice;
@ -69,20 +74,6 @@ pub struct ExprParseOptions {
///
/// > Just foo if foo == 2 -> ...
pub check_for_arrow: bool,
/// Check for a suffixed expression, if we find one then
/// subsequent parsing for this expression should have an increased
/// indent, this is so we can distinguish between the end of the
/// statement and the next expression.
pub suffixed_found: bool,
}
impl ExprParseOptions {
pub fn set_suffixed_found(&self) -> Self {
let mut new = *self;
new.suffixed_found = true;
new
}
}
pub fn expr_help<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
@ -137,7 +128,7 @@ fn loc_expr_in_parens_etc_help<'a>() -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>
specialize_err(EExpr::InParens, loc_expr_in_parens_help()),
record_field_access_chain(),
)),
move |arena: &'a Bump, value: Loc<(Loc<Expr<'a>>, Vec<'a, Accessor<'a>>)>| {
move |arena: &'a Bump, value: Loc<(Loc<Expr<'a>>, Vec<'a, Suffix<'a>>)>| {
let Loc {
mut region,
value: (loc_expr, field_accesses),
@ -158,16 +149,23 @@ fn loc_expr_in_parens_etc_help<'a>() -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>
)
}
fn record_field_access_chain<'a>() -> impl Parser<'a, Vec<'a, Accessor<'a>>, EExpr<'a>> {
zero_or_more(skip_first(
byte(b'.', EExpr::Access),
specialize_err(
|_, pos| EExpr::Access(pos),
one_of!(
map(lowercase_ident(), Accessor::RecordField),
map(integer_ident(), Accessor::TupleIndex),
),
fn record_field_access_chain<'a>() -> impl Parser<'a, Vec<'a, Suffix<'a>>, EExpr<'a>> {
zero_or_more(one_of!(
skip_first(
byte(b'.', EExpr::Access),
specialize_err(
|_, pos| EExpr::Access(pos),
one_of!(
map(lowercase_ident(), |x| Suffix::Accessor(
Accessor::RecordField(x)
)),
map(integer_ident(), |x| Suffix::Accessor(Accessor::TupleIndex(
x
))),
)
)
),
map(byte(b'!', EExpr::Access), |_| Suffix::TaskAwaitBang),
))
}
@ -190,10 +188,7 @@ fn loc_term_or_underscore_or_conditional<'a>(
loc(underscore_expression()),
loc(record_literal_help()),
loc(specialize_err(EExpr::List, list_literal_help())),
loc(map_with_arena(
assign_or_destructure_identifier(),
ident_to_expr
)),
ident_seq(),
)
}
@ -213,10 +208,7 @@ fn loc_term_or_underscore<'a>(
loc(underscore_expression()),
loc(record_literal_help()),
loc(specialize_err(EExpr::List, list_literal_help())),
loc(map_with_arena(
assign_or_destructure_identifier(),
ident_to_expr
)),
ident_seq(),
)
}
@ -231,13 +223,30 @@ fn loc_term<'a>(options: ExprParseOptions) -> impl Parser<'a, Loc<Expr<'a>>, EEx
loc(specialize_err(EExpr::Closure, closure_help(options))),
loc(record_literal_help()),
loc(specialize_err(EExpr::List, list_literal_help())),
loc(map_with_arena(
assign_or_destructure_identifier(),
ident_to_expr
)),
ident_seq(),
)
}
fn ident_seq<'a>() -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
parse_ident_seq.trace("ident_seq")
}
fn parse_ident_seq<'a>(
arena: &'a Bump,
state: State<'a>,
min_indent: u32,
) -> ParseResult<'a, Loc<Expr<'a>>, EExpr<'a>> {
let (_, loc_ident, state) =
loc(assign_or_destructure_identifier()).parse(arena, state, min_indent)?;
let expr = ident_to_expr(arena, loc_ident.value);
let (_p, suffixes, state) = record_field_access_chain()
.trace("record_field_access_chain")
.parse(arena, state, min_indent)
.map_err(|(_p, e)| (MadeProgress, e))?;
let expr = apply_expr_access_chain(arena, expr, suffixes);
Ok((MadeProgress, Loc::at(loc_ident.region, expr), state))
}
fn underscore_expression<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
move |arena: &'a Bump, state: State<'a>, min_indent: u32| {
let start = state.pos();
@ -269,17 +278,19 @@ fn crash_kw<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
fn loc_possibly_negative_or_negated_term<'a>(
options: ExprParseOptions,
) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
let parse_unary_negate = move |arena, state: State<'a>, min_indent: u32| {
let initial = state.clone();
let (_, (loc_op, loc_expr), state) =
and(loc(unary_negate()), loc_term(options)).parse(arena, state, min_indent)?;
let loc_expr = numeric_negate_expression(arena, initial, loc_op, loc_expr, &[]);
Ok((MadeProgress, loc_expr, state))
};
one_of![
|arena, state: State<'a>, min_indent: u32| {
let initial = state.clone();
let (_, (loc_op, loc_expr), state) =
and(loc(unary_negate()), loc_term(options)).parse(arena, state, min_indent)?;
let loc_expr = numeric_negate_expression(arena, initial, loc_op, loc_expr, &[]);
Ok((MadeProgress, loc_expr, state))
},
parse_unary_negate,
// this will parse negative numbers, which the unary negate thing up top doesn't (for now)
loc(specialize_err(EExpr::Number, number_literal_help())),
loc(map_with_arena(
@ -328,6 +339,7 @@ fn expr_start<'a>(options: ExprParseOptions) -> impl Parser<'a, Loc<Expr<'a>>, E
loc(specialize_err(EExpr::When, when::expr_help(options))),
loc(specialize_err(EExpr::Expect, expect_help(options))),
loc(specialize_err(EExpr::Dbg, dbg_help(options))),
loc(import_help(options)),
loc(specialize_err(EExpr::Closure, closure_help(options))),
loc(expr_operator_chain(options)),
fail_expr_start_e()
@ -343,10 +355,10 @@ fn expr_operator_chain<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a
let initial_state = state.clone();
let end = state.pos();
let new_options = if is_loc_expr_suffixed(&expr) {
options.set_suffixed_found()
let new_min_indent = if is_expr_suffixed(&expr.value) {
min_indent + 1
} else {
options
min_indent
};
match space0_e(EExpr::IndentEnd).parse(arena, state.clone(), min_indent) {
@ -361,8 +373,8 @@ fn expr_operator_chain<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a
};
match parse_expr_end(
min_indent,
new_options,
new_min_indent,
options,
expr_state,
arena,
state,
@ -372,7 +384,7 @@ fn expr_operator_chain<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a
Ok((progress, expr, new_state)) => {
// We need to check if we have just parsed a suffixed statement,
// if so, this is a defs node.
if is_loc_expr_suffixed(&Loc::at_zero(expr)) {
if is_expr_suffixed(&expr) {
let def_region = Region::new(end, new_state.pos());
let value_def = ValueDef::Stmt(arena.alloc(Loc::at(def_region, expr)));
@ -444,7 +456,7 @@ impl<'a> ExprState<'a> {
} else if !self.expr.value.is_tag()
&& !self.expr.value.is_opaque()
&& !self.arguments.is_empty()
&& !is_loc_expr_suffixed(&self.expr)
&& !is_expr_suffixed(&self.expr.value)
{
let region = Region::across_all(self.arguments.iter().map(|v| &v.region));
@ -631,30 +643,48 @@ pub fn parse_single_def<'a>(
min_indent,
) {
Err((NoProgress, _)) => {
match parse_expect.parse(arena, state.clone(), min_indent) {
Err((_, _)) => {
// a hacky way to get expression-based error messages. TODO fix this
Ok((NoProgress, None, initial))
let pos_before_import = state.pos();
match import().parse(arena, state.clone(), min_indent) {
Err((NoProgress, _)) => {
match parse_expect.parse(arena, state.clone(), min_indent) {
Err((_, _)) => {
// a hacky way to get expression-based error messages. TODO fix this
Ok((NoProgress, None, initial))
}
Ok((_, expect_flavor, state)) => parse_statement_inside_def(
arena,
state,
min_indent,
options,
start,
spaces_before_current_start,
spaces_before_current,
|preceding_comment, loc_def_expr| match expect_flavor {
Either::Second(_) => ValueDef::Expect {
condition: arena.alloc(loc_def_expr),
preceding_comment,
},
Either::First(_) => ValueDef::ExpectFx {
condition: arena.alloc(loc_def_expr),
preceding_comment,
},
},
),
}
}
Ok((_, expect_flavor, state)) => parse_statement_inside_def(
arena,
Err((MadeProgress, err)) => {
Err((MadeProgress, EExpr::Import(err, pos_before_import)))
}
Ok((_, (loc_import, spaces_after), state)) => Ok((
MadeProgress,
Some(SingleDef {
type_or_value: Either::Second(loc_import.value),
region: loc_import.region,
spaces_before: spaces_before_current,
spaces_after,
}),
state,
min_indent,
options,
start,
spaces_before_current_start,
spaces_before_current,
|preceding_comment, loc_def_expr| match expect_flavor {
Either::Second(_) => ValueDef::Expect {
condition: arena.alloc(loc_def_expr),
preceding_comment,
},
Either::First(_) => ValueDef::ExpectFx {
condition: arena.alloc(loc_def_expr),
preceding_comment,
},
},
),
)),
}
}
Err((MadeProgress, _)) => {
@ -671,7 +701,9 @@ pub fn parse_single_def<'a>(
|_, loc_def_expr| -> ValueDef<'a> { ValueDef::Stmt(arena.alloc(loc_def_expr)) },
) {
Ok((_, Some(single_def), state)) => match single_def.type_or_value {
Either::Second(ValueDef::Stmt(loc_expr)) if is_loc_expr_suffixed(loc_expr) => {
Either::Second(ValueDef::Stmt(loc_expr))
if is_expr_suffixed(&loc_expr.value) =>
{
Ok((MadeProgress, Some(single_def), state))
}
_ => Ok((NoProgress, None, initial)), // a hacky way to get expression-based error messages. TODO fix this
@ -713,6 +745,7 @@ pub fn parse_single_def<'a>(
type_or_value: Either::First(type_def),
region: def_region,
spaces_before: spaces_before_current,
spaces_after: &[],
}),
state,
));
@ -773,6 +806,7 @@ pub fn parse_single_def<'a>(
type_or_value: Either::First(type_def),
region,
spaces_before: spaces_before_current,
spaces_after: &[],
}),
state,
));
@ -796,6 +830,7 @@ pub fn parse_single_def<'a>(
type_or_value: Either::First(type_def),
region,
spaces_before: spaces_before_current,
spaces_after: &[],
}),
state,
));
@ -809,6 +844,7 @@ pub fn parse_single_def<'a>(
type_or_value: Either::Second(value_def),
region,
spaces_before: spaces_before_current,
spaces_after: &[],
}),
state,
));
@ -847,6 +883,7 @@ pub fn parse_single_def<'a>(
type_or_value: Either::First(type_def),
region,
spaces_before: spaces_before_current,
spaces_after: &[],
}),
state,
));
@ -871,6 +908,7 @@ pub fn parse_single_def<'a>(
type_or_value: Either::First(type_def),
region,
spaces_before: spaces_before_current,
spaces_after: &[],
}),
state,
));
@ -884,6 +922,7 @@ pub fn parse_single_def<'a>(
type_or_value: Either::Second(value_def),
region,
spaces_before: spaces_before_current,
spaces_after: &[],
}),
state,
));
@ -904,7 +943,9 @@ pub fn parse_single_def<'a>(
|_, loc_def_expr| -> ValueDef<'a> { ValueDef::Stmt(arena.alloc(loc_def_expr)) },
) {
Ok((_, Some(single_def), state)) => match single_def.type_or_value {
Either::Second(ValueDef::Stmt(loc_expr)) if is_loc_expr_suffixed(loc_expr) => {
Either::Second(ValueDef::Stmt(loc_expr))
if is_expr_suffixed(&loc_expr.value) =>
{
Ok((MadeProgress, Some(single_def), state))
}
_ => Ok((NoProgress, None, initial)),
@ -915,6 +956,189 @@ pub fn parse_single_def<'a>(
}
}
fn import<'a>() -> impl Parser<'a, (Loc<ValueDef<'a>>, &'a [CommentOrNewline<'a>]), EImport<'a>> {
then(
and(
loc(skip_first(
parser::keyword(keyword::IMPORT, EImport::Import),
increment_min_indent(one_of!(import_body(), import_ingested_file_body()))
)),
space0_e(EImport::EndNewline)
),
|_arena, state, progress, (import, spaces_after)| {
if !spaces_after.is_empty() || state.has_reached_end() {
Ok((progress, (import, spaces_after), state))
} else {
// We require EOF, comment, or newline after import
Err((progress, EImport::EndNewline(state.pos())))
}
},
)
}
fn import_body<'a>() -> impl Parser<'a, ValueDef<'a>, EImport<'a>> {
map(
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())
}),
ValueDef::ModuleImport
)
}
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 {
package: optional(skip_second(
specialize_err(|_, pos| EImport::PackageShorthand(pos), lowercase_ident()),
byte(b'.', EImport::PackageShorthandDot)
)),
name: module_name_help(EImport::ModuleName)
})
}
#[inline(always)]
fn import_as<'a>(
) -> impl Parser<'a, header::KeywordItem<'a, ImportAsKeyword, Loc<ImportAlias<'a>>>, EImport<'a>> {
record!(header::KeywordItem {
keyword: module::spaces_around_keyword(
ImportAsKeyword,
EImport::As,
EImport::IndentAs,
EImport::IndentAlias
),
item: then(
specialize_err(|_, pos| EImport::Alias(pos), loc(unqualified_ident())),
|_arena, state, _progress, loc_ident| {
match loc_ident.value.chars().next() {
Some(first) if first.is_uppercase() => Ok((
MadeProgress,
loc_ident.map(|ident| ImportAlias::new(ident)),
state,
)),
Some(_) => Err((MadeProgress, EImport::LowercaseAlias(loc_ident.region))),
None => Err((MadeProgress, EImport::Alias(state.pos()))),
}
}
)
})
}
#[inline(always)]
fn import_exposing<'a>() -> impl Parser<
'a,
header::KeywordItem<
'a,
ImportExposingKeyword,
Collection<'a, Loc<Spaced<'a, header::ExposedName<'a>>>>,
>,
EImport<'a>,
> {
record!(header::KeywordItem {
keyword: module::spaces_around_keyword(
ImportExposingKeyword,
EImport::Exposing,
EImport::IndentExposing,
EImport::ExposingListStart,
),
item: collection_trailing_sep_e(
byte(b'[', EImport::ExposingListStart),
loc(import_exposed_name()),
byte(b',', EImport::ExposingListEnd),
byte(b']', EImport::ExposingListEnd),
Spaced::SpaceBefore
)
})
}
#[inline(always)]
fn import_exposed_name<'a>(
) -> impl Parser<'a, crate::ast::Spaced<'a, crate::header::ExposedName<'a>>, EImport<'a>> {
map(
specialize_err(|_, pos| EImport::ExposedName(pos), unqualified_ident()),
|n| Spaced::Item(crate::header::ExposedName::new(n))
)
}
#[inline(always)]
fn import_ingested_file_body<'a>() -> impl Parser<'a, ValueDef<'a>, EImport<'a>> {
map(
record!(IngestedFileImport {
before_path: space0_e(EImport::IndentStart),
path: loc(specialize_err(
|_, pos| EImport::IngestedPath(pos),
string_literal::parse_str_literal()
)),
name: import_ingested_file_as(),
annotation: optional(import_ingested_file_annotation())
}),
ValueDef::IngestedFileImport
)
}
#[inline(always)]
fn import_ingested_file_as<'a>(
) -> impl Parser<'a, header::KeywordItem<'a, ImportAsKeyword, Loc<&'a str>>, EImport<'a>> {
record!(header::KeywordItem {
keyword: module::spaces_around_keyword(
ImportAsKeyword,
EImport::As,
EImport::IndentAs,
EImport::IndentIngestedName
),
item: specialize_err(
|(), pos| EImport::IngestedName(pos),
loc(lowercase_ident())
)
})
}
#[inline(always)]
fn import_ingested_file_annotation<'a>() -> impl Parser<'a, IngestedFileAnnotation<'a>, EImport<'a>>
{
record!(IngestedFileAnnotation {
before_colon: skip_second(
backtrackable(space0_e(EImport::IndentColon)),
byte(b':', EImport::Colon)
),
annotation: specialize_err(EImport::Annotation, type_annotation::located(false))
})
}
pub fn parse_single_def_assignment<'a>(
options: ExprParseOptions,
min_indent: u32,
@ -933,7 +1157,7 @@ pub fn parse_single_def_assignment<'a>(
// If the expression is actually a suffixed statement, then we need to continue
// to parse the rest of the expression
if crate::ast::is_loc_expr_suffixed(&first_loc_expr) {
if crate::ast::is_expr_suffixed(&first_loc_expr.value) {
let mut defs = Defs::default();
// Take the suffixed value and make it a e.g. Body(`{}=`, Apply(Var(...)))
// we will keep the pattern `def_loc_pattern` for the new Defs
@ -963,6 +1187,7 @@ pub fn parse_single_def_assignment<'a>(
type_or_value: Either::Second(value_def),
region,
spaces_before: spaces_before_current,
spaces_after: &[],
}),
state_after_rest_of_def,
));
@ -984,6 +1209,7 @@ pub fn parse_single_def_assignment<'a>(
type_or_value: Either::Second(value_def),
region,
spaces_before: spaces_before_current,
spaces_after: &[],
}),
state_after_first_expression,
));
@ -999,6 +1225,7 @@ pub fn parse_single_def_assignment<'a>(
type_or_value: Either::Second(value_def),
region,
spaces_before: spaces_before_current,
spaces_after: &[],
}),
state_after_first_expression,
))
@ -1046,6 +1273,7 @@ fn parse_statement_inside_def<'a>(
type_or_value: Either::Second(value_def),
region,
spaces_before: spaces_before_current,
spaces_after: &[],
}),
state,
))
@ -1129,10 +1357,16 @@ fn parse_defs_end<'a>(
Ok((_, Some(single_def), next_state)) => {
let region = single_def.region;
let spaces_before_current = single_def.spaces_before;
let spaces_after_current = single_def.spaces_after;
match single_def.type_or_value {
Either::First(type_def) => {
defs.push_type_def(type_def, region, spaces_before_current, &[]);
defs.push_type_def(
type_def,
region,
spaces_before_current,
spaces_after_current,
);
}
Either::Second(value_def) => {
// If we got a ValueDef::Body, check if a type annotation preceded it.
@ -1194,7 +1428,12 @@ fn parse_defs_end<'a>(
if !joined {
// the previous and current def can't be joined up
defs.push_value_def(value_def, region, spaces_before_current, &[]);
defs.push_value_def(
value_def,
region,
spaces_before_current,
spaces_after_current,
);
}
}
}
@ -1216,6 +1455,7 @@ pub struct SingleDef<'a> {
pub type_or_value: Either<TypeDef<'a>, ValueDef<'a>>,
pub region: Region,
pub spaces_before: &'a [CommentOrNewline<'a>],
pub spaces_after: &'a [CommentOrNewline<'a>],
}
fn parse_defs_expr<'a>(
@ -1834,15 +2074,15 @@ fn parse_expr_operator<'a>(
expr_state.end = new_end;
expr_state.spaces_after = spaces;
let new_options = if is_loc_expr_suffixed(&new_expr) {
options.set_suffixed_found()
let new_min_indent = if is_expr_suffixed(&new_expr.value) {
min_indent + 1
} else {
options
min_indent
};
match parse_expr_end(
min_indent,
new_options,
new_min_indent,
options,
expr_state,
arena,
state,
@ -1853,7 +2093,7 @@ fn parse_expr_operator<'a>(
let def_region = expr.get_region_spanning_binops();
let mut new_expr = Loc::at(def_region, expr);
if is_loc_expr_suffixed(&new_expr) {
if is_expr_suffixed(&new_expr.value) {
// We have parsed a statement such as `"hello" |> line!`
// put the spaces from after the operator in front of the call
if !spaces_after_operator.is_empty() {
@ -1897,18 +2137,12 @@ fn parse_expr_end<'a>(
state: State<'a>,
initial_state: State<'a>,
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
let inner_min_indent = if options.suffixed_found {
min_indent + 1
} else {
min_indent
};
let parser = skip_first(
crate::blankspace::check_indent(EExpr::IndentEnd),
loc_term_or_underscore(options),
);
match parser.parse(arena, state.clone(), inner_min_indent) {
match parser.parse(arena, state.clone(), min_indent) {
Err((MadeProgress, f)) => Err((MadeProgress, f)),
Ok((
_,
@ -1966,10 +2200,10 @@ fn parse_expr_end<'a>(
Ok((_, mut arg, state)) => {
let new_end = state.pos();
let new_options = if is_loc_expr_suffixed(&arg) {
options.set_suffixed_found()
let min_indent = if is_expr_suffixed(&arg.value) {
min_indent + 1
} else {
options
min_indent
};
// now that we have `function arg1 ... <spaces> argn`, attach the spaces to the `argn`
@ -1996,14 +2230,7 @@ fn parse_expr_end<'a>(
expr_state.end = new_end;
expr_state.spaces_after = new_spaces;
parse_expr_end(
min_indent,
new_options,
expr_state,
arena,
state,
initial_state,
)
parse_expr_end(min_indent, options, expr_state, arena, state, initial_state)
}
}
}
@ -2118,11 +2345,13 @@ fn parse_expr_end<'a>(
}
pub fn loc_expr<'a>(accept_multi_backpassing: bool) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
expr_start(ExprParseOptions {
accept_multi_backpassing,
check_for_arrow: true,
suffixed_found: false,
})
space0_before_e(
expr_start(ExprParseOptions {
accept_multi_backpassing,
check_for_arrow: true,
}),
EExpr::IndentEnd,
)
}
pub fn merge_spaces<'a>(
@ -2156,19 +2385,11 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
}
let mut pat = match expr.item {
Expr::Var {
module_name,
ident,
suffixed,
} => {
Expr::Var { module_name, ident } => {
if module_name.is_empty() {
Pattern::Identifier { ident, suffixed }
Pattern::Identifier { ident }
} else {
Pattern::QualifiedIdentifier {
module_name,
ident,
suffixed,
}
Pattern::QualifiedIdentifier { module_name, ident }
}
}
Expr::Underscore(opt_name) => Pattern::Underscore(opt_name),
@ -2228,8 +2449,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
is_negative,
},
// These would not have parsed as patterns
Expr::IngestedFile(_, _)
| Expr::AccessorFunction(_)
Expr::AccessorFunction(_)
| Expr::RecordAccess(_, _)
| Expr::TupleAccess(_, _)
| Expr::List { .. }
@ -2249,6 +2469,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
| Expr::UnappliedRecordBuilder { .. }
| Expr::RecordUpdate { .. }
| Expr::UnaryOp(_, _)
| Expr::TaskAwaitBang(..)
| Expr::Crash => return Err(()),
Expr::Str(string) => Pattern::StrLiteral(string),
@ -2303,10 +2524,7 @@ fn assigned_expr_field_to_pattern_help<'a>(
)
}
}
AssignedField::LabelOnly(name) => Pattern::Identifier {
ident: name.value,
suffixed: 0,
},
AssignedField::LabelOnly(name) => Pattern::Identifier { ident: name.value },
AssignedField::SpaceBefore(nested, spaces) => Pattern::SpaceBefore(
arena.alloc(assigned_expr_field_to_pattern_help(arena, nested)?),
spaces,
@ -2319,41 +2537,41 @@ fn assigned_expr_field_to_pattern_help<'a>(
})
}
pub fn toplevel_defs<'a>() -> impl Parser<'a, Defs<'a>, EExpr<'a>> {
move |arena, state: State<'a>, min_indent: u32| {
let (_, initial_space, state) =
space0_e(EExpr::IndentEnd).parse(arena, state, min_indent)?;
pub fn parse_top_level_defs<'a>(
arena: &'a bumpalo::Bump,
state: State<'a>,
mut output: Defs<'a>,
) -> ParseResult<'a, Defs<'a>, EExpr<'a>> {
let (_, initial_space, state) = space0_e(EExpr::IndentEnd).parse(arena, state, 0)?;
let start_column = state.column();
let start_column = state.column();
let options = ExprParseOptions {
accept_multi_backpassing: true,
check_for_arrow: true,
suffixed_found: false,
};
let options = ExprParseOptions {
accept_multi_backpassing: true,
check_for_arrow: true,
};
let mut output = Defs::default();
let before = Slice::extend_new(&mut output.spaces, initial_space.iter().copied());
let existing_len = output.tags.len();
let (_, mut output, state) = parse_defs_end(options, start_column, output, arena, state)?;
let before = Slice::extend_new(&mut output.spaces, initial_space.iter().copied());
let (_, final_space, state) =
space0_e(EExpr::IndentEnd).parse(arena, state, start_column)?;
let (_, mut output, state) = parse_defs_end(options, start_column, output, arena, state)?;
if !output.tags.is_empty() {
// add surrounding whitespace
let after = Slice::extend_new(&mut output.spaces, final_space.iter().copied());
let (_, final_space, state) = space0_e(EExpr::IndentEnd).parse(arena, state, start_column)?;
debug_assert!(output.space_before[0].is_empty());
output.space_before[0] = before;
if output.tags.len() > existing_len {
// add surrounding whitespace
let after = Slice::extend_new(&mut output.spaces, final_space.iter().copied());
let last = output.tags.len() - 1;
debug_assert!(output.space_after[last].is_empty() || after.is_empty());
output.space_after[last] = after;
}
debug_assert!(output.space_before[existing_len].is_empty());
output.space_before[existing_len] = before;
Ok((MadeProgress, output, state))
let last = output.tags.len() - 1;
debug_assert!(output.space_after[last].is_empty() || after.is_empty());
output.space_after[last] = after;
}
Ok((MadeProgress, output, state))
}
// PARSER HELPERS
@ -2362,7 +2580,7 @@ fn closure_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EClo
// closure_help_help(options)
map_with_arena(
// After the first token, all other tokens must be indented past the start of the line
indented_seq(
indented_seq_skip_first(
// All closures start with a '\' - e.g. (\x -> x + 1)
byte_indent(b'\\', EClosure::Start),
// Once we see the '\', we're committed to parsing this as a closure.
@ -2406,7 +2624,7 @@ mod when {
pub fn expr_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EWhen<'a>> {
map_with_arena(
and(
indented_seq(
indented_seq_skip_first(
parser::keyword(keyword::WHEN, EWhen::When),
space0_around_e_no_after_indent_check(
specialize_err_ref(EWhen::Condition, expr_start(options)),
@ -2417,7 +2635,7 @@ mod when {
// ambiguity. The formatter will fix it up.
//
// We require that branches are indented relative to the line containing the `is`.
indented_seq(
indented_seq_skip_first(
parser::keyword(keyword::IS, EWhen::Is),
branches(options)
)
@ -2709,6 +2927,18 @@ fn dbg_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EExpect<
}
}
fn import_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
move |arena: &'a Bump, state: State<'a>, min_indent: u32| {
let (_, (import_def, spaces_after), state) =
specialize_err(EExpr::Import, import()).parse(arena, state, min_indent)?;
let mut defs = Defs::default();
defs.push_value_def(import_def.value, import_def.region, &[], spaces_after);
parse_defs_expr(options, min_indent, defs, arena, state)
}
}
fn if_expr_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EIf<'a>> {
move |arena: &'a Bump, state, min_indent| {
let (_, _, state) =
@ -2798,21 +3028,13 @@ fn ident_to_expr<'a>(arena: &'a Bump, src: Ident<'a>) -> Expr<'a> {
match src {
Ident::Tag(string) => Expr::Tag(string),
Ident::OpaqueRef(string) => Expr::OpaqueRef(string),
Ident::Access {
module_name,
parts,
suffixed,
} => {
Ident::Access { module_name, parts } => {
let mut iter = parts.iter();
// The first value in the iterator is the variable name,
// e.g. `foo` in `foo.bar.baz`
let mut answer = match iter.next() {
Some(Accessor::RecordField(ident)) => Expr::Var {
module_name,
ident,
suffixed,
},
Some(Accessor::RecordField(ident)) => Expr::Var { module_name, ident },
Some(Accessor::TupleIndex(_)) => {
// TODO: make this state impossible to represent in Ident::Access,
// by splitting out parts[0] into a separate field with a type of `&'a str`,
@ -3172,13 +3394,18 @@ fn record_builder_help<'a>(
fn apply_expr_access_chain<'a>(
arena: &'a Bump,
value: Expr<'a>,
accessors: Vec<'a, Accessor<'a>>,
accessors: Vec<'a, Suffix<'a>>,
) -> Expr<'a> {
accessors
.into_iter()
.fold(value, |value, accessor| match accessor {
Accessor::RecordField(field) => Expr::RecordAccess(arena.alloc(value), field),
Accessor::TupleIndex(field) => Expr::TupleAccess(arena.alloc(value), field),
Suffix::Accessor(Accessor::RecordField(field)) => {
Expr::RecordAccess(arena.alloc(value), field)
}
Suffix::Accessor(Accessor::TupleIndex(field)) => {
Expr::TupleAccess(arena.alloc(value), field)
}
Suffix::TaskAwaitBang => Expr::TaskAwaitBang(arena.alloc(value)),
})
}

View file

@ -1,12 +1,12 @@
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;
use crate::ident::{lowercase_ident, UppercaseIdent};
use crate::parser::{
and, byte, loc, map_with_arena, skip_second, specialize_err, EPackageEntry, EPackageName,
Parser,
Parser, skip_first
};
use crate::parser::{optional, then};
use crate::string_literal;
@ -22,7 +22,7 @@ impl<'a> HeaderType<'a> {
}
| HeaderType::Hosted { exposes, .. }
| HeaderType::Builtin { exposes, .. }
| HeaderType::Interface { exposes, .. } => exposes,
| HeaderType::Module { exposes, .. } => exposes,
HeaderType::Platform { .. } | HeaderType::Package { .. } => &[],
}
}
@ -33,7 +33,7 @@ impl<'a> HeaderType<'a> {
HeaderType::Builtin { .. } => "builtin",
HeaderType::Package { .. } => "package",
HeaderType::Platform { .. } => "platform",
HeaderType::Interface { .. } => "interface",
HeaderType::Module { .. } => "module",
}
}
}
@ -41,7 +41,6 @@ impl<'a> HeaderType<'a> {
#[derive(Debug)]
pub enum HeaderType<'a> {
App {
output_name: StrLiteral<'a>,
provides: &'a [Loc<ExposedName<'a>>],
to_platform: To<'a>,
},
@ -76,7 +75,7 @@ pub enum HeaderType<'a> {
/// usually `pf`
config_shorthand: &'a str,
},
Interface {
Module {
name: ModuleName<'a>,
exposes: &'a [Loc<ExposedName<'a>>],
},
@ -85,14 +84,10 @@ pub enum HeaderType<'a> {
impl<'a> HeaderType<'a> {
pub fn get_name(self) -> Option<&'a str> {
match self {
Self::Interface { name, .. }
| Self::Builtin { name, .. }
| Self::Hosted { name, .. } => Some(name.into()),
Self::App {
output_name: StrLiteral::PlainLine(name),
..
Self::Module { name, .. } | Self::Builtin { name, .. } | Self::Hosted { name, .. } => {
Some(name.into())
}
| Self::Platform {
Self::Platform {
config_shorthand: name,
..
}
@ -106,6 +101,17 @@ impl<'a> HeaderType<'a> {
}
}
}
pub fn to_maybe_builtin(self, module_id: ModuleId) -> Self {
match self {
HeaderType::Module { name, exposes } if module_id.is_builtin() => HeaderType::Builtin {
name,
exposes,
generates_with: &[],
},
_ => self,
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
@ -159,7 +165,15 @@ impl<'a> From<ModuleName<'a>> for &'a str {
}
}
impl<'a> From<ModuleName<'a>> for roc_module::ident::ModuleName {
fn from(name: ModuleName<'a>) -> Self {
name.0.into()
}
}
impl<'a> ModuleName<'a> {
const MODULE_SEPARATOR: char = '.';
pub const fn new(name: &'a str) -> Self {
ModuleName(name)
}
@ -167,6 +181,10 @@ impl<'a> ModuleName<'a> {
pub const fn as_str(&'a self) -> &'a str {
self.0
}
pub fn parts(&'a self) -> impl DoubleEndedIterator<Item = &'a str> {
self.0.split(Self::MODULE_SEPARATOR)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
@ -207,7 +225,6 @@ macro_rules! keywords {
keywords! {
ExposesKeyword => "exposes",
ImportsKeyword => "imports",
WithKeyword => "with",
GeneratesKeyword => "generates",
PackageKeyword => "package",
@ -215,23 +232,37 @@ keywords! {
RequiresKeyword => "requires",
ProvidesKeyword => "provides",
ToKeyword => "to",
PlatformKeyword => "platform",
// Deprecated
ImportsKeyword => "imports",
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct KeywordItem<'a, K, V> {
pub keyword: Spaces<'a, K>,
pub item: V,
}
#[derive(Clone, Debug, PartialEq)]
pub struct InterfaceHeader<'a> {
pub before_name: &'a [CommentOrNewline<'a>],
pub name: Loc<ModuleName<'a>>,
pub struct ModuleHeader<'a> {
pub after_keyword: &'a [CommentOrNewline<'a>],
pub params: Option<ModuleParams<'a>>,
pub exposes: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
pub exposes: KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>>,
pub imports: KeywordItem<'a, ImportsKeyword, Collection<'a, Loc<Spaced<'a, ImportsEntry<'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>>>>;
#[derive(Clone, Debug, PartialEq)]
pub struct HostedHeader<'a> {
pub before_name: &'a [CommentOrNewline<'a>],
@ -253,14 +284,13 @@ pub enum To<'a> {
#[derive(Clone, Debug, PartialEq)]
pub struct AppHeader<'a> {
pub before_name: &'a [CommentOrNewline<'a>],
pub name: Loc<StrLiteral<'a>>,
pub packages:
Option<KeywordItem<'a, PackagesKeyword, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>>,
pub imports:
Option<KeywordItem<'a, ImportsKeyword, Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>>>,
pub provides: ProvidesTo<'a>,
pub before_provides: &'a [CommentOrNewline<'a>],
pub provides: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
pub before_packages: &'a [CommentOrNewline<'a>],
pub packages: Loc<Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>,
// Old header pieces
pub old_imports: Option<KeywordItem<'a, ImportsKeyword, ImportsCollection<'a>>>,
pub old_provides_to_new_package: Option<PackageName<'a>>,
}
#[derive(Clone, Debug, PartialEq)]
@ -275,12 +305,10 @@ pub struct ProvidesTo<'a> {
#[derive(Clone, Debug, PartialEq)]
pub struct PackageHeader<'a> {
pub before_name: &'a [CommentOrNewline<'a>],
pub name: Loc<PackageName<'a>>,
pub exposes: KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>>,
pub packages:
KeywordItem<'a, PackagesKeyword, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>,
pub before_exposes: &'a [CommentOrNewline<'a>],
pub exposes: Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>,
pub before_packages: &'a [CommentOrNewline<'a>],
pub packages: Loc<Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>,
}
#[derive(Clone, Debug, PartialEq)]
@ -336,6 +364,7 @@ pub struct TypedIdent<'a> {
pub struct PackageEntry<'a> {
pub shorthand: &'a str,
pub spaces_after_shorthand: &'a [CommentOrNewline<'a>],
pub platform_marker: Option<&'a [CommentOrNewline<'a>]>,
pub package_name: Loc<PackageName<'a>>,
}
@ -356,9 +385,15 @@ pub fn package_entry<'a>() -> impl Parser<'a, Spaced<'a, PackageEntry<'a>>, EPac
),
space0_e(EPackageEntry::IndentPackage),
)),
loc(specialize_err(EPackageEntry::BadPackage, package_name())),
and(
optional(skip_first(
crate::parser::keyword(crate::keyword::PLATFORM, EPackageEntry::Platform),
space0_e(EPackageEntry::IndentPackage)
)),
loc(specialize_err(EPackageEntry::BadPackage, package_name()))
)
),
move |arena, (opt_shorthand, package_or_path)| {
move |arena, (opt_shorthand, (platform_marker, package_or_path))| {
let entry = match opt_shorthand {
Some(((shorthand, spaces_before_colon), spaces_after_colon)) => PackageEntry {
shorthand,
@ -367,11 +402,13 @@ pub fn package_entry<'a>() -> impl Parser<'a, Spaced<'a, PackageEntry<'a>>, EPac
spaces_before_colon,
spaces_after_colon,
),
platform_marker,
package_name: package_or_path,
},
None => PackageEntry {
shorthand: "",
spaces_after_shorthand: &[],
platform_marker,
package_name: package_or_path,
},
};
@ -405,7 +442,7 @@ where
}
}
impl<'a> Malformed for InterfaceHeader<'a> {
impl<'a> Malformed for ModuleHeader<'a> {
fn is_malformed(&self) -> bool {
false
}
@ -419,7 +456,7 @@ impl<'a> Malformed for HostedHeader<'a> {
impl<'a> Malformed for AppHeader<'a> {
fn is_malformed(&self) -> bool {
self.name.is_malformed()
false
}
}

View file

@ -42,7 +42,6 @@ pub enum Ident<'a> {
Access {
module_name: &'a str,
parts: &'a [Accessor<'a>],
suffixed: u8,
},
/// `.foo { foo: 42 }` or `.1 (1, 2, 3)`
AccessorFunction(Accessor<'a>),
@ -193,12 +192,7 @@ pub fn parse_ident<'a>(
match chomp_identifier_chain(arena, state.bytes(), state.pos()) {
Ok((width, ident)) => {
let state = advance_state!(state, width as usize)?;
if let Ident::Access {
module_name,
parts,
suffixed,
} = ident
{
if let Ident::Access { module_name, parts } = ident {
if module_name.is_empty() {
if let Some(first) = parts.first() {
for keyword in crate::keyword::KEYWORDS.iter() {
@ -209,15 +203,7 @@ pub fn parse_ident<'a>(
}
}
return Ok((
MadeProgress,
Ident::Access {
module_name,
parts,
suffixed,
},
state,
));
return Ok((MadeProgress, Ident::Access { module_name, parts }, state));
}
Ok((MadeProgress, ident, state))
@ -388,6 +374,12 @@ impl<'a> Accessor<'a> {
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Suffix<'a> {
Accessor(Accessor<'a>),
TaskAwaitBang,
}
/// a `.foo` or `.1` accessor function
fn chomp_accessor(buffer: &[u8], pos: Position) -> Result<Accessor, BadIdent> {
// assumes the leading `.` has been chomped already
@ -528,22 +520,9 @@ fn chomp_identifier_chain<'a>(
chomped += width as usize;
// Parse any `!` suffixes
let mut suffixed = 0;
while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
if ch == '!' {
suffixed += 1;
chomped += width;
} else {
// we're done
break;
}
}
let ident = Ident::Access {
module_name,
parts: parts.into_bump_slice(),
suffixed,
};
Ok((chomped as u32, ident))
@ -578,22 +557,9 @@ fn chomp_identifier_chain<'a>(
// just one segment, starting with a lowercase letter; that's a normal identifier
let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) };
// Parse any `!` suffixes
let mut suffixed = 0;
while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
if ch == '!' {
suffixed += 1;
chomped += width;
} else {
// we're done
break;
}
}
let ident = Ident::Access {
module_name: "",
parts: arena.alloc([Accessor::RecordField(value)]),
suffixed,
};
Ok((chomped as u32, ident))

View file

@ -6,12 +6,21 @@ pub const WHEN: &str = "when";
pub const AS: &str = "as";
pub const IS: &str = "is";
pub const DBG: &str = "dbg";
pub const IMPORT: &str = "import";
pub const EXPECT: &str = "expect";
pub const EXPECT_FX: &str = "expect-fx";
pub const CRASH: &str = "crash";
// These keywords are valid in imports
pub const EXPOSING: &str = "exposing";
// These keywords are valid in types
pub const IMPLEMENTS: &str = "implements";
pub const WHERE: &str = "where";
pub const KEYWORDS: [&str; 10] = [IF, THEN, ELSE, WHEN, AS, IS, DBG, EXPECT, EXPECT_FX, CRASH];
// These keywords are valid in headers
pub const PLATFORM: &str = "platform";
pub const KEYWORDS: [&str; 11] = [
IF, THEN, ELSE, WHEN, AS, IS, DBG, IMPORT, EXPECT, EXPECT_FX, CRASH,
];

View file

@ -1,10 +1,12 @@
use crate::ast::{Collection, Defs, Header, Module, Spaced, Spaces};
use crate::ast::{Collection, CommentOrNewline, Defs, Header, Module, Spaced, Spaces};
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::expr::merge_spaces;
use crate::header::{
package_entry, package_name, AppHeader, ExposedName, ExposesKeyword, GeneratesKeyword,
HostedHeader, ImportsEntry, ImportsKeyword, InterfaceHeader, Keyword, KeywordItem, ModuleName,
PackageEntry, PackageHeader, PackagesKeyword, PlatformHeader, PlatformRequires,
ProvidesKeyword, ProvidesTo, RequiresKeyword, To, ToKeyword, TypedIdent, WithKeyword,
HostedHeader, ImportsCollection, ImportsEntry, ImportsKeyword, ImportsKeywordItem, Keyword,
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, *};
@ -12,12 +14,13 @@ use crate::parser::{
and, backtrackable, byte, collection_trailing_sep_e, increment_min_indent, loc, map, optional,
reset_min_indent, skip_first, skip_second, specialize_err, two_bytes, zero_or_more, EExposes,
EGenerates, EGeneratesWith, EHeader, EImports, EPackages, EProvides, ERequires, ETypedIdent,
Parser, SourceError, SpaceProblem, SyntaxError,
Parser, SourceError, SpaceProblem, SyntaxError, map_with_arena, succeed, EParams
};
use crate::pattern::record_pattern_fields;
use crate::state::State;
use crate::string_literal::{self, parse_str_literal};
use crate::type_annotation;
use roc_region::all::{Loc, Position};
use roc_region::all::{Loc, Position, Region};
fn end_of_file<'a>() -> impl Parser<'a, (), SyntaxError<'a>> {
|_arena, state: State<'a>, _min_indent: u32| {
@ -29,12 +32,19 @@ fn end_of_file<'a>() -> impl Parser<'a, (), SyntaxError<'a>> {
}
}
#[inline(always)]
pub fn module_defs<'a>() -> impl Parser<'a, Defs<'a>, SyntaxError<'a>> {
skip_second(
specialize_err(SyntaxError::Expr, crate::expr::toplevel_defs()),
end_of_file(),
)
pub fn parse_module_defs<'a>(
arena: &'a bumpalo::Bump,
state: State<'a>,
defs: Defs<'a>,
) -> Result<Defs<'a>, SyntaxError<'a>> {
let min_indent = 0;
match crate::expr::parse_top_level_defs(arena, state.clone(), defs) {
Ok((_, defs, state)) => match end_of_file().parse(arena, state, min_indent) {
Ok(_) => Ok(defs),
Err((_, fail)) => Err(fail),
},
Err((_, fail)) => Err(SyntaxError::Expr(fail, state.pos())),
}
}
pub fn parse_header<'a>(
@ -54,24 +64,31 @@ pub fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> {
record!(Module {
comments: space0_e(EHeader::IndentStart),
header: one_of![
map(
skip_first(
keyword("module", EHeader::Start),
increment_min_indent(module_header())
),
Header::Module
),
map(
skip_first(
keyword("interface", EHeader::Start),
increment_min_indent(interface_header())
),
Header::Interface
Header::Module
),
map(
skip_first(
keyword("app", EHeader::Start),
increment_min_indent(app_header())
increment_min_indent(one_of![app_header(), old_app_header()])
),
Header::App
),
map(
skip_first(
keyword("package", EHeader::Start),
increment_min_indent(package_header())
increment_min_indent(one_of![package_header(), old_package_header()])
),
Header::Package
),
@ -94,22 +111,82 @@ pub fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> {
}
#[inline(always)]
fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>, EHeader<'a>> {
record!(InterfaceHeader {
before_name: space0_e(EHeader::IndentStart),
name: loc(module_name_help(EHeader::ModuleName)),
exposes: specialize_err(EHeader::Exposes, exposes_values()),
imports: specialize_err(EHeader::Imports, imports()),
fn module_header<'a>() -> impl Parser<'a, ModuleHeader<'a>, EHeader<'a>> {
record!(ModuleHeader {
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),
})
}
// TODO does this need to be a macro?
macro_rules! merge_n_spaces {
($arena:expr, $($slice:expr),*) => {
{
let mut merged = bumpalo::collections::Vec::with_capacity_in(0 $(+ $slice.len())*, $arena);
$(merged.extend_from_slice($slice);)*
merged.into_bump_slice()
}
};
}
/// 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 after_keyword = map_with_arena(
and(
skip_second(
space0_e(EHeader::IndentStart),
loc(module_name_help(EHeader::ModuleName))
),
specialize_err(EHeader::Exposes, exposes_kw())
),
|arena: &'a bumpalo::Bump,
(before_name, kw): (&'a [CommentOrNewline<'a>], Spaces<'a, ExposesKeyword>)| {
merge_n_spaces!(arena, before_name, kw.before, kw.after)
}
);
record!(ModuleHeader {
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()),
imports_none_if_empty
)
.trace("imports"),
})
.trace("interface_header")
}
fn imports_none_if_empty(value: ImportsKeywordItem<'_>) -> Option<ImportsKeywordItem<'_>> {
if value.item.is_empty() {
None
} else {
Some(value)
}
}
#[inline(always)]
fn hosted_header<'a>() -> impl Parser<'a, HostedHeader<'a>, EHeader<'a>> {
record!(HostedHeader {
before_name: space0_e(EHeader::IndentStart),
name: loc(module_name_help(EHeader::ModuleName)),
exposes: specialize_err(EHeader::Exposes, exposes_values()),
exposes: specialize_err(EHeader::Exposes, exposes_values_kw()),
imports: specialize_err(EHeader::Imports, imports()),
generates: specialize_err(EHeader::Generates, generates()),
generates_with: specialize_err(EHeader::GeneratesWith, generates_with()),
@ -179,29 +256,194 @@ fn module_name<'a>() -> impl Parser<'a, ModuleName<'a>, ()> {
#[inline(always)]
fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> {
record!(AppHeader {
before_name: space0_e(EHeader::IndentStart),
name: loc(crate::parser::specialize_err(
EHeader::AppName,
string_literal::parse_str_literal()
)),
packages: optional(specialize_err(EHeader::Packages, packages())),
imports: optional(specialize_err(EHeader::Imports, imports())),
provides: specialize_err(EHeader::Provides, provides_to()),
before_provides: space0_e(EHeader::IndentStart),
provides: specialize_err(EHeader::Exposes, exposes_list()),
before_packages: space0_e(EHeader::IndentStart),
packages: specialize_err(EHeader::Packages, loc(packages_collection())),
old_imports: succeed(None),
old_provides_to_new_package: succeed(None),
})
.trace("app_header")
}
struct OldAppHeader<'a> {
pub before_name: &'a [CommentOrNewline<'a>],
pub packages: Option<Loc<OldAppPackages<'a>>>,
pub imports: Option<KeywordItem<'a, ImportsKeyword, ImportsCollection<'a>>>,
pub provides: ProvidesTo<'a>,
}
type OldAppPackages<'a> =
KeywordItem<'a, PackagesKeyword, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>;
#[inline(always)]
fn old_app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> {
let old = record!(OldAppHeader {
before_name: skip_second(
space0_e(EHeader::IndentStart),
loc(crate::parser::specialize_err(
EHeader::AppName,
string_literal::parse_str_literal()
))
),
packages: optional(specialize_err(EHeader::Packages, loc(packages()))),
imports: optional(specialize_err(EHeader::Imports, imports())),
provides: specialize_err(EHeader::Provides, provides_to()),
});
map_with_arena(old, |arena: &'a bumpalo::Bump, old: OldAppHeader<'a>| {
let mut before_packages: &'a [CommentOrNewline] = &[];
let packages = match old.packages {
Some(packages) => {
before_packages = merge_spaces(
arena,
packages.value.keyword.before,
packages.value.keyword.after,
);
if let To::ExistingPackage(platform_shorthand) = old.provides.to.value {
packages.map(|coll| {
coll.item.map_items(arena, |loc_spaced_pkg| {
if loc_spaced_pkg.value.item().shorthand == platform_shorthand {
loc_spaced_pkg.map(|spaced_pkg| {
spaced_pkg.map(arena, |pkg| {
let mut new_pkg = *pkg;
new_pkg.platform_marker = Some(merge_spaces(
arena,
old.provides.to_keyword.before,
old.provides.to_keyword.after,
));
new_pkg
})
})
} else {
*loc_spaced_pkg
}
})
})
} else {
packages.map(|kw| kw.item)
}
}
None => Loc {
region: Region::zero(),
value: Collection::empty(),
},
};
let provides = match old.provides.types {
Some(types) => {
let mut combined_items = bumpalo::collections::Vec::with_capacity_in(
old.provides.entries.items.len() + types.items.len(),
arena,
);
combined_items.extend_from_slice(old.provides.entries.items);
for loc_spaced_type_ident in types.items {
combined_items.push(loc_spaced_type_ident.map(|spaced_type_ident| {
spaced_type_ident.map(arena, |type_ident| {
ExposedName::new(From::from(*type_ident))
})
}));
}
let value_comments = old.provides.entries.final_comments();
let type_comments = types.final_comments();
let mut combined_comments = bumpalo::collections::Vec::with_capacity_in(
value_comments.len() + type_comments.len(),
arena,
);
combined_comments.extend_from_slice(value_comments);
combined_comments.extend_from_slice(type_comments);
Collection::with_items_and_comments(
arena,
combined_items.into_bump_slice(),
combined_comments.into_bump_slice(),
)
}
None => old.provides.entries,
};
AppHeader {
before_provides: merge_spaces(
arena,
old.before_name,
old.provides.provides_keyword.before,
),
provides,
before_packages: merge_spaces(
arena,
before_packages,
old.provides.provides_keyword.after,
),
packages,
old_imports: old.imports.and_then(imports_none_if_empty),
old_provides_to_new_package: match old.provides.to.value {
To::NewPackage(new_pkg) => Some(new_pkg),
To::ExistingPackage(_) => None,
},
}
})
}
#[inline(always)]
fn package_header<'a>() -> impl Parser<'a, PackageHeader<'a>, EHeader<'a>> {
record!(PackageHeader {
before_name: space0_e(EHeader::IndentStart),
name: loc(specialize_err(EHeader::PackageName, package_name())),
exposes: specialize_err(EHeader::Exposes, exposes_modules()),
packages: specialize_err(EHeader::Packages, packages()),
before_exposes: space0_e(EHeader::IndentStart),
exposes: specialize_err(EHeader::Exposes, exposes_module_collection()),
before_packages: space0_e(EHeader::IndentStart),
packages: specialize_err(EHeader::Packages, loc(packages_collection())),
})
.trace("package_header")
}
#[derive(Debug, Clone, PartialEq)]
struct OldPackageHeader<'a> {
before_name: &'a [CommentOrNewline<'a>],
exposes: KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>>,
packages:
Loc<KeywordItem<'a, PackagesKeyword, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>>>,
}
#[inline(always)]
fn old_package_header<'a>() -> impl Parser<'a, PackageHeader<'a>, EHeader<'a>> {
map_with_arena(
record!(OldPackageHeader {
before_name: skip_second(
space0_e(EHeader::IndentStart),
specialize_err(EHeader::PackageName, package_name())
),
exposes: specialize_err(EHeader::Exposes, exposes_modules()),
packages: specialize_err(EHeader::Packages, loc(packages())),
}),
|arena: &'a bumpalo::Bump, old: OldPackageHeader<'a>| {
let before_exposes = merge_n_spaces!(
arena,
old.before_name,
old.exposes.keyword.before,
old.exposes.keyword.after
);
let before_packages = merge_spaces(
arena,
old.packages.value.keyword.before,
old.packages.value.keyword.after,
);
PackageHeader {
before_exposes,
exposes: old.exposes.item,
before_packages,
packages: old.packages.map(|kw| kw.item),
}
}
)
.trace("old_package_header")
}
#[inline(always)]
fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> {
record!(PlatformHeader {
@ -382,29 +624,40 @@ fn requires_typed_ident<'a>() -> impl Parser<'a, Loc<Spaced<'a, TypedIdent<'a>>>
}
#[inline(always)]
fn exposes_values<'a>() -> impl Parser<
fn exposes_values_kw<'a>() -> impl Parser<
'a,
KeywordItem<'a, ExposesKeyword, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>>,
EExposes,
> {
record!(KeywordItem {
keyword: spaces_around_keyword(
ExposesKeyword,
EExposes::Exposes,
EExposes::IndentExposes,
EExposes::IndentListStart
),
item: collection_trailing_sep_e(
byte(b'[', EExposes::ListStart),
exposes_entry(EExposes::Identifier),
byte(b',', EExposes::ListEnd),
byte(b']', EExposes::ListEnd),
Spaced::SpaceBefore
)
keyword: exposes_kw(),
item: exposes_list()
})
}
fn spaces_around_keyword<'a, K: Keyword, E>(
#[inline(always)]
fn exposes_kw<'a>() -> impl Parser<'a, Spaces<'a, ExposesKeyword>, EExposes> {
spaces_around_keyword(
ExposesKeyword,
EExposes::Exposes,
EExposes::IndentExposes,
EExposes::IndentListStart,
)
}
#[inline(always)]
fn exposes_list<'a>() -> impl Parser<'a, Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>, EExposes>
{
collection_trailing_sep_e(
byte(b'[', EExposes::ListStart),
exposes_entry(EExposes::Identifier),
byte(b',', EExposes::ListEnd),
byte(b']', EExposes::ListEnd),
Spaced::SpaceBefore
)
}
pub fn spaces_around_keyword<'a, K: Keyword, E>(
keyword_item: K,
expectation: fn(Position) -> E,
indent_problem1: fn(Position) -> E,
@ -445,16 +698,21 @@ fn exposes_modules<'a>() -> impl Parser<
EExposes::IndentExposes,
EExposes::IndentListStart
),
item: collection_trailing_sep_e(
byte(b'[', EExposes::ListStart),
exposes_module(EExposes::Identifier),
byte(b',', EExposes::ListEnd),
byte(b']', EExposes::ListEnd),
Spaced::SpaceBefore
),
item: exposes_module_collection(),
})
}
fn exposes_module_collection<'a>(
) -> impl Parser<'a, Collection<'a, Loc<Spaced<'a, ModuleName<'a>>>>, EExposes> {
collection_trailing_sep_e(
byte(b'[', EExposes::ListStart),
exposes_module(EExposes::Identifier),
byte(b',', EExposes::ListEnd),
byte(b']', EExposes::ListEnd),
Spaced::SpaceBefore
)
}
fn exposes_module<'a, F, E>(
to_expectation: F,
) -> impl Parser<'a, Loc<Spaced<'a, ModuleName<'a>>>, E>
@ -476,22 +734,33 @@ fn packages<'a>() -> impl Parser<
EPackages<'a>,
> {
record!(KeywordItem {
keyword: spaces_around_keyword(
PackagesKeyword,
EPackages::Packages,
EPackages::IndentPackages,
EPackages::IndentListStart
),
item: collection_trailing_sep_e(
byte(b'{', EPackages::ListStart),
specialize_err(EPackages::PackageEntry, loc(package_entry())),
byte(b',', EPackages::ListEnd),
byte(b'}', EPackages::ListEnd),
Spaced::SpaceBefore
)
keyword: packages_kw(),
item: packages_collection()
})
}
#[inline(always)]
fn packages_kw<'a>() -> impl Parser<'a, Spaces<'a, PackagesKeyword>, EPackages<'a>> {
spaces_around_keyword(
PackagesKeyword,
EPackages::Packages,
EPackages::IndentPackages,
EPackages::IndentListStart,
)
}
#[inline(always)]
fn packages_collection<'a>(
) -> impl Parser<'a, Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>, EPackages<'a>> {
collection_trailing_sep_e(
byte(b'{', EPackages::ListStart),
specialize_err(EPackages::PackageEntry, loc(package_entry())),
byte(b',', EPackages::ListEnd),
byte(b'}', EPackages::ListEnd),
Spaced::SpaceBefore
)
}
#[inline(always)]
fn generates<'a>(
) -> impl Parser<'a, KeywordItem<'a, GeneratesKeyword, UppercaseIdent<'a>>, EGenerates> {
@ -554,7 +823,7 @@ fn imports<'a>() -> impl Parser<
}
#[inline(always)]
fn typed_ident<'a>() -> impl Parser<'a, Spaced<'a, TypedIdent<'a>>, ETypedIdent<'a>> {
pub fn typed_ident<'a>() -> impl Parser<'a, Spaced<'a, TypedIdent<'a>>, ETypedIdent<'a>> {
// e.g.
//
// printLine : Str -> Effect {}
@ -592,7 +861,7 @@ fn shortname<'a>() -> impl Parser<'a, &'a str, EImports> {
specialize_err(|_, pos| EImports::Shorthand(pos), lowercase_ident())
}
fn module_name_help<'a, F, E>(to_expectation: F) -> impl Parser<'a, ModuleName<'a>, E>
pub fn module_name_help<'a, F, E>(to_expectation: F) -> impl Parser<'a, ModuleName<'a>, E>
where
F: Fn(Position) -> E,
E: 'a,
@ -608,6 +877,21 @@ fn imports_entry<'a>() -> impl Parser<'a, Spaced<'a, ImportsEntry<'a>>, EImports
Option<Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>>,
);
let spaced_import =
|((opt_shortname, module_name), opt_values): Temp<'a>| {
let exposed_values = opt_values.unwrap_or_else(Collection::empty);
let entry = match opt_shortname {
Some(shortname) => {
ImportsEntry::Package(shortname, module_name, exposed_values)
}
None => ImportsEntry::Module(module_name, exposed_values),
};
Spaced::Item(entry)
};
one_of!(
map(
and(
@ -632,19 +916,7 @@ fn imports_entry<'a>() -> impl Parser<'a, Spaced<'a, ImportsEntry<'a>>, EImports
)
))
),
|((opt_shortname, module_name), opt_values): Temp<'a>| {
let exposed_values = opt_values.unwrap_or_else(Collection::empty);
let entry = match opt_shortname {
Some(shortname) => {
ImportsEntry::Package(shortname, module_name, exposed_values)
}
None => ImportsEntry::Module(module_name, exposed_values),
};
Spaced::Item(entry)
}
spaced_import
)
.trace("normal_import"),
map(

View file

@ -87,7 +87,10 @@ impl_space_problem! {
EGeneratesWith,
EHeader<'a>,
EIf<'a>,
EImport<'a>,
EParams<'a>,
EImports,
EImportParams<'a>,
EInParens<'a>,
EClosure<'a>,
EList<'a>,
@ -114,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),
@ -148,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),
@ -210,6 +223,8 @@ pub enum EPackageEntry<'a> {
Shorthand(Position),
Colon(Position),
IndentPackage(Position),
IndentPlatform(Position),
Platform(Position),
Space(BadInputError, Position),
}
@ -346,6 +361,7 @@ pub enum EExpr<'a> {
Expect(EExpect<'a>, Position),
Dbg(EExpect<'a>, Position),
Import(EImport<'a>, Position),
Closure(EClosure<'a>, Position),
Underscore(Position),
@ -519,6 +535,45 @@ pub enum EExpect<'a> {
IndentCondition(Position),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EImport<'a> {
Import(Position),
IndentStart(Position),
PackageShorthand(Position),
PackageShorthandDot(Position),
ModuleName(Position),
Params(EImportParams<'a>, Position),
IndentAs(Position),
As(Position),
IndentAlias(Position),
Alias(Position),
LowercaseAlias(Region),
IndentExposing(Position),
Exposing(Position),
ExposingListStart(Position),
ExposedName(Position),
ExposingListEnd(Position),
IndentIngestedPath(Position),
IngestedPath(Position),
IndentIngestedName(Position),
IngestedName(Position),
IndentColon(Position),
Colon(Position),
IndentAnnotation(Position),
Annotation(EType<'a>, Position),
Space(BadInputError, Position),
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),
@ -1684,7 +1739,7 @@ macro_rules! record {
/// Similar to [`skip_first`], but we modify the `min_indent` of the second
/// parser (`parser`) to be 1 greater than the `line_indent()` at the start of
/// the first parser (`before`).
pub fn indented_seq<'a, O, E: 'a>(
pub fn indented_seq_skip_first<'a, O, E: 'a>(
before: impl Parser<'a, (), E>,
parser: impl Parser<'a, O, E>,
) -> impl Parser<'a, O, E> {
@ -1710,6 +1765,35 @@ pub fn indented_seq<'a, O, E: 'a>(
}
}
/// Similar to `and`, but we modify the min_indent of the second parser to be
/// 1 greater than the line_indent() at the start of the first parser.
pub fn indented_seq<'a, Output1, Output2, E: 'a>(
p1: impl Parser<'a, Output1, E>,
p2: impl Parser<'a, Output2, E>,
) -> impl Parser<'a, (Output1, Output2), E> {
move |arena: &'a bumpalo::Bump, state: crate::state::State<'a>, _min_indent: u32| {
let start_indent = state.line_indent();
// TODO: we should account for min_indent here, but this doesn't currently work
// because min_indent is sometimes larger than it really should be, which is in turn
// due to uses of `increment_indent`.
//
// let p1_indent = std::cmp::max(start_indent, min_indent);
let p1_indent = start_indent;
let p2_indent = p1_indent + 1;
match p1.parse(arena, state, p1_indent) {
Ok((p1, out1, state)) => match p2.parse(arena, state, p2_indent) {
Ok((p2, out2, state)) => Ok((p1.or(p2), (out1, out2), state)),
Err((p2, fail)) => Err((p1.or(p2), fail)),
},
Err((progress, fail)) => Err((progress, fail)),
}
}
}
/// Similar to [`and`], but we modify the `min_indent` of the second parser to be
/// 1 greater than the `column()` at the start of the first parser.
pub fn absolute_indented_seq<'a, Output1, Output2, E: 'a>(

View file

@ -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;
@ -13,7 +13,6 @@ use crate::string_literal::StrLikeLiteral;
use bumpalo::collections::string::String;
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_error_macros::internal_error;
use roc_region::all::{Loc, Region};
/// Different patterns are supported in different circumstances.
@ -51,14 +50,6 @@ pub fn loc_pattern_help<'a>() -> impl Parser<'a, Loc<Pattern<'a>>, EPattern<'a>>
let pattern_state = state.clone();
// Return early with the suffixed statement
match pattern.value {
Pattern::Identifier { suffixed, .. } if suffixed > 0 => {
return Ok((MadeProgress, pattern, pattern_state))
}
_ => {}
}
let (pattern_spaces, state) =
match space0_e(EPattern::AsKeyword).parse(arena, state, min_indent) {
Err(_) => return Ok((MadeProgress, pattern, pattern_state)),
@ -390,46 +381,6 @@ fn loc_ident_pattern_help<'a>(
Ok((MadeProgress, loc_pat, state))
}
}
// Parse a statement that begins with a suffixed identifier, e.g. `Stdout.line! "Hello"`
Ident::Access {
module_name,
parts,
suffixed,
..
} if suffixed > 0 => {
if module_name.is_empty() && parts.len() == 1 {
if let Accessor::RecordField(var) = &parts[0] {
Ok((
MadeProgress,
Loc {
region: loc_ident.region,
value: Pattern::Identifier {
ident: var,
suffixed,
},
},
state,
))
} else {
internal_error!("unexpected suffixed TupleIndex");
}
} else if let Accessor::RecordField(var) = &parts[0] {
return Ok((
MadeProgress,
Loc {
region: loc_ident.region,
value: Pattern::QualifiedIdentifier {
module_name,
ident: var,
suffixed,
},
},
state,
));
} else {
internal_error!("unexpected suffixed TupleIndex");
}
}
Ident::Access {
module_name, parts, ..
} => {
@ -448,10 +399,7 @@ fn loc_ident_pattern_help<'a>(
MadeProgress,
Loc {
region: loc_ident.region,
value: Pattern::Identifier {
ident: var,
suffixed: 0,
},
value: Pattern::Identifier { ident: var },
},
state,
));
@ -517,15 +465,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
)
}
@ -612,18 +562,9 @@ fn record_pattern_field<'a>() -> impl Parser<'a, Loc<Pattern<'a>>, PRecord<'a>>
None => {
let Loc { value, region } = loc_label;
let value = if !spaces.is_empty() {
Pattern::SpaceAfter(
arena.alloc(Pattern::Identifier {
ident: value,
suffixed: 0,
}),
spaces,
)
Pattern::SpaceAfter(arena.alloc(Pattern::Identifier { ident: value }), spaces)
} else {
Pattern::Identifier {
ident: value,
suffixed: 0,
}
Pattern::Identifier { ident: value }
};
Ok((MadeProgress, Loc::at(region, value), state))

View file

@ -1,7 +1,6 @@
use crate::ast;
use crate::ast::Defs;
use crate::module::module_defs;
use crate::parser::Parser;
use crate::module::parse_module_defs;
use crate::parser::SourceError;
use crate::parser::SyntaxError;
use crate::state::State;
@ -34,12 +33,7 @@ pub fn parse_loc_with<'a>(
pub fn parse_defs_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Defs<'a>, SyntaxError<'a>> {
let state = State::new(input.trim().as_bytes());
let min_indent = 0;
match module_defs().parse(arena, state, min_indent) {
Ok(tuple) => Ok(tuple.1),
Err(tuple) => Err(tuple.1),
}
parse_module_defs(arena, state, Defs::default())
}
pub fn parse_header_with<'a>(

View file

@ -11,7 +11,7 @@ use crate::keyword;
use crate::parser::{
absolute_column_min_indent, and, collection_trailing_sep_e, either, increment_min_indent, loc,
map, map_with_arena, skip_first, skip_second, succeed, then, zero_or_more, ERecord,
ETypeAbilityImpl,
ETypeAbilityImpl, indented_seq
};
use crate::parser::{
allocated, backtrackable, byte, fail, optional, specialize_err, specialize_err_ref, two_bytes,
@ -72,13 +72,7 @@ fn check_type_alias<'a>(
var_names.reserve(vars.len());
for var in vars {
if let TypeAnnotation::BoundVariable(v) = var.value {
var_names.push(Loc::at(
var.region,
Pattern::Identifier {
ident: v,
suffixed: 0,
},
));
var_names.push(Loc::at(var.region, Pattern::Identifier { ident: v }));
} else {
return Err(ETypeInlineAlias::ArgumentNotLowercase(var.region.start()));
}
@ -400,7 +394,7 @@ fn record_type<'a>(
fn applied_type<'a>(stop_at_surface_has: bool) -> impl Parser<'a, TypeAnnotation<'a>, EType<'a>> {
map(
and(
indented_seq(
specialize_err(EType::TApply, concrete_type()),
// Optionally parse space-separated arguments for the constructor,
// e.g. `Str Float` in `Map Str Float`

View file

@ -22,8 +22,8 @@ mod test_parse {
use roc_parse::ast::StrSegment::*;
use roc_parse::ast::{self, EscapedChar};
use roc_parse::ast::{CommentOrNewline, StrLiteral::*};
use roc_parse::module::module_defs;
use roc_parse::parser::{Parser, SyntaxError};
use roc_parse::module::parse_module_defs;
use roc_parse::parser::SyntaxError;
use roc_parse::state::State;
use roc_parse::test_helpers::parse_expr_with;
use roc_region::all::{Loc, Region};
@ -175,7 +175,6 @@ mod test_parse {
let expr = arena.alloc(Var {
module_name: "",
ident: "name",
suffixed: 0,
});
bumpalo::vec![in arena;
@ -192,7 +191,6 @@ mod test_parse {
let expr = arena.alloc(Var {
module_name: "",
ident: "name",
suffixed: 0,
});
bumpalo::vec![in arena;
@ -238,7 +236,6 @@ mod test_parse {
let expr = arena.alloc(Var {
module_name: "",
ident: "name",
suffixed: 0,
});
bumpalo::vec![in arena;
@ -254,13 +251,11 @@ mod test_parse {
let expr1 = arena.alloc(Var {
module_name: "",
ident: "name",
suffixed: 0,
});
let expr2 = arena.alloc(Var {
module_name: "",
ident: "project",
suffixed: 0,
});
bumpalo::vec![in arena;
@ -281,13 +276,11 @@ mod test_parse {
let expr1 = arena.alloc(Var {
module_name: "",
ident: "name",
suffixed: 0,
});
let expr2 = arena.alloc(Var {
module_name: "",
ident: "project",
suffixed: 0,
});
bumpalo::vec![in arena;
@ -352,9 +345,7 @@ mod test_parse {
List.map list isTest
"
);
let actual = module_defs()
.parse(&arena, State::new(src.as_bytes()), 0)
.map(|tuple| tuple.1);
let actual = parse_module_defs(&arena, State::new(src.as_bytes()), ast::Defs::default());
// It should occur twice in the debug output - once for the pattern,
// and then again for the lookup.
@ -378,13 +369,12 @@ mod test_parse {
);
let state = State::new(src.as_bytes());
let parser = module_defs();
let parsed = parser.parse(arena, state, 0);
let parsed = parse_module_defs(arena, state, ast::Defs::default());
match parsed {
Ok((_, _, _state)) => {
Ok(_) => {
// dbg!(_state);
}
Err((_, _fail)) => {
Err(_) => {
// dbg!(_fail, _state);
panic!("Failed to parse!");
}