use alloc::collections::BTreeMap;
use alloc::string::String;
use core::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Arity {
min: u8,
max: u8,
}
impl Arity {
#[must_use]
pub const fn exact(n: u8) -> Self {
Arity { min: n, max: n }
}
#[must_use]
pub const fn range(min: u8, max: u8) -> Self {
Arity {
min,
max: if max < min { min } else { max },
}
}
#[must_use]
pub const fn at_least(n: u8) -> Self {
Arity::range(n, u8::MAX)
}
#[must_use]
pub const fn min(self) -> u8 {
self.min
}
#[must_use]
pub const fn max(self) -> u8 {
self.max
}
#[must_use]
pub const fn accepts(self, n: usize) -> bool {
n >= self.min as usize && n <= self.max as usize
}
}
impl fmt::Display for Arity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.min == self.max {
write!(f, "{}", self.min)
} else {
write!(f, "{} to {}", self.min, self.max)
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum LsbOp {
Concat,
Len,
Truthy,
Not,
TypeOf,
Iter,
IterNext,
Resume,
ResumeThrow,
CoroClose,
CoroKey,
CoroResult,
CoroStatus,
ErrCode,
ErrPayload,
}
impl LsbOp {
#[must_use]
pub const fn arity(self) -> Arity {
match self {
Self::Concat | Self::Resume | Self::ResumeThrow | Self::CoroClose => Arity::exact(2),
Self::Len
| Self::Truthy
| Self::Not
| Self::TypeOf
| Self::Iter
| Self::IterNext
| Self::CoroKey
| Self::CoroResult
| Self::CoroStatus
| Self::ErrCode
| Self::ErrPayload => Arity::exact(1),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Fast {
Concat,
ConcatN,
IntRem,
NumPow,
IntShl,
IntShr,
ScalarXor,
LooseEq,
LooseNe,
StrictEq,
StrictNe,
Lt,
Le,
Gt,
Ge,
IntSpaceship,
NumIdentity,
IntBitNot,
ToInt,
ToFloat,
ToStr,
ToBool,
ToArray,
}
impl Fast {
#[must_use]
pub const fn arity(self) -> Arity {
match self {
Self::ConcatN => Arity::at_least(0),
Self::NumIdentity
| Self::IntBitNot
| Self::ToInt
| Self::ToFloat
| Self::ToStr
| Self::ToBool
| Self::ToArray => Arity::exact(1),
_ => Arity::exact(2),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Lowering {
Lsb(LsbOp),
Host {
module: String,
name: String,
},
Fast {
fast: Fast,
module: String,
name: String,
},
}
impl Lowering {
#[must_use]
pub fn host(&self) -> Option<(&str, &str)> {
match self {
Self::Lsb(_) => None,
Self::Host { module, name } | Self::Fast { module, name, .. } => {
Some((module.as_str(), name.as_str()))
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct IntrinsicDef {
arity: Arity,
lowering: Lowering,
}
impl IntrinsicDef {
#[must_use]
pub fn arity(&self) -> Arity {
self.arity
}
#[must_use]
pub fn lowering(&self) -> &Lowering {
&self.lowering
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Intrinsics {
table: BTreeMap<String, IntrinsicDef>,
}
impl Intrinsics {
#[must_use]
pub fn new() -> Self {
Intrinsics::default()
}
#[must_use]
pub fn core() -> Self {
Intrinsics::new()
.with_lsb("concat", LsbOp::Concat)
.with_lsb("len", LsbOp::Len)
.with_lsb("truthy", LsbOp::Truthy)
.with_lsb("lnot", LsbOp::Not)
.with_lsb("type_of", LsbOp::TypeOf)
.with_lsb("iter", LsbOp::Iter)
.with_lsb("iter_next", LsbOp::IterNext)
.with_lsb("resume", LsbOp::Resume)
.with_lsb("resume_throw", LsbOp::ResumeThrow)
.with_lsb("coro_close", LsbOp::CoroClose)
.with_lsb("coro_key", LsbOp::CoroKey)
.with_lsb("coro_result", LsbOp::CoroResult)
.with_lsb("coro_status", LsbOp::CoroStatus)
.with_lsb("err_code", LsbOp::ErrCode)
.with_lsb("err_payload", LsbOp::ErrPayload)
}
#[must_use]
pub fn mox() -> Self {
const FAST: [(&str, Fast); 23] = [
("concat", Fast::Concat),
("concat_n", Fast::ConcatN),
("mod", Fast::IntRem),
("pow", Fast::NumPow),
("shl", Fast::IntShl),
("shr", Fast::IntShr),
("xor", Fast::ScalarXor),
("loose_eq", Fast::LooseEq),
("loose_ne", Fast::LooseNe),
("identical", Fast::StrictEq),
("not_identical", Fast::StrictNe),
("lt", Fast::Lt),
("le", Fast::Le),
("gt", Fast::Gt),
("ge", Fast::Ge),
("spaceship", Fast::IntSpaceship),
("to_number", Fast::NumIdentity),
("bit_not", Fast::IntBitNot),
("cast_int", Fast::ToInt),
("cast_float", Fast::ToFloat),
("cast_string", Fast::ToStr),
("cast_bool", Fast::ToBool),
("cast_array", Fast::ToArray),
];
const HOST: [(&str, u8); 3] = [("instanceof", 2), ("cast_object", 1), ("clone", 1)];
let mut t = Intrinsics::core();
for (name, fast) in FAST {
let mut full = String::from("mox.");
full.push_str(name);
t = t.with_fast(&full, fast, "mox", name);
}
for (name, n) in HOST {
let mut full = String::from("mox.");
full.push_str(name);
t = t.with_host(&full, Arity::exact(n), "mox", name);
}
t
}
#[must_use]
pub fn with_lsb(mut self, name: &str, op: LsbOp) -> Self {
let def = IntrinsicDef {
arity: op.arity(),
lowering: Lowering::Lsb(op),
};
let _previous = self.table.insert(String::from(name), def);
self
}
#[must_use]
pub fn with_host(mut self, name: &str, arity: Arity, module: &str, func: &str) -> Self {
let def = IntrinsicDef {
arity,
lowering: Lowering::Host {
module: String::from(module),
name: String::from(func),
},
};
let _previous = self.table.insert(String::from(name), def);
self
}
#[must_use]
pub fn with_fast(mut self, name: &str, fast: Fast, module: &str, func: &str) -> Self {
let def = IntrinsicDef {
arity: fast.arity(),
lowering: Lowering::Fast {
fast,
module: String::from(module),
name: String::from(func),
},
};
let _previous = self.table.insert(String::from(name), def);
self
}
#[must_use]
pub fn get(&self, name: &str) -> Option<&IntrinsicDef> {
self.table.get(name)
}
#[must_use]
pub fn len(&self) -> usize {
self.table.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.table.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &IntrinsicDef)> {
self.table.iter().map(|(n, d)| (n.as_str(), d))
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use super::{Arity, Fast, Intrinsics, Lowering};
#[test]
fn the_mox_preset_covers_every_sketch_intrinsic() {
let t = Intrinsics::mox();
for name in [
"concat",
"concat_n",
"mod",
"pow",
"shl",
"shr",
"xor",
"loose_eq",
"loose_ne",
"identical",
"not_identical",
"spaceship",
"lt",
"le",
"gt",
"ge",
"to_number",
"bit_not",
"instanceof",
"cast_int",
"cast_float",
"cast_string",
"cast_bool",
"cast_array",
"cast_object",
"clone",
] {
let full = alloc::format!("mox.{name}");
let def = t.get(&full).unwrap_or_else(|| panic!("{full} missing"));
assert_eq!(def.lowering().host(), Some(("mox", name)));
}
}
#[test]
fn later_entries_replace_earlier_ones() {
let t = Intrinsics::new()
.with_host("x", Arity::exact(1), "a", "b")
.with_fast("x", Fast::Concat, "c", "d");
assert_eq!(t.len(), 1);
assert!(matches!(
t.get("x").map(super::IntrinsicDef::lowering),
Some(Lowering::Fast { .. })
));
assert_eq!(
t.get("x").map(super::IntrinsicDef::arity),
Some(Arity::exact(2))
);
}
#[test]
fn arity_display_and_bounds() {
assert_eq!(Arity::exact(2).to_string(), "2");
assert_eq!(Arity::range(0, 3).to_string(), "0 to 3");
assert!(Arity::at_least(0).accepts(0));
assert!(!Arity::at_least(0).accepts(256));
}
}