//! Precedence tables for Fermented UnrealScript operators.
//!
//! These values don't follow the usual *binding power* convention for
//! a Pratt parser, where tighter binding corresponds to a larger number.\
//! Here, the smaller the number, the tighter the binding power.\
//! For this reason, we use the term *precedence rank* instead.
//!
//! ## Operators sorted by precedence (lowest number = tighter binding)
//!
//! ### Infix operators
//!
//! All infix operators in `UnrealScript` are
//! [left-associative](https://wiki.beyondunreal.com/Operators).
//!
//! 12: `**`
//! 16: `*`, `/`, `Cross`, `Dot`
//! 18: `%`
//! 20: `+`, `-`
//! 22: `<<`, `>>`, `>>>`
//! 24: `<`, `>`, `<=`, `>=`, `==`, `~=`, `ClockwiseFrom`
//! 26: `!=`
//! 28: `&`, `^`, `|`
//! 30: `&&`, `^^`
//! 32: `||`
//! 34: `*=`, `/=`, `+=`, `-=`
//! 40: `$`, `*`, `@`
//! 44: `$=`, `*=`, `@=`
//! 45: `-=`
//!
//! Some operator, such as `*`, appear twice with different precedence
//! ranks because they were defined with different values for different types
//! in separate script source files (as in the Killing Floor sources).\
//! However, `UnrealScript` uses only the first definition it encounters in
//! `Object.uc`, which corresponds to the lower value.
//!
//! ### Prefix operators
//!
//! `!`, `~`, `+`, `-`, `++`, `--`.
//!
//! ### Postfix operators
//!
//! `++`, `--`.

use crate::ast::{InfixOperator, infix_operator_info};
use crate::lexer::Token;

/// Compact precedence rank used by the Pratt Parser.
///
/// A smaller number means tighter binding, and a larger number means looser
/// binding. This inverted scale matches how `UnrealScript` tables were recorded.
#[must_use]
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct PrecedenceRank(u8);

impl PrecedenceRank {
    /// The loosest possible precedence rank.
    ///
    /// In this inverted scale (smaller number = tighter binding),
    /// this is represented by the maximum [`u8`] value.
    pub const LOOSEST: Self = Self(u8::MAX);

    /// The tightest possible precedence rank.
    ///
    /// In this inverted scale (smaller number = tighter binding),
    /// this is represented by zero.
    pub const TIGHTEST: Self = Self(0);

    /// Returns `true` if `self` has a looser binding than `other`.
    pub const fn is_looser_than(self, other: Self) -> bool {
        self.0 > other.0
    }
}

/// Maps a token to its infix operator along with its left and right binding
/// ranks: `(left_precedence_rank, operator, right_precedence_rank)`.
///
/// Returns [`None`] if and only if `token` is not an infix operator.
pub fn infix_precedence_ranks(
    token: Token,
) -> Option<(PrecedenceRank, InfixOperator, PrecedenceRank)> {
    let info = infix_operator_info(token)?;
    // All operators are left-associative, so `right_precedence_rank` is set to
    // `left_binding_rank - 1` (with our "smaller is tighter" scale, this
    // enforces left associativity in Pratt parsing).
    //
    // Since all precedences are even, subtracting one won't actually cross
    // any boundary between operator groups.
    Some((
        PrecedenceRank(info.right_precedence_rank),
        info.operator,
        PrecedenceRank(info.right_precedence_rank - 1),
    ))
}
