osd_core/
ast.rs

1//! AST definitions for sequence diagrams
2
3/// Diagram options (parsed from option directives)
4#[derive(Debug, Clone, Default, PartialEq)]
5pub struct DiagramOptions {
6    /// Footer style
7    pub footer: FooterStyle,
8}
9
10/// A complete sequence diagram
11#[derive(Debug, Clone, PartialEq)]
12pub struct Diagram {
13    /// Optional title
14    pub title: Option<String>,
15    /// Diagram items (messages, notes, blocks, etc.)
16    pub items: Vec<Item>,
17    /// Diagram options
18    pub options: DiagramOptions,
19}
20
21impl Diagram {
22    /// Extract all participants from the diagram in order of appearance
23    pub fn participants(&self) -> Vec<Participant> {
24        let mut participants = Vec::new();
25        let mut seen = std::collections::HashSet::new();
26
27        fn add_participant(
28            name: &str,
29            alias: Option<&str>,
30            kind: ParticipantKind,
31            participants: &mut Vec<Participant>,
32            seen: &mut std::collections::HashSet<String>,
33        ) {
34            let key = alias.unwrap_or(name).to_string();
35            if !seen.contains(&key) {
36                seen.insert(key.clone());
37                participants.push(Participant {
38                    name: name.to_string(),
39                    alias: alias.map(|s| s.to_string()),
40                    kind,
41                });
42            }
43        }
44
45        fn collect_from_items(
46            items: &[Item],
47            participants: &mut Vec<Participant>,
48            seen: &mut std::collections::HashSet<String>,
49        ) {
50            for item in items {
51                match item {
52                    Item::ParticipantDecl { name, alias, kind } => {
53                        add_participant(name, alias.as_deref(), *kind, participants, seen);
54                    }
55                    Item::Message { from, to, .. } => {
56                        // Skip boundary markers [ and ]
57                        if from != "[" && from != "]" {
58                            add_participant(
59                                from,
60                                None,
61                                ParticipantKind::Participant,
62                                participants,
63                                seen,
64                            );
65                        }
66                        if to != "[" && to != "]" {
67                            add_participant(to, None, ParticipantKind::Participant, participants, seen);
68                        }
69                    }
70                    Item::Note { participants: note_participants, .. } => {
71                        for p in note_participants {
72                            add_participant(p, None, ParticipantKind::Participant, participants, seen);
73                        }
74                    }
75                    Item::State { participants: state_participants, .. } => {
76                        for p in state_participants {
77                            add_participant(p, None, ParticipantKind::Participant, participants, seen);
78                        }
79                    }
80                    Item::Ref { participants: ref_participants, input_from, output_to, .. } => {
81                        // Add input_from first (e.g., Alice in "Alice->ref over Bob, Mary")
82                        if let Some(from) = input_from {
83                            add_participant(from, None, ParticipantKind::Participant, participants, seen);
84                        }
85                        // Then add ref participants (e.g., Bob, Mary)
86                        for p in ref_participants {
87                            add_participant(p, None, ParticipantKind::Participant, participants, seen);
88                        }
89                        // Finally add output_to if different
90                        if let Some(to) = output_to {
91                            add_participant(to, None, ParticipantKind::Participant, participants, seen);
92                        }
93                    }
94                    Item::Activate { participant } | Item::Deactivate { participant } | Item::Destroy { participant } => {
95                        add_participant(participant, None, ParticipantKind::Participant, participants, seen);
96                    }
97                    Item::Block {
98                        items, else_sections, ..
99                    } => {
100                        collect_from_items(items, participants, seen);
101                        for else_section in else_sections {
102                            collect_from_items(&else_section.items, participants, seen);
103                        }
104                    }
105                    _ => {}
106                }
107            }
108        }
109
110        collect_from_items(&self.items, &mut participants, &mut seen);
111        participants
112    }
113}
114
115/// A participant in the sequence diagram
116#[derive(Debug, Clone, PartialEq)]
117pub struct Participant {
118    /// Display name
119    pub name: String,
120    /// Optional short alias
121    pub alias: Option<String>,
122    /// Kind of participant (actor or regular)
123    pub kind: ParticipantKind,
124}
125
126impl Participant {
127    /// Get the identifier used in messages (alias if present, otherwise name)
128    pub fn id(&self) -> &str {
129        self.alias.as_deref().unwrap_or(&self.name)
130    }
131}
132
133/// Kind of participant
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135pub enum ParticipantKind {
136    /// Regular participant (box)
137    Participant,
138    /// Actor (stick figure)
139    Actor,
140}
141
142/// A diagram item
143#[derive(Debug, Clone, PartialEq)]
144pub enum Item {
145    /// Participant declaration
146    ParticipantDecl {
147        name: String,
148        alias: Option<String>,
149        kind: ParticipantKind,
150    },
151    /// Message between participants
152    Message {
153        from: String,
154        to: String,
155        text: String,
156        arrow: Arrow,
157        /// Activate the receiver
158        activate: bool,
159        /// Deactivate the sender
160        deactivate: bool,
161        /// Create the receiver
162        create: bool,
163    },
164    /// Note
165    Note {
166        position: NotePosition,
167        participants: Vec<String>,
168        text: String,
169    },
170    /// Activate a participant
171    Activate { participant: String },
172    /// Deactivate a participant
173    Deactivate { participant: String },
174    /// Destroy a participant
175    Destroy { participant: String },
176    /// Block (alt, opt, loop, par)
177    Block {
178        kind: BlockKind,
179        label: String,
180        items: Vec<Item>,
181        /// Multiple else sections (for alt blocks with multiple else branches)
182        else_sections: Vec<ElseSection>,
183    },
184    /// Autonumber control
185    Autonumber { enabled: bool, start: Option<u32> },
186    /// State box (rounded rectangle)
187    State {
188        participants: Vec<String>,
189        text: String,
190    },
191    /// Reference box
192    Ref {
193        participants: Vec<String>,
194        text: String,
195        /// Input signal sender (for A->ref over B: label syntax)
196        input_from: Option<String>,
197        /// Input signal label
198        input_label: Option<String>,
199        /// Output signal receiver (for end ref-->A: label syntax)
200        output_to: Option<String>,
201        /// Output signal label
202        output_label: Option<String>,
203    },
204    /// Diagram option
205    DiagramOption { key: String, value: String },
206    /// Extended text description (indented comment)
207    Description { text: String },
208}
209
210/// Arrow style
211#[derive(Debug, Clone, Copy, PartialEq, Eq)]
212pub struct Arrow {
213    /// Line style
214    pub line: LineStyle,
215    /// Arrowhead style
216    pub head: ArrowHead,
217    /// Delay amount (for `->(n)` syntax)
218    pub delay: Option<u32>,
219}
220
221impl Arrow {
222    pub const SYNC: Arrow = Arrow {
223        line: LineStyle::Solid,
224        head: ArrowHead::Filled,
225        delay: None,
226    };
227
228    pub const SYNC_OPEN: Arrow = Arrow {
229        line: LineStyle::Solid,
230        head: ArrowHead::Open,
231        delay: None,
232    };
233
234    pub const RESPONSE: Arrow = Arrow {
235        line: LineStyle::Dashed,
236        head: ArrowHead::Filled,
237        delay: None,
238    };
239
240    pub const RESPONSE_OPEN: Arrow = Arrow {
241        line: LineStyle::Dashed,
242        head: ArrowHead::Open,
243        delay: None,
244    };
245}
246
247/// Line style
248#[derive(Debug, Clone, Copy, PartialEq, Eq)]
249pub enum LineStyle {
250    /// Solid line (`->`)
251    Solid,
252    /// Dashed line (`-->`)
253    Dashed,
254}
255
256/// Arrowhead style
257#[derive(Debug, Clone, Copy, PartialEq, Eq)]
258pub enum ArrowHead {
259    /// Filled arrowhead (`->`)
260    Filled,
261    /// Open arrowhead (`->>`)
262    Open,
263}
264
265/// Note position
266#[derive(Debug, Clone, Copy, PartialEq, Eq)]
267pub enum NotePosition {
268    /// Left of participant
269    Left,
270    /// Right of participant
271    Right,
272    /// Over participant(s)
273    Over,
274}
275
276/// Footer style for diagram (controlled by option footer=xxx)
277#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
278pub enum FooterStyle {
279    /// No footer at all
280    None,
281    /// Simple horizontal line
282    Bar,
283    /// Participant boxes at bottom (default, WSD compatible)
284    #[default]
285    Box,
286}
287
288/// Block kind
289#[derive(Debug, Clone, Copy, PartialEq, Eq)]
290pub enum BlockKind {
291    /// Alternative (if/else)
292    Alt,
293    /// Optional
294    Opt,
295    /// Loop
296    Loop,
297    /// Parallel
298    Par,
299    /// Sequential (inside par)
300    Seq,
301    /// Parallel with braces syntax
302    Parallel,
303    /// Serial with braces syntax
304    Serial,
305}
306
307impl BlockKind {
308    pub fn as_str(&self) -> &'static str {
309        match self {
310            BlockKind::Alt => "alt",
311            BlockKind::Opt => "opt",
312            BlockKind::Loop => "loop",
313            BlockKind::Par => "par",
314            BlockKind::Seq => "seq",
315            BlockKind::Parallel => "parallel",
316            BlockKind::Serial => "serial",
317        }
318    }
319}
320
321/// An else section within a block (for alt/opt with multiple else branches)
322#[derive(Debug, Clone, PartialEq)]
323pub struct ElseSection {
324    /// Optional label for this else section
325    pub label: Option<String>,
326    /// Items in this else section
327    pub items: Vec<Item>,
328}