mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-13 07:16:18 +00:00
3997 lines
136 KiB
Rust
3997 lines
136 KiB
Rust
use crate::ast::{
|
|
is_expr_suffixed, AssignedField, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces,
|
|
Implements, ImplementsAbilities, ImportAlias, ImportAsKeyword, ImportExposingKeyword,
|
|
ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ModuleImport,
|
|
ModuleImportParams, OldRecordBuilderField, Pattern, Spaceable, Spaced, Spaces, SpacesBefore,
|
|
TryTarget, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
|
|
};
|
|
use crate::blankspace::{
|
|
loc_space0_e, require_newline_or_eof, space0_after_e, space0_around_ee, space0_before_e,
|
|
space0_before_optional_after, space0_e, spaces, spaces_around, spaces_before,
|
|
};
|
|
use crate::header::module_name_help;
|
|
use crate::ident::{
|
|
integer_ident, lowercase_ident, parse_ident, unqualified_ident, Accessor, Ident, Suffix,
|
|
};
|
|
use crate::parser::{
|
|
self, and, backtrackable, between, byte, byte_indent, collection_inner,
|
|
collection_trailing_sep_e, either, increment_min_indent, indented_seq_skip_first, 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, EImport, EImportParams, EInParens, EList, ENumber, EPattern, ERecord,
|
|
EString, EType, EWhen, Either, ParseResult, Parser, SpaceProblem,
|
|
};
|
|
use crate::pattern::closure_param;
|
|
use crate::state::State;
|
|
use crate::string_literal::{self, StrLikeLiteral};
|
|
use crate::type_annotation;
|
|
use crate::{header, keyword};
|
|
use bumpalo::collections::Vec;
|
|
use bumpalo::Bump;
|
|
use roc_collections::soa::Slice;
|
|
use roc_error_macros::internal_error;
|
|
use roc_module::called_via::{BinOp, CalledVia, UnaryOp};
|
|
use roc_region::all::{Loc, Position, Region};
|
|
|
|
use crate::parser::Progress::{self, *};
|
|
|
|
fn expr_end<'a>() -> impl Parser<'a, (), EExpr<'a>> {
|
|
|_arena, state: State<'a>, _min_indent: u32| {
|
|
if state.has_reached_end() {
|
|
Ok((NoProgress, (), state))
|
|
} else {
|
|
Err((NoProgress, EExpr::BadExprEnd(state.pos())))
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn test_parse_expr<'a>(
|
|
min_indent: u32,
|
|
arena: &'a bumpalo::Bump,
|
|
state: State<'a>,
|
|
) -> Result<Loc<Expr<'a>>, EExpr<'a>> {
|
|
let parser = skip_second(
|
|
space0_before_optional_after(loc_expr_block(true), EExpr::IndentStart, EExpr::IndentEnd),
|
|
expr_end(),
|
|
);
|
|
|
|
match parser.parse(arena, state, min_indent) {
|
|
Ok((_, expression, _)) => Ok(expression),
|
|
Err((_, fail)) => Err(fail),
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub struct ExprParseOptions {
|
|
/// Check for and accept multi-backpassing syntax
|
|
/// This is usually true, but false within list/record literals
|
|
/// because the comma separating backpassing arguments conflicts
|
|
/// with the comma separating literal elements
|
|
pub accept_multi_backpassing: bool,
|
|
|
|
/// Check for the `->` token, and raise an error if found
|
|
/// This is usually true, but false in if-guards
|
|
///
|
|
/// > Just foo if foo == 2 -> ...
|
|
pub check_for_arrow: bool,
|
|
}
|
|
|
|
pub fn expr_help<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
|
|
move |arena, state: State<'a>, min_indent: u32| {
|
|
loc_expr(true)
|
|
.parse(arena, state, min_indent)
|
|
.map(|(a, b, c)| (a, b.value, c))
|
|
}
|
|
}
|
|
|
|
fn loc_expr_in_parens_help<'a>() -> impl Parser<'a, Loc<Expr<'a>>, EInParens<'a>> {
|
|
then(
|
|
loc(collection_trailing_sep_e(
|
|
byte(b'(', EInParens::Open),
|
|
specialize_err_ref(
|
|
EInParens::Expr,
|
|
// space0_before_e(
|
|
loc_expr_block(false),
|
|
),
|
|
byte(b',', EInParens::End),
|
|
byte(b')', EInParens::End),
|
|
Expr::SpaceBefore,
|
|
)),
|
|
move |arena, state, _, loc_elements| {
|
|
let elements = loc_elements.value;
|
|
let region = loc_elements.region;
|
|
|
|
if elements.len() > 1 {
|
|
Ok((
|
|
MadeProgress,
|
|
Loc::at(region, Expr::Tuple(elements.ptrify_items(arena))),
|
|
state,
|
|
))
|
|
} else if elements.is_empty() {
|
|
Err((NoProgress, EInParens::Empty(state.pos())))
|
|
} else {
|
|
// TODO: don't discard comments before/after
|
|
// (stored in the Collection)
|
|
Ok((
|
|
MadeProgress,
|
|
Loc::at(
|
|
elements.items[0].region,
|
|
Expr::ParensAround(&elements.items[0].value),
|
|
),
|
|
state,
|
|
))
|
|
}
|
|
},
|
|
)
|
|
.trace("in_parens")
|
|
}
|
|
|
|
fn loc_expr_in_parens_etc_help<'a>() -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
|
|
map_with_arena(
|
|
loc(and(
|
|
specialize_err(EExpr::InParens, loc_expr_in_parens_help()),
|
|
record_field_access_chain(),
|
|
)),
|
|
move |arena: &'a Bump, value: Loc<(Loc<Expr<'a>>, Vec<'a, Suffix<'a>>)>| {
|
|
let Loc {
|
|
mut region,
|
|
value: (loc_expr, field_accesses),
|
|
} = value;
|
|
|
|
let mut value = loc_expr.value;
|
|
|
|
// if there are field accesses, include the parentheses in the region
|
|
// otherwise, don't include the parentheses
|
|
if field_accesses.is_empty() {
|
|
region = loc_expr.region;
|
|
} else {
|
|
value = apply_expr_access_chain(arena, value, field_accesses);
|
|
}
|
|
|
|
Loc::at(region, value)
|
|
},
|
|
)
|
|
}
|
|
|
|
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::TrySuffix(
|
|
TryTarget::Task
|
|
)),
|
|
map(byte(b'?', EExpr::Access), |_| Suffix::TrySuffix(
|
|
TryTarget::Result
|
|
)),
|
|
))
|
|
}
|
|
|
|
/// In some contexts we want to parse the `_` as an expression, so it can then be turned into a
|
|
/// pattern later
|
|
fn loc_term_or_underscore_or_conditional<'a>(
|
|
options: ExprParseOptions,
|
|
) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
|
|
one_of!(
|
|
loc_expr_in_parens_etc_help(),
|
|
loc(specialize_err(EExpr::If, if_expr_help(options))),
|
|
loc(specialize_err(EExpr::When, when::when_expr_help(options))),
|
|
loc(specialize_err(EExpr::Str, string_like_literal_help())),
|
|
loc(specialize_err(
|
|
EExpr::Number,
|
|
positive_number_literal_help()
|
|
)),
|
|
loc(specialize_err(EExpr::Closure, closure_help(options))),
|
|
loc(crash_kw()),
|
|
loc(specialize_err(EExpr::Dbg, dbg_kw())),
|
|
loc(underscore_expression()),
|
|
loc(record_literal_help()),
|
|
loc(specialize_err(EExpr::List, list_literal_help())),
|
|
ident_seq(),
|
|
)
|
|
.trace("term_or_underscore_or_conditional")
|
|
}
|
|
|
|
/// In some contexts we want to parse the `_` as an expression, so it can then be turned into a
|
|
/// pattern later
|
|
fn loc_term_or_underscore<'a>(
|
|
options: ExprParseOptions,
|
|
) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
|
|
one_of!(
|
|
loc_expr_in_parens_etc_help(),
|
|
loc(specialize_err(EExpr::Str, string_like_literal_help())),
|
|
loc(specialize_err(
|
|
EExpr::Number,
|
|
positive_number_literal_help()
|
|
)),
|
|
loc(specialize_err(EExpr::Closure, closure_help(options))),
|
|
loc(specialize_err(EExpr::Dbg, dbg_kw())),
|
|
loc(underscore_expression()),
|
|
loc(record_literal_help()),
|
|
loc(specialize_err(EExpr::List, list_literal_help())),
|
|
ident_seq(),
|
|
)
|
|
.trace("term_or_underscore")
|
|
}
|
|
|
|
fn loc_term<'a>(options: ExprParseOptions) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
|
|
one_of!(
|
|
loc_expr_in_parens_etc_help(),
|
|
loc(specialize_err(EExpr::Str, string_like_literal_help())),
|
|
loc(specialize_err(
|
|
EExpr::Number,
|
|
positive_number_literal_help()
|
|
)),
|
|
loc(specialize_err(EExpr::Closure, closure_help(options))),
|
|
loc(specialize_err(EExpr::Dbg, dbg_kw())),
|
|
loc(record_literal_help()),
|
|
loc(specialize_err(EExpr::List, list_literal_help())),
|
|
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();
|
|
|
|
let (_, _, next_state) = byte(b'_', EExpr::Underscore).parse(arena, state, min_indent)?;
|
|
|
|
let lowercase_ident_expr =
|
|
{ specialize_err(move |_, _| EExpr::End(start), lowercase_ident()) };
|
|
|
|
let (_, output, final_state) =
|
|
optional(lowercase_ident_expr).parse(arena, next_state, min_indent)?;
|
|
|
|
match output {
|
|
Some(name) => Ok((MadeProgress, Expr::Underscore(name), final_state)),
|
|
None => Ok((MadeProgress, Expr::Underscore(""), final_state)),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn crash_kw<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
|
|
move |arena: &'a Bump, state: State<'a>, min_indent: u32| {
|
|
let (_, _, next_state) = crate::parser::keyword(crate::keyword::CRASH, EExpr::Crash)
|
|
.parse(arena, state, min_indent)?;
|
|
|
|
Ok((MadeProgress, Expr::Crash, next_state))
|
|
}
|
|
}
|
|
|
|
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![
|
|
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(
|
|
and(
|
|
loc(byte(b'!', EExpr::Start)),
|
|
space0_before_e(loc_term(options), EExpr::IndentStart)
|
|
),
|
|
|arena: &'a Bump, (loc_op, loc_expr): (Loc<_>, _)| {
|
|
Expr::UnaryOp(arena.alloc(loc_expr), Loc::at(loc_op.region, UnaryOp::Not))
|
|
}
|
|
)),
|
|
loc_term_or_underscore_or_conditional(options)
|
|
]
|
|
}
|
|
|
|
fn fail_expr_start_e<'a, T: 'a>() -> impl Parser<'a, T, EExpr<'a>> {
|
|
|_arena, state: State<'a>, _min_indent: u32| Err((NoProgress, EExpr::Start(state.pos())))
|
|
}
|
|
|
|
fn unary_negate<'a>() -> impl Parser<'a, (), EExpr<'a>> {
|
|
move |_arena: &'a Bump, state: State<'a>, _min_indent: u32| {
|
|
// a minus is unary iff
|
|
//
|
|
// - it is preceded by whitespace (spaces, newlines, comments)
|
|
// - it is not followed by whitespace
|
|
let followed_by_whitespace = state
|
|
.bytes()
|
|
.get(1)
|
|
.map(|c| c.is_ascii_whitespace() || *c == b'#')
|
|
.unwrap_or(false);
|
|
|
|
if state.bytes().starts_with(b"-") && !followed_by_whitespace {
|
|
// the negate is only unary if it is not followed by whitespace
|
|
let state = state.advance(1);
|
|
Ok((MadeProgress, (), state))
|
|
} else {
|
|
// this is not a negated expression
|
|
Err((NoProgress, EExpr::UnaryNot(state.pos())))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Entry point for parsing an expression.
|
|
fn expr_start<'a>(options: ExprParseOptions) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
|
|
one_of![
|
|
loc(specialize_err(EExpr::If, if_expr_help(options))),
|
|
loc(specialize_err(EExpr::When, when::when_expr_help(options))),
|
|
loc(specialize_err(EExpr::Closure, closure_help(options))),
|
|
loc(expr_operator_chain(options)),
|
|
fail_expr_start_e()
|
|
]
|
|
.trace("expr_start")
|
|
}
|
|
|
|
/// Parse a chain of expressions separated by operators. Also handles function application.
|
|
fn expr_operator_chain<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
|
|
(move |arena, state: State<'a>, min_indent: u32| {
|
|
parse_expr_operator_chain(arena, state, min_indent, options)
|
|
})
|
|
.trace("expr_operator_chain")
|
|
}
|
|
|
|
fn parse_expr_operator_chain<'a>(
|
|
arena: &'a Bump,
|
|
state: State<'a>,
|
|
min_indent: u32,
|
|
options: ExprParseOptions,
|
|
) -> Result<(Progress, Expr<'a>, State<'a>), (Progress, EExpr<'a>)> {
|
|
let line_indent = state.line_indent();
|
|
|
|
let (_, expr, state) =
|
|
loc_possibly_negative_or_negated_term(options).parse(arena, state, min_indent)?;
|
|
|
|
let mut initial_state = state.clone();
|
|
|
|
let (spaces_before_op, state) =
|
|
match space0_e(EExpr::IndentEnd).parse(arena, state.clone(), min_indent) {
|
|
Err((_, _)) => return Ok((MadeProgress, expr.value, state)),
|
|
Ok((_, spaces_before_op, state)) => (spaces_before_op, state),
|
|
};
|
|
|
|
let mut expr_state = ExprState {
|
|
operators: Vec::new_in(arena),
|
|
arguments: Vec::new_in(arena),
|
|
expr,
|
|
spaces_after: spaces_before_op,
|
|
end: initial_state.pos(),
|
|
};
|
|
|
|
let mut state = state;
|
|
|
|
let call_min_indent = line_indent + 1;
|
|
|
|
loop {
|
|
let parser = skip_first(
|
|
crate::blankspace::check_indent(EExpr::IndentEnd),
|
|
loc_term_or_underscore(options),
|
|
);
|
|
match parser.parse(arena, state.clone(), call_min_indent) {
|
|
Err((MadeProgress, f)) => return Err((MadeProgress, f)),
|
|
Err((NoProgress, _)) => {
|
|
let before_op = state.clone();
|
|
// try an operator
|
|
return parse_expr_after_apply(
|
|
arena,
|
|
state,
|
|
min_indent,
|
|
call_min_indent,
|
|
options,
|
|
true,
|
|
expr_state,
|
|
before_op,
|
|
initial_state,
|
|
);
|
|
}
|
|
Ok((_, arg, new_state)) => {
|
|
state = new_state;
|
|
initial_state = state.clone();
|
|
|
|
if parse_after_expr_arg_and_check_final(
|
|
arena,
|
|
&mut state,
|
|
min_indent,
|
|
&mut expr_state,
|
|
arg,
|
|
) {
|
|
let expr = parse_expr_final(expr_state, arena);
|
|
return Ok((MadeProgress, expr, state));
|
|
}
|
|
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// We're part way thru parsing an expression, e.g. `bar foo `.
|
|
/// We just tried parsing an argument and determined we couldn't -
|
|
/// so we're going to try parsing an operator.
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn parse_expr_after_apply<'a>(
|
|
arena: &'a Bump,
|
|
state: State<'a>,
|
|
min_indent: u32,
|
|
call_min_indent: u32,
|
|
options: ExprParseOptions,
|
|
check_for_defs: bool,
|
|
mut expr_state: ExprState<'a>,
|
|
before_op: State<'a>,
|
|
initial_state: State<'a>,
|
|
) -> Result<(Progress, Expr<'a>, State<'a>), (Progress, EExpr<'a>)> {
|
|
match loc(bin_op(check_for_defs)).parse(arena, state.clone(), min_indent) {
|
|
Err((MadeProgress, f)) => Err((MadeProgress, f)),
|
|
Ok((_, loc_op, state)) => {
|
|
expr_state.consume_spaces(arena);
|
|
let initial_state = before_op;
|
|
parse_expr_operator(
|
|
arena,
|
|
state,
|
|
min_indent,
|
|
call_min_indent,
|
|
options,
|
|
check_for_defs,
|
|
expr_state,
|
|
loc_op,
|
|
initial_state,
|
|
)
|
|
}
|
|
Err((NoProgress, _)) => {
|
|
let expr = parse_expr_final(expr_state, arena);
|
|
// roll back space parsing
|
|
Ok((MadeProgress, expr, initial_state))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn expr_to_stmt(expr: Loc<Expr<'_>>) -> Loc<Stmt<'_>> {
|
|
Loc::at(expr.region, Stmt::Expr(expr.value))
|
|
}
|
|
|
|
pub fn parse_repl_defs_and_optional_expr<'a>(
|
|
arena: &'a Bump,
|
|
state: State<'a>,
|
|
) -> ParseResult<'a, (Defs<'a>, Option<Loc<Expr<'a>>>), EExpr<'a>> {
|
|
let initial_state = state.clone();
|
|
let (spaces_before, state) = match loc(space0_e(EExpr::IndentEnd)).parse(arena, state, 0) {
|
|
Err((NoProgress, _)) => return Ok((NoProgress, (Defs::default(), None), initial_state)),
|
|
Err((MadeProgress, e)) => return Err((MadeProgress, e)),
|
|
Ok((_, sp, state)) => (sp, state),
|
|
};
|
|
|
|
let (_, stmts, state) = parse_stmt_seq(
|
|
arena,
|
|
state,
|
|
|e, _| e.clone(),
|
|
ExprParseOptions {
|
|
accept_multi_backpassing: true,
|
|
check_for_arrow: true,
|
|
},
|
|
0,
|
|
spaces_before,
|
|
EExpr::IndentEnd,
|
|
)?;
|
|
|
|
let state = match space0_e(EExpr::IndentEnd).parse(arena, state.clone(), 0) {
|
|
Err((NoProgress, _)) => state,
|
|
Err((MadeProgress, e)) => return Err((MadeProgress, e)),
|
|
Ok((_, _, state)) => state,
|
|
};
|
|
|
|
if !state.has_reached_end() {
|
|
return Err((MadeProgress, EExpr::End(state.pos())));
|
|
}
|
|
|
|
let (defs, last_expr) =
|
|
stmts_to_defs(&stmts, Defs::default(), false, arena).map_err(|e| (MadeProgress, e))?;
|
|
|
|
Ok((MadeProgress, (defs, last_expr), state))
|
|
}
|
|
|
|
fn stmt_start<'a>(
|
|
options: ExprParseOptions,
|
|
preceding_comment: Region,
|
|
) -> impl Parser<'a, Loc<Stmt<'a>>, EExpr<'a>> {
|
|
one_of![
|
|
map(
|
|
loc(specialize_err(EExpr::If, if_expr_help(options))),
|
|
expr_to_stmt
|
|
),
|
|
map(
|
|
loc(specialize_err(EExpr::When, when::when_expr_help(options))),
|
|
expr_to_stmt
|
|
),
|
|
loc(specialize_err(
|
|
EExpr::Expect,
|
|
expect_help(options, preceding_comment)
|
|
)),
|
|
loc(specialize_err(
|
|
EExpr::Dbg,
|
|
dbg_stmt_help(options, preceding_comment)
|
|
)),
|
|
loc(specialize_err(EExpr::Import, map(import(), Stmt::ValueDef))),
|
|
map(
|
|
loc(specialize_err(EExpr::Closure, closure_help(options))),
|
|
expr_to_stmt
|
|
),
|
|
loc(stmt_operator_chain(options)),
|
|
fail_expr_start_e()
|
|
]
|
|
.trace("stmt_start")
|
|
}
|
|
|
|
fn stmt_operator_chain<'a>(options: ExprParseOptions) -> impl Parser<'a, Stmt<'a>, EExpr<'a>> {
|
|
(move |arena, state: State<'a>, min_indent: u32| {
|
|
parse_stmt_operator_chain(arena, state, min_indent, options)
|
|
})
|
|
.trace("stmt_operator_chain")
|
|
}
|
|
|
|
fn parse_stmt_operator_chain<'a>(
|
|
arena: &'a Bump,
|
|
state: State<'a>,
|
|
min_indent: u32,
|
|
options: ExprParseOptions,
|
|
) -> Result<(Progress, Stmt<'a>, State<'a>), (Progress, EExpr<'a>)> {
|
|
let line_indent = state.line_indent();
|
|
|
|
let (_, expr, state) =
|
|
loc_possibly_negative_or_negated_term(options).parse(arena, state, min_indent)?;
|
|
|
|
let mut initial_state = state.clone();
|
|
let end = state.pos();
|
|
|
|
let (spaces_before_op, state) =
|
|
match space0_e(EExpr::IndentEnd).parse(arena, state.clone(), min_indent) {
|
|
Err((_, _)) => return Ok((MadeProgress, Stmt::Expr(expr.value), state)),
|
|
Ok((_, spaces_before_op, state)) => (spaces_before_op, state),
|
|
};
|
|
|
|
let mut expr_state = ExprState {
|
|
operators: Vec::new_in(arena),
|
|
arguments: Vec::new_in(arena),
|
|
expr,
|
|
spaces_after: spaces_before_op,
|
|
end,
|
|
};
|
|
|
|
let mut state = state;
|
|
|
|
let call_min_indent = line_indent + 1;
|
|
|
|
loop {
|
|
let parser = skip_first(
|
|
crate::blankspace::check_indent(EExpr::IndentEnd),
|
|
loc_term_or_underscore(options),
|
|
);
|
|
match parser.parse(arena, state.clone(), call_min_indent) {
|
|
Err((MadeProgress, f)) => return Err((MadeProgress, f)),
|
|
Ok((
|
|
_,
|
|
implements @ Loc {
|
|
value:
|
|
Expr::Var {
|
|
module_name: "",
|
|
ident: crate::keyword::IMPLEMENTS,
|
|
..
|
|
},
|
|
..
|
|
},
|
|
state,
|
|
)) if matches!(expr_state.expr.value, Expr::Tag(..)) => {
|
|
return parse_ability_def(expr_state, state, arena, implements, call_min_indent)
|
|
.map(|(td, s)| (MadeProgress, Stmt::TypeDef(td), s));
|
|
}
|
|
Err((NoProgress, _)) => {
|
|
// try an operator
|
|
return parse_stmt_after_apply(
|
|
arena,
|
|
state,
|
|
min_indent,
|
|
call_min_indent,
|
|
expr_state,
|
|
options,
|
|
initial_state,
|
|
);
|
|
}
|
|
Ok((_, arg, new_state)) => {
|
|
state = new_state;
|
|
initial_state = state.clone();
|
|
|
|
if parse_after_expr_arg_and_check_final(
|
|
arena,
|
|
&mut state,
|
|
min_indent,
|
|
&mut expr_state,
|
|
arg,
|
|
) {
|
|
let expr = parse_expr_final(expr_state, arena);
|
|
return Ok((MadeProgress, Stmt::Expr(expr), state));
|
|
}
|
|
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parse_after_expr_arg_and_check_final<'a>(
|
|
arena: &'a Bump,
|
|
state: &mut State<'a>,
|
|
min_indent: u32,
|
|
expr_state: &mut ExprState<'a>,
|
|
mut arg: Loc<Expr<'a>>,
|
|
) -> bool {
|
|
let new_end = state.pos();
|
|
|
|
if !expr_state.spaces_after.is_empty() {
|
|
arg = arena
|
|
.alloc(arg.value)
|
|
.with_spaces_before(expr_state.spaces_after, arg.region);
|
|
|
|
expr_state.spaces_after = &[];
|
|
}
|
|
let new_spaces = match space0_e(EExpr::IndentEnd).parse(arena, state.clone(), min_indent) {
|
|
Err((_, _)) => {
|
|
expr_state.arguments.push(arena.alloc(arg));
|
|
expr_state.end = new_end;
|
|
expr_state.spaces_after = &[];
|
|
|
|
return true;
|
|
}
|
|
Ok((_, new_spaces, new_state)) => {
|
|
*state = new_state;
|
|
new_spaces
|
|
}
|
|
};
|
|
expr_state.arguments.push(arena.alloc(arg));
|
|
expr_state.end = new_end;
|
|
expr_state.spaces_after = new_spaces;
|
|
|
|
false
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct ExprState<'a> {
|
|
operators: Vec<'a, (Loc<Expr<'a>>, Loc<BinOp>)>,
|
|
arguments: Vec<'a, &'a Loc<Expr<'a>>>,
|
|
expr: Loc<Expr<'a>>,
|
|
spaces_after: &'a [CommentOrNewline<'a>],
|
|
end: Position,
|
|
}
|
|
|
|
impl<'a> ExprState<'a> {
|
|
fn consume_spaces(&mut self, arena: &'a Bump) {
|
|
if !self.spaces_after.is_empty() {
|
|
if let Some(last) = self.arguments.pop() {
|
|
let new = last.value.with_spaces_after(self.spaces_after, last.region);
|
|
|
|
self.arguments.push(arena.alloc(new));
|
|
} else {
|
|
let region = self.expr.region;
|
|
|
|
let mut value = Expr::Num("");
|
|
std::mem::swap(&mut self.expr.value, &mut value);
|
|
|
|
self.expr = arena
|
|
.alloc(value)
|
|
.with_spaces_after(self.spaces_after, region);
|
|
};
|
|
|
|
self.spaces_after = &[];
|
|
}
|
|
}
|
|
|
|
fn validate_assignment_or_backpassing<F>(
|
|
mut self,
|
|
arena: &'a Bump,
|
|
loc_op: Loc<OperatorOrDef>,
|
|
argument_error: F,
|
|
) -> Result<Loc<Expr<'a>>, EExpr<'a>>
|
|
where
|
|
F: Fn(Region, Position) -> EExpr<'a>,
|
|
{
|
|
if !self.operators.is_empty() {
|
|
// this `=` or `<-` likely occurred inline; treat it as an invalid operator
|
|
let opchar = match loc_op.value {
|
|
OperatorOrDef::Assignment => "=",
|
|
OperatorOrDef::Backpassing => "<-",
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
let fail = EExpr::BadOperator(opchar, loc_op.region.start());
|
|
|
|
Err(fail)
|
|
} else if !self.expr.value.is_tag()
|
|
&& !self.expr.value.is_opaque()
|
|
&& !self.arguments.is_empty()
|
|
&& !is_expr_suffixed(&self.expr.value)
|
|
{
|
|
let region = Region::across_all(self.arguments.iter().map(|v| &v.region));
|
|
|
|
Err(argument_error(region, loc_op.region.start()))
|
|
} else {
|
|
self.consume_spaces(arena);
|
|
Ok(to_call(arena, self.arguments, self.expr))
|
|
}
|
|
}
|
|
|
|
fn validate_is_type_def(
|
|
mut self,
|
|
arena: &'a Bump,
|
|
kind: Loc<AliasOrOpaque>,
|
|
) -> Result<(Loc<Expr<'a>>, Vec<'a, &'a Loc<Expr<'a>>>), EExpr<'a>> {
|
|
if !self.operators.is_empty() {
|
|
// this `:`/`:=` likely occurred inline; treat it as an invalid operator
|
|
let op = match kind.value {
|
|
AliasOrOpaque::Alias => ":",
|
|
AliasOrOpaque::Opaque => ":=",
|
|
};
|
|
let fail = EExpr::BadOperator(op, kind.region.start());
|
|
|
|
Err(fail)
|
|
} else {
|
|
self.consume_spaces(arena);
|
|
Ok((self.expr, self.arguments))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::unnecessary_wraps)]
|
|
fn parse_expr_final<'a>(expr_state: ExprState<'a>, arena: &'a Bump) -> Expr<'a> {
|
|
let right_arg = to_call(arena, expr_state.arguments, expr_state.expr);
|
|
|
|
if expr_state.operators.is_empty() {
|
|
right_arg.value
|
|
} else {
|
|
Expr::BinOps(
|
|
expr_state.operators.into_bump_slice(),
|
|
arena.alloc(right_arg),
|
|
)
|
|
}
|
|
}
|
|
|
|
fn to_call<'a>(
|
|
arena: &'a Bump,
|
|
mut arguments: Vec<'a, &'a Loc<Expr<'a>>>,
|
|
loc_expr1: Loc<Expr<'a>>,
|
|
) -> Loc<Expr<'a>> {
|
|
if arguments.is_empty() {
|
|
loc_expr1
|
|
} else {
|
|
let last = arguments.last().map(|x| x.region).unwrap_or_default();
|
|
let region = Region::span_across(&loc_expr1.region, &last);
|
|
|
|
let spaces = if let Some(last) = arguments.last_mut() {
|
|
let spaces = last.value.extract_spaces();
|
|
|
|
if spaces.after.is_empty() {
|
|
&[]
|
|
} else {
|
|
let inner = if !spaces.before.is_empty() {
|
|
arena.alloc(spaces.item).before(spaces.before)
|
|
} else {
|
|
spaces.item
|
|
};
|
|
*last = arena.alloc(Loc::at(last.region, inner));
|
|
|
|
spaces.after
|
|
}
|
|
} else {
|
|
&[]
|
|
};
|
|
|
|
let mut apply = Expr::Apply(
|
|
arena.alloc(loc_expr1),
|
|
arguments.into_bump_slice(),
|
|
CalledVia::Space,
|
|
);
|
|
|
|
if !spaces.is_empty() {
|
|
apply = arena.alloc(apply).after(spaces)
|
|
}
|
|
|
|
Loc::at(region, apply)
|
|
}
|
|
}
|
|
|
|
fn numeric_negate_expression<'a, T>(
|
|
arena: &'a Bump,
|
|
state: State<'a>,
|
|
loc_op: Loc<T>,
|
|
expr: Loc<Expr<'a>>,
|
|
spaces: &'a [CommentOrNewline<'a>],
|
|
) -> Loc<Expr<'a>> {
|
|
debug_assert_eq!(state.bytes().first(), Some(&b'-'));
|
|
// for overflow reasons, we must make the unary minus part of the number literal.
|
|
let start = state.pos();
|
|
let region = Region::new(start, expr.region.end());
|
|
|
|
let new_expr = match expr.value {
|
|
Expr::Num(string) => {
|
|
let new_string =
|
|
unsafe { std::str::from_utf8_unchecked(&state.bytes()[..string.len() + 1]) };
|
|
|
|
Expr::Num(new_string)
|
|
}
|
|
Expr::Float(string) => {
|
|
let new_string =
|
|
unsafe { std::str::from_utf8_unchecked(&state.bytes()[..string.len() + 1]) };
|
|
|
|
Expr::Float(new_string)
|
|
}
|
|
Expr::NonBase10Int {
|
|
string,
|
|
base,
|
|
is_negative,
|
|
} => {
|
|
// don't include the minus sign here; it will not be parsed right
|
|
Expr::NonBase10Int {
|
|
is_negative: !is_negative,
|
|
string,
|
|
base,
|
|
}
|
|
}
|
|
_ => Expr::UnaryOp(arena.alloc(expr), Loc::at(loc_op.region, UnaryOp::Negate)),
|
|
};
|
|
|
|
let new_loc_expr = Loc::at(region, new_expr);
|
|
|
|
if spaces.is_empty() {
|
|
new_loc_expr
|
|
} else {
|
|
arena
|
|
.alloc(new_loc_expr.value)
|
|
.with_spaces_before(spaces, new_loc_expr.region)
|
|
}
|
|
}
|
|
|
|
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, loc(record_help())),
|
|
),
|
|
|arena, state, _, (before, loc_record): (_, Loc<RecordHelp<'a>>)| {
|
|
if let Some(prefix) = loc_record.value.prefix {
|
|
match prefix {
|
|
(update, RecordHelpPrefix::Update) => {
|
|
return Err((
|
|
MadeProgress,
|
|
EImportParams::RecordUpdateFound(update.region),
|
|
))
|
|
}
|
|
(mapper, RecordHelpPrefix::Mapper) => {
|
|
return Err((
|
|
MadeProgress,
|
|
EImportParams::RecordBuilderFound(mapper.region),
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
let params = loc_record
|
|
.value
|
|
.fields
|
|
.map_items_result(arena, |loc_field| {
|
|
match loc_field.value.to_assigned_field(arena) {
|
|
Ok(AssignedField::IgnoredValue(_, _, _)) => Err((
|
|
MadeProgress,
|
|
EImportParams::RecordIgnoredFieldFound(loc_field.region),
|
|
)),
|
|
Ok(field) => Ok(Loc::at(loc_field.region, field)),
|
|
Err(FoundApplyValue) => Err((
|
|
MadeProgress,
|
|
EImportParams::RecordApplyFound(loc_field.region),
|
|
)),
|
|
}
|
|
})?;
|
|
|
|
let import_params = ModuleImportParams {
|
|
before,
|
|
params: Loc::at(loc_record.region, 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: header::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: header::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: header::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))
|
|
})
|
|
}
|
|
|
|
fn alias_signature<'a>() -> impl Parser<'a, Loc<TypeAnnotation<'a>>, EExpr<'a>> {
|
|
increment_min_indent(specialize_err(EExpr::Type, type_annotation::located(false)))
|
|
}
|
|
|
|
fn opaque_signature<'a>() -> impl Parser<
|
|
'a,
|
|
(
|
|
Loc<TypeAnnotation<'a>>,
|
|
Option<Loc<ImplementsAbilities<'a>>>,
|
|
),
|
|
EExpr<'a>,
|
|
> {
|
|
and(
|
|
specialize_err(EExpr::Type, type_annotation::located_opaque_signature(true)),
|
|
optional(backtrackable(specialize_err(
|
|
EExpr::Type,
|
|
space0_before_e(type_annotation::implements_abilities(), EType::TIndentStart),
|
|
))),
|
|
)
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
enum AliasOrOpaque {
|
|
Alias, // ':'
|
|
Opaque, // ':='
|
|
}
|
|
|
|
fn extract_tag_and_spaces<'a>(arena: &'a Bump, expr: Expr<'a>) -> Option<Spaces<'a, &'a str>> {
|
|
let mut expr = expr.extract_spaces();
|
|
|
|
loop {
|
|
match &expr.item {
|
|
Expr::ParensAround(inner_expr) => {
|
|
let inner_expr = inner_expr.extract_spaces();
|
|
expr.item = inner_expr.item;
|
|
expr.before = merge_spaces(arena, expr.before, inner_expr.before);
|
|
expr.after = merge_spaces(arena, inner_expr.after, expr.after);
|
|
}
|
|
Expr::Tag(tag) => {
|
|
return Some(Spaces {
|
|
before: expr.before,
|
|
item: tag,
|
|
after: expr.after,
|
|
});
|
|
}
|
|
_ => return None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// We just saw a ':' or ':=', and we're trying to parse an alias or opaque type definition.
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn parse_stmt_alias_or_opaque<'a>(
|
|
arena: &'a Bump,
|
|
state: State<'a>,
|
|
min_indent: u32,
|
|
expr_state: ExprState<'a>,
|
|
kind: Loc<AliasOrOpaque>,
|
|
spaces_after_operator: &'a [CommentOrNewline<'a>],
|
|
) -> ParseResult<'a, Stmt<'a>, EExpr<'a>> {
|
|
let expr_region = expr_state.expr.region;
|
|
let indented_more = min_indent + 1;
|
|
|
|
let (expr, arguments) = expr_state
|
|
.validate_is_type_def(arena, kind)
|
|
.map_err(|fail| (MadeProgress, fail))?;
|
|
|
|
let (res, state) = if let Some(tag) = extract_tag_and_spaces(arena, expr.value) {
|
|
let name = tag.item;
|
|
let mut type_arguments = Vec::with_capacity_in(arguments.len(), arena);
|
|
|
|
for argument in arguments {
|
|
match expr_to_pattern_help(arena, &argument.value) {
|
|
Ok(good) => {
|
|
type_arguments.push(Loc::at(argument.region, good));
|
|
}
|
|
Err(()) => {
|
|
return Err((
|
|
MadeProgress,
|
|
EExpr::Pattern(
|
|
arena.alloc(EPattern::NotAPattern(state.pos())),
|
|
state.pos(),
|
|
),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
match kind.value {
|
|
AliasOrOpaque::Alias => {
|
|
let (_, signature, state) = alias_signature().parse(arena, state, min_indent)?;
|
|
|
|
// TODO: this code used to be broken and it dropped the spaces after the operator.
|
|
// The formatter is not expecting this, so let's keep it as is for now.
|
|
// let signature = signature.map(|v| v.maybe_before(arena, spaces_after_operator));
|
|
|
|
let header = TypeHeader {
|
|
name: Loc::at(expr.region, name),
|
|
vars: type_arguments.into_bump_slice(),
|
|
};
|
|
|
|
let def = TypeDef::Alias {
|
|
header,
|
|
ann: signature,
|
|
};
|
|
|
|
(Stmt::TypeDef(def), state)
|
|
}
|
|
|
|
AliasOrOpaque::Opaque => {
|
|
let (_, (signature, derived), state) =
|
|
opaque_signature().parse(arena, state, indented_more)?;
|
|
|
|
// TODO: this code used to be broken and it dropped the spaces after the operator.
|
|
// The formatter is not expecting this, so let's keep it as is for now.
|
|
// let signature = signature.map(|v| v.maybe_before(arena, spaces_after_operator));
|
|
|
|
let header = TypeHeader {
|
|
name: Loc::at(expr.region, name),
|
|
vars: type_arguments.into_bump_slice(),
|
|
};
|
|
|
|
let def = TypeDef::Opaque {
|
|
header,
|
|
typ: signature,
|
|
derived,
|
|
};
|
|
|
|
(Stmt::TypeDef(def), state)
|
|
}
|
|
}
|
|
} else {
|
|
let call = to_call(arena, arguments, expr);
|
|
|
|
match expr_to_pattern_help(arena, &call.value) {
|
|
Ok(good) => {
|
|
let parser = specialize_err(
|
|
EExpr::Type,
|
|
space0_before_e(
|
|
set_min_indent(indented_more, type_annotation::located(false)),
|
|
EType::TIndentStart,
|
|
),
|
|
);
|
|
|
|
match parser.parse(arena, state.clone(), min_indent) {
|
|
Err((_, fail)) => return Err((MadeProgress, fail)),
|
|
Ok((_, mut ann_type, state)) => {
|
|
// put the spaces from after the operator in front of the call
|
|
if !spaces_after_operator.is_empty() {
|
|
ann_type = arena
|
|
.alloc(ann_type.value)
|
|
.with_spaces_before(spaces_after_operator, ann_type.region);
|
|
}
|
|
|
|
let value_def = ValueDef::Annotation(Loc::at(expr_region, good), ann_type);
|
|
|
|
(Stmt::ValueDef(value_def), state)
|
|
}
|
|
}
|
|
}
|
|
Err(_) => {
|
|
// this `:`/`:=` likely occurred inline; treat it as an invalid operator
|
|
let op = match kind.value {
|
|
AliasOrOpaque::Alias => ":",
|
|
AliasOrOpaque::Opaque => ":=",
|
|
};
|
|
let fail = EExpr::BadOperator(op, kind.region.start());
|
|
|
|
return Err((MadeProgress, fail));
|
|
}
|
|
}
|
|
};
|
|
|
|
Ok((MadeProgress, res, state))
|
|
}
|
|
|
|
mod ability {
|
|
use parser::absolute_indented_seq;
|
|
|
|
use super::*;
|
|
use crate::{
|
|
ast::{AbilityMember, Spaceable, Spaced},
|
|
parser::EAbility,
|
|
};
|
|
|
|
/// Parses a single ability demand line; see `parse_demand`.
|
|
fn parse_demand_help<'a>() -> impl Parser<'a, AbilityMember<'a>, EAbility<'a>> {
|
|
map(
|
|
// Require the type to be more indented than the name
|
|
absolute_indented_seq(
|
|
specialize_err(|_, pos| EAbility::DemandName(pos), loc(lowercase_ident())),
|
|
skip_first(
|
|
and(
|
|
// TODO: do we get anything from picking up spaces here?
|
|
space0_e(EAbility::DemandName),
|
|
byte(b':', EAbility::DemandColon),
|
|
),
|
|
specialize_err(EAbility::Type, type_annotation::located(true)),
|
|
),
|
|
),
|
|
|(name, typ): (Loc<&'a str>, Loc<TypeAnnotation<'a>>)| AbilityMember {
|
|
name: name.map_owned(Spaced::Item),
|
|
typ,
|
|
},
|
|
)
|
|
}
|
|
|
|
pub enum IndentLevel {
|
|
PendingMin(u32),
|
|
Exact(u32),
|
|
}
|
|
|
|
/// Parses an ability demand like `hash : a -> U64 where a implements Hash`, in the context of a larger
|
|
/// ability definition.
|
|
/// This is basically the same as parsing a free-floating annotation, but with stricter rules.
|
|
pub fn parse_demand<'a>(
|
|
indent: IndentLevel,
|
|
) -> impl Parser<'a, (u32, AbilityMember<'a>), EAbility<'a>> {
|
|
move |arena, state: State<'a>, min_indent: u32| {
|
|
// Put no restrictions on the indent after the spaces; we'll check it manually.
|
|
match space0_e(EAbility::DemandName).parse(arena, state, 0) {
|
|
Err((MadeProgress, fail)) => Err((NoProgress, fail)),
|
|
Err((NoProgress, fail)) => Err((NoProgress, fail)),
|
|
|
|
Ok((_progress, spaces, state)) => {
|
|
match indent {
|
|
IndentLevel::PendingMin(min_indent) if state.column() < min_indent => {
|
|
let indent_difference = state.column() as i32 - min_indent as i32;
|
|
Err((
|
|
MadeProgress,
|
|
EAbility::DemandAlignment(indent_difference, state.pos()),
|
|
))
|
|
}
|
|
IndentLevel::Exact(wanted) if state.column() < wanted => {
|
|
// This demand is not indented correctly
|
|
let indent_difference = state.column() as i32 - wanted as i32;
|
|
Err((
|
|
// Rollback because the deindent may be because there is a next
|
|
// expression
|
|
NoProgress,
|
|
EAbility::DemandAlignment(indent_difference, state.pos()),
|
|
))
|
|
}
|
|
IndentLevel::Exact(wanted) if state.column() > wanted => {
|
|
// This demand is not indented correctly
|
|
let indent_difference = state.column() as i32 - wanted as i32;
|
|
|
|
// We might be trying to parse at EOF, at which case the indent level
|
|
// will be off, but there is actually nothing left.
|
|
let progress = if state.has_reached_end() {
|
|
NoProgress
|
|
} else {
|
|
MadeProgress
|
|
};
|
|
|
|
Err((
|
|
progress,
|
|
EAbility::DemandAlignment(indent_difference, state.pos()),
|
|
))
|
|
}
|
|
_ => {
|
|
let indent_column = state.column();
|
|
|
|
let parser = parse_demand_help();
|
|
|
|
match parser.parse(arena, state.clone(), min_indent) {
|
|
Err((MadeProgress, fail)) => Err((MadeProgress, fail)),
|
|
Err((NoProgress, fail)) => {
|
|
// We made progress relative to the entire ability definition,
|
|
// so this is an error.
|
|
Err((MadeProgress, fail))
|
|
}
|
|
|
|
Ok((_, mut demand, state)) => {
|
|
// Tag spaces onto the parsed demand name
|
|
if !spaces.is_empty() {
|
|
demand.name = arena
|
|
.alloc(demand.name.value)
|
|
.with_spaces_before(spaces, demand.name.region);
|
|
}
|
|
|
|
Ok((MadeProgress, (indent_column, demand), state))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Parse the series of "demands" (e.g. similar to methods in a rust trait), for an ability definition.
|
|
fn finish_parsing_ability_def_help<'a>(
|
|
call_min_indent: u32,
|
|
name: Loc<&'a str>,
|
|
args: &'a [Loc<Pattern<'a>>],
|
|
loc_implements: Loc<Implements<'a>>,
|
|
arena: &'a Bump,
|
|
state: State<'a>,
|
|
) -> ParseResult<'a, (TypeDef<'a>, Region), EExpr<'a>> {
|
|
let mut demands = Vec::with_capacity_in(2, arena);
|
|
|
|
// Parse the first demand. This will determine the indentation level all the
|
|
// other demands must observe.
|
|
let start = state.pos();
|
|
let (_, (demand_indent_level, first_demand), mut state) =
|
|
ability::parse_demand(ability::IndentLevel::PendingMin(call_min_indent))
|
|
.trace("ability_demand")
|
|
.parse(arena, state, call_min_indent)
|
|
.map_err(|(progress, err)| (progress, EExpr::Ability(err, start)))?;
|
|
demands.push(first_demand);
|
|
|
|
let demand_indent = ability::IndentLevel::Exact(demand_indent_level);
|
|
let demand_parser = ability::parse_demand(demand_indent).trace("ability_demand");
|
|
|
|
loop {
|
|
match demand_parser.parse(arena, state.clone(), call_min_indent) {
|
|
Ok((_, (_indent, demand), next_state)) => {
|
|
state = next_state;
|
|
demands.push(demand);
|
|
}
|
|
Err((MadeProgress, problem)) => {
|
|
return Err((MadeProgress, EExpr::Ability(problem, state.pos())));
|
|
}
|
|
Err((NoProgress, _)) => {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
let def_region = Region::span_across(&name.region, &demands.last().unwrap().typ.region);
|
|
let type_def = TypeDef::Ability {
|
|
header: TypeHeader { name, vars: args },
|
|
loc_implements,
|
|
members: demands.into_bump_slice(),
|
|
};
|
|
|
|
Ok((MadeProgress, (type_def, def_region), state))
|
|
}
|
|
|
|
/// A Stmt is an intermediate representation used only during parsing.
|
|
/// It consists of a fragment of code that hasn't been fully stitched together yet.
|
|
/// For example, each of the following lines is a Stmt:
|
|
/// - `foo bar` (Expr)
|
|
/// - `foo, bar <- baz` (Backpassing)
|
|
/// - `Foo : [A, B, C]` (TypeDef)
|
|
/// - `foo = \x -> x + 1` (ValueDef)
|
|
///
|
|
/// Note in particular that the Backpassing Stmt doesn't make any sense on its own;
|
|
/// we need to link it up with the following stmts to make a complete expression.
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum Stmt<'a> {
|
|
Expr(Expr<'a>),
|
|
Backpassing(&'a [Loc<Pattern<'a>>], &'a Loc<Expr<'a>>),
|
|
TypeDef(TypeDef<'a>),
|
|
ValueDef(ValueDef<'a>),
|
|
}
|
|
|
|
/// Having just parsed an operator, we need to dispatch to the appropriate
|
|
/// parsing function based on the operator.
|
|
///
|
|
/// Note, this function is very similar to `parse_expr_operator`, but it
|
|
/// handles additional cases to allow assignments / type annotations / etc.
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn parse_stmt_operator<'a>(
|
|
arena: &'a Bump,
|
|
state: State<'a>,
|
|
min_indent: u32,
|
|
call_min_indent: u32,
|
|
options: ExprParseOptions,
|
|
expr_state: ExprState<'a>,
|
|
loc_op: Loc<OperatorOrDef>,
|
|
initial_state: State<'a>,
|
|
) -> ParseResult<'a, Stmt<'a>, EExpr<'a>> {
|
|
let (_, spaces_after_operator, state) =
|
|
loc_space0_e(EExpr::IndentEnd).parse(arena, state, min_indent)?;
|
|
|
|
// a `-` is unary if it is preceded by a space and not followed by a space
|
|
|
|
let op = loc_op.value;
|
|
let op_start = loc_op.region.start();
|
|
let op_end = loc_op.region.end();
|
|
let new_start = state.pos();
|
|
match op {
|
|
OperatorOrDef::BinOp(BinOp::Minus) if expr_state.end != op_start && op_end == new_start => {
|
|
parse_negated_term(
|
|
arena,
|
|
state,
|
|
min_indent,
|
|
call_min_indent,
|
|
expr_state,
|
|
options,
|
|
initial_state,
|
|
loc_op.with_value(BinOp::Minus),
|
|
)
|
|
.map(|(progress, expr, state)| (progress, Stmt::Expr(expr), state))
|
|
}
|
|
OperatorOrDef::BinOp(op) => parse_after_binop(
|
|
arena,
|
|
state,
|
|
min_indent,
|
|
call_min_indent,
|
|
options,
|
|
true,
|
|
spaces_after_operator.value,
|
|
expr_state,
|
|
loc_op.with_value(op),
|
|
)
|
|
.map(|(progress, expr, state)| (progress, Stmt::Expr(expr), state)),
|
|
OperatorOrDef::Assignment => parse_stmt_assignment(
|
|
arena,
|
|
state,
|
|
call_min_indent,
|
|
expr_state,
|
|
loc_op,
|
|
options,
|
|
spaces_after_operator,
|
|
),
|
|
OperatorOrDef::Backpassing => parse_stmt_backpassing(
|
|
arena,
|
|
state,
|
|
call_min_indent,
|
|
expr_state,
|
|
loc_op,
|
|
options,
|
|
spaces_after_operator.value,
|
|
),
|
|
OperatorOrDef::AliasOrOpaque(kind) => parse_stmt_alias_or_opaque(
|
|
arena,
|
|
state,
|
|
call_min_indent,
|
|
expr_state,
|
|
loc_op.with_value(kind),
|
|
spaces_after_operator.value,
|
|
),
|
|
}
|
|
}
|
|
|
|
/// We just parsed an operator. Parse the expression that follows, taking special care
|
|
/// that this might be a negated term. (`-x` is a negated term, not a binary operation)
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn parse_expr_operator<'a>(
|
|
arena: &'a Bump,
|
|
state: State<'a>,
|
|
min_indent: u32,
|
|
call_min_indent: u32,
|
|
options: ExprParseOptions,
|
|
check_for_defs: bool,
|
|
expr_state: ExprState<'a>,
|
|
loc_op: Loc<BinOp>,
|
|
initial_state: State<'a>,
|
|
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
|
|
let (_, spaces_after_operator, state) =
|
|
space0_e(EExpr::IndentEnd).parse(arena, state, min_indent)?;
|
|
|
|
// a `-` is unary if it is preceded by a space and not followed by a space
|
|
|
|
let op = loc_op.value;
|
|
let op_start = loc_op.region.start();
|
|
let op_end = loc_op.region.end();
|
|
let new_start = state.pos();
|
|
match op {
|
|
BinOp::Minus if expr_state.end != op_start && op_end == new_start => parse_negated_term(
|
|
arena,
|
|
state,
|
|
min_indent,
|
|
call_min_indent,
|
|
expr_state,
|
|
options,
|
|
initial_state,
|
|
loc_op,
|
|
),
|
|
_ => parse_after_binop(
|
|
arena,
|
|
state,
|
|
min_indent,
|
|
call_min_indent,
|
|
options,
|
|
check_for_defs,
|
|
spaces_after_operator,
|
|
expr_state,
|
|
loc_op,
|
|
),
|
|
}
|
|
}
|
|
|
|
/// Continue parsing terms after we just parsed a binary operator
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn parse_after_binop<'a>(
|
|
arena: &'a Bump,
|
|
state: State<'a>,
|
|
min_indent: u32,
|
|
call_min_indent: u32,
|
|
options: ExprParseOptions,
|
|
check_for_defs: bool,
|
|
spaces_after_operator: &'a [CommentOrNewline],
|
|
mut expr_state: ExprState<'a>,
|
|
loc_op: Loc<BinOp>,
|
|
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
|
|
match loc_possibly_negative_or_negated_term(options).parse(
|
|
arena,
|
|
state.clone(),
|
|
call_min_indent,
|
|
) {
|
|
Err((MadeProgress, f)) => Err((MadeProgress, f)),
|
|
Ok((_, mut new_expr, state)) => {
|
|
let new_end = state.pos();
|
|
|
|
let initial_state = state.clone();
|
|
|
|
// put the spaces from after the operator in front of the new_expr
|
|
if !spaces_after_operator.is_empty() {
|
|
new_expr = arena
|
|
.alloc(new_expr.value)
|
|
.with_spaces_before(spaces_after_operator, new_expr.region);
|
|
}
|
|
|
|
match space0_e(EExpr::IndentEnd).parse(arena, state.clone(), min_indent) {
|
|
Err((_, _)) => {
|
|
let args = std::mem::replace(&mut expr_state.arguments, Vec::new_in(arena));
|
|
|
|
let call = to_call(arena, args, expr_state.expr);
|
|
|
|
expr_state.operators.push((call, loc_op));
|
|
expr_state.expr = new_expr;
|
|
expr_state.end = new_end;
|
|
expr_state.spaces_after = &[];
|
|
|
|
let expr = parse_expr_final(expr_state, arena);
|
|
Ok((MadeProgress, expr, state))
|
|
}
|
|
Ok((_, spaces, state)) => {
|
|
let args = std::mem::replace(&mut expr_state.arguments, Vec::new_in(arena));
|
|
|
|
let call = to_call(arena, args, expr_state.expr);
|
|
|
|
expr_state.operators.push((call, loc_op));
|
|
expr_state.expr = new_expr;
|
|
expr_state.end = new_end;
|
|
expr_state.spaces_after = spaces;
|
|
|
|
parse_expr_end(
|
|
arena,
|
|
state,
|
|
min_indent,
|
|
call_min_indent,
|
|
options,
|
|
check_for_defs,
|
|
expr_state,
|
|
initial_state,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
Err((NoProgress, _e)) => {
|
|
return Err((MadeProgress, EExpr::TrailingOperator(state.pos())));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Parse the rest of a backpassing statement, after the <- operator
|
|
fn parse_stmt_backpassing<'a>(
|
|
arena: &'a Bump,
|
|
state: State<'a>,
|
|
call_min_indent: u32,
|
|
expr_state: ExprState<'a>,
|
|
loc_op: Loc<OperatorOrDef>,
|
|
options: ExprParseOptions,
|
|
spaces_after_operator: &'a [CommentOrNewline],
|
|
) -> ParseResult<'a, Stmt<'a>, EExpr<'a>> {
|
|
let expr_region = expr_state.expr.region;
|
|
|
|
let call = expr_state
|
|
.validate_assignment_or_backpassing(arena, loc_op, |_, pos| EExpr::BadOperator("<-", pos))
|
|
.map_err(|fail| (MadeProgress, fail))?;
|
|
|
|
let (loc_pattern, loc_body, state) = {
|
|
match expr_to_pattern_help(arena, &call.value) {
|
|
Ok(good) => {
|
|
let (_, mut ann_type, state) =
|
|
expr_start(options).parse(arena, state, call_min_indent)?;
|
|
|
|
// put the spaces from after the operator in front of the call
|
|
if !spaces_after_operator.is_empty() {
|
|
ann_type = arena
|
|
.alloc(ann_type.value)
|
|
.with_spaces_before(spaces_after_operator, ann_type.region);
|
|
}
|
|
|
|
(Loc::at(expr_region, good), ann_type, state)
|
|
}
|
|
Err(_) => {
|
|
// this `=` likely occurred inline; treat it as an invalid operator
|
|
let fail = EExpr::BadOperator("=", loc_op.region.start());
|
|
|
|
return Err((MadeProgress, fail));
|
|
}
|
|
}
|
|
};
|
|
|
|
let ret = Stmt::Backpassing(arena.alloc([loc_pattern]), arena.alloc(loc_body));
|
|
|
|
Ok((MadeProgress, ret, state))
|
|
}
|
|
|
|
/// We just saw a `,` that we think is part of a backpassing statement.
|
|
/// Parse the rest of the statement.
|
|
fn parse_stmt_multi_backpassing<'a>(
|
|
mut expr_state: ExprState<'a>,
|
|
arena: &'a Bump,
|
|
state: State<'a>,
|
|
min_indent: u32,
|
|
options: ExprParseOptions,
|
|
) -> ParseResult<'a, Stmt<'a>, EExpr<'a>> {
|
|
// called after parsing the first , in `a, b <- c` (e.g.)
|
|
|
|
let (_, mut patterns, state) = specialize_err_ref(
|
|
EExpr::Pattern,
|
|
crate::parser::sep_by0(
|
|
byte(b',', EPattern::Start),
|
|
space0_around_ee(
|
|
crate::pattern::loc_pattern_help(),
|
|
EPattern::Start,
|
|
EPattern::IndentEnd,
|
|
),
|
|
),
|
|
)
|
|
.parse(arena, state, min_indent)
|
|
.map_err(|(progress, err)| {
|
|
// We were expecting the end of an expression, and parsed a comma
|
|
// therefore we are either on the LHS of backpassing or this is was
|
|
// in an invalid position.
|
|
if let EExpr::Pattern(EPattern::IndentEnd(_), pos) = err {
|
|
(progress, EExpr::UnexpectedComma(pos.sub(1)))
|
|
} else {
|
|
(progress, err)
|
|
}
|
|
})?;
|
|
|
|
expr_state.consume_spaces(arena);
|
|
let call = to_call(arena, expr_state.arguments, expr_state.expr);
|
|
|
|
let pattern = expr_to_pattern_help(arena, &call.value).map_err(|()| {
|
|
(
|
|
MadeProgress,
|
|
EExpr::Pattern(arena.alloc(EPattern::NotAPattern(state.pos())), state.pos()),
|
|
)
|
|
})?;
|
|
|
|
let loc_pattern = Loc::at(call.region, pattern);
|
|
|
|
patterns.insert(0, loc_pattern);
|
|
|
|
let line_indent = state.line_indent();
|
|
|
|
match two_bytes(b'<', b'-', EExpr::BackpassArrow).parse(arena, state.clone(), min_indent) {
|
|
Err((_, fail)) => Err((MadeProgress, fail)),
|
|
Ok((_, _, state)) => {
|
|
let parse_body = space0_before_e(expr_start(options), EExpr::IndentEnd);
|
|
|
|
let (_, loc_body, state) = parse_body.parse(arena, state, line_indent + 1)?;
|
|
|
|
let ret = Stmt::Backpassing(patterns.into_bump_slice(), arena.alloc(loc_body));
|
|
|
|
Ok((MadeProgress, ret, state))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// We just saw the '=' operator of an assignment stmt. Continue parsing from there.
|
|
fn parse_stmt_assignment<'a>(
|
|
arena: &'a Bump,
|
|
state: State<'a>,
|
|
call_min_indent: u32,
|
|
expr_state: ExprState<'a>,
|
|
loc_op: Loc<OperatorOrDef>,
|
|
options: ExprParseOptions,
|
|
spaces_after_operator: Loc<&'a [CommentOrNewline]>,
|
|
) -> ParseResult<'a, Stmt<'a>, EExpr<'a>> {
|
|
let call = expr_state
|
|
.validate_assignment_or_backpassing(arena, loc_op, EExpr::ElmStyleFunction)
|
|
.map_err(|fail| (MadeProgress, fail))?;
|
|
|
|
let (value_def, state) = {
|
|
match expr_to_pattern_help(arena, &call.value) {
|
|
Ok(good) => {
|
|
let (_, body, state) = parse_block_inner(
|
|
options,
|
|
arena,
|
|
state,
|
|
call_min_indent,
|
|
EExpr::IndentEnd,
|
|
|a, _| a.clone(),
|
|
spaces_after_operator,
|
|
!spaces_after_operator.value.is_empty(),
|
|
)?;
|
|
|
|
let alias =
|
|
ValueDef::Body(arena.alloc(Loc::at(call.region, good)), arena.alloc(body));
|
|
|
|
(alias, state)
|
|
}
|
|
Err(_) => {
|
|
// this `=` likely occurred inline; treat it as an invalid operator
|
|
let fail = EExpr::BadOperator(arena.alloc("="), loc_op.region.start());
|
|
|
|
return Err((MadeProgress, fail));
|
|
}
|
|
}
|
|
};
|
|
|
|
Ok((MadeProgress, Stmt::ValueDef(value_def), state))
|
|
}
|
|
|
|
/// We just saw a unary negation operator, and now we need to parse the expression.
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn parse_negated_term<'a>(
|
|
arena: &'a Bump,
|
|
state: State<'a>,
|
|
min_indent: u32,
|
|
call_min_indent: u32,
|
|
mut expr_state: ExprState<'a>,
|
|
options: ExprParseOptions,
|
|
initial_state: State<'a>,
|
|
loc_op: Loc<BinOp>,
|
|
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
|
|
let (_, negated_expr, state) = loc_term(options).parse(arena, state, min_indent)?;
|
|
let new_end = state.pos();
|
|
|
|
let arg = numeric_negate_expression(
|
|
arena,
|
|
initial_state,
|
|
loc_op,
|
|
negated_expr,
|
|
expr_state.spaces_after,
|
|
);
|
|
|
|
let initial_state = state.clone();
|
|
|
|
let (spaces, state) = match space0_e(EExpr::IndentEnd).parse(arena, state.clone(), min_indent) {
|
|
Err((_, _)) => (&[] as &[_], state),
|
|
Ok((_, spaces, state)) => (spaces, state),
|
|
};
|
|
|
|
expr_state.arguments.push(arena.alloc(arg));
|
|
expr_state.spaces_after = spaces;
|
|
expr_state.end = new_end;
|
|
|
|
// TODO: this should probably be handled in the caller, not here
|
|
parse_expr_end(
|
|
arena,
|
|
state,
|
|
min_indent,
|
|
call_min_indent,
|
|
options,
|
|
true,
|
|
expr_state,
|
|
initial_state,
|
|
)
|
|
}
|
|
|
|
/// Parse an expression, not allowing `if`/`when`/etc.
|
|
/// TODO: this should probably be subsumed into `parse_expr_operator_chain`
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn parse_expr_end<'a>(
|
|
arena: &'a Bump,
|
|
state: State<'a>,
|
|
min_indent: u32,
|
|
call_min_indent: u32,
|
|
options: ExprParseOptions,
|
|
check_for_defs: bool,
|
|
mut expr_state: ExprState<'a>,
|
|
initial_state: State<'a>,
|
|
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
|
|
let parser = skip_first(
|
|
crate::blankspace::check_indent(EExpr::IndentEnd),
|
|
loc_term_or_underscore(options),
|
|
);
|
|
|
|
match parser.parse(arena, state.clone(), call_min_indent) {
|
|
Err((MadeProgress, f)) => Err((MadeProgress, f)),
|
|
Ok((_, arg, state)) => parse_apply_arg(
|
|
arena,
|
|
state,
|
|
min_indent,
|
|
call_min_indent,
|
|
expr_state,
|
|
arg,
|
|
options,
|
|
check_for_defs,
|
|
),
|
|
Err((NoProgress, _)) => {
|
|
let before_op = state.clone();
|
|
// try an operator
|
|
match loc(bin_op(check_for_defs)).parse(arena, state.clone(), min_indent) {
|
|
Err((MadeProgress, f)) => Err((MadeProgress, f)),
|
|
Ok((_, loc_op, state)) => {
|
|
expr_state.consume_spaces(arena);
|
|
let initial_state = before_op;
|
|
parse_expr_operator(
|
|
arena,
|
|
state,
|
|
min_indent,
|
|
call_min_indent,
|
|
options,
|
|
check_for_defs,
|
|
expr_state,
|
|
loc_op,
|
|
initial_state,
|
|
)
|
|
}
|
|
Err((NoProgress, _)) => {
|
|
let expr = parse_expr_final(expr_state, arena);
|
|
// roll back space parsing
|
|
Ok((MadeProgress, expr, initial_state))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// We're part way thru parsing an expression, e.g. `bar foo `.
|
|
/// We just tried parsing an argument and determined we couldn't -
|
|
/// so we're going to try parsing an operator.
|
|
///
|
|
/// Note that this looks a lot like `parse_expr_after_apply`, except
|
|
/// we handle the additional case of backpassing, which is valid
|
|
/// at the statement level but not at the expression level.
|
|
fn parse_stmt_after_apply<'a>(
|
|
arena: &'a Bump,
|
|
state: State<'a>,
|
|
min_indent: u32,
|
|
call_min_indent: u32,
|
|
mut expr_state: ExprState<'a>,
|
|
options: ExprParseOptions,
|
|
initial_state: State<'a>,
|
|
) -> ParseResult<'a, Stmt<'a>, EExpr<'a>> {
|
|
let before_op = state.clone();
|
|
match loc(operator()).parse(arena, state.clone(), min_indent) {
|
|
Err((MadeProgress, f)) => Err((MadeProgress, f)),
|
|
Ok((_, loc_op, state)) => {
|
|
expr_state.consume_spaces(arena);
|
|
let initial_state = before_op;
|
|
parse_stmt_operator(
|
|
arena,
|
|
state,
|
|
min_indent,
|
|
call_min_indent,
|
|
options,
|
|
expr_state,
|
|
loc_op,
|
|
initial_state,
|
|
)
|
|
}
|
|
Err((NoProgress, _)) => {
|
|
let mut state = state;
|
|
// try multi-backpassing
|
|
if options.accept_multi_backpassing && state.bytes().starts_with(b",") {
|
|
state = state.advance(1);
|
|
|
|
parse_stmt_multi_backpassing(expr_state, arena, state, min_indent, options)
|
|
} else if options.check_for_arrow && state.bytes().starts_with(b"->") {
|
|
Err((MadeProgress, EExpr::BadOperator("->", state.pos())))
|
|
} else {
|
|
let expr = parse_expr_final(expr_state, arena);
|
|
|
|
// roll back space parsing
|
|
Ok((MadeProgress, Stmt::Expr(expr), initial_state))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn parse_apply_arg<'a>(
|
|
arena: &'a Bump,
|
|
state: State<'a>,
|
|
min_indent: u32,
|
|
call_min_indent: u32,
|
|
mut expr_state: ExprState<'a>,
|
|
mut arg: Loc<Expr<'a>>,
|
|
options: ExprParseOptions,
|
|
check_for_defs: bool,
|
|
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
|
|
let new_end = state.pos();
|
|
|
|
// now that we have `function arg1 ... <spaces> argn`, attach the spaces to the `argn`
|
|
if !expr_state.spaces_after.is_empty() {
|
|
arg = arena
|
|
.alloc(arg.value)
|
|
.with_spaces_before(expr_state.spaces_after, arg.region);
|
|
|
|
expr_state.spaces_after = &[];
|
|
}
|
|
let initial_state = state.clone();
|
|
|
|
match space0_e(EExpr::IndentEnd).parse(arena, state.clone(), min_indent) {
|
|
Err((_, _)) => {
|
|
expr_state.arguments.push(arena.alloc(arg));
|
|
expr_state.end = new_end;
|
|
expr_state.spaces_after = &[];
|
|
|
|
let expr = parse_expr_final(expr_state, arena);
|
|
Ok((MadeProgress, expr, state))
|
|
}
|
|
Ok((_, new_spaces, state)) => {
|
|
expr_state.arguments.push(arena.alloc(arg));
|
|
expr_state.end = new_end;
|
|
expr_state.spaces_after = new_spaces;
|
|
|
|
parse_expr_end(
|
|
arena,
|
|
state,
|
|
min_indent,
|
|
call_min_indent,
|
|
options,
|
|
check_for_defs,
|
|
expr_state,
|
|
initial_state,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parse_ability_def<'a>(
|
|
expr_state: ExprState<'a>,
|
|
state: State<'a>,
|
|
arena: &'a Bump,
|
|
implements: Loc<Expr<'a>>,
|
|
call_min_indent: u32,
|
|
) -> Result<(TypeDef<'a>, State<'a>), (Progress, EExpr<'a>)> {
|
|
// This is an ability definition, `Ability arg1 ... implements ...`.
|
|
|
|
let name = expr_state.expr.map_owned(|e| match e {
|
|
Expr::Tag(name) => name,
|
|
_ => unreachable!(),
|
|
});
|
|
|
|
let mut arguments = Vec::with_capacity_in(expr_state.arguments.len(), arena);
|
|
for argument in expr_state.arguments {
|
|
match expr_to_pattern_help(arena, &argument.value) {
|
|
Ok(good) => {
|
|
arguments.push(Loc::at(argument.region, good));
|
|
}
|
|
Err(_) => {
|
|
let start = argument.region.start();
|
|
let err = &*arena.alloc(EPattern::Start(start));
|
|
return Err((MadeProgress, EExpr::Pattern(err, argument.region.start())));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Attach any spaces to the `implements` keyword
|
|
let implements = if !expr_state.spaces_after.is_empty() {
|
|
arena
|
|
.alloc(Implements::Implements)
|
|
.with_spaces_before(expr_state.spaces_after, implements.region)
|
|
} else {
|
|
Loc::at(implements.region, Implements::Implements)
|
|
};
|
|
|
|
let args = arguments.into_bump_slice();
|
|
let (_, (type_def, _), state) =
|
|
finish_parsing_ability_def_help(call_min_indent, name, args, implements, arena, state)?;
|
|
|
|
Ok((type_def, state))
|
|
}
|
|
|
|
pub fn loc_expr_block<'a>(
|
|
accept_multi_backpassing: bool,
|
|
) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
|
|
space0_after_e(
|
|
move |arena: &'a Bump, state: State<'a>, min_indent: u32| {
|
|
let options = ExprParseOptions {
|
|
accept_multi_backpassing,
|
|
check_for_arrow: true,
|
|
};
|
|
|
|
let (_, loc_first_space, state) =
|
|
loc_space0_e(EExpr::IndentStart).parse(arena, state, min_indent)?;
|
|
|
|
parse_block_inner(
|
|
options,
|
|
arena,
|
|
state,
|
|
min_indent,
|
|
EExpr::IndentStart,
|
|
|a, _| a.clone(),
|
|
loc_first_space,
|
|
true,
|
|
)
|
|
},
|
|
EExpr::IndentEnd,
|
|
)
|
|
.trace("loc_expr_block")
|
|
}
|
|
|
|
pub fn loc_expr<'a>(accept_multi_backpassing: bool) -> impl Parser<'a, Loc<Expr<'a>>, EExpr<'a>> {
|
|
space0_before_e(
|
|
expr_start(ExprParseOptions {
|
|
accept_multi_backpassing,
|
|
check_for_arrow: true,
|
|
}),
|
|
EExpr::IndentEnd,
|
|
)
|
|
}
|
|
|
|
pub fn merge_spaces<'a>(
|
|
arena: &'a Bump,
|
|
a: &'a [CommentOrNewline<'a>],
|
|
b: &'a [CommentOrNewline<'a>],
|
|
) -> &'a [CommentOrNewline<'a>] {
|
|
if a.is_empty() {
|
|
b
|
|
} else if b.is_empty() {
|
|
a
|
|
} else {
|
|
let mut merged = Vec::with_capacity_in(a.len() + b.len(), arena);
|
|
merged.extend_from_slice(a);
|
|
merged.extend_from_slice(b);
|
|
merged.into_bump_slice()
|
|
}
|
|
}
|
|
|
|
/// If the given Expr would parse the same way as a valid Pattern, convert it.
|
|
/// Example: (foo) could be either an Expr::Var("foo") or Pattern::Identifier("foo")
|
|
fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<'a>, ()> {
|
|
let mut expr = expr.extract_spaces();
|
|
|
|
if let Expr::ParensAround(loc_expr) = &expr.item {
|
|
let expr_inner = loc_expr.extract_spaces();
|
|
|
|
expr.before = merge_spaces(arena, expr.before, expr_inner.before);
|
|
expr.after = merge_spaces(arena, expr_inner.after, expr.after);
|
|
expr.item = expr_inner.item;
|
|
}
|
|
|
|
let mut pat = match expr.item {
|
|
Expr::Var { module_name, ident } => {
|
|
if module_name.is_empty() {
|
|
Pattern::Identifier { ident }
|
|
} else {
|
|
Pattern::QualifiedIdentifier { module_name, ident }
|
|
}
|
|
}
|
|
Expr::Underscore(opt_name) => Pattern::Underscore(opt_name),
|
|
Expr::Tag(value) => Pattern::Tag(value),
|
|
Expr::OpaqueRef(value) => Pattern::OpaqueRef(value),
|
|
Expr::Apply(loc_val, loc_args, _) => {
|
|
let region = loc_val.region;
|
|
let value = expr_to_pattern_help(arena, &loc_val.value)?;
|
|
let val_pattern = arena.alloc(Loc { region, value });
|
|
|
|
let mut arg_patterns = Vec::with_capacity_in(loc_args.len(), arena);
|
|
|
|
for loc_arg in loc_args.iter() {
|
|
let region = loc_arg.region;
|
|
let value = expr_to_pattern_help(arena, &loc_arg.value)?;
|
|
|
|
arg_patterns.push(Loc { region, value });
|
|
}
|
|
|
|
let pattern = Pattern::Apply(val_pattern, arg_patterns.into_bump_slice());
|
|
|
|
pattern
|
|
}
|
|
|
|
Expr::SpaceBefore(..) | Expr::SpaceAfter(..) | Expr::ParensAround(..) => unreachable!(),
|
|
|
|
Expr::Record(fields) => {
|
|
let patterns = fields.map_items_result(arena, |loc_assigned_field| {
|
|
let region = loc_assigned_field.region;
|
|
let value = assigned_expr_field_to_pattern_help(arena, &loc_assigned_field.value)?;
|
|
Ok(Loc { region, value })
|
|
})?;
|
|
|
|
Pattern::RecordDestructure(patterns)
|
|
}
|
|
|
|
Expr::Tuple(fields) => Pattern::Tuple(fields.map_items_result(arena, |loc_expr| {
|
|
Ok(Loc {
|
|
region: loc_expr.region,
|
|
value: expr_to_pattern_help(arena, &loc_expr.value)?,
|
|
})
|
|
})?),
|
|
|
|
Expr::Float(string) => Pattern::FloatLiteral(string),
|
|
Expr::Num(string) => Pattern::NumLiteral(string),
|
|
Expr::NonBase10Int {
|
|
string,
|
|
base,
|
|
is_negative,
|
|
} => Pattern::NonBase10Literal {
|
|
string,
|
|
base,
|
|
is_negative,
|
|
},
|
|
// These would not have parsed as patterns
|
|
Expr::AccessorFunction(_)
|
|
| Expr::RecordAccess(_, _)
|
|
| Expr::TupleAccess(_, _)
|
|
| Expr::List { .. }
|
|
| Expr::Closure(_, _)
|
|
| Expr::Backpassing(_, _, _)
|
|
| Expr::BinOps { .. }
|
|
| Expr::Defs(_, _)
|
|
| Expr::If { .. }
|
|
| Expr::When(_, _)
|
|
| Expr::Expect(_, _)
|
|
| Expr::Dbg
|
|
| Expr::DbgStmt(_, _)
|
|
| Expr::LowLevelDbg(_, _, _)
|
|
| Expr::MalformedClosure
|
|
| Expr::MalformedSuffixed(..)
|
|
| Expr::PrecedenceConflict { .. }
|
|
| Expr::MultipleOldRecordBuilders { .. }
|
|
| Expr::UnappliedOldRecordBuilder { .. }
|
|
| Expr::EmptyRecordBuilder(_)
|
|
| Expr::SingleFieldRecordBuilder(_)
|
|
| Expr::OptionalFieldInRecordBuilder(_, _)
|
|
| Expr::RecordUpdate { .. }
|
|
| Expr::RecordUpdater(_)
|
|
| Expr::UnaryOp(_, _)
|
|
| Expr::TrySuffix { .. }
|
|
| Expr::Crash
|
|
| Expr::OldRecordBuilder(..)
|
|
| Expr::RecordBuilder { .. } => return Err(()),
|
|
|
|
Expr::Str(string) => Pattern::StrLiteral(string),
|
|
Expr::SingleQuote(string) => Pattern::SingleQuote(string),
|
|
Expr::MalformedIdent(string, problem) => Pattern::MalformedIdent(string, problem),
|
|
};
|
|
|
|
// Now we re-add the spaces
|
|
|
|
if !expr.before.is_empty() {
|
|
pat = Pattern::SpaceBefore(arena.alloc(pat), expr.before);
|
|
}
|
|
if !expr.after.is_empty() {
|
|
pat = Pattern::SpaceAfter(arena.alloc(pat), expr.after);
|
|
}
|
|
|
|
Ok(pat)
|
|
}
|
|
|
|
fn assigned_expr_field_to_pattern_help<'a>(
|
|
arena: &'a Bump,
|
|
assigned_field: &AssignedField<'a, Expr<'a>>,
|
|
) -> Result<Pattern<'a>, ()> {
|
|
// the assigned fields always store spaces, but this slice is often empty
|
|
Ok(match assigned_field {
|
|
AssignedField::RequiredValue(name, spaces, value) => {
|
|
let pattern = expr_to_pattern_help(arena, &value.value)?;
|
|
let result = arena.alloc(Loc {
|
|
region: value.region,
|
|
value: pattern,
|
|
});
|
|
if spaces.is_empty() {
|
|
Pattern::RequiredField(name.value, result)
|
|
} else {
|
|
Pattern::SpaceAfter(
|
|
arena.alloc(Pattern::RequiredField(name.value, result)),
|
|
spaces,
|
|
)
|
|
}
|
|
}
|
|
AssignedField::OptionalValue(name, spaces, value) => {
|
|
let result = arena.alloc(Loc {
|
|
region: value.region,
|
|
value: value.value,
|
|
});
|
|
if spaces.is_empty() {
|
|
Pattern::OptionalField(name.value, result)
|
|
} else {
|
|
Pattern::SpaceAfter(
|
|
arena.alloc(Pattern::OptionalField(name.value, result)),
|
|
spaces,
|
|
)
|
|
}
|
|
}
|
|
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,
|
|
),
|
|
AssignedField::SpaceAfter(nested, spaces) => Pattern::SpaceAfter(
|
|
arena.alloc(assigned_expr_field_to_pattern_help(arena, nested)?),
|
|
spaces,
|
|
),
|
|
AssignedField::Malformed(string) => Pattern::Malformed(string),
|
|
AssignedField::IgnoredValue(_, _, _) => return Err(()),
|
|
})
|
|
}
|
|
|
|
pub fn parse_top_level_defs<'a>(
|
|
arena: &'a bumpalo::Bump,
|
|
state: State<'a>,
|
|
output: Defs<'a>,
|
|
) -> ParseResult<'a, Defs<'a>, EExpr<'a>> {
|
|
let (_, loc_first_space, state) = loc_space0_e(EExpr::IndentStart).parse(arena, state, 0)?;
|
|
|
|
let (_, stmts, state) = parse_stmt_seq(
|
|
arena,
|
|
state,
|
|
|e, _| e.clone(),
|
|
ExprParseOptions {
|
|
accept_multi_backpassing: true,
|
|
check_for_arrow: true,
|
|
},
|
|
0,
|
|
loc_first_space,
|
|
EExpr::IndentEnd,
|
|
)?;
|
|
|
|
let (_, last_space, state) = space0_e(EExpr::IndentStart).parse(arena, state, 0)?;
|
|
|
|
let existing_len = output.tags.len();
|
|
|
|
let (mut output, last_expr) =
|
|
stmts_to_defs(&stmts, output, false, arena).map_err(|e| (MadeProgress, e))?;
|
|
|
|
if let Some(expr) = last_expr {
|
|
return Err((
|
|
MadeProgress,
|
|
EExpr::UnexpectedTopLevelExpr(expr.region.start()),
|
|
));
|
|
}
|
|
|
|
if output.tags.len() > existing_len {
|
|
let after = Slice::extend_new(&mut output.spaces, last_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;
|
|
}
|
|
|
|
Ok((MadeProgress, output, state))
|
|
}
|
|
|
|
// PARSER HELPERS
|
|
|
|
fn closure_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EClosure<'a>> {
|
|
// 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_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.
|
|
// It may turn out to be malformed, but it is definitely a closure.
|
|
and(
|
|
// Parse the params
|
|
// Params are comma-separated
|
|
sep_by1_e(
|
|
byte(b',', EClosure::Comma),
|
|
space0_around_ee(
|
|
specialize_err(EClosure::Pattern, closure_param()),
|
|
EClosure::IndentArg,
|
|
EClosure::IndentArrow,
|
|
),
|
|
EClosure::Arg,
|
|
),
|
|
skip_first(
|
|
// Parse the -> which separates params from body
|
|
two_bytes(b'-', b'>', EClosure::Arrow),
|
|
// Parse the body
|
|
block(options, true, EClosure::IndentBody, EClosure::Body),
|
|
),
|
|
),
|
|
),
|
|
|arena: &'a Bump, (params, body)| {
|
|
let params: Vec<'a, Loc<Pattern<'a>>> = params;
|
|
let params: &'a [Loc<Pattern<'a>>] = params.into_bump_slice();
|
|
Expr::Closure(params, arena.alloc(body))
|
|
},
|
|
)
|
|
}
|
|
|
|
mod when {
|
|
use parser::indented_seq_skip_first;
|
|
|
|
use super::*;
|
|
use crate::{ast::WhenBranch, blankspace::space0_around_e_no_after_indent_check};
|
|
|
|
/// Parser for when expressions.
|
|
pub fn when_expr_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EWhen<'a>> {
|
|
map_with_arena(
|
|
and(
|
|
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)),
|
|
EWhen::IndentCondition,
|
|
)
|
|
),
|
|
// Note that we allow the `is` to be at any indent level, since this doesn't introduce any
|
|
// ambiguity. The formatter will fix it up.
|
|
//
|
|
// We require that branches are indented relative to the line containing the `is`.
|
|
indented_seq_skip_first(
|
|
parser::keyword(keyword::IS, EWhen::Is),
|
|
branches(options)
|
|
)
|
|
),
|
|
move |arena: &'a Bump, (loc_condition, branches): (Loc<Expr<'a>>, Vec<'a, &'a WhenBranch<'a>>)| {
|
|
Expr::When(arena.alloc(loc_condition), branches.into_bump_slice())
|
|
}
|
|
).trace("when")
|
|
}
|
|
|
|
fn branches<'a>(
|
|
options: ExprParseOptions,
|
|
) -> impl Parser<'a, Vec<'a, &'a WhenBranch<'a>>, EWhen<'a>> {
|
|
move |arena, state: State<'a>, min_indent: u32| {
|
|
let mut branches: Vec<'a, &'a WhenBranch<'a>> = Vec::with_capacity_in(2, arena);
|
|
|
|
// 1. Parse the first branch and get its indentation level. (It must be >= min_indent.)
|
|
// 2. Parse the other branches. Their indentation levels must be == the first branch's.
|
|
|
|
let (_, ((pattern_indent_level, loc_first_patterns), loc_first_guard), state): (
|
|
_,
|
|
((_, _), _),
|
|
State<'a>,
|
|
) = branch_alternatives(options, None).parse(arena, state, min_indent)?;
|
|
|
|
let original_indent = pattern_indent_level;
|
|
|
|
// Parse the first "->" and the expression after it.
|
|
let (_, loc_first_expr, mut state) =
|
|
branch_result(original_indent + 1).parse(arena, state, original_indent + 1)?;
|
|
|
|
// Record this as the first branch, then optionally parse additional branches.
|
|
branches.push(arena.alloc(WhenBranch {
|
|
patterns: loc_first_patterns.into_bump_slice(),
|
|
value: loc_first_expr,
|
|
guard: loc_first_guard,
|
|
}));
|
|
|
|
let branch_parser = map(
|
|
and(
|
|
then(
|
|
branch_alternatives(options, Some(pattern_indent_level)),
|
|
move |_arena, state, _, ((indent_column, loc_patterns), loc_guard)| {
|
|
if pattern_indent_level == indent_column {
|
|
Ok((MadeProgress, (loc_patterns, loc_guard), state))
|
|
} else {
|
|
let indent = pattern_indent_level - indent_column;
|
|
Err((MadeProgress, EWhen::PatternAlignment(indent, state.pos())))
|
|
}
|
|
},
|
|
),
|
|
branch_result(original_indent + 1),
|
|
),
|
|
|((patterns, guard), expr)| {
|
|
let patterns: Vec<'a, _> = patterns;
|
|
WhenBranch {
|
|
patterns: patterns.into_bump_slice(),
|
|
value: expr,
|
|
guard,
|
|
}
|
|
},
|
|
);
|
|
|
|
while !state.bytes().is_empty() {
|
|
match branch_parser.parse(arena, state.clone(), min_indent) {
|
|
Ok((_, next_output, next_state)) => {
|
|
state = next_state;
|
|
|
|
branches.push(arena.alloc(next_output));
|
|
}
|
|
Err((MadeProgress, problem)) => {
|
|
return Err((MadeProgress, problem));
|
|
}
|
|
Err((NoProgress, _)) => {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok((MadeProgress, branches, state))
|
|
}
|
|
}
|
|
|
|
/// Parsing alternative patterns in `when` branches.
|
|
fn branch_alternatives<'a>(
|
|
options: ExprParseOptions,
|
|
pattern_indent_level: Option<u32>,
|
|
) -> impl Parser<'a, ((u32, Vec<'a, Loc<Pattern<'a>>>), Option<Loc<Expr<'a>>>), EWhen<'a>> {
|
|
let options = ExprParseOptions {
|
|
check_for_arrow: false,
|
|
..options
|
|
};
|
|
and(
|
|
branch_alternatives_help(pattern_indent_level),
|
|
one_of![
|
|
map(
|
|
skip_first(
|
|
parser::keyword(keyword::IF, EWhen::IfToken),
|
|
// TODO we should require space before the expression but not after
|
|
space0_around_ee(
|
|
specialize_err_ref(
|
|
EWhen::IfGuard,
|
|
increment_min_indent(expr_start(options))
|
|
),
|
|
EWhen::IndentIfGuard,
|
|
EWhen::IndentArrow,
|
|
)
|
|
),
|
|
Some
|
|
),
|
|
|_, s, _| Ok((NoProgress, None, s))
|
|
],
|
|
)
|
|
}
|
|
|
|
fn error_on_arrow<'a, T, E: 'a>(f: impl Fn(Position) -> E) -> impl Parser<'a, T, E> {
|
|
move |_, state: State<'a>, _| {
|
|
if state.bytes().starts_with(b"->") {
|
|
Err((MadeProgress, f(state.pos())))
|
|
} else {
|
|
Err((NoProgress, f(state.pos())))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn branch_single_alternative<'a>() -> impl Parser<'a, Loc<Pattern<'a>>, EWhen<'a>> {
|
|
move |arena, state, min_indent| {
|
|
let (_, spaces, state) =
|
|
backtrackable(space0_e(EWhen::IndentPattern)).parse(arena, state, min_indent)?;
|
|
|
|
let (_, loc_pattern, state) = space0_after_e(
|
|
specialize_err(EWhen::Pattern, crate::pattern::loc_pattern_help()),
|
|
EWhen::IndentPattern,
|
|
)
|
|
.parse(arena, state, min_indent)?;
|
|
|
|
Ok((
|
|
MadeProgress,
|
|
if spaces.is_empty() {
|
|
loc_pattern
|
|
} else {
|
|
arena
|
|
.alloc(loc_pattern.value)
|
|
.with_spaces_before(spaces, loc_pattern.region)
|
|
},
|
|
state,
|
|
))
|
|
}
|
|
}
|
|
|
|
fn branch_alternatives_help<'a>(
|
|
pattern_indent_level: Option<u32>,
|
|
) -> impl Parser<'a, (u32, Vec<'a, Loc<Pattern<'a>>>), EWhen<'a>> {
|
|
move |arena, state: State<'a>, min_indent: u32| {
|
|
// put no restrictions on the indent after the spaces; we'll check it manually
|
|
match space0_e(EWhen::IndentPattern).parse(arena, state, 0) {
|
|
Err((MadeProgress, fail)) => Err((NoProgress, fail)),
|
|
Err((NoProgress, fail)) => Err((NoProgress, fail)),
|
|
Ok((_progress, spaces, state)) => {
|
|
match pattern_indent_level {
|
|
Some(wanted) if state.column() > wanted => {
|
|
error_on_arrow(EWhen::IndentPattern).parse(arena, state, min_indent)
|
|
}
|
|
Some(wanted) if state.column() < wanted => {
|
|
let indent = wanted - state.column();
|
|
Err((NoProgress, EWhen::PatternAlignment(indent, state.pos())))
|
|
}
|
|
_ => {
|
|
let pattern_indent =
|
|
min_indent.max(pattern_indent_level.unwrap_or(min_indent));
|
|
// the region is not reliable for the indent column in the case of
|
|
// parentheses around patterns
|
|
let pattern_indent_column = state.column();
|
|
|
|
let parser =
|
|
sep_by1(byte(b'|', EWhen::Bar), branch_single_alternative());
|
|
|
|
match parser.parse(arena, state.clone(), pattern_indent) {
|
|
Err((MadeProgress, fail)) => Err((MadeProgress, fail)),
|
|
Err((NoProgress, fail)) => {
|
|
// roll back space parsing if the pattern made no progress
|
|
Err((NoProgress, fail))
|
|
}
|
|
|
|
Ok((_, mut loc_patterns, state)) => {
|
|
// tag spaces onto the first parsed pattern
|
|
if !spaces.is_empty() {
|
|
if let Some(first) = loc_patterns.get_mut(0) {
|
|
*first = arena
|
|
.alloc(first.value)
|
|
.with_spaces_before(spaces, first.region);
|
|
}
|
|
}
|
|
|
|
Ok((MadeProgress, (pattern_indent_column, loc_patterns), state))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Parsing the righthandside of a branch in a when conditional.
|
|
fn branch_result<'a>(indent: u32) -> impl Parser<'a, Loc<Expr<'a>>, EWhen<'a>> {
|
|
let options = ExprParseOptions {
|
|
accept_multi_backpassing: true,
|
|
check_for_arrow: true,
|
|
};
|
|
move |arena, state, _min_indent| {
|
|
skip_first(
|
|
two_bytes(b'-', b'>', EWhen::Arrow),
|
|
block(options, true, EWhen::IndentBranch, EWhen::Branch),
|
|
)
|
|
.parse(arena, state, indent)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn if_branch<'a>() -> impl Parser<'a, (Loc<Expr<'a>>, Loc<Expr<'a>>), EIf<'a>> {
|
|
let options = ExprParseOptions {
|
|
accept_multi_backpassing: true,
|
|
check_for_arrow: true,
|
|
};
|
|
skip_second(
|
|
and(
|
|
skip_second(
|
|
space0_around_ee(
|
|
specialize_err_ref(EIf::Condition, loc_expr(true)),
|
|
EIf::IndentCondition,
|
|
EIf::IndentThenToken,
|
|
),
|
|
parser::keyword(keyword::THEN, EIf::Then),
|
|
),
|
|
map_with_arena(
|
|
space0_after_e(
|
|
block(options, false, EIf::IndentThenBranch, EIf::ThenBranch),
|
|
EIf::IndentElseToken,
|
|
),
|
|
|arena: &'a Bump, block: Loc<Expr<'a>>| match block.value {
|
|
Expr::SpaceAfter(&Expr::SpaceBefore(x, before), after) => block.with_value(
|
|
Expr::SpaceBefore(arena.alloc(Expr::SpaceAfter(x, after)), before),
|
|
),
|
|
_ => block,
|
|
},
|
|
),
|
|
),
|
|
parser::keyword(keyword::ELSE, EIf::Else),
|
|
)
|
|
}
|
|
|
|
fn expect_help<'a>(
|
|
options: ExprParseOptions,
|
|
preceding_comment: Region,
|
|
) -> impl Parser<'a, Stmt<'a>, EExpect<'a>> {
|
|
move |arena: &'a Bump, state: State<'a>, min_indent| {
|
|
let parse_expect_vanilla = crate::parser::keyword(crate::keyword::EXPECT, EExpect::Expect);
|
|
let parse_expect_fx = crate::parser::keyword(crate::keyword::EXPECT_FX, EExpect::Expect);
|
|
let parse_expect = either(parse_expect_vanilla, parse_expect_fx);
|
|
|
|
let (_, kw, state) = parse_expect.parse(arena, state, min_indent)?;
|
|
|
|
let (_, condition, state) = parse_block(
|
|
options,
|
|
arena,
|
|
state,
|
|
true,
|
|
EExpect::IndentCondition,
|
|
EExpect::Condition,
|
|
)
|
|
.map_err(|(_, f)| (MadeProgress, f))?;
|
|
|
|
let vd = match kw {
|
|
Either::First(_) => ValueDef::Expect {
|
|
condition: arena.alloc(condition),
|
|
preceding_comment,
|
|
},
|
|
Either::Second(_) => ValueDef::ExpectFx {
|
|
condition: arena.alloc(condition),
|
|
preceding_comment,
|
|
},
|
|
};
|
|
|
|
Ok((MadeProgress, Stmt::ValueDef(vd), state))
|
|
}
|
|
}
|
|
|
|
fn dbg_stmt_help<'a>(
|
|
options: ExprParseOptions,
|
|
preceding_comment: Region,
|
|
) -> impl Parser<'a, Stmt<'a>, EExpect<'a>> {
|
|
(move |arena: &'a Bump, state: State<'a>, min_indent| {
|
|
let (_, _, state) =
|
|
parser::keyword(keyword::DBG, EExpect::Dbg).parse(arena, state, min_indent)?;
|
|
|
|
let (_, condition, state) = parse_block(
|
|
options,
|
|
arena,
|
|
state,
|
|
true,
|
|
EExpect::IndentCondition,
|
|
EExpect::Condition,
|
|
)
|
|
.map_err(|(_, f)| (MadeProgress, f))?;
|
|
|
|
let stmt = Stmt::ValueDef(ValueDef::Dbg {
|
|
condition: arena.alloc(condition),
|
|
preceding_comment,
|
|
});
|
|
|
|
Ok((MadeProgress, stmt, state))
|
|
})
|
|
.trace("dbg_stmt_help")
|
|
}
|
|
|
|
fn dbg_kw<'a>() -> impl Parser<'a, Expr<'a>, EExpect<'a>> {
|
|
(move |arena: &'a Bump, state: State<'a>, min_indent: u32| {
|
|
let (_, _, next_state) =
|
|
parser::keyword(keyword::DBG, EExpect::Dbg).parse(arena, state, min_indent)?;
|
|
|
|
Ok((MadeProgress, Expr::Dbg, next_state))
|
|
})
|
|
.trace("dbg_kw")
|
|
}
|
|
|
|
fn import<'a>() -> impl Parser<'a, ValueDef<'a>, EImport<'a>> {
|
|
skip_second(
|
|
skip_first(
|
|
parser::keyword(keyword::IMPORT, EImport::Import),
|
|
increment_min_indent(one_of!(import_body(), import_ingested_file_body())),
|
|
),
|
|
require_newline_or_eof(EImport::EndNewline),
|
|
)
|
|
}
|
|
|
|
fn if_expr_help<'a>(options: ExprParseOptions) -> impl Parser<'a, Expr<'a>, EIf<'a>> {
|
|
move |arena: &'a Bump, state, min_indent| {
|
|
let (_, _, state) =
|
|
parser::keyword(keyword::IF, EIf::If).parse(arena, state, min_indent)?;
|
|
|
|
let if_indent = state.line_indent();
|
|
|
|
let mut branches = Vec::with_capacity_in(1, arena);
|
|
|
|
let mut loop_state = state;
|
|
|
|
let state_final_else = loop {
|
|
let (_, (cond, then_branch), state) =
|
|
if_branch().parse(arena, loop_state, min_indent)?;
|
|
|
|
branches.push((cond, then_branch));
|
|
|
|
// try to parse another `if`
|
|
// NOTE this drops spaces between the `else` and the `if`
|
|
let optional_if = and(
|
|
backtrackable(space0_e(EIf::IndentIf)),
|
|
parser::keyword(keyword::IF, EIf::If),
|
|
);
|
|
|
|
match optional_if.parse(arena, state.clone(), min_indent) {
|
|
Err((_, _)) => break state,
|
|
Ok((_, _, state)) => {
|
|
loop_state = state;
|
|
continue;
|
|
}
|
|
}
|
|
};
|
|
|
|
let else_indent = state_final_else.line_indent();
|
|
let indented_else = else_indent > if_indent;
|
|
|
|
let min_indent = if !indented_else {
|
|
else_indent + 1
|
|
} else {
|
|
if_indent
|
|
};
|
|
|
|
let (_, loc_first_space, state_final_else) =
|
|
loc_space0_e(EIf::IndentElseBranch).parse(arena, state_final_else, min_indent)?;
|
|
|
|
let allow_defs = !loc_first_space.value.is_empty();
|
|
|
|
// use parse_block_inner so we can set min_indent
|
|
let (_, else_branch, state) = parse_block_inner(
|
|
options,
|
|
arena,
|
|
state_final_else,
|
|
min_indent,
|
|
EIf::IndentElseBranch,
|
|
EIf::ElseBranch,
|
|
loc_first_space,
|
|
allow_defs,
|
|
)?;
|
|
|
|
let expr = Expr::If {
|
|
if_thens: branches.into_bump_slice(),
|
|
final_else: arena.alloc(else_branch),
|
|
indented_else,
|
|
};
|
|
|
|
Ok((MadeProgress, expr, state))
|
|
}
|
|
}
|
|
|
|
/// Parse a block of statements (parser combinator version of `parse_block`)
|
|
fn block<'a, E>(
|
|
options: ExprParseOptions,
|
|
require_indent: bool,
|
|
indent_problem: fn(Position) -> E,
|
|
wrap_error: fn(&'a EExpr<'a>, Position) -> E,
|
|
) -> impl Parser<'a, Loc<Expr<'a>>, E>
|
|
where
|
|
E: 'a + SpaceProblem,
|
|
{
|
|
(move |arena: &'a Bump, state, _min_indent| {
|
|
parse_block(
|
|
options,
|
|
arena,
|
|
state,
|
|
require_indent,
|
|
indent_problem,
|
|
wrap_error,
|
|
)
|
|
})
|
|
.trace("block")
|
|
}
|
|
|
|
/// Parse a block of statements.
|
|
/// For example, the then and else branches of an `if` expression are both blocks.
|
|
/// There are two cases here:
|
|
/// 1. If there is a preceding newline, then the block must be indented and is allowed to have definitions.
|
|
/// 2. If there is no preceding newline, then the block must consist of a single expression (no definitions).
|
|
fn parse_block<'a, E>(
|
|
options: ExprParseOptions,
|
|
arena: &'a Bump,
|
|
state: State<'a>,
|
|
require_indent: bool,
|
|
indent_problem: fn(Position) -> E,
|
|
wrap_error: fn(&'a EExpr<'a>, Position) -> E,
|
|
) -> ParseResult<'a, Loc<Expr<'a>>, E>
|
|
where
|
|
E: 'a + SpaceProblem,
|
|
{
|
|
let min_indent = if require_indent {
|
|
state.line_indent() + 1
|
|
} else {
|
|
0
|
|
};
|
|
|
|
let (_, loc_first_space, state) =
|
|
loc_space0_e(indent_problem).parse(arena, state, min_indent)?;
|
|
|
|
let allow_defs = !loc_first_space.value.is_empty();
|
|
|
|
parse_block_inner(
|
|
options,
|
|
arena,
|
|
state,
|
|
min_indent,
|
|
indent_problem,
|
|
wrap_error,
|
|
loc_first_space,
|
|
allow_defs,
|
|
)
|
|
}
|
|
|
|
/// Parse a block of statements, and process that into an Expr.
|
|
/// Assumes the caller has already parsed the optional first "space" (newline),
|
|
/// and decided whether to allow definitions.
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn parse_block_inner<'a, E>(
|
|
options: ExprParseOptions,
|
|
arena: &'a Bump,
|
|
state: State<'a>,
|
|
min_indent: u32,
|
|
indent_problem: fn(Position) -> E,
|
|
wrap_error: fn(&'a EExpr<'a>, Position) -> E,
|
|
first_space: Loc<&'a [CommentOrNewline<'a>]>,
|
|
allow_defs: bool,
|
|
) -> ParseResult<'a, Loc<Expr<'a>>, E>
|
|
where
|
|
E: 'a + SpaceProblem,
|
|
{
|
|
if allow_defs {
|
|
let (_, stmts, state) = parse_stmt_seq(
|
|
arena,
|
|
state,
|
|
wrap_error,
|
|
options,
|
|
min_indent,
|
|
Loc::at(first_space.region, &[]),
|
|
indent_problem,
|
|
)?;
|
|
|
|
if stmts.is_empty() {
|
|
return Err((
|
|
NoProgress,
|
|
wrap_error(arena.alloc(EExpr::Start(state.pos())), state.pos()),
|
|
));
|
|
}
|
|
|
|
let last_pos = state.pos();
|
|
|
|
let loc_expr = stmts_to_expr(&stmts, arena)
|
|
.map_err(|e| (MadeProgress, wrap_error(arena.alloc(e), last_pos)))?;
|
|
|
|
let loc_expr = if first_space.value.is_empty() {
|
|
loc_expr
|
|
} else {
|
|
arena
|
|
.alloc(loc_expr.value)
|
|
.with_spaces_before(first_space.value, loc_expr.region)
|
|
};
|
|
|
|
Ok((MadeProgress, loc_expr, state))
|
|
} else {
|
|
let (p2, loc_expr, state) =
|
|
specialize_err_ref(wrap_error, expr_start(options)).parse(arena, state, min_indent)?;
|
|
|
|
let loc_expr = if first_space.value.is_empty() {
|
|
loc_expr
|
|
} else {
|
|
arena
|
|
.alloc(loc_expr.value)
|
|
.with_spaces_before(first_space.value, loc_expr.region)
|
|
};
|
|
|
|
Ok((p2, loc_expr, state))
|
|
}
|
|
}
|
|
|
|
/// Parse a sequence of statements, which we'll later process into an expression.
|
|
/// Statements can include:
|
|
/// - assignments
|
|
/// - type annotations
|
|
/// - expressions
|
|
/// - [multi]backpassing
|
|
///
|
|
/// This function doesn't care about whether the order of those statements makes any sense.
|
|
/// e.g. it will happily parse two expressions in a row, or backpassing with nothing following it.
|
|
fn parse_stmt_seq<'a, E: SpaceProblem + 'a>(
|
|
arena: &'a Bump,
|
|
mut state: State<'a>,
|
|
wrap_error: fn(&'a EExpr<'a>, Position) -> E,
|
|
options: ExprParseOptions,
|
|
min_indent: u32,
|
|
mut last_space: Loc<&'a [CommentOrNewline<'a>]>,
|
|
indent_problem: fn(Position) -> E,
|
|
) -> ParseResult<'a, Vec<'a, SpacesBefore<'a, Loc<Stmt<'a>>>>, E> {
|
|
let mut stmts = Vec::new_in(arena);
|
|
let mut state_before_space = state.clone();
|
|
loop {
|
|
if at_terminator(&state) {
|
|
state = state_before_space;
|
|
break;
|
|
}
|
|
|
|
let loc_stmt = match specialize_err_ref(wrap_error, stmt_start(options, last_space.region))
|
|
.parse(arena, state.clone(), min_indent)
|
|
{
|
|
Ok((_p, s, new_state)) => {
|
|
state_before_space = new_state.clone();
|
|
state = new_state;
|
|
s
|
|
}
|
|
Err((NoProgress, _)) => {
|
|
if stmts.is_empty() {
|
|
return Err((
|
|
NoProgress,
|
|
wrap_error(arena.alloc(EExpr::Start(state.pos())), state.pos()),
|
|
));
|
|
}
|
|
|
|
state = state_before_space;
|
|
break;
|
|
}
|
|
Err((MadeProgress, e)) => {
|
|
return Err((MadeProgress, e));
|
|
}
|
|
};
|
|
|
|
stmts.push(SpacesBefore {
|
|
before: last_space.value,
|
|
item: loc_stmt,
|
|
});
|
|
|
|
match loc_space0_e(indent_problem).parse(arena, state.clone(), min_indent) {
|
|
Ok((_p, s_loc, new_state)) => {
|
|
if s_loc.value.is_empty() {
|
|
// require a newline or a terminator after the statement
|
|
if at_terminator(&new_state) {
|
|
state = state_before_space;
|
|
break;
|
|
}
|
|
|
|
return Err((
|
|
MadeProgress,
|
|
wrap_error(arena.alloc(EExpr::BadExprEnd(state.pos())), state.pos()),
|
|
));
|
|
}
|
|
last_space = s_loc;
|
|
state = new_state;
|
|
}
|
|
Err(_) => {
|
|
break;
|
|
}
|
|
};
|
|
}
|
|
Ok((MadeProgress, stmts, state))
|
|
}
|
|
|
|
/// Check if the current byte is a terminator for a sequence of statements
|
|
fn at_terminator(state: &State<'_>) -> bool {
|
|
matches!(
|
|
state.bytes().first(),
|
|
None | Some(b']' | b'}' | b')' | b',')
|
|
)
|
|
}
|
|
|
|
/// Convert a sequence of statements into a `Expr::Defs` expression
|
|
/// (which is itself a Defs struct and final expr)
|
|
fn stmts_to_expr<'a>(
|
|
stmts: &[SpacesBefore<'a, Loc<Stmt<'a>>>],
|
|
arena: &'a Bump,
|
|
) -> Result<Loc<Expr<'a>>, EExpr<'a>> {
|
|
if stmts.len() > 1 {
|
|
let first_pos = stmts.first().unwrap().item.region.start();
|
|
let last_pos = stmts.last().unwrap().item.region.end();
|
|
|
|
let (defs, last_expr) = stmts_to_defs(stmts, Defs::default(), true, arena)?;
|
|
|
|
let final_expr = match last_expr {
|
|
Some(e) => e,
|
|
None => return Err(EExpr::DefMissingFinalExpr(last_pos)),
|
|
};
|
|
|
|
let region = Region::new(first_pos, last_pos);
|
|
|
|
if defs.is_empty() {
|
|
Ok(final_expr)
|
|
} else {
|
|
Ok(Loc::at(
|
|
region,
|
|
Expr::Defs(arena.alloc(defs), arena.alloc(final_expr)),
|
|
))
|
|
}
|
|
} else {
|
|
let SpacesBefore {
|
|
before: space,
|
|
item: loc_stmt,
|
|
} = *stmts.last().unwrap();
|
|
let expr = match loc_stmt.value {
|
|
Stmt::Expr(e) => {
|
|
if space.is_empty() {
|
|
e
|
|
} else {
|
|
arena.alloc(e).before(space)
|
|
}
|
|
}
|
|
Stmt::ValueDef(ValueDef::Dbg { condition, .. }) => {
|
|
// If we parse a `dbg` as the last thing in a series of statements then it's
|
|
// actually an expression.
|
|
Expr::Apply(
|
|
arena.alloc(Loc {
|
|
value: Expr::Dbg,
|
|
region: loc_stmt.region,
|
|
}),
|
|
arena.alloc([condition]),
|
|
CalledVia::Space,
|
|
)
|
|
}
|
|
Stmt::ValueDef(ValueDef::Expect { .. }) => {
|
|
return Err(EExpr::Expect(
|
|
EExpect::Continuation(
|
|
arena.alloc(EExpr::IndentEnd(loc_stmt.region.end())),
|
|
loc_stmt.region.end(),
|
|
),
|
|
loc_stmt.region.start(),
|
|
));
|
|
}
|
|
Stmt::Backpassing(..) | Stmt::TypeDef(_) | Stmt::ValueDef(_) => {
|
|
return Err(EExpr::IndentEnd(loc_stmt.region.end()))
|
|
}
|
|
};
|
|
|
|
Ok(loc_stmt.with_value(expr))
|
|
}
|
|
}
|
|
|
|
/// Convert a sequence of `Stmt` into a Defs and an optional final expression.
|
|
/// Future refactoring opportunity: push this logic directly into where we're
|
|
/// parsing the statements.
|
|
fn stmts_to_defs<'a>(
|
|
stmts: &[SpacesBefore<'a, Loc<Stmt<'a>>>],
|
|
mut defs: Defs<'a>,
|
|
exprify_dbg: bool,
|
|
arena: &'a Bump,
|
|
) -> Result<(Defs<'a>, Option<Loc<Expr<'a>>>), EExpr<'a>> {
|
|
let mut last_expr = None;
|
|
let mut i = 0;
|
|
while i < stmts.len() {
|
|
let sp_stmt = stmts[i];
|
|
match sp_stmt.item.value {
|
|
Stmt::Expr(e) => {
|
|
if is_expr_suffixed(&e) && i + 1 < stmts.len() {
|
|
defs.push_value_def(
|
|
ValueDef::Stmt(arena.alloc(Loc::at(sp_stmt.item.region, e))),
|
|
sp_stmt.item.region,
|
|
sp_stmt.before,
|
|
&[],
|
|
);
|
|
} else {
|
|
if last_expr.is_some() {
|
|
return Err(EExpr::StmtAfterExpr(sp_stmt.item.region.start()));
|
|
}
|
|
|
|
let e = if sp_stmt.before.is_empty() {
|
|
e
|
|
} else {
|
|
arena.alloc(e).before(sp_stmt.before)
|
|
};
|
|
|
|
last_expr = Some(sp_stmt.item.with_value(e));
|
|
}
|
|
}
|
|
Stmt::Backpassing(pats, call) => {
|
|
if last_expr.is_some() {
|
|
return Err(EExpr::StmtAfterExpr(sp_stmt.item.region.start()));
|
|
}
|
|
|
|
if i + 1 >= stmts.len() {
|
|
return Err(EExpr::BackpassContinue(sp_stmt.item.region.end()));
|
|
}
|
|
|
|
let rest = stmts_to_expr(&stmts[i + 1..], arena)?;
|
|
|
|
let e = Expr::Backpassing(arena.alloc(pats), arena.alloc(call), arena.alloc(rest));
|
|
|
|
let e = if sp_stmt.before.is_empty() {
|
|
e
|
|
} else {
|
|
arena.alloc(e).before(sp_stmt.before)
|
|
};
|
|
|
|
let region = Region::new(sp_stmt.item.region.start(), rest.region.end());
|
|
|
|
last_expr = Some(Loc::at(region, e));
|
|
|
|
// don't re-process the rest of the statements; they got consumed by the backpassing
|
|
break;
|
|
}
|
|
|
|
Stmt::TypeDef(td) => {
|
|
if last_expr.is_some() {
|
|
return Err(EExpr::StmtAfterExpr(sp_stmt.item.region.start()));
|
|
}
|
|
|
|
if let (
|
|
TypeDef::Alias {
|
|
header,
|
|
ann: ann_type,
|
|
},
|
|
Some((
|
|
spaces_middle,
|
|
Stmt::ValueDef(ValueDef::Body(loc_pattern, loc_def_expr)),
|
|
)),
|
|
) = (td, stmts.get(i + 1).map(|s| (s.before, s.item.value)))
|
|
{
|
|
if spaces_middle.len() <= 1
|
|
|| header
|
|
.vars
|
|
.first()
|
|
.map(|var| var.value.equivalent(&loc_pattern.value))
|
|
.unwrap_or(false)
|
|
{
|
|
// This is a case like
|
|
// UserId x : [UserId Int]
|
|
// UserId x = UserId 42
|
|
// We optimistically parsed the first line as an alias; we now turn it
|
|
// into an annotation.
|
|
|
|
let region = Region::span_across(&loc_pattern.region, &loc_def_expr.region);
|
|
|
|
let value_def = join_alias_to_body(
|
|
arena,
|
|
header,
|
|
ann_type,
|
|
spaces_middle,
|
|
loc_pattern,
|
|
loc_def_expr,
|
|
);
|
|
|
|
defs.push_value_def(
|
|
value_def,
|
|
Region::span_across(&header.name.region, ®ion),
|
|
sp_stmt.before,
|
|
&[],
|
|
);
|
|
|
|
i += 1;
|
|
} else {
|
|
defs.push_type_def(td, sp_stmt.item.region, sp_stmt.before, &[])
|
|
}
|
|
} else {
|
|
defs.push_type_def(td, sp_stmt.item.region, sp_stmt.before, &[])
|
|
}
|
|
}
|
|
Stmt::ValueDef(vd) => {
|
|
if last_expr.is_some() {
|
|
return Err(EExpr::StmtAfterExpr(sp_stmt.item.region.start()));
|
|
}
|
|
|
|
// NOTE: it shouldn't be necessary to convert ValueDef::Dbg into an expr, but
|
|
// it turns out that ValueDef::Dbg exposes some bugs in the rest of the compiler.
|
|
// In particular, it seems that the solver thinks the dbg expr must be a bool.
|
|
if let ValueDef::Dbg {
|
|
condition,
|
|
preceding_comment: _,
|
|
} = vd
|
|
{
|
|
if exprify_dbg {
|
|
let e = if i + 1 < stmts.len() {
|
|
let rest = stmts_to_expr(&stmts[i + 1..], arena)?;
|
|
Expr::DbgStmt(arena.alloc(condition), arena.alloc(rest))
|
|
} else {
|
|
Expr::Apply(
|
|
arena.alloc(Loc {
|
|
value: Expr::Dbg,
|
|
region: sp_stmt.item.region,
|
|
}),
|
|
arena.alloc([condition]),
|
|
CalledVia::Space,
|
|
)
|
|
};
|
|
|
|
let e = if sp_stmt.before.is_empty() {
|
|
e
|
|
} else {
|
|
arena.alloc(e).before(sp_stmt.before)
|
|
};
|
|
|
|
last_expr = Some(Loc::at(sp_stmt.item.region, e));
|
|
|
|
// don't re-process the rest of the statements; they got consumed by the dbg expr
|
|
break;
|
|
}
|
|
}
|
|
|
|
if let (
|
|
ValueDef::Annotation(ann_pattern, ann_type),
|
|
Some((
|
|
spaces_middle,
|
|
Stmt::ValueDef(ValueDef::Body(loc_pattern, loc_def_expr)),
|
|
)),
|
|
) = (vd, stmts.get(i + 1).map(|s| (s.before, s.item.value)))
|
|
{
|
|
if spaces_middle.len() <= 1 || ann_pattern.value.equivalent(&loc_pattern.value)
|
|
{
|
|
let region = Region::span_across(&loc_pattern.region, &loc_def_expr.region);
|
|
|
|
let value_def = ValueDef::AnnotatedBody {
|
|
ann_pattern: arena.alloc(ann_pattern),
|
|
ann_type: arena.alloc(ann_type),
|
|
lines_between: spaces_middle,
|
|
body_pattern: loc_pattern,
|
|
body_expr: loc_def_expr,
|
|
};
|
|
|
|
defs.push_value_def(
|
|
value_def,
|
|
roc_region::all::Region::span_across(&ann_pattern.region, ®ion),
|
|
sp_stmt.before,
|
|
&[],
|
|
);
|
|
i += 1;
|
|
} else {
|
|
defs.push_value_def(vd, sp_stmt.item.region, sp_stmt.before, &[])
|
|
}
|
|
} else {
|
|
defs.push_value_def(vd, sp_stmt.item.region, sp_stmt.before, &[])
|
|
}
|
|
}
|
|
}
|
|
|
|
i += 1;
|
|
}
|
|
Ok((defs, last_expr))
|
|
}
|
|
|
|
/// Given a type alias and a value definition, join them into a AnnotatedBody
|
|
pub fn join_alias_to_body<'a>(
|
|
arena: &'a Bump,
|
|
header: TypeHeader<'a>,
|
|
ann_type: Loc<TypeAnnotation<'a>>,
|
|
spaces_middle: &'a [CommentOrNewline<'a>],
|
|
body_pattern: &'a Loc<Pattern<'a>>,
|
|
body_expr: &'a Loc<Expr<'a>>,
|
|
) -> ValueDef<'a> {
|
|
let loc_name = arena.alloc(header.name.map(|x| Pattern::Tag(x)));
|
|
let ann_pattern = Pattern::Apply(loc_name, header.vars);
|
|
|
|
let vars_region = Region::across_all(header.vars.iter().map(|v| &v.region));
|
|
let region_ann_pattern = Region::span_across(&loc_name.region, &vars_region);
|
|
let loc_ann_pattern = Loc::at(region_ann_pattern, ann_pattern);
|
|
|
|
ValueDef::AnnotatedBody {
|
|
ann_pattern: arena.alloc(loc_ann_pattern),
|
|
ann_type: arena.alloc(ann_type),
|
|
lines_between: spaces_middle,
|
|
body_pattern,
|
|
body_expr,
|
|
}
|
|
}
|
|
|
|
/// This is a helper function for parsing function args.
|
|
/// The rules for (-) are special-cased, and they come up in function args.
|
|
///
|
|
/// They work like this:
|
|
///
|
|
/// x - y # "x minus y"
|
|
/// x-y # "x minus y"
|
|
/// x- y # "x minus y" (probably written in a rush)
|
|
/// x -y # "call x, passing (-y)"
|
|
///
|
|
/// Since operators have higher precedence than function application,
|
|
/// any time we encounter a '-' it is unary iff it is both preceded by spaces
|
|
/// and is *not* followed by a whitespace character.
|
|
|
|
/// When we parse an ident like `foo ` it could be any of these:
|
|
///
|
|
/// 1. A standalone variable with trailing whitespace (e.g. because an operator is next)
|
|
/// 2. The beginning of a function call (e.g. `foo bar baz`)
|
|
/// 3. The beginning of a definition (e.g. `foo =`)
|
|
/// 4. The beginning of a type annotation (e.g. `foo :`)
|
|
/// 5. A reserved keyword (e.g. `if ` or `when `), meaning we should do something else.
|
|
|
|
fn assign_or_destructure_identifier<'a>() -> impl Parser<'a, Ident<'a>, EExpr<'a>> {
|
|
parse_ident
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn with_indent<'a, E, T, P>(parser: P) -> impl Parser<'a, u32, E>
|
|
where
|
|
P: Parser<'a, T, E>,
|
|
E: 'a,
|
|
{
|
|
move |arena, state: State<'a>, min_indent: u32| {
|
|
let indent_column = state.column();
|
|
|
|
let (progress, _, state) = parser.parse(arena, state, min_indent)?;
|
|
|
|
Ok((progress, indent_column, state))
|
|
}
|
|
}
|
|
|
|
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 } => {
|
|
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 },
|
|
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`,
|
|
// rather than a `&'a [Accessor<'a>]`.
|
|
internal_error!("Parsed an Ident::Access with a first part of a tuple index");
|
|
}
|
|
None => {
|
|
internal_error!("Parsed an Ident::Access with no parts");
|
|
}
|
|
};
|
|
|
|
// The remaining items in the iterator are record field accesses,
|
|
// e.g. `bar` in `foo.bar.baz`, followed by `baz`
|
|
for field in iter {
|
|
// Wrap the previous answer in the new one, so we end up
|
|
// with a nested Expr. That way, `foo.bar.baz` gets represented
|
|
// in the AST as if it had been written (foo.bar).baz all along.
|
|
match field {
|
|
Accessor::RecordField(field) => {
|
|
answer = Expr::RecordAccess(arena.alloc(answer), field);
|
|
}
|
|
Accessor::TupleIndex(index) => {
|
|
answer = Expr::TupleAccess(arena.alloc(answer), index);
|
|
}
|
|
}
|
|
}
|
|
|
|
answer
|
|
}
|
|
Ident::AccessorFunction(string) => Expr::AccessorFunction(string),
|
|
Ident::RecordUpdaterFunction(string) => Expr::RecordUpdater(string),
|
|
Ident::Malformed(string, problem) => Expr::MalformedIdent(string, problem),
|
|
}
|
|
}
|
|
|
|
fn list_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EList<'a>> {
|
|
map_with_arena(
|
|
collection_trailing_sep_e(
|
|
byte(b'[', EList::Open),
|
|
specialize_err_ref(EList::Expr, loc_expr(false)),
|
|
byte(b',', EList::End),
|
|
byte(b']', EList::End),
|
|
Expr::SpaceBefore,
|
|
),
|
|
|arena, elements: Collection<'a, _>| {
|
|
let elements = elements.ptrify_items(arena);
|
|
Expr::List(elements)
|
|
},
|
|
)
|
|
.trace("list_literal")
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
pub enum RecordField<'a> {
|
|
RequiredValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Expr<'a>>),
|
|
OptionalValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Expr<'a>>),
|
|
IgnoredValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc<Expr<'a>>),
|
|
LabelOnly(Loc<&'a str>),
|
|
SpaceBefore(&'a RecordField<'a>, &'a [CommentOrNewline<'a>]),
|
|
SpaceAfter(&'a RecordField<'a>, &'a [CommentOrNewline<'a>]),
|
|
ApplyValue(
|
|
Loc<&'a str>,
|
|
&'a [CommentOrNewline<'a>],
|
|
&'a [CommentOrNewline<'a>],
|
|
&'a Loc<Expr<'a>>,
|
|
),
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct FoundApplyValue;
|
|
|
|
#[derive(Debug)]
|
|
pub enum NotOldBuilderFieldValue {
|
|
FoundOptionalValue,
|
|
FoundIgnoredValue,
|
|
}
|
|
|
|
impl<'a> RecordField<'a> {
|
|
fn is_apply_value(&self) -> bool {
|
|
let mut current = self;
|
|
|
|
loop {
|
|
match current {
|
|
RecordField::ApplyValue(_, _, _, _) => break true,
|
|
RecordField::SpaceBefore(field, _) | RecordField::SpaceAfter(field, _) => {
|
|
current = *field;
|
|
}
|
|
_ => break false,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn is_ignored_value(&self) -> bool {
|
|
let mut current = self;
|
|
|
|
loop {
|
|
match current {
|
|
RecordField::IgnoredValue(_, _, _) => break true,
|
|
RecordField::SpaceBefore(field, _) | RecordField::SpaceAfter(field, _) => {
|
|
current = *field;
|
|
}
|
|
_ => break false,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn to_assigned_field(
|
|
self,
|
|
arena: &'a Bump,
|
|
) -> Result<AssignedField<'a, Expr<'a>>, FoundApplyValue> {
|
|
use AssignedField::*;
|
|
|
|
match self {
|
|
RecordField::RequiredValue(loc_label, spaces, loc_expr) => {
|
|
Ok(RequiredValue(loc_label, spaces, loc_expr))
|
|
}
|
|
|
|
RecordField::OptionalValue(loc_label, spaces, loc_expr) => {
|
|
Ok(OptionalValue(loc_label, spaces, loc_expr))
|
|
}
|
|
|
|
RecordField::IgnoredValue(loc_label, spaces, loc_expr) => {
|
|
Ok(IgnoredValue(loc_label, spaces, loc_expr))
|
|
}
|
|
|
|
RecordField::LabelOnly(loc_label) => Ok(LabelOnly(loc_label)),
|
|
|
|
RecordField::ApplyValue(_, _, _, _) => Err(FoundApplyValue),
|
|
|
|
RecordField::SpaceBefore(field, spaces) => {
|
|
let assigned_field = field.to_assigned_field(arena)?;
|
|
|
|
Ok(SpaceBefore(arena.alloc(assigned_field), spaces))
|
|
}
|
|
|
|
RecordField::SpaceAfter(field, spaces) => {
|
|
let assigned_field = field.to_assigned_field(arena)?;
|
|
|
|
Ok(SpaceAfter(arena.alloc(assigned_field), spaces))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn to_builder_field(
|
|
self,
|
|
arena: &'a Bump,
|
|
) -> Result<OldRecordBuilderField<'a>, NotOldBuilderFieldValue> {
|
|
use OldRecordBuilderField::*;
|
|
|
|
match self {
|
|
RecordField::RequiredValue(loc_label, spaces, loc_expr) => {
|
|
Ok(Value(loc_label, spaces, loc_expr))
|
|
}
|
|
|
|
RecordField::OptionalValue(_, _, _) => Err(NotOldBuilderFieldValue::FoundOptionalValue),
|
|
|
|
RecordField::IgnoredValue(_, _, _) => Err(NotOldBuilderFieldValue::FoundIgnoredValue),
|
|
|
|
RecordField::LabelOnly(loc_label) => Ok(LabelOnly(loc_label)),
|
|
|
|
RecordField::ApplyValue(loc_label, colon_spaces, arrow_spaces, loc_expr) => {
|
|
Ok(ApplyValue(loc_label, colon_spaces, arrow_spaces, loc_expr))
|
|
}
|
|
|
|
RecordField::SpaceBefore(field, spaces) => {
|
|
let builder_field = field.to_builder_field(arena)?;
|
|
|
|
Ok(SpaceBefore(arena.alloc(builder_field), spaces))
|
|
}
|
|
|
|
RecordField::SpaceAfter(field, spaces) => {
|
|
let builder_field = field.to_builder_field(arena)?;
|
|
|
|
Ok(SpaceAfter(arena.alloc(builder_field), spaces))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> Spaceable<'a> for RecordField<'a> {
|
|
fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
|
RecordField::SpaceBefore(self, spaces)
|
|
}
|
|
fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self {
|
|
RecordField::SpaceAfter(self, spaces)
|
|
}
|
|
}
|
|
|
|
pub fn record_field<'a>() -> impl Parser<'a, RecordField<'a>, ERecord<'a>> {
|
|
use RecordField::*;
|
|
|
|
map_with_arena(
|
|
either(
|
|
and(
|
|
specialize_err(|_, pos| ERecord::Field(pos), loc(lowercase_ident())),
|
|
and(
|
|
spaces(),
|
|
optional(either(
|
|
and(byte(b':', ERecord::Colon), record_field_expr()),
|
|
and(
|
|
byte(b'?', ERecord::QuestionMark),
|
|
spaces_before(specialize_err_ref(ERecord::Expr, loc_expr(false))),
|
|
),
|
|
)),
|
|
),
|
|
),
|
|
and(
|
|
loc(skip_first(
|
|
byte(b'_', ERecord::UnderscoreField),
|
|
optional(specialize_err(
|
|
|_, pos| ERecord::Field(pos),
|
|
lowercase_ident(),
|
|
)),
|
|
)),
|
|
and(
|
|
spaces(),
|
|
skip_first(
|
|
byte(b':', ERecord::Colon),
|
|
spaces_before(specialize_err_ref(ERecord::Expr, loc_expr(false))),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|arena: &'a bumpalo::Bump, field_data| {
|
|
match field_data {
|
|
Either::First((loc_label, (spaces, opt_loc_val))) => {
|
|
match opt_loc_val {
|
|
Some(Either::First((_, RecordFieldExpr::Value(loc_val)))) => {
|
|
RequiredValue(loc_label, spaces, arena.alloc(loc_val))
|
|
}
|
|
|
|
Some(Either::First((_, RecordFieldExpr::Apply(arrow_spaces, loc_val)))) => {
|
|
ApplyValue(loc_label, spaces, arrow_spaces, arena.alloc(loc_val))
|
|
}
|
|
|
|
Some(Either::Second((_, loc_val))) => {
|
|
OptionalValue(loc_label, spaces, arena.alloc(loc_val))
|
|
}
|
|
|
|
// If no value was provided, record it as a Var.
|
|
// Canonicalize will know what to do with a Var later.
|
|
None => {
|
|
if !spaces.is_empty() {
|
|
SpaceAfter(arena.alloc(LabelOnly(loc_label)), spaces)
|
|
} else {
|
|
LabelOnly(loc_label)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Either::Second((loc_opt_label, (spaces, loc_val))) => {
|
|
let loc_label = loc_opt_label
|
|
.map(|opt_label| opt_label.unwrap_or_else(|| arena.alloc_str("")));
|
|
|
|
IgnoredValue(loc_label, spaces, arena.alloc(loc_val))
|
|
}
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
enum RecordFieldExpr<'a> {
|
|
Apply(&'a [CommentOrNewline<'a>], Loc<Expr<'a>>),
|
|
Value(Loc<Expr<'a>>),
|
|
}
|
|
|
|
fn record_field_expr<'a>() -> impl Parser<'a, RecordFieldExpr<'a>, ERecord<'a>> {
|
|
map_with_arena(
|
|
and(
|
|
spaces(),
|
|
either(
|
|
and(
|
|
two_bytes(b'<', b'-', ERecord::Arrow),
|
|
spaces_before(specialize_err_ref(ERecord::Expr, loc_expr(false))),
|
|
),
|
|
specialize_err_ref(ERecord::Expr, loc_expr(false)),
|
|
),
|
|
),
|
|
|arena: &'a bumpalo::Bump, (spaces, either)| match either {
|
|
Either::First((_, loc_expr)) => RecordFieldExpr::Apply(spaces, loc_expr),
|
|
Either::Second(loc_expr) => RecordFieldExpr::Value({
|
|
if spaces.is_empty() {
|
|
loc_expr
|
|
} else {
|
|
arena
|
|
.alloc(loc_expr.value)
|
|
.with_spaces_before(spaces, loc_expr.region)
|
|
}
|
|
}),
|
|
},
|
|
)
|
|
}
|
|
|
|
enum RecordHelpPrefix {
|
|
Update,
|
|
Mapper,
|
|
}
|
|
|
|
fn record_prefix_identifier<'a>() -> impl Parser<'a, Expr<'a>, ERecord<'a>> {
|
|
specialize_err(
|
|
|_, pos| ERecord::Prefix(pos),
|
|
map_with_arena(parse_ident, ident_to_expr),
|
|
)
|
|
}
|
|
|
|
struct RecordHelp<'a> {
|
|
prefix: Option<(Loc<Expr<'a>>, RecordHelpPrefix)>,
|
|
fields: Collection<'a, Loc<RecordField<'a>>>,
|
|
}
|
|
|
|
fn record_help<'a>() -> impl Parser<'a, RecordHelp<'a>, ERecord<'a>> {
|
|
between(
|
|
byte(b'{', ERecord::Open),
|
|
reset_min_indent(record!(RecordHelp {
|
|
// You can optionally have an identifier followed by an '&' to
|
|
// make this a record update, e.g. { Foo.user & username: "blah" }.
|
|
prefix: optional(backtrackable(and(
|
|
// We wrap the ident in an Expr here,
|
|
// so that we have a Spaceable value to work with,
|
|
// and then in canonicalization verify that it's an Expr::Var
|
|
// (and not e.g. an `Expr::Access`) and extract its string.
|
|
spaces_around(loc(record_prefix_identifier())),
|
|
map_with_arena(
|
|
either(
|
|
byte(b'&', ERecord::Ampersand),
|
|
two_bytes(b'<', b'-', ERecord::Arrow),
|
|
),
|
|
|_arena, output| match output {
|
|
Either::First(()) => RecordHelpPrefix::Update,
|
|
Either::Second(()) => RecordHelpPrefix::Mapper,
|
|
}
|
|
)
|
|
))),
|
|
fields: collection_inner(
|
|
loc(record_field()),
|
|
byte(b',', ERecord::End),
|
|
RecordField::SpaceBefore
|
|
),
|
|
})),
|
|
byte(b'}', ERecord::End),
|
|
)
|
|
}
|
|
|
|
fn record_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EExpr<'a>> {
|
|
then(
|
|
and(
|
|
specialize_err(EExpr::Record, record_help()),
|
|
// there can be field access, e.g. `{ x : 4 }.x`
|
|
record_field_access_chain(),
|
|
),
|
|
move |arena, state, _, (record, accessors)| {
|
|
let expr_result = match record.prefix {
|
|
Some((update, RecordHelpPrefix::Update)) => {
|
|
record_update_help(arena, update, record.fields)
|
|
}
|
|
Some((mapper, RecordHelpPrefix::Mapper)) => {
|
|
new_record_builder_help(arena, mapper, record.fields)
|
|
}
|
|
None => {
|
|
let special_field_found = record.fields.iter().find_map(|field| {
|
|
if field.value.is_apply_value() {
|
|
Some(old_record_builder_help(arena, record.fields))
|
|
} else if field.value.is_ignored_value() {
|
|
Some(Err(EExpr::RecordUpdateIgnoredField(field.region)))
|
|
} else {
|
|
None
|
|
}
|
|
});
|
|
|
|
special_field_found.unwrap_or_else(|| {
|
|
let fields = record.fields.map_items(arena, |loc_field| {
|
|
loc_field.map(|field| field.to_assigned_field(arena).unwrap())
|
|
});
|
|
|
|
Ok(Expr::Record(fields))
|
|
})
|
|
}
|
|
};
|
|
|
|
match expr_result {
|
|
Ok(expr) => {
|
|
let value = apply_expr_access_chain(arena, expr, accessors);
|
|
|
|
Ok((MadeProgress, value, state))
|
|
}
|
|
Err(err) => Err((MadeProgress, err)),
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
fn record_update_help<'a>(
|
|
arena: &'a Bump,
|
|
update: Loc<Expr<'a>>,
|
|
fields: Collection<'a, Loc<RecordField<'a>>>,
|
|
) -> Result<Expr<'a>, EExpr<'a>> {
|
|
let result = fields.map_items_result(arena, |loc_field| {
|
|
match loc_field.value.to_assigned_field(arena) {
|
|
Ok(AssignedField::IgnoredValue(_, _, _)) => {
|
|
Err(EExpr::RecordUpdateIgnoredField(loc_field.region))
|
|
}
|
|
Ok(builder_field) => Ok(Loc {
|
|
region: loc_field.region,
|
|
value: builder_field,
|
|
}),
|
|
Err(FoundApplyValue) => Err(EExpr::RecordUpdateOldBuilderField(loc_field.region)),
|
|
}
|
|
});
|
|
|
|
result.map(|fields| Expr::RecordUpdate {
|
|
update: &*arena.alloc(update),
|
|
fields,
|
|
})
|
|
}
|
|
|
|
fn new_record_builder_help<'a>(
|
|
arena: &'a Bump,
|
|
mapper: Loc<Expr<'a>>,
|
|
fields: Collection<'a, Loc<RecordField<'a>>>,
|
|
) -> Result<Expr<'a>, EExpr<'a>> {
|
|
let result = fields.map_items_result(arena, |loc_field| {
|
|
match loc_field.value.to_assigned_field(arena) {
|
|
Ok(builder_field) => Ok(Loc {
|
|
region: loc_field.region,
|
|
value: builder_field,
|
|
}),
|
|
Err(FoundApplyValue) => Err(EExpr::RecordBuilderOldBuilderField(loc_field.region)),
|
|
}
|
|
});
|
|
|
|
result.map(|fields| Expr::RecordBuilder {
|
|
mapper: &*arena.alloc(mapper),
|
|
fields,
|
|
})
|
|
}
|
|
|
|
fn old_record_builder_help<'a>(
|
|
arena: &'a Bump,
|
|
fields: Collection<'a, Loc<RecordField<'a>>>,
|
|
) -> Result<Expr<'a>, EExpr<'a>> {
|
|
let result = fields.map_items_result(arena, |loc_field| {
|
|
match loc_field.value.to_builder_field(arena) {
|
|
Ok(builder_field) => Ok(Loc {
|
|
region: loc_field.region,
|
|
value: builder_field,
|
|
}),
|
|
Err(NotOldBuilderFieldValue::FoundOptionalValue) => {
|
|
Err(EExpr::OptionalValueInOldRecordBuilder(loc_field.region))
|
|
}
|
|
Err(NotOldBuilderFieldValue::FoundIgnoredValue) => {
|
|
Err(EExpr::IgnoredValueInOldRecordBuilder(loc_field.region))
|
|
}
|
|
}
|
|
});
|
|
|
|
result.map(Expr::OldRecordBuilder)
|
|
}
|
|
|
|
fn apply_expr_access_chain<'a>(
|
|
arena: &'a Bump,
|
|
value: Expr<'a>,
|
|
accessors: Vec<'a, Suffix<'a>>,
|
|
) -> Expr<'a> {
|
|
accessors
|
|
.into_iter()
|
|
.fold(value, |value, accessor| match accessor {
|
|
Suffix::Accessor(Accessor::RecordField(field)) => {
|
|
Expr::RecordAccess(arena.alloc(value), field)
|
|
}
|
|
Suffix::Accessor(Accessor::TupleIndex(field)) => {
|
|
Expr::TupleAccess(arena.alloc(value), field)
|
|
}
|
|
Suffix::TrySuffix(target) => Expr::TrySuffix {
|
|
target,
|
|
expr: arena.alloc(value),
|
|
},
|
|
})
|
|
}
|
|
|
|
fn string_like_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EString<'a>> {
|
|
map_with_arena(
|
|
crate::string_literal::parse_str_like_literal(),
|
|
|arena, lit| match lit {
|
|
StrLikeLiteral::Str(s) => Expr::Str(s),
|
|
StrLikeLiteral::SingleQuote(s) => {
|
|
// TODO: preserve the original escaping
|
|
Expr::SingleQuote(s.to_str_in(arena))
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
fn positive_number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, ENumber> {
|
|
map(
|
|
crate::number_literal::positive_number_literal(),
|
|
|literal| {
|
|
use crate::number_literal::NumLiteral::*;
|
|
|
|
match literal {
|
|
Num(s) => Expr::Num(s),
|
|
Float(s) => Expr::Float(s),
|
|
NonBase10Int {
|
|
string,
|
|
base,
|
|
is_negative,
|
|
} => Expr::NonBase10Int {
|
|
string,
|
|
base,
|
|
is_negative,
|
|
},
|
|
}
|
|
},
|
|
)
|
|
}
|
|
|
|
fn number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, ENumber> {
|
|
map(crate::number_literal::number_literal(), |literal| {
|
|
use crate::number_literal::NumLiteral::*;
|
|
|
|
match literal {
|
|
Num(s) => Expr::Num(s),
|
|
Float(s) => Expr::Float(s),
|
|
NonBase10Int {
|
|
string,
|
|
base,
|
|
is_negative,
|
|
} => Expr::NonBase10Int {
|
|
string,
|
|
base,
|
|
is_negative,
|
|
},
|
|
}
|
|
})
|
|
}
|
|
|
|
const BINOP_CHAR_SET: &[u8] = b"+-/*=.<>:&|^?%!";
|
|
|
|
const BINOP_CHAR_MASK: [bool; 125] = {
|
|
let mut result = [false; 125];
|
|
|
|
let mut i = 0;
|
|
while i < BINOP_CHAR_SET.len() {
|
|
let index = BINOP_CHAR_SET[i] as usize;
|
|
|
|
result[index] = true;
|
|
|
|
i += 1;
|
|
}
|
|
|
|
result
|
|
};
|
|
|
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
|
enum OperatorOrDef {
|
|
BinOp(BinOp),
|
|
Assignment,
|
|
AliasOrOpaque(AliasOrOpaque),
|
|
Backpassing,
|
|
}
|
|
|
|
fn bin_op<'a>(check_for_defs: bool) -> impl Parser<'a, BinOp, EExpr<'a>> {
|
|
move |_, state: State<'a>, _m| {
|
|
let start = state.pos();
|
|
let (_, op, state) = operator_help(EExpr::Start, EExpr::BadOperator, state)?;
|
|
let err_progress = if check_for_defs {
|
|
MadeProgress
|
|
} else {
|
|
NoProgress
|
|
};
|
|
match op {
|
|
OperatorOrDef::BinOp(op) => Ok((MadeProgress, op, state)),
|
|
OperatorOrDef::Assignment => Err((err_progress, EExpr::BadOperator("=", start))),
|
|
OperatorOrDef::AliasOrOpaque(AliasOrOpaque::Alias) => {
|
|
Err((err_progress, EExpr::BadOperator(":", start)))
|
|
}
|
|
OperatorOrDef::AliasOrOpaque(AliasOrOpaque::Opaque) => {
|
|
Err((err_progress, EExpr::BadOperator(":=", start)))
|
|
}
|
|
OperatorOrDef::Backpassing => Err((err_progress, EExpr::BadOperator("<-", start))),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn operator<'a>() -> impl Parser<'a, OperatorOrDef, EExpr<'a>> {
|
|
(move |_, state, _m| operator_help(EExpr::Start, EExpr::BadOperator, state)).trace("operator")
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn operator_help<'a, F, G, E>(
|
|
to_expectation: F,
|
|
to_error: G,
|
|
mut state: State<'a>,
|
|
) -> ParseResult<'a, OperatorOrDef, E>
|
|
where
|
|
F: Fn(Position) -> E,
|
|
G: Fn(&'a str, Position) -> E,
|
|
E: 'a,
|
|
{
|
|
let chomped = chomp_ops(state.bytes());
|
|
|
|
macro_rules! good {
|
|
($op:expr, $width:expr) => {{
|
|
state = state.advance($width);
|
|
|
|
Ok((MadeProgress, $op, state))
|
|
}};
|
|
}
|
|
|
|
macro_rules! bad_made_progress {
|
|
($op:expr) => {{
|
|
Err((MadeProgress, to_error($op, state.pos())))
|
|
}};
|
|
}
|
|
|
|
match chomped {
|
|
"" => Err((NoProgress, to_expectation(state.pos()))),
|
|
"+" => good!(OperatorOrDef::BinOp(BinOp::Plus), 1),
|
|
"-" => good!(OperatorOrDef::BinOp(BinOp::Minus), 1),
|
|
"*" => good!(OperatorOrDef::BinOp(BinOp::Star), 1),
|
|
"/" => good!(OperatorOrDef::BinOp(BinOp::Slash), 1),
|
|
"%" => good!(OperatorOrDef::BinOp(BinOp::Percent), 1),
|
|
"^" => good!(OperatorOrDef::BinOp(BinOp::Caret), 1),
|
|
">" => good!(OperatorOrDef::BinOp(BinOp::GreaterThan), 1),
|
|
"<" => good!(OperatorOrDef::BinOp(BinOp::LessThan), 1),
|
|
"." => {
|
|
// a `.` makes no progress, so it does not interfere with `.foo` access(or)
|
|
Err((NoProgress, to_error(".", state.pos())))
|
|
}
|
|
"=" => good!(OperatorOrDef::Assignment, 1),
|
|
":=" => good!(OperatorOrDef::AliasOrOpaque(AliasOrOpaque::Opaque), 2),
|
|
":" => good!(OperatorOrDef::AliasOrOpaque(AliasOrOpaque::Alias), 1),
|
|
"|>" => good!(OperatorOrDef::BinOp(BinOp::Pizza), 2),
|
|
"==" => good!(OperatorOrDef::BinOp(BinOp::Equals), 2),
|
|
"!=" => good!(OperatorOrDef::BinOp(BinOp::NotEquals), 2),
|
|
">=" => good!(OperatorOrDef::BinOp(BinOp::GreaterThanOrEq), 2),
|
|
"<=" => good!(OperatorOrDef::BinOp(BinOp::LessThanOrEq), 2),
|
|
"&&" => good!(OperatorOrDef::BinOp(BinOp::And), 2),
|
|
"||" => good!(OperatorOrDef::BinOp(BinOp::Or), 2),
|
|
"//" => good!(OperatorOrDef::BinOp(BinOp::DoubleSlash), 2),
|
|
"->" => {
|
|
// makes no progress, so it does not interfere with `_ if isGood -> ...`
|
|
Err((NoProgress, to_error("->", state.pos())))
|
|
}
|
|
"<-" => good!(OperatorOrDef::Backpassing, 2),
|
|
"!" => Err((NoProgress, to_error("!", state.pos()))),
|
|
_ => bad_made_progress!(chomped),
|
|
}
|
|
}
|
|
|
|
fn chomp_ops(bytes: &[u8]) -> &str {
|
|
let mut chomped = 0;
|
|
|
|
for c in bytes.iter() {
|
|
if let Some(true) = BINOP_CHAR_MASK.get(*c as usize) {
|
|
chomped += 1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
unsafe {
|
|
// Safe because BINOP_CHAR_SET only contains ascii chars
|
|
std::str::from_utf8_unchecked(&bytes[..chomped])
|
|
}
|
|
}
|