1use std::collections::HashMap;
4use std::fmt::{Debug, Display};
5use std::ops::{Bound, RangeBounds};
6use std::sync::OnceLock;
7
8use documented::DocumentedVariants;
9use proc_macro2::{Ident, Literal, Span, TokenStream};
10use quote::quote_spanned;
11use serde::{Deserialize, Serialize};
12use slotmap::Key;
13use syn::punctuated::Punctuated;
14use syn::{Expr, Token, parse_quote_spanned};
15
16use super::{
17 AsCodeOptions, GraphLoopId, GraphNode, GraphNodeId, GraphSubgraphId, OpInstGenerics,
18 OperatorInstance, PortIndexValue,
19};
20use crate::diagnostic::{Diagnostic, Diagnostics, Level};
21use crate::parse::{Operator, PortIndex};
22
23#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug, Serialize, Deserialize)]
25pub enum DelayType {
26 Tick,
28 TickLazy,
30 Loop,
32 LoopLazy,
34}
35
36pub enum PortListSpec {
38 Variadic,
40 Fixed(Punctuated<PortIndex, Token![,]>),
42}
43
44pub struct OperatorConstraints {
46 pub name: &'static str,
48 pub categories: &'static [OperatorCategory],
50
51 pub hard_range_inn: &'static dyn RangeTrait<usize>,
54 pub soft_range_inn: &'static dyn RangeTrait<usize>,
56 pub hard_range_out: &'static dyn RangeTrait<usize>,
58 pub soft_range_out: &'static dyn RangeTrait<usize>,
60 pub num_args: usize,
62 pub persistence_args: &'static dyn RangeTrait<usize>,
64 pub type_args: &'static dyn RangeTrait<usize>,
68 pub is_external_input: bool,
71 pub flo_type: Option<FloType>,
73
74 pub ports_inn: Option<fn() -> PortListSpec>,
76 pub ports_out: Option<fn() -> PortListSpec>,
78
79 pub input_delaytype_fn: fn(&PortIndexValue) -> Option<DelayType>,
81 pub write_fn: WriteFn,
83}
84
85pub type WriteFn = fn(&WriteContextArgs<'_>, &mut Diagnostics) -> Result<OperatorWriteOutput, ()>;
87
88impl Debug for OperatorConstraints {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 f.debug_struct("OperatorConstraints")
91 .field("name", &self.name)
92 .field("hard_range_inn", &self.hard_range_inn)
93 .field("soft_range_inn", &self.soft_range_inn)
94 .field("hard_range_out", &self.hard_range_out)
95 .field("soft_range_out", &self.soft_range_out)
96 .field("num_args", &self.num_args)
97 .field("persistence_args", &self.persistence_args)
98 .field("type_args", &self.type_args)
99 .field("is_external_input", &self.is_external_input)
100 .field("ports_inn", &self.ports_inn)
101 .field("ports_out", &self.ports_out)
102 .finish()
106 }
107}
108
109pub struct OperatorWriteOutput {
116 pub write_prologue: TokenStream,
119 pub write_iterator: TokenStream,
126 pub write_iterator_after: TokenStream,
128 pub write_tick_end: TokenStream,
131}
132
133pub const RANGE_ANY: &'static dyn RangeTrait<usize> = &(0..);
135pub const RANGE_0: &'static dyn RangeTrait<usize> = &(0..=0);
137pub const RANGE_1: &'static dyn RangeTrait<usize> = &(1..=1);
139
140pub fn identity_write_iterator_fn(
148 &WriteContextArgs {
149 root,
150 op_span,
151 ident,
152 inputs,
153 outputs,
154 is_pull,
155 options,
156 op_inst:
157 OperatorInstance {
158 generics: OpInstGenerics { type_args, .. },
159 ..
160 },
161 ..
162 }: &WriteContextArgs<'_>,
163) -> TokenStream {
164 if type_args.is_empty() && options.exclude_type_guards {
165 let src = if is_pull { &inputs[0] } else { &outputs[0] };
167 return quote_spanned! {op_span=>
168 let #ident = #src;
169 };
170 }
171
172 let generic_type = type_args
173 .first()
174 .map(quote::ToTokens::to_token_stream)
175 .unwrap_or_else(|| quote_spanned!(op_span=> _));
176
177 if is_pull {
178 let input = &inputs[0];
179 quote_spanned! {op_span=>
180 let #ident = {
181 fn check_input<Pull, Item>(pull: Pull) -> impl #root::dfir_pipes::pull::Pull<Item = Item, Meta = Pull::Meta, CanPend = Pull::CanPend, CanEnd = Pull::CanEnd>
182 where
183 Pull: #root::dfir_pipes::pull::Pull<Item = Item>,
184 {
185 pull
186 }
187 check_input::<_, #generic_type>(#input)
188 };
189 }
190 } else {
191 let output = &outputs[0];
192 quote_spanned! {op_span=>
193 let #ident = {
194 fn check_output<Psh, Item>(push: Psh) -> impl #root::dfir_pipes::push::Push<Item, (), CanPend = Psh::CanPend>
195 where
196 Psh: #root::dfir_pipes::push::Push<Item, ()>,
197 {
198 push
199 }
200 check_output::<_, #generic_type>(#output)
201 };
202 }
203 }
204}
205
206pub const IDENTITY_WRITE_FN: WriteFn = |write_context_args, _| {
208 let write_iterator = identity_write_iterator_fn(write_context_args);
209 Ok(OperatorWriteOutput {
210 write_prologue: Default::default(),
211 write_iterator,
212 write_iterator_after: Default::default(),
213 write_tick_end: Default::default(),
214 })
215};
216
217pub fn null_write_iterator_fn(
220 &WriteContextArgs {
221 root,
222 op_span,
223 ident,
224 inputs,
225 outputs,
226 is_pull,
227 op_inst:
228 OperatorInstance {
229 generics: OpInstGenerics { type_args, .. },
230 ..
231 },
232 ..
233 }: &WriteContextArgs<'_>,
234) -> TokenStream {
235 let default_type = parse_quote_spanned! {op_span=> _};
236 let iter_type = type_args.first().unwrap_or(&default_type);
237
238 if is_pull {
239 quote_spanned! {op_span=>
240 let #ident = #root::dfir_pipes::pull::poll_fn({
241 #(
242 let mut #inputs = ::std::boxed::Box::pin(#inputs);
243 )*
244 move |_cx| {
245 #(
249 let #inputs = #root::dfir_pipes::pull::Pull::pull(
250 ::std::pin::Pin::as_mut(&mut #inputs),
251 <_ as #root::dfir_pipes::Context>::from_task(_cx),
252 );
253 )*
254 #(
255 if let #root::dfir_pipes::pull::PullStep::Pending(_) = #inputs {
256 return #root::dfir_pipes::pull::PullStep::Pending(#root::dfir_pipes::Yes);
257 }
258 )*
259 #root::dfir_pipes::pull::PullStep::<_, _, #root::dfir_pipes::Yes, _>::Ended(#root::dfir_pipes::Yes)
260 }
261 });
262 }
263 } else {
264 quote_spanned! {op_span=>
265 #[allow(clippy::let_unit_value, clippy::ignored_unit_patterns)]
266 let _ = (#(#outputs),*);
267 let #ident = #root::dfir_pipes::push::for_each::<_, #iter_type>(::std::mem::drop::<#iter_type>);
268 }
269 }
270}
271
272pub const NULL_WRITE_FN: WriteFn = |write_context_args, _| {
275 let write_iterator = null_write_iterator_fn(write_context_args);
276 Ok(OperatorWriteOutput {
277 write_prologue: Default::default(),
278 write_iterator,
279 write_iterator_after: Default::default(),
280 write_tick_end: Default::default(),
281 })
282};
283
284macro_rules! declare_ops {
285 ( $( $mod:ident :: $op:ident, )* ) => {
286 $( pub(crate) mod $mod; )*
287 pub const OPERATORS: &[OperatorConstraints] = &[
289 $( $mod :: $op, )*
290 ];
291 };
292}
293declare_ops![
294 all_iterations::ALL_ITERATIONS,
295 anti_join::ANTI_JOIN,
296 assert::ASSERT,
297 assert_eq::ASSERT_EQ,
298 batch::BATCH,
299 batch_eager::BATCH_EAGER,
300 batch_lazy::BATCH_LAZY,
301 chain::CHAIN,
302 chain_first_n::CHAIN_FIRST_N,
303 _counter::_COUNTER,
304 cross_join::CROSS_JOIN,
305 cross_join_multiset::CROSS_JOIN_MULTISET,
306 cross_singleton::CROSS_SINGLETON,
307 demux_enum::DEMUX_ENUM,
308 dest_file::DEST_FILE,
309 dest_sink::DEST_SINK,
310 dest_sink_serde::DEST_SINK_SERDE,
311 difference::DIFFERENCE,
312 enumerate::ENUMERATE,
313 filter::FILTER,
314 filter_map::FILTER_MAP,
315 flat_map::FLAT_MAP,
316 flat_map_stream_blocking::FLAT_MAP_STREAM_BLOCKING,
317 flatten::FLATTEN,
318 flatten_stream_blocking::FLATTEN_STREAM_BLOCKING,
319 fold::FOLD,
320 fold_no_replay::FOLD_NO_REPLAY,
321 for_each::FOR_EACH,
322 identity::IDENTITY,
323 initialize::INITIALIZE,
324 inspect::INSPECT,
325 iter_ref::ITER_REF,
326 join::JOIN,
327 join_fused::JOIN_FUSED,
328 join_fused_lhs::JOIN_FUSED_LHS,
329 join_fused_rhs::JOIN_FUSED_RHS,
330 join_multiset::JOIN_MULTISET,
331 join_multiset_half::JOIN_MULTISET_HALF,
332 fold_keyed::FOLD_KEYED,
333 reduce_keyed::REDUCE_KEYED,
334 lattice_bimorphism::LATTICE_BIMORPHISM,
335 _lattice_fold_batch::_LATTICE_FOLD_BATCH,
336 lattice_fold::LATTICE_FOLD,
337 _lattice_join_fused_join::_LATTICE_JOIN_FUSED_JOIN,
338 lattice_reduce::LATTICE_REDUCE,
339 map::MAP,
340 union::UNION,
341 multiset_delta::MULTISET_DELTA,
342 defer_signal::DEFER_SIGNAL,
343 defer_tick::DEFER_TICK,
344 defer_tick_lazy::DEFER_TICK_LAZY,
345 null::NULL,
346 partition::PARTITION,
347 persist::PERSIST,
348 resolve_futures::RESOLVE_FUTURES,
349 resolve_futures_blocking::RESOLVE_FUTURES_BLOCKING,
350 resolve_futures_blocking_ordered::RESOLVE_FUTURES_BLOCKING_ORDERED,
351 resolve_futures_ordered::RESOLVE_FUTURES_ORDERED,
352 reduce::REDUCE,
353 reduce_no_replay::REDUCE_NO_REPLAY,
354 scan::SCAN,
355 scan_async_blocking::SCAN_ASYNC_BLOCKING,
356 spin::SPIN,
357 sort::SORT,
358 sort_by_key::SORT_BY_KEY,
359 source_file::SOURCE_FILE,
360 source_interval::SOURCE_INTERVAL,
361 source_iter::SOURCE_ITER,
362 source_json::SOURCE_JSON,
363 source_stdin::SOURCE_STDIN,
364 source_stream::SOURCE_STREAM,
365 source_stream_serde::SOURCE_STREAM_SERDE,
366 state::STATE,
367 state_by::STATE_BY,
368 tee::TEE,
369 unique::UNIQUE,
370 unzip::UNZIP,
371 zip::ZIP,
372 zip_longest::ZIP_LONGEST,
373];
374
375pub fn operator_lookup() -> &'static HashMap<&'static str, &'static OperatorConstraints> {
377 pub static OPERATOR_LOOKUP: OnceLock<HashMap<&'static str, &'static OperatorConstraints>> =
378 OnceLock::new();
379 OPERATOR_LOOKUP.get_or_init(|| OPERATORS.iter().map(|op| (op.name, op)).collect())
380}
381pub fn find_node_op_constraints(node: &GraphNode) -> Option<&'static OperatorConstraints> {
383 if let GraphNode::Operator(operator) = node {
384 find_op_op_constraints(operator)
385 } else {
386 None
387 }
388}
389pub fn find_op_op_constraints(operator: &Operator) -> Option<&'static OperatorConstraints> {
391 let name = &*operator.name_string();
392 operator_lookup().get(name).copied()
393}
394
395#[derive(Clone)]
397pub struct WriteContextArgs<'a> {
398 pub root: &'a TokenStream,
400 pub context: &'a Ident,
403 pub df_ident: &'a Ident,
407 pub subgraph_id: GraphSubgraphId,
409 pub node_id: GraphNodeId,
411 pub loop_id: Option<GraphLoopId>,
413 pub op_span: Span,
415 pub op_tag: Option<String>,
417 pub work_fn: &'a Ident,
419 pub work_fn_async: &'a Ident,
421
422 pub ident: &'a Ident,
424 pub is_pull: bool,
426 pub inputs: &'a [Ident],
428 pub outputs: &'a [Ident],
430
431 pub op_name: &'static str,
433 pub op_inst: &'a OperatorInstance,
435 pub arguments: &'a Punctuated<Expr, Token![,]>,
441
442 pub options: &'a AsCodeOptions,
448}
449impl WriteContextArgs<'_> {
450 pub fn make_ident(&self, suffix: impl AsRef<str>) -> Ident {
456 Ident::new(
457 &format!(
458 "sg_{:?}_node_{:?}_{}",
459 self.subgraph_id.data(),
460 self.node_id.data(),
461 suffix.as_ref(),
462 ),
463 self.op_span,
464 )
465 }
466
467 pub fn persistence_args<const N: usize>(
469 &self,
470 diagnostics: &mut Diagnostics,
471 ) -> [Persistence; N] {
472 let len = self.op_inst.generics.persistence_args.len();
473 if 0 != len && 1 != len && N != len {
474 diagnostics.push(Diagnostic::spanned(
475 self.op_span,
476 Level::Error,
477 format!(
478 "The operator `{}` only accepts 0, 1, or {} persistence arguments",
479 self.op_name, N
480 ),
481 ));
482 }
483
484 let mut out = [Persistence::Tick; N];
485 self.op_inst
486 .generics
487 .persistence_args
488 .iter()
489 .copied()
490 .cycle() .take(N)
492 .enumerate()
493 .for_each(|(i, p)| {
494 out[i] = p;
495 });
496 out
497 }
498}
499
500pub trait RangeTrait<T>: Send + Sync + Debug
502where
503 T: ?Sized,
504{
505 fn start_bound(&self) -> Bound<&T>;
507 fn end_bound(&self) -> Bound<&T>;
509 fn contains(&self, item: &T) -> bool
511 where
512 T: PartialOrd<T>;
513
514 fn human_string(&self) -> String
516 where
517 T: Display + PartialEq,
518 {
519 match (self.start_bound(), self.end_bound()) {
520 (Bound::Unbounded, Bound::Unbounded) => "any number of".to_owned(),
521
522 (Bound::Included(n), Bound::Included(x)) if n == x => {
523 format!("exactly {}", n)
524 }
525 (Bound::Included(n), Bound::Included(x)) => {
526 format!("at least {} and at most {}", n, x)
527 }
528 (Bound::Included(n), Bound::Excluded(x)) => {
529 format!("at least {} and less than {}", n, x)
530 }
531 (Bound::Included(n), Bound::Unbounded) => format!("at least {}", n),
532 (Bound::Excluded(n), Bound::Included(x)) => {
533 format!("more than {} and at most {}", n, x)
534 }
535 (Bound::Excluded(n), Bound::Excluded(x)) => {
536 format!("more than {} and less than {}", n, x)
537 }
538 (Bound::Excluded(n), Bound::Unbounded) => format!("more than {}", n),
539 (Bound::Unbounded, Bound::Included(x)) => format!("at most {}", x),
540 (Bound::Unbounded, Bound::Excluded(x)) => format!("less than {}", x),
541 }
542 }
543}
544
545impl<R, T> RangeTrait<T> for R
546where
547 R: RangeBounds<T> + Send + Sync + Debug,
548{
549 fn start_bound(&self) -> Bound<&T> {
550 self.start_bound()
551 }
552
553 fn end_bound(&self) -> Bound<&T> {
554 self.end_bound()
555 }
556
557 fn contains(&self, item: &T) -> bool
558 where
559 T: PartialOrd<T>,
560 {
561 self.contains(item)
562 }
563}
564
565#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug, Serialize, Deserialize)]
567pub enum Persistence {
568 Tick,
570 Static,
572}
573impl Persistence {
574 pub fn to_str_lowercase(self) -> &'static str {
576 match self {
577 Persistence::Tick => "tick",
578 Persistence::Static => "static",
579 }
580 }
581}
582
583fn make_missing_runtime_msg(op_name: &str) -> Literal {
585 Literal::string(&format!(
586 "`{}()` must be used within a Tokio runtime. For example, use `#[dfir_rs::main]` on your main method.",
587 op_name
588 ))
589}
590
591#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, DocumentedVariants)]
593pub enum OperatorCategory {
594 Map,
596 Filter,
598 Flatten,
600 Fold,
602 KeyedFold,
604 LatticeFold,
606 Persistence,
608 MultiIn,
610 MultiOut,
612 Source,
614 Sink,
616 Control,
618 CompilerFusionOperator,
620 Windowing,
622 Unwindowing,
624}
625impl OperatorCategory {
626 pub fn name(self) -> &'static str {
628 self.get_variant_docs().split_once(':').unwrap().0
629 }
630 pub fn description(self) -> &'static str {
632 self.get_variant_docs().split_once(':').unwrap().1
633 }
634}
635
636#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Debug)]
638pub enum FloType {
639 Source,
641 Windowing,
643 WindowingLazy,
646 WindowingEager,
650 Unwindowing,
652}