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                        add_participant(
57                            from,
58                            None,
59                            ParticipantKind::Participant,
60                            participants,
61                            seen,
62                        );
63                        add_participant(to, None, ParticipantKind::Participant, participants, seen);
64                    }
65                    Item::Block {
66                        items, else_sections, ..
67                    } => {
68                        collect_from_items(items, participants, seen);
69                        for else_section in else_sections {
70                            collect_from_items(&else_section.items, participants, seen);
71                        }
72                    }
73                    _ => {}
74                }
75            }
76        }
77
78        collect_from_items(&self.items, &mut participants, &mut seen);
79        participants
80    }
81}
82
83/// A participant in the sequence diagram
84#[derive(Debug, Clone, PartialEq)]
85pub struct Participant {
86    /// Display name
87    pub name: String,
88    /// Optional short alias
89    pub alias: Option<String>,
90    /// Kind of participant (actor or regular)
91    pub kind: ParticipantKind,
92}
93
94impl Participant {
95    /// Get the identifier used in messages (alias if present, otherwise name)
96    pub fn id(&self) -> &str {
97        self.alias.as_deref().unwrap_or(&self.name)
98    }
99}
100
101/// Kind of participant
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum ParticipantKind {
104    /// Regular participant (box)
105    Participant,
106    /// Actor (stick figure)
107    Actor,
108}
109
110/// A diagram item
111#[derive(Debug, Clone, PartialEq)]
112pub enum Item {
113    /// Participant declaration
114    ParticipantDecl {
115        name: String,
116        alias: Option<String>,
117        kind: ParticipantKind,
118    },
119    /// Message between participants
120    Message {
121        from: String,
122        to: String,
123        text: String,
124        arrow: Arrow,
125        /// Activate the receiver
126        activate: bool,
127        /// Deactivate the sender
128        deactivate: bool,
129        /// Create the receiver
130        create: bool,
131    },
132    /// Note
133    Note {
134        position: NotePosition,
135        participants: Vec<String>,
136        text: String,
137    },
138    /// Activate a participant
139    Activate { participant: String },
140    /// Deactivate a participant
141    Deactivate { participant: String },
142    /// Destroy a participant
143    Destroy { participant: String },
144    /// Block (alt, opt, loop, par)
145    Block {
146        kind: BlockKind,
147        label: String,
148        items: Vec<Item>,
149        /// Multiple else sections (for alt blocks with multiple else branches)
150        else_sections: Vec<ElseSection>,
151    },
152    /// Autonumber control
153    Autonumber { enabled: bool, start: Option<u32> },
154    /// State box (rounded rectangle)
155    State {
156        participants: Vec<String>,
157        text: String,
158    },
159    /// Reference box
160    Ref {
161        participants: Vec<String>,
162        text: String,
163        /// Input signal sender (for A->ref over B: label syntax)
164        input_from: Option<String>,
165        /// Input signal label
166        input_label: Option<String>,
167        /// Output signal receiver (for end ref-->A: label syntax)
168        output_to: Option<String>,
169        /// Output signal label
170        output_label: Option<String>,
171    },
172    /// Diagram option
173    DiagramOption { key: String, value: String },
174    /// Extended text description (indented comment)
175    Description { text: String },
176}
177
178/// Arrow style
179#[derive(Debug, Clone, Copy, PartialEq, Eq)]
180pub struct Arrow {
181    /// Line style
182    pub line: LineStyle,
183    /// Arrowhead style
184    pub head: ArrowHead,
185    /// Delay amount (for `->(n)` syntax)
186    pub delay: Option<u32>,
187}
188
189impl Arrow {
190    pub const SYNC: Arrow = Arrow {
191        line: LineStyle::Solid,
192        head: ArrowHead::Filled,
193        delay: None,
194    };
195
196    pub const SYNC_OPEN: Arrow = Arrow {
197        line: LineStyle::Solid,
198        head: ArrowHead::Open,
199        delay: None,
200    };
201
202    pub const RESPONSE: Arrow = Arrow {
203        line: LineStyle::Dashed,
204        head: ArrowHead::Filled,
205        delay: None,
206    };
207
208    pub const RESPONSE_OPEN: Arrow = Arrow {
209        line: LineStyle::Dashed,
210        head: ArrowHead::Open,
211        delay: None,
212    };
213}
214
215/// Line style
216#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217pub enum LineStyle {
218    /// Solid line (`->`)
219    Solid,
220    /// Dashed line (`-->`)
221    Dashed,
222}
223
224/// Arrowhead style
225#[derive(Debug, Clone, Copy, PartialEq, Eq)]
226pub enum ArrowHead {
227    /// Filled arrowhead (`->`)
228    Filled,
229    /// Open arrowhead (`->>`)
230    Open,
231}
232
233/// Note position
234#[derive(Debug, Clone, Copy, PartialEq, Eq)]
235pub enum NotePosition {
236    /// Left of participant
237    Left,
238    /// Right of participant
239    Right,
240    /// Over participant(s)
241    Over,
242}
243
244/// Footer style for diagram (controlled by option footer=xxx)
245#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
246pub enum FooterStyle {
247    /// No footer at all
248    None,
249    /// Simple horizontal line
250    Bar,
251    /// Participant boxes at bottom (default, WSD compatible)
252    #[default]
253    Box,
254}
255
256/// Block kind
257#[derive(Debug, Clone, Copy, PartialEq, Eq)]
258pub enum BlockKind {
259    /// Alternative (if/else)
260    Alt,
261    /// Optional
262    Opt,
263    /// Loop
264    Loop,
265    /// Parallel
266    Par,
267    /// Sequential (inside par)
268    Seq,
269    /// Parallel with braces syntax
270    Parallel,
271    /// Serial with braces syntax
272    Serial,
273}
274
275impl BlockKind {
276    pub fn as_str(&self) -> &'static str {
277        match self {
278            BlockKind::Alt => "alt",
279            BlockKind::Opt => "opt",
280            BlockKind::Loop => "loop",
281            BlockKind::Par => "par",
282            BlockKind::Seq => "seq",
283            BlockKind::Parallel => "parallel",
284            BlockKind::Serial => "serial",
285        }
286    }
287}
288
289/// An else section within a block (for alt/opt with multiple else branches)
290#[derive(Debug, Clone, PartialEq)]
291pub struct ElseSection {
292    /// Optional label for this else section
293    pub label: Option<String>,
294    /// Items in this else section
295    pub items: Vec<Item>,
296}