diff --git a/compiler/parse/src/blankspace.rs b/compiler/parse/src/blankspace.rs index 8fd5dac813..908ab6009b 100644 --- a/compiler/parse/src/blankspace.rs +++ b/compiler/parse/src/blankspace.rs @@ -117,6 +117,22 @@ where ) } +pub fn check_indent<'a, E>( + min_indent: u16, + indent_problem: fn(Row, Col) -> E, +) -> impl Parser<'a, (), E> +where + E: 'a, +{ + move |_, state: State<'a>| { + if state.column > state.indent_col { + Ok((NoProgress, (), state)) + } else { + Err((NoProgress, indent_problem(state.line, state.column), state)) + } + } +} + pub fn space0_e<'a, E>( min_indent: u16, space_problem: fn(BadInputError, Row, Col) -> E, diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 5ceeae8bd2..b522119338 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -368,6 +368,22 @@ fn parse_loc_term<'a>( .parse(arena, state) } +fn parse_loc_term_better<'a>( + min_indent: u16, + arena: &'a Bump, + state: State<'a>, +) -> ParseResult<'a, Located>, EExpr<'a>> { + one_of!( + in_parens_region_fix(min_indent), + loc!(specialize(EExpr::Str, string_literal_help())), + loc!(specialize(EExpr::Number, positive_number_literal_help())), + loc!(record_literal_help(min_indent)), + loc!(specialize(EExpr::List, list_literal_help(min_indent))), + loc!(ident_etc_help(min_indent)) + ) + .parse(arena, state) +} + fn loc_possibly_negative_or_negated_term<'a>( min_indent: u16, ) -> impl Parser<'a, Located>, EExpr<'a>> { @@ -555,29 +571,271 @@ fn foobar<'a>( match space0_e(min_indent, EExpr::Space, EExpr::IndentEnd).parse(arena, state) { Err((_, _, state)) => Ok((MadeProgress, expr.value, state)), - Ok((_, spaces_before_op, state)) => parse_expr_end( - min_indent, - Vec::new_in(arena), - Vec::new_in(arena), - expr, - spaces_before_op, - initial, - arena, - state, - ), + Ok((_, spaces_before_op, state)) => { + let expr_state = ExprState { + operators: Vec::new_in(arena), + arguments: Vec::new_in(arena), + expr, + spaces_after: spaces_before_op, + initial, + end: state.get_position(), + }; + + parse_expr_end(min_indent, expr_state, arena, state) + } + } +} + +struct ExprState<'a> { + operators: Vec<'a, (Located>, Located)>, + arguments: Vec<'a, &'a Located>>, + expr: Located>, + spaces_after: &'a [CommentOrNewline<'a>], + initial: State<'a>, + end: Position, +} + +fn parse_expr_final<'a>( + min_indent: u16, + expr_state: ExprState<'a>, + arena: &'a Bump, + state: State<'a>, +) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { + let mut expr = to_call( + arena, + expr_state.arguments, + expr_state.expr, + expr_state.spaces_after, + ); + + for (left_arg, op) in expr_state.operators.into_iter().rev() { + let region = Region::span_across(&left_arg.region, &expr.region); + let new = Expr::BinOp(arena.alloc((left_arg, op, expr))); + expr = Located::at(region, new); + } + + Ok((MadeProgress, expr.value, state)) +} + +fn to_call<'a>( + arena: &'a Bump, + arguments: Vec<'a, &'a Located>>, + loc_expr1: Located>, + _spaces_before: &'a [CommentOrNewline<'a>], +) -> Located> { + 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 apply = Expr::Apply( + arena.alloc(loc_expr1), + arguments.into_bump_slice(), + CalledVia::Space, + ); + + Located::at(region, apply) + } +} + +fn parse_expr_operator<'a>( + min_indent: u16, + mut expr_state: ExprState<'a>, + loc_op: Located, + arena: &'a Bump, + state: State<'a>, +) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { + let (space_progress, mut spaces, state) = + space0_e(min_indent, EExpr::Space, EExpr::IndentEnd).parse(arena, state)?; + + // add previous spaces to the expr + std::mem::swap(&mut spaces, &mut expr_state.spaces_after); + + expr_state.expr = if spaces.is_empty() { + expr_state.expr + } else { + arena + .alloc(expr_state.expr.value) + .with_spaces_after(spaces, expr_state.expr.region) + }; + + // 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.get_position(); + if op == BinOp::Minus && expr_state.end != op_start && op_end == new_start { + // negative terms + + let (_, arg, state) = parse_loc_term(min_indent, arena, state)?; + let new_end = state.get_position(); + todo!() + } else { + match loc_possibly_negative_or_negated_term(min_indent).parse(arena, state) { + Err((MadeProgress, f, s)) => Err((MadeProgress, f, s)), + Ok((_, new_expr, state)) => { + let new_end = state.get_position(); + + expr_state.initial = state; + + match space0_e(min_indent, EExpr::Space, EExpr::IndentEnd).parse(arena, state) { + Err((_, _, state)) => { + let spaces = expr_state.spaces_after; + + let new_expr = if spaces.is_empty() { + new_expr + } else { + arena + .alloc(new_expr.value) + .with_spaces_before(spaces, new_expr.region) + }; + + let args = std::mem::replace(&mut expr_state.arguments, Vec::new_in(arena)); + + let call = to_call(arena, args, expr_state.expr, spaces); + + expr_state.operators.push((call, loc_op)); + expr_state.expr = new_expr; + expr_state.end = new_end; + expr_state.spaces_after = &[]; + + parse_expr_final(min_indent, expr_state, arena, state) + } + Ok((_, mut spaces, state)) => { + std::mem::swap(&mut spaces, &mut expr_state.spaces_after); + + let new_expr = if spaces.is_empty() { + new_expr + } else { + arena + .alloc(new_expr.value) + .with_spaces_before(spaces, new_expr.region) + }; + + let args = std::mem::replace(&mut expr_state.arguments, Vec::new_in(arena)); + + let call = to_call(arena, args, expr_state.expr, spaces); + + expr_state.operators.push((call, loc_op)); + expr_state.expr = new_expr; + expr_state.end = new_end; + + parse_expr_end(min_indent, expr_state, arena, state) + } + } + } + Err((NoProgress, _, _)) => { + todo!() + } + } + } +} + +fn parse_expr_end2<'a>( + min_indent: u16, + mut expr_state: ExprState<'a>, + arena: &'a Bump, + state: State<'a>, +) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { + let parser = skip_first!( + crate::blankspace::check_indent(min_indent, EExpr::IndentEnd), + move |a, s| parse_loc_term_better(min_indent, a, s) + ); + + match parser.parse(arena, state) { + Err((MadeProgress, f, s)) => Err((MadeProgress, f, s)), + Ok((_, arg, state)) => { + let new_end = state.get_position(); + + expr_state.initial = state; + + match space0_e(min_indent, EExpr::Space, EExpr::IndentEnd).parse(arena, state) { + Err((_, _, state)) => { + let spaces = expr_state.spaces_after; + + let arg = if spaces.is_empty() { + arg + } else { + arena + .alloc(arg.value) + .with_spaces_before(spaces, arg.region) + }; + + expr_state.arguments.push(arena.alloc(arg)); + expr_state.end = new_end; + expr_state.spaces_after = &[]; + + parse_expr_final(min_indent, expr_state, arena, state) + } + Ok((_, mut spaces, state)) => { + std::mem::swap(&mut spaces, &mut expr_state.spaces_after); + + let arg = if spaces.is_empty() { + arg + } else { + arena + .alloc(arg.value) + .with_spaces_before(spaces, arg.region) + }; + + expr_state.arguments.push(arena.alloc(arg)); + expr_state.end = new_end; + + parse_expr_end(min_indent, expr_state, arena, state) + } + } + } + Err((NoProgress, _, _)) => { + // try an operator + match loc!(operator()).parse(arena, state) { + Err((MadeProgress, f, s)) => Err((MadeProgress, f, s)), + Ok((_, loc_op, state)) => { + parse_expr_operator(min_indent, expr_state, loc_op, arena, state) + } + Err((NoProgress, _, _)) => { + // roll back space parsing + let state = expr_state.initial; + + if expr_state.operators.is_empty() { + let expr = to_call( + arena, + expr_state.arguments, + expr_state.expr, + expr_state.spaces_after, + ); + + Ok((MadeProgress, expr.value, state)) + } else { + let mut expr = to_call( + arena, + expr_state.arguments, + expr_state.expr, + expr_state.spaces_after, + ); + + for (left_arg, op) in expr_state.operators.into_iter().rev() { + let region = Region::span_across(&left_arg.region, &expr.region); + let new = Expr::BinOp(arena.alloc((left_arg, op, expr))); + expr = Located::at(region, new); + } + + Ok((MadeProgress, expr.value, state)) + } + } + } + } } } fn parse_expr_end<'a>( min_indent: u16, - operators: Vec<'a, (Located>, BinOp)>, - arguments: Vec<'a, Located>>, - loc_expr1: Located>, - spaces_before: &'a [CommentOrNewline<'a>], - initial: State<'a>, + mut expr_state: ExprState<'a>, arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { + return parse_expr_end2(min_indent, expr_state, arena, state); let parser = and!( loc!(operator()), // The spaces *after* the operator can be attached directly to @@ -592,6 +850,12 @@ fn parse_expr_end<'a>( match parser.parse(arena, state) { Ok((_, (loc_op, loc_expr2), state)) => { + let ExprState { + expr: loc_expr1, + spaces_after: spaces_before, + .. + } = expr_state; + let loc_expr1 = if spaces_before.is_empty() { loc_expr1 } else { @@ -604,7 +868,7 @@ fn parse_expr_end<'a>( Ok((MadeProgress, Expr::BinOp(tuple), state)) } - Err((NoProgress, _, _)) => Ok((MadeProgress, loc_expr1.value, initial)), + Err((NoProgress, _, _)) => Ok((MadeProgress, expr_state.expr.value, expr_state.initial)), Err((MadeProgress, fail, state)) => Err((MadeProgress, fail, state)), } } @@ -2389,6 +2653,29 @@ fn string_literal_help<'a>() -> impl Parser<'a, Expr<'a>, EString<'a>> { map!(crate::string_literal::parse(), Expr::Str) } +fn positive_number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, Number> { + 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>, Number> { map!(crate::number_literal::number_literal(), |literal| { use crate::number_literal::NumLiteral::*; diff --git a/compiler/parse/src/number_literal.rs b/compiler/parse/src/number_literal.rs index 1f6ee8609e..2c1796a0b8 100644 --- a/compiler/parse/src/number_literal.rs +++ b/compiler/parse/src/number_literal.rs @@ -11,6 +11,20 @@ pub enum NumLiteral<'a> { }, } +pub fn positive_number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, Number> { + move |_arena, state: State<'a>| { + match state.bytes.get(0) { + Some(first_byte) if (*first_byte as char).is_ascii_digit() => { + parse_number_base(false, &state.bytes, state) + } + _ => { + // this is not a number at all + Err((Progress::NoProgress, Number::End, state)) + } + } + } +} + pub fn number_literal<'a>() -> impl Parser<'a, NumLiteral<'a>, Number> { move |_arena, state: State<'a>| { match state.bytes.get(0) { diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index a152464ce0..8da95b9a6a 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -640,6 +640,33 @@ mod test_parse { assert_eq!(Ok(expected), actual); } + #[test] + fn newline_and_spaces_before_less_than() { + let arena = Bump::new(); + let spaced_int = arena.alloc(Num("1")).after(&[Newline]); + let tuple = arena.alloc(( + Located::new(0, 0, 4, 5, spaced_int), + Located::new(1, 1, 4, 5, LessThan), + Located::new(1, 1, 6, 7, Num("2")), + )); + + let newlines = bumpalo::vec![in &arena; Newline, Newline]; + let def = Def::Body( + arena.alloc(Located::new(0, 0, 0, 1, Identifier("x"))), + arena.alloc(Located::new(0, 1, 4, 7, BinOp(tuple))), + ); + let loc_def = &*arena.alloc(Located::new(0, 1, 0, 7, def)); + let defs = &[loc_def]; + let ret = Expr::SpaceBefore(arena.alloc(Num("42")), newlines.into_bump_slice()); + let loc_ret = Located::new(3, 3, 0, 2, ret); + let expected = Defs(defs, arena.alloc(loc_ret)); + + // let expected = BinOp(tuple); + let actual = parse_expr_with(&arena, "x = 1\n < 2\n\n42"); + + assert_eq!(Ok(expected), actual); + } + #[test] fn comment_with_non_ascii() { let arena = Bump::new(); diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index 2a0f18dd5e..5bd269559f 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -4199,14 +4199,14 @@ mod test_reporting { x == 5 Num.add 1 2 - x y + { x, y } "# ), indoc!( r#" ── TOO MANY ARGS ─────────────────────────────────────────────────────────────── - The `add` function expects 2 arguments, but it got 4 instead: + The `add` function expects 2 arguments, but it got 3 instead: 4│ Num.add 1 2 ^^^^^^^