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_items, ..
67                    } => {
68                        collect_from_items(items, participants, seen);
69                        if let Some(else_items) = else_items {
70                            collect_from_items(else_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        else_items: Option<Vec<Item>>,
150    },
151    /// Autonumber control
152    Autonumber { enabled: bool, start: Option<u32> },
153    /// State box (rounded rectangle)
154    State {
155        participants: Vec<String>,
156        text: String,
157    },
158    /// Reference box
159    Ref {
160        participants: Vec<String>,
161        text: String,
162        /// Input signal sender (for A->ref over B: label syntax)
163        input_from: Option<String>,
164        /// Input signal label
165        input_label: Option<String>,
166        /// Output signal receiver (for end ref-->A: label syntax)
167        output_to: Option<String>,
168        /// Output signal label
169        output_label: Option<String>,
170    },
171    /// Diagram option
172    DiagramOption { key: String, value: String },
173    /// Extended text description (indented comment)
174    Description { text: String },
175}
176
177/// Arrow style
178#[derive(Debug, Clone, Copy, PartialEq, Eq)]
179pub struct Arrow {
180    /// Line style
181    pub line: LineStyle,
182    /// Arrowhead style
183    pub head: ArrowHead,
184    /// Delay amount (for `->(n)` syntax)
185    pub delay: Option<u32>,
186}
187
188impl Arrow {
189    pub const SYNC: Arrow = Arrow {
190        line: LineStyle::Solid,
191        head: ArrowHead::Filled,
192        delay: None,
193    };
194
195    pub const SYNC_OPEN: Arrow = Arrow {
196        line: LineStyle::Solid,
197        head: ArrowHead::Open,
198        delay: None,
199    };
200
201    pub const RESPONSE: Arrow = Arrow {
202        line: LineStyle::Dashed,
203        head: ArrowHead::Filled,
204        delay: None,
205    };
206
207    pub const RESPONSE_OPEN: Arrow = Arrow {
208        line: LineStyle::Dashed,
209        head: ArrowHead::Open,
210        delay: None,
211    };
212}
213
214/// Line style
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
216pub enum LineStyle {
217    /// Solid line (`->`)
218    Solid,
219    /// Dashed line (`-->`)
220    Dashed,
221}
222
223/// Arrowhead style
224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
225pub enum ArrowHead {
226    /// Filled arrowhead (`->`)
227    Filled,
228    /// Open arrowhead (`->>`)
229    Open,
230}
231
232/// Note position
233#[derive(Debug, Clone, Copy, PartialEq, Eq)]
234pub enum NotePosition {
235    /// Left of participant
236    Left,
237    /// Right of participant
238    Right,
239    /// Over participant(s)
240    Over,
241}
242
243/// Footer style for diagram (controlled by option footer=xxx)
244#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
245pub enum FooterStyle {
246    /// No footer at all (default)
247    #[default]
248    None,
249    /// Simple horizontal line
250    Bar,
251    /// Participant boxes at bottom
252    Box,
253}
254
255/// Block kind
256#[derive(Debug, Clone, Copy, PartialEq, Eq)]
257pub enum BlockKind {
258    /// Alternative (if/else)
259    Alt,
260    /// Optional
261    Opt,
262    /// Loop
263    Loop,
264    /// Parallel
265    Par,
266    /// Sequential (inside par)
267    Seq,
268    /// Parallel with braces syntax
269    Parallel,
270    /// Serial with braces syntax
271    Serial,
272}
273
274impl BlockKind {
275    pub fn as_str(&self) -> &'static str {
276        match self {
277            BlockKind::Alt => "alt",
278            BlockKind::Opt => "opt",
279            BlockKind::Loop => "loop",
280            BlockKind::Par => "par",
281            BlockKind::Seq => "seq",
282            BlockKind::Parallel => "parallel",
283            BlockKind::Serial => "serial",
284        }
285    }
286}