mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 19:58:18 +00:00
Merge remote-tracking branch 'upstream/main' into unmacro_parsers
This commit is contained in:
commit
ab217ede3f
680 changed files with 15718 additions and 14181 deletions
|
@ -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());
|
||||
})
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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,
|
||||
];
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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>(
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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>(
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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!");
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue