///|
/// Whether an expression unit follows a `|>` (an `echo` there takes no
/// expression).
priv enum UnitContext {
  FollowingPipe
  Other
} derive(Eq)

///|
/// `unit (op unit)*`, reduced by precedence; `None` when `token0` does not
/// start an expression.
fn Parser::parse_expression(self : Parser) -> @ast.Expr? raise ParseError {
  self.parse_expression_inner(false)
}

///|
fn Parser::parse_expression_inner(
  self : Parser,
  is_let_binding : Bool,
) -> @ast.Expr? raise ParseError {
  let opstack : Array[OpEntry] = []
  let estack : Array[@ast.Expr] = []
  let mut last_op_start = 0
  let mut last_op_end = 0
  let mut context = Other
  while true {
    match self.parse_expression_unit(context) {
      Some(unit) => {
        self.post_process_expression_unit(unit, is_let_binding)
        estack.push(unit)
      }
      None =>
        if estack.length() == 0 {
          return None
        } else {
          parse_error(OpNakedRight, sp(last_op_start, last_op_end))
        }
    }
    guard self.token0 is Some(t) else { break }
    guard precedence(t.kind) is Some(prec) else { break }
    context = if t.kind is Pipe { FollowingPipe } else { Other }
    self.advance()
    last_op_start = t.span.start
    last_op_end = t.span.end
    ignore(
      handle_operator(
        Some({ token: t, precedence: prec, }),
        opstack,
        estack,
        reduce_expression,
      ),
    )
  }
  handle_operator(None, opstack, estack, reduce_expression)
}

///|
/// A better error for `[x] = [1]` outside a `let`.
fn Parser::post_process_expression_unit(
  self : Parser,
  unit : @ast.Expr,
  is_let_binding : Bool,
) -> Unit raise ParseError {
  if !is_let_binding && unit is List(..) {
    if self.token0 is Some({ kind: Equal, span, .. }) {
      parse_error(NoLetBinding, span)
    }
  }
}

///|
/// One expression unit: a literal, a name, `todo`, `panic`, `echo`, a tuple,
/// list, bit array, `fn`, block, `case`, `!x` or `-x`, with any field
/// accesses, tuple indexes, calls and record updates that follow it.
fn Parser::parse_expression_unit(
  self : Parser,
  context : UnitContext,
) -> @ast.Expr? raise ParseError {
  self.nested(op_expression, () => self.parse_expression_unit_inner(context))
}

///|
fn Parser::parse_expression_unit_inner(
  self : Parser,
  context : UnitContext,
) -> @ast.Expr? raise ParseError {
  let mut expr : @ast.Expr = match self.token0 {
    Some({ kind: String(value), span, .. }) => {
      self.advance()
      String(location=span, value~)
    }
    Some({ kind: Int(value~, int_value~), span, .. }) => {
      self.advance()
      Int(location=span, value~, int_value~)
    }
    Some({ kind: Float(value~, float_value~), span, .. }) => {
      self.advance()
      Float(location=span, value~, float_value~)
    }
    Some({ kind: Name(name) | UpName(name), span, .. }) => {
      self.advance()
      Var(location=span, name~)
    }
    Some({ kind: Keyword(Todo), span, .. }) => {
      self.advance()
      let message = self.maybe_parse_as_message()
      let end = match message {
        Some(m) => m.location().end
        None => span.end
      }
      Todo(kind=Keyword, location=sp(span.start, end), message~)
    }
    Some({ kind: Keyword(Panic), span, .. }) => {
      self.advance()
      let message = self.maybe_parse_as_message()
      let end = match message {
        Some(m) => m.location().end
        None => span.end
      }
      Panic(location=sp(span.start, end), message~)
    }
    Some({ kind: Keyword(Echo), span, .. }) => {
      self.advance()
      if context == FollowingPipe {
        // `|> echo` takes no expression.
        let message = self.maybe_parse_as_message()
        let end = match message {
          Some(m) => m.location().end
          None => span.end
        }
        Echo(
          location=sp(span.start, end),
          keyword_end=span.end,
          expression=None,
          message~,
        )
      } else {
        // A missing expression is an analysis error in the compiler, not a
        // parse error.
        let expression = self.parse_expression()
        let end = match expression {
          Some(e) => e.location().end
          None => span.end
        }
        let message = self.maybe_parse_as_message()
        let end = match message {
          Some(m) => m.location().end
          None => end
        }
        Echo(
          location=sp(span.start, end),
          keyword_end=span.end,
          expression~,
          message~,
        )
      }
    }
    Some({ kind: Hash, span, .. }) => {
      self.advance()
      let _ = try self.expect_one(LeftParen) catch {
        e => raise add_comment_style_hint(e)
      } noraise {
        v => v
      }
      let elements = self.series_of(p => p.parse_expression(), Some(Comma))
      let (_, end) = self.expect_one_following_series(
        RightParen,
        "an expression",
      )
      Tuple(location=sp(span.start, end), elements=elements[:])
    }
    Some({ kind: LeftSquare, span, .. }) => {
      self.advance()
      self.parse_list(span.start)
    }
    Some({ kind: LtLt, span, .. }) => {
      self.advance()
      let segments = self.series_of(
        p => {
          p.parse_bit_array_segment(
            q => q.parse_expression_unit(Other),
            q => q.expect_expression(),
            bit_array_expression_int,
            x => x.location(),
          )
        },
        Some(Comma),
      )
      let (_, end) = self.expect_one_following_series(
        GtGt,
        "a bit array segment",
      )
      BitArray(location=sp(span.start, end), segments=segments[:])
    }
    Some({ kind: Keyword(Fn), span, .. }) => {
      self.advance()
      match self.parse_function(span.start, false, true, Attributes::new()) {
        Some(Function(f)) => {
          if f.body.length() == 0 {
            parse_error(ExpectedFunctionBody, f.location)
          }
          Fn(
            location=sp(f.location.start, f.end_position),
            end_of_head_byte_index=f.location.end,
            kind=Anonymous(head=f.location),
            arguments=f.arguments,
            body=f.body,
            return_annotation=f.return_annotation,
          )
        }
        _ => self.next_token_unexpected(["An opening parenthesis."])
      }
    }
    Some({ kind: LeftBrace, span, .. }) => {
      self.advance()
      self.parse_block(span.start)
    }
    Some({ kind: Keyword(Case), span, .. }) => {
      self.advance()
      let subjects = self.series_of(p => p.parse_expression(), Some(Comma))
      if self.maybe_one(LeftBrace) is Some(_) {
        let clauses = self.series_of(p => p.parse_case_clause(), None)
        let (_, end) = self.expect_one_following_series(
          RightBrace,
          "a case clause",
        )
        if subjects.length() == 0 {
          parse_error(ExpectedExpr, sp(span.start, span.end))
        }
        Case(
          location=sp(span.start, end),
          subjects=subjects[:],
          clauses=Some(clauses[:]),
        )
      } else {
        let end = match subjects.last() {
          Some(subject) => subject.location().end
          None => span.end
        }
        Case(location=sp(span.start, end), subjects=subjects[:], clauses=None)
      }
    }
    // Helpful errors
    Some({ kind: Keyword(If), span, .. }) => parse_error(IfExpression, span)
    Some({ kind: LeftParen, span, .. }) =>
      parse_error(ExprLparStart, sp(span.start, span.start))
    Some({ kind: Bang, span, .. }) => {
      self.advance()
      match self.parse_expression_unit(Other) {
        Some(value) =>
          NegateBool(location=sp(span.start, value.location().end), value~)
        None => parse_error(ExpectedExpr, sp(span.start, span.start))
      }
    }
    Some({ kind: Minus, span, .. }) => {
      self.advance()
      match self.parse_expression_unit(Other) {
        Some(value) =>
          NegateInt(location=sp(span.start, value.location().end), value~)
        None => parse_error(ExpectedExpr, sp(span.start, span.start))
      }
    }
    _ => return None
  }
  // Field accesses and calls stack up.
  while true {
    match self.maybe_one(Dot) {
      Some((dot_start, _)) => {
        let start = expr.location().start
        match self.token0 {
          // Tuple access
          Some({ kind: Int(value~, ..), span, .. }) => {
            self.advance()
            match parse_u64(value.replace(old="_", new="")) {
              Some(index) =>
                expr = TupleIndex(
                  location=sp(start, span.end),
                  index~,
                  tuple=expr,
                )
              None => parse_error(InvalidTupleAccess, sp(start, span.end))
            }
          }
          Some({ kind: Name(label) | UpName(label), span, .. }) => {
            self.advance()
            expr = FieldAccess(
              location=sp(start, span.end),
              label_location=span,
              label~,
              container=expr,
            )
          }
          // A field access with no label.
          _ => {
            let end = dot_start + 1
            return Some(
              FieldAccess(
                location=sp(start, end),
                label_location=sp(dot_start, end),
                label="",
                container=expr,
              ),
            )
          }
        }
      }
      None =>
        match self.maybe_one(LeftParen) {
          Some((left_paren, _)) => {
            let start = expr.location().start
            match self.maybe_one(DotDot) {
              Some((dot_s, _)) => {
                // Record update
                let base = self.expect_expression()
                let record : @ast.RecordBeingUpdated[@ast.Expr] = {
                  location: sp(dot_s, base.location().end),
                  base,
                }
                let arguments = if self.maybe_one(Comma) is Some(_) {
                  self.series_of(
                    p => p.parse_record_update_argument(),
                    Some(Comma),
                  )
                } else {
                  []
                }
                let (_, end) = self.expect_one(RightParen)
                expr = RecordUpdate(
                  location=sp(start, end),
                  spread_start=dot_s,
                  constructor_=expr,
                  record~,
                  arguments=arguments[:],
                )
              }
              None => {
                // Call
                let arguments = self.parse_fn_arguments()
                if self.maybe_one(DotDot) is Some((dot_dot_start, dot_dot_end)) {
                  let value = try self.parse_const_value() catch {
                    _ => None
                  } noraise {
                    v => v
                  }
                  match value {
                    None =>
                      parse_error(
                        UnexpectedToken(
                          token=DotDot,
                          expected=[token_text(RightParen)][:],
                          hint=None,
                        ),
                        sp(dot_dot_start, dot_dot_end),
                      )
                    Some(value) => {
                      let update_location = sp(
                        dot_dot_start,
                        value.location().end,
                      )
                      let _ = self.expect_one(RightParen)
                      parse_error(RecordUpdateAfterFields, update_location)
                    }
                  }
                }
                let (_, end) = self.expect_one(RightParen)
                expr = make_call(expr, arguments, start, end, left_paren)
              }
            }
          }
          None => break
        }
    }
  }
  Some(expr)
}

///|
/// `[` elements `]` with an optional `..tail` and the compiler's errors for
/// malformed lists. `start` is the `[` position.
fn Parser::parse_list(
  self : Parser,
  start : Int32,
) -> @ast.Expr raise ParseError {
  let (elements, elements_end_with_comma) = self.series_of_has_trailing_separator(
    p => p.parse_expression(),
    Some(Comma),
  )
  let mut tail : @ast.Expr? = None
  let mut elements_after_tail : Array[@ast.Expr]? = None
  let mut dot_dot_location : (Int32, Int32)? = None
  if self.maybe_one(DotDot) is Some((dots_start, dots_end)) {
    dot_dot_location = Some((dots_start, dots_end))
    tail = self.parse_expression()
    if self.maybe_one(Comma) is Some(_) {
      // `[..wibble, wobble, wabble]`
      try self.series_of(p => p.parse_expression(), Some(Comma)) catch {
        _ => ()
      } noraise {
        after => elements_after_tail = Some(after)
      }
    }
    if tail is Some(_) {
      if !elements_end_with_comma {
        self.warn(DeprecatedListPrepend(location=sp(dots_start, dots_end)))
      }
      // `[..wibble, ..wabble, woo]`
      if self.maybe_one(DotDot) is Some((second_start, second_end)) {
        let _ = try self.parse_expression() catch {
          _ => None
        } noraise {
          v => v
        }
        if elements_after_tail is None || elements_after_tail is Some([]) {
          parse_error(
            ListSpreadWithAnotherSpread(
              first_spread_location=sp(dots_start, dots_end),
            ),
            sp(second_start, second_end),
          )
        }
      }
    }
  }
  let (_, end) = self.expect_one(RightSquare)
  if dot_dot_location is Some((s, e)) && tail is None {
    parse_error(ListSpreadWithoutTail, sp(s, e))
  }
  if tail is Some(_) &&
    elements.length() == 0 &&
    (elements_after_tail is None || elements_after_tail is Some([])) {
    parse_error(ListSpreadWithoutElements, sp(start, end))
  }
  if elements_after_tail is Some(after) && after.length() > 0 {
    let (s, e) = match (dot_dot_location, tail) {
      (Some((s, _)), Some(t)) => (s, t.location().end)
      _ => (start, end)
    }
    parse_error(ListSpreadFollowedByElements, sp(s, e))
  }
  List(location=sp(start, end), elements=elements[:], tail~)
}

///|
fn add_comment_style_hint(e : ParseError) -> ParseError {
  match e {
    ParseError(error=UnexpectedToken(token~, expected~, ..), location~) =>
      ParseError(
        error=UnexpectedToken(
          token~,
          expected~,
          hint=Some(
            "Maybe you meant to create a comment?\nComments in Gleam start with `//`, not `#`",
          ),
        ),
        location~,
      )
    other => other
  }
}

///|
fn add_multi_line_clause_hint(e : ParseError) -> ParseError {
  match e {
    ParseError(error=UnexpectedToken(token~, expected~, ..), location~) =>
      ParseError(
        error=UnexpectedToken(
          token~,
          expected~,
          hint=Some("Did you mean to wrap a multi line clause in curly braces?"),
        ),
        location~,
      )
    other => other
  }
}

///|
fn add_anonymous_fn_hint(e : ParseError) -> ParseError {
  match e {
    ParseError(error=UnexpectedToken(token=Name(n), expected~, ..), location~) =>
      ParseError(
        error=UnexpectedToken(
          token=Name(n),
          expected~,
          hint=Some("Only module-level functions can be named."),
        ),
        location~,
      )
    other => other
  }
}

///|
/// `as ` after `todo`, `panic`, `echo`, `assert`.
fn Parser::maybe_parse_as_message(self : Parser) -> @ast.Expr? raise ParseError {
  if self.maybe_one(Keyword(As)) is Some(_) {
    Some(self.expect_expression_unit(Other))
  } else {
    None
  }
}

///|
/// `a: expr` or `a:` (shorthand) in a record update.
fn Parser::parse_record_update_argument(
  self : Parser,
) -> @ast.RecordUpdateArg[@ast.Expr]? raise ParseError {
  match self.maybe_name() {
    Some((start, label, _)) => {
      let (_, end) = self.expect_one(Colon)
      match self.parse_expression() {
        Some(value) =>
          Some({ label, location: sp(start, value.location().end), value, })
        None =>
          Some({
            label,
            location: sp(start, end),
            value: Var(name=label, location=sp(start, end)),
          })
      }
    }
    None => None
  }
}

///|
/// `pattern (, pattern)* (| patterns)* (if guard)? -> expr`.
fn Parser::parse_case_clause(self : Parser) -> @ast.Clause? raise ParseError {
  let patterns = self.parse_patterns()
  guard patterns.length() > 0 else { return None }
  let lead = patterns[0]
  let alternative_patterns : Array[ArrayView[@ast.Pattern]] = []
  while self.maybe_one(Vbar) is Some((vbar_start, vbar_end)) {
    let alternatives = self.parse_patterns()
    if alternatives.length() == 0 {
      parse_error(ExpectedPattern, sp(vbar_start, vbar_end))
    }
    alternative_patterns.push(alternatives[:])
  }
  let guard_ = self.parse_case_clause_guard()
  let (arr_s, arr_e) = try self.expect_one(RArrow) catch {
    e => raise add_multi_line_clause_hint(e)
  } noraise {
    v => v
  }
  match self.parse_expression() {
    Some(then) =>
      Some({
        location: sp(lead.location().start, then.location().end),
        pattern: patterns[:],
        alternative_patterns: alternative_patterns[:],
        guard_,
        then,
      })
    None =>
      match self.token0 {
        Some({ kind: DiscardName(_), span, .. }) =>
          parse_error(IncorrectName(Variable), span)
        _ => parse_error(ExpectedExpr, sp(arr_s, arr_e))
      }
  }
}

///|
fn Parser::expect_expression(self : Parser) -> @ast.Expr raise ParseError {
  match self.parse_expression() {
    Some(e) => e
    None => self.next_token_unexpected(["An expression"])
  }
}

///|
fn Parser::expect_expression_unit(
  self : Parser,
  context : UnitContext,
) -> @ast.Expr raise ParseError {
  match self.parse_expression_unit(context) {
    Some(e) => e
    None => self.next_token_unexpected(["An expression"])
  }
}