///|
/// An operator waiting on the operator stack of the simple precedence
/// parser (`handle_operator` in the compiler).
priv struct OpEntry {
  token : @scanner.Token
  precedence : Int32
}

///|
/// The simple precedence parser's step: `None` as `next` means the input
/// ended. All operators are left associative. Returns the fully reduced
/// expression at the end, `None` while operators remain or when the
/// expression stack is empty.
fn[A] handle_operator(
  next : OpEntry?,
  opstack : Array[OpEntry],
  estack : Array[A],
  reduce : (@scanner.Token, Array[A]) -> Unit,
) -> A? {
  let mut next = next
  while true {
    match (opstack.pop(), next) {
      (None, None) => return estack.pop()
      (None, Some(operator)) => {
        opstack.push(operator)
        break
      }
      (Some(left), None) => reduce(left.token, estack)
      (Some(left), Some(right)) =>
        if left.precedence >= right.precedence {
          reduce(left.token, estack)
          next = Some(right)
        } else {
          opstack.push(left)
          opstack.push(right)
          break
        }
    }
  }
  None
}

///|
/// The expression precedence of a token: `|>` is 6; a binary operator has
/// its `BinOp` precedence; anything else is not an operator.
fn precedence(kind : @scanner.TokenKind) -> Int32? {
  if kind is Pipe {
    return Some(6)
  }
  token_to_binop(kind).map(binop_precedence)
}

///|
/// The compiler's `BinOp::precedence`.
fn binop_precedence(op : @ast.BinOp) -> Int32 {
  match op {
    Or => 1
    And => 2
    Eq | NotEq => 3
    LtInt
    | LtEqInt
    | LtFloat
    | LtEqFloat
    | GtEqInt
    | GtInt
    | GtEqFloat
    | GtFloat => 4
    Concatenate => 5
    AddInt | AddFloat | SubInt | SubFloat => 7
    MultInt | MultFloat | DivInt | DivFloat | RemainderInt => 8
  }
}

///|
/// The clause-guard precedence of a token (`Token::guard_precedence`): no
/// pipe, and `+ -` 6, `* / %` 7.
fn guard_precedence(kind : @scanner.TokenKind) -> Int32? {
  match kind {
    VbarVbar => Some(1)
    AmperAmper => Some(2)
    EqualEqual | NotEqual => Some(3)
    Less
    | LessEqual
    | LessDot
    | LessEqualDot
    | GreaterEqual
    | Greater
    | GreaterEqualDot
    | GreaterDot => Some(4)
    Concatenate => Some(5)
    Plus | PlusDot | Minus | MinusDot => Some(6)
    Star | StarDot | Slash | SlashDot | Percent => Some(7)
    _ => None
  }
}

///|
fn token_to_binop(kind : @scanner.TokenKind) -> @ast.BinOp? {
  match kind {
    VbarVbar => Some(Or)
    AmperAmper => Some(And)
    EqualEqual => Some(Eq)
    NotEqual => Some(NotEq)
    Less => Some(LtInt)
    LessEqual => Some(LtEqInt)
    Greater => Some(GtInt)
    GreaterEqual => Some(GtEqInt)
    LessDot => Some(LtFloat)
    LessEqualDot => Some(LtEqFloat)
    GreaterDot => Some(GtFloat)
    GreaterEqualDot => Some(GtEqFloat)
    Plus => Some(AddInt)
    Minus => Some(SubInt)
    PlusDot => Some(AddFloat)
    MinusDot => Some(SubFloat)
    Percent => Some(RemainderInt)
    Star => Some(MultInt)
    StarDot => Some(MultFloat)
    Slash => Some(DivInt)
    SlashDot => Some(DivFloat)
    Concatenate => Some(Concatenate)
    _ => None
  }
}

///|
fn token_to_int_operator(kind : @scanner.TokenKind) -> @ast.IntOperator? {
  match kind {
    Plus => Some(Add)
    Minus => Some(Subtract)
    Star => Some(Multiply)
    Slash => Some(Divide)
    Percent => Some(Remainder)
    _ => None
  }
}

///|
fn int_operator_precedence(op : @ast.IntOperator) -> Int32 {
  match op {
    Add | Subtract => 7
    Multiply | Divide | Remainder => 8
  }
}

///|
/// Reduce two expressions with `operator`: a pipe extends a `Pipeline`
/// (or starts one), any other operator is a `BinOp`.
fn reduce_expression(
  operator : @scanner.Token,
  estack : Array[@ast.Expr],
) -> Unit {
  guard estack.pop() is Some(right) && estack.pop() is Some(left) else {
    return
  }
  if operator.kind is Pipe {
    let expressions : Array[@ast.Expr] = match left {
      Pipeline(expressions~) => expressions.to_owned()
      other => [other]
    }
    expressions.push(right)
    estack.push(Pipeline(expressions=expressions[:]))
  } else if token_to_binop(operator.kind) is Some(op) {
    estack.push(
      BinOp(
        location=sp(left.location().start, right.location().end),
        operator=op,
        operator_start=operator.span.start,
        left~,
        right~,
      ),
    )
  }
}

///|
fn reduce_clause_guard(
  operator : @scanner.Token,
  estack : Array[@ast.ClauseGuard],
) -> Unit {
  guard estack.pop() is Some(right) && estack.pop() is Some(left) else {
    return
  }
  guard token_to_binop(operator.kind) is Some(op) else { return }
  estack.push(
    BinaryOperator(
      location=sp(left.location().start, right.location().end),
      operator=op,
      operator_start=operator.span.start,
      left~,
      right~,
    ),
  )
}

///|
fn reduce_constant(
  operator : @scanner.Token,
  estack : Array[@ast.Constant],
) -> Unit {
  guard estack.pop() is Some(right) && estack.pop() is Some(left) else {
    return
  }
  guard token_to_binop(operator.kind) is Some(op) else { return }
  estack.push(
    BinaryOperator(
      location=sp(left.location().start, right.location().end),
      operator_start=operator.span.start,
      operator=op,
      left~,
      right~,
    ),
  )
}

///|
fn reduce_bit_array_size(
  operator : @scanner.Token,
  estack : Array[@ast.BitArraySize],
) -> Unit {
  guard estack.pop() is Some(right) && estack.pop() is Some(left) else {
    return
  }
  guard token_to_int_operator(operator.kind) is Some(op) else { return }
  estack.push(
    BinaryOperator(
      location=sp(left.location().start, right.location().end),
      operator=op,
      left~,
      right~,
    ),
  )
}