Skip to main content

libpetri_core/
input.rs

1use std::any::Any;
2use std::sync::Arc;
3
4use crate::place::{Place, PlaceRef};
5
6/// Type-erased guard function.
7pub type GuardFn = Arc<dyn Fn(&dyn Any) -> bool + Send + Sync>;
8
9/// Input specification with cardinality and optional guard predicate.
10///
11/// CPN-compliant: cardinality determines how many tokens to consume,
12/// guard filters which tokens are eligible.
13///
14/// Inputs are always AND-joined (all must be satisfied to enable transition).
15#[derive(Clone)]
16pub enum In {
17    /// Consume exactly 1 token (standard CPN semantics).
18    One {
19        place: PlaceRef,
20        guard: Option<GuardFn>,
21    },
22    /// Consume exactly N tokens (batching).
23    Exactly {
24        place: PlaceRef,
25        count: usize,
26        guard: Option<GuardFn>,
27    },
28    /// Consume all available tokens (must be 1+).
29    All {
30        place: PlaceRef,
31        guard: Option<GuardFn>,
32    },
33    /// Wait for N+ tokens, consume all when enabled.
34    AtLeast {
35        place: PlaceRef,
36        minimum: usize,
37        guard: Option<GuardFn>,
38    },
39}
40
41impl In {
42    /// Returns the place reference for this input spec.
43    pub fn place(&self) -> &PlaceRef {
44        match self {
45            In::One { place, .. }
46            | In::Exactly { place, .. }
47            | In::All { place, .. }
48            | In::AtLeast { place, .. } => place,
49        }
50    }
51
52    /// Returns the place name.
53    pub fn place_name(&self) -> &str {
54        self.place().name()
55    }
56
57    /// Returns whether this input spec has a guard.
58    pub fn has_guard(&self) -> bool {
59        match self {
60            In::One { guard, .. }
61            | In::Exactly { guard, .. }
62            | In::All { guard, .. }
63            | In::AtLeast { guard, .. } => guard.is_some(),
64        }
65    }
66
67    /// Returns the guard function, if any.
68    pub fn guard(&self) -> Option<&GuardFn> {
69        match self {
70            In::One { guard, .. }
71            | In::Exactly { guard, .. }
72            | In::All { guard, .. }
73            | In::AtLeast { guard, .. } => guard.as_ref(),
74        }
75    }
76}
77
78impl std::fmt::Debug for In {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        match self {
81            In::One { place, guard } => f
82                .debug_struct("One")
83                .field("place", place)
84                .field("has_guard", &guard.is_some())
85                .finish(),
86            In::Exactly {
87                place,
88                count,
89                guard,
90            } => f
91                .debug_struct("Exactly")
92                .field("place", place)
93                .field("count", count)
94                .field("has_guard", &guard.is_some())
95                .finish(),
96            In::All { place, guard } => f
97                .debug_struct("All")
98                .field("place", place)
99                .field("has_guard", &guard.is_some())
100                .finish(),
101            In::AtLeast {
102                place,
103                minimum,
104                guard,
105            } => f
106                .debug_struct("AtLeast")
107                .field("place", place)
108                .field("minimum", minimum)
109                .field("has_guard", &guard.is_some())
110                .finish(),
111        }
112    }
113}
114
115// ==================== Factory Functions ====================
116
117/// Consume exactly 1 token from the place.
118pub fn one<T: 'static>(place: &Place<T>) -> In {
119    In::One {
120        place: place.as_ref(),
121        guard: None,
122    }
123}
124
125/// Consume exactly 1 token matching the guard.
126pub fn one_guarded<T: Send + Sync + 'static>(
127    place: &Place<T>,
128    guard: impl Fn(&T) -> bool + Send + Sync + 'static,
129) -> In {
130    In::One {
131        place: place.as_ref(),
132        guard: Some(Arc::new(move |v: &dyn Any| {
133            v.downcast_ref::<T>().is_some_and(&guard)
134        })),
135    }
136}
137
138/// Consume exactly N tokens from the place.
139///
140/// # Panics
141/// Panics if `count` is less than 1.
142pub fn exactly<T: 'static>(count: usize, place: &Place<T>) -> In {
143    assert!(count >= 1, "count must be >= 1, got: {count}");
144    In::Exactly {
145        place: place.as_ref(),
146        count,
147        guard: None,
148    }
149}
150
151/// Consume exactly N tokens matching the guard.
152///
153/// # Panics
154/// Panics if `count` is less than 1.
155pub fn exactly_guarded<T: Send + Sync + 'static>(
156    count: usize,
157    place: &Place<T>,
158    guard: impl Fn(&T) -> bool + Send + Sync + 'static,
159) -> In {
160    assert!(count >= 1, "count must be >= 1, got: {count}");
161    In::Exactly {
162        place: place.as_ref(),
163        count,
164        guard: Some(Arc::new(move |v: &dyn Any| {
165            v.downcast_ref::<T>().is_some_and(&guard)
166        })),
167    }
168}
169
170/// Consume all available tokens (must be 1+).
171pub fn all<T: 'static>(place: &Place<T>) -> In {
172    In::All {
173        place: place.as_ref(),
174        guard: None,
175    }
176}
177
178/// Consume all available tokens matching the guard.
179pub fn all_guarded<T: Send + Sync + 'static>(
180    place: &Place<T>,
181    guard: impl Fn(&T) -> bool + Send + Sync + 'static,
182) -> In {
183    In::All {
184        place: place.as_ref(),
185        guard: Some(Arc::new(move |v: &dyn Any| {
186            v.downcast_ref::<T>().is_some_and(&guard)
187        })),
188    }
189}
190
191/// Wait for N+ tokens, consume all when enabled.
192///
193/// # Panics
194/// Panics if `minimum` is less than 1.
195pub fn at_least<T: 'static>(minimum: usize, place: &Place<T>) -> In {
196    assert!(minimum >= 1, "minimum must be >= 1, got: {minimum}");
197    In::AtLeast {
198        place: place.as_ref(),
199        minimum,
200        guard: None,
201    }
202}
203
204/// Wait for N+ tokens matching guard, consume all when enabled.
205///
206/// # Panics
207/// Panics if `minimum` is less than 1.
208pub fn at_least_guarded<T: Send + Sync + 'static>(
209    minimum: usize,
210    place: &Place<T>,
211    guard: impl Fn(&T) -> bool + Send + Sync + 'static,
212) -> In {
213    assert!(minimum >= 1, "minimum must be >= 1, got: {minimum}");
214    In::AtLeast {
215        place: place.as_ref(),
216        minimum,
217        guard: Some(Arc::new(move |v: &dyn Any| {
218            v.downcast_ref::<T>().is_some_and(&guard)
219        })),
220    }
221}
222
223// ==================== Helper Functions ====================
224
225/// Returns the minimum number of tokens required to enable.
226pub fn required_count(spec: &In) -> usize {
227    match spec {
228        In::One { .. } => 1,
229        In::Exactly { count, .. } => *count,
230        In::All { .. } => 1,
231        In::AtLeast { minimum, .. } => *minimum,
232    }
233}
234
235/// Returns the actual number of tokens to consume given the available count.
236///
237/// # Panics
238/// Panics if `available` is less than the required count.
239pub fn consumption_count(spec: &In, available: usize) -> usize {
240    let required = required_count(spec);
241    assert!(
242        available >= required,
243        "Cannot consume from '{}': available={}, required={}",
244        spec.place_name(),
245        available,
246        required
247    );
248    match spec {
249        In::One { .. } => 1,
250        In::Exactly { count, .. } => *count,
251        In::All { .. } => available,
252        In::AtLeast { .. } => available,
253    }
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259
260    #[test]
261    fn one_required_count() {
262        let p = Place::<i32>::new("p");
263        let spec = one(&p);
264        assert_eq!(required_count(&spec), 1);
265    }
266
267    #[test]
268    fn exactly_required_count() {
269        let p = Place::<i32>::new("p");
270        let spec = exactly(3, &p);
271        assert_eq!(required_count(&spec), 3);
272    }
273
274    #[test]
275    fn all_required_count() {
276        let p = Place::<i32>::new("p");
277        let spec = all(&p);
278        assert_eq!(required_count(&spec), 1);
279    }
280
281    #[test]
282    fn at_least_required_count() {
283        let p = Place::<i32>::new("p");
284        let spec = at_least(5, &p);
285        assert_eq!(required_count(&spec), 5);
286    }
287
288    #[test]
289    fn consumption_count_one() {
290        let p = Place::<i32>::new("p");
291        let spec = one(&p);
292        assert_eq!(consumption_count(&spec, 3), 1);
293    }
294
295    #[test]
296    fn consumption_count_exactly() {
297        let p = Place::<i32>::new("p");
298        let spec = exactly(3, &p);
299        assert_eq!(consumption_count(&spec, 5), 3);
300    }
301
302    #[test]
303    fn consumption_count_all() {
304        let p = Place::<i32>::new("p");
305        let spec = all(&p);
306        assert_eq!(consumption_count(&spec, 7), 7);
307    }
308
309    #[test]
310    fn consumption_count_at_least() {
311        let p = Place::<i32>::new("p");
312        let spec = at_least(3, &p);
313        assert_eq!(consumption_count(&spec, 5), 5);
314    }
315
316    #[test]
317    #[should_panic(expected = "count must be >= 1")]
318    fn exactly_zero_panics() {
319        let p = Place::<i32>::new("p");
320        exactly(0, &p);
321    }
322
323    #[test]
324    #[should_panic(expected = "minimum must be >= 1")]
325    fn at_least_zero_panics() {
326        let p = Place::<i32>::new("p");
327        at_least(0, &p);
328    }
329
330    #[test]
331    #[should_panic(expected = "Cannot consume")]
332    fn consumption_count_insufficient_panics() {
333        let p = Place::<i32>::new("p");
334        let spec = exactly(3, &p);
335        consumption_count(&spec, 2);
336    }
337
338    #[test]
339    fn guarded_input() {
340        let p = Place::<i32>::new("p");
341        let spec = one_guarded(&p, |v| *v > 5);
342        assert!(spec.has_guard());
343        let guard = spec.guard().unwrap();
344        assert!(guard(&10i32 as &dyn Any));
345        assert!(!guard(&3i32 as &dyn Any));
346    }
347}