Skip to main content

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}