mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 11:52:19 +00:00
Merge branch 'main' into rust-1-76-0-upgrade
This commit is contained in:
commit
1a5e065055
249 changed files with 7985 additions and 4219 deletions
|
@ -7,6 +7,7 @@ use crate::parser::ESingleQuote;
|
|||
use bumpalo::collections::{String, Vec};
|
||||
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_region::all::{Loc, Position, Region};
|
||||
|
||||
|
@ -267,9 +268,6 @@ pub enum Expr<'a> {
|
|||
// Collection Literals
|
||||
List(Collection<'a, &'a Loc<Expr<'a>>>),
|
||||
|
||||
/// An expression followed by `!``
|
||||
Suffixed(&'a Expr<'a>),
|
||||
|
||||
RecordUpdate {
|
||||
update: &'a Loc<Expr<'a>>,
|
||||
fields: Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>,
|
||||
|
@ -289,6 +287,7 @@ pub enum Expr<'a> {
|
|||
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),
|
||||
|
@ -306,6 +305,11 @@ pub enum Expr<'a> {
|
|||
Closure(&'a [Loc<Pattern<'a>>], &'a Loc<Expr<'a>>),
|
||||
/// Multiple defs in a row
|
||||
Defs(&'a Defs<'a>, &'a Loc<Expr<'a>>),
|
||||
|
||||
/// Used in place of an expression when the final expression is empty
|
||||
/// This may happen if the final expression is actually a suffixed statement
|
||||
EmptyDefsFinal,
|
||||
|
||||
Backpassing(&'a [Loc<Pattern<'a>>], &'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>),
|
||||
Expect(&'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>),
|
||||
Dbg(&'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>),
|
||||
|
@ -341,6 +345,7 @@ pub enum Expr<'a> {
|
|||
// Problems
|
||||
MalformedIdent(&'a str, crate::ident::BadIdent),
|
||||
MalformedClosure,
|
||||
MalformedSuffixed(&'a Loc<Expr<'a>>),
|
||||
// Both operators were non-associative, e.g. (True == False == False).
|
||||
// We should tell the author to disambiguate by grouping them with parens.
|
||||
PrecedenceConflict(&'a PrecedenceConflict<'a>),
|
||||
|
@ -348,6 +353,110 @@ pub enum Expr<'a> {
|
|||
UnappliedRecordBuilder(&'a Loc<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) => {
|
||||
let mut region = last.region;
|
||||
|
||||
for (loc_expr, _) in firsts.iter() {
|
||||
region = Region::span_across(&loc_expr.region, ®ion);
|
||||
}
|
||||
|
||||
region
|
||||
}
|
||||
_ => internal_error!("other expr types not supported"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn split_loc_exprs_around<'a>(
|
||||
items: &'a [&Loc<Expr<'a>>],
|
||||
index: usize,
|
||||
) -> (&'a [&'a Loc<Expr<'a>>], &'a [&'a Loc<Expr<'a>>]) {
|
||||
let (before, rest) = items.split_at(index);
|
||||
let after = &rest[1..]; // Skip the index element
|
||||
|
||||
(before, after)
|
||||
}
|
||||
|
||||
pub fn is_loc_expr_suffixed(loc_expr: &Loc<Expr>) -> bool {
|
||||
match loc_expr.value.extract_spaces().item {
|
||||
// expression without arguments, `read!`
|
||||
Expr::Var { suffixed, .. } => suffixed > 0,
|
||||
|
||||
// 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));
|
||||
|
||||
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
|
||||
.iter()
|
||||
.any(|(chain_loc_expr, _)| is_loc_expr_suffixed(chain_loc_expr));
|
||||
|
||||
is_expr_suffixed || any_chain_suffixed
|
||||
}
|
||||
|
||||
// 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_loc_expr_suffixed(final_else) || 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))
|
||||
}
|
||||
|
||||
// expression in a closure
|
||||
Expr::Closure(_, sub_loc_expr) => is_loc_expr_suffixed(sub_loc_expr),
|
||||
|
||||
// expressions inside a Defs
|
||||
// note we ignore the final expression as it should not be suffixed
|
||||
Expr::Defs(defs, _) => {
|
||||
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),
|
||||
_ => false,
|
||||
},
|
||||
});
|
||||
|
||||
any_defs_suffixed
|
||||
}
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn split_around<T>(items: &[T], target: usize) -> (&[T], &[T]) {
|
||||
let (before, rest) = items.split_at(target);
|
||||
let after = &rest[1..];
|
||||
|
||||
(before, after)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct PrecedenceConflict<'a> {
|
||||
pub whole_region: Region,
|
||||
|
@ -458,6 +567,18 @@ pub enum ValueDef<'a> {
|
|||
condition: &'a Loc<Expr<'a>>,
|
||||
preceding_comment: Region,
|
||||
},
|
||||
|
||||
Stmt(&'a Loc<Expr<'a>>),
|
||||
}
|
||||
|
||||
impl<'a> ValueDef<'a> {
|
||||
pub fn replace_expr(&mut self, new_expr: &'a Loc<Expr<'a>>) {
|
||||
match self {
|
||||
ValueDef::Body(_, expr) => *expr = new_expr,
|
||||
ValueDef::AnnotatedBody { body_expr, .. } => *body_expr = new_expr,
|
||||
_ => internal_error!("replacing expr in unsupported ValueDef"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
|
@ -487,6 +608,16 @@ impl<'a> Defs<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn list_value_defs(&self) -> impl Iterator<Item = (usize, &ValueDef<'a>)> {
|
||||
self.tags
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(tag_index, tag)| match tag.split() {
|
||||
Ok(_) => None,
|
||||
Err(value_index) => Some((tag_index, &self.value_defs[value_index.index()])),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn last(&self) -> Option<Result<&TypeDef<'a>, &ValueDef<'a>>> {
|
||||
self.tags.last().map(|tag| match tag.split() {
|
||||
Ok(type_index) => Ok(&self.type_defs[type_index.index()]),
|
||||
|
@ -494,6 +625,47 @@ impl<'a> Defs<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
// We could have a type annotation as the last tag,
|
||||
// this helper ensures we refer to the last value_def
|
||||
// and that we remove the correct tag
|
||||
pub fn last_value_suffixed(&self) -> Option<(Self, &'a Loc<Expr<'a>>)> {
|
||||
let value_indexes =
|
||||
self.tags
|
||||
.clone()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(|(tag_index, tag)| match tag.split() {
|
||||
Ok(_) => None,
|
||||
Err(value_index) => Some((tag_index, value_index.index())),
|
||||
});
|
||||
|
||||
if let Some((tag_index, value_index)) = value_indexes.last() {
|
||||
match self.value_defs[value_index] {
|
||||
ValueDef::Body(
|
||||
Loc {
|
||||
value: Pattern::RecordDestructure(collection),
|
||||
..
|
||||
},
|
||||
loc_expr,
|
||||
) if collection.is_empty() && is_loc_expr_suffixed(loc_expr) => {
|
||||
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) => {
|
||||
let mut new_defs = self.clone();
|
||||
new_defs.remove_value_def(tag_index);
|
||||
|
||||
return Some((new_defs, loc_expr));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn remove_value_def(&mut self, index: usize) {
|
||||
match self
|
||||
.tags
|
||||
|
@ -567,6 +739,7 @@ impl<'a> Defs<'a> {
|
|||
self.push_def_help(tag, region, spaces_before, spaces_after)
|
||||
}
|
||||
|
||||
/// Replace the `value_def` at the given index
|
||||
pub fn replace_with_value_def(
|
||||
&mut self,
|
||||
index: usize,
|
||||
|
@ -592,51 +765,81 @@ impl<'a> Defs<'a> {
|
|||
self.push_def_help(tag, region, spaces_before, spaces_after)
|
||||
}
|
||||
|
||||
// Find the first definition that is a Apply Suffixed
|
||||
// We need the tag_index so we can use it to remove the value
|
||||
// We need the value index to know if it is the first
|
||||
pub fn search_suffixed_defs(&self) -> Option<(usize, usize)> {
|
||||
for (tag_index, tag) in self.tags.iter().enumerate() {
|
||||
if let Err(value_index) = tag.split() {
|
||||
let index = value_index.index();
|
||||
/// Split the defs around a given target index
|
||||
///
|
||||
/// This is useful for unwrapping suffixed `!`
|
||||
pub fn split_defs_around(&self, target: usize) -> SplitDefsAround<'a> {
|
||||
let mut before = Defs::default();
|
||||
let mut after = Defs::default();
|
||||
|
||||
if let ValueDef::Body(_, expr) = &self.value_defs[index] {
|
||||
// The Suffixed has arguments applied e.g. `Stdout.line! "Hello World"`
|
||||
if let Expr::Apply(sub_expr, _, _) = expr.value {
|
||||
if let Expr::Suffixed(_) = sub_expr.value {
|
||||
return Some((tag_index, index));
|
||||
for (tag_index, tag) in self.tags.iter().enumerate() {
|
||||
let region = self.regions[tag_index];
|
||||
let space_before = {
|
||||
let start = self.space_before[tag_index].start();
|
||||
let len = self.space_before[tag_index].len();
|
||||
|
||||
&self.spaces[start..(start + len)]
|
||||
};
|
||||
let space_after = {
|
||||
let start = self.space_after[tag_index].start();
|
||||
let len = self.space_after[tag_index].len();
|
||||
|
||||
&self.spaces[start..(start + len)]
|
||||
};
|
||||
|
||||
match tag.split() {
|
||||
Ok(type_def_index) => {
|
||||
let type_def = self.type_defs[type_def_index.index()];
|
||||
|
||||
match tag_index.cmp(&target) {
|
||||
std::cmp::Ordering::Less => {
|
||||
// before
|
||||
let type_def_index = Index::push_new(&mut before.type_defs, type_def);
|
||||
let tag = EitherIndex::from_left(type_def_index);
|
||||
before.push_def_help(tag, region, space_before, space_after);
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
// after
|
||||
let type_def_index = Index::push_new(&mut after.type_defs, type_def);
|
||||
let tag = EitherIndex::from_left(type_def_index);
|
||||
after.push_def_help(tag, region, space_before, space_after);
|
||||
}
|
||||
std::cmp::Ordering::Equal => {
|
||||
// target, do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(value_def_index) => {
|
||||
let value_def = self.value_defs[value_def_index.index()];
|
||||
|
||||
// The Suffixed has NO arguments applied e.g. `Stdin.line!`
|
||||
if let Expr::Suffixed(_) = expr.value {
|
||||
return Some((tag_index, index));
|
||||
match tag_index.cmp(&target) {
|
||||
std::cmp::Ordering::Less => {
|
||||
// before
|
||||
let new_value_def_index =
|
||||
Index::push_new(&mut before.value_defs, value_def);
|
||||
let tag = EitherIndex::from_right(new_value_def_index);
|
||||
before.push_def_help(tag, region, space_before, space_after);
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
// after
|
||||
let new_value_def_index =
|
||||
Index::push_new(&mut after.value_defs, value_def);
|
||||
let tag = EitherIndex::from_right(new_value_def_index);
|
||||
after.push_def_help(tag, region, space_before, space_after);
|
||||
}
|
||||
std::cmp::Ordering::Equal => {
|
||||
// target, do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
// For desugaring Suffixed Defs we need to split the defs around the Suffixed value
|
||||
pub fn split_values_either_side_of(&self, index: usize) -> SplitDefsAround {
|
||||
let mut before = self.clone();
|
||||
let mut after = self.clone();
|
||||
|
||||
before.tags = self.tags[0..index].to_vec();
|
||||
|
||||
if index >= self.tags.len() {
|
||||
after.tags = self.tags.clone();
|
||||
after.tags.clear();
|
||||
} else {
|
||||
after.tags = self.tags[(index + 1)..].to_vec();
|
||||
}
|
||||
|
||||
SplitDefsAround { before, after }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct SplitDefsAround<'a> {
|
||||
pub before: Defs<'a>,
|
||||
pub after: Defs<'a>,
|
||||
|
@ -871,7 +1074,15 @@ impl<'a> PatternAs<'a> {
|
|||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum Pattern<'a> {
|
||||
// Identifier
|
||||
Identifier(&'a str),
|
||||
Identifier {
|
||||
ident: &'a str,
|
||||
suffixed: u8,
|
||||
},
|
||||
QualifiedIdentifier {
|
||||
module_name: &'a str,
|
||||
ident: &'a str,
|
||||
suffixed: u8,
|
||||
},
|
||||
|
||||
Tag(&'a str),
|
||||
|
||||
|
@ -923,10 +1134,6 @@ pub enum Pattern<'a> {
|
|||
// Malformed
|
||||
Malformed(&'a str),
|
||||
MalformedIdent(&'a str, crate::ident::BadIdent),
|
||||
QualifiedIdentifier {
|
||||
module_name: &'a str,
|
||||
ident: &'a str,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
|
@ -992,11 +1199,22 @@ impl<'a> Pattern<'a> {
|
|||
// { x, y } : { x : Int, y ? Bool }
|
||||
// { x, y ? False } = rec
|
||||
OptionalField(x, _) => match other {
|
||||
Identifier(y) | OptionalField(y, _) => x == y,
|
||||
Identifier {
|
||||
ident: y,
|
||||
suffixed: 0,
|
||||
}
|
||||
| OptionalField(y, _) => x == y,
|
||||
_ => false,
|
||||
},
|
||||
Identifier(x) => match other {
|
||||
Identifier(y) | OptionalField(y, _) => x == y,
|
||||
Identifier {
|
||||
ident: x,
|
||||
suffixed: a,
|
||||
} => match other {
|
||||
Identifier {
|
||||
ident: y,
|
||||
suffixed: b,
|
||||
} => x == y && a == b,
|
||||
OptionalField(y, _) => x == y,
|
||||
_ => false,
|
||||
},
|
||||
NumLiteral(x) => {
|
||||
|
@ -1057,13 +1275,15 @@ 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
|
||||
a == b && x == y && i == j
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
@ -1128,6 +1348,15 @@ 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> {
|
||||
|
@ -1384,11 +1613,13 @@ 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> {
|
||||
|
@ -1617,6 +1848,7 @@ impl<'a> Malformed for Expr<'a> {
|
|||
OpaqueRef(_) |
|
||||
SingleQuote(_) | // This is just a &str - not a bunch of segments
|
||||
IngestedFile(_, _) |
|
||||
EmptyDefsFinal |
|
||||
Crash => false,
|
||||
|
||||
Str(inner) => inner.is_malformed(),
|
||||
|
@ -1650,10 +1882,10 @@ impl<'a> Malformed for Expr<'a> {
|
|||
|
||||
MalformedIdent(_, _) |
|
||||
MalformedClosure |
|
||||
MalformedSuffixed(..) |
|
||||
PrecedenceConflict(_) |
|
||||
MultipleRecordBuilders(_) |
|
||||
UnappliedRecordBuilder(_) => true,
|
||||
Suffixed(expr) => expr.is_malformed(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1748,7 +1980,7 @@ impl<'a> Malformed for Pattern<'a> {
|
|||
use Pattern::*;
|
||||
|
||||
match self {
|
||||
Identifier(_) |
|
||||
Identifier{ .. } |
|
||||
Tag(_) |
|
||||
OpaqueRef(_) => false,
|
||||
Apply(func, args) => func.is_malformed() || args.iter().any(|arg| arg.is_malformed()),
|
||||
|
@ -1888,6 +2120,7 @@ impl<'a> Malformed for ValueDef<'a> {
|
|||
condition,
|
||||
preceding_comment: _,
|
||||
} => condition.is_malformed(),
|
||||
ValueDef::Stmt(loc_expr) => loc_expr.is_malformed(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -197,12 +197,17 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
pub fn check_indent<'a, E>(indent_problem: fn(Position) -> E) -> impl Parser<'a, (), E>
|
||||
pub fn check_indent<'a, E>(
|
||||
indent_problem: fn(Position) -> E,
|
||||
inside_suffixed_statement: bool,
|
||||
) -> impl Parser<'a, (), E>
|
||||
where
|
||||
E: 'a,
|
||||
{
|
||||
let extra_spaces = if inside_suffixed_statement { 1 } else { 0 };
|
||||
|
||||
move |_, state: State<'a>, min_indent: u32| {
|
||||
if state.column() >= min_indent {
|
||||
if state.column() >= (min_indent + extra_spaces) {
|
||||
Ok((NoProgress, (), state))
|
||||
} else {
|
||||
Err((NoProgress, indent_problem(state.pos())))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::ast::{
|
||||
AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces, Implements,
|
||||
ImplementsAbilities, Pattern, RecordBuilderField, Spaceable, Spaces, TypeAnnotation, TypeDef,
|
||||
TypeHeader, ValueDef,
|
||||
is_loc_expr_suffixed, AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces,
|
||||
Implements, ImplementsAbilities, Pattern, RecordBuilderField, Spaceable, Spaces,
|
||||
TypeAnnotation, TypeDef, TypeHeader, ValueDef,
|
||||
};
|
||||
use crate::blankspace::{
|
||||
space0_after_e, space0_around_e_no_after_indent_check, space0_around_ee, space0_before_e,
|
||||
|
@ -67,6 +67,20 @@ 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>> {
|
||||
|
@ -323,24 +337,18 @@ fn expr_start<'a>(options: ExprParseOptions) -> impl Parser<'a, Loc<Expr<'a>>, E
|
|||
|
||||
fn expr_operator_chain<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
|
||||
line_min_indent(move |arena, state: State<'a>, min_indent: u32| {
|
||||
let (_, expr, state) = loc_possibly_negative_or_negated_term(options)
|
||||
.parse(arena, state, min_indent)
|
||||
.map(|(progress, expr, state)| {
|
||||
// If the next thing after the expression is a `!`, then it's Suffixed
|
||||
if state.bytes().starts_with(b"!") {
|
||||
(
|
||||
progress,
|
||||
Loc::at(expr.region, Expr::Suffixed(arena.alloc(expr.value))),
|
||||
state.advance(1),
|
||||
)
|
||||
} else {
|
||||
(progress, expr, state)
|
||||
}
|
||||
})?;
|
||||
let (_, expr, state) =
|
||||
loc_possibly_negative_or_negated_term(options).parse(arena, state, min_indent)?;
|
||||
|
||||
let initial_state = state.clone();
|
||||
let end = state.pos();
|
||||
|
||||
let new_options = if is_loc_expr_suffixed(&expr) {
|
||||
options.set_suffixed_found()
|
||||
} else {
|
||||
options
|
||||
};
|
||||
|
||||
match space0_e(EExpr::IndentEnd).parse(arena, state.clone(), min_indent) {
|
||||
Err((_, _)) => Ok((MadeProgress, expr.value, state)),
|
||||
Ok((_, spaces_before_op, state)) => {
|
||||
|
@ -352,7 +360,31 @@ fn expr_operator_chain<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a
|
|||
end,
|
||||
};
|
||||
|
||||
parse_expr_end(min_indent, options, expr_state, arena, state, initial_state)
|
||||
match parse_expr_end(
|
||||
min_indent,
|
||||
new_options,
|
||||
expr_state,
|
||||
arena,
|
||||
state,
|
||||
initial_state,
|
||||
) {
|
||||
Err(err) => Err(err),
|
||||
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)) {
|
||||
let def_region = Region::new(end, new_state.pos());
|
||||
let value_def = ValueDef::Stmt(arena.alloc(Loc::at(def_region, expr)));
|
||||
|
||||
let mut defs = Defs::default();
|
||||
defs.push_value_def(value_def, def_region, &[], &[]);
|
||||
|
||||
return parse_defs_expr(options, min_indent, defs, arena, new_state);
|
||||
} else {
|
||||
Ok((progress, expr, new_state))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -412,6 +444,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)
|
||||
{
|
||||
let region = Region::across_all(self.arguments.iter().map(|v| &v.region));
|
||||
|
||||
|
@ -625,8 +658,26 @@ pub fn parse_single_def<'a>(
|
|||
}
|
||||
}
|
||||
Err((MadeProgress, _)) => {
|
||||
// a hacky way to get expression-based error messages. TODO fix this
|
||||
Ok((NoProgress, None, initial))
|
||||
// Try to parse as a Statement
|
||||
match parse_statement_inside_def(
|
||||
arena,
|
||||
initial.clone(),
|
||||
min_indent,
|
||||
options,
|
||||
start,
|
||||
spaces_before_current_start,
|
||||
// TODO including spaces_before_current here doubles things up
|
||||
&[],
|
||||
|_, 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) => {
|
||||
Ok((MadeProgress, Some(single_def), state))
|
||||
}
|
||||
_ => Ok((NoProgress, None, initial)), // a hacky way to get expression-based error messages. TODO fix this
|
||||
},
|
||||
_ => Ok((NoProgress, None, initial)), // a hacky way to get expression-based error messages. TODO fix this
|
||||
}
|
||||
}
|
||||
Ok((_, loc_pattern, state)) => {
|
||||
// First let's check whether this is an ability definition.
|
||||
|
@ -668,185 +719,291 @@ pub fn parse_single_def<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
// Otherwise, this is a def or alias.
|
||||
match operator().parse(arena, state, min_indent) {
|
||||
Ok((_, BinOp::Assignment, state)) => {
|
||||
let parse_def_expr = space0_before_e(
|
||||
increment_min_indent(expr_start(options)),
|
||||
EExpr::IndentEnd,
|
||||
);
|
||||
// This may be a def or alias.
|
||||
let operator_result = operator().parse(arena, state.clone(), min_indent);
|
||||
|
||||
let (_, loc_def_expr, state) =
|
||||
parse_def_expr.parse(arena, state, min_indent)?;
|
||||
let value_def =
|
||||
ValueDef::Body(arena.alloc(loc_pattern), &*arena.alloc(loc_def_expr));
|
||||
let region = Region::span_across(&loc_pattern.region, &loc_def_expr.region);
|
||||
if let Ok((_, BinOp::Assignment, operator_result_state)) = operator_result {
|
||||
return parse_single_def_assignment(
|
||||
options,
|
||||
// to support statements we have to increase the indent here so that we can parse a child def
|
||||
// within a def and still continue to parse the final expression for this def
|
||||
// e.g.
|
||||
// main =
|
||||
// Stdout.line! "Bar"
|
||||
// a=Stdout.line! "Foo"
|
||||
// Task.ok {}
|
||||
&operator_result_state.line_indent() + 1,
|
||||
arena,
|
||||
operator_result_state,
|
||||
loc_pattern,
|
||||
spaces_before_current,
|
||||
);
|
||||
};
|
||||
|
||||
Ok((
|
||||
MadeProgress,
|
||||
Some(SingleDef {
|
||||
type_or_value: Either::Second(value_def),
|
||||
region,
|
||||
spaces_before: spaces_before_current,
|
||||
}),
|
||||
state,
|
||||
))
|
||||
}
|
||||
Ok((_, BinOp::IsAliasType, state)) => {
|
||||
// the increment_min_indent here is probably _wrong_, since alias_signature_with_space_before does
|
||||
// that internally.
|
||||
// TODO: re-evaluate this
|
||||
let parser = increment_min_indent(alias_signature_with_space_before());
|
||||
let (_, ann_type, state) = parser.parse(arena, state, min_indent)?;
|
||||
let region = Region::span_across(&loc_pattern.region, &ann_type.region);
|
||||
if let Ok((_, BinOp::IsAliasType, state)) = operator_result {
|
||||
// the increment_min_indent here is probably _wrong_, since alias_signature_with_space_before does
|
||||
// that internally.
|
||||
// TODO: re-evaluate this
|
||||
let parser = increment_min_indent(alias_signature_with_space_before());
|
||||
let (_, ann_type, state) = parser.parse(arena, state, min_indent)?;
|
||||
let region = Region::span_across(&loc_pattern.region, &ann_type.region);
|
||||
|
||||
match &loc_pattern.value.extract_spaces().item {
|
||||
Pattern::Apply(
|
||||
Loc {
|
||||
value: Pattern::Tag(name),
|
||||
..
|
||||
},
|
||||
alias_arguments,
|
||||
) => {
|
||||
let name = Loc::at(loc_pattern.region, *name);
|
||||
let header = TypeHeader {
|
||||
name,
|
||||
vars: alias_arguments,
|
||||
};
|
||||
match &loc_pattern.value.extract_spaces().item {
|
||||
Pattern::Apply(
|
||||
Loc {
|
||||
value: Pattern::Tag(name),
|
||||
..
|
||||
},
|
||||
alias_arguments,
|
||||
) => {
|
||||
let name = Loc::at(loc_pattern.region, *name);
|
||||
let header = TypeHeader {
|
||||
name,
|
||||
vars: alias_arguments,
|
||||
};
|
||||
|
||||
let type_def = TypeDef::Alias {
|
||||
header,
|
||||
ann: ann_type,
|
||||
};
|
||||
let type_def = TypeDef::Alias {
|
||||
header,
|
||||
ann: ann_type,
|
||||
};
|
||||
|
||||
Ok((
|
||||
MadeProgress,
|
||||
Some(SingleDef {
|
||||
type_or_value: Either::First(type_def),
|
||||
region,
|
||||
spaces_before: spaces_before_current,
|
||||
}),
|
||||
state,
|
||||
))
|
||||
}
|
||||
Pattern::Tag(name) => {
|
||||
let name = Loc::at(loc_pattern.region, *name);
|
||||
let pattern_arguments: &'a [Loc<Pattern<'a>>] = &[];
|
||||
let header = TypeHeader {
|
||||
name,
|
||||
vars: pattern_arguments,
|
||||
};
|
||||
return Ok((
|
||||
MadeProgress,
|
||||
Some(SingleDef {
|
||||
type_or_value: Either::First(type_def),
|
||||
region,
|
||||
spaces_before: spaces_before_current,
|
||||
}),
|
||||
state,
|
||||
));
|
||||
}
|
||||
Pattern::Tag(name) => {
|
||||
let name = Loc::at(loc_pattern.region, *name);
|
||||
let pattern_arguments: &'a [Loc<Pattern<'a>>] = &[];
|
||||
let header = TypeHeader {
|
||||
name,
|
||||
vars: pattern_arguments,
|
||||
};
|
||||
|
||||
let type_def = TypeDef::Alias {
|
||||
header,
|
||||
ann: ann_type,
|
||||
};
|
||||
let type_def = TypeDef::Alias {
|
||||
header,
|
||||
ann: ann_type,
|
||||
};
|
||||
|
||||
Ok((
|
||||
MadeProgress,
|
||||
Some(SingleDef {
|
||||
type_or_value: Either::First(type_def),
|
||||
region,
|
||||
spaces_before: spaces_before_current,
|
||||
}),
|
||||
state,
|
||||
))
|
||||
}
|
||||
_ => {
|
||||
let value_def = ValueDef::Annotation(loc_pattern, ann_type);
|
||||
return Ok((
|
||||
MadeProgress,
|
||||
Some(SingleDef {
|
||||
type_or_value: Either::First(type_def),
|
||||
region,
|
||||
spaces_before: spaces_before_current,
|
||||
}),
|
||||
state,
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
let value_def = ValueDef::Annotation(loc_pattern, ann_type);
|
||||
|
||||
Ok((
|
||||
MadeProgress,
|
||||
Some(SingleDef {
|
||||
type_or_value: Either::Second(value_def),
|
||||
region,
|
||||
spaces_before: spaces_before_current,
|
||||
}),
|
||||
state,
|
||||
))
|
||||
}
|
||||
return Ok((
|
||||
MadeProgress,
|
||||
Some(SingleDef {
|
||||
type_or_value: Either::Second(value_def),
|
||||
region,
|
||||
spaces_before: spaces_before_current,
|
||||
}),
|
||||
state,
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok((_, BinOp::IsOpaqueType, state)) => {
|
||||
let (_, (signature, derived), state) =
|
||||
opaque_signature_with_space_before().parse(arena, state, min_indent + 1)?;
|
||||
let region = Region::span_across(&loc_pattern.region, &signature.region);
|
||||
};
|
||||
|
||||
match &loc_pattern.value.extract_spaces().item {
|
||||
Pattern::Apply(
|
||||
Loc {
|
||||
value: Pattern::Tag(name),
|
||||
..
|
||||
},
|
||||
alias_arguments,
|
||||
) => {
|
||||
let name = Loc::at(loc_pattern.region, *name);
|
||||
let header = TypeHeader {
|
||||
name,
|
||||
vars: alias_arguments,
|
||||
};
|
||||
if let Ok((_, BinOp::IsOpaqueType, state)) = operator_result {
|
||||
let (_, (signature, derived), state) =
|
||||
opaque_signature_with_space_before().parse(arena, state, min_indent + 1)?;
|
||||
let region = Region::span_across(&loc_pattern.region, &signature.region);
|
||||
|
||||
let type_def = TypeDef::Opaque {
|
||||
header,
|
||||
typ: signature,
|
||||
derived,
|
||||
};
|
||||
match &loc_pattern.value.extract_spaces().item {
|
||||
Pattern::Apply(
|
||||
Loc {
|
||||
value: Pattern::Tag(name),
|
||||
..
|
||||
},
|
||||
alias_arguments,
|
||||
) => {
|
||||
let name = Loc::at(loc_pattern.region, *name);
|
||||
let header = TypeHeader {
|
||||
name,
|
||||
vars: alias_arguments,
|
||||
};
|
||||
|
||||
Ok((
|
||||
MadeProgress,
|
||||
Some(SingleDef {
|
||||
type_or_value: Either::First(type_def),
|
||||
region,
|
||||
spaces_before: spaces_before_current,
|
||||
}),
|
||||
state,
|
||||
))
|
||||
}
|
||||
Pattern::Tag(name) => {
|
||||
let name = Loc::at(loc_pattern.region, *name);
|
||||
let pattern_arguments: &'a [Loc<Pattern<'a>>] = &[];
|
||||
let header = TypeHeader {
|
||||
name,
|
||||
vars: pattern_arguments,
|
||||
};
|
||||
let type_def = TypeDef::Opaque {
|
||||
header,
|
||||
typ: signature,
|
||||
derived,
|
||||
};
|
||||
|
||||
let type_def = TypeDef::Opaque {
|
||||
header,
|
||||
typ: signature,
|
||||
derived,
|
||||
};
|
||||
return Ok((
|
||||
MadeProgress,
|
||||
Some(SingleDef {
|
||||
type_or_value: Either::First(type_def),
|
||||
region,
|
||||
spaces_before: spaces_before_current,
|
||||
}),
|
||||
state,
|
||||
));
|
||||
}
|
||||
Pattern::Tag(name) => {
|
||||
let name = Loc::at(loc_pattern.region, *name);
|
||||
let pattern_arguments: &'a [Loc<Pattern<'a>>] = &[];
|
||||
let header = TypeHeader {
|
||||
name,
|
||||
vars: pattern_arguments,
|
||||
};
|
||||
|
||||
Ok((
|
||||
MadeProgress,
|
||||
Some(SingleDef {
|
||||
type_or_value: Either::First(type_def),
|
||||
region,
|
||||
spaces_before: spaces_before_current,
|
||||
}),
|
||||
state,
|
||||
))
|
||||
}
|
||||
_ => {
|
||||
let value_def = ValueDef::Annotation(loc_pattern, signature);
|
||||
let type_def = TypeDef::Opaque {
|
||||
header,
|
||||
typ: signature,
|
||||
derived,
|
||||
};
|
||||
|
||||
Ok((
|
||||
MadeProgress,
|
||||
Some(SingleDef {
|
||||
type_or_value: Either::Second(value_def),
|
||||
region,
|
||||
spaces_before: spaces_before_current,
|
||||
}),
|
||||
state,
|
||||
))
|
||||
}
|
||||
return Ok((
|
||||
MadeProgress,
|
||||
Some(SingleDef {
|
||||
type_or_value: Either::First(type_def),
|
||||
region,
|
||||
spaces_before: spaces_before_current,
|
||||
}),
|
||||
state,
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
let value_def = ValueDef::Annotation(loc_pattern, signature);
|
||||
|
||||
return Ok((
|
||||
MadeProgress,
|
||||
Some(SingleDef {
|
||||
type_or_value: Either::Second(value_def),
|
||||
region,
|
||||
spaces_before: spaces_before_current,
|
||||
}),
|
||||
state,
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => Ok((MadeProgress, None, initial)),
|
||||
};
|
||||
|
||||
// Otherwise try to re-parse as a Statement
|
||||
match parse_statement_inside_def(
|
||||
arena,
|
||||
initial.clone(),
|
||||
min_indent,
|
||||
options,
|
||||
start,
|
||||
spaces_before_current_start,
|
||||
// TODO figure out why including spaces_before_current here doubles things up
|
||||
&[],
|
||||
|_, 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) => {
|
||||
Ok((MadeProgress, Some(single_def), state))
|
||||
}
|
||||
_ => Ok((NoProgress, None, initial)),
|
||||
},
|
||||
_ => Ok((NoProgress, None, initial)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_single_def_assignment<'a>(
|
||||
options: ExprParseOptions,
|
||||
min_indent: u32,
|
||||
arena: &'a Bump,
|
||||
initial_state: State<'a>,
|
||||
def_loc_pattern: Loc<Pattern<'a>>,
|
||||
spaces_before_current: &'a [CommentOrNewline<'a>],
|
||||
) -> ParseResult<'a, Option<SingleDef<'a>>, EExpr<'a>> {
|
||||
// Try and parse the expression
|
||||
let parse_def_expr =
|
||||
space0_before_e(increment_min_indent(expr_start(options)), EExpr::IndentEnd);
|
||||
let (progress_after_first, first_loc_expr, state_after_first_expression) =
|
||||
parse_def_expr.parse(arena, initial_state, min_indent)?;
|
||||
|
||||
let region = Region::span_across(&def_loc_pattern.region, &first_loc_expr.region);
|
||||
|
||||
// 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) {
|
||||
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
|
||||
defs.push_value_def(
|
||||
ValueDef::Stmt(arena.alloc(first_loc_expr)),
|
||||
Region::span_across(&def_loc_pattern.region, &first_loc_expr.region),
|
||||
spaces_before_current,
|
||||
&[],
|
||||
);
|
||||
|
||||
// Try to parse the rest of the expression as multiple defs, which may contain sub-assignments
|
||||
match parse_defs_expr(
|
||||
options,
|
||||
min_indent,
|
||||
defs.clone(),
|
||||
arena,
|
||||
state_after_first_expression.clone(),
|
||||
) {
|
||||
Ok((progress_after_rest_of_def, expr, state_after_rest_of_def)) => {
|
||||
let final_loc_expr = arena.alloc(Loc::at(region, expr));
|
||||
|
||||
let value_def = ValueDef::Body(arena.alloc(def_loc_pattern), final_loc_expr);
|
||||
|
||||
return Ok((
|
||||
progress_after_rest_of_def,
|
||||
Some(SingleDef {
|
||||
type_or_value: Either::Second(value_def),
|
||||
region,
|
||||
spaces_before: spaces_before_current,
|
||||
}),
|
||||
state_after_rest_of_def,
|
||||
));
|
||||
}
|
||||
Err(_) => {
|
||||
// Unable to parse more defs, continue and return the first parsed expression as a stement
|
||||
let empty_return =
|
||||
arena.alloc(Loc::at(first_loc_expr.region, Expr::EmptyDefsFinal));
|
||||
let value_def = ValueDef::Body(
|
||||
arena.alloc(def_loc_pattern),
|
||||
arena.alloc(Loc::at(
|
||||
first_loc_expr.region,
|
||||
Expr::Defs(arena.alloc(defs), empty_return),
|
||||
)),
|
||||
);
|
||||
return Ok((
|
||||
progress_after_first,
|
||||
Some(SingleDef {
|
||||
type_or_value: Either::Second(value_def),
|
||||
region,
|
||||
spaces_before: spaces_before_current,
|
||||
}),
|
||||
state_after_first_expression,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let value_def = ValueDef::Body(arena.alloc(def_loc_pattern), arena.alloc(first_loc_expr));
|
||||
|
||||
Ok((
|
||||
progress_after_first,
|
||||
Some(SingleDef {
|
||||
type_or_value: Either::Second(value_def),
|
||||
region,
|
||||
spaces_before: spaces_before_current,
|
||||
}),
|
||||
state_after_first_expression,
|
||||
))
|
||||
}
|
||||
|
||||
/// e.g. Things that can be on their own line in a def, e.g. `expect`, `expect-fx`, or `dbg`
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn parse_statement_inside_def<'a>(
|
||||
|
@ -880,6 +1037,7 @@ fn parse_statement_inside_def<'a>(
|
|||
}
|
||||
|
||||
let preceding_comment = Region::new(spaces_before_current_start, start);
|
||||
|
||||
let value_def = get_value_def(preceding_comment, loc_def_expr);
|
||||
|
||||
Ok((
|
||||
|
@ -962,6 +1120,9 @@ fn parse_defs_end<'a>(
|
|||
let mut global_state = state;
|
||||
|
||||
loop {
|
||||
// keep a copy in the event we get an EExpr::DefMissingFinalExpr
|
||||
let state_before = global_state.clone();
|
||||
|
||||
let state = global_state;
|
||||
|
||||
global_state = match parse_single_def(options, min_indent, arena, state) {
|
||||
|
@ -1041,11 +1202,16 @@ fn parse_defs_end<'a>(
|
|||
next_state
|
||||
}
|
||||
Ok((progress, None, s)) => return Ok((progress, defs, s)),
|
||||
Err((MadeProgress, EExpr::DefMissingFinalExpr(..)))
|
||||
| Err((MadeProgress, EExpr::DefMissingFinalExpr2(..))) => {
|
||||
return Ok((MadeProgress, defs, state_before))
|
||||
}
|
||||
Err((progress, err)) => return Err((progress, err)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SingleDef<'a> {
|
||||
pub type_or_value: Either<TypeDef<'a>, ValueDef<'a>>,
|
||||
pub region: Region,
|
||||
|
@ -1067,18 +1233,50 @@ fn parse_defs_expr<'a>(
|
|||
|
||||
match parse_final_expr.parse(arena, state.clone(), min_indent) {
|
||||
Err((_, fail)) => {
|
||||
return Err((
|
||||
// If the last def was a suffixed statement, assume this was
|
||||
// intentional by the application author instead of giving
|
||||
// an error.
|
||||
if let Some((new_defs, loc_ret)) = def_state.last_value_suffixed() {
|
||||
// note we check the tags here and not value_defs, as there may be redundant defs in Defs
|
||||
|
||||
let mut local_defs = new_defs.clone();
|
||||
|
||||
let last_stmt = ValueDef::Stmt(loc_ret);
|
||||
local_defs.push_value_def(last_stmt, loc_ret.region, &[], &[]);
|
||||
|
||||
//check the length of the defs we would return, if we only have one
|
||||
// we can just return the expression
|
||||
// note we use tags here, as we may have redundant defs in Defs
|
||||
if local_defs
|
||||
.tags
|
||||
.iter()
|
||||
.filter(|tag| tag.split().is_err())
|
||||
.count()
|
||||
== 1
|
||||
{
|
||||
return Ok((MadeProgress, loc_ret.value, state));
|
||||
}
|
||||
|
||||
return Ok((
|
||||
MadeProgress,
|
||||
Expr::Defs(
|
||||
arena.alloc(local_defs),
|
||||
arena.alloc(Loc::at_zero(Expr::EmptyDefsFinal)),
|
||||
),
|
||||
state,
|
||||
));
|
||||
}
|
||||
|
||||
Err((
|
||||
MadeProgress,
|
||||
EExpr::DefMissingFinalExpr2(arena.alloc(fail), state.pos()),
|
||||
));
|
||||
}
|
||||
Ok((_, loc_ret, state)) => {
|
||||
return Ok((
|
||||
MadeProgress,
|
||||
Expr::Defs(arena.alloc(def_state), arena.alloc(loc_ret)),
|
||||
state,
|
||||
));
|
||||
))
|
||||
}
|
||||
Ok((_, loc_ret, state)) => Ok((
|
||||
MadeProgress,
|
||||
Expr::Defs(arena.alloc(def_state), arena.alloc(loc_ret)),
|
||||
state,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1635,8 +1833,51 @@ fn parse_expr_operator<'a>(
|
|||
expr_state.end = new_end;
|
||||
expr_state.spaces_after = spaces;
|
||||
|
||||
// TODO new start?
|
||||
parse_expr_end(min_indent, options, expr_state, arena, state, initial_state)
|
||||
let new_options = if is_loc_expr_suffixed(&new_expr) {
|
||||
options.set_suffixed_found()
|
||||
} else {
|
||||
options
|
||||
};
|
||||
|
||||
match parse_expr_end(
|
||||
min_indent,
|
||||
new_options,
|
||||
expr_state,
|
||||
arena,
|
||||
state,
|
||||
initial_state,
|
||||
) {
|
||||
Ok((progress, expr, state)) => {
|
||||
if let Expr::BinOps(..) = expr {
|
||||
let def_region = expr.get_region_spanning_binops();
|
||||
let mut new_expr = Loc::at(def_region, expr);
|
||||
|
||||
if is_loc_expr_suffixed(&new_expr) {
|
||||
// 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() {
|
||||
new_expr = arena.alloc(expr).with_spaces_before(
|
||||
spaces_after_operator,
|
||||
def_region,
|
||||
);
|
||||
}
|
||||
|
||||
let value_def = ValueDef::Stmt(arena.alloc(new_expr));
|
||||
|
||||
let mut defs = Defs::default();
|
||||
defs.push_value_def(value_def, def_region, &[], &[]);
|
||||
|
||||
return parse_defs_expr(
|
||||
options, min_indent, defs, arena, state,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// else return the parsed expression
|
||||
Ok((progress, expr, state))
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1656,7 +1897,7 @@ fn parse_expr_end<'a>(
|
|||
initial_state: State<'a>,
|
||||
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
|
||||
let parser = skip_first!(
|
||||
crate::blankspace::check_indent(EExpr::IndentEnd),
|
||||
crate::blankspace::check_indent(EExpr::IndentEnd, options.suffixed_found),
|
||||
loc_term_or_underscore(options)
|
||||
);
|
||||
|
||||
|
@ -1669,6 +1910,7 @@ fn parse_expr_end<'a>(
|
|||
Expr::Var {
|
||||
module_name: "",
|
||||
ident: crate::keyword::IMPLEMENTS,
|
||||
..
|
||||
},
|
||||
..
|
||||
},
|
||||
|
@ -1717,6 +1959,12 @@ 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()
|
||||
} else {
|
||||
options
|
||||
};
|
||||
|
||||
// now that we have `function arg1 ... <spaces> argn`, attach the spaces to the `argn`
|
||||
if !expr_state.spaces_after.is_empty() {
|
||||
arg = arena
|
||||
|
@ -1741,7 +1989,14 @@ fn parse_expr_end<'a>(
|
|||
expr_state.end = new_end;
|
||||
expr_state.spaces_after = new_spaces;
|
||||
|
||||
parse_expr_end(min_indent, options, expr_state, arena, state, initial_state)
|
||||
parse_expr_end(
|
||||
min_indent,
|
||||
new_options,
|
||||
expr_state,
|
||||
arena,
|
||||
state,
|
||||
initial_state,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1857,6 +2112,7 @@ pub fn loc_expr<'a>(accept_multi_backpassing: bool) -> impl Parser<'a, Loc<Expr<
|
|||
expr_start(ExprParseOptions {
|
||||
accept_multi_backpassing,
|
||||
check_for_arrow: true,
|
||||
suffixed_found: false,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1891,11 +2147,19 @@ 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 } => {
|
||||
Expr::Var {
|
||||
module_name,
|
||||
ident,
|
||||
suffixed,
|
||||
} => {
|
||||
if module_name.is_empty() {
|
||||
Pattern::Identifier(ident)
|
||||
Pattern::Identifier { ident, suffixed }
|
||||
} else {
|
||||
Pattern::QualifiedIdentifier { module_name, ident }
|
||||
Pattern::QualifiedIdentifier {
|
||||
module_name,
|
||||
ident,
|
||||
suffixed,
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::Underscore(opt_name) => Pattern::Underscore(opt_name),
|
||||
|
@ -1923,7 +2187,8 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
|
|||
Expr::SpaceBefore(..)
|
||||
| Expr::SpaceAfter(..)
|
||||
| Expr::ParensAround(..)
|
||||
| Expr::RecordBuilder(..) => unreachable!(),
|
||||
| Expr::RecordBuilder(..)
|
||||
| Expr::EmptyDefsFinal => unreachable!(),
|
||||
|
||||
Expr::Record(fields) => {
|
||||
let patterns = fields.map_items_result(arena, |loc_assigned_field| {
|
||||
|
@ -1969,6 +2234,7 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
|
|||
| Expr::Dbg(_, _)
|
||||
| Expr::LowLevelDbg(_, _, _)
|
||||
| Expr::MalformedClosure
|
||||
| Expr::MalformedSuffixed(..)
|
||||
| Expr::PrecedenceConflict { .. }
|
||||
| Expr::MultipleRecordBuilders { .. }
|
||||
| Expr::UnappliedRecordBuilder { .. }
|
||||
|
@ -1979,7 +2245,6 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
|
|||
Expr::Str(string) => Pattern::StrLiteral(string),
|
||||
Expr::SingleQuote(string) => Pattern::SingleQuote(string),
|
||||
Expr::MalformedIdent(string, problem) => Pattern::MalformedIdent(string, problem),
|
||||
Expr::Suffixed(_) => todo!(),
|
||||
};
|
||||
|
||||
// Now we re-add the spaces
|
||||
|
@ -2029,7 +2294,10 @@ fn assigned_expr_field_to_pattern_help<'a>(
|
|||
)
|
||||
}
|
||||
}
|
||||
AssignedField::LabelOnly(name) => Pattern::Identifier(name.value),
|
||||
AssignedField::LabelOnly(name) => Pattern::Identifier {
|
||||
ident: name.value,
|
||||
suffixed: 0,
|
||||
},
|
||||
AssignedField::SpaceBefore(nested, spaces) => Pattern::SpaceBefore(
|
||||
arena.alloc(assigned_expr_field_to_pattern_help(arena, nested)?),
|
||||
spaces,
|
||||
|
@ -2052,6 +2320,7 @@ pub fn toplevel_defs<'a>() -> impl Parser<'a, Defs<'a>, EExpr<'a>> {
|
|||
let options = ExprParseOptions {
|
||||
accept_multi_backpassing: true,
|
||||
check_for_arrow: true,
|
||||
suffixed_found: false,
|
||||
};
|
||||
|
||||
let mut output = Defs::default();
|
||||
|
@ -2530,10 +2799,11 @@ fn ident_to_expr<'a>(arena: &'a Bump, src: Ident<'a>) -> Expr<'a> {
|
|||
// 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)) if suffixed => {
|
||||
Expr::Suffixed(arena.alloc(Expr::Var { module_name, ident }))
|
||||
}
|
||||
Some(Accessor::RecordField(ident)) => Expr::Var { module_name, ident },
|
||||
Some(Accessor::RecordField(ident)) => Expr::Var {
|
||||
module_name,
|
||||
ident,
|
||||
suffixed,
|
||||
},
|
||||
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`,
|
||||
|
|
|
@ -42,7 +42,7 @@ pub enum Ident<'a> {
|
|||
Access {
|
||||
module_name: &'a str,
|
||||
parts: &'a [Accessor<'a>],
|
||||
suffixed: bool,
|
||||
suffixed: u8,
|
||||
},
|
||||
/// `.foo { foo: 42 }` or `.1 (1, 2, 3)`
|
||||
AccessorFunction(Accessor<'a>),
|
||||
|
@ -194,7 +194,9 @@ pub fn parse_ident<'a>(
|
|||
Ok((width, ident)) => {
|
||||
let state = advance_state!(state, width as usize)?;
|
||||
if let Ident::Access {
|
||||
module_name, parts, ..
|
||||
module_name,
|
||||
parts,
|
||||
suffixed,
|
||||
} = ident
|
||||
{
|
||||
if module_name.is_empty() {
|
||||
|
@ -206,21 +208,16 @@ pub fn parse_ident<'a>(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse a suffixed `!` expression
|
||||
if state.bytes().starts_with(b"!") {
|
||||
if let Ident::Access {
|
||||
module_name, parts, ..
|
||||
} = ident
|
||||
{
|
||||
let new_ident = Ident::Access {
|
||||
return Ok((
|
||||
MadeProgress,
|
||||
Ident::Access {
|
||||
module_name,
|
||||
parts,
|
||||
suffixed: true,
|
||||
};
|
||||
return Ok((MadeProgress, new_ident, state.advance(1)));
|
||||
}
|
||||
suffixed,
|
||||
},
|
||||
state,
|
||||
));
|
||||
}
|
||||
|
||||
Ok((MadeProgress, ident, state))
|
||||
|
@ -531,10 +528,22 @@ 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: false,
|
||||
suffixed,
|
||||
};
|
||||
|
||||
Ok((chomped as u32, ident))
|
||||
|
@ -563,15 +572,30 @@ fn chomp_identifier_chain<'a>(
|
|||
} else if first_is_uppercase {
|
||||
// just one segment, starting with an uppercase letter; that's a tag
|
||||
let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) };
|
||||
|
||||
Ok((chomped as u32, Ident::Tag(value)))
|
||||
} else {
|
||||
// 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: false,
|
||||
suffixed,
|
||||
};
|
||||
|
||||
Ok((chomped as u32, ident))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ 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.
|
||||
|
@ -49,6 +50,14 @@ 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)),
|
||||
|
@ -138,7 +147,15 @@ fn loc_tag_pattern_arg<'a>(
|
|||
|
||||
let Loc { region, value } = loc_pat;
|
||||
|
||||
if stop_on_has_kw && matches!(value, Pattern::Identifier(crate::keyword::IMPLEMENTS)) {
|
||||
if stop_on_has_kw
|
||||
&& matches!(
|
||||
value,
|
||||
Pattern::Identifier {
|
||||
ident: crate::keyword::IMPLEMENTS,
|
||||
..
|
||||
}
|
||||
)
|
||||
{
|
||||
Err((NoProgress, EPattern::End(original_state.pos())))
|
||||
} else {
|
||||
Ok((
|
||||
|
@ -160,7 +177,10 @@ pub fn loc_implements_parser<'a>() -> impl Parser<'a, Loc<Implements<'a>>, EPatt
|
|||
|_arena, state, progress, pattern| {
|
||||
if matches!(
|
||||
pattern.value,
|
||||
Pattern::Identifier(crate::keyword::IMPLEMENTS)
|
||||
Pattern::Identifier {
|
||||
ident: crate::keyword::IMPLEMENTS,
|
||||
..
|
||||
}
|
||||
) {
|
||||
Ok((
|
||||
progress,
|
||||
|
@ -385,6 +405,46 @@ 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, ..
|
||||
} => {
|
||||
|
@ -403,7 +463,10 @@ fn loc_ident_pattern_help<'a>(
|
|||
MadeProgress,
|
||||
Loc {
|
||||
region: loc_ident.region,
|
||||
value: Pattern::Identifier(var),
|
||||
value: Pattern::Identifier {
|
||||
ident: var,
|
||||
suffixed: 0,
|
||||
},
|
||||
},
|
||||
state,
|
||||
));
|
||||
|
@ -564,9 +627,18 @@ 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(value)), spaces)
|
||||
Pattern::SpaceAfter(
|
||||
arena.alloc(Pattern::Identifier {
|
||||
ident: value,
|
||||
suffixed: 0,
|
||||
}),
|
||||
spaces,
|
||||
)
|
||||
} else {
|
||||
Pattern::Identifier(value)
|
||||
Pattern::Identifier {
|
||||
ident: value,
|
||||
suffixed: 0,
|
||||
}
|
||||
};
|
||||
|
||||
Ok((MadeProgress, Loc::at(region, value), state))
|
||||
|
|
|
@ -70,7 +70,13 @@ 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(v)));
|
||||
var_names.push(Loc::at(
|
||||
var.region,
|
||||
Pattern::Identifier {
|
||||
ident: v,
|
||||
suffixed: 0,
|
||||
},
|
||||
));
|
||||
} else {
|
||||
return Err(ETypeInlineAlias::ArgumentNotLowercase(var.region.start()));
|
||||
}
|
||||
|
|
|
@ -175,6 +175,7 @@ mod test_parse {
|
|||
let expr = arena.alloc(Var {
|
||||
module_name: "",
|
||||
ident: "name",
|
||||
suffixed: 0,
|
||||
});
|
||||
|
||||
bumpalo::vec![in arena;
|
||||
|
@ -191,6 +192,7 @@ mod test_parse {
|
|||
let expr = arena.alloc(Var {
|
||||
module_name: "",
|
||||
ident: "name",
|
||||
suffixed: 0,
|
||||
});
|
||||
|
||||
bumpalo::vec![in arena;
|
||||
|
@ -236,6 +238,7 @@ mod test_parse {
|
|||
let expr = arena.alloc(Var {
|
||||
module_name: "",
|
||||
ident: "name",
|
||||
suffixed: 0,
|
||||
});
|
||||
|
||||
bumpalo::vec![in arena;
|
||||
|
@ -251,11 +254,13 @@ 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;
|
||||
|
@ -276,11 +281,13 @@ 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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue