///|
/// The left side of `=` or `->`. `None` when `token0` does not start a
/// pattern.
fn Parser::parse_pattern(self : Parser) -> @ast.Pattern? raise ParseError {
  self.nested(op_pattern, () => self.parse_pattern_inner())
}

///|
fn Parser::parse_pattern_inner(self : Parser) -> @ast.Pattern? raise ParseError {
  let pattern : @ast.Pattern = match self.token0 {
    // Variable, or a module-qualified constructor
    Some({ kind: Name(name), span, .. }) => {
      self.advance()
      // A variable is not permitted on the left hand side of a `<>`
      if self.token0 is Some({ kind: Concatenate, .. }) {
        parse_error(ConcatPatternVariableLeftHandSide, span)
      }
      if self.maybe_one(Dot) is Some(_) {
        // A better message than "expecting a type" for `name.thing`
        // (gleam-lang/gleam#2841).
        try
          self.expect_constructor_pattern(Some((span.start, name, span.end)))
        catch {
          ParseError(location~, ..) =>
            parse_error(InvalidModuleTypePattern, sp(span.start, location.end))
        } noraise {
          result => result
        }
      } else {
        Variable(location=span, name~)
      }
    }
    // Constructor
    Some({ kind: UpName(_), .. }) => self.expect_constructor_pattern(None)
    Some({ kind: DiscardName(name), span, .. }) => {
      self.advance()
      if self.token0 is Some({ kind: Concatenate, .. }) {
        parse_error(ConcatPatternVariableLeftHandSide, span)
      }
      Discard(location=span, name~)
    }
    Some({ kind: String(value), span, .. }) => {
      self.advance()
      match self.token0 {
        // `"Hello, " as greeting <> name` or `"Hello, World!" as greeting`
        Some({ kind: Keyword(As), .. }) => {
          self.advance()
          let (name_start, name, name_end) = self.expect_name(AsPattern)
          match self.token0 {
            Some({ kind: Concatenate, .. }) => {
              self.advance()
              let (r_start, right, r_end) = self.expect_assign_name()
              self.reject_suffix_literal(right)
              StringPrefix(
                location=sp(span.start, r_end),
                left_location=sp(span.start, name_end),
                right_location=sp(r_start, r_end),
                left_side_string=value,
                left_side_assignment=Some({
                  name,
                  name_start_position: name_start,
                  location: sp(span.end, name_end),
                }),
                right_side_assignment=right,
              )
            }
            _ =>
              return Some(
                Assign(
                  name~,
                  location=sp(name_start, name_end),
                  pattern=String(location=span, value~),
                ),
              )
          }
        }
        // `"Hello, " <> name`
        Some({ kind: Concatenate, .. }) => {
          self.advance()
          let (r_start, right, r_end) = self.expect_assign_name()
          self.reject_suffix_literal(right)
          StringPrefix(
            location=sp(span.start, r_end),
            left_location=span,
            right_location=sp(r_start, r_end),
            left_side_string=value,
            left_side_assignment=None,
            right_side_assignment=right,
          )
        }
        _ => 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: Hash, span, .. }) => {
      self.advance()
      let _ = self.expect_one(LeftParen)
      let elements = self.series_of(p => p.parse_pattern(), Some(Comma))
      let (_, end) = self.expect_one_following_series(RightParen, "a pattern")
      Tuple(location=sp(span.start, end), elements=elements[:])
    }
    // Bit array
    Some({ kind: LtLt, span, .. }) => {
      self.advance()
      let segments = self.series_of(
        p => {
          p.parse_bit_array_segment(
            q => {
              match q.parse_pattern() {
                Some(BitArray(location~, ..)) =>
                  parse_error(NestedBitArrayPattern, location)
                x => x
              }
            },
            q => q.expect_bit_array_pattern_segment_argument(),
            bit_array_size_int,
            x => x.location(),
          )
        },
        Some(Comma),
      )
      let (_, end) = self.expect_one_following_series(
        GtGt,
        "a bit array segment pattern",
      )
      BitArray(location=sp(span.start, end), segments=segments[:])
    }
    // List
    Some({ kind: LeftSquare, span, .. }) => {
      self.advance()
      let start = span.start
      let (elements, elements_end_with_comma) = self.series_of_has_trailing_separator(
        p => p.parse_pattern(),
        Some(Comma),
      )
      let mut elements_after_tail : Array[@ast.Pattern]? = None
      let mut dot_dot_location : (Int32, Int32)? = None
      // `Some(Some(p))`: a tail with a pattern; `Some(None)`: `..` alone.
      let tail : @ast.Pattern?? = match self.token0 {
        Some({ kind: DotDot, span: dots, .. }) => {
          dot_dot_location = Some((dots.start, dots.end))
          if elements.length() > 0 && !elements_end_with_comma {
            self.warn(DeprecatedListPattern(location=dots))
          }
          self.advance()
          let tail = self.parse_pattern()
          if self.maybe_one(Comma) is Some(_) {
            // `[..wibble, wobble, wabble]`
            try self.series_of(p => p.parse_pattern(), Some(Comma)) catch {
              _ => ()
            } noraise {
              after => elements_after_tail = Some(after)
            }
          }
          Some(tail)
        }
        _ => None
      }
      let (end, closing_square_bracket_end) = self.expect_one_following_series(
        RightSquare,
        "a pattern",
      )
      if elements_after_tail is Some(after) && after.length() > 0 {
        let (s, e) = match (dot_dot_location, tail) {
          (Some((s, _)), Some(Some(t))) => (s, t.location().end)
          (Some((s, e)), Some(None)) => (s, e)
          _ => (start, end)
        }
        parse_error(ListPatternSpreadFollowedByElements, sp(s, e))
      }
      let tail : @ast.Pattern? = match tail {
        Some(Some(p)) if p is Variable(..) || p is Discard(..) => Some(p)
        Some(Some(p)) => parse_error(InvalidTailPattern, p.location())
        Some(None) =>
          Some(
            Discard(
              location=sp(
                closing_square_bracket_end - 1,
                closing_square_bracket_end,
              ),
              name="_",
            ),
          )
        None => None
      }
      if elements.length() == 0 {
        let location = sp(start, closing_square_bracket_end)
        match tail {
          Some(Discard(..)) =>
            self.warn(DeprecatedListCatchAllPattern(location~))
          Some(Variable(name~, ..)) =>
            self.warn(DeprecatedListSpreadAsVariablePattern(location~, name~))
          _ => ()
        }
      }
      List(
        location=sp(start, closing_square_bracket_end),
        elements=elements[:],
        tail=tail.map(pattern => {
          let dot_dot_start = match dot_dot_location {
            Some((s, _)) => s
            None => start
          }
          @ast.TailPattern::{
            location: sp(dot_dot_start, pattern.location().end),
            pattern,
          }
        }),
      )
    }
    _ => return None
  }
  match self.token0 {
    Some({ kind: Keyword(As), .. }) => {
      self.advance()
      let (start, name, end) = self.expect_name(AsPattern)
      Some(Assign(name~, location=sp(start, end), pattern~))
    }
    _ => Some(pattern)
  }
}

///|
/// A second `<>` after the right side of a string prefix would match on a
/// suffix literal: `ConcatPatternVariableWithSuffix` over it.
fn Parser::reject_suffix_literal(
  self : Parser,
  right : @ast.AssignName,
) -> Unit raise ParseError {
  if self.token0 is Some({ kind: Concatenate, span: second, .. }) {
    let suffix_end = match self.token1 {
      Some({ kind: String(_), span, .. }) => span.end
      _ => second.end
    }
    let name = match right {
      Variable(n) | Discard(n) => n
    }
    parse_error(
      ConcatPatternVariableWithSuffix(name~),
      sp(second.start, suffix_end),
    )
  }
}

///|
/// `UpName( args )`, with an optional module prefix already consumed.
fn Parser::expect_constructor_pattern(
  self : Parser,
  module_ : (Int32, String, Int32)?,
) -> @ast.Pattern raise ParseError {
  let (name_start, name, name_end) = self.expect_upname()
  let (arguments, spread, end) = self.parse_constructor_pattern_arguments(
    name_end,
  )
  let start = match module_ {
    Some((s, _, _)) => s
    None => name_start
  }
  Constructor(
    location=sp(start, end),
    name_location=sp(name_start, name_end),
    name~,
    arguments=arguments[:],
    module_=module_.map(m => @ast.SpannedString::{
      location: sp(m.0, m.2),
      name: m.1,
    }),
    spread~,
  )
}

///|
/// `( args )` of a constructor pattern: arguments, spread span and end.
fn Parser::parse_constructor_pattern_arguments(
  self : Parser,
  upname_end : Int32,
) -> (Array[@ast.CallArg[@ast.Pattern]], @span.SrcSpan?, Int32) raise ParseError {
  if self.maybe_one(LeftParen) is Some(_) {
    let (arguments, arguments_end_with_comma) = self.series_of_has_trailing_separator(
      p => p.parse_constructor_pattern_argument(),
      Some(Comma),
    )
    let spread = self.maybe_one(DotDot).map(s => sp(s.0, s.1))
    if spread is Some(spread_location) {
      let _ = self.maybe_one(Comma)
      if arguments.length() > 0 && !arguments_end_with_comma {
        self.warn(DeprecatedRecordSpreadPattern(location=spread_location))
      }
    }
    let (_, end) = self.expect_one(RightParen)
    (arguments, spread, end)
  } else {
    ([], None, upname_end)
  }
}

///|
/// `a: `, `a:` (label shorthand) or ``.
fn Parser::parse_constructor_pattern_argument(
  self : Parser,
) -> @ast.CallArg[@ast.Pattern]? raise ParseError {
  match (self.token0, self.token1) {
    (
      Some({ kind: Name(name), span, .. }),
      Some({ kind: Colon, span: colon, .. }),
    ) => {
      self.advance()
      self.advance()
      match self.parse_pattern() {
        Some(value) =>
          Some({
            implicit: None,
            location: sp(span.start, value.location().end),
            label: Some(name),
            value,
          })
        None =>
          Some({
            implicit: None,
            location: sp(span.start, colon.end),
            label: Some(name),
            value: Variable(name~, location=sp(span.start, colon.end)),
          })
      }
    }
    _ =>
      match self.parse_pattern() {
        Some(value) =>
          Some({
            implicit: None,
            location: value.location(),
            label: None,
            value,
          })
        None => None
      }
  }
}

///|
fn Parser::parse_patterns(
  self : Parser,
) -> Array[@ast.Pattern] raise ParseError {
  self.series_of(p => p.parse_pattern(), Some(Comma))
}