rott/rottlib/src/parser/grammar/expression/precedence.rs
dkanus 588790b9b4 Refactor everything
Huge dump of refactored code. Still in the middle of the changes that
are to be squashed later in a one huge monster commit, because there is
no value in anything atomic here.
2026-04-05 20:32:11 +07:00

94 lines
3.2 KiB
Rust

//! 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),
))
}