#![cfg_attr(not(feature = "all-operators"), allow(dead_code, unused_imports))]
use super::eager::{self, Arity};
use super::extract::{
self, Any, Int, Lazy, Lenient, Nullable, Obj, Opt, Rest, Str, StrictNum, Truthy,
};
use super::meta::*;
use crate::arena::singletons::{
singleton_empty_array, singleton_empty_string, singleton_false, singleton_null, singleton_true,
};
use crate::arena::{ContextStack, DataValue};
use crate::compile::hooks;
use crate::operators::array::IterArgKind;
use crate::{CompiledNode, Engine, Result};
use bumpalo::Bump;
#[cfg(feature = "ext-math")]
use super::arithmetic::UnaryMathOp;
#[cfg(feature = "datetime")]
use super::datetime;
#[cfg(feature = "error-handling")]
use super::error_handling;
#[cfg(feature = "flagd")]
use super::flagd;
#[cfg(feature = "ext-control")]
use super::inspect;
#[cfg(feature = "ext-object")]
use super::object;
#[cfg(feature = "tensor")]
use super::tensor::{self, DTypeArg, F64Arg, I64Arg, I64List, ShapeArg, TensorArg, UsizeArg};
use super::{arithmetic, array, comparison, control, logical, missing, string, variable};
#[doc(hidden)]
#[derive(Debug, Clone, Copy)]
pub struct CatalogueEntry {
pub family: &'static str,
pub gate: &'static str,
pub enabled: bool,
pub variant: &'static str,
pub names: &'static [&'static str],
pub shape: &'static str,
}
impl CatalogueEntry {
pub fn feature(&self) -> Option<&'static str> {
let rest = self.gate.strip_prefix("feature")?.trim_start();
let rest = rest.strip_prefix('=')?.trim();
Some(rest.trim_matches('"'))
}
}
macro_rules! operators {
(
$(
family $fam:ident ($gate:meta) = $default:path {
$(
$v:ident [ $($name:literal),* ] => $shape:ident $( [ $($ar:tt)* ] )? $( ( $($ext:ty),* ) )?
$($f:ident)::+ $( ( $($karg:expr),* ) )?
$( @ $alg:ident ( $payload:expr ) )?
$( { $($meta:tt)* } )? ;
)*
}
)*
) => {
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum OpCode {
$( $( #[cfg($gate)] $v, )* )*
}
const NAMES: &[&str] = &[ $( $( $( #[cfg($gate)] $name, )* )* )* ];
#[doc(hidden)]
pub const CATALOGUE: &[CatalogueEntry] = &[
$( $(
operators!(@entry $fam ($gate) $v [ $($name),* ] $shape $( ( $($ext),* ) )?),
)* )*
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Family {
$(
#[doc = concat!("The `", stringify!($fam), "` family (`", stringify!($gate), "`).")]
$fam,
)*
}
impl Family {
pub const ALL: &'static [Family] = &[ $( Family::$fam, )* ];
pub const fn name(self) -> &'static str {
match self {
$( Family::$fam => stringify!($fam), )*
}
}
pub const fn is_compiled(self) -> bool {
match self {
$( Family::$fam => cfg!($gate), )*
}
}
#[inline]
pub(crate) const fn bit(self) -> u32 {
1 << (self as u32)
}
}
impl std::str::FromStr for OpCode {
type Err = ();
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
$( $( $( #[cfg($gate)] $name => Ok(OpCode::$v), )* )* )*
_ => Err(()),
}
}
}
impl OpCode {
pub(crate) const ALL: &'static [OpCode] = &[ $( $( #[cfg($gate)] OpCode::$v, )* )* ];
pub(crate) const fn meta(self) -> &'static OpMeta {
match self {
$( $( #[cfg($gate)] OpCode::$v => { const META: OpMeta = operators!(@row_meta [$default] { $( $($meta)* )? } $( $alg ($payload) )?); &META } )* )*
}
}
pub(crate) const fn as_str(self) -> &'static str {
match self {
$( $( #[cfg($gate)] OpCode::$v => operators!(@canonical OpCode::$v; $($name),*), )* )*
}
}
#[inline(always)]
pub(crate) fn algebra(self) -> Option<Algebra> {
const ALGEBRA: &[Option<Algebra>] = &[
$( $( #[cfg($gate)] { const META: OpMeta = operators!(@row_meta [$default] { $( $($meta)* )? } $( $alg ($payload) )?); META.algebra }, )* )*
];
ALGEBRA[self as usize]
}
#[inline(always)]
pub(crate) const fn arith_op(self) -> Option<ArithOp> {
match self {
$( $(
#[cfg($gate)]
OpCode::$v => {
const OP: Option<ArithOp> = match (operators!(@row_meta [$default] { $( $($meta)* )? } $( $alg ($payload) )?)).algebra {
Some(Algebra::Arith(op)) => Some(op),
_ => None,
};
OP
}
)* )*
}
}
pub(crate) const fn family(self) -> Family {
match self {
$( $( #[cfg($gate)] OpCode::$v => Family::$fam, )* )*
}
}
#[inline]
pub(crate) const fn in_families(self, families: u32) -> bool {
families & self.family().bit() != 0
}
pub(crate) const fn catalogue_entry(self) -> &'static CatalogueEntry {
match self {
$( $(
#[cfg($gate)]
OpCode::$v => {
const ENTRY: CatalogueEntry =
operators!(@entry $fam ($gate) $v [ $($name),* ] $shape $( ( $($ext),* ) )?);
&ENTRY
}
)* )*
}
}
pub(crate) const fn iterates_arg0(self) -> bool {
match self {
$( $( #[cfg($gate)] OpCode::$v => operators!(@is_iter $shape), )* )*
}
}
pub(crate) const fn arity(self) -> Arity {
match self {
$( $( #[cfg($gate)] OpCode::$v => operators!(@arity $shape $( [ $($ar)* ] )? $( ( $($ext),* ) )?), )* )*
}
}
}
#[inline(always)]
pub(crate) fn dispatch_builtin<'a>(
opcode: OpCode,
args: &'a [CompiledNode],
iter_arg_kind: IterArgKind,
ctx: &mut ContextStack<'a>,
engine: &Engine,
arena: &'a Bump,
) -> Result<&'a DataValue<'a>> {
let _ = iter_arg_kind;
match opcode {
$( $(
#[cfg($gate)]
OpCode::$v => operators!(
@call $v $shape { $( [ $($ar)* ] )? } $( ( $($ext),* ) )? [ $($f)::+ ] [ $( $($karg),* )? ] [ $($payload)? ]
(args, iter_arg_kind, ctx, engine, arena)
),
)* )*
}
}
};
(@row_meta [$default:path] { .. $base:path } $($rest:tt)*) => {
operators!(@meta (OpMeta { ..$base }) $($rest)*)
};
(@row_meta [$default:path] { $( $field:ident : $value:expr ),* $(,)? } $($rest:tt)*) => {
operators!(@meta (OpMeta { $( $field: $value, )* ..$default }) $($rest)*)
};
(@row_meta [$default:path] { $( $field:ident : $value:expr , )+ .. $base:path } $($rest:tt)*) => {
operators!(@meta (OpMeta { $( $field: $value, )+ ..$base }) $($rest)*)
};
(@meta ($meta:expr)) => { $meta };
(@meta ($meta:expr) $alg:ident ($payload:expr)) => {{
const BASE: OpMeta = $meta;
assert!(
BASE.algebra.is_none(),
"a row with `@ Kind(payload)` must not also set `algebra`"
);
OpMeta { algebra: Some(Algebra::$alg($payload)), ..BASE }
}};
(@entry $fam:ident ($gate:meta) $v:ident [ $($name:literal),* ] $shape:ident $( ( $($ext:ty),* ) )?) => {
CatalogueEntry {
family: stringify!($fam),
gate: stringify!($gate),
enabled: cfg!($gate),
variant: stringify!($v),
names: &[ $($name),* ],
shape: stringify!($shape $( ( $($ext),* ) )?),
}
};
(@canonical $op:expr; $first:literal $(, $rest:literal)*) => { $first };
(@canonical $op:expr;) => {
match $op.meta().display {
Some(name) => name,
None => panic!("an internal opcode needs a `display` name"),
}
};
(@is_iter iter) => { true };
(@is_iter each) => { true };
(@is_iter $other:ident) => { false };
(@arity raw) => { Arity::ANY };
(@arity iter) => { Arity::ANY };
(@arity raw [ $($r:tt)* ]) => { operators!(@range $($r)*) };
(@arity iter [ $($r:tt)* ]) => { operators!(@range $($r)*) };
(@arity each [ $($r:tt)* ]) => { operators!(@range $($r)*) };
(@range $n:literal) => { Arity::exactly($n) };
(@range $lo:literal ..) => { Arity::at_least($lo) };
(@range $lo:literal ..= $hi:literal) => { Arity::between($lo, $hi) };
(@arity eager ( $($e:ty),* )) => {
const { Arity::of(&[ $( <$e as extract::Extract<'static>>::SPAN ),* ]) }
};
(@call $v:ident raw { } [ $($p:tt)* ] [ $($k:expr),* ] [ $($pl:expr)? ]
($args:ident, $iak:ident, $ctx:ident, $engine:ident, $arena:ident)) => {
$($p)*($args, $ctx, $engine, $arena $(, $k)* $(, $pl)?)
};
(@call $v:ident raw { [ $($ar:tt)* ] } [ $($p:tt)* ] [ $($k:expr),* ] [ $($pl:expr)? ]
($args:ident, $iak:ident, $ctx:ident, $engine:ident, $arena:ident)) => {
eager::raw::<{ OpCode::$v as u8 }, _>($args, $ctx, $engine, $arena,
|$args, $ctx, $engine, $arena| $($p)*($args, $ctx, $engine, $arena $(, $k)* $(, $pl)?))
};
(@call $v:ident iter { } [ $($p:tt)* ] [ $($k:expr),* ] [ $($pl:expr)? ]
($args:ident, $iak:ident, $ctx:ident, $engine:ident, $arena:ident)) => {
$($p)*($args, $iak, $ctx, $engine, $arena $(, $k)* $(, $pl)?)
};
(@call $v:ident iter { [ $($ar:tt)* ] } [ $($p:tt)* ] [ $($k:expr),* ] [ $($pl:expr)? ]
($args:ident, $iak:ident, $ctx:ident, $engine:ident, $arena:ident)) => {
eager::iter::<{ OpCode::$v as u8 }, _>($args, $iak, $ctx, $engine, $arena,
|$args, $iak, $ctx, $engine, $arena| $($p)*($args, $iak, $ctx, $engine, $arena $(, $k)* $(, $pl)?))
};
(@call $v:ident each { [ $($ar:tt)* ] } [ $($p:tt)* ] [ $($k:expr),* ] [ $($pl:expr)? ]
($args:ident, $iak:ident, $ctx:ident, $engine:ident, $arena:ident)) => {
eager::each::<{ OpCode::$v as u8 }, _>($args, $iak, $ctx, $engine, $arena,
|items, $args, $ctx, $engine, $arena| $($p)*(items, $args, $ctx, $engine, $arena $(, $k)* $(, $pl)?))
};
(@call $v:ident eager { } ( $($a:ty),* ) [ $($p:tt)* ] [ ] [ ]
($args:ident, $iak:ident, $ctx:ident, $engine:ident, $arena:ident)) => {
eager::call::<{ OpCode::$v as u8 }, ( $($a,)* ), _, _>($args, $ctx, $engine, $arena, $($p)*)
};
(@call $v:ident eager { } ( $($a:ty),* ) [ $($p:tt)* ] [ ] [ $k:expr ]
($args:ident, $iak:ident, $ctx:ident, $engine:ident, $arena:ident)) => {
eager::call::<{ OpCode::$v as u8 }, ( $($a,)* ), _, _>($args, $ctx, $engine, $arena,
eager::Bound($($p)*, $k))
};
(@call $v:ident eager { } ( $($a:ty),* ) [ $($p:tt)* ] [ $k:expr ] [ ]
($args:ident, $iak:ident, $ctx:ident, $engine:ident, $arena:ident)) => {
eager::call::<{ OpCode::$v as u8 }, ( $($a,)* ), _, _>($args, $ctx, $engine, $arena,
eager::Bound($($p)*, $k))
};
(@call $v:ident $($rest:tt)*) => {
compile_error!(concat!(
"operator row `", stringify!($v),
"`: unknown shape, more than six eager arguments, more than one bound argument, `[arity]` on an eager row, or `each` without `[arity]`"
))
};
}
operators! {
family Core (all()) = PURE {
Val ["val", "var"] => raw variable::evaluate_val {
reads_context: true,
compile: Some(CompileHook::Args(hooks::val)),
literal_args: LiteralArgs::All,
};
VarDefault [] => raw variable::evaluate_var_default { reads_context: true, display: Some("var") };
Equals ["=="] => raw[2..] comparison::equals @ Eq(EqOp::LOOSE) { cost: Cost::PerItem };
StrictEquals ["==="] => raw[2..] comparison::equals @ Eq(EqOp::STRICT) { cost: Cost::PerItem };
NotEquals ["!="] => raw[2] comparison::not_equals @ Eq(EqOp::LOOSE_NE) { cost: Cost::PerItem };
StrictNotEquals ["!=="] => raw[2] comparison::not_equals @ Eq(EqOp::STRICT_NE) { cost: Cost::PerItem };
GreaterThan [">"] => raw[2..] comparison::ordered @ Ord(OrdOp::Gt);
GreaterThanEqual [">="] => raw[2..] comparison::ordered @ Ord(OrdOp::Ge);
LessThan ["<"] => raw[2..] comparison::ordered @ Ord(OrdOp::Lt);
LessThanEqual ["<="] => raw[2..] comparison::ordered @ Ord(OrdOp::Le);
Not ["!"] => eager(Opt<Truthy>) logical::truth @ Truth(Truth::Not);
BoolCast ["!!"] => eager(Opt<Truthy>) logical::truth @ Truth(Truth::Bool);
And ["and"] => raw[1..] logical::short_circuit @ Logic(Logic::And)
{ args_form: ArgsForm::ArrayOnly, on_missing: Miss::Return(singleton_null) };
Or ["or"] => raw[1..] logical::short_circuit @ Logic(Logic::Or)
{ args_form: ArgsForm::ArrayOnly, on_missing: Miss::Return(singleton_null) };
If ["if", "?:"] => raw[1..] control::evaluate_if
{ args_form: ArgsForm::ArrayOnly, on_missing: Miss::Return(singleton_null) };
Add ["+"] => raw arithmetic::evaluate_add
{ algebra: Some(Algebra::Arith(ArithOp::Add)), literal_args: LiteralArgs::Sole, cost: Cost::PerItem };
Subtract ["-"] => raw[1..] arithmetic::evaluate_subtract
{ algebra: Some(Algebra::Arith(ArithOp::Sub)), cost: Cost::PerItem };
Multiply ["*"] => raw arithmetic::evaluate_multiply
{ algebra: Some(Algebra::Arith(ArithOp::Mul)), literal_args: LiteralArgs::Sole, cost: Cost::PerItem };
Divide ["/"] => raw[1..] arithmetic::div_or_mod @ Div(DivOp::Divide) { cost: Cost::PerItem };
Modulo ["%"] => raw[1..] arithmetic::div_or_mod @ Div(DivOp::Modulo) { cost: Cost::PerItem };
Max ["max"] => iter[1..] arithmetic::extremum @ Extremum(Extremum::Max)
{ fold: Fold::Never, literal_args: LiteralArgs::Sole, cost: Cost::PerItem };
Min ["min"] => iter[1..] arithmetic::extremum @ Extremum(Extremum::Min)
{ fold: Fold::Never, literal_args: LiteralArgs::Sole, cost: Cost::PerItem };
Concat ["cat"] => eager(Rest<Any>) string::concat { algebra: Some(Algebra::Concat), ..STRING };
Substr ["substr"] => eager(Str, Lenient<Int>, Lenient<Int>) string::substr
{ on_missing: Miss::Return(singleton_empty_string), ..STRING };
In ["in"] => eager(Any, Any) string::in_ { on_missing: Miss::Return(singleton_false), ..STRING };
Merge ["merge"] => raw array::evaluate_merge { fold: Fold::Never, cost: Cost::PerItem };
Filter ["filter"] => each[2] array::evaluate_filter
{ on_empty_source: Some(singleton_empty_array), ..ITERATOR };
Map ["map"] => each[2] array::evaluate_map { on_empty_source: Some(singleton_empty_array), ..ITERATOR };
Reduce ["reduce"] => iter[2..=3] array::evaluate_reduce { ..ITERATOR };
All ["all"] => each[2] array::quantifier @ Quant(Quant::All)
{ on_empty_source: Some(singleton_false), ..ITERATOR };
Some ["some"] => each[2] array::quantifier @ Quant(Quant::Some)
{ on_empty_source: Some(singleton_false), ..ITERATOR };
None ["none"] => each[2] array::quantifier @ Quant(Quant::None)
{ on_empty_source: Some(singleton_true), ..ITERATOR };
Missing ["missing"] => eager(Rest<Any>) missing::missing
{ reads_context: true, compile: Some(CompileHook::Args(hooks::missing)), cost: Cost::PerItem };
MissingSome ["missing_some"] => eager(Any, Any) missing::missing_some {
reads_context: true,
on_missing: Miss::Return(singleton_empty_array),
compile: Some(CompileHook::Args(hooks::missing_some)),
cost: Cost::PerItem,
};
}
family DateTime (feature = "datetime") = PURE {
Datetime ["datetime"] => eager(Any) datetime::datetime
{ on_missing: Miss::Err("datetime requires an argument") };
Timestamp ["timestamp"] => eager(Any) datetime::timestamp
{ on_missing: Miss::Err("timestamp requires an argument") };
ParseDate ["parse_date"] => eager(Any, Any, Lazy) datetime::parse_date {
on_missing: Miss::Err("parse_date requires date string and format"),
compile: Some(CompileHook::Args(hooks::timezone_literal)),
literal_args: LiteralArgs::At(2),
};
FormatDate ["format_date"] => eager(Any, Any, Lazy) datetime::format_date {
on_missing: Miss::Err("format_date requires datetime and format"),
compile: Some(CompileHook::Args(hooks::timezone_literal)),
literal_args: LiteralArgs::At(2),
};
DateDiff ["date_diff"] => eager(Any, Any, Any) datetime::date_diff
{ on_missing: Miss::Err("date_diff requires two dates and a unit") };
Now ["now"] => eager() datetime::now { effect: Effect::Clock };
}
family ExtString (feature = "ext-string") = STRING {
Length ["length"] => eager(Any) array::length { cost: Cost::Bytes, on_extra: Extra::InvalidArgs };
StartsWith ["starts_with"] => eager(Str, Str) string::starts_with;
EndsWith ["ends_with"] => eager(Str, Str) string::ends_with;
Upper ["upper"] => eager(Str) string::upper;
Lower ["lower"] => eager(Str) string::lower;
Trim ["trim"] => eager(Str) string::trim;
Split ["split"] => eager(Str, Str) string::split;
}
family ExtArray (feature = "ext-array") = ITERATOR {
Sort ["sort"] => iter[1..=3] array::evaluate_sort {
frames: Frames::At(2),
cost: Cost::NLogN,
on_extra: Extra::Ignore,
literal_args: LiteralArgs::At(0),
};
Slice ["slice"] => raw[1..=4] array::evaluate_slice { cost: Cost::PerItem, ..PURE };
GroupBy ["group_by"] => each[2] array::evaluate_group_by
{ on_extra: Extra::Ignore, on_empty_source: Some(singleton_empty_array), cost: Cost::Quadratic };
Distinct ["distinct"] => each[1..=2] array::evaluate_distinct {
cost: Cost::Quadratic,
on_extra: Extra::Ignore,
on_empty_source: Some(singleton_empty_array),
};
}
family ExtObject (feature = "ext-object") = PURE {
Keys ["keys"] => eager(Nullable<Obj>) object::keys { cost: Cost::PerItem };
Values ["values"] => eager(Nullable<Obj>) object::values { cost: Cost::PerItem };
Entries ["entries"] => eager(Nullable<Obj>) object::entries { cost: Cost::PerItem };
}
family ExtControl (feature = "ext-control") = PURE {
Exists ["exists"] => eager(Any, Rest<Any>) variable::exists {
reads_context: true,
on_missing: Miss::Return(singleton_true),
compile: Some(CompileHook::Args(hooks::exists)),
};
Coalesce ["??"] => eager(Rest<Any>) control::coalesce;
Switch ["switch", "match"] => raw[2..=3] control::evaluate_switch
{ on_missing: Miss::Return(singleton_null), literal_args: LiteralArgs::At(1) };
Type ["type"] => eager(Any) inspect::type_ { on_missing: Miss::Return(inspect::type_of_nothing) };
}
family ErrorHandling (feature = "error-handling") = PURE {
Try ["try"] => raw[1..] error_handling::evaluate_try {
effect: Effect::Catches,
frames: Frames::LastIfMulti,
on_missing: Miss::Return(singleton_null),
};
Throw ["throw"] => raw error_handling::evaluate_throw
{ effect: Effect::Throws, compile: Some(CompileHook::Args(hooks::throw_literal)) };
}
family ExtMath (feature = "ext-math") = PURE {
Abs ["abs"] => eager(StrictNum, Rest<StrictNum>) arithmetic::unary_math(UnaryMathOp::Abs);
Ceil ["ceil"] => eager(StrictNum, Rest<StrictNum>) arithmetic::unary_math(UnaryMathOp::Ceil);
Floor ["floor"] => eager(StrictNum, Rest<StrictNum>) arithmetic::unary_math(UnaryMathOp::Floor);
}
family Tensor (feature = "tensor") = TENSOR {
TensorMake ["tensor"] => eager(Any, Lazy) tensor::tensor
{ compile: Some(CompileHook::Raw(hooks::tensor_wire_body)) };
TensorZeros ["zeros"] => eager(ShapeArg, DTypeArg) tensor::zeros;
TensorFull ["full"] => eager(ShapeArg, DTypeArg, Any) tensor::full;
TensorScatter ["scatter"] => eager(Any, ShapeArg, DTypeArg, Opt<Any>) tensor::scatter;
TensorRleExpand ["rle_expand"] => eager(Any, ShapeArg, DTypeArg) tensor::rle_expand;
TensorOneHot ["one_hot"] => eager(Any, UsizeArg, DTypeArg) tensor::one_hot;
TensorStack ["stack"] => eager(Any, I64Arg) tensor::stack;
TensorConcat ["concat"] => eager(Any, I64Arg) tensor::concat;
TensorUnstack ["unstack"] => eager(TensorArg, I64Arg) tensor::unstack;
TensorReshape ["reshape"] => eager(TensorArg, ShapeArg) tensor::reshape { cost: Cost::Node };
TensorTranspose ["transpose"] => eager(TensorArg, Opt<ShapeArg>) tensor::transpose;
TensorPad ["pad"] => eager(TensorArg, ShapeArg, ShapeArg, Opt<Any>) tensor::pad;
TensorCrop ["crop"] => eager(TensorArg, ShapeArg, ShapeArg) tensor::crop;
TensorCast ["cast"] => eager(TensorArg, DTypeArg) tensor::cast;
TensorNormalize ["normalize"] => eager(TensorArg, F64Arg, Opt<F64Arg>) tensor::normalize;
TensorArgmax ["argmax"] => eager(TensorArg, I64Arg) tensor::argmax;
TensorGather ["gather"] => eager(TensorArg, I64List, Opt<I64Arg>) tensor::gather;
TensorToList ["to_list"] => eager(TensorArg) tensor::to_list;
TensorShape ["shape"] => eager(TensorArg) tensor::shape { cost: Cost::Node };
TensorDtype ["dtype"] => eager(TensorArg) tensor::dtype { cost: Cost::Node };
}
family Flagd (feature = "flagd") = PURE {
Fractional ["fractional"] => raw[1..] flagd::evaluate_fractional
{ reads_context: true, cse: Cse::Never, on_missing: Miss::Return(singleton_null) };
SemVer ["sem_ver"] => eager(Any, Any, Any) flagd::sem_ver {
on_missing: Miss::Return(singleton_null),
on_extra: Extra::Return(singleton_null),
cse: Cse::Never,
};
}
}
impl OpCode {
pub(crate) fn with_algebra(algebra: Algebra) -> Option<OpCode> {
OpCode::ALL
.iter()
.copied()
.find(|op| op.algebra() == Some(algebra))
}
}
#[cfg(test)]
pub(crate) fn builtin_operator_names() -> impl Iterator<Item = &'static str> {
NAMES.iter().copied()
}
pub(crate) fn builtin_operator_names_in(families: u32) -> impl Iterator<Item = &'static str> {
NAMES.iter().copied().filter(move |name| {
name.parse::<OpCode>()
.is_ok_and(|op| op.in_families(families))
})
}