libpetri_verification/
marking_state.rs1use std::collections::HashMap;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct MarkingState {
8 tokens: HashMap<String, usize>,
9}
10
11impl MarkingState {
12 pub fn new() -> Self {
13 Self {
14 tokens: HashMap::new(),
15 }
16 }
17
18 pub fn from_map(tokens: HashMap<String, usize>) -> Self {
19 let tokens = tokens.into_iter().filter(|(_, c)| *c > 0).collect();
20 Self { tokens }
21 }
22
23 pub fn count(&self, place: &str) -> usize {
25 self.tokens.get(place).copied().unwrap_or(0)
26 }
27
28 pub fn places(&self) -> impl Iterator<Item = (&str, usize)> {
30 self.tokens.iter().map(|(k, v)| (k.as_str(), *v))
31 }
32
33 pub fn is_empty(&self) -> bool {
35 self.tokens.is_empty()
36 }
37
38 pub fn total_tokens(&self) -> usize {
40 self.tokens.values().sum()
41 }
42
43 pub fn has_tokens_in_any(&self, place_names: &[&str]) -> bool {
45 place_names.iter().any(|name| self.count(name) > 0)
46 }
47
48 pub fn canonical_key(&self) -> String {
50 let mut entries: Vec<_> = self.tokens.iter().collect();
51 entries.sort_by_key(|(k, _)| k.as_str());
52 entries
53 .iter()
54 .map(|(k, v)| format!("{k}:{v}"))
55 .collect::<Vec<_>>()
56 .join(",")
57 }
58}
59
60impl Default for MarkingState {
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66pub struct MarkingStateBuilder {
68 tokens: HashMap<String, usize>,
69}
70
71impl MarkingStateBuilder {
72 pub fn new() -> Self {
73 Self {
74 tokens: HashMap::new(),
75 }
76 }
77
78 pub fn tokens(mut self, place: impl Into<String>, count: usize) -> Self {
79 let key = place.into();
80 if count > 0 {
81 self.tokens.insert(key, count);
82 } else {
83 self.tokens.remove(&key);
84 }
85 self
86 }
87
88 pub fn add_tokens(mut self, place: impl Into<String>, count: usize) -> Self {
89 let entry = self.tokens.entry(place.into()).or_insert(0);
90 *entry += count;
91 self
92 }
93
94 pub fn build(self) -> MarkingState {
95 MarkingState::from_map(self.tokens)
96 }
97}
98
99impl Default for MarkingStateBuilder {
100 fn default() -> Self {
101 Self::new()
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn marking_state_basics() {
111 let ms = MarkingStateBuilder::new()
112 .tokens("p1", 2)
113 .tokens("p2", 1)
114 .build();
115
116 assert_eq!(ms.count("p1"), 2);
117 assert_eq!(ms.count("p2"), 1);
118 assert_eq!(ms.count("p3"), 0);
119 assert_eq!(ms.total_tokens(), 3);
120 assert!(!ms.is_empty());
121 }
122
123 #[test]
124 fn canonical_key_deterministic() {
125 let ms1 = MarkingStateBuilder::new()
126 .tokens("b", 1)
127 .tokens("a", 2)
128 .build();
129 let ms2 = MarkingStateBuilder::new()
130 .tokens("a", 2)
131 .tokens("b", 1)
132 .build();
133 assert_eq!(ms1.canonical_key(), ms2.canonical_key());
134 }
135
136 #[test]
137 fn empty_marking() {
138 let ms = MarkingState::new();
139 assert!(ms.is_empty());
140 assert_eq!(ms.total_tokens(), 0);
141 assert_eq!(ms.canonical_key(), "");
142 }
143
144 #[test]
145 fn zero_count_filtered() {
146 let ms = MarkingStateBuilder::new()
147 .tokens("p1", 0)
148 .tokens("p2", 1)
149 .build();
150 assert_eq!(ms.count("p1"), 0);
151 assert_eq!(ms.count("p2"), 1);
152 assert_eq!(ms.total_tokens(), 1);
153 }
154
155 #[test]
156 fn add_tokens_accumulates() {
157 let ms = MarkingStateBuilder::new()
158 .add_tokens("p1", 2)
159 .add_tokens("p1", 3)
160 .build();
161 assert_eq!(ms.count("p1"), 5);
162 }
163
164 #[test]
165 fn equality() {
166 let ms1 = MarkingStateBuilder::new().tokens("p", 2).build();
167 let ms2 = MarkingStateBuilder::new().tokens("p", 2).build();
168 assert_eq!(ms1, ms2);
169 }
170
171 #[test]
172 fn inequality_different_count() {
173 let ms1 = MarkingStateBuilder::new().tokens("p", 1).build();
174 let ms2 = MarkingStateBuilder::new().tokens("p", 2).build();
175 assert_ne!(ms1, ms2);
176 }
177}