use std::{
f64::consts::{PI, TAU},
fmt,
};
use enum_iterator::{Sequence, all};
use crate::{AsciiToken, NumericSubscript, Primitive, Signature, Subscript};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Sequence)]
#[allow(missing_docs)]
pub enum PrimClass {
Arguments,
Constant,
MonadicPervasive,
DyadicPervasive,
MonadicArray,
DyadicArray,
MappingModifier,
IteratingModifier,
InversionModifier,
OtherModifier,
Comptime,
Debug,
Thread,
Map,
Encoding,
Algorithm,
Misc,
Rng,
Time,
Environment,
Sys(SysOpClass),
}
impl PrimClass {
pub fn all() -> impl Iterator<Item = Self> {
all()
}
pub fn is_pervasive(&self) -> bool {
matches!(
self,
PrimClass::MonadicPervasive | PrimClass::DyadicPervasive
)
}
pub fn primitives(self) -> impl Iterator<Item = Primitive> {
Primitive::all().filter(move |prim| prim.class() == self)
}
}
impl fmt::Debug for PrimClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use PrimClass::*;
match self {
Arguments => write!(f, "Arguments"),
Constant => write!(f, "Constant"),
MonadicPervasive => write!(f, "MonadicPervasive"),
DyadicPervasive => write!(f, "DyadicPervasive"),
MonadicArray => write!(f, "MonadicArray"),
DyadicArray => write!(f, "DyadicArray"),
MappingModifier => write!(f, "MappingModifier"),
IteratingModifier => write!(f, "IteratingModifier"),
InversionModifier => write!(f, "InversionModifier"),
OtherModifier => write!(f, "OtherModifier"),
Comptime => write!(f, "Comptime"),
Debug => write!(f, "Debug"),
Thread => write!(f, "Thread"),
Map => write!(f, "Map"),
Encoding => write!(f, "Encoding"),
Algorithm => write!(f, "Algorithm"),
Rng => write!(f, "RNG"),
Time => write!(f, "Time"),
Environment => write!(f, "Environment"),
Misc => write!(f, "Misc"),
Sys(op) => op.fmt(f),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PrimNames {
pub text: &'static str,
pub ascii: Option<AsciiToken>,
pub glyph: Option<char>,
}
impl From<&'static str> for PrimNames {
fn from(text: &'static str) -> Self {
Self {
text,
ascii: None,
glyph: None,
}
}
}
impl From<(&'static str, char)> for PrimNames {
fn from((text, glyph): (&'static str, char)) -> Self {
Self {
text,
ascii: None,
glyph: Some(glyph),
}
}
}
impl From<(&'static str, AsciiToken, char)> for PrimNames {
fn from((text, ascii, glyph): (&'static str, AsciiToken, char)) -> Self {
Self {
text,
ascii: Some(ascii),
glyph: Some(glyph),
}
}
}
impl fmt::Display for Primitive {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(c) = self.glyph() {
write!(f, "{c}")
} else if let Some(s) = self.ascii() {
write!(f, "{s}")
} else {
write!(f, "{}", self.name())
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct FormatPrimitive(pub Primitive);
impl fmt::Debug for FormatPrimitive {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self}")
}
}
impl fmt::Display for FormatPrimitive {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0.glyph().is_none() {
self.0.fmt(f)
} else {
write!(f, "{} {}", self.0, self.0.name())
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Purity {
Mutating,
Impure,
Pure,
}
impl Primitive {
pub fn all() -> impl Iterator<Item = Self> + Clone {
all()
}
pub fn non_deprecated() -> impl Iterator<Item = Self> + Clone {
Self::all().filter(|p| !p.is_deprecated())
}
pub fn name(&self) -> &'static str {
self.names().text
}
pub fn ascii(&self) -> Option<AsciiToken> {
self.names().ascii
}
pub fn glyph(&self) -> Option<char> {
self.names().glyph
}
pub fn from_name(name: &str) -> Option<Self> {
Self::all().find(|p| p.name() == name)
}
pub fn from_ascii(s: AsciiToken) -> Option<Self> {
Self::all().find(|p| p.ascii() == Some(s))
}
pub fn from_glyph(c: char) -> Option<Self> {
Self::all().find(|p| p.glyph() == Some(c))
}
pub fn sig(&self) -> Option<Signature> {
let (args, outputs) = self.args().zip(self.outputs())?;
Some(Signature::new(args, outputs))
}
pub fn is_modifier(&self) -> bool {
self.modifier_args().is_some()
}
pub fn is_constant(&self) -> bool {
self.constant().is_some()
}
pub fn constant(&self) -> Option<f64> {
use Primitive::*;
match self {
Eta => Some(PI / 2.0),
Pi => Some(PI),
Tau => Some(TAU),
Infinity => Some(f64::INFINITY),
_ => None,
}
}
pub fn format(&self) -> FormatPrimitive {
FormatPrimitive(*self)
}
pub fn subscript_sig(&self, sub: Option<&Subscript>) -> Option<Signature> {
use Primitive::*;
let sub = sub?;
let n = match sub.num.as_ref()? {
NumericSubscript::N(n) => Some(*n),
_ => None,
};
Some(match (self, n) {
(prim, Some(_)) if prim.class() == PrimClass::DyadicPervasive => Signature::new(1, 1),
(Select | Pick | Take | Drop | Rerank | Rotate | Orient | Windows | Base, Some(_)) => {
Signature::new(1, 1)
}
(First | Last, Some(n)) if n >= 0 => Signature::new(1, n as usize),
(Couple | Join | Box, Some(n)) if n >= 0 => Signature::new(n as usize, 1),
(Couple | Join, None) => Signature::new(2, 1),
(Box, None) => Signature::new(1, 1),
(Json, _) => Signature::new(1, 1),
(
Transpose | Sqrt | Exp | Round | Floor | Ceil | Rand | Utf8 | Len | Shape | Range
| Classify,
_,
) => return self.sig(),
(Args, Some(n)) if n >= 0 => Signature::new(n as usize, n as usize),
_ => return None,
})
}
#[allow(unused_parens)]
pub fn subscript_margs(&self, sub: Option<&Subscript>) -> Option<usize> {
use Primitive::*;
let Some(sub) = sub else {
return self.modifier_args();
};
Some(match self {
(Slf | Backward | On | By | With | Off | Both)
| (Rows | Each | Inventory | Table)
| (Repeat | Tuples | Stencil)
| Geometric => 1,
Bracket | Under | Fill => 2,
Reach if sub.side.is_some() => 2,
Reach | OldReach => 1,
_ => return None,
})
}
pub fn deprecation_suggestion(&self) -> Option<String> {
self.deprecation_suggestion_impl().map(|s| s())
}
fn deprecation_suggestion_impl(&self) -> Option<impl FnOnce() -> String + '_> {
use Primitive::*;
Some(match self {
Dup => || {
format!(
"use {}, {}, or {On}{Identity} instead",
Slf.format(),
By.format()
)
},
Flip => || format!("use {} instead", Backward.format()),
OldReach => || format!("use {} instead", Reach.format()),
Log => || format!("use subscripted {} or {Anti}{Backward}{Pow}", Exp.format()),
Rerank => || {
format!(
"use subscripted {} or {Un}{By}({Len}{Shape}) instead",
Deshape.format()
)
},
Windows => || format!("use {} {} instead", Stencil.format(), Identity.format()),
Each => || format!("use {} instead", Rows.format()),
Unique => || format!("use {Occurrences}₁"),
IndexOf => || format!("use {} {} instead", Backward.format(), IndexIn.format()),
_ => return None,
})
}
pub fn is_deprecated(&self) -> bool {
self.deprecation_suggestion_impl().is_some()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Sequence)]
#[allow(missing_docs)]
pub enum SysOpClass {
Filesystem,
StdIO,
Env,
Stream,
Command,
Media,
Tcp,
Udp,
Ffi,
Misc,
}
impl SysOpClass {
pub fn all() -> impl Iterator<Item = Self> {
all()
}
}