use php_lexer::TokenKind;
#[inline(always)]
pub fn infix_binding_power(kind: TokenKind) -> Option<(u8, u8)> {
const BP_TABLE: [Option<(u8, u8)>; 256] = build_bp_table();
BP_TABLE[kind as u8 as usize]
}
const fn build_bp_table() -> [Option<(u8, u8)>; 256] {
let mut table = [None; 256];
table[TokenKind::Or as u8 as usize] = Some((1, 2));
table[TokenKind::Xor as u8 as usize] = Some((3, 4));
table[TokenKind::And as u8 as usize] = Some((5, 6));
table[TokenKind::PipePipe as u8 as usize] = Some((15, 16));
table[TokenKind::AmpersandAmpersand as u8 as usize] = Some((17, 18));
table[TokenKind::Pipe as u8 as usize] = Some((19, 20));
table[TokenKind::Caret as u8 as usize] = Some((21, 22));
table[TokenKind::Ampersand as u8 as usize] = Some((23, 24));
table[TokenKind::EqualsEquals as u8 as usize] = Some((25, 26));
table[TokenKind::BangEquals as u8 as usize] = Some((25, 26));
table[TokenKind::EqualsEqualsEquals as u8 as usize] = Some((25, 26));
table[TokenKind::BangEqualsEquals as u8 as usize] = Some((25, 26));
table[TokenKind::Spaceship as u8 as usize] = Some((25, 26));
table[TokenKind::LessThan as u8 as usize] = Some((27, 28));
table[TokenKind::GreaterThan as u8 as usize] = Some((27, 28));
table[TokenKind::LessThanEquals as u8 as usize] = Some((27, 28));
table[TokenKind::GreaterThanEquals as u8 as usize] = Some((27, 28));
table[TokenKind::PipeArrow as u8 as usize] = Some((29, 30));
table[TokenKind::ShiftLeft as u8 as usize] = Some((31, 32));
table[TokenKind::ShiftRight as u8 as usize] = Some((31, 32));
table[TokenKind::Dot as u8 as usize] = Some((35, 36));
table[TokenKind::Plus as u8 as usize] = Some((35, 36));
table[TokenKind::Minus as u8 as usize] = Some((35, 36));
table[TokenKind::Star as u8 as usize] = Some((37, 38));
table[TokenKind::Slash as u8 as usize] = Some((37, 38));
table[TokenKind::Percent as u8 as usize] = Some((37, 38));
table[TokenKind::Instanceof as u8 as usize] = Some((45, 46));
table[TokenKind::StarStar as u8 as usize] = Some((60, 59));
table
}
#[inline(always)]
pub fn prefix_binding_power(kind: TokenKind) -> Option<u8> {
match kind {
TokenKind::Minus | TokenKind::Plus => Some(41),
TokenKind::Bang => Some(41),
TokenKind::Tilde => Some(41),
TokenKind::PlusPlus | TokenKind::MinusMinus => Some(41),
_ => None,
}
}
#[inline(always)]
pub fn postfix_binding_power(kind: TokenKind) -> Option<u8> {
match kind {
TokenKind::PlusPlus | TokenKind::MinusMinus => Some(43),
_ => None,
}
}
pub const ASSIGNMENT_BP: u8 = 8;
pub const TERNARY_BP: u8 = 10;
pub const NULL_COALESCE_LEFT_BP: u8 = 14;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_additive_lower_than_multiplicative() {
let (_, add_right) = infix_binding_power(TokenKind::Plus).unwrap();
let (mul_left, _) = infix_binding_power(TokenKind::Star).unwrap();
assert!(mul_left > add_right);
}
#[test]
fn test_mul_lower_than_pow() {
let (_, mul_right) = infix_binding_power(TokenKind::Star).unwrap();
let (pow_left, _) = infix_binding_power(TokenKind::StarStar).unwrap();
assert!(pow_left > mul_right);
}
#[test]
fn test_pow_is_right_associative() {
let (left, right) = infix_binding_power(TokenKind::StarStar).unwrap();
assert!(left > right);
}
#[test]
fn test_add_is_left_associative() {
let (left, right) = infix_binding_power(TokenKind::Plus).unwrap();
assert!(left < right);
}
#[test]
fn test_boolean_and_lower_than_bitwise_or() {
let (_, and_right) = infix_binding_power(TokenKind::AmpersandAmpersand).unwrap();
let (bitor_left, _) = infix_binding_power(TokenKind::Pipe).unwrap();
assert!(bitor_left > and_right);
}
#[test]
fn test_null_coalesce_not_in_table() {
assert!(
infix_binding_power(TokenKind::QuestionQuestion).is_none(),
"`??` must not appear in the infix table — it has a dedicated special-case handler"
);
const { assert!(NULL_COALESCE_LEFT_BP > TERNARY_BP + 1) }
const { assert!(NULL_COALESCE_LEFT_BP > TERNARY_BP) }
}
#[test]
fn test_comparison_lower_than_concat() {
let (_, cmp_right) = infix_binding_power(TokenKind::LessThan).unwrap();
let (concat_left, _) = infix_binding_power(TokenKind::Dot).unwrap();
assert!(concat_left > cmp_right);
}
#[test]
fn test_concat_same_level_as_additive() {
let (dot_left, dot_right) = infix_binding_power(TokenKind::Dot).unwrap();
let (plus_left, plus_right) = infix_binding_power(TokenKind::Plus).unwrap();
let (minus_left, minus_right) = infix_binding_power(TokenKind::Minus).unwrap();
assert_eq!((dot_left, dot_right), (plus_left, plus_right));
assert_eq!((dot_left, dot_right), (minus_left, minus_right));
}
#[test]
fn test_shift_lower_than_concat_and_additive() {
let (_, shift_right) = infix_binding_power(TokenKind::ShiftLeft).unwrap();
let (concat_left, _) = infix_binding_power(TokenKind::Dot).unwrap();
let (plus_left, _) = infix_binding_power(TokenKind::Plus).unwrap();
assert!(concat_left > shift_right);
assert!(plus_left > shift_right);
}
#[test]
fn test_instanceof_higher_than_additive() {
let (_, add_right) = infix_binding_power(TokenKind::Plus).unwrap();
let (inst_left, _) = infix_binding_power(TokenKind::Instanceof).unwrap();
assert!(inst_left > add_right);
}
#[test]
fn test_instanceof_higher_than_prefix_unary() {
let prefix_right_bp = prefix_binding_power(TokenKind::Minus).unwrap();
let (inst_left, _) = infix_binding_power(TokenKind::Instanceof).unwrap();
assert!(inst_left > prefix_right_bp);
}
#[test]
fn test_pow_higher_than_instanceof() {
let (_, pow_right) = infix_binding_power(TokenKind::StarStar).unwrap();
let (inst_left, _) = infix_binding_power(TokenKind::Instanceof).unwrap();
assert!(pow_right > inst_left);
}
#[test]
fn test_instanceof_higher_than_comparison() {
let (_, cmp_right) = infix_binding_power(TokenKind::LessThan).unwrap();
let (inst_left, _) = infix_binding_power(TokenKind::Instanceof).unwrap();
assert!(inst_left > cmp_right);
}
}