prebindgen_flat/shape.rs
1//! `Shape<N>` — a leaf wrapped in an ordered stack of structural layers
2//! (`Option`, `Vec`), with a bottom-up [`fold_shape`] combinator.
3//!
4//! One shared algebra replaces the three former per-module copies of the same
5//! "leaf + wrapper layers" idea:
6//! * jnigen's `FoldStrategy` (`Direct` / `Nullable { kind, inner }` /
7//! `Iterable`) → `Shape<NullableKind>` (the `Optional` layer carries the
8//! null-representation choice),
9//! * expand's `FoldShape` (`Construct` / `Optional` / `Iterable`) → `Shape`
10//! (= `Shape<()>`),
11//! * unfold's `UnfoldShape` (`Decompose` / `Optional` / `Iterable`) →
12//! `Shape`.
13//!
14//! `N` is the per-`Optional`-layer payload: `()` for the language-agnostic
15//! engines, an adapter type (e.g. jnigen's `NullableKind`) where the layer
16//! needs to remember how null is represented over the wire.
17
18/// A base leaf wrapped in zero or more `Optional` / `Iterable` layers, from the
19/// inside out. `N` is the payload each `Optional` layer carries.
20#[derive(Clone, Debug)]
21pub enum Shape<N = ()> {
22 /// The leaf — no wrapping layers.
23 Base,
24 /// `Option<…>` layer over `inner`. `meta` is this layer's payload.
25 Optional(N, Box<Shape<N>>),
26 /// `Vec<…>` / `List<…>` layer over `inner`.
27 Iterable(Box<Shape<N>>),
28}
29
30impl<N> Shape<N> {
31 /// `Optional(meta, inner)` without the explicit `Box`.
32 pub fn optional(meta: N, inner: Shape<N>) -> Self {
33 Shape::Optional(meta, Box::new(inner))
34 }
35
36 /// `Iterable(inner)` without the explicit `Box`.
37 pub fn iterable(inner: Shape<N>) -> Self {
38 Shape::Iterable(Box::new(inner))
39 }
40
41 /// True when any layer of the stack is `Iterable`. This is the
42 /// fold-delivery discriminator: a fold surface (accumulator + per-element
43 /// callback) is selected whether or not `Optional` layers wrap the
44 /// iterable, and an iterable-shaped value has no single return.
45 pub fn has_iterable_layer(&self) -> bool {
46 match self {
47 Shape::Base => false,
48 Shape::Optional(_, inner) => inner.has_iterable_layer(),
49 Shape::Iterable(_) => true,
50 }
51 }
52}
53
54/// Bottom-up fold over the layer stack: compute the leaf value with `on_base`,
55/// then apply `on_optional` / `on_iterable` for each wrapping layer from the
56/// inside out. `on_optional` also receives the layer's payload `&N` and the
57/// `Shape` it wraps, so callers can special-case e.g. a layer sitting directly
58/// over the leaf.
59///
60/// This is the generalization of jnigen's former `fold_strategy`.
61pub fn fold_shape<N, T>(
62 s: &Shape<N>,
63 on_base: &dyn Fn() -> T,
64 on_optional: &dyn Fn(T, &N, &Shape<N>) -> T,
65 on_iterable: &dyn Fn(T) -> T,
66) -> T {
67 match s {
68 Shape::Base => on_base(),
69 Shape::Optional(meta, inner) => {
70 let inner_val = fold_shape(inner, on_base, on_optional, on_iterable);
71 on_optional(inner_val, meta, inner)
72 }
73 Shape::Iterable(inner) => {
74 let inner_val = fold_shape(inner, on_base, on_optional, on_iterable);
75 on_iterable(inner_val)
76 }
77 }
78}