#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum PsOp {
Abs,
Add,
And,
Atan,
Bitshift,
Ceiling,
Copy,
Cos,
Cvi,
Cvr,
Div,
Dup,
Eq,
Exch,
Exp,
False,
Floor,
Ge,
Gt,
Idiv,
If,
IfElse,
Index,
Le,
Ln,
Log,
Lt,
Mod,
Mul,
Ne,
Neg,
Not,
Or,
Pop,
Roll,
Round,
Sin,
Sqrt,
Sub,
True,
Truncate,
Xor,
}
const NAMES: [(&[u8], PsOp); 42] = [
(b"abs", PsOp::Abs),
(b"add", PsOp::Add),
(b"and", PsOp::And),
(b"atan", PsOp::Atan),
(b"bitshift", PsOp::Bitshift),
(b"ceiling", PsOp::Ceiling),
(b"copy", PsOp::Copy),
(b"cos", PsOp::Cos),
(b"cvi", PsOp::Cvi),
(b"cvr", PsOp::Cvr),
(b"div", PsOp::Div),
(b"dup", PsOp::Dup),
(b"eq", PsOp::Eq),
(b"exch", PsOp::Exch),
(b"exp", PsOp::Exp),
(b"false", PsOp::False),
(b"floor", PsOp::Floor),
(b"ge", PsOp::Ge),
(b"gt", PsOp::Gt),
(b"idiv", PsOp::Idiv),
(b"if", PsOp::If),
(b"ifelse", PsOp::IfElse),
(b"index", PsOp::Index),
(b"le", PsOp::Le),
(b"ln", PsOp::Ln),
(b"log", PsOp::Log),
(b"lt", PsOp::Lt),
(b"mod", PsOp::Mod),
(b"mul", PsOp::Mul),
(b"ne", PsOp::Ne),
(b"neg", PsOp::Neg),
(b"not", PsOp::Not),
(b"or", PsOp::Or),
(b"pop", PsOp::Pop),
(b"roll", PsOp::Roll),
(b"round", PsOp::Round),
(b"sin", PsOp::Sin),
(b"sqrt", PsOp::Sqrt),
(b"sub", PsOp::Sub),
(b"true", PsOp::True),
(b"truncate", PsOp::Truncate),
(b"xor", PsOp::Xor),
];
impl PsOp {
#[must_use]
pub fn from_name(word: &[u8]) -> Option<Self> {
NAMES
.binary_search_by(|(name, _)| (*name).cmp(word))
.ok()
.and_then(|i| NAMES.get(i))
.map(|(_, op)| *op)
}
}
#[cfg(test)]
mod tests {
#![allow(
clippy::unreadable_literal,
clippy::float_cmp,
clippy::indexing_slicing,
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
reason = "test fixtures quote oracle vectors verbatim and compare exactly"
)]
use super::{NAMES, PsOp};
#[test]
fn the_table_is_sorted_so_the_search_works() {
for pair in NAMES.windows(2) {
let (Some(a), Some(b)) = (pair.first(), pair.get(1)) else {
continue;
};
assert!(a.0 < b.0, "{:?} must sort before {:?}", a.0, b.0);
}
assert_eq!(NAMES.len(), 42);
}
#[test]
fn every_spelling_resolves_to_its_operator() {
for &(name, op) in &NAMES {
assert_eq!(PsOp::from_name(name), Some(op), "for {name:?}");
}
}
#[test]
fn unknown_tokens_name_no_operator() {
for word in [&b"invalid"[..], b"", b"Add", b"addx", b"55"] {
assert!(PsOp::from_name(word).is_none(), "{word:?} should not match");
}
}
}