Skip to main content

sim_codec_bridge/
profile.rs

1use std::collections::BTreeMap;
2
3use sim_kernel::{Expr, Symbol};
4use sim_value::build::entry;
5
6use crate::BridgePacket;
7
8/// Repetition rule for a profile part kind.
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum ProfilePartCount {
11    /// Zero or more parts.
12    ZeroOrMore,
13    /// One or more parts.
14    OneOrMore,
15    /// Zero or one part.
16    Optional,
17    /// Exactly one part.
18    Required,
19}
20
21/// One part-kind rule in a BRIDGE profile.
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct ProfilePartRule {
24    /// Required part kind.
25    pub kind: Symbol,
26    /// Repetition rule.
27    pub count: ProfilePartCount,
28}
29
30impl ProfilePartRule {
31    /// Builds a profile part rule.
32    pub fn new(kind: Symbol, count: ProfilePartCount) -> Self {
33        Self { kind, count }
34    }
35}
36
37/// Registered BRIDGE profile.
38#[derive(Clone, Debug, PartialEq, Eq)]
39pub struct BridgeProfileSpec {
40    /// Profile id.
41    pub id: Symbol,
42    /// Ordered part-kind rules.
43    pub parts: Vec<ProfilePartRule>,
44    /// Alternative part kinds accepted one-or-more times in any order.
45    pub one_or_more_of: Vec<Symbol>,
46}
47
48impl BridgeProfileSpec {
49    /// Builds a profile spec.
50    pub fn new(id: Symbol, parts: Vec<ProfilePartRule>) -> Self {
51        Self {
52            id,
53            parts,
54            one_or_more_of: Vec::new(),
55        }
56    }
57
58    /// Builds a profile spec accepting one or more of the listed part kinds.
59    pub fn one_or_more_of(id: Symbol, kinds: Vec<Symbol>) -> Self {
60        Self {
61            id,
62            parts: Vec::new(),
63            one_or_more_of: kinds,
64        }
65    }
66
67    /// Returns true when `packet` matches this ordered profile.
68    pub fn matches_packet(&self, packet: &BridgePacket) -> bool {
69        let kinds = packet
70            .body
71            .iter()
72            .map(|part| &part.kind)
73            .collect::<Vec<_>>();
74        if !self.one_or_more_of.is_empty() {
75            return !kinds.is_empty()
76                && kinds
77                    .iter()
78                    .all(|kind| self.one_or_more_of.iter().any(|accepted| accepted == *kind));
79        }
80        let mut cursor = 0usize;
81        for rule in &self.parts {
82            let mut count = 0usize;
83            while cursor < kinds.len() && kinds[cursor] == &rule.kind {
84                count += 1;
85                cursor += 1;
86            }
87            match rule.count {
88                ProfilePartCount::ZeroOrMore => {}
89                ProfilePartCount::OneOrMore if count == 0 => return false,
90                ProfilePartCount::OneOrMore => {}
91                ProfilePartCount::Optional if count > 1 => return false,
92                ProfilePartCount::Optional => {}
93                ProfilePartCount::Required if count != 1 => return false,
94                ProfilePartCount::Required => {}
95            }
96        }
97        cursor == kinds.len()
98    }
99
100    /// Encodes this profile as a public descriptor expression.
101    pub fn to_expr(&self) -> Expr {
102        Expr::Map(vec![
103            entry("id", Expr::Symbol(self.id.clone())),
104            entry(
105                "parts",
106                if self.one_or_more_of.is_empty() {
107                    Expr::Vector(
108                        self.parts
109                            .iter()
110                            .map(|rule| {
111                                Expr::Map(vec![
112                                    entry("kind", Expr::Symbol(rule.kind.clone())),
113                                    entry("count", Expr::Symbol(rule.count.symbol())),
114                                ])
115                            })
116                            .collect(),
117                    )
118                } else {
119                    Expr::Vector(vec![Expr::Map(vec![
120                        entry(
121                            "any-of",
122                            Expr::Vector(
123                                self.one_or_more_of
124                                    .iter()
125                                    .cloned()
126                                    .map(Expr::Symbol)
127                                    .collect(),
128                            ),
129                        ),
130                        entry("count", Expr::Symbol(ProfilePartCount::OneOrMore.symbol())),
131                    ])])
132                },
133            ),
134        ])
135    }
136}
137
138/// Registry of BRIDGE profile specifications.
139#[derive(Clone, Debug, Default, PartialEq, Eq)]
140pub struct BridgeProfileBook {
141    specs: BTreeMap<Symbol, BridgeProfileSpec>,
142}
143
144impl BridgeProfileBook {
145    /// Builds an empty profile book.
146    pub fn new() -> Self {
147        Self::default()
148    }
149
150    /// Registers a profile spec.
151    pub fn register(&mut self, spec: BridgeProfileSpec) {
152        self.specs.insert(spec.id.clone(), spec);
153    }
154
155    /// Returns the registered profile spec for `id`.
156    pub fn spec(&self, id: &Symbol) -> Option<&BridgeProfileSpec> {
157        self.specs.get(id)
158    }
159
160    /// Returns all registered profile specs.
161    pub fn specs(&self) -> impl Iterator<Item = &BridgeProfileSpec> {
162        self.specs.values()
163    }
164
165    /// Returns the matching profile ids for `packet`.
166    pub fn matching_profiles(&self, packet: &BridgePacket) -> Vec<Symbol> {
167        self.specs
168            .values()
169            .filter(|spec| spec.matches_packet(packet))
170            .map(|spec| spec.id.clone())
171            .collect()
172    }
173}
174
175impl ProfilePartCount {
176    fn symbol(self) -> Symbol {
177        match self {
178            Self::ZeroOrMore => Symbol::qualified("bridge", "ZeroOrMore"),
179            Self::OneOrMore => Symbol::qualified("bridge", "OneOrMore"),
180            Self::Optional => Symbol::qualified("bridge", "Optional"),
181            Self::Required => Symbol::qualified("bridge", "Required"),
182        }
183    }
184}
185
186/// Profile id for BRIEF packets.
187pub fn brief_profile_symbol() -> Symbol {
188    Symbol::qualified("bridge", "BRIEF")
189}
190
191/// Profile id for ASK packets.
192pub fn ask_profile_symbol() -> Symbol {
193    Symbol::qualified("bridge", "ASK")
194}
195
196/// Profile id for LOOM packets.
197pub fn loom_profile_symbol() -> Symbol {
198    Symbol::qualified("bridge", "LOOM")
199}
200
201/// Profile id for COLLAB packets.
202pub fn collab_profile_symbol() -> Symbol {
203    Symbol::qualified("bridge", "COLLAB")
204}
205
206/// Shape descriptor for the registered BRIDGE profile catalog.
207pub fn bridge_profile_shape_expr() -> Expr {
208    Expr::Map(vec![
209        entry("shape", Expr::Symbol(Symbol::qualified("shape", "OneOf"))),
210        entry(
211            "choices",
212            Expr::Vector(vec![
213                Expr::Symbol(brief_profile_symbol()),
214                Expr::Symbol(ask_profile_symbol()),
215                Expr::Symbol(loom_profile_symbol()),
216                Expr::Symbol(collab_profile_symbol()),
217            ]),
218        ),
219    ])
220}
221
222/// Builds the BRIEF profile spec: `Given* Frame+ Return?`.
223pub fn brief_profile_spec() -> BridgeProfileSpec {
224    BridgeProfileSpec::new(
225        brief_profile_symbol(),
226        vec![
227            ProfilePartRule::new(part("Given"), ProfilePartCount::ZeroOrMore),
228            ProfilePartRule::new(part("Frame"), ProfilePartCount::OneOrMore),
229            ProfilePartRule::new(part("Return"), ProfilePartCount::Optional),
230        ],
231    )
232}
233
234/// Builds the ASK profile spec: `Given* Frame* Call+ Return`.
235pub fn ask_profile_spec() -> BridgeProfileSpec {
236    BridgeProfileSpec::new(
237        ask_profile_symbol(),
238        vec![
239            ProfilePartRule::new(part("Given"), ProfilePartCount::ZeroOrMore),
240            ProfilePartRule::new(part("Frame"), ProfilePartCount::ZeroOrMore),
241            ProfilePartRule::new(part("Call"), ProfilePartCount::OneOrMore),
242            ProfilePartRule::new(part("Return"), ProfilePartCount::Required),
243        ],
244    )
245}
246
247/// Builds the LOOM profile spec: `Given* Frame* Weave+ Return`.
248pub fn loom_profile_spec() -> BridgeProfileSpec {
249    BridgeProfileSpec::new(
250        loom_profile_symbol(),
251        vec![
252            ProfilePartRule::new(part("Given"), ProfilePartCount::ZeroOrMore),
253            ProfilePartRule::new(part("Frame"), ProfilePartCount::ZeroOrMore),
254            ProfilePartRule::new(part("Weave"), ProfilePartCount::OneOrMore),
255            ProfilePartRule::new(part("Return"), ProfilePartCount::Required),
256        ],
257    )
258}
259
260/// Builds the COLLAB profile spec: `(Review|Vote|Patch|Evidence|Receipt|Attest)+`.
261pub fn collab_profile_spec() -> BridgeProfileSpec {
262    BridgeProfileSpec::one_or_more_of(
263        collab_profile_symbol(),
264        vec![
265            part("Review"),
266            part("Vote"),
267            part("Patch"),
268            part("Evidence"),
269            part("Receipt"),
270            part("Attest"),
271        ],
272    )
273}
274
275/// Builds the standard BRIDGE profile book.
276pub fn standard_profile_book() -> BridgeProfileBook {
277    let mut book = BridgeProfileBook::new();
278    book.register(brief_profile_spec());
279    book.register(ask_profile_spec());
280    book.register(loom_profile_spec());
281    book.register(collab_profile_spec());
282    book
283}
284
285fn part(name: &str) -> Symbol {
286    Symbol::qualified("bridge", name)
287}