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.
94 lines
3.2 KiB
Rust
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),
|
|
))
|
|
}
|