datalogic_rs/operators/info.rs
1//! Public description of the built-in operators, read from the operator
2//! table. Backs [`crate::Engine::operators`].
3
4use super::meta::{Frames, OpMeta};
5use crate::OpCode;
6
7/// One built-in operator, as its operator-table row declares it.
8///
9/// Returned by [`crate::Engine::operators`]. Every field is a build-time
10/// fact: the same rows drive compilation, dispatch and the optimizer, so
11/// this description cannot drift from what the engine does.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13#[non_exhaustive]
14pub struct OperatorInfo {
15 /// Canonical name (`"val"`, `"if"`, `"switch"`).
16 pub name: &'static str,
17 /// Other names the compiler accepts for the same operator (`"var"`,
18 /// `"?:"`, `"match"`).
19 pub aliases: &'static [&'static str],
20 /// Operator family (`"Core"`, `"DateTime"`, `"ExtString"`, ...).
21 pub family: &'static str,
22 /// The Cargo feature that gates the family, or `None` for the core
23 /// JSONLogic operators.
24 pub feature: Option<&'static str>,
25 /// Fewest arguments the operator reads, as declared on its table row:
26 /// `==` and `map` report 2, `and` and `if` report 1. Variadics that
27 /// accept an empty list (`+`, `cat`, `missing`) report `0`.
28 pub min_args: usize,
29 /// Most arguments the operator's signature reads, or `None` when it
30 /// does not declare a maximum.
31 pub max_args: Option<usize>,
32 /// Whether the operator reads the data context (`val`, `missing`,
33 /// `exists`, `fractional`).
34 pub reads_context: bool,
35 /// What evaluating the operator does besides computing a value:
36 /// `"pure"`, `"clock"` (`now`), `"throws"` (`throw`) or `"catches"`
37 /// (`try`).
38 pub effect: &'static str,
39 /// What the operator's work is proportional to: `"node"` (constant),
40 /// `"bytes"`, `"per_item"`, `"n_log_n"`, `"quadratic"` or `"elements"`.
41 pub cost: &'static str,
42 /// Which argument, if any, runs under a pushed context frame (an
43 /// iterator body, a key expression, a catch arm).
44 pub scoped_arg: Option<ScopedArg>,
45}
46
47/// An argument position that runs under a pushed context frame, where
48/// `val` paths resolve against the current element (or the caught error)
49/// rather than the enclosing data.
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51#[non_exhaustive]
52pub enum ScopedArg {
53 /// The argument at this index (`map`'s body is `Index(1)`, `sort`'s
54 /// key expression `Index(2)`).
55 Index(usize),
56 /// The last argument, when there are at least two (`try`'s catch arm).
57 LastOfMany,
58}
59
60impl OperatorInfo {
61 pub(crate) fn of(op: OpCode) -> Option<Self> {
62 let entry = op.catalogue_entry();
63 let (name, aliases) = entry.names.split_first()?;
64 let meta: &OpMeta = op.meta();
65 let arity = op.arity();
66 Some(OperatorInfo {
67 name,
68 aliases,
69 family: entry.family,
70 feature: entry.feature(),
71 min_args: arity.min as usize,
72 max_args: arity.max.map(usize::from),
73 reads_context: meta.reads_context,
74 effect: meta.effect.name(),
75 cost: meta.cost.name(),
76 scoped_arg: match meta.frames {
77 Frames::None => None,
78 Frames::At(i) => Some(ScopedArg::Index(i as usize)),
79 Frames::LastIfMulti => Some(ScopedArg::LastOfMany),
80 },
81 })
82 }
83}
84
85/// Every operator of the families in `families` (a set of family bits)
86/// compiled into this build that has a name, in table order. Internal
87/// opcodes (no name) are left out.
88pub(crate) fn operators_in(families: u32) -> impl Iterator<Item = OperatorInfo> {
89 OpCode::ALL
90 .iter()
91 .filter(move |op| op.in_families(families))
92 .filter_map(|op| OperatorInfo::of(*op))
93}